aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/xyz/driver/pdsuicommon
diff options
context:
space:
mode:
authorKseniya Tomskikh <ktomskih@datamonsters.co>2017-09-28 17:52:21 +0700
committerKseniya Tomskikh <ktomskih@datamonsters.co>2017-09-28 17:52:21 +0700
commit283ca02360949143ffe7ee8ad87d51902426b450 (patch)
treec098efe0628fc80a0a6b8f05fa465a99ff494fb8 /src/main/scala/xyz/driver/pdsuicommon
parentd82c93fef0fc0bb937220334f73c264fbb1082f2 (diff)
parentc24679f1ae7d7ccc4e6693535b0aa3ac0e1ca225 (diff)
downloadrest-query-283ca02360949143ffe7ee8ad87d51902426b450.tar.gz
rest-query-283ca02360949143ffe7ee8ad87d51902426b450.tar.bz2
rest-query-283ca02360949143ffe7ee8ad87d51902426b450.zip
Merge branch 'master' into synch-refactor
Diffstat (limited to 'src/main/scala/xyz/driver/pdsuicommon')
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/acl/ACL.scala4
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/db/PostgresContext.scala2
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/db/QueryBuilder.scala4
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/db/SlickPostgresQueryBuilder.scala116
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/db/SlickQueryBuilder.scala358
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/domain/FuzzyValue.scala28
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParser.scala17
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/utils/CustomSwaggerJsonFormats.scala2
8 files changed, 512 insertions, 19 deletions
diff --git a/src/main/scala/xyz/driver/pdsuicommon/acl/ACL.scala b/src/main/scala/xyz/driver/pdsuicommon/acl/ACL.scala
index f2a0ef0..180ebf9 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/acl/ACL.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/acl/ACL.scala
@@ -168,8 +168,10 @@ object ACL extends PhiLogging {
object Intervention
extends BaseACL(
label = "intervention",
+ create = Set(TrialSummarizer, TrialAdmin),
read = Set(TrialSummarizer, TrialAdmin),
- update = Set(TrialSummarizer, TrialAdmin)
+ update = Set(TrialSummarizer, TrialAdmin),
+ delete = Set(TrialSummarizer, TrialAdmin)
)
object InterventionType
diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/PostgresContext.scala b/src/main/scala/xyz/driver/pdsuicommon/db/PostgresContext.scala
index cbb23d4..7bdfd1b 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/db/PostgresContext.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/db/PostgresContext.scala
@@ -28,7 +28,7 @@ object PostgresContext extends PhiLogging {
case Success(dataSource) => new PostgresContext(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")
+ throw new IllegalArgumentException("Can not load dataSource from config. Check your database and config", e)
}
}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/QueryBuilder.scala b/src/main/scala/xyz/driver/pdsuicommon/db/QueryBuilder.scala
index aa32166..0bf1ed6 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/db/QueryBuilder.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/db/QueryBuilder.scala
@@ -57,10 +57,10 @@ sealed trait QueryBuilderParameters {
def toSql(countQuery: Boolean, fields: Set[String], namingStrategy: NamingStrategy): (String, QueryBuilder.Binder) = {
val escapedTableName = namingStrategy.table(tableData.tableName)
val fieldsSql: String = if (countQuery) {
- val suffix: String = (tableData.lastUpdateFieldName match {
+ val suffix: String = tableData.lastUpdateFieldName match {
case Some(lastUpdateField) => s", max($escapedTableName.${namingStrategy.column(lastUpdateField)})"
case None => ""
- })
+ }
"count(*)" + suffix
} else {
if (fields == QueryBuilderParameters.AllFields) {
diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/SlickPostgresQueryBuilder.scala b/src/main/scala/xyz/driver/pdsuicommon/db/SlickPostgresQueryBuilder.scala
new file mode 100644
index 0000000..f882441
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuicommon/db/SlickPostgresQueryBuilder.scala
@@ -0,0 +1,116 @@
+package xyz.driver.pdsuicommon.db
+
+import java.time.{LocalDateTime, ZoneOffset}
+
+import slick.driver.JdbcProfile
+import slick.jdbc.GetResult
+import xyz.driver.core.database.SlickDal
+import xyz.driver.pdsuicommon.logging._
+
+import scala.collection.breakOut
+import scala.concurrent.ExecutionContext
+
+object SlickPostgresQueryBuilder extends PhiLogging {
+
+ 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] = {
+ val parameters = SlickPostgresQueryBuilderParameters(
+ databaseName = databaseName,
+ tableData = TableData(tableName, lastUpdateFieldName, nullableFields),
+ links = links.map(x => x.foreignTableName -> x)(breakOut)
+ )
+ 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,
+ 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]
+ logger.debug(phi"${Unsafe(sql)}")
+ sqlContext.execute(sql)
+ }
+
+ val countRunner: CountRunner = { parameters =>
+ implicit val getCountResult: GetResult[(Int, Option[LocalDateTime])] = GetResult({ r =>
+ val count = r.rs.getInt(1)
+ val lastUpdate = if (parameters.tableData.lastUpdateFieldName.isDefined) {
+ Option(r.rs.getTimestamp(2)).map(timestampToLocalDateTime)
+ } else None
+ (count, lastUpdate)
+ })
+ val sql = parameters.toSql(countQuery = true).as[(Int, Option[LocalDateTime])]
+ logger.debug(phi"${Unsafe(sql)}")
+ sqlContext.execute(sql).map(_.head)
+ }
+
+ apply[T](
+ databaseName = databaseName,
+ tableName = tableName,
+ lastUpdateFieldName = lastUpdateFieldName,
+ nullableFields = nullableFields,
+ links = links,
+ runner = runner,
+ countRunner = countRunner
+ )
+ }
+
+ def timestampToLocalDateTime(timestamp: java.sql.Timestamp): LocalDateTime = {
+ LocalDateTime.ofInstant(timestamp.toInstant, ZoneOffset.UTC)
+ }
+}
+
+class SlickPostgresQueryBuilder[T](parameters: SlickPostgresQueryBuilderParameters)(
+ implicit runner: SlickQueryBuilder.Runner[T],
+ countRunner: SlickQueryBuilder.CountRunner)
+ extends SlickQueryBuilder[T](parameters) {
+
+ def withFilter(newFilter: SearchFilterExpr): SlickQueryBuilder[T] = {
+ new SlickPostgresQueryBuilder[T](parameters.copy(filter = newFilter))
+ }
+
+ def withSorting(newSorting: Sorting): SlickQueryBuilder[T] = {
+ new SlickPostgresQueryBuilder[T](parameters.copy(sorting = newSorting))
+ }
+
+ def withPagination(newPagination: Pagination): SlickQueryBuilder[T] = {
+ new SlickPostgresQueryBuilder[T](parameters.copy(pagination = Some(newPagination)))
+ }
+
+ def resetPagination: SlickQueryBuilder[T] = {
+ new SlickPostgresQueryBuilder[T](parameters.copy(pagination = None))
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/SlickQueryBuilder.scala b/src/main/scala/xyz/driver/pdsuicommon/db/SlickQueryBuilder.scala
new file mode 100644
index 0000000..ab2757b
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuicommon/db/SlickQueryBuilder.scala
@@ -0,0 +1,358 @@
+package xyz.driver.pdsuicommon.db
+
+import java.sql.PreparedStatement
+import java.time.LocalDateTime
+
+import slick.driver.JdbcProfile
+import slick.jdbc.{PositionedParameters, SQLActionBuilder, SetParameter}
+import xyz.driver.pdsuicommon.db.Sorting.{Dimension, Sequential}
+import xyz.driver.pdsuicommon.db.SortingOrder.{Ascending, Descending}
+
+import scala.concurrent.{ExecutionContext, Future}
+
+object SlickQueryBuilder {
+
+ type Runner[T] = SlickQueryBuilderParameters => Future[Seq[T]]
+
+ type CountResult = Future[(Int, Option[LocalDateTime])]
+
+ type CountRunner = SlickQueryBuilderParameters => CountResult
+
+ /**
+ * Binder for PreparedStatement
+ */
+ type Binder = PreparedStatement => PreparedStatement
+
+ 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)
+ }
+ })
+ }
+ }
+}
+
+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, QueryBuilderParameters.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"$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(false, op, conditions.tail, condition)
+ } else {
+ multipleSqlToAction(false, op, conditions.tail, sql concat sql" #${op} " concat condition)
+ }
+ } else sql
+ }
+
+ filter match {
+ case x if isEmpty(x) =>
+ sql""
+
+ case AllowAll =>
+ sql"1"
+
+ case DenyAll =>
+ sql"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.toString})"
+
+ 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.toString}"""
+
+ case Atom.NAry(dimension, op, values) =>
+ val sqlOp = op match {
+ case SearchFilterNAryOperation.In => sql" in "
+ case SearchFilterNAryOperation.NotIn => sql" not in "
+ }
+
+ val formattedValues = if (values.nonEmpty) {
+ val condition = s"(${values.map(v => "'" + v.toString + "'").mkString(",")})"
+ sql"#${condition}"
+ } else sql"NULL"
+ sql"#${escapeDimension(dimension)}" concat sqlOp concat formattedValues
+
+ case Intersection(operands) =>
+ val filter = multipleSqlToAction(true, "and", filterToSqlMultiple(operands), sql"")
+ sql"(" concat filter concat sql")"
+
+ case Union(operands) =>
+ val filter = multipleSqlToAction(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)
+ extends SlickQueryBuilderParameters {
+
+ def limitToSql()(implicit profile: JdbcProfile): SQLActionBuilder = {
+ import profile.api._
+ pagination.map { pagination =>
+ val startFrom = (pagination.pageNumber - 1) * pagination.pageSize
+ sql"limit #${pagination.pageSize} OFFSET #$startFrom"
+ } getOrElse (sql"")
+ }
+
+ val qs = """""""
+
+}
+
+/**
+ * @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)
+ extends SlickQueryBuilderParameters {
+
+ def limitToSql()(implicit profile: JdbcProfile): SQLActionBuilder = {
+ import profile.api._
+ pagination
+ .map { pagination =>
+ val startFrom = (pagination.pageNumber - 1) * pagination.pageSize
+ sql"limit #$startFrom, #${pagination.pageSize}"
+ }
+ .getOrElse(sql"")
+ }
+
+ val qs = """`"""
+
+}
+
+abstract class SlickQueryBuilder[T](val parameters: SlickQueryBuilderParameters)(
+ implicit runner: SlickQueryBuilder.Runner[T],
+ countRunner: SlickQueryBuilder.CountRunner) {
+
+ def run()(implicit ec: ExecutionContext): Future[Seq[T]] = runner(parameters)
+
+ def runCount()(implicit ec: ExecutionContext): SlickQueryBuilder.CountResult = countRunner(parameters)
+
+ /**
+ * Runs the query and returns total found rows without considering of pagination.
+ */
+ def runWithCount()(implicit ec: ExecutionContext): Future[(Seq[T], Int, Option[LocalDateTime])] = {
+ for {
+ all <- run
+ (total, lastUpdate) <- runCount
+ } yield (all, total, lastUpdate)
+ }
+
+ def withFilter(newFilter: SearchFilterExpr): SlickQueryBuilder[T]
+
+ def withFilter(filter: Option[SearchFilterExpr]): SlickQueryBuilder[T] = {
+ filter.fold(this)(withFilter)
+ }
+
+ def resetFilter: SlickQueryBuilder[T] = withFilter(SearchFilterExpr.Empty)
+
+ def withSorting(newSorting: Sorting): SlickQueryBuilder[T]
+
+ def withSorting(sorting: Option[Sorting]): SlickQueryBuilder[T] = {
+ sorting.fold(this)(withSorting)
+ }
+
+ def resetSorting: SlickQueryBuilder[T] = withSorting(Sorting.Empty)
+
+ def withPagination(newPagination: Pagination): SlickQueryBuilder[T]
+
+ def withPagination(pagination: Option[Pagination]): SlickQueryBuilder[T] = {
+ pagination.fold(this)(withPagination)
+ }
+
+ def resetPagination: SlickQueryBuilder[T]
+
+}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/domain/FuzzyValue.scala b/src/main/scala/xyz/driver/pdsuicommon/domain/FuzzyValue.scala
index 4e98f40..36c3de7 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/domain/FuzzyValue.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/domain/FuzzyValue.scala
@@ -10,21 +10,31 @@ object FuzzyValue {
case object No extends FuzzyValue
case object Maybe extends FuzzyValue
- val All: Set[FuzzyValue] = Set(Yes, No, Maybe)
+ private val yes = "Yes"
+ private val no = "No"
+ private val maybe = "Maybe"
- def fromBoolean(x: Boolean): FuzzyValue = if (x) Yes else No
+ val All: Set[FuzzyValue] =
+ Set(Yes, No, Maybe)
- implicit def toPhiString(x: FuzzyValue): PhiString = Unsafe(Utils.getClassSimpleName(x.getClass))
+ def fromBoolean(x: Boolean): FuzzyValue =
+ if (x) Yes else No
+
+ implicit def toPhiString(x: FuzzyValue): PhiString =
+ Unsafe(Utils.getClassSimpleName(x.getClass))
val fromString: PartialFunction[String, FuzzyValue] = {
- case "Yes" => Yes
- case "No" => No
- case "Maybe" => Maybe
+ case fuzzy =>
+ fuzzy.toLowerCase.capitalize match {
+ case `yes` => Yes
+ case `no` => No
+ case `maybe` => Maybe
+ }
}
def valueToString(x: FuzzyValue): String = x match {
- case Yes => "Yes"
- case No => "No"
- case Maybe => "Maybe"
+ case Yes => `yes`
+ case No => `no`
+ case Maybe => `maybe`
}
}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParser.scala b/src/main/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParser.scala
index 8aff397..e0adeb8 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParser.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParser.scala
@@ -13,10 +13,15 @@ import scala.util.Try
object SearchFilterParser {
private object BinaryAtomFromTuple {
- def unapply(input: (SearchFilterExpr.Dimension, (String, String))): Option[SearchFilterExpr.Atom.Binary] = {
+ def unapply(input: (SearchFilterExpr.Dimension, (String, Any))): Option[SearchFilterExpr.Atom.Binary] = {
val (dimensionName, (strOperation, value)) = input
+ val updatedValue = value match {
+ case s: String => s.safeTrim
+ case a => a
+ }
+
parseOperation(strOperation.toLowerCase).map { op =>
- SearchFilterExpr.Atom.Binary(dimensionName, op, value.safeTrim)
+ SearchFilterExpr.Atom.Binary(dimensionName, op, updatedValue.asInstanceOf[AnyRef])
}
}
}
@@ -68,7 +73,7 @@ object SearchFilterParser {
}
private val numericOperatorParser: Parser[String] = {
- P((IgnoreCase("gt") | IgnoreCase("lt")) ~ IgnoreCase("eq").?).!
+ P(IgnoreCase("eq") | ((IgnoreCase("gt") | IgnoreCase("lt")) ~ IgnoreCase("eq").?)).!
}
private val naryOperatorParser: Parser[String] = P(IgnoreCase("in")).!
@@ -91,10 +96,12 @@ object SearchFilterParser {
private val nAryValueParser: Parser[String] = P(CharPred(_ != ',').rep(min = 1).!)
+ private val longParser: Parser[Long] = P(CharIn('0' to '9').rep(1).!.map(_.toLong))
+
private val binaryAtomParser: Parser[SearchFilterExpr.Atom.Binary] = P(
dimensionParser ~ whitespaceParser ~ (
- (commonOperatorParser.! ~/ whitespaceParser ~/ AnyChar.rep(min = 1).!)
- | (numericOperatorParser.! ~/ whitespaceParser ~/ numberParser.!)
+ (numericOperatorParser.! ~ whitespaceParser ~ (longParser | numberParser.!)) |
+ (commonOperatorParser.! ~ whitespaceParser ~ AnyChar.rep(min = 1).!)
) ~ End
).map {
case BinaryAtomFromTuple(atom) => atom
diff --git a/src/main/scala/xyz/driver/pdsuicommon/utils/CustomSwaggerJsonFormats.scala b/src/main/scala/xyz/driver/pdsuicommon/utils/CustomSwaggerJsonFormats.scala
index c1a2c7c..6c87858 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/utils/CustomSwaggerJsonFormats.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/utils/CustomSwaggerJsonFormats.scala
@@ -42,7 +42,7 @@ object CustomSwaggerJsonFormats {
xyz.driver.pdsuidomain.fakes.entities.trialcuration.nextTrialIssue()),
classOf[RichCriterion] -> richCriterionFormat.write(
xyz.driver.pdsuidomain.fakes.entities.trialcuration.nextRichCriterion()),
- classOf[InterventionWithArms] -> interventionWriter.write(
+ classOf[InterventionWithArms] -> interventionFormat.write(
xyz.driver.pdsuidomain.fakes.entities.trialcuration.nextInterventionWithArms()),
classOf[InterventionType] -> interventionTypeFormat.write(
xyz.driver.pdsuidomain.fakes.entities.trialcuration.nextInterventionType()),