Skip to content

Commit 5d617db

Browse files
authored
fix returning clause in upsert/insert on missing input columns (ydb-platform#16060) (ydb-platform#16633)
1 parent a56b602 commit 5d617db

File tree

2 files changed

+120
-9
lines changed

2 files changed

+120
-9
lines changed

ydb/core/kqp/opt/kqp_opt_kql.cpp

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,55 @@ using namespace NYql::NNodes;
1515

1616
namespace {
1717

18+
TVector<TString> GetMissingInputColumnsForReturning(
19+
const TKiWriteTable& write, const TCoAtomList& inputColumns)
20+
{
21+
auto returnColumns = write.ReturningColumns().Cast<TCoAtomList>();
22+
if (returnColumns.Ref().ChildrenSize() == 0) {
23+
return {};
24+
}
25+
26+
THashSet<TStringBuf> currentInput;
27+
for (const auto& name : inputColumns) {
28+
currentInput.insert(name.Value());
29+
}
30+
31+
TVector<TString> result;
32+
for(const auto& returnCol : returnColumns) {
33+
if (!currentInput.contains(returnCol.Value())) {
34+
result.push_back(TString(returnCol.Value()));
35+
}
36+
}
37+
38+
return result;
39+
}
40+
1841
// Replace absent input columns to NULL to perform REPLACE via UPSERT
19-
std::pair<TExprBase, TCoAtomList> CreateRowsToReplace(const TExprBase& input,
42+
std::pair<TExprBase, TCoAtomList> ExtendInputRowsWithAbsentNullColumns(const TKiWriteTable& write, const TExprBase& input,
2043
const TCoAtomList& inputColumns, const TKikimrTableDescription& tableDesc,
2144
TPositionHandle pos, TExprContext& ctx)
2245
{
46+
TVector<TString> maybeMissingColumnsToReplace;
47+
const auto op = GetTableOp(write);
48+
if (op == TYdbOperation::Replace) {
49+
for(const auto&[name, _]: tableDesc.Metadata->Columns) {
50+
maybeMissingColumnsToReplace.push_back(name);
51+
}
52+
}
53+
54+
if (op == TYdbOperation::InsertAbort || op == TYdbOperation::InsertRevert || op == TYdbOperation::Upsert) {
55+
maybeMissingColumnsToReplace = GetMissingInputColumnsForReturning(write, inputColumns);
56+
if (maybeMissingColumnsToReplace.size() > 0) {
57+
for(const auto& inputCol: inputColumns) {
58+
maybeMissingColumnsToReplace.push_back(TString(inputCol.Value()));
59+
}
60+
}
61+
}
62+
63+
if (maybeMissingColumnsToReplace.size() == 0) {
64+
return {input, inputColumns};
65+
}
66+
2367
THashSet<TStringBuf> inputColumnsSet;
2468
for (const auto& name : inputColumns) {
2569
inputColumnsSet.insert(name.Value());
@@ -32,7 +76,7 @@ std::pair<TExprBase, TCoAtomList> CreateRowsToReplace(const TExprBase& input,
3276
TVector<TCoAtom> writeColumns;
3377
TVector<TExprBase> writeMembers;
3478

35-
for (const auto& [name, _] : tableDesc.Metadata->Columns) {
79+
for (const auto& name : maybeMissingColumnsToReplace) {
3680
TMaybeNode<TExprBase> memberValue;
3781
if (tableDesc.GetKeyColumnIndex(name) || inputColumnsSet.contains(name)) {
3882
memberValue = Build<TCoMember>(ctx, pos)
@@ -225,11 +269,7 @@ std::pair<TExprBase, TCoAtomList> BuildWriteInput(const TKiWriteTable& write, co
225269
input = BuildKqlSequencer(input, table, inputCols, autoIncrement, pos, ctx);
226270
}
227271

228-
const bool isWriteReplace = (GetTableOp(write) == TYdbOperation::Replace);
229-
if (isWriteReplace) {
230-
// TODO: don't need it for sinks (can be disabled when secondary indexes are supported inside write actor)
231-
std::tie(input, inputCols) = CreateRowsToReplace(input, inputCols, table, write.Pos(), ctx);
232-
}
272+
std::tie(input, inputCols) = ExtendInputRowsWithAbsentNullColumns(write, input, inputCols, table, write.Pos(), ctx);
233273

234274
auto baseInput = Build<TKqpWriteConstraint>(ctx, pos)
235275
.Input(input)
@@ -239,15 +279,35 @@ std::pair<TExprBase, TCoAtomList> BuildWriteInput(const TKiWriteTable& write, co
239279
return {baseInput, inputCols};
240280
}
241281

282+
283+
TCoAtomList ExtendGenerateOnInsertColumnsList(const TKiWriteTable& write, TCoAtomList& generateColumnsIfInsert, const TCoAtomList& inputColumns, const TCoAtomList& autoincrement, TExprContext& ctx) {
284+
auto inputCols = BuildUpsertInputColumns(inputColumns, autoincrement, write.Pos(), ctx);
285+
auto maybeMissingColumnsToReplace = GetMissingInputColumnsForReturning(write, inputCols);
286+
TVector<TExprNode::TPtr> result;
287+
result.reserve(generateColumnsIfInsert.Ref().ChildrenSize() + maybeMissingColumnsToReplace.size());
288+
for(const auto& item: generateColumnsIfInsert) {
289+
result.push_back(item.Ptr());
290+
}
291+
292+
for(auto& name: maybeMissingColumnsToReplace) {
293+
auto atom = TCoAtom(ctx.NewAtom(write.Pos(), name));
294+
result.push_back(atom.Ptr());
295+
}
296+
297+
return Build<TCoAtomList>(ctx, write.Pos()).Add(result).Done();
298+
}
299+
242300
TExprBase BuildUpsertTable(const TKiWriteTable& write, const TCoAtomList& inputColumns,
243301
const TCoAtomList& autoincrement, const bool isSink,
244302
const TKikimrTableDescription& table, TExprContext& ctx)
245303
{
246304
auto generateColumnsIfInsertNode = GetSetting(write.Settings().Ref(), "generate_columns_if_insert");
247305
YQL_ENSURE(generateColumnsIfInsertNode);
248306
TCoAtomList generateColumnsIfInsert = TCoNameValueTuple(generateColumnsIfInsertNode).Value().Cast<TCoAtomList>();
249-
250307
auto settings = FilterSettings(write.Settings().Ref(), {"AllowInconsistentWrites"}, ctx);
308+
309+
generateColumnsIfInsert = ExtendGenerateOnInsertColumnsList(write, generateColumnsIfInsert, inputColumns, autoincrement, ctx);
310+
251311
settings = AddSetting(*settings, write.Pos(), "Mode", Build<TCoAtom>(ctx, write.Pos()).Value("upsert").Done().Ptr(), ctx);
252312
const auto [input, columns] = BuildWriteInput(write, table, inputColumns, autoincrement, isSink, write.Pos(), ctx);
253313
if (generateColumnsIfInsert.Ref().ChildrenSize() > 0) {
@@ -283,6 +343,8 @@ TExprBase BuildUpsertTableWithIndex(const TKiWriteTable& write, const TCoAtomLis
283343
YQL_ENSURE(generateColumnsIfInsertNode);
284344
TCoAtomList generateColumnsIfInsert = TCoNameValueTuple(generateColumnsIfInsertNode).Value().Cast<TCoAtomList>();
285345

346+
generateColumnsIfInsert = ExtendGenerateOnInsertColumnsList(write, generateColumnsIfInsert, inputColumns, autoincrement, ctx);
347+
286348
auto effect = Build<TKqlUpsertRowsIndex>(ctx, write.Pos())
287349
.Table(BuildTableMeta(table, write.Pos(), ctx))
288350
.Input(input.Ptr())

ydb/core/kqp/ut/opt/kqp_returning_ut.cpp

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ Y_UNIT_TEST(ReplaceSerial) {
174174

175175
Y_UNIT_TEST(ReturningSerial) {
176176
NKikimrConfig::TAppConfig appConfig;
177-
auto serverSettings = TKikimrSettings().SetAppConfig(appConfig);
177+
auto serverSettings = TKikimrSettings().SetAppConfig(appConfig).SetWithSampleTables(false);
178178
TKikimrRunner kikimr(serverSettings);
179179

180180
auto client = kikimr.GetTableClient();
@@ -198,6 +198,55 @@ Y_UNIT_TEST(ReturningSerial) {
198198
auto resultCreate = session.ExecuteSchemeQuery(queryCreate).GetValueSync();
199199
UNIT_ASSERT_C(resultCreate.IsSuccess(), resultCreate.GetIssues().ToString());
200200

201+
{
202+
const auto query = Q_(R"(
203+
--!syntax_v1
204+
INSERT INTO ReturningTable (key) VALUES(20000) RETURNING *
205+
)");
206+
207+
auto result = session.ExecuteDataQuery(query, TTxControl::BeginTx().CommitTx()).GetValueSync();
208+
UNIT_ASSERT(result.IsSuccess());
209+
210+
CompareYson(R"([[20000;#]])", FormatResultSetYson(result.GetResultSet(0)));
211+
}
212+
213+
214+
{
215+
const auto query = Q_(R"(
216+
--!syntax_v1
217+
UPSERT INTO ReturningTable (key) VALUES(20000) RETURNING *
218+
)");
219+
220+
auto result = session.ExecuteDataQuery(query, TTxControl::BeginTx().CommitTx()).GetValueSync();
221+
UNIT_ASSERT(result.IsSuccess());
222+
223+
CompareYson(R"([[20000;#]])", FormatResultSetYson(result.GetResultSet(0)));
224+
}
225+
226+
{
227+
const auto query = Q_(R"(
228+
--!syntax_v1
229+
UPSERT INTO ReturningTable (key, value) VALUES(20000, 100) RETURNING *
230+
)");
231+
232+
auto result = session.ExecuteDataQuery(query, TTxControl::BeginTx().CommitTx()).GetValueSync();
233+
UNIT_ASSERT(result.IsSuccess());
234+
235+
CompareYson(R"([[20000;[100]]])", FormatResultSetYson(result.GetResultSet(0)));
236+
}
237+
238+
{
239+
const auto query = Q_(R"(
240+
--!syntax_v1
241+
UPSERT INTO ReturningTable (key) VALUES(20000) RETURNING *
242+
)");
243+
244+
auto result = session.ExecuteDataQuery(query, TTxControl::BeginTx().CommitTx()).GetValueSync();
245+
UNIT_ASSERT(result.IsSuccess());
246+
247+
CompareYson(R"([[20000;[100]]])", FormatResultSetYson(result.GetResultSet(0)));
248+
}
249+
201250
{
202251
const auto query = Q_(R"(
203252
--!syntax_v1

0 commit comments

Comments
 (0)