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

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

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

# --------------------------------------------------
# MergeSelects
# --------------------------------------------------
opt expect=MergeSelects
SELECT * FROM (SELECT * FROM a WHERE k=3) WHERE s='foo'
----
select
 ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string!null) j:5(jsonb)
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(1-5)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb)
 │    ├── constraint: /1: [/3 - /3]
 │    ├── cardinality: [0 - 1]
 │    ├── key: ()
 │    └── fd: ()-->(1-5)
 └── filters [type=bool, outer=(4), constraints=(/4: [/'foo' - /'foo']; tight), fd=()-->(4)]
      └── s = 'foo' [type=bool, outer=(4), constraints=(/4: [/'foo' - /'foo']; tight)]

opt expect=MergeSelects
SELECT * FROM (SELECT * FROM a WHERE i=1) WHERE False
----
values
 ├── columns: k:1(int) i:2(int) f:3(float) s:4(string) j:5(jsonb)
 ├── cardinality: [0 - 0]
 ├── key: ()
 └── fd: ()-->(1-5)

opt expect=MergeSelects
SELECT * FROM (SELECT * FROM a WHERE i=1) WHERE False
----
values
 ├── columns: k:1(int) i:2(int) f:3(float) s:4(string) j:5(jsonb)
 ├── cardinality: [0 - 0]
 ├── key: ()
 └── fd: ()-->(1-5)

opt expect=MergeSelects
SELECT * FROM (SELECT * FROM a WHERE i<5) WHERE s='foo'
----
select
 ├── columns: k:1(int!null) i:2(int!null) f:3(float) 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) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 └── filters [type=bool, outer=(2,4), constraints=(/2: (/NULL - /4]; /4: [/'foo' - /'foo']; tight), fd=()-->(4)]
      ├── i < 5 [type=bool, outer=(2), constraints=(/2: (/NULL - /4]; tight)]
      └── s = 'foo' [type=bool, outer=(4), constraints=(/4: [/'foo' - /'foo']; tight)]

opt expect=MergeSelects
SELECT * FROM (SELECT * FROM a WHERE i>1 AND i<10) WHERE s='foo' OR k=5
----
select
 ├── columns: k:1(int!null) i:2(int!null) f:3(float) 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) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 └── filters [type=bool, outer=(1,2,4), constraints=(/2: [/2 - /9])]
      ├── i > 1 [type=bool, outer=(2), constraints=(/2: [/2 - ]; tight)]
      ├── i < 10 [type=bool, outer=(2), constraints=(/2: (/NULL - /9]; tight)]
      └── (s = 'foo') OR (k = 5) [type=bool, outer=(1,4)]

# --------------------------------------------------
# PushSelectIntoProject
# --------------------------------------------------
opt expect=PushSelectIntoProject
SELECT * FROM (SELECT i, i+1 AS r, f FROM a) a WHERE f=10.0
----
project
 ├── columns: i:2(int) r:6(int) f:3(float!null)
 ├── fd: ()-->(3), (2)-->(6)
 ├── select
 │    ├── columns: i:2(int) f:3(float!null)
 │    ├── fd: ()-->(3)
 │    ├── scan a
 │    │    └── columns: i:2(int) f:3(float)
 │    └── filters [type=bool, outer=(3), constraints=(/3: [/10.0 - /10.0]; tight), fd=()-->(3)]
 │         └── f = 10.0 [type=bool, outer=(3), constraints=(/3: [/10.0 - /10.0]; tight)]
 └── projections [outer=(2,3)]
      └── i + 1 [type=int, outer=(2)]

# Don't push down select if it depends on computed column that can't be inlined.
opt expect-not=PushSelectIntoProject
SELECT * FROM (SELECT i, i/2 div, f FROM a) a WHERE div=2
----
select
 ├── columns: i:2(int) div:6(decimal!null) f:3(float)
 ├── side-effects
 ├── fd: ()-->(6)
 ├── project
 │    ├── columns: div:6(decimal) i:2(int) f:3(float)
 │    ├── side-effects
 │    ├── scan a
 │    │    └── columns: i:2(int) f:3(float)
 │    └── projections [outer=(2,3), side-effects]
 │         └── i / 2 [type=decimal, outer=(2), side-effects]
 └── filters [type=bool, outer=(6), constraints=(/6: [/2 - /2]; tight), fd=()-->(6)]
      └── div = 2 [type=bool, outer=(6), constraints=(/6: [/2 - /2]; tight)]

