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

# --------------------------------------------------
# CommuteVar
# --------------------------------------------------

# Put variables on both sides of comparison operator to avoid matching constant
# patterns.
opt expect=CommuteVar
SELECT
    (1+i) = k AS r,
    (2-k) <> i AS s,
    (i+1) IS NOT DISTINCT FROM k AS t,
    (i-1) IS DISTINCT FROM k AS u,

    (i*2) + k AS v,
    (i+2) * k AS w,
    (i^2) & k AS x,
    (i^2) | k AS y,
    (i*i) # k AS z
FROM a
----
project
 ├── columns: r:7(bool) s:8(bool) t:9(bool) u:10(bool) v:11(int) w:12(int) x:13(int) y:14(int) z:15(int)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int)
 │    ├── key: (1)
 │    └── fd: (1)-->(2)
 └── projections
      ├── k = (i + 1) [type=bool, outer=(1,2)]
      ├── i != (2 - k) [type=bool, outer=(1,2)]
      ├── k IS NOT DISTINCT FROM (i + 1) [type=bool, outer=(1,2)]
      ├── k IS DISTINCT FROM (i - 1) [type=bool, outer=(1,2)]
      ├── k + (i * 2) [type=int, outer=(1,2)]
      ├── k * (i + 2) [type=int, outer=(1,2)]
      ├── k & (i ^ 2) [type=int, outer=(1,2)]
      ├── k | (i ^ 2) [type=int, outer=(1,2)]
      └── k # (i * i) [type=int, outer=(1,2)]

# --------------------------------------------------
# CommuteConst
# --------------------------------------------------
opt expect=CommuteConst
SELECT
    (length('foo')+1) = (i+k) AS r,
    length('bar') <> (i*2) AS s,
    5 IS NOT DISTINCT FROM (1-k) AS t,
    (10::decimal+1::int) IS DISTINCT FROM k AS u,

    1 + f AS v,
    (5*length('foo')) * (i*i) AS w,
    (100 ^ 2) & (i+i) AS x,
    length('foo')+1 | (i+i) AS y,
    1-length('foo') # (k^2) AS z
FROM a
----
project
 ├── columns: r:7(bool) s:8(bool) t:9(bool) u:10(bool) v:11(float) w:12(int) x:13(int) y:14(int) z:15(int)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float)
 │    ├── key: (1)
 │    └── fd: (1)-->(2,3)
 └── projections
      ├── (i + k) = 4 [type=bool, outer=(1,2)]
      ├── (i * 2) != 3 [type=bool, outer=(2)]
      ├── (1 - k) IS NOT DISTINCT FROM 5 [type=bool, outer=(1)]
      ├── k IS DISTINCT FROM 11 [type=bool, outer=(1)]
      ├── f + 1.0 [type=float, outer=(3)]
      ├── (i * i) * 15 [type=int, outer=(2)]
      ├── (i + i) & 10000 [type=int, outer=(2)]
      ├── (i + i) | 4 [type=int, outer=(2)]
      └── (k ^ 2) # -2 [type=int, outer=(1)]

# --------------------------------------------------
# EliminateCoalesce
# --------------------------------------------------
opt expect=EliminateCoalesce
SELECT COALESCE(i) FROM a
----
project
 ├── columns: coalesce:7(int)
 ├── scan a
 │    └── columns: i:2(int)
 └── projections
      └── variable: i [type=int, outer=(2)]

opt expect=EliminateCoalesce
SELECT COALESCE(NULL) FROM a
----
project
 ├── columns: coalesce:7(unknown)
 ├── fd: ()-->(7)
 ├── scan a
 └── projections
      └── null [type=unknown]

# --------------------------------------------------
# SimplifyCoalesce
# --------------------------------------------------

opt expect=SimplifyCoalesce
SELECT COALESCE(NULL, 'foo', s) FROM a
----
project
 ├── columns: coalesce:7(string!null)
 ├── fd: ()-->(7)
 ├── scan a
 └── projections
      └── const: 'foo' [type=string]

opt expect=SimplifyCoalesce
SELECT COALESCE(NULL, NULL, s, s || 'foo') FROM a
----
project
 ├── columns: coalesce:7(string)
 ├── scan a
 │    └── columns: s:4(string)
 └── projections
      └── COALESCE(s, s || 'foo') [type=string, outer=(4)]

# Trailing null can't be removed.
opt
SELECT COALESCE(i, NULL, NULL) FROM a
----
project
 ├── columns: coalesce:7(int)
 ├── scan a
 │    └── columns: i:2(int)
 └── projections
      └── COALESCE(i, NULL, NULL) [type=int, outer=(2)]

opt expect=SimplifyCoalesce
SELECT COALESCE((1, 2, 3), (2, 3, 4)) FROM a
----
project
 ├── columns: coalesce:7(tuple{int, int, int})
 ├── fd: ()-->(7)
 ├── scan a
 └── projections
      └── (1, 2, 3) [type=tuple{int, int, int}]


# --------------------------------------------------
# EliminateCast
# --------------------------------------------------
opt expect=EliminateCast
SELECT
    i::int, arr::int[], '[1, 2]'::jsonb::json, null::char(2)::bit, s::string::string
FROM a
----
project
 ├── columns: i:7(int) arr:8(int[]) jsonb:9(jsonb!null) bit:10(varbit) s:11(string)
 ├── fd: ()-->(9,10)
 ├── scan a
 │    └── columns: a.i:2(int) a.s:4(string) a.arr:6(int[])
 └── projections
      ├── variable: a.i [type=int, outer=(2)]
      ├── variable: a.arr [type=int[], outer=(6)]
      ├── const: '[1, 2]' [type=jsonb]
      ├── null [type=varbit]
      └── variable: a.s [type=string, outer=(4)]

# Shouldn't eliminate these casts.
opt
SELECT
    i::float,
    arr::decimal[],
    s::json::json,
    s::varchar(2),
    i::smallint::int8,
    s::text::char::varchar,
    ARRAY[1, 2]::OIDVECTOR,
    ARRAY[1, 2]::INT2VECTOR
