From 130c2cf032d2e90fa6d0ba2d52b90f27d4a56343 Mon Sep 17 00:00:00 2001
From: 924060929 <924060929@qq.com>
Date: Fri, 12 Apr 2024 19:54:36 +0800
Subject: [PATCH 1/5] [enhancement](Nereids) Enable parse sql from sql cache
(#33262)
Before this pr, the query must pass through parser, analyzer, rewriter, optimizer and translator, then we can check whether this query can use sql cache, if the query is too long, or the number of join tables too big, the plan time usually >= 500ms.
This pr reduce this time by skip the fashion plan path, because we can reuse the previous physical plan and query result if no any changed. In some cases we should not parse sql from sql cache, e.g. table structure changed, data changed, user policies changed, privileges changed, contains non-deterministic functions, and user variables changed.
In my test case: query a view which has lots of join and union, and the tables has empty partition, the query latency is about 3ms. if not parse sql from sql cache, the plan time is about 550ms
## Features
1. use Config.sql_cache_manage_num to control how many sql cache be reused in on fe
2. if explain plan appear some plans contains `LogicalSqlCache` or `PhysicalSqlCache`, it means the query can use sql cache, like this:
```sql
mysql> set enable_sql_cache=true;
Query OK, 0 rows affected (0.00 sec)
mysql> explain physical plan select * from test.t;
+----------------------------------------------------------------------------------+
| Explain String(Nereids Planner) |
+----------------------------------------------------------------------------------+
| cost = 3.135 |
| PhysicalResultSink[53] ( outputExprs=[c1#0, c2#1] ) |
| +--PhysicalDistribute[50]@0 ( stats=3, distributionSpec=DistributionSpecGather ) |
| +--PhysicalOlapScan[t]@0 ( stats=3 ) |
+----------------------------------------------------------------------------------+
4 rows in set (0.02 sec)
mysql> select * from test.t;
+------+------+
| c1 | c2 |
+------+------+
| 1 | 2 |
| -2 | -2 |
| NULL | 30 |
+------+------+
3 rows in set (0.05 sec)
mysql> explain physical plan select * from test.t;
+-------------------------------------------------------------------------------------------+
| Explain String(Nereids Planner) |
+-------------------------------------------------------------------------------------------+
| cost = 0.0 |
| PhysicalSqlCache[2] ( queryId=78511f515cda466b-95385d892d6c68d0, backend=127.0.0.1:9050 ) |
| +--PhysicalResultSink[52] ( outputExprs=[c1#0, c2#1] ) |
| +--PhysicalDistribute[49]@0 ( stats=3, distributionSpec=DistributionSpecGather ) |
| +--PhysicalOlapScan[t]@0 ( stats=3 ) |
+-------------------------------------------------------------------------------------------+
5 rows in set (0.01 sec)
```
(cherry picked from commit 03bd2a337d4a56ea9c91673b3bd4ae518ed10f20)
---
.../java/org/apache/doris/common/Config.java | 8 +
.../java/org/apache/doris/common/Pair.java | 4 +
.../doris/blockrule/SqlBlockRuleMgr.java | 8 +-
.../java/org/apache/doris/catalog/Env.java | 10 +
.../doris/common/NereidsSqlCacheManager.java | 360 ++++++++++++
.../doris/common/profile/SummaryProfile.java | 2 +-
.../apache/doris/nereids/CascadesContext.java | 6 +-
.../apache/doris/nereids/NereidsPlanner.java | 217 ++++---
.../apache/doris/nereids/SqlCacheContext.java | 353 +++++++++++
.../doris/nereids/StatementContext.java | 118 +++-
.../doris/nereids/parser/NereidsParser.java | 113 ++++
.../rules/analysis/BindExpression.java | 16 +-
.../nereids/rules/analysis/BindRelation.java | 118 ++--
.../rules/analysis/ExpressionAnalyzer.java | 73 ++-
.../expression/rules/FunctionBinder.java | 4 +-
.../rules/ReplaceVariableByLiteral.java | 13 +-
.../rules/rewrite/CheckPrivileges.java | 12 +-
.../expressions/ExpressionEvaluator.java | 13 +-
.../functions/AggCombinerFunctionBuilder.java | 17 +-
.../functions/BuiltinFunctionBuilder.java | 7 +-
.../functions/FunctionBuilder.java | 10 +-
.../functions/scalar/ConnectionId.java | 3 +-
.../functions/scalar/CurrentUser.java | 3 +-
.../functions/scalar/Database.java | 3 +-
.../expressions/functions/scalar/User.java | 3 +-
.../functions/table/TableValuedFunction.java | 4 +-
.../functions/udf/AliasUdfBuilder.java | 5 +-
.../functions/udf/JavaUdafBuilder.java | 21 +-
.../functions/udf/JavaUdfBuilder.java | 7 +-
.../expressions/literal/DateLiteral.java | 40 +-
.../expressions/literal/DateTimeLiteral.java | 77 ++-
.../literal/DateTimeV2Literal.java | 109 ++--
.../expressions/literal/DateV2Literal.java | 13 +-
.../nereids/trees/plans/AbstractPlan.java | 30 +-
.../doris/nereids/trees/plans/PlanType.java | 2 +
.../nereids/trees/plans/TreeStringPlan.java | 112 ++++
.../nereids/trees/plans/algebra/SqlCache.java | 36 ++
.../plans/logical/LogicalCheckPolicy.java | 15 +-
.../trees/plans/logical/LogicalSqlCache.java | 147 +++++
.../plans/physical/PhysicalSqlCache.java | 139 +++++
.../trees/plans/visitor/PlanVisitor.java | 8 +
.../org/apache/doris/qe/ConnectProcessor.java | 191 ++++--
.../org/apache/doris/qe/StmtExecutor.java | 60 +-
.../java/org/apache/doris/qe/cache/Cache.java | 19 +-
.../apache/doris/qe/cache/CacheAnalyzer.java | 156 ++++-
.../doris/qe/cache/CacheCoordinator.java | 5 +-
.../org/apache/doris/qe/cache/SqlCache.java | 56 +-
.../rules/analysis/FunctionRegistryTest.java | 2 +-
.../regression/action/ExplainAction.groovy | 4 +-
.../doris/regression/suite/Suite.groovy | 74 ++-
regression-test/plugins/test_helper.groovy | 63 ++
.../cache/parse_sql_from_sql_cache.groovy | 550 ++++++++++++++++++
.../suites/query_p0/cache/sql_cache.groovy | 2 +-
.../test_schema_change_duplicate.groovy | 1 -
54 files changed, 3021 insertions(+), 421 deletions(-)
create mode 100644 fe/fe-core/src/main/java/org/apache/doris/common/NereidsSqlCacheManager.java
create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/SqlCacheContext.java
create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/TreeStringPlan.java
create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/algebra/SqlCache.java
create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalSqlCache.java
create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/physical/PhysicalSqlCache.java
create mode 100644 regression-test/plugins/test_helper.groovy
create mode 100644 regression-test/suites/nereids_p0/cache/parse_sql_from_sql_cache.groovy
diff --git a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
index 8dff0e3f4db82b..4f2f67ac7a18c6 100644
--- a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
+++ b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
@@ -2008,6 +2008,14 @@ public class Config extends ConfigBase {
+ "the old load statement will be degraded."})
public static boolean enable_nereids_load = false;
+ /**
+ * the plan cache num which can be reused for the next query
+ */
+ @ConfField(mutable = false, varType = VariableAnnotation.EXPERIMENTAL, description = {
+ "当前默认设置为 100,用来控制控制NereidsSqlCacheManager管理的sql cache数量。",
+ "Now default set to 100, this config is used to control the number of "
+ + "sql cache managed by NereidsSqlCacheManager"})
+ public static int sql_cache_manage_num = 100;
/**
* Maximum number of events to poll in each RPC.
diff --git a/fe/fe-common/src/main/java/org/apache/doris/common/Pair.java b/fe/fe-common/src/main/java/org/apache/doris/common/Pair.java
index a699284676131c..3a8b1940d59759 100644
--- a/fe/fe-common/src/main/java/org/apache/doris/common/Pair.java
+++ b/fe/fe-common/src/main/java/org/apache/doris/common/Pair.java
@@ -43,6 +43,10 @@ private Pair(F first, S second) {
this.second = second;
}
+ public static
Pair ofSame(K same) {
+ return new Pair<>(same, same);
+ }
+
public static Pair of(F first, S second) {
return new Pair<>(first, second);
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/blockrule/SqlBlockRuleMgr.java b/fe/fe-core/src/main/java/org/apache/doris/blockrule/SqlBlockRuleMgr.java
index ca4e68a6ae2f9c..2eba1d4fae3385 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/blockrule/SqlBlockRuleMgr.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/blockrule/SqlBlockRuleMgr.java
@@ -269,10 +269,10 @@ public void checkLimitations(Long partitionNum, Long tabletNum, Long cardinality
return;
}
// match global rule
- List globalRules =
- nameToSqlBlockRuleMap.values().stream().filter(SqlBlockRule::getGlobal).collect(Collectors.toList());
- for (SqlBlockRule rule : globalRules) {
- checkLimitations(rule, partitionNum, tabletNum, cardinality);
+ for (SqlBlockRule rule : nameToSqlBlockRuleMap.values()) {
+ if (rule.getGlobal()) {
+ checkLimitations(rule, partitionNum, tabletNum, cardinality);
+ }
}
// match user rule
String[] bindSqlBlockRules = Env.getCurrentEnv().getAuth().getSqlBlockRules(user);
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
index 1012b9c2033d01..71bcdec4f29198 100755
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
@@ -105,6 +105,7 @@
import org.apache.doris.common.FeConstants;
import org.apache.doris.common.FeMetaVersion;
import org.apache.doris.common.MetaNotFoundException;
+import org.apache.doris.common.NereidsSqlCacheManager;
import org.apache.doris.common.Pair;
import org.apache.doris.common.ThreadPoolManager;
import org.apache.doris.common.UserException;
@@ -529,6 +530,8 @@ public class Env {
private DNSCache dnsCache;
+ private final NereidsSqlCacheManager sqlCacheManager;
+
public List getFrontendInfos() {
List res = new ArrayList<>();
@@ -764,6 +767,9 @@ public Env(boolean isCheckpointCatalog) {
this.mtmvService = new MTMVService();
this.insertOverwriteManager = new InsertOverwriteManager();
this.dnsCache = new DNSCache();
+ this.sqlCacheManager = new NereidsSqlCacheManager(
+ Config.sql_cache_manage_num, Config.cache_last_version_interval_second
+ );
}
public static void destroyCheckpoint() {
@@ -6052,6 +6058,10 @@ public StatisticsAutoCollector getStatisticsAutoCollector() {
return statisticsAutoCollector;
}
+ public NereidsSqlCacheManager getSqlCacheManager() {
+ return sqlCacheManager;
+ }
+
public void alterMTMVRefreshInfo(AlterMTMVRefreshInfo info) {
AlterMTMV alter = new AlterMTMV(info.getMvName(), info.getRefreshInfo(), MTMVAlterOpType.ALTER_REFRESH_INFO);
this.alter.processAlterMTMV(alter, false);
diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/NereidsSqlCacheManager.java b/fe/fe-core/src/main/java/org/apache/doris/common/NereidsSqlCacheManager.java
new file mode 100644
index 00000000000000..f3643046cade04
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/NereidsSqlCacheManager.java
@@ -0,0 +1,360 @@
+// 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.common;
+
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.catalog.Database;
+import org.apache.doris.catalog.DatabaseIf;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.catalog.OlapTable;
+import org.apache.doris.catalog.Partition;
+import org.apache.doris.catalog.Table;
+import org.apache.doris.catalog.TableIf;
+import org.apache.doris.catalog.View;
+import org.apache.doris.datasource.CatalogIf;
+import org.apache.doris.metric.MetricRepo;
+import org.apache.doris.mysql.privilege.DataMaskPolicy;
+import org.apache.doris.mysql.privilege.RowFilterPolicy;
+import org.apache.doris.nereids.CascadesContext;
+import org.apache.doris.nereids.SqlCacheContext;
+import org.apache.doris.nereids.SqlCacheContext.FullColumnName;
+import org.apache.doris.nereids.SqlCacheContext.FullTableName;
+import org.apache.doris.nereids.SqlCacheContext.ScanTable;
+import org.apache.doris.nereids.StatementContext;
+import org.apache.doris.nereids.analyzer.UnboundVariable;
+import org.apache.doris.nereids.properties.PhysicalProperties;
+import org.apache.doris.nereids.rules.analysis.ExpressionAnalyzer;
+import org.apache.doris.nereids.rules.analysis.UserAuthentication;
+import org.apache.doris.nereids.rules.expression.ExpressionRewriteContext;
+import org.apache.doris.nereids.rules.expression.rules.FoldConstantRuleOnFE;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.Variable;
+import org.apache.doris.nereids.trees.expressions.functions.Nondeterministic;
+import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.RelationId;
+import org.apache.doris.nereids.trees.plans.logical.LogicalEmptyRelation;
+import org.apache.doris.nereids.trees.plans.logical.LogicalSqlCache;
+import org.apache.doris.proto.InternalService;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.qe.cache.CacheAnalyzer;
+import org.apache.doris.qe.cache.SqlCache;
+
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.google.common.collect.ImmutableList;
+import org.apache.commons.collections.CollectionUtils;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+/** NereidsSqlCacheManager */
+public class NereidsSqlCacheManager {
+ // key: :
+ // value: CacheAnalyzer
+ private final Cache sqlCache;
+
+ public NereidsSqlCacheManager(int sqlCacheNum, long cacheIntervalSeconds) {
+ sqlCache = Caffeine.newBuilder()
+ .maximumSize(sqlCacheNum)
+ .expireAfterAccess(Duration.ofSeconds(cacheIntervalSeconds))
+ // auto evict cache when jvm memory too low
+ .softValues()
+ .build();
+ }
+
+ /** tryAddCache */
+ public void tryAddCache(
+ ConnectContext connectContext, String sql,
+ CacheAnalyzer analyzer, boolean currentMissParseSqlFromSqlCache) {
+ Optional sqlCacheContextOpt = connectContext.getStatementContext().getSqlCacheContext();
+ if (!sqlCacheContextOpt.isPresent()) {
+ return;
+ }
+ if (!(analyzer.getCache() instanceof SqlCache)) {
+ return;
+ }
+ SqlCacheContext sqlCacheContext = sqlCacheContextOpt.get();
+ UserIdentity currentUserIdentity = connectContext.getCurrentUserIdentity();
+ String key = currentUserIdentity.toString() + ":" + sql.trim();
+ if (analyzer.getCache() instanceof SqlCache
+ && (currentMissParseSqlFromSqlCache || sqlCache.getIfPresent(key) == null)) {
+ SqlCache cache = (SqlCache) analyzer.getCache();
+ sqlCacheContext.setCacheKeyMd5(cache.getOrComputeCacheMd5());
+ sqlCacheContext.setSumOfPartitionNum(cache.getSumOfPartitionNum());
+ sqlCacheContext.setLatestPartitionId(cache.getLatestId());
+ sqlCacheContext.setLatestPartitionVersion(cache.getLatestVersion());
+ sqlCacheContext.setLatestPartitionTime(cache.getLatestTime());
+ sqlCacheContext.setCacheProxy(cache.getProxy());
+
+ for (ScanTable scanTable : analyzer.getScanTables()) {
+ sqlCacheContext.addScanTable(scanTable);
+ }
+
+ sqlCache.put(key, sqlCacheContext);
+ }
+ }
+
+ /** invalidateCache */
+ public void invalidateCache(ConnectContext connectContext, String sql) {
+ UserIdentity currentUserIdentity = connectContext.getCurrentUserIdentity();
+ String key = currentUserIdentity.toString() + ":" + sql.trim();
+ sqlCache.invalidate(key);
+ }
+
+ /** tryParseSql */
+ public Optional tryParseSql(ConnectContext connectContext, String sql) {
+ UserIdentity currentUserIdentity = connectContext.getCurrentUserIdentity();
+ Env env = connectContext.getEnv();
+ String key = currentUserIdentity.toString() + ":" + sql.trim();
+ SqlCacheContext sqlCacheContext = sqlCache.getIfPresent(key);
+ if (sqlCacheContext == null) {
+ return Optional.empty();
+ }
+
+ // LOG.info("Total size: " + GraphLayout.parseInstance(sqlCacheContext).totalSize());
+
+ // check table and view and their columns authority
+ if (privilegeChanged(connectContext, env, sqlCacheContext)) {
+ return invalidateCache(key);
+ }
+ if (tablesOrDataChanged(env, sqlCacheContext)) {
+ return invalidateCache(key);
+ }
+ if (viewsChanged(env, sqlCacheContext)) {
+ return invalidateCache(key);
+ }
+ if (usedVariablesChanged(sqlCacheContext)) {
+ return invalidateCache(key);
+ }
+
+ LogicalEmptyRelation whateverPlan = new LogicalEmptyRelation(new RelationId(0), ImmutableList.of());
+ if (nondeterministicFunctionChanged(whateverPlan, connectContext, sqlCacheContext)) {
+ return invalidateCache(key);
+ }
+
+ // table structure and data not changed, now check policy
+ if (rowPoliciesChanged(currentUserIdentity, env, sqlCacheContext)) {
+ return invalidateCache(key);
+ }
+ if (dataMaskPoliciesChanged(currentUserIdentity, env, sqlCacheContext)) {
+ return invalidateCache(key);
+ }
+
+ try {
+ Status status = new Status();
+ InternalService.PFetchCacheResult cacheData =
+ SqlCache.getCacheData(sqlCacheContext.getCacheProxy(),
+ sqlCacheContext.getCacheKeyMd5(), sqlCacheContext.getLatestPartitionId(),
+ sqlCacheContext.getLatestPartitionVersion(), sqlCacheContext.getLatestPartitionTime(),
+ sqlCacheContext.getSumOfPartitionNum(), status);
+
+ if (status.ok() && cacheData != null && cacheData.getStatus() == InternalService.PCacheStatus.CACHE_OK) {
+ List cacheValues = cacheData.getValuesList();
+ String cachedPlan = sqlCacheContext.getPhysicalPlan();
+ String backendAddress = SqlCache.findCacheBe(sqlCacheContext.getCacheKeyMd5()).getAddress();
+
+ MetricRepo.COUNTER_CACHE_HIT_SQL.increase(1L);
+
+ LogicalSqlCache logicalSqlCache = new LogicalSqlCache(
+ sqlCacheContext.getQueryId(), sqlCacheContext.getColLabels(),
+ sqlCacheContext.getResultExprs(), cacheValues, backendAddress, cachedPlan);
+ return Optional.of(logicalSqlCache);
+ }
+ return Optional.empty();
+ } catch (Throwable t) {
+ return Optional.empty();
+ }
+ }
+
+ private boolean tablesOrDataChanged(Env env, SqlCacheContext sqlCacheContext) {
+ long latestPartitionTime = sqlCacheContext.getLatestPartitionTime();
+ long latestPartitionVersion = sqlCacheContext.getLatestPartitionVersion();
+
+ if (sqlCacheContext.hasUnsupportedTables()) {
+ return true;
+ }
+
+ for (ScanTable scanTable : sqlCacheContext.getScanTables()) {
+ FullTableName fullTableName = scanTable.fullTableName;
+ TableIf tableIf = findTableIf(env, fullTableName);
+ if (!(tableIf instanceof OlapTable)) {
+ return true;
+ }
+ OlapTable olapTable = (OlapTable) tableIf;
+ long currentTableTime = olapTable.getVisibleVersionTime();
+ long cacheTableTime = scanTable.latestTimestamp;
+ long currentTableVersion = olapTable.getVisibleVersion();
+ long cacheTableVersion = scanTable.latestVersion;
+ // some partitions have been dropped, or delete or update or insert rows into new partition?
+ if (currentTableTime > cacheTableTime
+ || (currentTableTime == cacheTableTime && currentTableVersion > cacheTableVersion)) {
+ return true;
+ }
+
+ for (Long scanPartitionId : scanTable.getScanPartitions()) {
+ Partition partition = olapTable.getPartition(scanPartitionId);
+ // partition == null: is this partition truncated?
+ if (partition == null || partition.getVisibleVersionTime() > latestPartitionTime
+ || (partition.getVisibleVersionTime() == latestPartitionTime
+ && partition.getVisibleVersion() > latestPartitionVersion)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean viewsChanged(Env env, SqlCacheContext sqlCacheContext) {
+ for (Entry cacheView : sqlCacheContext.getUsedViews().entrySet()) {
+ TableIf currentView = findTableIf(env, cacheView.getKey());
+ if (currentView == null) {
+ return true;
+ }
+
+ String cacheValueDdlSql = cacheView.getValue();
+ if (currentView instanceof View) {
+ if (!((View) currentView).getInlineViewDef().equals(cacheValueDdlSql)) {
+ return true;
+ }
+ } else {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean rowPoliciesChanged(UserIdentity currentUserIdentity, Env env, SqlCacheContext sqlCacheContext) {
+ for (Entry> kv : sqlCacheContext.getRowPolicies().entrySet()) {
+ FullTableName qualifiedTable = kv.getKey();
+ List extends RowFilterPolicy> cachedPolicies = kv.getValue();
+
+ List extends RowFilterPolicy> rowPolicies = env.getAccessManager().evalRowFilterPolicies(
+ currentUserIdentity, qualifiedTable.catalog, qualifiedTable.db, qualifiedTable.table);
+ if (!CollectionUtils.isEqualCollection(cachedPolicies, rowPolicies)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean dataMaskPoliciesChanged(
+ UserIdentity currentUserIdentity, Env env, SqlCacheContext sqlCacheContext) {
+ for (Entry> kv : sqlCacheContext.getDataMaskPolicies().entrySet()) {
+ FullColumnName qualifiedColumn = kv.getKey();
+ Optional cachedPolicy = kv.getValue();
+
+ Optional dataMaskPolicy = env.getAccessManager()
+ .evalDataMaskPolicy(currentUserIdentity, qualifiedColumn.catalog,
+ qualifiedColumn.db, qualifiedColumn.table, qualifiedColumn.column);
+ if (!Objects.equals(cachedPolicy, dataMaskPolicy)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean privilegeChanged(ConnectContext connectContext, Env env, SqlCacheContext sqlCacheContext) {
+ StatementContext currentStatementContext = connectContext.getStatementContext();
+ for (Entry> kv : sqlCacheContext.getCheckPrivilegeTablesOrViews().entrySet()) {
+ Set usedColumns = kv.getValue();
+ TableIf tableIf = findTableIf(env, kv.getKey());
+ if (tableIf == null) {
+ return true;
+ }
+ // release when close statementContext
+ currentStatementContext.addTableReadLock(tableIf);
+ try {
+ UserAuthentication.checkPermission(tableIf, connectContext, usedColumns);
+ } catch (Throwable t) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean usedVariablesChanged(SqlCacheContext sqlCacheContext) {
+ for (Variable variable : sqlCacheContext.getUsedVariables()) {
+ Variable currentVariable = ExpressionAnalyzer.resolveUnboundVariable(
+ new UnboundVariable(variable.getName(), variable.getType()));
+ if (!Objects.equals(currentVariable, variable)
+ || variable.getRealExpression().anyMatch(Nondeterministic.class::isInstance)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean nondeterministicFunctionChanged(
+ Plan plan, ConnectContext connectContext, SqlCacheContext sqlCacheContext) {
+ if (sqlCacheContext.containsCannotProcessExpression()) {
+ return true;
+ }
+
+ List> nondeterministicFunctions = sqlCacheContext.getFoldNondeterministicPairs();
+ if (nondeterministicFunctions.isEmpty()) {
+ return false;
+ }
+
+ CascadesContext tempCascadeContext = CascadesContext.initContext(
+ connectContext.getStatementContext(), plan, PhysicalProperties.ANY);
+ ExpressionRewriteContext rewriteContext = new ExpressionRewriteContext(tempCascadeContext);
+ for (Pair foldPair : nondeterministicFunctions) {
+ Expression nondeterministic = foldPair.first;
+ Expression deterministic = foldPair.second;
+ Expression fold = nondeterministic.accept(FoldConstantRuleOnFE.VISITOR_INSTANCE, rewriteContext);
+ if (!Objects.equals(deterministic, fold)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isValidDbAndTable(TableIf tableIf, Env env) {
+ return getTableFromEnv(tableIf, env) != null;
+ }
+
+ private TableIf getTableFromEnv(TableIf tableIf, Env env) {
+ Optional db = env.getInternalCatalog().getDb(tableIf.getDatabase().getId());
+ if (!db.isPresent()) {
+ return null;
+ }
+ Optional table = db.get().getTable(tableIf.getId());
+ return table.orElse(null);
+ }
+
+ private Optional invalidateCache(String key) {
+ sqlCache.invalidate(key);
+ return Optional.empty();
+ }
+
+ private TableIf findTableIf(Env env, FullTableName fullTableName) {
+ CatalogIf> catalog = env.getCatalogMgr().getCatalog(fullTableName.catalog);
+ if (catalog == null) {
+ return null;
+ }
+ Optional> db = catalog.getDb(fullTableName.db);
+ if (!db.isPresent()) {
+ return null;
+ }
+ return db.get().getTable(fullTableName.table).orElse(null);
+ }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/profile/SummaryProfile.java b/fe/fe-core/src/main/java/org/apache/doris/common/profile/SummaryProfile.java
index a8f64f1443545e..178a0e802d5bc1 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/common/profile/SummaryProfile.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/profile/SummaryProfile.java
@@ -585,7 +585,7 @@ public Map build() {
}
public String getPrettyParseSqlTime() {
- return getPrettyTime(parseSqlStartTime, parseSqlFinishTime, TUnit.TIME_MS);
+ return getPrettyTime(parseSqlFinishTime, parseSqlStartTime, TUnit.TIME_MS);
}
public String getPrettyNereidsAnalysisTime() {
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 60b7c0343a7001..dd569ef8f7519a 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
@@ -102,7 +102,7 @@ public class CascadesContext implements ScheduleContext {
private Optional currentRootRewriteJobContext;
// in optimize stage, the plan will storage in the memo
private Memo memo;
- private final StatementContext statementContext;
+ private StatementContext statementContext;
private final CTEContext cteContext;
private final RuleSet ruleSet;
@@ -265,6 +265,10 @@ public Memo getMemo() {
return memo;
}
+ public void releaseMemo() {
+ this.memo = null;
+ }
+
public void setTables(List tables) {
this.tables = tables.stream().collect(Collectors.toMap(TableIf::getId, t -> t, (t1, t2) -> t1));
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/NereidsPlanner.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/NereidsPlanner.java
index 7457e4de04a4ef..a19477093c7dcf 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/NereidsPlanner.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/NereidsPlanner.java
@@ -51,10 +51,12 @@
import org.apache.doris.nereids.trees.plans.Plan;
import org.apache.doris.nereids.trees.plans.commands.ExplainCommand.ExplainLevel;
import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalSqlCache;
import org.apache.doris.nereids.trees.plans.physical.PhysicalCatalogRelation;
import org.apache.doris.nereids.trees.plans.physical.PhysicalOneRowRelation;
import org.apache.doris.nereids.trees.plans.physical.PhysicalPlan;
import org.apache.doris.nereids.trees.plans.physical.PhysicalResultSink;
+import org.apache.doris.nereids.trees.plans.physical.PhysicalSqlCache;
import org.apache.doris.planner.PlanFragment;
import org.apache.doris.planner.Planner;
import org.apache.doris.planner.RuntimeFilter;
@@ -94,6 +96,7 @@ public class NereidsPlanner extends Planner {
private PhysicalPlan physicalPlan;
// The cost of optimized plan
private double cost = 0;
+ private LogicalPlanAdapter logicalPlanAdapter;
private List hooks = new ArrayList<>();
public NereidsPlanner(StatementContext statementContext) {
@@ -111,7 +114,7 @@ public void plan(StatementBase queryStmt, org.apache.doris.thrift.TQueryOptions
throw new RuntimeException("Wrong type of queryStmt, expected: extends LogicalPlanAdapter>");
}
- LogicalPlanAdapter logicalPlanAdapter = (LogicalPlanAdapter) queryStmt;
+ logicalPlanAdapter = (LogicalPlanAdapter) queryStmt;
ExplainLevel explainLevel = getExplainLevel(queryStmt.getExplainOptions());
@@ -128,32 +131,7 @@ public void plan(StatementBase queryStmt, org.apache.doris.thrift.TQueryOptions
return;
}
physicalPlan = (PhysicalPlan) resultPlan;
- PlanTranslatorContext planTranslatorContext = new PlanTranslatorContext(cascadesContext);
- PhysicalPlanTranslator physicalPlanTranslator = new PhysicalPlanTranslator(planTranslatorContext,
- statementContext.getConnectContext().getStatsErrorEstimator());
- if (statementContext.getConnectContext().getExecutor() != null) {
- statementContext.getConnectContext().getExecutor().getSummaryProfile().setNereidsTranslateTime();
- }
- if (cascadesContext.getConnectContext().getSessionVariable().isEnableNereidsTrace()) {
- CounterEvent.clearCounter();
- }
- if (cascadesContext.getConnectContext().getSessionVariable().isPlayNereidsDump()) {
- return;
- }
- PlanFragment root = physicalPlanTranslator.translatePlan(physicalPlan);
-
- scanNodeList.addAll(planTranslatorContext.getScanNodes());
- descTable = planTranslatorContext.getDescTable();
- fragments = new ArrayList<>(planTranslatorContext.getPlanFragments());
- for (int seq = 0; seq < fragments.size(); seq++) {
- fragments.get(seq).setFragmentSequenceNum(seq);
- }
- // set output exprs
- logicalPlanAdapter.setResultExprs(root.getOutputExprs());
- ArrayList columnLabelList = physicalPlan.getOutput().stream().map(NamedExpression::getName)
- .collect(Collectors.toCollection(ArrayList::new));
- logicalPlanAdapter.setColLabels(columnLabelList);
- logicalPlanAdapter.setViewDdlSqls(statementContext.getViewDdlSqls());
+ translate(physicalPlan);
}
@VisibleForTesting
@@ -183,84 +161,98 @@ public Plan plan(LogicalPlan plan, PhysicalProperties requireProperties, Explain
*/
public Plan plan(LogicalPlan plan, PhysicalProperties requireProperties,
ExplainLevel explainLevel, boolean showPlanProcess) {
- if (explainLevel == ExplainLevel.PARSED_PLAN || explainLevel == ExplainLevel.ALL_PLAN) {
- parsedPlan = plan;
- if (explainLevel == ExplainLevel.PARSED_PLAN) {
- return parsedPlan;
+ try {
+ if (plan instanceof LogicalSqlCache) {
+ rewrittenPlan = analyzedPlan = plan;
+ LogicalSqlCache logicalSqlCache = (LogicalSqlCache) plan;
+ physicalPlan = new PhysicalSqlCache(
+ logicalSqlCache.getQueryId(), logicalSqlCache.getColumnLabels(),
+ logicalSqlCache.getResultExprs(), logicalSqlCache.getCacheValues(),
+ logicalSqlCache.getBackendAddress(), logicalSqlCache.getPlanBody()
+ );
+ return physicalPlan;
+ }
+ if (explainLevel == ExplainLevel.PARSED_PLAN || explainLevel == ExplainLevel.ALL_PLAN) {
+ parsedPlan = plan;
+ if (explainLevel == ExplainLevel.PARSED_PLAN) {
+ return parsedPlan;
+ }
}
- }
- // pre-process logical plan out of memo, e.g. process SET_VAR hint
- plan = preprocess(plan);
+ // pre-process logical plan out of memo, e.g. process SET_VAR hint
+ plan = preprocess(plan);
- initCascadesContext(plan, requireProperties);
+ initCascadesContext(plan, requireProperties);
- try (Lock lock = new Lock(plan, cascadesContext)) {
- // resolve column, table and function
- // analyze this query
- analyze(showAnalyzeProcess(explainLevel, showPlanProcess));
- // minidump of input must be serialized first, this process ensure minidump string not null
- try {
- MinidumpUtils.serializeInputsToDumpFile(plan, cascadesContext.getTables());
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ try (Lock lock = new Lock(plan, cascadesContext)) {
+ // resolve column, table and function
+ // analyze this query
+ analyze(showAnalyzeProcess(explainLevel, showPlanProcess));
+ // minidump of input must be serialized first, this process ensure minidump string not null
+ try {
+ MinidumpUtils.serializeInputsToDumpFile(plan, cascadesContext.getTables());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
- if (statementContext.getConnectContext().getExecutor() != null) {
- statementContext.getConnectContext().getExecutor().getSummaryProfile().setQueryAnalysisFinishTime();
- statementContext.getConnectContext().getExecutor().getSummaryProfile().setNereidsAnalysisTime();
- }
+ if (statementContext.getConnectContext().getExecutor() != null) {
+ statementContext.getConnectContext().getExecutor().getSummaryProfile().setQueryAnalysisFinishTime();
+ statementContext.getConnectContext().getExecutor().getSummaryProfile().setNereidsAnalysisTime();
+ }
- if (explainLevel == ExplainLevel.ANALYZED_PLAN || explainLevel == ExplainLevel.ALL_PLAN) {
- analyzedPlan = cascadesContext.getRewritePlan();
- if (explainLevel == ExplainLevel.ANALYZED_PLAN) {
- return analyzedPlan;
+ if (explainLevel == ExplainLevel.ANALYZED_PLAN || explainLevel == ExplainLevel.ALL_PLAN) {
+ analyzedPlan = cascadesContext.getRewritePlan();
+ if (explainLevel == ExplainLevel.ANALYZED_PLAN) {
+ return analyzedPlan;
+ }
}
- }
- // rule-based optimize
- rewrite(showRewriteProcess(explainLevel, showPlanProcess));
- if (statementContext.getConnectContext().getExecutor() != null) {
- statementContext.getConnectContext().getExecutor().getSummaryProfile().setNereidsRewriteTime();
- }
+ // rule-based optimize
+ rewrite(showRewriteProcess(explainLevel, showPlanProcess));
+ if (statementContext.getConnectContext().getExecutor() != null) {
+ statementContext.getConnectContext().getExecutor().getSummaryProfile().setNereidsRewriteTime();
+ }
- if (explainLevel == ExplainLevel.REWRITTEN_PLAN || explainLevel == ExplainLevel.ALL_PLAN) {
- rewrittenPlan = cascadesContext.getRewritePlan();
- if (explainLevel == ExplainLevel.REWRITTEN_PLAN) {
- return rewrittenPlan;
+ if (explainLevel == ExplainLevel.REWRITTEN_PLAN || explainLevel == ExplainLevel.ALL_PLAN) {
+ rewrittenPlan = cascadesContext.getRewritePlan();
+ if (explainLevel == ExplainLevel.REWRITTEN_PLAN) {
+ return rewrittenPlan;
+ }
}
- }
- optimize();
- if (statementContext.getConnectContext().getExecutor() != null) {
- statementContext.getConnectContext().getExecutor().getSummaryProfile().setNereidsOptimizeTime();
- }
+ optimize();
+ if (statementContext.getConnectContext().getExecutor() != null) {
+ statementContext.getConnectContext().getExecutor().getSummaryProfile().setNereidsOptimizeTime();
+ }
- // print memo before choose plan.
- // if chooseNthPlan failed, we could get memo to debug
- if (cascadesContext.getConnectContext().getSessionVariable().dumpNereidsMemo) {
- String memo = cascadesContext.getMemo().toString();
- LOG.info(ConnectContext.get().getQueryIdentifier() + "\n" + memo);
- }
+ // print memo before choose plan.
+ // if chooseNthPlan failed, we could get memo to debug
+ if (cascadesContext.getConnectContext().getSessionVariable().dumpNereidsMemo) {
+ String memo = cascadesContext.getMemo().toString();
+ LOG.info(ConnectContext.get().getQueryIdentifier() + "\n" + memo);
+ }
- int nth = cascadesContext.getConnectContext().getSessionVariable().getNthOptimizedPlan();
- PhysicalPlan physicalPlan = chooseNthPlan(getRoot(), requireProperties, nth);
+ int nth = cascadesContext.getConnectContext().getSessionVariable().getNthOptimizedPlan();
+ PhysicalPlan physicalPlan = chooseNthPlan(getRoot(), requireProperties, nth);
- physicalPlan = postProcess(physicalPlan);
- if (cascadesContext.getConnectContext().getSessionVariable().dumpNereidsMemo) {
- String tree = physicalPlan.treeString();
- LOG.info(ConnectContext.get().getQueryIdentifier() + "\n" + tree);
- }
- if (explainLevel == ExplainLevel.OPTIMIZED_PLAN
- || explainLevel == ExplainLevel.ALL_PLAN
- || explainLevel == ExplainLevel.SHAPE_PLAN) {
- optimizedPlan = physicalPlan;
- }
- // serialize optimized plan to dumpfile, dumpfile do not have this part means optimize failed
- MinidumpUtils.serializeOutputToDumpFile(physicalPlan);
- NereidsTracer.output(statementContext.getConnectContext());
+ physicalPlan = postProcess(physicalPlan);
+ if (cascadesContext.getConnectContext().getSessionVariable().dumpNereidsMemo) {
+ String tree = physicalPlan.treeString();
+ LOG.info(ConnectContext.get().getQueryIdentifier() + "\n" + tree);
+ }
+ if (explainLevel == ExplainLevel.OPTIMIZED_PLAN
+ || explainLevel == ExplainLevel.ALL_PLAN
+ || explainLevel == ExplainLevel.SHAPE_PLAN) {
+ optimizedPlan = physicalPlan;
+ }
+ // serialize optimized plan to dumpfile, dumpfile do not have this part means optimize failed
+ MinidumpUtils.serializeOutputToDumpFile(physicalPlan);
+ NereidsTracer.output(statementContext.getConnectContext());
- return physicalPlan;
+ return physicalPlan;
+ }
+ } finally {
+ statementContext.releasePlannerResources();
}
}
@@ -313,6 +305,47 @@ private void optimize() {
}
}
+ private void translate(PhysicalPlan resultPlan) throws UserException {
+ if (resultPlan instanceof PhysicalSqlCache) {
+ return;
+ }
+
+ PlanTranslatorContext planTranslatorContext = new PlanTranslatorContext(cascadesContext);
+ PhysicalPlanTranslator physicalPlanTranslator = new PhysicalPlanTranslator(planTranslatorContext,
+ statementContext.getConnectContext().getStatsErrorEstimator());
+ if (statementContext.getConnectContext().getExecutor() != null) {
+ statementContext.getConnectContext().getExecutor().getSummaryProfile().setNereidsTranslateTime();
+ }
+ if (cascadesContext.getConnectContext().getSessionVariable().isEnableNereidsTrace()) {
+ CounterEvent.clearCounter();
+ }
+ if (cascadesContext.getConnectContext().getSessionVariable().isPlayNereidsDump()) {
+ return;
+ }
+ PlanFragment root = physicalPlanTranslator.translatePlan(physicalPlan);
+
+ scanNodeList.addAll(planTranslatorContext.getScanNodes());
+ descTable = planTranslatorContext.getDescTable();
+ fragments = new ArrayList<>(planTranslatorContext.getPlanFragments());
+ for (int seq = 0; seq < fragments.size(); seq++) {
+ fragments.get(seq).setFragmentSequenceNum(seq);
+ }
+ // set output exprs
+ logicalPlanAdapter.setResultExprs(root.getOutputExprs());
+ ArrayList columnLabelList = physicalPlan.getOutput().stream().map(NamedExpression::getName)
+ .collect(Collectors.toCollection(ArrayList::new));
+ logicalPlanAdapter.setColLabels(columnLabelList);
+ logicalPlanAdapter.setViewDdlSqls(statementContext.getViewDdlSqls());
+ if (statementContext.getSqlCacheContext().isPresent()) {
+ SqlCacheContext sqlCacheContext = statementContext.getSqlCacheContext().get();
+ sqlCacheContext.setColLabels(columnLabelList);
+ sqlCacheContext.setResultExprs(root.getOutputExprs());
+ sqlCacheContext.setPhysicalPlan(resultPlan.treeString());
+ }
+
+ cascadesContext.releaseMemo();
+ }
+
private PhysicalPlan postProcess(PhysicalPlan physicalPlan) {
return new PlanPostProcessors(cascadesContext).process(physicalPlan);
}
@@ -569,6 +602,10 @@ public PhysicalPlan getPhysicalPlan() {
return physicalPlan;
}
+ public LogicalPlanAdapter getLogicalPlanAdapter() {
+ return logicalPlanAdapter;
+ }
+
public List getHooks() {
return hooks;
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/SqlCacheContext.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/SqlCacheContext.java
new file mode 100644
index 00000000000000..6afda4e4fe96d6
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/SqlCacheContext.java
@@ -0,0 +1,353 @@
+// 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;
+
+import org.apache.doris.analysis.Expr;
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.catalog.DatabaseIf;
+import org.apache.doris.catalog.TableIf;
+import org.apache.doris.common.Pair;
+import org.apache.doris.datasource.CatalogIf;
+import org.apache.doris.mysql.privilege.DataMaskPolicy;
+import org.apache.doris.mysql.privilege.RowFilterPolicy;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.Variable;
+import org.apache.doris.nereids.util.Utils;
+import org.apache.doris.proto.Types.PUniqueId;
+import org.apache.doris.qe.cache.CacheProxy;
+import org.apache.doris.thrift.TUniqueId;
+
+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 com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+/** SqlCacheContext */
+public class SqlCacheContext {
+ private final UserIdentity userIdentity;
+ private final TUniqueId queryId;
+ // if contains udf/udaf/tableValuesFunction we can not process it and skip use sql cache
+ private volatile boolean cannotProcessExpression;
+ private volatile String physicalPlan;
+ private volatile long latestPartitionId = -1;
+ private volatile long latestPartitionTime = -1;
+ private volatile long latestPartitionVersion = -1;
+ private volatile long sumOfPartitionNum = -1;
+ private final Set usedTables = Sets.newLinkedHashSet();
+ // value: ddl sql
+ private final Map usedViews = Maps.newLinkedHashMap();
+ // value: usedColumns
+ private final Map> checkPrivilegeTablesOrViews = Maps.newLinkedHashMap();
+ private final Map> rowPolicies = Maps.newLinkedHashMap();
+ private final Map> dataMaskPolicies = Maps.newLinkedHashMap();
+ private final Set usedVariables = Sets.newLinkedHashSet();
+ // key: the expression which contains nondeterministic function, e.g. date(now())
+ // value: the expression which already try to fold nondeterministic function, e.g. '2024-01-01'
+ // note that value maybe contains nondeterministic function too, when fold failed
+ private final List> foldNondeterministicPairs = Lists.newArrayList();
+ private volatile boolean hasUnsupportedTables;
+ private final List scanTables = Lists.newArrayList();
+ private volatile CacheProxy cacheProxy;
+
+ private volatile List resultExprs;
+ private volatile List colLabels;
+
+ private volatile PUniqueId cacheKeyMd5;
+
+ public SqlCacheContext(UserIdentity userIdentity, TUniqueId queryId) {
+ this.userIdentity = Objects.requireNonNull(userIdentity, "userIdentity cannot be null");
+ this.queryId = Objects.requireNonNull(queryId, "queryId cannot be null");
+ }
+
+ public String getPhysicalPlan() {
+ return physicalPlan;
+ }
+
+ public void setPhysicalPlan(String physicalPlan) {
+ this.physicalPlan = physicalPlan;
+ }
+
+ public void setCannotProcessExpression(boolean cannotProcessExpression) {
+ this.cannotProcessExpression = cannotProcessExpression;
+ }
+
+ public boolean containsCannotProcessExpression() {
+ return cannotProcessExpression;
+ }
+
+ public boolean hasUnsupportedTables() {
+ return hasUnsupportedTables;
+ }
+
+ public void setHasUnsupportedTables(boolean hasUnsupportedTables) {
+ this.hasUnsupportedTables = hasUnsupportedTables;
+ }
+
+ /** addUsedTable */
+ public synchronized void addUsedTable(TableIf tableIf) {
+ if (tableIf == null) {
+ return;
+ }
+ DatabaseIf database = tableIf.getDatabase();
+ if (database == null) {
+ setCannotProcessExpression(true);
+ return;
+ }
+ CatalogIf catalog = database.getCatalog();
+ if (catalog == null) {
+ setCannotProcessExpression(true);
+ return;
+ }
+
+ usedTables.add(
+ new FullTableName(database.getCatalog().getName(), database.getFullName(), tableIf.getName())
+ );
+ }
+
+ /** addUsedView */
+ public synchronized void addUsedView(TableIf tableIf, String ddlSql) {
+ if (tableIf == null) {
+ return;
+ }
+ DatabaseIf database = tableIf.getDatabase();
+ if (database == null) {
+ setCannotProcessExpression(true);
+ return;
+ }
+ CatalogIf catalog = database.getCatalog();
+ if (catalog == null) {
+ setCannotProcessExpression(true);
+ return;
+ }
+
+ usedViews.put(
+ new FullTableName(database.getCatalog().getName(), database.getFullName(), tableIf.getName()),
+ ddlSql
+ );
+ }
+
+ /** addNeedCheckPrivilegeTablesOrViews */
+ public synchronized void addCheckPrivilegeTablesOrViews(TableIf tableIf, Set usedColumns) {
+ if (tableIf == null) {
+ return;
+ }
+ DatabaseIf database = tableIf.getDatabase();
+ if (database == null) {
+ setCannotProcessExpression(true);
+ return;
+ }
+ CatalogIf catalog = database.getCatalog();
+ if (catalog == null) {
+ setCannotProcessExpression(true);
+ return;
+ }
+ FullTableName fullTableName = new FullTableName(catalog.getName(), database.getFullName(), tableIf.getName());
+ Set existsColumns = checkPrivilegeTablesOrViews.get(fullTableName);
+ if (existsColumns == null) {
+ checkPrivilegeTablesOrViews.put(fullTableName, usedColumns);
+ } else {
+ ImmutableSet.Builder allUsedColumns = ImmutableSet.builderWithExpectedSize(
+ existsColumns.size() + usedColumns.size());
+ allUsedColumns.addAll(existsColumns);
+ allUsedColumns.addAll(usedColumns);
+ checkPrivilegeTablesOrViews.put(fullTableName, allUsedColumns.build());
+ }
+ }
+
+ public synchronized void setRowFilterPolicy(
+ String catalog, String db, String table, List extends RowFilterPolicy> rowFilterPolicy) {
+ rowPolicies.put(new FullTableName(catalog, db, table), Utils.fastToImmutableList(rowFilterPolicy));
+ }
+
+ public synchronized Map> getRowFilterPolicies() {
+ return ImmutableMap.copyOf(rowPolicies);
+ }
+
+ public synchronized void addDataMaskPolicy(
+ String catalog, String db, String table, String columnName, Optional dataMaskPolicy) {
+ dataMaskPolicies.put(
+ new FullColumnName(catalog, db, table, columnName.toLowerCase(Locale.ROOT)), dataMaskPolicy
+ );
+ }
+
+ public synchronized Map> getDataMaskPolicies() {
+ return ImmutableMap.copyOf(dataMaskPolicies);
+ }
+
+ public synchronized void addUsedVariable(Variable value) {
+ usedVariables.add(value);
+ }
+
+ public synchronized List getUsedVariables() {
+ return ImmutableList.copyOf(usedVariables);
+ }
+
+ public synchronized void addFoldNondeterministicPair(Expression unfold, Expression fold) {
+ foldNondeterministicPairs.add(Pair.of(unfold, fold));
+ }
+
+ public synchronized List> getFoldNondeterministicPairs() {
+ return ImmutableList.copyOf(foldNondeterministicPairs);
+ }
+
+ public boolean isCannotProcessExpression() {
+ return cannotProcessExpression;
+ }
+
+ public UserIdentity getUserIdentity() {
+ return userIdentity;
+ }
+
+ public long getLatestPartitionTime() {
+ return latestPartitionTime;
+ }
+
+ public void setLatestPartitionTime(long latestPartitionTime) {
+ this.latestPartitionTime = latestPartitionTime;
+ }
+
+ public long getLatestPartitionVersion() {
+ return latestPartitionVersion;
+ }
+
+ public void setLatestPartitionVersion(long latestPartitionVersion) {
+ this.latestPartitionVersion = latestPartitionVersion;
+ }
+
+ public long getLatestPartitionId() {
+ return latestPartitionId;
+ }
+
+ public void setLatestPartitionId(long latestPartitionId) {
+ this.latestPartitionId = latestPartitionId;
+ }
+
+ public long getSumOfPartitionNum() {
+ return sumOfPartitionNum;
+ }
+
+ public void setSumOfPartitionNum(long sumOfPartitionNum) {
+ this.sumOfPartitionNum = sumOfPartitionNum;
+ }
+
+ public CacheProxy getCacheProxy() {
+ return cacheProxy;
+ }
+
+ public void setCacheProxy(CacheProxy cacheProxy) {
+ this.cacheProxy = cacheProxy;
+ }
+
+ public Set getUsedTables() {
+ return ImmutableSet.copyOf(usedTables);
+ }
+
+ public Map getUsedViews() {
+ return ImmutableMap.copyOf(usedViews);
+ }
+
+ public synchronized Map> getCheckPrivilegeTablesOrViews() {
+ return ImmutableMap.copyOf(checkPrivilegeTablesOrViews);
+ }
+
+ public synchronized Map> getRowPolicies() {
+ return ImmutableMap.copyOf(rowPolicies);
+ }
+
+ public boolean isHasUnsupportedTables() {
+ return hasUnsupportedTables;
+ }
+
+ public synchronized void addScanTable(ScanTable scanTable) {
+ this.scanTables.add(scanTable);
+ }
+
+ public synchronized List getScanTables() {
+ return ImmutableList.copyOf(scanTables);
+ }
+
+ public List getResultExprs() {
+ return resultExprs;
+ }
+
+ public void setResultExprs(List resultExprs) {
+ this.resultExprs = ImmutableList.copyOf(resultExprs);
+ }
+
+ public List getColLabels() {
+ return colLabels;
+ }
+
+ public void setColLabels(List colLabels) {
+ this.colLabels = ImmutableList.copyOf(colLabels);
+ }
+
+ public TUniqueId getQueryId() {
+ return queryId;
+ }
+
+ public PUniqueId getCacheKeyMd5() {
+ return cacheKeyMd5;
+ }
+
+ public void setCacheKeyMd5(PUniqueId cacheKeyMd5) {
+ this.cacheKeyMd5 = cacheKeyMd5;
+ }
+
+ /** FullTableName */
+ @lombok.Data
+ @lombok.AllArgsConstructor
+ public static class FullTableName {
+ public final String catalog;
+ public final String db;
+ public final String table;
+ }
+
+ /** FullColumnName */
+ @lombok.Data
+ @lombok.AllArgsConstructor
+ public static class FullColumnName {
+ public final String catalog;
+ public final String db;
+ public final String table;
+ public final String column;
+ }
+
+ /** ScanTable */
+ @lombok.Data
+ @lombok.AllArgsConstructor
+ public static class ScanTable {
+ public final FullTableName fullTableName;
+ public final long latestTimestamp;
+ public final long latestVersion;
+ public final List scanPartitions = Lists.newArrayList();
+
+ public void addScanPartition(Long partitionId) {
+ this.scanPartitions.add(partitionId);
+ }
+ }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/StatementContext.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/StatementContext.java
index 403c605ba75992..5baa59a35dec1b 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/StatementContext.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/StatementContext.java
@@ -18,6 +18,7 @@
package org.apache.doris.nereids;
import org.apache.doris.analysis.StatementBase;
+import org.apache.doris.catalog.TableIf;
import org.apache.doris.common.IdGenerator;
import org.apache.doris.common.Pair;
import org.apache.doris.nereids.hint.Hint;
@@ -37,6 +38,7 @@
import org.apache.doris.qe.ConnectContext;
import org.apache.doris.qe.OriginStatement;
import org.apache.doris.qe.SessionVariable;
+import org.apache.doris.qe.cache.CacheAnalyzer;
import com.google.common.base.Stopwatch;
import com.google.common.base.Supplier;
@@ -44,7 +46,11 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.sparkproject.guava.base.Throwables;
+import java.io.Closeable;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
@@ -53,14 +59,18 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
+import java.util.Stack;
import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
import javax.annotation.concurrent.GuardedBy;
/**
* Statement context for nereids
*/
-public class StatementContext {
+public class StatementContext implements Closeable {
+ private static final Logger LOG = LogManager.getLogger(StatementContext.class);
private ConnectContext connectContext;
@@ -101,6 +111,8 @@ public class StatementContext {
private final Map rewrittenCteProducer = new HashMap<>();
private final Map rewrittenCteConsumer = new HashMap<>();
private final Set viewDdlSqlSet = Sets.newHashSet();
+ private final SqlCacheContext sqlCacheContext;
+ private Map> checkedPrivilegedTableAndUsedColumns = Maps.newLinkedHashMap();
// collect all hash join conditions to compute node connectivity in join graph
private final List joinFilters = new ArrayList<>();
@@ -122,18 +134,30 @@ public class StatementContext {
private BitSet disableRules;
+ // table locks
+ private Stack plannerResources = new Stack<>();
+
// for create view support in nereids
// key is the start and end position of the sql substring that needs to be replaced,
// and value is the new string used for replacement.
private TreeMap, String> indexInSqlToString = new TreeMap<>(new Pair.PairComparator<>());
public StatementContext() {
- this.connectContext = ConnectContext.get();
+ this(ConnectContext.get(), null);
}
+ /** StatementContext */
public StatementContext(ConnectContext connectContext, OriginStatement originStatement) {
this.connectContext = connectContext;
this.originStatement = originStatement;
+ if (connectContext != null && connectContext.getSessionVariable() != null
+ && connectContext.queryId() != null
+ && CacheAnalyzer.canUseSqlCache(connectContext.getSessionVariable())) {
+ this.sqlCacheContext = new SqlCacheContext(
+ connectContext.getCurrentUserIdentity(), connectContext.queryId());
+ } else {
+ this.sqlCacheContext = null;
+ }
}
public void setConnectContext(ConnectContext connectContext) {
@@ -172,6 +196,10 @@ public void setMaxContinuousJoin(int joinCount) {
}
}
+ public Optional getSqlCacheContext() {
+ return Optional.ofNullable(sqlCacheContext);
+ }
+
public int getMaxContinuousJoin() {
return joinCount;
}
@@ -368,4 +396,90 @@ public TreeMap, String> getIndexInSqlToString() {
public void addIndexInSqlToString(Pair pair, String replacement) {
indexInSqlToString.put(pair, replacement);
}
+
+ /** addTableReadLock */
+ public synchronized void addTableReadLock(TableIf tableIf) {
+ if (!tableIf.needReadLockWhenPlan()) {
+ return;
+ }
+ if (!tableIf.tryReadLock(1, TimeUnit.MINUTES)) {
+ close();
+ throw new RuntimeException(String.format("Failed to get read lock on table: %s", tableIf.getName()));
+ }
+
+ String fullTableName = tableIf.getNameWithFullQualifiers();
+ String resourceName = "tableReadLock(" + fullTableName + ")";
+ plannerResources.push(new CloseableResource(
+ resourceName, Thread.currentThread().getName(), originStatement.originStmt, tableIf::readUnlock));
+ }
+
+ /** releasePlannerResources */
+ public synchronized void releasePlannerResources() {
+ Throwable throwable = null;
+ while (!plannerResources.isEmpty()) {
+ try {
+ plannerResources.pop().close();
+ } catch (Throwable t) {
+ if (throwable == null) {
+ throwable = t;
+ }
+ }
+ }
+ if (throwable != null) {
+ Throwables.propagateIfInstanceOf(throwable, RuntimeException.class);
+ throw new IllegalStateException("Release resource failed", throwable);
+ }
+ }
+
+ // CHECKSTYLE OFF
+ @Override
+ protected void finalize() throws Throwable {
+ if (!plannerResources.isEmpty()) {
+ String msg = "Resources leak: " + plannerResources;
+ LOG.error(msg);
+ throw new IllegalStateException(msg);
+ }
+ }
+ // CHECKSTYLE ON
+
+ @Override
+ public void close() {
+ releasePlannerResources();
+ }
+
+ private static class CloseableResource implements Closeable {
+ public final String resourceName;
+ public final String threadName;
+ public final String sql;
+
+ private final Closeable resource;
+
+ private boolean closed;
+
+ public CloseableResource(String resourceName, String threadName, String sql, Closeable resource) {
+ this.resourceName = resourceName;
+ this.threadName = threadName;
+ this.sql = sql;
+ this.resource = resource;
+ }
+
+ @Override
+ public void close() {
+ if (!closed) {
+ try {
+ resource.close();
+ } catch (Throwable t) {
+ Throwables.propagateIfInstanceOf(t, RuntimeException.class);
+ throw new IllegalStateException("Close resource failed: " + t.getMessage(), t);
+ }
+ closed = true;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "\nResource {\n name: " + resourceName + ",\n thread: " + threadName
+ + ",\n sql:\n" + sql + "\n}";
+ }
+ }
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/NereidsParser.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/NereidsParser.java
index da6881a4905468..35e1a6a354ba33 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/NereidsParser.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/NereidsParser.java
@@ -17,6 +17,7 @@
package org.apache.doris.nereids.parser;
+import org.apache.doris.analysis.ExplainOptions;
import org.apache.doris.analysis.StatementBase;
import org.apache.doris.catalog.Env;
import org.apache.doris.common.Pair;
@@ -26,6 +27,7 @@
import org.apache.doris.nereids.glue.LogicalPlanAdapter;
import org.apache.doris.nereids.parser.plsql.PLSqlLogicalPlanBuilder;
import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.plans.commands.ExplainCommand.ExplainLevel;
import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
import org.apache.doris.nereids.types.DataType;
import org.apache.doris.plugin.DialectConverterPlugin;
@@ -37,14 +39,18 @@
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.ParserRuleContext;
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.TokenSource;
import org.antlr.v4.runtime.atn.PredictionMode;
import org.antlr.v4.runtime.misc.ParseCancellationException;
import org.apache.commons.collections.CollectionUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import java.util.BitSet;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.function.Function;
import javax.annotation.Nullable;
@@ -56,6 +62,21 @@ public class NereidsParser {
private static final ParseErrorListener PARSE_ERROR_LISTENER = new ParseErrorListener();
private static final PostProcessor POST_PROCESSOR = new PostProcessor();
+ private static final BitSet EXPLAIN_TOKENS = new BitSet();
+
+ static {
+ EXPLAIN_TOKENS.set(DorisLexer.EXPLAIN);
+ EXPLAIN_TOKENS.set(DorisLexer.PARSED);
+ EXPLAIN_TOKENS.set(DorisLexer.ANALYZED);
+ EXPLAIN_TOKENS.set(DorisLexer.LOGICAL);
+ EXPLAIN_TOKENS.set(DorisLexer.REWRITTEN);
+ EXPLAIN_TOKENS.set(DorisLexer.PHYSICAL);
+ EXPLAIN_TOKENS.set(DorisLexer.OPTIMIZED);
+ EXPLAIN_TOKENS.set(DorisLexer.PLAN);
+ EXPLAIN_TOKENS.set(DorisLexer.PROCESS);
+
+ }
+
/**
* In MySQL protocol, client could send multi-statement in a single packet.
* see docs for more information.
@@ -83,6 +104,98 @@ public List parseSQL(String originStr, @Nullable LogicalPlanBuild
return statementBases;
}
+ /**
+ * scan to token
+ * for example: select id from tbl return Tokens: ['select', 'id', 'from', 'tbl']
+ */
+ public static TokenSource scan(String sql) {
+ return new DorisLexer(new CaseInsensitiveStream(CharStreams.fromString(sql)));
+ }
+
+ /**
+ * tryParseExplainPlan
+ * @param sql sql
+ * @return key: ExplainOptions, value: explain body
+ */
+ public static Optional> tryParseExplainPlan(String sql) {
+ try {
+ TokenSource tokenSource = scan(sql);
+ if (expect(tokenSource, DorisLexer.EXPLAIN) == null) {
+ return Optional.empty();
+ }
+
+ Token token = readUntilNonComment(tokenSource);
+ if (token == null) {
+ return Optional.empty();
+ }
+
+ int tokenType = token.getType();
+ ExplainLevel explainLevel = ExplainLevel.ALL_PLAN;
+ if (tokenType == DorisLexer.PARSED) {
+ explainLevel = ExplainLevel.PARSED_PLAN;
+ token = readUntilNonComment(tokenSource);
+ } else if (tokenType == DorisLexer.ANALYZED) {
+ explainLevel = ExplainLevel.ANALYZED_PLAN;
+ token = readUntilNonComment(tokenSource);
+ } else if (tokenType == DorisLexer.LOGICAL || tokenType == DorisLexer.REWRITTEN) {
+ explainLevel = ExplainLevel.REWRITTEN_PLAN;
+ token = readUntilNonComment(tokenSource);
+ } else if (tokenType == DorisLexer.PHYSICAL || tokenType == DorisLexer.OPTIMIZED) {
+ explainLevel = ExplainLevel.OPTIMIZED_PLAN;
+ token = readUntilNonComment(tokenSource);
+ }
+
+ if (token == null) {
+ return Optional.empty();
+ }
+ tokenType = token.getType();
+ if (tokenType != DorisLexer.PLAN) {
+ return Optional.empty();
+ }
+
+ token = readUntilNonComment(tokenSource);
+ Token explainPlanBody;
+ boolean showPlanProcess = false;
+ if (token.getType() == DorisLexer.PROCESS) {
+ showPlanProcess = true;
+ explainPlanBody = readUntilNonComment(tokenSource);
+ } else {
+ explainPlanBody = token;
+ }
+
+ if (explainPlanBody == null) {
+ return Optional.empty();
+ }
+ ExplainOptions explainOptions = new ExplainOptions(explainLevel, showPlanProcess);
+ return Optional.of(Pair.of(explainOptions, sql.substring(explainPlanBody.getStartIndex())));
+ } catch (Throwable t) {
+ return Optional.empty();
+ }
+ }
+
+ private static Token expect(TokenSource tokenSource, int tokenType) {
+ Token nextToken = readUntilNonComment(tokenSource);
+ if (nextToken == null) {
+ return null;
+ }
+ return nextToken.getType() == tokenType ? nextToken : null;
+ }
+
+ private static Token readUntilNonComment(TokenSource tokenSource) {
+ Token token = tokenSource.nextToken();
+ while (token != null) {
+ int tokenType = token.getType();
+ if (tokenType == DorisLexer.BRACKETED_COMMENT
+ || tokenType == DorisLexer.SIMPLE_COMMENT
+ || tokenType == DorisLexer.WS) {
+ token = tokenSource.nextToken();
+ continue;
+ }
+ break;
+ }
+ return token;
+ }
+
private List parseSQLWithDialect(String sql,
SessionVariable sessionVariable) {
@Nullable Dialect sqlDialect = Dialect.getByName(sessionVariable.getSqlDialect());
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java
index 8957800c7ed430..2f184d1f4632ab 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java
@@ -19,8 +19,10 @@
import org.apache.doris.catalog.Env;
import org.apache.doris.catalog.FunctionRegistry;
+import org.apache.doris.common.Pair;
import org.apache.doris.nereids.CascadesContext;
import org.apache.doris.nereids.NereidsPlanner;
+import org.apache.doris.nereids.SqlCacheContext;
import org.apache.doris.nereids.StatementContext;
import org.apache.doris.nereids.analyzer.MappingSlot;
import org.apache.doris.nereids.analyzer.Scope;
@@ -49,6 +51,7 @@
import org.apache.doris.nereids.trees.expressions.Slot;
import org.apache.doris.nereids.trees.expressions.SlotReference;
import org.apache.doris.nereids.trees.expressions.StatementScopeIdGenerator;
+import org.apache.doris.nereids.trees.expressions.functions.BoundFunction;
import org.apache.doris.nereids.trees.expressions.functions.Function;
import org.apache.doris.nereids.trees.expressions.functions.FunctionBuilder;
import org.apache.doris.nereids.trees.expressions.functions.agg.AggregateFunction;
@@ -809,11 +812,16 @@ private LogicalTVFRelation bindTableValuedFunction(MatchingContext bindResult
+ = functionBuilder.build(functionName, arguments);
+ if (!(bindResult.first instanceof TableValuedFunction)) {
+ throw new AnalysisException(bindResult.first.toSql() + " is not a TableValuedFunction");
}
- return new LogicalTVFRelation(unboundTVFRelation.getRelationId(), (TableValuedFunction) function);
+ Optional sqlCacheContext = statementContext.getSqlCacheContext();
+ if (sqlCacheContext.isPresent()) {
+ sqlCacheContext.get().setCannotProcessExpression(true);
+ }
+ return new LogicalTVFRelation(unboundTVFRelation.getRelationId(), (TableValuedFunction) bindResult.first);
}
private void checkSameNameSlot(List childOutputs, String subQueryAlias) {
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 0aac17cff9b978..84a3021ecd1740 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
@@ -31,6 +31,7 @@
import org.apache.doris.datasource.hive.HMSExternalTable;
import org.apache.doris.nereids.CTEContext;
import org.apache.doris.nereids.CascadesContext;
+import org.apache.doris.nereids.SqlCacheContext;
import org.apache.doris.nereids.StatementContext;
import org.apache.doris.nereids.analyzer.Unbound;
import org.apache.doris.nereids.analyzer.UnboundRelation;
@@ -249,68 +250,89 @@ private LogicalPlan getLogicalPlan(TableIf table, UnboundRelation unboundRelatio
});
List qualifierWithoutTableName = Lists.newArrayList();
qualifierWithoutTableName.addAll(tableQualifier.subList(0, tableQualifier.size() - 1));
- switch (table.getType()) {
- case OLAP:
- case MATERIALIZED_VIEW:
- return makeOlapScan(table, unboundRelation, qualifierWithoutTableName);
- case VIEW:
- View view = (View) table;
- String inlineViewDef = view.getInlineViewDef();
- Plan viewBody = parseAndAnalyzeView(inlineViewDef, cascadesContext);
- LogicalView logicalView = new LogicalView<>(view, viewBody);
- return new LogicalSubQueryAlias<>(tableQualifier, logicalView);
- case HMS_EXTERNAL_TABLE:
- HMSExternalTable hmsTable = (HMSExternalTable) table;
- if (Config.enable_query_hive_views && hmsTable.isView()) {
- String hiveCatalog = hmsTable.getCatalog().getName();
- String ddlSql = hmsTable.getViewText();
- Plan hiveViewPlan = parseAndAnalyzeHiveView(hiveCatalog, ddlSql, cascadesContext);
- return new LogicalSubQueryAlias<>(tableQualifier, hiveViewPlan);
- }
- hmsTable.setScanParams(unboundRelation.getScanParams());
- return new LogicalFileScan(unboundRelation.getRelationId(), (HMSExternalTable) table,
- qualifierWithoutTableName, unboundRelation.getTableSample());
- case ICEBERG_EXTERNAL_TABLE:
- case PAIMON_EXTERNAL_TABLE:
- case MAX_COMPUTE_EXTERNAL_TABLE:
- return new LogicalFileScan(unboundRelation.getRelationId(), (ExternalTable) table,
- qualifierWithoutTableName, unboundRelation.getTableSample());
- case SCHEMA:
- return new LogicalSchemaScan(unboundRelation.getRelationId(), table, qualifierWithoutTableName);
- case JDBC_EXTERNAL_TABLE:
- case JDBC:
- return new LogicalJdbcScan(unboundRelation.getRelationId(), table, qualifierWithoutTableName);
- case ODBC:
- return new LogicalOdbcScan(unboundRelation.getRelationId(), table, qualifierWithoutTableName);
- case ES_EXTERNAL_TABLE:
- return new LogicalEsScan(unboundRelation.getRelationId(), (EsExternalTable) table,
- qualifierWithoutTableName);
- case TEST_EXTERNAL_TABLE:
- return new LogicalTestScan(unboundRelation.getRelationId(), table, qualifierWithoutTableName);
- default:
- try {
- // TODO: support other type table, such as ELASTICSEARCH
- cascadesContext.getConnectContext().getSessionVariable().enableFallbackToOriginalPlannerOnce();
- } catch (Exception e) {
- // ignore
+ boolean isView = false;
+ try {
+ switch (table.getType()) {
+ case OLAP:
+ case MATERIALIZED_VIEW:
+ return makeOlapScan(table, unboundRelation, qualifierWithoutTableName);
+ case VIEW:
+ View view = (View) table;
+ isView = true;
+ String inlineViewDef = view.getInlineViewDef();
+ Plan viewBody = parseAndAnalyzeView(view, inlineViewDef, cascadesContext);
+ LogicalView logicalView = new LogicalView<>(view, viewBody);
+ return new LogicalSubQueryAlias<>(tableQualifier, logicalView);
+ case HMS_EXTERNAL_TABLE:
+ HMSExternalTable hmsTable = (HMSExternalTable) table;
+ if (Config.enable_query_hive_views && hmsTable.isView()) {
+ isView = true;
+ String hiveCatalog = hmsTable.getCatalog().getName();
+ String ddlSql = hmsTable.getViewText();
+ Plan hiveViewPlan = parseAndAnalyzeHiveView(hmsTable, hiveCatalog, ddlSql, cascadesContext);
+ return new LogicalSubQueryAlias<>(tableQualifier, hiveViewPlan);
+ }
+ hmsTable.setScanParams(unboundRelation.getScanParams());
+ return new LogicalFileScan(unboundRelation.getRelationId(), (HMSExternalTable) table,
+ qualifierWithoutTableName, unboundRelation.getTableSample());
+ case ICEBERG_EXTERNAL_TABLE:
+ case PAIMON_EXTERNAL_TABLE:
+ case MAX_COMPUTE_EXTERNAL_TABLE:
+ return new LogicalFileScan(unboundRelation.getRelationId(), (ExternalTable) table,
+ qualifierWithoutTableName, unboundRelation.getTableSample());
+ case SCHEMA:
+ return new LogicalSchemaScan(unboundRelation.getRelationId(), table, qualifierWithoutTableName);
+ case JDBC_EXTERNAL_TABLE:
+ case JDBC:
+ return new LogicalJdbcScan(unboundRelation.getRelationId(), table, qualifierWithoutTableName);
+ case ODBC:
+ return new LogicalOdbcScan(unboundRelation.getRelationId(), table, qualifierWithoutTableName);
+ case ES_EXTERNAL_TABLE:
+ return new LogicalEsScan(unboundRelation.getRelationId(), (EsExternalTable) table,
+ qualifierWithoutTableName);
+ case TEST_EXTERNAL_TABLE:
+ return new LogicalTestScan(unboundRelation.getRelationId(), table, qualifierWithoutTableName);
+ default:
+ try {
+ // TODO: support other type table, such as ELASTICSEARCH
+ cascadesContext.getConnectContext().getSessionVariable().enableFallbackToOriginalPlannerOnce();
+ } catch (Exception e) {
+ // ignore
+ }
+ throw new AnalysisException("Unsupported tableType " + table.getType());
+ }
+ } finally {
+ if (!isView) {
+ Optional sqlCacheContext = cascadesContext.getStatementContext().getSqlCacheContext();
+ if (sqlCacheContext.isPresent()) {
+ if (table instanceof OlapTable) {
+ sqlCacheContext.get().addUsedTable(table);
+ } else {
+ sqlCacheContext.get().setHasUnsupportedTables(true);
+ }
}
- throw new AnalysisException("Unsupported tableType " + table.getType());
+ }
}
}
- private Plan parseAndAnalyzeHiveView(String hiveCatalog, String ddlSql, CascadesContext cascadesContext) {
+ private Plan parseAndAnalyzeHiveView(
+ HMSExternalTable table, String hiveCatalog, String ddlSql, CascadesContext cascadesContext) {
ConnectContext ctx = cascadesContext.getConnectContext();
String previousCatalog = ctx.getCurrentCatalog().getName();
String previousDb = ctx.getDatabase();
ctx.changeDefaultCatalog(hiveCatalog);
- Plan hiveViewPlan = parseAndAnalyzeView(ddlSql, cascadesContext);
+ Plan hiveViewPlan = parseAndAnalyzeView(table, ddlSql, cascadesContext);
ctx.changeDefaultCatalog(previousCatalog);
ctx.setDatabase(previousDb);
return hiveViewPlan;
}
- private Plan parseAndAnalyzeView(String ddlSql, CascadesContext parentContext) {
+ private Plan parseAndAnalyzeView(TableIf view, String ddlSql, CascadesContext parentContext) {
parentContext.getStatementContext().addViewDdlSql(ddlSql);
+ Optional sqlCacheContext = parentContext.getStatementContext().getSqlCacheContext();
+ if (sqlCacheContext.isPresent()) {
+ sqlCacheContext.get().addUsedView(view, ddlSql);
+ }
LogicalPlan parsedViewPlan = new NereidsParser().parseSingle(ddlSql);
// TODO: use a good to do this, such as eliminate UnboundResultSink
if (parsedViewPlan instanceof UnboundResultSink) {
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzer.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzer.java
index 954675a28c80a0..cc85bc01323c75 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzer.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzer.java
@@ -22,8 +22,10 @@
import org.apache.doris.catalog.Env;
import org.apache.doris.catalog.FunctionRegistry;
import org.apache.doris.common.DdlException;
+import org.apache.doris.common.Pair;
import org.apache.doris.common.util.Util;
import org.apache.doris.nereids.CascadesContext;
+import org.apache.doris.nereids.SqlCacheContext;
import org.apache.doris.nereids.StatementContext;
import org.apache.doris.nereids.analyzer.Scope;
import org.apache.doris.nereids.analyzer.UnboundAlias;
@@ -34,6 +36,7 @@
import org.apache.doris.nereids.analyzer.UnboundVariable.VariableType;
import org.apache.doris.nereids.exceptions.AnalysisException;
import org.apache.doris.nereids.rules.expression.ExpressionRewriteContext;
+import org.apache.doris.nereids.rules.expression.rules.FoldConstantRuleOnFE;
import org.apache.doris.nereids.trees.expressions.Alias;
import org.apache.doris.nereids.trees.expressions.ArrayItemReference;
import org.apache.doris.nereids.trees.expressions.BinaryArithmetic;
@@ -60,12 +63,15 @@
import org.apache.doris.nereids.trees.expressions.WhenClause;
import org.apache.doris.nereids.trees.expressions.functions.BoundFunction;
import org.apache.doris.nereids.trees.expressions.functions.FunctionBuilder;
+import org.apache.doris.nereids.trees.expressions.functions.Nondeterministic;
import org.apache.doris.nereids.trees.expressions.functions.agg.Count;
import org.apache.doris.nereids.trees.expressions.functions.scalar.ElementAt;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Lambda;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Nvl;
import org.apache.doris.nereids.trees.expressions.functions.scalar.PushDownToProjectionFunction;
import org.apache.doris.nereids.trees.expressions.functions.udf.AliasUdfBuilder;
+import org.apache.doris.nereids.trees.expressions.functions.udf.JavaUdaf;
+import org.apache.doris.nereids.trees.expressions.functions.udf.JavaUdf;
import org.apache.doris.nereids.trees.expressions.literal.BigIntLiteral;
import org.apache.doris.nereids.trees.expressions.literal.IntegerLikeLiteral;
import org.apache.doris.nereids.trees.expressions.literal.Literal;
@@ -83,6 +89,7 @@
import org.apache.doris.qe.SessionVariable;
import org.apache.doris.qe.VariableMgr;
import org.apache.doris.qe.VariableVarConverters;
+import org.apache.doris.qe.cache.CacheAnalyzer;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
@@ -113,6 +120,7 @@ public class ExpressionAnalyzer extends SubExprAnalyzer buildResult = builder.build(functionName, arguments);
+ StatementContext statementContext = context.cascadesContext.getStatementContext();
+ if (buildResult.second instanceof Nondeterministic) {
+ hasNondeterministic = true;
+ }
+ Optional sqlCacheContext = statementContext.getSqlCacheContext();
+ if (builder instanceof AliasUdfBuilder
+ || buildResult.second instanceof JavaUdf || buildResult.second instanceof JavaUdaf) {
+ if (sqlCacheContext.isPresent()) {
+ sqlCacheContext.get().setCannotProcessExpression(true);
+ }
+ }
if (builder instanceof AliasUdfBuilder) {
+ if (sqlCacheContext.isPresent()) {
+ sqlCacheContext.get().setCannotProcessExpression(true);
+ }
// we do type coercion in build function in alias function, so it's ok to return directly.
- return builder.build(functionName, arguments);
+ return buildResult.first;
} else {
- Expression boundFunction = TypeCoercionUtils
- .processBoundFunction((BoundFunction) builder.build(functionName, arguments));
- if (boundFunction instanceof Count
+ Expression castFunction = TypeCoercionUtils.processBoundFunction((BoundFunction) buildResult.first);
+ if (castFunction instanceof Count
&& context.cascadesContext.getOuterScope().isPresent()
&& !context.cascadesContext.getOuterScope().get().getCorrelatedSlots()
.isEmpty()) {
@@ -339,20 +392,20 @@ public Expression visitUnboundFunction(UnboundFunction unboundFunction, Expressi
// if there is no match, the row from right table is filled with nulls
// but COUNT function is always not nullable.
// so wrap COUNT with Nvl to ensure it's result is 0 instead of null to get the correct result
- boundFunction = new Nvl(boundFunction, new BigIntLiteral(0));
+ castFunction = new Nvl(castFunction, new BigIntLiteral(0));
}
if (currentElementAtLevel == 1
- && PushDownToProjectionFunction.validToPushDown(boundFunction)) {
+ && PushDownToProjectionFunction.validToPushDown(castFunction)) {
// Only rewrite the top level of PushDownToProjectionFunction, otherwise invalid slot will be generated
// currentElementAtLevel == 1 means at the top of element_at function, other levels will be ignored.
currentElementAtLevel = 0;
- return visitElementAt((ElementAt) boundFunction, context);
+ return visitElementAt((ElementAt) castFunction, context);
}
- if (boundFunction instanceof ElementAt) {
+ if (castFunction instanceof ElementAt) {
--currentElementAtLevel;
}
- return boundFunction;
+ return castFunction;
}
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/FunctionBinder.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/FunctionBinder.java
index e8ed474a1be733..8e79e60abad1bd 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/FunctionBinder.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/FunctionBinder.java
@@ -181,10 +181,10 @@ public Expression visitUnboundFunction(UnboundFunction unboundFunction, Expressi
unboundFunction.getDbName(), functionName, arguments);
if (builder instanceof AliasUdfBuilder) {
// we do type coercion in build function in alias function, so it's ok to return directly.
- return builder.build(functionName, arguments);
+ return builder.build(functionName, arguments).first;
} else {
Expression boundFunction = TypeCoercionUtils
- .processBoundFunction((BoundFunction) builder.build(functionName, arguments));
+ .processBoundFunction((BoundFunction) builder.build(functionName, arguments).first);
if (boundFunction instanceof Count
&& context.cascadesContext.getOuterScope().isPresent()
&& !context.cascadesContext.getOuterScope().get().getCorrelatedSlots()
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/ReplaceVariableByLiteral.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/ReplaceVariableByLiteral.java
index b4c5552706c589..74f41e17cac49d 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/ReplaceVariableByLiteral.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/ReplaceVariableByLiteral.java
@@ -17,6 +17,8 @@
package org.apache.doris.nereids.rules.expression.rules;
+import org.apache.doris.nereids.SqlCacheContext;
+import org.apache.doris.nereids.StatementContext;
import org.apache.doris.nereids.rules.expression.ExpressionPatternMatcher;
import org.apache.doris.nereids.rules.expression.ExpressionPatternRuleFactory;
import org.apache.doris.nereids.trees.expressions.Expression;
@@ -25,6 +27,7 @@
import com.google.common.collect.ImmutableList;
import java.util.List;
+import java.util.Optional;
/**
* replace varaible to real expression
@@ -35,7 +38,15 @@ public class ReplaceVariableByLiteral implements ExpressionPatternRuleFactory {
@Override
public List> buildRules() {
return ImmutableList.of(
- matchesType(Variable.class).then(Variable::getRealExpression)
+ matchesType(Variable.class).thenApply(ctx -> {
+ StatementContext statementContext = ctx.cascadesContext.getStatementContext();
+ Variable variable = ctx.expr;
+ Optional sqlCacheContext = statementContext.getSqlCacheContext();
+ if (sqlCacheContext.isPresent()) {
+ sqlCacheContext.get().addUsedVariable(variable);
+ }
+ return variable.getRealExpression();
+ })
);
}
}
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
index 70a5c593ee3dc8..713a9404dc08c4 100644
--- 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
@@ -19,6 +19,9 @@
import org.apache.doris.catalog.TableIf;
import org.apache.doris.common.UserException;
+import org.apache.doris.nereids.CascadesContext;
+import org.apache.doris.nereids.SqlCacheContext;
+import org.apache.doris.nereids.StatementContext;
import org.apache.doris.nereids.exceptions.AnalysisException;
import org.apache.doris.nereids.jobs.JobContext;
import org.apache.doris.nereids.rules.analysis.UserAuthentication;
@@ -34,6 +37,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
/** CheckPrivileges */
@@ -84,11 +88,17 @@ private Set computeUsedColumns(Plan plan, Set requiredSlots) {
}
private void checkColumnPrivileges(TableIf table, Set usedColumns) {
- ConnectContext connectContext = jobContext.getCascadesContext().getConnectContext();
+ CascadesContext cascadesContext = jobContext.getCascadesContext();
+ ConnectContext connectContext = cascadesContext.getConnectContext();
try {
UserAuthentication.checkPermission(table, connectContext, usedColumns);
} catch (UserException e) {
throw new AnalysisException(e.getMessage(), e);
}
+ StatementContext statementContext = cascadesContext.getStatementContext();
+ Optional sqlCacheContext = statementContext.getSqlCacheContext();
+ if (sqlCacheContext.isPresent()) {
+ sqlCacheContext.get().addCheckPrivilegeTablesOrViews(table, usedColumns);
+ }
}
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/ExpressionEvaluator.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/ExpressionEvaluator.java
index adced61c7b7508..566798ec2d4e46 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/ExpressionEvaluator.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/ExpressionEvaluator.java
@@ -20,7 +20,6 @@
import org.apache.doris.catalog.Env;
import org.apache.doris.common.AnalysisException;
import org.apache.doris.nereids.trees.expressions.functions.BoundFunction;
-import org.apache.doris.nereids.trees.expressions.functions.ExpressionTrait;
import org.apache.doris.nereids.trees.expressions.functions.agg.AggregateFunction;
import org.apache.doris.nereids.trees.expressions.functions.executable.DateTimeAcquire;
import org.apache.doris.nereids.trees.expressions.functions.executable.DateTimeArithmetic;
@@ -79,7 +78,10 @@ public Expression eval(Expression expression) {
} else if (expression instanceof BoundFunction) {
BoundFunction function = ((BoundFunction) expression);
fnName = function.getName();
- args = function.children().stream().map(ExpressionTrait::getDataType).toArray(DataType[]::new);
+ args = new DataType[function.arity()];
+ for (int i = 0; i < function.children().size(); i++) {
+ args[i] = function.child(i).getDataType();
+ }
}
if ((Env.getCurrentEnv().isNullResultWithOneNullParamFunction(fnName))) {
@@ -166,8 +168,11 @@ private void registerFEFunction(ImmutableMultimap.Builder arguments) {
}
private AggregateFunction buildState(String nestedName, List extends Object> arguments) {
- return (AggregateFunction) nestedBuilder.build(nestedName, arguments);
+ return (AggregateFunction) nestedBuilder.build(nestedName, arguments).first;
}
private AggregateFunction buildForEach(String nestedName, List extends Object> arguments) {
@@ -96,7 +97,7 @@ private AggregateFunction buildForEach(String nestedName, List extends Object>
DataType itemType = ((ArrayType) arrayType).getItemType();
return new SlotReference("mocked", itemType, (((ArrayType) arrayType).containsNull()));
}).collect(Collectors.toList());
- return (AggregateFunction) nestedBuilder.build(nestedName, forEachargs);
+ return (AggregateFunction) nestedBuilder.build(nestedName, forEachargs).first;
}
private AggregateFunction buildMergeOrUnion(String nestedName, List extends Object> arguments) {
@@ -118,24 +119,24 @@ private AggregateFunction buildMergeOrUnion(String nestedName, List extends Ob
Expression arg = (Expression) arguments.get(0);
AggStateType type = (AggStateType) arg.getDataType();
- return (AggregateFunction) nestedBuilder.build(nestedName, type.getMockedExpressions());
+ return (AggregateFunction) nestedBuilder.build(nestedName, type.getMockedExpressions()).first;
}
@Override
- public BoundFunction build(String name, List extends Object> arguments) {
+ public Pair build(String name, List extends Object> arguments) {
String nestedName = getNestedName(name);
if (combinatorSuffix.equals(STATE)) {
AggregateFunction nestedFunction = buildState(nestedName, arguments);
- return new StateCombinator((List) arguments, nestedFunction);
+ return Pair.of(new StateCombinator((List) arguments, nestedFunction), nestedFunction);
} else if (combinatorSuffix.equals(MERGE)) {
AggregateFunction nestedFunction = buildMergeOrUnion(nestedName, arguments);
- return new MergeCombinator((List) arguments, nestedFunction);
+ return Pair.of(new MergeCombinator((List) arguments, nestedFunction), nestedFunction);
} else if (combinatorSuffix.equals(UNION)) {
AggregateFunction nestedFunction = buildMergeOrUnion(nestedName, arguments);
- return new UnionCombinator((List) arguments, nestedFunction);
+ return Pair.of(new UnionCombinator((List) arguments, nestedFunction), nestedFunction);
} else if (combinatorSuffix.equals(FOREACH)) {
AggregateFunction nestedFunction = buildForEach(nestedName, arguments);
- return new ForEachCombinator((List) arguments, nestedFunction);
+ return Pair.of(new ForEachCombinator((List) arguments, nestedFunction), nestedFunction);
}
return null;
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/BuiltinFunctionBuilder.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/BuiltinFunctionBuilder.java
index e2dab713332fd6..a071328ec52635 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/BuiltinFunctionBuilder.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/BuiltinFunctionBuilder.java
@@ -17,6 +17,7 @@
package org.apache.doris.nereids.trees.expressions.functions;
+import org.apache.doris.common.Pair;
import org.apache.doris.common.util.ReflectionUtils;
import org.apache.doris.nereids.trees.expressions.Expression;
@@ -86,12 +87,12 @@ private Class getConstructorArgumentType(int index) {
}
@Override
- public BoundFunction build(String name, List extends Object> arguments) {
+ public Pair build(String name, List extends Object> arguments) {
try {
if (isVariableLength) {
- return builderMethod.newInstance(toVariableLengthArguments(arguments));
+ return Pair.ofSame(builderMethod.newInstance(toVariableLengthArguments(arguments)));
} else {
- return builderMethod.newInstance(arguments.toArray());
+ return Pair.ofSame(builderMethod.newInstance(arguments.toArray()));
}
} catch (Throwable t) {
String argString = arguments.stream()
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/FunctionBuilder.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/FunctionBuilder.java
index d1e69d3e307d6f..760edacaeabaf7 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/FunctionBuilder.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/FunctionBuilder.java
@@ -17,6 +17,7 @@
package org.apache.doris.nereids.trees.expressions.functions;
+import org.apache.doris.common.Pair;
import org.apache.doris.nereids.trees.expressions.Expression;
import com.google.common.collect.ImmutableList;
@@ -32,7 +33,7 @@ public abstract class FunctionBuilder {
/** check whether arguments can apply to the constructor */
public abstract boolean canApply(List extends Object> arguments);
- public final Expression build(String name, Object argument) {
+ public final Pair extends Expression, ? extends BoundFunction> build(String name, Object argument) {
return build(name, ImmutableList.of(argument));
}
@@ -40,7 +41,10 @@ public final Expression build(String name, Object argument) {
* build a BoundFunction by function name and arguments.
* @param name function name which in the sql expression
* @param arguments the function's argument expressions
- * @return the concrete bound function instance
+ * @return the concrete bound function instance,
+ * key: the final result expression that should return, e.g. the function wrapped some cast function,
+ * value: the real BoundFunction
*/
- public abstract Expression build(String name, List extends Object> arguments);
+ public abstract Pair extends Expression, ? extends BoundFunction> build(
+ String name, List extends Object> arguments);
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ConnectionId.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ConnectionId.java
index 7f16fa1a4acb69..6f17e247c44042 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ConnectionId.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ConnectionId.java
@@ -20,6 +20,7 @@
import org.apache.doris.catalog.FunctionSignature;
import org.apache.doris.nereids.trees.expressions.functions.AlwaysNotNullable;
import org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
+import org.apache.doris.nereids.trees.expressions.functions.Nondeterministic;
import org.apache.doris.nereids.trees.expressions.shape.LeafExpression;
import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
import org.apache.doris.nereids.types.BigIntType;
@@ -32,7 +33,7 @@
* ScalarFunction 'ConnectionId'.
*/
public class ConnectionId extends ScalarFunction
- implements LeafExpression, ExplicitlyCastableSignature, AlwaysNotNullable {
+ implements LeafExpression, ExplicitlyCastableSignature, AlwaysNotNullable, Nondeterministic {
public static final List SIGNATURES = ImmutableList.of(
FunctionSignature.ret(BigIntType.INSTANCE).args()
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/CurrentUser.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/CurrentUser.java
index f36e4548ebc5c4..5f00e374716f32 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/CurrentUser.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/CurrentUser.java
@@ -20,6 +20,7 @@
import org.apache.doris.catalog.FunctionSignature;
import org.apache.doris.nereids.trees.expressions.functions.AlwaysNotNullable;
import org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
+import org.apache.doris.nereids.trees.expressions.functions.Nondeterministic;
import org.apache.doris.nereids.trees.expressions.shape.LeafExpression;
import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
import org.apache.doris.nereids.types.StringType;
@@ -32,7 +33,7 @@
* ScalarFunction 'CurrentUser'.
*/
public class CurrentUser extends ScalarFunction
- implements LeafExpression, ExplicitlyCastableSignature, AlwaysNotNullable {
+ implements LeafExpression, ExplicitlyCastableSignature, AlwaysNotNullable, Nondeterministic {
public static final List SIGNATURES = ImmutableList.of(
FunctionSignature.ret(StringType.INSTANCE).args()
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Database.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Database.java
index acb31f5ae4db07..c213fc6bc5c29d 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Database.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Database.java
@@ -20,6 +20,7 @@
import org.apache.doris.catalog.FunctionSignature;
import org.apache.doris.nereids.trees.expressions.functions.AlwaysNotNullable;
import org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
+import org.apache.doris.nereids.trees.expressions.functions.Nondeterministic;
import org.apache.doris.nereids.trees.expressions.shape.LeafExpression;
import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
import org.apache.doris.nereids.types.VarcharType;
@@ -32,7 +33,7 @@
* ScalarFunction 'database'.
*/
public class Database extends ScalarFunction
- implements LeafExpression, ExplicitlyCastableSignature, AlwaysNotNullable {
+ implements LeafExpression, ExplicitlyCastableSignature, AlwaysNotNullable, Nondeterministic {
public static final List SIGNATURES = ImmutableList.of(
FunctionSignature.ret(VarcharType.SYSTEM_DEFAULT).args()
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/User.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/User.java
index 3a53c291f9db74..6cf547f8ab5ac1 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/User.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/User.java
@@ -20,6 +20,7 @@
import org.apache.doris.catalog.FunctionSignature;
import org.apache.doris.nereids.trees.expressions.functions.AlwaysNotNullable;
import org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
+import org.apache.doris.nereids.trees.expressions.functions.Nondeterministic;
import org.apache.doris.nereids.trees.expressions.shape.LeafExpression;
import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
import org.apache.doris.nereids.types.VarcharType;
@@ -32,7 +33,7 @@
* ScalarFunction 'User'.
*/
public class User extends ScalarFunction
- implements LeafExpression, ExplicitlyCastableSignature, AlwaysNotNullable {
+ implements LeafExpression, ExplicitlyCastableSignature, AlwaysNotNullable, Nondeterministic {
public static final List SIGNATURES = ImmutableList.of(
FunctionSignature.ret(VarcharType.SYSTEM_DEFAULT).args()
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/table/TableValuedFunction.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/table/TableValuedFunction.java
index e3e25481691c02..26602435651fab 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/table/TableValuedFunction.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/table/TableValuedFunction.java
@@ -27,6 +27,7 @@
import org.apache.doris.nereids.trees.expressions.Slot;
import org.apache.doris.nereids.trees.expressions.functions.BoundFunction;
import org.apache.doris.nereids.trees.expressions.functions.CustomSignature;
+import org.apache.doris.nereids.trees.expressions.functions.Nondeterministic;
import org.apache.doris.nereids.trees.expressions.shape.UnaryExpression;
import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
import org.apache.doris.nereids.types.DataType;
@@ -44,7 +45,8 @@
import java.util.stream.Collectors;
/** TableValuedFunction */
-public abstract class TableValuedFunction extends BoundFunction implements UnaryExpression, CustomSignature {
+public abstract class TableValuedFunction extends BoundFunction
+ implements UnaryExpression, CustomSignature, Nondeterministic {
protected final Supplier catalogFunctionCache = Suppliers.memoize(this::toCatalogFunction);
protected final Supplier tableCache = Suppliers.memoize(() -> {
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/AliasUdfBuilder.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/AliasUdfBuilder.java
index 733bd5fcae1164..1f15b7e6049a25 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/AliasUdfBuilder.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/AliasUdfBuilder.java
@@ -17,6 +17,7 @@
package org.apache.doris.nereids.trees.expressions.functions.udf;
+import org.apache.doris.common.Pair;
import org.apache.doris.common.util.ReflectionUtils;
import org.apache.doris.nereids.rules.expression.rules.FunctionBinder;
import org.apache.doris.nereids.trees.expressions.Expression;
@@ -72,7 +73,7 @@ public boolean canApply(List> arguments) {
}
@Override
- public Expression build(String name, List> arguments) {
+ public Pair build(String name, List> arguments) {
// use AliasFunction to process TypeCoercion
BoundFunction boundAliasFunction = ((BoundFunction) aliasUdf.withChildren(arguments.stream()
.map(Expression.class::cast).collect(Collectors.toList())));
@@ -95,7 +96,7 @@ public Expression build(String name, List> arguments) {
replaceMap.put(slots.get(parameter), inputs.get(i));
}
- return SlotReplacer.INSTANCE.replace(boundFunction, replaceMap);
+ return Pair.of(SlotReplacer.INSTANCE.replace(boundFunction, replaceMap), boundAliasFunction);
}
private static class SlotReplacer extends DefaultExpressionRewriter