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

morningman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new c0497eb0cf9 [improvement](fe) Short-circuit catalog/db/table privilege 
check when global privilege is granted (#63838)
c0497eb0cf9 is described below

commit c0497eb0cf9fa5a03ea68b7c0619461f3eadd3a1
Author: heguanhui <[email protected]>
AuthorDate: Fri Jun 12 22:25:24 2026 +0800

    [improvement](fe) Short-circuit catalog/db/table privilege check when 
global privilege is granted (#63838)
    
    ## Summary
    Short-circuit `checkCtlPriv`/`checkDbPriv`/`checkTblPriv` in
    `CatalogAccessController` when `hasGlobal=true`, avoiding unnecessary
    privilege lookups that become a performance bottleneck with large
    numbers of privileges.
    
    ## What problem does this PR solve?
    
    Issue Number: close #xxx
    
    Related PR: #xxx
    
    Problem Summary: In `CatalogAccessController`, the
    `checkCtlPriv`/`checkDbPriv`/`checkTblPriv` methods with `hasGlobal`
    parameter always invoke the underlying privilege check (e.g.
    `checkCtlPriv(currentUser, ctl, wanted)`) even when `hasGlobal` is true.
    Since `hasGlobal=true` means the user already has global-level
    privilege, the result of the specific-level check is irrelevant — the
    method will return true regardless. This causes unnecessary privilege
    lookups, which become a performance bottleneck when there are a large
    number of privileges configured.
    
    Before:
    ```java
    default boolean checkCtlPriv(boolean hasGlobal, UserIdentity currentUser, 
String ctl, PrivPredicate wanted) {
        boolean res = checkCtlPriv(currentUser, ctl, wanted);
        return hasGlobal || res;
    }
    ```
    
    After:
    ```java
    default boolean checkCtlPriv(boolean hasGlobal, UserIdentity currentUser, 
String ctl, PrivPredicate wanted) {
        if (hasGlobal) {
            return true;
        }
        return checkCtlPriv(currentUser, ctl, wanted);
    }
    ```
    
    The same pattern is applied to `checkDbPriv` and `checkTblPriv`.
    
    ## Release note
    
    Optimized privilege checking to short-circuit when global privilege is
    already granted, avoiding unnecessary catalog/db/table-level privilege
    lookups and improving performance in environments with many privileges.
---
 .../mysql/privilege/CatalogAccessController.java   |  27 ++-
 .../privilege/CatalogAccessControllerTest.java     | 258 +++++++++++++++++++++
 2 files changed, 273 insertions(+), 12 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/CatalogAccessController.java
 
b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/CatalogAccessController.java
index 0538e52c287..df7605256dd 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/CatalogAccessController.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/CatalogAccessController.java
@@ -28,8 +28,10 @@ import java.util.Set;
 public interface CatalogAccessController {
     // ==== Catalog ====
     default boolean checkCtlPriv(boolean hasGlobal, UserIdentity currentUser, 
String ctl, PrivPredicate wanted) {
-        boolean res = checkCtlPriv(currentUser, ctl, wanted);
-        return hasGlobal || res;
+        if (hasGlobal) {
+            return true;
+        }
+        return checkCtlPriv(currentUser, ctl, wanted);
     }
 
     // ==== Global ====
@@ -41,8 +43,10 @@ public interface CatalogAccessController {
     // ==== Database ====
     default boolean checkDbPriv(boolean hasGlobal, UserIdentity currentUser, 
String ctl, String db,
             PrivPredicate wanted) {
-        boolean res = checkDbPriv(currentUser, ctl, db, wanted);
-        return hasGlobal || res;
+        if (hasGlobal) {
+            return true;
+        }
+        return checkDbPriv(currentUser, ctl, db, wanted);
     }
 
     boolean checkDbPriv(UserIdentity currentUser, String ctl, String db, 
PrivPredicate wanted);
@@ -50,8 +54,10 @@ public interface CatalogAccessController {
     // ==== Table ====
     default boolean checkTblPriv(boolean hasGlobal, UserIdentity currentUser, 
String ctl, String db, String tbl,
             PrivPredicate wanted) {
-        boolean res = checkTblPriv(currentUser, ctl, db, tbl, wanted);
-        return hasGlobal || res;
+        if (hasGlobal) {
+            return true;
+        }
+        return checkTblPriv(currentUser, ctl, db, tbl, wanted);
     }
 
     boolean checkTblPriv(UserIdentity currentUser, String ctl, String db, 
String tbl, PrivPredicate wanted);
@@ -59,13 +65,10 @@ public interface CatalogAccessController {
     // ==== Column ====
     default void checkColsPriv(boolean hasGlobal, UserIdentity currentUser, 
String ctl, String db, String tbl,
             Set<String> cols, PrivPredicate wanted) throws 
AuthorizationException {
-        try {
-            checkColsPriv(currentUser, ctl, db, tbl, cols, wanted);
-        } catch (AuthorizationException e) {
-            if (!hasGlobal) {
-                throw e;
-            }
+        if (hasGlobal) {
+            return;
         }
+        checkColsPriv(currentUser, ctl, db, tbl, cols, wanted);
     }
 
     // ==== Resource ====
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/CatalogAccessControllerTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/CatalogAccessControllerTest.java
new file mode 100644
index 00000000000..163518b33a5
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/CatalogAccessControllerTest.java
@@ -0,0 +1,258 @@
+// 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.mysql.privilege;
+
+import org.apache.doris.analysis.ResourceTypeEnum;
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.common.AuthorizationException;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class CatalogAccessControllerTest {
+
+    private static class StubAccessController implements 
CatalogAccessController {
+        final AtomicBoolean ctlPrivCalled = new AtomicBoolean(false);
+        final AtomicBoolean dbPrivCalled = new AtomicBoolean(false);
+        final AtomicBoolean tblPrivCalled = new AtomicBoolean(false);
+        final AtomicBoolean colsPrivCalled = new AtomicBoolean(false);
+
+        private final boolean ctlResult;
+        private final boolean dbResult;
+        private final boolean tblResult;
+        private final boolean colsResult;
+
+        StubAccessController() {
+            this(false, false, false, false);
+        }
+
+        StubAccessController(boolean ctlResult, boolean dbResult, boolean 
tblResult) {
+            this(ctlResult, dbResult, tblResult, false);
+        }
+
+        StubAccessController(boolean ctlResult, boolean dbResult, boolean 
tblResult, boolean colsResult) {
+            this.ctlResult = ctlResult;
+            this.dbResult = dbResult;
+            this.tblResult = tblResult;
+            this.colsResult = colsResult;
+        }
+
+        @Override
+        public boolean checkGlobalPriv(UserIdentity currentUser, PrivPredicate 
wanted) {
+            return false;
+        }
+
+        @Override
+        public boolean checkCtlPriv(UserIdentity currentUser, String ctl, 
PrivPredicate wanted) {
+            ctlPrivCalled.set(true);
+            return ctlResult;
+        }
+
+        @Override
+        public boolean checkDbPriv(UserIdentity currentUser, String ctl, 
String db, PrivPredicate wanted) {
+            dbPrivCalled.set(true);
+            return dbResult;
+        }
+
+        @Override
+        public boolean checkTblPriv(UserIdentity currentUser, String ctl, 
String db, String tbl, PrivPredicate wanted) {
+            tblPrivCalled.set(true);
+            return tblResult;
+        }
+
+        @Override
+        public void checkColsPriv(UserIdentity currentUser, String ctl, String 
db, String tbl,
+                Set<String> cols, PrivPredicate wanted) throws 
AuthorizationException {
+            colsPrivCalled.set(true);
+            if (!colsResult) {
+                throw new AuthorizationException("denied");
+            }
+        }
+
+        @Override
+        public boolean checkResourcePriv(UserIdentity currentUser, String 
resourceName, PrivPredicate wanted) {
+            return false;
+        }
+
+        @Override
+        public boolean checkWorkloadGroupPriv(UserIdentity currentUser, String 
workloadGroupName,
+                PrivPredicate wanted) {
+            return false;
+        }
+
+        @Override
+        public boolean checkCloudPriv(UserIdentity currentUser, String 
cloudName,
+                PrivPredicate wanted, ResourceTypeEnum type) {
+            return false;
+        }
+
+        @Override
+        public boolean checkStorageVaultPriv(UserIdentity currentUser, String 
storageVaultName,
+                PrivPredicate wanted) {
+            return false;
+        }
+
+        @Override
+        public Optional<DataMaskPolicy> evalDataMaskPolicy(UserIdentity 
currentUser, String ctl, String db,
+                String tbl, String col) {
+            return Optional.empty();
+        }
+
+        @Override
+        public List<? extends RowFilterPolicy> 
evalRowFilterPolicies(UserIdentity currentUser, String ctl,
+                String db, String tbl) {
+            return ImmutableList.of();
+        }
+    }
+
+    @Test
+    public void testCheckCtlPrivShortCircuitOnHasGlobal() {
+        StubAccessController controller = new StubAccessController();
+        UserIdentity user = 
UserIdentity.createAnalyzedUserIdentWithIp("test_user", "%");
+
+        boolean result = controller.checkCtlPriv(true, user, "ctl", 
PrivPredicate.SELECT);
+
+        Assert.assertTrue(result);
+        Assert.assertFalse(controller.ctlPrivCalled.get());
+    }
+
+    @Test
+    public void testCheckCtlPrivFallsThroughWithoutHasGlobal() {
+        StubAccessController controller = new StubAccessController(true, 
false, false);
+        UserIdentity user = 
UserIdentity.createAnalyzedUserIdentWithIp("test_user", "%");
+
+        boolean result = controller.checkCtlPriv(false, user, "ctl", 
PrivPredicate.SELECT);
+
+        Assert.assertTrue(result);
+        Assert.assertTrue(controller.ctlPrivCalled.get());
+    }
+
+    @Test
+    public void testCheckCtlPrivFallsThroughAndReturnsFalse() {
+        StubAccessController controller = new StubAccessController(false, 
false, false);
+        UserIdentity user = 
UserIdentity.createAnalyzedUserIdentWithIp("test_user", "%");
+
+        boolean result = controller.checkCtlPriv(false, user, "ctl", 
PrivPredicate.SELECT);
+
+        Assert.assertFalse(result);
+        Assert.assertTrue(controller.ctlPrivCalled.get());
+    }
+
+    @Test
+    public void testCheckDbPrivShortCircuitOnHasGlobal() {
+        StubAccessController controller = new StubAccessController();
+        UserIdentity user = 
UserIdentity.createAnalyzedUserIdentWithIp("test_user", "%");
+
+        boolean result = controller.checkDbPriv(true, user, "ctl", "db", 
PrivPredicate.SELECT);
+
+        Assert.assertTrue(result);
+        Assert.assertFalse(controller.dbPrivCalled.get());
+    }
+
+    @Test
+    public void testCheckDbPrivFallsThroughWithoutHasGlobal() {
+        StubAccessController controller = new StubAccessController(false, 
true, false);
+        UserIdentity user = 
UserIdentity.createAnalyzedUserIdentWithIp("test_user", "%");
+
+        boolean result = controller.checkDbPriv(false, user, "ctl", "db", 
PrivPredicate.SELECT);
+
+        Assert.assertTrue(result);
+        Assert.assertTrue(controller.dbPrivCalled.get());
+    }
+
+    @Test
+    public void testCheckDbPrivFallsThroughAndReturnsFalse() {
+        StubAccessController controller = new StubAccessController(false, 
false, false);
+        UserIdentity user = 
UserIdentity.createAnalyzedUserIdentWithIp("test_user", "%");
+
+        boolean result = controller.checkDbPriv(false, user, "ctl", "db", 
PrivPredicate.SELECT);
+
+        Assert.assertFalse(result);
+        Assert.assertTrue(controller.dbPrivCalled.get());
+    }
+
+    @Test
+    public void testCheckTblPrivShortCircuitOnHasGlobal() {
+        StubAccessController controller = new StubAccessController();
+        UserIdentity user = 
UserIdentity.createAnalyzedUserIdentWithIp("test_user", "%");
+
+        boolean result = controller.checkTblPriv(true, user, "ctl", "db", 
"tbl", PrivPredicate.SELECT);
+
+        Assert.assertTrue(result);
+        Assert.assertFalse(controller.tblPrivCalled.get());
+    }
+
+    @Test
+    public void testCheckTblPrivFallsThroughWithoutHasGlobal() {
+        StubAccessController controller = new StubAccessController(false, 
false, true);
+        UserIdentity user = 
UserIdentity.createAnalyzedUserIdentWithIp("test_user", "%");
+
+        boolean result = controller.checkTblPriv(false, user, "ctl", "db", 
"tbl", PrivPredicate.SELECT);
+
+        Assert.assertTrue(result);
+        Assert.assertTrue(controller.tblPrivCalled.get());
+    }
+
+    @Test
+    public void testCheckTblPrivFallsThroughAndReturnsFalse() {
+        StubAccessController controller = new StubAccessController(false, 
false, false);
+        UserIdentity user = 
UserIdentity.createAnalyzedUserIdentWithIp("test_user", "%");
+
+        boolean result = controller.checkTblPriv(false, user, "ctl", "db", 
"tbl", PrivPredicate.SELECT);
+
+        Assert.assertFalse(result);
+        Assert.assertTrue(controller.tblPrivCalled.get());
+    }
+
+    @Test
+    public void testCheckColsPrivShortCircuitOnHasGlobal() throws 
AuthorizationException {
+        StubAccessController controller = new StubAccessController();
+        UserIdentity user = 
UserIdentity.createAnalyzedUserIdentWithIp("test_user", "%");
+
+        controller.checkColsPriv(true, user, "ctl", "db", "tbl", 
ImmutableSet.of("col1"), PrivPredicate.SELECT);
+
+        Assert.assertFalse(controller.colsPrivCalled.get());
+    }
+
+    @Test
+    public void testCheckColsPrivFallsThroughWithoutHasGlobal() throws 
AuthorizationException {
+        StubAccessController controller = new StubAccessController(false, 
false, false, true);
+        UserIdentity user = 
UserIdentity.createAnalyzedUserIdentWithIp("test_user", "%");
+
+        controller.checkColsPriv(false, user, "ctl", "db", "tbl", 
ImmutableSet.of("col1"), PrivPredicate.SELECT);
+
+        Assert.assertTrue(controller.colsPrivCalled.get());
+    }
+
+    @Test
+    public void testCheckColsPrivFallsThroughAndThrows() {
+        StubAccessController controller = new StubAccessController(false, 
false, false, false);
+        UserIdentity user = 
UserIdentity.createAnalyzedUserIdentWithIp("test_user", "%");
+
+        Assert.assertThrows(AuthorizationException.class, () ->
+                controller.checkColsPriv(false, user, "ctl", "db", "tbl", 
ImmutableSet.of("col1"), PrivPredicate.SELECT));
+        Assert.assertTrue(controller.colsPrivCalled.get());
+    }
+}


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

Reply via email to