exec-ddl
CREATE TABLE xy (x INT PRIMARY KEY, y INT);
----

exec-ddl
CREATE TABLE child (k INT PRIMARY KEY, x INT REFERENCES xy(x) ON UPDATE CASCADE ON DELETE CASCADE);
----

exec-ddl
CREATE TABLE computed (k INT PRIMARY KEY, v INT AS (k + 1) STORED, w INT AS (k + 2) VIRTUAL);
----

exec-ddl
CREATE TABLE ab (a INT, b INT);
----

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

# ------------------------------------------------------------------------------
# Row-level BEFORE triggers.
# ------------------------------------------------------------------------------

exec-ddl
CREATE TRIGGER tr BEFORE INSERT OR UPDATE OR DELETE ON xy FOR EACH ROW EXECUTE FUNCTION f();
----

exec-ddl
CREATE TRIGGER tr_child BEFORE INSERT OR UPDATE OR DELETE ON child FOR EACH ROW EXECUTE FUNCTION f();
----

norm format=(hide-all,show-columns)
INSERT INTO xy VALUES (1, 2);
----
insert xy
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── x_new:22 => x:1
 │    └── y_new:23 => y:2
 ├── before-triggers
 │    └── tr
 └── project
      ├── columns: x_new:22 y_new:23 column1:5 column2:6 new:7 f:21
      ├── barrier
      │    ├── columns: column1:5 column2:6 new:7 f:21
      │    └── select
      │         ├── columns: column1:5 column2:6 new:7 f:21
      │         ├── project
      │         │    ├── columns: f:21 column1:5 column2:6 new:7
      │         │    ├── barrier
      │         │    │    ├── columns: column1:5 column2:6 new:7
      │         │    │    └── values
      │         │    │         ├── columns: column1:5 column2:6 new:7
      │         │    │         └── (1, 2, ((1, 2) AS x, y))
      │         │    └── projections
      │         │         └── f(new:7, NULL, 'tr', 'BEFORE', 'ROW', 'INSERT', 53, 'xy', 'xy', 'public', 0, ARRAY[]) [as=f:21]
      │         └── filters
      │              └── f:21 IS DISTINCT FROM NULL
      └── projections
           ├── (f:21).x [as=x_new:22]
           └── (f:21).y [as=y_new:23]

build-post-queries format=(hide-all,show-columns)
UPDATE xy SET y = 3 WHERE x = 1;
----
root
 ├── update xy
 │    ├── columns: <none>
 │    ├── fetch columns: x:5 y:6
 │    ├── update-mapping:
 │    │    ├── x_new:26 => x:1
 │    │    └── y_new:27 => y:2
 │    ├── before-triggers
 │    │    └── tr
 │    ├── input binding: &1
 │    ├── cascades
 │    │    └── child_x_fkey
 │    └── project
 │         ├── columns: x_new:26 y_new:27 x:5 y:6 crdb_internal_mvcc_timestamp:7 tableoid:8 y_new:9 old:10 new:11 f:25
 │         ├── barrier
 │         │    ├── columns: x:5 y:6 crdb_internal_mvcc_timestamp:7 tableoid:8 y_new:9 old:10 new:11 f:25
 │         │    └── select
 │         │         ├── columns: x:5 y:6 crdb_internal_mvcc_timestamp:7 tableoid:8 y_new:9 old:10 new:11 f:25
 │         │         ├── project
 │         │         │    ├── columns: f:25 x:5 y:6 crdb_internal_mvcc_timestamp:7 tableoid:8 y_new:9 old:10 new:11
 │         │         │    ├── barrier
 │         │         │    │    ├── columns: x:5 y:6 crdb_internal_mvcc_timestamp:7 tableoid:8 y_new:9 old:10 new:11
 │         │         │    │    └── project
 │         │         │    │         ├── columns: new:11 x:5 y:6 crdb_internal_mvcc_timestamp:7 tableoid:8 y_new:9 old:10
 │         │         │    │         ├── project
 │         │         │    │         │    ├── columns: old:10 x:5 y:6 crdb_internal_mvcc_timestamp:7 tableoid:8 y_new:9
 │         │         │    │         │    ├── project
 │         │         │    │         │    │    ├── columns: y_new:9 x:5 y:6 crdb_internal_mvcc_timestamp:7 tableoid:8
 │         │         │    │         │    │    ├── select
 │         │         │    │         │    │    │    ├── columns: x:5 y:6 crdb_internal_mvcc_timestamp:7 tableoid:8
 │         │         │    │         │    │    │    ├── scan xy
 │         │         │    │         │    │    │    │    ├── columns: x:5 y:6 crdb_internal_mvcc_timestamp:7 tableoid:8
 │         │         │    │         │    │    │    │    └── flags: avoid-full-scan
 │         │         │    │         │    │    │    └── filters
 │         │         │    │         │    │    │         └── x:5 = 1
 │         │         │    │         │    │    └── projections
 │         │         │    │         │    │         └── 3 [as=y_new:9]
 │         │         │    │         │    └── projections
 │         │         │    │         │         └── ((x:5, y:6) AS x, y) [as=old:10]
 │         │         │    │         └── projections
 │         │         │    │              └── ((x:5, y_new:9) AS x, y) [as=new:11]
 │         │         │    └── projections
 │         │         │         └── f(new:11, old:10, 'tr', 'BEFORE', 'ROW', 'UPDATE', 53, 'xy', 'xy', 'public', 0, ARRAY[]) [as=f:25]
 │         │         └── filters
 │         │              └── f:25 IS DISTINCT FROM NULL
 │         └── projections
 │              ├── (f:25).x [as=x_new:26]
 │              └── (f:25).y [as=y_new:27]
 └── cascade
      └── update child
           ├── columns: <none>
           ├── fetch columns: k:32 child.x:33
           ├── update-mapping:
           │    └── x_new:37 => child.x:29
           ├── before-triggers
           │    └── tr_child
           ├── input binding: &2
           ├── barrier
           │    ├── columns: k:32 child.x:33 x_old:36 x_new:37 old:38 new:39 f:53 "check-rows":54
           │    └── select
           │         ├── columns: k:32 child.x:33 x_old:36 x_new:37 old:38 new:39 f:53 "check-rows":54
           │         ├── barrier
           │         │    ├── columns: k:32 child.x:33 x_old:36 x_new:37 old:38 new:39 f:53 "check-rows":54
           │         │    └── project
           │         │         ├── columns: "check-rows":54 k:32 child.x:33 x_old:36 x_new:37 old:38 new:39 f:53
           │         │         ├── project
           │         │         │    ├── columns: f:53 k:32 child.x:33 x_old:36 x_new:37 old:38 new:39
           │         │         │    ├── barrier
           │         │         │    │    ├── columns: k:32 child.x:33 x_old:36 x_new:37 old:38 new:39
           │         │         │    │    └── project
           │         │         │    │         ├── columns: new:39 k:32 child.x:33 x_old:36 x_new:37 old:38
           │         │         │    │         ├── project
           │         │         │    │         │    ├── columns: old:38 k:32 child.x:33 x_old:36 x_new:37
           │         │         │    │         │    ├── inner-join (hash)
           │         │         │    │         │    │    ├── columns: k:32 child.x:33 x_old:36 x_new:37
           │         │         │    │         │    │    ├── scan child
           │         │         │    │         │    │    │    ├── columns: k:32 child.x:33
           │         │         │    │         │    │    │    └── flags: avoid-full-scan
           │         │         │    │         │    │    ├── select
           │         │         │    │         │    │    │    ├── columns: x_old:36 x_new:37
           │         │         │    │         │    │    │    ├── with-scan &1
           │         │         │    │         │    │    │    │    ├── columns: x_old:36 x_new:37
           │         │         │    │         │    │    │    │    └── mapping:
           │         │         │    │         │    │    │    │         ├──  xy.x:5 => x_old:36
           │         │         │    │         │    │    │    │         └──  x_new:26 => x_new:37
           │         │         │    │         │    │    │    └── filters
           │         │         │    │         │    │    │         └── x_old:36 IS DISTINCT FROM x_new:37
           │         │         │    │         │    │    └── filters
           │         │         │    │         │    │         └── child.x:33 = x_old:36
           │         │         │    │         │    └── projections
           │         │         │    │         │         └── ((k:32, child.x:33) AS k, x) [as=old:38]
           │         │         │    │         └── projections
           │         │         │    │              └── ((k:32, x_new:37) AS k, x) [as=new:39]
           │         │         │    └── projections
           │         │         │         └── f(new:39, old:38, 'tr_child', 'BEFORE', 'ROW', 'UPDATE', 54, 'child', 'child', 'public', 0, ARRAY[]) [as=f:53]
           │         │         └── projections
           │         │              └── CASE WHEN f:53 IS DISTINCT FROM new:39 THEN crdb_internal.plpgsql_raise('ERROR', 'trigger tr_child attempted to modify or filter a row in a cascade operation: ' || new:39::STRING, e'changing the rows updated or deleted by a foreign-key cascade\n can cause constraint violations, and therefore is not allowed', e'to enable this behavior (with risk of constraint violation), set\nthe session variable \'unsafe_allow_triggers_modifying_cascades\' to true', '27000') ELSE CAST(NULL AS INT8) END [as="check-rows":54]
           │         └── filters
           │              └── f:53 IS DISTINCT FROM NULL
           └── f-k-checks
                └── f-k-checks-item: child(x) -> xy(x)
                     └── anti-join (hash)
                          ├── columns: x:55
                          ├── select
                          │    ├── columns: x:55
                          │    ├── with-scan &2
                          │    │    ├── columns: x:55
                          │    │    └── mapping:
                          │    │         └──  x_new:37 => x:55
                          │    └── filters
                          │         └── x:55 IS NOT NULL
                          ├── scan xy
                          │    ├── columns: xy.x:56
                          │    └── flags: avoid-full-scan
                          └── filters
                               └── x:55 = xy.x:56

