Line data Source code
1 : #include <cstddef>
2 : #include <cstdint>
3 : #include <filesystem>
4 : #include <memory>
5 : #include <string>
6 : #include <utility>
7 : #include <vector>
8 :
9 : #include "absl/status/status.h"
10 : #include "absl/strings/str_cat.h"
11 : #include "absl/types/span.h"
12 : #include "backend/schema/schema.h"
13 : #include "backend/storage/duckdb/duckdb_storage.h"
14 : #include "backend/storage/duckdb/duckdb_storage_test_fixture.h"
15 : #include "backend/storage/storage.h"
16 : #include "gtest/gtest.h"
17 :
18 : namespace bigquery_emulator {
19 : namespace backend {
20 : namespace storage {
21 : namespace duckdb {
22 : namespace {
23 :
24 : namespace fs = std::filesystem;
25 :
26 1 : TEST_F(DuckDBStorageTest, RoundTripsHundredRowsAcrossRestart) {
27 1 : const DatasetId ds{"proj-1", "ds_1"};
28 1 : const TableId table{"proj-1", "ds_1", "people"};
29 :
30 : // ---------------- First process: write 100 rows. -----------------
31 1 : {
32 1 : auto store_or = DuckDBStorage::Open(data_dir_.string());
33 2 : ASSERT_TRUE(store_or.ok()) << store_or.status();
34 1 : auto& store = **store_or;
35 :
36 1 : ASSERT_TRUE(store.CreateDataset(ds, "US").ok());
37 1 : ASSERT_TRUE(store.CreateTable(table, PeopleSchema()).ok());
38 :
39 1 : std::vector<Row> rows;
40 1 : rows.reserve(100);
41 101 : for (int64_t i = 0; i < 100; ++i) {
42 100 : rows.push_back(MakePerson(i, absl::StrCat("person-", i)));
43 100 : }
44 1 : ASSERT_TRUE(store.AppendRows(table, absl::MakeConstSpan(rows)).ok());
45 :
46 : // Sanity check: in-process scan agrees with what we just wrote.
47 1 : auto iter_or = store.ScanRows(table);
48 1 : ASSERT_TRUE(iter_or.ok());
49 1 : int64_t count = 0;
50 1 : Row r;
51 101 : while (true) {
52 101 : auto has = (*iter_or)->Next(&r);
53 101 : ASSERT_TRUE(has.ok());
54 101 : if (!*has) break;
55 100 : ++count;
56 100 : }
57 1 : EXPECT_EQ(count, 100);
58 1 : }
59 :
60 : // The DuckDBStorage destructor closes the connection and the
61 : // catalog.duckdb file; reopening below mirrors a process restart.
62 1 : EXPECT_TRUE(fs::exists(data_dir_ / "catalog.duckdb"));
63 1 : EXPECT_TRUE(fs::exists(data_dir_ / "proj-1" / "ds_1" / "people.parquet"));
64 1 : EXPECT_TRUE(fs::exists(data_dir_ / "proj-1" / "ds_1" / "people.meta.json"));
65 :
66 : // ---------------- Second process: read 100 rows back. --------------
67 1 : {
68 1 : auto store_or = DuckDBStorage::Open(data_dir_.string());
69 2 : ASSERT_TRUE(store_or.ok()) << store_or.status();
70 1 : auto& store = **store_or;
71 :
72 1 : auto schema_or = store.GetSchema(table);
73 1 : ASSERT_TRUE(schema_or.ok());
74 1 : ASSERT_EQ(schema_or->columns.size(), 2u);
75 1 : EXPECT_EQ(schema_or->columns[0].name, "id");
76 1 : EXPECT_EQ(schema_or->columns[0].type, schema::ColumnType::kInt64);
77 1 : EXPECT_EQ(schema_or->columns[1].name, "name");
78 1 : EXPECT_EQ(schema_or->columns[1].type, schema::ColumnType::kString);
79 :
80 1 : auto iter_or = store.ScanRows(table);
81 1 : ASSERT_TRUE(iter_or.ok());
82 1 : std::vector<Row> scanned;
83 1 : Row r;
84 101 : while (true) {
85 101 : auto has = (*iter_or)->Next(&r);
86 101 : ASSERT_TRUE(has.ok());
87 101 : if (!*has) break;
88 100 : scanned.push_back(r);
89 100 : }
90 1 : ASSERT_EQ(scanned.size(), 100u);
91 :
92 : // The parquet file does not have a guaranteed order (we did not
93 : // ORDER BY), so build a set of seen ids and confirm we got
94 : // exactly 0..99 with the matching name string.
95 1 : std::vector<bool> seen(100, false);
96 100 : for (const auto& row : scanned) {
97 100 : ASSERT_EQ(row.cells.size(), 2u);
98 100 : const int64_t id = row.cells[0].int64_value();
99 100 : ASSERT_GE(id, 0);
100 100 : ASSERT_LT(id, 100);
101 200 : EXPECT_FALSE(seen[id]) << "duplicate row for id " << id;
102 100 : seen[id] = true;
103 100 : EXPECT_EQ(row.cells[1].string_value(), absl::StrCat("person-", id));
104 100 : }
105 101 : for (size_t i = 0; i < seen.size(); ++i) {
106 200 : EXPECT_TRUE(seen[i]) << "missing row for id " << i;
107 100 : }
108 1 : }
109 1 : }
110 :
111 1 : TEST_F(DuckDBStorageTest, CreateTableMaterializesEmptyParquet) {
112 1 : auto store_or = DuckDBStorage::Open(data_dir_.string());
113 2 : ASSERT_TRUE(store_or.ok()) << store_or.status();
114 1 : auto& store = **store_or;
115 :
116 1 : const DatasetId ds{"proj-1", "ds_1"};
117 1 : const TableId table{"proj-1", "ds_1", "people"};
118 1 : ASSERT_TRUE(store.CreateDataset(ds, "US").ok());
119 1 : ASSERT_TRUE(store.CreateTable(table, PeopleSchema()).ok());
120 :
121 1 : const fs::path parquet = data_dir_ / "proj-1" / "ds_1" / "people.parquet";
122 1 : ASSERT_TRUE(fs::exists(parquet));
123 1 : EXPECT_GT(fs::file_size(parquet), 0u);
124 :
125 1 : auto iter_or = store.ScanRows(table);
126 1 : ASSERT_TRUE(iter_or.ok());
127 1 : Row r;
128 1 : auto has = (*iter_or)->Next(&r);
129 1 : ASSERT_TRUE(has.ok());
130 1 : EXPECT_FALSE(*has);
131 1 : }
132 :
133 : // Regression: thirdparty:node-bigquery-tests view/query/delete-table
134 : // `before all` hooks failed with `CREATE OR REPLACE TEMP TABLE
135 : // main.__bqemu_mkempty (): Parser Error: Table must have at least
136 : // one column!` whenever the gateway registered a view or other
137 : // schema-less table through CreateTable. The fix short-circuits the
138 : // DuckDB scratch path for empty schemas; this test pins it.
139 1 : TEST_F(DuckDBStorageTest, CreateTableWithEmptySchemaSkipsParquet) {
140 1 : auto store_or = DuckDBStorage::Open(data_dir_.string());
141 2 : ASSERT_TRUE(store_or.ok()) << store_or.status();
142 1 : auto& store = **store_or;
143 :
144 1 : const DatasetId ds{"proj-1", "ds_1"};
145 1 : const TableId table{"proj-1", "ds_1", "the_view"};
146 1 : ASSERT_TRUE(store.CreateDataset(ds, "US").ok());
147 :
148 1 : schema::TableSchema empty;
149 1 : ASSERT_TRUE(empty.columns.empty());
150 1 : ASSERT_TRUE(store.CreateTable(table, empty).ok());
151 :
152 1 : const fs::path sidecar = data_dir_ / "proj-1" / "ds_1" / "the_view.meta.json";
153 1 : const fs::path parquet = data_dir_ / "proj-1" / "ds_1" / "the_view.parquet";
154 1 : EXPECT_TRUE(fs::exists(sidecar));
155 1 : EXPECT_FALSE(fs::exists(parquet));
156 :
157 1 : auto schema_or = store.GetSchema(table);
158 2 : ASSERT_TRUE(schema_or.ok()) << schema_or.status();
159 1 : EXPECT_TRUE(schema_or->columns.empty());
160 1 : }
161 :
162 : // Regression: CREATE TABLE with a STRUCT column must not crash the
163 : // engine while materializing the empty parquet scratch table.
164 1 : TEST_F(DuckDBStorageTest, CreateTableWithStructColumnMaterializesParquet) {
165 1 : auto store_or = DuckDBStorage::Open(data_dir_.string());
166 2 : ASSERT_TRUE(store_or.ok()) << store_or.status();
167 1 : auto& store = **store_or;
168 :
169 1 : const DatasetId ds{"proj-1", "ds_1"};
170 1 : const TableId table{"proj-1", "ds_1", "infostruct"};
171 1 : ASSERT_TRUE(store.CreateDataset(ds, "US").ok());
172 :
173 1 : schema::TableSchema schema;
174 1 : schema::ColumnSchema k;
175 1 : k.name = "k";
176 1 : k.type = schema::ColumnType::kInt64;
177 1 : schema::ColumnSchema s;
178 1 : s.name = "s";
179 1 : s.type = schema::ColumnType::kStruct;
180 1 : schema::ColumnSchema a;
181 1 : a.name = "a";
182 1 : a.type = schema::ColumnType::kInt64;
183 1 : schema::ColumnSchema b;
184 1 : b.name = "b";
185 1 : b.type = schema::ColumnType::kString;
186 1 : s.fields = {a, b};
187 1 : schema.columns = {k, s};
188 :
189 1 : ASSERT_TRUE(store.CreateTable(table, schema).ok());
190 :
191 1 : const fs::path parquet = data_dir_ / "proj-1" / "ds_1" / "infostruct.parquet";
192 1 : ASSERT_TRUE(fs::exists(parquet));
193 1 : EXPECT_GT(fs::file_size(parquet), 0u);
194 :
195 1 : auto schema_or = store.GetSchema(table);
196 2 : ASSERT_TRUE(schema_or.ok()) << schema_or.status();
197 1 : ASSERT_EQ(schema_or->columns.size(), 2u);
198 1 : EXPECT_EQ(schema_or->columns[1].type, schema::ColumnType::kStruct);
199 1 : ASSERT_EQ(schema_or->columns[1].fields.size(), 2u);
200 :
201 1 : auto iter_or = store.ScanRows(table);
202 2 : ASSERT_TRUE(iter_or.ok()) << iter_or.status();
203 1 : Row r;
204 1 : auto has = (*iter_or)->Next(&r);
205 1 : ASSERT_TRUE(has.ok());
206 1 : EXPECT_FALSE(*has);
207 1 : }
208 :
209 1 : TEST_F(DuckDBStorageTest, ListDatasetsReturnsSortedIdsForProject) {
210 1 : auto store_or = DuckDBStorage::Open(data_dir_.string());
211 2 : ASSERT_TRUE(store_or.ok()) << store_or.status();
212 1 : auto& store = **store_or;
213 :
214 1 : ASSERT_TRUE(store.CreateDataset({"proj-a", "ds_charlie"}, "US").ok());
215 1 : ASSERT_TRUE(store.CreateDataset({"proj-a", "ds_alpha"}, "US").ok());
216 1 : ASSERT_TRUE(store.CreateDataset({"proj-a", "ds_bravo"}, "US").ok());
217 1 : ASSERT_TRUE(store.CreateDataset({"proj-other", "ds_zulu"}, "US").ok());
218 :
219 1 : auto list_a_or = store.ListDatasets("proj-a");
220 2 : ASSERT_TRUE(list_a_or.ok()) << list_a_or.status();
221 1 : ASSERT_EQ(list_a_or->size(), 3u);
222 1 : EXPECT_EQ((*list_a_or)[0].dataset_id, "ds_alpha");
223 1 : EXPECT_EQ((*list_a_or)[1].dataset_id, "ds_bravo");
224 1 : EXPECT_EQ((*list_a_or)[2].dataset_id, "ds_charlie");
225 3 : for (const auto& id : *list_a_or) {
226 3 : EXPECT_EQ(id.project_id, "proj-a");
227 3 : }
228 :
229 1 : auto list_other_or = store.ListDatasets("proj-other");
230 1 : ASSERT_TRUE(list_other_or.ok());
231 1 : ASSERT_EQ(list_other_or->size(), 1u);
232 1 : EXPECT_EQ((*list_other_or)[0].dataset_id, "ds_zulu");
233 :
234 : // Unknown project: empty vector, not NOT_FOUND. The gateway treats
235 : // "no datasets in this project" and "this project has nothing yet"
236 : // the same way (live BigQuery returns 200 with an empty `datasets`
237 : // array for both).
238 1 : auto list_unknown_or = store.ListDatasets("proj-missing");
239 2 : ASSERT_TRUE(list_unknown_or.ok()) << list_unknown_or.status();
240 1 : EXPECT_TRUE(list_unknown_or->empty());
241 1 : }
242 :
243 1 : TEST_F(DuckDBStorageTest, ListDatasetsRejectsEmptyProjectID) {
244 1 : auto store_or = DuckDBStorage::Open(data_dir_.string());
245 1 : ASSERT_TRUE(store_or.ok());
246 1 : auto& store = **store_or;
247 :
248 1 : auto list_or = store.ListDatasets("");
249 1 : ASSERT_FALSE(list_or.ok());
250 1 : EXPECT_EQ(list_or.status().code(), absl::StatusCode::kInvalidArgument);
251 1 : }
252 :
253 1 : TEST_F(DuckDBStorageTest, ListTablesReturnsSortedIdsForDataset) {
254 1 : auto store_or = DuckDBStorage::Open(data_dir_.string());
255 2 : ASSERT_TRUE(store_or.ok()) << store_or.status();
256 1 : auto& store = **store_or;
257 :
258 1 : const DatasetId ds{"proj-1", "ds_1"};
259 1 : ASSERT_TRUE(store.CreateDataset(ds, "US").ok());
260 1 : ASSERT_TRUE(
261 1 : store.CreateTable({"proj-1", "ds_1", "charlie"}, PeopleSchema()).ok());
262 1 : ASSERT_TRUE(
263 1 : store.CreateTable({"proj-1", "ds_1", "alpha"}, PeopleSchema()).ok());
264 : // Empty-schema entries (views; see the empty-schema regression test)
265 : // must still surface in ListTables. The sidecar is the canonical
266 : // existence marker.
267 1 : schema::TableSchema empty;
268 1 : ASSERT_TRUE(store.CreateTable({"proj-1", "ds_1", "bravo_view"}, empty).ok());
269 :
270 1 : auto list_or = store.ListTables(ds);
271 2 : ASSERT_TRUE(list_or.ok()) << list_or.status();
272 1 : ASSERT_EQ(list_or->size(), 3u);
273 1 : EXPECT_EQ((*list_or)[0].table_id, "alpha");
274 1 : EXPECT_EQ((*list_or)[1].table_id, "bravo_view");
275 1 : EXPECT_EQ((*list_or)[2].table_id, "charlie");
276 3 : for (const auto& id : *list_or) {
277 3 : EXPECT_EQ(id.project_id, "proj-1");
278 3 : EXPECT_EQ(id.dataset_id, "ds_1");
279 3 : }
280 1 : }
281 :
282 1 : TEST_F(DuckDBStorageTest, ListTablesOnMissingDatasetIsNotFound) {
283 1 : auto store_or = DuckDBStorage::Open(data_dir_.string());
284 1 : ASSERT_TRUE(store_or.ok());
285 1 : auto& store = **store_or;
286 :
287 1 : auto list_or = store.ListTables({"proj-x", "ds_missing"});
288 1 : ASSERT_FALSE(list_or.ok());
289 1 : EXPECT_EQ(list_or.status().code(), absl::StatusCode::kNotFound);
290 1 : }
291 :
292 1 : TEST_F(DuckDBStorageTest, ListTablesOnEmptyDatasetIsEmpty) {
293 1 : auto store_or = DuckDBStorage::Open(data_dir_.string());
294 1 : ASSERT_TRUE(store_or.ok());
295 1 : auto& store = **store_or;
296 :
297 1 : const DatasetId ds{"proj-1", "ds_empty"};
298 1 : ASSERT_TRUE(store.CreateDataset(ds, "US").ok());
299 :
300 1 : auto list_or = store.ListTables(ds);
301 2 : ASSERT_TRUE(list_or.ok()) << list_or.status();
302 1 : EXPECT_TRUE(list_or->empty());
303 1 : }
304 :
305 1 : TEST_F(DuckDBStorageTest, DropTableRemovesParquetAndSidecar) {
306 1 : auto store_or = DuckDBStorage::Open(data_dir_.string());
307 2 : ASSERT_TRUE(store_or.ok()) << store_or.status();
308 1 : auto& store = **store_or;
309 :
310 1 : const DatasetId ds{"proj-1", "ds_1"};
311 1 : const TableId table{"proj-1", "ds_1", "people"};
312 1 : ASSERT_TRUE(store.CreateDataset(ds, "US").ok());
313 1 : ASSERT_TRUE(store.CreateTable(table, PeopleSchema()).ok());
314 :
315 1 : const fs::path parquet = data_dir_ / "proj-1" / "ds_1" / "people.parquet";
316 1 : const fs::path sidecar = data_dir_ / "proj-1" / "ds_1" / "people.meta.json";
317 1 : ASSERT_TRUE(fs::exists(parquet));
318 1 : ASSERT_TRUE(fs::exists(sidecar));
319 :
320 1 : ASSERT_TRUE(store.DropTable(table).ok());
321 1 : EXPECT_FALSE(fs::exists(parquet));
322 1 : EXPECT_FALSE(fs::exists(sidecar));
323 1 : }
324 :
325 1 : TEST_F(DuckDBStorageTest, AppendRowsRejectsMisshapenBatch) {
326 1 : auto store_or = DuckDBStorage::Open(data_dir_.string());
327 2 : ASSERT_TRUE(store_or.ok()) << store_or.status();
328 1 : auto& store = **store_or;
329 :
330 1 : const DatasetId ds{"proj-1", "ds_1"};
331 1 : const TableId table{"proj-1", "ds_1", "people"};
332 1 : ASSERT_TRUE(store.CreateDataset(ds, "US").ok());
333 1 : ASSERT_TRUE(store.CreateTable(table, PeopleSchema()).ok());
334 :
335 1 : std::vector<Row> rows;
336 1 : rows.push_back(MakePerson(1, "ada"));
337 1 : Row malformed;
338 1 : malformed.cells = {Value::Int64(2)};
339 1 : rows.push_back(std::move(malformed));
340 :
341 1 : auto status = store.AppendRows(table, absl::MakeConstSpan(rows));
342 1 : EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
343 :
344 : // Misshapen batch must not have leaked the good row at index 0.
345 1 : auto iter_or = store.ScanRows(table);
346 1 : ASSERT_TRUE(iter_or.ok());
347 1 : Row r;
348 1 : auto has = (*iter_or)->Next(&r);
349 1 : ASSERT_TRUE(has.ok());
350 1 : EXPECT_FALSE(*has);
351 1 : }
352 :
353 1 : TEST_F(DuckDBStorageTest, AppendRowsAppendsAcrossMultipleBatches) {
354 1 : auto store_or = DuckDBStorage::Open(data_dir_.string());
355 2 : ASSERT_TRUE(store_or.ok()) << store_or.status();
356 1 : auto& store = **store_or;
357 :
358 1 : const DatasetId ds{"proj-1", "ds_1"};
359 1 : const TableId table{"proj-1", "ds_1", "people"};
360 1 : ASSERT_TRUE(store.CreateDataset(ds, "US").ok());
361 1 : ASSERT_TRUE(store.CreateTable(table, PeopleSchema()).ok());
362 :
363 1 : std::vector<Row> first;
364 1 : first.push_back(MakePerson(1, "ada"));
365 1 : first.push_back(MakePerson(2, "linus"));
366 1 : ASSERT_TRUE(store.AppendRows(table, absl::MakeConstSpan(first)).ok());
367 :
368 1 : std::vector<Row> second;
369 1 : second.push_back(MakePerson(3, "grace"));
370 1 : ASSERT_TRUE(store.AppendRows(table, absl::MakeConstSpan(second)).ok());
371 :
372 1 : auto iter_or = store.ScanRows(table);
373 1 : ASSERT_TRUE(iter_or.ok());
374 1 : std::vector<Row> scanned;
375 1 : Row r;
376 4 : while (true) {
377 4 : auto has = (*iter_or)->Next(&r);
378 4 : ASSERT_TRUE(has.ok());
379 4 : if (!*has) break;
380 3 : scanned.push_back(r);
381 3 : }
382 1 : EXPECT_EQ(scanned.size(), 3u);
383 1 : }
384 :
385 1 : TEST_F(DuckDBStorageTest, UpsertViewWritesSidecarAndListsInDataset) {
386 1 : auto store_or = DuckDBStorage::Open(data_dir_.string());
387 2 : ASSERT_TRUE(store_or.ok()) << store_or.status();
388 1 : auto& store = **store_or;
389 :
390 1 : const DatasetId ds{"proj-1", "ds_1"};
391 1 : const ViewId view_id{"proj-1", "ds_1", "my_view"};
392 1 : ASSERT_TRUE(store.CreateDataset(ds, "US").ok());
393 :
394 1 : ViewRecord rec;
395 1 : rec.id = view_id;
396 1 : rec.ddl_sql = "CREATE OR REPLACE VIEW `proj-1.ds_1.my_view` AS SELECT 1 AS x";
397 1 : rec.view_query = "SELECT 1 AS x";
398 1 : ASSERT_TRUE(store.UpsertView(rec).ok());
399 :
400 1 : const fs::path sidecar = data_dir_ / "proj-1" / "ds_1" / "my_view.meta.json";
401 1 : EXPECT_TRUE(fs::exists(sidecar));
402 :
403 1 : auto list_or = store.ListTables(ds);
404 2 : ASSERT_TRUE(list_or.ok()) << list_or.status();
405 1 : ASSERT_EQ(list_or->size(), 1u);
406 1 : EXPECT_EQ((*list_or)[0].table_id, "my_view");
407 :
408 1 : auto info_or =
409 1 : store.GetTableResourceInfo(TableId{"proj-1", "ds_1", "my_view"});
410 2 : ASSERT_TRUE(info_or.ok()) << info_or.status();
411 1 : EXPECT_EQ(info_or->table_type, "VIEW");
412 1 : EXPECT_EQ(info_or->view_query, "SELECT 1 AS x");
413 :
414 : // View sidecars exist for REST listing and restart rehydration; they
415 : // carry no physical column schema. Catalog resolution must consult the
416 : // in-memory view registry instead of materializing an empty table.
417 1 : auto schema_or = store.GetSchema(TableId{"proj-1", "ds_1", "my_view"});
418 1 : EXPECT_FALSE(schema_or.ok());
419 2 : EXPECT_TRUE(absl::IsNotFound(schema_or.status())) << schema_or.status();
420 :
421 1 : ASSERT_TRUE(store.DeleteView(view_id).ok());
422 1 : EXPECT_FALSE(fs::exists(sidecar));
423 1 : list_or = store.ListTables(ds);
424 1 : ASSERT_TRUE(list_or.ok());
425 1 : EXPECT_TRUE(list_or->empty());
426 1 : }
427 :
428 1 : TEST_F(DuckDBStorageTest, UpsertViewSurvivesReopenAndListAllViews) {
429 1 : const DatasetId ds{"proj-1", "ds_1"};
430 1 : const ViewId view_id{"proj-1", "ds_1", "persisted_view"};
431 1 : ViewRecord rec;
432 1 : rec.id = view_id;
433 1 : rec.ddl_sql =
434 1 : "CREATE OR REPLACE VIEW `proj-1.ds_1.persisted_view` AS SELECT 42 AS "
435 1 : "answer";
436 1 : rec.view_query = "SELECT 42 AS answer";
437 :
438 1 : {
439 1 : auto store_or = DuckDBStorage::Open(data_dir_.string());
440 2 : ASSERT_TRUE(store_or.ok()) << store_or.status();
441 1 : auto& store = **store_or;
442 1 : ASSERT_TRUE(store.CreateDataset(ds, "US").ok());
443 1 : ASSERT_TRUE(store.UpsertView(rec).ok());
444 1 : }
445 :
446 1 : {
447 1 : auto store_or = DuckDBStorage::Open(data_dir_.string());
448 2 : ASSERT_TRUE(store_or.ok()) << store_or.status();
449 1 : auto& store = **store_or;
450 1 : auto views_or = store.ListAllViews();
451 2 : ASSERT_TRUE(views_or.ok()) << views_or.status();
452 1 : ASSERT_EQ(views_or->size(), 1u);
453 1 : EXPECT_EQ((*views_or)[0].id.view_id, "persisted_view");
454 1 : EXPECT_EQ((*views_or)[0].view_query, "SELECT 42 AS answer");
455 1 : }
456 1 : }
457 :
458 : } // namespace
459 : } // namespace duckdb
460 : } // namespace storage
461 : } // namespace backend
462 : } // namespace bigquery_emulator
|