This is an automated email from the ASF dual-hosted git repository.

yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.1 by this push:
     new 85818813400 branch-4.1: [fix](nereids) Make role-mapping keywords 
RULE/CEL/MAPPING non-reserved (#64105)
85818813400 is described below

commit 8581881340069c91a40adcafe5953054839b75c4
Author: Calvin Kirs <[email protected]>
AuthorDate: Fri Jun 5 10:57:06 2026 +0800

    branch-4.1: [fix](nereids) Make role-mapping keywords RULE/CEL/MAPPING 
non-reserved (#64105)
    
    #64104
---
 .../antlr4/org/apache/doris/nereids/DorisParser.g4 |  3 +
 .../nereids/parser/RoleMappingParserTest.java      | 67 +++++++++++++++++++
 .../keyword/test_role_mapping_keyword.groovy       | 75 ++++++++++++++++++++++
 3 files changed, 145 insertions(+)

diff --git a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 
b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
index e66d06c7475..b6459387c1b 100644
--- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
+++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
@@ -2030,6 +2030,7 @@ nonReserved
     | CAST
     | CATALOG
     | CATALOGS
+    | CEL
     | CHAIN
     | CIPHER
     | CHAR
@@ -2187,6 +2188,7 @@ nonReserved
     | LOGICAL
     | MANUAL
     | MAP
+    | MAPPING
     | MATCHED
     | MATCH_ALL
     | MATCH_ANY
@@ -2294,6 +2296,7 @@ nonReserved
     | ROOT
     | ROTATE
     | ROUTINE
+    | RULE
     | S3
     | SAMPLE
     | SAN
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/RoleMappingParserTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/RoleMappingParserTest.java
index c90c713080c..d6d4aeb0905 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/RoleMappingParserTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/RoleMappingParserTest.java
@@ -18,18 +18,38 @@
 package org.apache.doris.nereids.parser;
 
 import org.apache.doris.nereids.exceptions.ParseException;
+import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.commands.CreateRoleMappingCommand;
 import org.apache.doris.nereids.trees.plans.commands.DropRoleMappingCommand;
 import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
+import org.apache.doris.qe.ConnectContext;
 
 import com.google.common.collect.ImmutableSet;
+import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
+import java.util.List;
+
 public class RoleMappingParserTest {
 
     private final NereidsParser parser = new NereidsParser();
 
+    @BeforeEach
+    public void setUp() {
+        // parsing some statements (qualified column refs, INSERT target) 
reads session state
+        ConnectContext ctx = new ConnectContext();
+        ctx.setDatabase("test");
+        ctx.setThreadLocalInfo();
+    }
+
+    @AfterEach
+    public void tearDown() {
+        ConnectContext.remove();
+    }
+
     @Test
     public void testCreateRoleMappingParse() {
         LogicalPlan plan = parser.parseSingle("CREATE ROLE MAPPING IF NOT 
EXISTS corp_mapping "
@@ -74,4 +94,51 @@ public class RoleMappingParserTest {
         DropRoleMappingCommand drop2 = (DropRoleMappingCommand) plan2;
         Assertions.assertTrue(drop2.isIfExists());
     }
+
+    /**
+     * RULE/CEL/MAPPING are keywords introduced by the role-mapping DDL. They 
are non-reserved,
+     * so they must still be usable as ordinary identifiers (column names, 
etc.). Otherwise legacy
+     * SQL such as `INSERT INTO t(..., RULE, ...)` breaks with "mismatched 
input 'RULE'".
+     */
+    @Test
+    public void testRoleMappingKeywordsAsIdentifier() {
+        LogicalPlan plan = parser.parseSingle("SELECT rule, cel, mapping FROM 
t");
+        // the top plan is an UnboundResultSink wrapping the project; descend 
to the project
+        Plan node = plan;
+        while (!(node instanceof LogicalProject) && 
!node.children().isEmpty()) {
+            node = node.child(0);
+        }
+        Assertions.assertInstanceOf(LogicalProject.class, node);
+        List<String> names = ((LogicalProject<?>) node).getProjects().stream()
+                .map(p -> 
p.getName()).collect(java.util.stream.Collectors.toList());
+        Assertions.assertEquals(3, names.size());
+        Assertions.assertTrue(names.get(0).equalsIgnoreCase("rule"), 
names.toString());
+        Assertions.assertTrue(names.get(1).equalsIgnoreCase("cel"), 
names.toString());
+        Assertions.assertTrue(names.get(2).equalsIgnoreCase("mapping"), 
names.toString());
+
+        // the keywords must also work as a table name / alias
+        Assertions.assertDoesNotThrow(() ->
+                parser.parseSingle("SELECT rule.cel FROM mapping AS rule"));
+
+        // the original failing case: keywords appearing in an INSERT column 
list
+        Assertions.assertDoesNotThrow(() -> parser.parseSingle(
+                "INSERT INTO fnd_rnk_info(query_level, rule, mapping) "
+                        + "SELECT query_level, rule, mapping FROM t"));
+    }
+
+    /**
+     * Making RULE/MAPPING non-reserved must not regress the role-mapping DDL: 
the parser still
+     * has to route `CREATE/DROP ROLE MAPPING ...` to the role-mapping command 
rather than treating
+     * MAPPING as a role name.
+     */
+    @Test
+    public void testRoleMappingDdlNotAmbiguous() {
+        Assertions.assertInstanceOf(CreateRoleMappingCommand.class, 
parser.parseSingle(
+                "CREATE ROLE MAPPING corp_mapping ON AUTHENTICATION 
INTEGRATION corp_oidc "
+                        + "RULE ( USING CEL 'true' GRANT ROLE analyst )"));
+        Assertions.assertInstanceOf(DropRoleMappingCommand.class,
+                parser.parseSingle("DROP ROLE MAPPING corp_mapping"));
+        // bare `CREATE ROLE mapping` now creates a role literally named 
"mapping"
+        Assertions.assertFalse(parser.parseSingle("CREATE ROLE mapping") 
instanceof CreateRoleMappingCommand);
+    }
 }
diff --git 
a/regression-test/suites/query_p0/keyword/test_role_mapping_keyword.groovy 
b/regression-test/suites/query_p0/keyword/test_role_mapping_keyword.groovy
new file mode 100644
index 00000000000..9cc0b27259d
--- /dev/null
+++ b/regression-test/suites/query_p0/keyword/test_role_mapping_keyword.groovy
@@ -0,0 +1,75 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// RULE / CEL / MAPPING are keywords introduced by the role-mapping DDL. They 
are non-reserved,
+// so they must keep working as ordinary column / table identifiers. This 
guards against the
+// regression where `INSERT INTO t(..., RULE, ...)` failed with "mismatched 
input 'RULE'".
+suite("test_role_mapping_keyword", "query,p0") {
+    def tbl = "test_role_mapping_keyword_tbl"
+    sql "drop table if exists ${tbl}"
+    sql """
+        create table ${tbl}(
+            `query_level` int NOT NULL,
+            `rule` varchar(64),
+            `mapping` varchar(64),
+            `cel` varchar(64)
+        ) ENGINE=OLAP
+        DUPLICATE KEY(`query_level`)
+        DISTRIBUTED BY HASH(`query_level`) BUCKETS 1
+        PROPERTIES ("replication_allocation" = "tag.location.default: 1");
+    """
+
+    // keywords in an INSERT column list -- the original failing shape
+    sql """insert into ${tbl}(query_level, rule, mapping, cel) values (1, 
'r1', 'm1', 'c1')"""
+
+    // Assert inline instead of using qt_/order_qt_ golden files: this test 
only needs to prove the
+    // keywords parse as identifiers and round-trip correctly, so explicit 
asserts keep it self-contained
+    // (no .out file to generate/commit).
+
+    // keywords as projection items
+    def r1 = sql "select rule, mapping, cel from ${tbl} order by query_level"
+    assertEquals(1, r1.size())
+    assertEquals("r1", r1[0][0])
+    assertEquals("m1", r1[0][1])
+    assertEquals("c1", r1[0][2])
+
+    // keyword as table alias and qualified column reference
+    def r2 = sql "select rule.rule from ${tbl} as rule"
+    assertEquals(1, r2.size())
+    assertEquals("r1", r2[0][0])
+
+    // keywords in a WHERE predicate
+    def r3 = sql "select query_level from ${tbl} where rule = 'r1' and cel = 
'c1' order by query_level"
+    assertEquals(1, r3.size())
+    assertEquals(1, r3[0][0])
+
+    // insert ... select carrying the keywords through both the target and 
source column lists
+    sql """insert into ${tbl}(query_level, rule, mapping, cel)
+           select 2, rule, mapping, cel from ${tbl}"""
+    def r4 = sql "select query_level, rule, mapping, cel from ${tbl} order by 
query_level"
+    assertEquals(2, r4.size())
+    assertEquals(1, r4[0][0])
+    assertEquals("r1", r4[0][1])
+    assertEquals("m1", r4[0][2])
+    assertEquals("c1", r4[0][3])
+    assertEquals(2, r4[1][0])
+    assertEquals("r1", r4[1][1])
+    assertEquals("m1", r4[1][2])
+    assertEquals("c1", r4[1][3])
+
+    sql "drop table if exists ${tbl}"
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to