From f3ff553e5ac565130aa14888d7a2b573992bbacc Mon Sep 17 00:00:00 2001 From: Drew Kimball Date: Mon, 18 Nov 2024 17:33:48 -0700 Subject: [PATCH] sql: return "unimplemented" errors for unsupported trigger syntax This commit adds "unimplemented" errors to prevent usage of currently unsupported `CREATE TRIGGER` syntax. For example, statement-level triggers are not yet supported. Informs #126362 Informs #126363 Informs #135655 Informs #135657 Informs #135656 Informs #135658 Fixes #135131 Release note: None --- .../logictestccl/testdata/logic_test/triggers | 41 +++++++++++++------ pkg/sql/opt/optbuilder/create_trigger.go | 41 +++++++++++++++++++ .../opt/optbuilder/testdata/create_trigger | 5 +-- .../scbuild/testdata/create_trigger | 29 ++----------- 4 files changed, 76 insertions(+), 40 deletions(-) diff --git a/pkg/ccl/logictestccl/testdata/logic_test/triggers b/pkg/ccl/logictestccl/testdata/logic_test/triggers index ac7ce7d5547b..fe763bfd6c5d 100644 --- a/pkg/ccl/logictestccl/testdata/logic_test/triggers +++ b/pkg/ccl/logictestccl/testdata/logic_test/triggers @@ -580,21 +580,22 @@ subtest when_clause # The WHEN clause must be of type BOOL. statement error pgcode 42804 pq: argument of WHEN must be type bool, not type int -CREATE TRIGGER foo AFTER INSERT ON xy WHEN (1) EXECUTE FUNCTION f(); +CREATE TRIGGER foo AFTER INSERT ON xy FOR EACH ROW WHEN (1) EXECUTE FUNCTION f(); # The WHEN clause cannot reference table columns. statement error pgcode 42703 pq: column "x" does not exist\nHINT: column references in a trigger WHEN clause must be prefixed with NEW or OLD -CREATE TRIGGER foo AFTER INSERT ON xy WHEN (x = 1) EXECUTE FUNCTION f(); +CREATE TRIGGER foo AFTER INSERT ON xy FOR EACH ROW WHEN (x = 1) EXECUTE FUNCTION f(); # The WHEN clause cannot contain a subquery. statement error pgcode 0A000 pq: subqueries are not allowed in WHEN -CREATE TRIGGER foo AFTER INSERT ON xy WHEN (SELECT 1) EXECUTE FUNCTION f(); +CREATE TRIGGER foo AFTER INSERT ON xy FOR EACH ROW WHEN (SELECT 1) EXECUTE FUNCTION f(); -statement error pgcode 42P17 pq: statement trigger's WHEN condition cannot reference column values -CREATE TRIGGER foo AFTER INSERT ON xy WHEN (NEW IS NULL) EXECUTE FUNCTION f(); - -statement error pgcode 42P17 pq: statement trigger's WHEN condition cannot reference column values -CREATE TRIGGER foo AFTER INSERT ON xy WHEN (OLD IS NULL) EXECUTE FUNCTION f(); +# TODO(#126362): uncomment these test cases. +# statement error pgcode 42P17 pq: statement trigger's WHEN condition cannot reference column values +# CREATE TRIGGER foo AFTER INSERT ON xy WHEN (NEW IS NULL) EXECUTE FUNCTION f(); +# +# statement error pgcode 42P17 pq: statement trigger's WHEN condition cannot reference column values +# CREATE TRIGGER foo AFTER INSERT ON xy WHEN (OLD IS NULL) EXECUTE FUNCTION f(); statement error pgcode 42P17 pq: DELETE trigger's WHEN condition cannot reference NEW values CREATE TRIGGER foo AFTER DELETE ON xy FOR EACH ROW WHEN (NEW IS NULL) EXECUTE FUNCTION f(); @@ -622,7 +623,7 @@ CREATE FUNCTION g() RETURNS TRIGGER AS $$ $$ LANGUAGE PLpgSQL; statement error pgcode 42P01 pq: relation "nonexistent" does not exist -CREATE TRIGGER foo BEFORE INSERT ON xy EXECUTE FUNCTION g(); +CREATE TRIGGER foo BEFORE INSERT ON xy FOR EACH ROW EXECUTE FUNCTION g(); statement ok DROP FUNCTION g; @@ -650,7 +651,7 @@ CREATE FUNCTION g() RETURNS TRIGGER AS $$ $$ LANGUAGE PLpgSQL; statement error pgcode 42883 pq: unknown function: f_nonexistent() -CREATE TRIGGER foo AFTER DELETE ON xy EXECUTE FUNCTION g(); +CREATE TRIGGER foo AFTER DELETE ON xy FOR EACH ROW EXECUTE FUNCTION g(); # Case with a nonexistent type reference. statement ok @@ -662,7 +663,7 @@ CREATE FUNCTION g() RETURNS TRIGGER AS $$ $$ LANGUAGE PLpgSQL; statement error pgcode 42704 pq: type "typ_nonexistent" does not exist -CREATE TRIGGER foo BEFORE INSERT ON xy EXECUTE FUNCTION g(); +CREATE TRIGGER foo BEFORE INSERT ON xy FOR EACH ROW EXECUTE FUNCTION g(); # Incorrect type in a SQL expression. statement ok @@ -691,7 +692,7 @@ CREATE FUNCTION g() RETURNS TRIGGER AS $$ $$ LANGUAGE PLpgSQL; statement error pgcode 0A000 pq: unimplemented: CREATE TABLE usage inside a function definition -CREATE TRIGGER foo AFTER DELETE ON xy EXECUTE FUNCTION g(); +CREATE TRIGGER foo AFTER DELETE ON xy FOR EACH ROW EXECUTE FUNCTION g(); statement ok DROP FUNCTION g; @@ -3477,6 +3478,22 @@ CREATE OR REPLACE TRIGGER foo AFTER INSERT ON xy FOR EACH ROW EXECUTE FUNCTION f statement error pgcode 0A000 pq: unimplemented: cascade dropping triggers DROP TRIGGER foo ON xy CASCADE; +statement error pgcode 0A000 pq: unimplemented: statement-level triggers are not yet supported +CREATE TRIGGER foo AFTER INSERT ON xy FOR EACH STATEMENT EXECUTE FUNCTION f(); + +statement error pgcode 0A000 pq: unimplemented: INSTEAD OF triggers are not yet supported +CREATE TRIGGER foo INSTEAD OF INSERT ON v FOR EACH ROW EXECUTE FUNCTION f(); + +statement error pgcode 0A000 pq: unimplemented: REFERENCING clause is not yet supported for triggers +CREATE TRIGGER foo AFTER INSERT ON xy REFERENCING NEW TABLE AS nt FOR EACH ROW EXECUTE FUNCTION f(); + +# TODO(#126362): uncomment this case. +# statement error pgcode 0A000 pq: unimplemented: TRUNCATE triggers are not yet supported +# CREATE TRIGGER foo AFTER TRUNCATE ON xy FOR EACH STATEMENT EXECUTE FUNCTION f(); + +statement error pgcode 0A000 pq: unimplemented: column lists are not yet supported for triggers +CREATE TRIGGER foo AFTER UPDATE OF y ON xy FOR EACH ROW EXECUTE FUNCTION f(); + statement ok CREATE TRIGGER foo AFTER INSERT ON xy FOR EACH ROW EXECUTE FUNCTION f(); diff --git a/pkg/sql/opt/optbuilder/create_trigger.go b/pkg/sql/opt/optbuilder/create_trigger.go index 66897870934f..15e88431b0d4 100644 --- a/pkg/sql/opt/optbuilder/create_trigger.go +++ b/pkg/sql/opt/optbuilder/create_trigger.go @@ -74,6 +74,9 @@ func (b *Builder) buildCreateTrigger(ct *tree.CreateTrigger, inScope *scope) (ou panic(err) } + // Check for unsupported CREATE TRIGGER statements. + checkUnsupportedCreateTrigger(ct, ds) + // Lookup the implicit table type. This must happen after the above checks, // since virtual/system tables do not have an implicit type. typeID := typedesc.TableIDToImplicitTypeOID(descpb.ID(ds.ID())) @@ -326,3 +329,41 @@ var triggerFuncStaticParams = []routineParam{ const triggerColNew = "new" const triggerColOld = "old" + +func checkUnsupportedCreateTrigger(ct *tree.CreateTrigger, ds cat.DataSource) { + if ct.ForEach == tree.TriggerForEachStatement { + panic(unimplementedStatementLevelErr) + } + if ct.ActionTime == tree.TriggerActionTimeInsteadOf { + panic(unimplementedInsteadOfErr) + } + if len(ct.Transitions) > 0 { + panic(unimplementedReferencingErr) + } + for _, event := range ct.Events { + if event.EventType == tree.TriggerEventTruncate { + panic(unimplementedTruncateErr) + } + if len(event.Columns) > 0 { + panic(unimplementedColumnListErr) + } + } + if _, ok := ds.(cat.View); ok { + panic(unimplementedViewTriggerErr) + } +} + +var ( + unimplementedStatementLevelErr = unimplemented.NewWithIssue(126362, + "statement-level triggers are not yet supported") + unimplementedInsteadOfErr = unimplemented.NewWithIssue(126363, + "INSTEAD OF triggers are not yet supported") + unimplementedReferencingErr = unimplemented.NewWithIssue(135655, + "REFERENCING clause is not yet supported for triggers") + unimplementedTruncateErr = unimplemented.NewWithIssue(135657, + "TRUNCATE triggers are not yet supported") + unimplementedColumnListErr = unimplemented.NewWithIssue(135656, + "column lists are not yet supported for triggers") + unimplementedViewTriggerErr = unimplemented.NewWithIssue(135658, + "triggers on views are not yet supported") +) diff --git a/pkg/sql/opt/optbuilder/testdata/create_trigger b/pkg/sql/opt/optbuilder/testdata/create_trigger index 5f1d88857b24..eaa78cd7290d 100644 --- a/pkg/sql/opt/optbuilder/testdata/create_trigger +++ b/pkg/sql/opt/optbuilder/testdata/create_trigger @@ -22,12 +22,11 @@ create-trigger ├── CREATE TRIGGER tr BEFORE INSERT OR UPDATE ON xy FOR EACH ROW EXECUTE FUNCTION f_basic() └── no dependencies +# TODO(#126362, #135655): implement this case. build CREATE TRIGGER foo AFTER DELETE ON xy REFERENCING OLD TABLE AS foo WHEN (1 = 1) EXECUTE FUNCTION f_basic(); ---- -create-trigger - ├── CREATE TRIGGER foo AFTER DELETE ON xy REFERENCING OLD TABLE AS foo FOR EACH STATEMENT WHEN (1 = 1) EXECUTE FUNCTION f_basic() - └── no dependencies +error (0A000): unimplemented: statement-level triggers are not yet supported build CREATE TRIGGER foo AFTER INSERT ON xy FOR EACH ROW EXECUTE FUNCTION f_basic(); diff --git a/pkg/sql/schemachanger/scbuild/testdata/create_trigger b/pkg/sql/schemachanger/scbuild/testdata/create_trigger index 3c9e50083cf1..8a9f3761fbcc 100644 --- a/pkg/sql/schemachanger/scbuild/testdata/create_trigger +++ b/pkg/sql/schemachanger/scbuild/testdata/create_trigger @@ -37,28 +37,7 @@ CREATE TRIGGER tr BEFORE INSERT OR UPDATE ON xy FOR EACH ROW EXECUTE FUNCTION f( - [[TriggerDeps:{DescID: 104, TriggerID: 1}, PUBLIC], ABSENT] {tableId: 104, triggerId: 1, usesRelationIds: [105, 108], usesRoutineIds: [109, 110], usesTypeIds: [106, 107]} -build -CREATE TRIGGER tr AFTER DELETE ON xy REFERENCING OLD TABLE AS foo WHEN (1 = 1) EXECUTE FUNCTION f('a', 'bc'); ----- -- [[IndexData:{DescID: 104, IndexID: 1}, PUBLIC], PUBLIC] - {indexId: 1, tableId: 104} -- [[TableData:{DescID: 104, ReferencedDescID: 100}, PUBLIC], PUBLIC] - {databaseId: 100, tableId: 104} -- [[Trigger:{DescID: 104, TriggerID: 1}, PUBLIC], ABSENT] - {tableId: 104, triggerId: 1} -- [[TriggerName:{DescID: 104, TriggerID: 1}, PUBLIC], ABSENT] - {name: tr, tableId: 104, triggerId: 1} -- [[TriggerEnabled:{DescID: 104, TriggerID: 1}, PUBLIC], ABSENT] - {enabled: true, tableId: 104, triggerId: 1} -- [[TriggerTiming:{DescID: 104, TriggerID: 1}, PUBLIC], ABSENT] - {actionTime: AFTER, tableId: 104, triggerId: 1} -- [[TriggerEvents:{DescID: 104, TriggerID: 1}, PUBLIC], ABSENT] - {events: [{columnNames: [], type: DELETE}], tableId: 104, triggerId: 1} -- [[TriggerTransition:{DescID: 104, TriggerID: 1}, PUBLIC], ABSENT] - {oldTransitionAlias: foo, tableId: 104, triggerId: 1} -- [[TriggerWhen:{DescID: 104, TriggerID: 1}, PUBLIC], ABSENT] - {tableId: 104, triggerId: 1, whenExpr: '(1:::INT8 = 1:::INT8)'} -- [[TriggerFunctionCall:{DescID: 104, TriggerID: 1}, PUBLIC], ABSENT] - {funcArgs: [a, bc], funcBody: "DECLARE\nfoo @100106 := 'a';\nBEGIN\nINSERT INTO defaultdb.public.ab VALUES ((new).x, (new).y);\nRAISE NOTICE '% %', public.g(), nextval(108:::REGCLASS);\nRETURN new;\nEND;\n", funcId: 110, tableId: 104, triggerId: 1} -- [[TriggerDeps:{DescID: 104, TriggerID: 1}, PUBLIC], ABSENT] - {tableId: 104, triggerId: 1, usesRelationIds: [105, 108], usesRoutineIds: [109, 110], usesTypeIds: [106, 107]} +# TODO(#126362, #135655): uncomment this test case. +# build +# CREATE TRIGGER tr AFTER DELETE ON xy REFERENCING OLD TABLE AS foo WHEN (1 = 1) EXECUTE FUNCTION f('a', 'bc'); +# ----