exec-ddl
CREATE TABLE xysd (x INT PRIMARY KEY, y INT, s STRING, d DECIMAL NOT NULL, UNIQUE (s DESC, d))
----
TABLE xysd
 ├── x int not null
 ├── y int
 ├── s string
 ├── d decimal not null
 ├── INDEX primary
 │    └── x int not null
 └── INDEX secondary
      ├── s string desc
      ├── d decimal not null
      └── x int not null (storing)

exec-ddl
CREATE TABLE uv (u INT, v INT NOT NULL)
----
TABLE uv
 ├── u int
 ├── v int not null
 ├── rowid int not null (hidden)
 └── INDEX primary
      └── rowid int not null (hidden)

exec-ddl
CREATE TABLE mn (m INT PRIMARY KEY, n INT, UNIQUE (n))
----
TABLE mn
 ├── m int not null
 ├── n int
 ├── INDEX primary
 │    └── m int not null
 └── INDEX secondary
      ├── n int
      └── m int not null (storing)

# Inner-join.
build
SELECT *, rowid FROM xysd INNER JOIN uv ON x=u
----
inner-join
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) u:5(int!null) v:6(int!null) rowid:7(int!null)
 ├── key: (7)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2), (7)-->(5,6), (1)==(5), (5)==(1)
 ├── prune: (2-4,6,7)
 ├── interesting orderings: (+1) (-3,+4,+1) (+7)
 ├── scan xysd
 │    ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 │    ├── prune: (1-4)
 │    └── interesting orderings: (+1) (-3,+4,+1)
 ├── scan uv
 │    ├── columns: u:5(int) v:6(int!null) rowid:7(int!null)
 │    ├── key: (7)
 │    ├── fd: (7)-->(5,6)
 │    ├── prune: (5-7)
 │    └── interesting orderings: (+7)
 └── filters [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
      └── eq [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ])]
           ├── variable: x [type=int, outer=(1)]
           └── variable: u [type=int, outer=(5)]

# Inner-join-apply.
opt
SELECT (SELECT (VALUES (x), (y))) FROM xysd
----
project
 ├── columns: column1:7(int)
 ├── prune: (7)
 ├── inner-join-apply
 │    ├── columns: x:1(int!null) y:2(int) column1:5(int)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,5)
 │    ├── interesting orderings: (+1)
 │    ├── scan xysd
 │    │    ├── columns: x:1(int!null) y:2(int)
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(2)
 │    │    ├── prune: (1,2)
 │    │    └── interesting orderings: (+1)
 │    ├── max1-row
 │    │    ├── columns: column1:5(int)
 │    │    ├── outer: (1,2)
 │    │    ├── cardinality: [1 - 1]
 │    │    ├── key: ()
 │    │    ├── fd: ()-->(5)
 │    │    └── values
 │    │         ├── columns: column1:5(int)
 │    │         ├── outer: (1,2)
 │    │         ├── cardinality: [2 - 2]
 │    │         ├── prune: (5)
 │    │         ├── tuple [type=tuple{int}, outer=(1)]
 │    │         │    └── variable: x [type=int, outer=(1)]
 │    │         └── tuple [type=tuple{int}, outer=(2)]
 │    │              └── variable: y [type=int, outer=(2)]
 │    └── true [type=bool]
 └── projections [outer=(5)]
      └── variable: column1 [type=int, outer=(5)]

# Inner-join-apply nested in inner-join-apply with outer column references to
# each parent.
opt
SELECT * FROM xysd WHERE (SELECT v FROM uv WHERE (SELECT n FROM mn WHERE n=v)=x)=x
----
project
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── prune: (1-4)
 ├── interesting orderings: (+1) (-3,+4,+1)
 └── inner-join-apply
      ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) v:6(int!null)
      ├── key: (1)
      ├── fd: (1)-->(2-4), (3,4)~~>(1,2), (1)==(6), (6)==(1)
      ├── prune: (2-4)
      ├── interesting orderings: (+1) (-3,+4,+1)
      ├── scan xysd
      │    ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
      │    ├── key: (1)
      │    ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
      │    ├── prune: (1-4)
      │    └── interesting orderings: (+1) (-3,+4,+1)
      ├── max1-row
      │    ├── columns: v:6(int!null)
      │    ├── outer: (1)
      │    ├── cardinality: [0 - 1]
      │    ├── key: ()
      │    ├── fd: ()-->(6)
      │    └── project
      │         ├── columns: v:6(int!null)
      │         ├── outer: (1)
      │         ├── fd: ()-->(6)
      │         ├── prune: (6)
      │         └── inner-join
      │              ├── columns: v:6(int!null) n:9(int!null)
      │              ├── outer: (1)
      │              ├── fd: ()-->(6,9)
      │              ├── interesting orderings: (+9)
      │              ├── scan uv
      │              │    ├── columns: v:6(int!null)
      │              │    └── prune: (6)
      │              ├── scan mn
      │              │    ├── columns: n:9(int)
      │              │    ├── lax-key: (9)
      │              │    ├── prune: (9)
      │              │    └── interesting orderings: (+9)
      │              └── filters [type=bool, outer=(1,6,9), constraints=(/1: (/NULL - ]; /6: (/NULL - ]; /9: (/NULL - ]), fd=(6)==(1,9), (9)==(1,6), (1)==(6,9)]
      │                   ├── eq [type=bool, outer=(6,9), constraints=(/6: (/NULL - ]; /9: (/NULL - ])]
      │                   │    ├── variable: n [type=int, outer=(9)]
      │                   │    └── variable: v [type=int, outer=(6)]
      │                   └── eq [type=bool, outer=(1,9), constraints=(/1: (/NULL - ]; /9: (/NULL - ])]
      │                        ├── variable: x [type=int, outer=(1)]
      │                        └── variable: n [type=int, outer=(9)]
      └── filters [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ]), fd=(1)==(6), (6)==(1)]
           └── eq [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ])]
                ├── variable: x [type=int, outer=(1)]
                └── variable: v [type=int, outer=(6)]