# Push down some conjuncts, but not others.
opt expect=PushSelectIntoProject
SELECT * FROM (SELECT i, i/2 div, f FROM a) a WHERE 10.0=f AND 2=div AND i=1
----
select
 ├── columns: i:2(int!null) div:6(decimal!null) f:3(float!null)
 ├── side-effects
 ├── fd: ()-->(2,3,6)
 ├── project
 │    ├── columns: div:6(decimal) i:2(int!null) f:3(float!null)
 │    ├── side-effects
 │    ├── fd: ()-->(2,3)
 │    ├── select
 │    │    ├── columns: i:2(int!null) f:3(float!null)
 │    │    ├── fd: ()-->(2,3)
 │    │    ├── scan a
 │    │    │    └── columns: i:2(int) f:3(float)
 │    │    └── filters [type=bool, outer=(2,3), constraints=(/2: [/1 - /1]; /3: [/10.0 - /10.0]; tight), fd=()-->(2,3)]
 │    │         ├── f = 10.0 [type=bool, outer=(3), constraints=(/3: [/10.0 - /10.0]; tight)]
 │    │         └── i = 1 [type=bool, outer=(2), constraints=(/2: [/1 - /1]; tight)]
 │    └── projections [outer=(2,3), side-effects]
 │         └── i / 2 [type=decimal, outer=(2), side-effects]
 └── filters [type=bool, outer=(6), constraints=(/6: [/2 - /2]; tight), fd=()-->(6)]
      └── div = 2 [type=bool, outer=(6), constraints=(/6: [/2 - /2]; tight)]

# Detect PushSelectIntoProject and FilterUnusedSelectCols dependency cycle.
opt
SELECT f, f+1.1 AS r FROM (SELECT f, i FROM a GROUP BY f, i HAVING sum(f)=10.0) a
----
project
 ├── columns: f:3(float) r:7(float)
 ├── select
 │    ├── columns: i:2(int) f:3(float) sum:6(float!null)
 │    ├── key: (2,3)
 │    ├── fd: ()-->(6)
 │    ├── group-by
 │    │    ├── columns: i:2(int) f:3(float) sum:6(float)
 │    │    ├── grouping columns: i:2(int) f:3(float)
 │    │    ├── key: (2,3)
 │    │    ├── fd: (2,3)-->(6)
 │    │    ├── scan a
 │    │    │    └── columns: i:2(int) f:3(float)
 │    │    └── aggregations [outer=(3)]
 │    │         └── sum [type=float, outer=(3)]
 │    │              └── variable: f [type=float, outer=(3)]
 │    └── filters [type=bool, outer=(6), constraints=(/6: [/10.0 - /10.0]; tight), fd=()-->(6)]
 │         └── sum = 10.0 [type=bool, outer=(6), constraints=(/6: [/10.0 - /10.0]; tight)]
 └── projections [outer=(3)]
      └── f + 1.1 [type=float, outer=(3)]

# --------------------------------------------------
# PushSelectCondLeftIntoJoinLeftAndRight
#   + PushSelectCondRightIntoJoinLeftAndRight
# --------------------------------------------------

# Only the filters bound by the left side are mapped and pushed down.
opt expect=PushSelectCondLeftIntoJoinLeftAndRight
SELECT * FROM a LEFT JOIN xy ON a.k=xy.x WHERE a.k > 5 AND (xy.x = 6 OR xy.x IS NULL)
----
select
 ├── columns: k:1(int!null) 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)
 ├── left-join (merge)
 │    ├── columns: k:1(int!null) 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) s:4(string) j:5(jsonb)
 │    │    ├── constraint: /1: [/6 - ]
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(2-5)
 │    │    └── ordering: +1
 │    ├── scan xy
 │    │    ├── columns: x:6(int!null) y:7(int)
 │    │    ├── constraint: /6: [/6 - ]
 │    │    ├── key: (6)
 │    │    ├── fd: (6)-->(7)
 │    │    └── ordering: +6
 │    └── merge-on
 │         ├── left ordering: +1
 │         ├── right ordering: +6
 │         └── true [type=bool]
 └── filters [type=bool, outer=(6)]
      └── (x = 6) OR (x IS NULL) [type=bool, outer=(6)]

opt expect=PushSelectCondLeftIntoJoinLeftAndRight
SELECT * FROM a WHERE EXISTS (SELECT * FROM xy WHERE a.k=xy.x) AND a.k > 5
----
semi-join (merge)
 ├── columns: k:1(int!null) i:2(int) f:3(float) 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) s:4(string) j:5(jsonb)
 │    ├── constraint: /1: [/6 - ]
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    └── ordering: +1
 ├── scan xy
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── constraint: /6: [/6 - ]
 │    ├── key: (6)
 │    ├── fd: (6)-->(7)
 │    └── ordering: +6
 └── merge-on
      ├── left ordering: +1
      ├── right ordering: +6
      └── true [type=bool]

opt expect=PushSelectCondLeftIntoJoinLeftAndRight
SELECT * FROM a WHERE NOT EXISTS (SELECT * FROM xy WHERE a.k=xy.x) AND a.k > 5
----
anti-join (merge)
 ├── columns: k:1(int!null) i:2(int) f:3(float) 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) s:4(string) j:5(jsonb)
 │    ├── constraint: /1: [/6 - ]
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    └── ordering: +1
 ├── scan xy
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── constraint: /6: [/6 - ]
 │    ├── key: (6)
 │    ├── fd: (6)-->(7)
 │    └── ordering: +6
 └── merge-on
      ├── left ordering: +1
      ├── right ordering: +6
      └── true [type=bool]

