From 77f39af2f9647e3cccac89b47bf4fbc533478a54 Mon Sep 17 00:00:00 2001 From: Hein Date: Thu, 18 Dec 2025 11:43:19 +0200 Subject: [PATCH] [breaking] Moved sql types to datatypes --- pkg/{common => datatypes}/sql_types.go | 4 +- pkg/{common => datatypes}/sql_types_test.go | 2 +- pkg/reflection/model_utils.go | 52 ++++++++++++++++++++- pkg/reflection/model_utils_sqltypes_test.go | 20 ++++---- 4 files changed, 64 insertions(+), 14 deletions(-) rename pkg/{common => datatypes}/sql_types.go (99%) rename pkg/{common => datatypes}/sql_types_test.go (99%) diff --git a/pkg/common/sql_types.go b/pkg/datatypes/sql_types.go similarity index 99% rename from pkg/common/sql_types.go rename to pkg/datatypes/sql_types.go index 0429ad7..8f802f6 100644 --- a/pkg/common/sql_types.go +++ b/pkg/datatypes/sql_types.go @@ -1,5 +1,5 @@ -// Package common provides nullable SQL types with automatic casting and conversion methods. -package common +// Package datatypes provides nullable SQL types with automatic casting and conversion methods. +package datatypes import ( "database/sql" diff --git a/pkg/common/sql_types_test.go b/pkg/datatypes/sql_types_test.go similarity index 99% rename from pkg/common/sql_types_test.go rename to pkg/datatypes/sql_types_test.go index 86d1c88..d8f29ad 100644 --- a/pkg/common/sql_types_test.go +++ b/pkg/datatypes/sql_types_test.go @@ -1,4 +1,4 @@ -package common +package datatypes import ( "database/sql/driver" diff --git a/pkg/reflection/model_utils.go b/pkg/reflection/model_utils.go index 2fda683..06c0fe9 100644 --- a/pkg/reflection/model_utils.go +++ b/pkg/reflection/model_utils.go @@ -6,6 +6,7 @@ import ( "reflect" "strconv" "strings" + "time" "github.com/bitechdev/ResolveSpec/pkg/modelregistry" ) @@ -1080,7 +1081,55 @@ func setFieldValue(field reflect.Value, value interface{}) error { // Handle struct types (like SqlTimeStamp, SqlDate, SqlTime which wrap SqlNull[time.Time]) if field.Kind() == reflect.Struct { - // Try to find a "Val" field (for SqlNull types) and set it + + // Handle datatypes.SqlNull[T] and wrapped types (SqlTimeStamp, SqlDate, SqlTime) + // Check if the type has a Scan method (sql.Scanner interface) + if field.CanAddr() { + scanMethod := field.Addr().MethodByName("Scan") + if scanMethod.IsValid() { + // Call the Scan method with the value + results := scanMethod.Call([]reflect.Value{reflect.ValueOf(value)}) + if len(results) > 0 { + // Check if there was an error + if err, ok := results[0].Interface().(error); ok && err != nil { + return err + } + return nil + } + } + } + + // Handle time.Time with ISO string fallback + if field.Type() == reflect.TypeOf(time.Time{}) { + switch v := value.(type) { + case time.Time: + field.Set(reflect.ValueOf(v)) + return nil + case string: + // Try parsing as ISO 8601 / RFC3339 + if t, err := time.Parse(time.RFC3339, v); err == nil { + field.Set(reflect.ValueOf(t)) + return nil + } + // Try other common formats + formats := []string{ + "2006-01-02T15:04:05.000-0700", + "2006-01-02T15:04:05.000", + "2006-01-02T15:04:05", + "2006-01-02 15:04:05", + "2006-01-02", + } + for _, format := range formats { + if t, err := time.Parse(format, v); err == nil { + field.Set(reflect.ValueOf(t)) + return nil + } + } + return fmt.Errorf("cannot parse time string: %s", v) + } + } + + // Fallback: Try to find a "Val" field (for SqlNull types) and set it directly valField := field.FieldByName("Val") if valField.IsValid() && valField.CanSet() { // Also set Valid field to true @@ -1095,6 +1144,7 @@ func setFieldValue(field reflect.Value, value interface{}) error { return nil } } + } // If we can convert the type, do it diff --git a/pkg/reflection/model_utils_sqltypes_test.go b/pkg/reflection/model_utils_sqltypes_test.go index 030c6d4..4543aa4 100644 --- a/pkg/reflection/model_utils_sqltypes_test.go +++ b/pkg/reflection/model_utils_sqltypes_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/bitechdev/ResolveSpec/pkg/common" + "github.com/bitechdev/ResolveSpec/pkg/datatypes" "github.com/bitechdev/ResolveSpec/pkg/reflection" ) @@ -12,7 +12,7 @@ func TestMapToStruct_SqlJSONB_PreservesDriverValuer(t *testing.T) { // Test that SqlJSONB type preserves driver.Valuer interface type TestModel struct { ID int64 `bun:"id,pk" json:"id"` - Meta common.SqlJSONB `bun:"meta" json:"meta"` + Meta datatypes.SqlJSONB `bun:"meta" json:"meta"` } dataMap := map[string]interface{}{ @@ -65,7 +65,7 @@ func TestMapToStruct_SqlJSONB_FromBytes(t *testing.T) { // Test that SqlJSONB can be set from []byte directly type TestModel struct { ID int64 `bun:"id,pk" json:"id"` - Meta common.SqlJSONB `bun:"meta" json:"meta"` + Meta datatypes.SqlJSONB `bun:"meta" json:"meta"` } jsonBytes := []byte(`{"direct":"bytes"}`) @@ -103,11 +103,11 @@ func TestMapToStruct_AllSqlTypes(t *testing.T) { type TestModel struct { ID int64 `bun:"id,pk" json:"id"` Name string `bun:"name" json:"name"` - CreatedAt common.SqlTimeStamp `bun:"created_at" json:"created_at"` - BirthDate common.SqlDate `bun:"birth_date" json:"birth_date"` - LoginTime common.SqlTime `bun:"login_time" json:"login_time"` - Meta common.SqlJSONB `bun:"meta" json:"meta"` - Tags common.SqlJSONB `bun:"tags" json:"tags"` + CreatedAt datatypes.SqlTimeStamp `bun:"created_at" json:"created_at"` + BirthDate datatypes.SqlDate `bun:"birth_date" json:"birth_date"` + LoginTime datatypes.SqlTime `bun:"login_time" json:"login_time"` + Meta datatypes.SqlJSONB `bun:"meta" json:"meta"` + Tags datatypes.SqlJSONB `bun:"tags" json:"tags"` } now := time.Now() @@ -225,8 +225,8 @@ func TestMapToStruct_SqlNull_NilValues(t *testing.T) { // Test that SqlNull types handle nil values correctly type TestModel struct { ID int64 `bun:"id,pk" json:"id"` - UpdatedAt common.SqlTimeStamp `bun:"updated_at" json:"updated_at"` - DeletedAt common.SqlTimeStamp `bun:"deleted_at" json:"deleted_at"` + UpdatedAt datatypes.SqlTimeStamp `bun:"updated_at" json:"updated_at"` + DeletedAt datatypes.SqlTimeStamp `bun:"deleted_at" json:"deleted_at"` } now := time.Now()