NetLife Guru

Open source Go packages for fast, maintainable web systems. Built with a documentation-first approach.

Product
OverviewGolang packagesNews
Documentation
DocumentationGo LoggerGo RouterGo DB Form
Company
OverviewContactNewsGitHub
Community / Support
Supportinfo@netlife.guru
© 2026 NetLife Guru. All rights reserved.
GitHubinfo@netlife.guru
NetLife GuruNetLife GuruNetLife Guru
NetLife GuruNetLife GuruNetLife Guru
OverviewDocumentationNewsSupportContact

Golang packages

About
ScanningMaps and RowsConvertersCacheErrors
MappingDynamic RowsEdge Cases
MapperAPI

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

APIPurpose
ScanStructRowsWithCacheKeyScan rows using a named scan-plan cache key
SetSchemaVersionSet a global schema version used by the named scan-plan cache
CurrentSchemaVersionReturn the current schema version
ClearNamedStructScanPlanCacheClear 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,
) error

Example:

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 users

and:

SELECT * FROM users

should 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 users

and:

SELECT id, email, created_at FROM users

should 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.

Converters

API reference for converting dynamic row values into common Go types.

Errors

API reference for mapper errors and recommended error handling patterns.

On this page

OverviewDefault Scan Plan CacheNamed Cache KeysComplete ExampleColumn Shape MattersSchema VersionClearing the Named CacheWhen to Use Named Cache KeysWhen Not to Use Named Cache KeysEmpty Cache KeyRecommended Usage