sqldb: add ExecutePagedQuery helper

Along with a test for it. This helper will allow us to easily create a
pagination wrapper for queries that will make use of the new
/*SLICE:<field_name>*/ directive. The next commit will add a test
showing this.
This commit is contained in:
Elle Mouton
2025-07-16 08:27:44 +02:00
parent 5afd9a5678
commit 006905d57f
2 changed files with 312 additions and 0 deletions

76
sqldb/paginate.go Normal file
View File

@@ -0,0 +1,76 @@
package sqldb
import (
"context"
"fmt"
)
// PagedQueryFunc represents a function that takes a slice of converted items
// and returns results.
type PagedQueryFunc[T any, R any] func(context.Context, []T) ([]R, error)
// ItemCallbackFunc represents a function that processes individual results.
type ItemCallbackFunc[R any] func(context.Context, R) error
// ConvertFunc represents a function that converts from input type to query type
type ConvertFunc[I any, T any] func(I) T
// PagedQueryConfig holds configuration values for calls to ExecutePagedQuery.
type PagedQueryConfig struct {
PageSize int
}
// DefaultPagedQueryConfig returns a default configuration
func DefaultPagedQueryConfig() *PagedQueryConfig {
return &PagedQueryConfig{
PageSize: 1000,
}
}
// ExecutePagedQuery executes a paginated query over a slice of input items.
// It converts the input items to a query type using the provided convertFunc,
// executes the query using the provided queryFunc, and applies the callback
// to each result.
func ExecutePagedQuery[I any, T any, R any](ctx context.Context,
cfg *PagedQueryConfig, inputItems []I, convertFunc ConvertFunc[I, T],
queryFunc PagedQueryFunc[T, R], callback ItemCallbackFunc[R]) error {
if len(inputItems) == 0 {
return nil
}
// Process items in pages.
for i := 0; i < len(inputItems); i += cfg.PageSize {
// Calculate the end index for this page.
end := i + cfg.PageSize
if end > len(inputItems) {
end = len(inputItems)
}
// Get the page slice of input items.
inputPage := inputItems[i:end]
// Convert only the items needed for this page.
convertedPage := make([]T, len(inputPage))
for j, inputItem := range inputPage {
convertedPage[j] = convertFunc(inputItem)
}
// Execute the query for this page.
results, err := queryFunc(ctx, convertedPage)
if err != nil {
return fmt.Errorf("query failed for page "+
"starting at %d: %w", i, err)
}
// Apply the callback to each result.
for _, result := range results {
if err := callback(ctx, result); err != nil {
return fmt.Errorf("callback failed for "+
"result: %w", err)
}
}
}
return nil
}