FROM a
----
project
 ├── columns: i:7(float) arr:8(decimal[]) s:9(jsonb) s:10(string) i:11(int) s:12(string) array:13(oid[]) array:14(int[])
 ├── fd: ()-->(13,14)
 ├── scan a
 │    └── columns: a.i:2(int) a.s:4(string) a.arr:6(int[])
 └── projections
      ├── a.i::FLOAT8 [type=float, outer=(2)]
      ├── a.arr::DECIMAL[] [type=decimal[], outer=(6)]
      ├── a.s::JSONB [type=jsonb, outer=(4)]
      ├── a.s::VARCHAR(2) [type=string, outer=(4)]
      ├── a.i::INT2 [type=int, outer=(2)]
      ├── a.s::CHAR::VARCHAR [type=string, outer=(4)]
      ├── ARRAY[1,2]::OIDVECTOR [type=oid[]]
      └── ARRAY[1,2]::INT2VECTOR [type=int[]]

# --------------------------------------------------
# FoldNullCast
# --------------------------------------------------
opt expect=FoldNullCast
SELECT
    null::int,
    null::timestamptz,
    null::decimal(19,2)::int::bit::char(2),
    null::oidvector,
    null::int2vector
----
values
 ├── columns: int8:1(int) timestamptz:2(timestamptz) char:3(string) oidvector:4(oid[]) int2vector:5(int[])
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1-5)
 └── (NULL, NULL, NULL, NULL, NULL) [type=tuple{int, timestamptz, string, oid[], int[]}]

# --------------------------------------------------
# FoldNullUnary
# --------------------------------------------------
opt expect=FoldNullUnary
SELECT +null::int AS r, -null::int AS s, ~null::int AS t FROM a
----
project
 ├── columns: r:7(int) s:8(int) t:9(int)
 ├── fd: ()-->(7-9)
 ├── scan a
 └── projections
      ├── null [type=int]
      ├── null [type=int]
      └── null [type=int]

# --------------------------------------------------
# FoldNullBinaryLeft, FoldNullBinaryRight
# --------------------------------------------------
opt expect=(FoldNullBinaryLeft,FoldNullBinaryRight)
SELECT
    null::int & 1 AS ra, 1 & null::int AS rb,
    null::decimal + 1 AS sa, 1 + null::decimal AS sb,
    null::float % 1 AS ta, 1 % null::float AS tb,
    null::int << 4 AS ua, 4 << null::int AS ub,

    -- These shouldn't be folded because AllowNullArgs is true for concat with arrays.
    arr::decimal[] || null AS va, null || arr::string[] AS vb,

    -- Scalars concatenated with nulls match array overloads, and shouldn't be folded.
    -- In other words, the only overload for decimal concatenation is an array overload.
    i::decimal || null AS wa, null || i::float AS wb
FROM a
----
project
 ├── columns: ra:7(int) rb:8(int) sa:9(decimal) sb:10(decimal) ta:11(float) tb:12(float) ua:13(int) ub:14(int) va:15(decimal[]) vb:16(string[]) wa:17(decimal[]) wb:18(float[])
 ├── fd: ()-->(7-14)
 ├── scan a
 │    └── columns: i:2(int) arr:6(int[])
 └── projections
      ├── null [type=int]
      ├── null [type=int]
      ├── null [type=decimal]
      ├── null [type=decimal]
      ├── null [type=float]
      ├── null [type=float]
      ├── null [type=int]
      ├── null [type=int]
      ├── arr::DECIMAL[] || CAST(NULL AS DECIMAL[]) [type=decimal[], outer=(6)]
      ├── CAST(NULL AS STRING[]) || arr::STRING[] [type=string[], outer=(6)]
      ├── i::DECIMAL || CAST(NULL AS DECIMAL[]) [type=decimal[], outer=(2)]
      └── CAST(NULL AS FLOAT8[]) || i::FLOAT8 [type=float[], outer=(2)]

opt
SELECT
    null::json || '[1, 2]' AS ra, '[1, 2]' || null::json AS rb,
    null::json->'foo' AS sa, '{}'::jsonb->null::string AS sb,
    null::json->>'foo' AS ta, '{}'::jsonb->>null::string AS tb,
    null::json->>'foo' AS ua, '{}'::jsonb->>null::string AS ub,
    null::json#>ARRAY['foo'] AS va, '{}'::jsonb#>NULL AS vb,
    null::json#>>ARRAY['foo'] AS wa, '{}'::jsonb#>>NULL AS wb
FROM a
----
project
 ├── columns: ra:7(jsonb) rb:8(jsonb) sa:9(jsonb) sb:10(jsonb) ta:11(string) tb:12(string) ua:11(string) ub:12(string) va:13(jsonb) vb:14(unknown) wa:15(string) wb:14(unknown)
 ├── fd: ()-->(7-15)
 ├── scan a
 └── projections
      ├── null [type=jsonb]
      ├── null [type=jsonb]
      ├── null [type=jsonb]
      ├── null [type=jsonb]
      ├── null [type=string]
      ├── null [type=string]
      ├── null [type=jsonb]
      ├── null [type=unknown]
      └── null [type=string]

# --------------------------------------------------
# FoldNullInNonEmpty
# --------------------------------------------------
opt expect=FoldNullInNonEmpty
SELECT null IN (i) AS r, null NOT IN (s) AS s FROM a
----
project
 ├── columns: r:7(bool) s:8(bool)
 ├── fd: ()-->(7,8)
 ├── scan a
 └── projections
      ├── null [type=bool]
      └── null [type=bool]

# --------------------------------------------------
# FoldInNull
# --------------------------------------------------
opt expect=FoldInNull
SELECT i IN (null, null) AS r, k NOT IN (1 * null, null::int, 1 < null) AS s FROM a
----
project
 ├── columns: r:7(bool) s:8(bool)
 ├── fd: ()-->(7,8)
 ├── scan a
 └── projections
      ├── null [type=bool]
      └── null [type=bool]

# --------------------------------------------------
# NormalizeInConst
# --------------------------------------------------
opt expect=NormalizeInConst
SELECT i IN (2, 1, 1, null, 3, 4.00, 4.0, null, 3.0) AS r FROM a
----
project
 ├── columns: r:7(bool)
 ├── scan a
 │    └── columns: i:2(int)
 └── projections
      └── i IN (NULL, 1, 2, 3, 4) [type=bool, outer=(2)]

# Single value.
opt expect-not=NormalizeInConst
SELECT s NOT IN ('foo') AS r FROM a
----
project
 ├── columns: r:7(bool)
 ├── scan a
 │    └── columns: s:4(string)
 └── projections
      └── s NOT IN ('foo',) [type=bool, outer=(4)]