build-post-queries format=(hide-all,show-columns)
DELETE FROM xy WHERE x = 1;
----
root
 ├── delete xy
 │    ├── columns: <none>
 │    ├── fetch columns: x:5 y:6
 │    ├── before-triggers
 │    │    └── tr
 │    ├── input binding: &1
 │    ├── cascades
 │    │    └── child_x_fkey
 │    └── barrier
 │         ├── columns: x:5 y:6 crdb_internal_mvcc_timestamp:7 tableoid:8 old:9 f:23
 │         └── select
 │              ├── columns: x:5 y:6 crdb_internal_mvcc_timestamp:7 tableoid:8 old:9 f:23
 │              ├── project
 │              │    ├── columns: f:23 x:5 y:6 crdb_internal_mvcc_timestamp:7 tableoid:8 old:9
 │              │    ├── barrier
 │              │    │    ├── columns: x:5 y:6 crdb_internal_mvcc_timestamp:7 tableoid:8 old:9
 │              │    │    └── project
 │              │    │         ├── columns: old:9 x:5 y:6 crdb_internal_mvcc_timestamp:7 tableoid:8
 │              │    │         ├── select
 │              │    │         │    ├── columns: x:5 y:6 crdb_internal_mvcc_timestamp:7 tableoid:8
 │              │    │         │    ├── scan xy
 │              │    │         │    │    ├── columns: x:5 y:6 crdb_internal_mvcc_timestamp:7 tableoid:8
 │              │    │         │    │    └── flags: avoid-full-scan
 │              │    │         │    └── filters
 │              │    │         │         └── x:5 = 1
 │              │    │         └── projections
 │              │    │              └── ((x:5, y:6) AS x, y) [as=old:9]
 │              │    └── projections
 │              │         └── f(NULL, old:9, 'tr', 'BEFORE', 'ROW', 'DELETE', 53, 'xy', 'xy', 'public', 0, ARRAY[]) [as=f:23]
 │              └── filters
 │                   └── f:23 IS DISTINCT FROM NULL
 └── cascade
      └── delete child
           ├── columns: <none>
           ├── fetch columns: k:28 child.x:29
           ├── before-triggers
           │    └── tr_child
           └── barrier
                ├── columns: k:28 child.x:29 old:33 f:47 "check-rows":48
                └── select
                     ├── columns: k:28 child.x:29 old:33 f:47 "check-rows":48
                     ├── barrier
                     │    ├── columns: k:28 child.x:29 old:33 f:47 "check-rows":48
                     │    └── project
                     │         ├── columns: "check-rows":48 k:28 child.x:29 old:33 f:47
                     │         ├── project
                     │         │    ├── columns: f:47 k:28 child.x:29 old:33
                     │         │    ├── barrier
                     │         │    │    ├── columns: k:28 child.x:29 old:33
                     │         │    │    └── project
                     │         │    │         ├── columns: old:33 k:28 child.x:29
                     │         │    │         ├── semi-join (hash)
                     │         │    │         │    ├── columns: k:28 child.x:29
                     │         │    │         │    ├── scan child
                     │         │    │         │    │    ├── columns: k:28 child.x:29
                     │         │    │         │    │    └── flags: avoid-full-scan
                     │         │    │         │    ├── with-scan &1
                     │         │    │         │    │    ├── columns: x:32
                     │         │    │         │    │    └── mapping:
                     │         │    │         │    │         └──  xy.x:5 => x:32
                     │         │    │         │    └── filters
                     │         │    │         │         └── child.x:29 = x:32
                     │         │    │         └── projections
                     │         │    │              └── ((k:28, child.x:29) AS k, x) [as=old:33]
                     │         │    └── projections
                     │         │         └── f(NULL, old:33, 'tr_child', 'BEFORE', 'ROW', 'DELETE', 54, 'child', 'child', 'public', 0, ARRAY[]) [as=f:47]
                     │         └── projections
                     │              └── CASE WHEN f:47 IS DISTINCT FROM old:33 THEN crdb_internal.plpgsql_raise('ERROR', 'trigger tr_child attempted to modify or filter a row in a cascade operation: ' || old:33::STRING, e'changing the rows updated or deleted by a foreign-key cascade\n can cause constraint violations, and therefore is not allowed', e'to enable this behavior (with risk of constraint violation), set\nthe session variable \'unsafe_allow_triggers_modifying_cascades\' to true', '27000') ELSE CAST(NULL AS INT8) END [as="check-rows":48]
                     └── filters
                          └── f:47 IS DISTINCT FROM NULL

