Skip to content

Commit ea7e9b6

Browse files
authored
Views: if exists / if not exists for DDL (#10831) (#11093)
1 parent e936c2b commit ea7e9b6

File tree

6 files changed

+225
-33
lines changed

6 files changed

+225
-33
lines changed

ydb/core/kqp/gateway/behaviour/view/manager.cpp

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ void FillCreateViewProposal(NKikimrSchemeOp::TModifyScheme& modifyScheme,
6060
const auto pathPair = SplitPathByDb(settings.GetObjectId(), context.GetDatabase());
6161
modifyScheme.SetWorkingDir(pathPair.first);
6262
modifyScheme.SetOperationType(NKikimrSchemeOp::ESchemeOpCreateView);
63+
modifyScheme.SetFailedOnAlreadyExists(!settings.GetExistingOk());
6364

6465
auto& viewDesc = *modifyScheme.MutableCreateView();
6566
viewDesc.SetName(pathPair.second);
@@ -77,16 +78,20 @@ void FillDropViewProposal(NKikimrSchemeOp::TModifyScheme& modifyScheme,
7778
const auto pathPair = SplitPathByObjectId(settings.GetObjectId());
7879
modifyScheme.SetWorkingDir(pathPair.first);
7980
modifyScheme.SetOperationType(NKikimrSchemeOp::ESchemeOpDropView);
81+
modifyScheme.SetSuccessOnNotExist(settings.GetMissingOk());
8082

8183
auto& drop = *modifyScheme.MutableDrop();
8284
drop.SetName(pathPair.second);
8385
}
8486

8587
NThreading::TFuture<TYqlConclusionStatus> SendSchemeRequest(TEvTxUserProxy::TEvProposeTransaction* request,
8688
TActorSystem* actorSystem,
87-
bool failOnAlreadyExists) {
89+
bool failedOnAlreadyExists,
90+
bool successOnNotExist) {
8891
const auto promiseScheme = NThreading::NewPromise<NKqp::TSchemeOpRequestHandler::TResult>();
89-
IActor* const requestHandler = new TSchemeOpRequestHandler(request, promiseScheme, failOnAlreadyExists);
92+
IActor* const requestHandler = new TSchemeOpRequestHandler(
93+
request, promiseScheme, failedOnAlreadyExists, successOnNotExist
94+
);
9095
actorSystem->Register(requestHandler);
9196
return promiseScheme.GetFuture().Apply([](const NThreading::TFuture<NKqp::TSchemeOpRequestHandler::TResult>& opResult) {
9297
if (opResult.HasValue()) {
@@ -109,7 +114,12 @@ NThreading::TFuture<TYqlConclusionStatus> CreateView(const NYql::TCreateObjectSe
109114
auto& schemeTx = *proposal->Record.MutableTransaction()->MutableModifyScheme();
110115
FillCreateViewProposal(schemeTx, settings, context.GetExternalData());
111116

112-
return SendSchemeRequest(proposal.Release(), context.GetExternalData().GetActorSystem(), true);
117+
return SendSchemeRequest(
118+
proposal.Release(),
119+
context.GetExternalData().GetActorSystem(),
120+
schemeTx.GetFailedOnAlreadyExists(),
121+
schemeTx.GetSuccessOnNotExist()
122+
);
113123
}
114124

115125
NThreading::TFuture<TYqlConclusionStatus> DropView(const NYql::TDropObjectSettings& settings,
@@ -122,7 +132,12 @@ NThreading::TFuture<TYqlConclusionStatus> DropView(const NYql::TDropObjectSettin
122132
auto& schemeTx = *proposal->Record.MutableTransaction()->MutableModifyScheme();
123133
FillDropViewProposal(schemeTx, settings);
124134

125-
return SendSchemeRequest(proposal.Release(), context.GetExternalData().GetActorSystem(), false);
135+
return SendSchemeRequest(
136+
proposal.Release(),
137+
context.GetExternalData().GetActorSystem(),
138+
schemeTx.GetFailedOnAlreadyExists(),
139+
schemeTx.GetSuccessOnNotExist()
140+
);
126141
}
127142

128143
void PrepareCreateView(NKqpProto::TKqpSchemeOperation& schemeOperation,
@@ -214,10 +229,10 @@ NThreading::TFuture<TYqlConclusionStatus> TViewManager::ExecutePrepared(const NK
214229
switch (schemeOperation.GetOperationCase()) {
215230
case NKqpProto::TKqpSchemeOperation::kCreateView:
216231
schemeTx.CopyFrom(schemeOperation.GetCreateView());
217-
return SendSchemeRequest(proposal.Release(), context.GetActorSystem(), true);
232+
break;
218233
case NKqpProto::TKqpSchemeOperation::kDropView:
219234
schemeTx.CopyFrom(schemeOperation.GetDropView());
220-
return SendSchemeRequest(proposal.Release(), context.GetActorSystem(), false);
235+
break;
221236
default:
222237
return NThreading::MakeFuture(TYqlConclusionStatus::Fail(
223238
TStringBuilder()
@@ -226,6 +241,12 @@ NThreading::TFuture<TYqlConclusionStatus> TViewManager::ExecutePrepared(const NK
226241
)
227242
);
228243
}
244+
return SendSchemeRequest(
245+
proposal.Release(),
246+
context.GetActorSystem(),
247+
schemeTx.GetFailedOnAlreadyExists(),
248+
schemeTx.GetSuccessOnNotExist()
249+
);
229250
}
230251

231252
}

ydb/core/kqp/ut/view/view_ut.cpp

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,10 +344,60 @@ Y_UNIT_TEST_SUITE(TCreateAndDropViewTest) {
344344
{
345345
const auto creationResult = session.ExecuteSchemeQuery(creationQuery).GetValueSync();
346346
UNIT_ASSERT(!creationResult.IsSuccess());
347-
UNIT_ASSERT(creationResult.GetIssues().ToString().Contains("error: path exist, request accepts it"));
347+
UNIT_ASSERT_STRING_CONTAINS(creationResult.GetIssues().ToString(), "error: path exist, request accepts it");
348348
}
349349
}
350350

351+
Y_UNIT_TEST(CreateViewOccupiedName) {
352+
TKikimrRunner kikimr(TKikimrSettings().SetWithSampleTables(false));
353+
EnableViewsFeatureFlag(kikimr);
354+
auto session = kikimr.GetQueryClient().GetSession().ExtractValueSync().GetSession();
355+
356+
constexpr const char* path = "table";
357+
358+
const TString createTable = std::format(R"(
359+
CREATE TABLE {} (key Int32, value Utf8, PRIMARY KEY (key));
360+
)", path
361+
);
362+
ExecuteQuery(session, createTable);
363+
364+
auto checkError = [&session](const TString& query, const TString& expectedError) {
365+
const auto result = session.ExecuteQuery(query, NQuery::TTxControl::NoTx()).ExtractValueSync();
366+
UNIT_ASSERT(!result.IsSuccess());
367+
UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), expectedError);
368+
};
369+
370+
const TString queryTemplate = std::format(R"(
371+
CREATE VIEW {{}}{} WITH (security_invoker = true) AS SELECT 1;
372+
)", path
373+
);
374+
const TString expectedError = std::format("path: '/Root/{}', error: unexpected path type", path);
375+
376+
for (std::string existenceCheck : {"", "IF NOT EXISTS "}) {
377+
const TString createView = std::vformat(queryTemplate, std::make_format_args(existenceCheck));
378+
checkError(createView, expectedError);
379+
}
380+
}
381+
382+
Y_UNIT_TEST(CreateViewIfNotExists) {
383+
TKikimrRunner kikimr(TKikimrSettings().SetWithSampleTables(false));
384+
EnableViewsFeatureFlag(kikimr);
385+
auto session = kikimr.GetQueryClient().GetSession().ExtractValueSync().GetSession();
386+
387+
constexpr const char* path = "/Root/TheView";
388+
constexpr const char* queryInView = "SELECT 1";
389+
390+
const TString creationQuery = std::format(R"(
391+
CREATE VIEW IF NOT EXISTS `{}` WITH (security_invoker = true) AS {};
392+
)",
393+
path,
394+
queryInView
395+
);
396+
ExecuteQuery(session, creationQuery);
397+
// an attempt to create a duplicate does not produce an error
398+
ExecuteQuery(session, creationQuery);
399+
}
400+
351401
Y_UNIT_TEST(DropView) {
352402
TKikimrRunner kikimr(TKikimrSettings().SetWithSampleTables(false));
353403
EnableViewsFeatureFlag(kikimr);
@@ -399,6 +449,42 @@ Y_UNIT_TEST_SUITE(TCreateAndDropViewTest) {
399449
UNIT_ASSERT_STRING_CONTAINS(dropResult.GetIssues().ToString(), "Error: Views are disabled");
400450
}
401451

452+
Y_UNIT_TEST(DropNonexistingView) {
453+
TKikimrRunner kikimr(TKikimrSettings().SetWithSampleTables(false));
454+
EnableViewsFeatureFlag(kikimr);
455+
auto session = kikimr.GetQueryClient().GetSession().ExtractValueSync().GetSession();
456+
457+
const auto dropResult = session.ExecuteQuery(
458+
"DROP VIEW NonexistingView;", NQuery::TTxControl::NoTx()
459+
).ExtractValueSync();
460+
461+
UNIT_ASSERT(!dropResult.IsSuccess());
462+
UNIT_ASSERT_STRING_CONTAINS(dropResult.GetIssues().ToString(), "Error: Path does not exist");
463+
}
464+
465+
Y_UNIT_TEST(CallDropViewOnTable) {
466+
TKikimrRunner kikimr(TKikimrSettings().SetWithSampleTables(false));
467+
EnableViewsFeatureFlag(kikimr);
468+
auto session = kikimr.GetQueryClient().GetSession().ExtractValueSync().GetSession();
469+
470+
constexpr const char* path = "table";
471+
472+
const TString createTable = std::format(R"(
473+
CREATE TABLE {} (key Int32, value Utf8, PRIMARY KEY (key));
474+
)", path
475+
);
476+
ExecuteQuery(session, createTable);
477+
478+
auto checkError = [&session](const TString& query, const TString& expectedError) {
479+
const auto result = session.ExecuteQuery(query, NQuery::TTxControl::NoTx()).ExtractValueSync();
480+
UNIT_ASSERT(!result.IsSuccess());
481+
UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), expectedError);
482+
};
483+
const TString expectedError = std::format("path: '/Root/{}', error: path is not a view", path);
484+
checkError(std::format("DROP VIEW {};", path), expectedError);
485+
checkError(std::format("DROP VIEW IF EXISTS {};", path), expectedError);
486+
}
487+
402488
Y_UNIT_TEST(DropSameViewTwice) {
403489
TKikimrRunner kikimr(TKikimrSettings().SetWithSampleTables(false));
404490
EnableViewsFeatureFlag(kikimr);
@@ -424,10 +510,36 @@ Y_UNIT_TEST_SUITE(TCreateAndDropViewTest) {
424510
{
425511
const auto dropResult = session.ExecuteSchemeQuery(dropQuery).GetValueSync();
426512
UNIT_ASSERT(!dropResult.IsSuccess());
427-
UNIT_ASSERT(dropResult.GetIssues().ToString().Contains("Error: Path does not exist"));
513+
UNIT_ASSERT_STRING_CONTAINS(dropResult.GetIssues().ToString(), "Error: Path does not exist");
428514
}
429515
}
430516

517+
Y_UNIT_TEST(DropViewIfExists) {
518+
TKikimrRunner kikimr(TKikimrSettings().SetWithSampleTables(false));
519+
EnableViewsFeatureFlag(kikimr);
520+
auto session = kikimr.GetQueryClient().GetSession().ExtractValueSync().GetSession();
521+
522+
constexpr const char* path = "/Root/TheView";
523+
constexpr const char* queryInView = "SELECT 1";
524+
525+
const TString creationQuery = std::format(R"(
526+
CREATE VIEW `{}` WITH (security_invoker = true) AS {};
527+
)",
528+
path,
529+
queryInView
530+
);
531+
ExecuteQuery(session, creationQuery);
532+
533+
const TString dropQuery = std::format(R"(
534+
DROP VIEW IF EXISTS `{}`;
535+
)",
536+
path
537+
);
538+
ExecuteQuery(session, dropQuery);
539+
// an attempt to drop an already deleted view does not produce an error
540+
ExecuteQuery(session, dropQuery);
541+
}
542+
431543
Y_UNIT_TEST(DropViewInFolder) {
432544
TKikimrRunner kikimr(TKikimrSettings().SetWithSampleTables(false));
433545
EnableViewsFeatureFlag(kikimr);

ydb/library/yql/sql/v1/SQLv1.g.in

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -596,12 +596,12 @@ alter_external_data_source_action:
596596

597597
drop_external_data_source_stmt: DROP EXTERNAL DATA SOURCE (IF EXISTS)? object_ref;
598598

599-
create_view_stmt: CREATE VIEW object_ref
599+
create_view_stmt: CREATE VIEW (IF NOT EXISTS)? object_ref
600600
create_object_features?
601601
AS select_stmt
602602
;
603603

604-
drop_view_stmt: DROP VIEW object_ref;
604+
drop_view_stmt: DROP VIEW (IF EXISTS)? object_ref;
605605

606606
upsert_object_stmt: UPSERT OBJECT object_ref
607607
LPAREN TYPE object_type_ref RPAREN

ydb/library/yql/sql/v1/format/sql_format_ut.cpp

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1579,19 +1579,30 @@ FROM Input MATCH_RECOGNIZE (PATTERN (A) DEFINE A AS A);
15791579
}
15801580

