exec-ddl
CREATE TABLE a (k INT PRIMARY KEY, i INT, f FLOAT NOT NULL, s STRING, j JSON)
----
TABLE a
 ├── k int not null
 ├── i int
 ├── f float not null
 ├── s string
 ├── j jsonb
 └── INDEX primary
      └── k int not null

exec-ddl
CREATE TABLE t.b (x INT PRIMARY KEY, y INT)
----
TABLE b
 ├── x int not null
 ├── y int
 └── INDEX primary
      └── x int not null

exec-ddl
CREATE TABLE c (x INT PRIMARY KEY, y INT NOT NULL REFERENCES a(k), z INT NOT NULL, UNIQUE (x,z))
----
TABLE c
 ├── x int not null
 ├── y int not null
 ├── z int not null
 ├── INDEX primary
 │    └── x int not null
 ├── INDEX secondary
 │    ├── x int not null
 │    └── z int not null
 ├── INDEX c_auto_index_fk_y_ref_a
 │    ├── y int not null
 │    └── x int not null
 └── FOREIGN KEY (y) REFERENCES t.public.a (k)

exec-ddl
CREATE TABLE d (x INT PRIMARY KEY, y INT NOT NULL, z INT NOT NULL, FOREIGN KEY (y,z) REFERENCES c(x,z))
----
TABLE d
 ├── x int not null
 ├── y int not null
 ├── z int not null
 ├── INDEX primary
 │    └── x int not null
 ├── INDEX d_auto_index_fk_y_ref_c
 │    ├── y int not null
 │    ├── z int not null
 │    └── x int not null
 └── FOREIGN KEY (y, z) REFERENCES t.public.c (x, z)

exec-ddl
CREATE TABLE xy (x INT PRIMARY KEY, y INT)
----
TABLE xy
 ├── x int not null
 ├── y int
 └── INDEX primary
      └── x int not null

exec-ddl
CREATE TABLE uv (u INT PRIMARY KEY, v INT)
----
TABLE uv
 ├── u int not null
 ├── v int
 └── INDEX primary
      └── u int not null

opt
SELECT * FROM a INNER JOIN b ON a.s='foo' OR b.y<10
----
inner-join
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb) x:6(int!null) y:7(int)
 ├── key: (1,6)
 ├── fd: (1)-->(2-5), (6)-->(7)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── scan b
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── key: (6)
 │    └── fd: (6)-->(7)
 └── filters
      └── (s = 'foo') OR (y < 10) [type=bool, outer=(4,7)]

# --------------------------------------------------
# DetectJoinContradiction
# --------------------------------------------------

opt expect=DetectJoinContradiction
SELECT * FROM a INNER JOIN b ON k IN ()
----
values
 ├── columns: k:1(int) i:2(int) f:3(float) s:4(string) j:5(jsonb) x:6(int) y:7(int)
 ├── cardinality: [0 - 0]
 ├── key: ()
 └── fd: ()-->(1-7)

opt expect=DetectJoinContradiction
SELECT * FROM a LEFT JOIN b ON i=5 AND k IN () AND s='foo'
----
left-join
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb) x:6(int) y:7(int)
 ├── key: (1)
 ├── fd: (1)-->(2-7), ()~~>(6,7)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── values
 │    ├── columns: x:6(int) y:7(int)
 │    ├── cardinality: [0 - 0]
 │    ├── key: ()
 │    └── fd: ()-->(6,7)
 └── filters (true)

opt expect=DetectJoinContradiction
SELECT * FROM a FULL JOIN b ON i=5 AND k IN () AND s='foo'
----
full-join
 ├── columns: k:1(int) i:2(int) f:3(float) s:4(string) j:5(jsonb) x:6(int) y:7(int)
 ├── key: (1,6)
 ├── fd: (1)-->(2-5), (6)-->(7)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── scan b
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── key: (6)
 │    └── fd: (6)-->(7)
 └── filters
      └── false [type=bool]

# --------------------------------------------------
# PushFilterIntoJoinLeft
# --------------------------------------------------
opt expect=PushFilterIntoJoinLeft
SELECT * FROM a INNER JOIN b ON a.k=b.x AND a.s='foo'
----
inner-join (lookup b)
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string!null) j:5(jsonb) x:6(int!null) y:7(int)
 ├── key columns: [1] = [6]
 ├── key: (6)
 ├── fd: ()-->(4), (1)-->(2,3,5), (6)-->(7), (1)==(6), (6)==(1)
 ├── select
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string!null) j:5(jsonb)
 │    ├── key: (1)
 │    ├── fd: ()-->(4), (1)-->(2,3,5)
 │    ├── scan a
 │    │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2-5)
 │    └── filters
 │         └── s = 'foo' [type=bool, outer=(4), constraints=(/4: [/'foo' - /'foo']; tight), fd=()-->(4)]
 └── filters (true)

opt expect=PushFilterIntoJoinLeft
SELECT * FROM a RIGHT JOIN b ON (a.i<0 OR a.i>10) AND b.y=1 AND a.s='foo' AND a.k=b.x
----
right-join (merge)
 ├── columns: k:1(int) i:2(int) f:3(float) s:4(string) j:5(jsonb) x:6(int!null) y:7(int)
 ├── left ordering: +1
 ├── right ordering: +6
 ├── key: (1,6)
 ├── fd: (1)-->(2,3,5), (6)-->(7), ()~~>(4), (1,6)-->(4)
 ├── select
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string!null) j:5(jsonb)
 │    ├── key: (1)
 │    ├── fd: ()-->(4), (1)-->(2,3,5)
 │    ├── ordering: +1 opt(4) [actual: +1]
 │    ├── scan a
 │    │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(2-5)
 │    │    └── ordering: +1 opt(4) [actual: +1]
 │    └── filters
 │         ├── (i < 0) OR (i > 10) [type=bool, outer=(2)]
 │         └── s = 'foo' [type=bool, outer=(4), constraints=(/4: [/'foo' - /'foo']; tight), fd=()-->(4)]
 ├── scan b
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── key: (6)
 │    ├── fd: (6)-->(7)
 │    └── ordering: +6
 └── filters
      └── y = 1 [type=bool, outer=(7), constraints=(/7: [/1 - /1]; tight), fd=()-->(7)]

# LEFT JOIN should not push down conditions to left side of join.
opt expect-not=PushFilterIntoJoinLeft
SELECT * FROM a LEFT JOIN b ON a.k=b.x AND a.i=1
----
left-join (merge)
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb) x:6(int) y:7(int)
 ├── left ordering: +1
 ├── right ordering: +6
 ├── key: (1,6)
 ├── fd: (1)-->(2-5), (6)-->(7)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    └── ordering: +1
 ├── scan b
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── key: (6)
 │    ├── fd: (6)-->(7)
 │    └── ordering: +6
 └── filters
      └── i = 1 [type=bool, outer=(2), constraints=(/2: [/1 - /1]; tight), fd=()-->(2)]

# Semi-join case.
opt expect=PushFilterIntoJoinLeft
SELECT * FROM a WHERE EXISTS(SELECT * FROM b WHERE x=k AND s='foo')
----
semi-join (merge)
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string!null) j:5(jsonb)
 ├── left ordering: +1
 ├── right ordering: +6
 ├── key: (1)
 ├── fd: ()-->(4), (1)-->(2,3,5)
 ├── select
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string!null) j:5(jsonb)
 │    ├── key: (1)
 │    ├── fd: ()-->(4), (1)-->(2,3,5)
 │    ├── ordering: +1 opt(4) [actual: +1]
 │    ├── scan a
 │    │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(2-5)
 │    │    └── ordering: +1 opt(4) [actual: +1]
 │    └── filters
 │         └── s = 'foo' [type=bool, outer=(4), constraints=(/4: [/'foo' - /'foo']; tight), fd=()-->(4)]
 ├── scan b
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── key: (6)
 │    ├── fd: (6)-->(7)
 │    └── ordering: +6
 └── filters (true)

# Do not push anti-join conditions into left input.
opt expect-not=PushFilterIntoJoinLeft
SELECT * FROM a WHERE NOT EXISTS(SELECT * FROM b WHERE x=k AND s='foo')
----
anti-join (merge)
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 ├── left ordering: +1
 ├── right ordering: +6
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    └── ordering: +1
 ├── scan b
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── key: (6)
 │    ├── fd: (6)-->(7)
 │    └── ordering: +6
 └── filters
      └── s = 'foo' [type=bool, outer=(4), constraints=(/4: [/'foo' - /'foo']; tight), fd=()-->(4)]

# --------------------------------------------------
# PushFilterIntoJoinRight
# --------------------------------------------------
opt expect=PushFilterIntoJoinRight
SELECT * FROM b INNER JOIN a ON b.x=a.k AND a.s='foo'
----
inner-join (lookup b)
 ├── columns: x:1(int!null) y:2(int) k:3(int!null) i:4(int) f:5(float!null) s:6(string!null) j:7(jsonb)
 ├── key columns: [3] = [1]
 ├── key: (3)
 ├── fd: ()-->(6), (1)-->(2), (3)-->(4,5,7), (1)==(3), (3)==(1)
 ├── select
 │    ├── columns: k:3(int!null) i:4(int) f:5(float!null) s:6(string!null) j:7(jsonb)
 │    ├── key: (3)
 │    ├── fd: ()-->(6), (3)-->(4,5,7)
 │    ├── scan a
 │    │    ├── columns: k:3(int!null) i:4(int) f:5(float!null) s:6(string) j:7(jsonb)
 │    │    ├── key: (3)
 │    │    └── fd: (3)-->(4-7)
 │    └── filters
 │         └── s = 'foo' [type=bool, outer=(6), constraints=(/6: [/'foo' - /'foo']; tight), fd=()-->(6)]
 └── filters (true)

opt expect=PushFilterIntoJoinRight
SELECT * FROM b LEFT JOIN a ON (a.i<0 OR a.i>10) AND b.y=1 AND a.s='foo' AND b.x=a.k
----
left-join (merge)
 ├── columns: x:1(int!null) y:2(int) k:3(int) i:4(int) f:5(float) s:6(string) j:7(jsonb)
 ├── left ordering: +1
 ├── right ordering: +3
 ├── key: (1,3)
 ├── fd: (1)-->(2), (3)-->(4,5,7), ()~~>(6), (1,3)-->(6)
 ├── scan b
 │    ├── columns: x:1(int!null) y:2(int)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2)
 │    └── ordering: +1
 ├── select
 │    ├── columns: k:3(int!null) i:4(int) f:5(float!null) s:6(string!null) j:7(jsonb)
 │    ├── key: (3)
 │    ├── fd: ()-->(6), (3)-->(4,5,7)
 │    ├── ordering: +3 opt(6) [actual: +3]
 │    ├── scan a
 │    │    ├── columns: k:3(int!null) i:4(int) f:5(float!null) s:6(string) j:7(jsonb)
 │    │    ├── key: (3)
 │    │    ├── fd: (3)-->(4-7)
 │    │    └── ordering: +3 opt(6) [actual: +3]
 │    └── filters
 │         ├── (i < 0) OR (i > 10) [type=bool, outer=(4)]
 │         └── s = 'foo' [type=bool, outer=(6), constraints=(/6: [/'foo' - /'foo']; tight), fd=()-->(6)]
 └── filters
      └── y = 1 [type=bool, outer=(2), constraints=(/2: [/1 - /1]; tight), fd=()-->(2)]

# RIGHT JOIN should not push down conditions to right side of join.
opt expect-not=PushFilterIntoJoinRight
SELECT * FROM b RIGHT JOIN a ON b.x=a.k AND a.i=1
----
right-join (merge)
 ├── columns: x:1(int) y:2(int) k:3(int!null) i:4(int) f:5(float!null) s:6(string) j:7(jsonb)
 ├── left ordering: +1
 ├── right ordering: +3
 ├── key: (1,3)
 ├── fd: (1)-->(2), (3)-->(4-7)
 ├── scan b
 │    ├── columns: x:1(int!null) y:2(int)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2)
 │    └── ordering: +1
 ├── scan a
 │    ├── columns: k:3(int!null) i:4(int) f:5(float!null) s:6(string) j:7(jsonb)
 │    ├── key: (3)
 │    ├── fd: (3)-->(4-7)
 │    └── ordering: +3
 └── filters
      └── i = 1 [type=bool, outer=(4), constraints=(/4: [/1 - /1]; tight), fd=()-->(4)]

# Semi-join case.
opt expect=PushFilterIntoJoinRight
SELECT * FROM a WHERE EXISTS(SELECT * FROM b WHERE x=k AND y>10)
----
semi-join (merge)
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 ├── left ordering: +1
 ├── right ordering: +6
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    └── ordering: +1
 ├── select
 │    ├── columns: x:6(int!null) y:7(int!null)
 │    ├── key: (6)
 │    ├── fd: (6)-->(7)
 │    ├── ordering: +6
 │    ├── scan b
 │    │    ├── columns: x:6(int!null) y:7(int)
 │    │    ├── key: (6)
 │    │    ├── fd: (6)-->(7)
 │    │    └── ordering: +6
 │    └── filters
 │         └── y > 10 [type=bool, outer=(7), constraints=(/7: [/11 - ]; tight)]
 └── filters (true)

