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 <[email protected]>
---
.../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: [email protected]
For additional commands, e-mail: [email protected]