build-post-queries format=(hide-all,show-columns)
UPSERT INTO xy VALUES (1, 2);
----
root
 ├── upsert xy
 │    ├── arbiter indexes: xy_pkey
 │    ├── columns: <none>
 │    ├── canary column: x:7
 │    ├── fetch columns: x:7 y:8
 │    ├── insert-mapping:
 │    │    ├── x_new:26 => x:1
 │    │    └── y_new:27 => y:2
 │    ├── update-mapping:
 │    │    ├── upsert_x:33 => x:1
 │    │    └── upsert_y:34 => y:2
 │    ├── before-triggers
 │    │    └── tr
 │    ├── input binding: &1
 │    ├── cascades
 │    │    └── child_x_fkey
 │    └── project
 │         ├── columns: upsert_x:33 upsert_y:34 column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10 new:11 f:25 x_new:26 y_new:27 old:28 new:29 f:30 x_new:31 y_new:32
 │         ├── project
 │         │    ├── columns: x_new:31 y_new:32 column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10 new:11 f:25 x_new:26 y_new:27 old:28 new:29 f:30
 │         │    ├── barrier
 │         │    │    ├── columns: column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10 new:11 f:25 x_new:26 y_new:27 old:28 new:29 f:30
 │         │    │    └── select
 │         │    │         ├── columns: column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10 new:11 f:25 x_new:26 y_new:27 old:28 new:29 f:30
 │         │    │         ├── project
 │         │    │         │    ├── columns: f:30 column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10 new:11 f:25 x_new:26 y_new:27 old:28 new:29
 │         │    │         │    ├── barrier
 │         │    │         │    │    ├── columns: column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10 new:11 f:25 x_new:26 y_new:27 old:28 new:29
 │         │    │         │    │    └── project
 │         │    │         │    │         ├── columns: new:29 column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10 new:11 f:25 x_new:26 y_new:27 old:28
 │         │    │         │    │         ├── project
 │         │    │         │    │         │    ├── columns: old:28 column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10 new:11 f:25 x_new:26 y_new:27
 │         │    │         │    │         │    ├── project
 │         │    │         │    │         │    │    ├── columns: x_new:26 y_new:27 column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10 new:11 f:25
 │         │    │         │    │         │    │    ├── barrier
 │         │    │         │    │         │    │    │    ├── columns: column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10 new:11 f:25
 │         │    │         │    │         │    │    │    └── select
 │         │    │         │    │         │    │    │         ├── columns: column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10 new:11 f:25
 │         │    │         │    │         │    │    │         ├── project
 │         │    │         │    │         │    │    │         │    ├── columns: f:25 column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10 new:11
 │         │    │         │    │         │    │    │         │    ├── barrier
 │         │    │         │    │         │    │    │         │    │    ├── columns: column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10 new:11
 │         │    │         │    │         │    │    │         │    │    └── project
 │         │    │         │    │         │    │    │         │    │         ├── columns: new:11 column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10
 │         │    │         │    │         │    │    │         │    │         ├── left-join (hash)
 │         │    │         │    │         │    │    │         │    │         │    ├── columns: column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10
 │         │    │         │    │         │    │    │         │    │         │    ├── ensure-upsert-distinct-on
 │         │    │         │    │         │    │    │         │    │         │    │    ├── columns: column1:5 column2:6
 │         │    │         │    │         │    │    │         │    │         │    │    ├── grouping columns: column1:5
 │         │    │         │    │         │    │    │         │    │         │    │    ├── values
 │         │    │         │    │         │    │    │         │    │         │    │    │    ├── columns: column1:5 column2:6
 │         │    │         │    │         │    │    │         │    │         │    │    │    └── (1, 2)
 │         │    │         │    │         │    │    │         │    │         │    │    └── aggregations
 │         │    │         │    │         │    │    │         │    │         │    │         └── first-agg [as=column2:6]
 │         │    │         │    │         │    │    │         │    │         │    │              └── column2:6
 │         │    │         │    │         │    │    │         │    │         │    ├── scan xy
 │         │    │         │    │         │    │    │         │    │         │    │    ├── columns: x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10
 │         │    │         │    │         │    │    │         │    │         │    │    └── flags: avoid-full-scan
 │         │    │         │    │         │    │    │         │    │         │    └── filters
 │         │    │         │    │         │    │    │         │    │         │         └── column1:5 = x:7
 │         │    │         │    │         │    │    │         │    │         └── projections
 │         │    │         │    │         │    │    │         │    │              └── ((column1:5, column2:6) AS x, y) [as=new:11]
 │         │    │         │    │         │    │    │         │    └── projections
 │         │    │         │    │         │    │    │         │         └── f(new:11, NULL, 'tr', 'BEFORE', 'ROW', 'INSERT', 53, 'xy', 'xy', 'public', 0, ARRAY[]) [as=f:25]
 │         │    │         │    │         │    │    │         └── filters
 │         │    │         │    │         │    │    │              └── f:25 IS DISTINCT FROM NULL
 │         │    │         │    │         │    │    └── projections
 │         │    │         │    │         │    │         ├── (f:25).x [as=x_new:26]
 │         │    │         │    │         │    │         └── (f:25).y [as=y_new:27]
 │         │    │         │    │         │    └── projections
 │         │    │         │    │         │         └── ((x:7, y:8) AS x, y) [as=old:28]
 │         │    │         │    │         └── projections
 │         │    │         │    │              └── ((x:7, y_new:27) AS x, y) [as=new:29]
 │         │    │         │    └── projections
 │         │    │         │         └── CASE WHEN x:7 IS NOT NULL THEN f(new:29, old:28, 'tr', 'BEFORE', 'ROW', 'UPDATE', 53, 'xy', 'xy', 'public', 0, ARRAY[]) ELSE new:29 END [as=f:30]
 │         │    │         └── filters
 │         │    │              └── f:30 IS DISTINCT FROM NULL
 │         │    └── projections
 │         │         ├── (f:30).x [as=x_new:31]
 │         │         └── (f:30).y [as=y_new:32]
 │         └── projections
 │              ├── CASE WHEN x:7 IS NULL THEN x_new:26 ELSE x_new:31 END [as=upsert_x:33]
 │              └── CASE WHEN x:7 IS NULL THEN y_new:27 ELSE y_new:32 END [as=upsert_y:34]
 └── cascade
      └── update child
           ├── columns: <none>
           ├── fetch columns: k:39 child.x:40
           ├── update-mapping:
           │    └── x_new:44 => child.x:36
           ├── before-triggers
           │    └── tr_child
           ├── input binding: &2
           ├── barrier
           │    ├── columns: k:39 child.x:40 x_old:43 x_new:44 old:45 new:46 f:60 "check-rows":61
           │    └── select
           │         ├── columns: k:39 child.x:40 x_old:43 x_new:44 old:45 new:46 f:60 "check-rows":61
           │         ├── barrier
           │         │    ├── columns: k:39 child.x:40 x_old:43 x_new:44 old:45 new:46 f:60 "check-rows":61
           │         │    └── project
           │         │         ├── columns: "check-rows":61 k:39 child.x:40 x_old:43 x_new:44 old:45 new:46 f:60
           │         │         ├── project
           │         │         │    ├── columns: f:60 k:39 child.x:40 x_old:43 x_new:44 old:45 new:46
           │         │         │    ├── barrier
           │         │         │    │    ├── columns: k:39 child.x:40 x_old:43 x_new:44 old:45 new:46
           │         │         │    │    └── project
           │         │         │    │         ├── columns: new:46 k:39 child.x:40 x_old:43 x_new:44 old:45
           │         │         │    │         ├── project
           │         │         │    │         │    ├── columns: old:45 k:39 child.x:40 x_old:43 x_new:44
           │         │         │    │         │    ├── inner-join (hash)
           │         │         │    │         │    │    ├── columns: k:39 child.x:40 x_old:43 x_new:44
           │         │         │    │         │    │    ├── scan child
           │         │         │    │         │    │    │    ├── columns: k:39 child.x:40
           │         │         │    │         │    │    │    └── flags: avoid-full-scan
           │         │         │    │         │    │    ├── select
           │         │         │    │         │    │    │    ├── columns: x_old:43 x_new:44
           │         │         │    │         │    │    │    ├── with-scan &1
           │         │         │    │         │    │    │    │    ├── columns: x_old:43 x_new:44
           │         │         │    │         │    │    │    │    └── mapping:
           │         │         │    │         │    │    │    │         ├──  xy.x:7 => x_old:43
           │         │         │    │         │    │    │    │         └──  upsert_x:33 => x_new:44
           │         │         │    │         │    │    │    └── filters
           │         │         │    │         │    │    │         └── x_old:43 IS DISTINCT FROM x_new:44
           │         │         │    │         │    │    └── filters
           │         │         │    │         │    │         └── child.x:40 = x_old:43
           │         │         │    │         │    └── projections
           │         │         │    │         │         └── ((k:39, child.x:40) AS k, x) [as=old:45]
           │         │         │    │         └── projections
           │         │         │    │              └── ((k:39, x_new:44) AS k, x) [as=new:46]
           │         │         │    └── projections
           │         │         │         └── f(new:46, old:45, 'tr_child', 'BEFORE', 'ROW', 'UPDATE', 54, 'child', 'child', 'public', 0, ARRAY[]) [as=f:60]
           │         │         └── projections
           │         │              └── CASE WHEN f:60 IS DISTINCT FROM new:46 THEN crdb_internal.plpgsql_raise('ERROR', 'trigger tr_child attempted to modify or filter a row in a cascade operation: ' || new:46::STRING, e'changing the rows updated or deleted by a foreign-key cascade\n can cause constraint violations, and therefore is not allowed', e'to enable this behavior (with risk of constraint violation), set\nthe session variable \'unsafe_allow_triggers_modifying_cascades\' to true', '27000') ELSE CAST(NULL AS INT8) END [as="check-rows":61]
           │         └── filters
           │              └── f:60 IS DISTINCT FROM NULL
           └── f-k-checks
                └── f-k-checks-item: child(x) -> xy(x)
                     └── anti-join (hash)
                          ├── columns: x:62
                          ├── select
                          │    ├── columns: x:62
                          │    ├── with-scan &2
                          │    │    ├── columns: x:62
                          │    │    └── mapping:
                          │    │         └──  x_new:44 => x:62
                          │    └── filters
                          │         └── x:62 IS NOT NULL
                          ├── scan xy
                          │    ├── columns: xy.x:63
                          │    └── flags: avoid-full-scan
                          └── filters
                               └── x:62 = xy.x:63