# Anti-join case.
opt expect=PushFilterIntoJoinRight
SELECT * FROM a WHERE NOT EXISTS(SELECT * FROM b WHERE x=k AND y>10)
----
anti-join (merge)
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 ├── left ordering: +1
 ├── right ordering: +6
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    └── ordering: +1
 ├── select
 │    ├── columns: x:6(int!null) y:7(int!null)
 │    ├── key: (6)
 │    ├── fd: (6)-->(7)
 │    ├── ordering: +6
 │    ├── scan b
 │    │    ├── columns: x:6(int!null) y:7(int)
 │    │    ├── key: (6)
 │    │    ├── fd: (6)-->(7)
 │    │    └── ordering: +6
 │    └── filters
 │         └── y > 10 [type=bool, outer=(7), constraints=(/7: [/11 - ]; tight)]
 └── filters (true)

# -------------------------------------------------------------------------------
# PushFilterIntoJoinLeftAndRight + MapFilterIntoJoinLeft + MapFilterIntoJoinRight
# -------------------------------------------------------------------------------

# Can push to both sides with inner join.
opt expect=(MapFilterIntoJoinLeft,MapFilterIntoJoinRight)
SELECT * FROM a INNER JOIN b ON a.k=b.x AND a.k + b.y > 5 AND b.x * a.i = 3
----
inner-join (merge)
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb) x:6(int!null) y:7(int)
 ├── left ordering: +1
 ├── right ordering: +6
 ├── key: (6)
 ├── fd: (1)-->(2-5), (6)-->(7), (1)==(6), (6)==(1)
 ├── select
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    ├── ordering: +1
 │    ├── scan a
 │    │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(2-5)
 │    │    └── ordering: +1
 │    └── filters
 │         └── (k * i) = 3 [type=bool, outer=(1,2)]
 ├── select
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── key: (6)
 │    ├── fd: (6)-->(7)
 │    ├── ordering: +6
 │    ├── scan b
 │    │    ├── columns: x:6(int!null) y:7(int)
 │    │    ├── key: (6)
 │    │    ├── fd: (6)-->(7)
 │    │    └── ordering: +6
 │    └── filters
 │         └── (x + y) > 5 [type=bool, outer=(6,7)]
 └── filters (true)

# Multiple equivalent columns.
# TODO(rytaft): We should also infer the equality predicates a.k=a.i and b.x=b.y.
opt expect=MapFilterIntoJoinLeft
SELECT * FROM a INNER JOIN b ON a.k=b.x AND a.i=b.x AND a.i=b.y AND a.f + b.y::FLOAT > 5 AND a.s || b.x::STRING = 'foo1'
----
inner-join (lookup b)
 ├── columns: k:1(int!null) i:2(int!null) f:3(float!null) s:4(string) j:5(jsonb) x:6(int!null) y:7(int!null)
 ├── key columns: [1] = [6]
 ├── key: (6)
 ├── fd: (1)-->(3-5), (1)==(2,6,7), (2)==(1,6,7), (6)==(1,2,7), (7)==(1,2,6)
 ├── select
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    ├── scan a
 │    │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2-5)
 │    └── filters
 │         ├── (f + i::FLOAT8) > 5.0 [type=bool, outer=(2,3)]
 │         └── (s || k::STRING) = 'foo1' [type=bool, outer=(1,4)]
 └── filters
      ├── i = x [type=bool, outer=(2,6), constraints=(/2: (/NULL - ]; /6: (/NULL - ]), fd=(2)==(6), (6)==(2)]
      └── i = y [type=bool, outer=(2,7), constraints=(/2: (/NULL - ]; /7: (/NULL - ]), fd=(2)==(7), (7)==(2)]

# Can push to both sides with semi-join.
opt expect=(MapFilterIntoJoinLeft,MapFilterIntoJoinRight)
SELECT * FROM a WHERE EXISTS(
  SELECT * FROM b WHERE a.k=b.x AND a.k + b.y > 5 AND b.x * a.i = 3
)
----
semi-join (merge)
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 ├── left ordering: +1
 ├── right ordering: +6
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── select
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    ├── ordering: +1
 │    ├── scan a
 │    │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(2-5)
 │    │    └── ordering: +1
 │    └── filters
 │         └── (k * i) = 3 [type=bool, outer=(1,2)]
 ├── select
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── key: (6)
 │    ├── fd: (6)-->(7)
 │    ├── ordering: +6
 │    ├── scan b
 │    │    ├── columns: x:6(int!null) y:7(int)
 │    │    ├── key: (6)
 │    │    ├── fd: (6)-->(7)
 │    │    └── ordering: +6
 │    └── filters
 │         └── (x + y) > 5 [type=bool, outer=(6,7)]
 └── filters (true)

opt expect=PushFilterIntoJoinLeftAndRight
SELECT * FROM a WHERE EXISTS(
  SELECT * FROM b WHERE a.k=b.x AND a.k > 5 AND b.x IN (3, 7, 10)
)
----
semi-join (merge)
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 ├── left ordering: +1
 ├── right ordering: +6
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── constraint: /1: [/7 - /7] [/10 - /10]
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    └── ordering: +1
 ├── scan b
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── constraint: /6: [/7 - /7] [/10 - /10]
 │    ├── key: (6)
 │    ├── fd: (6)-->(7)
 │    └── ordering: +6
 └── filters (true)

# Can only push to right side with left join.
opt expect=MapFilterIntoJoinRight expect-not=PushFilterIntoJoinLeftAndRight
SELECT * FROM a LEFT JOIN b ON a.k=b.x AND a.k + b.y > 5 AND b.x * a.i = 3
----
left-join (merge)
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb) x:6(int) y:7(int)
 ├── left ordering: +1
 ├── right ordering: +6
 ├── key: (1,6)
 ├── fd: (1)-->(2-5), (6)-->(7)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    └── ordering: +1
 ├── select
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── key: (6)
 │    ├── fd: (6)-->(7)
 │    ├── ordering: +6
 │    ├── scan b
 │    │    ├── columns: x:6(int!null) y:7(int)
 │    │    ├── key: (6)
 │    │    ├── fd: (6)-->(7)
 │    │    └── ordering: +6
 │    └── filters
 │         └── (x + y) > 5 [type=bool, outer=(6,7)]
 └── filters
      └── (x * i) = 3 [type=bool, outer=(2,6)]

opt expect=MapFilterIntoJoinRight expect-not=PushFilterIntoJoinLeftAndRight
SELECT * FROM a LEFT JOIN b ON a.k=b.x AND a.k > 5 AND b.x IN (3, 7, 10)
----
left-join (merge)
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb) x:6(int) y:7(int)
 ├── left ordering: +1
 ├── right ordering: +6
 ├── key: (1,6)
 ├── fd: (1)-->(2-5), (6)-->(7)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    └── ordering: +1
 ├── scan b
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── constraint: /6: [/7 - /7] [/10 - /10]
 │    ├── key: (6)
 │    ├── fd: (6)-->(7)
 │    └── ordering: +6
 └── filters (true)

# Can only push to left side with right join.
opt expect=MapFilterIntoJoinLeft expect-not=PushFilterIntoJoinLeftAndRight
SELECT * FROM a RIGHT JOIN b ON a.k=b.x AND a.k + b.y > 5 AND b.x * a.i = 3
----
right-join (merge)
 ├── columns: k:1(int) i:2(int) f:3(float) s:4(string) j:5(jsonb) x:6(int!null) y:7(int)
 ├── left ordering: +1
 ├── right ordering: +6
 ├── key: (1,6)
 ├── fd: (1)-->(2-5), (6)-->(7)
 ├── select
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    ├── ordering: +1
 │    ├── scan a
 │    │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(2-5)
 │    │    └── ordering: +1
 │    └── filters
 │         └── (k * i) = 3 [type=bool, outer=(1,2)]
 ├── scan b
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── key: (6)
 │    ├── fd: (6)-->(7)
 │    └── ordering: +6
 └── filters
      └── (k + y) > 5 [type=bool, outer=(1,7)]

opt expect=MapFilterIntoJoinLeft expect-not=PushFilterIntoJoinLeftAndRight
SELECT * FROM a RIGHT JOIN b ON a.k=b.x AND a.k > 5 AND b.x IN (3, 7, 10)
----
right-join (merge)
 ├── columns: k:1(int) i:2(int) f:3(float) s:4(string) j:5(jsonb) x:6(int!null) y:7(int)
 ├── left ordering: +1
 ├── right ordering: +6
 ├── key: (1,6)
 ├── fd: (1)-->(2-5), (6)-->(7)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── constraint: /1: [/7 - /7] [/10 - /10]
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    └── ordering: +1
 ├── scan b
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── key: (6)
 │    ├── fd: (6)-->(7)
 │    └── ordering: +6
 └── filters (true)

# Cannot push with full join.
opt expect-not=(PushFilterIntoJoinLeftAndRight,MapFilterIntoJoinLeft,MapFilterIntoJoinRight)
SELECT * FROM a FULL JOIN b ON a.k=b.x AND a.k + b.y > 5 AND b.x * a.i = 3
----
full-join (merge)
 ├── columns: k:1(int) i:2(int) f:3(float) s:4(string) j:5(jsonb) x:6(int) y:7(int)
 ├── left ordering: +1
 ├── right ordering: +6
 ├── key: (1,6)
 ├── fd: (1)-->(2-5), (6)-->(7)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    └── ordering: +1
 ├── scan b
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── key: (6)
 │    ├── fd: (6)-->(7)
 │    └── ordering: +6
 └── filters
      ├── (k + y) > 5 [type=bool, outer=(1,7)]
      └── (x * i) = 3 [type=bool, outer=(2,6)]

opt expect-not=(PushFilterIntoJoinLeftAndRight,MapFilterIntoJoinLeft,MapFilterIntoJoinRight)
SELECT * FROM a FULL JOIN b ON a.k=b.x AND a.k > 5 AND b.x IN (3, 7, 10)
----
full-join (merge)
 ├── columns: k:1(int) i:2(int) f:3(float) s:4(string) j:5(jsonb) x:6(int) y:7(int)
 ├── left ordering: +1
 ├── right ordering: +6
 ├── key: (1,6)
 ├── fd: (1)-->(2-5), (6)-->(7)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    └── ordering: +1
 ├── scan b
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── key: (6)
 │    ├── fd: (6)-->(7)
 │    └── ordering: +6
 └── filters
      ├── k > 5 [type=bool, outer=(1), constraints=(/1: [/6 - ]; tight)]
      └── x IN (3, 7, 10) [type=bool, outer=(6), constraints=(/6: [/3 - /3] [/7 - /7] [/10 - /10]; tight)]

# Can only push to right side with anti-join.
opt expect=MapFilterIntoJoinRight expect-not=PushFilterIntoJoinLeftAndRight
SELECT * FROM a WHERE NOT EXISTS(
  SELECT * FROM b WHERE a.k=b.x AND a.k + b.y > 5 AND b.x * a.i = 3
)
----
anti-join (merge)
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 ├── left ordering: +1
 ├── right ordering: +6
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    └── ordering: +1
 ├── select
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── key: (6)
 │    ├── fd: (6)-->(7)
 │    ├── ordering: +6
 │    ├── scan b
 │    │    ├── columns: x:6(int!null) y:7(int)
 │    │    ├── key: (6)
 │    │    ├── fd: (6)-->(7)
 │    │    └── ordering: +6
 │    └── filters
 │         └── (x + y) > 5 [type=bool, outer=(6,7)]
 └── filters
      └── (x * i) = 3 [type=bool, outer=(2,6)]

opt expect=MapFilterIntoJoinRight expect-not=PushFilterIntoJoinLeftAndRight
SELECT * FROM a WHERE NOT EXISTS(
  SELECT * FROM b WHERE a.k=b.x AND a.k > 5 AND b.x IN (3, 7, 10)
)
----
anti-join (merge)
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 ├── left ordering: +1
 ├── right ordering: +6
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    └── ordering: +1
 ├── scan b
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── constraint: /6: [/7 - /7] [/10 - /10]
 │    ├── key: (6)
 │    ├── fd: (6)-->(7)
 │    └── ordering: +6
 └── filters (true)

# Works with a non-correlated subquery.
opt expect=MapFilterIntoJoinLeft
SELECT * FROM a JOIN b ON a.k = b.x AND b.x * a.i = (SELECT min(b.x) FROM b)
----
inner-join (merge)
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb) x:6(int!null) y:7(int)
 ├── left ordering: +1
 ├── right ordering: +6
 ├── key: (6)
 ├── fd: (1)-->(2-5), (6)-->(7), (1)==(6), (6)==(1)
 ├── select
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    ├── ordering: +1
 │    ├── scan a
 │    │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(2-5)
 │    │    └── ordering: +1
 │    └── filters
 │         └── eq [type=bool, outer=(1,2), subquery]
 │              ├── k * i [type=int]
 │              └── subquery [type=int]
 │                   └── scalar-group-by
 │                        ├── columns: min:10(int)
 │                        ├── cardinality: [1 - 1]
 │                        ├── key: ()
 │                        ├── fd: ()-->(10)
 │                        ├── scan b
 │                        │    ├── columns: x:8(int!null)
 │                        │    ├── limit: 1
 │                        │    ├── key: ()
 │                        │    └── fd: ()-->(8)
 │                        └── aggregations
 │                             └── const-agg [type=int, outer=(8)]
 │                                  └── variable: x [type=int]
 ├── scan b
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── key: (6)
 │    ├── fd: (6)-->(7)
 │    └── ordering: +6
 └── filters (true)

