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

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

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:18 => child.p:9
           ├── input binding: &2
           ├── project
           │    ├── columns: p_new:18!null c:12!null child.p:13!null p_old:16!null p_new:17!null
           │    ├── 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
           │    └── projections
           │         └── 0 [as=p_new:18]
           └── f-k-checks
                └── f-k-checks-item: child(p) -> parent(p)
                     └── anti-join (hash)
                          ├── columns: p:19!null
                          ├── with-scan &2
                          │    ├── columns: p:19!null
                          │    └── mapping:
                          │         └──  p_new:18 => p:19
                          ├── scan parent
                          │    ├── columns: parent.p:20!null
                          │    └── flags: avoid-full-scan disabled not visible index feature
                          └── filters
                               └── p:19 = parent.p:20

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 DEFAULT 0,
  q INT DEFAULT 1,
  UNIQUE (c, q),
  CONSTRAINT fk FOREIGN KEY (p, q) REFERENCES parent_multi(p, q) ON UPDATE SET DEFAULT
)
----

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:27 => child_multi.p:14
           │    └── q_new:28 => child_multi.q:15
           ├── input binding: &2
           ├── project
           │    ├── columns: p_new:27!null q_new:28!null 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
           │    ├── 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
           │    └── projections
           │         ├── 0 [as=p_new:27]
           │         └── 1 [as=q_new:28]
           └── 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:27 => 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

