exec-ddl
CREATE TABLE parent (p INT PRIMARY KEY)
----

exec-ddl
CREATE TABLE child (c INT PRIMARY KEY, p INT NOT NULL REFERENCES parent(p) ON UPDATE CASCADE)
----

build-post-queries
UPDATE parent SET p = p * 10 WHERE p > 1
----
root
 ├── update parent
 │    ├── columns: <none>
 │    ├── fetch columns: p:4
 │    ├── update-mapping:
 │    │    └── p_new:7 => p:1
 │    ├── input binding: &1
 │    ├── cascades
 │    │    └── child_p_fkey
 │    └── project
 │         ├── columns: p_new:7!null p:4!null crdb_internal_mvcc_timestamp:5 tableoid:6
 │         ├── select
 │         │    ├── columns: p:4!null crdb_internal_mvcc_timestamp:5 tableoid:6
 │         │    ├── scan parent
 │         │    │    ├── columns: p:4!null crdb_internal_mvcc_timestamp:5 tableoid:6
 │         │    │    └── flags: avoid-full-scan
 │         │    └── filters
 │         │         └── p:4 > 1
 │         └── projections
 │              └── p:4 * 10 [as=p_new:7]
 └── cascade
      └── update child
           ├── columns: <none>
           ├── fetch columns: c:12 child.p:13
           ├── update-mapping:
           │    └── p_new:17 => child.p:9
           ├── input binding: &2
           ├── inner-join (hash)
           │    ├── columns: c:12!null child.p:13!null p_old:16!null p_new:17!null
           │    ├── scan child
           │    │    ├── columns: c:12!null child.p:13!null
           │    │    └── flags: avoid-full-scan disabled not visible index feature
           │    ├── select
           │    │    ├── columns: p_old:16!null p_new:17!null
           │    │    ├── with-scan &1
           │    │    │    ├── columns: p_old:16!null p_new:17!null
           │    │    │    └── mapping:
           │    │    │         ├──  parent.p:4 => p_old:16
           │    │    │         └──  p_new:7 => p_new:17
           │    │    └── filters
           │    │         └── p_old:16 IS DISTINCT FROM p_new:17
           │    └── filters
           │         └── child.p:13 = p_old:16
           └── f-k-checks
                └── f-k-checks-item: child(p) -> parent(p)
                     └── anti-join (hash)
                          ├── columns: p:18!null
                          ├── with-scan &2
                          │    ├── columns: p:18!null
                          │    └── mapping:
                          │         └──  p_new:17 => p:18
                          ├── scan parent
                          │    ├── columns: parent.p:19!null
                          │    └── flags: avoid-full-scan disabled not visible index feature
                          └── filters
                               └── p:18 = parent.p:19

exec-ddl
CREATE TABLE parent_multi (
  pk INT PRIMARY KEY,
  p INT, q INT,
  UNIQUE (p, q),
  FAMILY (pk),
  FAMILY (p),
  FAMILY (q)
)
----

exec-ddl
CREATE TABLE child_multi (
  c INT PRIMARY KEY,
  p INT, q INT,
  UNIQUE (c, q),
  CONSTRAINT fk FOREIGN KEY (p, q) REFERENCES parent_multi(p, q) ON UPDATE CASCADE
)
----

build-post-queries
UPDATE parent_multi SET p = p * 10, q = q + 1 WHERE pk > 1
----
root
 ├── update parent_multi
 │    ├── columns: <none>
 │    ├── fetch columns: pk:6 p:7 q:8
 │    ├── update-mapping:
 │    │    ├── p_new:11 => p:2
 │    │    └── q_new:12 => q:3
 │    ├── input binding: &1
 │    ├── cascades
 │    │    └── fk
 │    └── project
 │         ├── columns: p_new:11 q_new:12 pk:6!null p:7 q:8 crdb_internal_mvcc_timestamp:9 tableoid:10
 │         ├── select
 │         │    ├── columns: pk:6!null p:7 q:8 crdb_internal_mvcc_timestamp:9 tableoid:10
 │         │    ├── scan parent_multi
 │         │    │    ├── columns: pk:6!null p:7 q:8 crdb_internal_mvcc_timestamp:9 tableoid:10
 │         │    │    └── flags: avoid-full-scan
 │         │    └── filters
 │         │         └── pk:6 > 1
 │         └── projections
 │              ├── p:7 * 10 [as=p_new:11]
 │              └── q:8 + 1 [as=q_new:12]
 └── cascade
      └── update child_multi
           ├── columns: <none>
           ├── fetch columns: c:18 child_multi.p:19 child_multi.q:20
           ├── update-mapping:
           │    ├── p_new:24 => child_multi.p:14
           │    └── q_new:26 => child_multi.q:15
           ├── input binding: &2
           ├── inner-join (hash)
           │    ├── columns: c:18!null child_multi.p:19!null child_multi.q:20!null p_old:23!null p_new:24 q_old:25!null q_new:26
           │    ├── scan child_multi
           │    │    ├── columns: c:18!null child_multi.p:19 child_multi.q:20
           │    │    └── flags: avoid-full-scan disabled not visible index feature
           │    ├── select
           │    │    ├── columns: p_old:23 p_new:24 q_old:25 q_new:26
           │    │    ├── with-scan &1
           │    │    │    ├── columns: p_old:23 p_new:24 q_old:25 q_new:26
           │    │    │    └── mapping:
           │    │    │         ├──  parent_multi.p:7 => p_old:23
           │    │    │         ├──  parent_multi.q:8 => q_old:25
           │    │    │         ├──  p_new:11 => p_new:24
           │    │    │         └──  q_new:12 => q_new:26
           │    │    └── filters
           │    │         └── (p_old:23 IS DISTINCT FROM p_new:24) OR (q_old:25 IS DISTINCT FROM q_new:26)
           │    └── filters
           │         ├── child_multi.p:19 = p_old:23
           │         └── child_multi.q:20 = q_old:25
           └── f-k-checks
                └── f-k-checks-item: child_multi(p,q) -> parent_multi(p,q)
                     └── anti-join (hash)
                          ├── columns: p:27!null q:28!null
                          ├── select
                          │    ├── columns: p:27!null q:28!null
                          │    ├── with-scan &2
                          │    │    ├── columns: p:27 q:28
                          │    │    └── mapping:
                          │    │         ├──  p_new:24 => p:27
                          │    │         └──  q_new:26 => q:28
                          │    └── filters
                          │         ├── p:27 IS NOT NULL
                          │         └── q:28 IS NOT NULL
                          ├── scan parent_multi
                          │    ├── columns: parent_multi.p:30 parent_multi.q:31
                          │    └── flags: avoid-full-scan disabled not visible index feature
                          └── filters
                               ├── p:27 = parent_multi.p:30
                               └── q:28 = parent_multi.q:31

# Update only one of the two FK columns. The "before" and "after" values of q
# come from the same column in the mutation input.
build-post-queries
UPDATE parent_multi SET p = p * 10 WHERE p > 1
----
root
 ├── update parent_multi
 │    ├── columns: <none>
 │    ├── fetch columns: pk:6 p:7 q:8
 │    ├── update-mapping:
 │    │    └── p_new:11 => p:2
 │    ├── input binding: &1
 │    ├── cascades
 │    │    └── fk
 │    └── project
 │         ├── columns: p_new:11!null pk:6!null p:7!null q:8 crdb_internal_mvcc_timestamp:9 tableoid:10
 │         ├── select
 │         │    ├── columns: pk:6!null p:7!null q:8 crdb_internal_mvcc_timestamp:9 tableoid:10
 │         │    ├── scan parent_multi
 │         │    │    ├── columns: pk:6!null p:7 q:8 crdb_internal_mvcc_timestamp:9 tableoid:10
 │         │    │    └── flags: avoid-full-scan
 │         │    └── filters
 │         │         └── p:7 > 1
 │         └── projections
 │              └── p:7 * 10 [as=p_new:11]
 └── cascade
      └── update child_multi
           ├── columns: <none>
           ├── fetch columns: c:17 child_multi.p:18 child_multi.q:19
           ├── update-mapping:
           │    ├── p_new:23 => child_multi.p:13
           │    └── q_new:25 => child_multi.q:14
           ├── input binding: &2
           ├── inner-join (hash)
           │    ├── columns: c:17!null child_multi.p:18!null child_multi.q:19!null p_old:22!null p_new:23!null q_old:24!null q_new:25
           │    ├── scan child_multi
           │    │    ├── columns: c:17!null child_multi.p:18 child_multi.q:19
           │    │    └── flags: avoid-full-scan disabled not visible index feature
           │    ├── select
           │    │    ├── columns: p_old:22!null p_new:23!null q_old:24 q_new:25
           │    │    ├── with-scan &1
           │    │    │    ├── columns: p_old:22!null p_new:23!null q_old:24 q_new:25
           │    │    │    └── mapping:
           │    │    │         ├──  parent_multi.p:7 => p_old:22
           │    │    │         ├──  parent_multi.q:8 => q_old:24
           │    │    │         ├──  p_new:11 => p_new:23
           │    │    │         └──  parent_multi.q:8 => q_new:25
           │    │    └── filters
           │    │         └── (p_old:22 IS DISTINCT FROM p_new:23) OR (q_old:24 IS DISTINCT FROM q_new:25)
           │    └── filters
           │         ├── child_multi.p:18 = p_old:22
           │         └── child_multi.q:19 = q_old:24
           └── f-k-checks
                └── f-k-checks-item: child_multi(p,q) -> parent_multi(p,q)
                     └── anti-join (hash)
                          ├── columns: p:26!null q:27!null
                          ├── select
                          │    ├── columns: p:26!null q:27!null
                          │    ├── with-scan &2
                          │    │    ├── columns: p:26!null q:27
                          │    │    └── mapping:
                          │    │         ├──  p_new:23 => p:26
                          │    │         └──  q_new:25 => q:27
                          │    └── filters
                          │         └── q:27 IS NOT NULL
                          ├── scan parent_multi
                          │    ├── columns: parent_multi.p:29 parent_multi.q:30
                          │    └── flags: avoid-full-scan disabled not visible index feature
                          └── filters
                               ├── p:26 = parent_multi.p:29
                               └── q:27 = parent_multi.q:30

