Генерация кода из YQL (sqlc-ydb)

sqlc-ydb генерирует типизированный код для YDB по схеме и запросам на YQL. Вы пишете SQL, а утилита создаёт методы, передающие параметры и читающие результаты через выбранный SDK или драйвер. Это самостоятельный проект со знакомым подходом sqlc.

Ниже показан путь от схемы и двух запросов до сгенерированного кода и его вызовов в приложении.

Установка

На Linux и macOS выполните команды ниже. Генерация не требует SDK или подключения к базе. Установщик выбирает последнюю стабильную версию, проверяет SHA256 и помещает бинарник в ~/.local/bin. Если нужна настройка PATH, выполните напечатанную установщиком инструкцию перед sqlc-ydb version.

curl -fsSL https://raw.githubusercontent.com/ydb-platform/sqlc-ydb/main/install.sh | bash
sqlc-ydb version

Для Windows скачайте подходящий архив со страницы последнего стабильного релиза. Другие способы установки и выбор конкретной версии описаны в инструкции установки.

Схема и запросы

Создайте пустой каталог проекта и сохраните общую для всех языков схему в schema.sql:

CREATE TABLE authors (
    id Uint64 NOT NULL,
    name Utf8 NOT NULL,
    PRIMARY KEY (id)
);

В queries.sql сохраните два запроса. UpsertAuthor добавляет автора или обновляет имя существующего, а GetAuthor читает запись по ключу. Имена параметров начинаются с $; их типы выводятся из схемы, поэтому DECLARE здесь не нужен.

-- name: UpsertAuthor :exec
UPSERT INTO authors (id, name) VALUES ($author_id, $author_name);

-- name: GetAuthor :one
SELECT id, name FROM authors WHERE id = $author_id;

Комментарий -- name: задаёт имя метода. :exec означает выполнение без строк результата, :one — получение одной строки. Все примеры ниже используют именно эти файлы.

1. Настройка генератора в sqlc.yaml

Сохраните конфигурацию из выбранной вкладки в sqlc.yaml. Пути входных файлов и каталога out вычисляются относительно этого файла. Список языков и фреймворков, доступных в установленной версии, показывает команда:

sqlc-ydb init --help

Выберите язык и runtime из вывода этой команды, затем запросите справку по параметрам генератора:

sqlc-ydb init --language <language> --runtime <runtime> --help

Замените <language> и <runtime> значениями из справки. Команда покажет назначение параметров, их типы, допустимые значения и значения по умолчанию. Если не указывать --runtime, справка будет использовать runtime по умолчанию для выбранного языка. Вызов с --help не создаёт файлов.

Вкладки ниже показывают примеры настройки. Актуальный список поддерживаемых языков, runtime и параметров предоставляет справка установленной версии sqlc-ydb.

version: "2"
sql:
  - engine: ydb
    schema: schema.sql
    queries: queries.sql
    gen:
      cpp:
        out: "cpp/ydb"
        namespace: "authors"
        runtime: "ydb"
version: "2"
sql:
  - engine: ydb
    schema: schema.sql
    queries: queries.sql
    gen:
      cpp:
        out: "cpp/userver"
        namespace: "authors"
        runtime: "userver"
version: "2"
sql:
  - engine: ydb
    schema: schema.sql
    queries: queries.sql
    gen:
      go:
        out: "go/native"
        package: "authors"
        sql_package: "ydb"
version: "2"
sql:
  - engine: ydb
    schema: schema.sql
    queries: queries.sql
    gen:
      go:
        out: "go/sql"
        package: "authors"
        sql_package: "database/sql"
version: "2"
sql:
  - engine: ydb
    schema: schema.sql
    queries: queries.sql
    gen:
      java:
        out: "java/ydb"
        package: "authors"
        runtime: "ydb"
version: "2"
sql:
  - engine: ydb
    schema: schema.sql
    queries: queries.sql
    gen:
      java:
        out: "java/jdbc"
        package: "authors"
        runtime: "jdbc"
