exec-ddl
CREATE TABLE a (
  k INT PRIMARY KEY,
  i INT,
  f FLOAT,
  s STRING,
  j JSON,
  d DATE,
  ts TIMESTAMP,
  tz TIMESTAMPTZ,
  intv INTERVAL
)
----

exec-ddl
CREATE TABLE geom_geog (
  geom GEOMETRY,
  geog GEOGRAPHY,
  val FLOAT
)
----

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

# Put variables on both sides of comparison operator to avoid matching constant
# patterns.
norm 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!null i:2!null f:3 s:4 j:5 d:6 ts:7 tz:8 intv:9
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-9)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5 d:6 ts:7 tz:8 intv:9
 │    ├── key: (1)
 │    └── fd: (1)-->(2-9)
 └── filters
      ├── k:1 > (i:2 + 1) [outer=(1,2), immutable, constraints=(/1: (/NULL - ])]
      ├── i:2 >= (k:1 - 1) [outer=(1,2), immutable, constraints=(/2: (/NULL - ])]
      ├── k:1 < (i:2 * i:2) [outer=(1,2), immutable, constraints=(/1: (/NULL - ])]
      └── i:2 <= (k:1 / 2) [outer=(1,2), constraints=(/2: (/NULL - ])]

# --------------------------------------------------
# CommuteConstInequality
# --------------------------------------------------
norm 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!null i:2!null f:3 s:4!null j:5 d:6 ts:7 tz:8 intv:9
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-9)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5 d:6 ts:7 tz:8 intv:9
 │    ├── key: (1)
 │    └── fd: (1)-->(2-9)
 └── filters
      ├── (i:2 + k:1) > 6 [outer=(1,2), immutable]
      ├── (i:2 * 2) >= 8.3333333333333333333 [outer=(2), immutable]
      ├── i:2 < 5 [outer=(2), constraints=(/2: (/NULL - /4]; tight)]
      └── s:4 <= 'foo' [outer=(4), constraints=(/4: (/NULL - /'foo']; tight)]

norm expect=CommuteConstInequality
SELECT * FROM a WHERE length('foo')+1<i+k AND length('bar')<=i*2
----
select
 ├── columns: k:1!null i:2 f:3 s:4 j:5 d:6 ts:7 tz:8 intv:9
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-9)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5 d:6 ts:7 tz:8 intv:9
 │    ├── key: (1)
 │    └── fd: (1)-->(2-9)
 └── filters
      ├── (i:2 + k:1) > 4 [outer=(1,2), immutable]
      └── (i:2 * 2) >= 3 [outer=(2), immutable]

# Impure function should not be considered constant.
norm expect-not=CommuteConstInequality
SELECT * FROM a WHERE random()::int>a.i+a.i
----
select
 ├── columns: k:1!null i:2 f:3 s:4 j:5 d:6 ts:7 tz:8 intv:9
 ├── volatile
 ├── key: (1)
 ├── fd: (1)-->(2-9)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5 d:6 ts:7 tz:8 intv:9
 │    ├── key: (1)
 │    └── fd: (1)-->(2-9)
 └── filters
      └── random()::INT8 > (i:2 + i:2) [outer=(2), volatile]

# --------------------------------------------------
# NormalizeCmpPlusConst
# --------------------------------------------------
norm expect=NormalizeCmpPlusConst
SELECT *
FROM a
WHERE
    k+1 = 2 AND
    (f+f)+2 < 5 AND
    i+2+2 > 10 AND
    ts+'1 day'::INTERVAL > '2020-03-21 12:00:00'::TIMESTAMP AND
    tz+'1 day'::INTERVAL > '2020-03-21 12:00:00+00'::TIMESTAMPTZ
----
select
 ├── columns: k:1!null i:2!null f:3 s:4 j:5 d:6 ts:7!null tz:8!null intv:9
 ├── cardinality: [0 - 1]
 ├── immutable
 ├── key: ()
 ├── fd: ()-->(1-9)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5 d:6 ts:7 tz:8 intv:9
 │    ├── key: (1)
 │    └── fd: (1)-->(2-9)
 └── filters
      ├── k:1 = 1 [outer=(1), constraints=(/1: [/1 - /1]; tight), fd=()-->(1)]
      ├── (f:3 + f:3) < 3.0 [outer=(3), immutable]
      ├── i:2 > 6 [outer=(2), constraints=(/2: [/7 - ]; tight)]
      ├── ts:7 > '2020-03-20 12:00:00' [outer=(7), constraints=(/7: [/'2020-03-20 12:00:00.000001' - ]; tight)]
      └── tz:8 > '2020-03-20 12:00:00+00' [outer=(8), constraints=(/8: [/'2020-03-20 12:00:00.000001+00' - ]; tight)]

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

