LCOV - code coverage report
Current view: top level - pebble/internal/base - comparer.go (source / functions) Hit Total Coverage
Test: 2024-11-16 08:16Z 9ed54bc4 - tests only.lcov Lines: 169 202 83.7 %
Date: 2024-11-16 08:17:17 Functions: 0 0 -

          Line data    Source code
       1             : // Copyright 2011 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 base
       6             : 
       7             : import (
       8             :         "bytes"
       9             :         "encoding/binary"
      10             :         "fmt"
      11             :         "slices"
      12             :         "strconv"
      13             :         "unicode/utf8"
      14             : 
      15             :         "github.com/cockroachdb/errors"
      16             : )
      17             : 
      18             : // Compare returns -1, 0, or +1 depending on whether a is 'less than', 'equal
      19             : // to' or 'greater than' b.
      20             : //
      21             : // Both a and b must be valid keys. Note that because of synthetic prefix
      22             : // functionality, the Compare function can be called on a key (either from the
      23             : // database or passed as an argument for an iterator operation) after the
      24             : // synthetic prefix has been removed. In general, this implies that removing any
      25             : // leading bytes from a prefix must yield another valid prefix.
      26             : //
      27             : // A key a is less than b if a's prefix is byte-wise less than b's prefix, or if
      28             : // the prefixes are equal and a's suffix is less than b's suffix (according to
      29             : // ComparePointSuffixes).
      30             : //
      31             : // In other words, if prefix(a) = a[:Split(a)] and suffix(a) = a[Split(a):]:
      32             : //
      33             : //        Compare(a, b) = bytes.Compare(prefix(a), prefix(b)) if not 0,
      34             : //                              otherwise ComparePointSuffixes(suffix(a), suffix(b))
      35             : //
      36             : // Compare defaults to using the formula above but it can be customized if there
      37             : // is a (potentially faster) specialization or it has to compare suffixes
      38             : // differently.
      39             : type Compare func(a, b []byte) int
      40             : 
      41             : // CompareRangeSuffixes compares two suffixes where either or both suffix
      42             : // originates from a range key and returns -1, 0, or +1.
      43             : //
      44             : // For historical reasons (see
      45             : // https://github.com/cockroachdb/cockroach/issues/130533 for a summary), we
      46             : // allow this function to be more strict than Compare. Specifically, Compare may
      47             : // treat two suffixes as equal whereas CompareRangeSuffixes might not.
      48             : //
      49             : // CompareRangeSuffixes is allowed to be more strict than see
      50             : // ComparePointSuffixes, meaning it may return -1 or +1 when
      51             : // ComparePointSuffixes would return 0.
      52             : //
      53             : // The empty slice suffix must be 'less than' any non-empty suffix.
      54             : type CompareRangeSuffixes func(a, b []byte) int
      55             : 
      56             : // ComparePointSuffixes compares two point key suffixes and returns -1, 0, or +1.
      57             : //
      58             : // For historical reasons (see
      59             : // https://github.com/cockroachdb/cockroach/issues/130533 for a summary), this
      60             : // function is distinct from CompareRangeSuffixes. Specifically,
      61             : // ComparePointSuffixes may treat two suffixes as equal whereas
      62             : // CompareRangeSuffixes might not. Unlike CompareRangeSuffixes, this function
      63             : // must agree with Compare.
      64             : //
      65             : // The empty slice suffix must be 'less than' any non-empty suffix.
      66             : //
      67             : // A full key k is composed of a prefix k[:Split(k)] and suffix k[Split(k):].
      68             : // Suffixes are compared to break ties between equal prefixes.
      69             : type ComparePointSuffixes func(a, b []byte) int
      70             : 
      71             : // defaultCompare implements Compare in terms of Split and ComparePointSuffixes, as
      72             : // mentioned above.
      73           1 : func defaultCompare(split Split, compareSuffixes ComparePointSuffixes, a, b []byte) int {
      74           1 :         an := split(a)
      75           1 :         bn := split(b)
      76           1 :         if prefixCmp := bytes.Compare(a[:an], b[:bn]); prefixCmp != 0 {
      77           1 :                 return prefixCmp
      78           1 :         }
      79           1 :         return compareSuffixes(a[an:], b[bn:])
      80             : }
      81             : 
      82             : // Equal returns true if a and b are equivalent.
      83             : //
      84             : // For a given Compare, Equal(a,b)=true iff Compare(a,b)=0; that is, Equal is a
      85             : // (potentially faster) specialization of Compare.
      86             : type Equal func(a, b []byte) bool
      87             : 
      88             : // AbbreviatedKey returns a fixed length prefix of a user key such that
      89             : //
      90             : //      AbbreviatedKey(a) < AbbreviatedKey(b) implies a < b, and
      91             : //      AbbreviatedKey(a) > AbbreviatedKey(b) implies a > b.
      92             : //
      93             : // If AbbreviatedKey(a) == AbbreviatedKey(b), an additional comparison is
      94             : // required to determine if the two keys are actually equal.
      95             : //
      96             : // This helps optimize indexed batch comparisons for cache locality. If a Split
      97             : // function is specified, AbbreviatedKey usually returns the first eight bytes
      98             : // of the user key prefix in the order that gives the correct ordering.
      99             : type AbbreviatedKey func(key []byte) uint64
     100             : 
     101             : // FormatKey returns a formatter for the user key.
     102             : type FormatKey func(key []byte) fmt.Formatter
     103             : 
     104             : // DefaultFormatter is the default implementation of user key formatting:
     105             : // non-ASCII data is formatted as escaped hexadecimal values.
     106           1 : var DefaultFormatter FormatKey = func(key []byte) fmt.Formatter {
     107           1 :         return FormatBytes(key)
     108           1 : }
     109             : 
     110             : // FormatValue returns a formatter for the user value. The key is also specified
     111             : // for the value formatter in order to support value formatting that is
     112             : // dependent on the key.
     113             : type FormatValue func(key, value []byte) fmt.Formatter
     114             : 
     115             : // Separator is used to construct SSTable index blocks. A trivial implementation
     116             : // is `return append(dst, a...)`, but appending fewer bytes leads to smaller
     117             : // SSTables.
     118             : //
     119             : // Given keys a, b for which Compare(a, b) < 0, Separator produces a key k such
     120             : // that:
     121             : //
     122             : // 1. Compare(a, k) <= 0, and
     123             : // 2. Compare(k, b) < 0.
     124             : //
     125             : // For example, if a and b are the []byte equivalents of the strings "black" and
     126             : // "blue", then the function may append "blb" to dst.
     127             : type Separator func(dst, a, b []byte) []byte
     128             : 
     129             : // Successor appends to dst a shortened key k given a key a such that
     130             : // Compare(a, k) <= 0. A simple implementation may return a unchanged.
     131             : // The appended key k must be valid to pass to Compare.
     132             : type Successor func(dst, a []byte) []byte
     133             : 
     134             : // ImmediateSuccessor is invoked with a prefix key ([Split(a) == len(a)]) and
     135             : // appends to dst the smallest prefix key that is larger than the given prefix a.
     136             : //
     137             : // ImmediateSuccessor must generate a prefix key k such that:
     138             : //
     139             : //      Split(k) == len(k) and Compare(a, k) < 0
     140             : //
     141             : // and there exists no representable prefix key k2 such that:
     142             : //
     143             : //      Split(k2) == len(k2) and Compare(a, k2) < 0 and Compare(k2, k) < 0
     144             : //
     145             : // As an example, an implementation built on the natural byte ordering using
     146             : // bytes.Compare could append a `\0` to `a`.
     147             : //
     148             : // The appended key must be valid to pass to Compare.
     149             : type ImmediateSuccessor func(dst, a []byte) []byte
     150             : 
     151             : // Split returns the length of the prefix of the user key that corresponds to
     152             : // the key portion of an MVCC encoding scheme to enable the use of prefix bloom
     153             : // filters.
     154             : //
     155             : // The method will only ever be called with valid MVCC keys, that is, keys that
     156             : // the user could potentially store in the database. Pebble does not know which
     157             : // keys are MVCC keys and which are not, and may call Split on both MVCC keys
     158             : // and non-MVCC keys.
     159             : //
     160             : // A trivial MVCC scheme is one in which Split() returns len(a). This
     161             : // corresponds to assigning a constant version to each key in the database. For
     162             : // performance reasons, it is preferable to use a `nil` split in this case.
     163             : //
     164             : // Let prefix(a) = a[:Split(a)] and suffix(a) = a[Split(a):]. The following
     165             : // properties must hold:
     166             : //
     167             : //  1. A key consisting of just a prefix must sort before all other keys with
     168             : //     that prefix:
     169             : //
     170             : //     If len(suffix(a)) > 0, then Compare(prefix(a), a) < 0.
     171             : //
     172             : //  2. Prefixes must be used to order keys before suffixes:
     173             : //
     174             : //     If Compare(a, b) <= 0, then Compare(prefix(a), prefix(b)) <= 0.
     175             : //     If Compare(prefix(a), prefix(b)) < 0, then Compare(a, b) < 0
     176             : //
     177             : //  3. Suffixes themselves must be valid keys and comparable, respecting the same
     178             : //     ordering as within a key:
     179             : //
     180             : //     If Compare(prefix(a), prefix(b)) = 0, then Compare(a, b) = Compare(suffix(a), suffix(b)).
     181             : type Split func(a []byte) int
     182             : 
     183             : // Prefix returns the prefix of the key k, using s to split the key.
     184           1 : func (s Split) Prefix(k []byte) []byte {
     185           1 :         i := s(k)
     186           1 :         return k[:i:i]
     187           1 : }
     188             : 
     189             : // DefaultSplit is a trivial implementation of Split which always returns the
     190             : // full key.
     191           1 : var DefaultSplit Split = func(key []byte) int { return len(key) }
     192             : 
     193             : // Comparer defines a total ordering over the space of []byte keys: a 'less
     194             : // than' relationship.
     195             : type Comparer struct {
     196             :         // The following must always be specified.
     197             :         AbbreviatedKey AbbreviatedKey
     198             :         Separator      Separator
     199             :         Successor      Successor
     200             : 
     201             :         // ImmediateSuccessor must be specified if range keys are used.
     202             :         ImmediateSuccessor ImmediateSuccessor
     203             : 
     204             :         // Split defaults to a trivial implementation that returns the full key length
     205             :         // if it is not specified.
     206             :         Split Split
     207             : 
     208             :         // CompareRangeSuffixes defaults to bytes.Compare if it is not specified.
     209             :         CompareRangeSuffixes CompareRangeSuffixes
     210             :         // ComparePointSuffixes defaults to bytes.Compare if it is not specified.
     211             :         ComparePointSuffixes ComparePointSuffixes
     212             : 
     213             :         // Compare defaults to a generic implementation that uses Split,
     214             :         // bytes.Compare, and ComparePointSuffixes if it is not specified.
     215             :         Compare Compare
     216             :         // Equal defaults to using Compare() == 0 if it is not specified.
     217             :         Equal Equal
     218             :         // FormatKey defaults to the DefaultFormatter if it is not specified.
     219             :         FormatKey FormatKey
     220             : 
     221             :         // FormatValue is optional.
     222             :         FormatValue FormatValue
     223             : 
     224             :         // Name is the name of the comparer.
     225             :         //
     226             :         // The on-disk format stores the comparer name, and opening a database with a
     227             :         // different comparer from the one it was created with will result in an
     228             :         // error.
     229             :         Name string
     230             : }
     231             : 
     232             : // EnsureDefaults ensures that all non-optional fields are set.
     233             : //
     234             : // If c is nil, returns DefaultComparer.
     235             : //
     236             : // If any fields need to be set, returns a modified copy of c.
     237           1 : func (c *Comparer) EnsureDefaults() *Comparer {
     238           1 :         if c == nil {
     239           1 :                 return DefaultComparer
     240           1 :         }
     241           1 :         if c.AbbreviatedKey == nil || c.Separator == nil || c.Successor == nil || c.Name == "" {
     242           0 :                 panic("invalid Comparer: mandatory field not set")
     243             :         }
     244           1 :         if c.CompareRangeSuffixes != nil && c.Compare != nil && c.Equal != nil && c.Split != nil && c.FormatKey != nil {
     245           1 :                 return c
     246           1 :         }
     247           1 :         n := &Comparer{}
     248           1 :         *n = *c
     249           1 : 
     250           1 :         if n.Split == nil {
     251           1 :                 n.Split = DefaultSplit
     252           1 :         }
     253           1 :         if n.CompareRangeSuffixes == nil && n.Compare == nil && n.Equal == nil {
     254           0 :                 n.CompareRangeSuffixes = bytes.Compare
     255           0 :                 n.Compare = bytes.Compare
     256           0 :                 n.Equal = bytes.Equal
     257           1 :         } else {
     258           1 :                 if n.CompareRangeSuffixes == nil {
     259           1 :                         n.CompareRangeSuffixes = bytes.Compare
     260           1 :                 }
     261           1 :                 if n.Compare == nil {
     262           0 :                         n.Compare = func(a, b []byte) int {
     263           0 :                                 return defaultCompare(n.Split, n.ComparePointSuffixes, a, b)
     264           0 :                         }
     265             :                 }
     266           1 :                 if n.Equal == nil {
     267           1 :                         n.Equal = func(a, b []byte) bool {
     268           0 :                                 return n.Compare(a, b) == 0
     269           0 :                         }
     270             :                 }
     271             :         }
     272           1 :         if n.FormatKey == nil {
     273           1 :                 n.FormatKey = DefaultFormatter
     274           1 :         }
     275           1 :         return n
     276             : }
     277             : 
     278             : // DefaultComparer is the default implementation of the Comparer interface.
     279             : // It uses the natural ordering, consistent with bytes.Compare.
     280             : var DefaultComparer = &Comparer{
     281             :         ComparePointSuffixes: bytes.Compare,
     282             :         CompareRangeSuffixes: bytes.Compare,
     283             :         Compare:              bytes.Compare,
     284             :         Equal:                bytes.Equal,
     285             : 
     286           1 :         AbbreviatedKey: func(key []byte) uint64 {
     287           1 :                 if len(key) >= 8 {
     288           1 :                         return binary.BigEndian.Uint64(key)
     289           1 :                 }
     290           1 :                 var v uint64
     291           1 :                 for _, b := range key {
     292           1 :                         v <<= 8
     293           1 :                         v |= uint64(b)
     294           1 :                 }
     295           1 :                 return v << uint(8*(8-len(key)))
     296             :         },
     297             : 
     298             :         Split: DefaultSplit,
     299             : 
     300             :         FormatKey: DefaultFormatter,
     301             : 
     302           1 :         Separator: func(dst, a, b []byte) []byte {
     303           1 :                 i, n := SharedPrefixLen(a, b), len(dst)
     304           1 :                 dst = append(dst, a...)
     305           1 : 
     306           1 :                 min := len(a)
     307           1 :                 if min > len(b) {
     308           1 :                         min = len(b)
     309           1 :                 }
     310           1 :                 if i >= min {
     311           1 :                         // Do not shorten if one string is a prefix of the other.
     312           1 :                         return dst
     313           1 :                 }
     314             : 
     315           1 :                 if a[i] >= b[i] {
     316           1 :                         // b is smaller than a or a is already the shortest possible.
     317           1 :                         return dst
     318           1 :                 }
     319             : 
     320           1 :                 if i < len(b)-1 || a[i]+1 < b[i] {
     321           1 :                         i += n
     322           1 :                         dst[i]++
     323           1 :                         return dst[:i+1]
     324           1 :                 }
     325             : 
     326           1 :                 i += n + 1
     327           1 :                 for ; i < len(dst); i++ {
     328           1 :                         if dst[i] != 0xff {
     329           1 :                                 dst[i]++
     330           1 :                                 return dst[:i+1]
     331           1 :                         }
     332             :                 }
     333           1 :                 return dst
     334             :         },
     335             : 
     336           1 :         Successor: func(dst, a []byte) (ret []byte) {
     337           1 :                 for i := 0; i < len(a); i++ {
     338           1 :                         if a[i] != 0xff {
     339           1 :                                 dst = append(dst, a[:i+1]...)
     340           1 :                                 dst[len(dst)-1]++
     341           1 :                                 return dst
     342           1 :                         }
     343             :                 }
     344             :                 // a is a run of 0xffs, leave it alone.
     345           1 :                 return append(dst, a...)
     346             :         },
     347             : 
     348           0 :         ImmediateSuccessor: func(dst, a []byte) (ret []byte) {
     349           0 :                 return append(append(dst, a...), 0x00)
     350           0 :         },
     351             : 
     352             :         // This name is part of the C++ Level-DB implementation's default file
     353             :         // format, and should not be changed.
     354             :         Name: "leveldb.BytewiseComparator",
     355             : }
     356             : 
     357             : // SharedPrefixLen returns the largest i such that a[:i] equals b[:i].
     358             : // This function can be useful in implementing the Comparer interface.
     359           1 : func SharedPrefixLen(a, b []byte) int {
     360           1 :         i, n := 0, len(a)
     361           1 :         if n > len(b) {
     362           1 :                 n = len(b)
     363           1 :         }
     364           1 :         asUint64 := func(c []byte, i int) uint64 {
     365           1 :                 return binary.LittleEndian.Uint64(c[i:])
     366           1 :         }
     367           1 :         for i < n-7 && asUint64(a, i) == asUint64(b, i) {
     368           1 :                 i += 8
     369           1 :         }
     370           1 :         for i < n && a[i] == b[i] {
     371           1 :                 i++
     372           1 :         }
     373           1 :         return i
     374             : }
     375             : 
     376             : // MinUserKey returns the smaller of two user keys. If one of the keys is nil,
     377             : // the other one is returned.
     378           1 : func MinUserKey(cmp Compare, a, b []byte) []byte {
     379           1 :         if a != nil && (b == nil || cmp(a, b) < 0) {
     380           1 :                 return a
     381           1 :         }
     382           1 :         return b
     383             : }
     384             : 
     385             : // FormatBytes formats a byte slice using hexadecimal escapes for non-ASCII
     386             : // data.
     387             : type FormatBytes []byte
     388             : 
     389             : const lowerhex = "0123456789abcdef"
     390             : 
     391             : // Format implements the fmt.Formatter interface.
     392           1 : func (p FormatBytes) Format(s fmt.State, c rune) {
     393           1 :         buf := make([]byte, 0, len(p))
     394           1 :         for _, b := range p {
     395           1 :                 if b < utf8.RuneSelf && strconv.IsPrint(rune(b)) {
     396           1 :                         buf = append(buf, b)
     397           1 :                         continue
     398             :                 }
     399           1 :                 buf = append(buf, `\x`...)
     400           1 :                 buf = append(buf, lowerhex[b>>4])
     401           1 :                 buf = append(buf, lowerhex[b&0xF])
     402             :         }
     403           1 :         s.Write(buf)
     404             : }
     405             : 
     406             : // MakeAssertComparer creates a Comparer that is the same with the given
     407             : // Comparer except that it asserts that the Compare and Equal functions adhere
     408             : // to their specifications.
     409           1 : func MakeAssertComparer(c Comparer) Comparer {
     410           1 :         return Comparer{
     411           1 :                 Compare: func(a []byte, b []byte) int {
     412           1 :                         res := c.Compare(a, b)
     413           1 :                         // Verify that Compare is consistent with the default implementation.
     414           1 :                         if expected := defaultCompare(c.Split, c.ComparePointSuffixes, a, b); res != expected {
     415           0 :                                 panic(AssertionFailedf("%s: Compare(%s, %s)=%d, expected %d",
     416           0 :                                         c.Name, c.FormatKey(a), c.FormatKey(b), res, expected))
     417             :                         }
     418           1 :                         return res
     419             :                 },
     420             : 
     421           1 :                 Equal: func(a []byte, b []byte) bool {
     422           1 :                         eq := c.Equal(a, b)
     423           1 :                         // Verify that Equal is consistent with Compare.
     424           1 :                         if expected := c.Compare(a, b); eq != (expected == 0) {
     425           0 :                                 panic("Compare and Equal are not consistent")
     426             :                         }
     427           1 :                         return eq
     428             :                 },
     429             : 
     430             :                 // TODO(radu): add more checks.
     431             :                 ComparePointSuffixes: c.ComparePointSuffixes,
     432             :                 CompareRangeSuffixes: c.CompareRangeSuffixes,
     433             :                 AbbreviatedKey:       c.AbbreviatedKey,
     434             :                 Separator:            c.Separator,
     435             :                 Successor:            c.Successor,
     436             :                 ImmediateSuccessor:   c.ImmediateSuccessor,
     437             :                 FormatKey:            c.FormatKey,
     438             :                 Split:                c.Split,
     439             :                 FormatValue:          c.FormatValue,
     440             :                 Name:                 c.Name,
     441             :         }
     442             : }
     443             : 
     444             : // CheckComparer is a mini test suite that verifies a comparer implementation.
     445             : //
     446             : // It takes lists of valid prefixes and suffixes. It is recommended that both
     447             : // lists have at least three elements.
     448           1 : func CheckComparer(c *Comparer, prefixes [][]byte, suffixes [][]byte) error {
     449           1 :         // Empty slice is always a valid suffix.
     450           1 :         suffixes = append(suffixes, nil)
     451           1 : 
     452           1 :         // Verify the suffixes have a consistent ordering.
     453           1 :         slices.SortFunc(suffixes, c.CompareRangeSuffixes)
     454           1 :         if !slices.IsSortedFunc(suffixes, c.CompareRangeSuffixes) {
     455           0 :                 return errors.Errorf("CompareRangeSuffixes is inconsistent")
     456           0 :         }
     457             :         // Verify the ordering imposed by CompareRangeSuffixes is considered a valid
     458             :         // ordering for point suffixes. CompareRangeSuffixes imposes a stricter
     459             :         // ordering than ComaprePointSuffixes, but a CompareRangesSuffixes ordering
     460             :         // must be a valid ordering for point suffixes.
     461           1 :         if !slices.IsSortedFunc(suffixes, c.ComparePointSuffixes) {
     462           0 :                 return errors.Errorf("ComparePointSuffixes is inconsistent")
     463           0 :         }
     464             : 
     465           1 :         n := len(prefixes)
     466           1 :         // Removing leading bytes from prefixes must yield valid prefixes.
     467           1 :         for i := 0; i < n; i++ {
     468           1 :                 for j := 1; j < len(prefixes[i]); j++ {
     469           1 :                         prefixes = append(prefixes, prefixes[i][j:])
     470           1 :                 }
     471             :         }
     472             : 
     473             :         // Check the split function.
     474           1 :         for _, p := range prefixes {
     475           1 :                 for _, s := range suffixes {
     476           1 :                         key := slices.Concat(p, s)
     477           1 :                         if n := c.Split(key); n != len(p) {
     478           0 :                                 return errors.Errorf("incorrect Split result %d on '%x' (prefix '%x' suffix '%x')", n, key, p, s)
     479           0 :                         }
     480             :                 }
     481           1 :                 for i := 1; i < len(suffixes); i++ {
     482           1 :                         a := slices.Concat(p, suffixes[i-1])
     483           1 :                         b := slices.Concat(p, suffixes[i])
     484           1 :                         // Make sure the Compare function agrees with ComparePointSuffixes.
     485           1 :                         if cmp := c.Compare(a, b); cmp > 0 {
     486           0 :                                 return errors.Errorf("Compare(%s, %s)=%d, expected <= 0", c.FormatKey(a), c.FormatKey(b), cmp)
     487           0 :                         }
     488             :                 }
     489             :         }
     490             : 
     491             :         // Check the Compare/Equals functions on all possible combinations.
     492           1 :         for _, ap := range prefixes {
     493           1 :                 for _, as := range suffixes {
     494           1 :                         a := slices.Concat(ap, as)
     495           1 :                         for _, bp := range prefixes {
     496           1 :                                 for _, bs := range suffixes {
     497           1 :                                         b := slices.Concat(bp, bs)
     498           1 :                                         result := c.Compare(a, b)
     499           1 :                                         if (result == 0) != c.Equal(a, b) {
     500           0 :                                                 return errors.Errorf("Equal(%s, %s) doesn't agree with Compare", c.FormatKey(a), c.FormatKey(b))
     501           0 :                                         }
     502             : 
     503           1 :                                         if prefixCmp := bytes.Compare(ap, bp); prefixCmp != 0 {
     504           1 :                                                 if result != prefixCmp {
     505           0 :                                                         return errors.Errorf("Compare(%s, %s)=%d, expected %d", c.FormatKey(a), c.FormatKey(b), result, prefixCmp)
     506           0 :                                                 }
     507           1 :                                         } else {
     508           1 :                                                 // The prefixes are equal, so Compare's result should
     509           1 :                                                 // agree with ComparePointSuffixes.
     510           1 :                                                 if suffixCmp := c.ComparePointSuffixes(as, bs); result != suffixCmp {
     511           0 :                                                         return errors.Errorf("Compare(%s, %s)=%d but ComparePointSuffixes(%q, %q)=%d",
     512           0 :                                                                 c.FormatKey(a), c.FormatKey(b), result, as, bs, suffixCmp)
     513           0 :                                                 }
     514             :                                                 // If result == 0, CompareRangeSuffixes may not agree
     515             :                                                 // with ComparePointSuffixes, but otherwise it should.
     516           1 :                                                 if result != 0 {
     517           1 :                                                         if suffixCmp := c.CompareRangeSuffixes(as, bs); result != suffixCmp {
     518           0 :                                                                 return errors.Errorf("Compare(%s, %s)=%d, expected %d",
     519           0 :                                                                         c.FormatKey(a), c.FormatKey(b), result, suffixCmp)
     520           0 :                                                         }
     521             :                                                 }
     522             :                                         }
     523             :                                 }
     524             :                         }
     525             :                 }
     526             :         }
     527             : 
     528             :         // TODO(radu): check more methods.
     529           1 :         return nil
     530             : }

Generated by: LCOV version 1.14