From 022464ae25b3330e63978e52663d9adb5b2e366a Mon Sep 17 00:00:00 2001 From: Chen Dai Date: Wed, 13 May 2026 13:44:01 -0700 Subject: [PATCH 1/2] feat(calcite): Register missing relevance functions in Calcite function table Add SQL V2 relevance functions (query, wildcard_query, score, scorequery) and aliases (match_query, matchquery, matchphrase, matchphrasequery, multimatch, multimatchquery) to PPLFuncImpTable so CalciteRelNodeVisitor can resolve them when processing RelNodes generated by SQL V2 AstBuilder. Signed-off-by: Chen Dai --- .../utils/UserDefinedFunctionUtils.java | 19 +++++++++++++-- .../function/PPLBuiltinOperators.java | 22 +++++++++++++++++ .../expression/function/PPLFuncImpTable.java | 24 +++++++++++++++++++ 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/opensearch/sql/calcite/utils/UserDefinedFunctionUtils.java b/core/src/main/java/org/opensearch/sql/calcite/utils/UserDefinedFunctionUtils.java index f619d966cc8..c1c41ca4827 100644 --- a/core/src/main/java/org/opensearch/sql/calcite/utils/UserDefinedFunctionUtils.java +++ b/core/src/main/java/org/opensearch/sql/calcite/utils/UserDefinedFunctionUtils.java @@ -76,9 +76,24 @@ public class UserDefinedFunctionUtils { TYPE_FACTORY.createSqlType(SqlTypeName.VARCHAR), createArrayType(TYPE_FACTORY, TYPE_FACTORY.createSqlType(SqlTypeName.VARCHAR), false)); public static Set SINGLE_FIELD_RELEVANCE_FUNCTION_SET = - ImmutableSet.of("match", "match_phrase", "match_bool_prefix", "match_phrase_prefix"); + ImmutableSet.of( + "match", + "match_phrase", + "match_bool_prefix", + "match_phrase_prefix", + "query", + "wildcard_query", + "wildcardquery", + "score", + "scorequery", + "score_query", + "match_query", + "matchquery", + "matchphrase", + "matchphrasequery"); public static Set MULTI_FIELDS_RELEVANCE_FUNCTION_SET = - ImmutableSet.of("simple_query_string", "query_string", "multi_match"); + ImmutableSet.of( + "simple_query_string", "query_string", "multi_match", "multimatch", "multimatchquery"); public static String IP_FUNCTION_NAME = "IP"; /** diff --git a/core/src/main/java/org/opensearch/sql/expression/function/PPLBuiltinOperators.java b/core/src/main/java/org/opensearch/sql/expression/function/PPLBuiltinOperators.java index 0a5b0fe0e03..4ba925063ae 100644 --- a/core/src/main/java/org/opensearch/sql/expression/function/PPLBuiltinOperators.java +++ b/core/src/main/java/org/opensearch/sql/expression/function/PPLBuiltinOperators.java @@ -424,6 +424,28 @@ public class PPLBuiltinOperators extends ReflectiveSqlOperatorTable { RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("query_string", false); public static final SqlOperator MULTI_MATCH = RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("multi_match", false); + public static final SqlOperator QUERY = RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("query"); + public static final SqlOperator WILDCARD_QUERY = + RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("wildcard_query"); + public static final SqlOperator WILDCARDQUERY = + RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("wildcardquery"); + public static final SqlOperator SCORE = RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("score"); + public static final SqlOperator SCOREQUERY = + RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("scorequery"); + public static final SqlOperator SCORE_QUERY = + RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("score_query"); + public static final SqlOperator MATCH_QUERY = + RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("match_query"); + public static final SqlOperator MATCHQUERY = + RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("matchquery"); + public static final SqlOperator MATCHPHRASE = + RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("matchphrase"); + public static final SqlOperator MATCHPHRASEQUERY = + RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("matchphrasequery"); + public static final SqlOperator MULTIMATCH = + RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("multimatch", false); + public static final SqlOperator MULTIMATCHQUERY = + RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("multimatchquery", false); public static final SqlOperator NUMBER_TO_STRING = new NumberToStringFunction().toUDF("NUMBER_TO_STRING"); public static final SqlOperator TONUMBER = new ToNumberFunction().toUDF("TONUMBER"); diff --git a/core/src/main/java/org/opensearch/sql/expression/function/PPLFuncImpTable.java b/core/src/main/java/org/opensearch/sql/expression/function/PPLFuncImpTable.java index 849c60fe4eb..ddb5a55c96a 100644 --- a/core/src/main/java/org/opensearch/sql/expression/function/PPLFuncImpTable.java +++ b/core/src/main/java/org/opensearch/sql/expression/function/PPLFuncImpTable.java @@ -133,9 +133,13 @@ import static org.opensearch.sql.expression.function.BuiltinFunctionName.MAP_CONCAT; import static org.opensearch.sql.expression.function.BuiltinFunctionName.MAP_REMOVE; import static org.opensearch.sql.expression.function.BuiltinFunctionName.MATCH; +import static org.opensearch.sql.expression.function.BuiltinFunctionName.MATCHPHRASE; +import static org.opensearch.sql.expression.function.BuiltinFunctionName.MATCHPHRASEQUERY; +import static org.opensearch.sql.expression.function.BuiltinFunctionName.MATCHQUERY; import static org.opensearch.sql.expression.function.BuiltinFunctionName.MATCH_BOOL_PREFIX; import static org.opensearch.sql.expression.function.BuiltinFunctionName.MATCH_PHRASE; import static org.opensearch.sql.expression.function.BuiltinFunctionName.MATCH_PHRASE_PREFIX; +import static org.opensearch.sql.expression.function.BuiltinFunctionName.MATCH_QUERY; import static org.opensearch.sql.expression.function.BuiltinFunctionName.MAX; import static org.opensearch.sql.expression.function.BuiltinFunctionName.MD5; import static org.opensearch.sql.expression.function.BuiltinFunctionName.MEDIAN; @@ -154,6 +158,8 @@ import static org.opensearch.sql.expression.function.BuiltinFunctionName.MONTHNAME; import static org.opensearch.sql.expression.function.BuiltinFunctionName.MONTH_OF_YEAR; import static org.opensearch.sql.expression.function.BuiltinFunctionName.MSTIME; +import static org.opensearch.sql.expression.function.BuiltinFunctionName.MULTIMATCH; +import static org.opensearch.sql.expression.function.BuiltinFunctionName.MULTIMATCHQUERY; import static org.opensearch.sql.expression.function.BuiltinFunctionName.MULTIPLY; import static org.opensearch.sql.expression.function.BuiltinFunctionName.MULTIPLYFUNCTION; import static org.opensearch.sql.expression.function.BuiltinFunctionName.MULTI_MATCH; @@ -178,6 +184,7 @@ import static org.opensearch.sql.expression.function.BuiltinFunctionName.POW; import static org.opensearch.sql.expression.function.BuiltinFunctionName.POWER; import static org.opensearch.sql.expression.function.BuiltinFunctionName.QUARTER; +import static org.opensearch.sql.expression.function.BuiltinFunctionName.QUERY; import static org.opensearch.sql.expression.function.BuiltinFunctionName.QUERY_STRING; import static org.opensearch.sql.expression.function.BuiltinFunctionName.RADIANS; import static org.opensearch.sql.expression.function.BuiltinFunctionName.RAND; @@ -198,6 +205,9 @@ import static org.opensearch.sql.expression.function.BuiltinFunctionName.RTRIM; import static org.opensearch.sql.expression.function.BuiltinFunctionName.SCALAR_MAX; import static org.opensearch.sql.expression.function.BuiltinFunctionName.SCALAR_MIN; +import static org.opensearch.sql.expression.function.BuiltinFunctionName.SCORE; +import static org.opensearch.sql.expression.function.BuiltinFunctionName.SCOREQUERY; +import static org.opensearch.sql.expression.function.BuiltinFunctionName.SCORE_QUERY; import static org.opensearch.sql.expression.function.BuiltinFunctionName.SECOND; import static org.opensearch.sql.expression.function.BuiltinFunctionName.SECOND_OF_MINUTE; import static org.opensearch.sql.expression.function.BuiltinFunctionName.SEC_TO_TIME; @@ -254,6 +264,8 @@ import static org.opensearch.sql.expression.function.BuiltinFunctionName.WEEKOFYEAR; import static org.opensearch.sql.expression.function.BuiltinFunctionName.WEEK_OF_YEAR; import static org.opensearch.sql.expression.function.BuiltinFunctionName.WIDTH_BUCKET; +import static org.opensearch.sql.expression.function.BuiltinFunctionName.WILDCARDQUERY; +import static org.opensearch.sql.expression.function.BuiltinFunctionName.WILDCARD_QUERY; import static org.opensearch.sql.expression.function.BuiltinFunctionName.XOR; import static org.opensearch.sql.expression.function.BuiltinFunctionName.YEAR; import static org.opensearch.sql.expression.function.BuiltinFunctionName.YEARWEEK; @@ -908,6 +920,18 @@ void populate() { registerOperator(SIMPLE_QUERY_STRING, PPLBuiltinOperators.SIMPLE_QUERY_STRING); registerOperator(QUERY_STRING, PPLBuiltinOperators.QUERY_STRING); registerOperator(MULTI_MATCH, PPLBuiltinOperators.MULTI_MATCH); + registerOperator(QUERY, PPLBuiltinOperators.QUERY); + registerOperator(WILDCARD_QUERY, PPLBuiltinOperators.WILDCARD_QUERY); + registerOperator(WILDCARDQUERY, PPLBuiltinOperators.WILDCARDQUERY); + registerOperator(SCORE, PPLBuiltinOperators.SCORE); + registerOperator(SCOREQUERY, PPLBuiltinOperators.SCOREQUERY); + registerOperator(SCORE_QUERY, PPLBuiltinOperators.SCORE_QUERY); + registerOperator(MATCH_QUERY, PPLBuiltinOperators.MATCH_QUERY); + registerOperator(MATCHQUERY, PPLBuiltinOperators.MATCHQUERY); + registerOperator(MATCHPHRASE, PPLBuiltinOperators.MATCHPHRASE); + registerOperator(MATCHPHRASEQUERY, PPLBuiltinOperators.MATCHPHRASEQUERY); + registerOperator(MULTIMATCH, PPLBuiltinOperators.MULTIMATCH); + registerOperator(MULTIMATCHQUERY, PPLBuiltinOperators.MULTIMATCHQUERY); registerOperator(REX_EXTRACT, PPLBuiltinOperators.REX_EXTRACT); registerOperator(REX_EXTRACT_MULTI, PPLBuiltinOperators.REX_EXTRACT_MULTI); registerOperator(REX_OFFSET, PPLBuiltinOperators.REX_OFFSET); From 064092d2b17060bfbd7f288fefcc7c3f7c19c464 Mon Sep 17 00:00:00 2001 From: Chen Dai Date: Wed, 13 May 2026 18:41:21 -0700 Subject: [PATCH 2/2] feat(calcite): Register missing SQL V2 relevance functions in Calcite function table Add SQL V2 relevance functions and aliases to PPLFuncImpTable so CalciteRelNodeVisitor can resolve them from SQL V2 AstBuilder. Aliases normalize to canonical operators (e.g. matchquery -> match). New functions: query, wildcard_query Aliases: wildcardquery, match_query, matchquery, matchphrase, matchphrasequery, multimatch, multimatchquery Not included: score/scorequery/score_query (needs dedicated visitor). Signed-off-by: Chen Dai --- .../api/UnifiedRelevanceSearchSqlV2Test.java | 91 +++++++++++++++++++ .../utils/UserDefinedFunctionUtils.java | 19 +--- .../function/PPLBuiltinOperators.java | 19 ---- .../expression/function/PPLFuncImpTable.java | 20 ++-- 4 files changed, 100 insertions(+), 49 deletions(-) diff --git a/api/src/test/java/org/opensearch/sql/api/UnifiedRelevanceSearchSqlV2Test.java b/api/src/test/java/org/opensearch/sql/api/UnifiedRelevanceSearchSqlV2Test.java index 4112f475003..c82bebe0f50 100644 --- a/api/src/test/java/org/opensearch/sql/api/UnifiedRelevanceSearchSqlV2Test.java +++ b/api/src/test/java/org/opensearch/sql/api/UnifiedRelevanceSearchSqlV2Test.java @@ -132,4 +132,95 @@ public void matchCombinedWithBooleanFilter() { LogicalTableScan(table=[[catalog, employees]]) """); } + + @Test + public void matchQuery() { + givenQuery("SELECT * FROM catalog.employees WHERE match_query(name, 'John')") + .assertPlan( + """ + LogicalFilter(condition=[match(MAP('field', $1), MAP('query', 'John':VARCHAR))]) + LogicalTableScan(table=[[catalog, employees]]) + """); + } + + @Test + public void matchquery() { + givenQuery("SELECT * FROM catalog.employees WHERE matchquery(name, 'John')") + .assertPlan( + """ + LogicalFilter(condition=[match(MAP('field', $1), MAP('query', 'John':VARCHAR))]) + LogicalTableScan(table=[[catalog, employees]]) + """); + } + + @Test + public void matchphrase() { + givenQuery("SELECT * FROM catalog.employees WHERE matchphrase(name, 'John Doe')") + .assertPlan( + """ + LogicalFilter(condition=[match_phrase(MAP('field', $1), MAP('query', 'John Doe':VARCHAR))]) + LogicalTableScan(table=[[catalog, employees]]) + """); + } + + @Test + public void matchphrasequery() { + givenQuery("SELECT * FROM catalog.employees WHERE matchphrasequery(name, 'John Doe')") + .assertPlan( + """ + LogicalFilter(condition=[match_phrase(MAP('field', $1), MAP('query', 'John Doe':VARCHAR))]) + LogicalTableScan(table=[[catalog, employees]]) + """); + } + + @Test + public void multimatch() { + givenQuery("SELECT * FROM catalog.employees WHERE multimatch(['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 multimatchquery() { + givenQuery( + "SELECT * FROM catalog.employees WHERE multimatchquery(['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 query() { + givenQuery("SELECT * FROM catalog.employees WHERE query('name:John')") + .assertPlan( + """ + LogicalFilter(condition=[query(MAP('query', 'name:John':VARCHAR))]) + LogicalTableScan(table=[[catalog, employees]]) + """); + } + + @Test + public void wildcardQuery() { + givenQuery("SELECT * FROM catalog.employees WHERE wildcard_query(name, 'John*')") + .assertPlan( + """ + LogicalFilter(condition=[wildcard_query(MAP('field', $1), MAP('query', 'John*':VARCHAR))]) + LogicalTableScan(table=[[catalog, employees]]) + """); + } + + @Test + public void wildcardquery() { + givenQuery("SELECT * FROM catalog.employees WHERE wildcardquery(name, 'John*')") + .assertPlan( + """ + LogicalFilter(condition=[wildcard_query(MAP('field', $1), MAP('query', 'John*':VARCHAR))]) + LogicalTableScan(table=[[catalog, employees]]) + """); + } } diff --git a/core/src/main/java/org/opensearch/sql/calcite/utils/UserDefinedFunctionUtils.java b/core/src/main/java/org/opensearch/sql/calcite/utils/UserDefinedFunctionUtils.java index c1c41ca4827..f619d966cc8 100644 --- a/core/src/main/java/org/opensearch/sql/calcite/utils/UserDefinedFunctionUtils.java +++ b/core/src/main/java/org/opensearch/sql/calcite/utils/UserDefinedFunctionUtils.java @@ -76,24 +76,9 @@ public class UserDefinedFunctionUtils { TYPE_FACTORY.createSqlType(SqlTypeName.VARCHAR), createArrayType(TYPE_FACTORY, TYPE_FACTORY.createSqlType(SqlTypeName.VARCHAR), false)); public static Set SINGLE_FIELD_RELEVANCE_FUNCTION_SET = - ImmutableSet.of( - "match", - "match_phrase", - "match_bool_prefix", - "match_phrase_prefix", - "query", - "wildcard_query", - "wildcardquery", - "score", - "scorequery", - "score_query", - "match_query", - "matchquery", - "matchphrase", - "matchphrasequery"); + ImmutableSet.of("match", "match_phrase", "match_bool_prefix", "match_phrase_prefix"); public static Set MULTI_FIELDS_RELEVANCE_FUNCTION_SET = - ImmutableSet.of( - "simple_query_string", "query_string", "multi_match", "multimatch", "multimatchquery"); + ImmutableSet.of("simple_query_string", "query_string", "multi_match"); public static String IP_FUNCTION_NAME = "IP"; /** diff --git a/core/src/main/java/org/opensearch/sql/expression/function/PPLBuiltinOperators.java b/core/src/main/java/org/opensearch/sql/expression/function/PPLBuiltinOperators.java index 4ba925063ae..3c144967ef7 100644 --- a/core/src/main/java/org/opensearch/sql/expression/function/PPLBuiltinOperators.java +++ b/core/src/main/java/org/opensearch/sql/expression/function/PPLBuiltinOperators.java @@ -427,25 +427,6 @@ public class PPLBuiltinOperators extends ReflectiveSqlOperatorTable { public static final SqlOperator QUERY = RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("query"); public static final SqlOperator WILDCARD_QUERY = RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("wildcard_query"); - public static final SqlOperator WILDCARDQUERY = - RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("wildcardquery"); - public static final SqlOperator SCORE = RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("score"); - public static final SqlOperator SCOREQUERY = - RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("scorequery"); - public static final SqlOperator SCORE_QUERY = - RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("score_query"); - public static final SqlOperator MATCH_QUERY = - RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("match_query"); - public static final SqlOperator MATCHQUERY = - RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("matchquery"); - public static final SqlOperator MATCHPHRASE = - RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("matchphrase"); - public static final SqlOperator MATCHPHRASEQUERY = - RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("matchphrasequery"); - public static final SqlOperator MULTIMATCH = - RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("multimatch", false); - public static final SqlOperator MULTIMATCHQUERY = - RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("multimatchquery", false); public static final SqlOperator NUMBER_TO_STRING = new NumberToStringFunction().toUDF("NUMBER_TO_STRING"); public static final SqlOperator TONUMBER = new ToNumberFunction().toUDF("TONUMBER"); diff --git a/core/src/main/java/org/opensearch/sql/expression/function/PPLFuncImpTable.java b/core/src/main/java/org/opensearch/sql/expression/function/PPLFuncImpTable.java index ddb5a55c96a..76cd52c2958 100644 --- a/core/src/main/java/org/opensearch/sql/expression/function/PPLFuncImpTable.java +++ b/core/src/main/java/org/opensearch/sql/expression/function/PPLFuncImpTable.java @@ -205,9 +205,6 @@ import static org.opensearch.sql.expression.function.BuiltinFunctionName.RTRIM; import static org.opensearch.sql.expression.function.BuiltinFunctionName.SCALAR_MAX; import static org.opensearch.sql.expression.function.BuiltinFunctionName.SCALAR_MIN; -import static org.opensearch.sql.expression.function.BuiltinFunctionName.SCORE; -import static org.opensearch.sql.expression.function.BuiltinFunctionName.SCOREQUERY; -import static org.opensearch.sql.expression.function.BuiltinFunctionName.SCORE_QUERY; import static org.opensearch.sql.expression.function.BuiltinFunctionName.SECOND; import static org.opensearch.sql.expression.function.BuiltinFunctionName.SECOND_OF_MINUTE; import static org.opensearch.sql.expression.function.BuiltinFunctionName.SEC_TO_TIME; @@ -922,16 +919,13 @@ void populate() { registerOperator(MULTI_MATCH, PPLBuiltinOperators.MULTI_MATCH); registerOperator(QUERY, PPLBuiltinOperators.QUERY); registerOperator(WILDCARD_QUERY, PPLBuiltinOperators.WILDCARD_QUERY); - registerOperator(WILDCARDQUERY, PPLBuiltinOperators.WILDCARDQUERY); - registerOperator(SCORE, PPLBuiltinOperators.SCORE); - registerOperator(SCOREQUERY, PPLBuiltinOperators.SCOREQUERY); - registerOperator(SCORE_QUERY, PPLBuiltinOperators.SCORE_QUERY); - registerOperator(MATCH_QUERY, PPLBuiltinOperators.MATCH_QUERY); - registerOperator(MATCHQUERY, PPLBuiltinOperators.MATCHQUERY); - registerOperator(MATCHPHRASE, PPLBuiltinOperators.MATCHPHRASE); - registerOperator(MATCHPHRASEQUERY, PPLBuiltinOperators.MATCHPHRASEQUERY); - registerOperator(MULTIMATCH, PPLBuiltinOperators.MULTIMATCH); - registerOperator(MULTIMATCHQUERY, PPLBuiltinOperators.MULTIMATCHQUERY); + registerOperator(WILDCARDQUERY, PPLBuiltinOperators.WILDCARD_QUERY); + registerOperator(MATCH_QUERY, PPLBuiltinOperators.MATCH); + registerOperator(MATCHQUERY, PPLBuiltinOperators.MATCH); + registerOperator(MATCHPHRASE, PPLBuiltinOperators.MATCH_PHRASE); + registerOperator(MATCHPHRASEQUERY, PPLBuiltinOperators.MATCH_PHRASE); + registerOperator(MULTIMATCH, PPLBuiltinOperators.MULTI_MATCH); + registerOperator(MULTIMATCHQUERY, PPLBuiltinOperators.MULTI_MATCH); registerOperator(REX_EXTRACT, PPLBuiltinOperators.REX_EXTRACT); registerOperator(REX_EXTRACT_MULTI, PPLBuiltinOperators.REX_EXTRACT_MULTI); registerOperator(REX_OFFSET, PPLBuiltinOperators.REX_OFFSET);