From 61615e5637893bded87f9448c7b52a8fada68fd7 Mon Sep 17 00:00:00 2001 From: Abhishek Som Date: Tue, 24 Mar 2026 22:08:36 +0530 Subject: [PATCH 1/3] Added support for DSL Exists query to convert in RelNode format --- plugins/query-dsl-calcite/README.md | 13 ++- .../dsl/DslLogicalPlanIntegrationIT.java | 104 ++++++++++++++++++ .../dsl/query/ExistsQueryTranslator.java | 49 +++++++++ .../dsl/query/QueryRegistryFactory.java | 1 + 4 files changed, 161 insertions(+), 6 deletions(-) create mode 100644 plugins/query-dsl-calcite/src/main/java/org/opensearch/dsl/query/ExistsQueryTranslator.java diff --git a/plugins/query-dsl-calcite/README.md b/plugins/query-dsl-calcite/README.md index 07b603180d0a8..d6f49b488b215 100644 --- a/plugins/query-dsl-calcite/README.md +++ b/plugins/query-dsl-calcite/README.md @@ -61,12 +61,13 @@ RelNode (Calcite Logical Plan) ### Queries -| DSL Query | Calcite Representation | -|-----------|------------------------| -| `term` | `=($field, value)` — equality filter | -| `range` (gte, lte, gt, lt) | `AND(>=($field, min), <=($field, max))` — range filter | -| `bool` (must + filter) | `AND(condition1, condition2, ...)` — flattened conjunction | -| `match_all` | Skipped (boolean literal `TRUE`) | +| DSL Query | Calcite Representation | +|-----------|---------------------------------------------------------------------------| +| `term` | `=($field, value)` — equality filter | +| `range` (gte, lte, gt, lt) | `AND(>=($field, min), <=($field, max))` — range filter | +| `bool` (must + filter) | `AND(condition1, condition2, ...)` — flattened conjunction | +| `match_all` | Skipped (boolean literal `TRUE`) | +| `exists` | `IS NOT NULL($field)` — field existence check & boost not supported check | ### Aggregations diff --git a/plugins/query-dsl-calcite/src/internalClusterTest/java/org/opensearch/dsl/DslLogicalPlanIntegrationIT.java b/plugins/query-dsl-calcite/src/internalClusterTest/java/org/opensearch/dsl/DslLogicalPlanIntegrationIT.java index c9b5517206c54..e2b78f5e9934d 100644 --- a/plugins/query-dsl-calcite/src/internalClusterTest/java/org/opensearch/dsl/DslLogicalPlanIntegrationIT.java +++ b/plugins/query-dsl-calcite/src/internalClusterTest/java/org/opensearch/dsl/DslLogicalPlanIntegrationIT.java @@ -795,4 +795,108 @@ public void testInvalidFieldError() throws Exception { errorMessage.contains("unknown")); } } + + /** + * Test: Exists query conversion. + * Verifies that an exists query is converted to a LogicalFilter with IS NOT NULL condition. + * + * DSL Query: + * { + * "query": { + * "exists": { + * "field": "description" + * } + * } + * } + * + * Expected Calcite Plan: + * LogicalFilter(condition=[IS NOT NULL($0)]) + * LogicalTableScan(table=[[test-exists-query]]) + */ + public void testExistsQueryConversion() throws Exception { + String indexName = "test-exists-query"; + String mapping = "{" + + "\"properties\": {" + + " \"description\": {\"type\": \"text\"}," + + " \"price\": {\"type\": \"long\"}" + + "}" + + "}"; + client().admin().indices().prepareCreate(indexName) + .setMapping(mapping) + .get(); + ensureGreen(indexName); + + SearchSourceBuilder searchSource = new SearchSourceBuilder(); + searchSource.query(QueryBuilders.existsQuery("description")); + + SearchResponse response = convertDsl(searchSource, indexName); + + assertNotNull("SearchResponse should not be null", response); + } + + /** + * Test: Exists query combined with bool query. + * Verifies that exists query works correctly within bool query context. + * + * DSL Query: + * { + * "query": { + * "bool": { + * "must": [ + * { "exists": { "field": "description" } } + * ], + * "filter": [ + * { "range": { "price": { "gte": 100 } } } + * ] + * } + * } + * } + * + * Expected Calcite Plan: + * LogicalFilter(condition=[AND(IS NOT NULL($0), >=($1, 100))]) + * LogicalTableScan(table=[[test-exists-bool-query]]) + */ + public void testExistsQueryWithBoolQuery() throws Exception { + String indexName = "test-exists-bool-query"; + String mapping = "{" + + "\"properties\": {" + + " \"description\": {\"type\": \"text\"}," + + " \"price\": {\"type\": \"long\"}" + + "}" + + "}"; + client().admin().indices().prepareCreate(indexName) + .setMapping(mapping) + .get(); + ensureGreen(indexName); + + SearchSourceBuilder searchSource = new SearchSourceBuilder(); + searchSource.query( + QueryBuilders.boolQuery() + .must(QueryBuilders.existsQuery("description")) + .filter(QueryBuilders.rangeQuery("price").gte(100)) + ); + + SearchResponse response = convertDsl(searchSource, indexName); + + assertNotNull("SearchResponse should not be null", response); + } + + public void testExistsQueryWithBoostNotSupported() throws Exception { + String indexName = "test-exists-boost"; + String mapping = "{" + + "\"properties\": {" + + " \"description\": {\"type\": \"text\"}" + + "}" + + "}"; + client().admin().indices().prepareCreate(indexName) + .setMapping(mapping) + .get(); + ensureGreen(indexName); + + SearchSourceBuilder searchSource = new SearchSourceBuilder(); + searchSource.query(QueryBuilders.existsQuery("description").boost(2.0f)); + + RuntimeException exception = expectThrows(RuntimeException.class, () -> convertDsl(searchSource, indexName)); + assertTrue(exception.getMessage().contains("boost is unsupported for Exists query type")); + } } diff --git a/plugins/query-dsl-calcite/src/main/java/org/opensearch/dsl/query/ExistsQueryTranslator.java b/plugins/query-dsl-calcite/src/main/java/org/opensearch/dsl/query/ExistsQueryTranslator.java new file mode 100644 index 0000000000000..5a38459272db2 --- /dev/null +++ b/plugins/query-dsl-calcite/src/main/java/org/opensearch/dsl/query/ExistsQueryTranslator.java @@ -0,0 +1,49 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.dsl.query; + +import org.apache.calcite.rel.type.RelDataTypeField; +import org.apache.calcite.rex.RexNode; +import org.apache.calcite.sql.fun.SqlStdOperatorTable; +import org.opensearch.dsl.ConversionContext; +import org.opensearch.dsl.exception.ConversionException; +import org.opensearch.index.query.AbstractQueryBuilder; +import org.opensearch.index.query.ExistsQueryBuilder; +import org.opensearch.index.query.QueryBuilder; + +/** + * Converts an {@link ExistsQueryBuilder} to a Calcite IS NOT NULL RexNode. + */ +public class ExistsQueryTranslator implements QueryTranslator { + + @Override + public Class getQueryType() { + return ExistsQueryBuilder.class; + } + + @Override + public RexNode convert(QueryBuilder query, ConversionContext ctx) throws ConversionException { + ctx.requireOperatorSupported(SqlStdOperatorTable.IS_NOT_NULL); + + ExistsQueryBuilder existsQuery = (ExistsQueryBuilder) query; + String fieldName = existsQuery.fieldName(); + float boost = existsQuery.boost(); + + RelDataTypeField field = ctx.getRowType().getField(fieldName, false, false); + if (field == null) { + throw new RuntimeException("Field '" + fieldName + "' not found in schema"); + } + if (boost != AbstractQueryBuilder.DEFAULT_BOOST) { + throw new RuntimeException("boost is unsupported for Exists query type"); + } + + RexNode fieldRef = ctx.getRexBuilder().makeInputRef(field.getType(), field.getIndex()); + return ctx.getRexBuilder().makeCall(SqlStdOperatorTable.IS_NOT_NULL, fieldRef); + } +} diff --git a/plugins/query-dsl-calcite/src/main/java/org/opensearch/dsl/query/QueryRegistryFactory.java b/plugins/query-dsl-calcite/src/main/java/org/opensearch/dsl/query/QueryRegistryFactory.java index 9e48b60dcead1..f65b4d6f4c5e2 100644 --- a/plugins/query-dsl-calcite/src/main/java/org/opensearch/dsl/query/QueryRegistryFactory.java +++ b/plugins/query-dsl-calcite/src/main/java/org/opensearch/dsl/query/QueryRegistryFactory.java @@ -22,6 +22,7 @@ public static QueryRegistry create() { registry.register(new RangeQueryTranslator()); registry.register(new MatchAllQueryTranslator()); registry.register(new BoolQueryTranslator(registry)); + registry.register(new ExistsQueryTranslator()); return registry; } } From 4ff57ed5c313e10d7de1bddb5d889a7b77358a9b Mon Sep 17 00:00:00 2001 From: Abhishek Som Date: Thu, 26 Mar 2026 03:26:35 +0530 Subject: [PATCH 2/3] Added support for Terms query to convert in RelNode format --- plugins/query-dsl-calcite/README.md | 43 ++++++++++++++- .../dsl/DslLogicalPlanIntegrationIT.java | 37 +++++++++++++ .../dsl/query/QueryRegistryFactory.java | 1 + .../dsl/query/TermsQueryTranslator.java | 52 +++++++++++++++++++ 4 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 plugins/query-dsl-calcite/src/main/java/org/opensearch/dsl/query/TermsQueryTranslator.java diff --git a/plugins/query-dsl-calcite/README.md b/plugins/query-dsl-calcite/README.md index d6f49b488b215..c83db838b022d 100644 --- a/plugins/query-dsl-calcite/README.md +++ b/plugins/query-dsl-calcite/README.md @@ -64,6 +64,7 @@ RelNode (Calcite Logical Plan) | DSL Query | Calcite Representation | |-----------|---------------------------------------------------------------------------| | `term` | `=($field, value)` — equality filter | +| `terms` | `IN($field, value1, value2, ...)` — multi-value IN filter | | `range` (gte, lte, gt, lt) | `AND(>=($field, min), <=($field, max))` — range filter | | `bool` (must + filter) | `AND(condition1, condition2, ...)` — flattened conjunction | | `match_all` | Skipped (boolean literal `TRUE`) | @@ -120,7 +121,45 @@ LogicalFilter(condition=[=($0, 'electronics')]) LogicalTableScan(table=[[test-term-query]]) ``` -### 2. Range Query +### 2. Terms Query + +```json +{ + "query": { + "terms": { + "category": ["electronics", "computers", "laptops"] + } + } +} +``` +**Mapping:** `category: keyword, price: long` +``` +LogicalFilter(condition=[IN($0, 'electronics', 'computers', 'laptops')]) + LogicalTableScan(table=[[test-terms-query]]) +``` + +**Conversion Details:** +- DSL `terms` query → Calcite `IN` clause +- Supports multiple values for a single field +- More efficient than chaining multiple `OR` conditions +- Values are type-checked against field type + +**Test Coverage:** +```java +// Test: Terms query with string values +assertTermsQuery("category", List.of("electronics", "computers"), + "IN($0, 'electronics', 'computers')"); + +// Test: Terms query with numeric values +assertTermsQuery("price", List.of(100, 200, 300), + "IN($0, 100, 200, 300)"); + +// Test: Terms query with single value (equivalent to term query) +assertTermsQuery("status", List.of("active"), + "IN($0, 'active')"); +``` + +### 3. Range Query ```json { @@ -407,7 +446,7 @@ Current limitations: 1. **Read-only** — converts queries to logical plans but does not execute them 2. **Logging only** — converted plans are logged, not used for query execution -3. **Limited query types** — only `term`, `range`, `bool` (must + filter), and `match_all` +3. **Limited query types** — only `term`, `terms`, `range`, `bool` (must + filter), and `match_all` 4. **Bool query** — `should` and `must_not` clauses are not yet supported 5. **Nested objects** — flattened using dot notation, no true nested query support 6. **Pagination with aggregations** — `from`/`size` is not applied when aggregations are present diff --git a/plugins/query-dsl-calcite/src/internalClusterTest/java/org/opensearch/dsl/DslLogicalPlanIntegrationIT.java b/plugins/query-dsl-calcite/src/internalClusterTest/java/org/opensearch/dsl/DslLogicalPlanIntegrationIT.java index e2b78f5e9934d..04ec47585a0b0 100644 --- a/plugins/query-dsl-calcite/src/internalClusterTest/java/org/opensearch/dsl/DslLogicalPlanIntegrationIT.java +++ b/plugins/query-dsl-calcite/src/internalClusterTest/java/org/opensearch/dsl/DslLogicalPlanIntegrationIT.java @@ -70,6 +70,43 @@ public void testTermQueryConversion() throws Exception { // Verify it's an equality condition } + /** + * Test: Terms query conversion. + * Verifies that a terms query is converted to a LogicalFilter with IN condition. + * + * DSL Query: + * { + * "query": { + * "terms": { + * "category": ["electronics", "computers", "laptops"] + * } + * } + * } + * + * Expected Calcite Plan: + * LogicalFilter(condition=[IN($0, 'electronics', 'computers', 'laptops')]) + * LogicalTableScan(table=[[test-terms-query]]) + */ + public void testTermsQueryConversion() throws Exception { + String indexName = "test-terms-query"; + String mapping = "{" + + "\"properties\": {" + + " \"category\": {\"type\": \"keyword\"}," + + " \"price\": {\"type\": \"long\"}" + + "}" + + "}"; + client().admin().indices().prepareCreate(indexName) + .setMapping(mapping) + .get(); + ensureGreen(indexName); + + SearchSourceBuilder searchSource = new SearchSourceBuilder(); + searchSource.query(QueryBuilders.termsQuery("category", "electronics", "computers", "laptops")); + + SearchResponse response = convertDsl(searchSource, indexName); + assertNotNull("SearchResponse should not be null", response); + } + /** * Test: Range query conversion. * Verifies that a range query is converted to a LogicalFilter with comparison operators. diff --git a/plugins/query-dsl-calcite/src/main/java/org/opensearch/dsl/query/QueryRegistryFactory.java b/plugins/query-dsl-calcite/src/main/java/org/opensearch/dsl/query/QueryRegistryFactory.java index f65b4d6f4c5e2..daa4795b45c84 100644 --- a/plugins/query-dsl-calcite/src/main/java/org/opensearch/dsl/query/QueryRegistryFactory.java +++ b/plugins/query-dsl-calcite/src/main/java/org/opensearch/dsl/query/QueryRegistryFactory.java @@ -19,6 +19,7 @@ private QueryRegistryFactory() {} public static QueryRegistry create() { QueryRegistry registry = new QueryRegistry(); registry.register(new TermQueryTranslator()); + registry.register(new TermsQueryTranslator()); registry.register(new RangeQueryTranslator()); registry.register(new MatchAllQueryTranslator()); registry.register(new BoolQueryTranslator(registry)); diff --git a/plugins/query-dsl-calcite/src/main/java/org/opensearch/dsl/query/TermsQueryTranslator.java b/plugins/query-dsl-calcite/src/main/java/org/opensearch/dsl/query/TermsQueryTranslator.java new file mode 100644 index 0000000000000..6b59462d1e931 --- /dev/null +++ b/plugins/query-dsl-calcite/src/main/java/org/opensearch/dsl/query/TermsQueryTranslator.java @@ -0,0 +1,52 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.dsl.query; + +import org.apache.calcite.rel.type.RelDataTypeField; +import org.apache.calcite.rex.RexNode; +import org.apache.calcite.sql.fun.SqlStdOperatorTable; +import org.opensearch.dsl.exception.ConversionException; +import org.opensearch.dsl.ConversionContext; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.index.query.TermsQueryBuilder; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Converts a {@link TermsQueryBuilder} to a Calcite IN RexNode. + */ +public class TermsQueryTranslator implements QueryTranslator { + + @Override + public Class getQueryType() { + return TermsQueryBuilder.class; + } + + @Override + public RexNode convert(QueryBuilder query, ConversionContext ctx) throws ConversionException { + ctx.requireOperatorSupported(SqlStdOperatorTable.IN); + + TermsQueryBuilder termsQuery = (TermsQueryBuilder) query; + String fieldName = termsQuery.fieldName(); + List values = termsQuery.values(); + + RelDataTypeField field = ctx.getRowType().getField(fieldName, false, false); + if (field == null) { + throw new RuntimeException("Field '" + fieldName + "' not found in schema"); + } + + RexNode fieldRef = ctx.getRexBuilder().makeInputRef(field.getType(), field.getIndex()); + List literals = values.stream() + .map(value -> ctx.getRexBuilder().makeLiteral(value, field.getType(), true)) + .collect(Collectors.toList()); + + return ctx.getRexBuilder().makeIn(fieldRef, literals); + } +} From e896ce847e1f06dc222640f67013ccec6fac3d26 Mon Sep 17 00:00:00 2001 From: Abhishek Som Date: Fri, 27 Mar 2026 04:07:40 +0530 Subject: [PATCH 3/3] Revert "Added support for Terms query to convert in RelNode format" This reverts commit 4ff57ed5c313e10d7de1bddb5d889a7b77358a9b. --- plugins/query-dsl-calcite/README.md | 43 +-------------- .../dsl/DslLogicalPlanIntegrationIT.java | 37 ------------- .../dsl/query/QueryRegistryFactory.java | 1 - .../dsl/query/TermsQueryTranslator.java | 52 ------------------- 4 files changed, 2 insertions(+), 131 deletions(-) delete mode 100644 plugins/query-dsl-calcite/src/main/java/org/opensearch/dsl/query/TermsQueryTranslator.java diff --git a/plugins/query-dsl-calcite/README.md b/plugins/query-dsl-calcite/README.md index c83db838b022d..d6f49b488b215 100644 --- a/plugins/query-dsl-calcite/README.md +++ b/plugins/query-dsl-calcite/README.md @@ -64,7 +64,6 @@ RelNode (Calcite Logical Plan) | DSL Query | Calcite Representation | |-----------|---------------------------------------------------------------------------| | `term` | `=($field, value)` — equality filter | -| `terms` | `IN($field, value1, value2, ...)` — multi-value IN filter | | `range` (gte, lte, gt, lt) | `AND(>=($field, min), <=($field, max))` — range filter | | `bool` (must + filter) | `AND(condition1, condition2, ...)` — flattened conjunction | | `match_all` | Skipped (boolean literal `TRUE`) | @@ -121,45 +120,7 @@ LogicalFilter(condition=[=($0, 'electronics')]) LogicalTableScan(table=[[test-term-query]]) ``` -### 2. Terms Query - -```json -{ - "query": { - "terms": { - "category": ["electronics", "computers", "laptops"] - } - } -} -``` -**Mapping:** `category: keyword, price: long` -``` -LogicalFilter(condition=[IN($0, 'electronics', 'computers', 'laptops')]) - LogicalTableScan(table=[[test-terms-query]]) -``` - -**Conversion Details:** -- DSL `terms` query → Calcite `IN` clause -- Supports multiple values for a single field -- More efficient than chaining multiple `OR` conditions -- Values are type-checked against field type - -**Test Coverage:** -```java -// Test: Terms query with string values -assertTermsQuery("category", List.of("electronics", "computers"), - "IN($0, 'electronics', 'computers')"); - -// Test: Terms query with numeric values -assertTermsQuery("price", List.of(100, 200, 300), - "IN($0, 100, 200, 300)"); - -// Test: Terms query with single value (equivalent to term query) -assertTermsQuery("status", List.of("active"), - "IN($0, 'active')"); -``` - -### 3. Range Query +### 2. Range Query ```json { @@ -446,7 +407,7 @@ Current limitations: 1. **Read-only** — converts queries to logical plans but does not execute them 2. **Logging only** — converted plans are logged, not used for query execution -3. **Limited query types** — only `term`, `terms`, `range`, `bool` (must + filter), and `match_all` +3. **Limited query types** — only `term`, `range`, `bool` (must + filter), and `match_all` 4. **Bool query** — `should` and `must_not` clauses are not yet supported 5. **Nested objects** — flattened using dot notation, no true nested query support 6. **Pagination with aggregations** — `from`/`size` is not applied when aggregations are present diff --git a/plugins/query-dsl-calcite/src/internalClusterTest/java/org/opensearch/dsl/DslLogicalPlanIntegrationIT.java b/plugins/query-dsl-calcite/src/internalClusterTest/java/org/opensearch/dsl/DslLogicalPlanIntegrationIT.java index 04ec47585a0b0..e2b78f5e9934d 100644 --- a/plugins/query-dsl-calcite/src/internalClusterTest/java/org/opensearch/dsl/DslLogicalPlanIntegrationIT.java +++ b/plugins/query-dsl-calcite/src/internalClusterTest/java/org/opensearch/dsl/DslLogicalPlanIntegrationIT.java @@ -70,43 +70,6 @@ public void testTermQueryConversion() throws Exception { // Verify it's an equality condition } - /** - * Test: Terms query conversion. - * Verifies that a terms query is converted to a LogicalFilter with IN condition. - * - * DSL Query: - * { - * "query": { - * "terms": { - * "category": ["electronics", "computers", "laptops"] - * } - * } - * } - * - * Expected Calcite Plan: - * LogicalFilter(condition=[IN($0, 'electronics', 'computers', 'laptops')]) - * LogicalTableScan(table=[[test-terms-query]]) - */ - public void testTermsQueryConversion() throws Exception { - String indexName = "test-terms-query"; - String mapping = "{" - + "\"properties\": {" - + " \"category\": {\"type\": \"keyword\"}," - + " \"price\": {\"type\": \"long\"}" - + "}" - + "}"; - client().admin().indices().prepareCreate(indexName) - .setMapping(mapping) - .get(); - ensureGreen(indexName); - - SearchSourceBuilder searchSource = new SearchSourceBuilder(); - searchSource.query(QueryBuilders.termsQuery("category", "electronics", "computers", "laptops")); - - SearchResponse response = convertDsl(searchSource, indexName); - assertNotNull("SearchResponse should not be null", response); - } - /** * Test: Range query conversion. * Verifies that a range query is converted to a LogicalFilter with comparison operators. diff --git a/plugins/query-dsl-calcite/src/main/java/org/opensearch/dsl/query/QueryRegistryFactory.java b/plugins/query-dsl-calcite/src/main/java/org/opensearch/dsl/query/QueryRegistryFactory.java index daa4795b45c84..f65b4d6f4c5e2 100644 --- a/plugins/query-dsl-calcite/src/main/java/org/opensearch/dsl/query/QueryRegistryFactory.java +++ b/plugins/query-dsl-calcite/src/main/java/org/opensearch/dsl/query/QueryRegistryFactory.java @@ -19,7 +19,6 @@ private QueryRegistryFactory() {} public static QueryRegistry create() { QueryRegistry registry = new QueryRegistry(); registry.register(new TermQueryTranslator()); - registry.register(new TermsQueryTranslator()); registry.register(new RangeQueryTranslator()); registry.register(new MatchAllQueryTranslator()); registry.register(new BoolQueryTranslator(registry)); diff --git a/plugins/query-dsl-calcite/src/main/java/org/opensearch/dsl/query/TermsQueryTranslator.java b/plugins/query-dsl-calcite/src/main/java/org/opensearch/dsl/query/TermsQueryTranslator.java deleted file mode 100644 index 6b59462d1e931..0000000000000 --- a/plugins/query-dsl-calcite/src/main/java/org/opensearch/dsl/query/TermsQueryTranslator.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -package org.opensearch.dsl.query; - -import org.apache.calcite.rel.type.RelDataTypeField; -import org.apache.calcite.rex.RexNode; -import org.apache.calcite.sql.fun.SqlStdOperatorTable; -import org.opensearch.dsl.exception.ConversionException; -import org.opensearch.dsl.ConversionContext; -import org.opensearch.index.query.QueryBuilder; -import org.opensearch.index.query.TermsQueryBuilder; - -import java.util.List; -import java.util.stream.Collectors; - -/** - * Converts a {@link TermsQueryBuilder} to a Calcite IN RexNode. - */ -public class TermsQueryTranslator implements QueryTranslator { - - @Override - public Class getQueryType() { - return TermsQueryBuilder.class; - } - - @Override - public RexNode convert(QueryBuilder query, ConversionContext ctx) throws ConversionException { - ctx.requireOperatorSupported(SqlStdOperatorTable.IN); - - TermsQueryBuilder termsQuery = (TermsQueryBuilder) query; - String fieldName = termsQuery.fieldName(); - List values = termsQuery.values(); - - RelDataTypeField field = ctx.getRowType().getField(fieldName, false, false); - if (field == null) { - throw new RuntimeException("Field '" + fieldName + "' not found in schema"); - } - - RexNode fieldRef = ctx.getRexBuilder().makeInputRef(field.getType(), field.getIndex()); - List literals = values.stream() - .map(value -> ctx.getRexBuilder().makeLiteral(value, field.getType(), true)) - .collect(Collectors.toList()); - - return ctx.getRexBuilder().makeIn(fieldRef, literals); - } -}