15811581
Y_UNIT_TEST(CreateView) {
1582-
TCases cases = {
1583-
{"creAte vIEw TheView wiTh (security_invoker = trUE) As SELect 1",
1584-
"CREATE VIEW TheView WITH (security_invoker = TRUE) AS\nSELECT\n\t1;\n"},
1582+
TCases cases = {{
1583+
"creAte vIEw TheView As SELect 1",
1584+
"CREATE VIEW TheView AS\nSELECT\n\t1;\n"
1585+
}, {
1586+
"creAte vIEw If Not ExIsTs TheView As SELect 1",
1587+
"CREATE VIEW IF NOT EXISTS TheView AS\nSELECT\n\t1;\n"
1588+
}, {
1589+
"creAte vIEw TheView wiTh (option = tRuE) As SELect 1",
1590+
"CREATE VIEW TheView WITH (option = TRUE) AS\nSELECT\n\t1;\n"
1591+
}
15851592
};
15861593

15871594
TSetup setup;
15881595
setup.Run(cases);
15891596
}
15901597

15911598
Y_UNIT_TEST(DropView) {
1592-
TCases cases = {
1593-
{"dRop viEW theVIEW",
1594-
"DROP VIEW theVIEW;\n"},
1599+
TCases cases = {{
1600+
"dRop viEW theVIEW",
1601+
"DROP VIEW theVIEW;\n"
1602+
}, {
1603+
"dRop viEW iF EXistS theVIEW",
1604+
"DROP VIEW IF EXISTS theVIEW;\n"
1605+
}
15951606
};
15961607

15971608
TSetup setup;

ydb/library/yql/sql/v1/sql_query.cpp

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1220,60 +1220,64 @@ bool TSqlQuery::Statement(TVector<TNodePtr>& blocks, const TRule_sql_stmt_core&
12201220
break;
12211221
}
12221222
case TRule_sql_stmt_core::kAltSqlStmtCore42: {
1223-
// create_view_stmt: CREATE VIEW name WITH (k = v, ...) AS select_stmt;
1223+
// create_view_stmt: CREATE VIEW (IF NOT EXISTS)? name (WITH (k = v, ...))? AS select_stmt;
12241224
auto& node = core.GetAlt_sql_stmt_core42().GetRule_create_view_stmt1();
12251225
TObjectOperatorContext context(Ctx.Scoped);
1226-
if (node.GetRule_object_ref3().HasBlock1()) {
1227-
if (!ClusterExpr(node.GetRule_object_ref3().GetBlock1().GetRule_cluster_expr1(),
1226+
if (node.GetRule_object_ref4().HasBlock1()) {
1227+
if (!ClusterExpr(node.GetRule_object_ref4().GetBlock1().GetRule_cluster_expr1(),
12281228
false,
12291229
context.ServiceId,
12301230
context.Cluster)) {
12311231
return false;
12321232
}
12331233
}
12341234

1235+
const bool existingOk = node.HasBlock3();
1236+
12351237
std::map<TString, TDeferredAtom> features;
1236-
if (node.HasBlock4()) {
1237-
if (!ParseObjectFeatures(features, node.GetBlock4().GetRule_create_object_features1().GetRule_object_features2())) {
1238+
if (node.HasBlock5()) {
1239+
if (!ParseObjectFeatures(features, node.GetBlock5().GetRule_create_object_features1().GetRule_object_features2())) {
12381240
return false;
12391241
}
12401242
}
1241-
if (!ParseViewQuery(features, node.GetRule_select_stmt6())) {
1243+
if (!ParseViewQuery(features, node.GetRule_select_stmt7())) {
12421244
return false;
12431245
}
12441246

1245-
const TString objectId = Id(node.GetRule_object_ref3().GetRule_id_or_at2(), *this).second;
1247+
const TString objectId = Id(node.GetRule_object_ref4().GetRule_id_or_at2(), *this).second;
12461248
constexpr const char* TypeId = "VIEW";
12471249
AddStatementToBlocks(blocks,
12481250
BuildCreateObjectOperation(Ctx.Pos(),
12491251
BuildTablePath(Ctx.GetPrefixPath(context.ServiceId, context.Cluster), objectId),
12501252
TypeId,
1251-
false,
1253+
existingOk,
12521254
false,
12531255
std::move(features),
12541256
context));
12551257
break;
12561258
}
12571259
case TRule_sql_stmt_core::kAltSqlStmtCore43: {
1258-
// drop_view_stmt: DROP VIEW name;
1260+
// drop_view_stmt: DROP VIEW (IF EXISTS)? name;
12591261
auto& node = core.GetAlt_sql_stmt_core43().GetRule_drop_view_stmt1();
12601262
TObjectOperatorContext context(Ctx.Scoped);
1261-
if (node.GetRule_object_ref3().HasBlock1()) {
1262-
if (!ClusterExpr(node.GetRule_object_ref3().GetBlock1().GetRule_cluster_expr1(),
1263+
if (node.GetRule_object_ref4().HasBlock1()) {
1264+
if (!ClusterExpr(node.GetRule_object_ref4().GetBlock1().GetRule_cluster_expr1(),
12631265
false,
12641266
context.ServiceId,
12651267
context.Cluster)) {
12661268
return false;
12671269
}
12681270
}
12691271

1270-
const TString objectId = Id(node.GetRule_object_ref3().GetRule_id_or_at2(), *this).second;
1272+
const bool missingOk = node.HasBlock3();
1273+
1274+
const TString objectId = Id(node.GetRule_object_ref4().GetRule_id_or_at2(), *this).second;
12711275
constexpr const char* TypeId = "VIEW";
12721276
AddStatementToBlocks(blocks,
12731277
BuildDropObjectOperation(Ctx.Pos(),
12741278
BuildTablePath(Ctx.GetPrefixPath(context.ServiceId, context.Cluster), objectId),
12751279
TypeId,
1276-
false,
1280+
missingOk,
12771281
{},
12781282
context));
12791283
break;

0 commit comments

Comments
 (0)