This is an automated email from the ASF dual-hosted git repository.
Mryange 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 4c63661f60f [fix](fe) Quote dictionary data load identifiers (#67052)
4c63661f60f is described below
commit 4c63661f60f96c694757000640bcebc832e8bb6d
Author: Mryange <[email protected]>
AuthorDate: Fri Nov 6 16:52:10 2026 +0800
[fix](fe) Quote dictionary data load identifiers (#67052)
This change picks #66594 onto master and adapts it to the latest
dictionary loading code. Dictionary refresh previously concatenated raw
catalog, database, table, and dictionary names into an internal `INSERT
... SELECT` statement executed by the background loader. Identifier text
containing SQL syntax could therefore alter the parsed query structure.
The fix quotes and escapes every identifier component with the existing
Nereids helper and adds a parser-level unit test to verify that
hostile-looking names remain a single source relation.
Co-authored-by: Doris-Breakwater <[email protected]>
---
.../apache/doris/dictionary/DictionaryManager.java | 52 +++++++++---------
.../doris/dictionary/DictionaryManagerTest.java | 62 ++++++++++++++++++++++
2 files changed, 90 insertions(+), 24 deletions(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/dictionary/DictionaryManager.java
b/fe/fe-core/src/main/java/org/apache/doris/dictionary/DictionaryManager.java
index b389f4756c1..b8eba0b3174 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/dictionary/DictionaryManager.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/dictionary/DictionaryManager.java
@@ -37,6 +37,7 @@ import org.apache.doris.nereids.parser.NereidsParser;
import org.apache.doris.nereids.trees.plans.commands.info.CreateDictionaryInfo;
import
org.apache.doris.nereids.trees.plans.commands.insert.InsertIntoDictionaryCommand;
import
org.apache.doris.nereids.trees.plans.commands.insert.InsertIntoTableCommand;
+import org.apache.doris.nereids.util.Utils;
import org.apache.doris.persist.CreateDictionaryPersistInfo;
import org.apache.doris.persist.DictionaryDecreaseVersionInfo;
import org.apache.doris.persist.DictionaryIncreaseVersionInfo;
@@ -452,33 +453,30 @@ public class DictionaryManager extends MasterDaemon
implements Writable {
while
(DebugPointUtil.isEnable("DictionaryManager.dataLoad.blockBeforePlan")) {
Thread.sleep(10);
}
+ try {
+ if (ctx == null) { // for run with scheduler, not by command.
+ // priv check is done in relative(caller) command. so use
ADMIN here is ok.
+ ctx = InsertTask.makeConnectContext(UserIdentity.ADMIN,
dictionary.getDbName());
+ }
- if (ctx == null) { // for run with scheduler, not by command.
- // priv check is done in relative(caller) command. so use ADMIN
here is ok.
- ctx = InsertTask.makeConnectContext(UserIdentity.ADMIN,
dictionary.getDbName());
- }
-
- // not use rerfresh command's executor to avoid potential problems.
- String insertSql = "insert into " + dictionary.getDbName() + "." +
dictionary.getName() + " select * from "
- + dictionary.getSourceCtlName() + "." +
dictionary.getSourceDbName() + "."
- + dictionary.getSourceTableName();
- StmtExecutor executor = new StmtExecutor(ctx, insertSql);
- NereidsParser parser = new NereidsParser();
- InsertIntoTableCommand baseCommand = (InsertIntoTableCommand)
parser.parseSingle(insertSql);
- LOG.info("Loading to dictionary {} with query {}. adaptive: {}",
dictionary.getName(), ctx.queryId(),
- adaptiveLoad);
- if (!baseCommand.getLabelName().isPresent()) {
- baseCommand.setLabelName(Optional.of(DICTIONARY_JOB_ID + "_" +
ctx.queryId().toString()));
- }
- if (baseCommand.getJobId() == 0) {
- baseCommand.setJobId(DICTIONARY_JOB_ID);
- }
+ // not use rerfresh command's executor to avoid potential problems.
+ String insertSql = buildDataLoadSql(dictionary);
+ StmtExecutor executor = new StmtExecutor(ctx, insertSql);
+ NereidsParser parser = new NereidsParser();
+ InsertIntoTableCommand baseCommand = (InsertIntoTableCommand)
parser.parseSingle(insertSql);
+ LOG.info("Loading to dictionary {} with query {}. adaptive: {}",
dictionary.getName(), ctx.queryId(),
+ adaptiveLoad);
+ if (!baseCommand.getLabelName().isPresent()) {
+ baseCommand.setLabelName(Optional.of(DICTIONARY_JOB_ID + "_" +
ctx.queryId().toString()));
+ }
+ if (baseCommand.getJobId() == 0) {
+ baseCommand.setJobId(DICTIONARY_JOB_ID);
+ }
- InsertIntoDictionaryCommand command = new InsertIntoDictionaryCommand(
- baseCommand, database, dictionary, adaptiveLoad);
+ InsertIntoDictionaryCommand command = new
InsertIntoDictionaryCommand(
+ baseCommand, database, dictionary, adaptiveLoad);
- // run with sync by status.
- try {
+ // run with sync by status.
// avoid to generate EmptySetNode making us not able to get base
table version.
ctx.getSessionVariable().setVarOnce(SessionVariable.DISABLE_NEREIDS_RULES,
"OLAP_SCAN_PARTITION_PRUNE,PRUNE_EMPTY_PARTITION");
@@ -578,6 +576,12 @@ public class DictionaryManager extends MasterDaemon
implements Writable {
dictionary.getVersion(),
ctx.getStatementContext().getDictionaryUsedSrcVersion());
}
+ static String buildDataLoadSql(Dictionary dictionary) {
+ String targetName =
Utils.qualifiedNameWithBackquote(dictionary.getFullQualifiers());
+ String sourceName =
Utils.qualifiedNameWithBackquote(dictionary.getSourceQualifiedName());
+ return "insert into " + targetName + " select * from " + sourceName;
+ }
+
private boolean commitNowVersion(ConnectContext ctx, Dictionary
dictionary) {
if
(DebugPointUtil.isEnable("DictionaryManager.commitNowVersion.fail")) {
return false;
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/dictionary/DictionaryManagerTest.java
b/fe/fe-core/src/test/java/org/apache/doris/dictionary/DictionaryManagerTest.java
index ae15318a5ac..c0161f0a5de 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/dictionary/DictionaryManagerTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/dictionary/DictionaryManagerTest.java
@@ -17,14 +17,29 @@
package org.apache.doris.dictionary;
+import org.apache.doris.catalog.Database;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.datasource.InternalCatalog;
+import org.apache.doris.nereids.analyzer.UnboundDictionarySink;
+import org.apache.doris.nereids.analyzer.UnboundRelation;
+import org.apache.doris.nereids.analyzer.UnboundTableSink;
+import org.apache.doris.nereids.parser.NereidsParser;
+import
org.apache.doris.nereids.trees.plans.commands.insert.InsertIntoDictionaryCommand;
+import
org.apache.doris.nereids.trees.plans.commands.insert.InsertIntoTableCommand;
+import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
import org.apache.doris.persist.CreateDictionaryPersistInfo;
import org.apache.doris.persist.DictionaryDecreaseVersionInfo;
import org.apache.doris.persist.DictionaryIncreaseVersionInfo;
import org.apache.doris.persist.DropDictionaryPersistInfo;
import org.apache.doris.persist.gson.GsonUtils;
+import org.apache.doris.qe.ConnectContext;
+import com.google.common.collect.ImmutableList;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import java.util.List;
/**
* Tests for dictionary version journal replay robustness.
@@ -109,4 +124,51 @@ public class DictionaryManagerTest {
manager.replayIncreaseVersion(new DictionaryIncreaseVersionInfo(dict));
Assertions.assertEquals(2, manager.getDictionary(1001).getVersion());
}
+
+ @Test
+ public void testBuildDataLoadSqlQuotesIdentifierParts() {
+ List<String> targetName = ImmutableList.of("target.with` db",
"dict.with` union all select 1");
+ List<String> targetFullName = ImmutableList.of("internal",
targetName.get(0), targetName.get(1));
+ List<String> sourceName = ImmutableList.of(
+ "internal` catalog", "source` db", "src` union all select k, v
from secret.tbl");
+ Dictionary dictionary = Mockito.mock(Dictionary.class);
+ Mockito.when(dictionary.getDbName()).thenReturn(targetName.get(0));
+ Mockito.when(dictionary.getName()).thenReturn(targetName.get(1));
+
Mockito.when(dictionary.getFullQualifiers()).thenReturn(targetFullName);
+
Mockito.when(dictionary.getSourceQualifiedName()).thenReturn(sourceName);
+
Mockito.when(dictionary.getColumnNames()).thenReturn(ImmutableList.of("k",
"v"));
+
+ String insertSql = DictionaryManager.buildDataLoadSql(dictionary);
+
+ Assertions.assertEquals("insert into `internal`.`target.with``
db`.`dict.with`` union all select 1` "
+ + "select * from `internal`` catalog`.`source`` db`."
+ + "`src`` union all select k, v from secret.tbl`", insertSql);
+ ConnectContext context = new ConnectContext();
+ context.setEnv(Env.getCurrentEnv());
+ context.changeDefaultCatalog("external_catalog");
+ context.setThreadLocalInfo();
+ try {
+ InsertIntoTableCommand command = (InsertIntoTableCommand) new
NereidsParser().parseSingle(insertSql);
+ LogicalPlan query = command.getLogicalQuery();
+ Assertions.assertTrue(query instanceof UnboundTableSink);
+ Assertions.assertEquals(targetFullName, ((UnboundTableSink<?>)
query).getNameParts());
+
+ Database database = Mockito.mock(Database.class);
+ InternalCatalog catalog = Mockito.mock(InternalCatalog.class);
+ Mockito.when(database.getCatalog()).thenReturn(catalog);
+ Mockito.when(catalog.getName()).thenReturn(targetFullName.get(0));
+
Mockito.when(database.getFullName()).thenReturn(targetFullName.get(1));
+ InsertIntoDictionaryCommand dictionaryCommand = new
InsertIntoDictionaryCommand(
+ command, database, dictionary, false);
+ LogicalPlan dictionaryQuery = dictionaryCommand.getLogicalQuery();
+ Assertions.assertTrue(dictionaryQuery instanceof
UnboundDictionarySink);
+ Assertions.assertEquals(targetFullName,
((UnboundDictionarySink<?>) dictionaryQuery).getNameParts());
+
+ List<UnboundRelation> sourceRelations =
dictionaryQuery.collectToList(UnboundRelation.class::isInstance);
+ Assertions.assertEquals(1, sourceRelations.size());
+ Assertions.assertEquals(sourceName,
sourceRelations.get(0).getNameParts());
+ } finally {
+ ConnectContext.remove();
+ }
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]