# Inner-join nested in inner-join-apply with outer column reference to top-level
# inner-join-apply.
opt
SELECT * FROM xysd WHERE (SELECT v FROM uv WHERE (SELECT m FROM mn WHERE m=y)=x)=x
----
project
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── prune: (1-4)
 ├── interesting orderings: (+1) (-3,+4,+1)
 └── inner-join-apply
      ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) v:6(int!null)
      ├── key: (1)
      ├── fd: (1)-->(2-4), (3,4)~~>(1,2), (1)==(6), (6)==(1)
      ├── prune: (3,4)
      ├── interesting orderings: (+1) (-3,+4,+1)
      ├── scan xysd
      │    ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
      │    ├── key: (1)
      │    ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
      │    ├── prune: (1-4)
      │    └── interesting orderings: (+1) (-3,+4,+1)
      ├── max1-row
      │    ├── columns: v:6(int!null)
      │    ├── outer: (1,2)
      │    ├── cardinality: [0 - 1]
      │    ├── key: ()
      │    ├── fd: ()-->(6)
      │    └── project
      │         ├── columns: v:6(int!null)
      │         ├── outer: (1,2)
      │         ├── prune: (6)
      │         └── inner-join
      │              ├── columns: v:6(int!null) m:8(int!null)
      │              ├── outer: (1,2)
      │              ├── fd: ()-->(8)
      │              ├── prune: (6)
      │              ├── interesting orderings: (+8)
      │              ├── scan uv
      │              │    ├── columns: v:6(int!null)
      │              │    └── prune: (6)
      │              ├── scan mn
      │              │    ├── columns: m:8(int!null)
      │              │    ├── key: (8)
      │              │    ├── prune: (8)
      │              │    └── interesting orderings: (+8)
      │              └── filters [type=bool, outer=(1,2,8), constraints=(/1: (/NULL - ]; /2: (/NULL - ]; /8: (/NULL - ]), fd=(2)==(1,8), (8)==(1,2), (1)==(2,8)]
      │                   ├── eq [type=bool, outer=(2,8), constraints=(/2: (/NULL - ]; /8: (/NULL - ])]
      │                   │    ├── variable: m [type=int, outer=(8)]
      │                   │    └── variable: y [type=int, outer=(2)]
      │                   └── eq [type=bool, outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ])]
      │                        ├── variable: x [type=int, outer=(1)]
      │                        └── variable: m [type=int, outer=(8)]
      └── filters [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ]), fd=(1)==(6), (6)==(1)]
           └── eq [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ])]
                ├── variable: x [type=int, outer=(1)]
                └── variable: v [type=int, outer=(6)]

# Left-join.
build
SELECT *, rowid FROM xysd LEFT JOIN uv ON x=u
----
left-join
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) u:5(int) v:6(int) rowid:7(int)
 ├── key: (1,7)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2), (7)-->(5,6)
 ├── prune: (2-4,6,7)
 ├── reject-nulls: (5-7)
 ├── interesting orderings: (+1) (-3,+4,+1) (+7)
 ├── scan xysd
 │    ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 │    ├── prune: (1-4)
 │    └── interesting orderings: (+1) (-3,+4,+1)
 ├── scan uv
 │    ├── columns: u:5(int) v:6(int!null) rowid:7(int!null)
 │    ├── key: (7)
 │    ├── fd: (7)-->(5,6)
 │    ├── prune: (5-7)
 │    └── interesting orderings: (+7)
 └── filters [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
      └── eq [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ])]
           ├── variable: x [type=int, outer=(1)]
           └── variable: u [type=int, outer=(5)]

# Left-join-apply.
opt
SELECT * FROM xysd WHERE (SELECT u FROM uv WHERE u=x) IS NULL
----
project
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── prune: (1-4)
 ├── interesting orderings: (+1) (-3,+4,+1)
 └── select
      ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) u:5(int)
      ├── key: (1)
      ├── fd: ()-->(5), (1)-->(2-4), (3,4)~~>(1,2)
      ├── prune: (2-4)
      ├── interesting orderings: (+1) (-3,+4,+1)
      ├── left-join-apply
      │    ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) u:5(int)
      │    ├── key: (1)
      │    ├── fd: (1)-->(2-5), (3,4)~~>(1,2)
      │    ├── prune: (2-4)
      │    ├── reject-nulls: (5)
      │    ├── interesting orderings: (+1) (-3,+4,+1)
      │    ├── scan xysd
      │    │    ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
      │    │    ├── key: (1)
      │    │    ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
      │    │    ├── prune: (1-4)
      │    │    └── interesting orderings: (+1) (-3,+4,+1)
      │    ├── max1-row
      │    │    ├── columns: u:5(int!null)
      │    │    ├── outer: (1)
      │    │    ├── cardinality: [0 - 1]
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(5)
      │    │    └── select
      │    │         ├── columns: u:5(int!null)
      │    │         ├── outer: (1)
      │    │         ├── fd: ()-->(5)
      │    │         ├── scan uv
      │    │         │    ├── columns: u:5(int)
      │    │         │    └── prune: (5)
      │    │         └── filters [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
      │    │              └── eq [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ])]
      │    │                   ├── variable: u [type=int, outer=(5)]
      │    │                   └── variable: x [type=int, outer=(1)]
      │    └── true [type=bool]
      └── filters [type=bool, outer=(5), constraints=(/5: [/NULL - /NULL]; tight), fd=()-->(5)]
           └── is [type=bool, outer=(5), constraints=(/5: [/NULL - /NULL]; tight)]
                ├── variable: u [type=int, outer=(5)]
                └── null [type=unknown]

# Right-join.
build
SELECT *, rowid FROM xysd RIGHT JOIN uv ON x=u
----
right-join
 ├── columns: x:1(int) y:2(int) s:3(string) d:4(decimal) u:5(int) v:6(int!null) rowid:7(int!null)
 ├── key: (1,7)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2), (7)-->(5,6)
 ├── prune: (2-4,6,7)
 ├── reject-nulls: (1-4)
 ├── interesting orderings: (+1) (-3,+4,+1) (+7)
 ├── scan xysd
 │    ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 │    ├── prune: (1-4)
 │    └── interesting orderings: (+1) (-3,+4,+1)
 ├── scan uv
 │    ├── columns: u:5(int) v:6(int!null) rowid:7(int!null)
 │    ├── key: (7)
 │    ├── fd: (7)-->(5,6)
 │    ├── prune: (5-7)
 │    └── interesting orderings: (+7)
 └── filters [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
      └── eq [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ])]
           ├── variable: x [type=int, outer=(1)]
           └── variable: u [type=int, outer=(5)]

