yujun777 commented on code in PR #69890:
URL: https://github.com/apache/doris/pull/69890#discussion_r4510902348


##########
fe/fe-core/src/main/java/org/apache/doris/mtmv/ivm/IvmAggDeltaHandler.java:
##########
@@ -356,9 +367,15 @@ LogicalProject<?> buildApplyPlan(LogicalAggregate<?> 
normalizedAgg,
                         new TinyIntLiteral((byte) -1), new 
TinyIntLiteral((byte) 1));
 
         List<NamedExpression> finalOutputs = new ArrayList<>();
+        Set<String> emittedColumnNames = new HashSet<>();
         // Keep the normalized aggregate schema here. The normalize-added top 
project computes row-id above this
         // project, and the final sink project reorders columns by MV schema.
         for (Slot target : normalizedAgg.getOutput()) {
+            if 
(aggMeta.getInternalAggOutputNames().contains(target.getName())) {

Review Comment:
   Fixed in 1648691ba8aa840be909af805a865cbebf59143d. Normalize now collects 
internal aggregate outputs separately and appends them after all persisted 
aggregate outputs. Skipping COUNT_AT_MIN/MAX therefore trims only the suffix 
and preserves positional alignment of the persisted slots. The focused FE 
normalization/processor tests and all 124 IVM regression suites passed on the 
updated branch.



##########
fe/fe-core/src/main/java/org/apache/doris/catalog/MTMV.java:
##########
@@ -557,17 +560,43 @@ public void alterMvProperties(AlterMTMV alterMTMV, 
boolean isReplay) {
      * backlog was skipped.
      */
     private boolean rebuildsWholeMv(Set<TableNameInfo> 
oldExcludedTriggerTables,
-            Map<TableNameInfo, Integer> oldWindowLimits, Map<String, String> 
oldSyncWindow) {
-        // Judged once here rather than in each of the three: they answer "did 
this property move in the
+            Map<TableNameInfo, Integer> oldWindowLimits, Map<String, String> 
oldSyncWindow,
+            Long oldMinMaxTopK) {
+        // Judged once here rather than in each of the four: they answer "did 
this property move in the
         // direction that owes a rebuild", which is only a question an MV 
maintaining an IVM baseline has.
         if (!maintainsIvmBaseline()) {
             return false;
         }
-        return unexcludesABaseTable(oldExcludedTriggerTables)
+        // The persisted MIN/MAX state was built for the old capacity. A 
larger K needs values the old
+        // state already dropped; a smaller one would keep entries the new 
apply never produces. Either
+        // direction makes the state unreadable, so any change rebuilds the 
whole MV -- but only when
+        // the MV actually keeps that state: an aggregate MV without MIN/MAX, 
or a detail MV, has
+        // nothing for the property to describe.
+        boolean minMaxTopKChanged = !Objects.equals(oldMinMaxTopK,
+                
MTMVPropertyUtil.getIvmMinMaxTopK(this.mvProperties).orElse(null));
+        return (minMaxTopKChanged && keepsMinMaxTopKState())

Review Comment:
   Fixed in 
https://github.com/apache/doris/commit/b153c38b1f7116d79671f11e0363d1b98cafe877.
 Successful COMPLETE refreshes now capture a consistent layout signature across 
batches and publish it with the rebuilt baseline through the existing IvmInfo 
journal path. Partition refreshes do not publish a replacement signature; 
append-only detail MVs remain excluded. Extended topk_1 through ALTER K=2 -> 
K=4, COMPLETE, another insert, and strict INCREMENTAL; the task succeeds 
without fallback and the hidden counts are correct. All 124 IVM suites passed, 
including the snapshot and array/bitmap cases previously cited in the old guard 
comment.



##########
fe/fe-core/src/main/java/org/apache/doris/mtmv/ivm/agg/IvmAggFunctionProcessor.java:
##########
@@ -148,22 +204,43 @@ protected List<Expression> 
targetArguments(AggregateFunction function) {
         return ImmutableList.of(function.child(0));
     }
 
-    /** Aggregate function used to materialize one hidden state during 
normalize. */
-    protected AggregateFunction hiddenStateAggregate(IvmAggStateKey stateKey, 
AggregateFunction function) {
-        switch (stateKey) {
-            case COUNT:
-                return new Count(function.child(0));
-            case SUM:
-                return new Sum(function.child(0));
-            case BITMAP_UNION:
-                return new BitmapUnion(function.child(0));
-            case MIN_COUNT:
-                return new CountAtMin(function.child(0));
-            case MAX_COUNT:
-                return new CountAtMax(function.child(0));
-            default:
-                throw new IllegalArgumentException("Unsupported IVM aggregate 
state: " + stateKey);
-        }
+    /**
+     * Aggregate function used to materialize one hidden aggregate state 
during normalize.
+     *
+     * <p>{@code minMaxTopK} is the MV's resolved MIN/MAX capacity; only the 
MIN_TOPK/MAX_TOPK states
+     * read it, the other states ignore it.
+     */
+    protected AggregateFunction hiddenStateAggregate(IvmAggStateKey stateKey, 
AggregateFunction function,
+            long minMaxTopK) {
+        return switch (stateKey) {
+            case COUNT -> new Count(function.child(0));
+            case SUM -> new Sum(function.child(0));
+            case BITMAP_UNION -> new BitmapUnion(function.child(0));
+            case MIN_TOPK -> new MinTopK(function.child(0), new 
BigIntLiteral(minMaxTopK));

Review Comment:
   Fixed in 1449fef40a81ae4c8193c0d413a42ffe075212f0. Unsupported top-K 
argument types resolve to the K=1 extreme-plus-count path, and apply compares 
extremes with CASE and ordering comparisons instead of scalar-only 
LEAST/GREATEST. test_ivm_agg_minmax_array covers the persisted capacity, 
incremental inserts, and complete fallback after deleting the last minimum. 
That suite passed again on b153c38b1f7.



##########
regression-test/plugins/plugin_planner.groovy:
##########
@@ -149,10 +153,39 @@ def maskIvmDryRunSequence = { row, meta ->
     return row
 }
 
-// Named ivm_dry_run_qt instead of order_qt_*: Suite.invokeMethod intercepts 
every method whose
-// name starts with qt_ / order_qt_ and derives the tag from the method name, 
so a metaClass
-// method could never be reached under those prefixes.
-Suite.metaClass.ivm_dry_run_qt = { String tag, String sql ->
-    delegate.quickRunTest(tag, sql, true, maskIvmDryRunSequence)
+// One entry point for IVM output that carries a hidden sequence column: 
REFRESH ... WITH DRY RUN
+// returns the delta rows, SELECT * on an IVM MV shows the hidden state 
columns (for example the
+// MIN/MAX top-K struct). The sequence value differs between cloud and 
shared-nothing deployments,
+// so the mask makes both modes share one .out; show_hidden_columns is 
harmless to a REFRESH and is
+// what makes the hidden columns reach the SELECT * result.
+// Named ivm_hidden_qt instead of order_qt_*: Suite.invokeMethod intercepts 
every method whose name
+// starts with qt_ / order_qt_ and derives the tag from the method name, so a 
metaClass method could
+// never be reached under those prefixes.
+// Latest finished MV task row for one MV, waiting for a terminal status 
first. Shared by the IVM
+// suites so each one does not carry its own copy; the row carries the columns 
those suites assert
+// on (Status, RefreshMode, IvmFallbackReason, ErrorMsg).
+Suite.metaClass.latestIvmTask = { String mvName ->
+    def suite = delegate
+    def taskResult
+    Awaitility.await().atMost(300, SECONDS).pollInterval(2, SECONDS).until({
+        taskResult = suite.sql_return_maparray("""
+            SELECT Status, RefreshMode, IvmFallbackReason, ErrorMsg FROM 
tasks('type'='mv')
+            WHERE MvDatabaseName = '${suite.context.dbName}' AND MvName = 
'${mvName}'
+            ORDER BY CreateTime DESC, TaskId DESC LIMIT 1
+        """)
+        return !taskResult.isEmpty()
+                && taskResult[0].Status.toString() != 'PENDING'
+                && taskResult[0].Status.toString() != 'RUNNING'

Review Comment:
   Fixed in 
https://github.com/apache/doris/commit/b153c38b1f7116d79671f11e0363d1b98cafe877.
 refreshIvm records the latest TaskId immediately before submitting each 
refresh, and latestIvmTask requires a different TaskId in terminal status. This 
also covers the first helper call after an earlier refresh was waited for 
through another helper, which a cache of previously returned IDs did not cover. 
Updated all existing callers and made the shared cache concurrent. All 124 IVM 
regression suites passed.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/CommonSubExpressionCollector.java:
##########
@@ -80,12 +80,14 @@ private int collectChildrenDepth(List<Expression> children, 
Boolean inLambda) {
 
     private int collectCommonExpressionByDepth(int depth, Expression expr, 
boolean inLambda) {
         Set<Expression> expressions = getExpressionsFromDepthMap(depth, 
expressionsByDepth);
-        // ArrayItemSlot and ArrayItemReference could not be common expressions
-        // TODO: could not extract common expression when expression contains 
same lambda expression
-        //   because ArrayItemSlot in Lambda are not same.
+        // An expression that contains a lambda argument cannot be evaluated 
outside that lambda:
+        // hoisting it into an earlier projection leaves the argument without 
its binding. The
+        // check must not depend on the inLambda flag -- a lambda nested in 
another expression can
+        // be visited again from a context that does not carry the flag (for 
example when the same
+        // subtree appears in two projections), and the extraction then breaks 
the lambda scope.
         if (expressions.contains(expr)
                 && !(expr instanceof WhenClause)
-                && !(inLambda && expr.containsType(ArrayItemSlot.class, 
ArrayItemReference.class))) {
+                && !expr.containsType(ArrayItemSlot.class, 
ArrayItemReference.class)) {

Review Comment:
   Addressed by aedebf639435b4de10ed9d25faa391c4788d1bfb and completed in 
b153c38b1f7. Extraction is scope-aware: a whole ArrayMap whose lambda binds its 
item references remains eligible, while expressions with free array-item 
references are excluded. The existing testLambdaExpression expectation is 
preserved. All 11 CommonSubExpressionTest tests passed, including the added 
nested captured-input case.



-- 
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]

Reply via email to