Skip to content

Commit 1367d0b

Browse files
committed
sql: make TG_ARGV use 0-based indexing for PostgreSQL compatibility
Previously, TG_ARGV used 1-based indexing (standard SQL array behavior), which was incompatible with PostgreSQL's 0-based indexing for TG_ARGV. A session setting `allow_create_trigger_function_with_argv_references` was required to use TG_ARGV at all, since it was blocked by default due to this incompatibility. This change makes TG_ARGV use 0-based indexing to match PostgreSQL behavior. To implement this, we add a `zeroIndexed` field to the DArray struct that controls the FirstIndex() return value. When constructing TG_ARGV for trigger functions, we now call SetZeroIndexed() to mark the array as 0-indexed. The `allow_create_trigger_function_with_argv_references` session setting is no longer needed and has been converted to a backwards-compatibility no-op variable. Fixes: #135311 Release note (backward-incompatible change): The TG_ARGV trigger function parameter now uses 0-based indexing to match PostgreSQL behavior. Previously, TG_ARGV[1] returned the first argument; now TG_ARGV[0] returns the first argument and TG_ARGV[1] returns the second argument. Additionally, usage of TG_ARGV no longer requires setting the `allow_create_trigger_function_with_argv_references` session variable.
1 parent 34f0226 commit 1367d0b

File tree

11 files changed

+92
-67
lines changed

11 files changed

+92
-67
lines changed

pkg/ccl/logictestccl/testdata/logic_test/triggers

