Add endpoint to search for items and return their tags

This is a more expensive query, since the tags must be resolved per
item. This is now implemented by doing additional queries while
caching each resolved tag.
This commit is contained in:
Eike Kettner
2020-06-07 15:18:28 +02:00
parent 0382ff2308
commit 1d2a6e6caa
6 changed files with 117 additions and 14 deletions

View File

@ -4,6 +4,7 @@ import bitpeace.FileMeta
import cats.effect.Sync
import cats.data.OptionT
import cats.implicits._
import cats.effect.concurrent.Ref
import fs2.Stream
import doobie._
import doobie.implicits._
@ -255,18 +256,10 @@ object QItem {
) ++
fr"SELECT DISTINCT" ++ finalCols ++ fr" FROM items i" ++
fr"LEFT JOIN attachs a ON" ++ IC.id.prefix("i").is(AC.itemId.prefix("a")) ++
fr"LEFT JOIN persons p0 ON" ++ IC.corrPerson
.prefix("i")
.is(PC.pid.prefix("p0")) ++ // i.corrperson = p0.pid" ++
fr"LEFT JOIN orgs o0 ON" ++ IC.corrOrg
.prefix("i")
.is(OC.oid.prefix("o0")) ++ // i.corrorg = o0.oid" ++
fr"LEFT JOIN persons p1 ON" ++ IC.concPerson
.prefix("i")
.is(PC.pid.prefix("p1")) ++ // i.concperson = p1.pid" ++
fr"LEFT JOIN equips e1 ON" ++ IC.concEquipment
.prefix("i")
.is(EC.eid.prefix("e1")) // i.concequipment = e1.eid"
fr"LEFT JOIN persons p0 ON" ++ IC.corrPerson.prefix("i").is(PC.pid.prefix("p0")) ++
fr"LEFT JOIN orgs o0 ON" ++ IC.corrOrg.prefix("i").is(OC.oid.prefix("o0")) ++
fr"LEFT JOIN persons p1 ON" ++ IC.concPerson.prefix("i").is(PC.pid.prefix("p1")) ++
fr"LEFT JOIN equips e1 ON" ++ IC.concEquipment.prefix("i").is(EC.eid.prefix("e1"))
// inclusive tags are AND-ed
val tagSelectsIncl = q.tagsInclude
@ -339,6 +332,43 @@ object QItem {
frag.query[ListItem].stream
}
case class ListItemWithTags(item: ListItem, tags: List[RTag])
/** Same as `findItems` but resolves the tags for each item. Note that
* this is implemented by running an additional query per item.
*/
def findItemsWithTags(
q: Query,
batch: Batch
): Stream[ConnectionIO, ListItemWithTags] = {
def findTag(
cache: Ref[ConnectionIO, Map[Ident, RTag]],
tagItem: RTagItem
): ConnectionIO[Option[RTag]] =
for {
cc <- cache.get
fromCache = cc.get(tagItem.tagId)
orFromDB <-
if (fromCache.isDefined) fromCache.pure[ConnectionIO]
else RTag.findById(tagItem.tagId)
_ <-
if (fromCache.isDefined) ().pure[ConnectionIO]
else
orFromDB match {
case Some(t) => cache.update(tmap => tmap.updated(t.tagId, t))
case None => ().pure[ConnectionIO]
}
} yield orFromDB
for {
resolvedTags <- Stream.eval(Ref.of[ConnectionIO, Map[Ident, RTag]](Map.empty))
item <- findItems(q, batch)
tagItems <- Stream.eval(RTagItem.findByItem(item.id))
tags <- Stream.eval(tagItems.traverse(ti => findTag(resolvedTags, ti)))
ftags = tags.flatten.filter(t => t.collective == q.collective)
} yield ListItemWithTags(item, ftags.toList.sortBy(_.name))
}
def delete[F[_]: Sync](store: Store[F])(itemId: Ident, collective: Ident): F[Int] =
for {
tn <- store.transact(RTagItem.deleteItemTags(itemId))

View File

@ -38,4 +38,7 @@ object RTagItem {
tagFrag = tagValues.map(v => fr"${v.tagItemId},${v.itemId},${v.tagId}")
ins <- insertRows(table, all, tagFrag).update.run
} yield ins
def findByItem(item: Ident): ConnectionIO[Vector[RTagItem]] =
selectSimple(all, table, itemId.is(item)).query[RTagItem].to[Vector]
}