This is an automated email from the ASF dual-hosted git repository.
morrySnow pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new 1034d0a2d65 [fix](statistics) Quote identifiers in statistics queries
(#67703)
1034d0a2d65 is described below
commit 1034d0a2d655427a3cfab1cfc23e584895a54537
Author: morrySnow <[email protected]>
AuthorDate: Fri Sep 11 23:18:05 2026 +0800
[fix](statistics) Quote identifiers in statistics queries (#67703)
## Problem
Statistics collection builds internal SQL from catalog, database, table,
index, partition, and column names. Names containing an embedded
backtick could terminate a template-owned quoted identifier. Since
statistics statements execute through an internal privileged context,
the remaining name text could be interpreted as SQL and access objects
unavailable to the initiating user.
## Root cause
The SQL templates owned the surrounding backticks while callers
substituted raw names, or only partially escaped selected column names.
This split ownership was inconsistent across full, sampled, partition,
external-table, plugin-driven, and histogram collection paths. It also
made it easy for a newly added template expression to consume an
unescaped name.
## How to reproduce
1. Create a low-privilege user that can create and analyze tables in one
database but cannot read a table in another database.
2. In the permitted database, create a table whose quoted name contains
an embedded backtick followed by a crafted SQL fragment such as a
`UNION` query against the inaccessible table.
3. Trigger synchronous statistics collection for that table.
4. Before this change, the embedded backtick closes the template's
identifier and the remaining text is parsed in the privileged statistics
context. Values from the inaccessible table can consequently appear in
the attacker's statistics rows.
## Fix
- Build every object-name fragment with `SqlUtils.getIdentSql`, so
embedded backticks are doubled and the complete value remains one
identifier.
- Make templates consume already quoted identifier fragments instead of
adding their own backticks.
- Apply the same model to OLAP, external-table, plugin-driven sample,
partition, index, and histogram SQL generation.
- Keep metadata values used as string literals on a separate SQL-literal
escaping path.
- Add unit coverage for catalog, database, table, index, partition, and
column names containing embedded backticks, including a column name that
also contains an apostrophe.
## Tests
- `OlapAnalysisTaskTest`: 25 tests passed.
- `HistogramTaskTest`: 2 tests passed.
- `AnalyzeTest`: 2 tests passed.
- `BaseAnalysisTaskTest`: 3 tests passed.
- Re-triggered FE UT build: passed.
---
.../statistics/analysis/BaseAnalysisTask.java | 73 ++++++++++----------
.../statistics/analysis/ExternalAnalysisTask.java | 14 ++--
.../doris/statistics/analysis/HistogramTask.java | 17 ++---
.../statistics/analysis/OlapAnalysisTask.java | 25 +++----
.../analysis/PluginDrivenSampleAnalysisTask.java | 4 +-
.../doris/statistics/analysis/AnalyzeTest.java | 3 +
.../statistics/analysis/BaseAnalysisTaskTest.java | 6 +-
.../statistics/analysis/OlapAnalysisTaskTest.java | 79 ++++++++++++++++------
8 files changed, 134 insertions(+), 87 deletions(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/BaseAnalysisTask.java
b/fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/BaseAnalysisTask.java
index 52a1077aa8e..5bbb96e1134 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/BaseAnalysisTask.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/BaseAnalysisTask.java
@@ -28,6 +28,7 @@ import org.apache.doris.catalog.Type;
import org.apache.doris.common.DdlException;
import org.apache.doris.common.Status;
import org.apache.doris.common.util.DebugUtil;
+import org.apache.doris.common.util.SqlUtils;
import org.apache.doris.datasource.CatalogIf;
import org.apache.doris.metric.MetricRepo;
import org.apache.doris.qe.AuditLogHelper;
@@ -91,8 +92,8 @@ public abstract class BaseAnalysisTask {
public static final String ANALYZE_SKIP_LONG_STRING_COLUMN_MARKER =
"ANALYZE_SKIP_LONG_STRING_COLUMN";
protected static final String FULL_ANALYZE_TEMPLATE = "WITH cte1 AS ("
- + "SELECT `${colName}`${lengthAssert} "
- + "FROM `${catalogName}`.`${dbName}`.`${tblName}` ${index}), "
+ + "SELECT ${colName}${lengthAssert} "
+ + "FROM ${catalogName}.${dbName}.${tblName} ${index}), "
+ "cte2 AS ("
+ "SELECT CONCAT(${tblId}, '-', ${idxId}, '-', '${colId}') AS
`id`, "
+ "${catalogId} AS `catalog_id`, "
@@ -102,10 +103,10 @@ public abstract class BaseAnalysisTask {
+ "'${colId}' AS `col_id`, "
+ "NULL AS `part_id`, "
+ "COUNT(1) AS `row_count`, "
- + "NDV(`${colName}`) AS `ndv`, "
- + "COUNT(1) - COUNT(`${colName}`) AS `null_count`, "
- + "SUBSTRING(CAST(MIN(`${colName}`) AS STRING), 1, 1024) AS
`min`, "
- + "SUBSTRING(CAST(MAX(`${colName}`) AS STRING), 1, 1024) AS
`max`, "
+ + "NDV(${colName}) AS `ndv`, "
+ + "COUNT(1) - COUNT(${colName}) AS `null_count`, "
+ + "SUBSTRING(CAST(MIN(${colName}) AS STRING), 1, 1024) AS
`min`, "
+ + "SUBSTRING(CAST(MAX(${colName}) AS STRING), 1, 1024) AS
`max`, "
+ "${dataSizeFunction} AS `data_size`, "
+ "NOW() "
+ "FROM cte1), "
@@ -116,9 +117,9 @@ public abstract class BaseAnalysisTask {
+ "as `hot_value` "
+ "FROM ("
+ "SELECT ${subStringColName} as `hash_value`, "
- + "MAX(`${colName}`) as `column_key`, "
+ + "MAX(${colName}) as `column_key`, "
+ "COUNT(1) AS `count` "
- + "FROM cte1 WHERE `${colName}` IS NOT NULL "
+ + "FROM cte1 WHERE ${colName} IS NOT NULL "
+ "GROUP BY `hash_value` ORDER BY `count` DESC LIMIT
${hotValueCollectCount}) t) "
+ "SELECT * FROM cte2 CROSS JOIN cte3";
@@ -131,19 +132,19 @@ public abstract class BaseAnalysisTask {
+ "'${colId}' AS `col_id`, "
+ "NULL AS `part_id`, "
+ "COUNT(1) AS `row_count`, "
- + "NDV(`${colName}`) AS `ndv`, "
- + "COUNT(1) - COUNT(`${colName}`) AS `null_count`, "
- + "SUBSTRING(CAST(MIN(`${colName}`) AS STRING), 1, 1024) AS
`min`, "
- + "SUBSTRING(CAST(MAX(`${colName}`) AS STRING), 1, 1024) AS
`max`, "
+ + "NDV(${colName}) AS `ndv`, "
+ + "COUNT(1) - COUNT(${colName}) AS `null_count`, "
+ + "SUBSTRING(CAST(MIN(${colName}) AS STRING), 1, 1024) AS
`min`, "
+ + "SUBSTRING(CAST(MAX(${colName}) AS STRING), 1, 1024) AS
`max`, "
+ "${dataSizeFunction} AS `data_size`, "
+ "NOW() AS `update_time`, "
+ "null as `hot_value` "
- + "FROM (SELECT `${colName}`${lengthAssert} "
- + "FROM `${catalogName}`.`${dbName}`.`${tblName}` ${index})
__lc_t";
+ + "FROM (SELECT ${colName}${lengthAssert} "
+ + "FROM ${catalogName}.${dbName}.${tblName} ${index}) __lc_t";
protected static final String LINEAR_ANALYZE_TEMPLATE = "WITH cte1 AS ("
- + "SELECT `${colName}`${lengthAssert} "
- + "FROM `${catalogName}`.`${dbName}`.`${tblName}` ${index}
${sampleHints} ${limit} ${preAggHint}), "
+ + "SELECT ${colName}${lengthAssert} "
+ + "FROM ${catalogName}.${dbName}.${tblName} ${index}
${sampleHints} ${limit} ${preAggHint}), "
+ "cte2 AS ("
+ "SELECT CONCAT(${tblId}, '-', ${idxId}, '-', '${colId}') AS
`id`, "
+ "${catalogId} AS `catalog_id`, "
@@ -154,7 +155,7 @@ public abstract class BaseAnalysisTask {
+ "NULL AS `part_id`, "
+ "${rowCount} AS `row_count`, "
+ "${ndvFunction} as `ndv`, "
- + "ROUND(SUM(CASE WHEN `${colName}` IS NULL THEN 1 ELSE 0 END)
* ${scaleFactor}) AS `null_count`, "
+ + "ROUND(SUM(CASE WHEN ${colName} IS NULL THEN 1 ELSE 0 END) *
${scaleFactor}) AS `null_count`, "
+ "SUBSTRING(CAST(${min} AS STRING), 1, 1024) AS `min`, "
+ "SUBSTRING(CAST(${max} AS STRING), 1, 1024) AS `max`, "
+ "${dataSizeFunction} * ${scaleFactor} AS `data_size`, "
@@ -166,9 +167,9 @@ public abstract class BaseAnalysisTask {
+ "as `hot_value` "
+ "FROM ("
+ "SELECT ${subStringColName} as `hash_value`, "
- + "MAX(`${colName}`) as `column_key`, "
+ + "MAX(${colName}) as `column_key`, "
+ "COUNT(1) AS `count` "
- + "FROM cte1 WHERE `${colName}` IS NOT NULL "
+ + "FROM cte1 WHERE ${colName} IS NOT NULL "
+ "GROUP BY `hash_value` ORDER BY `count` DESC LIMIT
${hotValueCollectCount}) t) "
+ "SELECT * FROM cte2 CROSS JOIN cte3";
@@ -177,9 +178,9 @@ public abstract class BaseAnalysisTask {
+ "FROM "
+ "(SELECT "
+ "${subStringColName} AS `hash_value`, "
- + "`${colName}` AS `col_value`, "
- + "LENGTH(`${colName}`) as `len`${lengthAssert} "
- + "FROM `${catalogName}`.`${dbName}`.`${tblName}` ${index}
${sampleHints} ${limit}) as `t0` "
+ + "${colName} AS `col_value`, "
+ + "LENGTH(${colName}) as `len`${lengthAssert} "
+ + "FROM ${catalogName}.${dbName}.${tblName} ${index}
${sampleHints} ${limit}) as `t0` "
+ "${preAggHint} GROUP BY `t0`.`hash_value`), "
+ "cte2 AS ( "
+ "SELECT CONCAT('${tblId}', '-', '${idxId}', '-', '${colId}')
AS `id`, "
@@ -241,13 +242,13 @@ public abstract class BaseAnalysisTask {
+ "${partId} AS `part_id`, "
+ "'${colId}' AS `col_id`, "
+ "COUNT(1) AS `row_count`, "
- + "HLL_UNION(HLL_HASH(`${colName}`)) as ndv, "
- + "COUNT(1) - COUNT(`${colName}`) AS `null_count`, "
- + "SUBSTRING(CAST(MIN(`${colName}`) AS STRING), 1, 1024) AS `min`,
"
- + "SUBSTRING(CAST(MAX(`${colName}`) AS STRING), 1, 1024) AS `max`,
"
+ + "HLL_UNION(HLL_HASH(${colName})) as ndv, "
+ + "COUNT(1) - COUNT(${colName}) AS `null_count`, "
+ + "SUBSTRING(CAST(MIN(${colName}) AS STRING), 1, 1024) AS `min`, "
+ + "SUBSTRING(CAST(MAX(${colName}) AS STRING), 1, 1024) AS `max`, "
+ "${dataSizeFunction} AS `data_size`, "
+ "NOW() AS `update_time` "
- + "FROM `${catalogName}`.`${dbName}`.`${tblName}` ${index}
${partitionInfo}";
+ + "FROM ${catalogName}.${dbName}.${tblName} ${index}
${partitionInfo}";
protected static final String MERGE_PARTITION_TEMPLATE =
"SELECT CONCAT(${tblId}, '-', ${idxId}, '-', '${colId}') AS `id`, "
@@ -418,7 +419,7 @@ public abstract class BaseAnalysisTask {
}
} else {
if (column.getType().isStringType()) {
- return "SUM(LENGTH(`${colName}`))";
+ return "SUM(LENGTH(${colName}))";
} else {
return "COUNT(1) * " + column.getType().getSlotSize();
}
@@ -427,15 +428,15 @@ public abstract class BaseAnalysisTask {
protected String getStringTypeColName(Column column) {
if (column.getType().isStringType()) {
- return "xxhash_64(SUBSTRING(CAST(`${colName}` AS STRING), 1,
1024))";
+ return "xxhash_64(SUBSTRING(CAST(${colName} AS STRING), 1, 1024))";
} else {
- return "`${colName}`";
+ return "${colName}";
}
}
protected String getMinFunction() {
if (tableSample == null) {
- return "CAST(MIN(`${colName}`) as ${type}) ";
+ return "CAST(MIN(${colName}) as ${type}) ";
} else {
// Min value is not accurate while sample, so set it to NULL to
avoid optimizer generate bad plan.
return "NULL";
@@ -456,7 +457,7 @@ public abstract class BaseAnalysisTask {
// Max value is not accurate while sample, so set it to NULL to avoid
optimizer generate bad plan.
protected String getMaxFunction() {
if (tableSample == null) {
- return "CAST(MAX(`${colName}`) as ${type}) ";
+ return "CAST(MAX(${colName}) as ${type}) ";
} else {
return "NULL";
}
@@ -562,7 +563,7 @@ public abstract class BaseAnalysisTask {
}
}
}
- params.put("partName", "'" + StatisticsUtil.escapeColumnName(part)
+ "'");
+ params.put("partName",
StatisticsUtil.quote(StatisticsUtil.escapeSQL(part)));
params.put("partitionInfo", getPartitionInfo(part));
StringSubstitutor stringSubstitutor = new
StringSubstitutor(params);
sqls.add(stringSubstitutor.replace(PARTITION_ANALYZE_TEMPLATE));
@@ -646,11 +647,11 @@ public abstract class BaseAnalysisTask {
protected void addLengthAssertParam(Map<String, String> params) {
long maxLen =
org.apache.doris.common.Config.statistics_max_string_column_length;
if (col != null && col.getType().isStringType() && maxLen > 0) {
- String escapedColName =
StatisticsUtil.escapeColumnName(String.valueOf(info.colName));
+ String quotedColName =
SqlUtils.getIdentSql(String.valueOf(info.colName));
// The StringSubstitutor used by callers already has ${colName}
populated,
- // so we inline the escaped column name directly here.
+ // so we inline the quoted column name directly here.
params.put("lengthAssert",
- ", assert_true(`" + escapedColName + "` IS NULL OR
LENGTH(`" + escapedColName + "`) <= "
+ ", assert_true(" + quotedColName + " IS NULL OR LENGTH(" +
quotedColName + ") <= "
+ maxLen + ", '" +
ANALYZE_SKIP_LONG_STRING_COLUMN_MARKER + "') AS `__lc`");
} else {
params.put("lengthAssert", "");
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/ExternalAnalysisTask.java
b/fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/ExternalAnalysisTask.java
index d9e4ce9237f..d21deaff687 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/ExternalAnalysisTask.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/ExternalAnalysisTask.java
@@ -20,9 +20,11 @@ package org.apache.doris.statistics.analysis;
import org.apache.doris.common.DdlException;
import org.apache.doris.common.FeConstants;
import org.apache.doris.common.NotImplementedException;
+import org.apache.doris.common.util.SqlUtils;
import org.apache.doris.datasource.ExternalTable;
import org.apache.doris.qe.SessionVariable;
import org.apache.doris.statistics.StatisticConstants;
+import org.apache.doris.statistics.util.StatisticsUtil;
import org.apache.commons.text.StringSubstitutor;
@@ -68,7 +70,7 @@ public class ExternalAnalysisTask extends BaseAnalysisTask {
if (shouldCollectHotValue()) {
params.put("hotValueCollectCount",
String.valueOf(SessionVariable.getHotValueCollectCount()));
params.put("subStringColName", getStringTypeColName(col));
- params.put("rowCount2", "(SELECT COUNT(1) FROM cte1 WHERE
`${colName}` IS NOT NULL)");
+ params.put("rowCount2", "(SELECT COUNT(1) FROM cte1 WHERE
${colName} IS NOT NULL)");
template = FULL_ANALYZE_TEMPLATE;
} else {
template = FULL_ANALYZE_WITHOUT_HOT_VALUE_TEMPLATE;
@@ -87,11 +89,11 @@ public class ExternalAnalysisTask extends BaseAnalysisTask {
params.put("dbId", String.valueOf(db.getId()));
params.put("tblId", String.valueOf(tbl.getId()));
params.put("idxId", "-1");
- params.put("colName", info.colName);
- params.put("colId", info.colName);
- params.put("catalogName", catalog.getName());
- params.put("dbName", db.getFullName());
- params.put("tblName", tbl.getName());
+ params.put("colName", SqlUtils.getIdentSql(info.colName));
+ params.put("colId", StatisticsUtil.escapeSQL(info.colName));
+ params.put("catalogName", SqlUtils.getIdentSql(catalog.getName()));
+ params.put("dbName", SqlUtils.getIdentSql(db.getFullName()));
+ params.put("tblName", SqlUtils.getIdentSql(tbl.getName()));
params.put("sampleHints", getSampleHint());
params.put("limit", "");
params.put("scaleFactor", "1");
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/HistogramTask.java
b/fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/HistogramTask.java
index ed843c05cc9..46cdc0d3225 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/HistogramTask.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/HistogramTask.java
@@ -20,6 +20,7 @@ package org.apache.doris.statistics.analysis;
import org.apache.doris.catalog.Env;
import org.apache.doris.common.DdlException;
import org.apache.doris.common.FeConstants;
+import org.apache.doris.common.util.SqlUtils;
import org.apache.doris.statistics.StatisticConstants;
import org.apache.doris.statistics.analysis.AnalysisInfo.AnalysisMethod;
import org.apache.doris.statistics.util.StatisticsUtil;
@@ -44,10 +45,10 @@ public class HistogramTask extends BaseAnalysisTask {
+ " ${idxId} AS idx_id, "
+ " '${colId}' AS col_id, "
+ " ${sampleRate} AS sample_rate, "
- + " HISTOGRAM(`${colName}`, ${maxBucketNum}) AS buckets, "
+ + " HISTOGRAM(${colName}, ${maxBucketNum}) AS buckets, "
+ " NOW() AS create_time "
+ "FROM "
- + " `${dbName}`.`${tblName}`";
+ + " ${dbName}.${tblName}";
public HistogramTask(AnalysisInfo info) {
super(info);
@@ -56,16 +57,16 @@ public class HistogramTask extends BaseAnalysisTask {
@Override
public void doExecute() throws Exception {
Map<String, String> params = new HashMap<>();
- params.put("internalDB", FeConstants.INTERNAL_DB_NAME);
- params.put("histogramStatTbl", StatisticConstants.HISTOGRAM_TBL_NAME);
+ params.put("internalDB",
SqlUtils.getIdentSql(FeConstants.INTERNAL_DB_NAME));
+ params.put("histogramStatTbl",
SqlUtils.getIdentSql(StatisticConstants.HISTOGRAM_TBL_NAME));
params.put("catalogId", String.valueOf(catalog.getId()));
params.put("dbId", String.valueOf(db.getId()));
params.put("tblId", String.valueOf(tbl.getId()));
params.put("idxId", String.valueOf(info.indexId));
- params.put("colId", String.valueOf(info.colName));
- params.put("dbName", db.getFullName());
- params.put("tblName", tbl.getName());
- params.put("colName", String.valueOf(info.colName));
+ params.put("colId",
StatisticsUtil.escapeSQL(String.valueOf(info.colName)));
+ params.put("dbName", SqlUtils.getIdentSql(db.getFullName()));
+ params.put("tblName", SqlUtils.getIdentSql(tbl.getName()));
+ params.put("colName",
SqlUtils.getIdentSql(String.valueOf(info.colName)));
params.put("sampleRate", getSampleRateFunction());
params.put("maxBucketNum", String.valueOf(info.maxBucketNum));
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/OlapAnalysisTask.java
b/fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/OlapAnalysisTask.java
index 9644aadfb00..606a2424d10 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/OlapAnalysisTask.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/OlapAnalysisTask.java
@@ -34,6 +34,7 @@ import org.apache.doris.common.FeConstants;
import org.apache.doris.common.Pair;
import org.apache.doris.common.util.DebugPointUtil;
import org.apache.doris.common.util.DebugUtil;
+import org.apache.doris.common.util.SqlUtils;
import org.apache.doris.qe.AutoCloseConnectContext;
import org.apache.doris.qe.SessionVariable;
import org.apache.doris.qe.StmtExecutor;
@@ -67,9 +68,9 @@ import java.util.stream.Collectors;
public class OlapAnalysisTask extends BaseAnalysisTask {
private static final String BASIC_STATS_TEMPLATE = "SELECT "
- + "SUBSTRING(CAST(MIN(`${colName}`) AS STRING), 1, 1024) as min, "
- + "SUBSTRING(CAST(MAX(`${colName}`) AS STRING), 1, 1024) as max "
- + "FROM `${dbName}`.`${tblName}` ${index}";
+ + "SUBSTRING(CAST(MIN(${colName}) AS STRING), 1, 1024) as min, "
+ + "SUBSTRING(CAST(MAX(${colName}) AS STRING), 1, 1024) as max "
+ + "FROM ${dbName}.${tblName} ${index}";
private boolean keyColumnSampleTooManyRows = false;
private boolean partitionColumnSampleTooManyRows = false;
@@ -365,12 +366,12 @@ public class OlapAnalysisTask extends BaseAnalysisTask {
params.put("dataSizeFunction", getDataSizeFunction(col, true));
params.put("rowCount2", "(SELECT SUM(`count`) FROM cte1 WHERE
`col_value` IS NOT NULL)");
} else {
- params.put("rowCount2", "(SELECT COUNT(1) FROM cte1 WHERE
`${colName}` IS NOT NULL)");
+ params.put("rowCount2", "(SELECT COUNT(1) FROM cte1 WHERE
${colName} IS NOT NULL)");
// For single unique key, use count as ndv.
if (isSingleUniqueKey()) {
params.put("ndvFunction", String.valueOf(tableRowCount));
} else {
- params.put("ndvFunction", "ROUND(NDV(`${colName}`) *
${scaleFactor})");
+ params.put("ndvFunction", "ROUND(NDV(${colName}) *
${scaleFactor})");
}
}
}
@@ -387,7 +388,7 @@ public class OlapAnalysisTask extends BaseAnalysisTask {
if (shouldCollectHotValue()) {
params.put("hotValueCollectCount",
String.valueOf(SessionVariable.getHotValueCollectCount()));
params.put("subStringColName", getStringTypeColName(col));
- params.put("rowCount2", "(SELECT COUNT(1) FROM cte1 WHERE
`${colName}` IS NOT NULL)");
+ params.put("rowCount2", "(SELECT COUNT(1) FROM cte1 WHERE
${colName} IS NOT NULL)");
runQuery(stringSubstitutor.replace(FULL_ANALYZE_TEMPLATE));
} else {
runQuery(stringSubstitutor.replace(FULL_ANALYZE_WITHOUT_HOT_VALUE_TEMPLATE));
@@ -446,7 +447,7 @@ public class OlapAnalysisTask extends BaseAnalysisTask {
@Override
protected String getPartitionInfo(String partitionName) {
- return "partition " + partitionName;
+ return "partition " + SqlUtils.getIdentSql(partitionName);
}
@Override
@@ -460,10 +461,10 @@ public class OlapAnalysisTask extends BaseAnalysisTask {
params.put("idxId", String.valueOf(info.indexId));
params.put("colId",
StatisticsUtil.escapeSQL(String.valueOf(info.colName)));
params.put("dataSizeFunction", getDataSizeFunction(col, false));
- params.put("catalogName", catalog.getName());
- params.put("dbName", db.getFullName());
- params.put("colName",
StatisticsUtil.escapeColumnName(String.valueOf(info.colName)));
- params.put("tblName", String.valueOf(tbl.getName()));
+ params.put("catalogName", SqlUtils.getIdentSql(catalog.getName()));
+ params.put("dbName", SqlUtils.getIdentSql(db.getFullName()));
+ params.put("colName",
SqlUtils.getIdentSql(String.valueOf(info.colName)));
+ params.put("tblName",
SqlUtils.getIdentSql(String.valueOf(tbl.getName())));
params.put("index", getIndex());
params.put("preAggHint", "");
addLengthAssertParam(params);
@@ -475,7 +476,7 @@ public class OlapAnalysisTask extends BaseAnalysisTask {
return "";
} else {
OlapTable olapTable = (OlapTable) this.tbl;
- return "index `" + olapTable.getIndexNameById(info.indexId) + "`";
+ return "index " +
SqlUtils.getIdentSql(olapTable.getIndexNameById(info.indexId));
}
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/PluginDrivenSampleAnalysisTask.java
b/fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/PluginDrivenSampleAnalysisTask.java
index 6f55158189f..91690df1eb0 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/PluginDrivenSampleAnalysisTask.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/PluginDrivenSampleAnalysisTask.java
@@ -89,9 +89,9 @@ public class PluginDrivenSampleAnalysisTask extends
ExternalAnalysisTask {
if (distributionColumns.size() == 1 &&
distributionColumns.contains(col.getName().toLowerCase())) {
bucketFlag = true;
sb.append(LINEAR_ANALYZE_TEMPLATE);
- params.put("ndvFunction", "ROUND(NDV(`${colName}`) *
${scaleFactor})");
+ params.put("ndvFunction", "ROUND(NDV(${colName}) *
${scaleFactor})");
params.put("rowCount", "ROUND(COUNT(1) * ${scaleFactor})");
- params.put("rowCount2", "(SELECT COUNT(1) FROM cte1 WHERE
`${colName}` IS NOT NULL)");
+ params.put("rowCount2", "(SELECT COUNT(1) FROM cte1 WHERE
${colName} IS NOT NULL)");
} else {
sb.append(DUJ1_ANALYZE_TEMPLATE);
params.put("subStringColName", getStringTypeColName(col));
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/statistics/analysis/AnalyzeTest.java
b/fe/fe-core/src/test/java/org/apache/doris/statistics/analysis/AnalyzeTest.java
index 4e9658d4f53..0b82a21bd0f 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/statistics/analysis/AnalyzeTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/statistics/analysis/AnalyzeTest.java
@@ -91,6 +91,9 @@ public class AnalyzeTest extends TestWithFeService {
Database database = Mockito.mock(Database.class);
OlapTable olapTable = Mockito.mock(OlapTable.class);
+ Mockito.when(catalog.getName()).thenReturn("internal");
+ Mockito.when(database.getFullName()).thenReturn("analysis_job_test");
+ Mockito.when(olapTable.getName()).thenReturn("t1");
Mockito.when(olapTable.getColumn(Mockito.anyString()))
.thenReturn(new Column("col1", PrimitiveType.INT));
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/statistics/analysis/BaseAnalysisTaskTest.java
b/fe/fe-core/src/test/java/org/apache/doris/statistics/analysis/BaseAnalysisTaskTest.java
index ad23fb6155b..597c23da2d5 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/statistics/analysis/BaseAnalysisTaskTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/statistics/analysis/BaseAnalysisTaskTest.java
@@ -39,7 +39,7 @@ public class BaseAnalysisTaskTest {
String dataSizeFunction = olapAnalysisTask.getDataSizeFunction(column,
true);
Assertions.assertEquals("SUM(`column_length`)", dataSizeFunction);
dataSizeFunction = olapAnalysisTask.getDataSizeFunction(column, false);
- Assertions.assertEquals("SUM(LENGTH(`${colName}`))", dataSizeFunction);
+ Assertions.assertEquals("SUM(LENGTH(${colName}))", dataSizeFunction);
column = new Column("int_column", PrimitiveType.INT);
dataSizeFunction = olapAnalysisTask.getDataSizeFunction(column, false);
@@ -48,14 +48,14 @@ public class BaseAnalysisTaskTest {
Assertions.assertEquals("SUM(t1.count) * 4", dataSizeFunction);
String minFunction = olapAnalysisTask.getMinFunction();
- Assertions.assertEquals("CAST(MIN(`${colName}`) as ${type}) ",
minFunction);
+ Assertions.assertEquals("CAST(MIN(${colName}) as ${type}) ",
minFunction);
olapAnalysisTask.tableSample = new TableSample(true, 20L);
minFunction = olapAnalysisTask.getMinFunction();
Assertions.assertEquals("NULL", minFunction);
olapAnalysisTask.tableSample = null;
String maxFunction = olapAnalysisTask.getMaxFunction();
- Assertions.assertEquals("CAST(MAX(`${colName}`) as ${type}) ",
maxFunction);
+ Assertions.assertEquals("CAST(MAX(${colName}) as ${type}) ",
maxFunction);
olapAnalysisTask.tableSample = new TableSample(true, 20L);
maxFunction = olapAnalysisTask.getMaxFunction();
Assertions.assertEquals("NULL", maxFunction);
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/statistics/analysis/OlapAnalysisTaskTest.java
b/fe/fe-core/src/test/java/org/apache/doris/statistics/analysis/OlapAnalysisTaskTest.java
index c4c387e033e..b6976a5d41f 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/statistics/analysis/OlapAnalysisTaskTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/statistics/analysis/OlapAnalysisTaskTest.java
@@ -39,6 +39,7 @@ import org.apache.doris.common.AnalysisException;
import org.apache.doris.common.FeConstants;
import org.apache.doris.common.Pair;
import org.apache.doris.common.util.DebugPointUtil;
+import org.apache.doris.common.util.SqlUtils;
import org.apache.doris.datasource.CatalogIf;
import org.apache.doris.persist.gson.GsonUtils;
import org.apache.doris.qe.SessionVariable;
@@ -142,6 +143,7 @@ public class OlapAnalysisTaskTest {
Mockito.when(catalogIf.getId()).thenReturn(10001L);
Mockito.when(catalogIf.getName()).thenReturn("catalogName");
Mockito.when(databaseIf.getId()).thenReturn(20001L);
+ Mockito.when(databaseIf.getFullName()).thenReturn("dbName");
OlapAnalysisTask olapAnalysisTask = Mockito.spy(new
OlapAnalysisTask());
Mockito.doReturn(new ResultRow(Lists.newArrayList("1",
"2"))).when(olapAnalysisTask).collectMinMax();
@@ -153,7 +155,7 @@ public class OlapAnalysisTaskTest {
Mockito.doReturn(true).when(olapAnalysisTask).useLinearAnalyzeTemplate();
Mockito.doAnswer(inv -> {
String sql = inv.getArgument(0);
- Assertions.assertEquals("WITH cte1 AS (SELECT `null` FROM
`catalogName`.`${dbName}`.`null` "
+ Assertions.assertEquals("WITH cte1 AS (SELECT `null` FROM
`catalogName`.`dbName`.`null` "
+ "${sampleHints} ${limit} ), cte2 AS (SELECT
CONCAT(30001, '-', -1, '-', 'null') AS `id`, "
+ "10001 AS `catalog_id`, 20001 AS `db_id`, 30001 AS
`tbl_id`, -1 AS `idx_id`, "
+ "'null' AS `col_id`, NULL AS `part_id`, ${rowCount} AS
`row_count`, ${ndvFunction} as `ndv`, "
@@ -185,7 +187,7 @@ public class OlapAnalysisTaskTest {
String sql = inv.getArgument(0);
Assertions.assertEquals("WITH cte1 AS (SELECT MAX(t0.`col_value`)
as `col_value`, COUNT(1) as `count`,"
+ " SUM(`len`) as `column_length` FROM (SELECT
${subStringColName} AS `hash_value`, "
- + "`null` AS `col_value`, LENGTH(`null`) as `len` FROM
`catalogName`.`${dbName}`.`null` "
+ + "`null` AS `col_value`, LENGTH(`null`) as `len` FROM
`catalogName`.`dbName`.`null` "
+ "${sampleHints} ${limit}) as `t0` GROUP BY
`t0`.`hash_value`), "
+ "cte2 AS ( SELECT CONCAT('30001', '-', '-1', '-',
'null') AS `id`, 10001 AS `catalog_id`, "
+ "20001 AS `db_id`, 30001 AS `tbl_id`, -1 AS `idx_id`,
'null' AS `col_id`, NULL AS `part_id`, "
@@ -320,9 +322,9 @@ public class OlapAnalysisTaskTest {
new
OlapAnalysisTask.SampleCollectInfo(AnalyzeSampleAlgorithm.FULL, null));
Assertions.assertEquals("1", params.get("scaleFactor"));
Assertions.assertEquals("", params.get("sampleHints"));
- Assertions.assertEquals("(SELECT COUNT(1) FROM cte1 WHERE `${colName}`
IS NOT NULL)",
+ Assertions.assertEquals("(SELECT COUNT(1) FROM cte1 WHERE ${colName}
IS NOT NULL)",
params.get("rowCount2"));
- Assertions.assertEquals("ROUND(NDV(`${colName}`) * ${scaleFactor})",
params.get("ndvFunction"));
+ Assertions.assertEquals("ROUND(NDV(${colName}) * ${scaleFactor})",
params.get("ndvFunction"));
Assertions.assertNull(params.get("preAggHint"));
Assertions.assertEquals("COUNT(1)", params.get("rowCount"));
params.clear();
@@ -332,9 +334,9 @@ public class OlapAnalysisTaskTest {
new
OlapAnalysisTask.SampleCollectInfo(AnalyzeSampleAlgorithm.LINEAR,
Pair.of(Lists.newArrayList(1L, 2L), 100L)));
Assertions.assertEquals("TABLET(1, 2)", params.get("sampleHints"));
- Assertions.assertEquals("(SELECT COUNT(1) FROM cte1 WHERE `${colName}`
IS NOT NULL)",
+ Assertions.assertEquals("(SELECT COUNT(1) FROM cte1 WHERE ${colName}
IS NOT NULL)",
params.get("rowCount2"));
- Assertions.assertEquals("ROUND(NDV(`${colName}`) * ${scaleFactor})",
params.get("ndvFunction"));
+ Assertions.assertEquals("ROUND(NDV(${colName}) * ${scaleFactor})",
params.get("ndvFunction"));
params.clear();
// DUJ1 algorithm with sample tablets: rowCount2 and ndvFunction must
reference the cte1
@@ -366,7 +368,7 @@ public class OlapAnalysisTaskTest {
Assertions.assertEquals("TABLET(1, 2)", params.get("sampleHints"));
Assertions.assertEquals("SUM(`t1`.`count`) * COUNT(`t1`.`col_value`) /
(SUM(`t1`.`count`) - SUM(IF(`t1`.`count` = 1 and `t1`.`col_value` is not null,
1, 0)) + SUM(IF(`t1`.`count` = 1 and `t1`.`col_value` is not null, 1, 0)) *
SUM(`t1`.`count`) / 1000)", params.get("ndvFunction"));
Assertions.assertEquals("SUM(t1.count) * 4",
params.get("dataSizeFunction"));
- Assertions.assertEquals("`${colName}`",
params.get("subStringColName"));
+ Assertions.assertEquals("${colName}", params.get("subStringColName"));
Assertions.assertEquals("/*+PREAGGOPEN*/", params.get("preAggHint"));
params.clear();
@@ -417,7 +419,7 @@ public class OlapAnalysisTaskTest {
Pair.of(Lists.newArrayList(1L, 2L), 100L)));
Assertions.assertEquals("10.0", params.get("scaleFactor"));
Assertions.assertEquals("TABLET(1, 2)", params.get("sampleHints"));
- Assertions.assertEquals("ROUND(NDV(`${colName}`) * ${scaleFactor})",
params.get("ndvFunction"));
+ Assertions.assertEquals("ROUND(NDV(${colName}) * ${scaleFactor})",
params.get("ndvFunction"));
params.clear();
task = Mockito.spy(new OlapAnalysisTask());
@@ -774,6 +776,43 @@ public class OlapAnalysisTaskTest {
}
}
+ @Test
+ public void testBuildSqlParamsQuotesIdentifiers() {
+ CatalogIf catalog = Mockito.mock(CatalogIf.class);
+ DatabaseIf database = Mockito.mock(DatabaseIf.class);
+ OlapTable table = Mockito.mock(OlapTable.class);
+ Mockito.when(catalog.getId()).thenReturn(1L);
+ Mockito.when(catalog.getName()).thenReturn("cat`alog");
+ Mockito.when(database.getId()).thenReturn(2L);
+ Mockito.when(database.getFullName()).thenReturn("db`name");
+ Mockito.when(table.getId()).thenReturn(3L);
+ Mockito.when(table.getName()).thenReturn("table`name");
+ Mockito.when(table.getIndexNameById(4L)).thenReturn("index`name");
+
+ OlapAnalysisTask task = new OlapAnalysisTask();
+ task.catalog = catalog;
+ task.db = database;
+ task.tbl = table;
+ task.col = new Column("col`'name", PrimitiveType.INT);
+ task.info = new AnalysisInfoBuilder()
+ .setIndexId(4L)
+ .setColName("col`'name")
+ .build();
+
+ Map<String, String> params = task.buildSqlParams();
+ Assertions.assertEquals("`cat``alog`", params.get("catalogName"));
+ Assertions.assertEquals("`db``name`", params.get("dbName"));
+ Assertions.assertEquals("`table``name`", params.get("tblName"));
+ Assertions.assertEquals("`col``'name`", params.get("colName"));
+ Assertions.assertEquals("col`''name", params.get("colId"));
+ Assertions.assertEquals("index `index``name`", params.get("index"));
+ Assertions.assertEquals("partition `part``name`",
task.getPartitionInfo("part`name"));
+
+ String sql = new
StringSubstitutor(params).replace(BaseAnalysisTask.FULL_ANALYZE_WITHOUT_HOT_VALUE_TEMPLATE);
+ Assertions.assertTrue(sql.contains("FROM (SELECT `col``'name` FROM "
+ + "`cat``alog`.`db``name`.`table``name` index `index``name`)
__lc_t"), sql);
+ }
+
@Test
public void testFullAnalyzeTemplateRendersLengthAssert() {
// Confirm the rendered FULL_ANALYZE_TEMPLATE wraps the base table in
a subquery
@@ -787,10 +826,10 @@ public class OlapAnalysisTaskTest {
params.put("idxId", "3");
params.put("colId", "s");
params.put("dataSizeFunction", "100");
- params.put("catalogName", "internal");
- params.put("dbName", "db1");
- params.put("colName", "s");
- params.put("tblName", "tbl1");
+ params.put("catalogName", SqlUtils.getIdentSql("internal"));
+ params.put("dbName", SqlUtils.getIdentSql("db1"));
+ params.put("colName", SqlUtils.getIdentSql("s"));
+ params.put("tblName", SqlUtils.getIdentSql("tbl1"));
params.put("index", "");
params.put("lengthAssert",
", assert_true(`s` IS NULL OR LENGTH(`s`) <= 1024, '"
@@ -814,10 +853,10 @@ public class OlapAnalysisTaskTest {
params.put("idxId", "3");
params.put("colId", "id");
params.put("dataSizeFunction", "100");
- params.put("catalogName", "internal");
- params.put("dbName", "db1");
- params.put("colName", "id");
- params.put("tblName", "tbl1");
+ params.put("catalogName", SqlUtils.getIdentSql("internal"));
+ params.put("dbName", SqlUtils.getIdentSql("db1"));
+ params.put("colName", SqlUtils.getIdentSql("id"));
+ params.put("tblName", SqlUtils.getIdentSql("tbl1"));
params.put("index", "");
params.put("lengthAssert", "");
StringSubstitutor stringSubstitutor = new StringSubstitutor(params);
@@ -836,11 +875,11 @@ public class OlapAnalysisTaskTest {
params.put("tblId", "2");
params.put("idxId", "3");
params.put("colId", "col1");
- params.put("colName", "col1");
+ params.put("colName", SqlUtils.getIdentSql("col1"));
params.put("dataSizeFunction", "SUM(LENGTH(`col1`))");
- params.put("catalogName", "internal");
- params.put("dbName", "db1");
- params.put("tblName", "tbl1");
+ params.put("catalogName", SqlUtils.getIdentSql("internal"));
+ params.put("dbName", SqlUtils.getIdentSql("db1"));
+ params.put("tblName", SqlUtils.getIdentSql("tbl1"));
params.put("index", "");
params.put("hotValueCollectCount", "10");
params.put("subStringColName", "`col1`");
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]