This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.2 by this push:
new 9750722ab7e branch-4.2: [fix](fe) Fix SHOW ROW POLICY failing on a
predicate with a user variable #68795 (#69900)
9750722ab7e is described below
commit 9750722ab7e52f601c4bb992503ec25038cf9392
Author: Mr.Vitvara Varavithya <[email protected]>
AuthorDate: Mon Nov 2 08:09:57 2026 +0700
branch-4.2: [fix](fe) Fix SHOW ROW POLICY failing on a predicate with a
user variable #68795 (#69900)
### What problem does this PR solve?
Issue Number: None
Related PR: #68795
Problem Summary:
Cherry-picked from #68795. A row policy with a user variable in `USING`
(e.g. `restaurant_id = @authorized_restaurant`) makes `SHOW ROW POLICY`
fail with `Invalid call to sql on unbound object`. The fix implements
`UnboundVariable#computeToSql`.
We hit this in production on 4.1.3: we can't list our row policies,
which makes them hard to manage.
`RowPolicyFilterSqlTest.java` changes are left out because that test
does not exist on branch-4.2.
### Release note
Fix `SHOW ROW POLICY` failing when a row policy's `USING` clause
references a user variable.
### Check List (For Author)
- Test <!-- At least one of them must be included. -->
- [x] Regression test
- [x] Unit Test
- [ ] Manual test (add detailed scripts or steps below)
- [ ] No need to test or manual test. Explain why:
- [ ] This is a refactor/code format and no logic has been changed.
- [ ] Previous test can cover this change.
- [ ] No code files have been changed.
- [ ] Other reason <!-- Add your reason? -->
- Behavior changed:
- [x] No.
- [ ] Yes. <!-- Explain the behavior change -->
- Does this need documentation?
- [x] No.
- [ ] Yes. <!-- Add document PR link here. eg:
https://github.com/apache/doris-website/pull/1214 -->
### Check List (For Reviewer who merge this PR)
- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label <!-- Add branch pick label that this PR
should merge into -->
---
.../doris/nereids/analyzer/UnboundVariable.java | 14 ++++
.../nereids/analyzer/UnboundVariableTest.java | 59 +++++++++++++++
.../account_p0/test_row_policy_user_variable.out | 18 +++++
.../test_row_policy_user_variable.groovy | 83 ++++++++++++++++++++++
4 files changed, 174 insertions(+)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundVariable.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundVariable.java
index 66e19e486c1..fadd5a6defb 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundVariable.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundVariable.java
@@ -80,6 +80,20 @@ public class UnboundVariable extends Expression implements
Unbound {
}
}
+ @Override
+ public String computeToSql() {
+ switch (type) {
+ case USER:
+ return "@`" + name.replace("`", "``") + "`";
+ case GLOBAL:
+ return "@@global." + name;
+ case SESSION:
+ return "@@session." + name;
+ default:
+ return "@@" + name;
+ }
+ }
+
@Override
public String toDigest() {
return toString();
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/analyzer/UnboundVariableTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/analyzer/UnboundVariableTest.java
new file mode 100644
index 00000000000..da20f102ebd
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/analyzer/UnboundVariableTest.java
@@ -0,0 +1,59 @@
+// 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.
+
+package org.apache.doris.nereids.analyzer;
+
+import org.apache.doris.nereids.analyzer.UnboundVariable.VariableType;
+import org.apache.doris.nereids.parser.NereidsParser;
+import org.apache.doris.nereids.trees.expressions.Expression;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+/**
+ * An unbound variable renders as SQL the parser reads back as the same
variable. A user variable name is
+ * stored without the backticks it may have been written with, and may need
them to be read back, so it is
+ * always rendered backquoted.
+ */
+public class UnboundVariableTest {
+
+ private static final NereidsParser PARSER = new NereidsParser();
+
+ @ParameterizedTest(name = "{0} -> {1}")
+ @CsvSource({
+ "@authorized_region, @`authorized_region`, USER",
+ "@`authorized_region`, @`authorized_region`, USER",
+ "@`authorized-region`, @`authorized-region`, USER",
+ "@`select`, @`select`, USER",
+ "@`authorized``region`, @`authorized``region`, USER",
+ "@`authorized````region`, @`authorized````region`, USER",
+ "@@k1_limit, @@k1_limit, DEFAULT",
+ "@@session.k1_limit, @@session.k1_limit, SESSION",
+ "@@global.k1_limit, @@global.k1_limit, GLOBAL"
+ })
+ public void testToSqlReadsBackAsTheSameVariable(String sql, String
expected, VariableType type) {
+ Expression parsed = PARSER.parseExpression(sql);
+ Assertions.assertInstanceOf(UnboundVariable.class, parsed);
+ Assertions.assertEquals(type, ((UnboundVariable) parsed).getType());
+
+ String rendered = parsed.toSql();
+
+ Assertions.assertEquals(expected, rendered);
+ Assertions.assertEquals(parsed, PARSER.parseExpression(rendered));
+ }
+}
diff --git a/regression-test/data/account_p0/test_row_policy_user_variable.out
b/regression-test/data/account_p0/test_row_policy_user_variable.out
new file mode 100644
index 00000000000..7f3a9870176
--- /dev/null
+++ b/regression-test/data/account_p0/test_row_policy_user_variable.out
@@ -0,0 +1,18 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !show_user_var --
+p_user_var internal regression_test_account_p0
row_policy_user_var_tbl ROW RESTRICTIVE (region = @`authorized_region`)
row_policy_user_var_user \N CREATE ROW POLICY p_user_var ON
regression_test_account_p0.row_policy_user_var_tbl AS RESTRICTIVE TO
row_policy_user_var_user USING (region = @authorized_region)
+
+-- !cn_session --
+cn 1
+
+-- !us_session --
+us 2
+
+-- !unset_session --
+
+-- !show_quoted_var --
+p_quoted_var internal regression_test_account_p0
row_policy_user_var_tbl ROW RESTRICTIVE (region = @`authorized-region`)
row_policy_user_var_user \N CREATE ROW POLICY p_quoted_var ON
regression_test_account_p0.row_policy_user_var_tbl AS RESTRICTIVE TO
row_policy_user_var_user USING (region = @`authorized-region`)
+
+-- !quoted_session --
+de 3
+
diff --git
a/regression-test/suites/account_p0/test_row_policy_user_variable.groovy
b/regression-test/suites/account_p0/test_row_policy_user_variable.groovy
new file mode 100644
index 00000000000..abda528b518
--- /dev/null
+++ b/regression-test/suites/account_p0/test_row_policy_user_variable.groovy
@@ -0,0 +1,83 @@
+// 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.
+
+// A row policy may compare a column against a user variable, so one policy
serves every session and each
+// session says which rows it is allowed to see: USING (region =
@authorized_region). The predicate keeps the
+// variable unbound until a query binds it to the session's value, and SHOW
ROW POLICY used to fail on it
+// with an unbound-expression error - for every policy it listed, not only
this one.
+suite("test_row_policy_user_variable") {
+ def dbName = context.config.getDbNameByFile(context.file)
+ def user = 'row_policy_user_var_user'
+ def pwd = '123abc!@#'
+ def tokens = context.config.jdbcUrl.split('/')
+ def url = tokens[0] + "//" + tokens[2] + "/" + dbName + "?"
+
+ sql "DROP TABLE IF EXISTS row_policy_user_var_tbl"
+ sql """
+ CREATE TABLE row_policy_user_var_tbl (region varchar(8), v int)
+ DISTRIBUTED BY HASH(region) BUCKETS 1 PROPERTIES("replication_num" =
"1")
+ """
+ sql """INSERT INTO row_policy_user_var_tbl VALUES ('cn', 1), ('us', 2),
('de', 3)"""
+
+ sql "DROP USER IF EXISTS ${user}"
+ sql "CREATE USER ${user} IDENTIFIED BY '${pwd}'"
+ sql "GRANT SELECT_PRIV ON ${dbName}.row_policy_user_var_tbl TO ${user}"
+
+ def cloudMode = isCloudMode()
+ if (cloudMode) {
+ def clusters = sql " SHOW CLUSTERS; "
+ assertTrue(!clusters.isEmpty())
+ sql """GRANT USAGE_PRIV ON CLUSTER `${clusters[0][0]}` TO ${user}"""
+ }
+
+ sql "DROP ROW POLICY IF EXISTS p_user_var ON
${dbName}.row_policy_user_var_tbl FOR ${user}"
+ sql "DROP ROW POLICY IF EXISTS p_quoted_var ON
${dbName}.row_policy_user_var_tbl FOR ${user}"
+ sql "CREATE ROW POLICY p_user_var ON ${dbName}.row_policy_user_var_tbl " +
+ "AS RESTRICTIVE TO ${user} USING (region = @authorized_region)"
+
+ // Both the listing that names the policy's grantee and the unfiltered one
include this policy, and each
+ // has to render it rather than fail. The name is rendered backquoted,
which is how any name reads back.
+ order_qt_show_user_var "SHOW ROW POLICY FOR ${user}"
+ assertTrue(sql("SHOW ROW POLICY").any { it[0] == 'p_user_var' },
+ "SHOW ROW POLICY without a filter does not list the policy")
+
+ // The variable is bound per session, so the same policy admits different
rows to different sessions,
+ // and a session that never set it reads nothing: an unset user variable
is NULL.
+ connectToDoris(user, pwd, url) {
+ sql "SET @authorized_region = 'cn'"
+ order_qt_cn_session "SELECT region, v FROM row_policy_user_var_tbl
ORDER BY region"
+ }
+ connectToDoris(user, pwd, url) {
+ sql "SET @authorized_region = 'us'"
+ order_qt_us_session "SELECT region, v FROM row_policy_user_var_tbl
ORDER BY region"
+ }
+ connectToDoris(user, pwd, url) {
+ order_qt_unset_session "SELECT region, v FROM row_policy_user_var_tbl
ORDER BY region"
+ }
+
+ // A quoted variable name is stored without its backticks, so SHOW has to
put them back: rendered bare,
+ // @authorized-region reads as a subtraction, not the variable the policy
enforces. p_user_var is dropped
+ // first: restrictive policies combine with AND, and this session never
sets @authorized_region.
+ sql "DROP ROW POLICY IF EXISTS p_user_var ON
${dbName}.row_policy_user_var_tbl FOR ${user}"
+ sql "CREATE ROW POLICY p_quoted_var ON ${dbName}.row_policy_user_var_tbl "
+
+ "AS RESTRICTIVE TO ${user} USING (region = @`authorized-region`)"
+ order_qt_show_quoted_var "SHOW ROW POLICY FOR ${user}"
+ connectToDoris(user, pwd, url) {
+ sql "SET @`authorized-region` = 'de'"
+ order_qt_quoted_session "SELECT region, v FROM row_policy_user_var_tbl
ORDER BY region"
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]