build-post-queries
UPSERT INTO parent_multi VALUES (1, 10, 10), (2, 20, 20)
----
root
 ├── upsert parent_multi
 │    ├── arbiter indexes: parent_multi_pkey
 │    ├── columns: <none>
 │    ├── canary column: pk:9
 │    ├── fetch columns: pk:9 p:10 q:11
 │    ├── insert-mapping:
 │    │    ├── column1:6 => pk:1
 │    │    ├── column2:7 => p:2
 │    │    └── column3:8 => q:3
 │    ├── update-mapping:
 │    │    ├── column2:7 => p:2
 │    │    └── column3:8 => q:3
 │    ├── input binding: &1
 │    ├── cascades
 │    │    └── fk
 │    └── project
 │         ├── columns: upsert_pk:14 column1:6!null column2:7!null column3:8!null pk:9 p:10 q:11 crdb_internal_mvcc_timestamp:12 tableoid:13
 │         ├── left-join (hash)
 │         │    ├── columns: column1:6!null column2:7!null column3:8!null pk:9 p:10 q:11 crdb_internal_mvcc_timestamp:12 tableoid:13
 │         │    ├── ensure-upsert-distinct-on
 │         │    │    ├── columns: column1:6!null column2:7!null column3:8!null
 │         │    │    ├── grouping columns: column1:6!null
 │         │    │    ├── values
 │         │    │    │    ├── columns: column1:6!null column2:7!null column3:8!null
 │         │    │    │    ├── (1, 10, 10)
 │         │    │    │    └── (2, 20, 20)
 │         │    │    └── aggregations
 │         │    │         ├── first-agg [as=column2:7]
 │         │    │         │    └── column2:7
 │         │    │         └── first-agg [as=column3:8]
 │         │    │              └── column3:8
 │         │    ├── scan parent_multi
 │         │    │    ├── columns: pk:9!null p:10 q:11 crdb_internal_mvcc_timestamp:12 tableoid:13
 │         │    │    └── flags: avoid-full-scan disabled not visible index feature
 │         │    └── filters
 │         │         └── column1:6 = pk:9
 │         └── projections
 │              └── CASE WHEN pk:9 IS NULL THEN column1:6 ELSE pk:9 END [as=upsert_pk:14]
 └── cascade
      └── update child_multi
           ├── columns: <none>
           ├── fetch columns: c:20 child_multi.p:21 child_multi.q:22
           ├── update-mapping:
           │    ├── p_new:26 => child_multi.p:16
           │    └── q_new:28 => child_multi.q:17
           ├── input binding: &2
           ├── inner-join (hash)
           │    ├── columns: c:20!null child_multi.p:21!null child_multi.q:22!null p_old:25!null p_new:26!null q_old:27!null q_new:28!null
           │    ├── scan child_multi
           │    │    ├── columns: c:20!null child_multi.p:21 child_multi.q:22
           │    │    └── flags: avoid-full-scan disabled not visible index feature
           │    ├── select
           │    │    ├── columns: p_old:25 p_new:26!null q_old:27 q_new:28!null
           │    │    ├── with-scan &1
           │    │    │    ├── columns: p_old:25 p_new:26!null q_old:27 q_new:28!null
           │    │    │    └── mapping:
           │    │    │         ├──  parent_multi.p:10 => p_old:25
           │    │    │         ├──  parent_multi.q:11 => q_old:27
           │    │    │         ├──  column2:7 => p_new:26
           │    │    │         └──  column3:8 => q_new:28
           │    │    └── filters
           │    │         └── (p_old:25 IS DISTINCT FROM p_new:26) OR (q_old:27 IS DISTINCT FROM q_new:28)
           │    └── filters
           │         ├── child_multi.p:21 = p_old:25
           │         └── child_multi.q:22 = q_old:27
           └── f-k-checks
                └── f-k-checks-item: child_multi(p,q) -> parent_multi(p,q)
                     └── anti-join (hash)
                          ├── columns: p:29!null q:30!null
                          ├── with-scan &2
                          │    ├── columns: p:29!null q:30!null
                          │    └── mapping:
                          │         ├──  p_new:26 => p:29
                          │         └──  q_new:28 => q:30
                          ├── scan parent_multi
                          │    ├── columns: parent_multi.p:32 parent_multi.q:33
                          │    └── flags: avoid-full-scan disabled not visible index feature
                          └── filters
                               ├── p:29 = parent_multi.p:32
                               └── q:30 = parent_multi.q:33

# Upsert that only touches one of the FK columns.
build-post-queries
UPSERT INTO parent_multi(pk, p) VALUES (1, 10), (2, 20)
----
root
 ├── upsert parent_multi
 │    ├── arbiter indexes: parent_multi_pkey
 │    ├── columns: <none>
 │    ├── canary column: pk:9
 │    ├── fetch columns: pk:9 p:10 q:11
 │    ├── insert-mapping:
 │    │    ├── column1:6 => pk:1
 │    │    ├── column2:7 => p:2
 │    │    └── q_default:8 => q:3
 │    ├── update-mapping:
 │    │    └── column2:7 => p:2
 │    ├── input binding: &1
 │    ├── cascades
 │    │    └── fk
 │    └── project
 │         ├── columns: upsert_pk:14 upsert_q:15 column1:6!null column2:7!null q_default:8 pk:9 p:10 q:11 crdb_internal_mvcc_timestamp:12 tableoid:13
 │         ├── left-join (hash)
 │         │    ├── columns: column1:6!null column2:7!null q_default:8 pk:9 p:10 q:11 crdb_internal_mvcc_timestamp:12 tableoid:13
 │         │    ├── ensure-upsert-distinct-on
 │         │    │    ├── columns: column1:6!null column2:7!null q_default:8
 │         │    │    ├── grouping columns: column1:6!null
 │         │    │    ├── project
 │         │    │    │    ├── columns: q_default:8 column1:6!null column2:7!null
 │         │    │    │    ├── values
 │         │    │    │    │    ├── columns: column1:6!null column2:7!null
 │         │    │    │    │    ├── (1, 10)
 │         │    │    │    │    └── (2, 20)
 │         │    │    │    └── projections
 │         │    │    │         └── NULL::INT8 [as=q_default:8]
 │         │    │    └── aggregations
 │         │    │         ├── first-agg [as=column2:7]
 │         │    │         │    └── column2:7
 │         │    │         └── first-agg [as=q_default:8]
 │         │    │              └── q_default:8
 │         │    ├── scan parent_multi
 │         │    │    ├── columns: pk:9!null p:10 q:11 crdb_internal_mvcc_timestamp:12 tableoid:13
 │         │    │    └── flags: avoid-full-scan disabled not visible index feature
 │         │    └── filters
 │         │         └── column1:6 = pk:9
 │         └── projections
 │              ├── CASE WHEN pk:9 IS NULL THEN column1:6 ELSE pk:9 END [as=upsert_pk:14]
 │              └── CASE WHEN pk:9 IS NULL THEN q_default:8 ELSE q:11 END [as=upsert_q:15]
 └── cascade
      └── update child_multi
           ├── columns: <none>
           ├── fetch columns: c:21 child_multi.p:22 child_multi.q:23
           ├── update-mapping:
           │    ├── p_new:27 => child_multi.p:17
           │    └── q_new:29 => child_multi.q:18
           ├── input binding: &2
           ├── inner-join (hash)
           │    ├── columns: c:21!null child_multi.p:22!null child_multi.q:23!null p_old:26!null p_new:27!null q_old:28!null q_new:29
           │    ├── scan child_multi
           │    │    ├── columns: c:21!null child_multi.p:22 child_multi.q:23
           │    │    └── flags: avoid-full-scan disabled not visible index feature
           │    ├── select
           │    │    ├── columns: p_old:26 p_new:27!null q_old:28 q_new:29
           │    │    ├── with-scan &1
           │    │    │    ├── columns: p_old:26 p_new:27!null q_old:28 q_new:29
           │    │    │    └── mapping:
           │    │    │         ├──  parent_multi.p:10 => p_old:26
           │    │    │         ├──  parent_multi.q:11 => q_old:28
           │    │    │         ├──  column2:7 => p_new:27
           │    │    │         └──  parent_multi.q:11 => q_new:29
           │    │    └── filters
           │    │         └── (p_old:26 IS DISTINCT FROM p_new:27) OR (q_old:28 IS DISTINCT FROM q_new:29)
           │    └── filters
           │         ├── child_multi.p:22 = p_old:26
           │         └── child_multi.q:23 = q_old:28
           └── f-k-checks
                └── f-k-checks-item: child_multi(p,q) -> parent_multi(p,q)
                     └── anti-join (hash)
                          ├── columns: p:30!null q:31!null
                          ├── select
                          │    ├── columns: p:30!null q:31!null
                          │    ├── with-scan &2
                          │    │    ├── columns: p:30!null q:31
                          │    │    └── mapping:
                          │    │         ├──  p_new:27 => p:30
                          │    │         └──  q_new:29 => q:31
                          │    └── filters
                          │         └── q:31 IS NOT NULL
                          ├── scan parent_multi
                          │    ├── columns: parent_multi.p:33 parent_multi.q:34
                          │    └── flags: avoid-full-scan disabled not visible index feature
                          └── filters
                               ├── p:30 = parent_multi.p:33
                               └── q:31 = parent_multi.q:34

