-
Notifications
You must be signed in to change notification settings - Fork 3.8k
/
ordering.go
434 lines (409 loc) · 15.1 KB
/
ordering.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
// Copyright 2018 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
package ordering
import (
"github.com/cockroachdb/cockroach/pkg/sql/opt"
"github.com/cockroachdb/cockroach/pkg/sql/opt/memo"
"github.com/cockroachdb/cockroach/pkg/sql/opt/props"
"github.com/cockroachdb/cockroach/pkg/util"
"github.com/cockroachdb/cockroach/pkg/util/log"
"github.com/cockroachdb/errors"
)
// CanProvide returns true if the given operator returns rows that can
// satisfy the given required ordering.
func CanProvide(expr memo.RelExpr, required *props.OrderingChoice) bool {
if required.Any() {
return true
}
if util.CrdbTestBuild {
checkRequired(expr, required)
}
return funcMap[expr.Op()].canProvideOrdering(expr, required)
}
// BuildChildRequired returns the ordering that must be required of its
// given child in order to satisfy a required ordering. Can only be called if
// CanProvide is true for the required ordering.
func BuildChildRequired(
parent memo.RelExpr, required *props.OrderingChoice, childIdx int,
) props.OrderingChoice {
result := funcMap[parent.Op()].buildChildReqOrdering(parent, required, childIdx)
if util.CrdbTestBuild && !result.Any() {
checkRequired(parent.Child(childIdx).(memo.RelExpr), &result)
}
return result
}
// BuildProvided returns a specific ordering that the operator provides (and which
// must be maintained on the results during distributed execution).
//
// The returned ordering, in conjunction with the operator's functional
// dependencies, must intersect the required ordering.
//
// A best-effort attempt is made to make the provided orderings as simple as
// possible (while still satisfying the required ordering).
//
// For example, if we scan an index on x,y,z with required ordering "+y opt(x)",
// the provided ordering is "+x,+y". If we scan the same index with constraint
// x=1, the provided ordering is "+y".
//
// This function assumes that the provided orderings have already been set in
// the children of the expression.
func BuildProvided(expr memo.RelExpr, required *props.OrderingChoice) opt.Ordering {
if required.Any() {
return nil
}
provided := funcMap[expr.Op()].buildProvidedOrdering(expr, required)
if util.CrdbTestBuild {
checkProvided(expr, required, provided)
}
return provided
}
type funcs struct {
canProvideOrdering func(expr memo.RelExpr, required *props.OrderingChoice) bool
buildChildReqOrdering func(
parent memo.RelExpr, required *props.OrderingChoice, childIdx int,
) props.OrderingChoice
buildProvidedOrdering func(
expr memo.RelExpr, required *props.OrderingChoice,
) opt.Ordering
}
var funcMap [opt.NumOperators]funcs
func init() {
for _, op := range opt.RelationalOperators {
funcMap[op] = funcs{
canProvideOrdering: canNeverProvideOrdering,
buildChildReqOrdering: noChildReqOrdering,
buildProvidedOrdering: noProvidedOrdering,
}
}
funcMap[opt.ScanOp] = funcs{
canProvideOrdering: scanCanProvideOrdering,
buildChildReqOrdering: noChildReqOrdering,
buildProvidedOrdering: scanBuildProvided,
}
funcMap[opt.SelectOp] = funcs{
canProvideOrdering: selectCanProvideOrdering,
buildChildReqOrdering: selectBuildChildReqOrdering,
buildProvidedOrdering: selectBuildProvided,
}
funcMap[opt.ProjectOp] = funcs{
canProvideOrdering: projectCanProvideOrdering,
buildChildReqOrdering: projectBuildChildReqOrdering,
buildProvidedOrdering: projectBuildProvided,
}
funcMap[opt.UnionOp] = funcs{
canProvideOrdering: setOpCanProvideOrdering,
buildChildReqOrdering: setOpBuildChildReqOrdering,
buildProvidedOrdering: setOpBuildProvided,
}
funcMap[opt.UnionAllOp] = funcs{
canProvideOrdering: setOpCanProvideOrdering,
buildChildReqOrdering: setOpBuildChildReqOrdering,
buildProvidedOrdering: setOpBuildProvided,
}
funcMap[opt.IntersectOp] = funcs{
canProvideOrdering: setOpCanProvideOrdering,
buildChildReqOrdering: setOpBuildChildReqOrdering,
buildProvidedOrdering: setOpBuildProvided,
}
funcMap[opt.IntersectAllOp] = funcs{
canProvideOrdering: setOpCanProvideOrdering,
buildChildReqOrdering: setOpBuildChildReqOrdering,
buildProvidedOrdering: setOpBuildProvided,
}
funcMap[opt.ExceptOp] = funcs{
canProvideOrdering: setOpCanProvideOrdering,
buildChildReqOrdering: setOpBuildChildReqOrdering,
buildProvidedOrdering: setOpBuildProvided,
}
funcMap[opt.ExceptAllOp] = funcs{
canProvideOrdering: setOpCanProvideOrdering,
buildChildReqOrdering: setOpBuildChildReqOrdering,
buildProvidedOrdering: setOpBuildProvided,
}
funcMap[opt.IndexJoinOp] = funcs{
canProvideOrdering: lookupOrIndexJoinCanProvideOrdering,
buildChildReqOrdering: lookupOrIndexJoinBuildChildReqOrdering,
buildProvidedOrdering: indexJoinBuildProvided,
}
funcMap[opt.LookupJoinOp] = funcs{
canProvideOrdering: lookupOrIndexJoinCanProvideOrdering,
buildChildReqOrdering: lookupOrIndexJoinBuildChildReqOrdering,
buildProvidedOrdering: lookupJoinBuildProvided,
}
funcMap[opt.InvertedJoinOp] = funcs{
canProvideOrdering: invertedJoinCanProvideOrdering,
buildChildReqOrdering: invertedJoinBuildChildReqOrdering,
buildProvidedOrdering: invertedJoinBuildProvided,
}
funcMap[opt.OrdinalityOp] = funcs{
canProvideOrdering: ordinalityCanProvideOrdering,
buildChildReqOrdering: ordinalityBuildChildReqOrdering,
buildProvidedOrdering: ordinalityBuildProvided,
}
funcMap[opt.MergeJoinOp] = funcs{
canProvideOrdering: mergeJoinCanProvideOrdering,
buildChildReqOrdering: mergeJoinBuildChildReqOrdering,
buildProvidedOrdering: mergeJoinBuildProvided,
}
funcMap[opt.LimitOp] = funcs{
canProvideOrdering: limitOrOffsetCanProvideOrdering,
buildChildReqOrdering: limitOrOffsetBuildChildReqOrdering,
buildProvidedOrdering: limitOrOffsetBuildProvided,
}
funcMap[opt.OffsetOp] = funcs{
canProvideOrdering: limitOrOffsetCanProvideOrdering,
buildChildReqOrdering: limitOrOffsetBuildChildReqOrdering,
buildProvidedOrdering: limitOrOffsetBuildProvided,
}
funcMap[opt.ScalarGroupByOp] = funcs{
// ScalarGroupBy always has exactly one result; any required ordering should
// have been simplified to Any (unless normalization rules are disabled).
canProvideOrdering: canNeverProvideOrdering,
buildChildReqOrdering: scalarGroupByBuildChildReqOrdering,
buildProvidedOrdering: noProvidedOrdering,
}
funcMap[opt.GroupByOp] = funcs{
canProvideOrdering: groupByCanProvideOrdering,
buildChildReqOrdering: groupByBuildChildReqOrdering,
buildProvidedOrdering: groupByBuildProvided,
}
funcMap[opt.DistinctOnOp] = funcs{
canProvideOrdering: distinctOnCanProvideOrdering,
buildChildReqOrdering: distinctOnBuildChildReqOrdering,
buildProvidedOrdering: distinctOnBuildProvided,
}
funcMap[opt.EnsureDistinctOnOp] = funcs{
canProvideOrdering: distinctOnCanProvideOrdering,
buildChildReqOrdering: distinctOnBuildChildReqOrdering,
buildProvidedOrdering: distinctOnBuildProvided,
}
funcMap[opt.UpsertDistinctOnOp] = funcs{
canProvideOrdering: distinctOnCanProvideOrdering,
buildChildReqOrdering: distinctOnBuildChildReqOrdering,
buildProvidedOrdering: distinctOnBuildProvided,
}
funcMap[opt.EnsureUpsertDistinctOnOp] = funcs{
canProvideOrdering: distinctOnCanProvideOrdering,
buildChildReqOrdering: distinctOnBuildChildReqOrdering,
buildProvidedOrdering: distinctOnBuildProvided,
}
funcMap[opt.SortOp] = funcs{
canProvideOrdering: nil, // should never get called
buildChildReqOrdering: sortBuildChildReqOrdering,
buildProvidedOrdering: sortBuildProvided,
}
funcMap[opt.InsertOp] = funcs{
canProvideOrdering: mutationCanProvideOrdering,
buildChildReqOrdering: mutationBuildChildReqOrdering,
buildProvidedOrdering: mutationBuildProvided,
}
funcMap[opt.UpdateOp] = funcs{
canProvideOrdering: mutationCanProvideOrdering,
buildChildReqOrdering: mutationBuildChildReqOrdering,
buildProvidedOrdering: mutationBuildProvided,
}
funcMap[opt.UpsertOp] = funcs{
canProvideOrdering: mutationCanProvideOrdering,
buildChildReqOrdering: mutationBuildChildReqOrdering,
buildProvidedOrdering: mutationBuildProvided,
}
funcMap[opt.DeleteOp] = funcs{
canProvideOrdering: mutationCanProvideOrdering,
buildChildReqOrdering: mutationBuildChildReqOrdering,
buildProvidedOrdering: mutationBuildProvided,
}
funcMap[opt.ExplainOp] = funcs{
canProvideOrdering: canNeverProvideOrdering,
buildChildReqOrdering: explainBuildChildReqOrdering,
buildProvidedOrdering: noProvidedOrdering,
}
funcMap[opt.AlterTableSplitOp] = funcs{
canProvideOrdering: canNeverProvideOrdering,
buildChildReqOrdering: alterTableSplitBuildChildReqOrdering,
buildProvidedOrdering: noProvidedOrdering,
}
funcMap[opt.AlterTableUnsplitOp] = funcs{
canProvideOrdering: canNeverProvideOrdering,
buildChildReqOrdering: alterTableUnsplitBuildChildReqOrdering,
buildProvidedOrdering: noProvidedOrdering,
}
funcMap[opt.AlterTableRelocateOp] = funcs{
canProvideOrdering: canNeverProvideOrdering,
buildChildReqOrdering: alterTableRelocateBuildChildReqOrdering,
buildProvidedOrdering: noProvidedOrdering,
}
funcMap[opt.ControlJobsOp] = funcs{
canProvideOrdering: canNeverProvideOrdering,
buildChildReqOrdering: controlJobsBuildChildReqOrdering,
buildProvidedOrdering: noProvidedOrdering,
}
funcMap[opt.CancelQueriesOp] = funcs{
canProvideOrdering: canNeverProvideOrdering,
buildChildReqOrdering: cancelQueriesBuildChildReqOrdering,
buildProvidedOrdering: noProvidedOrdering,
}
funcMap[opt.CancelSessionsOp] = funcs{
canProvideOrdering: canNeverProvideOrdering,
buildChildReqOrdering: cancelSessionsBuildChildReqOrdering,
buildProvidedOrdering: noProvidedOrdering,
}
funcMap[opt.ExportOp] = funcs{
canProvideOrdering: canNeverProvideOrdering,
buildChildReqOrdering: exportBuildChildReqOrdering,
buildProvidedOrdering: noProvidedOrdering,
}
}
func canNeverProvideOrdering(expr memo.RelExpr, required *props.OrderingChoice) bool {
return false
}
func noChildReqOrdering(
parent memo.RelExpr, required *props.OrderingChoice, childIdx int,
) props.OrderingChoice {
return props.OrderingChoice{}
}
func noProvidedOrdering(expr memo.RelExpr, required *props.OrderingChoice) opt.Ordering {
return nil
}
// remapProvided remaps columns in a provided ordering (according to the given
// FDs) so that it only refers to columns in the given outCols set. It also
// removes any columns that are redundant according to the FDs.
//
// Can only be called if the provided ordering can be remapped.
//
// Does not modify <provided> in place, but it can return the same slice.
func remapProvided(provided opt.Ordering, fds *props.FuncDepSet, outCols opt.ColSet) opt.Ordering {
if len(provided) == 0 {
return nil
}
// result is nil until we determine that we need to make a copy.
var result opt.Ordering
// closure is the set of columns that are functionally determined by the
// columns in provided[:i].
closure := fds.ComputeClosure(opt.ColSet{})
for i := range provided {
col := provided[i].ID()
if closure.Contains(col) {
// At the level of the new operator, this column is redundant.
if result == nil {
result = make(opt.Ordering, i, len(provided))
copy(result, provided)
}
continue
}
if outCols.Contains(col) {
if result != nil {
result = append(result, provided[i])
}
} else {
equivCols := fds.ComputeEquivClosure(opt.MakeColSet(col))
remappedCol, ok := equivCols.Intersection(outCols).Next(0)
if !ok {
panic(errors.AssertionFailedf("no output column equivalent to %d", log.Safe(col)))
}
if result == nil {
result = make(opt.Ordering, i, len(provided))
copy(result, provided)
}
result = append(result, opt.MakeOrderingColumn(
remappedCol, provided[i].Descending(),
))
}
closure.Add(col)
closure = fds.ComputeClosure(closure)
}
if result == nil {
return provided
}
return result
}
// trimProvided returns the smallest prefix of <provided> that is sufficient to
// satisfy <required> (in conjunction with the FDs).
//
// This is useful because in a distributed setting execution is configured to
// maintain the provided ordering when merging results from multiple nodes, and
// we don't want to make needless comparisons.
func trimProvided(
provided opt.Ordering, required *props.OrderingChoice, fds *props.FuncDepSet,
) opt.Ordering {
if len(provided) == 0 {
return nil
}
// closure is the set of columns that are functionally determined by the
// columns in provided[:provIdx].
closure := fds.ComputeClosure(opt.ColSet{})
provIdx := 0
for reqIdx := range required.Columns {
c := &required.Columns[reqIdx]
// Consume columns from the provided ordering until their closure intersects
// the required group.
for !closure.Intersects(c.Group) {
closure.Add(provided[provIdx].ID())
closure = fds.ComputeClosure(closure)
provIdx++
if provIdx == len(provided) {
return provided
}
}
}
return provided[:provIdx]
}
// checkRequired runs sanity checks on the ordering required of an operator.
func checkRequired(expr memo.RelExpr, required *props.OrderingChoice) {
rel := expr.Relational()
// Verify that the ordering only refers to output columns.
if !required.SubsetOfCols(rel.OutputCols) {
panic(errors.AssertionFailedf("required ordering refers to non-output columns (op %s)", log.Safe(expr.Op())))
}
// Verify that columns in a column group are equivalent.
for i := range required.Columns {
c := &required.Columns[i]
if !c.Group.SubsetOf(rel.FuncDeps.ComputeEquivGroup(c.AnyID())) {
panic(errors.AssertionFailedf(
"ordering column group %s contains non-equivalent columns (op %s)",
c.Group, expr.Op(),
))
}
}
}
// checkProvided runs sanity checks on a provided ordering.
func checkProvided(expr memo.RelExpr, required *props.OrderingChoice, provided opt.Ordering) {
// The provided ordering must refer only to output columns.
if outCols := expr.Relational().OutputCols; !provided.ColSet().SubsetOf(outCols) {
panic(errors.AssertionFailedf(
"provided %s must refer only to output columns %s", provided, outCols,
))
}
// TODO(radu): this check would be nice to have, but it is too strict. In some
// cases, child expressions created during exploration (like constrained
// scans) have FDs that are more restricted than what was known when the
// parent expression was constructed. Related to #32320.
if false {
// The provided ordering must intersect the required ordering, after FDs are
// applied.
fds := &expr.Relational().FuncDeps
r := required.Copy()
r.Simplify(fds)
var p props.OrderingChoice
p.FromOrdering(provided)
p.Simplify(fds)
if !r.Any() && (p.Any() || !p.Intersects(&r)) {
panic(errors.AssertionFailedf(
"provided %s does not intersect required %s (FDs: %s)", provided, required, fds,
))
}
}
// The provided ordering should not have unnecessary columns.
fds := &expr.Relational().FuncDeps
if trimmed := trimProvided(provided, required, fds); len(trimmed) != len(provided) {
panic(errors.AssertionFailedf(
"provided %s can be trimmed to %s (FDs: %s)", log.Safe(provided), log.Safe(trimmed), log.Safe(fds),
))
}
}