exec-ddl
CREATE TABLE abc
(
    a INT,
    b INT,
    c INT,
    INDEX ab (a,b) STORING (c),
    INDEX bc (b,c) STORING (a)
)
----
TABLE abc
 ├── a int
 ├── b int
 ├── c int
 ├── rowid int not null (hidden)
 ├── INDEX primary
 │    └── rowid int not null (hidden)
 ├── INDEX ab
 │    ├── a int
 │    ├── b int
 │    ├── rowid int not null (hidden)
 │    └── c int (storing)
 └── INDEX bc
      ├── b int
      ├── c int
      ├── rowid int not null (hidden)
      └── a int (storing)

exec-ddl
CREATE TABLE stu
(
    s INT,
    t INT,
    u INT,
    PRIMARY KEY (s,t,u),
    INDEX uts (u,t,s)
)
----
TABLE stu
 ├── s int not null
 ├── t int not null
 ├── u int not null
 ├── INDEX primary
 │    ├── s int not null
 │    ├── t int not null
 │    └── u int not null
 └── INDEX uts
      ├── u int not null
      ├── t int not null
      └── s int not null

exec-ddl
CREATE TABLE xyz
(
    x INT,
    y INT,
    z INT,
    INDEX xy (x,y) STORING (z),
    INDEX yz (y,z) STORING (x)
)
----
TABLE xyz
 ├── x int
 ├── y int
 ├── z int
 ├── rowid int not null (hidden)
 ├── INDEX primary
 │    └── rowid int not null (hidden)
 ├── INDEX xy
 │    ├── x int
 │    ├── y int
 │    ├── rowid int not null (hidden)
 │    └── z int (storing)
 └── INDEX yz
      ├── y int
      ├── z int
      ├── rowid int not null (hidden)
      └── x int (storing)

exec-ddl
CREATE TABLE pqr
(
    p INT PRIMARY KEY,
    q INT,
    r INT,
    s STRING,
    t STRING,
    INDEX q (q),
    INDEX r (r),
    INDEX s (s) STORING (r),
    INDEX rs (r,s),
    INDEX ts (t,s)
)
----
TABLE pqr
 ├── p int not null
 ├── q int
 ├── r int
 ├── s string
 ├── t string
 ├── INDEX primary
 │    └── p int not null
 ├── INDEX q
 │    ├── q int
 │    └── p int not null
 ├── INDEX r
 │    ├── r int
 │    └── p int not null
 ├── INDEX s
 │    ├── s string
 │    ├── p int not null
 │    └── r int (storing)
 ├── INDEX rs
 │    ├── r int
 │    ├── s string
 │    └── p int not null
 └── INDEX ts
      ├── t string
      ├── s string
      └── p int not null

# --------------------------------------------------
# CommuteJoin
# --------------------------------------------------

# Verify that the reversed join expressions get added to the memo, and there
# are no duplicates.
memo
SELECT * FROM abc JOIN xyz ON a=z
----
memo (optimized, ~9KB, required=[presentation: a:1,b:2,c:3,x:5,y:6,z:7])
 ├── G1: (inner-join G2 G3 G4) (inner-join G3 G2 G4) (merge-join G2 G3 G5 inner-join,+1,+7) (lookup-join G3 G5 abc@ab,keyCols=[7],outCols=(1-3,5-7))
 │    └── [presentation: a:1,b:2,c:3,x:5,y:6,z:7]
 │         ├── best: (inner-join G2 G3 G4)
 │         └── cost: 2270.05
 ├── G2: (scan abc,cols=(1-3)) (scan abc@ab,cols=(1-3)) (scan abc@bc,cols=(1-3))
 │    ├── [ordering: +1]
 │    │    ├── best: (scan abc@ab,cols=(1-3))
 │    │    └── cost: 1070.02
 │    └── []
 │         ├── best: (scan abc,cols=(1-3))
 │         └── cost: 1070.02
 ├── G3: (scan xyz,cols=(5-7)) (scan xyz@xy,cols=(5-7)) (scan xyz@yz,cols=(5-7))
 │    ├── [ordering: +7]
 │    │    ├── best: (sort G3)
 │    │    └── cost: 1289.35
 │    └── []
 │         ├── best: (scan xyz,cols=(5-7))
 │         └── cost: 1070.02
 ├── G4: (filters G6)
 ├── G5: (filters)
 ├── G6: (eq G7 G8)
 ├── G7: (variable a)
 └── G8: (variable z)

memo
SELECT * FROM abc FULL OUTER JOIN xyz ON a=z
----
memo (optimized, ~8KB, required=[presentation: a:1,b:2,c:3,x:5,y:6,z:7])
 ├── G1: (full-join G2 G3 G4) (full-join G3 G2 G4) (merge-join G2 G3 G5 full-join,+1,+7)
 │    └── [presentation: a:1,b:2,c:3,x:5,y:6,z:7]
 │         ├── best: (full-join G2 G3 G4)
 │         └── cost: 2270.05
 ├── G2: (scan abc,cols=(1-3)) (scan abc@ab,cols=(1-3)) (scan abc@bc,cols=(1-3))
 │    ├── [ordering: +1]
 │    │    ├── best: (scan abc@ab,cols=(1-3))
 │    │    └── cost: 1070.02
 │    └── []
 │         ├── best: (scan abc,cols=(1-3))
 │         └── cost: 1070.02
 ├── G3: (scan xyz,cols=(5-7)) (scan xyz@xy,cols=(5-7)) (scan xyz@yz,cols=(5-7))
 │    ├── [ordering: +7]
 │    │    ├── best: (sort G3)
 │    │    └── cost: 1289.35
 │    └── []
 │         ├── best: (scan xyz,cols=(5-7))
 │         └── cost: 1070.02
 ├── G4: (filters G6)
 ├── G5: (filters)
 ├── G6: (eq G7 G8)
 ├── G7: (variable a)
 └── G8: (variable z)

# Verify that we swap to get the smaller side on the right.
opt
SELECT * FROM abc INNER JOIN xyz ON a=c WHERE b=1
----
inner-join
 ├── columns: a:1(int!null) b:2(int!null) c:3(int!null) x:5(int) y:6(int) z:7(int)
 ├── fd: ()-->(2), (1)==(3), (3)==(1)
 ├── scan xyz
 │    └── columns: x:5(int) y:6(int) z:7(int)
 ├── select
 │    ├── columns: a:1(int!null) b:2(int!null) c:3(int!null)
 │    ├── fd: ()-->(2), (1)==(3), (3)==(1)
 │    ├── scan abc@bc
 │    │    ├── columns: a:1(int) b:2(int!null) c:3(int!null)
 │    │    ├── constraint: /2/3/4: (/1/NULL - /1]
 │    │    └── fd: ()-->(2)
 │    └── filters
 │         └── a = c [type=bool, outer=(1,3), constraints=(/1: (/NULL - ]; /3: (/NULL - ]), fd=(1)==(3), (3)==(1)]
 └── filters (true)

opt
SELECT * FROM (SELECT * FROM abc WHERE b=1) FULL OUTER JOIN xyz ON a=z
----
full-join
 ├── columns: a:1(int) b:2(int) c:3(int) x:5(int) y:6(int) z:7(int)
 ├── fd: ()~~>(2)
 ├── scan xyz
 │    └── columns: x:5(int) y:6(int) z:7(int)
 ├── scan abc@bc
 │    ├── columns: a:1(int) b:2(int!null) c:3(int)
 │    ├── constraint: /2/3/4: [/1 - /1]
 │    └── fd: ()-->(2)
 └── filters
      └── a = z [type=bool, outer=(1,7), constraints=(/1: (/NULL - ]; /7: (/NULL - ]), fd=(1)==(7), (7)==(1)]

# Verify that we don't commute joins if there's a hint.
memo
SELECT * FROM abc INNER HASH JOIN xyz ON a=z
----
memo (optimized, ~7KB, required=[presentation: a:1,b:2,c:3,x:5,y:6,z:7])
 ├── G1: (inner-join G2 G3 G4)
 │    └── [presentation: a:1,b:2,c:3,x:5,y:6,z:7]
 │         ├── best: (inner-join G2 G3 G4)
 │         └── cost: 2270.05
 ├── G2: (scan abc,cols=(1-3)) (scan abc@ab,cols=(1-3)) (scan abc@bc,cols=(1-3))
 │    └── []
 │         ├── best: (scan abc,cols=(1-3))
 │         └── cost: 1070.02
 ├── G3: (scan xyz,cols=(5-7)) (scan xyz@xy,cols=(5-7)) (scan xyz@yz,cols=(5-7))
 │    └── []
 │         ├── best: (scan xyz,cols=(5-7))
 │         └── cost: 1070.02
 ├── G4: (filters G5)
 ├── G5: (eq G6 G7)
 ├── G6: (variable a)
 └── G7: (variable z)

# --------------------------------------------------
# CommuteLeftJoin
# --------------------------------------------------

memo
SELECT * FROM abc LEFT OUTER JOIN xyz ON a=z
----
memo (optimized, ~8KB, required=[presentation: a:1,b:2,c:3,x:5,y:6,z:7])
 ├── G1: (left-join G2 G3 G4) (right-join G3 G2 G4) (merge-join G2 G3 G5 left-join,+1,+7)
 │    └── [presentation: a:1,b:2,c:3,x:5,y:6,z:7]
 │         ├── best: (left-join G2 G3 G4)
 │         └── cost: 2270.05
 ├── G2: (scan abc,cols=(1-3)) (scan abc@ab,cols=(1-3)) (scan abc@bc,cols=(1-3))
 │    ├── [ordering: +1]
 │    │    ├── best: (scan abc@ab,cols=(1-3))
 │    │    └── cost: 1070.02
 │    └── []
 │         ├── best: (scan abc,cols=(1-3))
 │         └── cost: 1070.02
 ├── G3: (scan xyz,cols=(5-7)) (scan xyz@xy,cols=(5-7)) (scan xyz@yz,cols=(5-7))
 │    ├── [ordering: +7]
 │    │    ├── best: (sort G3)
 │    │    └── cost: 1289.35
 │    └── []
 │         ├── best: (scan xyz,cols=(5-7))
 │         └── cost: 1070.02
 ├── G4: (filters G6)
 ├── G5: (filters)
 ├── G6: (eq G7 G8)
 ├── G7: (variable a)
 └── G8: (variable z)

opt
SELECT * FROM abc LEFT OUTER JOIN xyz ON a=z WHERE b=1
----
right-join
 ├── columns: a:1(int) b:2(int!null) c:3(int) x:5(int) y:6(int) z:7(int)
 ├── fd: ()-->(2)
 ├── scan xyz
 │    └── columns: x:5(int) y:6(int) z:7(int)
 ├── scan abc@bc
 │    ├── columns: a:1(int) b:2(int!null) c:3(int)
 │    ├── constraint: /2/3/4: [/1 - /1]
 │    └── fd: ()-->(2)
 └── filters
      └── a = z [type=bool, outer=(1,7), constraints=(/1: (/NULL - ]; /7: (/NULL - ]), fd=(1)==(7), (7)==(1)]

# Verify that we don't commute joins if there's a hint.
memo
SELECT * FROM abc LEFT OUTER MERGE JOIN xyz ON a=z
----
memo (optimized, ~8KB, required=[presentation: a:1,b:2,c:3,x:5,y:6,z:7])
 ├── G1: (left-join G2 G3 G4) (merge-join G2 G3 G5 left-join,+1,+7)
 │    └── [presentation: a:1,b:2,c:3,x:5,y:6,z:7]
 │         ├── best: (merge-join G2="[ordering: +1]" G3="[ordering: +7]" G5 left-join,+1,+7)
 │         └── cost: 2479.38
 ├── G2: (scan abc,cols=(1-3)) (scan abc@ab,cols=(1-3)) (scan abc@bc,cols=(1-3))
 │    ├── [ordering: +1]
 │    │    ├── best: (scan abc@ab,cols=(1-3))
 │    │    └── cost: 1070.02
 │    └── []
 │         ├── best: (scan abc,cols=(1-3))
 │         └── cost: 1070.02
 ├── G3: (scan xyz,cols=(5-7)) (scan xyz@xy,cols=(5-7)) (scan xyz@yz,cols=(5-7))
 │    ├── [ordering: +7]
 │    │    ├── best: (sort G3)
 │    │    └── cost: 1289.35
 │    └── []
 │         ├── best: (scan xyz,cols=(5-7))
 │         └── cost: 1070.02
 ├── G4: (filters G6)
 ├── G5: (filters)
 ├── G6: (eq G7 G8)
 ├── G7: (variable a)
 └── G8: (variable z)

