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

exec-ddl
CREATE TABLE pqrs
(
    p INT NOT NULL,
    q INT NOT NULL,
    r INT NOT NULL,
    s INT NOT NULL,
    PRIMARY KEY (p, q),
    CHECK (p = 1 OR p = 5 OR p = 10),
    INDEX (r, s),
    CHECK (r > 0),
    CHECK (r < 3)
)
----

exec-ddl
ALTER TABLE pqrs INJECT STATISTICS '[
  {
    "columns": ["p"],
    "created_at": "2018-01-01 1:00:00.00000+00:00",
    "row_count": 1000,
    "distinct_count": 10
  }
]'
----

exec-ddl
CREATE TABLE index_tab
(
    id INT PRIMARY KEY,
    val INT,
    region STRING,
    latitude INT,
    longitude INT,
    data1 INT NOT NULL,
    data2 INT NOT NULL,
    geom GEOMETRY,
    INDEX a (id, data1, data2),
    INDEX b (val, data1, data2),
    INDEX c (region, data1, data2),
    INDEX d (latitude, longitude, data1, data2),
    INVERTED INDEX geomIdx(geom)
)
----

exec-ddl
CREATE TABLE partial_index_tab
(
    a INT,
    b INT,
    INDEX (a) WHERE b > 0
)
----

exec-ddl
CREATE TABLE partial_index_const
(
    a INT,
    b INT,
    c INT,
    INDEX (a) STORING (c) WHERE b = 1,
    INDEX (a) STORING (c) WHERE b IS NULL
)
----

# Insert statistics for index_tab. Histogram buckets are included for the
# latitude column in order to make the optimizer choose specific plans for
# SplitLimitedScanIntoUnionScans tests.
exec-ddl
ALTER TABLE index_tab INJECT STATISTICS '[
  {
    "columns": ["val"],
    "created_at": "2018-01-01 1:00:00.00000+00:00",
    "row_count": 1000000,
    "distinct_count": 100
  },
  {
    "columns": ["region"],
    "created_at": "2018-01-01 1:30:00.00000+00:00",
    "row_count": 1000000,
    "distinct_count": 1000
  },
  {
    "columns": ["latitude"],
    "created_at": "2018-01-01 2:00:00.00000+00:00",
    "row_count": 1000000,
    "distinct_count": 100,
    "null_count": 0,
    "histo_col_type": "int",
    "histo_buckets": [
      {"num_eq": 1000, "num_range": 0, "distinct_range": 0, "upper_bound": "-5"},
      {"num_eq": 1000, "num_range": 0, "distinct_range": 0, "upper_bound": "0"},
      {"num_eq": 0, "num_range": 998000, "distinct_range": 98, "upper_bound": "2000"}
    ]
  },
  {
    "columns": ["longitude"],
    "created_at": "2018-01-01 2:30:00.00000+00:00",
    "row_count": 1000000,
    "distinct_count": 100
  },
  {
    "columns": ["data1"],
    "created_at": "2018-01-01 3:00:00.00000+00:00",
    "row_count": 1000000,
    "distinct_count": 100
  },
  {
    "columns": ["data2"],
    "created_at": "2018-01-01 3:30:00.00000+00:00",
    "row_count": 1000000,
    "distinct_count": 100
  }
]'
----

# ---------------------------------------------------
# GenerateLimitedScans / PushLimitIntoFilteredScan
# ---------------------------------------------------

opt
SELECT * FROM a LIMIT 1
----
scan a
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── limit: 1
 ├── key: ()
 └── fd: ()-->(1-5)

# Combine limit with needed columns.
opt
SELECT s FROM a LIMIT 1
----
scan a@s_idx
 ├── columns: s:4
 ├── limit: 1
 ├── key: ()
 └── fd: ()-->(4)

# Combine limit with constraint.
opt
SELECT s FROM a WHERE s='foo' LIMIT 1
----
scan a@s_idx
 ├── columns: s:4!null
 ├── constraint: /4/1: [/'foo' - /'foo']
 ├── limit: 1
 ├── key: ()
 └── fd: ()-->(4)

# Limit of a limit.
opt
SELECT s FROM (SELECT s, i FROM a ORDER BY s LIMIT 10) a ORDER BY s, i LIMIT 1
----
top-k
 ├── columns: s:4  [hidden: i:2]
 ├── internal-ordering: +4,+2
 ├── k: 1
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(2,4)
 └── scan a@s_idx
      ├── columns: i:2 s:4
      └── limit: 10

# Limit an unconstrained partial index scan.
opt
SELECT a FROM partial_index_tab where b > 0 LIMIT 1
----
project
 ├── columns: a:1
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── scan partial_index_tab@partial_index_tab_a_idx,partial
      ├── columns: a:1 rowid:3!null
      ├── limit: 1
      ├── key: ()
      └── fd: ()-->(1,3)

# Limit a constrained partial index scan.
opt
SELECT a FROM partial_index_tab where b > 0 and a > 10 LIMIT 1
----
project
 ├── columns: a:1!null
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── scan partial_index_tab@partial_index_tab_a_idx,partial
      ├── columns: a:1!null rowid:3!null
      ├── constraint: /1/3: [/11 - ]
      ├── limit: 1
      ├── key: ()
      └── fd: ()-->(1,3)

# Don't push when scan doesn't satisfy limit's ordering.
opt
SELECT s FROM a ORDER BY f LIMIT 1
----
top-k
 ├── columns: s:4  [hidden: f:3]
 ├── internal-ordering: +3
 ├── k: 1
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(3,4)
 └── scan a@s_idx
      └── columns: f:3 s:4

# Don't push when limit is not a constant.
opt
SELECT s FROM a LIMIT (SELECT k FROM a LIMIT 1)
----
limit
 ├── columns: s:4
 ├── immutable
 ├── scan a@s_idx
 │    └── columns: s:4
 └── subquery
      └── scan a@s_idx
           ├── columns: k:8!null
           ├── limit: 1
           ├── key: ()
           └── fd: ()-->(8)

memo
SELECT s FROM a WHERE s='foo' LIMIT 1
----
memo (optimized, ~9KB, required=[presentation: s:4])
 ├── G1: (limit G2 G3) (scan a@s_idx,cols=(4),constrained,lim=1) (scan a@si_idx,cols=(4),constrained,lim=1)
 │    └── [presentation: s:4]
 │         ├── best: (scan a@s_idx,cols=(4),constrained,lim=1)
 │         └── cost: 9.06
 ├── G2: (select G4 G5) (scan a@s_idx,cols=(4),constrained) (scan a@si_idx,cols=(4),constrained)
 │    └── [limit hint: 1.00]
 │         ├── best: (scan a@s_idx,cols=(4),constrained)
 │         └── cost: 19.07
 ├── G3: (const 1)
 ├── G4: (scan a,cols=(4)) (scan a@s_idx,cols=(4)) (scan a@si_idx,cols=(4))
 │    └── [limit hint: 100.00]
 │         ├── best: (scan a@s_idx,cols=(4))
 │         └── cost: 123.02
 ├── G5: (filters G6)
 ├── G6: (eq G7 G8)
 ├── G7: (variable s)
 └── G8: (const 'foo')

# GenerateLimitedScans propagates row-level locking information.
opt
SELECT * FROM a LIMIT 1 FOR UPDATE
----
scan a
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── limit: 1
 ├── locking: for-update
 ├── volatile
 ├── key: ()
 └── fd: ()-->(1-5)

# PushLimitIntoFilteredScan propagates row-level locking information.
opt
SELECT s FROM a WHERE s='foo' LIMIT 1 FOR UPDATE
----
scan a@s_idx
 ├── columns: s:4!null
 ├── constraint: /4/1: [/'foo' - /'foo']
 ├── limit: 1
 ├── locking: for-update
 ├── volatile
 ├── key: ()
 └── fd: ()-->(4)

# --------------------------------------------------
# PushLimitIntoProjectFilteredScan
# --------------------------------------------------

opt expect=PushLimitIntoProjectFilteredScan
SELECT * FROM partial_index_const WHERE a > 0 AND b = 1 LIMIT 5
----
project
 ├── columns: a:1!null b:2!null c:3
 ├── cardinality: [0 - 5]
 ├── fd: ()-->(2)
 ├── scan partial_index_const@partial_index_const_a_idx,partial
 │    ├── columns: a:1!null c:3
 │    ├── constraint: /1/4: [/1 - ]
 │    └── limit: 5
 └── projections
      └── 1 [as=b:2]

opt expect=PushLimitIntoProjectFilteredScan
SELECT * FROM partial_index_const WHERE a > 0 AND b IS NULL LIMIT 5
----
project
 ├── columns: a:1!null b:2 c:3
 ├── cardinality: [0 - 5]
 ├── fd: ()-->(2)
 ├── scan partial_index_const@partial_index_const_a_idx1,partial
 │    ├── columns: a:1!null c:3
 │    ├── constraint: /1/4: [/1 - ]
 │    └── limit: 5
 └── projections
      └── CAST(NULL AS INT8) [as=b:2]

opt expect=PushLimitIntoProjectFilteredScan
SELECT * FROM partial_index_const WHERE a > 0 AND b = 1 LIMIT 5 OFFSET 10
----
offset
 ├── columns: a:1!null b:2!null c:3
 ├── cardinality: [0 - 5]
 ├── fd: ()-->(2)
 ├── project
 │    ├── columns: b:2!null a:1!null c:3
 │    ├── cardinality: [0 - 15]
 │    ├── fd: ()-->(2)
 │    ├── scan partial_index_const@partial_index_const_a_idx,partial
 │    │    ├── columns: a:1!null c:3
 │    │    ├── constraint: /1/4: [/1 - ]
 │    │    └── limit: 15
 │    └── projections
 │         └── 1 [as=b:2]
 └── 10

# PushLimitIntoProjectFilteredScan propagates row-level locking information.
opt expect=PushLimitIntoProjectFilteredScan
SELECT * FROM partial_index_const WHERE a > 0 AND b = 1 LIMIT 5 FOR UPDATE
----
project
 ├── columns: a:1!null b:2!null c:3
 ├── cardinality: [0 - 5]
 ├── volatile
 ├── fd: ()-->(2)
 ├── scan partial_index_const@partial_index_const_a_idx,partial
 │    ├── columns: a:1!null c:3
 │    ├── constraint: /1/4: [/1 - ]
 │    ├── limit: 5
 │    ├── locking: for-update
 │    └── volatile
 └── projections
      └── 1 [as=b:2]

opt set=(optimizer_use_lock_op_for_serializable=true) expect=PushLimitIntoProjectFilteredScan
SELECT * FROM partial_index_const WHERE a > 0 AND b = 1 LIMIT 5 FOR UPDATE
----
lock partial_index_const
 ├── columns: a:1!null b:2!null c:3  [hidden: rowid:4!null]
 ├── key columns: rowid:4
 ├── lock columns: (7-10)
 ├── locking: for-update
 ├── cardinality: [0 - 5]
 ├── volatile, mutations
 ├── key: (4)
 ├── fd: ()-->(2), (4)-->(1,3)
 └── project
      ├── columns: b:2!null a:1!null c:3 rowid:4!null
      ├── cardinality: [0 - 5]
      ├── key: (4)
      ├── fd: ()-->(2), (4)-->(1,3)
      ├── scan partial_index_const@partial_index_const_a_idx,partial
      │    ├── columns: a:1!null c:3 rowid:4!null
      │    ├── constraint: /1/4: [/1 - ]
      │    ├── limit: 5
      │    ├── key: (4)
      │    └── fd: (4)-->(1,3)
      └── projections
           └── 1 [as=b:2]

opt set=(optimizer_push_limit_into_project_filtered_scan=off) expect-not=PushLimitIntoProjectFilteredScan
SELECT * FROM partial_index_const WHERE a > 0 AND b = 1 LIMIT 5
----
limit
 ├── columns: a:1!null b:2!null c:3
 ├── cardinality: [0 - 5]
 ├── fd: ()-->(2)
 ├── project
 │    ├── columns: b:2!null a:1!null c:3
 │    ├── fd: ()-->(2)
 │    ├── limit hint: 5.00
 │    ├── scan partial_index_const@partial_index_const_a_idx,partial
 │    │    ├── columns: a:1!null c:3
 │    │    ├── constraint: /1/4: [/1 - ]
 │    │    └── limit hint: 5.00
 │    └── projections
 │         └── 1 [as=b:2]
 └── 5

# The rule does not apply when the limit is non-positive.
opt expect-not=PushLimitIntoProjectFilteredScan
SELECT * FROM partial_index_const WHERE a > 0 AND b = 1 LIMIT -5
----
limit
 ├── columns: a:1!null b:2!null c:3
 ├── cardinality: [0 - 0]
 ├── immutable
 ├── key: ()
 ├── fd: ()-->(1-3)
 ├── project
 │    ├── columns: b:2!null a:1!null c:3
 │    ├── fd: ()-->(2)
 │    ├── limit hint: 1.00
 │    ├── scan partial_index_const@partial_index_const_a_idx,partial
 │    │    ├── columns: a:1!null c:3
 │    │    ├── constraint: /1/4: [/1 - ]
 │    │    └── limit hint: 1.00
 │    └── projections
 │         └── 1 [as=b:2]
 └── -5

# The rule does not apply to non-filtered scans.
opt disable=PushLimitIntoProject expect-not=PushLimitIntoProjectFilteredScan
SELECT a, b, a+1 FROM partial_index_const LIMIT 5
----
limit
 ├── columns: a:1 b:2 "?column?":7
 ├── cardinality: [0 - 5]
 ├── immutable
 ├── fd: (1)-->(7)
 ├── project
 │    ├── columns: "?column?":7 a:1 b:2
 │    ├── immutable
 │    ├── fd: (1)-->(7)
 │    ├── limit hint: 5.00
 │    ├── scan partial_index_const
 │    │    ├── columns: a:1 b:2
 │    │    ├── partial index predicates
 │    │    │    ├── partial_index_const_a_idx: filters
 │    │    │    │    └── b:2 = 1 [outer=(2), constraints=(/2: [/1 - /1]; tight), fd=()-->(2)]
 │    │    │    └── partial_index_const_a_idx1: filters
 │    │    │         └── b:2 IS NULL [outer=(2), constraints=(/2: [/NULL - /NULL]; tight), fd=()-->(2)]
 │    │    └── limit hint: 5.00
 │    └── projections
 │         └── a:1 + 1 [as="?column?":7, outer=(1), immutable]
 └── 5

# The rule does not apply to filtered scans that cannot provide the desired
# ordering.
opt disable=GenerateTopK expect-not=PushLimitIntoProjectFilteredScan
SELECT * FROM partial_index_const WHERE a > 0 AND b = 1 ORDER BY c LIMIT 5
----
limit
 ├── columns: a:1!null b:2!null c:3
 ├── internal-ordering: +3 opt(2)
 ├── cardinality: [0 - 5]
 ├── fd: ()-->(2)
 ├── ordering: +3 opt(2) [actual: +3]
 ├── project
 │    ├── columns: b:2!null a:1!null c:3
 │    ├── fd: ()-->(2)
 │    ├── ordering: +3 opt(2) [actual: +3]
 │    ├── limit hint: 5.00
 │    ├── sort
 │    │    ├── columns: a:1!null c:3
 │    │    ├── ordering: +3
 │    │    ├── limit hint: 5.00
 │    │    └── scan partial_index_const@partial_index_const_a_idx,partial
 │    │         ├── columns: a:1!null c:3
 │    │         └── constraint: /1/4: [/1 - ]
 │    └── projections
 │         └── 1 [as=b:2]
 └── 5

# --------------------------------------------------
# PushLimitIntoIndexJoin
# --------------------------------------------------

exec-ddl
CREATE TABLE kuv (k INT, u INT, v INT, INDEX (k, u))
----

opt expect=PushLimitIntoIndexJoin
SELECT * FROM kuv WHERE k = 1 OR k = 2 ORDER BY u LIMIT 5
----
index-join kuv
 ├── columns: k:1!null u:2 v:3
 ├── cardinality: [0 - 5]
 ├── ordering: +2
 └── limit
      ├── columns: k:1!null u:2 rowid:4!null
      ├── internal-ordering: +2
      ├── cardinality: [0 - 5]
      ├── key: (4)
      ├── fd: (4)-->(1,2)
      ├── ordering: +2
      ├── union-all
      │    ├── columns: k:1!null u:2 rowid:4!null
      │    ├── left columns: k:7 u:8 rowid:10
      │    ├── right columns: k:13 u:14 rowid:16
      │    ├── cardinality: [0 - 10]
      │    ├── ordering: +2
      │    ├── limit hint: 5.00
      │    ├── scan kuv@kuv_k_u_idx
      │    │    ├── columns: k:7!null u:8 rowid:10!null
      │    │    ├── constraint: /7/8/10: [/1 - /1]
      │    │    ├── limit: 5
      │    │    ├── key: (10)
      │    │    ├── fd: ()-->(7), (10)-->(8)
      │    │    ├── ordering: +8 opt(7) [actual: +8]
      │    │    └── limit hint: 5.00
      │    └── scan kuv@kuv_k_u_idx
      │         ├── columns: k:13!null u:14 rowid:16!null
      │         ├── constraint: /13/14/16: [/2 - /2]
      │         ├── limit: 5
      │         ├── key: (16)
      │         ├── fd: ()-->(13), (16)-->(14)
      │         ├── ordering: +14 opt(13) [actual: +14]
      │         └── limit hint: 5.00
      └── 5

# Ensure that the limit is not pushed down when the ordering requires columns
# produced by the IndexJoin.
opt expect-not=PushLimitIntoIndexJoin
SELECT * FROM kuv WHERE u > 1 AND u < 10 ORDER BY u, v LIMIT 5
----
top-k
 ├── columns: k:1 u:2!null v:3
 ├── internal-ordering: +2,+3
 ├── k: 5
 ├── cardinality: [0 - 5]
 ├── ordering: +2,+3
 └── select
      ├── columns: k:1 u:2!null v:3
      ├── scan kuv
      │    └── columns: k:1 u:2 v:3
      └── filters
           └── (u:2 > 1) AND (u:2 < 10) [outer=(2), constraints=(/2: [/2 - /9]; tight)]