# Don't sort, since the list is not constant.
opt expect-not=NormalizeInConst
SELECT s NOT IN ('foo', s || 'foo', 'bar', length(s)::string, NULL) AS r FROM a
----
project
 ├── columns: r:7(bool)
 ├── scan a
 │    └── columns: s:4(string)
 └── projections
      └── s NOT IN ('foo', s || 'foo', 'bar', length(s)::STRING, NULL) [type=bool, outer=(4)]

# Regression test #36031.
opt expect-not=NormalizeInConst
SELECT
    true
    IN (
            NULL,
            NULL,
            (
                '201.249.149.90/18':::INET::INET
                & '97a7:3650:3dd8:d4e9:35fe:6cfb:a714:1c17/61':::INET::INET
            )::INET
            << 'e22f:2067:2ed2:7b07:b167:206f:f17b:5b7d/82':::INET::INET
        )
----
values
 ├── columns: "?column?":1(bool)
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (true IN (NULL, NULL, ('201.249.149.90/18' & '97a7:3650:3dd8:d4e9:35fe:6cfb:a714:1c17/61') << 'e22f:2067:2ed2:7b07:b167:206f:f17b:5b7d/82'),) [type=tuple{bool}]

# --------------------------------------------------
# EliminateExistsProject
# --------------------------------------------------
opt expect=EliminateExistsProject
SELECT * FROM a WHERE EXISTS(SELECT i+1, i*k FROM a)
----
select
 ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) arr:6(int[])
 ├── 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) arr:6(int[])
 │    ├── key: (1)
 │    └── fd: (1)-->(2-6)
 └── filters
      └── exists [type=bool, subquery]
           └── scan a
                ├── columns: k:7(int!null) i:8(int)
                ├── limit: 1
                ├── key: ()
                └── fd: ()-->(7,8)

# --------------------------------------------------
# EliminateExistsGroupBy
# --------------------------------------------------

# Scalar group by shouldn't get eliminated.
opt expect-not=EliminateExistsGroupBy
SELECT * FROM a WHERE EXISTS(SELECT max(s) FROM a WHERE False)
----
select
 ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) arr:6(int[])
 ├── 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) arr:6(int[])
 │    ├── key: (1)
 │    └── fd: (1)-->(2-6)
 └── filters
      └── exists [type=bool, subquery]
           └── scalar-group-by
                ├── columns: max:13(string)
                ├── cardinality: [1 - 1]
                ├── key: ()
                ├── fd: ()-->(13)
                ├── values
                │    ├── columns: s:10(string)
                │    ├── cardinality: [0 - 0]
                │    ├── key: ()
                │    └── fd: ()-->(10)
                └── aggregations
                     └── max [type=string, outer=(10)]
                          └── variable: s [type=string]

opt expect=EliminateExistsGroupBy
SELECT * FROM a WHERE EXISTS(SELECT DISTINCT s FROM a)
----
select
 ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) arr:6(int[])
 ├── 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) arr:6(int[])
 │    ├── key: (1)
 │    └── fd: (1)-->(2-6)
 └── filters
      └── exists [type=bool, subquery]
           └── scan a
                ├── columns: s:10(string)
                ├── limit: 1
                ├── key: ()
                └── fd: ()-->(10)

opt expect=EliminateExistsGroupBy
SELECT * FROM a WHERE EXISTS(SELECT DISTINCT ON (i) s FROM a)
----
select
 ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) arr:6(int[])
 ├── 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) arr:6(int[])
 │    ├── key: (1)
 │    └── fd: (1)-->(2-6)
 └── filters
      └── exists [type=bool, subquery]
           └── scan a
                ├── columns: i:8(int) s:10(string)
                ├── limit: 1
                ├── key: ()
                └── fd: ()-->(8,10)

# --------------------------------------------------
# EliminateExistsGroupBy + EliminateExistsProject
# --------------------------------------------------
opt expect=(EliminateExistsGroupBy,EliminateExistsProject)
SELECT * FROM a WHERE EXISTS(SELECT max(s) FROM a GROUP BY i)
----
select
 ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) arr:6(int[])
 ├── 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) arr:6(int[])
 │    ├── key: (1)
 │    └── fd: (1)-->(2-6)
 └── filters
      └── exists [type=bool, subquery]
           └── scan a
                ├── columns: i:8(int) s:10(string)
                ├── limit: 1
                ├── key: ()
                └── fd: ()-->(8,10)

# --------------------------------------------------
# NormalizeJSONFieldAccess
# --------------------------------------------------
opt expect=NormalizeJSONFieldAccess
SELECT * FROM a WHERE j->'a' = '"b"'::JSON
----
select
 ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) arr:6(int[])
 ├── 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) arr:6(int[])
 │    ├── key: (1)
 │    └── fd: (1)-->(2-6)
 └── filters
      └── j @> '{"a": "b"}' [type=bool, outer=(5)]

opt expect=NormalizeJSONFieldAccess
SELECT * FROM a WHERE j->'a'->'x' = '"b"'::JSON
----
select
 ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) arr:6(int[])
 ├── 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) arr:6(int[])
 │    ├── key: (1)
 │    └── fd: (1)-->(2-6)
 └── filters
      └── j @> '{"a": {"x": "b"}}' [type=bool, outer=(5)]

# The transformation is not valid in this case.
opt expect-not=NormalizeJSONFieldAccess
SELECT * FROM a WHERE j->2 = '"b"'::JSON
----
select
 ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) arr:6(int[])
 ├── 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) arr:6(int[])
 │    ├── key: (1)
 │    └── fd: (1)-->(2-6)
 └── filters
      └── (j->2) = '"b"' [type=bool, outer=(5)]

# The transformation is not valid in this case, since j->'a' could be an array.
opt expect-not=NormalizeJSONFieldAccess
SELECT * FROM a WHERE j->'a' @> '"b"'::JSON
----
select
 ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) arr:6(int[])
 ├── 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) arr:6(int[])
 │    ├── key: (1)
 │    └── fd: (1)-->(2-6)
 └── filters
      └── (j->'a') @> '"b"' [type=bool, outer=(5)]

# The transformation is not valid in this case, since containment doesn't imply
# equality for non-scalars.
opt
SELECT j->'a' = '["b"]'::JSON, j->'a' = '{"b": "c"}'::JSON FROM a
----
project
 ├── columns: "?column?":7(bool) "?column?":8(bool)
 ├── scan a
 │    └── columns: j:5(jsonb)
 └── projections
      ├── (j->'a') = '["b"]' [type=bool, outer=(5)]
      └── (j->'a') = '{"b": "c"}' [type=bool, outer=(5)]

# --------------------------------------------------
# NormalizeJSONContains
# --------------------------------------------------