build-post-queries
INSERT INTO parent_multi VALUES (1, 10, 10), (2, 20, 20) ON CONFLICT (p,q) DO UPDATE SET p = 100
----
root
 ├── upsert parent_multi
 │    ├── arbiter indexes: parent_multi_p_q_key
 │    ├── columns: <none>
 │    ├── canary column: pk:9
 │    ├── fetch columns: pk:9 p:10 q:11
 │    ├── insert-mapping:
 │    │    ├── column1:6 => pk:1
 │    │    ├── column2:7 => p:2
 │    │    └── column3:8 => q:3
 │    ├── update-mapping:
 │    │    └── upsert_p:16 => p:2
 │    ├── input binding: &1
 │    ├── cascades
 │    │    └── fk
 │    └── project
 │         ├── columns: upsert_pk:15 upsert_p:16!null upsert_q:17 column1:6!null column2:7!null column3:8!null pk:9 p:10 q:11 crdb_internal_mvcc_timestamp:12 tableoid:13 p_new:14!null
 │         ├── project
 │         │    ├── columns: p_new:14!null column1:6!null column2:7!null column3:8!null pk:9 p:10 q:11 crdb_internal_mvcc_timestamp:12 tableoid:13
 │         │    ├── left-join (hash)
 │         │    │    ├── columns: column1:6!null column2:7!null column3:8!null pk:9 p:10 q:11 crdb_internal_mvcc_timestamp:12 tableoid:13
 │         │    │    ├── ensure-upsert-distinct-on
 │         │    │    │    ├── columns: column1:6!null column2:7!null column3:8!null
 │         │    │    │    ├── grouping columns: column2:7!null column3:8!null
 │         │    │    │    ├── values
 │         │    │    │    │    ├── columns: column1:6!null column2:7!null column3:8!null
 │         │    │    │    │    ├── (1, 10, 10)
 │         │    │    │    │    └── (2, 20, 20)
 │         │    │    │    └── aggregations
 │         │    │    │         └── first-agg [as=column1:6]
 │         │    │    │              └── column1:6
 │         │    │    ├── scan parent_multi
 │         │    │    │    ├── columns: pk:9!null p:10 q:11 crdb_internal_mvcc_timestamp:12 tableoid:13
 │         │    │    │    └── flags: avoid-full-scan disabled not visible index feature
 │         │    │    └── filters
 │         │    │         ├── column2:7 = p:10
 │         │    │         └── column3:8 = q:11
 │         │    └── projections
 │         │         └── 100 [as=p_new:14]
 │         └── projections
 │              ├── CASE WHEN pk:9 IS NULL THEN column1:6 ELSE pk:9 END [as=upsert_pk:15]
 │              ├── CASE WHEN pk:9 IS NULL THEN column2:7 ELSE p_new:14 END [as=upsert_p:16]
 │              └── CASE WHEN pk:9 IS NULL THEN column3:8 ELSE q:11 END [as=upsert_q:17]
 └── cascade
      └── update child_multi
           ├── columns: <none>
           ├── fetch columns: c:23 child_multi.p:24 child_multi.q:25
           ├── update-mapping:
           │    ├── p_new:29 => child_multi.p:19
           │    └── q_new:31 => child_multi.q:20
           ├── input binding: &2
           ├── inner-join (hash)
           │    ├── columns: c:23!null child_multi.p:24!null child_multi.q:25!null p_old:28!null p_new:29!null q_old:30!null q_new:31
           │    ├── scan child_multi
           │    │    ├── columns: c:23!null child_multi.p:24 child_multi.q:25
           │    │    └── flags: avoid-full-scan disabled not visible index feature
           │    ├── select
           │    │    ├── columns: p_old:28 p_new:29!null q_old:30 q_new:31
           │    │    ├── with-scan &1
           │    │    │    ├── columns: p_old:28 p_new:29!null q_old:30 q_new:31
           │    │    │    └── mapping:
           │    │    │         ├──  parent_multi.p:10 => p_old:28
           │    │    │         ├──  parent_multi.q:11 => q_old:30
           │    │    │         ├──  upsert_p:16 => p_new:29
           │    │    │         └──  parent_multi.q:11 => q_new:31
           │    │    └── filters
           │    │         └── (p_old:28 IS DISTINCT FROM p_new:29) OR (q_old:30 IS DISTINCT FROM q_new:31)
           │    └── filters
           │         ├── child_multi.p:24 = p_old:28
           │         └── child_multi.q:25 = q_old:30
           └── f-k-checks
                └── f-k-checks-item: child_multi(p,q) -> parent_multi(p,q)
                     └── anti-join (hash)
                          ├── columns: p:32!null q:33!null
                          ├── select
                          │    ├── columns: p:32!null q:33!null
                          │    ├── with-scan &2
                          │    │    ├── columns: p:32!null q:33
                          │    │    └── mapping:
                          │    │         ├──  p_new:29 => p:32
                          │    │         └──  q_new:31 => q:33
                          │    └── filters
                          │         └── q:33 IS NOT NULL
                          ├── scan parent_multi
                          │    ├── columns: parent_multi.p:35 parent_multi.q:36
                          │    └── flags: avoid-full-scan disabled not visible index feature
                          └── filters
                               ├── p:32 = parent_multi.p:35
                               └── q:33 = parent_multi.q:36

# Test a two-level cascade.
exec-ddl
CREATE TABLE grandchild (
  g INT PRIMARY KEY,
  c INT, q INT,
  CONSTRAINT fk2 FOREIGN KEY (c, q) REFERENCES child_multi(c, q) ON UPDATE CASCADE
)
----

build-post-queries
UPDATE parent_multi SET q = q * 10 WHERE p > 1
----
root
 ├── update parent_multi
 │    ├── columns: <none>
 │    ├── fetch columns: pk:6 p:7 q:8
 │    ├── update-mapping:
 │    │    └── q_new:11 => q:3
 │    ├── input binding: &1
 │    ├── cascades
 │    │    └── fk
 │    └── project
 │         ├── columns: q_new:11 pk:6!null p:7!null q:8 crdb_internal_mvcc_timestamp:9 tableoid:10
 │         ├── select
 │         │    ├── columns: pk:6!null p:7!null q:8 crdb_internal_mvcc_timestamp:9 tableoid:10
 │         │    ├── scan parent_multi
 │         │    │    ├── columns: pk:6!null p:7 q:8 crdb_internal_mvcc_timestamp:9 tableoid:10
 │         │    │    └── flags: avoid-full-scan
 │         │    └── filters
 │         │         └── p:7 > 1
 │         └── projections
 │              └── q:8 * 10 [as=q_new:11]
 └── cascade
      ├── update child_multi
      │    ├── columns: <none>
      │    ├── fetch columns: c:17 child_multi.p:18 child_multi.q:19
      │    ├── update-mapping:
      │    │    ├── p_new:23 => child_multi.p:13
      │    │    └── q_new:25 => child_multi.q:14
      │    ├── input binding: &2
      │    ├── cascades
      │    │    └── fk2
      │    ├── inner-join (hash)
      │    │    ├── columns: c:17!null child_multi.p:18!null child_multi.q:19!null p_old:22!null p_new:23!null q_old:24!null q_new:25
      │    │    ├── scan child_multi
      │    │    │    ├── columns: c:17!null child_multi.p:18 child_multi.q:19
      │    │    │    └── flags: avoid-full-scan disabled not visible index feature
      │    │    ├── select
      │    │    │    ├── columns: p_old:22!null p_new:23!null q_old:24 q_new:25
      │    │    │    ├── with-scan &1
      │    │    │    │    ├── columns: p_old:22!null p_new:23!null q_old:24 q_new:25
      │    │    │    │    └── mapping:
      │    │    │    │         ├──  parent_multi.p:7 => p_old:22
      │    │    │    │         ├──  parent_multi.q:8 => q_old:24
      │    │    │    │         ├──  parent_multi.p:7 => p_new:23
      │    │    │    │         └──  q_new:11 => q_new:25
      │    │    │    └── filters
      │    │    │         └── (p_old:22 IS DISTINCT FROM p_new:23) OR (q_old:24 IS DISTINCT FROM q_new:25)
      │    │    └── filters
      │    │         ├── child_multi.p:18 = p_old:22
      │    │         └── child_multi.q:19 = q_old:24
      │    └── f-k-checks
      │         └── f-k-checks-item: child_multi(p,q) -> parent_multi(p,q)
      │              └── anti-join (hash)
      │                   ├── columns: p:26!null q:27!null
      │                   ├── select
      │                   │    ├── columns: p:26!null q:27!null
      │                   │    ├── with-scan &2
      │                   │    │    ├── columns: p:26!null q:27
      │                   │    │    └── mapping:
      │                   │    │         ├──  p_new:23 => p:26
      │                   │    │         └──  q_new:25 => q:27
      │                   │    └── filters
      │                   │         └── q:27 IS NOT NULL
      │                   ├── scan parent_multi
      │                   │    ├── columns: parent_multi.p:29 parent_multi.q:30
      │                   │    └── flags: avoid-full-scan disabled not visible index feature
      │                   └── filters
      │                        ├── p:26 = parent_multi.p:29
      │                        └── q:27 = parent_multi.q:30
      └── cascade
           └── update grandchild
                ├── columns: <none>
                ├── fetch columns: g:38 grandchild.c:39 grandchild.q:40
                ├── update-mapping:
                │    ├── c_new:44 => grandchild.c:34
                │    └── q_new:46 => grandchild.q:35
                ├── input binding: &3
                ├── inner-join (hash)
                │    ├── columns: g:38!null grandchild.c:39!null grandchild.q:40!null c_old:43!null c_new:44!null q_old:45!null q_new:46
                │    ├── scan grandchild
                │    │    ├── columns: g:38!null grandchild.c:39 grandchild.q:40
                │    │    └── flags: avoid-full-scan disabled not visible index feature
                │    ├── select
                │    │    ├── columns: c_old:43!null c_new:44!null q_old:45!null q_new:46
                │    │    ├── with-scan &2
                │    │    │    ├── columns: c_old:43!null c_new:44!null q_old:45!null q_new:46
                │    │    │    └── mapping:
                │    │    │         ├──  child_multi.c:17 => c_old:43
                │    │    │         ├──  child_multi.q:19 => q_old:45
                │    │    │         ├──  child_multi.c:17 => c_new:44
                │    │    │         └──  q_new:25 => q_new:46
                │    │    └── filters
                │    │         └── (c_old:43 IS DISTINCT FROM c_new:44) OR (q_old:45 IS DISTINCT FROM q_new:46)
                │    └── filters
                │         ├── grandchild.c:39 = c_old:43
                │         └── grandchild.q:40 = q_old:45
                └── f-k-checks
                     └── f-k-checks-item: grandchild(c,q) -> child_multi(c,q)
                          └── anti-join (hash)
                               ├── columns: c:47!null q:48!null
                               ├── select
                               │    ├── columns: c:47!null q:48!null
                               │    ├── with-scan &3
                               │    │    ├── columns: c:47!null q:48
                               │    │    └── mapping:
                               │    │         ├──  c_new:44 => c:47
                               │    │         └──  q_new:46 => q:48
                               │    └── filters
                               │         └── q:48 IS NOT NULL
                               ├── scan child_multi
                               │    ├── columns: child_multi.c:49!null child_multi.q:51
                               │    └── flags: avoid-full-scan disabled not visible index feature
                               └── filters
                                    ├── c:47 = child_multi.c:49
                                    └── q:48 = child_multi.q:51

