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