LCOV - code coverage report
Current view: top level - backend/sqltools - sql_tools_test.cc (source / functions) Coverage Total Hit
Test: _coverage_report.dat Lines: 99.0 % 198 196
Test Date: 2026-07-02 21:01:18 Functions: 100.0 % 14 14

            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
        

Generated by: LCOV version 2.0-1