opt expect=NormalizeJSONContains
SELECT * FROM a WHERE j->'a' @> '{"x": "b"}'::JSON
----
select
 ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) arr:6(int[])
 ├── 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) arr:6(int[])
 │    ├── key: (1)
 │    └── fd: (1)-->(2-6)
 └── filters
      └── j @> '{"a": {"x": "b"}}' [type=bool, outer=(5)]

# --------------------------------------------------
# SimplifyCaseWhenConstValue
# --------------------------------------------------

opt expect=SimplifyCaseWhenConstValue
SELECT CASE 1 WHEN 1 THEN 'one' END
----
values
 ├── columns: case:1(string)
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── ('one',) [type=tuple{string}]

opt expect=SimplifyCaseWhenConstValue
SELECT CASE WHEN 1 = 1 THEN 'one' END
----
values
 ├── columns: case:1(string)
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── ('one',) [type=tuple{string}]

opt expect=SimplifyCaseWhenConstValue
SELECT CASE false WHEN 0 = 1 THEN 'one' END
----
values
 ├── columns: case:1(string)
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── ('one',) [type=tuple{string}]

opt expect=SimplifyCaseWhenConstValue
SELECT CASE 1 WHEN 2 THEN 'one' END
----
values
 ├── columns: case:1(string)
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (NULL,) [type=tuple{string}]

opt expect=SimplifyCaseWhenConstValue
SELECT CASE 1 WHEN 2 THEN 'one' ELSE NULL END
----
values
 ├── columns: case:1(string)
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (NULL,) [type=tuple{string}]

# Regression test for #34930.
norm expect=SimplifyCaseWhenConstValue
SELECT
    CASE
    WHEN true THEN NULL
    ELSE -0.41697856420581636
    END
    - CASE WHEN NULL THEN 1.4034371360919229 ELSE ln(NULL) END
----
values
 ├── columns: "?column?":1(decimal)
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (NULL,) [type=tuple{decimal}]

# Regression test for #35246.
norm expect=SimplifyCaseWhenConstValue
SELECT
    CASE WHEN true THEN NULL ELSE 'foo' END ||
    CASE WHEN true THEN NULL ELSE 'bar' END
----
values
 ├── columns: "?column?":1(string)
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── (NULL,) [type=tuple{string}]

# Verify that a true condition does not remove non-constant expressions
# proceeding it.
opt expect=SimplifyCaseWhenConstValue
SELECT
    CASE 1
    WHEN k THEN 'one'
    WHEN 1 THEN 'two'
    WHEN 1 THEN 'three'
    ELSE 'four'
    END
FROM
    a
----
project
 ├── columns: case:7(string)
 ├── scan a
 │    ├── columns: k:1(int!null)
 │    └── key: (1)
 └── projections
      └── CASE 1 WHEN k THEN 'one' ELSE 'two' END [type=string, outer=(1)]

opt expect=SimplifyCaseWhenConstValue
SELECT
    CASE WHEN k = 1 THEN 'one' WHEN true THEN 'two' END
FROM
    a
----
project
 ├── columns: case:7(string)
 ├── scan a
 │    ├── columns: k:1(int!null)
 │    └── key: (1)
 └── projections
      └── CASE WHEN k = 1 THEN 'one' ELSE 'two' END [type=string, outer=(1)]

opt expect=SimplifyCaseWhenConstValue
SELECT CASE 1 WHEN 2 THEN 'one' ELSE 'three' END
----
values
 ├── columns: case:1(string)
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── ('three',) [type=tuple{string}]

opt expect=SimplifyCaseWhenConstValue
SELECT
    CASE 1
    WHEN 2 THEN 'one'
    WHEN k THEN 'two'
    WHEN 1 THEN 'three'
    WHEN 1 THEN 'four'
    END
FROM
    a
----
project
 ├── columns: case:7(string)
 ├── scan a
 │    ├── columns: k:1(int!null)
 │    └── key: (1)
 └── projections
      └── CASE 1 WHEN k THEN 'two' ELSE 'three' END [type=string, outer=(1)]

opt expect=SimplifyCaseWhenConstValue
SELECT
    CASE 1
    WHEN 2 THEN 'one'
    WHEN 1 THEN 'three'
    WHEN 1 THEN 'four'
    ELSE 'five'
    END
----
values
 ├── columns: case:1(string)
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── ('three',) [type=tuple{string}]

opt expect=SimplifyCaseWhenConstValue
SELECT
    CASE NULL
    WHEN true THEN 'one'
    WHEN false THEN 'two'
    WHEN NULL THEN 'three'
    ELSE 'four'
    END
----
values
 ├── columns: case:1(string)
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── ('four',) [type=tuple{string}]

opt expect=SimplifyCaseWhenConstValue
SELECT CASE WHEN false THEN 'one' WHEN true THEN 'two' END
----
values
 ├── columns: case:1(string)
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── ('two',) [type=tuple{string}]

# --------------------------------------------------
# UnifyComparisonTypes
# --------------------------------------------------

exec-ddl
CREATE TABLE e
(
    k INT PRIMARY KEY,
    i INT,
    t TIMESTAMP,
    tz TIMESTAMPTZ,
    d DATE,
    INDEX (i),
    INDEX (t),
    INDEX (tz),
    INDEX (d)
)
----
TABLE e
 ├── k int not null
 ├── i int
 ├── t timestamp
 ├── tz timestamptz
 ├── d date
 ├── INDEX primary
 │    └── k int not null
 ├── INDEX secondary
 │    ├── i int
 │    └── k int not null
 ├── INDEX secondary
 │    ├── t timestamp
 │    └── k int not null
 ├── INDEX secondary
 │    ├── tz timestamptz
 │    └── k int not null
 └── INDEX secondary
      ├── d date
      └── k int not null

## --------------------------------------------------
## INT / FLOAT / DECIMAL
## --------------------------------------------------

# Compare how we can generate spans with and without the rule enabled.
opt expect=UnifyComparisonTypes
SELECT * FROM e WHERE k > '1.0'::FLOAT
----
scan e
 ├── columns: k:1(int!null) i:2(int) t:3(timestamp) tz:4(timestamptz) d:5(date)
 ├── constraint: /1: [/2 - ]
 ├── key: (1)
 └── fd: (1)-->(2-5)

