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 DESC) STORING (j),
    INVERTED INDEX inv_idx_j (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 desc
 │    ├── k int not null
 │    └── j jsonb (storing)
 └── INVERTED INDEX inv_idx_j
      ├── j jsonb
      └── k int not null

# --------------------------------------------------
# GenerateIndexScans
# --------------------------------------------------

# Revscan won't be used here because there is no index with f
# sorted by ASC, k DESC
opt
SELECT k,f FROM a ORDER BY f DESC, k ASC LIMIT 10
----
limit
 ├── columns: k:1(int!null) f:3(float)
 ├── internal-ordering: -3,+1
 ├── cardinality: [0 - 10]
 ├── key: (1)
 ├── fd: (1)-->(3)
 ├── ordering: -3,+1
 ├── sort
 │    ├── columns: k:1(int!null) f:3(float)
 │    ├── key: (1)
 │    ├── fd: (1)-->(3)
 │    ├── ordering: -3,+1
 │    └── scan a@s_idx
 │         ├── columns: k:1(int!null) f:3(float)
 │         ├── key: (1)
 │         └── fd: (1)-->(3)
 └── const: 10 [type=int]

opt
SELECT k,f from a ORDER BY k DESC LIMIT 10
----
scan a,rev
 ├── columns: k:1(int!null) f:3(float)
 ├── limit: 10(rev)
 ├── key: (1)
 ├── fd: (1)-->(3)
 └── ordering: -1

memo
SELECT k,f FROM a ORDER BY k DESC LIMIT 10
----
memo (optimized, ~3KB, required=[presentation: k:1,f:3] [ordering: -1])
 ├── G1: (limit G2 G3 ordering=-1) (scan a,rev,cols=(1,3),lim=10(rev))
 │    ├── [presentation: k:1,f:3] [ordering: -1]
 │    │    ├── best: (scan a,rev,cols=(1,3),lim=10(rev))
 │    │    └── cost: 11.05
 │    └── []
 │         ├── best: (scan a,rev,cols=(1,3),lim=10(rev))
 │         └── cost: 11.05
 ├── G2: (scan a,cols=(1,3)) (scan a@s_idx,cols=(1,3))
 │    ├── [ordering: -1]
 │    │    ├── best: (scan a,rev,cols=(1,3))
 │    │    └── cost: 1169.68
 │    └── []
 │         ├── best: (scan a@s_idx,cols=(1,3))
 │         └── cost: 1060.02
 └── G3: (const 10)


opt
SELECT s FROM a ORDER BY k DESC
----
scan a,rev
 ├── columns: s:4(string)  [hidden: k:1(int!null)]
 ├── key: (1)
 ├── fd: (1)-->(4)
 └── ordering: -1

opt
SELECT k FROM a ORDER BY k ASC
----
scan a
 ├── columns: k:1(int!null)
 ├── key: (1)
 └── ordering: +1

opt
SELECT k FROM a ORDER BY k DESC
----
scan a,rev
 ├── columns: k:1(int!null)
 ├── key: (1)
 └── ordering: -1

opt
SELECT s,i,k,j FROM a ORDER BY s DESC, i DESC, k ASC
----
scan a@si_idx
 ├── columns: s:4(string) i:2(int) k:1(int!null) j:5(jsonb)
 ├── key: (1)
 ├── fd: (1)-->(2,4,5)
 └── ordering: -4,-2,+1

# Revscan node won't be used because ordering is
# only partial (reverse) match with existing indices
opt
SELECT s,i,k,j FROM a ORDER BY s DESC, i DESC, k DESC
----
sort
 ├── columns: s:4(string) i:2(int) k:1(int!null) j:5(jsonb)
 ├── key: (1)
 ├── fd: (1)-->(2,4,5)
 ├── ordering: -4,-2,-1
 └── scan a@si_idx
      ├── columns: k:1(int!null) i:2(int) s:4(string) j:5(jsonb)
      ├── key: (1)
      └── fd: (1)-->(2,4,5)

# Revscan node won't be used because ordering is
# only partial (reverse) match with existing indices
opt
SELECT s,i,k,j FROM a ORDER BY s DESC, i ASC, k DESC
----
sort
 ├── columns: s:4(string) i:2(int) k:1(int!null) j:5(jsonb)
 ├── key: (1)
 ├── fd: (1)-->(2,4,5)
 ├── ordering: -4,+2,-1
 └── scan a@si_idx
      ├── columns: k:1(int!null) i:2(int) s:4(string) j:5(jsonb)
      ├── key: (1)
      └── fd: (1)-->(2,4,5)

opt
SELECT s,i,k,j FROM a ORDER BY s ASC, i ASC, k DESC
----
scan a@si_idx,rev
 ├── columns: s:4(string) i:2(int) k:1(int!null) j:5(jsonb)
 ├── key: (1)
 ├── fd: (1)-->(2,4,5)
 └── ordering: +4,+2,-1

memo
SELECT k FROM a ORDER BY k ASC
----
memo (optimized, ~2KB, required=[presentation: k:1] [ordering: +1])
 └── G1: (scan a,cols=(1)) (scan a@s_idx,cols=(1)) (scan a@si_idx,cols=(1))
      ├── [presentation: k:1] [ordering: +1]
      │    ├── best: (scan a,cols=(1))
      │    └── cost: 1060.02
      └── []
           ├── best: (scan a@s_idx,cols=(1))
           └── cost: 1050.02

# Scan of secondary index is lowest cost.
opt
SELECT s, i, f FROM a ORDER BY s, k, i
----
scan a@s_idx
 ├── columns: s:4(string) i:2(int) f:3(float)  [hidden: k:1(int!null)]
 ├── key: (1)
 ├── fd: (1)-->(2-4)
 └── ordering: +4,+1

memo
SELECT s, i, f FROM a ORDER BY s, k, i
----
memo (optimized, ~2KB, required=[presentation: s:4,i:2,f:3] [ordering: +4,+1])
 └── G1: (scan a,cols=(1-4)) (scan a@s_idx,cols=(1-4))
      ├── [presentation: s:4,i:2,f:3] [ordering: +4,+1]
      │    ├── best: (scan a@s_idx,cols=(1-4))
      │    └── cost: 1080.02
      └── []
           ├── best: (scan a@s_idx,cols=(1-4))
           └── cost: 1080.02

# No index-join should be generated for a@si_idx, since it is not constrained.
exploretrace rule=GenerateIndexScans
SELECT s, i, f FROM a ORDER BY s, k, i
----
----
================================================================================
GenerateIndexScans
================================================================================
Source expression:
  sort
   ├── columns: s:4(string) i:2(int) f:3(float)  [hidden: k:1(int!null)]
   ├── key: (1)
   ├── fd: (1)-->(2-4)
   ├── ordering: +4,+1
   └── scan a
        ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string)
        ├── key: (1)
        └── fd: (1)-->(2-4)