# Right-join-apply.
opt
SELECT * FROM xysd RIGHT JOIN uv ON (SELECT u FROM uv WHERE u=x OFFSET 1) IS NULL
----
right-join
 ├── columns: x:1(int) y:2(int) s:3(string) d:4(decimal) u:5(int) v:6(int!null)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── prune: (1-6)
 ├── reject-nulls: (1-4)
 ├── interesting orderings: (+1) (-3,+4,+1)
 ├── project
 │    ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 │    ├── prune: (1-4)
 │    ├── interesting orderings: (+1) (-3,+4,+1)
 │    └── select
 │         ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) uv.u:8(int)
 │         ├── key: (1)
 │         ├── fd: ()-->(8), (1)-->(2-4), (3,4)~~>(1,2)
 │         ├── prune: (2-4)
 │         ├── interesting orderings: (+1) (-3,+4,+1)
 │         ├── left-join-apply
 │         │    ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) uv.u:8(int)
 │         │    ├── key: (1)
 │         │    ├── fd: (1)-->(2-4,8), (3,4)~~>(1,2)
 │         │    ├── prune: (2-4)
 │         │    ├── reject-nulls: (8)
 │         │    ├── interesting orderings: (+1) (-3,+4,+1)
 │         │    ├── scan xysd
 │         │    │    ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 │         │    │    ├── key: (1)
 │         │    │    ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 │         │    │    ├── prune: (1-4)
 │         │    │    └── interesting orderings: (+1) (-3,+4,+1)
 │         │    ├── max1-row
 │         │    │    ├── columns: uv.u:8(int!null)
 │         │    │    ├── outer: (1)
 │         │    │    ├── cardinality: [0 - 1]
 │         │    │    ├── key: ()
 │         │    │    ├── fd: ()-->(8)
 │         │    │    └── offset
 │         │    │         ├── columns: uv.u:8(int!null)
 │         │    │         ├── outer: (1)
 │         │    │         ├── fd: ()-->(8)
 │         │    │         ├── select
 │         │    │         │    ├── columns: uv.u:8(int!null)
 │         │    │         │    ├── outer: (1)
 │         │    │         │    ├── fd: ()-->(8)
 │         │    │         │    ├── scan uv
 │         │    │         │    │    ├── columns: uv.u:8(int)
 │         │    │         │    │    └── prune: (8)
 │         │    │         │    └── filters [type=bool, outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]
 │         │    │         │         └── eq [type=bool, outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ])]
 │         │    │         │              ├── variable: uv.u [type=int, outer=(8)]
 │         │    │         │              └── variable: x [type=int, outer=(1)]
 │         │    │         └── const: 1 [type=int]
 │         │    └── true [type=bool]
 │         └── filters [type=bool, outer=(8), constraints=(/8: [/NULL - /NULL]; tight), fd=()-->(8)]
 │              └── is [type=bool, outer=(8), constraints=(/8: [/NULL - /NULL]; tight)]
 │                   ├── variable: uv.u [type=int, outer=(8)]
 │                   └── null [type=unknown]
 ├── scan uv
 │    ├── columns: uv.u:5(int) uv.v:6(int!null)
 │    └── prune: (5,6)
 └── true [type=bool]

# Full-join.
build
SELECT *, rowid FROM xysd FULL JOIN uv ON x=u
----
full-join
 ├── columns: x:1(int) y:2(int) s:3(string) d:4(decimal) u:5(int) v:6(int) rowid:7(int)
 ├── key: (1,7)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2), (7)-->(5,6)
 ├── prune: (2-4,6,7)
 ├── reject-nulls: (1-7)
 ├── interesting orderings: (+1) (-3,+4,+1) (+7)
 ├── scan xysd
 │    ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 │    ├── prune: (1-4)
 │    └── interesting orderings: (+1) (-3,+4,+1)
 ├── scan uv
 │    ├── columns: u:5(int) v:6(int!null) rowid:7(int!null)
 │    ├── key: (7)
 │    ├── fd: (7)-->(5,6)
 │    ├── prune: (5-7)
 │    └── interesting orderings: (+7)
 └── filters [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
      └── eq [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ])]
           ├── variable: x [type=int, outer=(1)]
           └── variable: u [type=int, outer=(5)]

# Full-join-apply.
opt
SELECT * FROM xysd FULL JOIN uv ON (SELECT u FROM uv WHERE u=x OFFSET 1) IS NULL
----
project
 ├── columns: x:1(int) y:2(int) s:3(string) d:4(decimal) u:5(int) v:6(int)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── prune: (1-6)
 ├── interesting orderings: (+1) (-3,+4,+1)
 └── full-join-apply
      ├── columns: x:1(int) y:2(int) s:3(string) d:4(decimal) uv.u:5(int) uv.v:6(int) uv.u:8(int)
      ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
      ├── prune: (2-6)
      ├── reject-nulls: (1-6,8)
      ├── interesting orderings: (+1) (-3,+4,+1)
      ├── scan xysd
      │    ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
      │    ├── key: (1)
      │    ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
      │    ├── prune: (1-4)
      │    └── interesting orderings: (+1) (-3,+4,+1)
      ├── left-join
      │    ├── columns: uv.u:5(int) uv.v:6(int!null) uv.u:8(int)
      │    ├── outer: (1)
      │    ├── fd: ()~~>(8)
      │    ├── prune: (5,6)
      │    ├── reject-nulls: (8)
      │    ├── scan uv
      │    │    ├── columns: uv.u:5(int) uv.v:6(int!null)
      │    │    └── prune: (5,6)
      │    ├── max1-row
      │    │    ├── columns: uv.u:8(int!null)
      │    │    ├── outer: (1)
      │    │    ├── cardinality: [0 - 1]
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(8)
      │    │    └── offset
      │    │         ├── columns: uv.u:8(int!null)
      │    │         ├── outer: (1)
      │    │         ├── fd: ()-->(8)
      │    │         ├── select
      │    │         │    ├── columns: uv.u:8(int!null)
      │    │         │    ├── outer: (1)
      │    │         │    ├── fd: ()-->(8)
      │    │         │    ├── scan uv
      │    │         │    │    ├── columns: uv.u:8(int)
      │    │         │    │    └── prune: (8)
      │    │         │    └── filters [type=bool, outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]
      │    │         │         └── eq [type=bool, outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ])]
      │    │         │              ├── variable: uv.u [type=int, outer=(8)]
      │    │         │              └── variable: x [type=int, outer=(1)]
      │    │         └── const: 1 [type=int]
      │    └── true [type=bool]
      └── filters [type=bool, outer=(8), constraints=(/8: [/NULL - /NULL]; tight), fd=()-->(8)]
           └── is [type=bool, outer=(8), constraints=(/8: [/NULL - /NULL]; tight)]
                ├── variable: uv.u [type=int, outer=(8)]
                └── null [type=unknown]

# Semi-join.
opt
SELECT * FROM xysd WHERE EXISTS(SELECT * FROM uv WHERE x=u)
----
semi-join
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── prune: (2-4)
 ├── interesting orderings: (+1) (-3,+4,+1)
 ├── scan xysd
 │    ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 │    ├── prune: (1-4)
 │    └── interesting orderings: (+1) (-3,+4,+1)
 ├── scan uv
 │    ├── columns: u:5(int) v:6(int!null)
 │    └── prune: (5,6)
 └── filters [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
      └── eq [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ])]
           ├── variable: x [type=int, outer=(1)]
           └── variable: u [type=int, outer=(5)]

# Semi-join-apply.
opt
SELECT * FROM xysd WHERE EXISTS(SELECT * FROM uv WHERE v=x OFFSET 1)
----
semi-join-apply
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── prune: (2-4)
 ├── interesting orderings: (+1) (-3,+4,+1)
 ├── scan xysd
 │    ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 │    ├── prune: (1-4)
 │    └── interesting orderings: (+1) (-3,+4,+1)
 ├── offset
 │    ├── columns: u:5(int) v:6(int!null)
 │    ├── outer: (1)
 │    ├── fd: ()-->(6)
 │    ├── prune: (5)
 │    ├── select
 │    │    ├── columns: u:5(int) v:6(int!null)
 │    │    ├── outer: (1)
 │    │    ├── fd: ()-->(6)
 │    │    ├── prune: (5)
 │    │    ├── scan uv
 │    │    │    ├── columns: u:5(int) v:6(int!null)
 │    │    │    └── prune: (5,6)
 │    │    └── filters [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ]), fd=(1)==(6), (6)==(1)]
 │    │         └── eq [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ])]
 │    │              ├── variable: v [type=int, outer=(6)]
 │    │              └── variable: x [type=int, outer=(1)]
 │    └── const: 1 [type=int]
 └── true [type=bool]

