-
Notifications
You must be signed in to change notification settings - Fork 3.8k
/
Copy pathequiv_set.go
354 lines (331 loc) · 10.3 KB
/
equiv_set.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
// Copyright 2022 The Cockroach Authors.
//
// Use of this software is governed by the CockroachDB Software License
// included in the /LICENSE file.
package props
import (
"github.com/cockroachdb/cockroach/pkg/sql/opt"
"github.com/cockroachdb/cockroach/pkg/util/buildutil"
"github.com/cockroachdb/errors"
)
// EquivGroups describes a set of equivalence groups of columns. It can answer
// queries about which columns are equivalent to one another. Equivalence groups
// are always non-empty and disjoint.
//
// TODO(drewk): incorporate EquivGroups into FuncDepSets.
type EquivGroups struct {
groups []opt.ColSet
}
// Reset prepares the EquivGroups for reuse, maintaining references to any
// allocated slice memory.
func (eq *EquivGroups) Reset() {
for i := range eq.groups {
// Release any references to the large portion of ColSets.
eq.groups[i] = opt.ColSet{}
}
eq.groups = eq.groups[:0]
}
// Empty returns true if the set stores no equalities.
func (eq *EquivGroups) Empty() bool {
if buildutil.CrdbTestBuild {
defer eq.verify()
}
return len(eq.groups) == 0
}
// GroupCount returns the number of equiv groups stored in the set.
func (eq *EquivGroups) GroupCount() int {
if buildutil.CrdbTestBuild {
defer eq.verify()
}
return len(eq.groups)
}
// Group returns the equiv group at the given index. The returned ColSet should
// be considered immutable. The index must be less than GroupCount().
func (eq *EquivGroups) Group(idx int) opt.ColSet {
if buildutil.CrdbTestBuild {
defer eq.verify()
}
if idx >= len(eq.groups) {
panic(errors.AssertionFailedf("invalid equiv group index %d", idx))
}
return eq.groups[idx]
}
// GroupForCol returns the group of columns equivalent to the given column. It
// returns the empty set if no such group exists. The returned should not be
// mutated without being copied first.
func (eq *EquivGroups) GroupForCol(col opt.ColumnID) opt.ColSet {
if buildutil.CrdbTestBuild {
defer eq.verify()
}
for i := range eq.groups {
if eq.groups[i].Contains(col) {
return eq.groups[i]
}
}
return opt.ColSet{}
}
// ContainsCol returns true if the given column is contained in any of the equiv
// groups (it will be in at most one group).
func (eq *EquivGroups) ContainsCol(col opt.ColumnID) bool {
if buildutil.CrdbTestBuild {
defer eq.verify()
}
for i := range eq.groups {
if eq.groups[i].Contains(col) {
return true
}
}
return false
}
// AreColsEquiv indicates whether the given columns are equivalent.
func (eq *EquivGroups) AreColsEquiv(left, right opt.ColumnID) bool {
if buildutil.CrdbTestBuild {
defer eq.verify()
}
if left == right {
return true
}
for i := range eq.groups {
if eq.groups[i].Contains(left) {
return eq.groups[i].Contains(right)
}
if eq.groups[i].Contains(right) {
return eq.groups[i].Contains(left)
}
}
return false
}
// AreAllColsEquiv returns true if all columns in the given set are equivalent
// to all others in the set.
func (eq *EquivGroups) AreAllColsEquiv(cols opt.ColSet) bool {
if buildutil.CrdbTestBuild {
defer eq.verify()
}
if cols.Len() <= 1 {
return true
}
for i := range eq.groups {
if eq.groups[i].Intersects(cols) {
return cols.SubsetOf(eq.groups[i])
}
}
return false
}
// ComputeEquivClosureNoCopy returns the equivalence closure of the given
// columns. Note that the given ColSet is mutated and returned directly.
func (eq *EquivGroups) ComputeEquivClosureNoCopy(cols opt.ColSet) opt.ColSet {
if buildutil.CrdbTestBuild {
defer eq.verify()
}
for i := range eq.groups {
if eq.groups[i].Intersects(cols) {
cols.UnionWith(eq.groups[i])
if cols.Len() == eq.groups[i].Len() {
// Since we just took the union, equal lengths means all columns in cols
// were within the same equivalence group, so we can short-circuit.
break
}
}
}
return cols
}
// AddNoCopy adds the given equivalent columns to the EquivGroups. If possible,
// the columns are added to an existing group. Otherwise, a new one is created.
// NOTE: the given ColSet may be added to the EquivGroups without being copied,
// so it must be considered immutable after it is passed to addNoCopy.
func (eq *EquivGroups) AddNoCopy(equivCols opt.ColSet) {
if buildutil.CrdbTestBuild {
defer eq.verify()
}
if equivCols.Len() <= 1 {
// This is a trivial equivalence.
return
}
// Attempt to add the equivalence to an existing group.
for i := range eq.groups {
if eq.groups[i].Intersects(equivCols) {
if equivCols.SubsetOf(eq.groups[i]) {
// The equivalence is already contained in the set.
return
}
if eq.groups[i].SubsetOf(equivCols) {
// Avoid the copy.
eq.groups[i] = equivCols
} else {
eq.groups[i] = eq.groups[i].Union(equivCols)
}
eq.tryMergeGroups(i)
return
}
}
// Make a new equivalence group.
eq.groups = append(eq.groups, equivCols)
}
// AddFromFDs adds all equivalence relations from the given FuncDepSet to the
// EquivGroups.
func (eq *EquivGroups) AddFromFDs(fdset *FuncDepSet) {
if buildutil.CrdbTestBuild {
defer eq.verify()
}
for i := range fdset.deps {
fd := &fdset.deps[i]
if fd.equiv {
eq.AddNoCopy(fd.from.Union(fd.to))
}
}
}
// TranslateColsStrict remaps the column IDs of each equiv group according to
// the given "from" and "to" lists. It requires that all columns in each group
// are present in the "from" list, and that the "from" and "to" lists are the
// same length.
func (eq *EquivGroups) TranslateColsStrict(fromCols, toCols opt.ColList) {
if buildutil.CrdbTestBuild {
defer eq.verify()
}
// It is possible that the same column shows up more than once in either of
// the lists. In other words, a column can map to more than one column, and
// two different columns can map to the same column. The former is handled
// by TranslateColSetStrict, and may add a column to an equiv group. The
// latter can merge two equiv groups, so we need to handle it here.
var seenCols, dupCols opt.ColSet
for _, toCol := range toCols {
if seenCols.Contains(toCol) && !dupCols.Contains(toCol) {
var equiv opt.ColSet
for i, fromCol := range fromCols {
if toCols[i] == toCol {
equiv.Add(fromCol)
}
}
eq.AddNoCopy(equiv)
dupCols.Add(toCol)
}
seenCols.Add(toCol)
}
for i := range eq.groups {
eq.groups[i] = opt.TranslateColSetStrict(eq.groups[i], fromCols, toCols)
}
// Handle the case when multiple "in" columns map to the same "out" column,
// which could result in removal of an equiv group.
eq.removeTrivialGroups()
}
// ProjectCols removes all columns from the EquivGroups that are not in the
// given ColSet, removing equiv groups that become empty.
func (eq *EquivGroups) ProjectCols(cols opt.ColSet) {
if buildutil.CrdbTestBuild {
defer eq.verify()
}
for i := range eq.groups {
if !eq.groups[i].SubsetOf(cols) {
eq.groups[i] = eq.groups[i].Intersection(cols)
}
}
eq.removeTrivialGroups()
}
// PartitionBy divides the equiv groups according to the given columns. If an
// equiv group intersects the given ColSet but is not a subset, it is split into
// the intersection and difference with the given ColSet. Ex:
//
// eq := [(1-3), (4-8), (9-12)]
// eq.PartitionBy(1,5,6)
// eq == [(2,3), (4,7,8), (5,6), (9-12)]
//
// * In the example, the (1-3) group is split into (1) and (2,3). Since the (1)
// group only has a single column, it is discarded as a trivial equivalence.
// * The (4-8) group is split into (4,8) and (5,6). Since both subsets have at
// least two columns, both are kept in the EquivGroups.
// * Finally, the (9-12) group does not intersect the given cols, and so is not
// split.
func (eq *EquivGroups) PartitionBy(cols opt.ColSet) {
if buildutil.CrdbTestBuild {
defer eq.verify()
}
for i := len(eq.groups) - 1; i >= 0; i-- {
if eq.groups[i].Intersects(cols) && !eq.groups[i].SubsetOf(cols) {
left, right := eq.groups[i].Intersection(cols), eq.groups[i].Difference(cols)
eq.groups[i] = opt.ColSet{}
if left.Len() > 1 {
eq.groups = append(eq.groups, left)
}
if right.Len() > 1 {
eq.groups = append(eq.groups, right)
}
}
}
eq.removeTrivialGroups()
}
// CopyFrom copies the given EquivGroups into this EquivGroups, replacing any
// existing data.
func (eq *EquivGroups) CopyFrom(other *EquivGroups) {
if buildutil.CrdbTestBuild {
defer eq.verify()
}
eq.Reset()
eq.AppendFromDisjoint(other)
}
// AppendFromDisjoint unions the equiv groups from the given EquivGroups with
// this one, assuming the groups are disjoint.
func (eq *EquivGroups) AppendFromDisjoint(other *EquivGroups) {
if buildutil.CrdbTestBuild {
other.verify()
defer eq.verify()
}
neededCap := len(eq.groups) + len(other.groups)
if cap(eq.groups) < neededCap {
// Make sure to copy the old equiv groups into the new slice.
newGroups := make([]opt.ColSet, len(eq.groups), neededCap)
copy(newGroups, eq.groups)
eq.groups = newGroups
}
// There is no need to deep-copy the equiv groups, since they are never
// modified in-place.
eq.groups = append(eq.groups, other.groups...)
}
func (eq *EquivGroups) String() string {
if buildutil.CrdbTestBuild {
defer eq.verify()
}
ret := "["
for i := range eq.groups {
if i > 0 {
ret += ", "
}
ret += eq.groups[i].String()
}
return ret + "]"
}
// tryMergeGroups attempts to merge the equality group at the given index with
// any of the *following* groups. If a group can be merged, it is removed after
// its columns are added to the given group.
func (eq *EquivGroups) tryMergeGroups(idx int) {
for i := len(eq.groups) - 1; i > idx; i-- {
if eq.groups[idx].Intersects(eq.groups[i]) {
eq.groups[idx] = eq.groups[idx].Union(eq.groups[i])
eq.groups[i] = eq.groups[len(eq.groups)-1]
eq.groups[len(eq.groups)-1] = opt.ColSet{}
eq.groups = eq.groups[:len(eq.groups)-1]
}
}
}
// removeTrivialGroups removes groups with zero or one columns, which may be
// added by methods like makePartition.
func (eq *EquivGroups) removeTrivialGroups() {
for i := len(eq.groups) - 1; i >= 0; i-- {
if eq.groups[i].Len() <= 1 {
eq.groups[i] = eq.groups[len(eq.groups)-1]
eq.groups[len(eq.groups)-1] = opt.ColSet{}
eq.groups = eq.groups[:len(eq.groups)-1]
}
}
}
func (eq *EquivGroups) verify() {
var seen opt.ColSet
for _, group := range eq.groups {
if group.Len() <= 1 {
panic(errors.AssertionFailedf("expected non-trivial equiv group"))
}
if seen.Intersects(group) {
panic(errors.AssertionFailedf("expected non-intersecting equiv groups"))
}
seen.UnionWith(group)
}
}