# 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:26 => child_multi.p:13
           │    └── q_new:27 => child_multi.q:14
           ├── input binding: &2
           ├── project
           │    ├── columns: p_new:26!null q_new:27!null 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
           │    ├── 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
           │    └── projections
           │         ├── 0 [as=p_new:26]
           │         └── 1 [as=q_new:27]
           └── f-k-checks
                └── f-k-checks-item: child_multi(p,q) -> parent_multi(p,q)
                     └── anti-join (hash)
                          ├── columns: p:28!null q:29!null
                          ├── with-scan &2
                          │    ├── columns: p:28!null q:29!null
                          │    └── mapping:
                          │         ├──  p_new:26 => p:28
                          │         └──  q_new:27 => q:29
                          ├── scan parent_multi
                          │    ├── columns: parent_multi.p:31 parent_multi.q:32
                          │    └── flags: avoid-full-scan disabled not visible index feature
                          └── filters
                               ├── p:28 = parent_multi.p:31
                               └── q:29 = parent_multi.q:32

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

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:26 => child_multi.p:13
      │    │    └── q_new:27 => child_multi.q:14
      │    ├── input binding: &2
      │    ├── cascades
      │    │    └── fk2
      │    ├── project
      │    │    ├── columns: p_new:26!null q_new:27!null 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
      │    │    ├── 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
      │    │    └── projections
      │    │         ├── 0 [as=p_new:26]
      │    │         └── 1 [as=q_new:27]
      │    └── f-k-checks
      │         └── f-k-checks-item: child_multi(p,q) -> parent_multi(p,q)
      │              └── anti-join (hash)
      │                   ├── columns: p:28!null q:29!null
      │                   ├── with-scan &2
      │                   │    ├── columns: p:28!null q:29!null
      │                   │    └── mapping:
      │                   │         ├──  p_new:26 => p:28
      │                   │         └──  q_new:27 => q:29
      │                   ├── scan parent_multi
      │                   │    ├── columns: parent_multi.p:31 parent_multi.q:32
      │                   │    └── flags: avoid-full-scan disabled not visible index feature
      │                   └── filters
      │                        ├── p:28 = parent_multi.p:31
      │                        └── q:29 = parent_multi.q:32
      └── cascade
           └── update grandchild
                ├── columns: <none>
                ├── fetch columns: g:40 grandchild.c:41 grandchild.q:42
                ├── update-mapping:
                │    ├── c_new:49 => grandchild.c:36
                │    └── q_new:50 => grandchild.q:37
                ├── input binding: &3
                ├── project
                │    ├── columns: c_new:49!null q_new:50!null g:40!null grandchild.c:41!null grandchild.q:42!null c_old:45!null c_new:46!null q_old:47!null q_new:48!null
                │    ├── inner-join (hash)
                │    │    ├── columns: g:40!null grandchild.c:41!null grandchild.q:42!null c_old:45!null c_new:46!null q_old:47!null q_new:48!null
                │    │    ├── scan grandchild
                │    │    │    ├── columns: g:40!null grandchild.c:41 grandchild.q:42
                │    │    │    └── flags: avoid-full-scan disabled not visible index feature
                │    │    ├── select
                │    │    │    ├── columns: c_old:45!null c_new:46!null q_old:47!null q_new:48!null
                │    │    │    ├── with-scan &2
                │    │    │    │    ├── columns: c_old:45!null c_new:46!null q_old:47!null q_new:48!null
                │    │    │    │    └── mapping:
                │    │    │    │         ├──  child_multi.c:17 => c_old:45
                │    │    │    │         ├──  child_multi.q:19 => q_old:47
                │    │    │    │         ├──  child_multi.c:17 => c_new:46
                │    │    │    │         └──  q_new:27 => q_new:48
                │    │    │    └── filters
                │    │    │         └── (c_old:45 IS DISTINCT FROM c_new:46) OR (q_old:47 IS DISTINCT FROM q_new:48)
                │    │    └── filters
                │    │         ├── grandchild.c:41 = c_old:45
                │    │         └── grandchild.q:42 = q_old:47
                │    └── projections
                │         ├── 10 [as=c_new:49]
                │         └── 11 [as=q_new:50]
                └── f-k-checks
                     └── f-k-checks-item: grandchild(c,q) -> child_multi(c,q)
                          └── anti-join (hash)
                               ├── columns: c:51!null q:52!null
                               ├── with-scan &3
                               │    ├── columns: c:51!null q:52!null
                               │    └── mapping:
                               │         ├──  c_new:49 => c:51
                               │         └──  q_new:50 => q:52
                               ├── scan child_multi
                               │    ├── columns: child_multi.c:53!null child_multi.q:55
                               │    └── flags: avoid-full-scan disabled not visible index feature
                               └── filters
                                    ├── c:51 = child_multi.c:53
                                    └── q:52 = child_multi.q:55

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:29 => child_multi.p:16
      │    │    └── q_new:30 => child_multi.q:17
      │    ├── input binding: &2
      │    ├── cascades
      │    │    └── fk2
      │    ├── project
      │    │    ├── columns: p_new:29!null q_new:30!null 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
      │    │    ├── 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
      │    │    └── projections
      │    │         ├── 0 [as=p_new:29]
      │    │         └── 1 [as=q_new:30]
      │    └── f-k-checks
      │         └── f-k-checks-item: child_multi(p,q) -> parent_multi(p,q)
      │              └── anti-join (hash)
      │                   ├── columns: p:31!null q:32!null
      │                   ├── with-scan &2
      │                   │    ├── columns: p:31!null q:32!null
      │                   │    └── mapping:
      │                   │         ├──  p_new:29 => p:31
      │                   │         └──  q_new:30 => q:32
      │                   ├── scan parent_multi
      │                   │    ├── columns: parent_multi.p:34 parent_multi.q:35
      │                   │    └── flags: avoid-full-scan disabled not visible index feature
      │                   └── filters
      │                        ├── p:31 = parent_multi.p:34
      │                        └── q:32 = parent_multi.q:35
      └── cascade
           └── update grandchild
                ├── columns: <none>
                ├── fetch columns: g:43 grandchild.c:44 grandchild.q:45
                ├── update-mapping:
                │    ├── c_new:52 => grandchild.c:39
                │    └── q_new:53 => grandchild.q:40
                ├── input binding: &3
                ├── project
                │    ├── columns: c_new:52!null q_new:53!null g:43!null grandchild.c:44!null grandchild.q:45!null c_old:48!null c_new:49!null q_old:50!null q_new:51!null
                │    ├── inner-join (hash)
                │    │    ├── columns: g:43!null grandchild.c:44!null grandchild.q:45!null c_old:48!null c_new:49!null q_old:50!null q_new:51!null
                │    │    ├── scan grandchild
                │    │    │    ├── columns: g:43!null grandchild.c:44 grandchild.q:45
                │    │    │    └── flags: avoid-full-scan disabled not visible index feature
                │    │    ├── select
                │    │    │    ├── columns: c_old:48!null c_new:49!null q_old:50!null q_new:51!null
                │    │    │    ├── with-scan &2
                │    │    │    │    ├── columns: c_old:48!null c_new:49!null q_old:50!null q_new:51!null
                │    │    │    │    └── mapping:
                │    │    │    │         ├──  child_multi.c:20 => c_old:48
                │    │    │    │         ├──  child_multi.q:22 => q_old:50
                │    │    │    │         ├──  child_multi.c:20 => c_new:49
                │    │    │    │         └──  q_new:30 => q_new:51
                │    │    │    └── filters
                │    │    │         └── (c_old:48 IS DISTINCT FROM c_new:49) OR (q_old:50 IS DISTINCT FROM q_new:51)
                │    │    └── filters
                │    │         ├── grandchild.c:44 = c_old:48
                │    │         └── grandchild.q:45 = q_old:50
                │    └── projections
                │         ├── 10 [as=c_new:52]
                │         └── 11 [as=q_new:53]
                └── f-k-checks
                     └── f-k-checks-item: grandchild(c,q) -> child_multi(c,q)
                          └── anti-join (hash)
                               ├── columns: c:54!null q:55!null
                               ├── with-scan &3
                               │    ├── columns: c:54!null q:55!null
                               │    └── mapping:
                               │         ├──  c_new:52 => c:54
                               │         └──  q_new:53 => q:55
                               ├── scan child_multi
                               │    ├── columns: child_multi.c:56!null child_multi.q:58
                               │    └── flags: avoid-full-scan disabled not visible index feature
                               └── filters
                                    ├── c:54 = child_multi.c:56
                                    └── q:55 = child_multi.q:58

