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

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

commit 7a6e5022d3a97557408da32b63b8854c4d1921f2
Author: 924060929 <924060...@qq.com>
AuthorDate: Thu Feb 29 16:42:27 2024 +0800

    [enhancement](Nereids) New optimizer support check column privileges 
(#31494)
---
 .../org/apache/doris/nereids/CascadesContext.java  |  43 +--
 .../doris/nereids/jobs/executor/Rewriter.java      |  17 +-
 .../org/apache/doris/nereids/rules/RuleType.java   |   5 +-
 .../doris/nereids/rules/analysis/BindRelation.java |  34 +-
 .../rules/analysis/EliminateLogicalSelectHint.java |   1 +
 .../nereids/rules/analysis/UserAuthentication.java |  20 +-
 .../nereids/rules/rewrite/CheckPrivileges.java     |  87 +++++
 ....java => EliminateSortUnderSubqueryOrView.java} |  21 +-
 ...rtUnderSubquery.java => InlineLogicalView.java} |  11 +-
 .../apache/doris/nereids/trees/plans/PlanType.java |   2 +
 .../trees/plans/logical/LogicalTestScan.java       |  71 ++++
 .../nereids/trees/plans/logical/LogicalView.java   | 143 ++++++++
 .../nereids/trees/plans/visitor/PlanVisitor.java   |   5 +
 .../java/org/apache/doris/qe/SessionVariable.java  |   9 +-
 .../nereids/privileges/TestCheckPrivileges.java    | 397 +++++++++++++++++++++
 .../doris/nereids/trees/expressions/ViewTest.java  |   6 +-
 .../org/apache/doris/qe/OlapQueryCacheTest.java    |   4 +-
 .../apache/doris/utframe/TestWithFeService.java    |  14 +-
 .../account_p0/test_nereids_row_policy.groovy      |   2 +-
 .../authorization/view_authorization.groovy        |   4 +-
 20 files changed, 822 insertions(+), 74 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/CascadesContext.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/CascadesContext.java
