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

View File

@ -23,4 +23,9 @@ object DBFunction {
def as(a: String) =
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 {
def run(projection: Seq[SelectExpr], from: FromExpr): Fragment =
DoobieQuery(Select(projection, from, None))
def run(projection: Seq[SelectExpr], from: FromExpr, where: Condition): Fragment =
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] =
Seq(SelectExpr.SelectFun(dbf))
@ -21,12 +31,21 @@ trait DSL extends DoobieMeta {
def select(seq: Seq[Column[_]], seqs: Seq[Column[_]]*): Seq[SelectExpr] =
(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)
def fromSubSelect(sel: Select): FromExpr.SubSelect =
FromExpr.SubSelect(sel, "x")
def count(c: Column[_]): DBFunction =
DBFunction.Count(c, "cn")
def max(c: Column[_]): DBFunction =
DBFunction.Max(c, "mn")
def and(c: Condition, cs: Condition*): Condition =
c match {
case Condition.And(head, tail) =>

View File

@ -2,9 +2,9 @@ package docspell.store.qb
sealed trait FromExpr {
def innerJoin(other: TableDef, on: Condition): FromExpr
def leftJoin(other: TableDef, on: Condition): FromExpr
// def innerJoin(other: TableDef, on: Condition): FromExpr
//
// def leftJoin(other: TableDef, on: Condition): FromExpr
}
object FromExpr {
@ -23,6 +23,10 @@ object FromExpr {
def leftJoin(other: TableDef, on: Condition): Joined =
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]
}
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) =>
build(from) ++
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 =

View File

@ -13,16 +13,19 @@ object SelectExprBuilder {
column(col)
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)) =>
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 = {
val prefix =
Fragment.const0(col.table.alias.getOrElse(col.table.tableName))
prefix ++ Fragment.const0(".") ++ Fragment.const0(col.name)
val prefix = col.table.alias.getOrElse(col.table.tableName)
if (prefix.isEmpty) columnNoPrefix(col)
else Fragment.const0(prefix) ++ Fragment.const0(".") ++ Fragment.const0(col.name)
}
def columnNoPrefix(col: Column[_]): Fragment =

View File

@ -91,61 +91,28 @@ object QCollective {
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,
query: Option[String],
kind: Option[ContactKind]
): Stream[ConnectionIO, RContact] = {
val RO = ROrganization
val RP = RPerson
val RC = RContact
import docspell.store.qb.DSL._
import docspell.store.qb._
val orgCond = selectSimple(Seq(RO.Columns.oid), RO.table, RO.Columns.cid.is(coll))
val persCond = selectSimple(Seq(RP.Columns.pid), RP.table, RP.Columns.cid.is(coll))
val queryCond = query match {
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 ro = ROrganization.as("o")
val rp = RPerson.as("p")
val rc = RContact.as("c")
val q = selectSimple(
RC.Columns.all,
RC.table,
and(
Seq(
or(RC.Columns.orgId.isIn(orgCond), RC.Columns.personId.isIn(persCond))
) ++ queryCond ++ kindCond
)
) ++ orderBy(RC.Columns.value.f)
val orgCond = Select(select(ro.oid), from(ro), ro.cid === coll)
val persCond = Select(select(rp.pid), from(rp), rp.cid === coll)
val valueFilter = query.map(s => rc.value.like(s"%${s.toLowerCase}%"))
val kindFilter = kind.map(k => rc.kind === k)
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,10 +88,14 @@ object QItem {
def findItem(id: Ident): ConnectionIO[Option[ItemData]] = {
val equip = REquipment.as("e")
val org = ROrganization.as("o")
val pers0 = RPerson.as("p0")
val pers1 = RPerson.as("p1")
val IC = RItem.Columns.all.map(_.prefix("i"))
val OC = ROrganization.Columns.all.map(_.prefix("o"))
val P0C = RPerson.Columns.all.map(_.prefix("p0"))
val P1C = RPerson.Columns.all.map(_.prefix("p1"))
val OC = org.all.map(_.column)
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"))
@ -102,15 +106,21 @@ object QItem {
RItem.table ++ fr"i",
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")
.is(ROrganization.Columns.oid.prefix("o")) ++
fr"LEFT JOIN" ++ RPerson.table ++ fr"p0 ON" ++ RItem.Columns.corrPerson
.is(org.oid.column) ++
fr"LEFT JOIN" ++ Fragment.const(
pers0.tableName
) ++ fr"p0 ON" ++ RItem.Columns.corrPerson
.prefix("i")
.is(RPerson.Columns.pid.prefix("p0")) ++
fr"LEFT JOIN" ++ RPerson.table ++ fr"p1 ON" ++ RItem.Columns.concPerson
.is(pers0.pid.column) ++
fr"LEFT JOIN" ++ Fragment.const(
pers1.tableName
) ++ fr"p1 ON" ++ RItem.Columns.concPerson
.prefix("i")
.is(RPerson.Columns.pid.prefix("p1")) ++
.is(pers1.pid.column) ++
fr"LEFT JOIN" ++ Fragment.const(
equip.tableName
) ++ fr"e ON" ++ RItem.Columns.concEquipment
@ -309,14 +319,14 @@ object QItem {
ctes: (String, Fragment)*
): Fragment = {
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 AC = RAttachment.Columns
val PC = RPerson.Columns
val OC = ROrganization.Columns
val FC = RFolder.Columns
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 folderCols = List(FC.id, FC.name)
val cvItem = RCustomFieldValue.Columns.itemId.prefix("cv")
@ -332,12 +342,12 @@ object QItem {
IC.incoming.prefix("i").f,
IC.created.prefix("i").f,
fr"COALESCE(a.num, 0)",
OC.oid.prefix("o0").f,
OC.name.prefix("o0").f,
PC.pid.prefix("p0").f,
PC.name.prefix("p0").f,
PC.pid.prefix("p1").f,
PC.name.prefix("p1").f,
org.oid.column.f,
org.name.column.f,
pers0.pid.column.f,
pers0.name.column.f,
pers1.pid.column.f,
pers1.name.column.f,
equip.eid.oldColumn.prefix("e1").f,
equip.name.oldColumn.prefix("e1").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 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 =
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 =
selectSimple(
equipCols,
@ -386,9 +404,9 @@ object QItem {
) ++
selectKW ++ finalCols ++ fr" FROM items i" ++
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 orgs o0 ON" ++ IC.corrOrg.prefix("i").is(OC.oid.prefix("o0")) ++
fr"LEFT JOIN persons p1 ON" ++ IC.concPerson.prefix("i").is(PC.pid.prefix("p1")) ++
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(org.oid.column) ++
fr"LEFT JOIN persons p1 ON" ++ IC.concPerson.prefix("i").is(pers1.pid.column) ++
fr"LEFT JOIN equips e1 ON" ++ IC.concEquipment
.prefix("i")
.is(equip.eid.oldColumn.prefix("e1")) ++
@ -404,9 +422,10 @@ object QItem {
batch: Batch
): Stream[ConnectionIO, ListItem] = {
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 PC = RPerson.Columns
val OC = ROrganization.Columns
// inclusive tags are AND-ed
val tagSelectsIncl = q.tagsInclude
@ -436,18 +455,18 @@ object QItem {
allNames
.map(n =>
or(
OC.name.prefix("o0").lowerLike(n),
PC.name.prefix("p0").lowerLike(n),
PC.name.prefix("p1").lowerLike(n),
org.name.column.lowerLike(n),
pers0.name.column.lowerLike(n),
pers1.name.column.lowerLike(n),
equip.name.oldColumn.prefix("e1").lowerLike(n),
IC.name.prefix("i").lowerLike(n),
IC.notes.prefix("i").lowerLike(n)
)
)
.getOrElse(Fragment.empty),
RPerson.Columns.pid.prefix("p0").isOrDiscard(q.corrPerson),
ROrganization.Columns.oid.prefix("o0").isOrDiscard(q.corrOrg),
RPerson.Columns.pid.prefix("p1").isOrDiscard(q.concPerson),
pers0.pid.column.isOrDiscard(q.corrPerson),
org.oid.column.isOrDiscard(q.corrOrg),
pers1.pid.column.isOrDiscard(q.concPerson),
equip.eid.oldColumn.prefix("e1").isOrDiscard(q.concEquip),
RFolder.Columns.id.prefix("f1").isOrDiscard(q.folder),
if (q.tagsInclude.isEmpty && q.tagCategoryIncl.isEmpty) Fragment.empty

View File

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

View File

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

View File

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

View File

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

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