tdb/index_array.go

176 lines
4.2 KiB
Go

package tdb
import (
"bytes"
"fmt"
"git.keganmyers.com/terribleplan/tdb/stringy"
bolt "go.etcd.io/bbolt"
)
type ArrayIndexOptions struct {
ConstraintOptions
ElementsNotNull bool
}
type arrayIndex struct {
table *table
bucketName []byte
field dbField
idField dbField
options ArrayIndexOptions
constraints constraints
}
func newArrayIndex(table *table, options ArrayIndexOptions) (*arrayIndex, error) {
field := table.t.NamedField(options.Field)
index := &arrayIndex{
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 := newArrayForeignConstraint(table, options.Foreign, field, index); 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)
}
}
if options.ElementsNotNull {
if c, err := newElementsNotNullConstraint(table, field); err != nil {
return nil, err
} else {
constraints = append(constraints, c)
}
}
index.constraints = constraints
return index, nil
}
func (i *arrayIndex) debugLog(message string) {
i.table.debugLog(message)
}
func (i *arrayIndex) debugLogf(f string, args ...interface{}) {
i.table.debugLogf(f, args...)
}
func (i *arrayIndex) bucket(tx *Tx) *bolt.Bucket {
return tx.tx().Bucket(i.bucketName)
}
func (i *arrayIndex) count(tx *Tx) int {
return i.bucket(tx).Stats().KeyN
}
func (i *arrayIndex) indexedValues(pv dbPtrValue) [][]byte {
vals := pv.dangerous_Field(i.field).Interface().([]uint64)
strs := make([][]byte, len(vals))
for i, val := range vals {
strs[i] = []byte(stringy.LiteralUintToString(val))
}
return strs
}
func (i *arrayIndex) keyValue(pv dbPtrValue) []byte {
return []byte(stringy.ValToStringOrPanic(pv.dangerous_Field(i.idField)))
}
func (i *arrayIndex) indexKeys(pv dbPtrValue) [][]byte {
return indexishKeys(i, pv)
}
func (index *arrayIndex) initialize(tx *Tx) error {
_, err := tx.tx().CreateBucketIfNotExists(index.bucketName)
return err
}
func (i *arrayIndex) put(tx *Tx, newVal dbPtrValue) {
i.debugLogf("[arrayIndex.put] Putting index '%s' for '%s'", i.field.Name, i.table.name)
i.putRaw(tx, i.indexKeys(newVal))
}
func (i *arrayIndex) putRaw(tx *Tx, writes [][]byte) {
indexishPutRaw(i, tx, writes)
}
func (i *arrayIndex) delete(tx *Tx, oldVal dbPtrValue) {
i.debugLogf("[arrayIndex.delete] Deleting index '%s' for '%s'", i.field.Name, i.table.name)
i.deleteRaw(tx, i.indexKeys(oldVal))
}
func (i *arrayIndex) deleteRaw(tx *Tx, deletes [][]byte) {
indexishDeleteRaw(i, tx, deletes)
}
func (i *arrayIndex) update(tx *Tx, oldVal, newVal dbPtrValue) {
i.debugLogf("[arrayIndex.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 *arrayIndex) updateRaw(tx *Tx, writes, deletes [][]byte) {
indexishUpdateRaw(i, tx, writes, deletes)
}
func (i *arrayIndex) shouldUpdate(tx *Tx, oldVal, newVal dbPtrValue) (bool, [][]byte, [][]byte, [][]byte, [][]byte) {
return indexishShouldUpdate(i, oldVal, newVal)
}
func (i *arrayIndex) validate(tx *Tx, val dbPtrValue) error {
return i.constraints.validate(tx, val)
}
func (i *arrayIndex) 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
}