Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions plugins/query-dsl-calcite/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
}
}
Original file line number Diff line number Diff line change
@@ -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<? extends QueryBuilder> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}