Skip to content

Commit

Permalink
Start writing docs for dbscan
Browse files Browse the repository at this point in the history
  • Loading branch information
georgysavva committed Jun 28, 2020
1 parent 83bc235 commit b39af39
Show file tree
Hide file tree
Showing 7 changed files with 229 additions and 76 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ go get github.com/georgysavva/dbscan

```
type User struct {
ID string
ID string `db:"user_id"`
Name string
Email string
Age int
}
// Query rows from the database that implement dbscan.Rows interface, e.g. *sql.Rows:
db, _ := sql.Open("pgx", "example-connection-url")
rows, _ := db.Query(`SELECT id, name, email, age from users`)
rows, _ := db.Query(`SELECT user_id, name, email, age from users`)
var users []*User
if err := dbscan.ScanAll(&users, rows); err != nil {
Expand Down
105 changes: 104 additions & 1 deletion doc.go
Original file line number Diff line number Diff line change
@@ -1,2 +1,105 @@
// Package dbscan
// Package dbscan allows scanning data from database rows into complex Go types.
/*
dbscan works with abstract Rows and doesn't depend on any specific database or library.
If a type implements Rows interface it can leverage full functional of this package.
Subpackages sqlscan and pgxscan are wrappers around this package
they contain functions and adapters tailored to database/sql and
github.com/jackc/pgx/v4 libraries correspondingly. sqlscan and pgxscan proxy all calls to dbscan internally.
dbscan does all the logic, but generally, it shouldn't be imported by the application code directly.
If you are working with database/sql - use sqlscan subpackage.
If you are working with pgx - use pgxscan subpackage.
Scanning into struct
dbscan provides ability to scan row data into struct.
By default, to get the corresponding column dbscan translates field name to snake case.
In order to override this behaviour, specify column name in the `db` field tag, for example:
type User struct {
ID string `db:"user_id"`
FirstName string
Email string
}
will get mapped to the following columns: "user_id", "first_name", "email".
Struct can contain embedded structs as well. It allows to reuse models in different queries.
Note that non-embedded structs aren't allowed, this decision was made due to simplicity.
By default, dbscan maps fields from embedded structs to columns as is and doesn't add prefix,
this simulates behaviour of major SQL databases in case of a JOIN.
In order to add a prefix to all fields of the embedded struct specify it in the `db` field tag,
"." used as the separator for example:
type User struct {
UserID string
Email string
}
type Post struct {
ID string
Text string
}
type Row struct {
User
Post `db:post`
}
will get mapped to the following columns: "user_id", "email", "post.id", "post.text".
If dbscan can't find corresponding field for a column it returns an error,
this forces to only select data from the database that application needs.
Also if struct contains multiple fields that are mapped to the same column,
dbscan won't be able to make the chose to which field to assign and return an error, for example:
type User struct {
ID string
Email string
}
type Post struct {
ID string
Text string
}
type Row struct {
User
Post
}
Row struct is invalid since both User.ID and Post.ID are mapped to the "id" column.
Other destination types
Apart from scanning into structs, dbscan can handle maps,
in that case it uses column name as the map key and column data as the map value. For example:
var result map[string]interface{}
if err := dbscan.ScanOne(&result, rows); err != nil {
// Handle rows processing error
}
// result variable not contains data from the row.
Note that map type not limited to the map[string]interface{},
it can be map[string]string or map[string]int, if all values have the same specific type.
If the destination isn't a struct nor a map, dbscan handles it as single column scan,
it ensures that rows contain exactly one column and scans destination from the column, for example:
var result string
if err := dbscan.ScanOne(&result, rows); err != nil {
// Handle rows processing error
}
// result variable not contains data from the row single column.
1. + Abstract Rows
2. Structs, Maps, primitive types
3. + tag
4. + embedded
5. high-level functions.
6. + struct destination validation
*/
package dbscan
2 changes: 1 addition & 1 deletion doscan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ func TestRowScanner_DoScan_primitiveTypeDestination(t *testing.T) {
query: `
SELECT 'foo val' AS foo
`,
expected: "foo val",
expected: makeStrPtr("foo val"),
},
{
name: "slice",
Expand Down
98 changes: 98 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package dbscan_test

import (
"github.com/georgysavva/dbscan"
)

var rows dbscan.Rows

func ExampleScanAll() {
type User struct {
ID string
Name string
Email string
Age int
}

// Query rows from the database that implement dbscan.Rows interface.

var users []*User
if err := dbscan.ScanAll(&users, rows); err != nil {
// Handle rows processing error
}
// users variable now contains data from all rows.
}

func ExampleScanOne() {
type User struct {
ID string
Name string
Email string
Age int
}

// Query rows from the database that implement dbscan.Rows interface.

var user User
if err := dbscan.ScanOne(&user, rows); err != nil {
// Handle rows processing error.
}
// user variable now contains data from the single row.
}

func ExampleRowScanner() {
type User struct {
ID string
Name string
Email string
Age int
}

// Query rows from the database that implement dbscan.Rows interface.

// Make sure rows are closed.
defer rows.Close()

rs := dbscan.NewRowScanner(rows)
for rows.Next() {
var user User
if err := rs.Scan(&user); err != nil {
// Handle row scanning error.
}
// user variable now contains data from the current row.
}
if err := rows.Err(); err != nil {
// Handle rows final error.
}
if err := rows.Close(); err != nil {
// Handle rows closing error.
}
}

func ExampleRowScan() {
type User struct {
ID string
Name string
Email string
Age int
}

// Query rows from the database that implement dbscan.Rows interface.

// Make sure rows are closed.
defer rows.Close()

for rows.Next() {
var user User
if err := dbscan.ScanRow(&user, rows); err != nil {
// Handle row scanning error.
}
// user variable now contains data from the current row.
}
if err := rows.Err(); err != nil {
// Handle rows final error.
}
if err := rows.Close(); err != nil {
// Handle rows closing error.
}
}
24 changes: 12 additions & 12 deletions pgxscan/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (

func ExampleQueryAll() {
type User struct {
ID string
ID string `db:"user_id"`
Name string
Email string
Age int
Expand All @@ -17,7 +17,7 @@ func ExampleQueryAll() {

var users []*User
if err := pgxscan.QueryAll(
ctx, &users, db, `SELECT id, name, email, age from users`,
ctx, &users, db, `SELECT user_id, name, email, age from users`,
); err != nil {
// Handle query or rows processing error.
}
Expand All @@ -26,7 +26,7 @@ func ExampleQueryAll() {

func ExampleQueryOne() {
type User struct {
ID string
ID string `db:"user_id"`
Name string
Email string
Age int
Expand All @@ -36,7 +36,7 @@ func ExampleQueryOne() {

var user User
if err := pgxscan.QueryOne(
ctx, &user, db, `SELECT id, name, email, age from users where id='bob'`,
ctx, &user, db, `SELECT user_id, name, email, age from users where id='bob'`,
); err != nil {
// Handle query or rows processing error.
}
Expand All @@ -45,15 +45,15 @@ func ExampleQueryOne() {

func ExampleScanAll() {
type User struct {
ID string
ID string `db:"user_id"`
Name string
Email string
Age int
}

// Query pgx.Rows from the database.
db, _ := pgxpool.Connect(ctx, "example-connection-url")
rows, _ := db.Query(ctx, `SELECT id, name, email, age from users`)
rows, _ := db.Query(ctx, `SELECT user_id, name, email, age from users`)

var users []*User
if err := pgxscan.ScanAll(&users, rows); err != nil {
Expand All @@ -64,15 +64,15 @@ func ExampleScanAll() {

func ExampleScanOne() {
type User struct {
ID string
ID string `db:"user_id"`
Name string
Email string
Age int
}

// Query pgx.Rows from the database.
db, _ := pgxpool.Connect(ctx, "example-connection-url")
rows, _ := db.Query(ctx, `SELECT id, name, email, age from users where id='bob'`)
rows, _ := db.Query(ctx, `SELECT user_id, name, email, age from users where id='bob'`)

var user User
if err := pgxscan.ScanOne(&user, rows); err != nil {
Expand All @@ -83,15 +83,15 @@ func ExampleScanOne() {

func ExampleRowScanner() {
type User struct {
ID string
ID string `db:"user_id"`
Name string
Email string
Age int
}

// Query pgx.Rows from the database.
db, _ := pgxpool.Connect(ctx, "example-connection-url")
rows, _ := db.Query(ctx, `SELECT id, name, email, age from users`)
rows, _ := db.Query(ctx, `SELECT user_id, name, email, age from users`)
// Make sure rows are closed.
defer rows.Close()

Expand All @@ -110,15 +110,15 @@ func ExampleRowScanner() {

func ExampleRowScan() {
type User struct {
ID string
ID string `db:"user_id"`
Name string
Email string
Age int
}

// Query pgx.Rows from the database.
db, _ := pgxpool.Connect(ctx, "example-connection-url")
rows, _ := db.Query(ctx, `SELECT id, name, email, age from users`)
rows, _ := db.Query(ctx, `SELECT user_id, name, email, age from users`)
// Make sure rows are closed.
defer rows.Close()

Expand Down
Loading

0 comments on commit b39af39

Please sign in to comment.