build-post-queries format=(hide-all,show-columns)
INSERT INTO xy VALUES (1, 2) ON CONFLICT (x) DO UPDATE SET y = 3;
----
root
 ├── upsert xy
 │    ├── arbiter indexes: xy_pkey
 │    ├── columns: <none>
 │    ├── canary column: x:7
 │    ├── fetch columns: x:7 y:8
 │    ├── insert-mapping:
 │    │    ├── x_new:26 => x:1
 │    │    └── y_new:27 => y:2
 │    ├── update-mapping:
 │    │    ├── upsert_x:34 => x:1
 │    │    └── upsert_y:35 => y:2
 │    ├── before-triggers
 │    │    └── tr
 │    ├── input binding: &1
 │    ├── cascades
 │    │    └── child_x_fkey
 │    └── project
 │         ├── columns: upsert_x:34 upsert_y:35 column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10 new:11 f:25 x_new:26 y_new:27 y_new:28 old:29 new:30 f:31 x_new:32 y_new:33
 │         ├── project
 │         │    ├── columns: x_new:32 y_new:33 column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10 new:11 f:25 x_new:26 y_new:27 y_new:28 old:29 new:30 f:31
 │         │    ├── barrier
 │         │    │    ├── columns: column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10 new:11 f:25 x_new:26 y_new:27 y_new:28 old:29 new:30 f:31
 │         │    │    └── select
 │         │    │         ├── columns: column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10 new:11 f:25 x_new:26 y_new:27 y_new:28 old:29 new:30 f:31
 │         │    │         ├── project
 │         │    │         │    ├── columns: f:31 column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10 new:11 f:25 x_new:26 y_new:27 y_new:28 old:29 new:30
 │         │    │         │    ├── barrier
 │         │    │         │    │    ├── columns: column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10 new:11 f:25 x_new:26 y_new:27 y_new:28 old:29 new:30
 │         │    │         │    │    └── project
 │         │    │         │    │         ├── columns: new:30 column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10 new:11 f:25 x_new:26 y_new:27 y_new:28 old:29
 │         │    │         │    │         ├── project
 │         │    │         │    │         │    ├── columns: old:29 column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10 new:11 f:25 x_new:26 y_new:27 y_new:28
 │         │    │         │    │         │    ├── project
 │         │    │         │    │         │    │    ├── columns: y_new:28 column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10 new:11 f:25 x_new:26 y_new:27
 │         │    │         │    │         │    │    ├── project
 │         │    │         │    │         │    │    │    ├── columns: x_new:26 y_new:27 column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10 new:11 f:25
 │         │    │         │    │         │    │    │    ├── barrier
 │         │    │         │    │         │    │    │    │    ├── columns: column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10 new:11 f:25
 │         │    │         │    │         │    │    │    │    └── select
 │         │    │         │    │         │    │    │    │         ├── columns: column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10 new:11 f:25
 │         │    │         │    │         │    │    │    │         ├── project
 │         │    │         │    │         │    │    │    │         │    ├── columns: f:25 column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10 new:11
 │         │    │         │    │         │    │    │    │         │    ├── barrier
 │         │    │         │    │         │    │    │    │         │    │    ├── columns: column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10 new:11
 │         │    │         │    │         │    │    │    │         │    │    └── project
 │         │    │         │    │         │    │    │    │         │    │         ├── columns: new:11 column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10
 │         │    │         │    │         │    │    │    │         │    │         ├── left-join (hash)
 │         │    │         │    │         │    │    │    │         │    │         │    ├── columns: column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10
 │         │    │         │    │         │    │    │    │         │    │         │    ├── ensure-upsert-distinct-on
 │         │    │         │    │         │    │    │    │         │    │         │    │    ├── columns: column1:5 column2:6
 │         │    │         │    │         │    │    │    │         │    │         │    │    ├── grouping columns: column1:5
 │         │    │         │    │         │    │    │    │         │    │         │    │    ├── values
 │         │    │         │    │         │    │    │    │         │    │         │    │    │    ├── columns: column1:5 column2:6
 │         │    │         │    │         │    │    │    │         │    │         │    │    │    └── (1, 2)
 │         │    │         │    │         │    │    │    │         │    │         │    │    └── aggregations
 │         │    │         │    │         │    │    │    │         │    │         │    │         └── first-agg [as=column2:6]
 │         │    │         │    │         │    │    │    │         │    │         │    │              └── column2:6
 │         │    │         │    │         │    │    │    │         │    │         │    ├── scan xy
 │         │    │         │    │         │    │    │    │         │    │         │    │    ├── columns: x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10
 │         │    │         │    │         │    │    │    │         │    │         │    │    └── flags: avoid-full-scan
 │         │    │         │    │         │    │    │    │         │    │         │    └── filters
 │         │    │         │    │         │    │    │    │         │    │         │         └── column1:5 = x:7
 │         │    │         │    │         │    │    │    │         │    │         └── projections
 │         │    │         │    │         │    │    │    │         │    │              └── ((column1:5, column2:6) AS x, y) [as=new:11]
 │         │    │         │    │         │    │    │    │         │    └── projections
 │         │    │         │    │         │    │    │    │         │         └── f(new:11, NULL, 'tr', 'BEFORE', 'ROW', 'INSERT', 53, 'xy', 'xy', 'public', 0, ARRAY[]) [as=f:25]
 │         │    │         │    │         │    │    │    │         └── filters
 │         │    │         │    │         │    │    │    │              └── f:25 IS DISTINCT FROM NULL
 │         │    │         │    │         │    │    │    └── projections
 │         │    │         │    │         │    │    │         ├── (f:25).x [as=x_new:26]
 │         │    │         │    │         │    │    │         └── (f:25).y [as=y_new:27]
 │         │    │         │    │         │    │    └── projections
 │         │    │         │    │         │    │         └── 3 [as=y_new:28]
 │         │    │         │    │         │    └── projections
 │         │    │         │    │         │         └── ((x:7, y:8) AS x, y) [as=old:29]
 │         │    │         │    │         └── projections
 │         │    │         │    │              └── ((x:7, y_new:28) AS x, y) [as=new:30]
 │         │    │         │    └── projections
 │         │    │         │         └── CASE WHEN x:7 IS NOT NULL THEN f(new:30, old:29, 'tr', 'BEFORE', 'ROW', 'UPDATE', 53, 'xy', 'xy', 'public', 0, ARRAY[]) ELSE new:30 END [as=f:31]
 │         │    │         └── filters
 │         │    │              └── f:31 IS DISTINCT FROM NULL
 │         │    └── projections
 │         │         ├── (f:31).x [as=x_new:32]
 │         │         └── (f:31).y [as=y_new:33]
 │         └── projections
 │              ├── CASE WHEN x:7 IS NULL THEN x_new:26 ELSE x_new:32 END [as=upsert_x:34]
 │              └── CASE WHEN x:7 IS NULL THEN y_new:27 ELSE y_new:33 END [as=upsert_y:35]
 └── cascade
      └── update child
           ├── columns: <none>
           ├── fetch columns: k:40 child.x:41
           ├── update-mapping:
           │    └── x_new:45 => child.x:37
           ├── before-triggers
           │    └── tr_child
           ├── input binding: &2
           ├── barrier
           │    ├── columns: k:40 child.x:41 x_old:44 x_new:45 old:46 new:47 f:61 "check-rows":62
           │    └── select
           │         ├── columns: k:40 child.x:41 x_old:44 x_new:45 old:46 new:47 f:61 "check-rows":62
           │         ├── barrier
           │         │    ├── columns: k:40 child.x:41 x_old:44 x_new:45 old:46 new:47 f:61 "check-rows":62
           │         │    └── project
           │         │         ├── columns: "check-rows":62 k:40 child.x:41 x_old:44 x_new:45 old:46 new:47 f:61
           │         │         ├── project
           │         │         │    ├── columns: f:61 k:40 child.x:41 x_old:44 x_new:45 old:46 new:47
           │         │         │    ├── barrier
           │         │         │    │    ├── columns: k:40 child.x:41 x_old:44 x_new:45 old:46 new:47
           │         │         │    │    └── project
           │         │         │    │         ├── columns: new:47 k:40 child.x:41 x_old:44 x_new:45 old:46
           │         │         │    │         ├── project
           │         │         │    │         │    ├── columns: old:46 k:40 child.x:41 x_old:44 x_new:45
           │         │         │    │         │    ├── inner-join (hash)
           │         │         │    │         │    │    ├── columns: k:40 child.x:41 x_old:44 x_new:45
           │         │         │    │         │    │    ├── scan child
           │         │         │    │         │    │    │    ├── columns: k:40 child.x:41
           │         │         │    │         │    │    │    └── flags: avoid-full-scan
           │         │         │    │         │    │    ├── select
           │         │         │    │         │    │    │    ├── columns: x_old:44 x_new:45
           │         │         │    │         │    │    │    ├── with-scan &1
           │         │         │    │         │    │    │    │    ├── columns: x_old:44 x_new:45
           │         │         │    │         │    │    │    │    └── mapping:
           │         │         │    │         │    │    │    │         ├──  xy.x:7 => x_old:44
           │         │         │    │         │    │    │    │         └──  upsert_x:34 => x_new:45
           │         │         │    │         │    │    │    └── filters
           │         │         │    │         │    │    │         └── x_old:44 IS DISTINCT FROM x_new:45
           │         │         │    │         │    │    └── filters
           │         │         │    │         │    │         └── child.x:41 = x_old:44
           │         │         │    │         │    └── projections
           │         │         │    │         │         └── ((k:40, child.x:41) AS k, x) [as=old:46]
           │         │         │    │         └── projections
           │         │         │    │              └── ((k:40, x_new:45) AS k, x) [as=new:47]
           │         │         │    └── projections
           │         │         │         └── f(new:47, old:46, 'tr_child', 'BEFORE', 'ROW', 'UPDATE', 54, 'child', 'child', 'public', 0, ARRAY[]) [as=f:61]
           │         │         └── projections
           │         │              └── CASE WHEN f:61 IS DISTINCT FROM new:47 THEN crdb_internal.plpgsql_raise('ERROR', 'trigger tr_child attempted to modify or filter a row in a cascade operation: ' || new:47::STRING, e'changing the rows updated or deleted by a foreign-key cascade\n can cause constraint violations, and therefore is not allowed', e'to enable this behavior (with risk of constraint violation), set\nthe session variable \'unsafe_allow_triggers_modifying_cascades\' to true', '27000') ELSE CAST(NULL AS INT8) END [as="check-rows":62]
           │         └── filters
           │              └── f:61 IS DISTINCT FROM NULL
           └── f-k-checks
                └── f-k-checks-item: child(x) -> xy(x)
                     └── anti-join (hash)
                          ├── columns: x:63
                          ├── select
                          │    ├── columns: x:63
                          │    ├── with-scan &2
                          │    │    ├── columns: x:63
                          │    │    └── mapping:
                          │    │         └──  x_new:45 => x:63
                          │    └── filters
                          │         └── x:63 IS NOT NULL
                          ├── scan xy
                          │    ├── columns: xy.x:64
                          │    └── flags: avoid-full-scan
                          └── filters
                               └── x:63 = xy.x:64