# Optimization does not apply with correlated suqueries.
opt expect-not=(PushFilterIntoJoinLeftAndRight,MapFilterIntoJoinLeft,MapFilterIntoJoinRight)
SELECT * FROM a JOIN b ON a.k = b.x AND b.x * a.i = (SELECT a.k * b.y FROM b)
----
project
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb) x:6(int!null) y:7(int)
 ├── key: (6)
 ├── fd: (1)-->(2-5), (1,6)-->(7), (1)==(6), (6)==(1)
 └── inner-join-apply
      ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb) x:6(int!null) y:7(int) "?column?":10(int)
      ├── key: (6)
      ├── fd: (1)-->(2-5), (1,6)-->(7,10), (1)==(6), (6)==(1)
      ├── scan a
      │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
      │    ├── key: (1)
      │    └── fd: (1)-->(2-5)
      ├── left-join
      │    ├── columns: x:6(int!null) y:7(int) "?column?":10(int)
      │    ├── outer: (1)
      │    ├── key: (6)
      │    ├── fd: (6)-->(7,10), ()~~>(10)
      │    ├── scan b
      │    │    ├── columns: x:6(int!null) y:7(int)
      │    │    ├── key: (6)
      │    │    └── fd: (6)-->(7)
      │    ├── max1-row
      │    │    ├── columns: "?column?":10(int)
      │    │    ├── outer: (1)
      │    │    ├── cardinality: [0 - 1]
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(10)
      │    │    └── project
      │    │         ├── columns: "?column?":10(int)
      │    │         ├── outer: (1)
      │    │         ├── scan b
      │    │         │    └── columns: y:9(int)
      │    │         └── projections
      │    │              └── k * y [type=int, outer=(1,9)]
      │    └── filters (true)
      └── filters
           ├── k = x [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ]), fd=(1)==(6), (6)==(1)]
           └── ?column? = (x * i) [type=bool, outer=(2,6,10), constraints=(/10: (/NULL - ])]

# Ensure that we do not map filters for types with composite key encoding.
opt expect-not=(PushFilterIntoJoinLeftAndRight,MapFilterIntoJoinLeft,MapFilterIntoJoinRight)
SELECT *
FROM (VALUES (1.0), (2.0)) AS t1(x), (VALUES (1.00), (2.00)) AS t2(y)WHERE x=y AND x::text = '1.0'
----
inner-join
 ├── columns: x:1(decimal!null) y:2(decimal!null)
 ├── cardinality: [0 - 4]
 ├── fd: (1)==(2), (2)==(1)
 ├── values
 │    ├── columns: column1:2(decimal)
 │    ├── cardinality: [2 - 2]
 │    ├── (1.00,) [type=tuple{decimal}]
 │    └── (2.00,) [type=tuple{decimal}]
 ├── select
 │    ├── columns: column1:1(decimal)
 │    ├── cardinality: [0 - 2]
 │    ├── values
 │    │    ├── columns: column1:1(decimal)
 │    │    ├── cardinality: [2 - 2]
 │    │    ├── (1.0,) [type=tuple{decimal}]
 │    │    └── (2.0,) [type=tuple{decimal}]
 │    └── filters
 │         └── column1::STRING = '1.0' [type=bool, outer=(1)]
 └── filters
      └── column1 = column1 [type=bool, outer=(1,2), constraints=(/1: (/NULL - ]; /2: (/NULL - ]), fd=(1)==(2), (2)==(1)]

# Optimization does not apply if equality is only on one side.
opt expect-not=(PushFilterIntoJoinLeftAndRight,MapFilterIntoJoinLeft,MapFilterIntoJoinRight)
SELECT * FROM a INNER JOIN b ON b.y=b.x AND a.k=a.i AND a.k + b.y > 5 AND b.x * a.i = 3
----
inner-join
 ├── columns: k:1(int!null) i:2(int!null) f:3(float!null) s:4(string) j:5(jsonb) x:6(int!null) y:7(int!null)
 ├── key: (1,6)
 ├── fd: (1)-->(3-5), (1)==(2), (2)==(1), (6)==(7), (7)==(6)
 ├── select
 │    ├── columns: k:1(int!null) i:2(int!null) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    ├── fd: (1)-->(3-5), (1)==(2), (2)==(1)
 │    ├── scan a
 │    │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2-5)
 │    └── filters
 │         └── k = i [type=bool, outer=(1,2), constraints=(/1: (/NULL - ]; /2: (/NULL - ]), fd=(1)==(2), (2)==(1)]
 ├── select
 │    ├── columns: x:6(int!null) y:7(int!null)
 │    ├── key: (6)
 │    ├── fd: (6)==(7), (7)==(6)
 │    ├── scan b
 │    │    ├── columns: x:6(int!null) y:7(int)
 │    │    ├── key: (6)
 │    │    └── fd: (6)-->(7)
 │    └── filters
 │         └── y = x [type=bool, outer=(6,7), constraints=(/6: (/NULL - ]; /7: (/NULL - ]), fd=(6)==(7), (7)==(6)]
 └── filters
      ├── (k + y) > 5 [type=bool, outer=(1,7)]
      └── (x * i) = 3 [type=bool, outer=(2,6)]

# Ensure that MapFilterIntoJoinLeft doesn't cause cycle with decorrelation.
opt
SELECT
(
    SELECT b.x
    FROM (SELECT b.* FROM b FULL OUTER JOIN b AS b2 ON c.x=5) AS b, a
    WHERE a.k=b.x AND a.k+b.x < 5
)
FROM c
----
project
 ├── columns: x:13(int)
 ├── left-join-apply
 │    ├── columns: c.x:1(int!null) b.x:4(int)
 │    ├── key: (1)
 │    ├── fd: (1)-->(4)
 │    ├── scan c@secondary
 │    │    ├── columns: c.x:1(int!null)
 │    │    └── key: (1)
 │    ├── max1-row
 │    │    ├── columns: b.x:4(int!null)
 │    │    ├── outer: (1)
 │    │    ├── cardinality: [0 - 1]
 │    │    ├── key: ()
 │    │    ├── fd: ()-->(4)
 │    │    └── project
 │    │         ├── columns: b.x:4(int!null)
 │    │         ├── outer: (1)
 │    │         └── inner-join
 │    │              ├── columns: b.x:4(int!null) k:8(int!null)
 │    │              ├── outer: (1)
 │    │              ├── fd: (4)==(8), (8)==(4)
 │    │              ├── full-join
 │    │              │    ├── columns: b.x:4(int)
 │    │              │    ├── outer: (1)
 │    │              │    ├── scan b
 │    │              │    │    ├── columns: b.x:4(int!null)
 │    │              │    │    └── key: (4)
 │    │              │    ├── scan b2
 │    │              │    └── filters
 │    │              │         └── c.x = 5 [type=bool, outer=(1), constraints=(/1: [/5 - /5]; tight), fd=()-->(1)]
 │    │              ├── select
 │    │              │    ├── columns: k:8(int!null)
 │    │              │    ├── key: (8)
 │    │              │    ├── scan a
 │    │              │    │    ├── columns: k:8(int!null)
 │    │              │    │    └── key: (8)
 │    │              │    └── filters
 │    │              │         └── (k + k) < 5 [type=bool, outer=(8)]
 │    │              └── filters
 │    │                   └── k = b.x [type=bool, outer=(4,8), constraints=(/4: (/NULL - ]; /8: (/NULL - ]), fd=(4)==(8), (8)==(4)]
 │    └── filters (true)
 └── projections
      └── variable: b.x [type=int, outer=(4)]

# Ensure that MapFilterIntoJoinRight doesn't cause cycle with decorrelation.
opt
SELECT
(
    SELECT b.x FROM a, (SELECT b.* FROM b FULL OUTER JOIN b AS b2 ON c.x=5) AS b
    WHERE a.k=b.x AND a.k+b.x < 5
)
FROM c
----
project
 ├── columns: x:13(int)
 ├── left-join-apply
 │    ├── columns: c.x:1(int!null) b.x:9(int)
 │    ├── key: (1)
 │    ├── fd: (1)-->(9)
 │    ├── scan c@secondary
 │    │    ├── columns: c.x:1(int!null)
 │    │    └── key: (1)
 │    ├── max1-row
 │    │    ├── columns: b.x:9(int!null)
 │    │    ├── outer: (1)
 │    │    ├── cardinality: [0 - 1]
 │    │    ├── key: ()
 │    │    ├── fd: ()-->(9)
 │    │    └── project
 │    │         ├── columns: b.x:9(int!null)
 │    │         ├── outer: (1)
 │    │         └── inner-join
 │    │              ├── columns: k:4(int!null) b.x:9(int!null)
 │    │              ├── outer: (1)
 │    │              ├── fd: (4)==(9), (9)==(4)
 │    │              ├── full-join
 │    │              │    ├── columns: b.x:9(int)
 │    │              │    ├── outer: (1)
 │    │              │    ├── scan b
 │    │              │    │    ├── columns: b.x:9(int!null)
 │    │              │    │    └── key: (9)
 │    │              │    ├── scan b2
 │    │              │    └── filters
 │    │              │         └── c.x = 5 [type=bool, outer=(1), constraints=(/1: [/5 - /5]; tight), fd=()-->(1)]
 │    │              ├── select
 │    │              │    ├── columns: k:4(int!null)
 │    │              │    ├── key: (4)
 │    │              │    ├── scan a
 │    │              │    │    ├── columns: k:4(int!null)
 │    │              │    │    └── key: (4)
 │    │              │    └── filters
 │    │              │         └── (k + k) < 5 [type=bool, outer=(4)]
 │    │              └── filters
 │    │                   └── k = b.x [type=bool, outer=(4,9), constraints=(/4: (/NULL - ]; /9: (/NULL - ]), fd=(4)==(9), (9)==(4)]
 │    └── filters (true)
 └── projections
      └── variable: b.x [type=int, outer=(9)]

exec-ddl
CREATE TABLE t1 (a DATE)
----
TABLE t1
 ├── a date
 ├── rowid int not null (hidden)
 └── INDEX primary
      └── rowid int not null (hidden)

exec-ddl
CREATE TABLE t2 (b TIMESTAMPTZ)
----
TABLE t2
 ├── b timestamptz
 ├── rowid int not null (hidden)
 └── INDEX primary
      └── rowid int not null (hidden)

# Make sure that we do not create invalid filters due to substituting columns
# with different types.
opt
SELECT * FROM t1, t2 WHERE a = b AND age(b, TIMESTAMPTZ '2017-01-01') > INTERVAL '1 day'
----
inner-join
 ├── columns: a:1(date!null) b:3(timestamptz!null)
 ├── fd: (1)==(3), (3)==(1)
 ├── scan t1
 │    └── columns: a:1(date)
 ├── select
 │    ├── columns: b:3(timestamptz)
 │    ├── scan t2
 │    │    └── columns: b:3(timestamptz)
 │    └── filters
 │         └── age(b, '2017-01-01 00:00:00+00:00') > '1 day' [type=bool, outer=(3)]
 └── filters
      └── a = b [type=bool, outer=(1,3), constraints=(/1: (/NULL - ]; /3: (/NULL - ]), fd=(1)==(3), (3)==(1)]

