mirror of
https://github.com/mjl-/mox.git
synced 2025-07-10 07:14:40 +03:00
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:
130
vendor/github.com/mjl-/bstore/query.go
generated
vendored
130
vendor/github.com/mjl-/bstore/query.go
generated
vendored
@ -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()
|
||||
|
Reference in New Issue
Block a user