Convert more records

This commit is contained in:
Eike Kettner 2020-12-09 23:18:09 +01:00
parent 10b49fccf8
commit 5cbf0d5602
18 changed files with 381 additions and 314 deletions

View File

@ -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

View File

@ -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[_]]

View File

@ -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)
}

View File

@ -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
)
}

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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)))
@ -755,33 +755,35 @@ object QItem {
tagCategory: String,
pageSep: String
): ConnectionIO[TextAndTag] = {
val aId = RAttachment.Columns.id.prefix("a")
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 iId = RItem.Columns.id.prefix("i")
val iColl = RItem.Columns.cid.prefix("i")
val aId = RAttachment.Columns.id.prefix("a")
val aItem = RAttachment.Columns.itemId.prefix("a")
val mId = RAttachmentMeta.Columns.id.prefix("m")
val mText = RAttachmentMeta.Columns.content.prefix("m")
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(

View File

@ -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)
}
}

View File

@ -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)
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)
)
)
).update.run
}
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 delete(appId: Ident): ConnectionIO[Int] = {
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]] = {
val t = Table(None)
run(select(t.all), from(t), t.nodeType === nt).query[RNode].to[Vector]
}
def findById(nodeId: Ident): ConnectionIO[Option[RNode]] =
selectSimple(all, table, id.is(nodeId)).query[RNode].option
def findById(nodeId: Ident): ConnectionIO[Option[RNode]] = {
val t = Table(None)
run(select(t.all), from(t), t.id === nodeId).query[RNode].option
}
}

View File

@ -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,12 +83,11 @@ object RSource {
)
def incrementCounter(source: String, coll: Ident): ConnectionIO[Int] =
DML
.update(
table,
where(table.abbrev === source, table.cid === coll),
DML.set(table.counter.increment(1))
)
DML.update(
table,
where(table.abbrev === source, table.cid === coll),
DML.set(table.counter.increment(1))
)
def existsById(id: Ident): ConnectionIO[Boolean] = {
val sql = run(select(count(table.sid)), from(table), where(table.sid === id))
@ -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

View File

@ -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,
fr"${v.tagId},${v.collective},${v.name},${v.category},${v.created}"
)
sql.update.run
}
def insert(v: RTag): ConnectionIO[Int] =
DML.insert(
T,
T.all,
fr"${v.tagId},${v.collective},${v.name},${v.category},${v.created}"
)
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))
.query[RTag]
.to[Vector]
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)
}

View File

@ -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,
entities.map(v => fr"${v.tagItemId},${v.itemId},${v.tagId}")
).update.run
n <- DML
.insertMany(
t,
t.all,
entities.map(v => fr"${v.tagItemId},${v.itemId},${v.tagId}")
)
} yield n
def appendTags(item: Ident, tags: List[Ident]): ConnectionIO[Int] =

View File

@ -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,
entities.map(v => fr"${v.id},${v.sourceId},${v.tagId}")
).update.run
n <- DML
.insertMany(
t,
t.all,
entities.map(v => fr"${v.id},${v.sourceId},${v.tagId}")
)
} yield n
}

View File

@ -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)
}
}

View File

@ -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,58 +134,61 @@ 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)
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)
)
).update.run
)
}
def findByUser(userId: Ident): ConnectionIO[Vector[RUserEmail]] =
selectSimple(all, table, uid.is(userId)).query[RUserEmail].to[Vector]
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,
nameQ: Option[String],
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 user = RUser.as("u")
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(
@ -198,31 +201,26 @@ object RUserEmail {
findByAccount0(accId, Some(name.id), true).option
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))
val user = RUser.as("u")
deleteFrom(
table,
fr"uid in (" ++ selectSimple(
Seq(uId),
Fragment.const(user.tableName),
and(cond)
) ++ fr") AND" ++ name
.is(
connName
)
).update.run
val subsel = Select(
select(user.uid),
from(user),
user.cid === accId.collective && user.login === accId.user
)
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)
}
}

View File

@ -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(
t,
t.uid.in(subsel) && t.name === connName
)
.update
.run
DML.delete(
t,
t.uid.in(subsel) && t.name === connName
)
}
def exists(accId: AccountId, name: Ident): ConnectionIO[Boolean] =

View File

@ -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
}