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

exec-ddl
CREATE TABLE a (v INT PRIMARY KEY, w INT, x FLOAT, y STRING NOT NULL, z JSON)
----

exec-ddl
CREATE TABLE t (a INT PRIMARY KEY, b INT, c INT)
----

exec-ddl
CREATE TABLE t2 (
  a INT,
  b INT,
  c INT NOT NULL,
  d INT,
  PRIMARY KEY(a, b),
  UNIQUE INDEX (c),
  UNIQUE INDEX (d)
)
----

# --------------------------------------------------
# EliminateSetLeft
# --------------------------------------------------

norm expect=EliminateSetLeft
SELECT k FROM
  (SELECT k FROM b)
  UNION ALL
  (SELECT k FROM b WHERE k IN ())
----
project
 ├── columns: k:15!null
 ├── key: (15)
 ├── scan b
 │    ├── columns: b.k:1!null
 │    └── key: (1)
 └── projections
      └── b.k:1 [as=k:15, outer=(1)]

norm expect=EliminateSetLeft
SELECT k FROM
  (SELECT k FROM b)
  EXCEPT ALL
  (SELECT k FROM b WHERE k IN ())
----
scan b
 ├── columns: k:1!null
 └── key: (1)

# --------------------------------------------------
# EliminateSetRight
# --------------------------------------------------

norm expect=EliminateSetRight
SELECT k FROM
  (SELECT k FROM b WHERE Null)
  UNION ALL
  (SELECT k FROM b)
----
project
 ├── columns: k:15!null
 ├── key: (15)
 ├── scan b
 │    ├── columns: b.k:8!null
 │    └── key: (8)
 └── projections
      └── b.k:8 [as=k:15, outer=(8)]

# No-op case because EXCEPT ALL outputs left rows.
norm expect-not=EliminateSetLeft
SELECT k FROM
  (SELECT k FROM b WHERE k IN ())
  EXCEPT ALL
  (SELECT k FROM b)
----
values
 ├── columns: k:1!null
 ├── cardinality: [0 - 0]
 ├── key: ()
 └── fd: ()-->(1)

norm
SELECT k FROM
  (SELECT k FROM b WHERE False)
  UNION ALL
  (SELECT k FROM b WHERE i IN ())
----
values
 ├── columns: k:15!null
 ├── cardinality: [0 - 0]
 ├── key: ()
 └── fd: ()-->(15)

# --------------------------------------------------
# EliminateDistinctSetLeft
# --------------------------------------------------

norm expect=EliminateDistinctSetLeft
SELECT k FROM
  (SELECT k FROM b)
  UNION
  (SELECT k FROM b WHERE k IN ())
----
project
 ├── columns: k:15!null
 ├── key: (15)
 ├── scan b
 │    ├── columns: b.k:1!null
 │    └── key: (1)
 └── projections
      └── b.k:1 [as=k:15, outer=(1)]

norm expect=EliminateDistinctSetLeft
SELECT i FROM
  (SELECT i FROM b)
  EXCEPT
  (SELECT i FROM b WHERE k IN ())
----
distinct-on
 ├── columns: i:2
 ├── grouping columns: i:2
 ├── key: (2)
 └── scan b
      └── columns: i:2

# --------------------------------------------------
# EliminateDistinctSetRight
# --------------------------------------------------

norm expect=EliminateDistinctSetRight
SELECT k FROM
  (SELECT k FROM b WHERE Null)
  UNION
  (SELECT k FROM b)
----
project
 ├── columns: k:15!null
 ├── key: (15)
 ├── scan b
 │    ├── columns: b.k:8!null
 │    └── key: (8)
 └── projections
      └── b.k:8 [as=k:15, outer=(8)]

# No-op case because EXCEPT outputs left rows.
norm expect-not=EliminateDistinctSetLeft
SELECT i FROM
  (SELECT i FROM b WHERE k IN ())
  EXCEPT
  (SELECT i FROM b)
----
values
 ├── columns: i:2!null
 ├── cardinality: [0 - 0]
 ├── key: ()
 └── fd: ()-->(2)

norm
SELECT k FROM
  (SELECT k FROM b WHERE False)
  UNION
  (SELECT k FROM b WHERE i IN ())
----
values
 ├── columns: k:15!null
 ├── cardinality: [0 - 0]
 ├── key: ()
 └── fd: ()-->(15)

# -------------------------------------------------
# SimplifyExcept
# -------------------------------------------------

norm expect=SimplifyExcept
SELECT k FROM
  (SELECT k FROM b)
  EXCEPT
  (SELECT i FROM b)
----
except-all
 ├── columns: k:1
 ├── left columns: k:1
 ├── right columns: i:9
 ├── key: (1)
 ├── scan b
 │    ├── columns: k:1!null
 │    └── key: (1)
 └── scan b
      └── columns: i:9

# No-op case because the left side does not have a key.
norm expect-not=SimplifyExcept
SELECT i FROM
  (SELECT i FROM b)
  EXCEPT
  (SELECT k FROM b)