# Semi-join nested in semi-join with outer column reference to top-level join.
opt
SELECT * FROM xysd WHERE EXISTS(SELECT * FROM uv WHERE EXISTS(SELECT * FROM mn WHERE x=m AND x=v))
----
semi-join-apply
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── prune: (2-4)
 ├── interesting orderings: (+1) (-3,+4,+1)
 ├── scan xysd
 │    ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 │    ├── prune: (1-4)
 │    └── interesting orderings: (+1) (-3,+4,+1)
 ├── semi-join
 │    ├── columns: u:5(int) v:6(int!null)
 │    ├── outer: (1)
 │    ├── fd: ()-->(6)
 │    ├── prune: (5)
 │    ├── scan uv
 │    │    ├── columns: u:5(int) v:6(int!null)
 │    │    └── prune: (5,6)
 │    ├── scan mn
 │    │    ├── columns: m:8(int!null) n:9(int)
 │    │    ├── key: (8)
 │    │    ├── fd: (8)-->(9), (9)~~>(8)
 │    │    ├── prune: (8,9)
 │    │    └── interesting orderings: (+8) (+9,+8)
 │    └── filters [type=bool, outer=(1,6,8), constraints=(/1: (/NULL - ]; /6: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(6,8), (8)==(1,6), (6)==(1,8)]
 │         ├── eq [type=bool, outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ])]
 │         │    ├── variable: x [type=int, outer=(1)]
 │         │    └── variable: m [type=int, outer=(8)]
 │         └── eq [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ])]
 │              ├── variable: x [type=int, outer=(1)]
 │              └── variable: v [type=int, outer=(6)]
 └── true [type=bool]

# Anti-join.
opt
SELECT * FROM xysd WHERE NOT EXISTS(SELECT * FROM uv WHERE x=u)
----
anti-join
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── prune: (2-4)
 ├── interesting orderings: (+1) (-3,+4,+1)
 ├── scan xysd
 │    ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 │    ├── prune: (1-4)
 │    └── interesting orderings: (+1) (-3,+4,+1)
 ├── scan uv
 │    ├── columns: u:5(int) v:6(int!null)
 │    └── prune: (5,6)
 └── filters [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
      └── eq [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ])]
           ├── variable: x [type=int, outer=(1)]
           └── variable: u [type=int, outer=(5)]

# Anti-join-apply.
opt
SELECT * FROM xysd WHERE NOT EXISTS(SELECT * FROM uv WHERE v=x OFFSET 1)
----
anti-join-apply
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── prune: (2-4)
 ├── interesting orderings: (+1) (-3,+4,+1)
 ├── scan xysd
 │    ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 │    ├── prune: (1-4)
 │    └── interesting orderings: (+1) (-3,+4,+1)
 ├── offset
 │    ├── columns: u:5(int) v:6(int!null)
 │    ├── outer: (1)
 │    ├── fd: ()-->(6)
 │    ├── prune: (5)
 │    ├── select
 │    │    ├── columns: u:5(int) v:6(int!null)
 │    │    ├── outer: (1)
 │    │    ├── fd: ()-->(6)
 │    │    ├── prune: (5)
 │    │    ├── scan uv
 │    │    │    ├── columns: u:5(int) v:6(int!null)
 │    │    │    └── prune: (5,6)
 │    │    └── filters [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ]), fd=(1)==(6), (6)==(1)]
 │    │         └── eq [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ])]
 │    │              ├── variable: v [type=int, outer=(6)]
 │    │              └── variable: x [type=int, outer=(1)]
 │    └── const: 1 [type=int]
 └── true [type=bool]

# Cross-join.
build
SELECT * FROM xysd, uv
----
project
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) u:5(int) v:6(int!null)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── prune: (1-6)
 ├── interesting orderings: (+1) (-3,+4,+1)
 └── inner-join
      ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) u:5(int) v:6(int!null) rowid:7(int!null)
      ├── key: (1,7)
      ├── fd: (1)-->(2-4), (3,4)~~>(1,2), (7)-->(5,6)
      ├── prune: (1-7)
      ├── interesting orderings: (+1) (-3,+4,+1) (+7)
      ├── scan xysd
      │    ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
      │    ├── key: (1)
      │    ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
      │    ├── prune: (1-4)
      │    └── interesting orderings: (+1) (-3,+4,+1)
      ├── scan uv
      │    ├── columns: u:5(int) v:6(int!null) rowid:7(int!null)
      │    ├── key: (7)
      │    ├── fd: (7)-->(5,6)
      │    ├── prune: (5-7)
      │    └── interesting orderings: (+7)
      └── true [type=bool]

# Self-join.
build
SELECT * FROM xysd, xysd AS xysd
----
inner-join
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) x:5(int!null) y:6(int) s:7(string) d:8(decimal!null)
 ├── key: (1,5)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2), (5)-->(6-8), (7,8)~~>(5,6)
 ├── prune: (1-8)
 ├── interesting orderings: (+1) (-3,+4,+1) (+5) (-7,+8,+5)
 ├── scan xysd
 │    ├── columns: xysd.x:1(int!null) xysd.y:2(int) xysd.s:3(string) xysd.d:4(decimal!null)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 │    ├── prune: (1-4)
 │    └── interesting orderings: (+1) (-3,+4,+1)
 ├── scan xysd
 │    ├── columns: xysd.x:5(int!null) xysd.y:6(int) xysd.s:7(string) xysd.d:8(decimal!null)
 │    ├── key: (5)
 │    ├── fd: (5)-->(6-8), (7,8)~~>(5,6)
 │    ├── prune: (5-8)
 │    └── interesting orderings: (+5) (-7,+8,+5)
 └── true [type=bool]

# Propagate outer columns.
build
SELECT * FROM xysd WHERE EXISTS(SELECT * FROM (SELECT x) INNER JOIN (SELECT y) ON x::string = s)
----
select
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── prune: (4)
 ├── interesting orderings: (+1) (-3,+4,+1)
 ├── scan xysd
 │    ├── columns: xysd.x:1(int!null) xysd.y:2(int) s:3(string) d:4(decimal!null)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 │    ├── prune: (1-4)
 │    └── interesting orderings: (+1) (-3,+4,+1)
 └── filters [type=bool, outer=(1-3)]
      └── exists [type=bool, outer=(1-3)]
           └── inner-join
                ├── columns: x:5(int) y:6(int)
                ├── outer: (1-3)
                ├── cardinality: [0 - 1]
                ├── key: ()
                ├── fd: ()-->(5,6)
                ├── prune: (6)
                ├── project
                │    ├── columns: x:5(int)
                │    ├── outer: (1)
                │    ├── cardinality: [1 - 1]
                │    ├── key: ()
                │    ├── fd: ()-->(5)
                │    ├── prune: (5)
                │    ├── values
                │    │    ├── cardinality: [1 - 1]
                │    │    ├── key: ()
                │    │    └── tuple [type=tuple]
                │    └── projections [outer=(1)]
                │         └── variable: xysd.x [type=int, outer=(1)]
                ├── project
                │    ├── columns: y:6(int)
                │    ├── outer: (2)
                │    ├── cardinality: [1 - 1]
                │    ├── key: ()
                │    ├── fd: ()-->(6)
                │    ├── prune: (6)
                │    ├── values
                │    │    ├── cardinality: [1 - 1]
                │    │    ├── key: ()
                │    │    └── tuple [type=tuple]
                │    └── projections [outer=(2)]
                │         └── variable: xysd.y [type=int, outer=(2)]
                └── filters [type=bool, outer=(3,5)]
                     └── eq [type=bool, outer=(3,5)]
                          ├── cast: STRING [type=string, outer=(5)]
                          │    └── variable: x [type=int, outer=(5)]
                          └── variable: s [type=string, outer=(3)]