version: "2"
sql:
  - engine: ydb
    schema: schema.sql
    queries: queries.sql
    gen:
      java:
        out: "java/jooq"
        package: "authors"
        runtime: "jooq"
version: "2"
sql:
  - engine: ydb
    schema: schema.sql
    queries: queries.sql
    gen:
      python:
        out: "python/ydb"
        runtime: "ydb"
version: "2"
sql:
  - engine: ydb
    schema: schema.sql
    queries: queries.sql
    gen:
      python:
        out: "python/dbapi"
        runtime: "dbapi"
version: "2"
sql:
  - engine: ydb
    schema: schema.sql
    queries: queries.sql
    gen:
      python:
        out: "python/sqlalchemy"
        runtime: "sqlalchemy"
version: "2"
sql:
  - engine: ydb
    schema: schema.sql
    queries: queries.sql
    gen:
      csharp:
        out: "csharp/adonet"
        namespace: "Authors"
        runtime: "adonet"
version: "2"
sql:
  - engine: ydb
    schema: schema.sql
    queries: queries.sql
    gen:
      csharp:
        out: "csharp/dapper"
        namespace: "Authors"
        runtime: "dapper"
version: "2"
sql:
  - engine: ydb
    schema: schema.sql
    queries: queries.sql
    gen:
      typescript:
        out: "typescript"
        runtime: "ydb"
version: "2"
sql:
  - engine: ydb
    schema: schema.sql
    queries: queries.sql
    gen:
      rust:
        out: "rust"
        runtime: "ydb"
version: "2"
sql:
  - engine: ydb
    schema: schema.sql
    queries: queries.sql
    gen:
      php:
        out: "php"
        namespace: "Authors"
        runtime: "ydb"
version: "2"
sql:
  - engine: ydb
    schema: schema.sql
    queries: queries.sql
    gen:
      kotlin:
        out: "kotlin/ydb"
        package: "authors"
        runtime: "ydb"
version: "2"
sql:
  - engine: ydb
    schema: schema.sql
    queries: queries.sql
    gen:
      kotlin:
        out: "kotlin/jdbc"
        package: "authors"
        runtime: "jdbc"
version: "2"
sql:
  - engine: ydb
    schema: schema.sql
    queries: queries.sql
    gen:
      kotlin:
        out: "kotlin/exposed"
        package: "authors"
        runtime: "exposed"

2. Генерация кода

В каталоге с sqlc.yaml выполните команды. compile проверяет входные данные, generate записывает код, а diff проверяет, что сохранённый результат совпадает с генерацией. Последнюю команду можно включить в CI: при различиях она возвращает код 1.

sqlc-ydb compile
sqlc-ydb generate
sqlc-ydb diff

Ниже — фрагменты реального результата генерации из общего запроса: тип строки или метод чтения. Импорты и окружающие объявления классов опущены. Используйте полные файлы из каталога out; не редактируйте их вручную.

struct GetAuthorRow final {
    std::uint64_t id;
    std::string name;
};

    void UpsertAuthor(std::uint64_t author_id, const std::string& author_name) const;
    std::optional<GetAuthorRow> GetAuthor(std::uint64_t author_id) const;
struct GetAuthorRow final {
    std::uint64_t id;
    ::userver::ydb::Utf8 name;
};

    void UpsertAuthor(std::uint64_t author_id, const ::userver::ydb::Utf8& author_name) const;
    std::optional<GetAuthorRow> GetAuthor(std::uint64_t author_id) const;