build-post-queries
UPSERT INTO parent_multi VALUES (1, 10, 10), (2, 20, 20)
----
root
 ├── upsert parent_multi
 │    ├── arbiter indexes: parent_multi_pkey
 │    ├── columns: <none>
 │    ├── canary column: pk:9
 │    ├── fetch columns: pk:9 p:10 q:11
 │    ├── insert-mapping:
 │    │    ├── column1:6 => pk:1
 │    │    ├── column2:7 => p:2
 │    │    └── column3:8 => q:3
 │    ├── update-mapping:
 │    │    ├── column2:7 => p:2
 │    │    └── column3:8 => q:3
 │    ├── input binding: &1
 │    ├── cascades
 │    │    └── fk
 │    └── project
 │         ├── columns: upsert_pk:14 column1:6!null column2:7!null column3:8!null pk:9 p:10 q:11 crdb_internal_mvcc_timestamp:12 tableoid:13
 │         ├── left-join (hash)
 │         │    ├── columns: column1:6!null column2:7!null column3:8!null pk:9 p:10 q:11 crdb_internal_mvcc_timestamp:12 tableoid:13
 │         │    ├── ensure-upsert-distinct-on
 │         │    │    ├── columns: column1:6!null column2:7!null column3:8!null
 │         │    │    ├── grouping columns: column1:6!null
 │         │    │    ├── values
 │         │    │    │    ├── columns: column1:6!null column2:7!null column3:8!null
 │         │    │    │    ├── (1, 10, 10)
 │         │    │    │    └── (2, 20, 20)
 │         │    │    └── aggregations
 │         │    │         ├── first-agg [as=column2:7]
 │         │    │         │    └── column2:7
 │         │    │         └── first-agg [as=column3:8]
 │         │    │              └── column3:8
 │         │    ├── scan parent_multi
 │         │    │    ├── columns: pk:9!null p:10 q:11 crdb_internal_mvcc_timestamp:12 tableoid:13
 │         │    │    └── flags: avoid-full-scan disabled not visible index feature
 │         │    └── filters
 │         │         └── column1:6 = pk:9
 │         └── projections
 │              └── CASE WHEN pk:9 IS NULL THEN column1:6 ELSE pk:9 END [as=upsert_pk:14]
 └── cascade
      ├── update child_multi
      │    ├── columns: <none>
      │    ├── fetch columns: c:20 child_multi.p:21 child_multi.q:22
      │    ├── update-mapping:
      │    │    ├── p_new:26 => child_multi.p:16
      │    │    └── q_new:28 => child_multi.q:17
      │    ├── input binding: &2
      │    ├── cascades
      │    │    └── fk2
      │    ├── inner-join (hash)
      │    │    ├── columns: c:20!null child_multi.p:21!null child_multi.q:22!null p_old:25!null p_new:26!null q_old:27!null q_new:28!null
      │    │    ├── scan child_multi
      │    │    │    ├── columns: c:20!null child_multi.p:21 child_multi.q:22
      │    │    │    └── flags: avoid-full-scan disabled not visible index feature
      │    │    ├── select
      │    │    │    ├── columns: p_old:25 p_new:26!null q_old:27 q_new:28!null
      │    │    │    ├── with-scan &1
      │    │    │    │    ├── columns: p_old:25 p_new:26!null q_old:27 q_new:28!null
      │    │    │    │    └── mapping:
      │    │    │    │         ├──  parent_multi.p:10 => p_old:25
      │    │    │    │         ├──  parent_multi.q:11 => q_old:27
      │    │    │    │         ├──  column2:7 => p_new:26
      │    │    │    │         └──  column3:8 => q_new:28
      │    │    │    └── filters
      │    │    │         └── (p_old:25 IS DISTINCT FROM p_new:26) OR (q_old:27 IS DISTINCT FROM q_new:28)
      │    │    └── filters
      │    │         ├── child_multi.p:21 = p_old:25
      │    │         └── child_multi.q:22 = q_old:27
      │    └── f-k-checks
      │         └── f-k-checks-item: child_multi(p,q) -> parent_multi(p,q)
      │              └── anti-join (hash)
      │                   ├── columns: p:29!null q:30!null
      │                   ├── with-scan &2
      │                   │    ├── columns: p:29!null q:30!null
      │                   │    └── mapping:
      │                   │         ├──  p_new:26 => p:29
      │                   │         └──  q_new:28 => q:30
      │                   ├── scan parent_multi
      │                   │    ├── columns: parent_multi.p:32 parent_multi.q:33
      │                   │    └── flags: avoid-full-scan disabled not visible index feature
      │                   └── filters
      │                        ├── p:29 = parent_multi.p:32
      │                        └── q:30 = parent_multi.q:33
      └── cascade
           └── update grandchild
                ├── columns: <none>
                ├── fetch columns: g:41 grandchild.c:42 grandchild.q:43
                ├── update-mapping:
                │    ├── c_new:47 => grandchild.c:37
                │    └── q_new:49 => grandchild.q:38
                ├── input binding: &3
                ├── inner-join (hash)
                │    ├── columns: g:41!null grandchild.c:42!null grandchild.q:43!null c_old:46!null c_new:47!null q_old:48!null q_new:49!null
                │    ├── scan grandchild
                │    │    ├── columns: g:41!null grandchild.c:42 grandchild.q:43
                │    │    └── flags: avoid-full-scan disabled not visible index feature
                │    ├── select
                │    │    ├── columns: c_old:46!null c_new:47!null q_old:48!null q_new:49!null
                │    │    ├── with-scan &2
                │    │    │    ├── columns: c_old:46!null c_new:47!null q_old:48!null q_new:49!null
                │    │    │    └── mapping:
                │    │    │         ├──  child_multi.c:20 => c_old:46
                │    │    │         ├──  child_multi.q:22 => q_old:48
                │    │    │         ├──  child_multi.c:20 => c_new:47
                │    │    │         └──  q_new:28 => q_new:49
                │    │    └── filters
                │    │         └── (c_old:46 IS DISTINCT FROM c_new:47) OR (q_old:48 IS DISTINCT FROM q_new:49)
                │    └── filters
                │         ├── grandchild.c:42 = c_old:46
                │         └── grandchild.q:43 = q_old:48
                └── f-k-checks
                     └── f-k-checks-item: grandchild(c,q) -> child_multi(c,q)
                          └── anti-join (hash)
                               ├── columns: c:50!null q:51!null
                               ├── with-scan &3
                               │    ├── columns: c:50!null q:51!null
                               │    └── mapping:
                               │         ├──  c_new:47 => c:50
                               │         └──  q_new:49 => q:51
                               ├── scan child_multi
                               │    ├── columns: child_multi.c:52!null child_multi.q:54
                               │    └── flags: avoid-full-scan disabled not visible index feature
                               └── filters
                                    ├── c:50 = child_multi.c:52
                                    └── q:51 = child_multi.q:54

# Test a cascade to a child that requires an assignment cast because the
# referencing column type is not identical to the referenced column type.
exec-ddl
CREATE TABLE parent_assn_cast (p DECIMAL(10, 2) PRIMARY KEY, p2 DECIMAL(10, 2) UNIQUE)
----

exec-ddl
CREATE TABLE child_assn_cast (
  c INT PRIMARY KEY,
  p DECIMAL(10, 0) REFERENCES parent_assn_cast(p) ON UPDATE CASCADE
)
----

build-post-queries
UPDATE parent_assn_cast SET p = 1.45 WHERE p > 1
----
root
 ├── update parent_assn_cast
 │    ├── columns: <none>
 │    ├── fetch columns: p:5 p2:6
 │    ├── update-mapping:
 │    │    └── p_cast:10 => p:1
 │    ├── input binding: &1
 │    ├── cascades
 │    │    └── child_assn_cast_p_fkey
 │    └── project
 │         ├── columns: p_cast:10!null p:5!null p2:6 crdb_internal_mvcc_timestamp:7 tableoid:8
 │         ├── project
 │         │    ├── columns: p_new:9!null p:5!null p2:6 crdb_internal_mvcc_timestamp:7 tableoid:8
 │         │    ├── select
 │         │    │    ├── columns: p:5!null p2:6 crdb_internal_mvcc_timestamp:7 tableoid:8
 │         │    │    ├── scan parent_assn_cast
 │         │    │    │    ├── columns: p:5!null p2:6 crdb_internal_mvcc_timestamp:7 tableoid:8
 │         │    │    │    └── flags: avoid-full-scan
 │         │    │    └── filters
 │         │    │         └── p:5 > 1
 │         │    └── projections
 │         │         └── 1.45 [as=p_new:9]
 │         └── projections
 │              └── assignment-cast: DECIMAL(10,2) [as=p_cast:10]
 │                   └── p_new:9
 └── cascade
      └── update child_assn_cast
           ├── columns: <none>
           ├── fetch columns: c:15 child_assn_cast.p:16
           ├── update-mapping:
           │    └── p_cast:21 => child_assn_cast.p:12
           ├── input binding: &2
           ├── project
           │    ├── columns: p_cast:21!null c:15!null child_assn_cast.p:16!null p_old:19!null p_new:20!null
           │    ├── inner-join (hash)
           │    │    ├── columns: c:15!null child_assn_cast.p:16!null p_old:19!null p_new:20!null
           │    │    ├── scan child_assn_cast
           │    │    │    ├── columns: c:15!null child_assn_cast.p:16
           │    │    │    └── flags: avoid-full-scan disabled not visible index feature
           │    │    ├── select
           │    │    │    ├── columns: p_old:19!null p_new:20!null
           │    │    │    ├── with-scan &1
           │    │    │    │    ├── columns: p_old:19!null p_new:20!null
           │    │    │    │    └── mapping:
           │    │    │    │         ├──  parent_assn_cast.p:5 => p_old:19
           │    │    │    │         └──  p_cast:10 => p_new:20
           │    │    │    └── filters
           │    │    │         └── p_old:19 IS DISTINCT FROM p_new:20
           │    │    └── filters
           │    │         └── child_assn_cast.p:16 = p_old:19
           │    └── projections
           │         └── assignment-cast: DECIMAL(10) [as=p_cast:21]
           │              └── p_new:20
           └── f-k-checks
                └── f-k-checks-item: child_assn_cast(p) -> parent_assn_cast(p)
                     └── anti-join (hash)
                          ├── columns: p:22!null
                          ├── with-scan &2
                          │    ├── columns: p:22!null
                          │    └── mapping:
                          │         └──  p_cast:21 => p:22
                          ├── scan parent_assn_cast
                          │    ├── columns: parent_assn_cast.p:23!null
                          │    └── flags: avoid-full-scan disabled not visible index feature
                          └── filters
                               └── p:22 = parent_assn_cast.p:23

exec-ddl
CREATE TABLE child_assn_cast_p2 (
  c INT PRIMARY KEY,
  p2 DECIMAL(10, 0) REFERENCES parent_assn_cast(p2) ON UPDATE CASCADE
)
----