# --------------------------------------------------
# CommuteRightJoin
# --------------------------------------------------

memo
SELECT * FROM abc RIGHT OUTER JOIN xyz ON a=z
----
memo (optimized, ~9KB, required=[presentation: a:1,b:2,c:3,x:5,y:6,z:7])
 ├── G1: (right-join G2 G3 G4) (left-join G3 G2 G4) (merge-join G2 G3 G5 right-join,+1,+7) (lookup-join G3 G5 abc@ab,keyCols=[7],outCols=(1-3,5-7))
 │    └── [presentation: a:1,b:2,c:3,x:5,y:6,z:7]
 │         ├── best: (right-join G2 G3 G4)
 │         └── cost: 2270.05
 ├── G2: (scan abc,cols=(1-3)) (scan abc@ab,cols=(1-3)) (scan abc@bc,cols=(1-3))
 │    ├── [ordering: +1]
 │    │    ├── best: (scan abc@ab,cols=(1-3))
 │    │    └── cost: 1070.02
 │    └── []
 │         ├── best: (scan abc,cols=(1-3))
 │         └── cost: 1070.02
 ├── G3: (scan xyz,cols=(5-7)) (scan xyz@xy,cols=(5-7)) (scan xyz@yz,cols=(5-7))
 │    ├── [ordering: +7]
 │    │    ├── best: (sort G3)
 │    │    └── cost: 1289.35
 │    └── []
 │         ├── best: (scan xyz,cols=(5-7))
 │         └── cost: 1070.02
 ├── G4: (filters G6)
 ├── G5: (filters)
 ├── G6: (eq G7 G8)
 ├── G7: (variable a)
 └── G8: (variable z)

opt
SELECT * FROM (SELECT * FROM abc WHERE b=1) RIGHT OUTER JOIN xyz ON a=z
----
left-join
 ├── columns: a:1(int) b:2(int) c:3(int) x:5(int) y:6(int) z:7(int)
 ├── fd: ()~~>(2)
 ├── scan xyz
 │    └── columns: x:5(int) y:6(int) z:7(int)
 ├── scan abc@bc
 │    ├── columns: a:1(int) b:2(int!null) c:3(int)
 │    ├── constraint: /2/3/4: [/1 - /1]
 │    └── fd: ()-->(2)
 └── filters
      └── a = z [type=bool, outer=(1,7), constraints=(/1: (/NULL - ]; /7: (/NULL - ]), fd=(1)==(7), (7)==(1)]

# Verify that we don't commute joins if there's a hint.
memo
SELECT * FROM abc RIGHT HASH JOIN xyz ON a=z
----
memo (optimized, ~7KB, required=[presentation: a:1,b:2,c:3,x:5,y:6,z:7])
 ├── G1: (right-join G2 G3 G4)
 │    └── [presentation: a:1,b:2,c:3,x:5,y:6,z:7]
 │         ├── best: (right-join G2 G3 G4)
 │         └── cost: 2270.05
 ├── G2: (scan abc,cols=(1-3)) (scan abc@ab,cols=(1-3)) (scan abc@bc,cols=(1-3))
 │    └── []
 │         ├── best: (scan abc,cols=(1-3))
 │         └── cost: 1070.02
 ├── G3: (scan xyz,cols=(5-7)) (scan xyz@xy,cols=(5-7)) (scan xyz@yz,cols=(5-7))
 │    └── []
 │         ├── best: (scan xyz,cols=(5-7))
 │         └── cost: 1070.02
 ├── G4: (filters G5)
 ├── G5: (eq G6 G7)
 ├── G6: (variable a)
 └── G7: (variable z)


# --------------------------------------------------
# GenerateMergeJoins
# --------------------------------------------------

opt
SELECT * FROM abc JOIN xyz ON a=x
----
inner-join (merge)
 ├── columns: a:1(int!null) b:2(int) c:3(int) x:5(int!null) y:6(int) z:7(int)
 ├── left ordering: +1
 ├── right ordering: +5
 ├── fd: (1)==(5), (5)==(1)
 ├── scan abc@ab
 │    ├── columns: a:1(int) b:2(int) c:3(int)
 │    └── ordering: +1
 ├── scan xyz@xy
 │    ├── columns: x:5(int) y:6(int) z:7(int)
 │    └── ordering: +5
 └── filters (true)

memo
SELECT * FROM abc JOIN xyz ON a=x
----
memo (optimized, ~10KB, required=[presentation: a:1,b:2,c:3,x:5,y:6,z:7])
 ├── G1: (inner-join G2 G3 G4) (inner-join G3 G2 G4) (merge-join G2 G3 G5 inner-join,+1,+5) (lookup-join G2 G5 xyz@xy,keyCols=[1],outCols=(1-3,5-7)) (merge-join G3 G2 G5 inner-join,+5,+1) (lookup-join G3 G5 abc@ab,keyCols=[5],outCols=(1-3,5-7))
 │    └── [presentation: a:1,b:2,c:3,x:5,y:6,z:7]
 │         ├── best: (merge-join G2="[ordering: +1]" G3="[ordering: +5]" G5 inner-join,+1,+5)
 │         └── cost: 2260.05
 ├── G2: (scan abc,cols=(1-3)) (scan abc@ab,cols=(1-3)) (scan abc@bc,cols=(1-3))
 │    ├── [ordering: +1]
 │    │    ├── best: (scan abc@ab,cols=(1-3))
 │    │    └── cost: 1070.02
 │    └── []
 │         ├── best: (scan abc,cols=(1-3))
 │         └── cost: 1070.02
 ├── G3: (scan xyz,cols=(5-7)) (scan xyz@xy,cols=(5-7)) (scan xyz@yz,cols=(5-7))
 │    ├── [ordering: +5]
 │    │    ├── best: (scan xyz@xy,cols=(5-7))
 │    │    └── cost: 1070.02
 │    └── []
 │         ├── best: (scan xyz,cols=(5-7))
 │         └── cost: 1070.02
 ├── G4: (filters G6)
 ├── G5: (filters)
 ├── G6: (eq G7 G8)
 ├── G7: (variable a)
 └── G8: (variable x)

# Verify that we don't generate merge joins if there's a hint that says otherwise.
memo
SELECT * FROM abc INNER HASH JOIN xyz ON a=x
----
memo (optimized, ~7KB, required=[presentation: a:1,b:2,c:3,x:5,y:6,z:7])
 ├── G1: (inner-join G2 G3 G4)
 │    └── [presentation: a:1,b:2,c:3,x:5,y:6,z:7]
 │         ├── best: (inner-join G2 G3 G4)
 │         └── cost: 2270.05
 ├── G2: (scan abc,cols=(1-3)) (scan abc@ab,cols=(1-3)) (scan abc@bc,cols=(1-3))
 │    └── []
 │         ├── best: (scan abc,cols=(1-3))
 │         └── cost: 1070.02
 ├── G3: (scan xyz,cols=(5-7)) (scan xyz@xy,cols=(5-7)) (scan xyz@yz,cols=(5-7))
 │    └── []
 │         ├── best: (scan xyz,cols=(5-7))
 │         └── cost: 1070.02
 ├── G4: (filters G5)
 ├── G5: (eq G6 G7)
 ├── G6: (variable a)
 └── G7: (variable x)

opt
SELECT * FROM abc JOIN xyz ON x=a
----
inner-join (merge)
 ├── columns: a:1(int!null) b:2(int) c:3(int) x:5(int!null) y:6(int) z:7(int)
 ├── left ordering: +1
 ├── right ordering: +5
 ├── fd: (1)==(5), (5)==(1)
 ├── scan abc@ab
 │    ├── columns: a:1(int) b:2(int) c:3(int)
 │    └── ordering: +1
 ├── scan xyz@xy
 │    ├── columns: x:5(int) y:6(int) z:7(int)
 │    └── ordering: +5
 └── filters (true)

opt
SELECT * FROM abc JOIN xyz ON a=x AND a=x AND x=a
----
inner-join (merge)
 ├── columns: a:1(int!null) b:2(int) c:3(int) x:5(int!null) y:6(int) z:7(int)
 ├── left ordering: +1
 ├── right ordering: +5
 ├── fd: (1)==(5), (5)==(1)
 ├── scan abc@ab
 │    ├── columns: a:1(int) b:2(int) c:3(int)
 │    └── ordering: +1
 ├── scan xyz@xy
 │    ├── columns: x:5(int) y:6(int) z:7(int)
 │    └── ordering: +5
 └── filters (true)