----
except
 ├── columns: i:2
 ├── left columns: i:2
 ├── right columns: k:8
 ├── key: (2)
 ├── scan b
 │    └── columns: i:2
 └── scan b
      ├── columns: k:8!null
      └── key: (8)

# -------------------------------------------------
# SimplifyIntersect
# -------------------------------------------------

norm expect=SimplifyIntersectLeft expect-not=SimplifyIntersectRight
SELECT k FROM
  (SELECT k FROM b)
  Intersect
  (SELECT i FROM b)
----
intersect-all
 ├── columns: k:1
 ├── left columns: k:1
 ├── right columns: i:9
 ├── key: (1)
 ├── scan b
 │    ├── columns: k:1!null
 │    └── key: (1)
 └── scan b
      └── columns: i:9

norm expect=SimplifyIntersectRight expect-not=SimplifyIntersectLeft
SELECT i FROM
  (SELECT i FROM b)
  Intersect
  (SELECT k FROM b)
----
intersect-all
 ├── columns: i:2
 ├── left columns: i:2
 ├── right columns: k:8
 ├── scan b
 │    └── columns: i:2
 └── scan b
      ├── columns: k:8!null
      └── key: (8)

norm
SELECT k FROM
  (SELECT k FROM b)
  Intersect
  (SELECT k FROM b)
----
intersect-all
 ├── columns: k:1!null
 ├── left columns: k:1!null
 ├── right columns: k:8
 ├── key: (1)
 ├── scan b
 │    ├── columns: k:1!null
 │    └── key: (1)
 └── scan b
      ├── columns: k:8!null
      └── key: (8)

# No-op case because neither side has a key.
norm expect-not=(SimplifyIntersectLeft, SimplifyIntersectRight)
SELECT i FROM
  (SELECT i FROM b)
  Intersect
  (SELECT i FROM b)
----
intersect
 ├── columns: i:2
 ├── left columns: i:2
 ├── right columns: i:9
 ├── key: (2)
 ├── scan b
 │    └── columns: i:2
 └── scan b
      └── columns: i:9

# -------------------------------------------------
# ConvertUnionToDistinctUnionAll
# -------------------------------------------------

norm expect=ConvertUnionToDistinctUnionAll
SELECT a, b, c FROM
  (SELECT a, b, c FROM t WHERE a < 0)
UNION
  (SELECT a, b, c FROM t WHERE b > 10)
----
distinct-on
 ├── columns: a:11!null b:12 c:13
 ├── grouping columns: a:11!null
 ├── key: (11)
 ├── fd: (11)-->(12,13)
 ├── union-all
 │    ├── columns: a:11!null b:12 c:13
 │    ├── left columns: t.a:1 t.b:2 t.c:3
 │    ├── right columns: t.a:6 t.b:7 t.c:8
 │    ├── select
 │    │    ├── columns: t.a:1!null t.b:2 t.c:3
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(2,3)
 │    │    ├── scan t
 │    │    │    ├── columns: t.a:1!null t.b:2 t.c:3
 │    │    │    ├── key: (1)
 │    │    │    └── fd: (1)-->(2,3)
 │    │    └── filters
 │    │         └── t.a:1 < 0 [outer=(1), constraints=(/1: (/NULL - /-1]; tight)]
 │    └── select
 │         ├── columns: t.a:6!null t.b:7!null t.c:8
 │         ├── key: (6)
 │         ├── fd: (6)-->(7,8)
 │         ├── scan t
 │         │    ├── columns: t.a:6!null t.b:7 t.c:8
 │         │    ├── key: (6)
 │         │    └── fd: (6)-->(7,8)
 │         └── filters
 │              └── t.b:7 > 10 [outer=(7), constraints=(/7: [/11 - ]; tight)]
 └── aggregations
      ├── const-agg [as=b:12, outer=(12)]
      │    └── b:12
      └── const-agg [as=c:13, outer=(13)]
           └── c:13

# Case with union between three same-table scans. The rule doesn't match on the
# outer Union because the inner Union's output columns do not have an associated
# meta table.
norm expect=ConvertUnionToDistinctUnionAll
SELECT a, b, c FROM
  (SELECT a, b, c FROM t WHERE a < 0)
UNION
  (SELECT a, b, c FROM t WHERE b > 10 AND b < 100)
UNION
  (SELECT a, b, c FROM t WHERE b > 1000)