# Regression test for #89024 - don't attempt to evaluate op for NULL values.
norm expect-not=(NormalizeCmpPlusConst,NormalizeCmpMinusConst,NormalizeCmpConstMinus)
SELECT 1
WHERE (parse_time(e'D~<\x0bjN"@y')::TIME - '50 years'::INTERVAL)::TIME <= NULL::TIME
----
values
 ├── columns: "?column?":1!null
 ├── cardinality: [0 - 0]
 ├── key: ()
 └── fd: ()-->(1)

norm expect-not=(NormalizeCmpPlusConst,NormalizeCmpMinusConst,NormalizeCmpConstMinus)
SELECT 1 WHERE 1 - 10 <= NULL::INT
----
values
 ├── columns: "?column?":1!null
 ├── cardinality: [0 - 0]
 ├── key: ()
 └── fd: ()-->(1)

# The rule does not currently apply when it would subtract two TIMESTAMPs.
# TODO(mgartner): Determine if this case is safe to allow.
norm expect-not=NormalizeCmpPlusConst
SELECT intv + '2020-01-01 00:00:00'::TIMESTAMP > '2020-03-21 12:00:00'::TIMESTAMP,
       intv + '2020-01-01 00:00:00+00'::TIMESTAMPTZ > '2020-03-21 12:00:00+00'::TIMESTAMPTZ
FROM a
----
project
 ├── columns: "?column?":12 "?column?":13
 ├── stable
 ├── scan a
 │    └── columns: intv:9
 └── projections
      ├── (intv:9 + '2020-01-01 00:00:00') > '2020-03-21 12:00:00' [as="?column?":12, outer=(9), immutable]
      └── (intv:9 + '2020-01-01 00:00:00+00') > '2020-03-21 12:00:00+00' [as="?column?":13, outer=(9), stable]

# Regression test for #90053. This rule should not apply when the generated Plus
# or Minus can overflow or underflow without error.
norm expect-not=(NormalizeCmpPlusConst,NormalizeCmpMinusConst,NormalizeCmpConstMinus)
SELECT '00:01:40.01+09:00:00' < (col::TIMETZ + '-83 years -1 mons -38 days -10:32:23.707137')
FROM (VALUES ('03:16:01.252182+01:49:00')) v(col);
----
values
 ├── columns: "?column?":2!null
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(2)
 └── (true,)

# Regression test for #94264. This rule should not apply when the argument to a
# Plus or a Minus is NULL.
norm expect-not=(NormalizeCmpPlusConst)
SELECT (9223372036854775807+436256318)+2 < (CASE WHEN false THEN -1 END)
----
values
 ├── columns: "?column?":1
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (NULL,)

# --------------------------------------------------
# NormalizeCmpMinusConst
# --------------------------------------------------
norm expect=NormalizeCmpMinusConst
SELECT *
FROM a
WHERE
    k-1 = 2 AND
    (f+f)-2 < 5 AND
    i-2-2 < 10 AND
    f+i::float-10.0 >= 100.0 AND
    ts-'1 day'::INTERVAL > '2020-03-21 12:00:00'::TIMESTAMP AND
    tz-'1 day'::INTERVAL > '2020-03-21 12:00:00+00'::TIMESTAMPTZ
----
select
 ├── columns: k:1!null i:2!null f:3 s:4 j:5 d:6 ts:7!null tz:8!null intv:9
 ├── cardinality: [0 - 1]
 ├── immutable
 ├── key: ()
 ├── fd: ()-->(1-9)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5 d:6 ts:7 tz:8 intv:9
 │    ├── key: (1)
 │    └── fd: (1)-->(2-9)
 └── filters
      ├── k:1 = 3 [outer=(1), constraints=(/1: [/3 - /3]; tight), fd=()-->(1)]
      ├── (f:3 + f:3) < 7.0 [outer=(3), immutable]
      ├── i:2 < 14 [outer=(2), constraints=(/2: (/NULL - /13]; tight)]
      ├── (f:3 + i:2::FLOAT8) >= 110.0 [outer=(2,3), immutable]
      ├── ts:7 > '2020-03-22 12:00:00' [outer=(7), constraints=(/7: [/'2020-03-22 12:00:00.000001' - ]; tight)]
      └── tz:8 > '2020-03-22 12:00:00+00' [outer=(8), constraints=(/8: [/'2020-03-22 12:00:00.000001+00' - ]; tight)]

