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

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

build
INSERT INTO child VALUES (100, 1), (200, 1)
----
insert child
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── column1:5 => c:1
 │    └── column2:6 => child.p:2
 ├── input binding: &1
 ├── values
 │    ├── columns: column1:5!null column2:6!null
 │    ├── (100, 1)
 │    └── (200, 1)
 └── f-k-checks
      └── f-k-checks-item: child(p) -> parent(p)
           └── anti-join (hash)
                ├── columns: p:7!null
                ├── with-scan &1
                │    ├── columns: p:7!null
                │    └── mapping:
                │         └──  column2:6 => p:7
                ├── scan parent
                │    ├── columns: parent.p:8!null
                │    └── flags: avoid-full-scan disabled not visible index feature
                └── filters
                     └── p:7 = parent.p:8

build
INSERT INTO child VALUES (100, 1), (200, 1) ON CONFLICT DO NOTHING
----
insert child
 ├── arbiter indexes: child_pkey
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── column1:5 => c:1
 │    └── column2:6 => child.p:2
 ├── input binding: &1
 ├── upsert-distinct-on
 │    ├── columns: column1:5!null column2:6!null
 │    ├── grouping columns: column1:5!null
 │    ├── anti-join (hash)
 │    │    ├── columns: column1:5!null column2:6!null
 │    │    ├── values
 │    │    │    ├── columns: column1:5!null column2:6!null
 │    │    │    ├── (100, 1)
 │    │    │    └── (200, 1)
 │    │    ├── scan child
 │    │    │    ├── columns: c:7!null child.p:8!null
 │    │    │    └── flags: avoid-full-scan disabled not visible index feature
 │    │    └── filters
 │    │         └── column1:5 = c:7
 │    └── aggregations
 │         └── first-agg [as=column2:6]
 │              └── column2:6
 └── f-k-checks
      └── f-k-checks-item: child(p) -> parent(p)
           └── anti-join (hash)
                ├── columns: p:11!null
                ├── with-scan &1
                │    ├── columns: p:11!null
                │    └── mapping:
                │         └──  column2:6 => p:11
                ├── scan parent
                │    ├── columns: parent.p:12!null
                │    └── flags: avoid-full-scan disabled not visible index feature
                └── filters
                     └── p:11 = parent.p:12

# Use a non-constant input.
exec-ddl
CREATE TABLE xy (x INT, y INT)
----

build
INSERT INTO child SELECT x, y FROM xy
----
insert child
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── x:5 => c:1
 │    └── y:6 => child.p:2
 ├── input binding: &1
 ├── project
 │    ├── columns: x:5 y:6
 │    └── scan xy
 │         └── columns: x:5 y:6 rowid:7!null xy.crdb_internal_mvcc_timestamp:8 xy.tableoid:9
 └── f-k-checks
      └── f-k-checks-item: child(p) -> parent(p)
           └── anti-join (hash)
                ├── columns: p:10
                ├── with-scan &1
                │    ├── columns: p:10
                │    └── mapping:
                │         └──  y:6 => p:10
                ├── scan parent
                │    ├── columns: parent.p:11!null
                │    └── flags: avoid-full-scan disabled not visible index feature
                └── filters
                     └── p:10 = parent.p:11

exec-ddl
CREATE TABLE child_nullable (c INT PRIMARY KEY, p INT REFERENCES parent(p));
----

# Because the input column can be NULL (in which case it requires no FK match),
# we have to add an extra filter.
build
INSERT INTO child_nullable VALUES (100, 1), (200, NULL)
----
insert child_nullable
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── column1:5 => c:1
 │    └── column2:6 => child_nullable.p:2
 ├── input binding: &1
 ├── values
 │    ├── columns: column1:5!null column2:6
 │    ├── (100, 1)
 │    └── (200, NULL::INT8)
 └── f-k-checks
      └── f-k-checks-item: child_nullable(p) -> parent(p)
           └── anti-join (hash)
                ├── columns: p:7!null
                ├── select
                │    ├── columns: p:7!null
                │    ├── with-scan &1
                │    │    ├── columns: p:7
                │    │    └── mapping:
                │    │         └──  column2:6 => p:7
                │    └── filters
                │         └── p:7 IS NOT NULL
                ├── scan parent
                │    ├── columns: parent.p:8!null
                │    └── flags: avoid-full-scan disabled not visible index feature
                └── filters
                     └── p:7 = parent.p:8