# Only the filters bound by the right side are mapped and pushed down.
opt expect=PushSelectCondRightIntoJoinLeftAndRight
SELECT * FROM a RIGHT JOIN xy ON a.k=xy.x AND a.i=xy.y
WHERE xy.x + xy.y > 5 AND (xy.x + a.i = 6 OR xy.x IS NULL) AND (xy.y % 2 = 0) AND xy.x >= 10
----
select
 ├── columns: k:1(int) i:2(int) f:3(float) s:4(string) j:5(jsonb) x:6(int!null) y:7(int)
 ├── key: (1,6)
 ├── fd: (1)-->(2-5), (6)-->(7)
 ├── left-join (lookup a)
 │    ├── columns: k:1(int) i:2(int) f:3(float) s:4(string) j:5(jsonb) x:6(int!null) y:7(int)
 │    ├── key columns: [6] = [1]
 │    ├── key: (1,6)
 │    ├── fd: (1)-->(2-5), (6)-->(7)
 │    ├── select
 │    │    ├── columns: x:6(int!null) y:7(int)
 │    │    ├── key: (6)
 │    │    ├── fd: (6)-->(7)
 │    │    ├── scan xy
 │    │    │    ├── columns: x:6(int!null) y:7(int)
 │    │    │    ├── constraint: /6: [/10 - ]
 │    │    │    ├── key: (6)
 │    │    │    └── fd: (6)-->(7)
 │    │    └── filters [type=bool, outer=(6,7)]
 │    │         ├── (x + y) > 5 [type=bool, outer=(6,7)]
 │    │         └── (y % 2) = 0 [type=bool, outer=(7)]
 │    └── filters [type=bool, outer=(1,2,7), constraints=(/1: [/10 - ]; /2: (/NULL - ]; /7: (/NULL - ]), fd=(2)==(7), (7)==(2)]
 │         ├── i = y [type=bool, outer=(2,7), constraints=(/2: (/NULL - ]; /7: (/NULL - ])]
 │         ├── (k + i) > 5 [type=bool, outer=(1,2)]
 │         ├── (i % 2) = 0 [type=bool, outer=(2)]
 │         └── k >= 10 [type=bool, outer=(1), constraints=(/1: [/10 - ]; tight)]
 └── filters [type=bool, outer=(2,6)]
      └── ((x + i) = 6) OR (x IS NULL) [type=bool, outer=(2,6)]

# --------------------------------------------------
# PushSelectIntoJoinLeft
# --------------------------------------------------
opt expect=PushSelectIntoJoinLeft
SELECT * FROM a LEFT JOIN xy ON a.k=xy.x WHERE a.f=1.1
----
left-join (lookup xy)
 ├── 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 columns: [1] = [6]
 ├── key: (1,6)
 ├── fd: ()-->(3), (1)-->(2,4,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: ()-->(3), (1)-->(2,4,5)
 │    ├── scan a
 │    │    ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb)
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2-5)
 │    └── filters [type=bool, outer=(3), constraints=(/3: [/1.1 - /1.1]; tight), fd=()-->(3)]
 │         └── f = 1.1 [type=bool, outer=(3), constraints=(/3: [/1.1 - /1.1]; tight)]
 └── true [type=bool]

opt expect=PushSelectIntoJoinLeft
SELECT * FROM a LEFT JOIN xy ON a.k=xy.x
WHERE a.f=1.1 AND (a.i<xy.y OR xy.y IS NULL) AND (a.s='foo' OR a.s='bar')
----
select
 ├── 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,6)
 ├── fd: ()-->(3), (1)-->(2,4,5), (6)-->(7)
 ├── left-join (lookup xy)
 │    ├── 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 columns: [1] = [6]
 │    ├── key: (1,6)
 │    ├── fd: ()-->(3), (1)-->(2,4,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: ()-->(3), (1)-->(2,4,5)
 │    │    ├── scan a
 │    │    │    ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb)
 │    │    │    ├── key: (1)
 │    │    │    └── fd: (1)-->(2-5)
 │    │    └── filters [type=bool, outer=(3,4), constraints=(/3: [/1.1 - /1.1]), fd=()-->(3)]
 │    │         ├── f = 1.1 [type=bool, outer=(3), constraints=(/3: [/1.1 - /1.1]; tight)]
 │    │         └── (s = 'foo') OR (s = 'bar') [type=bool, outer=(4)]
 │    └── true [type=bool]
 └── filters [type=bool, outer=(2,7)]
      └── (i < y) OR (y IS NULL) [type=bool, outer=(2,7)]