----
union
 ├── columns: a:19!null b:20 c:21
 ├── left columns: a:11 b:12 c:13
 ├── right columns: t.a:14 t.b:15 t.c:16
 ├── key: (19-21)
 ├── distinct-on
 │    ├── columns: a:11!null b:12 c:13
 │    ├── grouping columns: a:11!null
 │    ├── key: (11)
 │    ├── fd: (11)-->(12,13)
 │    ├── union-all
 │    │    ├── columns: a:11!null b:12 c:13
 │    │    ├── left columns: t.a:1 t.b:2 t.c:3
 │    │    ├── right columns: t.a:6 t.b:7 t.c:8
 │    │    ├── select
 │    │    │    ├── columns: t.a:1!null t.b:2 t.c:3
 │    │    │    ├── key: (1)
 │    │    │    ├── fd: (1)-->(2,3)
 │    │    │    ├── scan t
 │    │    │    │    ├── columns: t.a:1!null t.b:2 t.c:3
 │    │    │    │    ├── key: (1)
 │    │    │    │    └── fd: (1)-->(2,3)
 │    │    │    └── filters
 │    │    │         └── t.a:1 < 0 [outer=(1), constraints=(/1: (/NULL - /-1]; tight)]
 │    │    └── select
 │    │         ├── columns: t.a:6!null t.b:7!null t.c:8
 │    │         ├── key: (6)
 │    │         ├── fd: (6)-->(7,8)
 │    │         ├── scan t
 │    │         │    ├── columns: t.a:6!null t.b:7 t.c:8
 │    │         │    ├── key: (6)
 │    │         │    └── fd: (6)-->(7,8)
 │    │         └── filters
 │    │              └── (t.b:7 > 10) AND (t.b:7 < 100) [outer=(7), constraints=(/7: [/11 - /99]; tight)]
 │    └── aggregations
 │         ├── const-agg [as=b:12, outer=(12)]
 │         │    └── b:12
 │         └── const-agg [as=c:13, outer=(13)]
 │              └── c:13
 └── select
      ├── columns: t.a:14!null t.b:15!null t.c:16
      ├── key: (14)
      ├── fd: (14)-->(15,16)
      ├── scan t
      │    ├── columns: t.a:14!null t.b:15 t.c:16
      │    ├── key: (14)
      │    └── fd: (14)-->(15,16)
      └── filters
           └── t.b:15 > 1000 [outer=(15), constraints=(/15: [/1001 - ]; tight)]

# The DistinctOn should group on the entire primary key (a, b) instead of just
# on column b, otherwise extra rows will be removed.
norm expect=ConvertUnionToDistinctUnionAll
SELECT a, b, d FROM
  (SELECT a, b, d FROM t2 WHERE a = 1)
UNION
  (SELECT a, b, d FROM t2 WHERE a = 2)
----
distinct-on
 ├── columns: a:13!null b:14!null d:15
 ├── grouping columns: a:13!null b:14!null
 ├── key: (13,14)
 ├── fd: (13,14)-->(15)
 ├── union-all
 │    ├── columns: a:13!null b:14!null d:15
 │    ├── left columns: t2.a:1 t2.b:2 t2.d:4
 │    ├── right columns: t2.a:7 t2.b:8 t2.d:10
 │    ├── select
 │    │    ├── columns: t2.a:1!null t2.b:2!null t2.d:4
 │    │    ├── key: (2)
 │    │    ├── fd: ()-->(1), (2)-->(4), (4)~~>(2)
 │    │    ├── scan t2
 │    │    │    ├── columns: t2.a:1!null t2.b:2!null t2.d:4
 │    │    │    ├── key: (1,2)
 │    │    │    └── fd: (1,2)-->(4), (4)~~>(1,2)
 │    │    └── filters
 │    │         └── t2.a:1 = 1 [outer=(1), constraints=(/1: [/1 - /1]; tight), fd=()-->(1)]
 │    └── select
 │         ├── columns: t2.a:7!null t2.b:8!null t2.d:10
 │         ├── key: (8)
 │         ├── fd: ()-->(7), (8)-->(10), (10)~~>(8)
 │         ├── scan t2
 │         │    ├── columns: t2.a:7!null t2.b:8!null t2.d:10
 │         │    ├── key: (7,8)
 │         │    └── fd: (7,8)-->(10), (10)~~>(7,8)
 │         └── filters
 │              └── t2.a:7 = 2 [outer=(7), constraints=(/7: [/2 - /2]; tight), fd=()-->(7)]
 └── aggregations
      └── const-agg [as=d:15, outer=(15)]
           └── d:15

# Case where the inputs have empty keys. Should group on the entire primary
# key nevertheless.
norm expect=ConvertUnionToDistinctUnionAll
SELECT a, b, d FROM
  (SELECT a, b, d FROM t2 LIMIT 1)
UNION
  (SELECT a, b, d FROM t2 LIMIT 1)