# 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:30 => child_multi.p:17
      │    │    └── q_new:31 => child_multi.q:18
      │    ├── input binding: &2
      │    ├── cascades
      │    │    └── fk2
      │    ├── project
      │    │    ├── columns: p_new:30!null q_new:31!null 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
      │    │    ├── 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
      │    │    └── projections
      │    │         ├── 0 [as=p_new:30]
      │    │         └── 1 [as=q_new:31]
      │    └── 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
      │                   ├── with-scan &2
      │                   │    ├── columns: p:32!null q:33!null
      │                   │    └── mapping:
      │                   │         ├──  p_new:30 => p:32
      │                   │         └──  q_new:31 => q:33
      │                   ├── 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
      └── cascade
           └── update grandchild
                ├── columns: <none>
                ├── fetch columns: g:44 grandchild.c:45 grandchild.q:46
                ├── update-mapping:
                │    ├── c_new:53 => grandchild.c:40
                │    └── q_new:54 => grandchild.q:41
                ├── input binding: &3
                ├── project
                │    ├── columns: c_new:53!null q_new:54!null g:44!null grandchild.c:45!null grandchild.q:46!null c_old:49!null c_new:50!null q_old:51!null q_new:52!null
                │    ├── inner-join (hash)
                │    │    ├── columns: g:44!null grandchild.c:45!null grandchild.q:46!null c_old:49!null c_new:50!null q_old:51!null q_new:52!null
                │    │    ├── scan grandchild
                │    │    │    ├── columns: g:44!null grandchild.c:45 grandchild.q:46
                │    │    │    └── flags: avoid-full-scan disabled not visible index feature
                │    │    ├── select
                │    │    │    ├── columns: c_old:49!null c_new:50!null q_old:51!null q_new:52!null
                │    │    │    ├── with-scan &2
                │    │    │    │    ├── columns: c_old:49!null c_new:50!null q_old:51!null q_new:52!null
                │    │    │    │    └── mapping:
                │    │    │    │         ├──  child_multi.c:21 => c_old:49
                │    │    │    │         ├──  child_multi.q:23 => q_old:51
                │    │    │    │         ├──  child_multi.c:21 => c_new:50
                │    │    │    │         └──  q_new:31 => q_new:52
                │    │    │    └── filters
                │    │    │         └── (c_old:49 IS DISTINCT FROM c_new:50) OR (q_old:51 IS DISTINCT FROM q_new:52)
                │    │    └── filters
                │    │         ├── grandchild.c:45 = c_old:49
                │    │         └── grandchild.q:46 = q_old:51
                │    └── projections
                │         ├── 10 [as=c_new:53]
                │         └── 11 [as=q_new:54]
                └── f-k-checks
                     └── f-k-checks-item: grandchild(c,q) -> child_multi(c,q)
                          └── anti-join (hash)
                               ├── columns: c:55!null q:56!null
                               ├── with-scan &3
                               │    ├── columns: c:55!null q:56!null
                               │    └── mapping:
                               │         ├──  c_new:53 => c:55
                               │         └──  q_new:54 => q:56
                               ├── scan child_multi
                               │    ├── columns: child_multi.c:57!null child_multi.q:59
                               │    └── flags: avoid-full-scan disabled not visible index feature
                               └── filters
                                    ├── c:55 = child_multi.c:57
                                    └── q:56 = child_multi.q:59

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:32 => child_multi.p:19
      │    │    └── q_new:33 => child_multi.q:20
      │    ├── input binding: &2
      │    ├── cascades
      │    │    └── fk2
      │    ├── project
      │    │    ├── columns: p_new:32!null q_new:33!null 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
      │    │    ├── 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
      │    │    └── projections
      │    │         ├── 0 [as=p_new:32]
      │    │         └── 1 [as=q_new:33]
      │    └── f-k-checks
      │         └── f-k-checks-item: child_multi(p,q) -> parent_multi(p,q)
      │              └── anti-join (hash)
      │                   ├── columns: p:34!null q:35!null
      │                   ├── with-scan &2
      │                   │    ├── columns: p:34!null q:35!null
      │                   │    └── mapping:
      │                   │         ├──  p_new:32 => p:34
      │                   │         └──  q_new:33 => q:35
      │                   ├── scan parent_multi
      │                   │    ├── columns: parent_multi.p:37 parent_multi.q:38
      │                   │    └── flags: avoid-full-scan disabled not visible index feature
      │                   └── filters
      │                        ├── p:34 = parent_multi.p:37
      │                        └── q:35 = parent_multi.q:38
      └── cascade
           └── update grandchild
                ├── columns: <none>
                ├── fetch columns: g:46 grandchild.c:47 grandchild.q:48
                ├── update-mapping:
                │    ├── c_new:55 => grandchild.c:42
                │    └── q_new:56 => grandchild.q:43
                ├── input binding: &3
                ├── project
                │    ├── columns: c_new:55!null q_new:56!null g:46!null grandchild.c:47!null grandchild.q:48!null c_old:51!null c_new:52!null q_old:53!null q_new:54!null
                │    ├── inner-join (hash)
                │    │    ├── columns: g:46!null grandchild.c:47!null grandchild.q:48!null c_old:51!null c_new:52!null q_old:53!null q_new:54!null
                │    │    ├── scan grandchild
                │    │    │    ├── columns: g:46!null grandchild.c:47 grandchild.q:48
                │    │    │    └── flags: avoid-full-scan disabled not visible index feature
                │    │    ├── select
                │    │    │    ├── columns: c_old:51!null c_new:52!null q_old:53!null q_new:54!null
                │    │    │    ├── with-scan &2
                │    │    │    │    ├── columns: c_old:51!null c_new:52!null q_old:53!null q_new:54!null
                │    │    │    │    └── mapping:
                │    │    │    │         ├──  child_multi.c:23 => c_old:51
                │    │    │    │         ├──  child_multi.q:25 => q_old:53
                │    │    │    │         ├──  child_multi.c:23 => c_new:52
                │    │    │    │         └──  q_new:33 => q_new:54
                │    │    │    └── filters
                │    │    │         └── (c_old:51 IS DISTINCT FROM c_new:52) OR (q_old:53 IS DISTINCT FROM q_new:54)
                │    │    └── filters
                │    │         ├── grandchild.c:47 = c_old:51
                │    │         └── grandchild.q:48 = q_old:53
                │    └── projections
                │         ├── 10 [as=c_new:55]
                │         └── 11 [as=q_new:56]
                └── f-k-checks
                     └── f-k-checks-item: grandchild(c,q) -> child_multi(c,q)
                          └── anti-join (hash)
                               ├── columns: c:57!null q:58!null
                               ├── with-scan &3
                               │    ├── columns: c:57!null q:58!null
                               │    └── mapping:
                               │         ├──  c_new:55 => c:57
                               │         └──  q_new:56 => q:58
                               ├── scan child_multi
                               │    ├── columns: child_multi.c:59!null child_multi.q:61
                               │    └── flags: avoid-full-scan disabled not visible index feature
                               └── filters
                                    ├── c:57 = child_multi.c:59
                                    └── q:58 = child_multi.q:61