# Show interaction with computed columns.
exec-ddl
CREATE TRIGGER tr BEFORE INSERT OR UPDATE ON computed FOR EACH ROW
WHEN ((NEW).v IS NULL AND (NEW).w IS NULL) EXECUTE FUNCTION f();
----

norm format=(hide-all,show-columns)
INSERT INTO computed (k) VALUES (1);
----
insert computed
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── k_new:24 => k:1
 │    ├── v_comp:25 => v:2
 │    └── w_comp:26 => w:3
 ├── before-triggers
 │    └── tr
 └── project
      ├── columns: v_comp:25 w_comp:26 column1:6 v_comp:7 w_comp:8 new:9 f:23 k_new:24
      ├── project
      │    ├── columns: k_new:24 column1:6 v_comp:7 w_comp:8 new:9 f:23
      │    ├── barrier
      │    │    ├── columns: column1:6 v_comp:7 w_comp:8 new:9 f:23
      │    │    └── select
      │    │         ├── columns: column1:6 v_comp:7 w_comp:8 new:9 f:23
      │    │         ├── project
      │    │         │    ├── columns: f:23 column1:6 v_comp:7 w_comp:8 new:9
      │    │         │    ├── barrier
      │    │         │    │    ├── columns: column1:6 v_comp:7 w_comp:8 new:9
      │    │         │    │    └── values
      │    │         │    │         ├── columns: column1:6 v_comp:7 w_comp:8 new:9
      │    │         │    │         └── (1, 2, 3, ((1, NULL, NULL) AS k, v, w))
      │    │         │    └── projections
      │    │         │         └── CASE WHEN ((new:9).v IS NULL) AND ((new:9).w IS NULL) THEN f(new:9, NULL, 'tr', 'BEFORE', 'ROW', 'INSERT', 55, 'computed', 'computed', 'public', 0, ARRAY[]) ELSE new:9 END [as=f:23]
      │    │         └── filters
      │    │              └── f:23 IS DISTINCT FROM NULL
      │    └── projections
      │         └── (f:23).k [as=k_new:24]
      └── projections
           ├── k_new:24 + 1 [as=v_comp:25]
           └── k_new:24 + 2 [as=w_comp:26]

norm format=(hide-all,show-columns)
UPDATE computed SET k = 2 WHERE k = 1;
----
update computed
 ├── columns: <none>
 ├── fetch columns: k:6 v:7 w:8
 ├── update-mapping:
 │    ├── k_new:30 => k:1
 │    ├── v_comp:31 => v:2
 │    └── w_comp:32 => w:3
 ├── before-triggers
 │    └── tr
 └── project
      ├── columns: v_comp:31 w_comp:32 k:6 v:7 w:8 k_new:30
      ├── project
      │    ├── columns: k_new:30 k:6 v:7 w:8
      │    ├── barrier
      │    │    ├── columns: k:6 v:7 w:8 crdb_internal_mvcc_timestamp:9 tableoid:10 k_new:11 v_comp:12 w_comp:13 old:14 new:15 f:29
      │    │    └── select
      │    │         ├── columns: k:6 v:7 w:8 crdb_internal_mvcc_timestamp:9 tableoid:10 k_new:11 v_comp:12 w_comp:13 old:14 new:15 f:29
      │    │         ├── project
      │    │         │    ├── columns: f:29 k:6 v:7 w:8 crdb_internal_mvcc_timestamp:9 tableoid:10 k_new:11 v_comp:12 w_comp:13 old:14 new:15
      │    │         │    ├── barrier
      │    │         │    │    ├── columns: k:6 v:7 w:8 crdb_internal_mvcc_timestamp:9 tableoid:10 k_new:11 v_comp:12 w_comp:13 old:14 new:15
      │    │         │    │    └── project
      │    │         │    │         ├── columns: new:15 old:14 v_comp:12 w_comp:13 k_new:11 w:8 k:6 v:7 crdb_internal_mvcc_timestamp:9 tableoid:10
      │    │         │    │         ├── select
      │    │         │    │         │    ├── columns: k:6 v:7 crdb_internal_mvcc_timestamp:9 tableoid:10
      │    │         │    │         │    ├── scan computed
      │    │         │    │         │    │    ├── columns: k:6 v:7 crdb_internal_mvcc_timestamp:9 tableoid:10
      │    │         │    │         │    │    ├── computed column expressions
      │    │         │    │         │    │    │    ├── v:7
      │    │         │    │         │    │    │    │    └── k:6 + 1
      │    │         │    │         │    │    │    └── w:8
      │    │         │    │         │    │    │         └── k:6 + 2
      │    │         │    │         │    │    └── flags: avoid-full-scan
      │    │         │    │         │    └── filters
      │    │         │    │         │         └── k:6 = 1
      │    │         │    │         └── projections
      │    │         │    │              ├── ((2, NULL, NULL) AS k, v, w) [as=new:15]
      │    │         │    │              ├── ((k:6, CAST(NULL AS INT8), CAST(NULL AS INT8)) AS k, v, w) [as=old:14]
      │    │         │    │              ├── 3 [as=v_comp:12]
      │    │         │    │              ├── 4 [as=w_comp:13]
      │    │         │    │              ├── 2 [as=k_new:11]
      │    │         │    │              └── k:6 + 2 [as=w:8]
      │    │         │    └── projections
      │    │         │         └── CASE WHEN ((new:15).v IS NULL) AND ((new:15).w IS NULL) THEN f(new:15, old:14, 'tr', 'BEFORE', 'ROW', 'UPDATE', 55, 'computed', 'computed', 'public', 0, ARRAY[]) ELSE new:15 END [as=f:29]
      │    │         └── filters
      │    │              └── f:29 IS DISTINCT FROM NULL
      │    └── projections
      │         └── (f:29).k [as=k_new:30]
      └── projections
           ├── k_new:30 + 1 [as=v_comp:31]
           └── k_new:30 + 2 [as=w_comp:32]

# Test a trigger that fires itself recursively.
exec-ddl
CREATE FUNCTION insert_ab() RETURNS TRIGGER AS $$
  BEGIN
    INSERT INTO ab VALUES ((NEW).a, (NEW).b);
    RETURN NEW;
  END;
$$ LANGUAGE PLpgSQL;
----

exec-ddl
CREATE TRIGGER tr BEFORE INSERT OR UPDATE ON ab FOR EACH ROW EXECUTE FUNCTION insert_ab();
----

