From 8e0d19252ad6abf57d1e16c0258a57f39422b890 Mon Sep 17 00:00:00 2001 From: Anton Khodakivskiy Date: Sun, 20 Apr 2014 22:54:14 -0700 Subject: [PATCH] Allow users define custom functions --- .../fwbrasil/activate/cache/LiveCache.scala | 3 ++ .../activate/statement/StatementWhere.scala | 24 +++++++++++-- .../activate/statement/query/Query.scala | 2 +- .../storage/relational/idiom/QlIdiom.scala | 5 ++- .../net/fwbrasil/activate/ActivateTest.scala | 2 +- .../activate/statement/query/QuerySpecs.scala | 35 ++++++++++++++++++- 6 files changed, 65 insertions(+), 6 deletions(-) diff --git a/activate-core/src/main/scala/net/fwbrasil/activate/cache/LiveCache.scala b/activate-core/src/main/scala/net/fwbrasil/activate/cache/LiveCache.scala index 09bd7e42..834768e2 100644 --- a/activate-core/src/main/scala/net/fwbrasil/activate/cache/LiveCache.scala +++ b/activate-core/src/main/scala/net/fwbrasil/activate/cache/LiveCache.scala @@ -63,6 +63,7 @@ import net.fwbrasil.activate.statement.FunctionApply import net.fwbrasil.activate.statement.ToUpperCase import net.fwbrasil.activate.entity.StringEntityValue import net.fwbrasil.activate.statement.ToLowerCase +import net.fwbrasil.activate.statement.ToUserFunction import com.google.common.collect.MapMaker import java.util.concurrent.ConcurrentMap import CacheType._ @@ -538,6 +539,8 @@ class LiveCache( executeStatementSelectValue(value.value).asInstanceOf[String].toUpperCase() case value: ToLowerCase => executeStatementSelectValue(value.value).asInstanceOf[String].toLowerCase() + case ToUserFunction(fn, value) => + fn.applyGeneric(executeStatementSelectValue(value)) } def executeStatementSelectValue(value: StatementSelectValue)(implicit entitySourceInstancesMap: Map[EntitySource, BaseEntity]): Any = diff --git a/activate-core/src/main/scala/net/fwbrasil/activate/statement/StatementWhere.scala b/activate-core/src/main/scala/net/fwbrasil/activate/statement/StatementWhere.scala index 8baab5fa..84d74c9f 100644 --- a/activate-core/src/main/scala/net/fwbrasil/activate/statement/StatementWhere.scala +++ b/activate-core/src/main/scala/net/fwbrasil/activate/statement/StatementWhere.scala @@ -22,6 +22,8 @@ case class SimpleStatementBooleanValue(value: Boolean)(implicit val tval: Boolea trait OperatorContext { import language.implicitConversions + type UserFunction[V, U] = OperatorContext.UserFunction[V, U] + implicit def toAnd(value: StatementBooleanValue) = And(value) implicit def toOr(value: StatementBooleanValue) = Or(value) implicit def toIsEqualTo[V](value: V)(implicit tval1: (=> V) => StatementSelectValue) = IsEqualTo(value) @@ -38,11 +40,29 @@ trait OperatorContext { def toUpperCase(value: String)(implicit tval1: (=> String) => StatementSelectValue) = ToUpperCase(value) def toLowerCase(value: String)(implicit tval1: (=> String) => StatementSelectValue) = ToLowerCase(value) + + def toUserFunction[U, V](fn: UserFunction[U, V])(value: U)(implicit tval1: (=> U) => StatementSelectValue, tval2: (=> V) => StatementSelectValue) = ToUserFunction(fn, value) +} + +object OperatorContext { + abstract class UserFunction[U, V] { + def apply(value: U): V + def toStorage(value: String): String + + def applyGeneric(value: Any): V = apply(value.asInstanceOf[U]) + } } +import OperatorContext.UserFunction + class SimpleOperator() extends Operator class CompositeOperator() extends Operator +case class ToUserFunction(fn: UserFunction[_, _], value: StatementSelectValue) extends FunctionApply(value) { + override def toString = s"$fn($value)" + def entityValue = value.entityValue.value.map(fn.applyGeneric) +} + case class Matcher(valueA: StatementSelectValue) extends CompositeOperator { def like(valueB: => String)(implicit f: Option[String] => EntityValue[String]) = CompositeOperatorCriteria(valueA, this, SimpleValue(() => wildcardToRegex(valueB), f)) @@ -75,7 +95,7 @@ case class ToUpperCase(value: StatementSelectValue) extends FunctionApply(value) case class ToLowerCase(value: StatementSelectValue) extends FunctionApply(value) { override def toString = s"toLowerCase($value)" - def entityValue = value.entityValue.asInstanceOf[StringEntityValue].value.map(_.toUpperCase) + def entityValue = value.entityValue.asInstanceOf[StringEntityValue].value.map(_.toLowerCase) } case class IsNotNull(valueA: StatementSelectValue) extends SimpleOperator { @@ -182,4 +202,4 @@ case class Where(valueOption: Option[Criteria]) { tval4(value4))) override def toString = valueOption.map(_.toString).getOrElse("()") -} \ No newline at end of file +} diff --git a/activate-core/src/main/scala/net/fwbrasil/activate/statement/query/Query.scala b/activate-core/src/main/scala/net/fwbrasil/activate/statement/query/Query.scala index 3ddb090b..3565ff84 100644 --- a/activate-core/src/main/scala/net/fwbrasil/activate/statement/query/Query.scala +++ b/activate-core/src/main/scala/net/fwbrasil/activate/statement/query/Query.scala @@ -64,4 +64,4 @@ class Query[S](override val from: From, override val where: Where, val select: S case class Select(values: StatementSelectValue*) { override def toString = "(" + values.mkString(", ") + ")" -} \ No newline at end of file +} diff --git a/activate-core/src/main/scala/net/fwbrasil/activate/storage/relational/idiom/QlIdiom.scala b/activate-core/src/main/scala/net/fwbrasil/activate/storage/relational/idiom/QlIdiom.scala index a0fd8cee..25e94ce6 100644 --- a/activate-core/src/main/scala/net/fwbrasil/activate/storage/relational/idiom/QlIdiom.scala +++ b/activate-core/src/main/scala/net/fwbrasil/activate/storage/relational/idiom/QlIdiom.scala @@ -20,6 +20,7 @@ import net.fwbrasil.activate.statement.IsLessThan import net.fwbrasil.activate.statement.IsNotEqualTo import net.fwbrasil.activate.statement.IsNotNull import net.fwbrasil.activate.statement.IsNull +import net.fwbrasil.activate.statement.ToUserFunction import net.fwbrasil.activate.statement.Matcher import net.fwbrasil.activate.statement.Operator import net.fwbrasil.activate.statement.Or @@ -280,6 +281,8 @@ trait QlIdiom { stringUpperFunction(toSqlDml(value.value)) case value: ToLowerCase => stringLowerFunction(toSqlDml(value.value)) + case ToUserFunction(fn, value) => + fn.toStorage(toSqlDml(value)) } def stringUpperFunction(value: String): String = s"UPPER($value)" @@ -468,4 +471,4 @@ trait QlIdiom { res } -} \ No newline at end of file +} diff --git a/activate-test/src/test/scala/net/fwbrasil/activate/ActivateTest.scala b/activate-test/src/test/scala/net/fwbrasil/activate/ActivateTest.scala index 21a3bd15..3ec9734b 100644 --- a/activate-test/src/test/scala/net/fwbrasil/activate/ActivateTest.scala +++ b/activate-test/src/test/scala/net/fwbrasil/activate/ActivateTest.scala @@ -266,4 +266,4 @@ trait ActivateTest extends SpecificationWithJUnit with Serializable { } } -} \ No newline at end of file +} diff --git a/activate-test/src/test/scala/net/fwbrasil/activate/statement/query/QuerySpecs.scala b/activate-test/src/test/scala/net/fwbrasil/activate/statement/query/QuerySpecs.scala index 2603d3f2..9ac44a85 100644 --- a/activate-test/src/test/scala/net/fwbrasil/activate/statement/query/QuerySpecs.scala +++ b/activate-test/src/test/scala/net/fwbrasil/activate/statement/query/QuerySpecs.scala @@ -10,6 +10,7 @@ import net.fwbrasil.activate.mongoContext import net.fwbrasil.activate.polyglotContext import net.fwbrasil.activate.ActivateContext import net.fwbrasil.activate.asyncMongoContext +import net.fwbrasil.activate.mysqlContext import net.fwbrasil.activate.migration.StorageVersion import net.fwbrasil.activate.multipleVms.CustomEncodedEntityValue @@ -576,6 +577,38 @@ class QuerySpecs extends ActivateTest { } + "support user function" in { + activateTest( + (step: StepExecutor) => { + val excludedContexts = Seq(mongoContext, asyncMongoContext, mysqlContext) + if (!excludedContexts.contains(step.ctx)) { + import step.ctx._ + val string1 = "-1" + val string2 = "-2" + import net.fwbrasil.activate.statement.StatementSelectValue + + object absFunc extends UserFunction[String, Int] { + def apply(value: String) = value.toInt.abs + def toStorage(value: String) = s"ABS(CAST($value as INTEGER))" + } + + def abs(value: String)(implicit tval1: (=> String) => StatementSelectValue) = toUserFunction(absFunc)(value) + step { + newEmptyActivateTestEntity.stringValue = string1 + newEmptyActivateTestEntity.stringValue = string2 + } + step { + val result = select[ActivateTestEntity].where(e => toUserFunction(absFunc)(e.stringValue) :== string1.toInt.abs) + result.size === 1 + } + step { + val result = select[ActivateTestEntity].where(e => abs(e.stringValue) :>= string1.toInt.abs) + result.size === 2 + } + } + }) + } + "support query with many results" in { activateTest( (step: StepExecutor) => { @@ -747,4 +780,4 @@ class QuerySpecs extends ActivateTest { }) } } -} \ No newline at end of file +}