This is an automated email from the ASF dual-hosted git repository.
xuzifu666 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/main by this push:
new 99c8d5911d [CALCITE-4645] In Elasticsearch adapter, a range predicate
should be translated to a range query
99c8d5911d is described below
commit 99c8d5911d9fd0e61d3a3a831d6b49bcc1f71582
Author: Yu Xu <[email protected]>
AuthorDate: Tue Apr 7 15:18:25 2026 +0800
[CALCITE-4645] In Elasticsearch adapter, a range predicate should be
translated to a range query
---
.../src/main/java/org/apache/calcite/util/Bug.java | 6 --
.../adapter/elasticsearch/PredicateAnalyzer.java | 81 +++++++++++++++++++++-
.../elasticsearch/AggregationAndSortTest.java | 29 +++++---
.../elasticsearch/ElasticSearchAdapterTest.java | 3 -
4 files changed, 98 insertions(+), 21 deletions(-)
diff --git a/core/src/main/java/org/apache/calcite/util/Bug.java
b/core/src/main/java/org/apache/calcite/util/Bug.java
index 8aaebeb4af..6d13a13d67 100644
--- a/core/src/main/java/org/apache/calcite/util/Bug.java
+++ b/core/src/main/java/org/apache/calcite/util/Bug.java
@@ -165,12 +165,6 @@ public abstract class Bug {
* fixed. */
public static final boolean CALCITE_4213_FIXED = false;
- /** Whether
- * <a
href="https://issues.apache.org/jira/browse/CALCITE-4645">[CALCITE-4645]
- * In Elasticsearch adapter, a range predicate should be translated to a
range query</a> is
- * fixed. */
- public static final boolean CALCITE_4645_FIXED = false;
-
/** Whether
* <a
href="https://issues.apache.org/jira/browse/CALCITE-4868">[CALCITE-4868]
* Elasticsearch adapter fails if GROUP BY is followed by ORDER BY</a> is
diff --git
a/elasticsearch/src/main/java/org/apache/calcite/adapter/elasticsearch/PredicateAnalyzer.java
b/elasticsearch/src/main/java/org/apache/calcite/adapter/elasticsearch/PredicateAnalyzer.java
index cfc8f20d82..de51a90013 100644
---
a/elasticsearch/src/main/java/org/apache/calcite/adapter/elasticsearch/PredicateAnalyzer.java
+++
b/elasticsearch/src/main/java/org/apache/calcite/adapter/elasticsearch/PredicateAnalyzer.java
@@ -33,6 +33,7 @@
import org.apache.calcite.util.Sarg;
import com.google.common.base.Throwables;
+import com.google.common.collect.BoundType;
import com.google.common.collect.Range;
import java.util.ArrayList;
@@ -215,7 +216,8 @@ private static boolean supportedRexCall(RexCall call) {
* @return true if it isSearchWithPoints or
isSearchWithComplementedPoints, other false
*/
static boolean canBeTranslatedToTermsQuery(RexCall search) {
- return isSearchWithPoints(search) ||
isSearchWithComplementedPoints(search);
+ return isSearchWithPoints(search) ||
isSearchWithComplementedPoints(search)
+ || isSearchWithRange(search);
}
static boolean isSearchWithPoints(RexCall search) {
@@ -230,6 +232,12 @@ static boolean isSearchWithComplementedPoints(RexCall
search) {
return sarg.isComplementedPoints();
}
+ static boolean isSearchWithRange(RexCall search) {
+ RexLiteral literal = (RexLiteral) search.getOperands().get(1);
+ final Sarg<?> sarg = requireNonNull(literal.getValueAs(Sarg.class),
"Sarg");
+ return !sarg.isPoints() && !sarg.isComplementedPoints();
+ }
+
@Override public Expression visitCall(RexCall call) {
SqlSyntax syntax = call.getOperator().getSyntax();
@@ -406,8 +414,10 @@ private QueryExpression binary(RexCall call) {
case SEARCH:
if (isSearchWithComplementedPoints(call)) {
return QueryExpression.create(pair.getKey()).notIn(pair.getValue());
- } else {
+ } else if (isSearchWithPoints(call)) {
return QueryExpression.create(pair.getKey()).in(pair.getValue());
+ } else {
+ return QueryExpression.create(pair.getKey()).range(pair.getValue());
}
default:
break;
@@ -601,6 +611,8 @@ public boolean isPartial() {
public abstract QueryExpression notIn(LiteralExpression literal);
+ public abstract QueryExpression range(LiteralExpression literal);
+
public abstract QueryExpression notEquals(LiteralExpression literal);
public abstract QueryExpression gt(LiteralExpression literal);
@@ -761,6 +773,10 @@ private CompoundQueryExpression(boolean partial,
BoolQueryBuilder builder) {
@Override public QueryExpression notIn(LiteralExpression literal) {
throw new PredicateAnalyzerException("notIn cannot be applied to a
compound expression");
}
+
+ @Override public QueryExpression range(LiteralExpression literal) {
+ throw new PredicateAnalyzerException("range cannot be applied to a
compound expression");
+ }
}
/**
@@ -899,6 +915,67 @@ private SimpleQueryExpression(NamedFieldExpression rel) {
builder = boolQuery().mustNot(termsQuery(getFieldReference(), iterable));
return this;
}
+
+ @Override public QueryExpression range(LiteralExpression literal) {
+ final Sarg<?> sarg =
requireNonNull(literal.literal.getValueAs(Sarg.class), "Sarg");
+ final Set<? extends Range<?>> ranges = sarg.rangeSet.asRanges();
+
+ if (ranges.isEmpty()) {
+ throw new PredicateAnalyzerException("Range query expects at least one
range");
+ }
+
+ if (ranges.size() == 1) {
+ // Single range, create a simple range query
+ final Range<?> range = ranges.iterator().next();
+ builder = createRangeQuery(range, literal);
+ } else {
+ // Multiple ranges, create bool query with should clauses (OR)
+ final BoolQueryBuilder boolQuery = boolQuery();
+ for (Range<?> range : ranges) {
+ boolQuery.should(createRangeQuery(range, literal));
+ }
+ builder = boolQuery;
+ }
+ return this;
+ }
+
+ private RangeQueryBuilder createRangeQuery(Range<?> range,
LiteralExpression literal) {
+ final RangeQueryBuilder rangeQuery = rangeQuery(getFieldReference());
+
+ // Handle lower bound
+ if (range.hasLowerBound()) {
+ final Object lowerValue =
+ literal.sargPointValue(range.lowerEndpoint(),
+ literal.literal.getType().getSqlTypeName());
+ if (range.lowerBoundType() == BoundType.CLOSED) {
+ rangeQuery.gte(lowerValue);
+ } else {
+ rangeQuery.gt(lowerValue);
+ }
+ // Directly check if lower bound endpoint is GregorianCalendar
+ if (range.lowerEndpoint() instanceof GregorianCalendar) {
+ rangeQuery.format("date_time");
+ }
+ }
+
+ // Handle upper bound
+ if (range.hasUpperBound()) {
+ final Object upperValue =
+ literal.sargPointValue(range.upperEndpoint(),
+ literal.literal.getType().getSqlTypeName());
+ if (range.upperBoundType() == BoundType.CLOSED) {
+ rangeQuery.lte(upperValue);
+ } else {
+ rangeQuery.lt(upperValue);
+ }
+ // Directly check if upper bound endpoint is GregorianCalendar
+ if (range.upperEndpoint() instanceof GregorianCalendar) {
+ rangeQuery.format("date_time");
+ }
+ }
+
+ return rangeQuery;
+ }
}
diff --git
a/elasticsearch/src/test/java/org/apache/calcite/adapter/elasticsearch/AggregationAndSortTest.java
b/elasticsearch/src/test/java/org/apache/calcite/adapter/elasticsearch/AggregationAndSortTest.java
index 27658bdfee..1d4dd0e1cd 100644
---
a/elasticsearch/src/test/java/org/apache/calcite/adapter/elasticsearch/AggregationAndSortTest.java
+++
b/elasticsearch/src/test/java/org/apache/calcite/adapter/elasticsearch/AggregationAndSortTest.java
@@ -115,21 +115,15 @@ private static Connection createConnection() throws
SQLException {
return connection;
}
- /**
- * Currently the patterns like below will be converted to Search in range
- * which is not supported in elastic search adapter.
- * (val1 >= 10 and val1 <= 20)
- * (val1 <= 10 or val1 >=20)
- * (val1 <= 10) or (val1 > 15 and val1 <= 20)
- * So disable this test case until the translation from Search in range
- * to rang Query in ES is implemented.
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-4645">[CALCITE-4645]
+ * In Elasticsearch adapter, a range predicate should be translated to a
range query</a>.
*/
@Test void searchInRange() {
- Assumptions.assumeTrue(Bug.CALCITE_4645_FIXED, "CALCITE-4645");
CalciteAssert.that()
.with(AggregationAndSortTest::createConnection)
.query("select count(*) from view where val1 >= 10 and val1 <=20")
- .returns("EXPR$0=1\n");
+ .returns("EXPR$0=0\n");
CalciteAssert.that()
.with(AggregationAndSortTest::createConnection)
@@ -140,6 +134,21 @@ private static Connection createConnection() throws
SQLException {
.with(AggregationAndSortTest::createConnection)
.query("select count(*) from view where val1 <= 10 or (val1 > 15 and
val1 <= 20)")
.returns("EXPR$0=2\n");
+
+ CalciteAssert.that()
+ .with(AggregationAndSortTest::createConnection)
+ .query("select count(*) from view where val1 = 1 or (val1 > 15 and
val1 <= 20)")
+ .returns("EXPR$0=1\n");
+
+ CalciteAssert.that()
+ .with(AggregationAndSortTest::createConnection)
+ .query("select count(*) from view where cat1 <= 'e' and cat1 >= 'a'")
+ .returns("EXPR$0=2\n");
+
+ CalciteAssert.that()
+ .with(AggregationAndSortTest::createConnection)
+ .query("select count(*) from view where cat4 >= '2017-12-22' and cat4
<= '2018-02-01'")
+ .returns("EXPR$0=1\n");
}
@Test void countStar() {
diff --git
a/elasticsearch/src/test/java/org/apache/calcite/adapter/elasticsearch/ElasticSearchAdapterTest.java
b/elasticsearch/src/test/java/org/apache/calcite/adapter/elasticsearch/ElasticSearchAdapterTest.java
index 0f31965fe5..b548d51154 100644
---
a/elasticsearch/src/test/java/org/apache/calcite/adapter/elasticsearch/ElasticSearchAdapterTest.java
+++
b/elasticsearch/src/test/java/org/apache/calcite/adapter/elasticsearch/ElasticSearchAdapterTest.java
@@ -23,7 +23,6 @@
import org.apache.calcite.schema.impl.ViewTable;
import org.apache.calcite.test.CalciteAssert;
import org.apache.calcite.test.ElasticsearchChecker;
-import org.apache.calcite.util.Bug;
import org.apache.calcite.util.TestUtil;
import org.apache.http.HttpHost;
@@ -33,7 +32,6 @@
import com.google.common.io.LineProcessor;
import com.google.common.io.Resources;
-import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.ResourceAccessMode;
@@ -651,7 +649,6 @@ private static Consumer<ResultSet>
sortedResultSetChecker(String column,
}
@Test void testFilterSortDesc() {
- Assumptions.assumeTrue(Bug.CALCITE_4645_FIXED, "CALCITE-4645");
final String sql = "select * from zips\n"
+ "where pop BETWEEN 95000 AND 100000\n"
+ "order by state desc, pop";