scan

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Feb 22, 2018 License: MIT Imports: 4 Imported by: 18

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

Why

While many other awesome db project support similar features (i.e. sqlx) this provides the ability to use other projects like sq to write fluent sql statements and pass the resulting row to scan for simple 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. Roughly 850ns for one field structs and 4.6μs for five field structs.

→ go test -bench=. -benchtime=5s ./
goos: darwin
goarch: amd64
pkg: github.com/blockloop/scan
BenchmarkScanRowOneField-8               1000000              9956 ns/op
BenchmarkDirectScanOneField-8            1000000              9111 ns/op
BenchmarkScanRowFiveFields-8              500000             21125 ns/op
BenchmarkDirectScanFiveFields-8           500000             16446 ns/op
BenchmarkScanRowsOneField-8               500000             17365 ns/op
BenchmarkDirectScanManyOneField-8         500000             13136 ns/op
PASS
ok      github.com/blockloop/scan       53.995s

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 Row

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

Row scans a single row into a single variable

Example
package main

import (
	"database/sql"
	"encoding/json"
	"os"

	"github.com/blockloop/scan"
	_ "github.com/mattn/go-sqlite3"
)

func openDB() *sql.DB {
	db, err := sql.Open("sqlite3", ":memory:")
	if err != nil {
		panic(err)
	}

	_, err = db.Exec(`CREATE TABLE persons (
		id INTEGER PRIMARY KEY AUTOINCREMENT,
		name VARCHAR(120) NOT NULL DEFAULT ''
	);

	INSERT INTO PERSONS (name)
	VALUES ('brett'), ('fred');`)
	if err != nil {
		panic(err)
	}

	return db
}

func main() {
	db := openDB()
	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)
package main

import (
	"database/sql"
	"fmt"

	"github.com/blockloop/scan"
	_ "github.com/mattn/go-sqlite3"
)

func openDB() *sql.DB {
	db, err := sql.Open("sqlite3", ":memory:")
	if err != nil {
		panic(err)
	}

	_, err = db.Exec(`CREATE TABLE persons (
		id INTEGER PRIMARY KEY AUTOINCREMENT,
		name VARCHAR(120) NOT NULL DEFAULT ''
	);

	INSERT INTO PERSONS (name)
	VALUES ('brett'), ('fred');`)
	if err != nil {
		panic(err)
	}

	return db
}

func main() {
	db := openDB()
	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
package main

import (
	"database/sql"
	"encoding/json"
	"os"

	"github.com/blockloop/scan"
	_ "github.com/mattn/go-sqlite3"
)

func openDB() *sql.DB {
	db, err := sql.Open("sqlite3", ":memory:")
	if err != nil {
		panic(err)
	}

	_, err = db.Exec(`CREATE TABLE persons (
		id INTEGER PRIMARY KEY AUTOINCREMENT,
		name VARCHAR(120) NOT NULL DEFAULT ''
	);

	INSERT INTO PERSONS (name)
	VALUES ('brett'), ('fred');`)
	if err != nil {
		panic(err)
	}

	return db
}

func main() {
	db := openDB()
	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)
package main

import (
	"database/sql"
	"encoding/json"
	"os"

	"github.com/blockloop/scan"
	_ "github.com/mattn/go-sqlite3"
)

func openDB() *sql.DB {
	db, err := sql.Open("sqlite3", ":memory:")
	if err != nil {
		panic(err)
	}

	_, err = db.Exec(`CREATE TABLE persons (
		id INTEGER PRIMARY KEY AUTOINCREMENT,
		name VARCHAR(120) NOT NULL DEFAULT ''
	);

	INSERT INTO PERSONS (name)
	VALUES ('brett'), ('fred');`)
	if err != nil {
		panic(err)
	}

	return db
}

func main() {
	db := openDB()
	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"]

Types

type RowsScanner

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

RowsScanner is a database scanner for many rows. It is most commonly the result of *(database/sql).DB.Query(...) but can be mocked or stubbed

type Scanner

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

Scanner is a single row scanner. It is most commonly the result of *(database/sql).DB.QueryRow(...) but can be mocked or stubbed

Directories

Path Synopsis
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