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 fn add_str_with_path(
184 &mut self,
185 content: &str,
186 path: Option<std::path::PathBuf>,
187 ) -> Result<DocumentId, MqdbError> {
188 let md =
189 Markdown::from_markdown_str(content).map_err(|e| MqdbError::Parse(e.to_string()))?;
190
191 let doc_id = self.next_doc_id;
192 self.next_doc_id += 1;
193
194 let mut blocks = index::build_blocks(doc_id, &md.nodes);
195 if !self.store_spans {
196 for block in &mut blocks {
197 block.span = None;
198 }
199 }
200 let doc = Document::new(doc_id, path, blocks);
201 self.documents.push(doc);
202 self.doc_indexes.push(None);
203
204 Ok(doc_id)
205 }
206
207 pub fn append_str(&mut self, content: &str) -> Result<DocumentId, MqdbError> {
217 self.do_append(content, None)
218 }
219
220 pub fn append_file(&mut self, path: impl AsRef<Path>) -> Result<DocumentId, MqdbError> {
224 let path = path.as_ref();
225 let content = std::fs::read_to_string(path)?;
226 self.do_append(&content, Some(path.to_path_buf()))
227 }
228
229 fn do_append(
230 &mut self,
231 content: &str,
232 md_path: Option<PathBuf>,
233 ) -> Result<DocumentId, MqdbError> {
234 let md =
235 Markdown::from_markdown_str(content).map_err(|e| MqdbError::Parse(e.to_string()))?;
236 let doc_id = self.next_doc_id;
237 self.next_doc_id += 1;
238
239 let mut blocks = index::build_blocks(doc_id, &md.nodes);
240 if !self.store_spans {
241 for block in &mut blocks {
242 block.span = None;
243 }
244 }
245 let mut doc = Document::new(doc_id, md_path, blocks);
246
247 let idx_opt = {
248 let mut storage_guard = self.storage.lock().unwrap();
249 if let Some(storage) = storage_guard.as_mut() {
250 let mut entries = self.catalog_entries();
252
253 let first_block_page = storage.write_document(&doc)?;
254 doc.first_block_page = first_block_page;
255
256 let idx = DocumentIndex::build(&doc.blocks);
257 let index_start_page = storage.write_index(&idx.to_bytes())?;
258 doc.index_start_page = index_start_page;
259
260 entries.push(CatalogEntry {
261 document_id: doc.id,
262 path: doc.path.as_ref().map(|p| p.to_string_lossy().into_owned()),
263 first_block_page,
264 num_blocks: doc.block_count,
265 zone_map_bytes: encode_zone_map(&doc.zone_maps),
266 index_start_page,
267 });
268
269 let custom = persist_unsaved_table_rows(storage, &self.custom_tables)?;
270 storage.flush_catalog(&entries, &custom)?;
271 Some(idx)
272 } else {
273 None
274 }
275 };
276 self.doc_indexes.push(idx_opt);
277
278 self.documents.push(doc);
279 Ok(doc_id)
280 }
281
282 pub fn documents(&self) -> &[Document] {
284 &self.documents
285 }
286
287 pub fn get_document(&self, id: DocumentId) -> Option<&Document> {
289 self.documents.iter().find(|d| d.id == id)
290 }
291
292 pub fn len(&self) -> usize {
294 self.documents.len()
295 }
296
297 pub fn is_empty(&self) -> bool {
299 self.documents.is_empty()
300 }
301
302 pub fn query(&self) -> Query<'_> {
304 Query::new(self)
305 }
306
307 pub fn load_all_blocks(&mut self) -> Result<(), MqdbError> {
315 let mut guard = self.storage.lock().unwrap();
316 let storage = match guard.as_mut() {
317 Some(s) => s,
318 None => return Ok(()),
319 };
320 for doc in &mut self.documents {
321 if doc.blocks.is_empty() && doc.block_count > 0 {
322 doc.blocks = storage.read_blocks(doc.first_block_page, doc.block_count)?;
323 }
324 }
325 Ok(())
326 }
327
328 pub fn load_all_indexes(&mut self) -> Result<(), MqdbError> {
334 for i in 0..self.documents.len() {
335 if self.doc_indexes[i].is_some() {
336 continue;
337 }
338
339 let idx = self.build_or_load_index_at(i)?;
340 self.doc_indexes[i] = Some(idx);
341 }
342 Ok(())
343 }
344
345 fn build_or_load_index_at(&mut self, i: usize) -> Result<DocumentIndex, MqdbError> {
346 let index_start_page = self.documents[i].index_start_page;
347
348 if index_start_page > 0 {
349 let mut guard = self.storage.lock().unwrap();
350 if let Some(storage) = guard.as_mut() {
351 let bytes = storage.read_index_bytes(index_start_page)?;
352 return DocumentIndex::from_bytes(&bytes);
353 }
354 }
355
356 Ok(DocumentIndex::build(&self.documents[i].blocks))
357 }
358
359 pub(crate) fn get_doc_index(&self, i: usize) -> Option<&DocumentIndex> {
361 self.doc_indexes.get(i).and_then(|o| o.as_ref())
362 }
363
364 fn catalog_entries(&self) -> Vec<CatalogEntry> {
366 self.documents
367 .iter()
368 .map(|d| CatalogEntry {
369 document_id: d.id,
370 path: d.path.as_ref().map(|p| p.to_string_lossy().into_owned()),
371 first_block_page: d.first_block_page,
372 num_blocks: d.block_count,
373 zone_map_bytes: encode_zone_map(&d.zone_maps),
374 index_start_page: d.index_start_page,
375 })
376 .collect()
377 }
378
379 pub(crate) fn try_flush_catalog_to_storage(&self) {
389 let mut guard = self.storage.lock().unwrap();
390 if let Some(storage) = guard.as_mut() {
391 let entries = self.catalog_entries();
392 if let Ok(custom) = persist_unsaved_table_rows(storage, &self.custom_tables) {
393 let _ = storage.flush_catalog(&entries, &custom);
394 }
395 }
396 }
397
398 pub(crate) fn try_append_table_rows_to_storage(
405 &self,
406 table_name: &str,
407 new_rows: &[Vec<String>],
408 ) {
409 let mut guard = self.storage.lock().unwrap();
410 let storage = match guard.as_mut() {
411 Some(s) => s,
412 None => return,
413 };
414
415 {
416 let mut ct_guard = self.custom_tables.write().unwrap();
417 if let Some(state) = ct_guard.get_mut(table_name) {
418 let persisted = if state.first_row_page == 0 {
419 storage.write_table_rows(&state.rows)
423 } else {
424 storage
425 .append_table_rows(state.last_row_page, new_rows)
426 .map(|last| (state.first_row_page, last))
427 };
428 if let Ok((first, last)) = persisted {
429 state.first_row_page = first;
430 state.last_row_page = last;
431 }
432 }
433 }
434
435 let entries = self.catalog_entries();
436 let ct_guard = self.custom_tables.read().unwrap();
437 let custom: Vec<CustomTableEntry> = ct_guard
438 .iter()
439 .map(|(name, state)| CustomTableEntry {
440 name: name.clone(),
441 columns: state.columns.clone(),
442 first_row_page: state.first_row_page,
443 last_row_page: state.last_row_page,
444 num_rows: state.rows.len() as u32,
445 })
446 .collect();
447 drop(ct_guard);
448 let _ = storage.flush_catalog(&entries, &custom);
449 }
450
451 pub fn save(&self, path: impl AsRef<Path>) -> Result<(), MqdbError> {
458 let path = path.as_ref();
459 let tmp_path = PathBuf::from(format!("{}.tmp", path.to_string_lossy()));
460 if tmp_path.exists() {
461 std::fs::remove_file(&tmp_path)?;
462 }
463
464 let write_result = (|| -> Result<(), MqdbError> {
465 let mut storage = Storage::create(&tmp_path)?;
466 let mut entries = Vec::with_capacity(self.documents.len());
467
468 for doc in &self.documents {
470 let first_block_page = storage.write_document(doc)?;
471 entries.push(CatalogEntry {
472 document_id: doc.id,
473 path: doc.path.as_ref().map(|p| p.to_string_lossy().into_owned()),
474 first_block_page,
475 num_blocks: doc.block_count,
476 zone_map_bytes: encode_zone_map(&doc.zone_maps),
477 index_start_page: 0,
478 });
479 }
480
481 for (i, doc) in self.documents.iter().enumerate() {
483 let idx = if let Some(cached) = self.doc_indexes.get(i).and_then(|o| o.as_ref()) {
484 std::borrow::Cow::Borrowed(cached)
485 } else {
486 std::borrow::Cow::Owned(DocumentIndex::build(&doc.blocks))
487 };
488 let bytes = idx.to_bytes();
489 entries[i].index_start_page = storage.write_index(&bytes)?;
490 }
491
492 let ct_guard = self.custom_tables.read().unwrap();
497 let mut custom = Vec::with_capacity(ct_guard.len());
498 for (name, state) in ct_guard.iter() {
499 let (first_row_page, last_row_page) = storage.write_table_rows(&state.rows)?;
500 custom.push(CustomTableEntry {
501 name: name.clone(),
502 columns: state.columns.clone(),
503 first_row_page,
504 last_row_page,
505 num_rows: state.rows.len() as u32,
506 });
507 }
508 drop(ct_guard);
509
510 storage.flush_catalog(&entries, &custom)?;
511 Ok(())
512 })();
513
514 if let Err(err) = write_result {
515 let _ = std::fs::remove_file(&tmp_path);
516 return Err(err);
517 }
518
519 std::fs::rename(&tmp_path, path)?;
520 Ok(())
521 }
522
523 pub fn open(path: impl AsRef<Path>) -> Result<Self, MqdbError> {
530 let mut storage = Storage::open(path.as_ref())?;
531 let (entries, custom_table_entries) = storage.load_catalog()?;
532 let cap = entries.len();
533 let mut documents = Vec::with_capacity(cap);
534 let mut max_doc_id = None;
535
536 for entry in entries {
537 let zone_maps = decode_zone_map(&entry.zone_map_bytes)?;
538 let document_id = entry.document_id;
539 let path = entry.path.map(PathBuf::from);
540 documents.push(Document::from_catalog_lazy(
541 document_id,
542 path,
543 entry.num_blocks,
544 zone_maps,
545 entry.first_block_page,
546 entry.index_start_page,
547 ));
548 max_doc_id =
549 Some(max_doc_id.map_or(document_id, |cur: DocumentId| cur.max(document_id)));
550 }
551
552 let mut custom_tables = HashMap::new();
553 for ct in custom_table_entries {
554 let rows = storage.read_table_rows(ct.first_row_page, ct.num_rows, ct.columns.len())?;
555 custom_tables.insert(
556 ct.name,
557 CustomTableState {
558 columns: ct.columns,
559 rows,
560 first_row_page: ct.first_row_page,
561 last_row_page: ct.last_row_page,
562 },
563 );
564 }
565
566 Ok(Self {
567 documents,
568 next_doc_id: max_doc_id.map_or(0, |id| id.saturating_add(1)),
569 store_spans: true,
570 storage: Mutex::new(Some(storage)),
571 doc_indexes: vec![None; cap],
572 custom_tables: RwLock::new(custom_tables),
573 })
574 }
575
576 pub fn load(path: impl AsRef<Path>) -> Result<Self, MqdbError> {
581 let mut storage = Storage::open(path.as_ref())?;
582 let (entries, custom_table_entries) = storage.load_catalog()?;
583 let cap = entries.len();
584 let mut documents = Vec::with_capacity(cap);
585 let mut max_doc_id = None;
586
587 for entry in entries {
588 let blocks = storage.read_blocks(entry.first_block_page, entry.num_blocks)?;
589 let zone_maps = decode_zone_map(&entry.zone_map_bytes)?;
590 let document_id = entry.document_id;
591 let path = entry.path.map(PathBuf::from);
592 let mut doc = Document::from_parts(document_id, path, blocks, zone_maps);
593 doc.index_start_page = entry.index_start_page;
594 documents.push(doc);
595 max_doc_id =
596 Some(max_doc_id.map_or(document_id, |cur: DocumentId| cur.max(document_id)));
597 }
598
599 let mut custom_tables = HashMap::new();
600 for ct in custom_table_entries {
601 let rows = storage.read_table_rows(ct.first_row_page, ct.num_rows, ct.columns.len())?;
602 custom_tables.insert(
603 ct.name,
604 CustomTableState {
605 columns: ct.columns,
606 rows,
607 first_row_page: ct.first_row_page,
608 last_row_page: ct.last_row_page,
609 },
610 );
611 }
612
613 Ok(Self {
614 documents,
615 next_doc_id: max_doc_id.map_or(0, |id| id.saturating_add(1)),
616 store_spans: true,
617 storage: Mutex::new(None),
618 doc_indexes: vec![None; cap],
619 custom_tables: RwLock::new(custom_tables),
620 })
621 }
622
623 pub fn load_catalog_only(path: impl AsRef<Path>) -> Result<Self, MqdbError> {
629 let mut storage = Storage::open(path.as_ref())?;
630 let (entries, _custom_table_entries) = storage.load_catalog()?;
631 let cap = entries.len();
632 let mut documents = Vec::with_capacity(cap);
633 let mut max_doc_id = None;
634
635 for entry in entries {
636 let zone_maps = decode_zone_map(&entry.zone_map_bytes)?;
637 let document_id = entry.document_id;
638 let path = entry.path.map(PathBuf::from);
639 documents.push(Document::from_catalog(
640 document_id,
641 path,
642 entry.num_blocks,
643 zone_maps,
644 ));
645 max_doc_id =
646 Some(max_doc_id.map_or(document_id, |cur: DocumentId| cur.max(document_id)));
647 }
648
649 Ok(Self {
650 documents,
651 next_doc_id: max_doc_id.map_or(0, |id| id.saturating_add(1)),
652 store_spans: true,
653 storage: Mutex::new(None),
654 doc_indexes: vec![None; cap],
655 custom_tables: RwLock::new(HashMap::new()),
656 })
657 }
658}