@@ -128,6 +128,159 @@ suite("test_backup_restore_with_view", "backup_restore") {
128128 def res = sql " SHOW VIEW FROM ${ dbName1} .${ tableName} "
129129 assertTrue (res. size() > 0 )
130130
131+ // Test cross-database view references preservation
132+ logger. info(" ========== Testing cross-database view references ==========" )
133+
134+ String baseDbName = " ${ suiteName} _base_db"
135+ String viewDbName = " ${ suiteName} _view_db"
136+ String restoreDbName = " ${ suiteName} _restore_db"
137+ String baseTableName = " base_table"
138+ String localTableName = " local_table"
139+ String crossDbViewName = " cross_db_view"
140+ String mixedViewName = " mixed_view"
141+
142+ try {
143+ // Create base database with base table
144+ sql " DROP DATABASE IF EXISTS ${ baseDbName} FORCE"
145+ sql " DROP DATABASE IF EXISTS ${ viewDbName} FORCE"
146+ sql " DROP DATABASE IF EXISTS ${ restoreDbName} FORCE"
147+
148+ sql " CREATE DATABASE ${ baseDbName} "
149+ sql " CREATE DATABASE ${ viewDbName} "
150+
151+ sql """
152+ CREATE TABLE ${ baseDbName} .${ baseTableName} (
153+ id INT,
154+ name VARCHAR(100),
155+ value INT
156+ )
157+ DUPLICATE KEY(id)
158+ DISTRIBUTED BY HASH(id) BUCKETS 2
159+ PROPERTIES ("replication_num" = "1")
160+ """
161+
162+ sql """
163+ CREATE TABLE ${ viewDbName} .${ localTableName} (
164+ id INT,
165+ category VARCHAR(100)
166+ )
167+ DUPLICATE KEY(id)
168+ DISTRIBUTED BY HASH(id) BUCKETS 2
169+ PROPERTIES ("replication_num" = "1")
170+ """
171+
172+ sql """
173+ INSERT INTO ${ baseDbName} .${ baseTableName} VALUES
174+ (1, 'Alice', 100),
175+ (2, 'Bob', 200),
176+ (3, 'Charlie', 300)
177+ """
178+
179+ sql """
180+ INSERT INTO ${ viewDbName} .${ localTableName} VALUES
181+ (1, 'TypeA'),
182+ (2, 'TypeB'),
183+ (3, 'TypeC')
184+ """
185+
186+ // Create cross-database view (references base_db only)
187+ sql """
188+ CREATE VIEW ${ viewDbName} .${ crossDbViewName} AS
189+ SELECT id, name, value
190+ FROM `internal`.`${ baseDbName} `.`${ baseTableName} `
191+ WHERE value > 100
192+ """
193+
194+ // Create mixed view (references both base_db and view_db)
195+ sql """
196+ CREATE VIEW ${ viewDbName} .${ mixedViewName} AS
197+ SELECT
198+ t1.id,
199+ t1.name,
200+ t1.value,
201+ t2.category
202+ FROM `internal`.`${ baseDbName} `.`${ baseTableName} ` t1
203+ JOIN `internal`.`${ viewDbName} `.`${ localTableName} ` t2
204+ ON t1.id = t2.id
205+ """
206+
207+ // Verify original views work
208+ def crossDbResult = sql " SELECT * FROM ${ viewDbName} .${ crossDbViewName} ORDER BY id"
209+ assertTrue (crossDbResult. size() == 2 )
210+ assertTrue (crossDbResult[0 ][0 ] == 2 )
211+ assertTrue (crossDbResult[0 ][1 ] == " Bob" )
212+
213+ def mixedResult = sql " SELECT * FROM ${ viewDbName} .${ mixedViewName} ORDER BY id"
214+ assertTrue (mixedResult. size() == 3 )
215+
216+ // Backup view_db
217+ String crossDbSnapshot = " ${ suiteName} _cross_db_snapshot"
218+ sql """
219+ BACKUP SNAPSHOT ${ viewDbName} .${ crossDbSnapshot}
220+ TO `${ repoName} `
221+ """
222+
223+ syncer. waitSnapshotFinish(viewDbName)
224+ def crossDbSnapshotTs = syncer. getSnapshotTimestamp(repoName, crossDbSnapshot)
225+ assertTrue (crossDbSnapshotTs != null )
226+ logger. info(" Cross-DB snapshot timestamp: ${ crossDbSnapshotTs} " )
227+
228+ // Create target database before restore (FIX: prevent database not exist error)
229+ sql " CREATE DATABASE IF NOT EXISTS ${ restoreDbName} "
230+
231+ // Restore to different database
232+ sql """
233+ RESTORE SNAPSHOT ${ restoreDbName} .${ crossDbSnapshot}
234+ FROM `${ repoName} `
235+ PROPERTIES
236+ (
237+ "backup_timestamp" = "${ crossDbSnapshotTs} ",
238+ "reserve_replica" = "true"
239+ )
240+ """
241+
242+ syncer. waitAllRestoreFinish(restoreDbName)
243+
244+ // Verify restore success
245+ def restoreResult = sql_return_maparray """ SHOW RESTORE FROM ${ restoreDbName} WHERE Label = "${ crossDbSnapshot} " """
246+ logger. info(" Cross-DB restore result: ${ restoreResult} " )
247+ assertTrue (restoreResult. last(). State == " FINISHED" )
248+
249+ // Critical verification: Check view definitions
250+ def crossDbViewDef = sql " SHOW CREATE VIEW ${ restoreDbName} .${ crossDbViewName} "
251+ logger. info(" Cross-DB view definition after restore: ${ crossDbViewDef[0][1]} " )
252+
253+ // Cross-DB view should still reference base_db, not restore_db
254+ assertTrue (crossDbViewDef[0 ][1 ]. contains(" `${ baseDbName} `" ),
255+ " Cross-DB view should preserve base_db reference" )
256+
257+ // Mixed view should preserve base_db reference but update view_db to restore_db
258+ def mixedViewDef = sql " SHOW CREATE VIEW ${ restoreDbName} .${ mixedViewName} "
259+ logger. info(" Mixed view definition after restore: ${ mixedViewDef[0][1]} " )
260+
261+ assertTrue (mixedViewDef[0 ][1 ]. contains(" `${ baseDbName} `" ),
262+ " Mixed view should preserve base_db reference" )
263+ assertTrue (mixedViewDef[0 ][1 ]. contains(" `${ restoreDbName} `" ),
264+ " Mixed view should reference restore_db for local tables" )
265+
266+ // Verify views still work after restore
267+ def restoredCrossDbResult = sql " SELECT * FROM ${ restoreDbName} .${ crossDbViewName} ORDER BY id"
268+ assertTrue (restoredCrossDbResult. size() == 2 )
269+ assertTrue (restoredCrossDbResult[0 ][0 ] == 2 )
270+ assertTrue (restoredCrossDbResult[0 ][1 ] == " Bob" )
271+
272+ def restoredMixedResult = sql " SELECT * FROM ${ restoreDbName} .${ mixedViewName} ORDER BY id"
273+ assertTrue (restoredMixedResult. size() == 3 )
274+ assertTrue (restoredMixedResult[0 ][0 ] == 1 )
275+ assertTrue (restoredMixedResult[0 ][1 ] == " Alice" )
276+ } finally {
277+ // Clean up cross-DB test resources
278+ sql " DROP DATABASE IF EXISTS ${ baseDbName} FORCE"
279+ sql " DROP DATABASE IF EXISTS ${ viewDbName} FORCE"
280+ sql " DROP DATABASE IF EXISTS ${ restoreDbName} FORCE"
281+ }
282+
283+ // Clean up original test resources
131284 sql " DROP TABLE ${ dbName} .${ tableName} FORCE"
132285 sql " DROP VIEW ${ dbName} .${ viewName} "
133286 sql " DROP DATABASE ${ dbName} FORCE"
0 commit comments