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

# --------------------------------------------------
# CommuteVarInequality
# --------------------------------------------------

# Put variables on both sides of comparison operator to avoid matching constant
# patterns.
opt expect=CommuteVarInequality
SELECT * FROM a WHERE 1+i<k AND k-1<=i AND i*i>k AND k/2>=i
----
select
 ├── columns: k:1(int!null) i:2(int!null) f:3(float) s:4(string) j:5(jsonb) d:6(date)
 ├── side-effects
 ├── key: (1)
 ├── fd: (1)-->(2-6)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) d:6(date)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-6)
 └── filters
      ├── k > (i + 1) [type=bool, outer=(1,2), constraints=(/1: (/NULL - ])]
      ├── i >= (k - 1) [type=bool, outer=(1,2), constraints=(/2: (/NULL - ])]
      ├── k < (i * i) [type=bool, outer=(1,2), constraints=(/1: (/NULL - ])]
      └── i <= (k / 2) [type=bool, outer=(1,2), side-effects, constraints=(/2: (/NULL - ])]

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

opt expect=CommuteConstInequality
SELECT * FROM a WHERE length('foo')+1<i+k AND length('bar')<=i*2
----
select
 ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) d:6(date)
 ├── key: (1)
 ├── fd: (1)-->(2-6)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) d:6(date)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-6)
 └── filters
      ├── (i + k) > 4 [type=bool, outer=(1,2)]
      └── (i * 2) >= 3 [type=bool, outer=(2)]

# Impure function should not be considered constant.
opt expect-not=CommuteConstInequality
SELECT * FROM a WHERE random()::int>a.i+a.i
----
select
 ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) d:6(date)
 ├── side-effects
 ├── key: (1)
 ├── fd: (1)-->(2-6)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) d:6(date)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-6)
 └── filters
      └── random()::INT8 > (i + i) [type=bool, outer=(2), side-effects]

# --------------------------------------------------
# NormalizeCmpPlusConst
# --------------------------------------------------
opt expect=NormalizeCmpPlusConst
SELECT *
FROM a
WHERE
    k+1 = 2 AND
    (f+f)+2 < 5 AND
    1::decimal+i >= length('foo') AND
    i+2+2 > 10 AND
    '1:00:00'::time + i::interval >= '2:00:00'::time
----
select
 ├── columns: k:1(int!null) i:2(int!null) f:3(float) s:4(string) j:5(jsonb) d:6(date)
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(1-6)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) d:6(date)
 │    ├── constraint: /1: [/1 - /1]
 │    ├── cardinality: [0 - 1]
 │    ├── key: ()
 │    └── fd: ()-->(1-6)
 └── filters
      ├── (i >= 2) AND (i > 6) [type=bool, outer=(2), constraints=(/2: [/7 - ]; tight)]
      ├── (f + f) < 3.0 [type=bool, outer=(3)]
      └── i::INTERVAL >= '01:00:00' [type=bool, outer=(2)]

# Try case that should not match pattern because Minus overload is not defined.
opt expect-not=NormalizeCmpPlusConst
SELECT * FROM a WHERE s::date + '02:00:00'::time = '2000-01-01T02:00:00'::timestamp
----
select
 ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) d:6(date)
 ├── key: (1)
 ├── fd: (1)-->(2-6)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) d:6(date)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-6)
 └── filters
      └── (s::DATE + '02:00:00') = '2000-01-01 02:00:00+00:00' [type=bool, outer=(4)]

# --------------------------------------------------
# NormalizeCmpMinusConst
# --------------------------------------------------
opt expect=NormalizeCmpMinusConst
SELECT *
FROM a
WHERE
    k-1 = 2 AND
    (f+f)-2 < 5 AND
    i-1::decimal >= length('foo') AND
    i-2-2 < 10 AND
    f+i::float-10.0 >= 100.0 AND
    d-'1w'::interval >= '2018-09-23'::date
----
select
 ├── columns: k:1(int!null) i:2(int!null) f:3(float) s:4(string) j:5(jsonb) d:6(date!null)
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(1-6)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) d:6(date)
 │    ├── constraint: /1: [/3 - /3]
 │    ├── cardinality: [0 - 1]
 │    ├── key: ()
 │    └── fd: ()-->(1-6)
 └── filters
      ├── (i >= 4) AND (i < 14) [type=bool, outer=(2), constraints=(/2: [/4 - /13]; tight)]
      ├── (f + f) < 7.0 [type=bool, outer=(3)]
      ├── (f + i::FLOAT8) >= 110.0 [type=bool, outer=(2,3)]
      └── d >= '2018-09-30' [type=bool, outer=(6), constraints=(/6: [/'2018-09-30' - ]; tight)]

# Try case that should not match pattern because Plus overload is not defined.
opt expect-not=NormalizeCmpMinusConst
SELECT * FROM a WHERE s::json - 1 = '[1]'::json
----
select
 ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) d:6(date)
 ├── key: (1)
 ├── fd: (1)-->(2-6)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) d:6(date)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-6)
 └── filters
      └── (s::JSONB - 1) = '[1]' [type=bool, outer=(4)]

# --------------------------------------------------
# NormalizeCmpConstMinus
# --------------------------------------------------
opt expect=NormalizeCmpConstMinus
SELECT *
FROM a
WHERE
    1-k = 2 AND
    2-(f+f) < 5 AND
    1::decimal-i <= length('foo') AND
    2-(2-i) > 10 AND
    10.0-(f+i::float) >= 100.0
