summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorvitya-smirnov <[email protected]>2025-06-24 15:50:39 +0300
committervitya-smirnov <[email protected]>2025-06-24 16:06:46 +0300
commit573dadf304e5729bdad2dc02fd7c4ad2cb7a0524 (patch)
treeed72d28bb7b17af07d1364abd08360829533792d
parent7ad54f585b44a26b4c619ab33dcfe06fd0add315 (diff)
YQL-19747: Synthesise subquery columns
commit_hash:40a2de243a67135e44505619fb766954ba24e2e8
-rw-r--r--yql/essentials/sql/v1/complete/analysis/global/base_visitor.cpp12
-rw-r--r--yql/essentials/sql/v1/complete/analysis/global/base_visitor.h12
-rw-r--r--yql/essentials/sql/v1/complete/analysis/global/column.cpp94
-rw-r--r--yql/essentials/sql/v1/complete/analysis/global/global.cpp42
-rw-r--r--yql/essentials/sql/v1/complete/analysis/global/global.h14
-rw-r--r--yql/essentials/sql/v1/complete/analysis/global/global_ut.cpp68
-rw-r--r--yql/essentials/sql/v1/complete/analysis/global/narrowing_visitor.cpp7
-rw-r--r--yql/essentials/sql/v1/complete/analysis/global/narrowing_visitor.h4
-rw-r--r--yql/essentials/sql/v1/complete/analysis/global/ya.make1
-rw-r--r--yql/essentials/sql/v1/complete/check/check_complete_ut.cpp46
-rw-r--r--yql/essentials/sql/v1/complete/check/ut/ya.make18
-rw-r--r--yql/essentials/sql/v1/complete/check/ya.make4
-rw-r--r--yql/essentials/sql/v1/complete/name/service/binding/name_service.cpp1
-rw-r--r--yql/essentials/sql/v1/complete/name/service/binding/name_service.h1
-rw-r--r--yql/essentials/sql/v1/complete/name/service/column/name_service.cpp76
-rw-r--r--yql/essentials/sql/v1/complete/name/service/column/name_service.h10
-rw-r--r--yql/essentials/sql/v1/complete/name/service/column/ya.make13
-rw-r--r--yql/essentials/sql/v1/complete/name/service/ya.make1
-rw-r--r--yql/essentials/sql/v1/complete/sql_complete.cpp15
-rw-r--r--yql/essentials/sql/v1/complete/sql_complete_ut.cpp28
-rw-r--r--yql/essentials/sql/v1/complete/ya.make1
21 files changed, 431 insertions, 37 deletions
diff --git a/yql/essentials/sql/v1/complete/analysis/global/base_visitor.cpp b/yql/essentials/sql/v1/complete/analysis/global/base_visitor.cpp
new file mode 100644
index 00000000000..f426e17b62f
--- /dev/null
+++ b/yql/essentials/sql/v1/complete/analysis/global/base_visitor.cpp
@@ -0,0 +1,12 @@
+#include "base_visitor.h"
+
+namespace NSQLComplete {
+
+ std::any TSQLv1BaseVisitor::aggregateResult(std::any aggregate, std::any nextResult) {
+ if (nextResult.has_value()) {
+ return nextResult;
+ }
+ return aggregate;
+ }
+
+} // namespace NSQLComplete
diff --git a/yql/essentials/sql/v1/complete/analysis/global/base_visitor.h b/yql/essentials/sql/v1/complete/analysis/global/base_visitor.h
new file mode 100644
index 00000000000..bbee6b796f2
--- /dev/null
+++ b/yql/essentials/sql/v1/complete/analysis/global/base_visitor.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include "parse_tree.h"
+
+namespace NSQLComplete {
+
+ class TSQLv1BaseVisitor: public SQLv1Antlr4BaseVisitor {
+ protected:
+ std::any aggregateResult(std::any aggregate, std::any nextResult) override;
+ };
+
+} // namespace NSQLComplete
diff --git a/yql/essentials/sql/v1/complete/analysis/global/column.cpp b/yql/essentials/sql/v1/complete/analysis/global/column.cpp
index b64b59a9f47..00da14c4b2d 100644
--- a/yql/essentials/sql/v1/complete/analysis/global/column.cpp
+++ b/yql/essentials/sql/v1/complete/analysis/global/column.cpp
@@ -1,5 +1,6 @@
#include "column.h"
+#include "base_visitor.h"
#include "narrowing_visitor.h"
#include <yql/essentials/sql/v1/complete/syntax/format.h>
@@ -10,7 +11,7 @@ namespace NSQLComplete {
// TODO: Extract it to `identifier.cpp` and reuse it also at `use.cpp`
// and replace `GetId` at `parse_tree.cpp`.
- class TIdentifierVisitor: public SQLv1Antlr4BaseVisitor {
+ class TIdentifierVisitor: public TSQLv1BaseVisitor {
public:
std::any visitCluster_expr(SQLv1::Cluster_exprContext* ctx) override {
if (auto* x = ctx->pure_column_or_named()) {
@@ -54,20 +55,10 @@ namespace NSQLComplete {
return std::any_cast<TString>(result);
}
- class TInferenceVisitor: public SQLv1Antlr4BaseVisitor {
+ class TInferenceVisitor: public TSQLv1BaseVisitor {
public:
std::any visitJoin_source(SQLv1::Join_sourceContext* ctx) override {
- TColumnContext united;
- for (SQLv1::Flatten_sourceContext* ctx : ctx->flatten_source()) {
- std::any any = visit(ctx);
- if (!any.has_value()) {
- continue;
- }
-
- TColumnContext child = std::move(std::any_cast<TColumnContext>(any));
- united = United(std::move(united), std::move(child));
- }
- return united;
+ return AccumulatingVisit(ctx->flatten_source());
}
std::any visitNamed_single_source(SQLv1::Named_single_sourceContext* ctx) override {
@@ -87,7 +78,7 @@ namespace NSQLComplete {
return context;
}
- return Renamed(std::move(context), *alias);
+ return std::move(context).Renamed(*alias);
}
std::any visitTable_ref(SQLv1::Table_refContext* ctx) override {
@@ -105,6 +96,35 @@ namespace NSQLComplete {
};
}
+ std::any visitSelect_stmt(SQLv1::Select_stmtContext* ctx) override {
+ return AccumulatingVisit(ctx->select_kind_parenthesis());
+ }
+
+ std::any visitSelect_core(SQLv1::Select_coreContext* ctx) override {
+ TMaybe<TColumnContext> head = Head(ctx);
+ if (head.Empty() || head->IsAsterisk()) {
+ return AccumulatingVisit(ctx->join_source());
+ }
+
+ return AccumulatingVisit(ctx->result_column());
+ }
+
+ std::any visitResult_column(SQLv1::Result_columnContext* ctx) override {
+ if (ctx->TOKEN_ASTERISK() != nullptr) {
+ return TColumnContext::Asterisk();
+ }
+
+ TMaybe<TString> column = GetAlias(ctx);
+ if (column.Defined()) {
+ return TColumnContext{
+ .Columns = {
+ {.Name = std::move(*column)},
+ }};
+ }
+
+ return {};
+ }
+
private:
TMaybe<TString> GetAlias(SQLv1::Named_single_sourceContext* ctx) const {
TMaybe<TString> alias = GetId(ctx->an_id());
@@ -112,21 +132,45 @@ namespace NSQLComplete {
return alias;
}
- TColumnContext Renamed(TColumnContext context, TString alias) {
- Y_ENSURE(!alias.empty());
+ TMaybe<TString> GetAlias(SQLv1::Result_columnContext* ctx) const {
+ antlr4::ParserRuleContext* id = nullptr;
+ if (ctx->TOKEN_AS() == nullptr) {
+ id = ctx->expr();
+ } else {
+ id = ctx->an_id_or_type();
+ id = id ? id : ctx->an_id_as_compat();
+ }
+ return GetId(id);
+ }
- for (TAliased<TTableId>& table : context.Tables) {
- table.Alias = alias;
+ TMaybe<TColumnContext> Head(SQLv1::Select_coreContext* ctx) {
+ SQLv1::Result_columnContext* column = ctx->result_column(0);
+ if (column == nullptr) {
+ return Nothing();
+ }
+
+ std::any any = visit(column);
+ if (!any.has_value()) {
+ return Nothing();
}
- return context;
+ return std::any_cast<TColumnContext>(any);
}
- static TColumnContext United(TColumnContext&& lhs, TColumnContext&& rhs) {
- lhs.Tables.reserve(lhs.Tables.size() + rhs.Tables.size());
- std::move(rhs.Tables.begin(), rhs.Tables.end(), std::back_inserter(lhs.Tables));
- SortUnique(lhs.Tables);
- return lhs;
+ template <std::derived_from<antlr4::ParserRuleContext> T>
+ TColumnContext AccumulatingVisit(std::vector<T*> contexts) {
+ return Accumulate(
+ contexts,
+ TColumnContext(),
+ [this](TColumnContext&& acc, T* ctx) {
+ std::any any = visit(ctx);
+ if (!any.has_value()) {
+ return acc;
+ }
+
+ TColumnContext child = std::move(std::any_cast<TColumnContext>(any));
+ return std::move(acc) | std::move(child);
+ });
}
};
@@ -153,7 +197,7 @@ namespace NSQLComplete {
return {};
}
- return TInferenceVisitor().visit(ctx);
+ return TInferenceVisitor().visit(source);
}
};
diff --git a/yql/essentials/sql/v1/complete/analysis/global/global.cpp b/yql/essentials/sql/v1/complete/analysis/global/global.cpp
index f73cad926e3..74f9b6909e8 100644
--- a/yql/essentials/sql/v1/complete/analysis/global/global.cpp
+++ b/yql/essentials/sql/v1/complete/analysis/global/global.cpp
@@ -15,6 +15,10 @@
namespace NSQLComplete {
+ bool operator<(const TColumnId& lhs, const TColumnId& rhs) {
+ return std::tie(lhs.TableAlias, lhs.Name) < std::tie(rhs.TableAlias, rhs.Name);
+ }
+
TVector<TTableId> TColumnContext::TablesWithAlias(TStringBuf alias) const {
if (alias.empty()) {
return TVector<TTableId>(Tables.begin(), Tables.end());
@@ -24,6 +28,38 @@ namespace NSQLComplete {
return TVector<TTableId>(filtered.begin(), filtered.end());
}
+ bool TColumnContext::IsAsterisk() const {
+ return Columns.size() == 1 && Columns[0].Name == "*";
+ }
+
+ TColumnContext TColumnContext::Renamed(TStringBuf alias) && {
+ Y_ENSURE(!alias.empty());
+ for (TAliased<TTableId>& table : Tables) {
+ table.Alias = alias;
+ }
+ for (TColumnId& column : Columns) {
+ column.TableAlias = alias;
+ }
+ return *this;
+ }
+
+ TColumnContext operator|(TColumnContext lhs, TColumnContext rhs) {
+ lhs.Tables.reserve(lhs.Tables.size() + rhs.Tables.size());
+ lhs.Columns.reserve(lhs.Columns.size() + rhs.Columns.size());
+
+ std::move(rhs.Tables.begin(), rhs.Tables.end(), std::back_inserter(lhs.Tables));
+ std::move(rhs.Columns.begin(), rhs.Columns.end(), std::back_inserter(lhs.Columns));
+
+ SortUnique(lhs.Tables);
+ SortUnique(lhs.Columns);
+
+ return lhs;
+ }
+
+ TColumnContext TColumnContext::Asterisk() {
+ return {.Columns = {{.Name = "*"}}};
+ }
+
class TErrorStrategy: public antlr4::DefaultErrorStrategy {
public:
antlr4::Token* singleTokenDeletion(antlr4::Parser* /* recognizer */) override {
@@ -132,8 +168,14 @@ void Out<NSQLComplete::TAliased<NSQLComplete::TTableId>>(IOutputStream& out, con
}
template <>
+void Out<NSQLComplete::TColumnId>(IOutputStream& out, const NSQLComplete::TColumnId& value) {
+ out << value.TableAlias << "." << value.Name;
+}
+
+template <>
void Out<NSQLComplete::TColumnContext>(IOutputStream& out, const NSQLComplete::TColumnContext& value) {
out << "TColumnContext { ";
out << "Tables: " << JoinSeq(", ", value.Tables);
+ out << ", Columns: " << JoinSeq(", ", value.Columns);
out << " }";
}
diff --git a/yql/essentials/sql/v1/complete/analysis/global/global.h b/yql/essentials/sql/v1/complete/analysis/global/global.h
index 31e5ea3ea53..972b2caa609 100644
--- a/yql/essentials/sql/v1/complete/analysis/global/global.h
+++ b/yql/essentials/sql/v1/complete/analysis/global/global.h
@@ -41,12 +41,26 @@ namespace NSQLComplete {
friend bool operator==(const TAliased& lhs, const TAliased& rhs) = default;
};
+ struct TColumnId {
+ TString TableAlias;
+ TString Name;
+
+ friend bool operator<(const TColumnId& lhs, const TColumnId& rhs);
+ friend bool operator==(const TColumnId& lhs, const TColumnId& rhs) = default;
+ };
+
struct TColumnContext {
TVector<TAliased<TTableId>> Tables;
+ TVector<TColumnId> Columns;
TVector<TTableId> TablesWithAlias(TStringBuf alias) const;
+ bool IsAsterisk() const;
+ TColumnContext Renamed(TStringBuf alias) &&;
friend bool operator==(const TColumnContext& lhs, const TColumnContext& rhs) = default;
+ friend TColumnContext operator|(TColumnContext lhs, TColumnContext rhs);
+
+ static TColumnContext Asterisk();
};
struct TGlobalContext {
diff --git a/yql/essentials/sql/v1/complete/analysis/global/global_ut.cpp b/yql/essentials/sql/v1/complete/analysis/global/global_ut.cpp
index 1065446c952..5e180fcb431 100644
--- a/yql/essentials/sql/v1/complete/analysis/global/global_ut.cpp
+++ b/yql/essentials/sql/v1/complete/analysis/global/global_ut.cpp
@@ -149,4 +149,72 @@ Y_UNIT_TEST_SUITE(GlobalAnalysisTests) {
}
}
+ Y_UNIT_TEST(Subquery) {
+ IGlobalAnalysis::TPtr global = MakeGlobalAnalysis();
+ {
+ TString query = "SELECT # FROM (SELECT * FROM x)";
+
+ TGlobalContext ctx = global->Analyze(SharpedInput(query), {});
+
+ TColumnContext expected = {.Tables = {TAliased<TTableId>("", {"", "x"})}};
+ UNIT_ASSERT_VALUES_EQUAL(ctx.Column, expected);
+ }
+ {
+ TString query = "SELECT # FROM (SELECT a, b FROM x)";
+
+ TGlobalContext ctx = global->Analyze(SharpedInput(query), {});
+
+ TColumnContext expected = {.Columns = {{.Name = "a"}, {.Name = "b"}}};
+ UNIT_ASSERT_VALUES_EQUAL(ctx.Column, expected);
+ }
+ {
+ TString query = "SELECT # FROM (SELECT 1 AS a, 2 AS b)";
+
+ TGlobalContext ctx = global->Analyze(SharpedInput(query), {});
+
+ TColumnContext expected = {.Columns = {{.Name = "a"}, {.Name = "b"}}};
+ UNIT_ASSERT_VALUES_EQUAL(ctx.Column, expected);
+ }
+ {
+ TString query = "SELECT # FROM (SELECT 1 AS a, 2 AS b) AS x";
+
+ TGlobalContext ctx = global->Analyze(SharpedInput(query), {});
+
+ TColumnContext expected = {.Columns = {{"x", "a"}, {"x", "b"}}};
+ UNIT_ASSERT_VALUES_EQUAL(ctx.Column, expected);
+ }
+ {
+ TString query = R"(
+ SELECT #
+ FROM (SELECT * FROM example.`/people`) AS ep
+ JOIN (SELECT room AS Room, time FROM example.`/yql/tutorial`) AS et ON 1 = 1
+ )";
+
+ TGlobalContext ctx = global->Analyze(SharpedInput(query), {});
+
+ TColumnContext expected = {
+ .Tables = {
+ TAliased<TTableId>("ep", {"example", "/people"}),
+ },
+ .Columns = {
+ {.TableAlias = "et", .Name = "Room"},
+ {.TableAlias = "et", .Name = "time"},
+ },
+ };
+ UNIT_ASSERT_VALUES_EQUAL(ctx.Column, expected);
+ }
+ }
+
+ Y_UNIT_TEST(Projection) {
+ IGlobalAnalysis::TPtr global = MakeGlobalAnalysis();
+ {
+ TString query = "SELECT a, b, # FROM x";
+
+ TGlobalContext ctx = global->Analyze(SharpedInput(query), {});
+
+ TColumnContext expected = {.Tables = {TAliased<TTableId>("", {"", "x"})}};
+ UNIT_ASSERT_VALUES_EQUAL(ctx.Column, expected);
+ }
+ }
+
} // Y_UNIT_TEST_SUITE(GlobalAnalysisTests)
diff --git a/yql/essentials/sql/v1/complete/analysis/global/narrowing_visitor.cpp b/yql/essentials/sql/v1/complete/analysis/global/narrowing_visitor.cpp
index a26523c94a0..f1cd303062c 100644
--- a/yql/essentials/sql/v1/complete/analysis/global/narrowing_visitor.cpp
+++ b/yql/essentials/sql/v1/complete/analysis/global/narrowing_visitor.cpp
@@ -12,13 +12,6 @@ namespace NSQLComplete {
return TextInterval(node).a < static_cast<ssize_t>(CursorPosition_);
}
- std::any TSQLv1NarrowingVisitor::aggregateResult(std::any aggregate, std::any nextResult) {
- if (nextResult.has_value()) {
- return nextResult;
- }
- return aggregate;
- }
-
bool TSQLv1NarrowingVisitor::IsEnclosing(antlr4::tree::ParseTree* tree) const {
return TextInterval(tree).properlyContains(CursorInterval());
}
diff --git a/yql/essentials/sql/v1/complete/analysis/global/narrowing_visitor.h b/yql/essentials/sql/v1/complete/analysis/global/narrowing_visitor.h
index 5a3947b9355..1ad4bbe2274 100644
--- a/yql/essentials/sql/v1/complete/analysis/global/narrowing_visitor.h
+++ b/yql/essentials/sql/v1/complete/analysis/global/narrowing_visitor.h
@@ -1,16 +1,16 @@
#pragma once
+#include "base_visitor.h"
#include "parse_tree.h"
namespace NSQLComplete {
- class TSQLv1NarrowingVisitor: public SQLv1Antlr4BaseVisitor {
+ class TSQLv1NarrowingVisitor: public TSQLv1BaseVisitor {
public:
TSQLv1NarrowingVisitor(antlr4::TokenStream* tokens, size_t cursorPosition);
protected:
bool shouldVisitNextChild(antlr4::tree::ParseTree* node, const std::any& /*currentResult*/) override;
- std::any aggregateResult(std::any aggregate, std::any nextResult) override;
bool IsEnclosing(antlr4::tree::ParseTree* tree) const;
ssize_t CursorPosition() const;
diff --git a/yql/essentials/sql/v1/complete/analysis/global/ya.make b/yql/essentials/sql/v1/complete/analysis/global/ya.make
index 1f1670a9675..b67f290a3be 100644
--- a/yql/essentials/sql/v1/complete/analysis/global/ya.make
+++ b/yql/essentials/sql/v1/complete/analysis/global/ya.make
@@ -1,6 +1,7 @@
LIBRARY()
SRCS(
+ base_visitor.cpp
column.cpp
evaluate.cpp
function.cpp
diff --git a/yql/essentials/sql/v1/complete/check/check_complete_ut.cpp b/yql/essentials/sql/v1/complete/check/check_complete_ut.cpp
new file mode 100644
index 00000000000..7743f574321
--- /dev/null
+++ b/yql/essentials/sql/v1/complete/check/check_complete_ut.cpp
@@ -0,0 +1,46 @@
+#include "check_complete.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <yql/essentials/sql/sql.h>
+#include <yql/essentials/sql/v1/sql.h>
+#include <yql/essentials/sql/v1/lexer/antlr4/lexer.h>
+#include <yql/essentials/sql/v1/lexer/antlr4_ansi/lexer.h>
+#include <yql/essentials/sql/v1/proto_parser/antlr4/proto_parser.h>
+#include <yql/essentials/sql/v1/proto_parser/antlr4_ansi/proto_parser.h>
+
+using namespace NSQLComplete;
+
+Y_UNIT_TEST_SUITE(CheckTests) {
+
+ Y_UNIT_TEST(Runs) {
+ TString query = R"(
+ SELECT * FROM (SELECT 1 AS x)
+ )";
+
+ NSQLTranslationV1::TLexers lexers = {
+ .Antlr4 = NSQLTranslationV1::MakeAntlr4LexerFactory(),
+ .Antlr4Ansi = NSQLTranslationV1::MakeAntlr4AnsiLexerFactory(),
+ };
+
+ NSQLTranslationV1::TParsers parsers = {
+ .Antlr4 = NSQLTranslationV1::MakeAntlr4ParserFactory(),
+ .Antlr4Ansi = NSQLTranslationV1::MakeAntlr4AnsiParserFactory(),
+ };
+
+ NSQLTranslation::TTranslators translators(
+ /* V0 = */ nullptr,
+ /* V1 = */ NSQLTranslationV1::MakeTranslator(lexers, parsers),
+ /* PG = */ nullptr);
+
+ google::protobuf::Arena arena;
+ NSQLTranslation::TTranslationSettings settings;
+ settings.Arena = &arena;
+
+ NYql::TAstParseResult result = NSQLTranslation::SqlToYql(translators, query, settings);
+ Y_ENSURE(result.IsOk());
+
+ Y_ENSURE(CheckComplete(query, *result.Root, result.Issues), result.Issues.ToString());
+ }
+
+} // Y_UNIT_TEST_SUITE(CheckTests)
diff --git a/yql/essentials/sql/v1/complete/check/ut/ya.make b/yql/essentials/sql/v1/complete/check/ut/ya.make
new file mode 100644
index 00000000000..3a16e9246f8
--- /dev/null
+++ b/yql/essentials/sql/v1/complete/check/ut/ya.make
@@ -0,0 +1,18 @@
+UNITTEST_FOR(yql/essentials/sql/v1/complete/check)
+
+SRCS(
+ check_complete_ut.cpp
+)
+
+PEERDIR(
+ yql/essentials/parser/pg_wrapper
+ yql/essentials/public/udf/service/stub
+ yql/essentials/sql
+ yql/essentials/sql/v1
+ yql/essentials/sql/v1/lexer/antlr4
+ yql/essentials/sql/v1/lexer/antlr4_ansi
+ yql/essentials/sql/v1/proto_parser/antlr4
+ yql/essentials/sql/v1/proto_parser/antlr4_ansi
+)
+
+END()
diff --git a/yql/essentials/sql/v1/complete/check/ya.make b/yql/essentials/sql/v1/complete/check/ya.make
index 2a4d577fb51..8312f6bede3 100644
--- a/yql/essentials/sql/v1/complete/check/ya.make
+++ b/yql/essentials/sql/v1/complete/check/ya.make
@@ -19,3 +19,7 @@ PEERDIR(
)
END()
+
+RECURSE_FOR_TESTS(
+ ut
+)
diff --git a/yql/essentials/sql/v1/complete/name/service/binding/name_service.cpp b/yql/essentials/sql/v1/complete/name/service/binding/name_service.cpp
index 9de6380410b..52a0afccf8d 100644
--- a/yql/essentials/sql/v1/complete/name/service/binding/name_service.cpp
+++ b/yql/essentials/sql/v1/complete/name/service/binding/name_service.cpp
@@ -44,7 +44,6 @@ namespace NSQLComplete {
}
TNameIndex Index_;
- IRanking::TPtr Ranking_;
};
} // namespace
diff --git a/yql/essentials/sql/v1/complete/name/service/binding/name_service.h b/yql/essentials/sql/v1/complete/name/service/binding/name_service.h
index 8fdac1a8d7e..4c9de468a93 100644
--- a/yql/essentials/sql/v1/complete/name/service/binding/name_service.h
+++ b/yql/essentials/sql/v1/complete/name/service/binding/name_service.h
@@ -3,7 +3,6 @@
#pragma once
#include <yql/essentials/sql/v1/complete/name/service/name_service.h>
-#include <yql/essentials/sql/v1/complete/name/service/ranking/ranking.h>
namespace NSQLComplete {
diff --git a/yql/essentials/sql/v1/complete/name/service/column/name_service.cpp b/yql/essentials/sql/v1/complete/name/service/column/name_service.cpp
new file mode 100644
index 00000000000..9b87f5fe5e1
--- /dev/null
+++ b/yql/essentials/sql/v1/complete/name/service/column/name_service.cpp
@@ -0,0 +1,76 @@
+#include "name_service.h"
+
+#include <yql/essentials/sql/v1/complete/name/object/simple/static/schema.h>
+
+namespace NSQLComplete {
+
+ namespace {
+
+ class TNameService: public INameService {
+ public:
+ explicit TNameService(TVector<TColumnId> columns) {
+ TSchemaData data;
+ for (auto& column : columns) {
+ Tables_.emplace(column.TableAlias);
+
+ data.Tables[""]["/" + Escaped(column.TableAlias)]
+ .Columns
+ .emplace_back(std::move(column.Name));
+ }
+
+ Schema_ = MakeSimpleSchema(MakeStaticSimpleSchema(std::move(data)));
+ }
+
+ NThreading::TFuture<TNameResponse> Lookup(TNameRequest request) const override {
+ if (!request.Constraints.Column) {
+ return NThreading::MakeFuture<TNameResponse>({});
+ }
+
+ TNameResponse response;
+
+ for (const TString& tableName : Tables_) {
+ TDescribeTableRequest describeRequest = {
+ .TableCluster = "",
+ .TablePath = Escaped(tableName),
+ .ColumnPrefix = request.Prefix,
+ .ColumnsLimit = request.Limit,
+ };
+
+ TDescribeTableResponse table =
+ Schema_
+ ->Describe(std::move(describeRequest))
+ .ExtractValue();
+
+ Y_ENSURE(table.IsExisting);
+ for (TString& column : table.Columns) {
+ TColumnName name;
+ name.Table = {.Cluster = "", .Path = tableName};
+ name.Indentifier = std::move(column);
+
+ response.RankedNames.emplace_back(std::move(name));
+ }
+ }
+
+ response.RankedNames.crop(request.Limit);
+
+ return NThreading::MakeFuture(std::move(response));
+ }
+
+ private:
+ static TString Escaped(TString tableName) {
+ // Saves when name is empty
+ tableName.prepend("table_");
+ return tableName;
+ }
+
+ THashSet<TString> Tables_;
+ ISchema::TPtr Schema_;
+ };
+
+ } // namespace
+
+ INameService::TPtr MakeColumnNameService(TVector<TColumnId> columns) {
+ return new TNameService(std::move(columns));
+ }
+
+} // namespace NSQLComplete
diff --git a/yql/essentials/sql/v1/complete/name/service/column/name_service.h b/yql/essentials/sql/v1/complete/name/service/column/name_service.h
new file mode 100644
index 00000000000..51d3239748a
--- /dev/null
+++ b/yql/essentials/sql/v1/complete/name/service/column/name_service.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include <yql/essentials/sql/v1/complete/analysis/global/global.h>
+#include <yql/essentials/sql/v1/complete/name/service/name_service.h>
+
+namespace NSQLComplete {
+
+ INameService::TPtr MakeColumnNameService(TVector<TColumnId> columns);
+
+} // namespace NSQLComplete
diff --git a/yql/essentials/sql/v1/complete/name/service/column/ya.make b/yql/essentials/sql/v1/complete/name/service/column/ya.make
new file mode 100644
index 00000000000..64c11175e2f
--- /dev/null
+++ b/yql/essentials/sql/v1/complete/name/service/column/ya.make
@@ -0,0 +1,13 @@
+LIBRARY()
+
+SRCS(
+ name_service.cpp
+)
+
+PEERDIR(
+ yql/essentials/sql/v1/complete/analysis/global
+ yql/essentials/sql/v1/complete/name/object/simple/static
+ yql/essentials/sql/v1/complete/name/service
+)
+
+END()
diff --git a/yql/essentials/sql/v1/complete/name/service/ya.make b/yql/essentials/sql/v1/complete/name/service/ya.make
index 75a55e0c71f..7ddd090559b 100644
--- a/yql/essentials/sql/v1/complete/name/service/ya.make
+++ b/yql/essentials/sql/v1/complete/name/service/ya.make
@@ -14,6 +14,7 @@ END()
RECURSE(
binding
cluster
+ column
impatient
ranking
schema
diff --git a/yql/essentials/sql/v1/complete/sql_complete.cpp b/yql/essentials/sql/v1/complete/sql_complete.cpp
index 8b732bd3913..2cb88c714a7 100644
--- a/yql/essentials/sql/v1/complete/sql_complete.cpp
+++ b/yql/essentials/sql/v1/complete/sql_complete.cpp
@@ -2,8 +2,11 @@
#include <yql/essentials/sql/v1/complete/syntax/grammar.h>
#include <yql/essentials/sql/v1/complete/text/word.h>
+#include <yql/essentials/sql/v1/complete/name/object/simple/static/schema.h>
#include <yql/essentials/sql/v1/complete/name/service/ranking/dummy.h>
#include <yql/essentials/sql/v1/complete/name/service/binding/name_service.h>
+#include <yql/essentials/sql/v1/complete/name/service/column/name_service.h>
+#include <yql/essentials/sql/v1/complete/name/service/schema/name_service.h>
#include <yql/essentials/sql/v1/complete/name/service/static/name_service.h>
#include <yql/essentials/sql/v1/complete/name/service/union/name_service.h>
#include <yql/essentials/sql/v1/complete/syntax/format.h>
@@ -61,7 +64,13 @@ namespace NSQLComplete {
}
TVector<INameService::TPtr> children;
+
children.emplace_back(MakeBindingNameService(std::move(global.Names)));
+
+ if (!context.Binding && global.Column) {
+ children.emplace_back(MakeColumnNameService(std::move(global.Column->Columns)));
+ }
+
if (!context.Binding) {
children.emplace_back(Names_);
}
@@ -300,7 +309,11 @@ namespace NSQLComplete {
}
if constexpr (std::is_base_of_v<TColumnName, T>) {
- const TString& alias = aliasByTable.at(name.Table);
+ TString alias = name.Table.Path;
+ if (auto it = aliasByTable.find(name.Table); it != end(aliasByTable)) {
+ alias = it->second;
+ }
+
if (context.Column->Table.empty() && !alias.empty()) {
name.Indentifier.prepend('.');
name.Indentifier.prepend(alias);
diff --git a/yql/essentials/sql/v1/complete/sql_complete_ut.cpp b/yql/essentials/sql/v1/complete/sql_complete_ut.cpp
index 89576f63c3d..603ddae812a 100644
--- a/yql/essentials/sql/v1/complete/sql_complete_ut.cpp
+++ b/yql/essentials/sql/v1/complete/sql_complete_ut.cpp
@@ -1141,6 +1141,34 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) {
}
}
+ Y_UNIT_TEST(ColumnsAtSubquery) {
+ auto engine = MakeSqlCompletionEngineUT();
+ {
+ TString query = R"(
+ SELECT #
+ FROM (SELECT * FROM example.`/people`) AS ep
+ JOIN (SELECT room AS Room, time FROM example.`/yql/tutorial`) AS et ON 1 = 1
+ )";
+
+ TVector<TCandidate> expected = {
+ {ColumnName, "et.Room"},
+ {ColumnName, "et.time"},
+ {ColumnName, "ep.Age"},
+ {ColumnName, "ep.Name"},
+ {Keyword, "ALL"},
+ };
+ UNIT_ASSERT_VALUES_EQUAL(CompleteTop(5, engine, query), expected);
+ }
+ {
+ TString query = "SELECT # FROM (SELECT 1 AS x)";
+
+ TVector<TCandidate> expected = {
+ {ColumnName, "x"},
+ };
+ UNIT_ASSERT_VALUES_EQUAL(CompleteTop(1, engine, query), expected);
+ }
+ }
+
Y_UNIT_TEST(Typing) {
const auto queryUtf16 = TUtf16String::FromUtf8(
"SELECT \n"
diff --git a/yql/essentials/sql/v1/complete/ya.make b/yql/essentials/sql/v1/complete/ya.make
index 8ad312703ea..e85ba90b524 100644
--- a/yql/essentials/sql/v1/complete/ya.make
+++ b/yql/essentials/sql/v1/complete/ya.make
@@ -16,6 +16,7 @@ PEERDIR(
# TODO(YQL-19747): extract NameIndex
yql/essentials/sql/v1/complete/name/service/ranking
yql/essentials/sql/v1/complete/name/service/binding
+ yql/essentials/sql/v1/complete/name/service/column
)
END()