Skip to content

Commit 0b18551

Browse files
craig[bot]spilchen
andcommitted
Merge #148107
148107: sql: prevent backref leak on self-referencing triggers r=spilchen a=spilchen Prior to this change, dropping a trigger on a table that had a self-referencing foreign key could leave a dangling back-reference to the table itself. This incorrect dependency caused DROP TABLE/SCHEMA/DATABASE to loop indefinitely. During depedency cleanup of DROP TRIGGER, we omit FKs when collecting the forward references. FKs can safely be omitted because they aren't tracked in the DependedOnBy relationship. They have thier own separate tracking in the table descriptor. Fixes #147981 Epic: none Release note: none Note: omitting a bug-fix release note because this is a problem only on master. Co-authored-by: Matt Spilchen <[email protected]>
2 parents cb987df + 03765dc commit 0b18551

File tree

5 files changed

+71
-15
lines changed

5 files changed

+71
-15
lines changed

pkg/ccl/logictestccl/testdata/logic_test/triggers

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4696,6 +4696,67 @@ DROP FUNCTION f1;
46964696

46974697
subtest end
46984698

4699+
subtest self_ref_and_fk
4700+
4701+
statement ok
4702+
CREATE SCHEMA sc1;
4703+
4704+
statement ok
4705+
CREATE TABLE sc1.tab1 (
4706+
col1 INT,
4707+
PRIMARY KEY (col1)
4708+
);
4709+
4710+
# Self-referencing FK
4711+
statement ok
4712+
ALTER TABLE sc1.tab1
4713+
ADD CONSTRAINT fk_self FOREIGN KEY (col1)
4714+
REFERENCES sc1.tab1 (col1)
4715+
ON DELETE CASCADE;
4716+
4717+
# Trigger function that selects from same table
4718+
statement ok
4719+
CREATE FUNCTION tf1() RETURNS TRIGGER AS $$
4720+
BEGIN
4721+
SELECT 1 FROM sc1.tab1;
4722+
RETURN NULL;
4723+
END;
4724+
$$ LANGUAGE PLpgSQL;
4725+
4726+
# Create the trigger that adds a forward and backward reference on sc1.tab1.
4727+
statement ok
4728+
CREATE TRIGGER tr1
4729+
BEFORE INSERT OR UPDATE ON sc1.tab1
4730+
FOR EACH ROW EXECUTE FUNCTION tf1();
4731+
4732+
# Drop the trigger. Prior to #147981, this used to leave the backref to sc1.tab1
4733+
# around causing an infinite loop during DROP SCHEMA.
4734+
statement ok
4735+
DROP TRIGGER tr1 ON sc1.tab1;
4736+
4737+
# This test doesn't run with the legacy schema config because CREATE/DROP
4738+
# trigger isn't implemented there. We enable legacy to run DROP DDL to verify
4739+
# the dependencies handling. We do that in a transaction that is rolled back so
4740+
# we can retry with the DSC.
4741+
let $use_decl_sc
4742+
SHOW use_declarative_schema_changer
4743+
4744+
statement ok
4745+
set use_declarative_schema_changer = 'off';
4746+
4747+
statement ok
4748+
BEGIN;
4749+
DROP SCHEMA sc1 CASCADE;
4750+
ROLLBACK;
4751+
4752+
statement ok
4753+
set use_declarative_schema_changer = $use_decl_sc;
4754+
4755+
statement ok
4756+
DROP SCHEMA sc1 CASCADE;
4757+
4758+
subtest end
4759+
46994760
# ==============================================================================
47004761
# Test information_schema.triggers and pg_catalog.pg_trigger
47014762
# ==============================================================================

pkg/sql/catalog/descriptor.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -641,10 +641,10 @@ type TableDescriptor interface {
641641
// is now deprecated.
642642
GetReplacementOf() descpb.TableDescriptor_Replacement
643643

644-
// GetAllReferencedTableIDs returns all relation IDs that this table
645-
// references. Table references can be from foreign keys, triggers, or via
646-
// direct references if the descriptor is a view.
647-
GetAllReferencedTableIDs() descpb.IDs
644+
// GetAllReferencedRelationIDsExceptFKs returns the IDs of all relations
645+
// this table depends on, excluding foreign key dependencies. Dependencies can
646+
// originate from triggers, policies, or direct references in views.
647+
GetAllReferencedRelationIDsExceptFKs() descpb.IDs
648648

649649
// GetAllReferencedTypeIDs returns all user defined type descriptor IDs that
650650
// this table references. It takes in a function that returns the TypeDescriptor

pkg/sql/catalog/tabledesc/structured.go

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -375,17 +375,10 @@ func ForEachExprStringInTableDesc(
375375
return nil
376376
}
377377

378-
// GetAllReferencedTableIDs implements the TableDescriptor interface.
379-
func (desc *wrapper) GetAllReferencedTableIDs() descpb.IDs {
378+
// GetAllReferencedRelationIDsExceptFKs implements the TableDescriptor interface.
379+
func (desc *wrapper) GetAllReferencedRelationIDsExceptFKs() descpb.IDs {
380380
var ids catalog.DescriptorIDSet
381381

382-
// Collect referenced table IDs in foreign keys.
383-
for _, fk := range desc.OutboundForeignKeys() {
384-
ids.Add(fk.GetReferencedTableID())
385-
}
386-
for _, fk := range desc.InboundForeignKeys() {
387-
ids.Add(fk.GetOriginTableID())
388-
}
389382
// Add trigger dependencies.
390383
for i := range desc.Triggers {
391384
ids = ids.Union(catalog.MakeDescriptorIDSet(desc.Triggers[i].DependsOn...))

pkg/sql/schemachanger/scexec/scmutationexec/references.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -689,7 +689,9 @@ func (i *immediateVisitor) UpdateTableBackReferencesInRelations(
689689
if err != nil {
690690
return err
691691
}
692-
forwardRefs := backRefTbl.GetAllReferencedTableIDs()
692+
// Collect all forward references from this table, excluding foreign keys.
693+
// Foreign key dependencies are not tracked via DependedOnBy, so we skip them here.
694+
forwardRefs := backRefTbl.GetAllReferencedRelationIDsExceptFKs()
693695
for _, ref := range op.RelationReferences {
694696
referenced, err := i.checkOutTable(ctx, ref.ID)
695697
if err != nil {

pkg/workload/schemachange/optype.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ var opWeights = []int{
315315
dropSchema: 1,
316316
dropSequence: 1,
317317
dropTable: 1,
318-
dropTrigger: 0, // TODO(147981): re-enable once we fix orphaning of backref in DROP TRIGGER
318+
dropTrigger: 1,
319319
dropView: 1,
320320
renameIndex: 1,
321321
renameSequence: 1,

0 commit comments

Comments
 (0)