From 3dd31307563576031e11607f04547d3a6fc84344 Mon Sep 17 00:00:00 2001 From: vlad Date: Thu, 25 Jan 2018 14:13:07 -0800 Subject: Extracting query library --- README.md | 20 +- build.sbt | 19 +- project/build.properties | 2 +- .../restquery/db/SlickPostgresQueryBuilder.scala | 78 +++--- .../driver/restquery/db/SlickQueryBuilder.scala | 299 ++------------------- .../restquery/db/SlickQueryBuilderParameters.scala | 19 +- .../xyz/driver/restquery/query/Pagination.scala | 2 +- .../query/SearchFilterBinaryOperation.scala | 12 +- .../driver/restquery/query/SearchFilterExpr.scala | 25 +- .../query/SearchFilterNAryOperation.scala | 7 +- .../scala/xyz/driver/restquery/query/Sorting.scala | 4 +- .../xyz/driver/restquery/rest/Directives.scala | 15 +- .../restquery/rest/parsers/DimensionsParser.scala | 2 +- .../restquery/rest/parsers/PaginationParser.scala | 9 +- .../rest/parsers/ParseQueryArgException.scala | 2 +- .../rest/parsers/SearchFilterParser.scala | 6 +- .../restquery/rest/parsers/SortingParser.scala | 11 +- .../scala/xyz/driver/restquery/utils/Utils.scala | 3 +- .../restquery/db/SearchFilterExprSuite.scala | 3 +- .../rest/parsers/PaginationParserSuite.scala | 6 +- .../rest/parsers/SearchFilterParserSuite.scala | 24 +- .../rest/parsers/SortingParserSuite.scala | 4 +- .../driver/restquery/rest/parsers/TestUtils.scala | 5 +- .../driver/restquery/utils/StringOpsSuite.scala | 15 +- 24 files changed, 171 insertions(+), 421 deletions(-) diff --git a/README.md b/README.md index 74faee6..a0d1c11 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,19 @@ -# PDS UI Common Library +# REST Query library -To make a change, create a PR, merge to master, do `sbt scalafmt`, follow instructions. +Allows to read queries from the REST requests (query parameters) and map them to the typed SQL Slick queries. +Supports ANDs and ORs, pagination and sorting. + +Queries can be build in the code as well (e.g., for testing): + +``` +SearchFilterExpr.Atom.NAry("id", In, objects.map(x => Long.box(x.id))) +``` + +or + +``` +SearchFilterExpr.Union(Seq( + SearchFilterExpr.Atom.Binary("status", Eq, "New"), + SearchFilterExpr.Atom.Binary("status", Eq, "Verified") +) +``` diff --git a/build.sbt b/build.sbt index 8f2198d..0ca1f7d 100644 --- a/build.sbt +++ b/build.sbt @@ -2,26 +2,17 @@ import sbt._ import Keys._ lazy val core = (project in file(".")) - .driverLibrary("pds-ui-common") - .settings(lintingSettings) - .settings(scalacOptions -= "-Xfatal-warnings") // this is needed to ignore unused implicits that are actually used in scala 2.11 + .driverLibrary("rest-query") + .settings(lintingSettings ++ formatSettings) .settings(sources in (Compile, doc) := Seq.empty, publishArtifact in (Compile, packageDoc) := false) .settings(libraryDependencies ++= Seq( - "com.github.pureconfig" %% "pureconfig" % "0.7.2", + "com.typesafe.akka" %% "akka-http" % "10.0.10", "com.lihaoyi" %% "fastparse" % "1.0.0", - "com.typesafe.akka" %% "akka-http" % "10.0.10", - "com.typesafe.scala-logging" %% "scala-logging" % "3.5.0", - "io.github.cloudify" %% "spdf" % "1.4.0", "xyz.driver" %% "core" % "1.6.12", - "xyz.driver" %% "domain-model" % "0.21.16", + "com.typesafe.scala-logging" %% "scala-logging" % "3.5.0", + "org.slf4j" % "slf4j-api" % "1.7.21", "ch.qos.logback" % "logback-classic" % "1.1.7", - "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.8.4", - "com.github.spullara.mustache.java" % "scala-extensions-2.11" % "0.9.4", - "com.google.cloud" % "google-cloud-storage" % "1.2.1", - "com.sendgrid" % "sendgrid-java" % "3.1.0" exclude ("org.mockito", "mockito-core"), "com.typesafe" % "config" % "1.3.0", - "org.asynchttpclient" % "async-http-client" % "2.0.24", - "org.slf4j" % "slf4j-api" % "1.7.21", "org.scalacheck" %% "scalacheck" % "1.13.4" % "test", "org.scalatest" %% "scalatest" % "3.0.1" % "test" )) diff --git a/project/build.properties b/project/build.properties index b7dd3cb..9abea12 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.0.2 +sbt.version=1.0.3 diff --git a/src/main/scala/xyz/driver/restquery/db/SlickPostgresQueryBuilder.scala b/src/main/scala/xyz/driver/restquery/db/SlickPostgresQueryBuilder.scala index 7038aa0..0b3fece 100644 --- a/src/main/scala/xyz/driver/restquery/db/SlickPostgresQueryBuilder.scala +++ b/src/main/scala/xyz/driver/restquery/db/SlickPostgresQueryBuilder.scala @@ -1,10 +1,11 @@ -package xyz.driver.pdsuicommon.db +package xyz.driver.restquery.db import java.time.{LocalDateTime, ZoneOffset} import org.slf4j.LoggerFactory import slick.jdbc.{GetResult, JdbcProfile} import xyz.driver.core.database.SlickDal +import xyz.driver.restquery.query.{Pagination, SearchFilterExpr, Sorting} import scala.collection.breakOut import scala.concurrent.ExecutionContext @@ -12,18 +13,20 @@ import scala.concurrent.ExecutionContext object SlickPostgresQueryBuilder { private val logger = LoggerFactory.getLogger(this.getClass) - import xyz.driver.pdsuicommon.db.SlickQueryBuilder._ - - def apply[T](databaseName: String, - tableName: String, - lastUpdateFieldName: Option[String], - nullableFields: Set[String], - links: Set[SlickTableLink], - runner: Runner[T], - countRunner: CountRunner)(implicit sqlContext: SlickDal, - profile: JdbcProfile, - getResult: GetResult[T], - ec: ExecutionContext): SlickPostgresQueryBuilder[T] = { + import xyz.driver.restquery.db.SlickQueryBuilder._ + + def apply[T]( + databaseName: String, + tableName: String, + lastUpdateFieldName: Option[String], + nullableFields: Set[String], + links: Set[SlickTableLink], + runner: Runner[T], + countRunner: CountRunner)( + implicit sqlContext: SlickDal, + profile: JdbcProfile, + getResult: GetResult[T], + ec: ExecutionContext): SlickPostgresQueryBuilder[T] = { val parameters = SlickPostgresQueryBuilderParameters( databaseName = databaseName, tableData = TableData(tableName, lastUpdateFieldName, nullableFields), @@ -32,31 +35,30 @@ object SlickPostgresQueryBuilder { new SlickPostgresQueryBuilder[T](parameters)(runner, countRunner) } - def apply[T](databaseName: String, - tableName: String, - lastUpdateFieldName: Option[String], - nullableFields: Set[String], - links: Set[SlickTableLink])(implicit sqlContext: SlickDal, - profile: JdbcProfile, - getResult: GetResult[T], - ec: ExecutionContext): SlickPostgresQueryBuilder[T] = { - apply[T](databaseName, - tableName, - SlickQueryBuilderParameters.AllFields, - lastUpdateFieldName, - nullableFields, - links) + def apply[T]( + databaseName: String, + tableName: String, + lastUpdateFieldName: Option[String], + nullableFields: Set[String], + links: Set[SlickTableLink])( + implicit sqlContext: SlickDal, + profile: JdbcProfile, + getResult: GetResult[T], + ec: ExecutionContext): SlickPostgresQueryBuilder[T] = { + apply[T](databaseName, tableName, SlickQueryBuilderParameters.AllFields, lastUpdateFieldName, nullableFields, links) } - def apply[T](databaseName: String, - tableName: String, - fields: Set[String], - lastUpdateFieldName: Option[String], - nullableFields: Set[String], - links: Set[SlickTableLink])(implicit sqlContext: SlickDal, - profile: JdbcProfile, - getResult: GetResult[T], - ec: ExecutionContext): SlickPostgresQueryBuilder[T] = { + def apply[T]( + databaseName: String, + tableName: String, + fields: Set[String], + lastUpdateFieldName: Option[String], + nullableFields: Set[String], + links: Set[SlickTableLink])( + implicit sqlContext: SlickDal, + profile: JdbcProfile, + getResult: GetResult[T], + ec: ExecutionContext): SlickPostgresQueryBuilder[T] = { val runner: Runner[T] = { parameters => val sql = parameters.toSql(countQuery = false, fields = fields).as[T] @@ -94,8 +96,8 @@ object SlickPostgresQueryBuilder { } class SlickPostgresQueryBuilder[T](parameters: SlickPostgresQueryBuilderParameters)( - implicit runner: SlickQueryBuilder.Runner[T], - countRunner: SlickQueryBuilder.CountRunner) + implicit runner: SlickQueryBuilder.Runner[T], + countRunner: SlickQueryBuilder.CountRunner) extends SlickQueryBuilder[T](parameters) { def withFilter(newFilter: SearchFilterExpr): SlickQueryBuilder[T] = { diff --git a/src/main/scala/xyz/driver/restquery/db/SlickQueryBuilder.scala b/src/main/scala/xyz/driver/restquery/db/SlickQueryBuilder.scala index 9962edf..67ce9f4 100644 --- a/src/main/scala/xyz/driver/restquery/db/SlickQueryBuilder.scala +++ b/src/main/scala/xyz/driver/restquery/db/SlickQueryBuilder.scala @@ -1,12 +1,10 @@ -package xyz.driver.pdsuicommon.db +package xyz.driver.restquery.db import java.sql.{JDBCType, PreparedStatement} import java.time.LocalDateTime import slick.jdbc.{JdbcProfile, PositionedParameters, SQLActionBuilder, SetParameter} -import xyz.driver.pdsuicommon.db.Sorting.{Dimension, Sequential} -import xyz.driver.pdsuicommon.db.SortingOrder.{Ascending, Descending} -import xyz.driver.pdsuicommon.domain.{LongId, StringId, UuidId} +import xyz.driver.restquery.query._ import scala.concurrent.{ExecutionContext, Future} @@ -23,19 +21,18 @@ object SlickQueryBuilder { */ type Binder = PreparedStatement => PreparedStatement - final case class TableData(tableName: String, - lastUpdateFieldName: Option[String] = None, - nullableFields: Set[String] = Set.empty) + final case class TableData( + tableName: String, + lastUpdateFieldName: Option[String] = None, + nullableFields: Set[String] = Set.empty) val AllFields = Set("*") implicit class SQLActionBuilderConcat(a: SQLActionBuilder) { def concat(b: SQLActionBuilder): SQLActionBuilder = { - SQLActionBuilder(a.queryParts ++ b.queryParts, new SetParameter[Unit] { - def apply(p: Unit, pp: PositionedParameters): Unit = { - a.unitPConv.apply(p, pp) - b.unitPConv.apply(p, pp) - } + SQLActionBuilder(a.queryParts ++ b.queryParts, (p: Unit, pp: PositionedParameters) => { + a.unitPConv.apply(p, pp) + b.unitPConv.apply(p, pp) }) } } @@ -45,264 +42,17 @@ object SlickQueryBuilder { pp.setObject(v, JDBCType.BINARY.getVendorTypeNumber) } } - - implicit def setLongIdQueryParameter[T]: SetParameter[LongId[T]] = SetParameter[LongId[T]] { (v, pp) => - pp.setLong(v.id) - } - - implicit def setStringIdQueryParameter[T]: SetParameter[StringId[T]] = SetParameter[StringId[T]] { (v, pp) => - pp.setString(v.id) - } - - implicit def setUuidIdQueryParameter[T]: SetParameter[UuidId[T]] = SetParameter[UuidId[T]] { (v, pp) => - pp.setObject(v.id, JDBCType.BINARY.getVendorTypeNumber) - } } final case class SlickTableLink(keyColumnName: String, foreignTableName: String, foreignKeyColumnName: String) -object SlickQueryBuilderParameters { - val AllFields = Set("*") -} - -sealed trait SlickQueryBuilderParameters { - import SlickQueryBuilder._ - - def databaseName: String - def tableData: SlickQueryBuilder.TableData - def links: Map[String, SlickTableLink] - def filter: SearchFilterExpr - def sorting: Sorting - def pagination: Option[Pagination] - - def qs: String - - def findLink(tableName: String): SlickTableLink = links.get(tableName) match { - case None => throw new IllegalArgumentException(s"Cannot find a link for `$tableName`") - case Some(link) => link - } - - def toSql(countQuery: Boolean = false)(implicit profile: JdbcProfile): SQLActionBuilder = { - toSql(countQuery, SlickQueryBuilderParameters.AllFields) - } - - def toSql(countQuery: Boolean, fields: Set[String])(implicit profile: JdbcProfile): SQLActionBuilder = { - import profile.api._ - val escapedTableName = s"""$qs$databaseName$qs.$qs${tableData.tableName}$qs""" - val fieldsSql: String = if (countQuery) { - val suffix: String = tableData.lastUpdateFieldName match { - case Some(lastUpdateField) => s", max($escapedTableName.$qs$lastUpdateField$qs)" - case None => "" - } - s"count(*) $suffix" - } else { - if (fields == SlickQueryBuilderParameters.AllFields) { - s"$escapedTableName.*" - } else { - fields - .map { field => - s"$escapedTableName.$qs$field$qs" - } - .mkString(", ") - } - } - val where = filterToSql(escapedTableName, filter) - val orderBy = sortingToSql(escapedTableName, sorting) - - val limitSql = limitToSql() - - val sql = sql"""select #$fieldsSql from #$escapedTableName""" - - val filtersTableLinks: Seq[SlickTableLink] = { - import SearchFilterExpr._ - def aux(expr: SearchFilterExpr): Seq[SlickTableLink] = expr match { - case Atom.TableName(tableName) => List(findLink(tableName)) - case Intersection(xs) => xs.flatMap(aux) - case Union(xs) => xs.flatMap(aux) - case _ => Nil - } - aux(filter) - } - - val sortingTableLinks: Seq[SlickTableLink] = Sorting.collect(sorting) { - case Dimension(Some(foreignTableName), _, _) => findLink(foreignTableName) - } - - // Combine links from sorting and filter without duplicates - val foreignTableLinks = (filtersTableLinks ++ sortingTableLinks).distinct - - def fkSql(fkLinksSql: SQLActionBuilder, tableLinks: Seq[SlickTableLink]): SQLActionBuilder = { - if (tableLinks.nonEmpty) { - tableLinks.head match { - case SlickTableLink(keyColumnName, foreignTableName, foreignKeyColumnName) => - val escapedForeignTableName = s"$qs$databaseName$qs.$qs$foreignTableName$qs" - val join = sql""" inner join #$escapedForeignTableName - on #$escapedTableName.#$qs#$keyColumnName#$qs=#$escapedForeignTableName.#$qs#$foreignKeyColumnName#$qs""" - fkSql(fkLinksSql concat join, tableLinks.tail) - } - } else fkLinksSql - } - val foreignTableLinksSql = fkSql(sql"", foreignTableLinks) - - val whereSql = if (where.queryParts.size > 1) { - sql" where " concat where - } else sql"" - - val orderSql = if (orderBy.nonEmpty && !countQuery) { - sql" order by #$orderBy" - } else sql"" - - val limSql = if (limitSql.queryParts.size > 1 && !countQuery) { - sql" " concat limitSql - } else sql"" - - sql concat foreignTableLinksSql concat whereSql concat orderSql concat limSql - } - - /** - * Converts filter expression to SQL expression. - * - * @return Returns SQL string and list of values for binding in prepared statement. - */ - protected def filterToSql(escapedTableName: String, filter: SearchFilterExpr)( - implicit profile: JdbcProfile): SQLActionBuilder = { - import SearchFilterBinaryOperation._ - import SearchFilterExpr._ - import profile.api._ - - def isNull(string: AnyRef) = Option(string).isEmpty || string.toString.toLowerCase == "null" - - def escapeDimension(dimension: SearchFilterExpr.Dimension) = { - s"${dimension.tableName.map(t => s"$qs$databaseName$qs.$qs$t$qs").getOrElse(escapedTableName)}.$qs${dimension.name}$qs" - } - - def filterToSqlMultiple(operands: Seq[SearchFilterExpr]) = operands.collect { - case x if !SearchFilterExpr.isEmpty(x) => filterToSql(escapedTableName, x) - } - - def multipleSqlToAction(first: Boolean, - op: String, - conditions: Seq[SQLActionBuilder], - sql: SQLActionBuilder): SQLActionBuilder = { - if (conditions.nonEmpty) { - val condition = conditions.head - if (first) { - multipleSqlToAction(first = false, op, conditions.tail, condition) - } else { - multipleSqlToAction(first = false, op, conditions.tail, sql concat sql" #${op} " concat condition) - } - } else sql - } - - def concatenateParameters(sql: SQLActionBuilder, first: Boolean, tail: Seq[AnyRef]): SQLActionBuilder = { - if (tail.nonEmpty) { - if (!first) { - concatenateParameters(sql concat sql""",${tail.head}""", first = false, tail.tail) - } else { - concatenateParameters(sql"""(${tail.head}""", first = false, tail.tail) - } - } else sql concat sql")" - } - - filter match { - case x if isEmpty(x) => - sql"" - - case AllowAll => - sql"1=1" - - case DenyAll => - sql"1=0" - - case Atom.Binary(dimension, Eq, value) if isNull(value) => - sql"#${escapeDimension(dimension)} is NULL" - - case Atom.Binary(dimension, NotEq, value) if isNull(value) => - sql"#${escapeDimension(dimension)} is not NULL" - - case Atom.Binary(dimension, NotEq, value) if tableData.nullableFields.contains(dimension.name) => - // In MySQL NULL <> Any === NULL - // So, to handle NotEq for nullable fields we need to use more complex SQL expression. - // http://dev.mysql.com/doc/refman/5.7/en/working-with-null.html - val escapedColumn = escapeDimension(dimension) - sql"(#${escapedColumn} is null or #${escapedColumn} != $value)" - - case Atom.Binary(dimension, op, value) => - val operator = op match { - case Eq => sql"=" - case NotEq => sql"!=" - case Like => sql" like " - case Gt => sql">" - case GtEq => sql">=" - case Lt => sql"<" - case LtEq => sql"<=" - } - sql"#${escapeDimension(dimension)}" concat operator concat sql"""$value""" - - case Atom.NAry(dimension, op, values) => - val sqlOp = op match { - case SearchFilterNAryOperation.In => sql" in " - case SearchFilterNAryOperation.NotIn => sql" not in " - } - - if (values.nonEmpty) { - val formattedValues = concatenateParameters(sql"", first = true, values) - sql"#${escapeDimension(dimension)}" concat sqlOp concat formattedValues - } else { - sql"1=0" - } - - case Intersection(operands) => - val filter = multipleSqlToAction(first = true, "and", filterToSqlMultiple(operands), sql"") - sql"(" concat filter concat sql")" - - case Union(operands) => - val filter = multipleSqlToAction(first = true, "or", filterToSqlMultiple(operands), sql"") - sql"(" concat filter concat sql")" - } - } - - protected def limitToSql()(implicit profile: JdbcProfile): SQLActionBuilder - - /** - * @param escapedMainTableName Should be escaped - */ - protected def sortingToSql(escapedMainTableName: String, sorting: Sorting)(implicit profile: JdbcProfile): String = { - sorting match { - case Dimension(optSortingTableName, field, order) => - val sortingTableName = - optSortingTableName.map(x => s"$qs$databaseName$qs.$qs$x$qs").getOrElse(escapedMainTableName) - val fullName = s"$sortingTableName.$qs$field$qs" - - s"$fullName ${orderToSql(order)}" - - case Sequential(xs) => - xs.map(sortingToSql(escapedMainTableName, _)).mkString(", ") - } - } - - protected def orderToSql(x: SortingOrder): String = x match { - case Ascending => "asc" - case Descending => "desc" - } - - protected def binder(bindings: List[AnyRef])(bind: PreparedStatement): PreparedStatement = { - bindings.zipWithIndex.foreach { - case (binding, index) => - bind.setObject(index + 1, binding) - } - - bind - } - -} - -final case class SlickPostgresQueryBuilderParameters(databaseName: String, - tableData: SlickQueryBuilder.TableData, - links: Map[String, SlickTableLink] = Map.empty, - filter: SearchFilterExpr = SearchFilterExpr.Empty, - sorting: Sorting = Sorting.Empty, - pagination: Option[Pagination] = None) +final case class SlickPostgresQueryBuilderParameters( + databaseName: String, + tableData: SlickQueryBuilder.TableData, + links: Map[String, SlickTableLink] = Map.empty, + filter: SearchFilterExpr = SearchFilterExpr.Empty, + sorting: Sorting = Sorting.Empty, + pagination: Option[Pagination] = None) extends SlickQueryBuilderParameters { def limitToSql()(implicit profile: JdbcProfile): SQLActionBuilder = { @@ -320,12 +70,13 @@ final case class SlickPostgresQueryBuilderParameters(databaseName: String, /** * @param links Links to another tables grouped by foreignTableName */ -final case class SlickMysqlQueryBuilderParameters(databaseName: String, - tableData: SlickQueryBuilder.TableData, - links: Map[String, SlickTableLink] = Map.empty, - filter: SearchFilterExpr = SearchFilterExpr.Empty, - sorting: Sorting = Sorting.Empty, - pagination: Option[Pagination] = None) +final case class SlickMysqlQueryBuilderParameters( + databaseName: String, + tableData: SlickQueryBuilder.TableData, + links: Map[String, SlickTableLink] = Map.empty, + filter: SearchFilterExpr = SearchFilterExpr.Empty, + sorting: Sorting = Sorting.Empty, + pagination: Option[Pagination] = None) extends SlickQueryBuilderParameters { def limitToSql()(implicit profile: JdbcProfile): SQLActionBuilder = { @@ -343,8 +94,8 @@ final case class SlickMysqlQueryBuilderParameters(databaseName: String, } abstract class SlickQueryBuilder[T](val parameters: SlickQueryBuilderParameters)( - implicit runner: SlickQueryBuilder.Runner[T], - countRunner: SlickQueryBuilder.CountRunner) { + implicit runner: SlickQueryBuilder.Runner[T], + countRunner: SlickQueryBuilder.CountRunner) { def run()(implicit ec: ExecutionContext): Future[Seq[T]] = runner(parameters) diff --git a/src/main/scala/xyz/driver/restquery/db/SlickQueryBuilderParameters.scala b/src/main/scala/xyz/driver/restquery/db/SlickQueryBuilderParameters.scala index 6ab7eb4..d4d1761 100644 --- a/src/main/scala/xyz/driver/restquery/db/SlickQueryBuilderParameters.scala +++ b/src/main/scala/xyz/driver/restquery/db/SlickQueryBuilderParameters.scala @@ -1,11 +1,15 @@ -package xyz.driver.restquery.query +package xyz.driver.restquery.db import java.sql.PreparedStatement import slick.jdbc.{JdbcProfile, SQLActionBuilder} -import xyz.driver.restquery.db.{SlickQueryBuilder, SlickQueryBuilderParameters, SlickTableLink} import xyz.driver.restquery.query.Sorting.{Dimension, Sequential} import xyz.driver.restquery.query.SortingOrder.{Ascending, Descending} +import xyz.driver.restquery.query._ + +object SlickQueryBuilderParameters { + val AllFields = Set("*") +} trait SlickQueryBuilderParameters { import SlickQueryBuilder._ @@ -107,7 +111,7 @@ trait SlickQueryBuilderParameters { * @return Returns SQL string and list of values for binding in prepared statement. */ protected def filterToSql(escapedTableName: String, filter: SearchFilterExpr)( - implicit profile: JdbcProfile): SQLActionBuilder = { + implicit profile: JdbcProfile): SQLActionBuilder = { import SearchFilterBinaryOperation._ import SearchFilterExpr._ import profile.api._ @@ -122,10 +126,11 @@ trait SlickQueryBuilderParameters { case x if !SearchFilterExpr.isEmpty(x) => filterToSql(escapedTableName, x) } - def multipleSqlToAction(first: Boolean, - op: String, - conditions: Seq[SQLActionBuilder], - sql: SQLActionBuilder): SQLActionBuilder = { + def multipleSqlToAction( + first: Boolean, + op: String, + conditions: Seq[SQLActionBuilder], + sql: SQLActionBuilder): SQLActionBuilder = { if (conditions.nonEmpty) { val condition = conditions.head if (first) { diff --git a/src/main/scala/xyz/driver/restquery/query/Pagination.scala b/src/main/scala/xyz/driver/restquery/query/Pagination.scala index 27b8f12..b899d1b 100644 --- a/src/main/scala/xyz/driver/restquery/query/Pagination.scala +++ b/src/main/scala/xyz/driver/restquery/query/Pagination.scala @@ -1,4 +1,4 @@ -package xyz.driver.restquery.domain +package xyz.driver.restquery.query /** * @param pageNumber Starts with 1 diff --git a/src/main/scala/xyz/driver/restquery/query/SearchFilterBinaryOperation.scala b/src/main/scala/xyz/driver/restquery/query/SearchFilterBinaryOperation.scala index dab466b..30d210c 100644 --- a/src/main/scala/xyz/driver/restquery/query/SearchFilterBinaryOperation.scala +++ b/src/main/scala/xyz/driver/restquery/query/SearchFilterBinaryOperation.scala @@ -1,5 +1,15 @@ package xyz.driver.restquery.query -class SearchFilterBinaryOperation { +sealed trait SearchFilterBinaryOperation + +object SearchFilterBinaryOperation { + + case object Eq extends SearchFilterBinaryOperation + case object NotEq extends SearchFilterBinaryOperation + case object Like extends SearchFilterBinaryOperation + case object Gt extends SearchFilterBinaryOperation + case object GtEq extends SearchFilterBinaryOperation + case object Lt extends SearchFilterBinaryOperation + case object LtEq extends SearchFilterBinaryOperation } diff --git a/src/main/scala/xyz/driver/restquery/query/SearchFilterExpr.scala b/src/main/scala/xyz/driver/restquery/query/SearchFilterExpr.scala index 6d2cb9a..8cbf685 100644 --- a/src/main/scala/xyz/driver/restquery/query/SearchFilterExpr.scala +++ b/src/main/scala/xyz/driver/restquery/query/SearchFilterExpr.scala @@ -1,4 +1,4 @@ -package xyz.driver.restquery.domain +package xyz.driver.restquery.query sealed trait SearchFilterExpr { def find(p: SearchFilterExpr => Boolean): Option[SearchFilterExpr] @@ -171,26 +171,3 @@ object SearchFilterExpr { } } - -sealed trait SearchFilterBinaryOperation - -object SearchFilterBinaryOperation { - - case object Eq extends SearchFilterBinaryOperation - case object NotEq extends SearchFilterBinaryOperation - case object Like extends SearchFilterBinaryOperation - case object Gt extends SearchFilterBinaryOperation - case object GtEq extends SearchFilterBinaryOperation - case object Lt extends SearchFilterBinaryOperation - case object LtEq extends SearchFilterBinaryOperation - -} - -sealed trait SearchFilterNAryOperation - -object SearchFilterNAryOperation { - - case object In extends SearchFilterNAryOperation - case object NotIn extends SearchFilterNAryOperation - -} diff --git a/src/main/scala/xyz/driver/restquery/query/SearchFilterNAryOperation.scala b/src/main/scala/xyz/driver/restquery/query/SearchFilterNAryOperation.scala index 0604c8f..a388597 100644 --- a/src/main/scala/xyz/driver/restquery/query/SearchFilterNAryOperation.scala +++ b/src/main/scala/xyz/driver/restquery/query/SearchFilterNAryOperation.scala @@ -1,5 +1,10 @@ package xyz.driver.restquery.query -class SearchFilterNAryOperation { +sealed trait SearchFilterNAryOperation + +object SearchFilterNAryOperation { + + case object In extends SearchFilterNAryOperation + case object NotIn extends SearchFilterNAryOperation } diff --git a/src/main/scala/xyz/driver/restquery/query/Sorting.scala b/src/main/scala/xyz/driver/restquery/query/Sorting.scala index e2642ad..af0066b 100644 --- a/src/main/scala/xyz/driver/restquery/query/Sorting.scala +++ b/src/main/scala/xyz/driver/restquery/query/Sorting.scala @@ -1,4 +1,4 @@ -package xyz.driver.restquery.domain +package xyz.driver.restquery.query import scala.collection.generic.CanBuildFrom @@ -43,7 +43,7 @@ object Sorting { } def collect[B, That](sorting: Sorting)(f: PartialFunction[Dimension, B])( - implicit bf: CanBuildFrom[Seq[Dimension], B, That]): That = sorting match { + implicit bf: CanBuildFrom[Seq[Dimension], B, That]): That = sorting match { case x: Dimension if f.isDefinedAt(x) => val r = bf.apply() r += f(x) diff --git a/src/main/scala/xyz/driver/restquery/rest/Directives.scala b/src/main/scala/xyz/driver/restquery/rest/Directives.scala index 2936f70..a8dcb3b 100644 --- a/src/main/scala/xyz/driver/restquery/rest/Directives.scala +++ b/src/main/scala/xyz/driver/restquery/rest/Directives.scala @@ -1,9 +1,9 @@ -package xyz.driver.restquery.http +package xyz.driver.restquery.rest import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server._ -import xyz.driver.restquery.domain.{SearchFilterExpr, _} -import xyz.driver.restquery.http.parsers._ +import xyz.driver.restquery.query.{SearchFilterExpr, _} +import xyz.driver.restquery.rest.parsers._ import scala.util._ @@ -41,15 +41,6 @@ trait Directives { } } - def StringIdInPath[T]: PathMatcher1[StringId[T]] = - PathMatchers.Segment.map((id) => StringId(id.toString)) - - def LongIdInPath[T]: PathMatcher1[LongId[T]] = - PathMatchers.LongNumber.map((id) => LongId(id)) - - def UuidIdInPath[T]: PathMatcher1[UuidId[T]] = - PathMatchers.JavaUUID.map((id) => UuidId(id)) - } object Directives extends Directives diff --git a/src/main/scala/xyz/driver/restquery/rest/parsers/DimensionsParser.scala b/src/main/scala/xyz/driver/restquery/rest/parsers/DimensionsParser.scala index 7e139db..3472e40 100644 --- a/src/main/scala/xyz/driver/restquery/rest/parsers/DimensionsParser.scala +++ b/src/main/scala/xyz/driver/restquery/rest/parsers/DimensionsParser.scala @@ -1,4 +1,4 @@ -package xyz.driver.restquery.http.parsers +package xyz.driver.restquery.rest.parsers import scala.util.{Failure, Success, Try} diff --git a/src/main/scala/xyz/driver/restquery/rest/parsers/PaginationParser.scala b/src/main/scala/xyz/driver/restquery/rest/parsers/PaginationParser.scala index 2b4547b..6d8aaf8 100644 --- a/src/main/scala/xyz/driver/restquery/rest/parsers/PaginationParser.scala +++ b/src/main/scala/xyz/driver/restquery/rest/parsers/PaginationParser.scala @@ -1,6 +1,6 @@ -package xyz.driver.restquery.http.parsers +package xyz.driver.restquery.rest.parsers -import xyz.driver.restquery.domain.Pagination +import xyz.driver.restquery.query.Pagination import scala.util._ @@ -16,8 +16,9 @@ object PaginationParser { } Try { - Pagination(validate("pageSize", Pagination.Default.pageSize), - validate("pageNumber", Pagination.Default.pageNumber)) + Pagination( + validate("pageSize", Pagination.Default.pageSize), + validate("pageNumber", Pagination.Default.pageNumber)) } } } diff --git a/src/main/scala/xyz/driver/restquery/rest/parsers/ParseQueryArgException.scala b/src/main/scala/xyz/driver/restquery/rest/parsers/ParseQueryArgException.scala index 096c28f..456329e 100644 --- a/src/main/scala/xyz/driver/restquery/rest/parsers/ParseQueryArgException.scala +++ b/src/main/scala/xyz/driver/restquery/rest/parsers/ParseQueryArgException.scala @@ -1,3 +1,3 @@ -package xyz.driver.restquery.http.parsers +package xyz.driver.restquery.rest.parsers class ParseQueryArgException(val errors: (String, String)*) extends Exception(errors.mkString(",")) diff --git a/src/main/scala/xyz/driver/restquery/rest/parsers/SearchFilterParser.scala b/src/main/scala/xyz/driver/restquery/rest/parsers/SearchFilterParser.scala index ce3009b..52a31bf 100644 --- a/src/main/scala/xyz/driver/restquery/rest/parsers/SearchFilterParser.scala +++ b/src/main/scala/xyz/driver/restquery/rest/parsers/SearchFilterParser.scala @@ -1,10 +1,10 @@ -package xyz.driver.restquery.http.parsers +package xyz.driver.restquery.rest.parsers import java.util.UUID import fastparse.all._ import fastparse.core.Parsed -import xyz.driver.restquery.domain.{SearchFilterBinaryOperation, SearchFilterExpr, SearchFilterNAryOperation} +import xyz.driver.restquery.query.{SearchFilterBinaryOperation, SearchFilterExpr, SearchFilterNAryOperation} import xyz.driver.restquery.utils.Utils import xyz.driver.restquery.utils.Utils._ @@ -47,7 +47,7 @@ object SearchFilterParser { } private val operationsMapping = { - import xyz.driver.restquery.domain.SearchFilterBinaryOperation._ + import xyz.driver.restquery.query.SearchFilterBinaryOperation._ Map[String, SearchFilterBinaryOperation]( "eq" -> Eq, diff --git a/src/main/scala/xyz/driver/restquery/rest/parsers/SortingParser.scala b/src/main/scala/xyz/driver/restquery/rest/parsers/SortingParser.scala index f2a3c04..fce18d1 100644 --- a/src/main/scala/xyz/driver/restquery/rest/parsers/SortingParser.scala +++ b/src/main/scala/xyz/driver/restquery/rest/parsers/SortingParser.scala @@ -1,8 +1,8 @@ -package xyz.driver.restquery.http.parsers +package xyz.driver.restquery.rest.parsers import fastparse.all._ import fastparse.core.Parsed -import xyz.driver.restquery.domain.{Sorting, SortingOrder} +import xyz.driver.restquery.query.{Sorting, SortingOrder} import xyz.driver.restquery.utils.Utils._ import scala.util.Try @@ -21,9 +21,10 @@ object SortingParser { prefixedFields.size match { case 1 => Sorting.Dimension(None, toSnakeCase(field), sortingOrder) case 2 => - Sorting.Dimension(Some(prefixedFields.head).map(toSnakeCase), - toSnakeCase(prefixedFields.last), - sortingOrder) + Sorting.Dimension( + Some(prefixedFields.head).map(toSnakeCase), + toSnakeCase(prefixedFields.last), + sortingOrder) } } } diff --git a/src/main/scala/xyz/driver/restquery/utils/Utils.scala b/src/main/scala/xyz/driver/restquery/utils/Utils.scala index 86c65d7..827f2a0 100644 --- a/src/main/scala/xyz/driver/restquery/utils/Utils.scala +++ b/src/main/scala/xyz/driver/restquery/utils/Utils.scala @@ -1,4 +1,4 @@ -package xyz.driver.pdsuicommon.utils +package xyz.driver.restquery.utils import java.time.LocalDateTime import java.util.regex.{Matcher, Pattern} @@ -59,7 +59,6 @@ object Utils { def isSafeControl(char: Char): Boolean = char <= '\u001f' || (char >= '\u007f' && char <= '\u009f') - def safeTrim(string: String): String = { def shouldKeep(c: Char): Boolean = !isSafeControl(c) && !isSafeWhitespace(c) diff --git a/src/test/scala/xyz/driver/restquery/db/SearchFilterExprSuite.scala b/src/test/scala/xyz/driver/restquery/db/SearchFilterExprSuite.scala index 35c8d30..56ad545 100644 --- a/src/test/scala/xyz/driver/restquery/db/SearchFilterExprSuite.scala +++ b/src/test/scala/xyz/driver/restquery/db/SearchFilterExprSuite.scala @@ -1,6 +1,7 @@ -package xyz.driver.pdsuicommon.db +package xyz.driver.restquery.db import org.scalatest.{FreeSpecLike, MustMatchers} +import xyz.driver.restquery.query.{SearchFilterBinaryOperation, SearchFilterExpr, SearchFilterNAryOperation} class SearchFilterExprSuite extends FreeSpecLike with MustMatchers { diff --git a/src/test/scala/xyz/driver/restquery/rest/parsers/PaginationParserSuite.scala b/src/test/scala/xyz/driver/restquery/rest/parsers/PaginationParserSuite.scala index c753480..e806f85 100644 --- a/src/test/scala/xyz/driver/restquery/rest/parsers/PaginationParserSuite.scala +++ b/src/test/scala/xyz/driver/restquery/rest/parsers/PaginationParserSuite.scala @@ -1,8 +1,8 @@ -package xyz.driver.restquery.http.parsers +package xyz.driver.restquery.rest.parsers -import xyz.driver.restquery.http.parsers.TestUtils._ +import xyz.driver.restquery.rest.parsers.TestUtils._ import org.scalatest.{FreeSpecLike, MustMatchers} -import xyz.driver.restquery.domain.Pagination +import xyz.driver.restquery.query.Pagination import scala.util.{Failure, Try} diff --git a/src/test/scala/xyz/driver/restquery/rest/parsers/SearchFilterParserSuite.scala b/src/test/scala/xyz/driver/restquery/rest/parsers/SearchFilterParserSuite.scala index efa8666..e0a1696 100644 --- a/src/test/scala/xyz/driver/restquery/rest/parsers/SearchFilterParserSuite.scala +++ b/src/test/scala/xyz/driver/restquery/rest/parsers/SearchFilterParserSuite.scala @@ -1,4 +1,4 @@ -package xyz.driver.restquery.http.parsers +package xyz.driver.restquery.rest.parsers import java.util.UUID @@ -7,11 +7,11 @@ import org.scalacheck.Arbitrary.arbitrary import org.scalacheck.{Gen, Prop} import org.scalatest.FreeSpecLike import org.scalatest.prop.Checkers -import xyz.driver.restquery.domain.SearchFilterBinaryOperation.Eq -import xyz.driver.restquery.domain.SearchFilterExpr.Dimension -import xyz.driver.restquery.domain.SearchFilterNAryOperation.In -import xyz.driver.restquery.domain.{SearchFilterExpr, SearchFilterNAryOperation} -import xyz.driver.restquery.http.parsers.TestUtils._ +import xyz.driver.restquery.query.SearchFilterBinaryOperation.Eq +import xyz.driver.restquery.query.SearchFilterExpr.Dimension +import xyz.driver.restquery.query.SearchFilterNAryOperation.In +import xyz.driver.restquery.query.{SearchFilterExpr, SearchFilterNAryOperation} +import xyz.driver.restquery.rest.parsers.TestUtils._ import xyz.driver.restquery.utils.Utils import xyz.driver.restquery.utils.Utils._ @@ -29,7 +29,7 @@ class SearchFilterParserSuite extends FreeSpecLike with Checkers { "parse" - { "should convert column names to snake case" in { - import xyz.driver.restquery.domain.SearchFilterBinaryOperation._ + import xyz.driver.restquery.query.SearchFilterBinaryOperation._ val filter = SearchFilterParser.parse( Seq( @@ -77,7 +77,7 @@ class SearchFilterParserSuite extends FreeSpecLike with Checkers { "binary" - { "common operators" - { "should be parsed with text values" in check { - import xyz.driver.restquery.domain.SearchFilterBinaryOperation._ + import xyz.driver.restquery.query.SearchFilterBinaryOperation._ val testQueryGen = queryGen( dimensionGen = Gen.identifier, @@ -123,15 +123,17 @@ class SearchFilterParserSuite extends FreeSpecLike with Checkers { "actual isVisible boolean" - { "should not be parsed with boolean values" in { val filter = SearchFilterParser.parse(Seq("filters" -> "isVisible EQ true")) - assert(filter === Success(SearchFilterExpr.Atom.Binary(Dimension(None, "is_visible"), Eq, Boolean.box(true)))) + assert( + filter === Success(SearchFilterExpr.Atom.Binary(Dimension(None, "is_visible"), Eq, Boolean.box(true)))) } } "actual patientId uuid" - { "should parse the full UUID as java.util.UUID type" in { val filter = SearchFilterParser.parse(Seq("filters" -> "patientId EQ 4b4879f7-42b3-4b7c-a685-5c97d9e69e7c")) - assert(filter === Success(SearchFilterExpr.Atom.Binary( - Dimension(None, "patient_id"), Eq, UUID.fromString("4b4879f7-42b3-4b7c-a685-5c97d9e69e7c")))) + assert( + filter === Success(SearchFilterExpr.Atom + .Binary(Dimension(None, "patient_id"), Eq, UUID.fromString("4b4879f7-42b3-4b7c-a685-5c97d9e69e7c")))) } } diff --git a/src/test/scala/xyz/driver/restquery/rest/parsers/SortingParserSuite.scala b/src/test/scala/xyz/driver/restquery/rest/parsers/SortingParserSuite.scala index 1813181..a18ac1e 100644 --- a/src/test/scala/xyz/driver/restquery/rest/parsers/SortingParserSuite.scala +++ b/src/test/scala/xyz/driver/restquery/rest/parsers/SortingParserSuite.scala @@ -1,6 +1,6 @@ -package xyz.driver.restquery.http.parsers +package xyz.driver.restquery.rest.parsers -import xyz.driver.restquery.http.parsers.TestUtils._ +import xyz.driver.restquery.rest.parsers.TestUtils._ import org.scalacheck.Arbitrary.arbitrary import org.scalacheck.{Gen, Prop} import org.scalatest.prop.Checkers diff --git a/src/test/scala/xyz/driver/restquery/rest/parsers/TestUtils.scala b/src/test/scala/xyz/driver/restquery/rest/parsers/TestUtils.scala index 9ea75b6..22ce5a0 100644 --- a/src/test/scala/xyz/driver/restquery/rest/parsers/TestUtils.scala +++ b/src/test/scala/xyz/driver/restquery/rest/parsers/TestUtils.scala @@ -1,7 +1,6 @@ -package xyz.driver.restquery.http.parsers +package xyz.driver.restquery.rest.parsers -import -org.scalacheck.Prop +import org.scalacheck.Prop import org.scalacheck.Prop.BooleanOperators import org.scalatest.matchers.{MatchResult, Matcher} import xyz.driver.restquery.utils.Utils diff --git a/src/test/scala/xyz/driver/restquery/utils/StringOpsSuite.scala b/src/test/scala/xyz/driver/restquery/utils/StringOpsSuite.scala index bd0bd2b..97a432f 100644 --- a/src/test/scala/xyz/driver/restquery/utils/StringOpsSuite.scala +++ b/src/test/scala/xyz/driver/restquery/utils/StringOpsSuite.scala @@ -1,33 +1,32 @@ -package xyz.driver.pdsuicommon.utils +package xyz.driver.restquery.utils -import xyz.driver.pdsuicommon.utils.Implicits.toStringOps import org.scalatest.FreeSpecLike class StringOpsSuite extends FreeSpecLike { "safeTrim" - { "empty string" in { - assert("".safeTrim == "") + assert(Utils.safeTrim("") == "") } "string with whitespace symbols" in { - assert("\u2002\u3000\r\u0085\u200A\u2005\u2000\u3000".safeTrim == "") + assert(Utils.safeTrim("\u2002\u3000\r\u0085\u200A\u2005\u2000\u3000") == "") } "string with control symbols" in { - assert("\u001f\u007f\t\n".safeTrim == "") + assert(Utils.safeTrim("\u001f\u007f\t\n") == "") } "whitespaces and control symbols from the left side" in { - assert("\u001f\u2002\u007f\nfoo".safeTrim == "foo") + assert(Utils.safeTrim("\u001f\u2002\u007f\nfoo") == "foo") } "whitespaces and control symbols from the right side" in { - assert("foo\u001f\u2002\u007f\n".safeTrim == "foo") + assert(Utils.safeTrim("foo\u001f\u2002\u007f\n") == "foo") } "already trimmed string" in { - assert("foo".safeTrim == "foo") + assert(Utils.safeTrim("foo") == "foo") } } } -- cgit v1.2.3