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