opt disable=UnifyComparisonTypes
SELECT * FROM e WHERE k > '1.0'::FLOAT
----
select
 ├── columns: k:1(int!null) i:2(int) t:3(timestamp) tz:4(timestamptz) d:5(date)
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan e
 │    ├── columns: k:1(int!null) i:2(int) t:3(timestamp) tz:4(timestamptz) d:5(date)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 └── filters
      └── k > 1.0 [type=bool, outer=(1), constraints=(/1: (/NULL - ])]

# Ensure the rest of normalization does its work and we move things around appropriately.
opt expect=UnifyComparisonTypes
SELECT * FROM e WHERE '1.0'::FLOAT > k
----
scan e
 ├── columns: k:1(int!null) i:2(int) t:3(timestamp) tz:4(timestamptz) d:5(date)
 ├── constraint: /1: [ - /0]
 ├── key: (1)
 └── fd: (1)-->(2-5)

opt expect=UnifyComparisonTypes
SELECT * FROM e WHERE k - 1 = 2::DECIMAL
----
scan e
 ├── columns: k:1(int!null) i:2(int) t:3(timestamp) tz:4(timestamptz) d:5(date)
 ├── constraint: /1: [/3 - /3]
 ├── cardinality: [0 - 1]
 ├── key: ()
 └── fd: ()-->(1-5)

# TODO(justin): we should theoretically be able to generate constraints in this
# case.
opt expect-not=UnifyComparisonTypes
SELECT * FROM e WHERE k > '1.1'::FLOAT
----
select
 ├── columns: k:1(int!null) i:2(int) t:3(timestamp) tz:4(timestamptz) d:5(date)
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan e
 │    ├── columns: k:1(int!null) i:2(int) t:3(timestamp) tz:4(timestamptz) d:5(date)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 └── filters
      └── k > 1.1 [type=bool, outer=(1), constraints=(/1: (/NULL - ])]

# -0 can generate spans
opt expect=UnifyComparisonTypes
SELECT * FROM e WHERE k > '-0'::FLOAT
----
scan e
 ├── columns: k:1(int!null) i:2(int) t:3(timestamp) tz:4(timestamptz) d:5(date)
 ├── constraint: /1: [/1 - ]
 ├── key: (1)
 └── fd: (1)-->(2-5)

# NaN cannot generate spans.
opt expect-not=UnifyComparisonTypes
SELECT * FROM e WHERE k > 'NaN'::FLOAT
----
select
 ├── columns: k:1(int!null) i:2(int) t:3(timestamp) tz:4(timestamptz) d:5(date)
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan e
 │    ├── columns: k:1(int!null) i:2(int) t:3(timestamp) tz:4(timestamptz) d:5(date)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 └── filters
      └── k > NaN [type=bool, outer=(1), constraints=(/1: (/NULL - ])]

# IS/IS NOT
# We do not do the unification here (the rule matches on Const and NULL is its
# own operator), but this is fine because when an explicit NULL is involved we
# can generate spans anyway.
opt expect-not=UnifyComparisonTypes format=show-all
SELECT k FROM e WHERE i IS NOT DISTINCT FROM NULL::FLOAT
----
project
 ├── columns: k:1(int!null)
 ├── stats: [rows=10]
 ├── cost: 10.52
 ├── key: (1)
 ├── prune: (1)
 ├── interesting orderings: (+1)
 └── scan t.public.e@secondary
      ├── columns: t.public.e.k:1(int!null) t.public.e.i:2(int)
      ├── constraint: /2/1: [/NULL - /NULL]
      ├── stats: [rows=10, distinct(1)=10, null(1)=0, distinct(2)=1, null(2)=10]
      ├── cost: 10.41
      ├── key: (1)
      ├── fd: ()-->(2)
      ├── prune: (1,2)
      └── interesting orderings: (+1) (+2,+1)

opt expect-not=UnifyComparisonTypes format=show-all
SELECT k FROM e WHERE i IS DISTINCT FROM NULL::FLOAT
----
project
 ├── columns: k:1(int!null)
 ├── stats: [rows=990]
 ├── cost: 1039.52
 ├── key: (1)
 ├── prune: (1)
 ├── interesting orderings: (+1)
 └── scan t.public.e@secondary
      ├── columns: t.public.e.k:1(int!null) t.public.e.i:2(int!null)
      ├── constraint: /2/1: (/NULL - ]
      ├── stats: [rows=990, distinct(1)=990, null(1)=0, distinct(2)=100, null(2)=0]
      ├── cost: 1029.61
      ├── key: (1)
      ├── fd: (1)-->(2)
      ├── prune: (1,2)
      └── interesting orderings: (+1) (+2,+1)

opt expect=UnifyComparisonTypes
SELECT * FROM e WHERE k IS NOT DISTINCT FROM '1.0'::FLOAT
----
scan e
 ├── columns: k:1(int!null) i:2(int) t:3(timestamp) tz:4(timestamptz) d:5(date)
 ├── constraint: /1: [/1 - /1]
 ├── cardinality: [0 - 1]
 ├── key: ()
 └── fd: ()-->(1-5)

## --------------------------------------------------
## TIMESTAMP / TIMESTAMPTZ / DATE
## --------------------------------------------------

opt disable=UnifyComparisonTypes
SELECT k FROM e WHERE tz > '2017-11-12 07:35:01+00:00'::TIMESTAMP
----
project
 ├── columns: k:1(int!null)
 ├── key: (1)
 └── select
      ├── columns: k:1(int!null) tz:4(timestamptz!null)
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── scan e@secondary
      │    ├── columns: k:1(int!null) tz:4(timestamptz!null)
      │    ├── constraint: /4/1: (/NULL - ]
      │    ├── key: (1)
      │    └── fd: (1)-->(4)
      └── filters
           └── tz > '2017-11-12 07:35:01+00:00' [type=bool, outer=(4), constraints=(/4: (/NULL - ])]

opt expect=UnifyComparisonTypes
SELECT k FROM e WHERE tz > '2017-11-12 07:35:01+00:00'::TIMESTAMP
----
project
 ├── columns: k:1(int!null)
 ├── key: (1)
 └── scan e@secondary
      ├── columns: k:1(int!null) tz:4(timestamptz!null)
      ├── constraint: /4/1: [/'2017-11-12 07:35:01.000001+00:00' - ]
      ├── key: (1)
      └── fd: (1)-->(4)

# Common case arising from constant folding: the folding here results in a
# TIMESTAMP, but we would still like to be able to generate DATE spans.
opt
SELECT k FROM e WHERE d > '2018-07-01' AND d < '2018-07-01'::DATE + '1w'::INTERVAL
----
project
 ├── columns: k:1(int!null)
 ├── key: (1)
 └── scan e@secondary
      ├── columns: k:1(int!null) d:5(date!null)
      ├── constraint: /5/1: [/'2018-07-02' - /'2018-07-07']
      ├── key: (1)
      └── fd: (1)-->(5)

