All Downloads are FREE. Search and download functionalities are using the official Maven repository.

molecule.datalog.datomic.query.DatomicQueryResolve.scala Maven / Gradle / Ivy

There is a newer version: 0.12.1
Show newest version
package molecule.datalog.datomic.query

import java.util
import java.util.{Collections, Comparator, ArrayList => jArrayList, Collection => jCollection, List => jList}
import datomic.{Database, Peer}
import molecule.base.error.ModelError
import molecule.boilerplate.ast.Model._
import molecule.boilerplate.util.MoleculeLogging
import molecule.core.marshalling.dbView._
import molecule.core.query.Pagination
import molecule.datalog.core.query.{DatomicQueryBase, Model2DatomicQuery}
import molecule.datalog.datomic.facade.DatomicConn_JVM
import scala.annotation.tailrec
import scala.collection.mutable.ListBuffer


abstract class DatomicQueryResolve[Tpl](
  elements: List[Element],
  dbView: Option[DbView],
  m2q: Model2DatomicQuery[Tpl] with DatomicQueryBase
) extends Pagination[Tpl] with MoleculeLogging {


  protected def postAdjustPullCasts(): Unit = {
    m2q.pullCastss = m2q.pullCastss :+ m2q.pullCasts.toList
    m2q.pullSortss = m2q.pullSortss :+ m2q.pullSorts.sortBy(_._1).map(_._2).toList
  }

  protected def getRawData(
    conn: DatomicConn_JVM,
    altElements: List[Element] = Nil,
    altDb: Option[datomic.Database] = None
  ): jCollection[jList[AnyRef]] = {
    val db = altDb.getOrElse(getDb(conn))
    m2q.getDatomicQueries(conn.optimizeQuery, altElements) match {
      case ("", query, _)       =>
        distinct(Peer.q(query, db +: m2q.inputs: _*))
      case (preQuery, query, _) =>
        // Pre-query
        val preRows = Peer.q(preQuery, db +: m2q.preInputs: _*)
        val preIds  = new java.util.HashSet[Long](preRows.size())
        preRows.forEach { row =>
          preIds.add(row.get(0).asInstanceOf[Long])
        }
        // Main query using entity ids from pre-query
        distinct(Peer.q(query, db +: m2q.inputs :+ preIds: _*))
    }
  }

  private def getDb(conn: DatomicConn_JVM): Database = {
    dbView.fold(conn.peerConn.db()) {
      case AsOf(TxLong(tx))  => conn.peerConn.db().asOf(tx)
      case AsOf(TxDate(d))   => conn.peerConn.db().asOf(d)
      case Since(TxLong(tx)) => conn.peerConn.db().since(tx)
      case Since(TxDate(d))  => conn.peerConn.db().since(d)
    }
  }


  private def distinct(rows: jCollection[jList[AnyRef]]): jCollection[jList[AnyRef]] = {
    if (m2q.hasOptAttr)
      new util.HashSet[jList[AnyRef]](rows)
    else
      rows
  }


  protected def sortRows(rows: jCollection[jList[AnyRef]]): jArrayList[jList[AnyRef]] = {
    val sorters = m2q.getFlatSorters(m2q.sortss)
    sorters.length match {
      case 0 => new jArrayList(rows)
      case n =>
        val nestedIdsCount = m2q.nestedIds.length
        val sortedRows     = new jArrayList(rows)
        val comparator     = new Comparator[m2q.Row] {
          override def compare(a: m2q.Row, b: m2q.Row): Int = {
            var i      = 0
            var result = 0;
            result = sorters(i)(nestedIdsCount)(a, b)
            i += 1
            while (result == 0 && i != n) {
              result = sorters(i)(nestedIdsCount)(a, b)
              i += 1
            }
            result
          }
        }
        Collections.sort(sortedRows, comparator)
        sortedRows
    }
  }


  protected def offsetRaw(
    sortedRows: jArrayList[jList[AnyRef]],
    fromUntil: Option[(Int, Int, Boolean)]
  ): jList[jList[AnyRef]] = {
    fromUntil.fold[jList[jList[AnyRef]]](sortedRows) {
      case (from, until, _) => sortedRows.subList(from, until)
    }
  }

  def paginateFromIdentifiers(
    conn: DatomicConn_JVM,
    limit: Int,
    forward: Boolean,
    allTokens: List[String],
    attrTokens: List[String],
    identifiers: List[Any],
    identifyTpl: Tpl => Any,
    identifyRow: Boolean => m2q.Row => Any,
    nextCursor: (List[Tpl], List[String]) => String
  ): (List[Tpl], String, Boolean) = {
    // Filter query by primary non-unique sort attribute
    val filterAttr  = {
      val List(_, dir, _, tpe, ns, attr, _, a, b, c, x, y, z) = attrTokens

      // Filter by most inclusive value
      val first   = List(c, b, a).filter(_.nonEmpty).head
      val last    = List(x, y, z).filter(_.nonEmpty).head
      val (fn, v) = (forward, dir) match {
        case (true, "a") => (Ge, last)
        case (true, _)   => (Le, first)
        case (_, "a")    => (Le, first)
        case (_, _)      => (Ge, last)
      }
      getFilterAttr(tpe, ns, attr, fn, v)
    }
    val altElements = filterAttr +: elements
    val rows        = getRawData(conn, altElements)
    val sortedRows  = sortRows(rows)
    logger.debug(sortedRows.toArray().mkString("\n"))

    if (sortedRows.size() == 0) {
      (Nil, "", false)
    } else {
      if (m2q.isNested) {
        val nestedTpls     = m2q.rows2nested(sortedRows)
        val totalCount     = nestedTpls.length
        val count          = getCount(limit, forward, totalCount)
        val nestedTpls1    = if (forward) nestedTpls else nestedTpls.reverse
        val (tuples, more) = paginateTpls(count, nestedTpls1, identifiers, identifyTpl)
        val tpls           = if (forward) tuples else tuples.reverse
        val cursor         = nextCursor(tpls, allTokens)
        (tpls, cursor, more > 0)

      } else {
        val totalCount = rows.size
        if (m2q.isNestedOpt) {
          postAdjustPullCasts()
          if (!forward) Collections.reverse(sortedRows)
          val count          = getCount(limit, forward, totalCount)
          val (tuples, more) = paginateRows(count, sortedRows, identifiers, identifyRow(true), m2q.pullRow2tpl)
          val tpls           = (if (forward) tuples else tuples.reverse).filterNot(_ == Nil)
          val cursor         = nextCursor(tpls, allTokens)
          (tpls, cursor, more > 0)

        } else {
          if (!forward) Collections.reverse(sortedRows)
          val count          = getCount(limit, forward, totalCount)
          val row2AnyTpl     = m2q.castRow2AnyTpl(m2q.aritiess.head, m2q.castss.head, 0, None)
          val row2tpl        = (row: m2q.Row) => row2AnyTpl(row).asInstanceOf[Tpl]
          val (tuples, more) = paginateRows(count, sortedRows, identifiers, identifyRow(false), row2tpl)
          val tpls           = if (forward) tuples else tuples.reverse
          val cursor         = nextCursor(tpls, allTokens)
          (tpls, cursor, more > 0)
        }
      }
    }
  }

  def paginateRows(
    count: Int,
    sortedRows: jList[jList[AnyRef]],
    identifiers: List[Any],
    identify: m2q.Row => Any,
    row2tpl: m2q.Row => Tpl,
  ): (List[Tpl], Int) = {
    val tuples = ListBuffer.empty[Tpl]
    var window = false
    var i      = 0
    var more   = 0
    @tailrec
    def findFrom(identifiers: List[Any]): Unit = {
      identifiers match {
        case identifier :: remainingIdentifiers =>
          sortedRows.forEach {
            case row if window && i != count        => i += 1; tuples += row2tpl(row)
            case row if identify(row) == identifier => window = true
            case _                                  => if (window) more += 1
          }
          if (tuples.isEmpty) {
            // Recursively try with next identifier
            findFrom(remainingIdentifiers)
          }

        case Nil => throw ModelError(edgeValuesNotFound)
      }
    }
    findFrom(identifiers)
    (tuples.toList, more)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy