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

dataroaring pushed a commit to branch branch-3.0
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-3.0 by this push:
     new a79b5e8cd9a branch-3.0: [feature](restore) Support force_replace 
different schema of view for restore #49870 (#49978)
a79b5e8cd9a is described below

commit a79b5e8cd9a7397cc8659f3f9497d0e97b45e9a7
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Apr 22 10:23:22 2025 +0800

    branch-3.0: [feature](restore) Support force_replace different schema of 
view for restore #49870 (#49978)
    
    Cherry-picked from #49870
    
    Co-authored-by: Uniqueyou <wangyix...@selectdb.com>
---
 .../java/org/apache/doris/backup/RestoreJob.java   | 99 ++++++++++++++++++++--
 ...t_backup_restore_force_replace_diff_view.groovy | 84 ++++++++++++++++++
 2 files changed, 176 insertions(+), 7 deletions(-)

diff --git a/fe/fe-core/src/main/java/org/apache/doris/backup/RestoreJob.java 
b/fe/fe-core/src/main/java/org/apache/doris/backup/RestoreJob.java
index c69afce0ee4..44e29e2c106 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/backup/RestoreJob.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/backup/RestoreJob.java
@@ -885,13 +885,25 @@ public class RestoreJob extends AbstractJob implements 
GsonPostProcessable {
                         String srcDbName = jobInfo.dbName;
                         remoteView.resetViewDefForRestore(srcDbName, 
db.getName());
                         if 
(!localViewSignature.equals(remoteView.getSignature(BackupHandler.SIGNATURE_VERSION)))
 {
-                            status = new Status(ErrCode.COMMON_ERROR, "View "
-                                    + 
jobInfo.getAliasByOriginNameIfSet(backupViewName)
-                                    + " already exist but with different 
schema");
-                            return;
+                            if (isForceReplace) {
+                                LOG.info("View {} already exist but with 
different schema, will force replace, "
+                                        + "local view: {}, remote view: {}",
+                                        backupViewName, localViewSignature,
+                                        
remoteView.getSignature(BackupHandler.SIGNATURE_VERSION));
+                            } else {
+                                LOG.warn("View {} already exist but with 
different schema, will force replace, "
+                                        + "local view: {}, remote view: {}",
+                                        backupViewName, localViewSignature,
+                                        
remoteView.getSignature(BackupHandler.SIGNATURE_VERSION));
+                                status = new Status(ErrCode.COMMON_ERROR, 
"View "
+                                        + 
jobInfo.getAliasByOriginNameIfSet(backupViewName)
+                                        + " already exist but with different 
schema");
+                                return;
+                            }
                         }
                     }
-                } else {
+                }
+                if (localTbl == null || isAtomicRestore) {
                     String srcDbName = jobInfo.dbName;
                     remoteView.resetViewDefForRestore(srcDbName, db.getName());
                     remoteView.resetIdsForRestore(env);
@@ -971,7 +983,8 @@ public class RestoreJob extends AbstractJob implements 
GsonPostProcessable {
                 if (Env.isStoredTableNamesLowerCase()) {
                     tableName = tableName.toLowerCase();
                 }
-                if (restoreTbl.getType() == TableType.OLAP && isAtomicRestore) 
{
+                if ((restoreTbl.getType() == TableType.OLAP || restoreTbl
+                        .getType() == TableType.VIEW) && isAtomicRestore) {
                     tableName = tableAliasWithAtomicRestore(tableName);
                 }
                 restoreTbl.setName(tableName);
@@ -2359,7 +2372,8 @@ public class RestoreJob extends AbstractJob implements 
GsonPostProcessable {
 
             // remove restored tbls
             for (Table restoreTbl : restoredTbls) {
-                if (isAtomicRestore && restoreTbl.getType() == TableType.OLAP
+                if (isAtomicRestore
+                        && (restoreTbl.getType() == TableType.OLAP || 
restoreTbl.getType() == TableType.VIEW)
                         && 
!restoreTbl.getName().startsWith(ATOMIC_RESTORE_TABLE_PREFIX)) {
                     // In atomic restore, a table registered to db must have a 
name with the prefix,
                     // otherwise, it has not been registered and can be 
ignored here.
@@ -2383,6 +2397,13 @@ public class RestoreJob extends AbstractJob implements 
GsonPostProcessable {
                             } finally {
                                 restoreTbl.writeUnlock();
                             }
+                        } else if (restoreTbl.getType() == TableType.VIEW) {
+                            restoreTbl.writeLock();
+                            try {
+                                db.unregisterTable(restoreTbl.getName());
+                            } finally {
+                                restoreTbl.writeUnlock();
+                            }
                         }
                     } finally {
                         db.writeUnlock();
@@ -2528,6 +2549,70 @@ public class RestoreJob extends AbstractJob implements 
GsonPostProcessable {
                 db.writeUnlock();
             }
         }
+        for (BackupJobInfo.BackupViewInfo backupViewInfo : 
jobInfo.newBackupObjects.views) {
+            String originName = 
jobInfo.getAliasByOriginNameIfSet(backupViewInfo.name);
+            if (Env.isStoredTableNamesLowerCase()) {
+                originName = originName.toLowerCase();
+            }
+            String aliasName = tableAliasWithAtomicRestore(originName);
+
+            if (!db.writeLockIfExist()) {
+                return Status.OK;
+            }
+            try {
+                Table newTbl = db.getTableNullable(aliasName);
+                if (newTbl == null) {
+                    LOG.warn("replace view from {} to {}, but the temp view is 
not found" + " isAtomicRestore: {}",
+                            aliasName, originName, isAtomicRestore);
+                    return new Status(ErrCode.COMMON_ERROR, "replace view 
failed, the temp view "
+                            + aliasName + " is not found");
+                }
+                if (newTbl.getType() != TableType.VIEW) {
+                    LOG.warn(
+                            "replace view from {} to {}, but the temp view is 
not VIEW, it type is {}"
+                                    + " isAtomicRestore: {}",
+                            aliasName, originName, newTbl.getType(), 
isAtomicRestore);
+                    return new Status(ErrCode.COMMON_ERROR, "replace view 
failed, the temp view " + aliasName
+                            + " is not OLAP, it is " + newTbl.getType());
+                }
+
+                View originViewTbl = null;
+                Table originTbl = db.getTableNullable(originName);
+                if (originTbl != null) {
+                    if (originTbl.getType() != TableType.VIEW) {
+                        LOG.warn(
+                                "replace view from {} to {}, but the origin 
view is not VIEW, it type is {}"
+                                        + " isAtomicRestore: {}",
+                                aliasName, originName, originTbl.getType(), 
isAtomicRestore);
+                        return new Status(ErrCode.COMMON_ERROR, "replace view 
failed, the origin view "
+                                + originName + " is not VIEW, it is " + 
originTbl.getType());
+                    }
+                    originViewTbl = (View) originTbl; // save the origin view, 
then drop it.
+                }
+
+                // replace the view.
+                View newViewTbl = (View) newTbl;
+                newViewTbl.writeLock();
+                try {
+                    // rename new view name to origin view name and add the 
new view to database.
+                    db.unregisterTable(aliasName);
+                    db.unregisterTable(originName);
+                    newViewTbl.setName(originName);
+                    db.registerTable(newViewTbl);
+
+                    LOG.info(
+                            "restore with replace view {} name to {}, origin 
view={}"
+                                    + " isAtomicRestore: {}",
+                            newViewTbl.getId(), originName,
+                            originViewTbl == null ? -1L : 
originViewTbl.getId(),
+                            isAtomicRestore);
+                } finally {
+                    newViewTbl.writeUnlock();
+                }
+            } finally {
+                db.writeUnlock();
+            }
+        }
 
         return Status.OK;
     }
diff --git 
a/regression-test/suites/backup_restore/test_backup_restore_force_replace_diff_view.groovy
 
b/regression-test/suites/backup_restore/test_backup_restore_force_replace_diff_view.groovy
new file mode 100644
index 00000000000..6ff50576212
--- /dev/null
+++ 
b/regression-test/suites/backup_restore/test_backup_restore_force_replace_diff_view.groovy
@@ -0,0 +1,84 @@
+// 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_backup_restore_force_replace_diff_view", "backup_restore") {
+    String suiteName = "test_backup_restore_force_replace_diff_view"
+    String dbName = "${suiteName}_db_0"
+    String repoName = "${suiteName}_repo_" + 
UUID.randomUUID().toString().replace("-", "")
+    String snapshotName = "${suiteName}_snapshot_" + System.currentTimeMillis()
+    String tableNamePrefix = "${suiteName}_tables"
+    String tableName = "${tableNamePrefix}_0"
+
+    def syncer = getSyncer()
+    syncer.createS3Repository(repoName)
+    sql "DROP DATABASE IF EXISTS ${dbName}"
+    sql "DROP VIEW IF EXISTS ${tableName}_view"
+    sql "CREATE DATABASE IF NOT EXISTS ${dbName}"
+
+    sql "DROP TABLE IF EXISTS ${dbName}.${tableName}"
+    sql """
+        CREATE TABLE ${dbName}.${tableName} (
+            `id` LARGEINT NOT NULL,
+            `k1` INT,
+            `k2` INT 
+        )
+        DUPLICATE KEY(`id`)
+        DISTRIBUTED BY HASH(`id`) BUCKETS 2
+        PROPERTIES
+        (
+            "replication_num" = "1"
+        )
+        """
+
+    sql """
+        CREATE VIEW ${dbName}.${tableName}_view AS
+        SELECT k1 FROM ${dbName}.${tableName}
+    """
+
+    sql """
+        BACKUP SNAPSHOT ${dbName}.${snapshotName}
+        TO `${repoName}`
+        ON (
+            ${tableName}_view
+        )
+    """
+
+    syncer.waitSnapshotFinish(dbName)
+
+    def snapshot = syncer.getSnapshotTimestamp(repoName, snapshotName)
+    assertTrue(snapshot != null)
+
+    sql "ALTER VIEW ${dbName}.${tableName}_view AS SELECT k2 FROM 
${dbName}.${tableName}"
+
+    sql """
+        RESTORE SNAPSHOT ${dbName}.${snapshotName}
+        FROM `${repoName}`
+        PROPERTIES
+        (
+            "backup_timestamp" = "${snapshot}",
+            "reserve_replica" = "true",
+            "atomic_restore" = "true",
+            "force_replace" = "true"
+        )
+    """
+
+    syncer.waitAllRestoreFinish(dbName)
+
+    def desc_res = sql "SHOW CREATE VIEW ${dbName}.${tableName}_view"
+    assertTrue(desc_res[0][1].contains("k1"))
+}
+


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org
For additional commands, e-mail: commits-h...@doris.apache.org

Reply via email to