scan

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 27, 2022 License: MIT Imports: 10 Imported by: 10

README

Scan

Test Status GitHub go.mod Go version Go Reference Go Report Card GitHub tag (latest SemVer) Coverage Status

Scan provides the ability to use database/sql/rows to scan datasets directly to any defined structure.

Reference

  • Standard library scan package. For use with database/sql. Link
  • PGX library scan package. For use with github.com/jackc/pgx/v5. Link
  • Base scan package. For use with any implementation of scan.Queryer. Link

Using with database/sql

package main

import (
    "context"
    "database/sql"

    "github.com/stephenafamo/scan"
    "github.com/stephenafamo/scan/stdscan"
)

type User struct {
    ID    int
    Name  string
    Email string
    Age   int
}

func main() {
    ctx := context.Background()
    db, _ := sql.Open("postgres", "example-connection-url")

    // count: 5 
    count, _ := stdscan.One(ctx, db, scan.SingleColumnMapper[int], "SELECT COUNT(*) FROM users")
    // []int{1, 2, 3, 4, 5}
    userIDs, _ := stdscan.All(ctx, db, scan.SingleColumnMapper[int], "SELECT id FROM users")
    // []User{...}
    users, _ := stdscan.All(ctx, db, scan.StructMapper[User](), `SELECT id, name, email, age FROM users`)

    // []any{
    //     []int{1, 2, 3, 4, 5},
    //     []string{"user1@example.com", "user2@example.com", ...},
    // }
    idsAndEmail, _ := stdscan.Collect(ctx, db, collectIDandEmail, `SELECT id, email FROM users`)
}

func collectIDandEmail(_ context.Context, c cols) any {
    return func(v *Values) (int, string, error) {
        return Value[int](v, "id"), Value[string](v, "email"), nil
    }
}

And many more!!

Using with pgx

ctx := context.Background()
db, _ := pgxpool.New(ctx, "example-connection-url")

// []User{...}
users, _ := pgxscan.All(ctx, db, scan.StructMapper[User](), `SELECT id, name, email, age FROM users`)

Using with other DB packages

Instead of github.com/stephenafamo/scan/stdscan, use the base package github.com/stephenafam/scan which only needs an executor that implements the right interface.
Both stdscan and pgxscan are based on this.

How it works

Scanning Functions
One()

Use One() to scan and return a single row.

// User{...}
user, _ := stdscan.One(ctx, db, scan.StructMapper[User](), `SELECT id, name, email, age FROM users`)
All()

Use All() to scan and return all rows.

// []User{...}
users, _ := stdscan.All(ctx, db, scan.StructMapper[User](), `SELECT id, name, email, age FROM users`)
Cursor()

Use Cursor() to scan each row on demand. This is useful when retrieving large results.

c, _ := stdscan.Cursor(ctx, db, scan.StructMapper[User](), `SELECT id, name, email, age FROM users`)
defer c.Close()

for c.Next() {
    // User{...}
    user := c.Get()
}
Collect

Use Collect() to group returned columns together. For example, getting a slice of all IDs and a slice of user emails.

func collect(v *scan.Value) (int, string, error) {
    return scan.Value[int](v, "id"), scan.Value[string](v, "email"), nil
}

// []User{...}
idsAndEmails, _ := stdscan.Collect(ctx, db, scan.StructMapper[User](), `SELECT id, email FROM users`)

// []int{1, 2, 3, ...}
ids := idsAndEmail[0].([]int)

// []string{"user1@example.com", "user2@example.com", "user3@example.com", ...}
emails := idsAndEmail[1].([]string)
Mappers

Each of these functions takes a Mapper to indicate how each row should be scanned.
The Mapper has the signature:

type Mapper[T any] func(context.Context, cols) func(*Values) (T, error)

Any function that has this signature can be used as a Mapper. There are some builtin mappers for common cases:

ColumnMapper[T any](name string)

Maps the value of a single column to the given type. The name of the column must be specified