----
distinct-on
 ├── columns: a:13!null b:14!null d:15
 ├── grouping columns: a:13!null b:14!null
 ├── cardinality: [0 - 2]
 ├── key: (13,14)
 ├── fd: (13,14)-->(15)
 ├── union-all
 │    ├── columns: a:13!null b:14!null d:15
 │    ├── left columns: t2.a:1 t2.b:2 t2.d:4
 │    ├── right columns: t2.a:7 t2.b:8 t2.d:10
 │    ├── cardinality: [0 - 2]
 │    ├── limit
 │    │    ├── columns: t2.a:1!null t2.b:2!null t2.d:4
 │    │    ├── cardinality: [0 - 1]
 │    │    ├── key: ()
 │    │    ├── fd: ()-->(1,2,4)
 │    │    ├── scan t2
 │    │    │    ├── columns: t2.a:1!null t2.b:2!null t2.d:4
 │    │    │    ├── key: (1,2)
 │    │    │    ├── fd: (1,2)-->(4), (4)~~>(1,2)
 │    │    │    └── limit hint: 1.00
 │    │    └── 1
 │    └── limit
 │         ├── columns: t2.a:7!null t2.b:8!null t2.d:10
 │         ├── cardinality: [0 - 1]
 │         ├── key: ()
 │         ├── fd: ()-->(7,8,10)
 │         ├── scan t2
 │         │    ├── columns: t2.a:7!null t2.b:8!null t2.d:10
 │         │    ├── key: (7,8)
 │         │    ├── fd: (7,8)-->(10), (10)~~>(7,8)
 │         │    └── limit hint: 1.00
 │         └── 1
 └── aggregations
      └── const-agg [as=d:15, outer=(15)]
           └── d:15

# Case where a secondary index key is used for the grouping.
norm expect=ConvertUnionToDistinctUnionAll
SELECT c, d FROM
  (SELECT c, d FROM t2 LIMIT 1)
UNION
  (SELECT c, d FROM t2 WHERE a = 1)
----
distinct-on
 ├── columns: c:13!null d:14
 ├── grouping columns: c:13!null
 ├── key: (13)
 ├── fd: (13)-->(14)
 ├── union-all
 │    ├── columns: c:13!null d:14
 │    ├── left columns: t2.c:3 t2.d:4
 │    ├── right columns: t2.c:9 t2.d:10
 │    ├── limit
 │    │    ├── columns: t2.c:3!null t2.d:4
 │    │    ├── cardinality: [0 - 1]
 │    │    ├── key: ()
 │    │    ├── fd: ()-->(3,4)
 │    │    ├── scan t2
 │    │    │    ├── columns: t2.c:3!null t2.d:4
 │    │    │    ├── key: (3)
 │    │    │    ├── fd: (3)-->(4), (4)~~>(3)
 │    │    │    └── limit hint: 1.00
 │    │    └── 1
 │    └── project
 │         ├── columns: t2.c:9!null t2.d:10
 │         ├── key: (9)
 │         ├── fd: (9)-->(10), (10)~~>(9)
 │         └── select
 │              ├── columns: a:7!null t2.c:9!null t2.d:10
 │              ├── key: (9)
 │              ├── fd: ()-->(7), (9)-->(10), (10)~~>(9)
 │              ├── scan t2
 │              │    ├── columns: a:7!null t2.c:9!null t2.d:10
 │              │    ├── key: (9)
 │              │    └── fd: (9)-->(7,10), (10)~~>(7,9)
 │              └── filters
 │                   └── a:7 = 1 [outer=(7), constraints=(/7: [/1 - /1]; tight), fd=()-->(7)]
 └── aggregations
      └── const-agg [as=d:14, outer=(14)]
           └── d:14

# Case where the left columns are null-extended (all columns filled with null
# values for null-extended rows). This is ok because grouping on any
# null-extended column has the same effect as grouping on all of them for the
# null rows - they all get grouped together.
norm expect=ConvertUnionToDistinctUnionAll
SELECT a, b, c FROM
  (SELECT a, b, c FROM
    (SELECT * FROM t
      FULL JOIN (VALUES (1), (2))
      ON False
    )
  )
UNION
  (SELECT a, b, c FROM t WHERE b > 10)
----
distinct-on
 ├── columns: a:12 b:13 c:14
 ├── grouping columns: a:12
 ├── cardinality: [1 - ]
 ├── key: (12)
 ├── fd: (12)-->(13,14)
 ├── union-all
 │    ├── columns: a:12 b:13 c:14
 │    ├── left columns: t.a:1 t.b:2 t.c:3
 │    ├── right columns: t.a:7 t.b:8 t.c:9
 │    ├── cardinality: [2 - ]
 │    ├── full-join (cross)
 │    │    ├── columns: t.a:1 t.b:2 t.c:3
 │    │    ├── cardinality: [2 - ]
 │    │    ├── fd: (1)-->(2,3)
 │    │    ├── scan t
 │    │    │    ├── columns: t.a:1!null t.b:2 t.c:3
 │    │    │    ├── key: (1)
 │    │    │    └── fd: (1)-->(2,3)
 │    │    ├── values
 │    │    │    ├── cardinality: [2 - 2]
 │    │    │    ├── ()
 │    │    │    └── ()
 │    │    └── filters
 │    │         └── false [constraints=(contradiction; tight)]
 │    └── select
 │         ├── columns: t.a:7!null t.b:8!null t.c:9
 │         ├── key: (7)
 │         ├── fd: (7)-->(8,9)
 │         ├── scan t
 │         │    ├── columns: t.a:7!null t.b:8 t.c:9
 │         │    ├── key: (7)
 │         │    └── fd: (7)-->(8,9)
 │         └── filters
 │              └── t.b:8 > 10 [outer=(8), constraints=(/8: [/11 - ]; tight)]
 └── aggregations
      ├── const-agg [as=b:13, outer=(13)]
      │    └── b:13
      └── const-agg [as=c:14, outer=(14)]
           └── c:14

