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

Simplify fragments #1881

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
159 changes: 59 additions & 100 deletions modules/core/src/main/scala/doobie/util/fragments.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ package util

import cats.data.NonEmptyList
import cats.syntax.all._
import cats.{Foldable, Functor, Reducible}
import cats.{Foldable, Reducible}
import doobie.implicits._

/** Module of `Fragment` constructors. */
Expand All @@ -19,126 +19,89 @@ object fragments {

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

/** 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"))))

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, fs1, ...))`, or `false` for empty `fs` by default. */
def in[F[_]: Foldable, A: util.Put](f: Fragment, fs: F[A], onEmpty: Boolean = false): Fragment = fs.toList.toNel match {
case Some(fs) => parentheses(f ++ fr"IN" ++ parentheses(comma(fs.map(a => fr"$a"))))
case None => fr"$onEmpty"
}

/** 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)" })))
/** Returns `(f IN ((fs0-A, fs0-B), (fs1-A, fs1-B), ...))`, or `false` for empty `fs` by default. */
def inPairs[F[_]: Foldable, A: util.Put, B: util.Put](f: Fragment, fs: F[(A,B)], onEmpty: Boolean = false): Fragment = fs.toList.toNel match {
case Some(fs) => parentheses(f ++ fr"IN" ++ parentheses(comma(fs.map { case (a,b) => fr0"($a,$b)" })))
case None => fr"$onEmpty"
}

/** 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))
notIn(f, 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"))))
}

def notInOpt[F[_]: Foldable, A: util.Put](f: Fragment, fs: F[A]): Option[Fragment] = {
NonEmptyList.fromFoldable(fs).map(nel => notIn(f, nel))
/** Returns `(f NOT IN (fs0, fs1, ...))`, or `true` for empty `fs` by default. */
def notIn[F[_]: Foldable, A: util.Put](f: Fragment, fs: F[A], onEmpty: Boolean = true): Fragment = fs.toList.toNel match {
case Some(fs) => parentheses(f ++ fr"NOT IN" ++ parentheses(comma(fs.map(a => fr"$a"))))
case None => fr"$onEmpty"
}

/** Returns `(f1 AND f2 AND ... fn)`. */
def and(f1: Fragment, f2: Fragment, fs: Fragment*): Fragment =
and(NonEmptyList(f1, f2 :: fs.toList))

/** 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")
if (withParen) parentheses(expr) else expr
}
and(f1 :: f2 :: fs.toList)

/** Returns `(f1 AND f2 AND ... fn)` for all defined fragments, returning None if there are no defined fragments */
def andOpt(opt1: Option[Fragment], opt2: Option[Fragment], opts: Option[Fragment]*): Option[Fragment] = {
andOpt((opt1 :: opt2 :: opts.toList).flatten)
/** Returns `(f1 AND f2 AND ... fn)`, or `true` for empty `fs` by default. */
def and[F[_]: Foldable](fs: F[Fragment], onEmpty: Boolean = true): Fragment = fs.toList.toNel match {
case Some(fs) => parentheses(fs.intercalate(fr"AND"))
case None => fr"$onEmpty"
}

/** Returns `(f1 AND f2 AND ... fn)`, or None if the collection is empty. */
def andOpt[F[_]: Foldable](fs: F[Fragment], withParen: Boolean = true): Option[Fragment] = {
NonEmptyList.fromFoldable(fs).map(nel => and(nel, withParen))
}
/** Returns `(f1 AND f2 AND ... fn)` for all defined fragments. */
def andOpt(f1: Option[Fragment], fs: Option[Fragment]*): Fragment =
and((f1 :: fs.toList).unite)

/** Similar to andOpt, but defaults to FALSE if passed an empty collection */
def andFallbackTrue[F[_] : Foldable](fs: F[Fragment]): Fragment = {
andOpt(fs).getOrElse(fr"TRUE")
}
/** Returns `(f1 AND f2 AND ... fn)` for all defined fragments. */
def andOpt[F[_]: Foldable](fs: F[Option[Fragment]], onEmpty: Boolean = true): Fragment =
and(fs.toList.unite, onEmpty)

/** Returns `(f1 OR f2 OR ... fn)`. */
def or(f1: Fragment, f2: Fragment, fs: Fragment*): Fragment =
or(NonEmptyList(f1, f2 :: fs.toList))

/** Returns `(f1 OR f2 OR ... fn)` for a non-empty collection.
*
* @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")
if (withParen) parentheses(expr) else expr
}
or(f1 :: f2 :: fs.toList)

/** Returns `(f1 OR f2 OR ... fn)` for all defined fragments, returning None if there are no defined fragments */
def orOpt(opt1: Option[Fragment], opt2: Option[Fragment], opts: Option[Fragment]*): Option[Fragment] = {
orOpt((opt1 :: opt2 :: opts.toList).flatten)
/** Returns `(f1 OR f2 OR ... fn)`, or `false` for empty `fs` by default. */
def or[F[_]: Foldable](fs: F[Fragment], onEmpty: Boolean = false): Fragment = fs.toList.toNel match {
case Some(fs) => parentheses(fs.intercalate(fr"OR"))
case None => fr"$onEmpty"
}

/** Returns `(f1 OR f2 OR ... fn)`, or None if the collection is empty. */
def orOpt[F[_]: Foldable](fs: F[Fragment], withParen: Boolean = true): Option[Fragment] = {
NonEmptyList.fromFoldable(fs).map(nel => or(nel, withParen))
}

/** Similar to orOpt, but defaults to FALSE if passed an empty collection */
def orFallbackFalse[F[_]: Foldable](fs: F[Fragment]): Fragment = {
orOpt(fs).getOrElse(fr"FALSE")
}
/** Returns `(f1 OR f2 OR ... fn)` for all defined fragments. */
def orOpt(f1: Option[Fragment], fs: Option[Fragment]*): Fragment =
or((f1 :: fs.toList).unite)

/** Returns `(f1 OR f2 OR ... fn)` for all defined fragments. */
def orOpt[F[_]: Foldable](fs: F[Option[Fragment]], onEmpty: Boolean = false): Fragment =
or(fs.toList.unite, onEmpty)

/** Returns `WHERE f1 AND f2 AND ... fn`. */
def whereAnd(f1: Fragment, fs: Fragment*): Fragment =
whereAnd(NonEmptyList(f1,fs.toList))
whereAnd(f1 :: fs.toList)

/** Returns `WHERE f1 AND f2 AND ... fn` or the empty fragment if `fs` is empty. */
def whereAnd[F[_]: Reducible](fs: F[Fragment]): Fragment =
fr"WHERE" ++ and(fs, withParen = false)
def whereAnd[F[_]: Foldable](fs: F[Fragment]): Fragment =
if (fs.isEmpty) Fragment.empty else fr"WHERE" ++ and(fs)

/** Returns `WHERE f1 AND f2 AND ... fn` for defined `f`, if any, otherwise the empty fragment. */
def whereAndOpt(f1: Option[Fragment], f2: Option[Fragment], fs: Option[Fragment]*): Fragment = {
whereAndOpt((f1 :: f2 :: fs.toList).flatten)
}

/** Returns `WHERE f1 AND f2 AND ... fn` if collection is not empty. If collection is empty returns an empty fragment. */
def whereAndOpt[F[_]: Foldable](fs: F[Fragment]): Fragment = {
NonEmptyList.fromFoldable(fs) match {
case Some(nel) => whereAnd(nel)
case None => Fragment.empty
}
}
def whereAndOpt(fs: Option[Fragment]*): Fragment =
whereAnd(fs.toList.unite)

/** Returns `WHERE f1 OR f2 OR ... fn`. */
def whereOr(f1: Fragment, fs: Fragment*): Fragment =
whereOr(NonEmptyList(f1, fs.toList))

/** Returns `WHERE f1 OR f2 OR ... fn` or the empty fragment if `fs` is empty. */
def whereOr[F[_] : Reducible](fs: F[Fragment]): Fragment =
fr"WHERE" ++ or(fs, withParen = false)
def whereOr[F[_]: Foldable](fs: F[Fragment]): Fragment =
if (fs.isEmpty) Fragment.empty else fr"WHERE" ++ or(fs)

/** Returns `WHERE f1 OR f2 OR ... fn` for defined `f`, if any, otherwise the empty fragment. */
def whereOrOpt(f1: Option[Fragment], f2: Option[Fragment], fs: Option[Fragment]*): Fragment = {
whereOrOpt((f1 :: f2 :: fs.toList).flatten)
}

/** Returns `WHERE f1 OR f2 OR ... fn` if collection is not empty. If collection is empty returns an empty fragment. */
def whereOrOpt[F[_] : Foldable](fs: F[Fragment]): Fragment = {
NonEmptyList.fromFoldable(fs) match {
case Some(nel) => whereOr(nel)
case None => Fragment.empty
}
}
def whereOrOpt(fs: Option[Fragment]*): Fragment =
whereOr(fs.toList.unite)

/** Returns `SET f1, f2, ... fn`. */
def set(f1: Fragment, fs: Fragment*): Fragment =
Expand All @@ -160,24 +123,20 @@ object fragments {
comma(NonEmptyList(f1, f2 :: fs.toList))

/** Returns `f1, f2, ... fn`. */
def comma[F[_]: Reducible](fs: F[Fragment]): Fragment =
fs.nonEmptyIntercalate(fr",")
def comma[F[_]: Foldable](fs: F[Fragment]): Fragment =
fs.intercalate(fr",")

/** Returns `ORDER BY f1, f2, ... fn`. */
def orderBy(f1: Fragment, fs: Fragment*): Fragment =
orderBy(NonEmptyList(f1, fs.toList))

def orderBy[F[_]: Reducible](fs: F[Fragment]): Fragment =
fr"ORDER BY" ++ comma(fs)
comma(NonEmptyList(f1, fs.toList))

/** Returns `ORDER BY f1, f2, ... fn` or the empty fragment if `fs` is empty. */
def orderByOpt[F[_]: Foldable](fs: F[Fragment]): Fragment =
NonEmptyList.fromFoldable(fs) match {
case Some(nel) => orderBy(nel)
case None => Fragment.empty
}
def orderBy[F[_]: Foldable](fs: F[Fragment]): Fragment = fs.toList.toNel match {
case Some(fs) => fr"ORDER BY" ++ comma(fs)
case None => Fragment.empty
}

/** Returns `ORDER BY f1, f2, ... fn` for defined `f`, if any, otherwise the empty fragment. */
def orderByOpt(f1: Option[Fragment], f2: Option[Fragment], fs: Option[Fragment]*): Fragment =
orderByOpt((f1 :: f2 :: fs.toList).flatten)
def orderByOpt(fs: Option[Fragment]*): Fragment =
orderBy(fs.toList.unite)
}
Loading