# Ensure that the limit is not pushed down when using SKIP LOCKED.
# We can push down a limit hint, though.
opt expect-not=PushLimitIntoIndexJoin
SELECT * FROM kuv WHERE k = 1 ORDER BY u LIMIT 5 FOR UPDATE SKIP LOCKED
----
limit
 ├── columns: k:1!null u:2 v:3
 ├── internal-ordering: +2 opt(1)
 ├── cardinality: [0 - 5]
 ├── volatile
 ├── fd: ()-->(1)
 ├── ordering: +2 opt(1) [actual: +2]
 ├── index-join kuv
 │    ├── columns: k:1!null u:2 v:3
 │    ├── locking: for-update,skip-locked
 │    ├── volatile
 │    ├── fd: ()-->(1)
 │    ├── ordering: +2 opt(1) [actual: +2]
 │    ├── limit hint: 5.00
 │    └── scan kuv@kuv_k_u_idx
 │         ├── columns: k:1!null u:2 rowid:4!null
 │         ├── constraint: /1/2/4: [/1 - /1]
 │         ├── locking: for-update,skip-locked
 │         ├── volatile
 │         ├── key: (4)
 │         ├── fd: ()-->(1), (4)-->(2)
 │         ├── ordering: +2 opt(1) [actual: +2]
 │         └── limit hint: 5.00
 └── 5

exec-ddl
CREATE TABLE abcd (
  a INT PRIMARY KEY,
  b INT,
  c INT,
  d INT,
  INDEX b (b),
  INDEX cd (c,d),
  UNIQUE INDEX bcd (b,c,d)
)
----

opt
EXPLAIN SELECT * FROM abcd@b WHERE a >= 20 AND a <= 30 ORDER BY b DESC LIMIT 5
----
explain
 ├── columns: info:7
 └── limit
      ├── columns: a:1!null b:2 c:3 d:4
      ├── internal-ordering: -2
      ├── cardinality: [0 - 5]
      ├── key: (1)
      ├── fd: (1)-->(2-4), (2-4)~~>(1)
      ├── ordering: -2
      ├── select
      │    ├── columns: a:1!null b:2 c:3 d:4
      │    ├── cardinality: [0 - 11]
      │    ├── key: (1)
      │    ├── fd: (1)-->(2-4), (2-4)~~>(1)
      │    ├── ordering: -2
      │    ├── limit hint: 5.00
      │    ├── index-join abcd
      │    │    ├── columns: a:1!null b:2 c:3 d:4
      │    │    ├── key: (1)
      │    │    ├── fd: (1)-->(2-4), (2-4)~~>(1)
      │    │    ├── ordering: -2
      │    │    ├── limit hint: 454.55
      │    │    └── scan abcd@b,rev
      │    │         ├── columns: a:1!null b:2
      │    │         ├── flags: force-index=b
      │    │         ├── key: (1)
      │    │         ├── fd: (1)-->(2)
      │    │         ├── ordering: -2
      │    │         └── limit hint: 454.55
      │    └── filters
      │         └── (a:1 >= 20) AND (a:1 <= 30) [outer=(1), constraints=(/1: [/20 - /30]; tight)]
      └── 5

optsteps
EXPLAIN SELECT * FROM abcd@b WHERE a >= 20 AND a <= 30 ORDER BY b DESC LIMIT 5
----
================================================================================
Initial expression
  Cost: 10000000000000000159028911097599180468360808563945281389781327557747838772170381060813469985856815104.00
================================================================================
  explain
   ├── columns: info:7
   └── sort
        ├── columns: a:1!null b:2 c:3 d:4
        ├── cardinality: [0 - 5]
        ├── key: (1)
        ├── fd: (1)-->(2-4), (2-4)~~>(1)
        ├── ordering: -2
        └── limit
             ├── columns: a:1!null b:2 c:3 d:4
             ├── internal-ordering: -2
             ├── cardinality: [0 - 5]
             ├── key: (1)
             ├── fd: (1)-->(2-4), (2-4)~~>(1)
             ├── sort
             │    ├── columns: a:1!null b:2 c:3 d:4
             │    ├── cardinality: [0 - 11]
             │    ├── key: (1)
             │    ├── fd: (1)-->(2-4), (2-4)~~>(1)
             │    ├── ordering: -2
             │    ├── limit hint: 5.00
             │    └── project
             │         ├── columns: a:1!null b:2 c:3 d:4
             │         ├── cardinality: [0 - 11]
             │         ├── key: (1)
             │         ├── fd: (1)-->(2-4), (2-4)~~>(1)
             │         └── select
             │              ├── columns: a:1!null b:2 c:3 d:4 crdb_internal_mvcc_timestamp:5 tableoid:6
             │              ├── cardinality: [0 - 11]
             │              ├── key: (1)
             │              ├── fd: (1)-->(2-6), (2-4)~~>(1,5,6)
             │              ├── scan abcd
             │              │    ├── columns: a:1!null b:2 c:3 d:4 crdb_internal_mvcc_timestamp:5 tableoid:6
             │              │    ├── flags: force-index=b
             │              │    ├── key: (1)
             │              │    └── fd: (1)-->(2-6), (2-4)~~>(1,5,6)
             │              └── filters
             │                   └── (a:1 >= 20) AND (a:1 <= 30) [outer=(1), constraints=(/1: [/20 - /30]; tight)]
             └── 5
================================================================================
SimplifySelectFilters
  Cost: 10000000000000000159028911097599180468360808563945281389781327557747838772170381060813469985856815104.00
================================================================================
   explain
    ├── columns: info:7
    └── sort
         ├── columns: a:1!null b:2 c:3 d:4
         ├── cardinality: [0 - 5]
         ├── key: (1)
         ├── fd: (1)-->(2-4), (2-4)~~>(1)
         ├── ordering: -2
         └── limit
              ├── columns: a:1!null b:2 c:3 d:4
              ├── internal-ordering: -2
              ├── cardinality: [0 - 5]
              ├── key: (1)
              ├── fd: (1)-->(2-4), (2-4)~~>(1)
              ├── sort
              │    ├── columns: a:1!null b:2 c:3 d:4
  -           │    ├── cardinality: [0 - 11]
              │    ├── key: (1)
              │    ├── fd: (1)-->(2-4), (2-4)~~>(1)
              │    ├── ordering: -2
              │    ├── limit hint: 5.00
              │    └── project
              │         ├── columns: a:1!null b:2 c:3 d:4
  -           │         ├── cardinality: [0 - 11]
              │         ├── key: (1)
              │         ├── fd: (1)-->(2-4), (2-4)~~>(1)
              │         └── select
              │              ├── columns: a:1!null b:2 c:3 d:4 crdb_internal_mvcc_timestamp:5 tableoid:6
  -           │              ├── cardinality: [0 - 11]
              │              ├── key: (1)
              │              ├── fd: (1)-->(2-6), (2-4)~~>(1,5,6)
              │              ├── scan abcd
              │              │    ├── columns: a:1!null b:2 c:3 d:4 crdb_internal_mvcc_timestamp:5 tableoid:6
              │              │    ├── flags: force-index=b
              │              │    ├── key: (1)
              │              │    └── fd: (1)-->(2-6), (2-4)~~>(1,5,6)
              │              └── filters
  -           │                   └── (a:1 >= 20) AND (a:1 <= 30) [outer=(1), constraints=(/1: [/20 - /30]; tight)]
  +           │                   ├── a:1 >= 20 [outer=(1), constraints=(/1: [/20 - ]; tight)]
  +           │                   └── a:1 <= 30 [outer=(1), constraints=(/1: (/NULL - /30]; tight)]
              └── 5
================================================================================
ConsolidateSelectFilters
  Cost: 10000000000000000159028911097599180468360808563945281389781327557747838772170381060813469985856815104.00
================================================================================
   explain
    ├── columns: info:7
    └── sort
         ├── columns: a:1!null b:2 c:3 d:4
         ├── cardinality: [0 - 5]
         ├── key: (1)
         ├── fd: (1)-->(2-4), (2-4)~~>(1)
         ├── ordering: -2
         └── limit
              ├── columns: a:1!null b:2 c:3 d:4
              ├── internal-ordering: -2
              ├── cardinality: [0 - 5]
              ├── key: (1)
              ├── fd: (1)-->(2-4), (2-4)~~>(1)
              ├── sort
              │    ├── columns: a:1!null b:2 c:3 d:4
  +           │    ├── cardinality: [0 - 11]
              │    ├── key: (1)
              │    ├── fd: (1)-->(2-4), (2-4)~~>(1)
              │    ├── ordering: -2
              │    ├── limit hint: 5.00
              │    └── project
              │         ├── columns: a:1!null b:2 c:3 d:4
  +           │         ├── cardinality: [0 - 11]
              │         ├── key: (1)
              │         ├── fd: (1)-->(2-4), (2-4)~~>(1)
              │         └── select
              │              ├── columns: a:1!null b:2 c:3 d:4 crdb_internal_mvcc_timestamp:5 tableoid:6
  +           │              ├── cardinality: [0 - 11]
              │              ├── key: (1)
              │              ├── fd: (1)-->(2-6), (2-4)~~>(1,5,6)
              │              ├── scan abcd
              │              │    ├── columns: a:1!null b:2 c:3 d:4 crdb_internal_mvcc_timestamp:5 tableoid:6
              │              │    ├── flags: force-index=b
              │              │    ├── key: (1)
              │              │    └── fd: (1)-->(2-6), (2-4)~~>(1,5,6)
              │              └── filters
  -           │                   ├── a:1 >= 20 [outer=(1), constraints=(/1: [/20 - ]; tight)]
  -           │                   └── a:1 <= 30 [outer=(1), constraints=(/1: (/NULL - /30]; tight)]
  +           │                   └── (a:1 >= 20) AND (a:1 <= 30) [outer=(1), constraints=(/1: [/20 - /30]; tight)]
              └── 5
================================================================================
PruneSelectCols
  Cost: 10000000000000000159028911097599180468360808563945281389781327557747838772170381060813469985856815104.00
================================================================================
   explain
    ├── columns: info:7
    └── sort
         ├── columns: a:1!null b:2 c:3 d:4
         ├── cardinality: [0 - 5]
         ├── key: (1)
         ├── fd: (1)-->(2-4), (2-4)~~>(1)
         ├── ordering: -2
         └── limit
              ├── columns: a:1!null b:2 c:3 d:4
              ├── internal-ordering: -2
              ├── cardinality: [0 - 5]
              ├── key: (1)
              ├── fd: (1)-->(2-4), (2-4)~~>(1)
              ├── sort
              │    ├── columns: a:1!null b:2 c:3 d:4
              │    ├── cardinality: [0 - 11]
              │    ├── key: (1)
              │    ├── fd: (1)-->(2-4), (2-4)~~>(1)
              │    ├── ordering: -2
              │    ├── limit hint: 5.00
              │    └── project
              │         ├── columns: a:1!null b:2 c:3 d:4
              │         ├── cardinality: [0 - 11]
              │         ├── key: (1)
              │         ├── fd: (1)-->(2-4), (2-4)~~>(1)
              │         └── select
  -           │              ├── columns: a:1!null b:2 c:3 d:4 crdb_internal_mvcc_timestamp:5 tableoid:6
  +           │              ├── columns: a:1!null b:2 c:3 d:4
              │              ├── cardinality: [0 - 11]
              │              ├── key: (1)
  -           │              ├── fd: (1)-->(2-6), (2-4)~~>(1,5,6)
  +           │              ├── fd: (1)-->(2-4), (2-4)~~>(1)
              │              ├── scan abcd
  -           │              │    ├── columns: a:1!null b:2 c:3 d:4 crdb_internal_mvcc_timestamp:5 tableoid:6
  +           │              │    ├── columns: a:1!null b:2 c:3 d:4
              │              │    ├── flags: force-index=b
              │              │    ├── key: (1)
  -           │              │    └── fd: (1)-->(2-6), (2-4)~~>(1,5,6)
  +           │              │    └── fd: (1)-->(2-4), (2-4)~~>(1)
              │              └── filters
              │                   └── (a:1 >= 20) AND (a:1 <= 30) [outer=(1), constraints=(/1: [/20 - /30]; tight)]
              └── 5
================================================================================
EliminateProject
  Cost: 10000000000000000159028911097599180468360808563945281389781327557747838772170381060813469985856815104.00
================================================================================
   explain
    ├── columns: info:7
    └── sort
         ├── columns: a:1!null b:2 c:3 d:4
         ├── cardinality: [0 - 5]
         ├── key: (1)
         ├── fd: (1)-->(2-4), (2-4)~~>(1)
         ├── ordering: -2
         └── limit
              ├── columns: a:1!null b:2 c:3 d:4
              ├── internal-ordering: -2
              ├── cardinality: [0 - 5]
              ├── key: (1)
              ├── fd: (1)-->(2-4), (2-4)~~>(1)
              ├── sort
              │    ├── columns: a:1!null b:2 c:3 d:4
              │    ├── cardinality: [0 - 11]
              │    ├── key: (1)
              │    ├── fd: (1)-->(2-4), (2-4)~~>(1)
              │    ├── ordering: -2
              │    ├── limit hint: 5.00
  -           │    └── project
  +           │    └── select
              │         ├── columns: a:1!null b:2 c:3 d:4
              │         ├── cardinality: [0 - 11]
              │         ├── key: (1)
              │         ├── fd: (1)-->(2-4), (2-4)~~>(1)
  -           │         └── select
  -           │              ├── columns: a:1!null b:2 c:3 d:4
  -           │              ├── cardinality: [0 - 11]
  -           │              ├── key: (1)
  -           │              ├── fd: (1)-->(2-4), (2-4)~~>(1)
  -           │              ├── scan abcd
  -           │              │    ├── columns: a:1!null b:2 c:3 d:4
  -           │              │    ├── flags: force-index=b
  -           │              │    ├── key: (1)
  -           │              │    └── fd: (1)-->(2-4), (2-4)~~>(1)
  -           │              └── filters
  -           │                   └── (a:1 >= 20) AND (a:1 <= 30) [outer=(1), constraints=(/1: [/20 - /30]; tight)]
  +           │         ├── scan abcd
  +           │         │    ├── columns: a:1!null b:2 c:3 d:4
  +           │         │    ├── flags: force-index=b
  +           │         │    ├── key: (1)
  +           │         │    └── fd: (1)-->(2-4), (2-4)~~>(1)
  +           │         └── filters
  +           │              └── (a:1 >= 20) AND (a:1 <= 30) [outer=(1), constraints=(/1: [/20 - /30]; tight)]
              └── 5
================================================================================
GenerateIndexScans
  Cost: 3575.71
================================================================================
   explain
    ├── columns: info:7
  - └── sort
  + └── limit
         ├── columns: a:1!null b:2 c:3 d:4
  +      ├── internal-ordering: -2
         ├── cardinality: [0 - 5]
         ├── key: (1)
         ├── fd: (1)-->(2-4), (2-4)~~>(1)
         ├── ordering: -2
  -      └── limit
  -           ├── columns: a:1!null b:2 c:3 d:4
  -           ├── internal-ordering: -2
  -           ├── cardinality: [0 - 5]
  -           ├── key: (1)
  -           ├── fd: (1)-->(2-4), (2-4)~~>(1)
  -           ├── sort
  -           │    ├── columns: a:1!null b:2 c:3 d:4
  -           │    ├── cardinality: [0 - 11]
  -           │    ├── key: (1)
  -           │    ├── fd: (1)-->(2-4), (2-4)~~>(1)
  -           │    ├── ordering: -2
  -           │    ├── limit hint: 5.00
  -           │    └── select
  -           │         ├── columns: a:1!null b:2 c:3 d:4
  -           │         ├── cardinality: [0 - 11]
  -           │         ├── key: (1)
  -           │         ├── fd: (1)-->(2-4), (2-4)~~>(1)
  -           │         ├── scan abcd
  -           │         │    ├── columns: a:1!null b:2 c:3 d:4
  -           │         │    ├── flags: force-index=b
  -           │         │    ├── key: (1)
  -           │         │    └── fd: (1)-->(2-4), (2-4)~~>(1)
  -           │         └── filters
  -           │              └── (a:1 >= 20) AND (a:1 <= 30) [outer=(1), constraints=(/1: [/20 - /30]; tight)]
  -           └── 5
  +      ├── select
  +      │    ├── columns: a:1!null b:2 c:3 d:4
  +      │    ├── cardinality: [0 - 11]
  +      │    ├── key: (1)
  +      │    ├── fd: (1)-->(2-4), (2-4)~~>(1)
  +      │    ├── ordering: -2
  +      │    ├── limit hint: 5.00
  +      │    ├── index-join abcd
  +      │    │    ├── columns: a:1!null b:2 c:3 d:4
  +      │    │    ├── key: (1)
  +      │    │    ├── fd: (1)-->(2-4), (2-4)~~>(1)
  +      │    │    ├── ordering: -2
  +      │    │    ├── limit hint: 454.55
  +      │    │    └── scan abcd@b,rev
  +      │    │         ├── columns: a:1!null b:2
  +      │    │         ├── flags: force-index=b
  +      │    │         ├── key: (1)
  +      │    │         ├── fd: (1)-->(2)
  +      │    │         ├── ordering: -2
  +      │    │         └── limit hint: 454.55
  +      │    └── filters
  +      │         └── (a:1 >= 20) AND (a:1 <= 30) [outer=(1), constraints=(/1: [/20 - /30]; tight)]
  +      └── 5
--------------------------------------------------------------------------------
GeneratePartialIndexScans (no changes)
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
GenerateConstrainedScans (no changes)
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
GenerateZigzagJoins (no changes)
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
GenerateTopK (higher cost)
--------------------------------------------------------------------------------
   explain
    ├── columns: info:7
  - └── limit
  + └── top-k
         ├── columns: a:1!null b:2 c:3 d:4
         ├── internal-ordering: -2
  +      ├── k: 5
         ├── cardinality: [0 - 5]
         ├── key: (1)
         ├── fd: (1)-->(2-4), (2-4)~~>(1)
         ├── ordering: -2
  -      ├── select
  -      │    ├── columns: a:1!null b:2 c:3 d:4
  -      │    ├── cardinality: [0 - 11]
  -      │    ├── key: (1)
  -      │    ├── fd: (1)-->(2-4), (2-4)~~>(1)
  -      │    ├── ordering: -2
  -      │    ├── limit hint: 5.00
  -      │    ├── index-join abcd
  -      │    │    ├── columns: a:1!null b:2 c:3 d:4
  -      │    │    ├── key: (1)
  -      │    │    ├── fd: (1)-->(2-4), (2-4)~~>(1)
  -      │    │    ├── ordering: -2
  -      │    │    ├── limit hint: 454.55
  -      │    │    └── scan abcd@b,rev
  -      │    │         ├── columns: a:1!null b:2
  -      │    │         ├── flags: force-index=b
  -      │    │         ├── key: (1)
  -      │    │         ├── fd: (1)-->(2)
  -      │    │         ├── ordering: -2
  -      │    │         └── limit hint: 454.55
  -      │    └── filters
  -      │         └── (a:1 >= 20) AND (a:1 <= 30) [outer=(1), constraints=(/1: [/20 - /30]; tight)]
  -      └── 5
  +      └── select
  +           ├── columns: a:1!null b:2 c:3 d:4
  +           ├── cardinality: [0 - 11]
  +           ├── key: (1)
  +           ├── fd: (1)-->(2-4), (2-4)~~>(1)
  +           ├── index-join abcd
  +           │    ├── columns: a:1!null b:2 c:3 d:4
  +           │    ├── key: (1)
  +           │    ├── fd: (1)-->(2-4), (2-4)~~>(1)
  +           │    └── scan abcd@b
  +           │         ├── columns: a:1!null b:2
  +           │         ├── flags: force-index=b
  +           │         ├── key: (1)
  +           │         └── fd: (1)-->(2)
  +           └── filters
  +                └── (a:1 >= 20) AND (a:1 <= 30) [outer=(1), constraints=(/1: [/20 - /30]; tight)]
