This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.2 by this push:
new a5d2661db5c branch-4.2: [fix](fe) Preserve Hudi scan state when
assigning table aliases (#69387)
a5d2661db5c is described below
commit a5d2661db5c794b7ba3af434af8fc4c7e3409732
Author: 924060929 <[email protected]>
AuthorDate: Thu Oct 22 11:22:57 2026 +0800
branch-4.2: [fix](fe) Preserve Hudi scan state when assigning table aliases
(#69387)
### What problem does this PR solve?
Regression introduced by #68213 (`branch-4.2 [feat](nereids) add table
alias for catalog relation`, commit `ee3f10d42156`). That PR added a
call to `LogicalCatalogRelation.withTableAlias()` in
`LogicalSubQueryAliasToLogicalProject`. `LogicalHudiScan` did not
override the new method, so it inherited the implementation that
constructs a `LogicalFileScan`, losing its Hudi-specific scan type and
incremental read state.
Queries that assign an alias to a Hudi table fail during physical plan
translation. The alias elimination rule calls `withTableAlias`, but
`LogicalHudiScan` inherits the implementation that creates a generic
`LogicalFileScan`, dropping the dedicated Hudi scan type and incremental
read state.
Override `withTableAlias` in `LogicalHudiScan` and retain the alias
through all subsequent copies. This preserves the Hudi implementation
path, partition selection, and relation-scoped read state.
This fix targets branch-4.2 directly because master removed the
dedicated Hudi scan classes in #64304. Branch-4.1 does not contain the
alias propagation change and is unaffected by this bug.
### Release note
Fix query planning failures when Hudi tables are referenced with table
aliases.
### Check List (For Author)
- Test: Added two FE unit tests. Both fail on the original
implementation with `LogicalFileScan` instead of `LogicalHudiScan`;
after the fix, all 12 tests in `LogicalHudiScanTest`,
`LogicalFileScanTest`, and `PruneFileScanPartitionTest` pass using JDK
17 and `run-fe-ut.sh`. FE Checkstyle passes with zero violations. The
external Hudi end-to-end regression suite was not run locally.
- Behavior changed: Yes, aliased Hudi scans retain their scan type and
read state instead of failing plan translation.
- Does this need documentation: No
---
.../trees/plans/logical/LogicalHudiScan.java | 40 +++++--
.../trees/plans/logical/LogicalHudiScanTest.java | 117 +++++++++++++++++++++
2 files changed, 149 insertions(+), 8 deletions(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalHudiScan.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalHudiScan.java
index c66e01e1587..4aa42aac96b 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalHudiScan.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalHudiScan.java
@@ -98,9 +98,25 @@ public class LogicalHudiScan extends LogicalFileScan {
Optional<List<Slot>> cachedOutputs,
Optional<List<Column>> relationSchema,
Optional<MvccSnapshot> relationSnapshot) {
+ this(id, table, qualifier, selectedPartitions, tableSample,
tableSnapshot, scanParams, incrementalRelation,
+ operativeSlots, virtualColumns, groupExpression,
logicalProperties, cachedOutputs,
+ relationSchema, relationSnapshot, "");
+ }
+
+ private LogicalHudiScan(RelationId id, ExternalTable table, List<String>
qualifier,
+ SelectedPartitions selectedPartitions, Optional<TableSample>
tableSample,
+ Optional<TableSnapshot> tableSnapshot,
+ Optional<TableScanParams> scanParams,
Optional<IncrementalRelation> incrementalRelation,
+ Collection<Slot> operativeSlots,
+ List<NamedExpression> virtualColumns,
+ Optional<GroupExpression> groupExpression,
+ Optional<LogicalProperties> logicalProperties,
+ Optional<List<Slot>> cachedOutputs,
+ Optional<List<Column>> relationSchema,
+ Optional<MvccSnapshot> relationSnapshot, String tableAlias) {
super(id, table, qualifier, selectedPartitions, operativeSlots,
virtualColumns,
tableSample, tableSnapshot, scanParams, groupExpression,
logicalProperties, cachedOutputs,
- relationSchema, relationSnapshot);
+ relationSchema, relationSnapshot, tableAlias);
Objects.requireNonNull(scanParams, "scanParams should not null");
Objects.requireNonNull(incrementalRelation, "incrementalRelation
should not null");
this.incrementalRelation = incrementalRelation;
@@ -186,7 +202,7 @@ public class LogicalHudiScan extends LogicalFileScan {
return new LogicalHudiScan(relationId, (ExternalTable) table,
qualifier,
selectedPartitions, tableSample, tableSnapshot, scanParams,
incrementalRelation,
operativeSlots, virtualColumns, groupExpression,
Optional.of(getLogicalProperties()),
- cachedOutputs, relationSchema, relationSnapshot);
+ cachedOutputs, relationSchema, relationSnapshot, tableAlias);
}
@Override
@@ -195,14 +211,14 @@ public class LogicalHudiScan extends LogicalFileScan {
return new LogicalHudiScan(relationId, (ExternalTable) table,
qualifier,
selectedPartitions, tableSample, tableSnapshot, scanParams,
incrementalRelation,
operativeSlots, virtualColumns, groupExpression,
logicalProperties,
- cachedOutputs, relationSchema, relationSnapshot);
+ cachedOutputs, relationSchema, relationSnapshot, tableAlias);
}
public LogicalHudiScan withSelectedPartitions(SelectedPartitions
selectedPartitions) {
return new LogicalHudiScan(relationId, (ExternalTable) table,
qualifier,
selectedPartitions, tableSample, tableSnapshot, scanParams,
incrementalRelation,
operativeSlots, virtualColumns, groupExpression,
Optional.of(getLogicalProperties()),
- cachedOutputs, relationSchema, relationSnapshot);
+ cachedOutputs, relationSchema, relationSnapshot, tableAlias);
}
@Override
@@ -210,7 +226,15 @@ public class LogicalHudiScan extends LogicalFileScan {
return new LogicalHudiScan(relationId, (ExternalTable) table,
qualifier,
selectedPartitions, tableSample, tableSnapshot, scanParams,
incrementalRelation,
operativeSlots, virtualColumns, groupExpression,
Optional.empty(),
- cachedOutputs, relationSchema, relationSnapshot);
+ cachedOutputs, relationSchema, relationSnapshot, tableAlias);
+ }
+
+ @Override
+ public LogicalHudiScan withTableAlias(String tableAlias) {
+ return new LogicalHudiScan(relationId, (ExternalTable) table,
qualifier,
+ selectedPartitions, tableSample, tableSnapshot, scanParams,
incrementalRelation,
+ operativeSlots, virtualColumns, Optional.empty(),
Optional.of(getLogicalProperties()),
+ cachedOutputs, relationSchema, relationSnapshot, tableAlias);
}
@Override
@@ -223,7 +247,7 @@ public class LogicalHudiScan extends LogicalFileScan {
return new LogicalHudiScan(relationId, (ExternalTable) table,
qualifier,
selectedPartitions, tableSample, tableSnapshot, scanParams,
incrementalRelation,
operativeSlots, virtualColumns, groupExpression,
Optional.of(getLogicalProperties()),
- cachedOutputs, relationSchema, relationSnapshot);
+ cachedOutputs, relationSchema, relationSnapshot, tableAlias);
}
@Override
@@ -231,7 +255,7 @@ public class LogicalHudiScan extends LogicalFileScan {
return new LogicalHudiScan(relationId, (ExternalTable) table,
qualifier,
selectedPartitions, tableSample, tableSnapshot, scanParams,
incrementalRelation,
operativeSlots, virtualColumns, groupExpression,
Optional.empty(),
- Optional.of(cachedOutputs), relationSchema, relationSnapshot);
+ Optional.of(cachedOutputs), relationSchema, relationSnapshot,
tableAlias);
}
/**
@@ -285,6 +309,6 @@ public class LogicalHudiScan extends LogicalFileScan {
return new LogicalHudiScan(relationId, (ExternalTable) table,
qualifier,
selectedPartitions, tableSample, tableSnapshot, scanParams,
newIncrementalRelation,
operativeSlots, virtualColumns, groupExpression,
Optional.of(getLogicalProperties()),
- cachedOutputs, relationSchema, relationSnapshot);
+ cachedOutputs, relationSchema, relationSnapshot, tableAlias);
}
}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/logical/LogicalHudiScanTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/logical/LogicalHudiScanTest.java
new file mode 100644
index 00000000000..11dd70988d4
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/logical/LogicalHudiScanTest.java
@@ -0,0 +1,117 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.nereids.trees.plans.logical;
+
+import org.apache.doris.analysis.TableScanParams;
+import org.apache.doris.analysis.TableSnapshot;
+import org.apache.doris.catalog.Column;
+import org.apache.doris.catalog.Type;
+import org.apache.doris.datasource.hive.HMSExternalTable;
+import org.apache.doris.datasource.hudi.source.IncrementalRelation;
+import org.apache.doris.datasource.mvcc.MvccSnapshot;
+import org.apache.doris.nereids.CascadesContext;
+import
org.apache.doris.nereids.rules.analysis.LogicalSubQueryAliasToLogicalProject;
+import
org.apache.doris.nereids.rules.implementation.LogicalHudiScanToPhysicalHudiScan;
+import org.apache.doris.nereids.trees.TableSample;
+import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.RelationId;
+import org.apache.doris.nereids.trees.plans.physical.PhysicalHudiScan;
+import org.apache.doris.nereids.types.IntegerType;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+class LogicalHudiScanTest {
+ @Test
+ void testAliasRewritePreservesHudiScanAndIncrementalState() {
+ LogicalHudiScan scan = newScan();
+ CascadesContext context = Mockito.mock(CascadesContext.class);
+ Plan rewritten = new LogicalSubQueryAliasToLogicalProject().build()
+ .transform(new LogicalSubQueryAlias<>("t1", scan),
context).get(0);
+ LogicalHudiScan aliased =
Assertions.assertInstanceOf(LogicalHudiScan.class, rewritten.child(0));
+ assertScanState(scan, aliased);
+ Assertions.assertEquals("t1", aliased.getTableAlias());
+
+ Plan physical = new
LogicalHudiScanToPhysicalHudiScan().build().transform(aliased, context).get(0);
+ PhysicalHudiScan hudiScan =
Assertions.assertInstanceOf(PhysicalHudiScan.class, physical);
+ Assertions.assertSame(scan.getIncrementalRelation(),
hudiScan.getIncrementalRelation());
+ Assertions.assertSame(scan.getRelationSnapshot(),
hudiScan.getRelationSnapshot());
+ Assertions.assertSame(scan.getSelectedPartitions(),
hudiScan.getSelectedPartitions());
+ }
+
+ @Test
+ void testCopiesPreserveHudiAliasAndScanState() {
+ LogicalHudiScan scan = newScan();
+ LogicalHudiScan aliased =
Assertions.assertInstanceOf(LogicalHudiScan.class, scan.withTableAlias("t1"));
+ List<Plan> copies = Arrays.asList(
+ aliased.withGroupExpression(Optional.empty()),
+ aliased.withGroupExprLogicalPropChildren(Optional.empty(),
+ Optional.of(aliased.getLogicalProperties()),
Collections.emptyList()),
+
aliased.withSelectedPartitions(aliased.getSelectedPartitions()),
+ aliased.withRelationId(new RelationId(2)),
+ aliased.withOperativeSlots(aliased.getOperativeSlots()),
+ aliased.withCachedOutput(aliased.getOutput()),
+ aliased.withScanParams((HMSExternalTable) aliased.getTable(),
Optional.empty()));
+ for (Plan copy : copies) {
+ LogicalHudiScan hudiScan =
Assertions.assertInstanceOf(LogicalHudiScan.class, copy);
+ Assertions.assertEquals("t1", hudiScan.getTableAlias());
+ Assertions.assertSame(scan.getRelationSnapshot(),
hudiScan.getRelationSnapshot());
+ Assertions.assertSame(scan.getSelectedPartitions(),
hudiScan.getSelectedPartitions());
+ Assertions.assertEquals(scan.getOutput(), hudiScan.getOutput());
+ }
+ assertScanState(scan, aliased);
+ Assertions.assertEquals("", scan.getTableAlias());
+ }
+
+ private LogicalHudiScan newScan() {
+ HMSExternalTable table = Mockito.mock(HMSExternalTable.class);
+ Mockito.when(table.getName()).thenReturn("hudi_table");
+ Slot slot = new SlotReference("id", IntegerType.INSTANCE);
+ List<Slot> output = Collections.singletonList(slot);
+ return new LogicalHudiScan(new RelationId(1), table,
Collections.singletonList("db"),
+ new LogicalFileScan.SelectedPartitions(1,
Collections.emptyMap(), true, true),
+ Optional.of(Mockito.mock(TableSample.class)),
Optional.of(Mockito.mock(TableSnapshot.class)),
+ Optional.of(Mockito.mock(TableScanParams.class)),
Optional.of(Mockito.mock(IncrementalRelation.class)),
+ output, Collections.emptyList(), Optional.empty(),
Optional.empty(), Optional.of(output),
+ Optional.of(Collections.singletonList(new Column("id",
Type.INT))),
+ Optional.of(Mockito.mock(MvccSnapshot.class)));
+ }
+
+ private void assertScanState(LogicalHudiScan expected, LogicalHudiScan
actual) {
+ Assertions.assertSame(expected.getTable(), actual.getTable());
+ Assertions.assertEquals(expected.getRelationId(),
actual.getRelationId());
+ Assertions.assertEquals(expected.getQualifier(),
actual.getQualifier());
+ Assertions.assertSame(expected.getSelectedPartitions(),
actual.getSelectedPartitions());
+ Assertions.assertSame(expected.getTableSample(),
actual.getTableSample());
+ Assertions.assertSame(expected.getTableSnapshot(),
actual.getTableSnapshot());
+ Assertions.assertSame(expected.getScanParams(),
actual.getScanParams());
+ Assertions.assertSame(expected.getIncrementalRelation(),
actual.getIncrementalRelation());
+ Assertions.assertSame(expected.getRelationSnapshot(),
actual.getRelationSnapshot());
+ Assertions.assertSame(expected.relationSchema, actual.relationSchema);
+ Assertions.assertEquals(expected.getOperativeSlots(),
actual.getOperativeSlots());
+ Assertions.assertEquals(expected.getOutput(), actual.getOutput());
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]