tdb/indicies.go
2020-04-15 23:53:50 -05:00

169 lines
4.8 KiB
Go

package tdb
import (
"bytes"
bolt "go.etcd.io/bbolt"
)
var (
IndexKeySeparator = []byte("&")
IndexFieldSeparator = []byte("=")
)
type indexish interface {
debugLogger
count(tx *Tx) int
initialize(tx *Tx) error
validate(tx *Tx, val dbPtrValue) error
update(tx *Tx, old, new dbPtrValue)
updateRaw(tx *Tx, write, delete [][]byte)
put(tx *Tx, val dbPtrValue)
putRaw(tx *Tx, val [][]byte)
delete(tx *Tx, val dbPtrValue)
deleteRaw(tx *Tx, val [][]byte)
bucket(tx *Tx) *bolt.Bucket
iteratePrefixed(tx *Tx, prefix []byte, i KeyIterator) error
indexedValues(val dbPtrValue) [][]byte
keyValue(val dbPtrValue) []byte
indexKeys(val dbPtrValue) [][]byte
shouldUpdate(tx *Tx, oldVal, newVal dbPtrValue) (needsUpdate bool, oldKeys, newKeys, writes, deletes [][]byte)
}
func indexishKeys(i indexish, pv dbPtrValue) [][]byte {
vals := i.indexedValues(pv)
if len(vals) == 0 {
return vals
}
keyVal := i.keyValue(pv)
for i, val := range vals {
bb := &bytes.Buffer{}
bb.Write(val)
bb.Write(IndexKeySeparator)
bb.Write(keyVal)
vals[i] = bb.Bytes()
}
return vals
}
func indexishPutRaw(i indexish, tx *Tx, writes [][]byte) {
lenWrites := len(writes)
i.debugLogf("[indexishPutRaw] putting %d keys", lenWrites)
if lenWrites == 0 {
return
}
b := i.bucket(tx)
for _, entry := range writes {
err := b.Put(entry, []byte{})
if err != nil {
i.debugLogf("%s", err)
panic(err)
}
}
}
func indexishDeleteRaw(i indexish, tx *Tx, deletes [][]byte) {
lenDeletes := len(deletes)
i.debugLogf("[indexishDeleteRaw] deleting %d keys", lenDeletes)
if lenDeletes == 0 {
return
}
b := i.bucket(tx)
for _, entry := range deletes {
err := b.Put(entry, []byte{})
if err != nil {
i.debugLogf("%s", err)
panic(err)
}
}
}
func indexishUpdateRaw(i indexish, tx *Tx, writes, deletes [][]byte) {
i.deleteRaw(tx, deletes)
i.putRaw(tx, writes)
}
func indexishShouldUpdate(i indexish, oldVal, newVal dbPtrValue) (bool, [][]byte, [][]byte, [][]byte, [][]byte) {
oldKeys := i.indexKeys(oldVal)
lenOldKeys := len(oldKeys)
newKeys := i.indexKeys(newVal)
lenNewKeys := len(newKeys)
// no keys before or after, nothing to do
if lenOldKeys == 0 && lenNewKeys == 0 {
return false, nil, nil, nil, nil
}
// either all new or all deleted, then just do that
if lenNewKeys == 0 || lenOldKeys == 0 {
return true, oldKeys, newKeys, newKeys, oldKeys
}
// we can handle things simply if we have exactly 1 of everything, this will be a fairly common case
if lenNewKeys == 1 && lenOldKeys == 1 {
// if the keys are the same then we don't need to do anything
if bytes.Equal(oldKeys[0], newKeys[0]) {
return false, nil, nil, nil, nil
}
// otherwise we need to delete and write the old and new respectively
return true, oldKeys, newKeys, newKeys, oldKeys
}
// the real meat and potatoes starts here
// we will need a lookup table of one of the slices, old was chosen for no particular reason
oldMap := make(map[string]bool)
for _, oldKey := range oldKeys {
oldMap[string(oldKey)] = true
}
i.debugLogf("[indexishDeleteRaw] indexed old with %d entries", len(oldMap))
writes := make([][]byte, 0, lenNewKeys)
// then we look at the other (new) slice
for _, newKey := range newKeys {
// and check if it needs to be created/not deleted (missing from lookup)
if _, has := oldMap[string(newKey)]; !has {
i.debugLogf("[indexishDeleteRaw] old does not have new key %s", newKey)
writes = append(writes, newKey)
} else {
i.debugLogf("[indexishDeleteRaw] old does has new key %s", newKey)
delete(oldMap, string(newKey))
}
}
// before having to do more we can check a few optimized paths
lenWrites := len(writes)
i.debugLogf("[indexishDeleteRaw] found %d writes", lenWrites)
// skip some steps if we need to write all keys, which implies deleting everything that's old
if lenWrites == lenNewKeys {
return true, oldKeys, newKeys, newKeys, oldKeys
}
// don't do anything if we have no writes and the old and new are the same length as they must be equal
if lenWrites == 0 && lenOldKeys == lenNewKeys {
i.debugLog("[indexishDeleteRaw] found no changes")
return false, nil, nil, nil, nil
}
lenDeletes := len(oldMap)
i.debugLogf("[indexishDeleteRaw] found %d deletes", lenDeletes)
// finally, we can skip building the deletion slice if we don't have to delete anything
if lenDeletes == 0 {
return true, oldKeys, newKeys, writes, [][]byte{}
}
deletes := make([][]byte, 0, lenDeletes)
// and finally we turn anything still in the lookup table into the list of deletions
for oldKey, _ := range oldMap {
deletes = append(deletes, []byte(oldKey))
}
// this case _should_ be unreachable due to our earlier optimized cases, but it is safer to leave it
if len(writes) == 0 && len(deletes) == 0 {
return false, nil, nil, nil, nil
}
return true, oldKeys, newKeys, writes, deletes
}