Extend query builder allowing more conditions

Before only a column or a dbfunction could be used in a condition. It
is now allowed for all `SelectExpr`.
This commit is contained in:
Eike Kettner 2021-03-08 20:46:42 +01:00
parent b514b85f39
commit e681ffa96f
4 changed files with 62 additions and 30 deletions

View File

@ -15,7 +15,7 @@ object Condition {
val P: Put[A] val P: Put[A]
) extends Condition ) extends Condition
case class CompareFVal[A](dbf: DBFunction, op: Operator, value: A)(implicit case class CompareFVal[A](sel: SelectExpr, op: Operator, value: A)(implicit
val P: Put[A] val P: Put[A]
) extends Condition ) extends Condition
@ -23,11 +23,11 @@ object Condition {
extends Condition extends Condition
case class InSubSelect[A](col: Column[A], subSelect: Select) extends Condition case class InSubSelect[A](col: Column[A], subSelect: Select) extends Condition
case class InValues[A](col: Column[A], values: NonEmptyList[A], lower: Boolean)(implicit case class InValues[A](sel: SelectExpr, values: NonEmptyList[A], lower: Boolean)(
val P: Put[A] implicit val P: Put[A]
) extends Condition ) extends Condition
case class IsNull(col: Column[_]) extends Condition case class IsNull(sel: SelectExpr) extends Condition
case class And(inner: NonEmptyList[Condition]) extends Condition { case class And(inner: NonEmptyList[Condition]) extends Condition {
def append(other: Condition): And = def append(other: Condition): And =

View File

@ -207,22 +207,22 @@ trait DSL extends DoobieMeta {
in(subsel).negate in(subsel).negate
def in(values: Nel[A])(implicit P: Put[A]): Condition = def in(values: Nel[A])(implicit P: Put[A]): Condition =
Condition.InValues(col, values, false) Condition.InValues(col.s, values, false)
def notIn(values: Nel[A])(implicit P: Put[A]): Condition = def notIn(values: Nel[A])(implicit P: Put[A]): Condition =
in(values).negate in(values).negate
def inLower(values: Nel[A])(implicit P: Put[A]): Condition = def inLower(values: Nel[A])(implicit P: Put[A]): Condition =
Condition.InValues(col, values, true) Condition.InValues(col.s, values, true)
def notInLower(values: Nel[A])(implicit P: Put[A]): Condition = def notInLower(values: Nel[A])(implicit P: Put[A]): Condition =
Condition.InValues(col, values, true).negate Condition.InValues(col.s, values, true).negate
def isNull: Condition = def isNull: Condition =
Condition.IsNull(col) Condition.IsNull(col.s)
def isNotNull: Condition = def isNotNull: Condition =
Condition.IsNull(col).negate Condition.IsNull(col.s).negate
def ===(other: Column[A]): Condition = def ===(other: Column[A]): Condition =
Condition.CompareCol(col, Operator.Eq, other) Condition.CompareCol(col, Operator.Eq, other)
@ -267,31 +267,31 @@ trait DSL extends DoobieMeta {
SelectExpr.SelectFun(dbf, Some(otherCol.name)) SelectExpr.SelectFun(dbf, Some(otherCol.name))
def ===[A](value: A)(implicit P: Put[A]): Condition = def ===[A](value: A)(implicit P: Put[A]): Condition =
Condition.CompareFVal(dbf, Operator.Eq, value) Condition.CompareFVal(dbf.s, Operator.Eq, value)
def ====(value: String): Condition = def ====(value: String): Condition =
Condition.CompareFVal(dbf, Operator.Eq, value) Condition.CompareFVal(dbf.s, Operator.Eq, value)
def like[A](value: A)(implicit P: Put[A]): Condition = def like[A](value: A)(implicit P: Put[A]): Condition =
Condition.CompareFVal(dbf, Operator.LowerLike, value) Condition.CompareFVal(dbf.s, Operator.LowerLike, value)
def likes(value: String): Condition = def likes(value: String): Condition =
Condition.CompareFVal(dbf, Operator.LowerLike, value) Condition.CompareFVal(dbf.s, Operator.LowerLike, value)
def <=[A](value: A)(implicit P: Put[A]): Condition = def <=[A](value: A)(implicit P: Put[A]): Condition =
Condition.CompareFVal(dbf, Operator.Lte, value) Condition.CompareFVal(dbf.s, Operator.Lte, value)
def >=[A](value: A)(implicit P: Put[A]): Condition = def >=[A](value: A)(implicit P: Put[A]): Condition =
Condition.CompareFVal(dbf, Operator.Gte, value) Condition.CompareFVal(dbf.s, Operator.Gte, value)
def >[A](value: A)(implicit P: Put[A]): Condition = def >[A](value: A)(implicit P: Put[A]): Condition =
Condition.CompareFVal(dbf, Operator.Gt, value) Condition.CompareFVal(dbf.s, Operator.Gt, value)
def <[A](value: A)(implicit P: Put[A]): Condition = def <[A](value: A)(implicit P: Put[A]): Condition =
Condition.CompareFVal(dbf, Operator.Lt, value) Condition.CompareFVal(dbf.s, Operator.Lt, value)
def <>[A](value: A)(implicit P: Put[A]): Condition = def <>[A](value: A)(implicit P: Put[A]): Condition =
Condition.CompareFVal(dbf, Operator.Neq, value) Condition.CompareFVal(dbf.s, Operator.Neq, value)
def -[A](value: A)(implicit P: Put[A]): DBFunction = def -[A](value: A)(implicit P: Put[A]): DBFunction =
DBFunction.Calc( DBFunction.Calc(
@ -300,6 +300,35 @@ trait DSL extends DoobieMeta {
SelectExpr.SelectConstant(value, None) SelectExpr.SelectConstant(value, None)
) )
} }
implicit final class SelectExprOps(sel: SelectExpr) {
def isNull: Condition =
Condition.IsNull(sel)
def isNotNull: Condition =
Condition.IsNull(sel).negate
def ===[A](value: A)(implicit P: Put[A]): Condition =
Condition.CompareFVal(sel, Operator.Eq, value)
def <=[A](value: A)(implicit P: Put[A]): Condition =
Condition.CompareFVal(sel, Operator.Lte, value)
def >=[A](value: A)(implicit P: Put[A]): Condition =
Condition.CompareFVal(sel, Operator.Gte, value)
def >[A](value: A)(implicit P: Put[A]): Condition =
Condition.CompareFVal(sel, Operator.Gt, value)
def <[A](value: A)(implicit P: Put[A]): Condition =
Condition.CompareFVal(sel, Operator.Lt, value)
def <>[A](value: A)(implicit P: Put[A]): Condition =
Condition.CompareFVal(sel, Operator.Neq, value)
def in[A](values: Nel[A])(implicit P: Put[A]): Condition =
Condition.InValues(sel, values, false)
}
} }
object DSL extends DSL { object DSL extends DSL {

View File

@ -92,7 +92,7 @@ object ItemQueryGenerator {
val dt = dateToTimestamp(today)(value) val dt = dateToTimestamp(today)(value)
val col = timestampColumn(tables)(attr) val col = timestampColumn(tables)(attr)
val noLikeOp = if (op == Operator.Like) Operator.Eq else op val noLikeOp = if (op == Operator.Like) Operator.Eq else op
Condition.CompareVal(col, makeOp(noLikeOp), dt) Condition.CompareFVal(col, makeOp(noLikeOp), dt)
case Expr.SimpleExpr(op, Property.IntProperty(attr, value)) => case Expr.SimpleExpr(op, Property.IntProperty(attr, value)) =>
val col = intColumn(tables)(attr) val col = intColumn(tables)(attr)
@ -203,22 +203,22 @@ object ItemQueryGenerator {
today today
} }
private def anyColumn(tables: Tables)(attr: Attr): Column[_] = private def anyColumn(tables: Tables)(attr: Attr): SelectExpr =
attr match { attr match {
case s: Attr.StringAttr => case s: Attr.StringAttr =>
stringColumn(tables)(s) stringColumn(tables)(s).s
case t: Attr.DateAttr => case t: Attr.DateAttr =>
timestampColumn(tables)(t) timestampColumn(tables)(t)
case n: Attr.IntAttr => case n: Attr.IntAttr =>
intColumn(tables)(n) intColumn(tables)(n).s
} }
private def timestampColumn(tables: Tables)(attr: Attr.DateAttr) = private def timestampColumn(tables: Tables)(attr: Attr.DateAttr): SelectExpr =
attr match { attr match {
case Attr.Date => case Attr.Date =>
tables.item.itemDate coalesce(tables.item.itemDate.s, tables.item.created.s).s
case Attr.DueDate => case Attr.DueDate =>
tables.item.dueDate tables.item.dueDate.s
} }
private def stringColumn(tables: Tables)(attr: Attr.StringAttr): Column[String] = private def stringColumn(tables: Tables)(attr: Attr.StringAttr): Column[String] =
@ -283,7 +283,7 @@ object ItemQueryGenerator {
value.toDoubleOption value.toDoubleOption
.map { n => .map { n =>
val numericCmp = Condition.CompareFVal(castNumeric(cfv.value.s), op, n) val numericCmp = Condition.CompareFVal(castNumeric(cfv.value.s).s, op, n)
val fieldIsNumeric = val fieldIsNumeric =
cf.ftype === CustomFieldType.Numeric || cf.ftype === CustomFieldType.Money cf.ftype === CustomFieldType.Numeric || cf.ftype === CustomFieldType.Money
val fieldNotNumeric = val fieldNotNumeric =

View File

@ -85,7 +85,7 @@ object ConditionBuilder {
case Operator.LowerEq => case Operator.LowerEq =>
lower(dbf) lower(dbf)
case _ => case _ =>
DBFunctionBuilder.build(dbf) SelectExprBuilder.build(dbf)
} }
dbfFrag ++ opFrag ++ valFrag dbfFrag ++ opFrag ++ valFrag
@ -105,13 +105,13 @@ object ConditionBuilder {
SelectExprBuilder.column(col) ++ sql" IN (" ++ sub ++ parenClose SelectExprBuilder.column(col) ++ sql" IN (" ++ sub ++ parenClose
case c @ Condition.InValues(col, values, toLower) => case c @ Condition.InValues(col, values, toLower) =>
val cfrag = if (toLower) lower(col) else SelectExprBuilder.column(col) val cfrag = if (toLower) lower(col) else SelectExprBuilder.build(col)
cfrag ++ sql" IN (" ++ values.toList cfrag ++ sql" IN (" ++ values.toList
.map(a => buildValue(a)(c.P)) .map(a => buildValue(a)(c.P))
.reduce(_ ++ comma ++ _) ++ parenClose .reduce(_ ++ comma ++ _) ++ parenClose
case Condition.IsNull(col) => case Condition.IsNull(col) =>
SelectExprBuilder.column(col) ++ fr" is null" SelectExprBuilder.build(col) ++ fr" is null"
case Condition.And(ands) => case Condition.And(ands) =>
val inner = ands.map(build).reduceLeft(_ ++ and ++ _) val inner = ands.map(build).reduceLeft(_ ++ and ++ _)
@ -124,7 +124,7 @@ object ConditionBuilder {
else parenOpen ++ inner ++ parenClose else parenOpen ++ inner ++ parenClose
case Condition.Not(Condition.IsNull(col)) => case Condition.Not(Condition.IsNull(col)) =>
SelectExprBuilder.column(col) ++ fr" is not null" SelectExprBuilder.build(col) ++ fr" is not null"
case Condition.Not(c) => case Condition.Not(c) =>
fr"NOT" ++ build(c) fr"NOT" ++ build(c)
@ -159,6 +159,9 @@ object ConditionBuilder {
def buildOptValue[A: Put](v: Option[A]): Fragment = def buildOptValue[A: Put](v: Option[A]): Fragment =
fr"$v" fr"$v"
def lower(sel: SelectExpr): Fragment =
Fragment.const0("LOWER(") ++ SelectExprBuilder.build(sel) ++ parenClose
def lower(col: Column[_]): Fragment = def lower(col: Column[_]): Fragment =
Fragment.const0("LOWER(") ++ SelectExprBuilder.column(col) ++ parenClose Fragment.const0("LOWER(") ++ SelectExprBuilder.column(col) ++ parenClose