mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-06-10 07:43:46 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4244e838b1 |
@@ -234,6 +234,8 @@ func (p *NestedCUDProcessor) injectForeignKeys(data map[string]interface{}, mode
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pkCol := reflection.GetPrimaryKeyName(reflect.New(modelType).Interface())
|
||||||
|
|
||||||
for parentKey, parentID := range parentIDs {
|
for parentKey, parentID := range parentIDs {
|
||||||
dbColNames := reflection.GetForeignKeyColumn(modelType, parentKey)
|
dbColNames := reflection.GetForeignKeyColumn(modelType, parentKey)
|
||||||
|
|
||||||
@@ -255,6 +257,9 @@ func (p *NestedCUDProcessor) injectForeignKeys(data map[string]interface{}, mode
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, dbColName := range dbColNames {
|
for _, dbColName := range dbColNames {
|
||||||
|
if pkCol != "" && strings.EqualFold(dbColName, pkCol) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if _, exists := data[dbColName]; !exists {
|
if _, exists := data[dbColName]; !exists {
|
||||||
logger.Debug("Injecting foreign key: %s = %v", dbColName, parentID)
|
logger.Debug("Injecting foreign key: %s = %v", dbColName, parentID)
|
||||||
data[dbColName] = parentID
|
data[dbColName] = parentID
|
||||||
|
|||||||
@@ -974,16 +974,21 @@ func GetRelationType(model interface{}, fieldName string) RelationType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetForeignKeyColumn returns the DB column names of the foreign key(s) that
|
// GetForeignKeyColumn returns the DB column names of the foreign key(s) that
|
||||||
// the relation field identified by parentKey owns on modelType. Composite keys
|
// relate parentKey to modelType. Composite keys (e.g. bun "join:a=b,join:c=d"
|
||||||
// (e.g. bun "join:a=b,join:c=d" or GORM "foreignKey:ColA,ColB") yield multiple
|
// or GORM "foreignKey:ColA,ColB") yield multiple entries. Returns nil when no
|
||||||
// entries. Returns nil when no tag is found (caller should fall back to
|
// tag is found (caller should fall back to convention).
|
||||||
// convention).
|
|
||||||
//
|
//
|
||||||
// It checks tags in priority order:
|
// Two lookup strategies are tried in order:
|
||||||
// 1. Bun join: tag — e.g. `bun:"rel:belongs-to,join:department_id=id"` → ["department_id"]
|
|
||||||
// 2. GORM foreignKey: tag — e.g. `gorm:"foreignKey:DepartmentID"` → [column of DepartmentID field]
|
|
||||||
//
|
//
|
||||||
// parentKey is matched case-insensitively against the field name and JSON tag.
|
// 1. Relation-field match: find a field whose name/json equals parentKey, then
|
||||||
|
// read its bun join: or GORM foreignKey: tag and return the local columns.
|
||||||
|
// e.g. parentKey="department", field `Department bun:"join:dept_id=id"` → ["dept_id"]
|
||||||
|
//
|
||||||
|
// 2. Join left-side scan: scan every bun join tag in the struct for pairs whose
|
||||||
|
// left side equals parentKey and return the right-side (child FK) columns.
|
||||||
|
// e.g. parentKey="rid_mastertaskitem", field `Children bun:"join:rid_mastertaskitem=rid_parentmastertaskitem"` → ["rid_parentmastertaskitem"]
|
||||||
|
// Strategy 1 is skipped if the matched field is a declared relation (rel:) or
|
||||||
|
// has a GORM tag but carries no explicit FK — callers should use convention.
|
||||||
func GetForeignKeyColumn(modelType reflect.Type, parentKey string) []string {
|
func GetForeignKeyColumn(modelType reflect.Type, parentKey string) []string {
|
||||||
for modelType.Kind() == reflect.Ptr || modelType.Kind() == reflect.Slice {
|
for modelType.Kind() == reflect.Ptr || modelType.Kind() == reflect.Slice {
|
||||||
modelType = modelType.Elem()
|
modelType = modelType.Elem()
|
||||||
@@ -992,6 +997,7 @@ func GetForeignKeyColumn(modelType reflect.Type, parentKey string) []string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Strategy 1: match parentKey against a field's name/json tag.
|
||||||
for i := 0; i < modelType.NumField(); i++ {
|
for i := 0; i < modelType.NumField(); i++ {
|
||||||
field := modelType.Field(i)
|
field := modelType.Field(i)
|
||||||
|
|
||||||
@@ -1001,9 +1007,11 @@ func GetForeignKeyColumn(modelType reflect.Type, parentKey string) []string {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bunTag := field.Tag.Get("bun")
|
||||||
|
|
||||||
// Bun: join:local_col=foreign_col (one join: part per pair)
|
// Bun: join:local_col=foreign_col (one join: part per pair)
|
||||||
var bunCols []string
|
var bunCols []string
|
||||||
for _, part := range strings.Split(field.Tag.Get("bun"), ",") {
|
for _, part := range strings.Split(bunTag, ",") {
|
||||||
part = strings.TrimSpace(part)
|
part = strings.TrimSpace(part)
|
||||||
if strings.HasPrefix(part, "join:") {
|
if strings.HasPrefix(part, "join:") {
|
||||||
pair := strings.TrimPrefix(part, "join:")
|
pair := strings.TrimPrefix(part, "join:")
|
||||||
@@ -1033,10 +1041,38 @@ func GetForeignKeyColumn(modelType reflect.Type, parentKey string) []string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The field matched by name/json but has no explicit FK tag. If it is a
|
||||||
|
// declared relation field (rel:) or carries a GORM tag, the caller should
|
||||||
|
// use naming convention — don't fall through to strategy 2. Otherwise the
|
||||||
|
// matched field is a plain scalar column; proceed to the join left-side scan.
|
||||||
|
if strings.Contains(bunTag, "rel:") || field.Tag.Get("gorm") != "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
// Strategy 2: scan every field's bun join tag for pairs whose left side (the
|
||||||
|
// parent's column) matches parentKey; the right side is the child FK column.
|
||||||
|
// This handles cases where parentKey is a raw column name rather than a
|
||||||
|
// relation field name (e.g. self-referential or has-many relationships).
|
||||||
|
seen := map[string]bool{}
|
||||||
|
var cols []string
|
||||||
|
for i := 0; i < modelType.NumField(); i++ {
|
||||||
|
for _, part := range strings.Split(modelType.Field(i).Tag.Get("bun"), ",") {
|
||||||
|
part = strings.TrimSpace(part)
|
||||||
|
if strings.HasPrefix(part, "join:") {
|
||||||
|
pair := strings.TrimPrefix(part, "join:")
|
||||||
|
if idx := strings.Index(pair, "="); idx > 0 {
|
||||||
|
left, right := pair[:idx], pair[idx+1:]
|
||||||
|
if strings.EqualFold(left, parentKey) && !seen[right] {
|
||||||
|
seen[right] = true
|
||||||
|
cols = append(cols, right)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cols // nil if empty
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRelationModel gets the model type for a relation field
|
// GetRelationModel gets the model type for a relation field
|
||||||
|
|||||||
@@ -37,6 +37,17 @@ type gormCompositeEmployee struct {
|
|||||||
Department *fkDept `gorm:"foreignKey:DeptID,TenantID" json:"department"`
|
Department *fkDept `gorm:"foreignKey:DeptID,TenantID" json:"department"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// selfRefItem mimics a self-referential model (like mastertaskitem) where the
|
||||||
|
// parent PK column appears as the left side of a has-many join tag.
|
||||||
|
type selfRefItem struct {
|
||||||
|
RidItem int32 `json:"rid_item" bun:"rid_item,type:integer,pk"`
|
||||||
|
RidParentItem int32 `json:"rid_parentitem" bun:"rid_parentitem,type:integer"`
|
||||||
|
// has-one (single parent pointer)
|
||||||
|
Parent *selfRefItem `json:"Parent,omitempty" bun:"rel:has-one,join:rid_item=rid_parentitem"`
|
||||||
|
// has-many (child collection) — same join, duplicate right-side must be deduped
|
||||||
|
Children []*selfRefItem `json:"Children,omitempty" bun:"rel:has-many,join:rid_item=rid_parentitem"`
|
||||||
|
}
|
||||||
|
|
||||||
// conventionEmployee has no explicit FK tag — relies on naming convention.
|
// conventionEmployee has no explicit FK tag — relies on naming convention.
|
||||||
type conventionEmployee struct {
|
type conventionEmployee struct {
|
||||||
DepartmentID string `json:"department_id"`
|
DepartmentID string `json:"department_id"`
|
||||||
@@ -101,6 +112,14 @@ func TestGetForeignKeyColumn(t *testing.T) {
|
|||||||
want: []string{"dept_id", "tenant_id"},
|
want: []string{"dept_id", "tenant_id"},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Join left-side scan (parentKey is a raw column name, not a relation field name)
|
||||||
|
{
|
||||||
|
name: "self-referential: parent PK column returns child FK column",
|
||||||
|
modelType: reflect.TypeOf(selfRefItem{}),
|
||||||
|
parentKey: "rid_item",
|
||||||
|
want: []string{"rid_parentitem"},
|
||||||
|
},
|
||||||
|
|
||||||
// Pointer and slice unwrapping
|
// Pointer and slice unwrapping
|
||||||
{
|
{
|
||||||
name: "pointer to struct is unwrapped",
|
name: "pointer to struct is unwrapped",
|
||||||
|
|||||||
Reference in New Issue
Block a user