From a2589fd65cb75562486002222ba69d37cb6af9fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Manciot?= Date: Mon, 13 Oct 2025 08:02:45 +0200 Subject: [PATCH 01/21] add PainlessContext --- build.sbt | 2 +- .../elastic/sql/function/cond/package.scala | 12 +- .../sql/function/convert/package.scala | 12 +- .../elastic/sql/function/geo/package.scala | 5 +- .../elastic/sql/function/math/package.scala | 14 +- .../elastic/sql/function/package.scala | 2 +- .../elastic/sql/function/string/package.scala | 31 +++-- .../elastic/sql/function/time/package.scala | 31 ++--- .../operator/math/ArithmeticExpression.scala | 2 +- .../elastic/sql/operator/package.scala | 2 +- .../elastic/sql/operator/time/package.scala | 8 +- .../app/softnetwork/elastic/sql/package.scala | 120 +++++++++++++++--- .../elastic/sql/query/Select.scala | 3 +- .../softnetwork/elastic/sql/query/Where.scala | 28 ++-- .../elastic/sql/time/package.scala | 9 +- .../elastic/sql/type/SQLTypeUtils.scala | 2 +- .../sql/SQLDateTimeFunctionSuite.scala | 4 - 17 files changed, 198 insertions(+), 89 deletions(-) diff --git a/build.sbt b/build.sbt index 3bf483e7..610063bf 100644 --- a/build.sbt +++ b/build.sbt @@ -19,7 +19,7 @@ ThisBuild / organization := "app.softnetwork" name := "softclient4es" -ThisBuild / version := "0.9.2" +ThisBuild / version := "0.9.3" ThisBuild / scalaVersion := scala213 diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala index 7a870dbc..93146db1 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala @@ -1,13 +1,13 @@ package app.softnetwork.elastic.sql.function -import app.softnetwork.elastic.sql.{Expr, Identifier, PainlessScript, TokenRegex} +import app.softnetwork.elastic.sql.{Expr, Identifier, PainlessContext, PainlessScript, TokenRegex} import app.softnetwork.elastic.sql.`type`.{SQLAny, SQLBool, SQLType, SQLTypeUtils, SQLTypes} import app.softnetwork.elastic.sql.query.Expression package object cond { sealed trait ConditionalOp extends PainlessScript with TokenRegex { - override def painless(): String = sql + override def painless(context: Option[PainlessContext] = None): String = sql } case object Coalesce extends Expr("COALESCE") with ConditionalOp @@ -44,7 +44,7 @@ package object cond { override def toSQL(base: String): String = sql - override def painless(): String = s" == null" + override def painless(context: Option[PainlessContext] = None): String = s" == null" override def toPainless(base: String, idx: Int): String = { if (nullable) s"(def e$idx = $base; e$idx${painless()})" @@ -62,7 +62,7 @@ package object cond { override def toSQL(base: String): String = sql - override def painless(): String = s" != null" + override def painless(context: Option[PainlessContext] = None): String = s" != null" override def toPainless(base: String, idx: Int): String = { if (nullable) s"(def e$idx = $base; e$idx${painless()})" @@ -101,7 +101,7 @@ package object cond { override def toPainless(base: String, idx: Int): String = s"$base${painless()}" - override def painless(): String = { + override def painless(context: Option[PainlessContext] = None): String = { require(values.nonEmpty, "COALESCE requires at least one argument") val checks = values @@ -183,7 +183,7 @@ package object cond { else Right(()) } - override def painless(): String = { + override def painless(context: Option[PainlessContext] = None): String = { val base = expression match { case Some(expr) => diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/convert/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/convert/package.scala index 0f74f7b9..cf85f96d 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/convert/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/convert/package.scala @@ -1,6 +1,13 @@ package app.softnetwork.elastic.sql.function -import app.softnetwork.elastic.sql.{Alias, DateMathRounding, Expr, PainlessScript, TokenRegex} +import app.softnetwork.elastic.sql.{ + Alias, + DateMathRounding, + Expr, + PainlessContext, + PainlessScript, + TokenRegex +} import app.softnetwork.elastic.sql.`type`.{SQLType, SQLTypeUtils} package object convert { @@ -19,7 +26,8 @@ package object convert { //override def nullable: Boolean = value.nullable - override def painless(): String = SQLTypeUtils.coerce(value, targetType) + override def painless(context: Option[PainlessContext] = None): String = + SQLTypeUtils.coerce(value, targetType) override def toPainless(base: String, idx: Int): String = { val ret = SQLTypeUtils.coerce(base, value.baseType, targetType, value.nullable) diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/geo/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/geo/package.scala index a574ff0c..74490996 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/geo/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/geo/package.scala @@ -5,6 +5,7 @@ import app.softnetwork.elastic.sql.{ DoubleValue, Expr, Identifier, + PainlessContext, PainlessParams, PainlessScript, Token, @@ -57,7 +58,7 @@ package object geo { case object Distance extends Expr("ST_DISTANCE") with Function with Operator { override def words: List[String] = List(sql, "DISTANCE") - override def painless(): String = ".arcDistance" + override def painless(context: Option[PainlessContext] = None): String = ".arcDistance" def haversine(lat1: Double, lon1: Double, lat2: Double, lon2: Double): Double = { val R = 6371e3 // Radius of the earth in meters @@ -125,7 +126,7 @@ package object geo { else Map.empty - override def painless(): String = { + override def painless(context: Option[PainlessContext]): String = { val nullCheck = identifiers.zipWithIndex .map { case (_, i) => s"arg$i == null" } diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/math/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/math/package.scala index 0e49d227..a5826c56 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/math/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/math/package.scala @@ -1,12 +1,20 @@ package app.softnetwork.elastic.sql.function -import app.softnetwork.elastic.sql.{Expr, Identifier, IntValue, PainlessScript, TokenRegex} +import app.softnetwork.elastic.sql.{ + Expr, + Identifier, + IntValue, + PainlessContext, + PainlessScript, + TokenRegex +} import app.softnetwork.elastic.sql.`type`.{SQLNumeric, SQLType, SQLTypes} package object math { sealed trait MathOp extends PainlessScript with TokenRegex { - override def painless(): String = s"Math.${sql.toLowerCase()}" + override def painless(context: Option[PainlessContext] = None): String = + s"Math.${sql.toLowerCase()}" override def toString: String = s" $sql " override def baseType: SQLNumeric = SQLTypes.Numeric @@ -88,7 +96,7 @@ package object math { override def args: List[PainlessScript] = List(arg) - override def painless(): String = { + override def painless(context: Option[PainlessContext]): String = { val ret = "arg0 > 0 ? 1 : (arg0 < 0 ? -1 : 0)" if (arg.nullable) s"(def arg0 = ${arg.painless()}; arg0 != null ? ($ret) : null)" diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala index ea2d9aee..e706164a 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala @@ -124,7 +124,7 @@ package object function { override def toSQL(base: String): String = s"$base$sql" - override def painless(): String = { + override def painless(context: Option[PainlessContext]): String = { val nullCheck = args.zipWithIndex .filter(_._1.nullable) diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/string/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/string/package.scala index 5e985fe7..e7b10414 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/string/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/string/package.scala @@ -1,6 +1,13 @@ package app.softnetwork.elastic.sql.function -import app.softnetwork.elastic.sql.{Expr, Identifier, IntValue, PainlessScript, TokenRegex} +import app.softnetwork.elastic.sql.{ + Expr, + Identifier, + IntValue, + PainlessContext, + PainlessScript, + TokenRegex +} import app.softnetwork.elastic.sql.`type`.{ SQLBigInt, SQLBool, @@ -13,14 +20,14 @@ import app.softnetwork.elastic.sql.`type`.{ package object string { sealed trait StringOp extends PainlessScript with TokenRegex { - override def painless(): String = s".${sql.toLowerCase()}()" + override def painless(context: Option[PainlessContext]): String = s".${sql.toLowerCase()}()" } case object Concat extends Expr("CONCAT") with StringOp { - override def painless(): String = " + " + override def painless(context: Option[PainlessContext]): String = " + " } case object Pipe extends Expr("\\|\\|") with StringOp { - override def painless(): String = " + " + override def painless(context: Option[PainlessContext]): String = " + " } case object Lower extends Expr("LOWER") with StringOp { override lazy val words: List[String] = List(sql, "LCASE") @@ -30,13 +37,15 @@ package object string { } case object Trim extends Expr("TRIM") with StringOp case object Ltrim extends Expr("LTRIM") with StringOp { - override def painless(): String = ".replaceAll(\"^\\\\s+\",\"\")" + override def painless(context: Option[PainlessContext]): String = + ".replaceAll(\"^\\\\s+\",\"\")" } case object Rtrim extends Expr("RTRIM") with StringOp { - override def painless(): String = ".replaceAll(\"\\\\s+$\",\"\")" + override def painless(context: Option[PainlessContext]): String = + ".replaceAll(\"\\\\s+$\",\"\")" } case object Substring extends Expr("SUBSTRING") with StringOp { - override def painless(): String = ".substring" + override def painless(context: Option[PainlessContext]): String = ".substring" override lazy val words: List[String] = List(sql, "SUBSTR") } case object LeftOp extends Expr("LEFT") with StringOp @@ -47,22 +56,22 @@ package object string { } case object Replace extends Expr("REPLACE") with StringOp { override lazy val words: List[String] = List(sql, "STR_REPLACE") - override def painless(): String = ".replace" + override def painless(context: Option[PainlessContext]): String = ".replace" } case object Reverse extends Expr("REVERSE") with StringOp case object Position extends Expr("POSITION") with StringOp { override lazy val words: List[String] = List(sql, "STRPOS") - override def painless(): String = ".indexOf" + override def painless(context: Option[PainlessContext]): String = ".indexOf" } case object RegexpLike extends Expr("REGEXP_LIKE") with StringOp { override lazy val words: List[String] = List(sql, "REGEXP") - override def painless(): String = ".matches" + override def painless(context: Option[PainlessContext]): String = ".matches" } case class MatchFlags(flags: String) extends PainlessScript { override def sql: String = s"'$flags'" - override def painless(): String = flags.toCharArray + override def painless(context: Option[PainlessContext]): String = flags.toCharArray .map { case 'i' => "java.util.regex.Pattern.CASE_INSENSITIVE" case 'c' => "0" diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala index aaf30401..3ed3c0a0 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala @@ -5,6 +5,7 @@ import app.softnetwork.elastic.sql.{ DateMathScript, Expr, Identifier, + PainlessContext, PainlessScript, StringValue, TokenRegex @@ -104,17 +105,17 @@ package object time { } sealed trait CurrentDateTimeFunction extends DateTimeFunction with CurrentFunction { - override def painless(): String = + override def painless(context: Option[PainlessContext]): String = SQLTypeUtils.coerce(now, this.baseType, this.out, nullable = false) } sealed trait CurrentDateFunction extends DateFunction with CurrentFunction { - override def painless(): String = + override def painless(context: Option[PainlessContext]): String = SQLTypeUtils.coerce(s"$now.toLocalDate()", this.baseType, this.out, nullable = false) } sealed trait CurrentTimeFunction extends TimeFunction with CurrentFunction { - override def painless(): String = + override def painless(context: Option[PainlessContext]): String = SQLTypeUtils.coerce(s"$now.toLocalTime()", this.baseType, this.out, nullable = false) } @@ -161,7 +162,7 @@ package object time { } case object DateTrunc extends Expr("DATE_TRUNC") with TokenRegex with PainlessScript { - override def painless(): String = ".truncatedTo" + override def painless(context: Option[PainlessContext]): String = ".truncatedTo" override lazy val words: List[String] = List(sql, "DATETRUNC") } @@ -188,7 +189,7 @@ package object time { } case object Extract extends Expr("EXTRACT") with TokenRegex with PainlessScript { - override def painless(): String = ".get" + override def painless(context: Option[PainlessContext]): String = ".get" } case class Extract(field: TimeField) @@ -248,7 +249,7 @@ package object time { class WeekOfWeekBasedYear extends TimeFieldExtract(WEEK_OF_WEEK_BASED_YEAR) case object LastDayOfMonth extends Expr("LAST_DAY") with TokenRegex with PainlessScript { - override def painless(): String = ".withDayOfMonth" + override def painless(context: Option[PainlessContext]): String = ".withDayOfMonth" override lazy val words: List[String] = List(sql, "LASTDAY") } @@ -289,7 +290,7 @@ package object time { } case object DateDiff extends Expr("DATE_DIFF") with TokenRegex with PainlessScript { - override def painless(): String = ".between" + override def painless(context: Option[PainlessContext]): String = ".between" override lazy val words: List[String] = List(sql, "DATEDIFF") } @@ -395,7 +396,7 @@ package object time { } case object DateParse extends Expr("DATE_PARSE") with TokenRegex with PainlessScript { - override def painless(): String = ".parse" + override def painless(context: Option[PainlessContext]): String = ".parse" } case class DateParse(identifier: Identifier, format: String) @@ -416,7 +417,7 @@ package object time { s"$sql($base, '$format')" } - override def painless(): String = throw new NotImplementedError( + override def painless(context: Option[PainlessContext]): String = throw new NotImplementedError( "Use toPainless instead" ) override def toPainless(base: String, idx: Int): String = @@ -442,7 +443,7 @@ package object time { } case object DateFormat extends Expr("DATE_FORMAT") with TokenRegex with PainlessScript { - override def painless(): String = ".format" + override def painless(context: Option[PainlessContext]): String = ".format" } case class DateFormat(identifier: Identifier, format: String) @@ -462,7 +463,7 @@ package object time { s"$sql($base, '$format')" } - override def painless(): String = throw new NotImplementedError( + override def painless(context: Option[PainlessContext]): String = throw new NotImplementedError( "Use toPainless instead" ) override def toPainless(base: String, idx: Int): String = @@ -509,7 +510,7 @@ package object time { } case object DateTimeParse extends Expr("DATETIME_PARSE") with TokenRegex with PainlessScript { - override def painless(): String = ".parse" + override def painless(context: Option[PainlessContext]): String = ".parse" } case class DateTimeParse(identifier: Identifier, format: String) @@ -530,7 +531,7 @@ package object time { s"$sql($base, '$format')" } - override def painless(): String = throw new NotImplementedError( + override def painless(context: Option[PainlessContext]): String = throw new NotImplementedError( "Use toPainless instead" ) override def toPainless(base: String, idx: Int): String = @@ -556,7 +557,7 @@ package object time { } case object DateTimeFormat extends Expr("DATETIME_FORMAT") with TokenRegex with PainlessScript { - override def painless(): String = ".format" + override def painless(context: Option[PainlessContext]): String = ".format" } case class DateTimeFormat(identifier: Identifier, format: String) @@ -576,7 +577,7 @@ package object time { s"$sql($base, '$format')" } - override def painless(): String = throw new NotImplementedError( + override def painless(context: Option[PainlessContext]): String = throw new NotImplementedError( "Use toPainless instead" ) override def toPainless(base: String, idx: Int): String = diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/operator/math/ArithmeticExpression.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/operator/math/ArithmeticExpression.scala index bdc702ca..87549a6a 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/operator/math/ArithmeticExpression.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/operator/math/ArithmeticExpression.scala @@ -71,7 +71,7 @@ case class ArithmeticExpression( s"$base${painless()}" } - override def painless(): String = { + override def painless(context: Option[PainlessContext]): String = { val l = SQLTypeUtils.coerce(left, out) val r = SQLTypeUtils.coerce(right, out) val expr = s"$l ${operator.painless()} $r" diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/operator/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/operator/package.scala index d63c7a97..1d1f2d16 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/operator/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/operator/package.scala @@ -3,7 +3,7 @@ package app.softnetwork.elastic.sql package object operator { trait Operator extends Token with PainlessScript with TokenRegex { - override def painless(): String = this match { + override def painless(context: Option[PainlessContext]): String = this match { case AND => "&&" case OR => "||" case NOT => "!" diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/operator/time/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/operator/time/package.scala index 828a4191..0683049b 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/operator/time/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/operator/time/package.scala @@ -1,13 +1,13 @@ package app.softnetwork.elastic.sql.operator -import app.softnetwork.elastic.sql.{DateMathScript, Expr} +import app.softnetwork.elastic.sql.{DateMathScript, Expr, PainlessContext} package object time { sealed trait IntervalOperator extends Operator with BinaryOperator with DateMathScript { override def script: Option[String] = Some(sql) override def toString: String = s" $sql " - override def painless(): String = this match { + override def painless(context: Option[PainlessContext]): String = this match { case PLUS => ".plus" case MINUS => ".minus" case _ => sql @@ -15,11 +15,11 @@ package object time { } case object PLUS extends Expr("+") with IntervalOperator { - override def painless(): String = ".plus" + override def painless(context: Option[PainlessContext]): String = ".plus" } case object MINUS extends Expr("-") with IntervalOperator { - override def painless(): String = ".minus" + override def painless(context: Option[PainlessContext]): String = ".minus" } } diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala index 4e4e05af..4885eea5 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala @@ -27,6 +27,8 @@ package object sql { case _ => "" } + /** Base trait for all tokens + */ trait Token extends Serializable with Validation { def sql: String override def toString: String = sql @@ -51,21 +53,79 @@ package object sql { def value: Any } + /** Trait for tokens that can be used in painless scripts + */ trait PainlessScript extends Token { - def painless(): String + + /** Generate painless script for this token + * + * @param context + * the painless context + * @return + * the painless script + */ + def painless(context: Option[PainlessContext] = None): String def nullValue: String = "null" } + /** Trait for tokens that can be used as parameters in painless scripts + */ + trait PainlessParam extends Token { + def param: String + def checkNotNull: String + override def hashCode(): Int = param.hashCode + override def equals(obj: Any): Boolean = { + obj match { + case p: PainlessParam => p.param == param + case _ => false + } + } + + override def toString: String = + if (nullable) + checkNotNull + else + param + } + + /** Context for painless scripts + */ + case class PainlessContext() { + // Map of parameters to their names + private[this] var params: collection.mutable.Map[PainlessParam, String] = + collection.mutable.Map.empty + + /** Add a parameter to the context if not already present + * + * @param param + * the parameter to add + * @return + * the parameter name + */ + def addParam(param: PainlessParam): String = { + params.get(param) match { + case Some(p) => p + case _ => + val paramName = s"param${params.size + 1}" + params = params ++ Map((param -> paramName)) + paramName + } + } + + override def toString: String = + params.map { case (k, v) => s"def $v=$k" }.mkString("; ") + } + trait PainlessParams extends PainlessScript { def params: Map[String, Any] } + /** Trait for tokens that can be used in date math scripts + */ trait DateMathScript extends Token { def script: Option[String] - def hasScript: Boolean = script.isDefined override def dateMathScript: Boolean = true def formatScript: Option[String] = None - def hasFormat: Boolean = formatScript.isDefined } object DateMathRounding { @@ -114,7 +174,7 @@ package object sql { case _ => values.headOption } } - override def painless(): String = + override def painless(context: Option[PainlessContext]): String = SQLTypeUtils.coerce( value match { case s: String => s""""$s"""" @@ -132,7 +192,7 @@ package object sql { case object Null extends Value[Null](null) with TokenRegex { override def sql: String = "NULL" - override def painless(): String = "null" + override def painless(context: Option[PainlessContext]): String = "null" override def nullable: Boolean = true override def baseType: SQLType = SQLTypes.Null } @@ -236,13 +296,13 @@ package object sql { case object PiValue extends Value[Double](Math.PI) with TokenRegex { override def sql: String = "PI" - override def painless(): String = "Math.PI" + override def painless(context: Option[PainlessContext]): String = "Math.PI" override def baseType: SQLNumeric = SQLTypes.Double } case object EValue extends Value[Double](Math.E) with TokenRegex { override def sql: String = "E" - override def painless(): String = "Math.E" + override def painless(context: Option[PainlessContext]): String = "Math.E" override def baseType: SQLNumeric = SQLTypes.Double } @@ -252,7 +312,7 @@ package object sql { override def baseType: SQLNumeric = SQLTypes.Double override def sql: String = s"$longValue $unit" def geoDistance: String = s"$longValue$unit" - override def painless(): String = s"$value" + override def painless(context: Option[PainlessContext]): String = s"$value" } sealed abstract class FromTo(val from: TokenValue, val to: TokenValue) extends Token { @@ -317,7 +377,7 @@ package object sql { extends Token with PainlessScript { override def sql = s"(${values.map(_.sql).mkString(",")})" - override def painless(): String = + override def painless(context: Option[PainlessContext]): String = s"[${values.map(_.painless()).mkString(",")}]" lazy val innerValues: Seq[R] = values.map(_.value) override def nullable: Boolean = values.exists(_.nullable) @@ -450,7 +510,8 @@ package object sql { with Source with FunctionChain with PainlessScript - with DateMathScript { + with DateMathScript + with PainlessParam { def name: String def withFunctions(functions: List[Function]): Identifier @@ -500,9 +561,13 @@ package object sql { lazy val identifierName: String = functions.reverse.foldLeft(name)((expr, fun) => { fun.toSQL(expr) - }) // FIXME use AliasUtils.normalize? + }) // TODO use AliasUtils.normalize? - lazy val innerHitsName: Option[String] = if (nested) tableAlias else None + lazy val innerHitsName: Option[String] = + nestedElement match { + case Some(ne) => Some(ne.innerHitsName) + case None => None + } lazy val aliasOrName: String = fieldAlias.getOrElse(name) @@ -590,12 +655,31 @@ package object sql { else s"(!doc.containsKey('$path') || doc['$path'].empty ? $nullValue : doc['$path'].value)" - override def painless(): String = toPainless( - if (nullable) - checkNotNull - else - paramName - ) + override def painless(context: Option[PainlessContext]): String = { + val base = + context match { + case Some(ctx) => + ctx.addParam(this) + ctx.toString + case _ => + if (nullable) + checkNotNull + else + paramName + } + val orderedFunctions = FunctionUtils.transformFunctions(this).reverse + var expr = base + orderedFunctions.zipWithIndex.foreach { case (f, idx) => + f match { + case f: TransformFunction[_, _] => expr = f.toPainless(expr, idx) + case f: PainlessScript => expr = s"$expr${f.painless(context)}" + case f => expr = f.toSQL(expr) // fallback + } + } + expr + } + + override def param: String = paramName private[this] var _nullable = this.name.nonEmpty && (!aggregation || functions.size > 1) diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/Select.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/Select.scala index 5cd3a74e..53efcf28 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/Select.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/Select.scala @@ -9,6 +9,7 @@ import app.softnetwork.elastic.sql.{ DateMathScript, Expr, Identifier, + PainlessContext, PainlessScript, TokenRegex, Updateable @@ -64,7 +65,7 @@ case class Field( this.copy(identifier = updated.update(request)) } - def painless(): String = identifier.painless() + def painless(context: Option[PainlessContext]): String = identifier.painless(context) def script: Option[String] = identifier.script diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/Where.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/Where.scala index 512695af..8cc01462 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/Where.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/Where.scala @@ -106,22 +106,22 @@ sealed trait Criteria extends Updateable with PainlessScript { override def out: SQLType = SQLTypes.Boolean - override def painless(): String = this match { + override def painless(context: Option[PainlessContext]): String = this match { case Predicate(left, op, right, maybeNot, group) => - val leftStr = left.painless() - val rightStr = right.painless() + val leftStr = left.painless(context) + val rightStr = right.painless(context) val opStr = op match { - case AND | OR => op.painless() + case AND | OR => op.painless(context) case _ => throw new IllegalArgumentException(s"Unsupported logical operator: $op") } val not = maybeNot.nonEmpty if (group || not) - s"${maybeNot.map(_.painless()).getOrElse("")}($leftStr $opStr $rightStr)" + s"${maybeNot.map(_.painless(context)).getOrElse("")}($leftStr $opStr $rightStr)" else s"$leftStr $opStr $rightStr" - case relation: ElasticRelation => asGroup(relation.criteria.painless()) - case m: MatchCriteria => asGroup(m.criteria.painless()) - case expr: Expression => asGroup(expr.painless()) + case relation: ElasticRelation => asGroup(relation.criteria.painless(context)) + case m: MatchCriteria => asGroup(m.criteria.painless(context)) + case expr: Expression => asGroup(expr.painless(context)) case _ => throw new IllegalArgumentException(s"Unsupported criteria: $this") } } @@ -380,7 +380,7 @@ sealed trait Expression extends FunctionChain with ElasticFilter with Criteria { case _ => s"$painlessOp($painlessValue)" } - override def painless(): String = { + override def painless(context: Option[PainlessContext]): String = { if (identifier.nullable) { return s"def left = $left; left == null ? false : ${painlessNot}left$check" } @@ -496,7 +496,7 @@ case class IsNullCriteria(identifier: Identifier) extends CriteriaWithConditiona } else updated } - override def painless(): String = { + override def painless(context: Option[PainlessContext]): String = { if (identifier.nullable) { return s"def left = $left; left == null" } @@ -519,7 +519,7 @@ case class IsNotNullCriteria(identifier: Identifier) updated } - override def painless(): String = { + override def painless(context: Option[PainlessContext]): String = { if (identifier.nullable) { return s"def left = $left; left != null" } @@ -568,7 +568,7 @@ case class InExpr[R, +T <: Value[R]]( override def asFilter(currentQuery: Option[ElasticBoolQuery]): ElasticFilter = this - override def painless(): String = + override def painless(context: Option[PainlessContext]): String = s"$painlessNot${identifier.painless()}$painlessOp($painlessValue)" } @@ -600,7 +600,7 @@ case class BetweenExpr( } yield () } - override def painless(): String = { + override def painless(context: Option[PainlessContext]): String = { if (identifier.nullable) { return s"def left = $left; left == null ? false : $painlessNot(${fromTo.from} <= left <= ${fromTo.to})" } @@ -705,7 +705,7 @@ case class ElasticMatch( override def matchCriteria: Boolean = true - override def painless(): String = + override def painless(context: Option[PainlessContext]): String = s"$painlessNot${identifier.painless()}$painlessOp($painlessValue)" } diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/time/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/time/package.scala index cbc2cad5..cb6eff37 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/time/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/time/package.scala @@ -7,7 +7,7 @@ import scala.util.matching.Regex package object time { sealed trait TimeField extends PainlessScript with TokenRegex { - override def painless(): String = s"ChronoField.$timeField" + override def painless(context: Option[PainlessContext]): String = s"ChronoField.$timeField" override def nullable: Boolean = false @@ -62,7 +62,8 @@ package object time { sealed trait IsoField extends TimeField { def isoField: String def timeField: String = isoField - override def painless(): String = s"java.time.temporal.IsoFields.$isoField" + override def painless(context: Option[PainlessContext]): String = + s"java.time.temporal.IsoFields.$isoField" } object IsoField { @@ -82,7 +83,7 @@ package object time { def timeUnit: String = sql.toUpperCase() + "S" - override def painless(): String = s"ChronoUnit.$timeUnit" + override def painless(context: Option[PainlessContext]): String = s"ChronoUnit.$timeUnit" override def nullable: Boolean = false @@ -134,7 +135,7 @@ package object time { def unit: TimeUnit override def sql: String = s"$Interval $value ${unit.sql}" - override def painless(): String = s"$value, ${unit.painless()}" + override def painless(context: Option[PainlessContext]): String = s"$value, ${unit.painless()}" override def script: Option[String] = Some(TimeInterval.script(this)) diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypeUtils.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypeUtils.scala index 57159f9e..ec791209 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypeUtils.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypeUtils.scala @@ -88,7 +88,7 @@ object SQLTypeUtils { } def coerce(in: PainlessScript, to: SQLType): String = { - val expr = in.painless + val expr = in.painless() val from = in.baseType val nullable = in.nullable coerce(expr, from, to, nullable) diff --git a/sql/src/test/scala/app/softnetwork/elastic/sql/SQLDateTimeFunctionSuite.scala b/sql/src/test/scala/app/softnetwork/elastic/sql/SQLDateTimeFunctionSuite.scala index d0834bdf..4a9e3621 100644 --- a/sql/src/test/scala/app/softnetwork/elastic/sql/SQLDateTimeFunctionSuite.scala +++ b/sql/src/test/scala/app/softnetwork/elastic/sql/SQLDateTimeFunctionSuite.scala @@ -4,7 +4,6 @@ import org.scalatest.funsuite.AnyFunSuite import app.softnetwork.elastic.sql.function._ import app.softnetwork.elastic.sql.function.time._ import app.softnetwork.elastic.sql.time._ -import TimeField._ import app.softnetwork.elastic.sql.`type`.SQLType class SQLDateTimeFunctionSuite extends AnyFunSuite { @@ -81,9 +80,6 @@ class SQLDateTimeFunctionSuite extends AnyFunSuite { val names = chain.map(_.sql).mkString(" -> ") test(s"Valid chain $idx: $names") { val chained = chainTransformsTyped(baseDate, chain) - val expected = chain.reverse.tail.foldLeft(chain.last.toPainless(baseDate, 0)) { (expr, f) => - f.toPainless(expr, 0) - } // On ne teste que la génération de code Painless sans évaluer le résultat assert(chained.nonEmpty) } From 0064007acbec8d2bca098e64275dfa8776cf0216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Manciot?= Date: Thu, 16 Oct 2025 15:12:44 +0200 Subject: [PATCH 02/21] fix painless scripts --- .../sql/bridge/ElasticAggregation.scala | 8 +- .../elastic/sql/bridge/package.scala | 28 +- .../elastic/sql/SQLQuerySpec.scala | 453 ++++++++++++------ .../sql/bridge/ElasticAggregation.scala | 8 +- .../elastic/sql/bridge/package.scala | 28 +- .../elastic/sql/SQLQuerySpec.scala | 401 +++++++++++----- .../elastic/sql/function/cond/package.scala | 260 ++++++---- .../sql/function/convert/package.scala | 8 +- .../elastic/sql/function/geo/package.scala | 4 +- .../elastic/sql/function/math/package.scala | 41 +- .../elastic/sql/function/package.scala | 99 +++- .../elastic/sql/function/string/package.scala | 82 +++- .../elastic/sql/function/time/package.scala | 212 +++++--- .../operator/math/ArithmeticExpression.scala | 71 ++- .../app/softnetwork/elastic/sql/package.scala | 122 +++-- .../elastic/sql/parser/Parser.scala | 2 +- .../sql/parser/function/string/package.scala | 49 +- .../sql/parser/function/time/package.scala | 6 +- .../elastic/sql/query/GroupBy.scala | 4 +- .../softnetwork/elastic/sql/query/Where.scala | 82 +++- .../elastic/sql/time/package.scala | 3 +- .../elastic/sql/type/SQLTypeUtils.scala | 58 ++- .../sql/SQLDateTimeFunctionSuite.scala | 6 +- 23 files changed, 1409 insertions(+), 626 deletions(-) diff --git a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticAggregation.scala b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticAggregation.scala index 991621a5..58120bfc 100644 --- a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticAggregation.scala +++ b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticAggregation.scala @@ -1,5 +1,6 @@ package app.softnetwork.elastic.sql.bridge +import app.softnetwork.elastic.sql.PainlessContext import app.softnetwork.elastic.sql.query.{ Asc, Bucket, @@ -105,8 +106,9 @@ object ElasticAggregation { buildScript: (String, Script) => Aggregation ): Aggregation = { if (transformFuncs.nonEmpty) { - val scriptSrc = identifier.painless() - val script = Script(scriptSrc).lang("painless") + val context = PainlessContext() + val scriptSrc = identifier.painless(Some(context)) + val script = Script(s"$context$scriptSrc").lang("painless") buildScript(aggName, script) } else { buildField(aggName, sourceField) @@ -145,7 +147,7 @@ object ElasticAggregation { .copy( scripts = th.fields .filter(_.isScriptField) - .map(f => f.sourceField -> Script(f.painless()).lang("painless")) + .map(f => f.sourceField -> Script(f.painless(None)).lang("painless")) .toMap ) .size(limit) sortBy th.orderBy.sorts.map(sort => diff --git a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala index d0a8c4f6..ba5d9612 100644 --- a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala +++ b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala @@ -295,9 +295,11 @@ package object bridge { case Nil => _search case _ => _search scriptfields scriptFields.map { field => + val context = PainlessContext() + val script = field.painless(Some(context)) scriptField( field.scriptName, - Script(script = field.painless()) + Script(script = s"$context$script") .lang("painless") .scriptType("source") .params(field.identifier.functions.headOption match { @@ -352,7 +354,11 @@ package object bridge { case _ => true })) ) { - return scriptQuery(Script(script = painless()).lang("painless").scriptType("source")) + val context = PainlessContext() + val script = painless(Some(context)) + return scriptQuery( + Script(script = s"$context$script").lang("painless").scriptType("source") + ) } // Geo distance special case identifier.functions.headOption match { @@ -566,10 +572,22 @@ package object bridge { case NE | DIFF => not(rangeQuery(identifier.name) gte script lte script) } case _ => - scriptQuery(Script(script = painless()).lang("painless").scriptType("source")) + val context = PainlessContext() + val script = painless(Some(context)) + scriptQuery( + Script(script = s"$context$script") + .lang("painless") + .scriptType("source") + ) } case _ => - scriptQuery(Script(script = painless()).lang("painless").scriptType("source")) + val context = PainlessContext() + val script = painless(Some(context)) + scriptQuery( + Script(script = s"$context$script") + .lang("painless") + .scriptType("source") + ) } case _ => matchAllQuery() } @@ -723,7 +741,7 @@ package object bridge { case _ => scriptQuery( Script( - script = distanceCriteria.painless(), + script = distanceCriteria.painless(None), lang = Some("painless"), scriptType = Source, params = distance.params diff --git a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index 8a5cef5e..77e9f3de 100644 --- a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -773,7 +773,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "ct": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.minus(35, ChronoUnit.MINUTES) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (param1 != null ? param1.minus(35, ChronoUnit.MINUTES) : null)" | } | } | }, @@ -784,7 +784,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s", "") - .replaceAll("defv", "def v") + .replaceAll("defp", "def p") .replaceAll("defe", "def e") .replaceAll("if\\(", "if (") .replaceAll("!=null", " != null") @@ -1026,7 +1026,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll(">", " > ") } - it should "handle parse_date function" in { + it should "handle date_parse function" in { val select: ElasticSearchRequest = SQLQuery(dateParse) val query = select.query @@ -1065,7 +1065,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "field": "createdAt", | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? DateTimeFormatter.ofPattern('yyyy-MM-dd').parse(e0, LocalDate::from) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.parse(param1, LocalDate::from)" | } | } | } @@ -1074,7 +1074,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s", "") - .replaceAll("defv", "def v") + .replaceAll("defp", "def p") .replaceAll("defe", "def e") .replaceAll("if\\(", "if (") .replaceAll("=\\(", " = (") @@ -1084,6 +1084,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("return", " return ") .replaceAll(";", "; ") .replaceAll(",ChronoUnit", ", ChronoUnit") + .replaceAll("=DateTimeFormatter", " = DateTimeFormatter") .replaceAll("==", " == ") .replaceAll("!=", " != ") .replaceAll("&&", " && ") @@ -1092,7 +1093,59 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll(",LocalDate", ", LocalDate") } - it should "handle parse_datetime function" in { + it should "handle date_format function" in { + val select: ElasticSearchRequest = + SQLQuery(dateFormat) + val query = select.query + println(query) + query shouldBe + """{ + | "query": { + | "bool": { + | "filter": [ + | { + | "exists": { + | "field": "identifier2" + | } + | } + | ] + | } + | }, + | "script_fields": { + | "lastSeen": { + | "script": { + | "lang": "painless", + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | } + | } + | }, + | "_source": { + | "includes": [ + | "identifier" + | ] + | } + |}""".stripMargin + .replaceAll("\\s", "") + .replaceAll("defp", "def p") + .replaceAll("defe", "def e") + .replaceAll("if\\(", "if (") + .replaceAll("=\\(", " = (") + .replaceAll("\\?", " ? ") + .replaceAll(":null", " : null") + .replaceAll("null:", "null : ") + .replaceAll("return", " return ") + .replaceAll(";", "; ") + .replaceAll(",ChronoUnit", ", ChronoUnit") + .replaceAll("=DateTimeFormatter", " = DateTimeFormatter") + .replaceAll("==", " == ") + .replaceAll("!=", " != ") + .replaceAll("&&", " && ") + .replaceAll("\\|\\|", " || ") + .replaceAll(">", " > ") + .replaceAll(",LocalDate", ", LocalDate") + } + + it should "handle datetime_parse function" in { val select: ElasticSearchRequest = SQLQuery(dateTimeParse) val query = select.query @@ -1131,7 +1184,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "field": "createdAt", | "script": { | "lang": "painless", - | "source": "(def e2 = (def e1 = (def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss.SSS XXX').parse(e0, ZonedDateTime::from) : null); e1 != null ? e1.truncatedTo(ChronoUnit.MINUTES) : null); e2 != null ? e2.get(ChronoField.YEAR) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss.SSS XXX'); (param1 == null) ? null : param2.parse(param1, ZonedDateTime::from).truncatedTo(ChronoUnit.MINUTES).get(ChronoField.YEAR)" | } | } | } @@ -1140,7 +1193,60 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s", "") - .replaceAll("defv", "def v") + .replaceAll("defp", "def p") + .replaceAll("defe", "def e") + .replaceAll("if\\(", "if (") + .replaceAll("=\\(", " = (") + .replaceAll("\\?", " ? ") + .replaceAll(":null", " : null") + .replaceAll("null:", "null : ") + .replaceAll("return", " return ") + .replaceAll(";", "; ") + .replaceAll("==", " == ") + .replaceAll("!=", " != ") + .replaceAll("&&", " && ") + .replaceAll("\\|\\|", " || ") + .replaceAll(">", " > ") + .replaceAll(",ZonedDateTime", ", ZonedDateTime") + .replaceAll("=DateTimeFormatter", " = DateTimeFormatter") + .replaceAll("SSSXXX", "SSS XXX") + .replaceAll("ddHH", "dd HH") + } + + it should "handle datetime_format function" in { + val select: ElasticSearchRequest = + SQLQuery(dateTimeFormat) + val query = select.query + println(query) + query shouldBe + """{ + | "query": { + | "bool": { + | "filter": [ + | { + | "exists": { + | "field": "identifier2" + | } + | } + | ] + | } + | }, + | "script_fields": { + | "lastSeen": { + | "script": { + | "lang": "painless", + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss XXX'); (param1 == null) ? null : param2.format(param1)" + | } + | } + | }, + | "_source": { + | "includes": [ + | "identifier" + | ] + | } + |}""".stripMargin + .replaceAll("\\s", "") + .replaceAll("defp", "def p") .replaceAll("defe", "def e") .replaceAll("if\\(", "if (") .replaceAll("=\\(", " = (") @@ -1155,8 +1261,10 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("\\|\\|", " || ") .replaceAll(">", " > ") .replaceAll(",ZonedDateTime", ", ZonedDateTime") + .replaceAll("=DateTimeFormatter", " = DateTimeFormatter") .replaceAll("SSSXXX", "SSS XXX") .replaceAll("ddHH", "dd HH") + .replaceAll("XXX", " XXX") } it should "handle date_diff function as script field" in { @@ -1173,7 +1281,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "diff": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('updatedAt') || doc['updatedAt'].empty ? null : doc['updatedAt'].value); def arg1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (arg0 == null || arg1 == null) ? null : ChronoUnit.DAYS.between(arg0, arg1))" + | "source": "def param1 = (!doc.containsKey('updatedAt') || doc['updatedAt'].empty ? null : doc['updatedAt'].value); def param2 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (param1 == null || param2 == null) ? null : ChronoUnit.DAYS.between(param1, param2)" | } | } | }, @@ -1184,7 +1292,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s", "") - .replaceAll("defv", "def v") + .replaceAll("defp", "def p") .replaceAll("defe", "def e") .replaceAll("defa", "def a") .replaceAll("if\\(", "if (") @@ -1193,7 +1301,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll(":null", " : null") .replaceAll("null:", "null : ") .replaceAll("return", " return ") - .replaceAll(",a", ", a") + .replaceAll(",p", ", p") .replaceAll(";", "; ") .replaceAll("==", " == ") .replaceAll("!=", " != ") @@ -1223,7 +1331,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "max": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('updatedAt') || doc['updatedAt'].empty ? null : doc['updatedAt'].value); def arg1 = (def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss.SSS XXX').parse(e0, ZonedDateTime::from) : null); (arg0 == null || arg1 == null) ? null : ChronoUnit.DAYS.between(arg0, arg1))" + | "source": "def param1 = (!doc.containsKey('updatedAt') || doc['updatedAt'].empty ? null : doc['updatedAt'].value); def param2 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (param1 == null || param2 == null) ? null : ChronoUnit.DAYS.between(param1, param2)" | } | } | } @@ -1232,7 +1340,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s", "") - .replaceAll("defv", "def v") + .replaceAll("defp", "def p") .replaceAll("defe", "def e") .replaceAll("defa", "def a") .replaceAll("if\\(", "if (") @@ -1241,7 +1349,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll(":null", " : null") .replaceAll("null:", "null : ") .replaceAll("return", " return ") - .replaceAll(",a", ", a") + .replaceAll(",p", ", p") .replaceAll(";", "; ") .replaceAll("==", " == ") .replaceAll("!=", " != ") @@ -1274,7 +1382,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "lastSeen": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); e0 != null ? e0.plus(10, ChronoUnit.DAYS) : null)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); (param1 != null ? param1.plus(10, ChronoUnit.DAYS) : null)" | } | } | }, @@ -1285,7 +1393,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s", "") - .replaceAll("defv", "def v") + .replaceAll("defp", "def p") .replaceAll("defe", "def e") .replaceAll("defs", "def s") .replaceAll("if\\(", "if (") @@ -1325,7 +1433,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "lastSeen": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); e0 != null ? e0.minus(10, ChronoUnit.DAYS) : null)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); (param1 != null ? param1.minus(10, ChronoUnit.DAYS) : null)" | } | } | }, @@ -1336,7 +1444,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s", "") - .replaceAll("defv", "def v") + .replaceAll("defp", "def p") .replaceAll("defe", "def e") .replaceAll("defs", "def s") .replaceAll("if\\(", "if (") @@ -1376,7 +1484,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "lastSeen": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); e0 != null ? e0.plus(10, ChronoUnit.DAYS) : null)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); (param1 != null ? param1.plus(10, ChronoUnit.DAYS) : null)" | } | } | }, @@ -1387,7 +1495,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s+", "") - .replaceAll("defv", "def v") + .replaceAll("defp", "def p") .replaceAll("defe", "def e") .replaceAll("defs", "def s") .replaceAll("if\\(", "if (") @@ -1427,7 +1535,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "lastSeen": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); e0 != null ? e0.minus(10, ChronoUnit.DAYS) : null)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); (param1 != null ? param1.minus(10, ChronoUnit.DAYS) : null)" | } | } | }, @@ -1438,7 +1546,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s+", "") - .replaceAll("defv", "def v") + .replaceAll("defp", "def p") .replaceAll("defe", "def e") .replaceAll("defs", "def s") .replaceAll("if\\(", "if (") @@ -1456,7 +1564,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("ChronoUnit", " ChronoUnit") } - it should "handle is_null function as script field" in { + it should "handle is_null function as script field" in { // TODO replace with param1 == null val select: ElasticSearchRequest = SQLQuery(isnull) val query = select.query @@ -1470,20 +1578,20 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "flag": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); e0 == null)" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); param1 == null" | } | } | }, | "_source": true |}""".stripMargin .replaceAll("\\s+", "") - .replaceAll("defv", "def v") + .replaceAll("defp", "def p") .replaceAll("defe", "def e") .replaceAll("defs", "def s") .replaceAll("if\\(", "if (") .replaceAll("=\\(", " = (") .replaceAll("\\?", " ? ") - .replaceAll(":null", " : null") + .replaceAll(":false", " : false") .replaceAll("null:", "null : ") .replaceAll("return", " return ") .replaceAll("between\\(s,", "between(s, ") @@ -1508,7 +1616,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "flag": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); e0 != null)" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); param1 != null" | } | } | }, @@ -1519,13 +1627,13 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s+", "") - .replaceAll("defv", "def v") + .replaceAll("defp", "def p") .replaceAll("defe", "def e") .replaceAll("defs", "def s") .replaceAll("if\\(", "if (") .replaceAll("=\\(", " = (") .replaceAll("\\?", " ? ") - .replaceAll(":null", " : null") + .replaceAll(":true", " : true") .replaceAll("null:", "null : ") .replaceAll("return", " return ") .replaceAll("between\\(s,", "between(s, ") @@ -1608,7 +1716,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "{ def v0 = (def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.minus(35, ChronoUnit.MINUTES) : null);if (v0 != null) return v0; return ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().atStartOfDay(ZoneId.of('Z')); }" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param1 != null ? param1 : param2" | } | } | }, @@ -1619,6 +1727,8 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s+", "") + .replaceAll(";defp", "; defp") + .replaceAll("defp", "def p") .replaceAll("defv", " def v") .replaceAll("defe", "def e") .replaceAll("if\\(", "if (") @@ -1627,6 +1737,9 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll(":null", " : null") .replaceAll(";}", "; }") .replaceAll(";e", "; e") + .replaceAll(";v", "; v") + .replaceAll(":p", " : p") + .replaceAll(";p", "; p") .replaceAll(":null", " : null") .replaceAll("null:", "null : ") .replaceAll("return", " return ") @@ -1635,6 +1748,8 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("&&", " && ") .replaceAll("\\|\\|", " || ") .replaceAll("ChronoUnit", " ChronoUnit") + .replaceAll("=ZonedDateTime", " = ZonedDateTime") + .replaceAll(":ZonedDateTime", " : ZonedDateTime") } it should "handle nullif function as script field" in { @@ -1651,7 +1766,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "{ def v0 = ((def arg0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (arg0 == null) ? null : arg0 == DateTimeFormatter.ofPattern('yyyy-MM-dd').parse(\"2025-09-11\", LocalDate::from).minus(2, ChronoUnit.DAYS) ? null : arg0));if (v0 != null) return v0; return ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); }" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd').parse(\"2025-09-11\", LocalDate::from); def param3 = param1 == param2.minus(2, ChronoUnit.DAYS) ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param3 != null ? param3 : param4" | } | } | }, @@ -1662,6 +1777,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s+", "") + .replaceAll("defp", "def p") .replaceAll("defv", " def v") .replaceAll("defa", "def a") .replaceAll("if\\(", "if (") @@ -1672,6 +1788,10 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("between\\(s,", "between(s, ") .replaceAll(";def", "; def") .replaceAll(";return", "; return") + .replaceAll(";v", "; v") + .replaceAll("(\\d)=p", "$1 = p") + .replaceAll(";p", "; p") + .replaceAll(":p", " : p") .replaceAll("returnv", " return v") .replaceAll("returne", " return e") .replaceAll(";}", "; }") @@ -1684,7 +1804,8 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("ChronoUnit", " ChronoUnit") .replaceAll(",LocalDate", ", LocalDate") .replaceAll("=DateTimeFormatter", " = DateTimeFormatter") - .replaceAll("ZonedDateTime", " ZonedDateTime") + .replaceAll("=ZonedDateTime", " = ZonedDateTime") + .replaceAll(":ZonedDateTime", " : ZonedDateTime") } it should "handle cast function as script field" in { @@ -1701,19 +1822,19 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "try { def v0 = ((def arg0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (arg0 == null) ? null : arg0 == DateTimeFormatter.ofPattern('yyyy-MM-dd').parse(\"2025-09-11\", LocalDate::from) ? null : arg0));if (v0 != null) return v0; return ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().atStartOfDay(ZoneId.of('Z')).minus(2, ChronoUnit.HOURS).toInstant().toEpochMilli(); } catch (Exception e) { return null; }" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd').parse(\"2025-09-11\", LocalDate::from); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); try { param3 != null ? param3 : param4.atStartOfDay(ZoneId.of('Z')).minus(2, ChronoUnit.HOURS) } catch (Exception e) { return null; }" | } | }, | "c2": { | "script": { | "lang": "painless", - | "source": "ZonedDateTime.now(ZoneId.of('Z')).toInstant().toEpochMilli()" + | "source": "def param1 = ZonedDateTime.now(ZoneId.of('Z')); param1.toInstant().toEpochMilli()" | } | }, | "c3": { | "script": { | "lang": "painless", - | "source": "ZonedDateTime.now(ZoneId.of('Z')).toLocalDate()" + | "source": "def param1 = ZonedDateTime.now(ZoneId.of('Z')); param1.toLocalDate()" | } | }, | "c4": { @@ -1725,7 +1846,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c5": { | "script": { | "lang": "painless", - | "source": "LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern('yyyy-MM-dd'))" + | "source": "def param1 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); param1" | } | } | }, @@ -1736,6 +1857,8 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s+", "") + .replaceAll(";defp", "; defp") + .replaceAll("defp", "def p") .replaceAll("defv", " def v") .replaceAll("defa", "def a") .replaceAll("if\\(", "if (") @@ -1759,6 +1882,14 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("\\}catch", "} catch ") .replaceAll("Exceptione\\)", "Exception e) ") .replaceAll(",DateTimeFormatter", ", DateTimeFormatter") + .replaceAll("(\\d)=p", "$1 = p") + .replaceAll(";p", "; p") + .replaceAll(":p", " : p") + .replaceAll("=ZonedDateTime", " = ZonedDateTime") + .replaceAll("=LocalDate", " = LocalDate") + .replaceAll(":ZonedDateTime", " : ZonedDateTime") + .replaceAll("try \\{", "try { ") + .replaceAll("} catch", " } catch") } it should "handle case function as script field" in { @@ -1775,7 +1906,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "{ if (def left = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); left == null ? false : left > ZonedDateTime.now(ZoneId.of('Z')).minus(7, ChronoUnit.DAYS)) return left; if (def left = (!doc.containsKey('lastSeen') || doc['lastSeen'].empty ? null : doc['lastSeen'].value); left != null) return left.plus(2, ChronoUnit.DAYS); def dval = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); return dval; }" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = ZonedDateTime.now(ZoneId.of('Z')); def param3 = param1 == null ? false : (param1 > param2.minus(7, ChronoUnit.DAYS)); def param4 = (!doc.containsKey('lastSeen') || doc['lastSeen'].empty ? null : doc['lastSeen'].value); def param5 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param3 ? param1 : param4 != null ? param4.plus(2, ChronoUnit.DAYS) : param5" | } | } | }, @@ -1786,7 +1917,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s+", "") - .replaceAll("defv", " def v") + .replaceAll("defp", "def p") .replaceAll("defd", " def d") .replaceAll("defe", " def e") .replaceAll("defl", " def l") @@ -1809,6 +1940,9 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll(">", " > ") .replaceAll("if \\(\\s*def", "if (def") .replaceAll("ChronoUnit", " ChronoUnit") + .replaceAll("=ZonedDateTime", " = ZonedDateTime") + .replaceAll("=p", " = p") + .replaceAll(":p", " : p") } it should "handle case with expression function as script field" in { @@ -1825,7 +1959,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "{ def expr = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(7, ChronoUnit.DAYS); def e0 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def val0 = e0 != null ? e0.minus(3, ChronoUnit.DAYS).atStartOfDay(ZoneId.of('Z')) : null; if (expr == val0) return e0; def val1 = (!doc.containsKey('lastSeen') || doc['lastSeen'].empty ? null : doc['lastSeen'].value); if (expr == val1) return val1.plus(2, ChronoUnit.DAYS); def dval = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); return dval; }" + | "source": "def param1 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); def param2 = param1.minus(7, ChronoUnit.DAYS); def param3 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param4 = (param3 != null ? param3.minus(3, ChronoUnit.DAYS) : null); def param5 = (!doc.containsKey('lastSeen') || doc['lastSeen'].empty ? null : doc['lastSeen'].value); def param6 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param2 == param4 ? param3 : param2 == param5 ? param5.plus(2, ChronoUnit.DAYS) : param6" | } | } | }, @@ -1836,7 +1970,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s+", "") - .replaceAll("defv", " def v") + .replaceAll("defp", "def p") .replaceAll("defd", " def d") .replaceAll("defe", " def e") .replaceAll("defl", " def l") @@ -1861,6 +1995,9 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("ChronoUnit", " ChronoUnit") .replaceAll("=ZonedDateTime", " = ZonedDateTime") .replaceAll("=e", " = e") + .replaceAll("=ZonedDateTime", " = ZonedDateTime") + .replaceAll("=p", " = p") + .replaceAll(":p", " : p") } it should "handle extract function as script field" in { @@ -1877,98 +2014,98 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "dom": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.DAY_OF_MONTH) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.DAY_OF_MONTH)" | } | }, | "dow": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.DAY_OF_WEEK) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.DAY_OF_WEEK)" | } | }, | "doy": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.DAY_OF_YEAR) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.DAY_OF_YEAR)" | } | }, | "m": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.MONTH_OF_YEAR) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MONTH_OF_YEAR)" | } | }, | "y": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.YEAR) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.YEAR)" | } | }, | "h": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.HOUR_OF_DAY) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.HOUR_OF_DAY)" | } | }, | "minutes": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.MINUTE_OF_HOUR) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MINUTE_OF_HOUR)" | } | }, | "s": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.SECOND_OF_MINUTE) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.SECOND_OF_MINUTE)" | } | }, | "nano": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.NANO_OF_SECOND) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.NANO_OF_SECOND)" | } | }, | "micro": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.MICRO_OF_SECOND) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MICRO_OF_SECOND)" | } | }, | "milli": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.MILLI_OF_SECOND) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MILLI_OF_SECOND)" | } | }, | "epoch": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.EPOCH_DAY) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.EPOCH_DAY)" | } | }, | "off": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.OFFSET_SECONDS) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.OFFSET_SECONDS)" | } | }, | "w": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(java.time.temporal.IsoFields.WEEK_OF_WEEK_BASED_YEAR) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(java.time.temporal.IsoFields.WEEK_OF_WEEK_BASED_YEAR)" | } | }, | "q": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(java.time.temporal.IsoFields.QUARTER_OF_YEAR) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(java.time.temporal.IsoFields.QUARTER_OF_YEAR)" | } | } | }, | "_source": true |}""".stripMargin .replaceAll("\\s+", "") - .replaceAll("defe", "def e") + .replaceAll("defp", "def p") .replaceAll("if\\(", "if (") .replaceAll("=\\(", " = (") .replaceAll("\\?", " ? ") @@ -2001,7 +2138,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "script": { | "script": { | "lang": "painless", - | "source": "def lv0 = ((!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value)); ( lv0 == null ) ? null : (lv0 * (ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().get(ChronoField.YEAR) - 10)) > 10000" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); def param2 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); (param1 == null) ? null : (param1 * (param2.get(ChronoField.YEAR) - 10)) > 10000" | } | } | } @@ -2012,37 +2149,37 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "add": { | "script": { | "lang": "painless", - | "source": "def lv0 = ((!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value)); ( lv0 == null ) ? null : (lv0 + 1)" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : (param1 + 1)" | } | }, | "sub": { | "script": { | "lang": "painless", - | "source": "def lv0 = ((!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value)); ( lv0 == null ) ? null : (lv0 - 1)" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : (param1 - 1)" | } | }, | "mul": { | "script": { | "lang": "painless", - | "source": "def lv0 = ((!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value)); ( lv0 == null ) ? null : (lv0 * 2)" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : (param1 * 2)" | } | }, | "div": { | "script": { | "lang": "painless", - | "source": "def lv0 = ((!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value)); ( lv0 == null ) ? null : (lv0 / 2)" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : (param1 / 2)" | } | }, | "mod": { | "script": { | "lang": "painless", - | "source": "def lv0 = ((!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value)); ( lv0 == null ) ? null : (lv0 % 2)" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : (param1 % 2)" | } | }, | "identifier_mul_identifier2_minus_10": { | "script": { | "lang": "painless", - | "source": "def lv0 = ((def lv1 = ((!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value)); def rv1 = ((!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value)); ( lv1 == null || rv1 == null ) ? null : (lv1 * rv1))); ( lv0 == null ) ? null : (lv0 - 10)" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); def param2 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); def lv0 = ((param1 == null || param2 == null) ? null : (param1 * param2)); (lv0 == null) ? null : (lv0 - 10)" | } | } | }, @@ -2053,12 +2190,14 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s", "") - .replaceAll("defv", "def v") + .replaceAll("defp", "def p") .replaceAll("defe", "def e") .replaceAll("defl", "def l") .replaceAll("defr", "def r") .replaceAll("if\\(", "if (") .replaceAll("=\\(", " = (") + // .replaceAll("(\\d)=", "$1 =") + .replaceAll("=ZonedDateTime", " = ZonedDateTime") .replaceAll("\\?", " ? ") .replaceAll(":null", " : null") .replaceAll("null:", "null : ") @@ -2088,7 +2227,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "script": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.sqrt(arg0)) > 100.0" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.sqrt(param1) > 100.0" | } | } | } @@ -2099,109 +2238,109 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "abs_identifier_plus_1_0_mul_2": { | "script": { | "lang": "painless", - | "source": "((def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.abs(arg0)) + 1.0) * ((double) 2)" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); ((param1 == null) ? null : Math.abs(param1) + 1.0) * ((double) 2)" | } | }, | "ceil_identifier": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.ceil(arg0))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.ceil(param1)" | } | }, | "floor_identifier": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.floor(arg0))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.floor(param1)" | } | }, | "sqrt_identifier": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.sqrt(arg0))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.sqrt(param1)" | } | }, | "exp_identifier": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.exp(arg0))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.exp(param1)" | } | }, | "log_identifier": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.log(arg0))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.log(param1)" | } | }, | "log10_identifier": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.log10(arg0))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.log10(param1)" | } | }, | "pow_identifier_3": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.pow(arg0, 3))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.pow(param1, 3)" | } | }, - | "round_identifier": { + | "round_identifier_math_pow_10_0": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : (def p = Math.pow(10, 0); Math.round((arg0 * p) / p)))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); def param2 = Math.pow(10, 0); (param1 == null || param2 == null) ? null : Math.round((param1 * param2) / param2)" | } | }, - | "round_identifier_2": { + | "round_identifier_math_pow_10_2": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : (def p = Math.pow(10, 2); Math.round((arg0 * p) / p)))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); def param2 = Math.pow(10, 2); (param1 == null || param2 == null) ? null : Math.round((param1 * param2) / param2)" | } | }, | "sign_identifier": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); arg0 != null ? (arg0 > 0 ? 1 : (arg0 < 0 ? -1 : 0)) : null)" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : (param1 > 0 ? 1 : (param1 < 0 ? -1 : 0))" | } | }, | "cos_identifier": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.cos(arg0))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.cos(param1)" | } | }, | "acos_identifier": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.acos(arg0))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.acos(param1)" | } | }, | "sin_identifier": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.sin(arg0))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.sin(param1)" | } | }, | "asin_identifier": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.asin(arg0))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.asin(param1)" | } | }, | "tan_identifier": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.tan(arg0))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.tan(param1)" | } | }, | "atan_identifier": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.atan(arg0))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.atan(param1)" | } | }, | "atan2_identifier_3_0": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.atan2(arg0, 3.0))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.atan2(param1, 3.0)" | } | } | }, @@ -2212,7 +2351,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s+", "") - .replaceAll("defv", " def v") + .replaceAll("defp", "def p") .replaceAll("defa", "def a") .replaceAll("defp", "def p") .replaceAll("if\\(", "if (") @@ -2258,7 +2397,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "script": { | "script": { | "lang": "painless", - | "source": "def left = (def e1 = (def e0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); e0 != null ? e0.trim() : null); e1 != null ? e1.length() : null); left == null ? false : left > 10" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.trim().length() > 10" | } | } | } @@ -2269,85 +2408,85 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "len": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); e0 != null ? e0.length() : null)" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.length()" | } | }, | "low": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); e0 != null ? e0.lower() : null)" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.lower()" | } | }, | "upp": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); e0 != null ? e0.upper() : null)" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.upper()" | } | }, | "sub": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (arg0 == null) ? null : arg0.substring(1 - 1, Math.min(1 - 1 + 3, arg0.length())))" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.substring(0, Math.min(3, param1.length()))" | } | }, | "tr": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); e0 != null ? e0.trim() : null)" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.trim()" | } | }, | "ltr": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); e0 != null ? e0.replaceAll(\"^\\\\s+\",\"\") : null)" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.replaceAll(\"^\\\\s+\",\"\")" | } | }, | "rtr": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); e0 != null ? e0.replaceAll(\"\\\\s+$\",\"\") : null)" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.replaceAll(\"\\\\s+$\",\"\")" | } | }, | "con": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (arg0 == null) ? null : String.valueOf(arg0) + \"_test\" + String.valueOf(1))" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : String.valueOf(param1) + \"_test\" + String.valueOf(1)" | } | }, | "l": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (arg0 == null) ? null : arg0.substring(0, Math.min(5, arg0.length())))" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.substring(0, Math.min(5, param1.length()))" | } | }, | "r": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (arg0 == null) ? null : 3 == 0 ? \"\" : arg0.substring(arg0.length() - Math.min(3, arg0.length())))" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.substring(param1.length() - Math.min(3, param1.length()))" | } | }, | "rep": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (arg0 == null) ? null : arg0.replace(\"el\", \"le\"))" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.replace(\"el\", \"le\")" | } | }, | "rev": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (arg0 == null) ? null : new StringBuilder(arg0).reverse().toString())" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : new StringBuilder(param1).reverse().toString()" | } | }, | "pos": { | "script": { | "lang": "painless", - | "source": "(def arg1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (arg1 == null) ? null : arg1.indexOf(\"soft\", 1 - 1) + 1)" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.indexOf(\"soft\", 0) + 1" | } | }, | "reg": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (arg0 == null) ? null : java.util.regex.Pattern.compile(\"soft\", java.util.regex.Pattern.CASE_INSENSITIVE | java.util.regex.Pattern.MULTILINE).matcher(arg0).find())" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : java.util.regex.Pattern.compile(\"soft\", java.util.regex.Pattern.CASE_INSENSITIVE | java.util.regex.Pattern.MULTILINE).matcher(param1).find()" | } | } | }, @@ -2358,7 +2497,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s+", "") - .replaceAll("defv", " def v") + .replaceAll("defp", "def p") .replaceAll("defa", "def a") .replaceAll("defe", "def e") .replaceAll("defl", "def l") @@ -2415,7 +2554,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "hire_date": { | "script": { | "lang": "painless", - | "source": "(!doc.containsKey('hire_date') || doc['hire_date'].empty ? null : doc['hire_date'].value)" + | "source": "def param1 = (!doc.containsKey('hire_date') || doc['hire_date'].empty ? null : doc['hire_date'].value); param1" | } | } | }, @@ -2494,7 +2633,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s+", "") - .replaceAll("defv", " def v") + .replaceAll("defp", "def p") .replaceAll("defa", "def a") .replaceAll("defe", "def e") .replaceAll("defl", "def l") @@ -2531,36 +2670,36 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { println(query) query shouldBe """{ - | "query": { - | "bool": { - | "filter": [ - | { - | "script": { - | "script": { - | "lang": "painless", - | "source": "(def e1 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); e1.withDayOfMonth(e1.lengthOfMonth())).get(ChronoField.DAY_OF_MONTH) > 28" - | } - | } - | } - | ] - | } - | }, - | "script_fields": { - | "ld": { - | "script": { - | "lang": "painless", - | "source": "(def e1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e1 != null ? e1.withDayOfMonth(e1.lengthOfMonth()) : null)" - | } - | } - | }, - | "_source": { - | "includes": [ - | "identifier" - | ] - | } - |}""".stripMargin + | "query": { + | "bool": { + | "filter": [ + | { + | "script": { + | "script": { + | "lang": "painless", + | "source": "def param1 = ZonedDateTime.now(ZoneId.of('Z')); param1.toLocalDate().withDayOfMonth(param1.toLocalDate().lengthOfMonth()).get(ChronoField.DAY_OF_MONTH) > 28" + | } + | } + | } + | ] + | } + | }, + | "script_fields": { + | "ld": { + | "script": { + | "lang": "painless", + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (param1 == null) ? null : param1.withDayOfMonth(param1.lengthOfMonth())" + | } + | } + | }, + | "_source": { + | "includes": [ + | "identifier" + | ] + | } + |}""".stripMargin .replaceAll("\\s+", "") - .replaceAll("defv", " def v") + .replaceAll("defp", "def p") .replaceAll("defa", "def a") .replaceAll("defe", "def e") .replaceAll("defl", "def l") @@ -2605,98 +2744,98 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "y": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.YEAR) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.YEAR)" | } | }, | "m": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.MONTH_OF_YEAR) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MONTH_OF_YEAR)" | } | }, | "wd": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.DAY_OF_WEEK) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.DAY_OF_WEEK)" | } | }, | "yd": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.DAY_OF_YEAR) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.DAY_OF_YEAR)" | } | }, | "d": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.DAY_OF_MONTH) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.DAY_OF_MONTH)" | } | }, | "h": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.HOUR_OF_DAY) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.HOUR_OF_DAY)" | } | }, | "minutes": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.MINUTE_OF_HOUR) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MINUTE_OF_HOUR)" | } | }, | "s": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.SECOND_OF_MINUTE) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.SECOND_OF_MINUTE)" | } | }, | "nano": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.NANO_OF_SECOND) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.NANO_OF_SECOND)" | } | }, | "micro": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.MICRO_OF_SECOND) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MICRO_OF_SECOND)" | } | }, | "milli": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.MILLI_OF_SECOND) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MILLI_OF_SECOND)" | } | }, | "epoch": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.EPOCH_DAY) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.EPOCH_DAY)" | } | }, | "off": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.OFFSET_SECONDS) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.OFFSET_SECONDS)" | } | }, | "w": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(java.time.temporal.IsoFields.WEEK_OF_WEEK_BASED_YEAR) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(java.time.temporal.IsoFields.WEEK_OF_WEEK_BASED_YEAR)" | } | }, | "q": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(java.time.temporal.IsoFields.QUARTER_OF_YEAR) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(java.time.temporal.IsoFields.QUARTER_OF_YEAR)" | } | } | }, | "_source": true |}""".stripMargin .replaceAll("\\s+", "") - .replaceAll("defv", " def v") + .replaceAll("defp", "def p") .replaceAll("defa", "def a") .replaceAll("defe", "def e") .replaceAll("defl", "def l") @@ -2874,7 +3013,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "script": { | "script": { | "lang": "painless", - | "source": "def left = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); left == null ? false : left >= (def e2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); e2.withDayOfMonth(e2.lengthOfMonth()))" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); param1 == null ? false : (param1 >= param2.withDayOfMonth(param2.lengthOfMonth()))" | } | } | }, @@ -2899,7 +3038,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { |}""".stripMargin .replaceAll("\\s+", "") .replaceAll("\\s+", "") - .replaceAll("defv", " def v") + .replaceAll("defp", "def p") .replaceAll("defa", "def a") .replaceAll("defe", "def e") .replaceAll("defl", "def l") @@ -2964,7 +3103,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "script": { | "script": { | "lang": "painless", - | "source": "def left = (!doc.containsKey('comments.replies.lastUpdated') || doc['comments.replies.lastUpdated'].empty ? null : doc['comments.replies.lastUpdated'].value); left == null ? false : left < (def e2 = LocalDate.parse(\"2025-09-10\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); e2.withDayOfMonth(e2.lengthOfMonth()))" + | "source": "def param1 = (!doc.containsKey('comments.replies.lastUpdated') || doc['comments.replies.lastUpdated'].empty ? null : doc['comments.replies.lastUpdated'].value); def param2 = LocalDate.parse(\"2025-09-10\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); param1 == null ? false : (param1 < param2.withDayOfMonth(param2.lengthOfMonth()))" | } | } | } @@ -3007,7 +3146,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("\\s+", "") .replaceAll("\\s+", "") .replaceAll("\\s+", "") - .replaceAll("defv", " def v") + .replaceAll("defp", "def p") .replaceAll("defa", "def a") .replaceAll("defe", "def e") .replaceAll("defl", "def l") @@ -3062,7 +3201,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "script": { | "script": { | "lang": "painless", - | "source": "def left = (!doc.containsKey('replies.lastUpdated') || doc['replies.lastUpdated'].empty ? null : doc['replies.lastUpdated'].value); left == null ? false : left < (def e2 = LocalDate.parse(\"2025-09-10\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); e2.withDayOfMonth(e2.lengthOfMonth()))" + | "source": "def param1 = (!doc.containsKey('replies.lastUpdated') || doc['replies.lastUpdated'].empty ? null : doc['replies.lastUpdated'].value); def param2 = LocalDate.parse(\"2025-09-10\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); param1 == null ? false : (param1 < param2.withDayOfMonth(param2.lengthOfMonth()))" | } | } | }, @@ -3117,7 +3256,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("\\s+", "") .replaceAll("\\s+", "") .replaceAll("\\s+", "") - .replaceAll("defv", " def v") + .replaceAll("defp", "def p") .replaceAll("defa", "def a") .replaceAll("defe", "def e") .replaceAll("defl", "def l") @@ -3169,7 +3308,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "script": { | "script": { | "lang": "painless", - | "source": "def left = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); left == null ? false : left < ZonedDateTime.now(ZoneId.of('Z')).toLocalDate()" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param1 == null ? false : (param1 < param2)" | } | } | } @@ -3221,7 +3360,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("\\s+", "") .replaceAll("\\s+", "") .replaceAll("\\s+", "") - .replaceAll("defv", " def v") + .replaceAll("defp", "def p") .replaceAll("defa", "def a") .replaceAll("defe", "def e") .replaceAll("defl", "def l") diff --git a/sql/bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticAggregation.scala b/sql/bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticAggregation.scala index 1b8950c3..01160dc6 100644 --- a/sql/bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticAggregation.scala +++ b/sql/bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticAggregation.scala @@ -1,5 +1,6 @@ package app.softnetwork.elastic.sql.bridge +import app.softnetwork.elastic.sql.PainlessContext import app.softnetwork.elastic.sql.query.{ Asc, Bucket, @@ -104,8 +105,9 @@ object ElasticAggregation { buildScript: (String, Script) => Aggregation ): Aggregation = { if (transformFuncs.nonEmpty) { - val scriptSrc = identifier.painless() - val script = Script(scriptSrc).lang("painless") + val context = PainlessContext() + val scriptSrc = identifier.painless(Some(context)) + val script = Script(s"$context$scriptSrc").lang("painless") buildScript(aggName, script) } else { buildField(aggName, sourceField) @@ -142,7 +144,7 @@ object ElasticAggregation { Array.empty ).copy( scripts = th.fields.filter(_.isScriptField).map(f => - f.sourceField -> Script(f.painless()).lang("painless") + f.sourceField -> Script(f.painless(None)).lang("painless") ).toMap ) .size(limit) sortBy th.orderBy.sorts.map(sort => diff --git a/sql/bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala b/sql/bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala index 343da652..3dce76c5 100644 --- a/sql/bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala +++ b/sql/bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala @@ -296,9 +296,11 @@ package object bridge { case Nil => _search case _ => _search scriptfields scriptFields.map { field => + val context = PainlessContext() + val script = field.painless(Some(context)) scriptField( field.scriptName, - Script(script = field.painless()) + Script(script = s"$context$script") .lang("painless") .scriptType("source") .params(field.identifier.functions.headOption match { @@ -353,7 +355,11 @@ package object bridge { case _ => true })) ) { - return scriptQuery(Script(script = painless()).lang("painless").scriptType("source")) + val context = PainlessContext() + val script = painless(Some(context)) + return scriptQuery( + Script(script = s"$context$script").lang("painless").scriptType("source") + ) } // Geo distance special case identifier.functions.headOption match { @@ -567,10 +573,22 @@ package object bridge { case NE | DIFF => not(rangeQuery(identifier.name) gte script lte script) } case _ => - scriptQuery(Script(script = painless()).lang("painless").scriptType("source")) + val context = PainlessContext() + val script = painless(Some(context)) + scriptQuery( + Script(script = s"$context$script") + .lang("painless") + .scriptType("source") + ) } case _ => - scriptQuery(Script(script = painless()).lang("painless").scriptType("source")) + val context = PainlessContext() + val script = painless(Some(context)) + scriptQuery( + Script(script = s"$context$script") + .lang("painless") + .scriptType("source") + ) } case _ => matchAllQuery() } @@ -724,7 +742,7 @@ package object bridge { case _ => scriptQuery( Script( - script = distanceCriteria.painless(), + script = distanceCriteria.painless(None), lang = Some("painless"), scriptType = Source, params = distance.params diff --git a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index 86617c50..46a3e0e9 100644 --- a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -773,7 +773,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "ct": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.minus(35, ChronoUnit.MINUTES) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (param1 != null ? param1.minus(35, ChronoUnit.MINUTES) : null)" | } | } | }, @@ -784,7 +784,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s", "") - .replaceAll("defv", "def v") + .replaceAll("defp", "def p") .replaceAll("defe", "def e") .replaceAll("if\\(", "if (") .replaceAll("!=null", " != null") @@ -1026,7 +1026,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll(">", " > ") } - it should "handle parse_date function" in { + it should "handle date_parse function" in { val select: ElasticSearchRequest = SQLQuery(dateParse) val query = select.query @@ -1065,7 +1065,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "field": "createdAt", | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? DateTimeFormatter.ofPattern('yyyy-MM-dd').parse(e0, LocalDate::from) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.parse(param1, LocalDate::from)" | } | } | } @@ -1074,7 +1074,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s", "") - .replaceAll("defv", "def v") + .replaceAll("defp", "def p") .replaceAll("defe", "def e") .replaceAll("if\\(", "if (") .replaceAll("=\\(", " = (") @@ -1084,6 +1084,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("return", " return ") .replaceAll(";", "; ") .replaceAll(",ChronoUnit", ", ChronoUnit") + .replaceAll("=DateTimeFormatter", " = DateTimeFormatter") .replaceAll("==", " == ") .replaceAll("!=", " != ") .replaceAll("&&", " && ") @@ -1092,7 +1093,59 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll(",LocalDate", ", LocalDate") } - it should "handle parse_datetime function" in { + it should "handle date_format function" in { + val select: ElasticSearchRequest = + SQLQuery(dateFormat) + val query = select.query + println(query) + query shouldBe + """{ + | "query": { + | "bool": { + | "filter": [ + | { + | "exists": { + | "field": "identifier2" + | } + | } + | ] + | } + | }, + | "script_fields": { + | "lastSeen": { + | "script": { + | "lang": "painless", + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | } + | } + | }, + | "_source": { + | "includes": [ + | "identifier" + | ] + | } + |}""".stripMargin + .replaceAll("\\s", "") + .replaceAll("defp", "def p") + .replaceAll("defe", "def e") + .replaceAll("if\\(", "if (") + .replaceAll("=\\(", " = (") + .replaceAll("\\?", " ? ") + .replaceAll(":null", " : null") + .replaceAll("null:", "null : ") + .replaceAll("return", " return ") + .replaceAll(";", "; ") + .replaceAll(",ChronoUnit", ", ChronoUnit") + .replaceAll("=DateTimeFormatter", " = DateTimeFormatter") + .replaceAll("==", " == ") + .replaceAll("!=", " != ") + .replaceAll("&&", " && ") + .replaceAll("\\|\\|", " || ") + .replaceAll(">", " > ") + .replaceAll(",LocalDate", ", LocalDate") + } + + it should "handle datetime_parse function" in { val select: ElasticSearchRequest = SQLQuery(dateTimeParse) val query = select.query @@ -1131,7 +1184,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "field": "createdAt", | "script": { | "lang": "painless", - | "source": "(def e2 = (def e1 = (def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss.SSS XXX').parse(e0, ZonedDateTime::from) : null); e1 != null ? e1.truncatedTo(ChronoUnit.MINUTES) : null); e2 != null ? e2.get(ChronoField.YEAR) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss.SSS XXX'); (param1 == null) ? null : param2.parse(param1, ZonedDateTime::from).truncatedTo(ChronoUnit.MINUTES).get(ChronoField.YEAR)" | } | } | } @@ -1140,7 +1193,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s", "") - .replaceAll("defv", "def v") + .replaceAll("defp", "def p") .replaceAll("defe", "def e") .replaceAll("if\\(", "if (") .replaceAll("=\\(", " = (") @@ -1155,10 +1208,65 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("\\|\\|", " || ") .replaceAll(">", " > ") .replaceAll(",ZonedDateTime", ", ZonedDateTime") + .replaceAll("=DateTimeFormatter", " = DateTimeFormatter") .replaceAll("SSSXXX", "SSS XXX") .replaceAll("ddHH", "dd HH") } + it should "handle datetime_format function" in { + val select: ElasticSearchRequest = + SQLQuery(dateTimeFormat) + val query = select.query + println(query) + query shouldBe + """{ + | "query": { + | "bool": { + | "filter": [ + | { + | "exists": { + | "field": "identifier2" + | } + | } + | ] + | } + | }, + | "script_fields": { + | "lastSeen": { + | "script": { + | "lang": "painless", + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss XXX'); (param1 == null) ? null : param2.format(param1)" + | } + | } + | }, + | "_source": { + | "includes": [ + | "identifier" + | ] + | } + |}""".stripMargin + .replaceAll("\\s", "") + .replaceAll("defp", "def p") + .replaceAll("defe", "def e") + .replaceAll("if\\(", "if (") + .replaceAll("=\\(", " = (") + .replaceAll("\\?", " ? ") + .replaceAll(":null", " : null") + .replaceAll("null:", "null : ") + .replaceAll("return", " return ") + .replaceAll(";", "; ") + .replaceAll("==", " == ") + .replaceAll("!=", " != ") + .replaceAll("&&", " && ") + .replaceAll("\\|\\|", " || ") + .replaceAll(">", " > ") + .replaceAll(",ZonedDateTime", ", ZonedDateTime") + .replaceAll("=DateTimeFormatter", " = DateTimeFormatter") + .replaceAll("SSSXXX", "SSS XXX") + .replaceAll("ddHH", "dd HH") + .replaceAll("XXX", " XXX") + } + it should "handle date_diff function as script field" in { val select: ElasticSearchRequest = SQLQuery(dateDiff) @@ -1173,7 +1281,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "diff": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('updatedAt') || doc['updatedAt'].empty ? null : doc['updatedAt'].value); def arg1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (arg0 == null || arg1 == null) ? null : ChronoUnit.DAYS.between(arg0, arg1))" + | "source": "def param1 = (!doc.containsKey('updatedAt') || doc['updatedAt'].empty ? null : doc['updatedAt'].value); def param2 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (param1 == null || param2 == null) ? null : ChronoUnit.DAYS.between(param1, param2)" | } | } | }, @@ -1184,7 +1292,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s", "") - .replaceAll("defv", "def v") + .replaceAll("defp", "def p") .replaceAll("defe", "def e") .replaceAll("defa", "def a") .replaceAll("if\\(", "if (") @@ -1193,7 +1301,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll(":null", " : null") .replaceAll("null:", "null : ") .replaceAll("return", " return ") - .replaceAll(",a", ", a") + .replaceAll(",p", ", p") .replaceAll(";", "; ") .replaceAll("==", " == ") .replaceAll("!=", " != ") @@ -1223,7 +1331,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "max": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('updatedAt') || doc['updatedAt'].empty ? null : doc['updatedAt'].value); def arg1 = (def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss.SSS XXX').parse(e0, ZonedDateTime::from) : null); (arg0 == null || arg1 == null) ? null : ChronoUnit.DAYS.between(arg0, arg1))" + | "source": "def param1 = (!doc.containsKey('updatedAt') || doc['updatedAt'].empty ? null : doc['updatedAt'].value); def param2 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (param1 == null || param2 == null) ? null : ChronoUnit.DAYS.between(param1, param2)" | } | } | } @@ -1232,7 +1340,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s", "") - .replaceAll("defv", "def v") + .replaceAll("defp", "def p") .replaceAll("defe", "def e") .replaceAll("defa", "def a") .replaceAll("if\\(", "if (") @@ -1241,7 +1349,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll(":null", " : null") .replaceAll("null:", "null : ") .replaceAll("return", " return ") - .replaceAll(",a", ", a") + .replaceAll(",p", ", p") .replaceAll(";", "; ") .replaceAll("==", " == ") .replaceAll("!=", " != ") @@ -1274,7 +1382,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "lastSeen": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); e0 != null ? e0.plus(10, ChronoUnit.DAYS) : null)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); (param1 != null ? param1.plus(10, ChronoUnit.DAYS) : null)" | } | } | }, @@ -1285,7 +1393,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s", "") - .replaceAll("defv", "def v") + .replaceAll("defp", "def p") .replaceAll("defe", "def e") .replaceAll("defs", "def s") .replaceAll("if\\(", "if (") @@ -1325,7 +1433,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "lastSeen": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); e0 != null ? e0.minus(10, ChronoUnit.DAYS) : null)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); (param1 != null ? param1.minus(10, ChronoUnit.DAYS) : null)" | } | } | }, @@ -1336,7 +1444,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s", "") - .replaceAll("defv", "def v") + .replaceAll("defp", "def p") .replaceAll("defe", "def e") .replaceAll("defs", "def s") .replaceAll("if\\(", "if (") @@ -1376,7 +1484,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "lastSeen": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); e0 != null ? e0.plus(10, ChronoUnit.DAYS) : null)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); (param1 != null ? param1.plus(10, ChronoUnit.DAYS) : null)" | } | } | }, @@ -1387,7 +1495,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s+", "") - .replaceAll("defv", "def v") + .replaceAll("defp", "def p") .replaceAll("defe", "def e") .replaceAll("defs", "def s") .replaceAll("if\\(", "if (") @@ -1427,7 +1535,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "lastSeen": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); e0 != null ? e0.minus(10, ChronoUnit.DAYS) : null)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); (param1 != null ? param1.minus(10, ChronoUnit.DAYS) : null)" | } | } | }, @@ -1438,7 +1546,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s+", "") - .replaceAll("defv", "def v") + .replaceAll("defp", "def p") .replaceAll("defe", "def e") .replaceAll("defs", "def s") .replaceAll("if\\(", "if (") @@ -1456,7 +1564,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("ChronoUnit", " ChronoUnit") } - it should "handle is_null function as script field" in { + it should "handle is_null function as script field" in { // TODO replace with param1 == null val select: ElasticSearchRequest = SQLQuery(isnull) val query = select.query @@ -1470,20 +1578,20 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "flag": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); e0 == null)" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); param1 == null" | } | } | }, | "_source": true |}""".stripMargin .replaceAll("\\s+", "") - .replaceAll("defv", "def v") + .replaceAll("defp", "def p") .replaceAll("defe", "def e") .replaceAll("defs", "def s") .replaceAll("if\\(", "if (") .replaceAll("=\\(", " = (") .replaceAll("\\?", " ? ") - .replaceAll(":null", " : null") + .replaceAll(":false", " : false") .replaceAll("null:", "null : ") .replaceAll("return", " return ") .replaceAll("between\\(s,", "between(s, ") @@ -1508,7 +1616,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "flag": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); e0 != null)" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); param1 != null" | } | } | }, @@ -1519,13 +1627,13 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s+", "") - .replaceAll("defv", "def v") + .replaceAll("defp", "def p") .replaceAll("defe", "def e") .replaceAll("defs", "def s") .replaceAll("if\\(", "if (") .replaceAll("=\\(", " = (") .replaceAll("\\?", " ? ") - .replaceAll(":null", " : null") + .replaceAll(":true", " : true") .replaceAll("null:", "null : ") .replaceAll("return", " return ") .replaceAll("between\\(s,", "between(s, ") @@ -1608,7 +1716,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "{ def v0 = (def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.minus(35, ChronoUnit.MINUTES) : null);if (v0 != null) return v0; return ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().atStartOfDay(ZoneId.of('Z')); }" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param1 != null ? param1 : param2" | } | } | }, @@ -1619,6 +1727,8 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s+", "") + .replaceAll(";defp", "; defp") + .replaceAll("defp", "def p") .replaceAll("defv", " def v") .replaceAll("defe", "def e") .replaceAll("if\\(", "if (") @@ -1627,6 +1737,9 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll(":null", " : null") .replaceAll(";}", "; }") .replaceAll(";e", "; e") + .replaceAll(";v", "; v") + .replaceAll(":p", " : p") + .replaceAll(";p", "; p") .replaceAll(":null", " : null") .replaceAll("null:", "null : ") .replaceAll("return", " return ") @@ -1635,6 +1748,8 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("&&", " && ") .replaceAll("\\|\\|", " || ") .replaceAll("ChronoUnit", " ChronoUnit") + .replaceAll("=ZonedDateTime", " = ZonedDateTime") + .replaceAll(":ZonedDateTime", " : ZonedDateTime") } it should "handle nullif function as script field" in { @@ -1651,7 +1766,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "{ def v0 = ((def arg0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (arg0 == null) ? null : arg0 == DateTimeFormatter.ofPattern('yyyy-MM-dd').parse(\"2025-09-11\", LocalDate::from).minus(2, ChronoUnit.DAYS) ? null : arg0));if (v0 != null) return v0; return ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); }" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd').parse(\"2025-09-11\", LocalDate::from); def param3 = param1 == param2.minus(2, ChronoUnit.DAYS) ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param3 != null ? param3 : param4" | } | } | }, @@ -1662,6 +1777,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s+", "") + .replaceAll("defp", "def p") .replaceAll("defv", " def v") .replaceAll("defa", "def a") .replaceAll("if\\(", "if (") @@ -1672,6 +1788,10 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("between\\(s,", "between(s, ") .replaceAll(";def", "; def") .replaceAll(";return", "; return") + .replaceAll(";v", "; v") + .replaceAll("(\\d)=p", "$1 = p") + .replaceAll(";p", "; p") + .replaceAll(":p", " : p") .replaceAll("returnv", " return v") .replaceAll("returne", " return e") .replaceAll(";}", "; }") @@ -1684,7 +1804,8 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("ChronoUnit", " ChronoUnit") .replaceAll(",LocalDate", ", LocalDate") .replaceAll("=DateTimeFormatter", " = DateTimeFormatter") - .replaceAll("ZonedDateTime", " ZonedDateTime") + .replaceAll("=ZonedDateTime", " = ZonedDateTime") + .replaceAll(":ZonedDateTime", " : ZonedDateTime") } it should "handle cast function as script field" in { @@ -1701,19 +1822,19 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "try { def v0 = ((def arg0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (arg0 == null) ? null : arg0 == DateTimeFormatter.ofPattern('yyyy-MM-dd').parse(\"2025-09-11\", LocalDate::from) ? null : arg0));if (v0 != null) return v0; return ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().atStartOfDay(ZoneId.of('Z')).minus(2, ChronoUnit.HOURS).toInstant().toEpochMilli(); } catch (Exception e) { return null; }" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd').parse(\"2025-09-11\", LocalDate::from); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); try { param3 != null ? param3 : param4.atStartOfDay(ZoneId.of('Z')).minus(2, ChronoUnit.HOURS) } catch (Exception e) { return null; }" | } | }, | "c2": { | "script": { | "lang": "painless", - | "source": "ZonedDateTime.now(ZoneId.of('Z')).toInstant().toEpochMilli()" + | "source": "def param1 = ZonedDateTime.now(ZoneId.of('Z')); param1.toInstant().toEpochMilli()" | } | }, | "c3": { | "script": { | "lang": "painless", - | "source": "ZonedDateTime.now(ZoneId.of('Z')).toLocalDate()" + | "source": "def param1 = ZonedDateTime.now(ZoneId.of('Z')); param1.toLocalDate()" | } | }, | "c4": { @@ -1725,7 +1846,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c5": { | "script": { | "lang": "painless", - | "source": "LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern('yyyy-MM-dd'))" + | "source": "def param1 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); param1" | } | } | }, @@ -1736,6 +1857,8 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s+", "") + .replaceAll(";defp", "; defp") + .replaceAll("defp", "def p") .replaceAll("defv", " def v") .replaceAll("defa", "def a") .replaceAll("if\\(", "if (") @@ -1759,6 +1882,14 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("\\}catch", "} catch ") .replaceAll("Exceptione\\)", "Exception e) ") .replaceAll(",DateTimeFormatter", ", DateTimeFormatter") + .replaceAll("(\\d)=p", "$1 = p") + .replaceAll(";p", "; p") + .replaceAll(":p", " : p") + .replaceAll("=ZonedDateTime", " = ZonedDateTime") + .replaceAll("=LocalDate", " = LocalDate") + .replaceAll(":ZonedDateTime", " : ZonedDateTime") + .replaceAll("try \\{", "try { ") + .replaceAll("} catch", " } catch") } it should "handle case function as script field" in { @@ -1775,7 +1906,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "{ if (def left = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); left == null ? false : left > ZonedDateTime.now(ZoneId.of('Z')).minus(7, ChronoUnit.DAYS)) return left; if (def left = (!doc.containsKey('lastSeen') || doc['lastSeen'].empty ? null : doc['lastSeen'].value); left != null) return left.plus(2, ChronoUnit.DAYS); def dval = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); return dval; }" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = ZonedDateTime.now(ZoneId.of('Z')); def param3 = param1 == null ? false : (param1 > param2.minus(7, ChronoUnit.DAYS)); def param4 = (!doc.containsKey('lastSeen') || doc['lastSeen'].empty ? null : doc['lastSeen'].value); def param5 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param3 ? param1 : param4 != null ? param4.plus(2, ChronoUnit.DAYS) : param5" | } | } | }, @@ -1786,7 +1917,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s+", "") - .replaceAll("defv", " def v") + .replaceAll("defp", "def p") .replaceAll("defd", " def d") .replaceAll("defe", " def e") .replaceAll("defl", " def l") @@ -1809,6 +1940,9 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll(">", " > ") .replaceAll("if \\(\\s*def", "if (def") .replaceAll("ChronoUnit", " ChronoUnit") + .replaceAll("=ZonedDateTime", " = ZonedDateTime") + .replaceAll("=p", " = p") + .replaceAll(":p", " : p") } it should "handle case with expression function as script field" in { @@ -1825,7 +1959,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "{ def expr = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(7, ChronoUnit.DAYS); def e0 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def val0 = e0 != null ? e0.minus(3, ChronoUnit.DAYS).atStartOfDay(ZoneId.of('Z')) : null; if (expr == val0) return e0; def val1 = (!doc.containsKey('lastSeen') || doc['lastSeen'].empty ? null : doc['lastSeen'].value); if (expr == val1) return val1.plus(2, ChronoUnit.DAYS); def dval = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); return dval; }" + | "source": "def param1 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); def param2 = param1.minus(7, ChronoUnit.DAYS); def param3 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param4 = (param3 != null ? param3.minus(3, ChronoUnit.DAYS) : null); def param5 = (!doc.containsKey('lastSeen') || doc['lastSeen'].empty ? null : doc['lastSeen'].value); def param6 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param2 == param4 ? param3 : param2 == param5 ? param5.plus(2, ChronoUnit.DAYS) : param6" | } | } | }, @@ -1836,7 +1970,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s+", "") - .replaceAll("defv", " def v") + .replaceAll("defp", "def p") .replaceAll("defd", " def d") .replaceAll("defe", " def e") .replaceAll("defl", " def l") @@ -1861,6 +1995,9 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("ChronoUnit", " ChronoUnit") .replaceAll("=ZonedDateTime", " = ZonedDateTime") .replaceAll("=e", " = e") + .replaceAll("=ZonedDateTime", " = ZonedDateTime") + .replaceAll("=p", " = p") + .replaceAll(":p", " : p") } it should "handle extract function as script field" in { @@ -1877,98 +2014,98 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "dom": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.DAY_OF_MONTH) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.DAY_OF_MONTH)" | } | }, | "dow": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.DAY_OF_WEEK) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.DAY_OF_WEEK)" | } | }, | "doy": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.DAY_OF_YEAR) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.DAY_OF_YEAR)" | } | }, | "m": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.MONTH_OF_YEAR) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MONTH_OF_YEAR)" | } | }, | "y": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.YEAR) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.YEAR)" | } | }, | "h": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.HOUR_OF_DAY) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.HOUR_OF_DAY)" | } | }, | "minutes": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.MINUTE_OF_HOUR) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MINUTE_OF_HOUR)" | } | }, | "s": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.SECOND_OF_MINUTE) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.SECOND_OF_MINUTE)" | } | }, | "nano": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.NANO_OF_SECOND) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.NANO_OF_SECOND)" | } | }, | "micro": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.MICRO_OF_SECOND) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MICRO_OF_SECOND)" | } | }, | "milli": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.MILLI_OF_SECOND) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MILLI_OF_SECOND)" | } | }, | "epoch": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.EPOCH_DAY) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.EPOCH_DAY)" | } | }, | "off": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.OFFSET_SECONDS) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.OFFSET_SECONDS)" | } | }, | "w": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(java.time.temporal.IsoFields.WEEK_OF_WEEK_BASED_YEAR) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(java.time.temporal.IsoFields.WEEK_OF_WEEK_BASED_YEAR)" | } | }, | "q": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(java.time.temporal.IsoFields.QUARTER_OF_YEAR) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(java.time.temporal.IsoFields.QUARTER_OF_YEAR)" | } | } | }, | "_source": true |}""".stripMargin .replaceAll("\\s+", "") - .replaceAll("defe", "def e") + .replaceAll("defp", "def p") .replaceAll("if\\(", "if (") .replaceAll("=\\(", " = (") .replaceAll("\\?", " ? ") @@ -2001,7 +2138,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "script": { | "script": { | "lang": "painless", - | "source": "def lv0 = ((!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value)); ( lv0 == null ) ? null : (lv0 * (ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().get(ChronoField.YEAR) - 10)) > 10000" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); def param2 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); (param1 == null) ? null : (param1 * (param2.get(ChronoField.YEAR) - 10)) > 10000" | } | } | } @@ -2012,37 +2149,37 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "add": { | "script": { | "lang": "painless", - | "source": "def lv0 = ((!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value)); ( lv0 == null ) ? null : (lv0 + 1)" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : (param1 + 1)" | } | }, | "sub": { | "script": { | "lang": "painless", - | "source": "def lv0 = ((!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value)); ( lv0 == null ) ? null : (lv0 - 1)" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : (param1 - 1)" | } | }, | "mul": { | "script": { | "lang": "painless", - | "source": "def lv0 = ((!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value)); ( lv0 == null ) ? null : (lv0 * 2)" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : (param1 * 2)" | } | }, | "div": { | "script": { | "lang": "painless", - | "source": "def lv0 = ((!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value)); ( lv0 == null ) ? null : (lv0 / 2)" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : (param1 / 2)" | } | }, | "mod": { | "script": { | "lang": "painless", - | "source": "def lv0 = ((!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value)); ( lv0 == null ) ? null : (lv0 % 2)" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : (param1 % 2)" | } | }, | "identifier_mul_identifier2_minus_10": { | "script": { | "lang": "painless", - | "source": "def lv0 = ((def lv1 = ((!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value)); def rv1 = ((!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value)); ( lv1 == null || rv1 == null ) ? null : (lv1 * rv1))); ( lv0 == null ) ? null : (lv0 - 10)" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); def param2 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); def lv0 = ((param1 == null || param2 == null) ? null : (param1 * param2)); (lv0 == null) ? null : (lv0 - 10)" | } | } | }, @@ -2053,12 +2190,14 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s", "") - .replaceAll("defv", "def v") + .replaceAll("defp", "def p") .replaceAll("defe", "def e") .replaceAll("defl", "def l") .replaceAll("defr", "def r") .replaceAll("if\\(", "if (") .replaceAll("=\\(", " = (") + // .replaceAll("(\\d)=", "$1 =") + .replaceAll("=ZonedDateTime", " = ZonedDateTime") .replaceAll("\\?", " ? ") .replaceAll(":null", " : null") .replaceAll("null:", "null : ") @@ -2088,7 +2227,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "script": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.sqrt(arg0)) > 100.0" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.sqrt(param1) > 100.0" | } | } | } @@ -2099,109 +2238,109 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "abs_identifier_plus_1_0_mul_2": { | "script": { | "lang": "painless", - | "source": "((def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.abs(arg0)) + 1.0) * ((double) 2)" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); ((param1 == null) ? null : Math.abs(param1) + 1.0) * ((double) 2)" | } | }, | "ceil_identifier": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.ceil(arg0))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.ceil(param1)" | } | }, | "floor_identifier": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.floor(arg0))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.floor(param1)" | } | }, | "sqrt_identifier": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.sqrt(arg0))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.sqrt(param1)" | } | }, | "exp_identifier": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.exp(arg0))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.exp(param1)" | } | }, | "log_identifier": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.log(arg0))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.log(param1)" | } | }, | "log10_identifier": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.log10(arg0))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.log10(param1)" | } | }, | "pow_identifier_3": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.pow(arg0, 3))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.pow(param1, 3)" | } | }, - | "round_identifier": { + | "round_identifier_math_pow_10_0": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : (def p = Math.pow(10, 0); Math.round((arg0 * p) / p)))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); def param2 = Math.pow(10, 0); (param1 == null || param2 == null) ? null : Math.round((param1 * param2) / param2)" | } | }, - | "round_identifier_2": { + | "round_identifier_math_pow_10_2": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : (def p = Math.pow(10, 2); Math.round((arg0 * p) / p)))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); def param2 = Math.pow(10, 2); (param1 == null || param2 == null) ? null : Math.round((param1 * param2) / param2)" | } | }, | "sign_identifier": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); arg0 != null ? (arg0 > 0 ? 1 : (arg0 < 0 ? -1 : 0)) : null)" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : (param1 > 0 ? 1 : (param1 < 0 ? -1 : 0))" | } | }, | "cos_identifier": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.cos(arg0))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.cos(param1)" | } | }, | "acos_identifier": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.acos(arg0))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.acos(param1)" | } | }, | "sin_identifier": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.sin(arg0))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.sin(param1)" | } | }, | "asin_identifier": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.asin(arg0))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.asin(param1)" | } | }, | "tan_identifier": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.tan(arg0))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.tan(param1)" | } | }, | "atan_identifier": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.atan(arg0))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.atan(param1)" | } | }, | "atan2_identifier_3_0": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (arg0 == null) ? null : Math.atan2(arg0, 3.0))" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.atan2(param1, 3.0)" | } | } | }, @@ -2212,7 +2351,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s+", "") - .replaceAll("defv", " def v") + .replaceAll("defp", "def p") .replaceAll("defa", "def a") .replaceAll("defp", "def p") .replaceAll("if\\(", "if (") @@ -2258,7 +2397,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "script": { | "script": { | "lang": "painless", - | "source": "def left = (def e1 = (def e0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); e0 != null ? e0.trim() : null); e1 != null ? e1.length() : null); left == null ? false : left > 10" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.trim().length() > 10" | } | } | } @@ -2269,85 +2408,85 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "len": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); e0 != null ? e0.length() : null)" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.length()" | } | }, | "low": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); e0 != null ? e0.lower() : null)" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.lower()" | } | }, | "upp": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); e0 != null ? e0.upper() : null)" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.upper()" | } | }, | "sub": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (arg0 == null) ? null : arg0.substring(1 - 1, Math.min(1 - 1 + 3, arg0.length())))" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.substring(0, Math.min(3, param1.length()))" | } | }, | "tr": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); e0 != null ? e0.trim() : null)" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.trim()" | } | }, | "ltr": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); e0 != null ? e0.replaceAll(\"^\\\\s+\",\"\") : null)" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.replaceAll(\"^\\\\s+\",\"\")" | } | }, | "rtr": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); e0 != null ? e0.replaceAll(\"\\\\s+$\",\"\") : null)" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.replaceAll(\"\\\\s+$\",\"\")" | } | }, | "con": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (arg0 == null) ? null : String.valueOf(arg0) + \"_test\" + String.valueOf(1))" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : String.valueOf(param1) + \"_test\" + String.valueOf(1)" | } | }, | "l": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (arg0 == null) ? null : arg0.substring(0, Math.min(5, arg0.length())))" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.substring(0, Math.min(5, param1.length()))" | } | }, | "r": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (arg0 == null) ? null : 3 == 0 ? \"\" : arg0.substring(arg0.length() - Math.min(3, arg0.length())))" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.substring(param1.length() - Math.min(3, param1.length()))" | } | }, | "rep": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (arg0 == null) ? null : arg0.replace(\"el\", \"le\"))" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.replace(\"el\", \"le\")" | } | }, | "rev": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (arg0 == null) ? null : new StringBuilder(arg0).reverse().toString())" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : new StringBuilder(param1).reverse().toString()" | } | }, | "pos": { | "script": { | "lang": "painless", - | "source": "(def arg1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (arg1 == null) ? null : arg1.indexOf(\"soft\", 1 - 1) + 1)" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.indexOf(\"soft\", 0) + 1" | } | }, | "reg": { | "script": { | "lang": "painless", - | "source": "(def arg0 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (arg0 == null) ? null : java.util.regex.Pattern.compile(\"soft\", java.util.regex.Pattern.CASE_INSENSITIVE | java.util.regex.Pattern.MULTILINE).matcher(arg0).find())" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : java.util.regex.Pattern.compile(\"soft\", java.util.regex.Pattern.CASE_INSENSITIVE | java.util.regex.Pattern.MULTILINE).matcher(param1).find()" | } | } | }, @@ -2358,7 +2497,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s+", "") - .replaceAll("defv", " def v") + .replaceAll("defp", "def p") .replaceAll("defa", "def a") .replaceAll("defe", "def e") .replaceAll("defl", "def l") @@ -2415,7 +2554,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "hire_date": { | "script": { | "lang": "painless", - | "source": "(!doc.containsKey('hire_date') || doc['hire_date'].empty ? null : doc['hire_date'].value)" + | "source": "def param1 = (!doc.containsKey('hire_date') || doc['hire_date'].empty ? null : doc['hire_date'].value); param1" | } | } | }, @@ -2494,7 +2633,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s+", "") - .replaceAll("defv", " def v") + .replaceAll("defp", "def p") .replaceAll("defa", "def a") .replaceAll("defe", "def e") .replaceAll("defl", "def l") @@ -2538,7 +2677,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "script": { | "script": { | "lang": "painless", - | "source": "(def e1 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); e1.withDayOfMonth(e1.lengthOfMonth())).get(ChronoField.DAY_OF_MONTH) > 28" + | "source": "def param1 = ZonedDateTime.now(ZoneId.of('Z')); param1.toLocalDate().withDayOfMonth(param1.toLocalDate().lengthOfMonth()).get(ChronoField.DAY_OF_MONTH) > 28" | } | } | } @@ -2549,7 +2688,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "ld": { | "script": { | "lang": "painless", - | "source": "(def e1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e1 != null ? e1.withDayOfMonth(e1.lengthOfMonth()) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (param1 == null) ? null : param1.withDayOfMonth(param1.lengthOfMonth())" | } | } | }, @@ -2560,7 +2699,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } |}""".stripMargin .replaceAll("\\s+", "") - .replaceAll("defv", " def v") + .replaceAll("defp", "def p") .replaceAll("defa", "def a") .replaceAll("defe", "def e") .replaceAll("defl", "def l") @@ -2605,98 +2744,98 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "y": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.YEAR) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.YEAR)" | } | }, | "m": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.MONTH_OF_YEAR) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MONTH_OF_YEAR)" | } | }, | "wd": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.DAY_OF_WEEK) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.DAY_OF_WEEK)" | } | }, | "yd": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.DAY_OF_YEAR) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.DAY_OF_YEAR)" | } | }, | "d": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.DAY_OF_MONTH) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.DAY_OF_MONTH)" | } | }, | "h": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.HOUR_OF_DAY) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.HOUR_OF_DAY)" | } | }, | "minutes": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.MINUTE_OF_HOUR) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MINUTE_OF_HOUR)" | } | }, | "s": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.SECOND_OF_MINUTE) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.SECOND_OF_MINUTE)" | } | }, | "nano": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.NANO_OF_SECOND) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.NANO_OF_SECOND)" | } | }, | "micro": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.MICRO_OF_SECOND) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MICRO_OF_SECOND)" | } | }, | "milli": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.MILLI_OF_SECOND) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MILLI_OF_SECOND)" | } | }, | "epoch": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.EPOCH_DAY) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.EPOCH_DAY)" | } | }, | "off": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(ChronoField.OFFSET_SECONDS) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.OFFSET_SECONDS)" | } | }, | "w": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(java.time.temporal.IsoFields.WEEK_OF_WEEK_BASED_YEAR) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(java.time.temporal.IsoFields.WEEK_OF_WEEK_BASED_YEAR)" | } | }, | "q": { | "script": { | "lang": "painless", - | "source": "(def e0 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); e0 != null ? e0.get(java.time.temporal.IsoFields.QUARTER_OF_YEAR) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(java.time.temporal.IsoFields.QUARTER_OF_YEAR)" | } | } | }, | "_source": true |}""".stripMargin .replaceAll("\\s+", "") - .replaceAll("defv", " def v") + .replaceAll("defp", "def p") .replaceAll("defa", "def a") .replaceAll("defe", "def e") .replaceAll("defl", "def l") @@ -2874,7 +3013,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "script": { | "script": { | "lang": "painless", - | "source": "def left = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); left == null ? false : left >= (def e2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); e2.withDayOfMonth(e2.lengthOfMonth()))" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); param1 == null ? false : (param1 >= param2.withDayOfMonth(param2.lengthOfMonth()))" | } | } | }, @@ -2899,7 +3038,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { |}""".stripMargin .replaceAll("\\s+", "") .replaceAll("\\s+", "") - .replaceAll("defv", " def v") + .replaceAll("defp", "def p") .replaceAll("defa", "def a") .replaceAll("defe", "def e") .replaceAll("defl", "def l") @@ -2964,7 +3103,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "script": { | "script": { | "lang": "painless", - | "source": "def left = (!doc.containsKey('comments.replies.lastUpdated') || doc['comments.replies.lastUpdated'].empty ? null : doc['comments.replies.lastUpdated'].value); left == null ? false : left < (def e2 = LocalDate.parse(\"2025-09-10\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); e2.withDayOfMonth(e2.lengthOfMonth()))" + | "source": "def param1 = (!doc.containsKey('comments.replies.lastUpdated') || doc['comments.replies.lastUpdated'].empty ? null : doc['comments.replies.lastUpdated'].value); def param2 = LocalDate.parse(\"2025-09-10\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); param1 == null ? false : (param1 < param2.withDayOfMonth(param2.lengthOfMonth()))" | } | } | } @@ -3007,7 +3146,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("\\s+", "") .replaceAll("\\s+", "") .replaceAll("\\s+", "") - .replaceAll("defv", " def v") + .replaceAll("defp", "def p") .replaceAll("defa", "def a") .replaceAll("defe", "def e") .replaceAll("defl", "def l") @@ -3062,7 +3201,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "script": { | "script": { | "lang": "painless", - | "source": "def left = (!doc.containsKey('replies.lastUpdated') || doc['replies.lastUpdated'].empty ? null : doc['replies.lastUpdated'].value); left == null ? false : left < (def e2 = LocalDate.parse(\"2025-09-10\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); e2.withDayOfMonth(e2.lengthOfMonth()))" + | "source": "def param1 = (!doc.containsKey('replies.lastUpdated') || doc['replies.lastUpdated'].empty ? null : doc['replies.lastUpdated'].value); def param2 = LocalDate.parse(\"2025-09-10\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); param1 == null ? false : (param1 < param2.withDayOfMonth(param2.lengthOfMonth()))" | } | } | }, @@ -3117,7 +3256,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("\\s+", "") .replaceAll("\\s+", "") .replaceAll("\\s+", "") - .replaceAll("defv", " def v") + .replaceAll("defp", "def p") .replaceAll("defa", "def a") .replaceAll("defe", "def e") .replaceAll("defl", "def l") @@ -3169,7 +3308,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "script": { | "script": { | "lang": "painless", - | "source": "def left = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); left == null ? false : left < ZonedDateTime.now(ZoneId.of('Z')).toLocalDate()" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param1 == null ? false : (param1 < param2)" | } | } | } @@ -3221,7 +3360,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("\\s+", "") .replaceAll("\\s+", "") .replaceAll("\\s+", "") - .replaceAll("defv", " def v") + .replaceAll("defp", "def p") .replaceAll("defa", "def a") .replaceAll("defe", "def e") .replaceAll("defl", "def l") diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala index 93146db1..dbbe8ba6 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala @@ -1,8 +1,16 @@ package app.softnetwork.elastic.sql.function -import app.softnetwork.elastic.sql.{Expr, Identifier, PainlessContext, PainlessScript, TokenRegex} +import app.softnetwork.elastic.sql.{ + Expr, + Identifier, + LiteralParam, + PainlessContext, + PainlessScript, + TokenRegex +} import app.softnetwork.elastic.sql.`type`.{SQLAny, SQLBool, SQLType, SQLTypeUtils, SQLTypes} -import app.softnetwork.elastic.sql.query.Expression +import app.softnetwork.elastic.sql.parser.Validator +import app.softnetwork.elastic.sql.query.{CriteriaWithConditionalFunction, Expression} package object cond { @@ -32,7 +40,6 @@ package object cond { override def outputType: SQLBool = SQLTypes.Boolean - override def toPainless(base: String, idx: Int): String = s"($base${painless()})" } case class IsNull(identifier: Identifier) extends ConditionalFunction[SQLAny] { @@ -44,13 +51,14 @@ package object cond { override def toSQL(base: String): String = sql - override def painless(context: Option[PainlessContext] = None): String = s" == null" - override def toPainless(base: String, idx: Int): String = { - if (nullable) - s"(def e$idx = $base; e$idx${painless()})" - else - s"$base${painless()}" - } + override def checkIfNullable: Boolean = false + + override def toPainlessCall(callArgs: List[String], context: Option[PainlessContext]): String = + callArgs match { + case List(arg) => + s"${arg.trim} == null" // TODO check when identifier is nullable and has functions + case _ => throw new IllegalArgumentException("ISNULL requires exactly one argument") + } } case class IsNotNull(identifier: Identifier) extends ConditionalFunction[SQLAny] { @@ -62,13 +70,14 @@ package object cond { override def toSQL(base: String): String = sql - override def painless(context: Option[PainlessContext] = None): String = s" != null" - override def toPainless(base: String, idx: Int): String = { - if (nullable) - s"(def e$idx = $base; e$idx${painless()})" - else - s"$base${painless()}" - } + override def checkIfNullable: Boolean = false + + override def toPainlessCall(callArgs: List[String], context: Option[PainlessContext]): String = + callArgs match { + case List(arg) => + s"${arg.trim} != null" // TODO check when identifier is nullable and has functions + case _ => throw new IllegalArgumentException("ISNOTNULL requires exactly one argument") + } } case class Coalesce(values: List[PainlessScript]) @@ -99,23 +108,19 @@ package object cond { else Right(()) } - override def toPainless(base: String, idx: Int): String = s"$base${painless()}" + override def checkIfNullable: Boolean = false - override def painless(context: Option[PainlessContext] = None): String = { - require(values.nonEmpty, "COALESCE requires at least one argument") - - val checks = values - .take(values.length - 1) - .zipWithIndex - .map { case (v, index) => - var check = s"def v$index = ${SQLTypeUtils.coerce(v, out)};" - check += s"if (v$index != null) return v$index;" - check - } - .mkString(" ") - // final fallback - s"{ $checks return ${SQLTypeUtils.coerce(values.last, out)}; }" - } + override def toPainlessCall(callArgs: List[String], context: Option[PainlessContext]): String = + callArgs match { + case Nil => throw new IllegalArgumentException("COALESCE requires at least one argument") + case _ => + callArgs + .take(values.length - 1) + .map { arg => + s"${arg.trim} != null ? ${arg.trim}" // TODO check when value is nullable and has functions + } + .mkString(" : ") + s" : ${callArgs.last}" + } override def nullable: Boolean = values.forall(_.nullable) } @@ -134,12 +139,39 @@ package object cond { override def applyType(in: SQLType): SQLType = out - override def toPainlessCall(callArgs: List[String]): String = { + override def checkIfNullable: Boolean = expr1.nullable && (expr1 match { + case f: FunctionChain if f.functions.nonEmpty => true + case _ => false + }) + + override def toPainlessCall( + callArgs: List[String], + context: Option[PainlessContext] + ): String = { callArgs match { - case List(arg0, arg1) => s"${arg0.trim} == ${arg1.trim} ? null : $arg0" + case List(arg0, arg1) => + val expr = + s"${arg0.trim} == ${arg1.trim} ? null : $arg0" // TODO check when expr1 and expr2 are nullable and have functions + context match { + case Some(ctx) => + ctx.addParam(LiteralParam(expr)) match { + case Some(e) => return e + case _ => + } + case _ => + } + expr case _ => throw new IllegalArgumentException("NULLIF requires exactly two arguments") } } + + override def validate(): Either[String, Unit] = { + for { + _ <- expr1.validate() + _ <- expr2.validate() + _ <- Validator.validateTypesMatching(expr1.out, expr2.out) + } yield () + } } case class Case( @@ -184,63 +216,119 @@ package object cond { } override def painless(context: Option[PainlessContext] = None): String = { - val base = - expression match { - case Some(expr) => - s"def expr = ${SQLTypeUtils.coerce(expr, expr.out)}; " - case _ => "" - } - val cases = conditions.zipWithIndex - .map { case ((cond, res), idx) => - val name = - cond match { - case e: Expression => - e.identifier.name - case i: Identifier => - i.name - case _ => "" - } - expression match { - case Some(expr) => - val c = SQLTypeUtils.coerce(cond, expr.out) - if (cond.sql == res.sql) { - s"def val$idx = $c; if (expr == val$idx) return val$idx;" - } else { - res match { - case i: Identifier if i.name == name && cond.isInstanceOf[Identifier] => - i.withNullable(false) - if (cond.asInstanceOf[Identifier].functions.isEmpty) - s"def val$idx = $c; if (expr == val$idx) return ${SQLTypeUtils.coerce(i.toPainless(s"val$idx"), i.baseType, out, nullable = false)};" - else { - cond.asInstanceOf[Identifier].withNullable(false) - s"def e$idx = ${i.checkNotNull}; def val$idx = e$idx != null ? ${SQLTypeUtils - .coerce(cond.asInstanceOf[Identifier].toPainless(s"e$idx"), cond.baseType, out, nullable = false)} : null; if (expr == val$idx) return ${SQLTypeUtils - .coerce(i.toPainless(s"e$idx"), i.baseType, out, nullable = false)};" + context match { + case Some(ctx) => + var cases = + expression match { + case Some(expr) => // case with expression to evaluate + val e = SQLTypeUtils.coerce(expr, expr.out, context) + val expParam = ctx.addParam( + LiteralParam(e) + ) + conditions + .map { case (cond, res) => + val name = + cond match { + case e: Expression => + e.identifier.name + case f: FunctionWithIdentifier => + f.identifier.name + case i: Identifier => + i.name + case _ => "" + } + val c = SQLTypeUtils.coerce(cond, expr.out, context) + val r = + res match { + case i: Identifier if i.name == name && name.nonEmpty => + i.withNullable(false) + SQLTypeUtils.coerce( + i.painless(context), + i.baseType, + out, + nullable = false, + context + ) + case _ => + SQLTypeUtils.coerce(res, out, context) + } + expParam match { + case Some(e) => + if (cond.nullable) { + ctx.addParam(LiteralParam(c)) match { + case Some(c) => s"$e == $c ? $r" + case _ => s"$e == $c ? $r" + } + } else { + s"$e == $c ? $r" + } + case _ => + s"$e == $c ? $r" + } + } + .mkString(" : ") + case _ => + conditions + .map { case (cond, res) => + val name = + cond match { + case e: Expression => + e.identifier.name + case f: FunctionWithIdentifier => + f.identifier.name + case i: Identifier => + i.name + case _ => "" + } + val c = SQLTypeUtils.coerce(cond, SQLTypes.Boolean, context) + val r = + res match { + case i: Identifier if i.name == name && name.nonEmpty => + i.withNullable(false) + SQLTypeUtils.coerce( + i.painless(context), + i.baseType, + out, + nullable = false, + context + ) + case _ => + SQLTypeUtils.coerce(res, out, context) + } + if (!cond.isInstanceOf[CriteriaWithConditionalFunction[_]] && cond.nullable) { + ctx.addParam(LiteralParam(c)) match { + case Some(c) => s"$c ? $r" + case _ => s"$c ? $r" + } + } else { + s"$c ? $r" } - case _ => - s"if (expr == $c) return ${SQLTypeUtils.coerce(res, out)};" + } + .mkString(" : ") + } + default match { + case Some(df) => + val d = SQLTypeUtils.coerce(df, out, context) + if (df.nullable) { + ctx.addParam(LiteralParam(d)) match { + case Some(d) => cases = s"$cases : $d" + case _ => cases = s"$cases : $d" } + } else { + cases = s"$cases : $d" } - case None => - val c = SQLTypeUtils.coerce(cond, SQLTypes.Boolean) - val r = - res match { - case i: Identifier if i.name == name && cond.isInstanceOf[Expression] => - i.withNullable(false) - SQLTypeUtils.coerce(i.toPainless("left"), i.baseType, out, nullable = false) - case _ => SQLTypeUtils.coerce(res, out) - } - s"if ($c) return $r;" + case _ => } - } - .mkString(" ") - val defaultCase = default - .map(d => s"def dval = ${SQLTypeUtils.coerce(d, out)}; return dval;") - .getOrElse("return null;") - s"{ $base$cases $defaultCase }" + + cases + + case _ => + throw new IllegalArgumentException(s"Painless context is required for $sql") + } } - override def toPainless(base: String, idx: Int): String = s"$base${painless()}" + override def toPainless(base: String, idx: Int, context: Option[PainlessContext]): String = + s"$base${painless(context)}" override def nullable: Boolean = conditions.exists { case (_, res) => res.nullable } || default.forall(_.nullable) diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/convert/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/convert/package.scala index cf85f96d..498c06ca 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/convert/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/convert/package.scala @@ -27,12 +27,12 @@ package object convert { //override def nullable: Boolean = value.nullable override def painless(context: Option[PainlessContext] = None): String = - SQLTypeUtils.coerce(value, targetType) + SQLTypeUtils.coerce(value, targetType, context) - override def toPainless(base: String, idx: Int): String = { - val ret = SQLTypeUtils.coerce(base, value.baseType, targetType, value.nullable) + override def toPainless(base: String, idx: Int, context: Option[PainlessContext]): String = { + val ret = SQLTypeUtils.coerce(base, value.baseType, targetType, value.nullable, context) val bloc = ret.startsWith("{") && ret.endsWith("}") - val retWithBrackets = if (bloc) ret else s"{ return $ret; }" + val retWithBrackets = if (bloc) ret else s"{ $ret }" if (safe) s"try $retWithBrackets catch (Exception e) { return null; }" else ret } diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/geo/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/geo/package.scala index 74490996..1cf98f4e 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/geo/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/geo/package.scala @@ -142,7 +142,7 @@ package object geo { val ret = if (oneIdentifier) { - s"arg0${fun.map(_.painless()).getOrElse("")}(params.lat, params.lon)" + s"arg0${fun.map(_.painless(context)).getOrElse("")}(params.lat, params.lon)" } else if (identifiers.isEmpty) { s"${Distance.haversine( fromPoint.get.lat.value, @@ -151,7 +151,7 @@ package object geo { toPoint.get.lon.value )}" } else { - s"arg0${fun.map(_.painless()).getOrElse("")}(arg1.lat, arg1.lon)" + s"arg0${fun.map(_.painless(context)).getOrElse("")}(arg1.lat, arg1.lon)" } if (identifiers.nonEmpty) diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/math/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/math/package.scala index a5826c56..bd5fc1b6 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/math/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/math/package.scala @@ -5,6 +5,7 @@ import app.softnetwork.elastic.sql.{ Identifier, IntValue, PainlessContext, + PainlessParam, PainlessScript, TokenRegex } @@ -80,15 +81,32 @@ package object math { override def nullable: Boolean = arg.nullable } + case class PowParam(scale: Int) extends PainlessParam with PainlessScript { + override def param: String = s"Math.pow(10, $scale)" + override def checkNotNull: String = "" + override def sql: String = param + override def nullable: Boolean = true + + /** Generate painless script for this token + * + * @param context + * the painless context + * @return + * the painless script + */ + override def painless(context: Option[PainlessContext]): String = param + } + case class Round(arg: PainlessScript, scale: Option[Int]) extends MathematicalFunction { override def mathOp: MathOp = Round - override def args: List[PainlessScript] = - List(arg) ++ scale.map(IntValue(_)).toList + override def args: List[PainlessScript] = List(arg, PowParam(scale.getOrElse(0))) - override def toPainlessCall(callArgs: List[String]): String = - s"(def p = ${Pow(IntValue(10), scale.getOrElse(0)) - .painless()}; ${mathOp.painless()}((${callArgs.head} * p) / p))" + override def toPainlessCall(callArgs: List[String], context: Option[PainlessContext]): String = + callArgs match { + case List(a, p) => s"${mathOp.painless(context)}(($a * $p) / $p)" + case _ => throw new IllegalArgumentException("Round function requires exactly one argument") + } } case class Sign(arg: PainlessScript) extends MathematicalFunction { @@ -96,13 +114,12 @@ package object math { override def args: List[PainlessScript] = List(arg) - override def painless(context: Option[PainlessContext]): String = { - val ret = "arg0 > 0 ? 1 : (arg0 < 0 ? -1 : 0)" - if (arg.nullable) - s"(def arg0 = ${arg.painless()}; arg0 != null ? ($ret) : null)" - else - s"(def arg0 = ${arg.painless()}; $ret)" - } + override def toPainlessCall(callArgs: List[String], context: Option[PainlessContext]): String = + callArgs match { + case List(a) => s"($a > 0 ? 1 : ($a < 0 ? -1 : 0))" + case _ => throw new IllegalArgumentException("Sign function requires exactly one argument") + } + } case class Atan2(y: PainlessScript, x: PainlessScript) extends MathematicalFunction { diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala index e706164a..bd240bc7 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala @@ -100,6 +100,18 @@ package object function { this.baseType } } + + def find(function: Function): Option[Function] = { + functions.find(_ == function) + } + + def contains(function: Function): Boolean = { + functions.contains(function) + } + + def indexOf(function: Function): Int = { + functions.indexOf(function) + } } trait FunctionN[In <: SQLType, Out <: SQLType] extends Function with PainlessScript { @@ -124,40 +136,65 @@ package object function { override def toSQL(base: String): String = s"$base$sql" + def checkIfNullable: Boolean = args.exists(_.nullable) + override def painless(context: Option[PainlessContext]): String = { + context match { + case Some(ctx) => + args.foreach(arg => ctx.addParam(arg)) // ensure all args are added to the context + case _ => + } + val nullCheck = - args.zipWithIndex - .filter(_._1.nullable) - .map { case (_, i) => s"arg$i == null" } - .mkString(" || ") + if (checkIfNullable) { + args.zipWithIndex + .filter(_._1.nullable) + .map { case (a, i) => + context.flatMap(ctx => ctx.get(a)).getOrElse(s"arg$i") + " == null" + } + .mkString(" || ") + } else + "" val assignments = args.zipWithIndex .filter(_._1.nullable) .map { case (a, i) => - s"def arg$i = ${SQLTypeUtils.coerce(a.painless(), a.baseType, argTypes(i), nullable = false)};" + context + .flatMap(ctx => ctx.get(a).map(_ => "")) + .getOrElse( + s"def arg$i = ${SQLTypeUtils + .coerce(a.painless(context), a.baseType, argTypes(i), nullable = false, context)};" + ) } .mkString(" ") + .trim val callArgs = args.zipWithIndex .map { case (a, i) => - if (a.nullable) - s"arg$i" - else - SQLTypeUtils.coerce(a.painless(), a.baseType, argTypes(i), nullable = false) + context.flatMap(ctx => ctx.get(a)).getOrElse { + if (a.nullable) s"arg$i" + else + SQLTypeUtils + .coerce(a.painless(context), a.baseType, argTypes(i), nullable = false, context) + } } - if (args.exists(_.nullable)) - s"($assignments ($nullCheck) ? null : ${toPainlessCall(callArgs)})" - else - s"${toPainlessCall(callArgs)}" + val painlessCall = toPainlessCall(callArgs, context) + + if (checkIfNullable) { + if (assignments.nonEmpty) + s"$assignments ($nullCheck) ? $nullValue : $painlessCall" + else s"($nullCheck) ? $nullValue : $painlessCall" + } else + s"$painlessCall" } - def toPainlessCall(callArgs: List[String]): String = + def toPainlessCall(callArgs: List[String], context: Option[PainlessContext]): String = if (callArgs.nonEmpty) - s"${fun.map(_.painless()).getOrElse("")}(${callArgs.mkString(argsSeparator)})" + s"${fun.map(_.painless(context)).getOrElse("")}(${callArgs.mkString(argsSeparator)})" else - fun.map(_.painless()).getOrElse("") + fun.map(_.painless(context)).getOrElse("") } trait BinaryFunction[In1 <: SQLType, In2 <: SQLType, Out <: SQLType] extends FunctionN[In2, Out] { @@ -172,11 +209,31 @@ package object function { } trait TransformFunction[In <: SQLType, Out <: SQLType] extends FunctionN[In, Out] { - def toPainless(base: String, idx: Int): String = { - if (nullable && base.nonEmpty) - s"(def e$idx = $base; e$idx != null ? e$idx${painless()} : null)" - else - s"$base${painless()}" + override def checkIfNullable: Boolean = + super.checkIfNullable && (this match { + case f: FunctionWithIdentifier + if f.identifier.functions.size > 1 && f.identifier.functions.reverse.headOption.exists( + !_.equals(this) + ) => + false + case _ => + true + }) + + def toPainless(base: String, idx: Int, context: Option[PainlessContext]): String = { + context match { + case Some(_) => + val p = painless(context) + if (p.startsWith(".")) // method call + s"$base$p" + else + p + case None => + if (checkIfNullable && base.nonEmpty) + s"(def e$idx = $base; e$idx != null ? e$idx${painless(context)} : null)" + else + s"$base${painless(context)}" + } } } diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/string/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/string/package.scala index e7b10414..2b58dced 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/string/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/string/package.scala @@ -97,8 +97,6 @@ package object string { def stringOp: StringOp - override def fun: Option[PainlessScript] = Some(stringOp) - override def identifier: Identifier = Identifier(this) override def toSQL(base: String): String = s"$sql($base)" @@ -108,11 +106,22 @@ package object string { s"${fun.map(_.sql).getOrElse("")}" else super.sql + + override def toPainlessCall( + callArgs: List[String], + context: Option[PainlessContext] + ): String = { + callArgs match { + case List(str) => s"$str${stringOp.painless(context)}" + case _ => throw new IllegalArgumentException(s"${stringOp.sql} requires 1 argument") + } + } } - case class StringFunctionWithOp(stringOp: StringOp) extends StringFunction[SQLVarchar] { + case class StringFunctionWithOp(str: PainlessScript, stringOp: StringOp) + extends StringFunction[SQLVarchar] { override def outputType: SQLVarchar = SQLTypes.Varchar - override def args: List[PainlessScript] = List.empty + override def args: List[PainlessScript] = List(str) } case class Substring(str: PainlessScript, start: Int, length: Option[Int]) @@ -125,15 +134,18 @@ package object string { override def nullable: Boolean = str.nullable - override def toPainlessCall(callArgs: List[String]): String = { + override def toPainlessCall( + callArgs: List[String], + context: Option[PainlessContext] + ): String = { callArgs match { // SUBSTRING(expr, start, length) - case List(arg0, arg1, arg2) => - s"$arg0.substring($arg1 - 1, Math.min($arg1 - 1 + $arg2, $arg0.length()))" + case List(arg0, _, _) => + s"$arg0.substring(${start - 1}, Math.min(${start - 1 + length.get}, $arg0.length()))" // SUBSTRING(expr, start) case List(arg0, arg1) => - s"$arg0.substring(Math.min($arg1 - 1, $arg0.length() - 1))" + s"$arg0.substring(Math.min(${start - 1}, $arg0.length() - 1))" case _ => throw new IllegalArgumentException("SUBSTRING requires 2 or 3 arguments") } @@ -159,15 +171,24 @@ package object string { override def nullable: Boolean = values.exists(_.nullable) - override def toPainlessCall(callArgs: List[String]): String = { + override def toPainlessCall( + callArgs: List[String], + context: Option[PainlessContext] + ): String = { if (callArgs.isEmpty) throw new IllegalArgumentException("CONCAT requires at least one argument") else callArgs.zipWithIndex .map { case (arg, idx) => - SQLTypeUtils.coerce(arg, values(idx).baseType, SQLTypes.Varchar, nullable = false) + SQLTypeUtils.coerce( + arg, + values(idx).baseType, + SQLTypes.Varchar, + nullable = false, + context + ) } - .mkString(stringOp.painless()) + .mkString(stringOp.painless(context)) } override def validate(): Either[String, Unit] = @@ -181,10 +202,10 @@ package object string { override def toSQL(base: String): String = sql } - case class Length() extends StringFunction[SQLBigInt] { + case class Length(str: PainlessScript) extends StringFunction[SQLBigInt] { override def outputType: SQLBigInt = SQLTypes.BigInt override def stringOp: StringOp = Length - override def args: List[PainlessScript] = List.empty + override def args: List[PainlessScript] = List(str) } case class LeftFunction(str: PainlessScript, length: Int) extends StringFunction[SQLVarchar] { @@ -195,7 +216,10 @@ package object string { override def nullable: Boolean = str.nullable - override def toPainlessCall(callArgs: List[String]): String = { + override def toPainlessCall( + callArgs: List[String], + context: Option[PainlessContext] + ): String = { callArgs match { case List(arg0, arg1) => s"$arg0.substring(0, Math.min($arg1, $arg0.length()))" @@ -220,10 +244,14 @@ package object string { override def nullable: Boolean = str.nullable - override def toPainlessCall(callArgs: List[String]): String = { + override def toPainlessCall( + callArgs: List[String], + context: Option[PainlessContext] + ): String = { callArgs match { case List(arg0, arg1) => - s"""$arg1 == 0 ? "" : $arg0.substring($arg0.length() - Math.min($arg1, $arg0.length()))""" + if (length == 0) "" + else s"""$arg0.substring($arg0.length() - Math.min($arg1, $arg0.length()))""" case _ => throw new IllegalArgumentException("RIGHT requires 2 arguments") } } @@ -246,7 +274,10 @@ package object string { override def nullable: Boolean = str.nullable || search.nullable || replace.nullable - override def toPainlessCall(callArgs: List[String]): String = { + override def toPainlessCall( + callArgs: List[String], + context: Option[PainlessContext] + ): String = { callArgs match { case List(arg0, arg1, arg2) => s"$arg0.replace($arg1, $arg2)" @@ -271,7 +302,10 @@ package object string { override def nullable: Boolean = str.nullable - override def toPainlessCall(callArgs: List[String]): String = { + override def toPainlessCall( + callArgs: List[String], + context: Option[PainlessContext] + ): String = { callArgs match { case List(arg0) => s"new StringBuilder($arg0).reverse().toString()" case _ => throw new IllegalArgumentException("REVERSE requires 1 argument") @@ -293,9 +327,12 @@ package object string { override def nullable: Boolean = substr.nullable || str.nullable - override def toPainlessCall(callArgs: List[String]): String = { + override def toPainlessCall( + callArgs: List[String], + context: Option[PainlessContext] + ): String = { callArgs match { - case List(arg0, arg1, arg2) => s"$arg1.indexOf($arg0, $arg2 - 1) + 1" + case List(arg0, arg1, _) => s"$arg1.indexOf($arg0, ${start - 1}) + 1" case _ => throw new IllegalArgumentException("POSITION requires 3 arguments") } } @@ -326,7 +363,10 @@ package object string { override def nullable: Boolean = str.nullable || pattern.nullable - override def toPainlessCall(callArgs: List[String]): String = { + override def toPainlessCall( + callArgs: List[String], + context: Option[PainlessContext] + ): String = { callArgs match { case List(arg0, arg1) => s"java.util.regex.Pattern.compile($arg1).matcher($arg0).find()" case List(arg0, arg1, arg2) => diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala index 3ed3c0a0..9ab41fac 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala @@ -5,7 +5,9 @@ import app.softnetwork.elastic.sql.{ DateMathScript, Expr, Identifier, + LiteralParam, PainlessContext, + PainlessParam, PainlessScript, StringValue, TokenRegex @@ -58,11 +60,21 @@ package object time { case Right(_) => Right(()) } - override def toPainless(base: String, idx: Int): String = - if (nullable) - s"(def e$idx = $base; e$idx != null ? ${SQLTypeUtils.coerce(s"e$idx", expr.baseType, out, nullable = false)}${painless()} : null)" - else - s"${SQLTypeUtils.coerce(base, expr.baseType, out, nullable = expr.nullable)}${painless()}" + override def toPainless(base: String, idx: Int, context: Option[PainlessContext]): String = + if (nullable) { + context match { + case Some(ctx) => + ctx.last match { + case Some(p) => + return s"($p != null ? ${SQLTypeUtils.coerce(base, expr.baseType, out, nullable = false, context)}${painless(context)} : null)" + case _ => + } + case _ => + } + // ensure unique variable names + s"(def e$idx = $base; e$idx != null ? ${SQLTypeUtils.coerce(s"e$idx", expr.baseType, out, nullable = false, context)}${painless(context)} : null)" + } else + s"${SQLTypeUtils.coerce(base, expr.baseType, out, nullable = expr.nullable, context)}${painless(context)}" } sealed trait AddInterval[IO <: SQLTemporal] extends IntervalFunction[IO] { @@ -102,21 +114,33 @@ package object time { sealed trait CurrentFunction extends SystemFunction with PainlessScript with DateMathScript { override def script: Option[String] = Some("now") + + def param: String + + override def painless(context: Option[PainlessContext]): String = { + context match { + case Some(ctx) => + ctx.addParam(LiteralParam(param)) match { + case Some(p) => + return SQLTypeUtils.coerce(p, this.baseType, this.out, nullable = false, context) + case _ => + } + case _ => + } + SQLTypeUtils.coerce(param, this.baseType, this.out, nullable = false, context) + } } sealed trait CurrentDateTimeFunction extends DateTimeFunction with CurrentFunction { - override def painless(context: Option[PainlessContext]): String = - SQLTypeUtils.coerce(now, this.baseType, this.out, nullable = false) + override def param: String = now } sealed trait CurrentDateFunction extends DateFunction with CurrentFunction { - override def painless(context: Option[PainlessContext]): String = - SQLTypeUtils.coerce(s"$now.toLocalDate()", this.baseType, this.out, nullable = false) + override def param: String = s"$now.toLocalDate()" } sealed trait CurrentTimeFunction extends TimeFunction with CurrentFunction { - override def painless(context: Option[PainlessContext]): String = - SQLTypeUtils.coerce(s"$now.toLocalTime()", this.baseType, this.out, nullable = false) + override def param: String = s"$now.toLocalTime()" } case object CurrentDate extends Expr("CURRENT_DATE") with TokenRegex { @@ -264,25 +288,18 @@ package object time { override def inputType: SQLDate = SQLTypes.Date override def outputType: SQLDate = SQLTypes.Date - override def nullable: Boolean = identifier.nullable - override def sql: String = LastDayOfMonth.sql override def toSQL(base: String): String = { s"$sql($base)" } - override def toPainless(base: String, idx: Int): String = { - val arg = SQLTypeUtils.coerce(base, identifier.baseType, SQLTypes.Date, nullable = false) - if (nullable && base.nonEmpty) - s"(def e$idx = $arg; e$idx != null ? ${toPainlessCall(List(s"e$idx"))} : null)" - else - s"(def e$idx = $arg; ${toPainlessCall(List(s"e$idx"))})" - } - - override def toPainlessCall(callArgs: List[String]): String = { + override def toPainlessCall( + callArgs: List[String], + context: Option[PainlessContext] + ): String = { callArgs match { - case arg :: Nil => s"$arg${LastDayOfMonth.painless()}($arg.lengthOfMonth())" + case arg :: Nil => s"$arg${LastDayOfMonth.painless(context)}($arg.lengthOfMonth())" case _ => throw new IllegalArgumentException("LastDayOfMonth requires exactly one argument") } } @@ -310,8 +327,8 @@ package object time { override def toSQL(base: String): String = s"$sql(${end.sql}, ${start.sql}, ${unit.sql})" - override def toPainlessCall(callArgs: List[String]): String = - s"${unit.painless()}${DateDiff.painless()}(${callArgs.mkString(", ")})" + override def toPainlessCall(callArgs: List[String], context: Option[PainlessContext]): String = + s"${unit.painless(context)}${DateDiff.painless(context)}(${callArgs.mkString(", ")})" } case object DateAdd extends Expr("DATE_ADD") with TokenRegex { @@ -353,6 +370,10 @@ package object time { sealed trait FunctionWithDateTimeFormat { def format: String + def includeTimeZone: Boolean = false + + protected def param: String = s"DateTimeFormatter.ofPattern('${convert()}')" + val sqlToJava: Map[String, String] = Map( "%Y" -> "yyyy", "%y" -> "yy", @@ -381,7 +402,7 @@ package object time { "%X" -> "YYYY" ) - def convert(includeTimeZone: Boolean = false): String = { + def convert(): String = { val basePattern = sqlToJava.foldLeft(format) { case (pattern, (sql, java)) => pattern.replace(sql, java) } @@ -405,9 +426,9 @@ package object time { with FunctionWithIdentifier with FunctionWithDateTimeFormat with DateMathScript { - override def fun: Option[PainlessScript] = Some(DateParse) + override def fun: Option[PainlessScript] = None - override def args: List[PainlessScript] = List.empty + override def args: List[PainlessScript] = List(identifier) override def inputType: SQLVarchar = SQLTypes.Varchar override def outputType: SQLDate = SQLTypes.Date @@ -417,14 +438,29 @@ package object time { s"$sql($base, '$format')" } - override def painless(context: Option[PainlessContext]): String = throw new NotImplementedError( - "Use toPainless instead" - ) - override def toPainless(base: String, idx: Int): String = - if (nullable) - s"(def e$idx = $base; e$idx != null ? DateTimeFormatter.ofPattern('${convert()}').parse(e$idx, LocalDate::from) : null)" - else - s"DateTimeFormatter.ofPattern('${convert()}').parse($base, LocalDate::from)" + override def toPainlessCall(callArgs: List[String], context: Option[PainlessContext]): String = + callArgs match { + case arg :: Nil => + context match { + case Some(ctx) => + identifier.baseType match { + case SQLTypes.Varchar => + ctx.addParam(LiteralParam(s"$param.parse($arg, LocalDate::from)")) match { + case Some(p) => return p + case _ => + } + case _ => + ctx.addParam(LiteralParam(param)) match { + case Some(p) => return s"$p.parse($arg, LocalDate::from)" + case _ => + } + + } + case _ => + } + s"$param.parse($arg, LocalDate::from)" + case _ => throw new IllegalArgumentException("DateParse requires exactly one argument") + } override def script: Option[String] = { val base: String = FunctionUtils @@ -453,7 +489,7 @@ package object time { with FunctionWithDateTimeFormat { override def fun: Option[PainlessScript] = Some(DateFormat) - override def args: List[PainlessScript] = List.empty + override def args: List[PainlessScript] = List(identifier) override def inputType: SQLDate = SQLTypes.Date override def outputType: SQLVarchar = SQLTypes.Varchar @@ -463,14 +499,29 @@ package object time { s"$sql($base, '$format')" } - override def painless(context: Option[PainlessContext]): String = throw new NotImplementedError( - "Use toPainless instead" - ) - override def toPainless(base: String, idx: Int): String = - if (nullable) - s"(def e$idx = $base; e$idx != null ? DateTimeFormatter.ofPattern('${convert()}').format(e$idx) : null)" - else - s"DateTimeFormatter.ofPattern('${convert()}').format($base)" + override def toPainlessCall(callArgs: List[String], context: Option[PainlessContext]): String = + callArgs match { + case arg :: Nil => + context match { + case Some(ctx) => + identifier.baseType match { + case SQLTypes.Varchar => + ctx.addParam(LiteralParam(s"$param.format($arg)")) match { + case Some(p) => return p + case _ => + } + case _ => + ctx.addParam(LiteralParam(param)) match { + case Some(p) => return s"$p.format($arg)" + case _ => + } + + } + case _ => + } + s"$param.format($arg)" + case _ => throw new IllegalArgumentException("DateParse requires exactly one argument") + } } case object DateTimeAdd extends Expr("DATETIME_ADD") with TokenRegex { @@ -521,7 +572,7 @@ package object time { with DateMathScript { override def fun: Option[PainlessScript] = Some(DateTimeParse) - override def args: List[PainlessScript] = List.empty + override def args: List[PainlessScript] = List(identifier) override def inputType: SQLVarchar = SQLTypes.Varchar override def outputType: SQLDateTime = SQLTypes.DateTime @@ -531,14 +582,31 @@ package object time { s"$sql($base, '$format')" } - override def painless(context: Option[PainlessContext]): String = throw new NotImplementedError( - "Use toPainless instead" - ) - override def toPainless(base: String, idx: Int): String = - if (nullable) - s"(def e$idx = $base; e$idx != null ? DateTimeFormatter.ofPattern('${convert(includeTimeZone = true)}').parse(e$idx, ZonedDateTime::from) : null)" - else - s"DateTimeFormatter.ofPattern('${convert(includeTimeZone = true)}').parse($base, ZonedDateTime::from)" + override def includeTimeZone: Boolean = true + + override def toPainlessCall(callArgs: List[String], context: Option[PainlessContext]): String = + callArgs match { + case arg :: Nil => + context match { + case Some(ctx) => + identifier.baseType match { + case SQLTypes.Varchar => + ctx.addParam(LiteralParam(s"$param.parse($arg, ZonedDateTime::from)")) match { + case Some(p) => return p + case _ => + } + case _ => + ctx.addParam(LiteralParam(param)) match { + case Some(p) => return s"$p.parse($arg, ZonedDateTime::from)" + case _ => + } + + } + case _ => + } + s"$param.parse($arg, ZonedDateTime::from)" + case _ => throw new IllegalArgumentException("DateParse requires exactly one argument") + } override def script: Option[String] = { val base: String = FunctionUtils @@ -567,7 +635,7 @@ package object time { with FunctionWithDateTimeFormat { override def fun: Option[PainlessScript] = Some(DateTimeFormat) - override def args: List[PainlessScript] = List.empty + override def args: List[PainlessScript] = List(identifier) override def inputType: SQLDateTime = SQLTypes.DateTime override def outputType: SQLVarchar = SQLTypes.Varchar @@ -577,14 +645,32 @@ package object time { s"$sql($base, '$format')" } - override def painless(context: Option[PainlessContext]): String = throw new NotImplementedError( - "Use toPainless instead" - ) - override def toPainless(base: String, idx: Int): String = - if (nullable) - s"(def e$idx = $base; e$idx != null ? DateTimeFormatter.ofPattern('${convert(includeTimeZone = true)}').format(e$idx) : null)" - else - s"DateTimeFormatter.ofPattern('${convert(includeTimeZone = true)}').format($base)" + override def includeTimeZone: Boolean = true + + override def toPainlessCall(callArgs: List[String], context: Option[PainlessContext]): String = + callArgs match { + case arg :: Nil => + context match { + case Some(ctx) => + identifier.baseType match { + case SQLTypes.Varchar => + ctx.addParam(LiteralParam(s"$param.format($arg)")) match { + case Some(p) => return p + case _ => + } + case _ => + ctx.addParam(LiteralParam(param)) match { + case Some(p) => return s"$p.format($arg)" + case _ => + } + + } + case _ => + } + s"$param.format($arg)" + case _ => throw new IllegalArgumentException("DateParse requires exactly one argument") + } + } } diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/operator/math/ArithmeticExpression.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/operator/math/ArithmeticExpression.scala index 87549a6a..8bd67770 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/operator/math/ArithmeticExpression.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/operator/math/ArithmeticExpression.scala @@ -41,40 +41,71 @@ case class ArithmeticExpression( override def nullable: Boolean = left.nullable || right.nullable - override def toPainless(base: String, idx: Int): String = { + override def toPainless(base: String, idx: Int, context: Option[PainlessContext]): String = { + context match { + case Some(ctx) => + ctx.addParam(left) + ctx.addParam(right) + case _ => + } if (nullable) { - val l = left match { - case t: TransformFunction[_, _] => - SQLTypeUtils.coerce(t.toPainless("", idx + 1), left.baseType, out, nullable = false) - case _ => SQLTypeUtils.coerce(left.painless(), left.baseType, out, nullable = false) - } - val r = right match { - case t: TransformFunction[_, _] => - SQLTypeUtils.coerce(t.toPainless("", idx + 1), right.baseType, out, nullable = false) - case _ => SQLTypeUtils.coerce(right.painless(), right.baseType, out, nullable = false) - } + val l = context + .flatMap(ctx => ctx.get(left)) + .getOrElse(left match { + case t: TransformFunction[_, _] => + SQLTypeUtils.coerce( + t.toPainless("", idx + 1, context), + left.baseType, + out, + nullable = false, + context + ) + case _ => + SQLTypeUtils + .coerce(left.painless(context), left.baseType, out, nullable = false, context) + }) + val r = context + .flatMap(ctx => ctx.get(right)) + .getOrElse(right match { + case t: TransformFunction[_, _] => + SQLTypeUtils.coerce( + t.toPainless("", idx + 1, context), + right.baseType, + out, + nullable = false, + context + ) + case _ => + SQLTypeUtils + .coerce(right.painless(context), right.baseType, out, nullable = false, context) + }) var expr = "" + val leftParam = context.flatMap(ctx => ctx.get(left)).getOrElse(s"lv$idx") + val rightParam = context.flatMap(ctx => ctx.get(right)).getOrElse(s"rv$idx") if (left.nullable) - expr += s"def lv$idx = ($l); " + expr += (if (context.exists(ctx => ctx.get(left).nonEmpty)) "" + else s"def $leftParam = $l; ") if (right.nullable) - expr += s"def rv$idx = ($r); " + expr += (if (context.exists(ctx => ctx.get(right).nonEmpty)) "" + else s"def $rightParam = $r; ") if (left.nullable && right.nullable) - expr += s"(lv$idx == null || rv$idx == null) ? null : (lv$idx ${operator.painless()} rv$idx)" + expr += s"($leftParam == null || $rightParam == null) ? null : ($leftParam ${operator + .painless(context)} $rightParam)" else if (left.nullable) - expr += s"(lv$idx == null) ? null : (lv$idx ${operator.painless()} $r)" + expr += s"($leftParam == null) ? null : ($leftParam ${operator.painless(context)} $r)" else - expr += s"(rv$idx == null) ? null : ($l ${operator.painless()} rv$idx)" + expr += s"($rightParam == null) ? null : ($l ${operator.painless(context)} $rightParam)" if (group) expr = s"($expr)" return s"$base$expr" } - s"$base${painless()}" + s"$base${painless(context)}" } override def painless(context: Option[PainlessContext]): String = { - val l = SQLTypeUtils.coerce(left, out) - val r = SQLTypeUtils.coerce(right, out) - val expr = s"$l ${operator.painless()} $r" + val l = SQLTypeUtils.coerce(left, out, context) + val r = SQLTypeUtils.coerce(right, out, context) + val expr = s"$l ${operator.painless(context)} $r" if (group) s"($expr)" else diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala index 4885eea5..15b7c48d 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala @@ -82,38 +82,94 @@ package object sql { } override def toString: String = - if (nullable) + if (nullable && checkNotNull.nonEmpty) checkNotNull else param } + case class LiteralParam(param: String) extends PainlessParam { + override def sql: String = "" + override def checkNotNull: String = "" + } + /** Context for painless scripts */ case class PainlessContext() { - // Map of parameters to their names - private[this] var params: collection.mutable.Map[PainlessParam, String] = - collection.mutable.Map.empty + // List of parameter keys + private[this] var _keys: collection.mutable.Seq[PainlessParam] = collection.mutable.Seq.empty + + // List of parameter names + private[this] var _values: collection.mutable.Seq[String] = collection.mutable.Seq.empty - /** Add a parameter to the context if not already present + // Last parameter name added + private[this] var _lastParam: Option[String] = None + + /** Add a token parameter to the context if not already present * - * @param param - * the parameter to add + * @param token + * the token parameter to add * @return - * the parameter name + * the optional parameter name */ - def addParam(param: PainlessParam): String = { - params.get(param) match { - case Some(p) => p - case _ => - val paramName = s"param${params.size + 1}" - params = params ++ Map((param -> paramName)) - paramName + def addParam(token: Token): Option[String] = { + token match { + case param: PainlessParam + if param.param.nonEmpty && (param.isInstanceOf[LiteralParam] || param.nullable) => + get(param) match { + case Some(p) => Some(p) + case _ => + val index = _values.indexOf(param.param) + if (index >= 0) { + Some(param.param) + } else { + val paramName = s"param${_keys.size + 1}" + _keys = _keys :+ param + _values = _values :+ paramName + _lastParam = Some(paramName) + _lastParam + } + } + case _ => None } } - override def toString: String = - params.map { case (k, v) => s"def $v=$k" }.mkString("; ") + def get(token: Token): Option[String] = { + token match { + case param: PainlessParam => + if (exists(param)) Try(_values(_keys.indexOf(param))).toOption + else None + case f: FunctionWithIdentifier => get(f.identifier) + case _ => None + } + } + + def exists(token: Token): Boolean = { + token match { + case param: PainlessParam => _keys.contains(param) + case f: FunctionWithIdentifier => exists(f.identifier) + case _ => false + } + } + + def isEmpty: Boolean = _keys.isEmpty + + def nonEmpty: Boolean = _keys.nonEmpty + + def last: Option[String] = _lastParam + + override def toString: String = { + if (isEmpty) "" + else + _keys + .flatMap { param => + get(param) match { + case Some(v) => Some(s"def $v = $param; ") + case None => None // should not happen + } + } + .mkString("") + } } trait PainlessParams extends PainlessScript { @@ -184,7 +240,8 @@ package object sql { }, this.baseType, this.out, - nullable = false + nullable = false, + context ) override def nullable: Boolean = false @@ -378,7 +435,7 @@ package object sql { with PainlessScript { override def sql = s"(${values.map(_.sql).mkString(",")})" override def painless(context: Option[PainlessContext]): String = - s"[${values.map(_.painless()).mkString(",")}]" + s"[${values.map(_.painless(context)).mkString(",")}]" lazy val innerValues: Seq[R] = values.map(_.value) override def nullable: Boolean = values.exists(_.nullable) override def baseType: SQLArray = SQLTypes.Array(SQLTypes.Any) @@ -587,13 +644,13 @@ package object sql { s"doc['$path'].value" else "" - def toPainless(base: String): String = { + def toPainless(base: String, context: Option[PainlessContext]): String = { val orderedFunctions = FunctionUtils.transformFunctions(this).reverse var expr = base orderedFunctions.zipWithIndex.foreach { case (f, idx) => f match { - case f: TransformFunction[_, _] => expr = f.toPainless(expr, idx) - case f: PainlessScript => expr = s"$expr${f.painless()}" + case f: TransformFunction[_, _] => expr = f.toPainless(expr, idx, context) + case f: PainlessScript => expr = s"$expr${f.painless(context)}" case f => expr = f.toSQL(expr) // fallback } } @@ -656,27 +713,18 @@ package object sql { s"(!doc.containsKey('$path') || doc['$path'].empty ? $nullValue : doc['$path'].value)" override def painless(context: Option[PainlessContext]): String = { - val base = + toPainless( context match { case Some(ctx) => - ctx.addParam(this) - ctx.toString + ctx.addParam(this).getOrElse("") case _ => if (nullable) checkNotNull else paramName - } - val orderedFunctions = FunctionUtils.transformFunctions(this).reverse - var expr = base - orderedFunctions.zipWithIndex.foreach { case (f, idx) => - f match { - case f: TransformFunction[_, _] => expr = f.toPainless(expr, idx) - case f: PainlessScript => expr = s"$expr${f.painless(context)}" - case f => expr = f.toSQL(expr) // fallback - } - } - expr + }, + context + ) } override def param: String = paramName @@ -698,7 +746,7 @@ package object sql { override def value: String = script match { case Some(s) => s - case _ => painless() + case _ => painless(None) } def withNested(nested: Boolean): Identifier = this match { diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Parser.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Parser.scala index 3e5ef049..15b319c5 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Parser.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Parser.scala @@ -106,7 +106,7 @@ trait Parser } def sql_function: PackratParser[Function] = - aggregate_function | time_function | conditional_function | string_function + aggregate_function | time_function | conditional_function private val reservedKeywords = Seq( "select", diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/string/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/string/package.scala index a265a5ff..f401d1ac 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/string/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/string/package.scala @@ -64,44 +64,53 @@ package object string { ) } - def stringFunctionWithIdentifier: PackratParser[Identifier] = - (concat | substr | left | right | replace | reverse | position | regexp) ^^ { sf => - sf.identifier - } - def length: PackratParser[StringFunction[SQLBigInt]] = - Length.regex ^^ { _ => - Length() + Length.regex ~ start ~ valueExpr ~ end ^^ { case _ ~ _ ~ v ~ _ => + Length(v) } def lower: PackratParser[StringFunction[SQLVarchar]] = - Lower.regex ^^ { _ => - StringFunctionWithOp(Lower) + Lower.regex ~ start ~ valueExpr ~ end ^^ { case _ ~ _ ~ v ~ _ => + StringFunctionWithOp(v, Lower) } def upper: PackratParser[StringFunction[SQLVarchar]] = - Upper.regex ^^ { _ => - StringFunctionWithOp(Upper) + Upper.regex ~ start ~ valueExpr ~ end ^^ { case _ ~ _ ~ v ~ _ => + StringFunctionWithOp(v, Upper) } def trim: PackratParser[StringFunction[SQLVarchar]] = - Trim.regex ^^ { _ => - StringFunctionWithOp(Trim) + Trim.regex ~ start ~ valueExpr ~ end ^^ { case _ ~ _ ~ v ~ _ => + StringFunctionWithOp(v, Trim) } def ltrim: PackratParser[StringFunction[SQLVarchar]] = - Ltrim.regex ^^ { _ => - StringFunctionWithOp(Ltrim) + Ltrim.regex ~ start ~ valueExpr ~ end ^^ { case _ ~ _ ~ v ~ _ => + StringFunctionWithOp(v, Ltrim) } def rtrim: PackratParser[StringFunction[SQLVarchar]] = - Rtrim.regex ^^ { _ => - StringFunctionWithOp(Rtrim) + Rtrim.regex ~ start ~ valueExpr ~ end ^^ { case _ ~ _ ~ v ~ _ => + StringFunctionWithOp(v, Rtrim) } - def string_function: Parser[ - StringFunction[_] - ] = /*concatFunction | substringFunction |*/ length | lower | upper | trim | ltrim | rtrim + def stringFunctionWithIdentifier: PackratParser[Identifier] = + (concat | + substr | + left | + right | + replace | + reverse | + position | + regexp | + length | + lower | + upper | + trim | + ltrim | + rtrim) ^^ { sf => + sf.identifier + } } } diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/time/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/time/package.scala index f0061cec..cd8b2882 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/time/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/time/package.scala @@ -1,7 +1,7 @@ package app.softnetwork.elastic.sql.parser.function import app.softnetwork.elastic.sql.{function, Identifier, StringValue} -import app.softnetwork.elastic.sql.`type`.{SQLLiteral, SQLNumeric, SQLTemporal} +import app.softnetwork.elastic.sql.`type`.{SQLLiteral, SQLNumeric, SQLTemporal, SQLTypes} import app.softnetwork.elastic.sql.function.{ BinaryFunction, FunctionWithIdentifier, @@ -83,7 +83,9 @@ package object time { def last_day: Parser[DateFunction with FunctionWithIdentifier] = LastDayOfMonth.regex ~ start ~ (identifierWithTransformation | identifierWithIntervalFunction | identifierWithFunction | identifier) ~ end ^^ { - case _ ~ _ ~ i ~ _ => LastDayOfMonth(i) + case _ ~ _ ~ i ~ _ => + i.cast(SQLTypes.Date) + LastDayOfMonth(i) } def date_function: PackratParser[DateFunction with FunctionWithIdentifier] = diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/GroupBy.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/GroupBy.scala index ba43a67b..5d6ffd4c 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/GroupBy.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/GroupBy.scala @@ -85,7 +85,7 @@ object MetricSelectorScript { val leftStr = metricSelector(left) val rightStr = metricSelector(right) val opStr = op match { - case AND | OR => op.painless() + case AND | OR => op.painless(None) case _ => throw new IllegalArgumentException(s"Unsupported logical operator: $op") } val not = maybeNot.nonEmpty @@ -99,7 +99,7 @@ object MetricSelectorScript { case _: MatchCriteria => "1 == 1" //MATCH is not supported in bucket_selector case e: Expression if e.aggregation => - val painless = e.painless() + val painless = e.painless(None) e.maybeValue match { case Some(value) if e.operator.isInstanceOf[ComparisonOperator] => value.out match { // compare epoch millis diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/Where.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/Where.scala index 8cc01462..be324297 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/Where.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/Where.scala @@ -346,17 +346,17 @@ sealed trait Expression extends FunctionChain with ElasticFilter with Criteria { def painlessNot: String = operator match { case _: ComparisonOperator => "" - case _ => maybeNot.map(_.painless()).getOrElse("") + case _ => maybeNot.map(_.painless(None)).getOrElse("") } def painlessOp: String = operator match { - case o: ComparisonOperator if maybeNot.isDefined => o.not.painless() - case _ => operator.painless() + case o: ComparisonOperator if maybeNot.isDefined => o.not.painless(None) + case _ => operator.painless(None) } - def painlessValue: String = maybeValue + def painlessValue(context: Option[PainlessContext]): String = maybeValue .map { - case v: PainlessScript => v.painless() + case v: PainlessScript => v.painless(context) case v => v.sql } .getOrElse("") /*{ @@ -366,25 +366,37 @@ sealed trait Expression extends FunctionChain with ElasticFilter with Criteria { } }*/ - protected lazy val left: String = { + protected def left(context: Option[PainlessContext]): String = { val targetedType = maybeValue match { - case Some(v) => v.out + case Some(v) => SQLTypeUtils.leastCommonSuperType(List(identifier.out, v.out)) case None => identifier.out } - SQLTypeUtils.coerce(identifier, targetedType) + SQLTypeUtils.coerce(identifier, targetedType, context) } - protected lazy val check: String = + protected def check(context: Option[PainlessContext]): String = operator match { - case _: ComparisonOperator => s" $painlessOp $painlessValue" - case _ => s"$painlessOp($painlessValue)" + case _: ComparisonOperator => s" $painlessOp ${painlessValue(context)}" + case _ => s"$painlessOp(${painlessValue(context)})" } override def painless(context: Option[PainlessContext]): String = { + context match { + case Some(ctx) => + ctx.addParam(identifier) match { + case Some(p) => + if (identifier.nullable) + return s"$p == null ? false : $painlessNot($p${check(context)})" + else + return s"$painlessNot($p${check(context)})" + case _ => + } + case _ => + } if (identifier.nullable) { - return s"def left = $left; left == null ? false : ${painlessNot}left$check" + return s"def left = ${left(context)}; left == null ? false : ${painlessNot}left${check(context)}" } - s"$painlessNot$left$check" + s"$painlessNot${left(context)}${check(context)}" } override def validate(): Either[String, Unit] = { @@ -497,10 +509,18 @@ case class IsNullCriteria(identifier: Identifier) extends CriteriaWithConditiona updated } override def painless(context: Option[PainlessContext]): String = { + context match { + case Some(ctx) => + ctx.addParam(identifier) match { + case Some(p) => return s"$p == null" // TODO check with not + case _ => + } + case _ => + } if (identifier.nullable) { - return s"def left = $left; left == null" + return s"def left = ${left(context)}; left == null" } - s"$painlessNot$left$check" + s"${left(context)} == null" } } @@ -520,10 +540,18 @@ case class IsNotNullCriteria(identifier: Identifier) } override def painless(context: Option[PainlessContext]): String = { + context match { + case Some(ctx) => + ctx.addParam(identifier) match { + case Some(p) => return s"$p != null" // TODO check with not + case _ => + } + case _ => + } if (identifier.nullable) { - return s"def left = $left; left != null" + return s"def left = ${left(context)}; left != null" } - s"$painlessNot$left$check" + s"${left(context)} != null" } } @@ -569,7 +597,7 @@ case class InExpr[R, +T <: Value[R]]( override def asFilter(currentQuery: Option[ElasticBoolQuery]): ElasticFilter = this override def painless(context: Option[PainlessContext]): String = - s"$painlessNot${identifier.painless()}$painlessOp($painlessValue)" + s"$painlessNot${identifier.painless(context)}$painlessOp(${painlessValue(context)})" } @@ -601,10 +629,22 @@ case class BetweenExpr( } override def painless(context: Option[PainlessContext]): String = { + context match { + case Some(ctx) => + ctx.addParam(identifier) match { + case Some(p) => + if (identifier.nullable) + return s"$p == null ? false : $painlessNot($p >= ${fromTo.from} && $p <= ${fromTo.to})" + else + return s"$painlessNot($p >= ${fromTo.from} && $p <= ${fromTo.to})" + case _ => + } + case _ => + } if (identifier.nullable) { - return s"def left = $left; left == null ? false : $painlessNot(${fromTo.from} <= left <= ${fromTo.to})" + return s"def left = ${left(context)}; left == null ? false : $painlessNot(${fromTo.from} <= left <= ${fromTo.to})" } - s"$painlessNot(${fromTo.from} <= $left <= ${fromTo.to})" + s"$painlessNot(${fromTo.from} <= ${left(context)} <= ${fromTo.to})" } } @@ -706,7 +746,7 @@ case class ElasticMatch( override def matchCriteria: Boolean = true override def painless(context: Option[PainlessContext]): String = - s"$painlessNot${identifier.painless()}$painlessOp($painlessValue)" + s"$painlessNot${identifier.painless(context)}$painlessOp(${painlessValue(context)})" } diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/time/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/time/package.scala index cb6eff37..7cb263c4 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/time/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/time/package.scala @@ -135,7 +135,8 @@ package object time { def unit: TimeUnit override def sql: String = s"$Interval $value ${unit.sql}" - override def painless(context: Option[PainlessContext]): String = s"$value, ${unit.painless()}" + override def painless(context: Option[PainlessContext]): String = + s"$value, ${unit.painless(context)}" override def script: Option[String] = Some(TimeInterval.script(this)) diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypeUtils.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypeUtils.scala index ec791209..7fe9b048 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypeUtils.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypeUtils.scala @@ -1,6 +1,6 @@ package app.softnetwork.elastic.sql.`type` -import app.softnetwork.elastic.sql.PainlessScript +import app.softnetwork.elastic.sql.{LiteralParam, PainlessContext, PainlessScript} import app.softnetwork.elastic.sql.`type`.SQLTypes._ object SQLTypeUtils { @@ -87,14 +87,20 @@ object SQLTypeUtils { SQLTypes.Any } - def coerce(in: PainlessScript, to: SQLType): String = { - val expr = in.painless() + def coerce(in: PainlessScript, to: SQLType, context: Option[PainlessContext]): String = { + val expr = in.painless(context) val from = in.baseType val nullable = in.nullable - coerce(expr, from, to, nullable) + coerce(expr, from, to, nullable, context) } - def coerce(expr: String, from: SQLType, to: SQLType, nullable: Boolean): String = { + def coerce( + expr: String, + from: SQLType, + to: SQLType, + nullable: Boolean, + context: Option[PainlessContext] + ): String = { val ret = { (from, to) match { // ---- DATE & TIME ---- @@ -149,12 +155,52 @@ object SQLTypeUtils { // ---- VARCHAR -> TEMPORAL ---- case (SQLTypes.Varchar, SQLTypes.Date) => + context match { + case Some(ctx) => + ctx.addParam( + LiteralParam(s"LocalDate.parse($expr, DateTimeFormatter.ofPattern('yyyy-MM-dd'))") + ) match { + case Some(p) => return p + case None => // continue + } + case None => // continue + } s"LocalDate.parse($expr, DateTimeFormatter.ofPattern('yyyy-MM-dd'))" case (SQLTypes.Varchar, SQLTypes.Time) => + context match { + case Some(ctx) => + ctx.addParam( + LiteralParam(s"LocalTime.parse($expr, DateTimeFormatter.ofPattern('HH:mm:ss'))") + ) match { + case Some(p) => return p + case None => // continue + } + case None => // continue + } s"LocalTime.parse($expr, DateTimeFormatter.ofPattern('HH:mm:ss'))" case (SQLTypes.Varchar, SQLTypes.DateTime) => - s"ZonedDateTime.parse($expr, DateTimeFormatter.ISO_DATE_TIME)" + context match { + case Some(ctx) => + ctx.addParam( + LiteralParam(s"LocalDateTime.parse($expr, DateTimeFormatter.ISO_DATE_TIME)") + ) match { + case Some(p) => return p + case None => // continue + } + case None => // continue + } + s"LocalDateTime.parse($expr, DateTimeFormatter.ISO_DATE_TIME)" case (SQLTypes.Varchar, SQLTypes.Timestamp) => + context match { + case Some(ctx) => + ctx.addParam( + LiteralParam(s"ZonedDateTime.parse($expr, DateTimeFormatter.ISO_ZONED_DATE_TIME)") + ) match { + case Some(p) => return p + case None => // continue + } + case None => // continue + } s"ZonedDateTime.parse($expr, DateTimeFormatter.ISO_ZONED_DATE_TIME)" // ---- IDENTITY ---- diff --git a/sql/src/test/scala/app/softnetwork/elastic/sql/SQLDateTimeFunctionSuite.scala b/sql/src/test/scala/app/softnetwork/elastic/sql/SQLDateTimeFunctionSuite.scala index 4a9e3621..a9998da9 100644 --- a/sql/src/test/scala/app/softnetwork/elastic/sql/SQLDateTimeFunctionSuite.scala +++ b/sql/src/test/scala/app/softnetwork/elastic/sql/SQLDateTimeFunctionSuite.scala @@ -39,7 +39,7 @@ class SQLDateTimeFunctionSuite extends AnyFunSuite { require(transforms.nonEmpty, "No transforms provided") val initial: (String, SQLType) = - (transforms.head.toPainless(base, 0), transforms.head.outputType.asInstanceOf[SQLType]) + (transforms.head.toPainless(base, 0, None), transforms.head.outputType.asInstanceOf[SQLType]) val (finalExpr, _) = transforms.tail.foldLeft(initial) { case ((expr, currentType), t: FunctionN[_, _]) => @@ -48,7 +48,7 @@ class SQLDateTimeFunctionSuite extends AnyFunSuite { s"Type mismatch: expected ${currentType.getClass.getSimpleName}, got ${t.inputType.getClass.getSimpleName}" ) } - (t.toPainless(expr, 0), t.outputType.asInstanceOf[SQLType]) + (t.toPainless(expr, 0, None), t.outputType.asInstanceOf[SQLType]) } finalExpr @@ -88,7 +88,7 @@ class SQLDateTimeFunctionSuite extends AnyFunSuite { // Test simple pour chaque fonction individuelle transformFunctions.foreach { f => test(s"Single transformation ${f.sql}") { - val result = f.toPainless(baseDate, 0) + val result = f.toPainless(baseDate, 0, None) assert(result.nonEmpty) } } From 77ba4a7bc362ad5a139dd3e2a23d78bc945db0b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Manciot?= Date: Fri, 17 Oct 2025 07:27:00 +0200 Subject: [PATCH 03/21] fix group by index --- .../elastic/sql/SQLQuerySpec.scala | 65 +++++++++++++++++ .../elastic/sql/SQLQuerySpec.scala | 69 +++++++++++++++++++ .../elastic/sql/query/SQLSearchRequest.scala | 16 ++++- 3 files changed, 147 insertions(+), 3 deletions(-) diff --git a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index 77e9f3de..039d011b 100644 --- a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -1026,6 +1026,71 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll(">", " > ") } + it should "handle group by index" in { + val select: ElasticSearchRequest = + SQLQuery( + groupByWithHavingAndDateTimeFunctions.replace("GROUP BY Country, City", "GROUP BY 3, 2") + ) + val query = select.query + println(query) + query shouldBe + """{ + | "query": { + | "match_all": {} + | }, + | "size": 0, + | "_source": true, + | "aggs": { + | "Country": { + | "terms": { + | "field": "Country.keyword", + | "exclude": "USA", + | "order": { + | "_key": "asc" + | } + | }, + | "aggs": { + | "City": { + | "terms": { + | "field": "City.keyword", + | "exclude": "Berlin" + | }, + | "aggs": { + | "cnt": { + | "value_count": { + | "field": "CustomerID" + | } + | }, + | "lastSeen": { + | "max": { + | "field": "createdAt" + | } + | }, + | "having_filter": { + | "bucket_selector": { + | "buckets_path": { + | "cnt": "cnt", + | "lastSeen": "lastSeen" + | }, + | "script": { + | "source": "params.cnt > 1 && params.lastSeen > ZonedDateTime.now(ZoneId.of('Z')).minus(7, ChronoUnit.DAYS).toInstant().toEpochMilli()" + | } + | } + | } + | } + | } + | } + | } + | } + |}""".stripMargin + .replaceAll("\\s", "") + .replaceAll("ChronoUnit", " ChronoUnit") + .replaceAll("==", " == ") + .replaceAll("!=", " != ") + .replaceAll("&&", " && ") + .replaceAll(">", " > ") + } + it should "handle date_parse function" in { val select: ElasticSearchRequest = SQLQuery(dateParse) diff --git a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index 46a3e0e9..100046f1 100644 --- a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -1026,6 +1026,75 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll(">", " > ") } + it should "handle group by index" in { + val select: ElasticSearchRequest = + SQLQuery( + groupByWithHavingAndDateTimeFunctions.replace("GROUP BY Country, City", "GROUP BY 3, 2") + ) + val query = select.query + println(query) + query shouldBe + """{ + | "query": { + | "match_all": {} + | }, + | "size": 0, + | "_source": true, + | "aggs": { + | "Country": { + | "terms": { + | "field": "Country.keyword", + | "exclude": [ + | "USA" + | ], + | "order": { + | "_key": "asc" + | } + | }, + | "aggs": { + | "City": { + | "terms": { + | "field": "City.keyword", + | "exclude": [ + | "Berlin" + | ] + | }, + | "aggs": { + | "cnt": { + | "value_count": { + | "field": "CustomerID" + | } + | }, + | "lastSeen": { + | "max": { + | "field": "createdAt" + | } + | }, + | "having_filter": { + | "bucket_selector": { + | "buckets_path": { + | "cnt": "cnt", + | "lastSeen": "lastSeen" + | }, + | "script": { + | "source": "params.cnt > 1 && params.lastSeen > ZonedDateTime.now(ZoneId.of('Z')).minus(7, ChronoUnit.DAYS).toInstant().toEpochMilli()" + | } + | } + | } + | } + | } + | } + | } + | } + |}""".stripMargin + .replaceAll("\\s", "") + .replaceAll("ChronoUnit", " ChronoUnit") + .replaceAll("==", " == ") + .replaceAll("!=", " != ") + .replaceAll("&&", " && ") + .replaceAll(">", " > ") + } + it should "handle date_parse function" in { val select: ElasticSearchRequest = SQLQuery(dateParse) diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/SQLSearchRequest.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/SQLSearchRequest.scala index 0f631a0c..0dc21cfc 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/SQLSearchRequest.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/SQLSearchRequest.scala @@ -19,8 +19,18 @@ case class SQLSearchRequest( lazy val fieldAliases: Map[String, String] = select.fieldAliases lazy val tableAliases: Map[String, String] = from.tableAliases lazy val unnestAliases: Map[String, (String, Option[Limit])] = from.unnestAliases - lazy val bucketNames: Map[String, Bucket] = buckets.map { b => - b.identifier.identifierName -> b + lazy val bucketNames: Map[String, Bucket] = buckets.flatMap { b => + val name = b.identifier.identifierName + "\\d+".r.findFirstIn(name) match { + case Some(n) => + val identifier = select.fields(n.toInt - 1).identifier + val updated = b.copy(identifier = select.fields(n.toInt - 1).identifier) + Map( + n -> updated, // also map numeric bucket to field name + identifier.identifierName -> updated + ) + case _ => Map(name -> b) + } }.toMap lazy val unnests: Map[String, Unnest] = from.unnests.map(u => u.alias.map(_.alias).getOrElse(u.name) -> u).toMap @@ -32,7 +42,6 @@ case class SQLSearchRequest( lazy val nested: Seq[NestedElement] = from.unnests.map(toNestedElement).groupBy(_.path).map(_._2.head).toList private[this] lazy val nestedFieldsWithoutCriteria: Map[String, Seq[Field]] = { - // nested fields that are not part of where, having or group by clauses val innerHitsWithCriteria = (where.map(_.nestedElements).getOrElse(Seq.empty) ++ having.map(_.nestedElements).getOrElse(Seq.empty) ++ groupBy.map(_.nestedElements).getOrElse(Seq.empty)) @@ -45,6 +54,7 @@ case class SQLSearchRequest( } ret } + // nested fields that are not part of where, having or group by clauses lazy val nestedElementsWithoutCriteria: Seq[NestedElement] = nested.filter(n => nestedFieldsWithoutCriteria.keys.toSeq.contains(n.innerHitsName)) From 50e1c83ac338c032de57d79413438094a832bf0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Manciot?= Date: Fri, 17 Oct 2025 07:28:55 +0200 Subject: [PATCH 04/21] rename MatchCriteria to MultiMatchCriteria and ElasticMatch to MatchCriteria --- .../elastic/sql/bridge/ElasticQuery.scala | 8 +-- .../elastic/sql/bridge/package.scala | 2 +- .../elastic/sql/bridge/ElasticQuery.scala | 4 +- .../elastic/sql/bridge/package.scala | 2 +- .../elastic/sql/parser/WhereParser.scala | 6 +- .../elastic/sql/query/GroupBy.scala | 4 +- .../softnetwork/elastic/sql/query/Where.scala | 72 ++++++++++--------- 7 files changed, 50 insertions(+), 48 deletions(-) diff --git a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticQuery.scala b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticQuery.scala index c99899f4..3d069d9e 100644 --- a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticQuery.scala +++ b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticQuery.scala @@ -6,7 +6,6 @@ import app.softnetwork.elastic.sql.query.{ ElasticBoolQuery, ElasticChild, ElasticFilter, - ElasticMatch, ElasticNested, ElasticParent, GenericExpression, @@ -15,6 +14,7 @@ import app.softnetwork.elastic.sql.query.{ IsNotNullExpr, IsNullCriteria, IsNullExpr, + MatchCriteria, NestedElement, NestedElements, Predicate @@ -152,9 +152,9 @@ case class ElasticQuery(filter: ElasticFilter) { case in: InExpr[_, _] => in case between: BetweenExpr => between // case geoDistance: DistanceCriteria => geoDistance - case matchExpression: ElasticMatch => matchExpression - case isNull: IsNullCriteria => isNull - case isNotNull: IsNotNullCriteria => isNotNull + case matchExpression: MatchCriteria => matchExpression + case isNull: IsNullCriteria => isNull + case isNotNull: IsNotNullCriteria => isNotNull case other => throw new IllegalArgumentException(s"Unsupported filter type: ${other.getClass.getName}") } diff --git a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala index ba5d9612..aae044ac 100644 --- a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala +++ b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala @@ -751,7 +751,7 @@ package object bridge { } implicit def matchToQuery( - matchExpression: ElasticMatch + matchExpression: MatchCriteria ): Query = { import matchExpression._ matchQuery(identifier.name, value.value) diff --git a/sql/bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticQuery.scala b/sql/bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticQuery.scala index 8cc8150a..300b7376 100644 --- a/sql/bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticQuery.scala +++ b/sql/bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticQuery.scala @@ -6,7 +6,7 @@ import app.softnetwork.elastic.sql.query.{ ElasticBoolQuery, ElasticChild, ElasticFilter, - ElasticMatch, + MatchCriteria, ElasticNested, ElasticParent, GenericExpression, @@ -157,7 +157,7 @@ case class ElasticQuery(filter: ElasticFilter) { case in: InExpr[_, _] => in case between: BetweenExpr => between // case geoDistance: DistanceCriteria => geoDistance - case matchExpression: ElasticMatch => matchExpression + case matchExpression: MatchCriteria => matchExpression case isNull: IsNullCriteria => isNull case isNotNull: IsNotNullCriteria => isNotNull case other => diff --git a/sql/bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala b/sql/bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala index 3dce76c5..c5cf10d1 100644 --- a/sql/bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala +++ b/sql/bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala @@ -752,7 +752,7 @@ package object bridge { } implicit def matchToQuery( - matchExpression: ElasticMatch + matchExpression: MatchCriteria ): Query = { import matchExpression._ matchQuery(identifier.name, value.value) diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/WhereParser.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/WhereParser.scala index 0eced705..9d655c6a 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/WhereParser.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/WhereParser.scala @@ -54,7 +54,7 @@ import app.softnetwork.elastic.sql.query.{ InExpr, IsNotNullExpr, IsNullExpr, - MatchCriteria, + MultiMatchCriteria, Predicate, Where } @@ -193,12 +193,12 @@ trait WhereParser { DistanceCriteria(d, o, g) }*/ - def matchCriteria: PackratParser[MatchCriteria] = + def matchCriteria: PackratParser[MultiMatchCriteria] = MATCH.regex ~ start ~ rep1sep( any_identifier, separator ) ~ end ~ AGAINST.regex ~ start ~ literal ~ end ^^ { case _ ~ _ ~ i ~ _ ~ _ ~ _ ~ l ~ _ => - MatchCriteria(i, l) + MultiMatchCriteria(i, l) } def and: PackratParser[PredicateOperator] = AND.regex ^^ (_ => AND) diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/GroupBy.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/GroupBy.scala index 5d6ffd4c..1de0a935 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/GroupBy.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/GroupBy.scala @@ -70,7 +70,7 @@ object MetricSelectorScript { case Predicate(left, _, right, _, _) => extractMetricsPath(left) ++ extractMetricsPath(right) case relation: ElasticRelation => extractMetricsPath(relation.criteria) - case _: MatchCriteria => Map.empty //MATCH is not supported in bucket_selector + case _: MultiMatchCriteria => Map.empty //MATCH is not supported in bucket_selector case e: Expression if e.aggregation => import e._ maybeValue match { @@ -96,7 +96,7 @@ object MetricSelectorScript { case relation: ElasticRelation => metricSelector(relation.criteria) - case _: MatchCriteria => "1 == 1" //MATCH is not supported in bucket_selector + case _: MultiMatchCriteria => "1 == 1" //MATCH is not supported in bucket_selector case e: Expression if e.aggregation => val painless = e.painless(None) diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/Where.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/Where.scala index be324297..b2982c3f 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/Where.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/Where.scala @@ -19,7 +19,7 @@ sealed trait Criteria extends Updateable with PainlessScript { case Predicate(left, _, right, _, _) => left.identifiers ++ right.identifiers case c: Expression => c.identifiers case relation: ElasticRelation => relation.criteria.identifiers - case m: MatchCriteria => m.identifiers + case m: MultiMatchCriteria => m.identifiers case _ => Nil } @@ -29,50 +29,52 @@ sealed trait Criteria extends Updateable with PainlessScript { def nestedElements: Seq[NestedElement] = this match { - case p: Predicate => p.nestedElements - case r: ElasticRelation => r.criteria.nestedElements - case e: Expression => e.nestedElement.toSeq - case m: MatchCriteria => m.criteria.nestedElements - case _ => Nil + case p: Predicate => p.nestedElements + case r: ElasticRelation => r.criteria.nestedElements + case e: Expression => e.nestedElement.toSeq + case m: MultiMatchCriteria => m.criteria.nestedElements + case _ => Nil } def nestedCriteria(innerHitsName: String): Seq[Criteria] = { this match { case e: ElasticNested => e.criteria.nestedCriteria(innerHitsName) case _ => - nestedElement - .filter(_ => nestedElement.exists(_.innerHitsName == innerHitsName)) + nestedElements + .find(n => n.innerHitsName == innerHitsName) .map(_ => this) .toSeq } } - def extractMetricsPath: Map[String, String] = this match { // used for bucket_selector - case Predicate(left, _, right, _, _) => - left.extractMetricsPath ++ right.extractMetricsPath - case relation: ElasticRelation => relation.criteria.extractMetricsPath - case _: MatchCriteria => Map.empty //MATCH is not supported in bucket_selector - case e: Expression => e.extractMetricsPath - case _ => Map.empty - } + def extractMetricsPath: Map[String, String] = + this match { // used for bucket_selector + case Predicate(left, _, right, _, _) => + left.extractMetricsPath ++ right.extractMetricsPath + case relation: ElasticRelation => relation.criteria.extractMetricsPath + case _: MultiMatchCriteria => Map.empty //MATCH is not supported in bucket_selector + case e: Expression => e.extractMetricsPath + case _ => Map.empty + } def includes( bucket: Bucket, not: Boolean, bucketIncludesExcludes: BucketIncludesExcludes - ): BucketIncludesExcludes = this match { - case Predicate(left, _, right, n, _) => - right.includes( - bucket, - (!not && n.isDefined) || (not && n.isEmpty), - left.includes(bucket, not, bucketIncludesExcludes) - ) - case relation: ElasticRelation => - relation.criteria.includes(bucket, not, bucketIncludesExcludes) - case m: MatchCriteria => m.criteria.includes(bucket, not, bucketIncludesExcludes) - case e: Expression => e.includes(bucket, not, bucketIncludesExcludes) - case _ => bucketIncludesExcludes - } + ): BucketIncludesExcludes = + this match { + case Predicate(left, _, right, n, _) => + right.includes( + bucket, + (!not && n.isDefined) || (not && n.isEmpty), + left.includes(bucket, not, bucketIncludesExcludes) + ) + case relation: ElasticRelation => + relation.criteria.includes(bucket, not, bucketIncludesExcludes) + case m: MultiMatchCriteria => m.criteria.includes(bucket, not, bucketIncludesExcludes) + case e: Expression => e.includes(bucket, not, bucketIncludesExcludes) + case _ => bucketIncludesExcludes + } def excludes( bucket: Bucket, @@ -120,7 +122,7 @@ sealed trait Criteria extends Updateable with PainlessScript { else s"$leftStr $opStr $rightStr" case relation: ElasticRelation => asGroup(relation.criteria.painless(context)) - case m: MatchCriteria => asGroup(m.criteria.painless(context)) + case m: MultiMatchCriteria => asGroup(m.criteria.painless(context)) case expr: Expression => asGroup(expr.painless(context)) case _ => throw new IllegalArgumentException(s"Unsupported criteria: $this") } @@ -669,11 +671,11 @@ case class DistanceCriteria( override def asFilter(currentQuery: Option[ElasticBoolQuery]): ElasticFilter = this } -case class MatchCriteria( +case class MultiMatchCriteria( override val identifiers: Seq[Identifier], value: StringValue, nestedElement: Option[NestedElement] = None -) extends Criteria { +) extends Criteria { // FIXME map to multi_match override def sql: String = s"$operator (${identifiers.mkString(",")}) $AGAINST ($value)" override def operator: Operator = MATCH @@ -683,7 +685,7 @@ case class MatchCriteria( override lazy val nested: Boolean = identifiers.forall(_.nested) @tailrec - private[this] def toCriteria(matches: List[ElasticMatch], curr: Criteria): Criteria = + private[this] def toCriteria(matches: List[MatchCriteria], curr: Criteria): Criteria = matches match { case Nil => curr case single :: Nil => Predicate(curr, OR, single) @@ -691,7 +693,7 @@ case class MatchCriteria( } lazy val criteria: Criteria = - (identifiers.map(id => ElasticMatch(id, value, None)) match { + (identifiers.map(id => MatchCriteria(id, value, None)) match { case Nil => throw new IllegalArgumentException("No identifiers for MATCH") case single :: Nil => single case first :: rest => toCriteria(rest, first) @@ -710,7 +712,7 @@ case class MatchCriteria( override def group: Boolean = false } -case class ElasticMatch( +case class MatchCriteria( identifier: Identifier, value: StringValue, options: Option[String] From de0c8b8191bbe15e143bca5ebdd39fe3f94d8675 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Manciot?= Date: Fri, 17 Oct 2025 07:31:26 +0200 Subject: [PATCH 05/21] take into account having criteria within nested filter aggregation --- .../elastic/sql/bridge/package.scala | 68 ++++++++-- .../elastic/sql/SQLQuerySpec.scala | 127 ++++++++++++++---- .../elastic/sql/bridge/package.scala | 73 ++++++++-- .../elastic/sql/SQLQuerySpec.scala | 119 +++++++++++++--- 4 files changed, 314 insertions(+), 73 deletions(-) diff --git a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala index aae044ac..caf9dc9c 100644 --- a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala +++ b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala @@ -23,27 +23,69 @@ package object bridge { implicit def requestToNestedFilterAggregation( request: SQLSearchRequest, innerHitsName: String - ): Option[FilterAggregation] = - request.where.flatMap(_.criteria) match { - case Some(f) => - f.nestedCriteria(innerHitsName) match { - case Nil => None - case cs => - val boolQuery = ElasticBoolQuery(group = true) - cs.map(c => boolQuery.filter(c.asFilter(Option(boolQuery)))) - Some( - filterAgg( - s"filtered_$innerHitsName", + ): Option[FilterAggregation] = { + val having: Option[Query] = + request.having.flatMap(_.criteria) match { + case Some(f) => + f.nestedCriteria(innerHitsName) match { + case Nil => None + case cs => + val boolQuery = ElasticBoolQuery(group = true) + cs.map(c => boolQuery.filter(c.asFilter(Option(boolQuery)))) + Some( boolQuery.query( request.aggregates.flatMap(_.identifier.innerHitsName).toSet, Option(boolQuery) ) ) - ) - } + } + case _ => + None + } + val where: Option[Query] = + request.where.flatMap(_.criteria) match { + case Some(f) => + f.nestedCriteria(innerHitsName) match { + case Nil => None + case cs => + val boolQuery = ElasticBoolQuery(group = true) + cs.map(c => boolQuery.filter(c.asFilter(Option(boolQuery)))) + Some( + boolQuery.query( + request.aggregates.flatMap(_.identifier.innerHitsName).toSet, + Option(boolQuery) + ) + ) + } + case _ => + None + } + (having, where) match { + case (Some(h), Some(w)) => + Some( + filterAgg( + s"filtered_$innerHitsName", + boolQuery().filter(h, w) + ) + ) + case (Some(h), None) => + Some( + filterAgg( + s"filtered_$innerHitsName", + h + ) + ) + case (None, Some(w)) => + Some( + filterAgg( + s"filtered_$innerHitsName", + w + ) + ) case _ => None } + } implicit def requestToFilterAggregation( request: SQLSearchRequest diff --git a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index 039d011b..2c8b29a4 100644 --- a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -28,7 +28,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { "SQLQuery" should "perform native count" in { val results: Seq[ElasticAggregation] = - SQLQuery("select count(t.id) c2 from Table t where t.nom = \"Nom\"") + SQLQuery("select count(t.id) c2 from Table t where t.nom = 'Nom'") results.size shouldBe 1 val result = results.head result.nested shouldBe false @@ -64,7 +64,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { it should "perform count distinct" in { val results: Seq[ElasticAggregation] = - SQLQuery("select count(distinct t.id) as c2 from Table as t where nom = \"Nom\"") + SQLQuery("select count(distinct t.id) as c2 from Table as t where nom = 'Nom'") results.size shouldBe 1 val result = results.head result.nested shouldBe false @@ -101,7 +101,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { it should "perform nested count" in { val results: Seq[ElasticAggregation] = SQLQuery( - "select count(inner_emails.value) as email from index i join unnest(i.emails) as inner_emails where i.nom = \"Nom\"" + "select count(inner_emails.value) as email from index i join unnest(i.emails) as inner_emails where i.nom = 'Nom'" ) results.size shouldBe 1 val result = results.head @@ -719,29 +719,106 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "path": "products" | }, | "aggs": { - | "cat": { - | "terms": { - | "field": "products.category.keyword" + | "filtered_inner_products": { + | "filter": { + | "bool": { + | "filter": [ + | { + | "bool": { + | "must_not": [ + | { + | "term": { + | "products.category": { + | "value": "coffee" + | } + | } + | } + | ] + | } + | }, + | { + | "match_all": {} + | }, + | { + | "match_all": {} + | }, + | { + | "bool": { + | "should": [ + | { + | "match": { + | "products.name": { + | "query": "lasagnes" + | } + | } + | }, + | { + | "match": { + | "products.description": { + | "query": "lasagnes" + | } + | } + | }, + | { + | "match": { + | "products.ingredients": { + | "query": "lasagnes" + | } + | } + | } + | ] + | } + | }, + | { + | "range": { + | "products.stock": { + | "gt": 0 + | } + | } + | }, + | { + | "term": { + | "products.upForSale": { + | "value": true + | } + | } + | }, + | { + | "term": { + | "products.deleted": { + | "value": false + | } + | } + | } + | ] + | } | }, | "aggs": { - | "min_price": { - | "min": { - | "field": "products.price" - | } - | }, - | "max_price": { - | "max": { - | "field": "products.price" - | } - | }, - | "having_filter": { - | "bucket_selector": { - | "buckets_path": { - | "min_price": "inner_products>min_price", - | "max_price": "inner_products>max_price" + | "cat": { + | "terms": { + | "field": "products.category.keyword" + | }, + | "aggs": { + | "min_price": { + | "min": { + | "field": "products.price" + | } | }, - | "script": { - | "source": "params.min_price > 5.0 && params.max_price < 50.0" + | "max_price": { + | "max": { + | "field": "products.price" + | } + | }, + | "having_filter": { + | "bucket_selector": { + | "buckets_path": { + | "min_price": "inner_products>min_price", + | "max_price": "inner_products>max_price" + | }, + | "script": { + | "source": "params.min_price > 5.0 && params.max_price < 50.0" + | } + | } | } | } | } @@ -965,7 +1042,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { it should "handle group by with having and date time functions" in { val select: ElasticSearchRequest = - SQLQuery(groupByWithHavingAndDateTimeFunctions.replace("GROUP BY 3, 2", "GROUP BY 3, 2")) + SQLQuery(groupByWithHavingAndDateTimeFunctions) val query = select.query println(query) query shouldBe @@ -1629,7 +1706,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("ChronoUnit", " ChronoUnit") } - it should "handle is_null function as script field" in { // TODO replace with param1 == null + it should "handle is_null function as script field" in { val select: ElasticSearchRequest = SQLQuery(isnull) val query = select.query diff --git a/sql/bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala b/sql/bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala index c5cf10d1..6ccc2488 100644 --- a/sql/bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala +++ b/sql/bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala @@ -27,24 +27,69 @@ package object bridge { implicit def requestToNestedFilterAggregation( request: SQLSearchRequest, innerHitsName: String - ): Option[FilterAggregation] = - request.where.flatMap(_.criteria) match { - case Some(f) => - f.nestedCriteria(innerHitsName) match { - case Nil => None - case cs => - val boolQuery = ElasticBoolQuery(group = true) - cs.map(c => boolQuery.filter(c.asFilter(Option(boolQuery)))) - Some( - filterAgg( - s"filtered_$innerHitsName", - boolQuery.query(request.aggregates.flatMap(_.identifier.innerHitsName).toSet, Option(boolQuery)) + ): Option[FilterAggregation] = { + val having: Option[Query] = + request.having.flatMap(_.criteria) match { + case Some(f) => + f.nestedCriteria(innerHitsName) match { + case Nil => None + case cs => + val boolQuery = ElasticBoolQuery(group = true) + cs.map(c => boolQuery.filter(c.asFilter(Option(boolQuery)))) + Some( + boolQuery.query( + request.aggregates.flatMap(_.identifier.innerHitsName).toSet, + Option(boolQuery) + ) ) - ) - } + } + case _ => + None + } + val where: Option[Query] = + request.where.flatMap(_.criteria) match { + case Some(f) => + f.nestedCriteria(innerHitsName) match { + case Nil => None + case cs => + val boolQuery = ElasticBoolQuery(group = true) + cs.map(c => boolQuery.filter(c.asFilter(Option(boolQuery)))) + Some( + boolQuery.query( + request.aggregates.flatMap(_.identifier.innerHitsName).toSet, + Option(boolQuery) + ) + ) + } + case _ => + None + } + (having, where) match { + case (Some(h), Some(w)) => + Some( + filterAgg( + s"filtered_$innerHitsName", + boolQuery().filter(h, w) + ) + ) + case (Some(h), None) => + Some( + filterAgg( + s"filtered_$innerHitsName", + h + ) + ) + case (None, Some(w)) => + Some( + filterAgg( + s"filtered_$innerHitsName", + w + ) + ) case _ => None } + } implicit def requestToFilterAggregation( request: SQLSearchRequest diff --git a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index 100046f1..ef93591c 100644 --- a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -719,29 +719,106 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "path": "products" | }, | "aggs": { - | "cat": { - | "terms": { - | "field": "products.category.keyword" + | "filtered_inner_products": { + | "filter": { + | "bool": { + | "filter": [ + | { + | "bool": { + | "must_not": [ + | { + | "term": { + | "products.category": { + | "value": "coffee" + | } + | } + | } + | ] + | } + | }, + | { + | "match_all": {} + | }, + | { + | "match_all": {} + | }, + | { + | "bool": { + | "should": [ + | { + | "match": { + | "products.name": { + | "query": "lasagnes" + | } + | } + | }, + | { + | "match": { + | "products.description": { + | "query": "lasagnes" + | } + | } + | }, + | { + | "match": { + | "products.ingredients": { + | "query": "lasagnes" + | } + | } + | } + | ] + | } + | }, + | { + | "range": { + | "products.stock": { + | "gt": 0 + | } + | } + | }, + | { + | "term": { + | "products.upForSale": { + | "value": true + | } + | } + | }, + | { + | "term": { + | "products.deleted": { + | "value": false + | } + | } + | } + | ] + | } | }, | "aggs": { - | "min_price": { - | "min": { - | "field": "products.price" - | } - | }, - | "max_price": { - | "max": { - | "field": "products.price" - | } - | }, - | "having_filter": { - | "bucket_selector": { - | "buckets_path": { - | "min_price": "inner_products>min_price", - | "max_price": "inner_products>max_price" + | "cat": { + | "terms": { + | "field": "products.category.keyword" + | }, + | "aggs": { + | "min_price": { + | "min": { + | "field": "products.price" + | } | }, - | "script": { - | "source": "params.min_price > 5.0 && params.max_price < 50.0" + | "max_price": { + | "max": { + | "field": "products.price" + | } + | }, + | "having_filter": { + | "bucket_selector": { + | "buckets_path": { + | "min_price": "inner_products>min_price", + | "max_price": "inner_products>max_price" + | }, + | "script": { + | "source": "params.min_price > 5.0 && params.max_price < 50.0" + | } + | } | } | } | } @@ -1633,7 +1710,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("ChronoUnit", " ChronoUnit") } - it should "handle is_null function as script field" in { // TODO replace with param1 == null + it should "handle is_null function as script field" in { val select: ElasticSearchRequest = SQLQuery(isnull) val query = select.query From eb836041a45d7877f7e818d0b03a7eb89e57a741 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Manciot?= Date: Fri, 17 Oct 2025 07:34:04 +0200 Subject: [PATCH 06/21] update join unnest example --- documentation/request_structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/request_structure.md b/documentation/request_structure.md index 1ad37f1d..f942df23 100644 --- a/documentation/request_structure.md +++ b/documentation/request_structure.md @@ -45,7 +45,7 @@ Expand an array / nested field into rows. Mapped to Elasticsearch `nested` and i ```sql SELECT id, phone FROM customers -JOIN UNNEST(phones) AS phone; +JOIN UNNEST(customers.phones) AS phone; ``` --- From 8a0757cd385648db4667b6d17efc68e60459c7ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Manciot?= Date: Fri, 17 Oct 2025 08:40:23 +0200 Subject: [PATCH 07/21] fix parser specification --- .../app/softnetwork/elastic/sql/function/math/package.scala | 5 +++++ .../softnetwork/elastic/sql/function/string/package.scala | 6 ++++-- .../main/scala/app/softnetwork/elastic/sql/package.scala | 4 ++-- .../scala/app/softnetwork/elastic/sql/SQLParserSpec.scala | 3 +-- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/math/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/math/package.scala index bd5fc1b6..769386cb 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/math/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/math/package.scala @@ -107,6 +107,11 @@ package object math { case List(a, p) => s"${mathOp.painless(context)}(($a * $p) / $p)" case _ => throw new IllegalArgumentException("Round function requires exactly one argument") } + + override def sql: String = { + s"${fun.map(_.sql).getOrElse("")}($arg${scale.map(s => s", $s").getOrElse("")})" + } + } case class Sign(arg: PainlessScript) extends MathematicalFunction { diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/string/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/string/package.scala index 2b58dced..189c1f1c 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/string/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/string/package.scala @@ -99,13 +99,15 @@ package object string { override def identifier: Identifier = Identifier(this) - override def toSQL(base: String): String = s"$sql($base)" + override def toSQL(base: String): String = + if (base.nonEmpty) s"$sql($base)" + else sql override def sql: String = if (args.isEmpty) s"${fun.map(_.sql).getOrElse("")}" else - super.sql + s"$stringOp(${args.map(_.sql).mkString(argsSeparator)})" override def toPainlessCall( callArgs: List[String], diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala index 15b7c48d..f3e56b91 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala @@ -81,7 +81,7 @@ package object sql { } } - override def toString: String = + def pvalue: String = if (nullable && checkNotNull.nonEmpty) checkNotNull else @@ -164,7 +164,7 @@ package object sql { _keys .flatMap { param => get(param) match { - case Some(v) => Some(s"def $v = $param; ") + case Some(v) => Some(s"def $v = ${param.pvalue}; ") case None => None // should not happen } } diff --git a/sql/src/test/scala/app/softnetwork/elastic/sql/SQLParserSpec.scala b/sql/src/test/scala/app/softnetwork/elastic/sql/SQLParserSpec.scala index c74d48a4..72e40fca 100644 --- a/sql/src/test/scala/app/softnetwork/elastic/sql/SQLParserSpec.scala +++ b/sql/src/test/scala/app/softnetwork/elastic/sql/SQLParserSpec.scala @@ -826,8 +826,7 @@ class SQLParserSpec extends AnyFlatSpec with Matchers { val result = Parser(mathematical) result.toOption .flatMap(_.left.toOption.map(_.sql)) - .getOrElse("") - .equalsIgnoreCase(mathematical) shouldBe true + .getOrElse("") shouldBe mathematical } it should "parse string functions" in { From 13047eac7e8db10daa64ca79e290286fcce682b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Manciot?= Date: Fri, 17 Oct 2025 08:54:39 +0200 Subject: [PATCH 08/21] fix query specification --- .../test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala | 4 ++-- .../test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index 2c8b29a4..b4ef803f 100644 --- a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -2425,13 +2425,13 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.pow(param1, 3)" | } | }, - | "round_identifier_math_pow_10_0": { + | "round_identifier": { | "script": { | "lang": "painless", | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); def param2 = Math.pow(10, 0); (param1 == null || param2 == null) ? null : Math.round((param1 * param2) / param2)" | } | }, - | "round_identifier_math_pow_10_2": { + | "round_identifier_2": { | "script": { | "lang": "painless", | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); def param2 = Math.pow(10, 2); (param1 == null || param2 == null) ? null : Math.round((param1 * param2) / param2)" diff --git a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index ef93591c..9846098f 100644 --- a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -2429,13 +2429,13 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); (param1 == null) ? null : Math.pow(param1, 3)" | } | }, - | "round_identifier_math_pow_10_0": { + | "round_identifier": { | "script": { | "lang": "painless", | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); def param2 = Math.pow(10, 0); (param1 == null || param2 == null) ? null : Math.round((param1 * param2) / param2)" | } | }, - | "round_identifier_math_pow_10_2": { + | "round_identifier_2": { | "script": { | "lang": "painless", | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); def param2 = Math.pow(10, 2); (param1 == null || param2 == null) ? null : Math.round((param1 * param2) / param2)" From d6fa0bda9fa37669b4d62c84180357483f979071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Manciot?= Date: Fri, 17 Oct 2025 10:17:33 +0200 Subject: [PATCH 09/21] add license header --- build.sbt | 4 ++++ .../elastic/client/ElasticConfig.scala | 16 ++++++++++++++++ .../elastic/client/AggregateResult.scala | 16 ++++++++++++++++ .../elastic/client/ElasticClientApi.scala | 16 ++++++++++++++++ .../elastic/client/MappingComparator.scala | 16 ++++++++++++++++ .../app/softnetwork/elastic/client/package.scala | 16 ++++++++++++++++ .../persistence/query/ElasticProvider.scala | 16 ++++++++++++++++ .../query/State2ElasticProcessorStream.scala | 16 ++++++++++++++++ .../elastic/persistence/typed/Elastic.scala | 16 ++++++++++++++++ .../elastic/client/jest/JestClientApi.scala | 16 ++++++++++++++++ .../client/jest/JestClientCompanion.scala | 16 ++++++++++++++++ .../client/jest/JestClientResultHandler.scala | 16 ++++++++++++++++ .../elastic/persistence/query/JestProvider.scala | 16 ++++++++++++++++ ...2ElasticProcessorStreamWithJestProvider.scala | 16 ++++++++++++++++ .../client/rest/RestHighLevelClientApi.scala | 16 ++++++++++++++++ .../rest/RestHighLevelClientCompanion.scala | 16 ++++++++++++++++ .../query/RestHighLevelClientProvider.scala | 16 ++++++++++++++++ ...2ElasticProcessorStreamWithRestProvider.scala | 16 ++++++++++++++++ .../elastic/sql/bridge/ElasticAggregation.scala | 16 ++++++++++++++++ .../elastic/sql/bridge/ElasticCriteria.scala | 16 ++++++++++++++++ .../sql/bridge/ElasticMultiSearchRequest.scala | 16 ++++++++++++++++ .../elastic/sql/bridge/ElasticQuery.scala | 16 ++++++++++++++++ .../sql/bridge/ElasticSearchRequest.scala | 16 ++++++++++++++++ .../softnetwork/elastic/sql/bridge/package.scala | 16 ++++++++++++++++ .../softnetwork/elastic/sql/SQLQuerySpec.scala | 2 +- .../scalatest/EmbeddedElasticTestKit.scala | 16 ++++++++++++++++ .../client/rest/RestHighLevelClientApi.scala | 16 ++++++++++++++++ .../rest/RestHighLevelClientCompanion.scala | 16 ++++++++++++++++ .../query/RestHighLevelClientProvider.scala | 16 ++++++++++++++++ ...2ElasticProcessorStreamWithRestProvider.scala | 16 ++++++++++++++++ .../client/java/ElasticsearchClientApi.scala | 16 ++++++++++++++++ .../java/ElasticsearchClientCompanion.scala | 16 ++++++++++++++++ .../query/ElasticsearchClientProvider.scala | 16 ++++++++++++++++ ...2ElasticProcessorStreamWithJavaProvider.scala | 16 ++++++++++++++++ .../client/java/ElasticsearchClientApi.scala | 16 ++++++++++++++++ .../java/ElasticsearchClientCompanion.scala | 16 ++++++++++++++++ .../query/ElasticsearchClientProvider.scala | 16 ++++++++++++++++ ...2ElasticProcessorStreamWithJavaProvider.scala | 16 ++++++++++++++++ project/plugins.sbt | 2 ++ .../softnetwork/elastic/sql/SQLImplicits.scala | 16 ++++++++++++++++ .../elastic/sql/function/aggregate/package.scala | 16 ++++++++++++++++ .../elastic/sql/function/cond/package.scala | 16 ++++++++++++++++ .../elastic/sql/function/convert/package.scala | 16 ++++++++++++++++ .../elastic/sql/function/geo/package.scala | 16 ++++++++++++++++ .../elastic/sql/function/math/package.scala | 16 ++++++++++++++++ .../elastic/sql/function/package.scala | 16 ++++++++++++++++ .../elastic/sql/function/string/package.scala | 16 ++++++++++++++++ .../elastic/sql/function/time/package.scala | 16 ++++++++++++++++ .../sql/operator/math/ArithmeticExpression.scala | 16 ++++++++++++++++ .../elastic/sql/operator/math/package.scala | 16 ++++++++++++++++ .../elastic/sql/operator/package.scala | 16 ++++++++++++++++ .../elastic/sql/operator/time/package.scala | 16 ++++++++++++++++ .../app/softnetwork/elastic/sql/package.scala | 16 ++++++++++++++++ .../elastic/sql/parser/Delimiter.scala | 16 ++++++++++++++++ .../elastic/sql/parser/FromParser.scala | 16 ++++++++++++++++ .../elastic/sql/parser/GroupByParser.scala | 16 ++++++++++++++++ .../elastic/sql/parser/HavingParser.scala | 16 ++++++++++++++++ .../elastic/sql/parser/LimitParser.scala | 16 ++++++++++++++++ .../elastic/sql/parser/OrderByParser.scala | 16 ++++++++++++++++ .../softnetwork/elastic/sql/parser/Parser.scala | 16 ++++++++++++++++ .../elastic/sql/parser/SelectParser.scala | 16 ++++++++++++++++ .../elastic/sql/parser/Validator.scala | 16 ++++++++++++++++ .../elastic/sql/parser/WhereParser.scala | 16 ++++++++++++++++ .../sql/parser/function/aggregate/package.scala | 16 ++++++++++++++++ .../sql/parser/function/cond/package.scala | 16 ++++++++++++++++ .../sql/parser/function/convert/package.scala | 16 ++++++++++++++++ .../sql/parser/function/geo/package.scala | 16 ++++++++++++++++ .../sql/parser/function/math/package.scala | 16 ++++++++++++++++ .../sql/parser/function/string/package.scala | 16 ++++++++++++++++ .../sql/parser/function/time/package.scala | 16 ++++++++++++++++ .../sql/parser/operator/math/package.scala | 16 ++++++++++++++++ .../elastic/sql/parser/time/package.scala | 16 ++++++++++++++++ .../elastic/sql/parser/type/package.scala | 16 ++++++++++++++++ .../app/softnetwork/elastic/sql/query/From.scala | 16 ++++++++++++++++ .../softnetwork/elastic/sql/query/GroupBy.scala | 16 ++++++++++++++++ .../softnetwork/elastic/sql/query/Having.scala | 16 ++++++++++++++++ .../softnetwork/elastic/sql/query/Limit.scala | 16 ++++++++++++++++ .../softnetwork/elastic/sql/query/OrderBy.scala | 16 ++++++++++++++++ .../sql/query/SQLMultiSearchRequest.scala | 16 ++++++++++++++++ .../softnetwork/elastic/sql/query/SQLQuery.scala | 16 ++++++++++++++++ .../elastic/sql/query/SQLSearchRequest.scala | 16 ++++++++++++++++ .../softnetwork/elastic/sql/query/Select.scala | 16 ++++++++++++++++ .../softnetwork/elastic/sql/query/Where.scala | 16 ++++++++++++++++ .../softnetwork/elastic/sql/time/package.scala | 16 ++++++++++++++++ .../softnetwork/elastic/sql/type/SQLType.scala | 16 ++++++++++++++++ .../elastic/sql/type/SQLTypeUtils.scala | 16 ++++++++++++++++ .../softnetwork/elastic/sql/type/SQLTypes.scala | 16 ++++++++++++++++ 87 files changed, 1351 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 610063bf..d2818533 100644 --- a/build.sbt +++ b/build.sbt @@ -23,6 +23,10 @@ ThisBuild / version := "0.9.3" ThisBuild / scalaVersion := scala213 +ThisBuild / organizationName := "SOFTNETWORK" +ThisBuild / startYear := Some(2015) +ThisBuild / licenses += ("Apache-2.0", url("https://www.apache.org/licenses/LICENSE-2.0.txt")) + ThisBuild / dependencyOverrides ++= Seq( "com.fasterxml.jackson.module" %% "jackson-module-scala" % Versions.jackson, "com.github.jnr" % "jnr-ffi" % "2.2.17", diff --git a/core/src/main/scala-2.13/app/softnetwork/elastic/client/ElasticConfig.scala b/core/src/main/scala-2.13/app/softnetwork/elastic/client/ElasticConfig.scala index 9c230d39..9fb89f78 100644 --- a/core/src/main/scala-2.13/app/softnetwork/elastic/client/ElasticConfig.scala +++ b/core/src/main/scala-2.13/app/softnetwork/elastic/client/ElasticConfig.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.client import com.typesafe.config.{Config, ConfigFactory} diff --git a/core/src/main/scala/app/softnetwork/elastic/client/AggregateResult.scala b/core/src/main/scala/app/softnetwork/elastic/client/AggregateResult.scala index f5544489..5cfb3d46 100644 --- a/core/src/main/scala/app/softnetwork/elastic/client/AggregateResult.scala +++ b/core/src/main/scala/app/softnetwork/elastic/client/AggregateResult.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.client import app.softnetwork.elastic.sql.function.aggregate.AggregateFunction diff --git a/core/src/main/scala/app/softnetwork/elastic/client/ElasticClientApi.scala b/core/src/main/scala/app/softnetwork/elastic/client/ElasticClientApi.scala index 418eca87..d6f5293c 100644 --- a/core/src/main/scala/app/softnetwork/elastic/client/ElasticClientApi.scala +++ b/core/src/main/scala/app/softnetwork/elastic/client/ElasticClientApi.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.client import java.time.LocalDate diff --git a/core/src/main/scala/app/softnetwork/elastic/client/MappingComparator.scala b/core/src/main/scala/app/softnetwork/elastic/client/MappingComparator.scala index dfa1ca37..197e5074 100644 --- a/core/src/main/scala/app/softnetwork/elastic/client/MappingComparator.scala +++ b/core/src/main/scala/app/softnetwork/elastic/client/MappingComparator.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.client import com.google.gson._ diff --git a/core/src/main/scala/app/softnetwork/elastic/client/package.scala b/core/src/main/scala/app/softnetwork/elastic/client/package.scala index a1883f77..c9227556 100644 --- a/core/src/main/scala/app/softnetwork/elastic/client/package.scala +++ b/core/src/main/scala/app/softnetwork/elastic/client/package.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic import akka.stream.{Attributes, FlowShape, Inlet, Outlet} diff --git a/core/src/main/scala/app/softnetwork/elastic/persistence/query/ElasticProvider.scala b/core/src/main/scala/app/softnetwork/elastic/persistence/query/ElasticProvider.scala index bf45187b..c7486a3f 100644 --- a/core/src/main/scala/app/softnetwork/elastic/persistence/query/ElasticProvider.scala +++ b/core/src/main/scala/app/softnetwork/elastic/persistence/query/ElasticProvider.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.persistence.query import app.softnetwork.elastic.client.ElasticClientApi diff --git a/core/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStream.scala b/core/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStream.scala index 88af3f3b..e74c6b22 100644 --- a/core/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStream.scala +++ b/core/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStream.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.persistence.query import app.softnetwork.persistence.ManifestWrapper diff --git a/core/src/main/scala/app/softnetwork/elastic/persistence/typed/Elastic.scala b/core/src/main/scala/app/softnetwork/elastic/persistence/typed/Elastic.scala index 1efe4d59..7d9c2775 100644 --- a/core/src/main/scala/app/softnetwork/elastic/persistence/typed/Elastic.scala +++ b/core/src/main/scala/app/softnetwork/elastic/persistence/typed/Elastic.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.persistence.typed import app.softnetwork.persistence._ diff --git a/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClientApi.scala b/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClientApi.scala index 2af5369f..322aa087 100644 --- a/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClientApi.scala +++ b/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClientApi.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.client.jest import akka.NotUsed diff --git a/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClientCompanion.scala b/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClientCompanion.scala index 0ed8655e..046ba3eb 100644 --- a/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClientCompanion.scala +++ b/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClientCompanion.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.client.jest import app.softnetwork.elastic.client.{ElasticConfig, ElasticCredentials} diff --git a/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClientResultHandler.scala b/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClientResultHandler.scala index fc05e2e2..3d6f4746 100644 --- a/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClientResultHandler.scala +++ b/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClientResultHandler.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.client.jest import io.searchbox.action.Action diff --git a/es6/jest/src/main/scala/app/softnetwork/elastic/persistence/query/JestProvider.scala b/es6/jest/src/main/scala/app/softnetwork/elastic/persistence/query/JestProvider.scala index 1cdb739d..e30610c8 100644 --- a/es6/jest/src/main/scala/app/softnetwork/elastic/persistence/query/JestProvider.scala +++ b/es6/jest/src/main/scala/app/softnetwork/elastic/persistence/query/JestProvider.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.persistence.query import app.softnetwork.elastic.client.jest.JestClientApi diff --git a/es6/jest/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithJestProvider.scala b/es6/jest/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithJestProvider.scala index 673457b9..0a2971dc 100644 --- a/es6/jest/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithJestProvider.scala +++ b/es6/jest/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithJestProvider.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.persistence.query import app.softnetwork.persistence.message.CrudEvent diff --git a/es6/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientApi.scala b/es6/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientApi.scala index 2b93ffa4..9b529b41 100644 --- a/es6/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientApi.scala +++ b/es6/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientApi.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.client.rest import akka.NotUsed diff --git a/es6/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientCompanion.scala b/es6/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientCompanion.scala index 2379923d..61732246 100644 --- a/es6/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientCompanion.scala +++ b/es6/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientCompanion.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.client.rest import app.softnetwork.elastic.client.ElasticConfig diff --git a/es6/rest/src/main/scala/app/softnetwork/elastic/persistence/query/RestHighLevelClientProvider.scala b/es6/rest/src/main/scala/app/softnetwork/elastic/persistence/query/RestHighLevelClientProvider.scala index b76c13b4..849c2ac3 100644 --- a/es6/rest/src/main/scala/app/softnetwork/elastic/persistence/query/RestHighLevelClientProvider.scala +++ b/es6/rest/src/main/scala/app/softnetwork/elastic/persistence/query/RestHighLevelClientProvider.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.persistence.query import app.softnetwork.elastic.client.rest.RestHighLevelClientApi diff --git a/es6/rest/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithRestProvider.scala b/es6/rest/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithRestProvider.scala index 3f79bb07..58526a3e 100644 --- a/es6/rest/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithRestProvider.scala +++ b/es6/rest/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithRestProvider.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.persistence.query import app.softnetwork.persistence.message.CrudEvent diff --git a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticAggregation.scala b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticAggregation.scala index 58120bfc..4e9aa3d3 100644 --- a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticAggregation.scala +++ b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticAggregation.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.bridge import app.softnetwork.elastic.sql.PainlessContext diff --git a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticCriteria.scala b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticCriteria.scala index f117d0a9..568f8305 100644 --- a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticCriteria.scala +++ b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticCriteria.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.bridge import app.softnetwork.elastic.sql.query.Criteria diff --git a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticMultiSearchRequest.scala b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticMultiSearchRequest.scala index 61925b7b..7380d050 100644 --- a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticMultiSearchRequest.scala +++ b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticMultiSearchRequest.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.bridge import com.sksamuel.elastic4s.http.search.MultiSearchBuilderFn diff --git a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticQuery.scala b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticQuery.scala index 3d069d9e..3d59c048 100644 --- a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticQuery.scala +++ b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticQuery.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.bridge import app.softnetwork.elastic.sql.operator.AND diff --git a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticSearchRequest.scala b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticSearchRequest.scala index bac7afb9..1801701c 100644 --- a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticSearchRequest.scala +++ b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticSearchRequest.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.bridge import app.softnetwork.elastic.sql.query.{Bucket, Criteria, Except, Field} diff --git a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala index caf9dc9c..072e5724 100644 --- a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala +++ b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql import app.softnetwork.elastic.sql.`type`.{SQLBigInt, SQLDouble, SQLTemporal, SQLVarchar} diff --git a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index b4ef803f..dbd167a9 100644 --- a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -1287,7 +1287,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll(",LocalDate", ", LocalDate") } - it should "handle datetime_parse function" in { + it should "handle datetime_parse function" in { // #25 val select: ElasticSearchRequest = SQLQuery(dateTimeParse) val query = select.query diff --git a/es6/testkit/src/main/scala/app/softnetwork/elastic/scalatest/EmbeddedElasticTestKit.scala b/es6/testkit/src/main/scala/app/softnetwork/elastic/scalatest/EmbeddedElasticTestKit.scala index 3fe39859..60772e96 100644 --- a/es6/testkit/src/main/scala/app/softnetwork/elastic/scalatest/EmbeddedElasticTestKit.scala +++ b/es6/testkit/src/main/scala/app/softnetwork/elastic/scalatest/EmbeddedElasticTestKit.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.scalatest import org.scalatest.Suite diff --git a/es7/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientApi.scala b/es7/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientApi.scala index 862d3d64..b4fbdca5 100644 --- a/es7/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientApi.scala +++ b/es7/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientApi.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.client.rest import akka.NotUsed diff --git a/es7/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientCompanion.scala b/es7/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientCompanion.scala index 1b490bb0..aa63476a 100644 --- a/es7/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientCompanion.scala +++ b/es7/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientCompanion.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.client.rest import app.softnetwork.elastic.client.ElasticConfig diff --git a/es7/rest/src/main/scala/app/softnetwork/elastic/persistence/query/RestHighLevelClientProvider.scala b/es7/rest/src/main/scala/app/softnetwork/elastic/persistence/query/RestHighLevelClientProvider.scala index b76c13b4..849c2ac3 100644 --- a/es7/rest/src/main/scala/app/softnetwork/elastic/persistence/query/RestHighLevelClientProvider.scala +++ b/es7/rest/src/main/scala/app/softnetwork/elastic/persistence/query/RestHighLevelClientProvider.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.persistence.query import app.softnetwork.elastic.client.rest.RestHighLevelClientApi diff --git a/es7/rest/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithRestProvider.scala b/es7/rest/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithRestProvider.scala index 3f79bb07..58526a3e 100644 --- a/es7/rest/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithRestProvider.scala +++ b/es7/rest/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithRestProvider.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.persistence.query import app.softnetwork.persistence.message.CrudEvent diff --git a/es8/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientApi.scala b/es8/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientApi.scala index f2e01392..946665c9 100644 --- a/es8/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientApi.scala +++ b/es8/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientApi.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.client.java import akka.NotUsed diff --git a/es8/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientCompanion.scala b/es8/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientCompanion.scala index ead36f44..e2ee0e35 100644 --- a/es8/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientCompanion.scala +++ b/es8/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientCompanion.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.client.java import app.softnetwork.elastic.client.ElasticConfig diff --git a/es8/java/src/main/scala/app/softnetwork/elastic/persistence/query/ElasticsearchClientProvider.scala b/es8/java/src/main/scala/app/softnetwork/elastic/persistence/query/ElasticsearchClientProvider.scala index d83d9408..b3756037 100644 --- a/es8/java/src/main/scala/app/softnetwork/elastic/persistence/query/ElasticsearchClientProvider.scala +++ b/es8/java/src/main/scala/app/softnetwork/elastic/persistence/query/ElasticsearchClientProvider.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.persistence.query import app.softnetwork.elastic.client.java.ElasticsearchClientApi diff --git a/es8/java/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithJavaProvider.scala b/es8/java/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithJavaProvider.scala index eaf53713..83d16bdf 100644 --- a/es8/java/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithJavaProvider.scala +++ b/es8/java/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithJavaProvider.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.persistence.query import app.softnetwork.persistence.message.CrudEvent diff --git a/es9/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientApi.scala b/es9/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientApi.scala index 5fbf9652..9aa02d27 100644 --- a/es9/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientApi.scala +++ b/es9/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientApi.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.client.java import akka.NotUsed diff --git a/es9/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientCompanion.scala b/es9/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientCompanion.scala index ead36f44..e2ee0e35 100644 --- a/es9/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientCompanion.scala +++ b/es9/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientCompanion.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.client.java import app.softnetwork.elastic.client.ElasticConfig diff --git a/es9/java/src/main/scala/app/softnetwork/elastic/persistence/query/ElasticsearchClientProvider.scala b/es9/java/src/main/scala/app/softnetwork/elastic/persistence/query/ElasticsearchClientProvider.scala index d83d9408..b3756037 100644 --- a/es9/java/src/main/scala/app/softnetwork/elastic/persistence/query/ElasticsearchClientProvider.scala +++ b/es9/java/src/main/scala/app/softnetwork/elastic/persistence/query/ElasticsearchClientProvider.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.persistence.query import app.softnetwork.elastic.client.java.ElasticsearchClientApi diff --git a/es9/java/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithJavaProvider.scala b/es9/java/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithJavaProvider.scala index eaf53713..83d16bdf 100644 --- a/es9/java/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithJavaProvider.scala +++ b/es9/java/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithJavaProvider.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.persistence.query import app.softnetwork.persistence.message.CrudEvent diff --git a/project/plugins.sbt b/project/plugins.sbt index ab6031fc..e19c083c 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -15,3 +15,5 @@ addDependencyTreePlugin addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.9.2") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.3.0") + +addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.10.0") diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/SQLImplicits.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/SQLImplicits.scala index 377867e9..b716915f 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/SQLImplicits.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/SQLImplicits.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql import app.softnetwork.elastic.sql.parser.Parser diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/aggregate/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/aggregate/package.scala index babe2357..c71e4c92 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/aggregate/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/aggregate/package.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.function import app.softnetwork.elastic.sql.query.{Bucket, Field, Limit, OrderBy, SQLSearchRequest} diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala index dbbe8ba6..c5481bf2 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.function import app.softnetwork.elastic.sql.{ diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/convert/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/convert/package.scala index 498c06ca..bff48017 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/convert/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/convert/package.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.function import app.softnetwork.elastic.sql.{ diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/geo/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/geo/package.scala index 1cf98f4e..f11d7fa0 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/geo/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/geo/package.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.function import app.softnetwork.elastic.sql.`type`.{SQLAny, SQLDouble, SQLTypes} diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/math/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/math/package.scala index 769386cb..93235c2c 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/math/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/math/package.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.function import app.softnetwork.elastic.sql.{ diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala index bd240bc7..290aa68c 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql import app.softnetwork.elastic.sql.`type`.{SQLType, SQLTypeUtils} diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/string/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/string/package.scala index 189c1f1c..1d3055d3 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/string/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/string/package.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.function import app.softnetwork.elastic.sql.{ diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala index 9ab41fac..408c8d04 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.function import app.softnetwork.elastic.sql.{ diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/operator/math/ArithmeticExpression.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/operator/math/ArithmeticExpression.scala index 8bd67770..68d24c46 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/operator/math/ArithmeticExpression.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/operator/math/ArithmeticExpression.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.operator.math import app.softnetwork.elastic.sql._ diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/operator/math/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/operator/math/package.scala index 4d91f947..ef59c726 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/operator/math/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/operator/math/package.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.operator import app.softnetwork.elastic.sql.Expr diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/operator/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/operator/package.scala index 1d1f2d16..f007e8e4 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/operator/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/operator/package.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql package object operator { diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/operator/time/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/operator/time/package.scala index 0683049b..9377774d 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/operator/time/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/operator/time/package.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.operator import app.softnetwork.elastic.sql.{DateMathScript, Expr, PainlessContext} diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala index f3e56b91..e143d0c9 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic import app.softnetwork.elastic.sql.function.aggregate.{MAX, MIN} diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Delimiter.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Delimiter.scala index f8ffc49f..f75645f3 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Delimiter.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Delimiter.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.parser import app.softnetwork.elastic.sql.{Expr, Token} diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/FromParser.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/FromParser.scala index 53800f8d..c1186dfc 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/FromParser.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/FromParser.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.parser import app.softnetwork.elastic.sql.query.{ diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/GroupByParser.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/GroupByParser.scala index c6d74c01..61cb1531 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/GroupByParser.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/GroupByParser.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.parser import app.softnetwork.elastic.sql.query.{Bucket, GroupBy} diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/HavingParser.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/HavingParser.scala index 59e3588e..53274adc 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/HavingParser.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/HavingParser.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.parser import app.softnetwork.elastic.sql.query.Having diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/LimitParser.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/LimitParser.scala index 2bc2a0cb..79724d97 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/LimitParser.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/LimitParser.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.parser import app.softnetwork.elastic.sql.query.{Limit, Offset} diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/OrderByParser.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/OrderByParser.scala index bbf56622..0f35d08c 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/OrderByParser.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/OrderByParser.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.parser import app.softnetwork.elastic.sql.function.Function diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Parser.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Parser.scala index 15b319c5..d6c51542 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Parser.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Parser.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.parser import app.softnetwork.elastic.sql._ diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/SelectParser.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/SelectParser.scala index 6746f1ee..76294674 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/SelectParser.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/SelectParser.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.parser import app.softnetwork.elastic.sql.query.{Except, Field, Select} diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Validator.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Validator.scala index 15e6e32d..b7ea98c0 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Validator.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Validator.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.parser import app.softnetwork.elastic.sql.`type`.{SQLType, SQLTypeUtils} diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/WhereParser.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/WhereParser.scala index 9d655c6a..be172a5a 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/WhereParser.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/WhereParser.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.parser import app.softnetwork.elastic.sql.function.geo.Meters diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/aggregate/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/aggregate/package.scala index 8da134ce..035374ad 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/aggregate/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/aggregate/package.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.parser.function import app.softnetwork.elastic.sql.Identifier diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/cond/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/cond/package.scala index 04b3a7f4..57ea821a 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/cond/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/cond/package.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.parser.function import app.softnetwork.elastic.sql.function.{FunctionWithIdentifier, TransformFunction} diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/convert/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/convert/package.scala index 6fce1809..6552f965 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/convert/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/convert/package.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.parser.function import app.softnetwork.elastic.sql.function.convert.{Cast, CastOperator, Convert, TryCast} diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/geo/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/geo/package.scala index 421dc0f6..95d79ff9 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/geo/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/geo/package.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.parser.function import app.softnetwork.elastic.sql.{GeoDistance, Identifier} diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/math/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/math/package.scala index 6893fe43..9e72e5ef 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/math/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/math/package.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.parser.function import app.softnetwork.elastic.sql.Identifier diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/string/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/string/package.scala index f401d1ac..8cb34d31 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/string/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/string/package.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.parser.function import app.softnetwork.elastic.sql.Identifier diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/time/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/time/package.scala index cd8b2882..fb9d2b37 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/time/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/time/package.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.parser.function import app.softnetwork.elastic.sql.{function, Identifier, StringValue} diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/operator/math/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/operator/math/package.scala index 991ec3ba..6646c2c9 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/operator/math/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/operator/math/package.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.parser.operator import app.softnetwork.elastic.sql.function.{Function, FunctionWithIdentifier} diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/time/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/time/package.scala index b3ec69f8..9909387c 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/time/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/time/package.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.parser import app.softnetwork.elastic.sql.Identifier diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/type/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/type/package.scala index f3195889..1915fcf7 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/type/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/type/package.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.parser import app.softnetwork.elastic.sql.{ diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/From.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/From.scala index d95c8b06..081df121 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/From.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/From.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.query import app.softnetwork.elastic.sql.{ diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/GroupBy.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/GroupBy.scala index 1de0a935..0b4f0474 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/GroupBy.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/GroupBy.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.query import app.softnetwork.elastic.sql.`type`.SQLTypes diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/Having.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/Having.scala index 929da2fd..cec14241 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/Having.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/Having.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.query import app.softnetwork.elastic.sql.{Expr, TokenRegex, Updateable} diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/Limit.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/Limit.scala index 3cb1f3af..c88a126a 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/Limit.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/Limit.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.query import app.softnetwork.elastic.sql.{Expr, TokenRegex} diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/OrderBy.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/OrderBy.scala index c0ba2906..36543967 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/OrderBy.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/OrderBy.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.query import app.softnetwork.elastic.sql.function.{Function, FunctionChain} diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/SQLMultiSearchRequest.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/SQLMultiSearchRequest.scala index eda9f18d..0a6006e6 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/SQLMultiSearchRequest.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/SQLMultiSearchRequest.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.query import app.softnetwork.elastic.sql.Token diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/SQLQuery.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/SQLQuery.scala index 7fd44b24..913211d1 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/SQLQuery.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/SQLQuery.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.query case class SQLQuery(query: String, score: Option[Double] = None) { diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/SQLSearchRequest.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/SQLSearchRequest.scala index 0dc21cfc..29420bd9 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/SQLSearchRequest.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/SQLSearchRequest.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.query import app.softnetwork.elastic.sql.function.aggregate.TopHitsAggregation diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/Select.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/Select.scala index 53efcf28..60d3000b 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/Select.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/Select.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.query import app.softnetwork.elastic.sql.function.aggregate.TopHitsAggregation diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/Where.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/Where.scala index b2982c3f..08dd7f21 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/Where.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/Where.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.query import app.softnetwork.elastic.sql.`type`.{SQLAny, SQLType, SQLTypeUtils, SQLTypes} diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/time/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/time/package.scala index 7cb263c4..b169b022 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/time/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/time/package.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql import app.softnetwork.elastic.sql.`type`._ diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLType.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLType.scala index b5b28491..8f691f99 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLType.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLType.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.`type` sealed trait SQLType { diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypeUtils.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypeUtils.scala index 7fe9b048..199f5247 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypeUtils.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypeUtils.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.`type` import app.softnetwork.elastic.sql.{LiteralParam, PainlessContext, PainlessScript} diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypes.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypes.scala index 0959ba29..4a3420fe 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypes.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypes.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2015 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package app.softnetwork.elastic.sql.`type` object SQLTypes { From 2d7909f0c9e769bf508127cce6c97095ddaa694c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Manciot?= Date: Fri, 17 Oct 2025 10:24:22 +0200 Subject: [PATCH 10/21] update license header --- build.sbt | 2 +- .../app/softnetwork/elastic/client/ElasticConfig.scala | 2 +- .../scala/app/softnetwork/elastic/client/AggregateResult.scala | 2 +- .../scala/app/softnetwork/elastic/client/ElasticClientApi.scala | 2 +- .../app/softnetwork/elastic/client/MappingComparator.scala | 2 +- .../src/main/scala/app/softnetwork/elastic/client/package.scala | 2 +- .../softnetwork/elastic/persistence/query/ElasticProvider.scala | 2 +- .../persistence/query/State2ElasticProcessorStream.scala | 2 +- .../app/softnetwork/elastic/persistence/typed/Elastic.scala | 2 +- .../app/softnetwork/elastic/client/jest/JestClientApi.scala | 2 +- .../softnetwork/elastic/client/jest/JestClientCompanion.scala | 2 +- .../elastic/client/jest/JestClientResultHandler.scala | 2 +- .../softnetwork/elastic/persistence/query/JestProvider.scala | 2 +- .../query/State2ElasticProcessorStreamWithJestProvider.scala | 2 +- .../elastic/client/rest/RestHighLevelClientApi.scala | 2 +- .../elastic/client/rest/RestHighLevelClientCompanion.scala | 2 +- .../elastic/persistence/query/RestHighLevelClientProvider.scala | 2 +- .../query/State2ElasticProcessorStreamWithRestProvider.scala | 2 +- .../app/softnetwork/elastic/sql/bridge/ElasticAggregation.scala | 2 +- .../app/softnetwork/elastic/sql/bridge/ElasticCriteria.scala | 2 +- .../elastic/sql/bridge/ElasticMultiSearchRequest.scala | 2 +- .../scala/app/softnetwork/elastic/sql/bridge/ElasticQuery.scala | 2 +- .../softnetwork/elastic/sql/bridge/ElasticSearchRequest.scala | 2 +- .../main/scala/app/softnetwork/elastic/sql/bridge/package.scala | 2 +- .../softnetwork/elastic/scalatest/EmbeddedElasticTestKit.scala | 2 +- .../elastic/client/rest/RestHighLevelClientApi.scala | 2 +- .../elastic/client/rest/RestHighLevelClientCompanion.scala | 2 +- .../elastic/persistence/query/RestHighLevelClientProvider.scala | 2 +- .../query/State2ElasticProcessorStreamWithRestProvider.scala | 2 +- .../elastic/client/java/ElasticsearchClientApi.scala | 2 +- .../elastic/client/java/ElasticsearchClientCompanion.scala | 2 +- .../elastic/persistence/query/ElasticsearchClientProvider.scala | 2 +- .../query/State2ElasticProcessorStreamWithJavaProvider.scala | 2 +- .../elastic/client/java/ElasticsearchClientApi.scala | 2 +- .../elastic/client/java/ElasticsearchClientCompanion.scala | 2 +- .../elastic/persistence/query/ElasticsearchClientProvider.scala | 2 +- .../query/State2ElasticProcessorStreamWithJavaProvider.scala | 2 +- .../main/scala/app/softnetwork/elastic/sql/SQLImplicits.scala | 2 +- .../softnetwork/elastic/sql/function/aggregate/package.scala | 2 +- .../app/softnetwork/elastic/sql/function/cond/package.scala | 2 +- .../app/softnetwork/elastic/sql/function/convert/package.scala | 2 +- .../app/softnetwork/elastic/sql/function/geo/package.scala | 2 +- .../app/softnetwork/elastic/sql/function/math/package.scala | 2 +- .../scala/app/softnetwork/elastic/sql/function/package.scala | 2 +- .../app/softnetwork/elastic/sql/function/string/package.scala | 2 +- .../app/softnetwork/elastic/sql/function/time/package.scala | 2 +- .../elastic/sql/operator/math/ArithmeticExpression.scala | 2 +- .../app/softnetwork/elastic/sql/operator/math/package.scala | 2 +- .../scala/app/softnetwork/elastic/sql/operator/package.scala | 2 +- .../app/softnetwork/elastic/sql/operator/time/package.scala | 2 +- sql/src/main/scala/app/softnetwork/elastic/sql/package.scala | 2 +- .../scala/app/softnetwork/elastic/sql/parser/Delimiter.scala | 2 +- .../scala/app/softnetwork/elastic/sql/parser/FromParser.scala | 2 +- .../app/softnetwork/elastic/sql/parser/GroupByParser.scala | 2 +- .../scala/app/softnetwork/elastic/sql/parser/HavingParser.scala | 2 +- .../scala/app/softnetwork/elastic/sql/parser/LimitParser.scala | 2 +- .../app/softnetwork/elastic/sql/parser/OrderByParser.scala | 2 +- .../main/scala/app/softnetwork/elastic/sql/parser/Parser.scala | 2 +- .../scala/app/softnetwork/elastic/sql/parser/SelectParser.scala | 2 +- .../scala/app/softnetwork/elastic/sql/parser/Validator.scala | 2 +- .../scala/app/softnetwork/elastic/sql/parser/WhereParser.scala | 2 +- .../elastic/sql/parser/function/aggregate/package.scala | 2 +- .../softnetwork/elastic/sql/parser/function/cond/package.scala | 2 +- .../elastic/sql/parser/function/convert/package.scala | 2 +- .../softnetwork/elastic/sql/parser/function/geo/package.scala | 2 +- .../softnetwork/elastic/sql/parser/function/math/package.scala | 2 +- .../elastic/sql/parser/function/string/package.scala | 2 +- .../softnetwork/elastic/sql/parser/function/time/package.scala | 2 +- .../softnetwork/elastic/sql/parser/operator/math/package.scala | 2 +- .../scala/app/softnetwork/elastic/sql/parser/time/package.scala | 2 +- .../scala/app/softnetwork/elastic/sql/parser/type/package.scala | 2 +- sql/src/main/scala/app/softnetwork/elastic/sql/query/From.scala | 2 +- .../main/scala/app/softnetwork/elastic/sql/query/GroupBy.scala | 2 +- .../main/scala/app/softnetwork/elastic/sql/query/Having.scala | 2 +- .../main/scala/app/softnetwork/elastic/sql/query/Limit.scala | 2 +- .../main/scala/app/softnetwork/elastic/sql/query/OrderBy.scala | 2 +- .../softnetwork/elastic/sql/query/SQLMultiSearchRequest.scala | 2 +- .../main/scala/app/softnetwork/elastic/sql/query/SQLQuery.scala | 2 +- .../app/softnetwork/elastic/sql/query/SQLSearchRequest.scala | 2 +- .../main/scala/app/softnetwork/elastic/sql/query/Select.scala | 2 +- .../main/scala/app/softnetwork/elastic/sql/query/Where.scala | 2 +- .../main/scala/app/softnetwork/elastic/sql/time/package.scala | 2 +- .../main/scala/app/softnetwork/elastic/sql/type/SQLType.scala | 2 +- .../scala/app/softnetwork/elastic/sql/type/SQLTypeUtils.scala | 2 +- .../main/scala/app/softnetwork/elastic/sql/type/SQLTypes.scala | 2 +- 85 files changed, 85 insertions(+), 85 deletions(-) diff --git a/build.sbt b/build.sbt index d2818533..3712b2b9 100644 --- a/build.sbt +++ b/build.sbt @@ -24,7 +24,7 @@ ThisBuild / version := "0.9.3" ThisBuild / scalaVersion := scala213 ThisBuild / organizationName := "SOFTNETWORK" -ThisBuild / startYear := Some(2015) +ThisBuild / startYear := Some(2025) ThisBuild / licenses += ("Apache-2.0", url("https://www.apache.org/licenses/LICENSE-2.0.txt")) ThisBuild / dependencyOverrides ++= Seq( diff --git a/core/src/main/scala-2.13/app/softnetwork/elastic/client/ElasticConfig.scala b/core/src/main/scala-2.13/app/softnetwork/elastic/client/ElasticConfig.scala index 9fb89f78..93c325d9 100644 --- a/core/src/main/scala-2.13/app/softnetwork/elastic/client/ElasticConfig.scala +++ b/core/src/main/scala-2.13/app/softnetwork/elastic/client/ElasticConfig.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core/src/main/scala/app/softnetwork/elastic/client/AggregateResult.scala b/core/src/main/scala/app/softnetwork/elastic/client/AggregateResult.scala index 5cfb3d46..4b6468cf 100644 --- a/core/src/main/scala/app/softnetwork/elastic/client/AggregateResult.scala +++ b/core/src/main/scala/app/softnetwork/elastic/client/AggregateResult.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core/src/main/scala/app/softnetwork/elastic/client/ElasticClientApi.scala b/core/src/main/scala/app/softnetwork/elastic/client/ElasticClientApi.scala index d6f5293c..dc8206cf 100644 --- a/core/src/main/scala/app/softnetwork/elastic/client/ElasticClientApi.scala +++ b/core/src/main/scala/app/softnetwork/elastic/client/ElasticClientApi.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core/src/main/scala/app/softnetwork/elastic/client/MappingComparator.scala b/core/src/main/scala/app/softnetwork/elastic/client/MappingComparator.scala index 197e5074..f9b9d1b3 100644 --- a/core/src/main/scala/app/softnetwork/elastic/client/MappingComparator.scala +++ b/core/src/main/scala/app/softnetwork/elastic/client/MappingComparator.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core/src/main/scala/app/softnetwork/elastic/client/package.scala b/core/src/main/scala/app/softnetwork/elastic/client/package.scala index c9227556..c6174aa7 100644 --- a/core/src/main/scala/app/softnetwork/elastic/client/package.scala +++ b/core/src/main/scala/app/softnetwork/elastic/client/package.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core/src/main/scala/app/softnetwork/elastic/persistence/query/ElasticProvider.scala b/core/src/main/scala/app/softnetwork/elastic/persistence/query/ElasticProvider.scala index c7486a3f..cdeed9e8 100644 --- a/core/src/main/scala/app/softnetwork/elastic/persistence/query/ElasticProvider.scala +++ b/core/src/main/scala/app/softnetwork/elastic/persistence/query/ElasticProvider.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStream.scala b/core/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStream.scala index e74c6b22..55df2cea 100644 --- a/core/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStream.scala +++ b/core/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStream.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core/src/main/scala/app/softnetwork/elastic/persistence/typed/Elastic.scala b/core/src/main/scala/app/softnetwork/elastic/persistence/typed/Elastic.scala index 7d9c2775..8f4cdc67 100644 --- a/core/src/main/scala/app/softnetwork/elastic/persistence/typed/Elastic.scala +++ b/core/src/main/scala/app/softnetwork/elastic/persistence/typed/Elastic.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClientApi.scala b/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClientApi.scala index 322aa087..f913170c 100644 --- a/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClientApi.scala +++ b/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClientApi.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClientCompanion.scala b/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClientCompanion.scala index 046ba3eb..497d1afe 100644 --- a/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClientCompanion.scala +++ b/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClientCompanion.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClientResultHandler.scala b/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClientResultHandler.scala index 3d6f4746..6c2e22ae 100644 --- a/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClientResultHandler.scala +++ b/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClientResultHandler.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/es6/jest/src/main/scala/app/softnetwork/elastic/persistence/query/JestProvider.scala b/es6/jest/src/main/scala/app/softnetwork/elastic/persistence/query/JestProvider.scala index e30610c8..c806e401 100644 --- a/es6/jest/src/main/scala/app/softnetwork/elastic/persistence/query/JestProvider.scala +++ b/es6/jest/src/main/scala/app/softnetwork/elastic/persistence/query/JestProvider.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/es6/jest/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithJestProvider.scala b/es6/jest/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithJestProvider.scala index 0a2971dc..5501a386 100644 --- a/es6/jest/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithJestProvider.scala +++ b/es6/jest/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithJestProvider.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/es6/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientApi.scala b/es6/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientApi.scala index 9b529b41..4e56975c 100644 --- a/es6/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientApi.scala +++ b/es6/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientApi.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/es6/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientCompanion.scala b/es6/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientCompanion.scala index 61732246..cb3098d7 100644 --- a/es6/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientCompanion.scala +++ b/es6/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientCompanion.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/es6/rest/src/main/scala/app/softnetwork/elastic/persistence/query/RestHighLevelClientProvider.scala b/es6/rest/src/main/scala/app/softnetwork/elastic/persistence/query/RestHighLevelClientProvider.scala index 849c2ac3..7a93fcf9 100644 --- a/es6/rest/src/main/scala/app/softnetwork/elastic/persistence/query/RestHighLevelClientProvider.scala +++ b/es6/rest/src/main/scala/app/softnetwork/elastic/persistence/query/RestHighLevelClientProvider.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/es6/rest/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithRestProvider.scala b/es6/rest/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithRestProvider.scala index 58526a3e..2e6eee32 100644 --- a/es6/rest/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithRestProvider.scala +++ b/es6/rest/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithRestProvider.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticAggregation.scala b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticAggregation.scala index 4e9aa3d3..2e427256 100644 --- a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticAggregation.scala +++ b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticAggregation.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticCriteria.scala b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticCriteria.scala index 568f8305..caa12dd0 100644 --- a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticCriteria.scala +++ b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticCriteria.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticMultiSearchRequest.scala b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticMultiSearchRequest.scala index 7380d050..0a904a6d 100644 --- a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticMultiSearchRequest.scala +++ b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticMultiSearchRequest.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticQuery.scala b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticQuery.scala index 3d59c048..2b3c9d6b 100644 --- a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticQuery.scala +++ b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticQuery.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticSearchRequest.scala b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticSearchRequest.scala index 1801701c..3afcdc6e 100644 --- a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticSearchRequest.scala +++ b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticSearchRequest.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala index 072e5724..c13ee796 100644 --- a/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala +++ b/es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/es6/testkit/src/main/scala/app/softnetwork/elastic/scalatest/EmbeddedElasticTestKit.scala b/es6/testkit/src/main/scala/app/softnetwork/elastic/scalatest/EmbeddedElasticTestKit.scala index 60772e96..8f84a3cc 100644 --- a/es6/testkit/src/main/scala/app/softnetwork/elastic/scalatest/EmbeddedElasticTestKit.scala +++ b/es6/testkit/src/main/scala/app/softnetwork/elastic/scalatest/EmbeddedElasticTestKit.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/es7/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientApi.scala b/es7/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientApi.scala index b4fbdca5..76b317dc 100644 --- a/es7/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientApi.scala +++ b/es7/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientApi.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/es7/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientCompanion.scala b/es7/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientCompanion.scala index aa63476a..a2bff72e 100644 --- a/es7/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientCompanion.scala +++ b/es7/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientCompanion.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/es7/rest/src/main/scala/app/softnetwork/elastic/persistence/query/RestHighLevelClientProvider.scala b/es7/rest/src/main/scala/app/softnetwork/elastic/persistence/query/RestHighLevelClientProvider.scala index 849c2ac3..7a93fcf9 100644 --- a/es7/rest/src/main/scala/app/softnetwork/elastic/persistence/query/RestHighLevelClientProvider.scala +++ b/es7/rest/src/main/scala/app/softnetwork/elastic/persistence/query/RestHighLevelClientProvider.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/es7/rest/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithRestProvider.scala b/es7/rest/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithRestProvider.scala index 58526a3e..2e6eee32 100644 --- a/es7/rest/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithRestProvider.scala +++ b/es7/rest/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithRestProvider.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/es8/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientApi.scala b/es8/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientApi.scala index 946665c9..c13f2ee7 100644 --- a/es8/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientApi.scala +++ b/es8/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientApi.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/es8/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientCompanion.scala b/es8/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientCompanion.scala index e2ee0e35..0fcd0123 100644 --- a/es8/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientCompanion.scala +++ b/es8/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientCompanion.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/es8/java/src/main/scala/app/softnetwork/elastic/persistence/query/ElasticsearchClientProvider.scala b/es8/java/src/main/scala/app/softnetwork/elastic/persistence/query/ElasticsearchClientProvider.scala index b3756037..33b2bd05 100644 --- a/es8/java/src/main/scala/app/softnetwork/elastic/persistence/query/ElasticsearchClientProvider.scala +++ b/es8/java/src/main/scala/app/softnetwork/elastic/persistence/query/ElasticsearchClientProvider.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/es8/java/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithJavaProvider.scala b/es8/java/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithJavaProvider.scala index 83d16bdf..2e2add10 100644 --- a/es8/java/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithJavaProvider.scala +++ b/es8/java/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithJavaProvider.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/es9/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientApi.scala b/es9/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientApi.scala index 9aa02d27..c0ad301c 100644 --- a/es9/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientApi.scala +++ b/es9/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientApi.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/es9/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientCompanion.scala b/es9/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientCompanion.scala index e2ee0e35..0fcd0123 100644 --- a/es9/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientCompanion.scala +++ b/es9/java/src/main/scala/app/softnetwork/elastic/client/java/ElasticsearchClientCompanion.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/es9/java/src/main/scala/app/softnetwork/elastic/persistence/query/ElasticsearchClientProvider.scala b/es9/java/src/main/scala/app/softnetwork/elastic/persistence/query/ElasticsearchClientProvider.scala index b3756037..33b2bd05 100644 --- a/es9/java/src/main/scala/app/softnetwork/elastic/persistence/query/ElasticsearchClientProvider.scala +++ b/es9/java/src/main/scala/app/softnetwork/elastic/persistence/query/ElasticsearchClientProvider.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/es9/java/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithJavaProvider.scala b/es9/java/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithJavaProvider.scala index 83d16bdf..2e2add10 100644 --- a/es9/java/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithJavaProvider.scala +++ b/es9/java/src/main/scala/app/softnetwork/elastic/persistence/query/State2ElasticProcessorStreamWithJavaProvider.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/SQLImplicits.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/SQLImplicits.scala index b716915f..924270c2 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/SQLImplicits.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/SQLImplicits.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/aggregate/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/aggregate/package.scala index c71e4c92..3dbd08b5 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/aggregate/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/aggregate/package.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala index c5481bf2..c909228a 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/convert/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/convert/package.scala index bff48017..c909891c 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/convert/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/convert/package.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/geo/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/geo/package.scala index f11d7fa0..4f2d46fb 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/geo/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/geo/package.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/math/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/math/package.scala index 93235c2c..79752d8a 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/math/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/math/package.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala index 290aa68c..3902bcfd 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/string/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/string/package.scala index 1d3055d3..32590adc 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/string/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/string/package.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala index 408c8d04..1bee27ed 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/operator/math/ArithmeticExpression.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/operator/math/ArithmeticExpression.scala index 68d24c46..f2670e80 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/operator/math/ArithmeticExpression.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/operator/math/ArithmeticExpression.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/operator/math/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/operator/math/package.scala index ef59c726..adb3b05b 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/operator/math/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/operator/math/package.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/operator/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/operator/package.scala index f007e8e4..b698a751 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/operator/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/operator/package.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/operator/time/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/operator/time/package.scala index 9377774d..e96bcaee 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/operator/time/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/operator/time/package.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala index e143d0c9..f21c6310 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Delimiter.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Delimiter.scala index f75645f3..fe9625c7 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Delimiter.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Delimiter.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/FromParser.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/FromParser.scala index c1186dfc..b18ea945 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/FromParser.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/FromParser.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/GroupByParser.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/GroupByParser.scala index 61cb1531..4b7670b4 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/GroupByParser.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/GroupByParser.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/HavingParser.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/HavingParser.scala index 53274adc..7187345d 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/HavingParser.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/HavingParser.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/LimitParser.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/LimitParser.scala index 79724d97..fbbe37d4 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/LimitParser.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/LimitParser.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/OrderByParser.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/OrderByParser.scala index 0f35d08c..884f0616 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/OrderByParser.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/OrderByParser.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Parser.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Parser.scala index d6c51542..7f8f6ac2 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Parser.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Parser.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/SelectParser.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/SelectParser.scala index 76294674..752fcaa8 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/SelectParser.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/SelectParser.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Validator.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Validator.scala index b7ea98c0..1ea0e14c 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Validator.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Validator.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/WhereParser.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/WhereParser.scala index be172a5a..b5d880e3 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/WhereParser.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/WhereParser.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/aggregate/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/aggregate/package.scala index 035374ad..8a962efd 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/aggregate/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/aggregate/package.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/cond/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/cond/package.scala index 57ea821a..e96bbb6f 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/cond/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/cond/package.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/convert/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/convert/package.scala index 6552f965..f1845297 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/convert/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/convert/package.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/geo/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/geo/package.scala index 95d79ff9..752f8db2 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/geo/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/geo/package.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/math/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/math/package.scala index 9e72e5ef..9c3038e4 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/math/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/math/package.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/string/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/string/package.scala index 8cb34d31..d924e914 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/string/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/string/package.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/time/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/time/package.scala index fb9d2b37..adbc9fd3 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/time/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/time/package.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/operator/math/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/operator/math/package.scala index 6646c2c9..4e5e7b81 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/operator/math/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/operator/math/package.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/time/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/time/package.scala index 9909387c..ff3983ff 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/time/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/time/package.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/type/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/type/package.scala index 1915fcf7..228673ea 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/type/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/type/package.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/From.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/From.scala index 081df121..eb0db185 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/From.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/From.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/GroupBy.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/GroupBy.scala index 0b4f0474..e619614e 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/GroupBy.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/GroupBy.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/Having.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/Having.scala index cec14241..07ad6625 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/Having.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/Having.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/Limit.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/Limit.scala index c88a126a..412d525a 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/Limit.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/Limit.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/OrderBy.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/OrderBy.scala index 36543967..68d6ae60 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/OrderBy.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/OrderBy.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/SQLMultiSearchRequest.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/SQLMultiSearchRequest.scala index 0a6006e6..f464f914 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/SQLMultiSearchRequest.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/SQLMultiSearchRequest.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/SQLQuery.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/SQLQuery.scala index 913211d1..96132b08 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/SQLQuery.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/SQLQuery.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/SQLSearchRequest.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/SQLSearchRequest.scala index 29420bd9..4dda85f8 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/SQLSearchRequest.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/SQLSearchRequest.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/Select.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/Select.scala index 60d3000b..57d9bf9b 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/Select.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/Select.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/Where.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/Where.scala index 08dd7f21..fbddc6e5 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/Where.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/Where.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/time/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/time/package.scala index b169b022..ec373753 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/time/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/time/package.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLType.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLType.scala index 8f691f99..a61d4f57 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLType.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLType.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypeUtils.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypeUtils.scala index 199f5247..0e017afe 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypeUtils.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypeUtils.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypes.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypes.scala index 4a3420fe..52cca678 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypes.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypes.scala @@ -1,5 +1,5 @@ /* - * Copyright 2015 SOFTNETWORK + * Copyright 2025 SOFTNETWORK * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From f883fcc041ec6026333373de6bc4e9ea4bd65fa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Manciot?= Date: Fri, 17 Oct 2025 15:06:40 +0200 Subject: [PATCH 11/21] update painless to truncate dates and call methods on a painless parameter --- .../elastic/sql/SQLQuerySpec.scala | 116 +++++++++--------- .../elastic/sql/SQLQuerySpec.scala | 66 +++++----- .../elastic/sql/function/package.scala | 14 ++- .../elastic/sql/function/time/package.scala | 34 ++++- .../app/softnetwork/elastic/sql/package.scala | 55 +++++---- 5 files changed, 168 insertions(+), 117 deletions(-) diff --git a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index dbd167a9..493b1567 100644 --- a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -1235,38 +1235,38 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll(",LocalDate", ", LocalDate") } - it should "handle date_format function" in { + it should "handle date_format function" in { // FIXME painless generated does not take into account date_trunc val select: ElasticSearchRequest = SQLQuery(dateFormat) val query = select.query println(query) query shouldBe """{ - | "query": { - | "bool": { - | "filter": [ - | { - | "exists": { - | "field": "identifier2" - | } - | } - | ] - | } - | }, - | "script_fields": { - | "lastSeen": { - | "script": { - | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" - | } - | } - | }, - | "_source": { - | "includes": [ - | "identifier" - | ] - | } - |}""".stripMargin + | "query": { + | "bool": { + | "filter": [ + | { + | "exists": { + | "field": "identifier2" + | } + | } + | ] + | } + | }, + | "script_fields": { + | "lastSeen": { + | "script": { + | "lang": "painless", + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | } + | } + | }, + | "_source": { + | "includes": [ + | "identifier" + | ] + | } + |}""".stripMargin .replaceAll("\\s", "") .replaceAll("defp", "def p") .replaceAll("defe", "def e") @@ -1377,7 +1377,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "lastSeen": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss XXX'); (param1 == null) ? null : param2.format(param1)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss XXX'); (param1 == null) ? null : param2.format(param1)" | } | } | }, @@ -2156,91 +2156,91 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "dom": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.DAY_OF_MONTH)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.DAY_OF_MONTH)); param1" | } | }, | "dow": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.DAY_OF_WEEK)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.DAY_OF_WEEK)); param1" | } | }, | "doy": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.DAY_OF_YEAR)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.DAY_OF_YEAR)); param1" | } | }, | "m": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MONTH_OF_YEAR)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.MONTH_OF_YEAR)); param1" | } | }, | "y": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.YEAR)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.YEAR)); param1" | } | }, | "h": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.HOUR_OF_DAY)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.HOUR_OF_DAY)); param1" | } | }, | "minutes": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MINUTE_OF_HOUR)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.MINUTE_OF_HOUR)); param1" | } | }, | "s": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.SECOND_OF_MINUTE)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.SECOND_OF_MINUTE)); param1" | } | }, | "nano": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.NANO_OF_SECOND)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.NANO_OF_SECOND)); param1" | } | }, | "micro": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MICRO_OF_SECOND)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.MICRO_OF_SECOND)); param1" | } | }, | "milli": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MILLI_OF_SECOND)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.MILLI_OF_SECOND)); param1" | } | }, | "epoch": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.EPOCH_DAY)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.EPOCH_DAY)); param1" | } | }, | "off": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.OFFSET_SECONDS)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.OFFSET_SECONDS)); param1" | } | }, | "w": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(java.time.temporal.IsoFields.WEEK_OF_WEEK_BASED_YEAR)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(java.time.temporal.IsoFields.WEEK_OF_WEEK_BASED_YEAR)); param1" | } | }, | "q": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(java.time.temporal.IsoFields.QUARTER_OF_YEAR)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(java.time.temporal.IsoFields.QUARTER_OF_YEAR)); param1" | } | } | }, @@ -2280,7 +2280,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "script": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); def param2 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); (param1 == null) ? null : (param1 * (param2.get(ChronoField.YEAR) - 10)) > 10000" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); def param2 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().get(ChronoField.YEAR); (param1 == null) ? null : (param1 * (param2 - 10)) > 10000" | } | } | } @@ -2886,91 +2886,91 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "y": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.YEAR)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.YEAR)); param1" | } | }, | "m": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MONTH_OF_YEAR)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.MONTH_OF_YEAR)); param1" | } | }, | "wd": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.DAY_OF_WEEK)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.DAY_OF_WEEK)); param1" | } | }, | "yd": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.DAY_OF_YEAR)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.DAY_OF_YEAR)); param1" | } | }, | "d": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.DAY_OF_MONTH)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.DAY_OF_MONTH)); param1" | } | }, | "h": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.HOUR_OF_DAY)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.HOUR_OF_DAY)); param1" | } | }, | "minutes": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MINUTE_OF_HOUR)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.MINUTE_OF_HOUR)); param1" | } | }, | "s": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.SECOND_OF_MINUTE)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.SECOND_OF_MINUTE)); param1" | } | }, | "nano": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.NANO_OF_SECOND)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.NANO_OF_SECOND)); param1" | } | }, | "micro": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MICRO_OF_SECOND)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.MICRO_OF_SECOND)); param1" | } | }, | "milli": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MILLI_OF_SECOND)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.MILLI_OF_SECOND)); param1" | } | }, | "epoch": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.EPOCH_DAY)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.EPOCH_DAY)); param1" | } | }, | "off": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.OFFSET_SECONDS)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.OFFSET_SECONDS)); param1" | } | }, | "w": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(java.time.temporal.IsoFields.WEEK_OF_WEEK_BASED_YEAR)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(java.time.temporal.IsoFields.WEEK_OF_WEEK_BASED_YEAR)); param1" | } | }, | "q": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(java.time.temporal.IsoFields.QUARTER_OF_YEAR)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(java.time.temporal.IsoFields.QUARTER_OF_YEAR)); param1" | } | } | }, diff --git a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index 9846098f..f25cb6cd 100644 --- a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -1261,7 +1261,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "lastSeen": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" | } | } | }, @@ -1381,7 +1381,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "lastSeen": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss XXX'); (param1 == null) ? null : param2.format(param1)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss XXX'); (param1 == null) ? null : param2.format(param1)" | } | } | }, @@ -2160,91 +2160,91 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "dom": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.DAY_OF_MONTH)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.DAY_OF_MONTH)); param1" | } | }, | "dow": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.DAY_OF_WEEK)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.DAY_OF_WEEK)); param1" | } | }, | "doy": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.DAY_OF_YEAR)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.DAY_OF_YEAR)); param1" | } | }, | "m": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MONTH_OF_YEAR)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.MONTH_OF_YEAR)); param1" | } | }, | "y": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.YEAR)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.YEAR)); param1" | } | }, | "h": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.HOUR_OF_DAY)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.HOUR_OF_DAY)); param1" | } | }, | "minutes": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MINUTE_OF_HOUR)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.MINUTE_OF_HOUR)); param1" | } | }, | "s": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.SECOND_OF_MINUTE)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.SECOND_OF_MINUTE)); param1" | } | }, | "nano": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.NANO_OF_SECOND)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.NANO_OF_SECOND)); param1" | } | }, | "micro": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MICRO_OF_SECOND)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.MICRO_OF_SECOND)); param1" | } | }, | "milli": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MILLI_OF_SECOND)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.MILLI_OF_SECOND)); param1" | } | }, | "epoch": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.EPOCH_DAY)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.EPOCH_DAY)); param1" | } | }, | "off": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.OFFSET_SECONDS)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.OFFSET_SECONDS)); param1" | } | }, | "w": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(java.time.temporal.IsoFields.WEEK_OF_WEEK_BASED_YEAR)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(java.time.temporal.IsoFields.WEEK_OF_WEEK_BASED_YEAR)); param1" | } | }, | "q": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(java.time.temporal.IsoFields.QUARTER_OF_YEAR)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(java.time.temporal.IsoFields.QUARTER_OF_YEAR)); param1" | } | } | }, @@ -2284,7 +2284,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "script": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); def param2 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); (param1 == null) ? null : (param1 * (param2.get(ChronoField.YEAR) - 10)) > 10000" + | "source": "def param1 = (!doc.containsKey('identifier') || doc['identifier'].empty ? null : doc['identifier'].value); def param2 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().get(ChronoField.YEAR); (param1 == null) ? null : (param1 * (param2 - 10)) > 10000" | } | } | } @@ -2890,91 +2890,91 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "y": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.YEAR)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.YEAR)); param1" | } | }, | "m": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MONTH_OF_YEAR)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.MONTH_OF_YEAR)); param1" | } | }, | "wd": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.DAY_OF_WEEK)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.DAY_OF_WEEK)); param1" | } | }, | "yd": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.DAY_OF_YEAR)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.DAY_OF_YEAR)); param1" | } | }, | "d": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.DAY_OF_MONTH)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.DAY_OF_MONTH)); param1" | } | }, | "h": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.HOUR_OF_DAY)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.HOUR_OF_DAY)); param1" | } | }, | "minutes": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MINUTE_OF_HOUR)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.MINUTE_OF_HOUR)); param1" | } | }, | "s": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.SECOND_OF_MINUTE)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.SECOND_OF_MINUTE)); param1" | } | }, | "nano": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.NANO_OF_SECOND)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.NANO_OF_SECOND)); param1" | } | }, | "micro": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MICRO_OF_SECOND)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.MICRO_OF_SECOND)); param1" | } | }, | "milli": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.MILLI_OF_SECOND)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.MILLI_OF_SECOND)); param1" | } | }, | "epoch": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.EPOCH_DAY)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.EPOCH_DAY)); param1" | } | }, | "off": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(ChronoField.OFFSET_SECONDS)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.OFFSET_SECONDS)); param1" | } | }, | "w": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(java.time.temporal.IsoFields.WEEK_OF_WEEK_BASED_YEAR)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(java.time.temporal.IsoFields.WEEK_OF_WEEK_BASED_YEAR)); param1" | } | }, | "q": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1.get(java.time.temporal.IsoFields.QUARTER_OF_YEAR)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(java.time.temporal.IsoFields.QUARTER_OF_YEAR)); param1" | } | } | }, diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala index 3902bcfd..03fdb722 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala @@ -238,11 +238,17 @@ package object function { def toPainless(base: String, idx: Int, context: Option[PainlessContext]): String = { context match { - case Some(_) => + case Some(ctx) => val p = painless(context) - if (p.startsWith(".")) // method call - s"$base$p" - else + if (p.startsWith(".") && base.nonEmpty) { // method call + ctx.find(base) match { + case Some(param) => + param.addPainlessMethod(p) + base + case _ => + s"$base$p" + } + } else p case None => if (checkIfNullable && base.nonEmpty) diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala index 1bee27ed..f15bccc5 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala @@ -210,7 +210,7 @@ package object time { extends DateTimeFunction with TransformFunction[SQLTemporal, SQLTemporal] with FunctionWithIdentifier - with DateMathRounding { + with DateMathRounding { // FIXME check Unit compatibility with inputType override def fun: Option[PainlessScript] = Some(DateTrunc) override def args: List[PainlessScript] = List(unit) @@ -226,6 +226,38 @@ package object time { override def roundingScript: Option[String] = unit.roundingScript override def dateMathScript: Boolean = identifier.dateMathScript + + override def toPainlessCall( + callArgs: List[String], + context: Option[PainlessContext] + ): String = { + unit match { + case TimeUnit.YEARS => ".withDayOfYear(1).truncatedTo(ChronoUnit.DAYS)" + case TimeUnit.MONTHS => ".withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS)" + case TimeUnit.WEEKS => ".with(DayOfWeek.SUNDAY).truncatedTo(ChronoUnit.DAYS)" + case TimeUnit.QUARTERS => + context match { + case Some(ctx) => + ctx.addParam(identifier) match { + case Some(p) => + val quarterExpr = + s".withMonth(((($p.getMonthValue() - 1) / 3) * 3) + 1).withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS) : null" + ctx.addParam( + LiteralParam( + quarterExpr + ) + ) match { + case Some(p) => return p + case _ => + } + case _ => + } + case _ => + } + super.toPainlessCall(callArgs, context) + case _ => super.toPainlessCall(callArgs, context) + } + } } case object Extract extends Expr("EXTRACT") with TokenRegex with PainlessScript { diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala index f21c6310..1ccb44ce 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala @@ -97,11 +97,22 @@ package object sql { } } - def pvalue: String = + def paramValue: String = if (nullable && checkNotNull.nonEmpty) checkNotNull else - param + s"$param${painlessMethods.mkString("")}" + + private[this] var _painlessMethods: collection.mutable.Seq[String] = + collection.mutable.Seq.empty + + def addPainlessMethod(method: String): PainlessParam = { + _painlessMethods = _painlessMethods :+ method + this + } + + def painlessMethods: Seq[String] = _painlessMethods.toSeq + } case class LiteralParam(param: String) extends PainlessParam { @@ -174,13 +185,19 @@ package object sql { def last: Option[String] = _lastParam + def find(paramName: String): Option[PainlessParam] = { + val index = _values.indexOf(paramName) + if (index >= 0) Some(_keys(index)) + else None + } + override def toString: String = { if (isEmpty) "" else _keys .flatMap { param => get(param) match { - case Some(v) => Some(s"def $v = ${param.pvalue}; ") + case Some(v) => Some(s"def $v = ${param.paramValue}; ") case None => None // should not happen } } @@ -660,19 +677,6 @@ package object sql { s"doc['$path'].value" else "" - def toPainless(base: String, context: Option[PainlessContext]): String = { - val orderedFunctions = FunctionUtils.transformFunctions(this).reverse - var expr = base - orderedFunctions.zipWithIndex.foreach { case (f, idx) => - f match { - case f: TransformFunction[_, _] => expr = f.toPainless(expr, idx, context) - case f: PainlessScript => expr = s"$expr${f.painless(context)}" - case f => expr = f.toSQL(expr) // fallback - } - } - expr - } - def script: Option[String] = if (isTemporal) { var orderedFunctions = FunctionUtils.transformFunctions(this).reverse @@ -726,10 +730,11 @@ package object sql { def checkNotNull: String = if (path.isEmpty) "" else - s"(!doc.containsKey('$path') || doc['$path'].empty ? $nullValue : doc['$path'].value)" + s"(!doc.containsKey('$path') || doc['$path'].empty ? $nullValue : doc['$path'].value${painlessMethods + .mkString("")})" override def painless(context: Option[PainlessContext]): String = { - toPainless( + val base = context match { case Some(ctx) => ctx.addParam(this).getOrElse("") @@ -738,9 +743,17 @@ package object sql { checkNotNull else paramName - }, - context - ) + } + val orderedFunctions = FunctionUtils.transformFunctions(this).reverse + var expr = base + orderedFunctions.zipWithIndex.foreach { case (f, idx) => + f match { + case f: TransformFunction[_, _] => expr = f.toPainless(expr, idx, context) + case f: PainlessScript => expr = s"$expr${f.painless(context)}" + case f => expr = f.toSQL(expr) // fallback + } + } + expr } override def param: String = paramName From b8e966dca18740a4c99e65b3ea4aacd98477d5d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Manciot?= Date: Fri, 17 Oct 2025 16:33:42 +0200 Subject: [PATCH 12/21] fix painless to truncate dates using QUARTER time unit --- .../elastic/sql/SQLQuerySpec.scala | 51 ++++++++++++++++++- .../elastic/sql/SQLQuerySpec.scala | 49 +++++++++++++++++- .../elastic/sql/function/time/package.scala | 2 +- .../elastic/sql/SQLParserSpec.scala | 15 +++++- 4 files changed, 111 insertions(+), 6 deletions(-) diff --git a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index 493b1567..07c55f0f 100644 --- a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -1235,7 +1235,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll(",LocalDate", ", LocalDate") } - it should "handle date_format function" in { // FIXME painless generated does not take into account date_trunc + it should "handle date_format function" in { val select: ElasticSearchRequest = SQLQuery(dateFormat) val query = select.query @@ -1254,11 +1254,53 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } | }, | "script_fields": { - | "lastSeen": { + | "y": { + | "script": { + | "lang": "painless", + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.withDayOfYear(1).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | } + | }, + | "q": { + | "script": { + | "lang": "painless", + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = param1.withMonth((((param1.getMonthValue() - 1) / 3) * 3) + 1).withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS) : null; def param3 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param3.format(param1)" + | } + | }, + | "m": { | "script": { | "lang": "painless", | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" | } + | }, + | "w": { + | "script": { + | "lang": "painless", + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.with(DayOfWeek.SUNDAY).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | } + | }, + | "d": { + | "script": { + | "lang": "painless", + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | } + | }, + | "h": { + | "script": { + | "lang": "painless", + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.truncatedTo(ChronoUnit.HOURS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | } + | }, + | "m2": { + | "script": { + | "lang": "painless", + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.truncatedTo(ChronoUnit.MINUTES)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | } + | }, + | "lastSeen": { + | "script": { + | "lang": "painless", + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.truncatedTo(ChronoUnit.SECONDS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | } | } | }, | "_source": { @@ -1280,6 +1322,11 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll(",ChronoUnit", ", ChronoUnit") .replaceAll("=DateTimeFormatter", " = DateTimeFormatter") .replaceAll("==", " == ") + .replaceAll("-(\\d)", " - $1") + .replaceAll("\\+", " + ") + .replaceAll("/", " / ") + .replaceAll("\\*", " * ") + .replaceAll("=p", " = p") .replaceAll("!=", " != ") .replaceAll("&&", " && ") .replaceAll("\\|\\|", " || ") diff --git a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index f25cb6cd..e6d11ffe 100644 --- a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -1258,11 +1258,53 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | } | }, | "script_fields": { - | "lastSeen": { + | "y": { + | "script": { + | "lang": "painless", + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.withDayOfYear(1).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | } + | }, + | "q": { + | "script": { + | "lang": "painless", + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = param1.withMonth((((param1.getMonthValue() - 1) / 3) * 3) + 1).withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS) : null; def param3 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param3.format(param1)" + | } + | }, + | "m": { | "script": { | "lang": "painless", | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" | } + | }, + | "w": { + | "script": { + | "lang": "painless", + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.with(DayOfWeek.SUNDAY).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | } + | }, + | "d": { + | "script": { + | "lang": "painless", + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | } + | }, + | "h": { + | "script": { + | "lang": "painless", + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.truncatedTo(ChronoUnit.HOURS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | } + | }, + | "m2": { + | "script": { + | "lang": "painless", + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.truncatedTo(ChronoUnit.MINUTES)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | } + | }, + | "lastSeen": { + | "script": { + | "lang": "painless", + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.truncatedTo(ChronoUnit.SECONDS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | } | } | }, | "_source": { @@ -1284,6 +1326,11 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll(",ChronoUnit", ", ChronoUnit") .replaceAll("=DateTimeFormatter", " = DateTimeFormatter") .replaceAll("==", " == ") + .replaceAll("-(\\d)", " - $1") + .replaceAll("\\+", " + ") + .replaceAll("/", " / ") + .replaceAll("\\*", " * ") + .replaceAll("=p", " = p") .replaceAll("!=", " != ") .replaceAll("&&", " && ") .replaceAll("\\|\\|", " || ") diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala index f15bccc5..528f5a26 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala @@ -241,7 +241,7 @@ package object time { ctx.addParam(identifier) match { case Some(p) => val quarterExpr = - s".withMonth(((($p.getMonthValue() - 1) / 3) * 3) + 1).withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS) : null" + s"$p.withMonth(((($p.getMonthValue() - 1) / 3) * 3) + 1).withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS) : null" ctx.addParam( LiteralParam( quarterExpr diff --git a/sql/src/test/scala/app/softnetwork/elastic/sql/SQLParserSpec.scala b/sql/src/test/scala/app/softnetwork/elastic/sql/SQLParserSpec.scala index 72e40fca..186833e3 100644 --- a/sql/src/test/scala/app/softnetwork/elastic/sql/SQLParserSpec.scala +++ b/sql/src/test/scala/app/softnetwork/elastic/sql/SQLParserSpec.scala @@ -123,8 +123,19 @@ object Queries { val aggregationWithDateDiff = "SELECT MAX(date_diff(datetime_parse(createdAt, '%Y-%m-%d %H:%i:%s.%f'), updatedAt, DAY)) AS max_diff FROM Table GROUP BY identifier" - val dateFormat = - "SELECT identifier, date_format(date_trunc(lastUpdated, MONTH), '%Y-%m-%d') AS lastSeen FROM Table WHERE identifier2 is NOT null" + val dateFormat: String = + """SELECT + |identifier, + |date_format(date_trunc(lastUpdated, YEAR), '%Y-%m-%d') AS y, + |date_format(date_trunc(lastUpdated, QUARTER), '%Y-%m-%d') AS q, + |date_format(date_trunc(lastUpdated, MONTH), '%Y-%m-%d') AS m, + |date_format(date_trunc(lastUpdated, WEEK), '%Y-%m-%d') AS w, + |date_format(date_trunc(lastUpdated, DAY), '%Y-%m-%d') AS d, + |date_format(date_trunc(lastUpdated, HOUR), '%Y-%m-%d') AS h, + |date_format(date_trunc(lastUpdated, MINUTE), '%Y-%m-%d') AS m2, + |date_format(date_trunc(lastUpdated, SECOND), '%Y-%m-%d') AS lastSeen + |FROM Table + |WHERE identifier2 IS NOT NULL""".stripMargin.replaceAll("\n", " ") val dateTimeFormat = "SELECT identifier, datetime_format(date_trunc(lastUpdated, MONTH), '%Y-%m-%d %H:%i:%s') AS lastSeen FROM Table WHERE identifier2 is NOT null" val dateAdd = From 93c7b474157575529ef76e74742a078adda9db2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Manciot?= Date: Sun, 19 Oct 2025 09:38:22 +0200 Subject: [PATCH 13/21] fix painless script for lower and upper string functions --- .../test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala | 4 ++-- .../test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala | 4 ++-- .../app/softnetwork/elastic/sql/function/string/package.scala | 4 ++++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index 07c55f0f..022b2f1c 100644 --- a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -2603,13 +2603,13 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "low": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.lower()" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.toLowerCase()" | } | }, | "upp": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.upper()" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.toUpperCase()" | } | }, | "sub": { diff --git a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index e6d11ffe..b99ddb04 100644 --- a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -2607,13 +2607,13 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "low": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.lower()" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.toLowerCase()" | } | }, | "upp": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.upper()" + | "source": "def param1 = (!doc.containsKey('identifier2') || doc['identifier2'].empty ? null : doc['identifier2'].value); (param1 == null) ? null : param1.toUpperCase()" | } | }, | "sub": { diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/string/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/string/package.scala index 32590adc..4629692c 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/string/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/string/package.scala @@ -47,9 +47,13 @@ package object string { } case object Lower extends Expr("LOWER") with StringOp { override lazy val words: List[String] = List(sql, "LCASE") + + override def painless(context: Option[PainlessContext]): String = s".toLowerCase()" } case object Upper extends Expr("UPPER") with StringOp { override lazy val words: List[String] = List(sql, "UCASE") + + override def painless(context: Option[PainlessContext]): String = s".toUpperCase()" } case object Trim extends Expr("TRIM") with StringOp case object Ltrim extends Expr("LTRIM") with StringOp { From 2d3d4579aee58da19a2d2dc2023f5581a2394d98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Manciot?= Date: Sun, 19 Oct 2025 10:27:51 +0200 Subject: [PATCH 14/21] fix painless script for day of week function --- .../softnetwork/elastic/sql/SQLQuerySpec.scala | 3 ++- .../softnetwork/elastic/sql/SQLQuerySpec.scala | 3 ++- .../elastic/sql/function/time/package.scala | 18 +++++++++++++++++- .../sql/parser/function/time/package.scala | 13 ++++++++++--- 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index 022b2f1c..d08e2dfc 100644 --- a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -2945,7 +2945,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "wd": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.DAY_OF_WEEK)); param1" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (param1 == null) ? null : (param1.get(ChronoField.DAY_OF_WEEK) + 6) % 7" | } | }, | "yd": { @@ -3047,6 +3047,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("-", " - ") .replaceAll("\\*", " * ") .replaceAll("/", " / ") + .replaceAll("%", " % ") .replaceAll(">", " > ") .replaceAll("<", " < ") .replaceAll("!=", " != ") diff --git a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index b99ddb04..0e1e6f77 100644 --- a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -2949,7 +2949,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "wd": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.get(ChronoField.DAY_OF_WEEK)); param1" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (param1 == null) ? null : (param1.get(ChronoField.DAY_OF_WEEK) + 6) % 7" | } | }, | "yd": { @@ -3051,6 +3051,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("-", " - ") .replaceAll("\\*", " * ") .replaceAll("/", " / ") + .replaceAll("%", " % ") .replaceAll(">", " > ") .replaceAll("<", " < ") .replaceAll("!=", " != ") diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala index 528f5a26..0047f787 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala @@ -294,7 +294,23 @@ package object time { class DayOfMonth extends TimeFieldExtract(DAY_OF_MONTH) - class DayOfWeek extends TimeFieldExtract(DAY_OF_WEEK) + class DayOfWeek(date: Identifier) + extends TimeFieldExtract(DAY_OF_WEEK) + with FunctionWithIdentifier { + override def identifier: Identifier = date + override def args: List[PainlessScript] = List(identifier) + override def toPainlessCall( + callArgs: List[String], + context: Option[PainlessContext] + ): String = { + callArgs match { + case arg :: Nil => + s"($arg.get(${field.painless(context)}) + 6) % 7" + case _ => throw new IllegalArgumentException("DayOfWeek requires exactly one argument") + } + } + + } class DayOfYear extends TimeFieldExtract(DAY_OF_YEAR) diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/time/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/time/package.scala index adbc9fd3..aead8076 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/time/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/function/time/package.scala @@ -191,14 +191,21 @@ package object time { import TimeField._ + def day_of_week_tr: PackratParser[FunctionWithIdentifier] = + DAY_OF_WEEK.regex ~ start ~ (identifierWithTransformation | identifierWithIntervalFunction | identifierWithFunction | identifier) ~ end ^^ { + case _ ~ _ ~ i ~ _ => new DayOfWeek(i) + } + + def day_of_week_identifier: PackratParser[Identifier] = day_of_week_tr ^^ { dw => + dw.identifier.withFunctions(dw +: dw.identifier.functions) + } + def year_tr: PackratParser[TransformFunction[SQLTemporal, SQLNumeric]] = YEAR.regex ^^ (_ => new Year) def month_of_year_tr: PackratParser[TransformFunction[SQLTemporal, SQLNumeric]] = MONTH_OF_YEAR.regex ^^ (_ => new MonthOfYear) def day_of_month_tr: PackratParser[TransformFunction[SQLTemporal, SQLNumeric]] = DAY_OF_MONTH.regex ^^ (_ => new DayOfMonth) - def day_of_week_tr: PackratParser[TransformFunction[SQLTemporal, SQLNumeric]] = - DAY_OF_WEEK.regex ^^ (_ => new DayOfWeek) def day_of_year_tr: PackratParser[TransformFunction[SQLTemporal, SQLNumeric]] = DAY_OF_YEAR.regex ^^ (_ => new DayOfYear) def hour_of_day_tr: PackratParser[TransformFunction[SQLTemporal, SQLNumeric]] = @@ -228,7 +235,6 @@ package object time { year_tr | month_of_year_tr | day_of_month_tr | - day_of_week_tr | day_of_year_tr | hour_of_day_tr | minute_of_hour_tr | @@ -250,6 +256,7 @@ package object time { dateTimeFunctionWithIdentifier | date_diff_identifier | date_trunc_identifier | + day_of_week_identifier | extract_identifier) ~ rep(intervalFunction) ^^ { case i ~ f => i.withFunctions(f ++ i.functions) } From 6f47a445bf93529df0120216fd95a88c8a297a82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Manciot?= Date: Mon, 20 Oct 2025 06:57:48 +0200 Subject: [PATCH 15/21] fix painless script for interval function and FunctionN --- .../elastic/sql/SQLQuerySpec.scala | 27 +++++++------- .../elastic/sql/SQLQuerySpec.scala | 37 ++++++++++--------- .../elastic/sql/function/package.scala | 35 +++++++++++++++++- .../elastic/sql/function/time/package.scala | 35 ++++++++++++------ .../app/softnetwork/elastic/sql/package.scala | 3 +- 5 files changed, 93 insertions(+), 44 deletions(-) diff --git a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index d08e2dfc..34d9034e 100644 --- a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -850,7 +850,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "ct": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (param1 != null ? param1.minus(35, ChronoUnit.MINUTES) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.minus(35, ChronoUnit.MINUTES)); param1" | } | } | }, @@ -1263,7 +1263,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "q": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = param1.withMonth((((param1.getMonthValue() - 1) / 3) * 3) + 1).withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS) : null; def param3 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param3.format(param1)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = param1 != null ? param1.withMonth((((param1.getMonthValue() - 1) / 3) * 3) + 1).withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS) : null; def param3 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param3.format(param2)" | } | }, | "m": { @@ -1520,7 +1520,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "max": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('updatedAt') || doc['updatedAt'].empty ? null : doc['updatedAt'].value); def param2 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (param1 == null || param2 == null) ? null : ChronoUnit.DAYS.between(param1, param2)" + | "source": "def param1 = (!doc.containsKey('updatedAt') || doc['updatedAt'].empty ? null : doc['updatedAt'].value); def param2 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param3 = DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss.SSS XXX'); def param4 = (param2 == null) ? null : param3.parse(param2, ZonedDateTime::from); (param1 == null || param2 == null) ? null : ChronoUnit.DAYS.between(param1, param4)" | } | } | } @@ -1545,6 +1545,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("&&", " && ") .replaceAll("\\|\\|", " || ") .replaceAll("ZonedDateTime", " ZonedDateTime") + .replaceAll("=DateTimeFormatter", " = DateTimeFormatter") .replaceAll("SSSXXX", "SSS XXX") .replaceAll("ddHH", "dd HH") } @@ -1571,7 +1572,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "lastSeen": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); (param1 != null ? param1.plus(10, ChronoUnit.DAYS) : null)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.plus(10, ChronoUnit.DAYS)); param1" | } | } | }, @@ -1600,7 +1601,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("ChronoUnit", " ChronoUnit") } - it should "handle date_sub function as script field" in { + it should "handle date_sub function as script field" in { // 30 val select: ElasticSearchRequest = SQLQuery(dateSub) val query = select.query @@ -1622,7 +1623,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "lastSeen": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); (param1 != null ? param1.minus(10, ChronoUnit.DAYS) : null)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.minus(10, ChronoUnit.DAYS)); param1" | } | } | }, @@ -1673,7 +1674,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "lastSeen": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); (param1 != null ? param1.plus(10, ChronoUnit.DAYS) : null)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.plus(10, ChronoUnit.DAYS)); param1" | } | } | }, @@ -1724,7 +1725,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "lastSeen": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); (param1 != null ? param1.minus(10, ChronoUnit.DAYS) : null)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.minus(10, ChronoUnit.DAYS)); param1" | } | } | }, @@ -1905,7 +1906,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param1 != null ? param1 : param2" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.minus(35, ChronoUnit.MINUTES)); def param2 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param1 != null ? param1 : param2" | } | } | }, @@ -1955,7 +1956,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd').parse(\"2025-09-11\", LocalDate::from); def param3 = param1 == param2.minus(2, ChronoUnit.DAYS) ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param3 != null ? param3 : param4" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd').parse(\"2025-09-11\", LocalDate::from).minus(2, ChronoUnit.DAYS); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param3 != null ? param3 : param4" | } | } | }, @@ -2011,7 +2012,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd').parse(\"2025-09-11\", LocalDate::from); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); try { param3 != null ? param3 : param4.atStartOfDay(ZoneId.of('Z')).minus(2, ChronoUnit.HOURS) } catch (Exception e) { return null; }" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd').parse(\"2025-09-11\", LocalDate::from); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(2, ChronoUnit.HOURS); try { param3 != null ? param3 : param4 } catch (Exception e) { return null; }" | } | }, | "c2": { @@ -2095,7 +2096,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = ZonedDateTime.now(ZoneId.of('Z')); def param3 = param1 == null ? false : (param1 > param2.minus(7, ChronoUnit.DAYS)); def param4 = (!doc.containsKey('lastSeen') || doc['lastSeen'].empty ? null : doc['lastSeen'].value); def param5 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param3 ? param1 : param4 != null ? param4.plus(2, ChronoUnit.DAYS) : param5" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = ZonedDateTime.now(ZoneId.of('Z')).minus(7, ChronoUnit.DAYS); def param3 = param1 == null ? false : (param1 > param2); def param4 = (!doc.containsKey('lastSeen') || doc['lastSeen'].empty ? null : doc['lastSeen'].value.plus(2, ChronoUnit.DAYS)); def param5 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param3 ? param1 : param4 != null ? param4 : param5" | } | } | }, @@ -2148,7 +2149,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); def param2 = param1.minus(7, ChronoUnit.DAYS); def param3 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param4 = (param3 != null ? param3.minus(3, ChronoUnit.DAYS) : null); def param5 = (!doc.containsKey('lastSeen') || doc['lastSeen'].empty ? null : doc['lastSeen'].value); def param6 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param2 == param4 ? param3 : param2 == param5 ? param5.plus(2, ChronoUnit.DAYS) : param6" + | "source": "def param1 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(7, ChronoUnit.DAYS); def param2 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.minus(3, ChronoUnit.DAYS)); def param3 = (!doc.containsKey('lastSeen') || doc['lastSeen'].empty ? null : doc['lastSeen'].value.plus(2, ChronoUnit.DAYS)); def param4 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1 == param2 ? param2 : param1 == param3 ? param3 : param4" | } | } | }, diff --git a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index 0e1e6f77..87f77886 100644 --- a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -28,7 +28,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { "SQLQuery" should "perform native count" in { val results: Seq[ElasticAggregation] = - SQLQuery("select count(t.id) c2 from Table t where t.nom = \"Nom\"") + SQLQuery("select count(t.id) c2 from Table t where t.nom = 'Nom'") results.size shouldBe 1 val result = results.head result.nested shouldBe false @@ -64,7 +64,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { it should "perform count distinct" in { val results: Seq[ElasticAggregation] = - SQLQuery("select count(distinct t.id) as c2 from Table as t where nom = \"Nom\"") + SQLQuery("select count(distinct t.id) as c2 from Table as t where nom = 'Nom'") results.size shouldBe 1 val result = results.head result.nested shouldBe false @@ -101,7 +101,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { it should "perform nested count" in { val results: Seq[ElasticAggregation] = SQLQuery( - "select count(inner_emails.value) as email from index i join unnest(i.emails) as inner_emails where i.nom = \"Nom\"" + "select count(inner_emails.value) as email from index i join unnest(i.emails) as inner_emails where i.nom = 'Nom'" ) results.size shouldBe 1 val result = results.head @@ -850,7 +850,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "ct": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (param1 != null ? param1.minus(35, ChronoUnit.MINUTES) : null)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.minus(35, ChronoUnit.MINUTES)); param1" | } | } | }, @@ -1042,7 +1042,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { it should "handle group by with having and date time functions" in { val select: ElasticSearchRequest = - SQLQuery(groupByWithHavingAndDateTimeFunctions.replace("GROUP BY 3, 2", "GROUP BY 3, 2")) + SQLQuery(groupByWithHavingAndDateTimeFunctions) val query = select.query println(query) query shouldBe @@ -1267,7 +1267,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "q": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = param1.withMonth((((param1.getMonthValue() - 1) / 3) * 3) + 1).withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS) : null; def param3 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param3.format(param1)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = param1 != null ? param1.withMonth((((param1.getMonthValue() - 1) / 3) * 3) + 1).withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS) : null; def param3 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param3.format(param2)" | } | }, | "m": { @@ -1338,7 +1338,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll(",LocalDate", ", LocalDate") } - it should "handle datetime_parse function" in { + it should "handle datetime_parse function" in { // #25 val select: ElasticSearchRequest = SQLQuery(dateTimeParse) val query = select.query @@ -1524,7 +1524,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "max": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('updatedAt') || doc['updatedAt'].empty ? null : doc['updatedAt'].value); def param2 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (param1 == null || param2 == null) ? null : ChronoUnit.DAYS.between(param1, param2)" + | "source": "def param1 = (!doc.containsKey('updatedAt') || doc['updatedAt'].empty ? null : doc['updatedAt'].value); def param2 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param3 = DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss.SSS XXX'); def param4 = (param2 == null) ? null : param3.parse(param2, ZonedDateTime::from); (param1 == null || param2 == null) ? null : ChronoUnit.DAYS.between(param1, param4)" | } | } | } @@ -1549,6 +1549,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("&&", " && ") .replaceAll("\\|\\|", " || ") .replaceAll("ZonedDateTime", " ZonedDateTime") + .replaceAll("=DateTimeFormatter", " = DateTimeFormatter") .replaceAll("SSSXXX", "SSS XXX") .replaceAll("ddHH", "dd HH") } @@ -1575,7 +1576,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "lastSeen": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); (param1 != null ? param1.plus(10, ChronoUnit.DAYS) : null)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.plus(10, ChronoUnit.DAYS)); param1" | } | } | }, @@ -1604,7 +1605,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("ChronoUnit", " ChronoUnit") } - it should "handle date_sub function as script field" in { + it should "handle date_sub function as script field" in { // 30 val select: ElasticSearchRequest = SQLQuery(dateSub) val query = select.query @@ -1626,7 +1627,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "lastSeen": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); (param1 != null ? param1.minus(10, ChronoUnit.DAYS) : null)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.minus(10, ChronoUnit.DAYS)); param1" | } | } | }, @@ -1677,7 +1678,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "lastSeen": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); (param1 != null ? param1.plus(10, ChronoUnit.DAYS) : null)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.plus(10, ChronoUnit.DAYS)); param1" | } | } | }, @@ -1728,7 +1729,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "lastSeen": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); (param1 != null ? param1.minus(10, ChronoUnit.DAYS) : null)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.minus(10, ChronoUnit.DAYS)); param1" | } | } | }, @@ -1909,7 +1910,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param1 != null ? param1 : param2" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.minus(35, ChronoUnit.MINUTES)); def param2 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param1 != null ? param1 : param2" | } | } | }, @@ -1959,7 +1960,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd').parse(\"2025-09-11\", LocalDate::from); def param3 = param1 == param2.minus(2, ChronoUnit.DAYS) ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param3 != null ? param3 : param4" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd').parse(\"2025-09-11\", LocalDate::from).minus(2, ChronoUnit.DAYS); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param3 != null ? param3 : param4" | } | } | }, @@ -2015,7 +2016,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd').parse(\"2025-09-11\", LocalDate::from); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); try { param3 != null ? param3 : param4.atStartOfDay(ZoneId.of('Z')).minus(2, ChronoUnit.HOURS) } catch (Exception e) { return null; }" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd').parse(\"2025-09-11\", LocalDate::from); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(2, ChronoUnit.HOURS); try { param3 != null ? param3 : param4 } catch (Exception e) { return null; }" | } | }, | "c2": { @@ -2099,7 +2100,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = ZonedDateTime.now(ZoneId.of('Z')); def param3 = param1 == null ? false : (param1 > param2.minus(7, ChronoUnit.DAYS)); def param4 = (!doc.containsKey('lastSeen') || doc['lastSeen'].empty ? null : doc['lastSeen'].value); def param5 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param3 ? param1 : param4 != null ? param4.plus(2, ChronoUnit.DAYS) : param5" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = ZonedDateTime.now(ZoneId.of('Z')).minus(7, ChronoUnit.DAYS); def param3 = param1 == null ? false : (param1 > param2); def param4 = (!doc.containsKey('lastSeen') || doc['lastSeen'].empty ? null : doc['lastSeen'].value.plus(2, ChronoUnit.DAYS)); def param5 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param3 ? param1 : param4 != null ? param4 : param5" | } | } | }, @@ -2152,7 +2153,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); def param2 = param1.minus(7, ChronoUnit.DAYS); def param3 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param4 = (param3 != null ? param3.minus(3, ChronoUnit.DAYS) : null); def param5 = (!doc.containsKey('lastSeen') || doc['lastSeen'].empty ? null : doc['lastSeen'].value); def param6 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param2 == param4 ? param3 : param2 == param5 ? param5.plus(2, ChronoUnit.DAYS) : param6" + | "source": "def param1 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(7, ChronoUnit.DAYS); def param2 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.minus(3, ChronoUnit.DAYS)); def param3 = (!doc.containsKey('lastSeen') || doc['lastSeen'].empty ? null : doc['lastSeen'].value.plus(2, ChronoUnit.DAYS)); def param4 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1 == param2 ? param2 : param1 == param3 ? param3 : param4" | } | } | }, diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala index 03fdb722..3ee069d9 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala @@ -188,7 +188,40 @@ package object function { val callArgs = args.zipWithIndex .map { case (a, i) => - context.flatMap(ctx => ctx.get(a)).getOrElse { + (context match { + case Some(ctx) => + ctx.get(a) match { + case Some(paramName) => + a match { + case chain: FunctionChain if chain.functions.nonEmpty => + val ret = SQLTypeUtils + .coerce( + a.painless(context), + a.baseType, + argTypes(i), + nullable = a.nullable, + context + ) + if (ret.startsWith(".")) { + // apply methods + ctx.find(paramName) match { + case Some(p) => + p.addPainlessMethod(ret) + case _ => + } + Option(paramName) + } else if (ret == paramName) + Option(paramName) + else { + ctx.addParam(LiteralParam(ret)) + } + case _ => + Option(paramName) + } + case _ => None + } + case _ => None + }).getOrElse { if (a.nullable) s"arg$i" else SQLTypeUtils diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala index 0047f787..2f77662f 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala @@ -76,21 +76,28 @@ package object time { case Right(_) => Right(()) } - override def toPainless(base: String, idx: Int, context: Option[PainlessContext]): String = + override def toPainless(base: String, idx: Int, context: Option[PainlessContext]): String = { + context match { + case Some(ctx) => + ctx.last match { + case Some(p) => + ctx.find(p) match { + case Some(param) => + param.addPainlessMethod(painless(context)) + return p + case _ => + return s"($p != null ? ${SQLTypeUtils.coerce(base, expr.baseType, out, nullable = false, context)}${painless(context)} : null)" + } + case _ => + } + case _ => + } if (nullable) { - context match { - case Some(ctx) => - ctx.last match { - case Some(p) => - return s"($p != null ? ${SQLTypeUtils.coerce(base, expr.baseType, out, nullable = false, context)}${painless(context)} : null)" - case _ => - } - case _ => - } // ensure unique variable names s"(def e$idx = $base; e$idx != null ? ${SQLTypeUtils.coerce(s"e$idx", expr.baseType, out, nullable = false, context)}${painless(context)} : null)" } else s"${SQLTypeUtils.coerce(base, expr.baseType, out, nullable = expr.nullable, context)}${painless(context)}" + } } sealed trait AddInterval[IO <: SQLTemporal] extends IntervalFunction[IO] { @@ -240,8 +247,14 @@ package object time { case Some(ctx) => ctx.addParam(identifier) match { case Some(p) => + val quarter = + s"$p.withMonth(((($p.getMonthValue() - 1) / 3) * 3) + 1).withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS)" val quarterExpr = - s"$p.withMonth(((($p.getMonthValue() - 1) / 3) * 3) + 1).withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS) : null" + if (identifier.nullable) { + s"$p != null ? $quarter : null" + } else { + quarter + } ctx.addParam( LiteralParam( quarterExpr diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala index 1ccb44ce..b84a5acb 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala @@ -107,7 +107,8 @@ package object sql { collection.mutable.Seq.empty def addPainlessMethod(method: String): PainlessParam = { - _painlessMethods = _painlessMethods :+ method + if (!_painlessMethods.contains(method)) + _painlessMethods = _painlessMethods :+ method // FIXME we should apply functions only once this } From d842c5e900b14f1936120347e4c3b6d063867298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Manciot?= Date: Mon, 20 Oct 2025 08:08:19 +0200 Subject: [PATCH 16/21] fix painless script for date parse and date time parse functions --- .../elastic/sql/SQLQuerySpec.scala | 16 +++++++++------ .../elastic/sql/SQLQuerySpec.scala | 16 +++++++++------ .../elastic/sql/function/time/package.scala | 20 +++++-------------- 3 files changed, 25 insertions(+), 27 deletions(-) diff --git a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index 34d9034e..2cd85548 100644 --- a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -1207,7 +1207,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "field": "createdAt", | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.parse(param1, LocalDate::from)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (param1 == null) ? null : LocalDate.parse(param1, DateTimeFormatter.ofPattern('yyyy-MM-dd'))" | } | } | } @@ -1227,6 +1227,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll(";", "; ") .replaceAll(",ChronoUnit", ", ChronoUnit") .replaceAll("=DateTimeFormatter", " = DateTimeFormatter") + .replaceAll(",DateTimeFormatter", ", DateTimeFormatter") .replaceAll("==", " == ") .replaceAll("!=", " != ") .replaceAll("&&", " && ") @@ -1373,7 +1374,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "field": "createdAt", | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss.SSS XXX'); (param1 == null) ? null : param2.parse(param1, ZonedDateTime::from).truncatedTo(ChronoUnit.MINUTES).get(ChronoField.YEAR)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (param1 == null) ? null : ZonedDateTime.parse(param1, DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss.SSS XXX')).truncatedTo(ChronoUnit.MINUTES).get(ChronoField.YEAR)" | } | } | } @@ -1398,6 +1399,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll(">", " > ") .replaceAll(",ZonedDateTime", ", ZonedDateTime") .replaceAll("=DateTimeFormatter", " = DateTimeFormatter") + .replaceAll(",DateTimeFormatter", ", DateTimeFormatter") .replaceAll("SSSXXX", "SSS XXX") .replaceAll("ddHH", "dd HH") } @@ -1520,7 +1522,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "max": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('updatedAt') || doc['updatedAt'].empty ? null : doc['updatedAt'].value); def param2 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param3 = DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss.SSS XXX'); def param4 = (param2 == null) ? null : param3.parse(param2, ZonedDateTime::from); (param1 == null || param2 == null) ? null : ChronoUnit.DAYS.between(param1, param4)" + | "source": "def param1 = (!doc.containsKey('updatedAt') || doc['updatedAt'].empty ? null : doc['updatedAt'].value); def param2 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param3 = (param2 == null) ? null : ZonedDateTime.parse(param2, DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss.SSS XXX')); (param1 == null || param2 == null) ? null : ChronoUnit.DAYS.between(param1, param3)" | } | } | } @@ -1544,8 +1546,8 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("!=", " != ") .replaceAll("&&", " && ") .replaceAll("\\|\\|", " || ") - .replaceAll("ZonedDateTime", " ZonedDateTime") .replaceAll("=DateTimeFormatter", " = DateTimeFormatter") + .replaceAll(",DateTimeFormatter", ", DateTimeFormatter") .replaceAll("SSSXXX", "SSS XXX") .replaceAll("ddHH", "dd HH") } @@ -1956,7 +1958,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd').parse(\"2025-09-11\", LocalDate::from).minus(2, ChronoUnit.DAYS); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param3 != null ? param3 : param4" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern('yyyy-MM-dd')).minus(2, ChronoUnit.DAYS); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param3 != null ? param3 : param4" | } | } | }, @@ -1993,7 +1995,9 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll(";\\s\\s", "; ") .replaceAll("ChronoUnit", " ChronoUnit") .replaceAll(",LocalDate", ", LocalDate") + .replaceAll("=LocalDate", " = LocalDate") .replaceAll("=DateTimeFormatter", " = DateTimeFormatter") + .replaceAll(",DateTimeFormatter", ", DateTimeFormatter") .replaceAll("=ZonedDateTime", " = ZonedDateTime") .replaceAll(":ZonedDateTime", " : ZonedDateTime") } @@ -2012,7 +2016,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd').parse(\"2025-09-11\", LocalDate::from); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(2, ChronoUnit.HOURS); try { param3 != null ? param3 : param4 } catch (Exception e) { return null; }" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(2, ChronoUnit.HOURS); try { param3 != null ? param3 : param4 } catch (Exception e) { return null; }" | } | }, | "c2": { diff --git a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index 87f77886..e1c37104 100644 --- a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -1211,7 +1211,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "field": "createdAt", | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.parse(param1, LocalDate::from)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (param1 == null) ? null : LocalDate.parse(param1, DateTimeFormatter.ofPattern('yyyy-MM-dd'))" | } | } | } @@ -1231,6 +1231,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll(";", "; ") .replaceAll(",ChronoUnit", ", ChronoUnit") .replaceAll("=DateTimeFormatter", " = DateTimeFormatter") + .replaceAll(",DateTimeFormatter", ", DateTimeFormatter") .replaceAll("==", " == ") .replaceAll("!=", " != ") .replaceAll("&&", " && ") @@ -1377,7 +1378,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "field": "createdAt", | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss.SSS XXX'); (param1 == null) ? null : param2.parse(param1, ZonedDateTime::from).truncatedTo(ChronoUnit.MINUTES).get(ChronoField.YEAR)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (param1 == null) ? null : ZonedDateTime.parse(param1, DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss.SSS XXX')).truncatedTo(ChronoUnit.MINUTES).get(ChronoField.YEAR)" | } | } | } @@ -1402,6 +1403,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll(">", " > ") .replaceAll(",ZonedDateTime", ", ZonedDateTime") .replaceAll("=DateTimeFormatter", " = DateTimeFormatter") + .replaceAll(",DateTimeFormatter", ", DateTimeFormatter") .replaceAll("SSSXXX", "SSS XXX") .replaceAll("ddHH", "dd HH") } @@ -1524,7 +1526,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "max": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('updatedAt') || doc['updatedAt'].empty ? null : doc['updatedAt'].value); def param2 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param3 = DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss.SSS XXX'); def param4 = (param2 == null) ? null : param3.parse(param2, ZonedDateTime::from); (param1 == null || param2 == null) ? null : ChronoUnit.DAYS.between(param1, param4)" + | "source": "def param1 = (!doc.containsKey('updatedAt') || doc['updatedAt'].empty ? null : doc['updatedAt'].value); def param2 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param3 = (param2 == null) ? null : ZonedDateTime.parse(param2, DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss.SSS XXX')); (param1 == null || param2 == null) ? null : ChronoUnit.DAYS.between(param1, param3)" | } | } | } @@ -1548,8 +1550,8 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("!=", " != ") .replaceAll("&&", " && ") .replaceAll("\\|\\|", " || ") - .replaceAll("ZonedDateTime", " ZonedDateTime") .replaceAll("=DateTimeFormatter", " = DateTimeFormatter") + .replaceAll(",DateTimeFormatter", ", DateTimeFormatter") .replaceAll("SSSXXX", "SSS XXX") .replaceAll("ddHH", "dd HH") } @@ -1960,7 +1962,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd').parse(\"2025-09-11\", LocalDate::from).minus(2, ChronoUnit.DAYS); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param3 != null ? param3 : param4" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern('yyyy-MM-dd')).minus(2, ChronoUnit.DAYS); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param3 != null ? param3 : param4" | } | } | }, @@ -1997,7 +1999,9 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll(";\\s\\s", "; ") .replaceAll("ChronoUnit", " ChronoUnit") .replaceAll(",LocalDate", ", LocalDate") + .replaceAll("=LocalDate", " = LocalDate") .replaceAll("=DateTimeFormatter", " = DateTimeFormatter") + .replaceAll(",DateTimeFormatter", ", DateTimeFormatter") .replaceAll("=ZonedDateTime", " = ZonedDateTime") .replaceAll(":ZonedDateTime", " : ZonedDateTime") } @@ -2016,7 +2020,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd').parse(\"2025-09-11\", LocalDate::from); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(2, ChronoUnit.HOURS); try { param3 != null ? param3 : param4 } catch (Exception e) { return null; }" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(2, ChronoUnit.HOURS); try { param3 != null ? param3 : param4 } catch (Exception e) { return null; }" | } | }, | "c2": { diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala index 2f77662f..d38f0d89 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala @@ -522,20 +522,15 @@ package object time { case Some(ctx) => identifier.baseType match { case SQLTypes.Varchar => - ctx.addParam(LiteralParam(s"$param.parse($arg, LocalDate::from)")) match { + ctx.addParam(LiteralParam(s"LocalDate.parse($arg, $param)")) match { case Some(p) => return p case _ => } case _ => - ctx.addParam(LiteralParam(param)) match { - case Some(p) => return s"$p.parse($arg, LocalDate::from)" - case _ => - } - } case _ => } - s"$param.parse($arg, LocalDate::from)" + s"LocalDate.parse($arg, $param)" case _ => throw new IllegalArgumentException("DateParse requires exactly one argument") } @@ -647,7 +642,7 @@ package object time { with FunctionWithIdentifier with FunctionWithDateTimeFormat with DateMathScript { - override def fun: Option[PainlessScript] = Some(DateTimeParse) + override def fun: Option[PainlessScript] = None override def args: List[PainlessScript] = List(identifier) @@ -668,20 +663,15 @@ package object time { case Some(ctx) => identifier.baseType match { case SQLTypes.Varchar => - ctx.addParam(LiteralParam(s"$param.parse($arg, ZonedDateTime::from)")) match { + ctx.addParam(LiteralParam(s"ZonedDateTime.parse($arg, $param)")) match { case Some(p) => return p case _ => } case _ => - ctx.addParam(LiteralParam(param)) match { - case Some(p) => return s"$p.parse($arg, ZonedDateTime::from)" - case _ => - } - } case _ => } - s"$param.parse($arg, ZonedDateTime::from)" + s"ZonedDateTime.parse($arg, $param)" case _ => throw new IllegalArgumentException("DateParse requires exactly one argument") } From aa0d475ed0562c704f07f14e7d2f327a7ce12d78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Manciot?= Date: Mon, 20 Oct 2025 08:51:57 +0200 Subject: [PATCH 17/21] fix painless script to cast implicitly identifiers to local date or local time when required --- .../softnetwork/elastic/sql/SQLQuerySpec.scala | 4 ++-- .../softnetwork/elastic/sql/SQLQuerySpec.scala | 4 ++-- .../elastic/sql/function/cond/package.scala | 2 +- .../elastic/sql/function/package.scala | 17 ++++++++++++++++- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index 2cd85548..2da8ca6f 100644 --- a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -1958,7 +1958,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern('yyyy-MM-dd')).minus(2, ChronoUnit.DAYS); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param3 != null ? param3 : param4" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.toLocalDate()); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern('yyyy-MM-dd')).minus(2, ChronoUnit.DAYS); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param3 != null ? param3 : param4" | } | } | }, @@ -2016,7 +2016,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(2, ChronoUnit.HOURS); try { param3 != null ? param3 : param4 } catch (Exception e) { return null; }" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.toLocalDate()); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(2, ChronoUnit.HOURS); try { param3 != null ? param3 : param4 } catch (Exception e) { return null; }" | } | }, | "c2": { diff --git a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index e1c37104..622bb63f 100644 --- a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -1962,7 +1962,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern('yyyy-MM-dd')).minus(2, ChronoUnit.DAYS); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param3 != null ? param3 : param4" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.toLocalDate()); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern('yyyy-MM-dd')).minus(2, ChronoUnit.DAYS); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param3 != null ? param3 : param4" | } | } | }, @@ -2020,7 +2020,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(2, ChronoUnit.HOURS); try { param3 != null ? param3 : param4 } catch (Exception e) { return null; }" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.toLocalDate()); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(2, ChronoUnit.HOURS); try { param3 != null ? param3 : param4 } catch (Exception e) { return null; }" | } | }, | "c2": { diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala index c909228a..d354d774 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala @@ -167,7 +167,7 @@ package object cond { callArgs match { case List(arg0, arg1) => val expr = - s"${arg0.trim} == ${arg1.trim} ? null : $arg0" // TODO check when expr1 and expr2 are nullable and have functions + s"${arg0.trim} == ${arg1.trim} ? null : $arg0" context match { case Some(ctx) => ctx.addParam(LiteralParam(expr)) match { diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala index 3ee069d9..1958c000 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala @@ -16,7 +16,7 @@ package app.softnetwork.elastic.sql -import app.softnetwork.elastic.sql.`type`.{SQLType, SQLTypeUtils} +import app.softnetwork.elastic.sql.`type`.{SQLType, SQLTypeUtils, SQLTypes} import app.softnetwork.elastic.sql.function.aggregate.AggregateFunction import app.softnetwork.elastic.sql.operator.math.ArithmeticExpression import app.softnetwork.elastic.sql.parser.Validator @@ -147,6 +147,8 @@ package object function { override def applyType(in: SQLType): SQLType = outputType + lazy val targetedType: SQLType = SQLTypeUtils.leastCommonSuperType(argTypes) + override def sql: String = s"${fun.map(_.sql).getOrElse("")}(${args.map(_.sql).mkString(argsSeparator)})" @@ -215,6 +217,19 @@ package object function { else { ctx.addParam(LiteralParam(ret)) } + case identifier: Identifier => + identifier.baseType match { + case SQLTypes.Any => // in painless context, Any is ZonedDateTime + targetedType match { + case SQLTypes.Date => + identifier.addPainlessMethod(".toLocalDate()") + case SQLTypes.Time => + identifier.addPainlessMethod(".toLocalTime()") + case _ => + } + case _ => + } + Option(paramName) case _ => Option(paramName) } From da9d6f1b87eb8a8c17f503b10e1aec5dd9d846d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Manciot?= Date: Mon, 20 Oct 2025 11:32:06 +0200 Subject: [PATCH 18/21] fix painless script for temporal comparison --- .../elastic/sql/SQLQuerySpec.scala | 46 +++++------ .../elastic/sql/SQLQuerySpec.scala | 40 +++++----- .../elastic/sql/function/package.scala | 15 ++++ .../elastic/sql/function/time/package.scala | 2 +- .../softnetwork/elastic/sql/query/Where.scala | 80 +++++++++++++++++-- .../elastic/sql/type/SQLTypeUtils.scala | 8 +- 6 files changed, 135 insertions(+), 56 deletions(-) diff --git a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index 2da8ca6f..4a7aa563 100644 --- a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -1207,7 +1207,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "field": "createdAt", | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (param1 == null) ? null : LocalDate.parse(param1, DateTimeFormatter.ofPattern('yyyy-MM-dd'))" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (param1 == null) ? null : LocalDate.parse(param1, DateTimeFormatter.ofPattern(\"yyyy-MM-dd\"))" | } | } | } @@ -1258,49 +1258,49 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "y": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.withDayOfYear(1).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.withDayOfYear(1).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern(\"yyyy-MM-dd\"); (param1 == null) ? null : param2.format(param1)" | } | }, | "q": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = param1 != null ? param1.withMonth((((param1.getMonthValue() - 1) / 3) * 3) + 1).withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS) : null; def param3 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param3.format(param2)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = param1 != null ? param1.withMonth((((param1.getMonthValue() - 1) / 3) * 3) + 1).withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS) : null; def param3 = DateTimeFormatter.ofPattern(\"yyyy-MM-dd\"); (param1 == null) ? null : param3.format(param2)" | } | }, | "m": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern(\"yyyy-MM-dd\"); (param1 == null) ? null : param2.format(param1)" | } | }, | "w": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.with(DayOfWeek.SUNDAY).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.with(DayOfWeek.SUNDAY).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern(\"yyyy-MM-dd\"); (param1 == null) ? null : param2.format(param1)" | } | }, | "d": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern(\"yyyy-MM-dd\"); (param1 == null) ? null : param2.format(param1)" | } | }, | "h": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.truncatedTo(ChronoUnit.HOURS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.truncatedTo(ChronoUnit.HOURS)); def param2 = DateTimeFormatter.ofPattern(\"yyyy-MM-dd\"); (param1 == null) ? null : param2.format(param1)" | } | }, | "m2": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.truncatedTo(ChronoUnit.MINUTES)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.truncatedTo(ChronoUnit.MINUTES)); def param2 = DateTimeFormatter.ofPattern(\"yyyy-MM-dd\"); (param1 == null) ? null : param2.format(param1)" | } | }, | "lastSeen": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.truncatedTo(ChronoUnit.SECONDS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.truncatedTo(ChronoUnit.SECONDS)); def param2 = DateTimeFormatter.ofPattern(\"yyyy-MM-dd\"); (param1 == null) ? null : param2.format(param1)" | } | } | }, @@ -1374,7 +1374,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "field": "createdAt", | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (param1 == null) ? null : ZonedDateTime.parse(param1, DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss.SSS XXX')).truncatedTo(ChronoUnit.MINUTES).get(ChronoField.YEAR)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (param1 == null) ? null : ZonedDateTime.parse(param1, DateTimeFormatter.ofPattern(\"yyyy-MM-dd HH:mm:ss.SSS XXX\")).truncatedTo(ChronoUnit.MINUTES).get(ChronoField.YEAR)" | } | } | } @@ -1426,7 +1426,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "lastSeen": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss XXX'); (param1 == null) ? null : param2.format(param1)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern(\"yyyy-MM-dd HH:mm:ss XXX\"); (param1 == null) ? null : param2.format(param1)" | } | } | }, @@ -1522,7 +1522,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "max": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('updatedAt') || doc['updatedAt'].empty ? null : doc['updatedAt'].value); def param2 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param3 = (param2 == null) ? null : ZonedDateTime.parse(param2, DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss.SSS XXX')); (param1 == null || param2 == null) ? null : ChronoUnit.DAYS.between(param1, param3)" + | "source": "def param1 = (!doc.containsKey('updatedAt') || doc['updatedAt'].empty ? null : doc['updatedAt'].value); def param2 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param3 = (param2 == null) ? null : ZonedDateTime.parse(param2, DateTimeFormatter.ofPattern(\"yyyy-MM-dd HH:mm:ss.SSS XXX\")); (param1 == null || param2 == null) ? null : ChronoUnit.DAYS.between(param1, param3)" | } | } | } @@ -1958,7 +1958,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.toLocalDate()); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern('yyyy-MM-dd')).minus(2, ChronoUnit.DAYS); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param3 != null ? param3 : param4" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.toLocalDate()); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern(\"yyyy-MM-dd\")).minus(2, ChronoUnit.DAYS); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param3 != null ? param3 : param4" | } | } | }, @@ -2016,7 +2016,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.toLocalDate()); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(2, ChronoUnit.HOURS); try { param3 != null ? param3 : param4 } catch (Exception e) { return null; }" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.toLocalDate()); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern(\"yyyy-MM-dd\")); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(2, ChronoUnit.HOURS); try { param3 != null ? param3 : param4 } catch (Exception e) { return null; }" | } | }, | "c2": { @@ -2040,7 +2040,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c5": { | "script": { | "lang": "painless", - | "source": "def param1 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); param1" + | "source": "def param1 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern(\"yyyy-MM-dd\")); param1" | } | } | }, @@ -2086,7 +2086,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("} catch", " } catch") } - it should "handle case function as script field" in { + it should "handle case function as script field" in { // 40 val select: ElasticSearchRequest = SQLQuery(caseWhen) val query = select.query @@ -2100,7 +2100,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = ZonedDateTime.now(ZoneId.of('Z')).minus(7, ChronoUnit.DAYS); def param3 = param1 == null ? false : (param1 > param2); def param4 = (!doc.containsKey('lastSeen') || doc['lastSeen'].empty ? null : doc['lastSeen'].value.plus(2, ChronoUnit.DAYS)); def param5 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param3 ? param1 : param4 != null ? param4 : param5" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = ZonedDateTime.now(ZoneId.of('Z')).minus(7, ChronoUnit.DAYS); def param3 = param1 == null ? false : (param1.isAfter(param2)); def param4 = (!doc.containsKey('lastSeen') || doc['lastSeen'].empty ? null : doc['lastSeen'].value.plus(2, ChronoUnit.DAYS)); def param5 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param3 ? param1 : param4 != null ? param4 : param5" | } | } | }, @@ -2577,7 +2577,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("\\(double\\)(\\d)", "(double) $1") } - it should "handle string function as script field and condition" in { + it should "handle string function as script field and condition" in { // 45 val select: ElasticSearchRequest = SQLQuery(string) val query = select.query @@ -3183,7 +3183,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { .replaceAll("lat,arg", "lat, arg") } - it should "handle between with temporal" in { + it should "handle between with temporal" in { // 50 val select: ElasticSearchRequest = SQLQuery(betweenTemporal) val query = select.query @@ -3208,7 +3208,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "script": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); param1 == null ? false : (param1 >= param2.withDayOfMonth(param2.lengthOfMonth()))" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.toLocalDate()); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern(\"yyyy-MM-dd\")); param1 == null ? false : (param1.isBefore(param2.withDayOfMonth(param2.lengthOfMonth())) == false)" | } | } | }, @@ -3298,7 +3298,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "script": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('comments.replies.lastUpdated') || doc['comments.replies.lastUpdated'].empty ? null : doc['comments.replies.lastUpdated'].value); def param2 = LocalDate.parse(\"2025-09-10\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); param1 == null ? false : (param1 < param2.withDayOfMonth(param2.lengthOfMonth()))" + | "source": "def param1 = (!doc.containsKey('comments.replies.lastUpdated') || doc['comments.replies.lastUpdated'].empty ? null : doc['comments.replies.lastUpdated'].value.toLocalDate()); def param2 = LocalDate.parse(\"2025-09-10\", DateTimeFormatter.ofPattern(\"yyyy-MM-dd\")); param1 == null ? false : (param1.isBefore(param2.withDayOfMonth(param2.lengthOfMonth())))" | } | } | } @@ -3396,7 +3396,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "script": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('replies.lastUpdated') || doc['replies.lastUpdated'].empty ? null : doc['replies.lastUpdated'].value); def param2 = LocalDate.parse(\"2025-09-10\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); param1 == null ? false : (param1 < param2.withDayOfMonth(param2.lengthOfMonth()))" + | "source": "def param1 = (!doc.containsKey('replies.lastUpdated') || doc['replies.lastUpdated'].empty ? null : doc['replies.lastUpdated'].value.toLocalDate()); def param2 = LocalDate.parse(\"2025-09-10\", DateTimeFormatter.ofPattern(\"yyyy-MM-dd\")); param1 == null ? false : (param1.isBefore(param2.withDayOfMonth(param2.lengthOfMonth())))" | } | } | }, @@ -3503,7 +3503,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "script": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param1 == null ? false : (param1 < param2)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param1 == null ? false : (param1.isBefore(param2))" | } | } | } diff --git a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index 622bb63f..75e27243 100644 --- a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -1211,7 +1211,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "field": "createdAt", | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (param1 == null) ? null : LocalDate.parse(param1, DateTimeFormatter.ofPattern('yyyy-MM-dd'))" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (param1 == null) ? null : LocalDate.parse(param1, DateTimeFormatter.ofPattern(\"yyyy-MM-dd\"))" | } | } | } @@ -1262,49 +1262,49 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "y": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.withDayOfYear(1).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.withDayOfYear(1).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern(\"yyyy-MM-dd\"); (param1 == null) ? null : param2.format(param1)" | } | }, | "q": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = param1 != null ? param1.withMonth((((param1.getMonthValue() - 1) / 3) * 3) + 1).withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS) : null; def param3 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param3.format(param2)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = param1 != null ? param1.withMonth((((param1.getMonthValue() - 1) / 3) * 3) + 1).withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS) : null; def param3 = DateTimeFormatter.ofPattern(\"yyyy-MM-dd\"); (param1 == null) ? null : param3.format(param2)" | } | }, | "m": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern(\"yyyy-MM-dd\"); (param1 == null) ? null : param2.format(param1)" | } | }, | "w": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.with(DayOfWeek.SUNDAY).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.with(DayOfWeek.SUNDAY).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern(\"yyyy-MM-dd\"); (param1 == null) ? null : param2.format(param1)" | } | }, | "d": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern(\"yyyy-MM-dd\"); (param1 == null) ? null : param2.format(param1)" | } | }, | "h": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.truncatedTo(ChronoUnit.HOURS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.truncatedTo(ChronoUnit.HOURS)); def param2 = DateTimeFormatter.ofPattern(\"yyyy-MM-dd\"); (param1 == null) ? null : param2.format(param1)" | } | }, | "m2": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.truncatedTo(ChronoUnit.MINUTES)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.truncatedTo(ChronoUnit.MINUTES)); def param2 = DateTimeFormatter.ofPattern(\"yyyy-MM-dd\"); (param1 == null) ? null : param2.format(param1)" | } | }, | "lastSeen": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.truncatedTo(ChronoUnit.SECONDS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd'); (param1 == null) ? null : param2.format(param1)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.truncatedTo(ChronoUnit.SECONDS)); def param2 = DateTimeFormatter.ofPattern(\"yyyy-MM-dd\"); (param1 == null) ? null : param2.format(param1)" | } | } | }, @@ -1378,7 +1378,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "field": "createdAt", | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (param1 == null) ? null : ZonedDateTime.parse(param1, DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss.SSS XXX')).truncatedTo(ChronoUnit.MINUTES).get(ChronoField.YEAR)" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (param1 == null) ? null : ZonedDateTime.parse(param1, DateTimeFormatter.ofPattern(\"yyyy-MM-dd HH:mm:ss.SSS XXX\")).truncatedTo(ChronoUnit.MINUTES).get(ChronoField.YEAR)" | } | } | } @@ -1430,7 +1430,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "lastSeen": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss XXX'); (param1 == null) ? null : param2.format(param1)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern(\"yyyy-MM-dd HH:mm:ss XXX\"); (param1 == null) ? null : param2.format(param1)" | } | } | }, @@ -1526,7 +1526,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "max": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('updatedAt') || doc['updatedAt'].empty ? null : doc['updatedAt'].value); def param2 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param3 = (param2 == null) ? null : ZonedDateTime.parse(param2, DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss.SSS XXX')); (param1 == null || param2 == null) ? null : ChronoUnit.DAYS.between(param1, param3)" + | "source": "def param1 = (!doc.containsKey('updatedAt') || doc['updatedAt'].empty ? null : doc['updatedAt'].value); def param2 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); def param3 = (param2 == null) ? null : ZonedDateTime.parse(param2, DateTimeFormatter.ofPattern(\"yyyy-MM-dd HH:mm:ss.SSS XXX\")); (param1 == null || param2 == null) ? null : ChronoUnit.DAYS.between(param1, param3)" | } | } | } @@ -1962,7 +1962,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.toLocalDate()); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern('yyyy-MM-dd')).minus(2, ChronoUnit.DAYS); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param3 != null ? param3 : param4" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.toLocalDate()); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern(\"yyyy-MM-dd\")).minus(2, ChronoUnit.DAYS); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param3 != null ? param3 : param4" | } | } | }, @@ -2020,7 +2020,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.toLocalDate()); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(2, ChronoUnit.HOURS); try { param3 != null ? param3 : param4 } catch (Exception e) { return null; }" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.toLocalDate()); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern(\"yyyy-MM-dd\")); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(2, ChronoUnit.HOURS); try { param3 != null ? param3 : param4 } catch (Exception e) { return null; }" | } | }, | "c2": { @@ -2044,7 +2044,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c5": { | "script": { | "lang": "painless", - | "source": "def param1 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); param1" + | "source": "def param1 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern(\"yyyy-MM-dd\")); param1" | } | } | }, @@ -2104,7 +2104,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = ZonedDateTime.now(ZoneId.of('Z')).minus(7, ChronoUnit.DAYS); def param3 = param1 == null ? false : (param1 > param2); def param4 = (!doc.containsKey('lastSeen') || doc['lastSeen'].empty ? null : doc['lastSeen'].value.plus(2, ChronoUnit.DAYS)); def param5 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param3 ? param1 : param4 != null ? param4 : param5" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = ZonedDateTime.now(ZoneId.of('Z')).minus(7, ChronoUnit.DAYS); def param3 = param1 == null ? false : (param1.isAfter(param2)); def param4 = (!doc.containsKey('lastSeen') || doc['lastSeen'].empty ? null : doc['lastSeen'].value.plus(2, ChronoUnit.DAYS)); def param5 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param3 ? param1 : param4 != null ? param4 : param5" | } | } | }, @@ -3212,7 +3212,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "script": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); param1 == null ? false : (param1 >= param2.withDayOfMonth(param2.lengthOfMonth()))" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.toLocalDate()); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern(\"yyyy-MM-dd\")); param1 == null ? false : (param1.isBefore(param2.withDayOfMonth(param2.lengthOfMonth())) == false)" | } | } | }, @@ -3302,7 +3302,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "script": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('comments.replies.lastUpdated') || doc['comments.replies.lastUpdated'].empty ? null : doc['comments.replies.lastUpdated'].value); def param2 = LocalDate.parse(\"2025-09-10\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); param1 == null ? false : (param1 < param2.withDayOfMonth(param2.lengthOfMonth()))" + | "source": "def param1 = (!doc.containsKey('comments.replies.lastUpdated') || doc['comments.replies.lastUpdated'].empty ? null : doc['comments.replies.lastUpdated'].value.toLocalDate()); def param2 = LocalDate.parse(\"2025-09-10\", DateTimeFormatter.ofPattern(\"yyyy-MM-dd\")); param1 == null ? false : (param1.isBefore(param2.withDayOfMonth(param2.lengthOfMonth())))" | } | } | } @@ -3400,7 +3400,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "script": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('replies.lastUpdated') || doc['replies.lastUpdated'].empty ? null : doc['replies.lastUpdated'].value); def param2 = LocalDate.parse(\"2025-09-10\", DateTimeFormatter.ofPattern('yyyy-MM-dd')); param1 == null ? false : (param1 < param2.withDayOfMonth(param2.lengthOfMonth()))" + | "source": "def param1 = (!doc.containsKey('replies.lastUpdated') || doc['replies.lastUpdated'].empty ? null : doc['replies.lastUpdated'].value.toLocalDate()); def param2 = LocalDate.parse(\"2025-09-10\", DateTimeFormatter.ofPattern(\"yyyy-MM-dd\")); param1 == null ? false : (param1.isBefore(param2.withDayOfMonth(param2.lengthOfMonth())))" | } | } | }, @@ -3507,7 +3507,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "script": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param1 == null ? false : (param1 < param2)" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param1 == null ? false : (param1.isBefore(param2))" | } | } | } diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala index 1958c000..de54c6bc 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala @@ -204,6 +204,21 @@ package object function { nullable = a.nullable, context ) + a match { + case identifier: Identifier => + identifier.baseType match { + case SQLTypes.Any => // in painless context, Any is ZonedDateTime + targetedType match { + case SQLTypes.Date => + identifier.addPainlessMethod(".toLocalDate()") + case SQLTypes.Time => + identifier.addPainlessMethod(".toLocalTime()") + case _ => + } + case _ => + } + case _ => + } if (ret.startsWith(".")) { // apply methods ctx.find(paramName) match { diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala index d38f0d89..c9177885 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala @@ -449,7 +449,7 @@ package object time { def includeTimeZone: Boolean = false - protected def param: String = s"DateTimeFormatter.ofPattern('${convert()}')" + protected def param: String = s"DateTimeFormatter.ofPattern(\"${convert()}\")" val sqlToJava: Map[String, String] = Map( "%Y" -> "yyyy", diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/Where.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/Where.scala index fbddc6e5..87df83a4 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/Where.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/Where.scala @@ -16,7 +16,7 @@ package app.softnetwork.elastic.sql.query -import app.softnetwork.elastic.sql.`type`.{SQLAny, SQLType, SQLTypeUtils, SQLTypes} +import app.softnetwork.elastic.sql.`type`.{SQLAny, SQLTemporal, SQLType, SQLTypeUtils, SQLTypes} import app.softnetwork.elastic.sql.function._ import app.softnetwork.elastic.sql.function.cond.{ConditionalFunction, IsNotNull, IsNull} import app.softnetwork.elastic.sql.function.geo.Distance @@ -392,29 +392,93 @@ sealed trait Expression extends FunctionChain with ElasticFilter with Criteria { SQLTypeUtils.coerce(identifier, targetedType, context) } - protected def check(context: Option[PainlessContext]): String = + protected def check(context: Option[PainlessContext], param: String): String = { operator match { - case _: ComparisonOperator => s" $painlessOp ${painlessValue(context)}" - case _ => s"$painlessOp(${painlessValue(context)})" + case comparison: ComparisonOperator => + comparison match { + case LT => + maybeValue.map(v => v.out).getOrElse(SQLTypes.Any) match { + case SQLTypes.Varchar => + return s"$param.compareTo(${painlessValue(context)}) < 0" + case _: SQLTemporal if !aggregation && !hasBucket => + return s"$param.isBefore(${painlessValue(context)})" + case _ => + } + case GT => + maybeValue.map(v => v.out).getOrElse(SQLTypes.Any) match { + case SQLTypes.Varchar => + return s"$param.compareTo(${painlessValue(context)}) > 0" + case _: SQLTemporal if !aggregation && !hasBucket => + return s"$param.isAfter(${painlessValue(context)})" + case _ => + } + case EQ => + maybeValue.map(v => v.out).getOrElse(SQLTypes.Any) match { + case SQLTypes.Varchar => + return s"$param.compareTo(${painlessValue(context)}) == 0" + case _: SQLTemporal if !aggregation && !hasBucket => + return s"$param.isEqual(${painlessValue(context)})" + case _ => + } + case NE | DIFF => + maybeValue.map(v => v.out).getOrElse(SQLTypes.Any) match { + case SQLTypes.Varchar => + return s"$param.compareTo(${painlessValue(context)}) != 0" + case _: SQLTemporal if !aggregation && !hasBucket => + return s"$param.isEqual(${painlessValue(context)}) == false" + case _ => + } + case GE => + maybeValue.map(v => v.out).getOrElse(SQLTypes.Any) match { + case SQLTypes.Varchar => + return s"$param.compareTo(${painlessValue(context)}) >= 0" + case _: SQLTemporal if !aggregation && !hasBucket => + return s"$param.isBefore(${painlessValue(context)}) == false" + case _ => + } + case LE => + maybeValue.map(v => v.out).getOrElse(SQLTypes.Any) match { + case SQLTypes.Varchar => + return s"$param.compareTo(${painlessValue(context)}) <= 0" + case _: SQLTemporal if !aggregation && !hasBucket => + return s"$param.isAfter(${painlessValue(context)}) == false" + case _ => + } + case _ => + } + s"$param $painlessOp ${painlessValue(context)}" + case _ => s"$param$painlessOp(${painlessValue(context)})" } + } override def painless(context: Option[PainlessContext]): String = { context match { case Some(ctx) => ctx.addParam(identifier) match { case Some(p) => + identifier.baseType match { + case SQLTypes.Any => // in painless context, Any is ZonedDateTime + maybeValue.map(_.out).getOrElse(SQLTypes.Any) match { + case SQLTypes.Date => + identifier.addPainlessMethod(".toLocalDate()") + case SQLTypes.Time => + identifier.addPainlessMethod(".toLocalTime()") + case _ => + } + case _ => + } if (identifier.nullable) - return s"$p == null ? false : $painlessNot($p${check(context)})" + return s"$p == null ? false : $painlessNot(${check(context, p)})" else - return s"$painlessNot($p${check(context)})" + return s"$painlessNot(${check(context, p)})" case _ => } case _ => } if (identifier.nullable) { - return s"def left = ${left(context)}; left == null ? false : ${painlessNot}left${check(context)}" + return s"def left = ${left(context)}; left == null ? false : $painlessNot(${check(context, "left")})" } - s"$painlessNot${left(context)}${check(context)}" + s"$painlessNot${check(context, left(context))}" } override def validate(): Either[String, Unit] = { diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypeUtils.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypeUtils.scala index 0e017afe..36270022 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypeUtils.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypeUtils.scala @@ -174,26 +174,26 @@ object SQLTypeUtils { context match { case Some(ctx) => ctx.addParam( - LiteralParam(s"LocalDate.parse($expr, DateTimeFormatter.ofPattern('yyyy-MM-dd'))") + LiteralParam(s"LocalDate.parse($expr, DateTimeFormatter.ofPattern(\"yyyy-MM-dd\"))") ) match { case Some(p) => return p case None => // continue } case None => // continue } - s"LocalDate.parse($expr, DateTimeFormatter.ofPattern('yyyy-MM-dd'))" + s"LocalDate.parse($expr, DateTimeFormatter.ofPattern(\"yyyy-MM-dd\"))" case (SQLTypes.Varchar, SQLTypes.Time) => context match { case Some(ctx) => ctx.addParam( - LiteralParam(s"LocalTime.parse($expr, DateTimeFormatter.ofPattern('HH:mm:ss'))") + LiteralParam(s"LocalTime.parse($expr, DateTimeFormatter.ofPattern(\"HH:mm:ss\"))") ) match { case Some(p) => return p case None => // continue } case None => // continue } - s"LocalTime.parse($expr, DateTimeFormatter.ofPattern('HH:mm:ss'))" + s"LocalTime.parse($expr, DateTimeFormatter.ofPattern(\"HH:mm:ss\"))" case (SQLTypes.Varchar, SQLTypes.DateTime) => context match { case Some(ctx) => From 0aaaf718abd15327c7a01a8307b55f3d07994905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Manciot?= Date: Mon, 20 Oct 2025 18:17:03 +0200 Subject: [PATCH 19/21] fix painless script for coalesce, nullif, case functions --- .../elastic/sql/SQLQuerySpec.scala | 8 ++-- .../elastic/sql/SQLQuerySpec.scala | 8 ++-- .../elastic/sql/function/cond/package.scala | 39 +++++++++---------- .../sql/function/convert/package.scala | 26 ++++++++++++- .../elastic/sql/function/package.scala | 23 +---------- .../elastic/sql/function/time/package.scala | 12 ++++-- .../app/softnetwork/elastic/sql/package.scala | 6 +-- .../softnetwork/elastic/sql/query/Where.scala | 37 +++++++++++------- .../elastic/sql/type/SQLTypeUtils.scala | 21 +++++++++- 9 files changed, 108 insertions(+), 72 deletions(-) diff --git a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index 4a7aa563..fe7fb7be 100644 --- a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -2153,7 +2153,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(7, ChronoUnit.DAYS); def param2 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.minus(3, ChronoUnit.DAYS)); def param3 = (!doc.containsKey('lastSeen') || doc['lastSeen'].empty ? null : doc['lastSeen'].value.plus(2, ChronoUnit.DAYS)); def param4 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1 == param2 ? param2 : param1 == param3 ? param3 : param4" + | "source": "def param1 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(7, ChronoUnit.DAYS); def param2 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.toLocalDate().minus(3, ChronoUnit.DAYS)); def param3 = (!doc.containsKey('lastSeen') || doc['lastSeen'].empty ? null : doc['lastSeen'].value.toLocalDate().plus(2, ChronoUnit.DAYS)); def param4 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.toLocalDate()); param1 == param2 ? param2 : param1 == param3 ? param3 : param4" | } | } | }, @@ -2748,7 +2748,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "hire_date": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('hire_date') || doc['hire_date'].empty ? null : doc['hire_date'].value); param1" + | "source": "def param1 = (!doc.containsKey('hire_date') || doc['hire_date'].empty ? null : doc['hire_date'].value.toLocalDate()); param1" | } | } | }, @@ -2882,7 +2882,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "ld": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (param1 == null) ? null : param1.withDayOfMonth(param1.lengthOfMonth())" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.toLocalDate()); (param1 == null) ? null : param1.withDayOfMonth(param1.lengthOfMonth())" | } | } | }, @@ -3503,7 +3503,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "script": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param1 == null ? false : (param1.isBefore(param2))" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.toLocalDate()); def param2 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param1 == null ? false : (param1.isBefore(param2))" | } | } | } diff --git a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index 75e27243..37dd9c3d 100644 --- a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -2157,7 +2157,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(7, ChronoUnit.DAYS); def param2 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.minus(3, ChronoUnit.DAYS)); def param3 = (!doc.containsKey('lastSeen') || doc['lastSeen'].empty ? null : doc['lastSeen'].value.plus(2, ChronoUnit.DAYS)); def param4 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); param1 == param2 ? param2 : param1 == param3 ? param3 : param4" + | "source": "def param1 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(7, ChronoUnit.DAYS); def param2 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.toLocalDate().minus(3, ChronoUnit.DAYS)); def param3 = (!doc.containsKey('lastSeen') || doc['lastSeen'].empty ? null : doc['lastSeen'].value.toLocalDate().plus(2, ChronoUnit.DAYS)); def param4 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.toLocalDate()); param1 == param2 ? param2 : param1 == param3 ? param3 : param4" | } | } | }, @@ -2752,7 +2752,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "hire_date": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('hire_date') || doc['hire_date'].empty ? null : doc['hire_date'].value); param1" + | "source": "def param1 = (!doc.containsKey('hire_date') || doc['hire_date'].empty ? null : doc['hire_date'].value.toLocalDate()); param1" | } | } | }, @@ -2886,7 +2886,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "ld": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); (param1 == null) ? null : param1.withDayOfMonth(param1.lengthOfMonth())" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.toLocalDate()); (param1 == null) ? null : param1.withDayOfMonth(param1.lengthOfMonth())" | } | } | }, @@ -3507,7 +3507,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "script": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value); def param2 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param1 == null ? false : (param1.isBefore(param2))" + | "source": "def param1 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.toLocalDate()); def param2 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param1 == null ? false : (param1.isBefore(param2))" | } | } | } diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala index d354d774..d7d4fb66 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala @@ -105,7 +105,8 @@ package object cond { override def args: List[PainlessScript] = values - override def outputType: SQLType = SQLTypeUtils.leastCommonSuperType(args.map(_.baseType)) + override def outputType: SQLType = + baseType //SQLTypeUtils.leastCommonSuperType(args.map(_.baseType)) override def identifier: Identifier = Identifier() @@ -114,10 +115,9 @@ package object cond { override def sql: String = s"$Coalesce(${values.map(_.sql).mkString(", ")})" // Reprend l’idée de SQLValues mais pour n’importe quel token - override def baseType: SQLType = - SQLTypeUtils.leastCommonSuperType(values.map(_.baseType).distinct) + override def baseType: SQLType = SQLTypeUtils.leastCommonSuperType(argTypes) - override def applyType(in: SQLType): SQLType = out + override def applyType(in: SQLType): SQLType = baseType override def validate(): Either[String, Unit] = { if (values.isEmpty) Left("COALESCE requires at least one argument") @@ -151,14 +151,17 @@ package object cond { override def inputType: SQLAny = SQLTypes.Any - override def baseType: SQLType = expr1.out + override def baseType: SQLType = SQLTypeUtils.leastCommonSuperType(argTypes) - override def applyType(in: SQLType): SQLType = out + override def applyType(in: SQLType): SQLType = baseType - override def checkIfNullable: Boolean = expr1.nullable && (expr1 match { + private[this] def checkIfExpressionNullable(expr: PainlessScript): Boolean = expr match { case f: FunctionChain if f.functions.nonEmpty => true case _ => false - }) + } + + override def checkIfNullable: Boolean = + false //checkIfExpressionNullable(expr1) || checkIfExpressionNullable(expr2) override def toPainlessCall( callArgs: List[String], @@ -195,7 +198,9 @@ package object cond { conditions: List[(PainlessScript, PainlessScript)], default: Option[PainlessScript] ) extends TransformFunction[SQLAny, SQLAny] { - override def args: List[PainlessScript] = List.empty + override def args: List[PainlessScript] = expression.toList ++ + conditions.map { case (_, res) => res } ++ + default.toList override def inputType: SQLAny = SQLTypes.Any override def outputType: SQLAny = SQLTypes.Any @@ -210,9 +215,7 @@ package object cond { } override def baseType: SQLType = - SQLTypeUtils.leastCommonSuperType( - conditions.map(_._2.baseType) ++ default.map(_.baseType).toList - ) + SQLTypeUtils.leastCommonSuperType(argTypes) override def applyType(in: SQLType): SQLType = baseType @@ -237,7 +240,7 @@ package object cond { var cases = expression match { case Some(expr) => // case with expression to evaluate - val e = SQLTypeUtils.coerce(expr, expr.out, context) + val e = SQLTypeUtils.coerce(expr, out, context) val expParam = ctx.addParam( LiteralParam(e) ) @@ -253,16 +256,14 @@ package object cond { i.name case _ => "" } - val c = SQLTypeUtils.coerce(cond, expr.out, context) + val c = SQLTypeUtils.coerce(cond, out, context) val r = res match { case i: Identifier if i.name == name && name.nonEmpty => i.withNullable(false) SQLTypeUtils.coerce( - i.painless(context), - i.baseType, + i, out, - nullable = false, context ) case _ => @@ -302,10 +303,8 @@ package object cond { case i: Identifier if i.name == name && name.nonEmpty => i.withNullable(false) SQLTypeUtils.coerce( - i.painless(context), - i.baseType, + i, out, - nullable = false, context ) case _ => diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/convert/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/convert/package.scala index c909891c..8a71bcf4 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/convert/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/convert/package.scala @@ -20,11 +20,12 @@ import app.softnetwork.elastic.sql.{ Alias, DateMathRounding, Expr, + Identifier, PainlessContext, PainlessScript, TokenRegex } -import app.softnetwork.elastic.sql.`type`.{SQLType, SQLTypeUtils} +import app.softnetwork.elastic.sql.`type`.{SQLType, SQLTypeUtils, SQLTypes} package object convert { @@ -46,6 +47,29 @@ package object convert { SQLTypeUtils.coerce(value, targetType, context) override def toPainless(base: String, idx: Int, context: Option[PainlessContext]): String = { + context match { + case Some(ctx) => + value match { + case _: Identifier => + inputType match { + case SQLTypes.Any => + ctx.find(base) match { + case Some(identifier) => + outputType match { + case SQLTypes.Date => + identifier.addPainlessMethod(".toLocalDate()") + case SQLTypes.Time => + identifier.addPainlessMethod(".toLocalTime()") + case _ => // do nothing + } + case _ => // do nothing + } + case _ => // do nothing + } + case _ => // do nothing + } + case _ => // do nothing + } val ret = SQLTypeUtils.coerce(base, value.baseType, targetType, value.nullable, context) val bloc = ret.startsWith("{") && ret.endsWith("}") val retWithBrackets = if (bloc) ret else s"{ $ret }" diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala index de54c6bc..37e1b458 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala @@ -147,8 +147,6 @@ package object function { override def applyType(in: SQLType): SQLType = outputType - lazy val targetedType: SQLType = SQLTypeUtils.leastCommonSuperType(argTypes) - override def sql: String = s"${fun.map(_.sql).getOrElse("")}(${args.map(_.sql).mkString(argsSeparator)})" @@ -198,27 +196,10 @@ package object function { case chain: FunctionChain if chain.functions.nonEmpty => val ret = SQLTypeUtils .coerce( - a.painless(context), - a.baseType, + a, argTypes(i), - nullable = a.nullable, context ) - a match { - case identifier: Identifier => - identifier.baseType match { - case SQLTypes.Any => // in painless context, Any is ZonedDateTime - targetedType match { - case SQLTypes.Date => - identifier.addPainlessMethod(".toLocalDate()") - case SQLTypes.Time => - identifier.addPainlessMethod(".toLocalTime()") - case _ => - } - case _ => - } - case _ => - } if (ret.startsWith(".")) { // apply methods ctx.find(paramName) match { @@ -235,7 +216,7 @@ package object function { case identifier: Identifier => identifier.baseType match { case SQLTypes.Any => // in painless context, Any is ZonedDateTime - targetedType match { + out match { case SQLTypes.Date => identifier.addPainlessMethod(".toLocalDate()") case SQLTypes.Time => diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala index c9177885..89ac0ece 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala @@ -62,13 +62,17 @@ package object time { case _ => None } - private[this] var _out: SQLType = outputType + //private[this] var _out: SQLType = outputType - override def out: SQLType = _out + //override def out: SQLType = _out override def applyType(in: SQLType): SQLType = { - _out = interval.checkType(in).getOrElse(out) - _out + interval.checkType(in) match { + case Left(_) => baseType + case Right(_) => cast(in) + } + //_out = interval.checkType(in).getOrElse(out) + //_out } override def validate(): Either[String, Unit] = interval.checkType(out) match { diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala index b84a5acb..b02cc9b9 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/package.scala @@ -52,11 +52,11 @@ package object sql { def in: SQLType = baseType private[this] var _out: SQLType = SQLTypes.Null def out: SQLType = if (_out == SQLTypes.Null) baseType else _out - def out_=(t: SQLType): Unit = { + /*def out_=(t: SQLType): Unit = { _out = t - } + }*/ def cast(targetType: SQLType): SQLType = { - this.out = targetType + this._out = targetType this.out } def system: Boolean = false diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/Where.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/Where.scala index 87df83a4..3e11657b 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/Where.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/Where.scala @@ -389,6 +389,25 @@ sealed trait Expression extends FunctionChain with ElasticFilter with Criteria { case Some(v) => SQLTypeUtils.leastCommonSuperType(List(identifier.out, v.out)) case None => identifier.out } + context match { + case Some(ctx) => + ctx.addParam(identifier) match { + case Some(_) => + identifier.baseType match { + case SQLTypes.Any => // in painless context, Any is ZonedDateTime + maybeValue.map(_.out).getOrElse(SQLTypes.Any) match { + case SQLTypes.Date => + identifier.addPainlessMethod(".toLocalDate()") + case SQLTypes.Time => + identifier.addPainlessMethod(".toLocalTime()") + case _ => + } + case _ => + } + case _ => // do nothing + } + case _ => // do nothing + } SQLTypeUtils.coerce(identifier, targetedType, context) } @@ -452,21 +471,11 @@ sealed trait Expression extends FunctionChain with ElasticFilter with Criteria { } override def painless(context: Option[PainlessContext]): String = { + val innerLeft = left(context) context match { case Some(ctx) => - ctx.addParam(identifier) match { + ctx.get(identifier) match { case Some(p) => - identifier.baseType match { - case SQLTypes.Any => // in painless context, Any is ZonedDateTime - maybeValue.map(_.out).getOrElse(SQLTypes.Any) match { - case SQLTypes.Date => - identifier.addPainlessMethod(".toLocalDate()") - case SQLTypes.Time => - identifier.addPainlessMethod(".toLocalTime()") - case _ => - } - case _ => - } if (identifier.nullable) return s"$p == null ? false : $painlessNot(${check(context, p)})" else @@ -476,9 +485,9 @@ sealed trait Expression extends FunctionChain with ElasticFilter with Criteria { case _ => } if (identifier.nullable) { - return s"def left = ${left(context)}; left == null ? false : $painlessNot(${check(context, "left")})" + return s"def left = $innerLeft; left == null ? false : $painlessNot(${check(context, "left")})" } - s"$painlessNot${check(context, left(context))}" + s"$painlessNot${check(context, innerLeft)}" } override def validate(): Either[String, Unit] = { diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypeUtils.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypeUtils.scala index 36270022..0f580aa3 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypeUtils.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/type/SQLTypeUtils.scala @@ -16,7 +16,7 @@ package app.softnetwork.elastic.sql.`type` -import app.softnetwork.elastic.sql.{LiteralParam, PainlessContext, PainlessScript} +import app.softnetwork.elastic.sql.{Identifier, LiteralParam, PainlessContext, PainlessScript} import app.softnetwork.elastic.sql.`type`.SQLTypes._ object SQLTypeUtils { @@ -104,6 +104,25 @@ object SQLTypeUtils { } def coerce(in: PainlessScript, to: SQLType, context: Option[PainlessContext]): String = { + context match { + case Some(_) => + in match { + case identifier: Identifier => + identifier.baseType match { + case SQLTypes.Any => // in painless context, Any is ZonedDateTime + to match { + case SQLTypes.Date => + identifier.addPainlessMethod(".toLocalDate()") + case SQLTypes.Time => + identifier.addPainlessMethod(".toLocalTime()") + case _ => // do nothing + } + case _ => // do nothing + } + case _ => // do nothing + } + case _ => // do nothing + } val expr = in.painless(context) val from = in.baseType val nullable = in.nullable From 918dc7c5ddc47aafe18e43f184b54a8c4156a8a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Manciot?= Date: Mon, 20 Oct 2025 18:41:34 +0200 Subject: [PATCH 20/21] fix painless script for nullif function for temporal --- .../elastic/sql/SQLQuerySpec.scala | 4 +-- .../elastic/sql/SQLQuerySpec.scala | 4 +-- .../elastic/sql/function/cond/package.scala | 25 +++++++++++-------- .../elastic/sql/function/package.scala | 2 +- .../elastic/sql/function/time/package.scala | 6 ----- 5 files changed, 20 insertions(+), 21 deletions(-) diff --git a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index fe7fb7be..91acda85 100644 --- a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -1958,7 +1958,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.toLocalDate()); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern(\"yyyy-MM-dd\")).minus(2, ChronoUnit.DAYS); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param3 != null ? param3 : param4" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.toLocalDate()); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern(\"yyyy-MM-dd\")).minus(2, ChronoUnit.DAYS); def param3 = param1 == null || param1.isEqual(param2) ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param3 != null ? param3 : param4" | } | } | }, @@ -2016,7 +2016,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.toLocalDate()); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern(\"yyyy-MM-dd\")); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(2, ChronoUnit.HOURS); try { param3 != null ? param3 : param4 } catch (Exception e) { return null; }" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.toLocalDate()); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern(\"yyyy-MM-dd\")); def param3 = param1 == null || param1.isEqual(param2) ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(2, ChronoUnit.HOURS); try { param3 != null ? param3 : param4 } catch (Exception e) { return null; }" | } | }, | "c2": { diff --git a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index 37dd9c3d..c73b85f5 100644 --- a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -1962,7 +1962,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.toLocalDate()); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern(\"yyyy-MM-dd\")).minus(2, ChronoUnit.DAYS); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param3 != null ? param3 : param4" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.toLocalDate()); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern(\"yyyy-MM-dd\")).minus(2, ChronoUnit.DAYS); def param3 = param1 == null || param1.isEqual(param2) ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate(); param3 != null ? param3 : param4" | } | } | }, @@ -2020,7 +2020,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.toLocalDate()); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern(\"yyyy-MM-dd\")); def param3 = param1 == param2 ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(2, ChronoUnit.HOURS); try { param3 != null ? param3 : param4 } catch (Exception e) { return null; }" + | "source": "def param1 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.toLocalDate()); def param2 = LocalDate.parse(\"2025-09-11\", DateTimeFormatter.ofPattern(\"yyyy-MM-dd\")); def param3 = param1 == null || param1.isEqual(param2) ? null : param1; def param4 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(2, ChronoUnit.HOURS); try { param3 != null ? param3 : param4 } catch (Exception e) { return null; }" | } | }, | "c2": { diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala index d7d4fb66..a383ccbf 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala @@ -24,7 +24,14 @@ import app.softnetwork.elastic.sql.{ PainlessScript, TokenRegex } -import app.softnetwork.elastic.sql.`type`.{SQLAny, SQLBool, SQLType, SQLTypeUtils, SQLTypes} +import app.softnetwork.elastic.sql.`type`.{ + SQLAny, + SQLBool, + SQLTemporal, + SQLType, + SQLTypeUtils, + SQLTypes +} import app.softnetwork.elastic.sql.parser.Validator import app.softnetwork.elastic.sql.query.{CriteriaWithConditionalFunction, Expression} @@ -117,8 +124,6 @@ package object cond { // Reprend l’idée de SQLValues mais pour n’importe quel token override def baseType: SQLType = SQLTypeUtils.leastCommonSuperType(argTypes) - override def applyType(in: SQLType): SQLType = baseType - override def validate(): Either[String, Unit] = { if (values.isEmpty) Left("COALESCE requires at least one argument") else Right(()) @@ -153,8 +158,6 @@ package object cond { override def baseType: SQLType = SQLTypeUtils.leastCommonSuperType(argTypes) - override def applyType(in: SQLType): SQLType = baseType - private[this] def checkIfExpressionNullable(expr: PainlessScript): Boolean = expr match { case f: FunctionChain if f.functions.nonEmpty => true case _ => false @@ -170,7 +173,12 @@ package object cond { callArgs match { case List(arg0, arg1) => val expr = - s"${arg0.trim} == ${arg1.trim} ? null : $arg0" + out match { + case SQLTypes.Varchar => + s"$arg0 == null || $arg0.compareTo($arg1) == 0 ? null : $arg0" + case _: SQLTemporal => s"$arg0 == null || $arg0.isEqual($arg1) ? null : $arg0" + case _ => s"$arg0 == $arg1 ? null : $arg0" + } context match { case Some(ctx) => ctx.addParam(LiteralParam(expr)) match { @@ -214,10 +222,7 @@ package object cond { s"$exprPart $whenThen$elsePart $END" } - override def baseType: SQLType = - SQLTypeUtils.leastCommonSuperType(argTypes) - - override def applyType(in: SQLType): SQLType = baseType + override def baseType: SQLType = SQLTypeUtils.leastCommonSuperType(argTypes) override def validate(): Either[String, Unit] = { if (conditions.isEmpty) Left("CASE WHEN requires at least one condition") diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala index 37e1b458..972b9989 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala @@ -145,7 +145,7 @@ package object function { override def in: SQLType = inputType override def baseType: SQLType = outputType - override def applyType(in: SQLType): SQLType = outputType + override def applyType(in: SQLType): SQLType = baseType override def sql: String = s"${fun.map(_.sql).getOrElse("")}(${args.map(_.sql).mkString(argsSeparator)})" diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala index 89ac0ece..72da4a77 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/time/package.scala @@ -62,17 +62,11 @@ package object time { case _ => None } - //private[this] var _out: SQLType = outputType - - //override def out: SQLType = _out - override def applyType(in: SQLType): SQLType = { interval.checkType(in) match { case Left(_) => baseType case Right(_) => cast(in) } - //_out = interval.checkType(in).getOrElse(out) - //_out } override def validate(): Either[String, Unit] = interval.checkType(out) match { From 1f1a9e2ab9dc2f96e6ec0621d17677cdc90c6a76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Manciot?= Date: Mon, 20 Oct 2025 18:51:18 +0200 Subject: [PATCH 21/21] fix painless script for case function with temporal --- .../softnetwork/elastic/sql/SQLQuerySpec.scala | 2 +- .../softnetwork/elastic/sql/SQLQuerySpec.scala | 2 +- .../elastic/sql/function/cond/package.scala | 18 ++++++++++++++---- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index 91acda85..d1a4a4ee 100644 --- a/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -2153,7 +2153,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(7, ChronoUnit.DAYS); def param2 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.toLocalDate().minus(3, ChronoUnit.DAYS)); def param3 = (!doc.containsKey('lastSeen') || doc['lastSeen'].empty ? null : doc['lastSeen'].value.toLocalDate().plus(2, ChronoUnit.DAYS)); def param4 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.toLocalDate()); param1 == param2 ? param2 : param1 == param3 ? param3 : param4" + | "source": "def param1 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(7, ChronoUnit.DAYS); def param2 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.toLocalDate().minus(3, ChronoUnit.DAYS)); def param3 = (!doc.containsKey('lastSeen') || doc['lastSeen'].empty ? null : doc['lastSeen'].value.toLocalDate().plus(2, ChronoUnit.DAYS)); def param4 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.toLocalDate()); param1 != null && param1.isEqual(param2) ? param2 : param1 != null && param1.isEqual(param3) ? param3 : param4" | } | } | }, diff --git a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala index c73b85f5..3462d3a4 100644 --- a/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala +++ b/sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala @@ -2157,7 +2157,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers { | "c": { | "script": { | "lang": "painless", - | "source": "def param1 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(7, ChronoUnit.DAYS); def param2 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.toLocalDate().minus(3, ChronoUnit.DAYS)); def param3 = (!doc.containsKey('lastSeen') || doc['lastSeen'].empty ? null : doc['lastSeen'].value.toLocalDate().plus(2, ChronoUnit.DAYS)); def param4 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.toLocalDate()); param1 == param2 ? param2 : param1 == param3 ? param3 : param4" + | "source": "def param1 = ZonedDateTime.now(ZoneId.of('Z')).toLocalDate().minus(7, ChronoUnit.DAYS); def param2 = (!doc.containsKey('lastUpdated') || doc['lastUpdated'].empty ? null : doc['lastUpdated'].value.toLocalDate().minus(3, ChronoUnit.DAYS)); def param3 = (!doc.containsKey('lastSeen') || doc['lastSeen'].empty ? null : doc['lastSeen'].value.toLocalDate().plus(2, ChronoUnit.DAYS)); def param4 = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value.toLocalDate()); param1 != null && param1.isEqual(param2) ? param2 : param1 != null && param1.isEqual(param3) ? param3 : param4" | } | } | }, diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala index a383ccbf..9670a497 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/function/cond/package.scala @@ -239,6 +239,16 @@ package object cond { else Right(()) } + private[this] def checkCase(e: String, c: String, v: String): String = { + out match { + case SQLTypes.Varchar => + s"$e != null && $e.compareTo($c) == 0 ? $v" + case _: SQLTemporal => + s"$e != null && $e.isEqual($c) ? $v" + case _ => s"$e == $c ? $v" + } + } + override def painless(context: Option[PainlessContext] = None): String = { context match { case Some(ctx) => @@ -278,14 +288,14 @@ package object cond { case Some(e) => if (cond.nullable) { ctx.addParam(LiteralParam(c)) match { - case Some(c) => s"$e == $c ? $r" - case _ => s"$e == $c ? $r" + case Some(c) => checkCase(e, c, r) + case _ => checkCase(e, c, r) } } else { - s"$e == $c ? $r" + checkCase(e, c, r) } case _ => - s"$e == $c ? $r" + checkCase(e, c, r) } } .mkString(" : ")