@@ -11077,3 +11077,119 @@ func TestBackupIndexCreatedAfterBackup(t *testing.T) {
1107711077 require .NoError (t , err )
1107811078 require .Len (t , files , 5 )
1107911079}
11080+
11081+ // TestBackupRestoreFunctionDependenciesRevisionHistory tests that revision
11082+ // history backups and restores correctly handle function dependencies.
11083+ func TestBackupRestoreFunctionDependenciesRevisionHistory (t * testing.T ) {
11084+ defer leaktest .AfterTest (t )()
11085+ defer log .Scope (t ).Close (t )
11086+
11087+ const numAccounts = 0
11088+ _ , sqlDB , _ , cleanupFn := backupRestoreTestSetup (t , singleNode , numAccounts , InitManualReplication )
11089+ defer cleanupFn ()
11090+
11091+ // Helper function to check which functions exist in a database.
11092+ checkFunctions := func (dbName string , expectedFuncs ... string ) {
11093+ var expected [][]string
11094+ for _ , fn := range expectedFuncs {
11095+ expected = append (expected , []string {fn })
11096+ }
11097+ if len (expected ) > 0 {
11098+ sqlDB .CheckQueryResults (t ,
11099+ fmt .Sprintf (`SELECT function_name FROM crdb_internal.create_function_statements WHERE database_name = '%s' ORDER BY function_name` , dbName ),
11100+ expected )
11101+ } else {
11102+ sqlDB .CheckQueryResults (t ,
11103+ fmt .Sprintf (`SELECT count(*) FROM crdb_internal.create_function_statements WHERE database_name = '%s'` , dbName ),
11104+ [][]string {{"0" }})
11105+ }
11106+
11107+ }
11108+
11109+ // t0: Create database with parent and child functions where child depends on parent.
11110+ sqlDB .Exec (t , `CREATE DATABASE test_db` )
11111+ sqlDB .Exec (t , `USE test_db` )
11112+ sqlDB .Exec (t , `CREATE FUNCTION test_db.parent_func() RETURNS INT LANGUAGE SQL AS $$ SELECT 42 $$` )
11113+ sqlDB .Exec (t , `CREATE FUNCTION test_db.child_func() RETURNS INT LANGUAGE SQL AS $$ SELECT parent_func() + 1 $$` )
11114+
11115+ // Verify both functions exist.
11116+ checkFunctions ("test_db" , "child_func" , "parent_func" )
11117+
11118+ // T1: Full backup with revision history.
11119+ backupPath := "nodelocal://1/function_deps_backup"
11120+ sqlDB .Exec (t , `BACKUP DATABASE test_db INTO $1 WITH revision_history` , backupPath )
11121+
11122+ // T2: Capture timestamp after backup (when parent and child exist).
11123+ var t2 string
11124+ sqlDB .QueryRow (t , `SELECT cluster_logical_timestamp()` ).Scan (& t2 )
11125+
11126+ sqlDB .Exec (t , `DROP FUNCTION child_func` )
11127+ sqlDB .Exec (t , `CREATE FUNCTION test_db.child_func_2() RETURNS INT LANGUAGE SQL AS $$ SELECT parent_func() + 2 $$` )
11128+
11129+ checkFunctions ("test_db" , "child_func_2" , "parent_func" )
11130+
11131+ // T3: Capture timestamp after dropping child and creating child 2.
11132+ var t3 string
11133+ sqlDB .QueryRow (t , `SELECT cluster_logical_timestamp()` ).Scan (& t3 )
11134+
11135+ // Drop child 2 so it does not appear at time 4.
11136+ sqlDB .Exec (t , `DROP FUNCTION child_func_2` )
11137+
11138+ // T4: Incremental backup with revision history.
11139+ sqlDB .Exec (t , `BACKUP DATABASE test_db INTO LATEST IN $1 WITH revision_history` , backupPath )
11140+
11141+ // T5: Capture timestamp after incremental backup (parent and child 2).
11142+ var t5 string
11143+ sqlDB .QueryRow (t , `SELECT cluster_logical_timestamp()` ).Scan (& t5 )
11144+
11145+ sqlDB .Exec (t , `DROP FUNCTION parent_func` )
11146+
11147+ // Verify no functions exist.
11148+ checkFunctions ("test_db" )
11149+
11150+ // T6: Capture timestamp after dropping parent.
11151+ var t6 string
11152+ sqlDB .QueryRow (t , `SELECT cluster_logical_timestamp()` ).Scan (& t6 )
11153+
11154+ // T6.2: Take another incremental backup to capture parent function drop.
11155+ sqlDB .Exec (t , `BACKUP DATABASE test_db INTO LATEST IN $1 WITH revision_history` , backupPath )
11156+
11157+ // T7: Restore AOST t2 -> expect both parent and child functions.
11158+ restoreQuery := fmt .Sprintf (
11159+ "RESTORE DATABASE test_db FROM LATEST IN $1 AS OF SYSTEM TIME %s with new_db_name = test2" , t2 )
11160+ sqlDB .Exec (t , restoreQuery , backupPath )
11161+ sqlDB .Exec (t , `USE test2` )
11162+
11163+ checkFunctions ("test2" , "child_func" , "parent_func" )
11164+
11165+ sqlDB .CheckQueryResults (t , `SELECT child_func()` , [][]string {{"43" }})
11166+
11167+ // T8: Restore AOST t3 -> expect parent and child 2.
11168+ restoreQuery = fmt .Sprintf (
11169+ "RESTORE DATABASE test_db FROM LATEST IN $1 AS OF SYSTEM TIME %s with new_db_name = test3" , t3 )
11170+ sqlDB .Exec (t , restoreQuery , backupPath )
11171+ sqlDB .Exec (t , `USE test3` )
11172+
11173+ checkFunctions ("test3" , "child_func_2" , "parent_func" )
11174+
11175+ sqlDB .CheckQueryResults (t , `SELECT parent_func()` , [][]string {{"42" }})
11176+ sqlDB .CheckQueryResults (t , `SELECT child_func_2()` , [][]string {{"44" }})
11177+
11178+ // T9: Restore AOST t5 -> expect parent.
11179+ restoreQuery = fmt .Sprintf (
11180+ "RESTORE DATABASE test_db FROM LATEST IN $1 AS OF SYSTEM TIME %s with new_db_name = test5" , t5 )
11181+ sqlDB .Exec (t , restoreQuery , backupPath )
11182+ sqlDB .Exec (t , `USE test5` )
11183+
11184+ checkFunctions ("test5" , "parent_func" )
11185+
11186+ sqlDB .CheckQueryResults (t , `SELECT parent_func()` , [][]string {{"42" }})
11187+
11188+ // T10: Restore AOST t6 -> expect no functions.
11189+ restoreQuery = fmt .Sprintf (
11190+ "RESTORE DATABASE test_db FROM LATEST IN $1 AS OF SYSTEM TIME %s with new_db_name = test6" , t6 )
11191+ sqlDB .Exec (t , restoreQuery , backupPath )
11192+ sqlDB .Exec (t , `USE test6` )
11193+
11194+ checkFunctions ("test6" )
11195+ }
0 commit comments