New expression 1 of 1:
  scan a@s_idx
   ├── columns: s:4(string) i:2(int) f:3(float)  [hidden: k:1(int!null)]
   ├── key: (1)
   ├── fd: (1)-->(2-4)
   └── ordering: +4,+1
----
----

# --------------------------------------------------
# GenerateConstrainedScans
# --------------------------------------------------

# Constrain the a@si_idx so that an index join is generated.
exploretrace rule=GenerateConstrainedScans
SELECT s, i, f FROM a WHERE s='foo' ORDER BY s, k, i
----
----
================================================================================
GenerateConstrainedScans
================================================================================
Source expression:
  select
   ├── columns: s:4(string!null) i:2(int) f:3(float)  [hidden: k:1(int!null)]
   ├── key: (1)
   ├── fd: ()-->(4), (1)-->(2,3)
   ├── ordering: +1 opt(4) [actual: +1]
   ├── scan a@s_idx
   │    ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string)
   │    ├── key: (1)
   │    ├── fd: (1)-->(2-4)
   │    └── ordering: +1 opt(4) [actual: +4,+1]
   └── filters
        └── s = 'foo' [type=bool, outer=(4), constraints=(/4: [/'foo' - /'foo']; tight), fd=()-->(4)]

New expression 1 of 2:
  scan a@s_idx
   ├── columns: s:4(string!null) i:2(int) f:3(float)  [hidden: k:1(int!null)]
   ├── constraint: /4/1: [/'foo' - /'foo']
   ├── key: (1)
   ├── fd: ()-->(4), (1)-->(2,3)
   └── ordering: +1 opt(4) [actual: +1]

