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 instructions below assume that Git and Go are installed. 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-sdk.git
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-sdk/examples && \
go run ./basic/native/query -ydb="grpc://localhost:2136/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-sdk/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-sdk/examples && \
go run ./basic -ydb="grpcs://ydb.example.com:2135/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 (
"context"
"log"
"path"
"github.com/ydb-platform/ydb-go-sdk/v3"
"github.com/ydb-platform/ydb-go-sdk/v3/query"
)
To interact with YDB, it is necessary to create a YDB driver:
db, err := ydb.Open(context.Background(), "grpc://localhost:2136/local")
if err != nil {
// handle connection error
}
// You should close the driver when your application finishes its work (for example, when exiting the program).
defer db.Close(context.Background())
The ydb.Open method returns a driver instance if successful. The driver performs several services, such as YDB cluster discovery and client-side load balancing.
The ydb.Open method takes two mandatory arguments:
- a context
- a YDB connection string
There are also many connection options available that let you override the default settings.
By default, anonymous authentication is used. To connect to the YDB cluster using a token, use the following syntax:
db, err := ydb.Open(context.Background(), clusterEndpoint,
ydb.WithAccessTokenCredentials(token),
)
You can see the full list of auth providers in the ydb-go-sdk documentation and on the recipes page.
It is necessary to close the driver at the end of work to clean up resources.
defer db.Close(ctx)
The db struct is the entry point for working with YDB. To query tables, use the db.Query() query service:
YQL queries are executed within special objects called query.Session. Sessions store the execution context of queries (for example, transactions) and provide server-side load balancing among the YDB cluster nodes.
The query service client provides an API for executing queries:
db.Query().Do(ctx, op)creates sessions in the background and automatically retries the providedop func(ctx context.Context, s query.Session) erroroperation if necessary. As soon as a session is ready, it is passed to the callback.db.Query().DoTx(ctx, op)automatically handles the transaction lifecycle. It provides a prepared transaction object,query.TxActor, to the user-defined functionop func(ctx context.Context, tx query.TxActor)error. If the operation returns without an error (nil), the transaction commits automatically. If the operation returns an error, the transaction rolls back automatically.db.Query().Execruns a single query that returns no result, with automatic retry logic on failure. This method returns nil if the execution is successful or an error otherwise.db.Query().Queryexecutes a single query containing one or more statements that return a result. It automatically handles retries. Upon successful execution, it returns a fully materialized result (query.Result). All result rows are loaded into memory and available for immediate iteration. For queries returning large datasets, this may lead to an out of memory problem.db.Query().QueryResultSetexecutes a query that contains exactly one statement returning results (it may contain other auxiliary statements that return no results, such asUPSERT). Likedb.Query().Query, it automatically retries failed operations and returns a fully materialized result set (query.ResultSet). Queries that return large datasets may cause an OOM error.db.Query().QueryRowruns queries expected to return exactly one row. It also automatically retries failed operations. On success, it returns aquery.Rowinstance.
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:
SeriesSeasonsEpisodes
After the tables are created, a method for retrieving information about data schema objects is called, and the result of its execution is displayed.
Example of a query with no returned result (table creation):
import "github.com/ydb-platform/ydb-go-sdk/v3/query"
err = db.Query().Exec(ctx, `
CREATE TABLE IF NOT EXISTS series (
series_id Bytes,
title Text,
series_info Text,
release_date Date,
comment Text,
PRIMARY KEY(series_id)
)`, query.WithTxControl(query.NoTx()),
)
if err != nil {
// handle query execution error
}
Retrieving data
Retrieve data using a SELECT statement in YQL. Handle the retrieved data selection in the app.
To execute YQL queries and fetch results, use query.Session methods: query.Session.Query, query.Session.QueryResultSet, or query.Session.QueryRow.
The YDB SDK supports explicit transaction control via the query.TxControl structure:
readTx := query.TxControl(
query.BeginTx(
query.WithSnapshotReadOnly(),
),
query.CommitTx(),
)
row, err := db.Query().QueryRow(ctx,`
DECLARE $seriesID AS Uint64;
SELECT
series_id,
title,
release_date
FROM
series
WHERE
series_id = $seriesID;`,
query.WithParameters(
ydb.ParamsBuilder().Param("$seriesID").Uint64(1).Build(),
),
query.WithTxControl(readTx),
)
if err != nil {
// handle query execution error
}
You can extract row data (query.Row) using the following methods:
query.Row.ScanStruct— scans row data into a struct based on struct field tags that match column names.query.Row.ScanNamed— scans data into variables using explicitly defined column-variable pairs.query.Row.Scan— scans data directly by column order into the provided variables.
var info struct {
SeriesID string `sql:"series_id"`
Title string `sql:"title"`
ReleaseDate time.Time `sql:"release_date"`
}
err = row.ScanStruct(&info)
if err != nil {
// handle query execution error
}
var seriesID, title string
var releaseDate time.Time
err = row.ScanNamed(query.Named("series_id", &seriesID), query.Named("title", &title), query.Named("release_date", &releaseDate))
if err != nil {
// handle query execution error
}
var seriesID, title string
var releaseDate time.Time
err = row.Scan(&seriesID, &title, &releaseDate)
if err != nil {
// handle 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.
Warning
If the expected query result is very large, avoid loading all data into memory using helper methods like query.Client.Query or query.Client.QueryResultSet. These methods return fully materialized results, storing all rows from the server in local client memory. Large result sets can cause an OOM problem.
Instead, use the query.TxActor.Query or query.TxActor.QueryResultSet methods on a transaction or session. These methods return iterators over results without fully materializing them upfront. The query.Session object is accessible via the query.Client.Do method, which handles automatic retries. Keep in mind that the read operation can be interrupted at any time, restarting the entire query process. Therefore, the user function passed to Do may run multiple times.
err = db.Query().Do(ctx,
func(ctx context.Context, s query.Session) error {
rows, err := s.QueryResultSet(ctx,`
SELECT series_id, season_id, title, first_aired
FROM seasons`,
)
if err != nil {
return err
}
defer rows.Close(ctx)
for row, err := range rows.Rows(ctx) {
if err != nil {
return err
}
var info struct {
SeriesID string `sql:"series_id"`
SeasonID string `sql:"season_id"`
Title string `sql:"title"`
FirstAired time.Time `sql:"first_aired"`
}
err = row.ScanStruct(&info)
if err != nil {
return err
}
fmt.Printf("%+v\n", info)
}
return nil
},
query.WithIdempotent(),
)
if err != nil {
// handle query execution error
}