# Case where the right columns are null-extended.
norm expect=ConvertUnionToDistinctUnionAll
SELECT a, b, c FROM
  (SELECT a, b, c FROM t WHERE b > 10)
UNION
  (SELECT a, b, c FROM
    (SELECT * FROM t
      FULL JOIN (VALUES (1), (2))
      ON False
    )
  )
----
distinct-on
 ├── columns: a:12 b:13 c:14
 ├── grouping columns: a:12
 ├── cardinality: [1 - ]
 ├── key: (12)
 ├── fd: (12)-->(13,14)
 ├── union-all
 │    ├── columns: a:12 b:13 c:14
 │    ├── left columns: t.a:1 t.b:2 t.c:3
 │    ├── right columns: t.a:6 t.b:7 t.c:8
 │    ├── cardinality: [2 - ]
 │    ├── select
 │    │    ├── columns: t.a:1!null t.b:2!null t.c:3
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(2,3)
 │    │    ├── scan t
 │    │    │    ├── columns: t.a:1!null t.b:2 t.c:3
 │    │    │    ├── key: (1)
 │    │    │    └── fd: (1)-->(2,3)
 │    │    └── filters
 │    │         └── t.b:2 > 10 [outer=(2), constraints=(/2: [/11 - ]; tight)]
 │    └── full-join (cross)
 │         ├── columns: t.a:6 t.b:7 t.c:8
 │         ├── cardinality: [2 - ]
 │         ├── fd: (6)-->(7,8)
 │         ├── scan t
 │         │    ├── columns: t.a:6!null t.b:7 t.c:8
 │         │    ├── key: (6)
 │         │    └── fd: (6)-->(7,8)
 │         ├── values
 │         │    ├── cardinality: [2 - 2]
 │         │    ├── ()
 │         │    └── ()
 │         └── filters
 │              └── false [constraints=(contradiction; tight)]
 └── aggregations
      ├── const-agg [as=b:13, outer=(13)]
      │    └── b:13
      └── const-agg [as=c:14, outer=(14)]
           └── c:14

# Case where the left columns are duplicated. This is ok because there are no
# tuples that did not exist in the base table, meaning it is ok to group on a
# set of columns that form a key over the base table.
norm expect=ConvertUnionToDistinctUnionAll
SELECT a, b, c FROM
  (SELECT a, b, c FROM
    (SELECT * FROM t
      INNER JOIN (VALUES (1), (2))
      ON True
    )
  )
UNION
  (SELECT a, b, c FROM t WHERE b > 10)
----
distinct-on
 ├── columns: a:12!null b:13 c:14
 ├── grouping columns: a:12!null
 ├── key: (12)
 ├── fd: (12)-->(13,14)
 ├── union-all
 │    ├── columns: a:12!null b:13 c:14
 │    ├── left columns: t.a:1 t.b:2 t.c:3
 │    ├── right columns: t.a:7 t.b:8 t.c:9
 │    ├── inner-join (cross)
 │    │    ├── columns: t.a:1!null t.b:2 t.c:3
 │    │    ├── multiplicity: left-rows(one-or-more), right-rows(zero-or-more)
 │    │    ├── fd: (1)-->(2,3)
 │    │    ├── scan t
 │    │    │    ├── columns: t.a:1!null t.b:2 t.c:3
 │    │    │    ├── key: (1)
 │    │    │    └── fd: (1)-->(2,3)
 │    │    ├── values
 │    │    │    ├── cardinality: [2 - 2]
 │    │    │    ├── ()
 │    │    │    └── ()
 │    │    └── filters (true)
 │    └── select
 │         ├── columns: t.a:7!null t.b:8!null t.c:9
 │         ├── key: (7)
 │         ├── fd: (7)-->(8,9)
 │         ├── scan t
 │         │    ├── columns: t.a:7!null t.b:8 t.c:9
 │         │    ├── key: (7)
 │         │    └── fd: (7)-->(8,9)
 │         └── filters
 │              └── t.b:8 > 10 [outer=(8), constraints=(/8: [/11 - ]; tight)]
 └── aggregations
      ├── const-agg [as=b:13, outer=(13)]
      │    └── b:13
      └── const-agg [as=c:14, outer=(14)]
           └── c:14

# Case where the right columns are duplicated. This is ok because there are no
# tuples that did not exist in the base table, meaning it is ok to group on a
# set of columns that form a key over the base table.
norm expect=ConvertUnionToDistinctUnionAll
SELECT a, b, c FROM
  (SELECT a, b, c FROM t WHERE b > 10)
UNION
  (SELECT a, b, c FROM
    (SELECT * FROM t
      INNER JOIN (VALUES (1), (2))
      ON True
    )
  )
