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)
)
----
TABLE a
 ├── k int not null
 ├── i int
 ├── f float
 ├── s string
 ├── j jsonb
 ├── INDEX primary
 │    └── k int not null
 ├── INDEX s_idx
 │    ├── s string
 │    ├── k int not null
 │    ├── i int (storing)
 │    └── f float (storing)
 └── INDEX si_idx
      ├── s string desc
      ├── i int
      ├── k int not null
      └── j jsonb (storing)

# --------------------------------------------------
# PushLimitIntoScan
# --------------------------------------------------

opt
SELECT * FROM a LIMIT 1
----
scan a
 ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb)
 ├── 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(string)
 ├── 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(string!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
----
limit
 ├── columns: s:4(string)  [hidden: i:2(int)]
 ├── internal-ordering: +4,+2
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(2,4)
 ├── sort
 │    ├── columns: i:2(int) s:4(string)
 │    ├── cardinality: [0 - 10]
 │    ├── ordering: +4,+2
 │    └── scan a@s_idx
 │         ├── columns: i:2(int) s:4(string)
 │         └── limit: 10
 └── const: 1 [type=int]

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

# 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(string)
 ├── side-effects
 ├── scan a@s_idx
 │    └── columns: s:4(string)
 └── subquery [type=int]
      └── scan a@s_idx
           ├── columns: k:6(int!null)
           ├── limit: 1
           ├── key: ()
           └── fd: ()-->(6)

memo
SELECT s FROM a WHERE s='foo' LIMIT 1
----
memo (optimized, ~6KB, 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: 1.06
 ├── G2: (select G4 G5) (scan a@s_idx,cols=(4),constrained) (scan a@si_idx,cols=(4),constrained)
 │    └── []
 │         ├── best: (scan a@s_idx,cols=(4),constrained)
 │         └── cost: 10.41
 ├── G3: (const 1)
 ├── G4: (scan a,cols=(4)) (scan a@s_idx,cols=(4)) (scan a@si_idx,cols=(4))
 │    └── []
 │         ├── best: (scan a@s_idx,cols=(4))
 │         └── cost: 1050.02
 ├── G5: (filters G6)
 ├── G6: (eq G7 G8)
 ├── G7: (variable s)
 └── G8: (const 'foo')

# --------------------------------------------------
# PushLimitIntoLookupJoin
# --------------------------------------------------

exec-ddl
CREATE TABLE kuv (k INT PRIMARY KEY, u INT, v INT, INDEX (u))
----
TABLE kuv
 ├── k int not null
 ├── u int
 ├── v int
 ├── INDEX primary
 │    └── k int not null
 └── INDEX secondary
      ├── u int
      └── k int not null

opt
SELECT * FROM kuv ORDER BY u LIMIT 5
----
index-join kuv
 ├── columns: k:1(int!null) u:2(int) v:3(int)
 ├── cardinality: [0 - 5]
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── ordering: +2
 └── scan kuv@secondary
      ├── columns: k:1(int!null) u:2(int)
      ├── limit: 5
      ├── key: (1)
      ├── fd: (1)-->(2)
      └── ordering: +2

# Verify we don't push the limit if the ordering depends on a column not in the
# input index.
opt
SELECT * FROM kuv WHERE u > 1 AND u < 10 ORDER BY u, v LIMIT 5
----
limit
 ├── columns: k:1(int!null) u:2(int!null) v:3(int)
 ├── internal-ordering: +2,+3
 ├── cardinality: [0 - 5]
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── ordering: +2,+3
 ├── sort
 │    ├── columns: k:1(int!null) u:2(int!null) v:3(int)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── ordering: +2,+3
 │    └── index-join kuv
 │         ├── columns: k:1(int!null) u:2(int!null) v:3(int)
 │         ├── key: (1)
 │         ├── fd: (1)-->(2,3)
 │         └── scan kuv@secondary
 │              ├── columns: k:1(int!null) u:2(int!null)
 │              ├── constraint: /2/1: [/2 - /9]
 │              ├── key: (1)
 │              └── fd: (1)-->(2)
 └── const: 5 [type=int]

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)
)
----
TABLE abcd
 ├── a int not null
 ├── b int
 ├── c int
 ├── d int
 ├── INDEX primary
 │    └── a int not null
 ├── INDEX b
 │    ├── b int
 │    └── a int not null
 ├── INDEX cd
 │    ├── c int
 │    ├── d int
 │    └── a int not null
 └── INDEX bcd
      ├── b int
      ├── c int
      ├── d int
      └── a int not null (storing)

