fix(spec): unwrap SQL wrapper metadata types

This commit is contained in:
2026-08-28 22:42:52 +02:00
parent dab4940ace
commit a68cf83be6
6 changed files with 218 additions and 19 deletions
+31 -3
View File
@@ -85,13 +85,19 @@ func buildModelInfo(schema, entity string, model interface{}) modelInfo {
// Skip relation fields (slice or user-defined struct that isn't time.Time).
fieldType, found := modelType.FieldByName(d.Name)
var unwrappedType reflect.Type
isSQLType := false
if found {
ft := fieldType.Type
if ft.Kind() == reflect.Pointer {
if sqlType, ok := unwrapSQLType(ft); ok {
unwrappedType = sqlType
ft = sqlType
isSQLType = true
} else if ft.Kind() == reflect.Pointer {
ft = ft.Elem()
}
isUserStruct := ft.Kind() == reflect.Struct && ft.Name() != "Time" && ft.PkgPath() != ""
if ft.Kind() == reflect.Slice || isUserStruct {
if !isSQLType && (ft.Kind() == reflect.Slice || isUserStruct) {
info.relationNames = append(info.relationNames, jsonName)
continue
}
@@ -104,6 +110,9 @@ func buildModelInfo(schema, entity string, model interface{}) modelInfo {
// Derive Go type name, unwrapping pointer if needed.
goType := d.DataType
if isSQLType {
goType = unwrappedType.Name()
}
if goType == "" && found {
ft := fieldType.Type
for ft.Kind() == reflect.Pointer {
@@ -125,7 +134,7 @@ func buildModelInfo(schema, entity string, model interface{}) modelInfo {
isPrimary: isPrimary,
isUnique: d.SQLKey == "unique" || d.SQLKey == "uniqueindex",
isFK: d.SQLKey == "foreign_key",
nullable: d.Nullable,
nullable: isSQLType || d.Nullable,
}
info.columns = append(info.columns, ci)
}
@@ -134,6 +143,25 @@ func buildModelInfo(schema, entity string, model interface{}) modelInfo {
return info
}
// unwrapSQLType returns the value type wrapped by a spectypes SQL value. These
// types are scalar columns even when their Go representation is a struct or a
// slice (for example, SqlNull[string] and SqlJSONB).
func unwrapSQLType(t reflect.Type) (reflect.Type, bool) {
for t.Kind() == reflect.Pointer {
t = t.Elem()
}
if t.PkgPath() != "github.com/bitechdev/ResolveSpec/pkg/spectypes" {
return nil, false
}
if t.Kind() == reflect.Struct {
if value, ok := t.FieldByName("Val"); ok {
return value.Type, true
}
}
return t, true
}
// fieldJSONName returns the JSON tag name for a struct field, falling back to the field name.
func fieldJSONName(modelType reflect.Type, fieldName string) string {
field, ok := modelType.FieldByName(fieldName)
+34
View File
@@ -0,0 +1,34 @@
package resolvemcp
import (
"testing"
"github.com/bitechdev/ResolveSpec/pkg/spectypes"
)
func TestBuildModelInfo_UnwrapsSQLTypes(t *testing.T) {
type related struct {
ID int64 `json:"id"`
}
type model struct {
Name spectypes.SqlString `gorm:"column:name" json:"name"`
Metadata spectypes.SqlJSONB `gorm:"column:metadata;type:jsonb" json:"metadata"`
Related related `json:"related"`
}
info := buildModelInfo("public", "models", model{})
columns := make(map[string]columnInfo, len(info.columns))
for _, column := range info.columns {
columns[column.jsonName] = column
}
if column, ok := columns["name"]; !ok || column.goType != "string" || !column.nullable {
t.Errorf("expected name SQL wrapper column, got %+v", column)
}
if column, ok := columns["metadata"]; !ok || !column.nullable {
t.Errorf("expected metadata SQL wrapper column, got %+v", column)
}
if len(info.relationNames) != 1 || info.relationNames[0] != "related" {
t.Errorf("expected only related to be a relation, got %v", info.relationNames)
}
}