Skip to content

Commit 450307c

Browse files
committed
finalize support for temporal between
1 parent ccd8b6a commit 450307c

File tree

6 files changed

+154
-21
lines changed

6 files changed

+154
-21
lines changed

documentation/operators.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,15 @@ FROM users
160160
WHERE age BETWEEN 18 AND 30;
161161
```
162162

163+
- Temporal BETWEEN
164+
```sql
165+
SELECT *
166+
FROM users
167+
WHERE createdAt BETWEEN CURRENT_DATE - INTERVAL 1 MONTH AND CURRENT_DATE
168+
AND
169+
lastUpdated BETWEEN LAST_DAY('2025-09-11'::DATE) AND DATE_TRUNC(CURRENT_TIMESTAMP, DAY)
170+
```
171+
163172
- Distance BETWEEN (using meters)
164173

165174
```sql

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

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3028,11 +3028,24 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers {
30283028
| }
30293029
| },
30303030
| {
3031-
| "range": {
3032-
| "lastUpdated": {
3033-
| "gte": "2025-09-11||/d",
3034-
| "lte": "now/d"
3035-
| }
3031+
| "bool": {
3032+
| "must": [
3033+
| {
3034+
| "script": {
3035+
| "script": {
3036+
| "lang": "painless",
3037+
| "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()))"
3038+
| }
3039+
| }
3040+
| },
3041+
| {
3042+
| "range": {
3043+
| "lastUpdated": {
3044+
| "lte": "now/d"
3045+
| }
3046+
| }
3047+
| }
3048+
| ]
30363049
| }
30373050
| }
30383051
| ]
@@ -3045,5 +3058,39 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers {
30453058
| }
30463059
|}""".stripMargin
30473060
.replaceAll("\\s+", "")
3061+
.replaceAll("\\s+", "")
3062+
.replaceAll("defv", " def v")
3063+
.replaceAll("defa", "def a")
3064+
.replaceAll("defe", "def e")
3065+
.replaceAll("defl", "def l")
3066+
.replaceAll("def_", "def _")
3067+
.replaceAll("=_", " = _")
3068+
.replaceAll(",_", ", _")
3069+
.replaceAll(",\\(", ", (")
3070+
.replaceAll("if\\(", "if (")
3071+
.replaceAll(">=", " >= ")
3072+
.replaceAll("=\\(", " = (")
3073+
.replaceAll(":\\(", " : (")
3074+
.replaceAll(",(\\d)", ", $1")
3075+
.replaceAll("\\?", " ? ")
3076+
.replaceAll(":null", " : null")
3077+
.replaceAll("null:", "null : ")
3078+
.replaceAll("return", " return ")
3079+
.replaceAll(";", "; ")
3080+
.replaceAll("; if", ";if")
3081+
.replaceAll("==", " == ")
3082+
.replaceAll("\\+", " + ")
3083+
.replaceAll(">(\\d)", " > $1")
3084+
.replaceAll("=(\\d)", "= $1")
3085+
.replaceAll("<", " < ")
3086+
.replaceAll("!=", " != ")
3087+
.replaceAll("&&", " && ")
3088+
.replaceAll("\\|\\|", " || ")
3089+
.replaceAll("(\\d)=", "$1 = ")
3090+
.replaceAll(",params", ", params")
3091+
.replaceAll("GeoPoint", " GeoPoint")
3092+
.replaceAll("lat,arg", "lat, arg")
3093+
.replaceAll("false:", "false : ")
3094+
.replaceAll("DateTimeFormatter", " DateTimeFormatter")
30483095
}
30493096
}

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

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2998,4 +2998,88 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers {
29982998
.replaceAll("lat,arg", "lat, arg")
29992999
}
30003000

3001+
it should "handle between with temporal" in {
3002+
val select: ElasticSearchRequest =
3003+
SQLQuery(betweenTemporal)
3004+
val query = select.query
3005+
println(query)
3006+
query shouldBe
3007+
"""{
3008+
| "query": {
3009+
| "bool": {
3010+
| "filter": [
3011+
| {
3012+
| "range": {
3013+
| "createdAt": {
3014+
| "gte": "now-1M/d",
3015+
| "lte": "now/d"
3016+
| }
3017+
| }
3018+
| },
3019+
| {
3020+
| "bool": {
3021+
| "must": [
3022+
| {
3023+
| "script": {
3024+
| "script": {
3025+
| "lang": "painless",
3026+
| "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()))"
3027+
| }
3028+
| }
3029+
| },
3030+
| {
3031+
| "range": {
3032+
| "lastUpdated": {
3033+
| "lte": "now/d"
3034+
| }
3035+
| }
3036+
| }
3037+
| ]
3038+
| }
3039+
| }
3040+
| ]
3041+
| }
3042+
| },
3043+
| "_source": {
3044+
| "includes": [
3045+
| "*"
3046+
| ]
3047+
| }
3048+
|}""".stripMargin
3049+
.replaceAll("\\s+", "")
3050+
.replaceAll("\\s+", "")
3051+
.replaceAll("defv", " def v")
3052+
.replaceAll("defa", "def a")
3053+
.replaceAll("defe", "def e")
3054+
.replaceAll("defl", "def l")
3055+
.replaceAll("def_", "def _")
3056+
.replaceAll("=_", " = _")
3057+
.replaceAll(",_", ", _")
3058+
.replaceAll(",\\(", ", (")
3059+
.replaceAll("if\\(", "if (")
3060+
.replaceAll(">=", " >= ")
3061+
.replaceAll("=\\(", " = (")
3062+
.replaceAll(":\\(", " : (")
3063+
.replaceAll(",(\\d)", ", $1")
3064+
.replaceAll("\\?", " ? ")
3065+
.replaceAll(":null", " : null")
3066+
.replaceAll("null:", "null : ")
3067+
.replaceAll("return", " return ")
3068+
.replaceAll(";", "; ")
3069+
.replaceAll("; if", ";if")
3070+
.replaceAll("==", " == ")
3071+
.replaceAll("\\+", " + ")
3072+
.replaceAll(">(\\d)", " > $1")
3073+
.replaceAll("=(\\d)", "= $1")
3074+
.replaceAll("<", " < ")
3075+
.replaceAll("!=", " != ")
3076+
.replaceAll("&&", " && ")
3077+
.replaceAll("\\|\\|", " || ")
3078+
.replaceAll("(\\d)=", "$1 = ")
3079+
.replaceAll(",params", ", params")
3080+
.replaceAll("GeoPoint", " GeoPoint")
3081+
.replaceAll("lat,arg", "lat, arg")
3082+
.replaceAll("false:", "false : ")
3083+
.replaceAll("DateTimeFormatter", " DateTimeFormatter")
3084+
}
30013085
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ package object time {
183183
}
184184

185185
override def roundingScript: Option[String] = unit.roundingScript
186+
187+
override def dateMathScript: Boolean = identifier.dateMathScript
186188
}
187189

188190
case object Extract extends Expr("EXTRACT") with TokenRegex with PainlessScript {

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,11 @@ package object time {
8686
case _ ~ _ ~ i ~ _ => LastDayOfMonth(i)
8787
}
8888

89-
def date_function: PackratParser[DateFunction] =
89+
def date_function: PackratParser[DateFunction with FunctionWithIdentifier] =
9090
date_add | date_sub | date_parse | date_format | last_day
9191

9292
def dateFunctionWithIdentifier: PackratParser[Identifier] =
93-
(date_parse | date_format | date_add | date_sub | last_day) ^^ (t =>
94-
t.identifier.withFunctions(t +: t.identifier.functions)
95-
)
93+
date_function ^^ (t => t.identifier.withFunctions(t +: t.identifier.functions))
9694

9795
}
9896

@@ -127,11 +125,11 @@ package object time {
127125
DateTimeFormat(i, f.value)
128126
}
129127

130-
def datetime_function: PackratParser[DateTimeFunction] =
128+
def datetime_function: PackratParser[DateTimeFunction with FunctionWithIdentifier] =
131129
datetime_add | datetime_sub | datetime_parse | datetime_format
132130

133131
def dateTimeFunctionWithIdentifier: PackratParser[Identifier] =
134-
(datetime_parse | datetime_format | datetime_add | datetime_sub) ^^ { t =>
132+
datetime_function ^^ { t =>
135133
t.identifier.withFunctions(t +: t.identifier.functions)
136134
}
137135

sql/src/test/scala/app/softnetwork/elastic/sql/SQLParserSpec.scala

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ object Queries {
178178
"SELECT ST_DISTANCE(POINT(-70.0, 40.0), toLocation) AS d1, ST_DISTANCE(fromLocation, POINT(-70.0, 40.0)) AS d2, ST_DISTANCE(POINT(-70.0, 40.0), POINT(0.0, 0.0)) AS d3 FROM Table WHERE ST_DISTANCE(POINT(-70.0, 40.0), toLocation) BETWEEN 4000 km AND 5000 km AND ST_DISTANCE(fromLocation, toLocation) < 2000 km AND ST_DISTANCE(POINT(-70.0, 40.0), POINT(-70.0, 40.0)) < 1000 km"
179179

180180
val betweenTemporal =
181-
"SELECT * FROM Table WHERE createdAt BETWEEN CURRENT_DATE - INTERVAL 1 MONTH AND CURRENT_DATE AND lastUpdated BETWEEN '2025-09-11'::DATE AND TODAY" //DATE_TRUNC(CURRENT_TIMESTAMP, DATE)"
181+
"SELECT * FROM Table WHERE createdAt BETWEEN CURRENT_DATE - INTERVAL 1 MONTH AND CURRENT_DATE AND lastUpdated BETWEEN LAST_DAY('2025-09-11'::DATE) AND DATE_TRUNC(CURRENT_TIMESTAMP, DAY)"
182182
}
183183

184184
/** Created by smanciot on 15/02/17.
@@ -195,13 +195,6 @@ class SQLParserSpec extends AnyFlatSpec with Matchers {
195195
.equalsIgnoreCase(numericalEq) shouldBe true
196196
}
197197

198-
it should "parse BETWEEN with temporal fields" in {
199-
val result = Parser(betweenTemporal)
200-
result.toOption
201-
.flatMap(_.left.toOption.map(_.sql))
202-
.getOrElse("") shouldBe betweenTemporal
203-
}
204-
205198
it should "parse numerical NE" in {
206199
val result = Parser(numericalNe)
207200
result.toOption
@@ -845,11 +838,11 @@ class SQLParserSpec extends AnyFlatSpec with Matchers {
845838
.getOrElse("") shouldBe geoDistance
846839
}
847840

848-
/*it should "parse BETWEEN with temporal fields" in {
841+
it should "parse BETWEEN with temporal fields" in {
849842
val result = Parser(betweenTemporal)
850843
result.toOption
851844
.flatMap(_.left.toOption.map(_.sql))
852845
.getOrElse("") shouldBe betweenTemporal
853-
}*/
846+
}
854847

855848
}

0 commit comments

Comments
 (0)