From 23a6247a1f01d5cdef55fcba21d50b85a7324182 Mon Sep 17 00:00:00 2001 From: lihangyu Date: Mon, 3 Aug 2026 20:36:10 +0800 Subject: [PATCH 1/2] [fix](reverted index) Validate nested variant MATCH predicates (#66207) Related PR: #61190 Problem Summary: Root VARIANT MATCH predicates were rejected only when the Match expression was a top-level filter conjunct. A Match nested under OR could bypass frontend analysis and reach backend execution, where it failed with an unrelated runtime error. Recursively collect Match expressions from each filter expression and apply the existing operand and root Variant validation to every Match node. Add focused FE unit and regression coverage for a mixed text/root Variant OR predicate. ### Release note Reject MATCH predicates on VARIANT root columns even when nested in compound filter expressions. ### Check List (For Author) - Test: Regression test / Unit Test - `./run-regression-test.sh --run -d search -s test_disable_root_variant_match` (failed before the fix and passed after it) - `./run-fe-ut.sh --run org.apache.doris.nereids.rules.rewrite.CheckMatchExpressionTest` - `./build.sh --fe` - Behavior changed: Yes. Nested root VARIANT MATCH predicates now fail during FE analysis, consistent with direct predicates. - Does this need documentation: No --- .../rules/rewrite/CheckMatchExpression.java | 26 +++++++++---------- .../rewrite/CheckMatchExpressionTest.java | 14 ++++++++++ .../test_disable_root_variant_match.groovy | 22 +++++++++++++--- 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/CheckMatchExpression.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/CheckMatchExpression.java index 8f0ad8706f35c5..c5923ab80da8e9 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/CheckMatchExpression.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/CheckMatchExpression.java @@ -47,20 +47,18 @@ public Rule build() { private Plan checkChildren(LogicalFilter filter) { List expressions = filter.getExpressions(); - for (Expression expr : expressions) { - if (expr instanceof Match) { - Match matchExpression = (Match) expr; - SlotReference slotReference = getSlotFromSlotCastOrAliasChain(matchExpression.left()); - if (slotReference == null - || !(matchExpression.right() instanceof Literal)) { - throw new AnalysisException(String.format("Only support match left operand is SlotRef," - + " right operand is Literal. But meet expression %s", matchExpression)); - } - if (slotReference.getDataType().isVariantType() && !slotReference.hasSubColPath()) { - throw new AnalysisException(String.format("VARIANT root column does not support MATCH predicates. " - + "Please query a subcolumn instead, for example %s['field'] MATCH 'xxx'", - slotReference.getName())); - } + List matchExpressions = ExpressionUtils.collectToList(expressions, Match.class::isInstance); + for (Match matchExpression : matchExpressions) { + SlotReference slotReference = getSlotFromSlotCastOrAliasChain(matchExpression.left()); + if (slotReference == null + || !(matchExpression.right() instanceof Literal)) { + throw new AnalysisException(String.format("Only support match left operand is SlotRef," + + " right operand is Literal. But meet expression %s", matchExpression)); + } + if (slotReference.getDataType().isVariantType() && !slotReference.hasSubColPath()) { + throw new AnalysisException(String.format("VARIANT root column does not support MATCH predicates. " + + "Please query a subcolumn instead, for example %s['field'] MATCH 'xxx'", + slotReference.getName())); } } return filter; diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/CheckMatchExpressionTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/CheckMatchExpressionTest.java index a127ed0e7c5508..9b2cc9d9bb1d92 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/CheckMatchExpressionTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/CheckMatchExpressionTest.java @@ -23,6 +23,7 @@ import org.apache.doris.nereids.trees.expressions.Cast; import org.apache.doris.nereids.trees.expressions.Expression; import org.apache.doris.nereids.trees.expressions.MatchAny; +import org.apache.doris.nereids.trees.expressions.Or; import org.apache.doris.nereids.trees.expressions.SlotReference; import org.apache.doris.nereids.trees.expressions.literal.IntegerLiteral; import org.apache.doris.nereids.trees.expressions.literal.StringLiteral; @@ -63,6 +64,19 @@ void testRejectsRootVariantMatch() { exception.getMessage()); } + @Test + void testRejectsRootVariantMatchNestedInOr() { + SlotReference textSlot = new SlotReference("response_body", StringType.INSTANCE, true); + SlotReference rootVariantSlot = new SlotReference("response", VariantType.INSTANCE, true, Arrays.asList()); + Or match = new Or( + new MatchAny(textSlot, new StringLiteral("doris")), + new MatchAny(rootVariantSlot, new StringLiteral("doris"))); + + AnalysisException exception = Assertions.assertThrows(AnalysisException.class, () -> invokeCheck(match)); + Assertions.assertTrue(exception.getMessage().contains("VARIANT root column does not support MATCH"), + exception.getMessage()); + } + @Test void testRejectsCastOnRootVariantMatch() { SlotReference rootVariantSlot = new SlotReference("response", VariantType.INSTANCE, true, Arrays.asList()); diff --git a/regression-test/suites/search/test_disable_root_variant_match.groovy b/regression-test/suites/search/test_disable_root_variant_match.groovy index f800e8e23419a0..40471d1eb127d3 100644 --- a/regression-test/suites/search/test_disable_root_variant_match.groovy +++ b/regression-test/suites/search/test_disable_root_variant_match.groovy @@ -26,10 +26,15 @@ suite("test_disable_root_variant_match", "p0") { sql """ CREATE TABLE test_disable_root_variant_match_tbl ( `id` INT NOT NULL, + `response_body` TEXT NULL, `response` variant< MATCH_NAME 'msg' : string, properties("variant_max_subcolumns_count" = "16") > NULL, + INDEX idx_response_body (response_body) USING INVERTED PROPERTIES( + "parser" = "unicode", + "lower_case" = "true" + ), INDEX idx_response (response) USING INVERTED PROPERTIES( "parser" = "unicode", "field_pattern" = "msg", @@ -45,9 +50,9 @@ suite("test_disable_root_variant_match", "p0") { """ sql """INSERT INTO test_disable_root_variant_match_tbl VALUES - (1, '{"msg": "doris community"}'), - (2, '{"msg": "apache software"}'), - (3, '{"msg": "doris variant index"}') + (1, 'doris community', '{"msg": "doris community"}'), + (2, 'apache software', '{"msg": "apache software"}'), + (3, 'doris variant index', '{"msg": "doris variant index"}') """ sql "sync" @@ -63,6 +68,17 @@ suite("test_disable_root_variant_match", "p0") { exception "VARIANT root column does not support MATCH" } + test { + sql """ + SELECT /*+SET_VAR(enable_segment_limit_pushdown=true)*/ id + FROM test_disable_root_variant_match_tbl + WHERE response_body MATCH_ANY 'doris' + OR response MATCH_ANY 'doris' + ORDER BY id + """ + exception "VARIANT root column does not support MATCH" + } + def variantSubcolumnMatchResult = sql """ SELECT /*+SET_VAR(enable_common_expr_pushdown=true)*/ id FROM test_disable_root_variant_match_tbl From 0a395d25bb8e27b8ef5b918c50e74e10612abcfd Mon Sep 17 00:00:00 2001 From: lihangyu Date: Tue, 4 Aug 2026 00:02:08 +0800 Subject: [PATCH 2/2] Fix SQL query by removing redundant SELECT statement --- .../suites/search/test_disable_root_variant_match.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/regression-test/suites/search/test_disable_root_variant_match.groovy b/regression-test/suites/search/test_disable_root_variant_match.groovy index 40471d1eb127d3..affb5a82e502a6 100644 --- a/regression-test/suites/search/test_disable_root_variant_match.groovy +++ b/regression-test/suites/search/test_disable_root_variant_match.groovy @@ -70,7 +70,7 @@ suite("test_disable_root_variant_match", "p0") { test { sql """ - SELECT /*+SET_VAR(enable_segment_limit_pushdown=true)*/ id + SELECT /*+SET_VAR(enable_common_expr_pushdown=true)*/ id FROM test_disable_root_variant_match_tbl WHERE response_body MATCH_ANY 'doris' OR response MATCH_ANY 'doris'