mirror of
				https://github.com/TheAnachronism/docspell.git
				synced 2025-10-30 21:40:12 +00:00 
			
		
		
		
	Convert folder
This commit is contained in:
		| @@ -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 = ? )")""" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user