From caa23328d636860e580cd9523e82a57df709968a Mon Sep 17 00:00:00 2001 From: Vitalii Gridnev Date: Tue, 21 Jan 2025 23:53:22 +0300 Subject: [PATCH] don't update indexes when all input columns are specified for indexes but values are not changed (#12940) --- .../effects/kqp_opt_phy_upsert_index.cpp | 12 -- ydb/core/kqp/ut/service/kqp_qs_queries_ut.cpp | 164 ++++++++++++++++++ 2 files changed, 164 insertions(+), 12 deletions(-) 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 d5bd02b7fb06..04a6176dffe8 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 @@ -153,18 +153,6 @@ TExprBase MakeUpsertIndexRows(TKqpPhyUpsertIndexMode mode, const TDqPhyPrecomput const TVector& indexColumns, const TKikimrTableDescription& table, TPositionHandle pos, TExprContext& ctx, bool opt) { - // Check if we can update index table from just input data - bool allColumnFromInput = true; // - indicate all data from input - for (const auto& column : indexColumns) { - allColumnFromInput = allColumnFromInput && inputColumns.contains(column); - } - - if (allColumnFromInput) { - return mode == TKqpPhyUpsertIndexMode::UpdateOn - ? MakeNonexistingRowsFilter(inputRows, lookupDict, table.Metadata->KeyColumnNames, pos, ctx) - : TExprBase(inputRows); - } - auto inputRowsArg = TCoArgument(ctx.NewArgument(pos, "input_rows")); auto inputRowArg = TCoArgument(ctx.NewArgument(pos, "input_row")); auto lookupDictArg = TCoArgument(ctx.NewArgument(pos, "lookup_dict")); diff --git a/ydb/core/kqp/ut/service/kqp_qs_queries_ut.cpp b/ydb/core/kqp/ut/service/kqp_qs_queries_ut.cpp index 74c6a13d3abd..0c5c0dd171d1 100644 --- a/ydb/core/kqp/ut/service/kqp_qs_queries_ut.cpp +++ b/ydb/core/kqp/ut/service/kqp_qs_queries_ut.cpp @@ -413,6 +413,170 @@ Y_UNIT_TEST_SUITE(KqpQueryService) { UNIT_ASSERT_VALUES_EQUAL(count, 1); } + Y_UNIT_TEST(ExecuteQueryUpsertDoesntChangeIndexedValuesIfNotChanged) { + auto kikimr = DefaultKikimrRunner(); + auto db = kikimr.GetQueryClient(); + + auto settings = TExecuteQuerySettings() + .StatsMode(EStatsMode::Full); + + { + auto result = db.ExecuteQuery(R"( + create table test ( + id1 Uint64, + id2 Uint64, + value Utf8, + PRIMARY KEY(id1, id2), + INDEX Id2Idx GLOBAL ON (id2), + INDEX ValueIdx GLOBAL ON (value) + ); + + )", TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + } + + { + auto result = db.ExecuteQuery(R"( + UPSERT INTO test (id1, id2, value) VALUES (2, 2, "c"u), (3, 3, null), (4, 4, null); + )", TTxControl::BeginTx().CommitTx(), settings).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + auto stats = NYdb::TProtoAccessor::GetProto(*result.GetStats()); + AssertTableStats(stats, "/Root/test", {.ExpectedUpdates = 3}); + AssertTableStats(stats, "/Root/test/Id2Idx/indexImplTable", {.ExpectedUpdates = 3}); + AssertTableStats(stats, "/Root/test/ValueIdx/indexImplTable", {.ExpectedUpdates = 3}); + } + + // value not changed, no updates to indexes + { + auto result = db.ExecuteQuery(R"( + UPSERT INTO test (id1, id2) VALUES (2, 2); + )", TTxControl::BeginTx().CommitTx(), settings).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + auto stats = NYdb::TProtoAccessor::GetProto(*result.GetStats()); + AssertTableStats(stats, "/Root/test", {.ExpectedUpdates = 1}); + AssertTableStats(stats, "/Root/test/Id2Idx/indexImplTable", {.ExpectedUpdates = 0}); + AssertTableStats(stats, "/Root/test/ValueIdx/indexImplTable", {.ExpectedUpdates = 0}); + } + + // value not changed, no updates to indexes + { + auto result = db.ExecuteQuery(R"( + UPSERT INTO test (id1, id2, value) VALUES (2, 2, "c"u); + )", TTxControl::BeginTx().CommitTx(), settings).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + auto stats = NYdb::TProtoAccessor::GetProto(*result.GetStats()); + AssertTableStats(stats, "/Root/test", {.ExpectedUpdates = 1}); + AssertTableStats(stats, "/Root/test/Id2Idx/indexImplTable", {.ExpectedUpdates = 0}); + AssertTableStats(stats, "/Root/test/ValueIdx/indexImplTable", {.ExpectedUpdates = 0}); + } + + // value IS changed, updates to index by VALUE + { + auto result = db.ExecuteQuery(R"( + UPSERT INTO test (id1, id2, value) VALUES (2, 2, "Q"u); + )", TTxControl::BeginTx().CommitTx(), settings).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + auto stats = NYdb::TProtoAccessor::GetProto(*result.GetStats()); + AssertTableStats(stats, "/Root/test", {.ExpectedUpdates = 1}); + AssertTableStats(stats, "/Root/test/Id2Idx/indexImplTable", {.ExpectedUpdates = 0}); + AssertTableStats(stats, "/Root/test/ValueIdx/indexImplTable", {.ExpectedUpdates = 1}); + const TString query = "SELECT id1, id2, value FROM test VIEW ValueIdx WHERE value = \"Q\"u"; + auto selectResult = db.ExecuteQuery(query, TTxControl::BeginTx().CommitTx(), settings).ExtractValueSync(); + CompareYson(R"([[[2u];[2u];["Q"]]])", FormatResultSetYson(selectResult.GetResultSet(0))); + } + + // value IS NOT changed + { + auto result = db.ExecuteQuery(R"( + UPDATE test ON SELECT 2 as id1, 2 as id2, "Q"u as value; + )", TTxControl::BeginTx().CommitTx(), settings).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + auto stats = NYdb::TProtoAccessor::GetProto(*result.GetStats()); + AssertTableStats(stats, "/Root/test", {.ExpectedUpdates = 1}); + AssertTableStats(stats, "/Root/test/Id2Idx/indexImplTable", {.ExpectedUpdates = 0}); + AssertTableStats(stats, "/Root/test/ValueIdx/indexImplTable", {.ExpectedUpdates = 0}); + const TString query = "SELECT id1, id2, value FROM test VIEW ValueIdx WHERE value = \"Q\"u"; + auto selectResult = db.ExecuteQuery(query, TTxControl::BeginTx().CommitTx(), settings).ExtractValueSync(); + CompareYson(R"([[[2u];[2u];["Q"]]])", FormatResultSetYson(selectResult.GetResultSet(0))); + } + + + // value IS NOT changed + { + auto result = db.ExecuteQuery(R"( + UPDATE test ON SELECT 2 as id1, 2 as id2, "R"u as value; + )", TTxControl::BeginTx().CommitTx(), settings).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + auto stats = NYdb::TProtoAccessor::GetProto(*result.GetStats()); + AssertTableStats(stats, "/Root/test", {.ExpectedUpdates = 1}); + AssertTableStats(stats, "/Root/test/Id2Idx/indexImplTable", {.ExpectedUpdates = 0}); + AssertTableStats(stats, "/Root/test/ValueIdx/indexImplTable", {.ExpectedUpdates = 1}); + const TString query = "SELECT id1, id2, value FROM test VIEW ValueIdx WHERE value = \"R\"u"; + auto selectResult = db.ExecuteQuery(query, TTxControl::BeginTx().CommitTx(), settings).ExtractValueSync(); + CompareYson(R"([[[2u];[2u];["R"]]])", FormatResultSetYson(selectResult.GetResultSet(0))); + } + + // value (null) IS NOT changed + { + auto result = db.ExecuteQuery(R"( + UPSERT INTO test (id1, id2, value) VALUES (3, 3, null); + )", TTxControl::BeginTx().CommitTx(), settings).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + auto stats = NYdb::TProtoAccessor::GetProto(*result.GetStats()); + AssertTableStats(stats, "/Root/test", {.ExpectedUpdates = 1}); + AssertTableStats(stats, "/Root/test/Id2Idx/indexImplTable", {.ExpectedUpdates = 0}); + AssertTableStats(stats, "/Root/test/ValueIdx/indexImplTable", {.ExpectedUpdates = 0}); + const TString query = "SELECT id1, id2, value FROM test VIEW ValueIdx WHERE value is null"; + auto selectResult = db.ExecuteQuery(query, TTxControl::BeginTx().CommitTx(), settings).ExtractValueSync(); + CompareYson(R"([[[3u];[3u];#];[[4u];[4u];#]])", FormatResultSetYson(selectResult.GetResultSet(0))); + } + + // value (null) IS NOT changed + { + auto result = db.ExecuteQuery(R"( + UPDATE test ON SELECT 3 as id1, 3 as id2, null as value; + )", TTxControl::BeginTx().CommitTx(), settings).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + auto stats = NYdb::TProtoAccessor::GetProto(*result.GetStats()); + AssertTableStats(stats, "/Root/test", {.ExpectedUpdates = 1}); + AssertTableStats(stats, "/Root/test/Id2Idx/indexImplTable", {.ExpectedUpdates = 0}); + AssertTableStats(stats, "/Root/test/ValueIdx/indexImplTable", {.ExpectedUpdates = 0}); + const TString query = "SELECT id1, id2, value FROM test VIEW ValueIdx WHERE value is null"; + auto selectResult = db.ExecuteQuery(query, TTxControl::BeginTx().CommitTx(), settings).ExtractValueSync(); + CompareYson(R"([[[3u];[3u];#];[[4u];[4u];#]])", FormatResultSetYson(selectResult.GetResultSet(0))); + } + + // value (null) IS changed + { + auto result = db.ExecuteQuery(R"( + UPSERT INTO test (id1, id2, value) VALUES (3, 3, "T"u); + )", TTxControl::BeginTx().CommitTx(), settings).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + auto stats = NYdb::TProtoAccessor::GetProto(*result.GetStats()); + AssertTableStats(stats, "/Root/test", {.ExpectedUpdates = 1}); + AssertTableStats(stats, "/Root/test/Id2Idx/indexImplTable", {.ExpectedUpdates = 0}); + AssertTableStats(stats, "/Root/test/ValueIdx/indexImplTable", {.ExpectedUpdates = 1}); + const TString query = "SELECT id1, id2, value FROM test VIEW ValueIdx WHERE value = \"T\"u"; + auto selectResult = db.ExecuteQuery(query, TTxControl::BeginTx().CommitTx(), settings).ExtractValueSync(); + CompareYson(R"([[[3u];[3u];["T"]]])", FormatResultSetYson(selectResult.GetResultSet(0))); + } + + // value (null) IS changed + { + auto result = db.ExecuteQuery(R"( + UPDATE test ON SELECT 4 as id1, 4 as id2, "L"u as value; + )", TTxControl::BeginTx().CommitTx(), settings).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + auto stats = NYdb::TProtoAccessor::GetProto(*result.GetStats()); + AssertTableStats(stats, "/Root/test", {.ExpectedUpdates = 1}); + AssertTableStats(stats, "/Root/test/Id2Idx/indexImplTable", {.ExpectedUpdates = 0}); + AssertTableStats(stats, "/Root/test/ValueIdx/indexImplTable", {.ExpectedUpdates = 1}); + const TString query = "SELECT id1, id2, value FROM test VIEW ValueIdx WHERE value = \"L\"u"; + auto selectResult = db.ExecuteQuery(query, TTxControl::BeginTx().CommitTx(), settings).ExtractValueSync(); + CompareYson(R"([[[4u];[4u];["L"]]])", FormatResultSetYson(selectResult.GetResultSet(0))); + } + } + Y_UNIT_TEST(ExecuteQueryPure) { auto kikimr = DefaultKikimrRunner(); auto db = kikimr.GetQueryClient();