# Use constraints to force the choice of an index which doesn't help, and
# verify that we don't prefer a merge-join that has to sort both of its inputs.
opt
SELECT * FROM abc JOIN xyz ON a=x AND b=y WHERE b=1 AND y=1
----
inner-join
 ├── columns: a:1(int!null) b:2(int!null) c:3(int) x:5(int!null) y:6(int!null) z:7(int)
 ├── fd: ()-->(2,6), (1)==(5), (5)==(1), (2)==(6), (6)==(2)
 ├── scan abc@bc
 │    ├── columns: a:1(int) b:2(int!null) c:3(int)
 │    ├── constraint: /2/3/4: [/1 - /1]
 │    └── fd: ()-->(2)
 ├── scan xyz@yz
 │    ├── columns: x:5(int) y:6(int!null) z:7(int)
 │    ├── constraint: /6/7/8: [/1 - /1]
 │    └── fd: ()-->(6)
 └── filters
      ├── a = x [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
      └── b = y [type=bool, outer=(2,6), constraints=(/2: (/NULL - ]; /6: (/NULL - ]), fd=(2)==(6), (6)==(2)]

# Verify case where we generate multiple merge-joins.
memo
SELECT * FROM stu AS l JOIN stu AS r ON (l.s, l.t, l.u) = (r.s, r.t, r.u)
----
memo (optimized, ~10KB, required=[presentation: s:1,t:2,u:3,s:4,t:5,u:6])
 ├── G1: (inner-join G2 G3 G4) (inner-join G3 G2 G4) (merge-join G2 G3 G5 inner-join,+1,+2,+3,+4,+5,+6) (merge-join G2 G3 G5 inner-join,+3,+2,+1,+6,+5,+4) (lookup-join G2 G5 stu,keyCols=[1 2 3],outCols=(1-6)) (lookup-join G2 G5 stu@uts,keyCols=[3 2 1],outCols=(1-6)) (merge-join G3 G2 G5 inner-join,+4,+5,+6,+1,+2,+3) (merge-join G3 G2 G5 inner-join,+6,+5,+4,+3,+2,+1) (lookup-join G3 G5 stu,keyCols=[4 5 6],outCols=(1-6)) (lookup-join G3 G5 stu@uts,keyCols=[6 5 4],outCols=(1-6))
 │    └── [presentation: s:1,t:2,u:3,s:4,t:5,u:6]
 │         ├── best: (merge-join G2="[ordering: +1,+2,+3]" G3="[ordering: +4,+5,+6]" G5 inner-join,+1,+2,+3,+4,+5,+6)
 │         └── cost: 2140.06
 ├── G2: (scan l) (scan l@uts)
 │    ├── [ordering: +1,+2,+3]
 │    │    ├── best: (scan l)
 │    │    └── cost: 1060.02
 │    ├── [ordering: +3,+2,+1]
 │    │    ├── best: (scan l@uts)
 │    │    └── cost: 1060.02
 │    └── []
 │         ├── best: (scan l)
 │         └── cost: 1060.02
 ├── G3: (scan r) (scan r@uts)
 │    ├── [ordering: +4,+5,+6]
 │    │    ├── best: (scan r)
 │    │    └── cost: 1060.02
 │    ├── [ordering: +6,+5,+4]
 │    │    ├── best: (scan r@uts)
 │    │    └── cost: 1060.02
 │    └── []
 │         ├── best: (scan r)
 │         └── cost: 1060.02
 ├── G4: (filters G6 G7 G8)
 ├── G5: (filters)
 ├── G6: (eq G9 G10)
 ├── G7: (eq G11 G12)
 ├── G8: (eq G13 G14)
 ├── G9: (variable l.s)
 ├── G10: (variable r.s)
 ├── G11: (variable l.t)
 ├── G12: (variable r.t)
 ├── G13: (variable l.u)
 └── G14: (variable r.u)

exploretrace rule=GenerateMergeJoins
SELECT * FROM stu AS l JOIN stu AS r ON (l.s, l.t, l.u) = (r.s, r.t, r.u)
----
----
================================================================================
GenerateMergeJoins
================================================================================
Source expression:
  inner-join
   ├── columns: s:1(int!null) t:2(int!null) u:3(int!null) s:4(int!null) t:5(int!null) u:6(int!null)
   ├── key: (4-6)
   ├── fd: (1)==(4), (4)==(1), (2)==(5), (5)==(2), (3)==(6), (6)==(3)
   ├── scan l
   │    ├── columns: l.s:1(int!null) l.t:2(int!null) l.u:3(int!null)
   │    └── key: (1-3)
   ├── scan r
   │    ├── columns: r.s:4(int!null) r.t:5(int!null) r.u:6(int!null)
   │    └── key: (4-6)
   └── filters
        ├── l.s = r.s [type=bool, outer=(1,4), constraints=(/1: (/NULL - ]; /4: (/NULL - ]), fd=(1)==(4), (4)==(1)]
        ├── l.t = r.t [type=bool, outer=(2,5), constraints=(/2: (/NULL - ]; /5: (/NULL - ]), fd=(2)==(5), (5)==(2)]
        └── l.u = r.u [type=bool, outer=(3,6), constraints=(/3: (/NULL - ]; /6: (/NULL - ]), fd=(3)==(6), (6)==(3)]

New expression 1 of 2:
  inner-join (merge)
   ├── columns: s:1(int!null) t:2(int!null) u:3(int!null) s:4(int!null) t:5(int!null) u:6(int!null)
   ├── left ordering: +1,+2,+3
   ├── right ordering: +4,+5,+6
   ├── key: (4-6)
   ├── fd: (1)==(4), (4)==(1), (2)==(5), (5)==(2), (3)==(6), (6)==(3)
   ├── scan l
   │    ├── columns: l.s:1(int!null) l.t:2(int!null) l.u:3(int!null)
   │    ├── key: (1-3)
   │    └── ordering: +1,+2,+3
   ├── scan r
   │    ├── columns: r.s:4(int!null) r.t:5(int!null) r.u:6(int!null)
   │    ├── key: (4-6)
   │    └── ordering: +4,+5,+6
   └── filters (true)

New expression 2 of 2:
  inner-join (merge)
   ├── columns: s:1(int!null) t:2(int!null) u:3(int!null) s:4(int!null) t:5(int!null) u:6(int!null)
   ├── left ordering: +3,+2,+1
   ├── right ordering: +6,+5,+4
   ├── key: (4-6)
   ├── fd: (1)==(4), (4)==(1), (2)==(5), (5)==(2), (3)==(6), (6)==(3)
   ├── scan l@uts
   │    ├── columns: l.s:1(int!null) l.t:2(int!null) l.u:3(int!null)
   │    ├── key: (1-3)
   │    └── ordering: +3,+2,+1
   ├── scan r@uts
   │    ├── columns: r.s:4(int!null) r.t:5(int!null) r.u:6(int!null)
   │    ├── key: (4-6)
   │    └── ordering: +6,+5,+4
   └── filters (true)

================================================================================
GenerateMergeJoins
================================================================================
Source expression:
  inner-join
   ├── columns: s:1(int!null) t:2(int!null) u:3(int!null) s:4(int!null) t:5(int!null) u:6(int!null)
   ├── key: (4-6)
   ├── fd: (1)==(4), (4)==(1), (2)==(5), (5)==(2), (3)==(6), (6)==(3)
   ├── scan r
   │    ├── columns: r.s:4(int!null) r.t:5(int!null) r.u:6(int!null)
   │    └── key: (4-6)
   ├── scan l
   │    ├── columns: l.s:1(int!null) l.t:2(int!null) l.u:3(int!null)
   │    └── key: (1-3)
   └── filters
        ├── l.s = r.s [type=bool, outer=(1,4), constraints=(/1: (/NULL - ]; /4: (/NULL - ]), fd=(1)==(4), (4)==(1)]
        ├── l.t = r.t [type=bool, outer=(2,5), constraints=(/2: (/NULL - ]; /5: (/NULL - ]), fd=(2)==(5), (5)==(2)]
        └── l.u = r.u [type=bool, outer=(3,6), constraints=(/3: (/NULL - ]; /6: (/NULL - ]), fd=(3)==(6), (6)==(3)]

New expression 1 of 2:
  inner-join (merge)
   ├── columns: s:1(int!null) t:2(int!null) u:3(int!null) s:4(int!null) t:5(int!null) u:6(int!null)
   ├── left ordering: +4,+5,+6
   ├── right ordering: +1,+2,+3
   ├── key: (4-6)
   ├── fd: (1)==(4), (4)==(1), (2)==(5), (5)==(2), (3)==(6), (6)==(3)
   ├── scan r
   │    ├── columns: r.s:4(int!null) r.t:5(int!null) r.u:6(int!null)
   │    ├── key: (4-6)
   │    └── ordering: +4,+5,+6
   ├── scan l
   │    ├── columns: l.s:1(int!null) l.t:2(int!null) l.u:3(int!null)
   │    ├── key: (1-3)
   │    └── ordering: +1,+2,+3
   └── filters (true)

New expression 2 of 2:
  inner-join (merge)
   ├── columns: s:1(int!null) t:2(int!null) u:3(int!null) s:4(int!null) t:5(int!null) u:6(int!null)
   ├── left ordering: +6,+5,+4
   ├── right ordering: +3,+2,+1
   ├── key: (4-6)
   ├── fd: (1)==(4), (4)==(1), (2)==(5), (5)==(2), (3)==(6), (6)==(3)
   ├── scan r@uts
   │    ├── columns: r.s:4(int!null) r.t:5(int!null) r.u:6(int!null)
   │    ├── key: (4-6)
   │    └── ordering: +6,+5,+4
   ├── scan l@uts
   │    ├── columns: l.s:1(int!null) l.t:2(int!null) l.u:3(int!null)
   │    ├── key: (1-3)
   │    └── ordering: +3,+2,+1
   └── filters (true)
----
----

# Add statistics to make table stu large (so that sorting abc is relatively cheap).
exec-ddl
ALTER TABLE stu INJECT STATISTICS '[
  {
    "columns": ["s"],
    "created_at": "2018-05-01 1:00:00.00000+00:00",
    "row_count": 1000000,
    "distinct_count": 1000000
  }
]'
----

# The ordering is coming from the left side.
opt
SELECT * FROM stu LEFT OUTER JOIN abc ON (c,b,a) = (s,t,u)
----
left-join (merge)
 ├── columns: s:1(int!null) t:2(int!null) u:3(int!null) a:4(int) b:5(int) c:6(int)
 ├── left ordering: +1,+2,+3
 ├── right ordering: +6,+5,+4
 ├── scan stu
 │    ├── columns: s:1(int!null) t:2(int!null) u:3(int!null)
 │    ├── key: (1-3)
 │    └── ordering: +1,+2,+3
 ├── sort
 │    ├── columns: a:4(int) b:5(int) c:6(int)
 │    ├── ordering: +6,+5,+4
 │    └── scan abc
 │         └── columns: a:4(int) b:5(int) c:6(int)
 └── filters (true)

# The ordering is coming from the right side.
opt
SELECT * FROM abc RIGHT OUTER JOIN stu ON (c,b,a) = (s,t,u)
----
left-join (merge)
 ├── columns: a:1(int) b:2(int) c:3(int) s:5(int!null) t:6(int!null) u:7(int!null)
 ├── left ordering: +5,+6,+7
 ├── right ordering: +3,+2,+1
 ├── scan stu
 │    ├── columns: s:5(int!null) t:6(int!null) u:7(int!null)
 │    ├── key: (5-7)
 │    └── ordering: +5,+6,+7
 ├── sort
 │    ├── columns: a:1(int) b:2(int) c:3(int)
 │    ├── ordering: +3,+2,+1
 │    └── scan abc
 │         └── columns: a:1(int) b:2(int) c:3(int)
 └── filters (true)

# In these cases, we shouldn't pick up equivalencies.
memo
SELECT * FROM abc JOIN xyz ON a=b
----
memo (optimized, ~12KB, required=[presentation: a:1,b:2,c:3,x:5,y:6,z:7])
 ├── G1: (inner-join G2 G3 G4) (inner-join G3 G2 G4)
 │    └── [presentation: a:1,b:2,c:3,x:5,y:6,z:7]
 │         ├── best: (inner-join G3 G2 G4)
 │         └── cost: 2251.92
 ├── G2: (select G5 G6) (select G7 G6) (select G8 G6)
 │    └── []
 │         ├── best: (select G7 G6)
 │         └── cost: 1069.22
 ├── G3: (scan xyz,cols=(5-7)) (scan xyz@xy,cols=(5-7)) (scan xyz@yz,cols=(5-7))
 │    └── []
 │         ├── best: (scan xyz,cols=(5-7))
 │         └── cost: 1070.02
 ├── G4: (filters)
 ├── G5: (scan abc,cols=(1-3)) (scan abc@ab,cols=(1-3)) (scan abc@bc,cols=(1-3))
 │    └── []
 │         ├── best: (scan abc,cols=(1-3))
 │         └── cost: 1070.02
 ├── G6: (filters G9)
 ├── G7: (scan abc@ab,cols=(1-3),constrained)
 │    └── []
 │         ├── best: (scan abc@ab,cols=(1-3),constrained)
 │         └── cost: 1059.31
 ├── G8: (scan abc@bc,cols=(1-3),constrained)
 │    └── []
 │         ├── best: (scan abc@bc,cols=(1-3),constrained)
 │         └── cost: 1059.31
 ├── G9: (eq G10 G11)
 ├── G10: (variable a)
 └── G11: (variable b)

exec-ddl
CREATE TABLE kfloat (k FLOAT PRIMARY KEY)
----
TABLE kfloat
 ├── k float not null
 └── INDEX primary
      └── k float not null

memo
SELECT * FROM abc JOIN kfloat ON a=k
----
memo (optimized, ~5KB, required=[presentation: a:1,b:2,c:3,k:5])
 ├── G1: (inner-join G2 G3 G4) (inner-join G3 G2 G4)
 │    └── [presentation: a:1,b:2,c:3,k:5]
 │         ├── best: (inner-join G2 G3 G4)
 │         └── cost: 2130.05
 ├── G2: (scan abc,cols=(1-3)) (scan abc@ab,cols=(1-3)) (scan abc@bc,cols=(1-3))
 │    └── []
 │         ├── best: (scan abc,cols=(1-3))
 │         └── cost: 1070.02
 ├── G3: (scan kfloat)
 │    └── []
 │         ├── best: (scan kfloat)
 │         └── cost: 1020.02
 ├── G4: (filters G5)
 ├── G5: (eq G6 G7)
 ├── G6: (variable a)
 └── G7: (variable k)

# We should only pick up one equivalency.
opt
SELECT * FROM abc JOIN xyz ON a=x AND a=y
----
inner-join (merge)
 ├── columns: a:1(int!null) b:2(int) c:3(int) x:5(int!null) y:6(int!null) z:7(int)
 ├── left ordering: +1
 ├── right ordering: +5
 ├── fd: (1)==(5,6), (5)==(1,6), (6)==(1,5)
 ├── scan abc@ab
 │    ├── columns: a:1(int) b:2(int) c:3(int)
 │    └── ordering: +1
 ├── scan xyz@xy
 │    ├── columns: x:5(int) y:6(int) z:7(int)
 │    └── ordering: +5
 └── filters
      └── a = y [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ]), fd=(1)==(6), (6)==(1)]

