diff --git a/ydb/core/kqp/opt/kqp_opt_impl.h b/ydb/core/kqp/opt/kqp_opt_impl.h index f9873e33f721..be83371d0fda 100644 --- a/ydb/core/kqp/opt/kqp_opt_impl.h +++ b/ydb/core/kqp/opt/kqp_opt_impl.h @@ -31,7 +31,7 @@ NYql::NNodes::TKqpTable BuildTableMeta(const NYql::TKikimrTableMetadata& tableMe TIntrusivePtr GetIndexMetadata(const NYql::NNodes::TKqlReadTableIndex& index, const NYql::TKikimrTablesData& tables, TStringBuf cluster); -TVector> BuildSecondaryIndexVector( +TVector> BuildAffectedIndexTables( const NYql::TKikimrTableDescription& table, NYql::TPositionHandle pos, NYql::TExprContext& ctx, const THashSet* filter, const std::function TExprBase { return BuildTableMeta(meta, pos, ctx); }); @@ -695,19 +695,24 @@ TExprBase BuildUpdateTableWithIndex(const TKiUpdateTable& update, const TKikimrT updateColumnsList.push_back(TCoAtom(ctx.NewAtom(update.Pos(), column))); } - auto indexes = BuildSecondaryIndexVector(tableData, update.Pos(), ctx, nullptr, + auto indexes = BuildAffectedIndexTables(tableData, update.Pos(), ctx, nullptr, [] (const TKikimrTableMetadata& meta, TPositionHandle pos, TExprContext& ctx) -> TExprBase { return BuildTableMeta(meta, pos, ctx); }); + // Rewrite UPDATE to UPDATE ON for complex indexes auto idxNeedsKqpEffect = [](std::pair& x) { - return x.second->Type == TIndexDescription::EType::GlobalSyncUnique || - x.second->Type == TIndexDescription::EType::GlobalSyncVectorKMeansTree; + switch (x.second->Type) { + case TIndexDescription::EType::GlobalSync: + case TIndexDescription::EType::GlobalAsync: + return false; + case TIndexDescription::EType::GlobalSyncUnique: + case TIndexDescription::EType::GlobalSyncVectorKMeansTree: + case TIndexDescription::EType::GlobalFulltext: + return true; + } }; - const bool needsKqpEffect = std::find_if(indexes.begin(), indexes.end(), idxNeedsKqpEffect) != indexes.end(); - - // For unique or vector index rewrite UPDATE to UPDATE ON if (needsKqpEffect) { return Build(ctx, update.Pos()) .Table(BuildTableMeta(tableData, update.Pos(), ctx)) diff --git a/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_delete_index.cpp b/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_delete_index.cpp index ef3ff1232d38..c35586456fd3 100644 --- a/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_delete_index.cpp +++ b/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_delete_index.cpp @@ -78,7 +78,7 @@ TExprBase BuildDeleteIndexStagesImpl(const TKikimrTableDescription& table, case TIndexDescription::EType::GlobalSync: case TIndexDescription::EType::GlobalAsync: case TIndexDescription::EType::GlobalSyncUnique: { - // deleteIndexKeys are already correct + // deleteIndexKeys are already correct break; } case TIndexDescription::EType::GlobalSyncVectorKMeansTree: { @@ -92,17 +92,8 @@ TExprBase BuildDeleteIndexStagesImpl(const TKikimrTableDescription& table, break; } case TIndexDescription::EType::GlobalFulltext: { - // For fulltext indexes, we need to tokenize the text from the rows being deleted - // and then delete the corresponding token rows from the index table - auto deleteKeysPrecompute = Build(ctx, del.Pos()) - .Connection() - .Output() - .Stage(ReadTableToStage(deleteIndexKeys, ctx)) - .Index().Build("0") - .Build() - .Build() - .Done(); - deleteIndexKeys = BuildFulltextIndexRows(table, indexDesc, deleteKeysPrecompute, indexTableColumnsSet, indexTableColumns, /*includeDataColumns=*/false, + // For fulltext indexes, we need to tokenize the text and create deleted rows + deleteIndexKeys = BuildFulltextIndexRows(table, indexDesc, deleteIndexKeys, indexTableColumnsSet, indexTableColumns, /*includeDataColumns=*/false, del.Pos(), ctx); break; } @@ -133,7 +124,7 @@ TExprBase KqpBuildDeleteIndexStages(TExprBase node, TExprContext& ctx, const TKq const auto& table = kqpCtx.Tables->ExistingTable(kqpCtx.Cluster, del.Table().Path()); const auto& pk = table.Metadata->KeyColumnNames; - const auto indexes = BuildSecondaryIndexVector(table, del.Pos(), ctx); + const auto indexes = BuildAffectedIndexTables(table, del.Pos(), ctx); YQL_ENSURE(indexes); // Skip lookup means that the input already has all required columns and we only need to project them diff --git a/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_effects_impl.h b/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_effects_impl.h index 623132772e89..ea3b6e826f31 100644 --- a/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_effects_impl.h +++ b/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_effects_impl.h @@ -12,7 +12,7 @@ using TSecondaryIndexes = TVector>; -TSecondaryIndexes BuildSecondaryIndexVector(const NYql::TKikimrTableDescription& table, NYql::TPositionHandle pos, +TSecondaryIndexes BuildAffectedIndexTables(const NYql::TKikimrTableDescription& table, NYql::TPositionHandle pos, NYql::TExprContext& ctx, const THashSet* filter = nullptr); struct TCondenseInputResult { @@ -98,7 +98,7 @@ NYql::NNodes::TKqpCnStreamLookup BuildStreamLookupOverPrecompute(const NYql::TKi NYql::NNodes::TExprBase input, const NYql::NNodes::TKqpTable& kqpTableNode, const NYql::TPositionHandle& pos, NYql::TExprContext& ctx, const TVector& extraColumnsToRead = {}); -NYql::NNodes::TDqStageBase ReadTableToStage(const NYql::NNodes::TExprBase& expr, NYql::TExprContext& ctx); +NYql::NNodes::TDqStageBase ReadInputToStage(const NYql::NNodes::TExprBase& expr, NYql::TExprContext& ctx); NYql::NNodes::TExprBase BuildVectorIndexPostingRows(const NYql::TKikimrTableDescription& table, const NYql::NNodes::TKqpTable& tableNode, diff --git a/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_fulltext_index.cpp b/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_fulltext_index.cpp index 0293b9c29a8c..1d2808f0880d 100644 --- a/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_fulltext_index.cpp +++ b/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_fulltext_index.cpp @@ -21,12 +21,19 @@ TExprBase BuildFulltextIndexRows(const TKikimrTableDescription& table, const TIn const TString textColumn = settings.columns().at(0).column(); const auto& analyzers = settings.columns().at(0).analyzers(); - // Serialize analyzer settings for runtime usage - TString settingsProto; - YQL_ENSURE(analyzers.SerializeToString(&settingsProto)); - auto inputRowArg = TCoArgument(ctx.NewArgument(pos, "input_row")); auto tokenArg = TCoArgument(ctx.NewArgument(pos, "token")); + + auto readInputRows = inputRows.Maybe() + ? inputRows + : Build(ctx, pos) + .Connection() + .Output() + .Stage(ReadInputToStage(inputRows, ctx)) + .Index().Build("0") + .Build() + .Build() + .Done(); // Build output row structure for each token TVector tokenRowTuples; @@ -93,12 +100,15 @@ TExprBase BuildFulltextIndexRows(const TKikimrTableDescription& table, const TIn .Name().Build(textColumn) .Done(); - // Create callable for fulltext tokenization - // Format: FulltextAnalyze(text: String, settings: String) -> List + // Serialize analyzer settings for FulltextAnalyze + TString settingsProto; + YQL_ENSURE(analyzers.SerializeToString(&settingsProto)); auto settingsLiteral = Build(ctx, pos) .Literal().Build(settingsProto) .Done(); + // Create callable for fulltext tokenization + // Format: FulltextAnalyze(text: String, settings: String) -> List auto analyzeCallable = ctx.Builder(pos) .Callable("FulltextAnalyze") .Add(0, textMember.Ptr()) @@ -108,7 +118,7 @@ TExprBase BuildFulltextIndexRows(const TKikimrTableDescription& table, const TIn auto analyzeStage = Build(ctx, pos) .Inputs() - .Add(inputRows) + .Add(readInputRows) .Build() .Program() .Args({"rows"}) diff --git a/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_indexes.cpp b/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_indexes.cpp index 9f37e06b7797..8d1178a85fe5 100644 --- a/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_indexes.cpp +++ b/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_indexes.cpp @@ -75,15 +75,14 @@ TDqPhyPrecompute PrecomputeCondenseInputResult(const TCondenseInputResult& conde .Done(); } -TVector> BuildSecondaryIndexVector( +TVector> BuildAffectedIndexTables( const TKikimrTableDescription& table, TPositionHandle pos, TExprContext& ctx, const THashSet* filter, const std::function& tableBuilder) { - TVector> secondaryIndexes; - secondaryIndexes.reserve(table.Metadata->Indexes.size()); + TVector> result(::Reserve(table.Metadata->Indexes.size())); YQL_ENSURE(table.Metadata->Indexes.size() == table.Metadata->ImplTables.size()); for (size_t i = 0; i < table.Metadata->Indexes.size(); i++) { const auto& index = table.Metadata->Indexes[i]; @@ -112,34 +111,42 @@ TVector> BuildSecondaryInde if (index.KeyColumns && addIndex) { auto& implTable = table.Metadata->ImplTables[i]; - if (index.Type == TIndexDescription::EType::GlobalSyncVectorKMeansTree) { - if (index.KeyColumns.size() == 1) { - YQL_ENSURE(implTable->Next && !implTable->Next->Next); - } else { - YQL_ENSURE(implTable->Next && implTable->Next->Next && !implTable->Next->Next->Next); + switch (index.Type) { + case TIndexDescription::EType::GlobalSync: + case TIndexDescription::EType::GlobalAsync: + case TIndexDescription::EType::GlobalSyncUnique: + case TIndexDescription::EType::GlobalFulltext: { + YQL_ENSURE(!implTable->Next); + auto indexTable = tableBuilder(*implTable, pos, ctx).Ptr(); + result.emplace_back(indexTable, &index); + break; + } + case TIndexDescription::EType::GlobalSyncVectorKMeansTree: { + if (index.KeyColumns.size() == 1) { + YQL_ENSURE(implTable->Next && !implTable->Next->Next); + } else { + YQL_ENSURE(implTable->Next && implTable->Next->Next && !implTable->Next->Next->Next); + } + auto postingTable = implTable->Next; + YQL_ENSURE(postingTable->Name.EndsWith(NTableIndex::NKMeans::PostingTable)); + auto indexTable = tableBuilder(*postingTable, pos, ctx).Ptr(); + result.emplace_back(indexTable, &index); + break; } - auto postingTable = implTable->Next; - YQL_ENSURE(postingTable->Name.EndsWith(NTableIndex::NKMeans::PostingTable)); - auto indexTable = tableBuilder(*postingTable, pos, ctx).Ptr(); - secondaryIndexes.emplace_back(indexTable, &index); - } else { - YQL_ENSURE(!implTable->Next); - auto indexTable = tableBuilder(*implTable, pos, ctx).Ptr(); - secondaryIndexes.emplace_back(indexTable, &index); } } } - return secondaryIndexes; + return result; } -TSecondaryIndexes BuildSecondaryIndexVector(const TKikimrTableDescription& table, TPositionHandle pos, +TSecondaryIndexes BuildAffectedIndexTables(const TKikimrTableDescription& table, TPositionHandle pos, TExprContext& ctx, const THashSet* filter) { static auto cb = [] (const TKikimrTableMetadata& meta, TPositionHandle pos, TExprContext& ctx) -> TExprBase { return BuildTableMeta(meta, pos, ctx); }; - return BuildSecondaryIndexVector(table, pos, ctx, filter, cb); + return BuildAffectedIndexTables(table, pos, ctx, filter, cb); } TMaybeNode PrecomputeTableLookupDict(const TDqPhyPrecompute& lookupKeys, diff --git a/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_insert_index.cpp b/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_insert_index.cpp index 66fd6c5fb1a1..328fd0be8c62 100644 --- a/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_insert_index.cpp +++ b/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_insert_index.cpp @@ -102,7 +102,7 @@ TExprBase KqpBuildInsertIndexStages(TExprBase node, TExprContext& ctx, const TKq const bool isSink = NeedSinks(table, kqpCtx); - auto indexes = BuildSecondaryIndexVector(table, insert.Pos(), ctx, nullptr); + auto indexes = BuildAffectedIndexTables(table, insert.Pos(), ctx, nullptr); YQL_ENSURE(indexes); const bool canUseStreamIndex = kqpCtx.Config->EnableIndexStreamWrite && std::all_of(indexes.begin(), indexes.end(), [](const auto& index) { @@ -227,7 +227,7 @@ TExprBase KqpBuildInsertIndexStages(TExprBase node, TExprContext& ctx, const TKq break; } case TIndexDescription::EType::GlobalFulltext: { - // For fulltext indexes, we need to tokenize the text and create index rows and refill index columns + // For fulltext indexes, we need to tokenize the text and create inserted rows upsertIndexRows = BuildFulltextIndexRows(table, indexDesc, insertRowsPrecompute, inputColumnsSet, indexTableColumns, /*includeDataColumns=*/true, insert.Pos(), ctx); break; diff --git a/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_update.cpp b/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_update.cpp index b3e1eb0bcd54..abcfa023645d 100644 --- a/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_update.cpp +++ b/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_update.cpp @@ -240,7 +240,7 @@ TExprBase KqpBuildUpdateStages(TExprBase node, TExprContext& ctx, const TKqpOpti } } -TDqStageBase ReadTableToStage(const TExprBase& expr, TExprContext& ctx) { +TDqStageBase ReadInputToStage(const TExprBase& expr, TExprContext& ctx) { if (expr.Maybe()) { return expr.Cast(); } diff --git a/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_upsert_index.cpp b/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_upsert_index.cpp index 9ba019dbc8e6..00e3b24773f6 100644 --- a/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_upsert_index.cpp +++ b/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_upsert_index.cpp @@ -610,7 +610,7 @@ TMaybeNode KqpPhyUpsertIndexEffectsImpl(TKqpPhyUpsertIndexMode mode, } auto filter = (mode == TKqpPhyUpsertIndexMode::UpdateOn) ? &inputColumnsSet : nullptr; - const auto indexes = BuildSecondaryIndexVector(table, pos, ctx, filter); + const auto indexes = BuildAffectedIndexTables(table, pos, ctx, filter); auto checkedInput = RewriteInputForConstraint(inputRows, inputColumnsSet, columnsWithDefaultsSet, table, indexes, pos, ctx); @@ -885,13 +885,26 @@ TMaybeNode KqpPhyUpsertIndexEffectsImpl(TKqpPhyUpsertIndexMode mode, ? MakeRowsFromTupleDict(lookupDictRecomputed, pk, indexTableColumnsWithoutData, pos, ctx) : MakeRowsFromDict(lookupDict.Cast(), pk, indexTableColumnsWithoutData, pos, ctx); - if (indexDesc->Type == TIndexDescription::EType::GlobalSyncVectorKMeansTree) { - if (indexDesc->KeyColumns.size() > 1) { - deleteIndexKeys = BuildVectorIndexPrefixRows(table, *prefixTable, false, indexDesc, deleteIndexKeys, indexTableColumnsWithoutData, pos, ctx); + switch (indexDesc->Type) { + case TIndexDescription::EType::GlobalSync: + case TIndexDescription::EType::GlobalAsync: + case TIndexDescription::EType::GlobalSyncUnique: + // deleteIndexKeys are already correct + break; + case TIndexDescription::EType::GlobalSyncVectorKMeansTree: { + if (indexDesc->KeyColumns.size() > 1) { + deleteIndexKeys = BuildVectorIndexPrefixRows(table, *prefixTable, false, indexDesc, deleteIndexKeys, indexTableColumnsWithoutData, pos, ctx); + } + deleteIndexKeys = BuildVectorIndexPostingRows(table, mainTableNode, + indexDesc->Name, indexTableColumnsWithoutData, deleteIndexKeys, false, pos, ctx); + break; + } + case TIndexDescription::EType::GlobalFulltext: { + // For fulltext indexes, we need to tokenize the text and create deleted rows + deleteIndexKeys = BuildFulltextIndexRows(table, indexDesc, deleteIndexKeys, indexTableColumnsSet, + indexTableColumnsWithoutData, /*includeDataColumns=*/false, pos, ctx); + break; } - - deleteIndexKeys = BuildVectorIndexPostingRows(table, mainTableNode, - indexDesc->Name, indexTableColumnsWithoutData, deleteIndexKeys, false, pos, ctx); } auto indexDelete = Build(ctx, pos) @@ -916,21 +929,34 @@ TMaybeNode KqpPhyUpsertIndexEffectsImpl(TKqpPhyUpsertIndexMode mode, : MakeUpsertIndexRows(mode, rowsPrecompute.Cast(), lookupDict.Cast(), inputColumnsSet, indexTableColumns, table, pos, ctx, false); - if (indexDesc->Type == TIndexDescription::EType::GlobalSyncVectorKMeansTree) { - if (indexDesc->KeyColumns.size() > 1) { - if (prefixTable->Metadata->Columns.at(NTableIndex::NKMeans::IdColumn).DefaultKind == NKikimrKqp::TKqpColumnMetadataProto::DEFAULT_KIND_SEQUENCE) { - auto res = BuildVectorIndexPrefixRowsWithNew(table, *prefixTable, indexDesc, upsertIndexRows, indexTableColumns, pos, ctx); - upsertIndexRows = std::move(res.first); - effects.emplace_back(std::move(res.second)); - } else { - // Handle old prefixed vector index tables without the sequence - upsertIndexRows = BuildVectorIndexPrefixRows(table, *prefixTable, true, indexDesc, upsertIndexRows, indexTableColumns, pos, ctx); + switch (indexDesc->Type) { + case TIndexDescription::EType::GlobalSync: + case TIndexDescription::EType::GlobalAsync: + case TIndexDescription::EType::GlobalSyncUnique: + // upsertIndexRows are already correct + break; + case TIndexDescription::EType::GlobalSyncVectorKMeansTree: { + if (indexDesc->KeyColumns.size() > 1) { + if (prefixTable->Metadata->Columns.at(NTableIndex::NKMeans::IdColumn).DefaultKind == NKikimrKqp::TKqpColumnMetadataProto::DEFAULT_KIND_SEQUENCE) { + auto res = BuildVectorIndexPrefixRowsWithNew(table, *prefixTable, indexDesc, upsertIndexRows, indexTableColumns, pos, ctx); + upsertIndexRows = std::move(res.first); + effects.emplace_back(std::move(res.second)); + } else { + // Handle old prefixed vector index tables without the sequence + upsertIndexRows = BuildVectorIndexPrefixRows(table, *prefixTable, true, indexDesc, upsertIndexRows, indexTableColumns, pos, ctx); + } } + upsertIndexRows = BuildVectorIndexPostingRows(table, mainTableNode, + indexDesc->Name, indexTableColumns, upsertIndexRows, true, pos, ctx); + indexTableColumns = BuildVectorIndexPostingColumns(table, indexDesc); + break; + } + case TIndexDescription::EType::GlobalFulltext: { + // For fulltext indexes, we need to tokenize the text and create upserted rows + upsertIndexRows = BuildFulltextIndexRows(table, indexDesc, upsertIndexRows, indexTableColumnsSet, + indexTableColumns, /*includeDataColumns=*/true, pos, ctx); + break; } - - upsertIndexRows = BuildVectorIndexPostingRows(table, mainTableNode, - indexDesc->Name, indexTableColumns, upsertIndexRows, true, pos, ctx); - indexTableColumns = BuildVectorIndexPostingColumns(table, indexDesc); } auto indexUpsert = Build(ctx, pos) diff --git a/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_vector_index.cpp b/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_vector_index.cpp index 040df03517b1..5968e5679c70 100644 --- a/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_vector_index.cpp +++ b/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_vector_index.cpp @@ -35,7 +35,7 @@ TExprBase BuildVectorIndexPostingRows(const TKikimrTableDescription& table, auto resolveInput = (inputRows.Maybe() ? inputRows.Maybe().Cast().Output() : Build(ctx, pos) - .Stage(ReadTableToStage(inputRows, ctx)) + .Stage(ReadInputToStage(inputRows, ctx)) .Index().Build(0) .Done()); @@ -190,7 +190,7 @@ TVectorIndexPrefixLookup BuildVectorIndexPrefixLookup( ? inputRows : Build(ctx, pos) .Output() - .Stage(ReadTableToStage(inputRows, ctx)) + .Stage(ReadInputToStage(inputRows, ctx)) .Index().Build(0) .Build() .Done()); diff --git a/ydb/core/kqp/ut/indexes/kqp_indexes_fulltext_ut.cpp b/ydb/core/kqp/ut/indexes/kqp_indexes_fulltext_ut.cpp index c9975be3814b..6adc7eebd63d 100644 --- a/ydb/core/kqp/ut/indexes/kqp_indexes_fulltext_ut.cpp +++ b/ydb/core/kqp/ut/indexes/kqp_indexes_fulltext_ut.cpp @@ -196,7 +196,7 @@ Y_UNIT_TEST(InsertRowMultipleTimes) { { // InsertRow TString query = R"sql( INSERT INTO `/Root/Texts` (Key, Text, Data) VALUES - (152, "Rabbit love foxes.", "rabbit data") + (152, "Rabbits love foxes.", "rabbit data") )sql"; auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); @@ -215,7 +215,7 @@ Y_UNIT_TEST(InsertRowMultipleTimes) { [[151u];"love"]; [[152u];"love"]; [[200u];"love"]; - [[152u];"rabbit"]; + [[152u];"rabbits"]; [[151u];"wolfs"] ])", NYdb::FormatResultSetYson(index)); } @@ -340,11 +340,653 @@ Y_UNIT_TEST(InsertRowCoveredReturning) { } Y_UNIT_TEST(UpsertRow) { - // TODO: upserts are not implemented + auto kikimr = Kikimr(); + auto db = kikimr.GetQueryClient(); + + CreateTexts(db); + UpsertSomeTexts(db); + AddIndex(db); + auto index = ReadIndex(db); + CompareYson(R"([ + [[100u];"cats"]; + [[200u];"dogs"]; + [[200u];"foxes"]; + [[100u];"love"]; + [[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // UpsertRow - insert new row + TString query = R"sql( + UPSERT INTO `/Root/Texts` (Key, Text, Data) VALUES + (150, "Foxes love cats.", "foxes data") + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + } + index = ReadIndex(db); + CompareYson(R"([ + [[100u];"cats"]; + [[150u];"cats"]; + [[200u];"dogs"]; + [[150u];"foxes"]; + [[200u];"foxes"]; + [[100u];"love"]; + [[150u];"love"]; + [[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // UpsertRow - modify existing row + TString query = R"sql( + UPSERT INTO `/Root/Texts` (Key, Text, Data) VALUES + (100, "Birds love foxes.", "birds data") + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + } + index = ReadIndex(db); + CompareYson(R"([ + [[100u];"birds"]; + [[150u];"cats"]; + [[200u];"dogs"]; + [[100u];"foxes"]; + [[150u];"foxes"]; + [[200u];"foxes"]; + [[100u];"love"]; + [[150u];"love"]; + [[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); +} + +Y_UNIT_TEST(UpsertRowMultipleTimes) { + auto kikimr = Kikimr(); + auto db = kikimr.GetQueryClient(); + + CreateTexts(db); + UpsertSomeTexts(db); + AddIndex(db); + auto index = ReadIndex(db); + CompareYson(R"([ + [[100u];"cats"]; + [[200u];"dogs"]; + [[200u];"foxes"]; + [[100u];"love"]; + [[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // UpsertRow - insert new rows + TString query = R"sql( + UPSERT INTO `/Root/Texts` (Key, Text, Data) VALUES + (150, "Foxes love cats.", "foxes data"), + (151, "Wolfs love foxes.", "cows data") + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + } + { // UpsertRow - insert new row + TString query = R"sql( + UPSERT INTO `/Root/Texts` (Key, Text, Data) VALUES + (152, "Rabbits love foxes.", "rabbits data") + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + } + index = ReadIndex(db); + CompareYson(R"([ + [[100u];"cats"]; + [[150u];"cats"]; + [[200u];"dogs"]; + [[150u];"foxes"]; + [[151u];"foxes"]; + [[152u];"foxes"]; + [[200u];"foxes"]; + [[100u];"love"]; + [[150u];"love"]; + [[151u];"love"]; + [[152u];"love"]; + [[200u];"love"]; + [[152u];"rabbits"]; + [[151u];"wolfs"] + ])", NYdb::FormatResultSetYson(index)); + + { // UpsertRow - modify existing row + TString query = R"sql( + UPSERT INTO `/Root/Texts` (Key, Text, Data) VALUES + (100, "Birds love rabbits.", "birds data") + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + } + index = ReadIndex(db); + CompareYson(R"([ + [[100u];"birds"]; + [[150u];"cats"]; + [[200u];"dogs"]; + [[150u];"foxes"]; + [[151u];"foxes"]; + [[152u];"foxes"]; + [[200u];"foxes"]; + [[100u];"love"]; + [[150u];"love"]; + [[151u];"love"]; + [[152u];"love"]; + [[200u];"love"]; + [[100u];"rabbits"]; + [[152u];"rabbits"]; + [[151u];"wolfs"] + ])", NYdb::FormatResultSetYson(index)); +} + +Y_UNIT_TEST(UpsertRowReturning) { + auto kikimr = Kikimr(); + auto db = kikimr.GetQueryClient(); + + CreateTexts(db); + UpsertSomeTexts(db); + AddIndex(db); + auto index = ReadIndex(db); + CompareYson(R"([ + [[100u];"cats"]; + [[200u];"dogs"]; + [[200u];"foxes"]; + [[100u];"love"]; + [[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // UpsertRow - insert new row + TString query = R"sql( + UPSERT INTO `/Root/Texts` (Key, Text, Data) VALUES + (150, "Foxes love cats.", "foxes data") + RETURNING * + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + CompareYson(R"([ + [["foxes data"];[150u];["Foxes love cats."]] + ])", NYdb::FormatResultSetYson(result.GetResultSet(0))); + } + index = ReadIndex(db); + CompareYson(R"([ + [[100u];"cats"]; + [[150u];"cats"]; + [[200u];"dogs"]; + [[150u];"foxes"]; + [[200u];"foxes"]; + [[100u];"love"]; + [[150u];"love"]; + [[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // UpsertRow - modify existing row + TString query = R"sql( + UPSERT INTO `/Root/Texts` (Key, Text, Data) VALUES + (200, "Birds love rabbits.", "birds data") + RETURNING * + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + CompareYson(R"([ + [["birds data"];[200u];["Birds love rabbits."]] + ])", NYdb::FormatResultSetYson(result.GetResultSet(0))); + } + index = ReadIndex(db); + CompareYson(R"([ + [[200u];"birds"]; + [[100u];"cats"]; + [[150u];"cats"]; + [[150u];"foxes"]; + [[100u];"love"]; + [[150u];"love"]; + [[200u];"love"]; + [[200u];"rabbits"] + ])", NYdb::FormatResultSetYson(index)); } Y_UNIT_TEST(UpsertRowCovered) { - // TODO: upserts are not implemented + auto kikimr = Kikimr(); + auto db = kikimr.GetQueryClient(); + + CreateTexts(db); + UpsertSomeTexts(db); + AddIndexCovered(db); + auto index = ReadIndex(db); + CompareYson(R"([ + [["cats data"];[100u];"cats"]; + [["cats data"];[200u];"dogs"]; + [["cats data"];[200u];"foxes"]; + [["cats data"];[100u];"love"]; + [["cats data"];[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // UpsertRow - insert new row + TString query = R"sql( + UPSERT INTO `/Root/Texts` (Key, Text, Data) VALUES + (150, "Foxes love cats.", "foxes data") + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + } + index = ReadIndex(db); + CompareYson(R"([ + [["cats data"];[100u];"cats"]; + [["foxes data"];[150u];"cats"]; + [["cats data"];[200u];"dogs"]; + [["foxes data"];[150u];"foxes"]; + [["cats data"];[200u];"foxes"]; + [["cats data"];[100u];"love"]; + [["foxes data"];[150u];"love"]; + [["cats data"];[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // UpsertRow - modify existing row + TString query = R"sql( + UPSERT INTO `/Root/Texts` (Key, Text, Data) VALUES + (100, "Birds love foxes.", "birds data") + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + } + index = ReadIndex(db); + CompareYson(R"([ + [["birds data"];[100u];"birds"]; + [["foxes data"];[150u];"cats"]; + [["cats data"];[200u];"dogs"]; + [["birds data"];[100u];"foxes"]; + [["foxes data"];[150u];"foxes"]; + [["cats data"];[200u];"foxes"]; + [["birds data"];[100u];"love"]; + [["foxes data"];[150u];"love"]; + [["cats data"];[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); +} + +Y_UNIT_TEST(UpsertRowCoveredReturning) { + auto kikimr = Kikimr(); + auto db = kikimr.GetQueryClient(); + + CreateTexts(db); + UpsertSomeTexts(db); + AddIndexCovered(db); + auto index = ReadIndex(db); + CompareYson(R"([ + [["cats data"];[100u];"cats"]; + [["cats data"];[200u];"dogs"]; + [["cats data"];[200u];"foxes"]; + [["cats data"];[100u];"love"]; + [["cats data"];[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // UpsertRow - insert new row + TString query = R"sql( + UPSERT INTO `/Root/Texts` (Key, Text, Data) VALUES + (150, "Foxes love cats.", "foxes data") + RETURNING * + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + CompareYson(R"([ + [["foxes data"];[150u];["Foxes love cats."]] + ])", NYdb::FormatResultSetYson(result.GetResultSet(0))); + } + index = ReadIndex(db); + CompareYson(R"([ + [["cats data"];[100u];"cats"]; + [["foxes data"];[150u];"cats"]; + [["cats data"];[200u];"dogs"]; + [["foxes data"];[150u];"foxes"]; + [["cats data"];[200u];"foxes"]; + [["cats data"];[100u];"love"]; + [["foxes data"];[150u];"love"]; + [["cats data"];[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // UpsertRow - modify existing row + TString query = R"sql( + UPSERT INTO `/Root/Texts` (Key, Text, Data) VALUES + (200, "Birds love rabbits.", "birds data") + RETURNING * + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + CompareYson(R"([ + [["birds data"];[200u];["Birds love rabbits."]] + ])", NYdb::FormatResultSetYson(result.GetResultSet(0))); + } + index = ReadIndex(db); + CompareYson(R"([ + [["birds data"];[200u];"birds"]; + [["cats data"];[100u];"cats"]; + [["foxes data"];[150u];"cats"]; + [["foxes data"];[150u];"foxes"]; + [["cats data"];[100u];"love"]; + [["foxes data"];[150u];"love"]; + [["birds data"];[200u];"love"]; + [["birds data"];[200u];"rabbits"] + ])", NYdb::FormatResultSetYson(index)); +} + +Y_UNIT_TEST(ReplaceRow) { + auto kikimr = Kikimr(); + auto db = kikimr.GetQueryClient(); + + CreateTexts(db); + UpsertSomeTexts(db); + AddIndex(db); + auto index = ReadIndex(db); + CompareYson(R"([ + [[100u];"cats"]; + [[200u];"dogs"]; + [[200u];"foxes"]; + [[100u];"love"]; + [[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // ReplaceRow - insert new row + TString query = R"sql( + REPLACE INTO `/Root/Texts` (Key, Text, Data) VALUES + (150, "Foxes love cats.", "foxes data") + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + } + index = ReadIndex(db); + CompareYson(R"([ + [[100u];"cats"]; + [[150u];"cats"]; + [[200u];"dogs"]; + [[150u];"foxes"]; + [[200u];"foxes"]; + [[100u];"love"]; + [[150u];"love"]; + [[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // ReplaceRow - replace existing row + TString query = R"sql( + REPLACE INTO `/Root/Texts` (Key, Text, Data) VALUES + (100, "Birds love foxes.", "birds data") + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + } + index = ReadIndex(db); + CompareYson(R"([ + [[100u];"birds"]; + [[150u];"cats"]; + [[200u];"dogs"]; + [[100u];"foxes"]; + [[150u];"foxes"]; + [[200u];"foxes"]; + [[100u];"love"]; + [[150u];"love"]; + [[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); +} + +Y_UNIT_TEST(ReplaceRowMultipleTimes) { + auto kikimr = Kikimr(); + auto db = kikimr.GetQueryClient(); + + CreateTexts(db); + UpsertSomeTexts(db); + AddIndex(db); + auto index = ReadIndex(db); + CompareYson(R"([ + [[100u];"cats"]; + [[200u];"dogs"]; + [[200u];"foxes"]; + [[100u];"love"]; + [[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // ReplaceRow - insert new rows + TString query = R"sql( + REPLACE INTO `/Root/Texts` (Key, Text, Data) VALUES + (150, "Foxes love cats.", "foxes data"), + (151, "Wolfs love foxes.", "cows data") + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + } + { // ReplaceRow - insert new row + TString query = R"sql( + REPLACE INTO `/Root/Texts` (Key, Text, Data) VALUES + (152, "Rabbits love foxes.", "rabbit data") + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + } + index = ReadIndex(db); + CompareYson(R"([ + [[100u];"cats"]; + [[150u];"cats"]; + [[200u];"dogs"]; + [[150u];"foxes"]; + [[151u];"foxes"]; + [[152u];"foxes"]; + [[200u];"foxes"]; + [[100u];"love"]; + [[150u];"love"]; + [[151u];"love"]; + [[152u];"love"]; + [[200u];"love"]; + [[152u];"rabbits"]; + [[151u];"wolfs"] + ])", NYdb::FormatResultSetYson(index)); + + { // ReplaceRow - replace existing row + TString query = R"sql( + REPLACE INTO `/Root/Texts` (Key, Text, Data) VALUES + (100, "Birds love rabbits.", "birds data") + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + } + index = ReadIndex(db); + CompareYson(R"([ + [[100u];"birds"]; + [[150u];"cats"]; + [[200u];"dogs"]; + [[150u];"foxes"]; + [[151u];"foxes"]; + [[152u];"foxes"]; + [[200u];"foxes"]; + [[100u];"love"]; + [[150u];"love"]; + [[151u];"love"]; + [[152u];"love"]; + [[200u];"love"]; + [[100u];"rabbits"]; + [[152u];"rabbits"]; + [[151u];"wolfs"] + ])", NYdb::FormatResultSetYson(index)); +} + +Y_UNIT_TEST(ReplaceRowReturning) { + auto kikimr = Kikimr(); + auto db = kikimr.GetQueryClient(); + + CreateTexts(db); + UpsertSomeTexts(db); + AddIndex(db); + auto index = ReadIndex(db); + CompareYson(R"([ + [[100u];"cats"]; + [[200u];"dogs"]; + [[200u];"foxes"]; + [[100u];"love"]; + [[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // ReplaceRow - insert new row + TString query = R"sql( + REPLACE INTO `/Root/Texts` (Key, Text, Data) VALUES + (150, "Foxes love cats.", "foxes data") + RETURNING * + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + CompareYson(R"([ + [["foxes data"];[150u];["Foxes love cats."]] + ])", NYdb::FormatResultSetYson(result.GetResultSet(0))); + } + index = ReadIndex(db); + CompareYson(R"([ + [[100u];"cats"]; + [[150u];"cats"]; + [[200u];"dogs"]; + [[150u];"foxes"]; + [[200u];"foxes"]; + [[100u];"love"]; + [[150u];"love"]; + [[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // ReplaceRow - replace existing row + TString query = R"sql( + REPLACE INTO `/Root/Texts` (Key, Text, Data) VALUES + (200, "Birds love rabbits.", "birds data") + RETURNING * + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + CompareYson(R"([ + [["birds data"];[200u];["Birds love rabbits."]] + ])", NYdb::FormatResultSetYson(result.GetResultSet(0))); + } + index = ReadIndex(db); + CompareYson(R"([ + [[200u];"birds"]; + [[100u];"cats"]; + [[150u];"cats"]; + [[150u];"foxes"]; + [[100u];"love"]; + [[150u];"love"]; + [[200u];"love"]; + [[200u];"rabbits"] + ])", NYdb::FormatResultSetYson(index)); +} + +Y_UNIT_TEST(ReplaceRowCovered) { + auto kikimr = Kikimr(); + auto db = kikimr.GetQueryClient(); + + CreateTexts(db); + UpsertSomeTexts(db); + AddIndexCovered(db); + auto index = ReadIndex(db); + CompareYson(R"([ + [["cats data"];[100u];"cats"]; + [["cats data"];[200u];"dogs"]; + [["cats data"];[200u];"foxes"]; + [["cats data"];[100u];"love"]; + [["cats data"];[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // ReplaceRow - insert new row + TString query = R"sql( + REPLACE INTO `/Root/Texts` (Key, Text, Data) VALUES + (150, "Foxes love cats.", "foxes data") + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + } + index = ReadIndex(db); + CompareYson(R"([ + [["cats data"];[100u];"cats"]; + [["foxes data"];[150u];"cats"]; + [["cats data"];[200u];"dogs"]; + [["foxes data"];[150u];"foxes"]; + [["cats data"];[200u];"foxes"]; + [["cats data"];[100u];"love"]; + [["foxes data"];[150u];"love"]; + [["cats data"];[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // ReplaceRow - replace existing row + TString query = R"sql( + REPLACE INTO `/Root/Texts` (Key, Text, Data) VALUES + (100, "Birds love foxes.", "birds data") + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + } + index = ReadIndex(db); + CompareYson(R"([ + [["birds data"];[100u];"birds"]; + [["foxes data"];[150u];"cats"]; + [["cats data"];[200u];"dogs"]; + [["birds data"];[100u];"foxes"]; + [["foxes data"];[150u];"foxes"]; + [["cats data"];[200u];"foxes"]; + [["birds data"];[100u];"love"]; + [["foxes data"];[150u];"love"]; + [["cats data"];[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); +} + +Y_UNIT_TEST(ReplaceRowCoveredReturning) { + auto kikimr = Kikimr(); + auto db = kikimr.GetQueryClient(); + + CreateTexts(db); + UpsertSomeTexts(db); + AddIndexCovered(db); + auto index = ReadIndex(db); + CompareYson(R"([ + [["cats data"];[100u];"cats"]; + [["cats data"];[200u];"dogs"]; + [["cats data"];[200u];"foxes"]; + [["cats data"];[100u];"love"]; + [["cats data"];[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // ReplaceRow - insert new row + TString query = R"sql( + REPLACE INTO `/Root/Texts` (Key, Text, Data) VALUES + (150, "Foxes love cats.", "foxes data") + RETURNING * + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + CompareYson(R"([ + [["foxes data"];[150u];["Foxes love cats."]] + ])", NYdb::FormatResultSetYson(result.GetResultSet(0))); + } + index = ReadIndex(db); + CompareYson(R"([ + [["cats data"];[100u];"cats"]; + [["foxes data"];[150u];"cats"]; + [["cats data"];[200u];"dogs"]; + [["foxes data"];[150u];"foxes"]; + [["cats data"];[200u];"foxes"]; + [["cats data"];[100u];"love"]; + [["foxes data"];[150u];"love"]; + [["cats data"];[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // ReplaceRow - replace existing row + TString query = R"sql( + REPLACE INTO `/Root/Texts` (Key, Text, Data) VALUES + (200, "Birds love rabbits.", "birds data") + RETURNING * + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + CompareYson(R"([ + [["birds data"];[200u];["Birds love rabbits."]] + ])", NYdb::FormatResultSetYson(result.GetResultSet(0))); + } + index = ReadIndex(db); + CompareYson(R"([ + [["birds data"];[200u];"birds"]; + [["cats data"];[100u];"cats"]; + [["foxes data"];[150u];"cats"]; + [["foxes data"];[150u];"foxes"]; + [["cats data"];[100u];"love"]; + [["foxes data"];[150u];"love"]; + [["birds data"];[200u];"love"]; + [["birds data"];[200u];"rabbits"] + ])", NYdb::FormatResultSetYson(index)); } Y_UNIT_TEST(DeleteRow) { @@ -722,11 +1364,333 @@ Y_UNIT_TEST(DeleteRowCoveredReturning) { } Y_UNIT_TEST(UpdateRow) { - // TODO: test update of key, text, data + auto kikimr = Kikimr(); + auto db = kikimr.GetQueryClient(); + + CreateTexts(db); + UpsertSomeTexts(db); + AddIndex(db); + auto index = ReadIndex(db); + CompareYson(R"([ + [[100u];"cats"]; + [[200u];"dogs"]; + [[200u];"foxes"]; + [[100u];"love"]; + [[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // Update Text - index key column updated + TString query = R"sql( + UPDATE `/Root/Texts` SET Text = "Birds love foxes." WHERE Key = 100; + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + } + index = ReadIndex(db); + CompareYson(R"([ + [[100u];"birds"]; + [[200u];"dogs"]; + [[100u];"foxes"]; + [[200u];"foxes"]; + [[100u];"love"]; + [[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // Update Data - non-indexed column updated + TString query = R"sql( + UPDATE `/Root/Texts` SET Data = "birds data" WHERE Key = 100; + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + } + index = ReadIndex(db); + CompareYson(R"([ + [[100u];"birds"]; + [[200u];"dogs"]; + [[100u];"foxes"]; + [[200u];"foxes"]; + [[100u];"love"]; + [[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // Update by ON + TString query = R"sql( + UPDATE `/Root/Texts` ON SELECT 200 AS `Key`, "Rabbits love birds." AS `Text`, "rabbits data" AS `Data`; + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + } + index = ReadIndex(db); + CompareYson(R"([ + [[100u];"birds"]; + [[200u];"birds"]; + [[100u];"foxes"]; + [[100u];"love"]; + [[200u];"love"]; + [[200u];"rabbits"] + ])", NYdb::FormatResultSetYson(index)); } Y_UNIT_TEST(UpdateRowCovered) { - // TODO: test update of key, text, data + auto kikimr = Kikimr(); + auto db = kikimr.GetQueryClient(); + + CreateTexts(db); + UpsertSomeTexts(db); + AddIndexCovered(db); + auto index = ReadIndex(db); + CompareYson(R"([ + [["cats data"];[100u];"cats"]; + [["cats data"];[200u];"dogs"]; + [["cats data"];[200u];"foxes"]; + [["cats data"];[100u];"love"]; + [["cats data"];[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // Update Text - index key column updated + TString query = R"sql( + UPDATE `/Root/Texts` SET Text = "Birds love foxes." WHERE Key = 100; + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + } + index = ReadIndex(db); + CompareYson(R"([ + [["cats data"];[100u];"birds"]; + [["cats data"];[200u];"dogs"]; + [["cats data"];[100u];"foxes"]; + [["cats data"];[200u];"foxes"]; + [["cats data"];[100u];"love"]; + [["cats data"];[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // Update Data - covered column updated + TString query = R"sql( + UPDATE `/Root/Texts` SET Data = "birds data" WHERE Key = 100; + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + } + index = ReadIndex(db); + CompareYson(R"([ + [["birds data"];[100u];"birds"]; + [["cats data"];[200u];"dogs"]; + [["birds data"];[100u];"foxes"]; + [["cats data"];[200u];"foxes"]; + [["birds data"];[100u];"love"]; + [["cats data"];[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // Update by ON + TString query = R"sql( + UPDATE `/Root/Texts` ON SELECT 200 AS `Key`, "Rabbits love birds." AS `Text`, "rabbits data" AS `Data`; + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + } + index = ReadIndex(db); + CompareYson(R"([ + [["birds data"];[100u];"birds"]; + [["rabbits data"];[200u];"birds"]; + [["birds data"];[100u];"foxes"]; + [["birds data"];[100u];"love"]; + [["rabbits data"];[200u];"love"]; + [["rabbits data"];[200u];"rabbits"] + ])", NYdb::FormatResultSetYson(index)); +} + +Y_UNIT_TEST(UpdateRowMultipleTimes) { + auto kikimr = Kikimr(); + auto db = kikimr.GetQueryClient(); + + CreateTexts(db); + UpsertSomeTexts(db); + AddIndex(db); + auto index = ReadIndex(db); + CompareYson(R"([ + [[100u];"cats"]; + [[200u];"dogs"]; + [[200u];"foxes"]; + [[100u];"love"]; + [[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // Update multiple rows + TString query = R"sql( + UPDATE `/Root/Texts` SET Text = "Birds love foxes." WHERE Key = 100 OR Key = 200; + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + } + index = ReadIndex(db); + CompareYson(R"([ + [[100u];"birds"]; + [[200u];"birds"]; + [[100u];"foxes"]; + [[200u];"foxes"]; + [[100u];"love"]; + [[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); +} + +Y_UNIT_TEST(UpdateRowReturning) { + auto kikimr = Kikimr(); + auto db = kikimr.GetQueryClient(); + + CreateTexts(db); + UpsertSomeTexts(db); + AddIndex(db); + auto index = ReadIndex(db); + CompareYson(R"([ + [[100u];"cats"]; + [[200u];"dogs"]; + [[200u];"foxes"]; + [[100u];"love"]; + [[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // Update Text - index key column updated + TString query = R"sql( + UPDATE `/Root/Texts` SET Text = "Birds love foxes." WHERE Key = 100 + RETURNING * + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + CompareYson(R"([ + [["cats data"];[100u];["Birds love foxes."]] + ])", NYdb::FormatResultSetYson(result.GetResultSet(0))); + } + index = ReadIndex(db); + CompareYson(R"([ + [[100u];"birds"]; + [[200u];"dogs"]; + [[100u];"foxes"]; + [[200u];"foxes"]; + [[100u];"love"]; + [[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // Update Data - non-indexed column updated + TString query = R"sql( + UPDATE `/Root/Texts` SET Data = "birds data" WHERE Key = 100 + RETURNING * + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + CompareYson(R"([ + [["birds data"];[100u];["Birds love foxes."]] + ])", NYdb::FormatResultSetYson(result.GetResultSet(0))); + } + index = ReadIndex(db); + CompareYson(R"([ + [[100u];"birds"]; + [[200u];"dogs"]; + [[100u];"foxes"]; + [[200u];"foxes"]; + [[100u];"love"]; + [[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // Update by ON + TString query = R"sql( + UPDATE `/Root/Texts` ON SELECT 200 AS `Key`, "Rabbits love birds." AS `Text`, "rabbits data" AS `Data` + RETURNING * + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + CompareYson(R"([ + [["rabbits data"];[200u];["Rabbits love birds."]] + ])", NYdb::FormatResultSetYson(result.GetResultSet(0))); + } + index = ReadIndex(db); + CompareYson(R"([ + [[100u];"birds"]; + [[200u];"birds"]; + [[100u];"foxes"]; + [[100u];"love"]; + [[200u];"love"]; + [[200u];"rabbits"] + ])", NYdb::FormatResultSetYson(index)); +} + +Y_UNIT_TEST(UpdateRowCoveredReturning) { + auto kikimr = Kikimr(); + auto db = kikimr.GetQueryClient(); + + CreateTexts(db); + UpsertSomeTexts(db); + AddIndexCovered(db); + auto index = ReadIndex(db); + CompareYson(R"([ + [["cats data"];[100u];"cats"]; + [["cats data"];[200u];"dogs"]; + [["cats data"];[200u];"foxes"]; + [["cats data"];[100u];"love"]; + [["cats data"];[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // Update Text - index key column updated + TString query = R"sql( + UPDATE `/Root/Texts` SET Text = "Birds love foxes." WHERE Key = 100 + RETURNING * + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + CompareYson(R"([ + [["cats data"];[100u];["Birds love foxes."]] + ])", NYdb::FormatResultSetYson(result.GetResultSet(0))); + } + index = ReadIndex(db); + CompareYson(R"([ + [["cats data"];[100u];"birds"]; + [["cats data"];[200u];"dogs"]; + [["cats data"];[100u];"foxes"]; + [["cats data"];[200u];"foxes"]; + [["cats data"];[100u];"love"]; + [["cats data"];[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // Update Data - covered column updated + TString query = R"sql( + UPDATE `/Root/Texts` SET Data = "birds data" WHERE Key = 100 + RETURNING * + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + CompareYson(R"([ + [["birds data"];[100u];["Birds love foxes."]] + ])", NYdb::FormatResultSetYson(result.GetResultSet(0))); + } + index = ReadIndex(db); + CompareYson(R"([ + [["birds data"];[100u];"birds"]; + [["cats data"];[200u];"dogs"]; + [["birds data"];[100u];"foxes"]; + [["cats data"];[200u];"foxes"]; + [["birds data"];[100u];"love"]; + [["cats data"];[200u];"love"] + ])", NYdb::FormatResultSetYson(index)); + + { // Update by ON + TString query = R"sql( + UPDATE `/Root/Texts` ON SELECT 200 AS `Key`, "Rabbits love birds." AS `Text`, "rabbits data" AS `Data` + RETURNING * + )sql"; + auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + CompareYson(R"([ + [["rabbits data"];[200u];["Rabbits love birds."]] + ])", NYdb::FormatResultSetYson(result.GetResultSet(0))); + } + index = ReadIndex(db); + CompareYson(R"([ + [["birds data"];[100u];"birds"]; + [["rabbits data"];[200u];"birds"]; + [["birds data"];[100u];"foxes"]; + [["birds data"];[100u];"love"]; + [["rabbits data"];[200u];"love"]; + [["rabbits data"];[200u];"rabbits"] + ])", NYdb::FormatResultSetYson(index)); } Y_UNIT_TEST(CreateTable) { @@ -749,7 +1713,6 @@ Y_UNIT_TEST(CreateTable) { auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); } - return; // TODO: upserts are not implemented UpsertTexts(db); auto index = ReadIndex(db); CompareYson(R"([ @@ -789,7 +1752,6 @@ Y_UNIT_TEST(CreateTableCovered) { auto result = db.ExecuteQuery(query, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); } - return; // TODO: upserts are not implemented UpsertTexts(db); auto index = ReadIndex(db); CompareYson(R"([