norm format=(hide-all,show-columns,show-scalars)
INSERT INTO ab VALUES (1, 2);
----
insert ab
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── a_new:49 => a:1
 │    ├── b_new:50 => b:2
 │    └── rowid_default:8 => rowid:3
 ├── before-triggers
 │    └── tr
 └── project
      ├── columns: a_new:49 b_new:50 column1:6 column2:7 rowid_default:8 new:9 insert_ab:48
      ├── barrier
      │    ├── columns: column1:6 column2:7 rowid_default:8 new:9 insert_ab:48
      │    └── select
      │         ├── columns: column1:6 column2:7 rowid_default:8 new:9 insert_ab:48
      │         ├── project
      │         │    ├── columns: insert_ab:48 column1:6 column2:7 rowid_default:8 new:9
      │         │    ├── barrier
      │         │    │    ├── columns: column1:6 column2:7 rowid_default:8 new:9
      │         │    │    └── values
      │         │    │         ├── columns: column1:6 column2:7 rowid_default:8 new:9
      │         │    │         └── tuple
      │         │    │              ├── const: 1
      │         │    │              ├── const: 2
      │         │    │              ├── function: unique_rowid
      │         │    │              └── tuple
      │         │    │                   ├── const: 1
      │         │    │                   └── const: 2
      │         │    └── projections
      │         │         └── udf: insert_ab [as=insert_ab:48]
      │         │              ├── args
      │         │              │    ├── variable: new:9
      │         │              │    ├── null
      │         │              │    ├── const: 'tr'
      │         │              │    ├── const: 'BEFORE'
      │         │              │    ├── const: 'ROW'
      │         │              │    ├── const: 'INSERT'
      │         │              │    ├── const: 56
      │         │              │    ├── const: 'ab'
      │         │              │    ├── const: 'ab'
      │         │              │    ├── const: 'public'
      │         │              │    ├── const: 0
      │         │              │    └── const: ARRAY[]
      │         │              ├── params: new:10 old:11 tg_name:12 tg_when:13 tg_level:14 tg_op:15 tg_relid:16 tg_relname:17 tg_table_name:18 tg_table_schema:19 tg_nargs:20 tg_argv:21
      │         │              └── body
      │         │                   └── values
      │         │                        ├── columns: "_stmt_exec_1":47
      │         │                        └── tuple
      │         │                             └── udf: _stmt_exec_1
      │         │                                  ├── tail-call
      │         │                                  ├── args
      │         │                                  │    ├── variable: new:10
      │         │                                  │    ├── variable: old:11
      │         │                                  │    ├── variable: tg_name:12
      │         │                                  │    ├── variable: tg_when:13
      │         │                                  │    ├── variable: tg_level:14
      │         │                                  │    ├── variable: tg_op:15
      │         │                                  │    ├── variable: tg_relid:16
      │         │                                  │    ├── variable: tg_relname:17
      │         │                                  │    ├── variable: tg_table_name:18
      │         │                                  │    ├── variable: tg_table_schema:19
      │         │                                  │    ├── variable: tg_nargs:20
      │         │                                  │    └── variable: tg_argv:21
      │         │                                  ├── params: new:22 old:23 tg_name:24 tg_when:25 tg_level:26 tg_op:27 tg_relid:28 tg_relname:29 tg_table_name:30 tg_table_schema:31 tg_nargs:32 tg_argv:33
      │         │                                  └── body
      │         │                                       ├── insert ab
      │         │                                       │    ├── columns: <none>
      │         │                                       │    ├── insert-mapping:
      │         │                                       │    │    ├── a_new:44 => a:34
      │         │                                       │    │    ├── b_new:45 => b:35
      │         │                                       │    │    └── rowid_default:41 => rowid:36
      │         │                                       │    ├── before-triggers
      │         │                                       │    │    └── tr
      │         │                                       │    └── project
      │         │                                       │         ├── columns: a_new:44 b_new:45 column1:39 column2:40 rowid_default:41 new:42 insert_ab:43
      │         │                                       │         ├── barrier
      │         │                                       │         │    ├── columns: column1:39 column2:40 rowid_default:41 new:42 insert_ab:43
      │         │                                       │         │    └── select
      │         │                                       │         │         ├── columns: column1:39 column2:40 rowid_default:41 new:42 insert_ab:43
      │         │                                       │         │         ├── project
      │         │                                       │         │         │    ├── columns: insert_ab:43 column1:39 column2:40 rowid_default:41 new:42
      │         │                                       │         │         │    ├── barrier
      │         │                                       │         │         │    │    ├── columns: column1:39 column2:40 rowid_default:41 new:42
      │         │                                       │         │         │    │    └── project
      │         │                                       │         │         │    │         ├── columns: new:42 column1:39 column2:40 rowid_default:41
      │         │                                       │         │         │    │         ├── values
      │         │                                       │         │         │    │         │    ├── columns: column1:39 column2:40 rowid_default:41
      │         │                                       │         │         │    │         │    └── tuple
      │         │                                       │         │         │    │         │         ├── column-access: 0
      │         │                                       │         │         │    │         │         │    └── variable: new:22
      │         │                                       │         │         │    │         │         ├── column-access: 1
      │         │                                       │         │         │    │         │         │    └── variable: new:22
      │         │                                       │         │         │    │         │         └── function: unique_rowid
      │         │                                       │         │         │    │         └── projections
      │         │                                       │         │         │    │              └── tuple [as=new:42]
      │         │                                       │         │         │    │                   ├── variable: column1:39
      │         │                                       │         │         │    │                   └── variable: column2:40
      │         │                                       │         │         │    └── projections
      │         │                                       │         │         │         └── udf: insert_ab [as=insert_ab:43]
      │         │                                       │         │         │              ├── args
      │         │                                       │         │         │              │    ├── variable: new:42
      │         │                                       │         │         │              │    ├── null
      │         │                                       │         │         │              │    ├── const: 'tr'
      │         │                                       │         │         │              │    ├── const: 'BEFORE'
      │         │                                       │         │         │              │    ├── const: 'ROW'
      │         │                                       │         │         │              │    ├── const: 'INSERT'
      │         │                                       │         │         │              │    ├── const: 56
      │         │                                       │         │         │              │    ├── const: 'ab'
      │         │                                       │         │         │              │    ├── const: 'ab'
      │         │                                       │         │         │              │    ├── const: 'public'
      │         │                                       │         │         │              │    ├── const: 0
      │         │                                       │         │         │              │    └── const: ARRAY[]
      │         │                                       │         │         │              └── recursive-call
      │         │                                       │         │         └── filters
      │         │                                       │         │              └── is-not
      │         │                                       │         │                   ├── variable: insert_ab:43
      │         │                                       │         │                   └── null
      │         │                                       │         └── projections
      │         │                                       │              ├── column-access: 0 [as=a_new:44]
      │         │                                       │              │    └── variable: insert_ab:43
      │         │                                       │              └── column-access: 1 [as=b_new:45]
      │         │                                       │                   └── variable: insert_ab:43
      │         │                                       └── values
      │         │                                            ├── columns: stmt_return_2:46
      │         │                                            └── tuple
      │         │                                                 └── variable: new:22
      │         └── filters
      │              └── is-not
      │                   ├── variable: insert_ab:48
      │                   └── null
      └── projections
           ├── column-access: 0 [as=a_new:49]
           │    └── variable: insert_ab:48
           └── column-access: 1 [as=b_new:50]
                └── variable: insert_ab:48

# ------------------------------------------------------------------------------
# Row-level AFTER triggers.
# ------------------------------------------------------------------------------

exec-ddl
DROP TRIGGER tr ON xy;
----

exec-ddl
CREATE TRIGGER tr AFTER INSERT OR UPDATE OR DELETE ON xy FOR EACH ROW EXECUTE FUNCTION f();
----

build-post-queries format=(hide-all,show-columns)
INSERT INTO xy VALUES (1, 2);
----
root
 ├── insert xy
 │    ├── columns: <none>
 │    ├── insert-mapping:
 │    │    ├── column1:5 => x:1
 │    │    └── column2:6 => y:2
 │    ├── input binding: &1
 │    ├── after-triggers
 │    │    └── tr
 │    └── values
 │         ├── columns: column1:5 column2:6
 │         └── (1, 2)
 └── after-triggers
      └── barrier
           ├── columns: column1_new:7 column2_new:8 old:9 new:10 f:24
           └── project
                ├── columns: f:24 column1_new:7 column2_new:8 old:9 new:10
                ├── project
                │    ├── columns: new:10 column1_new:7 column2_new:8 old:9
                │    ├── project
                │    │    ├── columns: old:9 column1_new:7 column2_new:8
                │    │    ├── with-scan &1
                │    │    │    ├── columns: column1_new:7 column2_new:8
                │    │    │    └── mapping:
                │    │    │         ├──  column1:5 => column1_new:7
                │    │    │         └──  column2:6 => column2_new:8
                │    │    └── projections
                │    │         └── NULL [as=old:9]
                │    └── projections
                │         └── ((column1_new:7, column2_new:8) AS x, y) [as=new:10]
                └── projections
                     └── f(new:10, old:9, 'tr', 'AFTER', 'ROW', 'INSERT', 53, 'xy', 'xy', 'public', 0, ARRAY[]) [as=f:24]

build-post-queries format=(hide-all,show-columns)
UPDATE xy SET y = 3 WHERE x = 1;
----
root
 ├── update xy
 │    ├── columns: <none>
 │    ├── fetch columns: x:5 y:6
 │    ├── update-mapping:
 │    │    └── y_new:9 => y:2
 │    ├── input binding: &1
 │    ├── after-triggers
 │    │    └── tr
 │    └── project
 │         ├── columns: y_new:9 x:5 y:6 crdb_internal_mvcc_timestamp:7 tableoid:8
 │         ├── select
 │         │    ├── columns: x:5 y:6 crdb_internal_mvcc_timestamp:7 tableoid:8
 │         │    ├── scan xy
 │         │    │    ├── columns: x:5 y:6 crdb_internal_mvcc_timestamp:7 tableoid:8
 │         │    │    └── flags: avoid-full-scan
 │         │    └── filters
 │         │         └── x:5 = 1
 │         └── projections
 │              └── 3 [as=y_new:9]
 └── after-triggers
      └── barrier
           ├── columns: x_old:10 y_old:11 x_new:12 y_new_new:13 old:14 new:15 f:29
           └── project
                ├── columns: f:29 x_old:10 y_old:11 x_new:12 y_new_new:13 old:14 new:15
                ├── project
                │    ├── columns: new:15 x_old:10 y_old:11 x_new:12 y_new_new:13 old:14
                │    ├── project
                │    │    ├── columns: old:14 x_old:10 y_old:11 x_new:12 y_new_new:13
                │    │    ├── with-scan &1
                │    │    │    ├── columns: x_old:10 y_old:11 x_new:12 y_new_new:13
                │    │    │    └── mapping:
                │    │    │         ├──  x:5 => x_old:10
                │    │    │         ├──  y:6 => y_old:11
                │    │    │         ├──  x:5 => x_new:12
                │    │    │         └──  y_new:9 => y_new_new:13
                │    │    └── projections
                │    │         └── ((x_old:10, y_old:11) AS x, y) [as=old:14]
                │    └── projections
                │         └── ((x_new:12, y_new_new:13) AS x, y) [as=new:15]
                └── projections
                     └── f(new:15, old:14, 'tr', 'AFTER', 'ROW', 'UPDATE', 53, 'xy', 'xy', 'public', 0, ARRAY[]) [as=f:29]