# Test a cascade to a child that requires an assignment cast because the
# referencing column's DEFAULT expression type is not identical to the
# referencing column type.
exec-ddl
CREATE TABLE parent_assn_cast (p INT PRIMARY KEY, p2 INT UNIQUE)
----

exec-ddl
CREATE TABLE child_assn_cast (
  c INT PRIMARY KEY,
  p DECIMAL(10, 0) DEFAULT 1.45::DECIMAL(10, 2) REFERENCES parent_assn_cast(p) ON UPDATE SET DEFAULT
)
----

build-post-queries
UPDATE parent_assn_cast SET p = 1 WHERE p > 1
----
root
 ├── update parent_assn_cast
 │    ├── columns: <none>
 │    ├── fetch columns: p:5 p2:6
 │    ├── update-mapping:
 │    │    └── p_new:9 => p:1
 │    ├── input binding: &1
 │    ├── cascades
 │    │    └── child_assn_cast_p_fkey
 │    └── 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 [as=p_new:9]
 └── cascade
      └── update child_assn_cast
           ├── columns: <none>
           ├── fetch columns: c:14 child_assn_cast.p:15
           ├── update-mapping:
           │    └── p_cast:21 => child_assn_cast.p:11
           ├── input binding: &2
           ├── project
           │    ├── columns: p_cast:21!null c:14!null child_assn_cast.p:15!null p_old:18!null p_new:19!null
           │    ├── project
           │    │    ├── columns: p_new:20!null c:14!null child_assn_cast.p:15!null p_old:18!null p_new:19!null
           │    │    ├── inner-join (cross)
           │    │    │    ├── columns: c:14!null child_assn_cast.p:15!null p_old:18!null p_new:19!null
           │    │    │    ├── scan child_assn_cast
           │    │    │    │    ├── columns: c:14!null child_assn_cast.p:15
           │    │    │    │    └── 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_assn_cast.p:5 => p_old:18
           │    │    │    │    │         └──  p_new:9 => p_new:19
           │    │    │    │    └── filters
           │    │    │    │         └── p_old:18 IS DISTINCT FROM p_new:19
           │    │    │    └── filters
           │    │    │         └── child_assn_cast.p:15 = p_old:18
           │    │    └── projections
           │    │         └── 1.45::DECIMAL(10,2) [as=p_new:20]
           │    └── 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 (cross)
                          ├── 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) DEFAULT 1.45::DECIMAL(10, 2) REFERENCES parent_assn_cast(p2) ON UPDATE SET DEFAULT
)
----

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

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

