Skip to content
Draft
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
9 changes: 7 additions & 2 deletions api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ plugins {

dependencies {
api project(':ppl')
api project(':sql')
api group: 'org.apache.calcite', name: 'calcite-babel', version: '1.41.0'

testImplementation testFixtures(project(':api'))
Expand Down Expand Up @@ -69,13 +70,17 @@ jacocoTestCoverageVerification {
limit {
minimum = 0.9
}

}
}
afterEvaluate {
classDirectories.setFrom(files(classDirectories.files.collect {
fileTree(dir: it,
exclude: ['**/antlr/parser/**'])
// Calcite native SQL parser path replaced by SQL V2 ANTLR parser for now
exclude: ['**/antlr/parser/**',
'**/CalciteSqlQueryParser.class',
'**/UnifiedQueryPlanner$CalciteNativeStrategy.class',
'**/LateBindingFunctionRule.class',
'**/LateBindingFunctionRule$*.class'])
}))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
import org.apache.calcite.tools.FrameworkConfig;
import org.apache.calcite.tools.Frameworks;
import org.apache.calcite.tools.Programs;
import org.opensearch.sql.api.parser.CalciteSqlQueryParser;
import org.opensearch.sql.api.parser.PPLQueryParser;
import org.opensearch.sql.api.parser.SqlV2QueryParser;
import org.opensearch.sql.api.parser.UnifiedQueryParser;
import org.opensearch.sql.api.spec.LanguageSpec;
import org.opensearch.sql.api.spec.UnifiedPplSpec;
Expand Down Expand Up @@ -246,7 +246,7 @@ public UnifiedQueryContext build() {
private UnifiedQueryParser<?> createParser(CalcitePlanContext planContext, Settings settings) {
return switch (queryType) {
case PPL -> new PPLQueryParser(settings);
case SQL -> new CalciteSqlQueryParser(planContext);
case SQL -> new SqlV2QueryParser();
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import org.opensearch.sql.ast.tree.UnresolvedPlan;
import org.opensearch.sql.calcite.CalciteRelNodeVisitor;
import org.opensearch.sql.common.antlr.SyntaxCheckException;
import org.opensearch.sql.executor.QueryType;

/**
* {@code UnifiedQueryPlanner} provides a high-level API for parsing and analyzing queries using the
Expand All @@ -45,10 +44,7 @@ public class UnifiedQueryPlanner {
*/
public UnifiedQueryPlanner(UnifiedQueryContext context) {
this.context = context;
this.strategy =
context.getPlanContext().queryType == QueryType.SQL
? new CalciteNativeStrategy(context)
: new CustomVisitorStrategy(context);
this.strategy = new CustomVisitorStrategy(context);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.sql.api.parser;

import org.antlr.v4.runtime.tree.ParseTree;
import org.opensearch.sql.ast.statement.Query;
import org.opensearch.sql.ast.statement.Statement;
import org.opensearch.sql.ast.tree.UnresolvedPlan;
import org.opensearch.sql.sql.antlr.SQLSyntaxParser;
import org.opensearch.sql.sql.parser.AstBuilder;
import org.opensearch.sql.sql.parser.AstStatementBuilder;

/** SQL query parser that produces {@link UnresolvedPlan} using the V2 ANTLR grammar. */
public class SqlV2QueryParser implements UnifiedQueryParser<UnresolvedPlan> {

/** Reusable ANTLR-based SQL syntax parser. Stateless and thread-safe. */
private final SQLSyntaxParser syntaxParser = new SQLSyntaxParser();

@Override
public UnresolvedPlan parse(String query) {
ParseTree cst = syntaxParser.parse(query);
AstStatementBuilder astStmtBuilder =
new AstStatementBuilder(
new AstBuilder(query), AstStatementBuilder.StatementBuilderContext.builder().build());
Statement statement = cst.accept(astStmtBuilder);

if (statement instanceof Query) {
return ((Query) statement).getPlan();
}
throw new UnsupportedOperationException(
"Only query statements are supported but got " + statement.getClass().getSimpleName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@
import org.apache.calcite.sql.parser.babel.SqlBabelParserImpl;
import org.apache.calcite.sql.validate.SqlConformanceEnum;
import org.apache.calcite.sql.validate.SqlValidator;
import org.opensearch.sql.api.spec.core.CoreExtension;
import org.opensearch.sql.api.spec.search.SearchExtension;
import org.opensearch.sql.api.spec.datetime.DatetimeExtension;

/**
* SQL language specification. Configures Calcite's parser, validator, and composable extensions for
Expand Down Expand Up @@ -51,7 +50,9 @@ public static UnifiedSqlSpec extended() {
Lex.BIG_QUERY,
SqlBabelParserImpl.FACTORY,
SqlConformanceEnum.BABEL,
List.of(new CoreExtension(), new SearchExtension()));
// CoreExtension and SearchExtension were for Calcite standard SQL pipeline and are
// unused in the V2 ANTLR parser path (CalciteRelNodeVisitor resolves functions directly)
List.of(/* new CoreExtension(), new SearchExtension(), */ new DatetimeExtension()));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.sql.Timestamp;
import org.apache.calcite.rel.RelNode;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.opensearch.sql.api.compiler.UnifiedQueryCompiler;
import org.opensearch.sql.api.spec.UnifiedFunctionSpec;
Expand All @@ -22,6 +23,7 @@
* Tests for scalar functions registered in {@link UnifiedFunctionSpec#SCALAR}. Verifies planning
* (function resolves correctly) and execution (produces correct results in-memory).
*/
@Ignore("Replaced by V2 ANTLR parser path for now")
public class UnifiedFunctionSpecTest extends UnifiedQueryTestBase {

private UnifiedQueryCompiler compiler;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
import java.util.Map;
import org.apache.calcite.schema.Schema;
import org.apache.calcite.schema.impl.AbstractSchema;
import org.junit.Ignore;
import org.junit.Test;
import org.opensearch.sql.executor.QueryType;

@Ignore("Replaced by V2 ANTLR parser path for now")
public class UnifiedQueryPlannerSqlTest extends UnifiedQueryTestBase {

private final AbstractSchema testDeepSchema =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.sql.api;

import org.junit.Test;
import org.opensearch.sql.executor.QueryType;

/**
* Tests for basic SQL query planning through the V2 ANTLR parser path. Covers SELECT, WHERE, ORDER
* BY operations that produce valid RelNode plans.
*/
public class UnifiedQueryPlannerSqlV2Test extends UnifiedQueryTestBase {

@Override
protected QueryType queryType() {
return QueryType.SQL;
}

@Test
public void selectStar() {
givenQuery("SELECT * FROM catalog.employees")
.assertPlan(
"""
LogicalTableScan(table=[[catalog, employees]])
""");
}

@Test
public void selectStarFields() {
givenQuery("SELECT * FROM catalog.employees")
.assertPlan(
"""
LogicalTableScan(table=[[catalog, employees]])
""")
.assertFields("id", "name", "age", "department");
}

@Test
public void testFilter() {
givenQuery("SELECT * FROM catalog.employees WHERE age > 30")
.assertPlan(
"""
LogicalFilter(condition=[>($2, 30)])
LogicalTableScan(table=[[catalog, employees]])
""");
}

@Test
public void testOrderBy() {
givenQuery("SELECT * FROM catalog.employees ORDER BY age")
.assertPlan(
"""
LogicalSort(sort0=[$2], dir0=[ASC-nulls-first])
LogicalTableScan(table=[[catalog, employees]])
""");
}

@Test
public void testFilterAndOrderBy() {
givenQuery("SELECT * FROM catalog.employees WHERE name = 'Alice' ORDER BY age")
.assertPlan(
"""
LogicalSort(sort0=[$2], dir0=[ASC-nulls-first])
LogicalFilter(condition=[=($1, 'Alice')])
LogicalTableScan(table=[[catalog, employees]])
""");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package org.opensearch.sql.api;

import org.junit.Ignore;
import org.junit.Test;
import org.opensearch.sql.executor.QueryType;

Expand All @@ -13,6 +14,7 @@
* tests in {@link UnifiedRelevanceSearchTest} with equivalent SQL queries. Both paths produce
* identical MAP-based plans for pushdown rules.
*/
@Ignore("Replaced by V2 ANTLR parser path for now")
public class UnifiedRelevanceSearchSqlTest extends UnifiedQueryTestBase {

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.sql.api;

import org.junit.Test;
import org.opensearch.sql.executor.QueryType;

/**
* Tests for relevance search functions through the V2 ANTLR parser path. Covers match,
* match_phrase, multi_match, match_bool_prefix, match_phrase_prefix, simple_query_string, and
* query_string with bracket syntax.
*/
public class UnifiedRelevanceSearchSqlV2Test extends UnifiedQueryTestBase {

@Override
protected QueryType queryType() {
return QueryType.SQL;
}

@Test
public void match() {
givenQuery("SELECT * FROM catalog.employees WHERE match(name, 'John')")
.assertPlan(
"""
LogicalFilter(condition=[match(MAP('field', $1), MAP('query', 'John':VARCHAR))])
LogicalTableScan(table=[[catalog, employees]])
""");
}

@Test
public void matchWithOptions() {
givenQuery(
"SELECT * FROM catalog.employees WHERE match(name, 'John', operator='AND', boost=2.0)")
.assertPlan(
"""
LogicalFilter(condition=[match(MAP('field', $1), MAP('query', 'John':VARCHAR), MAP('operator', 'AND':VARCHAR), MAP('boost', '2.0':VARCHAR))])
LogicalTableScan(table=[[catalog, employees]])
""");
}

@Test
public void matchPhrase() {
givenQuery("SELECT * FROM catalog.employees WHERE match_phrase(name, 'John Doe')")
.assertPlan(
"""
LogicalFilter(condition=[match_phrase(MAP('field', $1), MAP('query', 'John Doe':VARCHAR))])
LogicalTableScan(table=[[catalog, employees]])
""");
}

@Test
public void matchBoolPrefix() {
givenQuery("SELECT * FROM catalog.employees WHERE match_bool_prefix(name, 'John')")
.assertPlan(
"""
LogicalFilter(condition=[match_bool_prefix(MAP('field', $1), MAP('query', 'John':VARCHAR))])
LogicalTableScan(table=[[catalog, employees]])
""");
}

@Test
public void matchPhrasePrefix() {
givenQuery("SELECT * FROM catalog.employees WHERE match_phrase_prefix(name, 'John')")
.assertPlan(
"""
LogicalFilter(condition=[match_phrase_prefix(MAP('field', $1), MAP('query', 'John':VARCHAR))])
LogicalTableScan(table=[[catalog, employees]])
""");
}

@Test
public void multiMatchBracketSyntax() {
givenQuery("SELECT * FROM catalog.employees WHERE multi_match(['name', 'department'], 'John')")
.assertPlan(
"""
LogicalFilter(condition=[multi_match(MAP('fields', MAP('name':VARCHAR, 1.0E0:DOUBLE, 'department':VARCHAR, 1.0E0:DOUBLE)), MAP('query', 'John':VARCHAR))])
LogicalTableScan(table=[[catalog, employees]])
""");
}

@Test
public void multiMatchWithFieldBoost() {
givenQuery(
"""
SELECT * FROM catalog.employees\
WHERE multi_match(['name' ^ 2.0, 'department'], 'John')\
""")
.assertPlan(
"""
LogicalFilter(condition=[multi_match(MAP('fields', MAP('name':VARCHAR, 2.0E0:DOUBLE, 'department':VARCHAR, 1.0E0:DOUBLE)), MAP('query', 'John':VARCHAR))])
LogicalTableScan(table=[[catalog, employees]])
""");
}

@Test
public void simpleQueryStringBracketSyntax() {
givenQuery(
"""
SELECT * FROM catalog.employees\
WHERE simple_query_string(['name', 'department'], 'John')\
""")
.assertPlan(
"""
LogicalFilter(condition=[simple_query_string(MAP('fields', MAP('name':VARCHAR, 1.0E0:DOUBLE, 'department':VARCHAR, 1.0E0:DOUBLE)), MAP('query', 'John':VARCHAR))])
LogicalTableScan(table=[[catalog, employees]])
""");
}

@Test
public void queryStringBracketSyntax() {
givenQuery(
"""
SELECT * FROM catalog.employees\
WHERE query_string(['name', 'department'], 'John')\
""")
.assertPlan(
"""
LogicalFilter(condition=[query_string(MAP('fields', MAP('name':VARCHAR, 1.0E0:DOUBLE, 'department':VARCHAR, 1.0E0:DOUBLE)), MAP('query', 'John':VARCHAR))])
LogicalTableScan(table=[[catalog, employees]])
""");
}

@Test
public void matchCombinedWithBooleanFilter() {
givenQuery("SELECT * FROM catalog.employees WHERE match(name, 'John') AND age > 25")
.assertPlan(
"""
LogicalFilter(condition=[AND(match(MAP('field', $1), MAP('query', 'John':VARCHAR)), >($2, 25))])
LogicalTableScan(table=[[catalog, employees]])
""");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
import java.util.Map;
import org.apache.calcite.schema.Table;
import org.apache.calcite.schema.impl.AbstractSchema;
import org.junit.Ignore;
import org.junit.Test;
import org.opensearch.sql.executor.QueryType;

@Ignore("Replaced by V2 ANTLR parser path for now")
public class UnifiedSqlSpecTest extends UnifiedQueryTestBase {

@Override
Expand Down
Loading
Loading