# The column is nullable but we know that the input is not null, so we don't
# need to plan the filter.
build
INSERT INTO child_nullable VALUES (100, 1), (200, 1)
----
insert child_nullable
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── column1:5 => c:1
 │    └── column2:6 => child_nullable.p:2
 ├── input binding: &1
 ├── values
 │    ├── columns: column1:5!null column2:6!null
 │    ├── (100, 1)
 │    └── (200, 1)
 └── f-k-checks
      └── f-k-checks-item: child_nullable(p) -> parent(p)
           └── anti-join (hash)
                ├── columns: p:7!null
                ├── with-scan &1
                │    ├── columns: p:7!null
                │    └── mapping:
                │         └──  column2:6 => p:7
                ├── scan parent
                │    ├── columns: parent.p:8!null
                │    └── flags: avoid-full-scan disabled not visible index feature
                └── filters
                     └── p:7 = parent.p:8

# In this case, we know that we are inserting *only* NULL values, so we don't
# need to check any FKs.
# NOTE: We use the norm directive here so that assignment casts are eliminated
# by normalization rules, allowing removal of FK checks.
norm
INSERT INTO child_nullable VALUES (100, NULL), (200, NULL)
----
insert child_nullable
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── column1:5 => c:1
 │    └── column2:6 => p:2
 └── values
      ├── columns: column1:5!null column2:6
      ├── (100, NULL)
      └── (200, NULL)

# Same as above.
build
INSERT INTO child_nullable (c) VALUES (100), (200)
----
insert child_nullable
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── column1:5 => c:1
 │    └── p_default:6 => p:2
 └── project
      ├── columns: p_default:6 column1:5!null
      ├── values
      │    ├── columns: column1:5!null
      │    ├── (100,)
      │    └── (200,)
      └── projections
           └── NULL::INT8 [as=p_default:6]

# Check planning of filter with FULL match (which should be the same on a
# single column).
exec-ddl
CREATE TABLE child_nullable_full (c INT PRIMARY KEY, p INT REFERENCES parent(p) MATCH FULL)
----

build
INSERT INTO child_nullable_full VALUES (100, 1), (200, NULL)
----
insert child_nullable_full
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── column1:5 => c:1
 │    └── column2:6 => child_nullable_full.p:2
 ├── input binding: &1
 ├── values
 │    ├── columns: column1:5!null column2:6
 │    ├── (100, 1)
 │    └── (200, NULL::INT8)
 └── f-k-checks
      └── f-k-checks-item: child_nullable_full(p) -> parent(p)
           └── anti-join (hash)
                ├── columns: p:7!null
                ├── select
                │    ├── columns: p:7!null
                │    ├── with-scan &1
                │    │    ├── columns: p:7
                │    │    └── mapping:
                │    │         └──  column2:6 => p:7
                │    └── filters
                │         └── p:7 IS NOT NULL
                ├── scan parent
                │    ├── columns: parent.p:8!null
                │    └── flags: avoid-full-scan disabled not visible index feature
                └── filters
                     └── p:7 = parent.p:8

# No FK check needed.
build
INSERT INTO child_nullable_full (c) VALUES (100), (200)
----
insert child_nullable_full
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── column1:5 => c:1
 │    └── p_default:6 => p:2
 └── project
      ├── columns: p_default:6 column1:5!null
      ├── values
      │    ├── columns: column1:5!null
      │    ├── (100,)
      │    └── (200,)
      └── projections
           └── NULL::INT8 [as=p_default:6]

# Tests with multicolumn FKs.
exec-ddl
CREATE TABLE multi_col_parent (p INT, q INT, r INT, other INT, PRIMARY KEY (p, q, r))
----

exec-ddl
CREATE TABLE multi_col_child  (
  c INT PRIMARY KEY,
  p INT, q INT, r INT,
  CONSTRAINT fk FOREIGN KEY (p,q,r) REFERENCES multi_col_parent(p,q,r) MATCH SIMPLE
)
----