New expression 2 of 2:
  sort
   ├── columns: s:4(string!null) i:2(int) f:3(float)  [hidden: k:1(int!null)]
   ├── key: (1)
   ├── fd: ()-->(4), (1)-->(2,3)
   ├── ordering: +1 opt(4) [actual: +1]
   └── index-join a
        ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string!null)
        ├── key: (1)
        ├── fd: ()-->(4), (1)-->(2,3)
        └── scan a@si_idx
             ├── columns: k:1(int!null) i:2(int) s:4(string!null)
             ├── constraint: /-4/-2/1: [/'foo' - /'foo']
             ├── key: (1)
             └── fd: ()-->(4), (1)-->(2)
----
----

memo
SELECT s, i, f FROM a ORDER BY f
----
memo (optimized, ~2KB, required=[presentation: s:4,i:2,f:3] [ordering: +3])
 └── G1: (scan a,cols=(2-4)) (scan a@s_idx,cols=(2-4))
      ├── [presentation: s:4,i:2,f:3] [ordering: +3]
      │    ├── best: (sort G1)
      │    └── cost: 1289.35
      └── []
           ├── best: (scan a@s_idx,cols=(2-4))
           └── cost: 1070.02

memo
SELECT s, i, f FROM a ORDER BY s DESC, i
----
memo (optimized, ~2KB, required=[presentation: s:4,i:2,f:3] [ordering: -4,+2])
 └── G1: (scan a,cols=(2-4)) (scan a@s_idx,cols=(2-4))
      ├── [presentation: s:4,i:2,f:3] [ordering: -4,+2]
      │    ├── best: (sort G1)
      │    └── cost: 1300.31
      └── []
           ├── best: (scan a@s_idx,cols=(2-4))
           └── cost: 1070.02

memo
SELECT s, i, f FROM a WHERE s='foo' ORDER BY s DESC, i
----
memo (optimized, ~5KB, required=[presentation: s:4,i:2,f:3] [ordering: +2 opt(4)])
 ├── G1: (select G2 G3) (scan a@s_idx,cols=(2-4),constrained) (index-join G4 a,cols=(2-4))
 │    ├── [presentation: s:4,i:2,f:3] [ordering: +2 opt(4)]
 │    │    ├── best: (sort G1)
 │    │    └── cost: 11.47
 │    └── []
 │         ├── best: (scan a@s_idx,cols=(2-4),constrained)
 │         └── cost: 10.60
 ├── G2: (scan a,cols=(2-4)) (scan a@s_idx,cols=(2-4))
 │    ├── [ordering: +2 opt(4)]
 │    │    ├── best: (sort G2)
 │    │    └── cost: 1289.35
 │    └── []
 │         ├── best: (scan a@s_idx,cols=(2-4))
 │         └── cost: 1070.02
 ├── G3: (filters G5)
 ├── G4: (scan a@si_idx,cols=(1,2,4),constrained)
 │    ├── [ordering: +2 opt(4)]
 │    │    ├── best: (scan a@si_idx,rev,cols=(1,2,4),constrained)
 │    │    └── cost: 10.93
 │    └── []
 │         ├── best: (scan a@si_idx,cols=(1,2,4),constrained)
 │         └── cost: 10.60
 ├── G5: (eq G6 G7)
 ├── G6: (variable s)
 └── G7: (const 'foo')

# Force an index in order to ensure that an index join is created.
opt
SELECT * FROM a@si_idx
----
index-join a
 ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb)
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 └── scan a@si_idx
      ├── columns: k:1(int!null) i:2(int) s:4(string) j:5(jsonb)
      ├── flags: force-index=si_idx
      ├── key: (1)
      └── fd: (1)-->(2,4,5)

