Skip to content

Commit 37b4b75

Browse files
author
Hein
committed
Fixed preload and id fields with GetPrimaryKeyName
1 parent 0cef0f7 commit 37b4b75

File tree

10 files changed

+380
-33
lines changed

10 files changed

+380
-33
lines changed

.golangci.bck.yml

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
run:
2+
timeout: 5m
3+
tests: true
4+
skip-dirs:
5+
- vendor
6+
- .github
7+
8+
linters:
9+
enable:
10+
- errcheck
11+
- gosimple
12+
- govet
13+
- ineffassign
14+
- staticcheck
15+
- unused
16+
- gofmt
17+
- goimports
18+
- misspell
19+
- gocritic
20+
- revive
21+
- stylecheck
22+
disable:
23+
- typecheck # Can cause issues with generics in some cases
24+
25+
linters-settings:
26+
errcheck:
27+
check-type-assertions: false
28+
check-blank: false
29+
30+
govet:
31+
check-shadowing: false
32+
33+
gofmt:
34+
simplify: true
35+
36+
goimports:
37+
local-prefixes: github.com/bitechdev/ResolveSpec
38+
39+
gocritic:
40+
enabled-checks:
41+
- appendAssign
42+
- assignOp
43+
- boolExprSimplify
44+
- builtinShadow
45+
- captLocal
46+
- caseOrder
47+
- defaultCaseOrder
48+
- dupArg
49+
- dupBranchBody
50+
- dupCase
51+
- dupSubExpr
52+
- elseif
53+
- emptyFallthrough
54+
- equalFold
55+
- flagName
56+
- ifElseChain
57+
- indexAlloc
58+
- initClause
59+
- methodExprCall
60+
- nilValReturn
61+
- rangeExprCopy
62+
- rangeValCopy
63+
- regexpMust
64+
- singleCaseSwitch
65+
- sloppyLen
66+
- stringXbytes
67+
- switchTrue
68+
- typeAssertChain
69+
- typeSwitchVar
70+
- underef
71+
- unlabelStmt
72+
- unnamedResult
73+
- unnecessaryBlock
74+
- weakCond
75+
- yodaStyleExpr
76+
77+
revive:
78+
rules:
79+
- name: exported
80+
disabled: true
81+
- name: package-comments
82+
disabled: true
83+
84+
issues:
85+
exclude-use-default: false
86+
max-issues-per-linter: 0
87+
max-same-issues: 0
88+
89+
# Exclude some linters from running on tests files
90+
exclude-rules:
91+
- path: _test\.go
92+
linters:
93+
- errcheck
94+
- dupl
95+
- gosec
96+
- gocritic
97+
98+
# Ignore "error return value not checked" for defer statements
99+
- linters:
100+
- errcheck
101+
text: "Error return value of .((os\\.)?std(out|err)\\..*|.*Close|.*Flush|os\\.Remove(All)?|.*print(f|ln)?|os\\.(Un)?Setenv). is not checked"
102+
103+
# Ignore complexity in test files
104+
- path: _test\.go
105+
text: "cognitive complexity|cyclomatic complexity"
106+
107+
output:
108+
format: colored-line-number
109+
print-issued-lines: true
110+
print-linter-name: true

pkg/common/adapters/database/bun.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,40 @@ func (b *BunSelectQuery) Preload(relation string, conditions ...interface{}) com
215215
return b
216216
}
217217

218+
func (b *BunSelectQuery) PreloadRelation(relation string, apply ...func(common.SelectQuery) common.SelectQuery) common.SelectQuery {
219+
b.query = b.query.Relation(relation, func(sq *bun.SelectQuery) *bun.SelectQuery {
220+
if len(apply) == 0 {
221+
return sq
222+
}
223+
224+
// Wrap the incoming *bun.SelectQuery in our adapter
225+
wrapper := &BunSelectQuery{
226+
query: sq,
227+
db: b.db,
228+
}
229+
230+
// Start with the interface value (not pointer)
231+
current := common.SelectQuery(wrapper)
232+
233+
// Apply each function in sequence
234+
for _, fn := range apply {
235+
if fn != nil {
236+
// Pass &current (pointer to interface variable), fn modifies and returns new interface value
237+
modified := fn(current)
238+
current = modified
239+
}
240+
}
241+
242+
// Extract the final *bun.SelectQuery
243+
if finalBun, ok := current.(*BunSelectQuery); ok {
244+
return finalBun.query
245+
}
246+
247+
return sq // fallback
248+
})
249+
return b
250+
}
251+
218252
func (b *BunSelectQuery) Order(order string) common.SelectQuery {
219253
b.query = b.query.Order(order)
220254
return b

pkg/common/adapters/database/gorm.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,36 @@ func (g *GormSelectQuery) Preload(relation string, conditions ...interface{}) co
197197
return g
198198
}
199199

