diff --git a/modules/query/shared/src/main/scala/docspell/query/ItemQuery.scala b/modules/query/shared/src/main/scala/docspell/query/ItemQuery.scala index 61bec94d..4fce13d2 100644 --- a/modules/query/shared/src/main/scala/docspell/query/ItemQuery.scala +++ b/modules/query/shared/src/main/scala/docspell/query/ItemQuery.scala @@ -109,7 +109,8 @@ object ItemQuery { final case class CustomFieldIdMatch(id: String, op: Operator, value: String) extends Expr - final case class Fulltext(query: String) extends Expr + final case class Fulltext(query: String) extends Expr + final case class ChecksumMatch(checksum: String) extends Expr // things that can be expressed with terms above sealed trait MacroExpr extends Expr { diff --git a/modules/query/shared/src/main/scala/docspell/query/internal/ExprUtil.scala b/modules/query/shared/src/main/scala/docspell/query/internal/ExprUtil.scala index 3a82dd1f..f4f8193c 100644 --- a/modules/query/shared/src/main/scala/docspell/query/internal/ExprUtil.scala +++ b/modules/query/shared/src/main/scala/docspell/query/internal/ExprUtil.scala @@ -65,6 +65,8 @@ object ExprUtil { expr case CustomFieldIdMatch(_, _, _) => expr + case ChecksumMatch(_) => + expr } private def spliceAnd(nodes: Nel[Expr]): Nel[Expr] = diff --git a/modules/query/shared/src/main/scala/docspell/query/internal/SimpleExprParser.scala b/modules/query/shared/src/main/scala/docspell/query/internal/SimpleExprParser.scala index 55b92188..00bf5aa0 100644 --- a/modules/query/shared/src/main/scala/docspell/query/internal/SimpleExprParser.scala +++ b/modules/query/shared/src/main/scala/docspell/query/internal/SimpleExprParser.scala @@ -76,6 +76,9 @@ object SimpleExprParser { val dirExpr: P[Expr.DirectionExpr] = (P.string("incoming:") *> BasicParser.bool).map(Expr.DirectionExpr.apply) + val checksumExpr: P[Expr.ChecksumMatch] = + (P.string("checksum:") *> BasicParser.singleString).map(Expr.ChecksumMatch.apply) + val simpleExpr: P[Expr] = P.oneOf( List( @@ -89,7 +92,8 @@ object SimpleExprParser { customFieldIdExpr, customFieldExpr, inboxExpr, - dirExpr + dirExpr, + checksumExpr ) ) } diff --git a/modules/store/src/main/scala/docspell/store/qb/generator/ItemQueryGenerator.scala b/modules/store/src/main/scala/docspell/store/qb/generator/ItemQueryGenerator.scala index 71760463..c92367fe 100644 --- a/modules/store/src/main/scala/docspell/store/qb/generator/ItemQueryGenerator.scala +++ b/modules/store/src/main/scala/docspell/store/qb/generator/ItemQueryGenerator.scala @@ -3,15 +3,16 @@ package docspell.store.qb.generator import java.time.Instant import java.time.LocalDate -import cats.data.NonEmptyList +import cats.data.{NonEmptyList => Nel} import docspell.common._ import docspell.query.ItemQuery._ import docspell.query.{Date, ItemQuery} import docspell.store.qb.DSL._ import docspell.store.qb.{Operator => QOp, _} +import docspell.store.queries.QItem import docspell.store.queries.QueryWildcard -import docspell.store.records.{RCustomField, RCustomFieldValue, TagItemName} +import docspell.store.records._ import doobie.util.Put @@ -39,7 +40,7 @@ object ItemQueryGenerator { case Expr.TagIdsMatch(op, tags) => val ids = tags.toList.flatMap(s => Ident.fromString(s).toOption) - NonEmptyList + Nel .fromList(ids) .map { nel => op match { @@ -114,7 +115,7 @@ object ItemQueryGenerator { case Expr.TagIdsMatch(op, tags) => val ids = tags.toList.flatMap(s => Ident.fromString(s).toOption) - NonEmptyList + Nel .fromList(ids) .map { nel => op match { @@ -152,6 +153,10 @@ object ItemQueryGenerator { case Expr.CustomFieldIdMatch(field, op, value) => tables.item.id.in(itemsWithCustomField(_.id ==== field)(coll, makeOp(op), value)) + case Expr.ChecksumMatch(checksum) => + val select = QItem.findByChecksumQuery(checksum, coll, Set.empty) + tables.item.id.in(select.withSelect(Nel.of(RItem.as("i").id.s))) + case Expr.Fulltext(_) => // not supported here Condition.unit diff --git a/modules/store/src/main/scala/docspell/store/queries/QItem.scala b/modules/store/src/main/scala/docspell/store/queries/QItem.scala index a79db262..2e1e1296 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QItem.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QItem.scala @@ -509,6 +509,16 @@ object QItem { collective: Ident, excludeFileMeta: Set[Ident] ): ConnectionIO[Vector[RItem]] = { + val qq = findByChecksumQuery(checksum, collective, excludeFileMeta).build + logger.debug(s"FindByChecksum: $qq") + qq.query[RItem].to[Vector] + } + + def findByChecksumQuery( + checksum: String, + collective: Ident, + excludeFileMeta: Set[Ident] + ): Select = { val m1 = RFileMeta.as("m1") val m2 = RFileMeta.as("m2") val m3 = RFileMeta.as("m3") @@ -517,26 +527,23 @@ object QItem { val s = RAttachmentSource.as("s") val r = RAttachmentArchive.as("r") val fms = Nel.of(m1, m2, m3) - val qq = - Select( - select(i.all), - from(i) - .innerJoin(a, a.itemId === i.id) - .innerJoin(s, s.id === a.id) - .innerJoin(m1, m1.id === a.fileId) - .innerJoin(m2, m2.id === s.fileId) - .leftJoin(r, r.id === a.id) - .leftJoin(m3, m3.id === r.fileId), - where( - i.cid === collective && - Condition.Or(fms.map(m => m.checksum === checksum)) &&? - Nel - .fromList(excludeFileMeta.toList) - .map(excl => Condition.And(fms.map(m => m.id.isNull || m.id.notIn(excl)))) - ) - ).distinct.build - logger.debug(s"FindByChecksum: $qq") - qq.query[RItem].to[Vector] + Select( + select(i.all), + from(i) + .innerJoin(a, a.itemId === i.id) + .innerJoin(s, s.id === a.id) + .innerJoin(m1, m1.id === a.fileId) + .innerJoin(m2, m2.id === s.fileId) + .leftJoin(r, r.id === a.id) + .leftJoin(m3, m3.id === r.fileId), + where( + i.cid === collective && + Condition.Or(fms.map(m => m.checksum === checksum)) &&? + Nel + .fromList(excludeFileMeta.toList) + .map(excl => Condition.And(fms.map(m => m.id.isNull || m.id.notIn(excl)))) + ) + ).distinct } final case class NameAndNotes(