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";

Reply via email to