1use std::{
2 collections::HashMap,
3 path::{Path, PathBuf},
4 sync::{Mutex, RwLock},
5};
6
7pub(crate) struct CustomTableState {
14 pub columns: Vec<String>,
15 pub rows: Vec<Vec<String>>,
16 pub first_row_page: u32,
17 pub last_row_page: u32,
18}
19
20use mq_markdown::Markdown;
21
22use crate::{
23 block::DocumentId,
24 document::Document,
25 error::MqdbError,
26 index,
27 indexes::DocumentIndex,
28 query::Query,
29 storage::{
30 Storage,
31 catalog::{CatalogEntry, CustomTableEntry},
32 codec::{decode_zone_map, encode_zone_map},
33 },
34};
35
36fn persist_unsaved_table_rows(
43 storage: &mut Storage,
44 custom_tables: &RwLock<HashMap<String, CustomTableState>>,
45) -> Result<Vec<CustomTableEntry>, MqdbError> {
46 let mut guard = custom_tables.write().unwrap();
47 for state in guard.values_mut() {
48 if state.first_row_page == 0 && !state.rows.is_empty() {
49 let (first, last) = storage.write_table_rows(&state.rows)?;
50 state.first_row_page = first;
51 state.last_row_page = last;
52 }
53 }
54 Ok(guard
55 .iter()
56 .map(|(name, state)| CustomTableEntry {
57 name: name.clone(),
58 columns: state.columns.clone(),
59 first_row_page: state.first_row_page,
60 last_row_page: state.last_row_page,
61 num_rows: state.rows.len() as u32,
62 })
63 .collect())
64}
65
66pub struct DocumentStore {
96 documents: Vec<Document>,
97 next_doc_id: DocumentId,
98 store_spans: bool,
100 pub(crate) storage: Mutex<Option<Storage>>,
105 pub(crate) doc_indexes: Vec<Option<DocumentIndex>>,
108 pub(crate) custom_tables: RwLock<HashMap<String, CustomTableState>>,
112}
113
114impl Default for DocumentStore {
115 fn default() -> Self {
116 Self {
117 documents: Vec::new(),
118 next_doc_id: 0,
119 store_spans: true,
120 storage: Mutex::new(None),
121 doc_indexes: Vec::new(),
122 custom_tables: RwLock::new(HashMap::new()),
123 }
124 }
125}
126
127impl DocumentStore {
128 pub fn new() -> Self {
130 Self::default()
131 }
132
133 pub fn set_store_spans(&mut self, val: bool) {
136 self.store_spans = val;
137 }
138
139 pub fn register_table(
146 &mut self,
147 name: impl Into<String>,
148 columns: Vec<String>,
149 rows: Vec<Vec<String>>,
150 ) {
151 self.custom_tables.write().unwrap().insert(
152 name.into(),
153 CustomTableState {
154 columns,
155 rows,
156 first_row_page: 0,
157 last_row_page: 0,
158 },
159 );
160 }
161
162 pub fn unregister_table(&mut self, name: &str) -> bool {
164 self.custom_tables.write().unwrap().remove(name).is_some()
165 }
166
167 pub fn add_file(&mut self, path: impl AsRef<Path>) -> Result<DocumentId, MqdbError> {
171 let path = path.as_ref();
172 let content = std::fs::read_to_string(path)?;
173 self.add_str_with_path(&content, Some(path.to_path_buf()))
174 }
175
176 pub fn add_str(&mut self, content: &str) -> Result<DocumentId, MqdbError> {
180 self.add_str_with_path(content, None)
181 }
182
183 pub fn add_str_with_path(
187 &mut self,
188 content: &str,
189 path: Option<std::path::PathBuf>,
190 ) -> Result<DocumentId, MqdbError> {
191 let md =
192 Markdown::from_markdown_str(content).map_err(|e| MqdbError::Parse(e.to_string()))?;
193
194 let doc_id = self.next_doc_id;
195 self.next_doc_id += 1;
196
197 let mut blocks = index::build_blocks(doc_id, &md.nodes);
198 if !self.store_spans {
199 for block in &mut blocks {
200 block.span = None;
201 }
202 }
203 let doc = Document::new(doc_id, path, blocks);
204 self.documents.push(doc);
205 self.doc_indexes.push(None);
206
207 Ok(doc_id)
208 }
209
210 pub fn append_str(&mut self, content: &str) -> Result<DocumentId, MqdbError> {
220 self.do_append(content, None)
221 }
222
223 pub fn append_file(&mut self, path: impl AsRef<Path>) -> Result<DocumentId, MqdbError> {
227 let path = path.as_ref();
228 let content = std::fs::read_to_string(path)?;
229 self.do_append(&content, Some(path.to_path_buf()))
230 }
231
232 fn do_append(
233 &mut self,
234 content: &str,
235 md_path: Option<PathBuf>,
236 ) -> Result<DocumentId, MqdbError> {
237 let md =
238 Markdown::from_markdown_str(content).map_err(|e| MqdbError::Parse(e.to_string()))?;
239 let doc_id = self.next_doc_id;
240 self.next_doc_id += 1;
241
242 let mut blocks = index::build_blocks(doc_id, &md.nodes);
243 if !self.store_spans {
244 for block in &mut blocks {
245 block.span = None;
246 }
247 }
248 let mut doc = Document::new(doc_id, md_path, blocks);
249
250 let idx_opt = {
251 let mut storage_guard = self.storage.lock().unwrap();
252 if let Some(storage) = storage_guard.as_mut() {
253 let mut entries = self.catalog_entries();
255
256 let first_block_page = storage.write_document(&doc)?;
257 doc.first_block_page = first_block_page;
258
259 let idx = DocumentIndex::build(&doc.blocks);
260 let index_start_page = storage.write_index(&idx.to_bytes())?;
261 doc.index_start_page = index_start_page;
262
263 entries.push(CatalogEntry {
264 document_id: doc.id,
265 path: doc.path.as_ref().map(|p| p.to_string_lossy().into_owned()),
266 first_block_page,
267 num_blocks: doc.block_count,
268 zone_map_bytes: encode_zone_map(&doc.zone_maps),
269 index_start_page,
270 });
271
272 let custom = persist_unsaved_table_rows(storage, &self.custom_tables)?;
273 storage.flush_catalog(&entries, &custom)?;
274 Some(idx)
275 } else {
276 None
277 }
278 };
279 self.doc_indexes.push(idx_opt);
280
281 self.documents.push(doc);
282 Ok(doc_id)
283 }
284
285 pub fn documents(&self) -> &[Document] {
287 &self.documents
288 }
289
290 pub fn get_document(&self, id: DocumentId) -> Option<&Document> {
292 self.documents.iter().find(|d| d.id == id)
293 }
294
295 pub fn len(&self) -> usize {
297 self.documents.len()
298 }
299
300 pub fn is_empty(&self) -> bool {
302 self.documents.is_empty()
303 }
304
305 pub fn query(&self) -> Query<'_> {
307 Query::new(self)
308 }
309
310 pub fn load_all_blocks(&mut self) -> Result<(), MqdbError> {
318 let mut guard = self.storage.lock().unwrap();
319 let storage = match guard.as_mut() {
320 Some(s) => s,
321 None => return Ok(()),
322 };
323 for doc in &mut self.documents {
324 if doc.blocks.is_empty() && doc.block_count > 0 {
325 doc.blocks = storage.read_blocks(doc.first_block_page, doc.block_count)?;
326 }
327 }
328 Ok(())
329 }
330
331 pub fn load_all_indexes(&mut self) -> Result<(), MqdbError> {
337 for i in 0..self.documents.len() {
338 if self.doc_indexes[i].is_some() {
339 continue;
340 }
341
342 let idx = self.build_or_load_index_at(i)?;
343 self.doc_indexes[i] = Some(idx);
344 }
345 Ok(())
346 }
347
348 fn build_or_load_index_at(&mut self, i: usize) -> Result<DocumentIndex, MqdbError> {
349 let index_start_page = self.documents[i].index_start_page;
350
351 if index_start_page > 0 {
352 let mut guard = self.storage.lock().unwrap();
353 if let Some(storage) = guard.as_mut() {
354 let bytes = storage.read_index_bytes(index_start_page)?;
355 return DocumentIndex::from_bytes(&bytes);
356 }
357 }
358
359 Ok(DocumentIndex::build(&self.documents[i].blocks))
360 }
361
362 pub(crate) fn get_doc_index(&self, i: usize) -> Option<&DocumentIndex> {
364 self.doc_indexes.get(i).and_then(|o| o.as_ref())
365 }
366
367 fn catalog_entries(&self) -> Vec<CatalogEntry> {
369 self.documents
370 .iter()
371 .map(|d| CatalogEntry {
372 document_id: d.id,
373 path: d.path.as_ref().map(|p| p.to_string_lossy().into_owned()),
374 first_block_page: d.first_block_page,
375 num_blocks: d.block_count,
376 zone_map_bytes: encode_zone_map(&d.zone_maps),
377 index_start_page: d.index_start_page,
378 })
379 .collect()
380 }
381
382 pub(crate) fn try_flush_catalog_to_storage(&self) {
392 let mut guard = self.storage.lock().unwrap();
393 if let Some(storage) = guard.as_mut() {
394 let entries = self.catalog_entries();
395 if let Ok(custom) = persist_unsaved_table_rows(storage, &self.custom_tables) {
396 let _ = storage.flush_catalog(&entries, &custom);
397 }
398 }
399 }
400
401 pub(crate) fn try_append_table_rows_to_storage(
408 &self,
409 table_name: &str,
410 new_rows: &[Vec<String>],
411 ) {
412 let mut guard = self.storage.lock().unwrap();
413 let storage = match guard.as_mut() {
414 Some(s) => s,
415 None => return,
416 };
417
418 {
419 let mut ct_guard = self.custom_tables.write().unwrap();
420 if let Some(state) = ct_guard.get_mut(table_name) {
421 let persisted = if state.first_row_page == 0 {
422 storage.write_table_rows(&state.rows)
426 } else {
427 storage
428 .append_table_rows(state.last_row_page, new_rows)
429 .map(|last| (state.first_row_page, last))
430 };
431 if let Ok((first, last)) = persisted {
432 state.first_row_page = first;
433 state.last_row_page = last;
434 }
435 }
436 }
437
438 let entries = self.catalog_entries();
439 let ct_guard = self.custom_tables.read().unwrap();
440 let custom: Vec<CustomTableEntry> = ct_guard
441 .iter()
442 .map(|(name, state)| CustomTableEntry {
443 name: name.clone(),
444 columns: state.columns.clone(),
445 first_row_page: state.first_row_page,
446 last_row_page: state.last_row_page,
447 num_rows: state.rows.len() as u32,
448 })
449 .collect();
450 drop(ct_guard);
451 let _ = storage.flush_catalog(&entries, &custom);
452 }
453
454 pub fn save(&self, path: impl AsRef<Path>) -> Result<(), MqdbError> {
461 let path = path.as_ref();
462 let tmp_path = PathBuf::from(format!("{}.tmp", path.to_string_lossy()));
463 if tmp_path.exists() {
464 std::fs::remove_file(&tmp_path)?;
465 }
466
467 let write_result = (|| -> Result<(), MqdbError> {
468 let mut storage = Storage::create(&tmp_path)?;
469 let mut entries = Vec::with_capacity(self.documents.len());
470
471 for doc in &self.documents {
473 let first_block_page = storage.write_document(doc)?;
474 entries.push(CatalogEntry {
475 document_id: doc.id,
476 path: doc.path.as_ref().map(|p| p.to_string_lossy().into_owned()),
477 first_block_page,
478 num_blocks: doc.block_count,
479 zone_map_bytes: encode_zone_map(&doc.zone_maps),
480 index_start_page: 0,
481 });
482 }
483
484 for (i, doc) in self.documents.iter().enumerate() {
486 let idx = if let Some(cached) = self.doc_indexes.get(i).and_then(|o| o.as_ref()) {
487 std::borrow::Cow::Borrowed(cached)
488 } else {
489 std::borrow::Cow::Owned(DocumentIndex::build(&doc.blocks))
490 };
491 let bytes = idx.to_bytes();
492 entries[i].index_start_page = storage.write_index(&bytes)?;
493 }
494
495 let ct_guard = self.custom_tables.read().unwrap();
500 let mut custom = Vec::with_capacity(ct_guard.len());
501 for (name, state) in ct_guard.iter() {
502 let (first_row_page, last_row_page) = storage.write_table_rows(&state.rows)?;
503 custom.push(CustomTableEntry {
504 name: name.clone(),
505 columns: state.columns.clone(),
506 first_row_page,
507 last_row_page,
508 num_rows: state.rows.len() as u32,
509 });
510 }
511 drop(ct_guard);
512
513 storage.flush_catalog(&entries, &custom)?;
514 Ok(())
515 })();
516
517 if let Err(err) = write_result {
518 let _ = std::fs::remove_file(&tmp_path);
519 return Err(err);
520 }
521
522 std::fs::rename(&tmp_path, path)?;
523 Ok(())
524 }
525
526 pub fn open(path: impl AsRef<Path>) -> Result<Self, MqdbError> {
533 let mut storage = Storage::open(path.as_ref())?;
534 let (entries, custom_table_entries) = storage.load_catalog()?;
535 let cap = entries.len();
536 let mut documents = Vec::with_capacity(cap);
537 let mut max_doc_id = None;
538
539 for entry in entries {
540 let zone_maps = decode_zone_map(&entry.zone_map_bytes)?;
541 let document_id = entry.document_id;
542 let path = entry.path.map(PathBuf::from);
543 documents.push(Document::from_catalog_lazy(
544 document_id,
545 path,
546 entry.num_blocks,
547 zone_maps,
548 entry.first_block_page,
549 entry.index_start_page,
550 ));
551 max_doc_id =
552 Some(max_doc_id.map_or(document_id, |cur: DocumentId| cur.max(document_id)));
553 }
554
555 let mut custom_tables = HashMap::new();
556 for ct in custom_table_entries {
557 let rows = storage.read_table_rows(ct.first_row_page, ct.num_rows, ct.columns.len())?;
558 custom_tables.insert(
559 ct.name,
560 CustomTableState {
561 columns: ct.columns,
562 rows,
563 first_row_page: ct.first_row_page,
564 last_row_page: ct.last_row_page,
565 },
566 );
567 }
568
569 Ok(Self {
570 documents,
571 next_doc_id: max_doc_id.map_or(0, |id| id.saturating_add(1)),
572 store_spans: true,
573 storage: Mutex::new(Some(storage)),
574 doc_indexes: vec![None; cap],
575 custom_tables: RwLock::new(custom_tables),
576 })
577 }
578
579 pub fn load(path: impl AsRef<Path>) -> Result<Self, MqdbError> {
584 let mut storage = Storage::open(path.as_ref())?;
585 let (entries, custom_table_entries) = storage.load_catalog()?;
586 let cap = entries.len();
587 let mut documents = Vec::with_capacity(cap);
588 let mut max_doc_id = None;
589
590 for entry in entries {
591 let blocks = storage.read_blocks(entry.first_block_page, entry.num_blocks)?;
592 let zone_maps = decode_zone_map(&entry.zone_map_bytes)?;
593 let document_id = entry.document_id;
594 let path = entry.path.map(PathBuf::from);
595 let mut doc = Document::from_parts(document_id, path, blocks, zone_maps);
596 doc.index_start_page = entry.index_start_page;
597 documents.push(doc);
598 max_doc_id =
599 Some(max_doc_id.map_or(document_id, |cur: DocumentId| cur.max(document_id)));
600 }
601
602 let mut custom_tables = HashMap::new();
603 for ct in custom_table_entries {
604 let rows = storage.read_table_rows(ct.first_row_page, ct.num_rows, ct.columns.len())?;
605 custom_tables.insert(
606 ct.name,
607 CustomTableState {
608 columns: ct.columns,
609 rows,
610 first_row_page: ct.first_row_page,
611 last_row_page: ct.last_row_page,
612 },
613 );
614 }
615
616 Ok(Self {
617 documents,
618 next_doc_id: max_doc_id.map_or(0, |id| id.saturating_add(1)),
619 store_spans: true,
620 storage: Mutex::new(None),
621 doc_indexes: vec![None; cap],
622 custom_tables: RwLock::new(custom_tables),
623 })
624 }
625
626 pub fn load_catalog_only(path: impl AsRef<Path>) -> Result<Self, MqdbError> {
632 let mut storage = Storage::open(path.as_ref())?;
633 let (entries, _custom_table_entries) = storage.load_catalog()?;
634 let cap = entries.len();
635 let mut documents = Vec::with_capacity(cap);
636 let mut max_doc_id = None;
637
638 for entry in entries {
639 let zone_maps = decode_zone_map(&entry.zone_map_bytes)?;
640 let document_id = entry.document_id;
641 let path = entry.path.map(PathBuf::from);
642 documents.push(Document::from_catalog(
643 document_id,
644 path,
645 entry.num_blocks,
646 zone_maps,
647 ));
648 max_doc_id =
649 Some(max_doc_id.map_or(document_id, |cur: DocumentId| cur.max(document_id)));
650 }
651
652 Ok(Self {
653 documents,
654 next_doc_id: max_doc_id.map_or(0, |id| id.saturating_add(1)),
655 store_spans: true,
656 storage: Mutex::new(None),
657 doc_indexes: vec![None; cap],
658 custom_tables: RwLock::new(HashMap::new()),
659 })
660 }
661}