Skip to content

Commit

Permalink
24-3: Enable subqueries inside views (ydb-platform#10517) (ydb-platfo…
Browse files Browse the repository at this point in the history
  • Loading branch information
jepett0 authored and zinal committed Nov 5, 2024
1 parent 6cb43a7 commit f49bb58
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 49 deletions.
9 changes: 9 additions & 0 deletions ydb/core/kqp/ut/view/input/cases/in_subquery/create_view.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
CREATE VIEW in_subquery WITH (security_invoker = TRUE) AS
SELECT
*
FROM series
WHERE series_id IN (
SELECT
series_id
FROM series
);
1 change: 1 addition & 0 deletions ydb/core/kqp/ut/view/input/cases/in_subquery/drop_view.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP VIEW in_subquery;
12 changes: 12 additions & 0 deletions ydb/core/kqp/ut/view/input/cases/in_subquery/etalon_query.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
SELECT
*
FROM (
SELECT
*
FROM series
WHERE series_id IN (
SELECT
series_id
FROM series
)
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SELECT
*
FROM in_subquery;
24 changes: 16 additions & 8 deletions ydb/core/kqp/ut/view/view_ut.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,18 @@ void CompareResults(const TDataQueryResult& first, const TDataQueryResult& secon
}
}

void InitializeTablesAndSecondaryViews(TSession& session) {
void CompareResults(const TDataQueryResult& first, const TDataQueryResult& second) {
CompareResults(first.GetResultSets(), second.GetResultSets());
}

void CompareResults(const NQuery::TExecuteQueryResult& first, const NQuery::TExecuteQueryResult& second) {
CompareResults(first.GetResultSets(), second.GetResultSets());
}

void InitializeTablesAndSecondaryViews(NQuery::TSession& session) {
const auto inputFolder = ArcadiaFromCurrentLocation(__SOURCE_FILE__, "input");
ExecuteDataDefinitionQuery(session, ReadWholeFile(inputFolder + "/create_tables_and_secondary_views.sql"));
ExecuteDataModificationQuery(session, ReadWholeFile(inputFolder + "/fill_tables.sql"));
ExecuteQuery(session, ReadWholeFile(inputFolder + "/create_tables_and_secondary_views.sql"));
ExecuteQuery(session, ReadWholeFile(inputFolder + "/fill_tables.sql"));
}

}
Expand Down Expand Up @@ -439,7 +447,7 @@ Y_UNIT_TEST_SUITE(TSelectFromViewTest) {
Y_UNIT_TEST(ReadTestCasesFromFiles) {
TKikimrRunner kikimr;
EnableViewsFeatureFlag(kikimr);
auto session = kikimr.GetTableClient().CreateSession().GetValueSync().GetSession();
auto session = kikimr.GetQueryClient().GetSession().ExtractValueSync().GetSession();

InitializeTablesAndSecondaryViews(session);
EnableLogging();
Expand All @@ -450,13 +458,13 @@ Y_UNIT_TEST_SUITE(TSelectFromViewTest) {
TString testcase;
while (testcase = testcases.Next()) {
const auto pathPrefix = TStringBuilder() << testcasesFolder << '/' << testcase << '/';
ExecuteDataDefinitionQuery(session, ReadWholeFile(pathPrefix + "create_view.sql"));
ExecuteQuery(session, ReadWholeFile(pathPrefix + "create_view.sql"));

const auto etalonResults = ExecuteDataModificationQuery(session, ReadWholeFile(pathPrefix + "etalon_query.sql"));
const auto selectFromViewResults = ExecuteDataModificationQuery(session, ReadWholeFile(pathPrefix + "select_from_view.sql"));
const auto etalonResults = ExecuteQuery(session, ReadWholeFile(pathPrefix + "etalon_query.sql"));
const auto selectFromViewResults = ExecuteQuery(session, ReadWholeFile(pathPrefix + "select_from_view.sql"));
CompareResults(etalonResults, selectFromViewResults);

ExecuteDataDefinitionQuery(session, ReadWholeFile(pathPrefix + "drop_view.sql"));
ExecuteQuery(session, ReadWholeFile(pathPrefix + "drop_view.sql"));
}
}

Expand Down
112 changes: 71 additions & 41 deletions ydb/library/yql/sql/v1/sql_translation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,31 +53,67 @@ TString CollectTokens(const TRule_select_stmt& selectStatement) {
return tokenCollector.Tokens;
}

NSQLTranslation::TTranslationSettings CreateViewTranslationSettings(const NSQLTranslation::TTranslationSettings& base) {
NSQLTranslation::TTranslationSettings settings;
bool RecreateContext(
TContext& ctx, const NSQLTranslation::TTranslationSettings& settings, const TString& recreationQuery
) {
if (!recreationQuery) {
return true;
}
const TString queryName = "context recreation query";

const auto* ast = NSQLTranslationV1::SqlAST(
recreationQuery, queryName, ctx.Issues,
settings.MaxErrors, settings.AnsiLexer, settings.Arena
);
if (!ast) {
return false;
}

settings.ClusterMapping = base.ClusterMapping;
settings.Mode = NSQLTranslation::ESqlMode::LIMITED_VIEW;
TSqlQuery queryTranslator(ctx, ctx.Settings.Mode, true);
auto node = queryTranslator.Build(static_cast<const TSQLv1ParserAST&>(*ast));

return settings;
return node && node->Init(ctx, nullptr) && node->Translate(ctx);
}

TNodePtr BuildViewSelect(const TRule_select_stmt& query, TContext& ctx) {
const auto viewTranslationSettings = CreateViewTranslationSettings(ctx.Settings);
TContext viewParsingContext(viewTranslationSettings, {}, ctx.Issues);
TSqlSelect select(viewParsingContext, viewTranslationSettings.Mode);
TPosition pos;
auto source = select.Build(query, pos);
TNodePtr BuildViewSelect(
const TRule_select_stmt& selectStatement,
TContext& parentContext,
const TString& contextRecreationQuery
) {
TIssues issues;
TContext context(parentContext.Settings, {}, issues);
if (!RecreateContext(context, context.Settings, contextRecreationQuery)) {
parentContext.Issues.AddIssues(issues);
return nullptr;
}
issues.Clear();

// Holds (among other things) subquery references.
// These references need to be passed to the parent context
// to be able to compile view queries with subqueries.
context.PushCurrentBlocks(&parentContext.GetCurrentBlocks());

context.Settings.Mode = NSQLTranslation::ESqlMode::LIMITED_VIEW;

TSqlSelect selectTranslator(context, context.Settings.Mode);
TPosition pos = parentContext.Pos();
auto source = selectTranslator.Build(selectStatement, pos);
if (!source) {
parentContext.Issues.AddIssues(issues);
return nullptr;
}
return BuildSelectResult(
auto node = BuildSelectResult(
pos,
std::move(source),
false,
false,
viewParsingContext.Scoped
);
if (!node) {
parentContext.Issues.AddIssues(issues);
return nullptr;
}
return node;
}

}
Expand Down Expand Up @@ -4568,38 +4604,32 @@ bool TSqlTranslation::ValidateExternalTable(const TCreateTableParameters& params
return true;
}

bool TSqlTranslation::ParseViewOptions(std::map<TString, TDeferredAtom>& features,
const TRule_with_table_settings& options) {
const auto& firstEntry = options.GetRule_table_settings_entry3();
if (!StoreViewOptionsEntry(IdEx(firstEntry.GetRule_an_id1(), *this),
firstEntry.GetRule_table_setting_value3(),
features,
Ctx)) {
return false;
}
for (const auto& block : options.GetBlock4()) {
const auto& entry = block.GetRule_table_settings_entry2();
if (!StoreViewOptionsEntry(IdEx(entry.GetRule_an_id1(), *this),
entry.GetRule_table_setting_value3(),
features,
Ctx)) {
return false;
bool TSqlTranslation::ParseViewQuery(
std::map<TString, TDeferredAtom>& features,
const TRule_select_stmt& query
) {
TString queryText = CollectTokens(query);
TString contextRecreationQuery;
{
const auto& service = Ctx.Scoped->CurrService;
const auto& cluster = Ctx.Scoped->CurrCluster;
const auto effectivePathPrefix = Ctx.GetPrefixPath(service, cluster);

// TO DO: capture all runtime pragmas in a similar fashion.
if (effectivePathPrefix != Ctx.Settings.PathPrefix) {
contextRecreationQuery = TStringBuilder() << "PRAGMA TablePathPrefix = \"" << effectivePathPrefix << "\";\n";
}
}
if (const auto securityInvoker = features.find("security_invoker");
securityInvoker == features.end() || securityInvoker->second.Build()->GetLiteralValue() != "true") {
Ctx.Error(Ctx.Pos()) << "SECURITY_INVOKER option must be explicitly enabled";
return false;
}
return true;
}

bool TSqlTranslation::ParseViewQuery(std::map<TString, TDeferredAtom>& features,
const TRule_select_stmt& query) {
const TString queryText = CollectTokens(query);
features["query_text"] = {Ctx.Pos(), queryText};
// TO DO: capture other compilation-affecting statements except USE.
if (cluster.GetLiteral() && *cluster.GetLiteral() != Ctx.Settings.DefaultCluster) {
contextRecreationQuery = TStringBuilder() << "USE " << *cluster.GetLiteral() << ";\n";
}
}
features["query_text"] = { Ctx.Pos(), contextRecreationQuery + queryText };

const auto viewSelect = BuildViewSelect(query, Ctx);
// AST is needed for ready-made validation of CREATE VIEW statement.
// Query is stored as plain text, not AST.
const auto viewSelect = BuildViewSelect(query, Ctx, contextRecreationQuery);
if (!viewSelect) {
return false;
}
Expand Down

0 comments on commit f49bb58

Please sign in to comment.