tdb/internals.go

222 lines
4.7 KiB
Go

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
}