mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-22 02:18:26 +00:00
Add more convenient date parsers and some basic macros
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
package docspell.store.qb.generator
|
||||
|
||||
import java.time.{Instant, LocalDate}
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
|
||||
import cats.data.NonEmptyList
|
||||
|
||||
@ -9,27 +10,27 @@ import docspell.query.ItemQuery._
|
||||
import docspell.query.{Date, ItemQuery}
|
||||
import docspell.store.qb.DSL._
|
||||
import docspell.store.qb.{Operator => QOp, _}
|
||||
import docspell.store.queries.QueryWildcard
|
||||
import docspell.store.records.{RCustomField, RCustomFieldValue, TagItemName}
|
||||
|
||||
import doobie.util.Put
|
||||
import docspell.store.queries.QueryWildcard
|
||||
|
||||
object ItemQueryGenerator {
|
||||
|
||||
def apply(tables: Tables, coll: Ident)(q: ItemQuery)(implicit
|
||||
def apply(today: LocalDate, tables: Tables, coll: Ident)(q: ItemQuery)(implicit
|
||||
PT: Put[Timestamp]
|
||||
): Condition =
|
||||
fromExpr(tables, coll)(q.expr)
|
||||
fromExpr(today, tables, coll)(q.expr)
|
||||
|
||||
final def fromExpr(tables: Tables, coll: Ident)(
|
||||
final def fromExpr(today: LocalDate, tables: Tables, coll: Ident)(
|
||||
expr: Expr
|
||||
)(implicit PT: Put[Timestamp]): Condition =
|
||||
expr match {
|
||||
case Expr.AndExpr(inner) =>
|
||||
Condition.And(inner.map(fromExpr(tables, coll)))
|
||||
Condition.And(inner.map(fromExpr(today, tables, coll)))
|
||||
|
||||
case Expr.OrExpr(inner) =>
|
||||
Condition.Or(inner.map(fromExpr(tables, coll)))
|
||||
Condition.Or(inner.map(fromExpr(today, tables, coll)))
|
||||
|
||||
case Expr.NotExpr(inner) =>
|
||||
inner match {
|
||||
@ -71,7 +72,7 @@ object ItemQueryGenerator {
|
||||
Condition.unit
|
||||
|
||||
case _ =>
|
||||
Condition.Not(fromExpr(tables, coll)(inner))
|
||||
Condition.Not(fromExpr(today, tables, coll)(inner))
|
||||
}
|
||||
|
||||
case Expr.Exists(field) =>
|
||||
@ -87,9 +88,10 @@ object ItemQueryGenerator {
|
||||
}
|
||||
|
||||
case Expr.SimpleExpr(op, Property.DateProperty(attr, value)) =>
|
||||
val dt = dateToTimestamp(value)
|
||||
val col = timestampColumn(tables)(attr)
|
||||
Condition.CompareVal(col, makeOp(op), dt)
|
||||
val dt = dateToTimestamp(today)(value)
|
||||
val col = timestampColumn(tables)(attr)
|
||||
val noLikeOp = if (op == Operator.Like) Operator.Eq else op
|
||||
Condition.CompareVal(col, makeOp(noLikeOp), dt)
|
||||
|
||||
case Expr.InExpr(attr, values) =>
|
||||
val col = stringColumn(tables)(attr)
|
||||
@ -98,10 +100,18 @@ object ItemQueryGenerator {
|
||||
|
||||
case Expr.InDateExpr(attr, values) =>
|
||||
val col = timestampColumn(tables)(attr)
|
||||
val dts = values.map(dateToTimestamp)
|
||||
val dts = values.map(dateToTimestamp(today))
|
||||
if (values.tail.isEmpty) col === dts.head
|
||||
else col.in(dts)
|
||||
|
||||
case Expr.DirectionExpr(incoming) =>
|
||||
if (incoming) tables.item.incoming === Direction.Incoming
|
||||
else tables.item.incoming === Direction.Outgoing
|
||||
|
||||
case Expr.InboxExpr(flag) =>
|
||||
if (flag) tables.item.state === ItemState.created
|
||||
else tables.item.state === ItemState.confirmed
|
||||
|
||||
case Expr.TagIdsMatch(op, tags) =>
|
||||
val ids = tags.toList.flatMap(s => Ident.fromString(s).toOption)
|
||||
NonEmptyList
|
||||
@ -142,12 +152,31 @@ object ItemQueryGenerator {
|
||||
Condition.unit
|
||||
}
|
||||
|
||||
private def dateToTimestamp(date: Date): Timestamp =
|
||||
private def dateToTimestamp(today: LocalDate)(date: Date): Timestamp =
|
||||
date match {
|
||||
case Date.Local(year, month, day) =>
|
||||
Timestamp.atUtc(LocalDate.of(year, month, day).atStartOfDay())
|
||||
case d: Date.DateLiteral =>
|
||||
val ld = dateLiteralToDate(today)(d)
|
||||
println(s">>>> date= $ld")
|
||||
Timestamp.atUtc(ld.atStartOfDay)
|
||||
case Date.Calc(date, c, period) =>
|
||||
val ld = c match {
|
||||
case Date.CalcDirection.Plus =>
|
||||
dateLiteralToDate(today)(date).plus(period)
|
||||
case Date.CalcDirection.Minus =>
|
||||
dateLiteralToDate(today)(date).minus(period)
|
||||
}
|
||||
println(s">>>> date= $ld")
|
||||
Timestamp.atUtc(ld.atStartOfDay())
|
||||
}
|
||||
|
||||
private def dateLiteralToDate(today: LocalDate)(dateLit: Date.DateLiteral): LocalDate =
|
||||
dateLit match {
|
||||
case Date.Local(date) =>
|
||||
date
|
||||
case Date.Millis(ms) =>
|
||||
Timestamp(Instant.ofEpochMilli(ms))
|
||||
Instant.ofEpochMilli(ms).atZone(Timestamp.UTC).toLocalDate()
|
||||
case Date.Today =>
|
||||
today
|
||||
}
|
||||
|
||||
private def anyColumn(tables: Tables)(attr: Attr): Column[_] =
|
||||
@ -171,6 +200,7 @@ object ItemQueryGenerator {
|
||||
case Attr.ItemId => tables.item.id.cast[String]
|
||||
case Attr.ItemName => tables.item.name
|
||||
case Attr.ItemSource => tables.item.source
|
||||
case Attr.ItemNotes => tables.item.notes
|
||||
case Attr.Correspondent.OrgId => tables.corrOrg.oid.cast[String]
|
||||
case Attr.Correspondent.OrgName => tables.corrOrg.name
|
||||
case Attr.Correspondent.PersonId => tables.corrPers.pid.cast[String]
|
||||
|
@ -1,5 +1,7 @@
|
||||
package docspell.store.queries
|
||||
|
||||
import java.time.LocalDate
|
||||
|
||||
import cats.data.{NonEmptyList => Nel}
|
||||
import cats.effect.Sync
|
||||
import cats.effect.concurrent.Ref
|
||||
@ -226,41 +228,42 @@ object QItem {
|
||||
findCustomFieldValuesForColl(coll, q.customValues)
|
||||
.map(itemIds => i.id.in(itemIds))
|
||||
|
||||
def queryCondFromExpr(coll: Ident, q: ItemQuery): Condition = {
|
||||
def queryCondFromExpr(today: LocalDate, coll: Ident, q: ItemQuery): Condition = {
|
||||
val tables = Tables(i, org, pers0, pers1, equip, f, a, m)
|
||||
ItemQueryGenerator.fromExpr(tables, coll)(q.expr)
|
||||
ItemQueryGenerator.fromExpr(today, tables, coll)(q.expr)
|
||||
}
|
||||
|
||||
def queryCondition(coll: Ident, cond: Query.QueryCond): Condition =
|
||||
def queryCondition(today: LocalDate, coll: Ident, cond: Query.QueryCond): Condition =
|
||||
cond match {
|
||||
case fm: Query.QueryForm =>
|
||||
queryCondFromForm(coll, fm)
|
||||
case expr: Query.QueryExpr =>
|
||||
queryCondFromExpr(coll, expr.q)
|
||||
queryCondFromExpr(today, coll, expr.q)
|
||||
}
|
||||
|
||||
def findItems(
|
||||
q: Query,
|
||||
today: LocalDate,
|
||||
maxNoteLen: Int,
|
||||
batch: Batch
|
||||
): Stream[ConnectionIO, ListItem] = {
|
||||
val sql = findItemsBase(q.fix, maxNoteLen)
|
||||
.changeWhere(c => c && queryCondition(q.fix.account.collective, q.cond))
|
||||
.changeWhere(c => c && queryCondition(today, q.fix.account.collective, q.cond))
|
||||
.limit(batch)
|
||||
.build
|
||||
logger.trace(s"List $batch items: $sql")
|
||||
sql.query[ListItem].stream
|
||||
}
|
||||
|
||||
def searchStats(q: Query): ConnectionIO[SearchSummary] =
|
||||
def searchStats(today: LocalDate)(q: Query): ConnectionIO[SearchSummary] =
|
||||
for {
|
||||
count <- searchCountSummary(q)
|
||||
tags <- searchTagSummary(q)
|
||||
fields <- searchFieldSummary(q)
|
||||
folders <- searchFolderSummary(q)
|
||||
count <- searchCountSummary(today)(q)
|
||||
tags <- searchTagSummary(today)(q)
|
||||
fields <- searchFieldSummary(today)(q)
|
||||
folders <- searchFolderSummary(today)(q)
|
||||
} yield SearchSummary(count, tags, fields, folders)
|
||||
|
||||
def searchTagSummary(q: Query): ConnectionIO[List[TagCount]] = {
|
||||
def searchTagSummary(today: LocalDate)(q: Query): ConnectionIO[List[TagCount]] = {
|
||||
val tagFrom =
|
||||
from(ti)
|
||||
.innerJoin(tag, tag.tid === ti.tagId)
|
||||
@ -270,7 +273,7 @@ object QItem {
|
||||
findItemsBase(q.fix, 0).unwrap
|
||||
.withSelect(select(tag.all).append(count(i.id).as("num")))
|
||||
.changeFrom(_.prepend(tagFrom))
|
||||
.changeWhere(c => c && queryCondition(q.fix.account.collective, q.cond))
|
||||
.changeWhere(c => c && queryCondition(today, q.fix.account.collective, q.cond))
|
||||
.groupBy(tag.tid)
|
||||
.build
|
||||
.query[TagCount]
|
||||
@ -284,27 +287,27 @@ object QItem {
|
||||
} yield existing ++ other.map(TagCount(_, 0))
|
||||
}
|
||||
|
||||
def searchCountSummary(q: Query): ConnectionIO[Int] =
|
||||
def searchCountSummary(today: LocalDate)(q: Query): ConnectionIO[Int] =
|
||||
findItemsBase(q.fix, 0).unwrap
|
||||
.withSelect(Nel.of(count(i.id).as("num")))
|
||||
.changeWhere(c => c && queryCondition(q.fix.account.collective, q.cond))
|
||||
.changeWhere(c => c && queryCondition(today, q.fix.account.collective, q.cond))
|
||||
.build
|
||||
.query[Int]
|
||||
.unique
|
||||
|
||||
def searchFolderSummary(q: Query): ConnectionIO[List[FolderCount]] = {
|
||||
def searchFolderSummary(today: LocalDate)(q: Query): ConnectionIO[List[FolderCount]] = {
|
||||
val fu = RUser.as("fu")
|
||||
findItemsBase(q.fix, 0).unwrap
|
||||
.withSelect(select(f.id, f.name, f.owner, fu.login).append(count(i.id).as("num")))
|
||||
.changeFrom(_.innerJoin(fu, fu.uid === f.owner))
|
||||
.changeWhere(c => c && queryCondition(q.fix.account.collective, q.cond))
|
||||
.changeWhere(c => c && queryCondition(today, q.fix.account.collective, q.cond))
|
||||
.groupBy(f.id, f.name, f.owner, fu.login)
|
||||
.build
|
||||
.query[FolderCount]
|
||||
.to[List]
|
||||
}
|
||||
|
||||
def searchFieldSummary(q: Query): ConnectionIO[List[FieldStats]] = {
|
||||
def searchFieldSummary(today: LocalDate)(q: Query): ConnectionIO[List[FieldStats]] = {
|
||||
val fieldJoin =
|
||||
from(cv)
|
||||
.innerJoin(cf, cf.id === cv.field)
|
||||
@ -313,7 +316,7 @@ object QItem {
|
||||
val base =
|
||||
findItemsBase(q.fix, 0).unwrap
|
||||
.changeFrom(_.prepend(fieldJoin))
|
||||
.changeWhere(c => c && queryCondition(q.fix.account.collective, q.cond))
|
||||
.changeWhere(c => c && queryCondition(today, q.fix.account.collective, q.cond))
|
||||
.groupBy(GroupBy(cf.all))
|
||||
|
||||
val basicFields = Nel.of(
|
||||
|
@ -22,11 +22,12 @@ object ItemQueryGeneratorTest extends SimpleTestSuite {
|
||||
RAttachment.as("a"),
|
||||
RAttachmentMeta.as("m")
|
||||
)
|
||||
val now: LocalDate = LocalDate.of(2021, 2, 25)
|
||||
|
||||
test("basic test") {
|
||||
val q = ItemQueryParser
|
||||
.parseUnsafe("(& name:hello date>=2020-02-01 (| source=expense folder=test ))")
|
||||
val cond = ItemQueryGenerator(tables, Ident.unsafe("coll"))(q)
|
||||
val cond = ItemQueryGenerator(now, tables, Ident.unsafe("coll"))(q)
|
||||
val expect =
|
||||
tables.item.name.like("hello") && tables.item.itemDate >= Timestamp.atUtc(
|
||||
LocalDate.of(2020, 2, 1).atStartOfDay()
|
||||
@ -35,29 +36,4 @@ object ItemQueryGeneratorTest extends SimpleTestSuite {
|
||||
assertEquals(cond, expect)
|
||||
}
|
||||
|
||||
// test("migration2") {
|
||||
// withStore("db2") { store =>
|
||||
// val c = RCollective(
|
||||
// Ident.unsafe("coll1"),
|
||||
// CollectiveState.Active,
|
||||
// Language.German,
|
||||
// true,
|
||||
// Timestamp.Epoch
|
||||
// )
|
||||
// val e =
|
||||
// REquipment(
|
||||
// Ident.unsafe("equip"),
|
||||
// Ident.unsafe("coll1"),
|
||||
// "name",
|
||||
// Timestamp.Epoch,
|
||||
// Timestamp.Epoch,
|
||||
// None
|
||||
// )
|
||||
//
|
||||
// for {
|
||||
// _ <- store.transact(RCollective.insert(c))
|
||||
// _ <- store.transact(REquipment.insert(e)).map(_ => ())
|
||||
// } yield ()
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
Reference in New Issue
Block a user