--------------------------------------------------------------------------------
GeneratePartialOrderTopK (no changes)
--------------------------------------------------------------------------------
================================================================================
Final best expression
  Cost: 3575.71
================================================================================
  explain
   ├── columns: info:7
   └── limit
        ├── columns: a:1!null b:2 c:3 d:4
        ├── internal-ordering: -2
        ├── cardinality: [0 - 5]
        ├── key: (1)
        ├── fd: (1)-->(2-4), (2-4)~~>(1)
        ├── ordering: -2
        ├── select
        │    ├── columns: a:1!null b:2 c:3 d:4
        │    ├── cardinality: [0 - 11]
        │    ├── key: (1)
        │    ├── fd: (1)-->(2-4), (2-4)~~>(1)
        │    ├── ordering: -2
        │    ├── limit hint: 5.00
        │    ├── index-join abcd
        │    │    ├── columns: a:1!null b:2 c:3 d:4
        │    │    ├── key: (1)
        │    │    ├── fd: (1)-->(2-4), (2-4)~~>(1)
        │    │    ├── ordering: -2
        │    │    ├── limit hint: 454.55
        │    │    └── scan abcd@b,rev
        │    │         ├── columns: a:1!null b:2
        │    │         ├── flags: force-index=b
        │    │         ├── key: (1)
        │    │         ├── fd: (1)-->(2)
        │    │         ├── ordering: -2
        │    │         └── limit hint: 454.55
        │    └── filters
        │         └── (a:1 >= 20) AND (a:1 <= 30) [outer=(1), constraints=(/1: [/20 - /30]; tight)]
        └── 5

# --------------------------------------------------
# PushOffsetIntoIndexJoin
# --------------------------------------------------

opt expect=PushOffsetIntoIndexJoin
SELECT * FROM kuv WHERE k = 1 OR k = 2 ORDER BY u OFFSET 5
----
index-join kuv
 ├── columns: k:1!null u:2 v:3
 ├── ordering: +2
 └── offset
      ├── columns: k:1!null u:2 rowid:4!null
      ├── internal-ordering: +2
      ├── key: (4)
      ├── fd: (4)-->(1,2)
      ├── ordering: +2
      ├── sort
      │    ├── columns: k:1!null u:2 rowid:4!null
      │    ├── key: (4)
      │    ├── fd: (4)-->(1,2)
      │    ├── ordering: +2
      │    └── scan kuv@kuv_k_u_idx
      │         ├── columns: k:1!null u:2 rowid:4!null
      │         ├── constraint: /1/2/4: [/1 - /2]
      │         ├── key: (4)
      │         └── fd: (4)-->(1,2)
      └── 5

# Both LIMIT and OFFSET can be pushed below a join.
opt expect=(PushLimitIntoIndexJoin,PushOffsetIntoIndexJoin)
SELECT * FROM kuv WHERE k = 1 OR k = 2 ORDER BY u LIMIT 10 OFFSET 5
----
index-join kuv
 ├── columns: k:1!null u:2 v:3
 ├── cardinality: [0 - 10]
 ├── ordering: +2
 └── offset
      ├── columns: k:1!null u:2 rowid:4!null
      ├── internal-ordering: +2
      ├── cardinality: [0 - 10]
      ├── key: (4)
      ├── fd: (4)-->(1,2)
      ├── ordering: +2
      ├── top-k
      │    ├── columns: k:1!null u:2 rowid:4!null
      │    ├── internal-ordering: +2
      │    ├── k: 15
      │    ├── cardinality: [0 - 15]
      │    ├── key: (4)
      │    ├── fd: (4)-->(1,2)
      │    ├── ordering: +2
      │    └── scan kuv@kuv_k_u_idx
      │         ├── columns: k:1!null u:2 rowid:4!null
      │         ├── constraint: /1/2/4: [/1 - /2]
      │         ├── key: (4)
      │         └── fd: (4)-->(1,2)
      └── 5

# The offset is not pushed into the index join if
# optimizer_push_offset_into_index_join is false.
opt expect-not=PushOffsetIntoIndexJoin set=(optimizer_push_offset_into_index_join=off)
SELECT * FROM kuv WHERE k = 1 OR k = 2 ORDER BY u OFFSET 5
----
offset
 ├── columns: k:1!null u:2 v:3
 ├── internal-ordering: +2
 ├── ordering: +2
 ├── sort
 │    ├── columns: k:1!null u:2 v:3
 │    ├── ordering: +2
 │    └── index-join kuv
 │         ├── columns: k:1!null u:2 v:3
 │         └── scan kuv@kuv_k_u_idx
 │              ├── columns: k:1!null u:2 rowid:4!null
 │              ├── constraint: /1/2/4: [/1 - /2]
 │              ├── key: (4)
 │              └── fd: (4)-->(1,2)
 └── 5

# Ensure that the offset is not pushed down when the ordering requires columns
# produced by the IndexJoin.
opt expect-not=PushOffsetIntoIndexJoin
SELECT * FROM kuv WHERE u > 1 AND u < 10 ORDER BY u, v OFFSET 5
----
offset
 ├── columns: k:1 u:2!null v:3
 ├── internal-ordering: +2,+3
 ├── ordering: +2,+3
 ├── sort
 │    ├── columns: k:1 u:2!null v:3
 │    ├── ordering: +2,+3
 │    └── select
 │         ├── columns: k:1 u:2!null v:3
 │         ├── scan kuv
 │         │    └── columns: k:1 u:2 v:3
 │         └── filters
 │              └── (u:2 > 1) AND (u:2 < 10) [outer=(2), constraints=(/2: [/2 - /9]; tight)]
 └── 5

# Ensure that the offset is not pushed down when using SKIP LOCKED.
opt expect-not=PushOffsetIntoIndexJoin
SELECT * FROM kuv WHERE k = 1 ORDER BY u OFFSET 5 FOR UPDATE SKIP LOCKED
----
offset
 ├── columns: k:1!null u:2 v:3
 ├── internal-ordering: +2 opt(1)
 ├── volatile
 ├── fd: ()-->(1)
 ├── ordering: +2 opt(1) [actual: +2]
 ├── index-join kuv
 │    ├── columns: k:1!null u:2 v:3
 │    ├── locking: for-update,skip-locked
 │    ├── volatile
 │    ├── fd: ()-->(1)
 │    ├── ordering: +2 opt(1) [actual: +2]
 │    └── scan kuv@kuv_k_u_idx
 │         ├── columns: k:1!null u:2 rowid:4!null
 │         ├── constraint: /1/2/4: [/1 - /1]
 │         ├── locking: for-update,skip-locked
 │         ├── volatile
 │         ├── key: (4)
 │         ├── fd: ()-->(1), (4)-->(2)
 │         └── ordering: +2 opt(1) [actual: +2]
 └── 5

# --------------------------------------------------
# PushLimitIntoOffset + GenerateLimitedScans
# --------------------------------------------------

# Regression testing for #30416.
# The limit is pushed down the offset and so an appropriate index scan is used
# over a primary key scan.
opt
SELECT * from a ORDER BY s LIMIT 10 OFFSET 10
----
index-join a
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── cardinality: [0 - 10]
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── ordering: +4
 └── offset
      ├── columns: k:1!null i:2 f:3 s:4
      ├── internal-ordering: +4
      ├── cardinality: [0 - 10]
      ├── key: (1)
      ├── fd: (1)-->(2-4)
      ├── ordering: +4
      ├── scan a@s_idx
      │    ├── columns: k:1!null i:2 f:3 s:4
      │    ├── limit: 20
      │    ├── key: (1)
      │    ├── fd: (1)-->(2-4)
      │    └── ordering: +4
      └── 10

# The right index is used for the limited scan based on the order.
opt
SELECT * from a ORDER BY s DESC LIMIT 10 OFFSET 10
----
index-join a
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── cardinality: [0 - 10]
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── ordering: -4
 └── offset
      ├── columns: k:1!null i:2 s:4 j:5
      ├── internal-ordering: -4
      ├── cardinality: [0 - 10]
      ├── key: (1)
      ├── fd: (1)-->(2,4,5)
      ├── ordering: -4
      ├── scan a@si_idx
      │    ├── columns: k:1!null i:2 s:4 j:5
      │    ├── limit: 20
      │    ├── key: (1)
      │    ├── fd: (1)-->(2,4,5)
      │    └── ordering: -4
      └── 10

# PushLimitIntoIndexJoin propagates row-level locking information.
opt
SELECT * FROM kuv ORDER BY u LIMIT 5 FOR UPDATE
----
top-k
 ├── columns: k:1 u:2 v:3
 ├── internal-ordering: +2
 ├── k: 5
 ├── cardinality: [0 - 5]
 ├── volatile
 ├── ordering: +2
 └── scan kuv
      ├── columns: k:1 u:2 v:3
      ├── locking: for-update
      └── volatile

# -------------------------
# SplitLimitedScanIntoUnionScans
# -------------------------

# Case with limit 10.
opt
SELECT val, data1 FROM index_tab WHERE val > 0 AND val < 4 ORDER BY data1 LIMIT 10
----
limit
 ├── columns: val:2!null data1:6!null
 ├── internal-ordering: +6
 ├── cardinality: [0 - 10]
 ├── ordering: +6
 ├── union-all
 │    ├── columns: val:2!null data1:6!null
 │    ├── left columns: val:46 data1:50
 │    ├── right columns: val:35 data1:39
 │    ├── cardinality: [0 - 30]
 │    ├── ordering: +6
 │    ├── limit hint: 10.00
 │    ├── union-all
 │    │    ├── columns: val:46!null data1:50!null
 │    │    ├── left columns: val:13 data1:17
 │    │    ├── right columns: val:24 data1:28
 │    │    ├── cardinality: [0 - 20]
 │    │    ├── ordering: +50
 │    │    ├── limit hint: 10.00
 │    │    ├── scan index_tab@b
 │    │    │    ├── columns: val:13!null data1:17!null
 │    │    │    ├── constraint: /13/17/18/12: [/1 - /1]
 │    │    │    ├── limit: 10
 │    │    │    ├── fd: ()-->(13)
 │    │    │    ├── ordering: +17 opt(13) [actual: +17]
 │    │    │    └── limit hint: 10.00
 │    │    └── scan index_tab@b
 │    │         ├── columns: val:24!null data1:28!null
 │    │         ├── constraint: /24/28/29/23: [/2 - /2]
 │    │         ├── limit: 10
 │    │         ├── fd: ()-->(24)
 │    │         ├── ordering: +28 opt(24) [actual: +28]
 │    │         └── limit hint: 10.00
 │    └── scan index_tab@b
 │         ├── columns: val:35!null data1:39!null
 │         ├── constraint: /35/39/40/34: [/3 - /3]
 │         ├── limit: 10
 │         ├── fd: ()-->(35)
 │         ├── ordering: +39 opt(35) [actual: +39]
 │         └── limit hint: 10.00
 └── 10

# Case with single-key spans.
opt expect=SplitLimitedScanIntoUnionScans
SELECT max(data1) FROM index_tab WHERE region = 'US_EAST' OR region = 'US_WEST'
----
scalar-group-by
 ├── columns: max:12
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(12)
 ├── limit
 │    ├── columns: region:3!null data1:6!null
 │    ├── internal-ordering: -6
 │    ├── cardinality: [0 - 1]
 │    ├── key: ()
 │    ├── fd: ()-->(3,6)
 │    ├── union-all
 │    │    ├── columns: region:3!null data1:6!null
 │    │    ├── left columns: region:15 data1:18
 │    │    ├── right columns: region:26 data1:29
 │    │    ├── cardinality: [0 - 2]
 │    │    ├── ordering: -6
 │    │    ├── limit hint: 1.00
 │    │    ├── scan index_tab@c,rev
 │    │    │    ├── columns: region:15!null data1:18!null
 │    │    │    ├── constraint: /15/18/19/13: [/'US_EAST' - /'US_EAST']
 │    │    │    ├── limit: 1(rev)
 │    │    │    ├── key: ()
 │    │    │    ├── fd: ()-->(15,18)
 │    │    │    └── limit hint: 1.00
 │    │    └── scan index_tab@c,rev
 │    │         ├── columns: region:26!null data1:29!null
 │    │         ├── constraint: /26/29/30/24: [/'US_WEST' - /'US_WEST']
 │    │         ├── limit: 1(rev)
 │    │         ├── key: ()
 │    │         ├── fd: ()-->(26,29)
 │    │         └── limit hint: 1.00
 │    └── 1
 └── aggregations
      └── const-agg [as=max:12, outer=(6)]
           └── data1:6

# Case with multi-column keys in single-key spans.
opt expect=SplitLimitedScanIntoUnionScans
SELECT max(data1)
FROM index_tab
WHERE (latitude, longitude) = (1, 2) OR (latitude, longitude) = (4, 5)
----
scalar-group-by
 ├── columns: max:12
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(12)
 ├── limit
 │    ├── columns: latitude:4!null longitude:5!null data1:6!null
 │    ├── internal-ordering: -6
 │    ├── cardinality: [0 - 1]
 │    ├── key: ()
 │    ├── fd: ()-->(4-6)
 │    ├── union-all
 │    │    ├── columns: latitude:4!null longitude:5!null data1:6!null
 │    │    ├── left columns: latitude:16 longitude:17 data1:18
 │    │    ├── right columns: latitude:27 longitude:28 data1:29
 │    │    ├── cardinality: [0 - 2]
 │    │    ├── ordering: -6
 │    │    ├── limit hint: 1.00
 │    │    ├── scan index_tab@d,rev
 │    │    │    ├── columns: latitude:16!null longitude:17!null data1:18!null
 │    │    │    ├── constraint: /16/17/18/19/13: [/1/2 - /1/2]
 │    │    │    ├── limit: 1(rev)
 │    │    │    ├── key: ()
 │    │    │    ├── fd: ()-->(16-18)
 │    │    │    └── limit hint: 1.00
 │    │    └── scan index_tab@d,rev
 │    │         ├── columns: latitude:27!null longitude:28!null data1:29!null
 │    │         ├── constraint: /27/28/29/30/24: [/4/5 - /4/5]
 │    │         ├── limit: 1(rev)
 │    │         ├── key: ()
 │    │         ├── fd: ()-->(27-29)
 │    │         └── limit hint: 1.00
 │    └── 1
 └── aggregations
      └── const-agg [as=max:12, outer=(6)]
           └── data1:6

# Case with countable multi-key spans.
opt expect=SplitLimitedScanIntoUnionScans
SELECT max(data1) FROM index_tab WHERE val > 0 AND val < 4
----
scalar-group-by
 ├── columns: max:12
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(12)
 ├── limit
 │    ├── columns: val:2!null data1:6!null
 │    ├── internal-ordering: -6
 │    ├── cardinality: [0 - 1]
 │    ├── key: ()
 │    ├── fd: ()-->(2,6)
 │    ├── union-all
 │    │    ├── columns: val:2!null data1:6!null
 │    │    ├── left columns: val:47 data1:51
 │    │    ├── right columns: val:36 data1:40
 │    │    ├── cardinality: [0 - 3]
 │    │    ├── ordering: -6
 │    │    ├── limit hint: 1.00
 │    │    ├── union-all
 │    │    │    ├── columns: val:47!null data1:51!null
 │    │    │    ├── left columns: val:14 data1:18
 │    │    │    ├── right columns: val:25 data1:29
 │    │    │    ├── cardinality: [0 - 2]
 │    │    │    ├── ordering: -51
 │    │    │    ├── limit hint: 1.00
 │    │    │    ├── scan index_tab@b,rev
 │    │    │    │    ├── columns: val:14!null data1:18!null
 │    │    │    │    ├── constraint: /14/18/19/13: [/1 - /1]
 │    │    │    │    ├── limit: 1(rev)
 │    │    │    │    ├── key: ()
 │    │    │    │    ├── fd: ()-->(14,18)
 │    │    │    │    └── limit hint: 1.00
 │    │    │    └── scan index_tab@b,rev
 │    │    │         ├── columns: val:25!null data1:29!null
 │    │    │         ├── constraint: /25/29/30/24: [/2 - /2]
 │    │    │         ├── limit: 1(rev)
 │    │    │         ├── key: ()
 │    │    │         ├── fd: ()-->(25,29)
 │    │    │         └── limit hint: 1.00
 │    │    └── scan index_tab@b,rev
 │    │         ├── columns: val:36!null data1:40!null
 │    │         ├── constraint: /36/40/41/35: [/3 - /3]
 │    │         ├── limit: 1(rev)
 │    │         ├── key: ()
 │    │         ├── fd: ()-->(36,40)
 │    │         └── limit hint: 1.00
 │    └── 1
 └── aggregations
      └── const-agg [as=max:12, outer=(6)]
           └── data1:6

