Приложение на C++
На этой странице подробно разбирается код тестового приложения, доступного в составе C++ SDK YDB.
Инициализация соединения с базой данных
Для взаимодействия с YDB создается экземпляр драйвера, клиента и сессии:
- Драйвер YDB отвечает за взаимодействие приложения и YDB на транспортном уровне. Драйвер должен существовать на всем протяжении жизненного цикла работы с YDB и должен быть инициализирован перед созданием клиента и сессии.
- Клиент YDB работает поверх драйвера YDB и отвечает за работу с сущностями и транзакциями.
- Сессия YDB содержит информацию о выполняемых транзакциях и подготовленных запросах и содержится в контексте клиента YDB.
Фрагмент кода приложения для инициализации драйвера:
auto connectionParams = TConnectionsParams()
.SetEndpoint(endpoint)
.SetDatabase(database)
.SetAuthToken(GetEnv("YDB_TOKEN"));
TDriver driver(connectionParams);
Фрагмент кода приложения для создания клиента:
TClient client(driver);
Создание таблиц
Выполняется создание таблиц, которые используются в дальнейших операциях тестового приложения. В результате исполнения шага в базе данных будут созданы таблицы модели данных справочника сериалов:
series
- Сериалыseasons
- Сезоныepisodes
- Эпизоды
После создания вызывается метод получения информации об объекте схемы данных, и выводится результат его выполнения.
Для создания таблиц используется метод CreateTable
:
//! Creates sample tables with CrateTable API.
ThrowOnError(client.RetryOperationSync([path](TSession session) {
auto seriesDesc = TTableBuilder()
.AddNonNullableColumn("series_id", EPrimitiveType::Uint64)
.AddNullableColumn("title", EPrimitiveType::Utf8)
.AddNullableColumn("series_info", EPrimitiveType::Utf8)
.AddNullableColumn("release_date", EPrimitiveType::Uint64)
.SetPrimaryKeyColumn("series_id")
.Build();
return session.CreateTable(JoinPath(path, "series"), std::move(seriesDesc)).GetValueSync();
}));
С помощью метода DescribeTable
можно вывести информацию о структуре таблицы и убедиться, что она успешно создалась:
TMaybe<TTableDescription> desc;
ThrowOnError(client.RetryOperationSync([path, name, &desc](TSession session) {
auto result = session.DescribeTable(JoinPath(path, name)).GetValueSync();
if (result.IsSuccess()) {
desc = result.GetTableDescription();
}
return result;
}));
Cout << "> Describe table: " << name << Endl;
for (auto& column : desc->GetColumns()) {
Cout << "Column, name: " << column.Name << ", type: " << FormatType(column.Type) << Endl;
}
Приведенный фрагмент кода при запуске выводит на консоль текст:
> Describe table: series
Column, name: series_id, type: Uint64
Column, name: title, type: Utf8?
Column, name: series_info, type: Utf8?
Column, name: release_date, type: Uint64?
Запись данных
Выполняется запись данных в созданные таблицы с использованием команды UPSERT
языка запросов YQL. Применяется режим передачи запроса на изменение данных с автоматическим подтверждением транзакции в одном запросе к серверу.
Фрагмент кода, демонстрирующий выполнение запроса на запись/изменение данных:
//! Shows basic usage of mutating operations.
static TStatus UpsertSimpleTransaction(TSession session, const TString& path) {
auto query = Sprintf(R"(
PRAGMA TablePathPrefix("%s");
UPSERT INTO episodes (series_id, season_id, episode_id, title) VALUES
(2, 6, 1, "TBD");
)", path.c_str());
return session.ExecuteDataQuery(query,
TTxControl::BeginTx(TTxSettings::SerializableRW()).CommitTx()).GetValueSync();
}
PRAGMA TablePathPrefix
добавляет указанный префикс к путям таблиц внутри БД. Работает по принципу объединения путей в файловой системе — поддерживает ссылки на родительский каталог и не требует добавления слеша справа. Например:
PRAGMA TablePathPrefix = "/cluster/database";
SELECT * FROM episodes;
Подробнее о PRAGMA YQL можно прочитать в документации YQL.
Получение выборки данных
Выполняется запрос на получение выборки данных с использованием команды SELECT
языка запросов YQL. Демонстрируется обработка полученной выборки в приложении.
Для выполнения YQL-запросов используется метод ExecuteDataQuery
.
SDK позволяет в явном виде контролировать выполнение транзакций и настраивать необходимый режим выполнения транзакций с помощью класса TTxControl
.
В фрагменте кода, приведенном ниже, транзакция начинается методом TTxControl::BeginTx
. С помощью TTxSettings
устанавливается режим выполнения транзакции SerializableRW
. После завершения всех запросов транзакции она будет автоматически завершена явным указанием: CommitTx()
. Запрос query
, описанный с помощью синтаксиса YQL, передается методу ExecuteDataQuery
для выполнения.
//! Shows basic usage of YDB data queries and transactions.
static TStatus SelectSimpleTransaction(TSession session, const TString& path,
TMaybe<TResultSet>& resultSet)
{
auto query = Sprintf(R"(
PRAGMA TablePathPrefix("%s");
SELECT series_id, title, DateTime::ToDate(DateTime::FromDays(release_date)) AS release_date
FROM series
WHERE series_id = 1;
)", path.c_str());
auto txControl =
// Begin new transaction with SerializableRW mode
TTxControl::BeginTx(TTxSettings::SerializableRW())
// Commit transaction at the end of the query
.CommitTx();
// Executes data query with specified transaction control settings.
auto result = session.ExecuteDataQuery(query, txControl).GetValueSync();
if (result.IsSuccess()) {
// Index of result set corresponds to its order in YQL query
resultSet = result.GetResultSet(0);
}
return result;
}
Обработка результатов выполнения
Для обработки результатов выполнения запроса используется класс TResultSetParser
.
Фрагмент кода, приведенный ниже, демонстрирует обработку результатов запроса с помощью объекта parser
:
TResultSetParser parser(*resultSet);
if (parser.TryNextRow()) {
Cout << "> SelectSimple:" << Endl << "Series"
<< ", Id: " << parser.ColumnParser("series_id").GetUint64()
<< ", Title: " << parser.ColumnParser("title").GetOptionalUtf8()
<< ", Release date: " << parser.ColumnParser("release_date").GetOptionalString()
<< Endl;
}
Приведенный фрагмент кода при запуске выводит на консоль текст:
> SelectSimple:
series, Id: 1, title: IT Crowd, Release date: 2006-02-03
Параметризованные запросы
Выполняется запрос к данным с использованием параметров. Этот вариант выполнения запросов является предпочтительным, так как позволяет серверу переиспользовать план исполнения запроса при последующих его вызовах, а также спасает от уязвимостей вида SQL Injection.
Фрагмент кода демонстрирует использование параметризованных запросов и GetParamsBuilder
для формирования параметров и передачи их в ExecuteDataQuery
:
//! Shows usage of parameters in data queries.
static TStatus SelectWithParamsTransaction(TSession session, const TString& path,
ui64 seriesId, ui64 seasonId, TMaybe<TResultSet>& resultSet)
{
auto query = Sprintf(R"(
PRAGMA TablePathPrefix("%s");
DECLARE $seriesId AS Uint64;
DECLARE $seasonId AS Uint64;
SELECT sa.title AS season_title, sr.title AS series_title
FROM seasons AS sa
INNER JOIN series AS sr
ON sa.series_id = sr.series_id
WHERE sa.series_id = $seriesId AND sa.season_id = $seasonId;
)", path.c_str());
// Type of parameter values should be exactly the same as in DECLARE statements.
auto params = session.GetParamsBuilder()
.AddParam("$seriesId")
.Uint64(seriesId)
.Build()
.AddParam("$seasonId")
.Uint64(seasonId)
.Build()
.Build();
auto result = session.ExecuteDataQuery(
query,
TTxControl::BeginTx(TTxSettings::SerializableRW()).CommitTx(),
std::move(params)).GetValueSync();
if (result.IsSuccess()) {
resultSet = result.GetResultSet(0);
}
return result;
}
Приведенный фрагмент кода при запуске выводит на консоль текст:
> SelectWithParams:
Season, title: Season 3, series title: Silicon Valley
Finished preparing query: PreparedSelectTransaction
Параметризованные подготовленные запросы
Параметризованные подготовленные запросы (prepared queries) записываются в форме шаблона, в котором определенного вида имена заменяются конкретными параметрами при каждом выполнении запроса. Использование параметризованных запросов может улучшить производительность за счет сокращения частоты выполнения компиляции и перекомпиляции запросов, отличающихся только значениями параметров. Подготовленный запрос хранится в контексте сессии.
Фрагмент кода, демонстрирующий возможность использования параметризованных подготовленных запросов:
//! Shows usage of prepared queries.
static TStatus PreparedSelectTransaction(TSession session, const TString& path,
ui64 seriesId, ui64 seasonId, ui64 episodeId, TMaybe<TResultSet>& resultSet)
{
// Once prepared, query data is stored in the session and identified by QueryId.
// Local query cache is used to keep track of queries, prepared in current session.
auto query = Sprintf(R"(
PRAGMA TablePathPrefix("%s");
DECLARE $seriesId AS Uint64;
DECLARE $seasonId AS Uint64;
DECLARE $episodeId AS Uint64;
SELECT *
FROM episodes
WHERE series_id = $seriesId AND season_id = $seasonId AND episode_id = $episodeId;
)", path.c_str());
// Prepare query or get result from query cache
auto prepareResult = session.PrepareDataQuery(query).GetValueSync();
if (!prepareResult.IsSuccess()) {
return prepareResult;
}
if (!prepareResult.IsFromCache()) {
Cerr << "+Finished preparing query: PreparedSelectTransaction" << Endl;
}
auto dataQuery = prepareResult.GetQuery();
auto params = dataQuery.GetParamsBuilder()
.AddParam("$seriesId")
.Uint64(seriesId)
.Build()
.AddParam("$seasonId")
.Uint64(seasonId)
.Build()
.AddParam("$episodeId")
.Uint64(episodeId)
.Build()
.Build();
auto result = dataQuery.Execute(TTxControl::BeginTx(TTxSettings::SerializableRW()).CommitTx(),
std::move(params)).GetValueSync();
if (result.IsSuccess()) {
resultSet = result.GetResultSet(0);
}
return result;
}
Приведенный фрагмент кода при запуске выводит на консоль текст:
> PreparedSelect:
Episode 7, title: To Build a Better Beta, Air date: Sun Jun 05, 2016
Проверить наличие подготовленного запроса в сессии можно с помощью метода GetPreparedQuery
. Если подготовленного запроса в контексте сессии еще не существует, его можно подготовить с помощью PrepareDataQuery
, и сохранить для использования в рамках текущей сессии с помощью AddPreparedQuery
.
Многошаговые транзакции
Выполняется несколько команд в рамках одной многошаговой транзакции. Между выполнением запросов допустимо выполнение работы кода клиентского приложения. Использование транзакции позволяет гарантировать, что выполненные в её контексте выборки консистентны между собой.
Первый шаг — подготовка и выполнение первого запроса:
//! Shows usage of transactions consisting of multiple data queries with client logic between them.
static TStatus MultiStepTransaction(TSession session, const TString& path, ui64 seriesId, ui64 seasonId,
TMaybe<TResultSet>& resultSet)
{
auto query1 = Sprintf(R"(
PRAGMA TablePathPrefix("%s");
DECLARE $seriesId AS Uint64;
DECLARE $seasonId AS Uint64;
SELECT first_aired AS from_date FROM seasons
WHERE series_id = $seriesId AND season_id = $seasonId;
)", path.c_str());
auto params1 = session.GetParamsBuilder()
.AddParam("$seriesId")
.Uint64(seriesId)
.Build()
.AddParam("$seasonId")
.Uint64(seasonId)
.Build()
.Build();
// Execute first query to get the required values to the client.
// Transaction control settings don't set CommitTx flag to keep transaction active
// after query execution.
auto result = session.ExecuteDataQuery(
query1,
TTxControl::BeginTx(TTxSettings::SerializableRW()),
std::move(params1)).GetValueSync();
if (!result.IsSuccess()) {
return result;
}
Для продолжения работы в рамках текущей транзакции необходимо получить текущий transaction id
:
// Get active transaction id
auto tx = result.GetTransaction();
TResultSetParser parser(result.GetResultSet(0));
parser.TryNextRow();
auto date = parser.ColumnParser("from_date").GetOptionalUint64();
// Perform some client logic on returned values
auto userFunc = [] (const TInstant fromDate) {
return fromDate + TDuration::Days(15);
};
TInstant fromDate = TInstant::Days(*date);
TInstant toDate = userFunc(fromDate);
Следующий шаг — создание следующего запроса, использующего результаты выполнения кода на стороне клиентского приложения:
// Construct next query based on the results of client logic
auto query2 = Sprintf(R"(
PRAGMA TablePathPrefix("%s");
DECLARE $seriesId AS Uint64;
DECLARE $fromDate AS Uint64;
DECLARE $toDate AS Uint64;
SELECT season_id, episode_id, title, air_date FROM episodes
WHERE series_id = $seriesId AND air_date >= $fromDate AND air_date <= $toDate;
)", path.c_str());
auto params2 = session.GetParamsBuilder()
.AddParam("$seriesId")
.Uint64(seriesId)
.Build()
.AddParam("$fromDate")
.Uint64(fromDate.Days())
.Build()
.AddParam("$toDate")
.Uint64(toDate.Days())
.Build()
.Build();
// Execute second query.
// Transaction control settings continues active transaction (tx) and
// commits it at the end of second query execution.
result = session.ExecuteDataQuery(
query2,
TTxControl::Tx(*tx).CommitTx(),
std::move(params2)).GetValueSync();
if (result.IsSuccess()) {
resultSet = result.GetResultSet(0);
}
return result;
}
Приведенные фрагменты кода при запуске выводит на консоль текст:
> MultiStep:
Episode 1, Season: 5, title: Grow Fast or Die Slow, Air date: Sun Mar 25, 2018
Episode 2, Season: 5, title: Reorientation, Air date: Sun Apr 01, 2018
Episode 3, Season: 5, title: Chief Operating Officer, Air date: Sun Apr 08, 2018
Управление транзакциями
Выполняются вызовы операторов управления транзакциями TCL - Begin и Commit.
В большинстве случаев вместо явного использования вызовов Begin и Commit лучше использовать параметры контроля транзакций в вызовах execute. Это позволит избежать лишних обращений к YDB и эффективней выполнять запросы.
Фрагмент кода, демонстрирующий явное использование вызовов BeginTransaction
и tx.Commit()
:
// Show usage of explicit Begin/Commit transaction control calls.
// In most cases it's better to use transaction control settings in ExecuteDataQuery calls instead
// to avoid additional hops to YDB cluster and allow more efficient execution of queries.
static TStatus ExplicitTclTransaction(TSession session, const TString& path, const TInstant& airDate) {
auto beginResult = session.BeginTransaction(TTxSettings::SerializableRW()).GetValueSync();
if (!beginResult.IsSuccess()) {
return beginResult;
}
// Get newly created transaction id
auto tx = beginResult.GetTransaction();
auto query = Sprintf(R"(
PRAGMA TablePathPrefix("%s");
DECLARE $airDate AS Date;
UPDATE episodes SET air_date = DateTime::ToDays($airDate) WHERE title = "TBD";
)", path.c_str());
auto params = session.GetParamsBuilder()
.AddParam("$airDate")
.Date(airDate)
.Build()
.Build();
// Execute data query.
// Transaction control settings continues active transaction (tx)
auto updateResult = session.ExecuteDataQuery(query,
TTxControl::Tx(tx),
std::move(params)).GetValueSync();
if (!updateResult.IsSuccess()) {
return updateResult;
}
// Commit active transaction (tx)
return tx.Commit().GetValueSync();
}