----
select
 ├── columns: k:1(int!null) i:2(int!null) f:3(float) s:4(string) j:5(jsonb) d:6(date)
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(1-6)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) d:6(date)
 │    ├── constraint: /1: [/-1 - /-1]
 │    ├── cardinality: [0 - 1]
 │    ├── key: ()
 │    └── fd: ()-->(1-6)
 └── filters
      ├── (i >= -2) AND (i > 10) [type=bool, outer=(2), constraints=(/2: [/11 - ]; tight)]
      ├── (f + f) > -3.0 [type=bool, outer=(3)]
      └── (f + i::FLOAT8) <= -90.0 [type=bool, outer=(2,3)]

# Try case that should not match pattern because Minus overload is not defined.
opt expect-not=NormalizeCmpConstMinus
SELECT * FROM a WHERE '[1, 2]'::json - i = '[1]'
----
select
 ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) d:6(date)
 ├── key: (1)
 ├── fd: (1)-->(2-6)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) d:6(date)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-6)
 └── filters
      └── ('[1, 2]' - i) = '[1]' [type=bool, outer=(2)]

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

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

# --------------------------------------------------
# NormalizeTupleEquality, NormalizeNestedAnds
# --------------------------------------------------

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

# --------------------------------------------------
# FoldNullComparisonLeft, FoldNullComparisonRight
# --------------------------------------------------

# Use null::type to circumvent type checker constant folding.
opt expect=(FoldNullComparisonLeft,FoldNullComparisonRight)
SELECT *
FROM a
WHERE
    null::int = 1 OR 1 = null::int OR
    null::int <> 1 OR 1 <> null::int OR
    null::int > 1 OR 1 > null::int OR
    null::int >= 1 OR 1 >= null::int OR
    null::int < 1 OR 1 < null::int OR
    null::int <= 1 OR 1 <= null::int OR
    null::string LIKE 'foo' OR 'foo' LIKE null::string OR
    null::string NOT LIKE 'foo' OR 'foo' NOT LIKE null::string OR
    null::string ILIKE 'foo' OR 'foo' ILIKE null::string OR
    null::string NOT ILIKE 'foo' OR 'foo' NOT ILIKE null::string OR
    null::string SIMILAR TO 'foo' OR 'foo' SIMILAR TO null::string OR
    null::string NOT SIMILAR TO 'foo' OR 'foo' NOT SIMILAR TO null::string OR
    null::string ~ 'foo' OR 'foo' ~ null::string OR
    null::string !~ 'foo' OR 'foo' !~ null::string OR
    null::string ~* 'foo' OR 'foo' ~* null::string OR
    null::string !~* 'foo' OR 'foo' !~* null::string OR
    null::jsonb @> '"foo"' OR '"foo"' <@ null::jsonb OR
    null::jsonb ? 'foo' OR '{}' ? null::string OR
    null::jsonb ?| ARRAY['foo'] OR '{}' ?| null::string[] OR
    null::jsonb ?& ARRAY['foo'] OR '{}' ?& null::string[]
----
values
 ├── columns: k:1(int) i:2(int) f:3(float) s:4(string) j:5(jsonb) d:6(date)
 ├── cardinality: [0 - 0]
 ├── key: ()
 └── fd: ()-->(1-6)

# --------------------------------------------------
# FoldIsNull
# --------------------------------------------------
opt expect=FoldIsNull
SELECT NULL IS NULL AS r
----
values
 ├── columns: r:1(bool)
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (true,) [type=tuple{bool}]

# --------------------------------------------------
# FoldNonNullIsNull
# --------------------------------------------------
opt expect=FoldNonNullIsNull
SELECT 1 IS NULL AS r
----
values
 ├── columns: r:1(bool)
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (false,) [type=tuple{bool}]

opt expect=FoldNonNullIsNull
SELECT (1, 2, 3) IS NULL AS r
----
values
 ├── columns: r:1(bool)
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (false,) [type=tuple{bool}]

# --------------------------------------------------
# FoldIsNotNull
# --------------------------------------------------
opt expect=FoldIsNotNull
SELECT NULL IS NOT NULL AS r, NULL IS NOT TRUE AS s
----
values
 ├── columns: r:1(bool) s:2(bool)
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1,2)
 └── (false, true) [type=tuple{bool, bool}]

# --------------------------------------------------
# FoldNonNullIsNotNull
# --------------------------------------------------

# We could (but do not currently) infer that k IS NOT NULL is always True given
# that k is declared NOT NULL.
opt expect=FoldNonNullIsNotNull
SELECT 1 IS NOT NULL AS r, k IS NOT NULL AS s, i IS NOT NULL AS t FROM a
----
project
 ├── columns: r:7(bool!null) s:8(bool) t:9(bool)
 ├── fd: ()-->(7)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int)
 │    ├── key: (1)
 │    └── fd: (1)-->(2)
 └── projections
      ├── true [type=bool]
      ├── k IS NOT NULL [type=bool, outer=(1)]
      └── i IS NOT NULL [type=bool, outer=(2)]

opt expect=FoldNonNullIsNotNull
SELECT (1, 2, 3) IS NOT NULL AS r
----
values
 ├── columns: r:1(bool)
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (true,) [type=tuple{bool}]

# --------------------------------------------------
# CommuteNullIs
# --------------------------------------------------
opt expect=CommuteNullIs
SELECT NULL IS NOT TRUE AS r, NULL IS TRUE AS s
----
values
 ├── columns: r:1(bool) s:2(bool)
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1,2)
 └── (true, false) [type=tuple{bool, bool}]