# Pushdown constant condition.
norm expect=PushSelectIntoJoinLeft
SELECT * FROM a LEFT JOIN xy ON True WHERE a.i=100 AND $1>'2000-01-01T1:00:00'
----
left-join
 ├── columns: k:1(int!null) i:2(int!null) f:3(float) s:4(string) j:5(jsonb) x:6(int) y:7(int)
 ├── has-placeholder
 ├── key: (1,6)
 ├── fd: ()-->(2), (1)-->(3-5), (6)-->(7)
 ├── select
 │    ├── columns: k:1(int!null) i:2(int!null) f:3(float) s:4(string) j:5(jsonb)
 │    ├── has-placeholder
 │    ├── key: (1)
 │    ├── fd: ()-->(2), (1)-->(3-5)
 │    ├── scan a
 │    │    ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb)
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2-5)
 │    └── filters [type=bool, outer=(2), constraints=(/2: [/100 - /100]), fd=()-->(2)]
 │         ├── $1 > '2000-01-01T1:00:00' [type=bool]
 │         └── i = 100 [type=bool, outer=(2), constraints=(/2: [/100 - /100]; tight)]
 ├── select
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── has-placeholder
 │    ├── key: (6)
 │    ├── fd: (6)-->(7)
 │    ├── scan xy
 │    │    ├── columns: x:6(int!null) y:7(int)
 │    │    ├── key: (6)
 │    │    └── fd: (6)-->(7)
 │    └── filters [type=bool]
 │         └── $1 > '2000-01-01T1:00:00' [type=bool]
 └── true [type=bool]

# Don't push down conditions in case of RIGHT JOIN.
opt
SELECT * FROM a RIGHT JOIN xy ON a.k=xy.x WHERE a.i=100 OR a.i IS NULL
----
select
 ├── columns: k:1(int) i:2(int) f:3(float) s:4(string) j:5(jsonb) x:6(int!null) y:7(int)
 ├── key: (1,6)
 ├── fd: (1)-->(2-5), (6)-->(7)
 ├── 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)
 │    ├── key: (1,6)
 │    ├── fd: (1)-->(2-5), (6)-->(7)
 │    ├── scan a
 │    │    ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb)
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(2-5)
 │    │    └── ordering: +1
 │    ├── scan xy
 │    │    ├── columns: x:6(int!null) y:7(int)
 │    │    ├── key: (6)
 │    │    ├── fd: (6)-->(7)
 │    │    └── ordering: +6
 │    └── merge-on
 │         ├── left ordering: +1
 │         ├── right ordering: +6
 │         └── true [type=bool]
 └── filters [type=bool, outer=(2)]
      └── (i = 100) OR (i IS NULL) [type=bool, outer=(2)]

# Don't push down conditions in case of FULL JOIN.
opt
SELECT * FROM a FULL JOIN xy ON a.k=xy.x WHERE a.i=100 OR a.i IS NULL
----
select
 ├── 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)
 ├── 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)
 │    ├── key: (1,6)
 │    ├── fd: (1)-->(2-5), (6)-->(7)
 │    ├── scan a
 │    │    ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb)
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(2-5)
 │    │    └── ordering: +1
 │    ├── scan xy
 │    │    ├── columns: x:6(int!null) y:7(int)
 │    │    ├── key: (6)
 │    │    ├── fd: (6)-->(7)
 │    │    └── ordering: +6
 │    └── merge-on
 │         ├── left ordering: +1
 │         ├── right ordering: +6
 │         └── true [type=bool]
 └── filters [type=bool, outer=(2)]
      └── (i = 100) OR (i IS NULL) [type=bool, outer=(2)]

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

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

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

opt expect=PushSelectIntoJoinRight
SELECT * FROM xy RIGHT JOIN a ON xy.x=a.k
WHERE a.f=1.1 AND (a.i<xy.y OR xy.y IS NULL) AND (a.s='foo' OR a.s='bar')
----
select
 ├── columns: x:1(int) y:2(int) k:3(int!null) i:4(int) f:5(float!null) s:6(string) j:7(jsonb)
 ├── key: (1,3)
 ├── fd: ()-->(5), (1)-->(2), (3)-->(4,6,7)
 ├── left-join (lookup xy)
 │    ├── columns: x:1(int) y:2(int) k:3(int!null) i:4(int) f:5(float!null) s:6(string) j:7(jsonb)
 │    ├── key columns: [3] = [1]
 │    ├── key: (1,3)
 │    ├── fd: ()-->(5), (1)-->(2), (3)-->(4,6,7)
 │    ├── select
 │    │    ├── columns: k:3(int!null) i:4(int) f:5(float!null) s:6(string) j:7(jsonb)
 │    │    ├── key: (3)
 │    │    ├── fd: ()-->(5), (3)-->(4,6,7)
 │    │    ├── scan a
 │    │    │    ├── columns: k:3(int!null) i:4(int) f:5(float) s:6(string) j:7(jsonb)
 │    │    │    ├── key: (3)
 │    │    │    └── fd: (3)-->(4-7)
 │    │    └── filters [type=bool, outer=(5,6), constraints=(/5: [/1.1 - /1.1]), fd=()-->(5)]
 │    │         ├── f = 1.1 [type=bool, outer=(5), constraints=(/5: [/1.1 - /1.1]; tight)]
 │    │         └── (s = 'foo') OR (s = 'bar') [type=bool, outer=(6)]
 │    └── true [type=bool]
 └── filters [type=bool, outer=(2,4)]
      └── (i < y) OR (y IS NULL) [type=bool, outer=(2,4)]

