fix(reflection): update GetForeignKeyColumn to return multiple columns

* Change return type to []string for composite keys
* Adjust related logic in injectForeignKeys method
* Update tests to validate new behavior for composite foreign keys
This commit is contained in:
Hein
2026-05-18 12:39:06 +02:00
parent 85bb0f7874
commit c42fa11c1a
3 changed files with 74 additions and 38 deletions

View File

@@ -15,6 +15,13 @@ type bunEmployee struct {
Department *fkDept `bun:"rel:belongs-to,join:dept_id=id" json:"department"`
}
// bunCompositeEmployee has a composite bun join: (two join: parts).
type bunCompositeEmployee struct {
DeptID string `bun:"dept_id" json:"dept_id"`
TenantID string `bun:"tenant_id" json:"tenant_id"`
Department *fkDept `bun:"rel:belongs-to,join:dept_id=id,join:tenant_id=id" json:"department"`
}
// gormEmployee uses gorm foreignKey: tag (mirrors testmodels.Employee).
type gormEmployee struct {
DepartmentID string `json:"department_id"`
@@ -23,6 +30,13 @@ type gormEmployee struct {
Manager *fkDept `gorm:"foreignKey:ManagerID;references:ID" json:"manager"`
}
// gormCompositeEmployee has a composite GORM foreignKey.
type gormCompositeEmployee struct {
DeptID string `json:"dept_id"`
TenantID string `json:"tenant_id"`
Department *fkDept `gorm:"foreignKey:DeptID,TenantID" json:"department"`
}
// conventionEmployee has no explicit FK tag — relies on naming convention.
type conventionEmployee struct {
DepartmentID string `json:"department_id"`
@@ -39,20 +53,26 @@ func TestGetForeignKeyColumn(t *testing.T) {
name string
modelType reflect.Type
parentKey string
want string
want []string
}{
// Bun join: tag
{
name: "bun join tag returns local column",
modelType: reflect.TypeOf(bunEmployee{}),
parentKey: "department",
want: "dept_id",
want: []string{"dept_id"},
},
{
name: "bun join tag matched via json tag (case-insensitive)",
modelType: reflect.TypeOf(bunEmployee{}),
parentKey: "Department",
want: "dept_id",
want: []string{"dept_id"},
},
{
name: "bun composite join returns all local columns",
modelType: reflect.TypeOf(bunCompositeEmployee{}),
parentKey: "department",
want: []string{"dept_id", "tenant_id"},
},
// GORM foreignKey: tag
@@ -60,19 +80,25 @@ func TestGetForeignKeyColumn(t *testing.T) {
name: "gorm foreignKey resolves to column name",
modelType: reflect.TypeOf(gormEmployee{}),
parentKey: "department",
want: "department_id",
want: []string{"department_id"},
},
{
name: "gorm foreignKey resolves second relation",
modelType: reflect.TypeOf(gormEmployee{}),
parentKey: "manager",
want: "manager_id",
want: []string{"manager_id"},
},
{
name: "gorm foreignKey matched case-insensitively",
modelType: reflect.TypeOf(gormEmployee{}),
parentKey: "Department",
want: "department_id",
want: []string{"department_id"},
},
{
name: "gorm composite foreignKey returns all columns",
modelType: reflect.TypeOf(gormCompositeEmployee{}),
parentKey: "department",
want: []string{"dept_id", "tenant_id"},
},
// Pointer and slice unwrapping
@@ -80,43 +106,43 @@ func TestGetForeignKeyColumn(t *testing.T) {
name: "pointer to struct is unwrapped",
modelType: reflect.TypeOf(&gormEmployee{}),
parentKey: "department",
want: "department_id",
want: []string{"department_id"},
},
{
name: "slice of struct is unwrapped",
modelType: reflect.TypeOf([]gormEmployee{}),
parentKey: "department",
want: "department_id",
want: []string{"department_id"},
},
// No tag — returns "" so caller can fall back to convention
// No tag — returns nil so caller can fall back to convention
{
name: "relation with no FK tag returns empty string",
name: "relation with no FK tag returns nil",
modelType: reflect.TypeOf(conventionEmployee{}),
parentKey: "department",
want: "",
want: nil,
},
// Unknown parent key
{
name: "unknown parent key returns empty string",
name: "unknown parent key returns nil",
modelType: reflect.TypeOf(gormEmployee{}),
parentKey: "nonexistent",
want: "",
want: nil,
},
{
name: "non-struct type returns empty string",
name: "non-struct type returns nil",
modelType: reflect.TypeOf(""),
parentKey: "department",
want: "",
want: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := GetForeignKeyColumn(tt.modelType, tt.parentKey)
if got != tt.want {
t.Errorf("GetForeignKeyColumn(%v, %q) = %q, want %q", tt.modelType, tt.parentKey, got, tt.want)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetForeignKeyColumn(%v, %q) = %v, want %v", tt.modelType, tt.parentKey, got, tt.want)
}
})
}