mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2025-12-06 14:26:22 +00:00
Fixes on getRelationshipInfo, ShouldUseNestedProcessor
This commit is contained in:
parent
8b7db5b31a
commit
a44ef90d7c
@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/uptrace/bun"
|
"github.com/uptrace/bun"
|
||||||
|
|
||||||
"github.com/bitechdev/ResolveSpec/pkg/common"
|
"github.com/bitechdev/ResolveSpec/pkg/common"
|
||||||
|
"github.com/bitechdev/ResolveSpec/pkg/modelregistry"
|
||||||
"github.com/bitechdev/ResolveSpec/pkg/reflection"
|
"github.com/bitechdev/ResolveSpec/pkg/reflection"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -365,6 +366,14 @@ func (b *BunUpdateQuery) Model(model interface{}) common.UpdateQuery {
|
|||||||
|
|
||||||
func (b *BunUpdateQuery) Table(table string) common.UpdateQuery {
|
func (b *BunUpdateQuery) Table(table string) common.UpdateQuery {
|
||||||
b.query = b.query.Table(table)
|
b.query = b.query.Table(table)
|
||||||
|
if b.model == nil {
|
||||||
|
// Try to get table name from table string if model is not set
|
||||||
|
|
||||||
|
model, err := modelregistry.GetModelByName(table)
|
||||||
|
if err == nil {
|
||||||
|
b.model = model
|
||||||
|
}
|
||||||
|
}
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"github.com/bitechdev/ResolveSpec/pkg/common"
|
"github.com/bitechdev/ResolveSpec/pkg/common"
|
||||||
|
"github.com/bitechdev/ResolveSpec/pkg/modelregistry"
|
||||||
"github.com/bitechdev/ResolveSpec/pkg/reflection"
|
"github.com/bitechdev/ResolveSpec/pkg/reflection"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -98,6 +99,7 @@ func (g *GormSelectQuery) Table(table string) common.SelectQuery {
|
|||||||
g.db = g.db.Table(table)
|
g.db = g.db.Table(table)
|
||||||
// Check if the table name contains schema (e.g., "schema.table")
|
// Check if the table name contains schema (e.g., "schema.table")
|
||||||
g.schema, g.tableName = parseTableName(table)
|
g.schema, g.tableName = parseTableName(table)
|
||||||
|
|
||||||
return g
|
return g
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -340,6 +342,13 @@ func (g *GormUpdateQuery) Model(model interface{}) common.UpdateQuery {
|
|||||||
|
|
||||||
func (g *GormUpdateQuery) Table(table string) common.UpdateQuery {
|
func (g *GormUpdateQuery) Table(table string) common.UpdateQuery {
|
||||||
g.db = g.db.Table(table)
|
g.db = g.db.Table(table)
|
||||||
|
if g.model == nil {
|
||||||
|
// Try to get table name from table string if model is not set
|
||||||
|
model, err := modelregistry.GetModelByName(table)
|
||||||
|
if err == nil {
|
||||||
|
g.model = model
|
||||||
|
}
|
||||||
|
}
|
||||||
return g
|
return g
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -378,8 +378,16 @@ func (p *NestedCUDProcessor) getTableNameForModel(model interface{}, defaultName
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ShouldUseNestedProcessor determines if we should use nested CUD processing
|
// ShouldUseNestedProcessor determines if we should use nested CUD processing
|
||||||
// It checks if the data contains nested relations or a _request field
|
// It recursively checks if the data contains:
|
||||||
|
// 1. A _request field at any level, OR
|
||||||
|
// 2. Nested relations that themselves contain further nested relations or _request fields
|
||||||
|
// This ensures nested processing is only used when there are deeply nested operations
|
||||||
func ShouldUseNestedProcessor(data map[string]interface{}, model interface{}, relationshipHelper RelationshipInfoProvider) bool {
|
func ShouldUseNestedProcessor(data map[string]interface{}, model interface{}, relationshipHelper RelationshipInfoProvider) bool {
|
||||||
|
return shouldUseNestedProcessorDepth(data, model, relationshipHelper, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldUseNestedProcessorDepth is the internal recursive implementation with depth tracking
|
||||||
|
func shouldUseNestedProcessorDepth(data map[string]interface{}, model interface{}, relationshipHelper RelationshipInfoProvider, depth int) bool {
|
||||||
// Check for _request field
|
// Check for _request field
|
||||||
if _, hasCRUDRequest := data["_request"]; hasCRUDRequest {
|
if _, hasCRUDRequest := data["_request"]; hasCRUDRequest {
|
||||||
return true
|
return true
|
||||||
@ -407,19 +415,33 @@ func ShouldUseNestedProcessor(data map[string]interface{}, model interface{}, re
|
|||||||
if relInfo != nil {
|
if relInfo != nil {
|
||||||
// Check if the value is actually nested data (object or array)
|
// Check if the value is actually nested data (object or array)
|
||||||
switch v := value.(type) {
|
switch v := value.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]interface{}, []interface{}, []map[string]interface{}:
|
||||||
//logger.Debug("Found nested relation field: %s", key)
|
// If we're already at a nested level (depth > 0) and found a relation,
|
||||||
return ShouldUseNestedProcessor(v, relInfo.RelatedModel, relationshipHelper)
|
// that means we have multi-level nesting, so return true
|
||||||
case []interface{}, []map[string]interface{}:
|
if depth > 0 {
|
||||||
//logger.Debug("Found nested relation field: %s", key)
|
return true
|
||||||
for _, item := range v.([]interface{}) {
|
}
|
||||||
if itemMap, ok := item.(map[string]interface{}); ok {
|
// At depth 0, recurse to check if the nested data has further nesting
|
||||||
if ShouldUseNestedProcessor(itemMap, relInfo.RelatedModel, relationshipHelper) {
|
switch typedValue := v.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
if shouldUseNestedProcessorDepth(typedValue, relInfo.RelatedModel, relationshipHelper, depth+1) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case []interface{}:
|
||||||
|
for _, item := range typedValue {
|
||||||
|
if itemMap, ok := item.(map[string]interface{}); ok {
|
||||||
|
if shouldUseNestedProcessorDepth(itemMap, relInfo.RelatedModel, relationshipHelper, depth+1) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case []map[string]interface{}:
|
||||||
|
for _, itemMap := range typedValue {
|
||||||
|
if shouldUseNestedProcessorDepth(itemMap, relInfo.RelatedModel, relationshipHelper, depth+1) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -811,7 +811,7 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, id
|
|||||||
dataMap["id"] = targetID
|
dataMap["id"] = targetID
|
||||||
|
|
||||||
// Create update query
|
// Create update query
|
||||||
query := tx.NewUpdate().Table(tableName).SetMap(dataMap)
|
query := tx.NewUpdate().Model(model).Table(tableName).SetMap(dataMap)
|
||||||
pkName := reflection.GetPrimaryKeyName(model)
|
pkName := reflection.GetPrimaryKeyName(model)
|
||||||
query = query.Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), targetID)
|
query = query.Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), targetID)
|
||||||
|
|
||||||
@ -1904,7 +1904,8 @@ func filterExtendedOptions(validator *common.ColumnValidator, options ExtendedRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
// shouldUseNestedProcessor determines if we should use nested CUD processing
|
// shouldUseNestedProcessor determines if we should use nested CUD processing
|
||||||
// It checks if the data contains nested relations or a _request field
|
// It recursively checks if the data contains deeply nested relations or _request fields
|
||||||
|
// Simple one-level relations without further nesting don't require the nested processor
|
||||||
func (h *Handler) shouldUseNestedProcessor(data map[string]interface{}, model interface{}) bool {
|
func (h *Handler) shouldUseNestedProcessor(data map[string]interface{}, model interface{}) bool {
|
||||||
return common.ShouldUseNestedProcessor(data, model, h)
|
return common.ShouldUseNestedProcessor(data, model, h)
|
||||||
}
|
}
|
||||||
@ -1966,12 +1967,40 @@ func (h *Handler) getRelationshipInfo(modelType reflect.Type, relationName strin
|
|||||||
// Determine if it's belongsTo or hasMany/hasOne
|
// Determine if it's belongsTo or hasMany/hasOne
|
||||||
if field.Type.Kind() == reflect.Slice {
|
if field.Type.Kind() == reflect.Slice {
|
||||||
info.relationType = "hasMany"
|
info.relationType = "hasMany"
|
||||||
|
// Get the element type for slice
|
||||||
|
elemType := field.Type.Elem()
|
||||||
|
if elemType.Kind() == reflect.Ptr {
|
||||||
|
elemType = elemType.Elem()
|
||||||
|
}
|
||||||
|
if elemType.Kind() == reflect.Struct {
|
||||||
|
info.relatedModel = reflect.New(elemType).Elem().Interface()
|
||||||
|
}
|
||||||
} else if field.Type.Kind() == reflect.Ptr || field.Type.Kind() == reflect.Struct {
|
} else if field.Type.Kind() == reflect.Ptr || field.Type.Kind() == reflect.Struct {
|
||||||
info.relationType = "belongsTo"
|
info.relationType = "belongsTo"
|
||||||
|
elemType := field.Type
|
||||||
|
if elemType.Kind() == reflect.Ptr {
|
||||||
|
elemType = elemType.Elem()
|
||||||
|
}
|
||||||
|
if elemType.Kind() == reflect.Struct {
|
||||||
|
info.relatedModel = reflect.New(elemType).Elem().Interface()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if strings.Contains(gormTag, "many2many") {
|
} else if strings.Contains(gormTag, "many2many") {
|
||||||
info.relationType = "many2many"
|
info.relationType = "many2many"
|
||||||
info.joinTable = h.extractTagValue(gormTag, "many2many")
|
info.joinTable = h.extractTagValue(gormTag, "many2many")
|
||||||
|
// Get the element type for many2many (always slice)
|
||||||
|
if field.Type.Kind() == reflect.Slice {
|
||||||
|
elemType := field.Type.Elem()
|
||||||
|
if elemType.Kind() == reflect.Ptr {
|
||||||
|
elemType = elemType.Elem()
|
||||||
|
}
|
||||||
|
if elemType.Kind() == reflect.Struct {
|
||||||
|
info.relatedModel = reflect.New(elemType).Elem().Interface()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Field has no GORM relationship tags, so it's not a relation
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return info
|
return info
|
||||||
|
|||||||
@ -132,7 +132,7 @@ func TestShouldUseNestedProcessor(t *testing.T) {
|
|||||||
expected bool
|
expected bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Data with nested posts",
|
name: "Data with simple nested posts (no further nesting)",
|
||||||
data: map[string]interface{}{
|
data: map[string]interface{}{
|
||||||
"name": "John",
|
"name": "John",
|
||||||
"posts": []map[string]interface{}{
|
"posts": []map[string]interface{}{
|
||||||
@ -140,7 +140,23 @@ func TestShouldUseNestedProcessor(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
model: TestUser{},
|
model: TestUser{},
|
||||||
expected: true,
|
expected: false, // Simple one-level nesting doesn't require nested processor
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Data with deeply nested relations",
|
||||||
|
data: map[string]interface{}{
|
||||||
|
"name": "John",
|
||||||
|
"posts": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"title": "Post 1",
|
||||||
|
"comments": []map[string]interface{}{
|
||||||
|
{"content": "Comment 1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
model: TestUser{},
|
||||||
|
expected: true, // Multi-level nesting requires nested processor
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Data without nested relations",
|
name: "Data without nested relations",
|
||||||
@ -159,6 +175,20 @@ func TestShouldUseNestedProcessor(t *testing.T) {
|
|||||||
model: TestUser{},
|
model: TestUser{},
|
||||||
expected: true,
|
expected: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Nested data with _request field",
|
||||||
|
data: map[string]interface{}{
|
||||||
|
"name": "John",
|
||||||
|
"posts": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"_request": "insert",
|
||||||
|
"title": "Post 1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
model: TestUser{},
|
||||||
|
expected: true, // _request at nested level requires nested processor
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user