# Regression for issue 28818. Try to trigger undetectable cycle between the
# PushFilterIntoJoinLeftAndRight and TryDecorrelateSelect rules.
opt
SELECT 1
FROM a
WHERE EXISTS (
    SELECT 1
    FROM xy
    INNER JOIN uv
    ON EXISTS (
        SELECT 1
        FROM b
        WHERE a.s >= 'foo'
        LIMIT 10
    )
    WHERE
        (SELECT s FROM a) = 'foo'
)
----
project
 ├── columns: "?column?":22(int!null)
 ├── fd: ()-->(22)
 ├── distinct-on
 │    ├── columns: k:1(int!null)
 │    ├── grouping columns: k:1(int!null)
 │    ├── key: (1)
 │    └── select
 │         ├── columns: k:1(int!null) xy.x:6(int!null) u:8(int!null) true_agg:14(bool!null)
 │         ├── key: (1,6,8)
 │         ├── fd: (1,6,8)-->(14)
 │         ├── group-by
 │         │    ├── columns: k:1(int!null) xy.x:6(int!null) u:8(int!null) true_agg:14(bool)
 │         │    ├── grouping columns: k:1(int!null) xy.x:6(int!null) u:8(int!null)
 │         │    ├── key: (1,6,8)
 │         │    ├── fd: (1,6,8)-->(14)
 │         │    ├── project
 │         │    │    ├── columns: true:13(bool!null) k:1(int!null) xy.x:6(int!null) u:8(int!null)
 │         │    │    ├── fd: ()-->(13)
 │         │    │    ├── inner-join-apply
 │         │    │    │    ├── columns: k:1(int!null) s:4(string) xy.x:6(int!null) u:8(int!null)
 │         │    │    │    ├── fd: (1)-->(4)
 │         │    │    │    ├── scan a
 │         │    │    │    │    ├── columns: k:1(int!null) s:4(string)
 │         │    │    │    │    ├── key: (1)
 │         │    │    │    │    └── fd: (1)-->(4)
 │         │    │    │    ├── inner-join
 │         │    │    │    │    ├── columns: xy.x:6(int!null) u:8(int!null)
 │         │    │    │    │    ├── outer: (4)
 │         │    │    │    │    ├── inner-join
 │         │    │    │    │    │    ├── columns: u:8(int!null)
 │         │    │    │    │    │    ├── outer: (4)
 │         │    │    │    │    │    ├── select
 │         │    │    │    │    │    │    ├── columns: u:8(int!null)
 │         │    │    │    │    │    │    ├── key: (8)
 │         │    │    │    │    │    │    ├── scan uv
 │         │    │    │    │    │    │    │    ├── columns: u:8(int!null)
 │         │    │    │    │    │    │    │    └── key: (8)
 │         │    │    │    │    │    │    └── filters
 │         │    │    │    │    │    │         └── eq [type=bool, subquery]
 │         │    │    │    │    │    │              ├── subquery [type=string]
 │         │    │    │    │    │    │              │    └── max1-row
 │         │    │    │    │    │    │              │         ├── columns: s:19(string)
 │         │    │    │    │    │    │              │         ├── cardinality: [0 - 1]
 │         │    │    │    │    │    │              │         ├── key: ()
 │         │    │    │    │    │    │              │         ├── fd: ()-->(19)
 │         │    │    │    │    │    │              │         └── scan a
 │         │    │    │    │    │    │              │              └── columns: s:19(string)
 │         │    │    │    │    │    │              └── const: 'foo' [type=string]
 │         │    │    │    │    │    ├── limit
 │         │    │    │    │    │    │    ├── outer: (4)
 │         │    │    │    │    │    │    ├── cardinality: [0 - 10]
 │         │    │    │    │    │    │    ├── select
 │         │    │    │    │    │    │    │    ├── outer: (4)
 │         │    │    │    │    │    │    │    ├── scan b
 │         │    │    │    │    │    │    │    └── filters
 │         │    │    │    │    │    │    │         └── s >= 'foo' [type=bool, outer=(4), constraints=(/4: [/'foo' - ]; tight)]
 │         │    │    │    │    │    │    └── const: 10 [type=int]
 │         │    │    │    │    │    └── filters (true)
 │         │    │    │    │    ├── select
 │         │    │    │    │    │    ├── columns: xy.x:6(int!null)
 │         │    │    │    │    │    ├── key: (6)
 │         │    │    │    │    │    ├── scan xy
 │         │    │    │    │    │    │    ├── columns: xy.x:6(int!null)
 │         │    │    │    │    │    │    └── key: (6)
 │         │    │    │    │    │    └── filters
 │         │    │    │    │    │         └── eq [type=bool, subquery]
 │         │    │    │    │    │              ├── subquery [type=string]
 │         │    │    │    │    │              │    └── max1-row
 │         │    │    │    │    │              │         ├── columns: s:19(string)
 │         │    │    │    │    │              │         ├── cardinality: [0 - 1]
 │         │    │    │    │    │              │         ├── key: ()
 │         │    │    │    │    │              │         ├── fd: ()-->(19)
 │         │    │    │    │    │              │         └── scan a
 │         │    │    │    │    │              │              └── columns: s:19(string)
 │         │    │    │    │    │              └── const: 'foo' [type=string]
 │         │    │    │    │    └── filters (true)
 │         │    │    │    └── filters (true)
 │         │    │    └── projections
 │         │    │         └── true [type=bool]
 │         │    └── aggregations
 │         │         └── const-not-null-agg [type=bool, outer=(13)]
 │         │              └── variable: true [type=bool]
 │         └── filters
 │              └── true_agg IS NOT NULL [type=bool, outer=(14), constraints=(/14: (/NULL - ]; tight)]
 └── projections
      └── const: 1 [type=int]

# Regression for issue 36137. Try to trigger undetectable cycle between the
# PushFilterIntoJoinLeftAndRight and TryDecorrelateSelect rules.
opt
SELECT * FROM a JOIN b ON a.k = b.x
WHERE (a.k = b.x) OR (a.k IN (SELECT 5 FROM b WHERE x = y));
----
inner-join (merge)
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb) x:6(int!null) y:7(int)
 ├── left ordering: +1
 ├── right ordering: +6
 ├── key: (6)
 ├── fd: (1)-->(2-5), (6)-->(7), (1)==(6), (6)==(1)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    └── ordering: +1
 ├── scan b
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── key: (6)
 │    ├── fd: (6)-->(7)
 │    └── ordering: +6
 └── filters
      └── or [type=bool, outer=(1,6), correlated-subquery]
           ├── k = x [type=bool]
           └── any: eq [type=bool]
                ├── project
                │    ├── columns: "?column?":10(int!null)
                │    ├── fd: ()-->(10)
                │    ├── select
                │    │    ├── columns: x:8(int!null) y:9(int!null)
                │    │    ├── key: (8)
                │    │    ├── fd: (8)==(9), (9)==(8)
                │    │    ├── scan b
                │    │    │    ├── columns: x:8(int!null) y:9(int)
                │    │    │    ├── key: (8)
                │    │    │    └── fd: (8)-->(9)
                │    │    └── filters
                │    │         └── x = y [type=bool, outer=(8,9), constraints=(/8: (/NULL - ]; /9: (/NULL - ]), fd=(8)==(9), (9)==(8)]
                │    └── projections
                │         └── const: 5 [type=int]
                └── variable: k [type=int]

# --------------------------------------------------
# PushFilterIntoJoinLeft + PushFilterIntoJoinRight
# --------------------------------------------------

opt expect=(PushFilterIntoJoinLeft,PushFilterIntoJoinRight)
SELECT * FROM a INNER JOIN b ON a.k=b.x AND a.i=1 AND b.y=1
----
inner-join (lookup a)
 ├── columns: k:1(int!null) i:2(int!null) f:3(float!null) s:4(string) j:5(jsonb) x:6(int!null) y:7(int!null)
 ├── key columns: [6] = [1]
 ├── key: (6)
 ├── fd: ()-->(2,7), (1)-->(3-5), (1)==(6), (6)==(1)
 ├── select
 │    ├── columns: x:6(int!null) y:7(int!null)
 │    ├── key: (6)
 │    ├── fd: ()-->(7)
 │    ├── scan b
 │    │    ├── columns: x:6(int!null) y:7(int)
 │    │    ├── key: (6)
 │    │    └── fd: (6)-->(7)
 │    └── filters
 │         └── y = 1 [type=bool, outer=(7), constraints=(/7: [/1 - /1]; tight), fd=()-->(7)]
 └── filters
      └── i = 1 [type=bool, outer=(2), constraints=(/2: [/1 - /1]; tight), fd=()-->(2)]

# FULL JOIN should not push down conditions to either side of join.
opt expect-not=(PushFilterIntoJoinLeft,PushFilterIntoJoinRight)
SELECT * FROM a FULL JOIN b ON a.k=b.x AND a.i=1 AND b.y=1
----
full-join (merge)
 ├── columns: k:1(int) i:2(int) f:3(float) s:4(string) j:5(jsonb) x:6(int) y:7(int)
 ├── left ordering: +1
 ├── right ordering: +6
 ├── key: (1,6)
 ├── fd: (1)-->(2-5), (6)-->(7)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    └── ordering: +1
 ├── scan b
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── key: (6)
 │    ├── fd: (6)-->(7)
 │    └── ordering: +6
 └── filters
      ├── i = 1 [type=bool, outer=(2), constraints=(/2: [/1 - /1]; tight), fd=()-->(2)]
      └── y = 1 [type=bool, outer=(7), constraints=(/7: [/1 - /1]; tight), fd=()-->(7)]

# Nested semi/anti-join case.
opt expect=PushFilterIntoJoinRight
SELECT * FROM b
WHERE EXISTS
(
    SELECT * FROM a WHERE k=x AND s='foo' AND NOT EXISTS(SELECT * FROM a WHERE i=10 AND y>100)
)
----
semi-join-apply
 ├── columns: x:1(int!null) y:2(int)
 ├── key: (1)
 ├── fd: (1)-->(2)
 ├── scan b
 │    ├── columns: x:1(int!null) y:2(int)
 │    ├── key: (1)
 │    └── fd: (1)-->(2)
 ├── anti-join
 │    ├── columns: k:3(int!null) i:4(int) f:5(float!null) s:6(string!null) j:7(jsonb)
 │    ├── outer: (2)
 │    ├── key: (3)
 │    ├── fd: ()-->(6), (3)-->(4,5,7)
 │    ├── select
 │    │    ├── columns: k:3(int!null) i:4(int) f:5(float!null) s:6(string!null) j:7(jsonb)
 │    │    ├── key: (3)
 │    │    ├── fd: ()-->(6), (3)-->(4,5,7)
 │    │    ├── scan a
 │    │    │    ├── columns: k:3(int!null) i:4(int) f:5(float!null) s:6(string) j:7(jsonb)
 │    │    │    ├── key: (3)
 │    │    │    └── fd: (3)-->(4-7)
 │    │    └── filters
 │    │         └── s = 'foo' [type=bool, outer=(6), constraints=(/6: [/'foo' - /'foo']; tight), fd=()-->(6)]
 │    ├── select
 │    │    ├── columns: k:8(int!null) i:9(int!null) f:10(float!null) s:11(string) j:12(jsonb)
 │    │    ├── key: (8)
 │    │    ├── fd: ()-->(9), (8)-->(10-12)
 │    │    ├── scan a
 │    │    │    ├── columns: k:8(int!null) i:9(int) f:10(float!null) s:11(string) j:12(jsonb)
 │    │    │    ├── key: (8)
 │    │    │    └── fd: (8)-->(9-12)
 │    │    └── filters
 │    │         └── i = 10 [type=bool, outer=(9), constraints=(/9: [/10 - /10]; tight), fd=()-->(9)]
 │    └── filters
 │         └── y > 100 [type=bool, outer=(2), constraints=(/2: [/101 - ]; tight)]
 └── filters
      └── k = x [type=bool, outer=(1,3), constraints=(/1: (/NULL - ]; /3: (/NULL - ]), fd=(1)==(3), (3)==(1)]

# --------------------------------------------------
# SimplifyLeftJoinWithoutFilters
# --------------------------------------------------
opt expect=SimplifyLeftJoinWithoutFilters
SELECT * FROM a LEFT JOIN (SELECT count(*) FROM b) ON True
----
inner-join
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb) count:8(int)
 ├── key: (1)
 ├── fd: ()-->(8), (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── scalar-group-by
 │    ├── columns: count_rows:8(int)
 │    ├── cardinality: [1 - 1]
 │    ├── key: ()
 │    ├── fd: ()-->(8)
 │    ├── scan b
 │    └── aggregations
 │         └── count-rows [type=int]
 └── filters (true)

# Full-join.
opt expect=SimplifyLeftJoinWithoutFilters
SELECT * FROM a FULL JOIN (SELECT count(*) FROM b) ON True
----
right-join
 ├── columns: k:1(int) i:2(int) f:3(float) s:4(string) j:5(jsonb) count:8(int)
 ├── cardinality: [1 - ]
 ├── key: (1)
 ├── fd: ()-->(8), (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── scalar-group-by
 │    ├── columns: count_rows:8(int)
 │    ├── cardinality: [1 - 1]
 │    ├── key: ()
 │    ├── fd: ()-->(8)
 │    ├── scan b
 │    └── aggregations
 │         └── count-rows [type=int]
 └── filters (true)

# Left-join-apply.
opt expect=SimplifyLeftJoinWithoutFilters
SELECT * FROM a WHERE (SELECT sum(column1) FROM (VALUES (k), (1))) = 1
----
project
 ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb)
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 └── select
      ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) sum:7(decimal!null)
      ├── key: (1)
      ├── fd: ()-->(7), (1)-->(2-5)
      ├── group-by
      │    ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) sum:7(decimal)
      │    ├── grouping columns: k:1(int!null)
      │    ├── key: (1)
      │    ├── fd: (1)-->(2-5,7)
      │    ├── inner-join-apply
      │    │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb) column1:6(int)
      │    │    ├── fd: (1)-->(2-5)
      │    │    ├── scan a
      │    │    │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
      │    │    │    ├── key: (1)
      │    │    │    └── fd: (1)-->(2-5)
      │    │    ├── values
      │    │    │    ├── columns: column1:6(int)
      │    │    │    ├── outer: (1)
      │    │    │    ├── cardinality: [2 - 2]
      │    │    │    ├── (k,) [type=tuple{int}]
      │    │    │    └── (1,) [type=tuple{int}]
      │    │    └── filters (true)
      │    └── aggregations
      │         ├── sum [type=decimal, outer=(6)]
      │         │    └── variable: column1 [type=int]
      │         ├── const-agg [type=int, outer=(2)]
      │         │    └── variable: i [type=int]
      │         ├── const-agg [type=float, outer=(3)]
      │         │    └── variable: f [type=float]
      │         ├── const-agg [type=string, outer=(4)]
      │         │    └── variable: s [type=string]
      │         └── const-agg [type=jsonb, outer=(5)]
      │              └── variable: j [type=jsonb]
      └── filters
           └── sum = 1 [type=bool, outer=(7), constraints=(/7: [/1 - /1]; tight), fd=()-->(7)]

