diff --git a/examples/example_service/example_service_test.go b/examples/example_service/example_service_test.go index 533a5da..7ab84a2 100644 --- a/examples/example_service/example_service_test.go +++ b/examples/example_service/example_service_test.go @@ -9,6 +9,7 @@ import ( "github.com/tj/assert" "github.com/vingarcia/kissorm" "github.com/vingarcia/kissorm/nullable" + "github.com/vingarcia/kissorm/structs" ) func TestCreateUser(t *testing.T) { @@ -58,7 +59,7 @@ func TestCreateUser(t *testing.T) { // // If you are inserting an anonymous struct (not usual) this function // can make your tests shorter: - uMap, err := kissorm.StructToMap(record) + uMap, err := structs.StructToMap(record) if err != nil { return err } @@ -95,7 +96,7 @@ func TestUpdateUserScore(t *testing.T) { DoAndReturn(func(ctx context.Context, result interface{}, query string, params ...interface{}) error { // This function will use reflection to fill the // struct fields with the values from the map - return kissorm.FillStructWith(result, map[string]interface{}{ + return structs.FillStructWith(result, map[string]interface{}{ // Use int this map the keys you set on the kissorm tags, e.g. `kissorm:"score"` // Each of these fields represent the database rows returned // by the query. @@ -138,7 +139,7 @@ func TestListUsers(t *testing.T) { DoAndReturn(func(ctx context.Context, result interface{}, query string, params ...interface{}) error { // This function will use reflection to fill the // struct fields with the values from the map - return kissorm.FillStructWith(result, map[string]interface{}{ + return structs.FillStructWith(result, map[string]interface{}{ // Use int this map the keys you set on the kissorm tags, e.g. `kissorm:"score"` // Each of these fields represent the database rows returned // by the query. @@ -147,7 +148,7 @@ func TestListUsers(t *testing.T) { }), usersTableMock.EXPECT().Query(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). DoAndReturn(func(ctx context.Context, results interface{}, query string, params ...interface{}) error { - return kissorm.FillSliceWith(results, []map[string]interface{}{ + return structs.FillSliceWith(results, []map[string]interface{}{ { "id": 1, "name": "fake name", diff --git a/kiss_orm.go b/kiss_orm.go index 0d76a6c..47c2367 100644 --- a/kiss_orm.go +++ b/kiss_orm.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/pkg/errors" + "github.com/vingarcia/kissorm/structs" ) // DB represents the kissorm client responsible for @@ -110,7 +111,7 @@ func (c DB) Query( } sliceType := slicePtrType.Elem() slice := slicePtr.Elem() - structType, isSliceOfPtrs, err := decodeAsSliceOfStructs(sliceType) + structType, isSliceOfPtrs, err := structs.DecodeAsSliceOfStructs(sliceType) if err != nil { return err } @@ -230,7 +231,7 @@ func (c DB) QueryChunks( chunk := reflect.MakeSlice(chunkType, 0, parser.ChunkSize) - structType, isSliceOfPtrs, err := decodeAsSliceOfStructs(chunkType) + structType, isSliceOfPtrs, err := structs.DecodeAsSliceOfStructs(chunkType) if err != nil { return err } @@ -368,7 +369,7 @@ func (c DB) insertWithReturningID( if err = assertStructPtr(t); err != nil { return errors.Wrap(err, "can't write id field") } - info := getCachedTagInfo(tagInfoCache, t.Elem()) + info := structs.GetTagInfo(t.Elem()) var scanFields []interface{} for _, id := range idNames { @@ -403,7 +404,7 @@ func (c DB) insertWithLastInsertID( return errors.Wrap(err, "can't write to `"+idName+"` field") } - info := getCachedTagInfo(tagInfoCache, t.Elem()) + info := structs.GetTagInfo(t.Elem()) id, err := result.LastInsertId() if err != nil { @@ -485,7 +486,7 @@ func normalizeIDsAsMaps(idNames []string, ids []interface{}) ([]map[string]inter t := reflect.TypeOf(ids[i]) switch t.Kind() { case reflect.Struct: - m, err := StructToMap(ids[i]) + m, err := structs.StructToMap(ids[i]) if err != nil { return nil, errors.Wrapf(err, "could not get ID(s) from record on idx %d", i) } @@ -542,7 +543,7 @@ func buildInsertQuery( record interface{}, idFieldNames ...string, ) (query string, params []interface{}, err error) { - recordMap, err := StructToMap(record) + recordMap, err := structs.StructToMap(record) if err != nil { return "", nil, err } @@ -588,7 +589,7 @@ func buildUpdateQuery( record interface{}, idFieldNames ...string, ) (query string, args []interface{}, err error) { - recordMap, err := StructToMap(record) + recordMap, err := structs.StructToMap(record) if err != nil { return "", nil, err } @@ -683,12 +684,6 @@ func (c DB) Transaction(ctx context.Context, fn func(ORMProvider) error) error { } } -// This cache is kept as a pkg variable -// because the total number of types on a program -// should be finite. So keeping a single cache here -// works fine. -var tagInfoCache = map[reflect.Type]structInfo{} - var errType = reflect.TypeOf(new(error)).Elem() func parseInputFunc(fn interface{}) (reflect.Type, error) { @@ -744,7 +739,7 @@ func scanRows(rows *sql.Rows, record interface{}) error { return fmt.Errorf("kissorm: expected to receive a pointer to slice of structs, but got: %T", record) } - info := getCachedTagInfo(tagInfoCache, t) + info := structs.GetTagInfo(t) scanArgs := []interface{}{} for _, name := range names { @@ -760,15 +755,6 @@ func scanRows(rows *sql.Rows, record interface{}) error { return rows.Scan(scanArgs...) } -func getCachedTagInfo(tagInfoCache map[reflect.Type]structInfo, key reflect.Type) structInfo { - info, found := tagInfoCache[key] - if !found { - info = getTagNames(key) - tagInfoCache[key] = info - } - return info -} - func buildSingleKeyDeleteQuery( dialect dialect, table string, diff --git a/structs.go b/structs/structs.go similarity index 86% rename from structs.go rename to structs/structs.go index 50f0134..e36dbba 100644 --- a/structs.go +++ b/structs/structs.go @@ -1,4 +1,4 @@ -package kissorm +package structs import ( "fmt" @@ -12,6 +12,31 @@ type structInfo struct { Index map[string]int } +// This cache is kept as a pkg variable +// because the total number of types on a program +// should be finite. So keeping a single cache here +// works fine. +var tagInfoCache = map[reflect.Type]structInfo{} + +// GetTagInfo efficiently returns the type information +// using a global private cache +// +// In the future we might move this cache inside +// a struct, but for now this accessor is the one +// we are using +func GetTagInfo(key reflect.Type) structInfo { + return getCachedTagInfo(tagInfoCache, key) +} + +func getCachedTagInfo(tagInfoCache map[reflect.Type]structInfo, key reflect.Type) structInfo { + info, found := tagInfoCache[key] + if !found { + info = getTagNames(key) + tagInfoCache[key] = info + } + return info +} + // StructToMap converts any struct type to a map based on // the tag named `kissorm`, i.e. `kissorm:"map_key_name"` // @@ -200,7 +225,7 @@ func FillSliceWith(entities interface{}, dbRows []map[string]interface{}) error ) } - structType, isSliceOfPtrs, err := decodeAsSliceOfStructs(sliceType.Elem()) + structType, isSliceOfPtrs, err := DecodeAsSliceOfStructs(sliceType.Elem()) if err != nil { return errors.Wrap(err, "FillSliceWith") } @@ -249,7 +274,11 @@ func getTagNames(t reflect.Type) structInfo { return info } -func decodeAsSliceOfStructs(slice reflect.Type) ( +// DecodeAsSliceOfStructs makes several checks +// while decoding an input type and returns +// useful information so that it is easier +// to manipulate the original slice later. +func DecodeAsSliceOfStructs(slice reflect.Type) ( structType reflect.Type, isSliceOfPtrs bool, err error, diff --git a/structs_test.go b/structs/structs_test.go similarity index 99% rename from structs_test.go rename to structs/structs_test.go index b55c02c..35ec8f4 100644 --- a/structs_test.go +++ b/structs/structs_test.go @@ -1,4 +1,4 @@ -package kissorm +package structs import ( "testing"