Line data Source code
1 : #include "backend/sqltools/sql_tools.h"
2 :
3 : #include "backend/sqltools/sql_references.h"
4 : #include "googlesql/public/builtin_function_options.h"
5 : #include "googlesql/public/function.h"
6 : #include "googlesql/public/function_signature.h"
7 : #include "googlesql/public/simple_catalog.h"
8 : #include "googlesql/public/types/type_factory.h"
9 : #include "gtest/gtest.h"
10 :
11 : namespace bigquery_emulator {
12 : namespace backend {
13 : namespace sqltools {
14 : namespace {
15 :
16 : class SqlToolsTest : public ::testing::Test {
17 : protected:
18 12 : void SetUp() override {
19 12 : language_ = MakeSqlToolsLanguageOptions();
20 12 : catalog_ = std::make_unique<::googlesql::SimpleCatalog>("test-project",
21 12 : &type_factory_);
22 12 : catalog_->AddBuiltinFunctionsAndTypes(
23 12 : ::googlesql::BuiltinFunctionOptions(language_));
24 12 : }
25 :
26 1 : void AddScalarFunction(const std::string& name) {
27 1 : ::googlesql::FunctionSignature signature(
28 1 : ::googlesql::FunctionArgumentType(::googlesql::types::Int64Type()),
29 1 : /*arguments=*/{},
30 1 : /*context_id=*/static_cast<int64_t>(0));
31 1 : auto function = std::make_unique<::googlesql::Function>(
32 1 : std::vector<std::string>{name},
33 1 : /*group=*/"External_function",
34 1 : ::googlesql::Function::SCALAR,
35 1 : std::vector<::googlesql::FunctionSignature>{signature});
36 1 : catalog_->AddFunction(function.get());
37 1 : owned_functions_.push_back(std::move(function));
38 1 : }
39 :
40 : ::googlesql::LanguageOptions language_;
41 : ::googlesql::TypeFactory type_factory_;
42 : std::unique_ptr<::googlesql::SimpleCatalog> catalog_;
43 : std::vector<std::unique_ptr<const ::googlesql::Function>> owned_functions_;
44 : };
45 :
46 1 : TEST_F(SqlToolsTest, FormatLenientProducesIndentedSql) {
47 1 : const absl::StatusOr<FormatResult> result =
48 1 : FormatSqlText("select 1", FormatOptions{});
49 2 : ASSERT_TRUE(result.ok()) << result.status();
50 1 : EXPECT_NE(result->formatted_sql.find("SELECT"), std::string::npos);
51 1 : EXPECT_NE(result->formatted_sql.find("1"), std::string::npos);
52 1 : }
53 :
54 1 : TEST_F(SqlToolsTest, ParseValidSelectReturnsStatementKind) {
55 1 : const absl::StatusOr<ParseResult> result =
56 1 : ParseSqlText("SELECT 1", language_);
57 2 : ASSERT_TRUE(result.ok()) << result.status();
58 1 : EXPECT_TRUE(result->diagnostics.empty());
59 1 : ASSERT_EQ(result->statement_kinds.size(), 1u);
60 1 : EXPECT_EQ(result->statement_kinds[0], "QueryStatement");
61 1 : }
62 :
63 1 : TEST_F(SqlToolsTest, ParseInvalidSqlReturnsDiagnostic) {
64 1 : const absl::StatusOr<ParseResult> result = ParseSqlText("SELEC 1", language_);
65 2 : ASSERT_TRUE(result.ok()) << result.status();
66 1 : EXPECT_FALSE(result->diagnostics.empty());
67 1 : }
68 :
69 1 : TEST_F(SqlToolsTest, TokenizeSelectReturnsKeywords) {
70 1 : TokenizeOptions options;
71 1 : const absl::StatusOr<TokenizeResult> result =
72 1 : TokenizeSqlText("SELECT 1", language_, options);
73 2 : ASSERT_TRUE(result.ok()) << result.status();
74 1 : ASSERT_GE(result->tokens.size(), 2u);
75 1 : EXPECT_EQ(result->tokens[0].kind, "keyword");
76 1 : EXPECT_EQ(result->tokens[0].image, "SELECT");
77 1 : }
78 :
79 1 : TEST_F(SqlToolsTest, CompleteAfterSelectIncludesKeywords) {
80 1 : CatalogNames names;
81 1 : const std::string sql = "SELECT ";
82 1 : const absl::StatusOr<CompleteResult> result =
83 1 : CompleteSqlText(sql, sql.size(), language_, catalog_.get(), names, "");
84 2 : ASSERT_TRUE(result.ok()) << result.status();
85 1 : bool found_from = false;
86 142 : for (const CompletionCandidate& candidate : result->candidates) {
87 142 : if (candidate.label == "FROM") {
88 1 : found_from = true;
89 1 : break;
90 1 : }
91 142 : }
92 1 : EXPECT_TRUE(found_from);
93 1 : }
94 :
95 1 : TEST_F(SqlToolsTest, ParseInvalidSqlReturnsDiagnosticWithSpan) {
96 1 : const absl::StatusOr<ParseResult> result = ParseSqlText("SELEC 1", language_);
97 2 : ASSERT_TRUE(result.ok()) << result.status();
98 1 : ASSERT_FALSE(result->diagnostics.empty());
99 1 : const SqlDiagnostic& diag = result->diagnostics.front();
100 1 : EXPECT_GE(diag.start_byte, 0);
101 1 : EXPECT_GT(diag.end_byte, diag.start_byte);
102 1 : }
103 :
104 1 : TEST_F(SqlToolsTest, CompleteEmptyEditorAtCursorZero) {
105 1 : CatalogNames names;
106 1 : names.datasets = {"analytics"};
107 1 : const absl::StatusOr<CompleteResult> result =
108 1 : CompleteSqlText("", 0, language_, catalog_.get(), names, "analytics");
109 2 : ASSERT_TRUE(result.ok()) << result.status();
110 1 : bool found_select = false;
111 178 : for (const CompletionCandidate& candidate : result->candidates) {
112 178 : if (candidate.label == "SELECT") {
113 1 : found_select = true;
114 1 : break;
115 1 : }
116 178 : }
117 1 : EXPECT_TRUE(found_select);
118 1 : }
119 :
120 1 : TEST_F(SqlToolsTest, CompleteAfterFromUsesCatalogTables) {
121 1 : CatalogNames names;
122 1 : names.tables.push_back(
123 1 : CatalogTableEntry{"analytics.events", "p.analytics.events", "table", ""});
124 1 : names.tables.push_back(
125 1 : CatalogTableEntry{"events", "p.analytics.events", "table", ""});
126 1 : const std::string sql = "SELECT * FROM ev";
127 1 : const absl::StatusOr<CompleteResult> result = CompleteSqlText(
128 1 : sql, sql.size(), language_, catalog_.get(), names, "analytics");
129 2 : ASSERT_TRUE(result.ok()) << result.status();
130 1 : bool found_events = false;
131 1 : for (const CompletionCandidate& candidate : result->candidates) {
132 1 : if (candidate.label == "events") {
133 1 : found_events = true;
134 1 : break;
135 1 : }
136 1 : }
137 1 : EXPECT_TRUE(found_events);
138 1 : }
139 :
140 1 : TEST_F(SqlToolsTest, CompleteProjectQualifiedTableCandidate) {
141 1 : CatalogNames names;
142 1 : names.tables.push_back(
143 1 : CatalogTableEntry{"proj.ds.events", "proj.ds.events", "table", "table"});
144 1 : const std::string sql = "SELECT * FROM proj.d";
145 1 : const absl::StatusOr<CompleteResult> result =
146 1 : CompleteSqlText(sql, sql.size(), language_, catalog_.get(), names, "ds");
147 2 : ASSERT_TRUE(result.ok()) << result.status();
148 1 : bool found_fqn = false;
149 1 : for (const CompletionCandidate& candidate : result->candidates) {
150 1 : if (candidate.label == "proj.ds.events") {
151 1 : found_fqn = true;
152 1 : break;
153 1 : }
154 1 : }
155 1 : EXPECT_TRUE(found_fqn);
156 1 : }
157 :
158 1 : TEST_F(SqlToolsTest, CompleteIncompleteSqlUsesHeuristicColumns) {
159 1 : CatalogNames names;
160 1 : names.columns_by_table["analytics.events"] = {
161 1 : CatalogColumnEntry{"id", "INT64"},
162 1 : CatalogColumnEntry{"name", "STRING"},
163 1 : };
164 1 : PopulateInScopeTablesFromHeuristic(
165 1 : "SELECT na FROM analytics.events WHERE ", language_, "analytics", &names);
166 1 : ASSERT_EQ(names.in_scope_tables.size(), 1u);
167 1 : ASSERT_EQ(names.in_scope_tables[0].columns.size(), 2u);
168 :
169 1 : const std::string sql = "SELECT na FROM analytics.events WHERE ";
170 1 : const absl::StatusOr<CompleteResult> result = CompleteSqlText(
171 1 : sql, sql.size(), language_, catalog_.get(), names, "analytics");
172 2 : ASSERT_TRUE(result.ok()) << result.status();
173 1 : bool found_name = false;
174 437 : for (const CompletionCandidate& candidate : result->candidates) {
175 437 : if (candidate.label == "name") {
176 1 : found_name = true;
177 1 : EXPECT_EQ(candidate.kind, "column");
178 1 : break;
179 1 : }
180 437 : }
181 1 : EXPECT_TRUE(found_name);
182 1 : }
183 :
184 1 : TEST_F(SqlToolsTest, CompleteUserRoutineNotDuplicatedAsFunction) {
185 1 : AddScalarFunction("add_one");
186 1 : CatalogNames names;
187 1 : names.routines.push_back(CatalogRoutineEntry{"ds.add_one",
188 1 : "test-project.ds.add_one",
189 1 : "routine",
190 1 : "SQL scalar function"});
191 1 : names.routines.push_back(CatalogRoutineEntry{
192 1 : "add_one", "test-project.ds.add_one", "routine", "SQL scalar function"});
193 :
194 1 : const std::string sql = "SELECT add_";
195 1 : const absl::StatusOr<CompleteResult> result =
196 1 : CompleteSqlText(sql, sql.size(), language_, catalog_.get(), names, "ds");
197 2 : ASSERT_TRUE(result.ok()) << result.status();
198 :
199 1 : bool found_routine = false;
200 1 : bool found_function = false;
201 2 : for (const CompletionCandidate& candidate : result->candidates) {
202 2 : if (candidate.label == "add_one" && candidate.kind == "routine") {
203 1 : found_routine = true;
204 1 : EXPECT_EQ(candidate.fqn, "test-project.ds.add_one");
205 1 : EXPECT_EQ(candidate.insert_text, "add_one(");
206 1 : }
207 2 : if (candidate.label == "add_one" && candidate.kind == "function") {
208 0 : found_function = true;
209 0 : }
210 2 : }
211 1 : EXPECT_TRUE(found_routine);
212 1 : EXPECT_FALSE(found_function);
213 1 : }
214 :
215 1 : TEST_F(SqlToolsTest, CompleteRoutineCandidateIncludesFqn) {
216 1 : CatalogNames names;
217 1 : names.routines.push_back(CatalogRoutineEntry{
218 1 : "proj.ds.my_fn",
219 1 : "proj.ds.my_fn",
220 1 : "routine",
221 1 : "SQL scalar function",
222 1 : });
223 1 : const std::string sql = "CREATE FUNCTION ";
224 1 : const absl::StatusOr<CompleteResult> result =
225 1 : CompleteSqlText(sql, sql.size(), language_, catalog_.get(), names, "ds");
226 2 : ASSERT_TRUE(result.ok()) << result.status();
227 1 : bool found = false;
228 80 : for (const CompletionCandidate& candidate : result->candidates) {
229 80 : if (candidate.label == "proj.ds.my_fn") {
230 1 : found = true;
231 1 : EXPECT_EQ(candidate.kind, "routine");
232 1 : EXPECT_EQ(candidate.fqn, "proj.ds.my_fn");
233 1 : break;
234 1 : }
235 80 : }
236 1 : EXPECT_TRUE(found);
237 1 : }
238 :
239 : } // namespace
240 : } // namespace sqltools
241 : } // namespace backend
242 : } // namespace bigquery_emulator
|