Skip to content

Commit

Permalink
opt: add rule to fold limits
Browse files Browse the repository at this point in the history
Previously, there was no rule to fold a Limit on top of a Limit
when the outer limit value is smaller than the inner limit value.

This patch adds a rule to fold two Limits together when the
outer Limit has a smaller limit value than the inner Limit and
the orderings of the Limits intersect.

Release note (sql change): The optimizer can now fold two Limit
operators together.
  • Loading branch information
DrewKimball committed Jun 6, 2020
1 parent 57e37b4 commit bdf711c
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 25 deletions.
6 changes: 6 additions & 0 deletions pkg/sql/opt/norm/general_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -950,3 +950,9 @@ func (c *CustomFuncs) CanAddConstInts(first tree.Datum, second tree.Datum) bool
func (c *CustomFuncs) IntConst(d *tree.DInt) opt.ScalarExpr {
return c.f.ConstructConst(d, types.Int)
}

// IsGreaterThan returns true if the first datum compares as greater than the
// second.
func (c *CustomFuncs) IsGreaterThan(first, second tree.Datum) bool {
return first.Compare(c.f.evalCtx, second) == 1
}
22 changes: 22 additions & 0 deletions pkg/sql/opt/norm/rules/limit.opt
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,25 @@ $input
$limitExpr
$ordering
)

# FoldLimits replaces a Limit on top of a Limit with a single Limit operator
# when the outer limit value is smaller than or equal to the inner limit value
# and the orderings intersect.
[FoldLimits, Normalize]
(Limit
(Limit
$innerInput:*
$innerLimitExpr:(Const $innerLimit:*)
$innerOrdering:*
)
$outerLimitExpr:(Const $outerLimit:*) &
^(IsGreaterThan $outerLimit $innerLimit)
$outerOrdering:* &
(OrderingIntersects $innerOrdering $outerOrdering)
)
=>
(Limit
$innerInput
$outerLimitExpr
(OrderingIntersection $innerOrdering $outerOrdering)
)
173 changes: 150 additions & 23 deletions pkg/sql/opt/norm/testdata/rules/limit
Original file line number Diff line number Diff line change
Expand Up @@ -51,31 +51,25 @@ limit
│ └── limit hint: 100.00
└── 100

# Don't eliminate the outer limit if it's less than the inner.
norm
# Don't eliminate the outer limit if it's less than the inner (the limit is
# instead removed by FoldLimits).
norm expect-not=EliminateLimit
SELECT * FROM (SELECT * FROM a LIMIT 100) LIMIT 99
----
limit
├── columns: k:1!null i:2 f:3 s:4 j:5
├── cardinality: [0 - 99]
├── key: (1)
├── fd: (1)-->(2-5)
├── limit
├── scan a
│ ├── columns: k:1!null i:2 f:3 s:4 j:5
│ ├── cardinality: [0 - 100]
│ ├── key: (1)
│ ├── fd: (1)-->(2-5)
│ ├── limit hint: 99.00
│ ├── scan a
│ │ ├── columns: k:1!null i:2 f:3 s:4 j:5
│ │ ├── key: (1)
│ │ ├── fd: (1)-->(2-5)
│ │ └── limit hint: 100.00
│ └── 100
│ └── limit hint: 99.00
└── 99

# High limits (> max uint32), can't eliminate in this case.
norm
norm expect-not=EliminateLimit
SELECT * FROM (SELECT * FROM a LIMIT 5000000000) LIMIT 5100000000
----
limit
Expand All @@ -95,8 +89,9 @@ limit
│ └── 5000000000
└── 5100000000

# Don't eliminate in case of negative limit.
norm
# Don't eliminate in case of negative limit (the limit is instead removed by
# FoldLimits).
norm expect-not=EliminateLimit
SELECT * FROM (SELECT * FROM a LIMIT 0) LIMIT -1
----
limit
Expand Down Expand Up @@ -1109,18 +1104,11 @@ limit
│ │ ├── cardinality: [0 - 10]
│ │ ├── key: (1)
│ │ ├── fd: (1)-->(2)
│ │ ├── limit
│ │ ├── scan ab
│ │ │ ├── columns: a:1!null b:2
│ │ │ ├── cardinality: [0 - 20]
│ │ │ ├── key: (1)
│ │ │ ├── fd: (1)-->(2)
│ │ │ ├── limit hint: 10.00
│ │ │ ├── scan ab
│ │ │ │ ├── columns: a:1!null b:2
│ │ │ │ ├── key: (1)
│ │ │ │ ├── fd: (1)-->(2)
│ │ │ │ └── limit hint: 20.00
│ │ │ └── 20
│ │ │ └── limit hint: 10.00
│ │ └── 10
│ ├── scan uv
│ │ ├── columns: u:3!null v:4
Expand Down Expand Up @@ -1245,3 +1233,142 @@ limit
│ └── filters
│ └── a:1 = u:3 [outer=(1,3), constraints=(/1: (/NULL - ]; /3: (/NULL - ]), fd=(1)==(3), (3)==(1)]
└── 10

# ----------
# FoldLimits
# ----------

