Skip to content

Commit

Permalink
Seems to be working. Tweaking things ...
Browse files Browse the repository at this point in the history
  • Loading branch information
hohonuuli committed Oct 16, 2024
1 parent af9dee1 commit a18ebf4
Show file tree
Hide file tree
Showing 10 changed files with 171 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,20 @@
package org.mbari.annosaurus.controllers

import org.mbari.annosaurus.DatabaseConfig
import org.mbari.annosaurus.domain.QueryRequest
import org.mbari.annosaurus.repository.query.{
Constraint,
Constraints,
JDBC,
QueryResults,
QueryService
}
import org.mbari.annosaurus.domain.{Count, QueryRequest}
import org.mbari.annosaurus.repository.query.{JDBC, Query, QueryResults, QueryService}

class QueryController(databaseConfig: DatabaseConfig, viewName: String) {

private lazy val queryService = new QueryService(databaseConfig, viewName)

def count(constraints: Constraints): Either[Throwable, Int] =
queryService.count(constraints)
def count(queryRequest: QueryRequest): Either[Throwable, Count] =
val query = Query.from(queryRequest)
queryService.count(query).map(Count(_))

def query(queryRequest: QueryRequest): Either[Throwable, QueryResults] =
queryService.query(queryRequest.querySelects, queryRequest.constraints)
val query = Query.from(queryRequest)
queryService.query(query)

def listColumns(): Either[Throwable, Seq[JDBC.Metadata]] =
queryService.jdbc.listColumnsMetadata(viewName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,26 @@

package org.mbari.annosaurus.domain

import org.mbari.annosaurus.repository.query.Constraints
import org.mbari.annosaurus.repository.query.Query

import java.time.Instant

case class QueryRequest(
querySelects: Seq[String],
constraints: Constraints
where: Seq[ConstraintRequest],
select: Option[Seq[String]] = None,
limit: Option[Int] = None,
offset: Option[Int] = None,
concurrentObservations: Option[Boolean] = None,
relatedAssociations: Option[Boolean] = None
)

case class ConstraintRequest(
column: String,
in: Option[Seq[String]] = None,
like: Option[String] = None,
min: Option[Double] = None,
max: Option[Double] = None,
minmax: Option[Seq[Double]] = None,
between: Option[Seq[Instant]] = None,
isnull: Option[Boolean] = None
)
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import org.mbari.annosaurus.etc.circe.CirceCodecs
import org.mbari.annosaurus.etc.circe.CirceCodecs.{*, given}
import org.mbari.annosaurus.etc.jwt.JwtService
import org.mbari.annosaurus.etc.jdk.Logging.given
import org.mbari.annosaurus.repository.query.{Constraint, Constraints}
import org.mbari.annosaurus.repository.query.{Constraint, Query}

import scala.concurrent.ExecutionContext
import scala.concurrent.Future
Expand Down Expand Up @@ -119,18 +119,19 @@ trait Endpoints:
implicit lazy val sURL: Schema[URL] = Schema.string
implicit lazy val sInstant: Schema[Instant] = Schema.string
implicit lazy val sBulkAnnotationSc: Schema[BulkAnnotationSC] = Schema.derived[BulkAnnotationSC]
implicit lazy val sConstraintDate: Schema[Constraint.Date] = Schema.derived[Constraint.Date]
implicit lazy val sConstraintInString: Schema[Constraint.In[String]] =
Schema.derived[Constraint.In[String]]
implicit lazy val sConstraintLike: Schema[Constraint.Like] = Schema.derived[Constraint.Like]
implicit lazy val sConstraintMax: Schema[Constraint.Max] = Schema.derived[Constraint.Max]
implicit lazy val sConstraintMin: Schema[Constraint.Min] = Schema.derived[Constraint.Min]
implicit lazy val sConstraintMinMax: Schema[Constraint.MinMax] =
Schema.derived[Constraint.MinMax]
implicit lazy val sConstraintIsNull: Schema[Constraint.IsNull] =
Schema.derived[Constraint.IsNull]
implicit lazy val sConstraint: Schema[Constraint] = Schema.string
implicit lazy val sConstraints: Schema[Constraints] = Schema.derived[Constraints]
// implicit lazy val sConstraintDate: Schema[Constraint.Date] = Schema.derived[Constraint.Date]
// implicit lazy val sConstraintInString: Schema[Constraint.In[String]] =
// Schema.derived[Constraint.In[String]]
// implicit lazy val sConstraintLike: Schema[Constraint.Like] = Schema.derived[Constraint.Like]
// implicit lazy val sConstraintMax: Schema[Constraint.Max] = Schema.derived[Constraint.Max]
// implicit lazy val sConstraintMin: Schema[Constraint.Min] = Schema.derived[Constraint.Min]
// implicit lazy val sConstraintMinMax: Schema[Constraint.MinMax] =
// Schema.derived[Constraint.MinMax]
// implicit lazy val sConstraintIsNull: Schema[Constraint.IsNull] =
// Schema.derived[Constraint.IsNull]
// implicit lazy val sConstraint: Schema[Constraint] = Schema.string
// implicit lazy val sConstraints: Schema[Query] = Schema.derived[Query]
implicit lazy val sConstraintRequest: Schema[ConstraintRequest] = Schema.derived[ConstraintRequest]
implicit lazy val sQueryRequest: Schema[QueryRequest] = Schema.derived[QueryRequest]
// given Schema[Option[URL]] = Schema.string
// implicit lazy val sOptCAD: Schema[Option[CachedAncillaryDatumSC]] = Schema.derived[Option[CachedAncillaryDatumSC]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package org.mbari.annosaurus.endpoints

import CustomTapirJsonCirce.*
import org.mbari.annosaurus.controllers.QueryController
import org.mbari.annosaurus.domain.{ErrorMsg, QueryRequest}
import org.mbari.annosaurus.domain.{Count, ErrorMsg, QueryRequest}
import org.mbari.annosaurus.etc.circe.CirceCodecs.{*, given}
import org.mbari.annosaurus.etc.tapir.TapirCodecs.given
import org.mbari.annosaurus.repository.query.{JDBC, QueryResults}
Expand Down Expand Up @@ -59,20 +59,34 @@ class QueryEndpoints(queryController: QueryController)(using executionContext: E
.description("Run a query")
.tag(tag)

val runQueryImpl =
val runQueryImpl: Full[Unit, Unit, QueryRequest, ErrorMsg, String, Any, Future] =
runQuery.serverLogic(request =>
handleEitherAsync(
queryController.query(request).map(QueryResults.toTsv).map(_.mkString("\n"))
queryController.query(request).map(QueryResults.toTsv)
)
)

val count: Endpoint[Unit, QueryRequest, ErrorMsg, Count, Any] = openEndpoint
.post
.in(base / "count")
.in(jsonBody[QueryRequest])
.out(jsonBody[Count])
.name("countQuery")
.description("Count the number of rows in a query")
.tag(tag)

val countImpl: Full[Unit, Unit, QueryRequest, ErrorMsg, Count, Any, Future] =
count.serverLogic(request => handleEitherAsync(queryController.count(request)))

override def all: List[Endpoint[_, _, _, _, _]] = List(
listColumns,
runQuery
runQuery,
count
)

override def allImpl: List[ServerEndpoint[Any, Future]] = List(
listColumnsImpl,
runQueryImpl
runQueryImpl,
countImpl
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import io.circe.generic.semiauto.*
import io.circe.syntax.*
import org.mbari.annosaurus.util.HexUtil
import org.mbari.annosaurus.domain.*
import org.mbari.annosaurus.repository.query.{Constraint, Constraints, JDBC}
import org.mbari.annosaurus.repository.query.{Constraint, Query, JDBC}

import java.net.{URI, URL}
import java.time.Instant
Expand Down Expand Up @@ -396,9 +396,11 @@ object CirceCodecs {
case c: Constraint.Date => c.asJson
}

given constraintsDecoder: Decoder[Constraints] = deriveDecoder
given constraintsEncoder: Encoder[Constraints] = deriveEncoder
given constraintsDecoder: Decoder[Query] = deriveDecoder
given constraintsEncoder: Encoder[Query] = deriveEncoder

given constraintRequestDecoder: Decoder[ConstraintRequest] = deriveDecoder
given constraintRequestEncoder: Encoder[ConstraintRequest] = deriveEncoder
given queryRequestDecoder: Decoder[QueryRequest] = deriveDecoder
given queryRequestEncoder: Encoder[QueryRequest] = deriveEncoder
given jdbcMetadataDecoder: Decoder[JDBC.Metadata] = deriveDecoder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import java.sql.PreparedStatement
import scala.util.Try
object PreparedStatementGenerator {

val IndexTime = "index_recorded_timestamp"
val ObservationUuid = "observation_uuid"
val ImagedMomentUuid = "imaged_moment_uuid"

Expand Down Expand Up @@ -55,13 +56,16 @@ object PreparedStatementGenerator {

def buildPreparedStatementTemplate(
tableName: String,
querySelects: Seq[String],
columns: Seq[String],
constraints: Seq[Constraint],
includeConcurrentObservations: Boolean = false,
includeRelatedAssociations: Boolean = false
): String =

val select = (ObservationUuid +: querySelects).mkString(", ")
// Add InexTime to the columns if it is not already there so that we can always sort by time
val resolvedColumns1 = if columns.contains(IndexTime) then columns else IndexTime +: columns
val resolvedColumns2 = if resolvedColumns1.contains(ObservationUuid) then resolvedColumns1 else ObservationUuid +: resolvedColumns1
val select = resolvedColumns2.mkString(", ")
val where = buildWhereClause(
tableName,
constraints,
Expand All @@ -70,9 +74,10 @@ object PreparedStatementGenerator {
)

s"""
|SELECT $select
|SELECT DISTINCT $select
|FROM $tableName
|WHERE $where
|$where
|ORDER BY $IndexTime, $ObservationUuid ASC
|""".stripMargin

private def buildWhereClause(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,39 @@ package org.mbari.annosaurus.repository.query

import java.sql.ResultSet
import scala.collection.mutable
import scala.collection.mutable.ListBuffer

type QueryResults = Map[JDBC.Metadata, Seq[Any]]
type QueryResults = List[(JDBC.Metadata, Seq[Any])]

object QueryResults {

def fromResultSet(rs: ResultSet): QueryResults =
val metadata = JDBC.Metadata.fromResultSet(rs)
val map = scala.collection.mutable.Map[JDBC.Metadata, mutable.ListBuffer[Any]]()
val results = ListBuffer[ListBuffer[Any]]()
val numColumns = metadata.size
var isNew = true
while rs.next() do
metadata.foreach { m =>
val list = map.getOrElseUpdate(m, mutable.ListBuffer())
list += rs.getObject(m.columnName)
}
val data = map.map { case (k, v) => k -> v.result() }.toMap
data

def toTsv(queryResults: QueryResults): LazyList[String] =
val header = queryResults.keys.map(_.columnName).mkString("\t")
val values = queryResults.values
val n = values.headOption.map(_.size).getOrElse(0)
LazyList
.tabulate(n) { i =>
val s =
for v <- values
yield v(i)
s.mkString("\t")
}
.prepended(header)
for i <- 1 to numColumns do
if (isNew) results += ListBuffer[Any]()
val column = results(i - 1)
column += rs.getObject(i)
isNew = false
val columnData = results.result().map(_.result()) // Turn into immutable
metadata.zip(columnData).toList


def toTsv(queryResults: QueryResults): String =
val header = queryResults.map(_._1.columnName).mkString("\t")
val columnData = queryResults.map(_._2)
val numRows = columnData.headOption.map(_.size).getOrElse(0)
val numCols = columnData.size
val sb = new StringBuilder(header)
sb.append("\n")
for i <- 0 until numRows do
for j <- 0 until numCols do
sb.append(columnData(j)(i))
if j < numCols - 1 then sb.append("\t")
sb.append("\n")
sb.result()

}
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,12 @@ class QueryService(databaseConfig: DatabaseConfig, viewName: String) {

}

def count(constraints: Constraints): Either[Throwable, Int] =
def count(query: Query): Either[Throwable, Int] =
val sql = PreparedStatementGenerator.buildPreparedStatementTemplateForCounts(
viewName,
constraints.constraints,
constraints.concurrentObservations.getOrElse(false),
constraints.relatedAssociations.getOrElse(false)
query.constraints,
query.concurrentObservations.getOrElse(false),
query.relatedAssociations.getOrElse(false)
)
log.atDebug.log(s"Running query: $sql")
Using
Expand All @@ -81,22 +81,22 @@ class QueryService(databaseConfig: DatabaseConfig, viewName: String) {
ResultSet.CONCUR_READ_ONLY
)
)
PreparedStatementGenerator.bind(stmt, constraints.constraints)
val rs = use(stmt.executeQuery(sql))
PreparedStatementGenerator.bind(stmt, query.constraints)
val rs = stmt.executeQuery()
// val rs = use(stmt.executeQuery(sql))
if rs.next() then rs.getInt(1) else 0
)
.toEither

def query(
querySelects: Seq[String],
constraints: Constraints
query: Query
): Either[Throwable, QueryResults] =
val sql = PreparedStatementGenerator.buildPreparedStatementTemplate(
viewName,
querySelects,
constraints.constraints,
constraints.concurrentObservations.getOrElse(false),
constraints.relatedAssociations.getOrElse(false)
query.columns,
query.constraints,
query.concurrentObservations.getOrElse(false),
query.relatedAssociations.getOrElse(false)
)
log.atDebug.log(s"Running query: $sql")
Using
Expand All @@ -110,10 +110,11 @@ class QueryService(databaseConfig: DatabaseConfig, viewName: String) {
ResultSet.CONCUR_READ_ONLY
)
)
constraints.limit.foreach(stmt.setMaxRows)
constraints.offset.foreach(stmt.setFetchSize)
PreparedStatementGenerator.bind(stmt, constraints.constraints)
val rs = use(stmt.executeQuery(sql))
query.limit.foreach(stmt.setMaxRows)
query.offset.foreach(stmt.setFetchSize)
PreparedStatementGenerator.bind(stmt, query.constraints)
val rs = stmt.executeQuery()
// val rs = use(stmt.executeQuery(stmt))
QueryResults.fromResultSet(rs)
)
.toEither
Expand Down
Loading

0 comments on commit a18ebf4

Please sign in to comment.