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 48a7f051..c30e790d 100644 --- a/modules/store/src/main/scala/docspell/store/qb/DML.scala +++ b/modules/store/src/main/scala/docspell/store/qb/DML.scala @@ -74,6 +74,10 @@ object DML { case Setter.Increment(column, amount) => val colFrag = SelectExprBuilder.columnNoPrefix(column) colFrag ++ fr" =" ++ colFrag ++ fr" + $amount" + + case Setter.Decrement(column, amount) => + val colFrag = SelectExprBuilder.columnNoPrefix(column) + colFrag ++ fr" =" ++ colFrag ++ fr" - $amount" } def set(s: Setter[_], more: Setter[_]*): Nel[Setter[_]] = 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 7e388075..dbddbdc9 100644 --- a/modules/store/src/main/scala/docspell/store/qb/DSL.scala +++ b/modules/store/src/main/scala/docspell/store/qb/DSL.scala @@ -127,6 +127,9 @@ trait DSL extends DoobieMeta { def increment(amount: Int): Setter[A] = Setter.Increment(col, amount) + def decrement(amount: Int): Setter[A] = + Setter.Decrement(col, amount) + def asc: OrderBy = OrderBy(SelectExpr.SelectColumn(col, None), OrderBy.OrderType.Asc) @@ -177,6 +180,9 @@ trait DSL extends DoobieMeta { def ===(other: Column[A]): Condition = Condition.CompareCol(col, Operator.Eq, other) + + def <>(other: Column[A]): Condition = + Condition.CompareCol(col, Operator.Neq, other) } implicit final class ConditionOps(c: Condition) { diff --git a/modules/store/src/main/scala/docspell/store/qb/Setter.scala b/modules/store/src/main/scala/docspell/store/qb/Setter.scala index d86af800..b6808e55 100644 --- a/modules/store/src/main/scala/docspell/store/qb/Setter.scala +++ b/modules/store/src/main/scala/docspell/store/qb/Setter.scala @@ -13,5 +13,6 @@ object Setter { extends Setter[A] case class Increment[A](column: Column[A], amount: Int) extends Setter[A] + case class Decrement[A](column: Column[A], amount: Int) extends Setter[A] } diff --git a/modules/store/src/main/scala/docspell/store/queries/QAttachment.scala b/modules/store/src/main/scala/docspell/store/queries/QAttachment.scala index 86ae26f4..f1aae89a 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QAttachment.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QAttachment.scala @@ -180,14 +180,17 @@ object QAttachment { val iId = RItem.Columns.id.prefix("i") val iColl = RItem.Columns.cid.prefix("i") val iFolder = RItem.Columns.folder.prefix("i") - val cId = RCollective.Columns.id.prefix("c") - val cLang = RCollective.Columns.language.prefix("c") + val c = RCollective.as("c") + val cId = c.id.column + val cLang = c.language.column val cols = Seq(aId, aItem, iColl, iFolder, cLang, aName, mContent) val from = RAttachment.table ++ fr"a INNER JOIN" ++ RAttachmentMeta.table ++ fr"m ON" ++ aId.is(mId) ++ fr"INNER JOIN" ++ RItem.table ++ fr"i ON" ++ iId.is(aItem) ++ - fr"INNER JOIN" ++ RCollective.table ++ fr"c ON" ++ cId.is(iColl) + fr"INNER JOIN" ++ Fragment.const(RCollective.T.tableName) ++ fr"c ON" ++ cId.is( + iColl + ) val where = coll.map(cid => iColl.is(cid)).getOrElse(Fragment.empty) 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 da2981e9..81bb78b0 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QItem.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QItem.scala @@ -633,27 +633,31 @@ object QItem { limit: Option[Int], states: Set[ItemState] ): Fragment = { - val IC = RItem.Columns.all.map(_.prefix("i")) - val aItem = RAttachment.Columns.itemId.prefix("a") - val aId = RAttachment.Columns.id.prefix("a") - val aFileId = RAttachment.Columns.fileId.prefix("a") - val iId = RItem.Columns.id.prefix("i") - val iState = RItem.Columns.state.prefix("i") - val sId = RAttachmentSource.Columns.id.prefix("s") - val sFileId = RAttachmentSource.Columns.fileId.prefix("s") - val rId = RAttachmentArchive.Columns.id.prefix("r") - val rFileId = RAttachmentArchive.Columns.fileId.prefix("r") - val m1Id = RFileMeta.Columns.id.prefix("m1") - val m2Id = RFileMeta.Columns.id.prefix("m2") - val m3Id = RFileMeta.Columns.id.prefix("m3") + val IC = RItem.Columns.all.map(_.prefix("i")) + val aItem = RAttachment.Columns.itemId.prefix("a") + val aId = RAttachment.Columns.id.prefix("a") + val aFileId = RAttachment.Columns.fileId.prefix("a") + val iId = RItem.Columns.id.prefix("i") + val iState = RItem.Columns.state.prefix("i") + val sId = RAttachmentSource.Columns.id.prefix("s") + val sFileId = RAttachmentSource.Columns.fileId.prefix("s") + val rId = RAttachmentArchive.Columns.id.prefix("r") + val rFileId = RAttachmentArchive.Columns.fileId.prefix("r") + val m1 = RFileMeta.as("m1") + val m2 = RFileMeta.as("m2") + val m3 = RFileMeta.as("m3") + val m1Id = m1.id.column + val m2Id = m2.id.column + val m3Id = m3.id.column + val filemetaTable = Fragment.const(RFileMeta.T.tableName) val from = RItem.table ++ fr"i INNER JOIN" ++ RAttachment.table ++ fr"a ON" ++ aItem.is(iId) ++ fr"INNER JOIN" ++ RAttachmentSource.table ++ fr"s ON" ++ aId.is(sId) ++ - fr"INNER JOIN" ++ RFileMeta.table ++ fr"m1 ON" ++ m1Id.is(aFileId) ++ - fr"INNER JOIN" ++ RFileMeta.table ++ fr"m2 ON" ++ m2Id.is(sFileId) ++ + fr"INNER JOIN" ++ filemetaTable ++ fr"m1 ON" ++ m1Id.is(aFileId) ++ + fr"INNER JOIN" ++ filemetaTable ++ fr"m2 ON" ++ m2Id.is(sFileId) ++ fr"LEFT OUTER JOIN" ++ RAttachmentArchive.table ++ fr"r ON" ++ aId.is(rId) ++ - fr"LEFT OUTER JOIN" ++ RFileMeta.table ++ fr"m3 ON" ++ m3Id.is(rFileId) + fr"LEFT OUTER JOIN" ++ filemetaTable ++ fr"m3 ON" ++ m3Id.is(rFileId) val fileCond = or(m1Id.isIn(fileMetaIds), m2Id.isIn(fileMetaIds), m3Id.isIn(fileMetaIds)) @@ -691,30 +695,33 @@ object QItem { } def findByChecksum(checksum: String, collective: Ident): ConnectionIO[Vector[RItem]] = { - val IC = RItem.Columns.all.map(_.prefix("i")) - val aItem = RAttachment.Columns.itemId.prefix("a") - val aId = RAttachment.Columns.id.prefix("a") - val aFileId = RAttachment.Columns.fileId.prefix("a") - val iId = RItem.Columns.id.prefix("i") - val iColl = RItem.Columns.cid.prefix("i") - val sId = RAttachmentSource.Columns.id.prefix("s") - val sFileId = RAttachmentSource.Columns.fileId.prefix("s") - val rId = RAttachmentArchive.Columns.id.prefix("r") - val rFileId = RAttachmentArchive.Columns.fileId.prefix("r") - val m1Id = RFileMeta.Columns.id.prefix("m1") - val m2Id = RFileMeta.Columns.id.prefix("m2") - val m3Id = RFileMeta.Columns.id.prefix("m3") - val m1Checksum = RFileMeta.Columns.checksum.prefix("m1") - val m2Checksum = RFileMeta.Columns.checksum.prefix("m2") - val m3Checksum = RFileMeta.Columns.checksum.prefix("m3") - + val IC = RItem.Columns.all.map(_.prefix("i")) + val aItem = RAttachment.Columns.itemId.prefix("a") + val aId = RAttachment.Columns.id.prefix("a") + val aFileId = RAttachment.Columns.fileId.prefix("a") + val iId = RItem.Columns.id.prefix("i") + val iColl = RItem.Columns.cid.prefix("i") + val sId = RAttachmentSource.Columns.id.prefix("s") + val sFileId = RAttachmentSource.Columns.fileId.prefix("s") + val rId = RAttachmentArchive.Columns.id.prefix("r") + val rFileId = RAttachmentArchive.Columns.fileId.prefix("r") + val m1 = RFileMeta.as("m1") + val m2 = RFileMeta.as("m2") + val m3 = RFileMeta.as("m3") + val m1Id = m1.id.column + val m2Id = m2.id.column + val m3Id = m3.id.column + val m1Checksum = m1.checksum.column + val m2Checksum = m2.checksum.column + val m3Checksum = m3.checksum.column + val filemetaTable = Fragment.const(RFileMeta.T.tableName) val from = RItem.table ++ fr"i INNER JOIN" ++ RAttachment.table ++ fr"a ON" ++ aItem.is(iId) ++ fr"INNER JOIN" ++ RAttachmentSource.table ++ fr"s ON" ++ aId.is(sId) ++ - fr"INNER JOIN" ++ RFileMeta.table ++ fr"m1 ON" ++ m1Id.is(aFileId) ++ - fr"INNER JOIN" ++ RFileMeta.table ++ fr"m2 ON" ++ m2Id.is(sFileId) ++ + fr"INNER JOIN" ++ filemetaTable ++ fr"m1 ON" ++ m1Id.is(aFileId) ++ + fr"INNER JOIN" ++ filemetaTable ++ fr"m2 ON" ++ m2Id.is(sFileId) ++ fr"LEFT OUTER JOIN" ++ RAttachmentArchive.table ++ fr"r ON" ++ aId.is(rId) ++ - fr"LEFT OUTER JOIN" ++ RFileMeta.table ++ fr"m3 ON" ++ m3Id.is(rFileId) + fr"LEFT OUTER JOIN" ++ filemetaTable ++ fr"m3 ON" ++ m3Id.is(rFileId) selectSimple( IC, diff --git a/modules/store/src/main/scala/docspell/store/queries/QLogin.scala b/modules/store/src/main/scala/docspell/store/queries/QLogin.scala index 7dfdf59f..08af3265 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QLogin.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QLogin.scala @@ -3,8 +3,8 @@ package docspell.store.queries import cats.data.OptionT import docspell.common._ -import docspell.store.impl.Implicits._ -import docspell.store.records.RCollective.{Columns => CC} +import docspell.store.qb.DSL._ +import docspell.store.qb._ import docspell.store.records.{RCollective, RRememberMe, RUser} import doobie._ @@ -22,20 +22,14 @@ object QLogin { ) def findUser(acc: AccountId): ConnectionIO[Option[Data]] = { - val user = RUser.as("u") - val ucid = user.cid.column - val login = user.login.column - val pass = user.password.column - val ustate = user.state.column - val cstate = CC.state.prefix("c") - val ccid = CC.id.prefix("c") - - val sql = selectSimple( - List(ucid, login, pass, cstate, ustate), - Fragment.const(user.tableName) ++ fr"u, " ++ RCollective.table ++ fr"c", - and(ucid.is(ccid), login.is(acc.user), ucid.is(acc.collective)) - ) - + val user = RUser.as("u") + val coll = RCollective.as("c") + val sql = + Select( + select(user.cid, user.login, user.password, coll.state, user.state), + from(user).innerJoin(coll, user.cid === coll.id), + user.login === acc.user && user.cid === acc.collective + ).build logger.trace(s"SQL : $sql") sql.query[Data].option } diff --git a/modules/store/src/main/scala/docspell/store/records/RAttachment.scala b/modules/store/src/main/scala/docspell/store/records/RAttachment.scala index e9d5d935..26372748 100644 --- a/modules/store/src/main/scala/docspell/store/records/RAttachment.scala +++ b/modules/store/src/main/scala/docspell/store/records/RAttachment.scala @@ -5,8 +5,9 @@ import cats.implicits._ import fs2.Stream import docspell.common._ -import docspell.store.impl.Implicits._ -import docspell.store.impl._ +import docspell.store.qb.DSL._ +import docspell.store.qb.TableDef +import docspell.store.qb._ import bitpeace.FileMeta import doobie._ @@ -22,10 +23,27 @@ case class RAttachment( ) {} object RAttachment { + final case class Table(alias: Option[String]) extends TableDef { + val tableName = "attachment" + + val id = Column[Ident]("attachid", this) + val itemId = Column[Ident]("itemid", this) + val fileId = Column[Ident]("filemetaid", this) + val position = Column[Int]("position", this) + val created = Column[Timestamp]("created", this) + val name = Column[String]("name", this) + val all = NonEmptyList.of[Column[_]](id, itemId, fileId, position, created, name) + } + + val T = Table(None) + def as(alias: String): Table = + Table(Some(alias)) val table = fr"attachment" object Columns { + import docspell.store.impl._ + val id = Column("attachid") val itemId = Column("itemid") val fileId = Column("filemetaid") @@ -34,32 +52,37 @@ object RAttachment { val name = Column("name") val all = List(id, itemId, fileId, position, created, name) } - import Columns._ def insert(v: RAttachment): ConnectionIO[Int] = - insertRow( - table, - all, + DML.insert( + T, + T.all, fr"${v.id},${v.itemId},${v.fileId.id},${v.position},${v.created},${v.name}" - ).update.run + ) def decPositions(iId: Ident, lowerBound: Int, upperBound: Int): ConnectionIO[Int] = - updateRow( - table, - and(itemId.is(iId), position.isGte(lowerBound), position.isLte(upperBound)), - position.decrement(1) - ).update.run + DML.update( + T, + where( + T.itemId === iId && T.position >= lowerBound && T.position <= upperBound + ), + DML.set(T.position.decrement(1)) + ) def incPositions(iId: Ident, lowerBound: Int, upperBound: Int): ConnectionIO[Int] = - updateRow( - table, - and(itemId.is(iId), position.isGte(lowerBound), position.isLte(upperBound)), - position.increment(1) - ).update.run + DML.update( + T, + where( + T.itemId === iId && T.position >= lowerBound && T.position <= upperBound + ), + DML.set(T.position.increment(1)) + ) def nextPosition(id: Ident): ConnectionIO[Int] = for { - max <- selectSimple(position.max, table, itemId.is(id)).query[Option[Int]].unique + max <- Select(max(T.position).s, from(T), T.itemId === id).build + .query[Option[Int]] + .unique } yield max.map(_ + 1).getOrElse(0) def updateFileIdAndName( @@ -67,41 +90,49 @@ object RAttachment { fId: Ident, fname: Option[String] ): ConnectionIO[Int] = - updateRow( - table, - id.is(attachId), - commas(fileId.setTo(fId), name.setTo(fname)) - ).update.run + DML.update( + T, + T.id === attachId, + DML.set(T.fileId.setTo(fId), T.name.setTo(fname)) + ) def updateFileId( attachId: Ident, fId: Ident ): ConnectionIO[Int] = - updateRow( - table, - id.is(attachId), - fileId.setTo(fId) - ).update.run + DML.update( + T, + T.id === attachId, + DML.set(T.fileId.setTo(fId)) + ) def updatePosition(attachId: Ident, pos: Int): ConnectionIO[Int] = - updateRow(table, id.is(attachId), position.setTo(pos)).update.run + DML.update(T, T.id === attachId, DML.set(T.position.setTo(pos))) def findById(attachId: Ident): ConnectionIO[Option[RAttachment]] = - selectSimple(all, table, id.is(attachId)).query[RAttachment].option + run(select(T.all), from(T), T.id === attachId).query[RAttachment].option def findMeta(attachId: Ident): ConnectionIO[Option[FileMeta]] = { import bitpeace.sql._ - val cols = RFileMeta.Columns.all.map(_.prefix("m")) - val aId = id.prefix("a") - val aFileMeta = fileId.prefix("a") - val mId = RFileMeta.Columns.id.prefix("m") - - val from = - table ++ fr"a INNER JOIN" ++ RFileMeta.table ++ fr"m ON" ++ aFileMeta.is(mId) - val cond = aId.is(attachId) - - selectSimple(cols, from, cond).query[FileMeta].option +// val cols = RFileMeta.Columns.all.map(_.prefix("m")) +// val aId = id.prefix("a") +// val aFileMeta = fileId.prefix("a") +// val mId = RFileMeta.Columns.id.prefix("m") +// +// val from = +// table ++ fr"a INNER JOIN" ++ RFileMeta.table ++ fr"m ON" ++ aFileMeta.is(mId) +// val cond = aId.is(attachId) +// +// selectSimple(cols, from, cond).query[FileMeta].option + val m = RFileMeta.as("m") + val a = RAttachment.as("a") + Select( + select(m.all), + from(a) + .innerJoin(m, a.fileId === m.id), + a.id === attachId + ).build.query[FileMeta].option } def updateName( @@ -109,7 +140,7 @@ object RAttachment { collective: Ident, aname: Option[String] ): ConnectionIO[Int] = { - val update = updateRow(table, id.is(attachId), name.setTo(aname)).update.run + val update = DML.update(T, T.id === attachId, DML.set(T.name.setTo(aname))) for { exists <- existsByIdAndCollective(attachId, collective) n <- if (exists) update else 0.pure[ConnectionIO] @@ -119,44 +150,59 @@ object RAttachment { def findByIdAndCollective( attachId: Ident, collective: Ident - ): ConnectionIO[Option[RAttachment]] = - selectSimple( - all.map(_.prefix("a")), - table ++ fr"a," ++ RItem.table ++ fr"i", - and( - fr"a.itemid = i.itemid", - id.prefix("a").is(attachId), - RItem.Columns.cid.prefix("i").is(collective) - ) - ).query[RAttachment].option + ): ConnectionIO[Option[RAttachment]] = { + val a = RAttachment.as("a") + val i = RItem.as("i") + Select( + select(a.all), + from(a).innerJoin(i, a.itemId === i.id), + a.id === attachId && i.cid === collective + ).build.query[RAttachment].option + } def findByItem(id: Ident): ConnectionIO[Vector[RAttachment]] = - selectSimple(all, table, itemId.is(id)).query[RAttachment].to[Vector] + run(select(T.all), from(T), T.itemId === id).query[RAttachment].to[Vector] def existsByIdAndCollective( attachId: Ident, collective: Ident ): ConnectionIO[Boolean] = { - val aId = id.prefix("a") - val aItem = itemId.prefix("a") - val iId = RItem.Columns.id.prefix("i") - val iColl = RItem.Columns.cid.prefix("i") - val from = - table ++ fr"a INNER JOIN" ++ RItem.table ++ fr"i ON" ++ aItem.is(iId) - val cond = and(iColl.is(collective), aId.is(attachId)) - selectCount(id, from, cond).query[Int].unique.map(_ > 0) +// val aId = id.prefix("a") +// val aItem = itemId.prefix("a") +// val iId = RItem.Columns.id.prefix("i") +// val iColl = RItem.Columns.cid.prefix("i") +// val from = +// table ++ fr"a INNER JOIN" ++ RItem.table ++ fr"i ON" ++ aItem.is(iId) +// val cond = and(iColl.is(collective), aId.is(attachId)) +// selectCount(id, from, cond).query[Int].unique.map(_ > 0) + val a = RAttachment.as("a") + val i = RItem.as("i") + Select( + count(a.id).s, + from(a) + .innerJoin(i, a.itemId === i.id), + i.cid === collective && a.id === attachId + ).build.query[Int].unique.map(_ > 0) } def findByItemAndCollective( id: Ident, coll: Ident ): ConnectionIO[Vector[RAttachment]] = { - val q = selectSimple(all.map(_.prefix("a")), table ++ fr"a", Fragment.empty) ++ - fr"INNER JOIN" ++ RItem.table ++ fr"i ON" ++ RItem.Columns.id - .prefix("i") - .is(itemId.prefix("a")) ++ - fr"WHERE" ++ and(itemId.prefix("a").is(id), RItem.Columns.cid.prefix("i").is(coll)) - q.query[RAttachment].to[Vector] +// val q = selectSimple(all.map(_.prefix("a")), table ++ fr"a", Fragment.empty) ++ +// fr"INNER JOIN" ++ RItem.table ++ fr"i ON" ++ RItem.Columns.id +// .prefix("i") +// .is(itemId.prefix("a")) ++ +// fr"WHERE" ++ and(itemId.prefix("a").is(id), RItem.Columns.cid.prefix("i").is(coll)) +// q.query[RAttachment].to[Vector] + val a = RAttachment.as("a") + val i = RItem.as("i") + Select( + select(a.all), + from(a) + .innerJoin(i, i.id === a.itemId), + a.itemId === id && i.cid === coll + ).build.query[RAttachment].to[Vector] } def findByItemCollectiveSource( @@ -165,28 +211,42 @@ object RAttachment { fileIds: NonEmptyList[Ident] ): ConnectionIO[Vector[RAttachment]] = { - val iId = RItem.Columns.id.prefix("i") - val iColl = RItem.Columns.cid.prefix("i") - val aItem = Columns.itemId.prefix("a") - val aId = Columns.id.prefix("a") - val aFile = Columns.fileId.prefix("a") - val sId = RAttachmentSource.Columns.id.prefix("s") - val sFile = RAttachmentSource.Columns.fileId.prefix("s") - val rId = RAttachmentArchive.Columns.id.prefix("r") - val rFile = RAttachmentArchive.Columns.fileId.prefix("r") +// val iId = RItem.Columns.id.prefix("i") +// val iColl = RItem.Columns.cid.prefix("i") +// val aItem = Columns.itemId.prefix("a") +// val aId = Columns.id.prefix("a") +// val aFile = Columns.fileId.prefix("a") +// val sId = RAttachmentSource.Columns.id.prefix("s") +// val sFile = RAttachmentSource.Columns.fileId.prefix("s") +// val rId = RAttachmentArchive.Columns.id.prefix("r") +// val rFile = RAttachmentArchive.Columns.fileId.prefix("r") +// +// val from = table ++ fr"a INNER JOIN" ++ +// RItem.table ++ fr"i ON" ++ iId.is(aItem) ++ fr"LEFT JOIN" ++ +// RAttachmentSource.table ++ fr"s ON" ++ sId.is(aId) ++ fr"LEFT JOIN" ++ +// RAttachmentArchive.table ++ fr"r ON" ++ rId.is(aId) +// +// val cond = and( +// iId.is(id), +// iColl.is(coll), +// or(aFile.isIn(fileIds), sFile.isIn(fileIds), rFile.isIn(fileIds)) +// ) +// +// selectSimple(all.map(_.prefix("a")), from, cond).query[RAttachment].to[Vector] + val i = RItem.as("i") + val a = RAttachment.as("a") + val s = RAttachmentSource.as("s") + val r = RAttachmentArchive.as("r") - val from = table ++ fr"a INNER JOIN" ++ - RItem.table ++ fr"i ON" ++ iId.is(aItem) ++ fr"LEFT JOIN" ++ - RAttachmentSource.table ++ fr"s ON" ++ sId.is(aId) ++ fr"LEFT JOIN" ++ - RAttachmentArchive.table ++ fr"r ON" ++ rId.is(aId) - - val cond = and( - iId.is(id), - iColl.is(coll), - or(aFile.isIn(fileIds), sFile.isIn(fileIds), rFile.isIn(fileIds)) - ) - - selectSimple(all.map(_.prefix("a")), from, cond).query[RAttachment].to[Vector] + Select( + select(a.all), + from(a) + .innerJoin(i, i.id === a.itemId) + .leftJoin(s, s.id === a.id) + .leftJoin(r, r.id === a.id), + i.id === id && i.cid === coll && + (a.fileId.in(fileIds) || s.fileId.in(fileIds) || r.fileId.in(fileIds)) + ).build.query[RAttachment].to[Vector] } def findByItemAndCollectiveWithMeta( @@ -195,27 +255,45 @@ object RAttachment { ): ConnectionIO[Vector[(RAttachment, FileMeta)]] = { import bitpeace.sql._ - val cols = all.map(_.prefix("a")) ++ RFileMeta.Columns.all.map(_.prefix("m")) - val afileMeta = fileId.prefix("a") - val aItem = itemId.prefix("a") - val mId = RFileMeta.Columns.id.prefix("m") - val iId = RItem.Columns.id.prefix("i") - val iColl = RItem.Columns.cid.prefix("i") - - val from = - table ++ fr"a INNER JOIN" ++ RFileMeta.table ++ fr"m ON" ++ afileMeta.is(mId) ++ - fr"INNER JOIN" ++ RItem.table ++ fr"i ON" ++ aItem.is(iId) - val cond = Seq(aItem.is(id), iColl.is(coll)) - - selectSimple(cols, from, and(cond)).query[(RAttachment, FileMeta)].to[Vector] +// val cols = all.map(_.prefix("a")) ++ RFileMeta.Columns.all.map(_.prefix("m")) +// val afileMeta = fileId.prefix("a") +// val aItem = itemId.prefix("a") +// val mId = RFileMeta.Columns.id.prefix("m") +// val iId = RItem.Columns.id.prefix("i") +// val iColl = RItem.Columns.cid.prefix("i") +// +// val from = +// table ++ fr"a INNER JOIN" ++ RFileMeta.table ++ fr"m ON" ++ afileMeta.is(mId) ++ +// fr"INNER JOIN" ++ RItem.table ++ fr"i ON" ++ aItem.is(iId) +// val cond = Seq(aItem.is(id), iColl.is(coll)) +// +// selectSimple(cols, from, and(cond)).query[(RAttachment, FileMeta)].to[Vector] + val a = RAttachment.as("a") + val m = RFileMeta.as("m") + val i = RItem.as("i") + Select( + select(a.all, m.all), + from(a) + .innerJoin(m, a.fileId === m.id) + .innerJoin(i, a.itemId === i.id), + a.itemId === id && i.cid === coll + ).build.query[(RAttachment, FileMeta)].to[Vector] } def findByItemWithMeta(id: Ident): ConnectionIO[Vector[(RAttachment, FileMeta)]] = { import bitpeace.sql._ - val q = - fr"SELECT a.*,m.* FROM" ++ table ++ fr"a, filemeta m WHERE a.filemetaid = m.id AND a.itemid = $id ORDER BY a.position ASC" - q.query[(RAttachment, FileMeta)].to[Vector] +// val q = +// fr"SELECT a.*,m.* FROM" ++ table ++ fr"a, filzemeta m +// WHERE a.filemetaid = m.id AND a.itemid = $id ORDER BY a.position ASC" + val a = RAttachment.as("a") + val m = RFileMeta.as("m") + Select( + select(a.all, m.all), + from(a) + .innerJoin(m, a.fileId === m.id), + a.itemId === id + ).orderBy(a.position.asc).build.query[(RAttachment, FileMeta)].to[Vector] } /** Deletes the attachment and its related source and meta records. @@ -225,110 +303,159 @@ object RAttachment { n0 <- RAttachmentMeta.delete(attachId) n1 <- RAttachmentSource.delete(attachId) n2 <- RAttachmentPreview.delete(attachId) - n3 <- deleteFrom(table, id.is(attachId)).update.run + n3 <- DML.delete(T, T.id === attachId) } yield n0 + n1 + n2 + n3 def findItemId(attachId: Ident): ConnectionIO[Option[Ident]] = - selectSimple(Seq(itemId), table, id.is(attachId)).query[Ident].option + Select(T.itemId.s, from(T), T.id === attachId).build.query[Ident].option def findAll( coll: Option[Ident], chunkSize: Int ): Stream[ConnectionIO, RAttachment] = { - val aItem = Columns.itemId.prefix("a") - val iId = RItem.Columns.id.prefix("i") - val iColl = RItem.Columns.cid.prefix("i") - - val cols = all.map(_.prefix("a")) +// val aItem = Columns.itemId.prefix("a") +// val iId = RItem.Columns.id.prefix("i") +// val iColl = RItem.Columns.cid.prefix("i") +// +// val cols = all.map(_.prefix("a")) +// +// coll match { +// case Some(cid) => +// val join = table ++ fr"a INNER JOIN" ++ RItem.table ++ fr"i ON" ++ iId.is(aItem) +// val cond = iColl.is(cid) +// selectSimple(cols, join, cond) +// .query[RAttachment] +// .streamWithChunkSize(chunkSize) +// case None => +// selectSimple(cols, table, Fragment.empty) +// .query[RAttachment] +// .streamWithChunkSize(chunkSize) +// } + val a = RAttachment.as("a") + val i = RItem.as("i") coll match { case Some(cid) => - val join = table ++ fr"a INNER JOIN" ++ RItem.table ++ fr"i ON" ++ iId.is(aItem) - val cond = iColl.is(cid) - selectSimple(cols, join, cond) - .query[RAttachment] - .streamWithChunkSize(chunkSize) + Select( + select(a.all), + from(a) + .innerJoin(i, i.id === a.itemId), + i.cid === cid + ).build.query[RAttachment].streamWithChunkSize(chunkSize) case None => - selectSimple(cols, table, Fragment.empty) + Select(select(a.all), from(a)).build .query[RAttachment] .streamWithChunkSize(chunkSize) } } def findAllWithoutPageCount(chunkSize: Int): Stream[ConnectionIO, RAttachment] = { - val aId = Columns.id.prefix("a") - val aCreated = Columns.created.prefix("a") - val mId = RAttachmentMeta.Columns.id.prefix("m") - val mPages = RAttachmentMeta.Columns.pages.prefix("m") - - val cols = all.map(_.prefix("a")) - val join = table ++ fr"a LEFT OUTER JOIN" ++ - RAttachmentMeta.table ++ fr"m ON" ++ aId.is(mId) - val cond = mPages.isNull - - (selectSimple(cols, join, cond) ++ orderBy(aCreated.desc)) - .query[RAttachment] - .streamWithChunkSize(chunkSize) +// val aId = Columns.id.prefix("a") +// val aCreated = Columns.created.prefix("a") +// val mId = RAttachmentMeta.Columns.id.prefix("m") +// val mPages = RAttachmentMeta.Columns.pages.prefix("m") +// +// val cols = all.map(_.prefix("a")) +// val join = table ++ fr"a LEFT OUTER JOIN" ++ +// RAttachmentMeta.table ++ fr"m ON" ++ aId.is(mId) +// val cond = mPages.isNull +// +// (selectSimple(cols, join, cond) ++ orderBy(aCreated.desc)) +// .query[RAttachment] +// .streamWithChunkSize(chunkSize) + val a = RAttachment.as("a") + val m = RAttachmentMeta.as("m") + Select( + select(a.all), + from(a) + .leftJoin(m, a.id === m.id), + m.pages.isNull + ).build.query[RAttachment].streamWithChunkSize(chunkSize) } def findWithoutPreview( coll: Option[Ident], chunkSize: Int ): Stream[ConnectionIO, RAttachment] = { - val aId = Columns.id.prefix("a") - val aItem = Columns.itemId.prefix("a") - val aCreated = Columns.created.prefix("a") - val pId = RAttachmentPreview.Columns.id.prefix("p") - val iId = RItem.Columns.id.prefix("i") - val iColl = RItem.Columns.cid.prefix("i") +// val aId = Columns.id.prefix("a") +// val aItem = Columns.itemId.prefix("a") +// val aCreated = Columns.created.prefix("a") +// val pId = RAttachmentPreview.Columns.id.prefix("p") +// val iId = RItem.Columns.id.prefix("i") +// val iColl = RItem.Columns.cid.prefix("i") +// +// val cols = all.map(_.prefix("a")) +// val baseJoin = +// table ++ fr"a LEFT OUTER JOIN" ++ +// RAttachmentPreview.table ++ fr"p ON" ++ pId.is(aId) +// +// val baseCond = +// Seq(pId.isNull) +// +// coll match { +// case Some(cid) => +// val join = baseJoin ++ fr"INNER JOIN" ++ RItem.table ++ fr"i ON" ++ iId.is(aItem) +// val cond = and(baseCond ++ Seq(iColl.is(cid))) +// (selectSimple(cols, join, cond) ++ orderBy(aCreated.desc)) +// .query[RAttachment] +// .streamWithChunkSize(chunkSize) +// case None => +// (selectSimple(cols, baseJoin, and(baseCond)) ++ orderBy(aCreated.desc)) +// .query[RAttachment] +// .streamWithChunkSize(chunkSize) +// } + val a = RAttachment.as("a") + val p = RAttachmentPreview.as("p") + val i = RItem.as("i") - val cols = all.map(_.prefix("a")) - val baseJoin = - table ++ fr"a LEFT OUTER JOIN" ++ - RAttachmentPreview.table ++ fr"p ON" ++ pId.is(aId) - - val baseCond = - Seq(pId.isNull) - - coll match { - case Some(cid) => - val join = baseJoin ++ fr"INNER JOIN" ++ RItem.table ++ fr"i ON" ++ iId.is(aItem) - val cond = and(baseCond ++ Seq(iColl.is(cid))) - (selectSimple(cols, join, cond) ++ orderBy(aCreated.desc)) - .query[RAttachment] - .streamWithChunkSize(chunkSize) - case None => - (selectSimple(cols, baseJoin, and(baseCond)) ++ orderBy(aCreated.desc)) - .query[RAttachment] - .streamWithChunkSize(chunkSize) - } + val baseJoin = from(a).leftJoin(p, p.id === a.id) + Select( + select(a.all), + coll.map(_ => baseJoin.innerJoin(i, i.id === a.itemId)).getOrElse(baseJoin), + p.id.isNull &&? coll.map(cid => i.cid === cid) + ).orderBy(a.created.asc).build.query[RAttachment].streamWithChunkSize(chunkSize) } def findNonConvertedPdf( coll: Option[Ident], chunkSize: Int ): Stream[ConnectionIO, RAttachment] = { - val aId = Columns.id.prefix("a") - val aItem = Columns.itemId.prefix("a") - val aFile = Columns.fileId.prefix("a") - val sId = RAttachmentSource.Columns.id.prefix("s") - val sFile = RAttachmentSource.Columns.fileId.prefix("s") - val iId = RItem.Columns.id.prefix("i") - val iColl = RItem.Columns.cid.prefix("i") - val mId = RFileMeta.Columns.id.prefix("m") - val mType = RFileMeta.Columns.mimetype.prefix("m") +// val aId = Columns.id.prefix("a") +// val aItem = Columns.itemId.prefix("a") +// val aFile = Columns.fileId.prefix("a") +// val sId = RAttachmentSource.Columns.id.prefix("s") +// val sFile = RAttachmentSource.Columns.fileId.prefix("s") +// val iId = RItem.Columns.id.prefix("i") +// val iColl = RItem.Columns.cid.prefix("i") +// val mId = RFileMeta.Columns.id.prefix("m") +// val mType = RFileMeta.Columns.mimetype.prefix("m") val pdfType = "application/pdf%" +// +// val from = table ++ fr"a INNER JOIN" ++ +// RAttachmentSource.table ++ fr"s ON" ++ sId.is(aId) ++ fr"INNER JOIN" ++ +// RItem.table ++ fr"i ON" ++ iId.is(aItem) ++ fr"INNER JOIN" ++ +// RFileMeta.table ++ fr"m ON" ++ aFile.is(mId) +// val where = coll match { +// case Some(cid) => and(iColl.is(cid), aFile.is(sFile), mType.lowerLike(pdfType)) +// case None => and(aFile.is(sFile), mType.lowerLike(pdfType)) +// } +// selectSimple(all.map(_.prefix("a")), from, where) +// .query[RAttachment] +// .streamWithChunkSize(chunkSize) + val a = RAttachment.as("a") + val s = RAttachmentSource.as("s") + val i = RItem.as("i") + val m = RFileMeta.as("m") - val from = table ++ fr"a INNER JOIN" ++ - RAttachmentSource.table ++ fr"s ON" ++ sId.is(aId) ++ fr"INNER JOIN" ++ - RItem.table ++ fr"i ON" ++ iId.is(aItem) ++ fr"INNER JOIN" ++ - RFileMeta.table ++ fr"m ON" ++ aFile.is(mId) - val where = coll match { - case Some(cid) => and(iColl.is(cid), aFile.is(sFile), mType.lowerLike(pdfType)) - case None => and(aFile.is(sFile), mType.lowerLike(pdfType)) - } - selectSimple(all.map(_.prefix("a")), from, where) - .query[RAttachment] - .streamWithChunkSize(chunkSize) + Select( + select(a.all), + from(a) + .innerJoin(s, s.id === a.id) + .innerJoin(i, i.id === a.itemId) + .innerJoin(m, m.id === a.fileId), + a.fileId === s.fileId && + m.mimetype.likes(pdfType) &&? + coll.map(cid => i.cid === cid) + ).build.query[RAttachment].streamWithChunkSize(chunkSize) } } diff --git a/modules/store/src/main/scala/docspell/store/records/RAttachmentArchive.scala b/modules/store/src/main/scala/docspell/store/records/RAttachmentArchive.scala index 917741c3..ab97b9b2 100644 --- a/modules/store/src/main/scala/docspell/store/records/RAttachmentArchive.scala +++ b/modules/store/src/main/scala/docspell/store/records/RAttachmentArchive.scala @@ -3,8 +3,9 @@ package docspell.store.records import cats.data.NonEmptyList import docspell.common._ -import docspell.store.impl.Implicits._ -import docspell.store.impl._ +import docspell.store.qb.DSL._ +import docspell.store.qb.TableDef +import docspell.store.qb._ import bitpeace.FileMeta import doobie._ @@ -22,10 +23,25 @@ case class RAttachmentArchive( ) object RAttachmentArchive { + final case class Table(alias: Option[String]) extends TableDef { + val tableName = "attachment_archive" + + val id = Column[Ident]("id", this) + val fileId = Column[Ident]("file_id", this) + val name = Column[String]("filename", this) + val messageId = Column[String]("message_id", this) + val created = Column[Timestamp]("created", this) + + val all = NonEmptyList.of[Column[_]](id, fileId, name, messageId, created) + } + val T = Table(None) + def as(alias: String): Table = + Table(Some(alias)) val table = fr"attachment_archive" - object Columns { + import docspell.store.impl._ + val id = Column("id") val fileId = Column("file_id") val name = Column("filename") @@ -35,64 +51,83 @@ object RAttachmentArchive { val all = List(id, fileId, name, messageId, created) } - import Columns._ - def of(ra: RAttachment, mId: Option[String]): RAttachmentArchive = RAttachmentArchive(ra.id, ra.fileId, ra.name, mId, ra.created) def insert(v: RAttachmentArchive): ConnectionIO[Int] = - insertRow( - table, - all, + DML.insert( + T, + T.all, fr"${v.id},${v.fileId},${v.name},${v.messageId},${v.created}" - ).update.run + ) def findById(attachId: Ident): ConnectionIO[Option[RAttachmentArchive]] = - selectSimple(all, table, id.is(attachId)).query[RAttachmentArchive].option + run(select(T.all), from(T), T.id === attachId).query[RAttachmentArchive].option def delete(attachId: Ident): ConnectionIO[Int] = - deleteFrom(table, id.is(attachId)).update.run + DML.delete(T, T.id === attachId) def deleteAll(fId: Ident): ConnectionIO[Int] = - deleteFrom(table, fileId.is(fId)).update.run + DML.delete(T, T.fileId === fId) def findByIdAndCollective( attachId: Ident, collective: Ident ): ConnectionIO[Option[RAttachmentArchive]] = { - val bId = RAttachment.Columns.id.prefix("b") - val aId = Columns.id.prefix("a") - val bItem = RAttachment.Columns.itemId.prefix("b") - val iId = RItem.Columns.id.prefix("i") - val iColl = RItem.Columns.cid.prefix("i") +// val bId = RAttachment.Columns.id.prefix("b") +// val aId = Columns.id.prefix("a") +// val bItem = RAttachment.Columns.itemId.prefix("b") +// val iId = RItem.Columns.id.prefix("i") +// val iColl = RItem.Columns.cid.prefix("i") +// +// val from = table ++ fr"a INNER JOIN" ++ +// RAttachment.table ++ fr"b ON" ++ aId.is(bId) ++ +// fr"INNER JOIN" ++ RItem.table ++ fr"i ON" ++ bItem.is(iId) +// +// val where = and(aId.is(attachId), bId.is(attachId), iColl.is(collective)) +// +// selectSimple(all.map(_.prefix("a")), from, where).query[RAttachmentArchive].option + val b = RAttachment.as("b") + val a = RAttachmentArchive.as("a") + val i = RItem.as("i") - val from = table ++ fr"a INNER JOIN" ++ - RAttachment.table ++ fr"b ON" ++ aId.is(bId) ++ - fr"INNER JOIN" ++ RItem.table ++ fr"i ON" ++ bItem.is(iId) - - val where = and(aId.is(attachId), bId.is(attachId), iColl.is(collective)) - - selectSimple(all.map(_.prefix("a")), from, where).query[RAttachmentArchive].option + Select( + select(a.all), + from(a) + .innerJoin(b, b.id === a.id) + .innerJoin(i, i.id === b.itemId), + a.id === attachId && b.id === attachId && i.cid === collective + ).build.query[RAttachmentArchive].option } def findByMessageIdAndCollective( messageIds: NonEmptyList[String], collective: Ident ): ConnectionIO[Vector[RAttachmentArchive]] = { - val bId = RAttachment.Columns.id.prefix("b") - val bItem = RAttachment.Columns.itemId.prefix("b") - val aMsgId = Columns.messageId.prefix("a") - val aId = Columns.id.prefix("a") - val iId = RItem.Columns.id.prefix("i") - val iColl = RItem.Columns.cid.prefix("i") - - val from = table ++ fr"a INNER JOIN" ++ - RAttachment.table ++ fr"b ON" ++ aId.is(bId) ++ - fr"INNER JOIN" ++ RItem.table ++ fr"i ON" ++ bItem.is(iId) - - val where = and(aMsgId.isIn(messageIds), iColl.is(collective)) - - selectSimple(all.map(_.prefix("a")), from, where).query[RAttachmentArchive].to[Vector] +// val bId = RAttachment.Columns.id.prefix("b") +// val bItem = RAttachment.Columns.itemId.prefix("b") +// val aMsgId = Columns.messageId.prefix("a") +// val aId = Columns.id.prefix("a") +// val iId = RItem.Columns.id.prefix("i") +// val iColl = RItem.Columns.cid.prefix("i") +// +// val from = table ++ fr"a INNER JOIN" ++ +// RAttachment.table ++ fr"b ON" ++ aId.is(bId) ++ +// fr"INNER JOIN" ++ RItem.table ++ fr"i ON" ++ bItem.is(iId) +// +// val where = and(aMsgId.isIn(messageIds), iColl.is(collective)) +// +// selectSimple(all.map(_.prefix("a")), from, where).query[RAttachmentArchive].to[Vector] + val b = RAttachment.as("b") + val a = RAttachmentArchive.as("a") + val i = RItem.as("i") + Select( + select(a.all), + from(a) + .innerJoin(b, b.id === a.id) + .innerJoin(i, i.id === b.itemId), + a.messageId.in(messageIds) && i.cid === collective + ).build.query[RAttachmentArchive].to[Vector] } def findByItemWithMeta( @@ -100,31 +135,49 @@ object RAttachmentArchive { ): ConnectionIO[Vector[(RAttachmentArchive, FileMeta)]] = { import bitpeace.sql._ - val aId = Columns.id.prefix("a") - val afileMeta = fileId.prefix("a") - val bPos = RAttachment.Columns.position.prefix("b") - val bId = RAttachment.Columns.id.prefix("b") - val bItem = RAttachment.Columns.itemId.prefix("b") - val mId = RFileMeta.Columns.id.prefix("m") - - val cols = all.map(_.prefix("a")) ++ RFileMeta.Columns.all.map(_.prefix("m")) - val from = table ++ fr"a INNER JOIN" ++ - RFileMeta.table ++ fr"m ON" ++ afileMeta.is(mId) ++ fr"INNER JOIN" ++ - RAttachment.table ++ fr"b ON" ++ aId.is(bId) - val where = bItem.is(id) - - (selectSimple(cols, from, where) ++ orderBy(bPos.asc)) - .query[(RAttachmentArchive, FileMeta)] - .to[Vector] +// val aId = Columns.id.prefix("a") +// val afileMeta = fileId.prefix("a") +// val bPos = RAttachment.Columns.position.prefix("b") +// val bId = RAttachment.Columns.id.prefix("b") +// val bItem = RAttachment.Columns.itemId.prefix("b") +// val mId = RFileMeta.as("m").id.column +// +// val cols = all.map(_.prefix("a")) ++ RFileMeta.as("m").all.map(_.column).toList +// val from = table ++ fr"a INNER JOIN" ++ +// Fragment.const(RFileMeta.T.tableName) ++ fr"m ON" ++ afileMeta.is( +// mId +// ) ++ fr"INNER JOIN" ++ +// RAttachment.table ++ fr"b ON" ++ aId.is(bId) +// val where = bItem.is(id) +// +// (selectSimple(cols, from, where) ++ orderBy(bPos.asc)) +// .query[(RAttachmentArchive, FileMeta)] +// .to[Vector] + val a = RAttachmentArchive.as("a") + val b = RAttachment.as("b") + val m = RFileMeta.as("m") + Select( + select(a.all, m.all), + from(a) + .innerJoin(m, a.fileId === m.id) + .innerJoin(b, a.id === b.id), + b.itemId === id + ).orderBy(b.position.asc).build.query[(RAttachmentArchive, FileMeta)].to[Vector] } /** If the given attachment id has an associated archive, this returns * the number of all associated attachments. Returns 0 if there is * no archive for the given attachment. */ - def countEntries(attachId: Ident): ConnectionIO[Int] = { - val qFileId = selectSimple(Seq(fileId), table, id.is(attachId)) - val q = selectCount(id, table, fileId.isSubquery(qFileId)) - q.query[Int].unique - } + def countEntries(attachId: Ident): ConnectionIO[Int] = +// val qFileId = selectSimple(Seq(fileId), table, id.is(attachId)) +// val q = selectCount(id, table, fileId.isSubquery(qFileId)) +// q.query[Int].unique + Select( + count(T.id).s, + from(T), + T.fileId.in(Select(T.fileId.s, from(T), T.id === attachId)) + ).build.query[Int].unique + //TODO this looks strange, can be simplified + } diff --git a/modules/store/src/main/scala/docspell/store/records/RAttachmentMeta.scala b/modules/store/src/main/scala/docspell/store/records/RAttachmentMeta.scala index 5fcd5b93..ad5558b8 100644 --- a/modules/store/src/main/scala/docspell/store/records/RAttachmentMeta.scala +++ b/modules/store/src/main/scala/docspell/store/records/RAttachmentMeta.scala @@ -1,10 +1,11 @@ package docspell.store.records +import cats.data.NonEmptyList import cats.implicits._ import docspell.common._ -import docspell.store.impl.Implicits._ -import docspell.store.impl._ +import docspell.store.qb.DSL._ +import docspell.store.qb._ import doobie._ import doobie.implicits._ @@ -29,9 +30,25 @@ object RAttachmentMeta { def empty(attachId: Ident) = RAttachmentMeta(attachId, None, Nil, MetaProposalList.empty, None) - val table = fr"attachmentmeta" + final case class Table(alias: Option[String]) extends TableDef { + val tableName = "attachmentmeta" + val id = Column[Ident]("attachid", this) + val content = Column[String]("content", this) + val nerlabels = Column[List[NerLabel]]("nerlabels", this) + val proposals = Column[MetaProposalList]("itemproposals", this) + val pages = Column[Int]("page_count", this) + val all = NonEmptyList.of[Column[_]](id, content, nerlabels, proposals, pages) + } + + val T = Table(None) + def as(alias: String): Table = + Table(Some(alias)) + + val table = fr"attachmentmeta" object Columns { + import docspell.store.impl._ + val id = Column("attachid") val content = Column("content") val nerlabels = Column("nerlabels") @@ -39,23 +56,22 @@ object RAttachmentMeta { val pages = Column("page_count") val all = List(id, content, nerlabels, proposals, pages) } - import Columns._ def insert(v: RAttachmentMeta): ConnectionIO[Int] = - insertRow( - table, - all, + DML.insert( + T, + T.all, fr"${v.id},${v.content},${v.nerlabels},${v.proposals},${v.pages}" - ).update.run + ) def exists(attachId: Ident): ConnectionIO[Boolean] = - selectCount(id, table, id.is(attachId)).query[Int].unique.map(_ > 0) + Select(count(T.id).s, from(T), T.id === attachId).build.query[Int].unique.map(_ > 0) def findById(attachId: Ident): ConnectionIO[Option[RAttachmentMeta]] = - selectSimple(all, table, id.is(attachId)).query[RAttachmentMeta].option + run(select(T.all), from(T), T.id === attachId).query[RAttachmentMeta].option def findPageCountById(attachId: Ident): ConnectionIO[Option[Int]] = - selectSimple(Seq(pages), table, id.is(attachId)) + Select(T.pages.s, from(T), T.id === attachId).build .query[Option[Int]] .option .map(_.flatten) @@ -67,37 +83,37 @@ object RAttachmentMeta { } yield n1 def update(v: RAttachmentMeta): ConnectionIO[Int] = - updateRow( - table, - id.is(v.id), - commas( - content.setTo(v.content), - nerlabels.setTo(v.nerlabels), - proposals.setTo(v.proposals) + DML.update( + T, + T.id === v.id, + DML.set( + T.content.setTo(v.content), + T.nerlabels.setTo(v.nerlabels), + T.proposals.setTo(v.proposals) ) - ).update.run + ) def updateLabels(mid: Ident, labels: List[NerLabel]): ConnectionIO[Int] = - updateRow( - table, - id.is(mid), - commas( - nerlabels.setTo(labels) + DML.update( + T, + T.id === mid, + DML.set( + T.nerlabels.setTo(labels) ) - ).update.run + ) def updateProposals(mid: Ident, plist: MetaProposalList): ConnectionIO[Int] = - updateRow( - table, - id.is(mid), - commas( - proposals.setTo(plist) + DML.update( + T, + T.id === mid, + DML.set( + T.proposals.setTo(plist) ) - ).update.run + ) def updatePageCount(mid: Ident, pageCount: Option[Int]): ConnectionIO[Int] = - updateRow(table, id.is(mid), pages.setTo(pageCount)).update.run + DML.update(T, T.id === mid, DML.set(T.pages.setTo(pageCount))) def delete(attachId: Ident): ConnectionIO[Int] = - deleteFrom(table, id.is(attachId)).update.run + DML.delete(T, T.id === attachId) } diff --git a/modules/store/src/main/scala/docspell/store/records/RAttachmentPreview.scala b/modules/store/src/main/scala/docspell/store/records/RAttachmentPreview.scala index c28169b7..290efd50 100644 --- a/modules/store/src/main/scala/docspell/store/records/RAttachmentPreview.scala +++ b/modules/store/src/main/scala/docspell/store/records/RAttachmentPreview.scala @@ -1,8 +1,10 @@ package docspell.store.records +import cats.data.NonEmptyList + import docspell.common._ -import docspell.store.impl.Implicits._ -import docspell.store.impl._ +import docspell.store.qb.DSL._ +import docspell.store.qb._ import bitpeace.FileMeta import doobie._ @@ -19,10 +21,24 @@ case class RAttachmentPreview( ) object RAttachmentPreview { + final case class Table(alias: Option[String]) extends TableDef { + val tableName = "attachment_preview" + + val id = Column[Ident]("id", this) + val fileId = Column[Ident]("file_id", this) + val name = Column[String]("filename", this) + val created = Column[Timestamp]("created", this) + + val all = NonEmptyList.of[Column[_]](id, fileId, name, created) + } + + val T = Table(None) + def as(alias: String): Table = + Table(Some(alias)) val table = fr"attachment_preview" - object Columns { + import docspell.store.impl._ val id = Column("id") val fileId = Column("file_id") val name = Column("filename") @@ -31,67 +47,98 @@ object RAttachmentPreview { val all = List(id, fileId, name, created) } - import Columns._ - def insert(v: RAttachmentPreview): ConnectionIO[Int] = - insertRow(table, all, fr"${v.id},${v.fileId},${v.name},${v.created}").update.run + DML.insert(T, T.all, fr"${v.id},${v.fileId},${v.name},${v.created}") def findById(attachId: Ident): ConnectionIO[Option[RAttachmentPreview]] = - selectSimple(all, table, id.is(attachId)).query[RAttachmentPreview].option + run(select(T.all), from(T), T.id === attachId).query[RAttachmentPreview].option def delete(attachId: Ident): ConnectionIO[Int] = - deleteFrom(table, id.is(attachId)).update.run + DML.delete(T, T.id === attachId) def findByIdAndCollective( attachId: Ident, collective: Ident ): ConnectionIO[Option[RAttachmentPreview]] = { - val bId = RAttachment.Columns.id.prefix("b") - val aId = Columns.id.prefix("a") - val bItem = RAttachment.Columns.itemId.prefix("b") - val iId = RItem.Columns.id.prefix("i") - val iColl = RItem.Columns.cid.prefix("i") +// val bId = RAttachment.Columns.id.prefix("b") +// val aId = Columns.id.prefix("a") +// val bItem = RAttachment.Columns.itemId.prefix("b") +// val iId = RItem.Columns.id.prefix("i") +// val iColl = RItem.Columns.cid.prefix("i") +// +// val from = table ++ fr"a INNER JOIN" ++ +// RAttachment.table ++ fr"b ON" ++ aId.is(bId) ++ +// fr"INNER JOIN" ++ RItem.table ++ fr"i ON" ++ bItem.is(iId) +// +// val where = and(aId.is(attachId), bId.is(attachId), iColl.is(collective)) +// +// selectSimple(all.map(_.prefix("a")), from, where).query[RAttachmentPreview].option + val b = RAttachment.as("b") + val a = RAttachmentPreview.as("a") + val i = RItem.as("i") - val from = table ++ fr"a INNER JOIN" ++ - RAttachment.table ++ fr"b ON" ++ aId.is(bId) ++ - fr"INNER JOIN" ++ RItem.table ++ fr"i ON" ++ bItem.is(iId) - - val where = and(aId.is(attachId), bId.is(attachId), iColl.is(collective)) - - selectSimple(all.map(_.prefix("a")), from, where).query[RAttachmentPreview].option + Select( + select(a.all), + from(a) + .innerJoin(b, a.id === b.id) + .innerJoin(i, i.id === b.itemId), + a.id === attachId && b.id === attachId && i.cid === collective + ).build.query[RAttachmentPreview].option } def findByItem(itemId: Ident): ConnectionIO[Vector[RAttachmentPreview]] = { - val sId = Columns.id.prefix("s") - val aId = RAttachment.Columns.id.prefix("a") - val aItem = RAttachment.Columns.itemId.prefix("a") - - val from = table ++ fr"s INNER JOIN" ++ RAttachment.table ++ fr"a ON" ++ sId.is(aId) - selectSimple(all.map(_.prefix("s")), from, aItem.is(itemId)) - .query[RAttachmentPreview] - .to[Vector] +// val sId = Columns.id.prefix("s") +// val aId = RAttachment.Columns.id.prefix("a") +// val aItem = RAttachment.Columns.itemId.prefix("a") +// +// val from = table ++ fr"s INNER JOIN" ++ RAttachment.table ++ fr"a ON" ++ sId.is(aId) +// selectSimple(all.map(_.prefix("s")), from, aItem.is(itemId)) +// .query[RAttachmentPreview] +// .to[Vector] + val s = RAttachmentPreview.as("s") + val a = RAttachment.as("a") + Select( + select(s.all), + from(s) + .innerJoin(a, s.id === a.id), + a.itemId === itemId + ).build.query[RAttachmentPreview].to[Vector] } def findByItemAndCollective( itemId: Ident, coll: Ident ): ConnectionIO[Option[RAttachmentPreview]] = { - val sId = Columns.id.prefix("s") - val aId = RAttachment.Columns.id.prefix("a") - val aItem = RAttachment.Columns.itemId.prefix("a") - val aPos = RAttachment.Columns.position.prefix("a") - val iId = RItem.Columns.id.prefix("i") - val iColl = RItem.Columns.cid.prefix("i") +// val sId = Columns.id.prefix("s") +// val aId = RAttachment.Columns.id.prefix("a") +// val aItem = RAttachment.Columns.itemId.prefix("a") +// val aPos = RAttachment.Columns.position.prefix("a") +// val iId = RItem.Columns.id.prefix("i") +// val iColl = RItem.Columns.cid.prefix("i") +// +// val from = +// table ++ fr"s INNER JOIN" ++ RAttachment.table ++ fr"a ON" ++ sId.is(aId) ++ +// fr"INNER JOIN" ++ RItem.table ++ fr"i ON" ++ iId.is(aItem) +// +// selectSimple( +// all.map(_.prefix("s")) ++ List(aPos), +// from, +// and(aItem.is(itemId), iColl.is(coll)) +// ) +// .query[(RAttachmentPreview, Int)] +// .to[Vector] +// .map(_.sortBy(_._2).headOption.map(_._1)) + val s = RAttachmentPreview.as("s") + val a = RAttachment.as("a") + val i = RItem.as("i") - val from = - table ++ fr"s INNER JOIN" ++ RAttachment.table ++ fr"a ON" ++ sId.is(aId) ++ - fr"INNER JOIN" ++ RItem.table ++ fr"i ON" ++ iId.is(aItem) - - selectSimple( - all.map(_.prefix("s")) ++ List(aPos), - from, - and(aItem.is(itemId), iColl.is(coll)) - ) + Select( + select(s.all).append(a.position.s), + from(s) + .innerJoin(a, s.id === a.id) + .innerJoin(i, i.id === a.itemId), + a.itemId === itemId && i.cid === coll + ).build .query[(RAttachmentPreview, Int)] .to[Vector] .map(_.sortBy(_._2).headOption.map(_._1)) @@ -102,22 +149,33 @@ object RAttachmentPreview { ): ConnectionIO[Vector[(RAttachmentPreview, FileMeta)]] = { import bitpeace.sql._ - val aId = Columns.id.prefix("a") - val afileMeta = fileId.prefix("a") - val bPos = RAttachment.Columns.position.prefix("b") - val bId = RAttachment.Columns.id.prefix("b") - val bItem = RAttachment.Columns.itemId.prefix("b") - val mId = RFileMeta.Columns.id.prefix("m") +// val aId = Columns.id.prefix("a") +// val afileMeta = fileId.prefix("a") +// val bPos = RAttachment.Columns.position.prefix("b") +// val bId = RAttachment.Columns.id.prefix("b") +// val bItem = RAttachment.Columns.itemId.prefix("b") +// val mId = RFileMeta.Columns.id.prefix("m") +// +// val cols = all.map(_.prefix("a")) ++ RFileMeta.Columns.all.map(_.prefix("m")) +// val from = table ++ fr"a INNER JOIN" ++ +// RFileMeta.table ++ fr"m ON" ++ afileMeta.is(mId) ++ fr"INNER JOIN" ++ +// RAttachment.table ++ fr"b ON" ++ aId.is(bId) +// val where = bItem.is(id) +// +// (selectSimple(cols, from, where) ++ orderBy(bPos.asc)) +// .query[(RAttachmentPreview, FileMeta)] +// .to[Vector] + val a = RAttachmentPreview.as("a") + val b = RAttachment.as("b") + val m = RFileMeta.as("m") - val cols = all.map(_.prefix("a")) ++ RFileMeta.Columns.all.map(_.prefix("m")) - val from = table ++ fr"a INNER JOIN" ++ - RFileMeta.table ++ fr"m ON" ++ afileMeta.is(mId) ++ fr"INNER JOIN" ++ - RAttachment.table ++ fr"b ON" ++ aId.is(bId) - val where = bItem.is(id) - - (selectSimple(cols, from, where) ++ orderBy(bPos.asc)) - .query[(RAttachmentPreview, FileMeta)] - .to[Vector] + Select( + select(a.all, m.all), + from(a) + .innerJoin(m, a.fileId === m.id) + .innerJoin(b, b.id === a.id), + b.itemId === id + ).orderBy(b.position.asc).build.query[(RAttachmentPreview, FileMeta)].to[Vector] } } diff --git a/modules/store/src/main/scala/docspell/store/records/RAttachmentSource.scala b/modules/store/src/main/scala/docspell/store/records/RAttachmentSource.scala index f67a805f..4d94743b 100644 --- a/modules/store/src/main/scala/docspell/store/records/RAttachmentSource.scala +++ b/modules/store/src/main/scala/docspell/store/records/RAttachmentSource.scala @@ -1,8 +1,10 @@ package docspell.store.records +import cats.data.NonEmptyList + import docspell.common._ -import docspell.store.impl.Implicits._ -import docspell.store.impl._ +import docspell.store.qb.DSL._ +import docspell.store.qb._ import bitpeace.FileMeta import doobie._ @@ -19,10 +21,24 @@ case class RAttachmentSource( ) object RAttachmentSource { + final case class Table(alias: Option[String]) extends TableDef { + val tableName = "attachment_source" + + val id = Column[Ident]("id", this) + val fileId = Column[Ident]("file_id", this) + val name = Column[String]("filename", this) + val created = Column[Timestamp]("created", this) + + val all = NonEmptyList.of[Column[_]](id, fileId, name, created) + } + + val T = Table(None) + def as(alias: String): Table = + Table(Some(alias)) val table = fr"attachment_source" - object Columns { + import docspell.store.impl._ val id = Column("id") val fileId = Column("file_id") val name = Column("filename") @@ -31,67 +47,90 @@ object RAttachmentSource { val all = List(id, fileId, name, created) } - import Columns._ - def of(ra: RAttachment): RAttachmentSource = RAttachmentSource(ra.id, ra.fileId, ra.name, ra.created) def insert(v: RAttachmentSource): ConnectionIO[Int] = - insertRow(table, all, fr"${v.id},${v.fileId},${v.name},${v.created}").update.run + DML.insert(T, T.all, fr"${v.id},${v.fileId},${v.name},${v.created}") def findById(attachId: Ident): ConnectionIO[Option[RAttachmentSource]] = - selectSimple(all, table, id.is(attachId)).query[RAttachmentSource].option + run(select(T.all), from(T), T.id === attachId).query[RAttachmentSource].option def isSameFile(attachId: Ident, file: Ident): ConnectionIO[Boolean] = - selectCount(id, table, and(id.is(attachId), fileId.is(file))) + Select(count(T.id).s, from(T), T.id === attachId && T.fileId === file).build .query[Int] .unique .map(_ > 0) def isConverted(attachId: Ident): ConnectionIO[Boolean] = { - val sId = Columns.id.prefix("s") - val sFile = Columns.fileId.prefix("s") - val aId = RAttachment.Columns.id.prefix("a") - val aFile = RAttachment.Columns.fileId.prefix("a") + val s = RAttachmentSource.as("s") + val a = RAttachment.as("a") + Select( + count(a.id).s, + from(s).innerJoin(a, a.id === s.id), + a.id === attachId && a.fileId <> s.fileId + ).build.query[Int].unique.map(_ > 0) - val from = table ++ fr"s INNER JOIN" ++ - RAttachment.table ++ fr"a ON" ++ aId.is(sId) - - selectCount(aId, from, and(aId.is(attachId), aFile.isNot(sFile))) - .query[Int] - .unique - .map(_ > 0) +// val sId = Columns.id.prefix("s") +// val sFile = Columns.fileId.prefix("s") +// val aId = RAttachment.Columns.id.prefix("a") +// val aFile = RAttachment.Columns.fileId.prefix("a") +// +// val from = table ++ fr"s INNER JOIN" ++ +// RAttachment.table ++ fr"a ON" ++ aId.is(sId) +// +// selectCount(aId, from, and(aId.is(attachId), aFile.isNot(sFile))) +// .query[Int] +// .unique +// .map(_ > 0) } def delete(attachId: Ident): ConnectionIO[Int] = - deleteFrom(table, id.is(attachId)).update.run + DML.delete(T, T.id === attachId) def findByIdAndCollective( attachId: Ident, collective: Ident ): ConnectionIO[Option[RAttachmentSource]] = { - val bId = RAttachment.Columns.id.prefix("b") - val aId = Columns.id.prefix("a") - val bItem = RAttachment.Columns.itemId.prefix("b") - val iId = RItem.Columns.id.prefix("i") - val iColl = RItem.Columns.cid.prefix("i") +// val bId = RAttachment.Columns.id.prefix("b") +// val aId = Columns.id.prefix("a") +// val bItem = RAttachment.Columns.itemId.prefix("b") +// val iId = RItem.Columns.id.prefix("i") +// val iColl = RItem.Columns.cid.prefix("i") +// +// val from = table ++ fr"a INNER JOIN" ++ +// RAttachment.table ++ fr"b ON" ++ aId.is(bId) ++ +// fr"INNER JOIN" ++ RItem.table ++ fr"i ON" ++ bItem.is(iId) +// +// val where = and(aId.is(attachId), bId.is(attachId), iColl.is(collective)) +// +// selectSimple(all.map(_.prefix("a")), from, where).query[RAttachmentSource].option + val b = RAttachment.as("b") + val a = RAttachmentSource.as("a") + val i = RItem.as("i") - val from = table ++ fr"a INNER JOIN" ++ - RAttachment.table ++ fr"b ON" ++ aId.is(bId) ++ - fr"INNER JOIN" ++ RItem.table ++ fr"i ON" ++ bItem.is(iId) - - val where = and(aId.is(attachId), bId.is(attachId), iColl.is(collective)) - - selectSimple(all.map(_.prefix("a")), from, where).query[RAttachmentSource].option + Select( + select(a.all), + from(a) + .innerJoin(b, a.id === b.id) + .innerJoin(i, i.id === b.itemId), + a.id === attachId && b.id === attachId && i.cid === collective + ).build.query[RAttachmentSource].option } def findByItem(itemId: Ident): ConnectionIO[Vector[RAttachmentSource]] = { - val sId = Columns.id.prefix("s") - val aId = RAttachment.Columns.id.prefix("a") - val aItem = RAttachment.Columns.itemId.prefix("a") +// val sId = Columns.id.prefix("s") +// val aId = RAttachment.Columns.id.prefix("a") +// val aItem = RAttachment.Columns.itemId.prefix("a") +// +// val from = table ++ fr"s INNER JOIN" ++ RAttachment.table ++ fr"a ON" ++ sId.is(aId) +// selectSimple(all.map(_.prefix("s")), from, aItem.is(itemId)) +// .query[RAttachmentSource] +// .to[Vector] - val from = table ++ fr"s INNER JOIN" ++ RAttachment.table ++ fr"a ON" ++ sId.is(aId) - selectSimple(all.map(_.prefix("s")), from, aItem.is(itemId)) + val s = RAttachmentSource.as("s") + val a = RAttachment.as("a") + Select(select(s.all), from(s).innerJoin(a, a.id === s.id), a.itemId === itemId).build .query[RAttachmentSource] .to[Vector] } @@ -101,22 +140,33 @@ object RAttachmentSource { ): ConnectionIO[Vector[(RAttachmentSource, FileMeta)]] = { import bitpeace.sql._ - val aId = Columns.id.prefix("a") - val afileMeta = fileId.prefix("a") - val bPos = RAttachment.Columns.position.prefix("b") - val bId = RAttachment.Columns.id.prefix("b") - val bItem = RAttachment.Columns.itemId.prefix("b") - val mId = RFileMeta.Columns.id.prefix("m") +// val aId = Columns.id.prefix("a") +// val afileMeta = fileId.prefix("a") +// val bPos = RAttachment.Columns.position.prefix("b") +// val bId = RAttachment.Columns.id.prefix("b") +// val bItem = RAttachment.Columns.itemId.prefix("b") +// val mId = RFileMeta.Columns.id.prefix("m") +// +// val cols = all.map(_.prefix("a")) ++ RFileMeta.Columns.all.map(_.prefix("m")) +// val from = table ++ fr"a INNER JOIN" ++ +// RFileMeta.table ++ fr"m ON" ++ afileMeta.is(mId) ++ fr"INNER JOIN" ++ +// RAttachment.table ++ fr"b ON" ++ aId.is(bId) +// val where = bItem.is(id) +// +// (selectSimple(cols, from, where) ++ orderBy(bPos.asc)) +// .query[(RAttachmentSource, FileMeta)] +// .to[Vector] + val a = RAttachmentSource.as("a") + val b = RAttachment.as("b") + val m = RFileMeta.as("m") - val cols = all.map(_.prefix("a")) ++ RFileMeta.Columns.all.map(_.prefix("m")) - val from = table ++ fr"a INNER JOIN" ++ - RFileMeta.table ++ fr"m ON" ++ afileMeta.is(mId) ++ fr"INNER JOIN" ++ - RAttachment.table ++ fr"b ON" ++ aId.is(bId) - val where = bItem.is(id) - - (selectSimple(cols, from, where) ++ orderBy(bPos.asc)) - .query[(RAttachmentSource, FileMeta)] - .to[Vector] + Select( + select(a.all, m.all), + from(a) + .innerJoin(m, a.fileId === m.id) + .innerJoin(b, b.id === a.id), + b.itemId === id + ).orderBy(b.position.asc).build.query[(RAttachmentSource, FileMeta)].to[Vector] } } diff --git a/modules/store/src/main/scala/docspell/store/records/RClassifierSetting.scala b/modules/store/src/main/scala/docspell/store/records/RClassifierSetting.scala index 680741a0..749435d1 100644 --- a/modules/store/src/main/scala/docspell/store/records/RClassifierSetting.scala +++ b/modules/store/src/main/scala/docspell/store/records/RClassifierSetting.scala @@ -1,10 +1,11 @@ package docspell.store.records +import cats.data.NonEmptyList import cats.implicits._ import docspell.common._ -import docspell.store.impl.Implicits._ -import docspell.store.impl._ +import docspell.store.qb.DSL._ +import docspell.store.qb._ import com.github.eikek.calev._ import doobie._ @@ -21,71 +22,69 @@ case class RClassifierSetting( ) {} object RClassifierSetting { + final case class Table(alias: Option[String]) extends TableDef { + val tableName = "classifier_setting" - val table = fr"classifier_setting" - - object Columns { - val cid = Column("cid") - val enabled = Column("enabled") - val schedule = Column("schedule") - val category = Column("category") - val itemCount = Column("item_count") - val fileId = Column("file_id") - val created = Column("created") - val all = List(cid, enabled, schedule, category, itemCount, fileId, created) - } - import Columns._ - - def insert(v: RClassifierSetting): ConnectionIO[Int] = { - val sql = - insertRow( - table, - all, - fr"${v.cid},${v.enabled},${v.schedule},${v.category},${v.itemCount},${v.fileId},${v.created}" - ) - sql.update.run + val cid = Column[Ident]("cid", this) + val enabled = Column[Boolean]("enabled", this) + val schedule = Column[CalEvent]("schedule", this) + val category = Column[String]("category", this) + val itemCount = Column[Int]("item_count", this) + val fileId = Column[Ident]("file_id", this) + val created = Column[Timestamp]("created", this) + val all = NonEmptyList + .of[Column[_]](cid, enabled, schedule, category, itemCount, fileId, created) } - def updateAll(v: RClassifierSetting): ConnectionIO[Int] = { - val sql = updateRow( - table, - cid.is(v.cid), - commas( - enabled.setTo(v.enabled), - schedule.setTo(v.schedule), - category.setTo(v.category), - itemCount.setTo(v.itemCount), - fileId.setTo(v.fileId) + val T = Table(None) + def as(alias: String): Table = + Table(Some(alias)) + + def insert(v: RClassifierSetting): ConnectionIO[Int] = + DML.insert( + T, + T.all, + fr"${v.cid},${v.enabled},${v.schedule},${v.category},${v.itemCount},${v.fileId},${v.created}" + ) + + def updateAll(v: RClassifierSetting): ConnectionIO[Int] = + DML.update( + T, + T.cid === v.cid, + DML.set( + T.enabled.setTo(v.enabled), + T.schedule.setTo(v.schedule), + T.category.setTo(v.category), + T.itemCount.setTo(v.itemCount), + T.fileId.setTo(v.fileId) ) ) - sql.update.run - } def updateFile(coll: Ident, fid: Ident): ConnectionIO[Int] = - updateRow(table, cid.is(coll), fileId.setTo(fid)).update.run + DML.update(T, T.cid === coll, DML.set(T.fileId.setTo(fid))) def updateSettings(v: RClassifierSetting): ConnectionIO[Int] = for { - n1 <- updateRow( - table, - cid.is(v.cid), - commas( - enabled.setTo(v.enabled), - schedule.setTo(v.schedule), - itemCount.setTo(v.itemCount), - category.setTo(v.category) + n1 <- DML.update( + T, + T.cid === v.cid, + DML.set( + T.enabled.setTo(v.enabled), + T.schedule.setTo(v.schedule), + T.itemCount.setTo(v.itemCount), + T.category.setTo(v.category) ) - ).update.run + ) n2 <- if (n1 <= 0) insert(v) else 0.pure[ConnectionIO] } yield n1 + n2 def findById(id: Ident): ConnectionIO[Option[RClassifierSetting]] = { - val sql = selectSimple(all, table, cid.is(id)) + val sql = run(select(T.all), from(T), T.cid === id) sql.query[RClassifierSetting].option } def delete(coll: Ident): ConnectionIO[Int] = - deleteFrom(table, cid.is(coll)).update.run + DML.delete(T, T.cid === coll) case class Classifier( enabled: Boolean, diff --git a/modules/store/src/main/scala/docspell/store/records/RCollective.scala b/modules/store/src/main/scala/docspell/store/records/RCollective.scala index 2487ed22..ca3b2666 100644 --- a/modules/store/src/main/scala/docspell/store/records/RCollective.scala +++ b/modules/store/src/main/scala/docspell/store/records/RCollective.scala @@ -1,10 +1,11 @@ package docspell.store.records +import cats.data.NonEmptyList import fs2.Stream 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._ @@ -18,58 +19,54 @@ case class RCollective( ) object RCollective { + final case class Table(alias: Option[String]) extends TableDef { + val tableName = "collective" - val table = fr"collective" + val id = Column[Ident]("cid", this) + val state = Column[CollectiveState]("state", this) + val language = Column[Language]("doclang", this) + val integration = Column[Boolean]("integration_enabled", this) + val created = Column[Timestamp]("created", this) - object Columns { - - val id = Column("cid") - val state = Column("state") - val language = Column("doclang") - val integration = Column("integration_enabled") - val created = Column("created") - - val all = List(id, state, language, integration, created) + val all = NonEmptyList.of[Column[_]](id, state, language, integration, created) } - import Columns._ + val T = Table(None) + def as(alias: String): Table = + Table(Some(alias)) - def insert(value: RCollective): ConnectionIO[Int] = { - val sql = insertRow( - table, - Columns.all, + def insert(value: RCollective): ConnectionIO[Int] = + DML.insert( + T, + T.all, fr"${value.id},${value.state},${value.language},${value.integrationEnabled},${value.created}" ) - sql.update.run - } - def update(value: RCollective): ConnectionIO[Int] = { - val sql = updateRow( - table, - id.is(value.id), - commas( - state.setTo(value.state) + def update(value: RCollective): ConnectionIO[Int] = + DML.update( + T, + T.id === value.id, + DML.set( + T.state.setTo(value.state) ) ) - sql.update.run - } def findLanguage(cid: Ident): ConnectionIO[Option[Language]] = - selectSimple(List(language), table, id.is(cid)).query[Option[Language]].unique + Select(T.language.s, from(T), T.id === cid).build.query[Option[Language]].unique def updateLanguage(cid: Ident, lang: Language): ConnectionIO[Int] = - updateRow(table, id.is(cid), language.setTo(lang)).update.run + DML.update(T, T.id === cid, DML.set(T.language.setTo(lang))) def updateSettings(cid: Ident, settings: Settings): ConnectionIO[Int] = for { - n1 <- updateRow( - table, - id.is(cid), - commas( - language.setTo(settings.language), - integration.setTo(settings.integrationEnabled) + n1 <- DML.update( + T, + T.id === cid, + DML.set( + T.language.setTo(settings.language), + T.integration.setTo(settings.integrationEnabled) ) - ).update.run + ) cls <- Timestamp .current[ConnectionIO] @@ -83,66 +80,64 @@ object RCollective { } yield n1 + n2 def getSettings(coll: Ident): ConnectionIO[Option[Settings]] = { - val cId = id.prefix("c") - val CS = RClassifierSetting.Columns - val csCid = CS.cid.prefix("cs") + val c = RCollective.as("c") + val cs = RClassifierSetting.as("cs") - val cols = Seq( - language.prefix("c"), - integration.prefix("c"), - CS.enabled.prefix("cs"), - CS.schedule.prefix("cs"), - CS.itemCount.prefix("cs"), - CS.category.prefix("cs") - ) - val from = table ++ fr"c LEFT JOIN" ++ - RClassifierSetting.table ++ fr"cs ON" ++ csCid.is(cId) - - selectSimple(cols, from, cId.is(coll)) - .query[Settings] - .option + Select( + select( + c.language.s, + c.integration.s, + cs.enabled.s, + cs.schedule.s, + cs.itemCount.s, + cs.category.s + ), + from(c).leftJoin(cs, cs.cid === c.id), + c.id === coll + ).build.query[Settings].option } def findById(cid: Ident): ConnectionIO[Option[RCollective]] = { - val sql = selectSimple(all, table, id.is(cid)) + val sql = run(select(T.all), from(T), T.id === cid) sql.query[RCollective].option } def findByItem(itemId: Ident): ConnectionIO[Option[RCollective]] = { - val iColl = RItem.Columns.cid.prefix("i") - val iId = RItem.Columns.id.prefix("i") - val cId = id.prefix("c") - val from = RItem.table ++ fr"i INNER JOIN" ++ table ++ fr"c ON" ++ iColl.is(cId) - selectSimple(all.map(_.prefix("c")), from, iId.is(itemId)).query[RCollective].option + val i = RItem.as("i") + val c = RCollective.as("c") + Select( + select(c.all), + from(i).innerJoin(c, i.cid === c.id), + i.id === itemId + ).build.query[RCollective].option } def existsById(cid: Ident): ConnectionIO[Boolean] = { - val sql = selectCount(id, table, id.is(cid)) + val sql = Select(count(T.id).s, from(T), T.id === cid).build sql.query[Int].unique.map(_ > 0) } - def findAll(order: Columns.type => Column): ConnectionIO[Vector[RCollective]] = { - val sql = selectSimple(all, table, Fragment.empty) ++ orderBy(order(Columns).f) - sql.query[RCollective].to[Vector] + def findAll(order: Table => Column[_]): ConnectionIO[Vector[RCollective]] = { + val sql = Select(select(T.all), from(T)).orderBy(order(T)) + sql.build.query[RCollective].to[Vector] } - def streamAll(order: Columns.type => Column): Stream[ConnectionIO, RCollective] = { - val sql = selectSimple(all, table, Fragment.empty) ++ orderBy(order(Columns).f) - sql.query[RCollective].stream + def streamAll(order: Table => Column[_]): Stream[ConnectionIO, RCollective] = { + val sql = Select(select(T.all), from(T)).orderBy(order(T)) + sql.build.query[RCollective].stream } def findByAttachment(attachId: Ident): ConnectionIO[Option[RCollective]] = { - val iColl = RItem.Columns.cid.prefix("i") - val iId = RItem.Columns.id.prefix("i") - val aItem = RAttachment.Columns.itemId.prefix("a") - val aId = RAttachment.Columns.id.prefix("a") - val cId = Columns.id.prefix("c") - - val from = table ++ fr"c INNER JOIN" ++ - RItem.table ++ fr"i ON" ++ cId.is(iColl) ++ fr"INNER JOIN" ++ - RAttachment.table ++ fr"a ON" ++ aItem.is(iId) - - selectSimple(all.map(_.prefix("c")), from, aId.is(attachId)).query[RCollective].option + val i = RItem.as("i") + val a = RAttachment.as("a") + val c = RCollective.as("c") + Select( + select(c.all), + from(c) + .innerJoin(i, c.id === i.cid) + .innerJoin(a, a.itemId === i.id), + a.id === attachId + ).build.query[RCollective].option } case class Settings( diff --git a/modules/store/src/main/scala/docspell/store/records/RFileMeta.scala b/modules/store/src/main/scala/docspell/store/records/RFileMeta.scala index b9e73f77..2567562f 100644 --- a/modules/store/src/main/scala/docspell/store/records/RFileMeta.scala +++ b/modules/store/src/main/scala/docspell/store/records/RFileMeta.scala @@ -1,11 +1,13 @@ package docspell.store.records +import java.time.Instant + import cats.data.NonEmptyList import cats.implicits._ import docspell.common._ -import docspell.store.impl.Implicits._ -import docspell.store.impl._ +import docspell.store.qb.DSL._ +import docspell.store.qb._ import docspell.store.syntax.MimeTypes._ import bitpeace.FileMeta @@ -14,26 +16,30 @@ import doobie._ import doobie.implicits._ object RFileMeta { + final case class Table(alias: Option[String]) extends TableDef { + val tableName = "filemeta" - val table = fr"filemeta" + val id = Column[Ident]("id", this) + val timestamp = Column[Instant]("timestamp", this) + val mimetype = Column[Mimetype]("mimetype", this) + val length = Column[Long]("length", this) + val checksum = Column[String]("checksum", this) + val chunks = Column[Int]("chunks", this) + val chunksize = Column[Int]("chunksize", this) - object Columns { - val id = Column("id") - val timestamp = Column("timestamp") - val mimetype = Column("mimetype") - val length = Column("length") - val checksum = Column("checksum") - val chunks = Column("chunks") - val chunksize = Column("chunksize") - - val all = List(id, timestamp, mimetype, length, checksum, chunks, chunksize) + val all = NonEmptyList + .of[Column[_]](id, timestamp, mimetype, length, checksum, chunks, chunksize) } + val T = Table(None) + def as(alias: String): Table = + Table(Some(alias)) + def findById(fid: Ident): ConnectionIO[Option[FileMeta]] = { import bitpeace.sql._ - selectSimple(Columns.all, table, Columns.id.is(fid)).query[FileMeta].option + run(select(T.all), from(T), T.id === fid).query[FileMeta].option } def findByIds(ids: List[Ident]): ConnectionIO[Vector[FileMeta]] = { @@ -41,7 +47,7 @@ object RFileMeta { NonEmptyList.fromList(ids) match { case Some(nel) => - selectSimple(Columns.all, table, Columns.id.isIn(nel)).query[FileMeta].to[Vector] + run(select(T.all), from(T), T.id.in(nel)).query[FileMeta].to[Vector] case None => Vector.empty[FileMeta].pure[ConnectionIO] } @@ -50,7 +56,7 @@ object RFileMeta { def findMime(fid: Ident): ConnectionIO[Option[MimeType]] = { import bitpeace.sql._ - selectSimple(Seq(Columns.mimetype), table, Columns.id.is(fid)) + run(select(T.mimetype), from(T), T.id === fid) .query[Mimetype] .option .map(_.map(_.toLocal)) 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 eba4db3a..929b3528 100644 --- a/modules/store/src/main/scala/docspell/store/records/RItem.scala +++ b/modules/store/src/main/scala/docspell/store/records/RItem.scala @@ -5,9 +5,8 @@ import cats.effect.Sync import cats.implicits._ import docspell.common._ -import docspell.store.impl.Implicits._ -import docspell.store.impl._ -import docspell.store.qb.{Select, TableDef} +import docspell.store.qb.DSL._ +import docspell.store.qb._ import doobie._ import doobie.implicits._ @@ -110,8 +109,9 @@ object RItem { Table(Some(alias)) val table = fr"item" - object Columns { + import docspell.store.impl._ + val id = Column("itemid") val cid = Column("cid") val name = Column("name") @@ -149,19 +149,21 @@ object RItem { folder ) } - import Columns._ + + private val currentTime = + Timestamp.current[ConnectionIO] def insert(v: RItem): ConnectionIO[Int] = - insertRow( - table, - all, + DML.insert( + T, + T.all, fr"${v.id},${v.cid},${v.name},${v.itemDate},${v.source},${v.direction},${v.state}," ++ fr"${v.corrOrg},${v.corrPerson},${v.concPerson},${v.concEquipment},${v.inReplyTo},${v.dueDate}," ++ fr"${v.created},${v.updated},${v.notes},${v.folderId}" - ).update.run + ) def getCollective(itemId: Ident): ConnectionIO[Option[Ident]] = - selectSimple(List(cid), table, id.is(itemId)).query[Ident].option + Select(T.cid.s, from(T), T.id === itemId).build.query[Ident].option def updateState( itemId: Ident, @@ -170,11 +172,11 @@ object RItem { ): ConnectionIO[Int] = for { t <- currentTime - n <- updateRow( - table, - and(id.is(itemId), state.isIn(existing)), - commas(state.setTo(itemState), updated.setTo(t)) - ).update.run + n <- DML.update( + T, + T.id === itemId && T.state.in(existing), + DML.set(T.state.setTo(itemState), T.updated.setTo(t)) + ) } yield n def updateStateForCollective( @@ -184,11 +186,11 @@ object RItem { ): ConnectionIO[Int] = for { t <- currentTime - n <- updateRow( - table, - and(id.isIn(itemIds), cid.is(coll)), - commas(state.setTo(itemState), updated.setTo(t)) - ).update.run + n <- DML.update( + T, + T.id.in(itemIds) && T.cid === coll, + DML.set(T.state.setTo(itemState), T.updated.setTo(t)) + ) } yield n def updateDirection( @@ -198,11 +200,11 @@ object RItem { ): ConnectionIO[Int] = for { t <- currentTime - n <- updateRow( - table, - and(id.isIn(itemIds), cid.is(coll)), - commas(incoming.setTo(dir), updated.setTo(t)) - ).update.run + n <- DML.update( + T, + T.id.in(itemIds) && T.cid === coll, + DML.set(T.incoming.setTo(dir), T.updated.setTo(t)) + ) } yield n def updateCorrOrg( @@ -212,21 +214,21 @@ object RItem { ): ConnectionIO[Int] = for { t <- currentTime - n <- updateRow( - table, - and(id.isIn(itemIds), cid.is(coll)), - commas(corrOrg.setTo(org), updated.setTo(t)) - ).update.run + n <- DML.update( + T, + T.id.in(itemIds) && T.cid === coll, + DML.set(T.corrOrg.setTo(org), T.updated.setTo(t)) + ) } yield n def removeCorrOrg(coll: Ident, currentOrg: Ident): ConnectionIO[Int] = for { t <- currentTime - n <- updateRow( - table, - and(cid.is(coll), corrOrg.is(Some(currentOrg))), - commas(corrOrg.setTo(None: Option[Ident]), updated.setTo(t)) - ).update.run + n <- DML.update( + T, + T.cid === coll && T.corrOrg === currentOrg, + DML.set(T.corrOrg.setTo(None: Option[Ident]), T.updated.setTo(t)) + ) } yield n def updateCorrPerson( @@ -236,21 +238,21 @@ object RItem { ): ConnectionIO[Int] = for { t <- currentTime - n <- updateRow( - table, - and(id.isIn(itemIds), cid.is(coll)), - commas(corrPerson.setTo(person), updated.setTo(t)) - ).update.run + n <- DML.update( + T, + T.id.in(itemIds) && T.cid === coll, + DML.set(T.corrPerson.setTo(person), T.updated.setTo(t)) + ) } yield n def removeCorrPerson(coll: Ident, currentPerson: Ident): ConnectionIO[Int] = for { t <- currentTime - n <- updateRow( - table, - and(cid.is(coll), corrPerson.is(Some(currentPerson))), - commas(corrPerson.setTo(None: Option[Ident]), updated.setTo(t)) - ).update.run + n <- DML.update( + T, + T.cid === coll && T.corrPerson === currentPerson, + DML.set(T.corrPerson.setTo(None: Option[Ident]), T.updated.setTo(t)) + ) } yield n def updateConcPerson( @@ -260,21 +262,21 @@ object RItem { ): ConnectionIO[Int] = for { t <- currentTime - n <- updateRow( - table, - and(id.isIn(itemIds), cid.is(coll)), - commas(concPerson.setTo(person), updated.setTo(t)) - ).update.run + n <- DML.update( + T, + T.id.in(itemIds) && T.cid === coll, + DML.set(T.concPerson.setTo(person), T.updated.setTo(t)) + ) } yield n def removeConcPerson(coll: Ident, currentPerson: Ident): ConnectionIO[Int] = for { t <- currentTime - n <- updateRow( - table, - and(cid.is(coll), concPerson.is(Some(currentPerson))), - commas(concPerson.setTo(None: Option[Ident]), updated.setTo(t)) - ).update.run + n <- DML.update( + T, + T.cid === coll && T.concPerson === currentPerson, + DML.set(T.concPerson.setTo(None: Option[Ident]), T.updated.setTo(t)) + ) } yield n def updateConcEquip( @@ -284,21 +286,21 @@ object RItem { ): ConnectionIO[Int] = for { t <- currentTime - n <- updateRow( - table, - and(id.isIn(itemIds), cid.is(coll)), - commas(concEquipment.setTo(equip), updated.setTo(t)) - ).update.run + n <- DML.update( + T, + T.id.in(itemIds) && T.cid === coll, + DML.set(T.concEquipment.setTo(equip), T.updated.setTo(t)) + ) } yield n def removeConcEquip(coll: Ident, currentEquip: Ident): ConnectionIO[Int] = for { t <- currentTime - n <- updateRow( - table, - and(cid.is(coll), concEquipment.is(Some(currentEquip))), - commas(concEquipment.setTo(None: Option[Ident]), updated.setTo(t)) - ).update.run + n <- DML.update( + T, + T.cid === coll && T.concEquipment === currentEquip, + DML.set(T.concEquipment.setTo(None: Option[Ident]), T.updated.setTo(t)) + ) } yield n def updateFolder( @@ -308,31 +310,31 @@ object RItem { ): ConnectionIO[Int] = for { t <- currentTime - n <- updateRow( - table, - and(cid.is(coll), id.is(itemId)), - commas(folder.setTo(folderId), updated.setTo(t)) - ).update.run + n <- DML.update( + T, + T.cid === coll && T.id === itemId, + DML.set(T.folder.setTo(folderId), T.updated.setTo(t)) + ) } yield n def updateNotes(itemId: Ident, coll: Ident, text: Option[String]): ConnectionIO[Int] = for { t <- currentTime - n <- updateRow( - table, - and(id.is(itemId), cid.is(coll)), - commas(notes.setTo(text), updated.setTo(t)) - ).update.run + n <- DML.update( + T, + T.id === itemId && T.cid === coll, + DML.set(T.notes.setTo(text), T.updated.setTo(t)) + ) } yield n def updateName(itemId: Ident, coll: Ident, itemName: String): ConnectionIO[Int] = for { t <- currentTime - n <- updateRow( - table, - and(id.is(itemId), cid.is(coll)), - commas(name.setTo(itemName), updated.setTo(t)) - ).update.run + n <- DML.update( + T, + T.id === itemId && T.cid === coll, + DML.set(T.name.setTo(itemName), T.updated.setTo(t)) + ) } yield n def updateDate( @@ -342,11 +344,11 @@ object RItem { ): ConnectionIO[Int] = for { t <- currentTime - n <- updateRow( - table, - and(id.isIn(itemIds), cid.is(coll)), - commas(itemDate.setTo(date), updated.setTo(t)) - ).update.run + n <- DML.update( + T, + T.id.in(itemIds) && T.cid === coll, + DML.set(T.itemDate.setTo(date), T.updated.setTo(t)) + ) } yield n def updateDueDate( @@ -356,50 +358,50 @@ object RItem { ): ConnectionIO[Int] = for { t <- currentTime - n <- updateRow( - table, - and(id.isIn(itemIds), cid.is(coll)), - commas(dueDate.setTo(date), updated.setTo(t)) - ).update.run + n <- DML.update( + T, + T.id.in(itemIds) && T.cid === coll, + DML.set(T.dueDate.setTo(date), T.updated.setTo(t)) + ) } yield n def deleteByIdAndCollective(itemId: Ident, coll: Ident): ConnectionIO[Int] = - deleteFrom(table, and(id.is(itemId), cid.is(coll))).update.run + DML.delete(T, T.id === itemId && T.cid === coll) def existsById(itemId: Ident): ConnectionIO[Boolean] = - selectCount(id, table, id.is(itemId)).query[Int].unique.map(_ > 0) + Select(count(T.id).s, from(T), T.id === itemId).build.query[Int].unique.map(_ > 0) def existsByIdAndCollective(itemId: Ident, coll: Ident): ConnectionIO[Boolean] = - selectCount(id, table, and(id.is(itemId), cid.is(coll))).query[Int].unique.map(_ > 0) + Select(count(T.id).s, from(T), T.id === itemId && T.cid === coll).build + .query[Int] + .unique + .map(_ > 0) def existsByIdsAndCollective( itemIds: NonEmptyList[Ident], coll: Ident ): ConnectionIO[Boolean] = - selectCount(id, table, and(id.isIn(itemIds), cid.is(coll))) + Select(count(T.id).s, from(T), T.id.in(itemIds) && T.cid === coll).build .query[Int] .unique .map(_ == itemIds.size) def findByIdAndCollective(itemId: Ident, coll: Ident): ConnectionIO[Option[RItem]] = - selectSimple(all, table, and(id.is(itemId), cid.is(coll))).query[RItem].option + run(select(T.all), from(T), T.id === itemId && T.cid === coll).query[RItem].option def findById(itemId: Ident): ConnectionIO[Option[RItem]] = - selectSimple(all, table, id.is(itemId)).query[RItem].option + run(select(T.all), from(T), T.id === itemId).query[RItem].option def checkByIdAndCollective(itemId: Ident, coll: Ident): ConnectionIO[Option[Ident]] = - selectSimple(Seq(id), table, and(id.is(itemId), cid.is(coll))).query[Ident].option + Select(T.id.s, from(T), T.id === itemId && T.cid === coll).build.query[Ident].option def removeFolder(folderId: Ident): ConnectionIO[Int] = { val empty: Option[Ident] = None - updateRow(table, folder.is(folderId), folder.setTo(empty)).update.run + DML.update(T, T.folder === folderId, DML.set(T.folder.setTo(empty))) } - def filterItemsFragment(items: NonEmptyList[Ident], coll: Ident): Select = { - import docspell.store.qb.DSL._ - + def filterItemsFragment(items: NonEmptyList[Ident], coll: Ident): Select = 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).build.query[Ident].to[Vector]