# Case with limit ordering on more than one column.
opt expect=SplitLimitedScanIntoUnionScans
SELECT region, data1, data2
FROM index_tab
WHERE region='US_WEST' OR region='US_EAST'
ORDER BY data1, data2
LIMIT 10
----
limit
 ├── columns: region:3!null data1:6!null data2:7!null
 ├── internal-ordering: +6,+7
 ├── cardinality: [0 - 10]
 ├── ordering: +6,+7
 ├── union-all
 │    ├── columns: region:3!null data1:6!null data2:7!null
 │    ├── left columns: region:14 data1:17 data2:18
 │    ├── right columns: region:25 data1:28 data2:29
 │    ├── cardinality: [0 - 20]
 │    ├── ordering: +6,+7
 │    ├── limit hint: 10.00
 │    ├── scan index_tab@c
 │    │    ├── columns: region:14!null data1:17!null data2:18!null
 │    │    ├── constraint: /14/17/18/12: [/'US_EAST' - /'US_EAST']
 │    │    ├── limit: 10
 │    │    ├── fd: ()-->(14)
 │    │    ├── ordering: +17,+18 opt(14) [actual: +17,+18]
 │    │    └── limit hint: 10.00
 │    └── scan index_tab@c
 │         ├── columns: region:25!null data1:28!null data2:29!null
 │         ├── constraint: /25/28/29/23: [/'US_WEST' - /'US_WEST']
 │         ├── limit: 10
 │         ├── fd: ()-->(25)
 │         ├── ordering: +28,+29 opt(25) [actual: +28,+29]
 │         └── limit hint: 10.00
 └── 10

# Case with start key longer than the ordering prefix length.
opt expect=SplitLimitedScanIntoUnionScans
SELECT region, data1, data2
FROM index_tab
WHERE (region='US_WEST' OR region='US_EAST')
AND data1 > 3
ORDER BY data1, data2
LIMIT 10
----
limit
 ├── columns: region:3!null data1:6!null data2:7!null
 ├── internal-ordering: +6,+7
 ├── cardinality: [0 - 10]
 ├── ordering: +6,+7
 ├── union-all
 │    ├── columns: region:3!null data1:6!null data2:7!null
 │    ├── left columns: region:14 data1:17 data2:18
 │    ├── right columns: region:25 data1:28 data2:29
 │    ├── cardinality: [0 - 20]
 │    ├── ordering: +6,+7
 │    ├── limit hint: 10.00
 │    ├── scan index_tab@c
 │    │    ├── columns: region:14!null data1:17!null data2:18!null
 │    │    ├── constraint: /14/17/18/12: [/'US_EAST'/4 - /'US_EAST']
 │    │    ├── limit: 10
 │    │    ├── fd: ()-->(14)
 │    │    ├── ordering: +17,+18 opt(14) [actual: +17,+18]
 │    │    └── limit hint: 10.00
 │    └── scan index_tab@c
 │         ├── columns: region:25!null data1:28!null data2:29!null
 │         ├── constraint: /25/28/29/23: [/'US_WEST'/4 - /'US_WEST']
 │         ├── limit: 10
 │         ├── fd: ()-->(25)
 │         ├── ordering: +28,+29 opt(25) [actual: +28,+29]
 │         └── limit hint: 10.00
 └── 10

# Case with end key longer than the ordering prefix length.
opt expect=SplitLimitedScanIntoUnionScans
SELECT region, data1, data2
FROM index_tab
WHERE (region='US_WEST' OR region='US_EAST')
AND data1 < 3
ORDER BY data1, data2
LIMIT 10
----
limit
 ├── columns: region:3!null data1:6!null data2:7!null
 ├── internal-ordering: +6,+7
 ├── cardinality: [0 - 10]
 ├── ordering: +6,+7
 ├── union-all
 │    ├── columns: region:3!null data1:6!null data2:7!null
 │    ├── left columns: region:14 data1:17 data2:18
 │    ├── right columns: region:25 data1:28 data2:29
 │    ├── cardinality: [0 - 20]
 │    ├── ordering: +6,+7
 │    ├── limit hint: 10.00
 │    ├── scan index_tab@c
 │    │    ├── columns: region:14!null data1:17!null data2:18!null
 │    │    ├── constraint: /14/17/18/12: [/'US_EAST' - /'US_EAST'/2]
 │    │    ├── limit: 10
 │    │    ├── fd: ()-->(14)
 │    │    ├── ordering: +17,+18 opt(14) [actual: +17,+18]
 │    │    └── limit hint: 10.00
 │    └── scan index_tab@c
 │         ├── columns: region:25!null data1:28!null data2:29!null
 │         ├── constraint: /25/28/29/23: [/'US_WEST' - /'US_WEST'/2]
 │         ├── limit: 10
 │         ├── fd: ()-->(25)
 │         ├── ordering: +28,+29 opt(25) [actual: +28,+29]
 │         └── limit hint: 10.00
 └── 10

# Case with both keys longer than the ordering prefix length.
opt expect=SplitLimitedScanIntoUnionScans
SELECT region, data1, data2
FROM index_tab
WHERE (region='US_WEST' OR region='US_EAST')
AND data1 > 3
AND data1 < 1000
ORDER BY data1, data2
LIMIT 10
----
limit
 ├── columns: region:3!null data1:6!null data2:7!null
 ├── internal-ordering: +6,+7
 ├── cardinality: [0 - 10]
 ├── ordering: +6,+7
 ├── union-all
 │    ├── columns: region:3!null data1:6!null data2:7!null
 │    ├── left columns: region:14 data1:17 data2:18
 │    ├── right columns: region:25 data1:28 data2:29
 │    ├── cardinality: [0 - 20]
 │    ├── ordering: +6,+7
 │    ├── limit hint: 10.00
 │    ├── scan index_tab@c
 │    │    ├── columns: region:14!null data1:17!null data2:18!null
 │    │    ├── constraint: /14/17/18/12: [/'US_EAST'/4 - /'US_EAST'/999]
 │    │    ├── limit: 10
 │    │    ├── fd: ()-->(14)
 │    │    ├── ordering: +17,+18 opt(14) [actual: +17,+18]
 │    │    └── limit hint: 10.00
 │    └── scan index_tab@c
 │         ├── columns: region:25!null data1:28!null data2:29!null
 │         ├── constraint: /25/28/29/23: [/'US_WEST'/4 - /'US_WEST'/999]
 │         ├── limit: 10
 │         ├── fd: ()-->(25)
 │         ├── ordering: +28,+29 opt(25) [actual: +28,+29]
 │         └── limit hint: 10.00
 └── 10

# Case where one span can be used for a limited scan, but not the others. Note
# that the last Scan with constraints [/-5 - /-5] and [/0 - /0] is not limited.
opt expect=SplitLimitedScanIntoUnionScans
SELECT latitude, longitude, data1, data2
FROM index_tab
WHERE latitude = 0
OR latitude = -5
OR (latitude = 10 AND longitude > 10 AND longitude < 14)
ORDER BY data1, data2
LIMIT 10
----
top-k
 ├── columns: latitude:4!null longitude:5 data1:6!null data2:7!null
 ├── internal-ordering: +6,+7
 ├── k: 10
 ├── cardinality: [0 - 10]
 ├── ordering: +6,+7
 └── union-all
      ├── columns: latitude:4!null longitude:5 data1:6!null data2:7!null
      ├── left columns: latitude:81 longitude:82 data1:83 data2:84
      ├── right columns: latitude:92 longitude:93 data1:94 data2:95
      ├── union-all
      │    ├── columns: latitude:81!null longitude:82!null data1:83!null data2:84!null
      │    ├── left columns: latitude:70 longitude:71 data1:72 data2:73
      │    ├── right columns: latitude:59 longitude:60 data1:61 data2:62
      │    ├── cardinality: [0 - 30]
      │    ├── union-all
      │    │    ├── columns: latitude:70!null longitude:71!null data1:72!null data2:73!null
      │    │    ├── left columns: latitude:37 longitude:38 data1:39 data2:40
      │    │    ├── right columns: latitude:48 longitude:49 data1:50 data2:51
      │    │    ├── cardinality: [0 - 20]
      │    │    ├── scan index_tab@d
      │    │    │    ├── columns: latitude:37!null longitude:38!null data1:39!null data2:40!null
      │    │    │    ├── constraint: /37/38/39/40/34: [/10/11 - /10/11]
      │    │    │    ├── limit: 10
      │    │    │    └── fd: ()-->(37,38)
      │    │    └── scan index_tab@d
      │    │         ├── columns: latitude:48!null longitude:49!null data1:50!null data2:51!null
      │    │         ├── constraint: /48/49/50/51/45: [/10/12 - /10/12]
      │    │         ├── limit: 10
      │    │         └── fd: ()-->(48,49)
      │    └── scan index_tab@d
      │         ├── columns: latitude:59!null longitude:60!null data1:61!null data2:62!null
      │         ├── constraint: /59/60/61/62/56: [/10/13 - /10/13]
      │         ├── limit: 10
      │         └── fd: ()-->(59,60)
      └── scan index_tab@d
           ├── columns: latitude:92!null longitude:93 data1:94!null data2:95!null
           └── constraint: /92/93/94/95/89
                ├── [/-5 - /-5]
                └── [/0 - /0]

# Case with index join.
opt expect=SplitLimitedScanIntoUnionScans
SELECT *
FROM index_tab WHERE region = 'US_WEST' OR region = 'US_EAST'
ORDER BY data1 LIMIT 10
----
index-join index_tab
 ├── columns: id:1!null val:2 region:3!null latitude:4 longitude:5 data1:6!null data2:7!null geom:8
 ├── cardinality: [0 - 10]
 ├── key: (1)
 ├── fd: (1)-->(2-8)
 ├── ordering: +6
 └── limit
      ├── columns: id:1!null region:3!null data1:6!null data2:7!null
      ├── internal-ordering: +6
      ├── cardinality: [0 - 10]
      ├── key: (1)
      ├── fd: (1)-->(3,6,7)
      ├── ordering: +6
      ├── union-all
      │    ├── columns: id:1!null region:3!null data1:6!null data2:7!null
      │    ├── left columns: id:12 region:14 data1:17 data2:18
      │    ├── right columns: id:23 region:25 data1:28 data2:29
      │    ├── cardinality: [0 - 20]
      │    ├── ordering: +6
      │    ├── limit hint: 10.00
      │    ├── scan index_tab@c
      │    │    ├── columns: id:12!null region:14!null data1:17!null data2:18!null
      │    │    ├── constraint: /14/17/18/12: [/'US_EAST' - /'US_EAST']
      │    │    ├── limit: 10
      │    │    ├── key: (12)
      │    │    ├── fd: ()-->(14), (12)-->(17,18)
      │    │    ├── ordering: +17 opt(14) [actual: +17]
      │    │    └── limit hint: 10.00
      │    └── scan index_tab@c
      │         ├── columns: id:23!null region:25!null data1:28!null data2:29!null
      │         ├── constraint: /25/28/29/23: [/'US_WEST' - /'US_WEST']
      │         ├── limit: 10
      │         ├── key: (23)
      │         ├── fd: ()-->(25), (23)-->(28,29)
      │         ├── ordering: +28 opt(25) [actual: +28]
      │         └── limit hint: 10.00
      └── 10

# Case where check constraints are used.
opt expect=SplitLimitedScanIntoUnionScans
SELECT * FROM pqrs ORDER BY q LIMIT 5
----
limit
 ├── columns: p:1!null q:2!null r:3!null s:4!null
 ├── internal-ordering: +2
 ├── cardinality: [0 - 5]
 ├── key: (1,2)
 ├── fd: (1,2)-->(3,4)
 ├── ordering: +2
 ├── union-all
 │    ├── columns: p:1!null q:2!null r:3!null s:4!null
 │    ├── left columns: p:25 q:26 r:27 s:28
 │    ├── right columns: p:19 q:20 r:21 s:22
 │    ├── cardinality: [0 - 15]
 │    ├── ordering: +2
 │    ├── limit hint: 5.00
 │    ├── union-all
 │    │    ├── columns: p:25!null q:26!null r:27!null s:28!null
 │    │    ├── left columns: p:7 q:8 r:9 s:10
 │    │    ├── right columns: p:13 q:14 r:15 s:16
 │    │    ├── cardinality: [0 - 10]
 │    │    ├── ordering: +26
 │    │    ├── limit hint: 5.00
 │    │    ├── scan pqrs
 │    │    │    ├── columns: p:7!null q:8!null r:9!null s:10!null
 │    │    │    ├── constraint: /7/8: [/1 - /1]
 │    │    │    ├── limit: 5
 │    │    │    ├── key: (8)
 │    │    │    ├── fd: ()-->(7), (8)-->(9,10)
 │    │    │    ├── ordering: +8 opt(7) [actual: +8]
 │    │    │    └── limit hint: 5.00
 │    │    └── scan pqrs
 │    │         ├── columns: p:13!null q:14!null r:15!null s:16!null
 │    │         ├── constraint: /13/14: [/5 - /5]
 │    │         ├── limit: 5
 │    │         ├── key: (14)
 │    │         ├── fd: ()-->(13), (14)-->(15,16)
 │    │         ├── ordering: +14 opt(13) [actual: +14]
 │    │         └── limit hint: 5.00
 │    └── scan pqrs
 │         ├── columns: p:19!null q:20!null r:21!null s:22!null
 │         ├── constraint: /19/20: [/10 - /10]
 │         ├── limit: 5
 │         ├── key: (20)
 │         ├── fd: ()-->(19), (20)-->(21,22)
 │         ├── ordering: +20 opt(19) [actual: +20]
 │         └── limit hint: 5.00
 └── 5

# Case where multiple check constraints are combined into one constraint
# (CHECK (r > 0) and CHECK (r < 3)).
opt expect=SplitLimitedScanIntoUnionScans
SELECT * FROM pqrs ORDER BY s LIMIT 10
----
limit
 ├── columns: p:1!null q:2!null r:3!null s:4!null
 ├── internal-ordering: +4
 ├── cardinality: [0 - 10]
 ├── key: (1,2)
 ├── fd: (1,2)-->(3,4)
 ├── ordering: +4
 ├── union-all
 │    ├── columns: p:1!null q:2!null r:3!null s:4!null
 │    ├── left columns: p:7 q:8 r:9 s:10
 │    ├── right columns: p:13 q:14 r:15 s:16
 │    ├── cardinality: [0 - 20]
 │    ├── ordering: +4
 │    ├── limit hint: 10.00
 │    ├── scan pqrs@pqrs_r_s_idx
 │    │    ├── columns: p:7!null q:8!null r:9!null s:10!null
 │    │    ├── constraint: /9/10/7/8: [/1 - /1]
 │    │    ├── limit: 10
 │    │    ├── key: (7,8)
 │    │    ├── fd: ()-->(9), (7,8)-->(10)
 │    │    ├── ordering: +10 opt(9) [actual: +10]
 │    │    └── limit hint: 10.00
 │    └── scan pqrs@pqrs_r_s_idx
 │         ├── columns: p:13!null q:14!null r:15!null s:16!null
 │         ├── constraint: /15/16/13/14: [/2 - /2]
 │         ├── limit: 10
 │         ├── key: (13,14)
 │         ├── fd: ()-->(15), (13,14)-->(16)
 │         ├── ordering: +16 opt(15) [actual: +16]
 │         └── limit hint: 10.00
 └── 10

# Check constraints are not used because the scan is already constrained (the
# Scan's constraint is used instead).
opt expect=SplitLimitedScanIntoUnionScans
SELECT * FROM (SELECT * FROM pqrs WHERE p = 1 OR p = 5) ORDER BY q LIMIT 5
----
limit
 ├── columns: p:1!null q:2!null r:3!null s:4!null
 ├── internal-ordering: +2
 ├── cardinality: [0 - 5]
 ├── key: (1,2)
 ├── fd: (1,2)-->(3,4)
 ├── ordering: +2
 ├── union-all
 │    ├── columns: p:1!null q:2!null r:3!null s:4!null
 │    ├── left columns: p:7 q:8 r:9 s:10
 │    ├── right columns: p:13 q:14 r:15 s:16
 │    ├── cardinality: [0 - 10]
 │    ├── ordering: +2
 │    ├── limit hint: 5.00
 │    ├── scan pqrs
 │    │    ├── columns: p:7!null q:8!null r:9!null s:10!null
 │    │    ├── constraint: /7/8: [/1 - /1]
 │    │    ├── limit: 5
 │    │    ├── key: (8)
 │    │    ├── fd: ()-->(7), (8)-->(9,10)
 │    │    ├── ordering: +8 opt(7) [actual: +8]
 │    │    └── limit hint: 5.00
 │    └── scan pqrs
 │         ├── columns: p:13!null q:14!null r:15!null s:16!null
 │         ├── constraint: /13/14: [/5 - /5]
 │         ├── limit: 5
 │         ├── key: (14)
 │         ├── fd: ()-->(13), (14)-->(15,16)
 │         ├── ordering: +14 opt(13) [actual: +14]
 │         └── limit hint: 5.00
 └── 5

# Span boundary is exclusive, but the rule applies since the key is longer than
# the prefix.
opt expect=SplitLimitedScanIntoUnionScans
SELECT id, latitude, longitude FROM index_tab
WHERE (latitude = 0 OR latitude = 10) AND longitude IS NOT NULL
ORDER BY longitude LIMIT 10
----
limit
 ├── columns: id:1!null latitude:4!null longitude:5!null
 ├── internal-ordering: +5
 ├── cardinality: [0 - 10]
 ├── key: (1)
 ├── fd: (1)-->(4,5)
 ├── ordering: +5
 ├── union-all
 │    ├── columns: id:1!null latitude:4!null longitude:5!null
 │    ├── left columns: id:12 latitude:15 longitude:16
 │    ├── right columns: id:23 latitude:26 longitude:27
 │    ├── cardinality: [0 - 20]
 │    ├── ordering: +5
 │    ├── limit hint: 10.00
 │    ├── scan index_tab@d
 │    │    ├── columns: id:12!null latitude:15!null longitude:16!null
 │    │    ├── constraint: /15/16/17/18/12: (/0/NULL - /0]
 │    │    ├── limit: 10
 │    │    ├── key: (12)
 │    │    ├── fd: ()-->(15), (12)-->(16)
 │    │    ├── ordering: +16 opt(15) [actual: +16]
 │    │    └── limit hint: 10.00
 │    └── scan index_tab@d
 │         ├── columns: id:23!null latitude:26!null longitude:27!null
 │         ├── constraint: /26/27/28/29/23: (/10/NULL - /10]
 │         ├── limit: 10
 │         ├── key: (23)
 │         ├── fd: ()-->(26), (23)-->(27)
 │         ├── ordering: +27 opt(26) [actual: +27]
 │         └── limit hint: 10.00
 └── 10