# Try case that should not match pattern because Plus overload is not defined.
norm expect-not=NormalizeCmpMinusConst
SELECT * FROM a WHERE s::json - 1 = '[1]'::json
----
select
 ├── columns: k:1!null i:2 f:3 s:4 j:5 d:6 ts:7 tz:8 intv:9
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-9)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5 d:6 ts:7 tz:8 intv:9
 │    ├── key: (1)
 │    └── fd: (1)-->(2-9)
 └── filters
      └── (s:4::JSONB - 1) = '[1]' [outer=(4), immutable]

# Regression test for #90053. This rule should not apply when the generated Plus
# or Minus can overflow or underflow without error.
norm expect-not=(NormalizeCmpPlusConst,NormalizeCmpMinusConst,NormalizeCmpConstMinus)
SELECT (col::TIMETZ - '83 years -1 mons -38 days -10:32:23.707137') > '00:01:40.01+09:00:00'
FROM (VALUES ('03:16:01.252182+01:49:00')) v(col);
----
values
 ├── columns: "?column?":2!null
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(2)
 └── (true,)

# Regression test for #94264. This rule should not apply when the argument to a
# Plus or a Minus is NULL.
norm expect-not=(NormalizeCmpMinusConst)
SELECT (9223372036854775807+436256318)-2 < (CASE WHEN false THEN -1 END)
----
values
 ├── columns: "?column?":1
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (NULL,)


# --------------------------------------------------
# NormalizeCmpConstMinus
# --------------------------------------------------
norm 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 AND
    '2020-03-21 12:00:00'::TIMESTAMP-ts > '1 day'::INTERVAL AND
    '2020-03-21 12:00:00+00'::TIMESTAMPTZ-tz > '1 day'::INTERVAL
----
select
 ├── columns: k:1!null i:2!null f:3 s:4 j:5 d:6 ts:7!null tz:8!null intv:9
 ├── cardinality: [0 - 1]
 ├── immutable
 ├── key: ()
 ├── fd: ()-->(1-9)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5 d:6 ts:7 tz:8 intv:9
 │    ├── key: (1)
 │    └── fd: (1)-->(2-9)
 └── filters
      ├── (i:2 >= -2) AND (i:2 > 10) [outer=(2), constraints=(/2: [/11 - ]; tight)]
      ├── k:1 = -1 [outer=(1), constraints=(/1: [/-1 - /-1]; tight), fd=()-->(1)]
      ├── (f:3 + f:3) > -3.0 [outer=(3), immutable]
      ├── (f:3 + i:2::FLOAT8) <= -90.0 [outer=(2,3), immutable]
      ├── ts:7 < '2020-03-20 12:00:00' [outer=(7), constraints=(/7: (/NULL - /'2020-03-20 11:59:59.999999']; tight)]
      └── tz:8 < '2020-03-20 12:00:00+00' [outer=(8), constraints=(/8: (/NULL - /'2020-03-20 11:59:59.999999+00']; tight)]

# Try case that should not match pattern because Minus overload is not defined.
norm expect-not=NormalizeCmpConstMinus
SELECT * FROM a WHERE '[1, 2]'::json - i = '[1]'
----
select
 ├── columns: k:1!null i:2 f:3 s:4 j:5 d:6 ts:7 tz:8 intv:9
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-9)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5 d:6 ts:7 tz:8 intv:9
 │    ├── key: (1)
 │    └── fd: (1)-->(2-9)
 └── filters
      └── ('[1, 2]' - i:2) = '[1]' [outer=(2), immutable]

# The rule does not currently apply when it would subtract two TIMESTAMPs.
# TODO(mgartner): Determine if this case is safe to allow.
norm expect-not=NormalizeCmpConstMinus
SELECT '2020-01-01 00:00:00'::TIMESTAMP - intv > '2020-03-21 12:00:00'::TIMESTAMP,
       '2020-01-01 00:00:00+00'::TIMESTAMPTZ - intv > '2020-03-21 12:00:00'::TIMESTAMPTZ