Lines changed: 71 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4213,8 +4213,7 @@ CREATE FUNCTION g() RETURNS TRIGGER LANGUAGE PLpgSQL AS $$
42134213
RAISE NOTICE 'TG_TABLE_NAME: %', TG_TABLE_NAME;
42144214
RAISE NOTICE 'TG_TABLE_SCHEMA: %', TG_TABLE_SCHEMA;
42154215
RAISE NOTICE 'TG_NARGS: %', TG_NARGS;
4216-
-- TODO(#135311): uncomment this when we support TG_ARGV.
4217-
--RAISE NOTICE 'TG_ARGV: %s', TG_ARGV;
4216+
RAISE NOTICE 'TG_ARGV: %', TG_ARGV;
42184217
RETURN NEW;
42194218
END
42204219
$$;
@@ -4239,6 +4238,7 @@ NOTICE: TG_RELNAME: xy
42394238
NOTICE: TG_TABLE_NAME: xy
42404239
NOTICE: TG_TABLE_SCHEMA: public
42414240
NOTICE: TG_NARGS: 0
4241+
NOTICE: TG_ARGV: {}
42424242
NOTICE: NEW: (1,2)
42434243
NOTICE: OLD: <NULL>
42444244
NOTICE: TG_NAME: bar
@@ -4250,6 +4250,7 @@ NOTICE: TG_RELNAME: xy
42504250
NOTICE: TG_TABLE_NAME: xy
42514251
NOTICE: TG_TABLE_SCHEMA: public
42524252
NOTICE: TG_NARGS: 0
4253+
NOTICE: TG_ARGV: {}
42534254

42544255
query T noticetrace
42554256
UPDATE xy SET y = 3 WHERE x = 1;
@@ -4265,6 +4266,7 @@ NOTICE: TG_RELNAME: xy
42654266
NOTICE: TG_TABLE_NAME: xy
42664267
NOTICE: TG_TABLE_SCHEMA: public
42674268
NOTICE: TG_NARGS: 0
4269+
NOTICE: TG_ARGV: {}
42684270
NOTICE: NEW: (1,3)
42694271
NOTICE: OLD: (1,2)
42704272
NOTICE: TG_NAME: bar
@@ -4276,6 +4278,7 @@ NOTICE: TG_RELNAME: xy
42764278
NOTICE: TG_TABLE_NAME: xy
42774279
NOTICE: TG_TABLE_SCHEMA: public
42784280
NOTICE: TG_NARGS: 0
4281+
NOTICE: TG_ARGV: {}
42794282

42804283
statement ok
42814284
DROP TRIGGER foo ON xy;
@@ -4795,8 +4798,7 @@ DROP FUNCTION f4();
47954798

47964799
subtest end
47974800

4798-
# Until #135311 can be fixed, disallow TG_ARGV references by default. There is
4799-
# a setting to allow them if necessary.
4801+
# Regression test for #135311: TG_ARGV should be 0-indexed like PostgreSQL.
48004802
subtest regression_135311
48014803

48024804
statement ok
@@ -4806,43 +4808,95 @@ statement ok
48064808
CREATE FUNCTION g() RETURNS TRIGGER LANGUAGE PLpgSQL AS $$
48074809
BEGIN
48084810
RAISE NOTICE 'g()';
4811+
RAISE NOTICE 'TG_ARGV[0]: %', TG_ARGV[0];
48094812
RAISE NOTICE 'TG_ARGV[1]: %', TG_ARGV[1];
48104813
RETURN NEW;
48114814
END
48124815
$$;
48134816

4814-
statement error pgcode 0A000 pq: unimplemented: referencing the TG_ARGV trigger function parameter is not yet supported
4815-
CREATE TRIGGER foo BEFORE INSERT ON xy FOR EACH ROW EXECUTE FUNCTION g('foo', 'bar');
4817+
statement ok
4818+
CREATE TRIGGER foo BEFORE INSERT ON xy FOR EACH ROW EXECUTE FUNCTION g('first', 'second');
4819+
4820+
query T noticetrace
4821+
INSERT INTO xy VALUES (1, 2);
4822+
----
4823+
NOTICE: g()
4824+
NOTICE: TG_ARGV[0]: first
4825+
NOTICE: TG_ARGV[1]: second
48164826

48174827
statement ok
4818-
SET allow_create_trigger_function_with_argv_references = true;
4828+
DROP TRIGGER foo ON xy;
48194829

48204830
statement ok
4821-
CREATE TRIGGER foo BEFORE INSERT ON xy FOR EACH ROW EXECUTE FUNCTION g('foo', 'bar');
4831+
DROP FUNCTION g;
4832+
4833+
# Test assigning TG_ARGV to a TEXT[] variable, then doing a zero-indexed read.
4834+
# Note: This matches Postgres behavior, and keeps the array as zero-indexed.
4835+
statement ok
4836+
CREATE FUNCTION g() RETURNS TRIGGER LANGUAGE PLpgSQL AS $$
4837+
DECLARE
4838+
args TEXT[];
4839+
BEGIN
4840+
args := TG_ARGV;
4841+
RAISE NOTICE 'args[0]: %', args[0];
4842+
RAISE NOTICE 'args[1]: %', args[1];
4843+
RETURN NEW;
4844+
END
4845+
$$;
4846+
4847+
statement ok
4848+
CREATE TRIGGER foo BEFORE INSERT ON xy FOR EACH ROW EXECUTE FUNCTION g('arg_one', 'arg_two');
48224849

48234850
query T noticetrace
4824-
INSERT INTO xy VALUES (1, 2);
4851+
INSERT INTO xy VALUES (100, 200);
48254852
----
4826-
NOTICE: g()
4827-
NOTICE: TG_ARGV[1]: foo
4853+
NOTICE: args[0]: arg_one
4854+
NOTICE: args[1]: arg_two
48284855

48294856
statement ok
4830-
RESET allow_create_trigger_function_with_argv_references;
4857+
DROP TRIGGER foo ON xy;
4858+
4859+
statement ok
4860+
DROP FUNCTION g;
4861+
4862+
# Test a helper function that takes a TEXT[] argument and does a zero-indexed
4863+
# read on it.
4864+
# Note: In PostgreSQL, when TG_ARGV is passed to a function, it retains its
4865+
# zero-indexed behavior.
4866+
statement ok
4867+
CREATE FUNCTION read_args(arr TEXT[]) RETURNS TEXT LANGUAGE PLpgSQL AS $$
4868+
BEGIN
4869+
RETURN 'arr[0]=' || COALESCE(arr[0], 'NULL') || ', arr[1]=' || COALESCE(arr[1], 'NULL');
4870+
END
4871+
$$;
4872+
4873+
statement ok
4874+
CREATE FUNCTION g() RETURNS TRIGGER LANGUAGE PLpgSQL AS $$
4875+
BEGIN
4876+
RAISE NOTICE 'direct TG_ARGV[0]: %', TG_ARGV[0];
4877+
RAISE NOTICE 'via helper: %', read_args(TG_ARGV);
4878+
RETURN NEW;
4879+
END
4880+
$$;
4881+
4882+
statement ok
4883+
CREATE TRIGGER foo BEFORE INSERT ON xy FOR EACH ROW EXECUTE FUNCTION g('helper_one', 'helper_two');
48314884

4832-
# The setting only disallows creating the trigger - not executing it if it was
4833-
# already created.
48344885
query T noticetrace
4835-
INSERT INTO xy VALUES (3, 4);
4886+
INSERT INTO xy VALUES (102, 202);
48364887
----
4837-
NOTICE: g()
4838-
NOTICE: TG_ARGV[1]: foo
4888+
NOTICE: direct TG_ARGV[0]: helper_one
4889+
NOTICE: via helper: arr[0]=helper_one, arr[1]=helper_two
48394890

48404891
statement ok
48414892
DROP TRIGGER foo ON xy;
48424893

48434894
statement ok
48444895
DROP FUNCTION g;
48454896

4897+
statement ok
4898+
DROP FUNCTION read_args;
4899+
48464900
subtest end
48474901

48484902
subtest regression_146889

pkg/sql/logictest/testdata/logic_test/information_schema

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3752,7 +3752,6 @@ WHERE
37523752
ORDER BY variable
37533753
----
37543754
variable value
3755-
allow_create_trigger_function_with_argv_references off
37563755
allow_ordinal_column_references off
37573756
allow_role_memberships_to_change_during_transaction off
37583757
allow_unsafe_internals off

pkg/sql/logictest/testdata/logic_test/pg_catalog

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3023,7 +3023,6 @@ WHERE
30233023
ORDER BY name
30243024
----
30253025
name setting category short_desc extra_desc vartype
3026-
allow_create_trigger_function_with_argv_references off NULL NULL NULL string
30273026
allow_ordinal_column_references off NULL NULL NULL string
30283027
allow_role_memberships_to_change_during_transaction off NULL NULL NULL string
30293028
allow_unsafe_internals off NULL NULL NULL string
@@ -3276,7 +3275,6 @@ WHERE
32763275
ORDER BY name
32773276
----
32783277
name setting unit context enumvals boot_val reset_val
3279-
allow_create_trigger_function_with_argv_references off NULL user NULL off off
32803278
allow_ordinal_column_references off NULL user NULL off off
32813279
allow_role_memberships_to_change_during_transaction off NULL user NULL off off
32823280
allow_unsafe_internals off NULL user NULL off off
@@ -3513,7 +3511,6 @@ query TTTTTT colnames
35133511
SELECT name, source, min_val, max_val, sourcefile, sourceline FROM pg_catalog.pg_settings ORDER BY name
35143512
----
35153513
name source min_val max_val sourcefile sourceline
3516-
allow_create_trigger_function_with_argv_references NULL NULL NULL NULL NULL
35173514
allow_ordinal_column_references NULL NULL NULL NULL NULL
35183515
allow_role_memberships_to_change_during_transaction NULL NULL NULL NULL NULL
35193516
allow_unsafe_internals NULL NULL NULL NULL NULL

pkg/sql/logictest/testdata/logic_test/show_source

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ WHERE
3737
ORDER BY variable
3838
----
3939
variable value
40-
allow_create_trigger_function_with_argv_references off
4140
allow_ordinal_column_references off
4241
allow_role_memberships_to_change_during_transaction off
4342
allow_unsafe_internals off

pkg/sql/opt/optbuilder/create_trigger.go

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -225,12 +225,6 @@ func (b *Builder) buildFunctionForTrigger(
225225
paramColName := funcParamColName(param.name, i)
226226
col := b.synthesizeColumn(funcScope, paramColName, param.typ, nil /* expr */, nil /* scalar */)
227227
col.setParamOrd(i)
228-
if i == triggerArgvColIdx {
229-
// Due to #135311, we disallow references to the TG_ARGV param for now.
230-
if !b.evalCtx.SessionData().AllowCreateTriggerFunctionWithArgvReferences {
231-
col.resolveErr = unimplementedArgvErr
232-
}
233-
}
234228
}
235229

236230
// Now that the transition relations and table type are known, fully build and
@@ -336,10 +330,6 @@ var triggerFuncStaticParams = []routineParam{
336330
{name: "tg_argv", typ: types.StringArray, class: tree.RoutineParamIn},
337331
}
338332

339-
// The trigger function parameters consist of OLD and NEW, followed by the
340-
// static parameters. The TG_ARGV parameter is last.
341-
var triggerArgvColIdx = len(triggerFuncStaticParams) + 1
342-
343333
const triggerColNew = "new"
344334
const triggerColOld = "old"
345335

@@ -379,6 +369,4 @@ var (
379369
"column lists are not yet supported for triggers")
380370
unimplementedViewTriggerErr = unimplemented.NewWithIssue(135658,
381371
"triggers on views are not yet supported")
382-
unimplementedArgvErr = unimplemented.NewWithIssue(135311,
383-
"referencing the TG_ARGV trigger function parameter is not yet supported")
384372
)

