Line data Source code
1 : #include "backend/sqltools/catalog_names.h"
2 :
3 : #include <map>
4 : #include <string>
5 : #include <vector>
6 :
7 : #include "absl/status/status.h"
8 : #include "absl/status/statusor.h"
9 : #include "absl/strings/str_cat.h"
10 : #include "absl/types/span.h"
11 : #include "backend/schema/schema.h"
12 : #include "backend/storage/storage.h"
13 : #include "gtest/gtest.h"
14 :
15 : namespace bigquery_emulator {
16 : namespace backend {
17 : namespace sqltools {
18 : namespace {
19 :
20 : class FakeCatalogStorage : public storage::Storage {
21 : public:
22 4 : void AddDataset(absl::string_view project_id, absl::string_view dataset_id) {
23 4 : datasets_[std::string(project_id)].push_back(
24 4 : storage::DatasetId{std::string(project_id), std::string(dataset_id)});
25 4 : }
26 :
27 2 : void AddTable(const storage::TableId& id, schema::TableSchema schema) {
28 2 : tables_[id.dataset_id].push_back(id);
29 2 : schemas_[TableKey(id)] = std::move(schema);
30 2 : }
31 :
32 : void AddRoutine(const storage::DatasetId& dataset,
33 1 : storage::RoutineRecord routine) {
34 1 : routines_[dataset.dataset_id].push_back(std::move(routine));
35 1 : }
36 :
37 : absl::Status CreateDataset(const storage::DatasetId&,
38 0 : absl::string_view) override {
39 0 : return absl::UnimplementedError("FakeCatalogStorage::CreateDataset");
40 0 : }
41 : absl::Status DropDataset(const storage::DatasetId&,
42 : bool,
43 0 : absl::string_view = {}) override {
44 0 : return absl::UnimplementedError("FakeCatalogStorage::DropDataset");
45 0 : }
46 : absl::Status CreateTable(const storage::TableId&,
47 0 : const schema::TableSchema&) override {
48 0 : return absl::UnimplementedError("FakeCatalogStorage::CreateTable");
49 0 : }
50 0 : absl::Status DropTable(const storage::TableId&) override {
51 0 : return absl::UnimplementedError("FakeCatalogStorage::DropTable");
52 0 : }
53 : absl::StatusOr<std::vector<storage::DatasetId>> ListDatasets(
54 2 : absl::string_view project_id) const override {
55 2 : auto it = datasets_.find(std::string(project_id));
56 2 : if (it == datasets_.end()) {
57 0 : return std::vector<storage::DatasetId>{};
58 0 : }
59 2 : return it->second;
60 2 : }
61 : absl::StatusOr<std::vector<storage::TableId>> ListTables(
62 3 : const storage::DatasetId& dataset) const override {
63 3 : auto it = tables_.find(dataset.dataset_id);
64 3 : if (it == tables_.end()) {
65 1 : return std::vector<storage::TableId>{};
66 1 : }
67 2 : return it->second;
68 3 : }
69 : absl::StatusOr<schema::TableSchema> GetSchema(
70 2 : const storage::TableId& id) const override {
71 2 : auto it = schemas_.find(TableKey(id));
72 2 : if (it == schemas_.end()) {
73 0 : return absl::NotFoundError("schema not found");
74 0 : }
75 2 : return it->second;
76 2 : }
77 : absl::Status AppendRows(const storage::TableId&,
78 0 : absl::Span<const storage::Row>) override {
79 0 : return absl::UnimplementedError("FakeCatalogStorage::AppendRows");
80 0 : }
81 : absl::Status OverwriteRows(const storage::TableId&,
82 0 : absl::Span<const storage::Row>) override {
83 0 : return absl::UnimplementedError("FakeCatalogStorage::OverwriteRows");
84 0 : }
85 : absl::StatusOr<std::unique_ptr<storage::RowIterator>> ScanRows(
86 0 : const storage::TableId&) const override {
87 0 : return absl::UnimplementedError("FakeCatalogStorage::ScanRows");
88 0 : }
89 : absl::StatusOr<std::unique_ptr<storage::RowIterator>> CreateReadStream(
90 0 : const storage::TableId&, const storage::ReadFilter&) const override {
91 0 : return absl::UnimplementedError("FakeCatalogStorage::CreateReadStream");
92 0 : }
93 : absl::StatusOr<std::int64_t> CountRows(
94 0 : const storage::TableId&) const override {
95 0 : return absl::UnimplementedError("FakeCatalogStorage::CountRows");
96 0 : }
97 0 : absl::Status UpsertRoutine(const storage::RoutineRecord&) override {
98 0 : return absl::UnimplementedError("FakeCatalogStorage::UpsertRoutine");
99 0 : }
100 0 : absl::Status DeleteRoutine(const storage::RoutineId&) override {
101 0 : return absl::UnimplementedError("FakeCatalogStorage::DeleteRoutine");
102 0 : }
103 : absl::StatusOr<storage::RoutineRecord> GetRoutine(
104 0 : const storage::RoutineId&) const override {
105 0 : return absl::UnimplementedError("FakeCatalogStorage::GetRoutine");
106 0 : }
107 : absl::StatusOr<std::vector<storage::RoutineRecord>> ListRoutines(
108 3 : const storage::DatasetId& dataset) const override {
109 3 : auto it = routines_.find(dataset.dataset_id);
110 3 : if (it == routines_.end()) {
111 2 : return std::vector<storage::RoutineRecord>{};
112 2 : }
113 1 : return it->second;
114 3 : }
115 : absl::StatusOr<std::vector<storage::RoutineRecord>> ListAllRoutines()
116 0 : const override {
117 0 : return absl::UnimplementedError("FakeCatalogStorage::ListAllRoutines");
118 0 : }
119 0 : absl::Status UpsertView(const storage::ViewRecord&) override {
120 0 : return absl::UnimplementedError("FakeCatalogStorage::UpsertView");
121 0 : }
122 0 : absl::Status DeleteView(const storage::ViewId&) override {
123 0 : return absl::UnimplementedError("FakeCatalogStorage::DeleteView");
124 0 : }
125 : absl::StatusOr<std::vector<storage::ViewRecord>> ListAllViews()
126 0 : const override {
127 0 : return absl::UnimplementedError("FakeCatalogStorage::ListAllViews");
128 0 : }
129 :
130 : private:
131 4 : static std::string TableKey(const storage::TableId& id) {
132 4 : return absl::StrCat(id.project_id, "/", id.dataset_id, "/", id.table_id);
133 4 : }
134 :
135 : std::map<std::string, std::vector<storage::DatasetId>> datasets_{};
136 : std::map<std::string, std::vector<storage::TableId>> tables_{};
137 : std::map<std::string, schema::TableSchema> schemas_{};
138 : std::map<std::string, std::vector<storage::RoutineRecord>> routines_{};
139 : };
140 :
141 1 : TEST(CatalogNamesTest, PopulateFromStorageIncludesTablesColumnsAndRoutines) {
142 1 : FakeCatalogStorage storage;
143 1 : storage.AddDataset("proj", "ds");
144 1 : storage.AddDataset("proj", "other");
145 :
146 1 : const storage::TableId table_id{"proj", "ds", "events"};
147 1 : schema::TableSchema schema;
148 1 : schema.columns = {
149 1 : schema::ColumnSchema{.name = "id", .type = schema::ColumnType::kInt64},
150 1 : schema::ColumnSchema{.name = "name", .type = schema::ColumnType::kString},
151 1 : };
152 1 : storage.AddTable(table_id, schema);
153 :
154 1 : storage::RoutineRecord routine;
155 1 : routine.id = storage::RoutineId{"proj", "ds", "my_fn"};
156 1 : routine.language = "SQL";
157 1 : storage.AddRoutine(storage::DatasetId{"proj", "ds"}, routine);
158 :
159 1 : CatalogNames names;
160 1 : ASSERT_TRUE(
161 1 : PopulateCatalogNamesFromStorage("proj", "ds", &storage, &names).ok());
162 :
163 1 : ASSERT_EQ(names.datasets,
164 1 : (std::vector<std::string>{"ds", "proj.ds", "other", "proj.other"}));
165 :
166 1 : ASSERT_EQ(names.tables.size(), 3u);
167 1 : EXPECT_EQ(names.tables[0].label, "ds.events");
168 1 : EXPECT_EQ(names.tables[0].fqn, "proj.ds.events");
169 1 : EXPECT_EQ(names.tables[0].kind, "table");
170 1 : EXPECT_EQ(names.tables[1].label, "proj.ds.events");
171 1 : EXPECT_EQ(names.tables[1].fqn, "proj.ds.events");
172 1 : EXPECT_EQ(names.tables[2].label, "events");
173 1 : EXPECT_EQ(names.tables[2].fqn, "proj.ds.events");
174 :
175 1 : ASSERT_EQ(names.routines.size(), 3u);
176 1 : EXPECT_EQ(names.routines[0].label, "ds.my_fn");
177 1 : EXPECT_EQ(names.routines[0].detail, "SQL scalar function");
178 1 : EXPECT_EQ(names.routines[0].kind, "routine");
179 1 : EXPECT_EQ(names.routines[1].label, "proj.ds.my_fn");
180 1 : EXPECT_EQ(names.routines[2].label, "my_fn");
181 :
182 1 : ASSERT_EQ(names.columns.size(), 2u);
183 1 : EXPECT_EQ(names.columns[0].name, "id");
184 1 : EXPECT_EQ(names.columns[0].type, "INT64");
185 1 : EXPECT_EQ(names.columns[1].name, "name");
186 1 : EXPECT_EQ(names.columns[1].type, "STRING");
187 :
188 1 : const auto qualified = names.columns_by_table.find("ds.events");
189 1 : ASSERT_NE(qualified, names.columns_by_table.end());
190 1 : EXPECT_EQ(qualified->second.size(), 2u);
191 :
192 1 : const auto unqualified = names.columns_by_table.find("events");
193 1 : ASSERT_NE(unqualified, names.columns_by_table.end());
194 1 : EXPECT_EQ(unqualified->second.size(), 2u);
195 1 : }
196 :
197 1 : TEST(CatalogNamesTest, PopulateFromStorageIncludesProjectQualifiedNames) {
198 1 : FakeCatalogStorage storage;
199 1 : storage.AddDataset("proj", "ds");
200 :
201 1 : const storage::TableId table_id{"proj", "ds", "events"};
202 1 : schema::TableSchema schema;
203 1 : schema.columns = {
204 1 : schema::ColumnSchema{.name = "id", .type = schema::ColumnType::kInt64},
205 1 : };
206 1 : storage.AddTable(table_id, schema);
207 :
208 1 : CatalogNames names;
209 1 : ASSERT_TRUE(
210 1 : PopulateCatalogNamesFromStorage("proj", "", &storage, &names).ok());
211 :
212 1 : bool found_fqn_table = false;
213 1 : bool found_fqn_dataset = false;
214 2 : for (const CatalogTableEntry& table : names.tables) {
215 2 : if (table.label == "proj.ds.events") {
216 1 : found_fqn_table = true;
217 1 : }
218 2 : }
219 2 : for (const std::string& dataset : names.datasets) {
220 2 : if (dataset == "proj.ds") {
221 1 : found_fqn_dataset = true;
222 1 : }
223 2 : }
224 1 : EXPECT_TRUE(found_fqn_table);
225 1 : EXPECT_TRUE(found_fqn_dataset);
226 1 : }
227 :
228 1 : TEST(CatalogNamesTest, RejectsNullStorage) {
229 1 : CatalogNames names;
230 1 : EXPECT_FALSE(
231 1 : PopulateCatalogNamesFromStorage("proj", "ds", nullptr, &names).ok());
232 1 : }
233 :
234 1 : TEST(CatalogNamesTest, RejectsNullNames) {
235 1 : FakeCatalogStorage storage;
236 1 : storage.AddDataset("proj", "ds");
237 1 : EXPECT_FALSE(
238 1 : PopulateCatalogNamesFromStorage("proj", "ds", &storage, nullptr).ok());
239 1 : }
240 :
241 : } // namespace
242 : } // namespace sqltools
243 : } // namespace backend
244 : } // namespace bigquery_emulator
|