# Verify multiple merge-joins can be chained.
opt
SELECT * FROM abc JOIN xyz ON a=x AND b=y RIGHT OUTER JOIN stu ON a=s
----
right-join (merge)
 ├── columns: a:1(int) b:2(int) c:3(int) x:5(int) y:6(int) z:7(int) s:9(int!null) t:10(int!null) u:11(int!null)
 ├── left ordering: +1
 ├── right ordering: +9
 ├── fd: (1)==(5), (5)==(1), (2)==(6), (6)==(2)
 ├── inner-join (merge)
 │    ├── columns: a:1(int!null) b:2(int!null) c:3(int) x:5(int!null) y:6(int!null) z:7(int)
 │    ├── left ordering: +1,+2
 │    ├── right ordering: +5,+6
 │    ├── fd: (1)==(5), (5)==(1), (2)==(6), (6)==(2)
 │    ├── ordering: +(1|5) [actual: +1]
 │    ├── scan abc@ab
 │    │    ├── columns: a:1(int) b:2(int) c:3(int)
 │    │    └── ordering: +1,+2
 │    ├── scan xyz@xy
 │    │    ├── columns: x:5(int) y:6(int) z:7(int)
 │    │    └── ordering: +5,+6
 │    └── filters (true)
 ├── scan stu
 │    ├── columns: s:9(int!null) t:10(int!null) u:11(int!null)
 │    ├── key: (9-11)
 │    └── ordering: +9
 └── filters (true)

opt
SELECT * FROM abc JOIN xyz ON a=x AND b=y RIGHT OUTER JOIN stu ON a=u AND y=t
----
left-join (merge)
 ├── columns: a:1(int) b:2(int) c:3(int) x:5(int) y:6(int) z:7(int) s:9(int!null) t:10(int!null) u:11(int!null)
 ├── left ordering: +11,+10
 ├── right ordering: +1,+6
 ├── fd: (1)==(5), (5)==(1), (2)==(6), (6)==(2)
 ├── scan stu@uts
 │    ├── columns: s:9(int!null) t:10(int!null) u:11(int!null)
 │    ├── key: (9-11)
 │    └── ordering: +11,+10
 ├── inner-join (merge)
 │    ├── columns: a:1(int!null) b:2(int!null) c:3(int) x:5(int!null) y:6(int!null) z:7(int)
 │    ├── left ordering: +1,+2
 │    ├── right ordering: +5,+6
 │    ├── fd: (1)==(5), (5)==(1), (2)==(6), (6)==(2)
 │    ├── ordering: +(1|5),+(2|6) [actual: +1,+2]
 │    ├── scan abc@ab
 │    │    ├── columns: a:1(int) b:2(int) c:3(int)
 │    │    └── ordering: +1,+2
 │    ├── scan xyz@xy
 │    │    ├── columns: x:5(int) y:6(int) z:7(int)
 │    │    └── ordering: +5,+6
 │    └── filters (true)
 └── filters (true)

# --------------------------------------------------
# GenerateLookupJoins
# --------------------------------------------------

exec-ddl
CREATE TABLE abcd (a INT, b INT, c INT, INDEX (a,b))
----
TABLE abcd
 ├── a int
 ├── b int
 ├── c int
 ├── rowid int not null (hidden)
 ├── INDEX primary
 │    └── rowid int not null (hidden)
 └── INDEX secondary
      ├── a int
      ├── b int
      └── rowid int not null (hidden)

exec-ddl
CREATE TABLE small (m INT, n INT)
----
TABLE small
 ├── m int
 ├── n int
 ├── rowid int not null (hidden)
 └── INDEX primary
      └── rowid int not null (hidden)

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

# Covering case.
opt
SELECT a,b,n,m FROM small JOIN abcd ON a=m
----
inner-join (lookup abcd@secondary)
 ├── columns: a:4(int!null) b:5(int) n:2(int) m:1(int!null)
 ├── key columns: [1] = [4]
 ├── fd: (1)==(4), (4)==(1)
 ├── scan small
 │    └── columns: m:1(int) n:2(int)
 └── filters (true)

# Covering case, left-join.
opt
SELECT a,b,n,m FROM small LEFT JOIN abcd ON a=m
----
left-join (lookup abcd@secondary)
 ├── columns: a:4(int) b:5(int) n:2(int) m:1(int)
 ├── key columns: [1] = [4]
 ├── scan small
 │    └── columns: m:1(int) n:2(int)
 └── filters (true)

# Non-covering case.
opt
SELECT * FROM small JOIN abcd ON a=m
----
inner-join (lookup abcd)
 ├── columns: m:1(int!null) n:2(int) a:4(int!null) b:5(int) c:6(int)
 ├── key columns: [7] = [7]
 ├── fd: (1)==(4), (4)==(1)
 ├── inner-join (lookup abcd@secondary)
 │    ├── columns: m:1(int!null) n:2(int) a:4(int!null) b:5(int) abcd.rowid:7(int!null)
 │    ├── key columns: [1] = [4]
 │    ├── fd: (7)-->(4,5), (1)==(4), (4)==(1)
 │    ├── scan small
 │    │    └── columns: m:1(int) n:2(int)
 │    └── filters (true)
 └── filters (true)

# Non-covering case, left join.
opt
SELECT * FROM small LEFT JOIN abcd ON a=m
----
left-join (lookup abcd)
 ├── columns: m:1(int) n:2(int) a:4(int) b:5(int) c:6(int)
 ├── key columns: [7] = [7]
 ├── left-join (lookup abcd@secondary)
 │    ├── columns: m:1(int) n:2(int) a:4(int) b:5(int) abcd.rowid:7(int)
 │    ├── key columns: [1] = [4]
 │    ├── fd: (7)-->(4,5)
 │    ├── scan small
 │    │    └── columns: m:1(int) n:2(int)
 │    └── filters (true)
 └── filters (true)

# Non-covering case, extra filter bound by index.
opt
SELECT * FROM small JOIN abcd ON a=m AND b>n
----
inner-join (lookup abcd)
 ├── columns: m:1(int!null) n:2(int!null) a:4(int!null) b:5(int!null) c:6(int)
 ├── key columns: [7] = [7]
 ├── fd: (1)==(4), (4)==(1)
 ├── inner-join (lookup abcd@secondary)
 │    ├── columns: m:1(int!null) n:2(int!null) a:4(int!null) b:5(int!null) abcd.rowid:7(int!null)
 │    ├── key columns: [1] = [4]
 │    ├── fd: (7)-->(4,5), (1)==(4), (4)==(1)
 │    ├── scan small
 │    │    └── columns: m:1(int) n:2(int)
 │    └── filters
 │         └── b > n [type=bool, outer=(2,5), constraints=(/2: (/NULL - ]; /5: (/NULL - ])]
 └── filters (true)

# Non-covering case, extra filter bound by index, left join.
opt
SELECT * FROM small LEFT JOIN abcd ON a=m AND b>n
----
left-join (lookup abcd)
 ├── columns: m:1(int) n:2(int) a:4(int) b:5(int) c:6(int)
 ├── key columns: [7] = [7]
 ├── left-join (lookup abcd@secondary)
 │    ├── columns: m:1(int) n:2(int) a:4(int) b:5(int) abcd.rowid:7(int)
 │    ├── key columns: [1] = [4]
 │    ├── fd: (7)-->(4,5)
 │    ├── scan small
 │    │    └── columns: m:1(int) n:2(int)
 │    └── filters
 │         └── b > n [type=bool, outer=(2,5), constraints=(/2: (/NULL - ]; /5: (/NULL - ])]
 └── filters (true)

# Non-covering case, extra filter not bound by index.
opt
SELECT * FROM small JOIN abcd ON a=m AND c>n
----
inner-join (lookup abcd)
 ├── columns: m:1(int!null) n:2(int!null) a:4(int!null) b:5(int) c:6(int!null)
 ├── key columns: [7] = [7]
 ├── fd: (1)==(4), (4)==(1)
 ├── inner-join (lookup abcd@secondary)
 │    ├── columns: m:1(int!null) n:2(int) a:4(int!null) b:5(int) abcd.rowid:7(int!null)
 │    ├── key columns: [1] = [4]
 │    ├── fd: (7)-->(4,5), (1)==(4), (4)==(1)
 │    ├── scan small
 │    │    └── columns: m:1(int) n:2(int)
 │    └── filters (true)
 └── filters
      └── c > n [type=bool, outer=(2,6), constraints=(/2: (/NULL - ]; /6: (/NULL - ])]

# Non-covering case, extra filter not bound by index, left join.
# In this case, we can't yet convert to a lookup join (see
# the GenerateLookupJoins custom func).
opt
SELECT * FROM small LEFT JOIN abcd ON a=m AND c>n
----
right-join
 ├── columns: m:1(int) n:2(int) a:4(int) b:5(int) c:6(int)
 ├── scan abcd
 │    └── columns: a:4(int) b:5(int) c:6(int)
 ├── scan small
 │    └── columns: m:1(int) n:2(int)
 └── filters
      ├── a = m [type=bool, outer=(1,4), constraints=(/1: (/NULL - ]; /4: (/NULL - ]), fd=(1)==(4), (4)==(1)]
      └── c > n [type=bool, outer=(2,6), constraints=(/2: (/NULL - ]; /6: (/NULL - ])]


# Verify rule application when we can do a lookup join on both sides.
exploretrace rule=GenerateLookupJoins
SELECT * FROM abc JOIN xyz ON a=x AND a=y
----
----
================================================================================
GenerateLookupJoins
================================================================================
Source expression:
  inner-join
   ├── columns: a:1(int!null) b:2(int) c:3(int) x:5(int!null) y:6(int!null) z:7(int)
   ├── fd: (1)==(5,6), (5)==(1,6), (6)==(1,5)
   ├── scan abc
   │    └── columns: a:1(int) b:2(int) c:3(int)
   ├── scan xyz
   │    └── columns: x:5(int) y:6(int) z:7(int)
   └── filters
        ├── a = x [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
        └── a = y [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ]), fd=(1)==(6), (6)==(1)]

New expression 1 of 1:
  inner-join (lookup xyz@xy)
   ├── columns: a:1(int!null) b:2(int) c:3(int) x:5(int!null) y:6(int!null) z:7(int)
   ├── key columns: [1] = [5]
   ├── fd: (1)==(5,6), (5)==(1,6), (6)==(1,5)
   ├── scan abc
   │    └── columns: a:1(int) b:2(int) c:3(int)
   └── filters
        └── a = y [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ]), fd=(1)==(6), (6)==(1)]

================================================================================
GenerateLookupJoins
================================================================================
Source expression:
  inner-join
   ├── columns: a:1(int!null) b:2(int) c:3(int) x:5(int!null) y:6(int!null) z:7(int)
   ├── fd: (1)==(5,6), (5)==(1,6), (6)==(1,5)
   ├── scan xyz
   │    └── columns: x:5(int) y:6(int) z:7(int)
   ├── scan abc
   │    └── columns: a:1(int) b:2(int) c:3(int)
   └── filters
        ├── a = x [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
        └── a = y [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ]), fd=(1)==(6), (6)==(1)]

New expression 1 of 1:
  inner-join (lookup abc@ab)
   ├── columns: a:1(int!null) b:2(int) c:3(int) x:5(int!null) y:6(int!null) z:7(int)
   ├── key columns: [5] = [1]
   ├── fd: (1)==(5,6), (5)==(1,6), (6)==(1,5)
   ├── scan xyz
   │    └── columns: x:5(int) y:6(int) z:7(int)
   └── filters
        └── a = y [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ]), fd=(1)==(6), (6)==(1)]
----
----

# Verify rule application when we can do a lookup join on the left side.
exploretrace rule=GenerateLookupJoins
SELECT * FROM abc JOIN xyz ON a=z
----
----
================================================================================
GenerateLookupJoins
================================================================================
Source expression:
  inner-join
   ├── columns: a:1(int!null) b:2(int) c:3(int) x:5(int) y:6(int) z:7(int!null)
   ├── fd: (1)==(7), (7)==(1)
   ├── scan abc
   │    └── columns: a:1(int) b:2(int) c:3(int)
   ├── scan xyz
   │    └── columns: x:5(int) y:6(int) z:7(int)
   └── filters
        └── a = z [type=bool, outer=(1,7), constraints=(/1: (/NULL - ]; /7: (/NULL - ]), fd=(1)==(7), (7)==(1)]

No new expressions.

================================================================================
GenerateLookupJoins
================================================================================
Source expression:
  inner-join
   ├── columns: a:1(int!null) b:2(int) c:3(int) x:5(int) y:6(int) z:7(int!null)
   ├── fd: (1)==(7), (7)==(1)
   ├── scan xyz
   │    └── columns: x:5(int) y:6(int) z:7(int)
   ├── scan abc
   │    └── columns: a:1(int) b:2(int) c:3(int)
   └── filters
        └── a = z [type=bool, outer=(1,7), constraints=(/1: (/NULL - ]; /7: (/NULL - ]), fd=(1)==(7), (7)==(1)]