// []string{"user1@example.com", "user2@example.com", "user3@example.com", ...}
emails, _ := stdscan.All(ctx, db, scan.ColumnMapper[string]("email"), `SELECT id, name, email FROM users`)
SingleColumnMapper[T any]

For queries that return only one column. Since only one column is returned, there is no need to specify the column name.
This is why it throws an error if the query returns more than one column.

// []string{"user1@example.com", "user2@example.com", "user3@example.com", ...}
emails, _ := stdscan.All(ctx, db, scan.SingleColumnMapper[string], `SELECT email FROM users`)
SliceMapper[T any]

Maps a row into a slice of values []T. Unless all the columns are of the same type, it will likely be used to map the row to []any.

// [][]any{
//    []any{1, "John Doe", "john@example.com"},
//    []any{2, "Jane Doe", "jane@example.com"},
//    ...
// }
users, _ := stdscan.All(ctx, db, scan.SliceMapper[any], `SELECT id, name, email FROM users`)
MapMapper[T any]

Maps a row into a map of values map[string]T. The key of the map is the column names. Unless all columns are of the same type, it will likely be used to map to map[string]any.

// []map[string]any{
//    map[string]any{"id": 1, "name": John Doe", "email": "john@example.com"},
//    map[string]any{"id": 2, "name": Jane Doe", "email": "jane@example.com"},
//    ...
// }
users, _ := stdscan.All(ctx, db, scan.MapMapper[any], `SELECT id, name, email FROM users`)
StructMapper[T any](...MappingOption)

This is the most advanced mapper. Scans column values into the fields of the struct.

type User struct {
    ID    int    `db:"id"`
    Name  string `db:"name"`
    Email string `db:"email"`
    Age   int    `db:"age"`
}

// []User{...}
users, _ := stdscan.All(ctx, db, scan.StructMapper[User](), `SELECT id, name, email, age FROM users`)

The default behaviour of StructMapper is often good enough. For more advanced use cases, some options can be passed to the StructMapper.

  • WithStructTagPrefix: Use this when every column from the database has a prefix.

    users, _ := stdscan.All(ctx, db, scan.StructMapper[User](scan.WithStructTagPrefix("user-")),
        `SELECT id AS "user-id", name AS "user-name" FROM users`,
    )
    
  • WithRowValidator: If the StructMapper has a row validator, the values will be sent to it before scanning. If the row is invalid (i.e. it returns false), then scanning is skipped and the zero value of the row-type is returned.

CustomStructMapper[T any](MapperSource, ...MappingOption)

Uses a custom struct maping source which should have been created with NewStructMapperSource.

This works the same way as StructMapper, but instead of using the default mapping source, it uses a custom one.

In the example below, we want to use a scan as the struct tag key instead of db

type User struct {
    ID    int    `scan:"id"`
    Name  string `scan:"name"`
    Email string `scan:"email"`
    Age   int    `scan:"age"`
}

src, _ := NewStructMapperSource(scan.WithStructTagKey("scan"))
// []User{...}
users, _ := stdscan.All(ctx, db, scan.StructMapper[User](), `SELECT id, name, email, age FROM users`)

These are the options that can be passed to NewStructMapperSource:

  • WithStructTagKey: Change the struct tag used to map columns to struct fields. Default: db
  • WithColumnSeparator: Change the separator for column names of nested struct fields. Default: .
  • WithFieldNameMapper: Change how Struct field names are mapped to column names when there are no struct tags. Default: snake_case (i.e. CreatedAt is mapped to created_at).
  • WithScannableTypes: Pass a list of interfaces that if implemented, can be scanned by the executor. This means that a field with this type is treated as a single value and will not check the nested fields. Default: *sql.Scanner.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrBadCollectorReturn   = errors.New("collector does not return a function")
	ErrBadCollectFuncInput  = errors.New("collect func must only take *Values as input")
	ErrBadCollectFuncOutput = errors.New("collect func must return at least 2 values with the last being an error")
)
View Source
var CtxKeyAllowUnknownColumns contextKey = "allow unknown columns"

CtxKeyAllowUnknownColumns makes it possible to allow unknown columns using the context

