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
}
Previous
Next