New expression 1 of 1:
  inner-join (lookup abc@ab)
   ├── columns: a:1(int!null) b:2(int) c:3(int) x:5(int) y:6(int) z:7(int!null)
   ├── key columns: [7] = [1]
   ├── fd: (1)==(7), (7)==(1)
   ├── scan xyz
   │    └── columns: x:5(int) y:6(int) z:7(int)
   └── filters (true)
----
----

exploretrace rule=GenerateLookupJoins
SELECT * FROM abc RIGHT JOIN xyz ON a=z
----
----
================================================================================
GenerateLookupJoins
================================================================================
Source expression:
  left-join
   ├── columns: a:1(int) b:2(int) c:3(int) x:5(int) y:6(int) z:7(int)
   ├── scan xyz
   │    └── columns: x:5(int) y:6(int) z:7(int)
   ├── scan abc
   │    └── columns: a:1(int) b:2(int) c:3(int)
   └── filters
        └── a = z [type=bool, outer=(1,7), constraints=(/1: (/NULL - ]; /7: (/NULL - ]), fd=(1)==(7), (7)==(1)]

New expression 1 of 1:
  left-join (lookup abc@ab)
   ├── columns: a:1(int) b:2(int) c:3(int) x:5(int) y:6(int) z:7(int)
   ├── key columns: [7] = [1]
   ├── scan xyz
   │    └── columns: x:5(int) y:6(int) z:7(int)
   └── filters (true)
----
----

# Verify rule application when we can do a lookup join on the right side.
exploretrace rule=GenerateLookupJoins
SELECT * FROM abc JOIN xyz ON c=x
----
----
================================================================================
GenerateLookupJoins
================================================================================
Source expression:
  inner-join
   ├── columns: a:1(int) b:2(int) c:3(int!null) x:5(int!null) y:6(int) z:7(int)
   ├── fd: (3)==(5), (5)==(3)
   ├── scan abc
   │    └── columns: a:1(int) b:2(int) c:3(int)
   ├── scan xyz
   │    └── columns: x:5(int) y:6(int) z:7(int)
   └── filters
        └── c = x [type=bool, outer=(3,5), constraints=(/3: (/NULL - ]; /5: (/NULL - ]), fd=(3)==(5), (5)==(3)]

New expression 1 of 1:
  inner-join (lookup xyz@xy)
   ├── columns: a:1(int) b:2(int) c:3(int!null) x:5(int!null) y:6(int) z:7(int)
   ├── key columns: [3] = [5]
   ├── fd: (3)==(5), (5)==(3)
   ├── scan abc
   │    └── columns: a:1(int) b:2(int) c:3(int)
   └── filters (true)

================================================================================
GenerateLookupJoins
================================================================================
Source expression:
  inner-join
   ├── columns: a:1(int) b:2(int) c:3(int!null) x:5(int!null) y:6(int) z:7(int)
   ├── fd: (3)==(5), (5)==(3)
   ├── scan xyz
   │    └── columns: x:5(int) y:6(int) z:7(int)
   ├── scan abc
   │    └── columns: a:1(int) b:2(int) c:3(int)
   └── filters
        └── c = x [type=bool, outer=(3,5), constraints=(/3: (/NULL - ]; /5: (/NULL - ]), fd=(3)==(5), (5)==(3)]

No new expressions.
----
----

exploretrace rule=GenerateLookupJoins
SELECT * FROM abc LEFT JOIN xyz ON c=x
----
----
================================================================================
GenerateLookupJoins
================================================================================
Source expression:
  left-join
   ├── columns: a:1(int) b:2(int) c:3(int) x:5(int) y:6(int) z:7(int)
   ├── scan abc
   │    └── columns: a:1(int) b:2(int) c:3(int)
   ├── scan xyz
   │    └── columns: x:5(int) y:6(int) z:7(int)
   └── filters
        └── c = x [type=bool, outer=(3,5), constraints=(/3: (/NULL - ]; /5: (/NULL - ]), fd=(3)==(5), (5)==(3)]

New expression 1 of 1:
  left-join (lookup xyz@xy)
   ├── columns: a:1(int) b:2(int) c:3(int) x:5(int) y:6(int) z:7(int)
   ├── key columns: [3] = [5]
   ├── scan abc
   │    └── columns: a:1(int) b:2(int) c:3(int)
   └── filters (true)
----
----

# Verify we don't generate a lookup join.
exploretrace rule=GenerateLookupJoins
SELECT * FROM abc RIGHT JOIN xyz ON c=x
----
----
================================================================================
GenerateLookupJoins
================================================================================
Source expression:
  left-join
   ├── columns: a:1(int) b:2(int) c:3(int) x:5(int) y:6(int) z:7(int)
   ├── scan xyz
   │    └── columns: x:5(int) y:6(int) z:7(int)
   ├── scan abc
   │    └── columns: a:1(int) b:2(int) c:3(int)
   └── filters
        └── c = x [type=bool, outer=(3,5), constraints=(/3: (/NULL - ]; /5: (/NULL - ]), fd=(3)==(5), (5)==(3)]

No new expressions.
----
----

# Verify we don't generate lookup joins if there is a hint that says otherwise.
memo
SELECT a,b,n,m FROM small INNER HASH JOIN abcd ON a=m
----
memo (optimized, ~7KB, required=[presentation: a:4,b:5,n:2,m:1])
 ├── G1: (inner-join G2 G3 G4)
 │    └── [presentation: a:4,b:5,n:2,m:1]
 │         ├── best: (inner-join G2 G3 G4)
 │         └── cost: 1079.17
 ├── G2: (scan small,cols=(1,2))
 │    └── []
 │         ├── best: (scan small,cols=(1,2))
 │         └── cost: 10.52
 ├── G3: (scan abcd,cols=(4,5)) (scan abcd@secondary,cols=(4,5))
 │    └── []
 │         ├── best: (scan abcd@secondary,cols=(4,5))
 │         └── cost: 1050.02
 ├── G4: (filters G5)
 ├── G5: (eq G6 G7)
 ├── G6: (variable a)
 └── G7: (variable m)

# --------------------------------------------------
# GenerateLookupJoinsWithFilter
# --------------------------------------------------
# 
# The rule and cases are similar to GenerateLookupJoins, except that we have a
# filter that was pushed down to the lookup side (which needs to be pulled back
# into the ON condition).

# Covering case.
opt
SELECT a,b,n,m FROM small JOIN abcd ON a=m AND b>1
----
inner-join (lookup abcd@secondary)
 ├── columns: a:4(int!null) b:5(int!null) n:2(int) m:1(int!null)
 ├── key columns: [1] = [4]
 ├── fd: (1)==(4), (4)==(1)
 ├── scan small
 │    └── columns: m:1(int) n:2(int)
 └── filters
      └── b > 1 [type=bool, outer=(5), constraints=(/5: [/2 - ]; tight)]

# Covering case, left-join.
opt
SELECT a,b,n,m FROM small LEFT JOIN abcd ON a=m AND b>1
----
left-join (lookup abcd@secondary)
 ├── columns: a:4(int) b:5(int) n:2(int) m:1(int)
 ├── key columns: [1] = [4]
 ├── scan small
 │    └── columns: m:1(int) n:2(int)
 └── filters
      └── b > 1 [type=bool, outer=(5), constraints=(/5: [/2 - ]; tight)]

# Non-covering case.
opt
SELECT * FROM small JOIN abcd ON a=m AND b>1
----
inner-join (lookup abcd)
 ├── columns: m:1(int!null) n:2(int) a:4(int!null) b:5(int!null) c:6(int)
 ├── key columns: [7] = [7]
 ├── fd: (1)==(4), (4)==(1)
 ├── inner-join (lookup abcd@secondary)
 │    ├── columns: m:1(int!null) n:2(int) a:4(int!null) b:5(int!null) abcd.rowid:7(int!null)
 │    ├── key columns: [1] = [4]
 │    ├── fd: (7)-->(4,5), (1)==(4), (4)==(1)
 │    ├── scan small
 │    │    └── columns: m:1(int) n:2(int)
 │    └── filters
 │         └── b > 1 [type=bool, outer=(5), constraints=(/5: [/2 - ]; tight)]
 └── filters (true)

# Non-covering case, left join.
opt
SELECT * FROM small LEFT JOIN abcd ON a=m AND b>1
----
left-join (lookup abcd)
 ├── columns: m:1(int) n:2(int) a:4(int) b:5(int) c:6(int)
 ├── key columns: [7] = [7]
 ├── left-join (lookup abcd@secondary)
 │    ├── columns: m:1(int) n:2(int) a:4(int) b:5(int) abcd.rowid:7(int)
 │    ├── key columns: [1] = [4]
 │    ├── fd: (7)-->(4,5)
 │    ├── scan small
 │    │    └── columns: m:1(int) n:2(int)
 │    └── filters
 │         └── b > 1 [type=bool, outer=(5), constraints=(/5: [/2 - ]; tight)]
 └── filters (true)

# Non-covering case, extra filter bound by index.
opt
SELECT * FROM small JOIN abcd ON a=m AND b>n AND b>1
----
inner-join (lookup abcd)
 ├── columns: m:1(int!null) n:2(int!null) a:4(int!null) b:5(int!null) c:6(int)
 ├── key columns: [7] = [7]
 ├── fd: (1)==(4), (4)==(1)
 ├── inner-join (lookup abcd@secondary)
 │    ├── columns: m:1(int!null) n:2(int!null) a:4(int!null) b:5(int!null) abcd.rowid:7(int!null)
 │    ├── key columns: [1] = [4]
 │    ├── fd: (7)-->(4,5), (1)==(4), (4)==(1)
 │    ├── scan small
 │    │    └── columns: m:1(int) n:2(int)
 │    └── filters
 │         ├── b > n [type=bool, outer=(2,5), constraints=(/2: (/NULL - ]; /5: (/NULL - ])]
 │         └── b > 1 [type=bool, outer=(5), constraints=(/5: [/2 - ]; tight)]
 └── filters (true)

# Non-covering case, extra filter bound by index, left join.
opt
SELECT * FROM small LEFT JOIN abcd ON a=m AND b>n AND b>1
----
left-join (lookup abcd)
 ├── columns: m:1(int) n:2(int) a:4(int) b:5(int) c:6(int)
 ├── key columns: [7] = [7]
 ├── left-join (lookup abcd@secondary)
 │    ├── columns: m:1(int) n:2(int) a:4(int) b:5(int) abcd.rowid:7(int)
 │    ├── key columns: [1] = [4]
 │    ├── fd: (7)-->(4,5)
 │    ├── scan small
 │    │    └── columns: m:1(int) n:2(int)
 │    └── filters
 │         ├── b > n [type=bool, outer=(2,5), constraints=(/2: (/NULL - ]; /5: (/NULL - ])]
 │         └── b > 1 [type=bool, outer=(5), constraints=(/5: [/2 - ]; tight)]
 └── filters (true)

# Non-covering case, extra filter not bound by index.
opt
SELECT * FROM small JOIN abcd ON a=m AND c>n AND b>1
----
inner-join (lookup abcd)
 ├── columns: m:1(int!null) n:2(int!null) a:4(int!null) b:5(int!null) c:6(int!null)
 ├── key columns: [7] = [7]
 ├── fd: (1)==(4), (4)==(1)
 ├── inner-join (lookup abcd@secondary)
 │    ├── columns: m:1(int!null) n:2(int) a:4(int!null) b:5(int!null) abcd.rowid:7(int!null)
 │    ├── key columns: [1] = [4]
 │    ├── fd: (7)-->(4,5), (1)==(4), (4)==(1)
 │    ├── scan small
 │    │    └── columns: m:1(int) n:2(int)
 │    └── filters
 │         └── b > 1 [type=bool, outer=(5), constraints=(/5: [/2 - ]; tight)]
 └── filters
      └── c > n [type=bool, outer=(2,6), constraints=(/2: (/NULL - ]; /6: (/NULL - ])]