// -- name: GetAuthor :one
func (q *Queries) GetAuthor(ctx context.Context, arg uint64, opts ...query.ExecuteOption) (GetAuthorRow, error) {
    parameters := ydb.ParamsBuilder()
    parameters = parameters.Param("$author_id").Uint64(arg)

    callOptions := append([]query.ExecuteOption(nil), opts...)
    callOptions = append(callOptions, query.WithParameters(parameters.Build()))

    result, err := q.db.QueryRow(ctx, ""+
        "SELECT id, name FROM authors WHERE id = $author_id;",
        callOptions...,
    )
    if err != nil {
        return GetAuthorRow{}, err
    }

    var row GetAuthorRow
    if err := result.ScanNamed(
        query.Named("id", &row.ID),
        query.Named("name", &row.Name),
    ); err != nil {
        return GetAuthorRow{}, err
    }

    return row, nil
}
// -- name: GetAuthor :one
func (q *Queries) GetAuthor(ctx context.Context, arg uint64) (GetAuthorRow, error) {
    var row GetAuthorRow
    err := q.db.QueryRowContext(ctx, ""+
        "SELECT id, name FROM authors WHERE id = $author_id;",
        sql.Named("author_id", arg),
    ).Scan(
        &row.ID,
        &row.Name,
    )

    return row, err
}
public record GetAuthorRow(long id, String name) {}

    // -- name: GetAuthor :one
    public java.util.Optional<GetAuthorRow> getAuthor(long authorId) {
        var _params = Params.create();
        _params.put("$author_id", PrimitiveValue.newUint64(authorId));
        var _query = QueryReader.readFrom(
                client.createQuery("""
                    SELECT id, name FROM authors WHERE id = $author_id;\
                    """, _params)).join().getValue();
        if (_query.getResultSetCount() != 1) throw new IllegalStateException("Expected one result set");
        var _rows = _query.getResultSet(0);
        if (!_rows.next()) return java.util.Optional.empty();
        long _value0 = _rows.getColumn(0).getUint64();
        String _value1 = _rows.getColumn(1).getText();
        return java.util.Optional.of(new GetAuthorRow(_value0, _value1));
    }
public record GetAuthorRow(long id, String name) {}

    // -- name: GetAuthor :one
    public java.util.Optional<GetAuthorRow> getAuthor(long authorId) throws java.sql.SQLException {
        try (var _prepared = client.prepareStatement("""
            SELECT id, name FROM authors WHERE id = ?;\
            """)) {
            _prepared.setObject(1, PrimitiveValue.newUint64(authorId));
            try (var _rows = _prepared.executeQuery()) {
                if (!_rows.next()) return java.util.Optional.empty();
                long _value0 = _rows.getLong(1);
                String _value1 = _rows.getString(2);
                return java.util.Optional.of(new GetAuthorRow(_value0, _value1));
            }
        }
    }
public record GetAuthorRow(ULong id, String name) {}

    // -- name: GetAuthor :one
    public Optional<GetAuthorRow> getAuthor(ULong authorId) {
        return dsl.select(AUTHORS.ID, AUTHORS.NAME)
                .from(AUTHORS)
                .where(AUTHORS.ID.eq(val(authorId, YdbTypes.UINT64)))
                .fetchOptional(mapping(GetAuthorRow::new));
    }
@dataclass
class Authors:
    id: int
    name: str
@dataclass
class Authors:
    id: int
    name: str
@dataclass
class Authors:
    id: int
    name: str
