Convert more records

This commit is contained in:
Eike Kettner 2020-12-11 01:05:54 +01:00
parent fe4815c737
commit 3cef932ccd
15 changed files with 376 additions and 394 deletions

View File

@ -136,32 +136,21 @@ object RegexNerFile {
object Sql { object Sql {
import doobie._ import doobie._
import doobie.implicits._ import docspell.store.qb.DSL._
import docspell.store.impl.Implicits._ import docspell.store.qb._
import docspell.store.impl.Column
def latestUpdate(collective: Ident): ConnectionIO[Option[Timestamp]] = { def latestUpdate(collective: Ident): ConnectionIO[Option[Timestamp]] = {
def max(col: Column, table: Fragment, cidCol: Column): Fragment = def max_(col: Column[_], cidCol: Column[Ident]): Select =
selectSimple(col.max ++ fr"as t", table, cidCol.is(collective)) Select(select(max(col).as("t")), from(col.table), cidCol === collective)
val equip = REquipment.as("e") val sql = union(
val sql = max_(ROrganization.T.updated, ROrganization.T.cid),
List( max_(RPerson.T.updated, RPerson.T.cid),
max( max_(REquipment.T.updated, REquipment.T.cid)
ROrganization.Columns.updated, )
ROrganization.table, val t = Column[Timestamp]("t", TableDef(""))
ROrganization.Columns.cid
),
max(RPerson.Columns.updated, RPerson.table, RPerson.Columns.cid),
max(
equip.updated.oldColumn,
Fragment.const(equip.tableName),
equip.cid.oldColumn
)
)
.reduce(_ ++ fr"UNION ALL" ++ _)
selectSimple(fr"MAX(t)", fr"(" ++ sql ++ fr") as x", Fragment.empty) run(select(max(t)), fromSubSelect(sql).as("x"))
.query[Option[Timestamp]] .query[Option[Timestamp]]
.option .option
.map(_.flatten) .map(_.flatten)

View File

@ -23,4 +23,9 @@ object DBFunction {
def as(a: String) = def as(a: String) =
copy(alias = a) copy(alias = a)
} }
case class Max(column: Column[_], alias: String) extends DBFunction {
def as(a: String) =
copy(alias = a)
}
} }

View File

@ -9,9 +9,19 @@ import doobie.{Fragment, Put}
trait DSL extends DoobieMeta { trait DSL extends DoobieMeta {
def run(projection: Seq[SelectExpr], from: FromExpr): Fragment =
DoobieQuery(Select(projection, from, None))
def run(projection: Seq[SelectExpr], from: FromExpr, where: Condition): Fragment = def run(projection: Seq[SelectExpr], from: FromExpr, where: Condition): Fragment =
DoobieQuery(Select(projection, from, where)) DoobieQuery(Select(projection, from, where))
def runDistinct(
projection: Seq[SelectExpr],
from: FromExpr,
where: Condition
): Fragment =
DoobieQuery.distinct(Select(projection, from, where))
def select(dbf: DBFunction): Seq[SelectExpr] = def select(dbf: DBFunction): Seq[SelectExpr] =
Seq(SelectExpr.SelectFun(dbf)) Seq(SelectExpr.SelectFun(dbf))
@ -21,12 +31,21 @@ trait DSL extends DoobieMeta {
def select(seq: Seq[Column[_]], seqs: Seq[Column[_]]*): Seq[SelectExpr] = def select(seq: Seq[Column[_]], seqs: Seq[Column[_]]*): Seq[SelectExpr] =
(seq ++ seqs.flatten).map(SelectExpr.SelectColumn.apply) (seq ++ seqs.flatten).map(SelectExpr.SelectColumn.apply)
def from(table: TableDef): FromExpr = def union(s1: Select, sn: Select*): Select =
Select.Union(s1, sn.toVector)
def from(table: TableDef): FromExpr.From =
FromExpr.From(table) FromExpr.From(table)
def fromSubSelect(sel: Select): FromExpr.SubSelect =
FromExpr.SubSelect(sel, "x")
def count(c: Column[_]): DBFunction = def count(c: Column[_]): DBFunction =
DBFunction.Count(c, "cn") DBFunction.Count(c, "cn")
def max(c: Column[_]): DBFunction =
DBFunction.Max(c, "mn")
def and(c: Condition, cs: Condition*): Condition = def and(c: Condition, cs: Condition*): Condition =
c match { c match {
case Condition.And(head, tail) => case Condition.And(head, tail) =>

View File

@ -2,9 +2,9 @@ package docspell.store.qb
sealed trait FromExpr { sealed trait FromExpr {
def innerJoin(other: TableDef, on: Condition): FromExpr // def innerJoin(other: TableDef, on: Condition): FromExpr
//
def leftJoin(other: TableDef, on: Condition): FromExpr // def leftJoin(other: TableDef, on: Condition): FromExpr
} }
object FromExpr { object FromExpr {
@ -23,6 +23,10 @@ object FromExpr {
def leftJoin(other: TableDef, on: Condition): Joined = def leftJoin(other: TableDef, on: Condition): Joined =
Joined(from, joins :+ Join.LeftJoin(other, on)) Joined(from, joins :+ Join.LeftJoin(other, on))
}
case class SubSelect(sel: Select, name: String) extends FromExpr {
def as(name: String): SubSelect =
copy(name = name)
} }
} }

View File

@ -5,3 +5,12 @@ trait TableDef {
def alias: Option[String] def alias: Option[String]
} }
object TableDef {
def apply(table: String, aliasName: Option[String] = None): TableDef =
new TableDef {
def tableName: String = table
def alias: Option[String] = aliasName
}
}

View File

@ -15,6 +15,9 @@ object FromExprBuilder {
case FromExpr.Joined(from, joins) => case FromExpr.Joined(from, joins) =>
build(from) ++ build(from) ++
joins.map(buildJoin).foldLeft(Fragment.empty)(_ ++ _) joins.map(buildJoin).foldLeft(Fragment.empty)(_ ++ _)
case FromExpr.SubSelect(sel, name) =>
sql" FROM (" ++ DoobieQuery(sel) ++ fr") AS" ++ Fragment.const(name)
} }
def buildTable(table: TableDef): Fragment = def buildTable(table: TableDef): Fragment =

View File

@ -13,16 +13,19 @@ object SelectExprBuilder {
column(col) column(col)
case SelectExpr.SelectFun(DBFunction.CountAll(alias)) => case SelectExpr.SelectFun(DBFunction.CountAll(alias)) =>
fr"COUNT(*) AS" ++ Fragment.const(alias) sql"COUNT(*) AS" ++ Fragment.const(alias)
case SelectExpr.SelectFun(DBFunction.Count(col, alias)) => case SelectExpr.SelectFun(DBFunction.Count(col, alias)) =>
fr"COUNT(" ++ column(col) ++ fr") AS" ++ Fragment.const(alias) sql"COUNT(" ++ column(col) ++ fr") AS" ++ Fragment.const(alias)
case SelectExpr.SelectFun(DBFunction.Max(col, alias)) =>
sql"MAX(" ++ column(col) ++ fr") AS" ++ Fragment.const(alias)
} }
def column(col: Column[_]): Fragment = { def column(col: Column[_]): Fragment = {
val prefix = val prefix = col.table.alias.getOrElse(col.table.tableName)
Fragment.const0(col.table.alias.getOrElse(col.table.tableName)) if (prefix.isEmpty) columnNoPrefix(col)
prefix ++ Fragment.const0(".") ++ Fragment.const0(col.name) else Fragment.const0(prefix) ++ Fragment.const0(".") ++ Fragment.const0(col.name)
} }
def columnNoPrefix(col: Column[_]): Fragment = def columnNoPrefix(col: Column[_]): Fragment =

View File

@ -91,61 +91,28 @@ object QCollective {
sql.run.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,
query: Option[String], query: Option[String],
kind: Option[ContactKind] kind: Option[ContactKind]
): Stream[ConnectionIO, RContact] = { ): Stream[ConnectionIO, RContact] = {
val RO = ROrganization import docspell.store.qb.DSL._
val RP = RPerson import docspell.store.qb._
val RC = RContact
val orgCond = selectSimple(Seq(RO.Columns.oid), RO.table, RO.Columns.cid.is(coll)) val ro = ROrganization.as("o")
val persCond = selectSimple(Seq(RP.Columns.pid), RP.table, RP.Columns.cid.is(coll)) val rp = RPerson.as("p")
val queryCond = query match { val rc = RContact.as("c")
case Some(q) =>
Seq(RC.Columns.value.lowerLike(s"%${q.toLowerCase}%"))
case None =>
Seq.empty
}
val kindCond = kind match {
case Some(k) =>
Seq(RC.Columns.kind.is(k))
case None =>
Seq.empty
}
val q = selectSimple( val orgCond = Select(select(ro.oid), from(ro), ro.cid === coll)
RC.Columns.all, val persCond = Select(select(rp.pid), from(rp), rp.cid === coll)
RC.table, val valueFilter = query.map(s => rc.value.like(s"%${s.toLowerCase}%"))
and( val kindFilter = kind.map(k => rc.kind === k)
Seq(
or(RC.Columns.orgId.isIn(orgCond), RC.Columns.personId.isIn(persCond))
) ++ queryCond ++ kindCond
)
) ++ orderBy(RC.Columns.value.f)
q.query[RContact].stream Select(
select(rc.all),
from(rc),
(rc.orgId.in(orgCond) || rc.personId.in(persCond)) &&? valueFilter &&? kindFilter
).orderBy(rc.value).run.query[RContact].stream
} }
} }