# Non-covering case, extra filter not bound by index, left join.
# In this case, we can't yet convert to a lookup join (see
# the GenerateLookupJoins custom func).
opt
SELECT * FROM small LEFT JOIN abcd ON a=m AND c>n AND b>1
----
right-join
 ├── columns: m:1(int) n:2(int) a:4(int) b:5(int) c:6(int)
 ├── select
 │    ├── columns: a:4(int) b:5(int!null) c:6(int)
 │    ├── scan abcd
 │    │    └── columns: a:4(int) b:5(int) c:6(int)
 │    └── filters
 │         └── b > 1 [type=bool, outer=(5), constraints=(/5: [/2 - ]; tight)]
 ├── scan small
 │    └── columns: m:1(int) n:2(int)
 └── filters
      ├── a = m [type=bool, outer=(1,4), constraints=(/1: (/NULL - ]; /4: (/NULL - ]), fd=(1)==(4), (4)==(1)]
      └── c > n [type=bool, outer=(2,6), constraints=(/2: (/NULL - ]; /6: (/NULL - ])]

# --------------------------
# GenerateZigzagJoins
# --------------------------

# Simple zigzag case - where all requested columns are in the indexes being
# joined.
opt
SELECT q,r FROM pqr WHERE q = 1 AND r = 2
----
inner-join (zigzag pqr@q pqr@r)
 ├── columns: q:2(int!null) r:3(int!null)
 ├── eq columns: [1] = [1]
 ├── left fixed columns: [2] = [1]
 ├── right fixed columns: [3] = [2]
 ├── fd: ()-->(2,3)
 └── filters
      ├── q = 1 [type=bool, outer=(2), constraints=(/2: [/1 - /1]; tight), fd=()-->(2)]
      └── r = 2 [type=bool, outer=(3), constraints=(/3: [/2 - /2]; tight), fd=()-->(3)]

opt
SELECT q,r FROM pqr WHERE q = 1 AND r IS NULL
----
inner-join (zigzag pqr@q pqr@r)
 ├── columns: q:2(int!null) r:3(int)
 ├── eq columns: [1] = [1]
 ├── left fixed columns: [2] = [1]
 ├── right fixed columns: [3] = [NULL]
 ├── fd: ()-->(2,3)
 └── filters
      ├── q = 1 [type=bool, outer=(2), constraints=(/2: [/1 - /1]; tight), fd=()-->(2)]
      └── r IS NULL [type=bool, outer=(3), constraints=(/3: [/NULL - /NULL]; tight), fd=()-->(3)]

memo
SELECT q,r FROM pqr WHERE q = 1 AND r = 2
----
memo (optimized, ~12KB, required=[presentation: q:2,r:3])
 ├── G1: (select G2 G3) (zigzag-join G3 pqr@q pqr@r) (select G4 G5) (select G6 G7) (select G8 G7)
 │    └── [presentation: q:2,r:3]
 │         ├── best: (zigzag-join G3 pqr@q pqr@r)
 │         └── cost: 0.21
 ├── G2: (scan pqr,cols=(2,3))
 │    └── []
 │         ├── best: (scan pqr,cols=(2,3))
 │         └── cost: 1070.02
 ├── G3: (filters G9 G10)
 ├── G4: (index-join G11 pqr,cols=(2,3))
 │    └── []
 │         ├── best: (index-join G11 pqr,cols=(2,3))
 │         └── cost: 50.71
 ├── G5: (filters G10)
 ├── G6: (index-join G12 pqr,cols=(2,3))
 │    └── []
 │         ├── best: (index-join G12 pqr,cols=(2,3))
 │         └── cost: 50.71
 ├── G7: (filters G9)
 ├── G8: (index-join G13 pqr,cols=(2,3))
 │    └── []
 │         ├── best: (index-join G13 pqr,cols=(2,3))
 │         └── cost: 50.81
 ├── G9: (eq G14 G15)
 ├── G10: (eq G16 G17)
 ├── G11: (scan pqr@q,cols=(1,2),constrained)
 │    └── []
 │         ├── best: (scan pqr@q,cols=(1,2),constrained)
 │         └── cost: 10.31
 ├── G12: (scan pqr@r,cols=(1,3),constrained)
 │    └── []
 │         ├── best: (scan pqr@r,cols=(1,3),constrained)
 │         └── cost: 10.31
 ├── G13: (scan pqr@rs,cols=(1,3),constrained)
 │    └── []
 │         ├── best: (scan pqr@rs,cols=(1,3),constrained)
 │         └── cost: 10.41
 ├── G14: (variable q)
 ├── G15: (const 1)
 ├── G16: (variable r)
 └── G17: (const 2)

# Nested zigzag case - zigzag join needs to be wrapped in a lookup join to
# satisfy required columns.
opt
SELECT q,r,s FROM pqr WHERE q = 1 AND r = 2
----
inner-join (lookup pqr)
 ├── columns: q:2(int!null) r:3(int!null) s:4(string)
 ├── key columns: [1] = [1]
 ├── fd: ()-->(2,3)
 ├── inner-join (zigzag pqr@q pqr@r)
 │    ├── columns: p:1(int!null) q:2(int!null) r:3(int!null)
 │    ├── eq columns: [1] = [1]
 │    ├── left fixed columns: [2] = [1]
 │    ├── right fixed columns: [3] = [2]
 │    ├── fd: ()-->(2,3)
 │    └── filters
 │         ├── q = 1 [type=bool, outer=(2), constraints=(/2: [/1 - /1]; tight), fd=()-->(2)]
 │         └── r = 2 [type=bool, outer=(3), constraints=(/3: [/2 - /2]; tight), fd=()-->(3)]
 └── filters (true)

memo
SELECT q,r,s FROM pqr WHERE q = 1 AND r = 2
----
memo (optimized, ~14KB, required=[presentation: q:2,r:3,s:4])
 ├── G1: (select G2 G3) (lookup-join G4 G5 pqr,keyCols=[1],outCols=(2-4)) (select G6 G7) (select G8 G9) (select G10 G9)
 │    └── [presentation: q:2,r:3,s:4]
 │         ├── best: (lookup-join G4 G5 pqr,keyCols=[1],outCols=(2-4))
 │         └── cost: 0.82
 ├── G2: (scan pqr,cols=(2-4))
 │    └── []
 │         ├── best: (scan pqr,cols=(2-4))
 │         └── cost: 1080.02
 ├── G3: (filters G11 G12)
 ├── G4: (zigzag-join G3 pqr@q pqr@r)
 │    └── []
 │         ├── best: (zigzag-join G3 pqr@q pqr@r)
 │         └── cost: 0.21
 ├── G5: (filters)
 ├── G6: (index-join G13 pqr,cols=(2-4))
 │    └── []
 │         ├── best: (index-join G13 pqr,cols=(2-4))
 │         └── cost: 50.81
 ├── G7: (filters G12)
 ├── G8: (index-join G14 pqr,cols=(2-4))
 │    └── []
 │         ├── best: (index-join G14 pqr,cols=(2-4))
 │         └── cost: 50.81
 ├── G9: (filters G11)
 ├── G10: (index-join G15 pqr,cols=(2-4))
 │    └── []
 │         ├── best: (index-join G15 pqr,cols=(2-4))
 │         └── cost: 51.00
 ├── G11: (eq G16 G17)
 ├── G12: (eq G18 G19)
 ├── G13: (scan pqr@q,cols=(1,2),constrained)
 │    └── []
 │         ├── best: (scan pqr@q,cols=(1,2),constrained)
 │         └── cost: 10.31
 ├── G14: (scan pqr@r,cols=(1,3),constrained)
 │    └── []
 │         ├── best: (scan pqr@r,cols=(1,3),constrained)
 │         └── cost: 10.31
 ├── G15: (scan pqr@rs,cols=(1,3,4),constrained)
 │    └── []
 │         ├── best: (scan pqr@rs,cols=(1,3,4),constrained)
 │         └── cost: 10.50
 ├── G16: (variable q)
 ├── G17: (const 1)
 ├── G18: (variable r)
 └── G19: (const 2)

# Zigzag with fixed columns of different types.
opt
SELECT q,s FROM pqr WHERE q = 1 AND s = 'foo'
----
inner-join (zigzag pqr@q pqr@s)
 ├── columns: q:2(int!null) s:4(string!null)
 ├── eq columns: [1] = [1]
 ├── left fixed columns: [2] = [1]
 ├── right fixed columns: [4] = ['foo']
 ├── fd: ()-->(2,4)
 └── filters
      ├── q = 1 [type=bool, outer=(2), constraints=(/2: [/1 - /1]; tight), fd=()-->(2)]
      └── s = 'foo' [type=bool, outer=(4), constraints=(/4: [/'foo' - /'foo']; tight), fd=()-->(4)]

memo
SELECT q,s FROM pqr WHERE q = 1 AND s = 'foo'
----
memo (optimized, ~10KB, required=[presentation: q:2,s:4])
 ├── G1: (select G2 G3) (zigzag-join G3 pqr@q pqr@s) (select G4 G5) (select G6 G7)
 │    └── [presentation: q:2,s:4]
 │         ├── best: (zigzag-join G3 pqr@q pqr@s)
 │         └── cost: 0.21
 ├── G2: (scan pqr,cols=(2,4))
 │    └── []
 │         ├── best: (scan pqr,cols=(2,4))
 │         └── cost: 1070.02
 ├── G3: (filters G8 G9)
 ├── G4: (index-join G10 pqr,cols=(2,4))
 │    └── []
 │         ├── best: (index-join G10 pqr,cols=(2,4))
 │         └── cost: 50.71
 ├── G5: (filters G9)
 ├── G6: (index-join G11 pqr,cols=(2,4))
 │    └── []
 │         ├── best: (index-join G11 pqr,cols=(2,4))
 │         └── cost: 50.81
 ├── G7: (filters G8)
 ├── G8: (eq G12 G13)
 ├── G9: (eq G14 G15)
 ├── G10: (scan pqr@q,cols=(1,2),constrained)
 │    └── []
 │         ├── best: (scan pqr@q,cols=(1,2),constrained)
 │         └── cost: 10.31
 ├── G11: (scan pqr@s,cols=(1,4),constrained)
 │    └── []
 │         ├── best: (scan pqr@s,cols=(1,4),constrained)
 │         └── cost: 10.41
 ├── G12: (variable q)
 ├── G13: (const 1)
 ├── G14: (variable s)
 └── G15: (const 'foo')

# Zigzag with implicit equality column in addition to primary key:
# indexes on (r,s) and (t,s) should be chosen even though s is not being fixed
# in the ON clause.
opt
SELECT r,t FROM pqr WHERE r = 1 AND t = 'foo'
----
inner-join (zigzag pqr@rs pqr@ts)
 ├── columns: r:3(int!null) t:5(string!null)
 ├── eq columns: [4 1] = [4 1]
 ├── left fixed columns: [3] = [1]
 ├── right fixed columns: [5] = ['foo']
 ├── fd: ()-->(3,5)
 └── filters
      ├── r = 1 [type=bool, outer=(3), constraints=(/3: [/1 - /1]; tight), fd=()-->(3)]
      └── t = 'foo' [type=bool, outer=(5), constraints=(/5: [/'foo' - /'foo']; tight), fd=()-->(5)]

memo
SELECT r,t FROM pqr WHERE r = 1 AND t = 'foo'
----
memo (optimized, ~12KB, required=[presentation: r:3,t:5])
 ├── G1: (select G2 G3) (zigzag-join G3 pqr@rs pqr@ts) (select G4 G5) (select G6 G5) (select G7 G8)
 │    └── [presentation: r:3,t:5]
 │         ├── best: (zigzag-join G3 pqr@rs pqr@ts)
 │         └── cost: 0.22
 ├── G2: (scan pqr,cols=(3,5))
 │    └── []
 │         ├── best: (scan pqr,cols=(3,5))
 │         └── cost: 1070.02
 ├── G3: (filters G9 G10)
 ├── G4: (index-join G11 pqr,cols=(3,5))
 │    └── []
 │         ├── best: (index-join G11 pqr,cols=(3,5))
 │         └── cost: 50.71
 ├── G5: (filters G10)
 ├── G6: (index-join G12 pqr,cols=(3,5))
 │    └── []
 │         ├── best: (index-join G12 pqr,cols=(3,5))
 │         └── cost: 50.81
 ├── G7: (index-join G13 pqr,cols=(3,5))
 │    └── []
 │         ├── best: (index-join G13 pqr,cols=(3,5))
 │         └── cost: 50.81
 ├── G8: (filters G9)
 ├── G9: (eq G14 G15)
 ├── G10: (eq G16 G17)
 ├── G11: (scan pqr@r,cols=(1,3),constrained)
 │    └── []
 │         ├── best: (scan pqr@r,cols=(1,3),constrained)
 │         └── cost: 10.31
 ├── G12: (scan pqr@rs,cols=(1,3),constrained)
 │    └── []
 │         ├── best: (scan pqr@rs,cols=(1,3),constrained)
 │         └── cost: 10.41
 ├── G13: (scan pqr@ts,cols=(1,5),constrained)
 │    └── []
 │         ├── best: (scan pqr@ts,cols=(1,5),constrained)
 │         └── cost: 10.41
 ├── G14: (variable r)
 ├── G15: (const 1)
 ├── G16: (variable t)
 └── G17: (const 'foo')

