mirror of
				https://github.com/TheAnachronism/docspell.git
				synced 2025-10-31 09:30:12 +00:00 
			
		
		
		
	Convert more records
This commit is contained in:
		| @@ -1,5 +1,7 @@ | ||||
| package docspell.store.qb | ||||
|  | ||||
| import cats.data.NonEmptyList | ||||
|  | ||||
| import doobie._ | ||||
|  | ||||
| sealed trait Condition {} | ||||
| @@ -14,6 +16,9 @@ object Condition { | ||||
|       extends Condition | ||||
|  | ||||
|   case class InSubSelect[A](col: Column[A], subSelect: Select) extends Condition | ||||
|   case class InValues[A](col: Column[A], values: NonEmptyList[A], lower: Boolean)(implicit | ||||
|       val P: Put[A] | ||||
|   ) extends Condition | ||||
|  | ||||
|   case class And(c: Condition, cs: Vector[Condition]) extends Condition | ||||
|   case class Or(c: Condition, cs: Vector[Condition])  extends Condition | ||||
|   | ||||
| @@ -8,25 +8,42 @@ import doobie.implicits._ | ||||
| object DML { | ||||
|   private val comma = fr"," | ||||
|  | ||||
|   def delete(table: TableDef, cond: Condition): Fragment = | ||||
|   def delete(table: TableDef, cond: Condition): ConnectionIO[Int] = | ||||
|     deleteFragment(table, cond).update.run | ||||
|  | ||||
|   def deleteFragment(table: TableDef, cond: Condition): Fragment = | ||||
|     fr"DELETE FROM" ++ FromExprBuilder.buildTable(table) ++ fr"WHERE" ++ ConditionBuilder | ||||
|       .build(cond) | ||||
|  | ||||
|   def insert(table: TableDef, cols: Seq[Column[_]], values: Fragment): Fragment = | ||||
|   def insert(table: TableDef, cols: Seq[Column[_]], values: Fragment): ConnectionIO[Int] = | ||||
|     insertFragment(table, cols, List(values)).update.run | ||||
|  | ||||
|   def insertMany( | ||||
|       table: TableDef, | ||||
|       cols: Seq[Column[_]], | ||||
|       values: Seq[Fragment] | ||||
|   ): ConnectionIO[Int] = | ||||
|     insertFragment(table, cols, values).update.run | ||||
|  | ||||
|   def insertFragment( | ||||
|       table: TableDef, | ||||
|       cols: Seq[Column[_]], | ||||
|       values: Seq[Fragment] | ||||
|   ): Fragment = | ||||
|     fr"INSERT INTO" ++ FromExprBuilder.buildTable(table) ++ sql"(" ++ | ||||
|       cols | ||||
|         .map(SelectExprBuilder.columnNoPrefix) | ||||
|         .reduce(_ ++ comma ++ _) ++ fr") VALUES (" ++ | ||||
|       values ++ fr")" | ||||
|         .reduce(_ ++ comma ++ _) ++ fr") VALUES" ++ | ||||
|       values.map(f => sql"(" ++ f ++ sql")").reduce(_ ++ comma ++ _) | ||||
|  | ||||
|   def update( | ||||
|       table: TableDef, | ||||
|       cond: Condition, | ||||
|       setter: Seq[Setter[_]] | ||||
|   ): ConnectionIO[Int] = | ||||
|     update(table, Some(cond), setter).update.run | ||||
|     updateFragment(table, Some(cond), setter).update.run | ||||
|  | ||||
|   def update( | ||||
|   def updateFragment( | ||||
|       table: TableDef, | ||||
|       cond: Option[Condition], | ||||
|       setter: Seq[Setter[_]] | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| package docspell.store.qb | ||||
|  | ||||
| import cats.data.NonEmptyList | ||||
|  | ||||
| import docspell.store.impl.DoobieMeta | ||||
| import docspell.store.qb.impl.DoobieQuery | ||||
|  | ||||
| @@ -50,7 +52,8 @@ trait DSL extends DoobieMeta { | ||||
|     } | ||||
|  | ||||
|   def where(c: Condition, cs: Condition*): Condition = | ||||
|     and(c, cs: _*) | ||||
|     if (cs.isEmpty) c | ||||
|     else and(c, cs: _*) | ||||
|  | ||||
|   implicit final class ColumnOps[A](col: Column[A]) { | ||||
|  | ||||
| @@ -98,6 +101,12 @@ trait DSL extends DoobieMeta { | ||||
|     def in(subsel: Select): Condition = | ||||
|       Condition.InSubSelect(col, subsel) | ||||
|  | ||||
|     def in(values: NonEmptyList[A])(implicit P: Put[A]): Condition = | ||||
|       Condition.InValues(col, values, false) | ||||
|  | ||||
|     def inLower(values: NonEmptyList[A])(implicit P: Put[A]): Condition = | ||||
|       Condition.InValues(col, values, true) | ||||
|  | ||||
|     def ===(other: Column[A]): Condition = | ||||
|       Condition.CompareCol(col, Operator.Eq, other) | ||||
|   } | ||||
|   | ||||
| @@ -1,3 +1,13 @@ | ||||
| package docspell.store.qb | ||||
|  | ||||
| case class GroupBy(name: SelectExpr, names: Vector[SelectExpr], having: Option[Condition]) | ||||
|  | ||||
| object GroupBy { | ||||
|  | ||||
|   def apply(c: Column[_], cs: Column[_]*): GroupBy = | ||||
|     GroupBy( | ||||
|       SelectExpr.SelectColumn(c), | ||||
|       cs.toVector.map(SelectExpr.SelectColumn.apply), | ||||
|       None | ||||
|     ) | ||||
| } | ||||
|   | ||||
| @@ -38,7 +38,10 @@ object Select { | ||||
|       from: FromExpr, | ||||
|       where: Option[Condition], | ||||
|       groupBy: Option[GroupBy] | ||||
|   ) extends Select | ||||
|   ) extends Select { | ||||
|     def group(gb: GroupBy): SimpleSelect = | ||||
|       copy(groupBy = Some(gb)) | ||||
|   } | ||||
|  | ||||
|   case class Union(q: Select, qs: Vector[Select]) extends Select | ||||
|  | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import _root_.doobie.{Query => _, _} | ||||
| object ConditionBuilder { | ||||
|   val or         = fr"OR" | ||||
|   val and        = fr"AND" | ||||
|   val comma      = fr"," | ||||
|   val parenOpen  = Fragment.const0("(") | ||||
|   val parenClose = Fragment.const0(")") | ||||
|  | ||||
| @@ -37,6 +38,12 @@ object ConditionBuilder { | ||||
|         val sub = DoobieQuery(subsel) | ||||
|         SelectExprBuilder.column(col) ++ sql" IN (" ++ sub ++ sql")" | ||||
|  | ||||
|       case c @ Condition.InValues(col, values, toLower) => | ||||
|         val cfrag = if (toLower) lower(col) else SelectExprBuilder.column(col) | ||||
|         cfrag ++ sql" IN (" ++ values.toList | ||||
|           .map(a => buildValue(a)(c.P)) | ||||
|           .reduce(_ ++ comma ++ _) ++ sql")" | ||||
|  | ||||
|       case Condition.And(c, cs) => | ||||
|         val inner = cs.prepended(c).map(build).reduce(_ ++ and ++ _) | ||||
|         if (cs.isEmpty) inner | ||||
|   | ||||
| @@ -6,6 +6,7 @@ import fs2.Stream | ||||
| import docspell.common.ContactKind | ||||
| import docspell.common.{Direction, Ident} | ||||
| import docspell.store.impl.Implicits._ | ||||
| import docspell.store.qb.{GroupBy, Select} | ||||
| import docspell.store.records._ | ||||
|  | ||||
| import doobie._ | ||||
| @@ -77,25 +78,39 @@ object QCollective { | ||||
|   } | ||||
|  | ||||
|   def tagCloud(coll: Ident): ConnectionIO[List[TagCount]] = { | ||||
|     val TC = RTag.Columns | ||||
|     val RC = RTagItem.Columns | ||||
|     import docspell.store.qb.DSL._ | ||||
|  | ||||
|     val q3 = fr"SELECT" ++ commas( | ||||
|       TC.all.map(_.prefix("t").f) ++ Seq(fr"count(" ++ RC.itemId.prefix("r").f ++ fr")") | ||||
|     ) ++ | ||||
|       fr"FROM" ++ RTagItem.table ++ fr"r" ++ | ||||
|       fr"INNER JOIN" ++ RTag.table ++ fr"t ON" ++ RC.tagId | ||||
|         .prefix("r") | ||||
|         .is(TC.tid.prefix("t")) ++ | ||||
|       fr"WHERE" ++ TC.cid.prefix("t").is(coll) ++ | ||||
|       fr"GROUP BY" ++ commas( | ||||
|         TC.name.prefix("t").f, | ||||
|         TC.tid.prefix("t").f, | ||||
|         TC.category.prefix("t").f | ||||
|       ) | ||||
|     val ti = RTagItem.as("ti") | ||||
|     val t  = RTag.as("t") | ||||
|     val sql = | ||||
|       Select( | ||||
|         select(t.all) ++ select(count(ti.itemId)), | ||||
|         from(ti).innerJoin(t, ti.tagId === t.tid), | ||||
|         t.cid === coll | ||||
|       ).group(GroupBy(t.name, t.tid, t.category)) | ||||
|  | ||||
|     q3.query[TagCount].to[List] | ||||
|     sql.run.query[TagCount].to[List] | ||||
|   } | ||||
| //  def tagCloud2(coll: Ident): ConnectionIO[List[TagCount]] = { | ||||
| //    val tagItem = RTagItem.as("r") | ||||
| //    val TC      = RTag.Columns | ||||
| // | ||||
| //    val q3 = fr"SELECT" ++ commas( | ||||
| //      TC.all.map(_.prefix("t").f) ++ Seq(fr"count(" ++ tagItem.itemId.column.f ++ fr")") | ||||
| //    ) ++ | ||||
| //      fr"FROM" ++ Fragment.const(tagItem.tableName) ++ fr"r" ++ | ||||
| //      fr"INNER JOIN" ++ RTag.table ++ fr"t ON" ++ tagItem.tagId.column | ||||
| //        .prefix("r") | ||||
| //        .is(TC.tid.prefix("t")) ++ | ||||
| //      fr"WHERE" ++ TC.cid.prefix("t").is(coll) ++ | ||||
| //      fr"GROUP BY" ++ commas( | ||||
| //        TC.name.prefix("t").f, | ||||
| //        TC.tid.prefix("t").f, | ||||
| //        TC.category.prefix("t").f | ||||
| //      ) | ||||
| // | ||||
| //    q3.query[TagCount].to[List] | ||||
| //  } | ||||
|  | ||||
|   def getContacts( | ||||
|       coll: Ident, | ||||
|   | ||||
| @@ -412,9 +412,9 @@ object QItem { | ||||
|     val tagSelectsIncl = q.tagsInclude | ||||
|       .map(tid => | ||||
|         selectSimple( | ||||
|           List(RTagItem.Columns.itemId), | ||||
|           RTagItem.table, | ||||
|           RTagItem.Columns.tagId.is(tid) | ||||
|           List(RTagItem.t.itemId.column), | ||||
|           Fragment.const(RTagItem.t.tableName), | ||||
|           RTagItem.t.tagId.column.is(tid) | ||||
|         ) | ||||
|       ) ++ q.tagCategoryIncl.map(cat => TagItemName.itemsInCategory(NonEmptyList.of(cat))) | ||||
|  | ||||
| @@ -759,29 +759,31 @@ object QItem { | ||||
|     val aItem   = RAttachment.Columns.itemId.prefix("a") | ||||
|     val mId     = RAttachmentMeta.Columns.id.prefix("m") | ||||
|     val mText   = RAttachmentMeta.Columns.content.prefix("m") | ||||
|     val tiItem = RTagItem.Columns.itemId.prefix("ti") | ||||
|     val tiTag  = RTagItem.Columns.tagId.prefix("ti") | ||||
|     val tId    = RTag.Columns.tid.prefix("t") | ||||
|     val tName  = RTag.Columns.name.prefix("t") | ||||
|     val tCat   = RTag.Columns.category.prefix("t") | ||||
|     val tagItem = RTagItem.as("ti") //Columns.itemId.prefix("ti") | ||||
|     //val tiTag  = RTagItem.Columns.tagId.prefix("ti") | ||||
|     val tag = RTag.as("t") | ||||
| //    val tId   = RTag.Columns.tid.prefix("t") | ||||
| //    val tName = RTag.Columns.name.prefix("t") | ||||
| //    val tCat  = RTag.Columns.category.prefix("t") | ||||
|     val iId   = RItem.Columns.id.prefix("i") | ||||
|     val iColl = RItem.Columns.cid.prefix("i") | ||||
|  | ||||
|     val cte = withCTE( | ||||
|       "tags" -> selectSimple( | ||||
|         Seq(tiItem, tId, tName), | ||||
|         RTagItem.table ++ fr"ti INNER JOIN" ++ | ||||
|           RTag.table ++ fr"t ON" ++ tId.is(tiTag), | ||||
|         and(tiItem.is(itemId), tCat.is(tagCategory)) | ||||
|         Seq(tagItem.itemId.column, tag.tid.column, tag.name.column), | ||||
|         Fragment.const(RTagItem.t.tableName) ++ fr"ti INNER JOIN" ++ | ||||
|           Fragment.const(tag.tableName) ++ fr"t ON" ++ tag.tid.column | ||||
|             .is(tagItem.tagId.column), | ||||
|         and(tagItem.itemId.column.is(itemId), tag.category.column.is(tagCategory)) | ||||
|       ) | ||||
|     ) | ||||
|  | ||||
|     val cols = Seq(mText, tId, tName) | ||||
|     val cols = Seq(mText, tag.tid.column, tag.name.column) | ||||
|  | ||||
|     val from = RItem.table ++ fr"i INNER JOIN" ++ | ||||
|       RAttachment.table ++ fr"a ON" ++ aItem.is(iId) ++ fr"INNER JOIN" ++ | ||||
|       RAttachmentMeta.table ++ fr"m ON" ++ aId.is(mId) ++ fr"LEFT JOIN" ++ | ||||
|       fr"tags t ON" ++ RTagItem.Columns.itemId.prefix("t").is(iId) | ||||
|       fr"tags t ON" ++ RTagItem.t.itemId.oldColumn.prefix("t").is(iId) | ||||
|  | ||||
|     val where = | ||||
|       and( | ||||
|   | ||||
| @@ -38,8 +38,6 @@ object REquipment { | ||||
|         t.all, | ||||
|         fr"${v.eid},${v.cid},${v.name},${v.created},${v.updated}" | ||||
|       ) | ||||
|       .update | ||||
|       .run | ||||
|   } | ||||
|  | ||||
|   def update(v: REquipment): ConnectionIO[Int] = { | ||||
| @@ -95,6 +93,6 @@ object REquipment { | ||||
|  | ||||
|   def delete(id: Ident, coll: Ident): ConnectionIO[Int] = { | ||||
|     val t = Table(None) | ||||
|     DML.delete(t, t.eid === id && t.cid === coll).update.run | ||||
|     DML.delete(t, t.eid === id && t.cid === coll) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -4,8 +4,8 @@ import cats.effect.Sync | ||||
| 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._ | ||||
| @@ -23,35 +23,42 @@ object RNode { | ||||
|   def apply[F[_]: Sync](id: Ident, nodeType: NodeType, uri: LenientUri): F[RNode] = | ||||
|     Timestamp.current[F].map(now => RNode(id, nodeType, uri, now, now)) | ||||
|  | ||||
|   val table = fr"node" | ||||
|   final case class Table(alias: Option[String]) extends TableDef { | ||||
|     val tableName = "node" | ||||
|  | ||||
|   object Columns { | ||||
|     val id       = Column("id") | ||||
|     val nodeType = Column("type") | ||||
|     val url      = Column("url") | ||||
|     val updated  = Column("updated") | ||||
|     val created  = Column("created") | ||||
|     val id       = Column[Ident]("id", this) | ||||
|     val nodeType = Column[NodeType]("type", this) | ||||
|     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) | ||||
|   } | ||||
|   import Columns._ | ||||
|  | ||||
|   def insert(v: RNode): ConnectionIO[Int] = | ||||
|     insertRow( | ||||
|       table, | ||||
|       all, | ||||
|   def as(alias: String): Table = | ||||
|     Table(Some(alias)) | ||||
|  | ||||
|   def insert(v: RNode): ConnectionIO[Int] = { | ||||
|     val t = Table(None) | ||||
|     DML.insert( | ||||
|       t, | ||||
|       t.all, | ||||
|       fr"${v.id},${v.nodeType},${v.url},${v.updated},${v.created}" | ||||
|     ).update.run | ||||
|  | ||||
|   def update(v: RNode): ConnectionIO[Int] = | ||||
|     updateRow( | ||||
|       table, | ||||
|       id.is(v.id), | ||||
|       commas( | ||||
|         nodeType.setTo(v.nodeType), | ||||
|         url.setTo(v.url), | ||||
|         updated.setTo(v.updated) | ||||
|     ) | ||||
|     ).update.run | ||||
|   } | ||||
|  | ||||
|   def update(v: RNode): ConnectionIO[Int] = { | ||||
|     val t = Table(None) | ||||
|     DML | ||||
|       .update( | ||||
|         t, | ||||
|         t.id === v.id, | ||||
|         DML.set( | ||||
|           t.nodeType.setTo(v.nodeType), | ||||
|           t.url.setTo(v.url), | ||||
|           t.updated.setTo(v.updated) | ||||
|         ) | ||||
|       ) | ||||
|   } | ||||
|  | ||||
|   def set(v: RNode): ConnectionIO[Int] = | ||||
|     for { | ||||
| @@ -59,12 +66,18 @@ object RNode { | ||||
|       k <- if (n == 0) insert(v) else 0.pure[ConnectionIO] | ||||
|     } yield n + k | ||||
|  | ||||
|   def delete(appId: Ident): ConnectionIO[Int] = | ||||
|     (fr"DELETE FROM" ++ table ++ where(id.is(appId))).update.run | ||||
|  | ||||
|   def findAll(nt: NodeType): ConnectionIO[Vector[RNode]] = | ||||
|     selectSimple(all, table, nodeType.is(nt)).query[RNode].to[Vector] | ||||
|  | ||||
|   def findById(nodeId: Ident): ConnectionIO[Option[RNode]] = | ||||
|     selectSimple(all, table, id.is(nodeId)).query[RNode].option | ||||
|   def delete(appId: Ident): ConnectionIO[Int] = { | ||||
|     val t = Table(None) | ||||
|     DML.delete(t, t.id === appId) | ||||
|   } | ||||
|  | ||||
|   def findAll(nt: NodeType): ConnectionIO[Vector[RNode]] = { | ||||
|     val t = Table(None) | ||||
|     run(select(t.all), from(t), t.nodeType === nt).query[RNode].to[Vector] | ||||
|   } | ||||
|  | ||||
|   def findById(nodeId: Ident): ConnectionIO[Option[RNode]] = { | ||||
|     val t = Table(None) | ||||
|     run(select(t.all), from(t), t.id === nodeId).query[RNode].option | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -60,14 +60,12 @@ object RSource { | ||||
|  | ||||
|   val table = Table(None) | ||||
|  | ||||
|   def insert(v: RSource): ConnectionIO[Int] = { | ||||
|     val sql = DML.insert( | ||||
|   def insert(v: RSource): ConnectionIO[Int] = | ||||
|     DML.insert( | ||||
|       table, | ||||
|       table.all, | ||||
|       fr"${v.sid},${v.cid},${v.abbrev},${v.description},${v.counter},${v.enabled},${v.priority},${v.created},${v.folderId},${v.fileFilter}" | ||||
|     ) | ||||
|     sql.update.run | ||||
|   } | ||||
|  | ||||
|   def updateNoCounter(v: RSource): ConnectionIO[Int] = | ||||
|     DML.update( | ||||
| @@ -85,8 +83,7 @@ object RSource { | ||||
|     ) | ||||
|  | ||||
|   def incrementCounter(source: String, coll: Ident): ConnectionIO[Int] = | ||||
|     DML | ||||
|       .update( | ||||
|     DML.update( | ||||
|       table, | ||||
|       where(table.abbrev === source, table.cid === coll), | ||||
|       DML.set(table.counter.increment(1)) | ||||
| @@ -130,7 +127,7 @@ object RSource { | ||||
|   } | ||||
|  | ||||
|   def delete(sourceId: Ident, coll: Ident): ConnectionIO[Int] = | ||||
|     DML.delete(table, where(table.sid === sourceId, table.cid === coll)).update.run | ||||
|     DML.delete(table, where(table.sid === sourceId, table.cid === coll)) | ||||
|  | ||||
|   def removeFolder(folderId: Ident): ConnectionIO[Int] = { | ||||
|     val empty: Option[Ident] = None | ||||
|   | ||||
| @@ -4,8 +4,8 @@ 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._ | ||||
| @@ -19,101 +19,97 @@ case class RTag( | ||||
| ) {} | ||||
|  | ||||
| object RTag { | ||||
|   final case class Table(alias: Option[String]) extends TableDef { | ||||
|     val tableName = "tag" | ||||
|  | ||||
|   val table = fr"tag" | ||||
|  | ||||
|   object Columns { | ||||
|     val tid      = Column("tid") | ||||
|     val cid      = Column("cid") | ||||
|     val name     = Column("name") | ||||
|     val category = Column("category") | ||||
|     val created  = Column("created") | ||||
|     val tid      = Column[Ident]("tid", this) | ||||
|     val cid      = Column[Ident]("cid", this) | ||||
|     val name     = Column[String]("name", this) | ||||
|     val category = Column[String]("category", this) | ||||
|     val created  = Column[Timestamp]("created", this) | ||||
|     val all      = List(tid, cid, name, category, created) | ||||
|   } | ||||
|   import Columns._ | ||||
|   val T = Table(None) | ||||
|   def as(alias: String): Table = | ||||
|     Table(Some(alias)) | ||||
|  | ||||
|   def insert(v: RTag): ConnectionIO[Int] = { | ||||
|     val sql = | ||||
|       insertRow( | ||||
|         table, | ||||
|         all, | ||||
|   def insert(v: RTag): ConnectionIO[Int] = | ||||
|     DML.insert( | ||||
|       T, | ||||
|       T.all, | ||||
|       fr"${v.tagId},${v.collective},${v.name},${v.category},${v.created}" | ||||
|     ) | ||||
|     sql.update.run | ||||
|   } | ||||
|  | ||||
|   def update(v: RTag): ConnectionIO[Int] = { | ||||
|     val sql = updateRow( | ||||
|       table, | ||||
|       and(tid.is(v.tagId), cid.is(v.collective)), | ||||
|       commas( | ||||
|         cid.setTo(v.collective), | ||||
|         name.setTo(v.name), | ||||
|         category.setTo(v.category) | ||||
|   def update(v: RTag): ConnectionIO[Int] = | ||||
|     DML.update( | ||||
|       T, | ||||
|       T.tid === v.tagId && T.cid === v.collective, | ||||
|       DML.set( | ||||
|         T.cid.setTo(v.collective), | ||||
|         T.name.setTo(v.name), | ||||
|         T.category.setTo(v.category) | ||||
|       ) | ||||
|     ) | ||||
|     sql.update.run | ||||
|   } | ||||
|  | ||||
|   def findById(id: Ident): ConnectionIO[Option[RTag]] = { | ||||
|     val sql = selectSimple(all, table, tid.is(id)) | ||||
|     val sql = run(select(T.all), from(T), T.tid === id) | ||||
|     sql.query[RTag].option | ||||
|   } | ||||
|  | ||||
|   def findByIdAndCollective(id: Ident, coll: Ident): ConnectionIO[Option[RTag]] = { | ||||
|     val sql = selectSimple(all, table, and(tid.is(id), cid.is(coll))) | ||||
|     val sql = run(select(T.all), from(T), T.tid === id && T.cid === coll) | ||||
|     sql.query[RTag].option | ||||
|   } | ||||
|  | ||||
|   def existsByName(tag: RTag): ConnectionIO[Boolean] = { | ||||
|     val sql = selectCount( | ||||
|       tid, | ||||
|       table, | ||||
|       and(cid.is(tag.collective), name.is(tag.name)) | ||||
|     ) | ||||
|     val sql = | ||||
|       run(select(count(T.tid)), from(T), T.cid === tag.collective && T.name === tag.name) | ||||
|     sql.query[Int].unique.map(_ > 0) | ||||
|   } | ||||
|  | ||||
|   def findAll( | ||||
|       coll: Ident, | ||||
|       nameQ: Option[String], | ||||
|       order: Columns.type => Column | ||||
|       order: Table => Column[_] | ||||
|   ): ConnectionIO[Vector[RTag]] = { | ||||
|     val q = Seq(cid.is(coll)) ++ (nameQ match { | ||||
|       case Some(str) => Seq(name.lowerLike(s"%${str.toLowerCase}%")) | ||||
|       case None      => Seq.empty | ||||
|     }) | ||||
|     val sql = selectSimple(all, table, and(q)) ++ orderBy(order(Columns).f) | ||||
|     sql.query[RTag].to[Vector] | ||||
|     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] | ||||
|   } | ||||
|  | ||||
|   def findAllById(ids: List[Ident]): ConnectionIO[Vector[RTag]] = | ||||
|     selectSimple(all, table, tid.isIn(ids.map(id => sql"$id").toSeq)) | ||||
|     NonEmptyList.fromList(ids) match { | ||||
|       case Some(nel) => | ||||
|         run(select(T.all), from(T), T.tid.in(nel)) | ||||
|           .query[RTag] | ||||
|           .to[Vector] | ||||
|       case None => | ||||
|         Vector.empty.pure[ConnectionIO] | ||||
|     } | ||||
|  | ||||
|   def findByItem(itemId: Ident): ConnectionIO[Vector[RTag]] = { | ||||
|     val rcol = all.map(_.prefix("t")) | ||||
|     (selectSimple( | ||||
|       rcol, | ||||
|       table ++ fr"t," ++ RTagItem.table ++ fr"i", | ||||
|       and( | ||||
|         RTagItem.Columns.itemId.prefix("i").is(itemId), | ||||
|         RTagItem.Columns.tagId.prefix("i").is(tid.prefix("t")) | ||||
|       ) | ||||
|     ) ++ orderBy(name.prefix("t").asc)).query[RTag].to[Vector] | ||||
|     val ti = RTagItem.as("i") | ||||
|     val t  = RTag.as("t") | ||||
|     val sql = | ||||
|       Select( | ||||
|         select(t.all), | ||||
|         from(t).innerJoin(ti, ti.tagId === t.tid), | ||||
|         ti.itemId === itemId | ||||
|       ).orderBy(t.name.asc) | ||||
|     sql.run.query[RTag].to[Vector] | ||||
|   } | ||||
|  | ||||
|   def findBySource(source: Ident): ConnectionIO[Vector[RTag]] = { | ||||
|     val rcol = all.map(_.prefix("t")) | ||||
|     (selectSimple( | ||||
|       rcol, | ||||
|       table ++ fr"t," ++ RTagSource.table ++ fr"s", | ||||
|       and( | ||||
|         RTagSource.Columns.sourceId.prefix("s").is(source), | ||||
|         RTagSource.Columns.tagId.prefix("s").is(tid.prefix("t")) | ||||
|       ) | ||||
|     ) ++ orderBy(name.prefix("t").asc)).query[RTag].to[Vector] | ||||
|     val s = RTagSource.as("s") | ||||
|     val t = RTag.as("t") | ||||
|     val sql = | ||||
|       Select( | ||||
|         select(t.all), | ||||
|         from(t).innerJoin(s, s.tagId === t.tid), | ||||
|         s.sourceId === source | ||||
|       ).orderBy(t.name.asc) | ||||
|     sql.run.query[RTag].to[Vector] | ||||
|   } | ||||
|  | ||||
|   def findAllByNameOrId( | ||||
| @@ -121,16 +117,22 @@ object RTag { | ||||
|       coll: Ident | ||||
|   ): ConnectionIO[Vector[RTag]] = { | ||||
|     val idList = | ||||
|       NonEmptyList.fromList(nameOrIds.flatMap(s => Ident.fromString(s).toOption)).toSeq | ||||
|     val nameList = NonEmptyList.fromList(nameOrIds.map(_.toLowerCase)).toSeq | ||||
|  | ||||
|     val cond = idList.flatMap(ids => Seq(tid.isIn(ids))) ++ | ||||
|       nameList.flatMap(ns => Seq(name.isLowerIn(ns))) | ||||
|  | ||||
|     if (cond.isEmpty) Vector.empty.pure[ConnectionIO] | ||||
|     else selectSimple(all, table, and(cid.is(coll), or(cond))).query[RTag].to[Vector] | ||||
|       NonEmptyList.fromList(nameOrIds.flatMap(s => Ident.fromString(s).toOption)) | ||||
|     val nameList = NonEmptyList.fromList(nameOrIds.map(_.toLowerCase)) | ||||
|     (idList, nameList) match { | ||||
|       case (Some(ids), _) => | ||||
|         val cond = | ||||
|           T.cid === coll && (T.tid.in(ids) ||? nameList.map(names => T.name.in(names))) | ||||
|         run(select(T.all), from(T), cond).query[RTag].to[Vector] | ||||
|       case (_, Some(names)) => | ||||
|         val cond = | ||||
|           T.cid === coll && (T.name.in(names) ||? idList.map(ids => T.tid.in(ids))) | ||||
|         run(select(T.all), from(T), cond).query[RTag].to[Vector] | ||||
|       case (None, None) => | ||||
|         Vector.empty.pure[ConnectionIO] | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   def delete(tagId: Ident, coll: Ident): ConnectionIO[Int] = | ||||
|     deleteFrom(table, and(tid.is(tagId), cid.is(coll))).update.run | ||||
|     DML.delete(T, T.tid === tagId && T.cid === coll) | ||||
| } | ||||
|   | ||||
| @@ -4,8 +4,8 @@ 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._ | ||||
| @@ -13,41 +13,45 @@ import doobie.implicits._ | ||||
| case class RTagItem(tagItemId: Ident, itemId: Ident, tagId: Ident) {} | ||||
|  | ||||
| object RTagItem { | ||||
|   final case class Table(alias: Option[String]) extends TableDef { | ||||
|     val tableName = "tagitem" | ||||
|  | ||||
|   val table = fr"tagitem" | ||||
|  | ||||
|   object Columns { | ||||
|     val tagItemId = Column("tagitemid") | ||||
|     val itemId    = Column("itemid") | ||||
|     val tagId     = Column("tid") | ||||
|     val tagItemId = Column[Ident]("tagitemid", this) | ||||
|     val itemId    = Column[Ident]("itemid", this) | ||||
|     val tagId     = Column[Ident]("tid", this) | ||||
|     val all       = List(tagItemId, itemId, tagId) | ||||
|   } | ||||
|   import Columns._ | ||||
|   val t = Table(None) | ||||
|   def as(alias: String): Table = | ||||
|     Table(Some(alias)) | ||||
|  | ||||
|   def insert(v: RTagItem): ConnectionIO[Int] = | ||||
|     insertRow(table, all, fr"${v.tagItemId},${v.itemId},${v.tagId}").update.run | ||||
|     DML.insert(t, t.all, fr"${v.tagItemId},${v.itemId},${v.tagId}") | ||||
|  | ||||
|   def deleteItemTags(item: Ident): ConnectionIO[Int] = | ||||
|     deleteFrom(table, itemId.is(item)).update.run | ||||
|     DML.delete(t, t.itemId === item) | ||||
|  | ||||
|   def deleteItemTags(items: NonEmptyList[Ident], cid: Ident): ConnectionIO[Int] = { | ||||
|     val itemsFiltered = | ||||
|       RItem.filterItemsFragment(items, cid) | ||||
|     val sql = fr"DELETE FROM" ++ table ++ fr"WHERE" ++ itemId.isIn(itemsFiltered) | ||||
|  | ||||
|     sql.update.run | ||||
|     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 deleteTag(tid: Ident): ConnectionIO[Int] = | ||||
|     deleteFrom(table, tagId.is(tid)).update.run | ||||
|     DML.delete(t, t.tagId === tid) | ||||
|  | ||||
|   def findByItem(item: Ident): ConnectionIO[Vector[RTagItem]] = | ||||
|     selectSimple(all, table, itemId.is(item)).query[RTagItem].to[Vector] | ||||
|     run(select(t.all), from(t), t.itemId === item).query[RTagItem].to[Vector] | ||||
|  | ||||
|   def findAllIn(item: Ident, tags: Seq[Ident]): ConnectionIO[Vector[RTagItem]] = | ||||
|     NonEmptyList.fromList(tags.toList) match { | ||||
|       case Some(nel) => | ||||
|         selectSimple(all, table, and(itemId.is(item), tagId.isIn(nel))) | ||||
|         run(select(t.all), from(t), t.itemId === item && t.tagId.in(nel)) | ||||
|           .query[RTagItem] | ||||
|           .to[Vector] | ||||
|       case None => | ||||
| @@ -59,7 +63,7 @@ object RTagItem { | ||||
|       case None => | ||||
|         0.pure[ConnectionIO] | ||||
|       case Some(nel) => | ||||
|         deleteFrom(table, and(itemId.is(item), tagId.isIn(nel))).update.run | ||||
|         DML.delete(t, t.itemId === item && t.tagId.in(nel)) | ||||
|     } | ||||
|  | ||||
|   def setAllTags(item: Ident, tags: Seq[Ident]): ConnectionIO[Int] = | ||||
| @@ -69,11 +73,12 @@ object RTagItem { | ||||
|         entities <- tags.toList.traverse(tagId => | ||||
|           Ident.randomId[ConnectionIO].map(id => RTagItem(id, item, tagId)) | ||||
|         ) | ||||
|         n <- insertRows( | ||||
|           table, | ||||
|           all, | ||||
|         n <- DML | ||||
|           .insertMany( | ||||
|             t, | ||||
|             t.all, | ||||
|             entities.map(v => fr"${v.tagItemId},${v.itemId},${v.tagId}") | ||||
|         ).update.run | ||||
|           ) | ||||
|       } yield n | ||||
|  | ||||
|   def appendTags(item: Ident, tags: List[Ident]): ConnectionIO[Int] = | ||||
|   | ||||
| @@ -4,8 +4,8 @@ import cats.effect.Sync | ||||
| 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._ | ||||
| @@ -13,31 +13,33 @@ import doobie.implicits._ | ||||
| case class RTagSource(id: Ident, sourceId: Ident, tagId: Ident) {} | ||||
|  | ||||
| object RTagSource { | ||||
|   final case class Table(alias: Option[String]) extends TableDef { | ||||
|     val tableName = "tagsource" | ||||
|  | ||||
|   val table = fr"tagsource" | ||||
|  | ||||
|   object Columns { | ||||
|     val id       = Column("id") | ||||
|     val sourceId = Column("source_id") | ||||
|     val tagId    = Column("tag_id") | ||||
|     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) | ||||
|   } | ||||
|   import Columns._ | ||||
|  | ||||
|   private val t = Table(None) | ||||
|   def as(alias: String): Table = | ||||
|     Table(Some(alias)) | ||||
|  | ||||
|   def createNew[F[_]: Sync](source: Ident, tag: Ident): F[RTagSource] = | ||||
|     Ident.randomId[F].map(id => RTagSource(id, source, tag)) | ||||
|  | ||||
|   def insert(v: RTagSource): ConnectionIO[Int] = | ||||
|     insertRow(table, all, fr"${v.id},${v.sourceId},${v.tagId}").update.run | ||||
|     DML.insert(t, t.all, fr"${v.id},${v.sourceId},${v.tagId}") | ||||
|  | ||||
|   def deleteSourceTags(source: Ident): ConnectionIO[Int] = | ||||
|     deleteFrom(table, sourceId.is(source)).update.run | ||||
|     DML.delete(t, t.sourceId === source) | ||||
|  | ||||
|   def deleteTag(tid: Ident): ConnectionIO[Int] = | ||||
|     deleteFrom(table, tagId.is(tid)).update.run | ||||
|     DML.delete(t, t.tagId === tid) | ||||
|  | ||||
|   def findBySource(source: Ident): ConnectionIO[Vector[RTagSource]] = | ||||
|     selectSimple(all, table, sourceId.is(source)).query[RTagSource].to[Vector] | ||||
|     run(select(t.all), from(t), t.sourceId === source).query[RTagSource].to[Vector] | ||||
|  | ||||
|   def setAllTags(source: Ident, tags: Seq[Ident]): ConnectionIO[Int] = | ||||
|     if (tags.isEmpty) 0.pure[ConnectionIO] | ||||
| @@ -46,11 +48,12 @@ object RTagSource { | ||||
|         entities <- tags.toList.traverse(tagId => | ||||
|           Ident.randomId[ConnectionIO].map(id => RTagSource(id, source, tagId)) | ||||
|         ) | ||||
|         n <- insertRows( | ||||
|           table, | ||||
|           all, | ||||
|         n <- DML | ||||
|           .insertMany( | ||||
|             t, | ||||
|             t.all, | ||||
|             entities.map(v => fr"${v.id},${v.sourceId},${v.tagId}") | ||||
|         ).update.run | ||||
|           ) | ||||
|       } yield n | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -42,12 +42,11 @@ object RUser { | ||||
|  | ||||
|   def insert(v: RUser): ConnectionIO[Int] = { | ||||
|     val t = Table(None) | ||||
|     val sql = DML.insert( | ||||
|     DML.insert( | ||||
|       t, | ||||
|       t.all, | ||||
|       fr"${v.uid},${v.login},${v.cid},${v.password},${v.state},${v.email},${v.loginCount},${v.lastLogin},${v.created}" | ||||
|     ) | ||||
|     sql.update.run | ||||
|   } | ||||
|  | ||||
|   def update(v: RUser): ConnectionIO[Int] = { | ||||
| @@ -113,6 +112,6 @@ object RUser { | ||||
|  | ||||
|   def delete(user: Ident, coll: Ident): ConnectionIO[Int] = { | ||||
|     val t = Table(None) | ||||
|     DML.delete(t, t.cid === coll && t.login === user).update.run | ||||
|     DML.delete(t, t.cid === coll && t.login === user) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -5,8 +5,8 @@ import cats.effect._ | ||||
| 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._ | ||||
| @@ -101,22 +101,22 @@ object RUserEmail { | ||||
|       mailReplyTo, | ||||
|       now | ||||
|     ) | ||||
|   final case class Table(alias: Option[String]) extends TableDef { | ||||
|  | ||||
|   val table = fr"useremail" | ||||
|     val tableName = "useremail" | ||||
|  | ||||
|   object Columns { | ||||
|     val id            = Column("id") | ||||
|     val uid           = Column("uid") | ||||
|     val name          = Column("name") | ||||
|     val smtpHost      = Column("smtp_host") | ||||
|     val smtpPort      = Column("smtp_port") | ||||
|     val smtpUser      = Column("smtp_user") | ||||
|     val smtpPass      = Column("smtp_password") | ||||
|     val smtpSsl       = Column("smtp_ssl") | ||||
|     val smtpCertCheck = Column("smtp_certcheck") | ||||
|     val mailFrom      = Column("mail_from") | ||||
|     val mailReplyTo   = Column("mail_replyto") | ||||
|     val created       = Column("created") | ||||
|     val id            = Column[Ident]("id", this) | ||||
|     val uid           = Column[Ident]("uid", this) | ||||
|     val name          = Column[Ident]("name", this) | ||||
|     val smtpHost      = Column[String]("smtp_host", this) | ||||
|     val smtpPort      = Column[Int]("smtp_port", this) | ||||
|     val smtpUser      = Column[String]("smtp_user", this) | ||||
|     val smtpPass      = Column[Password]("smtp_password", this) | ||||
|     val smtpSsl       = Column[SSLType]("smtp_ssl", this) | ||||
|     val smtpCertCheck = Column[Boolean]("smtp_certcheck", this) | ||||
|     val mailFrom      = Column[MailAddress]("mail_from", this) | ||||
|     val mailReplyTo   = Column[MailAddress]("mail_replyto", this) | ||||
|     val created       = Column[Timestamp]("created", this) | ||||
|  | ||||
|     val all = List( | ||||
|       id, | ||||
| @@ -134,34 +134,41 @@ object RUserEmail { | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   import Columns._ | ||||
|   def as(alias: String): Table = | ||||
|     Table(Some(alias)) | ||||
|  | ||||
|   def insert(v: RUserEmail): ConnectionIO[Int] = | ||||
|     insertRow( | ||||
|       table, | ||||
|       all, | ||||
|   def insert(v: RUserEmail): ConnectionIO[Int] = { | ||||
|     val t = Table(None) | ||||
|     DML.insert( | ||||
|       t, | ||||
|       t.all, | ||||
|       sql"${v.id},${v.uid},${v.name},${v.smtpHost},${v.smtpPort},${v.smtpUser},${v.smtpPassword},${v.smtpSsl},${v.smtpCertCheck},${v.mailFrom},${v.mailReplyTo},${v.created}" | ||||
|     ).update.run | ||||
|  | ||||
|   def update(eId: Ident, v: RUserEmail): ConnectionIO[Int] = | ||||
|     updateRow( | ||||
|       table, | ||||
|       id.is(eId), | ||||
|       commas( | ||||
|         name.setTo(v.name), | ||||
|         smtpHost.setTo(v.smtpHost), | ||||
|         smtpPort.setTo(v.smtpPort), | ||||
|         smtpUser.setTo(v.smtpUser), | ||||
|         smtpPass.setTo(v.smtpPassword), | ||||
|         smtpSsl.setTo(v.smtpSsl), | ||||
|         smtpCertCheck.setTo(v.smtpCertCheck), | ||||
|         mailFrom.setTo(v.mailFrom), | ||||
|         mailReplyTo.setTo(v.mailReplyTo) | ||||
|     ) | ||||
|     ).update.run | ||||
|   } | ||||
|  | ||||
|   def findByUser(userId: Ident): ConnectionIO[Vector[RUserEmail]] = | ||||
|     selectSimple(all, table, uid.is(userId)).query[RUserEmail].to[Vector] | ||||
|   def update(eId: Ident, v: RUserEmail): ConnectionIO[Int] = { | ||||
|     val t = Table(None) | ||||
|     DML.update( | ||||
|       t, | ||||
|       t.id === eId, | ||||
|       DML.set( | ||||
|         t.name.setTo(v.name), | ||||
|         t.smtpHost.setTo(v.smtpHost), | ||||
|         t.smtpPort.setTo(v.smtpPort), | ||||
|         t.smtpUser.setTo(v.smtpUser), | ||||
|         t.smtpPass.setTo(v.smtpPassword), | ||||
|         t.smtpSsl.setTo(v.smtpSsl), | ||||
|         t.smtpCertCheck.setTo(v.smtpCertCheck), | ||||
|         t.mailFrom.setTo(v.mailFrom), | ||||
|         t.mailReplyTo.setTo(v.mailReplyTo) | ||||
|       ) | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   def findByUser(userId: Ident): ConnectionIO[Vector[RUserEmail]] = { | ||||
|     val t = Table(None) | ||||
|     run(select(t.all), from(t), t.uid === userId).query[RUserEmail].to[Vector] | ||||
|   } | ||||
|  | ||||
|   private def findByAccount0( | ||||
|       accId: AccountId, | ||||
| @@ -169,23 +176,19 @@ object RUserEmail { | ||||
|       exact: Boolean | ||||
|   ): Query0[RUserEmail] = { | ||||
|     val user  = RUser.as("u") | ||||
|     val mUid   = uid.prefix("m") | ||||
|     val mName  = name.prefix("m") | ||||
|     val uId    = user.uid.column | ||||
|     val uColl  = user.cid.column | ||||
|     val uLogin = user.login.column | ||||
|     val from = | ||||
|       table ++ fr"m INNER JOIN" ++ Fragment.const(user.tableName) ++ fr"u ON" ++ mUid.is( | ||||
|         uId | ||||
|       ) | ||||
|     val cond = Seq(uColl.is(accId.collective), uLogin.is(accId.user)) ++ (nameQ match { | ||||
|       case Some(str) if exact => Seq(mName.is(str)) | ||||
|       case Some(str)          => Seq(mName.lowerLike(s"%${str.toLowerCase}%")) | ||||
|       case None               => Seq.empty | ||||
|     }) | ||||
|     val email = as("m") | ||||
|  | ||||
|     (selectSimple(all.map(_.prefix("m")), from, and(cond)) ++ orderBy(mName.f)) | ||||
|       .query[RUserEmail] | ||||
|     val nameFilter = nameQ.map(s => | ||||
|       if (exact) email.name ==== s else email.name.likes(s"%${s.toLowerCase}%") | ||||
|     ) | ||||
|  | ||||
|     val sql = Select( | ||||
|       select(email.all), | ||||
|       from(email).innerJoin(user, email.uid === user.uid), | ||||
|       user.cid === accId.collective && user.login === accId.user &&? nameFilter | ||||
|     ).orderBy(email.name) | ||||
|  | ||||
|     sql.run.query[RUserEmail] | ||||
|   } | ||||
|  | ||||
|   def findByAccount( | ||||
| @@ -199,30 +202,25 @@ object RUserEmail { | ||||
|  | ||||
|   def delete(accId: AccountId, connName: Ident): ConnectionIO[Int] = { | ||||
|     val user = RUser.as("u") | ||||
|     val uId    = user.uid.column | ||||
|     val uColl  = user.cid.column | ||||
|     val uLogin = user.login.column | ||||
|     val cond   = Seq(uColl.is(accId.collective), uLogin.is(accId.user)) | ||||
|  | ||||
|     deleteFrom( | ||||
|       table, | ||||
|       fr"uid in (" ++ selectSimple( | ||||
|         Seq(uId), | ||||
|         Fragment.const(user.tableName), | ||||
|         and(cond) | ||||
|       ) ++ fr") AND" ++ name | ||||
|         .is( | ||||
|           connName | ||||
|     val subsel = Select( | ||||
|       select(user.uid), | ||||
|       from(user), | ||||
|       user.cid === accId.collective && user.login === accId.user | ||||
|     ) | ||||
|     ).update.run | ||||
|  | ||||
|     val t = Table(None) | ||||
|     DML.delete(t, t.uid.in(subsel) && t.name === connName) | ||||
|   } | ||||
|  | ||||
|   def exists(accId: AccountId, name: Ident): ConnectionIO[Boolean] = | ||||
|     getByName(accId, name).map(_.isDefined) | ||||
|  | ||||
|   def exists(userId: Ident, connName: Ident): ConnectionIO[Boolean] = | ||||
|     selectCount(id, table, and(uid.is(userId), name.is(connName))) | ||||
|   def exists(userId: Ident, connName: Ident): ConnectionIO[Boolean] = { | ||||
|     val t = Table(None) | ||||
|     run(select(count(t.id)), from(t), t.uid === userId && t.name === connName) | ||||
|       .query[Int] | ||||
|       .unique | ||||
|       .map(_ > 0) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -131,8 +131,6 @@ object RUserImap { | ||||
|         t.all, | ||||
|         sql"${v.id},${v.uid},${v.name},${v.imapHost},${v.imapPort},${v.imapUser},${v.imapPassword},${v.imapSsl},${v.imapCertCheck},${v.created}" | ||||
|       ) | ||||
|       .update | ||||
|       .run | ||||
|   } | ||||
|  | ||||
|   def update(eId: Ident, v: RUserImap): ConnectionIO[Int] = { | ||||
| @@ -195,13 +193,10 @@ object RUserImap { | ||||
|     val subsel = | ||||
|       Select(select(u.uid), from(u), u.cid === accId.collective && u.login === accId.user) | ||||
|  | ||||
|     DML | ||||
|       .delete( | ||||
|     DML.delete( | ||||
|       t, | ||||
|       t.uid.in(subsel) && t.name === connName | ||||
|     ) | ||||
|       .update | ||||
|       .run | ||||
|   } | ||||
|  | ||||
|   def exists(accId: AccountId, name: Ident): ConnectionIO[Boolean] = | ||||
|   | ||||
| @@ -3,10 +3,9 @@ package docspell.store.records | ||||
| import cats.data.NonEmptyList | ||||
|  | ||||
| import docspell.common._ | ||||
| import docspell.store.impl.Implicits._ | ||||
| import docspell.store.qb.DSL._ | ||||
|  | ||||
| import doobie._ | ||||
| import doobie.implicits._ | ||||
|  | ||||
| /** A helper class combining information from `RTag` and `RTagItem`. | ||||
|   * This is not a "record", there is no corresponding table. | ||||
| @@ -24,37 +23,27 @@ object TagItemName { | ||||
|  | ||||
|   def itemsInCategory(cats: NonEmptyList[String]): Fragment = { | ||||
|     val catsLower = cats.map(_.toLowerCase) | ||||
|     val tiItem    = RTagItem.Columns.itemId.prefix("ti") | ||||
|     val tiTag     = RTagItem.Columns.tagId.prefix("ti") | ||||
|     val tCat      = RTag.Columns.category.prefix("t") | ||||
|     val tId       = RTag.Columns.tid.prefix("t") | ||||
|  | ||||
|     val from = RTag.table ++ fr"t INNER JOIN" ++ | ||||
|       RTagItem.table ++ fr"ti ON" ++ tiTag.is(tId) | ||||
|  | ||||
|     val ti        = RTagItem.as("ti") | ||||
|     val t         = RTag.as("t") | ||||
|     val join      = from(t).innerJoin(ti, t.tid === ti.tagId) | ||||
|     if (cats.tail.isEmpty) | ||||
|       selectSimple(List(tiItem), from, tCat.lowerIs(catsLower.head)) | ||||
|       run(select(ti.itemId), join, t.category.likes(catsLower.head)) | ||||
|     else | ||||
|       selectSimple(List(tiItem), from, tCat.isLowerIn(catsLower)) | ||||
|       run(select(ti.itemId), join, t.category.inLower(cats)) | ||||
|   } | ||||
|  | ||||
|   def itemsWithTagOrCategory(tags: List[Ident], cats: List[String]): Fragment = { | ||||
|     val catsLower = cats.map(_.toLowerCase) | ||||
|     val tiItem    = RTagItem.Columns.itemId.prefix("ti") | ||||
|     val tiTag     = RTagItem.Columns.tagId.prefix("ti") | ||||
|     val tCat      = RTag.Columns.category.prefix("t") | ||||
|     val tId       = RTag.Columns.tid.prefix("t") | ||||
|  | ||||
|     val from = RTag.table ++ fr"t INNER JOIN" ++ | ||||
|       RTagItem.table ++ fr"ti ON" ++ tiTag.is(tId) | ||||
|  | ||||
|     val ti        = RTagItem.as("ti") | ||||
|     val t         = RTag.as("t") | ||||
|     val join      = from(t).innerJoin(ti, t.tid === ti.tagId) | ||||
|     (NonEmptyList.fromList(tags), NonEmptyList.fromList(catsLower)) match { | ||||
|       case (Some(tagNel), Some(catNel)) => | ||||
|         selectSimple(List(tiItem), from, or(tId.isIn(tagNel), tCat.isLowerIn(catNel))) | ||||
|         run(select(ti.itemId), join, t.tid.in(tagNel) || t.category.inLower(catNel)) | ||||
|       case (Some(tagNel), None) => | ||||
|         selectSimple(List(tiItem), from, tId.isIn(tagNel)) | ||||
|         run(select(ti.itemId), join, t.tid.in(tagNel)) | ||||
|       case (None, Some(catNel)) => | ||||
|         selectSimple(List(tiItem), from, tCat.isLowerIn(catNel)) | ||||
|         run(select(ti.itemId), join, t.category.inLower(catNel)) | ||||
|       case (None, None) => | ||||
|         Fragment.empty | ||||
|     } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user