From 061348b783e111bb1b6efe266d712a1b8536fc2c Mon Sep 17 00:00:00 2001 From: hayama-junpei Date: Sat, 5 Dec 2020 15:32:52 +0900 Subject: [PATCH] Added emit_group_by_file option --- examples/group/mysql/mygroup/authors.sql.go | 37 ++ examples/group/mysql/mygroup/books.sql.go | 237 +++++++ examples/group/mysql/mygroup/db.go | 15 + examples/group/mysql/mygroup/db_test.go | 168 +++++ examples/group/mysql/mygroup/models.go | 43 ++ .../group/mysql/mygroupiface/authors.sql.go | 42 ++ .../group/mysql/mygroupiface/books.sql.go | 248 ++++++++ examples/group/mysql/mygroupiface/db.go | 15 + examples/group/mysql/mygroupiface/db_test.go | 168 +++++ examples/group/mysql/mygroupiface/models.go | 43 ++ .../group/mysql/mygroupstmt/authors.sql.go | 90 +++ examples/group/mysql/mygroupstmt/books.sql.go | 519 ++++++++++++++++ examples/group/mysql/mygroupstmt/db.go | 15 + examples/group/mysql/mygroupstmt/db_test.go | 321 ++++++++++ examples/group/mysql/mygroupstmt/models.go | 43 ++ .../mysql/mygroupstmtiface/authors.sql.go | 109 ++++ .../group/mysql/mygroupstmtiface/books.sql.go | 586 ++++++++++++++++++ examples/group/mysql/mygroupstmtiface/db.go | 15 + .../group/mysql/mygroupstmtiface/db_test.go | 321 ++++++++++ .../group/mysql/mygroupstmtiface/models.go | 43 ++ examples/group/mysql/sql/query/authors.sql | 6 + examples/group/mysql/sql/query/books.sql | 56 ++ examples/group/mysql/sql/schema/schema.sql | 26 + .../group/postgresql/pggroup/authors.sql.go | 40 ++ .../group/postgresql/pggroup/books.sql.go | 236 +++++++ examples/group/postgresql/pggroup/db.go | 15 + examples/group/postgresql/pggroup/db_test.go | 157 +++++ examples/group/postgresql/pggroup/models.go | 43 ++ .../postgresql/pggroupiface/authors.sql.go | 45 ++ .../postgresql/pggroupiface/books.sql.go | 246 ++++++++ examples/group/postgresql/pggroupiface/db.go | 15 + .../group/postgresql/pggroupiface/db_test.go | 157 +++++ .../group/postgresql/pggroupiface/models.go | 43 ++ .../postgresql/pggroupstmt/authors.sql.go | 97 +++ .../group/postgresql/pggroupstmt/books.sql.go | 505 +++++++++++++++ examples/group/postgresql/pggroupstmt/db.go | 15 + .../group/postgresql/pggroupstmt/db_test.go | 307 +++++++++ .../group/postgresql/pggroupstmt/models.go | 43 ++ .../pggroupstmtiface/authors.sql.go | 116 ++++ .../postgresql/pggroupstmtiface/books.sql.go | 564 +++++++++++++++++ .../group/postgresql/pggroupstmtiface/db.go | 15 + .../postgresql/pggroupstmtiface/db_test.go | 307 +++++++++ .../postgresql/pggroupstmtiface/models.go | 43 ++ .../group/postgresql/sql/query/authors.sql | 7 + examples/group/postgresql/sql/query/books.sql | 52 ++ .../group/postgresql/sql/schema/schema.sql | 32 + examples/group/sqlc.json | 85 +++ internal/codegen/golang/gen.go | 43 +- internal/codegen/golang/imports.go | 16 +- internal/codegen/golang/query.go | 1 + internal/codegen/golang/result.go | 21 +- internal/codegen/golang/template_group.go | 360 +++++++++++ .../golang/template_group_interface.go | 427 +++++++++++++ internal/codegen/utils.go | 8 + internal/compiler/compile.go | 3 + internal/config/config.go | 1 + internal/config/v_one.go | 2 + 57 files changed, 7208 insertions(+), 25 deletions(-) create mode 100644 examples/group/mysql/mygroup/authors.sql.go create mode 100644 examples/group/mysql/mygroup/books.sql.go create mode 100644 examples/group/mysql/mygroup/db.go create mode 100644 examples/group/mysql/mygroup/db_test.go create mode 100644 examples/group/mysql/mygroup/models.go create mode 100644 examples/group/mysql/mygroupiface/authors.sql.go create mode 100644 examples/group/mysql/mygroupiface/books.sql.go create mode 100644 examples/group/mysql/mygroupiface/db.go create mode 100644 examples/group/mysql/mygroupiface/db_test.go create mode 100644 examples/group/mysql/mygroupiface/models.go create mode 100644 examples/group/mysql/mygroupstmt/authors.sql.go create mode 100644 examples/group/mysql/mygroupstmt/books.sql.go create mode 100644 examples/group/mysql/mygroupstmt/db.go create mode 100644 examples/group/mysql/mygroupstmt/db_test.go create mode 100644 examples/group/mysql/mygroupstmt/models.go create mode 100644 examples/group/mysql/mygroupstmtiface/authors.sql.go create mode 100644 examples/group/mysql/mygroupstmtiface/books.sql.go create mode 100644 examples/group/mysql/mygroupstmtiface/db.go create mode 100644 examples/group/mysql/mygroupstmtiface/db_test.go create mode 100644 examples/group/mysql/mygroupstmtiface/models.go create mode 100644 examples/group/mysql/sql/query/authors.sql create mode 100644 examples/group/mysql/sql/query/books.sql create mode 100644 examples/group/mysql/sql/schema/schema.sql create mode 100644 examples/group/postgresql/pggroup/authors.sql.go create mode 100644 examples/group/postgresql/pggroup/books.sql.go create mode 100644 examples/group/postgresql/pggroup/db.go create mode 100644 examples/group/postgresql/pggroup/db_test.go create mode 100644 examples/group/postgresql/pggroup/models.go create mode 100644 examples/group/postgresql/pggroupiface/authors.sql.go create mode 100644 examples/group/postgresql/pggroupiface/books.sql.go create mode 100644 examples/group/postgresql/pggroupiface/db.go create mode 100644 examples/group/postgresql/pggroupiface/db_test.go create mode 100644 examples/group/postgresql/pggroupiface/models.go create mode 100644 examples/group/postgresql/pggroupstmt/authors.sql.go create mode 100644 examples/group/postgresql/pggroupstmt/books.sql.go create mode 100644 examples/group/postgresql/pggroupstmt/db.go create mode 100644 examples/group/postgresql/pggroupstmt/db_test.go create mode 100644 examples/group/postgresql/pggroupstmt/models.go create mode 100644 examples/group/postgresql/pggroupstmtiface/authors.sql.go create mode 100644 examples/group/postgresql/pggroupstmtiface/books.sql.go create mode 100644 examples/group/postgresql/pggroupstmtiface/db.go create mode 100644 examples/group/postgresql/pggroupstmtiface/db_test.go create mode 100644 examples/group/postgresql/pggroupstmtiface/models.go create mode 100644 examples/group/postgresql/sql/query/authors.sql create mode 100644 examples/group/postgresql/sql/query/books.sql create mode 100644 examples/group/postgresql/sql/schema/schema.sql create mode 100644 examples/group/sqlc.json create mode 100644 internal/codegen/golang/template_group.go create mode 100644 internal/codegen/golang/template_group_interface.go diff --git a/examples/group/mysql/mygroup/authors.sql.go b/examples/group/mysql/mygroup/authors.sql.go new file mode 100644 index 0000000000..facbea5969 --- /dev/null +++ b/examples/group/mysql/mygroup/authors.sql.go @@ -0,0 +1,37 @@ +// Code generated by sqlc. DO NOT EDIT. +// source: authors.sql + +package mygroup + +import ( + "context" + "database/sql" +) + +type Authors struct { + db DBTX +} + +func NewAuthors(db DBTX) *Authors { + return &Authors{db: db} +} + +const authorsCreate = `-- name: Create :execresult +INSERT INTO authors (name) VALUES (?) +` + +func (q *Authors) Create(ctx context.Context, name string) (sql.Result, error) { + return q.db.ExecContext(ctx, authorsCreate, name) +} + +const authorsGet = `-- name: Get :one +SELECT author_id, name FROM authors +WHERE author_id = ? +` + +func (q *Authors) Get(ctx context.Context, authorID int32) (Author, error) { + row := q.db.QueryRowContext(ctx, authorsGet, authorID) + var i Author + err := row.Scan(&i.AuthorID, &i.Name) + return i, err +} diff --git a/examples/group/mysql/mygroup/books.sql.go b/examples/group/mysql/mygroup/books.sql.go new file mode 100644 index 0000000000..9f41ba8cb4 --- /dev/null +++ b/examples/group/mysql/mygroup/books.sql.go @@ -0,0 +1,237 @@ +// Code generated by sqlc. DO NOT EDIT. +// source: books.sql + +package mygroup + +import ( + "context" + "database/sql" + "time" +) + +type Books struct { + db DBTX +} + +func NewBooks(db DBTX) *Books { + return &Books{db: db} +} + +const booksCreate = `-- name: Create :execresult +INSERT INTO books ( + author_id, + isbn, + book_type, + title, + yr, + available, + tags +) VALUES ( + ?, + ?, + ?, + ?, + ?, + ?, + ? +) +` + +type BooksCreateParams struct { + AuthorID int32 + Isbn string + BookType BooksBookType + Title string + Yr int32 + Available time.Time + Tags string +} + +func (q *Books) Create(ctx context.Context, arg BooksCreateParams) (sql.Result, error) { + return q.db.ExecContext(ctx, booksCreate, + arg.AuthorID, + arg.Isbn, + arg.BookType, + arg.Title, + arg.Yr, + arg.Available, + arg.Tags, + ) +} + +const booksDelete = `-- name: Delete :exec +DELETE FROM books +WHERE book_id = ? +` + +func (q *Books) Delete(ctx context.Context, bookID int32) error { + _, err := q.db.ExecContext(ctx, booksDelete, bookID) + return err +} + +const booksDeleteAuthorBeforeYear = `-- name: DeleteAuthorBeforeYear :exec +DELETE FROM books +WHERE yr < ? AND author_id = ? +` + +type BooksDeleteAuthorBeforeYearParams struct { + Yr int32 + AuthorID int32 +} + +func (q *Books) DeleteAuthorBeforeYear(ctx context.Context, arg BooksDeleteAuthorBeforeYearParams) error { + _, err := q.db.ExecContext(ctx, booksDeleteAuthorBeforeYear, arg.Yr, arg.AuthorID) + return err +} + +const booksGet = `-- name: Get :one +SELECT book_id, author_id, isbn, book_type, title, yr, available, tags FROM books +WHERE book_id = ? +` + +func (q *Books) Get(ctx context.Context, bookID int32) (Book, error) { + row := q.db.QueryRowContext(ctx, booksGet, bookID) + var i Book + err := row.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Yr, + &i.Available, + &i.Tags, + ) + return i, err +} + +const booksListByTags = `-- name: ListByTags :many +SELECT + book_id, + title, + name, + isbn, + tags +FROM books +LEFT JOIN authors ON books.author_id = authors.author_id +WHERE tags = ? +` + +type BooksListByTagsRow struct { + BookID int32 + Title string + Name string + Isbn string + Tags string +} + +func (q *Books) ListByTags(ctx context.Context, tags string) ([]BooksListByTagsRow, error) { + rows, err := q.db.QueryContext(ctx, booksListByTags, tags) + if err != nil { + return nil, err + } + defer rows.Close() + items := []BooksListByTagsRow{} + for rows.Next() { + var i BooksListByTagsRow + if err := rows.Scan( + &i.BookID, + &i.Title, + &i.Name, + &i.Isbn, + &i.Tags, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const booksListByTitleYear = `-- name: ListByTitleYear :many +SELECT book_id, author_id, isbn, book_type, title, yr, available, tags FROM books +WHERE title = ? AND yr = ? +` + +type BooksListByTitleYearParams struct { + Title string + Yr int32 +} + +func (q *Books) ListByTitleYear(ctx context.Context, arg BooksListByTitleYearParams) ([]Book, error) { + rows, err := q.db.QueryContext(ctx, booksListByTitleYear, arg.Title, arg.Yr) + if err != nil { + return nil, err + } + defer rows.Close() + items := []Book{} + for rows.Next() { + var i Book + if err := rows.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Yr, + &i.Available, + &i.Tags, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const booksUpdate = `-- name: Update :exec +UPDATE books +SET title = ?, tags = ? +WHERE book_id = ? +` + +type BooksUpdateParams struct { + Title string + Tags string + BookID int32 +} + +func (q *Books) Update(ctx context.Context, arg BooksUpdateParams) error { + _, err := q.db.ExecContext(ctx, booksUpdate, arg.Title, arg.Tags, arg.BookID) + return err +} + +const booksUpdateISBN = `-- name: UpdateISBN :exec +UPDATE books +SET title = ?, tags = ?, isbn = ? +WHERE book_id = ? +` + +type BooksUpdateISBNParams struct { + Title string + Tags string + Isbn string + BookID int32 +} + +func (q *Books) UpdateISBN(ctx context.Context, arg BooksUpdateISBNParams) error { + _, err := q.db.ExecContext(ctx, booksUpdateISBN, + arg.Title, + arg.Tags, + arg.Isbn, + arg.BookID, + ) + return err +} diff --git a/examples/group/mysql/mygroup/db.go b/examples/group/mysql/mygroup/db.go new file mode 100644 index 0000000000..0ed5230986 --- /dev/null +++ b/examples/group/mysql/mygroup/db.go @@ -0,0 +1,15 @@ +// Code generated by sqlc. DO NOT EDIT. + +package mygroup + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} diff --git a/examples/group/mysql/mygroup/db_test.go b/examples/group/mysql/mygroup/db_test.go new file mode 100644 index 0000000000..908bd81494 --- /dev/null +++ b/examples/group/mysql/mygroup/db_test.go @@ -0,0 +1,168 @@ +// +build examples + +package mygroup_test + +import ( + "context" + "testing" + "time" + + "github.com/kyleconroy/sqlc/examples/group/mysql/mygroup" + "github.com/kyleconroy/sqlc/internal/sqltest" +) + +func TestBooks(t *testing.T) { + db, cleanup := sqltest.MySQL(t, []string{"../sql/schema/schema.sql"}) + defer cleanup() + + ctx := context.Background() + + dbauthors := mygroup.NewAuthors(db) + + // create an author + result, err := dbauthors.Create(ctx, "Unknown Master") + if err != nil { + t.Fatal(err) + } + authorID, err := result.LastInsertId() + if err != nil { + t.Fatal(err) + } + + // create transaction + tx, err := db.Begin() + if err != nil { + t.Fatal(err) + } + + txbooks := mygroup.NewBooks(tx) + + // save first book + now := time.Now() + _, err = txbooks.Create(ctx, mygroup.BooksCreateParams{ + AuthorID: int32(authorID), + Isbn: "1", + Title: "my book title", + BookType: mygroup.BooksBookTypeFICTION, + Yr: 2016, + Available: now, + }) + if err != nil { + t.Fatal(err) + } + + // save second book + result, err = txbooks.Create(ctx, mygroup.BooksCreateParams{ + AuthorID: int32(authorID), + Isbn: "2", + Title: "the second book", + BookType: mygroup.BooksBookTypeFICTION, + Yr: 2016, + Available: now, + Tags: "cool,unique", + }) + if err != nil { + t.Fatal(err) + } + bookOneID, err := result.LastInsertId() + if err != nil { + t.Fatal(err) + } + + // update the title and tags + err = txbooks.Update(ctx, mygroup.BooksUpdateParams{ + BookID: int32(bookOneID), + Title: "changed second title", + Tags: "cool,disastor", + }) + if err != nil { + t.Fatal(err) + } + + // save third book + _, err = txbooks.Create(ctx, mygroup.BooksCreateParams{ + AuthorID: int32(authorID), + Isbn: "3", + Title: "the third book", + BookType: mygroup.BooksBookTypeFICTION, + Yr: 2001, + Available: now, + Tags: "cool", + }) + if err != nil { + t.Fatal(err) + } + + // save fourth book + result, err = txbooks.Create(ctx, mygroup.BooksCreateParams{ + AuthorID: int32(authorID), + Isbn: "4", + Title: "4th place finisher", + BookType: mygroup.BooksBookTypeNONFICTION, + Yr: 2011, + Available: now, + Tags: "other", + }) + if err != nil { + t.Fatal(err) + } + bookThreeID, err := result.LastInsertId() + if err != nil { + t.Fatal(err) + } + + // tx commit + err = tx.Commit() + if err != nil { + t.Fatal(err) + } + + dbbooks := mygroup.NewBooks(db) + + // upsert, changing ISBN and title + err = dbbooks.UpdateISBN(ctx, mygroup.BooksUpdateISBNParams{ + BookID: int32(bookThreeID), + Isbn: "NEW ISBN", + Title: "never ever gonna finish, a quatrain", + Tags: "someother", + }) + if err != nil { + t.Fatal(err) + } + + // retrieve first book + books0, err := dbbooks.ListByTitleYear(ctx, mygroup.BooksListByTitleYearParams{ + Title: "my book title", + Yr: 2016, + }) + if err != nil { + t.Fatal(err) + } + for _, book := range books0 { + t.Logf("Book %d (%s): %s available: %s\n", book.BookID, book.BookType, book.Title, book.Available.Format(time.RFC822Z)) + author, err := dbauthors.Get(ctx, book.AuthorID) + if err != nil { + t.Fatal(err) + } + t.Logf("Book %d author: %s\n", book.BookID, author.Name) + } + + // find a book with either "cool" or "other" tag + t.Logf("---------\nTag search results:\n") + res, err := dbbooks.ListByTags(ctx, "cool") + if err != nil { + t.Fatal(err) + } + for _, ab := range res { + t.Logf("Book %d: '%s', Author: '%s', ISBN: '%s' Tags: '%v'\n", ab.BookID, ab.Title, ab.Name, ab.Isbn, ab.Tags) + } + + // get book 4 and delete + b5, err := dbbooks.Get(ctx, int32(bookThreeID)) + if err != nil { + t.Fatal(err) + } + if err := dbbooks.Delete(ctx, b5.BookID); err != nil { + t.Fatal(err) + } +} diff --git a/examples/group/mysql/mygroup/models.go b/examples/group/mysql/mygroup/models.go new file mode 100644 index 0000000000..32403333d3 --- /dev/null +++ b/examples/group/mysql/mygroup/models.go @@ -0,0 +1,43 @@ +// Code generated by sqlc. DO NOT EDIT. + +package mygroup + +import ( + "fmt" + "time" +) + +type BooksBookType string + +const ( + BooksBookTypeFICTION BooksBookType = "FICTION" + BooksBookTypeNONFICTION BooksBookType = "NONFICTION" +) + +func (e *BooksBookType) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = BooksBookType(s) + case string: + *e = BooksBookType(s) + default: + return fmt.Errorf("unsupported scan type for BooksBookType: %T", src) + } + return nil +} + +type Author struct { + AuthorID int32 + Name string +} + +type Book struct { + BookID int32 + AuthorID int32 + Isbn string + BookType BooksBookType + Title string + Yr int32 + Available time.Time + Tags string +} diff --git a/examples/group/mysql/mygroupiface/authors.sql.go b/examples/group/mysql/mygroupiface/authors.sql.go new file mode 100644 index 0000000000..5df9a7fd63 --- /dev/null +++ b/examples/group/mysql/mygroupiface/authors.sql.go @@ -0,0 +1,42 @@ +// Code generated by sqlc. DO NOT EDIT. +// source: authors.sql + +package mygroupiface + +import ( + "context" + "database/sql" +) + +type Authors interface { + Create(ctx context.Context, name string) (sql.Result, error) + Get(ctx context.Context, authorID int32) (Author, error) +} + +func NewAuthors(db DBTX) Authors { + return &authors{db: db} +} + +type authors struct { + db DBTX +} + +const authorsCreate = `-- name: Create :execresult +INSERT INTO authors (name) VALUES (?) +` + +func (q *authors) Create(ctx context.Context, name string) (sql.Result, error) { + return q.db.ExecContext(ctx, authorsCreate, name) +} + +const authorsGet = `-- name: Get :one +SELECT author_id, name FROM authors +WHERE author_id = ? +` + +func (q *authors) Get(ctx context.Context, authorID int32) (Author, error) { + row := q.db.QueryRowContext(ctx, authorsGet, authorID) + var i Author + err := row.Scan(&i.AuthorID, &i.Name) + return i, err +} diff --git a/examples/group/mysql/mygroupiface/books.sql.go b/examples/group/mysql/mygroupiface/books.sql.go new file mode 100644 index 0000000000..7e00715db9 --- /dev/null +++ b/examples/group/mysql/mygroupiface/books.sql.go @@ -0,0 +1,248 @@ +// Code generated by sqlc. DO NOT EDIT. +// source: books.sql + +package mygroupiface + +import ( + "context" + "database/sql" + "time" +) + +type Books interface { + Create(ctx context.Context, arg BooksCreateParams) (sql.Result, error) + Delete(ctx context.Context, bookID int32) error + DeleteAuthorBeforeYear(ctx context.Context, arg BooksDeleteAuthorBeforeYearParams) error + Get(ctx context.Context, bookID int32) (Book, error) + ListByTags(ctx context.Context, tags string) ([]BooksListByTagsRow, error) + ListByTitleYear(ctx context.Context, arg BooksListByTitleYearParams) ([]Book, error) + Update(ctx context.Context, arg BooksUpdateParams) error + UpdateISBN(ctx context.Context, arg BooksUpdateISBNParams) error +} + +func NewBooks(db DBTX) Books { + return &books{db: db} +} + +type books struct { + db DBTX +} + +const booksCreate = `-- name: Create :execresult +INSERT INTO books ( + author_id, + isbn, + book_type, + title, + yr, + available, + tags +) VALUES ( + ?, + ?, + ?, + ?, + ?, + ?, + ? +) +` + +type BooksCreateParams struct { + AuthorID int32 + Isbn string + BookType BooksBookType + Title string + Yr int32 + Available time.Time + Tags string +} + +func (q *books) Create(ctx context.Context, arg BooksCreateParams) (sql.Result, error) { + return q.db.ExecContext(ctx, booksCreate, + arg.AuthorID, + arg.Isbn, + arg.BookType, + arg.Title, + arg.Yr, + arg.Available, + arg.Tags, + ) +} + +const booksDelete = `-- name: Delete :exec +DELETE FROM books +WHERE book_id = ? +` + +func (q *books) Delete(ctx context.Context, bookID int32) error { + _, err := q.db.ExecContext(ctx, booksDelete, bookID) + return err +} + +const booksDeleteAuthorBeforeYear = `-- name: DeleteAuthorBeforeYear :exec +DELETE FROM books +WHERE yr < ? AND author_id = ? +` + +type BooksDeleteAuthorBeforeYearParams struct { + Yr int32 + AuthorID int32 +} + +func (q *books) DeleteAuthorBeforeYear(ctx context.Context, arg BooksDeleteAuthorBeforeYearParams) error { + _, err := q.db.ExecContext(ctx, booksDeleteAuthorBeforeYear, arg.Yr, arg.AuthorID) + return err +} + +const booksGet = `-- name: Get :one +SELECT book_id, author_id, isbn, book_type, title, yr, available, tags FROM books +WHERE book_id = ? +` + +func (q *books) Get(ctx context.Context, bookID int32) (Book, error) { + row := q.db.QueryRowContext(ctx, booksGet, bookID) + var i Book + err := row.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Yr, + &i.Available, + &i.Tags, + ) + return i, err +} + +const booksListByTags = `-- name: ListByTags :many +SELECT + book_id, + title, + name, + isbn, + tags +FROM books +LEFT JOIN authors ON books.author_id = authors.author_id +WHERE tags = ? +` + +type BooksListByTagsRow struct { + BookID int32 + Title string + Name string + Isbn string + Tags string +} + +func (q *books) ListByTags(ctx context.Context, tags string) ([]BooksListByTagsRow, error) { + rows, err := q.db.QueryContext(ctx, booksListByTags, tags) + if err != nil { + return nil, err + } + defer rows.Close() + items := []BooksListByTagsRow{} + for rows.Next() { + var i BooksListByTagsRow + if err := rows.Scan( + &i.BookID, + &i.Title, + &i.Name, + &i.Isbn, + &i.Tags, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const booksListByTitleYear = `-- name: ListByTitleYear :many +SELECT book_id, author_id, isbn, book_type, title, yr, available, tags FROM books +WHERE title = ? AND yr = ? +` + +type BooksListByTitleYearParams struct { + Title string + Yr int32 +} + +func (q *books) ListByTitleYear(ctx context.Context, arg BooksListByTitleYearParams) ([]Book, error) { + rows, err := q.db.QueryContext(ctx, booksListByTitleYear, arg.Title, arg.Yr) + if err != nil { + return nil, err + } + defer rows.Close() + items := []Book{} + for rows.Next() { + var i Book + if err := rows.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Yr, + &i.Available, + &i.Tags, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const booksUpdate = `-- name: Update :exec +UPDATE books +SET title = ?, tags = ? +WHERE book_id = ? +` + +type BooksUpdateParams struct { + Title string + Tags string + BookID int32 +} + +func (q *books) Update(ctx context.Context, arg BooksUpdateParams) error { + _, err := q.db.ExecContext(ctx, booksUpdate, arg.Title, arg.Tags, arg.BookID) + return err +} + +const booksUpdateISBN = `-- name: UpdateISBN :exec +UPDATE books +SET title = ?, tags = ?, isbn = ? +WHERE book_id = ? +` + +type BooksUpdateISBNParams struct { + Title string + Tags string + Isbn string + BookID int32 +} + +func (q *books) UpdateISBN(ctx context.Context, arg BooksUpdateISBNParams) error { + _, err := q.db.ExecContext(ctx, booksUpdateISBN, + arg.Title, + arg.Tags, + arg.Isbn, + arg.BookID, + ) + return err +} diff --git a/examples/group/mysql/mygroupiface/db.go b/examples/group/mysql/mygroupiface/db.go new file mode 100644 index 0000000000..168ccff1fd --- /dev/null +++ b/examples/group/mysql/mygroupiface/db.go @@ -0,0 +1,15 @@ +// Code generated by sqlc. DO NOT EDIT. + +package mygroupiface + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} diff --git a/examples/group/mysql/mygroupiface/db_test.go b/examples/group/mysql/mygroupiface/db_test.go new file mode 100644 index 0000000000..be2a3885c6 --- /dev/null +++ b/examples/group/mysql/mygroupiface/db_test.go @@ -0,0 +1,168 @@ +// +build examples + +package mygroupiface_test + +import ( + "context" + "testing" + "time" + + "github.com/kyleconroy/sqlc/examples/group/mysql/mygroupiface" + "github.com/kyleconroy/sqlc/internal/sqltest" +) + +func TestBooks(t *testing.T) { + db, cleanup := sqltest.MySQL(t, []string{"../sql/schema/schema.sql"}) + defer cleanup() + + ctx := context.Background() + + dbauthors := mygroupiface.NewAuthors(db) + + // create an author + result, err := dbauthors.Create(ctx, "Unknown Master") + if err != nil { + t.Fatal(err) + } + authorID, err := result.LastInsertId() + if err != nil { + t.Fatal(err) + } + + // create transaction + tx, err := db.Begin() + if err != nil { + t.Fatal(err) + } + + txbooks := mygroupiface.NewBooks(tx) + + // save first book + now := time.Now() + _, err = txbooks.Create(ctx, mygroupiface.BooksCreateParams{ + AuthorID: int32(authorID), + Isbn: "1", + Title: "my book title", + BookType: mygroupiface.BooksBookTypeFICTION, + Yr: 2016, + Available: now, + }) + if err != nil { + t.Fatal(err) + } + + // save second book + result, err = txbooks.Create(ctx, mygroupiface.BooksCreateParams{ + AuthorID: int32(authorID), + Isbn: "2", + Title: "the second book", + BookType: mygroupiface.BooksBookTypeFICTION, + Yr: 2016, + Available: now, + Tags: "cool,unique", + }) + if err != nil { + t.Fatal(err) + } + bookOneID, err := result.LastInsertId() + if err != nil { + t.Fatal(err) + } + + // update the title and tags + err = txbooks.Update(ctx, mygroupiface.BooksUpdateParams{ + BookID: int32(bookOneID), + Title: "changed second title", + Tags: "cool,disastor", + }) + if err != nil { + t.Fatal(err) + } + + // save third book + _, err = txbooks.Create(ctx, mygroupiface.BooksCreateParams{ + AuthorID: int32(authorID), + Isbn: "3", + Title: "the third book", + BookType: mygroupiface.BooksBookTypeFICTION, + Yr: 2001, + Available: now, + Tags: "cool", + }) + if err != nil { + t.Fatal(err) + } + + // save fourth book + result, err = txbooks.Create(ctx, mygroupiface.BooksCreateParams{ + AuthorID: int32(authorID), + Isbn: "4", + Title: "4th place finisher", + BookType: mygroupiface.BooksBookTypeNONFICTION, + Yr: 2011, + Available: now, + Tags: "other", + }) + if err != nil { + t.Fatal(err) + } + bookThreeID, err := result.LastInsertId() + if err != nil { + t.Fatal(err) + } + + // tx commit + err = tx.Commit() + if err != nil { + t.Fatal(err) + } + + dbbooks := mygroupiface.NewBooks(db) + + // upsert, changing ISBN and title + err = dbbooks.UpdateISBN(ctx, mygroupiface.BooksUpdateISBNParams{ + BookID: int32(bookThreeID), + Isbn: "NEW ISBN", + Title: "never ever gonna finish, a quatrain", + Tags: "someother", + }) + if err != nil { + t.Fatal(err) + } + + // retrieve first book + books0, err := dbbooks.ListByTitleYear(ctx, mygroupiface.BooksListByTitleYearParams{ + Title: "my book title", + Yr: 2016, + }) + if err != nil { + t.Fatal(err) + } + for _, book := range books0 { + t.Logf("Book %d (%s): %s available: %s\n", book.BookID, book.BookType, book.Title, book.Available.Format(time.RFC822Z)) + author, err := dbauthors.Get(ctx, book.AuthorID) + if err != nil { + t.Fatal(err) + } + t.Logf("Book %d author: %s\n", book.BookID, author.Name) + } + + // find a book with either "cool" or "other" tag + t.Logf("---------\nTag search results:\n") + res, err := dbbooks.ListByTags(ctx, "cool") + if err != nil { + t.Fatal(err) + } + for _, ab := range res { + t.Logf("Book %d: '%s', Author: '%s', ISBN: '%s' Tags: '%v'\n", ab.BookID, ab.Title, ab.Name, ab.Isbn, ab.Tags) + } + + // get book 4 and delete + b5, err := dbbooks.Get(ctx, int32(bookThreeID)) + if err != nil { + t.Fatal(err) + } + if err := dbbooks.Delete(ctx, b5.BookID); err != nil { + t.Fatal(err) + } +} diff --git a/examples/group/mysql/mygroupiface/models.go b/examples/group/mysql/mygroupiface/models.go new file mode 100644 index 0000000000..76b3309407 --- /dev/null +++ b/examples/group/mysql/mygroupiface/models.go @@ -0,0 +1,43 @@ +// Code generated by sqlc. DO NOT EDIT. + +package mygroupiface + +import ( + "fmt" + "time" +) + +type BooksBookType string + +const ( + BooksBookTypeFICTION BooksBookType = "FICTION" + BooksBookTypeNONFICTION BooksBookType = "NONFICTION" +) + +func (e *BooksBookType) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = BooksBookType(s) + case string: + *e = BooksBookType(s) + default: + return fmt.Errorf("unsupported scan type for BooksBookType: %T", src) + } + return nil +} + +type Author struct { + AuthorID int32 + Name string +} + +type Book struct { + BookID int32 + AuthorID int32 + Isbn string + BookType BooksBookType + Title string + Yr int32 + Available time.Time + Tags string +} diff --git a/examples/group/mysql/mygroupstmt/authors.sql.go b/examples/group/mysql/mygroupstmt/authors.sql.go new file mode 100644 index 0000000000..82cd7a0bff --- /dev/null +++ b/examples/group/mysql/mygroupstmt/authors.sql.go @@ -0,0 +1,90 @@ +// Code generated by sqlc. DO NOT EDIT. +// source: authors.sql + +package mygroupstmt + +import ( + "context" + "database/sql" +) + +type Authors struct { + db DBTX +} + +func NewAuthors(db DBTX) *Authors { + return &Authors{db: db} +} + +const authorsCreate = `-- name: Create :execresult +INSERT INTO authors (name) VALUES (?) +` + +func (q *Authors) Create(ctx context.Context, name string) (sql.Result, error) { + return q.db.ExecContext(ctx, authorsCreate, name) +} + +type AuthorsCreateStmt struct { + stmt *sql.Stmt +} + +func (s *AuthorsCreateStmt) Close() error { + return s.stmt.Close() +} + +func (s *AuthorsCreateStmt) JoinTx(ctx context.Context, tx *sql.Tx) *AuthorsCreateStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &AuthorsCreateStmt{stmt: stmt} +} + +func (s *AuthorsCreateStmt) Exec(ctx context.Context, name string) (sql.Result, error) { + return s.stmt.ExecContext(ctx, name) +} + +func (q *Authors) PrepareCreate(ctx context.Context) (*AuthorsCreateStmt, error) { + stmt, err := q.db.PrepareContext(ctx, authorsCreate) + if err != nil { + return nil, err + } + return &AuthorsCreateStmt{stmt: stmt}, nil +} + +const authorsGet = `-- name: Get :one +SELECT author_id, name FROM authors +WHERE author_id = ? +` + +func (q *Authors) Get(ctx context.Context, authorID int32) (Author, error) { + row := q.db.QueryRowContext(ctx, authorsGet, authorID) + var i Author + err := row.Scan(&i.AuthorID, &i.Name) + return i, err +} + +type AuthorsGetStmt struct { + stmt *sql.Stmt +} + +func (s *AuthorsGetStmt) Close() error { + return s.stmt.Close() +} + +func (s *AuthorsGetStmt) JoinTx(ctx context.Context, tx *sql.Tx) *AuthorsGetStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &AuthorsGetStmt{stmt: stmt} +} + +func (s *AuthorsGetStmt) Exec(ctx context.Context, authorID int32) (Author, error) { + row := s.stmt.QueryRowContext(ctx, authorID) + var i Author + err := row.Scan(&i.AuthorID, &i.Name) + return i, err +} + +func (q *Authors) PrepareGet(ctx context.Context) (*AuthorsGetStmt, error) { + stmt, err := q.db.PrepareContext(ctx, authorsGet) + if err != nil { + return nil, err + } + return &AuthorsGetStmt{stmt: stmt}, nil +} diff --git a/examples/group/mysql/mygroupstmt/books.sql.go b/examples/group/mysql/mygroupstmt/books.sql.go new file mode 100644 index 0000000000..2bf0970cbd --- /dev/null +++ b/examples/group/mysql/mygroupstmt/books.sql.go @@ -0,0 +1,519 @@ +// Code generated by sqlc. DO NOT EDIT. +// source: books.sql + +package mygroupstmt + +import ( + "context" + "database/sql" + "time" +) + +type Books struct { + db DBTX +} + +func NewBooks(db DBTX) *Books { + return &Books{db: db} +} + +const booksCreate = `-- name: Create :execresult +INSERT INTO books ( + author_id, + isbn, + book_type, + title, + yr, + available, + tags +) VALUES ( + ?, + ?, + ?, + ?, + ?, + ?, + ? +) +` + +type BooksCreateParams struct { + AuthorID int32 + Isbn string + BookType BooksBookType + Title string + Yr int32 + Available time.Time + Tags string +} + +func (q *Books) Create(ctx context.Context, arg BooksCreateParams) (sql.Result, error) { + return q.db.ExecContext(ctx, booksCreate, + arg.AuthorID, + arg.Isbn, + arg.BookType, + arg.Title, + arg.Yr, + arg.Available, + arg.Tags, + ) +} + +type BooksCreateStmt struct { + stmt *sql.Stmt +} + +func (s *BooksCreateStmt) Close() error { + return s.stmt.Close() +} + +func (s *BooksCreateStmt) JoinTx(ctx context.Context, tx *sql.Tx) *BooksCreateStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &BooksCreateStmt{stmt: stmt} +} + +func (s *BooksCreateStmt) Exec(ctx context.Context, arg BooksCreateParams) (sql.Result, error) { + return s.stmt.ExecContext(ctx, + arg.AuthorID, + arg.Isbn, + arg.BookType, + arg.Title, + arg.Yr, + arg.Available, + arg.Tags, + ) +} + +func (q *Books) PrepareCreate(ctx context.Context) (*BooksCreateStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksCreate) + if err != nil { + return nil, err + } + return &BooksCreateStmt{stmt: stmt}, nil +} + +const booksDelete = `-- name: Delete :exec +DELETE FROM books +WHERE book_id = ? +` + +func (q *Books) Delete(ctx context.Context, bookID int32) error { + _, err := q.db.ExecContext(ctx, booksDelete, bookID) + return err +} + +type BooksDeleteStmt struct { + stmt *sql.Stmt +} + +func (s *BooksDeleteStmt) Close() error { + return s.stmt.Close() +} + +func (s *BooksDeleteStmt) JoinTx(ctx context.Context, tx *sql.Tx) *BooksDeleteStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &BooksDeleteStmt{stmt: stmt} +} + +func (s *BooksDeleteStmt) Exec(ctx context.Context, bookID int32) error { + _, err := s.stmt.ExecContext(ctx, bookID) + return err +} + +func (q *Books) PrepareDelete(ctx context.Context) (*BooksDeleteStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksDelete) + if err != nil { + return nil, err + } + return &BooksDeleteStmt{stmt: stmt}, nil +} + +const booksDeleteAuthorBeforeYear = `-- name: DeleteAuthorBeforeYear :exec +DELETE FROM books +WHERE yr < ? AND author_id = ? +` + +type BooksDeleteAuthorBeforeYearParams struct { + Yr int32 + AuthorID int32 +} + +func (q *Books) DeleteAuthorBeforeYear(ctx context.Context, arg BooksDeleteAuthorBeforeYearParams) error { + _, err := q.db.ExecContext(ctx, booksDeleteAuthorBeforeYear, arg.Yr, arg.AuthorID) + return err +} + +type BooksDeleteAuthorBeforeYearStmt struct { + stmt *sql.Stmt +} + +func (s *BooksDeleteAuthorBeforeYearStmt) Close() error { + return s.stmt.Close() +} + +func (s *BooksDeleteAuthorBeforeYearStmt) JoinTx(ctx context.Context, tx *sql.Tx) *BooksDeleteAuthorBeforeYearStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &BooksDeleteAuthorBeforeYearStmt{stmt: stmt} +} + +func (s *BooksDeleteAuthorBeforeYearStmt) Exec(ctx context.Context, arg BooksDeleteAuthorBeforeYearParams) error { + _, err := s.stmt.ExecContext(ctx, arg.Yr, arg.AuthorID) + return err +} + +func (q *Books) PrepareDeleteAuthorBeforeYear(ctx context.Context) (*BooksDeleteAuthorBeforeYearStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksDeleteAuthorBeforeYear) + if err != nil { + return nil, err + } + return &BooksDeleteAuthorBeforeYearStmt{stmt: stmt}, nil +} + +const booksGet = `-- name: Get :one +SELECT book_id, author_id, isbn, book_type, title, yr, available, tags FROM books +WHERE book_id = ? +` + +func (q *Books) Get(ctx context.Context, bookID int32) (Book, error) { + row := q.db.QueryRowContext(ctx, booksGet, bookID) + var i Book + err := row.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Yr, + &i.Available, + &i.Tags, + ) + return i, err +} + +type BooksGetStmt struct { + stmt *sql.Stmt +} + +func (s *BooksGetStmt) Close() error { + return s.stmt.Close() +} + +func (s *BooksGetStmt) JoinTx(ctx context.Context, tx *sql.Tx) *BooksGetStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &BooksGetStmt{stmt: stmt} +} + +func (s *BooksGetStmt) Exec(ctx context.Context, bookID int32) (Book, error) { + row := s.stmt.QueryRowContext(ctx, bookID) + var i Book + err := row.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Yr, + &i.Available, + &i.Tags, + ) + return i, err +} + +func (q *Books) PrepareGet(ctx context.Context) (*BooksGetStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksGet) + if err != nil { + return nil, err + } + return &BooksGetStmt{stmt: stmt}, nil +} + +const booksListByTags = `-- name: ListByTags :many +SELECT + book_id, + title, + name, + isbn, + tags +FROM books +LEFT JOIN authors ON books.author_id = authors.author_id +WHERE tags = ? +` + +type BooksListByTagsRow struct { + BookID int32 + Title string + Name string + Isbn string + Tags string +} + +func (q *Books) ListByTags(ctx context.Context, tags string) ([]BooksListByTagsRow, error) { + rows, err := q.db.QueryContext(ctx, booksListByTags, tags) + if err != nil { + return nil, err + } + defer rows.Close() + items := []BooksListByTagsRow{} + for rows.Next() { + var i BooksListByTagsRow + if err := rows.Scan( + &i.BookID, + &i.Title, + &i.Name, + &i.Isbn, + &i.Tags, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +type BooksListByTagsStmt struct { + stmt *sql.Stmt +} + +func (s *BooksListByTagsStmt) Close() error { + return s.stmt.Close() +} + +func (s *BooksListByTagsStmt) JoinTx(ctx context.Context, tx *sql.Tx) *BooksListByTagsStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &BooksListByTagsStmt{stmt: stmt} +} + +func (s *BooksListByTagsStmt) Exec(ctx context.Context, tags string) ([]BooksListByTagsRow, error) { + rows, err := s.stmt.QueryContext(ctx, tags) + if err != nil { + return nil, err + } + defer rows.Close() + items := []BooksListByTagsRow{} + for rows.Next() { + var i BooksListByTagsRow + if err := rows.Scan( + &i.BookID, + &i.Title, + &i.Name, + &i.Isbn, + &i.Tags, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +func (q *Books) PrepareListByTags(ctx context.Context) (*BooksListByTagsStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksListByTags) + if err != nil { + return nil, err + } + return &BooksListByTagsStmt{stmt: stmt}, nil +} + +const booksListByTitleYear = `-- name: ListByTitleYear :many +SELECT book_id, author_id, isbn, book_type, title, yr, available, tags FROM books +WHERE title = ? AND yr = ? +` + +type BooksListByTitleYearParams struct { + Title string + Yr int32 +} + +func (q *Books) ListByTitleYear(ctx context.Context, arg BooksListByTitleYearParams) ([]Book, error) { + rows, err := q.db.QueryContext(ctx, booksListByTitleYear, arg.Title, arg.Yr) + if err != nil { + return nil, err + } + defer rows.Close() + items := []Book{} + for rows.Next() { + var i Book + if err := rows.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Yr, + &i.Available, + &i.Tags, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +type BooksListByTitleYearStmt struct { + stmt *sql.Stmt +} + +func (s *BooksListByTitleYearStmt) Close() error { + return s.stmt.Close() +} + +func (s *BooksListByTitleYearStmt) JoinTx(ctx context.Context, tx *sql.Tx) *BooksListByTitleYearStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &BooksListByTitleYearStmt{stmt: stmt} +} + +func (s *BooksListByTitleYearStmt) Exec(ctx context.Context, arg BooksListByTitleYearParams) ([]Book, error) { + rows, err := s.stmt.QueryContext(ctx, arg.Title, arg.Yr) + if err != nil { + return nil, err + } + defer rows.Close() + items := []Book{} + for rows.Next() { + var i Book + if err := rows.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Yr, + &i.Available, + &i.Tags, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +func (q *Books) PrepareListByTitleYear(ctx context.Context) (*BooksListByTitleYearStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksListByTitleYear) + if err != nil { + return nil, err + } + return &BooksListByTitleYearStmt{stmt: stmt}, nil +} + +const booksUpdate = `-- name: Update :exec +UPDATE books +SET title = ?, tags = ? +WHERE book_id = ? +` + +type BooksUpdateParams struct { + Title string + Tags string + BookID int32 +} + +func (q *Books) Update(ctx context.Context, arg BooksUpdateParams) error { + _, err := q.db.ExecContext(ctx, booksUpdate, arg.Title, arg.Tags, arg.BookID) + return err +} + +type BooksUpdateStmt struct { + stmt *sql.Stmt +} + +func (s *BooksUpdateStmt) Close() error { + return s.stmt.Close() +} + +func (s *BooksUpdateStmt) JoinTx(ctx context.Context, tx *sql.Tx) *BooksUpdateStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &BooksUpdateStmt{stmt: stmt} +} + +func (s *BooksUpdateStmt) Exec(ctx context.Context, arg BooksUpdateParams) error { + _, err := s.stmt.ExecContext(ctx, arg.Title, arg.Tags, arg.BookID) + return err +} + +func (q *Books) PrepareUpdate(ctx context.Context) (*BooksUpdateStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksUpdate) + if err != nil { + return nil, err + } + return &BooksUpdateStmt{stmt: stmt}, nil +} + +const booksUpdateISBN = `-- name: UpdateISBN :exec +UPDATE books +SET title = ?, tags = ?, isbn = ? +WHERE book_id = ? +` + +type BooksUpdateISBNParams struct { + Title string + Tags string + Isbn string + BookID int32 +} + +func (q *Books) UpdateISBN(ctx context.Context, arg BooksUpdateISBNParams) error { + _, err := q.db.ExecContext(ctx, booksUpdateISBN, + arg.Title, + arg.Tags, + arg.Isbn, + arg.BookID, + ) + return err +} + +type BooksUpdateISBNStmt struct { + stmt *sql.Stmt +} + +func (s *BooksUpdateISBNStmt) Close() error { + return s.stmt.Close() +} + +func (s *BooksUpdateISBNStmt) JoinTx(ctx context.Context, tx *sql.Tx) *BooksUpdateISBNStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &BooksUpdateISBNStmt{stmt: stmt} +} + +func (s *BooksUpdateISBNStmt) Exec(ctx context.Context, arg BooksUpdateISBNParams) error { + _, err := s.stmt.ExecContext(ctx, + arg.Title, + arg.Tags, + arg.Isbn, + arg.BookID, + ) + return err +} + +func (q *Books) PrepareUpdateISBN(ctx context.Context) (*BooksUpdateISBNStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksUpdateISBN) + if err != nil { + return nil, err + } + return &BooksUpdateISBNStmt{stmt: stmt}, nil +} diff --git a/examples/group/mysql/mygroupstmt/db.go b/examples/group/mysql/mygroupstmt/db.go new file mode 100644 index 0000000000..202999c40f --- /dev/null +++ b/examples/group/mysql/mygroupstmt/db.go @@ -0,0 +1,15 @@ +// Code generated by sqlc. DO NOT EDIT. + +package mygroupstmt + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} diff --git a/examples/group/mysql/mygroupstmt/db_test.go b/examples/group/mysql/mygroupstmt/db_test.go new file mode 100644 index 0000000000..bc64cafd09 --- /dev/null +++ b/examples/group/mysql/mygroupstmt/db_test.go @@ -0,0 +1,321 @@ +// +build examples + +package mygroupstmt_test + +import ( + "context" + "database/sql" + "testing" + "time" + + "github.com/kyleconroy/sqlc/examples/group/mysql/mygroupstmt" + "github.com/kyleconroy/sqlc/internal/sqltest" +) + +func TestBooks(t *testing.T) { + db, cleanup := sqltest.MySQL(t, []string{"../sql/schema/schema.sql"}) + defer cleanup() + + ctx := context.Background() + + // create an author + authorID, err := func() (retID int32, retErr error) { + dbauthors := mygroupstmt.NewAuthors(db) + dbcreate, err := dbauthors.PrepareCreate(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := dbcreate.Close(); err != nil && retErr == nil { + retErr = err + } + }() + result, err := dbcreate.Exec(ctx, "Unknown Master") + if err != nil { + retErr = err + return + } + authorID, err := result.LastInsertId() + if err != nil { + retErr = err + return + } + retID = int32(authorID) + return + }() + if err != nil { + t.Fatal(err) + } + + // create transaction + bookThreeID, err := func() (retID int32, retErr error) { + tx, err := db.Begin() + if err != nil { + retErr = err + return + } + defer func() { + if err := tx.Rollback(); err != nil && err != sql.ErrTxDone && retErr == nil { + retErr = err + } + }() + + txbooks := mygroupstmt.NewBooks(tx) + + txcreate, err := txbooks.PrepareCreate(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := txcreate.Close(); err != nil && retErr == nil { + retErr = err + } + }() + + // save first book + now := time.Now() + _, err = txcreate.Exec(ctx, mygroupstmt.BooksCreateParams{ + AuthorID: authorID, + Isbn: "1", + Title: "my book title", + BookType: mygroupstmt.BooksBookTypeFICTION, + Yr: 2016, + Available: now, + }) + if err != nil { + retErr = err + return + } + + // save second book + result, err := txcreate.Exec(ctx, mygroupstmt.BooksCreateParams{ + AuthorID: authorID, + Isbn: "2", + Title: "the second book", + BookType: mygroupstmt.BooksBookTypeFICTION, + Yr: 2016, + Available: now, + Tags: "cool,unique", + }) + if err != nil { + retErr = err + return + } + bookOneID, err := result.LastInsertId() + if err != nil { + retErr = err + return + } + + // update the title and tags + txupdate, err := txbooks.PrepareUpdate(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := txupdate.Close(); err != nil && retErr == nil { + retErr = err + } + }() + err = txupdate.Exec(ctx, mygroupstmt.BooksUpdateParams{ + BookID: int32(bookOneID), + Title: "changed second title", + Tags: "cool,disastor", + }) + if err != nil { + retErr = err + return + } + + // save third book + _, err = txcreate.Exec(ctx, mygroupstmt.BooksCreateParams{ + AuthorID: authorID, + Isbn: "3", + Title: "the third book", + BookType: mygroupstmt.BooksBookTypeFICTION, + Yr: 2001, + Available: now, + Tags: "cool", + }) + if err != nil { + retErr = err + return + } + + // save fourth book + result, err = txcreate.Exec(ctx, mygroupstmt.BooksCreateParams{ + AuthorID: authorID, + Isbn: "4", + Title: "4th place finisher", + BookType: mygroupstmt.BooksBookTypeNONFICTION, + Yr: 2011, + Available: now, + Tags: "other", + }) + if err != nil { + retErr = err + return + } + bookThreeID, err := result.LastInsertId() + if err != nil { + retErr = err + return + } + + // tx commit + err = tx.Commit() + if err != nil { + retErr = err + return + } + retID = int32(bookThreeID) + return + }() + if err != nil { + t.Fatal(err) + } + + // upsert, changing ISBN and title + err = func() (retErr error) { + dbbooks := mygroupstmt.NewBooks(db) + dbupdate, err := dbbooks.PrepareUpdateISBN(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := dbupdate.Close(); err != nil && retErr == nil { + retErr = err + } + }() + retErr = dbupdate.Exec(ctx, mygroupstmt.BooksUpdateISBNParams{ + BookID: bookThreeID, + Isbn: "NEW ISBN", + Title: "never ever gonna finish, a quatrain", + Tags: "someother", + }) + return + }() + if err != nil { + t.Fatal(err) + } + + // retrieve first book + err = func() (retErr error) { + dbbooks := mygroupstmt.NewBooks(db) + dblist, err := dbbooks.PrepareListByTitleYear(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := dblist.Close(); err != nil && retErr == nil { + retErr = err + return + } + }() + books0, err := dblist.Exec(ctx, mygroupstmt.BooksListByTitleYearParams{ + Title: "my book title", + Yr: 2016, + }) + if err != nil { + retErr = err + return + } + dbauthors := mygroupstmt.NewAuthors(db) + dbget, err := dbauthors.PrepareGet(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := dbget.Close(); err != nil && retErr == nil { + retErr = err + return + } + }() + for _, book := range books0 { + t.Logf("Book %d (%s): %s available: %s\n", book.BookID, book.BookType, book.Title, book.Available.Format(time.RFC822Z)) + author, err := dbget.Exec(ctx, book.AuthorID) + if err != nil { + retErr = err + return + } + t.Logf("Book %d author: %s\n", book.BookID, author.Name) + } + return + }() + if err != nil { + t.Fatal(err) + } + + // find a book with either "cool" or "other" tag + err = func() (retErr error) { + t.Logf("---------\nTag search results:\n") + dbbooks := mygroupstmt.NewBooks(db) + dblist, err := dbbooks.PrepareListByTags(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := dblist.Close(); err != nil && retErr == nil { + retErr = err + return + } + }() + res, err := dblist.Exec(ctx, "cool") + if err != nil { + retErr = err + return + } + for _, ab := range res { + t.Logf("Book %d: '%s', Author: '%s', ISBN: '%s' Tags: '%v'\n", ab.BookID, ab.Title, ab.Name, ab.Isbn, ab.Tags) + } + return + }() + if err != nil { + t.Fatal(err) + } + + // get book 4 and delete + err = func() (retErr error) { + dbbooks := mygroupstmt.NewBooks(db) + dbget, err := dbbooks.PrepareGet(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := dbget.Close(); err != nil && retErr == nil { + retErr = err + } + }() + b5, err := dbget.Exec(ctx, bookThreeID) + if err != nil { + retErr = err + return + } + dbdelete, err := dbbooks.PrepareDelete(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := dbdelete.Close(); err != nil && retErr == nil { + retErr = err + } + }() + if err := dbdelete.Exec(ctx, b5.BookID); err != nil { + retErr = err + return + } + return + }() + if err != nil { + t.Fatal(err) + } +} diff --git a/examples/group/mysql/mygroupstmt/models.go b/examples/group/mysql/mygroupstmt/models.go new file mode 100644 index 0000000000..9233f859c7 --- /dev/null +++ b/examples/group/mysql/mygroupstmt/models.go @@ -0,0 +1,43 @@ +// Code generated by sqlc. DO NOT EDIT. + +package mygroupstmt + +import ( + "fmt" + "time" +) + +type BooksBookType string + +const ( + BooksBookTypeFICTION BooksBookType = "FICTION" + BooksBookTypeNONFICTION BooksBookType = "NONFICTION" +) + +func (e *BooksBookType) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = BooksBookType(s) + case string: + *e = BooksBookType(s) + default: + return fmt.Errorf("unsupported scan type for BooksBookType: %T", src) + } + return nil +} + +type Author struct { + AuthorID int32 + Name string +} + +type Book struct { + BookID int32 + AuthorID int32 + Isbn string + BookType BooksBookType + Title string + Yr int32 + Available time.Time + Tags string +} diff --git a/examples/group/mysql/mygroupstmtiface/authors.sql.go b/examples/group/mysql/mygroupstmtiface/authors.sql.go new file mode 100644 index 0000000000..7c9e0bfda6 --- /dev/null +++ b/examples/group/mysql/mygroupstmtiface/authors.sql.go @@ -0,0 +1,109 @@ +// Code generated by sqlc. DO NOT EDIT. +// source: authors.sql + +package mygroupstmtiface + +import ( + "context" + "database/sql" +) + +type Authors interface { + Create(ctx context.Context, name string) (sql.Result, error) + PrepareCreate(ctx context.Context) (AuthorsCreateStmt, error) + Get(ctx context.Context, authorID int32) (Author, error) + PrepareGet(ctx context.Context) (AuthorsGetStmt, error) +} + +func NewAuthors(db DBTX) Authors { + return &authors{db: db} +} + +type authors struct { + db DBTX +} + +const authorsCreate = `-- name: Create :execresult +INSERT INTO authors (name) VALUES (?) +` + +func (q *authors) Create(ctx context.Context, name string) (sql.Result, error) { + return q.db.ExecContext(ctx, authorsCreate, name) +} + +type AuthorsCreateStmt interface { + Close() error + JoinTx(ctx context.Context, tx *sql.Tx) AuthorsCreateStmt + Exec(ctx context.Context, name string) (sql.Result, error) +} + +type authorsCreateStmt struct { + stmt *sql.Stmt +} + +func (s *authorsCreateStmt) Close() error { + return s.stmt.Close() +} + +func (s *authorsCreateStmt) JoinTx(ctx context.Context, tx *sql.Tx) AuthorsCreateStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &authorsCreateStmt{stmt: stmt} +} + +func (s *authorsCreateStmt) Exec(ctx context.Context, name string) (sql.Result, error) { + return s.stmt.ExecContext(ctx, name) +} + +func (q *authors) PrepareCreate(ctx context.Context) (AuthorsCreateStmt, error) { + stmt, err := q.db.PrepareContext(ctx, authorsCreate) + if err != nil { + return nil, err + } + return &authorsCreateStmt{stmt: stmt}, nil +} + +const authorsGet = `-- name: Get :one +SELECT author_id, name FROM authors +WHERE author_id = ? +` + +func (q *authors) Get(ctx context.Context, authorID int32) (Author, error) { + row := q.db.QueryRowContext(ctx, authorsGet, authorID) + var i Author + err := row.Scan(&i.AuthorID, &i.Name) + return i, err +} + +type AuthorsGetStmt interface { + Close() error + JoinTx(ctx context.Context, tx *sql.Tx) AuthorsGetStmt + Exec(ctx context.Context, authorID int32) (Author, error) +} + +type authorsGetStmt struct { + stmt *sql.Stmt +} + +func (s *authorsGetStmt) Close() error { + return s.stmt.Close() +} + +func (s *authorsGetStmt) JoinTx(ctx context.Context, tx *sql.Tx) AuthorsGetStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &authorsGetStmt{stmt: stmt} +} + +func (s *authorsGetStmt) Exec(ctx context.Context, authorID int32) (Author, error) { + row := s.stmt.QueryRowContext(ctx, authorID) + var i Author + err := row.Scan(&i.AuthorID, &i.Name) + return i, err +} + +func (q *authors) PrepareGet(ctx context.Context) (AuthorsGetStmt, error) { + stmt, err := q.db.PrepareContext(ctx, authorsGet) + if err != nil { + return nil, err + } + return &authorsGetStmt{stmt: stmt}, nil +} diff --git a/examples/group/mysql/mygroupstmtiface/books.sql.go b/examples/group/mysql/mygroupstmtiface/books.sql.go new file mode 100644 index 0000000000..f66817ba6d --- /dev/null +++ b/examples/group/mysql/mygroupstmtiface/books.sql.go @@ -0,0 +1,586 @@ +// Code generated by sqlc. DO NOT EDIT. +// source: books.sql + +package mygroupstmtiface + +import ( + "context" + "database/sql" + "time" +) + +type Books interface { + Create(ctx context.Context, arg BooksCreateParams) (sql.Result, error) + PrepareCreate(ctx context.Context) (BooksCreateStmt, error) + Delete(ctx context.Context, bookID int32) error + PrepareDelete(ctx context.Context) (BooksDeleteStmt, error) + DeleteAuthorBeforeYear(ctx context.Context, arg BooksDeleteAuthorBeforeYearParams) error + PrepareDeleteAuthorBeforeYear(ctx context.Context) (BooksDeleteAuthorBeforeYearStmt, error) + Get(ctx context.Context, bookID int32) (Book, error) + PrepareGet(ctx context.Context) (BooksGetStmt, error) + ListByTags(ctx context.Context, tags string) ([]BooksListByTagsRow, error) + PrepareListByTags(ctx context.Context) (BooksListByTagsStmt, error) + ListByTitleYear(ctx context.Context, arg BooksListByTitleYearParams) ([]Book, error) + PrepareListByTitleYear(ctx context.Context) (BooksListByTitleYearStmt, error) + Update(ctx context.Context, arg BooksUpdateParams) error + PrepareUpdate(ctx context.Context) (BooksUpdateStmt, error) + UpdateISBN(ctx context.Context, arg BooksUpdateISBNParams) error + PrepareUpdateISBN(ctx context.Context) (BooksUpdateISBNStmt, error) +} + +func NewBooks(db DBTX) Books { + return &books{db: db} +} + +type books struct { + db DBTX +} + +const booksCreate = `-- name: Create :execresult +INSERT INTO books ( + author_id, + isbn, + book_type, + title, + yr, + available, + tags +) VALUES ( + ?, + ?, + ?, + ?, + ?, + ?, + ? +) +` + +type BooksCreateParams struct { + AuthorID int32 + Isbn string + BookType BooksBookType + Title string + Yr int32 + Available time.Time + Tags string +} + +func (q *books) Create(ctx context.Context, arg BooksCreateParams) (sql.Result, error) { + return q.db.ExecContext(ctx, booksCreate, + arg.AuthorID, + arg.Isbn, + arg.BookType, + arg.Title, + arg.Yr, + arg.Available, + arg.Tags, + ) +} + +type BooksCreateStmt interface { + Close() error + JoinTx(ctx context.Context, tx *sql.Tx) BooksCreateStmt + Exec(ctx context.Context, arg BooksCreateParams) (sql.Result, error) +} + +type booksCreateStmt struct { + stmt *sql.Stmt +} + +func (s *booksCreateStmt) Close() error { + return s.stmt.Close() +} + +func (s *booksCreateStmt) JoinTx(ctx context.Context, tx *sql.Tx) BooksCreateStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &booksCreateStmt{stmt: stmt} +} + +func (s *booksCreateStmt) Exec(ctx context.Context, arg BooksCreateParams) (sql.Result, error) { + return s.stmt.ExecContext(ctx, + arg.AuthorID, + arg.Isbn, + arg.BookType, + arg.Title, + arg.Yr, + arg.Available, + arg.Tags, + ) +} + +func (q *books) PrepareCreate(ctx context.Context) (BooksCreateStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksCreate) + if err != nil { + return nil, err + } + return &booksCreateStmt{stmt: stmt}, nil +} + +const booksDelete = `-- name: Delete :exec +DELETE FROM books +WHERE book_id = ? +` + +func (q *books) Delete(ctx context.Context, bookID int32) error { + _, err := q.db.ExecContext(ctx, booksDelete, bookID) + return err +} + +type BooksDeleteStmt interface { + Close() error + JoinTx(ctx context.Context, tx *sql.Tx) BooksDeleteStmt + Exec(ctx context.Context, bookID int32) error +} + +type booksDeleteStmt struct { + stmt *sql.Stmt +} + +func (s *booksDeleteStmt) Close() error { + return s.stmt.Close() +} + +func (s *booksDeleteStmt) JoinTx(ctx context.Context, tx *sql.Tx) BooksDeleteStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &booksDeleteStmt{stmt: stmt} +} + +func (s *booksDeleteStmt) Exec(ctx context.Context, bookID int32) error { + _, err := s.stmt.ExecContext(ctx, bookID) + return err +} + +func (q *books) PrepareDelete(ctx context.Context) (BooksDeleteStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksDelete) + if err != nil { + return nil, err + } + return &booksDeleteStmt{stmt: stmt}, nil +} + +const booksDeleteAuthorBeforeYear = `-- name: DeleteAuthorBeforeYear :exec +DELETE FROM books +WHERE yr < ? AND author_id = ? +` + +type BooksDeleteAuthorBeforeYearParams struct { + Yr int32 + AuthorID int32 +} + +func (q *books) DeleteAuthorBeforeYear(ctx context.Context, arg BooksDeleteAuthorBeforeYearParams) error { + _, err := q.db.ExecContext(ctx, booksDeleteAuthorBeforeYear, arg.Yr, arg.AuthorID) + return err +} + +type BooksDeleteAuthorBeforeYearStmt interface { + Close() error + JoinTx(ctx context.Context, tx *sql.Tx) BooksDeleteAuthorBeforeYearStmt + Exec(ctx context.Context, arg BooksDeleteAuthorBeforeYearParams) error +} + +type booksDeleteAuthorBeforeYearStmt struct { + stmt *sql.Stmt +} + +func (s *booksDeleteAuthorBeforeYearStmt) Close() error { + return s.stmt.Close() +} + +func (s *booksDeleteAuthorBeforeYearStmt) JoinTx(ctx context.Context, tx *sql.Tx) BooksDeleteAuthorBeforeYearStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &booksDeleteAuthorBeforeYearStmt{stmt: stmt} +} + +func (s *booksDeleteAuthorBeforeYearStmt) Exec(ctx context.Context, arg BooksDeleteAuthorBeforeYearParams) error { + _, err := s.stmt.ExecContext(ctx, arg.Yr, arg.AuthorID) + return err +} + +func (q *books) PrepareDeleteAuthorBeforeYear(ctx context.Context) (BooksDeleteAuthorBeforeYearStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksDeleteAuthorBeforeYear) + if err != nil { + return nil, err + } + return &booksDeleteAuthorBeforeYearStmt{stmt: stmt}, nil +} + +const booksGet = `-- name: Get :one +SELECT book_id, author_id, isbn, book_type, title, yr, available, tags FROM books +WHERE book_id = ? +` + +func (q *books) Get(ctx context.Context, bookID int32) (Book, error) { + row := q.db.QueryRowContext(ctx, booksGet, bookID) + var i Book + err := row.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Yr, + &i.Available, + &i.Tags, + ) + return i, err +} + +type BooksGetStmt interface { + Close() error + JoinTx(ctx context.Context, tx *sql.Tx) BooksGetStmt + Exec(ctx context.Context, bookID int32) (Book, error) +} + +type booksGetStmt struct { + stmt *sql.Stmt +} + +func (s *booksGetStmt) Close() error { + return s.stmt.Close() +} + +func (s *booksGetStmt) JoinTx(ctx context.Context, tx *sql.Tx) BooksGetStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &booksGetStmt{stmt: stmt} +} + +func (s *booksGetStmt) Exec(ctx context.Context, bookID int32) (Book, error) { + row := s.stmt.QueryRowContext(ctx, bookID) + var i Book + err := row.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Yr, + &i.Available, + &i.Tags, + ) + return i, err +} + +func (q *books) PrepareGet(ctx context.Context) (BooksGetStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksGet) + if err != nil { + return nil, err + } + return &booksGetStmt{stmt: stmt}, nil +} + +const booksListByTags = `-- name: ListByTags :many +SELECT + book_id, + title, + name, + isbn, + tags +FROM books +LEFT JOIN authors ON books.author_id = authors.author_id +WHERE tags = ? +` + +type BooksListByTagsRow struct { + BookID int32 + Title string + Name string + Isbn string + Tags string +} + +func (q *books) ListByTags(ctx context.Context, tags string) ([]BooksListByTagsRow, error) { + rows, err := q.db.QueryContext(ctx, booksListByTags, tags) + if err != nil { + return nil, err + } + defer rows.Close() + items := []BooksListByTagsRow{} + for rows.Next() { + var i BooksListByTagsRow + if err := rows.Scan( + &i.BookID, + &i.Title, + &i.Name, + &i.Isbn, + &i.Tags, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +type BooksListByTagsStmt interface { + Close() error + JoinTx(ctx context.Context, tx *sql.Tx) BooksListByTagsStmt + Exec(ctx context.Context, tags string) ([]BooksListByTagsRow, error) +} + +type booksListByTagsStmt struct { + stmt *sql.Stmt +} + +func (s *booksListByTagsStmt) Close() error { + return s.stmt.Close() +} + +func (s *booksListByTagsStmt) JoinTx(ctx context.Context, tx *sql.Tx) BooksListByTagsStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &booksListByTagsStmt{stmt: stmt} +} + +func (s *booksListByTagsStmt) Exec(ctx context.Context, tags string) ([]BooksListByTagsRow, error) { + rows, err := s.stmt.QueryContext(ctx, tags) + if err != nil { + return nil, err + } + defer rows.Close() + items := []BooksListByTagsRow{} + for rows.Next() { + var i BooksListByTagsRow + if err := rows.Scan( + &i.BookID, + &i.Title, + &i.Name, + &i.Isbn, + &i.Tags, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +func (q *books) PrepareListByTags(ctx context.Context) (BooksListByTagsStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksListByTags) + if err != nil { + return nil, err + } + return &booksListByTagsStmt{stmt: stmt}, nil +} + +const booksListByTitleYear = `-- name: ListByTitleYear :many +SELECT book_id, author_id, isbn, book_type, title, yr, available, tags FROM books +WHERE title = ? AND yr = ? +` + +type BooksListByTitleYearParams struct { + Title string + Yr int32 +} + +func (q *books) ListByTitleYear(ctx context.Context, arg BooksListByTitleYearParams) ([]Book, error) { + rows, err := q.db.QueryContext(ctx, booksListByTitleYear, arg.Title, arg.Yr) + if err != nil { + return nil, err + } + defer rows.Close() + items := []Book{} + for rows.Next() { + var i Book + if err := rows.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Yr, + &i.Available, + &i.Tags, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +type BooksListByTitleYearStmt interface { + Close() error + JoinTx(ctx context.Context, tx *sql.Tx) BooksListByTitleYearStmt + Exec(ctx context.Context, arg BooksListByTitleYearParams) ([]Book, error) +} + +type booksListByTitleYearStmt struct { + stmt *sql.Stmt +} + +func (s *booksListByTitleYearStmt) Close() error { + return s.stmt.Close() +} + +func (s *booksListByTitleYearStmt) JoinTx(ctx context.Context, tx *sql.Tx) BooksListByTitleYearStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &booksListByTitleYearStmt{stmt: stmt} +} + +func (s *booksListByTitleYearStmt) Exec(ctx context.Context, arg BooksListByTitleYearParams) ([]Book, error) { + rows, err := s.stmt.QueryContext(ctx, arg.Title, arg.Yr) + if err != nil { + return nil, err + } + defer rows.Close() + items := []Book{} + for rows.Next() { + var i Book + if err := rows.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Yr, + &i.Available, + &i.Tags, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +func (q *books) PrepareListByTitleYear(ctx context.Context) (BooksListByTitleYearStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksListByTitleYear) + if err != nil { + return nil, err + } + return &booksListByTitleYearStmt{stmt: stmt}, nil +} + +const booksUpdate = `-- name: Update :exec +UPDATE books +SET title = ?, tags = ? +WHERE book_id = ? +` + +type BooksUpdateParams struct { + Title string + Tags string + BookID int32 +} + +func (q *books) Update(ctx context.Context, arg BooksUpdateParams) error { + _, err := q.db.ExecContext(ctx, booksUpdate, arg.Title, arg.Tags, arg.BookID) + return err +} + +type BooksUpdateStmt interface { + Close() error + JoinTx(ctx context.Context, tx *sql.Tx) BooksUpdateStmt + Exec(ctx context.Context, arg BooksUpdateParams) error +} + +type booksUpdateStmt struct { + stmt *sql.Stmt +} + +func (s *booksUpdateStmt) Close() error { + return s.stmt.Close() +} + +func (s *booksUpdateStmt) JoinTx(ctx context.Context, tx *sql.Tx) BooksUpdateStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &booksUpdateStmt{stmt: stmt} +} + +func (s *booksUpdateStmt) Exec(ctx context.Context, arg BooksUpdateParams) error { + _, err := s.stmt.ExecContext(ctx, arg.Title, arg.Tags, arg.BookID) + return err +} + +func (q *books) PrepareUpdate(ctx context.Context) (BooksUpdateStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksUpdate) + if err != nil { + return nil, err + } + return &booksUpdateStmt{stmt: stmt}, nil +} + +const booksUpdateISBN = `-- name: UpdateISBN :exec +UPDATE books +SET title = ?, tags = ?, isbn = ? +WHERE book_id = ? +` + +type BooksUpdateISBNParams struct { + Title string + Tags string + Isbn string + BookID int32 +} + +func (q *books) UpdateISBN(ctx context.Context, arg BooksUpdateISBNParams) error { + _, err := q.db.ExecContext(ctx, booksUpdateISBN, + arg.Title, + arg.Tags, + arg.Isbn, + arg.BookID, + ) + return err +} + +type BooksUpdateISBNStmt interface { + Close() error + JoinTx(ctx context.Context, tx *sql.Tx) BooksUpdateISBNStmt + Exec(ctx context.Context, arg BooksUpdateISBNParams) error +} + +type booksUpdateISBNStmt struct { + stmt *sql.Stmt +} + +func (s *booksUpdateISBNStmt) Close() error { + return s.stmt.Close() +} + +func (s *booksUpdateISBNStmt) JoinTx(ctx context.Context, tx *sql.Tx) BooksUpdateISBNStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &booksUpdateISBNStmt{stmt: stmt} +} + +func (s *booksUpdateISBNStmt) Exec(ctx context.Context, arg BooksUpdateISBNParams) error { + _, err := s.stmt.ExecContext(ctx, + arg.Title, + arg.Tags, + arg.Isbn, + arg.BookID, + ) + return err +} + +func (q *books) PrepareUpdateISBN(ctx context.Context) (BooksUpdateISBNStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksUpdateISBN) + if err != nil { + return nil, err + } + return &booksUpdateISBNStmt{stmt: stmt}, nil +} diff --git a/examples/group/mysql/mygroupstmtiface/db.go b/examples/group/mysql/mygroupstmtiface/db.go new file mode 100644 index 0000000000..77a8c781a9 --- /dev/null +++ b/examples/group/mysql/mygroupstmtiface/db.go @@ -0,0 +1,15 @@ +// Code generated by sqlc. DO NOT EDIT. + +package mygroupstmtiface + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} diff --git a/examples/group/mysql/mygroupstmtiface/db_test.go b/examples/group/mysql/mygroupstmtiface/db_test.go new file mode 100644 index 0000000000..aa88928303 --- /dev/null +++ b/examples/group/mysql/mygroupstmtiface/db_test.go @@ -0,0 +1,321 @@ +// +build examples + +package mygroupstmtiface_test + +import ( + "context" + "database/sql" + "testing" + "time" + + "github.com/kyleconroy/sqlc/examples/group/mysql/mygroupstmtiface" + "github.com/kyleconroy/sqlc/internal/sqltest" +) + +func TestBooks(t *testing.T) { + db, cleanup := sqltest.MySQL(t, []string{"../sql/schema/schema.sql"}) + defer cleanup() + + ctx := context.Background() + + // create an author + authorID, err := func() (retID int32, retErr error) { + dbauthors := mygroupstmtiface.NewAuthors(db) + dbcreate, err := dbauthors.PrepareCreate(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := dbcreate.Close(); err != nil && retErr == nil { + retErr = err + } + }() + result, err := dbcreate.Exec(ctx, "Unknown Master") + if err != nil { + retErr = err + return + } + authorID, err := result.LastInsertId() + if err != nil { + retErr = err + return + } + retID = int32(authorID) + return + }() + if err != nil { + t.Fatal(err) + } + + // create transaction + bookThreeID, err := func() (retID int32, retErr error) { + tx, err := db.Begin() + if err != nil { + retErr = err + return + } + defer func() { + if err := tx.Rollback(); err != nil && err != sql.ErrTxDone && retErr == nil { + retErr = err + } + }() + + txbooks := mygroupstmtiface.NewBooks(tx) + + txcreate, err := txbooks.PrepareCreate(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := txcreate.Close(); err != nil && retErr == nil { + retErr = err + } + }() + + // save first book + now := time.Now() + _, err = txcreate.Exec(ctx, mygroupstmtiface.BooksCreateParams{ + AuthorID: authorID, + Isbn: "1", + Title: "my book title", + BookType: mygroupstmtiface.BooksBookTypeFICTION, + Yr: 2016, + Available: now, + }) + if err != nil { + retErr = err + return + } + + // save second book + result, err := txcreate.Exec(ctx, mygroupstmtiface.BooksCreateParams{ + AuthorID: authorID, + Isbn: "2", + Title: "the second book", + BookType: mygroupstmtiface.BooksBookTypeFICTION, + Yr: 2016, + Available: now, + Tags: "cool,unique", + }) + if err != nil { + retErr = err + return + } + bookOneID, err := result.LastInsertId() + if err != nil { + retErr = err + return + } + + // update the title and tags + txupdate, err := txbooks.PrepareUpdate(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := txupdate.Close(); err != nil && retErr == nil { + retErr = err + } + }() + err = txupdate.Exec(ctx, mygroupstmtiface.BooksUpdateParams{ + BookID: int32(bookOneID), + Title: "changed second title", + Tags: "cool,disastor", + }) + if err != nil { + retErr = err + return + } + + // save third book + _, err = txcreate.Exec(ctx, mygroupstmtiface.BooksCreateParams{ + AuthorID: authorID, + Isbn: "3", + Title: "the third book", + BookType: mygroupstmtiface.BooksBookTypeFICTION, + Yr: 2001, + Available: now, + Tags: "cool", + }) + if err != nil { + retErr = err + return + } + + // save fourth book + result, err = txcreate.Exec(ctx, mygroupstmtiface.BooksCreateParams{ + AuthorID: authorID, + Isbn: "4", + Title: "4th place finisher", + BookType: mygroupstmtiface.BooksBookTypeNONFICTION, + Yr: 2011, + Available: now, + Tags: "other", + }) + if err != nil { + retErr = err + return + } + bookThreeID, err := result.LastInsertId() + if err != nil { + retErr = err + return + } + + // tx commit + err = tx.Commit() + if err != nil { + retErr = err + return + } + retID = int32(bookThreeID) + return + }() + if err != nil { + t.Fatal(err) + } + + // upsert, changing ISBN and title + err = func() (retErr error) { + dbbooks := mygroupstmtiface.NewBooks(db) + dbupdate, err := dbbooks.PrepareUpdateISBN(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := dbupdate.Close(); err != nil && retErr == nil { + retErr = err + } + }() + retErr = dbupdate.Exec(ctx, mygroupstmtiface.BooksUpdateISBNParams{ + BookID: bookThreeID, + Isbn: "NEW ISBN", + Title: "never ever gonna finish, a quatrain", + Tags: "someother", + }) + return + }() + if err != nil { + t.Fatal(err) + } + + // retrieve first book + err = func() (retErr error) { + dbbooks := mygroupstmtiface.NewBooks(db) + dblist, err := dbbooks.PrepareListByTitleYear(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := dblist.Close(); err != nil && retErr == nil { + retErr = err + return + } + }() + books0, err := dblist.Exec(ctx, mygroupstmtiface.BooksListByTitleYearParams{ + Title: "my book title", + Yr: 2016, + }) + if err != nil { + retErr = err + return + } + dbauthors := mygroupstmtiface.NewAuthors(db) + dbget, err := dbauthors.PrepareGet(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := dbget.Close(); err != nil && retErr == nil { + retErr = err + return + } + }() + for _, book := range books0 { + t.Logf("Book %d (%s): %s available: %s\n", book.BookID, book.BookType, book.Title, book.Available.Format(time.RFC822Z)) + author, err := dbget.Exec(ctx, book.AuthorID) + if err != nil { + retErr = err + return + } + t.Logf("Book %d author: %s\n", book.BookID, author.Name) + } + return + }() + if err != nil { + t.Fatal(err) + } + + // find a book with either "cool" or "other" tag + err = func() (retErr error) { + t.Logf("---------\nTag search results:\n") + dbbooks := mygroupstmtiface.NewBooks(db) + dblist, err := dbbooks.PrepareListByTags(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := dblist.Close(); err != nil && retErr == nil { + retErr = err + return + } + }() + res, err := dblist.Exec(ctx, "cool") + if err != nil { + retErr = err + return + } + for _, ab := range res { + t.Logf("Book %d: '%s', Author: '%s', ISBN: '%s' Tags: '%v'\n", ab.BookID, ab.Title, ab.Name, ab.Isbn, ab.Tags) + } + return + }() + if err != nil { + t.Fatal(err) + } + + // get book 4 and delete + err = func() (retErr error) { + dbbooks := mygroupstmtiface.NewBooks(db) + dbget, err := dbbooks.PrepareGet(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := dbget.Close(); err != nil && retErr == nil { + retErr = err + } + }() + b5, err := dbget.Exec(ctx, bookThreeID) + if err != nil { + retErr = err + return + } + dbdelete, err := dbbooks.PrepareDelete(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := dbdelete.Close(); err != nil && retErr == nil { + retErr = err + } + }() + if err := dbdelete.Exec(ctx, b5.BookID); err != nil { + retErr = err + return + } + return + }() + if err != nil { + t.Fatal(err) + } +} diff --git a/examples/group/mysql/mygroupstmtiface/models.go b/examples/group/mysql/mygroupstmtiface/models.go new file mode 100644 index 0000000000..ceea31b449 --- /dev/null +++ b/examples/group/mysql/mygroupstmtiface/models.go @@ -0,0 +1,43 @@ +// Code generated by sqlc. DO NOT EDIT. + +package mygroupstmtiface + +import ( + "fmt" + "time" +) + +type BooksBookType string + +const ( + BooksBookTypeFICTION BooksBookType = "FICTION" + BooksBookTypeNONFICTION BooksBookType = "NONFICTION" +) + +func (e *BooksBookType) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = BooksBookType(s) + case string: + *e = BooksBookType(s) + default: + return fmt.Errorf("unsupported scan type for BooksBookType: %T", src) + } + return nil +} + +type Author struct { + AuthorID int32 + Name string +} + +type Book struct { + BookID int32 + AuthorID int32 + Isbn string + BookType BooksBookType + Title string + Yr int32 + Available time.Time + Tags string +} diff --git a/examples/group/mysql/sql/query/authors.sql b/examples/group/mysql/sql/query/authors.sql new file mode 100644 index 0000000000..a6e14152e1 --- /dev/null +++ b/examples/group/mysql/sql/query/authors.sql @@ -0,0 +1,6 @@ +/* name: Get :one */ +SELECT * FROM authors +WHERE author_id = ?; + +/* name: Create :execresult */ +INSERT INTO authors (name) VALUES (?); diff --git a/examples/group/mysql/sql/query/books.sql b/examples/group/mysql/sql/query/books.sql new file mode 100644 index 0000000000..60858e3663 --- /dev/null +++ b/examples/group/mysql/sql/query/books.sql @@ -0,0 +1,56 @@ +/* name: Get :one */ +SELECT * FROM books +WHERE book_id = ?; + +/* name: Delete :exec */ +DELETE FROM books +WHERE book_id = ?; + +/* name: ListByTitleYear :many */ +SELECT * FROM books +WHERE title = ? AND yr = ?; + +/* name: ListByTags :many */ +SELECT + book_id, + title, + name, + isbn, + tags +FROM books +LEFT JOIN authors ON books.author_id = authors.author_id +WHERE tags = ?; + +/* name: Create :execresult */ +INSERT INTO books ( + author_id, + isbn, + book_type, + title, + yr, + available, + tags +) VALUES ( + ?, + ?, + ?, + ?, + ?, + ?, + ? +); + +/* name: Update :exec */ +UPDATE books +SET title = ?, tags = ? +WHERE book_id = ?; + +/* name: UpdateISBN :exec */ +UPDATE books +SET title = ?, tags = ?, isbn = ? +WHERE book_id = ?; + +/* name: DeleteAuthorBeforeYear :exec */ +DELETE FROM books +WHERE yr < ? AND author_id = ?; +-- WHERE yr < sqlc.arg(min_publish_year) AND author_id = ?; diff --git a/examples/group/mysql/sql/schema/schema.sql b/examples/group/mysql/sql/schema/schema.sql new file mode 100644 index 0000000000..e457da90d3 --- /dev/null +++ b/examples/group/mysql/sql/schema/schema.sql @@ -0,0 +1,26 @@ +CREATE TABLE authors ( + author_id integer NOT NULL AUTO_INCREMENT PRIMARY KEY, + name text NOT NULL +) ENGINE=InnoDB; + +CREATE INDEX authors_name_idx ON authors(name(255)); + +CREATE TABLE books ( + book_id integer NOT NULL AUTO_INCREMENT PRIMARY KEY, + author_id integer NOT NULL, + isbn varchar(255) NOT NULL DEFAULT '' UNIQUE, + book_type ENUM('FICTION', 'NONFICTION') NOT NULL DEFAULT 'FICTION', + title text NOT NULL, + yr integer NOT NULL DEFAULT 2000, + available datetime NOT NULL DEFAULT NOW(), + tags text NOT NULL + -- CONSTRAINT FOREIGN KEY (author_id) REFERENCES authors(author_id) +) ENGINE=InnoDB; + +CREATE INDEX books_title_idx ON books(title(255), yr); + +/* +CREATE FUNCTION say_hello(s text) RETURNS text + DETERMINISTIC + RETURN CONCAT('hello ', s); +*/ diff --git a/examples/group/postgresql/pggroup/authors.sql.go b/examples/group/postgresql/pggroup/authors.sql.go new file mode 100644 index 0000000000..f0645b5169 --- /dev/null +++ b/examples/group/postgresql/pggroup/authors.sql.go @@ -0,0 +1,40 @@ +// Code generated by sqlc. DO NOT EDIT. +// source: authors.sql + +package pggroup + +import ( + "context" +) + +type Authors struct { + db DBTX +} + +func NewAuthors(db DBTX) *Authors { + return &Authors{db: db} +} + +const authorsCreate = `-- name: Create :one +INSERT INTO authors (name) VALUES ($1) +RETURNING author_id, name +` + +func (q *Authors) Create(ctx context.Context, name string) (Author, error) { + row := q.db.QueryRowContext(ctx, authorsCreate, name) + var i Author + err := row.Scan(&i.AuthorID, &i.Name) + return i, err +} + +const authorsGet = `-- name: Get :one +SELECT author_id, name FROM authors +WHERE author_id = $1 +` + +func (q *Authors) Get(ctx context.Context, authorID int32) (Author, error) { + row := q.db.QueryRowContext(ctx, authorsGet, authorID) + var i Author + err := row.Scan(&i.AuthorID, &i.Name) + return i, err +} diff --git a/examples/group/postgresql/pggroup/books.sql.go b/examples/group/postgresql/pggroup/books.sql.go new file mode 100644 index 0000000000..68cd1cfc4b --- /dev/null +++ b/examples/group/postgresql/pggroup/books.sql.go @@ -0,0 +1,236 @@ +// Code generated by sqlc. DO NOT EDIT. +// source: books.sql + +package pggroup + +import ( + "context" + "time" + + "github.com/lib/pq" +) + +type Books struct { + db DBTX +} + +func NewBooks(db DBTX) *Books { + return &Books{db: db} +} + +const booksCreate = `-- name: Create :one +INSERT INTO books ( + author_id, + isbn, + book_type, + title, + year, + available, + tags +) VALUES ( + $1, + $2, + $3, + $4, + $5, + $6, + $7 +) +RETURNING book_id, author_id, isbn, book_type, title, year, available, tags +` + +type BooksCreateParams struct { + AuthorID int32 + Isbn string + BookType BookType + Title string + Year int32 + Available time.Time + Tags []string +} + +func (q *Books) Create(ctx context.Context, arg BooksCreateParams) (Book, error) { + row := q.db.QueryRowContext(ctx, booksCreate, + arg.AuthorID, + arg.Isbn, + arg.BookType, + arg.Title, + arg.Year, + arg.Available, + pq.Array(arg.Tags), + ) + var i Book + err := row.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Year, + &i.Available, + pq.Array(&i.Tags), + ) + return i, err +} + +const booksDelete = `-- name: Delete :exec +DELETE FROM books +WHERE book_id = $1 +` + +func (q *Books) Delete(ctx context.Context, bookID int32) error { + _, err := q.db.ExecContext(ctx, booksDelete, bookID) + return err +} + +const booksGet = `-- name: Get :one +SELECT book_id, author_id, isbn, book_type, title, year, available, tags FROM books +WHERE book_id = $1 +` + +func (q *Books) Get(ctx context.Context, bookID int32) (Book, error) { + row := q.db.QueryRowContext(ctx, booksGet, bookID) + var i Book + err := row.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Year, + &i.Available, + pq.Array(&i.Tags), + ) + return i, err +} + +const booksListByTags = `-- name: ListByTags :many +SELECT + book_id, + title, + name, + isbn, + tags +FROM books +LEFT JOIN authors ON books.author_id = authors.author_id +WHERE tags && $1::varchar[] +` + +type BooksListByTagsRow struct { + BookID int32 + Title string + Name string + Isbn string + Tags []string +} + +func (q *Books) ListByTags(ctx context.Context, dollar_1 []string) ([]BooksListByTagsRow, error) { + rows, err := q.db.QueryContext(ctx, booksListByTags, pq.Array(dollar_1)) + if err != nil { + return nil, err + } + defer rows.Close() + items := []BooksListByTagsRow{} + for rows.Next() { + var i BooksListByTagsRow + if err := rows.Scan( + &i.BookID, + &i.Title, + &i.Name, + &i.Isbn, + pq.Array(&i.Tags), + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const booksListByTitleYear = `-- name: ListByTitleYear :many +SELECT book_id, author_id, isbn, book_type, title, year, available, tags FROM books +WHERE title = $1 AND year = $2 +` + +type BooksListByTitleYearParams struct { + Title string + Year int32 +} + +func (q *Books) ListByTitleYear(ctx context.Context, arg BooksListByTitleYearParams) ([]Book, error) { + rows, err := q.db.QueryContext(ctx, booksListByTitleYear, arg.Title, arg.Year) + if err != nil { + return nil, err + } + defer rows.Close() + items := []Book{} + for rows.Next() { + var i Book + if err := rows.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Year, + &i.Available, + pq.Array(&i.Tags), + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const booksUpdate = `-- name: Update :exec +UPDATE books +SET title = $1, tags = $2 +WHERE book_id = $3 +` + +type BooksUpdateParams struct { + Title string + Tags []string + BookID int32 +} + +func (q *Books) Update(ctx context.Context, arg BooksUpdateParams) error { + _, err := q.db.ExecContext(ctx, booksUpdate, arg.Title, pq.Array(arg.Tags), arg.BookID) + return err +} + +const booksUpdateISBN = `-- name: UpdateISBN :exec +UPDATE books +SET title = $1, tags = $2, isbn = $4 +WHERE book_id = $3 +` + +type BooksUpdateISBNParams struct { + Title string + Tags []string + BookID int32 + Isbn string +} + +func (q *Books) UpdateISBN(ctx context.Context, arg BooksUpdateISBNParams) error { + _, err := q.db.ExecContext(ctx, booksUpdateISBN, + arg.Title, + pq.Array(arg.Tags), + arg.BookID, + arg.Isbn, + ) + return err +} diff --git a/examples/group/postgresql/pggroup/db.go b/examples/group/postgresql/pggroup/db.go new file mode 100644 index 0000000000..bee4ba3c4f --- /dev/null +++ b/examples/group/postgresql/pggroup/db.go @@ -0,0 +1,15 @@ +// Code generated by sqlc. DO NOT EDIT. + +package pggroup + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} diff --git a/examples/group/postgresql/pggroup/db_test.go b/examples/group/postgresql/pggroup/db_test.go new file mode 100644 index 0000000000..a076a5e317 --- /dev/null +++ b/examples/group/postgresql/pggroup/db_test.go @@ -0,0 +1,157 @@ +// +build examples + +package pggroup_test + +import ( + "context" + "testing" + "time" + + "github.com/kyleconroy/sqlc/examples/group/postgresql/pggroup" + "github.com/kyleconroy/sqlc/internal/sqltest" +) + +func TestBooks(t *testing.T) { + db, cleanup := sqltest.PostgreSQL(t, []string{"../sql/schema/schema.sql"}) + defer cleanup() + + ctx := context.Background() + + dbauthors := pggroup.NewAuthors(db) + + // create an author + a, err := dbauthors.Create(ctx, "Unknown Master") + if err != nil { + t.Fatal(err) + } + + // create transaction + tx, err := db.Begin() + if err != nil { + t.Fatal(err) + } + + txbooks := pggroup.NewBooks(tx) + + // save first book + now := time.Now() + _, err = txbooks.Create(ctx, pggroup.BooksCreateParams{ + AuthorID: a.AuthorID, + Isbn: "1", + Title: "my book title", + BookType: pggroup.BookTypeFICTION, + Year: 2016, + Available: now, + Tags: []string{}, + }) + if err != nil { + t.Fatal(err) + } + + // save second book + b1, err := txbooks.Create(ctx, pggroup.BooksCreateParams{ + AuthorID: a.AuthorID, + Isbn: "2", + Title: "the second book", + BookType: pggroup.BookTypeFICTION, + Year: 2016, + Available: now, + Tags: []string{"cool", "unique"}, + }) + if err != nil { + t.Fatal(err) + } + + // update the title and tags + err = txbooks.Update(ctx, pggroup.BooksUpdateParams{ + BookID: b1.BookID, + Title: "changed second title", + Tags: []string{"cool", "disastor"}, + }) + if err != nil { + t.Fatal(err) + } + + // save third book + _, err = txbooks.Create(ctx, pggroup.BooksCreateParams{ + AuthorID: a.AuthorID, + Isbn: "3", + Title: "the third book", + BookType: pggroup.BookTypeFICTION, + Year: 2001, + Available: now, + Tags: []string{"cool"}, + }) + if err != nil { + t.Fatal(err) + } + + // save fourth book + b3, err := txbooks.Create(ctx, pggroup.BooksCreateParams{ + AuthorID: a.AuthorID, + Isbn: "4", + Title: "4th place finisher", + BookType: pggroup.BookTypeNONFICTION, + Year: 2011, + Available: now, + Tags: []string{"other"}, + }) + if err != nil { + t.Fatal(err) + } + + // tx commit + err = tx.Commit() + if err != nil { + t.Fatal(err) + } + + dbbooks := pggroup.NewBooks(db) + + // upsert, changing ISBN and title + err = dbbooks.UpdateISBN(ctx, pggroup.BooksUpdateISBNParams{ + BookID: b3.BookID, + Isbn: "NEW ISBN", + Title: "never ever gonna finish, a quatrain", + Tags: []string{"someother"}, + }) + if err != nil { + t.Fatal(err) + } + + // retrieve first book + books0, err := dbbooks.ListByTitleYear(ctx, pggroup.BooksListByTitleYearParams{ + Title: "my book title", + Year: 2016, + }) + if err != nil { + t.Fatal(err) + } + for _, book := range books0 { + t.Logf("Book %d (%s): %s available: %s\n", book.BookID, book.BookType, book.Title, book.Available.Format(time.RFC822Z)) + author, err := dbauthors.Get(ctx, book.AuthorID) + if err != nil { + t.Fatal(err) + } + t.Logf("Book %d author: %s\n", book.BookID, author.Name) + } + + // find a book with either "cool" or "other" tag + t.Logf("---------\nTag search results:\n") + res, err := dbbooks.ListByTags(ctx, []string{"cool", "other", "someother"}) + if err != nil { + t.Fatal(err) + } + for _, ab := range res { + t.Logf("Book %d: '%s', Author: '%s', ISBN: '%s' Tags: '%v'\n", ab.BookID, ab.Title, ab.Name, ab.Isbn, ab.Tags) + } + + // get book 4 and delete + b5, err := dbbooks.Get(ctx, b3.BookID) + if err != nil { + t.Fatal(err) + } + if err := dbbooks.Delete(ctx, b5.BookID); err != nil { + t.Fatal(err) + } +} diff --git a/examples/group/postgresql/pggroup/models.go b/examples/group/postgresql/pggroup/models.go new file mode 100644 index 0000000000..0b51befa10 --- /dev/null +++ b/examples/group/postgresql/pggroup/models.go @@ -0,0 +1,43 @@ +// Code generated by sqlc. DO NOT EDIT. + +package pggroup + +import ( + "fmt" + "time" +) + +type BookType string + +const ( + BookTypeFICTION BookType = "FICTION" + BookTypeNONFICTION BookType = "NONFICTION" +) + +func (e *BookType) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = BookType(s) + case string: + *e = BookType(s) + default: + return fmt.Errorf("unsupported scan type for BookType: %T", src) + } + return nil +} + +type Author struct { + AuthorID int32 + Name string +} + +type Book struct { + BookID int32 + AuthorID int32 + Isbn string + BookType BookType + Title string + Year int32 + Available time.Time + Tags []string +} diff --git a/examples/group/postgresql/pggroupiface/authors.sql.go b/examples/group/postgresql/pggroupiface/authors.sql.go new file mode 100644 index 0000000000..fc99e44af8 --- /dev/null +++ b/examples/group/postgresql/pggroupiface/authors.sql.go @@ -0,0 +1,45 @@ +// Code generated by sqlc. DO NOT EDIT. +// source: authors.sql + +package pggroupiface + +import ( + "context" +) + +type Authors interface { + Create(ctx context.Context, name string) (Author, error) + Get(ctx context.Context, authorID int32) (Author, error) +} + +func NewAuthors(db DBTX) Authors { + return &authors{db: db} +} + +type authors struct { + db DBTX +} + +const authorsCreate = `-- name: Create :one +INSERT INTO authors (name) VALUES ($1) +RETURNING author_id, name +` + +func (q *authors) Create(ctx context.Context, name string) (Author, error) { + row := q.db.QueryRowContext(ctx, authorsCreate, name) + var i Author + err := row.Scan(&i.AuthorID, &i.Name) + return i, err +} + +const authorsGet = `-- name: Get :one +SELECT author_id, name FROM authors +WHERE author_id = $1 +` + +func (q *authors) Get(ctx context.Context, authorID int32) (Author, error) { + row := q.db.QueryRowContext(ctx, authorsGet, authorID) + var i Author + err := row.Scan(&i.AuthorID, &i.Name) + return i, err +} diff --git a/examples/group/postgresql/pggroupiface/books.sql.go b/examples/group/postgresql/pggroupiface/books.sql.go new file mode 100644 index 0000000000..805a118ce5 --- /dev/null +++ b/examples/group/postgresql/pggroupiface/books.sql.go @@ -0,0 +1,246 @@ +// Code generated by sqlc. DO NOT EDIT. +// source: books.sql + +package pggroupiface + +import ( + "context" + "time" + + "github.com/lib/pq" +) + +type Books interface { + Create(ctx context.Context, arg BooksCreateParams) (Book, error) + Delete(ctx context.Context, bookID int32) error + Get(ctx context.Context, bookID int32) (Book, error) + ListByTags(ctx context.Context, dollar_1 []string) ([]BooksListByTagsRow, error) + ListByTitleYear(ctx context.Context, arg BooksListByTitleYearParams) ([]Book, error) + Update(ctx context.Context, arg BooksUpdateParams) error + UpdateISBN(ctx context.Context, arg BooksUpdateISBNParams) error +} + +func NewBooks(db DBTX) Books { + return &books{db: db} +} + +type books struct { + db DBTX +} + +const booksCreate = `-- name: Create :one +INSERT INTO books ( + author_id, + isbn, + book_type, + title, + year, + available, + tags +) VALUES ( + $1, + $2, + $3, + $4, + $5, + $6, + $7 +) +RETURNING book_id, author_id, isbn, book_type, title, year, available, tags +` + +type BooksCreateParams struct { + AuthorID int32 + Isbn string + BookType BookType + Title string + Year int32 + Available time.Time + Tags []string +} + +func (q *books) Create(ctx context.Context, arg BooksCreateParams) (Book, error) { + row := q.db.QueryRowContext(ctx, booksCreate, + arg.AuthorID, + arg.Isbn, + arg.BookType, + arg.Title, + arg.Year, + arg.Available, + pq.Array(arg.Tags), + ) + var i Book + err := row.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Year, + &i.Available, + pq.Array(&i.Tags), + ) + return i, err +} + +const booksDelete = `-- name: Delete :exec +DELETE FROM books +WHERE book_id = $1 +` + +func (q *books) Delete(ctx context.Context, bookID int32) error { + _, err := q.db.ExecContext(ctx, booksDelete, bookID) + return err +} + +const booksGet = `-- name: Get :one +SELECT book_id, author_id, isbn, book_type, title, year, available, tags FROM books +WHERE book_id = $1 +` + +func (q *books) Get(ctx context.Context, bookID int32) (Book, error) { + row := q.db.QueryRowContext(ctx, booksGet, bookID) + var i Book + err := row.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Year, + &i.Available, + pq.Array(&i.Tags), + ) + return i, err +} + +const booksListByTags = `-- name: ListByTags :many +SELECT + book_id, + title, + name, + isbn, + tags +FROM books +LEFT JOIN authors ON books.author_id = authors.author_id +WHERE tags && $1::varchar[] +` + +type BooksListByTagsRow struct { + BookID int32 + Title string + Name string + Isbn string + Tags []string +} + +func (q *books) ListByTags(ctx context.Context, dollar_1 []string) ([]BooksListByTagsRow, error) { + rows, err := q.db.QueryContext(ctx, booksListByTags, pq.Array(dollar_1)) + if err != nil { + return nil, err + } + defer rows.Close() + items := []BooksListByTagsRow{} + for rows.Next() { + var i BooksListByTagsRow + if err := rows.Scan( + &i.BookID, + &i.Title, + &i.Name, + &i.Isbn, + pq.Array(&i.Tags), + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const booksListByTitleYear = `-- name: ListByTitleYear :many +SELECT book_id, author_id, isbn, book_type, title, year, available, tags FROM books +WHERE title = $1 AND year = $2 +` + +type BooksListByTitleYearParams struct { + Title string + Year int32 +} + +func (q *books) ListByTitleYear(ctx context.Context, arg BooksListByTitleYearParams) ([]Book, error) { + rows, err := q.db.QueryContext(ctx, booksListByTitleYear, arg.Title, arg.Year) + if err != nil { + return nil, err + } + defer rows.Close() + items := []Book{} + for rows.Next() { + var i Book + if err := rows.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Year, + &i.Available, + pq.Array(&i.Tags), + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const booksUpdate = `-- name: Update :exec +UPDATE books +SET title = $1, tags = $2 +WHERE book_id = $3 +` + +type BooksUpdateParams struct { + Title string + Tags []string + BookID int32 +} + +func (q *books) Update(ctx context.Context, arg BooksUpdateParams) error { + _, err := q.db.ExecContext(ctx, booksUpdate, arg.Title, pq.Array(arg.Tags), arg.BookID) + return err +} + +const booksUpdateISBN = `-- name: UpdateISBN :exec +UPDATE books +SET title = $1, tags = $2, isbn = $4 +WHERE book_id = $3 +` + +type BooksUpdateISBNParams struct { + Title string + Tags []string + BookID int32 + Isbn string +} + +func (q *books) UpdateISBN(ctx context.Context, arg BooksUpdateISBNParams) error { + _, err := q.db.ExecContext(ctx, booksUpdateISBN, + arg.Title, + pq.Array(arg.Tags), + arg.BookID, + arg.Isbn, + ) + return err +} diff --git a/examples/group/postgresql/pggroupiface/db.go b/examples/group/postgresql/pggroupiface/db.go new file mode 100644 index 0000000000..5bcf573d6a --- /dev/null +++ b/examples/group/postgresql/pggroupiface/db.go @@ -0,0 +1,15 @@ +// Code generated by sqlc. DO NOT EDIT. + +package pggroupiface + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} diff --git a/examples/group/postgresql/pggroupiface/db_test.go b/examples/group/postgresql/pggroupiface/db_test.go new file mode 100644 index 0000000000..2951b57019 --- /dev/null +++ b/examples/group/postgresql/pggroupiface/db_test.go @@ -0,0 +1,157 @@ +// +build examples + +package pggroupiface_test + +import ( + "context" + "testing" + "time" + + "github.com/kyleconroy/sqlc/examples/group/postgresql/pggroupiface" + "github.com/kyleconroy/sqlc/internal/sqltest" +) + +func TestBooks(t *testing.T) { + db, cleanup := sqltest.PostgreSQL(t, []string{"../sql/schema/schema.sql"}) + defer cleanup() + + ctx := context.Background() + + dbauthors := pggroupiface.NewAuthors(db) + + // create an author + a, err := dbauthors.Create(ctx, "Unknown Master") + if err != nil { + t.Fatal(err) + } + + // create transaction + tx, err := db.Begin() + if err != nil { + t.Fatal(err) + } + + txbooks := pggroupiface.NewBooks(tx) + + // save first book + now := time.Now() + _, err = txbooks.Create(ctx, pggroupiface.BooksCreateParams{ + AuthorID: a.AuthorID, + Isbn: "1", + Title: "my book title", + BookType: pggroupiface.BookTypeFICTION, + Year: 2016, + Available: now, + Tags: []string{}, + }) + if err != nil { + t.Fatal(err) + } + + // save second book + b1, err := txbooks.Create(ctx, pggroupiface.BooksCreateParams{ + AuthorID: a.AuthorID, + Isbn: "2", + Title: "the second book", + BookType: pggroupiface.BookTypeFICTION, + Year: 2016, + Available: now, + Tags: []string{"cool", "unique"}, + }) + if err != nil { + t.Fatal(err) + } + + // update the title and tags + err = txbooks.Update(ctx, pggroupiface.BooksUpdateParams{ + BookID: b1.BookID, + Title: "changed second title", + Tags: []string{"cool", "disastor"}, + }) + if err != nil { + t.Fatal(err) + } + + // save third book + _, err = txbooks.Create(ctx, pggroupiface.BooksCreateParams{ + AuthorID: a.AuthorID, + Isbn: "3", + Title: "the third book", + BookType: pggroupiface.BookTypeFICTION, + Year: 2001, + Available: now, + Tags: []string{"cool"}, + }) + if err != nil { + t.Fatal(err) + } + + // save fourth book + b3, err := txbooks.Create(ctx, pggroupiface.BooksCreateParams{ + AuthorID: a.AuthorID, + Isbn: "4", + Title: "4th place finisher", + BookType: pggroupiface.BookTypeNONFICTION, + Year: 2011, + Available: now, + Tags: []string{"other"}, + }) + if err != nil { + t.Fatal(err) + } + + // tx commit + err = tx.Commit() + if err != nil { + t.Fatal(err) + } + + dbbooks := pggroupiface.NewBooks(db) + + // upsert, changing ISBN and title + err = dbbooks.UpdateISBN(ctx, pggroupiface.BooksUpdateISBNParams{ + BookID: b3.BookID, + Isbn: "NEW ISBN", + Title: "never ever gonna finish, a quatrain", + Tags: []string{"someother"}, + }) + if err != nil { + t.Fatal(err) + } + + // retrieve first book + books0, err := dbbooks.ListByTitleYear(ctx, pggroupiface.BooksListByTitleYearParams{ + Title: "my book title", + Year: 2016, + }) + if err != nil { + t.Fatal(err) + } + for _, book := range books0 { + t.Logf("Book %d (%s): %s available: %s\n", book.BookID, book.BookType, book.Title, book.Available.Format(time.RFC822Z)) + author, err := dbauthors.Get(ctx, book.AuthorID) + if err != nil { + t.Fatal(err) + } + t.Logf("Book %d author: %s\n", book.BookID, author.Name) + } + + // find a book with either "cool" or "other" tag + t.Logf("---------\nTag search results:\n") + res, err := dbbooks.ListByTags(ctx, []string{"cool", "other", "someother"}) + if err != nil { + t.Fatal(err) + } + for _, ab := range res { + t.Logf("Book %d: '%s', Author: '%s', ISBN: '%s' Tags: '%v'\n", ab.BookID, ab.Title, ab.Name, ab.Isbn, ab.Tags) + } + + // get book 4 and delete + b5, err := dbbooks.Get(ctx, b3.BookID) + if err != nil { + t.Fatal(err) + } + if err := dbbooks.Delete(ctx, b5.BookID); err != nil { + t.Fatal(err) + } +} diff --git a/examples/group/postgresql/pggroupiface/models.go b/examples/group/postgresql/pggroupiface/models.go new file mode 100644 index 0000000000..2c57036a98 --- /dev/null +++ b/examples/group/postgresql/pggroupiface/models.go @@ -0,0 +1,43 @@ +// Code generated by sqlc. DO NOT EDIT. + +package pggroupiface + +import ( + "fmt" + "time" +) + +type BookType string + +const ( + BookTypeFICTION BookType = "FICTION" + BookTypeNONFICTION BookType = "NONFICTION" +) + +func (e *BookType) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = BookType(s) + case string: + *e = BookType(s) + default: + return fmt.Errorf("unsupported scan type for BookType: %T", src) + } + return nil +} + +type Author struct { + AuthorID int32 + Name string +} + +type Book struct { + BookID int32 + AuthorID int32 + Isbn string + BookType BookType + Title string + Year int32 + Available time.Time + Tags []string +} diff --git a/examples/group/postgresql/pggroupstmt/authors.sql.go b/examples/group/postgresql/pggroupstmt/authors.sql.go new file mode 100644 index 0000000000..ff241b2730 --- /dev/null +++ b/examples/group/postgresql/pggroupstmt/authors.sql.go @@ -0,0 +1,97 @@ +// Code generated by sqlc. DO NOT EDIT. +// source: authors.sql + +package pggroupstmt + +import ( + "context" + "database/sql" +) + +type Authors struct { + db DBTX +} + +func NewAuthors(db DBTX) *Authors { + return &Authors{db: db} +} + +const authorsCreate = `-- name: Create :one +INSERT INTO authors (name) VALUES ($1) +RETURNING author_id, name +` + +func (q *Authors) Create(ctx context.Context, name string) (Author, error) { + row := q.db.QueryRowContext(ctx, authorsCreate, name) + var i Author + err := row.Scan(&i.AuthorID, &i.Name) + return i, err +} + +type AuthorsCreateStmt struct { + stmt *sql.Stmt +} + +func (s *AuthorsCreateStmt) Close() error { + return s.stmt.Close() +} + +func (s *AuthorsCreateStmt) JoinTx(ctx context.Context, tx *sql.Tx) *AuthorsCreateStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &AuthorsCreateStmt{stmt: stmt} +} + +func (s *AuthorsCreateStmt) Exec(ctx context.Context, name string) (Author, error) { + row := s.stmt.QueryRowContext(ctx, name) + var i Author + err := row.Scan(&i.AuthorID, &i.Name) + return i, err +} + +func (q *Authors) PrepareCreate(ctx context.Context) (*AuthorsCreateStmt, error) { + stmt, err := q.db.PrepareContext(ctx, authorsCreate) + if err != nil { + return nil, err + } + return &AuthorsCreateStmt{stmt: stmt}, nil +} + +const authorsGet = `-- name: Get :one +SELECT author_id, name FROM authors +WHERE author_id = $1 +` + +func (q *Authors) Get(ctx context.Context, authorID int32) (Author, error) { + row := q.db.QueryRowContext(ctx, authorsGet, authorID) + var i Author + err := row.Scan(&i.AuthorID, &i.Name) + return i, err +} + +type AuthorsGetStmt struct { + stmt *sql.Stmt +} + +func (s *AuthorsGetStmt) Close() error { + return s.stmt.Close() +} + +func (s *AuthorsGetStmt) JoinTx(ctx context.Context, tx *sql.Tx) *AuthorsGetStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &AuthorsGetStmt{stmt: stmt} +} + +func (s *AuthorsGetStmt) Exec(ctx context.Context, authorID int32) (Author, error) { + row := s.stmt.QueryRowContext(ctx, authorID) + var i Author + err := row.Scan(&i.AuthorID, &i.Name) + return i, err +} + +func (q *Authors) PrepareGet(ctx context.Context) (*AuthorsGetStmt, error) { + stmt, err := q.db.PrepareContext(ctx, authorsGet) + if err != nil { + return nil, err + } + return &AuthorsGetStmt{stmt: stmt}, nil +} diff --git a/examples/group/postgresql/pggroupstmt/books.sql.go b/examples/group/postgresql/pggroupstmt/books.sql.go new file mode 100644 index 0000000000..6fa364ab9a --- /dev/null +++ b/examples/group/postgresql/pggroupstmt/books.sql.go @@ -0,0 +1,505 @@ +// Code generated by sqlc. DO NOT EDIT. +// source: books.sql + +package pggroupstmt + +import ( + "context" + "database/sql" + "time" + + "github.com/lib/pq" +) + +type Books struct { + db DBTX +} + +func NewBooks(db DBTX) *Books { + return &Books{db: db} +} + +const booksCreate = `-- name: Create :one +INSERT INTO books ( + author_id, + isbn, + book_type, + title, + year, + available, + tags +) VALUES ( + $1, + $2, + $3, + $4, + $5, + $6, + $7 +) +RETURNING book_id, author_id, isbn, book_type, title, year, available, tags +` + +type BooksCreateParams struct { + AuthorID int32 + Isbn string + BookType BookType + Title string + Year int32 + Available time.Time + Tags []string +} + +func (q *Books) Create(ctx context.Context, arg BooksCreateParams) (Book, error) { + row := q.db.QueryRowContext(ctx, booksCreate, + arg.AuthorID, + arg.Isbn, + arg.BookType, + arg.Title, + arg.Year, + arg.Available, + pq.Array(arg.Tags), + ) + var i Book + err := row.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Year, + &i.Available, + pq.Array(&i.Tags), + ) + return i, err +} + +type BooksCreateStmt struct { + stmt *sql.Stmt +} + +func (s *BooksCreateStmt) Close() error { + return s.stmt.Close() +} + +func (s *BooksCreateStmt) JoinTx(ctx context.Context, tx *sql.Tx) *BooksCreateStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &BooksCreateStmt{stmt: stmt} +} + +func (s *BooksCreateStmt) Exec(ctx context.Context, arg BooksCreateParams) (Book, error) { + row := s.stmt.QueryRowContext(ctx, + arg.AuthorID, + arg.Isbn, + arg.BookType, + arg.Title, + arg.Year, + arg.Available, + pq.Array(arg.Tags), + ) + var i Book + err := row.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Year, + &i.Available, + pq.Array(&i.Tags), + ) + return i, err +} + +func (q *Books) PrepareCreate(ctx context.Context) (*BooksCreateStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksCreate) + if err != nil { + return nil, err + } + return &BooksCreateStmt{stmt: stmt}, nil +} + +const booksDelete = `-- name: Delete :exec +DELETE FROM books +WHERE book_id = $1 +` + +func (q *Books) Delete(ctx context.Context, bookID int32) error { + _, err := q.db.ExecContext(ctx, booksDelete, bookID) + return err +} + +type BooksDeleteStmt struct { + stmt *sql.Stmt +} + +func (s *BooksDeleteStmt) Close() error { + return s.stmt.Close() +} + +func (s *BooksDeleteStmt) JoinTx(ctx context.Context, tx *sql.Tx) *BooksDeleteStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &BooksDeleteStmt{stmt: stmt} +} + +func (s *BooksDeleteStmt) Exec(ctx context.Context, bookID int32) error { + _, err := s.stmt.ExecContext(ctx, bookID) + return err +} + +func (q *Books) PrepareDelete(ctx context.Context) (*BooksDeleteStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksDelete) + if err != nil { + return nil, err + } + return &BooksDeleteStmt{stmt: stmt}, nil +} + +const booksGet = `-- name: Get :one +SELECT book_id, author_id, isbn, book_type, title, year, available, tags FROM books +WHERE book_id = $1 +` + +func (q *Books) Get(ctx context.Context, bookID int32) (Book, error) { + row := q.db.QueryRowContext(ctx, booksGet, bookID) + var i Book + err := row.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Year, + &i.Available, + pq.Array(&i.Tags), + ) + return i, err +} + +type BooksGetStmt struct { + stmt *sql.Stmt +} + +func (s *BooksGetStmt) Close() error { + return s.stmt.Close() +} + +func (s *BooksGetStmt) JoinTx(ctx context.Context, tx *sql.Tx) *BooksGetStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &BooksGetStmt{stmt: stmt} +} + +func (s *BooksGetStmt) Exec(ctx context.Context, bookID int32) (Book, error) { + row := s.stmt.QueryRowContext(ctx, bookID) + var i Book + err := row.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Year, + &i.Available, + pq.Array(&i.Tags), + ) + return i, err +} + +func (q *Books) PrepareGet(ctx context.Context) (*BooksGetStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksGet) + if err != nil { + return nil, err + } + return &BooksGetStmt{stmt: stmt}, nil +} + +const booksListByTags = `-- name: ListByTags :many +SELECT + book_id, + title, + name, + isbn, + tags +FROM books +LEFT JOIN authors ON books.author_id = authors.author_id +WHERE tags && $1::varchar[] +` + +type BooksListByTagsRow struct { + BookID int32 + Title string + Name string + Isbn string + Tags []string +} + +func (q *Books) ListByTags(ctx context.Context, dollar_1 []string) ([]BooksListByTagsRow, error) { + rows, err := q.db.QueryContext(ctx, booksListByTags, pq.Array(dollar_1)) + if err != nil { + return nil, err + } + defer rows.Close() + items := []BooksListByTagsRow{} + for rows.Next() { + var i BooksListByTagsRow + if err := rows.Scan( + &i.BookID, + &i.Title, + &i.Name, + &i.Isbn, + pq.Array(&i.Tags), + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +type BooksListByTagsStmt struct { + stmt *sql.Stmt +} + +func (s *BooksListByTagsStmt) Close() error { + return s.stmt.Close() +} + +func (s *BooksListByTagsStmt) JoinTx(ctx context.Context, tx *sql.Tx) *BooksListByTagsStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &BooksListByTagsStmt{stmt: stmt} +} + +func (s *BooksListByTagsStmt) Exec(ctx context.Context, dollar_1 []string) ([]BooksListByTagsRow, error) { + rows, err := s.stmt.QueryContext(ctx, pq.Array(dollar_1)) + if err != nil { + return nil, err + } + defer rows.Close() + items := []BooksListByTagsRow{} + for rows.Next() { + var i BooksListByTagsRow + if err := rows.Scan( + &i.BookID, + &i.Title, + &i.Name, + &i.Isbn, + pq.Array(&i.Tags), + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +func (q *Books) PrepareListByTags(ctx context.Context) (*BooksListByTagsStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksListByTags) + if err != nil { + return nil, err + } + return &BooksListByTagsStmt{stmt: stmt}, nil +} + +const booksListByTitleYear = `-- name: ListByTitleYear :many +SELECT book_id, author_id, isbn, book_type, title, year, available, tags FROM books +WHERE title = $1 AND year = $2 +` + +type BooksListByTitleYearParams struct { + Title string + Year int32 +} + +func (q *Books) ListByTitleYear(ctx context.Context, arg BooksListByTitleYearParams) ([]Book, error) { + rows, err := q.db.QueryContext(ctx, booksListByTitleYear, arg.Title, arg.Year) + if err != nil { + return nil, err + } + defer rows.Close() + items := []Book{} + for rows.Next() { + var i Book + if err := rows.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Year, + &i.Available, + pq.Array(&i.Tags), + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +type BooksListByTitleYearStmt struct { + stmt *sql.Stmt +} + +func (s *BooksListByTitleYearStmt) Close() error { + return s.stmt.Close() +} + +func (s *BooksListByTitleYearStmt) JoinTx(ctx context.Context, tx *sql.Tx) *BooksListByTitleYearStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &BooksListByTitleYearStmt{stmt: stmt} +} + +func (s *BooksListByTitleYearStmt) Exec(ctx context.Context, arg BooksListByTitleYearParams) ([]Book, error) { + rows, err := s.stmt.QueryContext(ctx, arg.Title, arg.Year) + if err != nil { + return nil, err + } + defer rows.Close() + items := []Book{} + for rows.Next() { + var i Book + if err := rows.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Year, + &i.Available, + pq.Array(&i.Tags), + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +func (q *Books) PrepareListByTitleYear(ctx context.Context) (*BooksListByTitleYearStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksListByTitleYear) + if err != nil { + return nil, err + } + return &BooksListByTitleYearStmt{stmt: stmt}, nil +} + +const booksUpdate = `-- name: Update :exec +UPDATE books +SET title = $1, tags = $2 +WHERE book_id = $3 +` + +type BooksUpdateParams struct { + Title string + Tags []string + BookID int32 +} + +func (q *Books) Update(ctx context.Context, arg BooksUpdateParams) error { + _, err := q.db.ExecContext(ctx, booksUpdate, arg.Title, pq.Array(arg.Tags), arg.BookID) + return err +} + +type BooksUpdateStmt struct { + stmt *sql.Stmt +} + +func (s *BooksUpdateStmt) Close() error { + return s.stmt.Close() +} + +func (s *BooksUpdateStmt) JoinTx(ctx context.Context, tx *sql.Tx) *BooksUpdateStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &BooksUpdateStmt{stmt: stmt} +} + +func (s *BooksUpdateStmt) Exec(ctx context.Context, arg BooksUpdateParams) error { + _, err := s.stmt.ExecContext(ctx, arg.Title, pq.Array(arg.Tags), arg.BookID) + return err +} + +func (q *Books) PrepareUpdate(ctx context.Context) (*BooksUpdateStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksUpdate) + if err != nil { + return nil, err + } + return &BooksUpdateStmt{stmt: stmt}, nil +} + +const booksUpdateISBN = `-- name: UpdateISBN :exec +UPDATE books +SET title = $1, tags = $2, isbn = $4 +WHERE book_id = $3 +` + +type BooksUpdateISBNParams struct { + Title string + Tags []string + BookID int32 + Isbn string +} + +func (q *Books) UpdateISBN(ctx context.Context, arg BooksUpdateISBNParams) error { + _, err := q.db.ExecContext(ctx, booksUpdateISBN, + arg.Title, + pq.Array(arg.Tags), + arg.BookID, + arg.Isbn, + ) + return err +} + +type BooksUpdateISBNStmt struct { + stmt *sql.Stmt +} + +func (s *BooksUpdateISBNStmt) Close() error { + return s.stmt.Close() +} + +func (s *BooksUpdateISBNStmt) JoinTx(ctx context.Context, tx *sql.Tx) *BooksUpdateISBNStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &BooksUpdateISBNStmt{stmt: stmt} +} + +func (s *BooksUpdateISBNStmt) Exec(ctx context.Context, arg BooksUpdateISBNParams) error { + _, err := s.stmt.ExecContext(ctx, + arg.Title, + pq.Array(arg.Tags), + arg.BookID, + arg.Isbn, + ) + return err +} + +func (q *Books) PrepareUpdateISBN(ctx context.Context) (*BooksUpdateISBNStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksUpdateISBN) + if err != nil { + return nil, err + } + return &BooksUpdateISBNStmt{stmt: stmt}, nil +} diff --git a/examples/group/postgresql/pggroupstmt/db.go b/examples/group/postgresql/pggroupstmt/db.go new file mode 100644 index 0000000000..a7c155be7a --- /dev/null +++ b/examples/group/postgresql/pggroupstmt/db.go @@ -0,0 +1,15 @@ +// Code generated by sqlc. DO NOT EDIT. + +package pggroupstmt + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} diff --git a/examples/group/postgresql/pggroupstmt/db_test.go b/examples/group/postgresql/pggroupstmt/db_test.go new file mode 100644 index 0000000000..a15ca99b68 --- /dev/null +++ b/examples/group/postgresql/pggroupstmt/db_test.go @@ -0,0 +1,307 @@ +// +build examples + +package pggroupstmt_test + +import ( + "context" + "database/sql" + "testing" + "time" + + "github.com/kyleconroy/sqlc/examples/group/postgresql/pggroupstmt" + "github.com/kyleconroy/sqlc/internal/sqltest" +) + +func TestBooks(t *testing.T) { + db, cleanup := sqltest.PostgreSQL(t, []string{"../sql/schema/schema.sql"}) + defer cleanup() + + ctx := context.Background() + + // create an author + a, err := func() (retModel pggroupstmt.Author, retErr error) { + dbauthors := pggroupstmt.NewAuthors(db) + dbcreate, err := dbauthors.PrepareCreate(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := dbcreate.Close(); err != nil && retErr == nil { + retErr = err + } + }() + a, err := dbcreate.Exec(ctx, "Unknown Master") + if err != nil { + retErr = err + return + } + retModel = a + return + }() + if err != nil { + t.Fatal(err) + } + + // create transaction + b3, err := func() (retModel pggroupstmt.Book, retErr error) { + tx, err := db.Begin() + if err != nil { + retErr = err + return + } + defer func() { + if err := tx.Rollback(); err != nil && err != sql.ErrTxDone && retErr == nil { + retErr = err + } + }() + + txbooks := pggroupstmt.NewBooks(tx) + + txcreate, err := txbooks.PrepareCreate(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := txcreate.Close(); err != nil && retErr == nil { + retErr = err + } + }() + + // save first book + now := time.Now() + _, err = txcreate.Exec(ctx, pggroupstmt.BooksCreateParams{ + AuthorID: a.AuthorID, + Isbn: "1", + Title: "my book title", + BookType: pggroupstmt.BookTypeFICTION, + Year: 2016, + Available: now, + Tags: []string{}, + }) + if err != nil { + retErr = err + return + } + + // save second book + b1, err := txcreate.Exec(ctx, pggroupstmt.BooksCreateParams{ + AuthorID: a.AuthorID, + Isbn: "2", + Title: "the second book", + BookType: pggroupstmt.BookTypeFICTION, + Year: 2016, + Available: now, + Tags: []string{"cool", "unique"}, + }) + if err != nil { + retErr = err + return + } + + // update the title and tags + txupdate, err := txbooks.PrepareUpdate(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := txupdate.Close(); err != nil && retErr == nil { + retErr = err + } + }() + err = txupdate.Exec(ctx, pggroupstmt.BooksUpdateParams{ + BookID: b1.BookID, + Title: "changed second title", + Tags: []string{"cool", "disastor"}, + }) + if err != nil { + retErr = err + return + } + + // save third book + _, err = txcreate.Exec(ctx, pggroupstmt.BooksCreateParams{ + AuthorID: a.AuthorID, + Isbn: "3", + Title: "the third book", + BookType: pggroupstmt.BookTypeFICTION, + Year: 2001, + Available: now, + Tags: []string{"cool"}, + }) + if err != nil { + retErr = err + return + } + + // save fourth book + b3, err := txcreate.Exec(ctx, pggroupstmt.BooksCreateParams{ + AuthorID: a.AuthorID, + Isbn: "4", + Title: "4th place finisher", + BookType: pggroupstmt.BookTypeNONFICTION, + Year: 2011, + Available: now, + Tags: []string{"other"}, + }) + if err != nil { + retErr = err + return + } + + // tx commit + err = tx.Commit() + if err != nil { + retErr = err + return + } + retModel = b3 + return + }() + if err != nil { + t.Fatal(err) + } + + // upsert, changing ISBN and title + err = func() (retErr error) { + dbbooks := pggroupstmt.NewBooks(db) + dbupdate, err := dbbooks.PrepareUpdateISBN(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := dbupdate.Close(); err != nil && retErr == nil { + retErr = err + } + }() + retErr = dbupdate.Exec(ctx, pggroupstmt.BooksUpdateISBNParams{ + BookID: b3.BookID, + Isbn: "NEW ISBN", + Title: "never ever gonna finish, a quatrain", + Tags: []string{"someother"}, + }) + return + }() + if err != nil { + t.Fatal(err) + } + + // retrieve first book + err = func() (retErr error) { + dbbooks := pggroupstmt.NewBooks(db) + dblist, err := dbbooks.PrepareListByTitleYear(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := dblist.Close(); err != nil && retErr == nil { + retErr = err + return + } + }() + books0, err := dblist.Exec(ctx, pggroupstmt.BooksListByTitleYearParams{ + Title: "my book title", + Year: 2016, + }) + if err != nil { + retErr = err + return + } + dbauthors := pggroupstmt.NewAuthors(db) + dbget, err := dbauthors.PrepareGet(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := dbget.Close(); err != nil && retErr == nil { + retErr = err + return + } + }() + for _, book := range books0 { + t.Logf("Book %d (%s): %s available: %s\n", book.BookID, book.BookType, book.Title, book.Available.Format(time.RFC822Z)) + author, err := dbget.Exec(ctx, book.AuthorID) + if err != nil { + retErr = err + return + } + t.Logf("Book %d author: %s\n", book.BookID, author.Name) + } + return + }() + if err != nil { + t.Fatal(err) + } + + // find a book with either "cool" or "other" tag + err = func() (retErr error) { + t.Logf("---------\nTag search results:\n") + dbbooks := pggroupstmt.NewBooks(db) + dblist, err := dbbooks.PrepareListByTags(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := dblist.Close(); err != nil && retErr == nil { + retErr = err + return + } + }() + res, err := dblist.Exec(ctx, []string{"cool", "other", "someother"}) + if err != nil { + retErr = err + return + } + for _, ab := range res { + t.Logf("Book %d: '%s', Author: '%s', ISBN: '%s' Tags: '%v'\n", ab.BookID, ab.Title, ab.Name, ab.Isbn, ab.Tags) + } + return + }() + if err != nil { + t.Fatal(err) + } + + // get book 4 and delete + err = func() (retErr error) { + dbbooks := pggroupstmt.NewBooks(db) + dbget, err := dbbooks.PrepareGet(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := dbget.Close(); err != nil && retErr == nil { + retErr = err + } + }() + b5, err := dbget.Exec(ctx, b3.BookID) + if err != nil { + retErr = err + return + } + dbdelete, err := dbbooks.PrepareDelete(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := dbdelete.Close(); err != nil && retErr == nil { + retErr = err + } + }() + if err := dbdelete.Exec(ctx, b5.BookID); err != nil { + retErr = err + return + } + return + }() + if err != nil { + t.Fatal(err) + } +} diff --git a/examples/group/postgresql/pggroupstmt/models.go b/examples/group/postgresql/pggroupstmt/models.go new file mode 100644 index 0000000000..45f7cf1197 --- /dev/null +++ b/examples/group/postgresql/pggroupstmt/models.go @@ -0,0 +1,43 @@ +// Code generated by sqlc. DO NOT EDIT. + +package pggroupstmt + +import ( + "fmt" + "time" +) + +type BookType string + +const ( + BookTypeFICTION BookType = "FICTION" + BookTypeNONFICTION BookType = "NONFICTION" +) + +func (e *BookType) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = BookType(s) + case string: + *e = BookType(s) + default: + return fmt.Errorf("unsupported scan type for BookType: %T", src) + } + return nil +} + +type Author struct { + AuthorID int32 + Name string +} + +type Book struct { + BookID int32 + AuthorID int32 + Isbn string + BookType BookType + Title string + Year int32 + Available time.Time + Tags []string +} diff --git a/examples/group/postgresql/pggroupstmtiface/authors.sql.go b/examples/group/postgresql/pggroupstmtiface/authors.sql.go new file mode 100644 index 0000000000..4f6d246556 --- /dev/null +++ b/examples/group/postgresql/pggroupstmtiface/authors.sql.go @@ -0,0 +1,116 @@ +// Code generated by sqlc. DO NOT EDIT. +// source: authors.sql + +package pggroupstmtiface + +import ( + "context" + "database/sql" +) + +type Authors interface { + Create(ctx context.Context, name string) (Author, error) + PrepareCreate(ctx context.Context) (AuthorsCreateStmt, error) + Get(ctx context.Context, authorID int32) (Author, error) + PrepareGet(ctx context.Context) (AuthorsGetStmt, error) +} + +func NewAuthors(db DBTX) Authors { + return &authors{db: db} +} + +type authors struct { + db DBTX +} + +const authorsCreate = `-- name: Create :one +INSERT INTO authors (name) VALUES ($1) +RETURNING author_id, name +` + +func (q *authors) Create(ctx context.Context, name string) (Author, error) { + row := q.db.QueryRowContext(ctx, authorsCreate, name) + var i Author + err := row.Scan(&i.AuthorID, &i.Name) + return i, err +} + +type AuthorsCreateStmt interface { + Close() error + JoinTx(ctx context.Context, tx *sql.Tx) AuthorsCreateStmt + Exec(ctx context.Context, name string) (Author, error) +} + +type authorsCreateStmt struct { + stmt *sql.Stmt +} + +func (s *authorsCreateStmt) Close() error { + return s.stmt.Close() +} + +func (s *authorsCreateStmt) JoinTx(ctx context.Context, tx *sql.Tx) AuthorsCreateStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &authorsCreateStmt{stmt: stmt} +} + +func (s *authorsCreateStmt) Exec(ctx context.Context, name string) (Author, error) { + row := s.stmt.QueryRowContext(ctx, name) + var i Author + err := row.Scan(&i.AuthorID, &i.Name) + return i, err +} + +func (q *authors) PrepareCreate(ctx context.Context) (AuthorsCreateStmt, error) { + stmt, err := q.db.PrepareContext(ctx, authorsCreate) + if err != nil { + return nil, err + } + return &authorsCreateStmt{stmt: stmt}, nil +} + +const authorsGet = `-- name: Get :one +SELECT author_id, name FROM authors +WHERE author_id = $1 +` + +func (q *authors) Get(ctx context.Context, authorID int32) (Author, error) { + row := q.db.QueryRowContext(ctx, authorsGet, authorID) + var i Author + err := row.Scan(&i.AuthorID, &i.Name) + return i, err +} + +type AuthorsGetStmt interface { + Close() error + JoinTx(ctx context.Context, tx *sql.Tx) AuthorsGetStmt + Exec(ctx context.Context, authorID int32) (Author, error) +} + +type authorsGetStmt struct { + stmt *sql.Stmt +} + +func (s *authorsGetStmt) Close() error { + return s.stmt.Close() +} + +func (s *authorsGetStmt) JoinTx(ctx context.Context, tx *sql.Tx) AuthorsGetStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &authorsGetStmt{stmt: stmt} +} + +func (s *authorsGetStmt) Exec(ctx context.Context, authorID int32) (Author, error) { + row := s.stmt.QueryRowContext(ctx, authorID) + var i Author + err := row.Scan(&i.AuthorID, &i.Name) + return i, err +} + +func (q *authors) PrepareGet(ctx context.Context) (AuthorsGetStmt, error) { + stmt, err := q.db.PrepareContext(ctx, authorsGet) + if err != nil { + return nil, err + } + return &authorsGetStmt{stmt: stmt}, nil +} diff --git a/examples/group/postgresql/pggroupstmtiface/books.sql.go b/examples/group/postgresql/pggroupstmtiface/books.sql.go new file mode 100644 index 0000000000..d15873e0b0 --- /dev/null +++ b/examples/group/postgresql/pggroupstmtiface/books.sql.go @@ -0,0 +1,564 @@ +// Code generated by sqlc. DO NOT EDIT. +// source: books.sql + +package pggroupstmtiface + +import ( + "context" + "database/sql" + "time" + + "github.com/lib/pq" +) + +type Books interface { + Create(ctx context.Context, arg BooksCreateParams) (Book, error) + PrepareCreate(ctx context.Context) (BooksCreateStmt, error) + Delete(ctx context.Context, bookID int32) error + PrepareDelete(ctx context.Context) (BooksDeleteStmt, error) + Get(ctx context.Context, bookID int32) (Book, error) + PrepareGet(ctx context.Context) (BooksGetStmt, error) + ListByTags(ctx context.Context, dollar_1 []string) ([]BooksListByTagsRow, error) + PrepareListByTags(ctx context.Context) (BooksListByTagsStmt, error) + ListByTitleYear(ctx context.Context, arg BooksListByTitleYearParams) ([]Book, error) + PrepareListByTitleYear(ctx context.Context) (BooksListByTitleYearStmt, error) + Update(ctx context.Context, arg BooksUpdateParams) error + PrepareUpdate(ctx context.Context) (BooksUpdateStmt, error) + UpdateISBN(ctx context.Context, arg BooksUpdateISBNParams) error + PrepareUpdateISBN(ctx context.Context) (BooksUpdateISBNStmt, error) +} + +func NewBooks(db DBTX) Books { + return &books{db: db} +} + +type books struct { + db DBTX +} + +const booksCreate = `-- name: Create :one +INSERT INTO books ( + author_id, + isbn, + book_type, + title, + year, + available, + tags +) VALUES ( + $1, + $2, + $3, + $4, + $5, + $6, + $7 +) +RETURNING book_id, author_id, isbn, book_type, title, year, available, tags +` + +type BooksCreateParams struct { + AuthorID int32 + Isbn string + BookType BookType + Title string + Year int32 + Available time.Time + Tags []string +} + +func (q *books) Create(ctx context.Context, arg BooksCreateParams) (Book, error) { + row := q.db.QueryRowContext(ctx, booksCreate, + arg.AuthorID, + arg.Isbn, + arg.BookType, + arg.Title, + arg.Year, + arg.Available, + pq.Array(arg.Tags), + ) + var i Book + err := row.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Year, + &i.Available, + pq.Array(&i.Tags), + ) + return i, err +} + +type BooksCreateStmt interface { + Close() error + JoinTx(ctx context.Context, tx *sql.Tx) BooksCreateStmt + Exec(ctx context.Context, arg BooksCreateParams) (Book, error) +} + +type booksCreateStmt struct { + stmt *sql.Stmt +} + +func (s *booksCreateStmt) Close() error { + return s.stmt.Close() +} + +func (s *booksCreateStmt) JoinTx(ctx context.Context, tx *sql.Tx) BooksCreateStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &booksCreateStmt{stmt: stmt} +} + +func (s *booksCreateStmt) Exec(ctx context.Context, arg BooksCreateParams) (Book, error) { + row := s.stmt.QueryRowContext(ctx, + arg.AuthorID, + arg.Isbn, + arg.BookType, + arg.Title, + arg.Year, + arg.Available, + pq.Array(arg.Tags), + ) + var i Book + err := row.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Year, + &i.Available, + pq.Array(&i.Tags), + ) + return i, err +} + +func (q *books) PrepareCreate(ctx context.Context) (BooksCreateStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksCreate) + if err != nil { + return nil, err + } + return &booksCreateStmt{stmt: stmt}, nil +} + +const booksDelete = `-- name: Delete :exec +DELETE FROM books +WHERE book_id = $1 +` + +func (q *books) Delete(ctx context.Context, bookID int32) error { + _, err := q.db.ExecContext(ctx, booksDelete, bookID) + return err +} + +type BooksDeleteStmt interface { + Close() error + JoinTx(ctx context.Context, tx *sql.Tx) BooksDeleteStmt + Exec(ctx context.Context, bookID int32) error +} + +type booksDeleteStmt struct { + stmt *sql.Stmt +} + +func (s *booksDeleteStmt) Close() error { + return s.stmt.Close() +} + +func (s *booksDeleteStmt) JoinTx(ctx context.Context, tx *sql.Tx) BooksDeleteStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &booksDeleteStmt{stmt: stmt} +} + +func (s *booksDeleteStmt) Exec(ctx context.Context, bookID int32) error { + _, err := s.stmt.ExecContext(ctx, bookID) + return err +} + +func (q *books) PrepareDelete(ctx context.Context) (BooksDeleteStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksDelete) + if err != nil { + return nil, err + } + return &booksDeleteStmt{stmt: stmt}, nil +} + +const booksGet = `-- name: Get :one +SELECT book_id, author_id, isbn, book_type, title, year, available, tags FROM books +WHERE book_id = $1 +` + +func (q *books) Get(ctx context.Context, bookID int32) (Book, error) { + row := q.db.QueryRowContext(ctx, booksGet, bookID) + var i Book + err := row.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Year, + &i.Available, + pq.Array(&i.Tags), + ) + return i, err +} + +type BooksGetStmt interface { + Close() error + JoinTx(ctx context.Context, tx *sql.Tx) BooksGetStmt + Exec(ctx context.Context, bookID int32) (Book, error) +} + +type booksGetStmt struct { + stmt *sql.Stmt +} + +func (s *booksGetStmt) Close() error { + return s.stmt.Close() +} + +func (s *booksGetStmt) JoinTx(ctx context.Context, tx *sql.Tx) BooksGetStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &booksGetStmt{stmt: stmt} +} + +func (s *booksGetStmt) Exec(ctx context.Context, bookID int32) (Book, error) { + row := s.stmt.QueryRowContext(ctx, bookID) + var i Book + err := row.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Year, + &i.Available, + pq.Array(&i.Tags), + ) + return i, err +} + +func (q *books) PrepareGet(ctx context.Context) (BooksGetStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksGet) + if err != nil { + return nil, err + } + return &booksGetStmt{stmt: stmt}, nil +} + +const booksListByTags = `-- name: ListByTags :many +SELECT + book_id, + title, + name, + isbn, + tags +FROM books +LEFT JOIN authors ON books.author_id = authors.author_id +WHERE tags && $1::varchar[] +` + +type BooksListByTagsRow struct { + BookID int32 + Title string + Name string + Isbn string + Tags []string +} + +func (q *books) ListByTags(ctx context.Context, dollar_1 []string) ([]BooksListByTagsRow, error) { + rows, err := q.db.QueryContext(ctx, booksListByTags, pq.Array(dollar_1)) + if err != nil { + return nil, err + } + defer rows.Close() + items := []BooksListByTagsRow{} + for rows.Next() { + var i BooksListByTagsRow + if err := rows.Scan( + &i.BookID, + &i.Title, + &i.Name, + &i.Isbn, + pq.Array(&i.Tags), + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +type BooksListByTagsStmt interface { + Close() error + JoinTx(ctx context.Context, tx *sql.Tx) BooksListByTagsStmt + Exec(ctx context.Context, dollar_1 []string) ([]BooksListByTagsRow, error) +} + +type booksListByTagsStmt struct { + stmt *sql.Stmt +} + +func (s *booksListByTagsStmt) Close() error { + return s.stmt.Close() +} + +func (s *booksListByTagsStmt) JoinTx(ctx context.Context, tx *sql.Tx) BooksListByTagsStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &booksListByTagsStmt{stmt: stmt} +} + +func (s *booksListByTagsStmt) Exec(ctx context.Context, dollar_1 []string) ([]BooksListByTagsRow, error) { + rows, err := s.stmt.QueryContext(ctx, pq.Array(dollar_1)) + if err != nil { + return nil, err + } + defer rows.Close() + items := []BooksListByTagsRow{} + for rows.Next() { + var i BooksListByTagsRow + if err := rows.Scan( + &i.BookID, + &i.Title, + &i.Name, + &i.Isbn, + pq.Array(&i.Tags), + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +func (q *books) PrepareListByTags(ctx context.Context) (BooksListByTagsStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksListByTags) + if err != nil { + return nil, err + } + return &booksListByTagsStmt{stmt: stmt}, nil +} + +const booksListByTitleYear = `-- name: ListByTitleYear :many +SELECT book_id, author_id, isbn, book_type, title, year, available, tags FROM books +WHERE title = $1 AND year = $2 +` + +type BooksListByTitleYearParams struct { + Title string + Year int32 +} + +func (q *books) ListByTitleYear(ctx context.Context, arg BooksListByTitleYearParams) ([]Book, error) { + rows, err := q.db.QueryContext(ctx, booksListByTitleYear, arg.Title, arg.Year) + if err != nil { + return nil, err + } + defer rows.Close() + items := []Book{} + for rows.Next() { + var i Book + if err := rows.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Year, + &i.Available, + pq.Array(&i.Tags), + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +type BooksListByTitleYearStmt interface { + Close() error + JoinTx(ctx context.Context, tx *sql.Tx) BooksListByTitleYearStmt + Exec(ctx context.Context, arg BooksListByTitleYearParams) ([]Book, error) +} + +type booksListByTitleYearStmt struct { + stmt *sql.Stmt +} + +func (s *booksListByTitleYearStmt) Close() error { + return s.stmt.Close() +} + +func (s *booksListByTitleYearStmt) JoinTx(ctx context.Context, tx *sql.Tx) BooksListByTitleYearStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &booksListByTitleYearStmt{stmt: stmt} +} + +func (s *booksListByTitleYearStmt) Exec(ctx context.Context, arg BooksListByTitleYearParams) ([]Book, error) { + rows, err := s.stmt.QueryContext(ctx, arg.Title, arg.Year) + if err != nil { + return nil, err + } + defer rows.Close() + items := []Book{} + for rows.Next() { + var i Book + if err := rows.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Year, + &i.Available, + pq.Array(&i.Tags), + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +func (q *books) PrepareListByTitleYear(ctx context.Context) (BooksListByTitleYearStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksListByTitleYear) + if err != nil { + return nil, err + } + return &booksListByTitleYearStmt{stmt: stmt}, nil +} + +const booksUpdate = `-- name: Update :exec +UPDATE books +SET title = $1, tags = $2 +WHERE book_id = $3 +` + +type BooksUpdateParams struct { + Title string + Tags []string + BookID int32 +} + +func (q *books) Update(ctx context.Context, arg BooksUpdateParams) error { + _, err := q.db.ExecContext(ctx, booksUpdate, arg.Title, pq.Array(arg.Tags), arg.BookID) + return err +} + +type BooksUpdateStmt interface { + Close() error + JoinTx(ctx context.Context, tx *sql.Tx) BooksUpdateStmt + Exec(ctx context.Context, arg BooksUpdateParams) error +} + +type booksUpdateStmt struct { + stmt *sql.Stmt +} + +func (s *booksUpdateStmt) Close() error { + return s.stmt.Close() +} + +func (s *booksUpdateStmt) JoinTx(ctx context.Context, tx *sql.Tx) BooksUpdateStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &booksUpdateStmt{stmt: stmt} +} + +func (s *booksUpdateStmt) Exec(ctx context.Context, arg BooksUpdateParams) error { + _, err := s.stmt.ExecContext(ctx, arg.Title, pq.Array(arg.Tags), arg.BookID) + return err +} + +func (q *books) PrepareUpdate(ctx context.Context) (BooksUpdateStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksUpdate) + if err != nil { + return nil, err + } + return &booksUpdateStmt{stmt: stmt}, nil +} + +const booksUpdateISBN = `-- name: UpdateISBN :exec +UPDATE books +SET title = $1, tags = $2, isbn = $4 +WHERE book_id = $3 +` + +type BooksUpdateISBNParams struct { + Title string + Tags []string + BookID int32 + Isbn string +} + +func (q *books) UpdateISBN(ctx context.Context, arg BooksUpdateISBNParams) error { + _, err := q.db.ExecContext(ctx, booksUpdateISBN, + arg.Title, + pq.Array(arg.Tags), + arg.BookID, + arg.Isbn, + ) + return err +} + +type BooksUpdateISBNStmt interface { + Close() error + JoinTx(ctx context.Context, tx *sql.Tx) BooksUpdateISBNStmt + Exec(ctx context.Context, arg BooksUpdateISBNParams) error +} + +type booksUpdateISBNStmt struct { + stmt *sql.Stmt +} + +func (s *booksUpdateISBNStmt) Close() error { + return s.stmt.Close() +} + +func (s *booksUpdateISBNStmt) JoinTx(ctx context.Context, tx *sql.Tx) BooksUpdateISBNStmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &booksUpdateISBNStmt{stmt: stmt} +} + +func (s *booksUpdateISBNStmt) Exec(ctx context.Context, arg BooksUpdateISBNParams) error { + _, err := s.stmt.ExecContext(ctx, + arg.Title, + pq.Array(arg.Tags), + arg.BookID, + arg.Isbn, + ) + return err +} + +func (q *books) PrepareUpdateISBN(ctx context.Context) (BooksUpdateISBNStmt, error) { + stmt, err := q.db.PrepareContext(ctx, booksUpdateISBN) + if err != nil { + return nil, err + } + return &booksUpdateISBNStmt{stmt: stmt}, nil +} diff --git a/examples/group/postgresql/pggroupstmtiface/db.go b/examples/group/postgresql/pggroupstmtiface/db.go new file mode 100644 index 0000000000..cf88f925ba --- /dev/null +++ b/examples/group/postgresql/pggroupstmtiface/db.go @@ -0,0 +1,15 @@ +// Code generated by sqlc. DO NOT EDIT. + +package pggroupstmtiface + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} diff --git a/examples/group/postgresql/pggroupstmtiface/db_test.go b/examples/group/postgresql/pggroupstmtiface/db_test.go new file mode 100644 index 0000000000..7d53d2c7bc --- /dev/null +++ b/examples/group/postgresql/pggroupstmtiface/db_test.go @@ -0,0 +1,307 @@ +// +build examples + +package pggroupstmtiface_test + +import ( + "context" + "database/sql" + "testing" + "time" + + "github.com/kyleconroy/sqlc/examples/group/postgresql/pggroupstmtiface" + "github.com/kyleconroy/sqlc/internal/sqltest" +) + +func TestBooks(t *testing.T) { + db, cleanup := sqltest.PostgreSQL(t, []string{"../sql/schema/schema.sql"}) + defer cleanup() + + ctx := context.Background() + + // create an author + a, err := func() (retModel pggroupstmtiface.Author, retErr error) { + dbauthors := pggroupstmtiface.NewAuthors(db) + dbcreate, err := dbauthors.PrepareCreate(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := dbcreate.Close(); err != nil && retErr == nil { + retErr = err + } + }() + a, err := dbcreate.Exec(ctx, "Unknown Master") + if err != nil { + retErr = err + return + } + retModel = a + return + }() + if err != nil { + t.Fatal(err) + } + + // create transaction + b3, err := func() (retModel pggroupstmtiface.Book, retErr error) { + tx, err := db.Begin() + if err != nil { + retErr = err + return + } + defer func() { + if err := tx.Rollback(); err != nil && err != sql.ErrTxDone && retErr == nil { + retErr = err + } + }() + + txbooks := pggroupstmtiface.NewBooks(tx) + + txcreate, err := txbooks.PrepareCreate(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := txcreate.Close(); err != nil && retErr == nil { + retErr = err + } + }() + + // save first book + now := time.Now() + _, err = txcreate.Exec(ctx, pggroupstmtiface.BooksCreateParams{ + AuthorID: a.AuthorID, + Isbn: "1", + Title: "my book title", + BookType: pggroupstmtiface.BookTypeFICTION, + Year: 2016, + Available: now, + Tags: []string{}, + }) + if err != nil { + retErr = err + return + } + + // save second book + b1, err := txcreate.Exec(ctx, pggroupstmtiface.BooksCreateParams{ + AuthorID: a.AuthorID, + Isbn: "2", + Title: "the second book", + BookType: pggroupstmtiface.BookTypeFICTION, + Year: 2016, + Available: now, + Tags: []string{"cool", "unique"}, + }) + if err != nil { + retErr = err + return + } + + // update the title and tags + txupdate, err := txbooks.PrepareUpdate(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := txupdate.Close(); err != nil && retErr == nil { + retErr = err + } + }() + err = txupdate.Exec(ctx, pggroupstmtiface.BooksUpdateParams{ + BookID: b1.BookID, + Title: "changed second title", + Tags: []string{"cool", "disastor"}, + }) + if err != nil { + retErr = err + return + } + + // save third book + _, err = txcreate.Exec(ctx, pggroupstmtiface.BooksCreateParams{ + AuthorID: a.AuthorID, + Isbn: "3", + Title: "the third book", + BookType: pggroupstmtiface.BookTypeFICTION, + Year: 2001, + Available: now, + Tags: []string{"cool"}, + }) + if err != nil { + retErr = err + return + } + + // save fourth book + b3, err := txcreate.Exec(ctx, pggroupstmtiface.BooksCreateParams{ + AuthorID: a.AuthorID, + Isbn: "4", + Title: "4th place finisher", + BookType: pggroupstmtiface.BookTypeNONFICTION, + Year: 2011, + Available: now, + Tags: []string{"other"}, + }) + if err != nil { + retErr = err + return + } + + // tx commit + err = tx.Commit() + if err != nil { + retErr = err + return + } + retModel = b3 + return + }() + if err != nil { + t.Fatal(err) + } + + // upsert, changing ISBN and title + err = func() (retErr error) { + dbbooks := pggroupstmtiface.NewBooks(db) + dbupdate, err := dbbooks.PrepareUpdateISBN(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := dbupdate.Close(); err != nil && retErr == nil { + retErr = err + } + }() + retErr = dbupdate.Exec(ctx, pggroupstmtiface.BooksUpdateISBNParams{ + BookID: b3.BookID, + Isbn: "NEW ISBN", + Title: "never ever gonna finish, a quatrain", + Tags: []string{"someother"}, + }) + return + }() + if err != nil { + t.Fatal(err) + } + + // retrieve first book + err = func() (retErr error) { + dbbooks := pggroupstmtiface.NewBooks(db) + dblist, err := dbbooks.PrepareListByTitleYear(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := dblist.Close(); err != nil && retErr == nil { + retErr = err + return + } + }() + books0, err := dblist.Exec(ctx, pggroupstmtiface.BooksListByTitleYearParams{ + Title: "my book title", + Year: 2016, + }) + if err != nil { + retErr = err + return + } + dbauthors := pggroupstmtiface.NewAuthors(db) + dbget, err := dbauthors.PrepareGet(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := dbget.Close(); err != nil && retErr == nil { + retErr = err + return + } + }() + for _, book := range books0 { + t.Logf("Book %d (%s): %s available: %s\n", book.BookID, book.BookType, book.Title, book.Available.Format(time.RFC822Z)) + author, err := dbget.Exec(ctx, book.AuthorID) + if err != nil { + retErr = err + return + } + t.Logf("Book %d author: %s\n", book.BookID, author.Name) + } + return + }() + if err != nil { + t.Fatal(err) + } + + // find a book with either "cool" or "other" tag + err = func() (retErr error) { + t.Logf("---------\nTag search results:\n") + dbbooks := pggroupstmtiface.NewBooks(db) + dblist, err := dbbooks.PrepareListByTags(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := dblist.Close(); err != nil && retErr == nil { + retErr = err + return + } + }() + res, err := dblist.Exec(ctx, []string{"cool", "other", "someother"}) + if err != nil { + retErr = err + return + } + for _, ab := range res { + t.Logf("Book %d: '%s', Author: '%s', ISBN: '%s' Tags: '%v'\n", ab.BookID, ab.Title, ab.Name, ab.Isbn, ab.Tags) + } + return + }() + if err != nil { + t.Fatal(err) + } + + // get book 4 and delete + err = func() (retErr error) { + dbbooks := pggroupstmtiface.NewBooks(db) + dbget, err := dbbooks.PrepareGet(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := dbget.Close(); err != nil && retErr == nil { + retErr = err + } + }() + b5, err := dbget.Exec(ctx, b3.BookID) + if err != nil { + retErr = err + return + } + dbdelete, err := dbbooks.PrepareDelete(ctx) + if err != nil { + retErr = err + return + } + defer func() { + if err := dbdelete.Close(); err != nil && retErr == nil { + retErr = err + } + }() + if err := dbdelete.Exec(ctx, b5.BookID); err != nil { + retErr = err + return + } + return + }() + if err != nil { + t.Fatal(err) + } +} diff --git a/examples/group/postgresql/pggroupstmtiface/models.go b/examples/group/postgresql/pggroupstmtiface/models.go new file mode 100644 index 0000000000..f94f776bb1 --- /dev/null +++ b/examples/group/postgresql/pggroupstmtiface/models.go @@ -0,0 +1,43 @@ +// Code generated by sqlc. DO NOT EDIT. + +package pggroupstmtiface + +import ( + "fmt" + "time" +) + +type BookType string + +const ( + BookTypeFICTION BookType = "FICTION" + BookTypeNONFICTION BookType = "NONFICTION" +) + +func (e *BookType) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = BookType(s) + case string: + *e = BookType(s) + default: + return fmt.Errorf("unsupported scan type for BookType: %T", src) + } + return nil +} + +type Author struct { + AuthorID int32 + Name string +} + +type Book struct { + BookID int32 + AuthorID int32 + Isbn string + BookType BookType + Title string + Year int32 + Available time.Time + Tags []string +} diff --git a/examples/group/postgresql/sql/query/authors.sql b/examples/group/postgresql/sql/query/authors.sql new file mode 100644 index 0000000000..202c61517c --- /dev/null +++ b/examples/group/postgresql/sql/query/authors.sql @@ -0,0 +1,7 @@ +-- name: Get :one +SELECT * FROM authors +WHERE author_id = $1; + +-- name: Create :one +INSERT INTO authors (name) VALUES ($1) +RETURNING *; diff --git a/examples/group/postgresql/sql/query/books.sql b/examples/group/postgresql/sql/query/books.sql new file mode 100644 index 0000000000..eb4937fc36 --- /dev/null +++ b/examples/group/postgresql/sql/query/books.sql @@ -0,0 +1,52 @@ +-- name: Get :one +SELECT * FROM books +WHERE book_id = $1; + +-- name: Delete :exec +DELETE FROM books +WHERE book_id = $1; + +-- name: ListByTitleYear :many +SELECT * FROM books +WHERE title = $1 AND year = $2; + +-- name: ListByTags :many +SELECT + book_id, + title, + name, + isbn, + tags +FROM books +LEFT JOIN authors ON books.author_id = authors.author_id +WHERE tags && $1::varchar[]; + +-- name: Create :one +INSERT INTO books ( + author_id, + isbn, + book_type, + title, + year, + available, + tags +) VALUES ( + $1, + $2, + $3, + $4, + $5, + $6, + $7 +) +RETURNING *; + +-- name: Update :exec +UPDATE books +SET title = $1, tags = $2 +WHERE book_id = $3; + +-- name: UpdateISBN :exec +UPDATE books +SET title = $1, tags = $2, isbn = $4 +WHERE book_id = $3; diff --git a/examples/group/postgresql/sql/schema/schema.sql b/examples/group/postgresql/sql/schema/schema.sql new file mode 100644 index 0000000000..2beecaba1a --- /dev/null +++ b/examples/group/postgresql/sql/schema/schema.sql @@ -0,0 +1,32 @@ +CREATE TABLE authors ( + author_id SERIAL PRIMARY KEY, + name text NOT NULL DEFAULT '' +); + +CREATE INDEX authors_name_idx ON authors(name); + +CREATE TYPE book_type AS ENUM ( + 'FICTION', + 'NONFICTION' +); + +CREATE TABLE books ( + book_id SERIAL PRIMARY KEY, + author_id integer NOT NULL REFERENCES authors(author_id), + isbn text NOT NULL DEFAULT '' UNIQUE, + book_type book_type NOT NULL DEFAULT 'FICTION', + title text NOT NULL DEFAULT '', + year integer NOT NULL DEFAULT 2000, + available timestamp with time zone NOT NULL DEFAULT 'NOW()', + tags varchar[] NOT NULL DEFAULT '{}' +); + +CREATE INDEX books_title_idx ON books(title, year); + +CREATE FUNCTION say_hello(text) RETURNS text AS $$ +BEGIN + RETURN CONCAT('hello ', $1); +END; +$$ LANGUAGE plpgsql; + +CREATE INDEX books_title_lower_idx ON books(title); diff --git a/examples/group/sqlc.json b/examples/group/sqlc.json new file mode 100644 index 0000000000..597bab0969 --- /dev/null +++ b/examples/group/sqlc.json @@ -0,0 +1,85 @@ +{ + "version": "1", + "packages": [ + { + "name": "mygroup", + "path": "mysql/mygroup", + "schema": "mysql/sql/schema/", + "queries": "mysql/sql/query/", + "engine": "mysql", + "emit_group_by_file": true, + "emit_empty_slices": true + }, + { + "name": "mygroupiface", + "path": "mysql/mygroupiface", + "schema": "mysql/sql/schema/", + "queries": "mysql/sql/query/", + "engine": "mysql", + "emit_group_by_file": true, + "emit_empty_slices": true, + "emit_interface": true + }, + { + "name": "mygroupstmt", + "path": "mysql/mygroupstmt", + "schema": "mysql/sql/schema/", + "queries": "mysql/sql/query/", + "engine": "mysql", + "emit_group_by_file": true, + "emit_empty_slices": true, + "emit_prepared_queries": true + }, + { + "name": "mygroupstmtiface", + "path": "mysql/mygroupstmtiface", + "schema": "mysql/sql/schema/", + "queries": "mysql/sql/query/", + "engine": "mysql", + "emit_group_by_file": true, + "emit_empty_slices": true, + "emit_prepared_queries": true, + "emit_interface": true + }, + { + "name": "pggroup", + "path": "postgresql/pggroup", + "schema": "postgresql/sql/schema/", + "queries": "postgresql/sql/query/", + "engine": "postgresql", + "emit_group_by_file": true, + "emit_empty_slices": true + }, + { + "name": "pggroupiface", + "path": "postgresql/pggroupiface", + "schema": "postgresql/sql/schema/", + "queries": "postgresql/sql/query/", + "engine": "postgresql", + "emit_group_by_file": true, + "emit_empty_slices": true, + "emit_interface": true + }, + { + "name": "pggroupstmt", + "path": "postgresql/pggroupstmt", + "schema": "postgresql/sql/schema/", + "queries": "postgresql/sql/query/", + "engine": "postgresql", + "emit_group_by_file": true, + "emit_empty_slices": true, + "emit_prepared_queries": true + }, + { + "name": "pggroupstmtiface", + "path": "postgresql/pggroupstmtiface", + "schema": "postgresql/sql/schema/", + "queries": "postgresql/sql/query/", + "engine": "postgresql", + "emit_group_by_file": true, + "emit_empty_slices": true, + "emit_prepared_queries": true, + "emit_interface": true + } + ] +} \ No newline at end of file diff --git a/internal/codegen/golang/gen.go b/internal/codegen/golang/gen.go index 138a9f41cd..a816c2b615 100644 --- a/internal/codegen/golang/gen.go +++ b/internal/codegen/golang/gen.go @@ -19,7 +19,7 @@ type Generateable interface { Enums(settings config.CombinedSettings) []Enum } -var templateSet = ` +const templateSet = ` {{define "dbFile"}}// Code generated by sqlc. DO NOT EDIT. package {{.Package}} @@ -360,6 +360,7 @@ type tmplCtx struct { // TODO: Race conditions SourceName string + StructName string EmitJSONTags bool EmitDBTags bool @@ -394,9 +395,18 @@ func generate(settings config.CombinedSettings, enums []Enum, structs []Struct, "imports": i.Imports, } - tmpl := template.Must(template.New("table").Funcs(funcMap).Parse(templateSet)) - golang := settings.Go + + tmpl := template.Must(template.New("table").Funcs(funcMap).Parse(func() string { + if golang.EmitGroupByFile { + if golang.EmitInterface { + return groupInterfaceTemplateSet + } + return groupTemplateSet + } + return templateSet + }())) + tctx := tmplCtx{ Settings: settings.Global, EmitInterface: golang.EmitInterface, @@ -413,10 +423,11 @@ func generate(settings config.CombinedSettings, enums []Enum, structs []Struct, output := map[string]string{} - execute := func(name, templateName string) error { + execute := func(sourceName, structName, templateName string) error { var b bytes.Buffer w := bufio.NewWriter(&b) - tctx.SourceName = name + tctx.SourceName = sourceName + tctx.StructName = structName err := tmpl.ExecuteTemplate(w, templateName, &tctx) w.Flush() if err != nil { @@ -427,32 +438,32 @@ func generate(settings config.CombinedSettings, enums []Enum, structs []Struct, fmt.Println(b.String()) return fmt.Errorf("source error: %w", err) } - if !strings.HasSuffix(name, ".go") { - name += ".go" + if !strings.HasSuffix(sourceName, ".go") { + sourceName += ".go" } - output[name] = string(code) + output[sourceName] = string(code) return nil } - if err := execute("db.go", "dbFile"); err != nil { + if err := execute("db.go", "Undeclared", "dbFile"); err != nil { return nil, err } - if err := execute("models.go", "modelsFile"); err != nil { + if err := execute("models.go", "Undeclared", "modelsFile"); err != nil { return nil, err } - if golang.EmitInterface { - if err := execute("querier.go", "interfaceFile"); err != nil { + if !golang.EmitGroupByFile && golang.EmitInterface { + if err := execute("querier.go", "Undeclared", "interfaceFile"); err != nil { return nil, err } } - files := map[string]struct{}{} + files := map[string]string{} for _, gq := range queries { - files[gq.SourceName] = struct{}{} + files[gq.SourceName] = gq.StructName } - for source := range files { - if err := execute(source, "queryFile"); err != nil { + for sourceName, structName := range files { + if err := execute(sourceName, structName, "queryFile"); err != nil { return nil, err } } diff --git a/internal/codegen/golang/imports.go b/internal/codegen/golang/imports.go index 63900ba763..99c7f2266b 100644 --- a/internal/codegen/golang/imports.go +++ b/internal/codegen/golang/imports.go @@ -105,7 +105,7 @@ func (i *importer) dbImports() fileImports { {Path: "context"}, {Path: "database/sql"}, } - if i.Settings.Go.EmitPreparedQueries { + if !i.Settings.Go.EmitGroupByFile && i.Settings.Go.EmitPreparedQueries { std = append(std, ImportSpec{Path: "fmt"}) } return fileImports{Std: std} @@ -339,12 +339,16 @@ func (i *importer) queryImports(filename string) fileImports { std := map[string]struct{}{ "context": struct{}{}, } - if uses("sql.Null") { + if i.Settings.Go.EmitGroupByFile && i.Settings.Go.EmitPreparedQueries { std["database/sql"] = struct{}{} - } - for _, q := range gq { - if q.Cmd == metadata.CmdExecResult { - std["database/sql"] = struct{}{} + } else if uses("sql.Null") { + std["database/sql"] = struct{}{} + } else { + for _, q := range gq { + if q.Cmd == metadata.CmdExecResult { + std["database/sql"] = struct{}{} + break + } } } if uses("json.RawMessage") { diff --git a/internal/codegen/golang/query.go b/internal/codegen/golang/query.go index afade092d4..7eb47c5440 100644 --- a/internal/codegen/golang/query.go +++ b/internal/codegen/golang/query.go @@ -102,6 +102,7 @@ type Query struct { ConstantName string SQL string SourceName string + StructName string Ret QueryValue Arg QueryValue } diff --git a/internal/codegen/golang/result.go b/internal/codegen/golang/result.go index 3c534b19f7..82209eb7e1 100644 --- a/internal/codegen/golang/result.go +++ b/internal/codegen/golang/result.go @@ -130,6 +130,18 @@ func argName(name string) string { } func buildQueries(r *compiler.Result, settings config.CombinedSettings, structs []Struct) []Query { + internalName := func(structName, variableName string) string { + if settings.Go.EmitGroupByFile { + return codegen.LowerTitle(structName) + variableName + } + return codegen.LowerTitle(variableName) + } + publicName := func(structName, variableName string) string { + if settings.Go.EmitGroupByFile { + return structName + variableName + } + return variableName + } qs := make([]Query, 0, len(r.Queries)) for _, query := range r.Queries { if query.Name == "" { @@ -139,12 +151,15 @@ func buildQueries(r *compiler.Result, settings config.CombinedSettings, structs continue } + structName := StructName(codegen.InitialComponent(query.Filename), settings) + gq := Query{ Cmd: query.Cmd, - ConstantName: codegen.LowerTitle(query.Name), + ConstantName: internalName(structName, query.Name), FieldName: codegen.LowerTitle(query.Name) + "Stmt", MethodName: query.Name, SourceName: query.Filename, + StructName: structName, SQL: query.SQL, Comments: query.Comments, } @@ -166,7 +181,7 @@ func buildQueries(r *compiler.Result, settings config.CombinedSettings, structs gq.Arg = QueryValue{ Emit: true, Name: "arg", - Struct: columnsToStruct(r, gq.MethodName+"Params", cols, settings), + Struct: columnsToStruct(r, publicName(structName, gq.MethodName+"Params"), cols, settings), } } @@ -208,7 +223,7 @@ func buildQueries(r *compiler.Result, settings config.CombinedSettings, structs Column: c, }) } - gs = columnsToStruct(r, gq.MethodName+"Row", columns, settings) + gs = columnsToStruct(r, publicName(structName, gq.MethodName+"Row"), columns, settings) emit = true } gq.Ret = QueryValue{ diff --git a/internal/codegen/golang/template_group.go b/internal/codegen/golang/template_group.go new file mode 100644 index 0000000000..b195649096 --- /dev/null +++ b/internal/codegen/golang/template_group.go @@ -0,0 +1,360 @@ +package golang + +const groupTemplateSet = ` +{{define "dbFile"}}// Code generated by sqlc. DO NOT EDIT. + +package {{.Package}} + +import ( + {{range imports .SourceName}} + {{range .}}{{.}} + {{end}} + {{end}} +) + +{{template "dbCode" . }} +{{end}} + +{{define "dbCode"}} +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} +{{end}} + +{{define "modelsFile"}}// Code generated by sqlc. DO NOT EDIT. + +package {{.Package}} + +import ( + {{range imports .SourceName}} + {{range .}}{{.}} + {{end}} + {{end}} +) + +{{template "modelsCode" . }} +{{end}} + +{{define "modelsCode"}} +{{range .Enums}} +{{if .Comment}}{{comment .Comment}}{{end}} +type {{.Name}} string + +const ( + {{- range .Constants}} + {{.Name}} {{.Type}} = "{{.Value}}" + {{- end}} +) + +func (e *{{.Name}}) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = {{.Name}}(s) + case string: + *e = {{.Name}}(s) + default: + return fmt.Errorf("unsupported scan type for {{.Name}}: %T", src) + } + return nil +} +{{end}} + +{{range .Structs}} +{{if .Comment}}{{comment .Comment}}{{end}} +type {{.Name}} struct { {{- range .Fields}} + {{- if .Comment}} + {{comment .Comment}}{{else}} + {{- end}} + {{.Name}} {{.Type}} {{if or ($.EmitJSONTags) ($.EmitDBTags)}}{{$.Q}}{{.Tag}}{{$.Q}}{{end}} + {{- end}} +} +{{end}} +{{end}} + +{{define "queryFile"}}// Code generated by sqlc. DO NOT EDIT. +// source: {{.SourceName}} + +package {{.Package}} + +import ( + {{range imports .SourceName}} + {{range .}}{{.}} + {{end}} + {{end}} +) + +type {{.StructName}} struct { + db DBTX +} + +func New{{.StructName}}(db DBTX) *{{.StructName}} { + return &{{.StructName}}{db: db} +} + +{{template "queryCode" . }} +{{end}} + +{{define "queryCode"}} +{{range .GoQueries}} +{{if $.OutputQuery .SourceName}} +const {{.ConstantName}} = {{$.Q}}-- name: {{.MethodName}} {{.Cmd}} +{{escape .SQL}} +{{$.Q}} + +{{if .Arg.EmitStruct}} +type {{.Arg.Type}} struct { {{- range .Arg.Struct.Fields}} + {{.Name}} {{.Type}} {{if or ($.EmitJSONTags) ($.EmitDBTags)}}{{$.Q}}{{.Tag}}{{$.Q}}{{end}} + {{- end}} +} +{{end}} + +{{if .Ret.EmitStruct}} +type {{.Ret.Type}} struct { {{- range .Ret.Struct.Fields}} + {{.Name}} {{.Type}} {{if or ($.EmitJSONTags) ($.EmitDBTags)}}{{$.Q}}{{.Tag}}{{$.Q}}{{end}} + {{- end}} +} +{{end}} + +{{if eq .Cmd ":one"}} +{{range .Comments}}//{{.}} +{{end -}} +func (q *{{.StructName}}) {{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) ({{.Ret.Type}}, error) { + row := q.db.QueryRowContext(ctx, {{.ConstantName}}, {{.Arg.Params}}) + var {{.Ret.Name}} {{.Ret.Type}} + err := row.Scan({{.Ret.Scan}}) + return {{.Ret.Name}}, err +} +{{- if $.EmitPreparedQueries}} + +type {{.StructName}}{{.MethodName}}Stmt struct { + stmt *sql.Stmt +} + +func (s *{{.StructName}}{{.MethodName}}Stmt) Close() error { + return s.stmt.Close() +} + +func (s *{{.StructName}}{{.MethodName}}Stmt) JoinTx(ctx context.Context, tx *sql.Tx) *{{.StructName}}{{.MethodName}}Stmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &{{.StructName}}{{.MethodName}}Stmt{stmt: stmt} +} + +func (s *{{.StructName}}{{.MethodName}}Stmt) Exec(ctx context.Context, {{.Arg.Pair}}) ({{.Ret.Type}}, error) { + row := s.stmt.QueryRowContext(ctx, {{.Arg.Params}}) + var {{.Ret.Name}} {{.Ret.Type}} + err := row.Scan({{.Ret.Scan}}) + return {{.Ret.Name}}, err +} + +func (q *{{.StructName}}) Prepare{{.MethodName}}(ctx context.Context) (*{{.StructName}}{{.MethodName}}Stmt, error) { + stmt, err := q.db.PrepareContext(ctx, {{.ConstantName}}) + if err != nil { + return nil, err + } + return &{{.StructName}}{{.MethodName}}Stmt{stmt: stmt}, nil +} +{{- end}} +{{end}} + +{{if eq .Cmd ":many"}} +{{range .Comments}}//{{.}} +{{end -}} +func (q *{{.StructName}}) {{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) ([]{{.Ret.Type}}, error) { + rows, err := q.db.QueryContext(ctx, {{.ConstantName}}, {{.Arg.Params}}) + if err != nil { + return nil, err + } + defer rows.Close() + {{- if $.EmitEmptySlices}} + items := []{{.Ret.Type}}{} + {{else}} + var items []{{.Ret.Type}} + {{end -}} + for rows.Next() { + var {{.Ret.Name}} {{.Ret.Type}} + if err := rows.Scan({{.Ret.Scan}}); err != nil { + return nil, err + } + items = append(items, {{.Ret.Name}}) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} +{{- if $.EmitPreparedQueries}} + +type {{.StructName}}{{.MethodName}}Stmt struct { + stmt *sql.Stmt +} + +func (s *{{.StructName}}{{.MethodName}}Stmt) Close() error { + return s.stmt.Close() +} + +func (s *{{.StructName}}{{.MethodName}}Stmt) JoinTx(ctx context.Context, tx *sql.Tx) *{{.StructName}}{{.MethodName}}Stmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &{{.StructName}}{{.MethodName}}Stmt{stmt: stmt} +} + +func (s *{{.StructName}}{{.MethodName}}Stmt) Exec(ctx context.Context, {{.Arg.Pair}}) ([]{{.Ret.Type}}, error) { + rows, err := s.stmt.QueryContext(ctx, {{.Arg.Params}}) + if err != nil { + return nil, err + } + defer rows.Close() + {{- if $.EmitEmptySlices}} + items := []{{.Ret.Type}}{} + {{else}} + var items []{{.Ret.Type}} + {{end -}} + for rows.Next() { + var {{.Ret.Name}} {{.Ret.Type}} + if err := rows.Scan({{.Ret.Scan}}); err != nil { + return nil, err + } + items = append(items, {{.Ret.Name}}) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +func (q *{{.StructName}}) Prepare{{.MethodName}}(ctx context.Context) (*{{.StructName}}{{.MethodName}}Stmt, error) { + stmt, err := q.db.PrepareContext(ctx, {{.ConstantName}}) + if err != nil { + return nil, err + } + return &{{.StructName}}{{.MethodName}}Stmt{stmt: stmt}, nil +} +{{- end}} +{{end}} + +{{if eq .Cmd ":exec"}} +{{range .Comments}}//{{.}} +{{end -}} +func (q *{{.StructName}}) {{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) error { + _, err := q.db.ExecContext(ctx, {{.ConstantName}}, {{.Arg.Params}}) + return err +} +{{- if $.EmitPreparedQueries}} + +type {{.StructName}}{{.MethodName}}Stmt struct { + stmt *sql.Stmt +} + +func (s *{{.StructName}}{{.MethodName}}Stmt) Close() error { + return s.stmt.Close() +} + +func (s *{{.StructName}}{{.MethodName}}Stmt) JoinTx(ctx context.Context, tx *sql.Tx) *{{.StructName}}{{.MethodName}}Stmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &{{.StructName}}{{.MethodName}}Stmt{stmt: stmt} +} + +func (s *{{.StructName}}{{.MethodName}}Stmt) Exec(ctx context.Context, {{.Arg.Pair}}) error { + _, err := s.stmt.ExecContext(ctx, {{.Arg.Params}}) + return err +} + +func (q *{{.StructName}}) Prepare{{.MethodName}}(ctx context.Context) (*{{.StructName}}{{.MethodName}}Stmt, error) { + stmt, err := q.db.PrepareContext(ctx, {{.ConstantName}}) + if err != nil { + return nil, err + } + return &{{.StructName}}{{.MethodName}}Stmt{stmt: stmt}, nil +} +{{- end}} +{{end}} + +{{if eq .Cmd ":execrows"}} +{{range .Comments}}//{{.}} +{{end -}} +func (q *{{.StructName}}) {{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) (int64, error) { + result, err := q.db.ExecContext(ctx, {{.ConstantName}}, {{.Arg.Params}}) + if err != nil { + return 0, err + } + return result.RowsAffected() +} +{{- if $.EmitPreparedQueries}} + +type {{.StructName}}{{.MethodName}}Stmt struct { + stmt *sql.Stmt +} + +func (s *{{.StructName}}{{.MethodName}}Stmt) Close() error { + return s.stmt.Close() +} + +func (s *{{.StructName}}{{.MethodName}}Stmt) JoinTx(ctx context.Context, tx *sql.Tx) *{{.StructName}}{{.MethodName}}Stmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &{{.StructName}}{{.MethodName}}Stmt{stmt: stmt} +} + +func (s *{{.StructName}}{{.MethodName}}Stmt) Exec(ctx context.Context, {{.Arg.Pair}}) (int64, error) { + result, err := s.stmt.ExecContext(ctx, {{.Arg.Params}}) + if err != nil { + return 0, err + } + return result.RowsAffected() +} + +func (q *{{.StructName}}) Prepare{{.MethodName}}(ctx context.Context) (*{{.StructName}}{{.MethodName}}Stmt, error) { + stmt, err := q.db.PrepareContext(ctx, {{.ConstantName}}) + if err != nil { + return nil, err + } + return &{{.StructName}}{{.MethodName}}Stmt{stmt: stmt}, nil +} +{{- end}} +{{end}} + +{{if eq .Cmd ":execresult"}} +{{range .Comments}}//{{.}} +{{end -}} +func (q *{{.StructName}}) {{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) (sql.Result, error) { + return q.db.ExecContext(ctx, {{.ConstantName}}, {{.Arg.Params}}) +} +{{- if $.EmitPreparedQueries}} + +type {{.StructName}}{{.MethodName}}Stmt struct { + stmt *sql.Stmt +} + +func (s *{{.StructName}}{{.MethodName}}Stmt) Close() error { + return s.stmt.Close() +} + +func (s *{{.StructName}}{{.MethodName}}Stmt) JoinTx(ctx context.Context, tx *sql.Tx) *{{.StructName}}{{.MethodName}}Stmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &{{.StructName}}{{.MethodName}}Stmt{stmt: stmt} +} + +func (s *{{.StructName}}{{.MethodName}}Stmt) Exec(ctx context.Context, {{.Arg.Pair}}) (sql.Result, error) { + return s.stmt.ExecContext(ctx, {{.Arg.Params}}) +} + +func (q *{{.StructName}}) Prepare{{.MethodName}}(ctx context.Context) (*{{.StructName}}{{.MethodName}}Stmt, error) { + stmt, err := q.db.PrepareContext(ctx, {{.ConstantName}}) + if err != nil { + return nil, err + } + return &{{.StructName}}{{.MethodName}}Stmt{stmt: stmt}, nil +} +{{- end}} +{{end}} +{{end}} +{{end}} +{{end}} +` diff --git a/internal/codegen/golang/template_group_interface.go b/internal/codegen/golang/template_group_interface.go new file mode 100644 index 0000000000..36785361ba --- /dev/null +++ b/internal/codegen/golang/template_group_interface.go @@ -0,0 +1,427 @@ +package golang + +const groupInterfaceTemplateSet = ` +{{define "dbFile"}}// Code generated by sqlc. DO NOT EDIT. + +package {{.Package}} + +import ( + {{range imports .SourceName}} + {{range .}}{{.}} + {{end}} + {{end}} +) + +{{template "dbCode" . }} +{{end}} + +{{define "dbCode"}} +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} +{{end}} + +{{define "modelsFile"}}// Code generated by sqlc. DO NOT EDIT. + +package {{.Package}} + +import ( + {{range imports .SourceName}} + {{range .}}{{.}} + {{end}} + {{end}} +) + +{{template "modelsCode" . }} +{{end}} + +{{define "modelsCode"}} +{{range .Enums}} +{{if .Comment}}{{comment .Comment}}{{end}} +type {{.Name}} string + +const ( + {{- range .Constants}} + {{.Name}} {{.Type}} = "{{.Value}}" + {{- end}} +) + +func (e *{{.Name}}) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = {{.Name}}(s) + case string: + *e = {{.Name}}(s) + default: + return fmt.Errorf("unsupported scan type for {{.Name}}: %T", src) + } + return nil +} +{{end}} + +{{range .Structs}} +{{if .Comment}}{{comment .Comment}}{{end}} +type {{.Name}} struct { {{- range .Fields}} + {{- if .Comment}} + {{comment .Comment}}{{else}} + {{- end}} + {{.Name}} {{.Type}} {{if or ($.EmitJSONTags) ($.EmitDBTags)}}{{$.Q}}{{.Tag}}{{$.Q}}{{end}} + {{- end}} +} +{{end}} +{{end}} + +{{define "queryFile"}}// Code generated by sqlc. DO NOT EDIT. +// source: {{.SourceName}} + +package {{.Package}} + +import ( + {{range imports .SourceName}} + {{range .}}{{.}} + {{end}} + {{end}} +) + +type {{.StructName}} interface { +{{range .GoQueries}} +{{- if $.OutputQuery .SourceName}} +{{- if eq .Cmd ":one"}} + {{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) ({{.Ret.Type}}, error) +{{- if $.EmitPreparedQueries}} + Prepare{{.MethodName}}(ctx context.Context) ({{.StructName}}{{.MethodName}}Stmt, error) +{{- end}} +{{- end}} +{{- if eq .Cmd ":many"}} + {{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) ([]{{.Ret.Type}}, error) +{{- if $.EmitPreparedQueries}} + Prepare{{.MethodName}}(ctx context.Context) ({{.StructName}}{{.MethodName}}Stmt, error) +{{- end}} +{{- end}} +{{- if eq .Cmd ":exec"}} + {{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) error +{{- if $.EmitPreparedQueries}} + Prepare{{.MethodName}}(ctx context.Context) ({{.StructName}}{{.MethodName}}Stmt, error) +{{- end}} +{{- end}} +{{- if eq .Cmd ":execrows"}} + {{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) (int64, error) +{{- if $.EmitPreparedQueries}} + Prepare{{.MethodName}}(ctx context.Context) ({{.StructName}}{{.MethodName}}Stmt, error) +{{- end}} +{{- end}} +{{- if eq .Cmd ":execresult"}} + {{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) (sql.Result, error) +{{- if $.EmitPreparedQueries}} + Prepare{{.MethodName}}(ctx context.Context) ({{.StructName}}{{.MethodName}}Stmt, error) +{{- end}} +{{- end}} +{{- end}} +{{- end}} +} + +func New{{.StructName}}(db DBTX) {{.StructName}} { + return &{{lowerTitle .StructName}}{db: db} +} + +type {{lowerTitle .StructName}} struct { + db DBTX +} + +{{template "queryCode" . }} +{{end}} + +{{define "queryCode"}} +{{range .GoQueries}} +{{if $.OutputQuery .SourceName}} +const {{.ConstantName}} = {{$.Q}}-- name: {{.MethodName}} {{.Cmd}} +{{escape .SQL}} +{{$.Q}} + +{{if .Arg.EmitStruct}} +type {{.Arg.Type}} struct { {{- range .Arg.Struct.Fields}} + {{.Name}} {{.Type}} {{if or ($.EmitJSONTags) ($.EmitDBTags)}}{{$.Q}}{{.Tag}}{{$.Q}}{{end}} + {{- end}} +} +{{end}} + +{{if .Ret.EmitStruct}} +type {{.Ret.Type}} struct { {{- range .Ret.Struct.Fields}} + {{.Name}} {{.Type}} {{if or ($.EmitJSONTags) ($.EmitDBTags)}}{{$.Q}}{{.Tag}}{{$.Q}}{{end}} + {{- end}} +} +{{end}} + +{{if eq .Cmd ":one"}} +{{range .Comments}}//{{.}} +{{end -}} +func (q *{{lowerTitle .StructName}}) {{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) ({{.Ret.Type}}, error) { + row := q.db.QueryRowContext(ctx, {{.ConstantName}}, {{.Arg.Params}}) + var {{.Ret.Name}} {{.Ret.Type}} + err := row.Scan({{.Ret.Scan}}) + return {{.Ret.Name}}, err +} +{{- if $.EmitPreparedQueries}} + +type {{.StructName}}{{.MethodName}}Stmt interface { + Close() error + JoinTx(ctx context.Context, tx *sql.Tx) {{.StructName}}{{.MethodName}}Stmt + Exec(ctx context.Context, {{.Arg.Pair}}) ({{.Ret.Type}}, error) +} + +type {{lowerTitle .StructName}}{{.MethodName}}Stmt struct { + stmt *sql.Stmt +} + +func (s *{{lowerTitle .StructName}}{{.MethodName}}Stmt) Close() error { + return s.stmt.Close() +} + +func (s *{{lowerTitle .StructName}}{{.MethodName}}Stmt) JoinTx(ctx context.Context, tx *sql.Tx) {{.StructName}}{{.MethodName}}Stmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &{{lowerTitle .StructName}}{{.MethodName}}Stmt{stmt: stmt} +} + +func (s *{{lowerTitle .StructName}}{{.MethodName}}Stmt) Exec(ctx context.Context, {{.Arg.Pair}}) ({{.Ret.Type}}, error) { + row := s.stmt.QueryRowContext(ctx, {{.Arg.Params}}) + var {{.Ret.Name}} {{.Ret.Type}} + err := row.Scan({{.Ret.Scan}}) + return {{.Ret.Name}}, err +} + +func (q *{{lowerTitle .StructName}}) Prepare{{.MethodName}}(ctx context.Context) ({{.StructName}}{{.MethodName}}Stmt, error) { + stmt, err := q.db.PrepareContext(ctx, {{.ConstantName}}) + if err != nil { + return nil, err + } + return &{{lowerTitle .StructName}}{{.MethodName}}Stmt{stmt: stmt}, nil +} +{{- end}} +{{end}} + +{{if eq .Cmd ":many"}} +{{range .Comments}}//{{.}} +{{end -}} +func (q *{{lowerTitle .StructName}}) {{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) ([]{{.Ret.Type}}, error) { + rows, err := q.db.QueryContext(ctx, {{.ConstantName}}, {{.Arg.Params}}) + if err != nil { + return nil, err + } + defer rows.Close() + {{- if $.EmitEmptySlices}} + items := []{{.Ret.Type}}{} + {{else}} + var items []{{.Ret.Type}} + {{end -}} + for rows.Next() { + var {{.Ret.Name}} {{.Ret.Type}} + if err := rows.Scan({{.Ret.Scan}}); err != nil { + return nil, err + } + items = append(items, {{.Ret.Name}}) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} +{{- if $.EmitPreparedQueries}} + +type {{.StructName}}{{.MethodName}}Stmt interface { + Close() error + JoinTx(ctx context.Context, tx *sql.Tx) {{.StructName}}{{.MethodName}}Stmt + Exec(ctx context.Context, {{.Arg.Pair}}) ([]{{.Ret.Type}}, error) +} + +type {{lowerTitle .StructName}}{{.MethodName}}Stmt struct { + stmt *sql.Stmt +} + +func (s *{{lowerTitle .StructName}}{{.MethodName}}Stmt) Close() error { + return s.stmt.Close() +} + +func (s *{{lowerTitle .StructName}}{{.MethodName}}Stmt) JoinTx(ctx context.Context, tx *sql.Tx) {{.StructName}}{{.MethodName}}Stmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &{{lowerTitle .StructName}}{{.MethodName}}Stmt{stmt: stmt} +} + +func (s *{{lowerTitle .StructName}}{{.MethodName}}Stmt) Exec(ctx context.Context, {{.Arg.Pair}}) ([]{{.Ret.Type}}, error) { + rows, err := s.stmt.QueryContext(ctx, {{.Arg.Params}}) + if err != nil { + return nil, err + } + defer rows.Close() + {{- if $.EmitEmptySlices}} + items := []{{.Ret.Type}}{} + {{else}} + var items []{{.Ret.Type}} + {{end -}} + for rows.Next() { + var {{.Ret.Name}} {{.Ret.Type}} + if err := rows.Scan({{.Ret.Scan}}); err != nil { + return nil, err + } + items = append(items, {{.Ret.Name}}) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +func (q *{{lowerTitle .StructName}}) Prepare{{.MethodName}}(ctx context.Context) ({{.StructName}}{{.MethodName}}Stmt, error) { + stmt, err := q.db.PrepareContext(ctx, {{.ConstantName}}) + if err != nil { + return nil, err + } + return &{{lowerTitle .StructName}}{{.MethodName}}Stmt{stmt: stmt}, nil +} +{{- end}} +{{end}} + +{{if eq .Cmd ":exec"}} +{{range .Comments}}//{{.}} +{{end -}} +func (q *{{lowerTitle .StructName}}) {{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) error { + _, err := q.db.ExecContext(ctx, {{.ConstantName}}, {{.Arg.Params}}) + return err +} +{{- if $.EmitPreparedQueries}} + +type {{.StructName}}{{.MethodName}}Stmt interface { + Close() error + JoinTx(ctx context.Context, tx *sql.Tx) {{.StructName}}{{.MethodName}}Stmt + Exec(ctx context.Context, {{.Arg.Pair}}) error +} + +type {{lowerTitle .StructName}}{{.MethodName}}Stmt struct { + stmt *sql.Stmt +} + +func (s *{{lowerTitle .StructName}}{{.MethodName}}Stmt) Close() error { + return s.stmt.Close() +} + +func (s *{{lowerTitle .StructName}}{{.MethodName}}Stmt) JoinTx(ctx context.Context, tx *sql.Tx) {{.StructName}}{{.MethodName}}Stmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &{{lowerTitle .StructName}}{{.MethodName}}Stmt{stmt: stmt} +} + +func (s *{{lowerTitle .StructName}}{{.MethodName}}Stmt) Exec(ctx context.Context, {{.Arg.Pair}}) error { + _, err := s.stmt.ExecContext(ctx, {{.Arg.Params}}) + return err +} + +func (q *{{lowerTitle .StructName}}) Prepare{{.MethodName}}(ctx context.Context) ({{.StructName}}{{.MethodName}}Stmt, error) { + stmt, err := q.db.PrepareContext(ctx, {{.ConstantName}}) + if err != nil { + return nil, err + } + return &{{lowerTitle .StructName}}{{.MethodName}}Stmt{stmt: stmt}, nil +} +{{- end}} +{{end}} + +{{if eq .Cmd ":execrows"}} +{{range .Comments}}//{{.}} +{{end -}} +func (q *{{lowerTitle .StructName}}) {{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) (int64, error) { + result, err := q.db.ExecContext(ctx, {{.ConstantName}}, {{.Arg.Params}}) + if err != nil { + return 0, err + } + return result.RowsAffected() +} +{{- if $.EmitPreparedQueries}} + +type {{.StructName}}{{.MethodName}}Stmt interface { + Close() error + JoinTx(ctx context.Context, tx *sql.Tx) {{.StructName}}{{.MethodName}}Stmt + Exec(ctx context.Context, {{.Arg.Pair}}) (int64, error) +} + +type {{lowerTitle .StructName}}{{.MethodName}}Stmt struct { + stmt *sql.Stmt +} + +func (s *{{lowerTitle .StructName}}{{.MethodName}}Stmt) Close() error { + return s.stmt.Close() +} + +func (s *{{lowerTitle .StructName}}{{.MethodName}}Stmt) JoinTx(ctx context.Context, tx *sql.Tx) {{.StructName}}{{.MethodName}}Stmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &{{lowerTitle .StructName}}{{.MethodName}}Stmt{stmt: stmt} +} + +func (s *{{lowerTitle .StructName}}{{.MethodName}}Stmt) Exec(ctx context.Context, {{.Arg.Pair}}) (int64, error) { + result, err := s.stmt.ExecContext(ctx, {{.Arg.Params}}) + if err != nil { + return 0, err + } + return result.RowsAffected() +} + +func (q *{{lowerTitle .StructName}}) Prepare{{.MethodName}}(ctx context.Context) ({{.StructName}}{{.MethodName}}Stmt, error) { + stmt, err := q.db.PrepareContext(ctx, {{.ConstantName}}) + if err != nil { + return nil, err + } + return &{{lowerTitle .StructName}}{{.MethodName}}Stmt{stmt: stmt}, nil +} +{{- end}} +{{end}} + +{{if eq .Cmd ":execresult"}} +{{range .Comments}}//{{.}} +{{end -}} +func (q *{{lowerTitle .StructName}}) {{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) (sql.Result, error) { + return q.db.ExecContext(ctx, {{.ConstantName}}, {{.Arg.Params}}) +} +{{- if $.EmitPreparedQueries}} + +type {{.StructName}}{{.MethodName}}Stmt interface { + Close() error + JoinTx(ctx context.Context, tx *sql.Tx) {{.StructName}}{{.MethodName}}Stmt + Exec(ctx context.Context, {{.Arg.Pair}}) (sql.Result, error) +} + +type {{lowerTitle .StructName}}{{.MethodName}}Stmt struct { + stmt *sql.Stmt +} + +func (s *{{lowerTitle .StructName}}{{.MethodName}}Stmt) Close() error { + return s.stmt.Close() +} + +func (s *{{lowerTitle .StructName}}{{.MethodName}}Stmt) JoinTx(ctx context.Context, tx *sql.Tx) {{.StructName}}{{.MethodName}}Stmt { + stmt := tx.StmtContext(ctx, s.stmt) + return &{{lowerTitle .StructName}}{{.MethodName}}Stmt{stmt: stmt} +} + +func (s *{{lowerTitle .StructName}}{{.MethodName}}Stmt) Exec(ctx context.Context, {{.Arg.Pair}}) (sql.Result, error) { + return s.stmt.ExecContext(ctx, {{.Arg.Params}}) +} + +func (q *{{lowerTitle .StructName}}) Prepare{{.MethodName}}(ctx context.Context) ({{.StructName}}{{.MethodName}}Stmt, error) { + stmt, err := q.db.PrepareContext(ctx, {{.ConstantName}}) + if err != nil { + return nil, err + } + return &{{lowerTitle .StructName}}{{.MethodName}}Stmt{stmt: stmt}, nil +} +{{- end}} +{{end}} +{{end}} +{{end}} +{{end}} +` diff --git a/internal/codegen/utils.go b/internal/codegen/utils.go index 8e21b9bdf5..9b338c3430 100644 --- a/internal/codegen/utils.go +++ b/internal/codegen/utils.go @@ -11,6 +11,14 @@ func LowerTitle(s string) string { return string(a) } +func InitialComponent(s string) string { + index := strings.Index(s, ".") + if index < 0 { + return s + } + return s[:index] +} + // Go string literals cannot contain backtick. If a string contains // a backtick, replace it the following way: // diff --git a/internal/compiler/compile.go b/internal/compiler/compile.go index 171bc9a6c1..7d3c3f1458 100644 --- a/internal/compiler/compile.go +++ b/internal/compiler/compile.go @@ -131,6 +131,9 @@ func (c *Compiler) parseQueries(o opts.Parser) (*Result, error) { q = append(q, query) } } + if c.conf.Gen.Go != nil && c.conf.Gen.Go.EmitGroupByFile { + set = map[string]struct{}{} + } } if len(merr.Errs()) > 0 { return nil, merr diff --git a/internal/config/config.go b/internal/config/config.go index e4eeff91ca..1c12803880 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -115,6 +115,7 @@ type SQLGo struct { EmitPreparedQueries bool `json:"emit_prepared_queries" yaml:"emit_prepared_queries"` EmitExactTableNames bool `json:"emit_exact_table_names,omitempty" yaml:"emit_exact_table_names"` EmitEmptySlices bool `json:"emit_empty_slices,omitempty" yaml:"emit_empty_slices"` + EmitGroupByFile bool `json:"emit_group_by_file,omitempty" yaml:"emit_group_by_file"` Package string `json:"package" yaml:"package"` Out string `json:"out" yaml:"out"` Overrides []Override `json:"overrides,omitempty" yaml:"overrides"` diff --git a/internal/config/v_one.go b/internal/config/v_one.go index 55d6b3ae32..d5fdfee569 100644 --- a/internal/config/v_one.go +++ b/internal/config/v_one.go @@ -27,6 +27,7 @@ type v1PackageSettings struct { EmitPreparedQueries bool `json:"emit_prepared_queries" yaml:"emit_prepared_queries"` EmitExactTableNames bool `json:"emit_exact_table_names,omitempty" yaml:"emit_exact_table_names"` EmitEmptySlices bool `json:"emit_empty_slices,omitempty" yaml:"emit_empty_slices"` + EmitGroupByFile bool `json:"emit_group_by_file,omitempty" yaml:"emit_group_by_file"` Overrides []Override `json:"overrides" yaml:"overrides"` } @@ -109,6 +110,7 @@ func (c *V1GenerateSettings) Translate() Config { EmitPreparedQueries: pkg.EmitPreparedQueries, EmitExactTableNames: pkg.EmitExactTableNames, EmitEmptySlices: pkg.EmitEmptySlices, + EmitGroupByFile: pkg.EmitGroupByFile, Package: pkg.Name, Out: pkg.Path, Overrides: pkg.Overrides,