# Zigzag with choice between indexes for multiple equality predicates.
opt
SELECT p,q,r,s FROM pqr WHERE q = 1 AND r = 1 AND s = 'foo'
----
inner-join (zigzag pqr@q pqr@s)
 ├── columns: p:1(int!null) q:2(int!null) r:3(int!null) s:4(string!null)
 ├── eq columns: [1] = [1]
 ├── left fixed columns: [2] = [1]
 ├── right fixed columns: [4] = ['foo']
 ├── key: (1)
 ├── fd: ()-->(2-4)
 └── filters
      ├── q = 1 [type=bool, outer=(2), constraints=(/2: [/1 - /1]; tight), fd=()-->(2)]
      ├── r = 1 [type=bool, outer=(3), constraints=(/3: [/1 - /1]; tight), fd=()-->(3)]
      └── s = 'foo' [type=bool, outer=(4), constraints=(/4: [/'foo' - /'foo']; tight), fd=()-->(4)]

memo
SELECT p,q,r,s FROM pqr WHERE q = 1 AND r = 1 AND s = 'foo'
----
memo (optimized, ~32KB, required=[presentation: p:1,q:2,r:3,s:4])
 ├── G1: (select G2 G3) (lookup-join G4 G5 pqr,keyCols=[1],outCols=(1-4)) (zigzag-join G3 pqr@q pqr@s) (zigzag-join G3 pqr@q pqr@rs) (lookup-join G6 G7 pqr,keyCols=[1],outCols=(1-4)) (lookup-join G8 G7 pqr,keyCols=[1],outCols=(1-4)) (lookup-join G9 G7 pqr,keyCols=[1],outCols=(1-4)) (select G10 G11) (select G12 G13) (select G14 G7) (select G15 G7)
 │    └── [presentation: p:1,q:2,r:3,s:4]
 │         ├── best: (zigzag-join G3 pqr@q pqr@s)
 │         └── cost: 0.01
 ├── G2: (scan pqr,cols=(1-4))
 │    └── []
 │         ├── best: (scan pqr,cols=(1-4))
 │         └── cost: 1090.02
 ├── G3: (filters G16 G17 G18)
 ├── G4: (zigzag-join G19 pqr@q pqr@r)
 │    └── []
 │         ├── best: (zigzag-join G19 pqr@q pqr@r)
 │         └── cost: 0.21
 ├── G5: (filters G18)
 ├── G6: (zigzag-join G11 pqr@r pqr@s)
 │    └── []
 │         ├── best: (zigzag-join G11 pqr@r pqr@s)
 │         └── cost: 0.22
 ├── G7: (filters G16)
 ├── G8: (zigzag-join G11 pqr@r pqr@rs)
 │    └── []
 │         ├── best: (zigzag-join G11 pqr@r pqr@rs)
 │         └── cost: 0.22
 ├── G9: (zigzag-join G11 pqr@s pqr@rs)
 │    └── []
 │         ├── best: (zigzag-join G11 pqr@s pqr@rs)
 │         └── cost: 0.22
 ├── G10: (index-join G20 pqr,cols=(1-4))
 │    └── []
 │         ├── best: (index-join G20 pqr,cols=(1-4))
 │         └── cost: 50.91
 ├── G11: (filters G17 G18)
 ├── G12: (index-join G21 pqr,cols=(1-4))
 │    └── []
 │         ├── best: (index-join G21 pqr,cols=(1-4))
 │         └── cost: 50.91
 ├── G13: (filters G16 G18)
 ├── G14: (index-join G22 pqr,cols=(1-4))
 │    └── []
 │         ├── best: (index-join G22 pqr,cols=(1-4))
 │         └── cost: 14.87
 ├── G15: (index-join G23 pqr,cols=(1-4))
 │    └── []
 │         ├── best: (index-join G23 pqr,cols=(1-4))
 │         └── cost: 0.53
 ├── G16: (eq G24 G25)
 ├── G17: (eq G26 G25)
 ├── G18: (eq G27 G28)
 ├── G19: (filters G16 G17)
 ├── G20: (scan pqr@q,cols=(1,2),constrained)
 │    └── []
 │         ├── best: (scan pqr@q,cols=(1,2),constrained)
 │         └── cost: 10.31
 ├── G21: (scan pqr@r,cols=(1,3),constrained)
 │    └── []
 │         ├── best: (scan pqr@r,cols=(1,3),constrained)
 │         └── cost: 10.31
 ├── G22: (select G29 G30)
 │    └── []
 │         ├── best: (select G29 G30)
 │         └── cost: 10.61
 ├── G23: (scan pqr@rs,cols=(1,3,4),constrained)
 │    └── []
 │         ├── best: (scan pqr@rs,cols=(1,3,4),constrained)
 │         └── cost: 0.11
 ├── G24: (variable q)
 ├── G25: (const 1)
 ├── G26: (variable r)
 ├── G27: (variable s)
 ├── G28: (const 'foo')
 ├── G29: (scan pqr@s,cols=(1,3,4),constrained)
 │    └── []
 │         ├── best: (scan pqr@s,cols=(1,3,4),constrained)
 │         └── cost: 10.50
 └── G30: (filters G17)

# Zigzag joins cannot be planned for indexes where equality columns do not
# immediately follow fixed columns. Here, the only index on t is (t,s,p) and
# s is not a fixed or equal column, so a zigzag join shouldn't be planned.
opt
SELECT q,t FROM pqr WHERE q = 1 AND t = 'foo'
----
select
 ├── columns: q:2(int!null) t:5(string!null)
 ├── fd: ()-->(2,5)
 ├── index-join pqr
 │    ├── columns: q:2(int) t:5(string)
 │    ├── fd: ()-->(2)
 │    └── scan pqr@q
 │         ├── columns: p:1(int!null) q:2(int!null)
 │         ├── constraint: /2/1: [/1 - /1]
 │         ├── key: (1)
 │         └── fd: ()-->(2)
 └── filters
      └── t = 'foo' [type=bool, outer=(5), constraints=(/5: [/'foo' - /'foo']; tight), fd=()-->(5)]

memo
SELECT q,t FROM pqr WHERE q = 1 AND t = 'foo'
----
memo (optimized, ~8KB, required=[presentation: q:2,t:5])
 ├── G1: (select G2 G3) (select G4 G5) (select G6 G7)
 │    └── [presentation: q:2,t:5]
 │         ├── best: (select G4 G5)
 │         └── cost: 50.82
 ├── G2: (scan pqr,cols=(2,5))
 │    └── []
 │         ├── best: (scan pqr,cols=(2,5))
 │         └── cost: 1070.02
 ├── G3: (filters G8 G9)
 ├── G4: (index-join G10 pqr,cols=(2,5))
 │    └── []
 │         ├── best: (index-join G10 pqr,cols=(2,5))
 │         └── cost: 50.71
 ├── G5: (filters G9)
 ├── G6: (index-join G11 pqr,cols=(2,5))
 │    └── []
 │         ├── best: (index-join G11 pqr,cols=(2,5))
 │         └── cost: 50.81
 ├── G7: (filters G8)
 ├── G8: (eq G12 G13)
 ├── G9: (eq G14 G15)
 ├── G10: (scan pqr@q,cols=(1,2),constrained)
 │    └── []
 │         ├── best: (scan pqr@q,cols=(1,2),constrained)
 │         └── cost: 10.31
 ├── G11: (scan pqr@ts,cols=(1,5),constrained)
 │    └── []
 │         ├── best: (scan pqr@ts,cols=(1,5),constrained)
 │         └── cost: 10.41
 ├── G12: (variable q)
 ├── G13: (const 1)
 ├── G14: (variable t)
 └── G15: (const 'foo')

# --------------------------
# GenerateInvertedIndexZigzagJoins
# --------------------------

exec-ddl
CREATE TABLE t5 (
    a INT PRIMARY KEY,
    b JSONB,
    c INT,
    INVERTED INDEX b_idx(b)
)
----
TABLE t5
 ├── a int not null
 ├── b jsonb
 ├── c int
 ├── INDEX primary
 │    └── a int not null
 └── INVERTED INDEX b_idx
      ├── b jsonb
      └── a int not null

# One path. Should generate a scan constrained on the inverted index.
opt
SELECT b,a FROM t5 WHERE b @> '{"a":1}'
----
index-join t5
 ├── columns: b:2(jsonb) a:1(int!null)
 ├── key: (1)
 ├── fd: (1)-->(2)
 └── scan t5@b_idx
      ├── columns: a:1(int!null)
      ├── constraint: /2/1: [/'{"a": 1}' - /'{"a": 1}']
      └── key: (1)

opt
SELECT b,a FROM t5 WHERE b @> '{"a":[[{"b":{"c":[{"d":"e"}]}}]]}'
----
index-join t5
 ├── columns: b:2(jsonb) a:1(int!null)
 ├── key: (1)
 ├── fd: (1)-->(2)
 └── scan t5@b_idx
      ├── columns: a:1(int!null)
      ├── constraint: /2/1: [/'{"a": [[{"b": {"c": [{"d": "e"}]}}]]}' - /'{"a": [[{"b": {"c": [{"d": "e"}]}}]]}']
      └── key: (1)

# Two paths. Should generate a zigzag join.
opt
SELECT b,a FROM t5 WHERE b @> '{"a":1, "c":2}'
----
inner-join (lookup t5)
 ├── columns: b:2(jsonb) a:1(int!null)
 ├── key columns: [1] = [1]
 ├── key: (1)
 ├── fd: (1)-->(2)
 ├── inner-join (zigzag t5@b_idx t5@b_idx)
 │    ├── columns: a:1(int!null)
 │    ├── eq columns: [1] = [1]
 │    ├── left fixed columns: [2] = ['{"a": 1}']
 │    ├── right fixed columns: [2] = ['{"c": 2}']
 │    └── filters (true)
 └── filters
      └── b @> '{"a": 1, "c": 2}' [type=bool, outer=(2)]

memo
SELECT a FROM t5 WHERE b @> '{"a":1, "c":2}'
----
memo (optimized, ~11KB, required=[presentation: a:1])
 ├── G1: (project G2 G3 a)
 │    └── [presentation: a:1]
 │         ├── best: (project G2 G3 a)
 │         └── cost: 100.40
 ├── G2: (select G4 G5) (lookup-join G6 G5 t5,keyCols=[1],outCols=(1,2)) (select G7 G5)
 │    └── []
 │         ├── best: (lookup-join G6 G5 t5,keyCols=[1],outCols=(1,2))
 │         └── cost: 100.27
 ├── G3: (projections)
 ├── G4: (scan t5,cols=(1,2))
 │    └── []
 │         ├── best: (scan t5,cols=(1,2))
 │         └── cost: 1050.02
 ├── G5: (filters G8)
 ├── G6: (zigzag-join G9 t5@b_idx t5@b_idx)
 │    └── []
 │         ├── best: (zigzag-join G9 t5@b_idx t5@b_idx)
 │         └── cost: 25.57
 ├── G7: (index-join G10 t5,cols=(1,2))
 │    └── []
 │         ├── best: (index-join G10 t5,cols=(1,2))
 │         └── cost: 565.58
 ├── G8: (contains G11 G12)
 ├── G9: (filters)
 ├── G10: (scan t5@b_idx,cols=(1),constrained)
 │    └── []
 │         ├── best: (scan t5@b_idx,cols=(1),constrained)
 │         └── cost: 114.45
 ├── G11: (variable b)
 └── G12: (const '{"a": 1, "c": 2}')

