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