Line data Source code
1 : // Copyright 2018 The LevelDB-Go and Pebble Authors. All rights reserved. Use
2 : // of this source code is governed by a BSD-style license that can be found in
3 : // the LICENSE file.
4 :
5 : package keyspan // import "github.com/cockroachdb/pebble/internal/keyspan"
6 :
7 : import (
8 : "bytes"
9 : "cmp"
10 : "fmt"
11 : "slices"
12 : "strings"
13 : "unicode"
14 :
15 : "github.com/cockroachdb/pebble/internal/base"
16 : )
17 :
18 : // Span represents a set of keys over a span of user key space. All of the keys
19 : // within a Span are applied across the span's key span indicated by Start and
20 : // End. Each internal key applied over the user key span appears as a separate
21 : // Key, with its own kind and sequence number. Optionally, each Key may also
22 : // have a Suffix and/or Value.
23 : //
24 : // Note that the start user key is inclusive and the end user key is exclusive.
25 : //
26 : // Currently the only supported key kinds are:
27 : //
28 : // RANGEDEL, RANGEKEYSET, RANGEKEYUNSET, RANGEKEYDEL.
29 : //
30 : // Spans either have only RANGEDEL keys (range del spans), or a mix of
31 : // RANGEKESET/RANGEKEYUNSET/RANGEKEYDEL keys (range key spans).
32 : //
33 : // Note that at the user level, range key span start and end keys never have
34 : // suffixes. Internally, range key spans get fragmented along sstable
35 : // boundaries; however, this is transparent to the user.
36 : type Span struct {
37 : // Start and End encode the user key range of all the contained items, with
38 : // an inclusive start key and exclusive end key. Both Start and End must be
39 : // non-nil, or both nil if representing an invalid Span.
40 : Start, End []byte
41 : // Keys holds the set of keys applied over the [Start, End) user key range.
42 : // Keys is sorted by (SeqNum, Kind) descending, unless otherwise specified
43 : // by the context. If SeqNum and Kind are equal, the order of Keys is
44 : // undefined. Keys may be empty, even if Start and End are non-nil.
45 : //
46 : // Keys are a decoded representation of the internal keys stored in batches
47 : // or sstable blocks. A single internal key in a range key block may produce
48 : // several decoded Keys.
49 : Keys []Key
50 : KeysOrder KeysOrder
51 : }
52 :
53 : // KeysOrder describes the ordering of Keys within a Span.
54 : type KeysOrder int8
55 :
56 : const (
57 : // ByTrailerDesc indicates a Span's keys are sorted by InternalKeyTrailer descending.
58 : // This is the default ordering, and the ordering used during physical
59 : // storage.
60 : ByTrailerDesc KeysOrder = iota
61 : // BySuffixAsc indicates a Span's keys are sorted by Suffix ascending. This
62 : // ordering is used during user iteration of range keys.
63 : BySuffixAsc
64 : )
65 :
66 : // Key represents a single key applied over a span of user keys. A Key is
67 : // contained by a Span which specifies the span of user keys over which the Key
68 : // is applied.
69 : type Key struct {
70 : // Trailer contains the key kind and sequence number.
71 : Trailer base.InternalKeyTrailer
72 : // Suffix holds an optional suffix associated with the key. This is only
73 : // non-nil for RANGEKEYSET and RANGEKEYUNSET keys.
74 : Suffix []byte
75 : // Value holds a logical value associated with the Key. It is NOT the
76 : // internal value stored in a range key or range deletion block. This is
77 : // only non-nil for RANGEKEYSET keys.
78 : Value []byte
79 : }
80 :
81 : // SeqNum returns the sequence number component of the key.
82 1 : func (k Key) SeqNum() base.SeqNum {
83 1 : return k.Trailer.SeqNum()
84 1 : }
85 :
86 : // VisibleAt returns true if the provided key is visible at the provided
87 : // snapshot sequence number. It interprets batch sequence numbers as always
88 : // visible, because non-visible batch span keys are filtered when they're
89 : // fragmented.
90 1 : func (k Key) VisibleAt(snapshot base.SeqNum) bool {
91 1 : seq := k.SeqNum()
92 1 : return seq < snapshot || seq&base.SeqNumBatchBit != 0
93 1 : }
94 :
95 : // Kind returns the kind component of the key.
96 1 : func (k Key) Kind() base.InternalKeyKind {
97 1 : return base.InternalKeyKind(k.Trailer & 0xff)
98 1 : }
99 :
100 : // Equal returns true if this Key is equal to the given key. Two keys are said
101 : // to be equal if the two Keys have equal trailers, suffix and value. Suffix
102 : // comparison uses the provided base.Compare func. Value comparison is bytewise.
103 1 : func (k Key) Equal(suffixCmp base.CompareRangeSuffixes, b Key) bool {
104 1 : return k.Trailer == b.Trailer &&
105 1 : suffixCmp(k.Suffix, b.Suffix) == 0 &&
106 1 : bytes.Equal(k.Value, b.Value)
107 1 : }
108 :
109 : // CopyFrom copies the contents of another key, retaining the Suffix and Value slices.
110 1 : func (k *Key) CopyFrom(other Key) {
111 1 : k.Trailer = other.Trailer
112 1 : k.Suffix = append(k.Suffix[:0], other.Suffix...)
113 1 : k.Value = append(k.Value[:0], other.Value...)
114 1 : }
115 :
116 : // Clone creates a deep clone of the key, copying the Suffix and Value
117 : // slices.
118 1 : func (k Key) Clone() Key {
119 1 : res := Key{
120 1 : Trailer: k.Trailer,
121 1 : }
122 1 : if len(k.Suffix) > 0 {
123 1 : res.Suffix = slices.Clone(k.Suffix)
124 1 : }
125 1 : if len(k.Value) > 0 {
126 1 : res.Value = slices.Clone(k.Value)
127 1 : }
128 1 : return res
129 : }
130 :
131 1 : func (k Key) String() string {
132 1 : var b strings.Builder
133 1 : fmt.Fprintf(&b, "(#%d,%s", k.SeqNum(), k.Kind())
134 1 : if len(k.Suffix) > 0 || len(k.Value) > 0 {
135 1 : fmt.Fprintf(&b, ",%s", k.Suffix)
136 1 : }
137 1 : if len(k.Value) > 0 {
138 1 : fmt.Fprintf(&b, ",%s", k.Value)
139 1 : }
140 1 : b.WriteString(")")
141 1 : return b.String()
142 : }
143 :
144 : // Valid returns true if the span is defined.
145 1 : func (s *Span) Valid() bool {
146 1 : return s.Start != nil && s.End != nil
147 1 : }
148 :
149 : // Empty returns true if the span does not contain any keys. An empty span may
150 : // still be Valid. A non-empty span must be Valid.
151 : //
152 : // An Empty span may be produced by Visible, or be produced by iterators in
153 : // order to surface the gaps between keys.
154 1 : func (s *Span) Empty() bool {
155 1 : return s == nil || len(s.Keys) == 0
156 1 : }
157 :
158 : // Bounds returns Start and End as UserKeyBounds.
159 0 : func (s *Span) Bounds() base.UserKeyBounds {
160 0 : return base.UserKeyBoundsEndExclusive(s.Start, s.End)
161 0 : }
162 :
163 : // SmallestKey returns the smallest internal key defined by the span's keys.
164 : // It requires the Span's keys be in ByTrailerDesc order. It panics if the span
165 : // contains no keys or its keys are sorted in a different order.
166 1 : func (s *Span) SmallestKey() base.InternalKey {
167 1 : if len(s.Keys) == 0 {
168 0 : panic("pebble: Span contains no keys")
169 1 : } else if s.KeysOrder != ByTrailerDesc {
170 0 : panic("pebble: span's keys unexpectedly not in trailer order")
171 : }
172 : // The first key has the highest (sequence number,kind) tuple.
173 1 : return base.InternalKey{
174 1 : UserKey: s.Start,
175 1 : Trailer: s.Keys[0].Trailer,
176 1 : }
177 : }
178 :
179 : // LargestKey returns the largest internal key defined by the span's keys. The
180 : // returned key will always be a "sentinel key" at the end boundary. The
181 : // "sentinel key" models the exclusive end boundary by returning an InternalKey
182 : // with the maximal sequence number, ensuring all InternalKeys with the same
183 : // user key sort after the sentinel key.
184 : //
185 : // It requires the Span's keys be in ByTrailerDesc order. It panics if the span
186 : // contains no keys or its keys are sorted in a different order.
187 1 : func (s *Span) LargestKey() base.InternalKey {
188 1 : if len(s.Keys) == 0 {
189 0 : panic("pebble: Span contains no keys")
190 1 : } else if s.KeysOrder != ByTrailerDesc {
191 0 : panic("pebble: span's keys unexpectedly not in trailer order")
192 : }
193 : // The last key has the lowest (sequence number,kind) tuple.
194 1 : kind := s.Keys[len(s.Keys)-1].Kind()
195 1 : return base.MakeExclusiveSentinelKey(kind, s.End)
196 : }
197 :
198 : // SmallestSeqNum returns the smallest sequence number of a key contained within
199 : // the span. It requires the Span's keys be in ByTrailerDesc order. It panics if
200 : // the span contains no keys or its keys are sorted in a different order.
201 1 : func (s *Span) SmallestSeqNum() base.SeqNum {
202 1 : if len(s.Keys) == 0 {
203 0 : panic("pebble: Span contains no keys")
204 1 : } else if s.KeysOrder != ByTrailerDesc {
205 0 : panic("pebble: span's keys unexpectedly not in trailer order")
206 : }
207 :
208 1 : return s.Keys[len(s.Keys)-1].SeqNum()
209 : }
210 :
211 : // LargestSeqNum returns the largest sequence number of a key contained within
212 : // the span. It requires the Span's keys be in ByTrailerDesc order. It panics if
213 : // the span contains no keys or its keys are sorted in a different order.
214 1 : func (s *Span) LargestSeqNum() base.SeqNum {
215 1 : if len(s.Keys) == 0 {
216 0 : panic("pebble: Span contains no keys")
217 1 : } else if s.KeysOrder != ByTrailerDesc {
218 0 : panic("pebble: span's keys unexpectedly not in trailer order")
219 : }
220 1 : return s.Keys[0].SeqNum()
221 : }
222 :
223 : // LargestVisibleSeqNum returns the largest sequence number of a key contained
224 : // within the span that's also visible at the provided snapshot sequence number.
225 : // It requires the Span's keys be in ByTrailerDesc order. It panics if the span
226 : // contains no keys or its keys are sorted in a different order.
227 1 : func (s *Span) LargestVisibleSeqNum(snapshot base.SeqNum) (largest base.SeqNum, ok bool) {
228 1 : if s == nil {
229 1 : return 0, false
230 1 : } else if len(s.Keys) == 0 {
231 0 : panic("pebble: Span contains no keys")
232 1 : } else if s.KeysOrder != ByTrailerDesc {
233 0 : panic("pebble: span's keys unexpectedly not in trailer order")
234 : }
235 1 : for i := range s.Keys {
236 1 : if s.Keys[i].VisibleAt(snapshot) {
237 1 : return s.Keys[i].SeqNum(), true
238 1 : }
239 : }
240 1 : return 0, false
241 : }
242 :
243 : // TODO(jackson): Replace most of the calls to Visible with more targeted calls
244 : // that avoid the need to construct a new Span.
245 :
246 : // Visible returns a span with the subset of keys visible at the provided
247 : // sequence number. It requires the Span's keys be in ByTrailerDesc order. It
248 : // panics if the span's keys are sorted in a different order.
249 : //
250 : // Visible may incur an allocation, so callers should prefer targeted,
251 : // non-allocating methods when possible.
252 1 : func (s Span) Visible(snapshot base.SeqNum) Span {
253 1 : if s.KeysOrder != ByTrailerDesc {
254 0 : panic("pebble: span's keys unexpectedly not in trailer order")
255 : }
256 :
257 1 : ret := Span{Start: s.Start, End: s.End}
258 1 : if len(s.Keys) == 0 {
259 0 : return ret
260 0 : }
261 :
262 : // Keys from indexed batches may force an allocation. The Keys slice is
263 : // ordered by sequence number, so ordinarily we can return the trailing
264 : // subslice containing keys with sequence numbers less than `seqNum`.
265 : //
266 : // However, batch keys are special. Only visible batch keys are included
267 : // when an Iterator's batch spans are fragmented. They must always be
268 : // visible.
269 : //
270 : // Batch keys can create a sandwich of visible batch keys at the beginning
271 : // of the slice and visible committed keys at the end of the slice, forcing
272 : // us to allocate a new slice and copy the contents.
273 : //
274 : // Care is taking to only incur an allocation only when batch keys and
275 : // visible keys actually sandwich non-visible keys.
276 :
277 : // lastBatchIdx and lastNonVisibleIdx are set to the last index of a batch
278 : // key and a non-visible key respectively.
279 1 : lastBatchIdx := -1
280 1 : lastNonVisibleIdx := -1
281 1 : for i := range s.Keys {
282 1 : if seqNum := s.Keys[i].SeqNum(); seqNum&base.SeqNumBatchBit != 0 {
283 1 : // Batch key. Always visible.
284 1 : lastBatchIdx = i
285 1 : } else if seqNum >= snapshot {
286 1 : // This key is not visible.
287 1 : lastNonVisibleIdx = i
288 1 : }
289 : }
290 :
291 : // In the following comments: b = batch, h = hidden, v = visible (committed).
292 1 : switch {
293 1 : case lastNonVisibleIdx == -1:
294 1 : // All keys are visible.
295 1 : //
296 1 : // [b b b], [v v v] and [b b b v v v]
297 1 : ret.Keys = s.Keys
298 1 : case lastBatchIdx == -1:
299 1 : // There are no batch keys, so we can return the continuous subslice
300 1 : // starting after the last non-visible Key.
301 1 : //
302 1 : // h h h [v v v]
303 1 : ret.Keys = s.Keys[lastNonVisibleIdx+1:]
304 1 : case lastNonVisibleIdx == len(s.Keys)-1:
305 1 : // While we have a batch key and non-visible keys, there are no
306 1 : // committed visible keys. The 'sandwich' is missing the bottom layer,
307 1 : // so we can return the continuous sublice at the beginning.
308 1 : //
309 1 : // [b b b] h h h
310 1 : ret.Keys = s.Keys[0 : lastBatchIdx+1]
311 1 : default:
312 1 : // This is the problematic sandwich case. Allocate a new slice, copying
313 1 : // the batch keys and the visible keys into it.
314 1 : //
315 1 : // [b b b] h h h [v v v]
316 1 : ret.Keys = make([]Key, (lastBatchIdx+1)+(len(s.Keys)-lastNonVisibleIdx-1))
317 1 : copy(ret.Keys, s.Keys[:lastBatchIdx+1])
318 1 : copy(ret.Keys[lastBatchIdx+1:], s.Keys[lastNonVisibleIdx+1:])
319 : }
320 1 : return ret
321 : }
322 :
323 : // VisibleAt returns true if the span contains a key visible at the provided
324 : // snapshot. Keys with sequence numbers with the batch bit set are treated as
325 : // always visible.
326 : //
327 : // VisibleAt requires the Span's keys be in ByTrailerDesc order. It panics if
328 : // the span's keys are sorted in a different order.
329 1 : func (s *Span) VisibleAt(snapshot base.SeqNum) bool {
330 1 : if s.KeysOrder != ByTrailerDesc {
331 0 : panic("pebble: span's keys unexpectedly not in trailer order")
332 : }
333 1 : if len(s.Keys) == 0 {
334 0 : return false
335 1 : } else if first := s.Keys[0].SeqNum(); first&base.SeqNumBatchBit != 0 {
336 1 : // Only visible batch keys are included when an Iterator's batch spans
337 1 : // are fragmented. They must always be visible.
338 1 : return true
339 1 : } else {
340 1 : // Otherwise we check the last key. Since keys are ordered decreasing in
341 1 : // sequence number, the last key has the lowest sequence number of any
342 1 : // of the span's keys. If any of the keys are visible, the last key must
343 1 : // be visible. Or put differently: if the last key is not visible, then
344 1 : // no key is visible.
345 1 : return s.Keys[len(s.Keys)-1].SeqNum() < snapshot
346 1 : }
347 : }
348 :
349 : // Clone clones the span, creating copies of all contained slices. Clone is
350 : // allocation heavy and should not be used in hot paths.
351 1 : func (s *Span) Clone() Span {
352 1 : c := Span{
353 1 : Start: slices.Clone(s.Start),
354 1 : End: slices.Clone(s.End),
355 1 : KeysOrder: s.KeysOrder,
356 1 : }
357 1 : c.Keys = make([]Key, len(s.Keys))
358 1 : for i := range c.Keys {
359 1 : c.Keys[i] = s.Keys[i].Clone()
360 1 : }
361 1 : return c
362 : }
363 :
364 : // Contains returns true if the specified key resides within the span's bounds.
365 1 : func (s *Span) Contains(cmp base.Compare, key []byte) bool {
366 1 : return cmp(s.Start, key) <= 0 && cmp(key, s.End) < 0
367 1 : }
368 :
369 : // Covers returns true if the span covers keys at seqNum.
370 : //
371 : // Covers requires the Span's keys be in ByTrailerDesc order. It panics if the
372 : // span's keys are sorted in a different order.
373 1 : func (s Span) Covers(seqNum base.SeqNum) bool {
374 1 : if s.KeysOrder != ByTrailerDesc {
375 0 : panic("pebble: span's keys unexpectedly not in trailer order")
376 : }
377 1 : return !s.Empty() && s.Keys[0].SeqNum() > seqNum
378 : }
379 :
380 : // CoversAt returns true if the span contains a key that is visible at the
381 : // provided snapshot sequence number, and that key's sequence number is higher
382 : // than seqNum.
383 : //
384 : // Keys with sequence numbers with the batch bit set are treated as always
385 : // visible.
386 : //
387 : // CoversAt requires the Span's keys be in ByTrailerDesc order. It panics if the
388 : // span's keys are sorted in a different order.
389 1 : func (s *Span) CoversAt(snapshot, seqNum base.SeqNum) bool {
390 1 : if s.KeysOrder != ByTrailerDesc {
391 0 : panic("pebble: span's keys unexpectedly not in trailer order")
392 : }
393 : // NB: A key is visible at `snapshot` if its sequence number is strictly
394 : // less than `snapshot`. See base.Visible.
395 1 : for i := range s.Keys {
396 1 : if kseq := s.Keys[i].SeqNum(); kseq&base.SeqNumBatchBit != 0 {
397 1 : // Only visible batch keys are included when an Iterator's batch spans
398 1 : // are fragmented. They must always be visible.
399 1 : return kseq > seqNum
400 1 : } else if kseq < snapshot {
401 1 : return kseq > seqNum
402 1 : }
403 : }
404 1 : return false
405 : }
406 :
407 : // Reset clears the span's Start, End, and Keys fields, retaining the slices for
408 : // reuse.
409 1 : func (s *Span) Reset() {
410 1 : s.Start = s.Start[:0]
411 1 : s.End = s.End[:0]
412 1 : s.Keys = s.Keys[:0]
413 1 : }
414 :
415 : // CopyFrom deep-copies the contents of the other span, retaining the slices
416 : // allocated in this span.
417 1 : func (s *Span) CopyFrom(other *Span) {
418 1 : s.Start = append(s.Start[:0], other.Start...)
419 1 : s.End = append(s.End[:0], other.End...)
420 1 :
421 1 : // We want to preserve any existing Suffix/Value buffers.
422 1 : if cap(s.Keys) >= len(other.Keys) {
423 1 : s.Keys = s.Keys[:len(other.Keys)]
424 1 : } else {
425 1 : s.Keys = append(s.Keys[:cap(s.Keys)], make([]Key, len(other.Keys)-cap(s.Keys))...)
426 1 : }
427 1 : for i := range other.Keys {
428 1 : s.Keys[i].CopyFrom(other.Keys[i])
429 1 : }
430 :
431 1 : s.KeysOrder = other.KeysOrder
432 : }
433 :
434 : // String returns a string representation of the span.
435 1 : func (s Span) String() string {
436 1 : return fmt.Sprint(prettySpan{Span: s, formatKey: base.DefaultFormatter})
437 1 : }
438 :
439 : // Pretty returns a formatter for the span.
440 1 : func (s Span) Pretty(f base.FormatKey) fmt.Formatter {
441 1 : // TODO(jackson): Take a base.FormatValue to format Key.Value too.
442 1 : return prettySpan{s, f}
443 1 : }
444 :
445 : type prettySpan struct {
446 : Span
447 : formatKey base.FormatKey
448 : }
449 :
450 1 : func (s prettySpan) Format(fs fmt.State, c rune) {
451 1 : if !s.Valid() {
452 1 : fmt.Fprintf(fs, "<invalid>")
453 1 : return
454 1 : }
455 1 : fmt.Fprintf(fs, "%s-%s:{", s.formatKey(s.Start), s.formatKey(s.End))
456 1 : for i, k := range s.Keys {
457 1 : if i > 0 {
458 1 : fmt.Fprint(fs, " ")
459 1 : }
460 1 : fmt.Fprint(fs, k.String())
461 : }
462 1 : fmt.Fprintf(fs, "}")
463 : }
464 :
465 : // SortKeysByTrailer sorts a Keys slice by trailer.
466 1 : func SortKeysByTrailer(keys []Key) {
467 1 : slices.SortFunc(keys, func(a, b Key) int {
468 1 : // Trailer are ordered in decreasing number order.
469 1 : return -cmp.Compare(a.Trailer, b.Trailer)
470 1 : })
471 : }
472 :
473 : // SortKeysByTrailerAndSuffix sorts a Keys slice by trailer, and among keys with
474 : // equal trailers, by suffix.
475 1 : func SortKeysByTrailerAndSuffix(suffixCmp base.CompareRangeSuffixes, keys []Key) {
476 1 : slices.SortFunc(keys, func(a, b Key) int {
477 1 : // Trailer are ordered in decreasing number order.
478 1 : if v := cmp.Compare(b.Trailer, a.Trailer); v != 0 {
479 1 : return v
480 1 : }
481 1 : return suffixCmp(a.Suffix, b.Suffix)
482 : })
483 : }
484 :
485 : // SortSpansByStartKey sorts the spans by start key.
486 : //
487 : // This is the ordering required by the Fragmenter. Usually spans are naturally
488 : // sorted by their start key, but that isn't true for range deletion tombstones
489 : // in the legacy range-del-v1 block format.
490 1 : func SortSpansByStartKey(cmp base.Compare, spans []Span) {
491 1 : slices.SortFunc(spans, func(a, b Span) int {
492 1 : return cmp(a.Start, b.Start)
493 1 : })
494 : }
495 :
496 : // SortSpansByEndKey sorts the spans by the end key.
497 1 : func SortSpansByEndKey(cmp base.Compare, spans []Span) {
498 1 : slices.SortFunc(spans, func(a, b Span) int {
499 1 : return cmp(a.End, b.End)
500 1 : })
501 : }
502 :
503 : // ParseSpan parses the string representation of a Span. It's intended for
504 : // tests. ParseSpan panics if passed a malformed span representation.
505 1 : func ParseSpan(input string) Span {
506 1 : var s Span
507 1 : parts := strings.FieldsFunc(input, func(r rune) bool {
508 1 : switch r {
509 1 : case '-', ':', '{', '}':
510 1 : return true
511 1 : default:
512 1 : return unicode.IsSpace(r)
513 : }
514 : })
515 1 : s.Start, s.End = []byte(parts[0]), []byte(parts[1])
516 1 :
517 1 : // Each of the remaining parts represents a single Key.
518 1 : s.Keys = make([]Key, 0, len(parts)-2)
519 1 : for _, p := range parts[2:] {
520 1 : if len(p) >= 2 && p[0] == '(' && p[len(p)-1] == ')' {
521 1 : p = p[1 : len(p)-1]
522 1 : }
523 1 : keyFields := strings.FieldsFunc(p, func(r rune) bool {
524 1 : switch r {
525 1 : case '#', ',':
526 1 : return true
527 1 : default:
528 1 : return unicode.IsSpace(r)
529 : }
530 : })
531 :
532 1 : var k Key
533 1 : seqNum := base.ParseSeqNum(keyFields[0])
534 1 : kind := base.ParseKind(keyFields[1])
535 1 : k.Trailer = base.MakeTrailer(seqNum, kind)
536 1 : // Parse the optional suffix.
537 1 : if len(keyFields) >= 3 {
538 1 : k.Suffix = []byte(keyFields[2])
539 1 : }
540 : // Parse the optional value.
541 1 : if len(keyFields) >= 4 {
542 1 : k.Value = []byte(keyFields[3])
543 1 : }
544 1 : s.Keys = append(s.Keys, k)
545 : }
546 1 : for i := 1; i < len(s.Keys); i++ {
547 1 : if s.Keys[i-1].Trailer < s.Keys[i].Trailer {
548 0 : panic(fmt.Sprintf("span keys not sorted: %s %s", s.Keys[i-1], s.Keys[i]))
549 : }
550 : }
551 1 : s.KeysOrder = ByTrailerDesc
552 1 : return s
553 : }
|