# Don't push down conditions in case of LEFT JOIN.
opt
SELECT * FROM xy LEFT JOIN a ON a.k=xy.x WHERE a.i=100 OR a.i IS NULL
----
select
 ├── columns: x:1(int!null) y:2(int) k:3(int) i:4(int) f:5(float) s:6(string) j:7(jsonb)
 ├── key: (1,3)
 ├── fd: (1)-->(2), (3)-->(4-7)
 ├── 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)
 │    ├── key: (1,3)
 │    ├── fd: (1)-->(2), (3)-->(4-7)
 │    ├── scan xy
 │    │    ├── 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) s:6(string) j:7(jsonb)
 │    │    ├── key: (3)
 │    │    ├── fd: (3)-->(4-7)
 │    │    └── ordering: +3
 │    └── merge-on
 │         ├── left ordering: +1
 │         ├── right ordering: +3
 │         └── true [type=bool]
 └── filters [type=bool, outer=(4)]
      └── (i = 100) OR (i IS NULL) [type=bool, outer=(4)]

# Don't push down conditions in case of FULL JOIN.
opt
SELECT * FROM xy FULL JOIN a ON a.k=xy.x WHERE a.i=100 OR a.i IS NULL
----
select
 ├── columns: x:1(int) y:2(int) k:3(int) i:4(int) f:5(float) s:6(string) j:7(jsonb)
 ├── key: (1,3)
 ├── fd: (1)-->(2), (3)-->(4-7)
 ├── full-join (merge)
 │    ├── columns: x:1(int) y:2(int) k:3(int) i:4(int) f:5(float) s:6(string) j:7(jsonb)
 │    ├── key: (1,3)
 │    ├── fd: (1)-->(2), (3)-->(4-7)
 │    ├── scan xy
 │    │    ├── 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) s:6(string) j:7(jsonb)
 │    │    ├── key: (3)
 │    │    ├── fd: (3)-->(4-7)
 │    │    └── ordering: +3
 │    └── merge-on
 │         ├── left ordering: +1
 │         ├── right ordering: +3
 │         └── true [type=bool]
 └── filters [type=bool, outer=(4)]
      └── (i = 100) OR (i IS NULL) [type=bool, outer=(4)]

# --------------------------------------------------
# MergeSelectInnerJoin
# --------------------------------------------------
opt expect=MergeSelectInnerJoin
SELECT * FROM a, xy WHERE a.k=xy.x AND (a.s='foo' OR xy.y<100)
----
inner-join (merge)
 ├── columns: k:1(int!null) i:2(int) f:3(float) 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) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    └── ordering: +1
 ├── scan xy
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── key: (6)
 │    ├── fd: (6)-->(7)
 │    └── ordering: +6
 └── merge-on
      ├── left ordering: +1
      ├── right ordering: +6
      └── filters [type=bool, outer=(4,7)]
           └── (s = 'foo') OR (y < 100) [type=bool, outer=(4,7)]

opt expect=MergeSelectInnerJoin
SELECT * FROM a INNER JOIN xy ON a.k=xy.x WHERE (a.s='foo' OR xy.y<100)
----
inner-join (merge)
 ├── columns: k:1(int!null) i:2(int) f:3(float) 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) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    └── ordering: +1
 ├── scan xy
 │    ├── columns: x:6(int!null) y:7(int)
 │    ├── key: (6)
 │    ├── fd: (6)-->(7)
 │    └── ordering: +6
 └── merge-on
      ├── left ordering: +1
      ├── right ordering: +6
      └── filters [type=bool, outer=(4,7)]
           └── (s = 'foo') OR (y < 100) [type=bool, outer=(4,7)]

opt expect=MergeSelectInnerJoin
SELECT * FROM a INNER JOIN xy ON a.k=xy.x WHERE False
----
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)

# Don't merge with LEFT JOIN.
opt expect-not=MergeSelectInnerJoin
SELECT * FROM a LEFT JOIN xy ON True WHERE a.k=xy.x OR xy.x IS NULL
----
select
 ├── columns: k:1(int!null) 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)
 ├── left-join
 │    ├── columns: k:1(int!null) 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) s:4(string) j:5(jsonb)
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2-5)
 │    ├── scan xy
 │    │    ├── columns: x:6(int!null) y:7(int)
 │    │    ├── key: (6)
 │    │    └── fd: (6)-->(7)
 │    └── true [type=bool]
 └── filters [type=bool, outer=(1,6)]
      └── (k = x) OR (x IS NULL) [type=bool, outer=(1,6)]

# Don't merge with RIGHT JOIN.
opt expect-not=MergeSelectInnerJoin
SELECT * FROM a RIGHT JOIN xy ON True WHERE a.k=xy.x OR a.k IS NULL
----
select
 ├── columns: k:1(int) i:2(int) f:3(float) s:4(string) j:5(jsonb) x:6(int!null) y:7(int)
 ├── key: (1,6)
 ├── fd: (1)-->(2-5), (6)-->(7)
 ├── right-join
 │    ├── columns: k:1(int) i:2(int) f:3(float) 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) s:4(string) j:5(jsonb)
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2-5)
 │    ├── scan xy
 │    │    ├── columns: x:6(int!null) y:7(int)
 │    │    ├── key: (6)
 │    │    └── fd: (6)-->(7)
 │    └── true [type=bool]
 └── filters [type=bool, outer=(1,6)]
      └── (k = x) OR (k IS NULL) [type=bool, outer=(1,6)]