exec-ddl
CREATE TABLE abc (
  a INT,
  b INT,
  c INT,
  d CHAR,
  PRIMARY KEY (a, b, c),
  UNIQUE INDEX bc (b, c),
  INDEX ba (b, a),
  FAMILY (a, b, c),
  FAMILY (d)
)
----
TABLE abc
 ├── a int not null
 ├── b int not null
 ├── c int not null
 ├── d string
 ├── INDEX primary
 │    ├── a int not null
 │    ├── b int not null
 │    └── c int not null
 ├── INDEX bc
 │    ├── b int not null
 │    ├── c int not null
 │    └── a int not null (storing)
 ├── INDEX ba
 │    ├── b int not null
 │    ├── a int not null
 │    └── c int not null
 ├── FAMILY family1 (a, b, c)
 └── FAMILY family2 (d)

memo
SELECT d FROM abc ORDER BY lower(d)
----
memo (optimized, ~3KB, required=[presentation: d:4] [ordering: +5])
 ├── G1: (project G2 G3 d)
 │    ├── [presentation: d:4] [ordering: +5]
 │    │    ├── best: (sort G1)
 │    │    └── cost: 1289.36
 │    └── []
 │         ├── best: (project G2 G3 d)
 │         └── cost: 1070.03
 ├── G2: (scan abc,cols=(4))
 │    └── []
 │         ├── best: (scan abc,cols=(4))
 │         └── cost: 1050.02
 ├── G3: (projections G4)
 ├── G4: (function G5 lower)
 ├── G5: (scalar-list G6)
 └── G6: (variable d)

memo
SELECT j FROM a WHERE s = 'foo'
----
memo (optimized, ~6KB, required=[presentation: j:5])
 ├── G1: (project G2 G3 j)
 │    └── [presentation: j:5]
 │         ├── best: (project G2 G3 j)
 │         └── cost: 10.61
 ├── G2: (select G4 G5) (index-join G6 a,cols=(4,5)) (scan a@si_idx,cols=(4,5),constrained)
 │    └── []
 │         ├── best: (scan a@si_idx,cols=(4,5),constrained)
 │         └── cost: 10.50
 ├── G3: (projections)
 ├── G4: (scan a,cols=(4,5)) (scan a@si_idx,cols=(4,5))
 │    └── []
 │         ├── best: (scan a@si_idx,cols=(4,5))
 │         └── cost: 1060.02
 ├── G5: (filters G7)
 ├── G6: (scan a@s_idx,cols=(1,4),constrained)
 │    └── []
 │         ├── best: (scan a@s_idx,cols=(1,4),constrained)
 │         └── cost: 10.50
 ├── G7: (eq G8 G9)
 ├── G8: (variable s)
 └── G9: (const 'foo')

# Scan of primary index is lowest cost.
opt
SELECT s, i, f FROM a ORDER BY k, i, s
----
scan a
 ├── columns: s:4(string) i:2(int) f:3(float)  [hidden: k:1(int!null)]
 ├── key: (1)
 ├── fd: (1)-->(2-4)
 └── ordering: +1

memo
SELECT s, i, f FROM a ORDER BY k, i, s
----
memo (optimized, ~2KB, required=[presentation: s:4,i:2,f:3] [ordering: +1])
 └── G1: (scan a,cols=(1-4)) (scan a@s_idx,cols=(1-4))
      ├── [presentation: s:4,i:2,f:3] [ordering: +1]
      │    ├── best: (scan a,cols=(1-4))
      │    └── cost: 1090.02
      └── []
           ├── best: (scan a@s_idx,cols=(1-4))
           └── cost: 1080.02

# Secondary index has right order
opt
SELECT s, j FROM a ORDER BY s
----
scan a@si_idx,rev
 ├── columns: s:4(string) j:5(jsonb)
 └── ordering: +4