----
distinct-on
 ├── columns: a:12!null b:13 c:14
 ├── grouping columns: a:12!null
 ├── key: (12)
 ├── fd: (12)-->(13,14)
 ├── union-all
 │    ├── columns: a:12!null b:13 c:14
 │    ├── left columns: t.a:1 t.b:2 t.c:3
 │    ├── right columns: t.a:6 t.b:7 t.c:8
 │    ├── select
 │    │    ├── columns: t.a:1!null t.b:2!null t.c:3
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(2,3)
 │    │    ├── scan t
 │    │    │    ├── columns: t.a:1!null t.b:2 t.c:3
 │    │    │    ├── key: (1)
 │    │    │    └── fd: (1)-->(2,3)
 │    │    └── filters
 │    │         └── t.b:2 > 10 [outer=(2), constraints=(/2: [/11 - ]; tight)]
 │    └── inner-join (cross)
 │         ├── columns: t.a:6!null t.b:7 t.c:8
 │         ├── multiplicity: left-rows(one-or-more), right-rows(zero-or-more)
 │         ├── fd: (6)-->(7,8)
 │         ├── scan t
 │         │    ├── columns: t.a:6!null t.b:7 t.c:8
 │         │    ├── key: (6)
 │         │    └── fd: (6)-->(7,8)
 │         ├── values
 │         │    ├── cardinality: [2 - 2]
 │         │    ├── ()
 │         │    └── ()
 │         └── filters (true)
 └── aggregations
      ├── const-agg [as=b:13, outer=(13)]
      │    └── b:13
      └── const-agg [as=c:14, outer=(14)]
           └── c:14

# No-op case because there is no key.
norm expect-not=ConvertUnionToDistinctUnionAll
SELECT b, c FROM
  (SELECT b, c FROM t WHERE c < 0)
UNION
  (SELECT b, c FROM t WHERE b > 10)
----
union
 ├── columns: b:11 c:12
 ├── left columns: t.b:2 t.c:3
 ├── right columns: t.b:7 t.c:8
 ├── key: (11,12)
 ├── select
 │    ├── columns: t.b:2 t.c:3!null
 │    ├── scan t
 │    │    └── columns: t.b:2 t.c:3
 │    └── filters
 │         └── t.c:3 < 0 [outer=(3), constraints=(/3: (/NULL - /-1]; tight)]
 └── select
      ├── columns: t.b:7!null t.c:8
      ├── scan t
      │    └── columns: t.b:7 t.c:8
      └── filters
           └── t.b:7 > 10 [outer=(7), constraints=(/7: [/11 - ]; tight)]

# No-op case because of the projection.
norm expect-not=ConvertUnionToDistinctUnionAll
SELECT a, b, c FROM
  (SELECT a, b, c FROM t WHERE a < 0)
UNION
  (SELECT a, b, c*2 FROM t WHERE b > 10)
----
union
 ├── columns: a:12!null b:13 c:14
 ├── left columns: t.a:1 t.b:2 t.c:3
 ├── right columns: t.a:6 t.b:7 "?column?":11
 ├── immutable
 ├── key: (12-14)
 ├── select
 │    ├── columns: t.a:1!null t.b:2 t.c:3
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── scan t
 │    │    ├── columns: t.a:1!null t.b:2 t.c:3
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2,3)
 │    └── filters
 │         └── t.a:1 < 0 [outer=(1), constraints=(/1: (/NULL - /-1]; tight)]
 └── project
      ├── columns: "?column?":11 t.a:6!null t.b:7!null
      ├── immutable
      ├── key: (6)
      ├── fd: (6)-->(7,11)
      ├── select
      │    ├── columns: t.a:6!null t.b:7!null t.c:8
      │    ├── key: (6)
      │    ├── fd: (6)-->(7,8)
      │    ├── scan t
      │    │    ├── columns: t.a:6!null t.b:7 t.c:8
      │    │    ├── key: (6)
      │    │    └── fd: (6)-->(7,8)
      │    └── filters
      │         └── t.b:7 > 10 [outer=(7), constraints=(/7: [/11 - ]; tight)]
      └── projections
           └── t.c:8 * 2 [as="?column?":11, outer=(8), immutable]

# No-op case because the columns don't all have the same ordinal positions.
norm expect-not=ConvertUnionToDistinctUnionAll
SELECT a, b, c FROM
  (SELECT a, b, c FROM t WHERE a < 0)
UNION
  (SELECT a, c, b FROM t WHERE b > 10)
