exec-ddl
CREATE TABLE t (a INT, b BOOL, c STRING)
----
TABLE t
 ├── a int
 ├── b bool
 ├── c string
 ├── rowid int not null (hidden)
 └── INDEX primary
      └── rowid int not null (hidden)

opt
SELECT * FROM t WHERE a = NULL
----
values
 ├── columns: a:1(int) b:2(bool) c:3(string)
 ├── cardinality: [0 - 0]
 ├── key: ()
 ├── fd: ()-->(1-3)
 └── prune: (1-3)

opt
SELECT * FROM t WHERE a < NULL
----
values
 ├── columns: a:1(int) b:2(bool) c:3(string)
 ├── cardinality: [0 - 0]
 ├── key: ()
 ├── fd: ()-->(1-3)
 └── prune: (1-3)

opt
SELECT * FROM t WHERE a IS NULL
----
select
 ├── columns: a:1(int) b:2(bool) c:3(string)
 ├── fd: ()-->(1)
 ├── prune: (2,3)
 ├── scan t
 │    ├── columns: a:1(int) b:2(bool) c:3(string)
 │    └── prune: (1-3)
 └── filters [type=bool, outer=(1), constraints=(/1: [/NULL - /NULL]; tight), fd=()-->(1)]
      └── is [type=bool, outer=(1), constraints=(/1: [/NULL - /NULL]; tight)]
           ├── variable: a [type=int, outer=(1)]
           └── null [type=unknown]

opt
SELECT * FROM t WHERE a IS NOT NULL
----
select
 ├── columns: a:1(int!null) b:2(bool) c:3(string)
 ├── prune: (2,3)
 ├── scan t
 │    ├── columns: a:1(int) b:2(bool) c:3(string)
 │    └── prune: (1-3)
 └── filters [type=bool, outer=(1), constraints=(/1: (/NULL - ]; tight)]
      └── is-not [type=bool, outer=(1), constraints=(/1: (/NULL - ]; tight)]
           ├── variable: a [type=int, outer=(1)]
           └── null [type=unknown]

opt
SELECT * FROM t WHERE b IS NULL AND c IS NULL
----
select
 ├── columns: a:1(int) b:2(bool) c:3(string)
 ├── fd: ()-->(2,3)
 ├── prune: (1)
 ├── scan t
 │    ├── columns: a:1(int) b:2(bool) c:3(string)
 │    └── prune: (1-3)
 └── filters [type=bool, outer=(2,3), constraints=(/2: [/NULL - /NULL]; /3: [/NULL - /NULL]; tight), fd=()-->(2,3)]
      ├── is [type=bool, outer=(2), constraints=(/2: [/NULL - /NULL]; tight)]
      │    ├── variable: b [type=bool, outer=(2)]
      │    └── null [type=unknown]
      └── is [type=bool, outer=(3), constraints=(/3: [/NULL - /NULL]; tight)]
           ├── variable: c [type=string, outer=(3)]
           └── null [type=unknown]

opt
SELECT * FROM t WHERE b IS NOT NULL AND c IS NOT NULL
----
select
 ├── columns: a:1(int) b:2(bool!null) c:3(string!null)
 ├── prune: (1)
 ├── scan t
 │    ├── columns: a:1(int) b:2(bool) c:3(string)
 │    └── prune: (1-3)
 └── filters [type=bool, outer=(2,3), constraints=(/2: (/NULL - ]; /3: (/NULL - ]; tight)]
      ├── is-not [type=bool, outer=(2), constraints=(/2: (/NULL - ]; tight)]
      │    ├── variable: b [type=bool, outer=(2)]
      │    └── null [type=unknown]
      └── is-not [type=bool, outer=(3), constraints=(/3: (/NULL - ]; tight)]
           ├── variable: c [type=string, outer=(3)]
           └── null [type=unknown]

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

# Test that we get a not-NULL constraint on x.
opt
SELECT * FROM xy WHERE x > abs(y)
----
select
 ├── columns: x:1(int!null) y:2(int)
 ├── scan xy
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters [type=bool, outer=(1,2), constraints=(/1: (/NULL - ])]
      └── gt [type=bool, outer=(1,2), constraints=(/1: (/NULL - ])]
           ├── variable: x [type=int, outer=(1)]
           └── function: abs [type=int, outer=(2)]
                └── variable: y [type=int, outer=(2)]

# Test that we get a not-NULL constraint on x.
opt
SELECT * FROM xy WHERE sin(x::float)::int < x
----
select
 ├── columns: x:1(int!null) y:2(int)
 ├── prune: (2)
 ├── scan xy
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters [type=bool, outer=(1), constraints=(/1: (/NULL - ])]
      └── gt [type=bool, outer=(1), constraints=(/1: (/NULL - ])]
           ├── variable: x [type=int, outer=(1)]
           └── cast: INT [type=int, outer=(1)]
                └── function: sin [type=float, outer=(1)]
                     └── cast: FLOAT8 [type=float, outer=(1)]
                          └── variable: x [type=int, outer=(1)]

# Test that we get a not-NULL constraint on x and y.
opt
SELECT * FROM xy WHERE x > y
----
select
 ├── columns: x:1(int!null) y:2(int!null)
 ├── scan xy
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters [type=bool, outer=(1,2), constraints=(/1: (/NULL - ]; /2: (/NULL - ])]
      └── gt [type=bool, outer=(1,2), constraints=(/1: (/NULL - ]; /2: (/NULL - ])]
           ├── variable: x [type=int, outer=(1)]
           └── variable: y [type=int, outer=(2)]

# Test that we get a not-NULL constraint on x and y.
opt
SELECT * FROM xy WHERE x = y
----
select
 ├── columns: x:1(int!null) y:2(int!null)
 ├── fd: (1)==(2), (2)==(1)
 ├── scan xy
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters [type=bool, outer=(1,2), constraints=(/1: (/NULL - ]; /2: (/NULL - ]), fd=(1)==(2), (2)==(1)]
      └── eq [type=bool, outer=(1,2), constraints=(/1: (/NULL - ]; /2: (/NULL - ])]
           ├── variable: x [type=int, outer=(1)]
           └── variable: y [type=int, outer=(2)]