public sealed record GetAuthorRow(
    ulong ID,
    string Name
);

    // -- name: GetAuthor :one
    public async Task<GetAuthorRow> GetAuthorAsync(ulong authorId, CancellationToken cancellationToken = default)
    {
        await using var command = new YdbCommand(
            "SELECT id, name FROM authors WHERE id = $author_id;", _connection) { Transaction = _transaction };
        command.Parameters.Add(new YdbParameter("$author_id", DbType.UInt64, authorId));
        await using var reader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
        if (!await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
        {
            throw new InvalidOperationException("query returned no rows");
        }
        return GetAuthorRowFrom(reader);
    }

    private static GetAuthorRow GetAuthorRowFrom(DbDataReader reader) => new(
        reader.GetFieldValue<ulong>(0),
        reader.GetFieldValue<string>(1)
    );
public sealed record GetAuthorRow(
    ulong ID,
    string Name
);

    // -- name: GetAuthor :one
    public async Task<GetAuthorRow> GetAuthorAsync(ulong authorId, CancellationToken cancellationToken = default, int? commandTimeout = null)
    {
        var parameters = new YdbParameters(
            new YdbParameter("$author_id", DbType.UInt64, authorId)
        );

        var command = new CommandDefinition(
            commandText: """
            SELECT id, name FROM authors WHERE id = $author_id;
            """,
            parameters: parameters,
            transaction: _transaction,
            commandTimeout: commandTimeout,
            cancellationToken: cancellationToken);

        return await _connection.QueryFirstAsync<GetAuthorRow>(command).ConfigureAwait(false);
    }
// -- name: GetAuthor :one
  async getAuthor(authorId: bigint, configure?: ConfigureQuery): Promise<GetAuthorRow | null> {
    const stmt = this.#sql<[GetAuthorRow]>`SELECT id, name FROM authors WHERE id = $author_id;`
      .parameter("author_id", new Uint64(authorId));
    configure?.(stmt);
    const [rows] = await stmt;

    return rows[0] ?? null;
  }
// -- name: GetAuthor :one
    #[builder(on(String, into))]
    pub async fn author(&mut self, author_id: u64) -> ydb::YdbResult<GetAuthorRow> {
        let mut row = self
            .client
            .query_row(r"SELECT id, name FROM authors WHERE id = $author_id;")
            .param("$author_id", author_id)
            .await?;
        Ok(GetAuthorRow {
            id: row.remove_field(0)?.try_into()?,
            name: row.remove_field(1)?.try_into()?,
        })
    }
final class GetAuthorRow
{
    public function __construct(
        public readonly string $id,
        public readonly string $name
    ) {}
}
data class GetAuthorRow(
    val id: Long,
    val name: String
)

    // -- name: GetAuthor :one
    fun getAuthor(authorId: Long): GetAuthorRow? {
        val _params = Params.create()
        _params.put("\$author_id", PrimitiveValue.newUint64(authorId))
        val _query = if (transaction != null) {
            QueryReader.readFrom(transaction.createQuery(
                "SELECT id, name FROM authors WHERE id = \$author_id;", _params)).join().getValue()
        } else {
            client!!.supplyResult { _session ->
                QueryReader.readFrom(_session.createQuery(
                "SELECT id, name FROM authors WHERE id = \$author_id;", TxMode.SERIALIZABLE_RW, _params))
            }.join().getValue()
        }
        kotlin.check(_query.getResultSetCount() == 1) { "Expected one result set" }
        val _rows = _query.getResultSet(0)
        if (!_rows.next()) return null
        val _value0: Long = _rows.getColumn(0).getUint64()
        val _value1: String = _rows.getColumn(1).getText()
        return GetAuthorRow(_value0, _value1)
    }
data class GetAuthorRow(
    val id: Long,
    val name: String
)

    // -- name: GetAuthor :one
    fun getAuthor(authorId: Long): GetAuthorRow? {
        client.prepareStatement(
            "SELECT id, name FROM authors WHERE id = ?;").use { _prepared ->
            _prepared.setObject(1, PrimitiveValue.newUint64(authorId))
            _prepared.executeQuery().use { _rows ->
                if (!_rows.next()) return null
                val _value0: Long = _rows.getLong(1)
                val _value1: String = _rows.getString(2)
                return GetAuthorRow(_value0, _value1)
            }
        }
    }
data class GetAuthorRow(
    val id: Long,
    val name: String
)

    // -- name: GetAuthor :one
    fun getAuthor(authorId: Long): GetAuthorRow? {
        val _connection = client.connection.connection as java.sql.Connection
        _connection.prepareStatement(
            "SELECT id, name FROM authors WHERE id = ?;").use { _prepared ->
            _prepared.setObject(1, PrimitiveValue.newUint64(authorId))
            _prepared.executeQuery().use { _rows ->
                if (!_rows.next()) return null
                val _value0: Long = _rows.getLong(1)
                val _value1: String = _rows.getString(2)
                return GetAuthorRow(_value0, _value1)
            }
        }
    }

Подготовка приложения

Генератор не создаёт таблицы в базе. Примените schema.sql через принятый в проекте механизм миграций или YDB CLI, а для примера одиночного чтения добавьте запись:

UPSERT INTO authors (id, name) VALUES (42, "Alice");

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

3. Выполнение отдельных запросов

Вызовите сгенерированный метод для автора с идентификатором 42:

authors::Queries queries{client};
auto author = queries.GetAuthor(42);

client — настроенный NYdb::NQuery::TQueryClient; нужны C++20 и YDB C++ SDK. Одиночный вызов использует SDK retry; RetryQuerySync повторяет всю транзакцию. Queries не завершает переданную транзакцию: пример явно делает commit или rollback. Полный пример.

authors::Queries queries{table_client};
auto author = queries.GetAuthor(42);

table_clientuserver::ydb::TableClient из компонента YDB; код выполняется в корутине userver. RetryTx повторяет весь callback и завершает транзакцию по TxAction; kRollback позволяет отменить изменения. Utf8 — строковый тип userver для YQL Utf8. Полный пример.

q := authors.New(driver.Query())
author, err := q.GetAuthor(ctx, 42)
if err != nil {
    return err
}
fmt.Println(author.Name)

driver — открытый *ydb.Driver, ctx — контекст. Импортируйте сгенерированный пакет как authors, а для транзакции — github.com/ydb-platform/ydb-go-sdk/v3/query. Фрагменты находятся в функции, возвращающей error.

q := authors.New(connection)
author, err := q.GetAuthor(ctx, 42)
if err != nil {
    return err
}
fmt.Println(author.Name)

connection — открытый *sql.DB с драйвером YDB, ctx — контекст. Импортируйте сгенерированный пакет как authors. Фрагменты находятся в функции, возвращающей error; при повторе выполните весь транзакционный блок заново.

GetAuthorRow author = retry.supplyResult(session -> {
    var tx = session.createNewTransaction(TxMode.SERIALIZABLE_RW);
    try {
        var row = new Queries(tx).getAuthor(42L).orElseThrow();
        return CompletableFuture.completedFuture(tx.commit().join().map(info -> row));
    } finally {
        if (tx.isActive()) {
            tx.rollback().join().expectSuccess();
        }
    }
}).join().getValue();

retry имеет тип SessionRetryContext и настроен приложением; импорты: TxMode, CompletableFuture, сгенерированные Queries и GetAuthorRow. Даже одиночный вызов Java native использует открытую транзакцию. retry повторяет всю функцию с новой сессией; завершение транзакции остаётся в вызывающем коде.

GetAuthorRow author = new Queries(connection).getAuthor(42L).orElseThrow();

connection имеет тип java.sql.Connection, исходно работает в autocommit и не содержит другой транзакции. Импортированы сгенерированные Queries и GetAuthorRow. Код транзакции возвращает autocommit после commit или rollback; исключения передаются вызывающему коду. Повтор всей транзакции при необходимости организует приложение.

GetAuthorRow author = new Queries(dsl).getAuthor(ULong.valueOf(42)).orElseThrow();

dsl имеет тип YdbDSLContext; импорты: YDB, ULong, сгенерированные Queries и GetAuthorRow. Для отдельного вызова соединение работает в autocommit. transactionResult фиксирует изменения при успехе и откатывает при исключении; YDB.using(configuration) сохраняет соединение текущей транзакции. Повторы организует приложение вокруг всей транзакции.

import ydb
from python.ydb.queries import Querier

queries = Querier(pool, retry_settings=ydb.RetrySettings(idempotent=True))
author = queries.get_author(42)

pool — готовый ydb.QuerySessionPool. retry_tx_sync повторяет всю транзакцию и выполняет commit после callback; callback не должен иметь внешних побочных эффектов. В примере повторяемый UPSERT задаёт фиксированные значения. Запускаемый пример.

from python.dbapi.queries import Querier

author = Querier(connection).get_author(42)

connection — открытое соединение ydb_dbapi без активной транзакции. По умолчанию драйвер использует AUTOCOMMIT: для общей транзакции нужно выбрать SERIALIZABLE до begin(). Повторы всей транзакции организует вызывающий код. Запускаемый пример.

from python.sqlalchemy.queries import Querier

with engine.connect() as connection:
    author = Querier(connection).get_author(42)

engine — готовый SQLAlchemy Engine с диалектом yql+ydb. SERIALIZABLE задаётся до begin(): один transaction block не отменяет AUTOCOMMIT драйвера. Контекст выполняет commit или rollback; повторы всей транзакции организует вызывающий код. Запускаемый пример.

var author = await dataSource.ExecuteAsync(
    (connection, ct) => new Authors.Queries(connection).GetAuthorAsync(42UL, ct),
    cancellationToken);

Нужны .NET 8, Ydb.Sdk, настроенный YdbDataSource dataSource и CancellationToken cancellationToken. ExecuteAsync повторяет весь callback согласно политике data source. Для нескольких вызовов в одной транзакции используйте ExecuteInTransactionAsync: провайдер управляет коммитом, откатом и повтором всей транзакции. Полный пример. Если строка не найдена, GetAuthorAsync выбрасывает InvalidOperationException.

var author = await dataSource.ExecuteAsync(
    (connection, ct) => new Authors.Queries(connection).GetAuthorAsync(42UL, ct),
    cancellationToken);

Нужны .NET 8, Ydb.Sdk и Dapper, настроенный YdbDataSource dataSource и CancellationToken cancellationToken. ExecuteAsync повторяет весь callback согласно политике data source. Для нескольких вызовов в одной транзакции используйте ExecuteInTransactionAsync: провайдер управляет коммитом, откатом и повтором всей транзакции. Полный пример. Если строка не найдена, GetAuthorAsync выбрасывает InvalidOperationException.

import { Queries } from "./typescript/queries.js";

const queries = new Queries(sql);
const author = await queries.getAuthor(42n);
console.log(author?.name);

sql — функция query(driver) из @ydbjs/query для подготовленного драйвера. transaction фиксирует изменения после успешного callback и откатывает при ошибке.

use generated::queries::Queries;

let mut queries = Queries::new(&mut query_client);
let author = queries.author().author_id(42).call().await?;
println!("{}", author.name);

query_client — изменяемый ydb::QueryClient. Подключите файлы из rust/ как модуль generated и зависимости ydb и bon. SDK фиксирует транзакцию после успешного callback; повторяется весь callback.

use Authors\Queries;

$queries = new Queries($table, idempotent: true);
$author = $queries->getAuthor('42');

$table — готовый YdbPlatform\Ydb\Table; сгенерированные классы подключены через autoload. Uint64 передаётся десятичной строкой. Для этого SELECT безопасные повторы явно включены через idempotent: true. Запускаемый пример.

val author = requireNotNull(Queries(retry).getAuthor(42L))

retry имеет тип SessionRetryContext и настроен приложением; импорты: TxMode, CompletableFuture и сгенерированный Queries. Одиночный вызов использует retry-контекст, а составная операция передаёт хелперам одну QueryTransaction. Внешний retry повторяет всю транзакцию.

val author = requireNotNull(Queries(connection).getAuthor(42L))

connection имеет тип java.sql.Connection, исходно работает в autocommit и не содержит другой транзакции; импортирован сгенерированный Queries. Код транзакции возвращает autocommit после commit или rollback; исключения передаются вызывающему коду. Повтор всей транзакции при необходимости организует приложение.

val author = ydbTransaction(database) {
    requireNotNull(Queries(this).getAuthor(42L))
}

database представляет настроенный Exposed Database с зарегистрированным YDB-диалектом; импорты: tech.ydb.exposed.dialect.ydbTransaction и сгенерированный Queries. Блок ydbTransaction передаёт одну JdbcTransaction, фиксирует её при успехе и откатывает при исключении. Политика повторов применяется к блоку целиком.

4. Выполнение нескольких запросов в транзакции

В каждом примере два сгенерированных метода выполняют два YQL-запроса в одной транзакции: UpsertAuthor записывает имя, затем GetAuthor читает его. Приложение управляет границами транзакции и повторами; при повторе выполняйте весь блок. В callback, который SDK может повторить, не помещайте внешние побочные эффекты.

std::optional<authors::GetAuthorRow> author;
const auto status = client.RetryQuerySync([&](NYdb::NQuery::TSession session) -> NYdb::TStatus {
    auto started = session.BeginTransaction(
        NYdb::NQuery::TTxSettings::SerializableRW()).GetValueSync();
    if (!started.IsSuccess()) {
        return started;
    }
    auto transaction = started.GetTransaction();
    try {
        authors::Queries queries{transaction};
        queries.UpsertAuthor(42, "Alice");
        author = queries.GetAuthor(42);
    } catch (const NYdb::NStatusHelpers::TYdbErrorException& error) {
        transaction.Rollback().GetValueSync();
        return error.GetStatus();
    } catch (...) {
        transaction.Rollback().GetValueSync();
        throw;
    }
    return transaction.Commit().GetValueSync();
});
NYdb::NStatusHelpers::ThrowOnError(status);
std::optional<authors::GetAuthorRow> author;
table_client.RetryTx("upsert-and-read-author", {}, [&](::userver::ydb::TxActor& transaction) {
    authors::Queries queries{transaction};
    queries.UpsertAuthor(42, ::userver::ydb::Utf8{"Alice"});
    author = queries.GetAuthor(42);
    return ::userver::ydb::TxAction::kCommit;
});
return driver.Query().DoTx(ctx, func(ctx context.Context, tx query.TxActor) error {
    q := authors.New(tx)
    if err := q.UpsertAuthor(ctx, authors.UpsertAuthorParams{
        AuthorID: 42, AuthorName: "Alice",
    }); err != nil {
        return err
    }
    _, err := q.GetAuthor(ctx, 42)
    return err
})
tx, err := connection.BeginTx(ctx, nil)
if err != nil {
    return err
}
defer tx.Rollback()

q := authors.New(tx)
if err := q.UpsertAuthor(ctx, authors.UpsertAuthorParams{
    AuthorID: 42, AuthorName: "Alice",
}); err != nil {
    return err
}
if _, err := q.GetAuthor(ctx, 42); err != nil {
    return err
}
return tx.Commit()
GetAuthorRow author = retry.supplyResult(session -> {
    var tx = session.createNewTransaction(TxMode.SERIALIZABLE_RW);
    try {
        var queries = new Queries(tx);
        queries.upsertAuthor(42L, "Alice");
        var row = queries.getAuthor(42L).orElseThrow();
        return CompletableFuture.completedFuture(tx.commit().join().map(info -> row));
    } finally {
        if (tx.isActive()) {
            tx.rollback().join().expectSuccess();
        }
    }
}).join().getValue();
connection.setAutoCommit(false);
try {
    var queries = new Queries(connection);
    queries.upsertAuthor(42L, "Alice");
    GetAuthorRow author = queries.getAuthor(42L).orElseThrow();
    connection.commit();
} catch (Exception error) {
    connection.rollback();
    throw error;
} finally {
    connection.setAutoCommit(true);
}
GetAuthorRow author = dsl.transactionResult(configuration -> {
    var queries = new Queries(YDB.using(configuration));
    var id = ULong.valueOf(42);
    queries.upsertAuthor(id, "Alice");
    return queries.getAuthor(id).orElseThrow();
});
import ydb
from python.ydb.queries import Querier


def update_author(tx):
    queries = Querier(tx)
    queries.upsert_author(42, "Alice")
    return queries.get_author(42)


author = pool.retry_tx_sync(
    update_author,
    tx_mode=ydb.QuerySerializableReadWrite(),
    retry_settings=ydb.RetrySettings(idempotent=True),
)
import ydb_dbapi
from python.dbapi.queries import Querier

connection.set_isolation_level(ydb_dbapi.IsolationLevel.SERIALIZABLE)
connection.begin()
try:
    queries = Querier(connection)
    queries.upsert_author(42, "Alice")
    author = queries.get_author(42)
    connection.commit()
except Exception:
    connection.rollback()
    raise
from python.sqlalchemy.queries import Querier

with engine.connect().execution_options(isolation_level="SERIALIZABLE") as connection:
    with connection.begin():
        queries = Querier(connection)
        queries.upsert_author(42, "Alice")
        author = queries.get_author(42)
var author = await dataSource.ExecuteInTransactionAsync(async (connection, ct) =>
{
    var queries = new Authors.Queries(connection);
    await queries.UpsertAuthorAsync(new Authors.UpsertAuthorParams(42UL, "Alice"), ct);
    return await queries.GetAuthorAsync(42UL, ct);
}, cancellationToken: cancellationToken);
var author = await dataSource.ExecuteInTransactionAsync(async (connection, ct) =>
{
    var queries = new Authors.Queries(connection);
    await queries.UpsertAuthorAsync(new Authors.UpsertAuthorParams(42UL, "Alice"), ct);
    return await queries.GetAuthorAsync(42UL, ct);
}, cancellationToken: cancellationToken);
import { Queries } from "./typescript/queries.js";
import type { ConfigureQuery } from "./typescript/queries.js";

const author = await sql.transaction(async (tx, signal) => {
  const queries = new Queries(tx);
  const configure: ConfigureQuery = (stmt) => { stmt.signal(signal); };
  await queries.upsertAuthor({ authorId: 42n, authorName: "Alice" }, configure);
  return queries.getAuthor(42n, configure);
});
console.log(author?.name);
use generated::queries::Queries;

let author = query_client.retry_tx(ydb::closure!(async |tx| {
    let mut queries = Queries::new(tx);
    queries.upsert_author()
        .author_id(42)
        .author_name("Alice")
        .call().await?;
    Ok(queries.author().author_id(42).call().await?)
})).await?;
println!("{}", author.name);
use Authors\Queries;
use Authors\UpsertAuthorParams;

$session = $table->session();
$txId = $session->beginTransaction();
$queries = (new Queries($table))->withTx($session, $txId);

$queries->upsertAuthor(new UpsertAuthorParams('42', 'Alice'));
$author = $queries->getAuthor('42');

$session->commitTransaction();

Оба запроса выполняются в одной транзакции. $table — настроенный YdbPlatform\Ydb\Table, сгенерированные классы доступны через автозагрузку. Обработка ошибок опущена: при ошибке приложение должно вызвать $session->rollbackTransaction(). Связанный helper не выполняет повторные попытки отдельных запросов; повторять нужно всю транзакцию. После коммита или отката сессия возвращается в пул, а для следующей транзакции нужно получить сессию заново и создать новый связанный helper. Контракт PHP.

val author = retry.supplyResult { session ->
    val tx = session.createNewTransaction(TxMode.SERIALIZABLE_RW)
    try {
        val queries = Queries(tx)
        queries.upsertAuthor(42L, "Alice")
        val row = requireNotNull(queries.getAuthor(42L))
        CompletableFuture.completedFuture(tx.commit().join().map { row })
    } finally {
        if (tx.isActive) {
            tx.rollback().join().expectSuccess()
        }
    }
}.join().getValue()
connection.autoCommit = false
try {
    val queries = Queries(connection)
    queries.upsertAuthor(42L, "Alice")
    val author = requireNotNull(queries.getAuthor(42L))
    connection.commit()
} catch (error: Exception) {
    connection.rollback()
    throw error
} finally {
    connection.autoCommit = true
}
val author = ydbTransaction(database) {
    val queries = Queries(this)
    queries.upsertAuthor(42L, "Alice")
    requireNotNull(queries.getAuthor(42L))
}

Актуальный состав поддерживаемых команд, конфигурации и конструкций YQL приведён в контракте совместимости sqlc-ydb. Если ваш запрос не поддерживается или сгенерированный код неудобен, создайте issue с минимальными схемой, запросом, конфигурацией и выводом sqlc-ydb version --verbose.

Предыдущая
Следующая