----
union
 ├── columns: a:11!null b:12 c:13
 ├── left columns: t.a:1 t.b:2 t.c:3
 ├── right columns: t.a:6 t.c:8 t.b:7
 ├── key: (11-13)
 ├── select
 │    ├── columns: t.a:1!null t.b:2 t.c:3
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── scan t
 │    │    ├── columns: t.a:1!null t.b:2 t.c:3
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2,3)
 │    └── filters
 │         └── t.a:1 < 0 [outer=(1), constraints=(/1: (/NULL - /-1]; tight)]
 └── select
      ├── columns: t.a:6!null t.b:7!null t.c:8
      ├── key: (6)
      ├── fd: (6)-->(7,8)
      ├── scan t
      │    ├── columns: t.a:6!null t.b:7 t.c:8
      │    ├── key: (6)
      │    └── fd: (6)-->(7,8)
      └── filters
           └── t.b:7 > 10 [outer=(7), constraints=(/7: [/11 - ]; tight)]

# No-op case because there is no base table.
norm expect-not=ConvertUnionToDistinctUnionAll
SELECT * FROM
  (SELECT * FROM (VALUES (1, 1), (2, 1)))
UNION
  (SELECT * FROM (VALUES (1, 1), (2, 1)))
----
union
 ├── columns: column1:5!null column2:6!null
 ├── left columns: column1:1 column2:2
 ├── right columns: column1:3 column2:4
 ├── cardinality: [1 - 4]
 ├── key: (5,6)
 ├── values
 │    ├── columns: column1:1!null column2:2!null
 │    ├── cardinality: [2 - 2]
 │    ├── (1, 1)
 │    └── (2, 1)
 └── values
      ├── columns: column1:3!null column2:4!null
      ├── cardinality: [2 - 2]
      ├── (1, 1)
      └── (2, 1)

# No-op case because the index key is nullable.
norm expect-not=ConvertUnionToDistinctUnionAll
SELECT d FROM
  (SELECT d FROM t2 LIMIT 1)
UNION
  (SELECT d FROM t2 WHERE a = 1)
----
union
 ├── columns: d:13
 ├── left columns: t2.d:4
 ├── right columns: t2.d:10
 ├── key: (13)
 ├── limit
 │    ├── columns: t2.d:4
 │    ├── cardinality: [0 - 1]
 │    ├── key: ()
 │    ├── fd: ()-->(4)
 │    ├── scan t2
 │    │    ├── columns: t2.d:4
 │    │    ├── lax-key: (4)
 │    │    └── limit hint: 1.00
 │    └── 1
 └── project
      ├── columns: t2.d:10
      ├── lax-key: (10)
      └── select
           ├── columns: a:7!null t2.d:10
           ├── lax-key: (10)
           ├── fd: ()-->(7)
           ├── scan t2
           │    ├── columns: a:7!null t2.d:10
           │    ├── lax-key: (7,10)
           │    └── fd: (10)~~>(7)
           └── filters
                └── a:7 = 1 [outer=(7), constraints=(/7: [/1 - /1]; tight), fd=()-->(7)]

# No-op case because the left side has columns from more than one meta table.
norm expect-not=ConvertUnionToDistinctUnionAll
SELECT a, b, c FROM
  (SELECT t.a, t.b, foo.c FROM t FULL JOIN t as foo ON False)
UNION
  (SELECT a, b, c FROM t WHERE a > 10)
----
union
 ├── columns: a:16 b:17 c:18
 ├── left columns: t.a:1 t.b:2 foo.c:8
 ├── right columns: t.a:11 t.b:12 t.c:13
 ├── key: (16-18)
 ├── full-join (cross)
 │    ├── columns: t.a:1 t.b:2 foo.c:8
 │    ├── fd: (1)-->(2)
 │    ├── scan t
 │    │    ├── columns: t.a:1!null t.b:2
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2)
 │    ├── scan t [as=foo]
 │    │    └── columns: foo.c:8
 │    └── filters
 │         └── false [constraints=(contradiction; tight)]
 └── select
      ├── columns: t.a:11!null t.b:12 t.c:13
      ├── key: (11)
      ├── fd: (11)-->(12,13)
      ├── scan t
      │    ├── columns: t.a:11!null t.b:12 t.c:13
      │    ├── key: (11)
      │    └── fd: (11)-->(12,13)
      └── filters
           └── t.a:11 > 10 [outer=(11), constraints=(/11: [/11 - ]; tight)]

# No-op case because the right side has columns from more than one meta table.
norm expect-not=ConvertUnionToDistinctUnionAll
SELECT a, b, c FROM
  (SELECT a, b, c FROM t WHERE a > 10)
UNION
  (SELECT t.a, t.b, foo.c FROM t FULL JOIN t as foo ON False)
----
union
 ├── columns: a:16 b:17 c:18
 ├── left columns: t.a:1 t.b:2 t.c:3
 ├── right columns: t.a:6 t.b:7 foo.c:13
 ├── key: (16-18)
 ├── select
 │    ├── columns: t.a:1!null t.b:2 t.c:3
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── scan t
 │    │    ├── columns: t.a:1!null t.b:2 t.c:3
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2,3)
 │    └── filters
 │         └── t.a:1 > 10 [outer=(1), constraints=(/1: [/11 - ]; tight)]
 └── full-join (cross)
      ├── columns: t.a:6 t.b:7 foo.c:13
      ├── fd: (6)-->(7)
      ├── scan t
      │    ├── columns: t.a:6!null t.b:7
      │    ├── key: (6)
      │    └── fd: (6)-->(7)
      ├── scan t [as=foo]
      │    └── columns: foo.c:13
      └── filters
           └── false [constraints=(contradiction; tight)]

