update to latest bstore (with support for an index on a []string: Message.DKIMDomains), and cyclic data types (to be used for Message.Part soon); also adds a context.Context to database operations.

This commit is contained in:
Mechiel Lukkien
2023-05-22 14:40:36 +02:00
parent f6ed860ccb
commit e81930ba20
58 changed files with 1970 additions and 1035 deletions

View File

@ -1,6 +1,8 @@
package bstore
import (
"context"
"errors"
"fmt"
"reflect"
)
@ -23,12 +25,14 @@ import (
//
// A Query is not safe for concurrent use.
type Query[T any] struct {
st storeType // Of T.
pkType reflect.Type // Shortcut for st.Current.Fields[0].
xtx *Tx // If nil, a new transaction is automatically created from db. Using a tx goes through tx() one exists.
xdb *DB // If not nil, xtx was created to execute the operation and is when the operation finishes (also on error).
err error // If set, returned by operations. For indicating failed filters, or that an operation has finished.
xfilterIDs *filterIDs[T] // Kept separately from filters because these filters make us use the PK without further index planning.
ctx context.Context
ctxDone <-chan struct{} // ctx.Done(), kept here for fast access.
st storeType // Of T.
pkType reflect.Type // Shortcut for st.Current.Fields[0].
xtx *Tx // If nil, a new transaction is automatically created from db. Using a tx goes through tx() one exists.
xdb *DB // If not nil, xtx was created to execute the operation and is when the operation finishes (also on error).
err error // If set, returned by operations. For indicating failed filters, or that an operation has finished.
xfilterIDs *filterIDs[T] // Kept separately from filters because these filters make us use the PK without further index planning.
xfilters []filter[T]
xorders []order
@ -99,6 +103,14 @@ type filterNotIn[T any] struct {
func (filterNotIn[T]) filter() {}
// For matching one of the values in a field that is a slice of the same type.
type filterInSlice[T any] struct {
field field // Of field type, a slice.
rvalue reflect.Value
}
func (filterInSlice[T]) filter() {}
type compareOp byte
const (
@ -158,23 +170,37 @@ func (p *pair[T]) Value(e *exec[T]) (T, error) {
// QueryDB returns a new Query for type T. When an operation on the query is
// executed, a read-only/writable transaction is created as appropriate for the
// operation.
func QueryDB[T any](db *DB) *Query[T] {
func QueryDB[T any](ctx context.Context, db *DB) *Query[T] {
// We lock db for storeTypes. We keep it locked until Query is done.
db.typesMutex.RLock()
q := &Query[T]{xdb: db}
q.init(db)
q.init(ctx, db)
return q
}
// Query returns a new Query that operates on type T using transaction tx.
// QueryTx returns a new Query that operates on type T using transaction tx.
// The context of the transaction is used for the query.
func QueryTx[T any](tx *Tx) *Query[T] {
// note: Since we are in a transaction, we already hold an rlock on the
// db types.
q := &Query[T]{xtx: tx}
q.init(tx.db)
if tx.err != nil {
q.err = tx.err
return q
}
q.init(tx.ctx, tx.db)
return q
}
func (q *Query[T]) ctxErr() error {
select {
case <-q.ctxDone:
return q.ctx.Err()
default:
return nil
}
}
// Stats returns the current statistics for this query. When a query finishes,
// its stats are added to those of its transaction. When a transaction
// finishes, its stats are added to those of its database.
@ -182,7 +208,7 @@ func (q *Query[T]) Stats() Stats {
return q.stats
}
func (q *Query[T]) init(db *DB) {
func (q *Query[T]) init(ctx context.Context, db *DB) {
var v T
t := reflect.TypeOf(v)
if t.Kind() != reflect.Struct {
@ -194,6 +220,11 @@ func (q *Query[T]) init(db *DB) {
q.stats.LastType = q.st.Name
q.pkType = q.st.Current.Fields[0].structField.Type
}
q.ctx = ctx
q.ctxDone = ctx.Done()
if err := q.ctxErr(); q.err == nil && err != nil {
q.err = err
}
}
func (q *Query[T]) tx(write bool) (*Tx, error) {
@ -207,7 +238,7 @@ func (q *Query[T]) tx(write bool) (*Tx, error) {
q.error(err)
return nil, q.err
}
q.xtx = &Tx{db: q.xdb, btx: tx}
q.xtx = &Tx{ctx: q.ctx, db: q.xdb, btx: tx}
if write {
q.stats.Writes++
} else {
@ -308,6 +339,11 @@ func (q *Query[T]) checkErr() bool {
// Probably the result of using a Query zero value.
q.errorf("%w: invalid query, use QueryDB or QueryTx to make a query", ErrParam)
}
if q.err == nil {
if err := q.ctxErr(); err != nil {
q.err = err
}
}
return q.err == nil
}
@ -365,7 +401,10 @@ func (q *Query[T]) foreachKey(write, value bool, fn func(bk []byte, v T) error)
return nil
} else if err != nil {
return err
} else if err := fn(bk, v); err != nil {
} else if err := fn(bk, v); err == StopForEach {
q.error(ErrFinished)
return nil
} else if err != nil {
q.error(err)
return err
}
@ -436,14 +475,14 @@ var convertFieldKinds = map[convertKinds]struct{}{
// Check type of value for field and return a reflect value that can directly be set on the field.
// If the field is a pointer, we allow non-pointers and convert them.
// We require value to be of a type that can be converted without loss of precision to the type of field.
func (q *Query[T]) prepareValue(fname string, ft fieldType, sf reflect.StructField, rv reflect.Value) (reflect.Value, bool) {
func (q *Query[T]) prepareValue(fname string, ft fieldType, st reflect.Type, rv reflect.Value) (reflect.Value, bool) {
if !rv.IsValid() {
q.errorf("%w: invalid value", ErrParam)
return rv, false
}
// Quick check first.
t := rv.Type()
if t == sf.Type {
if t == st {
return rv, true
}
if !ft.Ptr && rv.Kind() == reflect.Ptr {
@ -461,14 +500,14 @@ func (q *Query[T]) prepareValue(fname string, ft fieldType, sf reflect.StructFie
return reflect.Value{}, false
}
if k != ft.Kind {
dt := sf.Type
dt := st
if ft.Ptr {
dt = dt.Elem()
}
rv = rv.Convert(dt)
}
if ft.Ptr && rv.Kind() != reflect.Ptr {
nv := reflect.New(sf.Type.Elem())
nv := reflect.New(st.Elem())
nv.Elem().Set(rv)
rv = nv
}
@ -654,7 +693,7 @@ func (q *Query[T]) filterEqual(fieldName string, values []any, not bool) {
return
}
if len(values) == 1 {
rv, ok := q.prepareValue(ff.Name, ff.Type, ff.structField, reflect.ValueOf(values[0]))
rv, ok := q.prepareValue(ff.Name, ff.Type, ff.structField.Type, reflect.ValueOf(values[0]))
if !ok {
return
}
@ -667,7 +706,7 @@ func (q *Query[T]) filterEqual(fieldName string, values []any, not bool) {
}
rvs := make([]reflect.Value, len(values))
for i, value := range values {
rv, ok := q.prepareValue(ff.Name, ff.Type, ff.structField, reflect.ValueOf(value))
rv, ok := q.prepareValue(ff.Name, ff.Type, ff.structField.Type, reflect.ValueOf(value))
if !ok {
return
}
@ -680,6 +719,42 @@ func (q *Query[T]) filterEqual(fieldName string, values []any, not bool) {
}
}
// FilterIn selects records that have one of values of the string slice fieldName.
//
// If fieldName has an index, it is used to select rows.
//
// Note: Value must be a compatible type for comparison with the elements of
// fieldName. Go constant numbers become ints, which are not compatible with uint
// or float types.
func (q *Query[T]) FilterIn(fieldName string, value any) *Query[T] {
if !q.checkErr() {
return q
}
ff, ok := q.lookupField(fieldName)
if !ok {
return q
}
if ff.Type.Ptr {
q.errorf("%w: cannot compare pointer values", ErrParam)
return q
}
if ff.Type.Kind != kindSlice {
q.errorf("%w: field for FilterIn must be a slice", ErrParam)
return q
}
et := ff.Type.ListElem
if et.Ptr {
q.errorf("%w: cannot compare element pointer values", ErrParam)
return q
}
rv, ok := q.prepareValue(ff.Name, *et, ff.structField.Type.Elem(), reflect.ValueOf(value))
if !ok {
return q
}
q.addFilter(filterInSlice[T]{ff, rv})
return q
}
// FilterGreater selects records that have fieldName > value.
//
// Note: Value must be a compatible type for comparison with fieldName. Go
@ -716,7 +791,7 @@ func (q *Query[T]) filterCompare(fieldName string, op compareOp, value reflect.V
q.errorf("%w: cannot compare %s", ErrParam, ff.Type.Kind)
return q
}
rv, ok := q.prepareValue(ff.Name, ff.Type, ff.structField, value)
rv, ok := q.prepareValue(ff.Name, ff.Type, ff.structField.Type, value)
if !ok {
return q
}
@ -831,7 +906,8 @@ func (q *Query[T]) gather(v T, rv reflect.Value) {
}
}
// Err returns if an error is set on the query. Can happen for invalid filters.
// Err returns if an error is set on the query. Can happen for invalid filters or
// canceled contexts.
// Finished queries return ErrFinished.
func (q *Query[T]) Err() error {
q.checkErr()
@ -979,7 +1055,7 @@ next:
if i == 0 {
return 0, fmt.Errorf("%w: cannot update primary key", ErrParam)
}
rv, ok := q.prepareValue(f.Name, f.Type, f.structField, reflect.ValueOf(value))
rv, ok := q.prepareValue(f.Name, f.Type, f.structField.Type, reflect.ValueOf(value))
if !ok {
return 0, q.err
}
@ -991,7 +1067,7 @@ next:
if ef.Name != name {
continue
}
rv, ok := q.prepareValue(ef.Name, ef.Type, ef.structField, reflect.ValueOf(value))
rv, ok := q.prepareValue(ef.Name, ef.Type, ef.structField.Type, reflect.ValueOf(value))
if !ok {
return 0, q.err
}
@ -1051,6 +1127,8 @@ func (q *Query[T]) IDs(idsptr any) (rerr error) {
return nil
}
// todo: should we have an iteration object that we can call Next and NextID on?
// Next fetches the next record, moving the cursor forward.
//
// ErrAbsent is returned if no more records match.
@ -1116,7 +1194,13 @@ func (q *Query[T]) Exists() (exists bool, rerr error) {
return err == nil, err
}
// StopForEach is an error value that, if returned by the function passed to
// Query.ForEach, stops further iterations.
var StopForEach error = errors.New("stop foreach")
// ForEach calls fn on each selected record.
// If fn returns StopForEach, ForEach stops iterating, so no longer calls fn,
// and returns nil.
func (q *Query[T]) ForEach(fn func(value T) error) (rerr error) {
defer q.finish(&rerr)
q.checkNotNext()