aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/xyz/driver/common/db
diff options
context:
space:
mode:
authorvlad <vlad@driver.xyz>2017-06-13 16:12:20 -0700
committervlad <vlad@driver.xyz>2017-06-13 16:12:20 -0700
commitcd1b635b2ae90d9ac2d8b1779183a1fbd8c5fd5c (patch)
tree062e8dad1a1513e26b0fd08b1742d6ff2ee874f7 /src/main/scala/xyz/driver/common/db
parent0000a65ab4479a2a40e2d6468036438e9705b4aa (diff)
downloadrest-query-cd1b635b2ae90d9ac2d8b1779183a1fbd8c5fd5c.tar.gz
rest-query-cd1b635b2ae90d9ac2d8b1779183a1fbd8c5fd5c.tar.bz2
rest-query-cd1b635b2ae90d9ac2d8b1779183a1fbd8c5fd5c.zip
Adding domain entitiesv0.1.0
Diffstat (limited to 'src/main/scala/xyz/driver/common/db')
-rw-r--r--src/main/scala/xyz/driver/common/db/DbCommand.scala15
-rw-r--r--src/main/scala/xyz/driver/common/db/DbCommandFactory.scala14
-rw-r--r--src/main/scala/xyz/driver/common/db/EntityExtractorDerivation.scala71
-rw-r--r--src/main/scala/xyz/driver/common/db/EntityNotFoundException.scala10
-rw-r--r--src/main/scala/xyz/driver/common/db/MysqlQueryBuilder.scala90
-rw-r--r--src/main/scala/xyz/driver/common/db/Pagination.scala20
-rw-r--r--src/main/scala/xyz/driver/common/db/QueryBuilder.scala344
-rw-r--r--src/main/scala/xyz/driver/common/db/SearchFilterExpr.scala210
-rw-r--r--src/main/scala/xyz/driver/common/db/Sorting.scala62
-rw-r--r--src/main/scala/xyz/driver/common/db/SqlContext.scala184
-rw-r--r--src/main/scala/xyz/driver/common/db/Transactions.scala23
-rw-r--r--src/main/scala/xyz/driver/common/db/repositories/BridgeUploadQueueRepository.scala24
-rw-r--r--src/main/scala/xyz/driver/common/db/repositories/Repository.scala4
-rw-r--r--src/main/scala/xyz/driver/common/db/repositories/RepositoryLogging.scala62
14 files changed, 0 insertions, 1133 deletions
diff --git a/src/main/scala/xyz/driver/common/db/DbCommand.scala b/src/main/scala/xyz/driver/common/db/DbCommand.scala
deleted file mode 100644
index fec8b9f..0000000
--- a/src/main/scala/xyz/driver/common/db/DbCommand.scala
+++ /dev/null
@@ -1,15 +0,0 @@
-package xyz.driver.common.db
-
-import scala.concurrent.Future
-
-trait DbCommand {
- def runSync(): Unit
- def runAsync(transactions: Transactions): Future[Unit]
-}
-
-object DbCommand {
- val Empty: DbCommand = new DbCommand {
- override def runSync(): Unit = {}
- override def runAsync(transactions: Transactions): Future[Unit] = Future.successful(())
- }
-}
diff --git a/src/main/scala/xyz/driver/common/db/DbCommandFactory.scala b/src/main/scala/xyz/driver/common/db/DbCommandFactory.scala
deleted file mode 100644
index 84c1383..0000000
--- a/src/main/scala/xyz/driver/common/db/DbCommandFactory.scala
+++ /dev/null
@@ -1,14 +0,0 @@
-package xyz.driver.common.db
-
-import scala.concurrent.{ExecutionContext, Future}
-
-trait DbCommandFactory[T] {
- def createCommand(orig: T)(implicit ec: ExecutionContext): Future[DbCommand]
-}
-
-object DbCommandFactory {
- def empty[T]: DbCommandFactory[T] = new DbCommandFactory[T] {
- override def createCommand(orig: T)(implicit ec: ExecutionContext): Future[DbCommand] = Future.successful(DbCommand.Empty)
- }
-}
-
diff --git a/src/main/scala/xyz/driver/common/db/EntityExtractorDerivation.scala b/src/main/scala/xyz/driver/common/db/EntityExtractorDerivation.scala
deleted file mode 100644
index 0396ea5..0000000
--- a/src/main/scala/xyz/driver/common/db/EntityExtractorDerivation.scala
+++ /dev/null
@@ -1,71 +0,0 @@
-package xyz.driver.common.db
-
-import java.sql.ResultSet
-
-import io.getquill.NamingStrategy
-import io.getquill.dsl.EncodingDsl
-
-import scala.language.experimental.macros
-import scala.reflect.macros.blackbox
-
-trait EntityExtractorDerivation[Naming <: NamingStrategy] {
- this: EncodingDsl =>
-
- /**
- * Simple Quill extractor derivation for [[T]]
- * Only case classes available. Type parameters is not supported
- *
- * @tparam T
- * @return
- */
- def entityExtractor[T]: (ResultSet => T) = macro EntityExtractorDerivation.impl[T]
-}
-
-object EntityExtractorDerivation {
- def impl[T: c.WeakTypeTag](c: blackbox.Context): c.Tree = {
- import c.universe._
- val namingStrategy = c.prefix.actualType
- .baseType(c.weakTypeOf[EntityExtractorDerivation[NamingStrategy]].typeSymbol)
- .typeArgs
- .head
- .typeSymbol
- .companion
- val functionBody = {
- val tpe = weakTypeOf[T]
- val resultOpt = tpe.decls.collectFirst {
- // Find first constructor of T
- case cons: MethodSymbol if cons.isConstructor =>
- // Create param list for constructor
- val params = cons.paramLists.flatten.map { param =>
- val t = param.typeSignature
- val paramName = param.name.toString
- val col = q"$namingStrategy.column($paramName)"
- // Resolve implicit decoders (from SqlContext) and apply ResultSet for each
- val d = q"implicitly[${c.prefix}.Decoder[$t]]"
- // Minus 1 cause Quill JDBC decoders make plus one.
- // ¯\_(ツ)_/¯
- val i = q"row.findColumn($col) - 1"
- val decoderName = TermName(paramName + "Decoder")
- val valueName = TermName(paramName + "Value")
- (
- q"val $decoderName = $d",
- q"val $valueName = $decoderName($i, row)",
- valueName
- )
- }
- // Call constructor with param list
- q"""
- ..${params.map(_._1)}
- ..${params.map(_._2)}
- new $tpe(..${params.map(_._3)})
- """
- }
- resultOpt match {
- case Some(result) => result
- case None => c.abort(c.enclosingPosition,
- s"Can not derive extractor for $tpe. Constructor not found.")
- }
- }
- q"(row: java.sql.ResultSet) => $functionBody"
- }
-}
diff --git a/src/main/scala/xyz/driver/common/db/EntityNotFoundException.scala b/src/main/scala/xyz/driver/common/db/EntityNotFoundException.scala
deleted file mode 100644
index d4c11ac..0000000
--- a/src/main/scala/xyz/driver/common/db/EntityNotFoundException.scala
+++ /dev/null
@@ -1,10 +0,0 @@
-package xyz.driver.common.db
-
-import xyz.driver.common.domain.Id
-
-class EntityNotFoundException private(id: String, tableName: String)
- extends RuntimeException(s"Entity with id $id is not found in $tableName table") {
-
- def this(id: Id[_], tableName: String) = this(id.toString, tableName)
- def this(id: Long, tableName: String) = this(id.toString, tableName)
-}
diff --git a/src/main/scala/xyz/driver/common/db/MysqlQueryBuilder.scala b/src/main/scala/xyz/driver/common/db/MysqlQueryBuilder.scala
deleted file mode 100644
index d6b53d9..0000000
--- a/src/main/scala/xyz/driver/common/db/MysqlQueryBuilder.scala
+++ /dev/null
@@ -1,90 +0,0 @@
-package xyz.driver.common.db
-
-import java.sql.ResultSet
-
-import io.getquill.{MySQLDialect, MysqlEscape}
-
-import scala.collection.breakOut
-import scala.concurrent.{ExecutionContext, Future}
-
-object MysqlQueryBuilder {
- import xyz.driver.common.db.QueryBuilder._
-
- def apply[T](tableName: String,
- lastUpdateFieldName: Option[String],
- nullableFields: Set[String],
- links: Set[TableLink],
- runner: Runner[T],
- countRunner: CountRunner)
- (implicit ec: ExecutionContext): MysqlQueryBuilder[T] = {
- val parameters = MysqlQueryBuilderParameters(
- tableData = TableData(tableName, lastUpdateFieldName, nullableFields),
- links = links.map(x => x.foreignTableName -> x)(breakOut)
- )
- new MysqlQueryBuilder[T](parameters)(runner, countRunner, ec)
- }
-
- def apply[T](tableName: String,
- lastUpdateFieldName: Option[String],
- nullableFields: Set[String],
- links: Set[TableLink],
- extractor: (ResultSet) => T)
- (implicit sqlContext: SqlContext): MysqlQueryBuilder[T] = {
-
- val runner = (parameters: QueryBuilderParameters) => {
- Future {
- val (sql, binder) = parameters.toSql(namingStrategy = MysqlEscape)
- sqlContext.executeQuery[T](sql, binder, { resultSet =>
- extractor(resultSet)
- })
- }(sqlContext.executionContext)
- }
-
- val countRunner = (parameters: QueryBuilderParameters) => {
- Future {
- val (sql, binder) = parameters.toSql(countQuery = true, namingStrategy = MysqlEscape)
- sqlContext.executeQuery[CountResult](sql, binder, { resultSet =>
- val count = resultSet.getInt(1)
- val lastUpdate = if (parameters.tableData.lastUpdateFieldName.isDefined) {
- Option(sqlContext.localDateTimeDecoder.decoder(2, resultSet))
- } else None
-
- (count, lastUpdate)
- }).head
- }(sqlContext.executionContext)
- }
-
- apply[T](
- tableName = tableName,
- lastUpdateFieldName = lastUpdateFieldName,
- nullableFields = nullableFields,
- links = links,
- runner = runner,
- countRunner = countRunner
- )(sqlContext.executionContext)
- }
-}
-
-class MysqlQueryBuilder[T](parameters: MysqlQueryBuilderParameters)
- (implicit runner: QueryBuilder.Runner[T],
- countRunner: QueryBuilder.CountRunner,
- ec: ExecutionContext)
- extends QueryBuilder[T, MySQLDialect, MysqlEscape](parameters) {
-
- def withFilter(newFilter: SearchFilterExpr): QueryBuilder[T, MySQLDialect, MysqlEscape] = {
- new MysqlQueryBuilder[T](parameters.copy(filter = newFilter))
- }
-
- def withSorting(newSorting: Sorting): QueryBuilder[T, MySQLDialect, MysqlEscape] = {
- new MysqlQueryBuilder[T](parameters.copy(sorting = newSorting))
- }
-
- def withPagination(newPagination: Pagination): QueryBuilder[T, MySQLDialect, MysqlEscape] = {
- new MysqlQueryBuilder[T](parameters.copy(pagination = Some(newPagination)))
- }
-
- def resetPagination: QueryBuilder[T, MySQLDialect, MysqlEscape] = {
- new MysqlQueryBuilder[T](parameters.copy(pagination = None))
- }
-
-}
diff --git a/src/main/scala/xyz/driver/common/db/Pagination.scala b/src/main/scala/xyz/driver/common/db/Pagination.scala
deleted file mode 100644
index d4a96d3..0000000
--- a/src/main/scala/xyz/driver/common/db/Pagination.scala
+++ /dev/null
@@ -1,20 +0,0 @@
-package xyz.driver.common.db
-
-import xyz.driver.common.logging._
-
-/**
- * @param pageNumber Starts with 1
- */
-case class Pagination(pageSize: Int, pageNumber: Int)
-
-object Pagination {
-
- // @see https://driverinc.atlassian.net/wiki/display/RA/REST+API+Specification#RESTAPISpecification-CommonRequestQueryParametersForWebServices
- val Default = Pagination(pageSize = 100, pageNumber = 1)
-
- implicit def toPhiString(x: Pagination): PhiString = {
- import x._
- phi"Pagination(pageSize=${Unsafe(pageSize)}, pageNumber=${Unsafe(pageNumber)})"
- }
-
-}
diff --git a/src/main/scala/xyz/driver/common/db/QueryBuilder.scala b/src/main/scala/xyz/driver/common/db/QueryBuilder.scala
deleted file mode 100644
index f0beca6..0000000
--- a/src/main/scala/xyz/driver/common/db/QueryBuilder.scala
+++ /dev/null
@@ -1,344 +0,0 @@
-package xyz.driver.common.db
-
-import java.sql.PreparedStatement
-import java.time.LocalDateTime
-
-import io.getquill.NamingStrategy
-import io.getquill.context.sql.idiom.SqlIdiom
-import xyz.driver.common.db.Sorting.{Dimension, Sequential}
-import xyz.driver.common.db.SortingOrder.{Ascending, Descending}
-
-import scala.collection.mutable.ListBuffer
-import scala.concurrent.{ExecutionContext, Future}
-
-object QueryBuilder {
-
- type Runner[T] = (QueryBuilderParameters) => Future[Seq[T]]
-
- type CountResult = (Int, Option[LocalDateTime])
-
- type CountRunner = (QueryBuilderParameters) => Future[CountResult]
-
- /**
- * Binder for PreparedStatement
- */
- type Binder = PreparedStatement => PreparedStatement
-
- case class TableData(tableName: String,
- lastUpdateFieldName: Option[String] = None,
- nullableFields: Set[String] = Set.empty)
-
- val AllFields = Set("*")
-
-}
-
-case class TableLink(keyColumnName: String,
- foreignTableName: String,
- foreignKeyColumnName: String)
-
-object QueryBuilderParameters {
- val AllFields = Set("*")
-}
-
-sealed trait QueryBuilderParameters {
-
- def tableData: QueryBuilder.TableData
- def links: Map[String, TableLink]
- def filter: SearchFilterExpr
- def sorting: Sorting
- def pagination: Option[Pagination]
-
- def findLink(tableName: String): TableLink = 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, namingStrategy: NamingStrategy): (String, QueryBuilder.Binder) = {
- toSql(countQuery, QueryBuilderParameters.AllFields, namingStrategy)
- }
-
- def toSql(countQuery: Boolean,
- fields: Set[String],
- namingStrategy: NamingStrategy): (String, QueryBuilder.Binder) = {
- val escapedTableName = namingStrategy.table(tableData.tableName)
- val fieldsSql: String = if (countQuery) {
- "count(*)" + (tableData.lastUpdateFieldName match {
- case Some(lastUpdateField) => s", max($escapedTableName.${namingStrategy.column(lastUpdateField)})"
- case None => ""
- })
- } else {
- if (fields == QueryBuilderParameters.AllFields) {
- s"$escapedTableName.*"
- } else {
- fields
- .map { field =>
- s"$escapedTableName.${namingStrategy.column(field)}"
- }
- .mkString(", ")
- }
- }
- val (where, bindings) = filterToSql(escapedTableName, filter, namingStrategy)
- val orderBy = sortingToSql(escapedTableName, sorting, namingStrategy)
-
- val limitSql = limitToSql()
-
- val sql = new StringBuilder()
- sql.append("select ")
- sql.append(fieldsSql)
- sql.append("\nfrom ")
- sql.append(escapedTableName)
-
- val filtersTableLinks: Seq[TableLink] = {
- import SearchFilterExpr._
- def aux(expr: SearchFilterExpr): Seq[TableLink] = 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[TableLink] = Sorting.collect(sorting) {
- case Dimension(Some(foreignTableName), _, _) => findLink(foreignTableName)
- }
-
- // Combine links from sorting and filter without duplicates
- val foreignTableLinks = (filtersTableLinks ++ sortingTableLinks).distinct
-
- foreignTableLinks.foreach {
- case TableLink(keyColumnName, foreignTableName, foreignKeyColumnName) =>
- val escapedForeignTableName = namingStrategy.table(foreignTableName)
-
- sql.append("\ninner join ")
- sql.append(escapedForeignTableName)
- sql.append(" on ")
-
- sql.append(escapedTableName)
- sql.append('.')
- sql.append(namingStrategy.column(keyColumnName))
-
- sql.append(" = ")
-
- sql.append(escapedForeignTableName)
- sql.append('.')
- sql.append(namingStrategy.column(foreignKeyColumnName))
- }
-
- if (where.nonEmpty) {
- sql.append("\nwhere ")
- sql.append(where)
- }
-
- if (orderBy.nonEmpty && !countQuery) {
- sql.append("\norder by ")
- sql.append(orderBy)
- }
-
- if (limitSql.nonEmpty && !countQuery) {
- sql.append("\n")
- sql.append(limitSql)
- }
-
- (sql.toString, binder(bindings))
- }
-
- /**
- * 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,
- namingStrategy: NamingStrategy): (String, List[AnyRef]) = {
- import SearchFilterBinaryOperation._
- import SearchFilterExpr._
-
- def isNull(string: AnyRef) = Option(string).isEmpty || string.toString.toLowerCase == "null"
-
- def placeholder(field: String) = "?"
-
- def escapeDimension(dimension: SearchFilterExpr.Dimension) = {
- val tableName = dimension.tableName.fold(escapedTableName)(namingStrategy.table)
- s"$tableName.${namingStrategy.column(dimension.name)}"
- }
-
- def filterToSqlMultiple(operands: Seq[SearchFilterExpr]) = operands.collect {
- case x if !SearchFilterExpr.isEmpty(x) => filterToSql(escapedTableName, x, namingStrategy)
- }
-
- filter match {
- case x if isEmpty(x) =>
- ("", List.empty)
-
- case AllowAll =>
- ("1", List.empty)
-
- case DenyAll =>
- ("0", List.empty)
-
- case Atom.Binary(dimension, Eq, value) if isNull(value) =>
- (s"${escapeDimension(dimension)} is NULL", List.empty)
-
- case Atom.Binary(dimension, NotEq, value) if isNull(value) =>
- (s"${escapeDimension(dimension)} is not NULL", List.empty)
-
- 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)
- val sql = s"($escapedColumn is null or $escapedColumn != ${placeholder(dimension.name)})"
- (sql, List(value))
-
- case Atom.Binary(dimension, op, value) =>
- val operator = op match {
- case Eq => "="
- case NotEq => "!="
- case Like => "like"
- case Gt => ">"
- case GtEq => ">="
- case Lt => "<"
- case LtEq => "<="
- }
- (s"${escapeDimension(dimension)} $operator ${placeholder(dimension.name)}", List(value))
-
- case Atom.NAry(dimension, op, values) =>
- val sqlOp = op match {
- case SearchFilterNAryOperation.In => "in"
- case SearchFilterNAryOperation.NotIn => "not in"
- }
-
- val bindings = ListBuffer[AnyRef]()
- val sqlPlaceholder = placeholder(dimension.name)
- val formattedValues = values.map { value =>
- bindings += value
- sqlPlaceholder
- }.mkString(", ")
- (s"${escapeDimension(dimension)} $sqlOp ($formattedValues)", bindings.toList)
-
- case Intersection(operands) =>
- val (sql, bindings) = filterToSqlMultiple(operands).unzip
- (sql.mkString("(", " and ", ")"), bindings.flatten.toList)
-
- case Union(operands) =>
- val (sql, bindings) = filterToSqlMultiple(operands).unzip
- (sql.mkString("(", " or ", ")"), bindings.flatten.toList)
- }
- }
-
- protected def limitToSql(): String
-
- /**
- * @param escapedMainTableName Should be escaped
- */
- protected def sortingToSql(escapedMainTableName: String,
- sorting: Sorting,
- namingStrategy: NamingStrategy): String = {
- sorting match {
- case Dimension(optSortingTableName, field, order) =>
- val sortingTableName = optSortingTableName.map(namingStrategy.table).getOrElse(escapedMainTableName)
- val fullName = s"$sortingTableName.${namingStrategy.column(field)}"
-
- s"$fullName ${orderToSql(order)}"
-
- case Sequential(xs) =>
- xs.map(sortingToSql(escapedMainTableName, _, namingStrategy)).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
- }
-
-}
-
-case class PostgresQueryBuilderParameters(tableData: QueryBuilder.TableData,
- links: Map[String, TableLink] = Map.empty,
- filter: SearchFilterExpr = SearchFilterExpr.Empty,
- sorting: Sorting = Sorting.Empty,
- pagination: Option[Pagination] = None) extends QueryBuilderParameters {
-
- def limitToSql(): String = {
- pagination.map { pagination =>
- val startFrom = (pagination.pageNumber - 1) * pagination.pageSize
- s"limit ${pagination.pageSize} OFFSET $startFrom"
- } getOrElse ""
- }
-
-}
-
-/**
- * @param links Links to another tables grouped by foreignTableName
- */
-case class MysqlQueryBuilderParameters(tableData: QueryBuilder.TableData,
- links: Map[String, TableLink] = Map.empty,
- filter: SearchFilterExpr = SearchFilterExpr.Empty,
- sorting: Sorting = Sorting.Empty,
- pagination: Option[Pagination] = None) extends QueryBuilderParameters {
-
- def limitToSql(): String = pagination.map { pagination =>
- val startFrom = (pagination.pageNumber - 1) * pagination.pageSize
- s"limit $startFrom, ${pagination.pageSize}"
- }.getOrElse("")
-
-}
-
-abstract class QueryBuilder[T, D <: SqlIdiom, N <: NamingStrategy](val parameters: QueryBuilderParameters)
- (implicit runner: QueryBuilder.Runner[T],
- countRunner: QueryBuilder.CountRunner,
- ec: ExecutionContext) {
-
- def run: Future[Seq[T]] = runner(parameters)
-
- def runCount: Future[QueryBuilder.CountResult] = countRunner(parameters)
-
- /**
- * Runs the query and returns total found rows without considering of pagination.
- */
- def runWithCount: Future[(Seq[T], Int, Option[LocalDateTime])] = {
- val countFuture = runCount
- val selectAllFuture = run
- for {
- (total, lastUpdate) <- countFuture
- all <- selectAllFuture
- } yield (all, total, lastUpdate)
- }
-
- def withFilter(newFilter: SearchFilterExpr): QueryBuilder[T, D, N]
-
- def withFilter(filter: Option[SearchFilterExpr]): QueryBuilder[T, D, N] = {
- filter.fold(this)(withFilter)
- }
-
- def resetFilter: QueryBuilder[T, D, N] = withFilter(SearchFilterExpr.Empty)
-
-
- def withSorting(newSorting: Sorting): QueryBuilder[T, D, N]
-
- def withSorting(sorting: Option[Sorting]): QueryBuilder[T, D, N] = {
- sorting.fold(this)(withSorting)
- }
-
- def resetSorting: QueryBuilder[T, D, N] = withSorting(Sorting.Empty)
-
-
- def withPagination(newPagination: Pagination): QueryBuilder[T, D, N]
-
- def withPagination(pagination: Option[Pagination]): QueryBuilder[T, D, N] = {
- pagination.fold(this)(withPagination)
- }
-
- def resetPagination: QueryBuilder[T, D, N]
-
-}
diff --git a/src/main/scala/xyz/driver/common/db/SearchFilterExpr.scala b/src/main/scala/xyz/driver/common/db/SearchFilterExpr.scala
deleted file mode 100644
index 06b21cd..0000000
--- a/src/main/scala/xyz/driver/common/db/SearchFilterExpr.scala
+++ /dev/null
@@ -1,210 +0,0 @@
-package xyz.driver.common.db
-
-import xyz.driver.common.logging._
-
-sealed trait SearchFilterExpr {
- def find(p: SearchFilterExpr => Boolean): Option[SearchFilterExpr]
- def replace(f: PartialFunction[SearchFilterExpr, SearchFilterExpr]): SearchFilterExpr
-}
-
-object SearchFilterExpr {
-
- val Empty = Intersection.Empty
- val Forbid = Atom.Binary(
- dimension = Dimension(None, "true"),
- op = SearchFilterBinaryOperation.Eq,
- value = "false"
- )
-
- case class Dimension(tableName: Option[String], name: String) {
- def isForeign: Boolean = tableName.isDefined
- }
-
- sealed trait Atom extends SearchFilterExpr {
- override def find(p: SearchFilterExpr => Boolean): Option[SearchFilterExpr] = {
- if (p(this)) Some(this)
- else None
- }
-
- override def replace(f: PartialFunction[SearchFilterExpr, SearchFilterExpr]): SearchFilterExpr = {
- if (f.isDefinedAt(this)) f(this)
- else this
- }
- }
-
- object Atom {
- case class Binary(dimension: Dimension, op: SearchFilterBinaryOperation, value: AnyRef) extends Atom
- object Binary {
- def apply(field: String, op: SearchFilterBinaryOperation, value: AnyRef): Binary =
- Binary(Dimension(None, field), op, value)
- }
-
- case class NAry(dimension: Dimension, op: SearchFilterNAryOperation, values: Seq[AnyRef]) extends Atom
- object NAry {
- def apply(field: String, op: SearchFilterNAryOperation, values: Seq[AnyRef]): NAry =
- NAry(Dimension(None, field), op, values)
- }
-
- /** dimension.tableName extractor */
- object TableName {
- def unapply(value: Atom): Option[String] = value match {
- case Binary(Dimension(tableNameOpt, _), _, _) => tableNameOpt
- case NAry(Dimension(tableNameOpt, _), _, _) => tableNameOpt
- }
- }
- }
-
- case class Intersection private(operands: Seq[SearchFilterExpr])
- extends SearchFilterExpr with SearchFilterExprSeqOps {
-
- override def replace(f: PartialFunction[SearchFilterExpr, SearchFilterExpr]): SearchFilterExpr = {
- if (f.isDefinedAt(this)) f(this)
- else {
- this.copy(operands.map(_.replace(f)))
- }
- }
-
- }
-
- object Intersection {
-
- val Empty = Intersection(Seq())
-
- def create(operands: SearchFilterExpr*): SearchFilterExpr = {
- val filtered = operands.filterNot(SearchFilterExpr.isEmpty)
- filtered.size match {
- case 0 => Empty
- case 1 => filtered.head
- case _ => Intersection(filtered)
- }
- }
- }
-
-
- case class Union private(operands: Seq[SearchFilterExpr]) extends SearchFilterExpr with SearchFilterExprSeqOps {
-
- override def replace(f: PartialFunction[SearchFilterExpr, SearchFilterExpr]): SearchFilterExpr = {
- if (f.isDefinedAt(this)) f(this)
- else {
- this.copy(operands.map(_.replace(f)))
- }
- }
-
- }
-
- object Union {
-
- val Empty = Union(Seq())
-
- def create(operands: SearchFilterExpr*): SearchFilterExpr = {
- val filtered = operands.filterNot(SearchFilterExpr.isEmpty)
- filtered.size match {
- case 0 => Empty
- case 1 => filtered.head
- case _ => Union(filtered)
- }
- }
-
- def create(dimension: Dimension, values: String*): SearchFilterExpr = values.size match {
- case 0 => SearchFilterExpr.Empty
- case 1 => SearchFilterExpr.Atom.Binary(dimension, SearchFilterBinaryOperation.Eq, values.head)
- case _ =>
- val filters = values.map { value =>
- SearchFilterExpr.Atom.Binary(dimension, SearchFilterBinaryOperation.Eq, value)
- }
-
- create(filters: _*)
- }
-
- def create(dimension: Dimension, values: Set[String]): SearchFilterExpr =
- create(dimension, values.toSeq: _*)
-
- // Backwards compatible API
-
- /** Create SearchFilterExpr with empty tableName */
- def create(field: String, values: String*): SearchFilterExpr =
- create(Dimension(None, field), values:_*)
-
- /** Create SearchFilterExpr with empty tableName */
- def create(field: String, values: Set[String]): SearchFilterExpr =
- create(Dimension(None, field), values)
- }
-
-
- case object AllowAll extends SearchFilterExpr {
- override def find(p: SearchFilterExpr => Boolean): Option[SearchFilterExpr] = {
- if (p(this)) Some(this)
- else None
- }
-
- override def replace(f: PartialFunction[SearchFilterExpr, SearchFilterExpr]): SearchFilterExpr = {
- if (f.isDefinedAt(this)) f(this)
- else this
- }
- }
-
- case object DenyAll extends SearchFilterExpr {
- override def find(p: SearchFilterExpr => Boolean): Option[SearchFilterExpr] = {
- if (p(this)) Some(this)
- else None
- }
-
- override def replace(f: PartialFunction[SearchFilterExpr, SearchFilterExpr]): SearchFilterExpr = {
- if (f.isDefinedAt(this)) f(this)
- else this
- }
- }
-
- def isEmpty(expr: SearchFilterExpr): Boolean = {
- expr == Intersection.Empty || expr == Union.Empty
- }
-
- sealed trait SearchFilterExprSeqOps {
- this: SearchFilterExpr =>
-
- val operands: Seq[SearchFilterExpr]
-
- override def find(p: SearchFilterExpr => Boolean): Option[SearchFilterExpr] = {
- if (p(this)) Some(this)
- else {
- // Search the first expr among operands, which satisfy p
- // Is's ok to use foldLeft. If there will be performance issues, replace it by recursive loop
- operands.foldLeft(Option.empty[SearchFilterExpr]) {
- case (None, expr) => expr.find(p)
- case (x, _) => x
- }
- }
- }
-
- }
-
- // There is no case, when this is unsafe. At this time.
- implicit def toPhiString(x: SearchFilterExpr): PhiString = {
- if (isEmpty(x)) Unsafe("SearchFilterExpr.Empty")
- else Unsafe(x.toString)
- }
-
-}
-
-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/common/db/Sorting.scala b/src/main/scala/xyz/driver/common/db/Sorting.scala
deleted file mode 100644
index 70c25f2..0000000
--- a/src/main/scala/xyz/driver/common/db/Sorting.scala
+++ /dev/null
@@ -1,62 +0,0 @@
-package xyz.driver.common.db
-
-import xyz.driver.common.logging._
-
-import scala.collection.generic.CanBuildFrom
-
-sealed trait SortingOrder
-object SortingOrder {
-
- case object Ascending extends SortingOrder
- case object Descending extends SortingOrder
-
-}
-
-sealed trait Sorting
-
-object Sorting {
-
- val Empty = Sequential(Seq.empty)
-
- /**
- * @param tableName None if the table is default (same)
- * @param name Dimension name
- * @param order Order
- */
- case class Dimension(tableName: Option[String], name: String, order: SortingOrder) extends Sorting {
- def isForeign: Boolean = tableName.isDefined
- }
-
- case class Sequential(sorting: Seq[Dimension]) extends Sorting {
- override def toString: String = if (isEmpty(this)) "Empty" else super.toString
- }
-
- def isEmpty(input: Sorting): Boolean = {
- input match {
- case Sequential(Seq()) => true
- case _ => false
- }
- }
-
- def filter(sorting: Sorting, p: Dimension => Boolean): Seq[Dimension] = sorting match {
- case x: Dimension if p(x) => Seq(x)
- case x: Dimension => Seq.empty
- case Sequential(xs) => xs.filter(p)
- }
-
- def collect[B, That](sorting: Sorting)
- (f: PartialFunction[Dimension, B])
- (implicit bf: CanBuildFrom[Seq[Dimension], B, That]): That = sorting match {
- case x: Dimension if f.isDefinedAt(x) =>
- val r = bf.apply()
- r += f(x)
- r.result()
-
- case x: Dimension => bf.apply().result()
- case Sequential(xs) => xs.collect(f)
- }
-
- // Contains dimensions and ordering only, thus it is safe.
- implicit def toPhiString(x: Sorting): PhiString = Unsafe(x.toString)
-
-}
diff --git a/src/main/scala/xyz/driver/common/db/SqlContext.scala b/src/main/scala/xyz/driver/common/db/SqlContext.scala
deleted file mode 100644
index 4b9d676..0000000
--- a/src/main/scala/xyz/driver/common/db/SqlContext.scala
+++ /dev/null
@@ -1,184 +0,0 @@
-package xyz.driver.common.db
-
-import java.io.Closeable
-import java.net.URI
-import java.time._
-import java.util.UUID
-import java.util.concurrent.Executors
-import javax.sql.DataSource
-
-import xyz.driver.common.logging.{PhiLogging, Unsafe}
-import xyz.driver.common.concurrent.MdcExecutionContext
-import xyz.driver.common.db.SqlContext.Settings
-import xyz.driver.common.domain._
-import xyz.driver.common.error.IncorrectIdException
-import xyz.driver.common.utils.JsonSerializer
-import com.typesafe.config.Config
-import io.getquill._
-
-import scala.concurrent.ExecutionContext
-import scala.util.control.NonFatal
-import scala.util.{Failure, Success, Try}
-
-object SqlContext extends PhiLogging {
-
- case class DbCredentials(user: String,
- password: String,
- host: String,
- port: Int,
- dbName: String,
- dbCreateFlag: Boolean,
- dbContext: String,
- connectionParams: String,
- url: String)
-
- case class Settings(credentials: DbCredentials,
- connection: Config,
- connectionAttemptsOnStartup: Int,
- threadPoolSize: Int)
-
- def apply(settings: Settings): SqlContext = {
- // Prevent leaking credentials to a log
- Try(JdbcContextConfig(settings.connection).dataSource) match {
- case Success(dataSource) => new SqlContext(dataSource, settings)
- case Failure(NonFatal(e)) =>
- logger.error(phi"Can not load dataSource, error: ${Unsafe(e.getClass.getName)}")
- throw new IllegalArgumentException("Can not load dataSource from config. Check your database and config")
- }
- }
-
-}
-
-class SqlContext(dataSource: DataSource with Closeable, settings: Settings)
- extends MysqlJdbcContext[MysqlEscape](dataSource)
- with EntityExtractorDerivation[Literal] {
-
- private val tpe = Executors.newFixedThreadPool(settings.threadPoolSize)
-
- implicit val executionContext: ExecutionContext = {
- val orig = ExecutionContext.fromExecutor(tpe)
- MdcExecutionContext.from(orig)
- }
-
- override def close(): Unit = {
- super.close()
- tpe.shutdownNow()
- }
-
- // ///////// Encodes/Decoders ///////////
-
- /**
- * Overrode, because Quill JDBC optionDecoder pass null inside decoders.
- * If custom decoder don't have special null handler, it will failed.
- *
- * @see https://github.com/getquill/quill/issues/535
- */
- implicit override def optionDecoder[T](implicit d: Decoder[T]): Decoder[Option[T]] =
- decoder(
- sqlType = d.sqlType,
- row => index => {
- try {
- val res = d(index - 1, row)
- if (row.wasNull) {
- None
- }
- else {
- Some(res)
- }
- } catch {
- case _: NullPointerException => None
- case _: IncorrectIdException => None
- }
- }
- )
-
- implicit def encodeStringId[T] = MappedEncoding[StringId[T], String](_.id)
- implicit def decodeStringId[T] = MappedEncoding[String, StringId[T]] {
- case "" => throw IncorrectIdException("'' is an invalid Id value")
- case x => StringId(x)
- }
-
- def decodeOptStringId[T] = MappedEncoding[Option[String], Option[StringId[T]]] {
- case None | Some("") => None
- case Some(x) => Some(StringId(x))
- }
-
- implicit def encodeLongId[T] = MappedEncoding[LongId[T], Long](_.id)
- implicit def decodeLongId[T] = MappedEncoding[Long, LongId[T]] {
- case 0 => throw IncorrectIdException("0 is an invalid Id value")
- case x => LongId(x)
- }
-
- // TODO Dirty hack, see REP-475
- def decodeOptLongId[T] = MappedEncoding[Option[Long], Option[LongId[T]]] {
- case None | Some(0) => None
- case Some(x) => Some(LongId(x))
- }
-
- implicit def encodeUuidId[T] = MappedEncoding[UuidId[T], String](_.toString)
- implicit def decodeUuidId[T] = MappedEncoding[String, UuidId[T]] {
- case "" => throw IncorrectIdException("'' is an invalid Id value")
- case x => UuidId(x)
- }
-
- def decodeOptUuidId[T] = MappedEncoding[Option[String], Option[UuidId[T]]] {
- case None | Some("") => None
- case Some(x) => Some(UuidId(x))
- }
-
- implicit def encodeTextJson[T: Manifest] = MappedEncoding[TextJson[T], String](x => JsonSerializer.serialize(x.content))
- implicit def decodeTextJson[T: Manifest] = MappedEncoding[String, TextJson[T]] { x =>
- TextJson(JsonSerializer.deserialize[T](x))
- }
-
- implicit val encodeUserRole = MappedEncoding[User.Role, Int](_.bit)
- implicit val decodeUserRole = MappedEncoding[Int, User.Role] {
- // 0 is treated as null for numeric types
- case 0 => throw new NullPointerException("0 means no roles. A user must have a role")
- case x => User.Role(x)
- }
-
- implicit val encodeEmail = MappedEncoding[Email, String](_.value.toString)
- implicit val decodeEmail = MappedEncoding[String, Email](Email)
-
- implicit val encodePasswordHash = MappedEncoding[PasswordHash, Array[Byte]](_.value)
- implicit val decodePasswordHash = MappedEncoding[Array[Byte], PasswordHash](PasswordHash(_))
-
- implicit val encodeUri = MappedEncoding[URI, String](_.toString)
- implicit val decodeUri = MappedEncoding[String, URI](URI.create)
-
- implicit val encodeCaseId = MappedEncoding[CaseId, String](_.id.toString)
- implicit val decodeCaseId = MappedEncoding[String, CaseId](CaseId(_))
-
- implicit val encodeFuzzyValue = {
- MappedEncoding[FuzzyValue, String] {
- case FuzzyValue.No => "No"
- case FuzzyValue.Yes => "Yes"
- case FuzzyValue.Maybe => "Maybe"
- }
- }
- implicit val decodeFuzzyValue = MappedEncoding[String, FuzzyValue] {
- case "Yes" => FuzzyValue.Yes
- case "No" => FuzzyValue.No
- case "Maybe" => FuzzyValue.Maybe
- case x =>
- Option(x).fold {
- throw new NullPointerException("FuzzyValue is null") // See catch in optionDecoder
- } { _ =>
- throw new IllegalStateException(s"Unknown fuzzy value: $x")
- }
- }
-
-
- implicit val encodeRecordRequestId = MappedEncoding[RecordRequestId, String](_.id.toString)
- implicit val decodeRecordRequestId = MappedEncoding[String, RecordRequestId] { x =>
- RecordRequestId(UUID.fromString(x))
- }
-
- final implicit class LocalDateTimeDbOps(val left: LocalDateTime) {
-
- // scalastyle:off
- def <=(right: LocalDateTime): Quoted[Boolean] = quote(infix"$left <= $right".as[Boolean])
- }
-
-}
diff --git a/src/main/scala/xyz/driver/common/db/Transactions.scala b/src/main/scala/xyz/driver/common/db/Transactions.scala
deleted file mode 100644
index 2f5a2cc..0000000
--- a/src/main/scala/xyz/driver/common/db/Transactions.scala
+++ /dev/null
@@ -1,23 +0,0 @@
-package xyz.driver.common.db
-
-import xyz.driver.common.logging.PhiLogging
-
-import scala.concurrent.Future
-import scala.util.{Failure, Success, Try}
-
-class Transactions()(implicit context: SqlContext) extends PhiLogging {
- def run[T](f: SqlContext => T): Future[T] = {
- import context.executionContext
-
- Future(context.transaction(f(context))).andThen {
- case Failure(e) => logger.error(phi"Can't run a transaction: $e")
- }
- }
-
- def runSync[T](f: SqlContext => T): Unit = {
- Try(context.transaction(f(context))) match {
- case Success(_) =>
- case Failure(e) => logger.error(phi"Can't run a transaction: $e")
- }
- }
-}
diff --git a/src/main/scala/xyz/driver/common/db/repositories/BridgeUploadQueueRepository.scala b/src/main/scala/xyz/driver/common/db/repositories/BridgeUploadQueueRepository.scala
deleted file mode 100644
index e0d6ff2..0000000
--- a/src/main/scala/xyz/driver/common/db/repositories/BridgeUploadQueueRepository.scala
+++ /dev/null
@@ -1,24 +0,0 @@
-package xyz.driver.common.db.repositories
-
-import xyz.driver.common.concurrent.BridgeUploadQueue
-import xyz.driver.common.domain.LongId
-
-import scala.concurrent.Future
-
-trait BridgeUploadQueueRepository extends Repository {
-
- type EntityT = BridgeUploadQueue.Item
- type IdT = LongId[EntityT]
-
- def add(draft: EntityT): EntityT
-
- def getById(id: LongId[EntityT]): Option[EntityT]
-
- def isCompleted(kind: String, tag: String): Future[Boolean]
-
- def getOne(kind: String): Future[Option[BridgeUploadQueue.Item]]
-
- def update(entity: EntityT): EntityT
-
- def delete(id: IdT): Unit
-}
diff --git a/src/main/scala/xyz/driver/common/db/repositories/Repository.scala b/src/main/scala/xyz/driver/common/db/repositories/Repository.scala
deleted file mode 100644
index ae2a3e6..0000000
--- a/src/main/scala/xyz/driver/common/db/repositories/Repository.scala
+++ /dev/null
@@ -1,4 +0,0 @@
-package xyz.driver.common.db.repositories
-
-// For further usage and migration to Postgres and slick
-trait Repository extends RepositoryLogging
diff --git a/src/main/scala/xyz/driver/common/db/repositories/RepositoryLogging.scala b/src/main/scala/xyz/driver/common/db/repositories/RepositoryLogging.scala
deleted file mode 100644
index cb2c438..0000000
--- a/src/main/scala/xyz/driver/common/db/repositories/RepositoryLogging.scala
+++ /dev/null
@@ -1,62 +0,0 @@
-package xyz.driver.common.db.repositories
-
-import xyz.driver.common.logging._
-
-trait RepositoryLogging extends PhiLogging {
-
- protected def logCreatedOne[T](x: T)(implicit toPhiString: T => PhiString): T = {
- logger.info(phi"An entity was created: $x")
- x
- }
-
- protected def logCreatedMultiple[T <: Iterable[_]](xs: T)(implicit toPhiString: T => PhiString): T = {
- if (xs.nonEmpty) {
- logger.info(phi"Entities were created: $xs")
- }
- xs
- }
-
- protected def logUpdatedOne(rowsAffected: Long): Long = {
- rowsAffected match {
- case 0 => logger.trace(phi"The entity is up to date")
- case 1 => logger.info(phi"The entity was updated")
- case x => logger.warn(phi"The ${Unsafe(x)} entities were updated")
- }
- rowsAffected
- }
-
- protected def logUpdatedOneUnimportant(rowsAffected: Long): Long = {
- rowsAffected match {
- case 0 => logger.trace(phi"The entity is up to date")
- case 1 => logger.trace(phi"The entity was updated")
- case x => logger.warn(phi"The ${Unsafe(x)} entities were updated")
- }
- rowsAffected
- }
-
- protected def logUpdatedMultiple(rowsAffected: Long): Long = {
- rowsAffected match {
- case 0 => logger.trace(phi"All entities are up to date")
- case x => logger.info(phi"The ${Unsafe(x)} entities were updated")
- }
- rowsAffected
- }
-
- protected def logDeletedOne(rowsAffected: Long): Long = {
- rowsAffected match {
- case 0 => logger.trace(phi"The entity does not exist")
- case 1 => logger.info(phi"The entity was deleted")
- case x => logger.warn(phi"Deleted ${Unsafe(x)} entities, expected one")
- }
- rowsAffected
- }
-
- protected def logDeletedMultiple(rowsAffected: Long): Long = {
- rowsAffected match {
- case 0 => logger.trace(phi"Entities do not exist")
- case x => logger.info(phi"Deleted ${Unsafe(x)} entities")
- }
- rowsAffected
- }
-
-}