index 542ec6d8688..5addae25c29 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/CascadesContext.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/CascadesContext.java
@@ -123,7 +123,6 @@ public class CascadesContext implements ScheduleContext {
     private boolean isLeadingDisableJoinReorder = false;
 
     private final Map<String, Hint> hintMap = Maps.newLinkedHashMap();
-    private final boolean shouldCheckRelationAuthentication;
     private final ThreadLocal<Boolean> showPlanProcess = new ThreadLocal<>();
 
     // This list is used to listen the change event of the plan which
@@ -133,12 +132,12 @@ public class CascadesContext implements ScheduleContext {
     /**
      * Constructor of OptimizerContext.
      *
-     * @param memo {@link Memo} reference
      * @param statementContext {@link StatementContext} reference
+     * @param memo {@link Memo} reference
      */
     private CascadesContext(Optional<CascadesContext> parent, Optional<CTEId> 
currentTree,
             StatementContext statementContext, Plan plan, Memo memo,
-            CTEContext cteContext, PhysicalProperties requireProperties, 
boolean shouldCheckRelationAuthentication) {
+            CTEContext cteContext, PhysicalProperties requireProperties) {
         this.parent = Objects.requireNonNull(parent, "parent should not null");
         this.currentTree = Objects.requireNonNull(currentTree, "currentTree 
should not null");
         this.statementContext = Objects.requireNonNull(statementContext, 
"statementContext should not null");
@@ -152,7 +151,6 @@ public class CascadesContext implements ScheduleContext {
         this.subqueryExprIsAnalyzed = new HashMap<>();
         this.runtimeFilterContext = new 
RuntimeFilterContext(getConnectContext().getSessionVariable());
         this.materializationContexts = new ArrayList<>();
-        this.shouldCheckRelationAuthentication = 
shouldCheckRelationAuthentication;
     }
 
     /**
@@ -161,13 +159,7 @@ public class CascadesContext implements ScheduleContext {
     public static CascadesContext initContext(StatementContext 
statementContext,
             Plan initPlan, PhysicalProperties requireProperties) {
         return newContext(Optional.empty(), Optional.empty(), statementContext,
-                initPlan, new CTEContext(), requireProperties, true);
-    }
-
-    public static CascadesContext initViewContext(StatementContext 
statementContext,
-                                              Plan initPlan, 
PhysicalProperties requireProperties) {
-        return newContext(Optional.empty(), Optional.empty(), statementContext,
-            initPlan, new CTEContext(), requireProperties, false);
+                initPlan, new CTEContext(), requireProperties);
     }
 
     /**
@@ -176,14 +168,14 @@ public class CascadesContext implements ScheduleContext {
     public static CascadesContext newContextWithCteContext(CascadesContext 
cascadesContext,
             Plan initPlan, CTEContext cteContext) {
         return newContext(Optional.of(cascadesContext), Optional.empty(),
-                cascadesContext.getStatementContext(), initPlan, cteContext, 
PhysicalProperties.ANY,
-            cascadesContext.shouldCheckRelationAuthentication);
+                cascadesContext.getStatementContext(), initPlan, cteContext, 
PhysicalProperties.ANY
+        );
     }
 
     public static CascadesContext newCurrentTreeContext(CascadesContext 
context) {
         return CascadesContext.newContext(context.getParent(), 
context.getCurrentTree(), context.getStatementContext(),
                 context.getRewritePlan(), context.getCteContext(),
-                context.getCurrentJobContext().getRequiredProperties(), 
context.shouldCheckRelationAuthentication);
+                context.getCurrentJobContext().getRequiredProperties());
     }
 
     /**
@@ -192,14 +184,14 @@ public class CascadesContext implements ScheduleContext {
     public static CascadesContext newSubtreeContext(Optional<CTEId> subtree, 
CascadesContext context,
             Plan plan, PhysicalProperties requireProperties) {
         return CascadesContext.newContext(Optional.of(context), subtree, 
context.getStatementContext(),
-                plan, context.getCteContext(), requireProperties, 
context.shouldCheckRelationAuthentication);
+                plan, context.getCteContext(), requireProperties);
     }
 
     private static CascadesContext newContext(Optional<CascadesContext> 
parent, Optional<CTEId> subtree,
             StatementContext statementContext, Plan initPlan, CTEContext 
cteContext,
-            PhysicalProperties requireProperties, boolean 
shouldCheckRelationAuthentication) {
+            PhysicalProperties requireProperties) {
         return new CascadesContext(parent, subtree, statementContext, 
initPlan, null,
-            cteContext, requireProperties, shouldCheckRelationAuthentication);
+            cteContext, requireProperties);
     }
 
     public CascadesContext getRoot() {
@@ -655,10 +647,6 @@ public class CascadesContext implements ScheduleContext {
         isLeadingJoin = leadingJoin;
     }
 
-    public boolean shouldCheckRelationAuthentication() {
-        return shouldCheckRelationAuthentication;
-    }
-
     public boolean isLeadingDisableJoinReorder() {
         return isLeadingDisableJoinReorder;
     }
@@ -675,6 +663,10 @@ public class CascadesContext implements ScheduleContext {
         planProcesses.add(planProcess);
     }
 
+    public void addPlanProcesses(List<PlanProcess> planProcesses) {
+        this.planProcesses.addAll(planProcesses);
+    }
+
     public List<PlanProcess> getPlanProcesses() {
         return planProcesses;
     }
@@ -706,4 +698,13 @@ public class CascadesContext implements ScheduleContext {
             }
         }
     }
+
+    /** keepOrShowPlanProcess */
+    public void keepOrShowPlanProcess(boolean showPlanProcess, Runnable task) {
+        if (showPlanProcess) {
+            withPlanProcess(showPlanProcess, task);
+        } else {
+            task.run();
+        }
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Rewriter.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Rewriter.java
index e2d386dc910..db463354120 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Rewriter.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Rewriter.java
@@ -42,6 +42,7 @@ import 
org.apache.doris.nereids.rules.rewrite.CheckAndStandardizeWindowFunctionA
 import org.apache.doris.nereids.rules.rewrite.CheckDataTypes;
 import org.apache.doris.nereids.rules.rewrite.CheckMatchExpression;
 import org.apache.doris.nereids.rules.rewrite.CheckMultiDistinct;
+import org.apache.doris.nereids.rules.rewrite.CheckPrivileges;
 import org.apache.doris.nereids.rules.rewrite.CollectFilterAboveConsumer;
 import org.apache.doris.nereids.rules.rewrite.CollectProjectAboveConsumer;
 import org.apache.doris.nereids.rules.rewrite.ColumnPruning;
@@ -66,7 +67,7 @@ import 
org.apache.doris.nereids.rules.rewrite.EliminateNullAwareLeftAntiJoin;
 import org.apache.doris.nereids.rules.rewrite.EliminateOrderByConstant;
 import org.apache.doris.nereids.rules.rewrite.EliminateSemiJoin;
 import org.apache.doris.nereids.rules.rewrite.EliminateSort;
-import org.apache.doris.nereids.rules.rewrite.EliminateSortUnderSubquery;
+import org.apache.doris.nereids.rules.rewrite.EliminateSortUnderSubqueryOrView;
 import org.apache.doris.nereids.rules.rewrite.EliminateUnnecessaryProject;
 import org.apache.doris.nereids.rules.rewrite.EnsureProjectOnTopJoin;
 import 
org.apache.doris.nereids.rules.rewrite.ExtractAndNormalizeWindowExpression;
@@ -78,6 +79,7 @@ import 
org.apache.doris.nereids.rules.rewrite.InferFilterNotNull;
 import org.apache.doris.nereids.rules.rewrite.InferJoinNotNull;
 import org.apache.doris.nereids.rules.rewrite.InferPredicates;
 import org.apache.doris.nereids.rules.rewrite.InferSetOperatorDistinct;
+import org.apache.doris.nereids.rules.rewrite.InlineLogicalView;
 import org.apache.doris.nereids.rules.rewrite.LimitSortToTopN;
 import org.apache.doris.nereids.rules.rewrite.MergeFilters;
 import org.apache.doris.nereids.rules.rewrite.MergeOneRowRelationIntoUnion;
@@ -142,7 +144,7 @@ public class Rewriter extends AbstractBatchJobExecutor {
             topic("Plan Normalization",
                     topDown(
                             new EliminateOrderByConstant(),
-                            new EliminateSortUnderSubquery(),
+                            new EliminateSortUnderSubqueryOrView(),
                             new EliminateGroupByConstant(),
                             // MergeProjects depends on this rule
                             new LogicalSubQueryAliasToLogicalProject(),
@@ -231,6 +233,17 @@ public class Rewriter extends AbstractBatchJobExecutor {
                             new ApplyToJoin()
                     )
             ),
+            // before `Subquery unnesting` topic, some correlate slots should 
have appeared at LogicalApply.left,
+            // but it appeared at LogicalApply.right. After the `Subquery 
unnesting` topic, all slots is placed in a
+            // normal position, then we can check column privileges by these 
steps
+            //
+            // 1. use ColumnPruning rule to derive the used slots in 
LogicalView
+            // 2. and then check the column privileges
+            // 3. finally, we can eliminate the LogicalView
+            topic("Inline view and check column privileges",
+                    custom(RuleType.CHECK_PRIVILEGES, CheckPrivileges::new),
+                    bottomUp(new InlineLogicalView())
+            ),
             topic("Eliminate optimization",
                     bottomUp(
                             new EliminateLimit(),
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleType.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleType.java
index ea76eddd1d7..3b56738230e 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleType.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleType.java
@@ -107,7 +107,8 @@ public enum RuleType {
 
     ELIMINATE_LOGICAL_SELECT_HINT(RuleTypeClass.REWRITE),
     ELIMINATE_ORDER_BY_CONSTANT(RuleTypeClass.REWRITE),
-    ELIMINATE_SUBQUERY_ORDER_BY(RuleTypeClass.REWRITE),
+    ELIMINATE_ORDER_BY_UNDER_SUBQUERY(RuleTypeClass.REWRITE),
+    ELIMINATE_ORDER_BY_UNDER_VIEW(RuleTypeClass.REWRITE),
     ELIMINATE_HINT(RuleTypeClass.REWRITE),
     ELIMINATE_JOIN_ON_EMPTYRELATION(RuleTypeClass.REWRITE),
     ELIMINATE_FILTER_ON_EMPTYRELATION(RuleTypeClass.REWRITE),
@@ -288,6 +289,8 @@ public enum RuleType {
     CTE_INLINE(RuleTypeClass.REWRITE),
     REWRITE_CTE_CHILDREN(RuleTypeClass.REWRITE),
     COLLECT_FILTER_ON_CONSUMER(RuleTypeClass.REWRITE),
+    INLINE_VIEW(RuleTypeClass.REWRITE),
+    CHECK_PRIVILEGES(RuleTypeClass.REWRITE),
 
     COLLECT_FILTER(RuleTypeClass.REWRITE),
     COLLECT_JOIN_CONSTRAINT(RuleTypeClass.REWRITE),
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindRelation.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindRelation.java
index 54fa965e0eb..d74c487d296 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindRelation.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindRelation.java
@@ -59,6 +59,8 @@ import 
org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
 import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
 import org.apache.doris.nereids.trees.plans.logical.LogicalSchemaScan;
 import org.apache.doris.nereids.trees.plans.logical.LogicalSubQueryAlias;
+import org.apache.doris.nereids.trees.plans.logical.LogicalTestScan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalView;
 import org.apache.doris.nereids.util.RelationUtil;
 import org.apache.doris.qe.ConnectContext;
 
@@ -157,7 +159,7 @@ public class BindRelation extends OneAnalysisRuleFactory {
         }
 
         // TODO: should generate different Scan sub class according to table's 
type
-        LogicalPlan scan = getAndCheckLogicalPlan(table, unboundRelation, 
tableQualifier, cascadesContext);
+        LogicalPlan scan = getLogicalPlan(table, unboundRelation, 
tableQualifier, cascadesContext);
         if (cascadesContext.isLeadingJoin()) {
             LeadingHint leading = (LeadingHint) 
cascadesContext.getHintMap().get("Leading");
             
leading.putRelationIdAndTableName(Pair.of(unboundRelation.getRelationId(), 
tableName));
@@ -178,7 +180,7 @@ public class BindRelation extends OneAnalysisRuleFactory {
         if (table == null) {
             table = RelationUtil.getTable(tableQualifier, 
cascadesContext.getConnectContext().getEnv());
         }
-        return getAndCheckLogicalPlan(table, unboundRelation, tableQualifier, 
cascadesContext);
+        return getLogicalPlan(table, unboundRelation, tableQualifier, 
cascadesContext);
     }
 
     private LogicalPlan makeOlapScan(TableIf table, UnboundRelation 
unboundRelation, List<String> tableQualifier) {
@@ -234,25 +236,18 @@ public class BindRelation extends OneAnalysisRuleFactory {
         return scan;
     }
 
-    private LogicalPlan getAndCheckLogicalPlan(TableIf table, UnboundRelation 
unboundRelation,
+    private LogicalPlan getLogicalPlan(TableIf table, UnboundRelation 
unboundRelation,
                                                List<String> tableQualifier, 
CascadesContext cascadesContext) {
-        // if current context is in the view, we can skip check authentication 
because
-        // the view already checked authentication
-        if (cascadesContext.shouldCheckRelationAuthentication()) {
-            UserAuthentication.checkPermission(table, 
cascadesContext.getConnectContext());
-        }
-        return doGetLogicalPlan(table, unboundRelation, tableQualifier, 
cascadesContext);
-    }
-
-    private LogicalPlan doGetLogicalPlan(TableIf table, UnboundRelation 
unboundRelation, List<String> tableQualifier,
-                                       CascadesContext cascadesContext) {
         switch (table.getType()) {
             case OLAP:
             case MATERIALIZED_VIEW:
                 return makeOlapScan(table, unboundRelation, tableQualifier);
             case VIEW:
-                Plan viewPlan = parseAndAnalyzeView(((View) 
table).getInlineViewDef(), cascadesContext);
-                return new LogicalSubQueryAlias<>(tableQualifier, viewPlan);
+                View view = (View) table;
+                String inlineViewDef = view.getInlineViewDef();
+                Plan viewBody = parseAndAnalyzeView(inlineViewDef, 
cascadesContext);
+                LogicalView<Plan> logicalView = new LogicalView<>(view, 
viewBody);
+                return new LogicalSubQueryAlias<>(tableQualifier, logicalView);
             case HMS_EXTERNAL_TABLE:
                 if (Config.enable_query_hive_views && ((HMSExternalTable) 
table).isView()) {
                     String hiveCatalog = ((HMSExternalTable) 
table).getCatalog().getName();
@@ -276,6 +271,8 @@ public class BindRelation extends OneAnalysisRuleFactory {
                 return new LogicalOdbcScan(unboundRelation.getRelationId(), 
table, tableQualifier);
             case ES_EXTERNAL_TABLE:
                 return new LogicalEsScan(unboundRelation.getRelationId(), 
(EsExternalTable) table, tableQualifier);
+            case TEST_EXTERNAL_TABLE:
+                return new LogicalTestScan(unboundRelation.getRelationId(), 
table, tableQualifier);
             default:
                 throw new AnalysisException("Unsupported tableType " + 
table.getType());
         }
@@ -299,9 +296,12 @@ public class BindRelation extends OneAnalysisRuleFactory {
         if (parsedViewPlan instanceof UnboundResultSink) {
             parsedViewPlan = (LogicalPlan) ((UnboundResultSink<?>) 
parsedViewPlan).child();
         }
-        CascadesContext viewContext = CascadesContext.initViewContext(
+        CascadesContext viewContext = CascadesContext.initContext(
                 parentContext.getStatementContext(), parsedViewPlan, 
PhysicalProperties.ANY);
-        viewContext.newAnalyzer(true, customTableResolver).analyze();
+        viewContext.keepOrShowPlanProcess(parentContext.showPlanProcess(), () 
-> {
+            viewContext.newAnalyzer(true, customTableResolver).analyze();
+        });
+        parentContext.addPlanProcesses(viewContext.getPlanProcesses());
         // we should remove all group expression of the plan which in other 
memo, so the groupId would not conflict
         return viewContext.getRewritePlan();
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/EliminateLogicalSelectHint.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/EliminateLogicalSelectHint.java
index 9ca20709490..83eedb7ccd4 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/EliminateLogicalSelectHint.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/EliminateLogicalSelectHint.java
@@ -108,6 +108,7 @@ public class EliminateLogicalSelectHint extends 
OneRewriteRuleFactory {
             }
             throw new AnalysisException("The nereids is disabled in this sql, 
fallback to original planner");
         }
+        context.invalidCache(selectHint.getHintName());
     }
 
     private void extractLeading(SelectHintLeading selectHint, CascadesContext 
context,
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/UserAuthentication.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/UserAuthentication.java
index ffed8d4ea93..360131f2c4f 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/UserAuthentication.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/UserAuthentication.java
@@ -19,18 +19,20 @@ package org.apache.doris.nereids.rules.analysis;
 
 import org.apache.doris.catalog.DatabaseIf;
 import org.apache.doris.catalog.TableIf;
-import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.UserException;
 import org.apache.doris.datasource.CatalogIf;
 import org.apache.doris.mysql.privilege.PrivPredicate;
-import org.apache.doris.nereids.exceptions.AnalysisException;
 import org.apache.doris.qe.ConnectContext;
 
+import java.util.Set;
+
 /**
  * Check whether a user is permitted to scan specific tables.
  */
 public class UserAuthentication {
     /** checkPermission. */
-    public static void checkPermission(TableIf table, ConnectContext 
connectContext) {
+    public static void checkPermission(TableIf table, ConnectContext 
connectContext, Set<String> columns)
+            throws UserException {
         if (table == null) {
             return;
         }
@@ -40,7 +42,7 @@ public class UserAuthentication {
         }
         String tableName = table.getName();
         DatabaseIf db = table.getDatabase();
-        // when table inatanceof FunctionGenTable,db will be null
+        // when table instanceof FunctionGenTable,db will be null
         if (db == null) {
             return;
         }
@@ -50,13 +52,7 @@ public class UserAuthentication {
             return;
         }
         String ctlName = catalog.getName();
-        // TODO: 2023/7/19 checkColumnsPriv
-        if 
(!connectContext.getEnv().getAccessManager().checkTblPriv(connectContext, 
ctlName, dbName,
-                tableName, PrivPredicate.SELECT)) {
-            String message = 
ErrorCode.ERR_TABLEACCESS_DENIED_ERROR.formatErrorMsg("SELECT",
-                    ConnectContext.get().getQualifiedUser(), 
ConnectContext.get().getRemoteIP(),
-                    ctlName + ": " + dbName + ": " + tableName);
-            throw new AnalysisException(message);
-        }
+        connectContext.getEnv().getAccessManager().checkColumnsPriv(
+                connectContext.getCurrentUserIdentity(), ctlName, dbName, 
tableName, columns, PrivPredicate.SELECT);
     }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/CheckPrivileges.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/CheckPrivileges.java
new file mode 100644
index 00000000000..b4d7b800513
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/CheckPrivileges.java
@@ -0,0 +1,87 @@
+// 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.rules.rewrite;
+
+import org.apache.doris.catalog.TableIf;
+import org.apache.doris.common.UserException;
+import org.apache.doris.nereids.exceptions.AnalysisException;
+import org.apache.doris.nereids.jobs.JobContext;
+import org.apache.doris.nereids.rules.analysis.UserAuthentication;
+import org.apache.doris.nereids.trees.expressions.NamedExpression;
+import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalCatalogRelation;
+import org.apache.doris.nereids.trees.plans.logical.LogicalRelation;
+import org.apache.doris.nereids.trees.plans.logical.LogicalView;
+import org.apache.doris.qe.ConnectContext;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/** CheckPrivileges */
+public class CheckPrivileges extends ColumnPruning {
+    private JobContext jobContext;
+
+    @Override
+    public Plan rewriteRoot(Plan plan, JobContext jobContext) {
+        this.jobContext = jobContext;
+        super.rewriteRoot(plan, jobContext);
+
+        // don't rewrite plan
+        return plan;
+    }
+
+    @Override
+    public Plan visitLogicalView(LogicalView<? extends Plan> view, 
PruneContext context) {
+        checkColumnPrivileges(view.getView(), computeUsedColumns(view, 
context.requiredSlots));
+
+        // stop check privilege in the view
+        return view;
+    }
+
+    @Override
+    public Plan visitLogicalRelation(LogicalRelation relation, PruneContext 
context) {
+        if (relation instanceof LogicalCatalogRelation) {
+            TableIf table = ((LogicalCatalogRelation) relation).getTable();
+            checkColumnPrivileges(table, computeUsedColumns(relation, 
context.requiredSlots));
+        }
+        return super.visitLogicalRelation(relation, context);
+    }
+
+    private Set<String> computeUsedColumns(Plan plan, Set<Slot> requiredSlots) 
{
+        Map<Integer, Slot> idToSlot = plan.getOutputSet()
+                .stream()
+                .collect(Collectors.toMap(slot -> slot.getExprId().asInt(), 
slot -> slot));
+        return requiredSlots
+                .stream()
+                .map(slot -> idToSlot.get(slot.getExprId().asInt()))
+                .filter(slot -> slot != null)
+                .map(NamedExpression::getName)
+                .collect(Collectors.toSet());
+    }
+
+    private void checkColumnPrivileges(TableIf table, Set<String> usedColumns) 
{
+        ConnectContext connectContext = 
jobContext.getCascadesContext().getConnectContext();
+        try {
+            UserAuthentication.checkPermission(table, connectContext, 
usedColumns);
+        } catch (UserException e) {
+            throw new AnalysisException(e.getMessage(), e);
+        }
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateSortUnderSubquery.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateSortUnderSubqueryOrView.java
similarity index 61%
copy from 
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateSortUnderSubquery.java
copy to 
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateSortUnderSubqueryOrView.java
index 5eab038a67d..9d44d4fdb01 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateSortUnderSubquery.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateSortUnderSubqueryOrView.java
@@ -20,14 +20,25 @@ package org.apache.doris.nereids.rules.rewrite;
 import org.apache.doris.nereids.rules.Rule;
 import org.apache.doris.nereids.rules.RuleType;
 
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
 /**
  * SELECT * FROM lineorder ORDER BY 'f' -> SELECT * FROM lineorder
  */
-public class EliminateSortUnderSubquery extends OneRewriteRuleFactory {
+public class EliminateSortUnderSubqueryOrView implements RewriteRuleFactory {
     @Override
-    public Rule build() {
-        return logicalSubQueryAlias(logicalSort())
-                .then(subq -> subq.withChildren(subq.child().child(0)))
-                .toRule(RuleType.ELIMINATE_SUBQUERY_ORDER_BY);
+    public List<Rule> buildRules() {
+        return ImmutableList.of(
+            RuleType.ELIMINATE_ORDER_BY_UNDER_SUBQUERY.build(
+                logicalSubQueryAlias(logicalSort())
+                        .then(subq -> subq.withChildren(subq.child().child(0)))
+            ),
+            RuleType.ELIMINATE_ORDER_BY_UNDER_SUBQUERY.build(
+                logicalView(logicalSort())
+                        .then(view -> view.withChildren(view.child().child(0)))
+            )
+        );
     }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateSortUnderSubquery.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/InlineLogicalView.java
similarity index 74%
rename from 
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateSortUnderSubquery.java
rename to 
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/InlineLogicalView.java
index 5eab038a67d..4d3395a609f 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateSortUnderSubquery.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/InlineLogicalView.java
@@ -19,15 +19,12 @@ package org.apache.doris.nereids.rules.rewrite;
 
 import org.apache.doris.nereids.rules.Rule;
 import org.apache.doris.nereids.rules.RuleType;
+import org.apache.doris.nereids.trees.plans.logical.LogicalView;
 
-/**
- * SELECT * FROM lineorder ORDER BY 'f' -> SELECT * FROM lineorder
- */
-public class EliminateSortUnderSubquery extends OneRewriteRuleFactory {
+/** InlineLogicalView */
+public class InlineLogicalView extends OneRewriteRuleFactory {
     @Override
     public Rule build() {
-        return logicalSubQueryAlias(logicalSort())
-                .then(subq -> subq.withChildren(subq.child().child(0)))
-                .toRule(RuleType.ELIMINATE_SUBQUERY_ORDER_BY);
+        return 
logicalView().then(LogicalView::child).toRule(RuleType.INLINE_VIEW);
     }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java
index e09d8af389d..dc6358d3dec 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java
@@ -35,6 +35,7 @@ public enum PlanType {
     LOGICAL_JDBC_SCAN,
     LOGICAL_ODBC_SCAN,
     LOGICAL_OLAP_SCAN,
+    LOGICAL_TEST_SCAN,
     LOGICAL_ONE_ROW_RELATION,
     LOGICAL_SCHEMA_SCAN,
     LOGICAL_TVF_RELATION,
@@ -71,6 +72,7 @@ public enum PlanType {
     LOGICAL_REPEAT,
     LOGICAL_SELECT_HINT,
     LOGICAL_SUBQUERY_ALIAS,
+    LOGICAL_VIEW,
     LOGICAL_SORT,
     LOGICAL_TOP_N,
     LOGICAL_UNION,
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalTestScan.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalTestScan.java
new file mode 100644
index 00000000000..7d326ade3d6
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalTestScan.java
@@ -0,0 +1,71 @@
+// 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.trees.plans.logical;
+
+import org.apache.doris.catalog.TableIf;
+import org.apache.doris.nereids.memo.GroupExpression;
+import org.apache.doris.nereids.properties.LogicalProperties;
+import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.PlanType;
+import org.apache.doris.nereids.trees.plans.RelationId;
+import org.apache.doris.nereids.util.Utils;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * LogicalTestScan.
+ *
+ * only for ut
+ */
+public class LogicalTestScan extends LogicalCatalogRelation {
+    public LogicalTestScan(RelationId relationId, TableIf table, List<String> 
qualifier) {
+        this(relationId, table, qualifier, Optional.empty(), Optional.empty());
+    }
+
+    private LogicalTestScan(RelationId relationId, TableIf table, List<String> 
qualifier,
+               Optional<GroupExpression> groupExpression, 
Optional<LogicalProperties> logicalProperties) {
+        super(relationId, PlanType.LOGICAL_TEST_SCAN, table, qualifier, 
groupExpression, logicalProperties);
+    }
+
+    @Override
+    public String toString() {
+        return Utils.toSqlString("LogicalTestScan",
+                "qualified", qualifiedName(),
+                "output", getOutput()
+        );
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return super.equals(o) && Objects.equals(table, ((LogicalTestScan) 
table).getTable());
+    }
+
+    @Override
+    public Plan withGroupExpression(Optional<GroupExpression> groupExpression) 
{
+        return new LogicalTestScan(relationId, table, qualifier,
+                groupExpression, Optional.ofNullable(getLogicalProperties()));
+    }
+
+    @Override
+    public Plan withGroupExprLogicalPropChildren(Optional<GroupExpression> 
groupExpression,
+            Optional<LogicalProperties> logicalProperties, List<Plan> 
children) {
+        return new LogicalTestScan(relationId, table, qualifier, 
groupExpression, logicalProperties);
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalView.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalView.java
new file mode 100644
index 00000000000..6302c8d2c45
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalView.java
@@ -0,0 +1,143 @@
+// 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.trees.plans.logical;
+
+import org.apache.doris.catalog.View;
+import org.apache.doris.nereids.memo.GroupExpression;
+import org.apache.doris.nereids.properties.FdItem;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
+import org.apache.doris.nereids.properties.LogicalProperties;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.PlanType;
+import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
+import org.apache.doris.nereids.util.Utils;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Supplier;
+
+/** LogicalView */
+public class LogicalView<BODY extends Plan> extends LogicalUnary<BODY> {
+    private final View view;
+
+    /** LogicalView */
+    public LogicalView(View view, BODY body) {
+        super(PlanType.LOGICAL_VIEW, Optional.empty(), Optional.empty(), body);
+        this.view = Objects.requireNonNull(view, "catalog can not be null");
+        Preconditions.checkArgument(body instanceof LogicalPlan);
+    }
+
+    @Override
+    public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
+        return visitor.visitLogicalView(this, context);
+    }
+
+    @Override
+    public List<? extends Expression> getExpressions() {
+        return ImmutableList.of();
+    }
+
+    public String getCatalog() {
+        return view.getDatabase().getCatalog().getName();
+    }
+
+    public String getDb() {
+        return view.getDatabase().getFullName();
+    }
+
+    public String getName() {
+        return view.getName();
+    }
+
+    public String getViewString() {
+        return view.getInlineViewDef();
+    }
+
+    public View getView() {
+        return view;
+    }
+
+    @Override
+    public LogicalProperties getLogicalProperties() {
+        return child().getLogicalProperties();
+    }
+
+    @Override
+    public Plan withGroupExpression(Optional<GroupExpression> groupExpression) 
{
+        return new LogicalView(view, child());
+    }
+
+    @Override
+    public Plan withGroupExprLogicalPropChildren(Optional<GroupExpression> 
groupExpression,
+            Optional<LogicalProperties> logicalProperties, List<Plan> 
children) {
+        return new LogicalView(view, child());
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        LogicalView that = (LogicalView) o;
+        return Objects.equals(view, that.view);
+    }
+
+    @Override
+    public String toString() {
+        return Utils.toSqlString("LogicalView",
+                "catalog", getCatalog(),
+                "db", getDb(),
+                "name", getName()
+        );
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getCatalog(), getDb(), getName());
+    }
+
+    @Override
+    public List<Slot> computeOutput() {
+        return child().getOutput();
+    }
+
+    @Override
+    public FunctionalDependencies computeFuncDeps(Supplier<List<Slot>> 
outputSupplier) {
+        return ((LogicalPlan) child()).computeFuncDeps(outputSupplier);
+    }
+
+    @Override
+    public ImmutableSet<FdItem> computeFdItems(Supplier<List<Slot>> 
outputSupplier) {
+        return ((LogicalPlan) child()).computeFdItems(outputSupplier);
+    }
+
+    @Override
+    public Plan withChildren(List<Plan> children) {
+        return new LogicalView<>(view, (LogicalPlan) children.get(0));
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/PlanVisitor.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/PlanVisitor.java
index c71e2431341..82c99abb808 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/PlanVisitor.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/PlanVisitor.java
@@ -48,6 +48,7 @@ import 
org.apache.doris.nereids.trees.plans.logical.LogicalSort;
 import org.apache.doris.nereids.trees.plans.logical.LogicalSubQueryAlias;
 import org.apache.doris.nereids.trees.plans.logical.LogicalTopN;
 import org.apache.doris.nereids.trees.plans.logical.LogicalUnion;
+import org.apache.doris.nereids.trees.plans.logical.LogicalView;
 import org.apache.doris.nereids.trees.plans.logical.LogicalWindow;
 import org.apache.doris.nereids.trees.plans.physical.AbstractPhysicalJoin;
 import org.apache.doris.nereids.trees.plans.physical.AbstractPhysicalSort;
@@ -228,6 +229,10 @@ public abstract class PlanVisitor<R, C> implements 
CommandVisitor<R, C>, Relatio
         return visit(alias, context);
     }
 
+    public R visitLogicalView(LogicalView<? extends Plan> alias, C context) {
+        return visit(alias, context);
+    }
+
     public R visitLogicalTopN(LogicalTopN<? extends Plan> topN, C context) {
         return visit(topN, context);
     }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java 
b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
index a5bc477c3f6..82120a808c1 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
@@ -2624,8 +2624,12 @@ public class SessionVariable implements Serializable, 
Writable {
     }
 
     public Set<String> getDisableNereidsRuleNames() {
+        String checkPrivilege = RuleType.CHECK_PRIVILEGES.name();
+        String checkRowPolicy = RuleType.CHECK_ROW_POLICY.name();
         return Arrays.stream(disableNereidsRules.split(",[\\s]*"))
                 .map(rule -> rule.toUpperCase(Locale.ROOT))
+                .filter(rule -> !StringUtils.equalsIgnoreCase(rule, 
checkPrivilege)
+                        && !StringUtils.equalsIgnoreCase(rule, checkRowPolicy))
                 .collect(ImmutableSet.toImmutableSet());
     }
 
@@ -2633,7 +2637,10 @@ public class SessionVariable implements Serializable, 
Writable {
         return Arrays.stream(disableNereidsRules.split(",[\\s]*"))
                 .filter(rule -> !rule.isEmpty())
                 .map(rule -> rule.toUpperCase(Locale.ROOT))
-                .map(rule -> RuleType.valueOf(rule).type())
+                .map(rule -> RuleType.valueOf(rule))
+                .filter(ruleType -> ruleType != RuleType.CHECK_PRIVILEGES
+                        && ruleType != RuleType.CHECK_ROW_POLICY)
+                .map(RuleType::type)
                 .collect(ImmutableSet.toImmutableSet());
     }
 
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/privileges/TestCheckPrivileges.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/privileges/TestCheckPrivileges.java
new file mode 100644
index 00000000000..f9f6eb91002
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/privileges/TestCheckPrivileges.java
@@ -0,0 +1,397 @@
+// 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.privileges;
+
+import org.apache.doris.analysis.ResourceTypeEnum;
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.catalog.Column;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.catalog.PrimitiveType;
+import org.apache.doris.common.AuthorizationException;
+import org.apache.doris.common.FeConstants;
+import org.apache.doris.datasource.CatalogMgr;
+import 
org.apache.doris.datasource.test.TestExternalCatalog.TestCatalogProvider;
+import org.apache.doris.mysql.privilege.AccessControllerFactory;
+import org.apache.doris.mysql.privilege.AccessControllerManager;
+import org.apache.doris.mysql.privilege.CatalogAccessController;
+import org.apache.doris.mysql.privilege.PrivPredicate;
+import org.apache.doris.nereids.exceptions.AnalysisException;
+import org.apache.doris.nereids.util.PlanChecker;
+import org.apache.doris.utframe.TestWithFeService;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import mockit.Expectations;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class TestCheckPrivileges extends TestWithFeService {
+    private static final Map<String, Map<String, List<Column>>> CATALOG_META = 
ImmutableMap.of(
+            "test_db", ImmutableMap.of(
+                    "test_tbl1", ImmutableList.of(
+                            new Column("id", PrimitiveType.INT),
+                            new Column("name", PrimitiveType.VARCHAR)
+                    ),
+                    "test_tbl2", ImmutableList.of(
+                            new Column("id", PrimitiveType.INT),
+                            new Column("name", PrimitiveType.VARCHAR)
+                    ),
+                    "test_tbl3", ImmutableList.of(
+                            new Column("id", PrimitiveType.INT),
+                            new Column("name", PrimitiveType.VARCHAR)
+                    )
+            )
+    );
+
+    @Test
+    public void testColumnPrivileges() throws Exception {
+        FeConstants.runningUnitTest = true;
+        String catalogProvider
+                = 
"org.apache.doris.nereids.privileges.TestCheckPrivileges$CustomCatalogProvider";
+        String accessControllerFactory
+                = 
"org.apache.doris.nereids.privileges.TestCheckPrivileges$CustomAccessControllerFactory";
+
+        String catalog = "custom_catalog";
+        String db = "test_db";
+        createCatalog("create catalog " + catalog + " properties("
+                + " \"type\"=\"test\","
+                + " \"catalog_provider.class\"=\"" + catalogProvider + "\","
+                + " \"" + CatalogMgr.ACCESS_CONTROLLER_CLASS_PROP + "\"=\"" + 
accessControllerFactory + "\""
+                + ")");
+
+        createDatabase("internal_db");
+        String internalDb = "internal_db";
+        String table1 = "test_tbl1";
+        String table2 = "test_tbl2";
+        String table3 = "test_tbl3";
+
+        String view1 = "query_tbl2_view1";
+        createView("create view " + internalDb + "."
+                + view1 + " as select * from custom_catalog.test_db." + 
table2);
+        String view2 = "query_tbl2_view2";
+        createView("create view " + internalDb + "."
+                + view2 + " as select * from custom_catalog.test_db." + 
table2);
+        String view3 = "query_tbl2_view3";
+        createView("create view " + internalDb + "."
+                + view3 + " as select * from custom_catalog.test_db." + 
table3);
+        String view4 = "query_tbl2_view4";
+        createView("create view " + internalDb + "."
+                + view4 + " as select * from " + internalDb + "." + view3);
+
+        String user = "test_nereids_privilege_user";
+        addUser(user, true);
+        useUser(user);
+
+        List<MakeTablePrivileges> privileges = ImmutableList.of(
+                // base table privileges
+                MakePrivileges.table(catalog, db, 
table1).allowSelectTable(user),
+                MakePrivileges.table(catalog, db, 
table2).allowSelectColumns(user, ImmutableSet.of("id")),
+
+                // view privileges
+                MakePrivileges.table("internal", internalDb, 
view1).allowSelectTable(user),
+                MakePrivileges.table("internal", internalDb, view2)
+                        .allowSelectColumns(user, ImmutableSet.of("name")),
+
+                MakePrivileges.table("internal", internalDb, view4)
+                        .allowSelectColumns(user, ImmutableSet.of("id"))
+        );
+
+        AccessControllerManager accessManager = 
Env.getCurrentEnv().getAccessManager();
+        CatalogAccessController catalogAccessController = 
accessManager.getAccessControllerOrDefault(catalog);
+        new Expectations(accessManager) {
+            {
+                accessManager.getAccessControllerOrDefault("internal");
+                minTimes = 0;
+                result = catalogAccessController;
+            }
+        };
+
+        withPrivileges(privileges, () -> {
+                // test base table
+                {
+                    // has table privilege
+                    query("select * from custom_catalog.test_db.test_tbl1");
+
+                    // has id column privilege
+                    query("select id from custom_catalog.test_db.test_tbl2");
+
+                    // no name column privilege, throw exception:
+                    //
+                    // Permission denied: user 
['test_nereids_privilege_user'@'%'] does not have privilege for
+                    // [priv predicate: OR, Admin_priv Select_priv ] command on
+                    // [custom_catalog].[test_db].[test_tbl2].[name]
+                    Assertions.assertThrows(AnalysisException.class, () ->
+                            query("select * from 
custom_catalog.test_db.test_tbl2")
+                    );
+
+                    // no table privilege
+                    Assertions.assertThrows(AnalysisException.class, () ->
+                            query("select * from 
custom_catalog.test_db.test_tbl3")
+                    );
+                }
+
+                // test view
+                {
+                    // has view privilege
+                    query("select * from " + internalDb + "." + view1);
+
+                    // has view name privilege
+                    query("select name from " + internalDb + "." + view2);
+
+                    // no id column privilege
+                    Assertions.assertThrows(AnalysisException.class, () ->
+                            query("select id from " + internalDb + "." + view2)
+                    );
+
+                    // no view privilege
+                    Assertions.assertThrows(AnalysisException.class, () ->
+                            query("select * from " + internalDb + "." + view3)
+                    );
+
+                    // has id column privilege
+                    query("select id from " + internalDb + "." + view4);
+
+                    // no name column privilege
+                    Assertions.assertThrows(AnalysisException.class, () ->
+                            query("select name from " + internalDb + "." + 
view4)
+                    );
+                }
+        });
+    }
+
+    private void query(String sql) {
+        PlanChecker.from(connectContext)
+                .parse(sql)
+                .analyze()
+                .rewrite();
+    }
+
+    private void withPrivileges(List<MakeTablePrivileges> privileges, Runnable 
task) {
+        List<TablePrivilege> tablePrivileges = Lists.newArrayList();
+        List<ColumnPrivilege> columnPrivileges = Lists.newArrayList();
+
+        for (MakeTablePrivileges privilege : privileges) {
+            tablePrivileges.addAll(privilege.tablePrivileges);
+            columnPrivileges.addAll(privilege.columnPrivileges);
+        }
+
+        SimpleCatalogAccessController.tablePrivileges.set(tablePrivileges);
+        SimpleCatalogAccessController.columnPrivileges.set(columnPrivileges);
+
+        try {
+            task.run();
+        } finally {
+            SimpleCatalogAccessController.tablePrivileges.remove();
+            SimpleCatalogAccessController.columnPrivileges.remove();
+        }
+    }
+
+    public static class CustomCatalogProvider implements TestCatalogProvider {
+
+        @Override
+        public Map<String, Map<String, List<Column>>> getMetadata() {
+            return CATALOG_META;
+        }
+    }
+
+    public static class CustomAccessControllerFactory implements 
AccessControllerFactory {
+        @Override
+        public CatalogAccessController createAccessController(Map<String, 
String> prop) {
+            return new SimpleCatalogAccessController();
+        }
+    }
+
+    public static class SimpleCatalogAccessController implements 
CatalogAccessController {
+        private static ThreadLocal<List<TablePrivilege>> tablePrivileges = new 
ThreadLocal<>();
+        private static ThreadLocal<List<ColumnPrivilege>> columnPrivileges = 
new ThreadLocal<>();
+
+        @Override
+        public boolean checkGlobalPriv(UserIdentity currentUser, PrivPredicate 
wanted) {
+            return true;
+        }
+
+        @Override
+        public boolean checkCtlPriv(UserIdentity currentUser, String ctl, 
PrivPredicate wanted) {
+            return true;
+        }
+
+        @Override
+        public boolean checkDbPriv(UserIdentity currentUser, String ctl, 
String db, PrivPredicate wanted) {
+            return true;
+        }
+
+        @Override
+        public boolean checkTblPriv(UserIdentity currentUser, String ctl, 
String db, String tbl, PrivPredicate wanted) {
+            List<TablePrivilege> tablePrivileges = 
SimpleCatalogAccessController.tablePrivileges.get();
+            if (!CollectionUtils.isEmpty(tablePrivileges)
+                    && tablePrivileges.stream().anyMatch(p -> 
p.checkTblPriv(currentUser, ctl, db, tbl))) {
+                return true;
+            }
+            List<ColumnPrivilege> columnPrivileges = 
SimpleCatalogAccessController.columnPrivileges.get();
+            if (!CollectionUtils.isEmpty(columnPrivileges)
+                    && columnPrivileges.stream().anyMatch(p -> 
p.checkTblPriv(currentUser, ctl, db, tbl))) {
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public boolean checkResourcePriv(UserIdentity currentUser, String 
resourceName, PrivPredicate wanted) {
+            return true;
+        }
+
+        @Override
+        public boolean checkWorkloadGroupPriv(UserIdentity currentUser, String 
workloadGroupName,
+                PrivPredicate wanted) {
+            return true;
+        }
+
+        @Override
+        public void checkColsPriv(UserIdentity currentUser, String ctl, String 
db, String tbl, Set<String> cols,
+                PrivPredicate wanted) throws AuthorizationException {
+            List<TablePrivilege> tablePrivileges = 
SimpleCatalogAccessController.tablePrivileges.get();
+            if (!CollectionUtils.isEmpty(tablePrivileges)
+                    && tablePrivileges.stream().anyMatch(p -> 
p.checkTblPriv(currentUser, ctl, db, tbl))) {
+                return;
+            }
+
+            List<ColumnPrivilege> columnPrivileges = 
SimpleCatalogAccessController.columnPrivileges.get();
+            if (CollectionUtils.isEmpty(columnPrivileges)) {
+                String format = "Permission denied: user [%s] does not have 
privilege "
+                        + "for [%s] command on [%s].[%s].[%s].[%s]";
+                throw new AuthorizationException(String.format(
+                        format,
+                        currentUser, wanted, ctl, db, tbl, 
cols.iterator().next()));
+            }
+
+            for (String col : cols) {
+                boolean hasPrivilege = columnPrivileges.stream()
+                        .anyMatch(t -> t.checkColsPriv(currentUser, ctl, db, 
tbl, col));
+                if (!hasPrivilege) {
+                    String format = "Permission denied: user [%s] does not 
have privilege "
+                            + "for [%s] command on [%s].[%s].[%s].[%s]";
+                    throw new AuthorizationException(String.format(
+                            format,
+                            currentUser, wanted, ctl, db, tbl, col));
+                }
+            }
+        }
+
+        @Override
+        public boolean checkCloudPriv(UserIdentity currentUser, String 
resourceName, PrivPredicate wanted,
+                ResourceTypeEnum type) {
+            return true;
+        }
+    }
+
+    private static class MakePrivileges {
+        public static MakeTablePrivileges table(String catalog, String db, 
String table) {
+            return new MakeTablePrivileges(catalog, db, table);
+        }
+    }
+
+    private static class MakeTablePrivileges {
+        private String catalog;
+        private String db;
+        private String table;
+
+        private List<TablePrivilege> tablePrivileges;
+        private List<ColumnPrivilege> columnPrivileges;
+
+        public MakeTablePrivileges(String catalog, String db, String table) {
+            this.catalog = catalog;
+            this.db = db;
+            this.table = table;
+            this.tablePrivileges = Lists.newArrayList();
+            this.columnPrivileges = Lists.newArrayList();
+        }
+
+        public MakeTablePrivileges allowSelectTable(String user) {
+            tablePrivileges.add(new TablePrivilege(catalog, db, table, user));
+            return this;
+        }
+
+        public MakeTablePrivileges allowSelectColumns(String user, Set<String> 
allowColumns) {
+            columnPrivileges.add(new ColumnPrivilege(catalog, db, table, user, 
allowColumns));
+            return this;
+        }
+    }
+
+    private static class TablePrivilege {
+        private final String catalog;
+        private final String db;
+        private final String table;
+        private final String user;
+
+        public TablePrivilege(String catalog, String db, String table, String 
user) {
+            this.catalog = catalog;
+            this.db = db;
+            this.table = table;
+            this.user = user;
+        }
+
+        public boolean checkTblPriv(UserIdentity currentUser, String ctl, 
String db, String tbl) {
+            return isSameTable(ctl, db, tbl) && StringUtils.equals(this.user, 
currentUser.getUser());
+        }
+
+        public boolean isSameTable(String catalog, String db, String tbl) {
+            return StringUtils.equals(this.catalog, catalog)
+                    && StringUtils.equals(this.db, db)
+                    && StringUtils.equals(this.table, tbl);
+        }
+    }
+
+    private static class ColumnPrivilege {
+        private final String catalog;
+        private final String db;
+        private final String table;
+        private final String user;
+        private final Set<String> allowColumns;
+
+        public ColumnPrivilege(String catalog, String db, String table, String 
user, Set<String> allowColumns) {
+            this.catalog = catalog;
+            this.db = db;
+            this.table = table;
+            this.user = user;
+            this.allowColumns = allowColumns;
+        }
+
+        public boolean checkTblPriv(UserIdentity currentUser, String ctl, 
String db, String tbl) {
+            return isSameTable(ctl, db, tbl) && StringUtils.equals(this.user, 
currentUser.getUser());
+        }
+
+        public boolean checkColsPriv(UserIdentity currentUser, String ctl, 
String db, String tbl, String col) {
+            return isSameTable(ctl, db, tbl)
+                    && StringUtils.equals(this.user, currentUser.getUser()) && 
allowColumns.contains(col);
+        }
+
+        public boolean isSameTable(String catalog, String db, String tbl) {
+            return StringUtils.equals(this.catalog, catalog)
+                    && StringUtils.equals(this.db, db)
+                    && StringUtils.equals(this.table, tbl);
+        }
+    }
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/ViewTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/ViewTest.java
index 466ec87cc7e..d55a034710a 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/ViewTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/ViewTest.java
@@ -24,6 +24,7 @@ import 
org.apache.doris.nereids.glue.translator.PlanTranslatorContext;
 import org.apache.doris.nereids.parser.NereidsParser;
 import org.apache.doris.nereids.properties.PhysicalProperties;
 import 
org.apache.doris.nereids.rules.analysis.LogicalSubQueryAliasToLogicalProject;
+import org.apache.doris.nereids.rules.rewrite.InlineLogicalView;
 import org.apache.doris.nereids.rules.rewrite.MergeProjects;
 import org.apache.doris.nereids.trees.plans.physical.PhysicalPlan;
 import org.apache.doris.nereids.util.MemoPatternMatchSupported;
@@ -141,6 +142,7 @@ public class ViewTest extends TestWithFeService implements 
MemoPatternMatchSuppo
                         + "ON X.ID1 = Y.ID3"
                 )
                 .applyTopDown(new LogicalSubQueryAliasToLogicalProject())
+                .applyBottomUp(new InlineLogicalView())
                 .applyTopDown(new MergeProjects())
                 .matches(
                         logicalProject(
@@ -148,10 +150,10 @@ public class ViewTest extends TestWithFeService 
implements MemoPatternMatchSuppo
                                         logicalProject(
                                                 logicalJoin(
                                                         logicalProject(
-                                                                
logicalOlapScan()
+                                                                    
logicalOlapScan()
                                                         ),
                                                         logicalProject(
-                                                                
logicalOlapScan()
+                                                                    
logicalOlapScan()
                                                         )
                                                 )
                                         ),
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/qe/OlapQueryCacheTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/qe/OlapQueryCacheTest.java
index 359775a8009..ea02a89c2f5 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/qe/OlapQueryCacheTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/qe/OlapQueryCacheTest.java
@@ -102,7 +102,7 @@ import java.util.List;
 public class OlapQueryCacheTest {
     private static final Logger LOG = 
LogManager.getLogger(OlapQueryCacheTest.class);
     public static String fullDbName = "testDb";
-    public static String userName = "testUser";
+    public static String userName = "root";
 
     private static ConnectContext context;
 
@@ -256,7 +256,7 @@ public class OlapQueryCacheTest {
 
                 ctx.getCurrentUserIdentity();
                 minTimes = 0;
-                UserIdentity userIdentity = new UserIdentity(userName, 
"192.168.1.1");
+                UserIdentity userIdentity = new UserIdentity(userName, "%");
                 userIdentity.setIsAnalyzed();
                 result = userIdentity;
             }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/utframe/TestWithFeService.java 
b/fe/fe-core/src/test/java/org/apache/doris/utframe/TestWithFeService.java
index 65070dd504b..58b55f0a7b5 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/utframe/TestWithFeService.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/utframe/TestWithFeService.java
@@ -29,6 +29,7 @@ import org.apache.doris.analysis.CreatePolicyStmt;
 import org.apache.doris.analysis.CreateSqlBlockRuleStmt;
 import org.apache.doris.analysis.CreateTableAsSelectStmt;
 import org.apache.doris.analysis.CreateTableStmt;
+import org.apache.doris.analysis.CreateUserStmt;
 import org.apache.doris.analysis.CreateViewStmt;
 import org.apache.doris.analysis.DropDbStmt;
 import org.apache.doris.analysis.DropPolicyStmt;
@@ -70,6 +71,7 @@ import 
org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
 import org.apache.doris.nereids.util.MemoTestUtils;
 import org.apache.doris.planner.Planner;
 import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.qe.DdlExecutor;
 import org.apache.doris.qe.OriginStatement;
 import org.apache.doris.qe.QueryState;
 import org.apache.doris.qe.SessionVariable;
@@ -754,12 +756,22 @@ public abstract class TestWithFeService {
     }
 
     protected void useUser(String userName) throws AnalysisException {
-        UserIdentity user = new UserIdentity(userName, "%");
+        useUser(userName, "%");
+    }
+
+    protected void useUser(String userName, String host) throws 
AnalysisException {
+        UserIdentity user = new UserIdentity(userName, host);
         user.analyze();
         connectContext.setCurrentUserIdentity(user);
         connectContext.setQualifiedUser(userName);
     }
 
+    protected void addUser(String userName, boolean ifNotExists) throws 
Exception {
+        CreateUserStmt createUserStmt = (CreateUserStmt) 
UtFrameUtils.parseAndAnalyzeStmt(
+                "create user " + (ifNotExists ? "if not exists " : "") + 
userName + "@'%'", connectContext);
+        DdlExecutor.execute(Env.getCurrentEnv(), createUserStmt);
+    }
+
     protected void addRollup(String sql) throws Exception {
         AlterTableStmt alterTableStmt = (AlterTableStmt) 
UtFrameUtils.parseAndAnalyzeStmt(sql, connectContext);
         Env.getCurrentEnv().alterTable(alterTableStmt);
diff --git a/regression-test/suites/account_p0/test_nereids_row_policy.groovy 
b/regression-test/suites/account_p0/test_nereids_row_policy.groovy
index a803e4bcda4..6104416f49a 100644
--- a/regression-test/suites/account_p0/test_nereids_row_policy.groovy
+++ b/regression-test/suites/account_p0/test_nereids_row_policy.groovy
@@ -37,7 +37,7 @@ suite("test_nereids_row_policy") {
             sql "set enable_fallback_to_original_planner = false"
             test {
                 sql "SELECT * FROM ${viewName}"
-                exception "SELECT command denied to user"
+                exception "does not have privilege for"
             }
         }
         assertEquals(size, result1.size())
diff --git 
a/regression-test/suites/nereids_p0/authorization/view_authorization.groovy 
b/regression-test/suites/nereids_p0/authorization/view_authorization.groovy
index 672cd680ec8..aa76ccba3df 100644
--- a/regression-test/suites/nereids_p0/authorization/view_authorization.groovy
+++ b/regression-test/suites/nereids_p0/authorization/view_authorization.groovy
@@ -57,7 +57,7 @@ suite("view_authorization") {
         // no privilege to base table
         test {
             sql "select * from ${db}.${baseTable}"
-            exception "SELECT command denied to user"
+            exception "does not have privilege for"
         }
 
         // has privilege to view1
@@ -69,7 +69,7 @@ suite("view_authorization") {
         // no privilege to view2
         test {
             sql "select * from ${db}.${view2}"
-            exception "SELECT command denied to user"
+            exception "does not have privilege for"
         }
 
         // nested view


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org
For additional commands, e-mail: commits-h...@doris.apache.org

Reply via email to