Skip to content

Commit 72c3f55

Browse files
committed
base: make Compare more restrictive
The new block format assumes that prefixes are ordered lexicographically. We now require the `Compare` function to adhere to this assumption. This is already the case with our test comparators and the CRDB comparator. We add a `Comparer` wrapper that verifies this assumption, and we use this in a low percentage of `invariant` test runs.
1 parent 4f01ec4 commit 72c3f55

File tree

2 files changed

+57
-0
lines changed

2 files changed

+57
-0
lines changed

internal/base/comparer.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@ import (
2020
// the various DB methods, as well as those returned from Separator, Successor,
2121
// and Split. It is also used to compare key suffixes, i.e. the remainder of the
2222
// key after Split.
23+
//
24+
// The comparison of the prefix parts must be a simple byte-wise compare. In
25+
// other words, if a and be don't have suffixes, then
26+
// Compare(a, b) = bytes.Compare(a, b).
27+
//
28+
// In general, if prefix(a) = a[:Split(a)] and suffix(a) = a[Split(a):], then
29+
//
30+
// Compare(a, b) = bytes.Compare(prefix(a), prefix(b)) if not 0, or
31+
// bytes.Compare(suffix(a), suffix(b)) otherwise.
2332
type Compare func(a, b []byte) int
2433

2534
// Equal returns true if a and b are equivalent.
@@ -321,3 +330,46 @@ func (p FormatBytes) Format(s fmt.State, c rune) {
321330
}
322331
s.Write(buf)
323332
}
333+
334+
// MakeAssertComparer creates a Comparer that is the same with the given
335+
// Comparer except that it asserts that the Compare and Equal functions adhere
336+
// to their specifications.
337+
func MakeAssertComparer(c Comparer) Comparer {
338+
return Comparer{
339+
Compare: func(a []byte, b []byte) int {
340+
res := c.Compare(a, b)
341+
an := c.Split(a)
342+
aPrefix, aSuffix := a[:an], a[an:]
343+
bn := c.Split(b)
344+
bPrefix, bSuffix := b[:bn], b[bn:]
345+
if prefixCmp := bytes.Compare(aPrefix, bPrefix); prefixCmp == 0 {
346+
if suffixCmp := c.Compare(aSuffix, bSuffix); suffixCmp != res {
347+
panic(AssertionFailedf("%s: Compare with equal prefixes not consistent with Compare of suffixes: Compare(%q, %q)=%d, Compare(%q, %q)=%d",
348+
c.Name, a, b, res, aSuffix, bSuffix, suffixCmp,
349+
))
350+
}
351+
} else if prefixCmp != res {
352+
panic(AssertionFailedf("%s: Compare did not perform byte-wise comparison of prefixes", c.Name))
353+
}
354+
return res
355+
},
356+
357+
Equal: func(a []byte, b []byte) bool {
358+
eq := c.Equal(a, b)
359+
if cmp := c.Compare(a, b); eq != (cmp == 0) {
360+
panic("Compare and Equal are not consistent")
361+
}
362+
return eq
363+
},
364+
365+
// TODO(radu): add more checks.
366+
AbbreviatedKey: c.AbbreviatedKey,
367+
Separator: c.Separator,
368+
Successor: c.Successor,
369+
ImmediateSuccessor: c.ImmediateSuccessor,
370+
FormatKey: c.FormatKey,
371+
Split: c.Split,
372+
FormatValue: c.FormatValue,
373+
Name: c.Name,
374+
}
375+
}

open.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ func Open(dirname string, opts *Options) (db *DB, err error) {
8686
opts.Logger = opts.LoggerAndTracer
8787
}
8888

89+
if invariants.Sometimes(5) {
90+
assertComparer := base.MakeAssertComparer(*opts.Comparer)
91+
opts.Comparer = &assertComparer
92+
}
93+
8994
// In all error cases, we return db = nil; this is used by various
9095
// deferred cleanups.
9196

0 commit comments

Comments
 (0)