Приложение на Go

На этой странице подробно разбирается код тестового приложения, использующего Go SDK YDB.

Скачивание и запуск

Приведенный ниже сценарий запуска использует git и Go. Предварительно должен быть установлен YDB Go SDK.

Создайте рабочую директорию и выполните в ней из командной строки команду клонирования репозитория с GitHub:

git clone https://github.com/ydb-platform/ydb-go-examples/

Далее из этой же рабочей директории выполните команду запуска тестового приложения, которая будет отличаться в зависимости от того, к какой базе данных необходимо подключиться.

Для соединения с развернутой локальной базой данных YDB по сценарию Docker в конфигурации по умолчанию выполните следующую команду:

( export YDB_ANONYMOUS_CREDENTIALS=1 && cd ydb-go-examples && \
go run ./basic -ydb="grpc://localhost:2136?database=/local" )

Для выполнения примера с использованием любой доступной базы данных YDB вам потребуется знать эндпоинт и путь базы данных.

Если в базе данных включена аутентификация, то вам также понадобится выбрать режим аутентификации и получить секреты - токен или логин/пароль.

Выполните команду по следующему образцу:

( export <auth_mode_var>="<auth_mode_value>" && cd ydb-go-examples && \
go run ./basic -ydb="<endpoint>?database=<database>" )

, где

Например:

( export YDB_ACCESS_TOKEN_CREDENTIALS="t1.9euelZqOnJuJlc..." && cd ydb-go-examples && \
go run ./basic -ydb="grpcs://ydb.example.com:2135?database=/somepath/somelocation" )

Инициализация соединения с базой данных

Для взаимодействия с YDB создается экземпляр драйвера, клиента и сессии:

  • Драйвер YDB отвечает за взаимодействие приложения и YDB на транспортном уровне. Драйвер должен существовать на всем протяжении жизненного цикла работы с YDB и должен быть инициализирован перед созданием клиента и сессии.
  • Клиент YDB работает поверх драйвера YDB и отвечает за работу с сущностями и транзакциями.
  • Сессия YDB содержит информацию о выполняемых транзакциях и подготовленных запросах и содержится в контексте клиента YDB.

Для работы с YDB в go следует импортировать пакет драйвера ydb-go-sdk:

import (
  // общие импорты из стандартной библиотеки
  "context"
  "log"
  "path"

  // импорты пакетов ydb-go-sdk
  "github.com/ydb-platform/ydb-go-sdk/v3"
  "github.com/ydb-platform/ydb-go-sdk/v3/table" // для работы с table-сервисом
  "github.com/ydb-platform/ydb-go-sdk/v3/table/options" // для работы с table-сервисом
  "github.com/ydb-platform/ydb-go-sdk/v3/table/result" // для работы с table-сервисом
  "github.com/ydb-platform/ydb-go-sdk/v3/table/result/named" // для работы с table-сервисом
  "github.com/ydb-platform/ydb-go-sdk/v3/table/types" // для работы с типами YDB и значениями
  "github.com/ydb-platform/ydb-go-sdk-auth-environ" // для аутентификации с использованием перменных окружения
  "github.com/ydb-platform/ydb-go-yc" // для работы с YDB в Яндекс Облаке
)

Фрагмент кода приложения для инициализации драйвера:

ctx := context.Background()
// строка подключения
dsn := "grpcs://ydb.serverless.yandexcloud.net:2135/?database=/ru-central1/b1g8skpblkos03malf3s/etn01f8gv9an9sedo9fu"
// IAM-токен
token := "t1.9euelZrOy8aVmZKJm5HGjceMkMeVj-..."
// создаем объект подключения db, является входной точкой для сервисов YDB
db, err := ydb.Open(ctx, dsn,
//  yc.WithInternalCA(), // используем сертификаты Яндекс Облака
  ydb.WithAccessTokenCredentials(token), // аутентификация с помощью токена
//  ydb.WithAnonimousCredentials(), // анонимная аутентификация (например, в docker ydb)
//  yc.WithMetadataCredentials(token), // аутентификация изнутри виртуальной машины в Яндекс Облаке или из Яндекс Функции
//  yc.WithServiceAccountKeyFileCredentials("~/.ydb/sa.json"), // аутентификация в Яндекс Облаке с помощью файла сервисного аккаунта
//  environ.WithEnvironCredentials(ctx), // аутентификация с использованием переменных окружения
)
if err != nil {
  // обработка ошибки подключения
}
// закрытие драйвера по окончании работы программы обязательно
defer db.Close(ctx)

Объект db является входной точкой для работы с сервисами YDB.
Для работы сервисом таблиц следует использовать клиента table-сервиса db.Table().
Клиент table-сервиса предоставляет API для выполнения запросов над таблицами.
Наиболее востребован метод db.Table().Do(ctx, op), который реализует фоновое создание сессий и повторные попытки выполнить пользовательскую операцию op, в которую пользовательскому коду отдается подготовленная сессия.
Сессия имеет исчерпывающее API, позволяющее выполнять DDL, DML, DQL и TCL запросы.

Создание строковых таблиц

Выполняется создание строковых таблиц, которые используются в дальнейших операциях тестового приложения. В результате исполнения шага в базе данных будут созданы строковые таблицы модели данных справочника сериалов:

  • series - Сериалы
  • seasons - Сезоны
  • episodes - Эпизоды

После создания вызывается метод получения информации об объекте схемы данных, и выводится результат его выполнения.

Для создания строковых таблиц используется метод table.Session.CreateTable():

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 {
  // обработка ситуации, когда не удалось выполнить запрос
}

Метод table.Session.CreateTable() не позволяет создавать колоночные таблицы. Это можно сделать с помощью метода table.Session.Execute(), который выполняет YQL-запросы.

Если вы создали строковую таблицу и хотите вывести информацию о её структуре и убедиться, что она успешно создалась, воспользуйтесь методом table.Session.DescribeTable():

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 {
  // обработка ситуации, когда не удалось выполнить запрос
}

Получение выборки данных

Выполняется запрос на получение выборки данных с использованием команды SELECT языка запросов YQL. Демонстрируется обработка полученной выборки в приложении.

Для выполнения YQL-запросов используется метод table.Session.Execute().
SDK позволяет в явном виде контролировать выполнение транзакций и настраивать необходимый режим выполнения транзакций с помощью структуры table.TxControl.

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 // переменная для required результатов
      title *string // указатель - для опциональных результатов
      date  *time.Time // указатель - для опциональных результатов
    )
    _, 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)), // подстановка в условие запроса
      ),
    )
    if err != nil {
      return err
    }
    defer res.Close() // закрытие result'а обязательно
    log.Printf("> select_simple_transaction:\n")
    for res.NextResultSet(ctx) {
      for res.NextRow() {
        // в ScanNamed передаем имена колонок из строки сканирования,
        // адреса (и типы данных), куда следует присвоить результаты запроса
        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 {
  // обработка ошибки выполнения запроса
}

Скан запросы

Выполняется скан запрос данных, результатом исполнения которого является стрим. Стрим позволяет считать неограниченное количество строк и объем данных.

Для выполнения scan-запросов используется метод table.Session.StreamExecuteScanQuery().

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() // закрытие 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 позволяет "развернуть" опциональные
        // результаты или использовать дефолтное значение типа 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 {
  // обработка ошибки выполнения запроса
}
Предыдущая
Следующая