Add more convenient date parsers and some basic macros

This commit is contained in:
Eike Kettner
2021-02-28 16:11:25 +01:00
parent af73b59ec2
commit 9013d9264e
23 changed files with 445 additions and 142 deletions

View File

@ -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]

View File

@ -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(

View File

@ -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 ()
// }
// }
}