exec-ddl
CREATE TABLE a (k INT PRIMARY KEY, i INT, f FLOAT, s STRING, j JSON)
----
TABLE a
 ├── k int not null
 ├── i int
 ├── f float
 ├── s string
 ├── j jsonb
 └── INDEX primary
      └── k int not null

exec-ddl
CREATE TABLE t.b (x INT PRIMARY KEY, y INT)
----
TABLE b
 ├── x int not null
 ├── y int
 └── INDEX primary
      └── x int not null

# --------------------------------------------------
# EliminateLimit
# --------------------------------------------------
opt expect=EliminateLimit
SELECT * FROM (SELECT * FROM a LIMIT 99) LIMIT 100
----
scan a
 ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb)
 ├── limit: 99
 ├── key: (1)
 └── fd: (1)-->(2-5)

opt expect=EliminateLimit
SELECT * FROM (SELECT * FROM a LIMIT 100) LIMIT 100
----
scan a
 ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb)
 ├── limit: 100
 ├── key: (1)
 └── fd: (1)-->(2-5)

# Don't eliminate the outer limit if it's less than the inner.
opt
SELECT * FROM (SELECT * FROM a LIMIT 100) LIMIT 99
----
limit
 ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb)
 ├── cardinality: [0 - 99]
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb)
 │    ├── limit: 100
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 └── const: 99 [type=int]

# High limits (> max uint32), can't eliminate in this case.
opt
SELECT * FROM (SELECT * FROM a LIMIT 5000000000) LIMIT 5100000000
----
limit
 ├── 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
 │    ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb)
 │    ├── limit: 5000000000
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 └── const: 5100000000 [type=int]

# Don't eliminate in case of negative limit.
opt
SELECT * FROM (SELECT * FROM a LIMIT 0) LIMIT -1
----
limit
 ├── columns: k:1(int) i:2(int) f:3(float) s:4(string) j:5(jsonb)
 ├── cardinality: [0 - 0]
 ├── side-effects
 ├── key: ()
 ├── fd: ()-->(1-5)
 ├── values
 │    ├── columns: k:1(int) i:2(int) f:3(float) s:4(string) j:5(jsonb)
 │    ├── cardinality: [0 - 0]
 │    ├── key: ()
 │    └── fd: ()-->(1-5)
 └── const: -1 [type=int]

# --------------------------------------------------
# PushLimitIntoProject
# --------------------------------------------------
opt expect=PushLimitIntoProject
SELECT k, f*2.0 AS r FROM a LIMIT 5
----
project
 ├── columns: k:1(int!null) r:6(float)
 ├── cardinality: [0 - 5]
 ├── key: (1)
 ├── fd: (1)-->(6)
 ├── scan a
 │    ├── columns: k:1(int!null) f:3(float)
 │    ├── limit: 5
 │    ├── key: (1)
 │    └── fd: (1)-->(3)
 └── projections
      └── f * 2.0 [type=float, outer=(3)]

opt expect=PushLimitIntoProject
SELECT k, f*2.0 AS r FROM a ORDER BY k LIMIT 5
----
project
 ├── columns: k:1(int!null) r:6(float)
 ├── cardinality: [0 - 5]
 ├── key: (1)
 ├── fd: (1)-->(6)
 ├── ordering: +1
 ├── scan a
 │    ├── columns: k:1(int!null) f:3(float)
 │    ├── limit: 5
 │    ├── key: (1)
 │    ├── fd: (1)-->(3)
 │    └── ordering: +1
 └── projections
      └── f * 2.0 [type=float, outer=(3)]

