Example app in Go
This page provides a detailed description of the code for a test app that uses the YDB Go SDK.
Downloading and starting
The following execution scenario is based on Git and Go. Make sure to install the YDB Go SDK.
Create a working directory and use it to run the following command from the command line to clone the GitHub repository:
git clone https://github.com/ydb-platform/ydb-go-examples/
Next, from the same working directory, run the following command to start the test app:
To connect to a locally deployed YDB database according to the Docker use case, run the following command in the default configuration:
( export YDB_ANONYMOUS_CREDENTIALS=1 && cd ydb-go-examples && \
go run ./basic -ydb="grpc://localhost:2136?database=/local" )
To run the example against any available YDB database, the endpoint and the database path need to be provide.
If authentication is enabled for the database, the authentication mode needs to be chosen and credentials (a token or a username/password pair) need to be provided.
Run the command as follows:
( export <auth_mode_var>="<auth_mode_value>" && cd ydb-go-examples && \
go run ./basic -ydb="<endpoint>?database=<database>" )
where
<endpoint>
: The endpoint.<database>
: The database path.<auth_mode_var>
: The environment variable that determines the authentication mode.<auth_mode_value>
is the authentication parameter value for the selected mode.
For example:
( export YDB_ACCESS_TOKEN_CREDENTIALS="t1.9euelZqOnJuJlc..." && cd ydb-go-examples && \
go run ./basic -ydb="grpcs://ydb.example.com:2135?database=/somepath/somelocation" )
Initializing a database connection
To interact with YDB, create instances of the driver, client, and session:
- The YDB driver facilitates interaction between the app and YDB nodes at the transport layer. It must be initialized before creating a client or session and must persist throughout the YDB access lifecycle.
- The YDB client operates on top of the YDB driver and enables the handling of entities and transactions.
- The YDB session, which is part of the YDB client context, contains information about executed transactions and prepared queries.
To work with YDB in Go
, import the ydb-go-sdk
driver package:
import (
// general imports from standard library
"context"
"log"
"path"
// importing the packages ydb-go-sdk
"github.com/ydb-platform/ydb-go-sdk/v3"
"github.com/ydb-platform/ydb-go-sdk/v3/table" // needed to work with table service
"github.com/ydb-platform/ydb-go-sdk/v3/table/options" // needed to work with table service
"github.com/ydb-platform/ydb-go-sdk/v3/table/result" // needed to work with table service
"github.com/ydb-platform/ydb-go-sdk/v3/table/result/named" // needed to work with table service
"github.com/ydb-platform/ydb-go-sdk/v3/table/types" // needed to work with YDB types and values
"github.com/ydb-platform/ydb-go-sdk-auth-environ" // needed to authenticate using environment variables
"github.com/ydb-platform/ydb-go-yc" // to work with YDB in Yandex Cloud
)
App code snippet for driver initialization:
ctx := context.Background()
// connection string
dsn := "grpcs://ydb.serverless.yandexcloud.net:2135/?database=/ru-central1/b1g8skpblkos03malf3s/etn01f8gv9an9sedo9fu"
// IAM token
token := "t1.9euelZrOy8aVmZKJm5HGjceMkMeVj-..."
// create a connection object called db, it is an entry point for YDB services
db, err := ydb.Open(ctx, dsn,
// yc.WithInternalCA(), // use Yandex Cloud certificates
ydb.WithAccessTokenCredentials(token), // authenticate using the token
// ydb.WithAnonimousCredentials(), // authenticate anonymously (for example, using docker ydb)
// yc.WithMetadataCredentials(token), // authenticate from inside a VM in Yandex Cloud or Yandex Function
// yc.WithServiceAccountKeyFileCredentials("~/.ydb/sa.json"), // authenticate in Yandex Cloud using a service account file
// environ.WithEnvironCredentials(ctx), // authenticate using environment variables
)
if err != nil {
// handle a connection error
}
// driver must be closed when done
defer db.Close(ctx)
The db
object serves as the entry point for working with YDB services. To interact with the table service, use the db.Table()
client. This client provides an API
for making queries to tables. The most commonly used method is db.Table().Do(ctx, op)
, which handles session creation in the background and retries executing the specified op
operation, passing the created session to the user's code.
The session has a comprehensive API
that allows you to perform DDL
, DML
, DQL
, and TCL
requests.
Creating tables
Create tables to be used in operations on a test app. This step results in the creation of database tables for the series directory data model:
Series
Seasons
Episodes
After the tables are created, a method for retrieving information about data schema objects is called, and the result of its execution is displayed.
To create tables, use the table.Session.CreateTable()
method:
err = db.Table().Do(ctx,
func(ctx context.Context, s table.Session) (err error) {
return s.CreateTable(ctx, path.Join(db.Name(), "series"),
options.WithColumn("series_id", types.TypeUint64), // not null column
options.WithColumn("title", types.Optional(types.TypeUTF8)),
options.WithColumn("series_info", types.Optional(types.TypeUTF8)),
options.WithColumn("release_date", types.Optional(types.TypeDate)),
options.WithColumn("comment", types.Optional(types.TypeUTF8)),
options.WithPrimaryKeyColumn("series_id"),
)
},
)
if err != nil {
// handling query execution failure
}
You can use the table.Session.DescribeTable()
method to print information about the table structure and make sure that it was properly created:
err = db.Table().Do(ctx,
func(ctx context.Context, s table.Session) (err error) {
desc, err := s.DescribeTable(ctx, path.Join(db.Name(), "series"))
if err != nil {
return
}
log.Printf("> describe table: %s\n", tableName)
for _, c := range desc.Columns {
log.Printf(" > column, name: %s, %s\n", c.Type, c.Name)
}
return
},
)
if err != nil {
// handling query execution failure
}
Retrieving data
Retrieve data using a SELECT
statement in YQL. Handle the retrieved data selection in the app.
To execute YQL queries, use the table.Session.Execute()
method.
The SDK lets you explicitly control the execution of transactions and configure the transaction execution mode using the table.TxControl
structure.
var (
readTx = table.TxControl(
table.BeginTx(
table.WithOnlineReadOnly(),
),
table.CommitTx(),
)
)
err := db.Table().Do(ctx,
func(ctx context.Context, s table.Session) (err error) {
var (
res result.Result
id uint64 // a variable for required results
title *string // a pointer for optional results
date *time.Time // a pointer for optional results
)
_, res, err = s.Execute(
ctx,
readTx,
`
DECLARE $seriesID AS Uint64;
SELECT
series_id,
title,
release_date
FROM
series
WHERE
series_id = $seriesID;
`
, table.NewQueryParameters(
table.ValueParam("$seriesID", types.Uint64Value(1)), // insert into the query criteria
),
)
if err != nil {
return err
}
defer res.Close() // result must be closed
log.Printf("> select_simple_transaction:\n")
for res.NextResultSet(ctx) {
for res.NextRow() {
// use ScanNamed to pass column names from the scan string,
// addresses (and data types) to be assigned the query results
err = res.ScanNamed(
named.Optional("series_id", &id),
named.Optional("title", &title),
named.Optional("release_date", &date),
)
if err != nil {
return err
}
log.Printf(
" > %d %s %s\n",
id, *title, *date,
)
}
}
return res.Err()
},
)
if err != nil {
// handle a query execution error
}
Scan queries
Execute a scan query to produce a data stream. Streaming allows to read an unlimited number of rows and an unlimited amount of data.
To execute scan queries, use the table.Session.StreamExecuteScanQuery()
method.
var (
query = `
DECLARE $series AS List<UInt64>;
SELECT series_id, season_id, title, first_aired
FROM seasons
WHERE series_id IN $series
`
res result.StreamResult
)
err = c.Do(ctx,
func(ctx context.Context, s table.Session) (err error) {
res, err = s.StreamExecuteScanQuery(ctx, query,
table.NewQueryParameters(
table.ValueParam("$series",
types.ListValue(
types.Uint64Value(1),
types.Uint64Value(10),
),
),
),
)
if err != nil {
return err
}
defer res.Close() // be sure to close the result
var (
seriesID uint64
seasonID uint64
title string
date time.Time
)
log.Print("\n> scan_query_select:")
for res.NextResultSet(ctx) {
if err = res.Err(); err != nil {
return err
}
for res.NextRow() {
// named.OptionalWithDefault enables you to "deploy" optional
// results or use the default type value in Go
err = res.ScanNamed(
named.Required("series_id", &seriesID),
named.OptionalWithDefault("season_id", &seasonID),
named.OptionalWithDefault("title", &title),
named.OptionalWithDefault("first_aired", &date),
)
if err != nil {
return err
}
log.Printf("# Season, SeriesId: %d, SeasonId: %d, Title: %s, Air date: %s", seriesID, seasonID, title, date)
}
}
return res.Err()
},
)
if err != nil {
// handling a query execution error
}