scan

package module
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Dec 24, 2025 License: MIT Imports: 12 Imported by: 0

README

Scan

GoDoc Build Status Coverage Status

Scan is a Go package for scanning database rows into structs or slices of primitive types.

This project is forked from https://github.com/blockloop/scan and adjusted to be generic, while all dependencies are removed.

Installation

go get github.com/goapt/scan

Usage

import "github.com/goapt/scan"

Examples

Multiple Rows
db, err := sql.Open("sqlite3", "database.sqlite")
rows, err := db.Query("SELECT * FROM persons")
defer rows.Close()
persons, err := scan.Rows[Person](rows)

fmt.Printf("%#v", persons)
// []Person{
//    {ID: 1, Name: "brett"},
//    {ID: 2, Name: "fred"},
//    {ID: 3, Name: "stacy"},
// }
Multiple rows of primitive type
rows, err := db.Query("SELECT name FROM persons")
defer rows.Close()
names, err := scan.Rows[string](rows)

fmt.Printf("%#v", names)
// []string{
//    "brett",
//    "fred",
//    "stacy",
// }
Single row
rows, err := db.Query("SELECT * FROM persons where name = 'brett' LIMIT 1")
person, err := scan.Row[Person](rows)
defer rows.Close()

fmt.Printf("%#v", person)
// Person{ ID: 1, Name: "brett" }
Scalar value
rows, err := db.Query("SELECT age FROM persons where name = 'brett' LIMIT 1")
defer rows.Close()
age, err := scan.Row[int8](rows)

fmt.Printf("%d", age)
// 100
Nested Struct Fields
rows, err := db.Query(`
	SELECT person.id,person.name,company.name FROM person
	JOIN company on company.id = person.company_id
	LIMIT 1
`)
defer rows.Close()

type Person struct {
	ID      int    `db:"person.id"`
	Name    string `db:"person.name"`
	Company struct {
		Name string `db:"company.name"`
	}
}

person, err := scan.Row[Person](rows)

err = json.NewEncoder(os.Stdout).Encode(&person)
// Output:
// {"ID":1,"Name":"brett","Company":{"Name":"costco"}}
Custom Column Mapping

By default, column names are mapped to and from database column names using basic title case conversion. You can override this behavior by setting ColumnsMapper and ScannerMapper to custom functions.

Columns

Columns scans a struct and returns a string slice of the assumed column names based on the db tag or the struct field name respectively. To avoid assumptions, use ColumnsStrict which will only return the fields tagged with the db tag. Both Columns and ColumnsStrict are variadic. They both accept a string slice of column names to exclude from the list. It is recommended that you cache this slice.

package main

type User struct {
        ID        int64
        Name      string
        Age       int
        BirthDate string `db:"bday"`
        Zipcode   string `db:"-"`
        Store     struct {
                ID int
                // ...
        }
}

var nobody = new(User)
var userInsertCols = scan.Columns(nobody, "ID")
// []string{ "Name", "Age", "bday" }

var userSelectCols = scan.Columns(nobody)
// []string{ "ID", "Name", "Age", "bday" }
Values

Values scans a struct and returns the values associated with the provided columns. Values uses a sync.Map to cache fields of structs to greatly improve the performance of scanning types. The first time a struct is scanned it's exported fields locations are cached. When later retrieving values from the same struct it should be much faster.

user := &User{
        ID: 1,
        Name: "Brett",
        Age: 100,
}

vals := scan.Values([]string{"ID", "Name"}, user)
// []interface{}{ 1, "Brett" }

Why

While many other projects support similar features (i.e. sqlx) scan allows you to use any database lib such as the stdlib to write fluent SQL statements and pass the resulting rows to scan for scanning.

Documentation

Overview

Package scan provides functionality for scanning database/sql rows into slices, structs, and primitive types dynamically

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNotAPointer is returned when a non-pointer is received
	// when a pointer is expected.
	ErrNotAPointer = errors.New("not a pointer")

	// ErrNotAStructPointer is returned when a non-struct pointer
	// is received but a struct pointer was expected
	ErrNotAStructPointer = errors.New("not a struct pointer")

	// ErrNotASlicePointer is returned when receiving an argument
	// that is expected to be a slice pointer, but it is not
	ErrNotASlicePointer = errors.New("not a slice pointer")

	// ErrStructFieldMissing is returned when trying to scan a value
	// to a column which does not match a struct. This means that
	// the struct does not have a field that matches the column
	// specified.
	ErrStructFieldMissing = errors.New("struct field missing")

	// ColumnsMapper transforms struct/map field names
	// into the database column names.
	// E.g. you can set function for convert CamelCase into snake_case
	ColumnsMapper = func(name string) string { return name }
)
View Source
var (
	// ErrTooManyColumns indicates that a select query returned multiple columns and
	// attempted to bind to a slice of a primitive type. For example, trying to bind
	// `select col1, col2 from mutable` to []string
	ErrTooManyColumns = errors.New("too many columns returned for primitive slice")

	// ScannerMapper transforms database field names into struct/map field names
	// E.g. you can set function for convert snake_case into CamelCase
	ScannerMapper = func(name string) string { return toTitleCase(name) }
)

Functions

func Columns

func Columns(v any, excluded ...string) ([]string, error)

Columns scans a struct and returns a list of strings that represent the assumed column names based on the db struct tag, or the field name. Any field or struct tag that matches a string within the excluded list will be excluded from the result.

func ColumnsStrict

func ColumnsStrict(v any, excluded ...string) ([]string, error)

ColumnsStrict is identical to Columns, but it only searches struct tags and excludes fields not tagged with the db struct tag.

func Nullable added in v0.0.2

func Nullable(dest any) any

Nullable wrap value as a nullable sql.Scanner. If value returned from database is nil, nullable scanner will set dest to zero value.

func Row

func Row[T any](r *sql.Rows) (T, error)

Row scans a single row and returns a value of type T. It requires that you use db.Query and not db.QueryRow, because QueryRow does not return column names.

func Rows

func Rows[T any](r *sql.Rows) ([]T, error)

Rows scans sql rows into a slice of T.

func Values

func Values(cols []string, v any) ([]any, error)

Values scans a struct and returns the values associated with the columns provided. Only simple value types are supported (i.e. Bool, Ints, Uints, Floats, Interface, String)

Types

This section is empty.

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

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