memo
SELECT s, j FROM a ORDER BY s
----
memo (optimized, ~2KB, required=[presentation: s:4,j:5] [ordering: +4])
 └── G1: (scan a,cols=(4,5)) (scan a@si_idx,cols=(4,5))
      ├── [presentation: s:4,j:5] [ordering: +4]
      │    ├── best: (scan a@si_idx,rev,cols=(4,5))
      │    └── cost: 1159.68
      └── []
           ├── best: (scan a@si_idx,cols=(4,5))
           └── cost: 1060.02

# Consider three different indexes, and pick index with multiple keys.
opt
SELECT i, k FROM a ORDER BY s DESC, i, k
----
sort
 ├── columns: i:2(int) k:1(int!null)  [hidden: s:4(string)]
 ├── key: (1)
 ├── fd: (1)-->(2,4)
 ├── ordering: -4,+2,+1
 └── scan a@s_idx
      ├── columns: k:1(int!null) i:2(int) s:4(string)
      ├── key: (1)
      └── fd: (1)-->(2,4)

memo
SELECT i, k FROM a ORDER BY s DESC, i, k
----
memo (optimized, ~2KB, required=[presentation: i:2,k:1] [ordering: -4,+2,+1])
 └── G1: (scan a,cols=(1,2,4)) (scan a@s_idx,cols=(1,2,4)) (scan a@si_idx,cols=(1,2,4))
      ├── [presentation: i:2,k:1] [ordering: -4,+2,+1]
      │    ├── best: (sort G1)
      │    └── cost: 1301.41
      └── []
           ├── best: (scan a@s_idx,cols=(1,2,4))
           └── cost: 1070.02

memo
SELECT i, k FROM a WHERE s >= 'foo'
----
memo (optimized, ~5KB, required=[presentation: i:2,k:1])
 ├── G1: (project G2 G3 k i)
 │    └── [presentation: i:2,k:1]
 │         ├── best: (project G2 G3 k i)
 │         └── cost: 356.42
 ├── G2: (select G4 G5) (scan a@s_idx,cols=(1,2,4),constrained) (scan a@si_idx,cols=(1,2,4),constrained)
 │    └── []
 │         ├── best: (scan a@s_idx,cols=(1,2,4),constrained)
 │         └── cost: 353.11
 ├── G3: (projections)
 ├── G4: (scan a,cols=(1,2,4)) (scan a@s_idx,cols=(1,2,4)) (scan a@si_idx,cols=(1,2,4))
 │    └── []
 │         ├── best: (scan a@s_idx,cols=(1,2,4))
 │         └── cost: 1070.02
 ├── G5: (filters G6)
 ├── G6: (ge G7 G8)
 ├── G7: (variable s)
 └── G8: (const 'foo')

# Collated strings are treated properly.
exec-ddl
CREATE TABLE x (s STRING COLLATE en_u_ks_level1 PRIMARY KEY)
----
TABLE x
 ├── s collatedstring{en_u_ks_level1} not null
 └── INDEX primary
      └── s collatedstring{en_u_ks_level1} not null

opt
SELECT s FROM x WHERE s < 'hello' COLLATE en_u_ks_level1
----
scan x
 ├── columns: s:1(collatedstring{en_u_ks_level1}!null)
 ├── constraint: /1: [ - /'hello' COLLATE en_u_ks_level1)
 └── key: (1)

opt
SELECT s FROM x WHERE s = 'hello' COLLATE en_u_ks_level1
----
scan x
 ├── columns: s:1(collatedstring{en_u_ks_level1}!null)
 ├── constraint: /1: [/'hello' COLLATE en_u_ks_level1 - /'hello' COLLATE en_u_ks_level1]
 ├── cardinality: [0 - 1]
 ├── key: ()
 └── fd: ()-->(1)

# Can't generate spans for other collations.
opt
SELECT s FROM x WHERE s COLLATE en = 'hello' COLLATE en
----
select
 ├── columns: s:1(collatedstring{en_u_ks_level1}!null)
 ├── key: (1)
 ├── scan x
 │    ├── columns: s:1(collatedstring{en_u_ks_level1}!null)
 │    └── key: (1)
 └── filters
      └── s COLLATE en = 'hello' COLLATE en [type=bool, outer=(1)]