build-post-queries format=(hide-all,show-columns)
DELETE FROM xy WHERE x = 1;
----
root
 ├── delete xy
 │    ├── columns: <none>
 │    ├── fetch columns: x:5 y:6
 │    ├── input binding: &1
 │    ├── cascades
 │    │    └── child_x_fkey
 │    ├── after-triggers
 │    │    └── tr
 │    └── select
 │         ├── columns: x:5 y:6 crdb_internal_mvcc_timestamp:7 tableoid:8
 │         ├── scan xy
 │         │    ├── columns: x:5 y:6 crdb_internal_mvcc_timestamp:7 tableoid:8
 │         │    └── flags: avoid-full-scan
 │         └── filters
 │              └── x:5 = 1
 ├── cascade
 │    └── delete child
 │         ├── columns: <none>
 │         ├── fetch columns: k:13 child.x:14
 │         ├── before-triggers
 │         │    └── tr_child
 │         └── barrier
 │              ├── columns: k:13 child.x:14 old:17 f:31 "check-rows":32
 │              └── select
 │                   ├── columns: k:13 child.x:14 old:17 f:31 "check-rows":32
 │                   ├── barrier
 │                   │    ├── columns: k:13 child.x:14 old:17 f:31 "check-rows":32
 │                   │    └── project
 │                   │         ├── columns: "check-rows":32 k:13 child.x:14 old:17 f:31
 │                   │         ├── project
 │                   │         │    ├── columns: f:31 k:13 child.x:14 old:17
 │                   │         │    ├── barrier
 │                   │         │    │    ├── columns: k:13 child.x:14 old:17
 │                   │         │    │    └── project
 │                   │         │    │         ├── columns: old:17 k:13 child.x:14
 │                   │         │    │         ├── select
 │                   │         │    │         │    ├── columns: k:13 child.x:14
 │                   │         │    │         │    ├── scan child
 │                   │         │    │         │    │    ├── columns: k:13 child.x:14
 │                   │         │    │         │    │    └── flags: avoid-full-scan
 │                   │         │    │         │    └── filters
 │                   │         │    │         │         ├── child.x:14 = 1
 │                   │         │    │         │         └── child.x:14 IS DISTINCT FROM CAST(NULL AS INT8)
 │                   │         │    │         └── projections
 │                   │         │    │              └── ((k:13, child.x:14) AS k, x) [as=old:17]
 │                   │         │    └── projections
 │                   │         │         └── f(NULL, old:17, 'tr_child', 'BEFORE', 'ROW', 'DELETE', 54, 'child', 'child', 'public', 0, ARRAY[]) [as=f:31]
 │                   │         └── projections
 │                   │              └── CASE WHEN f:31 IS DISTINCT FROM old:17 THEN crdb_internal.plpgsql_raise('ERROR', 'trigger tr_child attempted to modify or filter a row in a cascade operation: ' || old:17::STRING, e'changing the rows updated or deleted by a foreign-key cascade\n can cause constraint violations, and therefore is not allowed', e'to enable this behavior (with risk of constraint violation), set\nthe session variable \'unsafe_allow_triggers_modifying_cascades\' to true', '27000') ELSE CAST(NULL AS INT8) END [as="check-rows":32]
 │                   └── filters
 │                        └── f:31 IS DISTINCT FROM NULL
 └── after-triggers
      └── barrier
           ├── columns: x_old:33 y_old:34 old:35 new:36 f:50
           └── project
                ├── columns: f:50 x_old:33 y_old:34 old:35 new:36
                ├── project
                │    ├── columns: new:36 x_old:33 y_old:34 old:35
                │    ├── project
                │    │    ├── columns: old:35 x_old:33 y_old:34
                │    │    ├── with-scan &1
                │    │    │    ├── columns: x_old:33 y_old:34
                │    │    │    └── mapping:
                │    │    │         ├──  xy.x:5 => x_old:33
                │    │    │         └──  y:6 => y_old:34
                │    │    └── projections
                │    │         └── ((x_old:33, y_old:34) AS x, y) [as=old:35]
                │    └── projections
                │         └── NULL [as=new:36]
                └── projections
                     └── f(new:36, old:35, 'tr', 'AFTER', 'ROW', 'DELETE', 53, 'xy', 'xy', 'public', 0, ARRAY[]) [as=f:50]

build-post-queries format=(hide-all,show-columns)
UPSERT INTO xy VALUES (1, 2);
----
root
 ├── upsert xy
 │    ├── arbiter indexes: xy_pkey
 │    ├── columns: <none>
 │    ├── canary column: x:7
 │    ├── fetch columns: x:7 y:8
 │    ├── insert-mapping:
 │    │    ├── column1:5 => x:1
 │    │    └── column2:6 => y:2
 │    ├── update-mapping:
 │    │    └── column2:6 => y:2
 │    ├── input binding: &1
 │    ├── after-triggers
 │    │    └── tr
 │    └── project
 │         ├── columns: upsert_x:11 column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10
 │         ├── left-join (hash)
 │         │    ├── columns: column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10
 │         │    ├── ensure-upsert-distinct-on
 │         │    │    ├── columns: column1:5 column2:6
 │         │    │    ├── grouping columns: column1:5
 │         │    │    ├── values
 │         │    │    │    ├── columns: column1:5 column2:6
 │         │    │    │    └── (1, 2)
 │         │    │    └── aggregations
 │         │    │         └── first-agg [as=column2:6]
 │         │    │              └── column2:6
 │         │    ├── scan xy
 │         │    │    ├── columns: x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10
 │         │    │    └── flags: avoid-full-scan
 │         │    └── filters
 │         │         └── column1:5 = x:7
 │         └── projections
 │              └── CASE WHEN x:7 IS NULL THEN column1:5 ELSE x:7 END [as=upsert_x:11]
 └── after-triggers
      └── barrier
           ├── columns: canary:12 x_old:13 y_old:14 x_new:15 column2_new:16 column1_new:17 column2_new:18 old:19 new:20 f:34
           └── project
                ├── columns: f:34 canary:12 x_old:13 y_old:14 x_new:15 column2_new:16 column1_new:17 column2_new:18 old:19 new:20
                ├── project
                │    ├── columns: new:20 canary:12 x_old:13 y_old:14 x_new:15 column2_new:16 column1_new:17 column2_new:18 old:19
                │    ├── project
                │    │    ├── columns: old:19 canary:12 x_old:13 y_old:14 x_new:15 column2_new:16 column1_new:17 column2_new:18
                │    │    ├── with-scan &1
                │    │    │    ├── columns: canary:12 x_old:13 y_old:14 x_new:15 column2_new:16 column1_new:17 column2_new:18
                │    │    │    └── mapping:
                │    │    │         ├──  x:7 => canary:12
                │    │    │         ├──  x:7 => x_old:13
                │    │    │         ├──  y:8 => y_old:14
                │    │    │         ├──  x:7 => x_new:15
                │    │    │         ├──  column2:6 => column2_new:16
                │    │    │         ├──  column1:5 => column1_new:17
                │    │    │         └──  column2:6 => column2_new:18
                │    │    └── projections
                │    │         └── CASE WHEN canary:12 IS NULL THEN CAST(NULL AS RECORD) ELSE ((x_old:13, y_old:14) AS x, y) END [as=old:19]
                │    └── projections
                │         └── CASE WHEN canary:12 IS NULL THEN ((column1_new:17, column2_new:18) AS x, y) ELSE ((x_new:15, column2_new:16) AS x, y) END [as=new:20]
                └── projections
                     └── f(new:20, old:19, 'tr', 'AFTER', 'ROW', CASE WHEN canary:12 IS NULL THEN 'INSERT' ELSE 'UPDATE' END, 53, 'xy', 'xy', 'public', 0, ARRAY[]) [as=f:34]