# No-op case because the left and right columns come from different base tables.
norm expect-not=ConvertUnionToDistinctUnionAll
SELECT a, b, c FROM
  (SELECT a, b, c FROM t WHERE a < 0)
UNION
  (SELECT a, b, c FROM t2 WHERE b > 10)
----
union
 ├── columns: a:12!null b:13 c:14
 ├── left columns: t.a:1 t.b:2 t.c:3
 ├── right columns: t2.a:6 t2.b:7 t2.c:8
 ├── key: (12-14)
 ├── select
 │    ├── columns: t.a:1!null t.b:2 t.c:3
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── scan t
 │    │    ├── columns: t.a:1!null t.b:2 t.c:3
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2,3)
 │    └── filters
 │         └── t.a:1 < 0 [outer=(1), constraints=(/1: (/NULL - /-1]; tight)]
 └── select
      ├── columns: t2.a:6!null t2.b:7!null t2.c:8!null
      ├── key: (8)
      ├── fd: (6,7)-->(8), (8)-->(6,7)
      ├── scan t2
      │    ├── columns: t2.a:6!null t2.b:7!null t2.c:8!null
      │    ├── key: (8)
      │    └── fd: (6,7)-->(8), (8)-->(6,7)
      └── filters
           └── t2.b:7 > 10 [outer=(7), constraints=(/7: [/11 - ]; tight)]

# No-op case because the key columns are not a strict subset of the union cols.
norm expect-not=ConvertUnionToDistinctUnionAll
SELECT a FROM
  (SELECT a FROM t WHERE a < 0)
UNION
  (SELECT a FROM t WHERE b > 10)
----
union
 ├── columns: a:11!null
 ├── left columns: t.a:1
 ├── right columns: t.a:6
 ├── key: (11)
 ├── select
 │    ├── columns: t.a:1!null
 │    ├── key: (1)
 │    ├── scan t
 │    │    ├── columns: t.a:1!null
 │    │    └── key: (1)
 │    └── filters
 │         └── t.a:1 < 0 [outer=(1), constraints=(/1: (/NULL - /-1]; tight)]
 └── project
      ├── columns: t.a:6!null
      ├── key: (6)
      └── select
           ├── columns: t.a:6!null b:7!null
           ├── key: (6)
           ├── fd: (6)-->(7)
           ├── scan t
           │    ├── columns: t.a:6!null b:7
           │    ├── key: (6)
           │    └── fd: (6)-->(7)
           └── filters
                └── b:7 > 10 [outer=(7), constraints=(/7: [/11 - ]; tight)]

# Regression test for #85502 - don't fire when the candidate key is empty.
exec-ddl
CREATE TABLE t1_85502 (c0 BOOL AS (1 IS NULL) STORED, CONSTRAINT "primary" PRIMARY KEY(c0));
----

exec-ddl
CREATE TABLE t2_85502 (c0 INT);
----

norm expect-not=ConvertUnionToDistinctUnionAll
SELECT t1_85502.c0 FROM t2_85502
FULL OUTER JOIN t1_85502 ON false WHERE false IN (t1_85502.c0 IS NULL)
UNION SELECT t1_85502.c0 FROM t2_85502
FULL OUTER JOIN t1_85502 ON false WHERE NOT false IN (t1_85502.c0 IS NULL);
----
union
 ├── columns: c0:15
 ├── left columns: t1_85502.c0:5
 ├── right columns: t1_85502.c0:12
 ├── cardinality: [0 - 3]
 ├── key: (15)
 ├── scan t1_85502
 │    ├── columns: t1_85502.c0:5!null
 │    ├── computed column expressions
 │    │    └── t1_85502.c0:5
 │    │         └── false
 │    ├── cardinality: [0 - 1]
 │    ├── key: ()
 │    └── fd: ()-->(5)
 └── select
      ├── columns: t1_85502.c0:12
      ├── fd: ()-->(12)
      ├── full-join (cross)
      │    ├── columns: t1_85502.c0:12
      │    ├── multiplicity: left-rows(exactly-one), right-rows(one-or-more)
      │    ├── scan t2_85502
      │    ├── scan t1_85502
      │    │    ├── columns: t1_85502.c0:12!null
      │    │    ├── computed column expressions
      │    │    │    └── t1_85502.c0:12
      │    │    │         └── false
      │    │    ├── cardinality: [0 - 1]
      │    │    ├── key: ()
      │    │    └── fd: ()-->(12)
      │    └── filters
      │         └── false [constraints=(contradiction; tight)]
      └── filters
           └── t1_85502.c0:12 IS NULL [outer=(12), constraints=(/12: [/NULL - /NULL]; tight), fd=()-->(12)]
