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]

Reply via email to