From c164b67dd0e2558677ac41a6abe19fcdee368092 Mon Sep 17 00:00:00 2001 From: harehare Date: Wed, 10 Jun 2026 22:59:35 +0900 Subject: [PATCH] feat(storage): persist custom tables to catalog storage --- Cargo.lock | 29 ++++--- src/sql.rs | 92 +++++++++++---------- src/storage.rs | 60 ++++++++++++-- src/storage/catalog.rs | 90 +++++++++++++++++++-- src/store.rs | 180 ++++++++++++++++++++++++++++------------- 5 files changed, 336 insertions(+), 115 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1189406..b4d9ef8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1396,7 +1396,7 @@ dependencies = [ [[package]] name = "mq-db" -version = "0.1.1" +version = "0.1.2" dependencies = [ "anyhow", "axum", @@ -1418,9 +1418,9 @@ dependencies = [ [[package]] name = "mq-lang" -version = "0.5.31" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b51f7340c495c7d3327bec83c45ea26335dae91acb1392cc45ce2fcc2130194" +checksum = "945450433662b786c8ce51f19ab70891dc442da43d39967c51537927e7f7305a" dependencies = [ "base64", "chrono", @@ -1450,14 +1450,15 @@ dependencies = [ "toml", "toon-format", "url", + "web-sys", "yaml-rust2", ] [[package]] name = "mq-macros" -version = "0.5.31" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d749dc5c73bcce7a6a978636aef01de4700e0e790d88b5a514ae2d5df393cb77" +checksum = "18f40d74822fe55553384f385363530300ea41ceadd598baf68fe0373bbb1fae" dependencies = [ "proc-macro2", "quote", @@ -1466,9 +1467,9 @@ dependencies = [ [[package]] name = "mq-markdown" -version = "0.5.31" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc96ff6f0490a589fae25145cbc61b883c7fd2d4b042f545eab954c37ed3a167" +checksum = "791c374af833381678f62652884282f70043110e35b8a4a9f76c72cefca2def2" dependencies = [ "ego-tree", "itertools 0.14.0", @@ -2419,9 +2420,9 @@ checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "toon-format" -version = "0.4.6" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1dae994fe9adfb44bdc74fc17546651604a59af32136256515bc1218850832" +checksum = "8f89570c1a68d73941f728cca32a4345b2ffca36667ad921af336c60309a3e7e" dependencies = [ "indexmap", "serde", @@ -2690,6 +2691,16 @@ dependencies = [ "semver", ] +[[package]] +name = "web-sys" +version = "0.3.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "web_atoms" version = "0.2.4" diff --git a/src/sql.rs b/src/sql.rs index cf4a819..fb559b9 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -1007,6 +1007,7 @@ impl<'a> SqlEngine<'a> { .write() .unwrap() .insert(table_name, (result.columns, result.rows)); + self.store.try_flush_catalog_to_storage(); return Ok(QueryOutput { columns: vec!["rows".to_string()], rows: vec![vec![n.to_string()]], @@ -1042,6 +1043,7 @@ impl<'a> SqlEngine<'a> { .write() .unwrap() .insert(table_name, (columns, vec![])); + self.store.try_flush_catalog_to_storage(); Ok(QueryOutput { columns: vec!["result".to_string()], rows: vec![vec!["ok".to_string()]], @@ -1086,36 +1088,40 @@ impl<'a> SqlEngine<'a> { Some(indices?) }; - let mut guard = self.store.custom_tables.write().unwrap(); - let (table_cols, table_rows) = guard - .get_mut(&table_name) - .ok_or_else(|| MqdbError::SqlExec(format!("unknown table: {table_name}")))?; - let ncols = table_cols.len(); - - let mut inserted = 0usize; - for src_row in &values_out.rows { - let mut row = vec![String::new(); ncols]; - match &col_indices { - None => { - if src_row.len() != ncols { - return Err(MqdbError::SqlExec(format!( - "expected {ncols} columns, got {}", - src_row.len() - ))); + let inserted = { + let mut guard = self.store.custom_tables.write().unwrap(); + let (table_cols, table_rows) = guard + .get_mut(&table_name) + .ok_or_else(|| MqdbError::SqlExec(format!("unknown table: {table_name}")))?; + let ncols = table_cols.len(); + + let mut inserted = 0usize; + for src_row in &values_out.rows { + let mut row = vec![String::new(); ncols]; + match &col_indices { + None => { + if src_row.len() != ncols { + return Err(MqdbError::SqlExec(format!( + "expected {ncols} columns, got {}", + src_row.len() + ))); + } + row = src_row.clone(); } - row = src_row.clone(); - } - Some(idx_map) => { - for (dst_idx, &src_idx) in idx_map.iter().enumerate() { - if let Some(v) = src_row.get(dst_idx) { - row[src_idx] = v.clone(); + Some(idx_map) => { + for (dst_idx, &src_idx) in idx_map.iter().enumerate() { + if let Some(v) = src_row.get(dst_idx) { + row[src_idx] = v.clone(); + } } } } + table_rows.push(row); + inserted += 1; } - table_rows.push(row); - inserted += 1; - } + inserted + }; // write lock released before flush + self.store.try_flush_catalog_to_storage(); Ok(QueryOutput { columns: vec!["rows_affected".to_string()], rows: vec![vec![inserted.to_string()]], @@ -1127,23 +1133,27 @@ impl<'a> SqlEngine<'a> { names: &[ObjectName], if_exists: bool, ) -> Result { - let mut guard = self.store.custom_tables.write().unwrap(); - let mut dropped = 0usize; - for name in names { - let table_name = name.0.last().map(ident_value).unwrap_or("").to_lowercase(); - if matches!(table_name.as_str(), "blocks" | "documents") { - return Err(MqdbError::SqlExec(format!( - "cannot drop built-in table '{table_name}'" - ))); - } - if guard.remove(&table_name).is_some() { - dropped += 1; - } else if !if_exists { - return Err(MqdbError::SqlExec(format!( - "table '{table_name}' does not exist" - ))); + let dropped = { + let mut guard = self.store.custom_tables.write().unwrap(); + let mut dropped = 0usize; + for name in names { + let table_name = name.0.last().map(ident_value).unwrap_or("").to_lowercase(); + if matches!(table_name.as_str(), "blocks" | "documents") { + return Err(MqdbError::SqlExec(format!( + "cannot drop built-in table '{table_name}'" + ))); + } + if guard.remove(&table_name).is_some() { + dropped += 1; + } else if !if_exists { + return Err(MqdbError::SqlExec(format!( + "table '{table_name}' does not exist" + ))); + } } - } + dropped + }; // write lock released before flush + self.store.try_flush_catalog_to_storage(); Ok(QueryOutput { columns: vec!["result".to_string()], rows: vec![vec![format!("{dropped} table(s) dropped")]], diff --git a/src/storage.rs b/src/storage.rs index 7031cc5..2ce9cd7 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -9,7 +9,7 @@ use crate::{ document::Document, error::MqdbError, storage::{ - catalog::{CatalogEntry, read_catalog, write_catalog}, + catalog::{CatalogEntry, CustomTableEntry, read_catalog, write_catalog}, codec::{decode_block, encode_block}, page::{ PAGE_BODY_SIZE, PAGE_HEADER_SIZE, PAGE_TYPE_BLOCK_DATA, PAGE_TYPE_CATALOG, @@ -146,12 +146,18 @@ impl Storage { } /// Save catalog (call after all write_document calls). - pub fn flush_catalog(&mut self, entries: &[CatalogEntry]) -> Result<(), MqdbError> { - write_catalog(&mut self.page_file, entries) + pub fn flush_catalog( + &mut self, + entries: &[CatalogEntry], + custom_tables: &[CustomTableEntry], + ) -> Result<(), MqdbError> { + write_catalog(&mut self.page_file, entries, custom_tables) } /// Read the catalog. - pub fn load_catalog(&mut self) -> Result, MqdbError> { + pub fn load_catalog( + &mut self, + ) -> Result<(Vec, Vec), MqdbError> { read_catalog(&mut self.page_file) } @@ -392,11 +398,11 @@ mod tests { zone_map_bytes: encode_zone_map(&document.zone_maps), index_start_page: 0, }; - storage.flush_catalog(&[catalog_entry]).unwrap(); + storage.flush_catalog(&[catalog_entry], &[]).unwrap(); drop(storage); let mut reopened = Storage::open(&path).unwrap(); - let catalog = reopened.load_catalog().unwrap(); + let (catalog, _) = reopened.load_catalog().unwrap(); assert_eq!(catalog.len(), 1); assert_eq!( decode_zone_map(&catalog[0].zone_map_bytes).unwrap(), @@ -511,6 +517,48 @@ mod tests { assert_eq!(decoded, block); } + #[test] + fn custom_table_round_trip() { + let path = test_file_path("custom-table-round-trip"); + cleanup(&path); + + let mut store = DocumentStore::new(); + store.add_str("# Hello\n\nWorld\n").unwrap(); + store.save(&path).unwrap(); + + // Open and CREATE TABLE + INSERT + let mut opened = DocumentStore::open(&path).unwrap(); + opened.load_all_blocks().unwrap(); + opened.load_all_indexes().unwrap(); + let engine = crate::SqlEngine::new(&opened).unwrap(); + engine + .execute("CREATE TABLE notes (id TEXT, body TEXT)") + .unwrap(); + engine + .execute("INSERT INTO notes VALUES ('1', 'hello')") + .unwrap(); + engine + .execute("INSERT INTO notes VALUES ('2', 'world')") + .unwrap(); + drop(engine); + drop(opened); + + // Re-open and verify tables persisted + let mut reopened = DocumentStore::open(&path).unwrap(); + reopened.load_all_blocks().unwrap(); + reopened.load_all_indexes().unwrap(); + let engine2 = crate::SqlEngine::new(&reopened).unwrap(); + let out = engine2 + .execute("SELECT body FROM notes WHERE id = '1'") + .unwrap(); + assert_eq!(out.rows.len(), 1); + assert_eq!(out.rows[0][0], "hello"); + let all = engine2.execute("SELECT * FROM notes").unwrap(); + assert_eq!(all.rows.len(), 2); + + cleanup(&path); + } + #[rstest] #[case(Some("My Title"))] #[case(None)] diff --git a/src/storage/catalog.rs b/src/storage/catalog.rs index 86c812c..579d25d 100644 --- a/src/storage/catalog.rs +++ b/src/storage/catalog.rs @@ -18,6 +18,14 @@ pub struct CatalogEntry { pub index_start_page: u32, } +/// A user-defined table entry stored in the catalog. +#[derive(Debug, Clone, PartialEq)] +pub struct CustomTableEntry { + pub name: String, + pub columns: Vec, + pub rows: Vec>, +} + fn invalid_data(message: impl Into) -> MqdbError { MqdbError::Storage(message.into()) } @@ -79,9 +87,21 @@ impl<'a> Decoder<'a> { String::from_utf8(bytes.to_vec()) .map_err(|e| invalid_data(format!("invalid catalog string UTF-8: {e}"))) } + + fn read_string_u32(&mut self) -> Result { + let len = usize::try_from(self.read_u32()?) + .map_err(|_| invalid_data("string length exceeds usize range"))?; + let bytes = self.read_exact(len)?; + String::from_utf8(bytes.to_vec()) + .map_err(|e| invalid_data(format!("invalid catalog string UTF-8: {e}"))) + } + + fn remaining(&self) -> usize { + self.data.len() - self.pos + } } -fn serialize_catalog(entries: &[CatalogEntry]) -> Vec { +fn serialize_catalog(entries: &[CatalogEntry], custom_tables: &[CustomTableEntry]) -> Vec { let mut out = Vec::new(); out.extend_from_slice(&as_u32(entries.len(), "catalog entry count").to_le_bytes()); @@ -102,15 +122,39 @@ fn serialize_catalog(entries: &[CatalogEntry]) -> Vec { out.extend_from_slice(&entry.index_start_page.to_le_bytes()); } + // Custom tables section (appended for backward compatibility — old readers see trailing zeros + // and stop; new readers detect the count field). + out.extend_from_slice(&as_u32(custom_tables.len(), "custom table count").to_le_bytes()); + for ct in custom_tables { + out.extend_from_slice(&as_u16(ct.name.len(), "table name length").to_le_bytes()); + out.extend_from_slice(ct.name.as_bytes()); + out.extend_from_slice(&as_u16(ct.columns.len(), "column count").to_le_bytes()); + for col in &ct.columns { + out.extend_from_slice(&as_u16(col.len(), "column name length").to_le_bytes()); + out.extend_from_slice(col.as_bytes()); + } + out.extend_from_slice(&as_u32(ct.rows.len(), "row count").to_le_bytes()); + for row in &ct.rows { + for cell in row { + out.extend_from_slice(&as_u32(cell.len(), "cell length").to_le_bytes()); + out.extend_from_slice(cell.as_bytes()); + } + } + } + out } -pub fn write_catalog(pf: &mut PageFile, entries: &[CatalogEntry]) -> Result<(), MqdbError> { +pub fn write_catalog( + pf: &mut PageFile, + entries: &[CatalogEntry], + custom_tables: &[CustomTableEntry], +) -> Result<(), MqdbError> { if pf.num_pages < 2 { return Err(invalid_data("catalog start page is missing")); } - let bytes = serialize_catalog(entries); + let bytes = serialize_catalog(entries, custom_tables); let chunks: Vec<&[u8]> = if bytes.is_empty() { vec![&[]] } else { @@ -136,7 +180,9 @@ pub fn write_catalog(pf: &mut PageFile, entries: &[CatalogEntry]) -> Result<(), Ok(()) } -pub fn read_catalog(pf: &mut PageFile) -> Result, MqdbError> { +pub fn read_catalog( + pf: &mut PageFile, +) -> Result<(Vec, Vec), MqdbError> { if pf.num_pages < 2 { return Err(invalid_data("catalog start page is missing")); } @@ -200,5 +246,39 @@ pub fn read_catalog(pf: &mut PageFile) -> Result, MqdbError> { }); } - Ok(entries) + // Custom tables section — present in new-format files; old files have trailing zeros here + // which decode as count=0 (backward compatible). + let custom_tables = if decoder.remaining() >= 4 { + let count = usize::try_from(decoder.read_u32()?) + .map_err(|_| invalid_data("custom table count exceeds usize range"))?; + let mut tables = Vec::with_capacity(count); + for _ in 0..count { + let name = decoder.read_string_u16()?; + let num_cols = usize::from(decoder.read_u16()?); + let mut columns = Vec::with_capacity(num_cols); + for _ in 0..num_cols { + columns.push(decoder.read_string_u16()?); + } + let num_rows = usize::try_from(decoder.read_u32()?) + .map_err(|_| invalid_data("row count exceeds usize range"))?; + let mut rows = Vec::with_capacity(num_rows); + for _ in 0..num_rows { + let mut row = Vec::with_capacity(num_cols); + for _ in 0..num_cols { + row.push(decoder.read_string_u32()?); + } + rows.push(row); + } + tables.push(CustomTableEntry { + name, + columns, + rows, + }); + } + tables + } else { + vec![] + }; + + Ok((entries, custom_tables)) } diff --git a/src/store.rs b/src/store.rs index afefbc5..5b24b49 100644 --- a/src/store.rs +++ b/src/store.rs @@ -1,7 +1,7 @@ use std::{ collections::HashMap, path::{Path, PathBuf}, - sync::RwLock, + sync::{Mutex, RwLock}, }; type CustomTable = (Vec, Vec>); @@ -17,7 +17,7 @@ use crate::{ query::Query, storage::{ Storage, - catalog::CatalogEntry, + catalog::{CatalogEntry, CustomTableEntry}, codec::{decode_zone_map, encode_zone_map}, }, }; @@ -58,7 +58,9 @@ pub struct DocumentStore { store_spans: bool, /// Open storage file kept for lazy block / index loading. `None` when the /// store was built entirely in memory or fully loaded via `load()`. - pub(crate) storage: Option, + /// Wrapped in `Mutex` so DDL operations (which hold only `&DocumentStore`) + /// can flush the updated catalog to disk. + pub(crate) storage: Mutex>, /// Per-document secondary index cache (same order as `documents`). /// `None` means the index has not been built/loaded for that document yet. pub(crate) doc_indexes: Vec>, @@ -74,7 +76,7 @@ impl Default for DocumentStore { documents: Vec::new(), next_doc_id: 0, store_spans: true, - storage: None, + storage: Mutex::new(None), doc_indexes: Vec::new(), custom_tables: RwLock::new(HashMap::new()), } @@ -196,42 +198,57 @@ impl DocumentStore { } let mut doc = Document::new(doc_id, md_path, blocks); - if let Some(storage) = self.storage.as_mut() { - // Reconstruct catalog entries from already-loaded document metadata. - let mut entries: Vec = self - .documents - .iter() - .map(|d| CatalogEntry { - document_id: d.id, - path: d.path.as_ref().map(|p| p.to_string_lossy().into_owned()), - first_block_page: d.first_block_page, - num_blocks: d.block_count, - zone_map_bytes: encode_zone_map(&d.zone_maps), - index_start_page: d.index_start_page, - }) - .collect(); + let idx_opt = { + let mut storage_guard = self.storage.lock().unwrap(); + if let Some(storage) = storage_guard.as_mut() { + // Reconstruct catalog entries from already-loaded document metadata. + let mut entries: Vec = self + .documents + .iter() + .map(|d| CatalogEntry { + document_id: d.id, + path: d.path.as_ref().map(|p| p.to_string_lossy().into_owned()), + first_block_page: d.first_block_page, + num_blocks: d.block_count, + zone_map_bytes: encode_zone_map(&d.zone_maps), + index_start_page: d.index_start_page, + }) + .collect(); + + let first_block_page = storage.write_document(&doc)?; + doc.first_block_page = first_block_page; + + let idx = DocumentIndex::build(&doc.blocks); + let index_start_page = storage.write_index(&idx.to_bytes())?; + doc.index_start_page = index_start_page; - let first_block_page = storage.write_document(&doc)?; - doc.first_block_page = first_block_page; - - let idx = DocumentIndex::build(&doc.blocks); - let index_start_page = storage.write_index(&idx.to_bytes())?; - doc.index_start_page = index_start_page; - - entries.push(CatalogEntry { - document_id: doc.id, - path: doc.path.as_ref().map(|p| p.to_string_lossy().into_owned()), - first_block_page, - num_blocks: doc.block_count, - zone_map_bytes: encode_zone_map(&doc.zone_maps), - index_start_page, - }); - - storage.flush_catalog(&entries)?; - self.doc_indexes.push(Some(idx)); - } else { - self.doc_indexes.push(None); - } + entries.push(CatalogEntry { + document_id: doc.id, + path: doc.path.as_ref().map(|p| p.to_string_lossy().into_owned()), + first_block_page, + num_blocks: doc.block_count, + zone_map_bytes: encode_zone_map(&doc.zone_maps), + index_start_page, + }); + + let ct_guard = self.custom_tables.read().unwrap(); + let custom: Vec = ct_guard + .iter() + .map(|(name, (cols, rows))| CustomTableEntry { + name: name.clone(), + columns: cols.clone(), + rows: rows.clone(), + }) + .collect(); + drop(ct_guard); + + storage.flush_catalog(&entries, &custom)?; + Some(idx) + } else { + None + } + }; + self.doc_indexes.push(idx_opt); self.documents.push(doc); Ok(doc_id) @@ -270,7 +287,8 @@ impl DocumentStore { /// /// No-op when the store was built in memory or fully loaded via `load()`. pub fn load_all_blocks(&mut self) -> Result<(), MqdbError> { - let storage = match self.storage.as_mut() { + let mut guard = self.storage.lock().unwrap(); + let storage = match guard.as_mut() { Some(s) => s, None => return Ok(()), }; @@ -302,11 +320,12 @@ impl DocumentStore { fn build_or_load_index_at(&mut self, i: usize) -> Result { let index_start_page = self.documents[i].index_start_page; - if index_start_page > 0 - && let Some(storage) = self.storage.as_mut() - { - let bytes = storage.read_index_bytes(index_start_page)?; - return DocumentIndex::from_bytes(&bytes); + if index_start_page > 0 { + let mut guard = self.storage.lock().unwrap(); + if let Some(storage) = guard.as_mut() { + let bytes = storage.read_index_bytes(index_start_page)?; + return DocumentIndex::from_bytes(&bytes); + } } Ok(DocumentIndex::build(&self.documents[i].blocks)) @@ -317,6 +336,38 @@ impl DocumentStore { self.doc_indexes.get(i).and_then(|o| o.as_ref()) } + /// Flush the catalog (including custom tables) to the backing storage file, + /// if one is open. Called automatically after DDL operations. No-op for + /// in-memory stores. + pub(crate) fn try_flush_catalog_to_storage(&self) { + let mut guard = self.storage.lock().unwrap(); + if let Some(storage) = guard.as_mut() { + let entries: Vec = self + .documents + .iter() + .map(|d| CatalogEntry { + document_id: d.id, + path: d.path.as_ref().map(|p| p.to_string_lossy().into_owned()), + first_block_page: d.first_block_page, + num_blocks: d.block_count, + zone_map_bytes: encode_zone_map(&d.zone_maps), + index_start_page: d.index_start_page, + }) + .collect(); + let ct_guard = self.custom_tables.read().unwrap(); + let custom: Vec = ct_guard + .iter() + .map(|(name, (cols, rows))| CustomTableEntry { + name: name.clone(), + columns: cols.clone(), + rows: rows.clone(), + }) + .collect(); + drop(ct_guard); + let _ = storage.flush_catalog(&entries, &custom); + } + } + // ───────────────────────────────────────────────────────────────────────── // Persistence // ───────────────────────────────────────────────────────────────────────── @@ -358,7 +409,18 @@ impl DocumentStore { entries[i].index_start_page = storage.write_index(&bytes)?; } - storage.flush_catalog(&entries)?; + let ct_guard = self.custom_tables.read().unwrap(); + let custom: Vec = ct_guard + .iter() + .map(|(name, (cols, rows))| CustomTableEntry { + name: name.clone(), + columns: cols.clone(), + rows: rows.clone(), + }) + .collect(); + drop(ct_guard); + + storage.flush_catalog(&entries, &custom)?; Ok(()) })(); @@ -379,7 +441,7 @@ impl DocumentStore { /// [`load_all_indexes`](DocumentStore::load_all_indexes). pub fn open(path: impl AsRef) -> Result { let mut storage = Storage::open(path.as_ref())?; - let entries = storage.load_catalog()?; + let (entries, custom_table_entries) = storage.load_catalog()?; let cap = entries.len(); let mut documents = Vec::with_capacity(cap); let mut max_doc_id = None; @@ -400,13 +462,18 @@ impl DocumentStore { Some(max_doc_id.map_or(document_id, |cur: DocumentId| cur.max(document_id))); } + let mut custom_tables = HashMap::new(); + for ct in custom_table_entries { + custom_tables.insert(ct.name, (ct.columns, ct.rows)); + } + Ok(Self { documents, next_doc_id: max_doc_id.map_or(0, |id| id.saturating_add(1)), store_spans: true, - storage: Some(storage), + storage: Mutex::new(Some(storage)), doc_indexes: vec![None; cap], - custom_tables: RwLock::new(HashMap::new()), + custom_tables: RwLock::new(custom_tables), }) } @@ -416,7 +483,7 @@ impl DocumentStore { /// here — [`crate::SqlEngine`] builds them lazily on construction. pub fn load(path: impl AsRef) -> Result { let mut storage = Storage::open(path.as_ref())?; - let entries = storage.load_catalog()?; + let (entries, custom_table_entries) = storage.load_catalog()?; let cap = entries.len(); let mut documents = Vec::with_capacity(cap); let mut max_doc_id = None; @@ -433,13 +500,18 @@ impl DocumentStore { Some(max_doc_id.map_or(document_id, |cur: DocumentId| cur.max(document_id))); } + let mut custom_tables = HashMap::new(); + for ct in custom_table_entries { + custom_tables.insert(ct.name, (ct.columns, ct.rows)); + } + Ok(Self { documents, next_doc_id: max_doc_id.map_or(0, |id| id.saturating_add(1)), store_spans: true, - storage: None, + storage: Mutex::new(None), doc_indexes: vec![None; cap], - custom_tables: RwLock::new(HashMap::new()), + custom_tables: RwLock::new(custom_tables), }) } @@ -450,7 +522,7 @@ impl DocumentStore { /// `list`), avoiding the cost of deserialising all block data. pub fn load_catalog_only(path: impl AsRef) -> Result { let mut storage = Storage::open(path.as_ref())?; - let entries = storage.load_catalog()?; + let (entries, _custom_table_entries) = storage.load_catalog()?; let cap = entries.len(); let mut documents = Vec::with_capacity(cap); let mut max_doc_id = None; @@ -473,7 +545,7 @@ impl DocumentStore { documents, next_doc_id: max_doc_id.map_or(0, |id| id.saturating_add(1)), store_spans: true, - storage: None, + storage: Mutex::new(None), doc_indexes: vec![None; cap], custom_tables: RwLock::new(HashMap::new()), })