# Don't simplify right join
opt expect-not=SimplifyLeftJoinWithoutFilters
SELECT * FROM a RIGHT JOIN (SELECT count(*) FROM b) ON True
----
right-join
 ├── columns: k:1(int) i:2(int) f:3(float) s:4(string) j:5(jsonb) count:8(int)
 ├── cardinality: [1 - ]
 ├── key: (1)
 ├── fd: ()-->(8), (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── scalar-group-by
 │    ├── columns: count_rows:8(int)
 │    ├── cardinality: [1 - 1]
 │    ├── key: ()
 │    ├── fd: ()-->(8)
 │    ├── scan b
 │    └── aggregations
 │         └── count-rows [type=int]
 └── filters (true)

# --------------------------------------------------
# SimplifyRightJoinWithoutFilters
# --------------------------------------------------
opt expect=SimplifyRightJoinWithoutFilters
SELECT * FROM (SELECT count(*) FROM b) RIGHT JOIN a ON True
----
inner-join
 ├── columns: count:3(int) k:4(int!null) i:5(int) f:6(float!null) s:7(string) j:8(jsonb)
 ├── key: (4)
 ├── fd: ()-->(3), (4)-->(5-8)
 ├── scan a
 │    ├── columns: k:4(int!null) i:5(int) f:6(float!null) s:7(string) j:8(jsonb)
 │    ├── key: (4)
 │    └── fd: (4)-->(5-8)
 ├── scalar-group-by
 │    ├── columns: count_rows:3(int)
 │    ├── cardinality: [1 - 1]
 │    ├── key: ()
 │    ├── fd: ()-->(3)
 │    ├── scan b
 │    └── aggregations
 │         └── count-rows [type=int]
 └── filters (true)

# Full-join.
opt expect=SimplifyRightJoinWithoutFilters
SELECT * FROM (SELECT count(*) FROM b) FULL JOIN a ON True
----
right-join
 ├── columns: count:3(int) k:4(int) i:5(int) f:6(float) s:7(string) j:8(jsonb)
 ├── cardinality: [1 - ]
 ├── key: (4)
 ├── fd: ()-->(3), (4)-->(5-8)
 ├── scan a
 │    ├── columns: k:4(int!null) i:5(int) f:6(float!null) s:7(string) j:8(jsonb)
 │    ├── key: (4)
 │    └── fd: (4)-->(5-8)
 ├── scalar-group-by
 │    ├── columns: count_rows:3(int)
 │    ├── cardinality: [1 - 1]
 │    ├── key: ()
 │    ├── fd: ()-->(3)
 │    ├── scan b
 │    └── aggregations
 │         └── count-rows [type=int]
 └── filters (true)

# Don't simplify left join
opt expect-not=SimplifyRightJoinWithoutFilters
SELECT * FROM (SELECT count(*) FROM b) LEFT JOIN a ON True
----
right-join
 ├── columns: count:3(int) k:4(int) i:5(int) f:6(float) s:7(string) j:8(jsonb)
 ├── cardinality: [1 - ]
 ├── key: (4)
 ├── fd: ()-->(3), (4)-->(5-8)
 ├── scan a
 │    ├── columns: k:4(int!null) i:5(int) f:6(float!null) s:7(string) j:8(jsonb)
 │    ├── key: (4)
 │    └── fd: (4)-->(5-8)
 ├── scalar-group-by
 │    ├── columns: count_rows:3(int)
 │    ├── cardinality: [1 - 1]
 │    ├── key: ()
 │    ├── fd: ()-->(3)
 │    ├── scan b
 │    └── aggregations
 │         └── count-rows [type=int]
 └── filters (true)

# --------------------------------------------------
# SimplifyLeftJoinWithFilters + SimplifyRightJoinWithFilters
# --------------------------------------------------
opt expect=(SimplifyLeftJoinWithFilters,SimplifyRightJoinWithFilters)
SELECT * FROM a FULL JOIN a AS a2 ON a.k=a2.k
----
inner-join (merge)
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb) k:6(int!null) i:7(int) f:8(float!null) s:9(string) j:10(jsonb)
 ├── left ordering: +1
 ├── right ordering: +6
 ├── key: (6)
 ├── fd: (1)-->(2-5), (6)-->(7-10), (1)==(6), (6)==(1)
 ├── scan a
 │    ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float!null) a.s:4(string) a.j:5(jsonb)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    └── ordering: +1
 ├── scan a2
 │    ├── columns: a2.k:6(int!null) a2.i:7(int) a2.f:8(float!null) a2.s:9(string) a2.j:10(jsonb)
 │    ├── key: (6)
 │    ├── fd: (6)-->(7-10)
 │    └── ordering: +6
 └── filters (true)

# Right side has partial rows, so only right-join can be simplified.
opt expect=SimplifyRightJoinWithFilters
SELECT * FROM a FULL JOIN (SELECT * FROM a WHERE k>0) AS a2 ON a.k=a2.k
----
left-join (merge)
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb) k:6(int) i:7(int) f:8(float) s:9(string) j:10(jsonb)
 ├── left ordering: +1
 ├── right ordering: +6
 ├── key: (1,6)
 ├── fd: (1)-->(2-5), (6)-->(7-10)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    └── ordering: +1
 ├── scan a
 │    ├── columns: k:6(int!null) i:7(int) f:8(float!null) s:9(string) j:10(jsonb)
 │    ├── constraint: /6: [/1 - ]
 │    ├── key: (6)
 │    ├── fd: (6)-->(7-10)
 │    └── ordering: +6
 └── filters (true)

# Multiple equality conditions, with duplicates and reversed columns.
opt expect=(SimplifyRightJoinWithFilters,SimplifyLeftJoinWithFilters)
SELECT * FROM a FULL JOIN a AS a2 ON a.k=a2.k AND a.k=a2.k AND a2.f=a.f
----
inner-join
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb) k:6(int!null) i:7(int) f:8(float!null) s:9(string) j:10(jsonb)
 ├── key: (6)
 ├── fd: (1)-->(2-5), (6)-->(7-10), (1)==(6), (6)==(1), (3)==(8), (8)==(3)
 ├── scan a
 │    ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float!null) a.s:4(string) a.j:5(jsonb)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── scan a2
 │    ├── columns: a2.k:6(int!null) a2.i:7(int) a2.f:8(float!null) a2.s:9(string) a2.j:10(jsonb)
 │    ├── key: (6)
 │    └── fd: (6)-->(7-10)
 └── filters
      ├── a.k = a2.k [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ]), fd=(1)==(6), (6)==(1)]
      ├── a.k = a2.k [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ]), fd=(1)==(6), (6)==(1)]
      └── a2.f = a.f [type=bool, outer=(3,8), constraints=(/3: (/NULL - ]; /8: (/NULL - ]), fd=(3)==(8), (8)==(3)]

# Input contains Project operator.
opt expect=(SimplifyRightJoinWithFilters,SimplifyLeftJoinWithFilters)
SELECT * FROM (SELECT length(s), f FROM a) AS a FULL JOIN a AS a2 ON a.f=a2.f
----
inner-join
 ├── columns: length:6(int) f:3(float!null) k:7(int!null) i:8(int) f:9(float!null) s:10(string) j:11(jsonb)
 ├── fd: (7)-->(8-11), (3)==(9), (9)==(3)
 ├── project
 │    ├── columns: length:6(int) a.f:3(float!null)
 │    ├── scan a
 │    │    └── columns: a.f:3(float!null) a.s:4(string)
 │    └── projections
 │         └── length(a.s) [type=int, outer=(4)]
 ├── scan a2
 │    ├── columns: a2.k:7(int!null) a2.i:8(int) a2.f:9(float!null) a2.s:10(string) a2.j:11(jsonb)
 │    ├── key: (7)
 │    └── fd: (7)-->(8-11)
 └── filters
      └── a.f = a2.f [type=bool, outer=(3,9), constraints=(/3: (/NULL - ]; /9: (/NULL - ]), fd=(3)==(9), (9)==(3)]

# Multiple join levels.
opt expect=(SimplifyRightJoinWithFilters,SimplifyLeftJoinWithFilters)
SELECT * FROM a FULL JOIN (SELECT * FROM a INNER JOIN a AS a2 ON a.k=a2.k) AS a2 ON a.f=a2.f
----
inner-join
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb) k:6(int!null) i:7(int) f:8(float!null) s:9(string) j:10(jsonb) k:11(int!null) i:12(int) f:13(float!null) s:14(string) j:15(jsonb)
 ├── key: (1,11)
 ├── fd: (1)-->(2-5), (6)-->(7-10), (11)-->(12-15), (6)==(11), (11)==(6), (3)==(8), (8)==(3)
 ├── scan a
 │    ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float!null) a.s:4(string) a.j:5(jsonb)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── inner-join (merge)
 │    ├── columns: a.k:6(int!null) a.i:7(int) a.f:8(float!null) a.s:9(string) a.j:10(jsonb) a2.k:11(int!null) a2.i:12(int) a2.f:13(float!null) a2.s:14(string) a2.j:15(jsonb)
 │    ├── left ordering: +6
 │    ├── right ordering: +11
 │    ├── key: (11)
 │    ├── fd: (6)-->(7-10), (11)-->(12-15), (6)==(11), (11)==(6)
 │    ├── scan a
 │    │    ├── columns: a.k:6(int!null) a.i:7(int) a.f:8(float!null) a.s:9(string) a.j:10(jsonb)
 │    │    ├── key: (6)
 │    │    ├── fd: (6)-->(7-10)
 │    │    └── ordering: +6
 │    ├── scan a2
 │    │    ├── columns: a2.k:11(int!null) a2.i:12(int) a2.f:13(float!null) a2.s:14(string) a2.j:15(jsonb)
 │    │    ├── key: (11)
 │    │    ├── fd: (11)-->(12-15)
 │    │    └── ordering: +11
 │    └── filters (true)
 └── filters
      └── a.f = a.f [type=bool, outer=(3,8), constraints=(/3: (/NULL - ]; /8: (/NULL - ]), fd=(3)==(8), (8)==(3)]

# Left joins on a foreign key turn into inner joins.
opt
SELECT *
FROM c
LEFT OUTER JOIN a
ON c.y = a.k
----
inner-join
 ├── columns: x:1(int!null) y:2(int!null) z:3(int!null) k:4(int!null) i:5(int) f:6(float!null) s:7(string) j:8(jsonb)
 ├── key: (1)
 ├── fd: (1)-->(2,3), (4)-->(5-8), (2)==(4), (4)==(2)
 ├── scan c
 │    ├── columns: x:1(int!null) y:2(int!null) z:3(int!null)
 │    ├── key: (1)
 │    └── fd: (1)-->(2,3)
 ├── scan a
 │    ├── columns: k:4(int!null) i:5(int) f:6(float!null) s:7(string) j:8(jsonb)
 │    ├── key: (4)
 │    └── fd: (4)-->(5-8)
 └── filters
      └── y = k [type=bool, outer=(2,4), constraints=(/2: (/NULL - ]; /4: (/NULL - ]), fd=(2)==(4), (4)==(2)]

# Left joins on a multiple-column foreign key turn into inner joins.
opt
SELECT *
FROM d
LEFT OUTER JOIN c
ON d.z = c.z
AND d.y = c.x
----
inner-join (merge)
 ├── columns: x:1(int!null) y:2(int!null) z:3(int!null) x:4(int!null) y:5(int!null) z:6(int!null)
 ├── left ordering: +2,+3
 ├── right ordering: +4,+6
 ├── key: (1)
 ├── fd: (1)-->(2,3), (4)-->(5,6), (3)==(6), (6)==(3), (2)==(4), (4)==(2)
 ├── scan d@d_auto_index_fk_y_ref_c
 │    ├── columns: d.x:1(int!null) d.y:2(int!null) d.z:3(int!null)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    └── ordering: +2,+3
 ├── scan c
 │    ├── columns: c.x:4(int!null) c.y:5(int!null) c.z:6(int!null)
 │    ├── key: (4)
 │    ├── fd: (4)-->(5,6)
 │    └── ordering: +4
 └── filters (true)

# Left join on a part of a foreign key turns into an inner join.
opt
SELECT *
FROM d
LEFT OUTER JOIN c
ON d.z = c.z
----
inner-join
 ├── columns: x:1(int!null) y:2(int!null) z:3(int!null) x:4(int!null) y:5(int!null) z:6(int!null)
 ├── key: (1,4)
 ├── fd: (1)-->(2,3), (4)-->(5,6), (3)==(6), (6)==(3)
 ├── scan d
 │    ├── columns: d.x:1(int!null) d.y:2(int!null) d.z:3(int!null)
 │    ├── key: (1)
 │    └── fd: (1)-->(2,3)
 ├── scan c
 │    ├── columns: c.x:4(int!null) c.y:5(int!null) c.z:6(int!null)
 │    ├── key: (4)
 │    └── fd: (4)-->(5,6)
 └── filters
      └── d.z = c.z [type=bool, outer=(3,6), constraints=(/3: (/NULL - ]; /6: (/NULL - ]), fd=(3)==(6), (6)==(3)]