FROM a
----
project
 ├── columns: "?column?":12 "?column?":13
 ├── stable
 ├── scan a
 │    └── columns: intv:9
 └── projections
      ├── ('2020-01-01 00:00:00' - intv:9) > '2020-03-21 12:00:00' [as="?column?":12, outer=(9), immutable]
      └── ('2020-01-01 00:00:00+00' - intv:9) > '2020-03-21 12:00:00+00' [as="?column?":13, outer=(9), stable]

# Regression test for #90053. This rule should not apply when the generated Plus
# or Minus can overflow or underflow without error.
norm expect-not=NormalizeCmpConstMinus
SELECT * FROM a WHERE '2022-01-01'::date - s::time >= '2022-01-01 1:00:00'::timestamp
----
select
 ├── columns: k:1!null i:2 f:3 s:4 j:5 d:6 ts:7 tz:8 intv:9
 ├── stable
 ├── key: (1)
 ├── fd: (1)-->(2-9)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5 d:6 ts:7 tz:8 intv:9
 │    ├── key: (1)
 │    └── fd: (1)-->(2-9)
 └── filters
      └── ('2022-01-01' - s:4::TIME) >= '2022-01-01 01:00:00' [outer=(4), stable]

# Regression test for #94264. This rule should not apply when the argument to a
# Plus or a Minus is NULL.
norm expect-not=(NormalizeCmpConstMinus)
SELECT 2-(9223372036854775807+436256318) < (CASE WHEN false THEN -1 END)
----
values
 ├── columns: "?column?":1
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (NULL,)

# --------------------------------------------------
# NormalizeTupleEquality
# --------------------------------------------------
norm expect=NormalizeTupleEquality
SELECT * FROM a WHERE (i, f, s) = (1, 3.5, 'foo')
----
select
 ├── columns: k:1!null i:2!null f:3!null s:4!null j:5 d:6 ts:7 tz:8 intv:9
 ├── key: (1)
 ├── fd: ()-->(2-4), (1)-->(5-9)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5 d:6 ts:7 tz:8 intv:9
 │    ├── key: (1)
 │    └── fd: (1)-->(2-9)
 └── filters
      ├── i:2 = 1 [outer=(2), constraints=(/2: [/1 - /1]; tight), fd=()-->(2)]
      ├── f:3 = 3.5 [outer=(3), constraints=(/3: [/3.5 - /3.5]; tight), fd=()-->(3)]
      └── s:4 = 'foo' [outer=(4), constraints=(/4: [/'foo' - /'foo']; tight), fd=()-->(4)]

# Empty tuples.
norm expect=NormalizeTupleEquality
SELECT * FROM a WHERE () = ()
----
scan a
 ├── columns: k:1!null i:2 f:3 s:4 j:5 d:6 ts:7 tz:8 intv:9
 ├── key: (1)
 └── fd: (1)-->(2-9)

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

# Nested tuples.
norm expect=(NormalizeTupleEquality,NormalizeNestedAnds)
SELECT * FROM a WHERE (1, (2, 'foo')) = (k, (i, s))
----
select
 ├── columns: k:1!null i:2!null f:3 s:4!null j:5 d:6 ts:7 tz:8 intv:9
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(1-9)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5 d:6 ts:7 tz:8 intv:9
 │    ├── key: (1)
 │    └── fd: (1)-->(2-9)
 └── filters
      ├── k:1 = 1 [outer=(1), constraints=(/1: [/1 - /1]; tight), fd=()-->(1)]
      ├── i:2 = 2 [outer=(2), constraints=(/2: [/2 - /2]; tight), fd=()-->(2)]
      └── s:4 = 'foo' [outer=(4), constraints=(/4: [/'foo' - /'foo']; tight), fd=()-->(4)]

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

# Use null::type to circumvent type checker constant folding.
norm 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::string[] && ARRAY['foo'] OR ARRAY['foo'] && null::string[] OR
    null::jsonb @> '"foo"' OR '"foo"' <@ null::jsonb 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!null i:2!null f:3!null s:4!null j:5!null d:6!null ts:7!null tz:8!null intv:9!null
 ├── cardinality: [0 - 0]
 ├── key: ()
 └── fd: ()-->(1-9)

# --------------------------------------------------
# FoldIsNull
# --------------------------------------------------
norm expect=FoldIsNull
SELECT NULL IS NULL AS r
----
values
 ├── columns: r:1!null
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (true,)

# --------------------------------------------------
# FoldNonNullIsNull
# --------------------------------------------------
norm expect=FoldNonNullIsNull
SELECT 1 IS NULL AS r
----
values
 ├── columns: r:1!null
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (false,)

