github.com/creachadair/ffs@v0.17.3/storage/dbkey/dbkey.go (about)

     1  // Copyright 2019 Michael J. Fromberger. All Rights Reserved.
     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 dbkey provides some common utility code for working with key-value
    16  // stores that use prefixes to partition the key space.
    17  package dbkey
    18  
    19  import (
    20  	"crypto/sha256"
    21  	"encoding/hex"
    22  	"strings"
    23  )
    24  
    25  // PrefixLen is the length in bytes of the prefixes generated by this package.
    26  const PrefixLen = 6
    27  
    28  // Prefix is a prefix that defines a subset of the keys of a key-value store.
    29  // Any string can be a valid prefix, including "".
    30  type Prefix string
    31  
    32  // Sub creates a subspace prefix based on p, using name.
    33  // Derived prefixes are always [PrefixLen] bytes long.
    34  func (p Prefix) Sub(name string) Prefix { return p.derive(".", name) }
    35  
    36  // Keyspace creates a keyspace prefix based on p, using name.
    37  // Derived prefixes are always [PrefixLen] bytes long.
    38  func (p Prefix) Keyspace(name string) Prefix { return p.derive(":", name) }
    39  
    40  // String renders p as a hexadecimal string.
    41  func (p Prefix) String() string { return hex.EncodeToString([]byte(p)) }
    42  
    43  // Add returns a copy of s with the representation of p prepended to it.
    44  func (p Prefix) Add(s string) string { return string(p) + s }
    45  
    46  // Remove returns a copy of s with the representation of p removed from it.
    47  func (p Prefix) Remove(s string) string { return strings.TrimPrefix(s, string(p)) }
    48  
    49  // Cut reports whether s begins with p, and if so returns p.Remove(s).
    50  // If not, it returns s unmodified.
    51  func (p Prefix) Cut(s string) (string, bool) { return strings.CutPrefix(s, string(p)) }
    52  
    53  // derive computes a key prefix based on p and the name of the derived
    54  // entity. The resulting prefix should be unique among all paths from the root.
    55  //
    56  // The prefix is constructed by hashing (SHA256) the base prefix with the name
    57  // and taking off "enough" bytes from the hash to ensure uniqueness without
    58  // too-much expanding the storage cost.  The separator distinguishes between
    59  // prefixes for substores (".") and for key spaces (":").
    60  //
    61  //   - The initial root store prefix is "".
    62  //   - To derive a prefix for a substore, compute SHA256(cur, ".", sub)
    63  //   - To derive a prefix for a keyspace, compute SHA256(cur, ":", name)
    64  //   - Truncate the hash to the first 6 bytes to form the prefix.
    65  //
    66  // Using different separators ensures a subspace cannot collide with a keyspace
    67  // at the same level of indirection. These rules are iterated, so that the
    68  // prefix for the keyspace "" → "sub1" → "sub2" → "ks" will be:
    69  //
    70  //	SHA256(SHA256(SHA256(".sub1")[:6]+".sub2")[:6]+":ks")[:6]
    71  //
    72  // or in this case, 47b256a71b00 (manually verified).
    73  func (p Prefix) derive(sep, name string) Prefix {
    74  	sum := sha256.Sum256([]byte(string(p) + sep + name))
    75  	return Prefix(sum[:6])
    76  }