-
Notifications
You must be signed in to change notification settings - Fork 3.8k
/
custom_funcs.go
3057 lines (2743 loc) · 109 KB
/
custom_funcs.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 xform
import (
"fmt"
"sort"
"github.com/cockroachdb/cockroach/pkg/geo/geoindex"
"github.com/cockroachdb/cockroach/pkg/sql/opt"
"github.com/cockroachdb/cockroach/pkg/sql/opt/cat"
"github.com/cockroachdb/cockroach/pkg/sql/opt/constraint"
"github.com/cockroachdb/cockroach/pkg/sql/opt/idxconstraint"
"github.com/cockroachdb/cockroach/pkg/sql/opt/invertedexpr"
"github.com/cockroachdb/cockroach/pkg/sql/opt/memo"
"github.com/cockroachdb/cockroach/pkg/sql/opt/norm"
"github.com/cockroachdb/cockroach/pkg/sql/opt/ordering"
"github.com/cockroachdb/cockroach/pkg/sql/opt/partialidx"
"github.com/cockroachdb/cockroach/pkg/sql/opt/props"
"github.com/cockroachdb/cockroach/pkg/sql/opt/props/physical"
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
"github.com/cockroachdb/cockroach/pkg/sql/types"
"github.com/cockroachdb/cockroach/pkg/util"
"github.com/cockroachdb/errors"
)
// CustomFuncs contains all the custom match and replace functions used by
// the exploration rules. The unnamed xfunc.CustomFuncs allows
// CustomFuncs to provide a clean interface for calling functions from both the
// xform and xfunc packages using the same struct.
type CustomFuncs struct {
norm.CustomFuncs
e *explorer
}
// Init initializes a new CustomFuncs with the given explorer.
func (c *CustomFuncs) Init(e *explorer) {
c.CustomFuncs.Init(e.f)
c.e = e
}
// ----------------------------------------------------------------------
//
// Scan Rules
// Custom match and replace functions used with scan.opt rules.
//
// ----------------------------------------------------------------------
// IsCanonicalScan returns true if the given ScanPrivate is an original
// unaltered primary index Scan operator (i.e. unconstrained and not limited).
func (c *CustomFuncs) IsCanonicalScan(scan *memo.ScanPrivate) bool {
return scan.IsCanonical()
}
// IsLocking returns true if the ScanPrivate is configured to use a row-level
// locking mode. This can be the case either because the Scan is in the scope of
// a SELECT .. FOR [KEY] UPDATE/SHARE clause or because the Scan was configured
// as part of the row retrieval of a DELETE or UPDATE statement.
func (c *CustomFuncs) IsLocking(scan *memo.ScanPrivate) bool {
return scan.IsLocking()
}
// GenerateIndexScans enumerates all non-inverted and non-partial secondary
// indexes on the given Scan operator's table and generates an alternate Scan
// operator for each index that includes the set of needed columns specified in
// the ScanOpDef.
//
// Partial indexes do not index every row in the table and they can only be used
// in cases where a query filter implies the partial index predicate.
// GenerateIndexScans does not deal with filters. Therefore, partial indexes
// cannot be considered for non-constrained index scans.
//
// NOTE: This does not generate index joins for non-covering indexes (except in
// case of ForceIndex). Index joins are usually only introduced "one level
// up", when the Scan operator is wrapped by an operator that constrains
// or limits scan output in some way (e.g. Select, Limit, InnerJoin).
// Index joins are only lower cost when their input does not include all
// rows from the table. See ConstrainScans and LimitScans for cases where
// index joins are introduced into the memo.
func (c *CustomFuncs) GenerateIndexScans(grp memo.RelExpr, scanPrivate *memo.ScanPrivate) {
// Iterate over all non-inverted and non-partial secondary indexes.
iter := makeScanIndexIter(c.e.mem, scanPrivate, rejectPrimaryIndex|rejectInvertedIndexes|rejectPartialIndexes)
for iter.Next() {
// If the secondary index includes the set of needed columns, then construct
// a new Scan operator using that index.
if iter.IsCovering() {
scan := memo.ScanExpr{ScanPrivate: *scanPrivate}
scan.Index = iter.IndexOrdinal()
c.e.mem.AddScanToGroup(&scan, grp)
continue
}
// Otherwise, if the index must be forced, then construct an IndexJoin
// operator that provides the columns missing from the index. Note that
// if ForceIndex=true, scanIndexIter only returns the one index that is
// being forced, so no need to check that here.
if !scanPrivate.Flags.ForceIndex {
continue
}
var sb indexScanBuilder
sb.init(c, scanPrivate.Table)
// Scan whatever columns we need which are available from the index, plus
// the PK columns.
newScanPrivate := *scanPrivate
newScanPrivate.Index = iter.IndexOrdinal()
newScanPrivate.Cols = iter.IndexColumns().Intersection(scanPrivate.Cols)
newScanPrivate.Cols.UnionWith(sb.primaryKeyCols())
sb.setScan(&newScanPrivate)
sb.addIndexJoin(scanPrivate.Cols)
sb.build(grp)
}
}
// ----------------------------------------------------------------------
//
// Select Rules
// Custom match and replace functions used with select.opt rules.
//
// ----------------------------------------------------------------------
// GeneratePartialIndexScans generates unconstrained index scans over all
// partial indexes with predicates that are implied by the filters. Partial
// indexes with predicates which cannot be proven to be implied by the filters
// are disregarded.
//
// When a filter completely matches the predicate, the remaining filters are
// simplified so that they do not include the filter. A redundant filter is
// unnecessary to include in the remaining filters because a scan over the partial
// index implicitly filters the results.
//
// For every partial index that is implied by the filters, a Scan will be
// generated along with a combination of an IndexJoin and Selects. There are
// three questions to consider which determine which operators are generated.
//
// 1. Does the index "cover" the columns needed?
// 2. Are there any remaining filters to apply after the Scan?
// 3. If there are remaining filters does the index cover the referenced
// columns?
//
// If the index covers the columns needed, no IndexJoin is need. The two
// possible generated expressions are either a lone Scan or a Scan wrapped in a
// Select that applies any remaining filters.
//
// (Scan $scanDef)
//
// (Select (Scan $scanDef) $remainingFilters)
//
// If the index is not covering, then an IndexJoin is required to retrieve the
// needed columns. Some or all of the remaining filters may be required to be
// applied after the IndexJoin, because they reference columns not covered by
// the index. Therefore, Selects can be constructed before, after, or both
// before and after the IndexJoin depending on the columns referenced in the
// remaining filters.
//
// If the index is not covering, then an IndexJoin is required to retrieve the
// needed columns. Some of the remaining filters may be applied in a Select
// before the IndexJoin, if all the columns referenced in the filter are covered
// by the index. Some of the remaining filters may be applied in a Select after
// the IndexJoin, if their columns are not covered. Therefore, Selects can be
// constructed before, after, or both before and after the IndexJoin.
//
// (IndexJoin (Scan $scanDef) $indexJoinDef)
//
// (IndexJoin
// (Select (Scan $scanDef) $remainingFilters)
// $indexJoinDef
// )
//
// (Select
// (IndexJoin (Scan $scanDef) $indexJoinDef)
// $outerFilter
// )
//
// (Select
// (IndexJoin
// (Select (Scan $scanDef) $innerFilter)
// $indexJoinDef
// )
// $outerFilter
// )
//
func (c *CustomFuncs) GeneratePartialIndexScans(
grp memo.RelExpr, scanPrivate *memo.ScanPrivate, filters memo.FiltersExpr,
) {
md := c.e.mem.Metadata()
tabMeta := md.TableMeta(scanPrivate.Table)
// Iterate over all partial indexes.
iter := makeScanIndexIter(c.e.mem, scanPrivate, rejectNonPartialIndexes)
for iter.Next() {
pred := tabMeta.PartialIndexPredicates[iter.IndexOrdinal()]
remainingFilters, ok := partialidx.FiltersImplyPredicate(filters, pred, c.e.f)
if !ok {
// The filters do not imply the predicate, so the partial index
// cannot be used.
continue
}
var sb indexScanBuilder
sb.init(c, scanPrivate.Table)
newScanPrivate := *scanPrivate
newScanPrivate.Index = iter.IndexOrdinal()
// If index is covering, just add a Select with the remaining filters,
// if there are any.
if iter.IsCovering() {
sb.setScan(&newScanPrivate)
sb.addSelect(remainingFilters)
sb.build(grp)
continue
}
// If the index is not covering, scan the needed index columns plus
// primary key columns.
newScanPrivate.Cols = iter.IndexColumns().Intersection(scanPrivate.Cols)
newScanPrivate.Cols.UnionWith(sb.primaryKeyCols())
sb.setScan(&newScanPrivate)
// Add a Select with any remaining filters that can be filtered before
// the IndexJoin. If there are no remaining filters this is a no-op. If
// all or parts of the remaining filters cannot be applied until after
// the IndexJoin, the new value of remainingFilters will contain those
// filters.
remainingFilters = sb.addSelectAfterSplit(remainingFilters, newScanPrivate.Cols)
// Add an IndexJoin to retrieve the columns not provided by the Scan.
sb.addIndexJoin(scanPrivate.Cols)
// Add a Select with any remaining filters.
sb.addSelect(remainingFilters)
sb.build(grp)
}
}
// GenerateConstrainedScans enumerates all non-inverted secondary indexes on the
// Scan operator's table and tries to push the given Select filter into new
// constrained Scan operators using those indexes. Since this only needs to be
// done once per table, GenerateConstrainedScans should only be called on the
// original unaltered primary index Scan operator (i.e. not constrained or
// limited).
//
// For each secondary index that "covers" the columns needed by the scan, there
// are three cases:
//
// - a filter that can be completely converted to a constraint over that index
// generates a single constrained Scan operator (to be added to the same
// group as the original Select operator):
//
// (Scan $scanDef)
//
// - a filter that can be partially converted to a constraint over that index
// generates a constrained Scan operator in a new memo group, wrapped in a
// Select operator having the remaining filter (to be added to the same group
// as the original Select operator):
//
// (Select (Scan $scanDef) $filter)
//
// - a filter that cannot be converted to a constraint generates nothing
//
// And for a secondary index that does not cover the needed columns:
//
// - a filter that can be completely converted to a constraint over that index
// generates a single constrained Scan operator in a new memo group, wrapped
// in an IndexJoin operator that looks up the remaining needed columns (and
// is added to the same group as the original Select operator)
//
// (IndexJoin (Scan $scanDef) $indexJoinDef)
//
// - a filter that can be partially converted to a constraint over that index
// generates a constrained Scan operator in a new memo group, wrapped in an
// IndexJoin operator that looks up the remaining needed columns; the
// remaining filter is distributed above and/or below the IndexJoin,
// depending on which columns it references:
//
// (IndexJoin
// (Select (Scan $scanDef) $filter)
// $indexJoinDef
// )
//
// (Select
// (IndexJoin (Scan $scanDef) $indexJoinDef)
// $filter
// )
//
// (Select
// (IndexJoin
// (Select (Scan $scanDef) $innerFilter)
// $indexJoinDef
// )
// $outerFilter
// )
//
// GenerateConstrainedScans will further constrain the enumerated index scans
// by trying to use the check constraints and computed columns that apply to the
// table being scanned, as well as the partitioning defined for the index. See
// comments above checkColumnFilters, computedColFilters, and
// partitionValuesFilters for more detail.
func (c *CustomFuncs) GenerateConstrainedScans(
grp memo.RelExpr, scanPrivate *memo.ScanPrivate, explicitFilters memo.FiltersExpr,
) {
var sb indexScanBuilder
sb.init(c, scanPrivate.Table)
// Generate implicit filters from constraints and computed columns and add
// them to the list of explicit filters provided in the query.
optionalFilters := c.checkConstraintFilters(scanPrivate.Table)
computedColFilters := c.computedColFilters(scanPrivate.Table, explicitFilters, optionalFilters)
optionalFilters = append(optionalFilters, computedColFilters...)
filterColumns := c.FilterOuterCols(explicitFilters)
filterColumns.UnionWith(c.FilterOuterCols(optionalFilters))
// Iterate over all non-inverted indexes.
md := c.e.mem.Metadata()
tabMeta := md.TableMeta(scanPrivate.Table)
iter := makeScanIndexIter(c.e.mem, scanPrivate, rejectInvertedIndexes|rejectPartialIndexes)
for iter.Next() {
// TODO(mgartner): Generate constrained scans for partial indexes if
// they are implied by the filter.
// We only consider the partition values when a particular index can otherwise
// not be constrained. For indexes that are constrained, the partitioned values
// add no benefit as they don't really constrain anything.
// Furthermore, if the filters don't take advantage of the index (use any of the
// index columns), using the partition values add no benefit.
//
// If the index is partitioned (by list), we generate two constraints and
// union them: the "main" constraint and the "in-between" constraint.The
// "main" constraint restricts the index to the known partition ranges. The
// "in-between" constraint restricts the index to the rest of the ranges
// (i.e. everything that falls in-between the main ranges); the in-between
// constraint is necessary for correctness (there can be rows outside of the
// partitioned ranges).
//
// For both constraints, the partition-related filters are passed as
// "optional" which guarantees that they return no remaining filters. This
// allows us to merge the remaining filters from both constraints.
//
// Consider the following index and its partition:
//
// CREATE INDEX orders_by_seq_num
// ON orders (region, seq_num, id)
// STORING (total)
// PARTITION BY LIST (region)
// (
// PARTITION us_east1 VALUES IN ('us-east1'),
// PARTITION us_west1 VALUES IN ('us-west1'),
// PARTITION europe_west2 VALUES IN ('europe-west2')
// )
//
// The constraint generated for the query:
// SELECT sum(total) FROM orders WHERE seq_num >= 100 AND seq_num < 200
// is:
// [/'europe-west2'/100 - /'europe-west2'/199]
// [/'us-east1'/100 - /'us-east1'/199]
// [/'us-west1'/100 - /'us-west1'/199]
//
// The spans before europe-west2, after us-west1 and in between the defined
// partitions are missing. We must add these spans now, appropriately
// constrained using the filters.
//
// It is important that we add these spans after the partition spans are generated
// because otherwise these spans would merge with the partition spans and would
// disallow the partition spans (and the in between ones) to be constrained further.
// Using the partitioning example and the query above, if we added the in between
// spans at the same time as the partitioned ones, we would end up with a span that
// looked like:
// [ - /'europe-west2'/99]
//
// Allowing the partition spans to be constrained further and then adding
// the spans give us a more constrained index scan as shown below:
// [ - /'europe-west2')
// [/'europe-west2'/100 - /'europe-west2'/199]
// [/e'europe-west2\x00'/100 - /'us-east1')
// [/'us-east1'/100 - /'us-east1'/199]
// [/e'us-east1\x00'/100 - /'us-west1')
// [/'us-west1'/100 - /'us-west1'/199]
// [/e'us-west1\x00'/100 - ]
//
// Notice how we 'skip' all the europe-west2 rows with seq_num < 100.
//
var partitionFilters, inBetweenFilters memo.FiltersExpr
indexColumns := tabMeta.IndexKeyColumns(iter.IndexOrdinal())
firstIndexCol := scanPrivate.Table.ColumnID(iter.Index().Column(0).Ordinal)
if !filterColumns.Contains(firstIndexCol) && indexColumns.Intersects(filterColumns) {
// Calculate any partition filters if appropriate (see below).
partitionFilters, inBetweenFilters = c.partitionValuesFilters(scanPrivate.Table, iter.Index())
}
// Check whether the filter (along with any partitioning filters) can constrain the index.
constraint, remainingFilters, ok := c.tryConstrainIndex(
explicitFilters,
append(optionalFilters, partitionFilters...),
scanPrivate.Table,
iter.IndexOrdinal(),
false, /* isInverted */
)
if !ok {
continue
}
if len(partitionFilters) > 0 {
inBetweenConstraint, inBetweenRemainingFilters, ok := c.tryConstrainIndex(
explicitFilters,
append(optionalFilters, inBetweenFilters...),
scanPrivate.Table,
iter.IndexOrdinal(),
false, /* isInverted */
)
if !ok {
panic(errors.AssertionFailedf("in-between filters didn't yield a constraint"))
}
constraint.UnionWith(c.e.evalCtx, inBetweenConstraint)
// Even though the partitioned constraints and the inBetween constraints
// were consolidated, we must make sure their Union is as well.
constraint.ConsolidateSpans(c.e.evalCtx)
// Add all remaining filters that need to be present in the
// inBetween spans. Some of the remaining filters are common
// between them, so we must deduplicate them.
remainingFilters = c.ConcatFilters(remainingFilters, inBetweenRemainingFilters)
remainingFilters.Sort()
remainingFilters.Deduplicate()
}
// Construct new constrained ScanPrivate.
newScanPrivate := *scanPrivate
newScanPrivate.Index = iter.IndexOrdinal()
newScanPrivate.Constraint = constraint
// Record whether we were able to use partitions to constrain the scan.
newScanPrivate.PartitionConstrainedScan = (len(partitionFilters) > 0)
// If the alternate index includes the set of needed columns, then construct
// a new Scan operator using that index.
if iter.IsCovering() {
sb.setScan(&newScanPrivate)
// If there are remaining filters, then the constrained Scan operator
// will be created in a new group, and a Select operator will be added
// to the same group as the original operator.
sb.addSelect(remainingFilters)
sb.build(grp)
continue
}
// Otherwise, construct an IndexJoin operator that provides the columns
// missing from the index.
if scanPrivate.Flags.NoIndexJoin {
continue
}
// Scan whatever columns we need which are available from the index, plus
// the PK columns.
newScanPrivate.Cols = iter.IndexColumns().Intersection(scanPrivate.Cols)
newScanPrivate.Cols.UnionWith(sb.primaryKeyCols())
sb.setScan(&newScanPrivate)
// If remaining filter exists, split it into one part that can be pushed
// below the IndexJoin, and one part that needs to stay above.
remainingFilters = sb.addSelectAfterSplit(remainingFilters, newScanPrivate.Cols)
sb.addIndexJoin(scanPrivate.Cols)
sb.addSelect(remainingFilters)
sb.build(grp)
}
}
// checkConstraintFilters generates all filters that we can derive from the
// check constraints. These are constraints that have been validated and are
// non-nullable. We only use non-nullable check constraints because they
// behave differently from filters on NULL. Check constraints are satisfied
// when their expression evaluates to NULL, while filters are not.
//
// For example, the check constraint a > 1 is satisfied if a is NULL but the
// equivalent filter a > 1 is not.
//
// These filters do not really filter any rows, they are rather facts or
// guarantees about the data but treating them as filters may allow some
// indexes to be constrained and used. Consider the following example:
//
// CREATE TABLE abc (
// a INT PRIMARY KEY,
// b INT NOT NULL,
// c STRING NOT NULL,
// CHECK (a < 10 AND a > 1),
// CHECK (b < 10 AND b > 1),
// CHECK (c in ('first', 'second')),
// INDEX secondary (b, a),
// INDEX tertiary (c, b, a))
//
// Now consider the query: SELECT a, b WHERE a > 5
//
// Notice that the filter provided previously wouldn't let the optimizer use
// the secondary or tertiary indexes. However, given that we can use the
// constraints on a, b and c, we can actually use the secondary and tertiary
// indexes. In fact, for the above query we can do the following:
//
// select
// ├── columns: a:1(int!null) b:2(int!null)
// ├── scan abc@tertiary
// │ ├── columns: a:1(int!null) b:2(int!null)
// │ └── constraint: /3/2/1: [/'first'/2/6 - /'first'/9/9] [/'second'/2/6 - /'second'/9/9]
// └── filters
// └── gt [type=bool]
// ├── variable: a [type=int]
// └── const: 5 [type=int]
//
// Similarly, the secondary index could also be used. All such index scans
// will be added to the memo group.
func (c *CustomFuncs) checkConstraintFilters(tabID opt.TableID) memo.FiltersExpr {
md := c.e.mem.Metadata()
tabMeta := md.TableMeta(tabID)
if tabMeta.Constraints == nil {
return memo.FiltersExpr{}
}
filters := *tabMeta.Constraints.(*memo.FiltersExpr)
// Limit slice capacity to allow the caller to append if necessary.
return filters[:len(filters):len(filters)]
}
// computedColFilters generates all filters that can be derived from the list of
// computed column expressions from the given table. A computed column can be
// used as a filter when it has a constant value. That is true when:
//
// 1. All other columns it references are constant, because other filters in
// the query constrain them to be so.
// 2. All functions in the computed column expression can be folded into
// constants (i.e. they do not have problematic side effects).
//
// Note that computed columns can depend on other computed columns; in general
// the dependencies form an acyclic directed graph. computedColFilters will
// return filters for all constant computed columns, regardless of the order of
// their dependencies.
//
// As with checkConstraintFilters, computedColFilters do not really filter any
// rows, they are rather facts or guarantees about the data. Treating them as
// filters may allow some indexes to be constrained and used. Consider the
// following example:
//
// CREATE TABLE t (
// k INT NOT NULL,
// hash INT AS (k % 4) STORED,
// PRIMARY KEY (hash, k)
// )
//
// SELECT * FROM t WHERE k = 5
//
// Notice that the filter provided explicitly wouldn't allow the optimizer to
// seek using the primary index (it would have to fall back to a table scan).
// However, column "hash" can be proven to have the constant value of 1, since
// it's dependent on column "k", which has the constant value of 5. This enables
// usage of the primary index:
//
// scan t
// ├── columns: k:1(int!null) hash:2(int!null)
// ├── constraint: /2/1: [/1/5 - /1/5]
// ├── key: (2)
// └── fd: ()-->(1)
//
// The values of both columns in that index are known, enabling a single value
// constraint to be generated.
func (c *CustomFuncs) computedColFilters(
tabID opt.TableID, requiredFilters, optionalFilters memo.FiltersExpr,
) memo.FiltersExpr {
tabMeta := c.e.mem.Metadata().TableMeta(tabID)
if len(tabMeta.ComputedCols) == 0 {
return nil
}
// Start with set of constant columns, as derived from the list of filter
// conditions.
constCols := make(map[opt.ColumnID]opt.ScalarExpr)
c.findConstantFilterCols(constCols, tabID, requiredFilters)
c.findConstantFilterCols(constCols, tabID, optionalFilters)
if len(constCols) == 0 {
// No constant values could be derived from filters, so assume that there
// are also no constant computed columns.
return nil
}
// Construct a new filter condition for each computed column that is
// constant (i.e. all of its variables are in the constCols set).
var computedColFilters memo.FiltersExpr
for colID := range tabMeta.ComputedCols {
if c.tryFoldComputedCol(tabMeta, colID, constCols) {
constVal := constCols[colID]
// Note: Eq is not correct here because of NULLs.
eqOp := c.e.f.ConstructIs(c.e.f.ConstructVariable(colID), constVal)
computedColFilters = append(computedColFilters, c.e.f.ConstructFiltersItem(eqOp))
}
}
return computedColFilters
}
// findConstantFilterCols adds to constFilterCols mappings from table column ID
// to the constant value of that column. It does this by iterating over the
// given lists of filters and finding expressions that constrain columns to a
// single constant value. For example:
//
// x = 5 AND y = 'foo'
//
// This would add a mapping from x => 5 and y => 'foo', which constants can
// then be used to prove that dependent computed columns are also constant.
func (c *CustomFuncs) findConstantFilterCols(
constFilterCols map[opt.ColumnID]opt.ScalarExpr, tabID opt.TableID, filters memo.FiltersExpr,
) {
tab := c.e.mem.Metadata().Table(tabID)
for i := range filters {
// If filter constraints are not tight, then no way to derive constant
// values.
props := filters[i].ScalarProps()
if !props.TightConstraints {
continue
}
// Iterate over constraint conjuncts with a single column and single
// span having a single key.
for i, n := 0, props.Constraints.Length(); i < n; i++ {
cons := props.Constraints.Constraint(i)
if cons.Columns.Count() != 1 || cons.Spans.Count() != 1 {
continue
}
// Skip columns with a data type that uses a composite key encoding.
// Each of these data types can have multiple distinct values that
// compare equal. For example, 0 == -0 for the FLOAT data type. It's
// not safe to treat these as constant inputs to computed columns,
// since the computed expression may differentiate between the
// different forms of the same value.
colID := cons.Columns.Get(0).ID()
colTyp := tab.Column(tabID.ColumnOrdinal(colID)).DatumType()
if sqlbase.HasCompositeKeyEncoding(colTyp) {
continue
}
span := cons.Spans.Get(0)
if !span.HasSingleKey(c.e.evalCtx) {
continue
}
datum := span.StartKey().Value(0)
if datum != tree.DNull {
constFilterCols[colID] = c.e.f.ConstructConstVal(datum, colTyp)
}
}
}
}
// tryFoldComputedCol tries to reduce the computed column with the given column
// ID into a constant value, by evaluating it with respect to a set of other
// columns that are constant. If the computed column is constant, enter it into
// the constCols map and return false. Otherwise, return false.
func (c *CustomFuncs) tryFoldComputedCol(
tabMeta *opt.TableMeta, computedColID opt.ColumnID, constCols map[opt.ColumnID]opt.ScalarExpr,
) bool {
// Check whether computed column has already been folded.
if _, ok := constCols[computedColID]; ok {
return true
}
var replace func(e opt.Expr) opt.Expr
replace = func(e opt.Expr) opt.Expr {
if variable, ok := e.(*memo.VariableExpr); ok {
// Can variable be folded?
if constVal, ok := constCols[variable.Col]; ok {
// Yes, so replace it with its constant value.
return constVal
}
// No, but that may be because the variable refers to a dependent
// computed column. In that case, try to recursively fold that
// computed column. There are no infinite loops possible because the
// dependency graph is guaranteed to be acyclic.
if _, ok := tabMeta.ComputedCols[variable.Col]; ok {
if c.tryFoldComputedCol(tabMeta, variable.Col, constCols) {
return constCols[variable.Col]
}
}
return e
}
return c.e.f.Replace(e, replace)
}
computedCol := tabMeta.ComputedCols[computedColID]
replaced := replace(computedCol).(opt.ScalarExpr)
// If the computed column is constant, enter it into the constCols map.
if opt.IsConstValueOp(replaced) {
constCols[computedColID] = replaced
return true
}
return false
}
// inBetweenFilters returns a set of filters that are required to cover all the
// in-between spans given a set of partition values. This is required for
// correctness reasons; although values are unlikely to exist between defined
// partitions, they may exist and so the constraints of the scan must incorporate
// these spans.
func (c *CustomFuncs) inBetweenFilters(
tabID opt.TableID, index cat.Index, partitionValues []tree.Datums,
) memo.FiltersExpr {
var inBetween memo.ScalarListExpr
if len(partitionValues) == 0 {
return memo.EmptyFiltersExpr
}
// Sort the partitionValues lexicographically.
sort.Slice(partitionValues, func(i, j int) bool {
return partitionValues[i].Compare(c.e.evalCtx, partitionValues[j]) < 0
})
// Add the beginning span.
beginExpr := c.columnComparison(tabID, index, partitionValues[0], -1)
inBetween = append(inBetween, beginExpr)
// Add the end span.
endExpr := c.columnComparison(tabID, index, partitionValues[len(partitionValues)-1], 1)
inBetween = append(inBetween, endExpr)
// Add the in-between spans.
for i := 1; i < len(partitionValues); i++ {
lowerPartition := partitionValues[i-1]
higherPartition := partitionValues[i]
// The between spans will be greater than the lower partition but smaller
// than the higher partition.
var largerThanLower opt.ScalarExpr
if c.isPrefixOf(lowerPartition, higherPartition) {
// Since the lower partition is a prefix of the higher partition, the span
// must begin with the values defined in the lower partition. Consider the
// partitions ('us') and ('us', 'cali'). In this case the in-between span
// should be [/'us - /'us'/'cali').
largerThanLower = c.columnComparison(tabID, index, lowerPartition, 0)
} else {
largerThanLower = c.columnComparison(tabID, index, lowerPartition, 1)
}
smallerThanHigher := c.columnComparison(tabID, index, higherPartition, -1)
// Add the in-between span to the list of inBetween spans.
betweenExpr := c.e.f.ConstructAnd(largerThanLower, smallerThanHigher)
inBetween = append(inBetween, betweenExpr)
}
// Return an Or expression between all the expressions.
return memo.FiltersExpr{c.e.f.ConstructFiltersItem(c.constructOr(inBetween))}
}
// columnComparison returns a filter that compares the index columns to the
// given values. The comp parameter can be -1, 0 or 1 to indicate whether the
// comparison type of the filter should be a Lt, Eq or Gt.
func (c *CustomFuncs) columnComparison(
tabID opt.TableID, index cat.Index, values tree.Datums, comp int,
) opt.ScalarExpr {
colTypes := make([]*types.T, len(values))
for i := range values {
colTypes[i] = values[i].ResolvedType()
}
columnVariables := make(memo.ScalarListExpr, len(values))
scalarValues := make(memo.ScalarListExpr, len(values))
for i, val := range values {
colID := tabID.ColumnID(index.Column(i).Ordinal)
columnVariables[i] = c.e.f.ConstructVariable(colID)
scalarValues[i] = c.e.f.ConstructConstVal(val, val.ResolvedType())
}
colsTuple := c.e.f.ConstructTuple(columnVariables, types.MakeTuple(colTypes))
valsTuple := c.e.f.ConstructTuple(scalarValues, types.MakeTuple(colTypes))
if comp == 0 {
return c.e.f.ConstructEq(colsTuple, valsTuple)
} else if comp > 0 {
return c.e.f.ConstructGt(colsTuple, valsTuple)
}
return c.e.f.ConstructLt(colsTuple, valsTuple)
}
// inPartitionFilters returns a FiltersExpr that is required to cover
// all the partition spans. For each partition defined, inPartitionFilters
// will contain a FilterItem that restricts the index columns by
// the partition values. Use inBetweenFilters to generate filters that
// cover all the spans that the partitions don't cover.
func (c *CustomFuncs) inPartitionFilters(
tabID opt.TableID, index cat.Index, partitionValues []tree.Datums,
) memo.FiltersExpr {
var partitions memo.ScalarListExpr
// Sort the partition values so the most selective ones are first.
sort.Slice(partitionValues, func(i, j int) bool {
return len(partitionValues[i]) >= len(partitionValues[j])
})
// Construct all the partition filters.
for i, partition := range partitionValues {
// Only add this partition if a more selective partition hasn't
// been defined on the same partition.
partitionSeen := false
for j, moreSelectivePartition := range partitionValues {
if j >= i {
break
}
// At this point we know whether the current partition was seen before.
partitionSeen = c.isPrefixOf(partition, moreSelectivePartition)
if partitionSeen {
break
}
}
// This partition is a prefix of a more selective partition and so,
// will be taken care of by the in-between partitions.
if partitionSeen {
continue
}
// Get an expression that restricts the values of the index to the
// partition values.
inPartition := c.columnComparison(tabID, index, partition, 0)
partitions = append(partitions, inPartition)
}
// Return an Or expression between all the expressions.
return memo.FiltersExpr{c.e.f.ConstructFiltersItem(c.constructOr(partitions))}
}
// isPrefixOf returns whether pre is a prefix of other.
func (c *CustomFuncs) isPrefixOf(pre []tree.Datum, other []tree.Datum) bool {
if len(pre) > len(other) {
// Pre can't be a prefix of other as it is larger.
return false
}
for i := range pre {
if pre[i].Compare(c.e.evalCtx, other[i]) != 0 {
return false
}
}
return true
}
// constructOr constructs an expression that is an OR between all the
// provided conditions
func (c *CustomFuncs) constructOr(conditions memo.ScalarListExpr) opt.ScalarExpr {
if len(conditions) == 0 {
return c.e.f.ConstructFalse()
}
orExpr := conditions[0]
for i := 1; i < len(conditions); i++ {
orExpr = c.e.f.ConstructOr(conditions[i], orExpr)
}
return orExpr
}
// partitionValuesFilters constructs filters with the purpose of
// constraining an index scan using the partition values similar to
// the filters added from the check constraints (see
// checkConstraintFilters). It returns two sets of filters, one to
// create the partition spans, and one to create the spans for all
// the in between ranges that are not part of any partitions.
//
// For example consider the following table and partitioned index:
//
// CREATE TABLE orders (
// region STRING NOT NULL, id INT8 NOT NULL, total DECIMAL NOT NULL, seq_num INT NOT NULL,
// PRIMARY KEY (region, id)
// )
//
// CREATE INDEX orders_by_seq_num
// ON orders (region, seq_num, id)
// STORING (total)
// PARTITION BY LIST (region)
// (
// PARTITION us_east1 VALUES IN ('us-east1'),
// PARTITION us_west1 VALUES IN ('us-west1'),
// PARTITION europe_west2 VALUES IN ('europe-west2')
// )
//
// Now consider the following query:
// SELECT sum(total) FROM orders WHERE seq_num >= 100 AND seq_num < 200
//
// Normally, the index would not be utilized but because we know what the
// partition values are for the prefix of the index, we can generate
// filters that allow us to use the index (adding the appropriate in-between
// filters to catch all the values that are not part of the partitions).
// By doing so, we get the following plan:
// scalar-group-by
// ├── select
// │ ├── scan orders@orders_by_seq_num
// │ │ └── constraint: /1/4/2: [ - /'europe-west2')
// │ │ [/'europe-west2'/100 - /'europe-west2'/199]
// │ │ [/e'europe-west2\x00'/100 - /'us-east1')
// │ │ [/'us-east1'/100 - /'us-east1'/199]
// │ │ [/e'us-east1\x00'/100 - /'us-west1')
// │ │ [/'us-west1'/100 - /'us-west1'/199]
// │ │ [/e'us-west1\x00'/100 - ]
// │ └── filters
// │ └── (seq_num >= 100) AND (seq_num < 200)
// └── aggregations
// └── sum
// └── variable: total
//
func (c *CustomFuncs) partitionValuesFilters(
tabID opt.TableID, index cat.Index,
) (partitionFilter, inBetweenFilter memo.FiltersExpr) {
// Find all the partition values
partitionValues := index.PartitionByListPrefixes()
if len(partitionValues) == 0 {
return partitionFilter, inBetweenFilter
}
// Get the in partition expressions.
inPartition := c.inPartitionFilters(tabID, index, partitionValues)
// Get the in between expressions.
inBetween := c.inBetweenFilters(tabID, index, partitionValues)
return inPartition, inBetween
}
// HasInvertedIndexes returns true if at least one inverted index is defined on
// the Scan operator's table.
func (c *CustomFuncs) HasInvertedIndexes(scanPrivate *memo.ScanPrivate) bool {
// Don't bother matching unless there's an inverted index.
iter := makeScanIndexIter(c.e.mem, scanPrivate, rejectNonInvertedIndexes)
return iter.Next()
}
// GenerateInvertedIndexScans enumerates all inverted indexes on the Scan
// operator's table and generates an alternate Scan operator for each inverted
// index that can service the query.
//
// The resulting Scan operator is pre-constrained and requires an IndexJoin to
// project columns other than the primary key columns. The reason it's pre-
// constrained is that we cannot treat an inverted index in the same way as a
// regular index, since it does not actually contain the indexed column.
func (c *CustomFuncs) GenerateInvertedIndexScans(
grp memo.RelExpr, scanPrivate *memo.ScanPrivate, filters memo.FiltersExpr,
) {
var sb indexScanBuilder
sb.init(c, scanPrivate.Table)
// Iterate over all inverted indexes.
iter := makeScanIndexIter(c.e.mem, scanPrivate, rejectNonInvertedIndexes)
for iter.Next() {
var spanExpr *invertedexpr.SpanExpression
var spansToRead invertedexpr.InvertedSpans
var constraint *constraint.Constraint
var remaining memo.FiltersExpr
var geoOk, nonGeoOk bool
// Check whether the filter can constrain the index.
// TODO(rytaft): Unify these two cases so both return a spanExpr.
spanExpr, geoOk = tryConstrainGeoIndex(
c.e.evalCtx.Context, filters, scanPrivate.Table, iter.Index(),
)
if geoOk {
// Geo index scans can never be tight, so remaining filters is always the
// same as filters.
remaining = filters
spansToRead = spanExpr.SpansToRead
} else {
constraint, remaining, nonGeoOk = c.tryConstrainIndex(
filters,
nil, /* optionalFilters */
scanPrivate.Table,
iter.IndexOrdinal(),
true, /* isInverted */
)
if !nonGeoOk {
continue
}
}
invertedCol := scanPrivate.Table.ColumnID(iter.Index().Column(0).Ordinal)