# Don't merge with FULL JOIN.
opt expect-not=MergeSelectInnerJoin
SELECT * FROM a FULL JOIN xy ON True WHERE a.k=xy.x OR a.k IS NULL OR xy.x IS NULL
----
select
 ├── 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)
 ├── 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) s:4(string) j:5(jsonb)
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2-5)
 │    ├── scan xy
 │    │    ├── columns: x:6(int!null) y:7(int)
 │    │    ├── key: (6)
 │    │    └── fd: (6)-->(7)
 │    └── true [type=bool]
 └── filters [type=bool, outer=(1,6)]
      └── ((k = x) OR (k IS NULL)) OR (x IS NULL) [type=bool, outer=(1,6)]

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

opt
SELECT * FROM a, xy WHERE a.i=100 AND $1>'2000-01-01T1:00:00' AND xy.x=a.k
----
inner-join (lookup xy)
 ├── columns: k:1(int!null) i:2(int!null) f:3(float) s:4(string) j:5(jsonb) x:6(int!null) y:7(int)
 ├── key columns: [1] = [6]
 ├── has-placeholder
 ├── key: (6)
 ├── fd: ()-->(2), (1)-->(3-5), (6)-->(7), (1)==(6), (6)==(1)
 ├── select
 │    ├── columns: k:1(int!null) i:2(int!null) f:3(float) s:4(string) j:5(jsonb)
 │    ├── has-placeholder
 │    ├── key: (1)
 │    ├── fd: ()-->(2), (1)-->(3-5)
 │    ├── scan a
 │    │    ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb)
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2-5)
 │    └── filters [type=bool, outer=(2), constraints=(/2: [/100 - /100]), fd=()-->(2)]
 │         ├── $1 > '2000-01-01T1:00:00' [type=bool]
 │         └── i = 100 [type=bool, outer=(2), constraints=(/2: [/100 - /100]; tight)]
 └── filters [type=bool]
      └── $1 > '2000-01-01T1:00:00' [type=bool]

# --------------------------------------------------
# PushSelectIntoGroupBy
# --------------------------------------------------

# Push down into GroupBy with aggregations.
opt expect=PushSelectIntoGroupBy
SELECT * FROM (SELECT i, count(*) FROM a GROUP BY i) a WHERE i=1
----
group-by
 ├── columns: i:2(int) count:6(int)
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(2,6)
 ├── select
 │    ├── columns: i:2(int!null)
 │    ├── fd: ()-->(2)
 │    ├── scan a
 │    │    └── columns: i:2(int)
 │    └── filters [type=bool, outer=(2), constraints=(/2: [/1 - /1]; tight), fd=()-->(2)]
 │         └── i = 1 [type=bool, outer=(2), constraints=(/2: [/1 - /1]; tight)]
 └── aggregations [outer=(2)]
      ├── count-rows [type=int]
      └── const-agg [type=int, outer=(2)]
           └── variable: i [type=int, outer=(2)]

# Push down into GroupBy with no aggregations.
opt expect=PushSelectIntoGroupBy
SELECT * FROM (SELECT i FROM a GROUP BY i) a WHERE i=1
----
limit
 ├── columns: i:2(int!null)
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(2)
 ├── select
 │    ├── columns: i:2(int!null)
 │    ├── fd: ()-->(2)
 │    ├── scan a
 │    │    └── columns: i:2(int)
 │    └── filters [type=bool, outer=(2), constraints=(/2: [/1 - /1]; tight), fd=()-->(2)]
 │         └── i = 1 [type=bool, outer=(2), constraints=(/2: [/1 - /1]; tight)]
 └── const: 1 [type=int]

# Push down only conditions that do not depend on aggregations.
opt expect=PushSelectIntoGroupBy
SELECT * FROM (SELECT k, i, max(s) m FROM a GROUP BY k, i) a WHERE i=k AND m='foo'
----
select
 ├── columns: k:1(int!null) i:2(int) m:6(string!null)
 ├── key: (1)
 ├── fd: ()-->(6), (1)==(2), (2)==(1), (1)-->(2)
 ├── group-by
 │    ├── columns: k:1(int!null) i:2(int) max:6(string)
 │    ├── grouping columns: k:1(int!null)
 │    ├── internal-ordering: +(1|2)
 │    ├── key: (1)
 │    ├── fd: (1)==(2), (2)==(1), (1)-->(2,6)
 │    ├── select
 │    │    ├── columns: k:1(int!null) i:2(int!null) s:4(string)
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(4), (1)==(2), (2)==(1)
 │    │    ├── ordering: +(1|2)
 │    │    ├── scan a
 │    │    │    ├── columns: k:1(int!null) i:2(int) s:4(string)
 │    │    │    ├── key: (1)
 │    │    │    ├── fd: (1)-->(2,4)
 │    │    │    └── ordering: +(1|2)
 │    │    └── filters [type=bool, outer=(1,2), constraints=(/1: (/NULL - ]; /2: (/NULL - ]), fd=(1)==(2), (2)==(1)]
 │    │         └── i = k [type=bool, outer=(1,2), constraints=(/1: (/NULL - ]; /2: (/NULL - ])]
 │    └── aggregations [outer=(2,4)]
 │         ├── max [type=string, outer=(4)]
 │         │    └── variable: s [type=string, outer=(4)]
 │         └── const-agg [type=int, outer=(2)]
 │              └── variable: i [type=int, outer=(2)]
 └── filters [type=bool, outer=(6), constraints=(/6: [/'foo' - /'foo']; tight), fd=()-->(6)]
      └── max = 'foo' [type=bool, outer=(6), constraints=(/6: [/'foo' - /'foo']; tight)]