# All columns are nullable and must be part of the filter.
build
INSERT INTO multi_col_child VALUES (4, NULL, NULL, NULL), (5, 1, 2, 3)
----
insert multi_col_child
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── column1:7 => c:1
 │    ├── column2:8 => multi_col_child.p:2
 │    ├── column3:9 => multi_col_child.q:3
 │    └── column4:10 => multi_col_child.r:4
 ├── input binding: &1
 ├── values
 │    ├── columns: column1:7!null column2:8 column3:9 column4:10
 │    ├── (4, NULL::INT8, NULL::INT8, NULL::INT8)
 │    └── (5, 1, 2, 3)
 └── f-k-checks
      └── f-k-checks-item: multi_col_child(p,q,r) -> multi_col_parent(p,q,r)
           └── anti-join (hash)
                ├── columns: p:11!null q:12!null r:13!null
                ├── select
                │    ├── columns: p:11!null q:12!null r:13!null
                │    ├── with-scan &1
                │    │    ├── columns: p:11 q:12 r:13
                │    │    └── mapping:
                │    │         ├──  column2:8 => p:11
                │    │         ├──  column3:9 => q:12
                │    │         └──  column4:10 => r:13
                │    └── filters
                │         ├── p:11 IS NOT NULL
                │         ├── q:12 IS NOT NULL
                │         └── r:13 IS NOT NULL
                ├── scan multi_col_parent
                │    ├── columns: multi_col_parent.p:14!null multi_col_parent.q:15!null multi_col_parent.r:16!null
                │    └── flags: avoid-full-scan disabled not visible index feature
                └── filters
                     ├── p:11 = multi_col_parent.p:14
                     ├── q:12 = multi_col_parent.q:15
                     └── r:13 = multi_col_parent.r:16

# Only p and q are nullable.
build
INSERT INTO multi_col_child VALUES (2, NULL, 20, 20), (3, 20, NULL, 20)
----
insert multi_col_child
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── column1:7 => c:1
 │    ├── column2:8 => multi_col_child.p:2
 │    ├── column3:9 => multi_col_child.q:3
 │    └── column4:10 => multi_col_child.r:4
 ├── input binding: &1
 ├── values
 │    ├── columns: column1:7!null column2:8 column3:9 column4:10!null
 │    ├── (2, NULL::INT8, 20, 20)
 │    └── (3, 20, NULL::INT8, 20)
 └── f-k-checks
      └── f-k-checks-item: multi_col_child(p,q,r) -> multi_col_parent(p,q,r)
           └── anti-join (hash)
                ├── columns: p:11!null q:12!null r:13!null
                ├── select
                │    ├── columns: p:11!null q:12!null r:13!null
                │    ├── with-scan &1
                │    │    ├── columns: p:11 q:12 r:13!null
                │    │    └── mapping:
                │    │         ├──  column2:8 => p:11
                │    │         ├──  column3:9 => q:12
                │    │         └──  column4:10 => r:13
                │    └── filters
                │         ├── p:11 IS NOT NULL
                │         └── q:12 IS NOT NULL
                ├── scan multi_col_parent
                │    ├── columns: multi_col_parent.p:14!null multi_col_parent.q:15!null multi_col_parent.r:16!null
                │    └── flags: avoid-full-scan disabled not visible index feature
                └── filters
                     ├── p:11 = multi_col_parent.p:14
                     ├── q:12 = multi_col_parent.q:15
                     └── r:13 = multi_col_parent.r:16

# All the FK columns are not-null; no filter necessary.
build
INSERT INTO multi_col_child VALUES (1, 10, 10, 10)
----
insert multi_col_child
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── column1:7 => c:1
 │    ├── column2:8 => multi_col_child.p:2
 │    ├── column3:9 => multi_col_child.q:3
 │    └── column4:10 => multi_col_child.r:4
 ├── input binding: &1
 ├── values
 │    ├── columns: column1:7!null column2:8!null column3:9!null column4:10!null
 │    └── (1, 10, 10, 10)
 └── f-k-checks
      └── f-k-checks-item: multi_col_child(p,q,r) -> multi_col_parent(p,q,r)
           └── anti-join (hash)
                ├── columns: p:11!null q:12!null r:13!null
                ├── with-scan &1
                │    ├── columns: p:11!null q:12!null r:13!null
                │    └── mapping:
                │         ├──  column2:8 => p:11
                │         ├──  column3:9 => q:12
                │         └──  column4:10 => r:13
                ├── scan multi_col_parent
                │    ├── columns: multi_col_parent.p:14!null multi_col_parent.q:15!null multi_col_parent.r:16!null
                │    └── flags: avoid-full-scan disabled not visible index feature
                └── filters
                     ├── p:11 = multi_col_parent.p:14
                     ├── q:12 = multi_col_parent.q:15
                     └── r:13 = multi_col_parent.r:16

