176 lines
4.2 KiB
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
|
||
|
}
|