aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniil Cherednik <dcherednik@yandex-team.ru>2022-02-17 20:48:54 +0300
committerDaniil Cherednik <dcherednik@yandex-team.ru>2022-02-17 20:48:54 +0300
commit3c6d2452a60e03ac4f2ab2b74b16d17d7fde4f5c (patch)
tree88f569aa18f5edacb3ca1162d76f2c206b03a84c
parent5c27e9c77778493fe201ac061f31fc2a6ea366c2 (diff)
downloadydb-3c6d2452a60e03ac4f2ab2b74b16d17d7fde4f5c.tar.gz
Move c++ sdk examples to ydb dir. KIKIMR-14385
ref:c696cf605e6e1b6ed32225d2dffcd081d3b1ffc0
-rw-r--r--ydb/public/sdk/cpp/examples/basic_example/basic_example.cpp609
-rw-r--r--ydb/public/sdk/cpp/examples/basic_example/basic_example.h8
-rw-r--r--ydb/public/sdk/cpp/examples/basic_example/basic_example_data.cpp205
-rw-r--r--ydb/public/sdk/cpp/examples/basic_example/main.cpp60
-rw-r--r--ydb/public/sdk/cpp/examples/basic_example/ya.make16
-rw-r--r--ydb/public/sdk/cpp/examples/bulk_upsert_simple/main.cpp166
-rw-r--r--ydb/public/sdk/cpp/examples/bulk_upsert_simple/ya.make14
-rw-r--r--ydb/public/sdk/cpp/examples/pagination/main.cpp46
-rw-r--r--ydb/public/sdk/cpp/examples/pagination/pagination.cpp191
-rw-r--r--ydb/public/sdk/cpp/examples/pagination/pagination.h8
-rw-r--r--ydb/public/sdk/cpp/examples/pagination/pagination_data.cpp53
-rw-r--r--ydb/public/sdk/cpp/examples/pagination/ya.make16
-rw-r--r--ydb/public/sdk/cpp/examples/secondary_index/main.cpp72
-rw-r--r--ydb/public/sdk/cpp/examples/secondary_index/secondary_index.cpp40
-rw-r--r--ydb/public/sdk/cpp/examples/secondary_index/secondary_index.h80
-rw-r--r--ydb/public/sdk/cpp/examples/secondary_index/secondary_index_create.cpp46
-rw-r--r--ydb/public/sdk/cpp/examples/secondary_index/secondary_index_delete.cpp79
-rw-r--r--ydb/public/sdk/cpp/examples/secondary_index/secondary_index_drop.cpp25
-rw-r--r--ydb/public/sdk/cpp/examples/secondary_index/secondary_index_generate.cpp206
-rw-r--r--ydb/public/sdk/cpp/examples/secondary_index/secondary_index_list.cpp336
-rw-r--r--ydb/public/sdk/cpp/examples/secondary_index/secondary_index_update.cpp87
-rw-r--r--ydb/public/sdk/cpp/examples/secondary_index/ya.make21
-rw-r--r--ydb/public/sdk/cpp/examples/secondary_index_builtin/README.md61
-rw-r--r--ydb/public/sdk/cpp/examples/secondary_index_builtin/main.cpp64
-rw-r--r--ydb/public/sdk/cpp/examples/secondary_index_builtin/secondary_index.cpp49
-rw-r--r--ydb/public/sdk/cpp/examples/secondary_index_builtin/secondary_index.h89
-rw-r--r--ydb/public/sdk/cpp/examples/secondary_index_builtin/secondary_index_create.cpp45
-rw-r--r--ydb/public/sdk/cpp/examples/secondary_index_builtin/secondary_index_drop.cpp19
-rw-r--r--ydb/public/sdk/cpp/examples/secondary_index_builtin/secondary_index_fill.cpp136
-rw-r--r--ydb/public/sdk/cpp/examples/secondary_index_builtin/secondary_index_select.cpp67
-rw-r--r--ydb/public/sdk/cpp/examples/secondary_index_builtin/secondary_index_select_join.cpp70
-rw-r--r--ydb/public/sdk/cpp/examples/secondary_index_builtin/ya.make23
-rw-r--r--ydb/public/sdk/cpp/examples/ttl/main.cpp46
-rw-r--r--ydb/public/sdk/cpp/examples/ttl/ttl.cpp323
-rw-r--r--ydb/public/sdk/cpp/examples/ttl/ttl.h8
-rw-r--r--ydb/public/sdk/cpp/examples/ttl/util.h41
-rw-r--r--ydb/public/sdk/cpp/examples/ttl/ya.make17
-rw-r--r--ydb/public/sdk/cpp/examples/ya.make10
-rw-r--r--ydb/public/sdk/cpp/ya.make1
39 files changed, 3453 insertions, 0 deletions
diff --git a/ydb/public/sdk/cpp/examples/basic_example/basic_example.cpp b/ydb/public/sdk/cpp/examples/basic_example/basic_example.cpp
new file mode 100644
index 0000000000..1a7c8d418f
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/basic_example/basic_example.cpp
@@ -0,0 +1,609 @@
+#include "basic_example.h"
+
+#include <util/folder/pathsplit.h>
+#include <util/string/printf.h>
+
+using namespace NYdb;
+using namespace NYdb::NTable;
+
+class TYdbErrorException : public yexception {
+public:
+ TYdbErrorException(const TStatus& status)
+ : Status(status) {}
+
+ TStatus Status;
+};
+
+static void ThrowOnError(const TStatus& status) {
+ if (!status.IsSuccess()) {
+ throw TYdbErrorException(status);
+ }
+}
+
+static void PrintStatus(const TStatus& status) {
+ Cerr << "Status: " << status.GetStatus() << Endl;
+ status.GetIssues().PrintTo(Cerr);
+}
+
+static TString JoinPath(const TString& basePath, const TString& path) {
+ if (basePath.empty()) {
+ return path;
+ }
+
+ TPathSplitUnix prefixPathSplit(basePath);
+ prefixPathSplit.AppendComponent(path);
+
+ return prefixPathSplit.Reconstruct();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+//! Creates sample tables with CrateTable API.
+static void CreateTables(TTableClient client, const TString& path) {
+ ThrowOnError(client.RetryOperationSync([path](TSession session) {
+ auto seriesDesc = TTableBuilder()
+ .AddNullableColumn("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();
+ }));
+
+ ThrowOnError(client.RetryOperationSync([path](TSession session) {
+ auto seasonsDesc = TTableBuilder()
+ .AddNullableColumn("series_id", EPrimitiveType::Uint64)
+ .AddNullableColumn("season_id", EPrimitiveType::Uint64)
+ .AddNullableColumn("title", EPrimitiveType::Utf8)
+ .AddNullableColumn("first_aired", EPrimitiveType::Uint64)
+ .AddNullableColumn("last_aired", EPrimitiveType::Uint64)
+ .SetPrimaryKeyColumns({"series_id", "season_id"})
+ .Build();
+
+ return session.CreateTable(JoinPath(path, "seasons"), std::move(seasonsDesc)).GetValueSync();
+ }));
+
+ ThrowOnError(client.RetryOperationSync([path](TSession session) {
+ auto episodesDesc = TTableBuilder()
+ .AddNullableColumn("series_id", EPrimitiveType::Uint64)
+ .AddNullableColumn("season_id", EPrimitiveType::Uint64)
+ .AddNullableColumn("episode_id", EPrimitiveType::Uint64)
+ .AddNullableColumn("title", EPrimitiveType::Utf8)
+ .AddNullableColumn("air_date", EPrimitiveType::Uint64)
+ .SetPrimaryKeyColumns({"series_id", "season_id", "episode_id"})
+ .Build();
+
+ return session.CreateTable(JoinPath(path, "episodes"),
+ std::move(episodesDesc)).GetValueSync();
+ }));
+}
+
+//! Describe existing table.
+static void DescribeTable(TTableClient client, const TString& path, const TString& name) {
+ 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;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+//! Fills sample tables with data in single parameterized data query.
+static TStatus FillTableDataTransaction(TSession session, const TString& path) {
+ auto query = Sprintf(R"(
+ PRAGMA TablePathPrefix("%s");
+
+ DECLARE $seriesData AS List<Struct<
+ series_id: Uint64,
+ title: Utf8,
+ series_info: Utf8,
+ release_date: Date>>;
+
+ DECLARE $seasonsData AS List<Struct<
+ series_id: Uint64,
+ season_id: Uint64,
+ title: Utf8,
+ first_aired: Date,
+ last_aired: Date>>;
+
+ DECLARE $episodesData AS List<Struct<
+ series_id: Uint64,
+ season_id: Uint64,
+ episode_id: Uint64,
+ title: Utf8,
+ air_date: Date>>;
+
+ REPLACE INTO series
+ SELECT
+ series_id,
+ title,
+ series_info,
+ CAST(release_date AS Uint16) AS release_date
+ FROM AS_TABLE($seriesData);
+
+ REPLACE INTO seasons
+ SELECT
+ series_id,
+ season_id,
+ title,
+ CAST(first_aired AS Uint16) AS first_aired,
+ CAST(last_aired AS Uint16) AS last_aired
+ FROM AS_TABLE($seasonsData);
+
+ REPLACE INTO episodes
+ SELECT
+ series_id,
+ season_id,
+ episode_id,
+ title,
+ CAST(air_date AS Uint16) AS air_date
+ FROM AS_TABLE($episodesData);
+ )", path.c_str());
+
+ auto params = GetTablesDataParams();
+
+ return session.ExecuteDataQuery(
+ query,
+ TTxControl::BeginTx(TTxSettings::SerializableRW()).CommitTx(),
+ params).GetValueSync();
+}
+
+//! 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, CAST(CAST(release_date AS Date) AS String) 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;
+}
+
+//! Shows basic usage of mutating operations.
+static TStatus UpsertSimpleTransaction(TSession session, const TString& path) {
+ auto query = Sprintf(R"(
+ --!syntax_v1
+ 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();
+}
+
+//! 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"(
+ --!syntax_v1
+ 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(),
+ params).GetValueSync();
+
+ if (result.IsSuccess()) {
+ resultSet = result.GetResultSet(0);
+ }
+
+ return result;
+}
+
+//! 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"(
+ --!syntax_v1
+ 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.IsQueryFromCache()) {
+ 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(),
+ params).GetValueSync();
+
+ if (result.IsSuccess()) {
+ resultSet = result.GetResultSet(0);
+ }
+
+ return result;
+}
+
+//! 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"(
+ --!syntax_v1
+ 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()),
+ params1).GetValueSync();
+
+ if (!result.IsSuccess()) {
+ return result;
+ }
+
+ // 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"(
+ --!syntax_v1
+ 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(),
+ params2).GetValueSync();
+
+ if (result.IsSuccess()) {
+ resultSet = result.GetResultSet(0);
+ }
+
+ return result;
+}
+
+// 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"(
+ --!syntax_v1
+ PRAGMA TablePathPrefix("%s");
+
+ DECLARE $airDate AS Date;
+
+ UPDATE episodes SET air_date = CAST($airDate AS Uint16) 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),
+ params).GetValueSync();
+
+ if (!updateResult.IsSuccess()) {
+ return updateResult;
+ }
+
+ // Commit active transaction (tx)
+ return tx.Commit().GetValueSync();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SelectSimple(TTableClient client, const TString& path) {
+ TMaybe<TResultSet> resultSet;
+ ThrowOnError(client.RetryOperationSync([path, &resultSet](TSession session) {
+ return SelectSimpleTransaction(session, path, resultSet);
+ }));
+
+ TResultSetParser parser(*resultSet);
+ if (parser.TryNextRow()) {
+ Cout << "> SelectSimple:" << Endl << "Series"
+ << ", Id: " << parser.ColumnParser("series_id").GetOptionalUint64()
+ << ", Title: " << parser.ColumnParser("title").GetOptionalUtf8()
+ << ", Release date: " << parser.ColumnParser("release_date").GetOptionalString()
+ << Endl;
+ }
+}
+
+void UpsertSimple(TTableClient client, const TString& path) {
+ ThrowOnError(client.RetryOperationSync([path](TSession session) {
+ return UpsertSimpleTransaction(session, path);
+ }));
+}
+
+void SelectWithParams(TTableClient client, const TString& path) {
+ TMaybe<TResultSet> resultSet;
+ ThrowOnError(client.RetryOperationSync([path, &resultSet](TSession session) {
+ return SelectWithParamsTransaction(session, path, 2, 3, resultSet);
+ }));
+
+ TResultSetParser parser(*resultSet);
+ if (parser.TryNextRow()) {
+ Cout << "> SelectWithParams:" << Endl << "Season"
+ << ", Title: " << parser.ColumnParser("season_title").GetOptionalUtf8()
+ << ", Series title: " << parser.ColumnParser("series_title").GetOptionalUtf8()
+ << Endl;
+ }
+}
+
+void PreparedSelect(TTableClient client, const TString& path, ui32 seriesId, ui32 seasonId, ui32 episodeId) {
+ TMaybe<TResultSet> resultSet;
+ ThrowOnError(client.RetryOperationSync([path, seriesId, seasonId, episodeId, &resultSet](TSession session) {
+ return PreparedSelectTransaction(session, path, seriesId, seasonId, episodeId, resultSet);
+ }));
+
+ TResultSetParser parser(*resultSet);
+ if (parser.TryNextRow()) {
+ auto airDate = TInstant::Days(*parser.ColumnParser("air_date").GetOptionalUint64());
+
+ Cout << "> PreparedSelect:" << Endl << "Episode " << parser.ColumnParser("episode_id").GetOptionalUint64()
+ << ", Title: " << parser.ColumnParser("title").GetOptionalUtf8()
+ << ", Air date: " << airDate.FormatLocalTime("%a %b %d, %Y")
+ << Endl;
+ }
+}
+
+void MultiStep(TTableClient client, const TString& path) {
+ TMaybe<TResultSet> resultSet;
+ ThrowOnError(client.RetryOperationSync([path, &resultSet](TSession session) {
+ return MultiStepTransaction(session, path, 2, 5, resultSet);
+ }));
+
+ 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;
+ }
+}
+
+void ExplicitTcl(TTableClient client, const TString& path) {
+ ThrowOnError(client.RetryOperationSync([path](TSession session) {
+ return ExplicitTclTransaction(session, path, TInstant::Now());
+ }));
+}
+
+void ScanQuerySelect(TTableClient client, const TString& path) {
+ auto query = Sprintf(R"(
+ --!syntax_v1
+ PRAGMA TablePathPrefix("%s");
+
+ DECLARE $series AS List<UInt64>;
+
+ SELECT series_id, season_id, title, CAST(CAST(first_aired AS Date) AS String) AS first_aired
+ FROM seasons
+ WHERE series_id IN $series
+ )", path.c_str());
+
+ auto parameters = TParamsBuilder()
+ .AddParam("$series")
+ .BeginList()
+ .AddListItem().Uint64(1)
+ .AddListItem().Uint64(10)
+ .EndList().Build()
+ .Build();
+
+ // Executes scan query
+ auto result = client.StreamExecuteScanQuery(query, parameters).GetValueSync();
+
+ if (!result.IsSuccess()) {
+ Cerr << "ScanQuery execution failure: " << result.GetIssues().ToString() << Endl;
+ return;
+ }
+
+ bool eos = false;
+ Cout << "> ScanQuerySelect:" << Endl;
+ while (!eos) {
+ auto streamPart = result.ReadNext().ExtractValueSync();
+
+ if (!streamPart.IsSuccess()) {
+ eos = true;
+ if (!streamPart.EOS()) {
+ Cerr << "ScanQuery execution failure: " << streamPart.GetIssues().ToString() << Endl;
+ }
+ continue;
+ }
+
+ if (streamPart.HasResultSet()) {
+ auto rs = streamPart.ExtractResultSet();
+ auto columns = rs.GetColumnsMeta();
+
+ 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").GetOptionalString()
+ << Endl;
+ }
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool Run(const TDriver& driver, const TString& path) {
+ TTableClient client(driver);
+
+ try {
+ CreateTables(client, path);
+
+ DescribeTable(client, path, "series");
+
+ ThrowOnError(client.RetryOperationSync([path](TSession session) {
+ return FillTableDataTransaction(session, path);
+ }));
+
+ SelectSimple(client, path);
+ UpsertSimple(client, path);
+
+ SelectWithParams(client, path);
+
+ PreparedSelect(client, path, 2, 3, 7);
+ PreparedSelect(client, path, 2, 3, 8);
+
+ MultiStep(client, path);
+
+ ExplicitTcl(client, path);
+
+ PreparedSelect(client, path, 2, 6, 1);
+
+ ScanQuerySelect(client, path);
+ }
+ catch (const TYdbErrorException& e) {
+ Cerr << "Execution failed due to fatal error:" << Endl;
+ PrintStatus(e.Status);
+ return false;
+ }
+
+ return true;
+}
diff --git a/ydb/public/sdk/cpp/examples/basic_example/basic_example.h b/ydb/public/sdk/cpp/examples/basic_example/basic_example.h
new file mode 100644
index 0000000000..d419c21668
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/basic_example/basic_example.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include <ydb/public/sdk/cpp/client/ydb_driver/driver.h>
+#include <ydb/public/sdk/cpp/client/ydb_table/table.h>
+
+NYdb::TParams GetTablesDataParams();
+
+bool Run(const NYdb::TDriver& driver, const TString& path);
diff --git a/ydb/public/sdk/cpp/examples/basic_example/basic_example_data.cpp b/ydb/public/sdk/cpp/examples/basic_example/basic_example_data.cpp
new file mode 100644
index 0000000000..6e9cecc29b
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/basic_example/basic_example_data.cpp
@@ -0,0 +1,205 @@
+#include "basic_example.h"
+
+using namespace NYdb;
+using namespace NYdb::NTable;
+
+struct TSeries {
+ ui64 SeriesId;
+ TString Title;
+ TInstant ReleaseDate;
+ TString SeriesInfo;
+
+ TSeries(ui64 seriesId, const TString& title, const TInstant& releaseDate, const TString& seriesInfo)
+ : SeriesId(seriesId)
+ , Title(title)
+ , ReleaseDate(releaseDate)
+ , SeriesInfo(seriesInfo) {}
+};
+
+struct TSeason {
+ ui64 SeriesId;
+ ui64 SeasonId;
+ TString Title;
+ TInstant FirstAired;
+ TInstant LastAired;
+
+ TSeason(ui64 seriesId, ui64 seasonId, const TString& title, const TInstant& firstAired, const TInstant& lastAired)
+ : SeriesId(seriesId)
+ , SeasonId(seasonId)
+ , Title(title)
+ , FirstAired(firstAired)
+ , LastAired(lastAired) {}
+};
+
+struct TEpisode {
+ ui64 SeriesId;
+ ui64 SeasonId;
+ ui64 EpisodeId;
+ TString Title;
+ TInstant AirDate;
+
+ TEpisode(ui64 seriesId, ui64 seasonId, ui64 episodeId, const TString& title, const TInstant& airDate)
+ : SeriesId(seriesId)
+ , SeasonId(seasonId)
+ , EpisodeId(episodeId)
+ , Title(title)
+ , AirDate(airDate) {}
+};
+
+TParams GetTablesDataParams() {
+ TVector<TSeries> seriesData = {
+ TSeries(1, "IT Crowd", TInstant::ParseIso8601("2006-02-03"),
+ "The IT Crowd is a British sitcom produced by Channel 4, written by Graham Linehan, produced by "
+ "Ash Atalla and starring Chris O'Dowd, Richard Ayoade, Katherine Parkinson, and Matt Berry."),
+ TSeries(2, "Silicon Valley", TInstant::ParseIso8601("2014-04-06"),
+ "Silicon Valley is an American comedy television series created by Mike Judge, John Altschuler and "
+ "Dave Krinsky. The series focuses on five young men who founded a startup company in Silicon Valley.")
+ };
+
+ TVector<TSeason> seasonsData = {
+ TSeason(1, 1, "Season 1", TInstant::ParseIso8601("2006-02-03"), TInstant::ParseIso8601("2006-03-03")),
+ TSeason(1, 2, "Season 2", TInstant::ParseIso8601("2007-08-24"), TInstant::ParseIso8601("2007-09-28")),
+ TSeason(1, 3, "Season 3", TInstant::ParseIso8601("2008-11-21"), TInstant::ParseIso8601("2008-12-26")),
+ TSeason(1, 4, "Season 4", TInstant::ParseIso8601("2010-06-25"), TInstant::ParseIso8601("2010-07-30")),
+ TSeason(2, 1, "Season 1", TInstant::ParseIso8601("2014-04-06"), TInstant::ParseIso8601("2014-06-01")),
+ TSeason(2, 2, "Season 2", TInstant::ParseIso8601("2015-04-12"), TInstant::ParseIso8601("2015-06-14")),
+ TSeason(2, 3, "Season 3", TInstant::ParseIso8601("2016-04-24"), TInstant::ParseIso8601("2016-06-26")),
+ TSeason(2, 4, "Season 4", TInstant::ParseIso8601("2017-04-23"), TInstant::ParseIso8601("2017-06-25")),
+ TSeason(2, 5, "Season 5", TInstant::ParseIso8601("2018-03-25"), TInstant::ParseIso8601("2018-05-13"))
+ };
+
+ TVector<TEpisode> episodesData = {
+ TEpisode(1, 1, 1, "Yesterday's Jam", TInstant::ParseIso8601("2006-02-03")),
+ TEpisode(1, 1, 2, "Calamity Jen", TInstant::ParseIso8601("2006-02-03")),
+ TEpisode(1, 1, 3, "Fifty-Fifty", TInstant::ParseIso8601("2006-02-10")),
+ TEpisode(1, 1, 4, "The Red Door", TInstant::ParseIso8601("2006-02-17")),
+ TEpisode(1, 1, 5, "The Haunting of Bill Crouse", TInstant::ParseIso8601("2006-02-24")),
+ TEpisode(1, 1, 6, "Aunt Irma Visits", TInstant::ParseIso8601("2006-03-03")),
+ TEpisode(1, 2, 1, "The Work Outing", TInstant::ParseIso8601("2006-08-24")),
+ TEpisode(1, 2, 2, "Return of the Golden Child", TInstant::ParseIso8601("2007-08-31")),
+ TEpisode(1, 2, 3, "Moss and the German", TInstant::ParseIso8601("2007-09-07")),
+ TEpisode(1, 2, 4, "The Dinner Party", TInstant::ParseIso8601("2007-09-14")),
+ TEpisode(1, 2, 5, "Smoke and Mirrors", TInstant::ParseIso8601("2007-09-21")),
+ TEpisode(1, 2, 6, "Men Without Women", TInstant::ParseIso8601("2007-09-28")),
+ TEpisode(1, 3, 1, "From Hell", TInstant::ParseIso8601("2008-11-21")),
+ TEpisode(1, 3, 2, "Are We Not Men?", TInstant::ParseIso8601("2008-11-28")),
+ TEpisode(1, 3, 3, "Tramps Like Us", TInstant::ParseIso8601("2008-12-05")),
+ TEpisode(1, 3, 4, "The Speech", TInstant::ParseIso8601("2008-12-12")),
+ TEpisode(1, 3, 5, "Friendface", TInstant::ParseIso8601("2008-12-19")),
+ TEpisode(1, 3, 6, "Calendar Geeks", TInstant::ParseIso8601("2008-12-26")),
+ TEpisode(1, 4, 1, "Jen The Fredo", TInstant::ParseIso8601("2010-06-25")),
+ TEpisode(1, 4, 2, "The Final Countdown", TInstant::ParseIso8601("2010-07-02")),
+ TEpisode(1, 4, 3, "Something Happened", TInstant::ParseIso8601("2010-07-09")),
+ TEpisode(1, 4, 4, "Italian For Beginners", TInstant::ParseIso8601("2010-07-16")),
+ TEpisode(1, 4, 5, "Bad Boys", TInstant::ParseIso8601("2010-07-23")),
+ TEpisode(1, 4, 6, "Reynholm vs Reynholm", TInstant::ParseIso8601("2010-07-30")),
+ TEpisode(2, 1, 1, "Minimum Viable Product", TInstant::ParseIso8601("2014-04-06")),
+ TEpisode(2, 1, 2, "The Cap Table", TInstant::ParseIso8601("2014-04-13")),
+ TEpisode(2, 1, 3, "Articles of Incorporation", TInstant::ParseIso8601("2014-04-20")),
+ TEpisode(2, 1, 4, "Fiduciary Duties", TInstant::ParseIso8601("2014-04-27")),
+ TEpisode(2, 1, 5, "Signaling Risk", TInstant::ParseIso8601("2014-05-04")),
+ TEpisode(2, 1, 6, "Third Party Insourcing", TInstant::ParseIso8601("2014-05-11")),
+ TEpisode(2, 1, 7, "Proof of Concept", TInstant::ParseIso8601("2014-05-18")),
+ TEpisode(2, 1, 8, "Optimal Tip-to-Tip Efficiency", TInstant::ParseIso8601("2014-06-01")),
+ TEpisode(2, 2, 1, "Sand Hill Shuffle", TInstant::ParseIso8601("2015-04-12")),
+ TEpisode(2, 2, 2, "Runaway Devaluation", TInstant::ParseIso8601("2015-04-19")),
+ TEpisode(2, 2, 3, "Bad Money", TInstant::ParseIso8601("2015-04-26")),
+ TEpisode(2, 2, 4, "The Lady", TInstant::ParseIso8601("2015-05-03")),
+ TEpisode(2, 2, 5, "Server Space", TInstant::ParseIso8601("2015-05-10")),
+ TEpisode(2, 2, 6, "Homicide", TInstant::ParseIso8601("2015-05-17")),
+ TEpisode(2, 2, 7, "Adult Content", TInstant::ParseIso8601("2015-05-24")),
+ TEpisode(2, 2, 8, "White Hat/Black Hat", TInstant::ParseIso8601("2015-05-31")),
+ TEpisode(2, 2, 9, "Binding Arbitration", TInstant::ParseIso8601("2015-06-07")),
+ TEpisode(2, 2, 10, "Two Days of the Condor", TInstant::ParseIso8601("2015-06-14")),
+ TEpisode(2, 3, 1, "Founder Friendly", TInstant::ParseIso8601("2016-04-24")),
+ TEpisode(2, 3, 2, "Two in the Box", TInstant::ParseIso8601("2016-05-01")),
+ TEpisode(2, 3, 3, "Meinertzhagen's Haversack", TInstant::ParseIso8601("2016-05-08")),
+ TEpisode(2, 3, 4, "Maleant Data Systems Solutions", TInstant::ParseIso8601("2016-05-15")),
+ TEpisode(2, 3, 5, "The Empty Chair", TInstant::ParseIso8601("2016-05-22")),
+ TEpisode(2, 3, 6, "Bachmanity Insanity", TInstant::ParseIso8601("2016-05-29")),
+ TEpisode(2, 3, 7, "To Build a Better Beta", TInstant::ParseIso8601("2016-06-05")),
+ TEpisode(2, 3, 8, "Bachman's Earnings Over-Ride", TInstant::ParseIso8601("2016-06-12")),
+ TEpisode(2, 3, 9, "Daily Active Users", TInstant::ParseIso8601("2016-06-19")),
+ TEpisode(2, 3, 10, "The Uptick", TInstant::ParseIso8601("2016-06-26")),
+ TEpisode(2, 4, 1, "Success Failure", TInstant::ParseIso8601("2017-04-23")),
+ TEpisode(2, 4, 2, "Terms of Service", TInstant::ParseIso8601("2017-04-30")),
+ TEpisode(2, 4, 3, "Intellectual Property", TInstant::ParseIso8601("2017-05-07")),
+ TEpisode(2, 4, 4, "Teambuilding Exercise", TInstant::ParseIso8601("2017-05-14")),
+ TEpisode(2, 4, 5, "The Blood Boy", TInstant::ParseIso8601("2017-05-21")),
+ TEpisode(2, 4, 6, "Customer Service", TInstant::ParseIso8601("2017-05-28")),
+ TEpisode(2, 4, 7, "The Patent Troll", TInstant::ParseIso8601("2017-06-04")),
+ TEpisode(2, 4, 8, "The Keenan Vortex", TInstant::ParseIso8601("2017-06-11")),
+ TEpisode(2, 4, 9, "Hooli-Con", TInstant::ParseIso8601("2017-06-18")),
+ TEpisode(2, 4, 10, "Server Error", TInstant::ParseIso8601("2017-06-25")),
+ TEpisode(2, 5, 1, "Grow Fast or Die Slow", TInstant::ParseIso8601("2018-03-25")),
+ TEpisode(2, 5, 2, "Reorientation", TInstant::ParseIso8601("2018-04-01")),
+ TEpisode(2, 5, 3, "Chief Operating Officer", TInstant::ParseIso8601("2018-04-08")),
+ TEpisode(2, 5, 4, "Tech Evangelist", TInstant::ParseIso8601("2018-04-15")),
+ TEpisode(2, 5, 5, "Facial Recognition", TInstant::ParseIso8601("2018-04-22")),
+ TEpisode(2, 5, 6, "Artificial Emotional Intelligence", TInstant::ParseIso8601("2018-04-29")),
+ TEpisode(2, 5, 7, "Initial Coin Offering", TInstant::ParseIso8601("2018-05-06")),
+ TEpisode(2, 5, 8, "Fifty-One Percent", TInstant::ParseIso8601("2018-05-13"))
+ };
+
+ TParamsBuilder paramsBuilder;
+
+ auto& seriesParam = paramsBuilder.AddParam("$seriesData");
+ seriesParam.BeginList();
+ for (auto& series : seriesData) {
+ seriesParam.AddListItem()
+ .BeginStruct()
+ .AddMember("series_id")
+ .Uint64(series.SeriesId)
+ .AddMember("title")
+ .Utf8(series.Title)
+ .AddMember("release_date")
+ .Date(series.ReleaseDate)
+ .AddMember("series_info")
+ .Utf8(series.SeriesInfo)
+ .EndStruct();
+ }
+ seriesParam.EndList();
+ seriesParam.Build();
+
+ auto& seasonsParam = paramsBuilder.AddParam("$seasonsData");
+ seasonsParam.BeginList();
+ for (auto& season : seasonsData) {
+ seasonsParam.AddListItem()
+ .BeginStruct()
+ .AddMember("series_id")
+ .Uint64(season.SeriesId)
+ .AddMember("season_id")
+ .Uint64(season.SeasonId)
+ .AddMember("title")
+ .Utf8(season.Title)
+ .AddMember("first_aired")
+ .Date(season.FirstAired)
+ .AddMember("last_aired")
+ .Date(season.LastAired)
+ .EndStruct();
+ }
+ seasonsParam.EndList();
+ seasonsParam.Build();
+
+ auto& episodesParam = paramsBuilder.AddParam("$episodesData");
+ episodesParam.BeginList();
+ for (auto& episode : episodesData) {
+ episodesParam.AddListItem()
+ .BeginStruct()
+ .AddMember("series_id")
+ .Uint64(episode.SeriesId)
+ .AddMember("season_id")
+ .Uint64(episode.SeasonId)
+ .AddMember("episode_id")
+ .Uint64(episode.EpisodeId)
+ .AddMember("title")
+ .Utf8(episode.Title)
+ .AddMember("air_date")
+ .Date(episode.AirDate)
+ .EndStruct();
+ }
+ episodesParam.EndList();
+ episodesParam.Build();
+
+ return paramsBuilder.Build();
+}
diff --git a/ydb/public/sdk/cpp/examples/basic_example/main.cpp b/ydb/public/sdk/cpp/examples/basic_example/main.cpp
new file mode 100644
index 0000000000..c0ad8b4d50
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/basic_example/main.cpp
@@ -0,0 +1,60 @@
+#include "basic_example.h"
+
+#include <library/cpp/getopt/last_getopt.h>
+
+#include <util/system/env.h>
+#include <util/stream/file.h>
+
+using namespace NLastGetopt;
+using namespace NYdb;
+
+void StopHandler(int) {
+ exit(1);
+}
+
+int main(int argc, char** argv) {
+ TOpts opts = TOpts::Default();
+
+ TString endpoint;
+ TString database;
+ TString path;
+ TString certPath;
+
+ opts.AddLongOption('e', "endpoint", "YDB endpoint").Required().RequiredArgument("HOST:PORT")
+ .StoreResult(&endpoint);
+ opts.AddLongOption('d', "database", "YDB database name").Required().RequiredArgument("PATH")
+ .StoreResult(&database);
+ opts.AddLongOption('p', "path", "Base path for tables").Optional().RequiredArgument("PATH")
+ .StoreResult(&path);
+ opts.AddLongOption('c', "cert", "Certificate path to use secure connection").Optional().RequiredArgument("PATH")
+ .StoreResult(&certPath);
+
+ signal(SIGINT, &StopHandler);
+ signal(SIGTERM, &StopHandler);
+
+ TOptsParseResult res(&opts, argc, argv);
+
+ if (path.empty()) {
+ path = database;
+ }
+
+ auto driverConfig = TDriverConfig()
+ .SetEndpoint(endpoint)
+ .SetDatabase(database)
+ .SetAuthToken(GetEnv("YDB_TOKEN"));
+
+ if (!certPath.empty()) {
+ TString cert = TFileInput(certPath).ReadAll();
+ driverConfig.UseSecureConnection(cert);
+ }
+
+ TDriver driver(driverConfig);
+
+ if (!Run(driver, path)) {
+ driver.Stop(true);
+ return 2;
+ }
+
+ driver.Stop(true);
+ return 0;
+}
diff --git a/ydb/public/sdk/cpp/examples/basic_example/ya.make b/ydb/public/sdk/cpp/examples/basic_example/ya.make
new file mode 100644
index 0000000000..a715d1cba2
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/basic_example/ya.make
@@ -0,0 +1,16 @@
+OWNER(g:kikimr)
+
+PROGRAM()
+
+SRCS(
+ main.cpp
+ basic_example_data.cpp
+ basic_example.cpp
+)
+
+PEERDIR(
+ library/cpp/getopt
+ ydb/public/sdk/cpp/client/ydb_table
+)
+
+END()
diff --git a/ydb/public/sdk/cpp/examples/bulk_upsert_simple/main.cpp b/ydb/public/sdk/cpp/examples/bulk_upsert_simple/main.cpp
new file mode 100644
index 0000000000..5fb2740d26
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/bulk_upsert_simple/main.cpp
@@ -0,0 +1,166 @@
+#include <ydb/public/sdk/cpp/client/ydb_table/table.h>
+
+#include <library/cpp/getopt/last_getopt.h>
+
+#include <util/system/env.h>
+#include <util/folder/pathsplit.h>
+
+Y_DECLARE_OUT_SPEC(, NYdb::TStatus, stream, value) {
+ stream << "Status: " << value.GetStatus() << Endl;
+ value.GetIssues().PrintTo(stream);
+}
+
+constexpr size_t BATCH_SIZE = 1000;
+
+struct TLogMessage {
+ TString App;
+ TString Host;
+ TInstant Timestamp;
+ ui32 HttpCode;
+ TString Message;
+};
+
+void GetLogBatch(ui64 logOffset, TVector<TLogMessage>& logBatch) {
+ logBatch.clear();
+ for (size_t i = 0; i < BATCH_SIZE; ++i) {
+ TLogMessage message;
+ message.App = "App_" + ToString(logOffset % 10);
+ message.Host = "192.168.0." + ToString(logOffset % 11);
+ message.Timestamp = TInstant::Now() + TDuration::MilliSeconds(i % 1000);
+ message.HttpCode = 200;
+ message.Message = i % 2 ? "GET / HTTP/1.1" : "GET /images/logo.png HTTP/1.1";
+ logBatch.emplace_back(message);
+ }
+}
+
+bool WriteLogBatch(NYdb::NTable::TTableClient& tableClient, const TString& table, const TVector<TLogMessage>& logBatch,
+ const NYdb::NTable::TRetryOperationSettings& retrySettings)
+{
+ NYdb::TValueBuilder rows;
+ rows.BeginList();
+ for (const auto& message : logBatch) {
+ rows.AddListItem()
+ .BeginStruct()
+ .AddMember("App").Utf8(message.App)
+ .AddMember("Host").Utf8(message.Host)
+ .AddMember("Timestamp").Timestamp(message.Timestamp)
+ .AddMember("HttpCode").Uint32(message.HttpCode)
+ .AddMember("Message").Utf8(message.Message)
+ .EndStruct();
+ }
+ rows.EndList();
+
+ auto bulkUpsertOperation = [table, rowsValue = rows.Build()](NYdb::NTable::TTableClient& tableClient) {
+ NYdb::TValue r = rowsValue;
+ auto status = tableClient.BulkUpsert(table, std::move(r));
+ return status.GetValueSync();
+ };
+
+ auto status = tableClient.RetryOperationSync(bulkUpsertOperation, retrySettings);
+
+ if (!status.IsSuccess()) {
+ Cerr << Endl << "Write failed with status: " << (const NYdb::TStatus&)status << Endl;
+ return false;
+ }
+ return true;
+}
+
+bool CreateLogTable(NYdb::NTable::TTableClient& client, const TString& table) {
+ Cerr << "Create table " << table << "\n";
+
+ NYdb::NTable::TRetryOperationSettings settings;
+ auto status = client.RetryOperationSync([&table](NYdb::NTable::TSession session) {
+ auto tableDesc = NYdb::NTable::TTableBuilder()
+ .AddNullableColumn("App", NYdb::EPrimitiveType::Utf8)
+ .AddNullableColumn("Timestamp", NYdb::EPrimitiveType::Timestamp)
+ .AddNullableColumn("Host", NYdb::EPrimitiveType::Utf8)
+ .AddNullableColumn("HttpCode", NYdb::EPrimitiveType::Uint32)
+ .AddNullableColumn("Message", NYdb::EPrimitiveType::Utf8)
+ .SetPrimaryKeyColumns({"App", "Timestamp", "Host"})
+ .Build();
+
+ return session.CreateTable(table, std::move(tableDesc)).GetValueSync();
+ }, settings);
+
+ if (!status.IsSuccess()) {
+ Cerr << "Create table failed with status: " << status << Endl;
+ return false;
+ }
+ return true;
+}
+
+bool Run(const NYdb::TDriver &driver, const TString &table, ui32 batchCount) {
+ NYdb::NTable::TTableClient client(driver);
+ if (!CreateLogTable(client, table)) {
+ return false;
+ }
+
+ NYdb::NTable::TRetryOperationSettings writeRetrySettings;
+ writeRetrySettings
+ .Idempotent(true)
+ .MaxRetries(20);
+
+ TVector<TLogMessage> logBatch;
+ for (ui32 offset = 0; offset < batchCount; ++offset) {
+ GetLogBatch(offset, logBatch);
+ if (!WriteLogBatch(client, table, logBatch, writeRetrySettings)) {
+ return false;
+ }
+ Cerr << ".";
+ }
+
+ Cerr << Endl << "Done." << Endl;
+ return true;
+}
+
+TString JoinPath(const TString& basePath, const TString& path) {
+ if (basePath.empty()) {
+ return path;
+ }
+
+ TPathSplitUnix prefixPathSplit(basePath);
+ prefixPathSplit.AppendComponent(path);
+
+ return prefixPathSplit.Reconstruct();
+}
+
+int main(int argc, char** argv) {
+ using namespace NLastGetopt;
+
+ TOpts opts = TOpts::Default();
+
+ TString endpoint;
+ TString database;
+ TString table = "bulk_upsert_example";
+ ui32 count = 1000;
+
+ opts.AddLongOption('e', "endpoint", "YDB endpoint").Required().RequiredArgument("HOST:PORT")
+ .StoreResult(&endpoint);
+ opts.AddLongOption('d', "database", "YDB database name").Required().RequiredArgument("PATH")
+ .StoreResult(&database);
+ opts.AddLongOption('p', "table", "Path for table").Optional().RequiredArgument("PATH")
+ .StoreResult(&table);
+ opts.AddLongOption('c', "count", "count requests").Optional().RequiredArgument("NUM")
+ .StoreResult(&count).DefaultValue(count);
+
+ TOptsParseResult res(&opts, argc, argv);
+ Y_UNUSED(res);
+
+ table = JoinPath(database, table);
+
+ auto driverConfig = NYdb::TDriverConfig()
+ .SetEndpoint(endpoint)
+ .SetDatabase(database)
+ .SetAuthToken(GetEnv("YDB_TOKEN"));
+
+ NYdb::TDriver driver(driverConfig);
+
+ if (!Run(driver, table, count)) {
+ driver.Stop(true);
+ return 2;
+ }
+
+ driver.Stop(true);
+
+ return 0;
+}
diff --git a/ydb/public/sdk/cpp/examples/bulk_upsert_simple/ya.make b/ydb/public/sdk/cpp/examples/bulk_upsert_simple/ya.make
new file mode 100644
index 0000000000..320989a8a1
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/bulk_upsert_simple/ya.make
@@ -0,0 +1,14 @@
+OWNER(g:kikimr)
+
+PROGRAM()
+
+SRCS(
+ main.cpp
+)
+
+PEERDIR(
+ library/cpp/getopt
+ ydb/public/sdk/cpp/client/ydb_table
+)
+
+END()
diff --git a/ydb/public/sdk/cpp/examples/pagination/main.cpp b/ydb/public/sdk/cpp/examples/pagination/main.cpp
new file mode 100644
index 0000000000..04a9bce64a
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/pagination/main.cpp
@@ -0,0 +1,46 @@
+#include "pagination.h"
+
+#include <library/cpp/getopt/last_getopt.h>
+#include <util/system/env.h>
+
+using namespace NLastGetopt;
+using namespace NYdb;
+
+void StopHandler(int) {
+ exit(1);
+}
+
+int main(int argc, char** argv) {
+ TOpts opts = TOpts::Default();
+
+ TString endpoint;
+ TString database;
+ TString path;
+ opts.AddLongOption('e', "endpoint", "YDB endpoint").Required().RequiredArgument("HOST:PORT")
+ .StoreResult(&endpoint);
+ opts.AddLongOption('d', "database", "YDB database name").Required().RequiredArgument("PATH")
+ .StoreResult(&database);
+ opts.AddLongOption('p', "path", "Base path for tables").Optional().RequiredArgument("PATH")
+ .StoreResult(&path);
+
+ signal(SIGINT, &StopHandler);
+ signal(SIGTERM, &StopHandler);
+
+ TOptsParseResult res(&opts, argc, argv);
+
+ if (path.empty()) {
+ path = database;
+ }
+
+ auto driverConfig = TDriverConfig()
+ .SetEndpoint(endpoint)
+ .SetDatabase(database)
+ .SetAuthToken(GetEnv("YDB_TOKEN"));
+ TDriver driver(driverConfig);
+
+ if (!Run(driver, path)) {
+ return 2;
+ }
+
+ return 0;
+}
diff --git a/ydb/public/sdk/cpp/examples/pagination/pagination.cpp b/ydb/public/sdk/cpp/examples/pagination/pagination.cpp
new file mode 100644
index 0000000000..f81d2580b8
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/pagination/pagination.cpp
@@ -0,0 +1,191 @@
+#include "pagination.h"
+
+#include <util/folder/pathsplit.h>
+#include <util/string/printf.h>
+
+using namespace NYdb;
+using namespace NYdb::NTable;
+
+const ui32 MaxPages = 10;
+
+class TYdbErrorException : public yexception {
+public:
+ TYdbErrorException(const TStatus& status)
+ : Status(status) {}
+
+ TStatus Status;
+};
+
+static void ThrowOnError(const TStatus& status) {
+ if (!status.IsSuccess()) {
+ throw TYdbErrorException(status);
+ }
+}
+
+static void PrintStatus(const TStatus& status) {
+ Cerr << "Status: " << status.GetStatus() << Endl;
+ status.GetIssues().PrintTo(Cerr);
+}
+
+static TString JoinPath(const TString& basePath, const TString& path) {
+ if (basePath.empty()) {
+ return path;
+ }
+
+ TPathSplitUnix prefixPathSplit(basePath);
+ prefixPathSplit.AppendComponent(path);
+
+ return prefixPathSplit.Reconstruct();
+}
+
+//! Creates sample table with CrateTable API.
+static void CreateTable(TTableClient client, const TString& path) {
+ ThrowOnError(client.RetryOperationSync([path](TSession session) {
+ auto schoolsDesc = TTableBuilder()
+ .AddNullableColumn("city", EPrimitiveType::Utf8)
+ .AddNullableColumn("number", EPrimitiveType::Uint32)
+ .AddNullableColumn("address", EPrimitiveType::Utf8)
+ .SetPrimaryKeyColumns({ "city", "number" })
+ .Build();
+
+ return session.CreateTable(JoinPath(path, "schools"), std::move(schoolsDesc)).GetValueSync();
+ }));
+}
+
+//! Fills sample tables with data in single parameterized data query.
+static TStatus FillTableDataTransaction(TSession& session, const TString& path) {
+ auto query = Sprintf(R"(
+ --!syntax_v1
+ PRAGMA TablePathPrefix("%s");
+
+ DECLARE $schoolsData AS List<Struct<
+ city: Utf8,
+ number: Uint32,
+ address: Utf8>>;
+
+ REPLACE INTO schools
+ SELECT
+ city,
+ number,
+ address
+ FROM AS_TABLE($schoolsData);
+ )", path.c_str());
+
+ auto params = GetTablesDataParams();
+
+ return session.ExecuteDataQuery(
+ query,
+ TTxControl::BeginTx(TTxSettings::SerializableRW()).CommitTx(),
+ std::move(params)).GetValueSync();
+}
+
+//! Shows usage of query paging.
+static TStatus SelectPagingTransaction(TSession& session, const TString& path,
+ ui64 pageLimit, const TString& lastCity, ui32 lastNumber, TMaybe<TResultSet>& resultSet)
+{
+ auto query = Sprintf(R"(
+ --!syntax_v1
+ PRAGMA TablePathPrefix("%s");
+
+ DECLARE $limit AS Uint64;
+ DECLARE $lastCity AS Utf8;
+ DECLARE $lastNumber AS Uint32;
+
+ $part1 = (
+ SELECT * FROM schools
+ WHERE city = $lastCity AND number > $lastNumber
+ ORDER BY city, number LIMIT $limit
+ );
+
+ $part2 = (
+ SELECT * FROM schools
+ WHERE city > $lastCity
+ ORDER BY city, number LIMIT $limit
+ );
+
+ $union = (
+ SELECT * FROM $part1
+ UNION ALL
+ SELECT * FROM $part2
+ );
+
+ SELECT * FROM $union
+ ORDER BY city, number LIMIT $limit;
+ )", path.c_str());
+
+ auto params = session.GetParamsBuilder()
+ .AddParam("$limit")
+ .Uint64(pageLimit)
+ .Build()
+ .AddParam("$lastCity")
+ .Utf8(lastCity)
+ .Build()
+ .AddParam("$lastNumber")
+ .Uint32(lastNumber)
+ .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;
+}
+
+bool SelectPaging(TTableClient client, const TString& path, ui64 pageLimit, TString& lastCity, ui32& lastNumber) {
+ TMaybe<TResultSet> resultSet;
+ ThrowOnError(client.RetryOperationSync([path, pageLimit, &lastCity, lastNumber, &resultSet](TSession session) {
+ return SelectPagingTransaction(session, path, pageLimit, lastCity, lastNumber, resultSet);
+ }));
+
+ TResultSetParser parser(*resultSet);
+
+ if (!parser.TryNextRow()) {
+ return false;
+ }
+ do {
+ lastCity = parser.ColumnParser("city").GetOptionalUtf8().GetRef();
+ lastNumber = parser.ColumnParser("number").GetOptionalUint32().GetRef();
+ Cout << lastCity << ", Школа №" << lastNumber << ", Адрес: " << parser.ColumnParser("address").GetOptionalUtf8() << Endl;
+ } while (parser.TryNextRow());
+ return true;
+}
+
+bool Run(const TDriver& driver, const TString& path) {
+ TTableClient client(driver);
+
+ try {
+ CreateTable(client, path);
+
+ ThrowOnError(client.RetryOperationSync([path](TSession session) {
+ return FillTableDataTransaction(session, path);
+ }));
+
+ ui64 limit = 3;
+ TString lastCity;
+ ui32 lastNumber = 0;
+ ui32 page = 0;
+ bool pageNotEmpty = true;
+
+ Cout << "> Pagination, Limit=" << limit << Endl;
+
+ // show first MaxPages=10 pages:
+ while (pageNotEmpty && page <= MaxPages) {
+ ++page;
+ Cout << "> Page " << page << ":" << Endl;
+ pageNotEmpty = SelectPaging(client, path, limit, lastCity, lastNumber);
+ }
+ }
+ catch (const TYdbErrorException& e) {
+ Cerr << "Execution failed due to fatal error:" << Endl;
+ PrintStatus(e.Status);
+ return false;
+ }
+
+ return true;
+}
diff --git a/ydb/public/sdk/cpp/examples/pagination/pagination.h b/ydb/public/sdk/cpp/examples/pagination/pagination.h
new file mode 100644
index 0000000000..d419c21668
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/pagination/pagination.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include <ydb/public/sdk/cpp/client/ydb_driver/driver.h>
+#include <ydb/public/sdk/cpp/client/ydb_table/table.h>
+
+NYdb::TParams GetTablesDataParams();
+
+bool Run(const NYdb::TDriver& driver, const TString& path);
diff --git a/ydb/public/sdk/cpp/examples/pagination/pagination_data.cpp b/ydb/public/sdk/cpp/examples/pagination/pagination_data.cpp
new file mode 100644
index 0000000000..30efd41f43
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/pagination/pagination_data.cpp
@@ -0,0 +1,53 @@
+#include "pagination.h"
+
+using namespace NYdb;
+using namespace NYdb::NTable;
+
+struct TSchool {
+ TString City;
+ ui32 Number;
+ TString Address;
+
+ TSchool(const TString& city, ui32 number, const TString& address)
+ : City(city)
+ , Number(number)
+ , Address(address) {}
+};
+
+TParams GetTablesDataParams() {
+ TVector<TSchool> schoolsData = {
+ TSchool("Орлов", 1, "Ст.Халтурина, 2"),
+ TSchool("Орлов", 2, "Свободы, 4"),
+ TSchool("Яранск", 1, "Гоголя, 25"),
+ TSchool("Яранск", 2, "Кирова, 18"),
+ TSchool("Яранск", 3, "Некрасова, 59"),
+ TSchool("Кирс", 3, "Кирова, 6"),
+ TSchool("Нолинск", 1, "Коммуны, 4"),
+ TSchool("Нолинск", 2, "Федосеева, 2Б"),
+ TSchool("Котельнич", 1, "Урицкого, 21"),
+ TSchool("Котельнич", 2, "Октябрьская, 109"),
+ TSchool("Котельнич", 3, "Советская, 153"),
+ TSchool("Котельнич", 5, "Школьная, 2"),
+ TSchool("Котельнич", 15, "Октябрьская, 91")
+ };
+
+ TParamsBuilder paramsBuilder;
+
+ auto& Param = paramsBuilder.AddParam("$schoolsData");
+ Param.BeginList();
+ for (auto& school : schoolsData) {
+ Param.AddListItem()
+ .BeginStruct()
+ .AddMember("city")
+ .Utf8(school.City)
+ .AddMember("number")
+ .Uint32(school.Number)
+ .AddMember("address")
+ .Utf8(school.Address)
+ .EndStruct();
+ }
+ Param.EndList();
+ Param.Build();
+
+ return paramsBuilder.Build();
+}
diff --git a/ydb/public/sdk/cpp/examples/pagination/ya.make b/ydb/public/sdk/cpp/examples/pagination/ya.make
new file mode 100644
index 0000000000..49d007d77c
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/pagination/ya.make
@@ -0,0 +1,16 @@
+OWNER(g:kikimr)
+
+PROGRAM()
+
+SRCS(
+ main.cpp
+ pagination_data.cpp
+ pagination.cpp
+)
+
+PEERDIR(
+ library/cpp/getopt
+ ydb/public/sdk/cpp/client/ydb_table
+)
+
+END()
diff --git a/ydb/public/sdk/cpp/examples/secondary_index/main.cpp b/ydb/public/sdk/cpp/examples/secondary_index/main.cpp
new file mode 100644
index 0000000000..830926298c
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/secondary_index/main.cpp
@@ -0,0 +1,72 @@
+#include "secondary_index.h"
+
+#include <util/system/env.h>
+
+using namespace NLastGetopt;
+using namespace NYdb;
+
+////////////////////////////////////////////////////////////////////////////////
+
+int main(int argc, char** argv) {
+ TOpts opts = TOpts::Default();
+
+ TString endpoint;
+ TString database;
+ TString prefix;
+
+ opts.AddLongOption('e', "endpoint", "YDB endpoint").Required().RequiredArgument("HOST:PORT")
+ .StoreResult(&endpoint);
+ opts.AddLongOption('d', "database", "YDB database name").Required().RequiredArgument("PATH")
+ .StoreResult(&database);
+ opts.AddLongOption('p', "prefix", "Base prefix for tables").Optional().RequiredArgument("PATH")
+ .StoreResult(&prefix);
+ opts.SetFreeArgsMin(1);
+ opts.SetFreeArgTitle(0, "<COMMAND>", GetCmdList());
+ opts.ArgPermutation_ = NLastGetopt::REQUIRE_ORDER;
+
+ TOptsParseResult res(&opts, argc, argv);
+ size_t freeArgsPos = res.GetFreeArgsPos();
+ argc -= freeArgsPos;
+ argv += freeArgsPos;
+
+ ECmd cmd = ParseCmd(*argv);
+ if (cmd == ECmd::NONE) {
+ Cerr << "Unsupported command '" << *argv << "'" << Endl;
+ return 1;
+ }
+
+ if (prefix.empty()) {
+ prefix = database;
+ }
+
+ auto config = TDriverConfig()
+ .SetEndpoint(endpoint)
+ .SetDatabase(database)
+ .SetAuthToken(GetEnv("YDB_TOKEN"));
+ TDriver driver(config);
+
+ try {
+ switch (cmd) {
+ case ECmd::NONE:
+ break;
+ case ECmd::CREATE_TABLES:
+ return RunCreateTables(driver, prefix, argc, argv);
+ case ECmd::DROP_TABLES:
+ return RunDropTables(driver, prefix, argc, argv);
+ case ECmd::UPDATE_VIEWS:
+ return RunUpdateViews(driver, prefix, argc, argv);
+ case ECmd::LIST_SERIES:
+ return RunListSeries(driver, prefix, argc, argv);
+ case ECmd::GENERATE_SERIES:
+ return RunGenerateSeries(driver, prefix, argc, argv);
+ case ECmd::DELETE_SERIES:
+ return RunDeleteSeries(driver, prefix, argc, argv);
+ }
+ } catch (const TYdbErrorException& e) {
+ Cerr << "Execution failed: " << e << Endl;
+ return 1;
+ }
+
+ Y_UNREACHABLE();
+ return 1;
+}
diff --git a/ydb/public/sdk/cpp/examples/secondary_index/secondary_index.cpp b/ydb/public/sdk/cpp/examples/secondary_index/secondary_index.cpp
new file mode 100644
index 0000000000..bd2ec617a9
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/secondary_index/secondary_index.cpp
@@ -0,0 +1,40 @@
+#include "secondary_index.h"
+
+#include <util/folder/pathsplit.h>
+
+TString GetCmdList() {
+ return "create_tables, drop_tables, update_views, list, generate, delete";
+}
+
+ECmd ParseCmd(const char* cmd) {
+ if (!strcmp(cmd, "create_tables")) {
+ return ECmd::CREATE_TABLES;
+ }
+ if (!strcmp(cmd, "drop_tables")) {
+ return ECmd::DROP_TABLES;
+ }
+ if (!strcmp(cmd, "update_views")) {
+ return ECmd::UPDATE_VIEWS;
+ }
+ if (!strcmp(cmd, "list")) {
+ return ECmd::LIST_SERIES;
+ }
+ if (!strcmp(cmd, "generate")) {
+ return ECmd::GENERATE_SERIES;
+ }
+ if (!strcmp(cmd, "delete")) {
+ return ECmd::DELETE_SERIES;
+ }
+ return ECmd::NONE;
+}
+
+TString JoinPath(const TString& prefix, const TString& path) {
+ if (prefix.empty()) {
+ return path;
+ }
+
+ TPathSplitUnix prefixPathSplit(prefix);
+ prefixPathSplit.AppendComponent(path);
+
+ return prefixPathSplit.Reconstruct();
+}
diff --git a/ydb/public/sdk/cpp/examples/secondary_index/secondary_index.h b/ydb/public/sdk/cpp/examples/secondary_index/secondary_index.h
new file mode 100644
index 0000000000..94b249f53d
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/secondary_index/secondary_index.h
@@ -0,0 +1,80 @@
+#pragma once
+
+#include <ydb/public/sdk/cpp/client/ydb_driver/driver.h>
+#include <ydb/public/sdk/cpp/client/ydb_table/table.h>
+
+#include <library/cpp/getopt/last_getopt.h>
+
+#include <util/generic/string.h>
+#include <util/generic/yexception.h>
+#include <util/stream/output.h>
+#include <util/string/builder.h>
+#include <util/string/printf.h>
+
+////////////////////////////////////////////////////////////////////////////////
+
+#define TABLE_SERIES "series"
+#define TABLE_SERIES_REV_VIEWS "series_rev_views"
+
+struct TSeries {
+ ui64 SeriesId;
+ TString Title;
+ TString SeriesInfo;
+ TInstant ReleaseDate;
+ ui64 Views;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+enum class ECmd {
+ NONE,
+ CREATE_TABLES,
+ DROP_TABLES,
+ UPDATE_VIEWS,
+ LIST_SERIES,
+ GENERATE_SERIES,
+ DELETE_SERIES,
+};
+
+TString GetCmdList();
+ECmd ParseCmd(const char* cmd);
+
+////////////////////////////////////////////////////////////////////////////////
+
+TString JoinPath(const TString& prefix, const TString& path);
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TYdbErrorException : public yexception {
+public:
+ TYdbErrorException(NYdb::TStatus status)
+ : Status(std::move(status))
+ { }
+
+ friend IOutputStream& operator<<(IOutputStream& out, const TYdbErrorException& e) {
+ out << "Status: " << e.Status.GetStatus();
+ if (e.Status.GetIssues()) {
+ out << Endl;
+ e.Status.GetIssues().PrintTo(out);
+ }
+ return out;
+ }
+
+private:
+ NYdb::TStatus Status;
+};
+
+inline void ThrowOnError(NYdb::TStatus status) {
+ if (!status.IsSuccess()) {
+ throw TYdbErrorException(std::move(status));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+int RunCreateTables(NYdb::TDriver& driver, const TString& prefix, int argc, char** argv);
+int RunDropTables(NYdb::TDriver& driver, const TString& prefix, int argc, char** argv);
+int RunUpdateViews(NYdb::TDriver& driver, const TString& prefix, int argc, char** argv);
+int RunListSeries(NYdb::TDriver& driver, const TString& prefix, int argc, char** argv);
+int RunGenerateSeries(NYdb::TDriver& driver, const TString& prefix, int argc, char** argv);
+int RunDeleteSeries(NYdb::TDriver& driver, const TString& prefix, int argc, char** argv);
diff --git a/ydb/public/sdk/cpp/examples/secondary_index/secondary_index_create.cpp b/ydb/public/sdk/cpp/examples/secondary_index/secondary_index_create.cpp
new file mode 100644
index 0000000000..f96ca7ff08
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/secondary_index/secondary_index_create.cpp
@@ -0,0 +1,46 @@
+#include "secondary_index.h"
+
+using namespace NLastGetopt;
+using namespace NYdb;
+using namespace NYdb::NTable;
+
+////////////////////////////////////////////////////////////////////////////////
+
+static void CreateSeriesTable(TTableClient& client, const TString& prefix) {
+ ThrowOnError(client.RetryOperationSync([prefix](TSession session) {
+ auto desc = TTableBuilder()
+ .AddNullableColumn("series_id", EPrimitiveType::Uint64)
+ .AddNullableColumn("title", EPrimitiveType::Utf8)
+ .AddNullableColumn("series_info", EPrimitiveType::Utf8)
+ .AddNullableColumn("release_date", EPrimitiveType::Uint32)
+ .AddNullableColumn("views", EPrimitiveType::Uint64)
+ .SetPrimaryKeyColumn("series_id")
+ .Build();
+
+ return session.CreateTable(JoinPath(prefix, TABLE_SERIES), std::move(desc)).ExtractValueSync();
+ }));
+}
+
+static void CreateSeriesIndexTable(TTableClient& client, const TString& prefix) {
+ ThrowOnError(client.RetryOperationSync([prefix](TSession session) {
+ auto desc = TTableBuilder()
+ .AddNullableColumn("rev_views", EPrimitiveType::Uint64)
+ .AddNullableColumn("series_id", EPrimitiveType::Uint64)
+ .SetPrimaryKeyColumns({ "rev_views", "series_id" })
+ .Build();
+
+ return session.CreateTable(JoinPath(prefix, TABLE_SERIES_REV_VIEWS), std::move(desc)).ExtractValueSync();
+ }));
+}
+
+int RunCreateTables(TDriver& driver, const TString& prefix, int argc, char**) {
+ if (argc > 1) {
+ Cerr << "Unexpected arguments after create_tables" << Endl;
+ return 1;
+ }
+
+ TTableClient client(driver);
+ CreateSeriesTable(client, prefix);
+ CreateSeriesIndexTable(client, prefix);
+ return 0;
+}
diff --git a/ydb/public/sdk/cpp/examples/secondary_index/secondary_index_delete.cpp b/ydb/public/sdk/cpp/examples/secondary_index/secondary_index_delete.cpp
new file mode 100644
index 0000000000..27e8695f29
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/secondary_index/secondary_index_delete.cpp
@@ -0,0 +1,79 @@
+#include "secondary_index.h"
+
+using namespace NLastGetopt;
+using namespace NYdb;
+using namespace NYdb::NTable;
+
+////////////////////////////////////////////////////////////////////////////////
+
+static TStatus DeleteSeries(TSession& session, const TString& prefix, ui64 seriesId, ui64& deletedCount) {
+ auto queryText = Sprintf(R"(
+ --!syntax_v1
+ PRAGMA TablePathPrefix("%1$s");
+
+ DECLARE $seriesId AS Uint64;
+
+ -- Simulate a DESC index by inverting views using max(uint64)-views
+ $maxUint64 = 0xffffffffffffffff;
+
+ $data = (
+ SELECT series_id, ($maxUint64 - views) AS rev_views
+ FROM [series]
+ WHERE series_id = $seriesId
+ );
+
+ DELETE FROM series
+ ON SELECT series_id FROM $data;
+
+ DELETE FROM series_rev_views
+ ON SELECT rev_views, series_id FROM $data;
+
+ SELECT COUNT(*) AS cnt FROM $data;
+ )", prefix.data());
+
+ auto prepareResult = session.PrepareDataQuery(queryText).ExtractValueSync();
+ if (!prepareResult.IsSuccess()) {
+ return prepareResult;
+ }
+
+ auto query = prepareResult.GetQuery();
+
+ auto params = query.GetParamsBuilder()
+ .AddParam("$seriesId")
+ .Uint64(seriesId)
+ .Build()
+ .Build();
+
+ auto result = query.Execute(
+ TTxControl::BeginTx(TTxSettings::SerializableRW()).CommitTx(),
+ std::move(params)).ExtractValueSync();
+
+ if (result.IsSuccess()) {
+ auto parser = result.GetResultSetParser(0);
+ if (parser.TryNextRow()) {
+ deletedCount = parser.ColumnParser(0).GetUint64();
+ }
+ }
+
+ return result;
+}
+
+int RunDeleteSeries(TDriver& driver, const TString& prefix, int argc, char** argv) {
+ TOpts opts = TOpts::Default();
+
+ ui64 seriesId;
+
+ opts.AddLongOption("id", "Series id").Required().RequiredArgument("NUM")
+ .StoreResult(&seriesId);
+
+ TOptsParseResult res(&opts, argc, argv);
+
+ ui64 deletedCount = 0;
+ TTableClient client(driver);
+ ThrowOnError(client.RetryOperationSync([&](TSession session) -> TStatus {
+ return DeleteSeries(session, prefix, seriesId, deletedCount);
+ }));
+
+ Cout << "Deleted " << deletedCount << " rows" << Endl;
+ return 0;
+}
diff --git a/ydb/public/sdk/cpp/examples/secondary_index/secondary_index_drop.cpp b/ydb/public/sdk/cpp/examples/secondary_index/secondary_index_drop.cpp
new file mode 100644
index 0000000000..810dd49543
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/secondary_index/secondary_index_drop.cpp
@@ -0,0 +1,25 @@
+#include "secondary_index.h"
+
+using namespace NLastGetopt;
+using namespace NYdb;
+using namespace NYdb::NTable;
+
+////////////////////////////////////////////////////////////////////////////////
+
+static void DropTable(TTableClient& client, const TString& path) {
+ ThrowOnError(client.RetryOperationSync([path](TSession session) {
+ return session.DropTable(path).ExtractValueSync();
+ }));
+}
+
+int RunDropTables(TDriver& driver, const TString& prefix, int argc, char**) {
+ if (argc > 1) {
+ Cerr << "Unexpected arguments after drop_tables" << Endl;
+ return 1;
+ }
+
+ TTableClient client(driver);
+ DropTable(client, JoinPath(prefix, TABLE_SERIES));
+ DropTable(client, JoinPath(prefix, TABLE_SERIES_REV_VIEWS));
+ return 0;
+}
diff --git a/ydb/public/sdk/cpp/examples/secondary_index/secondary_index_generate.cpp b/ydb/public/sdk/cpp/examples/secondary_index/secondary_index_generate.cpp
new file mode 100644
index 0000000000..a363101857
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/secondary_index/secondary_index_generate.cpp
@@ -0,0 +1,206 @@
+#include "secondary_index.h"
+
+#include <util/random/random.h>
+#include <util/thread/pool.h>
+
+using namespace NLastGetopt;
+using namespace NYdb;
+using namespace NYdb::NTable;
+
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TExecutor {
+public:
+ TExecutor()
+ : Queue(TThreadPool::TParams().SetBlocking(true).SetCatching(false))
+ { }
+
+ ~TExecutor() {
+ Stop(false);
+ }
+
+ void Start(size_t threads) {
+ Queue.Start(threads, threads);
+ }
+
+ void Stop(bool rethrow = true) {
+ with_lock (Lock) {
+ Stopped = true;
+ }
+ Wait(rethrow);
+ }
+
+ void Wait(bool rethrow = true) {
+ Queue.Stop();
+ if (rethrow) {
+ with_lock (Lock) {
+ if (Exception) {
+ std::rethrow_exception(Exception);
+ }
+ }
+ }
+ }
+
+ bool Execute(std::function<void()> callback) {
+ with_lock (Lock) {
+ if (Stopped) {
+ if (Exception) {
+ std::rethrow_exception(Exception);
+ } else {
+ return false;
+ }
+ }
+ }
+
+ THolder<TTask> task = MakeHolder<TTask>(this, std::move(callback));
+ if (Queue.Add(task.Get())) {
+ Y_UNUSED(task.Release());
+ return true;
+ }
+
+ return false;
+ }
+
+private:
+ struct TTask : public IObjectInQueue {
+ TExecutor* const Owner;
+ std::function<void()> Callback;
+
+ TTask(TExecutor* owner, std::function<void()> callback)
+ : Owner(owner)
+ , Callback(std::move(callback))
+ { }
+
+ void Process(void*) override {
+ THolder<TTask> self(this);
+ with_lock (Owner->Lock) {
+ if (Owner->Stopped) {
+ return;
+ }
+ }
+ try {
+ auto callback = std::move(Callback);
+ callback();
+ } catch (...) {
+ with_lock (Owner->Lock) {
+ if (!Owner->Stopped && !Owner->Exception) {
+ Owner->Stopped = true;
+ Owner->Exception = std::current_exception();
+ }
+ }
+ }
+ }
+ };
+
+private:
+ TAdaptiveLock Lock;
+ bool Stopped = false;
+ std::exception_ptr Exception;
+ TThreadPool Queue;
+};
+
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+static TStatus InsertSeries(TSession& session, const TString& prefix, const TSeries& series) {
+ auto queryText = Sprintf(R"(
+ --!syntax_v1
+ PRAGMA TablePathPrefix("%1$s");
+
+ DECLARE $seriesId AS Uint64;
+ DECLARE $title AS Utf8;
+ DECLARE $seriesInfo AS Utf8;
+ DECLARE $releaseDate AS Uint32;
+ DECLARE $views AS Uint64;
+
+ -- Simulate a DESC index by inverting views using max(uint64)-views
+ $maxUint64 = 0xffffffffffffffff;
+ $revViews = $maxUint64 - $views;
+
+ INSERT INTO series (series_id, title, series_info, release_date, views)
+ VALUES ($seriesId, $title, $seriesInfo, $releaseDate, $views);
+
+ -- Insert above already verified series_id is unique, so it is safe to use upsert
+ UPSERT INTO series_rev_views (rev_views, series_id)
+ VALUES ($revViews, $seriesId);
+ )", prefix.data());
+
+ auto prepareResult = session.PrepareDataQuery(queryText).ExtractValueSync();
+ if (!prepareResult.IsSuccess()) {
+ return prepareResult;
+ }
+
+ auto query = prepareResult.GetQuery();
+
+ auto params = query.GetParamsBuilder()
+ .AddParam("$seriesId")
+ .Uint64(series.SeriesId)
+ .Build()
+ .AddParam("$title")
+ .Utf8(series.Title)
+ .Build()
+ .AddParam("$seriesInfo")
+ .Utf8(series.SeriesInfo)
+ .Build()
+ .AddParam("$releaseDate")
+ .Uint32(series.ReleaseDate.Days())
+ .Build()
+ .AddParam("$views")
+ .Uint64(series.Views)
+ .Build()
+ .Build();
+
+ auto result = query.Execute(
+ TTxControl::BeginTx(TTxSettings::SerializableRW()).CommitTx(),
+ std::move(params)).ExtractValueSync();
+
+ return result;
+}
+
+int RunGenerateSeries(TDriver& driver, const TString& prefix, int argc, char** argv) {
+ TOpts opts = TOpts::Default();
+
+ ui64 seriesId = 1;
+ ui64 count = 10;
+ size_t threads = 10;
+
+ opts.AddLongOption("start", "First id to generate").Optional().RequiredArgument("NUM")
+ .StoreResult(&seriesId);
+ opts.AddLongOption("count", "Number of series to generate").Required().RequiredArgument("NUM")
+ .StoreResult(&count);
+ opts.AddLongOption("threads", "Number of threads to use").Optional().RequiredArgument("NUM")
+ .StoreResult(&threads);
+ TOptsParseResult res(&opts, argc, argv);
+
+ TTableClient client(driver);
+ TExecutor executor;
+ executor.Start(threads);
+
+ size_t generated = 0;
+ while (count > 0) {
+ bool ok = executor.Execute([&client, &prefix, seriesId] {
+ TSeries series;
+ series.SeriesId = seriesId;
+ series.Title = TStringBuilder() << "Name " << seriesId;
+ series.SeriesInfo = TStringBuilder() << "Info " << seriesId;
+ series.ReleaseDate = TInstant::Days(TInstant::Now().Days());
+ series.Views = RandomNumber<ui64>(1000000);
+ ThrowOnError(client.RetryOperationSync([&prefix, &series](TSession session) -> TStatus {
+ return InsertSeries(session, prefix, series);
+ }));
+ });
+ if (!ok) {
+ break;
+ }
+ ++generated;
+ ++seriesId;
+ --count;
+ }
+
+ executor.Wait();
+ Cout << "Generated " << generated << " new series" << Endl;
+ return 0;
+}
diff --git a/ydb/public/sdk/cpp/examples/secondary_index/secondary_index_list.cpp b/ydb/public/sdk/cpp/examples/secondary_index/secondary_index_list.cpp
new file mode 100644
index 0000000000..b761669a22
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/secondary_index/secondary_index_list.cpp
@@ -0,0 +1,336 @@
+#include "secondary_index.h"
+
+#include <util/charset/utf8.h>
+
+using namespace NLastGetopt;
+using namespace NYdb;
+using namespace NYdb::NTable;
+
+////////////////////////////////////////////////////////////////////////////////
+
+static void ParseSeries(TVector<TSeries>& results, TResultSetParser&& parser) {
+ results.clear();
+ while (parser.TryNextRow()) {
+ auto& series = results.emplace_back();
+ series.SeriesId = *parser.ColumnParser(0).GetOptionalUint64();
+ series.Title = *parser.ColumnParser(1).GetOptionalUtf8();
+ series.SeriesInfo = *parser.ColumnParser(2).GetOptionalUtf8();
+ series.ReleaseDate = TInstant::Days(*parser.ColumnParser(3).GetOptionalUint32());
+ series.Views = *parser.ColumnParser(4).GetOptionalUint64();
+ }
+}
+
+static TStatus ListByViews(
+ TVector<TSeries>& results,
+ TSession& session,
+ const TString& prefix,
+ ui64 limit,
+ ui64 lastSeriesId,
+ ui64 lastViews)
+{
+ auto queryText = Sprintf(R"(
+ --!syntax_v1
+ PRAGMA TablePathPrefix("%1$s");
+
+ DECLARE $limit AS Uint64;
+ DECLARE $lastSeriesId AS Uint64;
+ DECLARE $lastViews AS Uint64;
+
+ -- Simulate a DESC index by inverting views using max(uint64)-views
+ $maxUint64 = 0xffffffffffffffff;
+ $lastRevViews = $maxUint64 - $lastViews;
+
+ $filterRaw = (
+ SELECT rev_views, series_id
+ FROM series_rev_views
+ WHERE rev_views = $lastRevViews AND series_id > $lastSeriesId
+ ORDER BY rev_views, series_id
+ LIMIT $limit
+ UNION ALL
+ SELECT rev_views, series_id
+ FROM series_rev_views
+ WHERE rev_views > $lastRevViews
+ ORDER BY rev_views, series_id
+ LIMIT $limit
+ );
+
+ -- $filterRaw may have more than $limit rows
+ $filter = (
+ SELECT rev_views, series_id
+ FROM $filterRaw
+ ORDER BY rev_views, series_id
+ LIMIT $limit
+ );
+
+ SELECT t2.series_id AS series_id, title, series_info, release_date, views
+ FROM $filter AS t1
+ INNER JOIN series AS t2 USING (series_id)
+ ORDER BY views DESC, series_id ASC;
+ )", prefix.data());
+
+ auto prepareResult = session.PrepareDataQuery(queryText).ExtractValueSync();
+ if (!prepareResult.IsSuccess()) {
+ return prepareResult;
+ }
+
+ auto query = prepareResult.GetQuery();
+
+ auto params = query.GetParamsBuilder()
+ .AddParam("$limit")
+ .Uint64(limit)
+ .Build()
+ .AddParam("$lastSeriesId")
+ .Uint64(lastSeriesId)
+ .Build()
+ .AddParam("$lastViews")
+ .Uint64(lastViews)
+ .Build()
+ .Build();
+
+ auto result = query.Execute(
+ TTxControl::BeginTx(TTxSettings::SerializableRW()).CommitTx(),
+ std::move(params)).ExtractValueSync();
+
+ if (result.IsSuccess()) {
+ ParseSeries(results, result.GetResultSetParser(0));
+ }
+
+ return result;
+}
+
+static TStatus ListByViews(
+ TVector<TSeries>& results,
+ TSession& session,
+ const TString& prefix,
+ ui64 limit)
+{
+ auto queryText = Sprintf(R"(
+ --!syntax_v1
+ PRAGMA TablePathPrefix("%1$s");
+
+ DECLARE $limit AS Uint64;
+
+ $filter = (
+ SELECT rev_views, series_id
+ FROM series_rev_views
+ ORDER BY rev_views, series_id
+ LIMIT $limit
+ );
+
+ SELECT t2.series_id AS series_id, title, series_info, release_date, views
+ FROM $filter AS t1
+ INNER JOIN series AS t2 USING (series_id)
+ ORDER BY views DESC, series_id ASC;
+ )", prefix.data());
+
+ auto prepareResult = session.PrepareDataQuery(queryText).ExtractValueSync();
+ if (!prepareResult.IsSuccess()) {
+ return prepareResult;
+ }
+
+ auto query = prepareResult.GetQuery();
+
+ auto params = query.GetParamsBuilder()
+ .AddParam("$limit")
+ .Uint64(limit)
+ .Build()
+ .Build();
+
+ auto result = query.Execute(
+ TTxControl::BeginTx(TTxSettings::SerializableRW()).CommitTx(),
+ std::move(params)).ExtractValueSync();
+
+ if (result.IsSuccess()) {
+ ParseSeries(results, result.GetResultSetParser(0));
+ }
+
+ return result;
+}
+
+static TStatus ListById(
+ TVector<TSeries>& results,
+ TSession& session,
+ const TString& prefix,
+ ui64 limit,
+ ui64 lastSeriesId)
+{
+ auto queryText = Sprintf(R"(
+ --!syntax_v1
+ PRAGMA TablePathPrefix("%1$s");
+
+ DECLARE $limit AS Uint64;
+ DECLARE $lastSeriesId AS Uint64;
+
+ SELECT series_id, title, series_info, release_date, views
+ FROM series
+ WHERE series_id > $lastSeriesId
+ ORDER BY series_id
+ LIMIT $limit;
+ )", prefix.data());
+
+ auto prepareResult = session.PrepareDataQuery(queryText).ExtractValueSync();
+ if (!prepareResult.IsSuccess()) {
+ return prepareResult;
+ }
+
+ auto query = prepareResult.GetQuery();
+
+ auto params = query.GetParamsBuilder()
+ .AddParam("$limit")
+ .Uint64(limit)
+ .Build()
+ .AddParam("$lastSeriesId")
+ .Uint64(lastSeriesId)
+ .Build()
+ .Build();
+
+ auto result = query.Execute(
+ TTxControl::BeginTx(TTxSettings::SerializableRW()).CommitTx(),
+ std::move(params)).ExtractValueSync();
+
+ if (result.IsSuccess()) {
+ ParseSeries(results, result.GetResultSetParser(0));
+ }
+
+ return result;
+}
+
+static TStatus ListById(
+ TVector<TSeries>& results,
+ TSession& session,
+ const TString& prefix,
+ ui64 limit)
+{
+ auto queryText = Sprintf(R"(
+ --!syntax_v1
+ PRAGMA TablePathPrefix("%1$s");
+
+ DECLARE $limit AS Uint64;
+
+ SELECT series_id, title, series_info, release_date, views
+ FROM series
+ ORDER BY series_id
+ LIMIT $limit;
+ )", prefix.data());
+
+ auto prepareResult = session.PrepareDataQuery(queryText).ExtractValueSync();
+ if (!prepareResult.IsSuccess()) {
+ return prepareResult;
+ }
+
+ auto query = prepareResult.GetQuery();
+
+ auto params = query.GetParamsBuilder()
+ .AddParam("$limit")
+ .Uint64(limit)
+ .Build()
+ .Build();
+
+ auto result = query.Execute(
+ TTxControl::BeginTx(TTxSettings::SerializableRW()).CommitTx(),
+ std::move(params)).ExtractValueSync();
+
+ if (result.IsSuccess()) {
+ ParseSeries(results, result.GetResultSetParser(0));
+ }
+
+ return result;
+}
+
+int RunListSeries(TDriver& driver, const TString& prefix, int argc, char** argv) {
+ TOpts opts = TOpts::Default();
+
+ bool byViews = false;
+ ui64 limit = 10;
+ ui64 lastSeriesId = -1;
+ ui64 lastViews = -1;
+
+ opts.AddLongOption("by-views", "Sort by views").NoArgument().SetFlag(&byViews);
+ opts.AddLongOption("limit", "Maximum number of rows").Optional().RequiredArgument("NUM")
+ .StoreResult(&limit);
+ opts.AddLongOption("last-id", "Resume from this last series id").Optional().RequiredArgument("NUM")
+ .StoreResult(&lastSeriesId);
+ opts.AddLongOption("last-views", "Resume from this last series views").Optional().RequiredArgument("NUM")
+ .StoreResult(&lastViews);
+
+ TOptsParseResult res(&opts, argc, argv);
+
+ TVector<TSeries> results;
+ TTableClient client(driver);
+ ThrowOnError(client.RetryOperationSync([&](TSession session) -> TStatus {
+ if (byViews) {
+ if (res.Has("last-id") && res.Has("last-views")) {
+ return ListByViews(results, session, prefix, limit, lastSeriesId, lastViews);
+ } else {
+ return ListByViews(results, session, prefix, limit);
+ }
+ } else {
+ if (res.Has("last-id")) {
+ return ListById(results, session, prefix, limit, lastSeriesId);
+ } else {
+ return ListById(results, session, prefix, limit);
+ }
+ }
+ }));
+
+ size_t rows = results.size() + 1;
+ TVector<TString> columns[5];
+ for (size_t i = 0; i < 5; ++i) {
+ columns[i].reserve(rows);
+ }
+ columns[0].push_back("series_id");
+ columns[1].push_back("title");
+ columns[2].push_back("series_info");
+ columns[3].push_back("release_date");
+ columns[4].push_back("views");
+ for (const auto& result : results) {
+ columns[0].push_back(TStringBuilder() << result.SeriesId);
+ columns[1].push_back(TStringBuilder() << result.Title);
+ columns[2].push_back(TStringBuilder() << result.SeriesInfo);
+ columns[3].push_back(TStringBuilder() << result.ReleaseDate.FormatGmTime("%Y-%m-%d"));
+ columns[4].push_back(TStringBuilder() << result.Views);
+ }
+ size_t widths[5] = { 0 };
+ size_t total_width = 6;
+ for (size_t i = 0; i < 5; ++i) {
+ for (const auto& value : columns[i]) {
+ widths[i] = Max(widths[i], GetNumberOfUTF8Chars(value) + 2);
+ }
+ total_width += widths[i];
+ }
+ auto printLine = [&]() {
+ Cout << '+';
+ for (size_t i = 0; i < 5; ++i) {
+ for (size_t k = 0; k < widths[i]; ++k) {
+ Cout << '-';
+ }
+ Cout << '+';
+ }
+ Cout << Endl;
+ };
+ auto printRow = [&](size_t row) {
+ Cout << '|';
+ for (size_t i = 0; i < 5; ++i) {
+ Cout << ' ' << columns[i][row];
+ size_t printed = 1 + GetNumberOfUTF8Chars(columns[i][row]);
+ while (printed < widths[i]) {
+ Cout << ' ';
+ ++printed;
+ }
+ Cout << '|';
+ }
+ Cout << Endl;
+ };
+
+ printLine();
+ printRow(0);
+ printLine();
+ if (rows > 1) {
+ for (size_t row = 1; row < rows; ++row) {
+ printRow(row);
+ }
+ printLine();
+ }
+
+ return 0;
+}
diff --git a/ydb/public/sdk/cpp/examples/secondary_index/secondary_index_update.cpp b/ydb/public/sdk/cpp/examples/secondary_index/secondary_index_update.cpp
new file mode 100644
index 0000000000..14641e79cc
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/secondary_index/secondary_index_update.cpp
@@ -0,0 +1,87 @@
+#include "secondary_index.h"
+
+using namespace NLastGetopt;
+using namespace NYdb;
+using namespace NYdb::NTable;
+
+////////////////////////////////////////////////////////////////////////////////
+
+int RunUpdateViews(TDriver& driver, const TString& prefix, int argc, char** argv) {
+ TOpts opts = TOpts::Default();
+
+ ui64 seriesId;
+ ui64 newViews;
+
+ opts.AddLongOption("id", "Series id").Required().RequiredArgument("NUM")
+ .StoreResult(&seriesId);
+ opts.AddLongOption("views", "New views").Required().RequiredArgument("NUM")
+ .StoreResult(&newViews);
+
+ TOptsParseResult res(&opts, argc, argv);
+
+ TString queryText = Sprintf(R"(
+ --!syntax_v1
+ PRAGMA TablePathPrefix("%1$s");
+
+ DECLARE $seriesId AS Uint64;
+ DECLARE $newViews AS Uint64;
+
+ -- Simulate a DESC index by inverting views using max(uint64)-views
+ $maxUint64 = 0xffffffffffffffff;
+ $newRevViews = $maxUint64 - $newViews;
+
+ $data = (
+ SELECT series_id, ($maxUint64 - views) AS old_rev_views
+ FROM series
+ WHERE series_id = $seriesId
+ );
+
+ UPSERT INTO series
+ SELECT series_id, $newViews AS views FROM $data;
+
+ DELETE FROM series_rev_views
+ ON SELECT old_rev_views AS rev_views, series_id FROM $data;
+
+ UPSERT INTO series_rev_views
+ SELECT $newRevViews AS rev_views, series_id FROM $data;
+
+ SELECT COUNT(*) AS cnt FROM $data;
+ )", prefix.data());
+
+ ui64 updatedCount = 0;
+
+ TTableClient client(driver);
+ ThrowOnError(client.RetryOperationSync([&](TSession session) -> TStatus {
+ auto prepareResult = session.PrepareDataQuery(queryText).ExtractValueSync();
+ if (!prepareResult.IsSuccess()) {
+ return prepareResult;
+ }
+
+ auto query = prepareResult.GetQuery();
+
+ auto params = query.GetParamsBuilder()
+ .AddParam("$seriesId")
+ .Uint64(seriesId)
+ .Build()
+ .AddParam("$newViews")
+ .Uint64(newViews)
+ .Build()
+ .Build();
+
+ auto result = query.Execute(
+ TTxControl::BeginTx(TTxSettings::SerializableRW()).CommitTx(),
+ std::move(params)).ExtractValueSync();
+
+ if (result.IsSuccess()) {
+ auto parser = result.GetResultSetParser(0);
+ if (parser.TryNextRow()) {
+ updatedCount = parser.ColumnParser(0).GetUint64();
+ }
+ }
+
+ return result;
+ }));
+
+ Cout << "Updated " << updatedCount << " rows" << Endl;
+ return 0;
+}
diff --git a/ydb/public/sdk/cpp/examples/secondary_index/ya.make b/ydb/public/sdk/cpp/examples/secondary_index/ya.make
new file mode 100644
index 0000000000..3c3c26a91f
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/secondary_index/ya.make
@@ -0,0 +1,21 @@
+OWNER(g:kikimr)
+
+PROGRAM()
+
+SRCS(
+ main.cpp
+ secondary_index.cpp
+ secondary_index_create.cpp
+ secondary_index_delete.cpp
+ secondary_index_drop.cpp
+ secondary_index_generate.cpp
+ secondary_index_list.cpp
+ secondary_index_update.cpp
+)
+
+PEERDIR(
+ library/cpp/getopt
+ ydb/public/sdk/cpp/client/ydb_table
+)
+
+END()
diff --git a/ydb/public/sdk/cpp/examples/secondary_index_builtin/README.md b/ydb/public/sdk/cpp/examples/secondary_index_builtin/README.md
new file mode 100644
index 0000000000..330f08039a
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/secondary_index_builtin/README.md
@@ -0,0 +1,61 @@
+# YQL (only version 1) query with secondary index
+
+## Create table
+
+```cl
+USE plato;
+
+CREATE TABLE TableName (
+ Key1 Type,
+ Key2 Type,
+ …
+ PRIMARY KEY (SomeKey),
+ INDEX IndexName1 GLOBAL ON (SomeKey),
+ INDEX IndexName2 GLOBAL ON (SomeKey1, SomeKey2, ...)
+);
+COMMIT;
+```
+Пример создания таблицы series c полями series_id, title, info, release_date, views, uploaded_user_id, с первичным ключом series_id, и вторичными индексами views_index по полю views и users_index по полю uploaded_user_id.
+
+```cl
+USE plato;
+
+CREATE TABLE series (
+ series_id Uint64,
+ title Utf8,
+ info Utf8,
+ release_date Datetime,
+ views Uint64,
+ uploaded_user_id Uint64,
+ PRIMARY KEY (series_id),
+ INDEX views_index GLOBAL ON (views),
+ INDEX users_index GLOBAL ON (uploaded_user_id)
+);
+COMMIT;
+```
+## Select
+
+```cl
+SELECT *
+FROM TableName VIEW IndexName
+WHERE …
+```
+
+Пример SELECT запроса полей series_id, title, views, ... из таблицы series по индексу views_index
+с условием views >= someValue
+
+```cl
+SELECT series_id, title, info, release_date, views, uploaded_user_id
+FROM series view views_index
+WHERE views >= someValue
+```
+
+Пример SELECT с JOIN таблиц series и users по индексам users_index и name_index соответственно c заданным username.
+
+```cl
+SELECT t1.series_id, t1.title
+FROM series VIEW users_index AS t1
+INNER JOIN users VIEW name_index AS t2
+ON t1.uploaded_user_id == t2.user_id
+WHERE t2.name == username;
+```
diff --git a/ydb/public/sdk/cpp/examples/secondary_index_builtin/main.cpp b/ydb/public/sdk/cpp/examples/secondary_index_builtin/main.cpp
new file mode 100644
index 0000000000..32836a78f1
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/secondary_index_builtin/main.cpp
@@ -0,0 +1,64 @@
+#include "secondary_index.h"
+
+#include <util/system/env.h>
+
+using namespace NLastGetopt;
+using namespace NYdb;
+
+int main(int argc, char** argv) {
+
+ TString endpoint;
+ TString database;
+ TString command;
+
+ TOpts opts = TOpts::Default();
+
+ opts.AddLongOption('e', "endpoint", "YDB endpoint").Required().RequiredArgument("HOST:PORT").StoreResult(&endpoint);
+ opts.AddLongOption('d', "database", "YDB database").Required().RequiredArgument("PATH").StoreResult(&database);
+ opts.AddLongOption('c', "command", "execute command").Required().RequiredArgument("TYPE").StoreResult(&command);
+
+ opts.SetFreeArgsMin(0);
+ opts.ArgPermutation_ = NLastGetopt::REQUIRE_ORDER;
+ opts.AllowUnknownLongOptions_ = true;
+
+ TOptsParseResult result(&opts, argc, argv);
+
+ size_t freeArgPos = result.GetFreeArgsPos() - 1;
+ argc -= freeArgPos;
+ argv += freeArgPos;
+
+ TCommand cmd = Parse(command.c_str());
+
+ if (cmd == TCommand::NONE) {
+ Cerr << "Unsupported command: " << command << Endl;
+ return 1;
+ }
+
+ auto config = TDriverConfig()
+ .SetEndpoint(endpoint)
+ .SetDatabase(database)
+ .SetAuthToken(GetEnv("YDB_TOKEN"));
+
+ TDriver driver(config);
+
+ try {
+ switch (cmd) {
+ case TCommand::CREATE:
+ return Create(driver, database);
+ case TCommand::INSERT:
+ return Insert(driver, database);
+ case TCommand::SELECT:
+ return Select(driver, database, argc, argv);
+ case TCommand::SELECT_JOIN:
+ return SelectJoin(driver, database, argc, argv);
+ case TCommand::DROP:
+ return Drop(driver, database);
+ case TCommand::NONE:
+ return 1;
+ }
+
+ } catch (const TYdbErrorException& e) {
+ Cerr << "Execution failed: " << e << Endl;
+ return 1;
+ }
+}
diff --git a/ydb/public/sdk/cpp/examples/secondary_index_builtin/secondary_index.cpp b/ydb/public/sdk/cpp/examples/secondary_index_builtin/secondary_index.cpp
new file mode 100644
index 0000000000..fd82147712
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/secondary_index_builtin/secondary_index.cpp
@@ -0,0 +1,49 @@
+#include "secondary_index.h"
+
+#include <util/folder/pathsplit.h>
+
+TCommand Parse(const char * stringCmd) {
+
+ if (!strcmp(stringCmd, "create")) {
+ return TCommand::CREATE;
+ } else if (!strcmp(stringCmd, "insert")) {
+ return TCommand::INSERT;
+ } else if (!strcmp(stringCmd, "select")) {
+ return TCommand::SELECT;
+ } else if (!strcmp(stringCmd, "drop")) {
+ return TCommand::DROP;
+ } else if (!strcmp(stringCmd, "selectjoin")) {
+ return TCommand::SELECT_JOIN;
+ } else {
+ return TCommand::NONE;
+ }
+
+ return TCommand::NONE;
+}
+
+TString JoinPath(const TString& prefix, const TString& path) {
+ if (prefix.empty()) {
+ return path;
+ }
+
+ TPathSplitUnix prefixPathSplit(prefix);
+ prefixPathSplit.AppendComponent(path);
+
+ return prefixPathSplit.Reconstruct();
+}
+
+
+void ParseSelectSeries(TVector<TSeries>& parseResult, TResultSetParser&& parser) {
+ parseResult.clear();
+ while (parser.TryNextRow()) {
+ auto& series = parseResult.emplace_back();
+ series.SeriesId = *parser.ColumnParser(0).GetOptionalUint64();
+ series.Title = *parser.ColumnParser(1).GetOptionalUtf8();
+ series.Info = *parser.ColumnParser(2).GetOptionalUtf8();
+ series.ReleaseDate = *parser.ColumnParser(3).GetOptionalDatetime();
+ series.Views = *parser.ColumnParser(4).GetOptionalUint64();
+ series.UploadedUserId = *parser.ColumnParser(5).GetOptionalUint64();
+ }
+}
+
+
diff --git a/ydb/public/sdk/cpp/examples/secondary_index_builtin/secondary_index.h b/ydb/public/sdk/cpp/examples/secondary_index_builtin/secondary_index.h
new file mode 100644
index 0000000000..0659739812
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/secondary_index_builtin/secondary_index.h
@@ -0,0 +1,89 @@
+#pragma once
+
+#include <ydb/public/sdk/cpp/client/ydb_driver/driver.h>
+#include <ydb/public/sdk/cpp/client/ydb_table/table.h>
+
+#include <library/cpp/getopt/last_getopt.h>
+#include <util/generic/string.h>
+#include <util/generic/yexception.h>
+#include <util/stream/output.h>
+#include <util/string/builder.h>
+#include <util/string/printf.h>
+
+#define TABLE_USERS "users"
+#define TABLE_SERIES "series"
+
+using NYdb::TResultSetParser;
+
+enum class TCommand {
+ CREATE,
+ INSERT,
+ SELECT,
+ DROP,
+ SELECT_JOIN,
+ NONE
+};
+
+struct TUser {
+ ui64 UserId;
+ TString Name;
+ ui32 Age;
+ TUser(ui64 userId = 0, TString name = "", ui32 age = 0)
+ : UserId(userId)
+ , Name(name)
+ , Age(age) {}
+};
+
+struct TSeries {
+ ui64 SeriesId;
+ TString Title;
+ TInstant ReleaseDate;
+ TString Info;
+ ui64 Views;
+ ui64 UploadedUserId;
+
+ TSeries(ui64 seriesId = 0, TString title = "", TInstant releaseDate = TInstant::Days(0),
+ TString info = "", ui64 views = 0, ui64 uploadedUserId = 0)
+ : SeriesId(seriesId)
+ , Title(title)
+ , ReleaseDate(releaseDate)
+ , Info(info)
+ , Views(views)
+ , UploadedUserId(uploadedUserId) {}
+};
+
+class TYdbErrorException: yexception {
+public:
+ TYdbErrorException(NYdb::TStatus status)
+ : Status(std::move(status))
+ { }
+
+ friend IOutputStream& operator<<(IOutputStream& out, const TYdbErrorException& e) {
+ out << "Status:" << e.Status.GetStatus();
+ if (e.Status.GetIssues()) {
+ out << Endl;
+ e.Status.GetIssues().PrintTo(out);
+ }
+ return out;
+ }
+private:
+ NYdb::TStatus Status;
+};
+
+inline void ThrowOnError(NYdb::TStatus status) {
+ if (!status.IsSuccess()){
+ throw TYdbErrorException(std::move(status));
+ }
+}
+
+TString GetCommandsList();
+TCommand Parse(const char *stringCmnd);
+TString JoinPath(const TString& prefix, const TString& path);
+
+void ParseSelectSeries(TVector<TSeries>& parseResult, TResultSetParser&& parser);
+
+int Create(NYdb::TDriver& driver, const TString& path);
+int Insert(NYdb::TDriver& driver, const TString& path);
+int Drop(NYdb::TDriver& driver, const TString& path);
+int SelectJoin(NYdb::TDriver& driver, const TString& path, int argc, char **argv);
+int Select(NYdb::TDriver& driver, const TString& path, int argc, char **argv);
diff --git a/ydb/public/sdk/cpp/examples/secondary_index_builtin/secondary_index_create.cpp b/ydb/public/sdk/cpp/examples/secondary_index_builtin/secondary_index_create.cpp
new file mode 100644
index 0000000000..64a1755042
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/secondary_index_builtin/secondary_index_create.cpp
@@ -0,0 +1,45 @@
+#include "secondary_index.h"
+
+using namespace NYdb;
+using namespace NYdb::NTable;
+using namespace NLastGetopt;
+
+static void CreateSeriesTable(TTableClient& client, const TString& path) {
+
+ ThrowOnError(client.RetryOperationSync([path](TSession session) {
+
+ auto desc = TTableBuilder()
+ .AddNullableColumn("series_id", EPrimitiveType::Uint64)
+ .AddNullableColumn("title", EPrimitiveType::Utf8)
+ .AddNullableColumn("info", EPrimitiveType::Utf8)
+ .AddNullableColumn("release_date", EPrimitiveType::Datetime)
+ .AddNullableColumn("views", EPrimitiveType::Uint64)
+ .AddNullableColumn("uploaded_user_id", EPrimitiveType::Uint64)
+ .SetPrimaryKeyColumn("series_id")
+ .AddSecondaryIndex("views_index", "views")
+ .AddSecondaryIndex("users_index", "uploaded_user_id")
+ .Build();
+
+ return session.CreateTable(JoinPath(path, TABLE_SERIES), std::move(desc)).GetValueSync();
+ }));
+
+ ThrowOnError(client.RetryOperationSync([path] (TSession session) {
+
+ auto desc = TTableBuilder()
+ .AddNullableColumn("user_id", EPrimitiveType::Uint64)
+ .AddNullableColumn("name", EPrimitiveType::Utf8)
+ .AddNullableColumn("age", EPrimitiveType::Uint32)
+ .SetPrimaryKeyColumn("user_id")
+ .AddSecondaryIndex("name_index", "name")
+ .Build();
+
+ return session.CreateTable(JoinPath(path, TABLE_USERS), std::move(desc)).GetValueSync();
+ }));
+}
+
+int Create(TDriver& driver, const TString& path) {
+ TTableClient client(driver);
+ CreateSeriesTable(client, path);
+ return 0;
+}
+
diff --git a/ydb/public/sdk/cpp/examples/secondary_index_builtin/secondary_index_drop.cpp b/ydb/public/sdk/cpp/examples/secondary_index_builtin/secondary_index_drop.cpp
new file mode 100644
index 0000000000..cc0b87fe62
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/secondary_index_builtin/secondary_index_drop.cpp
@@ -0,0 +1,19 @@
+#include "secondary_index.h"
+
+using namespace NYdb::NTable;
+using namespace NYdb;
+
+static void DropTable(TTableClient& client, const TString& path) {
+ ThrowOnError(client.RetryOperationSync([path] (TSession session) {
+ return session.DropTable(path).ExtractValueSync();
+ }));
+}
+
+int Drop(NYdb::TDriver& driver, const TString& path) {
+
+ TTableClient client(driver);
+ DropTable(client, JoinPath(path, TABLE_SERIES));
+ DropTable(client, JoinPath(path, TABLE_USERS));
+
+ return 0;
+}
diff --git a/ydb/public/sdk/cpp/examples/secondary_index_builtin/secondary_index_fill.cpp b/ydb/public/sdk/cpp/examples/secondary_index_builtin/secondary_index_fill.cpp
new file mode 100644
index 0000000000..d0f6ce2d12
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/secondary_index_builtin/secondary_index_fill.cpp
@@ -0,0 +1,136 @@
+#include "secondary_index.h"
+
+#include <util/random/random.h>
+#include <util/string/printf.h>
+
+using namespace NYdb;
+using namespace NYdb::NTable;
+
+TVector<TSeries> GetSeries() {
+ TVector<TSeries> series = {
+ TSeries(1, "First episode", TInstant::ParseIso8601("2006-01-01"), "Pilot episode.", 1000, 0),
+ TSeries(2, "Second episode", TInstant::ParseIso8601("2006-02-01"), "Jon Snow knows nothing.", 2000, 1),
+ TSeries(3, "Third episode", TInstant::ParseIso8601("2006-03-01"), "Daenerys is the mother of dragons.", 3000, 2),
+ TSeries(4, "Fourth episode", TInstant::ParseIso8601("2006-04-01"), "Jorah Mormont is the king of the friendzone.", 4000, 3),
+ TSeries(5, "Fifth episode", TInstant::ParseIso8601("2006-05-01"), "Cercei is not good person.", 5000, 1),
+ TSeries(6, "Sixth episode", TInstant::ParseIso8601("2006-06-01"), "Tyrion is not big.", 6000, 2),
+ TSeries(7, "Seventh episode", TInstant::ParseIso8601("2006-07-01"), "Tywin should close the door.", 7000, 2),
+ TSeries(8, "Eighth episode", TInstant::ParseIso8601("2006-08-01"), "The white walkers are well-organized.", 8000, 3),
+ TSeries(9, "Ninth episode", TInstant::ParseIso8601("2006-09-01"), "Dragons can fly.", 9000, 1)
+ };
+
+ return series;
+}
+
+TVector<TUser> GetUsers() {
+ TVector<TUser> users = {
+ TUser(0, "Kit Harrington", 32),
+ TUser(1, "Emilia Clarke", 32),
+ TUser(2, "Jason Momoa", 39),
+ TUser(3, "Peter Dinklage", 49)
+ };
+ return users;
+}
+
+TParams Build(const TVector<TSeries>& seriesList, const TVector<TUser>& usersList) {
+
+ TParamsBuilder paramsBuilder;
+
+ auto& seriesParam = paramsBuilder.AddParam("$seriesData");
+ seriesParam.BeginList();
+
+ for (auto& series: seriesList) {
+ seriesParam.AddListItem()
+ .BeginStruct()
+ .AddMember("series_id")
+ .Uint64(series.SeriesId)
+ .AddMember("title")
+ .Utf8(series.Title)
+ .AddMember("info")
+ .Utf8(series.Info)
+ .AddMember("release_date")
+ .Date(series.ReleaseDate)
+ .AddMember("views")
+ .Uint64(series.Views)
+ .AddMember("uploaded_user_id")
+ .Uint64(series.UploadedUserId)
+ .EndStruct();
+ }
+
+ seriesParam.EndList();
+ seriesParam.Build();
+
+ auto& usersParam = paramsBuilder.AddParam("$usersData");
+ usersParam.BeginList();
+
+ for (auto& user: usersList) {
+ usersParam.AddListItem()
+ .BeginStruct()
+ .AddMember("user_id")
+ .Uint64(user.UserId)
+ .AddMember("name")
+ .Utf8(user.Name)
+ .AddMember("age")
+ .Uint32(user.Age)
+ .EndStruct();
+ }
+
+ usersParam.EndList();
+ usersParam.Build();
+
+ return paramsBuilder.Build();
+}
+
+static TStatus FillTable(TSession session, const TString& path) {
+
+ auto query = Sprintf(R"(
+ --!syntax_v1
+ PRAGMA TablePathPrefix("%s");
+
+ DECLARE $seriesData AS List<Struct<
+ series_id: Uint64,
+ title: Utf8,
+ info: Utf8,
+ release_date: Date,
+ views: Uint64,
+ uploaded_user_id: Uint64>>;
+
+ DECLARE $usersData AS List<Struct<
+ user_id: Uint64,
+ name: Utf8,
+ age: Uint32>>;
+
+
+ REPLACE INTO series
+ SELECT
+ series_id,
+ title,
+ info,
+ release_date,
+ views,
+ uploaded_user_id
+ FROM AS_TABLE($seriesData);
+
+ REPLACE INTO users
+ SELECT
+ user_id,
+ name,
+ age
+ FROM AS_TABLE($usersData);)", path.c_str());
+
+ TParams seriesParams = Build(GetSeries(), GetUsers());
+ return session.ExecuteDataQuery(query,
+ TTxControl::BeginTx(TTxSettings::SerializableRW()).CommitTx(),seriesParams)
+ .GetValueSync();
+}
+
+int Insert(NYdb::TDriver& driver, const TString& path) {
+
+ TTableClient client(driver);
+ ThrowOnError(client.RetryOperationSync([path] (TSession session) {
+ return FillTable(session, path);
+ }));
+
+ return 0;
+}
+
diff --git a/ydb/public/sdk/cpp/examples/secondary_index_builtin/secondary_index_select.cpp b/ydb/public/sdk/cpp/examples/secondary_index_builtin/secondary_index_select.cpp
new file mode 100644
index 0000000000..5d75527f86
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/secondary_index_builtin/secondary_index_select.cpp
@@ -0,0 +1,67 @@
+#include "secondary_index.h"
+
+#include <util/string/printf.h>
+
+using namespace NYdb;
+using namespace NYdb::NTable;
+using namespace NLastGetopt;
+
+TStatus SelectSeriesWithViews(TSession session, const TString& path, TVector<TSeries>& selectResult, ui64 minViews) {
+ auto queryText = Sprintf(R"(
+ --!syntax_v1
+ PRAGMA TablePathPrefix("%s");
+
+ DECLARE $minViews AS Uint64;
+
+ SELECT series_id, title, info, release_date, views, uploaded_user_id
+ FROM `series` VIEW views_index
+ WHERE views >= $minViews
+ )", path.c_str());
+
+ auto prepareResult = session.PrepareDataQuery(queryText).ExtractValueSync();
+ if (!prepareResult.IsSuccess()) {
+ return prepareResult;
+ }
+
+ auto query = prepareResult.GetQuery();
+ auto params = query.GetParamsBuilder()
+ .AddParam("$minViews")
+ .Uint64(minViews)
+ .Build()
+ .Build();
+
+ auto result = query.Execute(TTxControl::BeginTx(TTxSettings::SerializableRW()).CommitTx(), std::move(params))
+ .ExtractValueSync();
+
+ if (result.IsSuccess()) {
+ ParseSelectSeries(selectResult, result.GetResultSetParser(0));
+ }
+
+ return result;
+}
+
+int Select(TDriver& driver, const TString& path, int argc, char **argv) {
+
+ TOpts opts = TOpts::Default();
+
+ ui64 minViews = 0;
+ opts.AddLongOption("min-views", "Series with views greater than").Required().RequiredArgument("NUM")
+ .StoreResult(&minViews);
+
+ TOptsParseResult res(&opts, argc, argv);
+ TTableClient client(driver);
+
+ TVector<TSeries> selectResult;
+ ThrowOnError(client.RetryOperationSync([path, minViews, &selectResult](TSession session) {
+ return SelectSeriesWithViews(session, path, selectResult, minViews);
+ }));
+
+ for (auto& item: selectResult) {
+ Cout << item.SeriesId << ' ' << item.Title << ' ' << item.Info << ' '
+ << item.ReleaseDate << ' ' << item.Views << ' ' << item.UploadedUserId << Endl;
+ }
+
+ return 0;
+}
+
+
diff --git a/ydb/public/sdk/cpp/examples/secondary_index_builtin/secondary_index_select_join.cpp b/ydb/public/sdk/cpp/examples/secondary_index_builtin/secondary_index_select_join.cpp
new file mode 100644
index 0000000000..d5f6c8bd2d
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/secondary_index_builtin/secondary_index_select_join.cpp
@@ -0,0 +1,70 @@
+#include "secondary_index.h"
+
+#include <util/string/printf.h>
+
+using namespace NYdb;
+using namespace NYdb::NTable;
+using namespace NLastGetopt;
+
+TStatus SelectSeriesWithUserName(TSession session, const TString& path,
+ TVector<TSeries>& selectResult, const TString& name) {
+
+ auto queryText = Sprintf(R"(
+ --!syntax_v1
+ PRAGMA TablePathPrefix("%s");
+
+ DECLARE $userName AS Utf8;
+
+ SELECT t1.series_id, t1.title, t1.info, t1.release_date, t1.views, t1.uploaded_user_id
+ FROM `series` VIEW users_index AS t1
+ INNER JOIN `users` VIEW name_index AS t2
+ ON t1.uploaded_user_id == t2.user_id
+ WHERE t2.name == $userName;
+ )", path.c_str());
+
+ auto prepareResult = session.PrepareDataQuery(queryText).ExtractValueSync();
+ if (!prepareResult.IsSuccess()) {
+ return prepareResult;
+ }
+
+ auto query = prepareResult.GetQuery();
+ auto params = query.GetParamsBuilder()
+ .AddParam("$userName")
+ .Utf8(name)
+ .Build()
+ .Build();
+
+ auto result = query.Execute(TTxControl::BeginTx(TTxSettings::SerializableRW()).CommitTx(), std::move(params))
+ .ExtractValueSync();
+
+ if (result.IsSuccess()) {
+ ParseSelectSeries(selectResult, result.GetResultSetParser(0));
+ }
+
+ return result;
+}
+
+int SelectJoin(TDriver& driver, const TString& path, int argc, char **argv) {
+
+ TOpts opts = TOpts::Default();
+
+ TString name;
+ opts.AddLongOption("name", "User name").Required().RequiredArgument("TYPE")
+ .StoreResult(&name);
+
+ TOptsParseResult res(&opts, argc, argv);
+ TTableClient client(driver);
+
+ TVector<TSeries> selectResult;
+
+ ThrowOnError(client.RetryOperationSync([path, &selectResult, name](TSession session) {
+ return SelectSeriesWithUserName(session, path, selectResult, name);
+ }));
+
+ for (auto& item : selectResult) {
+ Cout << item.SeriesId << ' ' << item.Title << Endl;
+ }
+
+ return 0;
+}
+
diff --git a/ydb/public/sdk/cpp/examples/secondary_index_builtin/ya.make b/ydb/public/sdk/cpp/examples/secondary_index_builtin/ya.make
new file mode 100644
index 0000000000..0c757c112a
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/secondary_index_builtin/ya.make
@@ -0,0 +1,23 @@
+OWNER(
+ nusratbek
+ g:kikimr
+)
+
+PROGRAM()
+
+SRCS(
+ main.cpp
+ secondary_index.cpp
+ secondary_index_create.cpp
+ secondary_index_fill.cpp
+ secondary_index_select.cpp
+ secondary_index_drop.cpp
+ secondary_index_select_join.cpp
+)
+
+PEERDIR(
+ library/cpp/getopt
+ ydb/public/sdk/cpp/client/ydb_table
+)
+
+END()
diff --git a/ydb/public/sdk/cpp/examples/ttl/main.cpp b/ydb/public/sdk/cpp/examples/ttl/main.cpp
new file mode 100644
index 0000000000..718381b53a
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/ttl/main.cpp
@@ -0,0 +1,46 @@
+#include "ttl.h"
+
+#include <library/cpp/getopt/last_getopt.h>
+#include <util/system/env.h>
+
+using namespace NLastGetopt;
+using namespace NYdb;
+
+void StopHandler(int) {
+ exit(1);
+}
+
+int main(int argc, char** argv) {
+ TOpts opts = TOpts::Default();
+
+ TString endpoint;
+ TString database;
+ TString path;
+ opts.AddLongOption('e', "endpoint", "YDB endpoint").Required().RequiredArgument("HOST:PORT")
+ .StoreResult(&endpoint);
+ opts.AddLongOption('d', "database", "YDB database name").Required().RequiredArgument("PATH")
+ .StoreResult(&database);
+ opts.AddLongOption('p', "path", "Base path for tables").Optional().RequiredArgument("PATH")
+ .StoreResult(&path);
+
+ signal(SIGINT, &StopHandler);
+ signal(SIGTERM, &StopHandler);
+
+ TOptsParseResult res(&opts, argc, argv);
+
+ if (path.empty()) {
+ path = database;
+ }
+
+ auto driverConfig = TDriverConfig()
+ .SetEndpoint(endpoint)
+ .SetDatabase(database)
+ .SetAuthToken(GetEnv("YDB_TOKEN"));
+ TDriver driver(driverConfig);
+
+ if (!Run(driver, path)) {
+ return 2;
+ }
+
+ return 0;
+}
diff --git a/ydb/public/sdk/cpp/examples/ttl/ttl.cpp b/ydb/public/sdk/cpp/examples/ttl/ttl.cpp
new file mode 100644
index 0000000000..15a68912c2
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/ttl/ttl.cpp
@@ -0,0 +1,323 @@
+#include "ttl.h"
+#include "util.h"
+
+#include <util/folder/pathsplit.h>
+#include <util/string/printf.h>
+
+using namespace NExample;
+using namespace NYdb;
+using namespace NYdb::NTable;
+
+constexpr ui32 DOC_TABLE_PARTITION_COUNT = 4;
+constexpr ui32 EXPIRATION_QUEUE_COUNT = 4;
+
+//! Creates Documents table and multiple ExpirationQueue tables
+static void CreateTables(TTableClient client, const TString& path) {
+ // Documents table stores the contents of web pages.
+ // The table is partitioned by hash(Url) in order to evenly distribute the load.
+ ThrowOnError(client.RetryOperationSync([path](TSession session) {
+ auto documentsDesc = TTableBuilder()
+ .AddNullableColumn("doc_id", EPrimitiveType::Uint64)
+ .AddNullableColumn("url", EPrimitiveType::Utf8)
+ .AddNullableColumn("html", EPrimitiveType::Utf8)
+ .AddNullableColumn("timestamp", EPrimitiveType::Uint64)
+ .SetPrimaryKeyColumn("doc_id")
+ .Build();
+
+ // Partition Documents table by DocId
+ auto documnetsSettings = TCreateTableSettings();
+ documnetsSettings.PartitioningPolicy(TPartitioningPolicy().UniformPartitions(DOC_TABLE_PARTITION_COUNT));
+
+ return session.CreateTable(JoinPath(path, "documents"),
+ std::move(documentsDesc), std::move(documnetsSettings)).GetValueSync();
+ }));
+
+ // Multiple ExpirationQueue tables allow to scale the load.
+ // Each ExpirationQueue table can be handled by a dedicated worker.
+ for (ui32 i = 0; i < EXPIRATION_QUEUE_COUNT; ++i) {
+ ThrowOnError(client.RetryOperationSync([path, i](TSession session) {
+ auto expirationDesc = TTableBuilder()
+ .AddNullableColumn("timestamp", EPrimitiveType::Uint64)
+ .AddNullableColumn("doc_id", EPrimitiveType::Uint64)
+ .SetPrimaryKeyColumns({"timestamp", "doc_id"})
+ .Build();
+
+ return session.CreateTable(JoinPath(path, Sprintf("expiration_queue_%" PRIu32, i)),
+ std::move(expirationDesc)).GetValueSync();
+ }));
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+//! Insert or replaces a document.
+static TStatus AddDocumentTransaction(TSession session, const TString& path,
+ const TString& url, const TString& html, ui64 timestamp)
+{
+ // Add an entry to a random expiration queue in order to evenly distribute the load
+ ui32 queue = rand() % EXPIRATION_QUEUE_COUNT;
+
+ auto query = Sprintf(R"(
+ --!syntax_v1
+ PRAGMA TablePathPrefix("%s");
+
+ DECLARE $url AS Utf8;
+ DECLARE $html AS Utf8;
+ DECLARE $timestamp AS Uint64;
+
+ $doc_id = Digest::CityHash($url);
+
+ REPLACE INTO documents
+ (doc_id, url, html, `timestamp`)
+ VALUES
+ ($doc_id, $url, $html, $timestamp);
+
+ REPLACE INTO expiration_queue_%u
+ (`timestamp`, doc_id)
+ VALUES
+ ($timestamp, $doc_id);
+ )", path.c_str(), queue);
+
+ auto params = session.GetParamsBuilder()
+ .AddParam("$url").Utf8(url).Build()
+ .AddParam("$html").Utf8(html).Build()
+ .AddParam("$timestamp").Uint64(timestamp).Build()
+ .Build();
+
+ return session.ExecuteDataQuery(
+ query,
+ TTxControl::BeginTx(TTxSettings::SerializableRW()).CommitTx(),
+ std::move(params)).GetValueSync();
+}
+
+//! Reads document contents.
+static TStatus ReadDocumentTransaction(TSession session, const TString& path,
+ const TString& url, TMaybe<TResultSet>& resultSet)
+{
+ auto query = Sprintf(R"(
+ --!syntax_v1
+ PRAGMA TablePathPrefix("%s");
+
+ DECLARE $url AS Utf8;
+
+ $doc_id = Digest::CityHash($url);
+
+ SELECT doc_id, url, html, `timestamp`
+ FROM documents
+ WHERE doc_id = $doc_id;
+ )", path.c_str());
+
+ auto params = session.GetParamsBuilder()
+ .AddParam("$url").Utf8(url).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;
+}
+
+//! Reads a batch of entries from expiration queue
+static TStatus ReadExpiredBatchTransaction(TSession session, const TString& path, const ui32 queue,
+ const ui64 timestamp, const ui64 prevTimestamp, const ui64 prevDocId, TMaybe<TResultSet>& resultSet)
+{
+ auto query = Sprintf(R"(
+ --!syntax_v1
+ PRAGMA TablePathPrefix("%s");
+
+ DECLARE $timestamp AS Uint64;
+ DECLARE $prev_timestamp AS Uint64;
+ DECLARE $prev_doc_id AS Uint64;
+
+ $data = (
+ SELECT *
+ FROM expiration_queue_%u
+ WHERE
+ `timestamp` <= $timestamp
+ AND
+ `timestamp` > $prev_timestamp
+ ORDER BY `timestamp`, doc_id
+ LIMIT 100
+
+ UNION ALL
+
+ SELECT *
+ FROM expiration_queue_%u
+ WHERE
+ `timestamp` = $prev_timestamp AND doc_id > $prev_doc_id
+ ORDER BY `timestamp`, doc_id
+ LIMIT 100
+ );
+
+ SELECT `timestamp`, doc_id
+ FROM $data
+ ORDER BY `timestamp`, doc_id
+ LIMIT 100;
+ )", path.c_str(), queue, queue);
+
+ auto params = session.GetParamsBuilder()
+ .AddParam("$timestamp").Uint64(timestamp).Build()
+ .AddParam("$prev_timestamp").Uint64(prevTimestamp).Build()
+ .AddParam("$prev_doc_id").Uint64(prevDocId).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;
+}
+
+//! Deletes an expired document
+static TStatus DeleteDocumentWithTimestamp(TSession session, const TString& path, const ui32 queue,
+ const ui64 docId, const ui64 timestamp)
+{
+ auto query = Sprintf(R"(
+ --!syntax_v1
+ PRAGMA TablePathPrefix("%s");
+
+ DECLARE $doc_id AS Uint64;
+ DECLARE $timestamp AS Uint64;
+
+ DELETE FROM documents
+ WHERE doc_id = $doc_id AND `timestamp` = $timestamp;
+
+ DELETE FROM expiration_queue_%u
+ WHERE `timestamp` = $timestamp AND doc_id = $doc_id;
+ )", path.c_str(), queue);
+
+ auto params = session.GetParamsBuilder()
+ .AddParam("$doc_id").Uint64(docId).Build()
+ .AddParam("$timestamp").Uint64(timestamp).Build()
+ .Build();
+
+ auto result = session.ExecuteDataQuery(
+ query,
+ TTxControl::BeginTx(TTxSettings::SerializableRW()).CommitTx(),
+ std::move(params)).GetValueSync();
+
+ return result;
+}
+///////////////////////////////////////////////////////////////////////////////
+
+void AddDocument(TTableClient client, const TString& path, const TString& url,
+ const TString& html, const ui64 timestamp)
+{
+ Cout << "> AddDocument:" << Endl
+ << " Url: " << url << Endl
+ << " Timestamp: " << timestamp << Endl;
+
+ ThrowOnError(client.RetryOperationSync([path, url, html, timestamp](TSession session) {
+ return AddDocumentTransaction(session, path, url, html, timestamp);
+ }));
+ Cout << Endl;
+}
+
+void ReadDocument(TTableClient client, const TString& path, const TString& url) {
+ Cout << "> ReadDocument \"" << url << "\":" << Endl;
+ TMaybe<TResultSet> resultSet;
+ ThrowOnError(client.RetryOperationSync([path, url, &resultSet] (TSession session) {
+ return ReadDocumentTransaction(session, path, url, resultSet);
+ }));
+
+ TResultSetParser parser(*resultSet);
+ if (parser.TryNextRow()) {
+ Cout << " DocId: " << parser.ColumnParser("doc_id").GetOptionalUint64() << Endl
+ << " Url: " << parser.ColumnParser("url").GetOptionalUtf8() << Endl
+ << " Timestamp: " << parser.ColumnParser("timestamp").GetOptionalUint64() << Endl
+ << " Html: " << parser.ColumnParser("html").GetOptionalUtf8() << Endl;
+ } else {
+ Cout << " Not found" << Endl;
+ }
+ Cout << Endl;
+}
+
+void DeleteExpired(TTableClient client, const TString& path, const ui32 queue, const ui64 timestamp) {
+ Cout << "> DeleteExpired from queue #" << queue << ":" << Endl;
+ bool empty = false;
+ ui64 lastTimestamp = 0;
+ ui64 lastDocId = 0;
+ while (!empty) {
+ TMaybe<TResultSet> resultSet;
+ ThrowOnError(client.RetryOperationSync([path, queue, timestamp, lastDocId, lastTimestamp, &resultSet] (TSession session) {
+ return ReadExpiredBatchTransaction(session, path, queue, timestamp, lastTimestamp, lastDocId, resultSet);
+ }));
+
+ empty = true;
+ TResultSetParser parser(*resultSet);
+ while (parser.TryNextRow()) {
+ empty = false;
+ lastDocId = *parser.ColumnParser("doc_id").GetOptionalUint64();
+ lastTimestamp = *parser.ColumnParser("timestamp").GetOptionalUint64();
+ Cout << " DocId: " << lastDocId << " Timestamp: " << lastTimestamp << Endl;
+
+ ThrowOnError(client.RetryOperationSync([path, queue, lastDocId, lastTimestamp] (TSession session) {
+ return DeleteDocumentWithTimestamp(session, path, queue, lastDocId, lastTimestamp);
+ }));
+ }
+ }
+ Cout << Endl;
+}
+///////////////////////////////////////////////////////////////////////////////
+
+bool Run(const TDriver& driver, const TString& path) {
+ TTableClient client(driver);
+
+ try {
+ CreateTables(client, path);
+
+ AddDocument(client, path,
+ "https://yandex.ru/",
+ "<html><body><h1>Yandex</h1></body></html>",
+ 1);
+
+ AddDocument(client, path,
+ "https://ya.ru/",
+ "<html><body><h1>Yandex</h1></body></html>",
+ 2);
+
+ ReadDocument(client, path, "https://yandex.ru/");
+ ReadDocument(client, path, "https://ya.ru/");
+
+ for (ui32 q = 0; q < EXPIRATION_QUEUE_COUNT; ++q) {
+ DeleteExpired(client, path, q, 1);
+ }
+
+ ReadDocument(client, path, "https://ya.ru/");
+
+ AddDocument(client, path,
+ "https://yandex.ru/",
+ "<html><body><h1>Yandex</h1></body></html>",
+ 2);
+
+ AddDocument(client, path,
+ "https://yandex.ru/",
+ "<html><body><h1>Yandex</h1></body></html>",
+ 3);
+
+ for (ui32 q = 0; q < EXPIRATION_QUEUE_COUNT; ++q) {
+ DeleteExpired(client, path, q, 2);
+ }
+
+ ReadDocument(client, path, "https://yandex.ru/");
+ ReadDocument(client, path, "https://ya.ru/");
+ }
+ catch (const TYdbErrorException& e) {
+ Cerr << "Execution failed due to fatal error:" << Endl;
+ PrintStatus(e.Status);
+ return false;
+ }
+
+ return true;
+}
diff --git a/ydb/public/sdk/cpp/examples/ttl/ttl.h b/ydb/public/sdk/cpp/examples/ttl/ttl.h
new file mode 100644
index 0000000000..d419c21668
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/ttl/ttl.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include <ydb/public/sdk/cpp/client/ydb_driver/driver.h>
+#include <ydb/public/sdk/cpp/client/ydb_table/table.h>
+
+NYdb::TParams GetTablesDataParams();
+
+bool Run(const NYdb::TDriver& driver, const TString& path);
diff --git a/ydb/public/sdk/cpp/examples/ttl/util.h b/ydb/public/sdk/cpp/examples/ttl/util.h
new file mode 100644
index 0000000000..f4046b8892
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/ttl/util.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include <util/folder/pathsplit.h>
+#include <util/string/printf.h>
+
+namespace NExample {
+
+using namespace NYdb;
+using namespace NYdb::NTable;
+
+class TYdbErrorException : public yexception {
+public:
+ TYdbErrorException(const TStatus& status)
+ : Status(status) {}
+
+ TStatus Status;
+};
+
+inline void ThrowOnError(const TStatus& status) {
+ if (!status.IsSuccess()) {
+ throw TYdbErrorException(status);
+ }
+}
+
+inline void PrintStatus(const TStatus& status) {
+ Cerr << "Status: " << status.GetStatus() << Endl;
+ status.GetIssues().PrintTo(Cerr);
+}
+
+inline TString JoinPath(const TString& basePath, const TString& path) {
+ if (basePath.empty()) {
+ return path;
+ }
+
+ TPathSplitUnix prefixPathSplit(basePath);
+ prefixPathSplit.AppendComponent(path);
+
+ return prefixPathSplit.Reconstruct();
+}
+
+}
diff --git a/ydb/public/sdk/cpp/examples/ttl/ya.make b/ydb/public/sdk/cpp/examples/ttl/ya.make
new file mode 100644
index 0000000000..4ea5ebde6a
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/ttl/ya.make
@@ -0,0 +1,17 @@
+OWNER(g:kikimr)
+
+PROGRAM()
+
+SRCS(
+ main.cpp
+ ttl.h
+ ttl.cpp
+ util.h
+)
+
+PEERDIR(
+ library/cpp/getopt
+ ydb/public/sdk/cpp/client/ydb_table
+)
+
+END()
diff --git a/ydb/public/sdk/cpp/examples/ya.make b/ydb/public/sdk/cpp/examples/ya.make
new file mode 100644
index 0000000000..48aafeec74
--- /dev/null
+++ b/ydb/public/sdk/cpp/examples/ya.make
@@ -0,0 +1,10 @@
+OWNER(g:kikimr)
+
+RECURSE(
+ basic_example
+ bulk_upsert_simple
+ pagination
+ secondary_index
+ secondary_index_builtin
+ ttl
+)
diff --git a/ydb/public/sdk/cpp/ya.make b/ydb/public/sdk/cpp/ya.make
index f650212ab7..ea9cf4559a 100644
--- a/ydb/public/sdk/cpp/ya.make
+++ b/ydb/public/sdk/cpp/ya.make
@@ -55,4 +55,5 @@ RECURSE(
client/impl/ydb_internal/db_driver_state
client/draft/ut
client/draft
+ examples
)