exec-ddl
CREATE TABLE a (k INT PRIMARY KEY, i INT, f FLOAT, s STRING, j JSON)
----

exec-ddl
CREATE TABLE xy (x INT PRIMARY KEY, y INT)
----

exec-ddl
CREATE TABLE uv (u INT PRIMARY KEY, v INT)
----

exec-ddl
CREATE TABLE cd (c INT PRIMARY KEY, d INT NOT NULL)
----

exec-ddl
CREATE TABLE ab (a INT, b INT)
----

exec-ddl
CREATE TABLE xy_decimal (x DECIMAL(10, 2) PRIMARY KEY, y DECIMAL(10, 2));
----

exec-ddl
CREATE TABLE ab_decimal (a DECIMAL(10, 0), b DECIMAL(10, 0));
----

# --------------------------------------------------
# DecorrelateJoin
# --------------------------------------------------
norm expect=DecorrelateJoin
SELECT * FROM a WHERE EXISTS(SELECT * FROM xy WHERE x=k)
----
semi-join (hash)
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── scan xy
 │    ├── columns: x:8!null
 │    └── key: (8)
 └── filters
      └── x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]

norm expect=DecorrelateJoin
SELECT * FROM a WHERE NOT EXISTS(SELECT * FROM xy WHERE x=k)
----
anti-join (hash)
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── scan xy
 │    ├── columns: x:8!null
 │    └── key: (8)
 └── filters
      └── x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]

# Decorrelate UPDATE statement.
norm expect=DecorrelateJoin
UPDATE xy SET (x, y)=(SELECT * FROM uv WHERE u=x)
----
update xy
 ├── columns: <none>
 ├── fetch columns: x:5 y:6
 ├── update-mapping:
 │    ├── u:9 => x:1
 │    └── v:10 => y:2
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── left-join (hash)
      ├── columns: x:5!null y:6 u:9 v:10
      ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
      ├── key: (5)
      ├── fd: (5)-->(6,9,10), (9)-->(10)
      ├── scan xy
      │    ├── columns: x:5!null y:6
      │    ├── flags: avoid-full-scan
      │    ├── key: (5)
      │    └── fd: (5)-->(6)
      ├── scan uv
      │    ├── columns: u:9!null v:10
      │    ├── key: (9)
      │    └── fd: (9)-->(10)
      └── filters
           └── u:9 = x:5 [outer=(5,9), constraints=(/5: (/NULL - ]; /9: (/NULL - ]), fd=(5)==(9), (9)==(5)]

# Decorrelate INSERT..ON CONFLICT statement.
norm expect=DecorrelateJoin
INSERT INTO xy VALUES (1,2), (3,4)
ON CONFLICT (x) DO UPDATE SET (x, y)=(SELECT * FROM uv WHERE u=excluded.x)
RETURNING *
----
upsert xy
 ├── columns: x:1!null y:2
 ├── arbiter indexes: xy_pkey
 ├── canary column: x:7
 ├── fetch columns: x:7 y:8
 ├── insert-mapping:
 │    ├── column1:5 => x:1
 │    └── column2:6 => y:2
 ├── update-mapping:
 │    ├── upsert_x:15 => x:1
 │    └── upsert_y:16 => y:2
 ├── return-mapping:
 │    ├── upsert_x:15 => x:1
 │    └── upsert_y:16 => y:2
 ├── cardinality: [2 - 2]
 ├── volatile, mutations
 └── project
      ├── columns: upsert_x:15 upsert_y:16 column1:5!null column2:6!null x:7 y:8
      ├── cardinality: [2 - 2]
      ├── fd: (7)-->(8)
      ├── left-join (hash)
      │    ├── columns: column1:5!null column2:6!null x:7 y:8 u:11 v:12
      │    ├── cardinality: [2 - 2]
      │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more)
      │    ├── fd: (7)-->(8), (11)-->(12)
      │    ├── left-join (hash)
      │    │    ├── columns: column1:5!null column2:6!null x:7 y:8
      │    │    ├── cardinality: [2 - 2]
      │    │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more)
      │    │    ├── fd: (7)-->(8)
      │    │    ├── values
      │    │    │    ├── columns: column1:5!null column2:6!null
      │    │    │    ├── cardinality: [2 - 2]
      │    │    │    ├── (1, 2)
      │    │    │    └── (3, 4)
      │    │    ├── scan xy
      │    │    │    ├── columns: x:7!null y:8
      │    │    │    ├── flags: avoid-full-scan
      │    │    │    ├── key: (7)
      │    │    │    └── fd: (7)-->(8)
      │    │    └── filters
      │    │         └── column1:5 = x:7 [outer=(5,7), constraints=(/5: (/NULL - ]; /7: (/NULL - ]), fd=(5)==(7), (7)==(5)]
      │    ├── scan uv
      │    │    ├── columns: u:11!null v:12
      │    │    ├── key: (11)
      │    │    └── fd: (11)-->(12)
      │    └── filters
      │         └── u:11 = column1:5 [outer=(5,11), constraints=(/5: (/NULL - ]; /11: (/NULL - ]), fd=(5)==(11), (11)==(5)]
      └── projections
           ├── CASE WHEN x:7 IS NULL THEN column1:5 ELSE u:11 END [as=upsert_x:15, outer=(5,7,11)]
           └── CASE WHEN x:7 IS NULL THEN column2:6 ELSE v:12 END [as=upsert_y:16, outer=(6,7,12)]

# Decorrelate DELETE statement.
norm expect=DecorrelateJoin
DELETE FROM xy WHERE EXISTS(SELECT * FROM uv WHERE u=x)
----
delete xy
 ├── columns: <none>
 ├── fetch columns: x:5
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── semi-join (hash)
      ├── columns: x:5!null
      ├── key: (5)
      ├── scan xy
      │    ├── columns: x:5!null
      │    ├── flags: avoid-full-scan
      │    └── key: (5)
      ├── scan uv
      │    ├── columns: u:9!null
      │    └── key: (9)
      └── filters
           └── u:9 = x:5 [outer=(5,9), constraints=(/5: (/NULL - ]; /9: (/NULL - ]), fd=(5)==(9), (9)==(5)]

# --------------------------------------------------
# DecorrelateProjectSet
# --------------------------------------------------

norm expect=DecorrelateProjectSet
SELECT generate_series(0, 5) FROM xy
----
inner-join (cross)
 ├── columns: generate_series:5
 ├── immutable
 ├── scan xy
 ├── project-set
 │    ├── columns: generate_series:5
 │    ├── immutable
 │    ├── values
 │    │    ├── cardinality: [1 - 1]
 │    │    ├── key: ()
 │    │    └── ()
 │    └── zip
 │         └── generate_series(0, 5) [immutable]
 └── filters (true)

norm expect=DecorrelateProjectSet
SELECT * FROM a WHERE i IN (SELECT generate_series(k, i) FROM xy)
----
semi-join-apply
 ├── columns: k:1!null i:2!null f:3 s:4 j:5
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── inner-join (cross)
 │    ├── columns: generate_series:12
 │    ├── outer: (1,2)
 │    ├── immutable
 │    ├── scan xy
 │    ├── project-set
 │    │    ├── columns: generate_series:12
 │    │    ├── outer: (1,2)
 │    │    ├── immutable
 │    │    ├── values
 │    │    │    ├── cardinality: [1 - 1]
 │    │    │    ├── key: ()
 │    │    │    └── ()
 │    │    └── zip
 │    │         └── generate_series(k:1, i:2) [outer=(1,2), immutable]
 │    └── filters (true)
 └── filters
      └── i:2 = generate_series:12 [outer=(2,12), constraints=(/2: (/NULL - ]; /12: (/NULL - ]), fd=(2)==(12), (12)==(2)]

norm expect=DecorrelateProjectSet
SELECT generate_series(0, (SELECT generate_series(1,0) FROM xy)) FROM uv
----
inner-join (cross)
 ├── columns: generate_series:10
 ├── immutable
 ├── scan uv
 ├── project-set
 │    ├── columns: generate_series:10
 │    ├── immutable
 │    ├── values
 │    │    ├── cardinality: [1 - 1]
 │    │    ├── key: ()
 │    │    └── ()
 │    └── zip
 │         └── function: generate_series [immutable, subquery]
 │              ├── 0
 │              └── subquery
 │                   └── max1-row
 │                        ├── columns: generate_series:9
 │                        ├── error: "more than one row returned by a subquery used as an expression"
 │                        ├── cardinality: [0 - 1]
 │                        ├── immutable
 │                        ├── key: ()
 │                        ├── fd: ()-->(9)
 │                        └── inner-join (cross)
 │                             ├── columns: generate_series:9
 │                             ├── immutable
 │                             ├── scan xy
 │                             ├── project-set
 │                             │    ├── columns: generate_series:9
 │                             │    ├── immutable
 │                             │    ├── values
 │                             │    │    ├── cardinality: [1 - 1]
 │                             │    │    ├── key: ()
 │                             │    │    └── ()
 │                             │    └── zip
 │                             │         └── generate_series(1, 0) [immutable]
 │                             └── filters (true)
 └── filters (true)

# --------------------------------------------------
# TryDecorrelateWindow
# --------------------------------------------------

norm expect=TryDecorrelateWindow
SELECT
    *
FROM
    uv,
    LATERAL (SELECT rank() OVER (), i FROM (SELECT * FROM a WHERE k = u))
WHERE i = 3
----
project
 ├── columns: u:1!null v:2 rank:12 i:6!null
 ├── key: (1)
 ├── fd: ()-->(6), (1)-->(2,12)
 └── window partition=(1)
      ├── columns: u:1!null v:2 k:5!null i:6!null rank:12
      ├── key: (5)
      ├── fd: ()-->(6), (1)-->(2,5,12), (1)==(5), (5)==(1)
      ├── inner-join (hash)
      │    ├── columns: u:1!null v:2 k:5!null i:6!null
      │    ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one)
      │    ├── key: (5)
      │    ├── fd: ()-->(6), (1)-->(2), (1)==(5), (5)==(1)
      │    ├── scan uv
      │    │    ├── columns: u:1!null v:2
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2)
      │    ├── select
      │    │    ├── columns: k:5!null i:6!null
      │    │    ├── key: (5)
      │    │    ├── fd: ()-->(6)
      │    │    ├── scan a
      │    │    │    ├── columns: k:5!null i:6
      │    │    │    ├── key: (5)
      │    │    │    └── fd: (5)-->(6)
      │    │    └── filters
      │    │         └── i:6 = 3 [outer=(6), constraints=(/6: [/3 - /3]; tight), fd=()-->(6)]
      │    └── filters
      │         └── k:5 = u:1 [outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
      └── windows
           └── rank [as=rank:12]

# TryDecorrelateWindow will trigger twice here: first to pull the window above
# the non-apply join, and then again the pull it above the apply join.
norm expect=TryDecorrelateWindow
SELECT
    *
FROM
    uv,
    LATERAL (
        SELECT
            *
        FROM
            (SELECT x FROM xy) CROSS JOIN (SELECT ntile(u) OVER (), i FROM a)
    )
WHERE
    i = 3
----
project
 ├── columns: u:1!null v:2 x:5!null ntile:16 i:10!null
 ├── fd: ()-->(10), (1)-->(2)
 └── select
      ├── columns: u:1!null v:2 x:5!null i:10!null ntile:16 ntile_1_arg1:17!null
      ├── fd: ()-->(10), (1)-->(2), (1)==(17), (17)==(1)
      ├── window partition=(1,5)
      │    ├── columns: u:1!null v:2 x:5!null i:10 ntile:16 ntile_1_arg1:17!null
      │    ├── fd: (1)-->(2), (1)==(17), (17)==(1)
      │    ├── project
      │    │    ├── columns: ntile_1_arg1:17!null u:1!null v:2 x:5!null i:10
      │    │    ├── fd: (1)-->(2), (1)==(17), (17)==(1)
      │    │    ├── inner-join (cross)
      │    │    │    ├── columns: u:1!null v:2 x:5!null i:10
      │    │    │    ├── fd: (1)-->(2)
      │    │    │    ├── scan uv
      │    │    │    │    ├── columns: u:1!null v:2
      │    │    │    │    ├── key: (1)
      │    │    │    │    └── fd: (1)-->(2)
      │    │    │    ├── inner-join (cross)
      │    │    │    │    ├── columns: x:5!null i:10
      │    │    │    │    ├── scan xy
      │    │    │    │    │    ├── columns: x:5!null
      │    │    │    │    │    └── key: (5)
      │    │    │    │    ├── scan a
      │    │    │    │    │    └── columns: i:10
      │    │    │    │    └── filters (true)
      │    │    │    └── filters (true)
      │    │    └── projections
      │    │         └── u:1 [as=ntile_1_arg1:17, outer=(1)]
      │    └── windows
      │         └── ntile [as=ntile:16, outer=(17)]
      │              └── ntile_1_arg1:17
      └── filters
           └── i:10 = 3 [outer=(10), constraints=(/10: [/3 - /3]; tight), fd=()-->(10)]

# If the LHS has no key, we need to add one, or else identical rows would end up in the same
# partition.

norm expect=TryDecorrelateWindow
SELECT
    *
FROM
    (VALUES (1), (1), (1)) AS v (x),
    LATERAL (SELECT row_number() OVER (), i FROM (SELECT * FROM a WHERE k = x))
----
project
 ├── columns: x:1!null row_number:9 i:3
 ├── cardinality: [0 - 3]
 ├── fd: (1)-->(3)
 └── window partition=(10)
      ├── columns: column1:1!null k:2!null i:3 row_number:9 rownum:10!null
      ├── cardinality: [0 - 3]
      ├── key: (10)
      ├── fd: (10)-->(1-3,9), (2)-->(3), (1)==(2), (2)==(1)
      ├── inner-join (hash)
      │    ├── columns: column1:1!null k:2!null i:3 rownum:10!null
      │    ├── cardinality: [0 - 3]
      │    ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-more)
      │    ├── key: (10)
      │    ├── fd: (10)-->(1), (2)-->(3), (1)==(2), (2)==(1)
      │    ├── ordinality
      │    │    ├── columns: column1:1!null rownum:10!null
      │    │    ├── cardinality: [3 - 3]
      │    │    ├── key: (10)
      │    │    ├── fd: (10)-->(1)
      │    │    └── values
      │    │         ├── columns: column1:1!null
      │    │         ├── cardinality: [3 - 3]
      │    │         ├── (1,)
      │    │         ├── (1,)
      │    │         └── (1,)
      │    ├── scan a
      │    │    ├── columns: k:2!null i:3
      │    │    ├── key: (2)
      │    │    └── fd: (2)-->(3)
      │    └── filters
      │         └── k:2 = column1:1 [outer=(1,2), constraints=(/1: (/NULL - ]; /2: (/NULL - ]), fd=(1)==(2), (2)==(1)]
      └── windows
           └── row-number [as=row_number:9]

norm expect=TryDecorrelateWindow
SELECT
    *
FROM
    (VALUES (1), (1), (1)) AS v (x),
    LATERAL (SELECT row_number() OVER (ORDER BY i), i FROM (SELECT * FROM a WHERE x > 3))
----
project
 ├── columns: x:1!null row_number:9 i:3
 └── window partition=(10) ordering=+3 opt(1,10)
      ├── columns: column1:1!null i:3 row_number:9 rownum:10!null
      ├── fd: (10)-->(1)
      ├── inner-join (cross)
      │    ├── columns: column1:1!null i:3 rownum:10!null
      │    ├── fd: (10)-->(1)
      │    ├── ordinality
      │    │    ├── columns: column1:1!null rownum:10!null
      │    │    ├── cardinality: [0 - 3]
      │    │    ├── key: (10)
      │    │    ├── fd: (10)-->(1)
      │    │    └── select
      │    │         ├── columns: column1:1!null
      │    │         ├── cardinality: [0 - 3]
      │    │         ├── values
      │    │         │    ├── columns: column1:1!null
      │    │         │    ├── cardinality: [3 - 3]
      │    │         │    ├── (1,)
      │    │         │    ├── (1,)
      │    │         │    └── (1,)
      │    │         └── filters
      │    │              └── column1:1 > 3 [outer=(1), constraints=(/1: [/4 - ]; tight)]
      │    ├── scan a
      │    │    └── columns: i:3
      │    └── filters (true)
      └── windows
           └── row-number [as=row_number:9]

# In this example, we introduce a key called rownum, and after TryDecorrelateWindow triggers
# PARTITION BY x becomes PARTITION BY x, rownum. Then later, ReduceWindowPartitionCols triggers,
# recognizing that rownum determines x.

norm expect=TryDecorrelateWindow
SELECT
    *
FROM
    (VALUES (1), (1), (1)) AS v (x),
    LATERAL (SELECT row_number() OVER (PARTITION BY x), i FROM (SELECT * FROM a WHERE k = x))
----
project
 ├── columns: x:1!null row_number:9 i:3
 ├── cardinality: [0 - 3]
 ├── fd: (1)-->(3)
 └── window partition=(11)
      ├── columns: column1:1!null i:3 row_number:9 rownum:11!null
      ├── cardinality: [0 - 3]
      ├── key: (11)
      ├── fd: (11)-->(1,3,9), (1)-->(3)
      ├── project
      │    ├── columns: column1:1!null i:3 rownum:11!null
      │    ├── cardinality: [0 - 3]
      │    ├── key: (11)
      │    ├── fd: (11)-->(1), (1)-->(3)
      │    └── inner-join (hash)
      │         ├── columns: column1:1!null k:2!null i:3 rownum:11!null
      │         ├── cardinality: [0 - 3]
      │         ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-more)
      │         ├── key: (11)
      │         ├── fd: (11)-->(1), (2)-->(3), (1)==(2), (2)==(1)
      │         ├── ordinality
      │         │    ├── columns: column1:1!null rownum:11!null
      │         │    ├── cardinality: [3 - 3]
      │         │    ├── key: (11)
      │         │    ├── fd: (11)-->(1)
      │         │    └── values
      │         │         ├── columns: column1:1!null
      │         │         ├── cardinality: [3 - 3]
      │         │         ├── (1,)
      │         │         ├── (1,)
      │         │         └── (1,)
      │         ├── scan a
      │         │    ├── columns: k:2!null i:3
      │         │    ├── key: (2)
      │         │    └── fd: (2)-->(3)
      │         └── filters
      │              └── k:2 = column1:1 [outer=(1,2), constraints=(/1: (/NULL - ]; /2: (/NULL - ]), fd=(1)==(2), (2)==(1)]
      └── windows
           └── row-number [as=row_number:9]

norm expect=TryDecorrelateWindow
SELECT
    *
FROM
    (VALUES (1, 2), (1, 3), (1, 4)) AS v (x, y),
    LATERAL (SELECT row_number() OVER (PARTITION BY x ORDER BY y), i FROM (SELECT * FROM a WHERE k = x))
----
project
 ├── columns: x:1!null y:2!null row_number:10 i:4
 ├── cardinality: [0 - 3]
 ├── fd: (1)-->(4)
 └── window partition=(13)
      ├── columns: column1:1!null column2:2!null i:4 row_number:10 rownum:13!null
      ├── cardinality: [0 - 3]
      ├── key: (13)
      ├── fd: (13)-->(1,2,4,10), (1)-->(4)
      ├── project
      │    ├── columns: column1:1!null column2:2!null i:4 rownum:13!null
      │    ├── cardinality: [0 - 3]
      │    ├── key: (13)
      │    ├── fd: (13)-->(1,2), (1)-->(4)
      │    └── inner-join (hash)
      │         ├── columns: column1:1!null column2:2!null k:3!null i:4 rownum:13!null
      │         ├── cardinality: [0 - 3]
      │         ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-more)
      │         ├── key: (13)
      │         ├── fd: (13)-->(1,2), (3)-->(4), (1)==(3), (3)==(1)
      │         ├── ordinality
      │         │    ├── columns: column1:1!null column2:2!null rownum:13!null
      │         │    ├── cardinality: [3 - 3]
      │         │    ├── key: (13)
      │         │    ├── fd: (13)-->(1,2)
      │         │    └── values
      │         │         ├── columns: column1:1!null column2:2!null
      │         │         ├── cardinality: [3 - 3]
      │         │         ├── (1, 2)
      │         │         ├── (1, 3)
      │         │         └── (1, 4)
      │         ├── scan a
      │         │    ├── columns: k:3!null i:4
      │         │    ├── key: (3)
      │         │    └── fd: (3)-->(4)
      │         └── filters
      │              └── k:3 = column1:1 [outer=(1,3), constraints=(/1: (/NULL - ]; /3: (/NULL - ]), fd=(1)==(3), (3)==(1)]
      └── windows
           └── row-number [as=row_number:10]

norm expect=TryDecorrelateWindow
SELECT
    *
FROM
    uv,
    LATERAL (SELECT row_number() OVER (PARTITION BY u), i FROM (SELECT * FROM a WHERE k = u))
----
window partition=(1)
 ├── columns: u:1!null v:2 row_number:12 i:6
 ├── key: (1)
 ├── fd: (1)-->(2,6,12)
 ├── project
 │    ├── columns: u:1!null v:2 i:6
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,6)
 │    └── inner-join (hash)
 │         ├── columns: u:1!null v:2 k:5!null i:6
 │         ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one)
 │         ├── key: (5)
 │         ├── fd: (1)-->(2), (5)-->(6), (1)==(5), (5)==(1)
 │         ├── scan uv
 │         │    ├── columns: u:1!null v:2
 │         │    ├── key: (1)
 │         │    └── fd: (1)-->(2)
 │         ├── scan a
 │         │    ├── columns: k:5!null i:6
 │         │    ├── key: (5)
 │         │    └── fd: (5)-->(6)
 │         └── filters
 │              └── k:5 = u:1 [outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
 └── windows
      └── row-number [as=row_number:12]

norm expect=TryDecorrelateWindow
SELECT
    *
FROM
    uv,
    LATERAL (SELECT row_number() OVER (PARTITION BY s), i FROM (SELECT * FROM a WHERE k = u))
----
project
 ├── columns: u:1!null v:2 row_number:12 i:6
 ├── key: (1)
 ├── fd: (1)-->(2,6,12)
 └── window partition=(1)
      ├── columns: u:1!null v:2 k:5!null i:6 row_number:12
      ├── key: (5)
      ├── fd: (1)-->(2,5,6,12), (5)-->(6), (1)==(5), (5)==(1)
      ├── inner-join (hash)
      │    ├── columns: u:1!null v:2 k:5!null i:6
      │    ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one)
      │    ├── key: (5)
      │    ├── fd: (1)-->(2), (5)-->(6), (1)==(5), (5)==(1)
      │    ├── scan uv
      │    │    ├── columns: u:1!null v:2
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2)
      │    ├── scan a
      │    │    ├── columns: k:5!null i:6
      │    │    ├── key: (5)
      │    │    └── fd: (5)-->(6)
      │    └── filters
      │         └── k:5 = u:1 [outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
      └── windows
           └── row-number [as=row_number:12]

norm expect=TryDecorrelateWindow
SELECT
    *
FROM
    uv,
    LATERAL (SELECT row_number() OVER (PARTITION BY s), i FROM (SELECT * FROM a WHERE i = u))
----
project
 ├── columns: u:1!null v:2 row_number:12 i:6!null
 ├── fd: (1)-->(2), (1)==(6), (6)==(1)
 └── window partition=(1,8)
      ├── columns: u:1!null v:2 i:6!null s:8 row_number:12
      ├── fd: (1)-->(2), (1)==(6), (6)==(1)
      ├── inner-join (hash)
      │    ├── columns: u:1!null v:2 i:6!null s:8
      │    ├── multiplicity: left-rows(zero-or-more), right-rows(zero-or-one)
      │    ├── fd: (1)-->(2), (1)==(6), (6)==(1)
      │    ├── scan uv
      │    │    ├── columns: u:1!null v:2
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2)
      │    ├── scan a
      │    │    └── columns: i:6 s:8
      │    └── filters
      │         └── i:6 = u:1 [outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ]), fd=(1)==(6), (6)==(1)]
      └── windows
           └── row-number [as=row_number:12]

norm expect=TryDecorrelateWindow
SELECT * FROM uv, LATERAL (SELECT avg(f) FILTER (WHERE u = 3) OVER (), i FROM a)
----
project
 ├── columns: u:1!null v:2 avg:12 i:6
 ├── fd: (1)-->(2)
 └── window partition=(1)
      ├── columns: u:1!null v:2 i:6 f:7 avg:12 avg_1_filter:13!null
      ├── fd: (1)-->(2,13)
      ├── project
      │    ├── columns: avg_1_filter:13!null u:1!null v:2 i:6 f:7
      │    ├── fd: (1)-->(2,13)
      │    ├── inner-join (cross)
      │    │    ├── columns: u:1!null v:2 i:6 f:7
      │    │    ├── fd: (1)-->(2)
      │    │    ├── scan uv
      │    │    │    ├── columns: u:1!null v:2
      │    │    │    ├── key: (1)
      │    │    │    └── fd: (1)-->(2)
      │    │    ├── scan a
      │    │    │    └── columns: i:6 f:7
      │    │    └── filters (true)
      │    └── projections
      │         └── u:1 = 3 [as=avg_1_filter:13, outer=(1)]
      └── windows
           └── agg-filter [as=avg:12, outer=(7,13)]
                ├── avg
                │    └── f:7
                └── avg_1_filter:13

# --------------------------------------------------
# TryDecorrelateSelect
# --------------------------------------------------
norm expect=TryDecorrelateSelect
SELECT * FROM a WHERE EXISTS(SELECT * FROM (VALUES (k), (i)) WHERE column1=k)
----
semi-join-apply
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── values
 │    ├── columns: column1:8
 │    ├── outer: (1,2)
 │    ├── cardinality: [2 - 2]
 │    ├── (k:1,)
 │    └── (i:2,)
 └── filters
      └── column1:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]

norm expect=TryDecorrelateSelect
SELECT * FROM a WHERE NOT EXISTS(SELECT * FROM (VALUES (k), (i)) WHERE column1=k)
----
anti-join-apply
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── values
 │    ├── columns: column1:8
 │    ├── outer: (1,2)
 │    ├── cardinality: [2 - 2]
 │    ├── (k:1,)
 │    └── (i:2,)
 └── filters
      └── column1:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]

# Attempt to decorrelate query by pulling up outer select. But since limit query
# cannot be decorrelated, push the outer select back down again (and make sure
# potential rule cycle is detected and handled).
norm expect=TryDecorrelateSelect
SELECT * FROM a WHERE EXISTS(SELECT * FROM (SELECT * FROM xy WHERE y=k LIMIT 1) WHERE y=10)
----
semi-join-apply
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── limit
 │    ├── columns: y:9!null
 │    ├── outer: (1)
 │    ├── cardinality: [0 - 1]
 │    ├── key: ()
 │    ├── fd: ()-->(9)
 │    ├── select
 │    │    ├── columns: y:9!null
 │    │    ├── outer: (1)
 │    │    ├── fd: ()-->(9)
 │    │    ├── limit hint: 1.00
 │    │    ├── scan xy
 │    │    │    ├── columns: y:9
 │    │    │    └── limit hint: 1.01
 │    │    └── filters
 │    │         └── y:9 = k:1 [outer=(1,9), constraints=(/1: (/NULL - ]; /9: (/NULL - ]), fd=(1)==(9), (9)==(1)]
 │    └── 1
 └── filters
      └── y:9 = 10 [outer=(9), constraints=(/9: [/10 - /10]; tight), fd=()-->(9)]

# Same as previous, but using anti-join.
norm expect=TryDecorrelateSelect
SELECT * FROM a WHERE NOT EXISTS(SELECT * FROM (SELECT * FROM xy WHERE y=k LIMIT 1) WHERE y=10)
----
anti-join-apply
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── limit
 │    ├── columns: y:9!null
 │    ├── outer: (1)
 │    ├── cardinality: [0 - 1]
 │    ├── key: ()
 │    ├── fd: ()-->(9)
 │    ├── select
 │    │    ├── columns: y:9!null
 │    │    ├── outer: (1)
 │    │    ├── fd: ()-->(9)
 │    │    ├── limit hint: 1.00
 │    │    ├── scan xy
 │    │    │    ├── columns: y:9
 │    │    │    └── limit hint: 1.01
 │    │    └── filters
 │    │         └── y:9 = k:1 [outer=(1,9), constraints=(/1: (/NULL - ]; /9: (/NULL - ]), fd=(1)==(9), (9)==(1)]
 │    └── 1
 └── filters
      └── y:9 = 10 [outer=(9), constraints=(/9: [/10 - /10]; tight), fd=()-->(9)]

# Decorrelate Select with reference to outer column and no limit.
norm expect=TryDecorrelateSelect
SELECT * FROM a WHERE (SELECT x FROM xy WHERE x=i) > 100
----
project
 ├── columns: k:1!null i:2!null f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 └── inner-join (hash)
      ├── columns: k:1!null i:2!null f:3 s:4 j:5 x:8!null
      ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-more)
      ├── key: (1)
      ├── fd: (1)-->(2-5), (2)==(8), (8)==(2)
      ├── select
      │    ├── columns: k:1!null i:2!null f:3 s:4 j:5
      │    ├── key: (1)
      │    ├── fd: (1)-->(2-5)
      │    ├── scan a
      │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2-5)
      │    └── filters
      │         └── i:2 > 100 [outer=(2), constraints=(/2: [/101 - ]; tight)]
      ├── select
      │    ├── columns: x:8!null
      │    ├── key: (8)
      │    ├── scan xy
      │    │    ├── columns: x:8!null
      │    │    └── key: (8)
      │    └── filters
      │         └── x:8 > 100 [outer=(8), constraints=(/8: [/101 - ]; tight)]
      └── filters
           └── x:8 = i:2 [outer=(2,8), constraints=(/2: (/NULL - ]; /8: (/NULL - ]), fd=(2)==(8), (8)==(2)]

# Decorrelate Select with LeftJoinApply.
norm expect=TryDecorrelateSelect
SELECT * FROM a WHERE (SELECT x FROM (SELECT * FROM xy LIMIT 1) WHERE k=x) > 100
----
project
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(1-5)
 └── inner-join (hash)
      ├── columns: k:1!null i:2 f:3 s:4 j:5 x:8!null
      ├── cardinality: [0 - 1]
      ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one)
      ├── key: ()
      ├── fd: ()-->(1-5,8), (1)==(8), (8)==(1)
      ├── select
      │    ├── columns: k:1!null i:2 f:3 s:4 j:5
      │    ├── key: (1)
      │    ├── fd: (1)-->(2-5)
      │    ├── scan a
      │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2-5)
      │    └── filters
      │         └── k:1 > 100 [outer=(1), constraints=(/1: [/101 - ]; tight)]
      ├── select
      │    ├── columns: x:8!null
      │    ├── cardinality: [0 - 1]
      │    ├── key: ()
      │    ├── fd: ()-->(8)
      │    ├── limit
      │    │    ├── columns: x:8!null
      │    │    ├── cardinality: [0 - 1]
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(8)
      │    │    ├── scan xy
      │    │    │    ├── columns: x:8!null
      │    │    │    ├── key: (8)
      │    │    │    └── limit hint: 1.00
      │    │    └── 1
      │    └── filters
      │         └── x:8 > 100 [outer=(8), constraints=(/8: [/101 - ]; tight)]
      └── filters
           └── k:1 = x:8 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]

# Decorrelate with non-apply operator because of multi-level nesting.
norm expect=TryDecorrelateSelect
SELECT *
FROM a
WHERE EXISTS(SELECT * FROM xy WHERE x=k AND EXISTS(SELECT * FROM uv WHERE u=10 AND s='foo'))
----
semi-join-apply
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── semi-join (cross)
 │    ├── columns: x:8!null
 │    ├── outer: (4)
 │    ├── key: (8)
 │    ├── scan xy
 │    │    ├── columns: x:8!null
 │    │    └── key: (8)
 │    ├── select
 │    │    ├── columns: u:12!null
 │    │    ├── cardinality: [0 - 1]
 │    │    ├── key: ()
 │    │    ├── fd: ()-->(12)
 │    │    ├── scan uv
 │    │    │    ├── columns: u:12!null
 │    │    │    └── key: (12)
 │    │    └── filters
 │    │         └── u:12 = 10 [outer=(12), constraints=(/12: [/10 - /10]; tight), fd=()-->(12)]
 │    └── filters
 │         └── s:4 = 'foo' [outer=(4), constraints=(/4: [/'foo' - /'foo']; tight), fd=()-->(4)]
 └── filters
      └── x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]

# --------------------------------------------------
# TryDecorrelateProject +
# TryDecorrelateProjectSelect +
# TryDecorrelateScalarGroupBy
#
# Start with some shared test cases that exercise multiple
# decorrelation rules.
# --------------------------------------------------

# Left join caused by correlated ANY clause.
norm expect=(TryDecorrelateProject,TryDecorrelateProjectSelect,TryDecorrelateScalarGroupBy)
SELECT 5=ANY(SELECT y FROM xy WHERE x=k) AS r FROM a
----
project
 ├── columns: r:12
 ├── group-by (hash)
 │    ├── columns: k:1!null bool_or:14
 │    ├── grouping columns: k:1!null
 │    ├── key: (1)
 │    ├── fd: (1)-->(14)
 │    ├── left-join (hash)
 │    │    ├── columns: k:1!null x:8 notnull:13
 │    │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
 │    │    ├── key: (1)
 │    │    ├── fd: (8)-->(13), (1)-->(8,13)
 │    │    ├── scan a
 │    │    │    ├── columns: k:1!null
 │    │    │    └── key: (1)
 │    │    ├── project
 │    │    │    ├── columns: notnull:13!null x:8!null
 │    │    │    ├── key: (8)
 │    │    │    ├── fd: (8)-->(13)
 │    │    │    ├── select
 │    │    │    │    ├── columns: x:8!null y:9
 │    │    │    │    ├── key: (8)
 │    │    │    │    ├── fd: (8)-->(9)
 │    │    │    │    ├── scan xy
 │    │    │    │    │    ├── columns: x:8!null y:9
 │    │    │    │    │    ├── key: (8)
 │    │    │    │    │    └── fd: (8)-->(9)
 │    │    │    │    └── filters
 │    │    │    │         └── (y:9 = 5) IS NOT false [outer=(9)]
 │    │    │    └── projections
 │    │    │         └── y:9 IS NOT NULL [as=notnull:13, outer=(9)]
 │    │    └── filters
 │    │         └── x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]
 │    └── aggregations
 │         └── bool-or [as=bool_or:14, outer=(13)]
 │              └── notnull:13
 └── projections
      └── CASE WHEN bool_or:14 THEN true WHEN bool_or:14 IS NULL THEN false ELSE CAST(NULL AS BOOL) END [as=r:12, outer=(14)]

# Left join caused by zero or one cardinality subquery.
norm expect=TryDecorrelateProjectSelect
SELECT * FROM a WHERE (SELECT y+1 AS r FROM (SELECT * FROM xy LIMIT 1) WHERE x=k) > 10
----
project
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(1-5)
 └── inner-join (hash)
      ├── columns: k:1!null i:2 f:3 s:4 j:5 x:8!null y:9!null
      ├── cardinality: [0 - 1]
      ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one)
      ├── key: ()
      ├── fd: ()-->(1-5,8,9), (1)==(8), (8)==(1)
      ├── scan a
      │    ├── columns: k:1!null i:2 f:3 s:4 j:5
      │    ├── key: (1)
      │    └── fd: (1)-->(2-5)
      ├── select
      │    ├── columns: x:8!null y:9!null
      │    ├── cardinality: [0 - 1]
      │    ├── key: ()
      │    ├── fd: ()-->(8,9)
      │    ├── limit
      │    │    ├── columns: x:8!null y:9
      │    │    ├── cardinality: [0 - 1]
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(8,9)
      │    │    ├── scan xy
      │    │    │    ├── columns: x:8!null y:9
      │    │    │    ├── key: (8)
      │    │    │    ├── fd: (8)-->(9)
      │    │    │    └── limit hint: 1.00
      │    │    └── 1
      │    └── filters
      │         └── y:9 > 9 [outer=(9), constraints=(/9: [/10 - ]; tight)]
      └── filters
           └── x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]

# Any clause with constant.
norm expect=(TryDecorrelateProject,TryDecorrelateProjectSelect,TryDecorrelateScalarGroupBy)
SELECT 5=ANY(SELECT y FROM xy WHERE x=k) AS r FROM a
----
project
 ├── columns: r:12
 ├── group-by (hash)
 │    ├── columns: k:1!null bool_or:14
 │    ├── grouping columns: k:1!null
 │    ├── key: (1)
 │    ├── fd: (1)-->(14)
 │    ├── left-join (hash)
 │    │    ├── columns: k:1!null x:8 notnull:13
 │    │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
 │    │    ├── key: (1)
 │    │    ├── fd: (8)-->(13), (1)-->(8,13)
 │    │    ├── scan a
 │    │    │    ├── columns: k:1!null
 │    │    │    └── key: (1)
 │    │    ├── project
 │    │    │    ├── columns: notnull:13!null x:8!null
 │    │    │    ├── key: (8)
 │    │    │    ├── fd: (8)-->(13)
 │    │    │    ├── select
 │    │    │    │    ├── columns: x:8!null y:9
 │    │    │    │    ├── key: (8)
 │    │    │    │    ├── fd: (8)-->(9)
 │    │    │    │    ├── scan xy
 │    │    │    │    │    ├── columns: x:8!null y:9
 │    │    │    │    │    ├── key: (8)
 │    │    │    │    │    └── fd: (8)-->(9)
 │    │    │    │    └── filters
 │    │    │    │         └── (y:9 = 5) IS NOT false [outer=(9)]
 │    │    │    └── projections
 │    │    │         └── y:9 IS NOT NULL [as=notnull:13, outer=(9)]
 │    │    └── filters
 │    │         └── x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]
 │    └── aggregations
 │         └── bool-or [as=bool_or:14, outer=(13)]
 │              └── notnull:13
 └── projections
      └── CASE WHEN bool_or:14 THEN true WHEN bool_or:14 IS NULL THEN false ELSE CAST(NULL AS BOOL) END [as=r:12, outer=(14)]

# Any clause with variable.
norm expect=(TryDecorrelateProject,TryDecorrelateProjectSelect,TryDecorrelateScalarGroupBy)
SELECT i=ANY(SELECT y FROM xy WHERE x=k) AS r FROM a
----
project
 ├── columns: r:12
 ├── group-by (hash)
 │    ├── columns: k:1!null i:2 bool_or:14
 │    ├── grouping columns: k:1!null
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,14)
 │    ├── left-join (hash)
 │    │    ├── columns: k:1!null i:2 x:8 y:9 notnull:13
 │    │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(2,8,9,13), (8)-->(9), (9)~~>(13)
 │    │    ├── scan a
 │    │    │    ├── columns: k:1!null i:2
 │    │    │    ├── key: (1)
 │    │    │    └── fd: (1)-->(2)
 │    │    ├── project
 │    │    │    ├── columns: notnull:13!null x:8!null y:9
 │    │    │    ├── key: (8)
 │    │    │    ├── fd: (8)-->(9), (9)-->(13)
 │    │    │    ├── scan xy
 │    │    │    │    ├── columns: x:8!null y:9
 │    │    │    │    ├── key: (8)
 │    │    │    │    └── fd: (8)-->(9)
 │    │    │    └── projections
 │    │    │         └── y:9 IS NOT NULL [as=notnull:13, outer=(9)]
 │    │    └── filters
 │    │         ├── x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]
 │    │         └── (i:2 = y:9) IS NOT false [outer=(2,9)]
 │    └── aggregations
 │         ├── bool-or [as=bool_or:14, outer=(13)]
 │         │    └── notnull:13
 │         └── const-agg [as=i:2, outer=(2)]
 │              └── i:2
 └── projections
      └── CASE WHEN bool_or:14 AND (i:2 IS NOT NULL) THEN true WHEN bool_or:14 IS NULL THEN false ELSE CAST(NULL AS BOOL) END [as=r:12, outer=(2,14)]

# Any clause with more complex expression that must be cached.
norm expect=(TryDecorrelateProject,TryDecorrelateProjectSelect,TryDecorrelateScalarGroupBy)
SELECT i*i/5=ANY(SELECT y FROM xy WHERE x=k) AS r FROM a
----
project
 ├── columns: r:12
 ├── immutable
 ├── group-by (hash)
 │    ├── columns: k:1!null scalar:13 bool_or:15
 │    ├── grouping columns: k:1!null
 │    ├── immutable
 │    ├── key: (1)
 │    ├── fd: (1)-->(13,15)
 │    ├── left-join (hash)
 │    │    ├── columns: k:1!null x:8 y:9 scalar:13 notnull:14
 │    │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
 │    │    ├── immutable
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(8,9,13,14), (8)-->(9), (9)~~>(14)
 │    │    ├── project
 │    │    │    ├── columns: scalar:13 k:1!null
 │    │    │    ├── immutable
 │    │    │    ├── key: (1)
 │    │    │    ├── fd: (1)-->(13)
 │    │    │    ├── scan a
 │    │    │    │    ├── columns: k:1!null i:2
 │    │    │    │    ├── key: (1)
 │    │    │    │    └── fd: (1)-->(2)
 │    │    │    └── projections
 │    │    │         └── (i:2 * i:2) / 5 [as=scalar:13, outer=(2), immutable]
 │    │    ├── project
 │    │    │    ├── columns: notnull:14!null x:8!null y:9
 │    │    │    ├── key: (8)
 │    │    │    ├── fd: (8)-->(9), (9)-->(14)
 │    │    │    ├── scan xy
 │    │    │    │    ├── columns: x:8!null y:9
 │    │    │    │    ├── key: (8)
 │    │    │    │    └── fd: (8)-->(9)
 │    │    │    └── projections
 │    │    │         └── y:9 IS NOT NULL [as=notnull:14, outer=(9)]
 │    │    └── filters
 │    │         ├── x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]
 │    │         └── (scalar:13 = y:9) IS NOT false [outer=(9,13)]
 │    └── aggregations
 │         ├── bool-or [as=bool_or:15, outer=(14)]
 │         │    └── notnull:14
 │         └── const-agg [as=scalar:13, outer=(13)]
 │              └── scalar:13
 └── projections
      └── CASE WHEN bool_or:15 AND (scalar:13 IS NOT NULL) THEN true WHEN bool_or:15 IS NULL THEN false ELSE CAST(NULL AS BOOL) END [as=r:12, outer=(13,15), immutable]

# --------------------------------------------------
# TryDecorrelateProject
# --------------------------------------------------
norm expect=TryDecorrelateProject
SELECT k FROM a
WHERE EXISTS
(
    SELECT * FROM xy INNER JOIN (SELECT u, u/1.1 AS div FROM uv WHERE i=5) ON x=div
)
----
distinct-on
 ├── columns: k:1!null
 ├── grouping columns: k:1!null
 ├── key: (1)
 └── select
      ├── columns: k:1!null x:8!null div:16!null
      ├── fd: (8)==(16), (16)==(8)
      ├── project
      │    ├── columns: div:16!null k:1!null x:8!null
      │    ├── inner-join (cross)
      │    │    ├── columns: k:1!null i:2!null x:8!null u:12!null
      │    │    ├── key: (1,8,12)
      │    │    ├── fd: ()-->(2)
      │    │    ├── select
      │    │    │    ├── columns: k:1!null i:2!null
      │    │    │    ├── key: (1)
      │    │    │    ├── fd: ()-->(2)
      │    │    │    ├── scan a
      │    │    │    │    ├── columns: k:1!null i:2
      │    │    │    │    ├── key: (1)
      │    │    │    │    └── fd: (1)-->(2)
      │    │    │    └── filters
      │    │    │         └── i:2 = 5 [outer=(2), constraints=(/2: [/5 - /5]; tight), fd=()-->(2)]
      │    │    ├── inner-join (cross)
      │    │    │    ├── columns: x:8!null u:12!null
      │    │    │    ├── key: (8,12)
      │    │    │    ├── scan xy
      │    │    │    │    ├── columns: x:8!null
      │    │    │    │    └── key: (8)
      │    │    │    ├── scan uv
      │    │    │    │    ├── columns: u:12!null
      │    │    │    │    └── key: (12)
      │    │    │    └── filters (true)
      │    │    └── filters (true)
      │    └── projections
      │         └── u:12 / 1.1 [as=div:16, outer=(12)]
      └── filters
           └── x:8 = div:16 [outer=(8,16), constraints=(/8: (/NULL - ]; /16: (/NULL - ]), fd=(8)==(16), (16)==(8)]

# Don't hoist Project operator in right join case.
norm
SELECT k FROM a
WHERE EXISTS
(
    SELECT * FROM xy RIGHT JOIN (SELECT u, u/1.1 AS div FROM uv WHERE i=5) ON x=div
)
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── semi-join-apply
      ├── columns: k:1!null i:2
      ├── key: (1)
      ├── fd: (1)-->(2)
      ├── scan a
      │    ├── columns: k:1!null i:2
      │    ├── key: (1)
      │    └── fd: (1)-->(2)
      ├── left-join (cross)
      │    ├── columns: x:8 div:16!null
      │    ├── outer: (2)
      │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more)
      │    ├── project
      │    │    ├── columns: div:16!null
      │    │    ├── outer: (2)
      │    │    ├── select
      │    │    │    ├── columns: u:12!null
      │    │    │    ├── outer: (2)
      │    │    │    ├── key: (12)
      │    │    │    ├── scan uv
      │    │    │    │    ├── columns: u:12!null
      │    │    │    │    └── key: (12)
      │    │    │    └── filters
      │    │    │         └── i:2 = 5 [outer=(2), constraints=(/2: [/5 - /5]; tight), fd=()-->(2)]
      │    │    └── projections
      │    │         └── u:12 / 1.1 [as=div:16, outer=(12)]
      │    ├── scan xy
      │    │    ├── columns: x:8!null
      │    │    └── key: (8)
      │    └── filters
      │         └── x:8 = div:16 [outer=(8,16), constraints=(/8: (/NULL - ]; /16: (/NULL - ]), fd=(8)==(16), (16)==(8)]
      └── filters (true)

# --------------------------------------------------
# TryDecorrelateProjectSelect
# --------------------------------------------------
norm
SELECT k FROM a
WHERE EXISTS
(
    SELECT * FROM xy LEFT JOIN (SELECT u, u+1 AS plus FROM uv WHERE i=5) ON x=plus
)
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── semi-join-apply
      ├── columns: k:1!null i:2
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(2)
      ├── scan a
      │    ├── columns: k:1!null i:2
      │    ├── key: (1)
      │    └── fd: (1)-->(2)
      ├── left-join (hash)
      │    ├── columns: x:8!null plus:16
      │    ├── outer: (2)
      │    ├── multiplicity: left-rows(one-or-more), right-rows(zero-or-one)
      │    ├── immutable
      │    ├── scan xy
      │    │    ├── columns: x:8!null
      │    │    └── key: (8)
      │    ├── project
      │    │    ├── columns: plus:16!null
      │    │    ├── outer: (2)
      │    │    ├── immutable
      │    │    ├── select
      │    │    │    ├── columns: u:12!null
      │    │    │    ├── outer: (2)
      │    │    │    ├── key: (12)
      │    │    │    ├── scan uv
      │    │    │    │    ├── columns: u:12!null
      │    │    │    │    └── key: (12)
      │    │    │    └── filters
      │    │    │         └── i:2 = 5 [outer=(2), constraints=(/2: [/5 - /5]; tight), fd=()-->(2)]
      │    │    └── projections
      │    │         └── u:12 + 1 [as=plus:16, outer=(12), immutable]
      │    └── filters
      │         └── x:8 = plus:16 [outer=(8,16), constraints=(/8: (/NULL - ]; /16: (/NULL - ]), fd=(8)==(16), (16)==(8)]
      └── filters (true)

# Don't decorrelate FULL JOIN case.
norm
SELECT k FROM a
WHERE EXISTS
(
    SELECT * FROM xy FULL JOIN (SELECT u, u+1 AS plus FROM uv WHERE i=5) ON x=plus
)
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── semi-join-apply
      ├── columns: k:1!null i:2
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(2)
      ├── scan a
      │    ├── columns: k:1!null i:2
      │    ├── key: (1)
      │    └── fd: (1)-->(2)
      ├── full-join (hash)
      │    ├── columns: x:8 plus:16
      │    ├── outer: (2)
      │    ├── multiplicity: left-rows(one-or-more), right-rows(exactly-one)
      │    ├── immutable
      │    ├── scan xy
      │    │    ├── columns: x:8!null
      │    │    └── key: (8)
      │    ├── project
      │    │    ├── columns: plus:16!null
      │    │    ├── outer: (2)
      │    │    ├── immutable
      │    │    ├── select
      │    │    │    ├── columns: u:12!null
      │    │    │    ├── outer: (2)
      │    │    │    ├── key: (12)
      │    │    │    ├── scan uv
      │    │    │    │    ├── columns: u:12!null
      │    │    │    │    └── key: (12)
      │    │    │    └── filters
      │    │    │         └── i:2 = 5 [outer=(2), constraints=(/2: [/5 - /5]; tight), fd=()-->(2)]
      │    │    └── projections
      │    │         └── u:12 + 1 [as=plus:16, outer=(12), immutable]
      │    └── filters
      │         └── x:8 = plus:16 [outer=(8,16), constraints=(/8: (/NULL - ]; /16: (/NULL - ]), fd=(8)==(16), (16)==(8)]
      └── filters (true)

# --------------------------------------------------
# TryDecorrelateProjectInnerJoin
# --------------------------------------------------
norm expect=TryDecorrelateProjectInnerJoin
SELECT (SELECT sum(y + v) FROM xy, uv WHERE x=u AND x=k) FROM a
----
project
 ├── columns: sum:18
 ├── immutable
 ├── group-by (hash)
 │    ├── columns: k:1!null sum:17
 │    ├── grouping columns: k:1!null
 │    ├── immutable
 │    ├── key: (1)
 │    ├── fd: (1)-->(17)
 │    ├── left-join (hash)
 │    │    ├── columns: k:1!null x:8 column16:16
 │    │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
 │    │    ├── immutable
 │    │    ├── key: (1)
 │    │    ├── fd: (8)-->(16), (1)-->(8,16)
 │    │    ├── scan a
 │    │    │    ├── columns: k:1!null
 │    │    │    └── key: (1)
 │    │    ├── project
 │    │    │    ├── columns: column16:16 x:8!null
 │    │    │    ├── immutable
 │    │    │    ├── key: (8)
 │    │    │    ├── fd: (8)-->(16)
 │    │    │    ├── inner-join (hash)
 │    │    │    │    ├── columns: x:8!null y:9 u:12!null v:13
 │    │    │    │    ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one)
 │    │    │    │    ├── key: (12)
 │    │    │    │    ├── fd: (8)-->(9), (12)-->(13), (8)==(12), (12)==(8)
 │    │    │    │    ├── scan xy
 │    │    │    │    │    ├── columns: x:8!null y:9
 │    │    │    │    │    ├── key: (8)
 │    │    │    │    │    └── fd: (8)-->(9)
 │    │    │    │    ├── scan uv
 │    │    │    │    │    ├── columns: u:12!null v:13
 │    │    │    │    │    ├── key: (12)
 │    │    │    │    │    └── fd: (12)-->(13)
 │    │    │    │    └── filters
 │    │    │    │         └── x:8 = u:12 [outer=(8,12), constraints=(/8: (/NULL - ]; /12: (/NULL - ]), fd=(8)==(12), (12)==(8)]
 │    │    │    └── projections
 │    │    │         └── y:9 + v:13 [as=column16:16, outer=(9,13), immutable]
 │    │    └── filters
 │    │         └── x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]
 │    └── aggregations
 │         └── sum [as=sum:17, outer=(16)]
 │              └── column16:16
 └── projections
      └── sum:17 [as=sum:18, outer=(17)]

# --------------------------------------------------
# TryDecorrelateInnerJoin
# --------------------------------------------------
# Semi-join as outer.
norm expect=TryDecorrelateInnerJoin
SELECT k FROM a
WHERE EXISTS
(
    SELECT * FROM xy INNER JOIN uv ON x=u AND x=k
)
----
semi-join (hash)
 ├── columns: k:1!null
 ├── key: (1)
 ├── scan a
 │    ├── columns: k:1!null
 │    └── key: (1)
 ├── inner-join (hash)
 │    ├── columns: x:8!null u:12!null
 │    ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one)
 │    ├── key: (12)
 │    ├── fd: (8)==(12), (12)==(8)
 │    ├── scan xy
 │    │    ├── columns: x:8!null
 │    │    └── key: (8)
 │    ├── scan uv
 │    │    ├── columns: u:12!null
 │    │    └── key: (12)
 │    └── filters
 │         └── x:8 = u:12 [outer=(8,12), constraints=(/8: (/NULL - ]; /12: (/NULL - ]), fd=(8)==(12), (12)==(8)]
 └── filters
      └── x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]

# Anti-join as outer.
norm expect=TryDecorrelateInnerJoin
SELECT k FROM a
WHERE NOT EXISTS
(
    SELECT * FROM xy INNER JOIN uv ON x=u AND x=k
)
----
anti-join (hash)
 ├── columns: k:1!null
 ├── key: (1)
 ├── scan a
 │    ├── columns: k:1!null
 │    └── key: (1)
 ├── inner-join (hash)
 │    ├── columns: x:8!null u:12!null
 │    ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one)
 │    ├── key: (12)
 │    ├── fd: (8)==(12), (12)==(8)
 │    ├── scan xy
 │    │    ├── columns: x:8!null
 │    │    └── key: (8)
 │    ├── scan uv
 │    │    ├── columns: u:12!null
 │    │    └── key: (12)
 │    └── filters
 │         └── x:8 = u:12 [outer=(8,12), constraints=(/8: (/NULL - ]; /12: (/NULL - ]), fd=(8)==(12), (12)==(8)]
 └── filters
      └── x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]

# Right-join as outer.
norm expect=TryDecorrelateInnerJoin
SELECT k FROM a
WHERE
(
    SELECT count(*)
    FROM xy
    INNER JOIN uv
    ON x=u AND x=k
) IS DISTINCT FROM 1
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── select
      ├── columns: k:1!null count_rows:16!null
      ├── key: (1)
      ├── fd: (1)-->(16)
      ├── group-by (hash)
      │    ├── columns: k:1!null count_rows:16!null
      │    ├── grouping columns: k:1!null
      │    ├── key: (1)
      │    ├── fd: (1)-->(16)
      │    ├── left-join (hash)
      │    │    ├── columns: k:1!null x:8 u:12
      │    │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
      │    │    ├── key: (1)
      │    │    ├── fd: (1)-->(8,12), (8)==(12), (12)==(8)
      │    │    ├── scan a
      │    │    │    ├── columns: k:1!null
      │    │    │    └── key: (1)
      │    │    ├── inner-join (hash)
      │    │    │    ├── columns: x:8!null u:12!null
      │    │    │    ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one)
      │    │    │    ├── key: (12)
      │    │    │    ├── fd: (8)==(12), (12)==(8)
      │    │    │    ├── scan xy
      │    │    │    │    ├── columns: x:8!null
      │    │    │    │    └── key: (8)
      │    │    │    ├── scan uv
      │    │    │    │    ├── columns: u:12!null
      │    │    │    │    └── key: (12)
      │    │    │    └── filters
      │    │    │         └── x:8 = u:12 [outer=(8,12), constraints=(/8: (/NULL - ]; /12: (/NULL - ]), fd=(8)==(12), (12)==(8)]
      │    │    └── filters
      │    │         └── x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]
      │    └── aggregations
      │         └── count [as=count_rows:16, outer=(8)]
      │              └── x:8
      └── filters
           └── count_rows:16 IS DISTINCT FROM 1 [outer=(16), constraints=(/16: [ - /0] [/2 - ]; tight)]

# Can't decorrelate left-join as inner.
norm
SELECT k FROM a
WHERE EXISTS
(
    SELECT * FROM xy LEFT JOIN uv ON x=u AND x=k
)
----
semi-join-apply
 ├── columns: k:1!null
 ├── key: (1)
 ├── scan a
 │    ├── columns: k:1!null
 │    └── key: (1)
 ├── left-join (hash)
 │    ├── columns: x:8!null u:12
 │    ├── outer: (1)
 │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
 │    ├── key: (8)
 │    ├── fd: (8)-->(12)
 │    ├── scan xy
 │    │    ├── columns: x:8!null
 │    │    └── key: (8)
 │    ├── scan uv
 │    │    ├── columns: u:12!null
 │    │    └── key: (12)
 │    └── filters
 │         ├── x:8 = u:12 [outer=(8,12), constraints=(/8: (/NULL - ]; /12: (/NULL - ]), fd=(8)==(12), (12)==(8)]
 │         └── x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]
 └── filters (true)

# Can't decorrelate semi-join as inner.
norm
SELECT k
FROM a
WHERE EXISTS
(
    SELECT *
    FROM xy
    WHERE EXISTS
    (
        SELECT * FROM uv INNER JOIN uv AS uv2 ON uv2.u=k
    )
)
----
semi-join-apply
 ├── columns: k:1!null
 ├── key: (1)
 ├── scan a
 │    ├── columns: k:1!null
 │    └── key: (1)
 ├── semi-join (cross)
 │    ├── outer: (1)
 │    ├── scan xy
 │    ├── inner-join (cross)
 │    │    ├── columns: uv2.u:16!null
 │    │    ├── scan uv
 │    │    ├── scan uv [as=uv2]
 │    │    │    ├── columns: uv2.u:16!null
 │    │    │    └── key: (16)
 │    │    └── filters (true)
 │    └── filters
 │         └── uv2.u:16 = k:1 [outer=(1,16), constraints=(/1: (/NULL - ]; /16: (/NULL - ]), fd=(1)==(16), (16)==(1)]
 └── filters (true)

# --------------------------------------------------
# TryDecorrelateInnerLeftJoin
# --------------------------------------------------
norm expect=TryDecorrelateInnerLeftJoin
SELECT *
FROM (VALUES (1), (2)) AS v(v1)
WHERE EXISTS(
    SELECT k
    FROM a
    WHERE
    (
        SELECT y FROM xy LEFT JOIN (SELECT v1 FROM uv LIMIT 1) ON x=v1 WHERE x=k
    )=i
)
----
select
 ├── columns: v1:1!null
 ├── cardinality: [0 - 2]
 ├── values
 │    ├── columns: column1:1!null
 │    ├── cardinality: [2 - 2]
 │    ├── (1,)
 │    └── (2,)
 └── filters
      └── coalesce [subquery]
           ├── subquery
           │    └── project
           │         ├── columns: column19:19!null
           │         ├── cardinality: [0 - 1]
           │         ├── key: ()
           │         ├── fd: ()-->(19)
           │         ├── limit
           │         │    ├── columns: k:2!null i:3!null x:9!null y:10!null
           │         │    ├── cardinality: [0 - 1]
           │         │    ├── key: ()
           │         │    ├── fd: ()-->(2,3,9,10), (2)==(9), (9)==(2), (3)==(10), (10)==(3)
           │         │    ├── inner-join (hash)
           │         │    │    ├── columns: k:2!null i:3!null x:9!null y:10!null
           │         │    │    ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one)
           │         │    │    ├── key: (9)
           │         │    │    ├── fd: (2)-->(3), (9)-->(10), (2)==(9), (9)==(2), (3)==(10), (10)==(3)
           │         │    │    ├── limit hint: 1.00
           │         │    │    ├── scan a
           │         │    │    │    ├── columns: k:2!null i:3
           │         │    │    │    ├── key: (2)
           │         │    │    │    └── fd: (2)-->(3)
           │         │    │    ├── scan xy
           │         │    │    │    ├── columns: x:9!null y:10
           │         │    │    │    ├── key: (9)
           │         │    │    │    └── fd: (9)-->(10)
           │         │    │    └── filters
           │         │    │         ├── x:9 = k:2 [outer=(2,9), constraints=(/2: (/NULL - ]; /9: (/NULL - ]), fd=(2)==(9), (9)==(2)]
           │         │    │         └── i:3 = y:10 [outer=(3,10), constraints=(/3: (/NULL - ]; /10: (/NULL - ]), fd=(3)==(10), (10)==(3)]
           │         │    └── 1
           │         └── projections
           │              └── true [as=column19:19]
           └── false

norm expect=TryDecorrelateInnerLeftJoin
SELECT *
FROM xy, uv
WHERE (SELECT i FROM a WHERE k=x) IS DISTINCT FROM u
----
project
 ├── columns: x:1!null y:2 u:5!null v:6
 ├── key: (1,5)
 ├── fd: (1)-->(2), (5)-->(6)
 └── select
      ├── columns: x:1!null y:2 u:5!null v:6 k:9 i:10
      ├── key: (1,5)
      ├── fd: (1)-->(2), (5)-->(6), (9)-->(10), (1,5)-->(9,10)
      ├── left-join (hash)
      │    ├── columns: x:1!null y:2 u:5!null v:6 k:9 i:10
      │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more)
      │    ├── key: (1,5)
      │    ├── fd: (1)-->(2), (5)-->(6), (9)-->(10), (1,5)-->(9,10)
      │    ├── inner-join (cross)
      │    │    ├── columns: x:1!null y:2 u:5!null v:6
      │    │    ├── key: (1,5)
      │    │    ├── fd: (1)-->(2), (5)-->(6)
      │    │    ├── scan xy
      │    │    │    ├── columns: x:1!null y:2
      │    │    │    ├── key: (1)
      │    │    │    └── fd: (1)-->(2)
      │    │    ├── scan uv
      │    │    │    ├── columns: u:5!null v:6
      │    │    │    ├── key: (5)
      │    │    │    └── fd: (5)-->(6)
      │    │    └── filters (true)
      │    ├── scan a
      │    │    ├── columns: k:9!null i:10
      │    │    ├── key: (9)
      │    │    └── fd: (9)-->(10)
      │    └── filters
      │         └── k:9 = x:1 [outer=(1,9), constraints=(/1: (/NULL - ]; /9: (/NULL - ]), fd=(1)==(9), (9)==(1)]
      └── filters
           └── u:5 IS DISTINCT FROM i:10 [outer=(5,10)]

# --------------------------------------------------
# TryDecorrelateGroupBy
# --------------------------------------------------
norm expect=TryDecorrelateGroupBy
SELECT *
FROM a
WHERE EXISTS
(
    SELECT * FROM xy INNER JOIN (SELECT count(*) AS cnt, sum(v) FROM uv WHERE i=5 GROUP BY v) ON x=cnt
)
----
group-by (hash)
 ├── columns: k:1!null i:2!null f:3 s:4 j:5
 ├── grouping columns: k:1!null
 ├── key: (1)
 ├── fd: ()-->(2), (1)-->(2-5)
 ├── select
 │    ├── columns: k:1!null i:2!null f:3 s:4 j:5 x:8!null v:13 count_rows:16!null
 │    ├── key: (1,8,13)
 │    ├── fd: ()-->(2), (1)-->(3-5), (1,8,13)-->(3-5,16), (8)==(16), (16)==(8)
 │    ├── group-by (hash)
 │    │    ├── columns: k:1!null i:2!null f:3 s:4 j:5 x:8!null v:13 count_rows:16!null
 │    │    ├── grouping columns: k:1!null x:8!null v:13
 │    │    ├── key: (1,8,13)
 │    │    ├── fd: ()-->(2), (1)-->(3-5), (1,8,13)-->(2-5,16)
 │    │    ├── inner-join (cross)
 │    │    │    ├── columns: k:1!null i:2!null f:3 s:4 j:5 x:8!null v:13
 │    │    │    ├── fd: ()-->(2), (1)-->(3-5)
 │    │    │    ├── select
 │    │    │    │    ├── columns: k:1!null i:2!null f:3 s:4 j:5
 │    │    │    │    ├── key: (1)
 │    │    │    │    ├── fd: ()-->(2), (1)-->(3-5)
 │    │    │    │    ├── scan a
 │    │    │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    │    │    │    │    ├── key: (1)
 │    │    │    │    │    └── fd: (1)-->(2-5)
 │    │    │    │    └── filters
 │    │    │    │         └── i:2 = 5 [outer=(2), constraints=(/2: [/5 - /5]; tight), fd=()-->(2)]
 │    │    │    ├── inner-join (cross)
 │    │    │    │    ├── columns: x:8!null v:13
 │    │    │    │    ├── scan xy
 │    │    │    │    │    ├── columns: x:8!null
 │    │    │    │    │    └── key: (8)
 │    │    │    │    ├── scan uv
 │    │    │    │    │    └── columns: v:13
 │    │    │    │    └── filters (true)
 │    │    │    └── filters (true)
 │    │    └── aggregations
 │    │         ├── count-rows [as=count_rows:16]
 │    │         ├── const-agg [as=i:2, outer=(2)]
 │    │         │    └── i:2
 │    │         ├── const-agg [as=f:3, outer=(3)]
 │    │         │    └── f:3
 │    │         ├── const-agg [as=s:4, outer=(4)]
 │    │         │    └── s:4
 │    │         └── const-agg [as=j:5, outer=(5)]
 │    │              └── j:5
 │    └── filters
 │         └── x:8 = count_rows:16 [outer=(8,16), constraints=(/8: (/NULL - ]; /16: (/NULL - ]), fd=(8)==(16), (16)==(8)]
 └── aggregations
      ├── const-agg [as=i:2, outer=(2)]
      │    └── i:2
      ├── const-agg [as=f:3, outer=(3)]
      │    └── f:3
      ├── const-agg [as=s:4, outer=(4)]
      │    └── s:4
      └── const-agg [as=j:5, outer=(5)]
           └── j:5

norm expect=TryDecorrelateGroupBy
SELECT *
FROM a
WHERE EXISTS
(
    SELECT * FROM xy INNER JOIN (SELECT count(DISTINCT uv.v) AS cnt, sum(v) FROM uv WHERE i=5 GROUP BY v) ON x=cnt
)
----
group-by (hash)
 ├── columns: k:1!null i:2!null f:3 s:4 j:5
 ├── grouping columns: k:1!null
 ├── key: (1)
 ├── fd: ()-->(2), (1)-->(2-5)
 ├── select
 │    ├── columns: k:1!null i:2!null f:3 s:4 j:5 x:8!null v:13 count:16!null
 │    ├── key: (1,8,13)
 │    ├── fd: ()-->(2), (1)-->(3-5), (1,8,13)-->(3-5,16), (8)==(16), (16)==(8)
 │    ├── group-by (hash)
 │    │    ├── columns: k:1!null i:2!null f:3 s:4 j:5 x:8!null v:13 count:16!null
 │    │    ├── grouping columns: k:1!null x:8!null v:13
 │    │    ├── key: (1,8,13)
 │    │    ├── fd: ()-->(2), (1)-->(3-5), (1,8,13)-->(2-5,16)
 │    │    ├── inner-join (cross)
 │    │    │    ├── columns: k:1!null i:2!null f:3 s:4 j:5 x:8!null v:13
 │    │    │    ├── fd: ()-->(2), (1)-->(3-5)
 │    │    │    ├── select
 │    │    │    │    ├── columns: k:1!null i:2!null f:3 s:4 j:5
 │    │    │    │    ├── key: (1)
 │    │    │    │    ├── fd: ()-->(2), (1)-->(3-5)
 │    │    │    │    ├── scan a
 │    │    │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    │    │    │    │    ├── key: (1)
 │    │    │    │    │    └── fd: (1)-->(2-5)
 │    │    │    │    └── filters
 │    │    │    │         └── i:2 = 5 [outer=(2), constraints=(/2: [/5 - /5]; tight), fd=()-->(2)]
 │    │    │    ├── inner-join (cross)
 │    │    │    │    ├── columns: x:8!null v:13
 │    │    │    │    ├── scan xy
 │    │    │    │    │    ├── columns: x:8!null
 │    │    │    │    │    └── key: (8)
 │    │    │    │    ├── scan uv
 │    │    │    │    │    └── columns: v:13
 │    │    │    │    └── filters (true)
 │    │    │    └── filters (true)
 │    │    └── aggregations
 │    │         ├── agg-distinct [as=count:16, outer=(13)]
 │    │         │    └── count
 │    │         │         └── v:13
 │    │         ├── const-agg [as=i:2, outer=(2)]
 │    │         │    └── i:2
 │    │         ├── const-agg [as=f:3, outer=(3)]
 │    │         │    └── f:3
 │    │         ├── const-agg [as=s:4, outer=(4)]
 │    │         │    └── s:4
 │    │         └── const-agg [as=j:5, outer=(5)]
 │    │              └── j:5
 │    └── filters
 │         └── x:8 = count:16 [outer=(8,16), constraints=(/8: (/NULL - ]; /16: (/NULL - ]), fd=(8)==(16), (16)==(8)]
 └── aggregations
      ├── const-agg [as=i:2, outer=(2)]
      │    └── i:2
      ├── const-agg [as=f:3, outer=(3)]
      │    └── f:3
      ├── const-agg [as=s:4, outer=(4)]
      │    └── s:4
      └── const-agg [as=j:5, outer=(5)]
           └── j:5

# Indirectly decorrelate GROUP BY after decorrelating scalar GROUP BY.
norm expect=TryDecorrelateGroupBy
SELECT *
FROM xy, uv
WHERE x<v AND u=(SELECT max(i) FROM a WHERE k=x)
----
project
 ├── columns: x:1!null y:2 u:5!null v:6!null
 ├── key: (1,5)
 ├── fd: (1)-->(2), (5)-->(6), (1,5)-->(2,6)
 └── select
      ├── columns: x:1!null y:2 u:5!null v:6!null max:16!null
      ├── key: (1,5)
      ├── fd: (1)-->(2), (5)-->(6), (1,5)-->(2,6,16), (5)==(16), (16)==(5)
      ├── group-by (hash)
      │    ├── columns: x:1!null y:2 u:5!null v:6!null max:16!null
      │    ├── grouping columns: x:1!null u:5!null
      │    ├── key: (1,5)
      │    ├── fd: (1)-->(2), (5)-->(6), (1,5)-->(2,6,16)
      │    ├── inner-join (hash)
      │    │    ├── columns: x:1!null y:2 u:5!null v:6!null k:9!null i:10!null
      │    │    ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-more)
      │    │    ├── key: (5,9)
      │    │    ├── fd: (1)-->(2), (5)-->(6), (9)-->(10), (1)==(9), (9)==(1)
      │    │    ├── inner-join (cross)
      │    │    │    ├── columns: x:1!null y:2 u:5!null v:6!null
      │    │    │    ├── key: (1,5)
      │    │    │    ├── fd: (1)-->(2), (5)-->(6)
      │    │    │    ├── scan xy
      │    │    │    │    ├── columns: x:1!null y:2
      │    │    │    │    ├── key: (1)
      │    │    │    │    └── fd: (1)-->(2)
      │    │    │    ├── scan uv
      │    │    │    │    ├── columns: u:5!null v:6
      │    │    │    │    ├── key: (5)
      │    │    │    │    └── fd: (5)-->(6)
      │    │    │    └── filters
      │    │    │         └── x:1 < v:6 [outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ])]
      │    │    ├── select
      │    │    │    ├── columns: k:9!null i:10!null
      │    │    │    ├── key: (9)
      │    │    │    ├── fd: (9)-->(10)
      │    │    │    ├── scan a
      │    │    │    │    ├── columns: k:9!null i:10
      │    │    │    │    ├── key: (9)
      │    │    │    │    └── fd: (9)-->(10)
      │    │    │    └── filters
      │    │    │         └── i:10 IS NOT NULL [outer=(10), constraints=(/10: (/NULL - ]; tight)]
      │    │    └── filters
      │    │         └── k:9 = x:1 [outer=(1,9), constraints=(/1: (/NULL - ]; /9: (/NULL - ]), fd=(1)==(9), (9)==(1)]
      │    └── aggregations
      │         ├── max [as=max:16, outer=(10)]
      │         │    └── i:10
      │         ├── const-agg [as=v:6, outer=(6)]
      │         │    └── v:6
      │         └── const-agg [as=y:2, outer=(2)]
      │              └── y:2
      └── filters
           └── u:5 = max:16 [outer=(5,16), constraints=(/5: (/NULL - ]; /16: (/NULL - ]), fd=(5)==(16), (16)==(5)]

# Indirectly decorrelate GROUP BY after decorrelating scalar GROUP BY. Use
# IS DISTINCT FROM to retain left join.
norm expect=TryDecorrelateGroupBy
SELECT *
FROM xy, uv
WHERE x<v AND (SELECT max(i) FROM a WHERE k=x) IS DISTINCT FROM u
----
project
 ├── columns: x:1!null y:2 u:5!null v:6!null
 ├── key: (1,5)
 ├── fd: (1)-->(2), (5)-->(6), (1,5)-->(2,6)
 └── select
      ├── columns: x:1!null y:2 u:5!null v:6!null max:16
      ├── key: (1,5)
      ├── fd: (1)-->(2), (5)-->(6), (1,5)-->(2,6,16)
      ├── group-by (hash)
      │    ├── columns: x:1!null y:2 u:5!null v:6!null max:16
      │    ├── grouping columns: x:1!null u:5!null
      │    ├── key: (1,5)
      │    ├── fd: (1)-->(2), (5)-->(6), (1,5)-->(2,6,16)
      │    ├── left-join (hash)
      │    │    ├── columns: x:1!null y:2 u:5!null v:6!null k:9 i:10
      │    │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more)
      │    │    ├── key: (1,5)
      │    │    ├── fd: (1)-->(2), (5)-->(6), (9)-->(10), (1,5)-->(9,10)
      │    │    ├── inner-join (cross)
      │    │    │    ├── columns: x:1!null y:2 u:5!null v:6!null
      │    │    │    ├── key: (1,5)
      │    │    │    ├── fd: (1)-->(2), (5)-->(6)
      │    │    │    ├── scan xy
      │    │    │    │    ├── columns: x:1!null y:2
      │    │    │    │    ├── key: (1)
      │    │    │    │    └── fd: (1)-->(2)
      │    │    │    ├── scan uv
      │    │    │    │    ├── columns: u:5!null v:6
      │    │    │    │    ├── key: (5)
      │    │    │    │    └── fd: (5)-->(6)
      │    │    │    └── filters
      │    │    │         └── x:1 < v:6 [outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ])]
      │    │    ├── scan a
      │    │    │    ├── columns: k:9!null i:10
      │    │    │    ├── key: (9)
      │    │    │    └── fd: (9)-->(10)
      │    │    └── filters
      │    │         └── k:9 = x:1 [outer=(1,9), constraints=(/1: (/NULL - ]; /9: (/NULL - ]), fd=(1)==(9), (9)==(1)]
      │    └── aggregations
      │         ├── max [as=max:16, outer=(10)]
      │         │    └── i:10
      │         ├── const-agg [as=v:6, outer=(6)]
      │         │    └── v:6
      │         └── const-agg [as=y:2, outer=(2)]
      │              └── y:2
      └── filters
           └── u:5 IS DISTINCT FROM max:16 [outer=(5,16)]

# Synthesize key when one is not present.
norm expect=TryDecorrelateGroupBy
SELECT *
FROM
(
    SELECT y, 'foo' AS cst FROM xy
)
WHERE 'bar'=(SELECT max(s) FROM (SELECT * FROM a LIMIT 1) WHERE k=y GROUP BY i)
----
project
 ├── columns: y:2!null cst:5!null
 ├── fd: ()-->(2,5)
 ├── select
 │    ├── columns: x:1!null y:2!null max:13!null
 │    ├── key: (1)
 │    ├── fd: ()-->(2,13)
 │    ├── group-by (hash)
 │    │    ├── columns: x:1!null y:2!null max:13
 │    │    ├── grouping columns: x:1!null
 │    │    ├── key: (1)
 │    │    ├── fd: ()-->(2), (1)-->(2,13)
 │    │    ├── inner-join (hash)
 │    │    │    ├── columns: x:1!null y:2!null k:6!null s:9
 │    │    │    ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-more)
 │    │    │    ├── key: (1)
 │    │    │    ├── fd: ()-->(2,6,9), (2)==(6), (6)==(2)
 │    │    │    ├── scan xy
 │    │    │    │    ├── columns: x:1!null y:2
 │    │    │    │    ├── key: (1)
 │    │    │    │    └── fd: (1)-->(2)
 │    │    │    ├── limit
 │    │    │    │    ├── columns: k:6!null s:9
 │    │    │    │    ├── cardinality: [0 - 1]
 │    │    │    │    ├── key: ()
 │    │    │    │    ├── fd: ()-->(6,9)
 │    │    │    │    ├── scan a
 │    │    │    │    │    ├── columns: k:6!null s:9
 │    │    │    │    │    ├── key: (6)
 │    │    │    │    │    ├── fd: (6)-->(9)
 │    │    │    │    │    └── limit hint: 1.00
 │    │    │    │    └── 1
 │    │    │    └── filters
 │    │    │         └── k:6 = y:2 [outer=(2,6), constraints=(/2: (/NULL - ]; /6: (/NULL - ]), fd=(2)==(6), (6)==(2)]
 │    │    └── aggregations
 │    │         ├── max [as=max:13, outer=(9)]
 │    │         │    └── s:9
 │    │         └── const-agg [as=y:2, outer=(2)]
 │    │              └── y:2
 │    └── filters
 │         └── max:13 = 'bar' [outer=(13), constraints=(/13: [/'bar' - /'bar']; tight), fd=()-->(13)]
 └── projections
      └── 'foo' [as=cst:5]

# Decorrelate DistinctOn.
norm expect=TryDecorrelateGroupBy
SELECT *
FROM a
WHERE EXISTS
(
    SELECT * FROM xy INNER JOIN (
        SELECT DISTINCT ON (v) u, v FROM uv WHERE i=5
    ) ON x=u
)
----
group-by (hash)
 ├── columns: k:1!null i:2!null f:3 s:4 j:5
 ├── grouping columns: k:1!null
 ├── key: (1)
 ├── fd: ()-->(2), (1)-->(2-5)
 ├── select
 │    ├── columns: k:1!null i:2!null f:3 s:4 j:5 x:8!null u:12!null v:13
 │    ├── key: (1,12)
 │    ├── fd: ()-->(2), (1)-->(3-5), (12)-->(13), (1,8,13)-->(3-5,12), (8)==(12), (12)==(8)
 │    ├── distinct-on
 │    │    ├── columns: k:1!null i:2!null f:3 s:4 j:5 x:8!null u:12!null v:13
 │    │    ├── grouping columns: k:1!null x:8!null v:13
 │    │    ├── key: (1,8,12)
 │    │    ├── fd: ()-->(2), (1)-->(3-5), (12)-->(13), (1,8,13)-->(2-5,12)
 │    │    ├── inner-join (cross)
 │    │    │    ├── columns: k:1!null i:2!null f:3 s:4 j:5 x:8!null u:12!null v:13
 │    │    │    ├── key: (1,8,12)
 │    │    │    ├── fd: ()-->(2), (1)-->(3-5), (12)-->(13)
 │    │    │    ├── select
 │    │    │    │    ├── columns: k:1!null i:2!null f:3 s:4 j:5
 │    │    │    │    ├── key: (1)
 │    │    │    │    ├── fd: ()-->(2), (1)-->(3-5)
 │    │    │    │    ├── scan a
 │    │    │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    │    │    │    │    ├── key: (1)
 │    │    │    │    │    └── fd: (1)-->(2-5)
 │    │    │    │    └── filters
 │    │    │    │         └── i:2 = 5 [outer=(2), constraints=(/2: [/5 - /5]; tight), fd=()-->(2)]
 │    │    │    ├── inner-join (cross)
 │    │    │    │    ├── columns: x:8!null u:12!null v:13
 │    │    │    │    ├── key: (8,12)
 │    │    │    │    ├── fd: (12)-->(13)
 │    │    │    │    ├── scan xy
 │    │    │    │    │    ├── columns: x:8!null
 │    │    │    │    │    └── key: (8)
 │    │    │    │    ├── scan uv
 │    │    │    │    │    ├── columns: u:12!null v:13
 │    │    │    │    │    ├── key: (12)
 │    │    │    │    │    └── fd: (12)-->(13)
 │    │    │    │    └── filters (true)
 │    │    │    └── filters (true)
 │    │    └── aggregations
 │    │         ├── first-agg [as=u:12, outer=(12)]
 │    │         │    └── u:12
 │    │         ├── const-agg [as=i:2, outer=(2)]
 │    │         │    └── i:2
 │    │         ├── const-agg [as=f:3, outer=(3)]
 │    │         │    └── f:3
 │    │         ├── const-agg [as=s:4, outer=(4)]
 │    │         │    └── s:4
 │    │         └── const-agg [as=j:5, outer=(5)]
 │    │              └── j:5
 │    └── filters
 │         └── x:8 = u:12 [outer=(8,12), constraints=(/8: (/NULL - ]; /12: (/NULL - ]), fd=(8)==(12), (12)==(8)]
 └── aggregations
      ├── const-agg [as=i:2, outer=(2)]
      │    └── i:2
      ├── const-agg [as=f:3, outer=(3)]
      │    └── f:3
      ├── const-agg [as=s:4, outer=(4)]
      │    └── s:4
      └── const-agg [as=j:5, outer=(5)]
           └── j:5

# Regression test for #40592. Ensure that no new output columns are added
# as a result of TryDecorrelateGroupBy.
exec-ddl
CREATE TABLE tab_orig (
    _int2 INT8 NULL,
    _int8 INT8 NULL,
    _timestamptz TIMESTAMPTZ NULL,
    _bool BOOL NULL,
    _decimal INT8 NULL,
    _string INT8 NULL,
    FAMILY "primary" (_int2, _int8, _timestamptz, _bool, _decimal, _string, rowid)
)
----

norm expect=TryDecorrelateGroupBy disable=(InlineWith,EliminateGroupBy)
SELECT
  NULL
FROM
  tab_orig AS t0
WHERE
  EXISTS(
    SELECT
      NULL
    FROM
      tab_orig
      JOIN tab_orig AS t1
        INNER JOIN tab_orig AS t2
          JOIN tab_orig AS t3 ON true ON
            EXISTS(
              WITH
                w0 AS (SELECT NULL)
              SELECT
                t0._string FROM w0 CROSS JOIN w0 as w1
              WHERE t0._string = 1
            ) ON true
      JOIN tab_orig AS t4 ON
          t3._timestamptz
          = t4._timestamptz
          AND t3._int2 = t4._int8
      JOIN tab_orig AS t5 ON
          t1._decimal = t5._decimal
    WHERE
      t2._bool
    ORDER BY
      t2._decimal
    LIMIT
      66
  );
----
with &1 (w0)
 ├── columns: "?column?":74
 ├── fd: ()-->(74)
 ├── values
 │    ├── columns: "?column?":46
 │    ├── cardinality: [1 - 1]
 │    ├── key: ()
 │    ├── fd: ()-->(46)
 │    └── (NULL,)
 └── project
      ├── columns: "?column?":74
      ├── fd: ()-->(74)
      ├── semi-join-apply
      │    ├── columns: t0._string:6
      │    ├── scan tab_orig [as=t0]
      │    │    └── columns: t0._string:6
      │    ├── inner-join (hash)
      │    │    ├── columns: tab_orig.rowid:16!null t1._decimal:23!null t1.rowid:25!null t2._bool:31!null t2.rowid:34!null t3._int2:37!null t3._timestamptz:39!null t3.rowid:43!null t4._int8:55!null t4._timestamptz:56!null t5._decimal:67!null
      │    │    ├── outer: (6)
      │    │    ├── fd: ()-->(31), (25)-->(23), (43)-->(37,39), (16,25,34,43)-->(23,37,39), (39)==(56), (56)==(39), (37)==(55), (55)==(37), (23)==(67), (67)==(23)
      │    │    ├── inner-join (hash)
      │    │    │    ├── columns: tab_orig.rowid:16!null t1._decimal:23 t1.rowid:25!null t2._bool:31 t2.rowid:34!null t3._int2:37!null t3._timestamptz:39!null t3.rowid:43!null t4._int8:55!null t4._timestamptz:56!null
      │    │    │    ├── outer: (6)
      │    │    │    ├── fd: (25)-->(23), (34)-->(31), (43)-->(37,39), (16,25,34,43)-->(23,31,37,39), (39)==(56), (56)==(39), (37)==(55), (55)==(37)
      │    │    │    ├── group-by (hash)
      │    │    │    │    ├── columns: tab_orig.rowid:16!null t1._decimal:23 t1.rowid:25!null t2._bool:31 t2.rowid:34!null t3._int2:37 t3._timestamptz:39 t3.rowid:43!null
      │    │    │    │    ├── grouping columns: tab_orig.rowid:16!null t1.rowid:25!null t2.rowid:34!null t3.rowid:43!null
      │    │    │    │    ├── outer: (6)
      │    │    │    │    ├── key: (16,25,34,43)
      │    │    │    │    ├── fd: (25)-->(23), (34)-->(31), (43)-->(37,39), (16,25,34,43)-->(23,31,37,39)
      │    │    │    │    ├── inner-join (cross)
      │    │    │    │    │    ├── columns: tab_orig.rowid:16!null t1._decimal:23 t1.rowid:25!null t2._bool:31 t2.rowid:34!null t3._int2:37 t3._timestamptz:39 t3.rowid:43!null
      │    │    │    │    │    ├── outer: (6)
      │    │    │    │    │    ├── key: (16,25,34,43)
      │    │    │    │    │    ├── fd: (25)-->(23), (34)-->(31), (43)-->(37,39)
      │    │    │    │    │    ├── scan tab_orig
      │    │    │    │    │    │    ├── columns: tab_orig.rowid:16!null
      │    │    │    │    │    │    └── key: (16)
      │    │    │    │    │    ├── inner-join (cross)
      │    │    │    │    │    │    ├── columns: t1._decimal:23 t1.rowid:25!null t2._bool:31 t2.rowid:34!null t3._int2:37 t3._timestamptz:39 t3.rowid:43!null
      │    │    │    │    │    │    ├── key: (25,34,43)
      │    │    │    │    │    │    ├── fd: (25)-->(23), (34)-->(31), (43)-->(37,39)
      │    │    │    │    │    │    ├── scan tab_orig [as=t1]
      │    │    │    │    │    │    │    ├── columns: t1._decimal:23 t1.rowid:25!null
      │    │    │    │    │    │    │    ├── key: (25)
      │    │    │    │    │    │    │    └── fd: (25)-->(23)
      │    │    │    │    │    │    ├── inner-join (cross)
      │    │    │    │    │    │    │    ├── columns: t2._bool:31 t2.rowid:34!null t3._int2:37 t3._timestamptz:39 t3.rowid:43!null
      │    │    │    │    │    │    │    ├── key: (34,43)
      │    │    │    │    │    │    │    ├── fd: (34)-->(31), (43)-->(37,39)
      │    │    │    │    │    │    │    ├── scan tab_orig [as=t2]
      │    │    │    │    │    │    │    │    ├── columns: t2._bool:31 t2.rowid:34!null
      │    │    │    │    │    │    │    │    ├── key: (34)
      │    │    │    │    │    │    │    │    └── fd: (34)-->(31)
      │    │    │    │    │    │    │    ├── scan tab_orig [as=t3]
      │    │    │    │    │    │    │    │    ├── columns: t3._int2:37 t3._timestamptz:39 t3.rowid:43!null
      │    │    │    │    │    │    │    │    ├── key: (43)
      │    │    │    │    │    │    │    │    └── fd: (43)-->(37,39)
      │    │    │    │    │    │    │    └── filters (true)
      │    │    │    │    │    │    └── filters (true)
      │    │    │    │    │    └── filters
      │    │    │    │    │         └── t0._string:6 = 1 [outer=(6), constraints=(/6: [/1 - /1]; tight), fd=()-->(6)]
      │    │    │    │    └── aggregations
      │    │    │    │         ├── const-agg [as=t2._bool:31, outer=(31)]
      │    │    │    │         │    └── t2._bool:31
      │    │    │    │         ├── const-agg [as=t3._int2:37, outer=(37)]
      │    │    │    │         │    └── t3._int2:37
      │    │    │    │         ├── const-agg [as=t3._timestamptz:39, outer=(39)]
      │    │    │    │         │    └── t3._timestamptz:39
      │    │    │    │         └── const-agg [as=t1._decimal:23, outer=(23)]
      │    │    │    │              └── t1._decimal:23
      │    │    │    ├── scan tab_orig [as=t4]
      │    │    │    │    └── columns: t4._int8:55 t4._timestamptz:56
      │    │    │    └── filters
      │    │    │         ├── t3._timestamptz:39 = t4._timestamptz:56 [outer=(39,56), constraints=(/39: (/NULL - ]; /56: (/NULL - ]), fd=(39)==(56), (56)==(39)]
      │    │    │         └── t3._int2:37 = t4._int8:55 [outer=(37,55), constraints=(/37: (/NULL - ]; /55: (/NULL - ]), fd=(37)==(55), (55)==(37)]
      │    │    ├── scan tab_orig [as=t5]
      │    │    │    └── columns: t5._decimal:67
      │    │    └── filters
      │    │         ├── t2._bool:31 [outer=(31), constraints=(/31: [/true - /true]; tight), fd=()-->(31)]
      │    │         └── t1._decimal:23 = t5._decimal:67 [outer=(23,67), constraints=(/23: (/NULL - ]; /67: (/NULL - ]), fd=(23)==(67), (67)==(23)]
      │    └── filters (true)
      └── projections
           └── NULL [as="?column?":74]

# --------------------------------------------------
# TryDecorrelateScalarGroupBy
# --------------------------------------------------
norm expect=TryDecorrelateScalarGroupBy
SELECT *
FROM a
WHERE EXISTS
(
    SELECT * FROM xy INNER JOIN (SELECT sum(v), count(*) AS cnt FROM uv WHERE i=5) ON x=cnt
)
----
group-by (hash)
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── grouping columns: k:1!null
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── select
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5 x:8!null count_rows:17!null
 │    ├── key: (1,8)
 │    ├── fd: (1)-->(2-5), (1,8)-->(2-5,17), (8)==(17), (17)==(8)
 │    ├── group-by (hash)
 │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5 x:8!null count_rows:17!null
 │    │    ├── grouping columns: k:1!null x:8!null
 │    │    ├── key: (1,8)
 │    │    ├── fd: (1)-->(2-5), (1,8)-->(2-5,17)
 │    │    ├── left-join (cross)
 │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5 x:8!null canary:18
 │    │    │    ├── fd: (1)-->(2-5)
 │    │    │    ├── inner-join (cross)
 │    │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5 x:8!null
 │    │    │    │    ├── key: (1,8)
 │    │    │    │    ├── fd: (1)-->(2-5)
 │    │    │    │    ├── scan a
 │    │    │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    │    │    │    │    ├── key: (1)
 │    │    │    │    │    └── fd: (1)-->(2-5)
 │    │    │    │    ├── scan xy
 │    │    │    │    │    ├── columns: x:8!null
 │    │    │    │    │    └── key: (8)
 │    │    │    │    └── filters (true)
 │    │    │    ├── project
 │    │    │    │    ├── columns: canary:18!null
 │    │    │    │    ├── fd: ()-->(18)
 │    │    │    │    ├── scan uv
 │    │    │    │    └── projections
 │    │    │    │         └── true [as=canary:18]
 │    │    │    └── filters
 │    │    │         └── i:2 = 5 [outer=(2), constraints=(/2: [/5 - /5]; tight), fd=()-->(2)]
 │    │    └── aggregations
 │    │         ├── count [as=count_rows:17, outer=(18)]
 │    │         │    └── canary:18
 │    │         ├── const-agg [as=i:2, outer=(2)]
 │    │         │    └── i:2
 │    │         ├── const-agg [as=f:3, outer=(3)]
 │    │         │    └── f:3
 │    │         ├── const-agg [as=s:4, outer=(4)]
 │    │         │    └── s:4
 │    │         └── const-agg [as=j:5, outer=(5)]
 │    │              └── j:5
 │    └── filters
 │         └── x:8 = count_rows:17 [outer=(8,17), constraints=(/8: (/NULL - ]; /17: (/NULL - ]), fd=(8)==(17), (17)==(8)]
 └── aggregations
      ├── const-agg [as=i:2, outer=(2)]
      │    └── i:2
      ├── const-agg [as=f:3, outer=(3)]
      │    └── f:3
      ├── const-agg [as=s:4, outer=(4)]
      │    └── s:4
      └── const-agg [as=j:5, outer=(5)]
           └── j:5

# Synthesize key when one is not present.
norm expect=TryDecorrelateScalarGroupBy
SELECT * FROM (SELECT i, 'foo' AS cst FROM a) WHERE 5=(SELECT max(y) FROM xy WHERE x=i)
----
project
 ├── columns: i:2!null cst:8!null
 ├── fd: ()-->(8)
 ├── select
 │    ├── columns: k:1!null i:2!null max:13!null
 │    ├── key: (1)
 │    ├── fd: ()-->(13), (1)-->(2)
 │    ├── group-by (hash)
 │    │    ├── columns: k:1!null i:2!null max:13!null
 │    │    ├── grouping columns: k:1!null
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(2,13)
 │    │    ├── inner-join (hash)
 │    │    │    ├── columns: k:1!null i:2!null x:9!null y:10!null
 │    │    │    ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-more)
 │    │    │    ├── key: (1)
 │    │    │    ├── fd: (1)-->(2), (9)-->(10), (2)==(9), (9)==(2)
 │    │    │    ├── scan a
 │    │    │    │    ├── columns: k:1!null i:2
 │    │    │    │    ├── key: (1)
 │    │    │    │    └── fd: (1)-->(2)
 │    │    │    ├── select
 │    │    │    │    ├── columns: x:9!null y:10!null
 │    │    │    │    ├── key: (9)
 │    │    │    │    ├── fd: (9)-->(10)
 │    │    │    │    ├── scan xy
 │    │    │    │    │    ├── columns: x:9!null y:10
 │    │    │    │    │    ├── key: (9)
 │    │    │    │    │    └── fd: (9)-->(10)
 │    │    │    │    └── filters
 │    │    │    │         └── y:10 IS NOT NULL [outer=(10), constraints=(/10: (/NULL - ]; tight)]
 │    │    │    └── filters
 │    │    │         └── x:9 = i:2 [outer=(2,9), constraints=(/2: (/NULL - ]; /9: (/NULL - ]), fd=(2)==(9), (9)==(2)]
 │    │    └── aggregations
 │    │         ├── max [as=max:13, outer=(10)]
 │    │         │    └── y:10
 │    │         └── const-agg [as=i:2, outer=(2)]
 │    │              └── i:2
 │    └── filters
 │         └── max:13 = 5 [outer=(13), constraints=(/13: [/5 - /5]; tight), fd=()-->(13)]
 └── projections
      └── 'foo' [as=cst:8]

# With an aggregate that can't ignore nulls. xy.y = a.k rejects nulls, so
# there's no canary column to be synthesized.
norm expect=TryDecorrelateScalarGroupBy
SELECT k, (SELECT array_agg(xy.y) FROM xy WHERE xy.y = a.k) FROM a
----
project
 ├── columns: k:1!null array_agg:13
 ├── key: (1)
 ├── fd: (1)-->(13)
 ├── group-by (hash)
 │    ├── columns: k:1!null y:9 array_agg:14
 │    ├── grouping columns: k:1!null
 │    ├── key: (1)
 │    ├── fd: (1)-->(9,14)
 │    ├── left-join (hash)
 │    │    ├── columns: k:1!null y:9
 │    │    ├── multiplicity: left-rows(one-or-more), right-rows(zero-or-one)
 │    │    ├── scan a
 │    │    │    ├── columns: k:1!null
 │    │    │    └── key: (1)
 │    │    ├── scan xy
 │    │    │    └── columns: y:9
 │    │    └── filters
 │    │         └── y:9 = k:1 [outer=(1,9), constraints=(/1: (/NULL - ]; /9: (/NULL - ]), fd=(1)==(9), (9)==(1)]
 │    └── aggregations
 │         ├── array-agg [as=array_agg:14, outer=(9)]
 │         │    └── y:9
 │         └── any-not-null-agg [as=y:9, outer=(9)]
 │              └── y:9
 └── projections
      └── CASE WHEN y:9 IS NOT NULL THEN array_agg:14 ELSE CAST(NULL AS INT8[]) END [as=array_agg:13, outer=(9,14)]

# With multiple columns. Without LATERAL these tests are a bit verbose.
norm expect=TryDecorrelateScalarGroupBy
SELECT k, (SELECT (r, q) FROM (SELECT array_agg(xy.y) r, max(xy.y) q FROM xy WHERE xy.y = a.k)) FROM a
----
project
 ├── columns: k:1!null "?column?":15
 ├── key: (1)
 ├── fd: (1)-->(15)
 ├── group-by (hash)
 │    ├── columns: k:1!null y:9 max:13 array_agg:16
 │    ├── grouping columns: k:1!null
 │    ├── key: (1)
 │    ├── fd: (1)-->(9,13,16)
 │    ├── left-join (hash)
 │    │    ├── columns: k:1!null y:9
 │    │    ├── multiplicity: left-rows(one-or-more), right-rows(zero-or-one)
 │    │    ├── scan a
 │    │    │    ├── columns: k:1!null
 │    │    │    └── key: (1)
 │    │    ├── scan xy
 │    │    │    └── columns: y:9
 │    │    └── filters
 │    │         └── y:9 = k:1 [outer=(1,9), constraints=(/1: (/NULL - ]; /9: (/NULL - ]), fd=(1)==(9), (9)==(1)]
 │    └── aggregations
 │         ├── array-agg [as=array_agg:16, outer=(9)]
 │         │    └── y:9
 │         ├── max [as=max:13, outer=(9)]
 │         │    └── y:9
 │         └── any-not-null-agg [as=y:9, outer=(9)]
 │              └── y:9
 └── projections
      └── (CASE WHEN y:9 IS NOT NULL THEN array_agg:16 ELSE CAST(NULL AS INT8[]) END, max:13) [as="?column?":15, outer=(9,13,16)]


# With an aggregate that can't ignore nulls and when a non-nullable column must be synthesized.
norm expect=TryDecorrelateScalarGroupBy
SELECT k, ARRAY(SELECT y FROM xy WHERE xy.y = a.i OR xy.y IS NULL) FROM a
----
project
 ├── columns: k:1!null array:13
 ├── key: (1)
 ├── fd: (1)-->(13)
 ├── group-by (hash)
 │    ├── columns: k:1!null canary:14 array_agg:15
 │    ├── grouping columns: k:1!null
 │    ├── key: (1)
 │    ├── fd: (1)-->(14,15)
 │    ├── left-join (cross)
 │    │    ├── columns: k:1!null i:2 y:9 canary:14
 │    │    ├── fd: (1)-->(2)
 │    │    ├── scan a
 │    │    │    ├── columns: k:1!null i:2
 │    │    │    ├── key: (1)
 │    │    │    └── fd: (1)-->(2)
 │    │    ├── project
 │    │    │    ├── columns: canary:14!null y:9
 │    │    │    ├── fd: ()-->(14)
 │    │    │    ├── scan xy
 │    │    │    │    └── columns: y:9
 │    │    │    └── projections
 │    │    │         └── true [as=canary:14]
 │    │    └── filters
 │    │         └── (y:9 = i:2) OR (y:9 IS NULL) [outer=(2,9)]
 │    └── aggregations
 │         ├── array-agg [as=array_agg:15, outer=(9)]
 │         │    └── y:9
 │         └── any-not-null-agg [as=canary:14, outer=(14)]
 │              └── canary:14
 └── projections
      └── COALESCE(CASE WHEN canary:14 IS NOT NULL THEN array_agg:15 ELSE CAST(NULL AS INT8[]) END, ARRAY[]) [as=array:13, outer=(14,15)]

# With an ordering.
norm expect=TryDecorrelateScalarGroupBy
SELECT i, ARRAY(SELECT y FROM xy WHERE xy.y = a.k OR xy.y IS NULL ORDER BY y) FROM a
----
project
 ├── columns: i:2 array:13
 ├── group-by (hash)
 │    ├── columns: k:1!null i:2 canary:14 array_agg:15
 │    ├── grouping columns: k:1!null
 │    ├── internal-ordering: +9
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,14,15)
 │    ├── sort
 │    │    ├── columns: k:1!null i:2 y:9 canary:14
 │    │    ├── fd: (1)-->(2)
 │    │    ├── ordering: +9
 │    │    └── left-join (cross)
 │    │         ├── columns: k:1!null i:2 y:9 canary:14
 │    │         ├── fd: (1)-->(2)
 │    │         ├── scan a
 │    │         │    ├── columns: k:1!null i:2
 │    │         │    ├── key: (1)
 │    │         │    └── fd: (1)-->(2)
 │    │         ├── project
 │    │         │    ├── columns: canary:14!null y:9
 │    │         │    ├── fd: ()-->(14)
 │    │         │    ├── scan xy
 │    │         │    │    └── columns: y:9
 │    │         │    └── projections
 │    │         │         └── true [as=canary:14]
 │    │         └── filters
 │    │              └── (y:9 = k:1) OR (y:9 IS NULL) [outer=(1,9)]
 │    └── aggregations
 │         ├── array-agg [as=array_agg:15, outer=(9)]
 │         │    └── y:9
 │         ├── const-agg [as=i:2, outer=(2)]
 │         │    └── i:2
 │         └── any-not-null-agg [as=canary:14, outer=(14)]
 │              └── canary:14
 └── projections
      └── COALESCE(CASE WHEN canary:14 IS NOT NULL THEN array_agg:15 ELSE CAST(NULL AS INT8[]) END, ARRAY[]) [as=array:13, outer=(14,15)]

# Nest scalar decorrelation within scalar decorrelation, using IS NULL to force
# use of left joins.
norm expect=TryDecorrelateScalarGroupBy
SELECT *
FROM a
WHERE
(
    SELECT max(y)
    FROM xy
    WHERE
    (
        SELECT max(v) FROM uv WHERE u=k
    ) IS NULL
) IS NULL
----
project
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 └── select
      ├── columns: k:1!null i:2 f:3 s:4 j:5 max:17
      ├── key: (1)
      ├── fd: ()-->(17), (1)-->(2-5)
      ├── group-by (hash)
      │    ├── columns: k:1!null i:2 f:3 s:4 j:5 max:17
      │    ├── grouping columns: k:1!null
      │    ├── key: (1)
      │    ├── fd: (1)-->(2-5,17)
      │    ├── left-join-apply
      │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5 x:8 y:9 max:16
      │    │    ├── key: (1,8)
      │    │    ├── fd: (1)-->(2-5), (1,8)-->(9,16)
      │    │    ├── scan a
      │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
      │    │    │    ├── key: (1)
      │    │    │    └── fd: (1)-->(2-5)
      │    │    ├── group-by (hash)
      │    │    │    ├── columns: x:8!null y:9 max:16
      │    │    │    ├── grouping columns: x:8!null
      │    │    │    ├── outer: (1)
      │    │    │    ├── key: (8)
      │    │    │    ├── fd: (8)-->(9,16)
      │    │    │    ├── left-join (cross)
      │    │    │    │    ├── columns: x:8!null y:9 u:12 v:13
      │    │    │    │    ├── outer: (1)
      │    │    │    │    ├── key: (8,12)
      │    │    │    │    ├── fd: (8)-->(9), (12)-->(13)
      │    │    │    │    ├── scan xy
      │    │    │    │    │    ├── columns: x:8!null y:9
      │    │    │    │    │    ├── key: (8)
      │    │    │    │    │    └── fd: (8)-->(9)
      │    │    │    │    ├── scan uv
      │    │    │    │    │    ├── columns: u:12!null v:13
      │    │    │    │    │    ├── key: (12)
      │    │    │    │    │    └── fd: (12)-->(13)
      │    │    │    │    └── filters
      │    │    │    │         └── u:12 = k:1 [outer=(1,12), constraints=(/1: (/NULL - ]; /12: (/NULL - ]), fd=(1)==(12), (12)==(1)]
      │    │    │    └── aggregations
      │    │    │         ├── max [as=max:16, outer=(13)]
      │    │    │         │    └── v:13
      │    │    │         └── const-agg [as=y:9, outer=(9)]
      │    │    │              └── y:9
      │    │    └── filters
      │    │         └── max:16 IS NULL [outer=(16), constraints=(/16: [/NULL - /NULL]; tight), fd=()-->(16)]
      │    └── aggregations
      │         ├── max [as=max:17, outer=(9)]
      │         │    └── y:9
      │         ├── const-agg [as=i:2, outer=(2)]
      │         │    └── i:2
      │         ├── const-agg [as=f:3, outer=(3)]
      │         │    └── f:3
      │         ├── const-agg [as=s:4, outer=(4)]
      │         │    └── s:4
      │         └── const-agg [as=j:5, outer=(5)]
      │              └── j:5
      └── filters
           └── max:17 IS NULL [outer=(17), constraints=(/17: [/NULL - /NULL]; tight), fd=()-->(17)]

# ScalarGroupBy with non-null ignoring and a non-nullable column.
norm expect=TryDecorrelateScalarGroupBy
SELECT *
FROM cd
WHERE
(
    SELECT array_agg(y)
    FROM xy
    WHERE c = x
) = ARRAY[]:::INT[]
----
project
 ├── columns: c:1!null d:2!null
 ├── key: (1)
 ├── fd: (1)-->(2)
 └── select
      ├── columns: c:1!null d:2!null array_agg:9!null
      ├── key: (1)
      ├── fd: ()-->(9), (1)-->(2)
      ├── project
      │    ├── columns: array_agg:9 c:1!null d:2!null
      │    ├── key: (1)
      │    ├── fd: (1)-->(2,9)
      │    ├── group-by (hash)
      │    │    ├── columns: c:1!null d:2!null x:5 array_agg:10
      │    │    ├── grouping columns: c:1!null
      │    │    ├── key: (1)
      │    │    ├── fd: (1)-->(2,5,10)
      │    │    ├── left-join (hash)
      │    │    │    ├── columns: c:1!null d:2!null x:5 y:6
      │    │    │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
      │    │    │    ├── key: (1)
      │    │    │    ├── fd: (1)-->(2,5,6), (5)-->(6)
      │    │    │    ├── scan cd
      │    │    │    │    ├── columns: c:1!null d:2!null
      │    │    │    │    ├── key: (1)
      │    │    │    │    └── fd: (1)-->(2)
      │    │    │    ├── scan xy
      │    │    │    │    ├── columns: x:5!null y:6
      │    │    │    │    ├── key: (5)
      │    │    │    │    └── fd: (5)-->(6)
      │    │    │    └── filters
      │    │    │         └── c:1 = x:5 [outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
      │    │    └── aggregations
      │    │         ├── array-agg [as=array_agg:10, outer=(6)]
      │    │         │    └── y:6
      │    │         ├── const-agg [as=d:2, outer=(2)]
      │    │         │    └── d:2
      │    │         └── any-not-null-agg [as=x:5, outer=(5)]
      │    │              └── x:5
      │    └── projections
      │         └── CASE WHEN x:5 IS NOT NULL THEN array_agg:10 ELSE CAST(NULL AS INT8[]) END [as=array_agg:9, outer=(5,10)]
      └── filters
           └── array_agg:9 = ARRAY[] [outer=(9), constraints=(/9: [/ARRAY[] - /ARRAY[]]; tight), fd=()-->(9)]

norm expect=TryDecorrelateScalarGroupBy
SELECT * FROM a WHERE 'foo'=(SELECT concat_agg(y::string) FROM xy WHERE x=k)
----
project
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 └── select
      ├── columns: k:1!null i:2 f:3 s:4 j:5 concat_agg:13!null
      ├── immutable
      ├── key: (1)
      ├── fd: ()-->(13), (1)-->(2-5)
      ├── project
      │    ├── columns: concat_agg:13 k:1!null i:2 f:3 s:4 j:5
      │    ├── immutable
      │    ├── key: (1)
      │    ├── fd: (1)-->(2-5,13)
      │    ├── group-by (hash)
      │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5 canary:14 concat_agg:15
      │    │    ├── grouping columns: k:1!null
      │    │    ├── immutable
      │    │    ├── key: (1)
      │    │    ├── fd: (1)-->(2-5,14,15)
      │    │    ├── left-join (hash)
      │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5 x:8 column12:12 canary:14
      │    │    │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
      │    │    │    ├── immutable
      │    │    │    ├── key: (1)
      │    │    │    ├── fd: (1)-->(2-5,8,12,14), (8)-->(12)
      │    │    │    ├── scan a
      │    │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
      │    │    │    │    ├── key: (1)
      │    │    │    │    └── fd: (1)-->(2-5)
      │    │    │    ├── project
      │    │    │    │    ├── columns: canary:14!null column12:12 x:8!null
      │    │    │    │    ├── immutable
      │    │    │    │    ├── key: (8)
      │    │    │    │    ├── fd: ()-->(14), (8)-->(12)
      │    │    │    │    ├── scan xy
      │    │    │    │    │    ├── columns: x:8!null y:9
      │    │    │    │    │    ├── key: (8)
      │    │    │    │    │    └── fd: (8)-->(9)
      │    │    │    │    └── projections
      │    │    │    │         ├── true [as=canary:14]
      │    │    │    │         └── y:9::STRING [as=column12:12, outer=(9), immutable]
      │    │    │    └── filters
      │    │    │         └── x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]
      │    │    └── aggregations
      │    │         ├── concat-agg [as=concat_agg:15, outer=(12)]
      │    │         │    └── column12:12
      │    │         ├── const-agg [as=i:2, outer=(2)]
      │    │         │    └── i:2
      │    │         ├── const-agg [as=f:3, outer=(3)]
      │    │         │    └── f:3
      │    │         ├── const-agg [as=s:4, outer=(4)]
      │    │         │    └── s:4
      │    │         ├── const-agg [as=j:5, outer=(5)]
      │    │         │    └── j:5
      │    │         └── any-not-null-agg [as=canary:14, outer=(14)]
      │    │              └── canary:14
      │    └── projections
      │         └── CASE WHEN canary:14 IS NOT NULL THEN concat_agg:15 ELSE CAST(NULL AS STRING) END [as=concat_agg:13, outer=(14,15)]
      └── filters
           └── concat_agg:13 = 'foo' [outer=(13), constraints=(/13: [/'foo' - /'foo']; tight), fd=()-->(13)]

# With a multi-argument aggregate.
norm expect=TryDecorrelateScalarGroupBy
SELECT k, (SELECT string_agg(a.s, ',') FROM a WHERE a.k = a2.i) FROM a AS a2
----
project
 ├── columns: k:1!null string_agg:17
 ├── key: (1)
 ├── fd: (1)-->(17)
 ├── group-by (hash)
 │    ├── columns: a2.k:1!null string_agg:16
 │    ├── grouping columns: a2.k:1!null
 │    ├── key: (1)
 │    ├── fd: (1)-->(16)
 │    ├── left-join (hash)
 │    │    ├── columns: a2.k:1!null a2.i:2 a.k:8 a.s:11 column15:15
 │    │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more)
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(2,8,11,15), (8)-->(11)
 │    │    ├── scan a [as=a2]
 │    │    │    ├── columns: a2.k:1!null a2.i:2
 │    │    │    ├── key: (1)
 │    │    │    └── fd: (1)-->(2)
 │    │    ├── project
 │    │    │    ├── columns: column15:15!null a.k:8!null a.s:11
 │    │    │    ├── key: (8)
 │    │    │    ├── fd: ()-->(15), (8)-->(11)
 │    │    │    ├── scan a
 │    │    │    │    ├── columns: a.k:8!null a.s:11
 │    │    │    │    ├── key: (8)
 │    │    │    │    └── fd: (8)-->(11)
 │    │    │    └── projections
 │    │    │         └── ',' [as=column15:15]
 │    │    └── filters
 │    │         └── a.k:8 = a2.i:2 [outer=(2,8), constraints=(/2: (/NULL - ]; /8: (/NULL - ]), fd=(2)==(8), (8)==(2)]
 │    └── aggregations
 │         └── string-agg [as=string_agg:16, outer=(11,15)]
 │              ├── a.s:11
 │              └── column15:15
 └── projections
      └── string_agg:16 [as=string_agg:17, outer=(16)]

# --------------------------------------------------
# TryDecorrelateSemiJoin
# --------------------------------------------------

# Right input of SemiJoin is GroupBy.
norm expect=TryDecorrelateSemiJoin
SELECT *
FROM xy
WHERE EXISTS
(
    SELECT * FROM a WHERE i=(SELECT max(i) FROM a WHERE f=y::float)
)
----
group-by (hash)
 ├── columns: x:1!null y:2
 ├── grouping columns: x:1!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2)
 ├── select
 │    ├── columns: x:1!null y:2 k:5!null i:6!null max:19!null
 │    ├── immutable
 │    ├── key: (1,5)
 │    ├── fd: (1)-->(2), (5)-->(6), (1,5)-->(2,6,19), (6)==(19), (19)==(6)
 │    ├── group-by (hash)
 │    │    ├── columns: x:1!null y:2 k:5!null i:6 max:19!null
 │    │    ├── grouping columns: x:1!null k:5!null
 │    │    ├── immutable
 │    │    ├── key: (1,5)
 │    │    ├── fd: (1)-->(2), (5)-->(6), (1,5)-->(2,6,19)
 │    │    ├── inner-join (cross)
 │    │    │    ├── columns: x:1!null y:2 k:5!null i:6 i:13!null f:14!null column21:21!null
 │    │    │    ├── immutable
 │    │    │    ├── fd: (1)-->(2), (2)-->(21), (5)-->(6), (14)==(21), (21)==(14)
 │    │    │    ├── inner-join (hash)
 │    │    │    │    ├── columns: x:1!null y:2 i:13!null f:14!null column21:21!null
 │    │    │    │    ├── immutable
 │    │    │    │    ├── fd: (1)-->(2), (2)-->(21), (14)==(21), (21)==(14)
 │    │    │    │    ├── project
 │    │    │    │    │    ├── columns: column21:21 x:1!null y:2
 │    │    │    │    │    ├── immutable
 │    │    │    │    │    ├── key: (1)
 │    │    │    │    │    ├── fd: (1)-->(2), (2)-->(21)
 │    │    │    │    │    ├── scan xy
 │    │    │    │    │    │    ├── columns: x:1!null y:2
 │    │    │    │    │    │    ├── key: (1)
 │    │    │    │    │    │    └── fd: (1)-->(2)
 │    │    │    │    │    └── projections
 │    │    │    │    │         └── y:2::FLOAT8 [as=column21:21, outer=(2), immutable]
 │    │    │    │    ├── select
 │    │    │    │    │    ├── columns: i:13!null f:14
 │    │    │    │    │    ├── scan a
 │    │    │    │    │    │    └── columns: i:13 f:14
 │    │    │    │    │    └── filters
 │    │    │    │    │         └── i:13 IS NOT NULL [outer=(13), constraints=(/13: (/NULL - ]; tight)]
 │    │    │    │    └── filters
 │    │    │    │         └── column21:21 = f:14 [outer=(14,21), constraints=(/14: (/NULL - ]; /21: (/NULL - ]), fd=(14)==(21), (21)==(14)]
 │    │    │    ├── scan a
 │    │    │    │    ├── columns: k:5!null i:6
 │    │    │    │    ├── key: (5)
 │    │    │    │    └── fd: (5)-->(6)
 │    │    │    └── filters (true)
 │    │    └── aggregations
 │    │         ├── max [as=max:19, outer=(13)]
 │    │         │    └── i:13
 │    │         ├── const-agg [as=i:6, outer=(6)]
 │    │         │    └── i:6
 │    │         └── const-agg [as=y:2, outer=(2)]
 │    │              └── y:2
 │    └── filters
 │         └── i:6 = max:19 [outer=(6,19), constraints=(/6: (/NULL - ]; /19: (/NULL - ]), fd=(6)==(19), (19)==(6)]
 └── aggregations
      └── const-agg [as=y:2, outer=(2)]
           └── y:2

# Right input of SemiJoin is DistinctOn.
norm expect=TryDecorrelateSemiJoin
SELECT *
FROM xy
WHERE EXISTS
(
    SELECT * FROM (SELECT DISTINCT ON (f) i FROM a WHERE y > f) WHERE x=i
)
----
group-by (hash)
 ├── columns: x:1!null y:2!null
 ├── grouping columns: x:1!null
 ├── key: (1)
 ├── fd: (1)-->(2)
 ├── select
 │    ├── columns: x:1!null y:2!null i:6!null f:7!null
 │    ├── key: (1,7)
 │    ├── fd: (1)-->(2), (1,7)-->(2,6), (1)==(6), (6)==(1)
 │    ├── distinct-on
 │    │    ├── columns: x:1!null y:2!null i:6 f:7!null
 │    │    ├── grouping columns: x:1!null f:7!null
 │    │    ├── key: (1,7)
 │    │    ├── fd: (1)-->(2), (1,7)-->(2,6)
 │    │    ├── inner-join (cross)
 │    │    │    ├── columns: x:1!null y:2!null i:6 f:7!null
 │    │    │    ├── fd: (1)-->(2)
 │    │    │    ├── scan xy
 │    │    │    │    ├── columns: x:1!null y:2
 │    │    │    │    ├── key: (1)
 │    │    │    │    └── fd: (1)-->(2)
 │    │    │    ├── scan a
 │    │    │    │    └── columns: i:6 f:7
 │    │    │    └── filters
 │    │    │         └── y:2 > f:7 [outer=(2,7), constraints=(/2: (/NULL - ]; /7: (/NULL - ])]
 │    │    └── aggregations
 │    │         ├── first-agg [as=i:6, outer=(6)]
 │    │         │    └── i:6
 │    │         └── const-agg [as=y:2, outer=(2)]
 │    │              └── y:2
 │    └── filters
 │         └── x:1 = i:6 [outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ]), fd=(1)==(6), (6)==(1)]
 └── aggregations
      └── const-agg [as=y:2, outer=(2)]
           └── y:2

# Right input of SemiJoin is Project.
norm expect=TryDecorrelateSemiJoin
SELECT k FROM a
WHERE EXISTS
(
    SELECT * FROM xy INNER JOIN (SELECT coalesce(u, 10) AS computed FROM uv WHERE u=i) ON x=computed
)
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── select
      ├── columns: k:1!null x:8!null computed:16!null
      ├── key: (1)
      ├── fd: (1)-->(16), (8)==(16), (16)==(8)
      ├── project
      │    ├── columns: computed:16 k:1!null x:8!null
      │    ├── key: (1,8)
      │    ├── fd: (1)-->(16)
      │    ├── inner-join (cross)
      │    │    ├── columns: k:1!null i:2!null x:8!null u:12!null
      │    │    ├── key: (1,8)
      │    │    ├── fd: (1)-->(2), (2)==(12), (12)==(2)
      │    │    ├── inner-join (hash)
      │    │    │    ├── columns: k:1!null i:2!null u:12!null
      │    │    │    ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-more)
      │    │    │    ├── key: (1)
      │    │    │    ├── fd: (1)-->(2), (2)==(12), (12)==(2)
      │    │    │    ├── scan a
      │    │    │    │    ├── columns: k:1!null i:2
      │    │    │    │    ├── key: (1)
      │    │    │    │    └── fd: (1)-->(2)
      │    │    │    ├── scan uv
      │    │    │    │    ├── columns: u:12!null
      │    │    │    │    └── key: (12)
      │    │    │    └── filters
      │    │    │         └── u:12 = i:2 [outer=(2,12), constraints=(/2: (/NULL - ]; /12: (/NULL - ]), fd=(2)==(12), (12)==(2)]
      │    │    ├── scan xy
      │    │    │    ├── columns: x:8!null
      │    │    │    └── key: (8)
      │    │    └── filters (true)
      │    └── projections
      │         └── COALESCE(u:12, 10) [as=computed:16, outer=(12)]
      └── filters
           └── x:8 = computed:16 [outer=(8,16), constraints=(/8: (/NULL - ]; /16: (/NULL - ]), fd=(8)==(16), (16)==(8)]

# Right input of SemiJoin is ProjectSet.
norm expect=TryDecorrelateSemiJoin
SELECT * FROM xy WHERE EXISTS(SELECT generate_series(x, 10), generate_series(y, 10))
----
group-by (hash)
 ├── columns: x:1!null y:2
 ├── grouping columns: x:1!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2)
 ├── project-set
 │    ├── columns: x:1!null y:2 generate_series:5 generate_series:6
 │    ├── immutable
 │    ├── fd: (1)-->(2)
 │    ├── scan xy
 │    │    ├── columns: x:1!null y:2
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2)
 │    └── zip
 │         ├── generate_series(x:1, 10) [outer=(1), immutable]
 │         └── generate_series(y:2, 10) [outer=(2), immutable]
 └── aggregations
      └── const-agg [as=y:2, outer=(2)]
           └── y:2

# Right input of SemiJoin is Window.
norm expect=TryDecorrelateSemiJoin
SELECT *
FROM xy
WHERE EXISTS
(
    SELECT * FROM a WHERE EXISTS (SELECT max(i) OVER () FROM a WHERE f=y::float)
)
----
project
 ├── columns: x:1!null y:2
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2)
 └── semi-join (hash)
      ├── columns: x:1!null y:2 column22:22
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(2), (2)-->(22)
      ├── project
      │    ├── columns: column22:22 x:1!null y:2
      │    ├── immutable
      │    ├── key: (1)
      │    ├── fd: (1)-->(2), (2)-->(22)
      │    ├── scan xy
      │    │    ├── columns: x:1!null y:2
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2)
      │    └── projections
      │         └── y:2::FLOAT8 [as=column22:22, outer=(2), immutable]
      ├── inner-join (cross)
      │    ├── columns: f:14
      │    ├── scan a
      │    ├── scan a
      │    │    └── columns: f:14
      │    └── filters (true)
      └── filters
           └── column22:22 = f:14 [outer=(14,22), constraints=(/14: (/NULL - ]; /22: (/NULL - ]), fd=(14)==(22), (22)==(14)]

# --------------------------------------------------
# TryDecorrelateLimitOne
# --------------------------------------------------

# With inner join.
norm expect=TryDecorrelateLimitOne
SELECT *
FROM a
WHERE EXISTS
(
    SELECT x
    FROM xy
    INNER JOIN (SELECT * FROM uv WHERE v=i LIMIT 1)
    ON x=u
)
----
semi-join (hash)
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── inner-join (hash)
 │    ├── columns: x:8!null u:12!null v:13
 │    ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one)
 │    ├── key: (12)
 │    ├── fd: (12)-->(13), (8)==(12), (12)==(8)
 │    ├── scan xy
 │    │    ├── columns: x:8!null
 │    │    └── key: (8)
 │    ├── scan uv
 │    │    ├── columns: u:12!null v:13
 │    │    ├── key: (12)
 │    │    └── fd: (12)-->(13)
 │    └── filters
 │         └── x:8 = u:12 [outer=(8,12), constraints=(/8: (/NULL - ]; /12: (/NULL - ]), fd=(8)==(12), (12)==(8)]
 └── filters
      └── v:13 = i:2 [outer=(2,13), constraints=(/2: (/NULL - ]; /13: (/NULL - ]), fd=(2)==(13), (13)==(2)]

# With left join.
norm expect=TryDecorrelateLimitOne
SELECT (SELECT x FROM xy WHERE y=i LIMIT 1) FROM a
----
project
 ├── columns: x:12
 ├── distinct-on
 │    ├── columns: k:1!null xy.x:8
 │    ├── grouping columns: k:1!null
 │    ├── key: (1)
 │    ├── fd: (1)-->(8)
 │    ├── left-join (hash)
 │    │    ├── columns: k:1!null i:2 xy.x:8 y:9
 │    │    ├── key: (1,8)
 │    │    ├── fd: (1)-->(2), (8)-->(9)
 │    │    ├── scan a
 │    │    │    ├── columns: k:1!null i:2
 │    │    │    ├── key: (1)
 │    │    │    └── fd: (1)-->(2)
 │    │    ├── scan xy
 │    │    │    ├── columns: xy.x:8!null y:9
 │    │    │    ├── key: (8)
 │    │    │    └── fd: (8)-->(9)
 │    │    └── filters
 │    │         └── y:9 = i:2 [outer=(2,9), constraints=(/2: (/NULL - ]; /9: (/NULL - ]), fd=(2)==(9), (9)==(2)]
 │    └── aggregations
 │         └── first-agg [as=xy.x:8, outer=(8)]
 │              └── xy.x:8
 └── projections
      └── xy.x:8 [as=x:12, outer=(8)]

# With multiple limited queries.
norm expect=TryDecorrelateLimitOne
SELECT * FROM a WHERE (SELECT x FROM xy WHERE y=i LIMIT 1)=k AND (SELECT u FROM uv WHERE v=i LIMIT 1)=k
----
project
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 └── select
      ├── columns: k:1!null i:2 f:3 s:4 j:5 u:12!null
      ├── key: (1)
      ├── fd: (1)-->(2-5), (1)==(12), (12)==(1)
      ├── distinct-on
      │    ├── columns: k:1!null i:2 f:3 s:4 j:5 u:12
      │    ├── grouping columns: k:1!null
      │    ├── key: (1)
      │    ├── fd: (1)-->(2-5,12)
      │    ├── left-join (hash)
      │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5 x:8!null u:12 v:13
      │    │    ├── key: (1,12)
      │    │    ├── fd: (1)-->(2-5), (12)-->(13), (1)==(8), (8)==(1)
      │    │    ├── select
      │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5 x:8!null
      │    │    │    ├── key: (1)
      │    │    │    ├── fd: (1)-->(2-5), (1)==(8), (8)==(1)
      │    │    │    ├── distinct-on
      │    │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5 x:8
      │    │    │    │    ├── grouping columns: k:1!null
      │    │    │    │    ├── key: (1)
      │    │    │    │    ├── fd: (1)-->(2-5,8)
      │    │    │    │    ├── left-join (hash)
      │    │    │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5 x:8 y:9
      │    │    │    │    │    ├── key: (1,8)
      │    │    │    │    │    ├── fd: (1)-->(2-5), (8)-->(9)
      │    │    │    │    │    ├── scan a
      │    │    │    │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
      │    │    │    │    │    │    ├── key: (1)
      │    │    │    │    │    │    └── fd: (1)-->(2-5)
      │    │    │    │    │    ├── scan xy
      │    │    │    │    │    │    ├── columns: x:8!null y:9
      │    │    │    │    │    │    ├── key: (8)
      │    │    │    │    │    │    └── fd: (8)-->(9)
      │    │    │    │    │    └── filters
      │    │    │    │    │         └── y:9 = i:2 [outer=(2,9), constraints=(/2: (/NULL - ]; /9: (/NULL - ]), fd=(2)==(9), (9)==(2)]
      │    │    │    │    └── aggregations
      │    │    │    │         ├── const-agg [as=i:2, outer=(2)]
      │    │    │    │         │    └── i:2
      │    │    │    │         ├── const-agg [as=f:3, outer=(3)]
      │    │    │    │         │    └── f:3
      │    │    │    │         ├── const-agg [as=s:4, outer=(4)]
      │    │    │    │         │    └── s:4
      │    │    │    │         ├── const-agg [as=j:5, outer=(5)]
      │    │    │    │         │    └── j:5
      │    │    │    │         └── first-agg [as=x:8, outer=(8)]
      │    │    │    │              └── x:8
      │    │    │    └── filters
      │    │    │         └── k:1 = x:8 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]
      │    │    ├── scan uv
      │    │    │    ├── columns: u:12!null v:13
      │    │    │    ├── key: (12)
      │    │    │    └── fd: (12)-->(13)
      │    │    └── filters
      │    │         └── v:13 = i:2 [outer=(2,13), constraints=(/2: (/NULL - ]; /13: (/NULL - ]), fd=(2)==(13), (13)==(2)]
      │    └── aggregations
      │         ├── const-agg [as=i:2, outer=(2)]
      │         │    └── i:2
      │         ├── const-agg [as=f:3, outer=(3)]
      │         │    └── f:3
      │         ├── const-agg [as=s:4, outer=(4)]
      │         │    └── s:4
      │         ├── const-agg [as=j:5, outer=(5)]
      │         │    └── j:5
      │         └── first-agg [as=u:12, outer=(12)]
      │              └── u:12
      └── filters
           └── k:1 = u:12 [outer=(1,12), constraints=(/1: (/NULL - ]; /12: (/NULL - ]), fd=(1)==(12), (12)==(1)]

# With nested limited queries.
norm expect=TryDecorrelateLimitOne
SELECT *
FROM a
WHERE
(
    SELECT x
    FROM xy
    WHERE y=i AND
    (
        SELECT u FROM uv WHERE v=y LIMIT 1
    )=x
    LIMIT 1
)=k
----
project
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 └── select
      ├── columns: k:1!null i:2 f:3 s:4 j:5 x:8!null
      ├── key: (1)
      ├── fd: (1)-->(2-5), (1)==(8), (8)==(1)
      ├── distinct-on
      │    ├── columns: k:1!null i:2 f:3 s:4 j:5 x:8
      │    ├── grouping columns: k:1!null
      │    ├── key: (1)
      │    ├── fd: (1)-->(2-5,8)
      │    ├── left-join (hash)
      │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5 x:8 y:9 u:12
      │    │    ├── key: (1,8)
      │    │    ├── fd: (1)-->(2-5), (8)-->(9), (8)==(12), (12)==(8)
      │    │    ├── scan a
      │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
      │    │    │    ├── key: (1)
      │    │    │    └── fd: (1)-->(2-5)
      │    │    ├── select
      │    │    │    ├── columns: x:8!null y:9 u:12!null
      │    │    │    ├── key: (8)
      │    │    │    ├── fd: (8)-->(9), (8)==(12), (12)==(8)
      │    │    │    ├── distinct-on
      │    │    │    │    ├── columns: x:8!null y:9 u:12
      │    │    │    │    ├── grouping columns: x:8!null
      │    │    │    │    ├── key: (8)
      │    │    │    │    ├── fd: (8)-->(9,12)
      │    │    │    │    ├── left-join (hash)
      │    │    │    │    │    ├── columns: x:8!null y:9 u:12 v:13
      │    │    │    │    │    ├── key: (8,12)
      │    │    │    │    │    ├── fd: (8)-->(9), (12)-->(13)
      │    │    │    │    │    ├── scan xy
      │    │    │    │    │    │    ├── columns: x:8!null y:9
      │    │    │    │    │    │    ├── key: (8)
      │    │    │    │    │    │    └── fd: (8)-->(9)
      │    │    │    │    │    ├── scan uv
      │    │    │    │    │    │    ├── columns: u:12!null v:13
      │    │    │    │    │    │    ├── key: (12)
      │    │    │    │    │    │    └── fd: (12)-->(13)
      │    │    │    │    │    └── filters
      │    │    │    │    │         └── v:13 = y:9 [outer=(9,13), constraints=(/9: (/NULL - ]; /13: (/NULL - ]), fd=(9)==(13), (13)==(9)]
      │    │    │    │    └── aggregations
      │    │    │    │         ├── const-agg [as=y:9, outer=(9)]
      │    │    │    │         │    └── y:9
      │    │    │    │         └── first-agg [as=u:12, outer=(12)]
      │    │    │    │              └── u:12
      │    │    │    └── filters
      │    │    │         └── x:8 = u:12 [outer=(8,12), constraints=(/8: (/NULL - ]; /12: (/NULL - ]), fd=(8)==(12), (12)==(8)]
      │    │    └── filters
      │    │         └── y:9 = i:2 [outer=(2,9), constraints=(/2: (/NULL - ]; /9: (/NULL - ]), fd=(2)==(9), (9)==(2)]
      │    └── aggregations
      │         ├── const-agg [as=i:2, outer=(2)]
      │         │    └── i:2
      │         ├── const-agg [as=f:3, outer=(3)]
      │         │    └── f:3
      │         ├── const-agg [as=s:4, outer=(4)]
      │         │    └── s:4
      │         ├── const-agg [as=j:5, outer=(5)]
      │         │    └── j:5
      │         └── first-agg [as=x:8, outer=(8)]
      │              └── x:8
      └── filters
           └── k:1 = x:8 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]

# With inner join + ORDER BY.
norm expect=TryDecorrelateLimitOne
SELECT
(
    SELECT v
    FROM uv
    INNER JOIN (SELECT * FROM a WHERE i=x ORDER BY f LIMIT 1)
    ON u=k
    LIMIT 1
)
FROM xy
----
project
 ├── columns: v:16
 ├── distinct-on
 │    ├── columns: x:1!null uv.v:6
 │    ├── grouping columns: x:1!null
 │    ├── key: (1)
 │    ├── fd: (1)-->(6)
 │    ├── left-join (hash)
 │    │    ├── columns: x:1!null u:5 uv.v:6 k:9 i:10
 │    │    ├── multiplicity: left-rows(one-or-more), right-rows(zero-or-one)
 │    │    ├── key: (1,9)
 │    │    ├── fd: (5)-->(6), (9)-->(10), (5)==(9), (9)==(5)
 │    │    ├── scan xy
 │    │    │    ├── columns: x:1!null
 │    │    │    └── key: (1)
 │    │    ├── inner-join (hash)
 │    │    │    ├── columns: u:5!null uv.v:6 k:9!null i:10
 │    │    │    ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one)
 │    │    │    ├── key: (9)
 │    │    │    ├── fd: (5)-->(6), (9)-->(10), (5)==(9), (9)==(5)
 │    │    │    ├── scan uv
 │    │    │    │    ├── columns: u:5!null uv.v:6
 │    │    │    │    ├── key: (5)
 │    │    │    │    └── fd: (5)-->(6)
 │    │    │    ├── scan a
 │    │    │    │    ├── columns: k:9!null i:10
 │    │    │    │    ├── key: (9)
 │    │    │    │    └── fd: (9)-->(10)
 │    │    │    └── filters
 │    │    │         └── u:5 = k:9 [outer=(5,9), constraints=(/5: (/NULL - ]; /9: (/NULL - ]), fd=(5)==(9), (9)==(5)]
 │    │    └── filters
 │    │         └── i:10 = x:1 [outer=(1,10), constraints=(/1: (/NULL - ]; /10: (/NULL - ]), fd=(1)==(10), (10)==(1)]
 │    └── aggregations
 │         └── first-agg [as=uv.v:6, outer=(6)]
 │              └── uv.v:6
 └── projections
      └── uv.v:6 [as=v:16, outer=(6)]

# With left join + ORDER BY.
norm expect=TryDecorrelateLimitOne
SELECT * FROM xy WHERE (SELECT k FROM a WHERE i=y ORDER BY f,s LIMIT 1)=x
----
project
 ├── columns: x:1!null y:2
 ├── key: (1)
 ├── fd: (1)-->(2)
 └── select
      ├── columns: x:1!null y:2 k:5!null
      ├── key: (1)
      ├── fd: (1)-->(2), (1)==(5), (5)==(1)
      ├── distinct-on
      │    ├── columns: x:1!null y:2 k:5
      │    ├── grouping columns: x:1!null
      │    ├── internal-ordering: +7,+8
      │    ├── key: (1)
      │    ├── fd: (1)-->(2,5)
      │    ├── sort
      │    │    ├── columns: x:1!null y:2 k:5 i:6 f:7 s:8
      │    │    ├── key: (1,5)
      │    │    ├── fd: (1)-->(2), (5)-->(6-8)
      │    │    ├── ordering: +7,+8
      │    │    └── left-join (hash)
      │    │         ├── columns: x:1!null y:2 k:5 i:6 f:7 s:8
      │    │         ├── key: (1,5)
      │    │         ├── fd: (1)-->(2), (5)-->(6-8)
      │    │         ├── scan xy
      │    │         │    ├── columns: x:1!null y:2
      │    │         │    ├── key: (1)
      │    │         │    └── fd: (1)-->(2)
      │    │         ├── scan a
      │    │         │    ├── columns: k:5!null i:6 f:7 s:8
      │    │         │    ├── key: (5)
      │    │         │    └── fd: (5)-->(6-8)
      │    │         └── filters
      │    │              └── i:6 = y:2 [outer=(2,6), constraints=(/2: (/NULL - ]; /6: (/NULL - ]), fd=(2)==(6), (6)==(2)]
      │    └── aggregations
      │         ├── const-agg [as=y:2, outer=(2)]
      │         │    └── y:2
      │         └── first-agg [as=k:5, outer=(5)]
      │              └── k:5
      └── filters
           └── x:1 = k:5 [outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]

# --------------------------------------------------
# TryDecorrelateLimit
# --------------------------------------------------

# With inner join.
norm expect=TryDecorrelateLimit
SELECT *
FROM a
WHERE EXISTS
(
    SELECT x
    FROM xy
    INNER JOIN (SELECT * FROM uv WHERE v=i LIMIT 3)
    ON x=u
)
----
group-by (hash)
 ├── columns: k:1!null i:2!null f:3 s:4 j:5
 ├── grouping columns: k:1!null
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── select
 │    ├── columns: k:1!null i:2!null f:3 s:4 j:5 x:8!null u:12!null v:13!null row_num:16!null
 │    ├── key: (1,12)
 │    ├── fd: (1)-->(2-5), (12)-->(13), (1,8,12)-->(16), (2)==(13), (13)==(2), (8)==(12), (12)==(8)
 │    ├── window partition=(1,8)
 │    │    ├── columns: k:1!null i:2!null f:3 s:4 j:5 x:8!null u:12!null v:13!null row_num:16
 │    │    ├── key: (1,8,12)
 │    │    ├── fd: (1)-->(2-5), (12)-->(13), (1,8,12)-->(16), (2)==(13), (13)==(2)
 │    │    ├── inner-join (cross)
 │    │    │    ├── columns: k:1!null i:2!null f:3 s:4 j:5 x:8!null u:12!null v:13!null
 │    │    │    ├── key: (1,8,12)
 │    │    │    ├── fd: (1)-->(2-5), (12)-->(13), (2)==(13), (13)==(2)
 │    │    │    ├── inner-join (hash)
 │    │    │    │    ├── columns: k:1!null i:2!null f:3 s:4 j:5 u:12!null v:13!null
 │    │    │    │    ├── key: (1,12)
 │    │    │    │    ├── fd: (1)-->(2-5), (12)-->(13), (2)==(13), (13)==(2)
 │    │    │    │    ├── scan a
 │    │    │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    │    │    │    │    ├── key: (1)
 │    │    │    │    │    └── fd: (1)-->(2-5)
 │    │    │    │    ├── scan uv
 │    │    │    │    │    ├── columns: u:12!null v:13
 │    │    │    │    │    ├── key: (12)
 │    │    │    │    │    └── fd: (12)-->(13)
 │    │    │    │    └── filters
 │    │    │    │         └── v:13 = i:2 [outer=(2,13), constraints=(/2: (/NULL - ]; /13: (/NULL - ]), fd=(2)==(13), (13)==(2)]
 │    │    │    ├── scan xy
 │    │    │    │    ├── columns: x:8!null
 │    │    │    │    └── key: (8)
 │    │    │    └── filters (true)
 │    │    └── windows
 │    │         └── row-number [as=row_num:16]
 │    └── filters
 │         ├── x:8 = u:12 [outer=(8,12), constraints=(/8: (/NULL - ]; /12: (/NULL - ]), fd=(8)==(12), (12)==(8)]
 │         └── row_num:16 <= 3 [outer=(16), constraints=(/16: (/NULL - /3]; tight)]
 └── aggregations
      ├── const-agg [as=i:2, outer=(2)]
      │    └── i:2
      ├── const-agg [as=f:3, outer=(3)]
      │    └── f:3
      ├── const-agg [as=s:4, outer=(4)]
      │    └── s:4
      └── const-agg [as=j:5, outer=(5)]
           └── j:5

# With inner join + ORDER BY.
norm expect=TryDecorrelateLimit
SELECT
(
    SELECT v
    FROM uv
    INNER JOIN (SELECT * FROM a WHERE i=x ORDER BY f LIMIT 3)
    ON u=k
    LIMIT 3
)
FROM xy
----
project
 ├── columns: v:17
 ├── ensure-distinct-on
 │    ├── columns: x:1!null uv.v:6
 │    ├── grouping columns: x:1!null
 │    ├── error: "more than one row returned by a subquery used as an expression"
 │    ├── key: (1)
 │    ├── fd: (1)-->(6)
 │    ├── left-join-apply
 │    │    ├── columns: x:1!null u:5 uv.v:6 k:9 i:10 f:11 row_num:16
 │    │    ├── key: (1,9)
 │    │    ├── fd: (1,5)-->(6), (1,9)-->(10,11,16), (5)==(9), (9)==(5)
 │    │    ├── scan xy
 │    │    │    ├── columns: x:1!null
 │    │    │    └── key: (1)
 │    │    ├── limit
 │    │    │    ├── columns: u:5!null uv.v:6 k:9!null i:10!null f:11 row_num:16!null
 │    │    │    ├── outer: (1)
 │    │    │    ├── cardinality: [0 - 3]
 │    │    │    ├── key: (9)
 │    │    │    ├── fd: ()-->(10), (5)-->(6), (9)-->(11), (5,9)-->(16), (5)==(9), (9)==(5)
 │    │    │    ├── select
 │    │    │    │    ├── columns: u:5!null uv.v:6 k:9!null i:10!null f:11 row_num:16!null
 │    │    │    │    ├── outer: (1)
 │    │    │    │    ├── key: (9)
 │    │    │    │    ├── fd: ()-->(10), (5)-->(6), (9)-->(11), (5,9)-->(16), (5)==(9), (9)==(5)
 │    │    │    │    ├── limit hint: 3.00
 │    │    │    │    ├── window partition=(5) ordering=+11 opt(5-8,10)
 │    │    │    │    │    ├── columns: u:5!null uv.v:6 k:9!null i:10!null f:11 row_num:16
 │    │    │    │    │    ├── outer: (1)
 │    │    │    │    │    ├── key: (5,9)
 │    │    │    │    │    ├── fd: ()-->(10), (5)-->(6), (9)-->(11), (5,9)-->(16)
 │    │    │    │    │    ├── limit hint: 8999.57
 │    │    │    │    │    ├── inner-join (cross)
 │    │    │    │    │    │    ├── columns: u:5!null uv.v:6 k:9!null i:10!null f:11
 │    │    │    │    │    │    ├── outer: (1)
 │    │    │    │    │    │    ├── key: (5,9)
 │    │    │    │    │    │    ├── fd: ()-->(10), (5)-->(6), (9)-->(11)
 │    │    │    │    │    │    ├── scan uv
 │    │    │    │    │    │    │    ├── columns: u:5!null uv.v:6
 │    │    │    │    │    │    │    ├── key: (5)
 │    │    │    │    │    │    │    └── fd: (5)-->(6)
 │    │    │    │    │    │    ├── scan a
 │    │    │    │    │    │    │    ├── columns: k:9!null i:10 f:11
 │    │    │    │    │    │    │    ├── key: (9)
 │    │    │    │    │    │    │    └── fd: (9)-->(10,11)
 │    │    │    │    │    │    └── filters
 │    │    │    │    │    │         └── i:10 = x:1 [outer=(1,10), constraints=(/1: (/NULL - ]; /10: (/NULL - ]), fd=(1)==(10), (10)==(1)]
 │    │    │    │    │    └── windows
 │    │    │    │    │         └── row-number [as=row_num:16]
 │    │    │    │    └── filters
 │    │    │    │         ├── u:5 = k:9 [outer=(5,9), constraints=(/5: (/NULL - ]; /9: (/NULL - ]), fd=(5)==(9), (9)==(5)]
 │    │    │    │         └── row_num:16 <= 3 [outer=(16), constraints=(/16: (/NULL - /3]; tight)]
 │    │    │    └── 3
 │    │    └── filters (true)
 │    └── aggregations
 │         └── const-agg [as=uv.v:6, outer=(6)]
 │              └── uv.v:6
 └── projections
      └── uv.v:6 [as=v:17, outer=(6)]

# No-op because the limit is one.
norm expect-not=TryDecorrelateLimit
SELECT *
FROM a
WHERE EXISTS
(
    SELECT x
    FROM xy
    INNER JOIN (SELECT * FROM uv WHERE v=i LIMIT 1)
    ON x=u
)
----
semi-join (hash)
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── inner-join (hash)
 │    ├── columns: x:8!null u:12!null v:13
 │    ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one)
 │    ├── key: (12)
 │    ├── fd: (12)-->(13), (8)==(12), (12)==(8)
 │    ├── scan xy
 │    │    ├── columns: x:8!null
 │    │    └── key: (8)
 │    ├── scan uv
 │    │    ├── columns: u:12!null v:13
 │    │    ├── key: (12)
 │    │    └── fd: (12)-->(13)
 │    └── filters
 │         └── x:8 = u:12 [outer=(8,12), constraints=(/8: (/NULL - ]; /12: (/NULL - ]), fd=(8)==(12), (12)==(8)]
 └── filters
      └── v:13 = i:2 [outer=(2,13), constraints=(/2: (/NULL - ]; /13: (/NULL - ]), fd=(2)==(13), (13)==(2)]

# No-op because there is no correlation.
norm expect-not=TryDecorrelateLimit
SELECT * FROM xy
INNER JOIN (SELECT * FROM uv LIMIT 3)
ON x=u
----
inner-join (hash)
 ├── columns: x:1!null y:2 u:5!null v:6
 ├── cardinality: [0 - 3]
 ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one)
 ├── key: (5)
 ├── fd: (1)-->(2), (5)-->(6), (1)==(5), (5)==(1)
 ├── scan xy
 │    ├── columns: x:1!null y:2
 │    ├── key: (1)
 │    └── fd: (1)-->(2)
 ├── limit
 │    ├── columns: u:5!null v:6
 │    ├── cardinality: [0 - 3]
 │    ├── key: (5)
 │    ├── fd: (5)-->(6)
 │    ├── scan uv
 │    │    ├── columns: u:5!null v:6
 │    │    ├── key: (5)
 │    │    ├── fd: (5)-->(6)
 │    │    └── limit hint: 3.00
 │    └── 3
 └── filters
      └── x:1 = u:5 [outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]

# Don't match left joins.
norm expect-not=TryDecorrelateLimit
SELECT (SELECT x FROM xy WHERE y=i LIMIT 3) FROM a
----
project
 ├── columns: x:12
 ├── ensure-distinct-on
 │    ├── columns: k:1!null xy.x:8
 │    ├── grouping columns: k:1!null
 │    ├── error: "more than one row returned by a subquery used as an expression"
 │    ├── key: (1)
 │    ├── fd: (1)-->(8)
 │    ├── left-join-apply
 │    │    ├── columns: k:1!null i:2 xy.x:8 y:9
 │    │    ├── key: (1,8)
 │    │    ├── fd: (1)-->(2), (1,8)-->(9)
 │    │    ├── scan a
 │    │    │    ├── columns: k:1!null i:2
 │    │    │    ├── key: (1)
 │    │    │    └── fd: (1)-->(2)
 │    │    ├── limit
 │    │    │    ├── columns: xy.x:8!null y:9!null
 │    │    │    ├── outer: (2)
 │    │    │    ├── cardinality: [0 - 3]
 │    │    │    ├── key: (8)
 │    │    │    ├── fd: ()-->(9)
 │    │    │    ├── select
 │    │    │    │    ├── columns: xy.x:8!null y:9!null
 │    │    │    │    ├── outer: (2)
 │    │    │    │    ├── key: (8)
 │    │    │    │    ├── fd: ()-->(9)
 │    │    │    │    ├── limit hint: 3.00
 │    │    │    │    ├── scan xy
 │    │    │    │    │    ├── columns: xy.x:8!null y:9
 │    │    │    │    │    ├── key: (8)
 │    │    │    │    │    ├── fd: (8)-->(9)
 │    │    │    │    │    └── limit hint: 3.03
 │    │    │    │    └── filters
 │    │    │    │         └── y:9 = i:2 [outer=(2,9), constraints=(/2: (/NULL - ]; /9: (/NULL - ]), fd=(2)==(9), (9)==(2)]
 │    │    │    └── 3
 │    │    └── filters (true)
 │    └── aggregations
 │         └── const-agg [as=xy.x:8, outer=(8)]
 │              └── xy.x:8
 └── projections
      └── xy.x:8 [as=x:12, outer=(8)]

# Regression test for #122853 - remove the rowNum column from the transformed
# expression.
norm expect=TryDecorrelateLimit disable=PushLimitIntoProject
SELECT *, foo, xy.crdb_internal_mvcc_timestamp, xy.tableoid FROM xy
INNER JOIN LATERAL (SELECT *, v+y AS foo FROM uv LIMIT 3)
ON x=u
----
project
 ├── columns: x:1!null y:2 u:5!null v:6 foo:9 foo:9 crdb_internal_mvcc_timestamp:3 tableoid:4
 ├── immutable
 ├── key: (5)
 ├── fd: (1)-->(2-4), (5)-->(6), (2,6)-->(9), (1)==(5), (5)==(1)
 └── select
      ├── columns: x:1!null y:2 xy.crdb_internal_mvcc_timestamp:3 xy.tableoid:4 u:5!null v:6 foo:9 row_num:10!null
      ├── immutable
      ├── key: (5)
      ├── fd: (1)-->(2-4), (5)-->(6), (2,6)-->(9), (1,5)-->(10), (1)==(5), (5)==(1)
      ├── window partition=(1)
      │    ├── columns: x:1!null y:2 xy.crdb_internal_mvcc_timestamp:3 xy.tableoid:4 u:5!null v:6 foo:9 row_num:10
      │    ├── immutable
      │    ├── key: (1,5)
      │    ├── fd: (1)-->(2-4), (5)-->(6), (2,6)-->(9), (1,5)-->(10)
      │    ├── project
      │    │    ├── columns: foo:9 x:1!null y:2 xy.crdb_internal_mvcc_timestamp:3 xy.tableoid:4 u:5!null v:6
      │    │    ├── immutable
      │    │    ├── key: (1,5)
      │    │    ├── fd: (1)-->(2-4), (5)-->(6), (2,6)-->(9)
      │    │    ├── inner-join (cross)
      │    │    │    ├── columns: x:1!null y:2 xy.crdb_internal_mvcc_timestamp:3 xy.tableoid:4 u:5!null v:6
      │    │    │    ├── key: (1,5)
      │    │    │    ├── fd: (1)-->(2-4), (5)-->(6)
      │    │    │    ├── scan xy
      │    │    │    │    ├── columns: x:1!null y:2 xy.crdb_internal_mvcc_timestamp:3 xy.tableoid:4
      │    │    │    │    ├── key: (1)
      │    │    │    │    └── fd: (1)-->(2-4)
      │    │    │    ├── scan uv
      │    │    │    │    ├── columns: u:5!null v:6
      │    │    │    │    ├── key: (5)
      │    │    │    │    └── fd: (5)-->(6)
      │    │    │    └── filters (true)
      │    │    └── projections
      │    │         └── v:6 + y:2 [as=foo:9, outer=(2,6), immutable]
      │    └── windows
      │         └── row-number [as=row_num:10]
      └── filters
           ├── x:1 = u:5 [outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
           └── row_num:10 <= 3 [outer=(10), constraints=(/10: (/NULL - /3]; tight)]

# --------------------------------------------------
# TryDecorrelateMax1Row
# --------------------------------------------------

# Rule is activated on LeftJoinApply.
norm format=show-miscprops expect=TryDecorrelateMax1Row
SELECT (SELECT d FROM cd WHERE d=x) FROM xy
----
project
 ├── columns: d:9
 ├── ensure-distinct-on
 │    ├── columns: x:1!null cd.d:6
 │    ├── grouping columns: x:1!null
 │    ├── error: "more than one row returned by a subquery used as an expression"
 │    ├── key: (1)
 │    ├── fd: (1)-->(6)
 │    ├── left-join (hash)
 │    │    ├── columns: x:1!null cd.d:6
 │    │    ├── multiplicity: left-rows(one-or-more), right-rows(zero-or-one)
 │    │    ├── scan xy
 │    │    │    ├── columns: x:1!null
 │    │    │    └── key: (1)
 │    │    ├── scan cd
 │    │    │    └── columns: cd.d:6!null
 │    │    └── filters
 │    │         └── cd.d:6 = x:1 [outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ]), fd=(1)==(6), (6)==(1)]
 │    └── aggregations
 │         └── const-agg [as=cd.d:6, outer=(6)]
 │              └── cd.d:6
 └── projections
      └── cd.d:6 [as=d:9, outer=(6)]

# Rule is activated on LeftJoinApply.
# Case without a primary key on left side of join.
norm format=show-miscprops expect=TryDecorrelateMax1Row
SELECT * FROM (SELECT y FROM xy) WHERE 5 = (SELECT d FROM cd WHERE d=y)
----
project
 ├── columns: y:2
 └── select
      ├── columns: x:1!null y:2 d:6!null
      ├── key: (1)
      ├── fd: ()-->(6), (1)-->(2)
      ├── ensure-distinct-on
      │    ├── columns: x:1!null y:2 d:6
      │    ├── grouping columns: x:1!null
      │    ├── error: "more than one row returned by a subquery used as an expression"
      │    ├── key: (1)
      │    ├── fd: (1)-->(2,6)
      │    ├── left-join (hash)
      │    │    ├── columns: x:1!null y:2 d:6
      │    │    ├── fd: (1)-->(2)
      │    │    ├── scan xy
      │    │    │    ├── columns: x:1!null y:2
      │    │    │    ├── key: (1)
      │    │    │    └── fd: (1)-->(2)
      │    │    ├── scan cd
      │    │    │    └── columns: d:6!null
      │    │    └── filters
      │    │         └── d:6 = y:2 [outer=(2,6), constraints=(/2: (/NULL - ]; /6: (/NULL - ]), fd=(2)==(6), (6)==(2)]
      │    └── aggregations
      │         ├── const-agg [as=y:2, outer=(2)]
      │         │    └── y:2
      │         └── const-agg [as=d:6, outer=(6)]
      │              └── d:6
      └── filters
           └── d:6 = 5 [outer=(6), constraints=(/6: [/5 - /5]; tight), fd=()-->(6)]

# Rule is activated on LeftJoinApply.
# Case with multiple projected columns that will be translated to multiple
# const-agg functions.
norm format=show-miscprops expect=TryDecorrelateMax1Row
SELECT (SELECT d FROM cd WHERE d=k), i, f, s, j FROM a
----
project
 ├── columns: d:12 i:2 f:3 s:4 j:5
 ├── ensure-distinct-on
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5 cd.d:9
 │    ├── grouping columns: k:1!null
 │    ├── error: "more than one row returned by a subquery used as an expression"
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5,9)
 │    ├── left-join (hash)
 │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5 cd.d:9
 │    │    ├── multiplicity: left-rows(one-or-more), right-rows(zero-or-one)
 │    │    ├── fd: (1)-->(2-5)
 │    │    ├── scan a
 │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    │    │    ├── key: (1)
 │    │    │    └── fd: (1)-->(2-5)
 │    │    ├── scan cd
 │    │    │    └── columns: cd.d:9!null
 │    │    └── filters
 │    │         └── cd.d:9 = k:1 [outer=(1,9), constraints=(/1: (/NULL - ]; /9: (/NULL - ]), fd=(1)==(9), (9)==(1)]
 │    └── aggregations
 │         ├── const-agg [as=i:2, outer=(2)]
 │         │    └── i:2
 │         ├── const-agg [as=f:3, outer=(3)]
 │         │    └── f:3
 │         ├── const-agg [as=s:4, outer=(4)]
 │         │    └── s:4
 │         ├── const-agg [as=j:5, outer=(5)]
 │         │    └── j:5
 │         └── const-agg [as=cd.d:9, outer=(9)]
 │              └── cd.d:9
 └── projections
      └── cd.d:9 [as=d:12, outer=(9)]

# Rule is activated on LeftJoin.
# This test is a bit fragile because it depends on DecorrelateJoin activating
# before TryDecorrelateMax1Row.
# A LeftJoin is used because subquery cardinality might be zero.
norm format=show-miscprops expect=TryDecorrelateMax1Row
SELECT * FROM a LEFT JOIN LATERAL (SELECT * FROM uv WHERE (SELECT true FROM xy WHERE y=i)) ON true
----
project
 ├── columns: k:1!null i:2 f:3 s:4 j:5 u:8 v:9
 ├── key: (1,8)
 ├── fd: (1)-->(2-5), (1,8)-->(9)
 └── left-join-apply
      ├── columns: k:1!null i:2 f:3 s:4 j:5 u:8 v:9 bool:16
      ├── key: (1,8)
      ├── fd: (1)-->(2-5), (1,8)-->(9,16)
      ├── scan a
      │    ├── columns: k:1!null i:2 f:3 s:4 j:5
      │    ├── key: (1)
      │    └── fd: (1)-->(2-5)
      ├── ensure-distinct-on
      │    ├── columns: u:8!null v:9 bool:16
      │    ├── grouping columns: u:8!null
      │    ├── error: "more than one row returned by a subquery used as an expression"
      │    ├── outer: (2)
      │    ├── key: (8)
      │    ├── fd: (8)-->(9,16)
      │    ├── left-join (cross)
      │    │    ├── columns: u:8!null v:9 bool:16
      │    │    ├── outer: (2)
      │    │    ├── fd: (8)-->(9)
      │    │    ├── scan uv
      │    │    │    ├── columns: u:8!null v:9
      │    │    │    ├── key: (8)
      │    │    │    └── fd: (8)-->(9)
      │    │    ├── project
      │    │    │    ├── columns: bool:16!null
      │    │    │    ├── outer: (2)
      │    │    │    ├── fd: ()-->(16)
      │    │    │    ├── select
      │    │    │    │    ├── columns: y:13!null
      │    │    │    │    ├── outer: (2)
      │    │    │    │    ├── fd: ()-->(13)
      │    │    │    │    ├── scan xy
      │    │    │    │    │    └── columns: y:13
      │    │    │    │    └── filters
      │    │    │    │         └── y:13 = i:2 [outer=(2,13), constraints=(/2: (/NULL - ]; /13: (/NULL - ]), fd=(2)==(13), (13)==(2)]
      │    │    │    └── projections
      │    │    │         └── true [as=bool:16]
      │    │    └── filters (true)
      │    └── aggregations
      │         ├── const-agg [as=v:9, outer=(9)]
      │         │    └── v:9
      │         └── const-agg [as=bool:16, outer=(16)]
      │              └── bool:16
      └── filters
           └── bool:16 [outer=(16), constraints=(/16: [/true - /true]; tight), fd=()-->(16)]

# Rule is activated on InnerJoinApply.
# An InnerJoinApply is used because subquery cardinality is guaranteed to be
# greater than zero.
norm format=show-miscprops expect=TryDecorrelateMax1Row
SELECT (SELECT t=x FROM (VALUES (1), (2)) f(t)) FROM xy
----
project
 ├── columns: "?column?":7!null
 ├── ensure-distinct-on
 │    ├── columns: x:1!null "?column?":6!null
 │    ├── grouping columns: x:1!null
 │    ├── error: "more than one row returned by a subquery used as an expression"
 │    ├── key: (1)
 │    ├── fd: (1)-->(6)
 │    ├── project
 │    │    ├── columns: "?column?":6!null x:1!null
 │    │    ├── inner-join (cross)
 │    │    │    ├── columns: x:1!null column1:5!null
 │    │    │    ├── multiplicity: left-rows(one-or-more), right-rows(zero-or-more)
 │    │    │    ├── scan xy
 │    │    │    │    ├── columns: x:1!null
 │    │    │    │    └── key: (1)
 │    │    │    ├── values
 │    │    │    │    ├── columns: column1:5!null
 │    │    │    │    ├── cardinality: [2 - 2]
 │    │    │    │    ├── (1,)
 │    │    │    │    └── (2,)
 │    │    │    └── filters (true)
 │    │    └── projections
 │    │         └── column1:5 = x:1 [as="?column?":6, outer=(1,5)]
 │    └── aggregations
 │         └── const-agg [as="?column?":6, outer=(6)]
 │              └── "?column?":6
 └── projections
      └── "?column?":6 [as="?column?":7, outer=(6)]

# Rule is activated on InnerJoin.
# This test is a bit fragile because it depends on DecorrelateJoin activating
# before TryDecorrelateMax1Row.
norm format=show-miscprops expect=TryDecorrelateMax1Row
SELECT * FROM a LEFT JOIN LATERAL (SELECT * FROM uv WHERE (SELECT y=i FROM (VALUES (1), (2)) v(y))) ON true
----
project
 ├── columns: k:1!null i:2 f:3 s:4 j:5 u:8 v:9
 ├── key: (1,8)
 ├── fd: (1)-->(2-5), (1,8)-->(9)
 └── left-join-apply
      ├── columns: k:1!null i:2 f:3 s:4 j:5 u:8 v:9 "?column?":13
      ├── key: (1,8)
      ├── fd: (1)-->(2-5), (1,8)-->(9,13)
      ├── scan a
      │    ├── columns: k:1!null i:2 f:3 s:4 j:5
      │    ├── key: (1)
      │    └── fd: (1)-->(2-5)
      ├── ensure-distinct-on
      │    ├── columns: u:8!null v:9 "?column?":13
      │    ├── grouping columns: u:8!null
      │    ├── error: "more than one row returned by a subquery used as an expression"
      │    ├── outer: (2)
      │    ├── key: (8)
      │    ├── fd: (8)-->(9,13)
      │    ├── project
      │    │    ├── columns: "?column?":13 u:8!null v:9
      │    │    ├── outer: (2)
      │    │    ├── fd: (8)-->(9)
      │    │    ├── inner-join (cross)
      │    │    │    ├── columns: u:8!null v:9 column1:12!null
      │    │    │    ├── multiplicity: left-rows(one-or-more), right-rows(zero-or-more)
      │    │    │    ├── fd: (8)-->(9)
      │    │    │    ├── scan uv
      │    │    │    │    ├── columns: u:8!null v:9
      │    │    │    │    ├── key: (8)
      │    │    │    │    └── fd: (8)-->(9)
      │    │    │    ├── values
      │    │    │    │    ├── columns: column1:12!null
      │    │    │    │    ├── cardinality: [2 - 2]
      │    │    │    │    ├── (1,)
      │    │    │    │    └── (2,)
      │    │    │    └── filters (true)
      │    │    └── projections
      │    │         └── column1:12 = i:2 [as="?column?":13, outer=(2,12)]
      │    └── aggregations
      │         ├── const-agg [as=v:9, outer=(9)]
      │         │    └── v:9
      │         └── const-agg [as="?column?":13, outer=(13)]
      │              └── "?column?":13
      └── filters
           └── "?column?":13 [outer=(13), constraints=(/13: [/true - /true]; tight), fd=()-->(13)]

# Filter (y=3) must not be pushed through EnsureDistinctOn in order to ensure
# expected error behavior.
norm format=show-miscprops expect=TryDecorrelateMax1Row
SELECT a FROM (VALUES (0), (0)) v(a) WHERE (SELECT y FROM xy WHERE x<>a)=3
----
project
 ├── columns: a:1!null
 └── select
      ├── columns: column1:1!null y:3!null rownum:6!null
      ├── key: (6)
      ├── fd: ()-->(3), (6)-->(1)
      ├── ensure-distinct-on
      │    ├── columns: column1:1!null y:3 rownum:6!null
      │    ├── grouping columns: rownum:6!null
      │    ├── error: "more than one row returned by a subquery used as an expression"
      │    ├── cardinality: [1 - ]
      │    ├── key: (6)
      │    ├── fd: (6)-->(1,3)
      │    ├── left-join (cross)
      │    │    ├── columns: column1:1!null x:2 y:3 rownum:6!null
      │    │    ├── cardinality: [2 - ]
      │    │    ├── key: (2,6)
      │    │    ├── fd: (6)-->(1), (2)-->(3)
      │    │    ├── ordinality
      │    │    │    ├── columns: column1:1!null rownum:6!null
      │    │    │    ├── cardinality: [2 - 2]
      │    │    │    ├── key: (6)
      │    │    │    ├── fd: (6)-->(1)
      │    │    │    └── values
      │    │    │         ├── columns: column1:1!null
      │    │    │         ├── cardinality: [2 - 2]
      │    │    │         ├── (0,)
      │    │    │         └── (0,)
      │    │    ├── scan xy
      │    │    │    ├── columns: x:2!null y:3
      │    │    │    ├── key: (2)
      │    │    │    └── fd: (2)-->(3)
      │    │    └── filters
      │    │         └── x:2 != column1:1 [outer=(1,2), constraints=(/1: (/NULL - ]; /2: (/NULL - ])]
      │    └── aggregations
      │         ├── const-agg [as=column1:1, outer=(1)]
      │         │    └── column1:1
      │         └── const-agg [as=y:3, outer=(3)]
      │              └── y:3
      └── filters
           └── y:3 = 3 [outer=(3), constraints=(/3: [/3 - /3]; tight), fd=()-->(3)]

# --------------------------------------------------
# HoistSelectExists
# --------------------------------------------------
norm expect=HoistSelectExists
SELECT * FROM a WHERE EXISTS(SELECT * FROM xy WHERE x=k)
----
semi-join (hash)
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── scan xy
 │    ├── columns: x:8!null
 │    └── key: (8)
 └── filters
      └── x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]

# Ensure that EXISTS is hoisted even when it is one of several conjuncts.
norm expect=HoistSelectExists
SELECT * FROM a WHERE s='foo' AND EXISTS(SELECT * FROM xy WHERE x=k) AND i>1
----
semi-join (hash)
 ├── columns: k:1!null i:2!null f:3 s:4!null j:5
 ├── key: (1)
 ├── fd: ()-->(4), (1)-->(2,3,5)
 ├── select
 │    ├── columns: k:1!null i:2!null f:3 s:4!null j:5
 │    ├── key: (1)
 │    ├── fd: ()-->(4), (1)-->(2,3,5)
 │    ├── scan a
 │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2-5)
 │    └── filters
 │         ├── s:4 = 'foo' [outer=(4), constraints=(/4: [/'foo' - /'foo']; tight), fd=()-->(4)]
 │         └── i:2 > 1 [outer=(2), constraints=(/2: [/2 - ]; tight)]
 ├── scan xy
 │    ├── columns: x:8!null
 │    └── key: (8)
 └── filters
      └── x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]

# Multiple Exists operators in same Select list.
norm expect=HoistSelectExists
SELECT * FROM a WHERE EXISTS(SELECT * FROM xy WHERE x=k) AND EXISTS(SELECT * FROM xy WHERE x=i)
----
semi-join (hash)
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── semi-join (hash)
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    ├── scan a
 │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2-5)
 │    ├── scan xy
 │    │    ├── columns: x:12!null
 │    │    └── key: (12)
 │    └── filters
 │         └── x:12 = i:2 [outer=(2,12), constraints=(/2: (/NULL - ]; /12: (/NULL - ]), fd=(2)==(12), (12)==(2)]
 ├── scan xy
 │    ├── columns: x:8!null
 │    └── key: (8)
 └── filters
      └── x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]

# Don't hoist uncorrelated subqueries.
norm expect-not=HoistSelectExists
SELECT * FROM a WHERE EXISTS(SELECT * FROM xy)
----
select
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 └── filters
      └── coalesce [subquery]
           ├── subquery
           │    └── project
           │         ├── columns: column13:13!null
           │         ├── cardinality: [0 - 1]
           │         ├── key: ()
           │         ├── fd: ()-->(13)
           │         ├── limit
           │         │    ├── cardinality: [0 - 1]
           │         │    ├── key: ()
           │         │    ├── scan xy
           │         │    │    └── limit hint: 1.00
           │         │    └── 1
           │         └── projections
           │              └── true [as=column13:13]
           └── false

# Hoist nested EXISTS.
norm expect=HoistSelectExists
SELECT * FROM a WHERE EXISTS(SELECT * FROM xy WHERE EXISTS (SELECT * FROM uv WHERE x=u) AND x=k)
----
semi-join (hash)
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── semi-join (hash)
 │    ├── columns: x:8!null
 │    ├── key: (8)
 │    ├── scan xy
 │    │    ├── columns: x:8!null
 │    │    └── key: (8)
 │    ├── scan uv
 │    │    ├── columns: u:12!null
 │    │    └── key: (12)
 │    └── filters
 │         └── x:8 = u:12 [outer=(8,12), constraints=(/8: (/NULL - ]; /12: (/NULL - ]), fd=(8)==(12), (12)==(8)]
 └── filters
      └── x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]

# --------------------------------------------------
# HoistSelectNotExists
# --------------------------------------------------
norm expect=HoistSelectNotExists
SELECT * FROM a WHERE NOT EXISTS(SELECT * FROM xy WHERE x=k)
----
anti-join (hash)
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── scan xy
 │    ├── columns: x:8!null
 │    └── key: (8)
 └── filters
      └── x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]

# Ensure that NOT EXISTS is hoisted even when one of several conjuncts.
norm expect=HoistSelectNotExists
SELECT * FROM a WHERE s='foo' AND NOT EXISTS(SELECT * FROM xy WHERE x=k) AND i>1
----
anti-join (hash)
 ├── columns: k:1!null i:2!null f:3 s:4!null j:5
 ├── key: (1)
 ├── fd: ()-->(4), (1)-->(2,3,5)
 ├── select
 │    ├── columns: k:1!null i:2!null f:3 s:4!null j:5
 │    ├── key: (1)
 │    ├── fd: ()-->(4), (1)-->(2,3,5)
 │    ├── scan a
 │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2-5)
 │    └── filters
 │         ├── s:4 = 'foo' [outer=(4), constraints=(/4: [/'foo' - /'foo']; tight), fd=()-->(4)]
 │         └── i:2 > 1 [outer=(2), constraints=(/2: [/2 - ]; tight)]
 ├── scan xy
 │    ├── columns: x:8!null
 │    └── key: (8)
 └── filters
      └── x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]

# Multiple Not Exists operators in same Select list.
norm expect=HoistSelectNotExists
SELECT *
FROM a
WHERE NOT EXISTS(SELECT * FROM xy WHERE x=k) AND NOT EXISTS(SELECT * FROM xy WHERE x=i)
----
anti-join (hash)
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── anti-join (hash)
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    ├── scan a
 │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2-5)
 │    ├── scan xy
 │    │    ├── columns: x:12!null
 │    │    └── key: (12)
 │    └── filters
 │         └── x:12 = i:2 [outer=(2,12), constraints=(/2: (/NULL - ]; /12: (/NULL - ]), fd=(2)==(12), (12)==(2)]
 ├── scan xy
 │    ├── columns: x:8!null
 │    └── key: (8)
 └── filters
      └── x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]

# Don't hoist uncorrelated subqueries.
norm expect-not=HoistSelectNotExists
SELECT * FROM a WHERE NOT EXISTS(SELECT * FROM xy)
----
select
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 └── filters
      └── not [subquery]
           └── coalesce
                ├── subquery
                │    └── project
                │         ├── columns: column13:13!null
                │         ├── cardinality: [0 - 1]
                │         ├── key: ()
                │         ├── fd: ()-->(13)
                │         ├── limit
                │         │    ├── cardinality: [0 - 1]
                │         │    ├── key: ()
                │         │    ├── scan xy
                │         │    │    └── limit hint: 1.00
                │         │    └── 1
                │         └── projections
                │              └── true [as=column13:13]
                └── false

# --------------------------------------------------
# HoistSelectExists + HoistSelectNotExists
# --------------------------------------------------
norm expect=(HoistSelectExists,HoistSelectNotExists)
SELECT * FROM a WHERE EXISTS(SELECT * FROM xy WHERE x=k) AND NOT EXISTS(SELECT * FROM xy WHERE x=i)
----
semi-join (hash)
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── anti-join (hash)
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    ├── scan a
 │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2-5)
 │    ├── scan xy
 │    │    ├── columns: x:12!null
 │    │    └── key: (12)
 │    └── filters
 │         └── x:12 = i:2 [outer=(2,12), constraints=(/2: (/NULL - ]; /12: (/NULL - ]), fd=(2)==(12), (12)==(2)]
 ├── scan xy
 │    ├── columns: x:8!null
 │    └── key: (8)
 └── filters
      └── x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]

# --------------------------------------------------
# HoistSelectSubquery
# --------------------------------------------------
norm expect=HoistSelectSubquery
SELECT * FROM a WHERE (SELECT y FROM xy WHERE y=k LIMIT 1) = i
----
project
 ├── columns: k:1!null i:2!null f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 └── select
      ├── columns: k:1!null i:2!null f:3 s:4 j:5 y:9!null
      ├── key: (1)
      ├── fd: (1)-->(2-5,9), (2)==(9), (9)==(2)
      ├── distinct-on
      │    ├── columns: k:1!null i:2 f:3 s:4 j:5 y:9
      │    ├── grouping columns: k:1!null
      │    ├── key: (1)
      │    ├── fd: (1)-->(2-5,9)
      │    ├── left-join (hash)
      │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5 y:9
      │    │    ├── multiplicity: left-rows(one-or-more), right-rows(zero-or-one)
      │    │    ├── fd: (1)-->(2-5)
      │    │    ├── scan a
      │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
      │    │    │    ├── key: (1)
      │    │    │    └── fd: (1)-->(2-5)
      │    │    ├── scan xy
      │    │    │    └── columns: y:9
      │    │    └── filters
      │    │         └── y:9 = k:1 [outer=(1,9), constraints=(/1: (/NULL - ]; /9: (/NULL - ]), fd=(1)==(9), (9)==(1)]
      │    └── aggregations
      │         ├── const-agg [as=i:2, outer=(2)]
      │         │    └── i:2
      │         ├── const-agg [as=f:3, outer=(3)]
      │         │    └── f:3
      │         ├── const-agg [as=s:4, outer=(4)]
      │         │    └── s:4
      │         ├── const-agg [as=j:5, outer=(5)]
      │         │    └── j:5
      │         └── first-agg [as=y:9, outer=(9)]
      │              └── y:9
      └── filters
           └── i:2 = y:9 [outer=(2,9), constraints=(/2: (/NULL - ]; /9: (/NULL - ]), fd=(2)==(9), (9)==(2)]

# Multiple other conjuncts, including uncorrelated subquery (don't hoist).
norm expect=HoistSelectSubquery disable=InlineConstVar
SELECT *
FROM a
WHERE k=10 AND (SELECT y FROM xy WHERE y=k LIMIT 1) = i AND (SELECT x FROM xy LIMIT 1) = 100
----
project
 ├── columns: k:1!null i:2!null f:3 s:4 j:5
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(1-5)
 └── select
      ├── columns: k:1!null i:2!null f:3 s:4 j:5 y:9!null
      ├── cardinality: [0 - 1]
      ├── key: ()
      ├── fd: ()-->(1-5,9), (2)==(9), (9)==(2)
      ├── limit
      │    ├── columns: k:1!null i:2 f:3 s:4 j:5 y:9
      │    ├── cardinality: [0 - 1]
      │    ├── key: ()
      │    ├── fd: ()-->(1-5,9)
      │    ├── left-join (hash)
      │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5 y:9
      │    │    ├── multiplicity: left-rows(one-or-more), right-rows(zero-or-one)
      │    │    ├── fd: ()-->(1-5,9)
      │    │    ├── limit hint: 1.00
      │    │    ├── select
      │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
      │    │    │    ├── cardinality: [0 - 1]
      │    │    │    ├── key: ()
      │    │    │    ├── fd: ()-->(1-5)
      │    │    │    ├── scan a
      │    │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
      │    │    │    │    ├── key: (1)
      │    │    │    │    └── fd: (1)-->(2-5)
      │    │    │    └── filters
      │    │    │         ├── k:1 = 10 [outer=(1), constraints=(/1: [/10 - /10]; tight), fd=()-->(1)]
      │    │    │         └── eq [subquery]
      │    │    │              ├── subquery
      │    │    │              │    └── limit
      │    │    │              │         ├── columns: x:12!null
      │    │    │              │         ├── cardinality: [0 - 1]
      │    │    │              │         ├── key: ()
      │    │    │              │         ├── fd: ()-->(12)
      │    │    │              │         ├── scan xy
      │    │    │              │         │    ├── columns: x:12!null
      │    │    │              │         │    ├── key: (12)
      │    │    │              │         │    └── limit hint: 1.00
      │    │    │              │         └── 1
      │    │    │              └── 100
      │    │    ├── select
      │    │    │    ├── columns: y:9!null
      │    │    │    ├── fd: ()-->(9)
      │    │    │    ├── scan xy
      │    │    │    │    └── columns: y:9
      │    │    │    └── filters
      │    │    │         ├── y:9 = 10 [outer=(9), constraints=(/9: [/10 - /10]; tight), fd=()-->(9)]
      │    │    │         └── eq [subquery]
      │    │    │              ├── subquery
      │    │    │              │    └── limit
      │    │    │              │         ├── columns: x:12!null
      │    │    │              │         ├── cardinality: [0 - 1]
      │    │    │              │         ├── key: ()
      │    │    │              │         ├── fd: ()-->(12)
      │    │    │              │         ├── scan xy
      │    │    │              │         │    ├── columns: x:12!null
      │    │    │              │         │    ├── key: (12)
      │    │    │              │         │    └── limit hint: 1.00
      │    │    │              │         └── 1
      │    │    │              └── 100
      │    │    └── filters
      │    │         └── y:9 = k:1 [outer=(1,9), constraints=(/1: (/NULL - ]; /9: (/NULL - ]), fd=(1)==(9), (9)==(1)]
      │    └── 1
      └── filters
           └── i:2 = y:9 [outer=(2,9), constraints=(/2: (/NULL - ]; /9: (/NULL - ]), fd=(2)==(9), (9)==(2)]

# Multiple correlated subqueries.
norm expect=HoistSelectSubquery
SELECT * FROM a
WHERE (SELECT count(*) FROM xy WHERE y=k) > 0 AND (SELECT y FROM xy WHERE y=k LIMIT 1) = i
----
project
 ├── columns: k:1!null i:2!null f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 └── select
      ├── columns: k:1!null i:2!null f:3 s:4 j:5 y:14!null
      ├── key: (1)
      ├── fd: (1)-->(2-5,14), (2)==(14), (14)==(2)
      ├── distinct-on
      │    ├── columns: k:1!null i:2 f:3 s:4 j:5 y:14
      │    ├── grouping columns: k:1!null
      │    ├── key: (1)
      │    ├── fd: (1)-->(2-5,14)
      │    ├── left-join (hash)
      │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5 count_rows:12!null y:14
      │    │    ├── multiplicity: left-rows(one-or-more), right-rows(zero-or-one)
      │    │    ├── fd: (1)-->(2-5,12)
      │    │    ├── select
      │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5 count_rows:12!null
      │    │    │    ├── key: (1)
      │    │    │    ├── fd: (1)-->(2-5,12)
      │    │    │    ├── group-by (hash)
      │    │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5 count_rows:12!null
      │    │    │    │    ├── grouping columns: k:1!null
      │    │    │    │    ├── key: (1)
      │    │    │    │    ├── fd: (1)-->(2-5,12)
      │    │    │    │    ├── left-join (hash)
      │    │    │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5 y:9
      │    │    │    │    │    ├── multiplicity: left-rows(one-or-more), right-rows(zero-or-one)
      │    │    │    │    │    ├── fd: (1)-->(2-5)
      │    │    │    │    │    ├── scan a
      │    │    │    │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
      │    │    │    │    │    │    ├── key: (1)
      │    │    │    │    │    │    └── fd: (1)-->(2-5)
      │    │    │    │    │    ├── scan xy
      │    │    │    │    │    │    └── columns: y:9
      │    │    │    │    │    └── filters
      │    │    │    │    │         └── y:9 = k:1 [outer=(1,9), constraints=(/1: (/NULL - ]; /9: (/NULL - ]), fd=(1)==(9), (9)==(1)]
      │    │    │    │    └── aggregations
      │    │    │    │         ├── count [as=count_rows:12, outer=(9)]
      │    │    │    │         │    └── y:9
      │    │    │    │         ├── const-agg [as=i:2, outer=(2)]
      │    │    │    │         │    └── i:2
      │    │    │    │         ├── const-agg [as=f:3, outer=(3)]
      │    │    │    │         │    └── f:3
      │    │    │    │         ├── const-agg [as=s:4, outer=(4)]
      │    │    │    │         │    └── s:4
      │    │    │    │         └── const-agg [as=j:5, outer=(5)]
      │    │    │    │              └── j:5
      │    │    │    └── filters
      │    │    │         └── count_rows:12 > 0 [outer=(12), constraints=(/12: [/1 - ]; tight)]
      │    │    ├── scan xy
      │    │    │    └── columns: y:14
      │    │    └── filters
      │    │         └── y:14 = k:1 [outer=(1,14), constraints=(/1: (/NULL - ]; /14: (/NULL - ]), fd=(1)==(14), (14)==(1)]
      │    └── aggregations
      │         ├── const-agg [as=i:2, outer=(2)]
      │         │    └── i:2
      │         ├── const-agg [as=f:3, outer=(3)]
      │         │    └── f:3
      │         ├── const-agg [as=s:4, outer=(4)]
      │         │    └── s:4
      │         ├── const-agg [as=j:5, outer=(5)]
      │         │    └── j:5
      │         └── first-agg [as=y:14, outer=(14)]
      │              └── y:14
      └── filters
           └── i:2 = y:14 [outer=(2,14), constraints=(/2: (/NULL - ]; /14: (/NULL - ]), fd=(2)==(14), (14)==(2)]

# Subquery nested below interesting scalar operators like cast, function, tuple,
# or, etc).
norm expect=HoistSelectSubquery
SELECT * FROM a WHERE (0, length((SELECT count(*) FROM uv WHERE k=u)::string)) > (0, 1) OR i=1
----
project
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 └── select
      ├── columns: k:1!null i:2 f:3 s:4 j:5 count_rows:12!null
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(2-5,12)
      ├── group-by (hash)
      │    ├── columns: k:1!null i:2 f:3 s:4 j:5 count_rows:12!null
      │    ├── grouping columns: k:1!null
      │    ├── key: (1)
      │    ├── fd: (1)-->(2-5,12)
      │    ├── left-join (hash)
      │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5 u:8
      │    │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
      │    │    ├── key: (1)
      │    │    ├── fd: (1)-->(2-5,8)
      │    │    ├── scan a
      │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
      │    │    │    ├── key: (1)
      │    │    │    └── fd: (1)-->(2-5)
      │    │    ├── scan uv
      │    │    │    ├── columns: u:8!null
      │    │    │    └── key: (8)
      │    │    └── filters
      │    │         └── k:1 = u:8 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]
      │    └── aggregations
      │         ├── count [as=count_rows:12, outer=(8)]
      │         │    └── u:8
      │         ├── const-agg [as=i:2, outer=(2)]
      │         │    └── i:2
      │         ├── const-agg [as=f:3, outer=(3)]
      │         │    └── f:3
      │         ├── const-agg [as=s:4, outer=(4)]
      │         │    └── s:4
      │         └── const-agg [as=j:5, outer=(5)]
      │              └── j:5
      └── filters
           └── ((0, length(count_rows:12::STRING)) > (0, 1)) OR (i:2 = 1) [outer=(2,12), immutable]

# CREATE TABLE a (k INT PRIMARY KEY, i INT, f FLOAT, s STRING, j JSON)
# CREATE TABLE xy (x INT PRIMARY KEY, y INT)

# Hoist a leakproof subquery within a COALESCE.
norm expect=HoistSelectSubquery
SELECT * FROM a WHERE k = COALESCE(i, (SELECT y FROM xy WHERE x = i))
----
project
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 └── select
      ├── columns: k:1!null i:2 f:3 s:4 j:5 x:8 y:9
      ├── key: (1)
      ├── fd: (1)-->(2-5,8,9), (8)-->(9), (2,9)-->(1)
      ├── left-join (hash)
      │    ├── columns: k:1!null i:2 f:3 s:4 j:5 x:8 y:9
      │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more)
      │    ├── key: (1)
      │    ├── fd: (1)-->(2-5,8,9), (8)-->(9)
      │    ├── scan a
      │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2-5)
      │    ├── scan xy
      │    │    ├── columns: x:8!null y:9
      │    │    ├── key: (8)
      │    │    └── fd: (8)-->(9)
      │    └── filters
      │         └── x:8 = i:2 [outer=(2,8), constraints=(/2: (/NULL - ]; /8: (/NULL - ]), fd=(2)==(8), (8)==(2)]
      └── filters
           └── k:1 = COALESCE(i:2, y:9) [outer=(1,2,9), constraints=(/1: (/NULL - ]), fd=(2,9)-->(1)]

# Hoist a non-leakproof subquery within a COALESCE if it is the first argument.
norm expect=HoistSelectSubquery
SELECT * FROM a WHERE k = COALESCE((SELECT (1/y)::INT FROM xy WHERE x = i), i)
----
project
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 └── select
      ├── columns: k:1!null i:2 f:3 s:4 j:5 x:8 int8:12
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(2-5,8,12), (8)-->(12), (2,12)-->(1)
      ├── left-join (hash)
      │    ├── columns: k:1!null i:2 f:3 s:4 j:5 x:8 int8:12
      │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more)
      │    ├── immutable
      │    ├── key: (1)
      │    ├── fd: (1)-->(2-5,8,12), (8)-->(12)
      │    ├── scan a
      │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2-5)
      │    ├── project
      │    │    ├── columns: int8:12 x:8!null
      │    │    ├── immutable
      │    │    ├── key: (8)
      │    │    ├── fd: (8)-->(12)
      │    │    ├── scan xy
      │    │    │    ├── columns: x:8!null y:9
      │    │    │    ├── key: (8)
      │    │    │    └── fd: (8)-->(9)
      │    │    └── projections
      │    │         └── (1 / y:9)::INT8 [as=int8:12, outer=(9), immutable]
      │    └── filters
      │         └── x:8 = i:2 [outer=(2,8), constraints=(/2: (/NULL - ]; /8: (/NULL - ]), fd=(2)==(8), (8)==(2)]
      └── filters
           └── k:1 = COALESCE(int8:12, i:2) [outer=(1,2,12), constraints=(/1: (/NULL - ]), fd=(2,12)-->(1)]

# Do not hoist a non-leakproof subquery within a COALESCE if it is not the first
# argument.
norm expect-not=HoistSelectSubquery
SELECT * FROM a WHERE k = COALESCE(i, (SELECT (1/y)::INT FROM xy WHERE x = i))
----
select
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 └── filters
      └── eq [outer=(1,2), immutable, correlated-subquery, constraints=(/1: (/NULL - ])]
           ├── k:1
           └── coalesce
                ├── i:2
                └── subquery
                     └── project
                          ├── columns: int8:12
                          ├── outer: (2)
                          ├── cardinality: [0 - 1]
                          ├── immutable
                          ├── key: ()
                          ├── fd: ()-->(12)
                          ├── select
                          │    ├── columns: x:8!null y:9
                          │    ├── outer: (2)
                          │    ├── cardinality: [0 - 1]
                          │    ├── key: ()
                          │    ├── fd: ()-->(8,9)
                          │    ├── scan xy
                          │    │    ├── columns: x:8!null y:9
                          │    │    ├── key: (8)
                          │    │    └── fd: (8)-->(9)
                          │    └── filters
                          │         └── x:8 = i:2 [outer=(2,8), constraints=(/2: (/NULL - ]; /8: (/NULL - ]), fd=(2)==(8), (8)==(2)]
                          └── projections
                               └── (1 / y:9)::INT8 [as=int8:12, outer=(9), immutable]

# Exists within a disjunction.
norm expect=HoistSelectSubquery
SELECT * FROM a WHERE i=1 OR EXISTS(SELECT * FROM xy WHERE y=i)
----
project
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 └── select
      ├── columns: k:1!null i:2 f:3 s:4 j:5 canary_agg:13
      ├── key: (1)
      ├── fd: (1)-->(2-5,13)
      ├── group-by (hash)
      │    ├── columns: k:1!null i:2 f:3 s:4 j:5 canary_agg:13
      │    ├── grouping columns: k:1!null
      │    ├── key: (1)
      │    ├── fd: (1)-->(2-5,13)
      │    ├── left-join (hash)
      │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5 x:8 y:9
      │    │    ├── key: (1,8)
      │    │    ├── fd: (1)-->(2-5), (8)-->(9)
      │    │    ├── scan a
      │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
      │    │    │    ├── key: (1)
      │    │    │    └── fd: (1)-->(2-5)
      │    │    ├── scan xy
      │    │    │    ├── columns: x:8!null y:9
      │    │    │    ├── key: (8)
      │    │    │    └── fd: (8)-->(9)
      │    │    └── filters
      │    │         └── y:9 = i:2 [outer=(2,9), constraints=(/2: (/NULL - ]; /9: (/NULL - ]), fd=(2)==(9), (9)==(2)]
      │    └── aggregations
      │         ├── const-not-null-agg [as=canary_agg:13, outer=(8)]
      │         │    └── x:8
      │         ├── const-agg [as=i:2, outer=(2)]
      │         │    └── i:2
      │         ├── const-agg [as=f:3, outer=(3)]
      │         │    └── f:3
      │         ├── const-agg [as=s:4, outer=(4)]
      │         │    └── s:4
      │         └── const-agg [as=j:5, outer=(5)]
      │              └── j:5
      └── filters
           └── (i:2 = 1) OR (canary_agg:13 IS NOT NULL) [outer=(2,13)]

# Any with IS NULL.
norm expect=HoistSelectSubquery
SELECT * FROM a WHERE (i = ANY(SELECT y FROM xy WHERE x=k)) IS NULL
----
project
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 └── select
      ├── columns: k:1!null i:2 f:3 s:4 j:5 case:14
      ├── key: (1)
      ├── fd: ()-->(14), (1)-->(2-5)
      ├── project
      │    ├── columns: case:14 k:1!null i:2 f:3 s:4 j:5
      │    ├── key: (1)
      │    ├── fd: (1)-->(2-5,14)
      │    ├── group-by (hash)
      │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5 bool_or:13
      │    │    ├── grouping columns: k:1!null
      │    │    ├── key: (1)
      │    │    ├── fd: (1)-->(2-5,13)
      │    │    ├── left-join (hash)
      │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5 x:8 y:9 notnull:12
      │    │    │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
      │    │    │    ├── key: (1)
      │    │    │    ├── fd: (1)-->(2-5,8,9,12), (8)-->(9), (9)~~>(12)
      │    │    │    ├── scan a
      │    │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
      │    │    │    │    ├── key: (1)
      │    │    │    │    └── fd: (1)-->(2-5)
      │    │    │    ├── project
      │    │    │    │    ├── columns: notnull:12!null x:8!null y:9
      │    │    │    │    ├── key: (8)
      │    │    │    │    ├── fd: (8)-->(9), (9)-->(12)
      │    │    │    │    ├── scan xy
      │    │    │    │    │    ├── columns: x:8!null y:9
      │    │    │    │    │    ├── key: (8)
      │    │    │    │    │    └── fd: (8)-->(9)
      │    │    │    │    └── projections
      │    │    │    │         └── y:9 IS NOT NULL [as=notnull:12, outer=(9)]
      │    │    │    └── filters
      │    │    │         ├── x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]
      │    │    │         └── (i:2 = y:9) IS NOT false [outer=(2,9)]
      │    │    └── aggregations
      │    │         ├── bool-or [as=bool_or:13, outer=(12)]
      │    │         │    └── notnull:12
      │    │         ├── const-agg [as=i:2, outer=(2)]
      │    │         │    └── i:2
      │    │         ├── const-agg [as=f:3, outer=(3)]
      │    │         │    └── f:3
      │    │         ├── const-agg [as=s:4, outer=(4)]
      │    │         │    └── s:4
      │    │         └── const-agg [as=j:5, outer=(5)]
      │    │              └── j:5
      │    └── projections
      │         └── CASE WHEN bool_or:13 AND (i:2 IS NOT NULL) THEN true WHEN bool_or:13 IS NULL THEN false ELSE CAST(NULL AS BOOL) END [as=case:14, outer=(2,13)]
      └── filters
           └── case:14 IS NULL [outer=(14), constraints=(/14: [/NULL - /NULL]; tight), fd=()-->(14)]

# Any with tuple comparison should use IS NOT NULL (i.e., IsTupleNotNull
# expression) instead of IS DISTINCT FROM NULL.
norm expect=HoistSelectSubquery
SELECT * FROM a WHERE ((k, i) = ANY(SELECT (x, y) FROM xy WHERE x=k)) IS NULL
----
project
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 └── select
      ├── columns: k:1!null i:2 f:3 s:4 j:5 case:16
      ├── immutable
      ├── key: (1)
      ├── fd: ()-->(16), (1)-->(2-5)
      ├── project
      │    ├── columns: case:16 k:1!null i:2 f:3 s:4 j:5
      │    ├── immutable
      │    ├── key: (1)
      │    ├── fd: (1)-->(2-5,16)
      │    ├── group-by (hash)
      │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5 scalar:13 bool_or:15
      │    │    ├── grouping columns: k:1!null
      │    │    ├── immutable
      │    │    ├── key: (1)
      │    │    ├── fd: (1)-->(2-5,13,15)
      │    │    ├── left-join-apply
      │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5 "?column?":12 scalar:13 notnull:14
      │    │    │    ├── immutable
      │    │    │    ├── key: (1)
      │    │    │    ├── fd: (1)-->(2-5,12-14)
      │    │    │    ├── project
      │    │    │    │    ├── columns: scalar:13 k:1!null i:2 f:3 s:4 j:5
      │    │    │    │    ├── key: (1)
      │    │    │    │    ├── fd: (1)-->(2-5,13)
      │    │    │    │    ├── scan a
      │    │    │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
      │    │    │    │    │    ├── key: (1)
      │    │    │    │    │    └── fd: (1)-->(2-5)
      │    │    │    │    └── projections
      │    │    │    │         └── (k:1, i:2) [as=scalar:13, outer=(1,2)]
      │    │    │    ├── project
      │    │    │    │    ├── columns: notnull:14!null "?column?":12
      │    │    │    │    ├── outer: (1)
      │    │    │    │    ├── cardinality: [0 - 1]
      │    │    │    │    ├── key: ()
      │    │    │    │    ├── fd: ()-->(12,14)
      │    │    │    │    ├── project
      │    │    │    │    │    ├── columns: "?column?":12
      │    │    │    │    │    ├── outer: (1)
      │    │    │    │    │    ├── cardinality: [0 - 1]
      │    │    │    │    │    ├── key: ()
      │    │    │    │    │    ├── fd: ()-->(12)
      │    │    │    │    │    ├── select
      │    │    │    │    │    │    ├── columns: x:8!null y:9
      │    │    │    │    │    │    ├── outer: (1)
      │    │    │    │    │    │    ├── cardinality: [0 - 1]
      │    │    │    │    │    │    ├── key: ()
      │    │    │    │    │    │    ├── fd: ()-->(8,9)
      │    │    │    │    │    │    ├── scan xy
      │    │    │    │    │    │    │    ├── columns: x:8!null y:9
      │    │    │    │    │    │    │    ├── key: (8)
      │    │    │    │    │    │    │    └── fd: (8)-->(9)
      │    │    │    │    │    │    └── filters
      │    │    │    │    │    │         └── x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]
      │    │    │    │    │    └── projections
      │    │    │    │    │         └── (x:8, y:9) [as="?column?":12, outer=(8,9)]
      │    │    │    │    └── projections
      │    │    │    │         └── "?column?":12 IS NOT NULL [as=notnull:14, outer=(12)]
      │    │    │    └── filters
      │    │    │         └── (scalar:13 = "?column?":12) IS NOT false [outer=(12,13), immutable]
      │    │    └── aggregations
      │    │         ├── bool-or [as=bool_or:15, outer=(14)]
      │    │         │    └── notnull:14
      │    │         ├── const-agg [as=i:2, outer=(2)]
      │    │         │    └── i:2
      │    │         ├── const-agg [as=f:3, outer=(3)]
      │    │         │    └── f:3
      │    │         ├── const-agg [as=s:4, outer=(4)]
      │    │         │    └── s:4
      │    │         ├── const-agg [as=j:5, outer=(5)]
      │    │         │    └── j:5
      │    │         └── const-agg [as=scalar:13, outer=(13)]
      │    │              └── scalar:13
      │    └── projections
      │         └── CASE WHEN bool_or:15 AND (scalar:13 IS NOT NULL) THEN true WHEN bool_or:15 IS NULL THEN false ELSE CAST(NULL AS BOOL) END [as=case:16, outer=(13,15)]
      └── filters
           └── case:16 IS NULL [outer=(16), constraints=(/16: [/NULL - /NULL]; tight), fd=()-->(16)]

# Any with uncorrelated subquery (should not be hoisted).
norm
SELECT * FROM a WHERE (i = ANY(SELECT y FROM xy)) IS NULL
----
select
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 └── filters
      └── is [outer=(2), correlated-subquery]
           ├── any: eq
           │    ├── scan xy
           │    │    └── columns: y:9
           │    └── i:2
           └── NULL

# ALL with non-trivial expression on left.
norm
SELECT i*i/100 < ALL(SELECT y FROM xy WHERE x=k) AS r, s FROM a
----
project
 ├── columns: r:12 s:4
 ├── immutable
 ├── group-by (hash)
 │    ├── columns: k:1!null s:4 scalar:13 bool_or:15
 │    ├── grouping columns: k:1!null
 │    ├── immutable
 │    ├── key: (1)
 │    ├── fd: (1)-->(4,13,15)
 │    ├── left-join (hash)
 │    │    ├── columns: k:1!null s:4 x:8 y:9 scalar:13 notnull:14
 │    │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
 │    │    ├── immutable
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(4,8,9,13,14), (8)-->(9), (9)~~>(14)
 │    │    ├── project
 │    │    │    ├── columns: scalar:13 k:1!null s:4
 │    │    │    ├── immutable
 │    │    │    ├── key: (1)
 │    │    │    ├── fd: (1)-->(4,13)
 │    │    │    ├── scan a
 │    │    │    │    ├── columns: k:1!null i:2 s:4
 │    │    │    │    ├── key: (1)
 │    │    │    │    └── fd: (1)-->(2,4)
 │    │    │    └── projections
 │    │    │         └── (i:2 * i:2) / 100 [as=scalar:13, outer=(2), immutable]
 │    │    ├── project
 │    │    │    ├── columns: notnull:14!null x:8!null y:9
 │    │    │    ├── key: (8)
 │    │    │    ├── fd: (8)-->(9), (9)-->(14)
 │    │    │    ├── scan xy
 │    │    │    │    ├── columns: x:8!null y:9
 │    │    │    │    ├── key: (8)
 │    │    │    │    └── fd: (8)-->(9)
 │    │    │    └── projections
 │    │    │         └── y:9 IS NOT NULL [as=notnull:14, outer=(9)]
 │    │    └── filters
 │    │         ├── x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]
 │    │         └── (scalar:13 >= y:9) IS NOT false [outer=(9,13)]
 │    └── aggregations
 │         ├── bool-or [as=bool_or:15, outer=(14)]
 │         │    └── notnull:14
 │         ├── const-agg [as=s:4, outer=(4)]
 │         │    └── s:4
 │         └── const-agg [as=scalar:13, outer=(13)]
 │              └── scalar:13
 └── projections
      └── NOT CASE WHEN bool_or:15 AND (scalar:13 IS NOT NULL) THEN true WHEN bool_or:15 IS NULL THEN false ELSE CAST(NULL AS BOOL) END [as=r:12, outer=(13,15), immutable]

# Regress issue #32270: Panic when expression contains both correlated and
# uncorrelated subquery.
norm expect=HoistSelectSubquery
SELECT * FROM a WHERE EXISTS(SELECT * FROM xy) OR EXISTS(SELECT * FROM xy WHERE x=k)
----
project
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 └── select
      ├── columns: k:1!null i:2 f:3 s:4 j:5 x:12 column17:17
      ├── key: (1)
      ├── fd: (1)-->(2-5,12,17)
      ├── left-join (hash)
      │    ├── columns: k:1!null i:2 f:3 s:4 j:5 x:12 column17:17
      │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
      │    ├── key: (1)
      │    ├── fd: (1)-->(2-5,12,17)
      │    ├── left-join (cross)
      │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5 column17:17
      │    │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more)
      │    │    ├── key: (1)
      │    │    ├── fd: (1)-->(2-5,17)
      │    │    ├── scan a
      │    │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
      │    │    │    ├── key: (1)
      │    │    │    └── fd: (1)-->(2-5)
      │    │    ├── project
      │    │    │    ├── columns: column17:17!null
      │    │    │    ├── cardinality: [0 - 1]
      │    │    │    ├── key: ()
      │    │    │    ├── fd: ()-->(17)
      │    │    │    ├── limit
      │    │    │    │    ├── cardinality: [0 - 1]
      │    │    │    │    ├── key: ()
      │    │    │    │    ├── scan xy
      │    │    │    │    │    └── limit hint: 1.00
      │    │    │    │    └── 1
      │    │    │    └── projections
      │    │    │         └── true [as=column17:17]
      │    │    └── filters (true)
      │    ├── scan xy
      │    │    ├── columns: x:12!null
      │    │    └── key: (12)
      │    └── filters
      │         └── x:12 = k:1 [outer=(1,12), constraints=(/1: (/NULL - ]; /12: (/NULL - ]), fd=(1)==(12), (12)==(1)]
      └── filters
           └── COALESCE(column17:17, false) OR (x:12 IS NOT NULL) [outer=(12,17)]

# Hoist an uncorrelated equality subquery.
norm expect=HoistSelectSubquery
SELECT * FROM a WHERE k = (SELECT max(x) FROM xy)
----
project
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(1-5)
 └── inner-join (hash)
      ├── columns: k:1!null i:2 f:3 s:4 j:5 max:12!null
      ├── cardinality: [0 - 1]
      ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one)
      ├── key: ()
      ├── fd: ()-->(1-5,12), (1)==(12), (12)==(1)
      ├── scan a
      │    ├── columns: k:1!null i:2 f:3 s:4 j:5
      │    ├── key: (1)
      │    └── fd: (1)-->(2-5)
      ├── scalar-group-by
      │    ├── columns: max:12
      │    ├── cardinality: [1 - 1]
      │    ├── key: ()
      │    ├── fd: ()-->(12)
      │    ├── scan xy
      │    │    ├── columns: x:8!null
      │    │    └── key: (8)
      │    └── aggregations
      │         └── max [as=max:12, outer=(8)]
      │              └── x:8
      └── filters
           └── k:1 = max:12 [outer=(1,12), constraints=(/1: (/NULL - ]; /12: (/NULL - ]), fd=(1)==(12), (12)==(1)]

# Hoist an uncorrelated equality subquery that could return multiple rows.
norm expect=HoistSelectSubquery
SELECT * FROM a WHERE k = (SELECT x FROM xy)
----
project
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(1-5)
 └── inner-join (hash)
      ├── columns: k:1!null i:2 f:3 s:4 j:5 x:8!null
      ├── cardinality: [0 - 1]
      ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one)
      ├── key: ()
      ├── fd: ()-->(1-5,8), (1)==(8), (8)==(1)
      ├── scan a
      │    ├── columns: k:1!null i:2 f:3 s:4 j:5
      │    ├── key: (1)
      │    └── fd: (1)-->(2-5)
      ├── max1-row
      │    ├── columns: x:8!null
      │    ├── error: "more than one row returned by a subquery used as an expression"
      │    ├── cardinality: [0 - 1]
      │    ├── key: ()
      │    ├── fd: ()-->(8)
      │    └── scan xy
      │         ├── columns: x:8!null
      │         └── key: (8)
      └── filters
           └── k:1 = x:8 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]

# Do not hoist an uncorrelated equality subquery if the corresponding session
# setting is disabled.
norm set=optimizer_hoist_uncorrelated_equality_subqueries=off expect-not=HoistSelectSubquery
SELECT * FROM a WHERE k = (SELECT max(x) FROM xy)
----
select
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 └── filters
      └── eq [outer=(1), subquery, constraints=(/1: (/NULL - ])]
           ├── k:1
           └── subquery
                └── scalar-group-by
                     ├── columns: max:12
                     ├── cardinality: [1 - 1]
                     ├── key: ()
                     ├── fd: ()-->(12)
                     ├── scan xy
                     │    ├── columns: x:8!null
                     │    └── key: (8)
                     └── aggregations
                          └── max [as=max:12, outer=(8)]
                               └── x:8

# Do not hoist an uncorrelated inequality subquery. We have not yet proven that
# it will lead to a better plan.
norm expect-not=HoistSelectSubquery
SELECT * FROM a WHERE k < (SELECT max(x) FROM xy)
----
select
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 └── filters
      └── lt [outer=(1), subquery, constraints=(/1: (/NULL - ])]
           ├── k:1
           └── subquery
                └── scalar-group-by
                     ├── columns: max:12
                     ├── cardinality: [1 - 1]
                     ├── key: ()
                     ├── fd: ()-->(12)
                     ├── scan xy
                     │    ├── columns: x:8!null
                     │    └── key: (8)
                     └── aggregations
                          └── max [as=max:12, outer=(8)]
                               └── x:8

# The subquery should only be hoisted once to avoid creating an expression with
# children that have intersecting columns. See #114703.
norm expect=HoistSelectSubquery
SELECT NULL
FROM a AS t1
JOIN a AS t2 ON t1.i = t2.i
WHERE t2.i = (SELECT 0 FROM a)
----
project
 ├── columns: "?column?":23
 ├── fd: ()-->(23)
 ├── inner-join (hash)
 │    ├── columns: t1.i:2!null t2.i:9!null "?column?":22!null
 │    ├── fd: ()-->(2,9,22), (2)==(9,22), (9)==(2,22), (22)==(2,9)
 │    ├── inner-join (hash)
 │    │    ├── columns: t1.i:2!null "?column?":22!null
 │    │    ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-more)
 │    │    ├── fd: ()-->(2,22), (2)==(22), (22)==(2)
 │    │    ├── scan a [as=t1]
 │    │    │    └── columns: t1.i:2
 │    │    ├── max1-row
 │    │    │    ├── columns: "?column?":22!null
 │    │    │    ├── error: "more than one row returned by a subquery used as an expression"
 │    │    │    ├── cardinality: [0 - 1]
 │    │    │    ├── key: ()
 │    │    │    ├── fd: ()-->(22)
 │    │    │    └── project
 │    │    │         ├── columns: "?column?":22!null
 │    │    │         ├── fd: ()-->(22)
 │    │    │         ├── scan a
 │    │    │         └── projections
 │    │    │              └── 0 [as="?column?":22]
 │    │    └── filters
 │    │         └── t1.i:2 = "?column?":22 [outer=(2,22), constraints=(/2: (/NULL - ]; /22: (/NULL - ]), fd=(2)==(22), (22)==(2)]
 │    ├── select
 │    │    ├── columns: t2.i:9!null
 │    │    ├── scan a [as=t2]
 │    │    │    └── columns: t2.i:9
 │    │    └── filters
 │    │         └── eq [outer=(9), subquery, constraints=(/9: (/NULL - ])]
 │    │              ├── t2.i:9
 │    │              └── subquery
 │    │                   └── max1-row
 │    │                        ├── columns: "?column?":22!null
 │    │                        ├── error: "more than one row returned by a subquery used as an expression"
 │    │                        ├── cardinality: [0 - 1]
 │    │                        ├── key: ()
 │    │                        ├── fd: ()-->(22)
 │    │                        └── project
 │    │                             ├── columns: "?column?":22!null
 │    │                             ├── fd: ()-->(22)
 │    │                             ├── scan a
 │    │                             └── projections
 │    │                                  └── 0 [as="?column?":22]
 │    └── filters
 │         └── t1.i:2 = t2.i:9 [outer=(2,9), constraints=(/2: (/NULL - ]; /9: (/NULL - ]), fd=(2)==(9), (9)==(2)]
 └── projections
      └── NULL [as="?column?":23]

# Each subquery should only be hoisted once.
norm expect=HoistSelectSubquery
SELECT NULL
FROM a AS t1
JOIN a AS t2 ON t1.i = t2.i
WHERE t2.i = (SELECT 0 FROM a) AND t1.i = (SELECT 0 FROM a)
----
project
 ├── columns: "?column?":31
 ├── fd: ()-->(31)
 ├── inner-join (hash)
 │    ├── columns: t1.i:2!null t2.i:9!null "?column?":22!null "?column?":30!null
 │    ├── fd: ()-->(2,9,22,30), (2)==(9,22,30), (9)==(2,22,30), (22)==(2,9,30), (30)==(2,9,22)
 │    ├── inner-join (hash)
 │    │    ├── columns: t1.i:2!null "?column?":22!null "?column?":30!null
 │    │    ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-more)
 │    │    ├── fd: ()-->(2,22,30), (2)==(22,30), (22)==(2,30), (30)==(2,22)
 │    │    ├── inner-join (hash)
 │    │    │    ├── columns: t1.i:2!null "?column?":30!null
 │    │    │    ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-more)
 │    │    │    ├── fd: ()-->(2,30), (2)==(30), (30)==(2)
 │    │    │    ├── scan a [as=t1]
 │    │    │    │    └── columns: t1.i:2
 │    │    │    ├── max1-row
 │    │    │    │    ├── columns: "?column?":30!null
 │    │    │    │    ├── error: "more than one row returned by a subquery used as an expression"
 │    │    │    │    ├── cardinality: [0 - 1]
 │    │    │    │    ├── key: ()
 │    │    │    │    ├── fd: ()-->(30)
 │    │    │    │    └── project
 │    │    │    │         ├── columns: "?column?":30!null
 │    │    │    │         ├── fd: ()-->(30)
 │    │    │    │         ├── scan a
 │    │    │    │         └── projections
 │    │    │    │              └── 0 [as="?column?":30]
 │    │    │    └── filters
 │    │    │         └── t1.i:2 = "?column?":30 [outer=(2,30), constraints=(/2: (/NULL - ]; /30: (/NULL - ]), fd=(2)==(30), (30)==(2)]
 │    │    ├── select
 │    │    │    ├── columns: "?column?":22!null
 │    │    │    ├── cardinality: [0 - 1]
 │    │    │    ├── key: ()
 │    │    │    ├── fd: ()-->(22)
 │    │    │    ├── max1-row
 │    │    │    │    ├── columns: "?column?":22!null
 │    │    │    │    ├── error: "more than one row returned by a subquery used as an expression"
 │    │    │    │    ├── cardinality: [0 - 1]
 │    │    │    │    ├── key: ()
 │    │    │    │    ├── fd: ()-->(22)
 │    │    │    │    └── project
 │    │    │    │         ├── columns: "?column?":22!null
 │    │    │    │         ├── fd: ()-->(22)
 │    │    │    │         ├── scan a
 │    │    │    │         └── projections
 │    │    │    │              └── 0 [as="?column?":22]
 │    │    │    └── filters
 │    │    │         └── eq [outer=(22), subquery, constraints=(/22: (/NULL - ])]
 │    │    │              ├── "?column?":22
 │    │    │              └── subquery
 │    │    │                   └── max1-row
 │    │    │                        ├── columns: "?column?":30!null
 │    │    │                        ├── error: "more than one row returned by a subquery used as an expression"
 │    │    │                        ├── cardinality: [0 - 1]
 │    │    │                        ├── key: ()
 │    │    │                        ├── fd: ()-->(30)
 │    │    │                        └── project
 │    │    │                             ├── columns: "?column?":30!null
 │    │    │                             ├── fd: ()-->(30)
 │    │    │                             ├── scan a
 │    │    │                             └── projections
 │    │    │                                  └── 0 [as="?column?":30]
 │    │    └── filters
 │    │         └── t1.i:2 = "?column?":22 [outer=(2,22), constraints=(/2: (/NULL - ]; /22: (/NULL - ]), fd=(2)==(22), (22)==(2)]
 │    ├── select
 │    │    ├── columns: t2.i:9!null
 │    │    ├── scan a [as=t2]
 │    │    │    └── columns: t2.i:9
 │    │    └── filters
 │    │         ├── eq [outer=(9), subquery, constraints=(/9: (/NULL - ])]
 │    │         │    ├── t2.i:9
 │    │         │    └── subquery
 │    │         │         └── max1-row
 │    │         │              ├── columns: "?column?":22!null
 │    │         │              ├── error: "more than one row returned by a subquery used as an expression"
 │    │         │              ├── cardinality: [0 - 1]
 │    │         │              ├── key: ()
 │    │         │              ├── fd: ()-->(22)
 │    │         │              └── project
 │    │         │                   ├── columns: "?column?":22!null
 │    │         │                   ├── fd: ()-->(22)
 │    │         │                   ├── scan a
 │    │         │                   └── projections
 │    │         │                        └── 0 [as="?column?":22]
 │    │         └── eq [outer=(9), subquery, constraints=(/9: (/NULL - ])]
 │    │              ├── t2.i:9
 │    │              └── subquery
 │    │                   └── max1-row
 │    │                        ├── columns: "?column?":30!null
 │    │                        ├── error: "more than one row returned by a subquery used as an expression"
 │    │                        ├── cardinality: [0 - 1]
 │    │                        ├── key: ()
 │    │                        ├── fd: ()-->(30)
 │    │                        └── project
 │    │                             ├── columns: "?column?":30!null
 │    │                             ├── fd: ()-->(30)
 │    │                             ├── scan a
 │    │                             └── projections
 │    │                                  └── 0 [as="?column?":30]
 │    └── filters
 │         └── t1.i:2 = t2.i:9 [outer=(2,9), constraints=(/2: (/NULL - ]; /9: (/NULL - ]), fd=(2)==(9), (9)==(2)]
 └── projections
      └── NULL [as="?column?":31]

exec-ddl
CREATE FUNCTION f114703() RETURNS INT STABLE LANGUAGE SQL AS $$
  SELECT x FROM xy
$$
----

# The filter "t2.a = f()" is pushed into both sides of the join, and inlined as
# a subquery that is only hoisted once.
norm expect=HoistSelectSubquery
SELECT NULL
FROM a AS t1
JOIN a AS t2 ON t1.i = t2.i
WHERE t2.i = f114703()
----
project
 ├── columns: "?column?":19
 ├── fd: ()-->(19)
 ├── inner-join (hash)
 │    ├── columns: t1.i:2!null t2.i:9!null x:15!null
 │    ├── fd: ()-->(2,9,15), (2)==(9,15), (9)==(2,15), (15)==(2,9)
 │    ├── inner-join (hash)
 │    │    ├── columns: t1.i:2!null x:15!null
 │    │    ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-more)
 │    │    ├── fd: ()-->(2,15), (2)==(15), (15)==(2)
 │    │    ├── scan a [as=t1]
 │    │    │    └── columns: t1.i:2
 │    │    ├── limit
 │    │    │    ├── columns: x:15!null
 │    │    │    ├── cardinality: [0 - 1]
 │    │    │    ├── key: ()
 │    │    │    ├── fd: ()-->(15)
 │    │    │    ├── scan xy
 │    │    │    │    ├── columns: x:15!null
 │    │    │    │    ├── key: (15)
 │    │    │    │    └── limit hint: 1.00
 │    │    │    └── 1
 │    │    └── filters
 │    │         └── t1.i:2 = x:15 [outer=(2,15), constraints=(/2: (/NULL - ]; /15: (/NULL - ]), fd=(2)==(15), (15)==(2)]
 │    ├── select
 │    │    ├── columns: t2.i:9!null
 │    │    ├── scan a [as=t2]
 │    │    │    └── columns: t2.i:9
 │    │    └── filters
 │    │         └── eq [outer=(9), subquery, constraints=(/9: (/NULL - ])]
 │    │              ├── t2.i:9
 │    │              └── subquery
 │    │                   └── limit
 │    │                        ├── columns: x:15!null
 │    │                        ├── cardinality: [0 - 1]
 │    │                        ├── key: ()
 │    │                        ├── fd: ()-->(15)
 │    │                        ├── scan xy
 │    │                        │    ├── columns: x:15!null
 │    │                        │    ├── key: (15)
 │    │                        │    └── limit hint: 1.00
 │    │                        └── 1
 │    └── filters
 │         └── t1.i:2 = t2.i:9 [outer=(2,9), constraints=(/2: (/NULL - ]; /9: (/NULL - ]), fd=(2)==(9), (9)==(2)]
 └── projections
      └── NULL [as="?column?":19]


# --------------------------------------------------
# HoistProjectSubquery
# --------------------------------------------------
norm expect=HoistProjectSubquery
SELECT (SELECT x FROM xy WHERE x=k) FROM a
----
project
 ├── columns: x:12
 ├── left-join (hash)
 │    ├── columns: k:1!null xy.x:8
 │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
 │    ├── key: (1)
 │    ├── fd: (1)-->(8)
 │    ├── scan a
 │    │    ├── columns: k:1!null
 │    │    └── key: (1)
 │    ├── scan xy
 │    │    ├── columns: xy.x:8!null
 │    │    └── key: (8)
 │    └── filters
 │         └── xy.x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]
 └── projections
      └── xy.x:8 [as=x:12, outer=(8)]

# Mixed correlated and uncorrelated subqueries.
norm expect=HoistProjectSubquery
SELECT
    5 AS a,
    (SELECT x FROM xy WHERE x=k),
    (SELECT y FROM xy LIMIT 1),
    5 IN (SELECT y FROM xy) AS b,
    EXISTS(SELECT * FROM xy),
    (SELECT count(*) FROM xy WHERE y=k)
FROM a
----
project
 ├── columns: a:29!null x:30 y:31 b:32 exists:35 count:36!null
 ├── fd: ()-->(29)
 ├── group-by (hash)
 │    ├── columns: k:1!null xy.x:8 count_rows:28!null
 │    ├── grouping columns: k:1!null
 │    ├── key: (1)
 │    ├── fd: (1)-->(8,28)
 │    ├── left-join (hash)
 │    │    ├── columns: k:1!null xy.x:8 xy.y:25
 │    │    ├── multiplicity: left-rows(one-or-more), right-rows(zero-or-one)
 │    │    ├── fd: (1)-->(8)
 │    │    ├── left-join (hash)
 │    │    │    ├── columns: k:1!null xy.x:8
 │    │    │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
 │    │    │    ├── key: (1)
 │    │    │    ├── fd: (1)-->(8)
 │    │    │    ├── scan a
 │    │    │    │    ├── columns: k:1!null
 │    │    │    │    └── key: (1)
 │    │    │    ├── scan xy
 │    │    │    │    ├── columns: xy.x:8!null
 │    │    │    │    └── key: (8)
 │    │    │    └── filters
 │    │    │         └── xy.x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]
 │    │    ├── scan xy
 │    │    │    └── columns: xy.y:25
 │    │    └── filters
 │    │         └── xy.y:25 = k:1 [outer=(1,25), constraints=(/1: (/NULL - ]; /25: (/NULL - ]), fd=(1)==(25), (25)==(1)]
 │    └── aggregations
 │         ├── count [as=count_rows:28, outer=(25)]
 │         │    └── xy.y:25
 │         └── const-agg [as=xy.x:8, outer=(8)]
 │              └── xy.x:8
 └── projections
      ├── 5 [as=a:29]
      ├── xy.x:8 [as=x:30, outer=(8)]
      ├── subquery [as=y:31, subquery]
      │    └── limit
      │         ├── columns: xy.y:13
      │         ├── cardinality: [0 - 1]
      │         ├── key: ()
      │         ├── fd: ()-->(13)
      │         ├── scan xy
      │         │    ├── columns: xy.y:13
      │         │    └── limit hint: 1.00
      │         └── 1
      ├── any: eq [as=b:32, subquery]
      │    ├── scan xy
      │    │    └── columns: xy.y:17
      │    └── 5
      ├── coalesce [as=exists:35, subquery]
      │    ├── subquery
      │    │    └── project
      │    │         ├── columns: column34:34!null
      │    │         ├── cardinality: [0 - 1]
      │    │         ├── key: ()
      │    │         ├── fd: ()-->(34)
      │    │         ├── limit
      │    │         │    ├── cardinality: [0 - 1]
      │    │         │    ├── key: ()
      │    │         │    ├── scan xy
      │    │         │    │    └── limit hint: 1.00
      │    │         │    └── 1
      │    │         └── projections
      │    │              └── true [as=column34:34]
      │    └── false
      └── count_rows:28 [as=count:36, outer=(28)]

# Subquery in GroupBy aggregate (optbuilder creates correlated Project).
norm expect=HoistProjectSubquery
SELECT max((SELECT y FROM xy WHERE y=i)) FROM a
----
scalar-group-by
 ├── columns: max:13
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(13)
 ├── project
 │    ├── columns: column12:12
 │    ├── ensure-distinct-on
 │    │    ├── columns: k:1!null y:9
 │    │    ├── grouping columns: k:1!null
 │    │    ├── error: "more than one row returned by a subquery used as an expression"
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(9)
 │    │    ├── left-join (hash)
 │    │    │    ├── columns: k:1!null i:2 y:9
 │    │    │    ├── fd: (1)-->(2)
 │    │    │    ├── scan a
 │    │    │    │    ├── columns: k:1!null i:2
 │    │    │    │    ├── key: (1)
 │    │    │    │    └── fd: (1)-->(2)
 │    │    │    ├── scan xy
 │    │    │    │    └── columns: y:9
 │    │    │    └── filters
 │    │    │         └── y:9 = i:2 [outer=(2,9), constraints=(/2: (/NULL - ]; /9: (/NULL - ]), fd=(2)==(9), (9)==(2)]
 │    │    └── aggregations
 │    │         └── const-agg [as=y:9, outer=(9)]
 │    │              └── y:9
 │    └── projections
 │         └── y:9 [as=column12:12, outer=(9)]
 └── aggregations
      └── max [as=max:13, outer=(12)]
           └── column12:12

# Hoist a leakproof subquery within a COALESCE.
norm expect=HoistProjectSubquery
SELECT COALESCE(i, (SELECT y FROM xy WHERE x = i)) FROM a
----
project
 ├── columns: coalesce:12
 ├── left-join (hash)
 │    ├── columns: i:2 x:8 y:9
 │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more)
 │    ├── fd: (8)-->(9)
 │    ├── scan a
 │    │    └── columns: i:2
 │    ├── scan xy
 │    │    ├── columns: x:8!null y:9
 │    │    ├── key: (8)
 │    │    └── fd: (8)-->(9)
 │    └── filters
 │         └── x:8 = i:2 [outer=(2,8), constraints=(/2: (/NULL - ]; /8: (/NULL - ]), fd=(2)==(8), (8)==(2)]
 └── projections
      └── COALESCE(i:2, y:9) [as=coalesce:12, outer=(2,9)]

# Hoist a non-leakproof subquery within a COALESCE if it is the first argument.
norm expect=HoistProjectSubquery
SELECT COALESCE((SELECT (1/y)::INT FROM xy WHERE x = i), i) FROM a
----
project
 ├── columns: coalesce:13
 ├── immutable
 ├── left-join (hash)
 │    ├── columns: i:2 x:8 int8:12
 │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more)
 │    ├── immutable
 │    ├── fd: (8)-->(12)
 │    ├── scan a
 │    │    └── columns: i:2
 │    ├── project
 │    │    ├── columns: int8:12 x:8!null
 │    │    ├── immutable
 │    │    ├── key: (8)
 │    │    ├── fd: (8)-->(12)
 │    │    ├── scan xy
 │    │    │    ├── columns: x:8!null y:9
 │    │    │    ├── key: (8)
 │    │    │    └── fd: (8)-->(9)
 │    │    └── projections
 │    │         └── (1 / y:9)::INT8 [as=int8:12, outer=(9), immutable]
 │    └── filters
 │         └── x:8 = i:2 [outer=(2,8), constraints=(/2: (/NULL - ]; /8: (/NULL - ]), fd=(2)==(8), (8)==(2)]
 └── projections
      └── COALESCE(int8:12, i:2) [as=coalesce:13, outer=(2,12)]

# Do not hoist a non-leakproof subquery within a COALESCE if it is not the first
# argument.
norm expect-not=HoistProjectSubquery
SELECT COALESCE(i, (SELECT (1/y)::INT FROM xy WHERE x = i)) FROM a
----
project
 ├── columns: coalesce:13
 ├── immutable
 ├── scan a
 │    └── columns: i:2
 └── projections
      └── coalesce [as=coalesce:13, outer=(2), immutable, correlated-subquery]
           ├── i:2
           └── subquery
                └── project
                     ├── columns: int8:12
                     ├── outer: (2)
                     ├── cardinality: [0 - 1]
                     ├── immutable
                     ├── key: ()
                     ├── fd: ()-->(12)
                     ├── select
                     │    ├── columns: x:8!null y:9
                     │    ├── outer: (2)
                     │    ├── cardinality: [0 - 1]
                     │    ├── key: ()
                     │    ├── fd: ()-->(8,9)
                     │    ├── scan xy
                     │    │    ├── columns: x:8!null y:9
                     │    │    ├── key: (8)
                     │    │    └── fd: (8)-->(9)
                     │    └── filters
                     │         └── x:8 = i:2 [outer=(2,8), constraints=(/2: (/NULL - ]; /8: (/NULL - ]), fd=(2)==(8), (8)==(2)]
                     └── projections
                          └── (1 / y:9)::INT8 [as=int8:12, outer=(9), immutable]

# Exists in projection list.
norm expect=HoistProjectSubquery
SELECT EXISTS(SELECT * FROM xy WHERE y=i) FROM a
----
project
 ├── columns: exists:13!null
 ├── group-by (hash)
 │    ├── columns: k:1!null canary_agg:14
 │    ├── grouping columns: k:1!null
 │    ├── key: (1)
 │    ├── fd: (1)-->(14)
 │    ├── left-join (hash)
 │    │    ├── columns: k:1!null i:2 x:8 y:9
 │    │    ├── key: (1,8)
 │    │    ├── fd: (1)-->(2), (8)-->(9)
 │    │    ├── scan a
 │    │    │    ├── columns: k:1!null i:2
 │    │    │    ├── key: (1)
 │    │    │    └── fd: (1)-->(2)
 │    │    ├── scan xy
 │    │    │    ├── columns: x:8!null y:9
 │    │    │    ├── key: (8)
 │    │    │    └── fd: (8)-->(9)
 │    │    └── filters
 │    │         └── y:9 = i:2 [outer=(2,9), constraints=(/2: (/NULL - ]; /9: (/NULL - ]), fd=(2)==(9), (9)==(2)]
 │    └── aggregations
 │         └── const-not-null-agg [as=canary_agg:14, outer=(8)]
 │              └── x:8
 └── projections
      └── canary_agg:14 IS NOT NULL [as=exists:13, outer=(14)]

# Any in projection list.
norm expect=HoistProjectSubquery
SELECT 5 < ANY(SELECT y FROM xy WHERE y=i) AS r FROM a
----
project
 ├── columns: r:12
 ├── group-by (hash)
 │    ├── columns: k:1!null bool_or:14
 │    ├── grouping columns: k:1!null
 │    ├── key: (1)
 │    ├── fd: (1)-->(14)
 │    ├── left-join (hash)
 │    │    ├── columns: k:1!null i:2 y:9 notnull:13
 │    │    ├── fd: (1)-->(2), (9)~~>(13)
 │    │    ├── scan a
 │    │    │    ├── columns: k:1!null i:2
 │    │    │    ├── key: (1)
 │    │    │    └── fd: (1)-->(2)
 │    │    ├── project
 │    │    │    ├── columns: notnull:13!null y:9
 │    │    │    ├── fd: (9)-->(13)
 │    │    │    ├── select
 │    │    │    │    ├── columns: y:9
 │    │    │    │    ├── scan xy
 │    │    │    │    │    └── columns: y:9
 │    │    │    │    └── filters
 │    │    │    │         └── (y:9 > 5) IS NOT false [outer=(9)]
 │    │    │    └── projections
 │    │    │         └── y:9 IS NOT NULL [as=notnull:13, outer=(9)]
 │    │    └── filters
 │    │         └── y:9 = i:2 [outer=(2,9), constraints=(/2: (/NULL - ]; /9: (/NULL - ]), fd=(2)==(9), (9)==(2)]
 │    └── aggregations
 │         └── bool-or [as=bool_or:14, outer=(13)]
 │              └── notnull:13
 └── projections
      └── CASE WHEN bool_or:14 THEN true WHEN bool_or:14 IS NULL THEN false ELSE CAST(NULL AS BOOL) END [as=r:12, outer=(14)]

# Any in projection list with tuple comparison should use IS NOT NULL
# (i.e., IsTupleNotNull expression) instead of IS DISTINCT FROM NULL.
norm expect=HoistProjectSubquery
SELECT (5, 50) < ANY(SELECT x, y FROM xy WHERE y=i) AS r FROM a
----
project
 ├── columns: r:13
 ├── immutable
 ├── group-by (hash)
 │    ├── columns: k:1!null scalar:14!null bool_or:16
 │    ├── grouping columns: k:1!null
 │    ├── immutable
 │    ├── key: (1)
 │    ├── fd: ()-->(14), (1)-->(14,16)
 │    ├── left-join-apply
 │    │    ├── columns: k:1!null i:2 column12:12 scalar:14!null notnull:15
 │    │    ├── immutable
 │    │    ├── fd: ()-->(14), (1)-->(2)
 │    │    ├── project
 │    │    │    ├── columns: scalar:14!null k:1!null i:2
 │    │    │    ├── key: (1)
 │    │    │    ├── fd: ()-->(14), (1)-->(2)
 │    │    │    ├── scan a
 │    │    │    │    ├── columns: k:1!null i:2
 │    │    │    │    ├── key: (1)
 │    │    │    │    └── fd: (1)-->(2)
 │    │    │    └── projections
 │    │    │         └── (5, 50) [as=scalar:14]
 │    │    ├── project
 │    │    │    ├── columns: notnull:15!null column12:12!null
 │    │    │    ├── outer: (2)
 │    │    │    ├── fd: (12)-->(15)
 │    │    │    ├── project
 │    │    │    │    ├── columns: column12:12!null
 │    │    │    │    ├── outer: (2)
 │    │    │    │    ├── select
 │    │    │    │    │    ├── columns: x:8!null y:9!null
 │    │    │    │    │    ├── outer: (2)
 │    │    │    │    │    ├── key: (8)
 │    │    │    │    │    ├── fd: ()-->(9)
 │    │    │    │    │    ├── scan xy
 │    │    │    │    │    │    ├── columns: x:8!null y:9
 │    │    │    │    │    │    ├── key: (8)
 │    │    │    │    │    │    └── fd: (8)-->(9)
 │    │    │    │    │    └── filters
 │    │    │    │    │         └── y:9 = i:2 [outer=(2,9), constraints=(/2: (/NULL - ]; /9: (/NULL - ]), fd=(2)==(9), (9)==(2)]
 │    │    │    │    └── projections
 │    │    │    │         └── (x:8, y:9) [as=column12:12, outer=(8,9)]
 │    │    │    └── projections
 │    │    │         └── column12:12 IS NOT NULL [as=notnull:15, outer=(12)]
 │    │    └── filters
 │    │         └── (scalar:14 < column12:12) IS NOT false [outer=(12,14), immutable]
 │    └── aggregations
 │         ├── bool-or [as=bool_or:16, outer=(15)]
 │         │    └── notnull:15
 │         └── const-agg [as=scalar:14, outer=(14)]
 │              └── scalar:14
 └── projections
      └── CASE WHEN bool_or:16 AND (scalar:14 IS NOT NULL) THEN true WHEN bool_or:16 IS NULL THEN false ELSE CAST(NULL AS BOOL) END [as=r:13, outer=(14,16)]

# Correlated subquery nested in uncorrelated subquery.
norm expect=HoistProjectSubquery
SELECT EXISTS(SELECT EXISTS(SELECT * FROM xy WHERE y=i) FROM a)
----
values
 ├── columns: exists:18
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(18)
 └── tuple
      └── coalesce
           ├── subquery
           │    └── project
           │         ├── columns: column17:17!null
           │         ├── cardinality: [0 - 1]
           │         ├── key: ()
           │         ├── fd: ()-->(17)
           │         ├── limit
           │         │    ├── columns: i:2 y:9
           │         │    ├── cardinality: [0 - 1]
           │         │    ├── key: ()
           │         │    ├── fd: ()-->(2,9)
           │         │    ├── left-join (hash)
           │         │    │    ├── columns: i:2 y:9
           │         │    │    ├── multiplicity: left-rows(one-or-more), right-rows(zero-or-one)
           │         │    │    ├── fd: ()-->(2,9)
           │         │    │    ├── limit hint: 1.00
           │         │    │    ├── limit
           │         │    │    │    ├── columns: i:2
           │         │    │    │    ├── cardinality: [0 - 1]
           │         │    │    │    ├── key: ()
           │         │    │    │    ├── fd: ()-->(2)
           │         │    │    │    ├── scan a
           │         │    │    │    │    ├── columns: i:2
           │         │    │    │    │    └── limit hint: 1.00
           │         │    │    │    └── 1
           │         │    │    ├── scan xy
           │         │    │    │    └── columns: y:9
           │         │    │    └── filters
           │         │    │         └── y:9 = i:2 [outer=(2,9), constraints=(/2: (/NULL - ]; /9: (/NULL - ]), fd=(2)==(9), (9)==(2)]
           │         │    └── 1
           │         └── projections
           │              └── true [as=column17:17]
           └── false

# Hoist an uncorrelated equality subquery.
norm expect=HoistProjectSubquery
SELECT k = (SELECT max(x) FROM xy) FROM a
----
project
 ├── columns: "?column?":13
 ├── inner-join (cross)
 │    ├── columns: k:1!null max:12
 │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more)
 │    ├── key: (1)
 │    ├── fd: ()-->(12)
 │    ├── scan a
 │    │    ├── columns: k:1!null
 │    │    └── key: (1)
 │    ├── scalar-group-by
 │    │    ├── columns: max:12
 │    │    ├── cardinality: [1 - 1]
 │    │    ├── key: ()
 │    │    ├── fd: ()-->(12)
 │    │    ├── scan xy
 │    │    │    ├── columns: x:8!null
 │    │    │    └── key: (8)
 │    │    └── aggregations
 │    │         └── max [as=max:12, outer=(8)]
 │    │              └── x:8
 │    └── filters (true)
 └── projections
      └── k:1 = max:12 [as="?column?":13, outer=(1,12)]

# Hoist an uncorrelated equality subquery that could return multiple rows.
norm expect=HoistProjectSubquery
SELECT k = (SELECT x FROM xy) FROM a
----
project
 ├── columns: "?column?":12
 ├── left-join (cross)
 │    ├── columns: k:1!null x:8
 │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more)
 │    ├── key: (1)
 │    ├── fd: (1)-->(8)
 │    ├── scan a
 │    │    ├── columns: k:1!null
 │    │    └── key: (1)
 │    ├── max1-row
 │    │    ├── columns: x:8!null
 │    │    ├── error: "more than one row returned by a subquery used as an expression"
 │    │    ├── cardinality: [0 - 1]
 │    │    ├── key: ()
 │    │    ├── fd: ()-->(8)
 │    │    └── scan xy
 │    │         ├── columns: x:8!null
 │    │         └── key: (8)
 │    └── filters (true)
 └── projections
      └── k:1 = x:8 [as="?column?":12, outer=(1,8)]

# Do not hoist an uncorrelated equality subquery if the corresponding session
# setting is disabled.
norm set=optimizer_hoist_uncorrelated_equality_subqueries=off expect-not=HoistProjectSubquery
SELECT k = (SELECT max(x) FROM xy) FROM a
----
project
 ├── columns: "?column?":13
 ├── scan a
 │    ├── columns: k:1!null
 │    └── key: (1)
 └── projections
      └── eq [as="?column?":13, outer=(1), subquery]
           ├── k:1
           └── subquery
                └── scalar-group-by
                     ├── columns: max:12
                     ├── cardinality: [1 - 1]
                     ├── key: ()
                     ├── fd: ()-->(12)
                     ├── scan xy
                     │    ├── columns: x:8!null
                     │    └── key: (8)
                     └── aggregations
                          └── max [as=max:12, outer=(8)]
                               └── x:8

# Do not hoist an uncorrelated inequality subquery. We have not yet proven that
# it will lead to a better plan.
norm expect-not=HoistProjectSubquery
SELECT k < (SELECT max(x) FROM xy) FROM a
----
project
 ├── columns: "?column?":13
 ├── scan a
 │    ├── columns: k:1!null
 │    └── key: (1)
 └── projections
      └── lt [as="?column?":13, outer=(1), subquery]
           ├── k:1
           └── subquery
                └── scalar-group-by
                     ├── columns: max:12
                     ├── cardinality: [1 - 1]
                     ├── key: ()
                     ├── fd: ()-->(12)
                     ├── scan xy
                     │    ├── columns: x:8!null
                     │    └── key: (8)
                     └── aggregations
                          └── max [as=max:12, outer=(8)]
                               └── x:8


# --------------------------------------------------
# HoistJoinSubquery
# --------------------------------------------------
norm expect=HoistJoinSubquery
SELECT i, y FROM a INNER JOIN xy ON (SELECT k+1) = x
----
project
 ├── columns: i:2 y:9
 ├── immutable
 └── inner-join (hash)
      ├── columns: i:2 x:8!null y:9 column13:13!null
      ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-more)
      ├── immutable
      ├── fd: (8)-->(9), (8)==(13), (13)==(8)
      ├── project
      │    ├── columns: column13:13!null i:2
      │    ├── immutable
      │    ├── scan a
      │    │    ├── columns: k:1!null i:2
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2)
      │    └── projections
      │         └── k:1 + 1 [as=column13:13, outer=(1), immutable]
      ├── scan xy
      │    ├── columns: x:8!null y:9
      │    ├── key: (8)
      │    └── fd: (8)-->(9)
      └── filters
           └── column13:13 = x:8 [outer=(8,13), constraints=(/8: (/NULL - ]; /13: (/NULL - ]), fd=(8)==(13), (13)==(8)]

# Hoist Exists in join filter disjunction.
norm expect=HoistJoinSubquery
SELECT s, x FROM a INNER JOIN xy ON EXISTS(SELECT * FROM uv WHERE u=y) OR k=x
----
project
 ├── columns: s:4 x:8!null
 └── inner-join (cross)
      ├── columns: k:1!null s:4 x:8!null exists:18!null
      ├── key: (1,8)
      ├── fd: (1)-->(4), (8)-->(18)
      ├── scan a
      │    ├── columns: k:1!null s:4
      │    ├── key: (1)
      │    └── fd: (1)-->(4)
      ├── project
      │    ├── columns: exists:18!null x:8!null
      │    ├── key: (8)
      │    ├── fd: (8)-->(18)
      │    ├── left-join (hash)
      │    │    ├── columns: x:8!null y:9 u:12
      │    │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more)
      │    │    ├── key: (8)
      │    │    ├── fd: (8)-->(9,12)
      │    │    ├── scan xy
      │    │    │    ├── columns: x:8!null y:9
      │    │    │    ├── key: (8)
      │    │    │    └── fd: (8)-->(9)
      │    │    ├── scan uv
      │    │    │    ├── columns: u:12!null
      │    │    │    └── key: (12)
      │    │    └── filters
      │    │         └── u:12 = y:9 [outer=(9,12), constraints=(/9: (/NULL - ]; /12: (/NULL - ]), fd=(9)==(12), (12)==(9)]
      │    └── projections
      │         └── u:12 IS NOT NULL [as=exists:18, outer=(12)]
      └── filters
           └── exists:18 OR (k:1 = x:8) [outer=(1,8,18)]

# Any in Join filter disjunction.
norm expect=HoistJoinSubquery
SELECT j, y FROM a INNER JOIN xy ON x IN (SELECT v FROM uv WHERE u=y AND v=i) OR x IS NULL
----
project
 ├── columns: j:5 y:9
 └── select
      ├── columns: j:5 x:8!null y:9 case:18
      ├── fd: (8)-->(9)
      ├── project
      │    ├── columns: case:18 j:5 x:8!null y:9
      │    ├── fd: (8)-->(9)
      │    ├── group-by (hash)
      │    │    ├── columns: k:1!null j:5 x:8!null y:9 bool_or:17
      │    │    ├── grouping columns: k:1!null x:8!null
      │    │    ├── key: (1,8)
      │    │    ├── fd: (1)-->(5), (8)-->(9), (1,8)-->(5,9,17)
      │    │    ├── left-join (hash)
      │    │    │    ├── columns: k:1!null i:2 j:5 x:8!null y:9 u:12 v:13 notnull:16
      │    │    │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more)
      │    │    │    ├── key: (1,8)
      │    │    │    ├── fd: (1)-->(2,5), (8)-->(9), (12)-->(13), (13)~~>(16), (1,8)-->(12,13,16)
      │    │    │    ├── inner-join (cross)
      │    │    │    │    ├── columns: k:1!null i:2 j:5 x:8!null y:9
      │    │    │    │    ├── key: (1,8)
      │    │    │    │    ├── fd: (1)-->(2,5), (8)-->(9)
      │    │    │    │    ├── scan a
      │    │    │    │    │    ├── columns: k:1!null i:2 j:5
      │    │    │    │    │    ├── key: (1)
      │    │    │    │    │    └── fd: (1)-->(2,5)
      │    │    │    │    ├── scan xy
      │    │    │    │    │    ├── columns: x:8!null y:9
      │    │    │    │    │    ├── key: (8)
      │    │    │    │    │    └── fd: (8)-->(9)
      │    │    │    │    └── filters (true)
      │    │    │    ├── project
      │    │    │    │    ├── columns: notnull:16!null u:12!null v:13
      │    │    │    │    ├── key: (12)
      │    │    │    │    ├── fd: (12)-->(13), (13)-->(16)
      │    │    │    │    ├── scan uv
      │    │    │    │    │    ├── columns: u:12!null v:13
      │    │    │    │    │    ├── key: (12)
      │    │    │    │    │    └── fd: (12)-->(13)
      │    │    │    │    └── projections
      │    │    │    │         └── v:13 IS NOT NULL [as=notnull:16, outer=(13)]
      │    │    │    └── filters
      │    │    │         ├── u:12 = y:9 [outer=(9,12), constraints=(/9: (/NULL - ]; /12: (/NULL - ]), fd=(9)==(12), (12)==(9)]
      │    │    │         ├── v:13 = i:2 [outer=(2,13), constraints=(/2: (/NULL - ]; /13: (/NULL - ]), fd=(2)==(13), (13)==(2)]
      │    │    │         └── (x:8 = v:13) IS NOT false [outer=(8,13)]
      │    │    └── aggregations
      │    │         ├── bool-or [as=bool_or:17, outer=(16)]
      │    │         │    └── notnull:16
      │    │         ├── const-agg [as=y:9, outer=(9)]
      │    │         │    └── y:9
      │    │         └── const-agg [as=j:5, outer=(5)]
      │    │              └── j:5
      │    └── projections
      │         └── CASE WHEN bool_or:17 AND (x:8 IS NOT NULL) THEN true WHEN bool_or:17 IS NULL THEN false ELSE CAST(NULL AS BOOL) END [as=case:18, outer=(8,17)]
      └── filters
           └── case:18 OR (x:8 IS NULL) [outer=(8,18)]

# Regression test for #130398. Do not include columns from the RHS of the
# semi-join when constructin the Project expression. The opt directive is used
# here because the SplitDisjunctionOfJoinTerms exploration rule must fire to
# trigger HoistJoinSubquery.
opt expect=HoistJoinSubquery
SELECT EXISTS(
  SELECT 1
  FROM a
  WHERE EXISTS(
    SELECT
    FROM a AS a2
    WHERE a2.i = 0 OR (a2.s = a2.s AND a1.i IN (SELECT i FROM a))
  )
)
FROM a AS a1
UNION
SELECT true FROM a
----
union
 ├── columns: exists:44!null
 ├── left columns: exists:32
 ├── right columns: bool:43
 ├── cardinality: [0 - 2]
 ├── key: (44)
 ├── project
 │    ├── columns: exists:32!null
 │    ├── group-by (hash)
 │    │    ├── columns: a1.k:1!null canary_agg:34
 │    │    ├── grouping columns: a1.k:1!null
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(34)
 │    │    ├── left-join-apply
 │    │    │    ├── columns: a1.k:1!null a1.i:2 canary:33
 │    │    │    ├── fd: (1)-->(2)
 │    │    │    ├── scan a [as=a1]
 │    │    │    │    ├── columns: a1.k:1!null a1.i:2
 │    │    │    │    ├── key: (1)
 │    │    │    │    └── fd: (1)-->(2)
 │    │    │    ├── project
 │    │    │    │    ├── columns: canary:33!null
 │    │    │    │    ├── outer: (2)
 │    │    │    │    ├── fd: ()-->(33)
 │    │    │    │    ├── semi-join (cross)
 │    │    │    │    │    ├── outer: (2)
 │    │    │    │    │    ├── scan a
 │    │    │    │    │    ├── scan a [as=a2]
 │    │    │    │    │    │    └── columns: a2.i:16 a2.s:18
 │    │    │    │    │    └── filters
 │    │    │    │    │         └── or [outer=(2,16,18), correlated-subquery]
 │    │    │    │    │              ├── a2.i:16 = 0
 │    │    │    │    │              └── and
 │    │    │    │    │                   ├── (a2.s:18 IS DISTINCT FROM CAST(NULL AS STRING)) OR CAST(NULL AS BOOL)
 │    │    │    │    │                   └── any: eq
 │    │    │    │    │                        ├── scan a
 │    │    │    │    │                        │    └── columns: a.i:23
 │    │    │    │    │                        └── a1.i:2
 │    │    │    │    └── projections
 │    │    │    │         └── true [as=canary:33]
 │    │    │    └── filters (true)
 │    │    └── aggregations
 │    │         └── const-not-null-agg [as=canary_agg:34, outer=(33)]
 │    │              └── canary:33
 │    └── projections
 │         └── canary_agg:34 IS NOT NULL [as=exists:32, outer=(34)]
 └── project
      ├── columns: bool:43!null
      ├── fd: ()-->(43)
      ├── scan a
      └── projections
           └── true [as=bool:43]

# This is a more synthetic regression test for #130398.
exprnorm expect=HoistJoinSubquery disable=PushFilterIntoJoinLeft
(SemiJoin
    (Scan [ (Table "xy") (Cols "x,y") ])
    (Select
        (Scan [ (Table "uv") (Cols "u,v") ])
        [ (Is (Var "v") (Null "int")) ]
    )
    [
        (Exists
            (Select
                (Scan [ (Table "cd") (Cols "c,d") ])
                [ (Eq (Var "x") (Var "d")) ]
            )
            []
        )
    ]
    []
)
----
group-by (hash)
 ├── columns: x:1!null y:2
 ├── grouping columns: x:1!null
 ├── key: (1)
 ├── fd: (1)-->(2)
 ├── inner-join (cross)
 │    ├── columns: x:1!null y:2 v:6 d:10!null
 │    ├── fd: ()-->(6), (1)-->(2), (1)==(10), (10)==(1)
 │    ├── select
 │    │    ├── columns: v:6
 │    │    ├── fd: ()-->(6)
 │    │    ├── scan uv
 │    │    │    └── columns: v:6
 │    │    └── filters
 │    │         └── v:6 IS NOT DISTINCT FROM CAST(NULL AS INT8) [outer=(6), constraints=(/6: [/NULL - /NULL]; tight), fd=()-->(6)]
 │    ├── inner-join (hash)
 │    │    ├── columns: x:1!null y:2 d:10!null
 │    │    ├── multiplicity: left-rows(zero-or-more), right-rows(zero-or-one)
 │    │    ├── fd: (1)-->(2), (1)==(10), (10)==(1)
 │    │    ├── scan xy
 │    │    │    ├── columns: x:1!null y:2
 │    │    │    ├── key: (1)
 │    │    │    └── fd: (1)-->(2)
 │    │    ├── scan cd
 │    │    │    └── columns: d:10!null
 │    │    └── filters
 │    │         └── x:1 = d:10 [outer=(1,10), constraints=(/1: (/NULL - ]; /10: (/NULL - ]), fd=(1)==(10), (10)==(1)]
 │    └── filters (true)
 └── aggregations
      └── const-agg [as=y:2, outer=(2)]
           └── y:2

# This is a synthetic regression test for #130398 that tests an anti-join.
exprnorm expect=HoistJoinSubquery disable=PushFilterIntoJoinLeft
(AntiJoin
    (Scan [ (Table "xy") (Cols "x,y") ])
    (Select
        (Scan [ (Table "uv") (Cols "u,v") ])
        [ (Is (Var "v") (Null "int")) ]
    )
    [
        (Exists
            (Select
                (Scan [ (Table "cd") (Cols "c,d") ])
                [ (Eq (Var "x") (Var "d")) ]
            )
            []
        )
    ]
    []
)
----
anti-join-apply
 ├── columns: x:1!null y:2
 ├── key: (1)
 ├── fd: (1)-->(2)
 ├── scan xy
 │    ├── columns: x:1!null y:2
 │    ├── key: (1)
 │    └── fd: (1)-->(2)
 ├── project
 │    ├── columns: exists:14!null
 │    ├── outer: (1)
 │    ├── group-by (hash)
 │    │    ├── columns: u:5!null canary_agg:13
 │    │    ├── grouping columns: u:5!null
 │    │    ├── outer: (1)
 │    │    ├── key: (5)
 │    │    ├── fd: (5)-->(13)
 │    │    ├── left-join (cross)
 │    │    │    ├── columns: u:5!null v:6 c:9 d:10
 │    │    │    ├── outer: (1)
 │    │    │    ├── key: (5,9)
 │    │    │    ├── fd: ()-->(6), (9)-->(10)
 │    │    │    ├── select
 │    │    │    │    ├── columns: u:5!null v:6
 │    │    │    │    ├── key: (5)
 │    │    │    │    ├── fd: ()-->(6)
 │    │    │    │    ├── scan uv
 │    │    │    │    │    ├── columns: u:5!null v:6
 │    │    │    │    │    ├── key: (5)
 │    │    │    │    │    └── fd: (5)-->(6)
 │    │    │    │    └── filters
 │    │    │    │         └── v:6 IS NOT DISTINCT FROM CAST(NULL AS INT8) [outer=(6), constraints=(/6: [/NULL - /NULL]; tight), fd=()-->(6)]
 │    │    │    ├── scan cd
 │    │    │    │    ├── columns: c:9!null d:10!null
 │    │    │    │    ├── key: (9)
 │    │    │    │    └── fd: (9)-->(10)
 │    │    │    └── filters
 │    │    │         └── x:1 = d:10 [outer=(1,10), constraints=(/1: (/NULL - ]; /10: (/NULL - ]), fd=(1)==(10), (10)==(1)]
 │    │    └── aggregations
 │    │         └── const-not-null-agg [as=canary_agg:13, outer=(9)]
 │    │              └── c:9
 │    └── projections
 │         └── canary_agg:13 IS NOT NULL [as=exists:14, outer=(13)]
 └── filters
      └── exists:14 [outer=(14), constraints=(/14: [/true - /true]; tight), fd=()-->(14)]

# --------------------------------------------------
# HoistValuesSubquery
# --------------------------------------------------
norm expect=HoistValuesSubquery
SELECT (VALUES ((SELECT i+1 AS r)), (10), ((SELECT k+1 AS s))) FROM a
----
project
 ├── columns: column1:11
 ├── immutable
 ├── ensure-distinct-on
 │    ├── columns: k:1!null column1:10
 │    ├── grouping columns: k:1!null
 │    ├── error: "more than one row returned by a subquery used as an expression"
 │    ├── immutable
 │    ├── key: (1)
 │    ├── fd: (1)-->(10)
 │    ├── inner-join-apply
 │    │    ├── columns: k:1!null i:2 r:8 s:9 column1:10
 │    │    ├── immutable
 │    │    ├── fd: (1)-->(2)
 │    │    ├── scan a
 │    │    │    ├── columns: k:1!null i:2
 │    │    │    ├── key: (1)
 │    │    │    └── fd: (1)-->(2)
 │    │    ├── inner-join-apply
 │    │    │    ├── columns: r:8 s:9 column1:10
 │    │    │    ├── outer: (1,2)
 │    │    │    ├── cardinality: [3 - 3]
 │    │    │    ├── immutable
 │    │    │    ├── fd: ()-->(8,9)
 │    │    │    ├── values
 │    │    │    │    ├── columns: r:8 s:9
 │    │    │    │    ├── outer: (1,2)
 │    │    │    │    ├── cardinality: [1 - 1]
 │    │    │    │    ├── immutable
 │    │    │    │    ├── key: ()
 │    │    │    │    ├── fd: ()-->(8,9)
 │    │    │    │    └── (i:2 + 1, k:1 + 1)
 │    │    │    ├── values
 │    │    │    │    ├── columns: column1:10
 │    │    │    │    ├── outer: (8,9)
 │    │    │    │    ├── cardinality: [3 - 3]
 │    │    │    │    ├── (r:8,)
 │    │    │    │    ├── (10,)
 │    │    │    │    └── (s:9,)
 │    │    │    └── filters (true)
 │    │    └── filters (true)
 │    └── aggregations
 │         └── const-agg [as=column1:10, outer=(10)]
 │              └── column1:10
 └── projections
      └── column1:10 [as=column1:11, outer=(10)]

# Exists in values row.
norm expect=HoistValuesSubquery
SELECT (VALUES (EXISTS(SELECT * FROM xy WHERE x=k))) FROM a
----
project
 ├── columns: column1:16!null
 ├── left-join (hash)
 │    ├── columns: k:1!null x:8
 │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
 │    ├── key: (1)
 │    ├── fd: (1)-->(8)
 │    ├── scan a
 │    │    ├── columns: k:1!null
 │    │    └── key: (1)
 │    ├── scan xy
 │    │    ├── columns: x:8!null
 │    │    └── key: (8)
 │    └── filters
 │         └── x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]
 └── projections
      └── x:8 IS NOT NULL [as=column1:16, outer=(8)]

# Any in values row.
norm expect=HoistValuesSubquery
SELECT (VALUES (5 IN (SELECT y FROM xy WHERE x=k))) FROM a
----
project
 ├── columns: column1:16
 ├── group-by (hash)
 │    ├── columns: k:1!null bool_or:14
 │    ├── grouping columns: k:1!null
 │    ├── key: (1)
 │    ├── fd: (1)-->(14)
 │    ├── left-join (hash)
 │    │    ├── columns: k:1!null x:8 notnull:13
 │    │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
 │    │    ├── key: (1)
 │    │    ├── fd: (8)-->(13), (1)-->(8,13)
 │    │    ├── scan a
 │    │    │    ├── columns: k:1!null
 │    │    │    └── key: (1)
 │    │    ├── project
 │    │    │    ├── columns: notnull:13!null x:8!null
 │    │    │    ├── key: (8)
 │    │    │    ├── fd: (8)-->(13)
 │    │    │    ├── select
 │    │    │    │    ├── columns: x:8!null y:9
 │    │    │    │    ├── key: (8)
 │    │    │    │    ├── fd: (8)-->(9)
 │    │    │    │    ├── scan xy
 │    │    │    │    │    ├── columns: x:8!null y:9
 │    │    │    │    │    ├── key: (8)
 │    │    │    │    │    └── fd: (8)-->(9)
 │    │    │    │    └── filters
 │    │    │    │         └── (y:9 = 5) IS NOT false [outer=(9)]
 │    │    │    └── projections
 │    │    │         └── y:9 IS NOT NULL [as=notnull:13, outer=(9)]
 │    │    └── filters
 │    │         └── x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]
 │    └── aggregations
 │         └── bool-or [as=bool_or:14, outer=(13)]
 │              └── notnull:13
 └── projections
      └── CASE WHEN bool_or:14 THEN true WHEN bool_or:14 IS NULL THEN false ELSE CAST(NULL AS BOOL) END [as=column1:16, outer=(14)]

# ---------------------------------------------------
# HoistProjectSetSubquery + TryDecorrelateProjectSet
# ---------------------------------------------------
norm expect=HoistProjectSetSubquery
SELECT generate_series(1, (SELECT v FROM uv WHERE u=x)) FROM xy
----
project
 ├── columns: generate_series:9
 ├── immutable
 └── project-set
      ├── columns: v:6 generate_series:9
      ├── immutable
      ├── project
      │    ├── columns: v:6
      │    └── left-join (hash)
      │         ├── columns: x:1!null u:5 v:6
      │         ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
      │         ├── key: (1)
      │         ├── fd: (5)-->(6), (1)-->(5,6)
      │         ├── scan xy
      │         │    ├── columns: x:1!null
      │         │    └── key: (1)
      │         ├── scan uv
      │         │    ├── columns: u:5!null v:6
      │         │    ├── key: (5)
      │         │    └── fd: (5)-->(6)
      │         └── filters
      │              └── u:5 = x:1 [outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
      └── zip
           └── generate_series(1, v:6) [outer=(6), immutable]

# Zip correlation within EXISTS.
norm expect=(HoistProjectSetSubquery,TryDecorrelateSemiJoin,TryDecorrelateProjectSet)
SELECT * FROM xy WHERE EXISTS(SELECT * FROM generate_series(1, (SELECT v FROM uv WHERE u=x)))
----
group-by (hash)
 ├── columns: x:1!null y:2
 ├── grouping columns: x:1!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2)
 ├── project-set
 │    ├── columns: x:1!null y:2 v:6 generate_series:9
 │    ├── immutable
 │    ├── fd: (1)-->(2,6)
 │    ├── project
 │    │    ├── columns: x:1!null y:2 v:6
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(2,6)
 │    │    └── left-join (hash)
 │    │         ├── columns: x:1!null y:2 u:5 v:6
 │    │         ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
 │    │         ├── key: (1)
 │    │         ├── fd: (1)-->(2,5,6), (5)-->(6)
 │    │         ├── scan xy
 │    │         │    ├── columns: x:1!null y:2
 │    │         │    ├── key: (1)
 │    │         │    └── fd: (1)-->(2)
 │    │         ├── scan uv
 │    │         │    ├── columns: u:5!null v:6
 │    │         │    ├── key: (5)
 │    │         │    └── fd: (5)-->(6)
 │    │         └── filters
 │    │              └── u:5 = x:1 [outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
 │    └── zip
 │         └── generate_series(1, v:6) [outer=(6), immutable]
 └── aggregations
      └── const-agg [as=y:2, outer=(2)]
           └── y:2

# Function contains multiple subqueries in arguments.
norm expect=HoistProjectSetSubquery
SELECT generate_series((select y FROM xy WHERE x=k), (SELECT v FROM uv WHERE u=k)) FROM a
----
project
 ├── columns: generate_series:16
 ├── immutable
 └── project-set
      ├── columns: y:9 v:13 generate_series:16
      ├── immutable
      ├── project
      │    ├── columns: y:9 v:13
      │    └── left-join (hash)
      │         ├── columns: k:1!null x:8 y:9 u:12 v:13
      │         ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
      │         ├── key: (1)
      │         ├── fd: (8)-->(9), (1)-->(8,9,12,13), (12)-->(13)
      │         ├── left-join (hash)
      │         │    ├── columns: k:1!null x:8 y:9
      │         │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
      │         │    ├── key: (1)
      │         │    ├── fd: (8)-->(9), (1)-->(8,9)
      │         │    ├── scan a
      │         │    │    ├── columns: k:1!null
      │         │    │    └── key: (1)
      │         │    ├── scan xy
      │         │    │    ├── columns: x:8!null y:9
      │         │    │    ├── key: (8)
      │         │    │    └── fd: (8)-->(9)
      │         │    └── filters
      │         │         └── x:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]
      │         ├── scan uv
      │         │    ├── columns: u:12!null v:13
      │         │    ├── key: (12)
      │         │    └── fd: (12)-->(13)
      │         └── filters
      │              └── u:12 = k:1 [outer=(1,12), constraints=(/1: (/NULL - ]; /12: (/NULL - ]), fd=(1)==(12), (12)==(1)]
      └── zip
           └── generate_series(y:9, v:13) [outer=(9,13), immutable]

# Multiple functions.
norm expect=HoistProjectSetSubquery
SELECT
    generate_series(1, (SELECT v FROM uv WHERE u=k)),
    information_schema._pg_expandarray(ARRAY[(SELECT x FROM xy WHERE x=k)])
FROM a
----
project
 ├── columns: generate_series:12 information_schema._pg_expandarray:19
 ├── immutable
 ├── project-set
 │    ├── columns: v:9 generate_series:12 xy.x:13 x:17 n:18
 │    ├── immutable
 │    ├── project
 │    │    ├── columns: v:9 xy.x:13
 │    │    └── left-join (hash)
 │    │         ├── columns: k:1!null u:8 v:9 xy.x:13
 │    │         ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
 │    │         ├── key: (1)
 │    │         ├── fd: (8)-->(9), (1)-->(8,9,13)
 │    │         ├── left-join (hash)
 │    │         │    ├── columns: k:1!null u:8 v:9
 │    │         │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
 │    │         │    ├── key: (1)
 │    │         │    ├── fd: (8)-->(9), (1)-->(8,9)
 │    │         │    ├── scan a
 │    │         │    │    ├── columns: k:1!null
 │    │         │    │    └── key: (1)
 │    │         │    ├── scan uv
 │    │         │    │    ├── columns: u:8!null v:9
 │    │         │    │    ├── key: (8)
 │    │         │    │    └── fd: (8)-->(9)
 │    │         │    └── filters
 │    │         │         └── u:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]
 │    │         ├── scan xy
 │    │         │    ├── columns: xy.x:13!null
 │    │         │    └── key: (13)
 │    │         └── filters
 │    │              └── xy.x:13 = k:1 [outer=(1,13), constraints=(/1: (/NULL - ]; /13: (/NULL - ]), fd=(1)==(13), (13)==(1)]
 │    └── zip
 │         ├── generate_series(1, v:9) [outer=(9), immutable]
 │         └── information_schema._pg_expandarray(ARRAY[xy.x:13]) [outer=(13), immutable]
 └── projections
      └── ((x:17, n:18) AS x, n) [as=information_schema._pg_expandarray:19, outer=(17,18)]

norm expect=HoistProjectSetSubquery
SELECT a, generate_series(1, (SELECT a)) FROM (VALUES (1)) AS v (a)
----
project
 ├── columns: a:1!null generate_series:3
 ├── immutable
 ├── fd: ()-->(1)
 └── project-set
      ├── columns: column1:1!null a:2!null generate_series:3
      ├── immutable
      ├── fd: ()-->(1,2)
      ├── values
      │    ├── columns: column1:1!null a:2!null
      │    ├── cardinality: [1 - 1]
      │    ├── key: ()
      │    ├── fd: ()-->(1,2)
      │    └── (1, 1)
      └── zip
           └── generate_series(1, a:2) [outer=(2), immutable]

norm expect=HoistProjectSetSubquery
SELECT a, generate_series(1, (SELECT a)), generate_series(1, (SELECT a)) FROM (VALUES (1)) AS v (a)
----
project
 ├── columns: a:1!null generate_series:3 generate_series:5
 ├── immutable
 ├── fd: ()-->(1)
 └── project-set
      ├── columns: column1:1!null a:2!null generate_series:3 a:4!null generate_series:5
      ├── immutable
      ├── fd: ()-->(1,2,4)
      ├── values
      │    ├── columns: column1:1!null a:2!null a:4!null
      │    ├── cardinality: [1 - 1]
      │    ├── key: ()
      │    ├── fd: ()-->(1,2,4)
      │    └── (1, 1, 1)
      └── zip
           ├── generate_series(1, a:2) [outer=(2), immutable]
           └── generate_series(1, a:4) [outer=(4), immutable]

exec-ddl
CREATE TABLE articles (
  id INT PRIMARY KEY,
  body STRING,
  description STRING,
  title STRING,
  slug STRING,
  tag_list STRING[],
  user_id STRING,
  created_at TIMESTAMP,
  updated_at TIMESTAMP
)
----

# Regression test for #31706.
norm expect=(TryDecorrelateSemiJoin,TryDecorrelateProjectSet)
SELECT a0.id, a0.body, a0.description, a0.title, a0.slug, a0.tag_list, a0.user_id, a0.created_at, a0.updated_at
    FROM articles AS a0
   WHERE EXISTS(SELECT * FROM unnest(a0.tag_list) AS tag WHERE tag = 'dragons')
ORDER BY a0.created_at
   LIMIT 10
  OFFSET 0;
----
limit
 ├── columns: id:1!null body:2 description:3 title:4 slug:5 tag_list:6 user_id:7 created_at:8 updated_at:9
 ├── internal-ordering: +8
 ├── cardinality: [0 - 10]
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-9)
 ├── ordering: +8
 ├── sort
 │    ├── columns: id:1!null body:2 description:3 title:4 slug:5 tag_list:6 user_id:7 created_at:8 updated_at:9
 │    ├── immutable
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-9)
 │    ├── ordering: +8
 │    ├── limit hint: 10.00
 │    └── group-by (hash)
 │         ├── columns: id:1!null body:2 description:3 title:4 slug:5 tag_list:6 user_id:7 created_at:8 updated_at:9
 │         ├── grouping columns: id:1!null
 │         ├── immutable
 │         ├── key: (1)
 │         ├── fd: (1)-->(2-9)
 │         ├── select
 │         │    ├── columns: id:1!null body:2 description:3 title:4 slug:5 tag_list:6 user_id:7 created_at:8 updated_at:9 unnest:12!null
 │         │    ├── immutable
 │         │    ├── fd: ()-->(12), (1)-->(2-9)
 │         │    ├── project-set
 │         │    │    ├── columns: id:1!null body:2 description:3 title:4 slug:5 tag_list:6 user_id:7 created_at:8 updated_at:9 unnest:12
 │         │    │    ├── immutable
 │         │    │    ├── fd: (1)-->(2-9)
 │         │    │    ├── scan articles [as=a0]
 │         │    │    │    ├── columns: id:1!null body:2 description:3 title:4 slug:5 tag_list:6 user_id:7 created_at:8 updated_at:9
 │         │    │    │    ├── key: (1)
 │         │    │    │    └── fd: (1)-->(2-9)
 │         │    │    └── zip
 │         │    │         └── unnest(tag_list:6) [outer=(6), immutable]
 │         │    └── filters
 │         │         └── unnest:12 = 'dragons' [outer=(12), constraints=(/12: [/'dragons' - /'dragons']; tight), fd=()-->(12)]
 │         └── aggregations
 │              ├── const-agg [as=body:2, outer=(2)]
 │              │    └── body:2
 │              ├── const-agg [as=description:3, outer=(3)]
 │              │    └── description:3
 │              ├── const-agg [as=title:4, outer=(4)]
 │              │    └── title:4
 │              ├── const-agg [as=slug:5, outer=(5)]
 │              │    └── slug:5
 │              ├── const-agg [as=tag_list:6, outer=(6)]
 │              │    └── tag_list:6
 │              ├── const-agg [as=user_id:7, outer=(7)]
 │              │    └── user_id:7
 │              ├── const-agg [as=created_at:8, outer=(8)]
 │              │    └── created_at:8
 │              └── const-agg [as=updated_at:9, outer=(9)]
 │                   └── updated_at:9
 └── 10

# TODO(justin): figure out how to get this to decorrelate again.
norm
SELECT * FROM articles, xy WHERE EXISTS(
  SELECT * FROM ROWS FROM (generate_series(x, id), length(title), upper(title), unnest(tag_list))
)
----
project
 ├── columns: id:1!null body:2 description:3 title:4 slug:5 tag_list:6 user_id:7 created_at:8 updated_at:9 x:12!null y:13
 ├── immutable
 ├── key: (1,12)
 ├── fd: (1)-->(2-9), (1,12)-->(2-9,13)
 └── select
      ├── columns: id:1!null body:2 description:3 title:4 slug:5 tag_list:6 user_id:7 created_at:8 updated_at:9 x:12!null y:13 canary_agg:22!null
      ├── immutable
      ├── key: (1,12)
      ├── fd: (1)-->(2-9), (1,12)-->(2-9,13,22)
      ├── group-by (hash)
      │    ├── columns: id:1!null body:2 description:3 title:4 slug:5 tag_list:6 user_id:7 created_at:8 updated_at:9 x:12!null y:13 canary_agg:22
      │    ├── grouping columns: id:1!null x:12!null
      │    ├── immutable
      │    ├── key: (1,12)
      │    ├── fd: (1)-->(2-9), (1,12)-->(2-9,13,22)
      │    ├── inner-join-apply
      │    │    ├── columns: id:1!null body:2 description:3 title:4 slug:5 tag_list:6 user_id:7 created_at:8 updated_at:9 x:12!null y:13 canary:21
      │    │    ├── immutable
      │    │    ├── fd: (1)-->(2-9), (1,12)-->(13)
      │    │    ├── scan articles
      │    │    │    ├── columns: id:1!null body:2 description:3 title:4 slug:5 tag_list:6 user_id:7 created_at:8 updated_at:9
      │    │    │    ├── key: (1)
      │    │    │    └── fd: (1)-->(2-9)
      │    │    ├── left-join-apply
      │    │    │    ├── columns: x:12!null y:13 canary:21
      │    │    │    ├── outer: (1,4,6)
      │    │    │    ├── immutable
      │    │    │    ├── fd: (12)-->(13)
      │    │    │    ├── scan xy
      │    │    │    │    ├── columns: x:12!null y:13
      │    │    │    │    ├── key: (12)
      │    │    │    │    └── fd: (12)-->(13)
      │    │    │    ├── project
      │    │    │    │    ├── columns: canary:21!null
      │    │    │    │    ├── outer: (1,4,6,12)
      │    │    │    │    ├── immutable
      │    │    │    │    ├── fd: ()-->(21)
      │    │    │    │    ├── project-set
      │    │    │    │    │    ├── columns: generate_series:16 length:17 upper:18 unnest:19
      │    │    │    │    │    ├── outer: (1,4,6,12)
      │    │    │    │    │    ├── immutable
      │    │    │    │    │    ├── values
      │    │    │    │    │    │    ├── cardinality: [1 - 1]
      │    │    │    │    │    │    ├── key: ()
      │    │    │    │    │    │    └── ()
      │    │    │    │    │    └── zip
      │    │    │    │    │         ├── generate_series(x:12, id:1) [outer=(1,12), immutable]
      │    │    │    │    │         ├── length(title:4) [outer=(4), immutable]
      │    │    │    │    │         ├── upper(title:4) [outer=(4), immutable]
      │    │    │    │    │         └── unnest(tag_list:6) [outer=(6), immutable]
      │    │    │    │    └── projections
      │    │    │    │         └── true [as=canary:21]
      │    │    │    └── filters (true)
      │    │    └── filters (true)
      │    └── aggregations
      │         ├── const-not-null-agg [as=canary_agg:22, outer=(21)]
      │         │    └── canary:21
      │         ├── const-agg [as=y:13, outer=(13)]
      │         │    └── y:13
      │         ├── const-agg [as=body:2, outer=(2)]
      │         │    └── body:2
      │         ├── const-agg [as=description:3, outer=(3)]
      │         │    └── description:3
      │         ├── const-agg [as=title:4, outer=(4)]
      │         │    └── title:4
      │         ├── const-agg [as=slug:5, outer=(5)]
      │         │    └── slug:5
      │         ├── const-agg [as=tag_list:6, outer=(6)]
      │         │    └── tag_list:6
      │         ├── const-agg [as=user_id:7, outer=(7)]
      │         │    └── user_id:7
      │         ├── const-agg [as=created_at:8, outer=(8)]
      │         │    └── created_at:8
      │         └── const-agg [as=updated_at:9, outer=(9)]
      │              └── updated_at:9
      └── filters
           └── canary_agg:22 IS NOT NULL [outer=(22), constraints=(/22: (/NULL - ]; tight)]

norm expect=TryDecorrelateProjectSet
SELECT id FROM articles WHERE title = ANY(
  SELECT unnest FROM ROWS FROM (upper(title), unnest(tag_list), generate_series(0,1), lower('ABC'))
)
----
distinct-on
 ├── columns: id:1!null
 ├── grouping columns: id:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: id:1!null title:4!null tag_list:6 upper:12 unnest:13!null generate_series:14 lower:15
      ├── immutable
      ├── fd: (1)-->(4,6), (4)==(13), (13)==(4)
      ├── project-set
      │    ├── columns: id:1!null title:4 tag_list:6 upper:12 unnest:13 generate_series:14 lower:15
      │    ├── immutable
      │    ├── fd: (1)-->(4,6)
      │    ├── scan articles
      │    │    ├── columns: id:1!null title:4 tag_list:6
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(4,6)
      │    └── zip
      │         ├── upper(title:4) [outer=(4), immutable]
      │         ├── unnest(tag_list:6) [outer=(6), immutable]
      │         ├── generate_series(0, 1) [immutable]
      │         └── 'abc'
      └── filters
           └── title:4 = unnest:13 [outer=(4,13), constraints=(/4: (/NULL - ]; /13: (/NULL - ]), fd=(4)==(13), (13)==(4)]

# --------------------------------------------------
# NormalizeSelectAnyFilter + NormalizeJoinAnyFilter
# --------------------------------------------------
norm expect=NormalizeSelectAnyFilter
SELECT * FROM a WHERE i IN (SELECT y FROM xy)
----
semi-join (hash)
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── scan xy
 │    └── columns: y:9
 └── filters
      └── i:2 = y:9 [outer=(2,9), constraints=(/2: (/NULL - ]; /9: (/NULL - ]), fd=(2)==(9), (9)==(2)]

# Any is one of several conjuncts.
norm expect=NormalizeSelectAnyFilter
SELECT * FROM a WHERE k=10 AND i < ANY(SELECT y FROM xy) AND s='foo'
----
semi-join (cross)
 ├── columns: k:1!null i:2 f:3 s:4!null j:5
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(1-5)
 ├── select
 │    ├── columns: k:1!null i:2 f:3 s:4!null j:5
 │    ├── cardinality: [0 - 1]
 │    ├── key: ()
 │    ├── fd: ()-->(1-5)
 │    ├── scan a
 │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2-5)
 │    └── filters
 │         ├── k:1 = 10 [outer=(1), constraints=(/1: [/10 - /10]; tight), fd=()-->(1)]
 │         └── s:4 = 'foo' [outer=(4), constraints=(/4: [/'foo' - /'foo']; tight), fd=()-->(4)]
 ├── scan xy
 │    └── columns: y:9
 └── filters
      └── i:2 < y:9 [outer=(2,9), constraints=(/2: (/NULL - ]; /9: (/NULL - ])]

# Multiple ANY conjuncts.
norm expect=NormalizeSelectAnyFilter
SELECT * FROM a WHERE i < ANY(SELECT y FROM xy) AND s = ANY(SELECT y::string FROM xy)
----
semi-join (cross)
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── semi-join (hash)
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── immutable
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    ├── scan a
 │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2-5)
 │    ├── project
 │    │    ├── columns: y:16
 │    │    ├── immutable
 │    │    ├── scan xy
 │    │    │    └── columns: xy.y:13
 │    │    └── projections
 │    │         └── xy.y:13::STRING [as=y:16, outer=(13), immutable]
 │    └── filters
 │         └── s:4 = y:16 [outer=(4,16), constraints=(/4: (/NULL - ]; /16: (/NULL - ]), fd=(4)==(16), (16)==(4)]
 ├── scan xy
 │    └── columns: xy.y:9
 └── filters
      └── i:2 < xy.y:9 [outer=(2,9), constraints=(/2: (/NULL - ]; /9: (/NULL - ])]

# Don't hoist uncorrelated ANY (but rewrite it to EXISTS).
norm expect=NormalizeSelectAnyFilter
SELECT * FROM a WHERE 5 IN (SELECT y FROM xy)
----
select
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 └── filters
      └── coalesce [subquery]
           ├── subquery
           │    └── project
           │         ├── columns: column13:13!null
           │         ├── cardinality: [0 - 1]
           │         ├── key: ()
           │         ├── fd: ()-->(13)
           │         ├── limit
           │         │    ├── columns: y:9!null
           │         │    ├── cardinality: [0 - 1]
           │         │    ├── key: ()
           │         │    ├── fd: ()-->(9)
           │         │    ├── select
           │         │    │    ├── columns: y:9!null
           │         │    │    ├── fd: ()-->(9)
           │         │    │    ├── limit hint: 1.00
           │         │    │    ├── scan xy
           │         │    │    │    ├── columns: y:9
           │         │    │    │    └── limit hint: 100.00
           │         │    │    └── filters
           │         │    │         └── y:9 = 5 [outer=(9), constraints=(/9: [/5 - /5]; tight), fd=()-->(9)]
           │         │    └── 1
           │         └── projections
           │              └── true [as=column13:13]
           └── false

# ANY in Join On condition.
norm expect=NormalizeJoinAnyFilter
SELECT * FROM a INNER JOIN xy ON i IN (SELECT v FROM uv) AND k=x
----
inner-join (hash)
 ├── columns: k:1!null i:2 f:3 s:4 j:5 x:8!null y:9
 ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one)
 ├── key: (8)
 ├── fd: (1)-->(2-5), (8)-->(9), (1)==(8), (8)==(1)
 ├── semi-join (hash)
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    ├── scan a
 │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2-5)
 │    ├── scan uv
 │    │    └── columns: v:13
 │    └── filters
 │         └── i:2 = v:13 [outer=(2,13), constraints=(/2: (/NULL - ]; /13: (/NULL - ]), fd=(2)==(13), (13)==(2)]
 ├── scan xy
 │    ├── columns: x:8!null y:9
 │    ├── key: (8)
 │    └── fd: (8)-->(9)
 └── filters
      └── k:1 = x:8 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]

# --------------------------------------------------
# NormalizeSelectNotAnyFilter + NormalizeJoinNotAnyFilter
# --------------------------------------------------
norm expect=NormalizeSelectNotAnyFilter
SELECT * FROM a WHERE i NOT IN (SELECT y FROM xy)
----
anti-join (cross)
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 ├── scan xy
 │    └── columns: y:9
 └── filters
      └── (i:2 = y:9) IS NOT false [outer=(2,9)]

# NOT ANY is one of several conjuncts. Note that i > ALL(...) gets mapped to
# NOT i <= ANY(...) by optbuilder.
norm expect=NormalizeSelectNotAnyFilter
SELECT * FROM a WHERE k > 1 AND k < 5 AND i > ALL(SELECT y FROM xy)
----
anti-join (cross)
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── cardinality: [0 - 3]
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── select
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── cardinality: [0 - 3]
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    ├── scan a
 │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2-5)
 │    └── filters
 │         └── (k:1 > 1) AND (k:1 < 5) [outer=(1), constraints=(/1: [/2 - /4]; tight)]
 ├── scan xy
 │    └── columns: y:9
 └── filters
      └── (i:2 <= y:9) IS NOT false [outer=(2,9)]

# Multiple NOT ANY conjuncts.
norm expect=NormalizeSelectNotAnyFilter
SELECT * FROM a WHERE i < ALL(SELECT y FROM xy) AND s <> ALL(SELECT y::string FROM xy)
----
anti-join (cross)
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── anti-join (cross)
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── immutable
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    ├── scan a
 │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2-5)
 │    ├── project
 │    │    ├── columns: y:16
 │    │    ├── immutable
 │    │    ├── scan xy
 │    │    │    └── columns: xy.y:13
 │    │    └── projections
 │    │         └── xy.y:13::STRING [as=y:16, outer=(13), immutable]
 │    └── filters
 │         └── (s:4 = y:16) IS NOT false [outer=(4,16)]
 ├── scan xy
 │    └── columns: xy.y:9
 └── filters
      └── (i:2 >= xy.y:9) IS NOT false [outer=(2,9)]

# Don't hoist uncorrelated NOT ANY (but rewrite it to NOT EXISTS).
norm expect=NormalizeSelectNotAnyFilter
SELECT * FROM a WHERE 5 NOT IN (SELECT y FROM xy)
----
select
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 └── filters
      └── not [subquery]
           └── coalesce
                ├── subquery
                │    └── project
                │         ├── columns: column13:13!null
                │         ├── cardinality: [0 - 1]
                │         ├── key: ()
                │         ├── fd: ()-->(13)
                │         ├── limit
                │         │    ├── columns: y:9
                │         │    ├── cardinality: [0 - 1]
                │         │    ├── key: ()
                │         │    ├── fd: ()-->(9)
                │         │    ├── select
                │         │    │    ├── columns: y:9
                │         │    │    ├── limit hint: 1.00
                │         │    │    ├── scan xy
                │         │    │    │    ├── columns: y:9
                │         │    │    │    └── limit hint: 3.00
                │         │    │    └── filters
                │         │    │         └── (y:9 = 5) IS NOT false [outer=(9)]
                │         │    └── 1
                │         └── projections
                │              └── true [as=column13:13]
                └── false

# NOT ANY in Join On condition.
norm expect=NormalizeJoinNotAnyFilter
SELECT * FROM a INNER JOIN xy ON i NOT IN (SELECT v FROM uv) AND k=x
----
inner-join (hash)
 ├── columns: k:1!null i:2 f:3 s:4 j:5 x:8!null y:9
 ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one)
 ├── key: (8)
 ├── fd: (1)-->(2-5), (8)-->(9), (1)==(8), (8)==(1)
 ├── anti-join (cross)
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    ├── scan a
 │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2-5)
 │    ├── scan uv
 │    │    └── columns: v:13
 │    └── filters
 │         └── (i:2 = v:13) IS NOT false [outer=(2,13)]
 ├── scan xy
 │    ├── columns: x:8!null y:9
 │    ├── key: (8)
 │    └── fd: (8)-->(9)
 └── filters
      └── k:1 = x:8 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]

# --------------------------------------------------
# NormalizeSelectAnyFilter + NormalizeSelectNotAnyFilter
# --------------------------------------------------
norm expect=(NormalizeSelectAnyFilter,NormalizeSelectNotAnyFilter)
SELECT * FROM a WHERE i = ANY(SELECT y FROM xy) AND s <> ALL(SELECT y::string FROM xy)
----
semi-join (hash)
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── anti-join (cross)
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── immutable
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    ├── scan a
 │    │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2-5)
 │    ├── project
 │    │    ├── columns: y:16
 │    │    ├── immutable
 │    │    ├── scan xy
 │    │    │    └── columns: xy.y:13
 │    │    └── projections
 │    │         └── xy.y:13::STRING [as=y:16, outer=(13), immutable]
 │    └── filters
 │         └── (s:4 = y:16) IS NOT false [outer=(4,16)]
 ├── scan xy
 │    └── columns: xy.y:9
 └── filters
      └── i:2 = xy.y:9 [outer=(2,9), constraints=(/2: (/NULL - ]; /9: (/NULL - ]), fd=(2)==(9), (9)==(2)]

# --------------------------------------------------
# EnsureKey
# --------------------------------------------------

# Check that when the EnsureKey function is called on a Scan that has pruned its
# key away, it creates a new Scan with the primary key added back rather than
# introducing an ordinality operator.
#
# In this test case, the key column of a is pruned away from the Scan, but when
# TryDecorrelateLimitOne calls EnsureKey on the Scan, the key is added back.
norm
SELECT (SELECT x FROM xy WHERE y=i LIMIT 1) FROM a
----
project
 ├── columns: x:12
 ├── distinct-on
 │    ├── columns: k:1!null xy.x:8
 │    ├── grouping columns: k:1!null
 │    ├── key: (1)
 │    ├── fd: (1)-->(8)
 │    ├── left-join (hash)
 │    │    ├── columns: k:1!null i:2 xy.x:8 y:9
 │    │    ├── key: (1,8)
 │    │    ├── fd: (1)-->(2), (8)-->(9)
 │    │    ├── scan a
 │    │    │    ├── columns: k:1!null i:2
 │    │    │    ├── key: (1)
 │    │    │    └── fd: (1)-->(2)
 │    │    ├── scan xy
 │    │    │    ├── columns: xy.x:8!null y:9
 │    │    │    ├── key: (8)
 │    │    │    └── fd: (8)-->(9)
 │    │    └── filters
 │    │         └── y:9 = i:2 [outer=(2,9), constraints=(/2: (/NULL - ]; /9: (/NULL - ]), fd=(2)==(9), (9)==(2)]
 │    └── aggregations
 │         └── first-agg [as=xy.x:8, outer=(8)]
 │              └── xy.x:8
 └── projections
      └── xy.x:8 [as=x:12, outer=(8)]

# Case where EnsureKey retrieves an implicit key to add to the Scan.
norm
SELECT (SELECT x FROM xy WHERE y=b LIMIT 1) FROM ab
----
project
 ├── columns: x:10
 ├── distinct-on
 │    ├── columns: rowid:3!null xy.x:6
 │    ├── grouping columns: rowid:3!null
 │    ├── key: (3)
 │    ├── fd: (3)-->(6)
 │    ├── left-join (hash)
 │    │    ├── columns: b:2 rowid:3!null xy.x:6 y:7
 │    │    ├── key: (3,6)
 │    │    ├── fd: (3)-->(2), (6)-->(7)
 │    │    ├── scan ab
 │    │    │    ├── columns: b:2 rowid:3!null
 │    │    │    ├── key: (3)
 │    │    │    └── fd: (3)-->(2)
 │    │    ├── scan xy
 │    │    │    ├── columns: xy.x:6!null y:7
 │    │    │    ├── key: (6)
 │    │    │    └── fd: (6)-->(7)
 │    │    └── filters
 │    │         └── y:7 = b:2 [outer=(2,7), constraints=(/2: (/NULL - ]; /7: (/NULL - ]), fd=(2)==(7), (7)==(2)]
 │    └── aggregations
 │         └── first-agg [as=xy.x:6, outer=(6)]
 │              └── xy.x:6
 └── projections
      └── xy.x:6 [as=x:10, outer=(6)]

# EnsureKey should construct an Ordinality operator when it is called on a Scan
# over a virtual table.
norm
SELECT (SELECT x FROM xy WHERE y=version LIMIT 1) FROM information_schema.tables
----
project
 ├── columns: x:12
 ├── distinct-on
 │    ├── columns: xy.x:8 rownum:13!null
 │    ├── grouping columns: rownum:13!null
 │    ├── key: (13)
 │    ├── fd: (13)-->(8)
 │    ├── left-join (hash)
 │    │    ├── columns: version:7 xy.x:8 y:9 rownum:13!null
 │    │    ├── key: (8,13)
 │    │    ├── fd: (13)-->(7), (8)-->(9)
 │    │    ├── ordinality
 │    │    │    ├── columns: version:7 rownum:13!null
 │    │    │    ├── key: (13)
 │    │    │    ├── fd: (13)-->(7)
 │    │    │    └── scan tables
 │    │    │         └── columns: version:7
 │    │    ├── scan xy
 │    │    │    ├── columns: xy.x:8!null y:9
 │    │    │    ├── key: (8)
 │    │    │    └── fd: (8)-->(9)
 │    │    └── filters
 │    │         └── y:9 = version:7 [outer=(7,9), constraints=(/7: (/NULL - ]; /9: (/NULL - ]), fd=(7)==(9), (9)==(7)]
 │    └── aggregations
 │         └── first-agg [as=xy.x:8, outer=(8)]
 │              └── xy.x:8
 └── projections
      └── xy.x:8 [as=x:12, outer=(8)]

# --------------------------------------------------
# TryRemapJoinOuterColsRight
# --------------------------------------------------

# Case with Select. Matched join is an InnerJoin.
norm expect=TryRemapJoinOuterColsRight format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT * FROM ab WHERE b = x) ON a = x
----
inner-join (hash)
 ├── scan xy
 ├── select
 │    ├── scan ab
 │    └── filters
 │         └── b = a
 └── filters
      └── a = x

# Case with Select. Matched join is a LeftJoin.
norm expect=TryRemapJoinOuterColsRight format=hide-all
SELECT * FROM xy LEFT JOIN LATERAL (SELECT * FROM ab WHERE b = x) ON a = x
----
left-join (hash)
 ├── scan xy
 ├── select
 │    ├── scan ab
 │    └── filters
 │         └── b = a
 └── filters
      └── a = x

# Case with Project.
norm expect=TryRemapJoinOuterColsRight format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT a, a+x FROM ab) ON a = x
----
inner-join (hash)
 ├── scan xy
 ├── project
 │    ├── scan ab
 │    └── projections
 │         └── a + a
 └── filters
      └── a = x

# Case with ProjectSet.
norm expect=TryRemapJoinOuterColsRight disable=ConvertZipArraysToValues format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT a, unnest(ARRAY[x, b]) FROM ab) ON a = x
----
project
 └── inner-join (hash)
      ├── scan xy
      ├── project-set
      │    ├── scan ab
      │    └── zip
      │         └── unnest(ARRAY[a, b])
      └── filters
           └── a = x

# Case with an InnerJoin.
norm expect=TryRemapJoinOuterColsRight format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT * FROM (SELECT a, b, b+x FROM ab) INNER JOIN uv ON b = u) ON a = x
----
inner-join (hash)
 ├── scan xy
 ├── inner-join (hash)
 │    ├── project
 │    │    ├── scan ab
 │    │    └── projections
 │    │         └── b + a
 │    ├── scan uv
 │    └── filters
 │         └── b = u
 └── filters
      └── a = x

# Case with an InnerJoin. The outer column is in the join filters.
norm expect=TryRemapJoinOuterColsRight format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT * FROM ab INNER JOIN uv ON b = u OR b = x) ON a = x
----
inner-join (hash)
 ├── scan xy
 ├── inner-join (cross)
 │    ├── scan ab
 │    ├── scan uv
 │    └── filters
 │         └── (b = u) OR (b = a)
 └── filters
      └── a = x

# Case with a LeftJoin.
norm expect=TryRemapJoinOuterColsRight format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT * FROM (SELECT a, b, b+x FROM ab) LEFT JOIN uv ON b = u) ON a = x
----
inner-join (hash)
 ├── scan xy
 ├── left-join (hash)
 │    ├── project
 │    │    ├── scan ab
 │    │    └── projections
 │    │         └── b + a
 │    ├── scan uv
 │    └── filters
 │         └── b = u
 └── filters
      └── a = x

# Case with a LeftJoin. The outer column is in the join filters.
norm expect=TryRemapJoinOuterColsRight format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT * FROM ab LEFT JOIN uv ON b = u OR b = x) ON a = x
----
inner-join (hash)
 ├── scan xy
 ├── left-join (cross)
 │    ├── scan ab
 │    ├── scan uv
 │    └── filters
 │         └── (b = u) OR (b = a)
 └── filters
      └── a = x

# Case with a LeftJoin. The filter can be mapped to the right input.
norm expect=TryRemapJoinOuterColsRight format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT * FROM ab LEFT JOIN (SELECT u, v + x FROM uv) ON a = u) ON a = x
----
inner-join (hash)
 ├── scan xy
 ├── left-join (hash)
 │    ├── scan ab
 │    ├── project
 │    │    ├── scan uv
 │    │    └── projections
 │    │         └── v + u
 │    └── filters
 │         └── a = u
 └── filters
      └── a = x

# Case with a LeftJoin. Both inputs have an outer column reference that can be
# remapped.
norm expect=TryRemapJoinOuterColsRight format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (
  SELECT * FROM (SELECT a, b, b+x FROM ab) LEFT JOIN (SELECT u, v + x FROM uv) ON a = u
) ON a = x
----
inner-join (hash)
 ├── scan xy
 ├── left-join (hash)
 │    ├── project
 │    │    ├── scan ab
 │    │    └── projections
 │    │         └── b + a
 │    ├── project
 │    │    ├── scan uv
 │    │    └── projections
 │    │         └── v + u
 │    └── filters
 │         └── a = u
 └── filters
      └── a = x

# Case with a SemiJoin.
norm expect=TryRemapJoinOuterColsRight disable=TryDecorrelateSelect format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT * FROM (SELECT a, b+x AS c FROM ab) WHERE EXISTS (SELECT * FROM uv WHERE c = u)) ON a = x
----
inner-join (hash)
 ├── scan xy
 ├── semi-join-apply
 │    ├── project
 │    │    ├── scan ab
 │    │    └── projections
 │    │         └── b + a
 │    ├── select
 │    │    ├── scan uv
 │    │    └── filters
 │    │         └── c = u
 │    └── filters (true)
 └── filters
      └── a = x

# Case with a SemiJoin. The outer column is in the join filters.
norm expect=TryRemapJoinOuterColsRight format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT * FROM ab WHERE EXISTS (SELECT * FROM uv WHERE a = u OR v = x)) ON a = x
----
inner-join (hash)
 ├── scan xy
 ├── semi-join (cross)
 │    ├── scan ab
 │    ├── scan uv
 │    └── filters
 │         └── (a = u) OR (v = a)
 └── filters
      └── a = x

# Case with an AntiJoin.
norm expect=TryRemapJoinOuterColsRight format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT * FROM (SELECT a, b+x AS c FROM ab) WHERE NOT EXISTS (SELECT * FROM uv WHERE c = u)) ON a = x
----
inner-join (hash)
 ├── scan xy
 ├── anti-join (hash)
 │    ├── project
 │    │    ├── scan ab
 │    │    └── projections
 │    │         └── b + a
 │    ├── scan uv
 │    └── filters
 │         └── c = u
 └── filters
      └── a = x

# Case with an AntiJoin. The outer column is in the join filters.
norm expect=TryRemapJoinOuterColsRight format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT * FROM ab WHERE NOT EXISTS (SELECT * FROM uv WHERE a = u OR v = x)) ON a = x
----
inner-join (hash)
 ├── scan xy
 ├── anti-join (cross)
 │    ├── scan ab
 │    ├── scan uv
 │    └── filters
 │         └── (a = u) OR (v = a)
 └── filters
      └── a = x

# Case with a GroupBy. Equality references the grouping column.
norm expect=TryRemapJoinOuterColsRight format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT a, corr(b, x) FROM ab GROUP BY a) ON a = x
----
inner-join (hash)
 ├── scan xy
 ├── group-by (hash)
 │    ├── project
 │    │    ├── scan ab
 │    │    └── projections
 │    │         └── a
 │    └── aggregations
 │         └── corr
 │              ├── b
 │              └── x
 └── filters
      └── a = xy.x

# Case with a DistinctOn.
norm expect=TryRemapJoinOuterColsRight format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT DISTINCT ON (a) * FROM (SELECT *, b+x FROM ab)) ON a = x
----
inner-join (hash)
 ├── scan xy
 ├── distinct-on
 │    ├── project
 │    │    ├── scan ab
 │    │    └── projections
 │    │         └── b + a
 │    └── aggregations
 │         ├── first-agg
 │         │    └── b
 │         └── first-agg
 │              └── "?column?"
 └── filters
      └── a = x

# Case with a Union. The alias here is not necessary, but makes it more explicit
# that the "a = x" filter refers to the first output column of the Union, rather
# than just the left input.
norm expect=TryRemapJoinOuterColsRight format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT * FROM ab UNION (SELECT u, v+x FROM uv)) foo(a, b) ON foo.a = x
----
inner-join (hash)
 ├── scan xy
 ├── union
 │    ├── scan ab
 │    └── project
 │         ├── scan uv
 │         └── projections
 │              └── v + u
 └── filters
      └── a = x

# Case with a Union. Both inputs should be remapped.
norm expect=TryRemapJoinOuterColsRight format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT * FROM (SELECT a, b+x FROM ab) UNION (SELECT u, v+x FROM uv)) foo(a, b) ON foo.a = x
----
inner-join (hash)
 ├── scan xy
 ├── union
 │    ├── project
 │    │    ├── scan ab
 │    │    └── projections
 │    │         └── b + ab.a
 │    └── project
 │         ├── scan uv
 │         └── projections
 │              └── v + u
 └── filters
      └── a = x

# Case with an Intersect.
norm expect=TryRemapJoinOuterColsRight format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT * FROM ab INTERSECT (SELECT u, v+x FROM uv)) foo(a, b) ON foo.a = x
----
inner-join (hash)
 ├── scan xy
 ├── intersect-all
 │    ├── scan ab
 │    └── project
 │         ├── scan uv
 │         └── projections
 │              └── v + u
 └── filters
      └── a = x

# Case with an Intersect. Both inputs should be remapped.
norm format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT * FROM (SELECT a, b+x FROM ab) INTERSECT (SELECT u, v+x FROM uv)) foo(a, b) ON foo.a = x
----
inner-join (hash)
 ├── scan xy
 ├── intersect-all
 │    ├── project
 │    │    ├── scan ab
 │    │    └── projections
 │    │         └── b + a
 │    └── project
 │         ├── scan uv
 │         └── projections
 │              └── v + u
 └── filters
      └── a = x

# Case with an Except.
norm expect=TryRemapJoinOuterColsRight format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT * FROM ab EXCEPT (SELECT u, v+x FROM uv)) foo(a, b) ON foo.a = x
----
inner-join (hash)
 ├── scan xy
 ├── except
 │    ├── scan ab
 │    └── project
 │         ├── scan uv
 │         └── projections
 │              └── v + u
 └── filters
      └── a = x

# Case with an Except. Both inputs should be remapped.
norm format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT * FROM (SELECT a, b+x FROM ab) EXCEPT (SELECT u, v+x FROM uv)) foo(a, b) ON foo.a = x
----
inner-join (hash)
 ├── scan xy
 ├── except
 │    ├── project
 │    │    ├── scan ab
 │    │    └── projections
 │    │         └── b + a
 │    └── project
 │         ├── scan uv
 │         └── projections
 │              └── v + u
 └── filters
      └── a = x

# No-op case with a LeftJoin. The filter cannot be mapped to the right input,
# because there is no suitable equality between left and right columns.
norm expect-not=TryRemapJoinOuterColsRight format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT * FROM ab LEFT JOIN (SELECT u, v + x FROM uv) ON a <> u) ON a = x
----
left-join-apply
 ├── inner-join (hash)
 │    ├── scan xy
 │    ├── scan ab
 │    └── filters
 │         └── a = x
 ├── project
 │    ├── scan uv
 │    └── projections
 │         └── v + x
 └── filters
      └── a != u

# No-op case with FullJoin.
norm expect-not=TryRemapJoinOuterColsRight format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT * FROM (SELECT a, b, b+x FROM ab) FULL JOIN uv ON a = x) ON True
----
inner-join-apply
 ├── scan xy
 ├── full-join (cross)
 │    ├── project
 │    │    ├── scan ab
 │    │    └── projections
 │    │         └── b + x
 │    ├── scan uv
 │    └── filters
 │         └── a = x
 └── filters (true)

# No-op case because there is no outer-non-outer column equality.
norm expect-not=TryRemapJoinOuterColsRight format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT * FROM ab WHERE b = x) ON a <> x
----
inner-join (hash)
 ├── scan xy
 ├── select
 │    ├── scan ab
 │    └── filters
 │         └── a != b
 └── filters
      └── b = x

# No-op case because the equality is a disjunct.
norm expect-not=TryRemapJoinOuterColsRight format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT * FROM ab WHERE b = x) ON a = x OR x IS NULL
----
inner-join (hash)
 ├── scan xy
 ├── select
 │    ├── scan ab
 │    └── filters
 │         └── (a = b) OR (b IS NULL)
 └── filters
      └── b = x

# No-op case because the outer column is equal to a synthesized column, so push
# down is not possible below the Project.
norm expect-not=TryRemapJoinOuterColsRight format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT *, a+b AS c FROM ab WHERE b = x) ON c = x
----
project
 ├── inner-join (hash)
 │    ├── scan xy
 │    ├── select
 │    │    ├── scan ab
 │    │    └── filters
 │    │         └── b = (a + b)
 │    └── filters
 │         └── b = x
 └── projections
      └── a + b

# No-op case because the outer column is equal to a synthesized column, so push
# down is not possible below the GroupBy.
norm expect-not=TryRemapJoinOuterColsRight format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT v, corr(u, v) AS w FROM (SELECT *, u + x FROM uv) GROUP BY u, v) ON w = x
----
project
 └── inner-join (cross)
      ├── scan xy
      ├── group-by (hash)
      │    ├── scan uv
      │    └── aggregations
      │         ├── corr
      │         │    ├── u
      │         │    └── v
      │         └── const-agg
      │              └── v
      └── filters
           └── corr = x

# Regression test for #108057. Do not remap a column into an equivalent, but not
# identically typed column. The a + x projection should remain.
norm expect-not=(TryRemapJoinOuterColsRight,TryRemapJoinOuterColsLeft,TryRemapSelectOuterCols)
SELECT * FROM xy_decimal INNER JOIN LATERAL (SELECT a, a+x FROM ab_decimal) ON a = x
----
project
 ├── columns: x:1!null y:2 a:5!null "?column?":10!null
 ├── immutable
 ├── fd: (1)-->(2), (5)-->(10), (1)==(5), (5)==(1)
 ├── inner-join (hash)
 │    ├── columns: x:1!null y:2 a:5!null
 │    ├── multiplicity: left-rows(zero-or-more), right-rows(zero-or-one)
 │    ├── immutable
 │    ├── fd: (1)-->(2), (1)==(5), (5)==(1)
 │    ├── scan xy_decimal
 │    │    ├── columns: x:1!null y:2
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2)
 │    ├── scan ab_decimal
 │    │    └── columns: a:5
 │    └── filters
 │         └── a:5 = x:1 [outer=(1,5), immutable, constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
 └── projections
      └── a:5 + x:1 [as="?column?":10, outer=(1,5), immutable]

# Regression test for #130001. Do not remap columns under a GroupBy or
# DistinctOn unless the grouping columns include the equivalent non-outer
# column.
#
# Case with a GroupBy.
norm expect-not=TryRemapJoinOuterColsRight
SELECT * FROM xy INNER JOIN LATERAL (SELECT v, corr(u, x) FROM uv GROUP BY u, v) ON v = x
----
project
 ├── columns: x:1!null y:2 v:6!null corr:10
 ├── fd: (1)-->(2), (1)==(6), (6)==(1)
 └── group-by (hash)
      ├── columns: xy.x:1!null y:2 u:5!null v:6!null corr:10
      ├── grouping columns: u:5!null
      ├── key: (5)
      ├── fd: (1)-->(2), (5)-->(1,2,6,10), (1)==(6), (6)==(1)
      ├── project
      │    ├── columns: x:9!null xy.x:1!null y:2 u:5!null v:6!null
      │    ├── key: (5)
      │    ├── fd: (1)-->(2), (5)-->(6), (1)==(6,9), (6)==(1,9), (9)==(1,6)
      │    ├── inner-join (hash)
      │    │    ├── columns: xy.x:1!null y:2 u:5!null v:6!null
      │    │    ├── multiplicity: left-rows(zero-or-more), right-rows(zero-or-one)
      │    │    ├── key: (5)
      │    │    ├── fd: (1)-->(2), (5)-->(6), (1)==(6), (6)==(1)
      │    │    ├── scan xy
      │    │    │    ├── columns: xy.x:1!null y:2
      │    │    │    ├── key: (1)
      │    │    │    └── fd: (1)-->(2)
      │    │    ├── scan uv
      │    │    │    ├── columns: u:5!null v:6
      │    │    │    ├── key: (5)
      │    │    │    └── fd: (5)-->(6)
      │    │    └── filters
      │    │         └── v:6 = xy.x:1 [outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ]), fd=(1)==(6), (6)==(1)]
      │    └── projections
      │         └── xy.x:1 [as=x:9, outer=(1)]
      └── aggregations
           ├── corr [as=corr:10, outer=(5,9)]
           │    ├── u:5
           │    └── x:9
           ├── const-agg [as=v:6, outer=(6)]
           │    └── v:6
           ├── const-agg [as=y:2, outer=(2)]
           │    └── y:2
           └── const-agg [as=xy.x:1, outer=(1)]
                └── xy.x:1

# Case with a DistinctOn.
norm expect-not=TryRemapJoinOuterColsRight
SELECT * FROM xy INNER JOIN LATERAL (SELECT DISTINCT ON (a, foo) * FROM (SELECT 1 AS foo, *, b+x FROM ab)) ON foo = x
----
distinct-on
 ├── columns: x:1!null y:2 foo:10!null a:5 b:6 "?column?":11
 ├── grouping columns: a:5
 ├── immutable
 ├── key: (5)
 ├── fd: ()-->(1,2,10), (6)-->(11), (5)-->(1,2,6,10,11)
 ├── project
 │    ├── columns: foo:10!null "?column?":11 x:1!null y:2 a:5 b:6
 │    ├── immutable
 │    ├── fd: ()-->(1,2,10), (6)-->(11)
 │    ├── inner-join (cross)
 │    │    ├── columns: x:1!null y:2 a:5 b:6
 │    │    ├── multiplicity: left-rows(zero-or-more), right-rows(zero-or-one)
 │    │    ├── fd: ()-->(1,2)
 │    │    ├── select
 │    │    │    ├── columns: x:1!null y:2
 │    │    │    ├── cardinality: [0 - 1]
 │    │    │    ├── key: ()
 │    │    │    ├── fd: ()-->(1,2)
 │    │    │    ├── scan xy
 │    │    │    │    ├── columns: x:1!null y:2
 │    │    │    │    ├── key: (1)
 │    │    │    │    └── fd: (1)-->(2)
 │    │    │    └── filters
 │    │    │         └── x:1 = 1 [outer=(1), constraints=(/1: [/1 - /1]; tight), fd=()-->(1)]
 │    │    ├── scan ab
 │    │    │    └── columns: a:5 b:6
 │    │    └── filters (true)
 │    └── projections
 │         ├── 1 [as=foo:10]
 │         └── b:6 + x:1 [as="?column?":11, outer=(1,6), immutable]
 └── aggregations
      ├── first-agg [as=b:6, outer=(6)]
      │    └── b:6
      ├── first-agg [as="?column?":11, outer=(11)]
      │    └── "?column?":11
      ├── const-agg [as=foo:10, outer=(10)]
      │    └── foo:10
      ├── const-agg [as=y:2, outer=(2)]
      │    └── y:2
      └── const-agg [as=x:1, outer=(1)]
           └── x:1

# --------------------------------------------------
# TryRemapJoinOuterColsLeft
# --------------------------------------------------

# Case with InnerJoin.
norm expect=TryRemapJoinOuterColsLeft format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT * FROM (SELECT * FROM ab WHERE b = x) INNER JOIN uv ON a = x) ON True
----
inner-join (cross)
 ├── inner-join (hash)
 │    ├── scan xy
 │    ├── select
 │    │    ├── scan ab
 │    │    └── filters
 │    │         └── b = a
 │    └── filters
 │         └── a = x
 ├── scan uv
 └── filters (true)

# Case with SemiJoin.
norm expect=TryRemapJoinOuterColsLeft format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT * FROM (SELECT *, b+x FROM ab) WHERE EXISTS (SELECT * FROM uv WHERE a = x)) ON True
----
inner-join-apply
 ├── scan xy
 ├── semi-join (cross)
 │    ├── project
 │    │    ├── scan ab
 │    │    └── projections
 │    │         └── b + a
 │    ├── scan uv
 │    └── filters
 │         └── a = x
 └── filters (true)

# No-op case with LeftJoin.
norm expect-not=TryRemapJoinOuterColsLeft format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT * FROM (SELECT * FROM ab WHERE b = x) LEFT JOIN uv ON a = x) ON True
----
left-join (cross)
 ├── inner-join (hash)
 │    ├── scan xy
 │    ├── scan ab
 │    └── filters
 │         └── b = x
 ├── scan uv
 └── filters
      └── a = x

# No-op case with AntiJoin.
norm expect-not=TryRemapJoinOuterColsLeft format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT * FROM (SELECT *, b+x FROM ab) WHERE NOT EXISTS (SELECT * FROM uv WHERE a = x)) ON True
----
inner-join-apply
 ├── scan xy
 ├── anti-join (cross)
 │    ├── project
 │    │    ├── scan ab
 │    │    └── projections
 │    │         └── b + x
 │    ├── scan uv
 │    └── filters
 │         └── a = x
 └── filters (true)

# No-op case with FullJoin.
norm expect-not=TryRemapJoinOuterColsLeft format=hide-all
SELECT * FROM xy INNER JOIN LATERAL (SELECT * FROM (SELECT * FROM ab WHERE b = x) FULL JOIN uv ON a = x) ON True
----
inner-join-apply
 ├── scan xy
 ├── full-join (cross)
 │    ├── select
 │    │    ├── scan ab
 │    │    └── filters
 │    │         └── b = x
 │    ├── scan uv
 │    └── filters
 │         └── a = x
 └── filters (true)

# --------------------------------------------------
# TryRemapSelectOuterCols
# --------------------------------------------------

norm expect=TryRemapSelectOuterCols format=hide-all
SELECT * FROM xy LEFT JOIN LATERAL (SELECT * FROM (SELECT *, b+x FROM ab) WHERE a = x) ON True
----
left-join (hash)
 ├── scan xy
 ├── project
 │    ├── scan ab
 │    └── projections
 │         └── b + a
 └── filters
      └── a = x

# Case where the replacement leads to an "a = a" filter, which in turn gets
# simplified into "a IS DISTINCT FROM NULL".
norm expect=TryRemapSelectOuterCols format=hide-all
SELECT * FROM xy WHERE x IN (SELECT a FROM ab WHERE a = x);
----
semi-join (hash)
 ├── scan xy
 ├── select
 │    ├── scan ab
 │    └── filters
 │         └── a IS DISTINCT FROM CAST(NULL AS INT8)
 └── filters
      └── x = a

# --------------------------------------------------
# TryDecorrelateUnion
# --------------------------------------------------

# Case with UnionAll.
norm expect=TryDecorrelateUnion format=(hide-all,show-columns)
SELECT *, CASE WHEN EXISTS (SELECT 1 FROM xy WHERE x = k UNION ALL SELECT 1 FROM uv WHERE v = k) THEN 1 ELSE 0 END FROM a;
----
project
 ├── columns: k:1 i:2 f:3 s:4 j:5 case:20
 ├── group-by (hash)
 │    ├── columns: k:1 i:2 f:3 s:4 j:5 "?column?":12 "?column?":17
 │    ├── grouping columns: k:1
 │    ├── left-join (hash)
 │    │    ├── columns: k:1 i:2 f:3 s:4 j:5 x:8 "?column?":12 v:14 "?column?":17
 │    │    ├── left-join (hash)
 │    │    │    ├── columns: k:1 i:2 f:3 s:4 j:5 x:8 "?column?":12
 │    │    │    ├── scan a
 │    │    │    │    └── columns: k:1 i:2 f:3 s:4 j:5
 │    │    │    ├── project
 │    │    │    │    ├── columns: "?column?":12 x:8
 │    │    │    │    ├── scan xy
 │    │    │    │    │    └── columns: x:8
 │    │    │    │    └── projections
 │    │    │    │         └── 1 [as="?column?":12]
 │    │    │    └── filters
 │    │    │         └── x:8 = k:1
 │    │    ├── project
 │    │    │    ├── columns: "?column?":17 v:14
 │    │    │    ├── scan uv
 │    │    │    │    └── columns: v:14
 │    │    │    └── projections
 │    │    │         └── 1 [as="?column?":17]
 │    │    └── filters
 │    │         └── v:14 = k:1
 │    └── aggregations
 │         ├── any-not-null-agg [as="?column?":17]
 │         │    └── "?column?":17
 │         ├── const-agg [as="?column?":12]
 │         │    └── "?column?":12
 │         ├── const-agg [as=i:2]
 │         │    └── i:2
 │         ├── const-agg [as=f:3]
 │         │    └── f:3
 │         ├── const-agg [as=s:4]
 │         │    └── s:4
 │         └── const-agg [as=j:5]
 │              └── j:5
 └── projections
      └── CASE WHEN COALESCE("?column?":12, "?column?":17) IS NOT NULL THEN 1 ELSE 0 END [as=case:20]

# Case with Union.
norm expect=TryDecorrelateUnion format=(hide-all,show-columns)
SELECT *, CASE WHEN EXISTS (SELECT 1 FROM xy WHERE x = k UNION SELECT 1 FROM uv WHERE v = k) THEN 1 ELSE 0 END FROM a;
----
project
 ├── columns: k:1 i:2 f:3 s:4 j:5 case:20
 ├── group-by (hash)
 │    ├── columns: k:1 i:2 f:3 s:4 j:5 "?column?":12 "?column?":17
 │    ├── grouping columns: k:1
 │    ├── left-join (hash)
 │    │    ├── columns: k:1 i:2 f:3 s:4 j:5 x:8 "?column?":12 v:14 "?column?":17
 │    │    ├── left-join (hash)
 │    │    │    ├── columns: k:1 i:2 f:3 s:4 j:5 x:8 "?column?":12
 │    │    │    ├── scan a
 │    │    │    │    └── columns: k:1 i:2 f:3 s:4 j:5
 │    │    │    ├── project
 │    │    │    │    ├── columns: "?column?":12 x:8
 │    │    │    │    ├── scan xy
 │    │    │    │    │    └── columns: x:8
 │    │    │    │    └── projections
 │    │    │    │         └── 1 [as="?column?":12]
 │    │    │    └── filters
 │    │    │         └── x:8 = k:1
 │    │    ├── project
 │    │    │    ├── columns: "?column?":17 v:14
 │    │    │    ├── scan uv
 │    │    │    │    └── columns: v:14
 │    │    │    └── projections
 │    │    │         └── 1 [as="?column?":17]
 │    │    └── filters
 │    │         └── v:14 = k:1
 │    └── aggregations
 │         ├── any-not-null-agg [as="?column?":17]
 │         │    └── "?column?":17
 │         ├── const-agg [as="?column?":12]
 │         │    └── "?column?":12
 │         ├── const-agg [as=i:2]
 │         │    └── i:2
 │         ├── const-agg [as=f:3]
 │         │    └── f:3
 │         ├── const-agg [as=s:4]
 │         │    └── s:4
 │         └── const-agg [as=j:5]
 │              └── j:5
 └── projections
      └── CASE WHEN COALESCE("?column?":12, "?column?":17) IS NOT NULL THEN 1 ELSE 0 END [as=case:20]

# Case with an uncorrelated Union branch.
norm expect=TryDecorrelateUnion format=(hide-all,show-columns)
SELECT *, CASE WHEN EXISTS (SELECT 1 FROM xy WHERE x = k UNION ALL SELECT 1 FROM uv WHERE u = 1) THEN 1 ELSE 0 END FROM a;
----
project
 ├── columns: k:1 i:2 f:3 s:4 j:5 case:20
 ├── inner-join-apply
 │    ├── columns: k:1 i:2 f:3 s:4 j:5 "?column?":12 "?column?":17
 │    ├── scan a
 │    │    └── columns: k:1 i:2 f:3 s:4 j:5
 │    ├── inner-join (cross)
 │    │    ├── columns: "?column?":12 "?column?":17
 │    │    ├── scalar-group-by
 │    │    │    ├── columns: "?column?":12
 │    │    │    ├── project
 │    │    │    │    ├── columns: "?column?":12
 │    │    │    │    ├── select
 │    │    │    │    │    ├── columns: x:8
 │    │    │    │    │    ├── scan xy
 │    │    │    │    │    │    └── columns: x:8
 │    │    │    │    │    └── filters
 │    │    │    │    │         └── x:8 = k:1
 │    │    │    │    └── projections
 │    │    │    │         └── 1 [as="?column?":12]
 │    │    │    └── aggregations
 │    │    │         └── any-not-null-agg [as="?column?":12]
 │    │    │              └── "?column?":12
 │    │    ├── scalar-group-by
 │    │    │    ├── columns: "?column?":17
 │    │    │    ├── project
 │    │    │    │    ├── columns: "?column?":17
 │    │    │    │    ├── select
 │    │    │    │    │    ├── columns: u:13
 │    │    │    │    │    ├── scan uv
 │    │    │    │    │    │    └── columns: u:13
 │    │    │    │    │    └── filters
 │    │    │    │    │         └── u:13 = 1
 │    │    │    │    └── projections
 │    │    │    │         └── 1 [as="?column?":17]
 │    │    │    └── aggregations
 │    │    │         └── any-not-null-agg [as="?column?":17]
 │    │    │              └── "?column?":17
 │    │    └── filters (true)
 │    └── filters (true)
 └── projections
      └── CASE WHEN COALESCE("?column?":12, "?column?":17) IS NOT NULL THEN 1 ELSE 0 END [as=case:20]

# Case with more than one union operator.
norm expect=TryDecorrelateUnion format=(hide-all,show-columns)
SELECT *, CASE WHEN EXISTS (
  SELECT 1 FROM xy WHERE x = k
  UNION ALL SELECT 1 FROM uv WHERE v = k
  UNION ALL SELECT 1 FROM cd WHERE d = k
) THEN 1 ELSE 0 END FROM a;
----
project
 ├── columns: k:1 i:2 f:3 s:4 j:5 case:26
 ├── group-by (hash)
 │    ├── columns: k:1 i:2 f:3 s:4 j:5 "?column?":18 "?column?":23
 │    ├── grouping columns: k:1
 │    ├── left-join (hash)
 │    │    ├── columns: k:1 i:2 f:3 s:4 j:5 "?column?":18 d:20 "?column?":23
 │    │    ├── project
 │    │    │    ├── columns: "?column?":18 k:1 i:2 f:3 s:4 j:5
 │    │    │    ├── group-by (hash)
 │    │    │    │    ├── columns: k:1 i:2 f:3 s:4 j:5 "?column?":12 "?column?":17
 │    │    │    │    ├── grouping columns: k:1
 │    │    │    │    ├── left-join (hash)
 │    │    │    │    │    ├── columns: k:1 i:2 f:3 s:4 j:5 x:8 "?column?":12 v:14 "?column?":17
 │    │    │    │    │    ├── left-join (hash)
 │    │    │    │    │    │    ├── columns: k:1 i:2 f:3 s:4 j:5 x:8 "?column?":12
 │    │    │    │    │    │    ├── scan a
 │    │    │    │    │    │    │    └── columns: k:1 i:2 f:3 s:4 j:5
 │    │    │    │    │    │    ├── project
 │    │    │    │    │    │    │    ├── columns: "?column?":12 x:8
 │    │    │    │    │    │    │    ├── scan xy
 │    │    │    │    │    │    │    │    └── columns: x:8
 │    │    │    │    │    │    │    └── projections
 │    │    │    │    │    │    │         └── 1 [as="?column?":12]
 │    │    │    │    │    │    └── filters
 │    │    │    │    │    │         └── x:8 = k:1
 │    │    │    │    │    ├── project
 │    │    │    │    │    │    ├── columns: "?column?":17 v:14
 │    │    │    │    │    │    ├── scan uv
 │    │    │    │    │    │    │    └── columns: v:14
 │    │    │    │    │    │    └── projections
 │    │    │    │    │    │         └── 1 [as="?column?":17]
 │    │    │    │    │    └── filters
 │    │    │    │    │         └── v:14 = k:1
 │    │    │    │    └── aggregations
 │    │    │    │         ├── any-not-null-agg [as="?column?":17]
 │    │    │    │         │    └── "?column?":17
 │    │    │    │         ├── const-agg [as="?column?":12]
 │    │    │    │         │    └── "?column?":12
 │    │    │    │         ├── const-agg [as=i:2]
 │    │    │    │         │    └── i:2
 │    │    │    │         ├── const-agg [as=f:3]
 │    │    │    │         │    └── f:3
 │    │    │    │         ├── const-agg [as=s:4]
 │    │    │    │         │    └── s:4
 │    │    │    │         └── const-agg [as=j:5]
 │    │    │    │              └── j:5
 │    │    │    └── projections
 │    │    │         └── COALESCE("?column?":12, "?column?":17) [as="?column?":18]
 │    │    ├── project
 │    │    │    ├── columns: "?column?":23 d:20
 │    │    │    ├── scan cd
 │    │    │    │    └── columns: d:20
 │    │    │    └── projections
 │    │    │         └── 1 [as="?column?":23]
 │    │    └── filters
 │    │         └── d:20 = k:1
 │    └── aggregations
 │         ├── any-not-null-agg [as="?column?":23]
 │         │    └── "?column?":23
 │         ├── const-agg [as="?column?":18]
 │         │    └── "?column?":18
 │         ├── const-agg [as=i:2]
 │         │    └── i:2
 │         ├── const-agg [as=f:3]
 │         │    └── f:3
 │         ├── const-agg [as=s:4]
 │         │    └── s:4
 │         └── const-agg [as=j:5]
 │              └── j:5
 └── projections
      └── CASE WHEN COALESCE("?column?":18, "?column?":23) IS NOT NULL THEN 1 ELSE 0 END [as=case:26]

# Case with multiple projections.
norm expect=TryDecorrelateUnion format=(hide-all,show-columns)
SELECT *, CASE WHEN EXISTS (SELECT 1, 2 FROM xy WHERE x = k UNION SELECT 3, 4 FROM uv WHERE v = k) THEN 1 ELSE 0 END FROM a;
----
project
 ├── columns: k:1 i:2 f:3 s:4 j:5 case:23
 ├── group-by (hash)
 │    ├── columns: k:1 i:2 f:3 s:4 j:5 "?column?":12 "?column?":18
 │    ├── grouping columns: k:1
 │    ├── left-join (hash)
 │    │    ├── columns: k:1 i:2 f:3 s:4 j:5 x:8 "?column?":12 v:15 "?column?":18
 │    │    ├── left-join (hash)
 │    │    │    ├── columns: k:1 i:2 f:3 s:4 j:5 x:8 "?column?":12
 │    │    │    ├── scan a
 │    │    │    │    └── columns: k:1 i:2 f:3 s:4 j:5
 │    │    │    ├── project
 │    │    │    │    ├── columns: "?column?":12 x:8
 │    │    │    │    ├── scan xy
 │    │    │    │    │    └── columns: x:8
 │    │    │    │    └── projections
 │    │    │    │         └── 1 [as="?column?":12]
 │    │    │    └── filters
 │    │    │         └── x:8 = k:1
 │    │    ├── project
 │    │    │    ├── columns: "?column?":18 v:15
 │    │    │    ├── scan uv
 │    │    │    │    └── columns: v:15
 │    │    │    └── projections
 │    │    │         └── 3 [as="?column?":18]
 │    │    └── filters
 │    │         └── v:15 = k:1
 │    └── aggregations
 │         ├── any-not-null-agg [as="?column?":18]
 │         │    └── "?column?":18
 │         ├── const-agg [as="?column?":12]
 │         │    └── "?column?":12
 │         ├── const-agg [as=i:2]
 │         │    └── i:2
 │         ├── const-agg [as=f:3]
 │         │    └── f:3
 │         ├── const-agg [as=s:4]
 │         │    └── s:4
 │         └── const-agg [as=j:5]
 │              └── j:5
 └── projections
      └── CASE WHEN COALESCE("?column?":12, "?column?":18) IS NOT NULL THEN 1 ELSE 0 END [as=case:23]

norm expect=TryDecorrelateUnion format=(hide-all,show-columns)
SELECT *, CASE WHEN EXISTS (SELECT x + 10 FROM xy WHERE x = k UNION SELECT 1 FROM uv WHERE v = k) THEN 1 ELSE 0 END FROM a;
----
project
 ├── columns: k:1 i:2 f:3 s:4 j:5 case:20
 ├── group-by (hash)
 │    ├── columns: k:1 i:2 f:3 s:4 j:5 "?column?":12 "?column?":17
 │    ├── grouping columns: k:1
 │    ├── left-join (hash)
 │    │    ├── columns: k:1 i:2 f:3 s:4 j:5 x:8 "?column?":12 v:14 "?column?":17
 │    │    ├── left-join (hash)
 │    │    │    ├── columns: k:1 i:2 f:3 s:4 j:5 x:8 "?column?":12
 │    │    │    ├── scan a
 │    │    │    │    └── columns: k:1 i:2 f:3 s:4 j:5
 │    │    │    ├── project
 │    │    │    │    ├── columns: "?column?":12 x:8
 │    │    │    │    ├── scan xy
 │    │    │    │    │    └── columns: x:8
 │    │    │    │    └── projections
 │    │    │    │         └── x:8 + 10 [as="?column?":12]
 │    │    │    └── filters
 │    │    │         └── x:8 = k:1
 │    │    ├── project
 │    │    │    ├── columns: "?column?":17 v:14
 │    │    │    ├── scan uv
 │    │    │    │    └── columns: v:14
 │    │    │    └── projections
 │    │    │         └── 1 [as="?column?":17]
 │    │    └── filters
 │    │         └── v:14 = k:1
 │    └── aggregations
 │         ├── any-not-null-agg [as="?column?":17]
 │         │    └── "?column?":17
 │         ├── const-agg [as="?column?":12]
 │         │    └── "?column?":12
 │         ├── const-agg [as=i:2]
 │         │    └── i:2
 │         ├── const-agg [as=f:3]
 │         │    └── f:3
 │         ├── const-agg [as=s:4]
 │         │    └── s:4
 │         └── const-agg [as=j:5]
 │              └── j:5
 └── projections
      └── CASE WHEN COALESCE("?column?":12, "?column?":17) IS NOT NULL THEN 1 ELSE 0 END [as=case:20]

# Case with a projected variable.
norm expect=TryDecorrelateUnion format=(hide-all,show-columns)
SELECT *, CASE WHEN EXISTS (SELECT x FROM xy WHERE x = k UNION SELECT 1 FROM uv WHERE v = k) THEN 1 ELSE 0 END FROM a;
----
project
 ├── columns: k:1 i:2 f:3 s:4 j:5 case:19
 ├── group-by (hash)
 │    ├── columns: k:1 i:2 f:3 s:4 j:5 xy.x:8 "?column?":16
 │    ├── grouping columns: k:1
 │    ├── left-join (hash)
 │    │    ├── columns: k:1 i:2 f:3 s:4 j:5 xy.x:8 v:13 "?column?":16
 │    │    ├── left-join (hash)
 │    │    │    ├── columns: k:1 i:2 f:3 s:4 j:5 xy.x:8
 │    │    │    ├── scan a
 │    │    │    │    └── columns: k:1 i:2 f:3 s:4 j:5
 │    │    │    ├── scan xy
 │    │    │    │    └── columns: xy.x:8
 │    │    │    └── filters
 │    │    │         └── xy.x:8 = k:1
 │    │    ├── project
 │    │    │    ├── columns: "?column?":16 v:13
 │    │    │    ├── scan uv
 │    │    │    │    └── columns: v:13
 │    │    │    └── projections
 │    │    │         └── 1 [as="?column?":16]
 │    │    └── filters
 │    │         └── v:13 = k:1
 │    └── aggregations
 │         ├── any-not-null-agg [as="?column?":16]
 │         │    └── "?column?":16
 │         ├── const-agg [as=xy.x:8]
 │         │    └── xy.x:8
 │         ├── const-agg [as=i:2]
 │         │    └── i:2
 │         ├── const-agg [as=f:3]
 │         │    └── f:3
 │         ├── const-agg [as=s:4]
 │         │    └── s:4
 │         └── const-agg [as=j:5]
 │              └── j:5
 └── projections
      └── CASE WHEN COALESCE(xy.x:8, "?column?":16) IS NOT NULL THEN 1 ELSE 0 END [as=case:19]

# Case with multiple projected variables/expressions.
norm expect=TryDecorrelateUnion format=(hide-all,show-columns)
SELECT *, CASE WHEN EXISTS (SELECT x, y, x * y FROM xy WHERE x = k UNION SELECT u, v, u + v FROM uv WHERE v = k) THEN 1 ELSE 0 END FROM a;
----
project
 ├── columns: k:1 i:2 f:3 s:4 j:5 case:22
 ├── group-by (hash)
 │    ├── columns: k:1 i:2 f:3 s:4 j:5 xy.x:8 u:13
 │    ├── grouping columns: k:1
 │    ├── left-join (hash)
 │    │    ├── columns: k:1 i:2 f:3 s:4 j:5 xy.x:8 u:13 v:14
 │    │    ├── left-join (hash)
 │    │    │    ├── columns: k:1 i:2 f:3 s:4 j:5 xy.x:8
 │    │    │    ├── scan a
 │    │    │    │    └── columns: k:1 i:2 f:3 s:4 j:5
 │    │    │    ├── scan xy
 │    │    │    │    └── columns: xy.x:8
 │    │    │    └── filters
 │    │    │         └── xy.x:8 = k:1
 │    │    ├── scan uv
 │    │    │    └── columns: u:13 v:14
 │    │    └── filters
 │    │         └── v:14 = k:1
 │    └── aggregations
 │         ├── any-not-null-agg [as=u:13]
 │         │    └── u:13
 │         ├── const-agg [as=xy.x:8]
 │         │    └── xy.x:8
 │         ├── const-agg [as=i:2]
 │         │    └── i:2
 │         ├── const-agg [as=f:3]
 │         │    └── f:3
 │         ├── const-agg [as=s:4]
 │         │    └── s:4
 │         └── const-agg [as=j:5]
 │              └── j:5
 └── projections
      └── CASE WHEN COALESCE(xy.x:8, u:13) IS NOT NULL THEN 1 ELSE 0 END [as=case:22]

# No-op because there is no Union.
norm expect-not=TryDecorrelateUnion format=(hide-all,show-columns)
SELECT *, CASE WHEN EXISTS (SELECT 1 FROM xy WHERE x = k) THEN 1 ELSE 0 END FROM a;
----
project
 ├── columns: k:1 i:2 f:3 s:4 j:5 case:14
 ├── left-join (hash)
 │    ├── columns: k:1 i:2 f:3 s:4 j:5 x:8
 │    ├── scan a
 │    │    └── columns: k:1 i:2 f:3 s:4 j:5
 │    ├── scan xy
 │    │    └── columns: x:8
 │    └── filters
 │         └── x:8 = k:1
 └── projections
      └── CASE WHEN x:8 IS NOT NULL THEN 1 ELSE 0 END [as=case:14]

# No-op because there's an Intersect instead of a Union.
norm expect-not=TryDecorrelateUnion format=(hide-all,show-columns)
SELECT *, CASE WHEN EXISTS (SELECT 1 FROM xy WHERE x = k INTERSECT SELECT 1 FROM uv WHERE v = k) THEN 1 ELSE 0 END FROM a;
----
project
 ├── columns: k:1 i:2 f:3 s:4 j:5 case:19
 ├── left-join-apply
 │    ├── columns: k:1 i:2 f:3 s:4 j:5 "?column?":12
 │    ├── scan a
 │    │    └── columns: k:1 i:2 f:3 s:4 j:5
 │    ├── intersect-all
 │    │    ├── columns: "?column?":12
 │    │    ├── left columns: "?column?":12
 │    │    ├── right columns: "?column?":17
 │    │    ├── project
 │    │    │    ├── columns: "?column?":12
 │    │    │    ├── select
 │    │    │    │    ├── columns: x:8
 │    │    │    │    ├── scan xy
 │    │    │    │    │    └── columns: x:8
 │    │    │    │    └── filters
 │    │    │    │         └── x:8 = k:1
 │    │    │    └── projections
 │    │    │         └── 1 [as="?column?":12]
 │    │    └── project
 │    │         ├── columns: "?column?":17
 │    │         ├── select
 │    │         │    ├── columns: v:14
 │    │         │    ├── scan uv
 │    │         │    │    └── columns: v:14
 │    │         │    └── filters
 │    │         │         └── v:14 = k:1
 │    │         └── projections
 │    │              └── 1 [as="?column?":17]
 │    └── filters (true)
 └── projections
      └── CASE WHEN "?column?":12 IS NOT NULL THEN 1 ELSE 0 END [as=case:19]

# No-op case because one of the aggregations isn't any-not-null.
norm expect-not=TryDecorrelateUnion format=(hide-all,show-columns)
SELECT * FROM a INNER JOIN LATERAL (SELECT sum(x) FROM (SELECT x FROM xy WHERE x = k UNION ALL SELECT v FROM uv WHERE v = k)) ON True;
----
group-by (hash)
 ├── columns: k:1 i:2 f:3 s:4 j:5 sum:17
 ├── grouping columns: k:1
 ├── left-join-apply
 │    ├── columns: k:1 i:2 f:3 s:4 j:5 x:16
 │    ├── scan a
 │    │    └── columns: k:1 i:2 f:3 s:4 j:5
 │    ├── union-all
 │    │    ├── columns: x:16
 │    │    ├── left columns: xy.x:8
 │    │    ├── right columns: v:13
 │    │    ├── select
 │    │    │    ├── columns: xy.x:8
 │    │    │    ├── scan xy
 │    │    │    │    └── columns: xy.x:8
 │    │    │    └── filters
 │    │    │         └── xy.x:8 = k:1
 │    │    └── select
 │    │         ├── columns: v:13
 │    │         ├── scan uv
 │    │         │    └── columns: v:13
 │    │         └── filters
 │    │              └── v:13 = k:1
 │    └── filters (true)
 └── aggregations
      ├── sum [as=sum:17]
      │    └── x:16
      ├── const-agg [as=i:2]
      │    └── i:2
      ├── const-agg [as=f:3]
      │    └── f:3
      ├── const-agg [as=s:4]
      │    └── s:4
      └── const-agg [as=j:5]
           └── j:5

# No-op case because the Union isn't correlated.
norm expect-not=TryDecorrelateUnion format=(hide-all,show-columns)
SELECT * FROM a INNER JOIN LATERAL (SELECT 1 FROM xy UNION ALL SELECT 1 FROM uv) ON True;
----
inner-join (cross)
 ├── columns: k:1 i:2 f:3 s:4 j:5 "?column?":18
 ├── scan a
 │    └── columns: k:1 i:2 f:3 s:4 j:5
 ├── union-all
 │    ├── columns: "?column?":18
 │    ├── left columns: "?column?":12
 │    ├── right columns: "?column?":17
 │    ├── project
 │    │    ├── columns: "?column?":12
 │    │    ├── scan xy
 │    │    └── projections
 │    │         └── 1 [as="?column?":12]
 │    └── project
 │         ├── columns: "?column?":17
 │         ├── scan uv
 │         └── projections
 │              └── 1 [as="?column?":17]
 └── filters (true)

# No-op because there is a Project in between the ScalarGroupBy and the Union.
# TODO(drewk): decorrelate this case as well.
norm expect-not=TryDecorrelateUnion format=(hide-all,show-columns)
SELECT *, CASE WHEN EXISTS (SELECT x + y FROM xy WHERE x = k UNION SELECT 1 FROM uv WHERE v = k) THEN 1 ELSE 0 END FROM a;
----
project
 ├── columns: k:1 i:2 f:3 s:4 j:5 case:20
 ├── group-by (hash)
 │    ├── columns: k:1 i:2 f:3 s:4 j:5 canary_agg:22
 │    ├── grouping columns: k:1
 │    ├── left-join-apply
 │    │    ├── columns: k:1 i:2 f:3 s:4 j:5 canary:21
 │    │    ├── scan a
 │    │    │    └── columns: k:1 i:2 f:3 s:4 j:5
 │    │    ├── project
 │    │    │    ├── columns: canary:21
 │    │    │    ├── union
 │    │    │    │    ├── columns: "?column?":18
 │    │    │    │    ├── left columns: "?column?":12
 │    │    │    │    ├── right columns: "?column?":17
 │    │    │    │    ├── project
 │    │    │    │    │    ├── columns: "?column?":12
 │    │    │    │    │    ├── select
 │    │    │    │    │    │    ├── columns: x:8 y:9
 │    │    │    │    │    │    ├── scan xy
 │    │    │    │    │    │    │    └── columns: x:8 y:9
 │    │    │    │    │    │    └── filters
 │    │    │    │    │    │         └── x:8 = k:1
 │    │    │    │    │    └── projections
 │    │    │    │    │         └── x:8 + y:9 [as="?column?":12]
 │    │    │    │    └── project
 │    │    │    │         ├── columns: "?column?":17
 │    │    │    │         ├── select
 │    │    │    │         │    ├── columns: v:14
 │    │    │    │         │    ├── scan uv
 │    │    │    │         │    │    └── columns: v:14
 │    │    │    │         │    └── filters
 │    │    │    │         │         └── v:14 = k:1
 │    │    │    │         └── projections
 │    │    │    │              └── 1 [as="?column?":17]
 │    │    │    └── projections
 │    │    │         └── true [as=canary:21]
 │    │    └── filters (true)
 │    └── aggregations
 │         ├── const-not-null-agg [as=canary_agg:22]
 │         │    └── canary:21
 │         ├── const-agg [as=i:2]
 │         │    └── i:2
 │         ├── const-agg [as=f:3]
 │         │    └── f:3
 │         ├── const-agg [as=s:4]
 │         │    └── s:4
 │         └── const-agg [as=j:5]
 │              └── j:5
 └── projections
      └── CASE WHEN canary_agg:22 IS NOT NULL THEN 1 ELSE 0 END [as=case:20]