# Calculate semi-join cardinality when left side has non-zero cardinality.
opt
SELECT * FROM (SELECT count(*) cnt FROM xysd) WHERE EXISTS(SELECT * FROM uv WHERE cnt=1)
----
semi-join
 ├── columns: cnt:5(int!null)
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(5)
 ├── select
 │    ├── columns: count_rows:5(int!null)
 │    ├── cardinality: [0 - 1]
 │    ├── key: ()
 │    ├── fd: ()-->(5)
 │    ├── scalar-group-by
 │    │    ├── columns: count_rows:5(int)
 │    │    ├── cardinality: [1 - 1]
 │    │    ├── key: ()
 │    │    ├── fd: ()-->(5)
 │    │    ├── prune: (5)
 │    │    ├── scan xysd@secondary
 │    │    └── aggregations
 │    │         └── count-rows [type=int]
 │    └── filters [type=bool, outer=(5), constraints=(/5: [/1 - /1]; tight), fd=()-->(5)]
 │         └── eq [type=bool, outer=(5), constraints=(/5: [/1 - /1]; tight)]
 │              ├── variable: count_rows [type=int, outer=(5)]
 │              └── const: 1 [type=int]
 ├── scan uv
 │    ├── columns: u:6(int) v:7(int!null)
 │    └── prune: (6,7)
 └── true [type=bool]

# Calculate semi-join-apply cardinality.
opt
SELECT * FROM (SELECT * FROM xysd LIMIT 10) WHERE EXISTS(SELECT * FROM uv WHERE x=u LIMIT 5)
----
semi-join-apply
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 ├── cardinality: [0 - 10]
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── prune: (2-4)
 ├── interesting orderings: (+1) (-3,+4,+1)
 ├── scan xysd
 │    ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 │    ├── limit: 10
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 │    ├── prune: (1-4)
 │    └── interesting orderings: (+1) (-3,+4,+1)
 ├── limit
 │    ├── columns: u:5(int!null) v:6(int!null)
 │    ├── outer: (1)
 │    ├── cardinality: [0 - 5]
 │    ├── fd: ()-->(5)
 │    ├── prune: (6)
 │    ├── select
 │    │    ├── columns: u:5(int!null) v:6(int!null)
 │    │    ├── outer: (1)
 │    │    ├── fd: ()-->(5)
 │    │    ├── prune: (6)
 │    │    ├── scan uv
 │    │    │    ├── columns: u:5(int) v:6(int!null)
 │    │    │    └── prune: (5,6)
 │    │    └── filters [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
 │    │         └── eq [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ])]
 │    │              ├── variable: x [type=int, outer=(1)]
 │    │              └── variable: u [type=int, outer=(5)]
 │    └── const: 5 [type=int]
 └── true [type=bool]

# Calculate anti-join cardinality when left side has non-zero cardinality.
opt
SELECT * FROM (SELECT * FROM (VALUES (1))) WHERE NOT EXISTS(SELECT * FROM uv WHERE u=column1)
----
anti-join
 ├── columns: column1:1(int)
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 ├── values
 │    ├── columns: column1:1(int)
 │    ├── cardinality: [1 - 1]
 │    ├── key: ()
 │    ├── fd: ()-->(1)
 │    ├── prune: (1)
 │    └── tuple [type=tuple{int}]
 │         └── const: 1 [type=int]
 ├── scan uv
 │    ├── columns: u:2(int) v:3(int!null)
 │    └── prune: (2,3)
 └── filters [type=bool, outer=(1,2), constraints=(/1: (/NULL - ]; /2: (/NULL - ]), fd=(1)==(2), (2)==(1)]
      └── eq [type=bool, outer=(1,2), constraints=(/1: (/NULL - ]; /2: (/NULL - ])]
           ├── variable: u [type=int, outer=(2)]
           └── variable: column1 [type=int, outer=(1)]

# Calculate anti-join-apply cardinality.
opt
SELECT * FROM (SELECT * FROM xysd LIMIT 10) WHERE NOT EXISTS(SELECT * FROM uv WHERE x=u LIMIT 5)
----
anti-join-apply
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 ├── cardinality: [0 - 10]
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── prune: (2-4)
 ├── interesting orderings: (+1) (-3,+4,+1)
 ├── scan xysd
 │    ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 │    ├── limit: 10
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 │    ├── prune: (1-4)
 │    └── interesting orderings: (+1) (-3,+4,+1)
 ├── limit
 │    ├── columns: u:5(int!null) v:6(int!null)
 │    ├── outer: (1)
 │    ├── cardinality: [0 - 5]
 │    ├── fd: ()-->(5)
 │    ├── prune: (6)
 │    ├── select
 │    │    ├── columns: u:5(int!null) v:6(int!null)
 │    │    ├── outer: (1)
 │    │    ├── fd: ()-->(5)
 │    │    ├── prune: (6)
 │    │    ├── scan uv
 │    │    │    ├── columns: u:5(int) v:6(int!null)
 │    │    │    └── prune: (5,6)
 │    │    └── filters [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
 │    │         └── eq [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ])]
 │    │              ├── variable: x [type=int, outer=(1)]
 │    │              └── variable: u [type=int, outer=(5)]
 │    └── const: 5 [type=int]
 └── true [type=bool]

# Calculate inner-join cardinality.
build
SELECT * FROM (VALUES (1), (2)) INNER JOIN (SELECT * FROM uv LIMIT 2) ON True
----
inner-join
 ├── columns: column1:1(int) u:2(int) v:3(int!null)
 ├── cardinality: [0 - 4]
 ├── prune: (1-3)
 ├── values
 │    ├── columns: column1:1(int)
 │    ├── cardinality: [2 - 2]
 │    ├── prune: (1)
 │    ├── tuple [type=tuple{int}]
 │    │    └── const: 1 [type=int]
 │    └── tuple [type=tuple{int}]
 │         └── const: 2 [type=int]
 ├── limit
 │    ├── columns: u:2(int) v:3(int!null)
 │    ├── cardinality: [0 - 2]
 │    ├── prune: (2,3)
 │    ├── project
 │    │    ├── columns: u:2(int) v:3(int!null)
 │    │    ├── prune: (2,3)
 │    │    └── scan uv
 │    │         ├── columns: u:2(int) v:3(int!null) rowid:4(int!null)
 │    │         ├── key: (4)
 │    │         ├── fd: (4)-->(2,3)
 │    │         ├── prune: (2-4)
 │    │         └── interesting orderings: (+4)
 │    └── const: 2 [type=int]
 └── filters [type=bool]
      └── true [type=bool]

