mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-07 07:35:59 +00:00
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:
parent
c3cdec416c
commit
186014a1c6
@ -164,7 +164,7 @@ object OFulltext {
|
|||||||
.flatMap(r => Stream.emits(r.results.map(_.itemId)))
|
.flatMap(r => Stream.emits(r.results.map(_.itemId)))
|
||||||
.compile
|
.compile
|
||||||
.to(Set)
|
.to(Set)
|
||||||
q = Query.empty(account).copy(itemIds = itemIds.some)
|
q = Query.empty(account).withCond(_.copy(itemIds = itemIds.some))
|
||||||
res <- store.transact(QItem.searchStats(q))
|
res <- store.transact(QItem.searchStats(q))
|
||||||
} yield res
|
} yield res
|
||||||
}
|
}
|
||||||
@ -208,7 +208,7 @@ object OFulltext {
|
|||||||
search <- itemSearch.findItems(0)(q, Batch.all)
|
search <- itemSearch.findItems(0)(q, Batch.all)
|
||||||
fq = FtsQuery(
|
fq = FtsQuery(
|
||||||
ftsQ.query,
|
ftsQ.query,
|
||||||
q.account.collective,
|
q.fix.account.collective,
|
||||||
search.map(_.id).toSet,
|
search.map(_.id).toSet,
|
||||||
Set.empty,
|
Set.empty,
|
||||||
500,
|
500,
|
||||||
@ -220,7 +220,7 @@ object OFulltext {
|
|||||||
.flatMap(r => Stream.emits(r.results.map(_.itemId)))
|
.flatMap(r => Stream.emits(r.results.map(_.itemId)))
|
||||||
.compile
|
.compile
|
||||||
.to(Set)
|
.to(Set)
|
||||||
qnext = q.copy(itemIds = items.some)
|
qnext = q.withCond(_.copy(itemIds = items.some))
|
||||||
res <- store.transact(QItem.searchStats(qnext))
|
res <- store.transact(QItem.searchStats(qnext))
|
||||||
} yield res
|
} yield res
|
||||||
|
|
||||||
@ -253,7 +253,7 @@ object OFulltext {
|
|||||||
val sqlResult = search(q, batch)
|
val sqlResult = search(q, batch)
|
||||||
val fq = FtsQuery(
|
val fq = FtsQuery(
|
||||||
ftsQ.query,
|
ftsQ.query,
|
||||||
q.account.collective,
|
q.fix.account.collective,
|
||||||
Set.empty,
|
Set.empty,
|
||||||
Set.empty,
|
Set.empty,
|
||||||
0,
|
0,
|
||||||
|
@ -138,7 +138,9 @@ object OItemSearch {
|
|||||||
val search = QItem.findItems(q, maxNoteLen: Int, batch)
|
val search = QItem.findItems(q, maxNoteLen: Int, batch)
|
||||||
store
|
store
|
||||||
.transact(
|
.transact(
|
||||||
QItem.findItemsWithTags(q.account.collective, search).take(batch.limit.toLong)
|
QItem
|
||||||
|
.findItemsWithTags(q.fix.account.collective, search)
|
||||||
|
.take(batch.limit.toLong)
|
||||||
)
|
)
|
||||||
.compile
|
.compile
|
||||||
.toVector
|
.toVector
|
||||||
|
@ -72,13 +72,16 @@ object NotifyDueItemsTask {
|
|||||||
q =
|
q =
|
||||||
Query
|
Query
|
||||||
.empty(ctx.args.account)
|
.empty(ctx.args.account)
|
||||||
.copy(
|
.withOrder(orderAsc = _.dueDate)
|
||||||
|
.withCond(
|
||||||
|
_.copy(
|
||||||
states = ItemState.validStates.toList,
|
states = ItemState.validStates.toList,
|
||||||
tagsInclude = ctx.args.tagsInclude,
|
tagsInclude = ctx.args.tagsInclude,
|
||||||
tagsExclude = ctx.args.tagsExclude,
|
tagsExclude = ctx.args.tagsExclude,
|
||||||
dueDateFrom = ctx.args.daysBack.map(back => now - Duration.days(back.toLong)),
|
dueDateFrom =
|
||||||
dueDateTo = Some(now + Duration.days(ctx.args.remindDays.toLong)),
|
ctx.args.daysBack.map(back => now - Duration.days(back.toLong)),
|
||||||
orderAsc = Some(_.dueDate)
|
dueDateTo = Some(now + Duration.days(ctx.args.remindDays.toLong))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
res <-
|
res <-
|
||||||
ctx.store
|
ctx.store
|
||||||
|
@ -145,7 +145,8 @@ trait Conversions {
|
|||||||
|
|
||||||
def mkQuery(m: ItemSearch, account: AccountId): OItemSearch.Query =
|
def mkQuery(m: ItemSearch, account: AccountId): OItemSearch.Query =
|
||||||
OItemSearch.Query(
|
OItemSearch.Query(
|
||||||
account,
|
OItemSearch.Query.Fix(account, None),
|
||||||
|
OItemSearch.Query.QueryCond(
|
||||||
m.name,
|
m.name,
|
||||||
if (m.inbox) Seq(ItemState.Created)
|
if (m.inbox) Seq(ItemState.Created)
|
||||||
else ItemState.validStates.toList,
|
else ItemState.validStates.toList,
|
||||||
@ -168,8 +169,8 @@ trait Conversions {
|
|||||||
.map(_.ids.flatMap(i => Ident.fromString(i).toOption).toSet)
|
.map(_.ids.flatMap(i => Ident.fromString(i).toOption).toSet)
|
||||||
.filter(_.nonEmpty),
|
.filter(_.nonEmpty),
|
||||||
m.customValues.map(mkCustomValue),
|
m.customValues.map(mkCustomValue),
|
||||||
m.source,
|
m.source
|
||||||
None
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def mkCustomValue(v: CustomFieldValue): OItemSearch.CustomValue =
|
def mkCustomValue(v: CustomFieldValue): OItemSearch.CustomValue =
|
||||||
@ -182,7 +183,7 @@ trait Conversions {
|
|||||||
ItemLightGroup(g._1, g._2.map(mkItemLight).toList)
|
ItemLightGroup(g._1, g._2.map(mkItemLight).toList)
|
||||||
|
|
||||||
val gs =
|
val gs =
|
||||||
groups.map(mkGroup _).toList.sortWith((g1, g2) => g1.name.compareTo(g2.name) >= 0)
|
groups.map(mkGroup).toList.sortWith((g1, g2) => g1.name.compareTo(g2.name) >= 0)
|
||||||
ItemLightList(gs)
|
ItemLightList(gs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ object QItem {
|
|||||||
.map(nel => intersect(nel.map(singleSelect)))
|
.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 {
|
object Attachs extends TableDef {
|
||||||
val tableName = "attachs"
|
val tableName = "attachs"
|
||||||
val aliasName = "cta"
|
val aliasName = "cta"
|
||||||
@ -128,7 +128,7 @@ object QItem {
|
|||||||
|
|
||||||
val coll = q.account.collective
|
val coll = q.account.collective
|
||||||
|
|
||||||
val baseSelect = Select(
|
Select(
|
||||||
select(
|
select(
|
||||||
i.id.s,
|
i.id.s,
|
||||||
i.name.s,
|
i.name.s,
|
||||||
@ -172,27 +172,23 @@ object QItem {
|
|||||||
.leftJoin(pers1, pers1.pid === i.concPerson && pers1.cid === coll)
|
.leftJoin(pers1, pers1.pid === i.concPerson && pers1.cid === coll)
|
||||||
.leftJoin(equip, equip.eid === i.concEquipment && equip.cid === coll),
|
.leftJoin(equip, equip.eid === i.concEquipment && equip.cid === coll),
|
||||||
where(
|
where(
|
||||||
i.cid === coll &&? Nel.fromList(q.states.toList).map(nel => i.state.in(nel)) &&
|
i.cid === coll && or(
|
||||||
or(i.folder.isNull, i.folder.in(QFolder.findMemberFolderIds(q.account)))
|
i.folder.isNull,
|
||||||
|
i.folder.in(QFolder.findMemberFolderIds(q.account))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
).distinct.orderBy(
|
).distinct.orderBy(
|
||||||
q.orderAsc
|
q.orderAsc
|
||||||
.map(of => OrderBy.asc(coalesce(of(i).s, i.created.s).s))
|
.map(of => OrderBy.asc(coalesce(of(i).s, i.created.s).s))
|
||||||
.getOrElse(OrderBy.desc(coalesce(i.itemDate.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 &&?
|
Condition.unit &&?
|
||||||
q.direction.map(d => i.incoming === d) &&?
|
q.direction.map(d => i.incoming === d) &&?
|
||||||
q.name.map(n => i.name.like(QueryWildcard.lower(n))) &&?
|
q.name.map(n => i.name.like(QueryWildcard.lower(n))) &&?
|
||||||
|
Nel.fromList(q.states.toList).map(nel => i.state.in(nel)) &&?
|
||||||
q.allNames
|
q.allNames
|
||||||
.map(QueryWildcard.lower)
|
.map(QueryWildcard.lower)
|
||||||
.map(n =>
|
.map(n =>
|
||||||
@ -221,15 +217,17 @@ object QItem {
|
|||||||
.map(subsel => i.id.in(subsel)) &&?
|
.map(subsel => i.id.in(subsel)) &&?
|
||||||
TagItemName
|
TagItemName
|
||||||
.itemsWithEitherTagOrCategory(q.tagsExclude, q.tagCategoryExcl)
|
.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(
|
def findItems(
|
||||||
q: Query,
|
q: Query,
|
||||||
maxNoteLen: Int,
|
maxNoteLen: Int,
|
||||||
batch: Batch
|
batch: Batch
|
||||||
): Stream[ConnectionIO, ListItem] = {
|
): Stream[ConnectionIO, ListItem] = {
|
||||||
val sql = findItemsBase(q, maxNoteLen)
|
val sql = findItemsBase(q.fix, maxNoteLen)
|
||||||
.changeWhere(c => c && queryCondition(q))
|
.changeWhere(c => c && queryCondition(q.fix.account.collective, q.cond))
|
||||||
.limit(batch)
|
.limit(batch)
|
||||||
.build
|
.build
|
||||||
logger.trace(s"List $batch items: $sql")
|
logger.trace(s"List $batch items: $sql")
|
||||||
@ -251,10 +249,10 @@ object QItem {
|
|||||||
.innerJoin(i, i.id === ti.itemId)
|
.innerJoin(i, i.id === ti.itemId)
|
||||||
|
|
||||||
val tagCloud =
|
val tagCloud =
|
||||||
findItemsBase(q, 0).unwrap
|
findItemsBase(q.fix, 0).unwrap
|
||||||
.withSelect(select(tag.all).append(count(i.id).as("num")))
|
.withSelect(select(tag.all).append(count(i.id).as("num")))
|
||||||
.changeFrom(_.prepend(tagFrom))
|
.changeFrom(_.prepend(tagFrom))
|
||||||
.changeWhere(c => c && queryCondition(q))
|
.changeWhere(c => c && queryCondition(q.fix.account.collective, q.cond))
|
||||||
.groupBy(tag.tid)
|
.groupBy(tag.tid)
|
||||||
.build
|
.build
|
||||||
.query[TagCount]
|
.query[TagCount]
|
||||||
@ -264,24 +262,24 @@ object QItem {
|
|||||||
// are not included they are fetched separately
|
// are not included they are fetched separately
|
||||||
for {
|
for {
|
||||||
existing <- tagCloud
|
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))
|
} yield existing ++ other.map(TagCount(_, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
def searchCountSummary(q: Query): ConnectionIO[Int] =
|
def searchCountSummary(q: Query): ConnectionIO[Int] =
|
||||||
findItemsBase(q, 0).unwrap
|
findItemsBase(q.fix, 0).unwrap
|
||||||
.withSelect(Nel.of(count(i.id).as("num")))
|
.withSelect(Nel.of(count(i.id).as("num")))
|
||||||
.changeWhere(c => c && queryCondition(q))
|
.changeWhere(c => c && queryCondition(q.fix.account.collective, q.cond))
|
||||||
.build
|
.build
|
||||||
.query[Int]
|
.query[Int]
|
||||||
.unique
|
.unique
|
||||||
|
|
||||||
def searchFolderSummary(q: Query): ConnectionIO[List[FolderCount]] = {
|
def searchFolderSummary(q: Query): ConnectionIO[List[FolderCount]] = {
|
||||||
val fu = RUser.as("fu")
|
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")))
|
.withSelect(select(f.id, f.name, f.owner, fu.login).append(count(i.id).as("num")))
|
||||||
.changeFrom(_.innerJoin(fu, fu.uid === f.owner))
|
.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)
|
.groupBy(f.id, f.name, f.owner, fu.login)
|
||||||
.build
|
.build
|
||||||
.query[FolderCount]
|
.query[FolderCount]
|
||||||
@ -295,9 +293,9 @@ object QItem {
|
|||||||
.innerJoin(i, i.id === cv.itemId)
|
.innerJoin(i, i.id === cv.itemId)
|
||||||
|
|
||||||
val base =
|
val base =
|
||||||
findItemsBase(q, 0).unwrap
|
findItemsBase(q.fix, 0).unwrap
|
||||||
.changeFrom(_.prepend(fieldJoin))
|
.changeFrom(_.prepend(fieldJoin))
|
||||||
.changeWhere(c => c && queryCondition(q))
|
.changeWhere(c => c && queryCondition(q.fix.account.collective, q.cond))
|
||||||
.groupBy(GroupBy(cf.all))
|
.groupBy(GroupBy(cf.all))
|
||||||
|
|
||||||
val basicFields = Nel.of(
|
val basicFields = Nel.of(
|
||||||
@ -374,7 +372,7 @@ object QItem {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val from = findItemsBase(q, maxNoteLen)
|
val from = findItemsBase(q.fix, maxNoteLen)
|
||||||
.appendCte(cte)
|
.appendCte(cte)
|
||||||
.appendSelect(Tids.weight.s)
|
.appendSelect(Tids.weight.s)
|
||||||
.changeFrom(_.innerJoin(Tids, Tids.itemId === i.id))
|
.changeFrom(_.innerJoin(Tids, Tids.itemId === i.id))
|
||||||
|
@ -1,10 +1,22 @@
|
|||||||
package docspell.store.queries
|
package docspell.store.queries
|
||||||
|
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
|
import docspell.store.qb.Column
|
||||||
import docspell.store.records.RItem
|
import docspell.store.records.RItem
|
||||||
|
|
||||||
case class Query(
|
case class Query(fix: Query.Fix, cond: Query.QueryCond) {
|
||||||
account: AccountId,
|
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],
|
name: Option[String],
|
||||||
states: Seq[ItemState],
|
states: Seq[ItemState],
|
||||||
direction: Option[Direction],
|
direction: Option[Direction],
|
||||||
@ -24,14 +36,11 @@ case class Query(
|
|||||||
allNames: Option[String],
|
allNames: Option[String],
|
||||||
itemIds: Option[Set[Ident]],
|
itemIds: Option[Set[Ident]],
|
||||||
customValues: Seq[CustomValue],
|
customValues: Seq[CustomValue],
|
||||||
source: Option[String],
|
source: Option[String]
|
||||||
orderAsc: Option[RItem.Table => docspell.store.qb.Column[_]]
|
)
|
||||||
)
|
object QueryCond {
|
||||||
|
val empty =
|
||||||
object Query {
|
QueryCond(
|
||||||
def empty(account: AccountId): Query =
|
|
||||||
Query(
|
|
||||||
account,
|
|
||||||
None,
|
None,
|
||||||
Seq.empty,
|
Seq.empty,
|
||||||
None,
|
None,
|
||||||
@ -51,7 +60,11 @@ object Query {
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
Seq.empty,
|
Seq.empty,
|
||||||
None,
|
|
||||||
None
|
None
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
def empty(account: AccountId): Query =
|
||||||
|
Query(Fix(account, None), QueryCond.empty)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ object ItemQueryGeneratorTest extends SimpleTestSuite {
|
|||||||
RAttachmentMeta.as("m")
|
RAttachmentMeta.as("m")
|
||||||
)
|
)
|
||||||
|
|
||||||
test("migration") {
|
test("basic test") {
|
||||||
val q = ItemQueryParser
|
val q = ItemQueryParser
|
||||||
.parseUnsafe("(& name:hello date>=2020-02-01 (| source=expense folder=test ))")
|
.parseUnsafe("(& name:hello date>=2020-02-01 (| source=expense folder=test ))")
|
||||||
val cond = ItemQueryGenerator(tables, Ident.unsafe("coll"))(q)
|
val cond = ItemQueryGenerator(tables, Ident.unsafe("coll"))(q)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user