200+
func (g *GormSelectQuery) PreloadRelation(relation string, apply ...func(common.SelectQuery) common.SelectQuery) common.SelectQuery {
201+
g.db = g.db.Preload(relation, func(db *gorm.DB) *gorm.DB {
202+
if len(apply) == 0 {
203+
return db
204+
}
205+
206+
wrapper := &GormSelectQuery{
207+
db: g.db,
208+
}
209+
210+
current := common.SelectQuery(wrapper)
211+
212+
for _, fn := range apply {
213+
if fn != nil {
214+
215+
modified := fn(current)
216+
current = modified
217+
}
218+
}
219+
220+
if finalBun, ok := current.(*GormSelectQuery); ok {
221+
return finalBun.db
222+
}
223+
224+
return db // fallback
225+
})
226+
227+
return g
228+
}
229+
200230
func (g *GormSelectQuery) Order(order string) common.SelectQuery {
201231
g.db = g.db.Order(order)
202232
return g

pkg/common/interfaces.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type SelectQuery interface {
3232
Join(query string, args ...interface{}) SelectQuery
3333
LeftJoin(query string, args ...interface{}) SelectQuery
3434
Preload(relation string, conditions ...interface{}) SelectQuery
35+
PreloadRelation(relation string, apply ...func(SelectQuery) SelectQuery) SelectQuery
3536
Order(order string) SelectQuery
3637
Limit(n int) SelectQuery
3738
Offset(n int) SelectQuery

pkg/common/recursive_crud.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"strings"
88

99
"github.com/bitechdev/ResolveSpec/pkg/logger"
10+
"github.com/bitechdev/ResolveSpec/pkg/reflection"
1011
)
1112

1213
// CRUDRequestProvider interface for models that provide CRUD request strings
@@ -248,7 +249,7 @@ func (p *NestedCUDProcessor) processUpdate(
248249

249250
logger.Debug("Updating %s with ID %v, data: %+v", tableName, id, data)
250251

251-
query := p.db.NewUpdate().Table(tableName).SetMap(data).Where("id = ?", id)
252+
query := p.db.NewUpdate().Table(tableName).SetMap(data).Where(fmt.Sprintf("%s = ?", QuoteIdent(reflection.GetPrimaryKeyName(tableName))), id)
252253

253254
result, err := query.Exec(ctx)
254255
if err != nil {
@@ -268,7 +269,7 @@ func (p *NestedCUDProcessor) processDelete(ctx context.Context, tableName string
268269

269270
logger.Debug("Deleting from %s with ID %v", tableName, id)
270271

271-
query := p.db.NewDelete().Table(tableName).Where("id = ?", id)
272+
query := p.db.NewDelete().Table(tableName).Where(fmt.Sprintf("%s = ?", QuoteIdent(reflection.GetPrimaryKeyName(tableName))), id)
272273

273274
result, err := query.Exec(ctx)
274275
if err != nil {

pkg/common/types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ type PreloadOption struct {
3535
Relation string `json:"relation"`
3636
Columns []string `json:"columns"`
3737
OmitColumns []string `json:"omit_columns"`
38+
Sort []SortOption `json:"sort"`
3839
Filters []FilterOption `json:"filters"`
40+
Where string `json:"where"`
3941
Limit *int `json:"limit"`
4042
Offset *int `json:"offset"`
4143
Updatable *bool `json:"updateable"` // if true, the relation can be updated

pkg/common/validation.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,3 +270,11 @@ func (v *ColumnValidator) GetValidColumns() []string {
270270
}
271271
return columns
272272
}
273+
274+
func QuoteIdent(qualifier string) string {
275+
return `"` + strings.ReplaceAll(qualifier, `"`, `""`) + `"`
276+
}
277+
278+
func QuoteLiteral(value string) string {
279+
return `'` + strings.ReplaceAll(value, `'`, `''`) + `'`
280+
}

pkg/reflection/model_utils.go

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,31 @@ import (
44
"reflect"
55
"strings"
66

7-
"github.com/bitechdev/ResolveSpec/pkg/common"
7+
"github.com/bitechdev/ResolveSpec/pkg/modelregistry"
88
)
99

10+
type PrimaryKeyNameProvider interface {
11+
GetIDName() string
12+
}
13+
1014
// GetPrimaryKeyName extracts the primary key column name from a model
1115
// It first checks if the model implements PrimaryKeyNameProvider (GetIDName method)
1216
// Falls back to reflection to find bun:",pk" tag, then gorm:"primaryKey" tag
1317
func GetPrimaryKeyName(model any) string {
18+
if reflect.TypeOf(model) == nil {
19+
return ""
20+
}
21+
//If we are given a string model name, look up the model
22+
if reflect.TypeOf(model).Kind() == reflect.String {
23+
name := model.(string)
24+
m, err := modelregistry.GetModelByName(name)
25+
if err == nil {
26+
model = m
27+
}
28+
}
29+
1430
// Check if model implements PrimaryKeyNameProvider
15-
if provider, ok := model.(common.PrimaryKeyNameProvider); ok {
31+
if provider, ok := model.(PrimaryKeyNameProvider); ok {
1632
return provider.GetIDName()
1733
}
1834

@@ -22,7 +38,11 @@ func GetPrimaryKeyName(model any) string {
2238
}
2339

2440
// Fall back to GORM tag
25-
return getPrimaryKeyFromReflection(model, "gorm")
41+
if pkName := getPrimaryKeyFromReflection(model, "gorm"); pkName != "" {
42+
return pkName
43+
}
44+
45+
return ""
2646
}
2747

2848
// GetModelColumns extracts all column names from a model using reflection

0 commit comments

Comments
 (0)