# A case where we can theoretically generate spans, but do not.
# TODO(justin): modify the logic to allow us to create spans in this case.
opt
SELECT k FROM e WHERE d > '2018-07-01' AND d < '2018-07-01'::DATE + '1w1s'::INTERVAL
----
project
 ├── columns: k:1(int!null)
 ├── key: (1)
 └── select
      ├── columns: k:1(int!null) d:5(date!null)
      ├── key: (1)
      ├── fd: (1)-->(5)
      ├── scan e@secondary
      │    ├── columns: k:1(int!null) d:5(date!null)
      │    ├── constraint: /5/1: [/'2018-07-02' - ]
      │    ├── key: (1)
      │    └── fd: (1)-->(5)
      └── filters
           └── d < '2018-07-08 00:00:01+00:00' [type=bool, outer=(5), constraints=(/5: (/NULL - ])]

# NULL value.
opt
SELECT k FROM e WHERE tz > NULL::TIMESTAMP
----
values
 ├── columns: k:1(int)
 ├── cardinality: [0 - 0]
 ├── key: ()
 └── fd: ()-->(1)

# Working in concert with other norm rules
opt
SELECT k FROM e WHERE d - '1w'::INTERVAL > '2018-07-01'::DATE
----
project
 ├── columns: k:1(int!null)
 ├── key: (1)
 └── scan e@secondary
      ├── columns: k:1(int!null) d:5(date!null)
      ├── constraint: /5/1: [/'2018-07-09' - ]
      ├── key: (1)
      └── fd: (1)-->(5)

# --------------------------------------------------
# SimplifyEqualsAnyTuple
# --------------------------------------------------

opt expect=SimplifyEqualsAnyTuple
SELECT k FROM a WHERE k = ANY (1, 2, 3)
----
scan a
 ├── columns: k:1(int!null)
 ├── constraint: /1: [/1 - /3]
 └── key: (1)

opt expect=SimplifyEqualsAnyTuple
SELECT k FROM a WHERE k = ANY ()
----
values
 ├── columns: k:1(int)
 ├── cardinality: [0 - 0]
 ├── key: ()
 └── fd: ()-->(1)

# --------------------------------------------------
# SimplifyAnyScalarArray
# --------------------------------------------------

opt expect=SimplifyAnyScalarArray
SELECT k FROM a WHERE k > ANY ARRAY[1, 2, 3]
----
select
 ├── columns: k:1(int!null)
 ├── key: (1)
 ├── scan a
 │    ├── columns: k:1(int!null)
 │    └── key: (1)
 └── filters
      └── k > ANY (1, 2, 3) [type=bool, outer=(1)]

opt expect-not=SimplifyAnyScalarArray
SELECT k FROM a WHERE k > ANY ARRAY[1, 2, 3, i]
----
project
 ├── columns: k:1(int!null)
 ├── key: (1)
 └── select
      ├── columns: k:1(int!null) i:2(int)
      ├── key: (1)
      ├── fd: (1)-->(2)
      ├── scan a
      │    ├── columns: k:1(int!null) i:2(int)
      │    ├── key: (1)
      │    └── fd: (1)-->(2)
      └── filters
           └── k > ANY ARRAY[1, 2, 3, i] [type=bool, outer=(1,2)]

opt expect=SimplifyAnyScalarArray
SELECT k FROM a WHERE k > ANY ARRAY[]:::INT[]
----
select
 ├── columns: k:1(int!null)
 ├── key: (1)
 ├── scan a
 │    ├── columns: k:1(int!null)
 │    └── key: (1)
 └── filters
      └── k > ANY () [type=bool, outer=(1)]

# --------------------------------------------------
# SimplifyEqualsAnyTuple + SimplifyAnyScalarArray
# --------------------------------------------------

opt expect=(SimplifyAnyScalarArray,SimplifyEqualsAnyTuple)
SELECT k FROM a WHERE k = ANY ARRAY[1, 2, 3]
----
scan a
 ├── columns: k:1(int!null)
 ├── constraint: /1: [/1 - /3]
 └── key: (1)

opt expect=(SimplifyAnyScalarArray,SimplifyEqualsAnyTuple)
SELECT k FROM a WHERE k = ANY ARRAY[]:::INT[]
----
values
 ├── columns: k:1(int)
 ├── cardinality: [0 - 0]
 ├── key: ()
 └── fd: ()-->(1)

# TODO(justin): fold casts.
opt
SELECT k FROM a WHERE k = ANY '{1,2,3}'::INT[]
----
scan a
 ├── columns: k:1(int!null)
 ├── constraint: /1: [/1 - /3]
 └── key: (1)

# --------------------------------------------------
# FoldCollate
# --------------------------------------------------

norm expect=FoldCollate
SELECT 'hello' COLLATE en_u_ks_level1
----
values
 ├── columns: "?column?":1(collatedstring{en_u_ks_level1})
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── ('hello' COLLATE en_u_ks_level1,) [type=tuple{collatedstring{en_u_ks_level1}}]

norm expect=FoldCollate
SELECT ('hello' COLLATE en_u_ks_level1) COLLATE en_u_ks_level1
----
values
 ├── columns: "?column?":1(collatedstring{en_u_ks_level1})
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── ('hello' COLLATE en_u_ks_level1,) [type=tuple{collatedstring{en_u_ks_level1}}]

norm expect=FoldCollate
SELECT ('hello' COLLATE en) COLLATE en_u_ks_level1
----
values
 ├── columns: "?column?":1(collatedstring{en_u_ks_level1})
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── ('hello' COLLATE en_u_ks_level1,) [type=tuple{collatedstring{en_u_ks_level1}}]

norm expect-not=FoldCollate
SELECT s COLLATE en_u_ks_level1 FROM a
----
project
 ├── columns: s:7(collatedstring{en_u_ks_level1})
 ├── scan a
 │    └── columns: a.s:4(string)
 └── projections
      └── a.s COLLATE en_u_ks_level1 [type=collatedstring{en_u_ks_level1}, outer=(4)]

# --------------------------------------------------
# NormalizeArrayFlattenToAgg
# --------------------------------------------------

