GitHub - wroge/scan: scan sql rows into any type powered by generics (original) (raw)

Take a look at github.com/wroge/esquel.

go.dev reference Go Report Card golangci-lint codecov GitHub tag (latest SemVer)

Scan

This package offers a convenient and flexible way to scan SQL rows into any type, leveraging the power of generics.

Features

Usage

import "github.com/wroge/scan"

type Author struct { ID int64 Name string }

type Post struct { ID int64 Title string Authors []Author }

// Define mapping of database columns to struct fields. var columns = scan.Columns[Post]{ // Map the 'id' column to the 'ID' field in the 'Post' struct. // Uses the 'scan.Any' function for direct assignment without additional processing. "id": scan.Any(func(p *Post, id int64) { p.ID = id }),

// Map the 'title' column to the 'Title' field in the 'Post' struct.
// The 'scan.Null' function allows handling of nullable database columns.
// If the 'title' column is null, 'default title' is used as the value.
"title": scan.Null("default title", func(p *Post, title string) { p.Title = title }),

// Map the 'authors' column, expected to be in JSON format, to the 'Authors' field in the 'Post' struct.
// The 'scan.JSON' function automatically handles unmarshalling of the JSON data into the 'Author' struct slice.
"authors": scan.JSON(func(p *Post, authors []Author) { p.Authors = authors }),

// Or you could create a custom scanner with this function.
// "column": scan.Func[Post, V](func(p *Post, value V) error {
// 	return nil
// }),

}

rows, err := db.Query("SELECT ...") // handle error

Scanning all rows

posts, err := scan.All(rows, columns) // handle error

Scanning the first row

post, err := scan.First(rows, columns) if err != nil { if errors.Is(err, scan.ErrNoRows) { // handle no rows }

// handle other error

}

Scanning exactly one row

post, err := scan.One(rows, columns) if err != nil { if errors.Is(err, scan.ErrTooManyRows) { // handle too many rows // post is valid }

if errors.Is(err, scan.ErrNoRows) {
    // handle no rows
}

// handle other error

}

Scanning a limited number of rows

posts, err := scan.Limit(10, rows, columns) if err != nil { if errors.Is(err, scan.ErrTooManyRows) { // ignore if result set has more than 10 rows // len(posts) == 10 }

// handle other error

}

Using the Iterator directly

iter, err := scan.Iter(rows, columns) // handle error

defer iter.Close()

for iter.Next() { var post Post

err = iter.Scan(&post)
// handle error

// Or use the Value method:
post, err := iter.Value()
// handle error

}