pkg/sql/opt/optbuilder/plpgsql.go

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2222,12 +2222,6 @@ func (b *plpgsqlBuilder) makeContinuation(conName string) continuation {
22222222
// continuation UDFs.
22232223
paramOrd := len(params)
22242224
col.setParamOrd(paramOrd)
2225-
if b.ob.insideFuncDef && b.options.isTriggerFn && paramOrd == triggerArgvColIdx {
2226-
// Due to #135311, we disallow references to the TG_ARGV param for now.
2227-
if !b.ob.evalCtx.SessionData().AllowCreateTriggerFunctionWithArgvReferences {
2228-
col.resolveErr = unimplementedArgvErr
2229-
}
2230-
}
22312225
params = append(params, col.id)
22322226
}
22332227
// Invariant: the variables of a child block always follow those of a parent
@@ -2377,9 +2371,7 @@ func (b *plpgsqlBuilder) makeContinuationArgs(con *continuation, s *scope) memo.
23772371
block := &b.blocks[i]
23782372
for _, name := range block.vars {
23792373
_, source, _, err := s.FindSourceProvidingColumn(b.ob.ctx, name)
2380-
if err != nil && !errors.Is(err, unimplementedArgvErr) {
2381-
// Swallow unimplementedArgvErr, since it's ok to reference the TG_ARGV
2382-
// parameter when calling a continuation.
2374+
if err != nil {
23832375
panic(err)
23842376
}
23852377
args = append(args, b.ob.factory.ConstructVariable(source.(*scopeColumn).id))

pkg/sql/opt/optbuilder/trigger.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ func (mb *mutationBuilder) buildTriggerFunctionArgs(
234234
tgTableSchema := tree.NewDString(schema.Name().Schema())
235235
tgNumArgs := tree.NewDInt(tree.DInt(len(trigger.FuncArgs())))
236236
tgArgV := tree.NewDArray(types.String)
237+
tgArgV.SetZeroIndexed()
237238
for _, arg := range trigger.FuncArgs() {
238239
err = tgArgV.Append(arg)
239240
if err != nil {
@@ -700,6 +701,7 @@ func (tb *rowLevelAfterTriggerBuilder) Build(
700701
tgName := tree.NewDName(string(trigger.Name()))
701702
tgNumArgs := tree.NewDInt(tree.DInt(len(trigger.FuncArgs())))
702703
tgArgV := tree.NewDArray(types.String)
704+
tgArgV.SetZeroIndexed()
703705
for _, arg := range trigger.FuncArgs() {
704706
err = tgArgV.Append(arg)
705707
if err != nil {

pkg/sql/sem/tree/datum.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4771,6 +4771,10 @@ type DArray struct {
47714771

47724772
// customOid, if non-0, is the oid of this array datum.
47734773
customOid oid.Oid
4774+
4775+
// zeroIndexed, if true, makes FirstIndex() return 0 instead of 1.
4776+
// This is used for TG_ARGV to match PostgreSQL's 0-indexed behavior.
4777+
zeroIndexed bool
47744778
}
47754779

47764780
// NewDArray returns a DArray containing elements of the specified type.
@@ -4883,13 +4887,22 @@ func (d *DArray) IsComposite() bool {
48834887
// which are 1-indexed, and 0 for the special Postgers vector types which are
48844888
// 0-indexed.
48854889
func (d *DArray) FirstIndex() int {
4890+
if d.zeroIndexed {
4891+
return 0
4892+
}
48864893
switch d.customOid {
48874894
case oid.T_int2vector, oid.T_oidvector:
48884895
return 0
48894896
}
48904897
return 1
48914898
}
48924899

4900+
// SetZeroIndexed marks this array as 0-indexed. This is used for TG_ARGV
4901+
// to match PostgreSQL's 0-indexed behavior.
4902+
func (d *DArray) SetZeroIndexed() {
4903+
d.zeroIndexed = true
4904+
}
4905+
48934906
// Compare implements the Datum interface.
48944907
func (d *DArray) Compare(ctx context.Context, cmpCtx CompareContext, other Datum) (int, error) {
48954908
if other == DNull {

pkg/sql/sessiondatapb/local_only_session_data.proto

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -644,11 +644,7 @@ message LocalOnlySessionData {
644644
// OptimizerUseDeleteRangeFastPath, when true, indicates that the optimizer
645645
// should try to use the delete range fast-path when possible.
646646
bool optimizer_use_delete_range_fast_path = 164;
647-
// AllowCreateTriggerFunctionWithArgvReferences, when true, allows triggers to
648-
// be created with trigger functions that use the TG_ARGV parameter even
649-
// though it currently doesn't have Postgres-compatible 0-based indexing
650-
// behavior.
651-
bool allow_create_trigger_function_with_argv_references = 165;
647+
reserved 165;
652648
// CreateTableWithSchemaLocked, when true will create tables as schema_locked
653649
// by default.
654650
bool create_table_with_schema_locked = 166;

pkg/sql/sessionmutator/mutator.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,10 +1031,6 @@ func (m *SessionDataMutator) SetOptimizerUseDeleteRangeFastPath(val bool) {
10311031
m.Data.OptimizerUseDeleteRangeFastPath = val
10321032
}
10331033

1034-
func (m *SessionDataMutator) SetAllowCreateTriggerFunctionWithArgvReferences(val bool) {
1035-
m.Data.AllowCreateTriggerFunctionWithArgvReferences = val
1036-
}
1037-
10381034
func (m *SessionDataMutator) SetCreateTableWithSchemaLocked(val bool) {
10391035
m.Data.CreateTableWithSchemaLocked = val
10401036
}

0 commit comments

Comments
 (0)