Skip to content

Commit ccd8b6a

Browse files
committed
add support for between with identifiers, support for rounding date math script, add generic support for date math script with identifier, add TokenValue and review FromTo trait, add support for applying multiple intervals
1 parent d5c8d70 commit ccd8b6a

File tree

29 files changed

+449
-188
lines changed

29 files changed

+449
-188
lines changed

es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticQuery.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ case class ElasticQuery(filter: ElasticFilter) {
6666
case isNull: IsNullExpr => isNull
6767
case isNotNull: IsNotNullExpr => isNotNull
6868
case in: InExpr[_, _] => in
69-
case between: BetweenExpr[_] => between
69+
case between: BetweenExpr => between
7070
// case geoDistance: DistanceCriteria => geoDistance
7171
case matchExpression: ElasticMatch => matchExpression
7272
case isNull: IsNullCriteria => isNull

es6/sql-bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ package object bridge {
366366
case i: Identifier =>
367367
operator match {
368368
case op: ComparisonOperator =>
369-
i.toScript match {
369+
i.script match {
370370
case Some(script) =>
371371
val o = if (maybeNot.isDefined) op.not else op
372372
o match {
@@ -435,7 +435,7 @@ package object bridge {
435435
}
436436

437437
implicit def betweenToQuery(
438-
between: BetweenExpr[_]
438+
between: BetweenExpr
439439
): Query = {
440440
import between._
441441
// Geo distance special case
@@ -480,12 +480,38 @@ package object bridge {
480480
fromTo.to.value
481481
)
482482
case _: SQLTemporal =>
483-
// TODO
484-
throw new IllegalArgumentException(
485-
"Range queries on temporal values are not supported yet"
486-
)
487-
case other =>
488-
throw new IllegalArgumentException(s"Unsupported type for range query: $other")
483+
fromTo match {
484+
case ft: IdentifierFromTo =>
485+
(ft.from.script, ft.to.script) match {
486+
case (Some(from), Some(to)) =>
487+
rangeQuery(identifier.name) gte from lte to
488+
case (Some(from), None) =>
489+
val fq = rangeQuery(identifier.name) gte from
490+
val tq = GenericExpression(identifier, LE, ft.to, None)
491+
maybeNot match {
492+
case Some(_) => return not(fq, tq)
493+
case _ => return must(fq, tq)
494+
}
495+
case (None, Some(to)) =>
496+
val fq = GenericExpression(identifier, GE, ft.from, None)
497+
val tq = rangeQuery(identifier.name) lte to
498+
maybeNot match {
499+
case Some(_) => return not(fq, tq)
500+
case _ => return must(fq, tq)
501+
}
502+
case _ =>
503+
val fq = GenericExpression(identifier, GE, ft.from, None)
504+
val tq = GenericExpression(identifier, LE, ft.to, None)
505+
maybeNot match {
506+
case Some(_) => return not(fq, tq)
507+
case _ => return must(fq, tq)
508+
}
509+
}
510+
case other =>
511+
throw new IllegalArgumentException(s"Unsupported type for range query: $other")
512+
}
513+
case _ =>
514+
throw new IllegalArgumentException(s"Unsupported out type for range query: ${fromTo.out}")
489515
}
490516
maybeNot match {
491517
case Some(_) => not(r)

es6/sql-bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -989,18 +989,16 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers {
989989
| "bool": {
990990
| "filter": [
991991
| {
992-
| "script": {
993-
| "script": {
994-
| "lang": "painless",
995-
| "source": "def left = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); left == null ? false : left < ZonedDateTime.now(ZoneId.of('Z')).toLocalTime()"
992+
| "range": {
993+
| "createdAt": {
994+
| "lt": "now/s"
996995
| }
997996
| }
998997
| },
999998
| {
1000-
| "script": {
1001-
| "script": {
1002-
| "lang": "painless",
1003-
| "source": "def left = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); left == null ? false : left >= ZonedDateTime.now(ZoneId.of('Z')).toLocalTime().minus(10, ChronoUnit.MINUTES)"
999+
| "range": {
1000+
| "createdAt": {
1001+
| "gte": "now-10m/s"
10041002
| }
10051003
| }
10061004
| }
@@ -3011,4 +3009,41 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers {
30113009
.replaceAll("lat,arg", "lat, arg")
30123010
}
30133011

3012+
it should "handle between with temporal" in {
3013+
val select: ElasticSearchRequest =
3014+
SQLQuery(betweenTemporal)
3015+
val query = select.query
3016+
println(query)
3017+
query shouldBe
3018+
"""{
3019+
| "query": {
3020+
| "bool": {
3021+
| "filter": [
3022+
| {
3023+
| "range": {
3024+
| "createdAt": {
3025+
| "gte": "now-1M/d",
3026+
| "lte": "now/d"
3027+
| }
3028+
| }
3029+
| },
3030+
| {
3031+
| "range": {
3032+
| "lastUpdated": {
3033+
| "gte": "2025-09-11||/d",
3034+
| "lte": "now/d"
3035+
| }
3036+
| }
3037+
| }
3038+
| ]
3039+
| }
3040+
| },
3041+
| "_source": {
3042+
| "includes": [
3043+
| "*"
3044+
| ]
3045+
| }
3046+
|}""".stripMargin
3047+
.replaceAll("\\s+", "")
3048+
}
30143049
}

sql/bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/ElasticQuery.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ case class ElasticQuery(filter: ElasticFilter) {
6666
case isNull: IsNullExpr => isNull
6767
case isNotNull: IsNotNullExpr => isNotNull
6868
case in: InExpr[_, _] => in
69-
case between: BetweenExpr[_] => between
69+
case between: BetweenExpr => between
7070
// case geoDistance: DistanceCriteria => geoDistance
7171
case matchExpression: ElasticMatch => matchExpression
7272
case isNull: IsNullCriteria => isNull

sql/bridge/src/main/scala/app/softnetwork/elastic/sql/bridge/package.scala

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,9 @@ package object bridge {
111111
_search scriptfields scriptFields.map { field =>
112112
scriptField(
113113
field.scriptName,
114-
Script(script = field.painless).lang("painless").scriptType("source")
114+
Script(script = field.painless)
115+
.lang("painless")
116+
.scriptType("source")
115117
.params(field.identifier.functions.headOption match {
116118
case Some(f: PainlessParams) => f.params
117119
case _ => Map.empty[String, Any]
@@ -366,7 +368,7 @@ package object bridge {
366368
case i: Identifier =>
367369
operator match {
368370
case op: ComparisonOperator =>
369-
i.toScript match {
371+
i.script match {
370372
case Some(script) =>
371373
val o = if (maybeNot.isDefined) op.not else op
372374
o match {
@@ -435,7 +437,7 @@ package object bridge {
435437
}
436438

437439
implicit def betweenToQuery(
438-
between: BetweenExpr[_]
440+
between: BetweenExpr
439441
): Query = {
440442
import between._
441443
// Geo distance special case
@@ -480,10 +482,38 @@ package object bridge {
480482
fromTo.to.value
481483
)
482484
case _: SQLTemporal =>
483-
// TODO
484-
throw new IllegalArgumentException("Range queries on temporal values are not supported yet")
485-
case other =>
486-
throw new IllegalArgumentException(s"Unsupported type for range query: $other")
485+
fromTo match {
486+
case ft: IdentifierFromTo =>
487+
(ft.from.script, ft.to.script) match {
488+
case (Some(from), Some(to)) =>
489+
rangeQuery(identifier.name) gte from lte to
490+
case (Some(from), None) =>
491+
val fq = rangeQuery(identifier.name) gte from
492+
val tq = GenericExpression(identifier, LE, ft.to, None)
493+
maybeNot match {
494+
case Some(_) => return not(fq, tq)
495+
case _ => return must(fq, tq)
496+
}
497+
case (None, Some(to)) =>
498+
val fq = GenericExpression(identifier, GE, ft.from, None)
499+
val tq = rangeQuery(identifier.name) lte to
500+
maybeNot match {
501+
case Some(_) => return not(fq, tq)
502+
case _ => return must(fq, tq)
503+
}
504+
case _ =>
505+
val fq = GenericExpression(identifier, GE, ft.from, None)
506+
val tq = GenericExpression(identifier, LE, ft.to, None)
507+
maybeNot match {
508+
case Some(_) => return not(fq, tq)
509+
case _ => return must(fq, tq)
510+
}
511+
}
512+
case other =>
513+
throw new IllegalArgumentException(s"Unsupported type for range query: $other")
514+
}
515+
case _ =>
516+
throw new IllegalArgumentException(s"Unsupported out type for range query: ${fromTo.out}")
487517
}
488518
maybeNot match {
489519
case Some(_) => not(r)

sql/bridge/src/test/scala/app/softnetwork/elastic/sql/SQLQuerySpec.scala

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -986,18 +986,16 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers {
986986
| "bool": {
987987
| "filter": [
988988
| {
989-
| "script": {
990-
| "script": {
991-
| "lang": "painless",
992-
| "source": "def left = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); left == null ? false : left < ZonedDateTime.now(ZoneId.of('Z')).toLocalTime()"
989+
| "range": {
990+
| "createdAt": {
991+
| "lt": "now/s"
993992
| }
994993
| }
995994
| },
996995
| {
997-
| "script": {
998-
| "script": {
999-
| "lang": "painless",
1000-
| "source": "def left = (!doc.containsKey('createdAt') || doc['createdAt'].empty ? null : doc['createdAt'].value); left == null ? false : left >= ZonedDateTime.now(ZoneId.of('Z')).toLocalTime().minus(10, ChronoUnit.MINUTES)"
996+
| "range": {
997+
| "createdAt": {
998+
| "gte": "now-10m/s"
1001999
| }
10021000
| }
10031001
| }

sql/src/main/scala/app/softnetwork/elastic/sql/function/convert/package.scala

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package app.softnetwork.elastic.sql.function
22

3-
import app.softnetwork.elastic.sql.{Alias, Expr, PainlessScript, TokenRegex}
3+
import app.softnetwork.elastic.sql.{Alias, DateMathRounding, Expr, PainlessScript, TokenRegex}
44
import app.softnetwork.elastic.sql.`type`.{SQLType, SQLTypeUtils}
55

66
package object convert {
77

8-
sealed trait Conversion extends TransformFunction[SQLType, SQLType] {
8+
sealed trait Conversion extends TransformFunction[SQLType, SQLType] with DateMathRounding {
99
override def toSQL(base: String): String = sql
1010

1111
def value: PainlessScript
@@ -28,6 +28,10 @@ package object convert {
2828
if (safe) s"try $retWithBrackets catch (Exception e) { return null; }"
2929
else ret
3030
}
31+
32+
override def roundingScript: Option[String] = DateMathRounding(targetType)
33+
34+
override def dateMathScript: Boolean = isTemporal
3135
}
3236

3337
case object Cast extends Expr("CAST") with TokenRegex

sql/src/main/scala/app/softnetwork/elastic/sql/function/package.scala

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package app.softnetwork.elastic.sql
22

3-
import app.softnetwork.elastic.sql.`type`.{SQLType, SQLTypeUtils, SQLTypes}
3+
import app.softnetwork.elastic.sql.`type`.{SQLType, SQLTypeUtils}
44
import app.softnetwork.elastic.sql.function.aggregate.AggregateFunction
55
import app.softnetwork.elastic.sql.operator.math.ArithmeticExpression
66
import app.softnetwork.elastic.sql.parser.Validator
@@ -22,7 +22,7 @@ package object function {
2222
def identifier: Identifier
2323
}
2424

25-
trait FunctionWithValue[+T] extends Function {
25+
trait FunctionWithValue[+T] extends Function with TokenValue {
2626
def value: T
2727
}
2828

@@ -60,23 +60,6 @@ package object function {
6060
fun.toSQL(expr)
6161
})
6262

63-
def toScript: Option[String] = {
64-
val orderedFunctions = FunctionUtils.transformFunctions(this).reverse
65-
orderedFunctions.foldLeft(Option("")) {
66-
case (expr, f: DateMathScript) if expr.isDefined => Option(s"${expr.get}${f.script}")
67-
case (_, _) => None // ignore non math scripts
68-
} match {
69-
case Some(s) if s.nonEmpty =>
70-
out match {
71-
case SQLTypes.Date => Some(s"$s/d")
72-
case _ => Some(s)
73-
}
74-
case _ => None
75-
}
76-
}
77-
78-
override def dateMathScript: Boolean = toScript.isDefined
79-
8063
override def system: Boolean = functions.lastOption.exists(_.system)
8164

8265
def applyTo(expr: Token): Unit = {

0 commit comments

Comments
 (0)