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

            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
        

Generated by: LCOV version 2.0-1