# Calculate left-join cardinality.
build
SELECT * FROM (VALUES (1), (2), (3)) LEFT JOIN (SELECT * FROM uv LIMIT 2) ON True
----
left-join
 ├── columns: column1:1(int) u:2(int) v:3(int)
 ├── cardinality: [3 - 6]
 ├── prune: (1-3)
 ├── reject-nulls: (2,3)
 ├── values
 │    ├── columns: column1:1(int)
 │    ├── cardinality: [3 - 3]
 │    ├── prune: (1)
 │    ├── tuple [type=tuple{int}]
 │    │    └── const: 1 [type=int]
 │    ├── tuple [type=tuple{int}]
 │    │    └── const: 2 [type=int]
 │    └── tuple [type=tuple{int}]
 │         └── const: 3 [type=int]
 ├── limit
 │    ├── columns: u:2(int) v:3(int!null)
 │    ├── cardinality: [0 - 2]
 │    ├── prune: (2,3)
 │    ├── project
 │    │    ├── columns: u:2(int) v:3(int!null)
 │    │    ├── prune: (2,3)
 │    │    └── scan uv
 │    │         ├── columns: u:2(int) v:3(int!null) rowid:4(int!null)
 │    │         ├── key: (4)
 │    │         ├── fd: (4)-->(2,3)
 │    │         ├── prune: (2-4)
 │    │         └── interesting orderings: (+4)
 │    └── const: 2 [type=int]
 └── filters [type=bool]
      └── true [type=bool]

# Calculate right-join cardinality.
build
SELECT * FROM (SELECT * FROM uv LIMIT 2) RIGHT JOIN (VALUES (1), (2), (3)) ON True
----
right-join
 ├── columns: u:1(int) v:2(int) column1:4(int)
 ├── cardinality: [3 - 6]
 ├── prune: (1,2,4)
 ├── reject-nulls: (1,2)
 ├── limit
 │    ├── columns: u:1(int) v:2(int!null)
 │    ├── cardinality: [0 - 2]
 │    ├── prune: (1,2)
 │    ├── project
 │    │    ├── columns: u:1(int) v:2(int!null)
 │    │    ├── prune: (1,2)
 │    │    └── scan uv
 │    │         ├── columns: u:1(int) v:2(int!null) rowid:3(int!null)
 │    │         ├── key: (3)
 │    │         ├── fd: (3)-->(1,2)
 │    │         ├── prune: (1-3)
 │    │         └── interesting orderings: (+3)
 │    └── const: 2 [type=int]
 ├── values
 │    ├── columns: column1:4(int)
 │    ├── cardinality: [3 - 3]
 │    ├── prune: (4)
 │    ├── tuple [type=tuple{int}]
 │    │    └── const: 1 [type=int]
 │    ├── tuple [type=tuple{int}]
 │    │    └── const: 2 [type=int]
 │    └── tuple [type=tuple{int}]
 │         └── const: 3 [type=int]
 └── filters [type=bool]
      └── true [type=bool]

# Calculate full-join cardinality.
build
SELECT * FROM (VALUES (NULL), (NULL)) a FULL JOIN (VALUES (NULL), (NULL)) b ON True
----
full-join
 ├── columns: column1:1(unknown) column1:2(unknown)
 ├── cardinality: [2 - 4]
 ├── prune: (1,2)
 ├── reject-nulls: (1,2)
 ├── values
 │    ├── columns: column1:1(unknown)
 │    ├── cardinality: [2 - 2]
 │    ├── prune: (1)
 │    ├── tuple [type=tuple{unknown}]
 │    │    └── null [type=unknown]
 │    └── tuple [type=tuple{unknown}]
 │         └── null [type=unknown]
 ├── values
 │    ├── columns: column1:2(unknown)
 │    ├── cardinality: [2 - 2]
 │    ├── prune: (2)
 │    ├── tuple [type=tuple{unknown}]
 │    │    └── null [type=unknown]
 │    └── tuple [type=tuple{unknown}]
 │         └── null [type=unknown]
 └── filters [type=bool]
      └── true [type=bool]

# Calculate full-join cardinality with false filter.
build
SELECT * FROM (VALUES (NULL), (NULL)) a FULL JOIN (VALUES (NULL), (NULL)) b ON a.column1=b.column1
----
full-join
 ├── columns: column1:1(unknown) column1:2(unknown)
 ├── cardinality: [4 - 4]
 ├── prune: (1,2)
 ├── reject-nulls: (1,2)
 ├── values
 │    ├── columns: column1:1(unknown)
 │    ├── cardinality: [2 - 2]
 │    ├── prune: (1)
 │    ├── tuple [type=tuple{unknown}]
 │    │    └── null [type=unknown, constraints=(contradiction; tight)]
 │    └── tuple [type=tuple{unknown}]
 │         └── null [type=unknown, constraints=(contradiction; tight)]
 ├── values
 │    ├── columns: column1:2(unknown)
 │    ├── cardinality: [2 - 2]
 │    ├── prune: (2)
 │    ├── tuple [type=tuple{unknown}]
 │    │    └── null [type=unknown, constraints=(contradiction; tight)]
 │    └── tuple [type=tuple{unknown}]
 │         └── null [type=unknown, constraints=(contradiction; tight)]
 └── filters [type=bool, constraints=(contradiction; tight)]
      └── null [type=unknown, constraints=(contradiction; tight)]

# Calculate full-join cardinality of one input with unknown cardinality.
build
SELECT * FROM xysd FULL JOIN (SELECT * FROM (VALUES (1), (2))) ON True
----
full-join
 ├── columns: x:1(int) y:2(int) s:3(string) d:4(decimal) column1:5(int)
 ├── cardinality: [2 - ]
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── prune: (1-5)
 ├── reject-nulls: (1-5)
 ├── interesting orderings: (+1) (-3,+4,+1)
 ├── scan xysd
 │    ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 │    ├── prune: (1-4)
 │    └── interesting orderings: (+1) (-3,+4,+1)
 ├── values
 │    ├── columns: column1:5(int)
 │    ├── cardinality: [2 - 2]
 │    ├── prune: (5)
 │    ├── tuple [type=tuple{int}]
 │    │    └── const: 1 [type=int]
 │    └── tuple [type=tuple{int}]
 │         └── const: 2 [type=int]
 └── filters [type=bool]
      └── true [type=bool]

