Cache
API reference for named scan-plan caching and repeated struct scanning.
Mapper builds a scan plan when scanning database rows into structs.
A scan plan describes how returned database columns map to struct fields. It lets mapper reuse field metadata instead of resolving struct mapping from scratch for every row.
For most applications, the default cache used by ScanStructRows is enough.
For hot paths, you can use a named cache key with ScanStructRowsWithCacheKey.
Overview
| API | Purpose |
|---|---|
ScanStructRowsWithCacheKey | Scan rows using a named scan-plan cache key |
SetSchemaVersion | Set a global schema version used by the named scan-plan cache |
CurrentSchemaVersion | Return the current schema version |
ClearNamedStructScanPlanCache | Clear all named scan-plan cache entries |
Default Scan Plan Cache
Regular struct scanning already uses an internal cache.
err := mapper.ScanStructRows[User](rows, func(user *User) error {
fmt.Println(user.Name)
return nil
})This default cache is based on:
- the target struct type
- the returned column list
For most use cases, this is all you need.
Named Cache Keys
Use ScanStructRowsWithCacheKey when the same query shape is scanned repeatedly and you want to provide a stable cache key.
func ScanStructRowsWithCacheKey[T any](
rows mapper.Rows,
cacheKey string,
each func(*T) error,
) errorExample:
err := mapper.ScanStructRowsWithCacheKey[User](
rows,
"users:list",
func(user *User) error {
fmt.Println(user.Name)
return nil
},
)
if err != nil {
return err
}The cache key should describe the query shape, not only the table name.
Good cache keys:
"users:list"
"users:active-list"
"users:by-role"
"reports:user-summary"Less useful cache keys:
"users"
"query"
"list"Complete Example
rows, err := db.Query(`
SELECT *
FROM users
WHERE active = ?
ORDER BY created_at DESC
`, true)
if err != nil {
return err
}
defer rows.Close()
err = mapper.ScanStructRowsWithCacheKey[User](
rows,
"users:active-list",
func(user *User) error {
fmt.Println(user.ID, user.Name)
return nil
},
)
if err != nil {
return err
}Column Shape Matters
The cache key should be used for a stable result shape.
This means the returned columns should stay the same for the same cache key.
SELECT id, name, email FROM usersand:
SELECT * FROM usersshould not use the same cache key.
Use different cache keys:
"users:basic-list"
"users:active-list"Mapper also checks the returned column signature internally. If the cached plan does not match the current columns, mapper rebuilds the plan for that key.
Schema Version
Named cache entries include the current schema version.
You can change the schema version when your application schema or query shapes change.
mapper.SetSchemaVersion("2026-05-22")You can read the current value with:
version := mapper.CurrentSchemaVersion()
fmt.Println(version)If an empty schema version is provided, mapper falls back to:
"default"Clearing the Named Cache
Use ClearNamedStructScanPlanCache to remove all named scan-plan cache entries.
mapper.ClearNamedStructScanPlanCache()This is useful when:
- tests need a clean cache state
- query shapes changed dynamically
- a long-running process reloads configuration
- you want to force scan-plan rebuilding
When to Use Named Cache Keys
Use ScanStructRowsWithCacheKey for repeated hot paths.
Good candidates:
- high-traffic list queries
- repeated background jobs
- recurring exports
- API endpoints with stable query shapes
- frequently used reporting queries
For normal application queries, prefer the simpler API:
users, err := mapper.ScanStructSlice[User](rows)or:
err := mapper.ScanStructRows[User](rows, func(user *User) error {
return processUser(user)
})When Not to Use Named Cache Keys
Avoid named cache keys when the selected columns change frequently.
For example:
SELECT id, name FROM usersand:
SELECT id, email, created_at FROM usersshould not share the same key.
Also avoid cache keys for one-off queries where readability is more important than repeated scan-plan reuse.
Empty Cache Key
If the cache key is empty, mapper falls back to the default scan-plan behavior.
err := mapper.ScanStructRowsWithCacheKey[User](
rows,
"",
func(user *User) error {
fmt.Println(user.Name)
return nil
},
)This behaves like:
err := mapper.ScanStructRows[User](rows, func(user *User) error {
fmt.Println(user.Name)
return nil
})Recommended Usage
Use the regular scanning functions first.
users, err := mapper.ScanStructSlice[User](rows)Add named cache keys only when a query is repeated often and has a stable result shape.
err := mapper.ScanStructRowsWithCacheKey[User](
rows,
"users:list",
func(user *User) error {
return processUser(user)
},
)Keep cache keys descriptive and stable.