build-post-queries
UPSERT INTO parent_assn_cast (p, p2) VALUES (1.23, 4.56)
----
root
 ├── upsert parent_assn_cast
 │    ├── arbiter indexes: parent_assn_cast_pkey
 │    ├── columns: <none>
 │    ├── canary column: p:9
 │    ├── fetch columns: p:9 p2:10
 │    ├── insert-mapping:
 │    │    ├── p_cast:7 => p:1
 │    │    └── p2_cast:8 => p2:2
 │    ├── update-mapping:
 │    │    └── p2_cast:8 => p2:2
 │    ├── input binding: &1
 │    ├── cascades
 │    │    └── child_assn_cast_p2_p2_fkey
 │    └── project
 │         ├── columns: upsert_p:13 p_cast:7!null p2_cast:8!null p:9 p2:10 crdb_internal_mvcc_timestamp:11 tableoid:12
 │         ├── left-join (hash)
 │         │    ├── columns: p_cast:7!null p2_cast:8!null p:9 p2:10 crdb_internal_mvcc_timestamp:11 tableoid:12
 │         │    ├── ensure-upsert-distinct-on
 │         │    │    ├── columns: p_cast:7!null p2_cast:8!null
 │         │    │    ├── grouping columns: p_cast:7!null
 │         │    │    ├── project
 │         │    │    │    ├── columns: p_cast:7!null p2_cast:8!null
 │         │    │    │    ├── values
 │         │    │    │    │    ├── columns: column1:5!null column2:6!null
 │         │    │    │    │    └── (1.23, 4.56)
 │         │    │    │    └── projections
 │         │    │    │         ├── assignment-cast: DECIMAL(10,2) [as=p_cast:7]
 │         │    │    │         │    └── column1:5
 │         │    │    │         └── assignment-cast: DECIMAL(10,2) [as=p2_cast:8]
 │         │    │    │              └── column2:6
 │         │    │    └── aggregations
 │         │    │         └── first-agg [as=p2_cast:8]
 │         │    │              └── p2_cast:8
 │         │    ├── scan parent_assn_cast
 │         │    │    ├── columns: p:9!null p2:10 crdb_internal_mvcc_timestamp:11 tableoid:12
 │         │    │    └── flags: avoid-full-scan disabled not visible index feature
 │         │    └── filters
 │         │         └── p_cast:7 = p:9
 │         └── projections
 │              └── CASE WHEN p:9 IS NULL THEN p_cast:7 ELSE p:9 END [as=upsert_p:13]
 └── cascade
      └── update child_assn_cast_p2
           ├── columns: <none>
           ├── fetch columns: c:18 child_assn_cast_p2.p2:19
           ├── update-mapping:
           │    └── p2_cast:24 => child_assn_cast_p2.p2:15
           ├── input binding: &2
           ├── project
           │    ├── columns: p2_cast:24!null c:18!null child_assn_cast_p2.p2:19!null p2_old:22!null p2_new:23!null
           │    ├── inner-join (hash)
           │    │    ├── columns: c:18!null child_assn_cast_p2.p2:19!null p2_old:22!null p2_new:23!null
           │    │    ├── scan child_assn_cast_p2
           │    │    │    ├── columns: c:18!null child_assn_cast_p2.p2:19
           │    │    │    └── flags: avoid-full-scan disabled not visible index feature
           │    │    ├── select
           │    │    │    ├── columns: p2_old:22 p2_new:23!null
           │    │    │    ├── with-scan &1
           │    │    │    │    ├── columns: p2_old:22 p2_new:23!null
           │    │    │    │    └── mapping:
           │    │    │    │         ├──  parent_assn_cast.p2:10 => p2_old:22
           │    │    │    │         └──  p2_cast:8 => p2_new:23
           │    │    │    └── filters
           │    │    │         └── p2_old:22 IS DISTINCT FROM p2_new:23
           │    │    └── filters
           │    │         └── child_assn_cast_p2.p2:19 = p2_old:22
           │    └── projections
           │         └── assignment-cast: DECIMAL(10) [as=p2_cast:24]
           │              └── p2_new:23
           └── f-k-checks
                └── f-k-checks-item: child_assn_cast_p2(p2) -> parent_assn_cast(p2)
                     └── anti-join (hash)
                          ├── columns: p2:25!null
                          ├── with-scan &2
                          │    ├── columns: p2:25!null
                          │    └── mapping:
                          │         └──  p2_cast:24 => p2:25
                          ├── scan parent_assn_cast
                          │    ├── columns: parent_assn_cast.p2:27
                          │    └── flags: avoid-full-scan disabled not visible index feature
                          └── filters
                               └── p2:25 = parent_assn_cast.p2:27

build-post-queries
INSERT INTO parent_assn_cast (p, p2) VALUES (1.23, 4.56) ON CONFLICT (p) DO UPDATE SET p2 = 7.89
----
root
 ├── upsert parent_assn_cast
 │    ├── arbiter indexes: parent_assn_cast_pkey
 │    ├── columns: <none>
 │    ├── canary column: p:9
 │    ├── fetch columns: p:9 p2:10
 │    ├── insert-mapping:
 │    │    ├── p_cast:7 => p:1
 │    │    └── p2_cast:8 => p2:2
 │    ├── update-mapping:
 │    │    └── upsert_p2:16 => p2:2
 │    ├── input binding: &1
 │    ├── cascades
 │    │    └── child_assn_cast_p2_p2_fkey
 │    └── project
 │         ├── columns: upsert_p:15 upsert_p2:16!null p_cast:7!null p2_cast:8!null p:9 p2:10 crdb_internal_mvcc_timestamp:11 tableoid:12 p2_cast:14!null
 │         ├── project
 │         │    ├── columns: p2_cast:14!null p_cast:7!null p2_cast:8!null p:9 p2:10 crdb_internal_mvcc_timestamp:11 tableoid:12
 │         │    ├── project
 │         │    │    ├── columns: p2_new:13!null p_cast:7!null p2_cast:8!null p:9 p2:10 crdb_internal_mvcc_timestamp:11 tableoid:12
 │         │    │    ├── left-join (hash)
 │         │    │    │    ├── columns: p_cast:7!null p2_cast:8!null p:9 p2:10 crdb_internal_mvcc_timestamp:11 tableoid:12
 │         │    │    │    ├── ensure-upsert-distinct-on
 │         │    │    │    │    ├── columns: p_cast:7!null p2_cast:8!null
 │         │    │    │    │    ├── grouping columns: p_cast:7!null
 │         │    │    │    │    ├── project
 │         │    │    │    │    │    ├── columns: p_cast:7!null p2_cast:8!null
 │         │    │    │    │    │    ├── values
 │         │    │    │    │    │    │    ├── columns: column1:5!null column2:6!null
 │         │    │    │    │    │    │    └── (1.23, 4.56)
 │         │    │    │    │    │    └── projections
 │         │    │    │    │    │         ├── assignment-cast: DECIMAL(10,2) [as=p_cast:7]
 │         │    │    │    │    │         │    └── column1:5
 │         │    │    │    │    │         └── assignment-cast: DECIMAL(10,2) [as=p2_cast:8]
 │         │    │    │    │    │              └── column2:6
 │         │    │    │    │    └── aggregations
 │         │    │    │    │         └── first-agg [as=p2_cast:8]
 │         │    │    │    │              └── p2_cast:8
 │         │    │    │    ├── scan parent_assn_cast
 │         │    │    │    │    ├── columns: p:9!null p2:10 crdb_internal_mvcc_timestamp:11 tableoid:12
 │         │    │    │    │    └── flags: avoid-full-scan disabled not visible index feature
 │         │    │    │    └── filters
 │         │    │    │         └── p_cast:7 = p:9
 │         │    │    └── projections
 │         │    │         └── 7.89 [as=p2_new:13]
 │         │    └── projections
 │         │         └── assignment-cast: DECIMAL(10,2) [as=p2_cast:14]
 │         │              └── p2_new:13
 │         └── projections
 │              ├── CASE WHEN p:9 IS NULL THEN p_cast:7 ELSE p:9 END [as=upsert_p:15]
 │              └── CASE WHEN p:9 IS NULL THEN p2_cast:8 ELSE p2_cast:14 END [as=upsert_p2:16]
 └── cascade
      └── update child_assn_cast_p2
           ├── columns: <none>
           ├── fetch columns: c:21 child_assn_cast_p2.p2:22
           ├── update-mapping:
           │    └── p2_cast:27 => child_assn_cast_p2.p2:18
           ├── input binding: &2
           ├── project
           │    ├── columns: p2_cast:27!null c:21!null child_assn_cast_p2.p2:22!null p2_old:25!null p2_new:26!null
           │    ├── inner-join (hash)
           │    │    ├── columns: c:21!null child_assn_cast_p2.p2:22!null p2_old:25!null p2_new:26!null
           │    │    ├── scan child_assn_cast_p2
           │    │    │    ├── columns: c:21!null child_assn_cast_p2.p2:22
           │    │    │    └── flags: avoid-full-scan disabled not visible index feature
           │    │    ├── select
           │    │    │    ├── columns: p2_old:25 p2_new:26!null
           │    │    │    ├── with-scan &1
           │    │    │    │    ├── columns: p2_old:25 p2_new:26!null
           │    │    │    │    └── mapping:
           │    │    │    │         ├──  parent_assn_cast.p2:10 => p2_old:25
           │    │    │    │         └──  upsert_p2:16 => p2_new:26
           │    │    │    └── filters
           │    │    │         └── p2_old:25 IS DISTINCT FROM p2_new:26
           │    │    └── filters
           │    │         └── child_assn_cast_p2.p2:22 = p2_old:25
           │    └── projections
           │         └── assignment-cast: DECIMAL(10) [as=p2_cast:27]
           │              └── p2_new:26
           └── f-k-checks
                └── f-k-checks-item: child_assn_cast_p2(p2) -> parent_assn_cast(p2)
                     └── anti-join (hash)
                          ├── columns: p2:28!null
                          ├── with-scan &2
                          │    ├── columns: p2:28!null
                          │    └── mapping:
                          │         └──  p2_cast:27 => p2:28
                          ├── scan parent_assn_cast
                          │    ├── columns: parent_assn_cast.p2:30
                          │    └── flags: avoid-full-scan disabled not visible index feature
                          └── filters
                               └── p2:28 = parent_assn_cast.p2:30

# Test a cascade to a child with a partial index.
exec-ddl
CREATE TABLE parent_partial (p INT PRIMARY KEY)
----

exec-ddl
CREATE TABLE child_partial (
  c INT PRIMARY KEY,
  p INT REFERENCES parent_partial(p) ON UPDATE CASCADE,
  i INT,
  INDEX (p) WHERE i > 0,
  INDEX (i) WHERE p > 0
)
----