# Keys on both sides of full-join.
build
SELECT * FROM (SELECT * FROM xysd LIMIT 1) FULL JOIN (SELECT * FROM xysd LIMIT 1) ON True
----
full-join
 ├── columns: x:1(int) y:2(int) s:3(string) d:4(decimal) x:5(int) y:6(int) s:7(string) d:8(decimal)
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(1-8)
 ├── prune: (1-8)
 ├── reject-nulls: (1-8)
 ├── interesting orderings: (+1) (-3,+4,+1) (+5) (-7,+8,+5)
 ├── limit
 │    ├── columns: xysd.x:1(int!null) xysd.y:2(int) xysd.s:3(string) xysd.d:4(decimal!null)
 │    ├── cardinality: [0 - 1]
 │    ├── key: ()
 │    ├── fd: ()-->(1-4)
 │    ├── prune: (1-4)
 │    ├── interesting orderings: (+1) (-3,+4,+1)
 │    ├── scan xysd
 │    │    ├── columns: xysd.x:1(int!null) xysd.y:2(int) xysd.s:3(string) xysd.d:4(decimal!null)
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 │    │    ├── prune: (1-4)
 │    │    └── interesting orderings: (+1) (-3,+4,+1)
 │    └── const: 1 [type=int]
 ├── limit
 │    ├── columns: xysd.x:5(int!null) xysd.y:6(int) xysd.s:7(string) xysd.d:8(decimal!null)
 │    ├── cardinality: [0 - 1]
 │    ├── key: ()
 │    ├── fd: ()-->(5-8)
 │    ├── prune: (5-8)
 │    ├── interesting orderings: (+5) (-7,+8,+5)
 │    ├── scan xysd
 │    │    ├── columns: xysd.x:5(int!null) xysd.y:6(int) xysd.s:7(string) xysd.d:8(decimal!null)
 │    │    ├── key: (5)
 │    │    ├── fd: (5)-->(6-8), (7,8)~~>(5,6)
 │    │    ├── prune: (5-8)
 │    │    └── interesting orderings: (+5) (-7,+8,+5)
 │    └── const: 1 [type=int]
 └── filters [type=bool]
      └── true [type=bool]

# Nullable FD determinant on right side of left-join becomes lax.
build
SELECT * FROM xysd LEFT JOIN (SELECT u, sum(v) FROM uv GROUP BY u) ON u IS NOT NULL
----
left-join
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) u:5(int) sum:8(decimal)
 ├── key: (1,5)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2), (5)~~>(8), (1,5)-->(8)
 ├── prune: (1-4,8)
 ├── reject-nulls: (5,8)
 ├── interesting orderings: (+1) (-3,+4,+1)
 ├── scan xysd
 │    ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 │    ├── prune: (1-4)
 │    └── interesting orderings: (+1) (-3,+4,+1)
 ├── group-by
 │    ├── columns: u:5(int) sum:8(decimal)
 │    ├── grouping columns: u:5(int)
 │    ├── key: (5)
 │    ├── fd: (5)-->(8)
 │    ├── prune: (8)
 │    ├── project
 │    │    ├── columns: u:5(int) v:6(int!null)
 │    │    ├── prune: (5,6)
 │    │    └── scan uv
 │    │         ├── columns: u:5(int) v:6(int!null) rowid:7(int!null)
 │    │         ├── key: (7)
 │    │         ├── fd: (7)-->(5,6)
 │    │         ├── prune: (5-7)
 │    │         └── interesting orderings: (+7)
 │    └── aggregations [outer=(6)]
 │         └── sum [type=decimal, outer=(6)]
 │              └── variable: v [type=int, outer=(6)]
 └── filters [type=bool, outer=(5), constraints=(/5: (/NULL - ]; tight)]
      └── is-not [type=bool, outer=(5), constraints=(/5: (/NULL - ]; tight)]
           ├── variable: u [type=int, outer=(5)]
           └── null [type=unknown]

# Not-null FD determinant on right side of left-join stays strict.
build
SELECT * FROM xysd LEFT JOIN (SELECT u, sum(v) FROM uv WHERE u IS NOT NULL GROUP BY u) ON True
----
left-join
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) u:5(int) sum:8(decimal)
 ├── key: (1,5)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2), (5)-->(8)
 ├── prune: (1-4,8)
 ├── reject-nulls: (5,8)
 ├── interesting orderings: (+1) (-3,+4,+1)
 ├── scan xysd
 │    ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 │    ├── prune: (1-4)
 │    └── interesting orderings: (+1) (-3,+4,+1)
 ├── group-by
 │    ├── columns: u:5(int!null) sum:8(decimal)
 │    ├── grouping columns: u:5(int!null)
 │    ├── key: (5)
 │    ├── fd: (5)-->(8)
 │    ├── prune: (8)
 │    ├── project
 │    │    ├── columns: u:5(int!null) v:6(int!null)
 │    │    ├── prune: (5,6)
 │    │    └── select
 │    │         ├── columns: u:5(int!null) v:6(int!null) rowid:7(int!null)
 │    │         ├── key: (7)
 │    │         ├── fd: (7)-->(5,6)
 │    │         ├── prune: (6,7)
 │    │         ├── interesting orderings: (+7)
 │    │         ├── scan uv
 │    │         │    ├── columns: u:5(int) v:6(int!null) rowid:7(int!null)
 │    │         │    ├── key: (7)
 │    │         │    ├── fd: (7)-->(5,6)
 │    │         │    ├── prune: (5-7)
 │    │         │    └── interesting orderings: (+7)
 │    │         └── filters [type=bool, outer=(5), constraints=(/5: (/NULL - ]; tight)]
 │    │              └── is-not [type=bool, outer=(5), constraints=(/5: (/NULL - ]; tight)]
 │    │                   ├── variable: u [type=int, outer=(5)]
 │    │                   └── null [type=unknown]
 │    └── aggregations [outer=(6)]
 │         └── sum [type=decimal, outer=(6)]
 │              └── variable: v [type=int, outer=(6)]
 └── filters [type=bool]
      └── true [type=bool]

# Nullable FD determinant on left side of right-join becomes lax.
build
SELECT * FROM (SELECT u, sum(v) FROM uv GROUP BY u) RIGHT JOIN xysd ON u IS NOT NULL
----
right-join
 ├── columns: u:1(int) sum:4(decimal) x:5(int!null) y:6(int) s:7(string) d:8(decimal!null)
 ├── key: (1,5)
 ├── fd: (5)-->(6-8), (7,8)~~>(5,6), (1)~~>(4), (1,5)-->(4)
 ├── prune: (4-8)
 ├── reject-nulls: (1,4)
 ├── interesting orderings: (+5) (-7,+8,+5)
 ├── group-by
 │    ├── columns: u:1(int) sum:4(decimal)
 │    ├── grouping columns: u:1(int)
 │    ├── key: (1)
 │    ├── fd: (1)-->(4)
 │    ├── prune: (4)
 │    ├── project
 │    │    ├── columns: u:1(int) v:2(int!null)
 │    │    ├── prune: (1,2)
 │    │    └── scan uv
 │    │         ├── columns: u:1(int) v:2(int!null) rowid:3(int!null)
 │    │         ├── key: (3)
 │    │         ├── fd: (3)-->(1,2)
 │    │         ├── prune: (1-3)
 │    │         └── interesting orderings: (+3)
 │    └── aggregations [outer=(2)]
 │         └── sum [type=decimal, outer=(2)]
 │              └── variable: v [type=int, outer=(2)]
 ├── scan xysd
 │    ├── columns: x:5(int!null) y:6(int) s:7(string) d:8(decimal!null)
 │    ├── key: (5)
 │    ├── fd: (5)-->(6-8), (7,8)~~>(5,6)
 │    ├── prune: (5-8)
 │    └── interesting orderings: (+5) (-7,+8,+5)
 └── filters [type=bool, outer=(1), constraints=(/1: (/NULL - ]; tight)]
      └── is-not [type=bool, outer=(1), constraints=(/1: (/NULL - ]; tight)]
           ├── variable: u [type=int, outer=(1)]
           └── null [type=unknown]

