diff --git a/fe/fe-common/src/main/java/org/apache/doris/catalog/ScalarType.java b/fe/fe-common/src/main/java/org/apache/doris/catalog/ScalarType.java
index 67346fc21609c1..adf064c0000324 100644
--- a/fe/fe-common/src/main/java/org/apache/doris/catalog/ScalarType.java
+++ b/fe/fe-common/src/main/java/org/apache/doris/catalog/ScalarType.java
@@ -744,15 +744,19 @@ public void toThrift(TTypeDesc container) {
case DECIMAL128:
case DECIMAL256:
case DATETIMEV2: {
- Preconditions.checkArgument(precision >= scale,
- String.format("given precision %d is out of scale bound %d", precision, scale));
+ if (precision < scale) {
+ throw new IllegalArgumentException(
+ String.format("given precision %d is out of scale bound %d", precision, scale));
+ }
scalarType.setScale(scale);
scalarType.setPrecision(precision);
break;
}
case TIMEV2: {
- Preconditions.checkArgument(precision >= scale,
- String.format("given precision %d is out of scale bound %d", precision, scale));
+ if (precision < scale) {
+ throw new IllegalArgumentException(
+ String.format("given precision %d is out of scale bound %d", precision, scale));
+ }
scalarType.setScale(scale);
scalarType.setPrecision(precision);
break;
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..460a824349720c 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
@@ -1300,7 +1300,11 @@ public class Config extends ConfigBase {
* Minimum interval between last version when caching results,
* This parameter distinguishes between offline and real-time updates
*/
- @ConfField(mutable = true, masterOnly = false)
+ @ConfField(
+ mutable = true,
+ masterOnly = false,
+ callbackClassString = "org.apache.doris.common.NereidsSqlCacheManager$UpdateConfig"
+ )
public static int cache_last_version_interval_second = 30;
/**
@@ -2008,6 +2012,20 @@ 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 = true,
+ varType = VariableAnnotation.EXPERIMENTAL,
+ callbackClassString = "org.apache.doris.common.NereidsSqlCacheManager$UpdateConfig",
+ 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/ConfigBase.java b/fe/fe-common/src/main/java/org/apache/doris/common/ConfigBase.java
index 2411ff997c7d0b..dd66fdcb0f593b 100644
--- a/fe/fe-common/src/main/java/org/apache/doris/common/ConfigBase.java
+++ b/fe/fe-common/src/main/java/org/apache/doris/common/ConfigBase.java
@@ -56,6 +56,8 @@ public class ConfigBase {
Class extends ConfHandler> callback() default DefaultConfHandler.class;
+ String callbackClassString() default "";
+
// description for this config item.
// There should be 2 elements in the array.
// The first element is the description in Chinese.
@@ -329,6 +331,16 @@ public static synchronized void setMutableConfig(String key, String value) throw
throw new ConfigException("Failed to set config '" + key + "'. err: " + e.getMessage());
}
+ String callbackClassString = anno.callbackClassString();
+ if (!Strings.isNullOrEmpty(callbackClassString)) {
+ try {
+ ConfHandler confHandler = (ConfHandler) Class.forName(anno.callbackClassString()).newInstance();
+ confHandler.handle(field, value);
+ } catch (Exception e) {
+ throw new ConfigException("Failed to set config '" + key + "'. err: " + e.getMessage());
+ }
+ }
+
LOG.info("set config {} to {}", key, value);
}
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..8989375c07f7d2
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/NereidsSqlCacheManager.java
@@ -0,0 +1,410 @@
+// 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.DatabaseIf;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.catalog.OlapTable;
+import org.apache.doris.catalog.Partition;
+import org.apache.doris.catalog.TableIf;
+import org.apache.doris.catalog.View;
+import org.apache.doris.common.ConfigBase.DefaultConfHandler;
+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.proto.Types.PUniqueId;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.qe.ResultSet;
+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.lang.reflect.Field;
+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: SqlCacheContext
+ private volatile Cache sqlCaches;
+
+ public NereidsSqlCacheManager(int sqlCacheNum, long cacheIntervalSeconds) {
+ sqlCaches = buildSqlCaches(sqlCacheNum, cacheIntervalSeconds);
+ }
+
+ public static synchronized void updateConfig() {
+ Env currentEnv = Env.getCurrentEnv();
+ if (currentEnv == null) {
+ return;
+ }
+ NereidsSqlCacheManager sqlCacheManager = currentEnv.getSqlCacheManager();
+ if (sqlCacheManager == null) {
+ return;
+ }
+
+ Cache sqlCaches = buildSqlCaches(
+ Config.sql_cache_manage_num,
+ Config.cache_last_version_interval_second
+ );
+ sqlCaches.putAll(sqlCacheManager.sqlCaches.asMap());
+ sqlCacheManager.sqlCaches = sqlCaches;
+ }
+
+ private static Cache buildSqlCaches(int sqlCacheNum, long cacheIntervalSeconds) {
+ sqlCacheNum = sqlCacheNum < 0 ? 100 : sqlCacheNum;
+ cacheIntervalSeconds = cacheIntervalSeconds < 0 ? 30 : cacheIntervalSeconds;
+
+ return Caffeine.newBuilder()
+ .maximumSize(sqlCacheNum)
+ .expireAfterAccess(Duration.ofSeconds(cacheIntervalSeconds))
+ // auto evict cache when jvm memory too low
+ .softValues()
+ .build();
+ }
+
+ /** tryAddFeCache */
+ public void tryAddFeSqlCache(ConnectContext connectContext, String sql) {
+ Optional sqlCacheContextOpt = connectContext.getStatementContext().getSqlCacheContext();
+ if (!sqlCacheContextOpt.isPresent()) {
+ return;
+ }
+
+ SqlCacheContext sqlCacheContext = sqlCacheContextOpt.get();
+ UserIdentity currentUserIdentity = connectContext.getCurrentUserIdentity();
+ String key = currentUserIdentity.toString() + ":" + sql.trim();
+ if ((sqlCaches.getIfPresent(key) == null) && sqlCacheContext.getOrComputeCacheKeyMd5() != null
+ && sqlCacheContext.getResultSetInFe().isPresent()) {
+ sqlCaches.put(key, sqlCacheContext);
+ }
+ }
+
+ /** 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 ((currentMissParseSqlFromSqlCache || sqlCaches.getIfPresent(key) == null)
+ && sqlCacheContext.getOrComputeCacheKeyMd5() != null) {
+ SqlCache cache = (SqlCache) analyzer.getCache();
+ 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);
+ }
+
+ sqlCaches.put(key, sqlCacheContext);
+ }
+ }
+
+ /** tryParseSql */
+ public Optional tryParseSql(ConnectContext connectContext, String sql) {
+ UserIdentity currentUserIdentity = connectContext.getCurrentUserIdentity();
+ Env env = connectContext.getEnv();
+ String key = currentUserIdentity.toString() + ":" + sql.trim();
+ SqlCacheContext sqlCacheContext = sqlCaches.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 {
+ Optional resultSetInFe = sqlCacheContext.getResultSetInFe();
+ if (resultSetInFe.isPresent()) {
+ MetricRepo.COUNTER_CACHE_HIT_SQL.increase(1L);
+
+ String cachedPlan = sqlCacheContext.getPhysicalPlan();
+ LogicalSqlCache logicalSqlCache = new LogicalSqlCache(
+ sqlCacheContext.getQueryId(), sqlCacheContext.getColLabels(),
+ sqlCacheContext.getResultExprs(), resultSetInFe, ImmutableList.of(),
+ "none", cachedPlan
+ );
+ return Optional.of(logicalSqlCache);
+ }
+
+ Status status = new Status();
+ PUniqueId cacheKeyMd5 = sqlCacheContext.getOrComputeCacheKeyMd5();
+ InternalService.PFetchCacheResult cacheData =
+ SqlCache.getCacheData(sqlCacheContext.getCacheProxy(),
+ cacheKeyMd5, 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(cacheKeyMd5).getAddress();
+
+ MetricRepo.COUNTER_CACHE_HIT_SQL.increase(1L);
+
+ LogicalSqlCache logicalSqlCache = new LogicalSqlCache(
+ sqlCacheContext.getQueryId(), sqlCacheContext.getColLabels(),
+ sqlCacheContext.getResultExprs(), Optional.empty(),
+ cacheValues, backendAddress, cachedPlan
+ );
+ return Optional.of(logicalSqlCache);
+ }
+ return invalidateCache(key);
+ } catch (Throwable t) {
+ return invalidateCache(key);
+ }
+ }
+
+ 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.getFoldFullNondeterministicPairs();
+ 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 Optional invalidateCache(String key) {
+ sqlCaches.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);
+ }
+
+ // NOTE: used in Config.sql_cache_manage_num.callbackClassString and
+ // Config.cache_last_version_interval_second.callbackClassString,
+ // don't remove it!
+ public static class UpdateConfig extends DefaultConfHandler {
+ @Override
+ public void handle(Field field, String confVal) throws Exception {
+ super.handle(field, confVal);
+ NereidsSqlCacheManager.updateConfig();
+ }
+ }
+}
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..99aa6ed73a1bd8 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
@@ -22,6 +22,7 @@
import org.apache.doris.analysis.LiteralExpr;
import org.apache.doris.analysis.StatementBase;
import org.apache.doris.catalog.Column;
+import org.apache.doris.catalog.Env;
import org.apache.doris.catalog.MTMV;
import org.apache.doris.common.NereidsException;
import org.apache.doris.common.Pair;
@@ -51,10 +52,13 @@
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.PhysicalEmptyRelation;
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;
@@ -63,8 +67,10 @@
import org.apache.doris.qe.ConnectContext;
import org.apache.doris.qe.ResultSet;
import org.apache.doris.qe.ResultSetMetaData;
+import org.apache.doris.qe.cache.CacheAnalyzer;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -94,6 +100,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 +118,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 +135,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 +165,99 @@ 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.getResultSetInFe(),
+ 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 +310,47 @@ private void optimize() {
}
}
+ private void translate(PhysicalPlan resultPlan) {
+ 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);
}
@@ -491,31 +529,66 @@ public Optional handleQueryInFe(StatementBase parsedStmt) {
if (!(parsedStmt instanceof LogicalPlanAdapter)) {
return Optional.empty();
}
- if (!(physicalPlan instanceof PhysicalResultSink)) {
- return Optional.empty();
+ if (physicalPlan instanceof PhysicalSqlCache
+ && ((PhysicalSqlCache) physicalPlan).getResultSet().isPresent()) {
+ return Optional.of(((PhysicalSqlCache) physicalPlan).getResultSet().get());
}
- if (!(((PhysicalResultSink>) physicalPlan).child() instanceof PhysicalOneRowRelation)) {
+ if (!(physicalPlan instanceof PhysicalResultSink)) {
return Optional.empty();
}
- PhysicalOneRowRelation physicalOneRowRelation
- = (PhysicalOneRowRelation) ((PhysicalResultSink>) physicalPlan).child();
- List columns = Lists.newArrayList();
- List data = Lists.newArrayList();
- for (int i = 0; i < physicalOneRowRelation.getProjects().size(); i++) {
- NamedExpression item = physicalOneRowRelation.getProjects().get(i);
- NamedExpression output = physicalPlan.getOutput().get(i);
- Expression expr = item.child(0);
- if (expr instanceof Literal) {
- LiteralExpr legacyExpr = ((Literal) expr).toLegacyLiteral();
+
+ Optional sqlCacheContext = statementContext.getSqlCacheContext();
+ boolean enableSqlCache
+ = CacheAnalyzer.canUseSqlCache(statementContext.getConnectContext().getSessionVariable());
+ Plan child = physicalPlan.child(0);
+ if (child instanceof PhysicalOneRowRelation) {
+ PhysicalOneRowRelation physicalOneRowRelation = (PhysicalOneRowRelation) physicalPlan.child(0);
+ List columns = Lists.newArrayList();
+ List data = Lists.newArrayList();
+ for (int i = 0; i < physicalOneRowRelation.getProjects().size(); i++) {
+ NamedExpression item = physicalOneRowRelation.getProjects().get(i);
+ NamedExpression output = physicalPlan.getOutput().get(i);
+ Expression expr = item.child(0);
+ if (expr instanceof Literal) {
+ LiteralExpr legacyExpr = ((Literal) expr).toLegacyLiteral();
+ columns.add(new Column(output.getName(), output.getDataType().toCatalogDataType()));
+ data.add(legacyExpr.getStringValueInFe());
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ ResultSetMetaData metadata = new CommonResultSet.CommonResultSetMetaData(columns);
+ ResultSet resultSet = new CommonResultSet(metadata, Collections.singletonList(data));
+ if (sqlCacheContext.isPresent() && enableSqlCache) {
+ sqlCacheContext.get().setResultSetInFe(resultSet);
+ Env.getCurrentEnv().getSqlCacheManager().tryAddFeSqlCache(
+ statementContext.getConnectContext(),
+ statementContext.getOriginStatement().originStmt
+ );
+ }
+ return Optional.of(resultSet);
+ } else if (child instanceof PhysicalEmptyRelation) {
+ List columns = Lists.newArrayList();
+ PhysicalEmptyRelation physicalEmptyRelation = (PhysicalEmptyRelation) physicalPlan.child(0);
+ for (int i = 0; i < physicalEmptyRelation.getProjects().size(); i++) {
+ NamedExpression output = physicalPlan.getOutput().get(i);
columns.add(new Column(output.getName(), output.getDataType().toCatalogDataType()));
- data.add(legacyExpr.getStringValueInFe());
- } else {
- return Optional.empty();
}
+
+ ResultSetMetaData metadata = new CommonResultSet.CommonResultSetMetaData(columns);
+ ResultSet resultSet = new CommonResultSet(metadata, ImmutableList.of());
+ if (sqlCacheContext.isPresent() && enableSqlCache) {
+ sqlCacheContext.get().setResultSetInFe(resultSet);
+ Env.getCurrentEnv().getSqlCacheManager().tryAddFeSqlCache(
+ statementContext.getConnectContext(),
+ statementContext.getOriginStatement().originStmt
+ );
+ }
+ return Optional.of(resultSet);
+ } else {
+ return Optional.empty();
}
- ResultSetMetaData metadata = new CommonResultSet.CommonResultSetMetaData(columns);
- ResultSet resultSet = new CommonResultSet(metadata, Collections.singletonList(data));
- return Optional.of(resultSet);
}
@VisibleForTesting
@@ -569,6 +642,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..f3fa61cecaad8b
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/SqlCacheContext.java
@@ -0,0 +1,433 @@
+// 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.ResultSet;
+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.Map.Entry;
+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 originSql;
+ 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_add(date_column, date(now()))
+ // value: the expression which already try to fold nondeterministic function,
+ // e.g. date_add(date_column, '2024-01-01')
+ // note that value maybe contains nondeterministic function too, when fold failed
+ private final List> foldFullNondeterministicPairs = Lists.newArrayList();
+ // key: the expression which **is** nondeterministic function, e.g. now()
+ // value: the expression which already try to fold nondeterministic function, e.g. '2024-01-01 10:01:03'
+ 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;
+ private volatile ResultSet resultSetInFe;
+
+ 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 addFoldFullNondeterministicPair(Expression unfold, Expression fold) {
+ foldFullNondeterministicPairs.add(Pair.of(unfold, fold));
+ }
+
+ public synchronized List> getFoldFullNondeterministicPairs() {
+ return ImmutableList.copyOf(foldFullNondeterministicPairs);
+ }
+
+ 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 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;
+ }
+
+ /** getOrComputeCacheKeyMd5 */
+ public PUniqueId getOrComputeCacheKeyMd5() {
+ if (cacheKeyMd5 == null && originSql != null) {
+ synchronized (this) {
+ if (cacheKeyMd5 != null) {
+ return cacheKeyMd5;
+ }
+
+ StringBuilder cacheKey = new StringBuilder(originSql);
+ for (Entry entry : usedViews.entrySet()) {
+ cacheKey.append("|")
+ .append(entry.getKey())
+ .append("=")
+ .append(entry.getValue());
+ }
+ for (Variable usedVariable : usedVariables) {
+ cacheKey.append("|")
+ .append(usedVariable.getType().name())
+ .append(":")
+ .append(usedVariable.getName())
+ .append("=")
+ .append(usedVariable.getRealExpression().toSql());
+ }
+ for (Pair pair : foldNondeterministicPairs) {
+ cacheKey.append("|")
+ .append(pair.key().toSql())
+ .append("=")
+ .append(pair.value().toSql());
+ }
+ for (Entry> entry : rowPolicies.entrySet()) {
+ List policy = entry.getValue();
+ if (policy.isEmpty()) {
+ continue;
+ }
+ cacheKey.append("|")
+ .append(entry.getKey())
+ .append("=")
+ .append(policy);
+ }
+ for (Entry> entry : dataMaskPolicies.entrySet()) {
+ if (!entry.getValue().isPresent()) {
+ continue;
+ }
+ cacheKey.append("|")
+ .append(entry.getKey())
+ .append("=")
+ .append(entry.getValue().map(Object::toString).orElse(""));
+ }
+ cacheKeyMd5 = CacheProxy.getMd5(cacheKey.toString());
+ }
+ }
+ return cacheKeyMd5;
+ }
+
+ public void setOriginSql(String originSql) {
+ this.originSql = originSql.trim();
+ }
+
+ public Optional getResultSetInFe() {
+ return Optional.ofNullable(resultSetInFe);
+ }
+
+ public void setResultSetInFe(ResultSet resultSetInFe) {
+ this.resultSetInFe = resultSetInFe;
+ }
+
+ /** FullTableName */
+ @lombok.Data
+ @lombok.AllArgsConstructor
+ public static class FullTableName {
+ public final String catalog;
+ public final String db;
+ public final String table;
+
+ @Override
+ public String toString() {
+ return catalog + "." + db + "." + 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;
+
+ @Override
+ public String toString() {
+ return catalog + "." + db + "." + table + "." + 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..a468a0f0cb7f21 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,7 @@ 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;
// collect all hash join conditions to compute node connectivity in join graph
private final List joinFilters = new ArrayList<>();
@@ -122,18 +133,33 @@ 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());
+ if (originStatement != null) {
+ this.sqlCacheContext.setOriginSql(originStatement.originStmt.trim());
+ }
+ } else {
+ this.sqlCacheContext = null;
+ }
}
public void setConnectContext(ConnectContext connectContext) {
@@ -146,6 +172,9 @@ public ConnectContext getConnectContext() {
public void setOriginStatement(OriginStatement originStatement) {
this.originStatement = originStatement;
+ if (originStatement != null && sqlCacheContext != null) {
+ sqlCacheContext.setOriginSql(originStatement.originStmt.trim());
+ }
}
public OriginStatement getOriginStatement() {
@@ -172,6 +201,10 @@ public void setMaxContinuousJoin(int joinCount) {
}
}
+ public Optional getSqlCacheContext() {
+ return Optional.ofNullable(sqlCacheContext);
+ }
+
public int getMaxContinuousJoin() {
return joinCount;
}
@@ -368,4 +401,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..691e4aca1a337d 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 +396,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