package tdb import ( "bytes" "errors" "fmt" // "sort" "git.keganmyers.com/terribleplan/tdb/stringy" bolt "go.etcd.io/bbolt" ) type queryOpCreator func(*table, string, interface{}) (queryOpish, error) var queryOps map[string]queryOpCreator = map[string]queryOpCreator{ "=": createEqualQueryOp, // "in": createInQueryOp, } func createQueryOp(table *table, field, opType string, value interface{}) (queryOpish, error) { create, ok := queryOps[opType] if !ok { return nil, fmt.Errorf("Unknown query operation '%s'", opType) } op, err := create(table, field, value) if err != nil { table.debugLogf("Failed creating query for '%s' on table '%s'", opType, table.name) return nil, err } err = op.valid() if err != nil { table.debugLogf("Unable to form valid query for '%s' on table '%s'", opType, table.name) return nil, err } return op, nil } type queryOpish interface { rawIterable Iterable match(v dbPtrValue) bool indexed() bool valid() error String() string } type queryOp struct { table *table field dbField value []byte index indexish } func createCommonQueryOp(table *table, field string, value interface{}) (*queryOp, error) { index, ok := table.indicies[field] if ok { table.debugLogf("[query] found index for field '%s'", field) } else { table.debugLogf("[query] did not find index for field '%s'", field) if len(table.indicies) == 0 { table.debugLog("[query] (0 indicies found)") } else { for name, _ := range table.indicies { table.debugLogf("[query] '%s' is indexed", name) } } } q := &queryOp{ table: table, field: table.t.NamedField(field), value: []byte(stringy.ToStringOrPanic(value)), index: index, } return q, nil } // re-used for queryOpEqual func (op *queryOp) indexed() bool { return op.index != nil } type queryOpEqual queryOp func createEqualQueryOp(table *table, field string, value interface{}) (queryOpish, error) { qo, err := createCommonQueryOp(table, field, value) if err != nil { return nil, err } qoe := queryOpEqual(*qo) return &qoe, nil } func (op *queryOpEqual) String() string { return fmt.Sprintf("%s = %s", op.field.Name, op.value) } func (op *queryOpEqual) indexed() bool { indexed := op.index != nil op.table.debugLogf("[query] Table '%s'.'%s' indexed() -> %t", op.table.name, op.field.Name, indexed) return indexed } func (op *queryOpEqual) valid() error { return nil } func (op *queryOpEqual) match(pv dbPtrValue) bool { return bytes.Equal([]byte(stringy.ValToStringOrPanic(pv.dangerous_Field(op.field))), op.value) } func (op *queryOpEqual) rawIterateKeys(i keyIteratorWithBucket, txs ...*Tx) error { if op.index == nil { return errors.New("This method is only applicable if the op is indexed") } return op.table.db.readTxHelper(func(tx *Tx) error { db := op.table.bucket(tx) return op.index.iteratePrefixed(tx, op.value, func(key []byte) (IterationSignal, error) { return i(key, db) }) }, txs...) } func (op *queryOpEqual) IterateKeys(i KeyIterator, txs ...*Tx) error { return op.rawIterateKeys(func(k []byte, _ *bolt.Bucket) (IterationSignal, error) { return i(k) }, txs...) } func (op *queryOpEqual) Iterate(i Iterator, txs ...*Tx) error { return op.rawIterateKeys(func(k []byte, b *bolt.Bucket) (IterationSignal, error) { v, err := op.table.getWithinTx(b, k) if err != nil { op.table.debugLogf("[query] Encountered error while iterating (%s)", err) return StopIteration, err } return i(v) }, txs...) } func (op *queryOpEqual) iterateRaw(i rawIterator, txs ...*Tx) error { return op.rawIterateKeys(func(k []byte, b *bolt.Bucket) (IterationSignal, error) { v, err := op.table.getValWithinTx(b, k) if err != nil { op.table.debugLogf("[query] Encountered error while iterating (%s)", err) return StopIteration, err } return i(v) }, txs...) }