Skip to content

Commit 0cf72b3

Browse files
committed
add support for predicate with distinct nested trees
1 parent 7abc0fa commit 0cf72b3

File tree

8 files changed

+324
-32
lines changed

8 files changed

+324
-32
lines changed

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

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

3+
import app.softnetwork.elastic.sql.operator.AND
34
import app.softnetwork.elastic.sql.query.{
45
BetweenExpr,
56
ElasticBoolQuery,
@@ -15,7 +16,8 @@ import app.softnetwork.elastic.sql.query.{
1516
IsNullCriteria,
1617
IsNullExpr,
1718
NestedElement,
18-
NestedElements
19+
NestedElements,
20+
Predicate
1921
}
2022
import com.sksamuel.elastic4s.ElasticApi._
2123
import com.sksamuel.elastic4s.FetchSourceContext
@@ -42,15 +44,10 @@ case class ElasticQuery(filter: ElasticFilter) {
4244
if (innerHitsNames.contains(innerHitsName.getOrElse(""))) {
4345
criteria.asFilter(currentQuery).query(innerHitsNames, currentQuery)
4446
} else {
45-
val boolQuery = Option(ElasticBoolQuery(group = true))
46-
val q = criteria
47-
.asFilter(boolQuery)
48-
.query(innerHitsNames + innerHitsName.getOrElse(""), boolQuery)
49-
5047
NestedElements.buildNestedTrees(criteria.nestedElements) match {
5148
case Nil =>
5249
matchAllQuery()
53-
case nestedElements =>
50+
case nestedTrees =>
5451
def nestedInner(n: NestedElement): InnerHit = {
5552
var inner = innerHits(n.innerHitsName)
5653
n.size match {
@@ -69,10 +66,10 @@ case class ElasticQuery(filter: ElasticFilter) {
6966
inner
7067
}
7168

72-
def buildNestedQuery(n: NestedElement): Query = {
69+
def buildNestedQuery(n: NestedElement, q: Query): Query = {
7370
val children = n.children
7471
if (children.nonEmpty) {
75-
val innerQueries = children.map(buildNestedQuery)
72+
val innerQueries = children.map(child => buildNestedQuery(child, q))
7673
val combinedQuery = if (innerQueries.size == 1) {
7774
innerQueries.head
7875
} else {
@@ -96,11 +93,41 @@ case class ElasticQuery(filter: ElasticFilter) {
9693
}
9794
}
9895

99-
if (nestedElements.size == 1) {
100-
buildNestedQuery(nestedElements.head)
101-
} else {
102-
val innerQueries = nestedElements.map(buildNestedQuery)
103-
must(innerQueries)
96+
criteria match {
97+
case p: Predicate if nestedTrees.size > 1 =>
98+
val leftNested = ElasticNested(p.leftCriteria, p.leftCriteria.limit)
99+
val leftBoolQuery = Option(ElasticBoolQuery(group = true))
100+
val leftQuery = ElasticQuery(leftNested)
101+
.query(innerHitsNames /*++ leftNested.innerHitsName.toSet*/, leftBoolQuery)
102+
103+
val rightNested = ElasticNested(p.rightCriteria, p.rightCriteria.limit)
104+
val rightBoolQuery = Option(ElasticBoolQuery(group = true))
105+
val rightQuery = ElasticQuery(rightNested)
106+
.query(innerHitsNames /*++ rightNested.innerHitsName.toSet*/, rightBoolQuery)
107+
108+
p.operator match {
109+
case AND =>
110+
p.not match {
111+
case Some(_) => not(rightQuery).filter(leftQuery)
112+
case _ => must(leftQuery, rightQuery)
113+
}
114+
case _ =>
115+
p.not match {
116+
case Some(_) => not(rightQuery).should(leftQuery)
117+
case _ => should(leftQuery, rightQuery)
118+
}
119+
}
120+
case _ =>
121+
val boolQuery = Option(ElasticBoolQuery(group = true))
122+
val q = criteria
123+
.asFilter(boolQuery)
124+
.query(innerHitsNames + innerHitsName.getOrElse(""), boolQuery)
125+
if (nestedTrees.size == 1) {
126+
buildNestedQuery(nestedTrees.head, q)
127+
} else {
128+
val innerQueries = nestedTrees.map(nested => buildNestedQuery(nested, q))
129+
must(innerQueries)
130+
}
104131
}
105132
}
106133
}

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

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3207,4 +3207,114 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers {
32073207
.replaceAll("DateTimeFormatter", " DateTimeFormatter")
32083208
}
32093209

3210+
it should "handle predicate with distinct nested" in {
3211+
val select: ElasticSearchRequest =
3212+
SQLQuery(predicateWithDistinctNested)
3213+
val query = select.query
3214+
println(query)
3215+
query shouldBe
3216+
"""{
3217+
| "query": {
3218+
| "bool": {
3219+
| "filter": [
3220+
| {
3221+
| "bool": {
3222+
| "must_not": [
3223+
| {
3224+
| "nested": {
3225+
| "path": "replies",
3226+
| "query": {
3227+
| "script": {
3228+
| "script": {
3229+
| "lang": "painless",
3230+
| "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()))"
3231+
| }
3232+
| }
3233+
| },
3234+
| "inner_hits": {
3235+
| "name": "matched_replies",
3236+
| "from": 0,
3237+
| "_source": {
3238+
| "includes": [
3239+
| "reply_author",
3240+
| "reply_text"
3241+
| ]
3242+
| },
3243+
| "size": 5
3244+
| }
3245+
| }
3246+
| }
3247+
| ],
3248+
| "filter": [
3249+
| {
3250+
| "nested": {
3251+
| "path": "comments",
3252+
| "query": {
3253+
| "match": {
3254+
| "comments.content": {
3255+
| "query": "Nice"
3256+
| }
3257+
| }
3258+
| },
3259+
| "inner_hits": {
3260+
| "name": "matched_comments",
3261+
| "from": 0,
3262+
| "_source": {
3263+
| "includes": [
3264+
| "author",
3265+
| "comments"
3266+
| ]
3267+
| },
3268+
| "size": 5
3269+
| }
3270+
| }
3271+
| }
3272+
| ]
3273+
| }
3274+
| }
3275+
| ]
3276+
| }
3277+
| },
3278+
| "from": 0,
3279+
| "size": 5,
3280+
| "_source": true
3281+
|}""".stripMargin
3282+
.replaceAll("\\s+", "")
3283+
.replaceAll("\\s+", "")
3284+
.replaceAll("\\s+", "")
3285+
.replaceAll("defv", " def v")
3286+
.replaceAll("defa", "def a")
3287+
.replaceAll("defe", "def e")
3288+
.replaceAll("defl", "def l")
3289+
.replaceAll("def_", "def _")
3290+
.replaceAll("=_", " = _")
3291+
.replaceAll(",_", ", _")
3292+
.replaceAll(",\\(", ", (")
3293+
.replaceAll("if\\(", "if (")
3294+
.replaceAll(">=", " >= ")
3295+
.replaceAll("=\\(", " = (")
3296+
.replaceAll(":\\(", " : (")
3297+
.replaceAll(",(\\d)", ", $1")
3298+
.replaceAll("\\?", " ? ")
3299+
.replaceAll(":null", " : null")
3300+
.replaceAll("null:", "null : ")
3301+
.replaceAll("return", " return ")
3302+
.replaceAll(";", "; ")
3303+
.replaceAll("; if", ";if")
3304+
.replaceAll("==", " == ")
3305+
.replaceAll("\\+", " + ")
3306+
.replaceAll(">(\\d)", " > $1")
3307+
.replaceAll("=(\\d)", "= $1")
3308+
.replaceAll("<", " < ")
3309+
.replaceAll("!=", " != ")
3310+
.replaceAll("&&", " && ")
3311+
.replaceAll("\\|\\|", " || ")
3312+
.replaceAll("(\\d)=", "$1 = ")
3313+
.replaceAll(",params", ", params")
3314+
.replaceAll("GeoPoint", " GeoPoint")
3315+
.replaceAll("lat,arg", "lat, arg")
3316+
.replaceAll("false:", "false : ")
3317+
.replaceAll("DateTimeFormatter", " DateTimeFormatter")
3318+
}
3319+
32103320
}

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

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

3+
import app.softnetwork.elastic.sql.operator.AND
34
import app.softnetwork.elastic.sql.query.{
45
BetweenExpr,
56
ElasticBoolQuery,
@@ -15,7 +16,8 @@ import app.softnetwork.elastic.sql.query.{
1516
IsNullCriteria,
1617
IsNullExpr,
1718
NestedElement,
18-
NestedElements
19+
NestedElements,
20+
Predicate
1921
}
2022
import com.sksamuel.elastic4s.ElasticApi._
2123
import com.sksamuel.elastic4s.requests.common.FetchSourceContext
@@ -50,7 +52,7 @@ case class ElasticQuery(filter: ElasticFilter) {
5052
NestedElements.buildNestedTrees(criteria.nestedElements) match {
5153
case Nil =>
5254
matchAllQuery()
53-
case nestedElements =>
55+
case nestedTrees =>
5456
def nestedInner(n: NestedElement): InnerHit = {
5557
var inner = innerHits(n.innerHitsName)
5658
n.size match {
@@ -69,10 +71,10 @@ case class ElasticQuery(filter: ElasticFilter) {
6971
inner
7072
}
7173

72-
def buildNestedQuery(n: NestedElement): Query = {
74+
def buildNestedQuery(n: NestedElement, q: Query): Query = {
7375
val children = n.children
7476
if (children.nonEmpty) {
75-
val innerQueries = children.map(buildNestedQuery)
77+
val innerQueries = children.map(child => buildNestedQuery(child, q))
7678
val combinedQuery = if (innerQueries.size == 1) {
7779
innerQueries.head
7880
} else {
@@ -96,11 +98,41 @@ case class ElasticQuery(filter: ElasticFilter) {
9698
}
9799
}
98100

99-
if(nestedElements.size == 1){
100-
buildNestedQuery(nestedElements.head)
101-
} else {
102-
val innerQueries = nestedElements.map(buildNestedQuery)
103-
must(innerQueries)
101+
criteria match {
102+
case p: Predicate if nestedTrees.size > 1 =>
103+
val leftNested = ElasticNested(p.leftCriteria, p.leftCriteria.limit)
104+
val leftBoolQuery = Option(ElasticBoolQuery(group = true))
105+
val leftQuery = ElasticQuery(leftNested)
106+
.query(innerHitsNames /*++ leftNested.innerHitsName.toSet*/, leftBoolQuery)
107+
108+
val rightNested = ElasticNested(p.rightCriteria, p.rightCriteria.limit)
109+
val rightBoolQuery = Option(ElasticBoolQuery(group = true))
110+
val rightQuery = ElasticQuery(rightNested)
111+
.query(innerHitsNames /*++ rightNested.innerHitsName.toSet*/, rightBoolQuery)
112+
113+
p.operator match {
114+
case AND =>
115+
p.not match {
116+
case Some(_) => not(rightQuery).filter(leftQuery)
117+
case _ => must(leftQuery, rightQuery)
118+
}
119+
case _ =>
120+
p.not match {
121+
case Some(_) => not(rightQuery).should(leftQuery)
122+
case _ => should(leftQuery, rightQuery)
123+
}
124+
}
125+
case _ =>
126+
val boolQuery = Option(ElasticBoolQuery(group = true))
127+
val q = criteria
128+
.asFilter(boolQuery)
129+
.query(innerHitsNames + innerHitsName.getOrElse(""), boolQuery)
130+
if (nestedTrees.size == 1) {
131+
buildNestedQuery(nestedTrees.head, q)
132+
} else {
133+
val innerQueries = nestedTrees.map(nested => buildNestedQuery(nested, q))
134+
must(innerQueries)
135+
}
104136
}
105137
}
106138
}

0 commit comments

Comments
 (0)