https://github.com/blockloop/scan
Tiny lib to scan SQL rows directly to structs, slices, and primitive types
https://github.com/blockloop/scan
database golang scanning sql squirrel
Last synced: 3 months ago
JSON representation
Tiny lib to scan SQL rows directly to structs, slices, and primitive types
- Host: GitHub
- URL: https://github.com/blockloop/scan
- Owner: blockloop
- License: mit
- Created: 2017-11-27T23:22:18.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2024-06-05T11:32:46.000Z (about 1 year ago)
- Last Synced: 2024-08-02T20:47:53.904Z (11 months ago)
- Topics: database, golang, scanning, sql, squirrel
- Language: Go
- Homepage:
- Size: 4.17 MB
- Stars: 543
- Watchers: 10
- Forks: 31
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
- awesome-go-extra - scan - 11-27T23:22:18Z|2022-07-30T19:08:48Z| (Utilities / Fail injection)
- my-awesome - blockloop/scan - 06 star:0.6k fork:0.0k Tiny lib to scan SQL rows directly to structs, slices, and primitive types (Go)
- awesome-go - blockloop/scan
- awesome-go - blockloop/scan
README
# Scan
> [!NOTE]
> For all intents and purposes, this project is considered 'complete.' I do not plan on making any changes in the near future and have not written any code here since approximately 2021. To the best of my knowledge, everything functions as intended. If you are seeking a more comprehensive solution, please refer to https://github.com/stytchauth/sqx. That being said, this project operates effectively in its current state and requires no new code. Feel free to fork it and continue development if desired.
>[](https://godoc.org/github.com/blockloop/scan)
[](https://github.com/blockloop/scan/actions)
[](https://coveralls.io/github/blockloop/scan)
[](https://goreportcard.com/report/github.com/blockloop/scan)Scan standard lib database rows directly to structs or slices.
For the most comprehensive and up-to-date docs see the [godoc](https://godoc.org/github.com/blockloop/scan)```go
import "github.com/blockloop/scan/v2"
```## Examples
### Multiple Rows
```go
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```go
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
```go
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
```go
rows, err := db.Query("SELECT age FROM persons where name = 'brett' LIMIT 1")
var age int8
err := scan.Row(&age, rows)fmt.Printf("%d", age)
// 100
```### Nested Struct Fields (as of v2.0.0)
```go
rows, err := db.Query(`
SELECT person.id,person.name,company.name FROM person
JOIN company on company.id = person.company_id
LIMIT 1
`)var person struct {
ID int `db:"person.id"`
Name string `db:"person.name"`
Company struct {
Name string `db:"company.name"`
}
}err = scan.RowStrict(&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](https://github.com/blockloop/scan/blob/4741cc8ac5746ca7e5893d3b54a3347a7735c168/columns.go#L35) and [from](https://github.com/blockloop/scan/blob/4741cc8ac5746ca7e5893d3b54a3347a7735c168/scanner.go#L33) database column names using basic title case conversion. You can override this behavior by setting `ColumnsMapper` and `ScannerMapper` to custom functions.
### Strict Scanning
Both `Rows` and `Row` have strict alternatives to allow scanning to structs _strictly_ based on their `db` tag.
To avoid unwanted behavior you can use `RowsStrict` or `RowStrict` to scan without using field names.
Any fields not tagged with the `db` tag will be ignored even if columns are found that match the field names.### 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.
```go
package maintype 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](#Benchmarks) below.
```go
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][].
```go
sq.Insert(userCols...).
Into("users").
Values(scan.Values(userCols, &user)...)
```## Configuration
AutoClose: Automatically call `rows.Close()` after scan completes (default true)
## Why
While many other projects support similar features (i.e. [sqlx](https://github.com/jmoiron/sqlx)) scan allows you to use any database lib such as the stdlib or [squirrel][sq] to write fluent SQL statements and pass the resulting `rows` to `scan` for scanning.
## Benchmarks
```
$ go test -bench=. -benchtime=10s ./...
goos: linux
goarch: amd64
pkg: github.com/blockloop/scan
cpu: 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz
BenchmarkColumnsLargeStruct-8 41527964 288.0 ns/op
BenchmarkValuesLargeStruct-8 6816885 1807 ns/op
BenchmarkScanRowOneField-8 5686971 2074 ns/op
BenchmarkScanRowFiveFields-8 4962622 2381 ns/op
BenchmarkScanTenRowsOneField-8 1537761 8598 ns/op
BenchmarkScanTenRowsTenFields-8 322106 50431 ns/op
PASS
ok github.com/blockloop/scan 92.374s
```[sq]: https://github.com/Masterminds/squirrel "Squirrel"