diff --git a/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_returning.cpp b/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_returning.cpp index c2042869d1fe..ef683a247558 100644 --- a/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_returning.cpp +++ b/ydb/core/kqp/opt/physical/effects/kqp_opt_phy_returning.cpp @@ -151,6 +151,22 @@ TExprBase KqpBuildReturning(TExprBase node, TExprContext& ctx, const TKqpOptimiz .Index().Build("0") .Build() .Done(); + } else if (NDq::IsDqPureExpr(input.Cast())) { + input = Build(ctx, pos) + .Output() + .Stage() + .Inputs().Build() + .Program() + .Args({}) + .Body() + .Input(input.Cast()) + .Build() + .Build() + .Settings().Build() + .Build() + .Index().Build("0") + .Build() + .Done(); } auto inputExpr = Build(ctx, pos) @@ -173,6 +189,10 @@ TExprBase KqpBuildReturning(TExprBase node, TExprContext& ctx, const TKqpOptimiz return buildReturningRows(del.Input().Cast(), MakeColumnsList(tableDesc.Metadata->KeyColumnNames, ctx, node.Pos()), returning.Columns()); } } + + if (item.Maybe()) { + return node; + } } } @@ -183,6 +203,10 @@ TExprBase KqpBuildReturning(TExprBase node, TExprContext& ctx, const TKqpOptimiz return buildReturningRows(del.Input().Cast(), MakeColumnsList(tableDesc.Metadata->KeyColumnNames, ctx, node.Pos()), returning.Columns()); } + if (returning.Update().Maybe()) { + return node; + } + TExprNode::TPtr result = returning.Update().Ptr(); auto status = TryConvertTo(result, *result->GetTypeAnn(), *returning.Raw()->GetTypeAnn(), ctx); YQL_ENSURE(status.Level != IGraphTransformer::TStatus::Error, "wrong returning expr type"); 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 f0d25a04a2fa..df3bc5003d00 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 @@ -391,21 +391,13 @@ RewriteInputForConstraint(const TExprBase& inputRows, const THashSet const THashSet indexKeyColumns = CreateKeyColumnSetToRead(indexes); const THashSet indexDataColumns = CreateDataColumnSetToRead(indexes); - for (const auto& x : indexKeyColumns) { - columns.push_back(Build(ctx, pos).Value(x).Done()); - } + THashSet columnsToReadInPrecomputeLookupDict; + columnsToReadInPrecomputeLookupDict.insert(indexKeyColumns.begin(), indexKeyColumns.end()); + columnsToReadInPrecomputeLookupDict.insert(indexDataColumns.begin(), indexDataColumns.end()); + columnsToReadInPrecomputeLookupDict.insert(mainPk.begin(), mainPk.end()); + columnsToReadInPrecomputeLookupDict.insert(checkDefaults.begin(), checkDefaults.end()); - for (const auto& x : indexDataColumns) { - // Handle the case of multiple indexes - // one of them has 'foo' as data column but for another one foo is just indexed column - if (indexKeyColumns.contains(x)) - continue; - columns.push_back(Build(ctx, pos).Value(x).Done()); - } - - for (const auto& x : mainPk) { - if (indexKeyColumns.contains(x)) - continue; + for (const auto& x : columnsToReadInPrecomputeLookupDict) { columns.push_back(Build(ctx, pos).Value(x).Done()); } diff --git a/ydb/core/kqp/ut/opt/kqp_returning_ut.cpp b/ydb/core/kqp/ut/opt/kqp_returning_ut.cpp index 3b7096d456cb..b8d19298b4c1 100644 --- a/ydb/core/kqp/ut/opt/kqp_returning_ut.cpp +++ b/ydb/core/kqp/ut/opt/kqp_returning_ut.cpp @@ -314,6 +314,206 @@ Y_UNIT_TEST(ReturningSerial) { } } +TString ExecuteReturningQuery(TKikimrRunner& kikimr, bool queryService, TString query) { + if (queryService) { + auto qdb = kikimr.GetQueryClient(); + auto qSession = qdb.GetSession().GetValueSync().GetSession(); + auto result = qSession.ExecuteQuery( + query, NYdb::NQuery::TTxControl::BeginTx().CommitTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + return FormatResultSetYson(result.GetResultSet(0)); + } + + auto db = kikimr.GetTableClient(); + auto session = db.CreateSession().GetValueSync().GetSession(); + auto result = session.ExecuteDataQuery(query, TTxControl::BeginTx().CommitTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + return FormatResultSetYson(result.GetResultSet(0)); +} + +Y_UNIT_TEST_TWIN(ReturningWorks, QueryService) { + auto kikimr = DefaultKikimrRunner(); + auto db = kikimr.GetTableClient(); + auto session = db.CreateSession().GetValueSync().GetSession(); + CreateSampleTablesWithIndex(session, true); + CompareYson( + R"([[[101];[101];["Payload1"]];])", + ExecuteReturningQuery(kikimr, QueryService, R"( + UPSERT INTO `/Root/SecondaryKeys` (Key, Fk, Value) VALUES (101, 101, "Payload1") RETURNING *; + )") + ); + CompareYson( + R"( + [[#;#;["Payload8"]]; + [[1];[1];["Payload1"]]; + [[2];[2];["Payload2"]]; + [[5];[5];["Payload5"]]; + [#;[7];["Payload7"]]; + [[101];[101];["Payload1"]] + ])", + ExecuteReturningQuery(kikimr, QueryService, "SELECT * FROM `/Root/SecondaryKeys` ORDER BY Key, Fk;") + ); +} + +Y_UNIT_TEST_TWIN(ReturningWorksIndexedUpsert, QueryService) { + auto kikimr = DefaultKikimrRunner(); + auto db = kikimr.GetTableClient(); + auto session = db.CreateSession().GetValueSync().GetSession(); + CreateSampleTablesWithIndex(session, true); + CompareYson(R"([ + [[110];[110];["Payload5"]]; + ])", ExecuteReturningQuery(kikimr, QueryService, R"( + $v1 = (SELECT Key + 100 as Key, Fk + 100 as Fk, Value FROM `/Root/SecondaryKeys` WHERE Key IS NOT NULL AND Fk IS NOT NULL); + $v2 = (SELECT Key + 105 as Key, Fk + 105 as Fk, Value FROM `/Root/SecondaryKeys` WHERE Key IS NOT NULL AND Fk IS NOT NULL); + UPSERT INTO `/Root/SecondaryKeys` + SELECT * FROM (SELECT * FROM $v1 UNION ALL SELECT * FROM $v2) WHERE Key > 107 RETURNING *; + )")); + CompareYson( + R"( + [[#;#;["Payload8"]]; + [[1];[1];["Payload1"]]; + [[2];[2];["Payload2"]]; + [[5];[5];["Payload5"]]; + [#;[7];["Payload7"]]; + [[110];[110];["Payload5"]] + ])", + ExecuteReturningQuery(kikimr, QueryService, "SELECT * FROM `/Root/SecondaryKeys` ORDER BY Key, Fk;") + ); +} + +Y_UNIT_TEST_TWIN(ReturningWorksIndexedDelete, QueryService) { + auto kikimr = DefaultKikimrRunner(); + auto db = kikimr.GetTableClient(); + auto session = db.CreateSession().GetValueSync().GetSession(); + CreateSampleTablesWithIndex(session, true); + CompareYson(R"([ + [[5];[5];["Payload5"]]; + ])", ExecuteReturningQuery(kikimr, QueryService, R"( + $v1 = (SELECT Key, Fk, Value FROM `/Root/SecondaryKeys` WHERE Key IS NOT NULL AND Fk IS NOT NULL AND Key >= 1); + $v2 = (SELECT Key, Fk, Value FROM `/Root/SecondaryKeys` WHERE Key IS NOT NULL AND Fk IS NOT NULL AND Key <= 5); + DELETE FROM `/Root/SecondaryKeys` ON + SELECT * FROM (SELECT * FROM $v1 UNION ALL SELECT * FROM $v2) WHERE Key >= 5 RETURNING *; + )")); + CompareYson( + R"( + [[#;#;["Payload8"]]; + [[1];[1];["Payload1"]]; + [[2];[2];["Payload2"]]; + [#;[7];["Payload7"]]; + ])", + ExecuteReturningQuery(kikimr, QueryService, "SELECT * FROM `/Root/SecondaryKeys` ORDER BY Key, Fk;") + ); +} + +Y_UNIT_TEST_TWIN(ReturningWorksIndexedDeleteV2, QueryService) { + auto kikimr = DefaultKikimrRunner(); + auto db = kikimr.GetTableClient(); + auto session = db.CreateSession().GetValueSync().GetSession(); + CreateSampleTablesWithIndex(session, true); + CompareYson(R"([ + [[1];[1];["Payload1"]]; + ])", ExecuteReturningQuery(kikimr, QueryService, R"( + DELETE FROM `/Root/SecondaryKeys` WHERE Key = 1 RETURNING *; + )")); + CompareYson( + R"( + [[#;#;["Payload8"]]; + [[2];[2];["Payload2"]]; + [[5];[5];["Payload5"]]; + [#;[7];["Payload7"]]; + ])", + ExecuteReturningQuery(kikimr, QueryService, "SELECT * FROM `/Root/SecondaryKeys` ORDER BY Key, Fk;") + ); +} + + +Y_UNIT_TEST_TWIN(ReturningWorksIndexedInsert, QueryService) { + auto kikimr = DefaultKikimrRunner(); + auto db = kikimr.GetTableClient(); + auto session = db.CreateSession().GetValueSync().GetSession(); + CreateSampleTablesWithIndex(session, true); + + CompareYson(R"([ + [[101];[101];["Payload1"]]; + ])", ExecuteReturningQuery(kikimr, QueryService, R"( + $v1 = (SELECT Key + 100 as Key, Fk + 100 as Fk, Value FROM `/Root/SecondaryKeys` WHERE Key IS NOT NULL AND Fk IS NOT NULL); + $v2 = (SELECT Key + 205 as Key, Fk + 205 as Fk, Value FROM `/Root/SecondaryKeys` WHERE Key IS NOT NULL AND Fk IS NOT NULL); + INSERT INTO `/Root/SecondaryKeys` + SELECT * FROM (SELECT * FROM $v1 UNION ALL SELECT * FROM $v2 ) WHERE Key < 102 RETURNING *; + )")); + + CompareYson( + R"( + [[#;#;["Payload8"]]; + [[1];[1];["Payload1"]]; + [[2];[2];["Payload2"]]; + [[5];[5];["Payload5"]]; + [#;[7];["Payload7"]]; + [[101];[101];["Payload1"]] + ])", + ExecuteReturningQuery(kikimr, QueryService, "SELECT * FROM `/Root/SecondaryKeys` ORDER BY Key, Fk;") + ); +} + +Y_UNIT_TEST_TWIN(ReturningWorksIndexedReplace, QueryService) { + auto kikimr = DefaultKikimrRunner(); + auto db = kikimr.GetTableClient(); + auto session = db.CreateSession().GetValueSync().GetSession(); + CreateSampleTablesWithIndex(session, true); + + CompareYson(R"([ + [[101];[101];["Payload1"]]; + ])", ExecuteReturningQuery(kikimr, QueryService, R"( + $v1 = (SELECT Key + 100 as Key, Fk + 100 as Fk, Value FROM `/Root/SecondaryKeys` WHERE Key IS NOT NULL AND Fk IS NOT NULL); + $v2 = (SELECT Key + 205 as Key, Fk + 205 as Fk, Value FROM `/Root/SecondaryKeys` WHERE Key IS NOT NULL AND Fk IS NOT NULL); + REPLACE INTO `/Root/SecondaryKeys` + SELECT * FROM (SELECT * FROM $v1 UNION ALL SELECT * FROM $v2 ) WHERE Key < 102 RETURNING *; + )")); + + CompareYson( + R"( + [[#;#;["Payload8"]]; + [[1];[1];["Payload1"]]; + [[2];[2];["Payload2"]]; + [[5];[5];["Payload5"]]; + [#;[7];["Payload7"]]; + [[101];[101];["Payload1"]] + ])", + ExecuteReturningQuery(kikimr, QueryService, "SELECT * FROM `/Root/SecondaryKeys` ORDER BY Key, Fk;") + ); +} + +Y_UNIT_TEST_TWIN(ReturningWorksIndexedOperationsWithDefault, QueryService) { + auto kikimr = DefaultKikimrRunner(); + auto db = kikimr.GetTableClient(); + auto session = db.CreateSession().GetValueSync().GetSession(); + { + auto res = session.ExecuteSchemeQuery(R"( + --!syntax_v1 + CREATE TABLE `/Root/SecondaryKeys` ( + Key Serial, + Fk Int32, + Value String, + PRIMARY KEY (Key), + INDEX Index GLOBAL ON (Fk) + ); + )").GetValueSync(); + } + + CompareYson(R"([ + [1;[1];["Payload"]]; + ])", ExecuteReturningQuery(kikimr, QueryService, R"( + REPLACE INTO `/Root/SecondaryKeys` (Fk, Value) VALUES (1, "Payload") RETURNING Key, Fk, Value; + )")); + + CompareYson( + R"([ + [1;[1];["Payload"]]; + ])", + ExecuteReturningQuery(kikimr, QueryService, "SELECT Key, Fk, Value FROM `/Root/SecondaryKeys` ORDER BY Key, Fk;") + ); +} + Y_UNIT_TEST(ReturningColumnsOrder) { auto kikimr = DefaultKikimrRunner(); diff --git a/ydb/core/kqp/ut/scheme/kqp_constraints_ut.cpp b/ydb/core/kqp/ut/scheme/kqp_constraints_ut.cpp index 86f42ef03889..fce490e1891c 100644 --- a/ydb/core/kqp/ut/scheme/kqp_constraints_ut.cpp +++ b/ydb/core/kqp/ut/scheme/kqp_constraints_ut.cpp @@ -1290,6 +1290,76 @@ Y_UNIT_TEST_SUITE(KqpConstraints) { } + Y_UNIT_TEST(DefaultAndIndexesTestDefaultColumnNotIncludedInIndex) { + NKikimrConfig::TAppConfig appConfig; + TKikimrRunner kikimr(TKikimrSettings().SetPQConfig(DefaultPQConfig()).SetAppConfig(appConfig)); + + auto db = kikimr.GetTableClient(); + auto session = db.CreateSession().GetValueSync().GetSession(); + + { + auto query = R"( + --!syntax_v1 + CREATE TABLE test ( + A Int64 NOT NULL, + B Int64, + Created Int32 DEFAULT 1, + Deleted Int32 DEFAULT 0, + PRIMARY KEY (A ), + INDEX testIndex GLOBAL ON (B, A) + ) + )"; + + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, + result.GetIssues().ToString()); + } + + auto fQuery = [&](TString query) -> TString { + NYdb::NTable::TExecDataQuerySettings execSettings; + execSettings.KeepInQueryCache(true); + execSettings.CollectQueryStats(ECollectQueryStatsMode::Basic); + + auto result = + session + .ExecuteDataQuery(query, TTxControl::BeginTx().CommitTx(), + execSettings) + .ExtractValueSync(); + + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, + result.GetIssues().ToString()); + if (result.GetResultSets().size() > 0) + return NYdb::FormatResultSetYson(result.GetResultSet(0)); + return ""; + }; + + fQuery(R"( + upsert into test (A, B, Created, Deleted) values (5, 15, 1, 0) + )"); + + fQuery(R"( + $to_upsert = ( + select A from + `test` + where A = 5 + ); + + upsert into `test` (A, Deleted) + select A, 10 as Deleted from $to_upsert; + )"); + + CompareYson( + R"( + [ + [5;[15];[1];[10]] + ] + )", + fQuery(R"( + SELECT A, B, Created, Deleted FROM `test` ORDER BY A; + )") + ); + } + Y_UNIT_TEST(Utf8AndDefault) { NKikimrConfig::TAppConfig appConfig;