Query binding

The binding system lies at the heart of all sqlbunny queries. It is in charge of converting SQL rows into Go structs.

It matches SQL columns to Go struct fields, and copies their values over.

Binding behavior is controlled by the bunny struct tag.

Field binds

To bind a field to an SQL column, specify its SQL column name in the bunny tag. Fields without a bunny tag are never bound.

SQL-to-Go conversion is done using the same rules as sql.Rows.Scan(). In a nutshell, it means destination types should be either supported directly by the SQL driver, or implement sql.Scanner.

type MyStruct struct {
    Foo     int    `bunny:"foo"`
    Bar     string `bunny:"bar"`
    Ignored string
}
var v MyStruct
err = queries.Raw("SELECT 1 AS foo, 'fun' AS bar, 'hello' AS ignored").Bind(ctx, &v)
// v contains foo=1, bar="fun", and ignored=""

Recursive binds

If the ",bind" option is specified on a field of struct type, binding will recurse into it to look for fields for binding. "name" is added as a prefix to the SQL column names of the inner fields.

type Bar struct {
    One int `bunny:"one"`
    Two int `bunny:"two"`
}
type MyStruct struct {
    Foo     int `bunny:"foo"`
    Bar     Bar `bunny:"bar__,bind"`
}
var v MyStruct
err = queries.Raw("SELECT 1 AS foo, 2 AS bar__one, 3 as bar__two").Bind(ctx, &v)
// v contains foo=1, bar={ one=2, two=3 }

Aggregating

Bind to a custom struct to read the results of an aggregation.

type BooksByYear struct {
    Year  int `bunny:"year"`
    Count int `bunny:"count"`
}

var years []BooksByYear

err = models.Books(
    qm.Select("year", "count(*) AS count"),
    qm.GroupBy("year"),
    qm.OrderBy("year ASC"),
).Bind(ctx, &years)

Joining

Binding to a custom "join struct" can be handy for receiving the results of a join.

type BookAndAuthor struct {
    models.Author `bunny:"author.,bind"`
    models.Book   `bunny:"book.,bind"`
}

var res []BookAndAuthor

err = models.Authors(
    qm.Select(
        "author.id", "author.name",
        "book.id", "book.author_id", "book.title", "book.year",
    ),
    qm.InnerJoin("book ON book.author_id = author.id"),
).Bind(ctx, &res)

// This wil execute the following query:
// SELECT 
//     "author"."id" as "author.id", "author"."name" as "author.name",
//     "book"."id" as "book.id", "book"."author_id" as "book.author_id", "book"."title" as "book.title", "book"."year" as "book.year"
// FROM "author"
// INNER JOIN book ON book.author_id = author.id;

Extending models

Recursive binding with empty prefix can be used to "extend" a model query with an extra computed column.

You simply define your own struct extending Author, and bind to it. Since Author is recursively bound with empty prefix, all the Author

type AuthorWithCount struct {
    models.Author `bunny:",bind"`
    BookCount     int `bunny:"book_count"`
}

var authors []AuthorWithCount

err = models.Authors(
    qm.Select("*", "(SELECT COUNT(*) FROM book WHERE book.author_id = author.id) AS book_count"),
).Bind(ctx, &authors)

This gives us all the authors with their respective book counts!