This is an automated email from the ASF dual-hosted git repository.

yujun777 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 22c95eb8d5c [fix](ivm) Carry the row-binlog hidden columns in the 
analyzed MTMV schema (#67853)
22c95eb8d5c is described below

commit 22c95eb8d5cea9fb36a767222d726e987d1004ce
Author: yujun <[email protected]>
AuthorDate: Fri Sep 11 22:53:01 2026 +0800

    [fix](ivm) Carry the row-binlog hidden columns in the analyzed MTMV schema 
(#67853)
    
    An MTMV re-validates its schema against a fresh analysis of its own
    query whenever a base table changes
    (`MTMVPlanUtil.ensureMTMVQueryUsable` -> `checkColumnIfChange`). A table
    created with row binlog carries hidden columns that the query itself
    never produces (`__DORIS_COMMIT_TSO_COL__`, plus `__DORIS_ROW_LSN_COL__`
    for duplicate keys), and an MV ends up with them in its physical schema
    when its own properties enable row binlog.
    
    The analyzed column list did not contain them, so for those MVs the
    comparison was permanently short by a column and every refresh failed
    with `column length not equals, please check whether columns of base
    table have changed` -- including `COMPLETE`, which is the only way out
    of a stale baseline. The MV then became unrecoverable without dropping
    and recreating it.
    
    Cascade IVM always hits this, because a cascade source has to keep row
    binlog for its downstream level. Reported shape and trace issue:
    https://github.com/apache/doris/issues/65418
    
    The root cause is that the hidden-column rules were split across two
    classes: `CreateTableInfo` owns the OLAP ones (delete sign / row store /
    version / skip bitmap), while the row-binlog ones are added by
    `InternalCatalog#createOlapTable` right before the table is built. The
    analyzed schema path only mirrored the first half.
    
    ### What changed
    
    - Extracted the row-binlog hidden-column rule into
    `CreateTableInfo.addRowBinlogHiddenColumns`, and let
    `createRowBinlogHiddenColumnsIfNecessary` delegate to it. The helper is
    idempotent.
    - Applied the same rule when the analyzed MTMV column list is built, so
    that list matches the physical schema of the table it produces. The
    binlog config is derived from the properties passed in: for a refresh
    those are the MV table's own properties, where `InternalCatalog` already
    stores the resolved (database-merged) binlog config.
    - The physical schema is unchanged: both paths append the same columns
    at the same position, so a table built from the analyzed list still ends
    up with exactly one of each. `checkColumnIfChange` compares types index
    by index, so the fix also pins the column order, not just the count.
---
 .../java/org/apache/doris/mtmv/MTMVPlanUtil.java   |   9 +
 .../trees/plans/commands/info/CreateTableInfo.java |  28 ++-
 .../org/apache/doris/mtmv/MTMVPlanUtilTest.java    |  64 +++++++
 .../ivm/test_ivm_row_binlog_schema_validation.out  |  27 +++
 .../test_ivm_row_binlog_schema_validation.groovy   | 194 +++++++++++++++++++++
 5 files changed, 318 insertions(+), 4 deletions(-)

diff --git a/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVPlanUtil.java 
b/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVPlanUtil.java
index 4d708208c5b..11f50909ccc 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVPlanUtil.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVPlanUtil.java
@@ -23,6 +23,7 @@ import org.apache.doris.analysis.StatementBase;
 import org.apache.doris.analysis.ToSqlParams;
 import org.apache.doris.analysis.UserIdentity;
 import org.apache.doris.catalog.AggregateType;
+import org.apache.doris.catalog.BinlogConfig;
 import org.apache.doris.catalog.Column;
 import org.apache.doris.catalog.DatabaseIf;
 import org.apache.doris.catalog.DistributionInfo;
@@ -625,6 +626,14 @@ public class MTMVPlanUtil {
             properties = CreateTableInfo.addOlapHiddenColumns(
                     columns, isIvm ? KeysType.UNIQUE_KEYS : KeysType.DUP_KEYS,
                     isIvm, properties, false);
+            // A row-binlog table carries hidden columns on top of the OLAP 
ones above, added by
+            // InternalCatalog#createOlapTable just before the table is built. 
The analyzed list has
+            // to carry them too: an MTMV re-validates its schema against it 
whenever a base table
+            // changes (MTMVPlanUtil#checkColumnIfChange), and a column 
missing here is
+            // indistinguishable from a real schema change. Idempotent, so the 
table still gets one.
+            CreateTableInfo.addRowBinlogHiddenColumns(columns,
+                    isIvm ? KeysType.UNIQUE_KEYS : KeysType.DUP_KEYS, isIvm,
+                    BinlogConfig.fromProperties(properties));
             // analyze column
             final boolean finalEnableMergeOnWrite = isIvm;
             Set<String> keysSet = 
Sets.newTreeSet(String.CASE_INSENSITIVE_ORDER);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/CreateTableInfo.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/CreateTableInfo.java
index 69357d6f39a..6c5e728e84f 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/CreateTableInfo.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/CreateTableInfo.java
@@ -1646,14 +1646,34 @@ public class CreateTableInfo {
      * Add hidden columns required by row binlog.
      */
     public void createRowBinlogHiddenColumnsIfNecessary(BinlogConfig 
binlogConfig) {
-        if (!binlogConfig.isRowFormat()) {
+        addRowBinlogHiddenColumns(columns, keysType, isEnableMergeOnWrite, 
binlogConfig);
+    }
+
+    /**
+     * Append the hidden columns a row-binlog table carries. Callers that 
build the column list
+     * outside the create-table flow (an analyzed MTMV schema) go through here 
as well, so both
+     * sides of {@code MTMVPlanUtil#checkColumnIfChange} agree on the physical 
layout.
+     *
+     * <p>Idempotent: a column that is already present is kept once.
+     */
+    public static void addRowBinlogHiddenColumns(List<ColumnDefinition> 
columns, KeysType keysType,
+            boolean isEnableMergeOnWrite, BinlogConfig binlogConfig) {
+        if (binlogConfig == null || !binlogConfig.isRowFormat()) {
             return;
         }
         if (keysType.equals(KeysType.DUP_KEYS)) {
-            
columns.add(ColumnDefinition.newCommitTsoColumnDefinition(AggregateType.NONE));
-            
columns.add(ColumnDefinition.newRowLsnColumnDefinition(AggregateType.NONE));
+            addIfAbsent(columns, 
ColumnDefinition.newCommitTsoColumnDefinition(AggregateType.NONE));
+            addIfAbsent(columns, 
ColumnDefinition.newRowLsnColumnDefinition(AggregateType.NONE));
         } else if (keysType.equals(KeysType.UNIQUE_KEYS) && 
isEnableMergeOnWrite) {
-            
columns.add(ColumnDefinition.newCommitTsoColumnDefinition(AggregateType.NONE));
+            addIfAbsent(columns, 
ColumnDefinition.newCommitTsoColumnDefinition(AggregateType.NONE));
+        }
+    }
+
+    private static void addIfAbsent(List<ColumnDefinition> columns, 
ColumnDefinition columnDefinition) {
+        boolean present = columns.stream()
+                .anyMatch(column -> 
column.getName().equalsIgnoreCase(columnDefinition.getName()));
+        if (!present) {
+            columns.add(columnDefinition);
         }
     }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/mtmv/MTMVPlanUtilTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/mtmv/MTMVPlanUtilTest.java
index 162660ed8e5..c2b0f83d44f 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/mtmv/MTMVPlanUtilTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/mtmv/MTMVPlanUtilTest.java
@@ -490,6 +490,70 @@ public class MTMVPlanUtilTest extends SqlTestBase {
                 
incrementalCtx.getStatementContext().getIvmRewriteContext().orElseThrow().getMode());
     }
 
+    @Test
+    public void testEnsureMTMVQueryUsableWithRowBinlogHiddenColumns() throws 
Exception {
+        // A table created with row binlog carries hidden columns its query 
never produces:
+        // __DORIS_COMMIT_TSO_COL__ for merge-on-write unique keys, plus 
__DORIS_ROW_LSN_COL__ for
+        // duplicate keys. InternalCatalog#createOlapTable adds them while the 
table is built, so an
+        // MV that enables row binlog itself ends up with them in its physical 
schema.
+        // ensureMTMVQueryUsable re-derives the schema from the query and 
compares the two
+        // (checkColumnIfChange), so the analyzed column list has to carry 
them too -- otherwise
+        // every refresh of such an MV fails with a spurious "column length 
not equals".
+        createTable("CREATE TABLE IF NOT EXISTS row_binlog_schema_base (\n"
+                + "    k1 int,\n"
+                + "    v1 int\n"
+                + ")\n"
+                + "DUPLICATE KEY(k1)\n"
+                + "DISTRIBUTED BY HASH(k1) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1', 'binlog.enable' = 
'true', 'binlog.format' = 'ROW')\n");
+
+        createMvByNereids("create materialized view row_binlog_schema_ivm "
+                + "BUILD DEFERRED REFRESH INCREMENTAL ON MANUAL\n"
+                + "        DISTRIBUTED BY RANDOM BUCKETS 1\n"
+                + "        PROPERTIES ('replication_num' = '1', 
'binlog.enable' = 'true', "
+                + "'binlog.format' = 'ROW') \n"
+                + "        as select k1, v1 from 
test.row_binlog_schema_base;");
+        createMvByNereids("create materialized view row_binlog_schema_dup "
+                + "BUILD DEFERRED REFRESH COMPLETE ON MANUAL\n"
+                + "        DISTRIBUTED BY RANDOM BUCKETS 1\n"
+                + "        PROPERTIES ('replication_num' = '1', 
'binlog.enable' = 'true', "
+                + "'binlog.format' = 'ROW') \n"
+                + "        as select k1, v1 from 
test.row_binlog_schema_base;");
+        createMvByNereids("create materialized view row_binlog_schema_plain "
+                + "BUILD DEFERRED REFRESH COMPLETE ON MANUAL\n"
+                + "        DISTRIBUTED BY RANDOM BUCKETS 1\n"
+                + "        PROPERTIES ('replication_num' = '1') \n"
+                + "        as select k1, v1 from 
test.row_binlog_schema_base;");
+
+        Database db = 
Env.getCurrentEnv().getInternalCatalog().getDbOrAnalysisException("test");
+        MTMV ivmMv = (MTMV) 
db.getTableOrAnalysisException("row_binlog_schema_ivm");
+        MTMV dupMv = (MTMV) 
db.getTableOrAnalysisException("row_binlog_schema_dup");
+        MTMV plainMv = (MTMV) 
db.getTableOrAnalysisException("row_binlog_schema_plain");
+
+        Assertions.assertEquals(Lists.newArrayList(Column.COMMIT_TSO_COL), 
rowBinlogHiddenColumns(ivmMv));
+        Assertions.assertEquals(Lists.newArrayList(Column.COMMIT_TSO_COL, 
Column.ROW_LSN_COL),
+                rowBinlogHiddenColumns(dupMv));
+        Assertions.assertTrue(rowBinlogHiddenColumns(plainMv).isEmpty());
+
+        for (MTMV mtmv : Lists.newArrayList(ivmMv, dupMv, plainMv)) {
+            ConnectContext ctx = MTMVPlanUtil.createMTMVContext(mtmv,
+                    MTMVPlanUtil.DISABLE_RULES_WHEN_GENERATE_MTMV_CACHE);
+            Assertions.assertDoesNotThrow(() -> 
MTMVPlanUtil.ensureMTMVQueryUsable(mtmv, ctx),
+                    "analyzed schema must match the physical schema of " + 
mtmv.getName());
+        }
+    }
+
+    private static List<String> rowBinlogHiddenColumns(MTMV mtmv) {
+        List<String> hidden = Lists.newArrayList();
+        for (Column column : mtmv.getBaseSchema(true)) {
+            if (column.getName().equalsIgnoreCase(Column.COMMIT_TSO_COL)
+                    || column.getName().equalsIgnoreCase(Column.ROW_LSN_COL)) {
+                hidden.add(column.getName().toUpperCase());
+            }
+        }
+        return hidden;
+    }
+
     @Test
     public void testEnsureMTMVQueryAnalyzeFailed() throws Exception {
         createTable("CREATE TABLE IF NOT EXISTS analyze_faild_t_partition (\n"
diff --git 
a/regression-test/data/mtmv_p0/ivm/test_ivm_row_binlog_schema_validation.out 
b/regression-test/data/mtmv_p0/ivm/test_ivm_row_binlog_schema_validation.out
new file mode 100644
index 00000000000..9d9acb797cb
--- /dev/null
+++ b/regression-test/data/mtmv_p0/ivm/test_ivm_row_binlog_schema_validation.out
@@ -0,0 +1,27 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !rb_ivm_rows --
+1      10
+2      20
+
+-- !rb_ivm_no_binlog_rows --
+1      10
+2      20
+
+-- !rb_dup_rows --
+1      10
+2      20
+
+-- !rb_dup_no_binlog_rows --
+1      10
+2      20
+
+-- !cascade_l2_baseline --
+10     2       300
+20     1       300
+
+-- !cascade_l1_after_aba --
+0      3       600
+
+-- !cascade_l2_after_aba --
+0      3       600
+
diff --git 
a/regression-test/suites/mtmv_p0/ivm/test_ivm_row_binlog_schema_validation.groovy
 
b/regression-test/suites/mtmv_p0/ivm/test_ivm_row_binlog_schema_validation.groovy
new file mode 100644
index 00000000000..b93dff85fdd
--- /dev/null
+++ 
b/regression-test/suites/mtmv_p0/ivm/test_ivm_row_binlog_schema_validation.groovy
@@ -0,0 +1,194 @@
+// 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.
+
+import org.awaitility.Awaitility
+import static java.util.concurrent.TimeUnit.SECONDS
+
+// A materialized view re-validates its schema against a fresh analysis of its 
own query whenever a
+// base table changes (MTMVPlanUtil.ensureMTMVQueryUsable -> 
checkColumnIfChange). A table created
+// with row binlog carries hidden columns that the query never produces, and 
an MV carries them too
+// when its own properties enable row binlog (which cascade IVM requires). The 
analyzed schema has to
+// contain them as well, otherwise every refresh of such an MV fails with
+// "column length not equals, please check whether columns of base table have 
changed" -- including a
+// COMPLETE refresh, which is the only way out of a stale baseline.
+//
+// Covered here: the four MV shapes, and the DORIS-28306 shape where a cascade 
L1 could not recover
+// through COMPLETE after a schema ABA on a referenced column.
+suite("test_ivm_row_binlog_schema_validation") {
+    def rowBinlogProps = "'replication_num' = '1', 'binlog.enable' = 'true', 
'binlog.format' = 'ROW'"
+    def plainProps = "'replication_num' = '1'"
+    // A cascade source keeps historical values so its own downstream MV can 
read the binlog.
+    def cascadeProps = rowBinlogProps + ", 'binlog.need_historical_value' = 
'true'"
+
+    def ddlJobCount = { String table ->
+        return sql("""SHOW ALTER TABLE COLUMN WHERE TableName = 
'${table}'""").size()
+    }
+
+    // `SHOW ALTER TABLE COLUMN` keeps finished jobs, so wait for a *new* job 
that is FINISHED.
+    def waitDdlFinished = { String table, int previousJobCount ->
+        Awaitility.await().atMost(300, SECONDS).pollInterval(2, 
SECONDS).until({
+            def jobs = sql """SHOW ALTER TABLE COLUMN WHERE TableName = 
'${table}'"""
+            return jobs.size() > previousJobCount
+                    && jobs.every({ row -> row[9].toString() == 'FINISHED' })
+        })
+    }
+
+    def lastTaskId = null
+    // tasks('type'='mv') can briefly miss the just-finished task, so require 
a *new* TaskId.
+    def waitTerminalTask = { String mv ->
+        def taskResult
+        Awaitility.await().atMost(300, SECONDS).pollInterval(2, 
SECONDS).until({
+            taskResult = sql_return_maparray("""
+                SELECT TaskId, Status, RefreshMode, ErrorMsg
+                FROM tasks('type'='mv')
+                WHERE MvDatabaseName = '${context.dbName}' AND MvName = '${mv}'
+                ORDER BY CreateTime DESC, TaskId DESC LIMIT 1
+            """)
+            return !taskResult.isEmpty()
+                    && taskResult[0].TaskId.toString() != lastTaskId
+                    && taskResult[0].Status.toString() != 'PENDING'
+                    && taskResult[0].Status.toString() != 'RUNNING'
+        })
+        lastTaskId = taskResult[0].TaskId.toString()
+        return taskResult[0]
+    }
+
+    def refresh = { String mv, String mode ->
+        sql """REFRESH MATERIALIZED VIEW ${mv} ${mode}"""
+        return waitTerminalTask(mv)
+    }
+
+    // ---------------------------------------------------------------- 1. 
four MV shapes
+    // Same query and same base table for all four; what differs is whether 
the MV itself enables
+    // row binlog, and (for the IVM ones) whether the MV is a MOW unique table.
+    def cases = [
+            [name: "rb_ivm", mvProps: rowBinlogProps, ivm: true],
+            [name: "rb_ivm_no_binlog", mvProps: plainProps, ivm: true],
+            [name: "rb_dup", mvProps: rowBinlogProps, ivm: false],
+            [name: "rb_dup_no_binlog", mvProps: plainProps, ivm: false],
+    ]
+
+    for (def c : cases) {
+        def table = c.name + "_base"
+        def mv = c.name + "_mv"
+
+        sql """DROP MATERIALIZED VIEW IF EXISTS ${mv}"""
+        sql """DROP TABLE IF EXISTS ${table}"""
+        sql """
+            CREATE TABLE ${table} (
+                k1 INT NOT NULL,
+                v1 INT NULL,
+                spare INT NULL
+            )
+            UNIQUE KEY(k1)
+            DISTRIBUTED BY HASH(k1) BUCKETS 1
+            PROPERTIES ('replication_num' = '1', 
'enable_unique_key_merge_on_write' = 'true',
+                        'binlog.enable' = 'true', 'binlog.format' = 'ROW',
+                        'binlog.need_historical_value' = 'true')
+        """
+        sql """INSERT INTO ${table} VALUES (1, 10, 1), (2, 20, 1)"""
+
+        sql """
+            CREATE MATERIALIZED VIEW ${mv}
+            BUILD DEFERRED REFRESH ${c.ivm ? 'INCREMENTAL' : 'COMPLETE'} ON 
MANUAL
+            KEY(k1)
+            DISTRIBUTED BY HASH(k1) BUCKETS 1
+            PROPERTIES (${c.mvProps})
+            AS SELECT k1, SUM(v1) AS total FROM ${table} GROUP BY k1
+        """
+        def task = refresh(mv, "COMPLETE")
+        assertEquals("SUCCESS", task.Status.toString(), "${c.name} baseline 
refresh: " + task.ErrorMsg)
+
+        // A schema change that leaves the query intact: the MV must stay 
refreshable. The dropped
+        // column is not referenced, so nothing about the MV's result changes.
+        def before = ddlJobCount(table)
+        sql """ALTER TABLE ${table} DROP COLUMN spare"""
+        waitDdlFinished(table, before)
+
+        task = refresh(mv, c.ivm ? "INCREMENTAL" : "COMPLETE")
+        assertEquals("SUCCESS", task.Status.toString(),
+                "${c.name} refresh after an unrelated DROP COLUMN: " + 
task.ErrorMsg)
+    }
+
+    // The dropped column was never referenced, so all four MVs must still 
hold their baseline rows.
+    order_qt_rb_ivm_rows "SELECT k1, total FROM rb_ivm_mv"
+    order_qt_rb_ivm_no_binlog_rows "SELECT k1, total FROM rb_ivm_no_binlog_mv"
+    order_qt_rb_dup_rows "SELECT k1, total FROM rb_dup_mv"
+    order_qt_rb_dup_no_binlog_rows "SELECT k1, total FROM rb_dup_no_binlog_mv"
+
+    // ------------------------------------- 2. DORIS-28306: cascade L1 must 
recover via COMPLETE
+    // A cascade forces row binlog onto L1, so L1 is exactly the shape that 
used to make every
+    // refresh of L1 fail after a schema change. An ABA on a referenced column 
must still leave the
+    // explicit COMPLETE recovery path usable.
+    def cTable = "cascade_aba_base"
+    sql """DROP MATERIALIZED VIEW IF EXISTS cascade_l2"""
+    sql """DROP MATERIALIZED VIEW IF EXISTS cascade_l1"""
+    sql """DROP TABLE IF EXISTS ${cTable}"""
+    sql """
+        CREATE TABLE ${cTable} (
+            id BIGINT NOT NULL,
+            grp INT NULL,
+            amount BIGINT NULL
+        )
+        UNIQUE KEY(id)
+        DISTRIBUTED BY HASH(id) BUCKETS 1
+        PROPERTIES ('replication_num' = '1', 
'enable_unique_key_merge_on_write' = 'true',
+                    'binlog.enable' = 'true', 'binlog.format' = 'ROW',
+                    'binlog.need_historical_value' = 'true')
+    """
+    sql """INSERT INTO ${cTable} VALUES (1, 10, 100), (2, 10, 200), (3, 20, 
300)"""
+
+    sql """
+        CREATE MATERIALIZED VIEW cascade_l1
+        BUILD DEFERRED REFRESH INCREMENTAL ON MANUAL KEY(id)
+        PROPERTIES (${cascadeProps})
+        AS SELECT id, grp, amount FROM ${cTable}
+    """
+    def cTask = refresh("cascade_l1", "COMPLETE")
+    assertEquals("SUCCESS", cTask.Status.toString(), "cascade L1 baseline: " + 
cTask.ErrorMsg)
+
+    sql """
+        CREATE MATERIALIZED VIEW cascade_l2
+        BUILD DEFERRED REFRESH INCREMENTAL ON MANUAL KEY(grp)
+        PROPERTIES (${cascadeProps})
+        AS SELECT grp, COUNT(*) AS row_count, SUM(amount) AS total_amount FROM 
cascade_l1 GROUP BY grp
+    """
+    cTask = refresh("cascade_l2", "COMPLETE")
+    assertEquals("SUCCESS", cTask.Status.toString(), "cascade L2 baseline: " + 
cTask.ErrorMsg)
+    order_qt_cascade_l2_baseline "SELECT grp, row_count, total_amount FROM 
cascade_l2"
+
+    def cBefore = ddlJobCount(cTable)
+    sql """ALTER TABLE ${cTable} DROP COLUMN grp"""
+    waitDdlFinished(cTable, cBefore)
+    cBefore = ddlJobCount(cTable)
+    sql """ALTER TABLE ${cTable} ADD COLUMN grp INT NULL DEFAULT '0'"""
+    waitDdlFinished(cTable, cBefore)
+
+    // COMPLETE is the recovery path: it has to rebuild L1 from the current 
base-table semantics,
+    // where every pre-existing row now reads the re-added column's default 
value. Whether the
+    // strict INCREMENTAL in between is rejected is a separate contract, owned 
by the suite for the
+    // baseline invalidation itself, so it is deliberately not asserted here 
-- this suite has to
+    // hold with or without that change.
+    cTask = refresh("cascade_l1", "COMPLETE")
+    assertEquals("SUCCESS", cTask.Status.toString(), "COMPLETE after schema 
ABA: " + cTask.ErrorMsg)
+    order_qt_cascade_l1_after_aba "SELECT grp, COUNT(*) AS c, SUM(amount) AS s 
FROM cascade_l1 GROUP BY grp"
+
+    // ... and the downstream level stays refreshable afterwards.
+    cTask = refresh("cascade_l2", "COMPLETE")
+    assertEquals("SUCCESS", cTask.Status.toString(), "cascade L2 after L1 
recovery: " + cTask.ErrorMsg)
+    order_qt_cascade_l2_after_aba "SELECT grp, row_count, total_amount FROM 
cascade_l2"
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to