# Don't push the limit through project when the ordering is on a
# synthesized column.
opt expect-not=PushLimitIntoProject
SELECT k, f*2.0 AS r FROM a ORDER BY r LIMIT 5 
----
limit
 ├── columns: k:1(int!null) r:6(float)
 ├── internal-ordering: +6
 ├── cardinality: [0 - 5]
 ├── key: (1)
 ├── fd: (1)-->(6)
 ├── ordering: +6
 ├── sort
 │    ├── columns: k:1(int!null) r:6(float)
 │    ├── key: (1)
 │    ├── fd: (1)-->(6)
 │    ├── ordering: +6
 │    └── project
 │         ├── columns: r:6(float) k:1(int!null)
 │         ├── key: (1)
 │         ├── fd: (1)-->(6)
 │         ├── scan a
 │         │    ├── columns: k:1(int!null) f:3(float)
 │         │    ├── key: (1)
 │         │    └── fd: (1)-->(3)
 │         └── projections
 │              └── f * 2.0 [type=float, outer=(3)]
 └── const: 5 [type=int]


# Detect PushLimitIntoProject and FilterUnusedLimitCols dependency cycle.
opt
SELECT f, f+1.1 AS r FROM (SELECT f, i FROM a GROUP BY f, i) a ORDER BY f LIMIT 5
----
project
 ├── columns: f:3(float) r:6(float)
 ├── cardinality: [0 - 5]
 ├── ordering: +3
 ├── limit
 │    ├── columns: i:2(int) f:3(float)
 │    ├── internal-ordering: +3
 │    ├── cardinality: [0 - 5]
 │    ├── key: (2,3)
 │    ├── ordering: +3
 │    ├── distinct-on
 │    │    ├── columns: i:2(int) f:3(float)
 │    │    ├── grouping columns: i:2(int) f:3(float)
 │    │    ├── key: (2,3)
 │    │    ├── ordering: +3
 │    │    └── sort
 │    │         ├── columns: i:2(int) f:3(float)
 │    │         ├── ordering: +3
 │    │         └── scan a
 │    │              └── columns: i:2(int) f:3(float)
 │    └── const: 5 [type=int]
 └── projections
      └── f + 1.1 [type=float, outer=(3)]

# Don't push negative limit into Scan.
opt
SELECT * FROM a LIMIT -1
----
limit
 ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb)
 ├── cardinality: [0 - 0]
 ├── side-effects
 ├── key: ()
 ├── fd: ()-->(1-5)
 ├── scan a
 │    ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb)
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 └── const: -1 [type=int]

# --------------------------------------------------
# PushOffsetIntoProject
# --------------------------------------------------
opt expect=PushOffsetIntoProject
SELECT k, f*2.0 AS r FROM a OFFSET 5
----
project
 ├── columns: k:1(int!null) r:6(float)
 ├── key: (1)
 ├── fd: (1)-->(6)
 ├── offset
 │    ├── columns: k:1(int!null) f:3(float)
 │    ├── key: (1)
 │    ├── fd: (1)-->(3)
 │    ├── scan a
 │    │    ├── columns: k:1(int!null) f:3(float)
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(3)
 │    └── const: 5 [type=int]
 └── projections
      └── f * 2.0 [type=float, outer=(3)]

opt expect=PushOffsetIntoProject
SELECT k, f*2.0 AS r FROM a ORDER BY k OFFSET 5
----
project
 ├── columns: k:1(int!null) r:6(float)
 ├── key: (1)
 ├── fd: (1)-->(6)
 ├── ordering: +1
 ├── offset
 │    ├── columns: k:1(int!null) f:3(float)
 │    ├── internal-ordering: +1
 │    ├── key: (1)
 │    ├── fd: (1)-->(3)
 │    ├── ordering: +1
 │    ├── scan a
 │    │    ├── columns: k:1(int!null) f:3(float)
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(3)
 │    │    └── ordering: +1
 │    └── const: 5 [type=int]
 └── projections
      └── f * 2.0 [type=float, outer=(3)]

# Don't push the offset through project when the ordering is on a
# synthesized column.
opt expect-not=PushOffsetIntoProject
SELECT k, f*2.0 AS r FROM a ORDER BY r OFFSET 5
----
offset
 ├── columns: k:1(int!null) r:6(float)
 ├── internal-ordering: +6
 ├── key: (1)
 ├── fd: (1)-->(6)
 ├── ordering: +6
 ├── sort
 │    ├── columns: k:1(int!null) r:6(float)
 │    ├── key: (1)
 │    ├── fd: (1)-->(6)
 │    ├── ordering: +6
 │    └── project
 │         ├── columns: r:6(float) k:1(int!null)
 │         ├── key: (1)
 │         ├── fd: (1)-->(6)
 │         ├── scan a
 │         │    ├── columns: k:1(int!null) f:3(float)
 │         │    ├── key: (1)
 │         │    └── fd: (1)-->(3)
 │         └── projections
 │              └── f * 2.0 [type=float, outer=(3)]
 └── const: 5 [type=int]