norm expect=FoldNonNullIsNull
SELECT (1, 2, 3) IS NOT DISTINCT FROM NULL AS r
----
values
 ├── columns: r:1!null
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (false,)

norm expect=FoldNonNullIsNull
SELECT (1, NULL) IS NOT DISTINCT FROM NULL AS r
----
values
 ├── columns: r:1!null
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (false,)

norm expect=FoldNonNullIsNull
SELECT (NULL, NULL) IS NOT DISTINCT FROM NULL AS r
----
values
 ├── columns: r:1!null
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (false,)

norm expect=FoldNonNullIsNull
SELECT (i,f) IS NOT DISTINCT FROM NULL FROM a
----
project
 ├── columns: "?column?":12!null
 ├── fd: ()-->(12)
 ├── scan a
 └── projections
      └── false [as="?column?":12]

norm expect=FoldNonNullIsNull
SELECT ARRAY[k,i] IS NOT DISTINCT FROM NULL FROM a
----
project
 ├── columns: "?column?":12!null
 ├── fd: ()-->(12)
 ├── scan a
 └── projections
      └── false [as="?column?":12]

norm expect-not=FoldNonNullIsNull
SELECT (i,f) IS NULL FROM a
----
project
 ├── columns: "?column?":12!null
 ├── scan a
 │    └── columns: i:2 f:3
 └── projections
      └── (i:2, f:3) IS NULL [as="?column?":12, outer=(2,3)]

# --------------------------------------------------
# FoldNullTupleIsTupleNull
# --------------------------------------------------
norm expect=FoldNullTupleIsTupleNull
SELECT (NULL, NULL) IS NULL AS r
----
values
 ├── columns: r:1!null
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (true,)

norm expect=FoldNullTupleIsTupleNull
SELECT () IS NULL AS r
----
values
 ├── columns: r:1!null
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (true,)

norm expect-not=FoldNullTupleIsTupleNull
SELECT (k, NULL) IS NULL FROM a
----
project
 ├── columns: "?column?":12!null
 ├── scan a
 │    ├── columns: k:1!null
 │    └── key: (1)
 └── projections
      └── (k:1, NULL) IS NULL [as="?column?":12, outer=(1)]

# --------------------------------------------------
# FoldNonNullTupleIsTupleNull
# --------------------------------------------------
norm expect=FoldNonNullTupleIsTupleNull
SELECT (1, 2) IS NULL AS r
----
values
 ├── columns: r:1!null
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (false,)

norm expect=FoldNonNullTupleIsTupleNull
SELECT (1, NULL) IS NULL AS r
----
values
 ├── columns: r:1!null
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (false,)

norm expect=FoldNonNullTupleIsTupleNull
SELECT (1, k) IS NULL FROM a
----
project
 ├── columns: "?column?":12!null
 ├── fd: ()-->(12)
 ├── scan a
 └── projections
      └── false [as="?column?":12]

norm expect=FoldNonNullTupleIsTupleNull
SELECT ((NULL, NULL), NULL) IS NULL AS r
----
values
 ├── columns: r:1!null
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (false,)

norm expect=FoldNonNullTupleIsTupleNull
SELECT (ARRAY[NULL, NULL], NULL) IS NULL AS r
----
values
 ├── columns: r:1!null
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (false,)

norm expect-not=FoldNonNullTupleIsTupleNull
SELECT (k, NULL) IS NULL FROM a
----
project
 ├── columns: "?column?":12!null
 ├── scan a
 │    ├── columns: k:1!null
 │    └── key: (1)
 └── projections
      └── (k:1, NULL) IS NULL [as="?column?":12, outer=(1)]

# --------------------------------------------------
# FoldIsNotNull
# --------------------------------------------------
norm expect=FoldIsNotNull
SELECT NULL IS NOT NULL AS r, NULL IS NOT TRUE AS s
----
values
 ├── columns: r:1!null s:2!null
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1,2)
 └── (false, true)

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

# We could (but do not currently) infer that k IS NOT NULL is always True given
# that k is declared NOT NULL.
norm 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:12!null s:13!null t:14!null
 ├── fd: ()-->(12)
 ├── scan a
 │    ├── columns: k:1!null i:2
 │    ├── key: (1)
 │    └── fd: (1)-->(2)
 └── projections
      ├── true [as=r:12]
      ├── k:1 IS NOT NULL [as=s:13, outer=(1)]
      └── i:2 IS NOT NULL [as=t:14, outer=(2)]

