Convert folder

This commit is contained in:
Eike Kettner 2020-12-13 00:26:58 +01:00
parent 87eb8c7f55
commit d6f28d3eca
21 changed files with 295 additions and 210 deletions

View File

@ -1,5 +1,14 @@
package docspell.store.qb
case class Column[A](name: String, table: TableDef)
case class Column[A](name: String, table: TableDef) {
def inTable(t: TableDef): Column[A] =
copy(table = t)
def s: SelectExpr =
SelectExpr.SelectColumn(this, None)
def as(alias: String): SelectExpr =
SelectExpr.SelectColumn(this, Some(alias))
}
object Column {}

View File

@ -4,7 +4,13 @@ import cats.data.NonEmptyList
import doobie._
sealed trait Condition {}
sealed trait Condition {
def s: SelectExpr.SelectCondition =
SelectExpr.SelectCondition(this, None)
def as(alias: String): SelectExpr.SelectCondition =
SelectExpr.SelectCondition(this, Some(alias))
}
object Condition {

View File

@ -0,0 +1,9 @@
package docspell.store.qb
case class CteBind(name: TableDef, select: Select) {}
object CteBind {
def apply(t: (TableDef, Select)): CteBind =
CteBind(t._1, t._2)
}

View File

@ -21,7 +21,12 @@ object DBFunction {
case class Power(expr: SelectExpr, base: Int) extends DBFunction
case class Plus(expr: SelectExpr, exprs: Vector[SelectExpr]) extends DBFunction
case class Calc(op: Operator, left: SelectExpr, right: SelectExpr) extends DBFunction
case class Mult(expr: SelectExpr, exprs: Vector[SelectExpr]) extends DBFunction
sealed trait Operator
object Operator {
case object Plus extends Operator
case object Minus extends Operator
case object Mult extends Operator
}
}

View File

@ -48,7 +48,7 @@ object DML {
cond: Option[Condition],
setter: Seq[Setter[_]]
): Fragment = {
val condFrag = cond.map(DoobieQuery.cond).getOrElse(Fragment.empty)
val condFrag = cond.map(SelectBuilder.cond).getOrElse(Fragment.empty)
fr"UPDATE" ++ FromExprBuilder.buildTable(table) ++ fr"SET" ++
setter
.map(s => buildSetter(s))

View File

@ -3,30 +3,39 @@ package docspell.store.qb
import cats.data.NonEmptyList
import docspell.store.impl.DoobieMeta
import docspell.store.qb.impl.DoobieQuery
import docspell.store.qb.impl.SelectBuilder
import doobie.{Fragment, Put}
trait DSL extends DoobieMeta {
def run(projection: Seq[SelectExpr], from: FromExpr): Fragment =
DoobieQuery(Select(projection, from, None))
SelectBuilder(Select(projection, from))
def run(projection: Seq[SelectExpr], from: FromExpr, where: Condition): Fragment =
DoobieQuery(Select(projection, from, where))
SelectBuilder(Select(projection, from, where))
def runDistinct(
projection: Seq[SelectExpr],
from: FromExpr,
where: Condition
): Fragment =
DoobieQuery.distinct(Select(projection, from, where))
SelectBuilder(Select(projection, from, where).distinct)
def withCte(cte: (TableDef, Select), more: (TableDef, Select)*): DSL.WithCteDsl =
DSL.WithCteDsl(CteBind(cte), more.map(CteBind.apply).toVector)
def select(cond: Condition): Seq[SelectExpr] =
Seq(SelectExpr.SelectCondition(cond, None))
def select(dbf: DBFunction): Seq[SelectExpr] =
Seq(SelectExpr.SelectFun(dbf, None))
def select(e: SelectExpr, es: SelectExpr*): Seq[SelectExpr] =
es.prepended(e)
def select(c: Column[_], cs: Column[_]*): Seq[SelectExpr] =
select(c :: cs.toList)
cs.prepended(c).map(col => SelectExpr.SelectColumn(col, None))
def select(seq: Seq[Column[_]], seqs: Seq[Column[_]]*): Seq[SelectExpr] =
(seq ++ seqs.flatten).map(c => SelectExpr.SelectColumn(c, None))
@ -43,6 +52,9 @@ trait DSL extends DoobieMeta {
def count(c: Column[_]): DBFunction =
DBFunction.Count(c)
def countAll: DBFunction =
DBFunction.CountAll
def max(c: Column[_]): DBFunction =
DBFunction.Max(c)
@ -58,11 +70,11 @@ trait DSL extends DoobieMeta {
def lit[A](value: A)(implicit P: Put[A]): SelectExpr.SelectLit[A] =
SelectExpr.SelectLit(value, None)
def plus(expr: SelectExpr, more: SelectExpr*): DBFunction =
DBFunction.Plus(expr, more.toVector)
def plus(left: SelectExpr, right: SelectExpr): DBFunction =
DBFunction.Calc(DBFunction.Operator.Plus, left, right)
def mult(expr: SelectExpr, more: SelectExpr*): DBFunction =
DBFunction.Mult(expr, more.toVector)
def mult(left: SelectExpr, right: SelectExpr): DBFunction =
DBFunction.Calc(DBFunction.Operator.Mult, left, right)
def and(c: Condition, cs: Condition*): Condition =
c match {
@ -205,8 +217,22 @@ trait DSL extends DoobieMeta {
def <>[A](value: A)(implicit P: Put[A]): Condition =
Condition.CompareFVal(dbf, Operator.Neq, value)
def -[A](value: A)(implicit P: Put[A]): DBFunction =
DBFunction.Calc(
DBFunction.Operator.Minus,
SelectExpr.SelectFun(dbf, None),
SelectExpr.SelectLit(value, None)
)
}
}
object DSL extends DSL {
final case class WithCteDsl(cte: CteBind, ctes: Vector[CteBind]) {
def select(s: Select): Select.WithCte =
Select.WithCte(cte, ctes, s)
}
}
object DSL extends DSL

View File

@ -1,15 +1,15 @@
package docspell.store.qb
import docspell.store.qb.impl.DoobieQuery
import docspell.store.qb.impl.SelectBuilder
import doobie._
sealed trait Select {
def distinct: Fragment =
DoobieQuery.distinct(this)
def run: Fragment =
DoobieQuery(this)
SelectBuilder(this)
def as(alias: String): SelectExpr.SelectQuery =
SelectExpr.SelectQuery(this, Some(alias))
def orderBy(ob: OrderBy, obs: OrderBy*): Select.Ordered =
Select.Ordered(this, ob, obs.toVector)
@ -20,27 +20,29 @@ sealed trait Select {
def limit(n: Int): Select =
this match {
case Select.Limit(q, _) => Select.Limit(q, n)
case _ =>
Select.Limit(this, n)
case _ => Select.Limit(this, n)
}
}
object Select {
def apply(projection: Seq[SelectExpr], from: FromExpr) =
SimpleSelect(false, projection, from, None, None)
def apply(
projection: Seq[SelectExpr],
from: FromExpr,
where: Condition
) = SimpleSelect(projection, from, Some(where), None)
) = SimpleSelect(false, projection, from, Some(where), None)
def apply(
projection: Seq[SelectExpr],
from: FromExpr,
where: Option[Condition] = None,
groupBy: Option[GroupBy] = None
) = SimpleSelect(projection, from, where, groupBy)
where: Condition,
groupBy: GroupBy
) = SimpleSelect(false, projection, from, Some(where), Some(groupBy))
case class SimpleSelect(
distinctFlag: Boolean,
projection: Seq[SelectExpr],
from: FromExpr,
where: Option[Condition],
@ -48,6 +50,9 @@ object Select {
) extends Select {
def group(gb: GroupBy): SimpleSelect =
copy(groupBy = Some(gb))
def distinct: SimpleSelect =
copy(distinctFlag = true)
}
case class Union(q: Select, qs: Vector[Select]) extends Select
@ -58,4 +63,6 @@ object Select {
extends Select
case class Limit(q: Select, limit: Int) extends Select
case class WithCte(cte: CteBind, ctes: Vector[CteBind], query: Select) extends Select
}

View File

@ -2,7 +2,7 @@ package docspell.store.qb
import doobie.Put
sealed trait SelectExpr { self =>
sealed trait SelectExpr {
def as(alias: String): SelectExpr
}
@ -24,4 +24,14 @@ object SelectExpr {
copy(alias = Some(a))
}
case class SelectQuery(query: Select, alias: Option[String]) extends SelectExpr {
def as(a: String): SelectQuery =
copy(alias = Some(a))
}
case class SelectCondition(cond: Condition, alias: Option[String]) extends SelectExpr {
def as(a: String): SelectCondition =
copy(alias = Some(a))
}
}

View File

@ -9,8 +9,11 @@ trait TableDef {
object TableDef {
def apply(table: String, aliasName: Option[String] = None): TableDef =
new TableDef {
def tableName: String = table
def alias: Option[String] = aliasName
}
BasicTable(table, aliasName)
final case class BasicTable(tableName: String, alias: Option[String]) extends TableDef {
def as(alias: String): BasicTable =
copy(alias = Some(alias))
}
}

View File

@ -6,8 +6,8 @@ import _root_.doobie.implicits._
import _root_.doobie.{Query => _, _}
object ConditionBuilder {
val or = fr"OR"
val and = fr"AND"
val or = fr" OR"
val and = fr" AND"
val comma = fr","
val parenOpen = Fragment.const0("(")
val parenClose = Fragment.const0(")")
@ -46,7 +46,7 @@ object ConditionBuilder {
c1Frag ++ operator(op) ++ c2Frag
case Condition.InSubSelect(col, subsel) =>
val sub = DoobieQuery(subsel)
val sub = SelectBuilder(subsel)
SelectExprBuilder.column(col) ++ sql" IN (" ++ sub ++ parenClose
case c @ Condition.InValues(col, values, toLower) =>

View File

@ -29,12 +29,20 @@ object DBFunctionBuilder extends CommonBuilder {
case DBFunction.Power(expr, base) =>
sql"POWER($base, " ++ SelectExprBuilder.build(expr) ++ sql")"
case DBFunction.Plus(expr, more) =>
val v = more.prepended(expr).map(SelectExprBuilder.build)
v.reduce(_ ++ fr" +" ++ _)
case DBFunction.Calc(op, left, right) =>
SelectExprBuilder.build(left) ++
buildOperator(op) ++
SelectExprBuilder.build(right)
case DBFunction.Mult(expr, more) =>
val v = more.prepended(expr).map(SelectExprBuilder.build)
v.reduce(_ ++ fr" *" ++ _)
}
def buildOperator(op: DBFunction.Operator): Fragment =
op match {
case DBFunction.Operator.Minus =>
fr" -"
case DBFunction.Operator.Plus =>
fr" +"
case DBFunction.Operator.Mult =>
fr" *"
}
}

View File

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

View File

@ -5,7 +5,7 @@ import docspell.store.qb._
import _root_.doobie.implicits._
import _root_.doobie.{Query => _, _}
object DoobieQuery {
object SelectBuilder {
val comma = fr","
val asc = fr" ASC"
val desc = fr" DESC"
@ -13,29 +13,30 @@ object DoobieQuery {
val union = fr"UNION ALL"
def apply(q: Select): Fragment =
build(false)(q)
build(q)
def distinct(q: Select): Fragment =
build(true)(q)
def build(distinct: Boolean)(q: Select): Fragment =
def build(q: Select): Fragment =
q match {
case sq: Select.SimpleSelect =>
val sel = if (distinct) fr"SELECT DISTINCT" else fr"SELECT"
val sel = if (sq.distinctFlag) fr"SELECT DISTINCT" else fr"SELECT"
sel ++ buildSimple(sq)
case Select.Union(q, qs) =>
qs.prepended(q).map(build(false)).reduce(_ ++ union ++ _)
qs.prepended(q).map(build).reduce(_ ++ union ++ _)
case Select.Intersect(q, qs) =>
qs.prepended(q).map(build(false)).reduce(_ ++ intersect ++ _)
qs.prepended(q).map(build).reduce(_ ++ intersect ++ _)
case Select.Ordered(q, ob, obs) =>
val order = obs.prepended(ob).map(orderBy).reduce(_ ++ comma ++ _)
build(distinct)(q) ++ fr"ORDER BY" ++ order
build(q) ++ fr" ORDER BY" ++ order
case Select.Limit(q, n) =>
build(distinct)(q) ++ fr" LIMIT $n"
build(q) ++ fr" LIMIT $n"
case Select.WithCte(cte, moreCte, query) =>
val ctes = moreCte.prepended(cte)
fr"WITH" ++ ctes.map(buildCte).reduce(_ ++ comma ++ _) ++ fr" " ++ build(query)
}
def buildSimple(sq: Select.SimpleSelect): Fragment = {
@ -71,4 +72,7 @@ object DoobieQuery {
val f1 = gb.having.map(cond).getOrElse(Fragment.empty)
fr"GROUP BY" ++ f0 ++ f1
}
def buildCte(bind: CteBind): Fragment =
Fragment.const(bind.name.tableName) ++ sql"AS (" ++ build(bind.select) ++ sql")"
}

View File

@ -2,7 +2,8 @@ package docspell.store.qb.impl
import docspell.store.qb._
import _root_.doobie.{Query => _, _}
import doobie._
import doobie.implicits._
object SelectExprBuilder extends CommonBuilder {
@ -17,6 +18,11 @@ object SelectExprBuilder extends CommonBuilder {
case SelectExpr.SelectFun(fun, alias) =>
DBFunctionBuilder.build(fun) ++ appendAs(alias)
case SelectExpr.SelectQuery(query, alias) =>
sql"(" ++ SelectBuilder.build(query) ++ sql")" ++ appendAs(alias)
case SelectExpr.SelectCondition(cond, alias) =>
sql"(" ++ ConditionBuilder.build(cond) ++ sql")" ++ appendAs(alias)
}
}

View File

@ -4,7 +4,8 @@ import cats.data.OptionT
import cats.implicits._
import docspell.common._
import docspell.store.impl.Implicits._
import docspell.store.qb.DSL._
import docspell.store.qb._
import docspell.store.records._
import doobie._
@ -136,22 +137,16 @@ object QFolder {
}
def findById(id: Ident, account: AccountId): ConnectionIO[Option[FolderDetail]] = {
val user = RUser.as("u")
val mUserId = RFolderMember.Columns.user.prefix("m")
val mFolderId = RFolderMember.Columns.folder.prefix("m")
val uId = user.uid.column
val uLogin = user.login.column
val sColl = RFolder.Columns.collective.prefix("s")
val sId = RFolder.Columns.id.prefix("s")
val user = RUser.as("u")
val member = RFolderMember.as("m")
val folder = RFolder.as("s")
val from = RFolderMember.table ++ fr"m INNER JOIN" ++
Fragment.const(user.tableName) ++ fr"u ON" ++ mUserId.is(uId) ++ fr"INNER JOIN" ++
RFolder.table ++ fr"s ON" ++ mFolderId.is(sId)
val memberQ = selectSimple(
Seq(uId, uLogin),
from,
and(mFolderId.is(id), sColl.is(account.collective))
val memberQ = run(
select(user.uid, user.login),
from(member)
.innerJoin(user, member.user === user.uid)
.innerJoin(folder, member.folder === folder.id),
member.folder === id && folder.collective === account.collective
).query[IdRef].to[Vector]
(for {
@ -188,92 +183,85 @@ object QFolder {
// inner join user_ u on u.uid = s.owner
// where s.cid = 'eike';
val user = RUser.as("u")
val uId = user.uid.column
val uLogin = user.login.column
val sId = RFolder.Columns.id.prefix("s")
val sOwner = RFolder.Columns.owner.prefix("s")
val sName = RFolder.Columns.name.prefix("s")
val sColl = RFolder.Columns.collective.prefix("s")
val mUser = RFolderMember.Columns.user.prefix("m")
val mFolder = RFolderMember.Columns.folder.prefix("m")
val user = RUser.as("u")
val member = RFolderMember.as("m")
val folder = RFolder.as("s")
val memlogin = TableDef("memberlogin")
val memloginFolder = member.folder.inTable(memlogin)
val memloginLogn = user.login.inTable(memlogin)
//CTE
val cte: Fragment = {
val from1 = RFolderMember.table ++ fr"m INNER JOIN" ++
Fragment.const(user.tableName) ++ fr"u ON" ++ uId.is(mUser) ++ fr"INNER JOIN" ++
RFolder.table ++ fr"s ON" ++ sId.is(mFolder)
val from2 = RFolder.table ++ fr"s INNER JOIN" ++
Fragment.const(user.tableName) ++ fr"u ON" ++ uId.is(sOwner)
withCTE(
"memberlogin" ->
(selectSimple(Seq(mFolder, uLogin), from1, sColl.is(account.collective)) ++
fr"UNION ALL" ++
selectSimple(Seq(sId, uLogin), from2, sColl.is(account.collective)))
val sql =
withCte(
memlogin -> union(
Select(
select(member.folder, user.login),
from(member)
.innerJoin(user, user.uid === member.user)
.innerJoin(folder, folder.id === member.folder),
folder.collective === account.collective
),
Select(
select(folder.id, user.login),
from(folder)
.innerJoin(user, user.uid === folder.owner),
folder.collective === account.collective
)
)
)
}
.select(
Select(
select(
folder.id.s,
folder.name.s,
folder.owner.s,
user.login.s,
folder.created.s,
Select(
select(countAll > 0),
from(memlogin),
memloginFolder === folder.id && memloginLogn === account.user
).as("member"),
Select(
select(countAll - 1),
from(memlogin),
memloginFolder === folder.id
).as("member_count")
),
from(folder)
.innerJoin(user, user.uid === folder.owner),
where(
folder.collective === account.collective &&?
idQ.map(id => folder.id === id) &&?
nameQ.map(q => folder.name.like(s"%${q.toLowerCase}%")) &&?
ownerLogin.map(login => user.login === login)
)
).orderBy(folder.name.asc)
)
val isMember =
fr"SELECT COUNT(*) > 0 FROM memberlogin WHERE" ++ mFolder.prefix("").is(sId) ++
fr"AND" ++ uLogin.prefix("").is(account.user)
val memberCount =
fr"SELECT COUNT(*) - 1 FROM memberlogin WHERE" ++ mFolder.prefix("").is(sId)
//Query
val cols = Seq(
sId.f,
sName.f,
sOwner.f,
uLogin.f,
RFolder.Columns.created.prefix("s").f,
fr"(" ++ isMember ++ fr") as mem",
fr"(" ++ memberCount ++ fr") as cnt"
)
val from = RFolder.table ++ fr"s INNER JOIN" ++
Fragment.const(user.tableName) ++ fr"u ON" ++ uId.is(sOwner)
val where =
sColl.is(account.collective) :: idQ.toList
.map(id => sId.is(id)) ::: nameQ.toList.map(q =>
sName.lowerLike(s"%${q.toLowerCase}%")
) ::: ownerLogin.toList.map(login => uLogin.is(login))
(cte ++ selectSimple(commas(cols), from, and(where) ++ orderBy(sName.asc)))
sql.run
.query[FolderItem]
.to[Vector]
}
/** Select all folder_id where the given account is member or owner. */
def findMemberFolderIds(account: AccountId): Fragment = {
val user = RUser.as("u")
val fId = RFolder.Columns.id.prefix("f")
val fOwner = RFolder.Columns.owner.prefix("f")
val fColl = RFolder.Columns.collective.prefix("f")
val uId = user.uid.column
val uLogin = user.login.column
val mFolder = RFolderMember.Columns.folder.prefix("m")
val mUser = RFolderMember.Columns.user.prefix("m")
selectSimple(
Seq(fId),
RFolder.table ++ fr"f INNER JOIN" ++ Fragment.const(
user.tableName
) ++ fr"u ON" ++ fOwner.is(uId),
and(fColl.is(account.collective), uLogin.is(account.user))
) ++
fr"UNION ALL" ++
selectSimple(
Seq(mFolder),
RFolderMember.table ++ fr"m INNER JOIN" ++ RFolder.table ++ fr"f ON" ++ fId.is(
mFolder
) ++
fr"INNER JOIN" ++ Fragment.const(user.tableName) ++ fr"u ON" ++ uId.is(mUser),
and(fColl.is(account.collective), uLogin.is(account.user))
val user = RUser.as("u")
val f = RFolder.as("f")
val m = RFolderMember.as("m")
union(
Select(
select(f.id),
from(f).innerJoin(user, f.owner === user.uid),
f.collective === account.collective && user.login === account.user
),
Select(
select(m.folder),
from(m)
.innerJoin(f, f.id === m.folder)
.innerJoin(user, user.uid === m.user),
f.collective === account.collective && user.login === account.user
)
).run
}
def getMemberFolders(account: AccountId): ConnectionIO[Set[Ident]] =

View File

@ -91,6 +91,7 @@ object QItem {
val org = ROrganization.as("o")
val pers0 = RPerson.as("p0")
val pers1 = RPerson.as("p1")
val f = RFolder.as("f")
val IC = RItem.Columns.all.map(_.prefix("i"))
val OC = org.all.map(_.column)
@ -98,7 +99,7 @@ object QItem {
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 FC = List(f.id.column, f.name.column)
val cq =
selectSimple(
@ -129,9 +130,11 @@ object QItem {
fr"LEFT JOIN" ++ RItem.table ++ fr"ref ON" ++ RItem.Columns.inReplyTo
.prefix("i")
.is(RItem.Columns.id.prefix("ref")) ++
fr"LEFT JOIN" ++ RFolder.table ++ fr"f ON" ++ RItem.Columns.folder
fr"LEFT JOIN" ++ Fragment.const(
RFolder.T.tableName
) ++ fr"f ON" ++ RItem.Columns.folder
.prefix("i")
.is(RFolder.Columns.id.prefix("f")) ++
.is(f.id.column) ++
fr"WHERE" ++ RItem.Columns.id.prefix("i").is(id)
val q = cq
@ -322,13 +325,13 @@ object QItem {
val org = ROrganization.as("o0")
val pers0 = RPerson.as("p0")
val pers1 = RPerson.as("p1")
val f = RFolder.as("f1")
val IC = RItem.Columns
val AC = RAttachment.Columns
val FC = RFolder.Columns
val itemCols = IC.all
val equipCols = List(equip.eid.oldColumn, equip.name.oldColumn)
val folderCols = List(FC.id, FC.name)
val folderCols = List(f.id.oldColumn, f.name.oldColumn)
val cvItem = RCustomFieldValue.Columns.itemId.prefix("cv")
val finalCols = commas(
@ -350,8 +353,8 @@ object QItem {
pers1.name.column.f,
equip.eid.oldColumn.prefix("e1").f,
equip.name.oldColumn.prefix("e1").f,
FC.id.prefix("f1").f,
FC.name.prefix("f1").f,
f.id.column.f,
f.name.column.f,
// sql uses 1 for first character
IC.notes.prefix("i").substring(1, noteMaxLen),
// last column is only for sorting
@ -384,7 +387,11 @@ object QItem {
equip.cid.oldColumn.is(q.account.collective)
)
val withFolder =
selectSimple(folderCols, RFolder.table, FC.collective.is(q.account.collective))
selectSimple(
folderCols,
Fragment.const(f.tableName),
f.collective.oldColumn.is(q.account.collective)
)
val withAttach = fr"SELECT COUNT(" ++ AC.id.f ++ fr") as num, " ++ AC.itemId.f ++
fr"from" ++ RAttachment.table ++ fr"GROUP BY (" ++ AC.itemId.f ++ fr")"
@ -410,7 +417,7 @@ object QItem {
fr"LEFT JOIN equips e1 ON" ++ IC.concEquipment
.prefix("i")
.is(equip.eid.oldColumn.prefix("e1")) ++
fr"LEFT JOIN folders f1 ON" ++ IC.folder.prefix("i").is(FC.id.prefix("f1")) ++
fr"LEFT JOIN folders f1 ON" ++ IC.folder.prefix("i").is(f.id.column) ++
(if (q.customValues.isEmpty) Fragment.empty
else
fr"INNER JOIN customvalues cv ON" ++ cvItem.is(IC.id.prefix("i")))
@ -425,6 +432,7 @@ object QItem {
val org = ROrganization.as("o0")
val pers0 = RPerson.as("p0")
val pers1 = RPerson.as("p1")
val f = RFolder.as("f1")
val IC = RItem.Columns
// inclusive tags are AND-ed
@ -468,7 +476,7 @@ object QItem {
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),
f.id.column.isOrDiscard(q.folder),
if (q.tagsInclude.isEmpty && q.tagCategoryIncl.isEmpty) Fragment.empty
else
IC.id.prefix("i") ++ sql" IN (" ++ tagSelectsIncl

View File

@ -27,7 +27,7 @@ object RContact {
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)
val all = List[Column[_]](contactId, value, kind, personId, orgId, created)
}
private val T = Table(None)

View File

@ -4,8 +4,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._
@ -26,61 +26,58 @@ object RFolder {
now <- Timestamp.current[F]
} yield RFolder(nId, name, account.collective, account.user, now)
val table = fr"folder"
final case class Table(alias: Option[String]) extends TableDef {
val tableName = "folder"
object Columns {
val id = Column("id")
val name = Column("name")
val collective = Column("cid")
val owner = Column("owner")
val created = Column("created")
val id = Column[Ident]("id", this)
val name = Column[String]("name", this)
val collective = Column[Ident]("cid", this)
val owner = Column[Ident]("owner", this)
val created = Column[Timestamp]("created", this)
val all = List(id, name, collective, owner, created)
}
import Columns._
val T = Table(None)
def as(alias: String): Table =
Table(Some(alias))
def insert(value: RFolder): ConnectionIO[Int] = {
val sql = insertRow(
table,
all,
def insert(value: RFolder): ConnectionIO[Int] =
DML.insert(
T,
T.all,
fr"${value.id},${value.name},${value.collectiveId},${value.owner},${value.created}"
)
sql.update.run
}
def update(v: RFolder): ConnectionIO[Int] =
updateRow(
table,
and(id.is(v.id), collective.is(v.collectiveId), owner.is(v.owner)),
name.setTo(v.name)
).update.run
DML.update(
T,
T.id === v.id && T.collective === v.collectiveId && T.owner === v.owner,
DML.set(T.name.setTo(v.name))
)
def existsByName(coll: Ident, folderName: String): ConnectionIO[Boolean] =
selectCount(id, table, and(collective.is(coll), name.is(folderName)))
run(select(count(T.id)), from(T), T.collective === coll && T.name === folderName)
.query[Int]
.unique
.map(_ > 0)
def findById(folderId: Ident): ConnectionIO[Option[RFolder]] = {
val sql = selectSimple(all, table, id.is(folderId))
val sql = run(select(T.all), from(T), T.id === folderId)
sql.query[RFolder].option
}
def findAll(
coll: Ident,
nameQ: Option[String],
order: Columns.type => Column
order: Table => Column[_]
): ConnectionIO[Vector[RFolder]] = {
val q = Seq(collective.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[RFolder].to[Vector]
val nameFilter = nameQ.map(n => T.name.like(s"%${n.toLowerCase}%"))
val sql = Select(select(T.all), from(T), T.collective === coll &&? nameFilter)
.orderBy(order(T))
sql.run.query[RFolder].to[Vector]
}
def delete(folderId: Ident): ConnectionIO[Int] =
deleteFrom(table, id.is(folderId)).update.run
DML.delete(T, T.id === folderId)
}

View File

@ -4,8 +4,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._
@ -25,37 +25,36 @@ object RFolderMember {
now <- Timestamp.current[F]
} yield RFolderMember(nId, folder, user, now)
val table = fr"folder_member"
final case class Table(alias: Option[String]) extends TableDef {
val tableName = "folder_member"
object Columns {
val id = Column("id")
val folder = Column("folder_id")
val user = Column("user_id")
val created = Column("created")
val id = Column[Ident]("id", this)
val folder = Column[Ident]("folder_id", this)
val user = Column[Ident]("user_id", this)
val created = Column[Timestamp]("created", this)
val all = List(id, folder, user, created)
}
import Columns._
val T = Table(None)
def as(alias: String): Table =
Table(Some(alias))
def insert(value: RFolderMember): ConnectionIO[Int] = {
val sql = insertRow(
table,
all,
def insert(value: RFolderMember): ConnectionIO[Int] =
DML.insert(
T,
T.all,
fr"${value.id},${value.folderId},${value.userId},${value.created}"
)
sql.update.run
}
def findByUserId(userId: Ident, folderId: Ident): ConnectionIO[Option[RFolderMember]] =
selectSimple(all, table, and(folder.is(folderId), user.is(userId)))
run(select(T.all), from(T), T.folder === folderId && T.user === userId)
.query[RFolderMember]
.option
def delete(userId: Ident, folderId: Ident): ConnectionIO[Int] =
deleteFrom(table, and(folder.is(folderId), user.is(userId))).update.run
DML.delete(T, T.folder === folderId && T.user === userId)
def deleteAll(folderId: Ident): ConnectionIO[Int] =
deleteFrom(table, folder.is(folderId)).update.run
DML.delete(T, T.folder === folderId)
}

View File

@ -27,7 +27,7 @@ object RTag {
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)
val all = List[Column[_]](tid, cid, name, category, created)
}
val T = Table(None)
def as(alias: String): Table =

View File

@ -22,7 +22,7 @@ object DoobieQueryTest extends SimpleTestSuite {
)
val q = Select(proj, table, cond)
val frag = DoobieQuery(q)
val frag = SelectBuilder(q)
assertEquals(
frag.toString,
"""Fragment("SELECT c.id, c.name, c.owner_id, c.lecturer_id, c.lessons FROM course c INNER JOIN person o ON c.owner_id = o.id LEFT JOIN person l ON c.lecturer_id = l.id WHERE (LOWER(c.name) LIKE ? AND o.name = ? )")"""