Allow to specify ordering when retrieving meta data

The query now searches in more fields. For example, when getting a
list of tags, the query is applied to the tag name *and* category.
When listing persons, the query now also looks in the associated
organization name.

This has been used to make some headers in the meta data tables
clickable to sort the list accordingly.

Refs: #965, #538
This commit is contained in:
eikek
2021-08-24 21:35:57 +02:00
parent 5926565267
commit cf88f5c2de
52 changed files with 1236 additions and 208 deletions

View File

@ -12,6 +12,7 @@ case class Column[A](name: String, table: TableDef) {
def cast[B]: Column[B] =
this.asInstanceOf[Column[B]]
}
object Column {}

View File

@ -303,6 +303,12 @@ trait DSL extends DoobieMeta {
def as(otherCol: Column[_]): SelectExpr =
SelectExpr.SelectFun(dbf, Some(otherCol.name))
def asc: OrderBy =
OrderBy(SelectExpr.SelectFun(dbf, None), OrderBy.OrderType.Asc)
def desc: OrderBy =
OrderBy(SelectExpr.SelectFun(dbf, None), OrderBy.OrderType.Desc)
def ===[A](value: A)(implicit P: Put[A]): Condition =
Condition.CompareFVal(dbf.s, Operator.Eq, value)

View File

@ -135,6 +135,9 @@ object Select {
def orderBy(ob: OrderBy, obs: OrderBy*): Ordered =
Ordered(this, ob, obs.toVector)
def orderBy(ob: Nel[OrderBy]): Ordered =
Ordered(this, ob.head, ob.tail.toVector)
}
case class RawSelect(fragment: Fragment) extends Select {

View File

@ -24,9 +24,10 @@ object QCustomField {
def findAllLike(
coll: Ident,
nameQuery: Option[String]
nameQuery: Option[String],
order: RCustomField.Table => Nel[OrderBy]
): ConnectionIO[Vector[CustomFieldData]] =
findFragment(coll, nameQuery, None).build.query[CustomFieldData].to[Vector]
findFragment(coll, nameQuery, None, order).build.query[CustomFieldData].to[Vector]
def findById(field: Ident, collective: Ident): ConnectionIO[Option[CustomFieldData]] =
findFragment(collective, None, field.some).build.query[CustomFieldData].option
@ -34,7 +35,8 @@ object QCustomField {
private def findFragment(
coll: Ident,
nameQuery: Option[String],
fieldId: Option[Ident]
fieldId: Option[Ident],
order: RCustomField.Table => Nel[OrderBy] = t => Nel.of(t.name.asc)
): Select = {
val nameFilter = nameQuery.map { q =>
f.name.likes(q) || (f.label.isNotNull && f.label.like(q))
@ -46,7 +48,7 @@ object QCustomField {
.leftJoin(v, f.id === v.field),
f.cid === coll &&? nameFilter &&? fieldId.map(fid => f.id === fid),
GroupBy(f.all)
)
).orderBy(order(f))
}
final case class FieldValue(

View File

@ -7,6 +7,7 @@
package docspell.store.queries
import cats.data.OptionT
import cats.data.{NonEmptyList => Nel}
import cats.implicits._
import docspell.common._
@ -156,8 +157,11 @@ object QFolder {
).query[IdRef].to[Vector]
(for {
folder <- OptionT(findAll(account, Some(id), None, None).map(_.headOption))
memb <- OptionT.liftF(memberQ)
folder <- OptionT(
findAll(account, Some(id), None, None, (ft, _) => Nel.of(ft.name.asc))
.map(_.headOption)
)
memb <- OptionT.liftF(memberQ)
} yield folder.withMembers(memb.toList)).value
}
@ -165,7 +169,8 @@ object QFolder {
account: AccountId,
idQ: Option[Ident],
ownerLogin: Option[Ident],
nameQ: Option[String]
nameQ: Option[String],
order: (RFolder.Table, RUser.Table) => Nel[OrderBy]
): ConnectionIO[Vector[FolderItem]] = {
// with memberlogin as
// (select m.folder_id,u.login
@ -239,7 +244,7 @@ object QFolder {
nameQ.map(q => folder.name.like(s"%${q.toLowerCase}%")) &&?
ownerLogin.map(login => user.login === login)
)
).orderBy(folder.name.asc)
).orderBy(order(folder, user))
).build.query[FolderItem].to[Vector]
}

View File

@ -6,7 +6,7 @@
package docspell.store.queries
import cats.data.NonEmptyList
import cats.data.{NonEmptyList => Nel}
import cats.implicits._
import fs2._
@ -27,7 +27,7 @@ object QOrganization {
def findOrgAndContact(
coll: Ident,
query: Option[String],
order: ROrganization.Table => Column[_]
order: ROrganization.Table => Nel[OrderBy]
): Stream[ConnectionIO, (ROrganization, Vector[RContact])] = {
val valFilter = query.map { q =>
val v = s"%$q%"
@ -74,18 +74,18 @@ object QOrganization {
def findPersonAndContact(
coll: Ident,
query: Option[String],
order: RPerson.Table => Column[_]
order: (RPerson.Table, ROrganization.Table) => Nel[OrderBy]
): Stream[ConnectionIO, (RPerson, Option[ROrganization], Vector[RContact])] = {
val valFilter = query
.map(s => s"%$s%")
.map(v => c.value.like(v) || p.name.like(v) || p.notes.like(v))
.map(v => c.value.like(v) || p.name.like(v) || org.name.like(v) || p.notes.like(v))
val sql = Select(
select(p.all, org.all, c.all),
from(p)
.leftJoin(org, org.oid === p.oid)
.leftJoin(c, c.personId === p.pid),
p.cid === coll &&? valFilter
).orderBy(order(p))
).orderBy(order(p, org))
sql.build
.query[(RPerson, Option[ROrganization], Option[RContact])]
@ -128,7 +128,7 @@ object QOrganization {
coll: Ident,
value: String,
ck: Option[ContactKind],
use: Option[NonEmptyList[PersonUse]]
use: Option[Nel[PersonUse]]
): Stream[ConnectionIO, RPerson] =
runDistinct(
select(p.all),

View File

@ -87,7 +87,7 @@ object REquipment {
def findAll(
coll: Ident,
nameQ: Option[String],
order: Table => Column[_]
order: Table => NonEmptyList[OrderBy]
): ConnectionIO[Vector[REquipment]] = {
val t = Table(None)

View File

@ -7,7 +7,7 @@
package docspell.store.records
import cats.Eq
import cats.data.NonEmptyList
import cats.data.{NonEmptyList => Nel}
import fs2.Stream
import docspell.common.{IdRef, _}
@ -52,7 +52,7 @@ object ROrganization {
val shortName = Column[String]("short_name", this)
val use = Column[OrgUse]("org_use", this)
val all =
NonEmptyList.of[Column[_]](
Nel.of[Column[_]](
oid,
cid,
name,
@ -122,7 +122,7 @@ object ROrganization {
def findLike(
coll: Ident,
orgName: String,
use: NonEmptyList[OrgUse]
use: Nel[OrgUse]
): ConnectionIO[Vector[IdRef]] =
run(
select(T.oid, T.name),
@ -163,7 +163,7 @@ object ROrganization {
def findAllRef(
coll: Ident,
nameQ: Option[String],
order: Table => Column[_]
order: Table => Nel[OrderBy]
): ConnectionIO[Vector[IdRef]] = {
val nameFilter = nameQ.map(s =>
T.name.like(s"%${s.toLowerCase}%") || T.shortName.like(s"%${s.toLowerCase}%")

View File

@ -7,7 +7,7 @@
package docspell.store.records
import cats.Eq
import cats.data.NonEmptyList
import cats.data.{NonEmptyList => Nel}
import cats.effect._
import fs2.Stream
@ -52,7 +52,7 @@ object RPerson {
val updated = Column[Timestamp]("updated", this)
val oid = Column[Ident]("oid", this)
val use = Column[PersonUse]("person_use", this)
val all = NonEmptyList.of[Column[_]](
val all = Nel.of[Column[_]](
pid,
cid,
name,
@ -122,7 +122,7 @@ object RPerson {
def findLike(
coll: Ident,
personName: String,
use: NonEmptyList[PersonUse]
use: Nel[PersonUse]
): ConnectionIO[Vector[IdRef]] =
run(
select(T.pid, T.name),
@ -134,7 +134,7 @@ object RPerson {
coll: Ident,
contactKind: ContactKind,
value: String,
use: NonEmptyList[PersonUse]
use: Nel[PersonUse]
): ConnectionIO[Vector[IdRef]] = {
val p = RPerson.as("p")
val c = RContact.as("c")
@ -162,7 +162,7 @@ object RPerson {
def findAllRef(
coll: Ident,
nameQ: Option[String],
order: Table => Column[_]
order: Table => Nel[OrderBy]
): ConnectionIO[Vector[IdRef]] = {
val nameFilter = nameQ.map(s => T.name.like(s"%${s.toLowerCase}%"))
@ -176,7 +176,7 @@ object RPerson {
DML.delete(T, T.pid === personId && T.cid === coll)
def findOrganization(ids: Set[Ident]): ConnectionIO[Vector[PersonRef]] =
NonEmptyList.fromList(ids.toList) match {
Nel.fromList(ids.toList) match {
case Some(nel) =>
run(select(T.pid, T.name, T.oid), from(T), T.pid.in(nel))
.query[PersonRef]

View File

@ -75,10 +75,11 @@ object RTag {
def findAll(
coll: Ident,
nameQ: Option[String],
order: Table => Column[_]
query: Option[String],
order: Table => NonEmptyList[OrderBy]
): ConnectionIO[Vector[RTag]] = {
val nameFilter = nameQ.map(s => T.name.like(s"%${s.toLowerCase}%"))
val nameFilter =
query.map(_.toLowerCase).map(s => T.name.like(s"%$s%") || T.category.like(s"%$s%"))
val sql =
Select(select(T.all), from(T), T.cid === coll &&? nameFilter).orderBy(order(T))
sql.build.query[RTag].to[Vector]