mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2025-12-30 08:14:25 +00:00
Updated logging, added getRowNumber and a few more
This commit is contained in:
100
pkg/reflection/generic_model.go
Normal file
100
pkg/reflection/generic_model.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package reflection
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/bitechdev/ResolveSpec/pkg/logger"
|
||||
)
|
||||
|
||||
type ModelFieldDetail struct {
|
||||
Name string `json:"name"`
|
||||
DataType string `json:"datatype"`
|
||||
SQLName string `json:"sqlname"`
|
||||
SQLDataType string `json:"sqldatatype"`
|
||||
SQLKey string `json:"sqlkey"`
|
||||
Nullable bool `json:"nullable"`
|
||||
FieldValue reflect.Value `json:"-"`
|
||||
}
|
||||
|
||||
// GetModelColumnDetail - Get a list of columns in the SQL declaration of the model
|
||||
func GetModelColumnDetail(record reflect.Value) []ModelFieldDetail {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logger.Error("Panic in GetModelColumnDetail : %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
var lst []ModelFieldDetail
|
||||
lst = make([]ModelFieldDetail, 0)
|
||||
|
||||
if !record.IsValid() {
|
||||
return lst
|
||||
}
|
||||
if record.Kind() == reflect.Pointer || record.Kind() == reflect.Interface {
|
||||
record = record.Elem()
|
||||
}
|
||||
if record.Kind() != reflect.Struct {
|
||||
return lst
|
||||
}
|
||||
modeltype := record.Type()
|
||||
|
||||
for i := 0; i < modeltype.NumField(); i++ {
|
||||
fieldtype := modeltype.Field(i)
|
||||
gormdetail := fieldtype.Tag.Get("gorm")
|
||||
gormdetail = strings.Trim(gormdetail, " ")
|
||||
fielddetail := ModelFieldDetail{}
|
||||
fielddetail.FieldValue = record.Field(i)
|
||||
fielddetail.Name = fieldtype.Name
|
||||
fielddetail.DataType = fieldtype.Type.Name()
|
||||
fielddetail.SQLName = fnFindKeyVal(gormdetail, "column:")
|
||||
fielddetail.SQLDataType = fnFindKeyVal(gormdetail, "type:")
|
||||
if strings.Index(strings.ToLower(gormdetail), "identity") > 0 ||
|
||||
strings.Index(strings.ToLower(gormdetail), "primary_key") > 0 {
|
||||
fielddetail.SQLKey = "primary_key"
|
||||
} else if strings.Contains(strings.ToLower(gormdetail), "unique") {
|
||||
fielddetail.SQLKey = "unique"
|
||||
} else if strings.Contains(strings.ToLower(gormdetail), "uniqueindex") {
|
||||
fielddetail.SQLKey = "uniqueindex"
|
||||
}
|
||||
|
||||
if strings.Contains(strings.ToLower(gormdetail), "nullable") {
|
||||
fielddetail.Nullable = true
|
||||
} else if strings.Contains(strings.ToLower(gormdetail), "null") {
|
||||
fielddetail.Nullable = true
|
||||
}
|
||||
if strings.Contains(strings.ToLower(gormdetail), "not null") {
|
||||
fielddetail.Nullable = false
|
||||
}
|
||||
|
||||
if strings.Contains(strings.ToLower(gormdetail), "foreignkey:") {
|
||||
fielddetail.SQLKey = "foreign_key"
|
||||
ik := strings.Index(strings.ToLower(gormdetail), "foreignkey:")
|
||||
ie := strings.Index(gormdetail[ik:], ";")
|
||||
if ie > ik && ik > 0 {
|
||||
fielddetail.SQLName = strings.ToLower(gormdetail)[ik+11 : ik+ie]
|
||||
//fmt.Printf("\r\nforeignkey: %v", fielddetail)
|
||||
}
|
||||
|
||||
}
|
||||
//";foreignkey:rid_parent;association_foreignkey:id_atevent;save_associations:false;association_autocreate:false;"
|
||||
|
||||
lst = append(lst, fielddetail)
|
||||
|
||||
}
|
||||
return lst
|
||||
}
|
||||
|
||||
func fnFindKeyVal(src, key string) string {
|
||||
icolStart := strings.Index(strings.ToLower(src), strings.ToLower(key))
|
||||
val := ""
|
||||
if icolStart >= 0 {
|
||||
val = src[icolStart+len(key):]
|
||||
icolend := strings.Index(val, ";")
|
||||
if icolend > 0 {
|
||||
val = val[:icolend]
|
||||
}
|
||||
return val
|
||||
}
|
||||
return ""
|
||||
}
|
||||
162
pkg/reflection/model_utils.go
Normal file
162
pkg/reflection/model_utils.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package reflection
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/bitechdev/ResolveSpec/pkg/common"
|
||||
)
|
||||
|
||||
// GetPrimaryKeyName extracts the primary key column name from a model
|
||||
// It first checks if the model implements PrimaryKeyNameProvider (GetIDName method)
|
||||
// Falls back to reflection to find bun:",pk" tag, then gorm:"primaryKey" tag
|
||||
func GetPrimaryKeyName(model any) string {
|
||||
// Check if model implements PrimaryKeyNameProvider
|
||||
if provider, ok := model.(common.PrimaryKeyNameProvider); ok {
|
||||
return provider.GetIDName()
|
||||
}
|
||||
|
||||
// Try Bun tag first
|
||||
if pkName := getPrimaryKeyFromReflection(model, "bun"); pkName != "" {
|
||||
return pkName
|
||||
}
|
||||
|
||||
// Fall back to GORM tag
|
||||
return getPrimaryKeyFromReflection(model, "gorm")
|
||||
}
|
||||
|
||||
// GetModelColumns extracts all column names from a model using reflection
|
||||
// It checks bun tags first, then gorm tags, then json tags, and finally falls back to lowercase field names
|
||||
func GetModelColumns(model any) []string {
|
||||
var columns []string
|
||||
|
||||
modelType := reflect.TypeOf(model)
|
||||
|
||||
// Unwrap pointers, slices, and arrays to get to the base struct type
|
||||
for modelType != nil && (modelType.Kind() == reflect.Pointer || modelType.Kind() == reflect.Slice || modelType.Kind() == reflect.Array) {
|
||||
modelType = modelType.Elem()
|
||||
}
|
||||
|
||||
// Validate that we have a struct type
|
||||
if modelType == nil || modelType.Kind() != reflect.Struct {
|
||||
return columns
|
||||
}
|
||||
|
||||
for i := 0; i < modelType.NumField(); i++ {
|
||||
field := modelType.Field(i)
|
||||
|
||||
// Get column name using the same logic as primary key extraction
|
||||
columnName := getColumnNameFromField(field)
|
||||
|
||||
if columnName != "" {
|
||||
columns = append(columns, columnName)
|
||||
}
|
||||
}
|
||||
|
||||
return columns
|
||||
}
|
||||
|
||||
// getColumnNameFromField extracts the column name from a struct field
|
||||
// Priority: bun tag -> gorm tag -> json tag -> lowercase field name
|
||||
func getColumnNameFromField(field reflect.StructField) string {
|
||||
// Try bun tag first
|
||||
bunTag := field.Tag.Get("bun")
|
||||
if bunTag != "" && bunTag != "-" {
|
||||
if colName := ExtractColumnFromBunTag(bunTag); colName != "" {
|
||||
return colName
|
||||
}
|
||||
}
|
||||
|
||||
// Try gorm tag
|
||||
gormTag := field.Tag.Get("gorm")
|
||||
if gormTag != "" && gormTag != "-" {
|
||||
if colName := ExtractColumnFromGormTag(gormTag); colName != "" {
|
||||
return colName
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to json tag
|
||||
jsonTag := field.Tag.Get("json")
|
||||
if jsonTag != "" && jsonTag != "-" {
|
||||
// Extract just the field name before any options
|
||||
parts := strings.Split(jsonTag, ",")
|
||||
if len(parts) > 0 && parts[0] != "" {
|
||||
return parts[0]
|
||||
}
|
||||
}
|
||||
|
||||
// Last resort: use field name in lowercase
|
||||
return strings.ToLower(field.Name)
|
||||
}
|
||||
|
||||
// getPrimaryKeyFromReflection uses reflection to find the primary key field
|
||||
func getPrimaryKeyFromReflection(model any, ormType string) string {
|
||||
val := reflect.ValueOf(model)
|
||||
if val.Kind() == reflect.Pointer {
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
if val.Kind() != reflect.Struct {
|
||||
return ""
|
||||
}
|
||||
|
||||
typ := val.Type()
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
field := typ.Field(i)
|
||||
|
||||
switch ormType {
|
||||
case "gorm":
|
||||
// Check for gorm tag with primaryKey
|
||||
gormTag := field.Tag.Get("gorm")
|
||||
if strings.Contains(gormTag, "primaryKey") {
|
||||
// Try to extract column name from gorm tag
|
||||
if colName := ExtractColumnFromGormTag(gormTag); colName != "" {
|
||||
return colName
|
||||
}
|
||||
// Fall back to json tag
|
||||
if jsonTag := field.Tag.Get("json"); jsonTag != "" {
|
||||
return strings.Split(jsonTag, ",")[0]
|
||||
}
|
||||
}
|
||||
case "bun":
|
||||
// Check for bun tag with pk flag
|
||||
bunTag := field.Tag.Get("bun")
|
||||
if strings.Contains(bunTag, "pk") {
|
||||
// Extract column name from bun tag
|
||||
if colName := ExtractColumnFromBunTag(bunTag); colName != "" {
|
||||
return colName
|
||||
}
|
||||
// Fall back to json tag
|
||||
if jsonTag := field.Tag.Get("json"); jsonTag != "" {
|
||||
return strings.Split(jsonTag, ",")[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// ExtractColumnFromGormTag extracts the column name from a gorm tag
|
||||
// Example: "column:id;primaryKey" -> "id"
|
||||
func ExtractColumnFromGormTag(tag string) string {
|
||||
parts := strings.Split(tag, ";")
|
||||
for _, part := range parts {
|
||||
part = strings.TrimSpace(part)
|
||||
if colName, found := strings.CutPrefix(part, "column:"); found {
|
||||
return colName
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ExtractColumnFromBunTag extracts the column name from a bun tag
|
||||
// Example: "id,pk" -> "id"
|
||||
// Example: ",pk" -> "" (will fall back to json tag)
|
||||
func ExtractColumnFromBunTag(tag string) string {
|
||||
parts := strings.Split(tag, ",")
|
||||
if len(parts) > 0 && parts[0] != "" {
|
||||
return parts[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
233
pkg/reflection/model_utils_test.go
Normal file
233
pkg/reflection/model_utils_test.go
Normal file
@@ -0,0 +1,233 @@
|
||||
package reflection
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test models for GORM
|
||||
type GormModelWithGetIDName struct {
|
||||
ID int `gorm:"column:rid_test;primaryKey" json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func (m GormModelWithGetIDName) GetIDName() string {
|
||||
return "rid_test"
|
||||
}
|
||||
|
||||
type GormModelWithColumnTag struct {
|
||||
ID int `gorm:"column:custom_id;primaryKey" json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type GormModelWithJSONFallback struct {
|
||||
ID int `gorm:"primaryKey" json:"user_id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Test models for Bun
|
||||
type BunModelWithGetIDName struct {
|
||||
ID int `bun:"rid_test,pk" json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func (m BunModelWithGetIDName) GetIDName() string {
|
||||
return "rid_test"
|
||||
}
|
||||
|
||||
type BunModelWithColumnTag struct {
|
||||
ID int `bun:"custom_id,pk" json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type BunModelWithJSONFallback struct {
|
||||
ID int `bun:",pk" json:"user_id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func TestGetPrimaryKeyName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
model any
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "GORM model with GetIDName method",
|
||||
model: GormModelWithGetIDName{},
|
||||
expected: "rid_test",
|
||||
},
|
||||
{
|
||||
name: "GORM model with column tag",
|
||||
model: GormModelWithColumnTag{},
|
||||
expected: "custom_id",
|
||||
},
|
||||
{
|
||||
name: "GORM model with JSON fallback",
|
||||
model: GormModelWithJSONFallback{},
|
||||
expected: "user_id",
|
||||
},
|
||||
{
|
||||
name: "GORM model pointer with GetIDName",
|
||||
model: &GormModelWithGetIDName{},
|
||||
expected: "rid_test",
|
||||
},
|
||||
{
|
||||
name: "GORM model pointer with column tag",
|
||||
model: &GormModelWithColumnTag{},
|
||||
expected: "custom_id",
|
||||
},
|
||||
{
|
||||
name: "Bun model with GetIDName method",
|
||||
model: BunModelWithGetIDName{},
|
||||
expected: "rid_test",
|
||||
},
|
||||
{
|
||||
name: "Bun model with column tag",
|
||||
model: BunModelWithColumnTag{},
|
||||
expected: "custom_id",
|
||||
},
|
||||
{
|
||||
name: "Bun model with JSON fallback",
|
||||
model: BunModelWithJSONFallback{},
|
||||
expected: "user_id",
|
||||
},
|
||||
{
|
||||
name: "Bun model pointer with GetIDName",
|
||||
model: &BunModelWithGetIDName{},
|
||||
expected: "rid_test",
|
||||
},
|
||||
{
|
||||
name: "Bun model pointer with column tag",
|
||||
model: &BunModelWithColumnTag{},
|
||||
expected: "custom_id",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := GetPrimaryKeyName(tt.model)
|
||||
if result != tt.expected {
|
||||
t.Errorf("GetPrimaryKeyName() = %v, want %v", result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractColumnFromGormTag(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tag string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "column tag with primaryKey",
|
||||
tag: "column:rid_test;primaryKey",
|
||||
expected: "rid_test",
|
||||
},
|
||||
{
|
||||
name: "column tag with spaces",
|
||||
tag: "column:user_id ; primaryKey ; autoIncrement",
|
||||
expected: "user_id",
|
||||
},
|
||||
{
|
||||
name: "no column tag",
|
||||
tag: "primaryKey;autoIncrement",
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := ExtractColumnFromGormTag(tt.tag)
|
||||
if result != tt.expected {
|
||||
t.Errorf("ExtractColumnFromGormTag() = %v, want %v", result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractColumnFromBunTag(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tag string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "column name with pk flag",
|
||||
tag: "rid_test,pk",
|
||||
expected: "rid_test",
|
||||
},
|
||||
{
|
||||
name: "only pk flag",
|
||||
tag: ",pk",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "column with multiple flags",
|
||||
tag: "user_id,pk,autoincrement",
|
||||
expected: "user_id",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := ExtractColumnFromBunTag(tt.tag)
|
||||
if result != tt.expected {
|
||||
t.Errorf("ExtractColumnFromBunTag() = %v, want %v", result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetModelColumns(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
model any
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "Bun model with multiple columns",
|
||||
model: BunModelWithColumnTag{},
|
||||
expected: []string{"custom_id", "name"},
|
||||
},
|
||||
{
|
||||
name: "GORM model with multiple columns",
|
||||
model: GormModelWithColumnTag{},
|
||||
expected: []string{"custom_id", "name"},
|
||||
},
|
||||
{
|
||||
name: "Bun model pointer",
|
||||
model: &BunModelWithColumnTag{},
|
||||
expected: []string{"custom_id", "name"},
|
||||
},
|
||||
{
|
||||
name: "GORM model pointer",
|
||||
model: &GormModelWithColumnTag{},
|
||||
expected: []string{"custom_id", "name"},
|
||||
},
|
||||
{
|
||||
name: "Bun model with JSON fallback",
|
||||
model: BunModelWithJSONFallback{},
|
||||
expected: []string{"user_id", "name"},
|
||||
},
|
||||
{
|
||||
name: "GORM model with JSON fallback",
|
||||
model: GormModelWithJSONFallback{},
|
||||
expected: []string{"user_id", "name"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := GetModelColumns(tt.model)
|
||||
if len(result) != len(tt.expected) {
|
||||
t.Errorf("GetModelColumns() returned %d columns, want %d", len(result), len(tt.expected))
|
||||
return
|
||||
}
|
||||
for i, col := range result {
|
||||
if col != tt.expected[i] {
|
||||
t.Errorf("GetModelColumns()[%d] = %v, want %v", i, col, tt.expected[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user