diff --git a/modules/joex/src/main/scala/docspell/joex/analysis/RegexNerFile.scala b/modules/joex/src/main/scala/docspell/joex/analysis/RegexNerFile.scala index e5dcce3e..75c21673 100644 --- a/modules/joex/src/main/scala/docspell/joex/analysis/RegexNerFile.scala +++ b/modules/joex/src/main/scala/docspell/joex/analysis/RegexNerFile.scala @@ -141,7 +141,7 @@ object RegexNerFile { def latestUpdate(collective: Ident): ConnectionIO[Option[Timestamp]] = { def max_(col: Column[_], cidCol: Column[Ident]): Select = - Select(List(max(col).as("t")), from(col.table), cidCol === collective) + Select(max(col).as("t"), from(col.table), cidCol === collective) val sql = union( max_(ROrganization.T.updated, ROrganization.T.cid), diff --git a/modules/store/src/main/scala/docspell/store/qb/Column.scala b/modules/store/src/main/scala/docspell/store/qb/Column.scala index 60d49abc..3e59a62c 100644 --- a/modules/store/src/main/scala/docspell/store/qb/Column.scala +++ b/modules/store/src/main/scala/docspell/store/qb/Column.scala @@ -3,12 +3,6 @@ package docspell.store.qb case class Column[A](name: String, table: TableDef) { def inTable(t: TableDef): Column[A] = copy(table = t) - - def s: SelectExpr = - SelectExpr.SelectColumn(this, None) - - def as(alias: String): SelectExpr = - SelectExpr.SelectColumn(this, Some(alias)) } object Column {} diff --git a/modules/store/src/main/scala/docspell/store/qb/Condition.scala b/modules/store/src/main/scala/docspell/store/qb/Condition.scala index 264889f7..c89b7178 100644 --- a/modules/store/src/main/scala/docspell/store/qb/Condition.scala +++ b/modules/store/src/main/scala/docspell/store/qb/Condition.scala @@ -4,13 +4,7 @@ import cats.data.NonEmptyList import doobie._ -sealed trait Condition { - def s: SelectExpr.SelectCondition = - SelectExpr.SelectCondition(this, None) - - def as(alias: String): SelectExpr.SelectCondition = - SelectExpr.SelectCondition(this, Some(alias)) -} +sealed trait Condition object Condition { diff --git a/modules/store/src/main/scala/docspell/store/qb/DML.scala b/modules/store/src/main/scala/docspell/store/qb/DML.scala index e8fd6dcf..48a7f051 100644 --- a/modules/store/src/main/scala/docspell/store/qb/DML.scala +++ b/modules/store/src/main/scala/docspell/store/qb/DML.scala @@ -1,5 +1,7 @@ package docspell.store.qb +import cats.data.{NonEmptyList => Nel} + import docspell.store.qb.impl._ import doobie._ @@ -15,44 +17,44 @@ object DML { fr"DELETE FROM" ++ FromExprBuilder.buildTable(table) ++ fr"WHERE" ++ ConditionBuilder .build(cond) - def insert(table: TableDef, cols: Seq[Column[_]], values: Fragment): ConnectionIO[Int] = + def insert(table: TableDef, cols: Nel[Column[_]], values: Fragment): ConnectionIO[Int] = insertFragment(table, cols, List(values)).update.run def insertMany( table: TableDef, - cols: Seq[Column[_]], + cols: Nel[Column[_]], values: Seq[Fragment] ): ConnectionIO[Int] = insertFragment(table, cols, values).update.run def insertFragment( table: TableDef, - cols: Seq[Column[_]], + cols: Nel[Column[_]], values: Seq[Fragment] ): Fragment = fr"INSERT INTO" ++ FromExprBuilder.buildTable(table) ++ sql"(" ++ cols .map(SelectExprBuilder.columnNoPrefix) - .reduce(_ ++ comma ++ _) ++ fr") VALUES" ++ + .reduceLeft(_ ++ comma ++ _) ++ fr") VALUES" ++ values.map(f => sql"(" ++ f ++ sql")").reduce(_ ++ comma ++ _) def update( table: TableDef, cond: Condition, - setter: Seq[Setter[_]] + setter: Nel[Setter[_]] ): ConnectionIO[Int] = updateFragment(table, Some(cond), setter).update.run def updateFragment( table: TableDef, cond: Option[Condition], - setter: Seq[Setter[_]] + setter: Nel[Setter[_]] ): Fragment = { val condFrag = cond.map(SelectBuilder.cond).getOrElse(Fragment.empty) fr"UPDATE" ++ FromExprBuilder.buildTable(table) ++ fr"SET" ++ setter .map(s => buildSetter(s)) - .reduce(_ ++ comma ++ _) ++ + .reduceLeft(_ ++ comma ++ _) ++ condFrag } @@ -74,6 +76,6 @@ object DML { colFrag ++ fr" =" ++ colFrag ++ fr" + $amount" } - def set(s: Setter[_], more: Setter[_]*): Seq[Setter[_]] = - more :+ s + def set(s: Setter[_], more: Setter[_]*): Nel[Setter[_]] = + Nel(s, more.toList) } diff --git a/modules/store/src/main/scala/docspell/store/qb/DSL.scala b/modules/store/src/main/scala/docspell/store/qb/DSL.scala index 6223ebc0..7e388075 100644 --- a/modules/store/src/main/scala/docspell/store/qb/DSL.scala +++ b/modules/store/src/main/scala/docspell/store/qb/DSL.scala @@ -1,6 +1,6 @@ package docspell.store.qb -import cats.data.NonEmptyList +import cats.data.{NonEmptyList => Nel} import docspell.store.impl.DoobieMeta import docspell.store.qb.impl.SelectBuilder @@ -9,14 +9,14 @@ import doobie.{Fragment, Put} trait DSL extends DoobieMeta { - def run(projection: Seq[SelectExpr], from: FromExpr): Fragment = + def run(projection: Nel[SelectExpr], from: FromExpr): Fragment = SelectBuilder(Select(projection, from)) - def run(projection: Seq[SelectExpr], from: FromExpr, where: Condition): Fragment = + def run(projection: Nel[SelectExpr], from: FromExpr, where: Condition): Fragment = SelectBuilder(Select(projection, from, where)) def runDistinct( - projection: Seq[SelectExpr], + projection: Nel[SelectExpr], from: FromExpr, where: Condition ): Fragment = @@ -25,24 +25,30 @@ trait DSL extends DoobieMeta { def withCte(cte: (TableDef, Select), more: (TableDef, Select)*): DSL.WithCteDsl = DSL.WithCteDsl(CteBind(cte), more.map(CteBind.apply).toVector) - def select(cond: Condition): Seq[SelectExpr] = - Seq(SelectExpr.SelectCondition(cond, None)) + def select(cond: Condition): Nel[SelectExpr] = + Nel.of(SelectExpr.SelectCondition(cond, None)) - def select(dbf: DBFunction): Seq[SelectExpr] = - Seq(SelectExpr.SelectFun(dbf, None)) + def select(dbf: DBFunction): Nel[SelectExpr] = + Nel.of(SelectExpr.SelectFun(dbf, None)) - def select(e: SelectExpr, es: SelectExpr*): Seq[SelectExpr] = - es.prepended(e) + def select(e: SelectExpr, es: SelectExpr*): Nel[SelectExpr] = + Nel(e, es.toList) - def select(c: Column[_], cs: Column[_]*): Seq[SelectExpr] = - cs.prepended(c).map(col => SelectExpr.SelectColumn(col, None)) + def select(c: Column[_], cs: Column[_]*): Nel[SelectExpr] = + Nel(c, cs.toList).map(col => SelectExpr.SelectColumn(col, None)) - def select(seq: Seq[Column[_]], seqs: Seq[Column[_]]*): Seq[SelectExpr] = - (seq ++ seqs.flatten).map(c => SelectExpr.SelectColumn(c, None)) + def select(seq: Nel[Column[_]], seqs: Nel[Column[_]]*): Nel[SelectExpr] = + seqs.foldLeft(seq)(_ concatNel _).map(c => SelectExpr.SelectColumn(c, None)) def union(s1: Select, sn: Select*): Select = Select.Union(s1, sn.toVector) + def intersect(s1: Select, sn: Select*): Select = + Select.Intersect(s1, sn.toVector) + + def intersect(nel: Nel[Select]): Select = + Select.Intersect(nel.head, nel.tail.toVector) + def from(table: TableDef): FromExpr.From = FromExpr.From(table) @@ -105,8 +111,12 @@ trait DSL extends DoobieMeta { else and(c, cs: _*) implicit final class ColumnOps[A](col: Column[A]) { - def s: SelectExpr = SelectExpr.SelectColumn(col, None) - def as(alias: String) = SelectExpr.SelectColumn(col, Some(alias)) + def s: SelectExpr = + SelectExpr.SelectColumn(col, None) + def as(alias: String): SelectExpr = + SelectExpr.SelectColumn(col, Some(alias)) + def as(otherCol: Column[A]): SelectExpr = + SelectExpr.SelectColumn(col, Some(otherCol.name)) def setTo(value: A)(implicit P: Put[A]): Setter[A] = Setter.SetValue(col, value) @@ -153,20 +163,28 @@ trait DSL extends DoobieMeta { def in(subsel: Select): Condition = Condition.InSubSelect(col, subsel) - def in(values: NonEmptyList[A])(implicit P: Put[A]): Condition = + def in(values: Nel[A])(implicit P: Put[A]): Condition = Condition.InValues(col, values, false) - def inLower(values: NonEmptyList[A])(implicit P: Put[A]): Condition = + def inLower(values: Nel[A])(implicit P: Put[A]): Condition = Condition.InValues(col, values, true) def isNull: Condition = Condition.IsNull(col) + def isNotNull: Condition = + Condition.IsNull(col).negate + def ===(other: Column[A]): Condition = Condition.CompareCol(col, Operator.Eq, other) } implicit final class ConditionOps(c: Condition) { + def s: SelectExpr = + SelectExpr.SelectCondition(c, None) + + def as(alias: String): SelectExpr = + SelectExpr.SelectCondition(c, Some(alias)) def &&(other: Condition): Condition = and(c, other) @@ -188,8 +206,10 @@ trait DSL extends DoobieMeta { } implicit final class DBFunctionOps(dbf: DBFunction) { - def s: SelectExpr = SelectExpr.SelectFun(dbf, None) - def as(alias: String) = SelectExpr.SelectFun(dbf, Some(alias)) + def s: SelectExpr = + SelectExpr.SelectFun(dbf, None) + def as(alias: String): SelectExpr = + SelectExpr.SelectFun(dbf, Some(alias)) def ===[A](value: A)(implicit P: Put[A]): Condition = Condition.CompareFVal(dbf, Operator.Eq, value) @@ -233,6 +253,9 @@ object DSL extends DSL { def select(s: Select): Select.WithCte = Select.WithCte(cte, ctes, s) + + def apply(s: Select): Select.WithCte = + select(s) } } diff --git a/modules/store/src/main/scala/docspell/store/qb/FromExpr.scala b/modules/store/src/main/scala/docspell/store/qb/FromExpr.scala index d78f3143..8a8d65b1 100644 --- a/modules/store/src/main/scala/docspell/store/qb/FromExpr.scala +++ b/modules/store/src/main/scala/docspell/store/qb/FromExpr.scala @@ -1,11 +1,6 @@ package docspell.store.qb -sealed trait FromExpr { - -// def innerJoin(other: TableDef, on: Condition): FromExpr -// -// def leftJoin(other: TableDef, on: Condition): FromExpr -} +sealed trait FromExpr object FromExpr { diff --git a/modules/store/src/main/scala/docspell/store/qb/GroupBy.scala b/modules/store/src/main/scala/docspell/store/qb/GroupBy.scala index fffa53f9..8a5b451b 100644 --- a/modules/store/src/main/scala/docspell/store/qb/GroupBy.scala +++ b/modules/store/src/main/scala/docspell/store/qb/GroupBy.scala @@ -1,5 +1,7 @@ package docspell.store.qb +import cats.data.NonEmptyList + case class GroupBy(name: SelectExpr, names: Vector[SelectExpr], having: Option[Condition]) object GroupBy { @@ -10,4 +12,7 @@ object GroupBy { cs.toVector.map(c => SelectExpr.SelectColumn(c, None)), None ) + + def apply(nel: NonEmptyList[Column[_]]): GroupBy = + apply(nel.head, nel.tail: _*) } diff --git a/modules/store/src/main/scala/docspell/store/qb/Select.scala b/modules/store/src/main/scala/docspell/store/qb/Select.scala index ac6d0ca1..406135f0 100644 --- a/modules/store/src/main/scala/docspell/store/qb/Select.scala +++ b/modules/store/src/main/scala/docspell/store/qb/Select.scala @@ -1,11 +1,13 @@ package docspell.store.qb +import cats.data.{NonEmptyList => Nel} + import docspell.store.qb.impl.SelectBuilder import doobie._ sealed trait Select { - def run: Fragment = + def build: Fragment = SelectBuilder(this) def as(alias: String): SelectExpr.SelectQuery = @@ -25,17 +27,26 @@ sealed trait Select { } object Select { - def apply(projection: Seq[SelectExpr], from: FromExpr) = + def apply(projection: Nel[SelectExpr], from: FromExpr) = SimpleSelect(false, projection, from, None, None) + def apply(projection: SelectExpr, from: FromExpr) = + SimpleSelect(false, Nel.of(projection), from, None, None) + def apply( - projection: Seq[SelectExpr], + projection: Nel[SelectExpr], from: FromExpr, where: Condition ) = SimpleSelect(false, projection, from, Some(where), None) def apply( - projection: Seq[SelectExpr], + projection: SelectExpr, + from: FromExpr, + where: Condition + ) = SimpleSelect(false, Nel.of(projection), from, Some(where), None) + + def apply( + projection: Nel[SelectExpr], from: FromExpr, where: Condition, groupBy: GroupBy @@ -43,7 +54,7 @@ object Select { case class SimpleSelect( distinctFlag: Boolean, - projection: Seq[SelectExpr], + projection: Nel[SelectExpr], from: FromExpr, where: Option[Condition], groupBy: Option[GroupBy] diff --git a/modules/store/src/main/scala/docspell/store/qb/impl/SelectBuilder.scala b/modules/store/src/main/scala/docspell/store/qb/impl/SelectBuilder.scala index 3d64c67b..23ca286e 100644 --- a/modules/store/src/main/scala/docspell/store/qb/impl/SelectBuilder.scala +++ b/modules/store/src/main/scala/docspell/store/qb/impl/SelectBuilder.scala @@ -40,7 +40,7 @@ object SelectBuilder { } def buildSimple(sq: Select.SimpleSelect): Fragment = { - val f0 = sq.projection.map(selectExpr).reduce(_ ++ comma ++ _) + val f0 = sq.projection.map(selectExpr).reduceLeft(_ ++ comma ++ _) val f1 = fromExpr(sq.from) val f2 = sq.where.map(cond).getOrElse(Fragment.empty) val f3 = sq.groupBy.map(groupBy).getOrElse(Fragment.empty) diff --git a/modules/store/src/main/scala/docspell/store/queries/QCollective.scala b/modules/store/src/main/scala/docspell/store/queries/QCollective.scala index 62b23ab0..36361780 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QCollective.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QCollective.scala @@ -84,12 +84,12 @@ object QCollective { val t = RTag.as("t") val sql = Select( - select(t.all) ++ select(count(ti.itemId)), + select(t.all).append(count(ti.itemId).s), from(ti).innerJoin(t, ti.tagId === t.tid), t.cid === coll ).group(GroupBy(t.name, t.tid, t.category)) - sql.run.query[TagCount].to[List] + sql.build.query[TagCount].to[List] } def getContacts( @@ -113,6 +113,6 @@ object QCollective { select(rc.all), from(rc), (rc.orgId.in(orgCond) || rc.personId.in(persCond)) &&? valueFilter &&? kindFilter - ).orderBy(rc.value).run.query[RContact].stream + ).orderBy(rc.value).build.query[RContact].stream } } diff --git a/modules/store/src/main/scala/docspell/store/queries/QCustomField.scala b/modules/store/src/main/scala/docspell/store/queries/QCustomField.scala index 7b10964e..0990a12b 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QCustomField.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QCustomField.scala @@ -1,17 +1,17 @@ package docspell.store.queries -import cats.data.NonEmptyList import cats.implicits._ import docspell.common._ -import docspell.store.impl.Column -import docspell.store.impl.Implicits._ +import docspell.store.qb.DSL._ +import docspell.store.qb._ import docspell.store.records._ import doobie._ -import doobie.implicits._ object QCustomField { + private val f = RCustomField.as("f") + private val v = RCustomFieldValue.as("v") case class CustomFieldData(field: RCustomField, usageCount: Int) @@ -19,46 +19,57 @@ object QCustomField { coll: Ident, nameQuery: Option[String] ): ConnectionIO[Vector[CustomFieldData]] = - findFragment(coll, nameQuery, None).query[CustomFieldData].to[Vector] + findFragment(coll, nameQuery, None).build.query[CustomFieldData].to[Vector] def findById(field: Ident, collective: Ident): ConnectionIO[Option[CustomFieldData]] = - findFragment(collective, None, field.some).query[CustomFieldData].option + findFragment(collective, None, field.some).build.query[CustomFieldData].option private def findFragment( coll: Ident, nameQuery: Option[String], fieldId: Option[Ident] - ): Fragment = { - val fId = RCustomField.Columns.id.prefix("f") - val fColl = RCustomField.Columns.cid.prefix("f") - val fName = RCustomField.Columns.name.prefix("f") - val fLabel = RCustomField.Columns.label.prefix("f") - val vField = RCustomFieldValue.Columns.field.prefix("v") + ): Select = { +// val fId = RCustomField.Columns.id.prefix("f") +// val fColl = RCustomField.Columns.cid.prefix("f") +// val fName = RCustomField.Columns.name.prefix("f") +// val fLabel = RCustomField.Columns.label.prefix("f") +// val vField = RCustomFieldValue.Columns.field.prefix("v") +// +// val join = RCustomField.table ++ fr"f LEFT OUTER JOIN" ++ +// RCustomFieldValue.table ++ fr"v ON" ++ fId.is(vField) +// +// val cols = RCustomField.Columns.all.map(_.prefix("f")) :+ Column("COUNT(v.id)") +// +// val nameCond = nameQuery.map(QueryWildcard.apply) match { +// case Some(q) => +// or(fName.lowerLike(q), and(fLabel.isNotNull, fLabel.lowerLike(q))) +// case None => +// Fragment.empty +// } +// val fieldCond = fieldId match { +// case Some(id) => +// fId.is(id) +// case None => +// Fragment.empty +// } +// val cond = and(fColl.is(coll), nameCond, fieldCond) +// +// val group = NonEmptyList.fromList(RCustomField.Columns.all) match { +// case Some(nel) => groupBy(nel.map(_.prefix("f"))) +// case None => Fragment.empty +// } +// +// selectSimple(cols, join, cond) ++ group - val join = RCustomField.table ++ fr"f LEFT OUTER JOIN" ++ - RCustomFieldValue.table ++ fr"v ON" ++ fId.is(vField) - - val cols = RCustomField.Columns.all.map(_.prefix("f")) :+ Column("COUNT(v.id)") - - val nameCond = nameQuery.map(QueryWildcard.apply) match { - case Some(q) => - or(fName.lowerLike(q), and(fLabel.isNotNull, fLabel.lowerLike(q))) - case None => - Fragment.empty - } - val fieldCond = fieldId match { - case Some(id) => - fId.is(id) - case None => - Fragment.empty - } - val cond = and(fColl.is(coll), nameCond, fieldCond) - - val group = NonEmptyList.fromList(RCustomField.Columns.all) match { - case Some(nel) => groupBy(nel.map(_.prefix("f"))) - case None => Fragment.empty + val nameFilter = nameQuery.map { q => + f.name.likes(q) || (f.label.isNotNull && f.label.like(q)) } - selectSimple(cols, join, cond) ++ group + Select( + f.all.map(_.s).append(count(v.id).as("num")), + from(f).leftJoin(v, f.id === v.field), + f.cid === coll &&? nameFilter &&? fieldId.map(fid => f.id === fid), + GroupBy(f.all) + ) } } diff --git a/modules/store/src/main/scala/docspell/store/queries/QFolder.scala b/modules/store/src/main/scala/docspell/store/queries/QFolder.scala index 79b6341f..08192cf3 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QFolder.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QFolder.scala @@ -183,68 +183,62 @@ object QFolder { // inner join user_ u on u.uid = s.owner // where s.cid = 'eike'; - val user = RUser.as("u") - val member = RFolderMember.as("m") - val folder = RFolder.as("s") - val memlogin = TableDef("memberlogin") - val memloginFolder = member.folder.inTable(memlogin) - val memloginLogn = user.login.inTable(memlogin) + val user = RUser.as("u") + val member = RFolderMember.as("m") + val folder = RFolder.as("s") + val memlogin = TableDef("memberlogin") + val mlFolder = Column[Ident]("folder", memlogin) + val mlLogin = Column[Ident]("login", memlogin) - val sql = - withCte( - memlogin -> union( - Select( - select(member.folder, user.login), - from(member) - .innerJoin(user, user.uid === member.user) - .innerJoin(folder, folder.id === member.folder), - folder.collective === account.collective - ), - Select( - select(folder.id, user.login), - from(folder) - .innerJoin(user, user.uid === folder.owner), - folder.collective === account.collective - ) + withCte( + memlogin -> union( + Select( + select(member.folder.as(mlFolder), user.login.as(mlLogin)), + from(member) + .innerJoin(user, user.uid === member.user) + .innerJoin(folder, folder.id === member.folder), + folder.collective === account.collective + ), + Select( + select(folder.id.as(mlFolder), user.login.as(mlLogin)), + from(folder) + .innerJoin(user, user.uid === folder.owner), + folder.collective === account.collective ) ) - .select( + )( + Select( + select( + folder.id.s, + folder.name.s, + folder.owner.s, + user.login.s, + folder.created.s, Select( - select( - folder.id.s, - folder.name.s, - folder.owner.s, - user.login.s, - folder.created.s, - Select( - select(countAll > 0), - from(memlogin), - memloginFolder === folder.id && memloginLogn === account.user - ).as("member"), - Select( - select(countAll - 1), - from(memlogin), - memloginFolder === folder.id - ).as("member_count") - ), - from(folder) - .innerJoin(user, user.uid === folder.owner), - where( - folder.collective === account.collective &&? - idQ.map(id => folder.id === id) &&? - nameQ.map(q => folder.name.like(s"%${q.toLowerCase}%")) &&? - ownerLogin.map(login => user.login === login) - ) - ).orderBy(folder.name.asc) + select(countAll > 0), + from(memlogin), + mlFolder === folder.id && mlLogin === account.user + ).as("member"), + Select( + select(countAll - 1), + from(memlogin), + mlFolder === folder.id + ).as("member_count") + ), + from(folder) + .innerJoin(user, user.uid === folder.owner), + where( + folder.collective === account.collective &&? + idQ.map(id => folder.id === id) &&? + nameQ.map(q => folder.name.like(s"%${q.toLowerCase}%")) &&? + ownerLogin.map(login => user.login === login) ) - - sql.run - .query[FolderItem] - .to[Vector] + ).orderBy(folder.name.asc) + ).build.query[FolderItem].to[Vector] } /** Select all folder_id where the given account is member or owner. */ - def findMemberFolderIds(account: AccountId): Fragment = { + def findMemberFolderIds(account: AccountId): Select = { val user = RUser.as("u") val f = RFolder.as("f") val m = RFolderMember.as("m") @@ -261,11 +255,11 @@ object QFolder { .innerJoin(user, user.uid === m.user), f.collective === account.collective && user.login === account.user ) - ).run + ) } def getMemberFolders(account: AccountId): ConnectionIO[Set[Ident]] = - findMemberFolderIds(account).query[Ident].to[Set] + findMemberFolderIds(account).build.query[Ident].to[Set] private def findUserId(account: AccountId): ConnectionIO[Option[Ident]] = RUser.findByAccount(account).map(_.map(_.uid)) diff --git a/modules/store/src/main/scala/docspell/store/queries/QItem.scala b/modules/store/src/main/scala/docspell/store/queries/QItem.scala index b7c50f44..da2981e9 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QItem.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QItem.scala @@ -12,6 +12,7 @@ import docspell.common.{IdRef, _} import docspell.store.Store import docspell.store.impl.Implicits._ import docspell.store.impl._ +import docspell.store.qb.Select import docspell.store.records._ import bitpeace.FileMeta @@ -94,10 +95,10 @@ object QItem { val f = RFolder.as("f") val IC = RItem.Columns.all.map(_.prefix("i")) - val OC = org.all.map(_.column) - val P0C = pers0.all.map(_.column) - val P1C = pers1.all.map(_.column) - val EC = equip.all.map(_.oldColumn).map(_.prefix("e")) + val OC = org.all.map(_.column).toList + val P0C = pers0.all.map(_.column).toList + val P1C = pers1.all.map(_.column).toList + val EC = equip.all.map(_.oldColumn).map(_.prefix("e")).toList val ICC = List(RItem.Columns.id, RItem.Columns.name).map(_.prefix("ref")) val FC = List(f.id.column, f.name.column) @@ -172,23 +173,17 @@ object QItem { def findCustomFieldValuesForItem( itemId: Ident ): ConnectionIO[Vector[ItemFieldValue]] = { - val cfId = RCustomField.Columns.id.prefix("cf") - val cfName = RCustomField.Columns.name.prefix("cf") - val cfLabel = RCustomField.Columns.label.prefix("cf") - val cfType = RCustomField.Columns.ftype.prefix("cf") - val cvItem = RCustomFieldValue.Columns.itemId.prefix("cvf") - val cvValue = RCustomFieldValue.Columns.value.prefix("cvf") - val cvField = RCustomFieldValue.Columns.field.prefix("cvf") + import docspell.store.qb.DSL._ - val cfFrom = - RCustomFieldValue.table ++ fr"cvf INNER JOIN" ++ RCustomField.table ++ fr"cf ON" ++ cvField - .is(cfId) + val cf = RCustomField.as("cf") + val cv = RCustomFieldValue.as("cvf") - selectSimple( - Seq(cfId, cfName, cfLabel, cfType, cvValue), - cfFrom, - cvItem.is(itemId) - ).query[ItemFieldValue].to[Vector] + Select( + select(cf.id, cf.name, cf.label, cf.ftype, cv.value), + from(cv) + .innerJoin(cf, cf.id === cv.field), + cv.itemId === itemId + ).build.query[ItemFieldValue].to[Vector] } case class ListItem( @@ -287,31 +282,30 @@ object QItem { private def findCustomFieldValuesForColl( coll: Ident, - cv: Seq[CustomValue] + values: Seq[CustomValue] ): Seq[(String, Fragment)] = { - val cfId = RCustomField.Columns.id.prefix("cf") - val cfName = RCustomField.Columns.name.prefix("cf") - val cfColl = RCustomField.Columns.cid.prefix("cf") - val cvValue = RCustomFieldValue.Columns.value.prefix("cvf") - val cvField = RCustomFieldValue.Columns.field.prefix("cvf") - val cvItem = RCustomFieldValue.Columns.itemId.prefix("cvf") + import docspell.store.qb.DSL._ - val cfFrom = - RCustomFieldValue.table ++ fr"cvf INNER JOIN" ++ RCustomField.table ++ fr"cf ON" ++ cvField - .is(cfId) + val cf = RCustomField.as("cf") + val cv = RCustomFieldValue.as("cv") def singleSelect(v: CustomValue) = - selectSimple( - Seq(cvItem), - cfFrom, - and( - cfColl.is(coll), - or(cfName.is(v.field), cfId.is(v.field)), - cvValue.lowerLike(QueryWildcard(v.value.toLowerCase)) + Select( + cv.itemId.s, + from(cv).innerJoin(cf, cv.field === cf.id), + where( + cf.cid === coll && + (cf.name === v.field || cf.id === v.field) && + cv.value.like(QueryWildcard(v.value.toLowerCase)) ) ) - if (cv.isEmpty) Seq.empty - else Seq("customvalues" -> cv.map(singleSelect).reduce(_ ++ fr"INTERSECT" ++ _)) + + NonEmptyList.fromList(values.toList) match { + case Some(nel) => + Seq("customvalues" -> intersect(nel.map(singleSelect)).build) + case None => + Seq.empty + } } private def findItemsBase( @@ -326,13 +320,14 @@ object QItem { val pers0 = RPerson.as("p0") val pers1 = RPerson.as("p1") val f = RFolder.as("f1") + val cv = RCustomFieldValue.as("cv") val IC = RItem.Columns val AC = RAttachment.Columns val itemCols = IC.all val equipCols = List(equip.eid.oldColumn, equip.name.oldColumn) val folderCols = List(f.id.oldColumn, f.name.oldColumn) - val cvItem = RCustomFieldValue.Columns.itemId.prefix("cv") + val cvItem = cv.itemId.column val finalCols = commas( Seq( @@ -504,7 +499,7 @@ object QItem { .getOrElse(IC.id.prefix("i").is("")) ) .getOrElse(Fragment.empty), - or(iFolder.isNull, iFolder.isIn(QFolder.findMemberFolderIds(q.account))) + or(iFolder.isNull, iFolder.isIn(QFolder.findMemberFolderIds(q.account).build)) ) val order = q.orderAsc match { diff --git a/modules/store/src/main/scala/docspell/store/queries/QJob.scala b/modules/store/src/main/scala/docspell/store/queries/QJob.scala index 815f9de6..c85080a1 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QJob.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QJob.scala @@ -100,20 +100,20 @@ object QJob { val sql1 = Select( - List(max(JC.group).as("g")), + max(JC.group).as("g"), from(JC).innerJoin(G, JC.group === G.group), G.worker === worker && stateCond ) val sql2 = - Select(List(min(JC.group).as("g")), from(JC), stateCond) + Select(min(JC.group).as("g"), from(JC), stateCond) val gcol = Column[String]("g", TableDef("")) val groups = Select(select(gcol), fromSubSelect(union(sql1, sql2)).as("t0"), gcol.isNull.negate) // either 0, one or two results, but may be empty if RJob table is empty - groups.run.query[Ident].to[List].map(_.headOption) + groups.build.query[Ident].to[List].map(_.headOption) } private def stuckTriggerValue(t: RJob.Table, initialPause: Duration, now: Timestamp) = @@ -144,7 +144,7 @@ object QJob { (JC.state === stuck && stuckTrigger < now.toMillis)) ).orderBy(JC.state.asc, psort, JC.submitted.asc).limit(1) - sql.run.query[RJob].option + sql.build.query[RJob].option } def setCancelled[F[_]: Effect](id: Ident, store: Store[F]): F[Unit] = @@ -215,13 +215,13 @@ object QJob { select(JC.all), from(JC), JC.group === collective && JC.state.in(running) - ).orderBy(JC.submitted.desc).run.query[RJob].stream + ).orderBy(JC.submitted.desc).build.query[RJob].stream val waitingJobs = Select( select(JC.all), from(JC), JC.group === collective && JC.state.in(waiting) && JC.submitted > refDate - ).orderBy(JC.submitted.desc).run.query[RJob].stream.take(max) + ).orderBy(JC.submitted.desc).build.query[RJob].stream.take(max) val doneJobs = Select( select(JC.all), @@ -231,7 +231,7 @@ object QJob { JC.state.in(JobState.done), JC.submitted > refDate ) - ).orderBy(JC.submitted.desc).run.query[RJob].stream.take(max) + ).orderBy(JC.submitted.desc).build.query[RJob].stream.take(max) runningJobs ++ waitingJobs ++ doneJobs } diff --git a/modules/store/src/main/scala/docspell/store/queries/QMails.scala b/modules/store/src/main/scala/docspell/store/queries/QMails.scala index 06b528fa..30d476af 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QMails.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QMails.scala @@ -65,7 +65,7 @@ object QMails { mUser ) - (cols, from) + (cols.toList, from) } } diff --git a/modules/store/src/main/scala/docspell/store/queries/QOrganization.scala b/modules/store/src/main/scala/docspell/store/queries/QOrganization.scala index 6d41a841..585d2fd0 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QOrganization.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QOrganization.scala @@ -32,7 +32,7 @@ object QOrganization { org.cid === coll &&? valFilter ).orderBy(order(org)) - sql.run + sql.build .query[(ROrganization, Option[RContact])] .stream .groupAdjacentBy(_._1) @@ -86,7 +86,7 @@ object QOrganization { pers.cid === coll &&? valFilter ).orderBy(order(pers)) - sql.run + sql.build .query[(RPerson, Option[ROrganization], Option[RContact])] .stream .groupAdjacentBy(_._1) diff --git a/modules/store/src/main/scala/docspell/store/queries/QPeriodicTask.scala b/modules/store/src/main/scala/docspell/store/queries/QPeriodicTask.scala index 46d8a273..9cccf026 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QPeriodicTask.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QPeriodicTask.scala @@ -49,7 +49,7 @@ object QPeriodicTask { case None => RT.enabled === true } val sql = - Select(select(RT.all), from(RT), where).orderBy(RT.nextrun.asc).run + Select(select(RT.all), from(RT), where).orderBy(RT.nextrun.asc).build sql.query[RPeriodicTask].streamWithChunkSize(2).take(1).compile.last } diff --git a/modules/store/src/main/scala/docspell/store/records/RContact.scala b/modules/store/src/main/scala/docspell/store/records/RContact.scala index 40053665..e625a86c 100644 --- a/modules/store/src/main/scala/docspell/store/records/RContact.scala +++ b/modules/store/src/main/scala/docspell/store/records/RContact.scala @@ -1,5 +1,7 @@ package docspell.store.records +import cats.data.NonEmptyList + import docspell.common._ import docspell.store.qb.DSL._ import docspell.store.qb._ @@ -27,7 +29,7 @@ object RContact { val personId = Column[Ident]("pid", this) val orgId = Column[Ident]("oid", this) val created = Column[Timestamp]("created", this) - val all = List[Column[_]](contactId, value, kind, personId, orgId, created) + val all = NonEmptyList.of[Column[_]](contactId, value, kind, personId, orgId, created) } private val T = Table(None) diff --git a/modules/store/src/main/scala/docspell/store/records/RCustomField.scala b/modules/store/src/main/scala/docspell/store/records/RCustomField.scala index f74c7cc3..e63c6b9c 100644 --- a/modules/store/src/main/scala/docspell/store/records/RCustomField.scala +++ b/modules/store/src/main/scala/docspell/store/records/RCustomField.scala @@ -1,10 +1,11 @@ package docspell.store.records +import cats.data.NonEmptyList import cats.implicits._ import docspell.common._ -import docspell.store.impl.Column -import docspell.store.impl.Implicits._ +import docspell.store.qb.DSL._ +import docspell.store.qb._ import doobie._ import doobie.implicits._ @@ -19,58 +20,63 @@ case class RCustomField( ) object RCustomField { + final case class Table(alias: Option[String]) extends TableDef { + val tableName = "custom_field" - val table = fr"custom_field" + val id = Column[Ident]("id", this) + val name = Column[Ident]("name", this) + val label = Column[String]("label", this) + val cid = Column[Ident]("cid", this) + val ftype = Column[CustomFieldType]("ftype", this) + val created = Column[Timestamp]("created", this) - object Columns { - - val id = Column("id") - val name = Column("name") - val label = Column("label") - val cid = Column("cid") - val ftype = Column("ftype") - val created = Column("created") - - val all = List(id, name, label, cid, ftype, created) + val all = NonEmptyList.of[Column[_]](id, name, label, cid, ftype, created) } - import Columns._ - def insert(value: RCustomField): ConnectionIO[Int] = { - val sql = insertRow( - table, - Columns.all, + val T = Table(None) + def as(alias: String): Table = + Table(Some(alias)) + + def insert(value: RCustomField): ConnectionIO[Int] = + DML.insert( + T, + T.all, fr"${value.id},${value.name},${value.label},${value.cid},${value.ftype},${value.created}" ) - sql.update.run - } def exists(fname: Ident, coll: Ident): ConnectionIO[Boolean] = - selectCount(id, table, and(name.is(fname), cid.is(coll))).query[Int].unique.map(_ > 0) + run(select(count(T.id)), from(T), T.name === fname && T.cid === coll) + .query[Int] + .unique + .map(_ > 0) def findById(fid: Ident, coll: Ident): ConnectionIO[Option[RCustomField]] = - selectSimple(all, table, and(id.is(fid), cid.is(coll))).query[RCustomField].option + run(select(T.all), from(T), T.id === fid && T.cid === coll).query[RCustomField].option def findByIdOrName(idOrName: Ident, coll: Ident): ConnectionIO[Option[RCustomField]] = - selectSimple(all, table, and(cid.is(coll), or(id.is(idOrName), name.is(idOrName)))) - .query[RCustomField] - .option + Select( + select(T.all), + from(T), + T.cid === coll && (T.id === idOrName || T.name === idOrName) + ).build.query[RCustomField].option def deleteById(fid: Ident, coll: Ident): ConnectionIO[Int] = - deleteFrom(table, and(id.is(fid), cid.is(coll))).update.run + DML.delete(T, T.id === fid && T.cid === coll) def findAll(coll: Ident): ConnectionIO[Vector[RCustomField]] = - selectSimple(all, table, cid.is(coll)).query[RCustomField].to[Vector] + run(select(T.all), from(T), T.cid === coll).query[RCustomField].to[Vector] def update(value: RCustomField): ConnectionIO[Int] = - updateRow( - table, - and(id.is(value.id), cid.is(value.cid)), - commas( - name.setTo(value.name), - label.setTo(value.label), - ftype.setTo(value.ftype) + DML + .update( + T, + T.id === value.id && T.cid === value.cid, + DML.set( + T.name.setTo(value.name), + T.label.setTo(value.label), + T.ftype.setTo(value.ftype) + ) ) - ).update.run def setValue(f: RCustomField, item: Ident, fval: String): ConnectionIO[Int] = for { diff --git a/modules/store/src/main/scala/docspell/store/records/RCustomFieldValue.scala b/modules/store/src/main/scala/docspell/store/records/RCustomFieldValue.scala index 8830dc58..3a5eaa2c 100644 --- a/modules/store/src/main/scala/docspell/store/records/RCustomFieldValue.scala +++ b/modules/store/src/main/scala/docspell/store/records/RCustomFieldValue.scala @@ -3,8 +3,8 @@ package docspell.store.records import cats.data.NonEmptyList import docspell.common._ -import docspell.store.impl.Column -import docspell.store.impl.Implicits._ +import docspell.store.qb.DSL._ +import docspell.store.qb._ import doobie._ import doobie.implicits._ @@ -17,51 +17,51 @@ case class RCustomFieldValue( ) object RCustomFieldValue { + final case class Table(alias: Option[String]) extends TableDef { + val tableName = "custom_field_value" - val table = fr"custom_field_value" + val id = Column[Ident]("id", this) + val itemId = Column[Ident]("item_id", this) + val field = Column[Ident]("field", this) + val value = Column[String]("field_value", this) - object Columns { - - val id = Column("id") - val itemId = Column("item_id") - val field = Column("field") - val value = Column("field_value") - - val all = List(id, itemId, field, value) + val all = NonEmptyList.of[Column[_]](id, itemId, field, value) } - def insert(value: RCustomFieldValue): ConnectionIO[Int] = { - val sql = insertRow( - table, - Columns.all, + val T = Table(None) + def as(alias: String): Table = + Table(Some(alias)) + + def insert(value: RCustomFieldValue): ConnectionIO[Int] = + DML.insert( + T, + T.all, fr"${value.id},${value.itemId},${value.field},${value.value}" ) - sql.update.run - } def updateValue( fieldId: Ident, item: Ident, value: String ): ConnectionIO[Int] = - updateRow( - table, - and(Columns.itemId.is(item), Columns.field.is(fieldId)), - Columns.value.setTo(value) - ).update.run + DML.update( + T, + T.itemId === item && T.field === fieldId, + DML.set(T.value.setTo(value)) + ) def countField(fieldId: Ident): ConnectionIO[Int] = - selectCount(Columns.id, table, Columns.field.is(fieldId)).query[Int].unique + Select(count(T.id).s, from(T), T.field === fieldId).build.query[Int].unique def deleteByField(fieldId: Ident): ConnectionIO[Int] = - deleteFrom(table, Columns.field.is(fieldId)).update.run + DML.delete(T, T.field === fieldId) def deleteByItem(item: Ident): ConnectionIO[Int] = - deleteFrom(table, Columns.itemId.is(item)).update.run + DML.delete(T, T.itemId === item) def deleteValue(fieldId: Ident, items: NonEmptyList[Ident]): ConnectionIO[Int] = - deleteFrom( - table, - and(Columns.field.is(fieldId), Columns.itemId.isIn(items)) - ).update.run + DML.delete( + T, + T.field === fieldId && T.itemId.in(items) + ) } diff --git a/modules/store/src/main/scala/docspell/store/records/REquipment.scala b/modules/store/src/main/scala/docspell/store/records/REquipment.scala index 3e0276ea..d1cfa83a 100644 --- a/modules/store/src/main/scala/docspell/store/records/REquipment.scala +++ b/modules/store/src/main/scala/docspell/store/records/REquipment.scala @@ -1,5 +1,7 @@ package docspell.store.records +import cats.data.NonEmptyList + import docspell.common._ import docspell.store.qb.DSL._ import docspell.store.qb._ @@ -24,7 +26,7 @@ object REquipment { val name = Column[String]("name", this) val created = Column[Timestamp]("created", this) val updated = Column[Timestamp]("updated", this) - val all = List(eid, cid, name, created, updated) + val all = NonEmptyList.of[Column[_]](eid, cid, name, created, updated) } val T = Table(None) @@ -81,13 +83,13 @@ object REquipment { .map(str => s"%${str.toLowerCase}%") .map(v => t.name.like(v)) - val sql = Select(select(t.all), from(t), q).orderBy(order(t)).run + val sql = Select(select(t.all), from(t), q).orderBy(order(t)).build sql.query[REquipment].to[Vector] } def findLike(coll: Ident, equipName: String): ConnectionIO[Vector[IdRef]] = { val t = Table(None) - run(select(List(t.eid, t.name)), from(t), t.cid === coll && t.name.like(equipName)) + run(select(t.eid, t.name), from(t), t.cid === coll && t.name.like(equipName)) .query[IdRef] .to[Vector] } diff --git a/modules/store/src/main/scala/docspell/store/records/RFolder.scala b/modules/store/src/main/scala/docspell/store/records/RFolder.scala index d678fe9f..a83ef1a6 100644 --- a/modules/store/src/main/scala/docspell/store/records/RFolder.scala +++ b/modules/store/src/main/scala/docspell/store/records/RFolder.scala @@ -1,5 +1,6 @@ package docspell.store.records +import cats.data.NonEmptyList import cats.effect._ import cats.implicits._ @@ -35,7 +36,7 @@ object RFolder { val owner = Column[Ident]("owner", this) val created = Column[Timestamp]("created", this) - val all = List(id, name, collective, owner, created) + val all = NonEmptyList.of[Column[_]](id, name, collective, owner, created) } val T = Table(None) @@ -75,7 +76,7 @@ object RFolder { val nameFilter = nameQ.map(n => T.name.like(s"%${n.toLowerCase}%")) val sql = Select(select(T.all), from(T), T.collective === coll &&? nameFilter) .orderBy(order(T)) - sql.run.query[RFolder].to[Vector] + sql.build.query[RFolder].to[Vector] } def delete(folderId: Ident): ConnectionIO[Int] = diff --git a/modules/store/src/main/scala/docspell/store/records/RFolderMember.scala b/modules/store/src/main/scala/docspell/store/records/RFolderMember.scala index f16d880d..2e38ab74 100644 --- a/modules/store/src/main/scala/docspell/store/records/RFolderMember.scala +++ b/modules/store/src/main/scala/docspell/store/records/RFolderMember.scala @@ -1,5 +1,6 @@ package docspell.store.records +import cats.data.NonEmptyList import cats.effect._ import cats.implicits._ @@ -33,7 +34,7 @@ object RFolderMember { val user = Column[Ident]("user_id", this) val created = Column[Timestamp]("created", this) - val all = List(id, folder, user, created) + val all = NonEmptyList.of[Column[_]](id, folder, user, created) } val T = Table(None) diff --git a/modules/store/src/main/scala/docspell/store/records/RFtsMigration.scala b/modules/store/src/main/scala/docspell/store/records/RFtsMigration.scala index b2f21930..18183b1b 100644 --- a/modules/store/src/main/scala/docspell/store/records/RFtsMigration.scala +++ b/modules/store/src/main/scala/docspell/store/records/RFtsMigration.scala @@ -1,5 +1,6 @@ package docspell.store.records +import cats.data.NonEmptyList import cats.effect._ import cats.implicits._ @@ -39,7 +40,7 @@ object RFtsMigration { val description = Column[String]("description", this) val created = Column[Timestamp]("created", this) - val all = List(id, version, ftsEngine, description, created) + val all = NonEmptyList.of[Column[_]](id, version, ftsEngine, description, created) } val T = Table(None) diff --git a/modules/store/src/main/scala/docspell/store/records/RInvitation.scala b/modules/store/src/main/scala/docspell/store/records/RInvitation.scala index f3243566..bacecc0f 100644 --- a/modules/store/src/main/scala/docspell/store/records/RInvitation.scala +++ b/modules/store/src/main/scala/docspell/store/records/RInvitation.scala @@ -1,5 +1,6 @@ package docspell.store.records +import cats.data.NonEmptyList import cats.effect.Sync import cats.implicits._ @@ -18,7 +19,7 @@ object RInvitation { val id = Column[Ident]("id", this) val created = Column[Timestamp]("created", this) - val all = List(id, created) + val all = NonEmptyList.of[Column[_]](id, created) } val T = Table(None) diff --git a/modules/store/src/main/scala/docspell/store/records/RItem.scala b/modules/store/src/main/scala/docspell/store/records/RItem.scala index 980cd324..eba4db3a 100644 --- a/modules/store/src/main/scala/docspell/store/records/RItem.scala +++ b/modules/store/src/main/scala/docspell/store/records/RItem.scala @@ -7,6 +7,7 @@ import cats.implicits._ import docspell.common._ import docspell.store.impl.Implicits._ import docspell.store.impl._ +import docspell.store.qb.{Select, TableDef} import doobie._ import doobie.implicits._ @@ -63,6 +64,51 @@ object RItem { None ) + final case class Table(alias: Option[String]) extends TableDef { + import docspell.store.qb.Column + val tableName = "item" + + val id = Column[Ident]("itemid", this) + val cid = Column[Ident]("cid", this) + val name = Column[String]("name", this) + val itemDate = Column[Timestamp]("itemdate", this) + val source = Column[String]("source", this) + val incoming = Column[Direction]("incoming", this) + val state = Column[ItemState]("state", this) + val corrOrg = Column[Ident]("corrorg", this) + val corrPerson = Column[Ident]("corrperson", this) + val concPerson = Column[Ident]("concperson", this) + val concEquipment = Column[Ident]("concequipment", this) + val inReplyTo = Column[Ident]("inreplyto", this) + val dueDate = Column[Timestamp]("duedate", this) + val created = Column[Timestamp]("created", this) + val updated = Column[Timestamp]("updated", this) + val notes = Column[String]("notes", this) + val folder = Column[Ident]("folder_id", this) + val all = NonEmptyList.of[Column[_]]( + id, + cid, + name, + itemDate, + source, + incoming, + state, + corrOrg, + corrPerson, + concPerson, + concEquipment, + inReplyTo, + dueDate, + created, + updated, + notes, + folder + ) + } + val T = Table(None) + def as(alias: String): Table = + Table(Some(alias)) + val table = fr"item" object Columns { @@ -349,9 +395,12 @@ object RItem { updateRow(table, folder.is(folderId), folder.setTo(empty)).update.run } - def filterItemsFragment(items: NonEmptyList[Ident], coll: Ident): Fragment = - selectSimple(Seq(id), table, and(cid.is(coll), id.isIn(items))) + def filterItemsFragment(items: NonEmptyList[Ident], coll: Ident): Select = { + import docspell.store.qb.DSL._ + + Select(select(T.id), from(T), T.cid === coll && T.id.in(items)) + } def filterItems(items: NonEmptyList[Ident], coll: Ident): ConnectionIO[Vector[Ident]] = - filterItemsFragment(items, coll).query[Ident].to[Vector] + filterItemsFragment(items, coll).build.query[Ident].to[Vector] } diff --git a/modules/store/src/main/scala/docspell/store/records/RJob.scala b/modules/store/src/main/scala/docspell/store/records/RJob.scala index 5f7b8850..e9112c5a 100644 --- a/modules/store/src/main/scala/docspell/store/records/RJob.scala +++ b/modules/store/src/main/scala/docspell/store/records/RJob.scala @@ -90,7 +90,7 @@ object RJob { val started = Column[Timestamp]("started", this) val startedmillis = Column[Long]("startedmillis", this) val finished = Column[Timestamp]("finished", this) - val all = List( + val all = NonEmptyList.of[Column[_]]( id, task, group, @@ -263,7 +263,7 @@ object RJob { def selectGroupInState(states: NonEmptyList[JobState]): ConnectionIO[Vector[Ident]] = { val sql = Select(select(T.group), from(T), T.state.in(states)).orderBy(T.group) - sql.run.query[Ident].to[Vector] + sql.build.query[Ident].to[Vector] } def delete(jobId: Ident): ConnectionIO[Int] = diff --git a/modules/store/src/main/scala/docspell/store/records/RJobGroupUse.scala b/modules/store/src/main/scala/docspell/store/records/RJobGroupUse.scala index 9cf4aec4..8763753c 100644 --- a/modules/store/src/main/scala/docspell/store/records/RJobGroupUse.scala +++ b/modules/store/src/main/scala/docspell/store/records/RJobGroupUse.scala @@ -1,5 +1,6 @@ package docspell.store.records +import cats.data.NonEmptyList import cats.implicits._ import docspell.common._ @@ -17,7 +18,7 @@ object RJobGroupUse { val group = Column[Ident]("groupid", this) val worker = Column[Ident]("workerid", this) - val all = List(group, worker) + val all = NonEmptyList.of[Column[_]](group, worker) } val T = Table(None) diff --git a/modules/store/src/main/scala/docspell/store/records/RJobLog.scala b/modules/store/src/main/scala/docspell/store/records/RJobLog.scala index 999e9570..3e14bda7 100644 --- a/modules/store/src/main/scala/docspell/store/records/RJobLog.scala +++ b/modules/store/src/main/scala/docspell/store/records/RJobLog.scala @@ -1,5 +1,7 @@ package docspell.store.records +import cats.data.NonEmptyList + import docspell.common._ import docspell.store.qb.DSL._ import docspell.store.qb._ @@ -24,7 +26,7 @@ object RJobLog { val level = Column[LogLevel]("level", this) val created = Column[Timestamp]("created", this) val message = Column[String]("message", this) - val all = List(id, jobId, level, created, message) + val all = NonEmptyList.of[Column[_]](id, jobId, level, created, message) // separate column only for sorting, so not included in `all` and // the case class @@ -45,7 +47,7 @@ object RJobLog { def findLogs(id: Ident): ConnectionIO[Vector[RJobLog]] = Select(select(T.all), from(T), T.jobId === id) .orderBy(T.created.asc, T.counter.asc) - .run + .build .query[RJobLog] .to[Vector] diff --git a/modules/store/src/main/scala/docspell/store/records/RNode.scala b/modules/store/src/main/scala/docspell/store/records/RNode.scala index 8bc7bff1..4c3838fe 100644 --- a/modules/store/src/main/scala/docspell/store/records/RNode.scala +++ b/modules/store/src/main/scala/docspell/store/records/RNode.scala @@ -1,5 +1,6 @@ package docspell.store.records +import cats.data.NonEmptyList import cats.effect.Sync import cats.implicits._ @@ -31,7 +32,7 @@ object RNode { val url = Column[LenientUri]("url", this) val updated = Column[Timestamp]("updated", this) val created = Column[Timestamp]("created", this) - val all = List(id, nodeType, url, updated, created) + val all = NonEmptyList.of[Column[_]](id, nodeType, url, updated, created) } def as(alias: String): Table = diff --git a/modules/store/src/main/scala/docspell/store/records/ROrganization.scala b/modules/store/src/main/scala/docspell/store/records/ROrganization.scala index 2aa0a743..0e8efafd 100644 --- a/modules/store/src/main/scala/docspell/store/records/ROrganization.scala +++ b/modules/store/src/main/scala/docspell/store/records/ROrganization.scala @@ -1,6 +1,7 @@ package docspell.store.records import cats.Eq +import cats.data.NonEmptyList import fs2.Stream import docspell.common.{IdRef, _} @@ -40,7 +41,19 @@ object ROrganization { val notes = Column[String]("notes", this) val created = Column[Timestamp]("created", this) val updated = Column[Timestamp]("updated", this) - val all = List(oid, cid, name, street, zip, city, country, notes, created, updated) + val all = + NonEmptyList.of[Column[_]]( + oid, + cid, + name, + street, + zip, + city, + country, + notes, + created, + updated + ) } val T = Table(None) @@ -120,7 +133,7 @@ object ROrganization { order: Table => Column[_] ): Stream[ConnectionIO, ROrganization] = { val sql = Select(select(T.all), from(T), T.cid === coll).orderBy(order(T)) - sql.run.query[ROrganization].stream + sql.build.query[ROrganization].stream } def findAllRef( @@ -131,7 +144,7 @@ object ROrganization { val nameFilter = nameQ.map(s => T.name.like(s"%${s.toLowerCase}%")) val sql = Select(select(T.oid, T.name), from(T), T.cid === coll &&? nameFilter) .orderBy(order(T)) - sql.run.query[IdRef].to[Vector] + sql.build.query[IdRef].to[Vector] } def delete(id: Ident, coll: Ident): ConnectionIO[Int] = diff --git a/modules/store/src/main/scala/docspell/store/records/RPeriodicTask.scala b/modules/store/src/main/scala/docspell/store/records/RPeriodicTask.scala index e0dcdb3f..8e0383cf 100644 --- a/modules/store/src/main/scala/docspell/store/records/RPeriodicTask.scala +++ b/modules/store/src/main/scala/docspell/store/records/RPeriodicTask.scala @@ -1,5 +1,6 @@ package docspell.store.records +import cats.data.NonEmptyList import cats.effect._ import cats.implicits._ @@ -123,7 +124,7 @@ object RPeriodicTask { val timer = Column[CalEvent]("timer", this) val nextrun = Column[Timestamp]("nextrun", this) val created = Column[Timestamp]("created", this) - val all = List( + val all = NonEmptyList.of[Column[_]]( id, enabled, task, diff --git a/modules/store/src/main/scala/docspell/store/records/RPerson.scala b/modules/store/src/main/scala/docspell/store/records/RPerson.scala index 04eb7831..cb2d2909 100644 --- a/modules/store/src/main/scala/docspell/store/records/RPerson.scala +++ b/modules/store/src/main/scala/docspell/store/records/RPerson.scala @@ -46,7 +46,7 @@ object RPerson { val created = Column[Timestamp]("created", this) val updated = Column[Timestamp]("updated", this) val oid = Column[Ident]("oid", this) - val all = List( + val all = NonEmptyList.of[Column[_]]( pid, cid, name, @@ -150,7 +150,7 @@ object RPerson { order: Table => Column[_] ): Stream[ConnectionIO, RPerson] = { val sql = Select(select(T.all), from(T), T.cid === coll).orderBy(order(T)) - sql.run.query[RPerson].stream + sql.build.query[RPerson].stream } def findAllRef( @@ -163,7 +163,7 @@ object RPerson { val sql = Select(select(T.pid, T.name), from(T), T.cid === coll &&? nameFilter) .orderBy(order(T)) - sql.run.query[IdRef].to[Vector] + sql.build.query[IdRef].to[Vector] } def delete(personId: Ident, coll: Ident): ConnectionIO[Int] = diff --git a/modules/store/src/main/scala/docspell/store/records/RRememberMe.scala b/modules/store/src/main/scala/docspell/store/records/RRememberMe.scala index 464c0576..1e0606ff 100644 --- a/modules/store/src/main/scala/docspell/store/records/RRememberMe.scala +++ b/modules/store/src/main/scala/docspell/store/records/RRememberMe.scala @@ -1,5 +1,6 @@ package docspell.store.records +import cats.data.NonEmptyList import cats.effect.Sync import cats.implicits._ @@ -21,7 +22,7 @@ object RRememberMe { val username = Column[Ident]("login", this) val created = Column[Timestamp]("created", this) val uses = Column[Int]("uses", this) - val all = List(id, cid, username, created, uses) + val all = NonEmptyList.of[Column[_]](id, cid, username, created, uses) } private val T = Table(None) diff --git a/modules/store/src/main/scala/docspell/store/records/RSentMail.scala b/modules/store/src/main/scala/docspell/store/records/RSentMail.scala index cd4aa224..f2aa1d72 100644 --- a/modules/store/src/main/scala/docspell/store/records/RSentMail.scala +++ b/modules/store/src/main/scala/docspell/store/records/RSentMail.scala @@ -92,7 +92,7 @@ object RSentMail { val body = Column[String]("body", this) val created = Column[Timestamp]("created", this) - val all = List( + val all = NonEmptyList.of[Column[_]]( id, uid, messageId, diff --git a/modules/store/src/main/scala/docspell/store/records/RSentMailItem.scala b/modules/store/src/main/scala/docspell/store/records/RSentMailItem.scala index 04dffc0b..d648567b 100644 --- a/modules/store/src/main/scala/docspell/store/records/RSentMailItem.scala +++ b/modules/store/src/main/scala/docspell/store/records/RSentMailItem.scala @@ -1,5 +1,6 @@ package docspell.store.records +import cats.data.NonEmptyList import cats.effect._ import cats.implicits._ @@ -37,7 +38,7 @@ object RSentMailItem { val sentMailId = Column[Ident]("sentmail_id", this) val created = Column[Timestamp]("created", this) - val all = List( + val all = NonEmptyList.of[Column[_]]( id, itemId, sentMailId, @@ -60,7 +61,7 @@ object RSentMailItem { DML.delete(T, T.sentMailId === mailId) def findSentMailIdsByItem(item: Ident): ConnectionIO[Set[Ident]] = - run(select(Seq(T.sentMailId)), from(T), T.itemId === item).query[Ident].to[Set] + run(select(T.sentMailId.s), from(T), T.itemId === item).query[Ident].to[Set] def deleteAllByItem(item: Ident): ConnectionIO[Int] = DML.delete(T, T.itemId === item) diff --git a/modules/store/src/main/scala/docspell/store/records/RSource.scala b/modules/store/src/main/scala/docspell/store/records/RSource.scala index 11b87270..25847d50 100644 --- a/modules/store/src/main/scala/docspell/store/records/RSource.scala +++ b/modules/store/src/main/scala/docspell/store/records/RSource.scala @@ -1,5 +1,7 @@ package docspell.store.records +import cats.data.NonEmptyList + import docspell.common._ import docspell.store.qb.DSL._ import docspell.store.qb._ @@ -41,7 +43,7 @@ object RSource { val fileFilter = Column[Glob]("file_filter", this) val all = - List( + NonEmptyList.of[Column[_]]( sid, cid, abbrev, @@ -123,7 +125,7 @@ object RSource { order: Table => Column[_] ): Fragment = { val t = RSource.as("s") - Select(select(t.all), from(t), t.cid === coll).orderBy(order(t)).run + Select(select(t.all), from(t), t.cid === coll).orderBy(order(t)).build } def delete(sourceId: Ident, coll: Ident): ConnectionIO[Int] = diff --git a/modules/store/src/main/scala/docspell/store/records/RTag.scala b/modules/store/src/main/scala/docspell/store/records/RTag.scala index 577e6e5d..777bcc1a 100644 --- a/modules/store/src/main/scala/docspell/store/records/RTag.scala +++ b/modules/store/src/main/scala/docspell/store/records/RTag.scala @@ -27,7 +27,7 @@ object RTag { val name = Column[String]("name", this) val category = Column[String]("category", this) val created = Column[Timestamp]("created", this) - val all = List[Column[_]](tid, cid, name, category, created) + val all = NonEmptyList.of[Column[_]](tid, cid, name, category, created) } val T = Table(None) def as(alias: String): Table = @@ -75,7 +75,7 @@ object RTag { val nameFilter = nameQ.map(s => T.name.like(s"%${s.toLowerCase}%")) val sql = Select(select(T.all), from(T), T.cid === coll &&? nameFilter).orderBy(order(T)) - sql.run.query[RTag].to[Vector] + sql.build.query[RTag].to[Vector] } def findAllById(ids: List[Ident]): ConnectionIO[Vector[RTag]] = @@ -97,7 +97,7 @@ object RTag { from(t).innerJoin(ti, ti.tagId === t.tid), ti.itemId === itemId ).orderBy(t.name.asc) - sql.run.query[RTag].to[Vector] + sql.build.query[RTag].to[Vector] } def findBySource(source: Ident): ConnectionIO[Vector[RTag]] = { @@ -109,7 +109,7 @@ object RTag { from(t).innerJoin(s, s.tagId === t.tid), s.sourceId === source ).orderBy(t.name.asc) - sql.run.query[RTag].to[Vector] + sql.build.query[RTag].to[Vector] } def findAllByNameOrId( diff --git a/modules/store/src/main/scala/docspell/store/records/RTagItem.scala b/modules/store/src/main/scala/docspell/store/records/RTagItem.scala index b702beb3..5e9f4eb2 100644 --- a/modules/store/src/main/scala/docspell/store/records/RTagItem.scala +++ b/modules/store/src/main/scala/docspell/store/records/RTagItem.scala @@ -19,7 +19,7 @@ object RTagItem { val tagItemId = Column[Ident]("tagitemid", this) val itemId = Column[Ident]("itemid", this) val tagId = Column[Ident]("tid", this) - val all = List(tagItemId, itemId, tagId) + val all = NonEmptyList.of[Column[_]](tagItemId, itemId, tagId) } val t = Table(None) def as(alias: String): Table = @@ -31,16 +31,8 @@ object RTagItem { def deleteItemTags(item: Ident): ConnectionIO[Int] = DML.delete(t, t.itemId === item) - def deleteItemTags(items: NonEmptyList[Ident], cid: Ident): ConnectionIO[Int] = { - print(cid) - DML.delete(t, t.itemId.in(items)) - //TODO match those of the collective - //val itemsFiltered = - // RItem.filterItemsFragment(items, cid) - //val sql = fr"DELETE FROM" ++ Fragment.const(t.tableName) ++ fr"WHERE" ++ - // t.itemId.isIn(itemsFiltered) - //sql.update.run - } + def deleteItemTags(items: NonEmptyList[Ident], cid: Ident): ConnectionIO[Int] = + DML.delete(t, t.itemId.in(RItem.filterItemsFragment(items, cid))) def deleteTag(tid: Ident): ConnectionIO[Int] = DML.delete(t, t.tagId === tid) diff --git a/modules/store/src/main/scala/docspell/store/records/RTagSource.scala b/modules/store/src/main/scala/docspell/store/records/RTagSource.scala index 8558c98a..86f6faff 100644 --- a/modules/store/src/main/scala/docspell/store/records/RTagSource.scala +++ b/modules/store/src/main/scala/docspell/store/records/RTagSource.scala @@ -1,5 +1,6 @@ package docspell.store.records +import cats.data.NonEmptyList import cats.effect.Sync import cats.implicits._ @@ -19,7 +20,7 @@ object RTagSource { val id = Column[Ident]("id", this) val sourceId = Column[Ident]("source_id", this) val tagId = Column[Ident]("tag_id", this) - val all = List(id, sourceId, tagId) + val all = NonEmptyList.of[Column[_]](id, sourceId, tagId) } private val t = Table(None) diff --git a/modules/store/src/main/scala/docspell/store/records/RUser.scala b/modules/store/src/main/scala/docspell/store/records/RUser.scala index 2862f729..befd4865 100644 --- a/modules/store/src/main/scala/docspell/store/records/RUser.scala +++ b/modules/store/src/main/scala/docspell/store/records/RUser.scala @@ -1,5 +1,7 @@ package docspell.store.records +import cats.data.NonEmptyList + import docspell.common._ import docspell.store.qb.DSL._ import docspell.store.qb._ @@ -34,7 +36,17 @@ object RUser { val created = Column[Timestamp]("created", this) val all = - List(uid, login, cid, password, state, email, loginCount, lastLogin, created) + NonEmptyList.of[Column[_]]( + uid, + login, + cid, + password, + state, + email, + loginCount, + lastLogin, + created + ) } def as(alias: String): Table = @@ -83,7 +95,7 @@ object RUser { def findAll(coll: Ident, order: Table => Column[_]): ConnectionIO[Vector[RUser]] = { val t = Table(None) - val sql = Select(select(t.all), from(t), t.cid === coll).orderBy(order(t)).run + val sql = Select(select(t.all), from(t), t.cid === coll).orderBy(order(t)).build sql.query[RUser].to[Vector] } diff --git a/modules/store/src/main/scala/docspell/store/records/RUserEmail.scala b/modules/store/src/main/scala/docspell/store/records/RUserEmail.scala index 17935f08..ca9cf28a 100644 --- a/modules/store/src/main/scala/docspell/store/records/RUserEmail.scala +++ b/modules/store/src/main/scala/docspell/store/records/RUserEmail.scala @@ -1,6 +1,6 @@ package docspell.store.records -import cats.data.OptionT +import cats.data.{NonEmptyList, OptionT} import cats.effect._ import cats.implicits._ @@ -118,7 +118,7 @@ object RUserEmail { val mailReplyTo = Column[MailAddress]("mail_replyto", this) val created = Column[Timestamp]("created", this) - val all = List( + val all = NonEmptyList.of[Column[_]]( id, uid, name, @@ -188,7 +188,7 @@ object RUserEmail { user.cid === accId.collective && user.login === accId.user &&? nameFilter ).orderBy(email.name) - sql.run.query[RUserEmail] + sql.build.query[RUserEmail] } def findByAccount( diff --git a/modules/store/src/main/scala/docspell/store/records/RUserImap.scala b/modules/store/src/main/scala/docspell/store/records/RUserImap.scala index d40b9c03..d7e9edc0 100644 --- a/modules/store/src/main/scala/docspell/store/records/RUserImap.scala +++ b/modules/store/src/main/scala/docspell/store/records/RUserImap.scala @@ -1,6 +1,6 @@ package docspell.store.records -import cats.data.OptionT +import cats.data.{NonEmptyList, OptionT} import cats.effect._ import cats.implicits._ @@ -106,7 +106,7 @@ object RUserImap { val imapCertCheck = Column[Boolean]("imap_certcheck", this) val created = Column[Timestamp]("created", this) - val all = List( + val all = NonEmptyList.of[Column[_]]( id, uid, name, @@ -173,7 +173,7 @@ object RUserImap { select(m.all), from(m).innerJoin(u, m.uid === u.uid), u.cid === accId.collective && u.login === accId.user &&? nameFilter - ).orderBy(m.name).run + ).orderBy(m.name).build sql.query[RUserImap] } diff --git a/modules/store/src/test/scala/docspell/store/qb/QueryBuilderTest.scala b/modules/store/src/test/scala/docspell/store/qb/QueryBuilderTest.scala index f13550a7..e35c73d9 100644 --- a/modules/store/src/test/scala/docspell/store/qb/QueryBuilderTest.scala +++ b/modules/store/src/test/scala/docspell/store/qb/QueryBuilderTest.scala @@ -1,7 +1,6 @@ package docspell.store.qb import minitest._ -import docspell.store.qb._ import docspell.store.qb.model._ import docspell.store.qb.DSL._ @@ -31,9 +30,16 @@ object QueryBuilderTest extends SimpleTestSuite { val q = Select(proj, tables, cond).orderBy(c.name.desc) q match { - case Select.Ordered(Select.SimpleSelect(proj, from, where, group), sb, vempty) => + case Select.Ordered( + Select.SimpleSelect(false, proj, from, where, group), + sb, + vempty + ) => assert(vempty.isEmpty) - assertEquals(sb, OrderBy(SelectExpr.SelectColumn(c.name), OrderBy.OrderType.Desc)) + assertEquals( + sb, + OrderBy(SelectExpr.SelectColumn(c.name, None), OrderBy.OrderType.Desc) + ) assertEquals(11, proj.size) from match { case FromExpr.From(_) => @@ -55,6 +61,8 @@ object QueryBuilderTest extends SimpleTestSuite { case _ => fail("Unexpected join result") } + case _ => + fail("Unexpected result") } assertEquals(group, None) assert(where.isDefined) diff --git a/modules/store/src/test/scala/docspell/store/qb/impl/DoobieQueryTest.scala b/modules/store/src/test/scala/docspell/store/qb/impl/SelectBuilderTest.scala similarity index 90% rename from modules/store/src/test/scala/docspell/store/qb/impl/DoobieQueryTest.scala rename to modules/store/src/test/scala/docspell/store/qb/impl/SelectBuilderTest.scala index 2d920352..f3fc8a9e 100644 --- a/modules/store/src/test/scala/docspell/store/qb/impl/DoobieQueryTest.scala +++ b/modules/store/src/test/scala/docspell/store/qb/impl/SelectBuilderTest.scala @@ -5,7 +5,7 @@ import docspell.store.qb._ import docspell.store.qb.model._ import docspell.store.qb.DSL._ -object DoobieQueryTest extends SimpleTestSuite { +object SelectBuilderTest extends SimpleTestSuite { test("basic fragment") { val c = CourseRecord.as("c") @@ -25,7 +25,7 @@ object DoobieQueryTest extends SimpleTestSuite { val frag = SelectBuilder(q) assertEquals( frag.toString, - """Fragment("SELECT c.id, c.name, c.owner_id, c.lecturer_id, c.lessons FROM course c INNER JOIN person o ON c.owner_id = o.id LEFT JOIN person l ON c.lecturer_id = l.id WHERE (LOWER(c.name) LIKE ? AND o.name = ? )")""" + """Fragment("SELECT c.id, c.name, c.owner_id, c.lecturer_id, c.lessons FROM course c INNER JOIN person o ON c.owner_id = o.id LEFT JOIN person l ON c.lecturer_id = l.id WHERE (LOWER(c.name) LIKE ? AND o.name = ? )")""" ) } diff --git a/modules/store/src/test/scala/docspell/store/qb/model/CourseRecord.scala b/modules/store/src/test/scala/docspell/store/qb/model/CourseRecord.scala index 2024fd1f..6b53fdfb 100644 --- a/modules/store/src/test/scala/docspell/store/qb/model/CourseRecord.scala +++ b/modules/store/src/test/scala/docspell/store/qb/model/CourseRecord.scala @@ -1,5 +1,6 @@ package docspell.store.qb.model +import cats.data.NonEmptyList import docspell.store.qb._ case class CourseRecord( @@ -22,7 +23,7 @@ object CourseRecord { val lecturerId = Column[Long]("lecturer_id", this) val lessons = Column[Int]("lessons", this) - val all = List(id, name, ownerId, lecturerId, lessons) + val all = NonEmptyList.of[Column[_]](id, name, ownerId, lecturerId, lessons) } def as(alias: String): Table = diff --git a/modules/store/src/test/scala/docspell/store/qb/model/PersonRecord.scala b/modules/store/src/test/scala/docspell/store/qb/model/PersonRecord.scala index a328c6a8..5ea5b653 100644 --- a/modules/store/src/test/scala/docspell/store/qb/model/PersonRecord.scala +++ b/modules/store/src/test/scala/docspell/store/qb/model/PersonRecord.scala @@ -1,5 +1,6 @@ package docspell.store.qb.model +import cats.data.NonEmptyList import docspell.store.qb._ import docspell.common._ @@ -15,7 +16,7 @@ object PersonRecord { val name = Column[String]("name", this) val created = Column[Timestamp]("created", this) - val all = List(id, name, created) + val all = NonEmptyList.of[Column[_]](id, name, created) } def as(alias: String): Table =