opt
EXPLAIN SELECT * FROM abcd@b WHERE a >= 20 AND a <= 30 ORDER BY b DESC LIMIT 5
----
explain
 ├── columns: tree:5(string) field:6(string) description:7(string)
 └── limit
      ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
      ├── internal-ordering: -2
      ├── cardinality: [0 - 5]
      ├── key: (1)
      ├── fd: (1)-->(2-4), (2-4)~~>(1)
      ├── ordering: -2
      ├── sort
      │    ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
      │    ├── key: (1)
      │    ├── fd: (1)-->(2-4), (2-4)~~>(1)
      │    ├── ordering: -2
      │    └── select
      │         ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
      │         ├── key: (1)
      │         ├── fd: (1)-->(2-4), (2-4)~~>(1)
      │         ├── index-join abcd
      │         │    ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
      │         │    ├── key: (1)
      │         │    ├── fd: (1)-->(2-4), (2-4)~~>(1)
      │         │    └── scan abcd@b
      │         │         ├── columns: a:1(int!null) b:2(int)
      │         │         ├── flags: force-index=b
      │         │         ├── key: (1)
      │         │         └── fd: (1)-->(2)
      │         └── filters
      │              └── (a >= 20) AND (a <= 30) [type=bool, outer=(1), constraints=(/1: [/20 - /30]; tight)]
      └── const: 5 [type=int]

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: tree:5(string) field:6(string) description:7(string)
   └── sort
        ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
        ├── cardinality: [0 - 5]
        ├── key: (1)
        ├── fd: (1)-->(2-4), (2-4)~~>(1)
        ├── ordering: -2
        └── limit
             ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
             ├── internal-ordering: -2
             ├── cardinality: [0 - 5]
             ├── key: (1)
             ├── fd: (1)-->(2-4), (2-4)~~>(1)
             ├── sort
             │    ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
             │    ├── key: (1)
             │    ├── fd: (1)-->(2-4), (2-4)~~>(1)
             │    ├── ordering: -2
             │    └── select
             │         ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
             │         ├── key: (1)
             │         ├── fd: (1)-->(2-4), (2-4)~~>(1)
             │         ├── scan abcd
             │         │    ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
             │         │    ├── flags: force-index=b
             │         │    ├── key: (1)
             │         │    └── fd: (1)-->(2-4), (2-4)~~>(1)
             │         └── filters
             │              └── (a >= 20) AND (a <= 30) [type=bool, outer=(1), constraints=(/1: [/20 - /30]; tight)]
             └── const: 5 [type=int]
================================================================================
SimplifySelectFilters
  Cost: 10000000000000000159028911097599180468360808563945281389781327557747838772170381060813469985856815104.00
================================================================================
   explain
    ├── columns: tree:5(string) field:6(string) description:7(string)
    └── sort
         ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
         ├── cardinality: [0 - 5]
         ├── key: (1)
         ├── fd: (1)-->(2-4), (2-4)~~>(1)
         ├── ordering: -2
         └── limit
              ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
              ├── internal-ordering: -2
              ├── cardinality: [0 - 5]
              ├── key: (1)
              ├── fd: (1)-->(2-4), (2-4)~~>(1)
              ├── sort
              │    ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
              │    ├── key: (1)
              │    ├── fd: (1)-->(2-4), (2-4)~~>(1)
              │    ├── ordering: -2
              │    └── select
              │         ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
              │         ├── key: (1)
              │         ├── fd: (1)-->(2-4), (2-4)~~>(1)
              │         ├── scan abcd
              │         │    ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
              │         │    ├── flags: force-index=b
              │         │    ├── key: (1)
              │         │    └── fd: (1)-->(2-4), (2-4)~~>(1)
              │         └── filters
  -           │              └── (a >= 20) AND (a <= 30) [type=bool, outer=(1), constraints=(/1: [/20 - /30]; tight)]
  +           │              ├── a >= 20 [type=bool, outer=(1), constraints=(/1: [/20 - ]; tight)]
  +           │              └── a <= 30 [type=bool, outer=(1), constraints=(/1: (/NULL - /30]; tight)]
              └── const: 5 [type=int]
================================================================================
ConsolidateSelectFilters
  Cost: 10000000000000000159028911097599180468360808563945281389781327557747838772170381060813469985856815104.00
