mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-04-05 02:49:32 +00:00
Convert folder
This commit is contained in:
parent
87eb8c7f55
commit
d6f28d3eca
@ -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 {}
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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) =>
|
||||
|
@ -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" *"
|
||||
}
|
||||
}
|
||||
|
@ -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 =
|
||||
|
@ -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")"
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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]] =
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 =
|
||||
|
@ -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 = ? )")"""
|
||||
|
Loading…
x
Reference in New Issue
Block a user