tdb/index_simple.go

176 lines
4.2 KiB
Go

package tdb
import (
"bytes"
"fmt"
"git.keganmyers.com/terribleplan/tdb/stringy"
bolt "go.etcd.io/bbolt"
)
type SimpleIndexOptions struct {
ConstraintOptions
Unique bool
}
type simpleIndex struct {
table *table
bucketName []byte
field dbField
idField dbField
options SimpleIndexOptions
constraints constraints
}
func newSimpleIndex(table *table, options SimpleIndexOptions) (*simpleIndex, error) {
field := table.t.NamedField(options.Field)
index := &simpleIndex{
table: table,
bucketName: []byte(fmt.Sprintf("i@%s.%s", table.name, options.Field)),
field: field,
idField: table.idField,
options: options,
}
constraints := make([]constraintish, 0)
if options.Foreign != "" {
if c, err := newSimpleForeignConstraint(table, options.Foreign, field, index); err != nil {
return nil, err
} else {
constraints = append(constraints, c)
}
}
if options.Unique {
if c, err := newUniqueConstraint(table, index, field); err != nil {
return nil, err
} else {
constraints = append(constraints, c)
}
}
if options.NotNull {
if c, err := newNotNullConstraint(table, field); err != nil {
return nil, err
} else {
constraints = append(constraints, c)
}
}
index.constraints = constraints
return index, nil
}
func (i *simpleIndex) debugLog(message string) {
i.table.debugLog(message)
}
func (i *simpleIndex) debugLogf(f string, args ...interface{}) {
i.table.debugLogf(f, args...)
}
func (i *simpleIndex) bucket(tx *Tx) *bolt.Bucket {
return tx.tx().Bucket(i.bucketName)
}
func (i *simpleIndex) count(tx *Tx) int {
return i.bucket(tx).Stats().KeyN
}
func (i *simpleIndex) indexedValues(pv dbPtrValue) [][]byte {
return [][]byte{[]byte(stringy.ValToStringOrPanic(pv.dangerous_Field(i.field)))}
}
func (i *simpleIndex) keyValue(pv dbPtrValue) []byte {
return []byte(stringy.ValToStringOrPanic(pv.dangerous_Field(i.idField)))
}
func (i *simpleIndex) indexKeys(pv dbPtrValue) [][]byte {
return indexishKeys(i, pv)
}
func (index *simpleIndex) initialize(tx *Tx) error {
_, err := tx.tx().CreateBucketIfNotExists(index.bucketName)
return err
}
// func (i *simpleIndex) getAll(tx *Tx, indexed []byte) ([][]byte, error) {
// b := i.bucket(tx)
// }
func (i *simpleIndex) put(tx *Tx, newVal dbPtrValue) {
i.debugLogf("[simpleIndex.put] Putting index '%s' for '%s'", i.field.Name, i.table.name)
i.putRaw(tx, i.indexKeys(newVal))
}
func (i *simpleIndex) putRaw(tx *Tx, writes [][]byte) {
indexishPutRaw(i, tx, writes)
}
func (i *simpleIndex) delete(tx *Tx, oldVal dbPtrValue) {
i.debugLogf("Deleting index '%s' for '%s'", i.field.Name, i.table.name)
i.deleteRaw(tx, i.indexKeys(oldVal))
}
func (i *simpleIndex) deleteRaw(tx *Tx, deletes [][]byte) {
indexishDeleteRaw(i, tx, deletes)
}
func (i *simpleIndex) update(tx *Tx, oldVal, newVal dbPtrValue) {
i.debugLogf("[simpleIndex.update] Updating index '%s' for '%s'", i.field.Name, i.table.name)
shouldUpdate, _, _, writes, deletes := i.shouldUpdate(tx, oldVal, newVal)
if !shouldUpdate {
return
}
i.updateRaw(tx, writes, deletes)
}
func (i *simpleIndex) updateRaw(tx *Tx, writes, deletes [][]byte) {
indexishUpdateRaw(i, tx, writes, deletes)
}
func (i *simpleIndex) shouldUpdate(tx *Tx, oldVal, newVal dbPtrValue) (bool, [][]byte, [][]byte, [][]byte, [][]byte) {
return indexishShouldUpdate(i, oldVal, newVal)
}
func (i *simpleIndex) validate(tx *Tx, val dbPtrValue) error {
return i.constraints.validate(tx, val)
}
func (i *simpleIndex) iteratePrefixed(tx *Tx, prefix []byte, ki KeyIterator) error {
pb := &bytes.Buffer{}
pb.Write(prefix)
pb.Write(IndexKeySeparator)
i.debugLogf("[index.iteratePrefixed] seeking prefix '%s'", pb.Bytes())
c := i.bucket(tx).Cursor()
for k, _ := c.Seek(pb.Bytes()); k != nil; k, _ = c.Next() {
parts := bytes.Split(k, IndexKeySeparator)
lenParts := len(parts)
if lenParts != 2 {
i.debugLogf("[index.iteratePrefixed] iterating prefix '%s', got %d parts from key '%s'", prefix, lenParts, k)
return fmt.Errorf("[index.iteratePrefixed] Invalid index key for '%s'.'%s': %s", i.table.name, i.field.Name, k)
}
if !bytes.Equal(prefix, parts[0]) {
break
}
signal, err := ki(parts[1])
if err != nil {
return err
}
if signal == StopIteration {
break
}
}
return nil
}