-
Notifications
You must be signed in to change notification settings - Fork 3.8k
/
ordering_choice.go
1188 lines (1070 loc) · 36.7 KB
/
ordering_choice.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
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// 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 props
import (
"bytes"
"fmt"
"regexp"
"strconv"
"strings"
"sync"
"github.com/cockroachdb/cockroach/pkg/sql/opt"
"github.com/cockroachdb/errors"
)
// OrderingChoice defines the set of possible row orderings that are provided or
// required by an operator. An OrderingChoice consists of two parts: an ordered
// sequence of equivalent column groups and a set of optional columns. Together,
// these parts specify a simple pattern that can match one or more candidate
// orderings. Here are some examples:
//
// +1 ORDER BY a
// +1,-2 ORDER BY a,b DESC
// +(1|2) ORDER BY a | ORDER BY b
// +(1|2),+3 ORDER BY a,c | ORDER BY b, c
// -(3|4),+5 opt(1,2) ORDER BY c DESC,e | ORDER BY a,d DESC,b DESC,e | ...
//
// Each column in the ordering sequence forms the corresponding column of the
// sort key, from most significant to least significant. Each column has a sort
// direction, either ascending or descending. The relation is ordered by the
// first column; rows that have the same value are then ordered by the second
// column; rows that still have the same value are ordered by the third column,
// and so on.
//
// Sometimes multiple columns in the relation have equivalent values. The
// OrderingChoiceColumn stores these columns in a group; any of the columns in
// the group can be used to form the corresponding column in the sort key. The
// equivalent group columns come from SQL expressions like:
//
// a=b
//
// The optional column set contains columns that can appear anywhere (or
// nowhere) in the ordering. Optional columns come from SQL expressions like:
//
// a=1
//
// Another case for optional columns is when we are grouping along a set of
// columns and only care about the intra-group ordering.
//
// The optional columns can be interleaved anywhere in the sequence of ordering
// columns, as they have no effect on the ordering.
type OrderingChoice struct {
// Optional is the set of columns that can appear at any position in the
// ordering. Columns in Optional must not appear in the Columns sequence.
// In addition, if Columns is empty, then Optional must be as well.
// After initial construction, Optional is immutable. To update, replace
// with a different set containing the desired columns.
Optional opt.ColSet
// Columns is the sequence of equivalent column groups that can be used to
// form each column in the sort key. Columns must not appear in the Optional
// set. The array memory is owned by this struct, and should not be copied
// to another OrderingChoice unless both are kept immutable.
Columns []OrderingColumnChoice
}
// OrderingColumnChoice specifies the set of columns which can form one of the
// columns in the sort key, as well as the direction of that column (ascending
// or descending).
type OrderingColumnChoice struct {
// Group is a set of equivalent columns, any of which can be used to form a
// column in the sort key. After initial construction, Group is immutable.
// To update, replace with a different set containing the desired columns.
Group opt.ColSet
// Descending is true if the sort key column is ordered from highest to
// lowest. Otherwise, it's ordered from lowest to highest.
Descending bool
}
const (
colChoiceRegexStr = `(?:\((\d+(?:\|\d+)*)\))`
ordColRegexStr = `^(?:(?:\+|\-)(?:(\d+)|` + colChoiceRegexStr + `))$`
colListRegexStr = `(\d+(?:,\d+)*)`
optRegexStr = `^\s*([\S]+)?\s*(?:opt\(` + colListRegexStr + `\))?\s*$`
)
var once sync.Once
var optRegex, ordColRegex *regexp.Regexp
// ParseOrderingChoice parses the string representation of an OrderingChoice for
// testing purposes. Here are some examples of the string format:
//
// +1
// -(1|2),+3
// +(1|2),+3 opt(5,6)
//
// The input string is expected to be valid; ParseOrderingChoice will panic if
// it is not.
func ParseOrderingChoice(s string) OrderingChoice {
once.Do(func() {
optRegex = regexp.MustCompile(optRegexStr)
ordColRegex = regexp.MustCompile(ordColRegexStr)
})
var ordering OrderingChoice
// Separate string into column sequence and optional column parts:
// +(1|2),+3 opt(5,6)
// matches[1]: +(1|2),+3
// matches[2]: opt(5,6)
matches := optRegex.FindStringSubmatch(s)
if matches == nil {
panic(errors.AssertionFailedf("could not parse ordering choice: %s", s))
}
// Handle Any case.
if len(matches[1]) == 0 {
return OrderingChoice{}
}
// Split column sequence by comma:
// +(1|2),+3:
// +(1|2)
// +3
for _, ordColStr := range strings.Split(matches[1], ",") {
// Parse one item in the column sequence:
// +(1|2):
// matches[1]: <empty>
// matches[2]: 1|2
//
// +3:
// matches[1]: 3
// matches[2]: <empty>
ordColMatches := ordColRegex.FindStringSubmatch(ordColStr)
// First character is the direction indicator.
var colChoice OrderingColumnChoice
colChoice.Descending = strings.HasPrefix(ordColStr, "-")
if len(ordColMatches[1]) != 0 {
// Single column in equivalence group.
id, _ := strconv.Atoi(ordColMatches[1])
colChoice.Group.Add(opt.ColumnID(id))
} else {
// Split multiple columns in equivalence group by pipe:
// 1|2:
// 1
// 2
for _, idStr := range strings.Split(ordColMatches[2], "|") {
id, _ := strconv.Atoi(idStr)
colChoice.Group.Add(opt.ColumnID(id))
}
}
ordering.Columns = append(ordering.Columns, colChoice)
}
// Parse any optional columns by splitting by comma:
// opt(5,6):
// 5
// 6
if len(matches[2]) != 0 {
for _, idStr := range strings.Split(matches[2], ",") {
id, _ := strconv.Atoi(idStr)
ordering.Optional.Add(opt.ColumnID(id))
}
}
return ordering
}
// ParseOrdering parses a simple opt.Ordering; for example: "+1,-3".
//
// The input string is expected to be valid; ParseOrdering will panic if it is
// not.
func ParseOrdering(str string) opt.Ordering {
prov := ParseOrderingChoice(str)
if !prov.Optional.Empty() {
panic(errors.AssertionFailedf("invalid ordering %s", str))
}
for i := range prov.Columns {
if prov.Group(i).Len() != 1 {
panic(errors.AssertionFailedf("invalid ordering %s", str))
}
}
return prov.ToOrdering()
}
// Any is true if this instance allows any ordering (any length, any columns).
func (oc *OrderingChoice) Any() bool {
return len(oc.Columns) == 0
}
// FromOrdering sets this OrderingChoice to the given opt.Ordering.
func (oc *OrderingChoice) FromOrdering(ord opt.Ordering) {
oc.Optional = opt.ColSet{}
oc.Columns = make([]OrderingColumnChoice, len(ord))
for i := range ord {
oc.Columns[i].Group.Add(ord[i].ID())
oc.Columns[i].Descending = ord[i].Descending()
}
}
// FromOrderingWithOptCols sets this OrderingChoice to the given opt.Ordering
// and with the given optional columns. Any optional columns in the given
// ordering are ignored.
func (oc *OrderingChoice) FromOrderingWithOptCols(ord opt.Ordering, optCols opt.ColSet) {
oc.Columns = make([]OrderingColumnChoice, 0, len(ord))
for i := range ord {
if !optCols.Contains(ord[i].ID()) {
oc.Columns = append(oc.Columns, OrderingColumnChoice{
Group: opt.MakeColSet(ord[i].ID()),
Descending: ord[i].Descending(),
})
}
}
// If Columns is empty, then Optional must be as well.
if oc.Any() {
oc.Optional = opt.ColSet{}
} else {
oc.Optional = optCols.Copy()
}
}
// ToOrdering returns an opt.Ordering instance composed of the shortest possible
// orderings that this instance allows. If there are several, then one is chosen
// arbitrarily.
func (oc *OrderingChoice) ToOrdering() opt.Ordering {
ordering := make(opt.Ordering, len(oc.Columns))
for i := range oc.Columns {
col := &oc.Columns[i]
ordering[i] = opt.MakeOrderingColumn(col.AnyID(), col.Descending)
}
return ordering
}
// ColSet returns the set of all non-optional columns that are part of this
// instance. For example, (1,2,3) will be returned if the OrderingChoice is:
//
// +1,(2|3) opt(4,5)
//
func (oc *OrderingChoice) ColSet() opt.ColSet {
var cs opt.ColSet
for i := range oc.Columns {
cs.UnionWith(oc.Group(i))
}
return cs
}
// Implies returns true if any ordering allowed by <oc> is also allowed by <other>.
//
// In the case of no optional or equivalent columns, Implies returns true when
// the given ordering is a prefix of this ordering.
//
// Examples:
//
// <empty> implies <empty>
// +1 implies <empty> (given set is prefix)
// +1 implies +1
// +1,-2 implies +1 (given set is prefix)
// +1,-2 implies +1,-2
// +1 implies +1 opt(2) (unused optional col is ignored)
// -2,+1 implies +1 opt(2) (optional col is ignored)
// +1 implies +(1|2) (subset of choice)
// +(1|2) implies +(1|2|3) (subset of choice)
// +(1|2),-4 implies +(1|2|3),-(4|5)
// +(1|2) opt(4) implies +(1|2|3) opt(4)
// +1,+2,+3 implies +(1|2),+3 (unused group columns become optional)
//
// <empty> !implies +1
// +1 !implies -1 (direction mismatch)
// +1 !implies +1,-2 (prefix matching not commutative)
// +1 opt(2) !implies +1 (extra optional cols not allowed)
// +1 opt(2) !implies +1 opt(3)
// +(1|2) !implies -(1|2) (direction mismatch)
// +(1|2) !implies +(3|4) (no intersection)
// +(1|2) !implies +(2|3) (intersects, but not subset)
// +(1|2|3) !implies +(1|2) (subset of choice not commutative)
// +(1|2) !implies +1 opt(2)
//
func (oc *OrderingChoice) Implies(other *OrderingChoice) bool {
if !oc.Optional.SubsetOf(other.Optional) {
return false
}
// Copy the optional columns so we can add to the set. All group columns
// become optional after one column in the group is used.
optional := colSetHelper{colSet: other.Optional}
for left, right := 0, 0; right < len(other.Columns); {
if left >= len(oc.Columns) {
return false
}
leftCol, rightCol := &oc.Columns[left], &other.Columns[right]
switch {
case leftCol.Descending == rightCol.Descending && leftCol.Group.SubsetOf(rightCol.Group):
// The columns match.
optional.unionWith(rightCol.Group)
left, right = left+1, right+1
case optional.intersects(leftCol.Group):
// Left column is optional in the right set.
left++
default:
return false
}
}
return true
}
// Intersects returns true if there are orderings that satisfy both
// OrderingChoices. See Intersection for more information.
func (oc *OrderingChoice) Intersects(other *OrderingChoice) bool {
// Copy the optional columns so we can add to the sets. All group columns
// become optional after one column in the group is used.
leftOptional := colSetHelper{colSet: oc.Optional}
rightOptional := colSetHelper{colSet: other.Optional}
for left, right := 0, 0; left < len(oc.Columns) && right < len(other.Columns); {
leftCol, rightCol := &oc.Columns[left], &other.Columns[right]
switch {
case leftCol.Descending == rightCol.Descending && leftCol.Group.Intersects(rightCol.Group):
// The columns match.
leftOptional.unionWith(leftCol.Group)
rightOptional.unionWith(rightCol.Group)
left, right = left+1, right+1
case rightOptional.intersects(leftCol.Group):
// Left column is optional in the right set.
leftOptional.unionWith(leftCol.Group)
left++
case leftOptional.intersects(rightCol.Group):
// Right column is optional in the left set.
rightOptional.unionWith(rightCol.Group)
right++
default:
return false
}
}
return true
}
// Intersection returns an OrderingChoice that Implies both ordering choices.
// Can only be called if Intersects is true. Some examples:
//
// +1 ∩ <empty> = +1
// +1 ∩ +1,+2 = +1,+2
// +1,+2 opt(3) ∩ +1,+3 = +1,+3,+2
//
// In general, OrderingChoice is not expressive enough to represent the
// intersection. In such cases, an OrderingChoice representing a subset of the
// intersection is returned. For example,
// +1 opt(2) ∩ +2 opt(1)
// can be either +1,+2 or +2,+1; only one of these is returned. Note that
// the function may not be commutative in this case. In practice, such cases are
// unlikely.
//
// It is guaranteed that if one OrderingChoice Implies the other, it will also
// be the Intersection.
func (oc *OrderingChoice) Intersection(other *OrderingChoice) OrderingChoice {
// We have to handle Any cases separately because an Any ordering choice has
// no optional columns (even though semantically it should have all possible
// columns as optional).
if oc.Any() {
return other.Copy()
}
if other.Any() {
return oc.Copy()
}
result := make([]OrderingColumnChoice, 0, len(oc.Columns)+len(other.Columns))
// Copy the optional columns so we can add to the sets. All group columns
// become optional after one column in the group is used.
leftOptional := colSetHelper{colSet: oc.Optional}
rightOptional := colSetHelper{colSet: other.Optional}
left, right := 0, 0
for left < len(oc.Columns) && right < len(other.Columns) {
leftCol, rightCol := &oc.Columns[left], &other.Columns[right]
switch {
case leftCol.Descending == rightCol.Descending && leftCol.Group.Intersects(rightCol.Group):
// The columns match.
result = append(result, OrderingColumnChoice{
Group: leftCol.Group.Intersection(rightCol.Group),
Descending: leftCol.Descending,
})
leftOptional.unionWith(leftCol.Group)
rightOptional.unionWith(rightCol.Group)
left, right = left+1, right+1
case rightOptional.intersects(leftCol.Group):
// Left column is optional in the right set.
result = append(result, OrderingColumnChoice{
Group: rightOptional.intersection(leftCol.Group),
Descending: leftCol.Descending,
})
leftOptional.unionWith(leftCol.Group)
left++
case leftOptional.intersects(rightCol.Group):
// Right column is optional in the left set.
result = append(result, OrderingColumnChoice{
Group: leftOptional.intersection(rightCol.Group),
Descending: rightCol.Descending,
})
rightOptional.unionWith(rightCol.Group)
right++
default:
panic(errors.AssertionFailedf("non-intersecting sets"))
}
}
// An ordering matched a prefix of the other. Append the tail of the other
// ordering.
for ; left < len(oc.Columns); left++ {
result = append(result, oc.Columns[left])
}
for ; right < len(other.Columns); right++ {
result = append(result, other.Columns[right])
}
var optional opt.ColSet
// If Columns is empty, then Optional must be as well. len(result) should
// never be zero here, but check anyway in case the logic changes.
if len(result) != 0 {
optional = oc.Optional.Intersection(other.Optional)
}
return OrderingChoice{
Optional: optional,
Columns: result,
}
}
// CommonPrefix is similar to Intersection, but it does not panic if the orderings
// are non-intersecting. Instead, it returns the longest prefix of intersecting
// columns. Some examples:
//
// +1 common prefix <empty> = <empty>
// +1 common prefix +1,+2 = +1
// +1,+2 opt(3) common prefix +1,+3 = +1,+3
//
// Note that CommonPrefix is asymmetric: optional columns of oc will be used to
// match trailing columns of other, but the reverse is not true. For example:
//
// +1 opt(2) common prefix +1,+2 = +1,+2
// +1,+2 common prefix +1 opt(2) = +1
//
func (oc *OrderingChoice) CommonPrefix(other *OrderingChoice) OrderingChoice {
if oc.Any() || other.Any() {
return OrderingChoice{}
}
result := make([]OrderingColumnChoice, 0, len(oc.Columns))
leftOptional := colSetHelper{colSet: oc.Optional}
rightOptional := colSetHelper{colSet: other.Optional}
left, right := 0, 0
for left < len(oc.Columns) && right < len(other.Columns) {
leftCol, rightCol := &oc.Columns[left], &other.Columns[right]
switch {
case leftCol.Descending == rightCol.Descending && leftCol.Group.Intersects(rightCol.Group):
// The columns match.
result = append(result, OrderingColumnChoice{
Group: leftCol.Group.Intersection(rightCol.Group),
Descending: leftCol.Descending,
})
leftOptional.unionWith(leftCol.Group)
rightOptional.unionWith(rightCol.Group)
left, right = left+1, right+1
case rightOptional.intersects(leftCol.Group):
// Left column is optional in the right set.
result = append(result, OrderingColumnChoice{
Group: rightOptional.intersection(leftCol.Group),
Descending: leftCol.Descending,
})
leftOptional.unionWith(leftCol.Group)
left++
case leftOptional.intersects(rightCol.Group):
// Right column is optional in the left set.
result = append(result, OrderingColumnChoice{
Group: leftOptional.intersection(rightCol.Group),
Descending: rightCol.Descending,
})
rightOptional.unionWith(rightCol.Group)
right++
default:
var optional opt.ColSet
// If Columns is empty, then Optional must be as well.
if len(result) != 0 {
optional = oc.Optional.Intersection(other.Optional)
}
return OrderingChoice{
Optional: optional,
Columns: result,
}
}
}
// oc matched a prefix of other. Append the tail of the other ordering that is
// included in the optional columns of oc.
for ; right < len(other.Columns); right++ {
rightCol := &other.Columns[right]
if !leftOptional.intersects(rightCol.Group) {
break
}
result = append(result, OrderingColumnChoice{
Group: leftOptional.intersection(rightCol.Group),
Descending: rightCol.Descending,
})
}
var optional opt.ColSet
// If Columns is empty, then Optional must be as well.
if len(result) != 0 {
optional = oc.Optional.Intersection(other.Optional)
}
return OrderingChoice{
Optional: optional,
Columns: result,
}
}
// commonPrefixLength returns the length of the OrderingChoice that would be
// returned by CommonPrefix, along with a boolean indicating whether the common
// prefix between the receiver and 'other' implies 'other'.
func (oc *OrderingChoice) commonPrefixLength(other *OrderingChoice) (length int, implies bool) {
if oc.Any() || other.Any() {
return 0 /* length */, other.Any()
}
leftOptional := colSetHelper{colSet: oc.Optional}
rightOptional := colSetHelper{colSet: other.Optional}
left, right := 0, 0
for left < len(oc.Columns) && right < len(other.Columns) {
leftCol, rightCol := &oc.Columns[left], &other.Columns[right]
switch {
case leftCol.Descending == rightCol.Descending && leftCol.Group.Intersects(rightCol.Group):
// The columns match.
length++
leftOptional.unionWith(leftCol.Group)
rightOptional.unionWith(rightCol.Group)
left, right = left+1, right+1
case rightOptional.intersects(leftCol.Group):
// Left column is optional in the right set.
length++
leftOptional.unionWith(leftCol.Group)
left++
case leftOptional.intersects(rightCol.Group):
// Right column is optional in the left set.
length++
rightOptional.unionWith(rightCol.Group)
right++
default:
// If we have reached this point, the common prefix will not include all
// of the non-optional columns from the 'other' OrderingChoice, and
// therefore will not imply 'other'.
return length, false
}
}
// oc matched a prefix of other. Append the tail of the other ordering that is
// included in the optional columns of oc.
for ; right < len(other.Columns); right++ {
rightCol := &other.Columns[right]
if !leftOptional.intersects(rightCol.Group) {
break
}
length++
}
// The common prefix will imply 'other' if and only if it includes all of the
// non-optional columns from 'other'.
return length, right == len(other.Columns)
}
// SubsetOfCols is true if the OrderingChoice only references columns in the
// given set.
func (oc *OrderingChoice) SubsetOfCols(cs opt.ColSet) bool {
if !oc.Optional.SubsetOf(cs) {
return false
}
for i := range oc.Columns {
if !oc.Group(i).SubsetOf(cs) {
return false
}
}
return true
}
// CanProjectCols is true if at least one column in each ordering column group is
// part of the given column set. For example, if the OrderingChoice is:
//
// +1,-(2|3) opt(4,5)
//
// then CanProjectCols will behave as follows for these input sets:
//
// (1,2) => true
// (1,3) => true
// (1,2,4) => true
// (1) => false
// (3,4) => false
//
func (oc *OrderingChoice) CanProjectCols(cs opt.ColSet) bool {
for i := range oc.Columns {
if !oc.Group(i).Intersects(cs) {
return false
}
}
return true
}
// MatchesAt returns true if the ordering column at the given index in this
// instance matches the given column. The column matches if its id is part of
// the equivalence group and if it has the same direction.
func (oc *OrderingChoice) MatchesAt(index int, col opt.OrderingColumn) bool {
if oc.Optional.Contains(col.ID()) {
return true
}
choice := &oc.Columns[index]
if choice.Descending != col.Descending() {
return false
}
if !choice.Group.Contains(col.ID()) {
return false
}
return true
}
// AppendCol adds a new column to the end of the sequence of ordering columns
// maintained by this instance. The new column has the given ID and direction as
// the only ordering choice.
func (oc *OrderingChoice) AppendCol(id opt.ColumnID, descending bool) {
ordCol := OrderingColumnChoice{Descending: descending}
ordCol.Group.Add(id)
oc.Optional.Remove(id)
oc.Columns = append(oc.Columns, ordCol)
}
// Copy returns a complete copy of this instance, with a private version of the
// ordering column array.
func (oc *OrderingChoice) Copy() OrderingChoice {
var other OrderingChoice
other.Optional = oc.Optional.Copy()
other.Columns = make([]OrderingColumnChoice, len(oc.Columns))
copy(other.Columns, oc.Columns)
return other
}
// CanSimplify returns true if a call to Simplify would result in any changes to
// the OrderingChoice. Changes include additional constant columns, removed
// groups, additional equivalent columns, or removed non-equivalent columns.
// This is used to quickly check whether Simplify needs to be called without
// requiring allocations in the common case. This logic should be changed in
// concert with the Simplify logic.
func (oc *OrderingChoice) CanSimplify(fdset *FuncDepSet) bool {
if oc.Any() {
// Any ordering allowed, so can't simplify further.
return false
}
// Check whether optional columns can be added by the FD set.
optional := fdset.ComputeClosure(oc.Optional)
if !optional.Equals(oc.Optional) {
return true
}
closure := optional
for i := range oc.Columns {
group := &oc.Columns[i]
// If group contains an optional column, then group can be simplified
// or removed entirely.
if group.Group.Intersects(optional) {
return true
}
// If group is functionally determined by previous groups, then it can
// be removed entirely.
if group.Group.SubsetOf(closure) {
return true
}
// Check whether the equivalency group needs to change based on the FD.
equiv := fdset.ComputeEquivGroup(group.AnyID())
if !equiv.Equals(group.Group) {
return true
}
// Add this group's columns and find closure with new columns.
closure.UnionWith(equiv)
closure = fdset.ComputeClosure(closure)
}
return false
}
// Simplify uses the given FD set to streamline the orderings allowed by this
// instance. It can both increase and decrease the number of allowed orderings:
//
// 1. Constant columns add additional optional column choices.
//
// 2. Equivalent columns allow additional choices within an ordering column
// group.
//
// 3. Non-equivalent columns in an ordering column group are removed.
//
// 4. If the columns in a group are functionally determined by columns from
// previous groups, the group can be dropped. This technique is described
// in the "Reduce Order" section of this paper:
//
// Simmen, David & Shekita, Eugene & Malkemus, Timothy. (1996).
// Fundamental Techniques for Order Optimization.
// Sigmod Record. Volume 25 Issue 2, June 1996. Pages 57-67.
// https://cs.uwaterloo.ca/~gweddell/cs798/p57-simmen.pdf
//
// This logic should be changed in concert with the CanSimplify logic.
func (oc *OrderingChoice) Simplify(fdset *FuncDepSet) {
oc.Optional = fdset.ComputeClosure(oc.Optional)
closure := oc.Optional
n := 0
for i := range oc.Columns {
group := &oc.Columns[i]
// Constant columns from the FD set become optional ordering columns and
// so can be removed.
if group.Group.Intersects(oc.Optional) {
if group.Group.SubsetOf(oc.Optional) {
continue
}
group.Group = group.Group.Difference(oc.Optional)
}
// If this group is functionally determined from previous groups, then
// discard it.
if group.Group.SubsetOf(closure) {
continue
}
// Set group to columns equivalent to an arbitrary column in the group
// based on the FD set. This can both add and remove columns from the
// group.
group.Group = fdset.ComputeEquivGroup(group.AnyID())
// Add this group's columns and find closure with the new columns.
closure = closure.Union(group.Group)
closure = fdset.ComputeClosure(closure)
if n != i {
oc.Columns[n] = oc.Columns[i]
}
n++
}
oc.Columns = oc.Columns[:n]
if len(oc.Columns) == 0 {
// Normalize Any case by dropping any optional columns.
oc.Optional = opt.ColSet{}
}
}
// Truncate removes all ordering columns beyond the given index. For example,
// +1,+(2|3),-4 opt(5,6) would be truncated to:
//
// prefix=0 => opt(5,6)
// prefix=1 => +1 opt(5,6)
// prefix=2 => +1,+(2|3) opt(5,6)
// prefix=3+ => +1,+(2|3),-4 opt(5,6)
//
func (oc *OrderingChoice) Truncate(prefix int) {
if prefix < len(oc.Columns) {
oc.Columns = oc.Columns[:prefix]
if len(oc.Columns) == 0 {
// Normalize Any case by dropping any optional columns.
oc.Optional = opt.ColSet{}
}
}
}
// ProjectCols removes any references to columns that are not in the given
// set. This method can only be used when the OrderingChoice can be expressed
// with the given columns; i.e. all groups have at least one column in the set.
func (oc *OrderingChoice) ProjectCols(cols opt.ColSet) {
startLen := len(oc.Columns)
oc.RestrictToCols(cols)
if startLen != len(oc.Columns) {
panic(errors.AssertionFailedf("no columns left from group"))
}
}
// RestrictToCols removes any references to columns that are not in the given
// set. If a group does not contain any columns in the given set, it removes
// that group and all following groups.
func (oc *OrderingChoice) RestrictToCols(cols opt.ColSet) {
if !oc.Optional.SubsetOf(cols) {
oc.Optional = oc.Optional.Intersection(cols)
}
for i := range oc.Columns {
if !oc.Group(i).SubsetOf(cols) {
oc.Columns[i].Group = oc.Group(i).Intersection(cols)
if oc.Group(i).Empty() {
oc.Columns = oc.Columns[:i]
break
}
}
}
// Normalize when OrderingChoice is Any.
if oc.Any() {
oc.Optional = opt.ColSet{}
}
}
// PrefixIntersection computes an OrderingChoice which:
// - implies <oc> (this instance), and
// - implies a "segmented ordering", which is any ordering which starts with a
// permutation of all columns in <prefix> followed by the <suffix> ordering.
//
// Note that <prefix> and <suffix> cannot have any columns in common.
//
// Such an ordering can be computed via the following rules:
//
// - if <prefix> and <suffix> are empty: return this instance.
//
// - if <oc> is empty: generate an arbitrary segmented ordering.
//
// - if the first column of <oc> is either in <prefix> or is the first column
// of <suffix> while <prefix> is empty: this column is the first column of
// the result; calculate the rest recursively.
//
func (oc OrderingChoice) PrefixIntersection(
prefix opt.ColSet, suffix []OrderingColumnChoice,
) (_ OrderingChoice, ok bool) {
var result OrderingChoice
oc = oc.Copy()
prefixHelper := colSetHelper{colSet: prefix}
for {
switch {
case prefixHelper.empty() && len(suffix) == 0:
// Any ordering is allowed by <prefix>+<suffix>, so use <oc> directly.
result.Columns = append(result.Columns, oc.Columns...)
return result, true
case len(oc.Columns) == 0:
// Any ordering is allowed by <oc>, so pick an arbitrary ordering of the
// columns in <prefix> then append suffix.
// TODO(justin): investigate picking an order more intelligently here.
for col, ok := prefixHelper.next(0); ok; col, ok = prefixHelper.next(col + 1) {
result.AppendCol(col, false /* descending */)
}
result.Columns = append(result.Columns, suffix...)
return result, true
case prefixHelper.empty() && len(oc.Columns) > 0 && len(suffix) > 0 &&
oc.Group(0).Intersects(suffix[0].Group) &&
oc.Columns[0].Descending == suffix[0].Descending:
// <prefix> is empty, and <suffix> and <oc> agree on the first column, so
// emit that column, remove it from both, and loop.
newCol := oc.Columns[0]
newCol.Group = oc.Group(0).Intersection(suffix[0].Group)
result.Columns = append(result.Columns, newCol)
oc.Columns = oc.Columns[1:]
suffix = suffix[1:]
case len(oc.Columns) > 0 && prefixHelper.intersects(oc.Group(0)):
// <prefix> contains the first column in <oc>, so emit it and remove it
// from both.
result.Columns = append(result.Columns, oc.Columns[0])
prefixHelper.differenceWith(oc.Group(0))
oc.Columns = oc.Columns[1:]
default:
// If no rule applied, fail.
return OrderingChoice{}, false
}
}
}
// Equals returns true if the set of orderings matched by this instance is the
// same as the set matched by the given instance.
func (oc *OrderingChoice) Equals(rhs *OrderingChoice) bool {
if len(oc.Columns) != len(rhs.Columns) {
return false
}
if !oc.Optional.Equals(rhs.Optional) {
return false
}
for i := range oc.Columns {
left := &oc.Columns[i]
y := &rhs.Columns[i]
if left.Descending != y.Descending {
return false
}
if !left.Group.Equals(y.Group) {
return false
}
}
return true
}
// Group returns the group of this instance's column col.
func (oc *OrderingChoice) Group(col int) opt.ColSet {
if col < 0 || col >= len(oc.Columns) {
return opt.ColSet{}
}
return oc.Columns[col].Group
}
func (oc OrderingChoice) String() string {
var buf bytes.Buffer
oc.Format(&buf)
return buf.String()
}
// Format writes the OrderingChoice to the given buffer in a human-readable
// string representation that can also be parsed by ParseOrderingChoice:
//
// +1
// +1,-2
// +(1|2)
// +(1|2),+3
// -(3|4),+5 opt(1,2)
//
func (oc OrderingChoice) Format(buf *bytes.Buffer) {
for g := range oc.Columns {
group := &oc.Columns[g]
count := group.Group.Len()
if group.Descending {
buf.WriteByte('-')
} else {
buf.WriteByte('+')
}
// Write equivalence group.
if count > 1 {
buf.WriteByte('(')
}
first := true
for i, ok := group.Group.Next(0); ok; i, ok = group.Group.Next(i + 1) {
if !first {
buf.WriteByte('|')
} else {
first = false
}
fmt.Fprintf(buf, "%d", i)
}
if count > 1 {
buf.WriteByte(')')
}
if g+1 != len(oc.Columns) {
buf.WriteByte(',')
}
}
// Write set of optional columns.
if !oc.Optional.Empty() {
if len(oc.Columns) != 0 {
buf.WriteByte(' ')
}
fmt.Fprintf(buf, "opt%s", oc.Optional)
}
}
// RemapColumns returns a copy of oc with all columns in from mapped to columns
// in to.
func (oc *OrderingChoice) RemapColumns(from, to opt.ColList) OrderingChoice {
var other OrderingChoice
other.Optional = opt.TranslateColSetStrict(oc.Optional, from, to)
other.Columns = make([]OrderingColumnChoice, len(oc.Columns))
for i := range oc.Columns {
col := &oc.Columns[i]
other.Columns[i] = OrderingColumnChoice{
Group: opt.TranslateColSetStrict(col.Group, from, to),
Descending: col.Descending,