norm expect=FoldNonNullIsNotNull
SELECT (1, 2, 3) IS DISTINCT FROM NULL AS r
----
values
 ├── columns: r:1!null
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (true,)

norm expect=FoldNonNullIsNotNull
SELECT (1, NULL) IS DISTINCT FROM NULL AS r
----
values
 ├── columns: r:1!null
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (true,)

norm expect=FoldNonNullIsNotNull
SELECT (1, NULL) IS DISTINCT FROM NULL AS r
----
values
 ├── columns: r:1!null
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (true,)

norm expect=FoldNonNullIsNotNull
SELECT (i,f) IS DISTINCT FROM NULL FROM a
----
project
 ├── columns: "?column?":12!null
 ├── fd: ()-->(12)
 ├── scan a
 └── projections
      └── true [as="?column?":12]

norm expect=FoldNonNullIsNotNull
SELECT ARRAY[k,i] IS DISTINCT FROM NULL FROM a
----
project
 ├── columns: "?column?":12!null
 ├── fd: ()-->(12)
 ├── scan a
 └── projections
      └── true [as="?column?":12]

norm expect-not=FoldNonNullIsNotNull
SELECT (i,f) IS NOT NULL FROM a
----
project
 ├── columns: "?column?":12!null
 ├── scan a
 │    └── columns: i:2 f:3
 └── projections
      └── (i:2, f:3) IS NOT NULL [as="?column?":12, outer=(2,3)]

# --------------------------------------------------
# FoldNonNullTupleIsTupleNotNull
# --------------------------------------------------
norm expect=FoldNonNullTupleIsTupleNotNull
SELECT (1, 1) IS NOT NULL AS r
----
values
 ├── columns: r:1!null
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (true,)

norm expect=FoldNonNullTupleIsTupleNotNull
SELECT (1, (NULL, NULL)) IS NOT NULL AS r
----
values
 ├── columns: r:1!null
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (true,)

norm expect=FoldNonNullTupleIsTupleNotNull
SELECT (1, ARRAY[NULL, NULL]) IS NOT NULL AS r
----
values
 ├── columns: r:1!null
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (true,)

norm expect=FoldNonNullTupleIsTupleNotNull
SELECT () IS NOT NULL AS r
----
values
 ├── columns: r:1!null
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (true,)

norm expect-not=FoldNonNullTupleIsTupleNotNull
SELECT (1, k) IS NOT NULL FROM a
----
project
 ├── columns: "?column?":12!null
 ├── scan a
 │    ├── columns: k:1!null
 │    └── key: (1)
 └── projections
      └── (1, k:1) IS NOT NULL [as="?column?":12, outer=(1)]

# --------------------------------------------------
# FoldNullTupleIsTupleNotNull
# --------------------------------------------------
norm expect=FoldNullTupleIsTupleNotNull
SELECT (1, NULL) IS NOT NULL AS r
----
values
 ├── columns: r:1!null
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (false,)

norm expect=FoldNullTupleIsTupleNotNull
SELECT (k, NULL) IS NOT NULL FROM a
----
project
 ├── columns: "?column?":12!null
 ├── fd: ()-->(12)
 ├── scan a
 └── projections
      └── false [as="?column?":12]

norm expect-not=FoldNonNullTupleIsTupleNotNull
SELECT (1, k) IS NOT NULL FROM a
----
project
 ├── columns: "?column?":12!null
 ├── scan a
 │    ├── columns: k:1!null
 │    └── key: (1)
 └── projections
      └── (1, k:1) IS NOT NULL [as="?column?":12, outer=(1)]

# --------------------------------------------------
# CommuteNullIs
# --------------------------------------------------
norm expect=CommuteNullIs
SELECT NULL IS NOT TRUE AS r, NULL IS TRUE AS s
----
values
 ├── columns: r:1!null s:2!null
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1,2)
 └── (true, false)

# --------------------------------------------------
# NormalizeCmpTimeZoneFunction
# --------------------------------------------------
exec-ddl
CREATE TABLE t (ts TIMESTAMP, tz TIMESTAMPTZ)
----

norm expect=NormalizeCmpTimeZoneFunction
SELECT timezone('America/Denver', ts) >= '2020-06-01 12:35:55-07' FROM t
----
project
 ├── columns: "?column?":6
 ├── scan t
 │    └── columns: ts:1
 └── projections
      └── ts:1 >= '2020-06-01 13:35:55' [as="?column?":6, outer=(1)]