# Detect PushOffsetIntoProject and FilterUnusedOffsetCols dependency cycle.
opt
SELECT f, f+1.1 AS r FROM (SELECT f, i FROM a GROUP BY f, i) a ORDER BY f OFFSET 5
----
project
 ├── columns: f:3(float) r:6(float)
 ├── ordering: +3
 ├── offset
 │    ├── columns: i:2(int) f:3(float)
 │    ├── internal-ordering: +3
 │    ├── key: (2,3)
 │    ├── ordering: +3
 │    ├── distinct-on
 │    │    ├── columns: i:2(int) f:3(float)
 │    │    ├── grouping columns: i:2(int) f:3(float)
 │    │    ├── key: (2,3)
 │    │    ├── ordering: +3
 │    │    └── sort
 │    │         ├── columns: i:2(int) f:3(float)
 │    │         ├── ordering: +3
 │    │         └── scan a
 │    │              └── columns: i:2(int) f:3(float)
 │    └── const: 5 [type=int]
 └── projections
      └── f + 1.1 [type=float, outer=(3)]

# --------------------------------------------------
# PushLimitIntoProject + PushOffsetIntoProject
# --------------------------------------------------
opt expect=(PushLimitIntoProject,PushOffsetIntoProject)
SELECT k, f*2.0 AS r FROM a OFFSET 5 LIMIT 10
----
project
 ├── columns: k:1(int!null) r:6(float)
 ├── cardinality: [0 - 10]
 ├── key: (1)
 ├── fd: (1)-->(6)
 ├── limit
 │    ├── columns: k:1(int!null) f:3(float)
 │    ├── cardinality: [0 - 10]
 │    ├── key: (1)
 │    ├── fd: (1)-->(3)
 │    ├── offset
 │    │    ├── columns: k:1(int!null) f:3(float)
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(3)
 │    │    ├── scan a
 │    │    │    ├── columns: k:1(int!null) f:3(float)
 │    │    │    ├── key: (1)
 │    │    │    └── fd: (1)-->(3)
 │    │    └── const: 5 [type=int]
 │    └── const: 10 [type=int]
 └── projections
      └── f * 2.0 [type=float, outer=(3)]

opt expect=(PushLimitIntoProject,PushOffsetIntoProject)
SELECT f, f+1.1 AS r FROM (SELECT f, i FROM a GROUP BY f, i) a ORDER BY f OFFSET 5 LIMIT 10
----
project
 ├── columns: f:3(float) r:6(float)
 ├── cardinality: [0 - 10]
 ├── ordering: +3
 ├── limit
 │    ├── columns: i:2(int) f:3(float)
 │    ├── internal-ordering: +3
 │    ├── cardinality: [0 - 10]
 │    ├── key: (2,3)
 │    ├── ordering: +3
 │    ├── offset
 │    │    ├── columns: i:2(int) f:3(float)
 │    │    ├── internal-ordering: +3
 │    │    ├── key: (2,3)
 │    │    ├── ordering: +3
 │    │    ├── distinct-on
 │    │    │    ├── columns: i:2(int) f:3(float)
 │    │    │    ├── grouping columns: i:2(int) f:3(float)
 │    │    │    ├── key: (2,3)
 │    │    │    ├── ordering: +3
 │    │    │    └── sort
 │    │    │         ├── columns: i:2(int) f:3(float)
 │    │    │         ├── ordering: +3
 │    │    │         └── scan a
 │    │    │              └── columns: i:2(int) f:3(float)
 │    │    └── const: 5 [type=int]
 │    └── const: 10 [type=int]
 └── projections
      └── f + 1.1 [type=float, outer=(3)]