# 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 DEFAULT 0 NOT NULL REFERENCES parent_partial(p) ON UPDATE SET DEFAULT,
  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:20 => child_partial.p:9
           ├── partial index put columns: partial_index_put1:21 partial_index_put2:22
           ├── partial index del columns: partial_index_put1:21 partial_index_del2:23
           ├── input binding: &2
           ├── project
           │    ├── columns: partial_index_put1:21 partial_index_put2:22!null partial_index_del2:23!null c:13!null child_partial.p:14!null i:15 p_old:18!null p_new:19!null p_new:20!null
           │    ├── project
           │    │    ├── columns: p_new:20!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!null 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
           │    │         └── 0 [as=p_new:20]
           │    └── projections
           │         ├── i:15 > 0 [as=partial_index_put1:21]
           │         ├── p_new:20 > 0 [as=partial_index_put2:22]
           │         └── child_partial.p:14 > 0 [as=partial_index_del2:23]
           └── f-k-checks
                └── f-k-checks-item: child_partial(p) -> parent_partial(p)
                     └── anti-join (hash)
                          ├── columns: p:24!null
                          ├── with-scan &2
                          │    ├── columns: p:24!null
                          │    └── mapping:
                          │         └──  p_new:20 => p:24
                          ├── scan parent_partial
                          │    ├── columns: parent_partial.p:25!null
                          │    └── flags: avoid-full-scan disabled not visible index feature
                          └── filters
                               └── p:24 = parent_partial.p:25

# 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 DEFAULT 0 NOT NULL REFERENCES parent_virt(p) ON UPDATE SET DEFAULT,
  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:20 => child_virt.p:9
           │    └── p_new:20 => v:10
           ├── input binding: &2
           ├── project
           │    ├── columns: p_new:20!null c:13!null child_virt.p:14!null v:15!null p_old:18!null p_new:19!null
           │    ├── inner-join (hash)
           │    │    ├── columns: c:13!null child_virt.p:14!null v:15!null p_old:18!null p_new:19!null
           │    │    ├── project
           │    │    │    ├── columns: v:15!null c:13!null child_virt.p:14!null
           │    │    │    ├── scan child_virt
           │    │    │    │    ├── columns: c:13!null child_virt.p:14!null
           │    │    │    │    ├── 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
           │    └── projections
           │         └── 0 [as=p_new:20]
           └── f-k-checks
                └── f-k-checks-item: child_virt(p) -> parent_virt(p)
                     └── anti-join (hash)
                          ├── columns: p:21!null
                          ├── with-scan &2
                          │    ├── columns: p:21!null
                          │    └── mapping:
                          │         └──  p_new:20 => p:21
                          ├── scan parent_virt
                          │    ├── columns: parent_virt.p:22!null
                          │    └── flags: avoid-full-scan disabled not visible index feature
                          └── filters
                               └── p:21 = parent_virt.p:22