# Basic case with no orderings.
norm expect=FoldLimits
SELECT * FROM (SELECT * FROM ab LIMIT 10) LIMIT 5
----
limit
├── columns: a:1!null b:2
├── cardinality: [0 - 5]
├── key: (1)
├── fd: (1)-->(2)
├── scan ab
│ ├── columns: a:1!null b:2
│ ├── key: (1)
│ ├── fd: (1)-->(2)
│ └── limit hint: 5.00
└── 5

# Case where the outer limit has an ordering and the inner limit is unordered.
norm expect=FoldLimits
SELECT * FROM (SELECT * FROM ab LIMIT 10) ORDER BY a LIMIT 5
----
limit
├── columns: a:1!null b:2
├── internal-ordering: +1
├── cardinality: [0 - 5]
├── key: (1)
├── fd: (1)-->(2)
├── ordering: +1
├── scan ab
│ ├── columns: a:1!null b:2
│ ├── key: (1)
│ ├── fd: (1)-->(2)
│ ├── ordering: +1
│ └── limit hint: 5.00
└── 5

# Case where the outer limit ordering implies the inner ordering.
norm expect=FoldLimits
SELECT * FROM (SELECT * FROM a ORDER BY i LIMIT 10) ORDER BY i, f LIMIT 5
----
limit
├── columns: k:1!null i:2 f:3 s:4 j:5
├── internal-ordering: +2,+3
├── cardinality: [0 - 5]
├── key: (1)
├── fd: (1)-->(2-5)
├── ordering: +2,+3
├── sort
│ ├── columns: k:1!null i:2 f:3 s:4 j:5
│ ├── key: (1)
│ ├── fd: (1)-->(2-5)
│ ├── ordering: +2,+3
│ ├── limit hint: 5.00
│ └── scan a
│ ├── columns: k:1!null i:2 f:3 s:4 j:5
│ ├── key: (1)
│ └── fd: (1)-->(2-5)
└── 5

# Case where the inner limit ordering implies the outer ordering.
norm expect=FoldLimits
SELECT * FROM (SELECT * FROM a ORDER BY i, f LIMIT 10) ORDER BY i LIMIT 5
----
limit
├── columns: k:1!null i:2 f:3 s:4 j:5
├── internal-ordering: +2,+3
├── cardinality: [0 - 5]
├── key: (1)
├── fd: (1)-->(2-5)
├── ordering: +2
├── sort
│ ├── columns: k:1!null i:2 f:3 s:4 j:5
│ ├── key: (1)
│ ├── fd: (1)-->(2-5)
│ ├── ordering: +2,+3
│ ├── limit hint: 5.00
│ └── scan a
│ ├── columns: k:1!null i:2 f:3 s:4 j:5
│ ├── key: (1)
│ └── fd: (1)-->(2-5)
└── 5

# No-op case where the outer limit is larger than the inner limit.
norm expect-not=FoldLimits
SELECT * FROM (SELECT * FROM ab LIMIT 5) LIMIT 10
----
limit
├── columns: a:1!null b:2
├── cardinality: [0 - 5]
├── key: (1)
├── fd: (1)-->(2)
├── scan ab
│ ├── columns: a:1!null b:2
│ ├── key: (1)
│ ├── fd: (1)-->(2)
│ └── limit hint: 5.00
└── 5

# No-op case where the outer limit ordering does not imply the inner limit
# ordering.
norm expect-not=FoldLimits
SELECT * FROM (SELECT * FROM ab ORDER BY b LIMIT 10) ORDER BY a LIMIT 5
----
limit
├── columns: a:1!null b:2
├── internal-ordering: +1
├── cardinality: [0 - 5]
├── key: (1)
├── fd: (1)-->(2)
├── ordering: +1
├── sort
│ ├── columns: a:1!null b:2
│ ├── cardinality: [0 - 10]
│ ├── key: (1)
│ ├── fd: (1)-->(2)
│ ├── ordering: +1
│ ├── limit hint: 5.00
│ └── limit
│ ├── columns: a:1!null b:2
│ ├── internal-ordering: +2
│ ├── cardinality: [0 - 10]
│ ├── key: (1)
│ ├── fd: (1)-->(2)
│ ├── sort
│ │ ├── columns: a:1!null b:2
│ │ ├── key: (1)
│ │ ├── fd: (1)-->(2)
│ │ ├── ordering: +2
│ │ ├── limit hint: 10.00
│ │ └── scan ab
│ │ ├── columns: a:1!null b:2
│ │ ├── key: (1)
│ │ └── fd: (1)-->(2)
│ └── 10
└── 5
2 changes: 0 additions & 2 deletions pkg/sql/opt/xform/testdata/rules/limit
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,10 @@ limit
├── fd: ()-->(2,4)
├── sort (segmented)
│ ├── columns: i:2 s:4
│ ├── cardinality: [0 - 10]
│ ├── ordering: +4,+2
│ ├── limit hint: 1.00
│ └── scan a@s_idx
│ ├── columns: i:2 s:4
│ ├── limit: 10
│ └── ordering: +4
└── 1

Expand Down

0 comments on commit bdf711c

Please sign in to comment.