diff --git a/pkg/ccl/logictestccl/testdata/logic_test/triggers b/pkg/ccl/logictestccl/testdata/logic_test/triggers new file mode 100644 index 000000000000..01d51a55adf0 --- /dev/null +++ b/pkg/ccl/logictestccl/testdata/logic_test/triggers @@ -0,0 +1,164 @@ +# TODO(#126356): remove this case when trigger functions are supported. +statement error pgcode 0A000 pq: unimplemented: trigger functions are not yet supported +CREATE FUNCTION f() RETURNS TRIGGER LANGUAGE PLpgSQL AS $$ BEGIN RETURN NULL; END $$; + +# ============================================================================== +# Trigger functions cannot be directly invoked. +# ============================================================================== + +subtest direct_invocation + +# TODO(#126356): uncomment these cases when trigger functions are supported. +#statement ok +#CREATE FUNCTION f() RETURNS TRIGGER LANGUAGE PLpgSQL AS $$ BEGIN RETURN NULL; END $$; +# +#statement error pgcode 0A000 pq: trigger functions can only be called as triggers +#SELECT f(); +# +#statement error pgcode 0A000 pq: trigger functions can only be called as triggers +#CREATE FUNCTION foo() RETURNS INT LANGUAGE SQL AS $$ SELECT f(); SELECT 1; $$; +# +#statement error pgcode 0A000 pq: trigger functions can only be called as triggers +#CREATE FUNCTION foo() RETURNS INT LANGUAGE PLpgSQL AS $$ BEGIN SELECT f(); RETURN 1; END $$; +# +#statement ok +#DROP FUNCTION f; + +# ============================================================================== +# Test invalid usage of parameters in trigger functions. +# ============================================================================== + +# Trigger functions are not allowed to be defined with parameters. Instead, +# arguments are passed through the implicitly defined TG_ARGV variable. +subtest parameters + +statement error pgcode 42P13 pq: trigger functions cannot have declared arguments +CREATE FUNCTION f(x TEXT) RETURNS TRIGGER LANGUAGE PLpgSQL AS $$ BEGIN RETURN NULL; END $$; + +statement error pgcode 42P13 pq: function result type must be string because of OUT parameters +CREATE FUNCTION f(OUT x TEXT) RETURNS TRIGGER LANGUAGE PLpgSQL AS $$ BEGIN RETURN NULL; END $$; + +statement error pgcode 42P13 pq: function result type must be string because of OUT parameters +CREATE FUNCTION f(INOUT x TEXT) RETURNS TRIGGER LANGUAGE PLpgSQL AS $$ BEGIN RETURN NULL; END $$; + +# ============================================================================== +# Test invalid usage of the TRIGGER datatype in PL/pgSQL routines. +# ============================================================================== + +subtest trigger_in_plpgsql_routine + +statement error pgcode 0A000 pq: cannot accept a value of type trigger +CREATE FUNCTION f() RETURNS RECORD LANGUAGE PLpgSQL AS $$ BEGIN RETURN NULL::TRIGGER; END $$; + +statement error pgcode 0A000 pq: PL/pgSQL functions cannot accept type trigger +CREATE FUNCTION f(x TRIGGER) RETURNS INT LANGUAGE PLpgSQL AS $$ BEGIN RETURN NULL; END $$; + +statement error pgcode 0A000 pq: PL/pgSQL functions cannot accept type trigger +CREATE FUNCTION f(OUT x TRIGGER) LANGUAGE PLpgSQL AS $$ BEGIN RETURN NULL; END $$; + +statement error pgcode 0A000 pq: PL/pgSQL functions cannot accept type trigger +CREATE FUNCTION f(INOUT x TRIGGER) RETURNS INT LANGUAGE PLpgSQL AS $$ BEGIN RETURN NULL; END $$; + +statement error pgcode 0A000 pq: PL/pgSQL functions cannot accept type trigger +CREATE PROCEDURE p(x TRIGGER) LANGUAGE PLpgSQL AS $$ BEGIN END $$; + +statement error pgcode 0A000 pq: PL/pgSQL functions cannot accept type trigger +CREATE PROCEDURE p(OUT x TRIGGER) LANGUAGE PLpgSQL AS $$ BEGIN END $$; + +statement error pgcode 0A000 pq: PL/pgSQL functions cannot accept type trigger +CREATE PROCEDURE p(INOUT x TRIGGER) LANGUAGE PLpgSQL AS $$ BEGIN END $$; + +# ============================================================================== +# Test invalid usage of the TRIGGER datatype in SQL routines. +# ============================================================================== + +subtest trigger_in_sql_routine + +statement error pgcode 0A000 pq: cannot accept a value of type trigger +CREATE FUNCTION f() RETURNS RECORD LANGUAGE SQL AS $$ SELECT NULL::TRIGGER; $$; + +statement error pgcode 42P13 pq: SQL functions cannot return type trigger +CREATE FUNCTION f() RETURNS TRIGGER LANGUAGE SQL AS $$ SELECT NULL $$; + +statement error pgcode 42P13 pq: SQL functions cannot have arguments of type trigger +CREATE FUNCTION f(x TRIGGER) RETURNS INT LANGUAGE SQL AS $$ SELECT NULL $$; + +statement error pgcode 42P13 pq: SQL functions cannot return type trigger +CREATE FUNCTION f(OUT x TRIGGER) LANGUAGE SQL AS $$ SELECT NULL $$; + +statement error pgcode 42P13 pq: SQL functions cannot return type trigger +CREATE FUNCTION f(INOUT x TRIGGER) RETURNS INT LANGUAGE SQL AS $$ SELECT NULL $$; + +statement error pgcode 42P13 pq: SQL functions cannot have arguments of type trigger +CREATE PROCEDURE p(x TRIGGER) LANGUAGE SQL AS $$ SELECT NULL $$; + +statement error pgcode 42P13 pq: SQL functions cannot return type trigger +CREATE PROCEDURE p(OUT x TRIGGER) LANGUAGE SQL AS $$ SELECT NULL $$; + +statement error pgcode 42P13 pq: SQL functions cannot return type trigger +CREATE PROCEDURE p(INOUT x TRIGGER) LANGUAGE SQL AS $$ SELECT NULL $$; + +# ============================================================================== +# Test invalid usage of the TRIGGER datatype in SQL statements. +# ============================================================================== + +subtest trigger_in_sql_statement + +# Cast. +statement error pgcode 0A000 pq: cannot accept a value of type trigger +SELECT NULL::TRIGGER; + +# Trigger array cast. +statement error pgcode 42704 pq: at or near "EOF": syntax error: type trigger\[\] does not exist +SELECT NULL::TRIGGER[]; + +# Invalid cast from integer. +statement error pgcode 42846 pq: invalid cast: int -> trigger +SELECT 1::TRIGGER; + +# Type annotation. +statement error pgcode 0A000 pq: cannot accept a value of type trigger +SELECT NULL:::TRIGGER; + +# Builtin type conversion function. +statement error pgcode 0A000 pq: cannot accept a value of type trigger +SELECT triggerin(1); + +# Builtin type conversion function with NULL argument. +# NOTE: this case succeeds because the function is not actually called +# or fully type-checked when it is supplied NULL arguments. +# statement error pgcode 0A000 pq: cannot accept a value of type trigger +statement ok +SELECT triggerin(NULL); + +# ============================================================================== +# Test invalid usage of the TRIGGER datatype in CREATE statements. +# ============================================================================== + +subtest trigger_in_create + +# Column type. +statement error pgcode 42P16 pq: value type trigger cannot be used for table columns +CREATE TABLE t (x INT, y TRIGGER, z TEXT); + +# Array column type. +statement error pgcode 42704 pq: at or near ",": syntax error: type trigger\[\] does not exist +CREATE TABLE t (x INT, y TRIGGER[], z TEXT); + +# Cast in partial index predicate. +statement error pgcode 0A000 pq: cannot accept a value of type trigger +CREATE TABLE t (x INT, y INT, INDEX (y) WHERE (NULL::TRIGGER IS NOT NULL)); + +# Cast in computed column expression. +statement error pgcode 0A000 pq: cannot accept a value of type trigger +CREATE TABLE t (x INT, y BOOL GENERATED ALWAYS AS (NULL::TRIGGER IS NOT NULL) STORED); + +# Trigger UDT field. +statement error pgcode 0A000 pq: cannot accept a value of type trigger +CREATE TYPE udt AS (x INT, y TRIGGER, z TEXT); + +# Trigger array UDT field. +statement error pgcode 42601 pq: at or near "\[": syntax error +CREATE TYPE udt AS (x INT, y TRIGGER[], z TEXT); + +subtest end diff --git a/pkg/ccl/logictestccl/tests/3node-tenant/generated_test.go b/pkg/ccl/logictestccl/tests/3node-tenant/generated_test.go index 77cd5ff6ff2a..05c384211198 100644 --- a/pkg/ccl/logictestccl/tests/3node-tenant/generated_test.go +++ b/pkg/ccl/logictestccl/tests/3node-tenant/generated_test.go @@ -2797,6 +2797,13 @@ func TestTenantLogicCCL_tenant_unsupported( runCCLLogicTest(t, "tenant_unsupported") } +func TestTenantLogicCCL_triggers( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runCCLLogicTest(t, "triggers") +} + func TestTenantLogicCCL_udf_params( t *testing.T, ) { diff --git a/pkg/ccl/logictestccl/tests/fakedist-disk/BUILD.bazel b/pkg/ccl/logictestccl/tests/fakedist-disk/BUILD.bazel index 5297e82de11f..e8bbc3647c4d 100644 --- a/pkg/ccl/logictestccl/tests/fakedist-disk/BUILD.bazel +++ b/pkg/ccl/logictestccl/tests/fakedist-disk/BUILD.bazel @@ -12,7 +12,7 @@ go_test( "//build/toolchains:is_heavy": {"test.Pool": "heavy"}, "//conditions:default": {"test.Pool": "large"}, }), - shard_count = 29, + shard_count = 30, tags = ["cpu:2"], deps = [ "//pkg/base", diff --git a/pkg/ccl/logictestccl/tests/fakedist-disk/generated_test.go b/pkg/ccl/logictestccl/tests/fakedist-disk/generated_test.go index d61025d5a97f..9e86a3af1b08 100644 --- a/pkg/ccl/logictestccl/tests/fakedist-disk/generated_test.go +++ b/pkg/ccl/logictestccl/tests/fakedist-disk/generated_test.go @@ -239,6 +239,13 @@ func TestCCLLogic_subject( runCCLLogicTest(t, "subject") } +func TestCCLLogic_triggers( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runCCLLogicTest(t, "triggers") +} + func TestCCLLogic_udf_params( t *testing.T, ) { diff --git a/pkg/ccl/logictestccl/tests/fakedist-vec-off/BUILD.bazel b/pkg/ccl/logictestccl/tests/fakedist-vec-off/BUILD.bazel index d63444795f09..ed20d166d24a 100644 --- a/pkg/ccl/logictestccl/tests/fakedist-vec-off/BUILD.bazel +++ b/pkg/ccl/logictestccl/tests/fakedist-vec-off/BUILD.bazel @@ -12,7 +12,7 @@ go_test( "//build/toolchains:is_heavy": {"test.Pool": "heavy"}, "//conditions:default": {"test.Pool": "large"}, }), - shard_count = 29, + shard_count = 30, tags = ["cpu:2"], deps = [ "//pkg/base", diff --git a/pkg/ccl/logictestccl/tests/fakedist-vec-off/generated_test.go b/pkg/ccl/logictestccl/tests/fakedist-vec-off/generated_test.go index c24a523488fc..d76c084448ad 100644 --- a/pkg/ccl/logictestccl/tests/fakedist-vec-off/generated_test.go +++ b/pkg/ccl/logictestccl/tests/fakedist-vec-off/generated_test.go @@ -239,6 +239,13 @@ func TestCCLLogic_subject( runCCLLogicTest(t, "subject") } +func TestCCLLogic_triggers( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runCCLLogicTest(t, "triggers") +} + func TestCCLLogic_udf_params( t *testing.T, ) { diff --git a/pkg/ccl/logictestccl/tests/fakedist/BUILD.bazel b/pkg/ccl/logictestccl/tests/fakedist/BUILD.bazel index 4c8d25f92413..2887729097b5 100644 --- a/pkg/ccl/logictestccl/tests/fakedist/BUILD.bazel +++ b/pkg/ccl/logictestccl/tests/fakedist/BUILD.bazel @@ -12,7 +12,7 @@ go_test( "//build/toolchains:is_heavy": {"test.Pool": "heavy"}, "//conditions:default": {"test.Pool": "large"}, }), - shard_count = 30, + shard_count = 31, tags = ["cpu:2"], deps = [ "//pkg/base", diff --git a/pkg/ccl/logictestccl/tests/fakedist/generated_test.go b/pkg/ccl/logictestccl/tests/fakedist/generated_test.go index bf73ec236736..687b1bad73b8 100644 --- a/pkg/ccl/logictestccl/tests/fakedist/generated_test.go +++ b/pkg/ccl/logictestccl/tests/fakedist/generated_test.go @@ -246,6 +246,13 @@ func TestCCLLogic_subject( runCCLLogicTest(t, "subject") } +func TestCCLLogic_triggers( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runCCLLogicTest(t, "triggers") +} + func TestCCLLogic_udf_params( t *testing.T, ) { diff --git a/pkg/ccl/logictestccl/tests/local-legacy-schema-changer/BUILD.bazel b/pkg/ccl/logictestccl/tests/local-legacy-schema-changer/BUILD.bazel index 2d52175c8146..229112f58c04 100644 --- a/pkg/ccl/logictestccl/tests/local-legacy-schema-changer/BUILD.bazel +++ b/pkg/ccl/logictestccl/tests/local-legacy-schema-changer/BUILD.bazel @@ -9,7 +9,7 @@ go_test( "//pkg/ccl/logictestccl:testdata", # keep ], exec_properties = {"test.Pool": "large"}, - shard_count = 29, + shard_count = 30, tags = ["cpu:1"], deps = [ "//pkg/base", diff --git a/pkg/ccl/logictestccl/tests/local-legacy-schema-changer/generated_test.go b/pkg/ccl/logictestccl/tests/local-legacy-schema-changer/generated_test.go index 5374bf1bf141..981632982992 100644 --- a/pkg/ccl/logictestccl/tests/local-legacy-schema-changer/generated_test.go +++ b/pkg/ccl/logictestccl/tests/local-legacy-schema-changer/generated_test.go @@ -239,6 +239,13 @@ func TestCCLLogic_subject( runCCLLogicTest(t, "subject") } +func TestCCLLogic_triggers( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runCCLLogicTest(t, "triggers") +} + func TestCCLLogic_udf_params( t *testing.T, ) { diff --git a/pkg/ccl/logictestccl/tests/local-mixed-23.2/BUILD.bazel b/pkg/ccl/logictestccl/tests/local-mixed-23.2/BUILD.bazel index 041eb4ecf92b..5dcbba9d6161 100644 --- a/pkg/ccl/logictestccl/tests/local-mixed-23.2/BUILD.bazel +++ b/pkg/ccl/logictestccl/tests/local-mixed-23.2/BUILD.bazel @@ -9,7 +9,7 @@ go_test( "//pkg/ccl/logictestccl:testdata", # keep ], exec_properties = {"test.Pool": "large"}, - shard_count = 25, + shard_count = 26, tags = ["cpu:1"], deps = [ "//pkg/base", diff --git a/pkg/ccl/logictestccl/tests/local-mixed-23.2/generated_test.go b/pkg/ccl/logictestccl/tests/local-mixed-23.2/generated_test.go index f275fbec36cf..b93e0049653d 100644 --- a/pkg/ccl/logictestccl/tests/local-mixed-23.2/generated_test.go +++ b/pkg/ccl/logictestccl/tests/local-mixed-23.2/generated_test.go @@ -218,6 +218,13 @@ func TestCCLLogic_show_create( runCCLLogicTest(t, "show_create") } +func TestCCLLogic_triggers( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runCCLLogicTest(t, "triggers") +} + func TestCCLLogic_udf_params( t *testing.T, ) { diff --git a/pkg/ccl/logictestccl/tests/local-read-committed/BUILD.bazel b/pkg/ccl/logictestccl/tests/local-read-committed/BUILD.bazel index 1ce636f6aa8d..e2e8bb3d5a71 100644 --- a/pkg/ccl/logictestccl/tests/local-read-committed/BUILD.bazel +++ b/pkg/ccl/logictestccl/tests/local-read-committed/BUILD.bazel @@ -10,7 +10,7 @@ go_test( "//pkg/sql/opt/exec/execbuilder:testdata", # keep ], exec_properties = {"test.Pool": "large"}, - shard_count = 36, + shard_count = 37, tags = ["cpu:1"], deps = [ "//pkg/base", diff --git a/pkg/ccl/logictestccl/tests/local-read-committed/generated_test.go b/pkg/ccl/logictestccl/tests/local-read-committed/generated_test.go index bd066c1e9173..1c01d0fc3a7a 100644 --- a/pkg/ccl/logictestccl/tests/local-read-committed/generated_test.go +++ b/pkg/ccl/logictestccl/tests/local-read-committed/generated_test.go @@ -266,6 +266,13 @@ func TestReadCommittedLogicCCL_subject( runCCLLogicTest(t, "subject") } +func TestReadCommittedLogicCCL_triggers( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runCCLLogicTest(t, "triggers") +} + func TestReadCommittedLogicCCL_udf_params( t *testing.T, ) { diff --git a/pkg/ccl/logictestccl/tests/local-vec-off/BUILD.bazel b/pkg/ccl/logictestccl/tests/local-vec-off/BUILD.bazel index 8f49d20f505c..2593a77e3775 100644 --- a/pkg/ccl/logictestccl/tests/local-vec-off/BUILD.bazel +++ b/pkg/ccl/logictestccl/tests/local-vec-off/BUILD.bazel @@ -9,7 +9,7 @@ go_test( "//pkg/ccl/logictestccl:testdata", # keep ], exec_properties = {"test.Pool": "large"}, - shard_count = 29, + shard_count = 30, tags = ["cpu:1"], deps = [ "//pkg/base", diff --git a/pkg/ccl/logictestccl/tests/local-vec-off/generated_test.go b/pkg/ccl/logictestccl/tests/local-vec-off/generated_test.go index 67c0c4b6610c..19d0536f1e68 100644 --- a/pkg/ccl/logictestccl/tests/local-vec-off/generated_test.go +++ b/pkg/ccl/logictestccl/tests/local-vec-off/generated_test.go @@ -239,6 +239,13 @@ func TestCCLLogic_subject( runCCLLogicTest(t, "subject") } +func TestCCLLogic_triggers( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runCCLLogicTest(t, "triggers") +} + func TestCCLLogic_udf_params( t *testing.T, ) { diff --git a/pkg/ccl/logictestccl/tests/local/BUILD.bazel b/pkg/ccl/logictestccl/tests/local/BUILD.bazel index 804e6ab9496c..035b110f5544 100644 --- a/pkg/ccl/logictestccl/tests/local/BUILD.bazel +++ b/pkg/ccl/logictestccl/tests/local/BUILD.bazel @@ -9,7 +9,7 @@ go_test( "//pkg/ccl/logictestccl:testdata", # keep ], exec_properties = {"test.Pool": "large"}, - shard_count = 46, + shard_count = 47, tags = ["cpu:1"], deps = [ "//pkg/base", diff --git a/pkg/ccl/logictestccl/tests/local/generated_test.go b/pkg/ccl/logictestccl/tests/local/generated_test.go index 4ec338491f50..a480f2d76de8 100644 --- a/pkg/ccl/logictestccl/tests/local/generated_test.go +++ b/pkg/ccl/logictestccl/tests/local/generated_test.go @@ -358,6 +358,13 @@ func TestCCLLogic_tenant_usage( runCCLLogicTest(t, "tenant_usage") } +func TestCCLLogic_triggers( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runCCLLogicTest(t, "triggers") +} + func TestCCLLogic_udf_params( t *testing.T, ) { diff --git a/pkg/sql/create_type.go b/pkg/sql/create_type.go index 6ea80fc75507..ff67d5fa5e6a 100644 --- a/pkg/sql/create_type.go +++ b/pkg/sql/create_type.go @@ -418,8 +418,10 @@ func createCompositeTypeDesc( if err != nil { return nil, err } - err = tree.CheckUnsupportedType(params.ctx, ¶ms.p.semaCtx, typ) - if err != nil { + if typ.Identical(types.Trigger) { + return nil, tree.CannotAcceptTriggerErr + } + if err = tree.CheckUnsupportedType(params.ctx, ¶ms.p.semaCtx, typ); err != nil { return nil, err } if typ.UserDefined() { diff --git a/pkg/sql/exec_util.go b/pkg/sql/exec_util.go index 1a71c885df45..2e374d08a1b1 100644 --- a/pkg/sql/exec_util.go +++ b/pkg/sql/exec_util.go @@ -2070,6 +2070,10 @@ func checkResultType(typ *types.T, fmtCode pgwirebase.FormatCode) error { case types.AnyFamily: // Placeholder case. return errors.Errorf("could not determine data type of %s", typ) + case types.TriggerFamily: + // The TRIGGER datatype is only allowed as the return type of a trigger + // function. + return tree.CannotAcceptTriggerErr default: return errors.Errorf("unsupported result type: %s", typ) } diff --git a/pkg/sql/logictest/testdata/logic_test/grant_table b/pkg/sql/logictest/testdata/logic_test/grant_table index 5dd4160aea81..913e3ced9343 100644 --- a/pkg/sql/logictest/testdata/logic_test/grant_table +++ b/pkg/sql/logictest/testdata/logic_test/grant_table @@ -570,6 +570,9 @@ test pg_catalog timetz test pg_catalog timetz[] type admin ALL false test pg_catalog timetz[] type public USAGE false test pg_catalog timetz[] type root ALL false +test pg_catalog trigger type admin ALL false +test pg_catalog trigger type public USAGE false +test pg_catalog trigger type root ALL false test pg_catalog tsquery type admin ALL false test pg_catalog tsquery type public USAGE false test pg_catalog tsquery type root ALL false @@ -775,6 +778,8 @@ test pg_catalog timetz type admin ALL test pg_catalog timetz type root ALL false test pg_catalog timetz[] type admin ALL false test pg_catalog timetz[] type root ALL false +test pg_catalog trigger type admin ALL false +test pg_catalog trigger type root ALL false test pg_catalog tsquery type admin ALL false test pg_catalog tsquery type root ALL false test pg_catalog tsquery[] type admin ALL false @@ -1393,6 +1398,8 @@ a pg_catalog timetz type admin a pg_catalog timetz type root ALL false a pg_catalog timetz[] type admin ALL false a pg_catalog timetz[] type root ALL false +a pg_catalog trigger type admin ALL false +a pg_catalog trigger type root ALL false a pg_catalog tsquery type admin ALL false a pg_catalog tsquery type root ALL false a pg_catalog tsquery[] type admin ALL false @@ -1575,6 +1582,8 @@ defaultdb pg_catalog timetz type admin defaultdb pg_catalog timetz type root ALL false defaultdb pg_catalog timetz[] type admin ALL false defaultdb pg_catalog timetz[] type root ALL false +defaultdb pg_catalog trigger type admin ALL false +defaultdb pg_catalog trigger type root ALL false defaultdb pg_catalog tsquery type admin ALL false defaultdb pg_catalog tsquery type root ALL false defaultdb pg_catalog tsquery[] type admin ALL false @@ -1757,6 +1766,8 @@ postgres pg_catalog timetz type admin postgres pg_catalog timetz type root ALL false postgres pg_catalog timetz[] type admin ALL false postgres pg_catalog timetz[] type root ALL false +postgres pg_catalog trigger type admin ALL false +postgres pg_catalog trigger type root ALL false postgres pg_catalog tsquery type admin ALL false postgres pg_catalog tsquery type root ALL false postgres pg_catalog tsquery[] type admin ALL false @@ -1939,6 +1950,8 @@ system pg_catalog timetz type admin system pg_catalog timetz type root ALL false system pg_catalog timetz[] type admin ALL false system pg_catalog timetz[] type root ALL false +system pg_catalog trigger type admin ALL false +system pg_catalog trigger type root ALL false system pg_catalog tsquery type admin ALL false system pg_catalog tsquery type root ALL false system pg_catalog tsquery[] type admin ALL false @@ -2502,6 +2515,8 @@ test pg_catalog timetz type admin test pg_catalog timetz type root ALL false test pg_catalog timetz[] type admin ALL false test pg_catalog timetz[] type root ALL false +test pg_catalog trigger type admin ALL false +test pg_catalog trigger type root ALL false test pg_catalog tsquery type admin ALL false test pg_catalog tsquery type root ALL false test pg_catalog tsquery[] type admin ALL false diff --git a/pkg/sql/logictest/testdata/logic_test/pg_catalog b/pkg/sql/logictest/testdata/logic_test/pg_catalog index 69c46e75062e..865b1068ae75 100644 --- a/pkg/sql/logictest/testdata/logic_test/pg_catalog +++ b/pkg/sql/logictest/testdata/logic_test/pg_catalog @@ -1901,6 +1901,7 @@ oid typname typnamespace typowner typlen typbyval typty 2249 record 4294967103 NULL 0 true p 2277 anyarray 4294967103 NULL -1 false p 2278 void 4294967103 NULL 0 true p +2279 trigger 4294967103 NULL 4 true p 2283 anyelement 4294967103 NULL -1 false p 2287 _record 4294967103 NULL -1 false b 2950 uuid 4294967103 NULL 16 true b @@ -2018,6 +2019,7 @@ oid typname typcategory typispreferred typisdefined typdel 2249 record P false true , 0 0 2287 2277 anyarray P false true , 0 0 0 2278 void P false true , 0 0 0 +2279 trigger P false true , 0 0 0 2283 anyelement P false true , 0 0 2277 2287 _record A false true , 0 2249 0 2950 uuid U false true , 0 0 2951 @@ -2135,6 +2137,7 @@ oid typname typinput typoutput typreceive 2249 record record_in record_out record_recv record_send 0 0 0 2277 anyarray anyarray_in anyarray_out anyarray_recv anyarray_send 0 0 0 2278 void voidin voidout voidrecv voidsend 0 0 0 +2279 trigger triggerin triggerout triggerrecv triggersend 0 0 0 2283 anyelement anyelement_in anyelement_out anyelement_recv anyelement_send 0 0 0 2287 _record array_in array_out array_recv array_send 0 0 0 2950 uuid uuid_in uuid_out uuid_recv uuid_send 0 0 0 @@ -2252,6 +2255,7 @@ oid typname typalign typstorage typnotnull typbasetype ty 2249 record NULL NULL false 0 -1 2277 anyarray NULL NULL false 0 -1 2278 void NULL NULL false 0 -1 +2279 trigger NULL NULL false 0 -1 2283 anyelement NULL NULL false 0 -1 2287 _record NULL NULL false 0 -1 2950 uuid NULL NULL false 0 -1 @@ -2369,6 +2373,7 @@ oid typname typndims typcollation typdefaultbin typdefault 2249 record 0 0 NULL NULL NULL 2277 anyarray 0 3403232968 NULL NULL NULL 2278 void 0 0 NULL NULL NULL +2279 trigger 0 0 NULL NULL NULL 2283 anyelement 0 0 NULL NULL NULL 2287 _record 0 0 NULL NULL NULL 2950 uuid 0 0 NULL NULL NULL diff --git a/pkg/sql/logictest/testdata/logic_test/pgoidtype b/pkg/sql/logictest/testdata/logic_test/pgoidtype index f84cd3cab83a..851ac1e3b61e 100644 --- a/pkg/sql/logictest/testdata/logic_test/pgoidtype +++ b/pkg/sql/logictest/testdata/logic_test/pgoidtype @@ -432,7 +432,7 @@ SELECT proargtypes::REGTYPE[] FROM pg_proc WHERE proname = 'obj_description' query I SELECT 'trigger'::REGTYPE::INT ---- -0 +2279 # Regression test for #41708. diff --git a/pkg/sql/opt/optbuilder/create_function.go b/pkg/sql/opt/optbuilder/create_function.go index e08e375d1264..52c9a06516e5 100644 --- a/pkg/sql/opt/optbuilder/create_function.go +++ b/pkg/sql/opt/optbuilder/create_function.go @@ -181,6 +181,23 @@ func (b *Builder) buildCreateFunction(cf *tree.CreateRoutine, inScope *scope) (o if err != nil { panic(err) } + if typ.Identical(types.Trigger) { + // TRIGGER is not allowed in this context. + if language == tree.RoutineLangPLpgSQL { + panic(pgerror.New(pgcode.FeatureNotSupported, + "PL/pgSQL functions cannot accept type trigger", + )) + } + if param.IsOutParam() { + panic(pgerror.New(pgcode.InvalidFunctionDefinition, + "SQL functions cannot return type trigger", + )) + } else { + panic(pgerror.New(pgcode.InvalidFunctionDefinition, + "SQL functions cannot have arguments of type trigger", + )) + } + } if param.Class == tree.RoutineParamInOut && param.Name == "" { panic(unimplemented.NewWithIssue(121251, "unnamed INOUT parameters are not yet supported")) } @@ -342,6 +359,15 @@ func (b *Builder) buildCreateFunction(cf *tree.CreateRoutine, inScope *scope) (o } else if language == tree.RoutineLangPLpgSQL { panic(pgerror.New(pgcode.InvalidFunctionDefinition, "PL/pgSQL functions cannot return type unknown")) } + } else if funcReturnType.Identical(types.Trigger) { + if language == tree.RoutineLangSQL { + // Postgres does not allow SQL trigger functions. + panic(pgerror.New(pgcode.InvalidFunctionDefinition, "SQL functions cannot return type trigger")) + } + if len(cf.Params) > 0 { + // Trigger functions cannot have parameters. + panic(pgerror.New(pgcode.InvalidFunctionDefinition, "trigger functions cannot have declared arguments")) + } } // Collect the user defined type dependency of the return type. typedesc.GetTypeDescriptorClosure(funcReturnType).ForEach(func(id descpb.ID) { @@ -395,6 +421,9 @@ func (b *Builder) buildCreateFunction(cf *tree.CreateRoutine, inScope *scope) (o "set-returning PL/pgSQL functions are not yet supported", )) } + if funcReturnType.Identical(types.Trigger) { + panic(unimplemented.NewWithIssue(126356, "trigger functions are not yet supported")) + } // Parse the function body. stmt, err := plpgsqlparser.Parse(funcBodyStr) @@ -512,6 +541,11 @@ func validateReturnType( ) } + // Any column types are allowed when the return type is TRIGGER. + if expected.Identical(types.Trigger) { + return nil + } + // If return type is RECORD and the tuple content types unspecified by OUT // parameters, any column types are valid. This is the case when we have // RETURNS RECORD without OUT-params - we don't need to check the types diff --git a/pkg/sql/opt/optbuilder/routine.go b/pkg/sql/opt/optbuilder/routine.go index 041b96c48f86..bb661e970ed4 100644 --- a/pkg/sql/opt/optbuilder/routine.go +++ b/pkg/sql/opt/optbuilder/routine.go @@ -60,6 +60,14 @@ func (b *Builder) buildUDF( } } + // Trigger functions cannot be directly invoked. + if f.ResolvedType().Identical(types.Trigger) { + // Note: Postgres also uses the "0A000" error code. + panic(pgerror.New(pgcode.FeatureNotSupported, + "trigger functions can only be called as triggers", + )) + } + // Build the routine. routine := b.buildRoutine(f, def, inScope, outScope, colRefs) diff --git a/pkg/sql/parser/parse.go b/pkg/sql/parser/parse.go index da4edb6b6aa4..1f351296f706 100644 --- a/pkg/sql/parser/parse.go +++ b/pkg/sql/parser/parse.go @@ -489,12 +489,11 @@ func arrayOf( // If the reference is a statically known type, then return an array type, // rather than an array type reference. if typ, ok := tree.GetStaticallyKnownType(ref); ok { - // Do not allow type unknown[]. This is consistent with Postgres' behavior. - if typ.Family() == types.UnknownFamily { - return nil, pgerror.Newf(pgcode.UndefinedObject, "type unknown[] does not exist") - } - if typ.Family() == types.VoidFamily { - return nil, pgerror.Newf(pgcode.UndefinedObject, "type void[] does not exist") + switch typ.Family() { + case types.UnknownFamily, types.VoidFamily, types.TriggerFamily: + // Do not allow arrays of these types. This is consistent with Postgres' + // behavior. + return nil, pgerror.Newf(pgcode.UndefinedObject, "type %s[] does not exist", typ.Name()) } if err := types.CheckArrayElementType(typ); err != nil { return nil, err diff --git a/pkg/sql/parser/testdata/select_exprs b/pkg/sql/parser/testdata/select_exprs index 3525f0a2298f..0c252b3f5fb7 100644 --- a/pkg/sql/parser/testdata/select_exprs +++ b/pkg/sql/parser/testdata/select_exprs @@ -1940,6 +1940,30 @@ DETAIL: source SQL: SELECT ''::void[] ^ +parse +SELECT ''::trigger +---- +SELECT ''::TRIGGER -- normalized! +SELECT (('')::TRIGGER) -- fully parenthesized +SELECT '_'::TRIGGER -- literals removed +SELECT ''::TRIGGER -- identifiers removed + +parse +SELECT '':::trigger +---- +SELECT '':::TRIGGER -- normalized! +SELECT ((''):::TRIGGER) -- fully parenthesized +SELECT '_':::TRIGGER -- literals removed +SELECT '':::TRIGGER -- identifiers removed + +error +SELECT ''::trigger[] +---- +at or near "EOF": syntax error: type trigger[] does not exist +DETAIL: source SQL: +SELECT ''::trigger[] + ^ + parse SELECT overlaps( TIMESTAMP '2000-01-01 00:00:00', diff --git a/pkg/sql/pg_catalog.go b/pkg/sql/pg_catalog.go index 51d6678c8a16..e78a115e1897 100644 --- a/pkg/sql/pg_catalog.go +++ b/pkg/sql/pg_catalog.go @@ -3395,6 +3395,8 @@ func addPGTypeRow( } case types.VoidFamily: // void does not have an array type. + case types.TriggerFamily: + // trigger does not have an array type. default: typArray = tree.NewDOid(types.CalcArrayOid(typ)) } @@ -4725,6 +4727,9 @@ func typLen(typ *types.T) *tree.DInt { } func typByVal(typ *types.T) tree.Datum { + if typ.Identical(types.Trigger) { + return tree.DBoolTrue + } _, variable := tree.DatumTypeSize(typ) return tree.MakeDBool(tree.DBool(!variable)) } @@ -4781,6 +4786,7 @@ var datumToTypeCategory = map[types.Family]*tree.DString{ types.INetFamily: typCategoryNetworkAddr, types.UnknownFamily: typCategoryUnknown, types.VoidFamily: typCategoryPseudo, + types.TriggerFamily: typCategoryPseudo, } func typCategory(typ *types.T) tree.Datum { diff --git a/pkg/sql/randgen/type.go b/pkg/sql/randgen/type.go index 30baaad79cfa..1cbcc31b747f 100644 --- a/pkg/sql/randgen/type.go +++ b/pkg/sql/randgen/type.go @@ -44,7 +44,7 @@ func init() { // Temporarily don't include this. // TODO(msirek): Remove this exclusion once // https://github.com/cockroachdb/cockroach/issues/55791 is fixed. - case oid.T_unknown, oid.T_anyelement: + case oid.T_unknown, oid.T_anyelement, oid.T_trigger: // Don't include these. case oid.T_anyarray, oid.T_oidvector, oid.T_int2vector: // Include these. diff --git a/pkg/sql/randgen/types_test.go b/pkg/sql/randgen/types_test.go index fc1ca32b5249..8a6558210d1c 100644 --- a/pkg/sql/randgen/types_test.go +++ b/pkg/sql/randgen/types_test.go @@ -35,7 +35,7 @@ loop: case types.EncodedKeyFamily: // It's not a real type. continue loop - case types.UnknownFamily, types.AnyFamily: + case types.UnknownFamily, types.AnyFamily, types.TriggerFamily: // These are not included on purpose. continue loop } diff --git a/pkg/sql/rowenc/encoded_datum_test.go b/pkg/sql/rowenc/encoded_datum_test.go index dadb8b0278a5..2cdbb263762c 100644 --- a/pkg/sql/rowenc/encoded_datum_test.go +++ b/pkg/sql/rowenc/encoded_datum_test.go @@ -221,7 +221,7 @@ func TestEncDatumCompare(t *testing.T) { for _, typ := range types.OidToType { switch typ.Family() { case types.AnyFamily, types.UnknownFamily, types.ArrayFamily, types.JsonFamily, types.TupleFamily, types.VoidFamily, - types.TSQueryFamily, types.TSVectorFamily, types.PGVectorFamily: + types.TSQueryFamily, types.TSVectorFamily, types.PGVectorFamily, types.TriggerFamily: continue case types.CollatedStringFamily: typ = types.MakeCollatedString(types.String, *randgen.RandCollationLocale(rng)) diff --git a/pkg/sql/sem/builtins/fixed_oids.go b/pkg/sql/sem/builtins/fixed_oids.go index bfbc76604596..ef85e6f5d7c7 100644 --- a/pkg/sql/sem/builtins/fixed_oids.go +++ b/pkg/sql/sem/builtins/fixed_oids.go @@ -2602,6 +2602,20 @@ var builtinOidsArray = []string{ 2634: `vector_dims(vector: vector) -> int`, 2635: `vector_norm(vector: vector) -> float`, 2636: `crdb_internal.log(msg: string) -> void`, + 2637: `crdb_internal.start_logical_replication_job(conn_str: string, table_names: string[]) -> int`, + 2638: `crdb_internal.plan_logical_replication(req: bytes) -> bytes`, + 2639: `crdb_internal.start_replication_stream_for_tables(req: bytes) -> bytes`, + 2640: `triggerin(input: anyelement) -> trigger`, + 2641: `triggerout(trigger: trigger) -> bytes`, + 2642: `triggersend(trigger: trigger) -> bytes`, + 2643: `triggerrecv(input: anyelement) -> trigger`, + 2644: `trigger(trigger: trigger) -> trigger`, + 2645: `trigger(string: string) -> trigger`, + 2646: `bpchar(trigger: trigger) -> char`, + 2647: `char(trigger: trigger) -> "char"`, + 2648: `name(trigger: trigger) -> name`, + 2649: `text(trigger: trigger) -> string`, + 2650: `varchar(trigger: trigger) -> varchar`, } var builtinOidsBySignature map[string]oid.Oid diff --git a/pkg/sql/sem/tree/datum.go b/pkg/sql/sem/tree/datum.go index 167805807aaa..b8a7cf689f6a 100644 --- a/pkg/sql/sem/tree/datum.go +++ b/pkg/sql/sem/tree/datum.go @@ -5998,6 +5998,9 @@ func PGWireTypeSize(t *types.T) int { if tOid == oid.T_date { return 4 } + if tOid == oid.T_trigger { + return 4 + } if sz, variable := DatumTypeSize(t); !variable { return int(sz) } diff --git a/pkg/sql/sem/tree/type_check.go b/pkg/sql/sem/tree/type_check.go index cfe94fc640cb..69dc84c27fe3 100644 --- a/pkg/sql/sem/tree/type_check.go +++ b/pkg/sql/sem/tree/type_check.go @@ -691,6 +691,11 @@ func (expr *CastExpr) TypeCheck( if err != nil { return nil, err } + if exprType.Identical(types.Trigger) { + // Trigger is not allowed in casts. This happens after resolving the cast to + // ensure that we return an "invalid cast" error when postgres does. + return nil, CannotAcceptTriggerErr + } expr.Expr = typedSubExpr expr.Type = exprType expr.typ = exprType @@ -771,6 +776,9 @@ func (expr *AnnotateTypeExpr) TypeCheck( if err != nil { return nil, err } + if annotateType.Identical(types.Trigger) { + return nil, CannotAcceptTriggerErr + } if err = CheckUnsupportedType(ctx, semaCtx, annotateType); err != nil { return nil, err } @@ -1371,13 +1379,20 @@ func (expr *FuncExpr) TypeCheck( } } - if overloadImpl.Type == BuiltinRoutine && (def.Name == "min" || def.Name == "max") { - // Special case: for REFCURSOR, we disallow min/max during type-checking - // despite having overloads for REFCURSOR. This maintains compatibility with - // postgres without having to add special checks in optimizer rules for - // REFCURSOR. - if len(s.typedExprs) > 0 && s.typedExprs[0].ResolvedType().Family() == types.RefCursorFamily { - return nil, pgerror.Newf(pgcode.UndefinedFunction, "function %s(refcursor) does not exist", def.Name) + // Some builtins are disabled in certain contexts for Postgres compatibility. + if overloadImpl.Type == BuiltinRoutine { + switch def.Name { + case "min", "max": + // Special case: for REFCURSOR, we disallow min/max during type-checking + // despite having overloads for REFCURSOR. This maintains compatibility + // with postgres without having to add special checks in optimizer rules + // for REFCURSOR. + if len(s.typedExprs) > 0 && s.typedExprs[0].ResolvedType().Family() == types.RefCursorFamily { + return nil, pgerror.Newf(pgcode.UndefinedFunction, "function %s(refcursor) does not exist", def.Name) + } + case "triggerin", "triggerrecv", "trigger": + // Built-in IO functions that output TRIGGER are not allowed. + return nil, CannotAcceptTriggerErr } } @@ -3737,6 +3752,10 @@ func CheckUnsupportedType(ctx context.Context, semaCtx *SemaContext, typ *types. return semaCtx.UnsupportedTypeChecker.CheckType(ctx, typ) } +var CannotAcceptTriggerErr = pgerror.New(pgcode.FeatureNotSupported, + "cannot accept a value of type trigger", +) + // checkRefCursorComparison checks whether the given types are or contain the // REFCURSOR data type, which is invalid for comparison. We don't simply remove // the relevant comparison overloads because we rely on their existence in diff --git a/pkg/sql/types/oid.go b/pkg/sql/types/oid.go index 12a83a87872c..28262d9cdd9d 100644 --- a/pkg/sql/types/oid.go +++ b/pkg/sql/types/oid.go @@ -97,6 +97,7 @@ var OidToType = map[oid.Oid]*T{ oid.T_timetz: TimeTZ, oid.T_timestamp: Timestamp, oid.T_timestamptz: TimestampTZ, + oid.T_trigger: Trigger, oid.T_tsquery: TSQuery, oid.T_tsvector: TSVector, oid.T_unknown: Unknown, diff --git a/pkg/sql/types/types.go b/pkg/sql/types/types.go index 54c52d299a03..ff496d52f20c 100644 --- a/pkg/sql/types/types.go +++ b/pkg/sql/types/types.go @@ -620,6 +620,11 @@ var ( EmptyTuple = &T{InternalType: InternalType{ Family: TupleFamily, Oid: oid.T_record, Locale: &emptyLocale}} + // Trigger is a special type used for trigger functions, which return a row of + // their target table. + Trigger = &T{InternalType: InternalType{ + Family: TriggerFamily, Oid: oid.T_trigger, Locale: &emptyLocale}} + // StringArray is the type of an array value having String-typed elements. StringArray = &T{InternalType: InternalType{ Family: ArrayFamily, ArrayContents: String, Oid: oid.T__text, Locale: &emptyLocale}} @@ -1539,6 +1544,7 @@ var familyNames = map[Family]redact.SafeString{ TimestampFamily: "timestamp", TimestampTZFamily: "timestamptz", TimeTZFamily: "timetz", + TriggerFamily: "trigger", TSQueryFamily: "tsquery", TSVectorFamily: "tsvector", TupleFamily: "tuple", @@ -1875,6 +1881,8 @@ func (t *T) SQLStandardNameWithTypmod(haveTypmod bool, typmod int) string { return "timestamp with time zone" } return fmt.Sprintf("timestamp(%d) with time zone", typmod) + case TriggerFamily: + return "trigger" case TSQueryFamily: return "tsquery" case TSVectorFamily: @@ -2248,6 +2256,11 @@ func (t *T) IsPolymorphicType() bool { return false } +// IsPseudoType returns true if the type is a pseudotype. +func (t *T) IsPseudoType() bool { + return t.Identical(Trigger) || t.IsPolymorphicType() +} + // Size returns the size, in bytes, of this type once it has been marshaled to // a byte buffer. This is typically called to determine the size of the buffer // that needs to be allocated before calling Marshal. diff --git a/pkg/sql/types/types.proto b/pkg/sql/types/types.proto index 539dbc55f8cc..e391a42f8cec 100644 --- a/pkg/sql/types/types.proto +++ b/pkg/sql/types/types.proto @@ -406,6 +406,12 @@ enum Family { // type representing pgvector vectors. PGVectorFamily = 32; + // TriggerFamily is a type family for the trigger type, which is the return + // type for trigger functions. + // Canonical: types.Trigger + // Oid : T_trigger + TriggerFamily = 33; + // AnyFamily is a special type family used during static analysis as a // wildcard type that matches any other type, including scalar, array, and // tuple types. Execution-time values should never have this type. As an