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 ecbc5feab9d branch-4.2: [fix](mtmv) CREATE MATERIALIZED VIEW IF NOT 
EXISTS skips existing sync MV #69370 (#69531)
ecbc5feab9d is described below

commit ecbc5feab9d3518305cab8f4e90e12a5024f391d
Author: yujun <[email protected]>
AuthorDate: Sat Oct 24 10:53:36 2026 +0800

    branch-4.2: [fix](mtmv) CREATE MATERIALIZED VIEW IF NOT EXISTS skips 
existing sync MV #69370 (#69531)
    
    cherry-pick: #69370
---
 .../doris/alter/MaterializedViewHandler.java       |  6 ++
 .../doris/nereids/parser/LogicalPlanBuilder.java   |  3 +-
 .../commands/CreateMaterializedViewCommand.java    |  8 ++-
 .../doris/nereids/parser/NereidsParserTest.java    | 15 +++++
 .../test_create_mv_if_not_exists.out               |  9 +++
 .../test_create_mv_if_not_exists.groovy            | 74 ++++++++++++++++++++++
 6 files changed, 113 insertions(+), 2 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/alter/MaterializedViewHandler.java 
b/fe/fe-core/src/main/java/org/apache/doris/alter/MaterializedViewHandler.java
index 5e3e64bf8c8..ac39e630ce3 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/alter/MaterializedViewHandler.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/alter/MaterializedViewHandler.java
@@ -202,6 +202,12 @@ public class MaterializedViewHandler extends AlterHandler {
             if (olapTable.existTempPartitions()) {
                 throw new DdlException("Can not alter table when there are 
temp partitions in table");
             }
+            if (createMvCommand.isIfNotExists()
+                    && 
olapTable.hasMaterializedIndex(createMvCommand.getMVName())) {
+                LOG.info("materialized view[{}] already exists on table[{}], 
skip creating it",
+                        createMvCommand.getMVName(), olapTable.getName());
+                return;
+            }
             // check no duplicate column name in full schema
             Set<String> allColumnNames = new 
TreeSet<>(String.CASE_INSENSITIVE_ORDER);
             for (Column column : olapTable.getFullSchema()) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
index e98772cd44f..9fffd1180b3 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
@@ -1707,7 +1707,8 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
         LogicalPlan logicalPlan = new 
UnboundResultSink<>(visitQuery(ctx.query()));
         Map<String, String> properties = ctx.propertyClause() != null
                 ? Maps.newHashMap(visitPropertyClause(ctx.propertyClause())) : 
Maps.newHashMap();
-        return new CreateMaterializedViewCommand(new TableNameInfo(nameParts), 
logicalPlan, properties);
+        return new CreateMaterializedViewCommand(ctx.EXISTS() != null, new 
TableNameInfo(nameParts),
+                logicalPlan, properties);
     }
 
     /**
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CreateMaterializedViewCommand.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CreateMaterializedViewCommand.java
index fc1327586e6..8ae49261e6a 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CreateMaterializedViewCommand.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CreateMaterializedViewCommand.java
@@ -105,6 +105,7 @@ import java.util.stream.Collectors;
  */
 public class CreateMaterializedViewCommand extends Command implements 
ForwardWithSync {
     private static final String SYNC_MV_PLANER_DISABLE_RULES = 
"HAVING_TO_FILTER";
+    private final boolean ifNotExists;
     private final TableNameInfo name;
 
     private final LogicalPlan logicalPlan;
@@ -116,9 +117,10 @@ public class CreateMaterializedViewCommand extends Command 
implements ForwardWit
     private KeysType mvKeysType;
     private OriginStatement originStatement;
 
-    public CreateMaterializedViewCommand(TableNameInfo name, LogicalPlan 
logicalPlan,
+    public CreateMaterializedViewCommand(boolean ifNotExists, TableNameInfo 
name, LogicalPlan logicalPlan,
                                          Map<String, String> properties) {
         super(PlanType.CREATE_MATERIALIZED_VIEW_COMMAND);
+        this.ifNotExists = ifNotExists;
         this.name = name;
         this.logicalPlan = logicalPlan;
         this.properties = properties;
@@ -141,6 +143,10 @@ public class CreateMaterializedViewCommand extends Command 
implements ForwardWit
         return StmtType.CREATE;
     }
 
+    public boolean isIfNotExists() {
+        return ifNotExists;
+    }
+
     public String getMVName() {
         return name.getTbl();
     }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/NereidsParserTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/NereidsParserTest.java
index 6e16892e4bc..c91d01b2928 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/NereidsParserTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/NereidsParserTest.java
@@ -1550,6 +1550,21 @@ public class NereidsParserTest extends ParserTestBase {
         Assertions.assertInstanceOf(CreateMaterializedViewCommand.class, 
logicalPlan);
     }
 
+    @Test
+    public void testCreateSyncMvIfNotExists() {
+        NereidsParser parser = new NereidsParser();
+        LogicalPlan logicalPlan = parser.parseSingle(
+                "CREATE MATERIALIZED VIEW IF NOT EXISTS mv1 SELECT k1 FROM 
t2");
+        CreateMaterializedViewCommand command = Assertions.assertInstanceOf(
+                CreateMaterializedViewCommand.class, logicalPlan);
+        Assertions.assertTrue(command.isIfNotExists());
+
+        LogicalPlan withoutClause = parser.parseSingle("CREATE MATERIALIZED 
VIEW mv1 SELECT k1 FROM t2");
+        CreateMaterializedViewCommand withoutClauseCommand = 
Assertions.assertInstanceOf(
+                CreateMaterializedViewCommand.class, withoutClause);
+        Assertions.assertFalse(withoutClauseCommand.isIfNotExists());
+    }
+
     @Test
     public void testDropTemporaryTable() throws Exception {
         NereidsParser parser = new NereidsParser();
diff --git 
a/regression-test/data/mv_p0/test_create_mv_if_not_exists/test_create_mv_if_not_exists.out
 
b/regression-test/data/mv_p0/test_create_mv_if_not_exists/test_create_mv_if_not_exists.out
new file mode 100644
index 00000000000..d732e365bf5
--- /dev/null
+++ 
b/regression-test/data/mv_p0/test_create_mv_if_not_exists/test_create_mv_if_not_exists.out
@@ -0,0 +1,9 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !select_k1 --
+1      10
+2      20
+
+-- !select_k2 --
+1      10
+2      20
+
diff --git 
a/regression-test/suites/mv_p0/test_create_mv_if_not_exists/test_create_mv_if_not_exists.groovy
 
b/regression-test/suites/mv_p0/test_create_mv_if_not_exists/test_create_mv_if_not_exists.groovy
new file mode 100644
index 00000000000..7fc35aea524
--- /dev/null
+++ 
b/regression-test/suites/mv_p0/test_create_mv_if_not_exists/test_create_mv_if_not_exists.groovy
@@ -0,0 +1,74 @@
+// 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.
+
+suite("test_create_mv_if_not_exists") {
+    sql """ DROP TABLE IF EXISTS t_create_mv_if_not_exists """
+    sql """
+        CREATE TABLE t_create_mv_if_not_exists (
+            k1 INT,
+            k2 INT,
+            v1 BIGINT
+        )
+        DUPLICATE KEY(k1)
+        DISTRIBUTED BY HASH(k1) BUCKETS 1
+        PROPERTIES ("replication_num" = "1")
+    """
+    sql """ DROP MATERIALIZED VIEW IF EXISTS mv_create_if_not_exists_k1 ON 
t_create_mv_if_not_exists """
+    sql """ DROP MATERIALIZED VIEW IF EXISTS mv_create_if_not_exists_k2 ON 
t_create_mv_if_not_exists """
+    sql """ INSERT INTO t_create_mv_if_not_exists VALUES (1, 1, 10), (2, 2, 
20) """
+
+    def syncMvIndexId = { String tableName, String mvName ->
+        def dbId = sql("SHOW PROC '/dbs'").find { it[1].toString() == 
context.dbName }[0]
+        def tableId = sql("SHOW PROC '/dbs/${dbId}'").find { it[1].toString() 
== tableName }[0]
+        def rows = sql("SHOW PROC '/dbs/${dbId}/${tableId}/index_schema'")
+        return rows.find { it[1].toString() == mvName }[0].toString()
+    }
+
+    create_sync_mv(context.dbName, "t_create_mv_if_not_exists", 
"mv_create_if_not_exists_k1", """
+        SELECT k1 AS mv_k1, SUM(v1) AS mv_sum_v1
+        FROM t_create_mv_if_not_exists
+        GROUP BY k1""")
+    def k1IndexId = syncMvIndexId("t_create_mv_if_not_exists", 
"mv_create_if_not_exists_k1")
+
+    // Both variants of IF NOT EXISTS must succeed and leave the existing MV 
untouched.
+    sql """
+        CREATE MATERIALIZED VIEW IF NOT EXISTS mv_create_if_not_exists_k1 AS
+        SELECT k1 AS mv_k1, SUM(v1) AS mv_sum_v1
+        FROM t_create_mv_if_not_exists
+        GROUP BY k1
+    """
+    assertEquals(k1IndexId, syncMvIndexId("t_create_mv_if_not_exists", 
"mv_create_if_not_exists_k1"))
+    sql """
+        CREATE MATERIALIZED VIEW IF NOT EXISTS mv_create_if_not_exists_k1 AS
+        SELECT k2 AS mv_k2, SUM(v1) AS mv_sum_v1_2
+        FROM t_create_mv_if_not_exists
+        GROUP BY k2
+    """
+    assertEquals(k1IndexId, syncMvIndexId("t_create_mv_if_not_exists", 
"mv_create_if_not_exists_k1"))
+
+    // IF NOT EXISTS must still create the sync MV when the name is free.
+    sql """
+        CREATE MATERIALIZED VIEW IF NOT EXISTS mv_create_if_not_exists_k2 AS
+        SELECT k2 AS mv_k2, SUM(v1) AS mv_sum_v1_2
+        FROM t_create_mv_if_not_exists
+        GROUP BY k2
+    """
+    waitingMVTaskFinishedByMvName(context.dbName, "t_create_mv_if_not_exists", 
"mv_create_if_not_exists_k2")
+
+    order_qt_select_k1 "SELECT * FROM t_create_mv_if_not_exists INDEX 
mv_create_if_not_exists_k1 ORDER BY 1"
+    order_qt_select_k2 "SELECT * FROM t_create_mv_if_not_exists INDEX 
mv_create_if_not_exists_k2 ORDER BY 1"
+}


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

Reply via email to