Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
234 changes: 234 additions & 0 deletions docs/tutorials/getting-started-ydb.md
Original file line number Diff line number Diff line change
@@ -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 <PROJECT_ID> with your project ID from the sqlc Cloud dashboard
project: "<PROJECT_ID>"
sql:
- engine: "ydb"
queries: "query.sql"
schema: "schema.sql"
gen:
go:
package: "tutorial"
out: "tutorial"
```

Replace `<PROJECT_ID>` 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="<your 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
```
107 changes: 30 additions & 77 deletions examples/authors/ydb/db_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

package authors

import (
Expand All @@ -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)
}
}
2 changes: 1 addition & 1 deletion examples/authors/ydb/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 11 additions & 6 deletions examples/authors/ydb/query.sql
Original file line number Diff line number Diff line change
@@ -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;
DROP TABLE IF EXISTS authors;
Loading
Loading