# Can't simplify: joins on non-foreign keys.
opt
SELECT *
FROM c
LEFT OUTER JOIN a
ON c.z = a.k
----
left-join
 ├── columns: x:1(int!null) y:2(int!null) z:3(int!null) k:4(int) i:5(int) f:6(float) s:7(string) j:8(jsonb)
 ├── key: (1,4)
 ├── fd: (1)-->(2,3), (4)-->(5-8)
 ├── scan c
 │    ├── columns: x:1(int!null) y:2(int!null) z:3(int!null)
 │    ├── key: (1)
 │    └── fd: (1)-->(2,3)
 ├── scan a
 │    ├── columns: k:4(int!null) i:5(int) f:6(float!null) s:7(string) j:8(jsonb)
 │    ├── key: (4)
 │    └── fd: (4)-->(5-8)
 └── filters
      └── z = k [type=bool, outer=(3,4), constraints=(/3: (/NULL - ]; /4: (/NULL - ]), fd=(3)==(4), (4)==(3)]

# Can't simplify: joins on non-foreign keys still in foreign key index.
opt
SELECT *
FROM c
LEFT OUTER JOIN a
ON c.x = a.k
----
left-join (merge)
 ├── columns: x:1(int!null) y:2(int!null) z:3(int!null) k:4(int) i:5(int) f:6(float) s:7(string) j:8(jsonb)
 ├── left ordering: +1
 ├── right ordering: +4
 ├── key: (1,4)
 ├── fd: (1)-->(2,3), (4)-->(5-8)
 ├── scan c
 │    ├── columns: x:1(int!null) y:2(int!null) z:3(int!null)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    └── ordering: +1
 ├── scan a
 │    ├── columns: k:4(int!null) i:5(int) f:6(float!null) s:7(string) j:8(jsonb)
 │    ├── key: (4)
 │    ├── fd: (4)-->(5-8)
 │    └── ordering: +4
 └── filters (true)

# Can't simplify: non-equality condition.
opt
SELECT * FROM a FULL JOIN a AS a2 ON a.k<a2.k
----
full-join
 ├── columns: k:1(int) i:2(int) f:3(float) s:4(string) j:5(jsonb) k:6(int) i:7(int) f:8(float) s:9(string) j:10(jsonb)
 ├── key: (1,6)
 ├── fd: (1)-->(2-5), (6)-->(7-10)
 ├── scan a
 │    ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float!null) a.s:4(string) a.j:5(jsonb)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── scan a2
 │    ├── columns: a2.k:6(int!null) a2.i:7(int) a2.f:8(float!null) a2.s:9(string) a2.j:10(jsonb)
 │    ├── key: (6)
 │    └── fd: (6)-->(7-10)
 └── filters
      └── a.k < a2.k [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ])]

# Can't simplify: non-join equality condition.
opt
SELECT * FROM a FULL JOIN a AS a2 ON a.f=1 AND a.f=a2.f
----
full-join
 ├── columns: k:1(int) i:2(int) f:3(float) s:4(string) j:5(jsonb) k:6(int) i:7(int) f:8(float) s:9(string) j:10(jsonb)
 ├── key: (1,6)
 ├── fd: (1)-->(2-5), (6)-->(7-10)
 ├── scan a
 │    ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float!null) a.s:4(string) a.j:5(jsonb)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── scan a2
 │    ├── columns: a2.k:6(int!null) a2.i:7(int) a2.f:8(float!null) a2.s:9(string) a2.j:10(jsonb)
 │    ├── key: (6)
 │    └── fd: (6)-->(7-10)
 └── filters
      ├── a.f = 1.0 [type=bool, outer=(3), constraints=(/3: [/1.0 - /1.0]; tight), fd=()-->(3)]
      └── a.f = a2.f [type=bool, outer=(3,8), constraints=(/3: (/NULL - ]; /8: (/NULL - ]), fd=(3)==(8), (8)==(3)]

# Can't simplify: non-null column.
opt
SELECT * FROM a FULL JOIN a AS a2 ON a.s=a2.s
----
full-join
 ├── columns: k:1(int) i:2(int) f:3(float) s:4(string) j:5(jsonb) k:6(int) i:7(int) f:8(float) s:9(string) j:10(jsonb)
 ├── key: (1,6)
 ├── fd: (1)-->(2-5), (6)-->(7-10)
 ├── scan a
 │    ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float!null) a.s:4(string) a.j:5(jsonb)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── scan a2
 │    ├── columns: a2.k:6(int!null) a2.i:7(int) a2.f:8(float!null) a2.s:9(string) a2.j:10(jsonb)
 │    ├── key: (6)
 │    └── fd: (6)-->(7-10)
 └── filters
      └── a.s = a2.s [type=bool, outer=(4,9), constraints=(/4: (/NULL - ]; /9: (/NULL - ]), fd=(4)==(9), (9)==(4)]

# Can't simplify: equality column that is synthesized.
opt
SELECT * FROM a FULL JOIN (SELECT k+1 AS k FROM a) AS a2 ON a.k=a2.k
----
full-join
 ├── columns: k:1(int) i:2(int) f:3(float) s:4(string) j:5(jsonb) k:11(int)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: a.k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── project
 │    ├── columns: k:11(int)
 │    ├── scan a
 │    │    ├── columns: a.k:6(int!null)
 │    │    └── key: (6)
 │    └── projections
 │         └── a.k + 1 [type=int, outer=(6)]
 └── filters
      └── a.k = k [type=bool, outer=(1,11), constraints=(/1: (/NULL - ]; /11: (/NULL - ]), fd=(1)==(11), (11)==(1)]

# Can't simplify: equality condition with different column ordinals.
opt
SELECT * FROM a FULL JOIN a AS a2 ON a.k=a2.f
----
full-join
 ├── columns: k:1(int) i:2(int) f:3(float) s:4(string) j:5(jsonb) k:6(int) i:7(int) f:8(float) s:9(string) j:10(jsonb)
 ├── key: (1,6)
 ├── fd: (1)-->(2-5), (6)-->(7-10)
 ├── scan a
 │    ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float!null) a.s:4(string) a.j:5(jsonb)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── scan a2
 │    ├── columns: a2.k:6(int!null) a2.i:7(int) a2.f:8(float!null) a2.s:9(string) a2.j:10(jsonb)
 │    ├── key: (6)
 │    └── fd: (6)-->(7-10)
 └── filters
      └── a.k = a2.f [type=bool, outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]

# Can't simplify: one equality condition has columns from same side of join.
opt
SELECT * FROM a FULL JOIN a AS a2 ON a.k=a2.k AND a.f=a.f AND a2.f=a2.f
----
full-join (merge)
 ├── columns: k:1(int) i:2(int) f:3(float) s:4(string) j:5(jsonb) k:6(int) i:7(int) f:8(float) s:9(string) j:10(jsonb)
 ├── left ordering: +1
 ├── right ordering: +6
 ├── key: (1,6)
 ├── fd: (1)-->(2-5), (6)-->(7-10)
 ├── scan a
 │    ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float!null) a.s:4(string) a.j:5(jsonb)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    └── ordering: +1
 ├── scan a2
 │    ├── columns: a2.k:6(int!null) a2.i:7(int) a2.f:8(float!null) a2.s:9(string) a2.j:10(jsonb)
 │    ├── key: (6)
 │    ├── fd: (6)-->(7-10)
 │    └── ordering: +6
 └── filters
      ├── a.f = a.f [type=bool, outer=(3), constraints=(/3: (/NULL - ])]
      └── a2.f = a2.f [type=bool, outer=(8), constraints=(/8: (/NULL - ])]

# Can't simplify: equality conditions have columns from different tables.
opt
SELECT * FROM (SELECT * FROM a, b) AS a FULL JOIN a AS a2 ON a.k=a2.k AND a.x=a2.k
----
full-join
 ├── columns: k:1(int) i:2(int) f:3(float) s:4(string) j:5(jsonb) x:6(int) y:7(int) k:8(int) i:9(int) f:10(float) s:11(string) j:12(jsonb)
 ├── key: (1,6,8)
 ├── fd: (1)-->(2-5), (6)-->(7), (8)-->(9-12)
 ├── inner-join
 │    ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float!null) a.s:4(string) a.j:5(jsonb) x:6(int!null) y:7(int)
 │    ├── key: (1,6)
 │    ├── fd: (1)-->(2-5), (6)-->(7)
 │    ├── scan a
 │    │    ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float!null) a.s:4(string) a.j:5(jsonb)
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2-5)
 │    ├── scan b
 │    │    ├── columns: x:6(int!null) y:7(int)
 │    │    ├── key: (6)
 │    │    └── fd: (6)-->(7)
 │    └── filters (true)
 ├── scan a2
 │    ├── columns: a2.k:8(int!null) a2.i:9(int) a2.f:10(float!null) a2.s:11(string) a2.j:12(jsonb)
 │    ├── key: (8)
 │    └── fd: (8)-->(9-12)
 └── filters
      ├── a.k = a2.k [type=bool, outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]
      └── x = a2.k [type=bool, outer=(6,8), constraints=(/6: (/NULL - ]; /8: (/NULL - ]), fd=(6)==(8), (8)==(6)]

# Can't simplify: The a2.x column is not part of unfilteredCols.
opt
SELECT * FROM a LEFT JOIN (SELECT * FROM a, b) AS a2 ON a.k=a2.x
----
right-join
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb) k:6(int) i:7(int) f:8(float) s:9(string) j:10(jsonb) x:11(int) y:12(int)
 ├── key: (1,6,11)
 ├── fd: (1)-->(2-5), (6)-->(7-10), (11)-->(12)
 ├── inner-join
 │    ├── columns: k:6(int!null) i:7(int) f:8(float!null) s:9(string) j:10(jsonb) x:11(int!null) y:12(int)
 │    ├── key: (6,11)
 │    ├── fd: (6)-->(7-10), (11)-->(12)
 │    ├── scan a
 │    │    ├── columns: k:6(int!null) i:7(int) f:8(float!null) s:9(string) j:10(jsonb)
 │    │    ├── key: (6)
 │    │    └── fd: (6)-->(7-10)
 │    ├── scan b
 │    │    ├── columns: x:11(int!null) y:12(int)
 │    │    ├── key: (11)
 │    │    └── fd: (11)-->(12)
 │    └── filters (true)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 └── filters
      └── k = x [type=bool, outer=(1,11), constraints=(/1: (/NULL - ]; /11: (/NULL - ]), fd=(1)==(11), (11)==(1)]

# --------------------------------------------------
# EliminateSemiJoin
# --------------------------------------------------
opt expect=EliminateSemiJoin
SELECT * FROM a WHERE EXISTS(SELECT count(*) FROM b WHERE x=k)
----
scan a
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 ├── key: (1)
 └── fd: (1)-->(2-5)

# --------------------------------------------------
# EliminateAntiJoin
# --------------------------------------------------
# TODO(justin): figure out if there's a good way to make this still apply.
opt disable=SimplifyZeroCardinalityGroup expect=EliminateAntiJoin
SELECT * FROM a WHERE NOT EXISTS(SELECT * FROM (VALUES (k)) OFFSET 1)
----
scan a
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 ├── key: (1)
 └── fd: (1)-->(2-5)

# --------------------------------------------------
# EliminateJoinNoColsLeft
# --------------------------------------------------
opt expect=EliminateJoinNoColsLeft
SELECT s FROM (VALUES (1, 2)) INNER JOIN a ON s='foo'
----
select
 ├── columns: s:6(string!null)
 ├── fd: ()-->(6)
 ├── scan a
 │    └── columns: s:6(string)
 └── filters
      └── s = 'foo' [type=bool, outer=(6), constraints=(/6: [/'foo' - /'foo']; tight), fd=()-->(6)]

# --------------------------------------------------
# EliminateJoinNoColsRight
# --------------------------------------------------
opt expect=EliminateJoinNoColsRight
SELECT s FROM a INNER JOIN (SELECT count(*) FROM b) ON s='foo'
----
select
 ├── columns: s:4(string!null)
 ├── fd: ()-->(4)
 ├── scan a
 │    └── columns: s:4(string)
 └── filters
      └── s = 'foo' [type=bool, outer=(4), constraints=(/4: [/'foo' - /'foo']; tight), fd=()-->(4)]

# --------------------------------------------------
# HoistJoinProjectRight
#   InnerJoinApply and LeftJoinApply tested by TryDecorrelateLimitOne tests.
# --------------------------------------------------

# Inner-join case.
opt expect=HoistJoinProjectRight
SELECT * FROM a INNER JOIN (SELECT x FROM b WHERE y=10) ON x=k
----
project
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb) x:6(int!null)
 ├── key: (6)
 ├── fd: (1)-->(2-5), (1)==(6), (6)==(1)
 └── inner-join (lookup a)
      ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb) x:6(int!null) y:7(int!null)
      ├── key columns: [6] = [1]
      ├── key: (6)
      ├── fd: ()-->(7), (1)-->(2-5), (1)==(6), (6)==(1)
      ├── select
      │    ├── columns: x:6(int!null) y:7(int!null)
      │    ├── key: (6)
      │    ├── fd: ()-->(7)
      │    ├── scan b
      │    │    ├── columns: x:6(int!null) y:7(int)
      │    │    ├── key: (6)
      │    │    └── fd: (6)-->(7)
      │    └── filters
      │         └── y = 10 [type=bool, outer=(7), constraints=(/7: [/10 - /10]; tight), fd=()-->(7)]
      └── filters (true)