# DistinctOn case.
opt expect=PushSelectIntoGroupBy
SELECT * FROM (SELECT DISTINCT ON (i, f) i, s, f FROM a) WHERE i>f
----
distinct-on
 ├── columns: i:2(int!null) s:4(string) f:3(float!null)
 ├── grouping columns: i:2(int!null) f:3(float!null)
 ├── key: (2,3)
 ├── fd: (2,3)-->(4)
 ├── select
 │    ├── columns: i:2(int!null) f:3(float!null) s:4(string)
 │    ├── scan a
 │    │    └── columns: i:2(int) f:3(float) s:4(string)
 │    └── filters [type=bool, outer=(2,3), constraints=(/2: (/NULL - ]; /3: (/NULL - ])]
 │         └── i > f [type=bool, outer=(2,3), constraints=(/2: (/NULL - ]; /3: (/NULL - ])]
 └── aggregations [outer=(4)]
      └── first-agg [type=string, outer=(4)]
           └── variable: s [type=string, outer=(4)]

# DistinctOn case with a ConstAgg.
opt expect=PushSelectIntoGroupBy
SELECT * FROM (SELECT DISTINCT ON (k, f, s) k, i, f, x FROM a JOIN xy ON i=y) WHERE k > f
----
distinct-on
 ├── columns: k:1(int!null) i:2(int) f:3(float) x:6(int)
 ├── grouping columns: k:1(int!null)
 ├── key: (1)
 ├── fd: (1)-->(2,3,6), (6)-->(2)
 ├── inner-join
 │    ├── columns: k:1(int!null) i:2(int!null) f:3(float!null) x:6(int!null) y:7(int!null)
 │    ├── key: (1,6)
 │    ├── fd: (1)-->(2,3), (6)-->(7), (2)==(7), (7)==(2)
 │    ├── scan xy
 │    │    ├── columns: x:6(int!null) y:7(int)
 │    │    ├── key: (6)
 │    │    └── fd: (6)-->(7)
 │    ├── select
 │    │    ├── columns: k:1(int!null) i:2(int) f:3(float!null)
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(2,3)
 │    │    ├── scan a
 │    │    │    ├── columns: k:1(int!null) i:2(int) f:3(float)
 │    │    │    ├── key: (1)
 │    │    │    └── fd: (1)-->(2,3)
 │    │    └── filters [type=bool, outer=(1,3), constraints=(/1: (/NULL - ]; /3: (/NULL - ])]
 │    │         └── k > f [type=bool, outer=(1,3), constraints=(/1: (/NULL - ]; /3: (/NULL - ])]
 │    └── filters [type=bool, outer=(2,7), constraints=(/2: (/NULL - ]; /7: (/NULL - ]), fd=(2)==(7), (7)==(2)]
 │         └── i = y [type=bool, outer=(2,7), constraints=(/2: (/NULL - ]; /7: (/NULL - ])]
 └── aggregations [outer=(2,3,6)]
      ├── first-agg [type=int, outer=(2)]
      │    └── variable: i [type=int, outer=(2)]
      ├── first-agg [type=int, outer=(6)]
      │    └── variable: x [type=int, outer=(6)]
      └── const-agg [type=float, outer=(3)]
           └── variable: f [type=float, outer=(3)]

# Do *not* push down into scalar GroupBy.
opt expect-not=PushSelectIntoGroupBy
SELECT * FROM (SELECT count(*) c FROM a) a WHERE $1<'2000-01-01T10:00:00' AND c=0
----
select
 ├── columns: c:6(int!null)
 ├── cardinality: [0 - 1]
 ├── has-placeholder
 ├── key: ()
 ├── fd: ()-->(6)
 ├── scalar-group-by
 │    ├── columns: count_rows:6(int)
 │    ├── cardinality: [1 - 1]
 │    ├── key: ()
 │    ├── fd: ()-->(6)
 │    ├── scan a
 │    └── aggregations
 │         └── count-rows [type=int]
 └── filters [type=bool, outer=(6), constraints=(/6: [/0 - /0]), fd=()-->(6)]
      ├── $1 < '2000-01-01T10:00:00' [type=bool]
      └── count_rows = 0 [type=bool, outer=(6), constraints=(/6: [/0 - /0]; tight)]

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