build-post-queries
UPDATE parent_partial SET p = p * 10 WHERE p > 1
----
root
 ├── update parent_partial
 │    ├── columns: <none>
 │    ├── fetch columns: p:4
 │    ├── update-mapping:
 │    │    └── p_new:7 => p:1
 │    ├── input binding: &1
 │    ├── cascades
 │    │    └── child_partial_p_fkey
 │    └── project
 │         ├── columns: p_new:7!null p:4!null crdb_internal_mvcc_timestamp:5 tableoid:6
 │         ├── select
 │         │    ├── columns: p:4!null crdb_internal_mvcc_timestamp:5 tableoid:6
 │         │    ├── scan parent_partial
 │         │    │    ├── columns: p:4!null crdb_internal_mvcc_timestamp:5 tableoid:6
 │         │    │    └── flags: avoid-full-scan
 │         │    └── filters
 │         │         └── p:4 > 1
 │         └── projections
 │              └── p:4 * 10 [as=p_new:7]
 └── cascade
      └── update child_partial
           ├── columns: <none>
           ├── fetch columns: c:13 child_partial.p:14 i:15
           ├── update-mapping:
           │    └── p_new:19 => child_partial.p:9
           ├── partial index put columns: partial_index_put1:20 partial_index_put2:21
           ├── partial index del columns: partial_index_put1:20 partial_index_del2:22
           ├── input binding: &2
           ├── project
           │    ├── columns: partial_index_put1:20 partial_index_put2:21!null partial_index_del2:22!null c:13!null child_partial.p:14!null i:15 p_old:18!null p_new:19!null
           │    ├── inner-join (hash)
           │    │    ├── columns: c:13!null child_partial.p:14!null i:15 p_old:18!null p_new:19!null
           │    │    ├── scan child_partial
           │    │    │    ├── columns: c:13!null child_partial.p:14 i:15
           │    │    │    ├── partial index predicates
           │    │    │    │    ├── child_partial_p_idx: filters
           │    │    │    │    │    └── i:15 > 0
           │    │    │    │    └── child_partial_i_idx: filters
           │    │    │    │         └── child_partial.p:14 > 0
           │    │    │    └── flags: avoid-full-scan disabled not visible index feature
           │    │    ├── select
           │    │    │    ├── columns: p_old:18!null p_new:19!null
           │    │    │    ├── with-scan &1
           │    │    │    │    ├── columns: p_old:18!null p_new:19!null
           │    │    │    │    └── mapping:
           │    │    │    │         ├──  parent_partial.p:4 => p_old:18
           │    │    │    │         └──  p_new:7 => p_new:19
           │    │    │    └── filters
           │    │    │         └── p_old:18 IS DISTINCT FROM p_new:19
           │    │    └── filters
           │    │         └── child_partial.p:14 = p_old:18
           │    └── projections
           │         ├── i:15 > 0 [as=partial_index_put1:20]
           │         ├── p_new:19 > 0 [as=partial_index_put2:21]
           │         └── child_partial.p:14 > 0 [as=partial_index_del2:22]
           └── f-k-checks
                └── f-k-checks-item: child_partial(p) -> parent_partial(p)
                     └── anti-join (hash)
                          ├── columns: p:23!null
                          ├── with-scan &2
                          │    ├── columns: p:23!null
                          │    └── mapping:
                          │         └──  p_new:19 => p:23
                          ├── scan parent_partial
                          │    ├── columns: parent_partial.p:24!null
                          │    └── flags: avoid-full-scan disabled not visible index feature
                          └── filters
                               └── p:23 = parent_partial.p:24

# Test a cascade to a child with a partial index with an ambiguous name.
exec-ddl
CREATE TABLE parent_partial_ambig (p INT PRIMARY KEY)
----

exec-ddl
CREATE TABLE child_partial_ambig (
  c INT PRIMARY KEY,
  p_new INT REFERENCES parent_partial_ambig(p) ON UPDATE CASCADE,
  i INT,
  INDEX (i) WHERE p_new > 0
)
----

build-post-queries
UPDATE parent_partial_ambig SET p = p * 10 WHERE p > 1
----
root
 ├── update parent_partial_ambig
 │    ├── columns: <none>
 │    ├── fetch columns: p:4
 │    ├── update-mapping:
 │    │    └── p_new:7 => p:1
 │    ├── input binding: &1
 │    ├── cascades
 │    │    └── child_partial_ambig_p_new_fkey
 │    └── project
 │         ├── columns: p_new:7!null p:4!null crdb_internal_mvcc_timestamp:5 tableoid:6
 │         ├── select
 │         │    ├── columns: p:4!null crdb_internal_mvcc_timestamp:5 tableoid:6
 │         │    ├── scan parent_partial_ambig
 │         │    │    ├── columns: p:4!null crdb_internal_mvcc_timestamp:5 tableoid:6
 │         │    │    └── flags: avoid-full-scan
 │         │    └── filters
 │         │         └── p:4 > 1
 │         └── projections
 │              └── p:4 * 10 [as=p_new:7]
 └── cascade
      └── update child_partial_ambig
           ├── columns: <none>
           ├── fetch columns: c:13 child_partial_ambig.p_new:14 i:15
           ├── update-mapping:
           │    └── p_new_new:19 => child_partial_ambig.p_new:9
           ├── partial index put columns: partial_index_put1:20
           ├── partial index del columns: partial_index_del1:21
           ├── input binding: &2
           ├── project
           │    ├── columns: partial_index_put1:20!null partial_index_del1:21!null c:13!null child_partial_ambig.p_new:14!null i:15 p_new_old:18!null p_new_new:19!null
           │    ├── inner-join (hash)
           │    │    ├── columns: c:13!null child_partial_ambig.p_new:14!null i:15 p_new_old:18!null p_new_new:19!null
           │    │    ├── scan child_partial_ambig
           │    │    │    ├── columns: c:13!null child_partial_ambig.p_new:14 i:15
           │    │    │    ├── partial index predicates
           │    │    │    │    └── child_partial_ambig_i_idx: filters
           │    │    │    │         └── child_partial_ambig.p_new:14 > 0
           │    │    │    └── flags: avoid-full-scan disabled not visible index feature
           │    │    ├── select
           │    │    │    ├── columns: p_new_old:18!null p_new_new:19!null
           │    │    │    ├── with-scan &1
           │    │    │    │    ├── columns: p_new_old:18!null p_new_new:19!null
           │    │    │    │    └── mapping:
           │    │    │    │         ├──  p:4 => p_new_old:18
           │    │    │    │         └──  p_new:7 => p_new_new:19
           │    │    │    └── filters
           │    │    │         └── p_new_old:18 IS DISTINCT FROM p_new_new:19
           │    │    └── filters
           │    │         └── child_partial_ambig.p_new:14 = p_new_old:18
           │    └── projections
           │         ├── p_new_new:19 > 0 [as=partial_index_put1:20]
           │         └── child_partial_ambig.p_new:14 > 0 [as=partial_index_del1:21]
           └── f-k-checks
                └── f-k-checks-item: child_partial_ambig(p_new) -> parent_partial_ambig(p)
                     └── anti-join (hash)
                          ├── columns: p_new:22!null
                          ├── with-scan &2
                          │    ├── columns: p_new:22!null
                          │    └── mapping:
                          │         └──  p_new_new:19 => p_new:22
                          ├── scan parent_partial_ambig
                          │    ├── columns: p:23!null
                          │    └── flags: avoid-full-scan disabled not visible index feature
                          └── filters
                               └── p_new:22 = p:23

# Test an UPSERT that cascades to a child with a partial index.
exec-ddl
CREATE TABLE parent_multi_partial (
  pk INT PRIMARY KEY,
  p INT, q INT,
  UNIQUE (p, q),
  FAMILY (pk),
  FAMILY (p),
  FAMILY (q)
)
----

exec-ddl
CREATE TABLE child_multi_partial (
  c INT PRIMARY KEY,
  p INT, q INT,
  i INT,
  UNIQUE (c, q),
  INDEX (p, q) WHERE i > 0,
  INDEX (i) WHERE p > 0 AND q > 0,
  CONSTRAINT fk FOREIGN KEY (p, q) REFERENCES parent_multi_partial(p, q) ON UPDATE CASCADE
)
----

build-post-queries
UPSERT INTO parent_multi_partial VALUES (1), (2)
----
root
 ├── upsert parent_multi_partial
 │    ├── arbiter indexes: parent_multi_partial_pkey
 │    ├── columns: <none>
 │    ├── canary column: pk:8
 │    ├── fetch columns: pk:8 p:9 q:10
 │    ├── insert-mapping:
 │    │    ├── column1:6 => pk:1
 │    │    ├── p_default:7 => p:2
 │    │    └── p_default:7 => q:3
 │    ├── update-mapping:
 │    │    ├── p_default:7 => p:2
 │    │    └── p_default:7 => q:3
 │    ├── input binding: &1
 │    ├── cascades
 │    │    └── fk
 │    └── project
 │         ├── columns: upsert_pk:13 column1:6!null p_default:7 pk:8 p:9 q:10 crdb_internal_mvcc_timestamp:11 tableoid:12
 │         ├── left-join (hash)
 │         │    ├── columns: column1:6!null p_default:7 pk:8 p:9 q:10 crdb_internal_mvcc_timestamp:11 tableoid:12
 │         │    ├── ensure-upsert-distinct-on
 │         │    │    ├── columns: column1:6!null p_default:7
 │         │    │    ├── grouping columns: column1:6!null
 │         │    │    ├── project
 │         │    │    │    ├── columns: p_default:7 column1:6!null
 │         │    │    │    ├── values
 │         │    │    │    │    ├── columns: column1:6!null
 │         │    │    │    │    ├── (1,)
 │         │    │    │    │    └── (2,)
 │         │    │    │    └── projections
 │         │    │    │         └── NULL::INT8 [as=p_default:7]
 │         │    │    └── aggregations
 │         │    │         └── first-agg [as=p_default:7]
 │         │    │              └── p_default:7
 │         │    ├── scan parent_multi_partial
 │         │    │    ├── columns: pk:8!null p:9 q:10 crdb_internal_mvcc_timestamp:11 tableoid:12
 │         │    │    └── flags: avoid-full-scan disabled not visible index feature
 │         │    └── filters
 │         │         └── column1:6 = pk:8
 │         └── projections
 │              └── CASE WHEN pk:8 IS NULL THEN column1:6 ELSE pk:8 END [as=upsert_pk:13]
 └── cascade
      └── update child_multi_partial
           ├── columns: <none>
           ├── fetch columns: c:20 child_multi_partial.p:21 child_multi_partial.q:22 i:23
           ├── update-mapping:
           │    ├── p_new:27 => child_multi_partial.p:15
           │    └── q_new:29 => child_multi_partial.q:16
           ├── partial index put columns: partial_index_put1:30 partial_index_put2:31
           ├── partial index del columns: partial_index_put1:30 partial_index_del2:32
           ├── input binding: &2
           ├── project
           │    ├── columns: partial_index_put1:30 partial_index_put2:31 partial_index_del2:32!null c:20!null child_multi_partial.p:21!null child_multi_partial.q:22!null i:23 p_old:26!null p_new:27 q_old:28!null q_new:29
           │    ├── inner-join (hash)
           │    │    ├── columns: c:20!null child_multi_partial.p:21!null child_multi_partial.q:22!null i:23 p_old:26!null p_new:27 q_old:28!null q_new:29
           │    │    ├── scan child_multi_partial
           │    │    │    ├── columns: c:20!null child_multi_partial.p:21 child_multi_partial.q:22 i:23
           │    │    │    ├── partial index predicates
           │    │    │    │    ├── child_multi_partial_p_q_idx: filters
           │    │    │    │    │    └── i:23 > 0
           │    │    │    │    └── child_multi_partial_i_idx: filters
           │    │    │    │         └── (child_multi_partial.p:21 > 0) AND (child_multi_partial.q:22 > 0)
           │    │    │    └── flags: avoid-full-scan disabled not visible index feature
           │    │    ├── select
           │    │    │    ├── columns: p_old:26 p_new:27 q_old:28 q_new:29
           │    │    │    ├── with-scan &1
           │    │    │    │    ├── columns: p_old:26 p_new:27 q_old:28 q_new:29
           │    │    │    │    └── mapping:
           │    │    │    │         ├──  parent_multi_partial.p:9 => p_old:26
           │    │    │    │         ├──  parent_multi_partial.q:10 => q_old:28
           │    │    │    │         ├──  p_default:7 => p_new:27
           │    │    │    │         └──  p_default:7 => q_new:29
           │    │    │    └── filters
           │    │    │         └── (p_old:26 IS DISTINCT FROM p_new:27) OR (q_old:28 IS DISTINCT FROM q_new:29)
           │    │    └── filters
           │    │         ├── child_multi_partial.p:21 = p_old:26
           │    │         └── child_multi_partial.q:22 = q_old:28
           │    └── projections
           │         ├── i:23 > 0 [as=partial_index_put1:30]
           │         ├── (p_new:27 > 0) AND (q_new:29 > 0) [as=partial_index_put2:31]
           │         └── (child_multi_partial.p:21 > 0) AND (child_multi_partial.q:22 > 0) [as=partial_index_del2:32]
           └── f-k-checks
                └── f-k-checks-item: child_multi_partial(p,q) -> parent_multi_partial(p,q)
                     └── anti-join (hash)
                          ├── columns: p:33!null q:34!null
                          ├── select
                          │    ├── columns: p:33!null q:34!null
                          │    ├── with-scan &2
                          │    │    ├── columns: p:33 q:34
                          │    │    └── mapping:
                          │    │         ├──  p_new:27 => p:33
                          │    │         └──  q_new:29 => q:34
                          │    └── filters
                          │         ├── p:33 IS NOT NULL
                          │         └── q:34 IS NOT NULL
                          ├── scan parent_multi_partial
                          │    ├── columns: parent_multi_partial.p:36 parent_multi_partial.q:37
                          │    └── flags: avoid-full-scan disabled not visible index feature
                          └── filters
                               ├── p:33 = parent_multi_partial.p:36
                               └── q:34 = parent_multi_partial.q:37