================================================================================
   explain
    ├── columns: tree:5(string) field:6(string) description:7(string)
    └── sort
         ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
         ├── cardinality: [0 - 5]
         ├── key: (1)
         ├── fd: (1)-->(2-4), (2-4)~~>(1)
         ├── ordering: -2
         └── limit
              ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
              ├── internal-ordering: -2
              ├── cardinality: [0 - 5]
              ├── key: (1)
              ├── fd: (1)-->(2-4), (2-4)~~>(1)
              ├── sort
              │    ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
              │    ├── key: (1)
              │    ├── fd: (1)-->(2-4), (2-4)~~>(1)
              │    ├── ordering: -2
              │    └── select
              │         ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
              │         ├── key: (1)
              │         ├── fd: (1)-->(2-4), (2-4)~~>(1)
              │         ├── scan abcd
              │         │    ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
              │         │    ├── flags: force-index=b
              │         │    ├── key: (1)
              │         │    └── fd: (1)-->(2-4), (2-4)~~>(1)
              │         └── filters
  -           │              ├── a >= 20 [type=bool, outer=(1), constraints=(/1: [/20 - ]; tight)]
  -           │              └── a <= 30 [type=bool, outer=(1), constraints=(/1: (/NULL - /30]; tight)]
  +           │              └── (a >= 20) AND (a <= 30) [type=bool, outer=(1), constraints=(/1: [/20 - /30]; tight)]
              └── const: 5 [type=int]
================================================================================
GenerateIndexScans
  Cost: 5141.10
================================================================================
   explain
    ├── columns: tree:5(string) field:6(string) description:7(string)
  - └── sort
  + └── limit
         ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
  +      ├── internal-ordering: -2
         ├── cardinality: [0 - 5]
         ├── key: (1)
         ├── fd: (1)-->(2-4), (2-4)~~>(1)
         ├── ordering: -2
  -      └── limit
  -           ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
  -           ├── internal-ordering: -2
  -           ├── cardinality: [0 - 5]
  -           ├── key: (1)
  -           ├── fd: (1)-->(2-4), (2-4)~~>(1)
  -           ├── sort
  -           │    ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
  -           │    ├── key: (1)
  -           │    ├── fd: (1)-->(2-4), (2-4)~~>(1)
  -           │    ├── ordering: -2
  -           │    └── select
  -           │         ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
  -           │         ├── key: (1)
  -           │         ├── fd: (1)-->(2-4), (2-4)~~>(1)
  -           │         ├── scan abcd
  -           │         │    ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
  -           │         │    ├── flags: force-index=b
  -           │         │    ├── key: (1)
  -           │         │    └── fd: (1)-->(2-4), (2-4)~~>(1)
  -           │         └── filters
  -           │              └── (a >= 20) AND (a <= 30) [type=bool, outer=(1), constraints=(/1: [/20 - /30]; tight)]
  -           └── const: 5 [type=int]
  +      ├── sort
  +      │    ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
  +      │    ├── key: (1)
  +      │    ├── fd: (1)-->(2-4), (2-4)~~>(1)
  +      │    ├── ordering: -2
  +      │    └── select
  +      │         ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
  +      │         ├── key: (1)
  +      │         ├── fd: (1)-->(2-4), (2-4)~~>(1)
  +      │         ├── index-join abcd
  +      │         │    ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
  +      │         │    ├── key: (1)
  +      │         │    ├── fd: (1)-->(2-4), (2-4)~~>(1)
  +      │         │    └── scan abcd@b
  +      │         │         ├── columns: a:1(int!null) b:2(int)
  +      │         │         ├── flags: force-index=b
  +      │         │         ├── key: (1)
  +      │         │         └── fd: (1)-->(2)
  +      │         └── filters
  +      │              └── (a >= 20) AND (a <= 30) [type=bool, outer=(1), constraints=(/1: [/20 - /30]; tight)]
  +      └── const: 5 [type=int]
--------------------------------------------------------------------------------
GenerateZigzagJoins (no changes)
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
GenerateConstrainedScans (no changes)
--------------------------------------------------------------------------------
================================================================================
Final best expression
  Cost: 5141.10
================================================================================
  explain
   ├── columns: tree:5(string) field:6(string) description:7(string)
   └── limit
        ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
        ├── internal-ordering: -2
        ├── cardinality: [0 - 5]
        ├── key: (1)
        ├── fd: (1)-->(2-4), (2-4)~~>(1)
        ├── ordering: -2
        ├── sort
        │    ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
        │    ├── key: (1)
        │    ├── fd: (1)-->(2-4), (2-4)~~>(1)
        │    ├── ordering: -2
        │    └── select
        │         ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
        │         ├── key: (1)
        │         ├── fd: (1)-->(2-4), (2-4)~~>(1)
        │         ├── index-join abcd
        │         │    ├── columns: a:1(int!null) b:2(int) c:3(int) d:4(int)
        │         │    ├── key: (1)
        │         │    ├── fd: (1)-->(2-4), (2-4)~~>(1)
        │         │    └── scan abcd@b
        │         │         ├── columns: a:1(int!null) b:2(int)
        │         │         ├── flags: force-index=b
        │         │         ├── key: (1)
        │         │         └── fd: (1)-->(2)
        │         └── filters
        │              └── (a >= 20) AND (a <= 30) [type=bool, outer=(1), constraints=(/1: [/20 - /30]; tight)]
        └── const: 5 [type=int]
