mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2025-12-13 17:10:36 +00:00
6.4 KiB
6.4 KiB
Automatic Relation Loading Strategies
Overview
NEW: The database adapters now automatically choose the optimal loading strategy by inspecting your model's relationship tags!
Simply use PreloadRelation() and the system automatically:
- Detects relationship type from Bun/GORM tags
- Uses JOIN for many-to-one and one-to-one (efficient, no duplication)
- Uses separate query for one-to-many and many-to-many (avoids duplication)
How It Works
// Just write this - the system handles the rest!
db.NewSelect().
Model(&links).
PreloadRelation("Provider"). // ✓ Auto-detects belongs-to → uses JOIN
PreloadRelation("Tags"). // ✓ Auto-detects has-many → uses separate query
Scan(ctx, &links)
Detection Logic
The system inspects your model's struct tags:
Bun models:
type Link struct {
Provider *Provider `bun:"rel:belongs-to"` // → Detected: belongs-to → JOIN
Tags []Tag `bun:"rel:has-many"` // → Detected: has-many → Separate query
}
GORM models:
type Link struct {
ProviderID int
Provider *Provider `gorm:"foreignKey:ProviderID"` // → Detected: belongs-to → JOIN
Tags []Tag `gorm:"many2many:link_tags"` // → Detected: many-to-many → Separate query
}
Type inference (fallback):
[]Type(slice) → has-many → Separate query*Type(pointer) → belongs-to → JOINType(struct) → belongs-to → JOIN
What Gets Logged
Enable debug logging to see strategy selection:
bunAdapter.EnableQueryDebug()
Output:
DEBUG: PreloadRelation 'Provider' detected as: belongs-to
INFO: Using JOIN strategy for belongs-to relation 'Provider'
DEBUG: PreloadRelation 'Links' detected as: has-many
DEBUG: Using separate query for has-many relation 'Links'
Relationship Types
| Bun Tag | GORM Pattern | Field Type | Strategy | Why |
|---|---|---|---|---|
rel:has-many |
Slice field | []Type |
Separate Query | Avoids duplicating parent data |
rel:belongs-to |
foreignKey: |
*Type |
JOIN | Single parent, no duplication |
rel:has-one |
Single pointer | *Type |
JOIN | One-to-one, no duplication |
rel:many-to-many |
many2many: |
[]Type |
Separate Query | Complex join, avoid cartesian |
Manual Override
If you need to force a specific strategy, use JoinRelation():
// Force JOIN even for has-many (not recommended)
db.NewSelect().
Model(&providers).
JoinRelation("Links"). // Explicitly use JOIN
Scan(ctx, &providers)
Examples
Automatic Strategy Selection (Recommended)
// Example 1: Loading parent provider for each link
// System detects belongs-to → uses JOIN automatically
db.NewSelect().
Model(&links).
PreloadRelation("Provider", func(q common.SelectQuery) common.SelectQuery {
return q.Where("active = ?", true)
}).
Scan(ctx, &links)
// Generated SQL: Single query with JOIN
// SELECT links.*, providers.*
// FROM links
// LEFT JOIN providers ON links.provider_id = providers.id
// WHERE providers.active = true
// Example 2: Loading child links for each provider
// System detects has-many → uses separate query automatically
db.NewSelect().
Model(&providers).
PreloadRelation("Links", func(q common.SelectQuery) common.SelectQuery {
return q.Where("active = ?", true)
}).
Scan(ctx, &providers)
// Generated SQL: Two queries
// Query 1: SELECT * FROM providers
// Query 2: SELECT * FROM links
// WHERE provider_id IN (1, 2, 3, ...)
// AND active = true
Mixed Relationships
type Order struct {
ID int
CustomerID int
Customer *Customer `bun:"rel:belongs-to"` // JOIN
Items []Item `bun:"rel:has-many"` // Separate
Invoice *Invoice `bun:"rel:has-one"` // JOIN
}
// All three handled optimally!
db.NewSelect().
Model(&orders).
PreloadRelation("Customer"). // → JOIN (many-to-one)
PreloadRelation("Items"). // → Separate (one-to-many)
PreloadRelation("Invoice"). // → JOIN (one-to-one)
Scan(ctx, &orders)
Performance Benefits
Before (Manual Strategy Selection)
// You had to remember which to use:
.PreloadRelation("Provider") // Should I use PreloadRelation or JoinRelation?
.PreloadRelation("Links") // Which is more efficient here?
After (Automatic Selection)
// Just use PreloadRelation everywhere:
.PreloadRelation("Provider") // ✓ System uses JOIN automatically
.PreloadRelation("Links") // ✓ System uses separate query automatically
Migration Guide
No changes needed! If you're already using PreloadRelation(), it now automatically optimizes:
// Before: Always used separate query
.PreloadRelation("Provider") // Inefficient: extra round trip
// After: Automatic optimization
.PreloadRelation("Provider") // ✓ Now uses JOIN automatically!
Implementation Details
Supported Bun Tags
rel:has-many→ Separate queryrel:belongs-to→ JOINrel:has-one→ JOINrel:many-to-manyorrel:m2m→ Separate query
Supported GORM Patterns
many2many:tag → Separate queryforeignKey:tag → JOIN (belongs-to)[]Typeslice without many2many → Separate query (has-many)*Typepointer with foreignKey → JOIN (belongs-to)*Typepointer without foreignKey → JOIN (has-one)
Fallback Behavior
[]Type(slice) → Separate query (safe default for collections)*TypeorType(single) → JOIN (safe default for single relations)- Unknown → Separate query (safest default)
Debugging
To see strategy selection in action:
// Enable debug logging
bunAdapter.EnableQueryDebug() // or gormAdapter.EnableQueryDebug()
// Run your query
db.NewSelect().
Model(&records).
PreloadRelation("RelationName").
Scan(ctx, &records)
// Check logs for:
// - "PreloadRelation 'X' detected as: belongs-to"
// - "Using JOIN strategy for belongs-to relation 'X'"
// - Actual SQL queries executed
Best Practices
- Use PreloadRelation() for everything - Let the system optimize
- Define proper relationship tags - Ensures correct detection
- Only use JoinRelation() for overrides - When you know better than auto-detection
- Enable debug logging during development - Verify optimal strategies are chosen
- Trust the system - It's designed to choose correctly based on relationship type