norm expect=NormalizeArrayFlattenToAgg
SELECT ARRAY(SELECT k FROM a WHERE a.k = b.k) FROM a AS b
----
project
 ├── columns: array:14(int[])
 ├── group-by
 │    ├── columns: b.k:1(int!null) a.k:7(int) array_agg:15(int[])
 │    ├── grouping columns: b.k:1(int!null)
 │    ├── key: (7)
 │    ├── fd: (1)==(7), (7)==(1), (7)-->(15), (1)-->(7,15)
 │    ├── inner-join
 │    │    ├── columns: b.k:1(int!null) a.k:7(int!null)
 │    │    ├── key: (7)
 │    │    ├── fd: (1)==(7), (7)==(1)
 │    │    ├── scan b
 │    │    │    ├── columns: b.k:1(int!null)
 │    │    │    └── key: (1)
 │    │    ├── scan a
 │    │    │    ├── columns: a.k:7(int!null)
 │    │    │    └── key: (7)
 │    │    └── filters
 │    │         └── a.k = b.k [type=bool, outer=(1,7), constraints=(/1: (/NULL - ]; /7: (/NULL - ]), fd=(1)==(7), (7)==(1)]
 │    └── aggregations
 │         ├── array-agg [type=int[], outer=(7)]
 │         │    └── variable: a.k [type=int]
 │         └── any-not-null-agg [type=int, outer=(7)]
 │              └── variable: a.k [type=int]
 └── projections
      └── COALESCE(CASE WHEN a.k IS NOT NULL THEN array_agg END, ARRAY[]) [type=int[], outer=(7,15)]

# Ensure ordering is maintained.
norm expect=NormalizeArrayFlattenToAgg
SELECT ARRAY(SELECT k FROM a WHERE a.i = b.i ORDER BY a.k) FROM a AS b
----
project
 ├── columns: array:14(int[])
 ├── group-by
 │    ├── columns: a.k:7(int) rownum:15(int!null) array_agg:16(int[])
 │    ├── grouping columns: rownum:15(int!null)
 │    ├── internal-ordering: +7 opt(8)
 │    ├── key: (15)
 │    ├── fd: (15)-->(7,16)
 │    ├── sort
 │    │    ├── columns: b.i:2(int) a.k:7(int) a.i:8(int) rownum:15(int!null)
 │    │    ├── key: (7,15)
 │    │    ├── fd: (15)-->(2), (7)-->(8)
 │    │    ├── ordering: +7 opt(8) [actual: +7]
 │    │    └── left-join
 │    │         ├── columns: b.i:2(int) a.k:7(int) a.i:8(int) rownum:15(int!null)
 │    │         ├── key: (7,15)
 │    │         ├── fd: (15)-->(2), (7)-->(8)
 │    │         ├── row-number
 │    │         │    ├── columns: b.i:2(int) rownum:15(int!null)
 │    │         │    ├── key: (15)
 │    │         │    ├── fd: (15)-->(2)
 │    │         │    └── scan b
 │    │         │         └── columns: b.i:2(int)
 │    │         ├── scan a
 │    │         │    ├── columns: a.k:7(int!null) a.i:8(int)
 │    │         │    ├── key: (7)
 │    │         │    └── fd: (7)-->(8)
 │    │         └── filters
 │    │              └── a.i = b.i [type=bool, outer=(2,8), constraints=(/2: (/NULL - ]; /8: (/NULL - ]), fd=(2)==(8), (8)==(2)]
 │    └── aggregations
 │         ├── array-agg [type=int[], outer=(7)]
 │         │    └── variable: a.k [type=int]
 │         └── any-not-null-agg [type=int, outer=(7)]
 │              └── variable: a.k [type=int]
 └── projections
      └── COALESCE(CASE WHEN a.k IS NOT NULL THEN array_agg END, ARRAY[]) [type=int[], outer=(7,16)]

norm expect=NormalizeArrayFlattenToAgg
SELECT ARRAY(SELECT generate_series(1, a.k) ORDER BY 1 DESC) FROM a
----
project
 ├── columns: array:9(int[])
 ├── side-effects
 ├── group-by
 │    ├── columns: k:1(int!null) canary:10(bool) array_agg:11(int[])
 │    ├── grouping columns: k:1(int!null)
 │    ├── internal-ordering: -7
 │    ├── side-effects
 │    ├── key: (1)
 │    ├── fd: (1)-->(10,11)
 │    ├── sort
 │    │    ├── columns: k:1(int!null) generate_series:7(int) canary:10(bool)
 │    │    ├── side-effects
 │    │    ├── ordering: -7
 │    │    └── left-join-apply
 │    │         ├── columns: k:1(int!null) generate_series:7(int) canary:10(bool)
 │    │         ├── side-effects
 │    │         ├── scan a
 │    │         │    ├── columns: k:1(int!null)
 │    │         │    └── key: (1)
 │    │         ├── project
 │    │         │    ├── columns: canary:10(bool!null) generate_series:7(int)
 │    │         │    ├── outer: (1)
 │    │         │    ├── side-effects
 │    │         │    ├── fd: ()-->(10)
 │    │         │    ├── project-set
 │    │         │    │    ├── columns: generate_series:7(int)
 │    │         │    │    ├── outer: (1)
 │    │         │    │    ├── side-effects
 │    │         │    │    ├── values
 │    │         │    │    │    ├── cardinality: [1 - 1]
 │    │         │    │    │    ├── key: ()
 │    │         │    │    │    └── tuple [type=tuple]
 │    │         │    │    └── zip
 │    │         │    │         └── function: generate_series [type=int, outer=(1), side-effects]
 │    │         │    │              ├── const: 1 [type=int]
 │    │         │    │              └── variable: k [type=int]
 │    │         │    └── projections
 │    │         │         └── true [type=bool]
 │    │         └── filters (true)
 │    └── aggregations
 │         ├── array-agg [type=int[], outer=(7)]
 │         │    └── variable: generate_series [type=int]
 │         └── any-not-null-agg [type=bool, outer=(10)]
 │              └── variable: canary [type=bool]
 └── projections
      └── COALESCE(CASE WHEN canary IS NOT NULL THEN array_agg END, ARRAY[]) [type=int[], outer=(10,11)]