# Left-join case.
opt expect=HoistJoinProjectRight
SELECT * FROM a LEFT JOIN (SELECT x FROM b WHERE y=10) ON x=k
----
project
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb) x:6(int)
 ├── key: (1,6)
 ├── fd: (1)-->(2-5)
 └── left-join (merge)
      ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb) x:6(int) y:7(int)
      ├── left ordering: +1
      ├── right ordering: +6
      ├── key: (1,6)
      ├── fd: (1)-->(2-5), ()~~>(7), (1,6)-->(7)
      ├── scan a
      │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
      │    ├── key: (1)
      │    ├── fd: (1)-->(2-5)
      │    └── ordering: +1
      ├── select
      │    ├── columns: x:6(int!null) y:7(int!null)
      │    ├── key: (6)
      │    ├── fd: ()-->(7)
      │    ├── ordering: +6 opt(7) [actual: +6]
      │    ├── scan b
      │    │    ├── columns: x:6(int!null) y:7(int)
      │    │    ├── key: (6)
      │    │    ├── fd: (6)-->(7)
      │    │    └── ordering: +6 opt(7) [actual: +6]
      │    └── filters
      │         └── y = 10 [type=bool, outer=(7), constraints=(/7: [/10 - /10]; tight), fd=()-->(7)]
      └── filters (true)

# --------------------------------------------------
# HoistJoinProjectLeft
# --------------------------------------------------

# Inner-join case.
opt expect=HoistJoinProjectLeft
SELECT * FROM (SELECT x FROM b WHERE y=10) INNER JOIN a ON x=k
----
project
 ├── columns: x:1(int!null) k:3(int!null) i:4(int) f:5(float!null) s:6(string) j:7(jsonb)
 ├── key: (3)
 ├── fd: (3)-->(4-7), (1)==(3), (3)==(1)
 └── inner-join (lookup a)
      ├── columns: x:1(int!null) y:2(int!null) k:3(int!null) i:4(int) f:5(float!null) s:6(string) j:7(jsonb)
      ├── key columns: [1] = [3]
      ├── key: (3)
      ├── fd: ()-->(2), (3)-->(4-7), (1)==(3), (3)==(1)
      ├── select
      │    ├── columns: x:1(int!null) y:2(int!null)
      │    ├── key: (1)
      │    ├── fd: ()-->(2)
      │    ├── scan b
      │    │    ├── columns: x:1(int!null) y:2(int)
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2)
      │    └── filters
      │         └── y = 10 [type=bool, outer=(2), constraints=(/2: [/10 - /10]; tight), fd=()-->(2)]
      └── filters (true)

# Left-join case.
opt expect=HoistJoinProjectLeft
SELECT * FROM (SELECT x FROM b WHERE y=10) LEFT JOIN a ON x=k
----
project
 ├── columns: x:1(int!null) k:3(int) i:4(int) f:5(float) s:6(string) j:7(jsonb)
 ├── key: (1,3)
 ├── fd: (3)-->(4-7)
 └── left-join (lookup a)
      ├── columns: x:1(int!null) y:2(int!null) k:3(int) i:4(int) f:5(float) s:6(string) j:7(jsonb)
      ├── key columns: [1] = [3]
      ├── key: (1,3)
      ├── fd: ()-->(2), (3)-->(4-7)
      ├── select
      │    ├── columns: x:1(int!null) y:2(int!null)
      │    ├── key: (1)
      │    ├── fd: ()-->(2)
      │    ├── scan b
      │    │    ├── columns: x:1(int!null) y:2(int)
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2)
      │    └── filters
      │         └── y = 10 [type=bool, outer=(2), constraints=(/2: [/10 - /10]; tight), fd=()-->(2)]
      └── filters (true)

# --------------------------------------------------
# SimplifyJoinNotNullEquality
# --------------------------------------------------
norm expect=SimplifyJoinNotNullEquality
SELECT * FROM a INNER JOIN b ON (a.k=b.x) IS True
----
inner-join
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb) x:6(int!null) y:7(int)
 ├── key: (6)
 ├── fd: (1)-->(2-5), (6)-->(7), (1)==(6), (6)==(1)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── scan b
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── key: (6)
 │    └── fd: (6)-->(7)
 └── filters
      └── k = x [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ]), fd=(1)==(6), (6)==(1)]

norm expect=SimplifyJoinNotNullEquality
SELECT * FROM a INNER JOIN b ON (a.k=b.x) IS False
----
inner-join
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb) x:6(int!null) y:7(int)
 ├── key: (1,6)
 ├── fd: (1)-->(2-5), (6)-->(7)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── scan b
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── key: (6)
 │    └── fd: (6)-->(7)
 └── filters
      └── k != x [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ])]

norm expect=SimplifyJoinNotNullEquality
SELECT * FROM a INNER JOIN b ON (a.k=b.x) IS Null
----
values
 ├── columns: k:1(int) i:2(int) f:3(float) s:4(string) j:5(jsonb) x:6(int) y:7(int)
 ├── cardinality: [0 - 0]
 ├── key: ()
 └── fd: ()-->(1-7)

norm expect=SimplifyJoinNotNullEquality
SELECT * FROM a INNER JOIN b ON (a.k=b.x) IS NOT True
----
inner-join
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb) x:6(int!null) y:7(int)
 ├── key: (1,6)
 ├── fd: (1)-->(2-5), (6)-->(7)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── scan b
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── key: (6)
 │    └── fd: (6)-->(7)
 └── filters
      └── k != x [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ])]

norm expect=SimplifyJoinNotNullEquality
SELECT * FROM a INNER JOIN b ON (a.k=b.x) IS NOT False
----
inner-join
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb) x:6(int!null) y:7(int)
 ├── key: (6)
 ├── fd: (1)-->(2-5), (6)-->(7), (1)==(6), (6)==(1)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── scan b
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── key: (6)
 │    └── fd: (6)-->(7)
 └── filters
      └── k = x [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ]), fd=(1)==(6), (6)==(1)]

norm expect=SimplifyJoinNotNullEquality
SELECT * FROM a INNER JOIN b ON (a.k=b.x) IS NOT Null
----
inner-join
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb) x:6(int!null) y:7(int)
 ├── key: (1,6)
 ├── fd: (1)-->(2-5), (6)-->(7)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── scan b
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── key: (6)
 │    └── fd: (6)-->(7)
 └── filters (true)

# Simply multiple conditions, with other conditions present as well.
norm expect=SimplifyJoinNotNullEquality
SELECT *
FROM (SELECT * FROM a WHERE i>0) AS a
INNER JOIN (SELECT x, y, y+1 AS z FROM b WHERE y>10) AS b
ON a.f>=b.z::float AND (a.k=b.x) IS True AND a.f>=b.z::float AND (a.i=b.y) IS NOT False
----
inner-join
 ├── columns: k:1(int!null) i:2(int!null) f:3(float!null) s:4(string) j:5(jsonb) x:6(int!null) y:7(int!null) z:8(int)
 ├── key: (6)
 ├── fd: (1)-->(2-5), (6)-->(7), (7)-->(8), (1)==(6), (6)==(1), (2)==(7), (7)==(2)
 ├── select
 │    ├── columns: k:1(int!null) i:2(int!null) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    ├── scan a
 │    │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2-5)
 │    └── filters
 │         └── i > 0 [type=bool, outer=(2), constraints=(/2: [/1 - ]; tight)]
 ├── project
 │    ├── columns: z:8(int) x:6(int!null) y:7(int!null)
 │    ├── key: (6)
 │    ├── fd: (6)-->(7), (7)-->(8)
 │    ├── select
 │    │    ├── columns: x:6(int!null) y:7(int!null)
 │    │    ├── key: (6)
 │    │    ├── fd: (6)-->(7)
 │    │    ├── scan b
 │    │    │    ├── columns: x:6(int!null) y:7(int)
 │    │    │    ├── key: (6)
 │    │    │    └── fd: (6)-->(7)
 │    │    └── filters
 │    │         └── y > 10 [type=bool, outer=(7), constraints=(/7: [/11 - ]; tight)]
 │    └── projections
 │         └── y + 1 [type=int, outer=(7)]
 └── filters
      ├── f >= z::FLOAT8 [type=bool, outer=(3,8), constraints=(/3: (/NULL - ])]
      ├── f >= z::FLOAT8 [type=bool, outer=(3,8), constraints=(/3: (/NULL - ])]
      ├── k = x [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ]), fd=(1)==(6), (6)==(1)]
      └── i = y [type=bool, outer=(2,7), constraints=(/2: (/NULL - ]; /7: (/NULL - ]), fd=(2)==(7), (7)==(2)]

# Don't trigger rule when one of the variables is nullable.
norm expect-not=SimplifyJoinNotNullEquality
SELECT * FROM a INNER JOIN b ON (a.k=b.y) IS True AND (a.i=b.x) IS False
----
inner-join
 ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb) x:6(int!null) y:7(int)
 ├── key: (1,6)
 ├── fd: (1)-->(2-5), (6)-->(7)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── scan b
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── key: (6)
 │    └── fd: (6)-->(7)
 └── filters
      ├── (k = y) IS true [type=bool, outer=(1,7)]
      └── (i = x) IS false [type=bool, outer=(2,6)]

# --------------------------------------------------
# ExtractJoinEqualities
# --------------------------------------------------

opt expect=ExtractJoinEqualities
SELECT * FROM xy JOIN uv ON x+y=u
----
project
 ├── columns: x:1(int!null) y:2(int) u:3(int!null) v:4(int)
 ├── key: (1)
 ├── fd: (1)-->(2), (1,2)-->(3,4), (3)-->(4)
 └── inner-join
      ├── columns: x:1(int!null) y:2(int) u:3(int!null) v:4(int) column5:5(int!null)
      ├── key: (1)
      ├── fd: (1)-->(2), (1,2)-->(5), (3)-->(4), (3)==(5), (5)==(3)
      ├── project
      │    ├── columns: column5:5(int) x:1(int!null) y:2(int)
      │    ├── key: (1)
      │    ├── fd: (1)-->(2), (1,2)-->(5)
      │    ├── scan xy
      │    │    ├── columns: x:1(int!null) y:2(int)
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2)
      │    └── projections
      │         └── x + y [type=int, outer=(1,2)]
      ├── scan uv
      │    ├── columns: u:3(int!null) v:4(int)
      │    ├── key: (3)
      │    └── fd: (3)-->(4)
      └── filters
           └── column5 = u [type=bool, outer=(3,5), constraints=(/3: (/NULL - ]; /5: (/NULL - ]), fd=(3)==(5), (5)==(3)]

opt expect=ExtractJoinEqualities
SELECT * FROM xy JOIN uv ON u=x+y
----
project
 ├── columns: x:1(int!null) y:2(int) u:3(int!null) v:4(int)
 ├── key: (1)
 ├── fd: (1)-->(2), (1,2)-->(3,4), (3)-->(4)
 └── inner-join
      ├── columns: x:1(int!null) y:2(int) u:3(int!null) v:4(int) column5:5(int!null)
      ├── key: (1)
      ├── fd: (1)-->(2), (1,2)-->(5), (3)-->(4), (3)==(5), (5)==(3)
      ├── project
      │    ├── columns: column5:5(int) x:1(int!null) y:2(int)
      │    ├── key: (1)
      │    ├── fd: (1)-->(2), (1,2)-->(5)
      │    ├── scan xy
      │    │    ├── columns: x:1(int!null) y:2(int)
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2)
      │    └── projections
      │         └── x + y [type=int, outer=(1,2)]
      ├── scan uv
      │    ├── columns: u:3(int!null) v:4(int)
      │    ├── key: (3)
      │    └── fd: (3)-->(4)
      └── filters
           └── column5 = u [type=bool, outer=(3,5), constraints=(/3: (/NULL - ]; /5: (/NULL - ]), fd=(3)==(5), (5)==(3)]

opt expect=ExtractJoinEqualities
SELECT * FROM xy JOIN uv ON x=u+v
----
project
 ├── columns: x:1(int!null) y:2(int) u:3(int!null) v:4(int)
 ├── key: (3)
 ├── fd: (1)-->(2), (3)-->(4), (3,4)-->(1,2)
 └── inner-join
      ├── columns: x:1(int!null) y:2(int) u:3(int!null) v:4(int) column5:5(int!null)
      ├── key: (3)
      ├── fd: (1)-->(2), (3)-->(4), (3,4)-->(5), (1)==(5), (5)==(1)
      ├── scan xy
      │    ├── columns: x:1(int!null) y:2(int)
      │    ├── key: (1)
      │    └── fd: (1)-->(2)
      ├── project
      │    ├── columns: column5:5(int) u:3(int!null) v:4(int)
      │    ├── key: (3)
      │    ├── fd: (3)-->(4), (3,4)-->(5)
      │    ├── scan uv
      │    │    ├── columns: u:3(int!null) v:4(int)
      │    │    ├── key: (3)
      │    │    └── fd: (3)-->(4)
      │    └── projections
      │         └── u + v [type=int, outer=(3,4)]
      └── filters
           └── x = column5 [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]

