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 }