# Uncorrelated ArrayFlatten inside a correlated ArrayFlatten.
norm expect=NormalizeArrayFlattenToAgg
SELECT ARRAY(SELECT ARRAY(SELECT k FROM a)[1] FROM a as b WHERE b.k = c.k) FROM a AS c
----
project
 ├── columns: array:21(int[])
 ├── group-by
 │    ├── columns: c.k:1(int!null) canary:22(bool) array_agg:23(int[])
 │    ├── grouping columns: c.k:1(int!null)
 │    ├── key: (1)
 │    ├── fd: ()-->(22), (1)-->(22,23)
 │    ├── inner-join
 │    │    ├── columns: c.k:1(int!null) b.k:7(int!null) array:19(int) canary:22(bool!null)
 │    │    ├── key: (7)
 │    │    ├── fd: ()-->(19,22), (1)==(7), (7)==(1)
 │    │    ├── scan c
 │    │    │    ├── columns: c.k:1(int!null)
 │    │    │    └── key: (1)
 │    │    ├── project
 │    │    │    ├── columns: canary:22(bool!null) array:19(int) b.k:7(int!null)
 │    │    │    ├── key: (7)
 │    │    │    ├── fd: ()-->(19,22)
 │    │    │    ├── scan b
 │    │    │    │    ├── columns: b.k:7(int!null)
 │    │    │    │    └── key: (7)
 │    │    │    └── projections
 │    │    │         ├── true [type=bool]
 │    │    │         └── indirection [type=int, subquery]
 │    │    │              ├── array-flatten [type=int[]]
 │    │    │              │    └── scan a
 │    │    │              │         ├── columns: k:13(int!null)
 │    │    │              │         └── key: (13)
 │    │    │              └── const: 1 [type=int]
 │    │    └── filters
 │    │         └── b.k = c.k [type=bool, outer=(1,7), constraints=(/1: (/NULL - ]; /7: (/NULL - ]), fd=(1)==(7), (7)==(1)]
 │    └── aggregations
 │         ├── array-agg [type=int[], outer=(19)]
 │         │    └── variable: array [type=int]
 │         └── any-not-null-agg [type=bool, outer=(22)]
 │              └── variable: canary [type=bool]
 └── projections
      └── COALESCE(CASE WHEN canary IS NOT NULL THEN array_agg END, ARRAY[]) [type=int[], outer=(22,23)]

# Correlated ArrayFlatten inside another correlated ArrayFlatten.
norm expect=NormalizeArrayFlattenToAgg
SELECT ARRAY(SELECT ARRAY(SELECT k FROM a WHERE a.k = b.k)[1] FROM a as b WHERE b.k = c.k) FROM a AS c
----
project
 ├── columns: array:23(int[])
 ├── group-by
 │    ├── columns: c.k:1(int!null) canary:24(bool) array_agg:25(int[])
 │    ├── grouping columns: c.k:1(int!null)
 │    ├── key: (1)
 │    ├── fd: (1)-->(24,25)
 │    ├── left-join-apply
 │    │    ├── columns: c.k:1(int!null) array:20(int) canary:24(bool)
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(20,24)
 │    │    ├── scan c
 │    │    │    ├── columns: c.k:1(int!null)
 │    │    │    └── key: (1)
 │    │    ├── project
 │    │    │    ├── columns: canary:24(bool!null) array:20(int)
 │    │    │    ├── outer: (1)
 │    │    │    ├── cardinality: [0 - 1]
 │    │    │    ├── key: ()
 │    │    │    ├── fd: ()-->(20,24)
 │    │    │    ├── group-by
 │    │    │    │    ├── columns: a.k:13(int) array_agg:21(int[])
 │    │    │    │    ├── outer: (1)
 │    │    │    │    ├── cardinality: [0 - 1]
 │    │    │    │    ├── key: ()
 │    │    │    │    ├── fd: ()-->(13,21)
 │    │    │    │    ├── inner-join
 │    │    │    │    │    ├── columns: b.k:7(int!null) a.k:13(int!null)
 │    │    │    │    │    ├── outer: (1)
 │    │    │    │    │    ├── cardinality: [0 - 1]
 │    │    │    │    │    ├── key: ()
 │    │    │    │    │    ├── fd: ()-->(7,13)
 │    │    │    │    │    ├── select
 │    │    │    │    │    │    ├── columns: b.k:7(int!null)
 │    │    │    │    │    │    ├── outer: (1)
 │    │    │    │    │    │    ├── cardinality: [0 - 1]
 │    │    │    │    │    │    ├── key: ()
 │    │    │    │    │    │    ├── fd: ()-->(7)
 │    │    │    │    │    │    ├── scan b
 │    │    │    │    │    │    │    ├── columns: b.k:7(int!null)
 │    │    │    │    │    │    │    └── key: (7)
 │    │    │    │    │    │    └── filters
 │    │    │    │    │    │         └── b.k = c.k [type=bool, outer=(1,7), constraints=(/1: (/NULL - ]; /7: (/NULL - ]), fd=(1)==(7), (7)==(1)]
 │    │    │    │    │    ├── scan a
 │    │    │    │    │    │    ├── columns: a.k:13(int!null)
 │    │    │    │    │    │    └── key: (13)
 │    │    │    │    │    └── filters
 │    │    │    │    │         └── a.k = b.k [type=bool, outer=(7,13), constraints=(/7: (/NULL - ]; /13: (/NULL - ]), fd=(7)==(13), (13)==(7)]
 │    │    │    │    └── aggregations
 │    │    │    │         ├── array-agg [type=int[], outer=(13)]
 │    │    │    │         │    └── variable: a.k [type=int]
 │    │    │    │         └── any-not-null-agg [type=int, outer=(13)]
 │    │    │    │              └── variable: a.k [type=int]
 │    │    │    └── projections
 │    │    │         ├── true [type=bool]
 │    │    │         └── COALESCE(CASE WHEN a.k IS NOT NULL THEN array_agg END, ARRAY[])[1] [type=int, outer=(13,21)]
 │    │    └── filters (true)
 │    └── aggregations
 │         ├── array-agg [type=int[], outer=(20)]
 │         │    └── variable: array [type=int]
 │         └── any-not-null-agg [type=bool, outer=(24)]
 │              └── variable: canary [type=bool]
 └── projections
      └── COALESCE(CASE WHEN canary IS NOT NULL THEN array_agg END, ARRAY[]) [type=int[], outer=(24,25)]

# Shouldn't trigger if there's no correlation.
norm expect-not=NormalizeArrayFlattenToAgg
SELECT ARRAY(SELECT k FROM a) FROM a
----
project
 ├── columns: array:13(int[])
 ├── fd: ()-->(13)
 ├── scan a
 └── projections
      └── array-flatten [type=int[], subquery]
           └── scan a
                ├── columns: k:7(int!null)
                └── key: (7)