opt expect=RemoveNotNullCondition
SELECT k FROM b WHERE k IS NOT NULL AND k > 4
----
scan b
 ├── columns: k:1(int!null)
 ├── constraint: /1: [/5 - ]
 └── key: (1)

opt
SELECT k FROM b WHERE k IS NULL
----
scan b
 ├── columns: k:1(int!null)
 ├── constraint: /1: contradiction
 ├── cardinality: [0 - 1]
 ├── key: ()
 └── fd: ()-->(1)

opt expect=RemoveNotNullCondition
SELECT k,i FROM b WHERE k IS NOT NULL AND k > 4 AND i < 100 AND i IS NOT NULL
----
select
 ├── columns: k:1(int!null) i:2(int!null)
 ├── key: (1)
 ├── fd: (1)-->(2)
 ├── scan b
 │    ├── columns: k:1(int!null) i:2(int)
 │    ├── constraint: /1: [/5 - ]
 │    ├── key: (1)
 │    └── fd: (1)-->(2)
 └── filters [type=bool, outer=(2), constraints=(/2: (/NULL - /99]; tight)]
      ├── i < 100 [type=bool, outer=(2), constraints=(/2: (/NULL - /99]; tight)]
      └── i IS NOT NULL [type=bool, outer=(2), constraints=(/2: (/NULL - ]; tight)]

opt expect=RemoveNotNullCondition
SELECT k,s FROM b WHERE k IS NOT NULL AND s IS NOT NULL
----
scan b
 ├── columns: k:1(int!null) s:4(string!null)
 ├── key: (1)
 └── fd: (1)-->(4)

# RemoveNotNullCondition partially applied
opt expect=RemoveNotNullCondition
SELECT k,s,i FROM b WHERE k IS NOT NULL AND s IS NOT NULL AND i IS NOT NULL
----
select
 ├── columns: k:1(int!null) s:4(string!null) i:2(int!null)
 ├── key: (1)
 ├── fd: (1)-->(2,4)
 ├── scan b
 │    ├── columns: k:1(int!null) i:2(int) s:4(string!null)
 │    ├── key: (1)
 │    └── fd: (1)-->(2,4)
 └── filters [type=bool, outer=(2), constraints=(/2: (/NULL - ]; tight)]
      └── i IS NOT NULL [type=bool, outer=(2), constraints=(/2: (/NULL - ]; tight)]

# RemoveNotNullCondition rule is not applied
opt expect-not=RemoveNotNullCondition
SELECT i FROM b WHERE i IS NOT NULL
----
select
 ├── columns: i:2(int!null)
 ├── scan b
 │    └── columns: i:2(int)
 └── filters [type=bool, outer=(2), constraints=(/2: (/NULL - ]; tight)]
      └── i IS NOT NULL [type=bool, outer=(2), constraints=(/2: (/NULL - ]; tight)]

# RemoveNotNullCondition rule is not applied
opt expect-not=RemoveNotNullCondition
SELECT k FROM b WHERE i+k IS NOT NULL
----
project
 ├── columns: k:1(int!null)
 ├── key: (1)
 └── select
      ├── columns: k:1(int!null) i:2(int)
      ├── key: (1)
      ├── fd: (1)-->(2)
      ├── scan b
      │    ├── columns: k:1(int!null) i:2(int)
      │    ├── key: (1)
      │    └── fd: (1)-->(2)
      └── filters [type=bool, outer=(1,2)]
           └── (i + k) IS NOT NULL [type=bool, outer=(1,2)]

# --------------------------------------------------
# EliminateUnionAllLeft
# --------------------------------------------------

opt expect=EliminateUnionAllLeft
SELECT k FROM
  (SELECT k FROM b)
  UNION ALL
  (SELECT k FROM b WHERE i < 4 AND i > 10)
----
project
 ├── columns: k:11(int)
 ├── scan b
 │    ├── columns: b.k:1(int!null)
 │    └── key: (1)
 └── projections [outer=(1)]
      └── variable: b.k [type=int, outer=(1)]

# --------------------------------------------------
# EliminateUnionAllRight
# --------------------------------------------------

opt expect=EliminateUnionAllRight
SELECT k FROM
  (SELECT k FROM b WHERE i < 4 AND i > 10)
  UNION ALL
  (SELECT k FROM b)
----
project
 ├── columns: k:11(int)
 ├── scan b
 │    ├── columns: b.k:6(int!null)
 │    └── key: (6)
 └── projections [outer=(6)]
      └── variable: b.k [type=int, outer=(6)]

opt 
SELECT k FROM
  (SELECT k FROM b WHERE i < 4 AND i > 10)
  UNION ALL
  (SELECT k FROM b WHERE i < 4 AND i > 10)
----
values
 ├── columns: k:11(int)
 ├── cardinality: [0 - 0]
 ├── key: ()
 └── fd: ()-->(11)