# Three or more paths. Should generate zigzag joins.
opt
SELECT b,a FROM t5 WHERE b @> '{"a":[{"b":"c", "d":3}, 5]}'
----
inner-join (lookup t5)
 ├── columns: b:2(jsonb) a:1(int!null)
 ├── key columns: [1] = [1]
 ├── key: (1)
 ├── fd: (1)-->(2)
 ├── inner-join (zigzag t5@b_idx t5@b_idx)
 │    ├── columns: a:1(int!null)
 │    ├── eq columns: [1] = [1]
 │    ├── left fixed columns: [2] = ['{"a": [{"b": "c"}]}']
 │    ├── right fixed columns: [2] = ['{"a": [{"d": 3}]}']
 │    └── filters (true)
 └── filters
      └── b @> '{"a": [{"b": "c", "d": 3}, 5]}' [type=bool, outer=(2)]

# Regression test for issue where zero-column expressions could exist multiple
# times in the tree, causing collisions.
opt
SELECT 1 FROM (VALUES (1), (1)) JOIN (VALUES (1), (1), (1)) ON true
UNION ALL
SELECT 1 FROM (VALUES (1), (1), (1)) JOIN (VALUES (1), (1)) ON true
----
union-all
 ├── columns: "?column?":7(int!null)
 ├── left columns: "?column?":3(int)
 ├── right columns: "?column?":6(int)
 ├── cardinality: [12 - 12]
 ├── project
 │    ├── columns: "?column?":3(int!null)
 │    ├── cardinality: [6 - 6]
 │    ├── fd: ()-->(3)
 │    ├── inner-join
 │    │    ├── cardinality: [6 - 6]
 │    │    ├── values
 │    │    │    ├── cardinality: [3 - 3]
 │    │    │    ├── tuple [type=tuple{}]
 │    │    │    ├── tuple [type=tuple{}]
 │    │    │    └── tuple [type=tuple{}]
 │    │    ├── values
 │    │    │    ├── cardinality: [2 - 2]
 │    │    │    ├── tuple [type=tuple{}]
 │    │    │    └── tuple [type=tuple{}]
 │    │    └── filters (true)
 │    └── projections
 │         └── const: 1 [type=int]
 └── project
      ├── columns: "?column?":6(int!null)
      ├── cardinality: [6 - 6]
      ├── fd: ()-->(6)
      ├── inner-join
      │    ├── cardinality: [6 - 6]
      │    ├── values
      │    │    ├── cardinality: [3 - 3]
      │    │    ├── tuple [type=tuple{}]
      │    │    ├── tuple [type=tuple{}]
      │    │    └── tuple [type=tuple{}]
      │    ├── values
      │    │    ├── cardinality: [2 - 2]
      │    │    ├── tuple [type=tuple{}]
      │    │    └── tuple [type=tuple{}]
      │    └── filters (true)
      └── projections
           └── const: 1 [type=int]

memo
SELECT 1 FROM (VALUES (1), (1)) JOIN (VALUES (1), (1), (1)) ON true
UNION ALL
SELECT 1 FROM (VALUES (1), (1), (1)) JOIN (VALUES (1), (1)) ON true
----
memo (optimized, ~17KB, required=[presentation: ?column?:7])
 ├── G1: (union-all G2 G3)
 │    └── [presentation: ?column?:7]
 │         ├── best: (union-all G2 G3)
 │         └── cost: 0.82
 ├── G2: (project G4 G5)
 │    └── []
 │         ├── best: (project G4 G5)
 │         └── cost: 0.34
 ├── G3: (project G6 G5)
 │    └── []
 │         ├── best: (project G6 G5)
 │         └── cost: 0.34
 ├── G4: (inner-join G7 G8 G9) (inner-join G8 G7 G9)
 │    └── []
 │         ├── best: (inner-join G8 G7 G9)
 │         └── cost: 0.21
 ├── G5: (projections G10)
 ├── G6: (inner-join G11 G12 G9) (inner-join G12 G11 G9)
 │    └── []
 │         ├── best: (inner-join G11 G12 G9)
 │         └── cost: 0.21
 ├── G7: (values G13 id=v1)
 │    └── []
 │         ├── best: (values G13 id=v1)
 │         └── cost: 0.03
 ├── G8: (values G14 id=v2)
 │    └── []
 │         ├── best: (values G14 id=v2)
 │         └── cost: 0.04
 ├── G9: (filters)
 ├── G10: (const 1)
 ├── G11: (values G14 id=v3)
 │    └── []
 │         ├── best: (values G14 id=v3)
 │         └── cost: 0.04
 ├── G12: (values G13 id=v4)
 │    └── []
 │         ├── best: (values G13 id=v4)
 │         └── cost: 0.03
 ├── G13: (scalar-list G15 G15)
 ├── G14: (scalar-list G15 G15 G15)
 ├── G15: (tuple G16)
 └── G16: (scalar-list)

opt join-limit=3
SELECT
    false
FROM
    abc AS x JOIN [INSERT INTO abc (a) SELECT 1 FROM abc RETURNING 1] JOIN abc AS y ON true ON false
----
project
 ├── columns: bool:21(bool!null)
 ├── cardinality: [0 - 0]
 ├── side-effects, mutations
 ├── fd: ()-->(21)
 ├── inner-join
 │    ├── columns: abc.a:5(int!null) abc.b:6(int) abc.c:7(int) abc.rowid:8(int!null)
 │    ├── cardinality: [0 - 0]
 │    ├── side-effects, mutations
 │    ├── fd: ()-->(5-7)
 │    ├── select
 │    │    ├── columns: abc.a:5(int!null) abc.b:6(int) abc.c:7(int) abc.rowid:8(int!null)
 │    │    ├── cardinality: [0 - 0]
 │    │    ├── side-effects, mutations
 │    │    ├── fd: ()-->(5-7)
 │    │    ├── insert abc
 │    │    │    ├── columns: abc.a:5(int!null) abc.b:6(int) abc.c:7(int) abc.rowid:8(int!null)
 │    │    │    ├── insert-mapping:
 │    │    │    │    ├──  "?column?":13 => abc.a:5
 │    │    │    │    ├──  column14:14 => abc.b:6
 │    │    │    │    ├──  column14:14 => abc.c:7
 │    │    │    │    └──  column15:15 => abc.rowid:8
 │    │    │    ├── side-effects, mutations
 │    │    │    ├── fd: ()-->(5-7)
 │    │    │    └── project
 │    │    │         ├── columns: column14:14(int) column15:15(int) "?column?":13(int!null)
 │    │    │         ├── side-effects
 │    │    │         ├── fd: ()-->(13,14)
 │    │    │         ├── scan abc
 │    │    │         └── projections
 │    │    │              ├── null [type=int]
 │    │    │              ├── unique_rowid() [type=int, side-effects]
 │    │    │              └── const: 1 [type=int]
 │    │    └── filters
 │    │         └── false [type=bool]
 │    ├── values
 │    │    ├── cardinality: [0 - 0]
 │    │    └── key: ()
 │    └── filters (true)
 └── projections
      └── false [type=bool]

opt join-limit=3
SELECT 1 FROM ((VALUES (1), (1)) JOIN ((VALUES (1), (1), (1)) JOIN (VALUES (1), (1), (1), (1)) ON true) ON true)
UNION ALL
SELECT 1 FROM ((VALUES (1), (1)) JOIN (VALUES (1), (1), (1)) ON true) JOIN (VALUES (1), (1), (1), (1)) ON true
----
union-all
 ├── columns: "?column?":9(int!null)
 ├── left columns: "?column?":4(int)
 ├── right columns: "?column?":8(int)
 ├── cardinality: [48 - 48]
 ├── project
 │    ├── columns: "?column?":4(int!null)
 │    ├── cardinality: [24 - 24]
 │    ├── fd: ()-->(4)
 │    ├── inner-join
 │    │    ├── cardinality: [24 - 24]
 │    │    ├── inner-join
 │    │    │    ├── cardinality: [6 - 6]
 │    │    │    ├── values
 │    │    │    │    ├── cardinality: [3 - 3]
 │    │    │    │    ├── tuple [type=tuple{}]
 │    │    │    │    ├── tuple [type=tuple{}]
 │    │    │    │    └── tuple [type=tuple{}]
 │    │    │    ├── values
 │    │    │    │    ├── cardinality: [2 - 2]
 │    │    │    │    ├── tuple [type=tuple{}]
 │    │    │    │    └── tuple [type=tuple{}]
 │    │    │    └── filters (true)
 │    │    ├── values
 │    │    │    ├── cardinality: [4 - 4]
 │    │    │    ├── tuple [type=tuple{}]
 │    │    │    ├── tuple [type=tuple{}]
 │    │    │    ├── tuple [type=tuple{}]
 │    │    │    └── tuple [type=tuple{}]
 │    │    └── filters (true)
 │    └── projections
 │         └── const: 1 [type=int]
 └── project
      ├── columns: "?column?":8(int!null)
      ├── cardinality: [24 - 24]
      ├── fd: ()-->(8)
      ├── inner-join
      │    ├── cardinality: [24 - 24]
      │    ├── inner-join
      │    │    ├── cardinality: [6 - 6]
      │    │    ├── values
      │    │    │    ├── cardinality: [3 - 3]
      │    │    │    ├── tuple [type=tuple{}]
      │    │    │    ├── tuple [type=tuple{}]
      │    │    │    └── tuple [type=tuple{}]
      │    │    ├── values
      │    │    │    ├── cardinality: [2 - 2]
      │    │    │    ├── tuple [type=tuple{}]
      │    │    │    └── tuple [type=tuple{}]
      │    │    └── filters (true)
      │    ├── values
      │    │    ├── cardinality: [4 - 4]
      │    │    ├── tuple [type=tuple{}]
      │    │    ├── tuple [type=tuple{}]
      │    │    ├── tuple [type=tuple{}]
      │    │    └── tuple [type=tuple{}]
      │    └── filters (true)
      └── projections
           └── const: 1 [type=int]

opt
SELECT 1 FROM (VALUES (1), (1)) LEFT JOIN (VALUES (1), (1), (1)) ON random() = 0
UNION ALL
SELECT 1 FROM (VALUES (1), (1), (1)) RIGHT JOIN (VALUES (1), (1)) ON random() = 0
----
union-all
 ├── columns: "?column?":7(int!null)
 ├── left columns: "?column?":3(int)
 ├── right columns: "?column?":6(int)
 ├── cardinality: [4 - 12]
 ├── side-effects
 ├── project
 │    ├── columns: "?column?":3(int!null)
 │    ├── cardinality: [2 - 6]
 │    ├── side-effects
 │    ├── fd: ()-->(3)
 │    ├── left-join
 │    │    ├── cardinality: [2 - 6]
 │    │    ├── side-effects
 │    │    ├── values
 │    │    │    ├── cardinality: [2 - 2]
 │    │    │    ├── tuple [type=tuple{}]
 │    │    │    └── tuple [type=tuple{}]
 │    │    ├── select
 │    │    │    ├── cardinality: [0 - 3]
 │    │    │    ├── side-effects
 │    │    │    ├── values
 │    │    │    │    ├── cardinality: [3 - 3]
 │    │    │    │    ├── tuple [type=tuple{}]
 │    │    │    │    ├── tuple [type=tuple{}]
 │    │    │    │    └── tuple [type=tuple{}]
 │    │    │    └── filters
 │    │    │         └── random() = 0.0 [type=bool, side-effects]
 │    │    └── filters (true)
 │    └── projections
 │         └── const: 1 [type=int]
 └── project
      ├── columns: "?column?":6(int!null)
      ├── cardinality: [2 - 6]
      ├── side-effects
      ├── fd: ()-->(6)
      ├── left-join
      │    ├── cardinality: [2 - 6]
      │    ├── side-effects
      │    ├── values
      │    │    ├── cardinality: [2 - 2]
      │    │    ├── tuple [type=tuple{}]
      │    │    └── tuple [type=tuple{}]
      │    ├── select
      │    │    ├── cardinality: [0 - 3]
      │    │    ├── side-effects
      │    │    ├── values
      │    │    │    ├── cardinality: [3 - 3]
      │    │    │    ├── tuple [type=tuple{}]
      │    │    │    ├── tuple [type=tuple{}]
      │    │    │    └── tuple [type=tuple{}]
      │    │    └── filters
      │    │         └── random() = 0.0 [type=bool, side-effects]
      │    └── filters (true)
      └── projections
           └── const: 1 [type=int]
