mirror of
https://github.com/mjl-/mox.git
synced 2025-07-14 08:14:37 +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:
356
vendor/github.com/mjl-/bstore/register.go
generated
vendored
356
vendor/github.com/mjl-/bstore/register.go
generated
vendored
@ -2,9 +2,12 @@ package bstore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
@ -14,10 +17,22 @@ import (
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
// todo: implement changing PK type, eg to wider int. requires rewriting all values, and removing old typeVersions.
|
||||
// todo: allow schema change between []byte and string?
|
||||
// todo: allow more schema changes, eg int to string, bool to int or string, int to bool, perhaps even string to int/bool. and between structs and maps. would require rewriting the records.
|
||||
|
||||
const (
|
||||
// First version.
|
||||
ondiskVersion1 = 1
|
||||
|
||||
// With support for cyclic types, adding typeField.FieldsTypeSeq to
|
||||
// define/reference types. Only used when a type has a field that references another
|
||||
// struct type.
|
||||
ondiskVersion2 = 2
|
||||
)
|
||||
|
||||
var errSchemaCheck = errors.New("schema check")
|
||||
|
||||
// Register registers the Go types of each value in typeValues for use with the
|
||||
// database. Each value must be a struct, not a pointer.
|
||||
//
|
||||
@ -30,7 +45,11 @@ const (
|
||||
//
|
||||
// Register can be called multiple times, with different types. But types that
|
||||
// reference each other must be registered in the same call to Registers.
|
||||
func (db *DB) Register(typeValues ...any) error {
|
||||
//
|
||||
// To help during development, if environment variable "bstore_schema_check" is set
|
||||
// to "changed", an error is returned if there is no schema change. If it is set to
|
||||
// "unchanged", an error is returned if there was a schema change.
|
||||
func (db *DB) Register(ctx context.Context, typeValues ...any) error {
|
||||
// We will drop/create new indices as needed. For changed indices, we drop
|
||||
// and recreate. E.g. if an index becomes a unique index, or if a field in
|
||||
// an index changes. These values map type and index name to their index.
|
||||
@ -41,7 +60,10 @@ func (db *DB) Register(typeValues ...any) error {
|
||||
ntypeversions := map[string]*typeVersion{} // New typeversions, through new types or updated versions of existing types.
|
||||
registered := map[string]*storeType{} // Registered in this call.
|
||||
|
||||
return db.Write(func(tx *Tx) error {
|
||||
checkSchemaChanged := os.Getenv("bstore_schema_check") == "changed"
|
||||
checkSchemaUnchanged := os.Getenv("bstore_schema_check") == "unchanged"
|
||||
|
||||
return db.Write(ctx, func(tx *Tx) error {
|
||||
for _, t := range typeValues {
|
||||
rt := reflect.TypeOf(t)
|
||||
if rt.Kind() != reflect.Struct {
|
||||
@ -118,6 +140,11 @@ func (db *DB) Register(typeValues ...any) error {
|
||||
|
||||
// Decide if we need to add a new typeVersion to the database. I.e. a new type schema.
|
||||
if st.Current == nil || !st.Current.typeEqual(*tv) {
|
||||
if checkSchemaUnchanged {
|
||||
return fmt.Errorf("%w: schema changed but bstore_schema_check=unchanged is set (type %v)", errSchemaCheck, st.Name)
|
||||
}
|
||||
checkSchemaChanged = false // After registering types, we check that it is false.
|
||||
|
||||
tv.Version = 1
|
||||
if st.Current != nil {
|
||||
tv.Version = st.Current.Version + 1
|
||||
@ -127,6 +154,13 @@ func (db *DB) Register(typeValues ...any) error {
|
||||
return fmt.Errorf("internal error: packing schema for type %q", tv.name)
|
||||
}
|
||||
|
||||
// Sanity check: parse the typeVersion again, and check that we think it is equal to the typeVersion just written.
|
||||
if xtv, err := parseSchema(k, v); err != nil {
|
||||
return fmt.Errorf("%w: parsing generated typeVersion: %v", ErrStore, err)
|
||||
} else if !xtv.typeEqual(*tv) {
|
||||
return fmt.Errorf("%w: generated typeVersion not equal to itself after pack and parse", ErrStore)
|
||||
}
|
||||
|
||||
// note: we don't track types bucket operations in stats.
|
||||
if err := tb.Put(k, v); err != nil {
|
||||
return fmt.Errorf("storing new schema: %w", err)
|
||||
@ -202,6 +236,10 @@ func (db *DB) Register(typeValues ...any) error {
|
||||
registered[st.Name] = &st
|
||||
}
|
||||
|
||||
if checkSchemaChanged {
|
||||
return fmt.Errorf("%w: schema did not change while bstore_schema_check=changed is set", errSchemaCheck)
|
||||
}
|
||||
|
||||
// Check that referenced types exist, and make links in the referenced types.
|
||||
for _, st := range registered {
|
||||
tv := st.Current
|
||||
@ -237,7 +275,7 @@ func (db *DB) Register(typeValues ...any) error {
|
||||
// We cannot just recalculate the ReferencedBy, because the whole point is to
|
||||
// detect types that are missing in this Register.
|
||||
updateReferencedBy := map[string]struct{}{}
|
||||
for _, ntv := range ntypeversions {
|
||||
for ntname, ntv := range ntypeversions {
|
||||
otv := otypeversions[ntv.name] // Can be nil, on first register.
|
||||
|
||||
// Look for references that were added.
|
||||
@ -251,6 +289,66 @@ func (db *DB) Register(typeValues ...any) error {
|
||||
if _, ok := registered[name].Current.ReferencedBy[ntv.name]; ok {
|
||||
return fmt.Errorf("%w: type %q introduces reference to %q but is already marked as ReferencedBy in that type", ErrStore, ntv.name, name)
|
||||
}
|
||||
|
||||
// Verify that the new reference does not violate the foreign key constraint.
|
||||
var foundField bool
|
||||
for _, f := range ntv.Fields {
|
||||
for _, rname := range f.References {
|
||||
if rname != name {
|
||||
continue
|
||||
}
|
||||
|
||||
foundField = true
|
||||
|
||||
// For newly added references, check they are valid.
|
||||
b, err := tx.recordsBucket(ntname, ntv.fillPercent)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: bucket for type %s with field with new reference: %v", ErrStore, ntname, err)
|
||||
}
|
||||
|
||||
rb, err := tx.recordsBucket(name, registered[name].Current.fillPercent)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: bucket for referenced type %s: %v", ErrStore, name, err)
|
||||
}
|
||||
|
||||
nst := registered[ntname]
|
||||
rv := reflect.New(nst.Type).Elem()
|
||||
ctxDone := ctx.Done()
|
||||
err = b.ForEach(func(bk, bv []byte) error {
|
||||
tx.stats.Records.Cursor++
|
||||
|
||||
select {
|
||||
case <-ctxDone:
|
||||
return tx.ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
if err := nst.parse(rv, bv); err != nil {
|
||||
return fmt.Errorf("parsing record for %s: %w", ntname, err)
|
||||
}
|
||||
frv := rv.FieldByIndex(f.structField.Index)
|
||||
if frv.IsZero() {
|
||||
return nil
|
||||
}
|
||||
rpk, err := packPK(frv)
|
||||
if err != nil {
|
||||
return fmt.Errorf("packing pk for referenced type %s: %w", name, err)
|
||||
}
|
||||
tx.stats.Records.Cursor++
|
||||
if rb.Get(rpk) == nil {
|
||||
return fmt.Errorf("%w: value %v not in %s", ErrReference, frv.Interface(), name)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: ensuring referential integrity for newly added reference of %s.%s", err, ntname, f.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !foundField {
|
||||
return fmt.Errorf("%w: could not find field causing newly referenced type %s in type %s", ErrStore, name, ntname)
|
||||
}
|
||||
|
||||
// note: we are updating the previous tv's ReferencedBy, not tidy but it is safe.
|
||||
registered[name].Current.ReferencedBy[ntv.name] = struct{}{}
|
||||
updateReferencedBy[name] = struct{}{}
|
||||
@ -271,8 +369,10 @@ func (db *DB) Register(typeValues ...any) error {
|
||||
if _, ok := ntv.references[name]; ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := registered[name].Current.ReferencedBy[ntv.name]; !ok {
|
||||
return fmt.Errorf("%w: previously referenced type %q not present in %q", ErrStore, ntv.name, name)
|
||||
if rtv, ok := registered[name]; !ok {
|
||||
return fmt.Errorf("%w: type %q formerly referenced by %q not yet registered", ErrStore, name, ntv.name)
|
||||
} else if _, ok := rtv.Current.ReferencedBy[ntv.name]; !ok {
|
||||
return fmt.Errorf("%w: formerly referenced type %q missing from %q", ErrStore, name, ntv.name)
|
||||
}
|
||||
// note: we are updating the previous tv's ReferencedBy, not tidy but it is safe.
|
||||
delete(registered[name].Current.ReferencedBy, ntv.name)
|
||||
@ -416,20 +516,29 @@ func (db *DB) Register(typeValues ...any) error {
|
||||
}
|
||||
ibkeys := make([][]key, len(idxs))
|
||||
|
||||
ctxDone := ctx.Done()
|
||||
err = rb.ForEach(func(bk, bv []byte) error {
|
||||
tx.stats.Records.Cursor++
|
||||
|
||||
select {
|
||||
case <-ctxDone:
|
||||
return tx.ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
rv := reflect.New(st.Type).Elem()
|
||||
if err := st.parse(rv, bv); err != nil {
|
||||
return fmt.Errorf("parsing record for index for %s: %w", name, err)
|
||||
}
|
||||
|
||||
for i, idx := range idxs {
|
||||
prek, ik, err := idx.packKey(rv, bk)
|
||||
ikl, err := idx.packKey(rv, bk)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating key for %s.%s: %w", name, idx.Name, err)
|
||||
}
|
||||
ibkeys[i] = append(ibkeys[i], key{ik, uint16(len(prek))})
|
||||
for _, ik := range ikl {
|
||||
ibkeys[i] = append(ibkeys[i], key{ik.full, uint16(len(ik.pre))})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@ -447,14 +556,14 @@ func (db *DB) Register(typeValues ...any) error {
|
||||
prev := keys[i-1]
|
||||
if bytes.Equal(prev.buf[:prev.pre], k.buf[:k.pre]) {
|
||||
// Do quite a bit of work to make a helpful error message.
|
||||
a := reflect.New(reflect.TypeOf(idx.tv.Fields[0].Type.zero(nil))).Elem()
|
||||
b := reflect.New(reflect.TypeOf(idx.tv.Fields[0].Type.zero(nil))).Elem()
|
||||
a := reflect.New(reflect.TypeOf(idx.tv.Fields[0].Type.zeroKey())).Elem()
|
||||
b := reflect.New(reflect.TypeOf(idx.tv.Fields[0].Type.zeroKey())).Elem()
|
||||
parsePK(a, prev.buf[prev.pre:]) // Ignore error, nothing to do.
|
||||
parsePK(b, k.buf[k.pre:]) // Ignore error, nothing to do.
|
||||
var dup []any
|
||||
_, values, _ := idx.parseKey(k.buf, true)
|
||||
for i := range values {
|
||||
x := reflect.New(reflect.TypeOf(idx.Fields[i].Type.zero(nil))).Elem()
|
||||
x := reflect.New(reflect.TypeOf(idx.Fields[i].Type.zeroKey())).Elem()
|
||||
parsePK(x, values[i]) // Ignore error, nothing to do.
|
||||
dup = append(dup, x.Interface())
|
||||
}
|
||||
@ -502,8 +611,10 @@ func parseSchema(bk, bv []byte) (*typeVersion, error) {
|
||||
if tv.Version != version {
|
||||
return nil, fmt.Errorf("%w: version in schema %d does not match key %d", ErrStore, tv.Version, version)
|
||||
}
|
||||
if tv.OndiskVersion != ondiskVersion1 {
|
||||
return nil, fmt.Errorf("internal error: OndiskVersion %d not supported", tv.OndiskVersion)
|
||||
switch tv.OndiskVersion {
|
||||
case ondiskVersion1, ondiskVersion2:
|
||||
default:
|
||||
return nil, fmt.Errorf("internal error: OndiskVersion %d not recognized/supported", tv.OndiskVersion)
|
||||
}
|
||||
|
||||
// Fill references, used for comparing/checking schema updates.
|
||||
@ -514,12 +625,72 @@ func parseSchema(bk, bv []byte) (*typeVersion, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve fieldType.structFields, for referencing defined types, used for
|
||||
// supporting cyclic types. The type itself always implicitly has sequence 1.
|
||||
seqFields := map[int][]field{1: tv.Fields}
|
||||
origOndiskVersion := tv.OndiskVersion
|
||||
for i := range tv.Fields {
|
||||
if err := tv.resolveStructFields(seqFields, &tv.Fields[i].Type); err != nil {
|
||||
return nil, fmt.Errorf("%w: resolving struct fields for referencing types: %v", ErrStore, err)
|
||||
}
|
||||
}
|
||||
if tv.OndiskVersion != origOndiskVersion {
|
||||
return nil, fmt.Errorf("%w: resolving cyclic types changed ondisk version from %d to %d", ErrStore, origOndiskVersion, tv.OndiskVersion)
|
||||
}
|
||||
|
||||
return &tv, nil
|
||||
}
|
||||
|
||||
// Resolve structFields in ft (and recursively), either by setting it to Fields
|
||||
// (common), or by setting it to the fields of a referenced type in case of a
|
||||
// cyclic data type.
|
||||
func (tv *typeVersion) resolveStructFields(seqFields map[int][]field, ft *fieldType) error {
|
||||
if ft.Kind == kindStruct {
|
||||
if ft.FieldsTypeSeq < 0 {
|
||||
var ok bool
|
||||
ft.structFields, ok = seqFields[-ft.FieldsTypeSeq]
|
||||
if !ok {
|
||||
return fmt.Errorf("reference to undefined FieldsTypeSeq %d (n %d)", -ft.FieldsTypeSeq, len(seqFields))
|
||||
}
|
||||
if len(ft.DefinitionFields) != 0 {
|
||||
return fmt.Errorf("reference to FieldsTypeSeq while also defining fields")
|
||||
}
|
||||
} else if ft.FieldsTypeSeq > 0 {
|
||||
if _, ok := seqFields[ft.FieldsTypeSeq]; ok {
|
||||
return fmt.Errorf("duplicate definition of FieldsTypeSeq %d (n %d)", ft.FieldsTypeSeq, len(seqFields))
|
||||
}
|
||||
seqFields[ft.FieldsTypeSeq] = ft.DefinitionFields
|
||||
ft.structFields = ft.DefinitionFields
|
||||
}
|
||||
// note: ondiskVersion1 does not have/use this field, so it defaults to 0.
|
||||
if ft.FieldsTypeSeq == 0 {
|
||||
ft.structFields = ft.DefinitionFields
|
||||
}
|
||||
|
||||
for i := range ft.DefinitionFields {
|
||||
if err := tv.resolveStructFields(seqFields, &ft.DefinitionFields[i].Type); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xftl := []*fieldType{ft.MapKey, ft.MapValue, ft.ListElem}
|
||||
for _, xft := range xftl {
|
||||
if xft == nil {
|
||||
continue
|
||||
}
|
||||
if err := tv.resolveStructFields(seqFields, xft); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// packSchema returns a key and value to store in the types bucket.
|
||||
func packSchema(tv *typeVersion) ([]byte, []byte, error) {
|
||||
if tv.OndiskVersion != ondiskVersion1 {
|
||||
switch tv.OndiskVersion {
|
||||
case ondiskVersion1, ondiskVersion2:
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("internal error: invalid OndiskVersion %d", tv.OndiskVersion)
|
||||
}
|
||||
v, err := json.Marshal(tv)
|
||||
@ -540,12 +711,17 @@ func gatherTypeVersion(t reflect.Type) (*typeVersion, error) {
|
||||
}
|
||||
tv := &typeVersion{
|
||||
Version: 0, // Set by caller.
|
||||
OndiskVersion: ondiskVersion1, // Current on-disk format.
|
||||
OndiskVersion: ondiskVersion2, // When opening a database with ondiskVersion1, we add a new typeVersion.
|
||||
ReferencedBy: map[string]struct{}{},
|
||||
name: tname,
|
||||
fillPercent: 0.5,
|
||||
}
|
||||
tv.Fields, tv.embedFields, err = gatherTypeFields(t, true, true, false)
|
||||
|
||||
// The type being parsed implicitly has sequence 1. Next struct types will be
|
||||
// assigned the next value (based on length of typeseqs). FieldTypes referencing
|
||||
// another type are resolved below, after all fields have been gathered.
|
||||
typeSeqs := map[reflect.Type]int{t: 1}
|
||||
tv.Fields, tv.embedFields, err = gatherTypeFields(typeSeqs, t, true, true, false, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -562,6 +738,15 @@ func gatherTypeVersion(t reflect.Type) (*typeVersion, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve structFields for the typeFields that reference an earlier defined type,
|
||||
// using the same function as used when loading a type from disk.
|
||||
seqFields := map[int][]field{1: tv.Fields}
|
||||
for i := range tv.Fields {
|
||||
if err := tv.resolveStructFields(seqFields, &tv.Fields[i].Type); err != nil {
|
||||
return nil, fmt.Errorf("%w: resolving struct fields for referencing types: %v", ErrStore, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Find indices.
|
||||
tv.Indices = map[string]*index{}
|
||||
|
||||
@ -572,6 +757,7 @@ func gatherTypeVersion(t reflect.Type) (*typeVersion, error) {
|
||||
}
|
||||
idx = &index{unique, iname, nil, tv}
|
||||
tv.Indices[iname] = idx
|
||||
nslice := 0
|
||||
for _, f := range fields {
|
||||
// todo: can we have a unique index on bytes? seems like this should be possible to have max 1 []byte in an index key, only to be used for unique get plans.
|
||||
if f.Type.Ptr {
|
||||
@ -579,6 +765,14 @@ func gatherTypeVersion(t reflect.Type) (*typeVersion, error) {
|
||||
}
|
||||
switch f.Type.Kind {
|
||||
case kindBool, kindInt8, kindInt16, kindInt32, kindInt64, kindInt, kindUint8, kindUint16, kindUint32, kindUint64, kindUint, kindString, kindTime:
|
||||
case kindSlice:
|
||||
nslice++
|
||||
if nslice > 1 {
|
||||
return fmt.Errorf("%w: can only have one slice field in index, for field %q", ErrType, f.Name)
|
||||
}
|
||||
if unique {
|
||||
return fmt.Errorf("%w: can only use slice field %v in field %q as index without unique", ErrType, f.Type.Kind, f.Name)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("%w: cannot use type %v in field %q as index/unique", ErrType, f.Type.Kind, f.Name)
|
||||
}
|
||||
@ -692,7 +886,7 @@ func gatherTypeVersion(t reflect.Type) (*typeVersion, error) {
|
||||
// field must not be ignored and be a valid primary key field (eg no pointer).
|
||||
// topLevel must be true only for the top-level struct fields, not for fields of
|
||||
// deeper levels. Deeper levels cannot have index/unique constraints.
|
||||
func gatherTypeFields(t reflect.Type, needFirst, topLevel, inMap bool) ([]field, []embed, error) {
|
||||
func gatherTypeFields(typeSeqs map[reflect.Type]int, t reflect.Type, needFirst, topLevel, inMap, newSeq bool) ([]field, []embed, error) {
|
||||
var fields []field
|
||||
var embedFields []embed
|
||||
|
||||
@ -744,7 +938,7 @@ func gatherTypeFields(t reflect.Type, needFirst, topLevel, inMap bool) ([]field,
|
||||
}
|
||||
names[name] = struct{}{}
|
||||
|
||||
ft, err := gatherFieldType(sf.Type, inMap)
|
||||
ft, err := gatherFieldType(typeSeqs, sf.Type, inMap, newSeq && !sf.Anonymous)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("field %q: %w", sf.Name, err)
|
||||
}
|
||||
@ -817,11 +1011,13 @@ func gatherTypeFields(t reflect.Type, needFirst, topLevel, inMap bool) ([]field,
|
||||
}
|
||||
}
|
||||
|
||||
if sf.Anonymous {
|
||||
// We don't store anonymous/embed fields, unless it is a cyclic type, because then
|
||||
// we wouldn't have included any of its type's fields.
|
||||
if sf.Anonymous && ft.FieldsTypeSeq == 0 {
|
||||
e := embed{name, ft, sf}
|
||||
embedFields = append(embedFields, e)
|
||||
} else {
|
||||
f := field{name, ft, nonzero, tags.List("ref"), defstr, def, sf, nil}
|
||||
f := field{name, ft, nonzero, tags.List("ref"), defstr, def, sf, false, nil}
|
||||
fields = append(fields, f)
|
||||
}
|
||||
}
|
||||
@ -842,12 +1038,13 @@ func checkKeyType(t reflect.Type) error {
|
||||
return fmt.Errorf("%w: type %v not valid for primary key", ErrType, t)
|
||||
}
|
||||
|
||||
func gatherFieldType(t reflect.Type, inMap bool) (fieldType, error) {
|
||||
func gatherFieldType(typeSeqs map[reflect.Type]int, t reflect.Type, inMap, newSeq bool) (fieldType, error) {
|
||||
ft := fieldType{}
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
ft.Ptr = true
|
||||
}
|
||||
|
||||
k, err := typeKind(t)
|
||||
if err != nil {
|
||||
return fieldType{}, err
|
||||
@ -855,32 +1052,52 @@ func gatherFieldType(t reflect.Type, inMap bool) (fieldType, error) {
|
||||
ft.Kind = k
|
||||
switch ft.Kind {
|
||||
case kindSlice:
|
||||
l, err := gatherFieldType(t.Elem(), inMap)
|
||||
l, err := gatherFieldType(typeSeqs, t.Elem(), inMap, newSeq)
|
||||
if err != nil {
|
||||
return ft, fmt.Errorf("list: %w", err)
|
||||
return ft, fmt.Errorf("slice: %w", err)
|
||||
}
|
||||
ft.List = &l
|
||||
ft.ListElem = &l
|
||||
case kindArray:
|
||||
l, err := gatherFieldType(typeSeqs, t.Elem(), inMap, newSeq)
|
||||
if err != nil {
|
||||
return ft, fmt.Errorf("array: %w", err)
|
||||
}
|
||||
ft.ListElem = &l
|
||||
ft.ArrayLength = t.Len()
|
||||
case kindMap:
|
||||
kft, err := gatherFieldType(t.Key(), true)
|
||||
kft, err := gatherFieldType(typeSeqs, t.Key(), true, newSeq)
|
||||
if err != nil {
|
||||
return ft, fmt.Errorf("map key: %w", err)
|
||||
}
|
||||
if kft.Ptr {
|
||||
return ft, fmt.Errorf("%w: map key with pointer type not supported", ErrType)
|
||||
}
|
||||
vft, err := gatherFieldType(t.Elem(), true)
|
||||
vft, err := gatherFieldType(typeSeqs, t.Elem(), true, newSeq)
|
||||
if err != nil {
|
||||
return ft, fmt.Errorf("map value: %w", err)
|
||||
}
|
||||
ft.MapKey = &kft
|
||||
ft.MapValue = &vft
|
||||
case kindStruct:
|
||||
// note: we have no reason to gather embed field beyond top-level
|
||||
fields, _, err := gatherTypeFields(t, false, false, inMap)
|
||||
// If this is a known type, track a reference to the earlier defined type. Once the
|
||||
// type with all Fields is fully parsed, the references will be resolved.
|
||||
if seq, ok := typeSeqs[t]; ok {
|
||||
ft.FieldsTypeSeq = -seq
|
||||
return ft, nil
|
||||
}
|
||||
|
||||
// If we are processing an anonymous (embed) field, we don't assign a new seq,
|
||||
// because we won't be walking it when resolving again.
|
||||
seq := len(typeSeqs) + 1
|
||||
if newSeq {
|
||||
typeSeqs[t] = seq
|
||||
ft.FieldsTypeSeq = seq
|
||||
}
|
||||
fields, _, err := gatherTypeFields(typeSeqs, t, false, false, inMap, newSeq)
|
||||
if err != nil {
|
||||
return fieldType{}, fmt.Errorf("struct: %w", err)
|
||||
}
|
||||
ft.Fields = fields
|
||||
ft.DefinitionFields = fields
|
||||
}
|
||||
return ft, nil
|
||||
}
|
||||
@ -941,6 +1158,10 @@ tv:
|
||||
}
|
||||
|
||||
func (f *field) prepare(nfields []field, later, mvlater [][]field) {
|
||||
if f.prepared {
|
||||
return
|
||||
}
|
||||
f.prepared = true
|
||||
for _, nf := range nfields {
|
||||
if nf.Name == f.Name {
|
||||
f.structField = nf.structField
|
||||
@ -954,26 +1175,26 @@ func (ft fieldType) laterFields() (later, mvlater []field) {
|
||||
later, _ = ft.MapKey.laterFields()
|
||||
mvlater, _ = ft.MapValue.laterFields()
|
||||
return later, mvlater
|
||||
} else if ft.List != nil {
|
||||
return ft.List.laterFields()
|
||||
} else if ft.ListElem != nil {
|
||||
return ft.ListElem.laterFields()
|
||||
}
|
||||
return ft.Fields, nil
|
||||
return ft.structFields, nil
|
||||
}
|
||||
|
||||
func (ft fieldType) prepare(nft *fieldType, later, mvlater [][]field) {
|
||||
for i, f := range ft.Fields {
|
||||
for i, f := range ft.structFields {
|
||||
nlater, nmvlater, skip := lookupLater(f.Name, later)
|
||||
if skip {
|
||||
continue
|
||||
}
|
||||
ft.Fields[i].prepare(nft.Fields, nlater, nmvlater)
|
||||
ft.structFields[i].prepare(nft.structFields, nlater, nmvlater)
|
||||
}
|
||||
if ft.MapKey != nil {
|
||||
ft.MapKey.prepare(nft.MapKey, later, nil)
|
||||
ft.MapValue.prepare(nft.MapValue, mvlater, nil)
|
||||
}
|
||||
if ft.List != nil {
|
||||
ft.List.prepare(nft.List, later, mvlater)
|
||||
if ft.ListElem != nil {
|
||||
ft.ListElem.prepare(nft.ListElem, later, mvlater)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1032,18 +1253,24 @@ func (ft fieldType) typeEqual(nft fieldType) bool {
|
||||
if ft.Ptr != nft.Ptr || ft.Kind != nft.Kind {
|
||||
return false
|
||||
}
|
||||
if len(ft.Fields) != len(nft.Fields) {
|
||||
if ft.FieldsTypeSeq != nft.FieldsTypeSeq {
|
||||
return false
|
||||
}
|
||||
for i, f := range ft.Fields {
|
||||
if !f.typeEqual(nft.Fields[i]) {
|
||||
if len(ft.DefinitionFields) != len(nft.DefinitionFields) {
|
||||
return false
|
||||
}
|
||||
for i, f := range ft.DefinitionFields {
|
||||
if !f.typeEqual(nft.DefinitionFields[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if ft.MapKey != nil && (!ft.MapKey.typeEqual(*nft.MapKey) || !ft.MapValue.typeEqual(*nft.MapValue)) {
|
||||
return false
|
||||
}
|
||||
if ft.List != nil && !ft.List.typeEqual(*nft.List) {
|
||||
if ft.ListElem != nil && !ft.ListElem.typeEqual(*nft.ListElem) {
|
||||
return false
|
||||
}
|
||||
if ft.ArrayLength != nft.ArrayLength {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@ -1069,12 +1296,16 @@ func (idx *index) typeEqual(nidx *index) bool {
|
||||
// into an int32. Indices that need to be recreated (for an int width change) are
|
||||
// recorded in recreateIndices.
|
||||
func (tx *Tx) checkTypes(otv, ntv *typeVersion, recreateIndices map[string]struct{}) error {
|
||||
// Used to track that two nonzero FieldsTypeSeq have been checked, to prevent
|
||||
// recursing while checking.
|
||||
checked := map[[2]int]struct{}{}
|
||||
|
||||
for _, f := range ntv.Fields {
|
||||
for _, of := range otv.Fields {
|
||||
if f.Name != of.Name {
|
||||
continue
|
||||
}
|
||||
increase, err := of.Type.compatible(f.Type)
|
||||
increase, err := of.Type.compatible(f.Type, checked)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: field %q: %s", ErrIncompatible, f.Name, err)
|
||||
}
|
||||
@ -1099,7 +1330,7 @@ func (tx *Tx) checkTypes(otv, ntv *typeVersion, recreateIndices map[string]struc
|
||||
// for maps/slices/structs). If not an error is returned. If they are, the first
|
||||
// return value indicates if this is a field that needs it index recreated
|
||||
// (currently for ints that are packed with fixed width encoding).
|
||||
func (ft fieldType) compatible(nft fieldType) (bool, error) {
|
||||
func (ft fieldType) compatible(nft fieldType, checked map[[2]int]struct{}) (bool, error) {
|
||||
need := func(incr bool, l ...kind) (bool, error) {
|
||||
for _, k := range l {
|
||||
if nft.Kind == k {
|
||||
@ -1160,10 +1391,10 @@ func (ft fieldType) compatible(nft fieldType) (bool, error) {
|
||||
if nk != k {
|
||||
return false, fmt.Errorf("map to %v: %w", nk, ErrIncompatible)
|
||||
}
|
||||
if _, err := ft.MapKey.compatible(*nft.MapKey); err != nil {
|
||||
if _, err := ft.MapKey.compatible(*nft.MapKey, checked); err != nil {
|
||||
return false, fmt.Errorf("map key: %w", err)
|
||||
}
|
||||
if _, err := ft.MapValue.compatible(*nft.MapValue); err != nil {
|
||||
if _, err := ft.MapValue.compatible(*nft.MapValue, checked); err != nil {
|
||||
return false, fmt.Errorf("map value: %w", err)
|
||||
}
|
||||
return false, nil
|
||||
@ -1171,18 +1402,41 @@ func (ft fieldType) compatible(nft fieldType) (bool, error) {
|
||||
if nk != k {
|
||||
return false, fmt.Errorf("slice to %v: %w", nk, ErrIncompatible)
|
||||
}
|
||||
if _, err := ft.List.compatible(*nft.List); err != nil {
|
||||
return false, fmt.Errorf("list: %w", err)
|
||||
if _, err := ft.ListElem.compatible(*nft.ListElem, checked); err != nil {
|
||||
return false, fmt.Errorf("slice: %w", err)
|
||||
}
|
||||
return false, nil
|
||||
case kindArray:
|
||||
if nk != k {
|
||||
return false, fmt.Errorf("array to %v: %w", nk, ErrIncompatible)
|
||||
}
|
||||
if nft.ArrayLength != ft.ArrayLength {
|
||||
return false, fmt.Errorf("array size cannot change (from %d to %d)", ft.ArrayLength, nft.ArrayLength)
|
||||
}
|
||||
if _, err := ft.ListElem.compatible(*nft.ListElem, checked); err != nil {
|
||||
return false, fmt.Errorf("array: %w", err)
|
||||
}
|
||||
return false, nil
|
||||
case kindStruct:
|
||||
if nk != k {
|
||||
return false, fmt.Errorf("struct to %v: %w", nk, ErrIncompatible)
|
||||
}
|
||||
for _, nf := range nft.Fields {
|
||||
for _, f := range ft.Fields {
|
||||
|
||||
// For ondiskVersion2, the seqs are both nonzero, and we must check that we already
|
||||
// did the check to prevent recursion.
|
||||
haveSeq := nft.FieldsTypeSeq != 0 || ft.FieldsTypeSeq != 0
|
||||
if haveSeq {
|
||||
k := [2]int{nft.FieldsTypeSeq, ft.FieldsTypeSeq}
|
||||
if _, ok := checked[k]; ok {
|
||||
return false, nil
|
||||
}
|
||||
checked[k] = struct{}{} // Set early to prevent recursion in call below.
|
||||
}
|
||||
|
||||
for _, nf := range nft.structFields {
|
||||
for _, f := range ft.structFields {
|
||||
if nf.Name == f.Name {
|
||||
_, err := f.Type.compatible(nf.Type)
|
||||
_, err := f.Type.compatible(nf.Type, checked)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("field %q: %w", nf.Name, err)
|
||||
}
|
||||
@ -1201,11 +1455,11 @@ func (ft fieldType) hasNonzeroField(stopAtPtr bool) bool {
|
||||
}
|
||||
switch ft.Kind {
|
||||
case kindMap:
|
||||
return ft.List.hasNonzeroField(true)
|
||||
case kindSlice:
|
||||
return ft.MapValue.hasNonzeroField(true)
|
||||
case kindSlice, kindArray:
|
||||
return ft.ListElem.hasNonzeroField(true)
|
||||
case kindStruct:
|
||||
for _, f := range ft.Fields {
|
||||
for _, f := range ft.structFields {
|
||||
if f.Nonzero || f.Type.hasNonzeroField(true) {
|
||||
return true
|
||||
}
|
||||
|
Reference in New Issue
Block a user