Skip to content

Commit e7f20b6

Browse files
authored
Merge pull request #156608 from msbutler/blathers/backport-release-25.4-156397
release-25.4: backup: capture function descriptor changes in revision history backups
2 parents 88bdf31 + bba837c commit e7f20b6

File tree

3 files changed

+127
-3
lines changed

3 files changed

+127
-3
lines changed

pkg/backup/backup_test.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

pkg/backup/backupinfo/manifest_handling.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1156,11 +1156,15 @@ func LoadSQLDescsFromBackupsAtTime(
11561156
asOf = lastBackupManifest.EndTime
11571157
}
11581158

1159-
for _, b := range backupManifests {
1159+
// TODO(msbutler): this logic can be removed because we already crop backup
1160+
// manifests that are too new. Keeping this around in case there is a bug
1161+
// upstream.
1162+
for i, b := range backupManifests {
11601163
if asOf.Less(b.StartTime) {
11611164
break
11621165
}
11631166
lastBackupManifest = b
1167+
lastIterFactory = layerToBackupManifestFileIterFactory[i]
11641168
}
11651169

11661170
// From this point on we try to load descriptors based on descriptor

pkg/backup/targets.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,12 @@ func getRelevantDescChanges(
126126
}
127127
for _, i := range starting {
128128
switch desc := i.(type) {
129-
case catalog.TableDescriptor, catalog.TypeDescriptor, catalog.SchemaDescriptor:
129+
case catalog.TableDescriptor, catalog.TypeDescriptor, catalog.SchemaDescriptor, catalog.FunctionDescriptor:
130130
// We need to add to interestingIDs so that if we later see a delete for
131131
// this ID we still know it is interesting to us, even though we will not
132132
// have a parentID at that point (since the delete is a nil desc).
133+
//
134+
// In other words, descriptor exists at start time, but not at end time.
133135
if _, ok := interestingParents[desc.GetParentID()]; ok {
134136
interestingIDs[desc.GetID()] = struct{}{}
135137
}
@@ -159,7 +161,9 @@ func getRelevantDescChanges(
159161
} else if change.Desc != nil {
160162
desc := backupinfo.NewDescriptorForManifest(change.Desc)
161163
switch desc := desc.(type) {
162-
case catalog.TableDescriptor, catalog.TypeDescriptor, catalog.SchemaDescriptor:
164+
case catalog.TableDescriptor, catalog.TypeDescriptor, catalog.SchemaDescriptor, catalog.FunctionDescriptor:
165+
// In other words, does not exist at start or end time, but within the
166+
// interval.
163167
if _, ok := interestingParents[desc.GetParentID()]; ok {
164168
interestingIDs[desc.GetID()] = struct{}{}
165169
interestingChanges = append(interestingChanges, change)

0 commit comments

Comments
 (0)