scan

package module
v1.2.1 Latest Latest
Warning

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

Go to latest
Published: Mar 1, 2018 License: MIT Imports: 6 Imported by: 0

README

scan

GoDoc Travis Coveralls github Report Card

scan provides the ability to use database/sql/rows to scan datasets directly to structs or slices. For the most comprehensive and up-to-date docs see the godoc

Examples

Multiple Rows
db, err := sql.Open("sqlite3", "database.sqlite")
rows, err := db.Query("SELECT * FROM persons")
var persons []Person
err := scan.Rows(&persons, 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")
var names []string
err := scan.Rows(&names, rows)

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

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

fmt.Printf("%d", age)
// 100
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. See Benchmarks below.

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

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

I find that the usefulness of both Values and Columns lies within using a library such as sq.

vals := scan.Values(userCols, &user)
sq.Insert(userCols...).
        Into("users").
        Values(vals...)

Why

While many other awesome db project support similar features (i.e. sqlx) this provides the ability to use the stdlib or squirrel to write fluent sql statements and pass the resulting row to scan for scanning

Benchmarks

I created some benchmarks in bench_scanner_test.go to compare using scan against manually scanning directly to structs and/or appending to slices. The results aren't staggering as you can see.

> go test -benchtime=10s -bench=.
goos: darwin
goarch: amd64
pkg: github.com/blockloop/scan
BenchmarkValuesLargeStruct-8             2000000              8088 ns/op
BenchmarkValuesLargeStructCached-8      10000000              1564 ns/op
BenchmarkScanRowOneField-8               1000000             12496 ns/op
BenchmarkDirectScanOneField-8            2000000              9448 ns/op
BenchmarkScanRowFiveFields-8              500000             23949 ns/op
BenchmarkDirectScanFiveFields-8          1000000             16993 ns/op
BenchmarkScanRowsOneField-8              1000000             16872 ns/op
BenchmarkDirectScanManyOneField-8        1000000             14030 ns/op
PASS
ok      github.com/blockloop/scan       125.432s

Documentation

Overview

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

Index

Examples

Constants

This section is empty.

Variables

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 mytable` to []string
	ErrTooManyColumns = errors.New("too many columns returned for primitive slice")

	// ErrSliceForRow occurs when trying to use Row on a slice
	ErrSliceForRow = errors.New("cannot scan Row into slice")

	// AutoClose is true when scan should automatically close Scanner when the scan
	// is complete. If you set it to false, then you must defer rows.Close() manually
	AutoClose = true
)

Functions

func Columns added in v1.1.0

func Columns(v interface{}, excluded ...string) []string

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

Example
package main

import (
	"fmt"

	"github.com/blockloop/scan"
)

func main() {
	var person struct {
		ID   int `db:"person_id"`
		Name string
	}

	cols := scan.Columns(&person)
	fmt.Printf("%+v", cols)
}
Output:

[person_id Name]
Example (Exclude)
package main

import (
	"fmt"

	"github.com/blockloop/scan"
)

func main() {
	var person struct {
		ID   int    `db:"id"`
		Name string `db:"name"`
		Age  string `db:"-"`
	}

	cols := scan.Columns(&person)
	fmt.Printf("%+v", cols)
}
Output:

[id name]

func ColumnsStrict added in v1.1.0

func ColumnsStrict(v interface{}, excluded ...string) []string

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

Example
package main

import (
	"fmt"

	"github.com/blockloop/scan"
)

func main() {
	var person struct {
		ID   int `db:"id"`
		Name string
		Age  string `db:"age"`
	}

	cols := scan.ColumnsStrict(&person)
	fmt.Printf("%+v", cols)
}
Output:

[id age]

func Row

func Row(v interface{}, rows RowsScanner) error

Row scans a single row into a single variable. It requires that you use db.Query and not db.QueryRow, because QueryRow does not return column names. There is no performance impact in using one over the other. QueryRow only defers returning err until Scan is called, which is an unnecessary optimization for this library.

Example
db := exampleDB()
rows, err := db.Query("SELECT * FROM persons LIMIT 1")
if err != nil {
	panic(err)
}

var person struct {
	ID   int    `db:"id"`
	Name string `db:"name"`
}

err = scan.Row(&person, rows)
if err != nil {
	panic(err)
}

json.NewEncoder(os.Stdout).Encode(&person)
Output:

{"ID":1,"Name":"brett"}
Example (Scalar)
db := exampleDB()
rows, err := db.Query("SELECT name FROM persons LIMIT 1")
if err != nil {
	panic(err)
}

var name string

err = scan.Row(&name, rows)
if err != nil {
	panic(err)
}

fmt.Printf("%q", name)
Output:

"brett"

func Rows

func Rows(v interface{}, rows RowsScanner) error

Rows scans sql rows into a slice (v)

Example
db := exampleDB()
rows, err := db.Query("SELECT * FROM persons ORDER BY name")
if err != nil {
	panic(err)
}

var persons []struct {
	ID   int    `db:"id"`
	Name string `db:"name"`
}

err = scan.Rows(&persons, rows)
if err != nil {
	panic(err)
}

json.NewEncoder(os.Stdout).Encode(&persons)
Output:

[{"ID":1,"Name":"brett"},{"ID":2,"Name":"fred"}]
Example (Primitive)
db := exampleDB()
rows, err := db.Query("SELECT name FROM persons ORDER BY name")
if err != nil {
	panic(err)
}

var names []string
err = scan.Rows(&names, rows)
if err != nil {
	panic(err)
}

json.NewEncoder(os.Stdout).Encode(&names)
Output:

["brett","fred"]

func Values added in v1.2.0

func Values(cols []string, v interface{}) []interface{}

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)

Example
package main

import (
	"fmt"

	"github.com/blockloop/scan"
)

func main() {
	person := struct {
		ID   int    `db:"id"`
		Name string `db:"name"`
	}{
		ID:   1,
		Name: "Brett",
	}

	cols := []string{"id", "name"}
	vals := scan.Values(cols, &person)
	fmt.Printf("%+v", vals)
}
Output:

[1 Brett]

Types

type RowsScanner

type RowsScanner interface {
	Close() error
	Scan(dest ...interface{}) error
	Columns() ([]string, error)
	ColumnTypes() ([]*sql.ColumnType, error)
	Err() error
	Next() bool
}

RowsScanner is a database scanner for many rows. It is most commonly the result of *sql.DB Query(...)

Directories

Path Synopsis
internal
mocks
Package mocks is a generated GoMock package.
Package mocks is a generated GoMock package.

Jump to

Keyboard shortcuts

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