# No-op case since span boundary is exclusive and the key length equals the
# prefix length.
opt expect-not=SplitLimitedScanIntoUnionScans
SELECT id, latitude, longitude FROM index_tab
WHERE latitude IS NOT NULL AND latitude < 4
ORDER BY longitude LIMIT 10
----
top-k
 ├── columns: id:1!null latitude:4!null longitude:5
 ├── internal-ordering: +5
 ├── k: 10
 ├── cardinality: [0 - 10]
 ├── key: (1)
 ├── fd: (1)-->(4,5)
 ├── ordering: +5
 └── scan index_tab@d
      ├── columns: id:1!null latitude:4!null longitude:5
      ├── constraint: /4/5/6/7/1: (/NULL - /3]
      ├── key: (1)
      └── fd: (1)-->(4,5)

# No-op case because the scan has an inverted index.
opt expect-not=SplitLimitedScanIntoUnionScans
SELECT geom FROM index_tab WHERE ST_Intersects('POINT(3.0 3.0)'::geometry, geom)
----
select
 ├── columns: geom:8!null
 ├── immutable
 ├── index-join index_tab
 │    ├── columns: geom:8
 │    └── inverted-filter
 │         ├── columns: id:1!null
 │         ├── inverted expression: /11
 │         │    ├── tight: false, unique: false
 │         │    └── union spans
 │         │         ├── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x10\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\x14\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x14\x00\x00\x00\x00\x00\x00\x00"]
 │         │         └── ["B\xfd\x14\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x16\x00\x00\x00\x00\x00\x00\x00")
 │         ├── pre-filterer expression
 │         │    └── st_intersects('010100000000000000000008400000000000000840', geom:8)
 │         ├── key: (1)
 │         └── scan index_tab@geomidx,inverted
 │              ├── columns: id:1!null geom_inverted_key:11!null
 │              └── inverted constraint: /11/1
 │                   └── spans
 │                        ├── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x10\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\x14\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x14\x00\x00\x00\x00\x00\x00\x00"]
 │                        └── ["B\xfd\x14\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x16\x00\x00\x00\x00\x00\x00\x00")
 └── filters
      └── st_intersects('010100000000000000000008400000000000000840', geom:8) [outer=(8), immutable, constraints=(/8: (/NULL - ])]

# No-op case because the multi-key span isn't countable.
opt expect-not=SplitLimitedScanIntoUnionScans
SELECT max(data1) FROM index_tab WHERE region > 'US_EAST' AND region < 'US_WEST'
----
scalar-group-by
 ├── columns: max:12
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(12)
 ├── scan index_tab@c
 │    ├── columns: region:3!null data1:6!null
 │    └── constraint: /3/6/7/1: [/e'US_EAST\x00' - /'US_WEST')
 └── aggregations
      └── max [as=max:12, outer=(6)]
           └── data1:6

# No-op case because the number of keys exceeds maxScanCount.
opt expect-not=SplitLimitedScanIntoUnionScans
SELECT max(data1) FROM index_tab WHERE val > 0 AND val < 300
----
scalar-group-by
 ├── columns: max:12
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(12)
 ├── scan index_tab@b
 │    ├── columns: val:2!null data1:6!null
 │    └── constraint: /2/6/7/1: [/1 - /299]
 └── aggregations
      └── max [as=max:12, outer=(6)]
           └── data1:6

# No-op case because the same number of rows would be scanned by the split-up
# scans as by the original.
opt expect-not=SplitLimitedScanIntoUnionScans
SELECT max(data1) FROM index_tab WHERE id > 0 AND id < 4
----
scalar-group-by
 ├── columns: max:12
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(12)
 ├── scan index_tab@a
 │    ├── columns: id:1!null data1:6!null
 │    ├── constraint: /1/6/7: [/1 - /3]
 │    ├── cardinality: [0 - 3]
 │    ├── key: (1)
 │    └── fd: (1)-->(6)
 └── aggregations
      └── max [as=max:12, outer=(6)]
           └── data1:6

# No-op case because the scan is already limited.
opt expect-not=SplitLimitedScanIntoUnionScans
SELECT max(data1)
FROM (SELECT region, data1 FROM index_tab LIMIT 10)
WHERE region='ASIA' OR region='AUSTRALIA'
----
scalar-group-by
 ├── columns: max:12
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(12)
 ├── select
 │    ├── columns: region:3!null data1:6!null
 │    ├── cardinality: [0 - 10]
 │    ├── scan index_tab@c
 │    │    ├── columns: region:3 data1:6!null
 │    │    └── limit: 10
 │    └── filters
 │         └── (region:3 = 'ASIA') OR (region:3 = 'AUSTRALIA') [outer=(3), constraints=(/3: [/'ASIA' - /'ASIA'] [/'AUSTRALIA' - /'AUSTRALIA']; tight)]
 └── aggregations
      └── max [as=max:12, outer=(6)]
           └── data1:6

# No-op case because the limit is negative.
opt expect-not=SplitLimitedScanIntoUnionScans
SELECT region, data1
FROM index_tab
WHERE region = 'ASIA' OR region = 'EUROPE' ORDER BY data1 LIMIT -1
----
limit
 ├── columns: region:3!null data1:6!null
 ├── internal-ordering: +6
 ├── cardinality: [0 - 0]
 ├── immutable
 ├── key: ()
 ├── fd: ()-->(3,6)
 ├── sort
 │    ├── columns: region:3!null data1:6!null
 │    ├── ordering: +6
 │    ├── limit hint: 1.00
 │    └── scan index_tab@c
 │         ├── columns: region:3!null data1:6!null
 │         └── constraint: /3/6/7/1
 │              ├── [/'ASIA' - /'ASIA']
 │              └── [/'EUROPE' - /'EUROPE']
 └── -1

# No-op case because scan is unconstrained.
opt expect-not=SplitLimitedScanIntoUnionScans
SELECT max(data1) FROM index_tab@b
----
scalar-group-by
 ├── columns: max:12
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(12)
 ├── scan index_tab@b
 │    ├── columns: data1:6!null
 │    └── flags: force-index=b
 └── aggregations
      └── max [as=max:12, outer=(6)]
           └── data1:6

# ---------------------------------------------------
# GenerateTopK
# ---------------------------------------------------

opt expect=GenerateTopK
SELECT * FROM a ORDER BY i LIMIT 1
----
top-k
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── internal-ordering: +2
 ├── k: 1
 ├── 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)

opt expect=GenerateTopK
SELECT * FROM a ORDER BY i LIMIT 10
----
top-k
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── internal-ordering: +2
 ├── k: 10
 ├── cardinality: [0 - 10]
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── ordering: +2
 └── scan a
      ├── columns: k:1!null i:2 f:3 s:4 j:5
      ├── key: (1)
      └── fd: (1)-->(2-5)

memo expect=GenerateTopK
SELECT * FROM a ORDER BY k LIMIT 1
----
memo (optimized, ~5KB, required=[presentation: k:1,i:2,f:3,s:4,j:5])
 ├── G1: (limit G2 G3 ordering=+1) (scan a,cols=(1-5),lim=1) (top-k G2 &{1 +1 })
 │    └── [presentation: k:1,i:2,f:3,s:4,j:5]
 │         ├── best: (scan a,cols=(1-5),lim=1)
 │         └── cost: 9.11
 ├── G2: (scan a,cols=(1-5))
 │    ├── [ordering: +1] [limit hint: 1.00]
 │    │    ├── best: (scan a,cols=(1-5))
 │    │    └── cost: 19.12
 │    └── []
 │         ├── best: (scan a,cols=(1-5))
 │         └── cost: 1129.02
 └── G3: (const 1)

# GenerateTopK is not triggered when the limit is not constant
opt expect-not=GenerateTopK
SELECT * FROM a ORDER BY i LIMIT (SELECT count(*) FROM a WHERE i < 10)
----
limit
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── internal-ordering: +2
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── ordering: +2
 ├── sort
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    ├── ordering: +2
 │    └── scan a
 │         ├── columns: k:1!null i:2 f:3 s:4 j:5
 │         ├── key: (1)
 │         └── fd: (1)-->(2-5)
 └── subquery
      └── scalar-group-by
           ├── columns: count_rows:15!null
           ├── cardinality: [1 - 1]
           ├── key: ()
           ├── fd: ()-->(15)
           ├── select
           │    ├── columns: i:9!null
           │    ├── scan a@s_idx
           │    │    └── columns: i:9
           │    └── filters
           │         └── i:9 < 10 [outer=(9), constraints=(/9: (/NULL - /9]; tight)]
           └── aggregations
                └── count-rows [as=count_rows:15]

# Nested GenerateTopK expressions
opt expect=GenerateTopK
SELECT * FROM (SELECT s,i FROM a ORDER BY f LIMIT 5) ORDER BY i LIMIT 2
----
project
 ├── columns: s:4 i:2
 ├── cardinality: [0 - 2]
 ├── ordering: +2
 └── top-k
      ├── columns: i:2 f:3 s:4
      ├── internal-ordering: +2
      ├── k: 2
      ├── cardinality: [0 - 2]
      ├── ordering: +2
      └── top-k
           ├── columns: i:2 f:3 s:4
           ├── internal-ordering: +3
           ├── k: 5
           ├── cardinality: [0 - 5]
           └── scan a@s_idx
                └── columns: i:2 f:3 s:4

# GenerateTopK is not triggered when the limit is a negative integer
opt expect-not=GenerateTopK
SELECT * FROM a ORDER BY i LIMIT -1
----
limit
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── internal-ordering: +2
 ├── cardinality: [0 - 0]
 ├── immutable
 ├── key: ()
 ├── fd: ()-->(1-5)
 ├── sort
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    ├── ordering: +2
 │    ├── limit hint: 1.00
 │    └── scan a
 │         ├── columns: k:1!null i:2 f:3 s:4 j:5
 │         ├── key: (1)
 │         └── fd: (1)-->(2-5)
 └── -1

# GenerateTopK is not triggered when there is no ordering
opt expect-not=GenerateTopK
SELECT * FROM a LIMIT 10
----
scan a
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── limit: 10
 ├── key: (1)
 └── fd: (1)-->(2-5)

# GenerateTopK triggered with offset.
opt
SELECT * FROM a ORDER BY i LIMIT 5 OFFSET 3
----
offset
 ├── columns: k:1!null i:2 f:3 s:4 j:5
 ├── internal-ordering: +2
 ├── cardinality: [0 - 5]
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── ordering: +2
 ├── top-k
 │    ├── columns: k:1!null i:2 f:3 s:4 j:5
 │    ├── internal-ordering: +2
 │    ├── k: 8
 │    ├── cardinality: [0 - 8]
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    ├── ordering: +2
 │    └── scan a
 │         ├── columns: k:1!null i:2 f:3 s:4 j:5
 │         ├── key: (1)
 │         └── fd: (1)-->(2-5)
 └── 3

# ---------------------------------------------------
# GenerateLimitedTopKScans
# ---------------------------------------------------

exec-ddl
CREATE TABLE defg (
d INT,
e INT,
f INT,
g INT,
INDEX dd (d),
INDEX dfg (d, f, g),
INDEX df (d, f)
)
----

# Generates an index scan on dd, dfg, and df and an index join to get all
# columns, though these are not the best cost plans.
memo expect=GenerateLimitedTopKScans
SELECT d, e FROM defg ORDER BY d, e LIMIT 10
----
memo (optimized, ~15KB, required=[presentation: d:1,e:2] [ordering: +1,+2])
 ├── G1: (limit G2 G3 ordering=+1,+2) (top-k G2 &{10 +1,+2 }) (top-k G4 &{10 +1,+2 }) (top-k G5 &{10 +1,+2 }) (top-k G6 &{10 +1,+2 }) (top-k G2 &{10 +1,+2 +1}) (top-k G4 &{10 +1,+2 +1}) (top-k G5 &{10 +1,+2 +1}) (top-k G6 &{10 +1,+2 +1})
 │    ├── [presentation: d:1,e:2] [ordering: +1,+2]
 │    │    ├── best: (top-k G2 &{10 +1,+2 })
 │    │    └── cost: 1189.69
 │    └── []
 │         ├── best: (top-k G2 &{10 +1,+2 })
 │         └── cost: 1189.69
 ├── G2: (scan defg,cols=(1,2))
 │    ├── [ordering: +1,+2] [limit hint: 10.00]
 │    │    ├── best: (sort G2)
 │    │    └── cost: 1349.17
 │    ├── [ordering: +1]
 │    │    ├── best: (sort G2)
 │    │    └── cost: 1338.20
 │    ├── [ordering: +1] [limit hint: 104.58]
 │    │    ├── best: (sort G2)
 │    │    └── cost: 1338.20
 │    └── []
 │         ├── best: (scan defg,cols=(1,2))
 │         └── cost: 1098.72
 ├── G3: (const 10)
 ├── G4: (index-join G7 defg,cols=(1,2))
 │    ├── [ordering: +1]
 │    │    ├── best: (index-join G7="[ordering: +1]" defg,cols=(1,2))
 │    │    └── cost: 7138.44
 │    ├── [ordering: +1] [limit hint: 104.58]
 │    │    ├── best: (index-join G7="[ordering: +1] [limit hint: 104.58]" defg,cols=(1,2))
 │    │    └── cost: 1340.81
 │    └── []
 │         ├── best: (index-join G7 defg,cols=(1,2))
 │         └── cost: 7138.44
 ├── G5: (index-join G8 defg,cols=(1,2))
 │    ├── [ordering: +1]
 │    │    ├── best: (index-join G8="[ordering: +1]" defg,cols=(1,2))
 │    │    └── cost: 7158.64
 │    ├── [ordering: +1] [limit hint: 104.58]
 │    │    ├── best: (index-join G8="[ordering: +1] [limit hint: 104.58]" defg,cols=(1,2))
 │    │    └── cost: 1342.90
 │    └── []
 │         ├── best: (index-join G8 defg,cols=(1,2))
 │         └── cost: 7158.64
 ├── G6: (index-join G9 defg,cols=(1,2))
 │    ├── [ordering: +1]
 │    │    ├── best: (index-join G9="[ordering: +1]" defg,cols=(1,2))
 │    │    └── cost: 7148.54
 │    ├── [ordering: +1] [limit hint: 104.58]
 │    │    ├── best: (index-join G9="[ordering: +1] [limit hint: 104.58]" defg,cols=(1,2))
 │    │    └── cost: 1341.85
 │    └── []
 │         ├── best: (index-join G9 defg,cols=(1,2))
 │         └── cost: 7148.54
 ├── G7: (scan defg@dd,cols=(1,5))
 │    ├── [ordering: +1]
 │    │    ├── best: (scan defg@dd,cols=(1,5))
 │    │    └── cost: 1068.42
 │    ├── [ordering: +1] [limit hint: 104.58]
 │    │    ├── best: (scan defg@dd,cols=(1,5))
 │    │    └── cost: 126.79
 │    └── []
 │         ├── best: (scan defg@dd,cols=(1,5))
 │         └── cost: 1068.42
 ├── G8: (scan defg@dfg,cols=(1,5))
 │    ├── [ordering: +1]
 │    │    ├── best: (scan defg@dfg,cols=(1,5))
 │    │    └── cost: 1088.62
 │    ├── [ordering: +1] [limit hint: 104.58]
 │    │    ├── best: (scan defg@dfg,cols=(1,5))
 │    │    └── cost: 128.88
 │    └── []
 │         ├── best: (scan defg@dfg,cols=(1,5))
 │         └── cost: 1088.62
 └── G9: (scan defg@df,cols=(1,5))
      ├── [ordering: +1]
      │    ├── best: (scan defg@df,cols=(1,5))
      │    └── cost: 1078.52
      ├── [ordering: +1] [limit hint: 104.58]
      │    ├── best: (scan defg@df,cols=(1,5))
      │    └── cost: 127.83
      └── []
           ├── best: (scan defg@df,cols=(1,5))
           └── cost: 1078.52

# Generates an index scan on df and an index join to get all columns.
opt expect=GenerateLimitedTopKScans
SELECT d, f, e FROM defg ORDER BY d, f, e LIMIT 10
----
top-k
 ├── columns: d:1 f:3 e:2
 ├── internal-ordering: +1,+3,+2
 ├── k: 10
 ├── cardinality: [0 - 10]
 ├── ordering: +1,+3,+2
 └── index-join defg
      ├── columns: d:1 e:2 f:3
      ├── ordering: +1,+3
      ├── limit hint: 100.00
      └── scan defg@df
           ├── columns: d:1 f:3 rowid:5!null
           ├── key: (5)
           ├── fd: (5)-->(1,3)
           ├── ordering: +1,+3
           └── limit hint: 100.00

# Does not generate a limited top K scan because the order by columns are covered.
opt expect-not=GenerateLimitedTopKScans
SELECT d FROM defg ORDER BY d LIMIT 10
----
scan defg@dd
 ├── columns: d:1
 ├── limit: 10
 └── ordering: +1

# First order column is not the first column in an index.
opt expect-not=GenerateLimitedTopKScans
SELECT * FROM defg ORDER BY f LIMIT 10
----
top-k
 ├── columns: d:1 e:2 f:3 g:4
 ├── internal-ordering: +3
 ├── k: 10
 ├── cardinality: [0 - 10]
 ├── ordering: +3
 └── scan defg
      └── columns: d:1 e:2 f:3 g:4

# GenerateLimitedTopKScans will be triggered, but not add an index
# scan to the memo since NO_INDEX_JOIN is specified.
memo expect-not=GenerateLimitedTopKScans
SELECT d, f, e FROM defg@{NO_INDEX_JOIN} ORDER BY d, f, e LIMIT 10
----
memo (optimized, ~5KB, required=[presentation: d:1,f:3,e:2] [ordering: +1,+3,+2])
 ├── G1: (limit G2 G3 ordering=+1,+3,+2) (top-k G2 &{10 +1,+3,+2 }) (top-k G2 &{10 +1,+3,+2 +1,+3})
 │    ├── [presentation: d:1,f:3,e:2] [ordering: +1,+3,+2]
 │    │    ├── best: (top-k G2 &{10 +1,+3,+2 })
 │    │    └── cost: 1200.32
 │    └── []
 │         ├── best: (top-k G2 &{10 +1,+3,+2 })
 │         └── cost: 1200.32
 ├── G2: (scan defg,cols=(1-3))
 │    ├── [ordering: +1,+3,+2] [limit hint: 10.00]
 │    │    ├── best: (sort G2)
 │    │    └── cost: 1370.36
 │    ├── [ordering: +1,+3]
 │    │    ├── best: (sort G2)
 │    │    └── cost: 1369.27
 │    ├── [ordering: +1,+3] [limit hint: 100.00]
 │    │    ├── best: (sort G2)
 │    │    └── cost: 1369.27
 │    └── []
 │         ├── best: (scan defg,cols=(1-3))
 │         └── cost: 1108.82
 └── G3: (const 10)

