Skip to content

Commit d3b81e1

Browse files
craig[bot]rafiss
andcommitted
Merge #149901
149901: backup/rewrite: handle UDF references from views when restoring r=rafiss a=rafiss When we added support for views referencing UDFs, we did not account for these references in the RESTORE logic. This patch adds the logic to rewrite those references when needed. No release note since this bug was not released. fixes #149782 Release note: None Co-authored-by: Rafi Shamim <[email protected]>
2 parents 1957a64 + 91e57b2 commit d3b81e1

File tree

4 files changed

+205
-6
lines changed

4 files changed

+205
-6
lines changed

pkg/backup/backup_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2984,7 +2984,7 @@ func TestBackupRestoreCrossTableReferences(t *testing.T) {
29842984
db := sqlutils.MakeSQLRunner(tc.Conns[0])
29852985
db.Exec(t, createStore)
29862986
db.ExpectErr(
2987-
t, `cannot restore view "early_customers" without restoring referenced table`,
2987+
t, `cannot restore view "early_customers" without restoring referenced object`,
29882988
`RESTORE TABLE store.early_customers FROM LATEST IN $1`, localFoo,
29892989
)
29902990
db.Exec(t, `RESTORE TABLE store.early_customers, store.customers, store.orders FROM LATEST IN $1`, localFoo)
@@ -3015,15 +3015,15 @@ func TestBackupRestoreCrossTableReferences(t *testing.T) {
30153015
db := sqlutils.MakeSQLRunner(tc.Conns[0])
30163016

30173017
db.ExpectErr(
3018-
t, `cannot restore view "ordercounts" without restoring referenced table`,
3018+
t, `cannot restore view "ordercounts" without restoring referenced object`,
30193019
`RESTORE DATABASE storestats FROM LATEST IN $1`, localFoo,
30203020
)
30213021

30223022
db.Exec(t, createStore)
30233023
db.Exec(t, createStoreStats)
30243024

30253025
db.ExpectErr(
3026-
t, `cannot restore view "ordercounts" without restoring referenced table`,
3026+
t, `cannot restore view "ordercounts" without restoring referenced object`,
30273027
`RESTORE TABLE storestats.ordercounts, store.customers FROM LATEST IN $1`, localFoo,
30283028
)
30293029

@@ -5404,7 +5404,7 @@ func TestBackupRestoreSequencesInViews(t *testing.T) {
54045404
sqlDB.Exec(t, `DROP VIEW v`)
54055405
// Restore v.
54065406
sqlDB.ExpectErr(
5407-
t, "pq: cannot restore view \"v\" without restoring referenced table \\(or \"skip_missing_views\" option\\)",
5407+
t, "pq: cannot restore view \"v\" without restoring referenced object \\(or \"skip_missing_views\" option\\)",
54085408
`RESTORE TABLE v FROM LATEST IN 'nodelocal://1/test/'`,
54095409
)
54105410
})

pkg/backup/restore_planning.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,22 +107,32 @@ var restoreCompactedBackups = settings.RegisterBoolSetting(
107107
func maybeFilterMissingViews(
108108
tablesByID map[descpb.ID]*tabledesc.Mutable,
109109
typesByID map[descpb.ID]*typedesc.Mutable,
110+
functionsByID map[descpb.ID]*funcdesc.Mutable,
110111
skipMissingViews bool,
112+
skipMissingUDFs bool,
111113
) (map[descpb.ID]*tabledesc.Mutable, error) {
112114
// Function that recursively determines whether a given table, if it is a
113115
// view, has valid dependencies. Dependencies are looked up in tablesByID.
116+
missingOnlyFunctionDeps := true
114117
var hasValidViewDependencies func(desc *tabledesc.Mutable) bool
115118
hasValidViewDependencies = func(desc *tabledesc.Mutable) bool {
116119
if !desc.IsView() {
117120
return true
118121
}
119122
for _, id := range desc.DependsOn {
120123
if depDesc, ok := tablesByID[id]; !ok || !hasValidViewDependencies(depDesc) {
124+
missingOnlyFunctionDeps = false
121125
return false
122126
}
123127
}
124128
for _, id := range desc.DependsOnTypes {
125129
if _, ok := typesByID[id]; !ok {
130+
missingOnlyFunctionDeps = false
131+
return false
132+
}
133+
}
134+
for _, id := range desc.DependsOnFunctions {
135+
if _, ok := functionsByID[id]; !ok {
126136
return false
127137
}
128138
}
@@ -135,8 +145,12 @@ func maybeFilterMissingViews(
135145
filteredTablesByID[id] = table
136146
} else {
137147
if !skipMissingViews {
148+
if skipMissingUDFs && missingOnlyFunctionDeps {
149+
// Skip this view since only function dependencies are missing.
150+
continue
151+
}
138152
return nil, errors.Errorf(
139-
"cannot restore view %q without restoring referenced table (or %q option)",
153+
"cannot restore view %q without restoring referenced object (or %q option)",
140154
table.Name, restoreOptSkipMissingViews,
141155
)
142156
}
@@ -1953,7 +1967,10 @@ func doRestorePlan(
19531967
filteredTablesByID, err := maybeFilterMissingViews(
19541968
tablesByID,
19551969
typesByID,
1956-
restoreStmt.Options.SkipMissingViews)
1970+
functionsByID,
1971+
restoreStmt.Options.SkipMissingViews,
1972+
restoreStmt.Options.SkipMissingUDFs,
1973+
)
19571974
if err != nil {
19581975
return err
19591976
}

pkg/backup/testdata/backup-restore/views

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,174 @@ db1_new.sc1.v1 CREATE VIEW sc1.v1 (
6666
a,
6767
enum1
6868
) AS SELECT a, 'Good':::sc1.enum1 FROM db1_new.sc1.tbl1;
69+
70+
# Test backing up and restoring a view that references a UDF.
71+
# Regression test for issue #149782.
72+
73+
subtest restore_table_view_with_skip_missing
74+
75+
exec-sql
76+
CREATE DATABASE udf_db;
77+
----
78+
79+
exec-sql
80+
USE udf_db;
81+
----
82+
83+
exec-sql
84+
CREATE FUNCTION f1(x INT) RETURNS INT AS $$
85+
BEGIN
86+
RETURN x + 1;
87+
END;
88+
$$ LANGUAGE PLpgSQL;
89+
----
90+
91+
exec-sql
92+
CREATE TABLE t (id INT PRIMARY KEY, name VARCHAR(256), money INT);
93+
----
94+
95+
exec-sql
96+
CREATE VIEW v AS SELECT id, name, f1(money) FROM t;
97+
----
98+
99+
exec-sql
100+
INSERT INTO t VALUES (1, 'test', 100);
101+
----
102+
103+
query-sql
104+
SELECT * FROM v;
105+
----
106+
1 test 101
107+
108+
exec-sql
109+
BACKUP DATABASE udf_db INTO 'nodelocal://1/test_udf/';
110+
----
111+
112+
exec-sql
113+
DROP VIEW v;
114+
----
115+
116+
exec-sql
117+
DROP TABLE t;
118+
----
119+
120+
exec-sql
121+
DROP FUNCTION f1;
122+
----
123+
124+
# This should fail without one of the skip_missing_* options.
125+
exec-sql expect-error-regex=(cannot restore view "v" without restoring referenced object \(or "skip_missing_views" option\))
126+
RESTORE TABLE udf_db.v, udf_db.t FROM LATEST IN 'nodelocal://1/test_udf/'
127+
----
128+
regex matches error
129+
130+
# With skip_missing_udfs option, the view should be skipped since the function is missing.
131+
exec-sql
132+
RESTORE TABLE udf_db.v, udf_db.t FROM LATEST IN 'nodelocal://1/test_udf/' WITH skip_missing_udfs;
133+
----
134+
135+
# Check that the view was skipped.
136+
query-sql
137+
SELECT count(*) FROM information_schema.views WHERE table_name = 'v';
138+
----
139+
0
140+
141+
# But the table should still be restored.
142+
query-sql
143+
SELECT * FROM t;
144+
----
145+
1 test 100
146+
147+
exec-sql
148+
DROP TABLE t;
149+
----
150+
151+
# With skip_missing_views option, the view should be skipped also.
152+
exec-sql
153+
RESTORE TABLE udf_db.v, udf_db.t FROM LATEST IN 'nodelocal://1/test_udf/' WITH skip_missing_views;
154+
----
155+
156+
# Check that the view was skipped.
157+
query-sql
158+
SELECT count(*) FROM information_schema.views WHERE table_name = 'v';
159+
----
160+
0
161+
162+
# But the table should still be restored.
163+
query-sql
164+
SELECT * FROM t;
165+
----
166+
1 test 100
167+
168+
exec-sql
169+
DROP TABLE t;
170+
----
171+
172+
exec-sql
173+
DROP DATABASE udf_db;
174+
----
175+
176+
subtest end
177+
178+
subtest restore_database_with_view_referencing_udf
179+
180+
exec-sql
181+
CREATE DATABASE udf_db2;
182+
----
183+
184+
exec-sql
185+
USE udf_db2;
186+
----
187+
188+
exec-sql
189+
CREATE FUNCTION f1(x INT) RETURNS INT AS $$
190+
BEGIN
191+
RETURN x + 1;
192+
END;
193+
$$ LANGUAGE PLpgSQL;
194+
----
195+
196+
exec-sql
197+
CREATE TABLE t (id INT PRIMARY KEY, name VARCHAR(256), money INT);
198+
----
199+
200+
exec-sql
201+
CREATE VIEW v AS SELECT id, name, f1(money) FROM t;
202+
----
203+
204+
exec-sql
205+
INSERT INTO t VALUES (1, 'test', 100);
206+
----
207+
208+
query-sql
209+
SELECT * FROM v;
210+
----
211+
1 test 101
212+
213+
exec-sql
214+
BACKUP DATABASE udf_db2 INTO 'nodelocal://1/test_udf2/';
215+
----
216+
217+
exec-sql
218+
DROP DATABASE udf_db2;
219+
----
220+
221+
# This should restore the database with proper references between the view and the function.
222+
exec-sql
223+
RESTORE DATABASE udf_db2 FROM LATEST IN 'nodelocal://1/test_udf2/' WITH new_db_name='udf_db2_new';
224+
----
225+
226+
exec-sql
227+
USE udf_db2_new;
228+
----
229+
230+
query-sql
231+
SELECT * FROM v;
232+
----
233+
1 test 101
234+
235+
exec-sql
236+
DROP DATABASE udf_db2_new;
237+
----
238+
239+
subtest end

pkg/sql/catalog/rewrite/rewrite.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,17 @@ func TableDescs(
218218
table.Name, dest)
219219
}
220220
}
221+
for i, dest := range table.DependsOnFunctions {
222+
if depRewrite, ok := descriptorRewrites[dest]; ok {
223+
table.DependsOnFunctions[i] = depRewrite.ID
224+
} else {
225+
// If skipMissingUDFs is set, views with missing function dependencies
226+
// should have been filtered out in maybeFilterMissingViews.
227+
return nil, errors.AssertionFailedf(
228+
"cannot restore %q because referenced function %d was not found",
229+
table.Name, dest)
230+
}
231+
}
221232
origRefs := table.DependedOnBy
222233
table.DependedOnBy = nil
223234
for _, ref := range origRefs {

0 commit comments

Comments
 (0)