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


##########
fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRelationManager.java:
##########
@@ -502,10 +511,19 @@ private void processBaseTableChange(BaseTableInfo 
baseTableInfo, String msgPrefi
                 LOG.warn(e);
                 continue;
             }
-            if (checkQueryUsable && invalidateMvIfQueryUnusable(baseTableInfo, 
mvTable)) {
-                // Invalidated with the reason, which is the more specific of 
the two messages and the one
-                // this change is worth recording: the state is the same one 
the generic record below
-                // would set, so writing it too would only bury the reason.
+            if (checkQueryUsable) {

Review Comment:
   Fixed in 0d62f6ec5bf, on the query's lineage. The decision is now whether 
the query, as it is analysed, reads a column by the name the change is about, 
taken from the analysed plan's lineage -- projections, filters, joins, 
aggregation, and through the views in between -- so an alias or a string that 
happens to read the same is not a hit, and a column reached inside a view is.
   
   Measured on the shape in the comment (`SELECT o.id FROM outer_t o WHERE 
EXISTS (SELECT 1 FROM inner_t i WHERE i.id = o.id AND flag = 1)`): dropping 
`inner_t.flag` left the MV in NORMAL holding the row the old column produced, 
and the same query then analyses with `flag` reached from `outer_t`; with the 
change it invalidates. The plan used is the analysed logical one -- the 
physical plan's lineage does not reach the filters, which I had to find the 
hard way.
   
   The add of a column is judged the same way, in the other direction: a column 
added where the query reaches a name answers for it from then on. What is not 
addressed here is the creation-time gap you point at, qualifying slots inside 
nested subqueries; until that is fixed this invalidates on the name rather than 
on the bound identity, which is worth an issue of its own.
   



##########
regression-test/suites/mtmv_p0/test_drop_unreferenced_column_mtmv.groovy:
##########
@@ -0,0 +1,134 @@
+// 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.junit.Assert;
+
+/**
+ * Which base-table column changes invalidate a materialized view.
+ *
+ * <p>A dropped column is judged by re-analysing the MV's own query -- but 
only where that question has an
+ * answer. The query is analysed against the table as it is when the alter 
reaches the MV hook, and unless
+ * the change is a light one it has not been applied yet at that point: it was 
submitted as a job, the
+ * column is still there, and every query still analyses. An invalidation 
decided on that answer would be
+ * about the table from before the change, which is exactly where a dropped 
and re-added column leaves the
+ * ABA the invalidation exists for. So a change that is not in place keeps 
invalidating, and the two halves
+ * of this suite pin the two answers:
+ * <ol>
+ *   <li>a merge-on-write table, where dropping a value column is a light 
change: the MV whose query does
+ *       not name the column is left alone, and the MV whose query names it is 
invalidated;</li>
+ *   <li>a duplicate table, where the same drop is not a light one and a job 
does the data rewrite: the
+ *       column is out of the table's schema before the hook runs all the 
same, so the same answer holds.
+ *       What the gate is for is the other side of that -- a change a job has 
not applied yet, where the
+ *       query would be analysed against the table from before it. There the 
MV is invalidated, which is
+ *       what every column change did before the queries were asked at 
all.</li>
+ * </ol>
+ *
+ * <p>The second half is also where the consequence is observable: the rewrite 
reaches the MV on that table
+ * and not on a merge-on-write one, so the state the change records is what is 
left to report on the first.
+ * The IVM side of the same change, where the state is what escalates the next 
refresh to a whole-MV
+ * COMPLETE, is pinned in the ivm directory.
+ */
+suite("test_drop_unreferenced_column_mtmv", "mtmv") {
+    String dbName = context.config.getDbNameByFile(context.file)
+    String suiteName = "test_drop_unreferenced_column_mtmv"
+
+    // ------------------------------------------------- 1. the change is in 
place: the query decides
+    String mowTable = "${suiteName}_mow_table"
+    String mowMv = "${suiteName}_mow_mv"
+
+    sql """drop materialized view if exists ${mowMv}"""
+    sql """drop table if exists ${mowTable}"""
+    sql """
+        CREATE TABLE ${mowTable}
+        (
+            k1 INT NOT NULL,
+            amount BIGINT,
+            spare BIGINT
+        )
+        UNIQUE KEY(k1)
+        DISTRIBUTED BY HASH(k1) BUCKETS 2
+        PROPERTIES ("replication_num" = "1", 
"enable_unique_key_merge_on_write" = "true")
+    """
+    sql """INSERT INTO ${mowTable} VALUES (1, 100, 7), (2, 200, 8)"""
+    sql """
+        CREATE MATERIALIZED VIEW ${mowMv}
+        BUILD DEFERRED REFRESH COMPLETE ON MANUAL
+        DISTRIBUTED BY HASH(k1) BUCKETS 2
+        PROPERTIES ("replication_num" = "1")
+        AS SELECT k1, SUM(amount) AS total FROM ${mowTable} GROUP BY k1
+    """
+    sql """REFRESH MATERIALIZED VIEW ${mowMv} COMPLETE"""
+    waitingMTMVTaskFinishedByMvName(mowMv)
+    order_qt_mow_baseline "SELECT k1, total FROM ${mowMv}"
+
+    // A column the query does not name. Dropping it gives this MV nothing to 
recompute, so it is not
+    // invalidated: it stays a refresh candidate, and the state is where that 
shows.
+    sql """ALTER TABLE ${mowTable} DROP COLUMN spare"""
+    assertEquals("FINISHED", getAlterColumnFinalState("${mowTable}"))
+    order_qt_mow_state_after_unreferenced_drop "select 
Name,State,RefreshState,SyncWithBaseTables from 
mv_infos('database'='${dbName}') where Name='${mowMv}'"
+
+    // A column the query names. The MV cannot be computed from the table any 
more, so it is invalidated.
+    sql """ALTER TABLE ${mowTable} DROP COLUMN amount"""
+    assertEquals("FINISHED", getAlterColumnFinalState("${mowTable}"))
+    order_qt_mow_state_after_referenced_drop "select 
Name,State,RefreshState,SyncWithBaseTables from 
mv_infos('database'='${dbName}') where Name='${mowMv}'"
+    // Neither column was one the rows the MV holds depended on: the 
invalidation above is not about the
+    // data being wrong, it is about the query the data stands for.
+    order_qt_mow_rows_after_both_drops "SELECT k1, total FROM ${mowMv}"
+
+    // ------------------------- 2. the same drop on a table whose data a job 
rewrites: same answer
+    String dupTable = "${suiteName}_dup_table"
+    String dupMv = "${suiteName}_dup_mv"
+    String dupQuery = "SELECT k1, SUM(amount) AS total FROM ${dupTable} GROUP 
BY k1"
+
+    sql """drop materialized view if exists ${dupMv}"""
+    sql """drop table if exists ${dupTable}"""
+    sql """
+        CREATE TABLE ${dupTable}
+        (
+            k1 INT,
+            amount BIGINT,
+            spare BIGINT
+        )
+        DUPLICATE KEY(k1)
+        DISTRIBUTED BY HASH(k1) BUCKETS 2
+        PROPERTIES ("replication_num" = "1")

Review Comment:
   Fixed in 0d62f6ec5bf. You are right, and the FE log says the same: the drop 
took `SchemaChangeHandler#modifyTableLightSchemaChange`.
   
   The second half of the suite now creates its table with 
`light_schema_change=false`, so it is the job path it describes: the hook runs 
before the job has applied the change, the table still holds the column and 
every query analyses against it, so nothing can be concluded from the query and 
the MV is invalidated the way it was before the queries were asked. It asserts 
`SCHEMA_CHANGE` and that the rewrite no longer chooses the MV. The light path 
keeps its own assertions in the first half, where the column is out of the 
schema before the hook runs.
   



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