opt expect=ExtractJoinEqualities
SELECT * FROM xy JOIN uv ON u+v=x
----
project
 ├── columns: x:1(int!null) y:2(int) u:3(int!null) v:4(int)
 ├── key: (3)
 ├── fd: (1)-->(2), (3)-->(4), (3,4)-->(1,2)
 └── inner-join
      ├── columns: x:1(int!null) y:2(int) u:3(int!null) v:4(int) column5:5(int!null)
      ├── key: (3)
      ├── fd: (1)-->(2), (3)-->(4), (3,4)-->(5), (1)==(5), (5)==(1)
      ├── scan xy
      │    ├── columns: x:1(int!null) y:2(int)
      │    ├── key: (1)
      │    └── fd: (1)-->(2)
      ├── project
      │    ├── columns: column5:5(int) u:3(int!null) v:4(int)
      │    ├── key: (3)
      │    ├── fd: (3)-->(4), (3,4)-->(5)
      │    ├── scan uv
      │    │    ├── columns: u:3(int!null) v:4(int)
      │    │    ├── key: (3)
      │    │    └── fd: (3)-->(4)
      │    └── projections
      │         └── u + v [type=int, outer=(3,4)]
      └── filters
           └── x = column5 [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]

opt expect=ExtractJoinEqualities
SELECT * FROM xy JOIN uv ON x+y=u+v
----
project
 ├── columns: x:1(int!null) y:2(int) u:3(int!null) v:4(int)
 ├── key: (1,3)
 ├── fd: (1)-->(2), (3)-->(4)
 └── inner-join
      ├── columns: x:1(int!null) y:2(int) u:3(int!null) v:4(int) column5:5(int!null) column6:6(int!null)
      ├── key: (1,3)
      ├── fd: (1)-->(2), (1,2)-->(5), (3)-->(4), (3,4)-->(6), (5)==(6), (6)==(5)
      ├── project
      │    ├── columns: column5:5(int) x:1(int!null) y:2(int)
      │    ├── key: (1)
      │    ├── fd: (1)-->(2), (1,2)-->(5)
      │    ├── scan xy
      │    │    ├── columns: x:1(int!null) y:2(int)
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2)
      │    └── projections
      │         └── x + y [type=int, outer=(1,2)]
      ├── project
      │    ├── columns: column6:6(int) u:3(int!null) v:4(int)
      │    ├── key: (3)
      │    ├── fd: (3)-->(4), (3,4)-->(6)
      │    ├── scan uv
      │    │    ├── columns: u:3(int!null) v:4(int)
      │    │    ├── key: (3)
      │    │    └── fd: (3)-->(4)
      │    └── projections
      │         └── u + v [type=int, outer=(3,4)]
      └── filters
           └── column5 = column6 [type=bool, outer=(5,6), constraints=(/5: (/NULL - ]; /6: (/NULL - ]), fd=(5)==(6), (6)==(5)]

# Multiple extractable equalities.
opt expect=ExtractJoinEqualities
SELECT * FROM xy JOIN uv ON x+y=u AND x=u+v AND x*y+1=u*v+2
----
project
 ├── columns: x:1(int!null) y:2(int) u:3(int!null) v:4(int)
 ├── key: (1)
 ├── fd: (1)-->(2), (1,2)-->(3,4), (3)-->(4), (3,4)-->(1,2)
 └── inner-join
      ├── columns: x:1(int!null) y:2(int) u:3(int!null) v:4(int) column5:5(int!null) column6:6(int!null) column7:7(int!null) column8:8(int!null)
      ├── key: (1)
      ├── fd: (1)-->(2), (1,2)-->(5,7), (3)-->(4), (3,4)-->(6,8), (3)==(5), (5)==(3), (1)==(6), (6)==(1), (7)==(8), (8)==(7)
      ├── project
      │    ├── columns: column7:7(int) column5:5(int) x:1(int!null) y:2(int)
      │    ├── key: (1)
      │    ├── fd: (1)-->(2), (1,2)-->(5,7)
      │    ├── scan xy
      │    │    ├── columns: x:1(int!null) y:2(int)
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2)
      │    └── projections
      │         ├── (x * y) + 1 [type=int, outer=(1,2)]
      │         └── x + y [type=int, outer=(1,2)]
      ├── project
      │    ├── columns: column8:8(int) column6:6(int) u:3(int!null) v:4(int)
      │    ├── key: (3)
      │    ├── fd: (3)-->(4), (3,4)-->(6,8)
      │    ├── scan uv
      │    │    ├── columns: u:3(int!null) v:4(int)
      │    │    ├── key: (3)
      │    │    └── fd: (3)-->(4)
      │    └── projections
      │         ├── (u * v) + 2 [type=int, outer=(3,4)]
      │         └── u + v [type=int, outer=(3,4)]
      └── filters
           ├── column5 = u [type=bool, outer=(3,5), constraints=(/3: (/NULL - ]; /5: (/NULL - ]), fd=(3)==(5), (5)==(3)]
           ├── x = column6 [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ]), fd=(1)==(6), (6)==(1)]
           └── column7 = column8 [type=bool, outer=(7,8), constraints=(/7: (/NULL - ]; /8: (/NULL - ]), fd=(7)==(8), (8)==(7)]

# An extractable equality with another expression.
opt expect=ExtractJoinEqualities
SELECT * FROM xy JOIN uv ON x+y=u AND x+u=v
----
project
 ├── columns: x:1(int!null) y:2(int) u:3(int!null) v:4(int!null)
 ├── key: (1)
 ├── fd: (1)-->(2), (1,2)-->(3,4), (3)-->(4)
 └── inner-join
      ├── columns: x:1(int!null) y:2(int) u:3(int!null) v:4(int!null) column5:5(int!null)
      ├── key: (1)
      ├── fd: (1)-->(2), (1,2)-->(5), (3)-->(4), (3)==(5), (5)==(3)
      ├── project
      │    ├── columns: column5:5(int) x:1(int!null) y:2(int)
      │    ├── key: (1)
      │    ├── fd: (1)-->(2), (1,2)-->(5)
      │    ├── scan xy
      │    │    ├── columns: x:1(int!null) y:2(int)
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2)
      │    └── projections
      │         └── x + y [type=int, outer=(1,2)]
      ├── scan uv
      │    ├── columns: u:3(int!null) v:4(int)
      │    ├── key: (3)
      │    └── fd: (3)-->(4)
      └── filters
           ├── v = (x + u) [type=bool, outer=(1,3,4), constraints=(/4: (/NULL - ])]
           └── column5 = u [type=bool, outer=(3,5), constraints=(/3: (/NULL - ]; /5: (/NULL - ]), fd=(3)==(5), (5)==(3)]

# Cases with non-extractable equality.
opt expect-not=ExtractJoinEqualities
SELECT * FROM xy FULL OUTER JOIN uv ON x=u
----
full-join (merge)
 ├── columns: x:1(int) y:2(int) u:3(int) v:4(int)
 ├── left ordering: +1
 ├── right ordering: +3
 ├── key: (1,3)
 ├── fd: (1)-->(2), (3)-->(4)
 ├── scan xy
 │    ├── columns: x:1(int!null) y:2(int)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2)
 │    └── ordering: +1
 ├── scan uv
 │    ├── columns: u:3(int!null) v:4(int)
 │    ├── key: (3)
 │    ├── fd: (3)-->(4)
 │    └── ordering: +3
 └── filters (true)

opt expect-not=ExtractJoinEqualities
SELECT * FROM xy FULL OUTER JOIN uv ON x+y=1
----
full-join
 ├── columns: x:1(int) y:2(int) u:3(int) v:4(int)
 ├── key: (1,3)
 ├── fd: (1)-->(2), (3)-->(4)
 ├── scan xy
 │    ├── columns: x:1(int!null) y:2(int)
 │    ├── key: (1)
 │    └── fd: (1)-->(2)
 ├── scan uv
 │    ├── columns: u:3(int!null) v:4(int)
 │    ├── key: (3)
 │    └── fd: (3)-->(4)
 └── filters
      └── (x + y) = 1 [type=bool, outer=(1,2)]

opt expect-not=ExtractJoinEqualities
SELECT * FROM xy FULL OUTER JOIN uv ON 1=u+v
----
full-join
 ├── columns: x:1(int) y:2(int) u:3(int) v:4(int)
 ├── key: (1,3)
 ├── fd: (1)-->(2), (3)-->(4)
 ├── scan xy
 │    ├── columns: x:1(int!null) y:2(int)
 │    ├── key: (1)
 │    └── fd: (1)-->(2)
 ├── scan uv
 │    ├── columns: u:3(int!null) v:4(int)
 │    ├── key: (3)
 │    └── fd: (3)-->(4)
 └── filters
      └── (u + v) = 1 [type=bool, outer=(3,4)]

opt expect-not=ExtractJoinEqualities
SELECT * FROM xy FULL OUTER JOIN uv ON (SELECT k FROM a WHERE i=x)=u
----
project
 ├── columns: x:1(int) y:2(int) u:3(int) v:4(int)
 ├── key: (1,3)
 ├── fd: (1)-->(2), (1,3)-->(4)
 └── full-join-apply
      ├── columns: x:1(int) y:2(int) u:3(int) v:4(int) k:5(int)
      ├── key: (1,3)
      ├── fd: (1)-->(2), (1,3)-->(4,5)
      ├── scan xy
      │    ├── columns: x:1(int!null) y:2(int)
      │    ├── key: (1)
      │    └── fd: (1)-->(2)
      ├── left-join
      │    ├── columns: u:3(int!null) v:4(int) k:5(int)
      │    ├── outer: (1)
      │    ├── key: (3)
      │    ├── fd: (3)-->(4,5), ()~~>(5)
      │    ├── scan uv
      │    │    ├── columns: u:3(int!null) v:4(int)
      │    │    ├── key: (3)
      │    │    └── fd: (3)-->(4)
      │    ├── max1-row
      │    │    ├── columns: k:5(int!null)
      │    │    ├── outer: (1)
      │    │    ├── cardinality: [0 - 1]
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(5)
      │    │    └── project
      │    │         ├── columns: k:5(int!null)
      │    │         ├── outer: (1)
      │    │         ├── key: (5)
      │    │         └── select
      │    │              ├── columns: k:5(int!null) i:6(int!null)
      │    │              ├── outer: (1)
      │    │              ├── key: (5)
      │    │              ├── fd: ()-->(6)
      │    │              ├── scan a
      │    │              │    ├── columns: k:5(int!null) i:6(int)
      │    │              │    ├── key: (5)
      │    │              │    └── fd: (5)-->(6)
      │    │              └── filters
      │    │                   └── i = x [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ]), fd=(1)==(6), (6)==(1)]
      │    └── filters (true)
      └── filters
           └── u = k [type=bool, outer=(3,5), constraints=(/3: (/NULL - ]; /5: (/NULL - ]), fd=(3)==(5), (5)==(3)]

opt expect-not=ExtractJoinEqualities
SELECT * FROM xy FULL OUTER JOIN uv ON x=(SELECT k FROM a WHERE i=u)
----
project
 ├── columns: x:1(int) y:2(int) u:3(int) v:4(int)
 ├── key: (1,3)
 ├── fd: (1)-->(2), (3)-->(4)
 └── full-join
      ├── columns: x:1(int) y:2(int) u:3(int) v:4(int) k:5(int)
      ├── key: (1,3)
      ├── fd: (1)-->(2), (3)-->(4,5)
      ├── scan xy
      │    ├── columns: x:1(int!null) y:2(int)
      │    ├── key: (1)
      │    └── fd: (1)-->(2)
      ├── left-join-apply
      │    ├── columns: u:3(int!null) v:4(int) k:5(int)
      │    ├── key: (3)
      │    ├── fd: (3)-->(4,5)
      │    ├── scan uv
      │    │    ├── columns: u:3(int!null) v:4(int)
      │    │    ├── key: (3)
      │    │    └── fd: (3)-->(4)
      │    ├── max1-row
      │    │    ├── columns: k:5(int!null)
      │    │    ├── outer: (3)
      │    │    ├── cardinality: [0 - 1]
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(5)
      │    │    └── project
      │    │         ├── columns: k:5(int!null)
      │    │         ├── outer: (3)
      │    │         ├── key: (5)
      │    │         └── select
      │    │              ├── columns: k:5(int!null) i:6(int!null)
      │    │              ├── outer: (3)
      │    │              ├── key: (5)
      │    │              ├── fd: ()-->(6)
      │    │              ├── scan a
      │    │              │    ├── columns: k:5(int!null) i:6(int)
      │    │              │    ├── key: (5)
      │    │              │    └── fd: (5)-->(6)
      │    │              └── filters
      │    │                   └── i = u [type=bool, outer=(3,6), constraints=(/3: (/NULL - ]; /6: (/NULL - ]), fd=(3)==(6), (6)==(3)]
      │    └── filters (true)
      └── filters
           └── x = k [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