# No FK check needed - we have only NULL values for a FK column.
# NOTE: We use the norm directive here so that assignment casts are eliminated
# by normalization rules, allowing removal of FK checks.
norm
INSERT INTO multi_col_child VALUES (1, 10, NULL, 10)
----
insert multi_col_child
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── column1:7 => c:1
 │    ├── column2:8 => p:2
 │    ├── column3:9 => q:3
 │    └── column4:10 => r:4
 └── values
      ├── columns: column1:7!null column2:8!null column3:9 column4:10!null
      └── (1, 10, NULL, 10)

exec-ddl
CREATE TABLE multi_col_child_full  (
  c INT PRIMARY KEY,
  p INT, q INT, r INT,
  CONSTRAINT fk FOREIGN KEY (p,q,r) REFERENCES multi_col_parent(p,q,r) MATCH FULL
)
----

# All columns are nullable and must be part of the filter.
build
INSERT INTO multi_col_child_full VALUES (4, NULL, NULL, NULL), (5, 1, 2, 3)
----
insert multi_col_child_full
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── column1:7 => c:1
 │    ├── column2:8 => multi_col_child_full.p:2
 │    ├── column3:9 => multi_col_child_full.q:3
 │    └── column4:10 => multi_col_child_full.r:4
 ├── input binding: &1
 ├── values
 │    ├── columns: column1:7!null column2:8 column3:9 column4:10
 │    ├── (4, NULL::INT8, NULL::INT8, NULL::INT8)
 │    └── (5, 1, 2, 3)
 └── f-k-checks
      └── f-k-checks-item: multi_col_child_full(p,q,r) -> multi_col_parent(p,q,r)
           └── anti-join (hash)
                ├── columns: p:11 q:12 r:13
                ├── select
                │    ├── columns: p:11 q:12 r:13
                │    ├── with-scan &1
                │    │    ├── columns: p:11 q:12 r:13
                │    │    └── mapping:
                │    │         ├──  column2:8 => p:11
                │    │         ├──  column3:9 => q:12
                │    │         └──  column4:10 => r:13
                │    └── filters
                │         └── ((p:11 IS NOT NULL) OR (q:12 IS NOT NULL)) OR (r:13 IS NOT NULL)
                ├── scan multi_col_parent
                │    ├── columns: multi_col_parent.p:14!null multi_col_parent.q:15!null multi_col_parent.r:16!null
                │    └── flags: avoid-full-scan disabled not visible index feature
                └── filters
                     ├── p:11 = multi_col_parent.p:14
                     ├── q:12 = multi_col_parent.q:15
                     └── r:13 = multi_col_parent.r:16

# Only p and q are nullable; no filter necessary.
build
INSERT INTO multi_col_child_full VALUES (2, NULL, 20, 20), (3, 20, NULL, 20)
----
insert multi_col_child_full
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── column1:7 => c:1
 │    ├── column2:8 => multi_col_child_full.p:2
 │    ├── column3:9 => multi_col_child_full.q:3
 │    └── column4:10 => multi_col_child_full.r:4
 ├── input binding: &1
 ├── values
 │    ├── columns: column1:7!null column2:8 column3:9 column4:10!null
 │    ├── (2, NULL::INT8, 20, 20)
 │    └── (3, 20, NULL::INT8, 20)
 └── f-k-checks
      └── f-k-checks-item: multi_col_child_full(p,q,r) -> multi_col_parent(p,q,r)
           └── anti-join (hash)
                ├── columns: p:11 q:12 r:13!null
                ├── with-scan &1
                │    ├── columns: p:11 q:12 r:13!null
                │    └── mapping:
                │         ├──  column2:8 => p:11
                │         ├──  column3:9 => q:12
                │         └──  column4:10 => r:13
                ├── scan multi_col_parent
                │    ├── columns: multi_col_parent.p:14!null multi_col_parent.q:15!null multi_col_parent.r:16!null
                │    └── flags: avoid-full-scan disabled not visible index feature
                └── filters
                     ├── p:11 = multi_col_parent.p:14
                     ├── q:12 = multi_col_parent.q:15
                     └── r:13 = multi_col_parent.r:16