Functions

func All

func All[T any](ctx context.Context, exec Queryer, m Mapper[T], query string, args ...any) ([]T, error)

All scans all rows from the query and returns a slice []T of all rows using a Queryer

func Collect

func Collect(ctx context.Context, exec Queryer, collector func(context.Context, cols) any, query string, args ...any) ([]any, error)

Collect multiple slices of values from a single query collector must be of the structure func(context.Context, map[string]int) func(*Values) (t1, t2, ..., error) The returned slice contains values like this {[]t1, []t2}

func ColumnMapper

func ColumnMapper[T any](name string) func(context.Context, cols) func(*Values) (T, error)

Map a column by name.

func MapMapper

func MapMapper[T any](ctx context.Context, c cols) func(*Values) (map[string]T, error)

Maps all rows into map[string]T Most likely used with interface{} to get a map[string]interface{}

func NewStructMapperSource

func NewStructMapperSource(opts ...MappingSourceOption) (mapperSourceImpl, error)

NewStructMapperSource creates a new Mapping object with provided list of options.

func One

func One[T any](ctx context.Context, exec Queryer, m Mapper[T], query string, args ...any) (T, error)

One scans a single row from the query and maps it to T using a Queryer

func ReflectedValue

func ReflectedValue(v *Values, name string, typ reflect.Type) reflect.Value

ReflectedValue returns the named value as an reflect.Value When not recording, it will panic if the requested type does not match what was recorded

func SingleColumnMapper

func SingleColumnMapper[T any](ctx context.Context, c cols) func(*Values) (T, error)

For queries that return only one column throws an error if there is more than one column

func SliceMapper

func SliceMapper[T any](ctx context.Context, c cols) func(*Values) ([]T, error)

Maps each row into []any in the order

func Value

func Value[T any](v *Values, name string) T

Value retrieves a value from Values with the specified name if Values.IsRecording returns true, it will ALWAYS return the zero value of that type When not recording, it will panic if the requested type does not match what was recorded

func ValueCallback

func ValueCallback(v *Values, name string, f func() reflect.Value) reflect.Value

ValueCallback passes a function that will be called for the column this is meant to be used when you need to control the exact value that the column is mapped into.

Types

type ICursor

type ICursor[T any] interface {
	// Close the underlying rows
	Close() error
	// Prepare the next row
	Next() bool
	// Get the values of the current row
	Get() (T, error)
	// Return any error with the underlying rows
	Err() error
}

func Cursor

func Cursor[T any](ctx context.Context, exec Queryer, m Mapper[T], query string, args ...any) (ICursor[T], error)

Cursor returns a cursor that works similar to *sql.Rows

type Mappable

type Mappable[T any] interface {
	MapValues(cols) func(context.Context, *Values) (T, error)
}

Mappable is an interface of a type that can map its own values if a struct implement IMapper, using StructMapper to map its values will use the MapValues method instead

type Mapper

type Mapper[T any] func(context.Context, cols) func(*Values) (T, error)

Mapper is a function that return the mapping function. Any expensive operation, like reflection should be done outside the returned function. It is called with the columns from the query to get the mapping function which is then used to map every row.

The generator function does not return an error itself to make it less cumbersome It is recommended to instead return a mapping function that returns an error the ErrorMapper is provider for this

The returned function is called once with values.IsRecording() == true to record the expected types For each row, the DB values are then scanned into Values before calling the returned function.

func CustomStructMapper

func CustomStructMapper[T any](src StructMapperSource, optMod ...MappingOption) Mapper[T]

Uses reflection to create a mapping function for a struct type using with custom options

func Mod

func Mod[T any](m Mapper[T], mods ...MapperMod) Mapper[T]

func StructMapper

func StructMapper[T any](opts ...MappingOption) Mapper[T]

Uses reflection to create a mapping function for a struct type using the default options

type MapperMod

type MapperMod = func(context.Context, cols) MapperModFunc

type MapperModFunc

type MapperModFunc = func(*Values, any) error

type MappingError