# Regression test for #57148. A check constraint or computed column in a child
# table that references a column with the same name as the parent's synthesized
# update column should not result in an ambiguous column reference error. In
# this test the synthesized update column for the parent is "p" + "_new" =
# "p_new", which is the name of the FK column in the child.
exec-ddl
CREATE TABLE parent_check_ambig (p INT PRIMARY KEY)
----

exec-ddl
CREATE TABLE child_check_ambig (
  c INT PRIMARY KEY,
  p_new INT REFERENCES parent_check_ambig(p) ON UPDATE CASCADE,
  i INT AS (p_new * 2) STORED,
  CHECK (p_new > 0)
)
----

build-post-queries
UPDATE parent_check_ambig SET p = p * 10 WHERE p > 1
----
root
 ├── update parent_check_ambig
 │    ├── columns: <none>
 │    ├── fetch columns: p:4
 │    ├── update-mapping:
 │    │    └── p_new:7 => p:1
 │    ├── input binding: &1
 │    ├── cascades
 │    │    └── child_check_ambig_p_new_fkey
 │    └── project
 │         ├── columns: p_new:7!null p:4!null crdb_internal_mvcc_timestamp:5 tableoid:6
 │         ├── select
 │         │    ├── columns: p:4!null crdb_internal_mvcc_timestamp:5 tableoid:6
 │         │    ├── scan parent_check_ambig
 │         │    │    ├── columns: p:4!null crdb_internal_mvcc_timestamp:5 tableoid:6
 │         │    │    └── flags: avoid-full-scan
 │         │    └── filters
 │         │         └── p:4 > 1
 │         └── projections
 │              └── p:4 * 10 [as=p_new:7]
 └── cascade
      └── update child_check_ambig
           ├── columns: <none>
           ├── fetch columns: c:13 child_check_ambig.p_new:14 i:15
           ├── update-mapping:
           │    ├── p_new_new:19 => child_check_ambig.p_new:9
           │    └── i_comp:20 => i:10
           ├── check columns: check1:21
           ├── input binding: &2
           ├── project
           │    ├── columns: check1:21!null c:13!null child_check_ambig.p_new:14!null i:15 p_new_old:18!null p_new_new:19!null i_comp:20!null
           │    ├── project
           │    │    ├── columns: i_comp:20!null c:13!null child_check_ambig.p_new:14!null i:15 p_new_old:18!null p_new_new:19!null
           │    │    ├── inner-join (hash)
           │    │    │    ├── columns: c:13!null child_check_ambig.p_new:14!null i:15 p_new_old:18!null p_new_new:19!null
           │    │    │    ├── scan child_check_ambig
           │    │    │    │    ├── columns: c:13!null child_check_ambig.p_new:14 i:15
           │    │    │    │    ├── computed column expressions
           │    │    │    │    │    └── i:15
           │    │    │    │    │         └── child_check_ambig.p_new:14 * 2
           │    │    │    │    └── flags: avoid-full-scan disabled not visible index feature
           │    │    │    ├── select
           │    │    │    │    ├── columns: p_new_old:18!null p_new_new:19!null
           │    │    │    │    ├── with-scan &1
           │    │    │    │    │    ├── columns: p_new_old:18!null p_new_new:19!null
           │    │    │    │    │    └── mapping:
           │    │    │    │    │         ├──  p:4 => p_new_old:18
           │    │    │    │    │         └──  p_new:7 => p_new_new:19
           │    │    │    │    └── filters
           │    │    │    │         └── p_new_old:18 IS DISTINCT FROM p_new_new:19
           │    │    │    └── filters
           │    │    │         └── child_check_ambig.p_new:14 = p_new_old:18
           │    │    └── projections
           │    │         └── p_new_new:19 * 2 [as=i_comp:20]
           │    └── projections
           │         └── p_new_new:19 > 0 [as=check1:21]
           └── f-k-checks
                └── f-k-checks-item: child_check_ambig(p_new) -> parent_check_ambig(p)
                     └── anti-join (hash)
                          ├── columns: p_new:22!null
                          ├── with-scan &2
                          │    ├── columns: p_new:22!null
                          │    └── mapping:
                          │         └──  p_new_new:19 => p_new:22
                          ├── scan parent_check_ambig
                          │    ├── columns: p:23!null
                          │    └── flags: avoid-full-scan disabled not visible index feature
                          └── filters
                               └── p_new:22 = p:23

# Test cascade to a child with a virtual column that references the FK.
exec-ddl
CREATE TABLE parent_virt (p INT PRIMARY KEY)
----

exec-ddl
CREATE TABLE child_virt (
  c INT PRIMARY KEY,
  p INT REFERENCES parent_virt(p) ON UPDATE CASCADE,
  v INT AS (p) VIRTUAL
)
----

build-post-queries
UPDATE parent_virt SET p = p * 10 WHERE p > 1
----
root
 ├── update parent_virt
 │    ├── columns: <none>
 │    ├── fetch columns: p:4
 │    ├── update-mapping:
 │    │    └── p_new:7 => p:1
 │    ├── input binding: &1
 │    ├── cascades
 │    │    └── child_virt_p_fkey
 │    └── project
 │         ├── columns: p_new:7!null p:4!null crdb_internal_mvcc_timestamp:5 tableoid:6
 │         ├── select
 │         │    ├── columns: p:4!null crdb_internal_mvcc_timestamp:5 tableoid:6
 │         │    ├── scan parent_virt
 │         │    │    ├── columns: p:4!null crdb_internal_mvcc_timestamp:5 tableoid:6
 │         │    │    └── flags: avoid-full-scan
 │         │    └── filters
 │         │         └── p:4 > 1
 │         └── projections
 │              └── p:4 * 10 [as=p_new:7]
 └── cascade
      └── update child_virt
           ├── columns: <none>
           ├── fetch columns: c:13 child_virt.p:14 v:15
           ├── update-mapping:
           │    ├── p_new:19 => child_virt.p:9
           │    └── p_new:19 => v:10
           ├── input binding: &2
           ├── inner-join (hash)
           │    ├── columns: c:13!null child_virt.p:14!null v:15 p_old:18!null p_new:19!null
           │    ├── project
           │    │    ├── columns: v:15 c:13!null child_virt.p:14
           │    │    ├── scan child_virt
           │    │    │    ├── columns: c:13!null child_virt.p:14
           │    │    │    ├── computed column expressions
           │    │    │    │    └── v:15
           │    │    │    │         └── child_virt.p:14
           │    │    │    └── flags: avoid-full-scan disabled not visible index feature
           │    │    └── projections
           │    │         └── child_virt.p:14 [as=v:15]
           │    ├── select
           │    │    ├── columns: p_old:18!null p_new:19!null
           │    │    ├── with-scan &1
           │    │    │    ├── columns: p_old:18!null p_new:19!null
           │    │    │    └── mapping:
           │    │    │         ├──  parent_virt.p:4 => p_old:18
           │    │    │         └──  p_new:7 => p_new:19
           │    │    └── filters
           │    │         └── p_old:18 IS DISTINCT FROM p_new:19
           │    └── filters
           │         └── child_virt.p:14 = p_old:18
           └── f-k-checks
                └── f-k-checks-item: child_virt(p) -> parent_virt(p)
                     └── anti-join (hash)
                          ├── columns: p:20!null
                          ├── with-scan &2
                          │    ├── columns: p:20!null
                          │    └── mapping:
                          │         └──  p_new:19 => p:20
                          ├── scan parent_virt
                          │    ├── columns: parent_virt.p:21!null
                          │    └── flags: avoid-full-scan disabled not visible index feature
                          └── filters
                               └── p:20 = parent_virt.p:21

# Test cascade to a child with a reference column of a different type.
exec-ddl
CREATE TABLE parent_diff_type (p INT PRIMARY KEY)
----

exec-ddl
CREATE TABLE child_diff_type (
  c INT PRIMARY KEY,
  p INT2 REFERENCES parent_diff_type(p) ON UPDATE CASCADE
)
----