# Apply after commuting the inequality.
norm expect=NormalizeCmpTimeZoneFunction
SELECT '2020-06-01 12:35:55-07' >= timezone('America/Denver', ts)  FROM t
----
project
 ├── columns: "?column?":6
 ├── scan t
 │    └── columns: ts:1
 └── projections
      └── ts:1 <= '2020-06-01 13:35:55' [as="?column?":6, outer=(1)]

# Don't normalize when the right-hand-side is not a constant.
norm expect-not=NormalizeCmpTimeZoneFunction
SELECT timezone('America/Denver', ts) >= tz FROM t
----
project
 ├── columns: "?column?":6
 ├── immutable
 ├── scan t
 │    └── columns: ts:1 tz:2
 └── projections
      └── tz:2 <= timezone('America/Denver', ts:1) [as="?column?":6, outer=(1,2), immutable]

# Don't normalize when the timezone() arguments are constants.
norm expect-not=NormalizeCmpTimeZoneFunction
SELECT timezone('America/Denver', '2020-06-01 12:35:55'::TIMESTAMP) >= tz FROM t
----
project
 ├── columns: "?column?":6
 ├── scan t
 │    └── columns: tz:2
 └── projections
      └── tz:2 <= '2020-06-01 18:35:55+00' [as="?column?":6, outer=(2)]

# --------------------------------------------------
# NormalizeCmpTimeZoneFunctionTZ
# --------------------------------------------------
norm expect=NormalizeCmpTimeZoneFunctionTZ
SELECT timezone('America/Denver', tz) >= '2020-06-01 12:35:55' FROM t
----
project
 ├── columns: "?column?":6
 ├── scan t
 │    └── columns: tz:2
 └── projections
      └── tz:2 >= '2020-06-01 18:35:55+00' [as="?column?":6, outer=(2)]

# Apply after commuting the inequality.
norm expect=NormalizeCmpTimeZoneFunctionTZ
SELECT '2020-06-01 12:35:55' >= timezone('America/Denver', tz)  FROM t
----
project
 ├── columns: "?column?":6
 ├── scan t
 │    └── columns: tz:2
 └── projections
      └── tz:2 <= '2020-06-01 18:35:55+00' [as="?column?":6, outer=(2)]

# Don't normalize when the right-hand-side is not a constant.
norm expect-not=NormalizeCmpTimeZoneFunctionTZ
SELECT timezone('America/Denver', tz) >= ts FROM t
----
project
 ├── columns: "?column?":6
 ├── immutable
 ├── scan t
 │    └── columns: ts:1 tz:2
 └── projections
      └── ts:1 <= timezone('America/Denver', tz:2) [as="?column?":6, outer=(1,2), immutable]

# Don't normalize when the timezone() arguments are constants.
norm expect-not=NormalizeCmpTimeZoneFunctionTZ
SELECT timezone('America/Denver', '2020-06-01 12:35:55-07'::TIMESTAMPTZ) >= ts FROM t
----
project
 ├── columns: "?column?":6
 ├── scan t
 │    └── columns: ts:1
 └── projections
      └── ts:1 <= '2020-06-01 13:35:55' [as="?column?":6, outer=(1)]

# --------------------------------------------------
# FoldEqTrue + FoldEqFalse
# --------------------------------------------------

exec-ddl
CREATE TABLE tbl (k INT PRIMARY KEY, b BOOL)
----

norm expect=FoldEqTrue
SELECT * FROM tbl WHERE b=TRUE
----
select
 ├── columns: k:1!null b:2!null
 ├── key: (1)
 ├── fd: ()-->(2)
 ├── scan tbl
 │    ├── columns: k:1!null b:2
 │    ├── key: (1)
 │    └── fd: (1)-->(2)
 └── filters
      └── b:2 [outer=(2), constraints=(/2: [/true - /true]; tight), fd=()-->(2)]

norm expect=FoldEqTrue
SELECT b=TRUE FROM tbl
----
project
 ├── columns: "?column?":5
 ├── scan tbl
 │    └── columns: b:2
 └── projections
      └── b:2 [as="?column?":5, outer=(2)]

