morrySnow commented on code in PR #65569:
URL: https://github.com/apache/doris/pull/65569#discussion_r3602094277
##########
fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/TopnFilterPushDownVisitor.java:
##########
@@ -263,7 +263,6 @@ public Boolean
visitPhysicalLazyMaterializeOlapScan(PhysicalLazyMaterializeOlapS
private boolean supportPhysicalRelations(PhysicalRelation relation) {
Review Comment:
PhysicalLazyMaterializeOlapScan was removed from supportPhysicalRelations().
This is correct because the new PlanVisitor now delegates
visitPhysicalLazyMaterializeOlapScan to visitPhysicalOlapScan, so the OlapScan
instanceof check covers it implicitly.
However, this dependency is non-obvious. A brief comment explaining why
PhysicalLazyMaterializeOlapScan no longer needs to be listed here would help
future maintainers.
##########
fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/materialize/MaterializeSource.java:
##########
@@ -20,18 +20,46 @@
import org.apache.doris.nereids.trees.expressions.SlotReference;
import org.apache.doris.nereids.trees.plans.algebra.Relation;
-/**
- the table and slot used to do lazy materialize
- */
-public class MaterializeSource {
- public final Relation relation;
- public final SlotReference baseSlot;
-
- /*
- constructor
- */
+import java.util.Objects;
+
+/** Resolved physical source for one lazy output. */
+public final class MaterializeSource {
+ private final Relation relation;
Review Comment:
The relation and baseSlot fields changed from public final to private final
with getter methods. While this is a style improvement (better encapsulation),
it is a binary-incompatible API change. If any external code outside this PR
scope directly accesses .relation or .baseSlot on MaterializeSource objects, it
will fail to compile. Please verify there are no such references in other
PlanPostProcessor implementations or test utilities.
##########
fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/materialize/TopNLazyMaterializationAnalyzer.java:
##########
@@ -0,0 +1,155 @@
+// 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.processor.post.materialize;
+
+import org.apache.doris.catalog.KeysType;
+import org.apache.doris.catalog.OlapTable;
+import
org.apache.doris.nereids.processor.post.materialize.MaterializationAnalysisResult.NotApplicableReason;
+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.physical.PhysicalCatalogRelation;
+import org.apache.doris.nereids.trees.plans.physical.PhysicalTopN;
+
+import com.google.common.collect.ImmutableSet;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/** Orchestrates one lineage pass and one demand pass for a candidate physical
TopN. */
+public final class TopNLazyMaterializationAnalyzer {
+ private final SlotLineageAnalyzer lineageAnalyzer = new
SlotLineageAnalyzer();
+ private final SlotDemandAnalyzer demandAnalyzer = new SlotDemandAnalyzer();
+
+ /** Analyze one physical TopN and return either an immutable decision or a
structured skip reason. */
+ public MaterializationAnalysisResult<TopNLazyAnalysis> analyze(
+ PhysicalTopN<? extends Plan> topN, List<Slot> effectiveOutput) {
+ Map<Slot, MaterializeSource> candidates = new LinkedHashMap<>();
+ List<Slot> materializedSlots = new ArrayList<>();
+ Set<Slot> slotsToResolve = new LinkedHashSet<>(effectiveOutput);
+ collectInputSlots(topN, slotsToResolve);
+ Map<Slot, MaterializationAnalysisResult<OutputLineage>> lineages =
+ lineageAnalyzer.analyze(topN.child(),
ImmutableSet.copyOf(slotsToResolve).asList());
+
+ for (Slot outputSlot : effectiveOutput) {
+ if (!(outputSlot instanceof SlotReference)) {
+ materializedSlots.add(outputSlot);
+ continue;
+ }
+ MaterializationAnalysisResult<OutputLineage> lineageResult =
lineages.get(outputSlot);
+ if (!lineageResult.isApplicable() ||
!lineageResult.getValue().getSource().isPresent()) {
+ materializedSlots.add(outputSlot);
+ continue;
+ }
+ candidates.put(outputSlot,
lineageResult.getValue().getSource().get());
+ }
+
+ Set<Slot> requiredEagerSlots = new LinkedHashSet<>(
+ demandAnalyzer.analyzeRequiredEagerSlots(topN));
+ collectRequiredRelationSlots(topN, requiredEagerSlots);
+ Set<SourceColumnKey> eagerSourceColumns = new LinkedHashSet<>();
+ Set<Slot> eagerSlots = new LinkedHashSet<>(requiredEagerSlots);
+ eagerSlots.addAll(materializedSlots);
+ for (Slot eagerSlot : eagerSlots) {
+ MaterializationAnalysisResult<OutputLineage> lineage =
lineages.get(eagerSlot);
+ if (lineage != null && lineage.isApplicable() &&
lineage.getValue().getSource().isPresent()) {
+
eagerSourceColumns.add(lineage.getValue().getSource().get().getSourceColumnKey());
+ }
+ }
+ removeConflictingCandidates(candidates, requiredEagerSlots,
+ ImmutableSet.copyOf(materializedSlots), eagerSourceColumns);
+ for (Slot outputSlot : effectiveOutput) {
+ if (!candidates.containsKey(outputSlot) &&
!materializedSlots.contains(outputSlot)) {
+ materializedSlots.add(outputSlot);
+ }
+ }
+ if (candidates.isEmpty()) {
+ return MaterializationAnalysisResult.notApplicable(
+ NotApplicableReason.NO_DEFERRED_OUTPUT, topN.shapeInfo());
+ }
+
+ return MaterializationAnalysisResult.applicable(
+ new TopNLazyAnalysis(materializedSlots, candidates));
+ }
+
+ static void removeConflictingCandidates(Map<Slot, MaterializeSource>
candidates,
+ Set<Slot> requiredEagerSlots, Set<Slot> materializedSlots) {
+ removeConflictingCandidates(candidates, requiredEagerSlots,
materializedSlots, new LinkedHashSet<>());
+ }
+
+ private static void removeConflictingCandidates(Map<Slot,
MaterializeSource> candidates,
+ Set<Slot> requiredEagerSlots, Set<Slot> materializedSlots,
+ Set<SourceColumnKey> eagerSourceColumns) {
+ // Fetch is scheduled per physical source column. If any alias of a
column is needed eagerly,
+ // every output backed by the same column must remain eager as well.
+ for (Map.Entry<Slot, MaterializeSource> entry : candidates.entrySet())
{
+ if (requiredEagerSlots.contains(entry.getKey())
+ ||
requiredEagerSlots.contains(entry.getValue().getBaseSlot())
+ || materializedSlots.contains(entry.getKey())
+ ||
materializedSlots.contains(entry.getValue().getBaseSlot())) {
+ eagerSourceColumns.add(entry.getValue().getSourceColumnKey());
+ }
+ }
+ candidates.entrySet().removeIf(
+ entry ->
eagerSourceColumns.contains(entry.getValue().getSourceColumnKey()));
+ }
+
+ private void collectInputSlots(Plan plan, Set<Slot> slots) {
+ slots.addAll(plan.getInputSlots());
+ for (Plan child : plan.children()) {
+ collectInputSlots(child, slots);
+ }
+ }
+
+ private void collectRequiredRelationSlots(Plan plan, Set<Slot>
requiredSlots) {
+ if (plan instanceof PhysicalCatalogRelation) {
+ collectRequiredRelationSlots((PhysicalCatalogRelation) plan,
requiredSlots);
+ }
+ for (Plan child : plan.children()) {
+ collectRequiredRelationSlots(child, requiredSlots);
+ }
+ }
+
+ private void collectRequiredRelationSlots(PhysicalCatalogRelation
relation, Set<Slot> requiredSlots) {
+ if (relation.getTable() instanceof OlapTable) {
+ OlapTable table = (OlapTable) relation.getTable();
+ if (KeysType.UNIQUE_KEYS.equals(table.getKeysType())
Review Comment:
The condition mixing && and || operators is correct due to Java operator
precedence (&& binds tighter than ||), but is easy to misread:
```java
if (KeysType.UNIQUE_KEYS.equals(table.getKeysType())
&& !table.getTableProperty().getEnableUniqueKeyMergeOnWrite()
|| KeysType.AGG_KEYS.equals(table.getKeysType())
|| KeysType.PRIMARY_KEYS.equals(table.getKeysType()))
```
This evaluates as: (UNIQUE_KEYS && !MoW) || AGG_KEYS || PRIMARY_KEYS.
Consider adding explicit parentheses to make the intent immediately clear,
since this logic was moved from its original location.
##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/physical/PhysicalLazyMaterializeTVFScan.java:
##########
@@ -32,41 +32,42 @@
import java.util.List;
import java.util.Optional;
-/**
- wrapper for PhysicalTVFRelation used for lazy materialization
- */
+/** TVF scan wrapper that replaces deferred columns with a row-id used by the
materialize node. */
public class PhysicalLazyMaterializeTVFScan extends PhysicalTVFRelation {
- private PhysicalTVFRelation scan;
- private SlotReference rowId;
+ private final PhysicalTVFRelation scan;
+ private final SlotReference rowId;
private final List<Slot> lazySlots;
- private List<Slot> output;
- /**
- * PhysicalLazyMaterializeTVFScan
- */
public PhysicalLazyMaterializeTVFScan(PhysicalTVFRelation scan,
SlotReference rowId, List<Slot> lazySlots) {
- super(scan.getRelationId(), scan.getFunction(),
scan.getOperativeSlots(), scan.getLogicalProperties());
+ this(scan, rowId, lazySlots, scan.getGroupExpression(), null,
+ scan.getPhysicalProperties(), scan.getStats());
+ }
+
+ private PhysicalLazyMaterializeTVFScan(PhysicalTVFRelation scan,
SlotReference rowId, List<Slot> lazySlots,
+ Optional<GroupExpression> groupExpression, LogicalProperties
logicalProperties,
+ PhysicalProperties physicalProperties, Statistics statistics) {
+ super(scan.getRelationId(), scan.getFunction(),
scan.getOperativeSlots(),
+ groupExpression, logicalProperties, physicalProperties,
statistics);
this.scan = scan;
this.rowId = rowId;
- this.lazySlots = lazySlots;
+ this.lazySlots = ImmutableList.copyOf(lazySlots);
}
@Override
public List<Slot> computeOutput() {
Review Comment:
The old code cached computeOutput() with an `output == null` guard. The new
code recomputes the output on every call. While the result is stable (derived
from scan.getOutput() minus lazy slots plus row ID), every getOutput() or
getOutputSet() call in the rewriter loop will re-allocate the output list.
For wide TVF scans with many lazy slots, this could add measurable overhead.
Consider restoring the cached output pattern, or ensuring callers cache the
result locally.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]