package tdb import ( "errors" "fmt" "reflect" "strings" // "git.keganmyers.com/terribleplan/tdb/stringy" "github.com/golang/protobuf/proto" bolt "go.etcd.io/bbolt" ) // these types are simply to help pass around things and know what they are // some would argue that having such types is bad practice, but I see it as // a necessary evil since the reflection in golang is fairly bare-bones // dbValue - a non-pointer thing stored by the database type dbValue reflect.Value func (val dbValue) Ptr() dbPtrValue { return dbPtrValue(reflect.Value(val).Addr()) } func (val dbValue) Type() *dbType { return &dbType{ T: reflect.Value(val).Type(), } } func (val dbValue) dangerous_Field(f dbField) reflect.Value { return reflect.Value(val).FieldByIndex(reflect.StructField(f).Index) } func (val dbValue) Marshal() ([]byte, error) { return proto.Marshal(reflect.Value(val).Addr().Interface().(proto.Message)) } // dbPtrValue - a pointer to a thing stored by the database func dbPtrValueOf(p proto.Message) dbPtrValue { return dbPtrValue(reflect.ValueOf(p)) } type dbPtrValue reflect.Value func (ptrVal dbPtrValue) Val() dbValue { return dbValue(reflect.Value(ptrVal).Elem()) } func (ptrVal dbPtrValue) PtrType() *dbPtrType { return &dbPtrType{ T: reflect.Value(ptrVal).Type(), } } func (ptrVal dbPtrValue) Proto() proto.Message { return reflect.Value(ptrVal).Interface().(proto.Message) } func (ptrVal dbPtrValue) IsOfPtrType(pt *dbPtrType) bool { return pt.T == reflect.Value(ptrVal).Type() } func (ptrVal dbPtrValue) IsNil() bool { return reflect.Value(ptrVal).IsNil() } func (ptrVal dbPtrValue) dangerous_Field(f dbField) reflect.Value { return reflect.Value(ptrVal).Elem().FieldByIndex(reflect.StructField(f).Index) } func (ptrVal dbPtrValue) Marshal() ([]byte, error) { return proto.Marshal(reflect.Value(ptrVal).Interface().(proto.Message)) } // dbType - the type of a non-pointer thing stored by the database func dbTypeOf(p proto.Message) *dbType { t := reflect.TypeOf(p).Elem() typeString := t.String() nameComponents := strings.Split(typeString, ".") name := nameComponents[len(nameComponents)-1] if name[0] == '*' { name = name[1:] } if name == "" { panic(errors.New("[tdb] [internal] ]Unable to reliably determine name of thing")) } return &dbType{ Name: name, T: t, } } type dbType struct { Name string T reflect.Type } func (t *dbType) New() dbPtrValue { return dbPtrValue(reflect.New(t.T)) } func (t *dbType) PtrType() *dbPtrType { return &dbPtrType{ Name: "*" + t.Name, T: reflect.PtrTo(t.T), } } func (t *dbType) IdField() dbField { idField := t.NamedField("Id") if idField.Type.Kind() != reflect.Uint64 { panic(fmt.Errorf("[tdb] [internal] %s's 'Id' field is not a uint64", t.Name)) } return idField } func (t *dbType) NamedField(name string) dbField { field, exists := t.T.FieldByName(name) if !exists { panic(fmt.Errorf("[tdb] [internal] %s lacks a '%s' field", t.Name, name)) } return dbField(field) } // dbPtrType - the type of a pointer to a thing stored by the database type dbPtrType struct { Name string T reflect.Type } func (ptr *dbPtrType) New() dbPtrValue { return dbPtrValue(reflect.New(ptr.T.Elem())) } func (ptr *dbPtrType) Type() dbType { return dbType{ Name: ptr.Name[1:], T: ptr.T.Elem(), } } func (ptr *dbPtrType) String() string { return ptr.T.String() } func (ptr *dbPtrType) Zero() dbPtrValue { return dbPtrValue(reflect.Zero(ptr.T)) } func (ptr *dbPtrType) Unmarshal(data []byte) (dbPtrValue, error) { pv := ptr.New() if err := proto.Unmarshal(data, pv.Proto()); err != nil { return ptr.Zero(), err } return pv, nil } func (ptr *dbPtrType) IdField() dbField { idField := ptr.NamedField("Id") if idField.Type.Kind() != reflect.Uint64 { panic(fmt.Errorf("[tdb] [internal] %s's 'Id' field is not a uint64", ptr.Name)) } return idField } func (ptr *dbPtrType) NamedField(name string) dbField { field, exists := ptr.T.Elem().FieldByName(name) if !exists { panic(fmt.Errorf("[tdb] [internal] %s lacks a '%s' field", ptr.Name, name)) } return dbField(field) } // dbField - a field on a struct... this one is quite unnecessary type dbField reflect.StructField func (f dbField) IsUint64() bool { return f.Type.Kind() == reflect.Uint64 } func (f dbField) IsUint64Slice() bool { return f.Type.Kind() == reflect.Slice && f.Type.Elem().Kind() == reflect.Uint64 } func (f dbField) IsSliceish() bool { fieldKind := f.Type.Kind() return fieldKind == reflect.Array || fieldKind == reflect.Slice } // Tx - is it dumb to provide this just so consumers of this package don't have to include bolt? // I think not type Tx struct { btx *bolt.Tx } func convertTx(btx *bolt.Tx) *Tx { return &Tx{btx: btx} } func (tx *Tx) tx() *bolt.Tx { return tx.btx }