# All the FK columns are not-null; no filter necessary.
build
INSERT INTO multi_col_child_full VALUES (1, 10, 10, 10)
----
insert multi_col_child_full
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── column1:7 => c:1
 │    ├── column2:8 => multi_col_child_full.p:2
 │    ├── column3:9 => multi_col_child_full.q:3
 │    └── column4:10 => multi_col_child_full.r:4
 ├── input binding: &1
 ├── values
 │    ├── columns: column1:7!null column2:8!null column3:9!null column4:10!null
 │    └── (1, 10, 10, 10)
 └── f-k-checks
      └── f-k-checks-item: multi_col_child_full(p,q,r) -> multi_col_parent(p,q,r)
           └── anti-join (hash)
                ├── columns: p:11!null q:12!null r:13!null
                ├── with-scan &1
                │    ├── columns: p:11!null q:12!null r:13!null
                │    └── mapping:
                │         ├──  column2:8 => p:11
                │         ├──  column3:9 => q:12
                │         └──  column4:10 => r:13
                ├── scan multi_col_parent
                │    ├── columns: multi_col_parent.p:14!null multi_col_parent.q:15!null multi_col_parent.r:16!null
                │    └── flags: avoid-full-scan disabled not visible index feature
                └── filters
                     ├── p:11 = multi_col_parent.p:14
                     ├── q:12 = multi_col_parent.q:15
                     └── r:13 = multi_col_parent.r:16

# No FK check needed when all FK columns only have NULL values.
# NOTE: We use the norm directive here so that assignment casts are eliminated
# by normalization rules, allowing removal of FK checks.
norm
INSERT INTO multi_col_child_full VALUES (1, NULL, NULL, NULL)
----
insert multi_col_child_full
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── column1:7 => c:1
 │    ├── column2:8 => p:2
 │    ├── column3:9 => q:3
 │    └── column4:10 => r:4
 └── values
      ├── columns: column1:7!null column2:8 column3:9 column4:10
      └── (1, NULL, NULL, NULL)

# But with MATCH FULL, the FK check is needed when only a subset of the columns
# only have NULL values.
build
INSERT INTO multi_col_child_full VALUES (1, NULL, 2, NULL)
----
insert multi_col_child_full
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── column1:7 => c:1
 │    ├── column2:8 => multi_col_child_full.p:2
 │    ├── column3:9 => multi_col_child_full.q:3
 │    └── column4:10 => multi_col_child_full.r:4
 ├── input binding: &1
 ├── values
 │    ├── columns: column1:7!null column2:8 column3:9!null column4:10
 │    └── (1, NULL::INT8, 2, NULL::INT8)
 └── f-k-checks
      └── f-k-checks-item: multi_col_child_full(p,q,r) -> multi_col_parent(p,q,r)
           └── anti-join (hash)
                ├── columns: p:11 q:12!null r:13
                ├── with-scan &1
                │    ├── columns: p:11 q:12!null r:13
                │    └── mapping:
                │         ├──  column2:8 => p:11
                │         ├──  column3:9 => q:12
                │         └──  column4:10 => r:13
                ├── scan multi_col_parent
                │    ├── columns: multi_col_parent.p:14!null multi_col_parent.q:15!null multi_col_parent.r:16!null
                │    └── flags: avoid-full-scan disabled not visible index feature
                └── filters
                     ├── p:11 = multi_col_parent.p:14
                     ├── q:12 = multi_col_parent.q:15
                     └── r:13 = multi_col_parent.r:16

exec-ddl
CREATE TABLE multi_ref_parent_a (a INT PRIMARY KEY, other INT)
----

exec-ddl
CREATE TABLE multi_ref_parent_bc (b INT, c INT, PRIMARY KEY (b,c), other INT)
----

exec-ddl
CREATE TABLE multi_ref_child (
  k INT PRIMARY KEY,
  a INT,
  b INT,
  c INT,
  CONSTRAINT fk FOREIGN KEY (a) REFERENCES multi_ref_parent_a(a),
  CONSTRAINT fk FOREIGN KEY (b,c) REFERENCES multi_ref_parent_bc(b,c)
)
----