# Not-null FD determinant on left side of right-join stays strict.
build
SELECT * FROM (SELECT u, sum(v) FROM uv WHERE u IS NOT NULL GROUP BY u) RIGHT JOIN xysd ON True
----
right-join
 ├── columns: u:1(int) sum:4(decimal) x:5(int!null) y:6(int) s:7(string) d:8(decimal!null)
 ├── key: (1,5)
 ├── fd: (1)-->(4), (5)-->(6-8), (7,8)~~>(5,6)
 ├── prune: (4-8)
 ├── reject-nulls: (1,4)
 ├── interesting orderings: (+5) (-7,+8,+5)
 ├── group-by
 │    ├── columns: u:1(int!null) sum:4(decimal)
 │    ├── grouping columns: u:1(int!null)
 │    ├── key: (1)
 │    ├── fd: (1)-->(4)
 │    ├── prune: (4)
 │    ├── project
 │    │    ├── columns: u:1(int!null) v:2(int!null)
 │    │    ├── prune: (1,2)
 │    │    └── select
 │    │         ├── columns: u:1(int!null) v:2(int!null) rowid:3(int!null)
 │    │         ├── key: (3)
 │    │         ├── fd: (3)-->(1,2)
 │    │         ├── prune: (2,3)
 │    │         ├── interesting orderings: (+3)
 │    │         ├── scan uv
 │    │         │    ├── columns: u:1(int) v:2(int!null) rowid:3(int!null)
 │    │         │    ├── key: (3)
 │    │         │    ├── fd: (3)-->(1,2)
 │    │         │    ├── prune: (1-3)
 │    │         │    └── interesting orderings: (+3)
 │    │         └── filters [type=bool, outer=(1), constraints=(/1: (/NULL - ]; tight)]
 │    │              └── is-not [type=bool, outer=(1), constraints=(/1: (/NULL - ]; tight)]
 │    │                   ├── variable: u [type=int, outer=(1)]
 │    │                   └── null [type=unknown]
 │    └── aggregations [outer=(2)]
 │         └── sum [type=decimal, outer=(2)]
 │              └── variable: v [type=int, outer=(2)]
 ├── scan xysd
 │    ├── columns: x:5(int!null) y:6(int) s:7(string) d:8(decimal!null)
 │    ├── key: (5)
 │    ├── fd: (5)-->(6-8), (7,8)~~>(5,6)
 │    ├── prune: (5-8)
 │    └── interesting orderings: (+5) (-7,+8,+5)
 └── filters [type=bool]
      └── true [type=bool]

# Nullable FD determinant on right side of full-join becomes lax.
build
SELECT * FROM xysd FULL JOIN (SELECT u, sum(v) FROM uv GROUP BY u) ON u IS NOT NULL
----
full-join
 ├── columns: x:1(int) y:2(int) s:3(string) d:4(decimal) u:5(int) sum:8(decimal)
 ├── key: (1,5)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2), (5)~~>(8), (1,5)-->(8)
 ├── prune: (1-4,8)
 ├── reject-nulls: (1-5,8)
 ├── interesting orderings: (+1) (-3,+4,+1)
 ├── scan xysd
 │    ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 │    ├── prune: (1-4)
 │    └── interesting orderings: (+1) (-3,+4,+1)
 ├── group-by
 │    ├── columns: u:5(int) sum:8(decimal)
 │    ├── grouping columns: u:5(int)
 │    ├── key: (5)
 │    ├── fd: (5)-->(8)
 │    ├── prune: (8)
 │    ├── project
 │    │    ├── columns: u:5(int) v:6(int!null)
 │    │    ├── prune: (5,6)
 │    │    └── scan uv
 │    │         ├── columns: u:5(int) v:6(int!null) rowid:7(int!null)
 │    │         ├── key: (7)
 │    │         ├── fd: (7)-->(5,6)
 │    │         ├── prune: (5-7)
 │    │         └── interesting orderings: (+7)
 │    └── aggregations [outer=(6)]
 │         └── sum [type=decimal, outer=(6)]
 │              └── variable: v [type=int, outer=(6)]
 └── filters [type=bool, outer=(5), constraints=(/5: (/NULL - ]; tight)]
      └── is-not [type=bool, outer=(5), constraints=(/5: (/NULL - ]; tight)]
           ├── variable: u [type=int, outer=(5)]
           └── null [type=unknown]

# Nullable FD determinant on left side of full-join becomes lax.
build
SELECT * FROM (SELECT u, sum(v) FROM uv GROUP BY u) FULL JOIN xysd ON u IS NOT NULL
----
full-join
 ├── columns: u:1(int) sum:4(decimal) x:5(int) y:6(int) s:7(string) d:8(decimal)
 ├── key: (1,5)
 ├── fd: (5)-->(6-8), (7,8)~~>(5,6), (1)~~>(4), (1,5)-->(4)
 ├── prune: (4-8)
 ├── reject-nulls: (1,4-8)
 ├── interesting orderings: (+5) (-7,+8,+5)
 ├── group-by
 │    ├── columns: u:1(int) sum:4(decimal)
 │    ├── grouping columns: u:1(int)
 │    ├── key: (1)
 │    ├── fd: (1)-->(4)
 │    ├── prune: (4)
 │    ├── project
 │    │    ├── columns: u:1(int) v:2(int!null)
 │    │    ├── prune: (1,2)
 │    │    └── scan uv
 │    │         ├── columns: u:1(int) v:2(int!null) rowid:3(int!null)
 │    │         ├── key: (3)
 │    │         ├── fd: (3)-->(1,2)
 │    │         ├── prune: (1-3)
 │    │         └── interesting orderings: (+3)
 │    └── aggregations [outer=(2)]
 │         └── sum [type=decimal, outer=(2)]
 │              └── variable: v [type=int, outer=(2)]
 ├── scan xysd
 │    ├── columns: x:5(int!null) y:6(int) s:7(string) d:8(decimal!null)
 │    ├── key: (5)
 │    ├── fd: (5)-->(6-8), (7,8)~~>(5,6)
 │    ├── prune: (5-8)
 │    └── interesting orderings: (+5) (-7,+8,+5)
 └── filters [type=bool, outer=(1), constraints=(/1: (/NULL - ]; tight)]
      └── is-not [type=bool, outer=(1), constraints=(/1: (/NULL - ]; tight)]
           ├── variable: u [type=int, outer=(1)]
           └── null [type=unknown]
