Приложение на 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
- Эпизоды
После создания вызывается метод получения информации об объекте схемы данных, и выводится результат его выполнения.
//! Creates sample tables with the ExecuteQuery method
ThrowOnError(client.RetryQuerySync([](TSession session) {
auto query = Sprintf(R"(
CREATE TABLE series (
series_id Uint64,
title Utf8,
series_info Utf8,
release_date Uint64,
PRIMARY KEY (series_id)
);
)");
return session.ExecuteQuery(query, TTxControl::NoTx()).GetValueSync();
}));
Запись данных
Выполняется запись данных в созданные строковые таблицы с использованием команды UPSERT
языка запросов YQL. Применяется режим передачи запроса на изменение данных с автоматическим подтверждением транзакции в одном запросе к серверу.
Фрагмент кода, демонстрирующий выполнение запроса на запись/изменение данных:
//! Shows basic usage of mutating operations.
void UpsertSimple(TQueryClient client) {
ThrowOnError(client.RetryQuerySync([](TSession session) {
auto query = Sprintf(R"(
UPSERT INTO episodes (series_id, season_id, episode_id, title) VALUES
(2, 6, 1, "TBD");
)");
return session.ExecuteQuery(query,
TTxControl::BeginTx(TTxSettings::SerializableRW()).CommitTx()).GetValueSync();
}));
}
PRAGMA TablePathPrefix
добавляет указанный префикс к путям таблиц внутри БД. Работает по принципу объединения путей в файловой системе — поддерживает ссылки на родительский каталог и не требует добавления слеша справа. Например:
PRAGMA TablePathPrefix = "/cluster/database";
SELECT * FROM episodes;
Подробнее о PRAGMA YQL можно прочитать в документации YQL.
Получение выборки данных
Выполняется запрос на получение выборки данных с использованием команды SELECT
языка запросов YQL. Демонстрируется обработка полученной выборки в приложении.
Для выполнения YQL-запросов используется метод ExecuteQuery
.
SDK позволяет в явном виде контролировать выполнение транзакций и настраивать необходимый режим выполнения транзакций с помощью класса TTxControl
.
В приведенном ниже фрагменте кода параметры транзакции определены с помощью метод TTxControl::BeginTx
. С помощью TTxSettings
устанавливается режим выполнения транзакции SerializableRW
. После завершения всех запросов транзакции она будет автоматически завершена явным указанием: CommitTx()
. Запрос query
, описанный с помощью синтаксиса YQL, передается методу ExecuteQuery
для выполнения.
void SelectSimple(TQueryClient client) {
TMaybe<TResultSet> resultSet;
ThrowOnError(client.RetryQuerySync([&resultSet](TSession session) {
auto query = Sprintf(R"(
SELECT series_id, title, CAST(release_date AS Date) AS release_date
FROM series
WHERE series_id = 1;
)");
auto txControl =
// Begin a new transaction with SerializableRW mode
TTxControl::BeginTx(TTxSettings::SerializableRW())
// Commit the transaction at the end of the query
.CommitTx();
auto result = session.ExecuteQuery(query, txControl).GetValueSync();
if (!result.IsSuccess()) {
return result;
}
resultSet = result.GetResultSet(0);
return result;
}));
Обработка результатов выполнения
Для обработки результатов выполнения запроса используется класс TResultSetParser
.
Фрагмент кода, приведенный ниже, демонстрирует обработку результатов запроса с помощью объекта parser
:
TResultSetParser parser(*resultSet);
while (parser.TryNextRow()) {
Cout << "> SelectSimple:" << Endl << "Series"
<< ", Id: " << parser.ColumnParser("series_id").GetOptionalUint64()
<< ", Title: " << parser.ColumnParser("title").GetOptionalUtf8()
<< ", Release date: " << parser.ColumnParser("release_date").GetOptionalDate()->FormatLocalTime("%Y-%m-%d")
<< Endl;
}
}
Приведенный фрагмент кода при запуске выводит на консоль текст:
> SelectSimple:
series, Id: 1, title: IT Crowd, Release date: 2006-02-03
Параметризованные запросы
Выполняется запрос к данным с использованием параметров. Этот вариант выполнения запросов является предпочтительным, так как позволяет серверу переиспользовать план исполнения запроса при последующих его вызовах, а также спасает от уязвимостей вида SQL Injection.
Фрагмент кода демонстрирует использование параметризованных запросов и TParamsBuilder
для формирования параметров и передачи их в ExecuteQuery
:
void SelectWithParams(TQueryClient client) {
TMaybe<TResultSet> resultSet;
ThrowOnError(client.RetryQuerySync([&resultSet](TSession session) {
ui64 seriesId = 2;
ui64 seasonId = 3;
auto query = Sprintf(R"(
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;
)");
auto params = TParamsBuilder()
.AddParam("$seriesId")
.Uint64(seriesId)
.Build()
.AddParam("$seasonId")
.Uint64(seasonId)
.Build()
.Build();
auto result = session.ExecuteQuery(
query,
TTxControl::BeginTx(TTxSettings::SerializableRW()).CommitTx(),
params).GetValueSync();
if (!result.IsSuccess()) {
return result;
}
resultSet = result.GetResultSet(0);
return result;
}));
TResultSetParser parser(*resultSet);
if (parser.TryNextRow()) {
Cout << "> SelectWithParams:" << Endl << "Season"
<< ", Title: " << parser.ColumnParser("season_title").GetOptionalUtf8()
<< ", Series title: " << parser.ColumnParser("series_title").GetOptionalUtf8()
<< Endl;
}
}
Приведенный фрагмент кода при запуске выводит на консоль текст:
> SelectWithParams:
Season, title: Season 3, series title: Silicon Valley
Потоковые запросы
Выполняется потоковый запрос данных, результатом исполнения которого является стрим. Стрим позволяет считать неограниченное количество строк и объем данных.
Важно
Не используйте метод StreamExecuteQuery
без оборачивания вызова в RetryQuery
или RetryQuerySync
.
void StreamQuerySelect(TQueryClient client) {
Cout << "> StreamQuery:" << Endl;
ThrowOnError(client.RetryQuerySync([](TQueryClient client) -> TStatus {
auto query = Sprintf(R"(
DECLARE $series AS List<UInt64>;
SELECT series_id, season_id, title, CAST(first_aired AS Date) AS first_aired
FROM seasons
WHERE series_id IN $series
ORDER BY season_id;
)");
auto paramsBuilder = TParamsBuilder();
auto& listParams = paramsBuilder
.AddParam("$series")
.BeginList();
for (auto x : {1, 10}) {
listParams.AddListItem().Uint64(x);
}
auto parameters = listParams
.EndList()
.Build()
.Build();
// Executes stream query
auto resultStreamQuery = client.StreamExecuteQuery(query, TTxControl::NoTx(), parameters).GetValueSync();
if (!resultStreamQuery.IsSuccess()) {
return resultStreamQuery;
}
// Iterates over results
bool eos = false;
while (!eos) {
auto streamPart = resultStreamQuery.ReadNext().ExtractValueSync();
if (!streamPart.IsSuccess()) {
eos = true;
if (!streamPart.EOS()) {
return streamPart;
}
continue;
}
// It is possible for lines to be duplicated in the output stream due to an external retrier
if (streamPart.HasResultSet()) {
auto rs = streamPart.ExtractResultSet();
TResultSetParser parser(rs);
while (parser.TryNextRow()) {
Cout << "Season"
<< ", SeriesId: " << parser.ColumnParser("series_id").GetOptionalUint64()
<< ", SeasonId: " << parser.ColumnParser("season_id").GetOptionalUint64()
<< ", Title: " << parser.ColumnParser("title").GetOptionalUtf8()
<< ", Air date: " << parser.ColumnParser("first_aired").GetOptionalDate()->FormatLocalTime("%Y-%m-%d")
<< Endl;
}
}
}
return TStatus(EStatus::SUCCESS, NYql::TIssues());
}));
}
Приведенный фрагмент кода при запуске выводит на консоль текст (возможны дубликаты строк в потоке вывода из-за внешнего RetryQuerySync
):
> StreamQuery:
Season, SeriesId: 1, SeasonId: 1, Title: Season 1, Air date: 2006-02-03
Season, SeriesId: 1, SeasonId: 2, Title: Season 2, Air date: 2007-08-24
Season, SeriesId: 1, SeasonId: 3, Title: Season 3, Air date: 2008-11-21
Season, SeriesId: 1, SeasonId: 4, Title: Season 4, Air date: 2010-06-25
Многошаговые транзакции
Выполняется несколько команд в рамках одной многошаговой транзакции. Между выполнением запросов допустимо выполнение работы кода клиентского приложения. Использование транзакции позволяет гарантировать, что выполненные в её контексте выборки консистентны между собой.
Первый шаг — подготовка и выполнение первого запроса:
//! Shows usage of transactions consisting of multiple data queries with client logic between them.
void MultiStep(TQueryClient client) {
TMaybe<TResultSet> resultSet;
ThrowOnError(client.RetryQuerySync([&resultSet](TSession session) {
ui64 seriesId = 2;
ui64 seasonId = 5;
auto query1 = Sprintf(R"(
DECLARE $seriesId AS Uint64;
DECLARE $seasonId AS Uint64;
SELECT first_aired AS from_date FROM seasons
WHERE series_id = $seriesId AND season_id = $seasonId;
)");
auto params1 = TParamsBuilder()
.AddParam("$seriesId")
.Uint64(seriesId)
.Build()
.AddParam("$seasonId")
.Uint64(seasonId)
.Build()
.Build();
// Execute the first query to retrieve the required values for the client.
// Transaction control settings do not set the CommitTx flag, allowing the transaction to remain active
// after query execution.
auto result = session.ExecuteQuery(
query1,
TTxControl::BeginTx(TTxSettings::SerializableRW()),
params1);
auto resultValue = result.GetValueSync();
if (!resultValue.IsSuccess()) {
return resultValue;
}
Для продолжения работы в рамках текущей транзакции необходимо получить идентификатор транзакции:
// Get the active transaction id
auto txId = resultValue.GetTransaction()->GetId();
// Processing the request result
TResultSetParser parser(resultValue.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"(
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;
)");
auto params2 = TParamsBuilder()
.AddParam("$seriesId")
.Uint64(seriesId)
.Build()
.AddParam("$fromDate")
.Uint64(fromDate.Days())
.Build()
.AddParam("$toDate")
.Uint64(toDate.Days())
.Build()
.Build();
// Execute the second query.
// The transaction control settings continue the active transaction (tx)
// and commit it at the end of the second query execution.
auto result2 = session.ExecuteQuery(
query2,
TTxControl::Tx(txId).CommitTx(),
params2).GetValueSync();
if (!result2.IsSuccess()) {
return result2;
}
resultSet = result2.GetResultSet(0);
return result2;
})); // The end of the retried lambda
TResultSetParser parser(*resultSet);
Cout << "> MultiStep:" << Endl;
while (parser.TryNextRow()) {
auto airDate = TInstant::Days(*parser.ColumnParser("air_date").GetOptionalUint64());
Cout << "Episode " << parser.ColumnParser("episode_id").GetOptionalUint64()
<< ", Season: " << parser.ColumnParser("season_id").GetOptionalUint64()
<< ", Title: " << parser.ColumnParser("title").GetOptionalUtf8()
<< ", Air date: " << airDate.FormatLocalTime("%a %b %d, %Y")
<< Endl;
}
}
Приведенные фрагменты кода при запуске выводят на консоль текст:
> 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()
:
void ExplicitTcl(TQueryClient client) {
// Demonstrate the use of explicit Begin and Commit transaction control calls.
// In most cases, it's preferable to use transaction control settings within ExecuteDataQuery calls instead,
// as this avoids additional hops to the YDB cluster and allows for more efficient query execution.
ThrowOnError(client.RetryQuerySync([](TQueryClient client) -> TStatus {
auto airDate = TInstant::Now();
auto session = client.GetSession().GetValueSync().GetSession();
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"(
DECLARE $airDate AS Date;
UPDATE episodes SET air_date = CAST($airDate AS Uint16) WHERE title = "TBD";
)");
auto params = TParamsBuilder()
.AddParam("$airDate")
.Date(airDate)
.Build()
.Build();
// Execute query.
// Transaction control settings continues active transaction (tx)
auto updateResult = session.ExecuteQuery(query,
TTxControl::Tx(tx.GetId()),
params).GetValueSync();
if (!updateResult.IsSuccess()) {
return updateResult;
}
// Commit active transaction (tx)
return tx.Commit().GetValueSync();
}));
}