build
INSERT INTO multi_ref_child VALUES (1, 1, NULL, NULL), (2, NULL, 2, NULL), (3, NULL, NULL, 3)
----
insert multi_ref_child
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── column1:7 => k:1
 │    ├── column2:8 => multi_ref_child.a:2
 │    ├── column3:9 => multi_ref_child.b:3
 │    └── column4:10 => multi_ref_child.c:4
 ├── input binding: &1
 ├── values
 │    ├── columns: column1:7!null column2:8 column3:9 column4:10
 │    ├── (1, 1, NULL::INT8, NULL::INT8)
 │    ├── (2, NULL::INT8, 2, NULL::INT8)
 │    └── (3, NULL::INT8, NULL::INT8, 3)
 └── f-k-checks
      ├── f-k-checks-item: multi_ref_child(a) -> multi_ref_parent_a(a)
      │    └── anti-join (hash)
      │         ├── columns: a:11!null
      │         ├── select
      │         │    ├── columns: a:11!null
      │         │    ├── with-scan &1
      │         │    │    ├── columns: a:11
      │         │    │    └── mapping:
      │         │    │         └──  column2:8 => a:11
      │         │    └── filters
      │         │         └── a:11 IS NOT NULL
      │         ├── scan multi_ref_parent_a
      │         │    ├── columns: multi_ref_parent_a.a:12!null
      │         │    └── flags: avoid-full-scan disabled not visible index feature
      │         └── filters
      │              └── a:11 = multi_ref_parent_a.a:12
      └── f-k-checks-item: multi_ref_child(b,c) -> multi_ref_parent_bc(b,c)
           └── anti-join (hash)
                ├── columns: b:16!null c:17!null
                ├── select
                │    ├── columns: b:16!null c:17!null
                │    ├── with-scan &1
                │    │    ├── columns: b:16 c:17
                │    │    └── mapping:
                │    │         ├──  column3:9 => b:16
                │    │         └──  column4:10 => c:17
                │    └── filters
                │         ├── b:16 IS NOT NULL
                │         └── c:17 IS NOT NULL
                ├── scan multi_ref_parent_bc
                │    ├── columns: multi_ref_parent_bc.b:18!null multi_ref_parent_bc.c:19!null
                │    └── flags: avoid-full-scan disabled not visible index feature
                └── filters
                     ├── b:16 = multi_ref_parent_bc.b:18
                     └── c:17 = multi_ref_parent_bc.c:19

# NOTE: We use the norm directive here so that assignment casts are eliminated
# by normalization rules, allowing removal of FK checks.
norm
INSERT INTO multi_ref_child VALUES (1, NULL, NULL, NULL)
----
insert multi_ref_child
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── column1:7 => k:1
 │    ├── column2:8 => a:2
 │    ├── column3:9 => b:3
 │    └── column4:10 => c:4
 └── values
      ├── columns: column1:7!null column2:8 column3:9 column4:10
      └── (1, NULL, NULL, NULL)

# Verify that the join hint is set.
build set=prefer_lookup_joins_for_fks=true
INSERT INTO child VALUES (100, 1), (200, 1)
----
insert child
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── column1:5 => c:1
 │    └── column2:6 => child.p:2
 ├── input binding: &1
 ├── values
 │    ├── columns: column1:5!null column2:6!null
 │    ├── (100, 1)
 │    └── (200, 1)
 └── f-k-checks
      └── f-k-checks-item: child(p) -> parent(p)
           └── anti-join (hash)
                ├── columns: p:7!null
                ├── flags: prefer lookup join (into right side)
                ├── with-scan &1
                │    ├── columns: p:7!null
                │    └── mapping:
                │         └──  column2:6 => p:7
                ├── scan parent
                │    ├── columns: parent.p:8!null
                │    └── flags: avoid-full-scan disabled not visible index feature
                └── filters
                     └── p:7 = parent.p:8

# Verify that we lock the parent when necessary.
build set=enable_implicit_fk_locking_for_serializable=true
INSERT INTO child VALUES (100, 1), (200, 1)
----
insert child
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── column1:5 => c:1
 │    └── column2:6 => child.p:2
 ├── input binding: &1
 ├── values
 │    ├── columns: column1:5!null column2:6!null
 │    ├── (100, 1)
 │    └── (200, 1)
 └── f-k-checks
      └── f-k-checks-item: child(p) -> parent(p)
           └── anti-join (hash)
                ├── columns: p:7!null
                ├── with-scan &1
                │    ├── columns: p:7!null
                │    └── mapping:
                │         └──  column2:6 => p:7
                ├── scan parent
                │    ├── columns: parent.p:8!null
                │    ├── flags: avoid-full-scan disabled not visible index feature
                │    └── locking: for-share
                └── filters
                     └── p:7 = parent.p:8