View File

@ -88,13 +88,17 @@ object QItem {
def findItem(id: Ident): ConnectionIO[Option[ItemData]] = { def findItem(id: Ident): ConnectionIO[Option[ItemData]] = {
val equip = REquipment.as("e") val equip = REquipment.as("e")
val IC = RItem.Columns.all.map(_.prefix("i")) val org = ROrganization.as("o")
val OC = ROrganization.Columns.all.map(_.prefix("o")) val pers0 = RPerson.as("p0")
val P0C = RPerson.Columns.all.map(_.prefix("p0")) val pers1 = RPerson.as("p1")
val P1C = RPerson.Columns.all.map(_.prefix("p1"))
val EC = equip.all.map(_.oldColumn).map(_.prefix("e")) val IC = RItem.Columns.all.map(_.prefix("i"))
val ICC = List(RItem.Columns.id, RItem.Columns.name).map(_.prefix("ref")) val OC = org.all.map(_.column)
val FC = List(RFolder.Columns.id, RFolder.Columns.name).map(_.prefix("f")) val P0C = pers0.all.map(_.column)
val P1C = pers1.all.map(_.column)
val EC = equip.all.map(_.oldColumn).map(_.prefix("e"))
val ICC = List(RItem.Columns.id, RItem.Columns.name).map(_.prefix("ref"))
val FC = List(RFolder.Columns.id, RFolder.Columns.name).map(_.prefix("f"))
val cq = val cq =
selectSimple( selectSimple(
@ -102,15 +106,21 @@ object QItem {
RItem.table ++ fr"i", RItem.table ++ fr"i",
Fragment.empty Fragment.empty
) ++ ) ++
fr"LEFT JOIN" ++ ROrganization.table ++ fr"o ON" ++ RItem.Columns.corrOrg fr"LEFT JOIN" ++ Fragment.const(
org.tableName
) ++ fr"o ON" ++ RItem.Columns.corrOrg
.prefix("i") .prefix("i")
.is(ROrganization.Columns.oid.prefix("o")) ++ .is(org.oid.column) ++
fr"LEFT JOIN" ++ RPerson.table ++ fr"p0 ON" ++ RItem.Columns.corrPerson fr"LEFT JOIN" ++ Fragment.const(
pers0.tableName
) ++ fr"p0 ON" ++ RItem.Columns.corrPerson
.prefix("i") .prefix("i")
.is(RPerson.Columns.pid.prefix("p0")) ++ .is(pers0.pid.column) ++
fr"LEFT JOIN" ++ RPerson.table ++ fr"p1 ON" ++ RItem.Columns.concPerson fr"LEFT JOIN" ++ Fragment.const(
pers1.tableName
) ++ fr"p1 ON" ++ RItem.Columns.concPerson
.prefix("i") .prefix("i")
.is(RPerson.Columns.pid.prefix("p1")) ++ .is(pers1.pid.column) ++
fr"LEFT JOIN" ++ Fragment.const( fr"LEFT JOIN" ++ Fragment.const(
equip.tableName equip.tableName
) ++ fr"e ON" ++ RItem.Columns.concEquipment ) ++ fr"e ON" ++ RItem.Columns.concEquipment
@ -308,15 +318,15 @@ object QItem {
moreCols: Seq[Fragment], moreCols: Seq[Fragment],
ctes: (String, Fragment)* ctes: (String, Fragment)*
): Fragment = { ): Fragment = {
val equip = REquipment.as("e1") val equip = REquipment.as("e1")
val org = ROrganization.as("o0")
val pers0 = RPerson.as("p0")
val pers1 = RPerson.as("p1")
val IC = RItem.Columns val IC = RItem.Columns
val AC = RAttachment.Columns val AC = RAttachment.Columns
val PC = RPerson.Columns
val OC = ROrganization.Columns
val FC = RFolder.Columns val FC = RFolder.Columns
val itemCols = IC.all val itemCols = IC.all
val personCols = List(PC.pid, PC.name)
val orgCols = List(OC.oid, OC.name)
val equipCols = List(equip.eid.oldColumn, equip.name.oldColumn) val equipCols = List(equip.eid.oldColumn, equip.name.oldColumn)
val folderCols = List(FC.id, FC.name) val folderCols = List(FC.id, FC.name)
val cvItem = RCustomFieldValue.Columns.itemId.prefix("cv") val cvItem = RCustomFieldValue.Columns.itemId.prefix("cv")
@ -332,12 +342,12 @@ object QItem {
IC.incoming.prefix("i").f, IC.incoming.prefix("i").f,
IC.created.prefix("i").f, IC.created.prefix("i").f,
fr"COALESCE(a.num, 0)", fr"COALESCE(a.num, 0)",
OC.oid.prefix("o0").f, org.oid.column.f,
OC.name.prefix("o0").f, org.name.column.f,
PC.pid.prefix("p0").f, pers0.pid.column.f,
PC.name.prefix("p0").f, pers0.name.column.f,
PC.pid.prefix("p1").f, pers1.pid.column.f,
PC.name.prefix("p1").f, pers1.name.column.f,
equip.eid.oldColumn.prefix("e1").f, equip.eid.oldColumn.prefix("e1").f,
equip.name.oldColumn.prefix("e1").f, equip.name.oldColumn.prefix("e1").f,
FC.id.prefix("f1").f, FC.id.prefix("f1").f,
@ -356,9 +366,17 @@ object QItem {
val withItem = selectSimple(itemCols, RItem.table, IC.cid.is(q.account.collective)) val withItem = selectSimple(itemCols, RItem.table, IC.cid.is(q.account.collective))
val withPerson = val withPerson =
selectSimple(personCols, RPerson.table, PC.cid.is(q.account.collective)) selectSimple(
List(RPerson.T.pid.column, RPerson.T.name.column),
Fragment.const(RPerson.T.tableName),
RPerson.T.cid.column.is(q.account.collective)
)
val withOrgs = val withOrgs =
selectSimple(orgCols, ROrganization.table, OC.cid.is(q.account.collective)) selectSimple(
List(ROrganization.T.oid.column, ROrganization.T.name.column),
Fragment.const(ROrganization.T.tableName),
ROrganization.T.cid.column.is(q.account.collective)
)
val withEquips = val withEquips =
selectSimple( selectSimple(
equipCols, equipCols,
@ -386,9 +404,9 @@ object QItem {
) ++ ) ++
selectKW ++ finalCols ++ fr" FROM items i" ++ selectKW ++ finalCols ++ fr" FROM items i" ++
fr"LEFT JOIN attachs a ON" ++ IC.id.prefix("i").is(AC.itemId.prefix("a")) ++ fr"LEFT JOIN attachs a ON" ++ IC.id.prefix("i").is(AC.itemId.prefix("a")) ++
fr"LEFT JOIN persons p0 ON" ++ IC.corrPerson.prefix("i").is(PC.pid.prefix("p0")) ++ fr"LEFT JOIN persons p0 ON" ++ IC.corrPerson.prefix("i").is(pers0.pid.column) ++
fr"LEFT JOIN orgs o0 ON" ++ IC.corrOrg.prefix("i").is(OC.oid.prefix("o0")) ++ fr"LEFT JOIN orgs o0 ON" ++ IC.corrOrg.prefix("i").is(org.oid.column) ++
fr"LEFT JOIN persons p1 ON" ++ IC.concPerson.prefix("i").is(PC.pid.prefix("p1")) ++ fr"LEFT JOIN persons p1 ON" ++ IC.concPerson.prefix("i").is(pers1.pid.column) ++
fr"LEFT JOIN equips e1 ON" ++ IC.concEquipment fr"LEFT JOIN equips e1 ON" ++ IC.concEquipment
.prefix("i") .prefix("i")
.is(equip.eid.oldColumn.prefix("e1")) ++ .is(equip.eid.oldColumn.prefix("e1")) ++
@ -404,9 +422,10 @@ object QItem {
batch: Batch batch: Batch
): Stream[ConnectionIO, ListItem] = { ): Stream[ConnectionIO, ListItem] = {
val equip = REquipment.as("e1") val equip = REquipment.as("e1")
val org = ROrganization.as("o0")
val pers0 = RPerson.as("p0")
val pers1 = RPerson.as("p1")
val IC = RItem.Columns val IC = RItem.Columns
val PC = RPerson.Columns
val OC = ROrganization.Columns
// inclusive tags are AND-ed // inclusive tags are AND-ed
val tagSelectsIncl = q.tagsInclude val tagSelectsIncl = q.tagsInclude
@ -436,18 +455,18 @@ object QItem {
allNames allNames
.map(n => .map(n =>
or( or(
OC.name.prefix("o0").lowerLike(n), org.name.column.lowerLike(n),
PC.name.prefix("p0").lowerLike(n), pers0.name.column.lowerLike(n),
PC.name.prefix("p1").lowerLike(n), pers1.name.column.lowerLike(n),
equip.name.oldColumn.prefix("e1").lowerLike(n), equip.name.oldColumn.prefix("e1").lowerLike(n),
IC.name.prefix("i").lowerLike(n), IC.name.prefix("i").lowerLike(n),
IC.notes.prefix("i").lowerLike(n) IC.notes.prefix("i").lowerLike(n)
) )
) )
.getOrElse(Fragment.empty), .getOrElse(Fragment.empty),
RPerson.Columns.pid.prefix("p0").isOrDiscard(q.corrPerson), pers0.pid.column.isOrDiscard(q.corrPerson),
ROrganization.Columns.oid.prefix("o0").isOrDiscard(q.corrOrg), org.oid.column.isOrDiscard(q.corrOrg),
RPerson.Columns.pid.prefix("p1").isOrDiscard(q.concPerson), pers1.pid.column.isOrDiscard(q.concPerson),
equip.eid.oldColumn.prefix("e1").isOrDiscard(q.concEquip), equip.eid.oldColumn.prefix("e1").isOrDiscard(q.concEquip),
RFolder.Columns.id.prefix("f1").isOrDiscard(q.folder), RFolder.Columns.id.prefix("f1").isOrDiscard(q.folder),
if (q.tagsInclude.isEmpty && q.tagCategoryIncl.isEmpty) Fragment.empty if (q.tagsInclude.isEmpty && q.tagCategoryIncl.isEmpty) Fragment.empty

View File

@ -4,10 +4,8 @@ import cats.implicits._
import fs2._ import fs2._
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 docspell.store.records.ROrganization.{Columns => OC}
import docspell.store.records.RPerson.{Columns => PC}
import docspell.store.records._ import docspell.store.records._
import docspell.store.{AddResult, Store} import docspell.store.{AddResult, Store}
@ -19,29 +17,22 @@ object QOrganization {
def findOrgAndContact( def findOrgAndContact(
coll: Ident, coll: Ident,
query: Option[String], query: Option[String],
order: OC.type => Column order: ROrganization.Table => Column[_]
): Stream[ConnectionIO, (ROrganization, Vector[RContact])] = { ): Stream[ConnectionIO, (ROrganization, Vector[RContact])] = {
val oColl = ROrganization.Columns.cid.prefix("o") val org = ROrganization.as("o")
val oName = ROrganization.Columns.name.prefix("o") val c = RContact.as("c")
val oNotes = ROrganization.Columns.notes.prefix("o")
val oId = ROrganization.Columns.oid.prefix("o")
val cOrg = RContact.Columns.orgId.prefix("c")
val cVal = RContact.Columns.value.prefix("c")
val cols = ROrganization.Columns.all.map(_.prefix("o")) ++ RContact.Columns.all val valFilter = query.map { q =>
.map(_.prefix("c")) val v = s"%$q%"
val from = ROrganization.table ++ fr"o LEFT JOIN" ++ c.value.like(v) || org.name.like(v) || org.notes.like(v)
RContact.table ++ fr"c ON" ++ cOrg.is(oId) }
val sql = Select(
select(org.all, c.all),
from(org).leftJoin(c, c.orgId === org.oid),
org.cid === coll &&? valFilter
).orderBy(order(org))
val q = Seq(oColl.is(coll)) ++ (query match { sql.run
case Some(str) =>
val v = s"%$str%"
Seq(or(cVal.lowerLike(v), oName.lowerLike(v), oNotes.lowerLike(v)))
case None =>
Seq.empty
})
(selectSimple(cols, from, and(q)) ++ orderBy(order(OC).prefix("o").f))
.query[(ROrganization, Option[RContact])] .query[(ROrganization, Option[RContact])]
.stream .stream
.groupAdjacentBy(_._1) .groupAdjacentBy(_._1)
@ -55,18 +46,16 @@ object QOrganization {
coll: Ident, coll: Ident,
orgId: Ident orgId: Ident
): ConnectionIO[Option[(ROrganization, Vector[RContact])]] = { ): ConnectionIO[Option[(ROrganization, Vector[RContact])]] = {
val oColl = ROrganization.Columns.cid.prefix("o") val org = ROrganization.as("o")
val oId = ROrganization.Columns.oid.prefix("o") val c = RContact.as("c")
val cOrg = RContact.Columns.orgId.prefix("c")
val cols = ROrganization.Columns.all.map(_.prefix("o")) ++ RContact.Columns.all val sql = run(
.map(_.prefix("c")) select(org.all, c.all),
val from = ROrganization.table ++ fr"o LEFT JOIN" ++ from(org).leftJoin(c, c.orgId === org.oid),
RContact.table ++ fr"c ON" ++ cOrg.is(oId) org.cid === coll && org.oid === orgId
)
val q = and(oColl.is(coll), oId.is(orgId)) sql
selectSimple(cols, from, q)
.query[(ROrganization, Option[RContact])] .query[(ROrganization, Option[RContact])]
.stream .stream
.groupAdjacentBy(_._1) .groupAdjacentBy(_._1)
@ -81,33 +70,23 @@ object QOrganization {
def findPersonAndContact( def findPersonAndContact(
coll: Ident, coll: Ident,
query: Option[String], query: Option[String],
order: PC.type => Column order: RPerson.Table => Column[_]
): Stream[ConnectionIO, (RPerson, Option[ROrganization], Vector[RContact])] = { ): Stream[ConnectionIO, (RPerson, Option[ROrganization], Vector[RContact])] = {
val pColl = PC.cid.prefix("p") val pers = RPerson.as("p")
val pName = RPerson.Columns.name.prefix("p") val org = ROrganization.as("o")
val pNotes = RPerson.Columns.notes.prefix("p") val c = RContact.as("c")
val pId = RPerson.Columns.pid.prefix("p") val valFilter = query
val cPers = RContact.Columns.personId.prefix("c") .map(s => s"%$s%")
val cVal = RContact.Columns.value.prefix("c") .map(v => c.value.like(v) || pers.name.like(v) || pers.notes.like(v))
val oId = ROrganization.Columns.oid.prefix("o") val sql = Select(
val pOid = RPerson.Columns.oid.prefix("p") select(pers.all, org.all, c.all),
from(pers)
.leftJoin(org, org.oid === pers.oid)
.leftJoin(c, c.personId === pers.pid),
pers.cid === coll &&? valFilter
).orderBy(order(pers))
val cols = RPerson.Columns.all.map(_.prefix("p")) ++ sql.run
ROrganization.Columns.all.map(_.prefix("o")) ++
RContact.Columns.all.map(_.prefix("c"))
val from = RPerson.table ++ fr"p LEFT JOIN" ++
ROrganization.table ++ fr"o ON" ++ pOid.is(oId) ++ fr"LEFT JOIN" ++
RContact.table ++ fr"c ON" ++ cPers.is(pId)
val q = Seq(pColl.is(coll)) ++ (query match {
case Some(str) =>
val v = s"%${str.toLowerCase}%"
Seq(or(cVal.lowerLike(v), pName.lowerLike(v), pNotes.lowerLike(v)))
case None =>
Seq.empty
})
(selectSimple(cols, from, and(q)) ++ orderBy(order(PC).prefix("p").f))
.query[(RPerson, Option[ROrganization], Option[RContact])] .query[(RPerson, Option[ROrganization], Option[RContact])]
.stream .stream
.groupAdjacentBy(_._1) .groupAdjacentBy(_._1)
@ -122,22 +101,19 @@ object QOrganization {
coll: Ident, coll: Ident,
persId: Ident persId: Ident
): ConnectionIO[Option[(RPerson, Option[ROrganization], Vector[RContact])]] = { ): ConnectionIO[Option[(RPerson, Option[ROrganization], Vector[RContact])]] = {
val pColl = PC.cid.prefix("p") val pers = RPerson.as("p")
val pId = RPerson.Columns.pid.prefix("p") val org = ROrganization.as("o")
val cPers = RContact.Columns.personId.prefix("c") val c = RContact.as("c")
val oId = ROrganization.Columns.oid.prefix("o") val sql =
val pOid = RPerson.Columns.oid.prefix("p") run(
select(pers.all, org.all, c.all),
from(pers)
.leftJoin(org, pers.oid === org.oid)
.leftJoin(c, c.personId === pers.pid),
pers.cid === coll && pers.pid === persId
)
val cols = RPerson.Columns.all.map(_.prefix("p")) ++ sql
ROrganization.Columns.all.map(_.prefix("o")) ++
RContact.Columns.all.map(_.prefix("c"))
val from = RPerson.table ++ fr"p LEFT JOIN" ++
ROrganization.table ++ fr"o ON" ++ pOid.is(oId) ++ fr"LEFT JOIN" ++
RContact.table ++ fr"c ON" ++ cPers.is(pId)
val q = and(pColl.is(coll), pId.is(persId))
selectSimple(cols, from, q)
.query[(RPerson, Option[ROrganization], Option[RContact])] .query[(RPerson, Option[ROrganization], Option[RContact])]
.stream .stream
.groupAdjacentBy(_._1) .groupAdjacentBy(_._1)
@ -156,23 +132,15 @@ object QOrganization {
ck: Option[ContactKind], ck: Option[ContactKind],
concerning: Option[Boolean] concerning: Option[Boolean]
): Stream[ConnectionIO, RPerson] = { ): Stream[ConnectionIO, RPerson] = {
val pColl = PC.cid.prefix("p") val p = RPerson.as("p")
val pConc = PC.concerning.prefix("p") val c = RContact.as("c")
val pId = PC.pid.prefix("p") runDistinct(
val cPers = RContact.Columns.personId.prefix("c") select(p.all),
val cVal = RContact.Columns.value.prefix("c") from(p).innerJoin(c, c.personId === p.pid),
val cKind = RContact.Columns.kind.prefix("c") c.value.like(s"%${value.toLowerCase}%") && p.cid === coll &&?
concerning.map(c => p.concerning === c) &&?
val from = RPerson.table ++ fr"p INNER JOIN" ++ ck.map(k => c.kind === k)
RContact.table ++ fr"c ON" ++ cPers.is(pId) ).query[RPerson].stream
val q = Seq(
cVal.lowerLike(s"%${value.toLowerCase}%"),
pColl.is(coll)
) ++ concerning.map(pConc.is(_)).toSeq ++ ck.map(cKind.is(_)).toSeq
selectDistinct(PC.all.map(_.prefix("p")), from, and(q))
.query[RPerson]
.stream
} }
def addOrg[F[_]]( def addOrg[F[_]](

View File

@ -1,8 +1,8 @@
package docspell.store.records package docspell.store.records
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._
@ -18,64 +18,62 @@ case class RContact(
object RContact { object RContact {
val table = fr"contact" final case class Table(alias: Option[String]) extends TableDef {
val tableName = "contact"
object Columns { val contactId = Column[Ident]("contactid", this)
val contactId = Column("contactid") val value = Column[String]("value", this)
val value = Column("value") val kind = Column[ContactKind]("kind", this)
val kind = Column("kind") val personId = Column[Ident]("pid", this)
val personId = Column("pid") val orgId = Column[Ident]("oid", this)
val orgId = Column("oid") val created = Column[Timestamp]("created", this)
val created = Column("created")
val all = List(contactId, value, kind, personId, orgId, created) val all = List(contactId, value, kind, personId, orgId, created)
} }
import Columns._ private val T = Table(None)
def as(alias: String): Table =
Table(Some(alias))
def insert(v: RContact): ConnectionIO[Int] = { def insert(v: RContact): ConnectionIO[Int] =
val sql = insertRow( DML.insert(
table, T,
all, T.all,
fr"${v.contactId},${v.value},${v.kind},${v.personId},${v.orgId},${v.created}" fr"${v.contactId},${v.value},${v.kind},${v.personId},${v.orgId},${v.created}"
) )
sql.update.run
}
def update(v: RContact): ConnectionIO[Int] = { def update(v: RContact): ConnectionIO[Int] =
val sql = updateRow( DML.update(
table, T,
contactId.is(v.contactId), T.contactId === v.contactId,
commas( DML.set(
value.setTo(v.value), T.value.setTo(v.value),
kind.setTo(v.kind), T.kind.setTo(v.kind),
personId.setTo(v.personId), T.personId.setTo(v.personId),
orgId.setTo(v.orgId) T.orgId.setTo(v.orgId)
) )
) )
sql.update.run
}
def delete(v: RContact): ConnectionIO[Int] = def delete(v: RContact): ConnectionIO[Int] =
deleteFrom(table, contactId.is(v.contactId)).update.run DML.delete(T, T.contactId === v.contactId)
def deleteOrg(oid: Ident): ConnectionIO[Int] = def deleteOrg(oid: Ident): ConnectionIO[Int] =
deleteFrom(table, orgId.is(oid)).update.run DML.delete(T, T.orgId === oid)
def deletePerson(pid: Ident): ConnectionIO[Int] = def deletePerson(pid: Ident): ConnectionIO[Int] =
deleteFrom(table, personId.is(pid)).update.run DML.delete(T, T.personId === pid)
def findById(id: Ident): ConnectionIO[Option[RContact]] = { def findById(id: Ident): ConnectionIO[Option[RContact]] = {
val sql = selectSimple(all, table, contactId.is(id)) val sql = run(select(T.all), from(T), T.contactId === id)
sql.query[RContact].option sql.query[RContact].option
} }
def findAllPerson(pid: Ident): ConnectionIO[Vector[RContact]] = { def findAllPerson(pid: Ident): ConnectionIO[Vector[RContact]] = {
val sql = selectSimple(all, table, personId.is(pid)) val sql = run(select(T.all), from(T), T.personId === pid)
sql.query[RContact].to[Vector] sql.query[RContact].to[Vector]
} }
def findAllOrg(oid: Ident): ConnectionIO[Vector[RContact]] = { def findAllOrg(oid: Ident): ConnectionIO[Vector[RContact]] = {
val sql = selectSimple(all, table, orgId.is(oid)) val sql = run(select(T.all), from(T), T.orgId === oid)
sql.query[RContact].to[Vector] sql.query[RContact].to[Vector]
} }
} }

View File

@ -27,6 +27,7 @@ object REquipment {
val all = List(eid, cid, name, created, updated) val all = List(eid, cid, name, created, updated)
} }
val T = Table(None)
def as(alias: String): Table = def as(alias: String): Table =
Table(Some(alias)) Table(Some(alias))

View File

@ -4,8 +4,8 @@ import cats.Eq
import fs2.Stream import fs2.Stream
import docspell.common.{IdRef, _} import docspell.common.{IdRef, _}
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._
@ -27,73 +27,73 @@ object ROrganization {
implicit val orgEq: Eq[ROrganization] = implicit val orgEq: Eq[ROrganization] =
Eq.by[ROrganization, Ident](_.oid) Eq.by[ROrganization, Ident](_.oid)
val table = fr"organization" final case class Table(alias: Option[String]) extends TableDef {
val tableName = "organization"
object Columns { val oid = Column[Ident]("oid", this)
val oid = Column("oid") val cid = Column[Ident]("cid", this)
val cid = Column("cid") val name = Column[String]("name", this)
val name = Column("name") val street = Column[String]("street", this)
val street = Column("street") val zip = Column[String]("zip", this)
val zip = Column("zip") val city = Column[String]("city", this)
val city = Column("city") val country = Column[String]("country", this)
val country = Column("country") val notes = Column[String]("notes", this)
val notes = Column("notes") val created = Column[Timestamp]("created", this)
val created = Column("created") val updated = Column[Timestamp]("updated", this)
val updated = Column("updated")
val all = List(oid, cid, name, street, zip, city, country, notes, created, updated) val all = List(oid, cid, name, street, zip, city, country, notes, created, updated)
} }
import Columns._ val T = Table(None)
def as(alias: String): Table =
Table(Some(alias))
def insert(v: ROrganization): ConnectionIO[Int] = { def insert(v: ROrganization): ConnectionIO[Int] =
val sql = insertRow( DML.insert(
table, T,
all, T.all,
fr"${v.oid},${v.cid},${v.name},${v.street},${v.zip},${v.city},${v.country},${v.notes},${v.created},${v.updated}" fr"${v.oid},${v.cid},${v.name},${v.street},${v.zip},${v.city},${v.country},${v.notes},${v.created},${v.updated}"
) )
sql.update.run
}
def update(v: ROrganization): ConnectionIO[Int] = { def update(v: ROrganization): ConnectionIO[Int] = {
def sql(now: Timestamp) = def sql(now: Timestamp) =
updateRow( DML.update(
table, T,
and(oid.is(v.oid), cid.is(v.cid)), T.oid === v.oid && T.cid === v.cid,
commas( DML.set(
cid.setTo(v.cid), T.cid.setTo(v.cid),
name.setTo(v.name), T.name.setTo(v.name),
street.setTo(v.street), T.street.setTo(v.street),
zip.setTo(v.zip), T.zip.setTo(v.zip),
city.setTo(v.city), T.city.setTo(v.city),
country.setTo(v.country), T.country.setTo(v.country),
notes.setTo(v.notes), T.notes.setTo(v.notes),
updated.setTo(now) T.updated.setTo(now)
) )
) )
for { for {
now <- Timestamp.current[ConnectionIO] now <- Timestamp.current[ConnectionIO]
n <- sql(now).update.run n <- sql(now)
} yield n } yield n
} }
def existsByName(coll: Ident, oname: String): ConnectionIO[Boolean] = def existsByName(coll: Ident, oname: String): ConnectionIO[Boolean] =
selectCount(oid, table, and(cid.is(coll), name.is(oname))) run(select(count(T.oid)), from(T), T.cid === coll && T.name === oname)
.query[Int] .query[Int]
.unique .unique
.map(_ > 0) .map(_ > 0)
def findById(id: Ident): ConnectionIO[Option[ROrganization]] = { def findById(id: Ident): ConnectionIO[Option[ROrganization]] = {
val sql = selectSimple(all, table, cid.is(id)) val sql = run(select(T.all), from(T), T.cid === id)
sql.query[ROrganization].option sql.query[ROrganization].option
} }
def find(coll: Ident, orgName: String): ConnectionIO[Option[ROrganization]] = { def find(coll: Ident, orgName: String): ConnectionIO[Option[ROrganization]] = {
val sql = selectSimple(all, table, and(cid.is(coll), name.is(orgName))) val sql = run(select(T.all), from(T), T.cid === coll && T.name === orgName)
sql.query[ROrganization].option sql.query[ROrganization].option
} }
def findLike(coll: Ident, orgName: String): ConnectionIO[Vector[IdRef]] = def findLike(coll: Ident, orgName: String): ConnectionIO[Vector[IdRef]] =
selectSimple(List(oid, name), table, and(cid.is(coll), name.lowerLike(orgName))) run(select(T.oid, T.name), from(T), T.cid === coll && T.name.like(orgName))
.query[IdRef] .query[IdRef]
.to[Vector] .to[Vector]
@ -102,42 +102,38 @@ object ROrganization {
contactKind: ContactKind, contactKind: ContactKind,
value: String value: String
): ConnectionIO[Vector[IdRef]] = { ): ConnectionIO[Vector[IdRef]] = {
val CC = RContact.Columns val c = RContact.as("c")
val q = fr"SELECT DISTINCT" ++ commas(oid.prefix("o").f, name.prefix("o").f) ++ val o = ROrganization.as("o")
fr"FROM" ++ table ++ fr"o" ++ runDistinct(
fr"INNER JOIN" ++ RContact.table ++ fr"c ON" ++ CC.orgId select(o.oid, o.name),
.prefix("c") from(o).innerJoin(c, c.orgId === o.oid),
.is(oid.prefix("o")) ++ where(
fr"WHERE" ++ and( o.cid === coll,
cid.prefix("o").is(coll), c.kind === contactKind,
CC.kind.prefix("c").is(contactKind), c.value.like(value)
CC.value.prefix("c").lowerLike(value)
) )
).query[IdRef].to[Vector]
q.query[IdRef].to[Vector]
} }
def findAll( def findAll(
coll: Ident, coll: Ident,
order: Columns.type => Column order: Table => Column[_]
): Stream[ConnectionIO, ROrganization] = { ): Stream[ConnectionIO, ROrganization] = {
val sql = selectSimple(all, table, cid.is(coll)) ++ orderBy(order(Columns).f) val sql = Select(select(T.all), from(T), T.cid === coll).orderBy(order(T))
sql.query[ROrganization].stream sql.run.query[ROrganization].stream
} }
def findAllRef( def findAllRef(
coll: Ident, coll: Ident,
nameQ: Option[String], nameQ: Option[String],
order: Columns.type => Column order: Table => Column[_]
): ConnectionIO[Vector[IdRef]] = { ): ConnectionIO[Vector[IdRef]] = {
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 = Select(select(T.oid, T.name), from(T), T.cid === coll &&? nameFilter)
case None => Seq.empty .orderBy(order(T))
}) sql.run.query[IdRef].to[Vector]
val sql = selectSimple(List(oid, name), table, and(q)) ++ orderBy(order(Columns).f)
sql.query[IdRef].to[Vector]
} }
def delete(id: Ident, coll: Ident): ConnectionIO[Int] = def delete(id: Ident, coll: Ident): ConnectionIO[Int] =
deleteFrom(table, and(oid.is(id), cid.is(coll))).update.run DML.delete(T, T.oid === id && T.cid === coll)
} }

View File

@ -6,8 +6,8 @@ import cats.effect._
import fs2.Stream import fs2.Stream
import docspell.common.{IdRef, _} import docspell.common.{IdRef, _}
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._
@ -31,21 +31,21 @@ object RPerson {
implicit val personEq: Eq[RPerson] = implicit val personEq: Eq[RPerson] =
Eq.by(_.pid) Eq.by(_.pid)
val table = fr"person" final case class Table(alias: Option[String]) extends TableDef {
val tableName = "person"
object Columns { val pid = Column[Ident]("pid", this)
val pid = Column("pid") val cid = Column[Ident]("cid", this)
val cid = Column("cid") val name = Column[String]("name", this)
val name = Column("name") val street = Column[String]("street", this)
val street = Column("street") val zip = Column[String]("zip", this)
val zip = Column("zip") val city = Column[String]("city", this)
val city = Column("city") val country = Column[String]("country", this)
val country = Column("country") val notes = Column[String]("notes", this)
val notes = Column("notes") val concerning = Column[Boolean]("concerning", this)
val concerning = Column("concerning") val created = Column[Timestamp]("created", this)
val created = Column("created") val updated = Column[Timestamp]("updated", this)
val updated = Column("updated") val oid = Column[Ident]("oid", this)
val oid = Column("oid")
val all = List( val all = List(
pid, pid,
cid, cid,
@ -62,54 +62,54 @@ object RPerson {
) )
} }
import Columns._ val T = Table(None)
def as(alias: String): Table =
Table(Some(alias))
def insert(v: RPerson): ConnectionIO[Int] = { def insert(v: RPerson): ConnectionIO[Int] =
val sql = insertRow( DML.insert(
table, T,
all, T.all,
fr"${v.pid},${v.cid},${v.name},${v.street},${v.zip},${v.city},${v.country},${v.notes},${v.concerning},${v.created},${v.updated},${v.oid}" fr"${v.pid},${v.cid},${v.name},${v.street},${v.zip},${v.city},${v.country},${v.notes},${v.concerning},${v.created},${v.updated},${v.oid}"
) )
sql.update.run
}
def update(v: RPerson): ConnectionIO[Int] = { def update(v: RPerson): ConnectionIO[Int] = {
def sql(now: Timestamp) = def sql(now: Timestamp) =
updateRow( DML.update(
table, T,
and(pid.is(v.pid), cid.is(v.cid)), T.pid === v.pid && T.cid === v.cid,
commas( DML.set(
cid.setTo(v.cid), T.cid.setTo(v.cid),
name.setTo(v.name), T.name.setTo(v.name),
street.setTo(v.street), T.street.setTo(v.street),
zip.setTo(v.zip), T.zip.setTo(v.zip),
city.setTo(v.city), T.city.setTo(v.city),
country.setTo(v.country), T.country.setTo(v.country),
concerning.setTo(v.concerning), T.concerning.setTo(v.concerning),
notes.setTo(v.notes), T.notes.setTo(v.notes),
oid.setTo(v.oid), T.oid.setTo(v.oid),
updated.setTo(now) T.updated.setTo(now)
) )
) )
for { for {
now <- Timestamp.current[ConnectionIO] now <- Timestamp.current[ConnectionIO]
n <- sql(now).update.run n <- sql(now)
} yield n } yield n
} }
def existsByName(coll: Ident, pname: String): ConnectionIO[Boolean] = def existsByName(coll: Ident, pname: String): ConnectionIO[Boolean] =
selectCount(pid, table, and(cid.is(coll), name.is(pname))) run(select(count(T.pid)), from(T), T.cid === coll && T.name === pname)
.query[Int] .query[Int]
.unique .unique
.map(_ > 0) .map(_ > 0)
def findById(id: Ident): ConnectionIO[Option[RPerson]] = { def findById(id: Ident): ConnectionIO[Option[RPerson]] = {
val sql = selectSimple(all, table, cid.is(id)) val sql = run(select(T.all), from(T), T.cid === id)
sql.query[RPerson].option sql.query[RPerson].option
} }
def find(coll: Ident, personName: String): ConnectionIO[Option[RPerson]] = { def find(coll: Ident, personName: String): ConnectionIO[Option[RPerson]] = {
val sql = selectSimple(all, table, and(cid.is(coll), name.is(personName))) val sql = run(select(T.all), from(T), T.cid === coll && T.name === personName)
sql.query[RPerson].option sql.query[RPerson].option
} }
@ -118,10 +118,10 @@ object RPerson {
personName: String, personName: String,
concerningOnly: Boolean concerningOnly: Boolean
): ConnectionIO[Vector[IdRef]] = ): ConnectionIO[Vector[IdRef]] =
selectSimple( run(
List(pid, name), select(T.pid, T.name),
table, from(T),
and(cid.is(coll), concerning.is(concerningOnly), name.lowerLike(personName)) where(T.cid === coll, T.concerning === concerningOnly, T.name.like(personName))
).query[IdRef].to[Vector] ).query[IdRef].to[Vector]
def findLike( def findLike(
@ -130,53 +130,52 @@ object RPerson {
value: String, value: String,
concerningOnly: Boolean concerningOnly: Boolean
): ConnectionIO[Vector[IdRef]] = { ): ConnectionIO[Vector[IdRef]] = {
val CC = RContact.Columns val p = RPerson.as("p")
val q = fr"SELECT DISTINCT" ++ commas(pid.prefix("p").f, name.prefix("p").f) ++ val c = RContact.as("c")
fr"FROM" ++ table ++ fr"p" ++
fr"INNER JOIN" ++ RContact.table ++ fr"c ON" ++ CC.personId
.prefix("c")
.is(pid.prefix("p")) ++
fr"WHERE" ++ and(
cid.prefix("p").is(coll),
CC.kind.prefix("c").is(contactKind),
concerning.prefix("p").is(concerningOnly),
CC.value.prefix("c").lowerLike(value)
)
q.query[IdRef].to[Vector] runDistinct(
select(p.pid, p.name),
from(p).innerJoin(c, p.pid === c.personId),
where(
p.cid === coll,
c.kind === contactKind,
p.concerning === concerningOnly,
c.value.like(value)
)
).query[IdRef].to[Vector]
} }
def findAll( def findAll(
coll: Ident, coll: Ident,
order: Columns.type => Column order: Table => Column[_]
): Stream[ConnectionIO, RPerson] = { ): Stream[ConnectionIO, RPerson] = {
val sql = selectSimple(all, table, cid.is(coll)) ++ orderBy(order(Columns).f) val sql = Select(select(T.all), from(T), T.cid === coll).orderBy(order(T))
sql.query[RPerson].stream sql.run.query[RPerson].stream
} }
def findAllRef( def findAllRef(
coll: Ident, coll: Ident,
nameQ: Option[String], nameQ: Option[String],
order: Columns.type => Column order: Table => Column[_]
): ConnectionIO[Vector[IdRef]] = { ): ConnectionIO[Vector[IdRef]] = {
val q = Seq(cid.is(coll)) ++ (nameQ match {
case Some(str) => Seq(name.lowerLike(s"%${str.toLowerCase}%")) val nameFilter = nameQ.map(s => T.name.like(s"%${s.toLowerCase}%"))
case None => Seq.empty
}) val sql = Select(select(T.pid, T.name), from(T), T.cid === coll &&? nameFilter)
val sql = selectSimple(List(pid, name), table, and(q)) ++ orderBy(order(Columns).f) .orderBy(order(T))
sql.query[IdRef].to[Vector] sql.run.query[IdRef].to[Vector]
} }
def delete(personId: Ident, coll: Ident): ConnectionIO[Int] = def delete(personId: Ident, coll: Ident): ConnectionIO[Int] =
deleteFrom(table, and(pid.is(personId), cid.is(coll))).update.run DML.delete(T, T.pid === personId && T.cid === coll)
def findOrganization(ids: Set[Ident]): ConnectionIO[Vector[PersonRef]] = { def findOrganization(ids: Set[Ident]): ConnectionIO[Vector[PersonRef]] =
val cols = Seq(pid, name, oid)
NonEmptyList.fromList(ids.toList) match { NonEmptyList.fromList(ids.toList) match {
case Some(nel) => case Some(nel) =>
selectSimple(cols, table, pid.isIn(nel)).query[PersonRef].to[Vector] run(select(T.pid, T.name, T.oid), from(T), T.pid.in(nel))
.query[PersonRef]
.to[Vector]
case None => case None =>
Sync[ConnectionIO].pure(Vector.empty) Sync[ConnectionIO].pure(Vector.empty)
} }
}
} }

View File

@ -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,18 +13,20 @@ import doobie.implicits._
case class RRememberMe(id: Ident, accountId: AccountId, created: Timestamp, uses: Int) {} case class RRememberMe(id: Ident, accountId: AccountId, created: Timestamp, uses: Int) {}
object RRememberMe { object RRememberMe {
final case class Table(alias: Option[String]) extends TableDef {
val tableName = "rememberme"
val table = fr"rememberme" val id = Column[Ident]("id", this)
val cid = Column[Ident]("cid", this)
object Columns { val username = Column[Ident]("login", this)
val id = Column("id") val created = Column[Timestamp]("created", this)
val cid = Column("cid") val uses = Column[Int]("uses", this)
val username = Column("login")
val created = Column("created")
val uses = Column("uses")
val all = List(id, cid, username, created, uses) val all = List(id, cid, username, created, uses)
} }
import Columns._
private val T = Table(None)
def as(alias: String): Table =
Table(Some(alias))
def generate[F[_]: Sync](account: AccountId): F[RRememberMe] = def generate[F[_]: Sync](account: AccountId): F[RRememberMe] =
for { for {
@ -33,29 +35,29 @@ object RRememberMe {
} yield RRememberMe(i, account, c, 0) } yield RRememberMe(i, account, c, 0)
def insert(v: RRememberMe): ConnectionIO[Int] = def insert(v: RRememberMe): ConnectionIO[Int] =
insertRow( DML.insert(
table, T,
all, T.all,
fr"${v.id},${v.accountId.collective},${v.accountId.user},${v.created},${v.uses}" fr"${v.id},${v.accountId.collective},${v.accountId.user},${v.created},${v.uses}"
).update.run )
def insertNew(acc: AccountId): ConnectionIO[RRememberMe] = def insertNew(acc: AccountId): ConnectionIO[RRememberMe] =
generate[ConnectionIO](acc).flatMap(v => insert(v).map(_ => v)) generate[ConnectionIO](acc).flatMap(v => insert(v).map(_ => v))
def findById(rid: Ident): ConnectionIO[Option[RRememberMe]] = def findById(rid: Ident): ConnectionIO[Option[RRememberMe]] =
selectSimple(all, table, id.is(rid)).query[RRememberMe].option run(select(T.all), from(T), T.id === rid).query[RRememberMe].option
def delete(rid: Ident): ConnectionIO[Int] = def delete(rid: Ident): ConnectionIO[Int] =
deleteFrom(table, id.is(rid)).update.run DML.delete(T, T.id === rid)
def incrementUse(rid: Ident): ConnectionIO[Int] = def incrementUse(rid: Ident): ConnectionIO[Int] =
updateRow(table, id.is(rid), uses.increment(1)).update.run DML.update(T, T.id === rid, DML.set(T.uses.increment(1)))
def useRememberMe( def useRememberMe(
rid: Ident, rid: Ident,
minCreated: Timestamp minCreated: Timestamp
): ConnectionIO[Option[RRememberMe]] = { ): ConnectionIO[Option[RRememberMe]] = {
val get = selectSimple(all, table, and(id.is(rid), created.isGt(minCreated))) val get = run(select(T.all), from(T), T.id === rid && T.created > minCreated)
.query[RRememberMe] .query[RRememberMe]
.option .option
for { for {
@ -65,5 +67,5 @@ object RRememberMe {
} }
def deleteOlderThan(ts: Timestamp): ConnectionIO[Int] = def deleteOlderThan(ts: Timestamp): ConnectionIO[Int] =
deleteFrom(table, created.isLt(ts)).update.run DML.delete(T, T.created < ts)
} }