diff --git a/docs/tutorials/getting-started-ydb.md b/docs/tutorials/getting-started-ydb.md new file mode 100644 index 0000000000..a8fdf85377 --- /dev/null +++ b/docs/tutorials/getting-started-ydb.md @@ -0,0 +1,234 @@ +# Getting started with YDB + +This tutorial assumes that the latest version of sqlc is +[installed](../overview/install.md) and ready to use. + +We'll generate Go code here, but other +[language plugins](../reference/language-support.rst) are available. You'll +naturally need the Go toolchain if you want to build and run a program with the +code sqlc generates, but sqlc itself has no dependencies. + +At the end, you'll push your SQL queries to [sqlc +Cloud](https://dashboard.sqlc.dev/) for further insights and analysis. + +## Setting up + +Create a new directory called `sqlc-tutorial` and open it up. + +Initialize a new Go module named `tutorial.sqlc.dev/app`: + +```shell +go mod init tutorial.sqlc.dev/app +``` + +sqlc looks for either a `sqlc.(yaml|yml)` or `sqlc.json` file in the current +directory. In our new directory, create a file named `sqlc.yaml` with the +following contents: + +```yaml +version: "2" +sql: + - engine: "ydb" + queries: "query.sql" + schema: "schema.sql" + gen: + go: + package: "tutorial" + out: "tutorial" +``` + +## Schema and queries + +sqlc needs to know your database schema and queries in order to generate code. +In the same directory, create a file named `schema.sql` with the following +content: + +```sql +CREATE TABLE authors ( + id Serial, + name Text NOT NULL, + bio Text, + PRIMARY KEY (id) +); +``` + +Next, create a `query.sql` file with the following five queries: + +```sql +-- name: GetAuthor :one +SELECT * FROM authors +WHERE id = $id LIMIT 1; + +-- name: ListAuthors :many +SELECT * FROM authors +ORDER BY name; + +-- name: CreateOrUpdateAuthor :one +UPSERT INTO authors (name, bio) +VALUES ( + $name, $bio +) +RETURNING *; + +-- name: DeleteAuthor :exec +DELETE FROM authors WHERE id = $id; + +-- name: DropTable :exec +DROP TABLE IF EXISTS authors; +``` + +Note that YDB uses named parameters (`$id`, `$name`, `$bio`) rather than +positional parameters. + +## Generating code + +You are now ready to generate code. You shouldn't see any output when you run +the `generate` subcommand, unless something goes wrong: + +```shell +sqlc generate +``` + +You should now have a `tutorial` subdirectory with three files containing Go +source code. These files comprise a Go package named `tutorial`: + +``` +├── go.mod +├── query.sql +├── schema.sql +├── sqlc.yaml +└── tutorial + ├── db.go + ├── models.go + └── query.sql.go +``` + +## Using generated code + +You can use your newly-generated `tutorial` package from any Go program. +Create a file named `tutorial.go` and add the following contents: + +```go +package main + +import ( + "context" + "log" + + "github.com/ydb-platform/ydb-go-sdk/v3" + "github.com/ydb-platform/ydb-go-sdk/v3/query" + + "tutorial.sqlc.dev/app/tutorial" +) + +func ptr(s string) *string { + return &s +} + +func run() error { + ctx := context.Background() + + // Create YDB connection + // Replace with your actual YDB endpoint + db, err := ydb.Open(ctx, "grpcs://localhost:2136/local") + if err != nil { + return err + } + defer db.Close(ctx) + + queries := tutorial.New(db.Query()) + + // list all authors + authors, err := queries.ListAuthors(ctx) + if err != nil { + return err + } + log.Println(authors) + + // create an author + insertedAuthor, err := queries.CreateOrUpdateAuthor(ctx, tutorial.CreateOrUpdateAuthorParams{ + Name: "Brian Kernighan", + Bio: ptr("Co-author of The C Programming Language and The Go Programming Language"), + }, query.WithIdempotent()) + if err != nil { + return err + } + log.Println(insertedAuthor) + + // get the author we just inserted + fetchedAuthor, err := queries.GetAuthor(ctx, insertedAuthor.ID) + if err != nil { + return err + } + log.Println(fetchedAuthor) + return nil +} + +func main() { + if err := run(); err != nil { + log.Fatal(err) + } +} +``` + +Before this code will compile you'll need to fetch the relevant YDB driver: + +```shell +go get github.com/ydb-platform/ydb-go-sdk/v3 +go build ./... +``` + +The program should compile without errors. To make that possible, sqlc generates +readable, **idiomatic** Go code that you otherwise would've had to write +yourself. Take a look in `tutorial/query.sql.go`. + +Of course for this program to run successfully you'll need +to compile after replacing the database connection parameters in the call to +`ydb.Open()` with the correct parameters for your database. And your +database must have the `authors` table as defined in `schema.sql`. + +You should now have a working program using sqlc's generated Go source code, +and hopefully can see how you'd use sqlc in your own real-world applications. + +## Query verification (Not supported for YDB yet) + +[sqlc Cloud](https://dashboard.sqlc.dev) provides additional verification, catching subtle bugs. To get started, create a +[dashboard account](https://dashboard.sqlc.dev). Once you've signed in, create a +project and generate an auth token. Add your project's ID to the `cloud` block +to your sqlc.yaml. + +```yaml +version: "2" +cloud: + # Replace with your project ID from the sqlc Cloud dashboard + project: "" +sql: + - engine: "ydb" + queries: "query.sql" + schema: "schema.sql" + gen: + go: + package: "tutorial" + out: "tutorial" +``` + +Replace `` with your project ID from the sqlc Cloud dashboard. It +will look something like `01HA8SZH31HKYE9RR3N3N3TSJM`. + +And finally, set the `SQLC_AUTH_TOKEN` environment variable: + +```shell +export SQLC_AUTH_TOKEN="" +``` + +```shell +$ sqlc push --tag tutorial +``` + +In the sidebar, go to the "Queries" section to see your published queries. Run +`verify` to ensure that previously published queries continue to work against +updated database schema. + +```shell +$ sqlc verify --against tutorial +``` diff --git a/examples/authors/ydb/db_test.go b/examples/authors/ydb/db_test.go index ab5324e76d..88ae04caec 100644 --- a/examples/authors/ydb/db_test.go +++ b/examples/authors/ydb/db_test.go @@ -1,3 +1,4 @@ + package authors import ( @@ -15,86 +16,38 @@ func ptr(s string) *string { func TestAuthors(t *testing.T) { ctx := context.Background() - db := local.YDB(t, []string{"schema.sql"}) defer db.Close(ctx) q := New(db.Query()) - t.Run("InsertAuthors", func(t *testing.T) { - authorsToInsert := []CreateOrUpdateAuthorParams{ - {P0: 1, P1: "Leo Tolstoy", P2: ptr("Russian writer, author of \"War and Peace\"")}, - {P0: 2, P1: "Alexander Pushkin", P2: ptr("Author of \"Eugene Onegin\"")}, - {P0: 3, P1: "Alexander Pushkin", P2: ptr("Russian poet, playwright, and prose writer")}, - {P0: 4, P1: "Fyodor Dostoevsky", P2: ptr("Author of \"Crime and Punishment\"")}, - {P0: 5, P1: "Nikolai Gogol", P2: ptr("Author of \"Dead Souls\"")}, - {P0: 6, P1: "Anton Chekhov", P2: nil}, - {P0: 7, P1: "Ivan Turgenev", P2: ptr("Author of \"Fathers and Sons\"")}, - {P0: 8, P1: "Mikhail Lermontov", P2: nil}, - {P0: 9, P1: "Daniil Kharms", P2: ptr("Absurdist, writer and poet")}, - {P0: 10, P1: "Maxim Gorky", P2: ptr("Author of \"At the Bottom\"")}, - {P0: 11, P1: "Vladimir Mayakovsky", P2: nil}, - {P0: 12, P1: "Sergei Yesenin", P2: ptr("Russian lyric poet")}, - {P0: 13, P1: "Boris Pasternak", P2: ptr("Author of \"Doctor Zhivago\"")}, - } - - for _, author := range authorsToInsert { - if err := q.CreateOrUpdateAuthor(ctx, author, query.WithIdempotent()); err != nil { - t.Fatalf("failed to insert author %q: %v", author.P1, err) - } - } - }) - - t.Run("ListAuthors", func(t *testing.T) { - authors, err := q.ListAuthors(ctx) - if err != nil { - t.Fatal(err) - } - if len(authors) == 0 { - t.Fatal("expected at least one author, got none") - } - t.Log("Authors:") - for _, a := range authors { - bio := "Null" - if a.Bio != nil { - bio = *a.Bio - } - t.Logf("- ID: %d | Name: %s | Bio: %s", a.ID, a.Name, bio) - } - }) - - t.Run("GetAuthor", func(t *testing.T) { - singleAuthor, err := q.GetAuthor(ctx, 10) - if err != nil { - t.Fatal(err) - } - bio := "Null" - if singleAuthor.Bio != nil { - bio = *singleAuthor.Bio - } - t.Logf("- ID: %d | Name: %s | Bio: %s", singleAuthor.ID, singleAuthor.Name, bio) - }) - - t.Run("Delete All Authors", func(t *testing.T) { - var i uint64 - for i = 1; i <= 13; i++ { - if err := q.DeleteAuthor(ctx, i, query.WithIdempotent()); err != nil { - t.Fatalf("failed to delete author: %v", err) - } - } - authors, err := q.ListAuthors(ctx) - if err != nil { - t.Fatal(err) - } - if len(authors) != 0 { - t.Fatalf("expected no authors, got %d", len(authors)) - } - }) - - t.Run("Drop Table Authors", func(t *testing.T) { - err := q.DropTable(ctx) - if err != nil { - t.Fatal(err) - } - }) + // list all authors + authors, err := q.ListAuthors(ctx) + if err != nil { + t.Fatal(err) + } + t.Log(authors) + + // create an author + insertedAuthor, err := q.CreateOrUpdateAuthor(ctx, CreateOrUpdateAuthorParams{ + Name: "Brian Kernighan", + Bio: ptr("Co-author of The C Programming Language and The Go Programming Language"), + }, query.WithIdempotent()) + if err != nil { + t.Fatal(err) + } + t.Log(insertedAuthor) + + // get the author we just inserted + fetchedAuthor, err := q.GetAuthor(ctx, insertedAuthor.ID) + if err != nil { + t.Fatal(err) + } + t.Log(fetchedAuthor) + + // drop table + err = q.DropTable(ctx) + if err != nil { + t.Fatal(err) + } } diff --git a/examples/authors/ydb/models.go b/examples/authors/ydb/models.go index 2806beacfe..a0c2e18920 100644 --- a/examples/authors/ydb/models.go +++ b/examples/authors/ydb/models.go @@ -5,7 +5,7 @@ package authors type Author struct { - ID uint64 `json:"id"` + ID int32 `json:"id"` Name string `json:"name"` Bio *string `json:"bio"` } diff --git a/examples/authors/ydb/query.sql b/examples/authors/ydb/query.sql index 804150615d..34ee633a17 100644 --- a/examples/authors/ydb/query.sql +++ b/examples/authors/ydb/query.sql @@ -1,15 +1,20 @@ -- name: GetAuthor :one SELECT * FROM authors -WHERE id = $p0 LIMIT 1; +WHERE id = $id LIMIT 1; -- name: ListAuthors :many -SELECT * FROM authors ORDER BY name; +SELECT * FROM authors +ORDER BY name; --- name: CreateOrUpdateAuthor :exec -UPSERT INTO authors (id, name, bio) VALUES ($p0, $p1, $p2); +-- name: CreateOrUpdateAuthor :one +UPSERT INTO authors (name, bio) +VALUES ( + $name, $bio +) +RETURNING *; -- name: DeleteAuthor :exec -DELETE FROM authors WHERE id = $p0; +DELETE FROM authors WHERE id = $id; -- name: DropTable :exec -DROP TABLE IF EXISTS authors; \ No newline at end of file +DROP TABLE IF EXISTS authors; diff --git a/examples/authors/ydb/query.sql.go b/examples/authors/ydb/query.sql.go index 6d3c6743a9..e460675c92 100644 --- a/examples/authors/ydb/query.sql.go +++ b/examples/authors/ydb/query.sql.go @@ -13,37 +13,44 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/query" ) -const createOrUpdateAuthor = `-- name: CreateOrUpdateAuthor :exec -UPSERT INTO authors (id, name, bio) VALUES ($p0, $p1, $p2) +const createOrUpdateAuthor = `-- name: CreateOrUpdateAuthor :one +UPSERT INTO authors (name, bio) +VALUES ( + $name, $bio +) +RETURNING id, name, bio ` type CreateOrUpdateAuthorParams struct { - P0 uint64 `json:"p0"` - P1 string `json:"p1"` - P2 *string `json:"p2"` + Name string `json:"name"` + Bio *string `json:"bio"` } -func (q *Queries) CreateOrUpdateAuthor(ctx context.Context, arg CreateOrUpdateAuthorParams, opts ...query.ExecuteOption) error { +func (q *Queries) CreateOrUpdateAuthor(ctx context.Context, arg CreateOrUpdateAuthorParams, opts ...query.ExecuteOption) (Author, error) { parameters := ydb.ParamsBuilder() - parameters = parameters.Param("$p0").Uint64(arg.P0) - parameters = parameters.Param("$p1").Text(arg.P1) - parameters = parameters.Param("$p2").BeginOptional().Text(arg.P2).EndOptional() - err := q.db.Exec(ctx, createOrUpdateAuthor, + parameters = parameters.Param("$name").Text(arg.Name) + parameters = parameters.Param("$bio").BeginOptional().Text(arg.Bio).EndOptional() + row, err := q.db.QueryRow(ctx, createOrUpdateAuthor, append(opts, query.WithParameters(parameters.Build()))..., ) + var i Author if err != nil { - return xerrors.WithStackTrace(err) + return i, xerrors.WithStackTrace(err) } - return nil + err = row.Scan(&i.ID, &i.Name, &i.Bio) + if err != nil { + return i, xerrors.WithStackTrace(err) + } + return i, nil } const deleteAuthor = `-- name: DeleteAuthor :exec -DELETE FROM authors WHERE id = $p0 +DELETE FROM authors WHERE id = $id ` -func (q *Queries) DeleteAuthor(ctx context.Context, p0 uint64, opts ...query.ExecuteOption) error { +func (q *Queries) DeleteAuthor(ctx context.Context, id int32, opts ...query.ExecuteOption) error { parameters := ydb.ParamsBuilder() - parameters = parameters.Param("$p0").Uint64(p0) + parameters = parameters.Param("$id").Int32(id) err := q.db.Exec(ctx, deleteAuthor, append(opts, query.WithParameters(parameters.Build()))..., ) @@ -67,12 +74,12 @@ func (q *Queries) DropTable(ctx context.Context, opts ...query.ExecuteOption) er const getAuthor = `-- name: GetAuthor :one SELECT id, name, bio FROM authors -WHERE id = $p0 LIMIT 1 +WHERE id = $id LIMIT 1 ` -func (q *Queries) GetAuthor(ctx context.Context, p0 uint64, opts ...query.ExecuteOption) (Author, error) { +func (q *Queries) GetAuthor(ctx context.Context, id int32, opts ...query.ExecuteOption) (Author, error) { parameters := ydb.ParamsBuilder() - parameters = parameters.Param("$p0").Uint64(p0) + parameters = parameters.Param("$id").Int32(id) row, err := q.db.QueryRow(ctx, getAuthor, append(opts, query.WithParameters(parameters.Build()))..., ) @@ -88,7 +95,8 @@ func (q *Queries) GetAuthor(ctx context.Context, p0 uint64, opts ...query.Execut } const listAuthors = `-- name: ListAuthors :many -SELECT id, name, bio FROM authors ORDER BY name +SELECT id, name, bio FROM authors +ORDER BY name ` func (q *Queries) ListAuthors(ctx context.Context, opts ...query.ExecuteOption) ([]Author, error) { diff --git a/examples/authors/ydb/schema.sql b/examples/authors/ydb/schema.sql index 5207fb3b1e..c25f40986e 100644 --- a/examples/authors/ydb/schema.sql +++ b/examples/authors/ydb/schema.sql @@ -1,6 +1,6 @@ CREATE TABLE authors ( - id Uint64, - name Utf8 NOT NULL, - bio Utf8, + id Serial, + name Text NOT NULL, + bio Text, PRIMARY KEY (id) ); diff --git a/examples/booktest/sqlc.json b/examples/booktest/sqlc.json index b0b0d71d01..7baeff68e7 100644 --- a/examples/booktest/sqlc.json +++ b/examples/booktest/sqlc.json @@ -46,6 +46,14 @@ "rules": [ "sqlc/db-prepare" ] + }, + { + "name": "booktest", + "path": "ydb", + "schema": "ydb/schema.sql", + "queries": "ydb/query.sql", + "engine": "ydb", + "sql_package": "ydb-go-sdk" } ] } diff --git a/examples/booktest/ydb/db.go b/examples/booktest/ydb/db.go new file mode 100644 index 0000000000..5de267426c --- /dev/null +++ b/examples/booktest/ydb/db.go @@ -0,0 +1,26 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package booktest + +import ( + "context" + + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +type DBTX interface { + Exec(ctx context.Context, sql string, opts ...query.ExecuteOption) error + Query(ctx context.Context, sql string, opts ...query.ExecuteOption) (query.Result, error) + QueryResultSet(ctx context.Context, sql string, opts ...query.ExecuteOption) (query.ClosableResultSet, error) + QueryRow(ctx context.Context, sql string, opts ...query.ExecuteOption) (query.Row, error) +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} diff --git a/examples/booktest/ydb/db_test.go b/examples/booktest/ydb/db_test.go new file mode 100644 index 0000000000..bd4535e5ee --- /dev/null +++ b/examples/booktest/ydb/db_test.go @@ -0,0 +1,148 @@ +package booktest + +import ( + "context" + "testing" + "time" + + "github.com/sqlc-dev/sqlc/internal/sqltest/local" + _ "github.com/ydb-platform/ydb-go-sdk/v3" + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +const ( + BooksBookTypeFICTION string = "FICTION" + BooksBookTypeNONFICTION string = "NONFICTION" +) + +func TestBooks(t *testing.T) { + ctx := context.Background() + db := local.YDB(t, []string{"schema.sql"}) + defer db.Close(ctx) + + dq := New(db.Query()) + + // create an author + a, err := dq.CreateAuthor(ctx, "Unknown Master") + if err != nil { + t.Fatal(err) + } + + // save first book + now := time.Now() + _, err = dq.CreateBook(ctx, CreateBookParams{ + AuthorID: a.AuthorID, + Isbn: "1", + Title: "my book title", + BookType: BooksBookTypeFICTION, + Year: 2016, + Available: now, + Tag: "", + }, query.WithIdempotent()) + if err != nil { + t.Fatal(err) + } + + // save second book + b1, err := dq.CreateBook(ctx, CreateBookParams{ + AuthorID: a.AuthorID, + Isbn: "2", + Title: "the second book", + BookType: BooksBookTypeFICTION, + Year: 2016, + Available: now, + Tag: "unique", + }, query.WithIdempotent()) + if err != nil { + t.Fatal(err) + } + + // update the title and tags + err = dq.UpdateBook(ctx, UpdateBookParams{ + BookID: b1.BookID, + Title: "changed second title", + Tag: "disastor", + }, query.WithIdempotent()) + if err != nil { + t.Fatal(err) + } + + // save third book + _, err = dq.CreateBook(ctx, CreateBookParams{ + AuthorID: a.AuthorID, + Isbn: "3", + Title: "the third book", + BookType: BooksBookTypeFICTION, + Year: 2001, + Available: now, + Tag: "cool", + }, query.WithIdempotent()) + if err != nil { + t.Fatal(err) + } + + // save fourth book + b3, err := dq.CreateBook(ctx, CreateBookParams{ + AuthorID: a.AuthorID, + Isbn: "4", + Title: "4th place finisher", + BookType: BooksBookTypeFICTION, + Year: 2011, + Available: now, + Tag: "other", + }, query.WithIdempotent()) + if err != nil { + t.Fatal(err) + } + + // upsert, changing ISBN and title + err = dq.UpdateBookISBN(ctx, UpdateBookISBNParams{ + BookID: b3.BookID, + Isbn: "NEW ISBN", + Title: "never ever gonna finish, a quatrain", + Tag: "someother", + }, query.WithIdempotent()) + if err != nil { + t.Fatal(err) + } + + // retrieve first book + books0, err := dq.BooksByTitleYear(ctx, BooksByTitleYearParams{ + 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 := dq.GetAuthor(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" or "someother" tag + t.Logf("---------\nTag search results:\n") + res, err := dq.BooksByTags(ctx, []string{"cool", "other", "someother"}) + if err != nil { + t.Fatal(err) + } + for _, ab := range res { + name := "" + if ab.Name != nil { + name = *ab.Name + } + t.Logf("Book %d: '%s', Author: '%s', ISBN: '%s' Tag: '%s'\n", ab.BookID, ab.Title, name, ab.Isbn, ab.Tag) + } + + // get book 4 and delete + b5, err := dq.GetBook(ctx, b3.BookID) + if err != nil { + t.Fatal(err) + } + if err := dq.DeleteBook(ctx, b5.BookID); err != nil { + t.Fatal(err) + } +} diff --git a/examples/booktest/ydb/models.go b/examples/booktest/ydb/models.go new file mode 100644 index 0000000000..d6d0804151 --- /dev/null +++ b/examples/booktest/ydb/models.go @@ -0,0 +1,25 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package booktest + +import ( + "time" +) + +type Author struct { + AuthorID int32 + Name string +} + +type Book struct { + BookID int32 + AuthorID int32 + Isbn string + BookType string + Title string + Year int32 + Available time.Time + Tag string +} diff --git a/examples/booktest/ydb/query.sql b/examples/booktest/ydb/query.sql new file mode 100644 index 0000000000..09a3338690 --- /dev/null +++ b/examples/booktest/ydb/query.sql @@ -0,0 +1,63 @@ +-- name: GetAuthor :one +SELECT * FROM authors +WHERE author_id = $author_id LIMIT 1; + +-- name: GetBook :one +SELECT * FROM books +WHERE book_id = $book_id LIMIT 1; + +-- name: DeleteBook :exec +DELETE FROM books +WHERE book_id = $book_id; + +-- name: BooksByTitleYear :many +SELECT * FROM books +WHERE title = $title AND year = $year; + +-- name: BooksByTags :many +SELECT + book_id, + title, + name, + isbn, + tag +FROM books +LEFT JOIN authors ON books.author_id = authors.author_id +WHERE tag IN sqlc.slice(tags); + +-- name: CreateAuthor :one +INSERT INTO authors (name) +VALUES ($name) +RETURNING *; + +-- name: CreateBook :one +INSERT INTO books ( + author_id, + isbn, + book_type, + title, + year, + available, + tag +) VALUES ( + $author_id, + $isbn, + $book_type, + $title, + $year, + $available, + $tag +) +RETURNING *; + +-- name: UpdateBook :exec +UPDATE books +SET title = $title, tag = $tag +WHERE book_id = $book_id; + +-- name: UpdateBookISBN :exec +UPDATE books +SET title = $title, tag = $tag, isbn = $isbn +WHERE book_id = $book_id; + + diff --git a/examples/booktest/ydb/query.sql.go b/examples/booktest/ydb/query.sql.go new file mode 100644 index 0000000000..cfd8da06ff --- /dev/null +++ b/examples/booktest/ydb/query.sql.go @@ -0,0 +1,327 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: query.sql + +package booktest + +import ( + "context" + "time" + + "github.com/ydb-platform/ydb-go-sdk/v3" + "github.com/ydb-platform/ydb-go-sdk/v3/pkg/xerrors" + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +const booksByTags = `-- name: BooksByTags :many +SELECT + book_id, + title, + name, + isbn, + tag +FROM books +LEFT JOIN authors ON books.author_id = authors.author_id +WHERE tag IN $tags +` + +type BooksByTagsRow struct { + BookID int32 + Title string + Name *string + Isbn string + Tag string +} + +func (q *Queries) BooksByTags(ctx context.Context, tags []string, opts ...query.ExecuteOption) ([]BooksByTagsRow, error) { + parameters := ydb.ParamsBuilder() + var list = parameters.Param("$tags").BeginList() + for _, param := range tags { + list = list.Add().Text(param) + } + parameters = list.EndList() + result, err := q.db.QueryResultSet(ctx, booksByTags, + append(opts, query.WithParameters(parameters.Build()))..., + ) + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + var items []BooksByTagsRow + for row, err := range result.Rows(ctx) { + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + var i BooksByTagsRow + if err := row.Scan( + &i.BookID, + &i.Title, + &i.Name, + &i.Isbn, + &i.Tag, + ); err != nil { + return nil, xerrors.WithStackTrace(err) + } + items = append(items, i) + } + if err := result.Close(ctx); err != nil { + return nil, xerrors.WithStackTrace(err) + } + return items, nil +} + +const booksByTitleYear = `-- name: BooksByTitleYear :many +SELECT book_id, author_id, isbn, book_type, title, year, available, tag FROM books +WHERE title = $title AND year = $year +` + +type BooksByTitleYearParams struct { + Title string + Year int32 +} + +func (q *Queries) BooksByTitleYear(ctx context.Context, arg BooksByTitleYearParams, opts ...query.ExecuteOption) ([]Book, error) { + parameters := ydb.ParamsBuilder() + parameters = parameters.Param("$title").Text(arg.Title) + parameters = parameters.Param("$year").Int32(arg.Year) + result, err := q.db.QueryResultSet(ctx, booksByTitleYear, + append(opts, query.WithParameters(parameters.Build()))..., + ) + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + var items []Book + for row, err := range result.Rows(ctx) { + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + var i Book + if err := row.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Year, + &i.Available, + &i.Tag, + ); err != nil { + return nil, xerrors.WithStackTrace(err) + } + items = append(items, i) + } + if err := result.Close(ctx); err != nil { + return nil, xerrors.WithStackTrace(err) + } + return items, nil +} + +const createAuthor = `-- name: CreateAuthor :one +INSERT INTO authors (name) +VALUES ($name) +RETURNING author_id, name +` + +func (q *Queries) CreateAuthor(ctx context.Context, name string, opts ...query.ExecuteOption) (Author, error) { + parameters := ydb.ParamsBuilder() + parameters = parameters.Param("$name").Text(name) + row, err := q.db.QueryRow(ctx, createAuthor, + append(opts, query.WithParameters(parameters.Build()))..., + ) + var i Author + if err != nil { + return i, xerrors.WithStackTrace(err) + } + err = row.Scan(&i.AuthorID, &i.Name) + if err != nil { + return i, xerrors.WithStackTrace(err) + } + return i, nil +} + +const createBook = `-- name: CreateBook :one +INSERT INTO books ( + author_id, + isbn, + book_type, + title, + year, + available, + tag +) VALUES ( + $author_id, + $isbn, + $book_type, + $title, + $year, + $available, + $tag +) +RETURNING book_id, author_id, isbn, book_type, title, year, available, tag +` + +type CreateBookParams struct { + AuthorID int32 + Isbn string + BookType string + Title string + Year int32 + Available time.Time + Tag string +} + +func (q *Queries) CreateBook(ctx context.Context, arg CreateBookParams, opts ...query.ExecuteOption) (Book, error) { + parameters := ydb.ParamsBuilder() + parameters = parameters.Param("$author_id").Int32(arg.AuthorID) + parameters = parameters.Param("$isbn").Text(arg.Isbn) + parameters = parameters.Param("$book_type").Text(arg.BookType) + parameters = parameters.Param("$title").Text(arg.Title) + parameters = parameters.Param("$year").Int32(arg.Year) + parameters = parameters.Param("$available").Timestamp(arg.Available) + parameters = parameters.Param("$tag").Text(arg.Tag) + row, err := q.db.QueryRow(ctx, createBook, + append(opts, query.WithParameters(parameters.Build()))..., + ) + var i Book + if err != nil { + return i, xerrors.WithStackTrace(err) + } + err = row.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Year, + &i.Available, + &i.Tag, + ) + if err != nil { + return i, xerrors.WithStackTrace(err) + } + return i, nil +} + +const deleteBook = `-- name: DeleteBook :exec +DELETE FROM books +WHERE book_id = $book_id +` + +func (q *Queries) DeleteBook(ctx context.Context, bookID int32, opts ...query.ExecuteOption) error { + parameters := ydb.ParamsBuilder() + parameters = parameters.Param("$book_id").Int32(bookID) + err := q.db.Exec(ctx, deleteBook, + append(opts, query.WithParameters(parameters.Build()))..., + ) + if err != nil { + return xerrors.WithStackTrace(err) + } + return nil +} + +const getAuthor = `-- name: GetAuthor :one +SELECT author_id, name FROM authors +WHERE author_id = $author_id LIMIT 1 +` + +func (q *Queries) GetAuthor(ctx context.Context, authorID int32, opts ...query.ExecuteOption) (Author, error) { + parameters := ydb.ParamsBuilder() + parameters = parameters.Param("$author_id").Int32(authorID) + row, err := q.db.QueryRow(ctx, getAuthor, + append(opts, query.WithParameters(parameters.Build()))..., + ) + var i Author + if err != nil { + return i, xerrors.WithStackTrace(err) + } + err = row.Scan(&i.AuthorID, &i.Name) + if err != nil { + return i, xerrors.WithStackTrace(err) + } + return i, nil +} + +const getBook = `-- name: GetBook :one +SELECT book_id, author_id, isbn, book_type, title, year, available, tag FROM books +WHERE book_id = $book_id LIMIT 1 +` + +func (q *Queries) GetBook(ctx context.Context, bookID int32, opts ...query.ExecuteOption) (Book, error) { + parameters := ydb.ParamsBuilder() + parameters = parameters.Param("$book_id").Int32(bookID) + row, err := q.db.QueryRow(ctx, getBook, + append(opts, query.WithParameters(parameters.Build()))..., + ) + var i Book + if err != nil { + return i, xerrors.WithStackTrace(err) + } + err = row.Scan( + &i.BookID, + &i.AuthorID, + &i.Isbn, + &i.BookType, + &i.Title, + &i.Year, + &i.Available, + &i.Tag, + ) + if err != nil { + return i, xerrors.WithStackTrace(err) + } + return i, nil +} + +const updateBook = `-- name: UpdateBook :exec +UPDATE books +SET title = $title, tag = $tag +WHERE book_id = $book_id +` + +type UpdateBookParams struct { + Title string + Tag string + BookID int32 +} + +func (q *Queries) UpdateBook(ctx context.Context, arg UpdateBookParams, opts ...query.ExecuteOption) error { + parameters := ydb.ParamsBuilder() + parameters = parameters.Param("$title").Text(arg.Title) + parameters = parameters.Param("$tag").Text(arg.Tag) + parameters = parameters.Param("$book_id").Int32(arg.BookID) + err := q.db.Exec(ctx, updateBook, + append(opts, query.WithParameters(parameters.Build()))..., + ) + if err != nil { + return xerrors.WithStackTrace(err) + } + return nil +} + +const updateBookISBN = `-- name: UpdateBookISBN :exec +UPDATE books +SET title = $title, tag = $tag, isbn = $isbn +WHERE book_id = $book_id +` + +type UpdateBookISBNParams struct { + Title string + Tag string + Isbn string + BookID int32 +} + +func (q *Queries) UpdateBookISBN(ctx context.Context, arg UpdateBookISBNParams, opts ...query.ExecuteOption) error { + parameters := ydb.ParamsBuilder() + parameters = parameters.Param("$title").Text(arg.Title) + parameters = parameters.Param("$tag").Text(arg.Tag) + parameters = parameters.Param("$isbn").Text(arg.Isbn) + parameters = parameters.Param("$book_id").Int32(arg.BookID) + err := q.db.Exec(ctx, updateBookISBN, + append(opts, query.WithParameters(parameters.Build()))..., + ) + if err != nil { + return xerrors.WithStackTrace(err) + } + return nil +} diff --git a/examples/booktest/ydb/schema.sql b/examples/booktest/ydb/schema.sql new file mode 100644 index 0000000000..d5ea97972f --- /dev/null +++ b/examples/booktest/ydb/schema.sql @@ -0,0 +1,18 @@ +CREATE TABLE authors ( + author_id Serial, + name Text NOT NULL DEFAULT '', + PRIMARY KEY (author_id) +); + +CREATE TABLE books ( + book_id Serial, + author_id Int32 NOT NULL, + isbn Text NOT NULL DEFAULT '', + book_type Text NOT NULL DEFAULT 'FICTION', + title Text NOT NULL DEFAULT '', + year Int32 NOT NULL DEFAULT 2000, + available Timestamp NOT NULL, + tag Text NOT NULL DEFAULT '', + PRIMARY KEY (book_id) +); + diff --git a/examples/jets/sqlc.json b/examples/jets/sqlc.json index 8dfa0df777..1924af6fe0 100644 --- a/examples/jets/sqlc.json +++ b/examples/jets/sqlc.json @@ -20,6 +20,14 @@ "rules": [ "sqlc/db-prepare" ] + }, + { + "path": "ydb", + "name": "jets", + "schema": "ydb/schema.sql", + "queries": "ydb/query-building.sql", + "engine": "ydb", + "sql_package": "ydb-go-sdk" } ] } diff --git a/examples/jets/ydb/db.go b/examples/jets/ydb/db.go new file mode 100644 index 0000000000..c8f3a5ed08 --- /dev/null +++ b/examples/jets/ydb/db.go @@ -0,0 +1,26 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package jets + +import ( + "context" + + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +type DBTX interface { + Exec(ctx context.Context, sql string, opts ...query.ExecuteOption) error + Query(ctx context.Context, sql string, opts ...query.ExecuteOption) (query.Result, error) + QueryResultSet(ctx context.Context, sql string, opts ...query.ExecuteOption) (query.ClosableResultSet, error) + QueryRow(ctx context.Context, sql string, opts ...query.ExecuteOption) (query.Row, error) +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} diff --git a/examples/jets/ydb/db_test.go b/examples/jets/ydb/db_test.go new file mode 100644 index 0000000000..3b7b03e3ac --- /dev/null +++ b/examples/jets/ydb/db_test.go @@ -0,0 +1,73 @@ +package jets + +import ( + "context" + "testing" + + "github.com/sqlc-dev/sqlc/internal/sqltest/local" + "github.com/ydb-platform/ydb-go-sdk/v3" + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +func TestJets(t *testing.T) { + ctx := context.Background() + db := local.YDB(t, []string{"schema.sql"}) + defer db.Close(ctx) + + q := New(db.Query()) + + // insert test data + pilots := []struct { + id int32 + name string + }{ + {1, "John Doe"}, + {2, "Jane Smith"}, + {3, "Bob Johnson"}, + } + + for _, p := range pilots { + parameters := ydb.ParamsBuilder() + parameters = parameters.Param("$id").Int32(p.id) + parameters = parameters.Param("$name").Text(p.name) + err := db.Query().Exec(ctx, "UPSERT INTO pilots (id, name) VALUES ($id, $name)", + query.WithParameters(parameters.Build()), + ) + if err != nil { + t.Fatalf("failed to insert pilot: %v", err) + } + } + + // list all pilots + pilotsList, err := q.ListPilots(ctx) + if err != nil { + t.Fatal(err) + } + t.Log("Pilots:", pilotsList) + + // count pilots + count, err := q.CountPilots(ctx) + if err != nil { + t.Fatal(err) + } + t.Logf("Total pilots: %d", count) + + if count != 3 { + t.Errorf("expected 3 pilots, got %d", count) + } + + // delete a pilot + err = q.DeletePilot(ctx, 1) + if err != nil { + t.Fatal(err) + } + + // count after delete + count, err = q.CountPilots(ctx) + if err != nil { + t.Fatal(err) + } + if count != 2 { + t.Errorf("expected 2 pilots after delete, got %d", count) + } +} diff --git a/examples/jets/ydb/models.go b/examples/jets/ydb/models.go new file mode 100644 index 0000000000..166bf06f3d --- /dev/null +++ b/examples/jets/ydb/models.go @@ -0,0 +1,28 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package jets + +type Jet struct { + ID int32 + PilotID int32 + Age int32 + Name string + Color string +} + +type Language struct { + ID int32 + Language string +} + +type Pilot struct { + ID int32 + Name string +} + +type PilotLanguage struct { + PilotID int32 + LanguageID int32 +} diff --git a/examples/jets/ydb/query-building.sql b/examples/jets/ydb/query-building.sql new file mode 100644 index 0000000000..07f2ad23ec --- /dev/null +++ b/examples/jets/ydb/query-building.sql @@ -0,0 +1,12 @@ +-- name: CountPilots :one +SELECT COUNT(*) FROM pilots; + +-- name: ListPilots :many +SELECT * FROM pilots LIMIT 5; + +-- name: DeletePilot :exec +DELETE FROM pilots WHERE id = $id; + + + + diff --git a/examples/jets/ydb/query-building.sql.go b/examples/jets/ydb/query-building.sql.go new file mode 100644 index 0000000000..5ec73a4edf --- /dev/null +++ b/examples/jets/ydb/query-building.sql.go @@ -0,0 +1,73 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: query-building.sql + +package jets + +import ( + "context" + + "github.com/ydb-platform/ydb-go-sdk/v3" + "github.com/ydb-platform/ydb-go-sdk/v3/pkg/xerrors" + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +const countPilots = `-- name: CountPilots :one +SELECT COUNT(*) FROM pilots +` + +func (q *Queries) CountPilots(ctx context.Context, opts ...query.ExecuteOption) (uint64, error) { + row, err := q.db.QueryRow(ctx, countPilots, opts...) + var count uint64 + if err != nil { + return count, xerrors.WithStackTrace(err) + } + err = row.Scan(&count) + if err != nil { + return count, xerrors.WithStackTrace(err) + } + return count, nil +} + +const deletePilot = `-- name: DeletePilot :exec +DELETE FROM pilots WHERE id = $id +` + +func (q *Queries) DeletePilot(ctx context.Context, id int32, opts ...query.ExecuteOption) error { + parameters := ydb.ParamsBuilder() + parameters = parameters.Param("$id").Int32(id) + err := q.db.Exec(ctx, deletePilot, + append(opts, query.WithParameters(parameters.Build()))..., + ) + if err != nil { + return xerrors.WithStackTrace(err) + } + return nil +} + +const listPilots = `-- name: ListPilots :many +SELECT id, name FROM pilots LIMIT 5 +` + +func (q *Queries) ListPilots(ctx context.Context, opts ...query.ExecuteOption) ([]Pilot, error) { + result, err := q.db.QueryResultSet(ctx, listPilots, opts...) + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + var items []Pilot + for row, err := range result.Rows(ctx) { + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + var i Pilot + if err := row.Scan(&i.ID, &i.Name); err != nil { + return nil, xerrors.WithStackTrace(err) + } + items = append(items, i) + } + if err := result.Close(ctx); err != nil { + return nil, xerrors.WithStackTrace(err) + } + return items, nil +} diff --git a/examples/jets/ydb/schema.sql b/examples/jets/ydb/schema.sql new file mode 100644 index 0000000000..bb256e5d5f --- /dev/null +++ b/examples/jets/ydb/schema.sql @@ -0,0 +1,30 @@ +CREATE TABLE pilots ( + id Int32 NOT NULL, + name Text NOT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE jets ( + id Int32 NOT NULL, + pilot_id Int32 NOT NULL, + age Int32 NOT NULL, + name Text NOT NULL, + color Text NOT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE languages ( + id Int32 NOT NULL, + language Text NOT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE pilot_languages ( + pilot_id Int32 NOT NULL, + language_id Int32 NOT NULL, + PRIMARY KEY (pilot_id, language_id) +); + + + + diff --git a/examples/ondeck/sqlc.json b/examples/ondeck/sqlc.json index 7b97328b3f..a39c743a0c 100644 --- a/examples/ondeck/sqlc.json +++ b/examples/ondeck/sqlc.json @@ -55,6 +55,17 @@ "emit_json_tags": true, "emit_prepared_queries": true, "emit_interface": true + }, + { + "path": "ydb", + "name": "ondeck", + "schema": "ydb/schema", + "queries": "ydb/query", + "engine": "ydb", + "sql_package": "ydb-go-sdk", + "emit_json_tags": true, + "emit_prepared_queries": true, + "emit_interface": true } ] } diff --git a/examples/ondeck/ydb/city.sql.go b/examples/ondeck/ydb/city.sql.go new file mode 100644 index 0000000000..e9078dad6f --- /dev/null +++ b/examples/ondeck/ydb/city.sql.go @@ -0,0 +1,126 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: city.sql + +package ondeck + +import ( + "context" + + "github.com/ydb-platform/ydb-go-sdk/v3" + "github.com/ydb-platform/ydb-go-sdk/v3/pkg/xerrors" + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +const createCity = `-- name: CreateCity :one +INSERT INTO city ( + name, + slug +) VALUES ( + $name, + $slug +) RETURNING slug, name +` + +type CreateCityParams struct { + Name string `json:"name"` + Slug string `json:"slug"` +} + +// Create a new city. The slug must be unique. +// This is the second line of the comment +// This is the third line + +func (q *Queries) CreateCity(ctx context.Context, arg CreateCityParams, opts ...query.ExecuteOption) (City, error) { + parameters := ydb.ParamsBuilder() + parameters = parameters.Param("$name").Text(arg.Name) + parameters = parameters.Param("$slug").Text(arg.Slug) + row, err := q.db.QueryRow(ctx, createCity, + append(opts, query.WithParameters(parameters.Build()))..., + ) + var i City + if err != nil { + return i, xerrors.WithStackTrace(err) + } + err = row.Scan(&i.Slug, &i.Name) + if err != nil { + return i, xerrors.WithStackTrace(err) + } + return i, nil +} + +const getCity = `-- name: GetCity :one +SELECT slug, name +FROM city +WHERE slug = $slug +` + +func (q *Queries) GetCity(ctx context.Context, slug string, opts ...query.ExecuteOption) (City, error) { + parameters := ydb.ParamsBuilder() + parameters = parameters.Param("$slug").Text(slug) + row, err := q.db.QueryRow(ctx, getCity, + append(opts, query.WithParameters(parameters.Build()))..., + ) + var i City + if err != nil { + return i, xerrors.WithStackTrace(err) + } + err = row.Scan(&i.Slug, &i.Name) + if err != nil { + return i, xerrors.WithStackTrace(err) + } + return i, nil +} + +const listCities = `-- name: ListCities :many +SELECT slug, name +FROM city +ORDER BY name +` + +func (q *Queries) ListCities(ctx context.Context, opts ...query.ExecuteOption) ([]City, error) { + result, err := q.db.QueryResultSet(ctx, listCities, opts...) + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + var items []City + for row, err := range result.Rows(ctx) { + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + var i City + if err := row.Scan(&i.Slug, &i.Name); err != nil { + return nil, xerrors.WithStackTrace(err) + } + items = append(items, i) + } + if err := result.Close(ctx); err != nil { + return nil, xerrors.WithStackTrace(err) + } + return items, nil +} + +const updateCityName = `-- name: UpdateCityName :exec +UPDATE city +SET name = $name +WHERE slug = $slug +` + +type UpdateCityNameParams struct { + Name string `json:"name"` + Slug string `json:"slug"` +} + +func (q *Queries) UpdateCityName(ctx context.Context, arg UpdateCityNameParams, opts ...query.ExecuteOption) error { + parameters := ydb.ParamsBuilder() + parameters = parameters.Param("$name").Text(arg.Name) + parameters = parameters.Param("$slug").Text(arg.Slug) + err := q.db.Exec(ctx, updateCityName, + append(opts, query.WithParameters(parameters.Build()))..., + ) + if err != nil { + return xerrors.WithStackTrace(err) + } + return nil +} diff --git a/examples/ondeck/ydb/db.go b/examples/ondeck/ydb/db.go new file mode 100644 index 0000000000..7102987eee --- /dev/null +++ b/examples/ondeck/ydb/db.go @@ -0,0 +1,26 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package ondeck + +import ( + "context" + + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +type DBTX interface { + Exec(ctx context.Context, sql string, opts ...query.ExecuteOption) error + Query(ctx context.Context, sql string, opts ...query.ExecuteOption) (query.Result, error) + QueryResultSet(ctx context.Context, sql string, opts ...query.ExecuteOption) (query.ClosableResultSet, error) + QueryRow(ctx context.Context, sql string, opts ...query.ExecuteOption) (query.Row, error) +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} diff --git a/examples/ondeck/ydb/db_test.go b/examples/ondeck/ydb/db_test.go new file mode 100644 index 0000000000..24c34dadcb --- /dev/null +++ b/examples/ondeck/ydb/db_test.go @@ -0,0 +1,130 @@ +//go:build examples + +package ondeck + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/sqlc-dev/sqlc/internal/sqltest/local" + _ "github.com/ydb-platform/ydb-go-sdk/v3" + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +func runOnDeckQueries(t *testing.T, q *Queries) { + ctx := context.Background() + + city, err := q.CreateCity(ctx, CreateCityParams{ + Slug: "san-francisco", + Name: "San Francisco", + }, query.WithIdempotent()) + if err != nil { + t.Fatal(err) + } + + tags := "rock, punk" + venueID, err := q.CreateVenue(ctx, CreateVenueParams{ + Slug: "the-fillmore", + Name: "The Fillmore", + City: city.Slug, + SpotifyPlaylist: "spotify:uri", + Status: "op!en", + Tags: &tags, + }, query.WithIdempotent()) + if err != nil { + t.Fatal(err) + } + + venue, err := q.GetVenue(ctx, GetVenueParams{ + Slug: "the-fillmore", + City: city.Slug, + }) + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(venue.ID, venueID); diff != "" { + t.Errorf("venue ID mismatch:\n%s", diff) + } + + { + actual, err := q.GetCity(ctx, city.Slug) + if err != nil { + t.Error(err) + } + if diff := cmp.Diff(actual, city); diff != "" { + t.Errorf("get city mismatch:\n%s", diff) + } + } + + { + actual, err := q.VenueCountByCity(ctx) + if err != nil { + t.Error(err) + } + if diff := cmp.Diff(actual, []VenueCountByCityRow{ + {city.Slug, uint64(1)}, + }); diff != "" { + t.Errorf("venue count mismatch:\n%s", diff) + } + } + + { + actual, err := q.ListCities(ctx) + if err != nil { + t.Error(err) + } + if diff := cmp.Diff(actual, []City{city}); diff != "" { + t.Errorf("list city mismatch:\n%s", diff) + } + } + + { + actual, err := q.ListVenues(ctx, city.Slug) + if err != nil { + t.Error(err) + } + if diff := cmp.Diff(actual, []Venue{venue}); diff != "" { + t.Errorf("list venue mismatch:\n%s", diff) + } + } + + { + err := q.UpdateCityName(ctx, UpdateCityNameParams{ + Slug: city.Slug, + Name: "SF", + }, query.WithIdempotent()) + if err != nil { + t.Error(err) + } + } + + { + id, err := q.UpdateVenueName(ctx, UpdateVenueNameParams{ + Slug: venue.Slug, + Name: "Fillmore", + }, query.WithIdempotent()) + if err != nil { + t.Error(err) + } + if diff := cmp.Diff(id, venue.ID); diff != "" { + t.Errorf("update venue mismatch:\n%s", diff) + } + } + + { + err := q.DeleteVenue(ctx, venue.Slug) + if err != nil { + t.Error(err) + } + } +} + +func TestQueries(t *testing.T) { + ctx := context.Background() + db := local.YDB(t, []string{"schema"}) + defer db.Close(ctx) + + runOnDeckQueries(t, New(db.Query())) +} diff --git a/examples/ondeck/ydb/models.go b/examples/ondeck/ydb/models.go new file mode 100644 index 0000000000..2d0202f1fa --- /dev/null +++ b/examples/ondeck/ydb/models.go @@ -0,0 +1,26 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package ondeck + +import ( + "time" +) + +type City struct { + Slug string `json:"slug"` + Name string `json:"name"` +} + +type Venue struct { + ID int32 `json:"id"` + Status string `json:"status"` + Slug string `json:"slug"` + Name string `json:"name"` + City string `json:"city"` + SpotifyPlaylist string `json:"spotify_playlist"` + SongkickID *string `json:"songkick_id"` + Tags *string `json:"tags"` + CreatedAt *time.Time `json:"created_at"` +} diff --git a/examples/ondeck/ydb/querier.go b/examples/ondeck/ydb/querier.go new file mode 100644 index 0000000000..28b34ed8ac --- /dev/null +++ b/examples/ondeck/ydb/querier.go @@ -0,0 +1,29 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package ondeck + +import ( + "context" + + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +type Querier interface { + // Create a new city. The slug must be unique. + // This is the second line of the comment + // This is the third line + CreateCity(ctx context.Context, arg CreateCityParams, opts ...query.ExecuteOption) (City, error) + CreateVenue(ctx context.Context, arg CreateVenueParams, opts ...query.ExecuteOption) (int32, error) + DeleteVenue(ctx context.Context, slug string, opts ...query.ExecuteOption) error + GetCity(ctx context.Context, slug string, opts ...query.ExecuteOption) (City, error) + GetVenue(ctx context.Context, arg GetVenueParams, opts ...query.ExecuteOption) (Venue, error) + ListCities(ctx context.Context, opts ...query.ExecuteOption) ([]City, error) + ListVenues(ctx context.Context, city string, opts ...query.ExecuteOption) ([]Venue, error) + UpdateCityName(ctx context.Context, arg UpdateCityNameParams, opts ...query.ExecuteOption) error + UpdateVenueName(ctx context.Context, arg UpdateVenueNameParams, opts ...query.ExecuteOption) (int32, error) + VenueCountByCity(ctx context.Context, opts ...query.ExecuteOption) ([]VenueCountByCityRow, error) +} + +var _ Querier = (*Queries)(nil) diff --git a/examples/ondeck/ydb/query/city.sql b/examples/ondeck/ydb/query/city.sql new file mode 100644 index 0000000000..7d5ddb169f --- /dev/null +++ b/examples/ondeck/ydb/query/city.sql @@ -0,0 +1,27 @@ +-- name: ListCities :many +SELECT * +FROM city +ORDER BY name; + +-- name: GetCity :one +SELECT * +FROM city +WHERE slug = $slug; + +-- name: CreateCity :one +-- Create a new city. The slug must be unique. +-- This is the second line of the comment +-- This is the third line +INSERT INTO city ( + name, + slug +) VALUES ( + $name, + $slug +) RETURNING *; + +-- name: UpdateCityName :exec +UPDATE city +SET name = $name +WHERE slug = $slug; + diff --git a/examples/ondeck/ydb/query/venue.sql b/examples/ondeck/ydb/query/venue.sql new file mode 100644 index 0000000000..4eec18d349 --- /dev/null +++ b/examples/ondeck/ydb/query/venue.sql @@ -0,0 +1,48 @@ +-- name: ListVenues :many +SELECT * +FROM venue +WHERE city = $city +ORDER BY name; + +-- name: DeleteVenue :exec +DELETE FROM venue +WHERE slug = $slug AND slug = $slug; + +-- name: GetVenue :one +SELECT * +FROM venue +WHERE slug = $slug AND city = $city; + +-- name: CreateVenue :one +INSERT INTO venue ( + slug, + name, + city, + created_at, + spotify_playlist, + status, + tags +) VALUES ( + $slug, + $name, + $city, + CurrentUtcTimestamp(), + $spotify_playlist, + $status, + $tags +) RETURNING id; + +-- name: UpdateVenueName :one +UPDATE venue +SET name = $name +WHERE slug = $slug +RETURNING id; + +-- name: VenueCountByCity :many +SELECT + city, + count(*) as count +FROM venue +GROUP BY city +ORDER BY city; + diff --git a/examples/ondeck/ydb/schema/0001_city.sql b/examples/ondeck/ydb/schema/0001_city.sql new file mode 100644 index 0000000000..d0941ef052 --- /dev/null +++ b/examples/ondeck/ydb/schema/0001_city.sql @@ -0,0 +1,6 @@ +CREATE TABLE city ( + slug Text NOT NULL, + name Text NOT NULL, + PRIMARY KEY (slug) +); + diff --git a/examples/ondeck/ydb/schema/0002_venue.sql b/examples/ondeck/ydb/schema/0002_venue.sql new file mode 100644 index 0000000000..f6296c9099 --- /dev/null +++ b/examples/ondeck/ydb/schema/0002_venue.sql @@ -0,0 +1,13 @@ +CREATE TABLE venues ( + id Serial NOT NULL, + dropped Text, + status Text NOT NULL, + slug Text NOT NULL, + name Text NOT NULL, + city Text NOT NULL, + spotify_playlist Text NOT NULL, + songkick_id Text, + tags Text, + PRIMARY KEY (id) +); + diff --git a/examples/ondeck/ydb/schema/0003_add_column.sql b/examples/ondeck/ydb/schema/0003_add_column.sql new file mode 100644 index 0000000000..d0a954e008 --- /dev/null +++ b/examples/ondeck/ydb/schema/0003_add_column.sql @@ -0,0 +1,4 @@ +ALTER TABLE venues RENAME TO venue; +ALTER TABLE venue DROP COLUMN dropped; +ALTER TABLE venue ADD COLUMN created_at Timestamp; + diff --git a/examples/ondeck/ydb/venue.sql.go b/examples/ondeck/ydb/venue.sql.go new file mode 100644 index 0000000000..b191518591 --- /dev/null +++ b/examples/ondeck/ydb/venue.sql.go @@ -0,0 +1,230 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: venue.sql + +package ondeck + +import ( + "context" + + "github.com/ydb-platform/ydb-go-sdk/v3" + "github.com/ydb-platform/ydb-go-sdk/v3/pkg/xerrors" + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +const createVenue = `-- name: CreateVenue :one +INSERT INTO venue ( + slug, + name, + city, + created_at, + spotify_playlist, + status, + tags +) VALUES ( + $slug, + $name, + $city, + CurrentUtcTimestamp(), + $spotify_playlist, + $status, + $tags +) RETURNING id +` + +type CreateVenueParams struct { + Slug string `json:"slug"` + Name string `json:"name"` + City string `json:"city"` + SpotifyPlaylist string `json:"spotify_playlist"` + Status string `json:"status"` + Tags *string `json:"tags"` +} + +func (q *Queries) CreateVenue(ctx context.Context, arg CreateVenueParams, opts ...query.ExecuteOption) (int32, error) { + parameters := ydb.ParamsBuilder() + parameters = parameters.Param("$slug").Text(arg.Slug) + parameters = parameters.Param("$name").Text(arg.Name) + parameters = parameters.Param("$city").Text(arg.City) + parameters = parameters.Param("$spotify_playlist").Text(arg.SpotifyPlaylist) + parameters = parameters.Param("$status").Text(arg.Status) + parameters = parameters.Param("$tags").BeginOptional().Text(arg.Tags).EndOptional() + row, err := q.db.QueryRow(ctx, createVenue, + append(opts, query.WithParameters(parameters.Build()))..., + ) + var id int32 + if err != nil { + return id, xerrors.WithStackTrace(err) + } + err = row.Scan(&id) + if err != nil { + return id, xerrors.WithStackTrace(err) + } + return id, nil +} + +const deleteVenue = `-- name: DeleteVenue :exec +DELETE FROM venue +WHERE slug = $slug AND slug = $slug +` + +func (q *Queries) DeleteVenue(ctx context.Context, slug string, opts ...query.ExecuteOption) error { + parameters := ydb.ParamsBuilder() + parameters = parameters.Param("$slug").Text(slug) + err := q.db.Exec(ctx, deleteVenue, + append(opts, query.WithParameters(parameters.Build()))..., + ) + if err != nil { + return xerrors.WithStackTrace(err) + } + return nil +} + +const getVenue = `-- name: GetVenue :one +SELECT id, status, slug, name, city, spotify_playlist, songkick_id, tags, created_at +FROM venue +WHERE slug = $slug AND city = $city +` + +type GetVenueParams struct { + Slug string `json:"slug"` + City string `json:"city"` +} + +func (q *Queries) GetVenue(ctx context.Context, arg GetVenueParams, opts ...query.ExecuteOption) (Venue, error) { + parameters := ydb.ParamsBuilder() + parameters = parameters.Param("$slug").Text(arg.Slug) + parameters = parameters.Param("$city").Text(arg.City) + row, err := q.db.QueryRow(ctx, getVenue, + append(opts, query.WithParameters(parameters.Build()))..., + ) + var i Venue + if err != nil { + return i, xerrors.WithStackTrace(err) + } + err = row.Scan( + &i.ID, + &i.Status, + &i.Slug, + &i.Name, + &i.City, + &i.SpotifyPlaylist, + &i.SongkickID, + &i.Tags, + &i.CreatedAt, + ) + if err != nil { + return i, xerrors.WithStackTrace(err) + } + return i, nil +} + +const listVenues = `-- name: ListVenues :many +SELECT id, status, slug, name, city, spotify_playlist, songkick_id, tags, created_at +FROM venue +WHERE city = $city +ORDER BY name +` + +func (q *Queries) ListVenues(ctx context.Context, city string, opts ...query.ExecuteOption) ([]Venue, error) { + parameters := ydb.ParamsBuilder() + parameters = parameters.Param("$city").Text(city) + result, err := q.db.QueryResultSet(ctx, listVenues, + append(opts, query.WithParameters(parameters.Build()))..., + ) + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + var items []Venue + for row, err := range result.Rows(ctx) { + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + var i Venue + if err := row.Scan( + &i.ID, + &i.Status, + &i.Slug, + &i.Name, + &i.City, + &i.SpotifyPlaylist, + &i.SongkickID, + &i.Tags, + &i.CreatedAt, + ); err != nil { + return nil, xerrors.WithStackTrace(err) + } + items = append(items, i) + } + if err := result.Close(ctx); err != nil { + return nil, xerrors.WithStackTrace(err) + } + return items, nil +} + +const updateVenueName = `-- name: UpdateVenueName :one +UPDATE venue +SET name = $name +WHERE slug = $slug +RETURNING id +` + +type UpdateVenueNameParams struct { + Name string `json:"name"` + Slug string `json:"slug"` +} + +func (q *Queries) UpdateVenueName(ctx context.Context, arg UpdateVenueNameParams, opts ...query.ExecuteOption) (int32, error) { + parameters := ydb.ParamsBuilder() + parameters = parameters.Param("$name").Text(arg.Name) + parameters = parameters.Param("$slug").Text(arg.Slug) + row, err := q.db.QueryRow(ctx, updateVenueName, + append(opts, query.WithParameters(parameters.Build()))..., + ) + var id int32 + if err != nil { + return id, xerrors.WithStackTrace(err) + } + err = row.Scan(&id) + if err != nil { + return id, xerrors.WithStackTrace(err) + } + return id, nil +} + +const venueCountByCity = `-- name: VenueCountByCity :many +SELECT + city, + count(*) as count +FROM venue +GROUP BY city +ORDER BY city +` + +type VenueCountByCityRow struct { + City string `json:"city"` + Count uint64 `json:"count"` +} + +func (q *Queries) VenueCountByCity(ctx context.Context, opts ...query.ExecuteOption) ([]VenueCountByCityRow, error) { + result, err := q.db.QueryResultSet(ctx, venueCountByCity, opts...) + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + var items []VenueCountByCityRow + for row, err := range result.Rows(ctx) { + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + var i VenueCountByCityRow + if err := row.Scan(&i.City, &i.Count); err != nil { + return nil, xerrors.WithStackTrace(err) + } + items = append(items, i) + } + if err := result.Close(ctx); err != nil { + return nil, xerrors.WithStackTrace(err) + } + return items, nil +} diff --git a/internal/codegen/golang/templates/ydb-go-sdk/interfaceCode.tmpl b/internal/codegen/golang/templates/ydb-go-sdk/interfaceCode.tmpl index 9ed0dd1bc3..f9c06cc705 100644 --- a/internal/codegen/golang/templates/ydb-go-sdk/interfaceCode.tmpl +++ b/internal/codegen/golang/templates/ydb-go-sdk/interfaceCode.tmpl @@ -3,17 +3,17 @@ {{- $dbtxParam := .EmitMethodsWithDBArgument -}} {{- range .GoQueries}} {{- if and (eq .Cmd ":one") ($dbtxParam) }} - {{- range .Comments}}//{{.}} - {{- end}} + {{range .Comments}}//{{.}} + {{end -}} {{.MethodName}}(ctx context.Context, db DBTX, {{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) ({{.Ret.DefineType}}, error) {{- else if eq .Cmd ":one"}} - {{- range .Comments}}//{{.}} - {{- end}} + {{range .Comments}}//{{.}} + {{end -}} {{.MethodName}}(ctx context.Context, {{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) ({{.Ret.DefineType}}, error) {{- end}} {{- if and (eq .Cmd ":many") ($dbtxParam) }} - {{- range .Comments}}//{{.}} - {{- end}} + {{range .Comments}}//{{.}} + {{end -}} {{.MethodName}}(ctx context.Context, db DBTX, {{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) ([]{{.Ret.DefineType}}, error) {{- else if eq .Cmd ":many"}} {{range .Comments}}//{{.}} @@ -21,12 +21,12 @@ {{.MethodName}}(ctx context.Context, {{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) ([]{{.Ret.DefineType}}, error) {{- end}} {{- if and (eq .Cmd ":exec") ($dbtxParam) }} - {{- range .Comments}}//{{.}} - {{- end}} + {{range .Comments}}//{{.}} + {{end -}} {{.MethodName}}(ctx context.Context, db DBTX, {{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) error {{- else if eq .Cmd ":exec"}} - {{- range .Comments}}//{{.}} - {{- end}} + {{range .Comments}}//{{.}} + {{end -}} {{.MethodName}}(ctx context.Context, {{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) error {{- end}} {{- end}} diff --git a/internal/codegen/golang/ydb_type.go b/internal/codegen/golang/ydb_type.go index 572f79f334..0192b80e52 100644 --- a/internal/codegen/golang/ydb_type.go +++ b/internal/codegen/golang/ydb_type.go @@ -32,7 +32,7 @@ func YDBType(req *plugin.GenerateRequest, options *opts.Options, col *plugin.Col // return "sql.NullBool" return "*bool" - case "int8": + case "int8", "tinyint": if notNull { return "int8" } diff --git a/internal/sqltest/local/ydb.go b/internal/sqltest/local/ydb.go index e3e51e3716..b5dde93315 100644 --- a/internal/sqltest/local/ydb.go +++ b/internal/sqltest/local/ydb.go @@ -5,6 +5,7 @@ import ( "fmt" "math/rand" "os" + "strings" "testing" "time" @@ -57,10 +58,7 @@ func link_YDB(t *testing.T, migrations []string, rw bool, tls bool) *ydb.Driver connectionString = fmt.Sprintf("grpc://%s%s", dbuiri, baseDB) } - db, err := ydb.Open(ctx, connectionString, - ydb.WithInsecure(), - ydb.WithDiscoveryInterval(time.Hour), - ) + db, err := ydb.Open(ctx, connectionString) if err != nil { t.Fatalf("failed to open YDB connection: %s", err) } @@ -77,9 +75,16 @@ func link_YDB(t *testing.T, migrations []string, rw bool, tls bool) *ydb.Driver } stmt := migrate.RemoveRollbackStatements(string(blob)) - err = db.Query().Exec(ctx, stmt, query.WithTxControl(query.EmptyTxControl())) - if err != nil { - t.Fatalf("failed to apply migration: %s\nSQL: %s", err, stmt) + statements := strings.Split(stmt, ";") + for _, singleStmt := range statements { + singleStmt = strings.TrimSpace(singleStmt) + if singleStmt == "" { + continue + } + err = db.Query().Exec(ctx, singleStmt, query.WithTxControl(query.EmptyTxControl())) + if err != nil { + t.Fatalf("failed to apply migration: %s\nSQL: %s", err, singleStmt) + } } }