diff options
author | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-17 20:48:54 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-17 20:48:54 +0300 |
commit | 3c6d2452a60e03ac4f2ab2b74b16d17d7fde4f5c (patch) | |
tree | 88f569aa18f5edacb3ca1162d76f2c206b03a84c | |
parent | 5c27e9c77778493fe201ac061f31fc2a6ea366c2 (diff) | |
download | ydb-3c6d2452a60e03ac4f2ab2b74b16d17d7fde4f5c.tar.gz |
Move c++ sdk examples to ydb dir. KIKIMR-14385
ref:c696cf605e6e1b6ed32225d2dffcd081d3b1ffc0
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 ) |