Refactor search to separate between a base query and user query

The `findBase` is adding only strictly required conditions. Everything
else comes from the user.
This commit is contained in:
Eike Kettner
2021-02-24 21:57:41 +01:00
parent c3cdec416c
commit 186014a1c6
7 changed files with 129 additions and 112 deletions

View File

@ -117,7 +117,7 @@ object QItem {
.map(nel => intersect(nel.map(singleSelect)))
}
private def findItemsBase(q: Query, noteMaxLen: Int): Select = {
private def findItemsBase(q: Query.Fix, noteMaxLen: Int): Select = {
object Attachs extends TableDef {
val tableName = "attachs"
val aliasName = "cta"
@ -128,7 +128,7 @@ object QItem {
val coll = q.account.collective
val baseSelect = Select(
Select(
select(
i.id.s,
i.name.s,
@ -172,27 +172,23 @@ object QItem {
.leftJoin(pers1, pers1.pid === i.concPerson && pers1.cid === coll)
.leftJoin(equip, equip.eid === i.concEquipment && equip.cid === coll),
where(
i.cid === coll &&? Nel.fromList(q.states.toList).map(nel => i.state.in(nel)) &&
or(i.folder.isNull, i.folder.in(QFolder.findMemberFolderIds(q.account)))
i.cid === coll && or(
i.folder.isNull,
i.folder.in(QFolder.findMemberFolderIds(q.account))
)
)
).distinct.orderBy(
q.orderAsc
.map(of => OrderBy.asc(coalesce(of(i).s, i.created.s).s))
.getOrElse(OrderBy.desc(coalesce(i.itemDate.s, i.created.s).s))
)
findCustomFieldValuesForColl(coll, q.customValues) match {
case Some(itemIds) =>
baseSelect.changeWhere(c => c && i.id.in(itemIds))
case None =>
baseSelect
}
}
def queryCondition(q: Query): Condition =
def queryCondition(coll: Ident, q: Query.QueryCond): Condition =
Condition.unit &&?
q.direction.map(d => i.incoming === d) &&?
q.name.map(n => i.name.like(QueryWildcard.lower(n))) &&?
Nel.fromList(q.states.toList).map(nel => i.state.in(nel)) &&?
q.allNames
.map(QueryWildcard.lower)
.map(n =>
@ -221,15 +217,17 @@ object QItem {
.map(subsel => i.id.in(subsel)) &&?
TagItemName
.itemsWithEitherTagOrCategory(q.tagsExclude, q.tagCategoryExcl)
.map(subsel => i.id.notIn(subsel))
.map(subsel => i.id.notIn(subsel)) &&?
findCustomFieldValuesForColl(coll, q.customValues)
.map(itemIds => i.id.in(itemIds))
def findItems(
q: Query,
maxNoteLen: Int,
batch: Batch
): Stream[ConnectionIO, ListItem] = {
val sql = findItemsBase(q, maxNoteLen)
.changeWhere(c => c && queryCondition(q))
val sql = findItemsBase(q.fix, maxNoteLen)
.changeWhere(c => c && queryCondition(q.fix.account.collective, q.cond))
.limit(batch)
.build
logger.trace(s"List $batch items: $sql")
@ -251,10 +249,10 @@ object QItem {
.innerJoin(i, i.id === ti.itemId)
val tagCloud =
findItemsBase(q, 0).unwrap
findItemsBase(q.fix, 0).unwrap
.withSelect(select(tag.all).append(count(i.id).as("num")))
.changeFrom(_.prepend(tagFrom))
.changeWhere(c => c && queryCondition(q))
.changeWhere(c => c && queryCondition(q.fix.account.collective, q.cond))
.groupBy(tag.tid)
.build
.query[TagCount]
@ -264,24 +262,24 @@ object QItem {
// are not included they are fetched separately
for {
existing <- tagCloud
other <- RTag.findOthers(q.account.collective, existing.map(_.tag.tagId))
other <- RTag.findOthers(q.fix.account.collective, existing.map(_.tag.tagId))
} yield existing ++ other.map(TagCount(_, 0))
}
def searchCountSummary(q: Query): ConnectionIO[Int] =
findItemsBase(q, 0).unwrap
findItemsBase(q.fix, 0).unwrap
.withSelect(Nel.of(count(i.id).as("num")))
.changeWhere(c => c && queryCondition(q))
.changeWhere(c => c && queryCondition(q.fix.account.collective, q.cond))
.build
.query[Int]
.unique
def searchFolderSummary(q: Query): ConnectionIO[List[FolderCount]] = {
val fu = RUser.as("fu")
findItemsBase(q, 0).unwrap
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))
.changeWhere(c => c && queryCondition(q.fix.account.collective, q.cond))
.groupBy(f.id, f.name, f.owner, fu.login)
.build
.query[FolderCount]
@ -295,9 +293,9 @@ object QItem {
.innerJoin(i, i.id === cv.itemId)
val base =
findItemsBase(q, 0).unwrap
findItemsBase(q.fix, 0).unwrap
.changeFrom(_.prepend(fieldJoin))
.changeWhere(c => c && queryCondition(q))
.changeWhere(c => c && queryCondition(q.fix.account.collective, q.cond))
.groupBy(GroupBy(cf.all))
val basicFields = Nel.of(
@ -374,7 +372,7 @@ object QItem {
)
)
val from = findItemsBase(q, maxNoteLen)
val from = findItemsBase(q.fix, maxNoteLen)
.appendCte(cte)
.appendSelect(Tids.weight.s)
.changeFrom(_.innerJoin(Tids, Tids.itemId === i.id))

View File

@ -1,57 +1,70 @@
package docspell.store.queries
import docspell.common._
import docspell.store.qb.Column
import docspell.store.records.RItem
case class Query(
account: AccountId,
name: Option[String],
states: Seq[ItemState],
direction: Option[Direction],
corrPerson: Option[Ident],
corrOrg: Option[Ident],
concPerson: Option[Ident],
concEquip: Option[Ident],
folder: Option[Ident],
tagsInclude: List[Ident],
tagsExclude: List[Ident],
tagCategoryIncl: List[String],
tagCategoryExcl: List[String],
dateFrom: Option[Timestamp],
dateTo: Option[Timestamp],
dueDateFrom: Option[Timestamp],
dueDateTo: Option[Timestamp],
allNames: Option[String],
itemIds: Option[Set[Ident]],
customValues: Seq[CustomValue],
source: Option[String],
orderAsc: Option[RItem.Table => docspell.store.qb.Column[_]]
)
case class Query(fix: Query.Fix, cond: Query.QueryCond) {
def withCond(f: Query.QueryCond => Query.QueryCond): Query =
copy(cond = f(cond))
def withOrder(orderAsc: RItem.Table => Column[_]): Query =
copy(fix = fix.copy(orderAsc = Some(orderAsc)))
}
object Query {
case class Fix(account: AccountId, orderAsc: Option[RItem.Table => Column[_]])
case class QueryCond(
name: Option[String],
states: Seq[ItemState],
direction: Option[Direction],
corrPerson: Option[Ident],
corrOrg: Option[Ident],
concPerson: Option[Ident],
concEquip: Option[Ident],
folder: Option[Ident],
tagsInclude: List[Ident],
tagsExclude: List[Ident],
tagCategoryIncl: List[String],
tagCategoryExcl: List[String],
dateFrom: Option[Timestamp],
dateTo: Option[Timestamp],
dueDateFrom: Option[Timestamp],
dueDateTo: Option[Timestamp],
allNames: Option[String],
itemIds: Option[Set[Ident]],
customValues: Seq[CustomValue],
source: Option[String]
)
object QueryCond {
val empty =
QueryCond(
None,
Seq.empty,
None,
None,
None,
None,
None,
None,
Nil,
Nil,
Nil,
Nil,
None,
None,
None,
None,
None,
None,
Seq.empty,
None
)
}
def empty(account: AccountId): Query =
Query(
account,
None,
Seq.empty,
None,
None,
None,
None,
None,
None,
Nil,
Nil,
Nil,
Nil,
None,
None,
None,
None,
None,
None,
Seq.empty,
None,
None
)
Query(Fix(account, None), QueryCond.empty)
}

View File

@ -23,7 +23,7 @@ object ItemQueryGeneratorTest extends SimpleTestSuite {
RAttachmentMeta.as("m")
)
test("migration") {
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)