build-post-queries format=(hide-all,show-columns)
INSERT INTO xy VALUES (1, 2) ON CONFLICT (x) DO UPDATE SET y = 3;
----
root
 ├── upsert xy
 │    ├── arbiter indexes: xy_pkey
 │    ├── columns: <none>
 │    ├── canary column: x:7
 │    ├── fetch columns: x:7 y:8
 │    ├── insert-mapping:
 │    │    ├── column1:5 => x:1
 │    │    └── column2:6 => y:2
 │    ├── update-mapping:
 │    │    └── upsert_y:13 => y:2
 │    ├── input binding: &1
 │    ├── after-triggers
 │    │    └── tr
 │    └── project
 │         ├── columns: upsert_x:12 upsert_y:13 column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10 y_new:11
 │         ├── project
 │         │    ├── columns: y_new:11 column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10
 │         │    ├── left-join (hash)
 │         │    │    ├── columns: column1:5 column2:6 x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10
 │         │    │    ├── ensure-upsert-distinct-on
 │         │    │    │    ├── columns: column1:5 column2:6
 │         │    │    │    ├── grouping columns: column1:5
 │         │    │    │    ├── values
 │         │    │    │    │    ├── columns: column1:5 column2:6
 │         │    │    │    │    └── (1, 2)
 │         │    │    │    └── aggregations
 │         │    │    │         └── first-agg [as=column2:6]
 │         │    │    │              └── column2:6
 │         │    │    ├── scan xy
 │         │    │    │    ├── columns: x:7 y:8 crdb_internal_mvcc_timestamp:9 tableoid:10
 │         │    │    │    └── flags: avoid-full-scan
 │         │    │    └── filters
 │         │    │         └── column1:5 = x:7
 │         │    └── projections
 │         │         └── 3 [as=y_new:11]
 │         └── projections
 │              ├── CASE WHEN x:7 IS NULL THEN column1:5 ELSE x:7 END [as=upsert_x:12]
 │              └── CASE WHEN x:7 IS NULL THEN column2:6 ELSE y_new:11 END [as=upsert_y:13]
 └── after-triggers
      └── barrier
           ├── columns: canary:14 x_old:15 y_old:16 x_new:17 upsert_y_new:18 column1_new:19 column2_new:20 old:21 new:22 f:36
           └── project
                ├── columns: f:36 canary:14 x_old:15 y_old:16 x_new:17 upsert_y_new:18 column1_new:19 column2_new:20 old:21 new:22
                ├── project
                │    ├── columns: new:22 canary:14 x_old:15 y_old:16 x_new:17 upsert_y_new:18 column1_new:19 column2_new:20 old:21
                │    ├── project
                │    │    ├── columns: old:21 canary:14 x_old:15 y_old:16 x_new:17 upsert_y_new:18 column1_new:19 column2_new:20
                │    │    ├── with-scan &1
                │    │    │    ├── columns: canary:14 x_old:15 y_old:16 x_new:17 upsert_y_new:18 column1_new:19 column2_new:20
                │    │    │    └── mapping:
                │    │    │         ├──  x:7 => canary:14
                │    │    │         ├──  x:7 => x_old:15
                │    │    │         ├──  y:8 => y_old:16
                │    │    │         ├──  x:7 => x_new:17
                │    │    │         ├──  upsert_y:13 => upsert_y_new:18
                │    │    │         ├──  column1:5 => column1_new:19
                │    │    │         └──  column2:6 => column2_new:20
                │    │    └── projections
                │    │         └── CASE WHEN canary:14 IS NULL THEN CAST(NULL AS RECORD) ELSE ((x_old:15, y_old:16) AS x, y) END [as=old:21]
                │    └── projections
                │         └── CASE WHEN canary:14 IS NULL THEN ((column1_new:19, column2_new:20) AS x, y) ELSE ((x_new:17, upsert_y_new:18) AS x, y) END [as=new:22]
                └── projections
                     └── f(new:22, old:21, 'tr', 'AFTER', 'ROW', CASE WHEN canary:14 IS NULL THEN 'INSERT' ELSE 'UPDATE' END, 53, 'xy', 'xy', 'public', 0, ARRAY[]) [as=f:36]

# Case with multiple triggers.
exec-ddl
CREATE TRIGGER tr2 AFTER INSERT ON xy FOR EACH ROW EXECUTE FUNCTION f();
----

build-post-queries format=(hide-all,show-columns)
INSERT INTO xy VALUES (1, 2);
----
root
 ├── insert xy
 │    ├── columns: <none>
 │    ├── insert-mapping:
 │    │    ├── column1:5 => x:1
 │    │    └── column2:6 => y:2
 │    ├── input binding: &1
 │    ├── after-triggers
 │    │    ├── tr
 │    │    └── tr2
 │    └── values
 │         ├── columns: column1:5 column2:6
 │         └── (1, 2)
 └── after-triggers
      └── barrier
           ├── columns: column1_new:7 column2_new:8 old:9 new:10 f:24 f:38
           └── project
                ├── columns: f:38 column1_new:7 column2_new:8 old:9 new:10 f:24
                ├── barrier
                │    ├── columns: column1_new:7 column2_new:8 old:9 new:10 f:24
                │    └── project
                │         ├── columns: f:24 column1_new:7 column2_new:8 old:9 new:10
                │         ├── project
                │         │    ├── columns: new:10 column1_new:7 column2_new:8 old:9
                │         │    ├── project
                │         │    │    ├── columns: old:9 column1_new:7 column2_new:8
                │         │    │    ├── with-scan &1
                │         │    │    │    ├── columns: column1_new:7 column2_new:8
                │         │    │    │    └── mapping:
                │         │    │    │         ├──  column1:5 => column1_new:7
                │         │    │    │         └──  column2:6 => column2_new:8
                │         │    │    └── projections
                │         │    │         └── NULL [as=old:9]
                │         │    └── projections
                │         │         └── ((column1_new:7, column2_new:8) AS x, y) [as=new:10]
                │         └── projections
                │              └── f(new:10, old:9, 'tr', 'AFTER', 'ROW', 'INSERT', 53, 'xy', 'xy', 'public', 0, ARRAY[]) [as=f:24]
                └── projections
                     └── f(new:10, old:9, 'tr2', 'AFTER', 'ROW', 'INSERT', 53, 'xy', 'xy', 'public', 0, ARRAY[]) [as=f:38]

# ------------------------------------------------------------------------------
# Regression tests.
# ------------------------------------------------------------------------------

# Regression test for #133329 - do not inline insert values into uniqueness
# checks when the table has row-level BEFORE triggers that could modify the
# rows.
exec-ddl
CREATE TABLE t133329 (k INT PRIMARY KEY, a INT UNIQUE WITHOUT INDEX);
----

build format=(hide-all,show-fastpathchecks)
INSERT INTO t133329 VALUES (1, 1);
----
insert t133329
 ├── values
 │    └── (1, 1)
 ├── unique-checks
 │    └── unique-checks-item: t133329(a)
 │         └── project
 │              └── semi-join (hash)
 │                   ├── values
 │                   │    └── (1, 1)
 │                   ├── scan t133329
 │                   │    └── flags: avoid-full-scan
 │                   └── filters
 │                        ├── a = t133329.a
 │                        └── k != t133329.k
 └── fast-path-unique-checks
      └── fast-path-unique-checks-item: t133329(a)
           └── select
                ├── scan t133329
                │    └── flags: avoid-full-scan
                └── filters
                     └── t133329.a = 1

exec-ddl
CREATE FUNCTION f133329() RETURNS TRIGGER LANGUAGE PLpgSQL AS $$
  BEGIN
    RETURN NEW;
  END
$$;
----

exec-ddl
CREATE TRIGGER tr BEFORE INSERT ON t133329 FOR EACH ROW EXECUTE FUNCTION f();
----

build format=(hide-all,show-fastpathchecks)
INSERT INTO t133329 VALUES (1, 1);
----
insert t133329
 ├── project
 │    ├── barrier
 │    │    └── select
 │    │         ├── project
 │    │         │    ├── barrier
 │    │         │    │    └── project
 │    │         │    │         ├── values
 │    │         │    │         │    └── (1, 1)
 │    │         │    │         └── projections
 │    │         │    │              └── ((column1, column2) AS k, a)
 │    │         │    └── projections
 │    │         │         └── f(new, NULL, 'tr', 'BEFORE', 'ROW', 'INSERT', 59, 't133329', 't133329', 'public', 0, ARRAY[])
 │    │         └── filters
 │    │              └── f IS DISTINCT FROM NULL
 │    └── projections
 │         ├── (f).k
 │         └── (f).a
 └── unique-checks
      └── unique-checks-item: t133329(a)
           └── project
                └── semi-join (hash)
                     ├── with-scan &1
                     ├── scan t133329
                     │    └── flags: avoid-full-scan
                     └── filters
                          ├── a = t133329.a
                          └── k != t133329.k