norm expect=FoldEqFalse
SELECT * FROM tbl WHERE b=FALSE
----
select
 ├── columns: k:1!null b:2!null
 ├── key: (1)
 ├── fd: ()-->(2)
 ├── scan tbl
 │    ├── columns: k:1!null b:2
 │    ├── key: (1)
 │    └── fd: (1)-->(2)
 └── filters
      └── NOT b:2 [outer=(2), constraints=(/2: [/false - /false]; tight), fd=()-->(2)]

norm expect=FoldEqFalse
SELECT b=FALSE FROM tbl
----
project
 ├── columns: "?column?":5
 ├── scan tbl
 │    └── columns: b:2
 └── projections
      └── NOT b:2 [as="?column?":5, outer=(2)]

exec-ddl
CREATE INVERTED INDEX ON geom_geog(geom)
----

# Regression test for #65684.
# We use opt here to show that the inverted index is used.
opt expect=FoldEqTrue
SELECT count(*) FROM geom_geog WHERE (geom && st_geomfromewkt('SRID=4326;POLYGON((0 0,0 100,100 100,100 0,0 0))'))=TRUE;
----
scalar-group-by
 ├── columns: count:8!null
 ├── cardinality: [1 - 1]
 ├── immutable
 ├── key: ()
 ├── fd: ()-->(8)
 ├── select
 │    ├── columns: geom:1!null
 │    ├── immutable
 │    ├── index-join geom_geog
 │    │    ├── columns: geom:1
 │    │    └── inverted-filter
 │    │         ├── columns: rowid:4!null
 │    │         ├── inverted expression: /7
 │    │         │    ├── tight: false, unique: false
 │    │         │    └── union spans
 │    │         │         ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
 │    │         │         └── ["B\xfd\xff\xff\xff\xff\xff\xff\xff\xff", "B\xfd\xff\xff\xff\xff\xff\xff\xff\xff"]
 │    │         ├── pre-filterer expression
 │    │         │    └── st_intersects('0103000020E610000001000000050000000000000000000000000000000000000000000000000000000000000000005940000000000000594000000000000059400000000000005940000000000000000000000000000000000000000000000000', geom:1)
 │    │         ├── key: (4)
 │    │         └── scan geom_geog@geom_geog_geom_idx,inverted
 │    │              ├── columns: rowid:4!null geom_inverted_key:7!null
 │    │              └── inverted constraint: /7/4
 │    │                   └── spans
 │    │                        ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
 │    │                        └── ["B\xfd\xff\xff\xff\xff\xff\xff\xff\xff", "B\xfd\xff\xff\xff\xff\xff\xff\xff\xff"]
 │    └── filters
 │         └── geom:1 && '0103000020E610000001000000050000000000000000000000000000000000000000000000000000000000000000005940000000000000594000000000000059400000000000005940000000000000000000000000000000000000000000000000' [outer=(1), immutable, constraints=(/1: (/NULL - ])]
 └── aggregations
      └── count-rows [as=count_rows:8]

# --------------------------------------------------
# FoldNeTrue + FoldNeFalse
# --------------------------------------------------

norm expect=FoldNeTrue
SELECT * FROM tbl WHERE b != TRUE
----
select
 ├── columns: k:1!null b:2!null
 ├── key: (1)
 ├── fd: ()-->(2)
 ├── scan tbl
 │    ├── columns: k:1!null b:2
 │    ├── key: (1)
 │    └── fd: (1)-->(2)
 └── filters
      └── NOT b:2 [outer=(2), constraints=(/2: [/false - /false]; tight), fd=()-->(2)]

norm expect=FoldNeTrue
SELECT b != TRUE FROM tbl
----
project
 ├── columns: "?column?":5
 ├── scan tbl
 │    └── columns: b:2
 └── projections
      └── NOT b:2 [as="?column?":5, outer=(2)]

norm expect=FoldNeFalse
SELECT * FROM tbl WHERE b != FALSE
----
select
 ├── columns: k:1!null b:2!null
 ├── key: (1)
 ├── fd: ()-->(2)
 ├── scan tbl
 │    ├── columns: k:1!null b:2
 │    ├── key: (1)
 │    └── fd: (1)-->(2)
 └── filters
      └── b:2 [outer=(2), constraints=(/2: [/true - /true]; tight), fd=()-->(2)]

norm expect=FoldNeFalse
SELECT b != FALSE FROM tbl
----
project
 ├── columns: "?column?":5
 ├── scan tbl
 │    └── columns: b:2
 └── projections
      └── b:2 [as="?column?":5, outer=(2)]