build-post-queries format=show-all
UPDATE parent_diff_type SET p = 1 WHERE p = 0
----
root
 ├── update t.public.parent_diff_type
 │    ├── columns: <none>
 │    ├── fetch columns: t.public.parent_diff_type.p:4(int)
 │    ├── update-mapping:
 │    │    └── p_new:7 => t.public.parent_diff_type.p:1
 │    ├── input binding: &1
 │    ├── cascades
 │    │    └── child_diff_type_p_fkey
 │    ├── cardinality: [0 - 0]
 │    ├── volatile, mutations
 │    ├── stats: [rows=0]
 │    └── project
 │         ├── columns: p_new:7(int!null) t.public.parent_diff_type.p:4(int!null) t.public.parent_diff_type.crdb_internal_mvcc_timestamp:5(decimal) t.public.parent_diff_type.tableoid:6(oid)
 │         ├── cardinality: [0 - 1]
 │         ├── stats: [rows=1]
 │         ├── key: ()
 │         ├── fd: ()-->(4-7)
 │         ├── prune: (4-7)
 │         ├── select
 │         │    ├── columns: t.public.parent_diff_type.p:4(int!null) t.public.parent_diff_type.crdb_internal_mvcc_timestamp:5(decimal) t.public.parent_diff_type.tableoid:6(oid)
 │         │    ├── cardinality: [0 - 1]
 │         │    ├── stats: [rows=1, distinct(4)=1, null(4)=0]
 │         │    ├── key: ()
 │         │    ├── fd: ()-->(4-6)
 │         │    ├── scan t.public.parent_diff_type
 │         │    │    ├── columns: t.public.parent_diff_type.p:4(int!null) t.public.parent_diff_type.crdb_internal_mvcc_timestamp:5(decimal) t.public.parent_diff_type.tableoid:6(oid)
 │         │    │    ├── flags: avoid-full-scan
 │         │    │    ├── stats: [rows=1000, distinct(4)=1000, null(4)=0]
 │         │    │    ├── key: (4)
 │         │    │    ├── fd: (4)-->(5,6)
 │         │    │    └── prune: (4-6)
 │         │    └── filters
 │         │         └── eq [type=bool, outer=(4), constraints=(/4: [/0 - /0]; tight), fd=()-->(4)]
 │         │              ├── variable: t.public.parent_diff_type.p:4 [type=int]
 │         │              └── const: 0 [type=int]
 │         └── projections
 │              └── const: 1 [as=p_new:7, type=int]
 └── cascade
      └── update t.public.child_diff_type
           ├── columns: <none>
           ├── fetch columns: t.public.child_diff_type.c:12(int) t.public.child_diff_type.p:13(int2)
           ├── update-mapping:
           │    └── p_cast:18 => t.public.child_diff_type.p:9
           ├── input binding: &2
           ├── cardinality: [0 - 0]
           ├── volatile, mutations
           ├── stats: [rows=0]
           ├── project
           │    ├── columns: p_cast:18(int2!null) t.public.child_diff_type.c:12(int!null) t.public.child_diff_type.p:13(int2!null) p_old:16(int!null) p_new:17(int!null)
           │    ├── immutable
           │    ├── stats: [rows=3.3, distinct(18)=0.333317, null(18)=0]
           │    ├── key: (12)
           │    ├── fd: ()-->(13,16-18), (13)==(16), (16)==(13)
           │    ├── prune: (12,13,16-18)
           │    ├── inner-join (hash)
           │    │    ├── columns: t.public.child_diff_type.c:12(int!null) t.public.child_diff_type.p:13(int2!null) p_old:16(int!null) p_new:17(int!null)
           │    │    ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-more)
           │    │    ├── stats: [rows=3.3, distinct(13)=0.333333, null(13)=0, distinct(16)=0.333333, null(16)=0, distinct(17)=0.333317, null(17)=0]
           │    │    ├── key: (12)
           │    │    ├── fd: ()-->(13,16,17), (13)==(16), (16)==(13)
           │    │    ├── scan t.public.child_diff_type
           │    │    │    ├── columns: t.public.child_diff_type.c:12(int!null) t.public.child_diff_type.p:13(int2)
           │    │    │    ├── flags: avoid-full-scan disabled not visible index feature
           │    │    │    ├── stats: [rows=1000, distinct(13)=100, null(13)=10]
           │    │    │    ├── key: (12)
           │    │    │    ├── fd: (12)-->(13)
           │    │    │    ├── prune: (12,13)
           │    │    │    └── unfiltered-cols: (12-15)
           │    │    ├── select
           │    │    │    ├── columns: p_old:16(int!null) p_new:17(int!null)
           │    │    │    ├── cardinality: [0 - 1]
           │    │    │    ├── stats: [rows=0.3333333, distinct(16)=0.333333, null(16)=0, distinct(17)=0.333333, null(17)=0]
           │    │    │    ├── key: ()
           │    │    │    ├── fd: ()-->(16,17)
           │    │    │    ├── with-scan &1
           │    │    │    │    ├── columns: p_old:16(int!null) p_new:17(int!null)
           │    │    │    │    ├── mapping:
           │    │    │    │    │    ├──  t.public.parent_diff_type.p:4(int) => p_old:16(int)
           │    │    │    │    │    └──  p_new:7(int) => p_new:17(int)
           │    │    │    │    ├── cardinality: [0 - 1]
           │    │    │    │    ├── stats: [rows=1, distinct(16)=1, null(16)=0, distinct(17)=1, null(17)=0]
           │    │    │    │    ├── key: ()
           │    │    │    │    ├── fd: ()-->(16,17)
           │    │    │    │    └── prune: (16,17)
           │    │    │    └── filters
           │    │    │         └── is-not [type=bool, outer=(16,17)]
           │    │    │              ├── variable: p_old:16 [type=int]
           │    │    │              └── variable: p_new:17 [type=int]
           │    │    └── filters
           │    │         └── eq [type=bool, outer=(13,16), constraints=(/13: (/NULL - ]; /16: (/NULL - ]), fd=(13)==(16), (16)==(13)]
           │    │              ├── variable: t.public.child_diff_type.p:13 [type=int2]
           │    │              └── variable: p_old:16 [type=int]
           │    └── projections
           │         └── assignment-cast: INT2 [as=p_cast:18, type=int2, outer=(17), immutable]
           │              └── variable: p_new:17 [type=int]
           └── f-k-checks
                └── f-k-checks-item: child_diff_type(p) -> parent_diff_type(p)
                     └── anti-join (hash)
                          ├── columns: p:19(int2!null)
                          ├── stats: [rows=1e-10]
                          ├── fd: ()-->(19)
                          ├── cte-uses
                          │    └── &2: count=1 used-columns=(18)
                          ├── with-scan &2
                          │    ├── columns: p:19(int2!null)
                          │    ├── mapping:
                          │    │    └──  p_cast:18(int2) => p:19(int2)
                          │    ├── stats: [rows=3.3, distinct(19)=0.333317, null(19)=0]
                          │    ├── fd: ()-->(19)
                          │    └── cte-uses
                          │         └── &2: count=1 used-columns=(18)
                          ├── scan t.public.parent_diff_type
                          │    ├── columns: t.public.parent_diff_type.p:20(int!null)
                          │    ├── flags: avoid-full-scan disabled not visible index feature
                          │    ├── stats: [rows=1000, distinct(20)=1000, null(20)=0]
                          │    ├── key: (20)
                          │    └── prune: (20)
                          └── filters
                               └── eq [type=bool, outer=(19,20), constraints=(/19: (/NULL - ]; /20: (/NULL - ]), fd=(19)==(20), (20)==(19)]
                                    ├── variable: p:19 [type=int2]
                                    └── variable: t.public.parent_diff_type.p:20 [type=int]

build-post-queries
UPSERT INTO parent_diff_type VALUES (0)
----
root
 └── upsert parent_diff_type
      ├── columns: <none>
      ├── upsert-mapping:
      │    └── column1:4 => p:1
      └── values
           ├── columns: column1:4!null
           └── (0,)

build-post-queries
INSERT INTO parent_diff_type VALUES (0) ON CONFLICT (p) DO UPDATE SET p = 1
----
root
 ├── upsert parent_diff_type
 │    ├── arbiter indexes: parent_diff_type_pkey
 │    ├── columns: <none>
 │    ├── canary column: p:5
 │    ├── fetch columns: p:5
 │    ├── insert-mapping:
 │    │    └── column1:4 => p:1
 │    ├── update-mapping:
 │    │    └── upsert_p:9 => p:1
 │    ├── input binding: &1
 │    ├── cascades
 │    │    └── child_diff_type_p_fkey
 │    └── project
 │         ├── columns: upsert_p:9!null column1:4!null p:5 crdb_internal_mvcc_timestamp:6 tableoid:7 p_new:8!null
 │         ├── project
 │         │    ├── columns: p_new:8!null column1:4!null p:5 crdb_internal_mvcc_timestamp:6 tableoid:7
 │         │    ├── left-join (hash)
 │         │    │    ├── columns: column1:4!null p:5 crdb_internal_mvcc_timestamp:6 tableoid:7
 │         │    │    ├── ensure-upsert-distinct-on
 │         │    │    │    ├── columns: column1:4!null
 │         │    │    │    ├── grouping columns: column1:4!null
 │         │    │    │    └── values
 │         │    │    │         ├── columns: column1:4!null
 │         │    │    │         └── (0,)
 │         │    │    ├── scan parent_diff_type
 │         │    │    │    ├── columns: p:5!null crdb_internal_mvcc_timestamp:6 tableoid:7
 │         │    │    │    └── flags: avoid-full-scan disabled not visible index feature
 │         │    │    └── filters
 │         │    │         └── column1:4 = p:5
 │         │    └── projections
 │         │         └── 1 [as=p_new:8]
 │         └── projections
 │              └── CASE WHEN p:5 IS NULL THEN column1:4 ELSE p_new:8 END [as=upsert_p:9]
 └── cascade
      └── update child_diff_type
           ├── columns: <none>
           ├── fetch columns: c:14 child_diff_type.p:15
           ├── update-mapping:
           │    └── p_cast:20 => child_diff_type.p:11
           ├── input binding: &2
           ├── project
           │    ├── columns: p_cast:20!null c:14!null child_diff_type.p:15!null p_old:18!null p_new:19!null
           │    ├── inner-join (hash)
           │    │    ├── columns: c:14!null child_diff_type.p:15!null p_old:18!null p_new:19!null
           │    │    ├── scan child_diff_type
           │    │    │    ├── columns: c:14!null child_diff_type.p:15
           │    │    │    └── flags: avoid-full-scan disabled not visible index feature
           │    │    ├── select
           │    │    │    ├── columns: p_old:18 p_new:19!null
           │    │    │    ├── with-scan &1
           │    │    │    │    ├── columns: p_old:18 p_new:19!null
           │    │    │    │    └── mapping:
           │    │    │    │         ├──  parent_diff_type.p:5 => p_old:18
           │    │    │    │         └──  upsert_p:9 => p_new:19
           │    │    │    └── filters
           │    │    │         └── p_old:18 IS DISTINCT FROM p_new:19
           │    │    └── filters
           │    │         └── child_diff_type.p:15 = p_old:18
           │    └── projections
           │         └── assignment-cast: INT2 [as=p_cast:20]
           │              └── p_new:19
           └── f-k-checks
                └── f-k-checks-item: child_diff_type(p) -> parent_diff_type(p)
                     └── anti-join (hash)
                          ├── columns: p:21!null
                          ├── with-scan &2
                          │    ├── columns: p:21!null
                          │    └── mapping:
                          │         └──  p_cast:20 => p:21
                          ├── scan parent_diff_type
                          │    ├── columns: parent_diff_type.p:22!null
                          │    └── flags: avoid-full-scan disabled not visible index feature
                          └── filters
                               └── p:21 = parent_diff_type.p:22
