|
| 1 | +// Copyright (c) 2024 Couchbase, Inc. |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | + |
| 15 | +package zap |
| 16 | + |
| 17 | +import ( |
| 18 | + "bytes" |
| 19 | + |
| 20 | + "github.com/blevesearch/vellum" |
| 21 | +) |
| 22 | + |
| 23 | +// enumerator provides an ordered traversal of multiple vellum |
| 24 | +// iterators. Like JOIN of iterators, the enumerator produces a |
| 25 | +// sequence of (key, iteratorIndex, value) tuples, sorted by key ASC, |
| 26 | +// then iteratorIndex ASC, where the same key might be seen or |
| 27 | +// repeated across multiple child iterators. |
| 28 | +type basicEnumerator struct { |
| 29 | + itrs []vellum.Iterator |
| 30 | + currKs [][]byte |
| 31 | + currVs []uint64 |
| 32 | + id int |
| 33 | + |
| 34 | + lowK []byte |
| 35 | + lowIdxs []int |
| 36 | + lowCurr int |
| 37 | +} |
| 38 | + |
| 39 | +// newEnumerator returns a new enumerator over the vellum Iterators |
| 40 | +func newBasicEnumerator(itrs []vellum.Iterator, id int) (enumerator, error) { |
| 41 | + rv := &basicEnumerator{ |
| 42 | + itrs: itrs, |
| 43 | + currKs: make([][]byte, len(itrs)), |
| 44 | + currVs: make([]uint64, len(itrs)), |
| 45 | + lowIdxs: make([]int, 0, len(itrs)), |
| 46 | + id: id, |
| 47 | + } |
| 48 | + for i, itr := range rv.itrs { |
| 49 | + rv.currKs[i], rv.currVs[i] = itr.Current() |
| 50 | + } |
| 51 | + rv.updateMatches(false) |
| 52 | + if rv.lowK == nil && len(rv.lowIdxs) == 0 { |
| 53 | + return rv, vellum.ErrIteratorDone |
| 54 | + } |
| 55 | + return rv, nil |
| 56 | +} |
| 57 | + |
| 58 | +// updateMatches maintains the low key matches based on the currKs |
| 59 | +func (m *basicEnumerator) updateMatches(skipEmptyKey bool) { |
| 60 | + m.lowK = nil |
| 61 | + m.lowIdxs = m.lowIdxs[:0] |
| 62 | + m.lowCurr = 0 |
| 63 | + |
| 64 | + for i, key := range m.currKs { |
| 65 | + if (key == nil && m.currVs[i] == 0) || // in case of empty iterator |
| 66 | + (len(key) == 0 && skipEmptyKey) { // skip empty keys |
| 67 | + continue |
| 68 | + } |
| 69 | + |
| 70 | + cmp := bytes.Compare(key, m.lowK) |
| 71 | + if cmp < 0 || len(m.lowIdxs) == 0 { |
| 72 | + // reached a new low |
| 73 | + m.lowK = key |
| 74 | + m.lowIdxs = m.lowIdxs[:0] |
| 75 | + m.lowIdxs = append(m.lowIdxs, i+m.id*maxItrsPerBasicEnum) |
| 76 | + } else if cmp == 0 { |
| 77 | + m.lowIdxs = append(m.lowIdxs, i+m.id*maxItrsPerBasicEnum) |
| 78 | + } |
| 79 | + } |
| 80 | +} |
| 81 | + |
| 82 | +// Current returns the enumerator's current key, iterator-index, and |
| 83 | +// value. If the enumerator is not pointing at a valid value (because |
| 84 | +// Next returned an error previously), Current will return nil,0,0. |
| 85 | +func (m *basicEnumerator) Current() ([]byte, int, uint64) { |
| 86 | + var i int |
| 87 | + var v uint64 |
| 88 | + if m.lowCurr < len(m.lowIdxs) { |
| 89 | + i = m.lowIdxs[m.lowCurr] |
| 90 | + v = m.currVs[i%maxItrsPerBasicEnum] |
| 91 | + } |
| 92 | + return m.lowK, i, v |
| 93 | +} |
| 94 | + |
| 95 | +// GetLowIdxsAndValues will return all of the iterator indices |
| 96 | +// which point to the current key, and their corresponding |
| 97 | +// values. This can be used by advanced caller which may need |
| 98 | +// to peek into these other sets of data before processing. |
| 99 | +func (m *basicEnumerator) GetLowIdxsAndValues() ([]int, []uint64) { |
| 100 | + values := make([]uint64, 0, len(m.lowIdxs)) |
| 101 | + for _, idx := range m.lowIdxs { |
| 102 | + values = append(values, m.currVs[idx%maxItrsPerBasicEnum]) |
| 103 | + } |
| 104 | + return m.lowIdxs, values |
| 105 | +} |
| 106 | + |
| 107 | +// Next advances the enumerator to the next key/iterator/value result, |
| 108 | +// else vellum.ErrIteratorDone is returned. |
| 109 | +func (m *basicEnumerator) Next() error { |
| 110 | + m.lowCurr += 1 |
| 111 | + if m.lowCurr >= len(m.lowIdxs) { |
| 112 | + // move all the current low iterators forwards |
| 113 | + for _, vi := range m.lowIdxs { |
| 114 | + err := m.itrs[vi%maxItrsPerBasicEnum].Next() |
| 115 | + if err != nil && err != vellum.ErrIteratorDone { |
| 116 | + return err |
| 117 | + } |
| 118 | + m.currKs[vi%maxItrsPerBasicEnum], m.currVs[vi%maxItrsPerBasicEnum] = m.itrs[vi%maxItrsPerBasicEnum].Current() |
| 119 | + } |
| 120 | + // can skip any empty keys encountered at this point |
| 121 | + m.updateMatches(true) |
| 122 | + } |
| 123 | + if m.lowK == nil && len(m.lowIdxs) == 0 { |
| 124 | + return vellum.ErrIteratorDone |
| 125 | + } |
| 126 | + return nil |
| 127 | +} |
| 128 | + |
| 129 | +// Close all the underlying Iterators. The first error, if any, will |
| 130 | +// be returned. |
| 131 | +func (m *basicEnumerator) Close() error { |
| 132 | + var rv error |
| 133 | + for _, itr := range m.itrs { |
| 134 | + err := itr.Close() |
| 135 | + if rv == nil { |
| 136 | + rv = err |
| 137 | + } |
| 138 | + } |
| 139 | + return rv |
| 140 | +} |
0 commit comments