Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix fragment helpers to handle fragments without spaces at the end (i.e. fr0, sql interpolator) #1912

Merged
merged 1 commit into from
Sep 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions modules/core/src/main/scala/doobie/util/fragments.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,22 @@ object fragments {

/** Returns `(f IN (fs0, fs1, ...))`, or `false` for empty `fs`. */
def in[F[_]: Reducible: Functor, A: util.Put](f: Fragment, fs: F[A]): Fragment =
parentheses(f ++ fr"IN" ++ parentheses(comma(fs.map(a => fr"$a"))))
parentheses(f ++ fr" IN" ++ parentheses(comma(fs.map(a => fr"$a"))))

def inOpt[F[_]: Foldable, A: util.Put](f: Fragment, fs: F[A]): Option[Fragment] =
NonEmptyList.fromFoldable(fs).map(nel => in(f, nel))

/** Returns `(f IN ((fs0-A, fs0-B), (fs1-A, fs1-B), ...))`, or `false` for empty `fs`. */
def in[F[_]: Reducible: Functor, A: util.Put, B: util.Put](f: Fragment, fs: F[(A,B)]): Fragment =
parentheses(f ++ fr"IN" ++ parentheses(comma(fs.map { case (a,b) => fr0"($a,$b)" })))
parentheses(f ++ fr" IN" ++ parentheses(comma(fs.map { case (a,b) => fr0"($a,$b)" })))

/** Returns `(f NOT IN (fs0, fs1, ...))`. */
def notIn[A: util.Put](f: Fragment, fs0: A, fs1: A, fs: A*): Fragment =
notIn(f, NonEmptyList(fs0, fs1 :: fs.toList))

/** Returns `(f NOT IN (fs0, fs1, ...))`, or `true` for empty `fs`. */
def notIn[F[_]: Reducible: Functor, A: util.Put](f: Fragment, fs: F[A]): Fragment = {
parentheses(f ++ fr"NOT IN" ++ parentheses(comma(fs.map(a => fr"$a"))))
parentheses(f ++ fr" NOT IN" ++ parentheses(comma(fs.map(a => fr"$a"))))
}

def notInOpt[F[_]: Foldable, A: util.Put](f: Fragment, fs: F[A]): Option[Fragment] = {
Expand All @@ -52,7 +52,7 @@ object fragments {
/** Returns `(f1 AND f2 AND ... fn)` for a non-empty collection.
* @param withParen If this is false, does not wrap the resulting expression with parenthesis */
def and[F[_]: Reducible](fs: F[Fragment], withParen: Boolean = true): Fragment = {
val expr = fs.nonEmptyIntercalate(fr"AND")
val expr = fs.nonEmptyIntercalate(fr" AND")
if (withParen) parentheses(expr) else expr
}

Expand All @@ -79,7 +79,7 @@ object fragments {
*
* @param withParen If this is false, does not wrap the resulting expression with parenthesis */
def or[F[_] : Reducible](fs: F[Fragment], withParen: Boolean = true): Fragment = {
val expr = fs.nonEmptyIntercalate(fr"OR")
val expr = fs.nonEmptyIntercalate(fr" OR")
if (withParen) parentheses(expr) else expr
}

Expand Down
96 changes: 48 additions & 48 deletions modules/core/src/test/scala/doobie/util/FragmentsSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ class FragmentsSuite extends munit.FunSuite {

val nelInt = NonEmptyList.of(1,2,3)
val listInt = nelInt.toList
val nel1 = NonEmptyList.of(1).map(i => fr"$i")
val nel = NonEmptyList.of(1,2,3).map(i => fr"$i")
val nel1 = NonEmptyList.of(1).map(i => sql"$i")
val nel = NonEmptyList.of(1,2,3).map(i => sql"$i")
val fs = nel.toList
val someF: Option[Fragment] = Some(fr"${1}")
val someF: Option[Fragment] = Some(sql"${1}")
val noneF: Option[Fragment] = None
val ofs = List(Some(fr"${1}"), None, Some(fr"${3}"))
val ofs = List(Some(sql"${1}"), None, Some(sql"${3}"))

test("values for one column") {
assertEquals(values(nelInt).query[Unit].sql, "VALUES (?) , (?) , (?) ")
Expand All @@ -40,83 +40,83 @@ class FragmentsSuite extends munit.FunSuite {
}

test("in (1-column varargs)") {
assertEquals(in(fr"foo", 1,2,3).query[Unit].sql, "(foo IN (? , ? , ? ) ) ")
assertEquals(in(sql"foo", 1,2,3).query[Unit].sql, "(foo IN (? , ? , ? ) ) ")
}

test("in (1-column Reducible many)") {
assertEquals(in(fr"foo", nelInt).query[Unit].sql, "(foo IN (? , ? , ? ) ) ")
assertEquals(in(sql"foo", nelInt).query[Unit].sql, "(foo IN (? , ? , ? ) ) ")
}

test("inOpt (1-column Reducible empty)") {
assertEquals(inOpt(fr"foo", List.empty[Int]).map(_.query[Unit].sql), None)
assertEquals(inOpt(sql"foo", List.empty[Int]).map(_.query[Unit].sql), None)
}

test("inOpt (1-column Reducible many)") {
assertEquals(inOpt(fr"foo", listInt).map(_.query[Unit].sql), Some("(foo IN (? , ? , ? ) ) "))
assertEquals(inOpt(sql"foo", listInt).map(_.query[Unit].sql), Some("(foo IN (? , ? , ? ) ) "))
}

test("in (2-column varargs)") {
assertEquals(in(fr"foo", NonEmptyList.of((1, true), (2, false))).query[Unit].sql, "(foo IN ((?,?), (?,?)) ) ")
assertEquals(in(sql"foo", NonEmptyList.of((1, true), (2, false))).query[Unit].sql, "(foo IN ((?,?), (?,?)) ) ")
}

test("notIn (varargs many)") {
assertEquals(notIn(fr"foo", 1, 2, 3).query[Unit].sql, "(foo NOT IN (? , ? , ? ) ) ")
assertEquals(notIn(sql"foo", 1, 2, 3).query[Unit].sql, "(foo NOT IN (? , ? , ? ) ) ")
}

test("notIn (Reducible 1)") {
assertEquals(notIn(fr"foo", NonEmptyList.of(1)).query[Unit].sql, "(foo NOT IN (? ) ) ")
assertEquals(notIn(sql"foo", NonEmptyList.of(1)).query[Unit].sql, "(foo NOT IN (? ) ) ")
}

test("notIn (Reducible many)") {
assertEquals(notIn(fr"foo", nelInt).query[Unit].sql, "(foo NOT IN (? , ? , ? ) ) ")
assertEquals(notIn(sql"foo", nelInt).query[Unit].sql, "(foo NOT IN (? , ? , ? ) ) ")
}

test("notInOpt (Foldable empty)") {
assertEquals(notInOpt(fr"foo", List.empty[Int]).map(_.query[Unit].sql), None)
assertEquals(notInOpt(sql"foo", List.empty[Int]).map(_.query[Unit].sql), None)
}

test("notInOpt (Foldable 1)") {
assertEquals(notInOpt(fr"foo", List(1)).map(_.query[Unit].sql), Some("(foo NOT IN (? ) ) "))
assertEquals(notInOpt(sql"foo", List(1)).map(_.query[Unit].sql), Some("(foo NOT IN (? ) ) "))
}

test("notInOpt (Foldable many)") {
assertEquals(notInOpt(fr"foo", listInt).map(_.query[Unit].sql), Some("(foo NOT IN (? , ? , ? ) ) "))
assertEquals(notInOpt(sql"foo", listInt).map(_.query[Unit].sql), Some("(foo NOT IN (? , ? , ? ) ) "))
}

test("and (vararg 2)") {
assertEquals(and(fs(0), fs(1)).query[Unit].sql, "(? AND ? ) ")
assertEquals(and(fs(0), fs(1)).query[Unit].sql, "(? AND ?) ")
}

test("and (Reducible 1)") {
assertEquals(and(nel1).query[Unit].sql, "(? ) ")
assertEquals(and(nel1).query[Unit].sql, "(?) ")
}

test("and (Reducible many)") {
assertEquals(and(nel).query[Unit].sql, "(? AND ? AND ? ) ")
assertEquals(and(nel).query[Unit].sql, "(? AND ? AND ?) ")
}

test("andOpt (vararg many none)") {
assertEquals(andOpt(None, None).map(_.query[Unit].sql), None)
}

test("andOpt (vararg 1 Some)") {
assertEquals(andOpt(noneF, someF).map(_.query[Unit].sql), Some("(? ) "))
assertEquals(andOpt(noneF, someF).map(_.query[Unit].sql), Some("(?) "))
}

test("andOpt (vararg 2 Some)") {
assertEquals(andOpt(someF, someF).map(_.query[Unit].sql), Some("(? AND ? ) "))
assertEquals(andOpt(someF, someF).map(_.query[Unit].sql), Some("(? AND ?) "))
}

test("andOpt (Foldable empty)") {
assertEquals(andOpt(List.empty[Fragment]).map(_.query[Unit].sql), None)
}

test("andOpt (Foldable 1)") {
assertEquals(andOpt(nel.take(1)).map(_.query[Unit].sql), Some("(? ) "))
assertEquals(andOpt(nel.take(1)).map(_.query[Unit].sql), Some("(?) "))
}

test("andOpt (Foldable many)") {
assertEquals(andOpt(nel.toList).map(_.query[Unit].sql), Some("(? AND ? AND ? ) "))
assertEquals(andOpt(nel.toList).map(_.query[Unit].sql), Some("(? AND ? AND ?) "))
}

test("andOpt (list empty)") {
Expand All @@ -128,43 +128,43 @@ class FragmentsSuite extends munit.FunSuite {
}

test("andFallbackTrue (many)") {
assertEquals(andFallbackTrue(fs).query[Unit].sql, "(? AND ? AND ? ) ")
assertEquals(andFallbackTrue(fs).query[Unit].sql, "(? AND ? AND ?) ")
}

test("or (vararg 2)") {
assertEquals(or(fs(0), fs(1)).query[Unit].sql, "(? OR ? ) ")
assertEquals(or(fs(0), fs(1)).query[Unit].sql, "(? OR ?) ")
}

test("or (Reducible 1)") {
assertEquals(or(nel1).query[Unit].sql, "(? ) ")
assertEquals(or(nel1).query[Unit].sql, "(?) ")
}

test("or (Reducible many)") {
assertEquals(or(nel).query[Unit].sql, "(? OR ? OR ? ) ")
assertEquals(or(nel).query[Unit].sql, "(? OR ? OR ?) ")
}

test("orOpt (vararg many none)") {
assertEquals(orOpt(None, None).map(_.query[Unit].sql), None)
}

test("orOpt (vararg 1 Some)") {
assertEquals(orOpt(noneF, someF).map(_.query[Unit].sql), Some("(? ) "))
assertEquals(orOpt(noneF, someF).map(_.query[Unit].sql), Some("(?) "))
}

test("orOpt (vararg 2 Some)") {
assertEquals(orOpt(someF, someF).map(_.query[Unit].sql), Some("(? OR ? ) "))
assertEquals(orOpt(someF, someF).map(_.query[Unit].sql), Some("(? OR ?) "))
}

test("orOpt (Foldable empty)") {
assertEquals(orOpt(List.empty[Fragment]).map(_.query[Unit].sql), None)
}

test("orOpt (Foldable 1)") {
assertEquals(orOpt(nel.take(1)).map(_.query[Unit].sql), Some("(? ) "))
assertEquals(orOpt(nel.take(1)).map(_.query[Unit].sql), Some("(?) "))
}

test("orOpt (Foldable many)") {
assertEquals(orOpt(nel.toList).map(_.query[Unit].sql), Some("(? OR ? OR ? ) "))
assertEquals(orOpt(nel.toList).map(_.query[Unit].sql), Some("(? OR ? OR ?) "))
}

test("orOpt (list empty)") {
Expand All @@ -176,31 +176,31 @@ class FragmentsSuite extends munit.FunSuite {
}

test("orFallbackFalse (many)") {
assertEquals(orFallbackFalse(fs).query[Unit].sql, "(? OR ? OR ? ) ")
assertEquals(orFallbackFalse(fs).query[Unit].sql, "(? OR ? OR ?) ")
}

test("whereAnd (varargs single)") {
assertEquals(whereAnd(fs(0)).query[Unit].sql, "WHERE ? ")
assertEquals(whereAnd(fs(0)).query[Unit].sql, "WHERE ?")
}

test("whereAnd (varargs many)") {
assertEquals(whereAnd(fs(0), fs(0), fs(0)).query[Unit].sql, "WHERE ? AND ? AND ? ")
assertEquals(whereAnd(fs(0), fs(0), fs(0)).query[Unit].sql, "WHERE ? AND ? AND ?")
}

test("whereAnd (Reducible 1)") {
assertEquals(whereAnd(nel1).query[Unit].sql, "WHERE ? ")
assertEquals(whereAnd(nel1).query[Unit].sql, "WHERE ?")
}

test("whereAnd (Reducible many)") {
assertEquals(whereAnd(nel).query[Unit].sql, "WHERE ? AND ? AND ? ")
assertEquals(whereAnd(nel).query[Unit].sql, "WHERE ? AND ? AND ?")
}

test("whereAndOpt (varargs many Some)") {
assertEquals(whereAndOpt(someF, someF).query[Unit].sql, "WHERE ? AND ? ")
assertEquals(whereAndOpt(someF, someF).query[Unit].sql, "WHERE ? AND ?")
}

test("whereAndOpt (varargs 1 Some)") {
assertEquals(whereAndOpt(ofs(0)).query[Unit].sql, "WHERE ? ")
assertEquals(whereAndOpt(ofs(0)).query[Unit].sql, "WHERE ?")
}

test("whereAndOpt (varargs all none)") {
Expand All @@ -212,31 +212,31 @@ class FragmentsSuite extends munit.FunSuite {
}

test("whereAndOpt (Foldable many)") {
assertEquals(whereAndOpt(fs).query[Unit].sql, "WHERE ? AND ? AND ? ")
assertEquals(whereAndOpt(fs).query[Unit].sql, "WHERE ? AND ? AND ?")
}

test("whereOr (varargs single)") {
assertEquals(whereOr(fs(0)).query[Unit].sql, "WHERE ? ")
assertEquals(whereOr(fs(0)).query[Unit].sql, "WHERE ?")
}

test("whereOr (varargs many)") {
assertEquals(whereOr(fs(0), fs(0), fs(0)).query[Unit].sql, "WHERE ? OR ? OR ? ")
assertEquals(whereOr(fs(0), fs(0), fs(0)).query[Unit].sql, "WHERE ? OR ? OR ?")
}

test("whereOr (Reducible 1)") {
assertEquals(whereOr(nel1).query[Unit].sql, "WHERE ? ")
assertEquals(whereOr(nel1).query[Unit].sql, "WHERE ?")
}

test("whereOr (Reducible many)") {
assertEquals(whereOr(nel).query[Unit].sql, "WHERE ? OR ? OR ? ")
assertEquals(whereOr(nel).query[Unit].sql, "WHERE ? OR ? OR ?")
}

test("whereOrOpt (varargs many Some)") {
assertEquals(whereOrOpt(someF, someF).query[Unit].sql, "WHERE ? OR ? ")
assertEquals(whereOrOpt(someF, someF).query[Unit].sql, "WHERE ? OR ?")
}

test("whereOrOpt (varargs 1 Some)") {
assertEquals(whereOrOpt(ofs(0)).query[Unit].sql, "WHERE ? ")
assertEquals(whereOrOpt(ofs(0)).query[Unit].sql, "WHERE ?")
}

test("whereOrOpt (varargs all none)") {
Expand All @@ -248,7 +248,7 @@ class FragmentsSuite extends munit.FunSuite {
}

test("whereOrOpt (Foldable many)") {
assertEquals(whereOrOpt(fs).query[Unit].sql, "WHERE ? OR ? OR ? ")
assertEquals(whereOrOpt(fs).query[Unit].sql, "WHERE ? OR ? OR ?")
}

test("orderBy (varargs 1)") {
Expand Down Expand Up @@ -284,21 +284,21 @@ class FragmentsSuite extends munit.FunSuite {
}

test("Usage test: whereAndOpt") {
assertEquals(whereAndOpt(Some(fr"hi"), orOpt(List.empty[Fragment]), orOpt(List(fr"a", fr"b"))).query[Unit].sql, "WHERE hi AND (a OR b ) ")
assertEquals(whereAndOpt(Some(sql"hi"), orOpt(List.empty[Fragment]), orOpt(List(sql"a", sql"b"))).query[Unit].sql, "WHERE hi AND (a OR b) ")
}

case class Person(name: String, age: Int)
case class Contact(person: Person, address: Option[String])

test("values (1)") {
val c = Contact(Person("Bob", 42), Some("addr"))
val f = fr"select" ++ Fragments.values(c)
val f = sql"select" ++ Fragments.values(c)
assertEquals(f.query[Contact].unique.transact(xa).unsafeRunSync(), c)
}

test("values (2)") {
val c = Contact(Person("Bob", 42), None)
val f = fr"select" ++ Fragments.values(c)
val f = sql"select" ++ Fragments.values(c)
assertEquals(f.query[Contact].unique.transact(xa).unsafeRunSync(), c)
}

Expand Down