Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/sql/opt/optbuilder/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func (b *Builder) buildDelete(del *tree.Delete, inScope *scope) (outScope *scope
mb.buildInputForDelete(inScope, del.Table, del.Where, del.Using, del.Limit, del.OrderBy)

// Project row-level BEFORE triggers for DELETE.
mb.buildRowLevelBeforeTriggers(tree.TriggerEventDelete, false /* cascade */)
mb.buildRowLevelBeforeTriggers(tree.TriggerEventDelete, false /* cascade */, true /* recomputeCols */)

// Build the final delete statement, including any returned expressions.
if resultsNeeded(del.Returning) {
Expand Down
8 changes: 4 additions & 4 deletions pkg/sql/opt/optbuilder/fk_cascade.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func (cb *onDeleteCascadeBuilder) Build(
b.checkMultipleMutations(mb.tab, generalMutation)

// Cascades can fire triggers on the child table.
mb.buildRowLevelBeforeTriggers(tree.TriggerEventDelete, true /* cascade */)
mb.buildRowLevelBeforeTriggers(tree.TriggerEventDelete, true /* cascade */, true /* recomputeCols */)

mb.buildDelete(nil /* returning */)
return mb.outScope.expr
Expand Down Expand Up @@ -426,7 +426,7 @@ func (cb *onDeleteFastCascadeBuilder) Build(
b.checkMultipleMutations(mb.tab, generalMutation)

// Cascades can fire triggers on the child table.
mb.buildRowLevelBeforeTriggers(tree.TriggerEventDelete, true /* cascade */)
mb.buildRowLevelBeforeTriggers(tree.TriggerEventDelete, true /* cascade */, true /* recomputeCols */)

mb.buildDelete(nil /* returning */)
return mb.outScope.expr
Expand Down Expand Up @@ -567,7 +567,7 @@ func (cb *onDeleteSetBuilder) Build(
b.checkMultipleMutations(mb.tab, generalMutation)

// Cascades can fire triggers on the child table.
mb.buildRowLevelBeforeTriggers(tree.TriggerEventUpdate, true /* cascade */)
mb.buildRowLevelBeforeTriggers(tree.TriggerEventUpdate, true /* cascade */, true /* recomputeCols */)

// TODO(radu): consider plumbing a flag to prevent building the FK check
// against the parent we are cascading from. Need to investigate in which
Expand Down Expand Up @@ -840,7 +840,7 @@ func (cb *onUpdateCascadeBuilder) Build(
b.checkMultipleMutations(mb.tab, generalMutation)

// Cascades can fire triggers on the child table.
mb.buildRowLevelBeforeTriggers(tree.TriggerEventUpdate, true /* cascade */)
mb.buildRowLevelBeforeTriggers(tree.TriggerEventUpdate, true /* cascade */, true /* recomputeCols */)

// The exempt policy is used for RLS to maintain data integrity.
mb.buildUpdate(nil /* returning */, cat.PolicyScopeExempt, nil /* colRefs */)
Expand Down
16 changes: 9 additions & 7 deletions pkg/sql/opt/optbuilder/insert.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ func (b *Builder) buildInsert(ins *tree.Insert, inScope *scope) (outScope *scope
// Case 1: Simple INSERT statement.
case ins.OnConflict == nil:
// Project row-level BEFORE triggers for INSERT.
mb.buildRowLevelBeforeTriggers(tree.TriggerEventInsert, false /* cascade */)
mb.buildRowLevelBeforeTriggers(tree.TriggerEventInsert, false /* cascade */, true /* recomputeCols */)

// Build the final insert statement, including any returned expressions.
mb.buildInsert(returning, ins.VectorInsert(), false /* hasOnConflict */)
Expand All @@ -316,7 +316,7 @@ func (b *Builder) buildInsert(ins *tree.Insert, inScope *scope) (outScope *scope
case ins.OnConflict.DoNothing:

// Project row-level BEFORE triggers for INSERT.
mb.buildRowLevelBeforeTriggers(tree.TriggerEventInsert, false /* cascade */)
mb.buildRowLevelBeforeTriggers(tree.TriggerEventInsert, false /* cascade */, true /* recomputeCols */)

// Wrap the input in one ANTI JOIN per UNIQUE index, and filter out rows
// that have conflicts. See the buildInputForDoNothing comment for more
Expand Down Expand Up @@ -345,7 +345,7 @@ func (b *Builder) buildInsert(ins *tree.Insert, inScope *scope) (outScope *scope
// NOTE: we avoid building INSERT triggers until after buildInputForUpsert
// so that there are no buffering operators between INSERT and UPDATE
// triggers. This helps preserve Postgres compatibility
if mb.buildRowLevelBeforeTriggers(tree.TriggerEventInsert, false /* cascade */) {
if mb.buildRowLevelBeforeTriggers(tree.TriggerEventInsert, false /* cascade */, true /* recomputeCols */) {
// INSERT triggers are able to modify the row being inserted, so we need
// to recompute the upsert columns.
mb.setUpsertCols(nil /* insertCols */)
Expand All @@ -356,10 +356,10 @@ func (b *Builder) buildInsert(ins *tree.Insert, inScope *scope) (outScope *scope
mb.addSynthesizedColsForUpdate()

// Project row-level BEFORE triggers for UPDATE.
mb.buildRowLevelBeforeTriggers(tree.TriggerEventUpdate, false /* cascade */)
mb.buildRowLevelBeforeTriggers(tree.TriggerEventUpdate, false /* cascade */, true /* recomputeCols */)
} else {
// Project row-level BEFORE triggers for INSERT.
if mb.buildRowLevelBeforeTriggers(tree.TriggerEventInsert, false /* cascade */) {
if mb.buildRowLevelBeforeTriggers(tree.TriggerEventInsert, false /* cascade */, true /* recomputeCols */) {
// INSERT triggers are able to modify the row being inserted, so we need
// to recompute the upsert columns.
mb.setUpsertCols(nil /* insertCols */)
Expand All @@ -376,7 +376,9 @@ func (b *Builder) buildInsert(ins *tree.Insert, inScope *scope) (outScope *scope
canaryCol := mb.buildInputForUpsert(inScope, ins.Table, ins.OnConflict)

// Project row-level BEFORE triggers for INSERT.
mb.buildRowLevelBeforeTriggers(tree.TriggerEventInsert, false /* cascade */)
// NOTE: Do not recompute computed columns, this will happen in
// addUpdateCols.
mb.buildRowLevelBeforeTriggers(tree.TriggerEventInsert, false /* cascade */, false /* recomputeCols */)

// Add a filter from the WHERE clause if one exists. This must happen after
// the INSERT triggers are added, since BEFORE INSERT triggers are called
Expand All @@ -392,7 +394,7 @@ func (b *Builder) buildInsert(ins *tree.Insert, inScope *scope) (outScope *scope
mb.addUpdateCols(ins.OnConflict.Exprs, nil /* colRefs */)

// Project row-level BEFORE triggers for UPDATE.
mb.buildRowLevelBeforeTriggers(tree.TriggerEventUpdate, false /* cascade */)
mb.buildRowLevelBeforeTriggers(tree.TriggerEventUpdate, false /* cascade */, true /* recomputeCols */)

// Build the final upsert statement, including any returned expressions.
mb.buildUpsert(returning)
Expand Down
105 changes: 105 additions & 0 deletions pkg/sql/opt/optbuilder/testdata/trigger
Original file line number Diff line number Diff line change
Expand Up @@ -1253,3 +1253,108 @@ insert t133329
└── filters
├── a = t133329.a
└── k != t133329.k

# Regression test for #154672. Do not set target columns when re-projecting
# computed columns after a BEFORE trigger.
exec-ddl
CREATE TABLE t154672 (
k INT PRIMARY KEY,
a INT,
b INT AS (a + 1) STORED
)
----

exec-ddl
CREATE FUNCTION f154672() RETURNS TRIGGER LANGUAGE PLpgSQL AS $$
BEGIN
RETURN COALESCE(NEW, OLD);
END
$$
----

exec-ddl
CREATE TRIGGER tr154672 BEFORE INSERT OR UPDATE OR DELETE ON t154672 FOR EACH ROW EXECUTE FUNCTION f154672()
----

norm
INSERT INTO t154672 (k, a) VALUES (1, 2) ON CONFLICT (k) DO UPDATE SET a = 3
----
upsert t154672
├── arbiter indexes: t154672_pkey
├── columns: <none>
├── canary column: k:9
├── fetch columns: k:9 a:10 b:11
├── insert-mapping:
│ ├── k_new:29 => k:1
│ ├── a_new:30 => a:2
│ └── b_comp:8 => b:3
├── update-mapping:
│ ├── upsert_k:39 => k:1
│ ├── upsert_a:40 => a:2
│ └── upsert_b:41 => b:3
├── before-triggers
│ └── tr154672
└── project
├── columns: upsert_k:39 upsert_a:40 upsert_b:41 b_comp:8!null k:9 a:10 b:11 k_new:29 a_new:30
├── project
│ ├── columns: k_new:36 a_new:37 b_comp:8!null k:9 a:10 b:11 k_new:29 a_new:30
│ ├── barrier
│ │ ├── columns: column1:6!null column2:7!null b_comp:8!null k:9 a:10 b:11 crdb_internal_mvcc_timestamp:12 tableoid:13 new:14 f154672:28!null k_new:29 a_new:30 a_new:31!null b_comp:32!null old:33 new:34 f154672:35!null
│ │ └── select
│ │ ├── columns: column1:6!null column2:7!null b_comp:8!null k:9 a:10 b:11 crdb_internal_mvcc_timestamp:12 tableoid:13 new:14 f154672:28!null k_new:29 a_new:30 a_new:31!null b_comp:32!null old:33 new:34 f154672:35!null
│ │ ├── project
│ │ │ ├── columns: f154672:35 column1:6!null column2:7!null b_comp:8!null k:9 a:10 b:11 crdb_internal_mvcc_timestamp:12 tableoid:13 new:14 f154672:28!null k_new:29 a_new:30 a_new:31!null b_comp:32!null old:33 new:34
│ │ │ ├── barrier
│ │ │ │ ├── columns: column1:6!null column2:7!null b_comp:8!null k:9 a:10 b:11 crdb_internal_mvcc_timestamp:12 tableoid:13 new:14 f154672:28!null k_new:29 a_new:30 a_new:31!null b_comp:32!null old:33 new:34
│ │ │ │ └── project
│ │ │ │ ├── columns: new:34 old:33 b_comp:32!null a_new:31!null k_new:29 a_new:30 column1:6!null column2:7!null b_comp:8!null k:9 a:10 b:11 crdb_internal_mvcc_timestamp:12 tableoid:13 new:14 f154672:28!null
│ │ │ │ ├── barrier
│ │ │ │ │ ├── columns: column1:6!null column2:7!null b_comp:8!null k:9 a:10 b:11 crdb_internal_mvcc_timestamp:12 tableoid:13 new:14 f154672:28!null
│ │ │ │ │ └── select
│ │ │ │ │ ├── columns: column1:6!null column2:7!null b_comp:8!null k:9 a:10 b:11 crdb_internal_mvcc_timestamp:12 tableoid:13 new:14 f154672:28!null
│ │ │ │ │ ├── project
│ │ │ │ │ │ ├── columns: f154672:28 column1:6!null column2:7!null b_comp:8!null k:9 a:10 b:11 crdb_internal_mvcc_timestamp:12 tableoid:13 new:14
│ │ │ │ │ │ ├── barrier
│ │ │ │ │ │ │ ├── columns: column1:6!null column2:7!null b_comp:8!null k:9 a:10 b:11 crdb_internal_mvcc_timestamp:12 tableoid:13 new:14
│ │ │ │ │ │ │ └── project
│ │ │ │ │ │ │ ├── columns: new:14 column1:6!null column2:7!null b_comp:8!null k:9 a:10 b:11 crdb_internal_mvcc_timestamp:12 tableoid:13
│ │ │ │ │ │ │ ├── left-join (cross)
│ │ │ │ │ │ │ │ ├── columns: column1:6!null column2:7!null b_comp:8!null k:9 a:10 b:11 crdb_internal_mvcc_timestamp:12 tableoid:13
│ │ │ │ │ │ │ │ ├── values
│ │ │ │ │ │ │ │ │ ├── columns: column1:6!null column2:7!null b_comp:8!null
│ │ │ │ │ │ │ │ │ └── (1, 2, 3)
│ │ │ │ │ │ │ │ ├── select
│ │ │ │ │ │ │ │ │ ├── columns: k:9!null a:10 b:11 crdb_internal_mvcc_timestamp:12 tableoid:13
│ │ │ │ │ │ │ │ │ ├── scan t154672
│ │ │ │ │ │ │ │ │ │ ├── columns: k:9!null a:10 b:11 crdb_internal_mvcc_timestamp:12 tableoid:13
│ │ │ │ │ │ │ │ │ │ ├── computed column expressions
│ │ │ │ │ │ │ │ │ │ │ └── b:11
│ │ │ │ │ │ │ │ │ │ │ └── a:10 + 1
│ │ │ │ │ │ │ │ │ │ └── flags: avoid-full-scan disabled not visible index feature
│ │ │ │ │ │ │ │ │ └── filters
│ │ │ │ │ │ │ │ │ └── k:9 = 1
│ │ │ │ │ │ │ │ └── filters (true)
│ │ │ │ │ │ │ └── projections
│ │ │ │ │ │ │ └── ((column1:6, column2:7, CAST(NULL AS INT8)) AS k, a, b) [as=new:14]
│ │ │ │ │ │ └── projections
│ │ │ │ │ │ └── f154672(new:14, NULL, 'tr154672', 'BEFORE', 'ROW', 'INSERT', 61, 't154672', 't154672', 'public', 0, ARRAY[]) [as=f154672:28]
│ │ │ │ │ └── filters
│ │ │ │ │ └── f154672:28 IS DISTINCT FROM NULL
│ │ │ │ └── projections
│ │ │ │ ├── ((k:9, 3, CAST(NULL AS INT8)) AS k, a, b) [as=new:34]
│ │ │ │ ├── ((k:9, a:10, CAST(NULL AS INT8)) AS k, a, b) [as=old:33]
│ │ │ │ ├── 4 [as=b_comp:32]
│ │ │ │ ├── 3 [as=a_new:31]
│ │ │ │ ├── (f154672:28).k [as=k_new:29]
│ │ │ │ └── (f154672:28).a [as=a_new:30]
│ │ │ └── projections
│ │ │ └── CASE WHEN k:9 IS NOT NULL THEN f154672(new:34, old:33, 'tr154672', 'BEFORE', 'ROW', 'UPDATE', 61, 't154672', 't154672', 'public', 0, ARRAY[]) ELSE new:34 END [as=f154672:35]
│ │ └── filters
│ │ └── f154672:35 IS DISTINCT FROM NULL
│ └── projections
│ ├── (f154672:35).k [as=k_new:36]
│ └── (f154672:35).a [as=a_new:37]
└── projections
├── CASE WHEN k:9 IS NULL THEN k_new:29 ELSE k_new:36 END [as=upsert_k:39]
├── CASE WHEN k:9 IS NULL THEN a_new:30 ELSE a_new:37 END [as=upsert_a:40]
└── CASE WHEN k:9 IS NULL THEN b_comp:8 ELSE a_new:37 + 1 END [as=upsert_b:41]
4 changes: 2 additions & 2 deletions pkg/sql/opt/optbuilder/trigger.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import (
// based on the event type. It returns true if triggers were built, and false
// otherwise.
func (mb *mutationBuilder) buildRowLevelBeforeTriggers(
eventType tree.TriggerEventType, cascade bool,
eventType tree.TriggerEventType, cascade bool, recomputeCols bool,
) bool {
var eventsToMatch tree.TriggerEventTypeSet
eventsToMatch.Add(eventType)
Expand Down Expand Up @@ -155,7 +155,7 @@ func (mb *mutationBuilder) buildRowLevelBeforeTriggers(

// Since INSERT and UPDATE triggers can modify the row, we need to recompute
// the computed columns.
if eventType == tree.TriggerEventInsert || eventType == tree.TriggerEventUpdate {
if recomputeCols && (eventType == tree.TriggerEventInsert || eventType == tree.TriggerEventUpdate) {
mb.recomputeComputedColsForTrigger(eventType)
}
return true
Expand Down
2 changes: 1 addition & 1 deletion pkg/sql/opt/optbuilder/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func (b *Builder) buildUpdate(upd *tree.Update, inScope *scope) (outScope *scope
mb.addUpdateCols(upd.Exprs, &exprColRefs)

// Project row-level BEFORE triggers for UPDATE.
mb.buildRowLevelBeforeTriggers(tree.TriggerEventUpdate, false /* cascade */)
mb.buildRowLevelBeforeTriggers(tree.TriggerEventUpdate, false /* cascade */, true /* recomputeCols */)

// Build the final update statement, including any returned expressions.
var returningExpr *tree.ReturningExprs
Expand Down