type MappingError struct {
	// contains filtered or unexported fields
}

MappingError wraps another error and holds some additional metadata

func (*MappingError) Error

func (m *MappingError) Error() string

Error implements the error interface

func (*MappingError) Unwrap

func (m *MappingError) Unwrap() error

Unwrap returns the wrapped error

type MappingOption

type MappingOption func(*mappingOptions)

MappingeOption is a function type that changes how the mapper is generated

func WithRowValidator

func WithRowValidator(rv RowValidator) MappingOption

WithRowValidator sets the RowValidator for the struct mapper after scanning all values in a row, they are passed to the RowValidator if it returns false, the zero value for that row is returned

func WithStructTagPrefix

func WithStructTagPrefix(prefix string) MappingOption

WithStructTagPrefix should be used when every column from the database has a prefix.

func WithTypeConverter

func WithTypeConverter(tc TypeConverter) MappingOption

TypeConverter sets the TypeConverter for the struct mapper it is called to modify the type of a column and get the original value back

type MappingSourceOption

type MappingSourceOption func(src *mapperSourceImpl) error

MappingSourceOption are options to modify how a struct's mappings are interpreted

func WithColumnSeparator

func WithColumnSeparator(separator string) MappingSourceOption

WithColumnSeparator allows to use a custom separator character for column name when combining nested structs. The default separator is "." character.

func WithFieldNameMapper

func WithFieldNameMapper(mapperFn NameMapperFunc) MappingSourceOption

WithFieldNameMapper allows to use a custom function to map field name to column names. The default function maps fields names to "snake_case"

func WithScannableTypes

func WithScannableTypes(scannableTypes ...interface{}) MappingSourceOption

WithScannableTypes specifies a list of interfaces that underlying database library can scan into. In case the destination type passed to scan implements one of those interfaces, scan will handle it as primitive type case i.e. simply pass the destination to the database library. Instead of attempting to map database columns to destination struct fields or map keys. In order for reflection to capture the interface type, you must pass it by pointer.

For example your database library defines a scanner interface like this:

type Scanner interface {
    Scan(...) error
}

You can pass it to scan this way: scan.WithScannableTypes((*Scanner)(nil)).

func WithStructTagKey

func WithStructTagKey(tagKey string) MappingSourceOption

WithStructTagKey allows to use a custom struct tag key. The default tag key is `db`.

type NameMapperFunc

type NameMapperFunc func(string) string

NameMapperFunc is a function type that maps a struct field name to the database column name.

type Queryer

type Queryer interface {
	QueryContext(ctx context.Context, query string, args ...any) (Rows, error)
}

Queryer is the main interface used in this package it is expected to run the query and args and return a set of Rows

func Debug

func Debug(q Queryer, w io.Writer) Queryer

type Row

type Row interface {
	Scan(...any) error
}

Row represents a single row in the Rows results can be scanned into a number of given values

type RowValidator

type RowValidator = func(map[string]reflect.Value) bool

RowValidator is called with all the values from a row to determine if the row is valid if it is not, the zero type for that row is returned

type Rows

type Rows interface {
	Row
	Columns() ([]string, error)
	Next() bool
	Close() error
	Err() error
}

Rows is an interface that is expected to be returned as the result of a query

type StructMapperSource

type StructMapperSource interface {
	// contains filtered or unexported methods
}

type TypeConverter

type TypeConverter interface {
	// ConvertType modifies the type of the struct
	// the method is called with the expected type of the column
	ConvertType(reflect.Type) reflect.Value

	// OriginalValue retrieves the original value from the converted type
	// the value given is a value of the type returned by ConvertType
	OriginalValue(reflect.Value) reflect.Value
}

type Values

type Values struct {
	// contains filtered or unexported fields
}

Values holds the values of a row use Value() to retrieve a value by column name Column names must be unique, so if multiple columns have the same name, only the last one remains

func (*Values) IsRecording

func (v *Values) IsRecording() bool

IsRecording returns wether the values are currently in recording mode When recording, calls to Get() will record the expected type

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL