Skip to content

Commit 679f10b

Browse files
craig[bot]DrewKimball
andcommitted
Merge #148616
148616: sql: support referencing a routine from a view query r=rafiss,rytaft,mgartner a=DrewKimball #### sql,schemachanger: add remaining handling for routine references in views This commit adds the remaining logic needed to keep track of references to routines from a view query in the schema changer. The following commit will resolve these references upon planning the `CREATE VIEW` statement and pass them to the schema changer. Informs #146123 Release note: None #### sql: support referencing a routine from a view query This commit adds support for invoking a UDF from a view (materialized or otherwise). This requires resolving routine references while planning the `CREATE VIEW` statement and passing them to the schema changer. Fixes #146123 Release note (sql change): Added support for invoking a UDF from a view query. Renaming or setting the schema on the routine is currently not allowed if it is referenced by a view. Co-authored-by: Drew Kimball <[email protected]>
2 parents 85c9415 + 4a03f79 commit 679f10b

File tree

28 files changed

+586
-101
lines changed

28 files changed

+586
-101
lines changed

pkg/sql/alter_function.go

Lines changed: 50 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -210,32 +210,22 @@ func (n *alterFunctionRenameNode) startExec(params runParams) error {
210210
}
211211
}
212212

213-
// Disallow renaming if this rename operation will break other UDF's invoking
214-
// this one.
215-
var dependentFuncs []string
216-
for _, dep := range fnDesc.GetDependedOnBy() {
217-
desc, err := params.p.Descriptors().ByIDWithoutLeased(params.p.Txn()).Get().Desc(params.ctx, dep.ID)
218-
if err != nil {
219-
return err
220-
}
221-
_, ok := desc.(catalog.FunctionDescriptor)
222-
if !ok {
223-
continue
224-
}
225-
fullyResolvedName, err := params.p.GetQualifiedFunctionNameByID(params.ctx, int64(dep.ID))
226-
if err != nil {
227-
return err
228-
}
229-
dependentFuncs = append(dependentFuncs, fullyResolvedName.FQString())
213+
// Disallow renaming if this rename operation will break other UDF's or views
214+
// invoking this one.
215+
dependentObjects, err := getFuncRefsDisallowingAlter(params, fnDesc)
216+
if err != nil {
217+
return err
230218
}
231-
if len(dependentFuncs) > 0 {
219+
if len(dependentObjects) > 0 {
220+
// TODO(#146475): once functions are rewritten to use ID references, we can
221+
// allow renames for views.
232222
return errors.UnimplementedErrorf(
233223
errors.IssueLink{
234224
IssueURL: build.MakeIssueURL(83233),
235225
Detail: "renames are disallowed because references are by name",
236226
},
237-
"cannot rename function %q because other functions ([%v]) still depend on it",
238-
fnDesc.Name, strings.Join(dependentFuncs, ", "))
227+
"cannot rename function %q because other functions or views ([%v]) still depend on it",
228+
fnDesc.Name, strings.Join(dependentObjects, ", "))
239229
}
240230

241231
scDesc.RemoveFunction(fnDesc.GetName(), fnDesc.GetID())
@@ -383,33 +373,21 @@ func (n *alterFunctionSetSchemaNode) startExec(params runParams) error {
383373
if err != nil {
384374
return err
385375
}
386-
// Disallow renaming if this rename operation will break other UDF's invoking
387-
// this one.
388-
var dependentFuncs []string
389-
for _, dep := range fnDesc.GetDependedOnBy() {
390-
desc, err := params.p.Descriptors().ByIDWithoutLeased(params.p.Txn()).Get().Desc(params.ctx, dep.ID)
391-
if err != nil {
392-
return err
393-
}
394-
_, ok := desc.(catalog.FunctionDescriptor)
395-
if !ok {
396-
continue
397-
}
398-
fullyResolvedName, err := params.p.GetQualifiedFunctionNameByID(params.ctx, int64(dep.ID))
399-
if err != nil {
400-
return err
401-
}
402-
dependentFuncs = append(dependentFuncs, fullyResolvedName.FQString())
376+
// Disallow renaming if this rename operation will break other UDF's or views
377+
// invoking this one.
378+
dependentObjects, err := getFuncRefsDisallowingAlter(params, fnDesc)
379+
if err != nil {
380+
return err
403381
}
404-
if len(dependentFuncs) > 0 {
382+
if len(dependentObjects) > 0 {
405383
return errors.UnimplementedErrorf(
406384
errors.IssueLink{
407385
IssueURL: build.MakeIssueURL(83233),
408386
Detail: "set schema is disallowed because there are references from " +
409387
"other objects by name",
410388
},
411-
"cannot set schema for function %q because other functions ([%v]) still depend on it",
412-
fnDesc.Name, strings.Join(dependentFuncs, ", "))
389+
"cannot set schema for function %q because other functions or views ([%v]) still depend on it",
390+
fnDesc.Name, strings.Join(dependentObjects, ", "))
413391
}
414392

415393
switch sc.SchemaKind() {
@@ -548,3 +526,35 @@ func toSchemaOverloadSignature(fnDesc *funcdesc.Mutable) descpb.SchemaDescriptor
548526
}
549527
return ret
550528
}
529+
530+
func getFuncRefsDisallowingAlter(
531+
params runParams, fnDesc *funcdesc.Mutable,
532+
) (dependentObjects []string, err error) {
533+
for _, dep := range fnDesc.GetDependedOnBy() {
534+
desc, err := params.p.Descriptors().ByIDWithoutLeased(params.p.Txn()).Get().Desc(params.ctx, dep.ID)
535+
if err != nil {
536+
return nil, err
537+
}
538+
switch t := desc.(type) {
539+
case catalog.TableDescriptor:
540+
if !t.IsView() {
541+
continue
542+
}
543+
fullyResolvedName, err := params.p.GetQualifiedTableNameByID(
544+
params.ctx, int64(dep.ID), tree.ResolveAnyTableKind)
545+
if err != nil {
546+
return nil, err
547+
}
548+
dependentObjects = append(dependentObjects, fullyResolvedName.FQString())
549+
case catalog.FunctionDescriptor:
550+
fullyResolvedName, err := params.p.GetQualifiedFunctionNameByID(params.ctx, int64(dep.ID))
551+
if err != nil {
552+
return nil, err
553+
}
554+
dependentObjects = append(dependentObjects, fullyResolvedName.FQString())
555+
default:
556+
continue
557+
}
558+
}
559+
return dependentObjects, nil
560+
}

pkg/sql/catalog/descpb/structured.proto

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1838,6 +1838,9 @@ message FunctionDescriptor {
18381838
(gogoproto.casttype) = "TriggerID"];
18391839
repeated uint32 policy_ids = 6 [(gogoproto.customname) = "PolicyIDs",
18401840
(gogoproto.casttype) = "PolicyID"];
1841+
// ViewQuery indicates that this function is referenced in the query of a
1842+
// view.
1843+
optional bool view_query = 7 [(gogoproto.nullable) = false];
18411844
}
18421845

18431846
optional string name = 1 [(gogoproto.nullable) = false];

pkg/sql/catalog/funcdesc/func_desc.go

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -890,6 +890,34 @@ func (desc *Mutable) AddIndexReference(id descpb.ID, indexID descpb.IndexID) err
890890
return nil
891891
}
892892

893+
// AddViewReference adds back reference to a view to the function.
894+
func (desc *Mutable) AddViewReference(id descpb.ID) error {
895+
for _, dep := range desc.DependsOn {
896+
if dep == id {
897+
return pgerror.Newf(pgcode.InvalidFunctionDefinition,
898+
"cannot add dependency from descriptor %d to function %s (%d) because there will be a dependency cycle", id, desc.GetName(), desc.GetID(),
899+
)
900+
}
901+
}
902+
for i := range desc.DependedOnBy {
903+
if desc.DependedOnBy[i].ID == id {
904+
desc.DependedOnBy[i].ViewQuery = true
905+
return nil
906+
}
907+
}
908+
desc.DependedOnBy = append(
909+
desc.DependedOnBy,
910+
descpb.FunctionDescriptor_Reference{
911+
ID: id,
912+
ViewQuery: true,
913+
},
914+
)
915+
sort.Slice(desc.DependedOnBy, func(i, j int) bool {
916+
return desc.DependedOnBy[i].ID < desc.DependedOnBy[j].ID
917+
})
918+
return nil
919+
}
920+
893921
// RemoveIndexReference removes back reference to an index from the function.
894922
func (desc *Mutable) RemoveIndexReference(id descpb.ID, indexID descpb.IndexID) {
895923
for i := range desc.DependedOnBy {
@@ -906,6 +934,17 @@ func (desc *Mutable) RemoveIndexReference(id descpb.ID, indexID descpb.IndexID)
906934
}
907935
}
908936

937+
// RemoveViewReference removes back reference to a view from the function.
938+
func (desc *Mutable) RemoveViewReference(id descpb.ID) {
939+
for i := range desc.DependedOnBy {
940+
if desc.DependedOnBy[i].ID == id {
941+
desc.DependedOnBy[i].ViewQuery = false
942+
desc.maybeRemoveTableReference(id)
943+
return
944+
}
945+
}
946+
}
947+
909948
// maybeRemoveTableReference removes a table's references from the function if
910949
// the column, index and constraint references are all empty. This function is
911950
// only used internally when removing an individual column, index or constraint
@@ -914,7 +953,8 @@ func (desc *Mutable) maybeRemoveTableReference(id descpb.ID) {
914953
var ret []descpb.FunctionDescriptor_Reference
915954
for _, ref := range desc.DependedOnBy {
916955
if ref.ID == id && len(ref.ColumnIDs) == 0 && len(ref.IndexIDs) == 0 &&
917-
len(ref.ConstraintIDs) == 0 && len(ref.TriggerIDs) == 0 && len(ref.PolicyIDs) == 0 {
956+
len(ref.ConstraintIDs) == 0 && len(ref.TriggerIDs) == 0 &&
957+
len(ref.PolicyIDs) == 0 && !ref.ViewQuery {
918958
continue
919959
}
920960
ret = append(ret, ref)

pkg/sql/create_view.go

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -299,12 +299,12 @@ func (n *createViewNode) startExec(params runParams) error {
299299
}
300300
desc.DependsOnTypes = append(desc.DependsOnTypes, orderedTypeDeps.Ordered()...)
301301

302-
// Collect all functions this view depends on.
303-
orderedFuncDeps := catalog.DescriptorIDSet{}
302+
// Collect all routines this view depends on.
303+
orderedRoutineDeps := catalog.DescriptorIDSet{}
304304
for backrefID := range n.funcDeps {
305-
orderedFuncDeps.Add(backrefID)
305+
orderedRoutineDeps.Add(backrefID)
306306
}
307-
desc.DependsOnFunctions = append(desc.DependsOnFunctions, orderedFuncDeps.Ordered()...)
307+
desc.DependsOnFunctions = append(desc.DependsOnFunctions, orderedRoutineDeps.Ordered()...)
308308

309309
newDesc = &desc
310310

@@ -357,6 +357,13 @@ func (n *createViewNode) startExec(params runParams) error {
357357
}
358358
}
359359

360+
// Add back references for the routine dependencies.
361+
for id := range n.funcDeps {
362+
if err := params.p.addRoutineViewBackReference(params.ctx, id, newDesc.ID); err != nil {
363+
return err
364+
}
365+
}
366+
360367
if err := validateDescriptor(params.ctx, params.p, newDesc); err != nil {
361368
return err
362369
}
@@ -796,6 +803,18 @@ func (p *planner) replaceViewDesc(
796803
return nil, err
797804
}
798805

806+
// For each old function dependency (i.e. before replacing the view),
807+
// see if we still depend on it. If not, then remove the back reference.
808+
var outdatedRoutineRefs []descpb.ID
809+
for _, id := range toReplace.DependsOnFunctions {
810+
if _, ok := n.funcDeps[id]; !ok {
811+
outdatedRoutineRefs = append(outdatedRoutineRefs, id)
812+
}
813+
}
814+
if err := p.removeRoutineViewBackReferences(ctx, outdatedRoutineRefs, toReplace.ID); err != nil {
815+
return nil, err
816+
}
817+
799818
// Since the view query has been replaced, the dependencies that this
800819
// table descriptor had are gone.
801820
toReplace.DependsOn = make([]descpb.ID, 0, len(n.planDeps))
@@ -806,6 +825,10 @@ func (p *planner) replaceViewDesc(
806825
for backrefID := range n.typeDeps {
807826
toReplace.DependsOnTypes = append(toReplace.DependsOnTypes, backrefID)
808827
}
828+
toReplace.DependsOnFunctions = make([]descpb.ID, 0, len(n.funcDeps))
829+
for backrefID := range n.funcDeps {
830+
toReplace.DependsOnFunctions = append(toReplace.DependsOnFunctions, backrefID)
831+
}
809832

810833
// Since we are replacing an existing view here, we need to write the new
811834
// descriptor into place.
@@ -907,3 +930,36 @@ func crossDBReferenceDeprecationHint() string {
907930
return fmt.Sprintf("Note that cross-database references will be removed in future releases. See: %s",
908931
docs.ReleaseNotesURL(`#deprecations`))
909932
}
933+
934+
func (p *planner) addRoutineViewBackReference(
935+
ctx context.Context, routineID descpb.ID, ref descpb.ID,
936+
) error {
937+
mutDesc, err := p.Descriptors().MutableByID(p.txn).Function(ctx, routineID)
938+
if err != nil {
939+
return err
940+
}
941+
942+
if err := mutDesc.AddViewReference(ref); err != nil {
943+
return err
944+
}
945+
if err := p.writeFuncSchemaChange(ctx, mutDesc); err != nil {
946+
return err
947+
}
948+
return nil
949+
}
950+
951+
func (p *planner) removeRoutineViewBackReferences(
952+
ctx context.Context, routineIDs []descpb.ID, ref descpb.ID,
953+
) error {
954+
for _, routineID := range routineIDs {
955+
mutDesc, err := p.Descriptors().MutableByID(p.txn).Function(ctx, routineID)
956+
if err != nil {
957+
return err
958+
}
959+
mutDesc.RemoveViewReference(ref)
960+
if err := p.writeFuncSchemaChange(ctx, mutDesc); err != nil {
961+
return err
962+
}
963+
}
964+
return nil
965+
}

pkg/sql/distsql_spec_exec_factory.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1580,6 +1580,7 @@ func (e *distSQLSpecExecFactory) ConstructCreateView(
15801580
columns colinfo.ResultColumns,
15811581
deps opt.SchemaDeps,
15821582
typeDeps opt.SchemaTypeDeps,
1583+
funcDeps opt.SchemaFunctionDeps,
15831584
) (exec.Node, error) {
15841585
return nil, unimplemented.NewWithIssue(47473, "experimental opt-driven distsql planning: create view")
15851586
}

pkg/sql/drop_view.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,12 @@ func (p *planner) dropViewImpl(
323323
return cascadeDroppedViews, err
324324
}
325325

326+
// Remove back-references from the routines this view depends on.
327+
routinesDependedOn := append([]descpb.ID(nil), viewDesc.DependsOnFunctions...)
328+
if err := p.removeRoutineViewBackReferences(ctx, routinesDependedOn, viewDesc.ID); err != nil {
329+
return cascadeDroppedViews, err
330+
}
331+
326332
if behavior == tree.DropCascade {
327333
dependedOnBy := append([]descpb.TableDescriptor_Reference(nil), viewDesc.DependedOnBy...)
328334
for _, ref := range dependedOnBy {

pkg/sql/logictest/testdata/logic_test/drop_function

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ CREATE SCHEMA altSchema;
301301
CREATE FUNCTION altSchema.f_called_by_b() RETURNS INT LANGUAGE SQL AS $$ SELECT 1 $$;
302302
CREATE TABLE t1_with_b_2_ref(j int default altSchema.f_called_by_b() CHECK (altSchema.f_called_by_b() > 0));
303303

304-
statement error pgcode 0A000 cannot set schema for function \"f_called_by_b\" because other functions \(\[test.public.f_called_by_b2\]\) still depend on it
304+
statement error pgcode 0A000 cannot set schema for function \"f_called_by_b\" because other functions or views \(\[test.public.f_called_by_b2\]\) still depend on it
305305
ALTER FUNCTION f_called_by_b SET SCHEMA altSchema;
306306

307307
skipif config local-legacy-schema-changer

0 commit comments

Comments
 (0)