# ---------------------------------------------------
# GeneratePartialOrderTopK
# ---------------------------------------------------

# Index orderings dd, dfg, and df can be used.
memo expect=GeneratePartialOrderTopK
SELECT * FROM defg ORDER BY d, f, e LIMIT 10
----
memo (optimized, ~16KB, required=[presentation: d:1,e:2,f:3,g:4] [ordering: +1,+3,+2])
 ├── G1: (limit G2 G3 ordering=+1,+3,+2) (top-k G2 &{10 +1,+3,+2 }) (top-k G4 &{10 +1,+3,+2 }) (top-k G5 &{10 +1,+3,+2 }) (top-k G6 &{10 +1,+3,+2 }) (top-k G2 &{10 +1,+3,+2 +1,+3}) (top-k G4 &{10 +1,+3,+2 +1}) (top-k G5 &{10 +1,+3,+2 +1,+3}) (top-k G6 &{10 +1,+3,+2 +1,+3}) (top-k G4 &{10 +1,+3,+2 +1,+3})
 │    ├── [presentation: d:1,e:2,f:3,g:4] [ordering: +1,+3,+2]
 │    │    ├── best: (top-k G6="[ordering: +1,+3] [limit hint: 100.00]" &{10 +1,+3,+2 +1,+3})
 │    │    └── cost: 741.57
 │    └── []
 │         ├── best: (top-k G2 &{10 +1,+3,+2 })
 │         └── cost: 1210.52
 ├── G2: (scan defg,cols=(1-4))
 │    ├── [ordering: +1,+3,+2] [limit hint: 10.00]
 │    │    ├── best: (sort G2)
 │    │    └── cost: 1390.46
 │    ├── [ordering: +1,+3]
 │    │    ├── best: (sort G2)
 │    │    └── cost: 1389.37
 │    ├── [ordering: +1,+3] [limit hint: 100.00]
 │    │    ├── best: (sort G2)
 │    │    └── cost: 1389.37
 │    └── []
 │         ├── best: (scan defg,cols=(1-4))
 │         └── cost: 1118.92
 ├── G3: (const 10)
 ├── G4: (index-join G7 defg,cols=(1-4))
 │    ├── [ordering: +1,+3]
 │    │    ├── best: (sort G4="[ordering: +1]")
 │    │    └── cost: 7294.90
 │    ├── [ordering: +1,+3] [limit hint: 100.00]
 │    │    ├── best: (sort G4="[ordering: +1]")
 │    │    └── cost: 7294.90
 │    ├── [ordering: +1]
 │    │    ├── best: (index-join G7="[ordering: +1]" defg,cols=(1-4))
 │    │    └── cost: 7158.44
 │    ├── [ordering: +1] [limit hint: 104.58]
 │    │    ├── best: (index-join G7="[ordering: +1] [limit hint: 104.58]" defg,cols=(1-4))
 │    │    └── cost: 1344.81
 │    └── []
 │         ├── best: (index-join G7 defg,cols=(1-4))
 │         └── cost: 7158.44
 ├── G5: (index-join G8 defg,cols=(1-4))
 │    ├── [ordering: +1,+3]
 │    │    ├── best: (index-join G8="[ordering: +1,+3]" defg,cols=(1-4))
 │    │    └── cost: 7178.84
 │    ├── [ordering: +1,+3] [limit hint: 100.00]
 │    │    ├── best: (index-join G8="[ordering: +1,+3] [limit hint: 100.00]" defg,cols=(1-4))
 │    │    └── cost: 733.04
 │    └── []
 │         ├── best: (index-join G8 defg,cols=(1-4))
 │         └── cost: 7178.84
 ├── G6: (index-join G9 defg,cols=(1-4))
 │    ├── [ordering: +1,+3]
 │    │    ├── best: (index-join G9="[ordering: +1,+3]" defg,cols=(1-4))
 │    │    └── cost: 7168.64
 │    ├── [ordering: +1,+3] [limit hint: 100.00]
 │    │    ├── best: (index-join G9="[ordering: +1,+3] [limit hint: 100.00]" defg,cols=(1-4))
 │    │    └── cost: 732.04
 │    └── []
 │         ├── best: (index-join G9 defg,cols=(1-4))
 │         └── cost: 7168.64
 ├── G7: (scan defg@dd,cols=(1,5))
 │    ├── [ordering: +1]
 │    │    ├── best: (scan defg@dd,cols=(1,5))
 │    │    └── cost: 1068.42
 │    ├── [ordering: +1] [limit hint: 104.58]
 │    │    ├── best: (scan defg@dd,cols=(1,5))
 │    │    └── cost: 126.79
 │    └── []
 │         ├── best: (scan defg@dd,cols=(1,5))
 │         └── cost: 1068.42
 ├── G8: (scan defg@dfg,cols=(1,3-5))
 │    ├── [ordering: +1,+3]
 │    │    ├── best: (scan defg@dfg,cols=(1,3-5))
 │    │    └── cost: 1108.82
 │    ├── [ordering: +1,+3] [limit hint: 100.00]
 │    │    ├── best: (scan defg@dfg,cols=(1,3-5))
 │    │    └── cost: 126.02
 │    └── []
 │         ├── best: (scan defg@dfg,cols=(1,3-5))
 │         └── cost: 1108.82
 └── G9: (scan defg@df,cols=(1,3,5))
      ├── [ordering: +1,+3]
      │    ├── best: (scan defg@df,cols=(1,3,5))
      │    └── cost: 1088.62
      ├── [ordering: +1,+3] [limit hint: 100.00]
      │    ├── best: (scan defg@df,cols=(1,3,5))
      │    └── cost: 124.02
      └── []
           ├── best: (scan defg@df,cols=(1,3,5))
           └── cost: 1088.62

# Only index ordering dfg can be used for the topk.
memo expect=GeneratePartialOrderTopK disable=GenerateLimitedTopKScans
SELECT d, f, g FROM defg ORDER BY d, g LIMIT 10
----
memo (optimized, ~5KB, required=[presentation: d:1,f:3,g:4] [ordering: +1,+4])
 ├── G1: (limit G2 G3 ordering=+1,+4) (top-k G2 &{10 +1,+4 }) (top-k G2 &{10 +1,+4 +1})
 │    ├── [presentation: d:1,f:3,g:4] [ordering: +1,+4]
 │    │    ├── best: (top-k G2="[ordering: +1] [limit hint: 104.58]" &{10 +1,+4 +1})
 │    │    └── cost: 139.73
 │    └── []
 │         ├── best: (top-k G2 &{10 +1,+4 })
 │         └── cost: 1189.79
 ├── G2: (scan defg,cols=(1,3,4)) (scan defg@dfg,cols=(1,3,4))
 │    ├── [ordering: +1,+4] [limit hint: 10.00]
 │    │    ├── best: (sort G2="[ordering: +1]")
 │    │    └── cost: 1225.18
 │    ├── [ordering: +1]
 │    │    ├── best: (scan defg@dfg,cols=(1,3,4))
 │    │    └── cost: 1098.72
 │    ├── [ordering: +1] [limit hint: 104.58]
 │    │    ├── best: (scan defg@dfg,cols=(1,3,4))
 │    │    └── cost: 129.92
 │    └── []
 │         ├── best: (scan defg@dfg,cols=(1,3,4))
 │         └── cost: 1098.72
 └── G3: (const 10)

# Ensure that we don't incorrectly use orderings that don't match the direction.
memo expect-not=GeneratePartialOrderTopK
SELECT * FROM defg ORDER BY g DESC LIMIT 10
----
memo (optimized, ~4KB, required=[presentation: d:1,e:2,f:3,g:4] [ordering: -4])
 ├── G1: (limit G2 G3 ordering=-4) (top-k G2 &{10 -4 })
 │    ├── [presentation: d:1,e:2,f:3,g:4] [ordering: -4]
 │    │    ├── best: (top-k G2 &{10 -4 })
 │    │    └── cost: 1205.77
 │    └── []
 │         ├── best: (top-k G2 &{10 -4 })
 │         └── cost: 1205.77
 ├── G2: (scan defg,cols=(1-4))
 │    ├── [ordering: -4] [limit hint: 10.00]
 │    │    ├── best: (sort G2)
 │    │    └── cost: 1378.40
 │    └── []
 │         ├── best: (scan defg,cols=(1-4))
 │         └── cost: 1118.92
 └── G3: (const 10)

# No index matches.
opt expect-not=GeneratePartialOrderTopK
SELECT * FROM defg ORDER BY e LIMIT 10
----
top-k
 ├── columns: d:1 e:2 f:3 g:4
 ├── internal-ordering: +2
 ├── k: 10
 ├── cardinality: [0 - 10]
 ├── ordering: +2
 └── scan defg
      └── columns: d:1 e:2 f:3 g:4

# Regression testing for #76102.
exec-ddl
CREATE TABLE tab_76102 (
 a INT2 NULL,
 b INT2 NULL AS (a + NULL) STORED,
 UNIQUE (a DESC)
)
----

# The GROUP BY column can only have one value (NULL), so  we do not benefit from
# limited TopK, and trying to apply the rule may lead to errors since the
# required ordering is optimized away in this case.
opt expect-not=GenerateLimitedTopKScans
SELECT b FROM tab_76102@tab_76102_a_key GROUP BY b LIMIT 5
----
index-join tab_76102
 ├── columns: b:2
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(2)
 └── scan tab_76102@tab_76102_a_key
      ├── columns: rowid:3!null
      ├── limit: 1
      ├── flags: force-index=tab_76102_a_key
      ├── key: ()
      └── fd: ()-->(3)

exec-ddl
DROP TABLE pqrs
----

exec-ddl
CREATE TABLE pqrs
(
    p INT NOT NULL,
    q INT NOT NULL,
    r INT NOT NULL,
    s INT NOT NULL,
    PRIMARY KEY (p, q, r, s),
    CHECK (p IN (1,5,10))
)
----

exec-ddl
ALTER TABLE pqrs INJECT STATISTICS '[
  {
    "columns": ["q"],
    "created_at": "2018-01-01 1:00:00.00000+00:00",
    "row_count": 100000,
    "distinct_count": 10000
  }
]'
----

# ----------------------------------
# SplitLimitedSelectIntoUnionSelects
# ----------------------------------
opt expect=SplitLimitedSelectIntoUnionSelects
SELECT * FROM pqrs WHERE q > 2 ORDER BY q LIMIT 10
----
limit
 ├── columns: p:1!null q:2!null r:3!null s:4!null
 ├── internal-ordering: +2
 ├── cardinality: [0 - 10]
 ├── key: (1-4)
 ├── ordering: +2
 ├── union-all
 │    ├── columns: p:1!null q:2!null r:3!null s:4!null
 │    ├── left columns: p:25 q:26 r:27 s:28
 │    ├── right columns: p:19 q:20 r:21 s:22
 │    ├── cardinality: [0 - 30]
 │    ├── ordering: +2
 │    ├── limit hint: 10.00
 │    ├── union-all
 │    │    ├── columns: p:25!null q:26!null r:27!null s:28!null
 │    │    ├── left columns: p:7 q:8 r:9 s:10
 │    │    ├── right columns: p:13 q:14 r:15 s:16
 │    │    ├── cardinality: [0 - 20]
 │    │    ├── ordering: +26
 │    │    ├── limit hint: 10.00
 │    │    ├── scan pqrs
 │    │    │    ├── columns: p:7!null q:8!null r:9!null s:10!null
 │    │    │    ├── constraint: /7/8/9/10: [/1/3 - /1]
 │    │    │    ├── limit: 10
 │    │    │    ├── key: (8-10)
 │    │    │    ├── fd: ()-->(7)
 │    │    │    ├── ordering: +8 opt(7) [actual: +8]
 │    │    │    └── limit hint: 10.00
 │    │    └── scan pqrs
 │    │         ├── columns: p:13!null q:14!null r:15!null s:16!null
 │    │         ├── constraint: /13/14/15/16: [/5/3 - /5]
 │    │         ├── limit: 10
 │    │         ├── key: (14-16)
 │    │         ├── fd: ()-->(13)
 │    │         ├── ordering: +14 opt(13) [actual: +14]
 │    │         └── limit hint: 10.00
 │    └── scan pqrs
 │         ├── columns: p:19!null q:20!null r:21!null s:22!null
 │         ├── constraint: /19/20/21/22: [/10/3 - /10]
 │         ├── limit: 10
 │         ├── key: (20-22)
 │         ├── fd: ()-->(19)
 │         ├── ordering: +20 opt(19) [actual: +20]
 │         └── limit hint: 10.00
 └── 10

opt expect=SplitLimitedSelectIntoUnionSelects
SELECT * FROM pqrs WHERE q IN (0,2,4) AND r IN (0,2,4) AND s IN (0,2,4) ORDER BY q LIMIT 10
----
top-k
 ├── columns: p:1!null q:2!null r:3!null s:4!null
 ├── internal-ordering: +2
 ├── k: 10
 ├── cardinality: [0 - 10]
 ├── key: (1-4)
 ├── ordering: +2
 └── scan pqrs
      ├── columns: p:1!null q:2!null r:3!null s:4!null
      ├── constraint: /1/2/3/4
      │    ├── [/1/0/0/0 - /1/0/0/0]
      │    ├── [/1/0/0/2 - /1/0/0/2]
      │    ├── [/1/0/0/4 - /1/0/0/4]
      │    ├── [/1/0/2/0 - /1/0/2/0]
      │    ├── [/1/0/2/2 - /1/0/2/2]
      │    ├── [/1/0/2/4 - /1/0/2/4]
      │    ├── [/1/0/4/0 - /1/0/4/0]
      │    ├── [/1/0/4/2 - /1/0/4/2]
      │    ├── [/1/0/4/4 - /1/0/4/4]
      │    ├── [/1/2/0/0 - /1/2/0/0]
      │    ├── [/1/2/0/2 - /1/2/0/2]
      │    ├── [/1/2/0/4 - /1/2/0/4]
      │    ├── [/1/2/2/0 - /1/2/2/0]
      │    ├── [/1/2/2/2 - /1/2/2/2]
      │    ├── [/1/2/2/4 - /1/2/2/4]
      │    ├── [/1/2/4/0 - /1/2/4/0]
      │    ├── [/1/2/4/2 - /1/2/4/2]
      │    ├── [/1/2/4/4 - /1/2/4/4]
      │    ├── [/1/4/0/0 - /1/4/0/0]
      │    ├── [/1/4/0/2 - /1/4/0/2]
      │    ├── [/1/4/0/4 - /1/4/0/4]
      │    ├── [/1/4/2/0 - /1/4/2/0]
      │    ├── [/1/4/2/2 - /1/4/2/2]
      │    ├── [/1/4/2/4 - /1/4/2/4]
      │    ├── [/1/4/4/0 - /1/4/4/0]
      │    ├── [/1/4/4/2 - /1/4/4/2]
      │    ├── [/1/4/4/4 - /1/4/4/4]
      │    ├── [/5/0/0/0 - /5/0/0/0]
      │    ├── [/5/0/0/2 - /5/0/0/2]
      │    ├── [/5/0/0/4 - /5/0/0/4]
      │    ├── [/5/0/2/0 - /5/0/2/0]
      │    ├── [/5/0/2/2 - /5/0/2/2]
      │    ├── [/5/0/2/4 - /5/0/2/4]
      │    ├── [/5/0/4/0 - /5/0/4/0]
      │    ├── [/5/0/4/2 - /5/0/4/2]
      │    ├── [/5/0/4/4 - /5/0/4/4]
      │    ├── [/5/2/0/0 - /5/2/0/0]
      │    ├── [/5/2/0/2 - /5/2/0/2]
      │    ├── [/5/2/0/4 - /5/2/0/4]
      │    ├── [/5/2/2/0 - /5/2/2/0]
      │    ├── [/5/2/2/2 - /5/2/2/2]
      │    ├── [/5/2/2/4 - /5/2/2/4]
      │    ├── [/5/2/4/0 - /5/2/4/0]
      │    ├── [/5/2/4/2 - /5/2/4/2]
      │    ├── [/5/2/4/4 - /5/2/4/4]
      │    ├── [/5/4/0/0 - /5/4/0/0]
      │    ├── [/5/4/0/2 - /5/4/0/2]
      │    ├── [/5/4/0/4 - /5/4/0/4]
      │    ├── [/5/4/2/0 - /5/4/2/0]
      │    ├── [/5/4/2/2 - /5/4/2/2]
      │    ├── [/5/4/2/4 - /5/4/2/4]
      │    ├── [/5/4/4/0 - /5/4/4/0]
      │    ├── [/5/4/4/2 - /5/4/4/2]
      │    ├── [/5/4/4/4 - /5/4/4/4]
      │    ├── [/10/0/0/0 - /10/0/0/0]
      │    ├── [/10/0/0/2 - /10/0/0/2]
      │    ├── [/10/0/0/4 - /10/0/0/4]
      │    ├── [/10/0/2/0 - /10/0/2/0]
      │    ├── [/10/0/2/2 - /10/0/2/2]
      │    ├── [/10/0/2/4 - /10/0/2/4]
      │    ├── [/10/0/4/0 - /10/0/4/0]
      │    ├── [/10/0/4/2 - /10/0/4/2]
      │    ├── [/10/0/4/4 - /10/0/4/4]
      │    ├── [/10/2/0/0 - /10/2/0/0]
      │    ├── [/10/2/0/2 - /10/2/0/2]
      │    ├── [/10/2/0/4 - /10/2/0/4]
      │    ├── [/10/2/2/0 - /10/2/2/0]
      │    ├── [/10/2/2/2 - /10/2/2/2]
      │    ├── [/10/2/2/4 - /10/2/2/4]
      │    ├── [/10/2/4/0 - /10/2/4/0]
      │    ├── [/10/2/4/2 - /10/2/4/2]
      │    ├── [/10/2/4/4 - /10/2/4/4]
      │    ├── [/10/4/0/0 - /10/4/0/0]
      │    ├── [/10/4/0/2 - /10/4/0/2]
      │    ├── [/10/4/0/4 - /10/4/0/4]
      │    ├── [/10/4/2/0 - /10/4/2/0]
      │    ├── [/10/4/2/2 - /10/4/2/2]
      │    ├── [/10/4/2/4 - /10/4/2/4]
      │    ├── [/10/4/4/0 - /10/4/4/0]
      │    ├── [/10/4/4/2 - /10/4/4/2]
      │    └── [/10/4/4/4 - /10/4/4/4]
      └── key: (1-4)

# Do not split limit select into union of selects when an index cannot provide
# the required ordering.
opt expect-not=SplitLimitedSelectIntoUnionSelects
SELECT * FROM pqrs WHERE q IN (0,2,4) ORDER BY q, s LIMIT 10
----
top-k
 ├── columns: p:1!null q:2!null r:3!null s:4!null
 ├── internal-ordering: +2,+4
 ├── k: 10
 ├── cardinality: [0 - 10]
 ├── key: (1-4)
 ├── ordering: +2,+4
 └── scan pqrs
      ├── columns: p:1!null q:2!null r:3!null s:4!null
      ├── constraint: /1/2/3/4
      │    ├── [/1/0 - /1/0]
      │    ├── [/1/2 - /1/2]
      │    ├── [/1/4 - /1/4]
      │    ├── [/5/0 - /5/0]
      │    ├── [/5/2 - /5/2]
      │    ├── [/5/4 - /5/4]
      │    ├── [/10/0 - /10/0]
      │    ├── [/10/2 - /10/2]
      │    └── [/10/4 - /10/4]
      └── key: (1-4)

# Regression test for #82730.
exec-ddl
CREATE TABLE t82730a (
  col1_0 NAME,
  col1_1 INT8,
  col1_3 INT8,
  col1_5 VARCHAR,
  PRIMARY KEY (col1_0 ASC),
  UNIQUE (col1_1 ASC, col1_3 ASC)
    PARTITION BY LIST (col1_1,col1_3) (
      PARTITION table1_part_0 VALUES IN (
                                (
                                  1,
                                  NULL
                                )
                              ),
      PARTITION table1_part_1 VALUES IN (
                                (
                                  1000000000,
                                  NULL
                                )
                              ),
      PARTITION table1_part_2 VALUES IN (
                                (
                                  2000000000,
                                  NULL
                                )
                              )
      )
)
----

opt expect-not=SplitLimitedSelectIntoUnionSelects
UPDATE t82730a
     SET col1_5 = col1_5
   WHERE col1_0 ILIKE col1_0
ORDER BY col1_3
   LIMIT 84
----
update t82730a
 ├── columns: <none>
 ├── fetch columns: col1_0:7 col1_1:8 col1_3:9 col1_5:10
 ├── update-mapping:
 │    └── col1_5:10 => col1_5:4
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── index-join t82730a
      ├── columns: col1_0:7!null col1_1:8 col1_3:9 col1_5:10
      ├── cardinality: [0 - 84]
      ├── key: (7)
      ├── fd: (7)-->(8-10), (8,9)~~>(7,10)
      └── top-k
           ├── columns: col1_0:7!null col1_1:8 col1_3:9
           ├── internal-ordering: +9
           ├── k: 84
           ├── cardinality: [0 - 84]
           ├── key: (7)
           ├── fd: (7)-->(8,9), (8,9)~~>(7)
           └── select
                ├── columns: col1_0:7!null col1_1:8 col1_3:9
                ├── key: (7)
                ├── fd: (7)-->(8,9), (8,9)~~>(7)
                ├── scan t82730a@t82730a_col1_1_col1_3_key
                │    ├── columns: col1_0:7!null col1_1:8 col1_3:9
                │    ├── constraint: /8/9
                │    │    ├── [/NULL - /0]
                │    │    ├── [/2 - /999999999]
                │    │    ├── [/1000000001 - /1999999999]
                │    │    └── [/2000000001 - ]
                │    ├── flags: avoid-full-scan
                │    ├── key: (7)
                │    └── fd: (7)-->(8,9), (8,9)~~>(7)
                └── filters
                     └── col1_0:7 ILIKE col1_0:7 [outer=(7), constraints=(/7: (/NULL - ])]

exec-ddl
CREATE TABLE t82730b (
  col1_0 NAME,
  col1_1 INT8,
  col1_3 INT8,
  col1_5 VARCHAR,
  PRIMARY KEY (col1_0 ASC),
  UNIQUE (col1_1 ASC, col1_3 ASC)
    PARTITION BY LIST (col1_1,col1_3) (
      PARTITION table2_part_0 VALUES IN (
                                (
                                  1,
                                  NULL
                                )
                              ),
      PARTITION table2_part_1 VALUES IN (
                                (
                                  5,
                                  NULL
                                )
                              ),
      PARTITION table2_part_2 VALUES IN (
                                (
                                  5000000,
                                  NULL
                                )
                              )
      )
)
----

opt expect=SplitLimitedSelectIntoUnionSelects
UPDATE t82730b AS tab_41831
     SET col1_5 = col1_5
   WHERE col1_0 ILIKE col1_0
ORDER BY col1_3
   LIMIT 84
----
update t82730b [as=tab_41831]
 ├── columns: <none>
 ├── fetch columns: col1_0:7 col1_1:8 col1_3:9 col1_5:10
 ├── update-mapping:
 │    └── col1_5:10 => col1_5:4
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── index-join t82730b
      ├── columns: col1_0:7!null col1_1:8 col1_3:9 col1_5:10
      ├── cardinality: [0 - 84]
      ├── key: (7)
      ├── fd: (7)-->(8-10), (8,9)~~>(7,10)
      └── top-k
           ├── columns: col1_0:7!null col1_1:8 col1_3:9
           ├── internal-ordering: +9
           ├── k: 84
           ├── cardinality: [0 - 84]
           ├── key: (7)
           ├── fd: (7)-->(8,9), (8,9)~~>(7)
           └── select
                ├── columns: col1_0:7!null col1_1:8 col1_3:9
                ├── key: (7)
                ├── fd: (7)-->(8,9), (8,9)~~>(7)
                ├── scan t82730b@t82730b_col1_1_col1_3_key [as=tab_41831]
                │    ├── columns: col1_0:7!null col1_1:8 col1_3:9
                │    ├── constraint: /8/9
                │    │    ├── [/NULL - /0]
                │    │    ├── [/2 - /4]
                │    │    ├── [/6 - /4999999]
                │    │    └── [/5000001 - ]
                │    ├── flags: avoid-full-scan
                │    ├── key: (7)
                │    └── fd: (7)-->(8,9), (8,9)~~>(7)
                └── filters
                     └── col1_0:7 ILIKE col1_0:7 [outer=(7), constraints=(/7: (/NULL - ])]

# Regression test for #88993. Do not construct an unordered limit below the
# UnionAll.
exec-ddl
CREATE TABLE t88993 (
  a INT,
  b INT,
  c INT,
  INDEX bca_idx (b, c, a)
);
----

opt disable=GenerateTopK
SELECT min(a) FROM t88993@bca_idx WHERE (b <= 11 AND c < 50) OR (b = 11 AND c = 50) OR (b >= 11 AND c > 50)
----
scalar-group-by
 ├── columns: min:7
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(7)
 ├── limit
 │    ├── columns: a:1!null b:2!null c:3!null
 │    ├── internal-ordering: +1
 │    ├── cardinality: [0 - 1]
 │    ├── key: ()
 │    ├── fd: ()-->(1-3)
 │    ├── sort
 │    │    ├── columns: a:1!null b:2!null c:3!null
 │    │    ├── ordering: +1
 │    │    ├── limit hint: 1.00
 │    │    └── select
 │    │         ├── columns: a:1!null b:2!null c:3!null
 │    │         ├── scan t88993@bca_idx
 │    │         │    ├── columns: a:1 b:2!null c:3
 │    │         │    ├── constraint: /2/3/1/4
 │    │         │    │    ├── (/NULL - /11/49]
 │    │         │    │    ├── (/11/50/NULL - /11/50]
 │    │         │    │    └── (/11/51/NULL - ]
 │    │         │    └── flags: force-index=bca_idx
 │    │         └── filters
 │    │              ├── (((b:2 <= 11) AND (c:3 < 50)) OR ((b:2 = 11) AND (c:3 = 50))) OR ((b:2 >= 11) AND (c:3 > 50)) [outer=(2,3), constraints=(/2: (/NULL - ]; /3: (/NULL - /49] [/50 - /50] [/51 - ])]
 │    │              └── a:1 IS NOT NULL [outer=(1), constraints=(/1: (/NULL - ]; tight)]
 │    └── 1
 └── aggregations
      └── const-agg [as=min:7, outer=(1)]
           └── a:1

# Regression Test for #93410
exec-ddl
CREATE TABLE t93410 (
        col1 INT NOT NULL,
        col2 INT NOT NULL,
        col3 INT NOT NULL,
        col4 INT NOT NULL,
        col5 INT NOT NULL,
        col6 INT NOT NULL,
        col7 INT NOT NULL,
        col8 INT NOT NULL,
        col9 INT NOT NULL,
        col10 INT NOT NULL,
        col11 INT NOT NULL,
        col12 INT NULL,
        col13 INT NULL,
        col14 INT NOT NULL,
        col15 INT NULL,
        col16 INT,
        CONSTRAINT "primary" PRIMARY KEY (col1 ASC, col2 ASC, col3 ASC, col4 ASC, col5 ASC),
        CONSTRAINT fk_col1_ref_t93410 FOREIGN KEY (col1, col2, col3, col4, col12) REFERENCES t93410(col1, col2, col3, col4, col5) ON DELETE CASCADE,
        UNIQUE INDEX t93410_col1_col2_col3_col5_key (col1 ASC, col2 ASC, col3 ASC, col5 ASC),
        INDEX t93410_col13_idx (col13 ASC),
        INDEX t93410_col1_col2_col13_col15_idx (col1 ASC, col2 ASC, col13 ASC, col15 ASC),
        UNIQUE INDEX t93410_col1_col2_col5_col9_key (col1 ASC, col2 ASC, col5 ASC, col9 ASC),
        UNIQUE INDEX t93410_col1_col2_col5_key (col1 ASC, col2 ASC, col5 ASC)
)
----

exec-ddl
CREATE TABLE t93410_2 (
        col1 INT NOT NULL,
        col2 INT NOT NULL,
        col3 INT NOT NULL,
        col4 INT NOT NULL,
        col5 INT NOT NULL,
        col6 INT NOT NULL,
        CONSTRAINT "primary" PRIMARY KEY (col1 ASC, col2 ASC, col3 ASC, col5 ASC, col6 ASC),
        CONSTRAINT fk_col1_ref_t93410 FOREIGN KEY (col1, col2, col3, col4, col5) REFERENCES t93410(col1, col2, col3, col4, col5) ON DELETE CASCADE,
        INDEX t93410_2_col1_col2_col3_col4_col5_idx (col1 ASC, col2 ASC, col3 ASC, col4 ASC, col5 ASC)
)
----

exec-ddl
ALTER TABLE t93410 INJECT STATISTICS '[
    {
        "avg_size": 2,
        "columns": [
            "col1"
        ],
        "created_at": "2022-12-15 19:58:40.9211",
        "distinct_count": 100000,
        "histo_buckets": [
            {
                "distinct_range": 0,
                "num_eq": 300002,
                "num_range": 0,
                "upper_bound": "1"
            },
            {
                "distinct_range": 100000,
                "num_eq": 0,
                "num_range": 100000,
                "upper_bound": "100000"
            }
        ],
        "histo_col_type": "INT8",
        "histo_version": 2,
        "null_count": 0,
        "row_count": 400001
    },
    {
        "avg_size": 2,
        "columns": [
            "col2"
        ],
        "created_at": "2022-12-15 19:58:40.9211",
        "distinct_count": 100000,
        "histo_buckets": [
            {
                "distinct_range": 0,
                "num_eq": 300002,
                "num_range": 0,
                "upper_bound": "1"
            },
            {
                "distinct_range": 100000,
                "num_eq": 0,
                "num_range": 100000,
                "upper_bound": "100000"
            }
        ],
        "histo_col_type": "INT8",
        "histo_version": 2,
        "null_count": 0,
        "row_count": 400001
    }
]';
----

exec-ddl
ALTER TABLE t93410_2 INJECT STATISTICS '[
    {
        "avg_size": 2,
        "columns": [
            "col1"
        ],
        "created_at": "2022-12-15 19:58:40.9211",
        "distinct_count": 100000,
        "histo_buckets": [
            {
                "distinct_range": 0,
                "num_eq": 300002,
                "num_range": 0,
                "upper_bound": "1"
            },
            {
                "distinct_range": 100000,
                "num_eq": 0,
                "num_range": 100000,
                "upper_bound": "100000"
            }
        ],
        "histo_col_type": "INT8",
        "histo_version": 2,
        "null_count": 0,
        "row_count": 400001
    },
    {
        "avg_size": 2,
        "columns": [
            "col2"
        ],
        "created_at": "2022-12-15 19:58:40.9211",
        "distinct_count": 100000,
        "histo_buckets": [
            {
                "distinct_range": 0,
                "num_eq": 300002,
                "num_range": 0,
                "upper_bound": "1"
            },
            {
                "distinct_range": 100000,
                "num_eq": 0,
                "num_range": 100000,
                "upper_bound": "100000"
            }
        ],
        "histo_col_type": "INT8",
        "histo_version": 2,
        "null_count": 0,
        "row_count": 400001
    }
]';
----

# A streaming group-by with no TopK operation should be generated.
opt expect=GenerateStreamingGroupByLimitOrderingHint
SELECT
  DISTINCT
  t1.col1,
  t1.col2,
  t1.col3,
  t1.col4,
  t1.col5,
  t1.col6,
  t1.col7,
  t1.col8,
  t1.col9,
  t1.col10,
  t1.col11,
  t1.col12,
  t1.col13,
  t1.col14,
  t1.col15,
  t1.col16,
  array_remove(array_agg(t2.col6), NULL) AS col6s
FROM
  t93410 AS t1
  LEFT OUTER JOIN t93410_2
      AS t2 USING (col1, col2, col3, col4, col5)
WHERE
  t1.col2 = 1
  AND t1.col1 = 1
GROUP BY
  t1.col1,
  t1.col2,
  t1.col3,
  t1.col4,
  t1.col5
ORDER BY
  col9 DESC, t1.col5 DESC
LIMIT 20
----
project
 ├── columns: col1:1!null col2:2!null col3:3!null col4:4!null col5:5!null col6:6!null col7:7!null col8:8!null col9:9!null col10:10!null col11:11!null col12:12 col13:13 col14:14!null col15:15 col16:16 col6s:28
 ├── cardinality: [0 - 20]
 ├── immutable
 ├── key: (5)
 ├── fd: ()-->(1,2), (5)-->(3,4,6-16,28)
 ├── ordering: -9,-5 opt(1,2) [actual: -9,-5]
 ├── limit
 │    ├── columns: t1.col1:1!null t1.col2:2!null t1.col3:3!null t1.col4:4!null t1.col5:5!null t1.col6:6!null col7:7!null col8:8!null col9:9!null col10:10!null col11:11!null col12:12 col13:13 col14:14!null col15:15 col16:16 array_agg:27
 │    ├── internal-ordering: -9,-5 opt(1,2)
 │    ├── cardinality: [0 - 20]
 │    ├── key: (5)
 │    ├── fd: ()-->(1,2), (5)-->(1-4,6-16,27)
 │    ├── ordering: -9,-5 opt(1,2) [actual: -9,-5]
 │    ├── group-by (streaming)
 │    │    ├── columns: t1.col1:1!null t1.col2:2!null t1.col3:3!null t1.col4:4!null t1.col5:5!null t1.col6:6!null col7:7!null col8:8!null col9:9!null col10:10!null col11:11!null col12:12 col13:13 col14:14!null col15:15 col16:16 array_agg:27
 │    │    ├── grouping columns: t1.col5:5!null col9:9!null
 │    │    ├── internal-ordering: -9,-5 opt(1,2)
 │    │    ├── key: (5)
 │    │    ├── fd: ()-->(1,2), (5)-->(1-4,6-16,27)
 │    │    ├── ordering: -9,-5 opt(1,2) [actual: -9,-5]
 │    │    ├── limit hint: 20.00
 │    │    ├── left-join (lookup t93410_2@t93410_2_col1_col2_col3_col4_col5_idx [as=t2])
 │    │    │    ├── columns: t1.col1:1!null t1.col2:2!null t1.col3:3!null t1.col4:4!null t1.col5:5!null t1.col6:6!null col7:7!null col8:8!null col9:9!null col10:10!null col11:11!null col12:12 col13:13 col14:14!null col15:15 col16:16 t2.col1:19 t2.col2:20 t2.col3:21 t2.col4:22 t2.col5:23 t2.col6:24
 │    │    │    ├── key columns: [1 2 3 4 5] = [19 20 21 22 23]
 │    │    │    ├── key: (5,21,23,24)
 │    │    │    ├── fd: ()-->(1,2), (5)-->(3,4,6-16), (21,23,24)-->(22), (5,21,23,24)-->(19,20)
 │    │    │    ├── ordering: -9,-5 opt(1,2) [actual: -9,-5]
 │    │    │    ├── limit hint: 20.00
 │    │    │    ├── index-join t93410
 │    │    │    │    ├── columns: t1.col1:1!null t1.col2:2!null t1.col3:3!null t1.col4:4!null t1.col5:5!null t1.col6:6!null col7:7!null col8:8!null col9:9!null col10:10!null col11:11!null col12:12 col13:13 col14:14!null col15:15 col16:16
 │    │    │    │    ├── key: (5)
 │    │    │    │    ├── fd: ()-->(1,2), (5)-->(3,4,6-16)
 │    │    │    │    ├── ordering: -9,-5 opt(1,2) [actual: -9,-5]
 │    │    │    │    ├── limit hint: 100.00
 │    │    │    │    └── sort
 │    │    │    │         ├── columns: t1.col1:1!null t1.col2:2!null t1.col3:3!null t1.col4:4!null t1.col5:5!null col9:9!null
 │    │    │    │         ├── key: (5)
 │    │    │    │         ├── fd: ()-->(1,2), (5)-->(3,4,9)
 │    │    │    │         ├── ordering: -9,-5 opt(1,2) [actual: -9,-5]
 │    │    │    │         ├── limit hint: 100.00
 │    │    │    │         └── scan t93410@t93410_col1_col2_col5_col9_key [as=t1]
 │    │    │    │              ├── columns: t1.col1:1!null t1.col2:2!null t1.col3:3!null t1.col4:4!null t1.col5:5!null col9:9!null
 │    │    │    │              ├── constraint: /1/2/5/9: [/1/1 - /1/1]
 │    │    │    │              ├── key: (5)
 │    │    │    │              └── fd: ()-->(1,2), (5)-->(3,4,9)
 │    │    │    └── filters
 │    │    │         ├── t2.col2:20 = 1 [outer=(20), constraints=(/20: [/1 - /1]; tight), fd=()-->(20)]
 │    │    │         └── t2.col1:19 = 1 [outer=(19), constraints=(/19: [/1 - /1]; tight), fd=()-->(19)]
 │    │    └── aggregations
 │    │         ├── array-agg [as=array_agg:27, outer=(24)]
 │    │         │    └── t2.col6:24
 │    │         ├── const-agg [as=t1.col1:1, outer=(1)]
 │    │         │    └── t1.col1:1
 │    │         ├── const-agg [as=t1.col2:2, outer=(2)]
 │    │         │    └── t1.col2:2
 │    │         ├── const-agg [as=t1.col3:3, outer=(3)]
 │    │         │    └── t1.col3:3
 │    │         ├── const-agg [as=t1.col4:4, outer=(4)]
 │    │         │    └── t1.col4:4
 │    │         ├── const-agg [as=t1.col6:6, outer=(6)]
 │    │         │    └── t1.col6:6
 │    │         ├── const-agg [as=col7:7, outer=(7)]
 │    │         │    └── col7:7
 │    │         ├── const-agg [as=col8:8, outer=(8)]
 │    │         │    └── col8:8
 │    │         ├── const-agg [as=col10:10, outer=(10)]
 │    │         │    └── col10:10
 │    │         ├── const-agg [as=col11:11, outer=(11)]
 │    │         │    └── col11:11
 │    │         ├── const-agg [as=col12:12, outer=(12)]
 │    │         │    └── col12:12
 │    │         ├── const-agg [as=col13:13, outer=(13)]
 │    │         │    └── col13:13
 │    │         ├── const-agg [as=col14:14, outer=(14)]
 │    │         │    └── col14:14
 │    │         ├── const-agg [as=col15:15, outer=(15)]
 │    │         │    └── col15:15
 │    │         └── const-agg [as=col16:16, outer=(16)]
 │    │              └── col16:16
 │    └── 20
 └── projections
      └── array_remove(array_agg:27, NULL) [as=col6s:28, outer=(27), immutable]

# GenerateStreamingGroupByLimitOrderingHint should not fire if there is no
# overlap between the grouping columns and order-by columns.
opt expect-not=GenerateStreamingGroupByLimitOrderingHint
SELECT
  DISTINCT
  t2.col1,
  t2.col2,
  t2.col3,
  t2.col4,
  t2.col5,
  array_remove(array_agg(t1.col6), NULL) AS col6s
FROM
  t93410 AS t1
  LEFT OUTER JOIN t93410_2
      AS t2 USING (col1, col2, col3, col4, col5)
WHERE
  t1.col2 = 1
  AND t1.col1 = 1
GROUP BY
  t2.col1,
  t2.col2,
  t2.col3,
  t2.col4,
  t2.col5
ORDER BY
  col6s DESC
LIMIT 20
----
top-k
 ├── columns: col1:19 col2:20 col3:21 col4:22 col5:23 col6s:28
 ├── internal-ordering: -28
 ├── k: 20
 ├── cardinality: [0 - 20]
 ├── immutable
 ├── key: (19-23)
 ├── fd: (19-23)-->(28)
 ├── ordering: -28
 └── project
      ├── columns: col6s:28 t2.col1:19 t2.col2:20 t2.col3:21 t2.col4:22 t2.col5:23
      ├── immutable
      ├── key: (19-23)
      ├── fd: (19-23)-->(28)
      ├── group-by (hash)
      │    ├── columns: t2.col1:19 t2.col2:20 t2.col3:21 t2.col4:22 t2.col5:23 array_agg:27!null
      │    ├── grouping columns: t2.col1:19 t2.col2:20 t2.col3:21 t2.col4:22 t2.col5:23
      │    ├── key: (19-23)
      │    ├── fd: (19-23)-->(27)
      │    ├── left-join (merge)
      │    │    ├── columns: t1.col1:1!null t1.col2:2!null t1.col3:3!null t1.col4:4!null t1.col5:5!null t1.col6:6!null t2.col1:19 t2.col2:20 t2.col3:21 t2.col4:22 t2.col5:23
      │    │    ├── left ordering: +3,+4,+5,+1,+2
      │    │    ├── right ordering: +21,+22,+23,+19,+20
      │    │    ├── fd: ()-->(1,2), (5)-->(3,4,6)
      │    │    ├── scan t93410 [as=t1]
      │    │    │    ├── columns: t1.col1:1!null t1.col2:2!null t1.col3:3!null t1.col4:4!null t1.col5:5!null t1.col6:6!null
      │    │    │    ├── constraint: /1/2/3/4/5: [/1/1 - /1/1]
      │    │    │    ├── key: (5)
      │    │    │    ├── fd: ()-->(1,2), (5)-->(3,4,6)
      │    │    │    └── ordering: +3,+4,+5 opt(1,2) [actual: +3,+4,+5]
      │    │    ├── scan t93410_2@t93410_2_col1_col2_col3_col4_col5_idx [as=t2]
      │    │    │    ├── columns: t2.col1:19!null t2.col2:20!null t2.col3:21!null t2.col4:22!null t2.col5:23!null
      │    │    │    ├── constraint: /19/20/21/22/23/24: [/1/1 - /1/1]
      │    │    │    ├── fd: ()-->(19,20)
      │    │    │    └── ordering: +21,+22,+23 opt(19,20) [actual: +21,+22,+23]
      │    │    └── filters (true)
      │    └── aggregations
      │         └── array-agg [as=array_agg:27, outer=(6)]
      │              └── t1.col6:6
      └── projections
           └── array_remove(array_agg:27, NULL) [as=col6s:28, outer=(27), immutable]

# A distinct-on which satisfies the required ordering should be used.
opt expect=GenerateStreamingGroupByLimitOrderingHint
SELECT
  DISTINCT ON (t1.col5, t1.col9) t2.col2
FROM
  t93410 AS t1
  LEFT OUTER JOIN t93410_2
      AS t2 USING (col1, col2, col3, col4, col5)
WHERE
  t1.col2 = 1
  AND t1.col1 = 1
ORDER BY
  col9 ASC, t1.col5 DESC
LIMIT 20
----
limit
 ├── columns: col2:20  [hidden: t1.col5:5!null col9:9!null]
 ├── internal-ordering: +9,-5
 ├── cardinality: [0 - 20]
 ├── key: (5)
 ├── fd: (5)-->(9,20)
 ├── ordering: +9,-5
 ├── distinct-on
 │    ├── columns: t1.col5:5!null col9:9!null t2.col2:20
 │    ├── grouping columns: t1.col5:5!null
 │    ├── key: (5)
 │    ├── fd: (5)-->(9,20)
 │    ├── ordering: +9,-5
 │    ├── limit hint: 20.00
 │    ├── left-join (lookup t93410_2@t93410_2_col1_col2_col3_col4_col5_idx [as=t2])
 │    │    ├── columns: t1.col1:1!null t1.col2:2!null t1.col3:3!null t1.col4:4!null t1.col5:5!null col9:9!null t2.col1:19 t2.col2:20 t2.col3:21 t2.col4:22 t2.col5:23
 │    │    ├── key columns: [1 2 3 4 5] = [19 20 21 22 23]
 │    │    ├── fd: ()-->(1,2), (5)-->(3,4,9)
 │    │    ├── ordering: +9,-5 opt(1,2) [actual: +9,-5]
 │    │    ├── limit hint: 24.01
 │    │    ├── sort
 │    │    │    ├── columns: t1.col1:1!null t1.col2:2!null t1.col3:3!null t1.col4:4!null t1.col5:5!null col9:9!null
 │    │    │    ├── key: (5)
 │    │    │    ├── fd: ()-->(1,2), (5)-->(3,4,9)
 │    │    │    ├── ordering: +9,-5 opt(1,2) [actual: +9,-5]
 │    │    │    ├── limit hint: 100.00
 │    │    │    └── scan t93410@t93410_col1_col2_col5_col9_key [as=t1]
 │    │    │         ├── columns: t1.col1:1!null t1.col2:2!null t1.col3:3!null t1.col4:4!null t1.col5:5!null col9:9!null
 │    │    │         ├── constraint: /1/2/5/9: [/1/1 - /1/1]
 │    │    │         ├── key: (5)
 │    │    │         └── fd: ()-->(1,2), (5)-->(3,4,9)
 │    │    └── filters
 │    │         ├── t2.col2:20 = 1 [outer=(20), constraints=(/20: [/1 - /1]; tight), fd=()-->(20)]
 │    │         └── t2.col1:19 = 1 [outer=(19), constraints=(/19: [/1 - /1]; tight), fd=()-->(19)]
 │    └── aggregations
 │         ├── first-agg [as=t2.col2:20, outer=(20)]
 │         │    └── t2.col2:20
 │         └── const-agg [as=col9:9, outer=(9)]
 │              └── col9:9
 └── 20

# This should generate a partial streaming aggregation.
opt expect=GenerateStreamingGroupByLimitOrderingHint
SELECT
  count(*)
FROM
  t93410 AS t1
  LEFT OUTER JOIN t93410_2
      AS t2 USING (col1, col2, col3, col4)
WHERE
  t1.col2 = 1
GROUP BY
  t1.col2,
  t1.col3,
  t1.col4
ORDER BY
  t1.col4 DESC
LIMIT 20
----
project
 ├── columns: count:27!null  [hidden: t1.col4:4!null]
 ├── cardinality: [0 - 20]
 ├── ordering: -4
 └── limit
      ├── columns: t1.col3:3!null t1.col4:4!null count_rows:27!null
      ├── internal-ordering: -4
      ├── cardinality: [0 - 20]
      ├── key: (3,4)
      ├── fd: (3,4)-->(27)
      ├── ordering: -4
      ├── group-by (partial streaming)
      │    ├── columns: t1.col3:3!null t1.col4:4!null count_rows:27!null
      │    ├── grouping columns: t1.col3:3!null t1.col4:4!null
      │    ├── key: (3,4)
      │    ├── fd: (3,4)-->(27)
      │    ├── ordering: -4
      │    ├── limit hint: 20.00
      │    ├── left-join (lookup t93410_2@t93410_2_col1_col2_col3_col4_col5_idx [as=t2])
      │    │    ├── columns: t1.col1:1!null t1.col2:2!null t1.col3:3!null t1.col4:4!null t2.col1:19 t2.col2:20 t2.col3:21 t2.col4:22
      │    │    ├── key columns: [1 2 3 4] = [19 20 21 22]
      │    │    ├── fd: ()-->(2)
      │    │    ├── ordering: -4 opt(2) [actual: -4]
      │    │    ├── limit hint: 20.00
      │    │    ├── sort
      │    │    │    ├── columns: t1.col1:1!null t1.col2:2!null t1.col3:3!null t1.col4:4!null
      │    │    │    ├── fd: ()-->(2)
      │    │    │    ├── ordering: -4 opt(2) [actual: -4]
      │    │    │    ├── limit hint: 100.00
      │    │    │    └── select
      │    │    │         ├── columns: t1.col1:1!null t1.col2:2!null t1.col3:3!null t1.col4:4!null
      │    │    │         ├── fd: ()-->(2)
      │    │    │         ├── scan t93410@t93410_col1_col2_col3_col5_key [as=t1]
      │    │    │         │    └── columns: t1.col1:1!null t1.col2:2!null t1.col3:3!null t1.col4:4!null
      │    │    │         └── filters
      │    │    │              └── t1.col2:2 = 1 [outer=(2), constraints=(/2: [/1 - /1]; tight), fd=()-->(2)]
      │    │    └── filters
      │    │         └── t2.col2:20 = 1 [outer=(20), constraints=(/20: [/1 - /1]; tight), fd=()-->(20)]
      │    └── aggregations
      │         └── count-rows [as=count_rows:27]
      └── 20

# A query which normally would produce a streaming group-by with no TopK
# operation produces a TopK if the GenerateStreamingGroupByLimitOrderingHint
# rule is disabled.
opt expect-not=GenerateStreamingGroupByLimitOrderingHint set=optimizer_use_limit_ordering_for_streaming_group_by=false
SELECT
  DISTINCT
  t1.col1,
  t1.col2,
  t1.col3,
  t1.col4,
  t1.col5,
  t1.col6,
  t1.col7,
  t1.col8,
  t1.col9,
  t1.col10,
  t1.col11,
  t1.col12,
  t1.col13,
  t1.col14,
  t1.col15,
  t1.col16,
  array_remove(array_agg(t2.col6), NULL) AS col6s
FROM
  t93410 AS t1
  LEFT OUTER JOIN t93410_2
      AS t2 USING (col1, col2, col3, col4, col5)
WHERE
  t1.col2 = 1
  AND t1.col1 = 1
GROUP BY
  t1.col1,
  t1.col2,
  t1.col3,
  t1.col4,
  t1.col5
ORDER BY
  col9 DESC, t1.col5 DESC
LIMIT 20
----
project
 ├── columns: col1:1!null col2:2!null col3:3!null col4:4!null col5:5!null col6:6!null col7:7!null col8:8!null col9:9!null col10:10!null col11:11!null col12:12 col13:13 col14:14!null col15:15 col16:16 col6s:28
 ├── cardinality: [0 - 20]
 ├── immutable
 ├── key: (5)
 ├── fd: ()-->(1,2), (5)-->(3,4,6-16,28)
 ├── ordering: -9,-5 opt(1,2) [actual: -9,-5]
 ├── top-k
 │    ├── columns: t1.col1:1!null t1.col2:2!null t1.col3:3!null t1.col4:4!null t1.col5:5!null t1.col6:6!null col7:7!null col8:8!null col9:9!null col10:10!null col11:11!null col12:12 col13:13 col14:14!null col15:15 col16:16 array_agg:27
 │    ├── internal-ordering: -9,-5 opt(1,2)
 │    ├── k: 20
 │    ├── cardinality: [0 - 20]
 │    ├── key: (5)
 │    ├── fd: ()-->(1,2), (5)-->(1-4,6-16,27)
 │    ├── ordering: -9,-5 opt(1,2) [actual: -9,-5]
 │    └── group-by (hash)
 │         ├── columns: t1.col1:1!null t1.col2:2!null t1.col3:3!null t1.col4:4!null t1.col5:5!null t1.col6:6!null col7:7!null col8:8!null col9:9!null col10:10!null col11:11!null col12:12 col13:13 col14:14!null col15:15 col16:16 array_agg:27
 │         ├── grouping columns: t1.col5:5!null
 │         ├── key: (5)
 │         ├── fd: ()-->(1,2), (5)-->(1-4,6-16,27)
 │         ├── left-join (merge)
 │         │    ├── columns: t1.col1:1!null t1.col2:2!null t1.col3:3!null t1.col4:4!null t1.col5:5!null t1.col6:6!null col7:7!null col8:8!null col9:9!null col10:10!null col11:11!null col12:12 col13:13 col14:14!null col15:15 col16:16 t2.col1:19 t2.col2:20 t2.col3:21 t2.col4:22 t2.col5:23 t2.col6:24
 │         │    ├── left ordering: +3,+4,+5,+1,+2
 │         │    ├── right ordering: +21,+22,+23,+19,+20
 │         │    ├── key: (5,21,23,24)
 │         │    ├── fd: ()-->(1,2), (5)-->(3,4,6-16), (21,23,24)-->(22), (5,21,23,24)-->(19,20)
 │         │    ├── scan t93410 [as=t1]
 │         │    │    ├── columns: t1.col1:1!null t1.col2:2!null t1.col3:3!null t1.col4:4!null t1.col5:5!null t1.col6:6!null col7:7!null col8:8!null col9:9!null col10:10!null col11:11!null col12:12 col13:13 col14:14!null col15:15 col16:16
 │         │    │    ├── constraint: /1/2/3/4/5: [/1/1 - /1/1]
 │         │    │    ├── key: (5)
 │         │    │    ├── fd: ()-->(1,2), (5)-->(3,4,6-16)
 │         │    │    └── ordering: +3,+4,+5 opt(1,2) [actual: +3,+4,+5]
 │         │    ├── scan t93410_2@t93410_2_col1_col2_col3_col4_col5_idx [as=t2]
 │         │    │    ├── columns: t2.col1:19!null t2.col2:20!null t2.col3:21!null t2.col4:22!null t2.col5:23!null t2.col6:24!null
 │         │    │    ├── constraint: /19/20/21/22/23/24: [/1/1 - /1/1]
 │         │    │    ├── key: (21,23,24)
 │         │    │    ├── fd: ()-->(19,20), (21,23,24)-->(22)
 │         │    │    └── ordering: +21,+22,+23 opt(19,20) [actual: +21,+22,+23]
 │         │    └── filters (true)
 │         └── aggregations
 │              ├── array-agg [as=array_agg:27, outer=(24)]
 │              │    └── t2.col6:24
 │              ├── const-agg [as=t1.col1:1, outer=(1)]
 │              │    └── t1.col1:1
 │              ├── const-agg [as=t1.col2:2, outer=(2)]
 │              │    └── t1.col2:2
 │              ├── const-agg [as=t1.col3:3, outer=(3)]
 │              │    └── t1.col3:3
 │              ├── const-agg [as=t1.col4:4, outer=(4)]
 │              │    └── t1.col4:4
 │              ├── const-agg [as=t1.col6:6, outer=(6)]
 │              │    └── t1.col6:6
 │              ├── const-agg [as=col7:7, outer=(7)]
 │              │    └── col7:7
 │              ├── const-agg [as=col8:8, outer=(8)]
 │              │    └── col8:8
 │              ├── const-agg [as=col9:9, outer=(9)]
 │              │    └── col9:9
 │              ├── const-agg [as=col10:10, outer=(10)]
 │              │    └── col10:10
 │              ├── const-agg [as=col11:11, outer=(11)]
 │              │    └── col11:11
 │              ├── const-agg [as=col12:12, outer=(12)]
 │              │    └── col12:12
 │              ├── const-agg [as=col13:13, outer=(13)]
 │              │    └── col13:13
 │              ├── const-agg [as=col14:14, outer=(14)]
 │              │    └── col14:14
 │              ├── const-agg [as=col15:15, outer=(15)]
 │              │    └── col15:15
 │              └── const-agg [as=col16:16, outer=(16)]
 │                   └── col16:16
 └── projections
      └── array_remove(array_agg:27, NULL) [as=col6s:28, outer=(27), immutable]

# End Regression Test for #93410
