1use std::collections::HashMap;
4
5use wasm_dbms_api::prelude::{
6 DEFAULT_ALIGNMENT, DataSize, Encode, MSize, MemoryResult, Page, PageOffset, TableFingerprint,
7 TableSchema,
8};
9
10use crate::table_registry::{AutoincrementLedger, IndexLedger};
11use crate::{MemoryAccess, MemoryManager, MemoryProvider};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct TableRegistryPage {
16 pub pages_list_page: Page,
18 pub free_segments_page: Page,
20 pub index_registry_page: Page,
22 pub autoincrement_registry_page: Option<Page>,
25}
26
27#[derive(Debug, Default, Clone, PartialEq, Eq)]
29pub struct SchemaRegistry {
30 tables: HashMap<TableFingerprint, TableRegistryPage>,
31}
32
33impl SchemaRegistry {
34 pub fn load(mm: &mut MemoryManager<impl MemoryProvider>) -> MemoryResult<Self> {
36 let page = mm.schema_page();
37 let registry: Self = mm.read_at(page, 0)?;
38 Ok(registry)
39 }
40
41 pub fn register_table<TS>(
45 &mut self,
46 mm: &mut MemoryManager<impl MemoryProvider>,
47 ) -> MemoryResult<TableRegistryPage>
48 where
49 TS: TableSchema,
50 {
51 let fingerprint = TS::fingerprint();
53 if let Some(pages) = self.tables.get(&fingerprint) {
54 return Ok(*pages);
55 }
56
57 let pages_list_page = mm.allocate_page()?;
59 let free_segments_page = mm.allocate_page()?;
60 let index_registry_page = mm.allocate_page()?;
61 let has_autoincrement = TS::columns().iter().any(|col| col.auto_increment);
63 let autoincrement_registry_page = if has_autoincrement {
64 Some(mm.allocate_page()?)
65 } else {
66 None
67 };
68
69 let pages = TableRegistryPage {
71 pages_list_page,
72 free_segments_page,
73 index_registry_page,
74 autoincrement_registry_page,
75 };
76 self.tables.insert(fingerprint, pages);
77
78 let page = mm.schema_page();
80 mm.write_at(page, 0, self)?;
82
83 IndexLedger::init(pages.index_registry_page, TS::indexes(), mm)?;
85 if let Some(autoinc_page) = pages.autoincrement_registry_page {
87 AutoincrementLedger::init::<TS>(autoinc_page, mm)?;
88 }
89
90 Ok(pages)
91 }
92
93 pub fn save(&self, mm: &mut MemoryManager<impl MemoryProvider>) -> MemoryResult<()> {
95 let page = mm.schema_page();
96 mm.write_at(page, 0, self)
97 }
98
99 pub fn table_registry_page<TS>(&self) -> Option<TableRegistryPage>
101 where
102 TS: TableSchema,
103 {
104 self.tables.get(&TS::fingerprint()).copied()
105 }
106}
107
108impl Encode for SchemaRegistry {
109 const SIZE: DataSize = DataSize::Dynamic;
110
111 const ALIGNMENT: PageOffset = DEFAULT_ALIGNMENT;
112
113 fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
114 let mut buffer = Vec::with_capacity(self.size() as usize);
116 buffer.extend_from_slice(&(self.tables.len() as u64).to_le_bytes());
118 for (fingerprint, page) in &self.tables {
120 buffer.extend_from_slice(&fingerprint.to_le_bytes());
121 buffer.extend_from_slice(&page.pages_list_page.to_le_bytes());
122 buffer.extend_from_slice(&page.free_segments_page.to_le_bytes());
123 buffer.extend_from_slice(&page.index_registry_page.to_le_bytes());
124 if let Some(autoinc_page) = page.autoincrement_registry_page {
126 buffer.push(1); buffer.extend_from_slice(&autoinc_page.to_le_bytes());
128 } else {
129 buffer.push(0); }
131 }
132 std::borrow::Cow::Owned(buffer)
133 }
134
135 fn decode(data: std::borrow::Cow<[u8]>) -> MemoryResult<Self>
136 where
137 Self: Sized,
138 {
139 let mut offset = 0;
140 let len = u64::from_le_bytes(
142 data[offset..offset + 8]
143 .try_into()
144 .expect("failed to read length"),
145 ) as usize;
146 offset += 8;
147 let mut tables = HashMap::with_capacity(len);
148 for _ in 0..len {
150 let fingerprint = u64::from_le_bytes(data[offset..offset + 8].try_into()?);
151 offset += 8;
152 let pages_list_page = Page::from_le_bytes(data[offset..offset + 4].try_into()?);
153 offset += 4;
154 let deleted_records_page = Page::from_le_bytes(data[offset..offset + 4].try_into()?);
155 offset += 4;
156 let index_registry_page = Page::from_le_bytes(data[offset..offset + 4].try_into()?);
157 offset += 4;
158 let has_autoincrement = data[offset] == 1;
159 offset += 1;
160 let autoincrement_registry_page = if has_autoincrement {
161 let page = Page::from_le_bytes(data[offset..offset + 4].try_into()?);
162 offset += 4;
163 Some(page)
164 } else {
165 None
166 };
167 tables.insert(
168 fingerprint,
169 TableRegistryPage {
170 pages_list_page,
171 free_segments_page: deleted_records_page,
172 index_registry_page,
173 autoincrement_registry_page,
174 },
175 );
176 }
177 Ok(Self { tables })
178 }
179
180 fn size(&self) -> MSize {
181 let autoinc_pages = self
190 .tables
191 .values()
192 .filter(|page| page.autoincrement_registry_page.is_some())
193 .count() as MSize;
194
195 8 + (self.tables.len() as MSize * (4 * 3 + 8 + 1)) + (autoinc_pages * 4)
196 }
197}
198
199#[cfg(test)]
200mod tests {
201
202 use candid::CandidType;
203 use serde::{Deserialize, Serialize};
204 use wasm_dbms_api::prelude::{
205 ColumnDef, DbmsResult, IndexDef, InsertRecord, Int32, NoForeignFetcher, TableColumns,
206 TableRecord, UpdateRecord,
207 };
208
209 use super::*;
210 use crate::{HeapMemoryProvider, RecordAddress};
211
212 fn make_mm() -> MemoryManager<HeapMemoryProvider> {
213 MemoryManager::init(HeapMemoryProvider::default())
214 }
215
216 #[test]
217 fn test_should_encode_and_decode_schema_registry() {
218 let mut mm = make_mm();
219
220 let mut registry =
222 SchemaRegistry::load(&mut mm).expect("failed to load init schema registry");
223
224 let registry_page = registry
226 .register_table::<User>(&mut mm)
227 .expect("failed to register table");
228
229 let fetched_page = registry
231 .table_registry_page::<User>()
232 .expect("failed to get table registry page");
233 assert_eq!(registry_page, fetched_page);
234
235 let encoded = registry.encode();
237 let decoded = SchemaRegistry::decode(encoded).expect("failed to decode");
239 assert_eq!(registry, decoded);
240
241 let another_registry_page = registry
243 .register_table::<AnotherTable>(&mut mm)
244 .expect("failed to register another table");
245 let another_fetched_page = registry
246 .table_registry_page::<AnotherTable>()
247 .expect("failed to get another table registry page");
248 assert_eq!(another_registry_page, another_fetched_page);
249
250 let reloaded = SchemaRegistry::load(&mut mm).expect("failed to reload schema registry");
252 assert_eq!(registry, reloaded);
253 assert_eq!(reloaded.tables.len(), 2);
255 assert_eq!(
256 reloaded
257 .table_registry_page::<User>()
258 .expect("failed to get first table registry page after reload"),
259 registry_page
260 );
261 assert_eq!(
262 reloaded
263 .table_registry_page::<AnotherTable>()
264 .expect("failed to get second table registry page after reload"),
265 another_registry_page
266 );
267 }
268
269 #[test]
270 fn test_should_not_register_same_table_twice() {
271 let mut mm = make_mm();
272 let mut registry = SchemaRegistry::default();
273
274 let first_page = registry
275 .register_table::<User>(&mut mm)
276 .expect("failed to register table first time");
277 let second_page = registry
278 .register_table::<User>(&mut mm)
279 .expect("failed to register table second time");
280
281 assert_eq!(first_page, second_page);
282 assert_eq!(registry.tables.len(), 1);
283 }
284
285 #[test]
286 fn test_should_init_index_ledger() {
287 let mut mm = make_mm();
288 let mut registry = SchemaRegistry::default();
289
290 let pages = registry
291 .register_table::<User>(&mut mm)
292 .expect("failed to register table");
293
294 let mut index_ledger = IndexLedger::load(pages.index_registry_page, &mut mm)
296 .expect("failed to load index ledger");
297
298 index_ledger
300 .insert(
301 &["id"],
302 Int32::from(1i32),
303 RecordAddress { page: 1, offset: 0 },
304 &mut mm,
305 )
306 .expect("failed to insert index");
307 let result = index_ledger
309 .search(&["id"], &Int32::from(1i32), &mut mm)
310 .expect("failed to search index")
311 .get(0)
312 .copied()
313 .expect("no index at 0");
314 assert_eq!(result, RecordAddress { page: 1, offset: 0 });
315 }
316
317 #[derive(Clone, CandidType)]
318 struct AnotherTable;
319
320 impl Encode for AnotherTable {
321 const SIZE: DataSize = DataSize::Dynamic;
322
323 const ALIGNMENT: PageOffset = DEFAULT_ALIGNMENT;
324
325 fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
326 std::borrow::Cow::Owned(vec![])
327 }
328
329 fn decode(_data: std::borrow::Cow<[u8]>) -> MemoryResult<Self>
330 where
331 Self: Sized,
332 {
333 Ok(AnotherTable)
334 }
335
336 fn size(&self) -> MSize {
337 0
338 }
339 }
340
341 #[derive(Clone, CandidType, Deserialize)]
342 struct AnotherTableRecord;
343
344 impl TableRecord for AnotherTableRecord {
345 type Schema = AnotherTable;
346
347 fn from_values(_values: TableColumns) -> Self {
348 AnotherTableRecord
349 }
350
351 fn to_values(&self) -> Vec<(ColumnDef, wasm_dbms_api::prelude::Value)> {
352 vec![]
353 }
354 }
355
356 #[derive(Clone, CandidType, Serialize)]
357 struct AnotherTableInsert;
358
359 impl InsertRecord for AnotherTableInsert {
360 type Record = AnotherTableRecord;
361 type Schema = AnotherTable;
362
363 fn from_values(_values: &[(ColumnDef, wasm_dbms_api::prelude::Value)]) -> DbmsResult<Self> {
364 Ok(AnotherTableInsert)
365 }
366
367 fn into_values(self) -> Vec<(ColumnDef, wasm_dbms_api::prelude::Value)> {
368 vec![]
369 }
370
371 fn into_record(self) -> Self::Schema {
372 AnotherTable
373 }
374 }
375
376 #[derive(Clone, CandidType, Serialize)]
377 struct AnotherTableUpdate;
378
379 impl UpdateRecord for AnotherTableUpdate {
380 type Record = AnotherTableRecord;
381 type Schema = AnotherTable;
382
383 fn from_values(
384 _values: &[(ColumnDef, wasm_dbms_api::prelude::Value)],
385 _where_clause: Option<wasm_dbms_api::prelude::Filter>,
386 ) -> Self {
387 AnotherTableUpdate
388 }
389
390 fn update_values(&self) -> Vec<(ColumnDef, wasm_dbms_api::prelude::Value)> {
391 vec![]
392 }
393
394 fn where_clause(&self) -> Option<wasm_dbms_api::prelude::Filter> {
395 None
396 }
397 }
398
399 impl TableSchema for AnotherTable {
400 type Record = AnotherTableRecord;
401 type Insert = AnotherTableInsert;
402 type Update = AnotherTableUpdate;
403 type ForeignFetcher = NoForeignFetcher;
404
405 fn table_name() -> &'static str {
406 "another_table"
407 }
408
409 fn columns() -> &'static [wasm_dbms_api::prelude::ColumnDef] {
410 &[]
411 }
412
413 fn primary_key() -> &'static str {
414 ""
415 }
416
417 fn indexes() -> &'static [wasm_dbms_api::prelude::IndexDef] {
418 &[]
419 }
420
421 fn to_values(self) -> Vec<(ColumnDef, wasm_dbms_api::prelude::Value)> {
422 vec![]
423 }
424
425 fn sanitizer(
426 _column_name: &'static str,
427 ) -> Option<Box<dyn wasm_dbms_api::prelude::Sanitize>> {
428 None
429 }
430
431 fn validator(
432 _column_name: &'static str,
433 ) -> Option<Box<dyn wasm_dbms_api::prelude::Validate>> {
434 None
435 }
436 }
437
438 #[derive(Clone, CandidType)]
441 struct User;
442
443 impl Encode for User {
444 const SIZE: DataSize = DataSize::Dynamic;
445 const ALIGNMENT: PageOffset = DEFAULT_ALIGNMENT;
446
447 fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
448 std::borrow::Cow::Owned(vec![])
449 }
450
451 fn decode(_data: std::borrow::Cow<[u8]>) -> MemoryResult<Self>
452 where
453 Self: Sized,
454 {
455 Ok(User)
456 }
457
458 fn size(&self) -> MSize {
459 0
460 }
461 }
462
463 #[derive(Clone, CandidType, Deserialize)]
464 struct UserRecord;
465
466 impl TableRecord for UserRecord {
467 type Schema = User;
468
469 fn from_values(_values: TableColumns) -> Self {
470 UserRecord
471 }
472
473 fn to_values(&self) -> Vec<(ColumnDef, wasm_dbms_api::prelude::Value)> {
474 vec![]
475 }
476 }
477
478 #[derive(Clone, CandidType, Serialize)]
479 struct UserInsert;
480
481 impl InsertRecord for UserInsert {
482 type Record = UserRecord;
483 type Schema = User;
484
485 fn from_values(_values: &[(ColumnDef, wasm_dbms_api::prelude::Value)]) -> DbmsResult<Self> {
486 Ok(UserInsert)
487 }
488
489 fn into_values(self) -> Vec<(ColumnDef, wasm_dbms_api::prelude::Value)> {
490 vec![]
491 }
492
493 fn into_record(self) -> Self::Schema {
494 User
495 }
496 }
497
498 #[derive(Clone, CandidType, Serialize)]
499 struct UserUpdate;
500
501 impl UpdateRecord for UserUpdate {
502 type Record = UserRecord;
503 type Schema = User;
504
505 fn from_values(
506 _values: &[(ColumnDef, wasm_dbms_api::prelude::Value)],
507 _where_clause: Option<wasm_dbms_api::prelude::Filter>,
508 ) -> Self {
509 UserUpdate
510 }
511
512 fn update_values(&self) -> Vec<(ColumnDef, wasm_dbms_api::prelude::Value)> {
513 vec![]
514 }
515
516 fn where_clause(&self) -> Option<wasm_dbms_api::prelude::Filter> {
517 None
518 }
519 }
520
521 impl TableSchema for User {
522 type Record = UserRecord;
523 type Insert = UserInsert;
524 type Update = UserUpdate;
525 type ForeignFetcher = NoForeignFetcher;
526
527 fn table_name() -> &'static str {
528 "users"
529 }
530
531 fn columns() -> &'static [wasm_dbms_api::prelude::ColumnDef] {
532 &[]
533 }
534
535 fn primary_key() -> &'static str {
536 "id"
537 }
538
539 fn indexes() -> &'static [wasm_dbms_api::prelude::IndexDef] {
540 &[IndexDef(&["id"])]
541 }
542
543 fn to_values(self) -> Vec<(ColumnDef, wasm_dbms_api::prelude::Value)> {
544 vec![]
545 }
546
547 fn sanitizer(
548 _column_name: &'static str,
549 ) -> Option<Box<dyn wasm_dbms_api::prelude::Sanitize>> {
550 None
551 }
552
553 fn validator(
554 _column_name: &'static str,
555 ) -> Option<Box<dyn wasm_dbms_api::prelude::Validate>> {
556 None
557 }
558 }
559
560 #[test]
561 fn test_table_registry_page_returns_none_for_unregistered_table() {
562 let registry = SchemaRegistry::default();
563 assert!(registry.table_registry_page::<User>().is_none());
564 }
565
566 #[test]
567 fn test_empty_registry_encode_decode() {
568 let registry = SchemaRegistry::default();
569 let encoded = registry.encode();
570 let decoded = SchemaRegistry::decode(encoded).expect("failed to decode empty registry");
571 assert_eq!(registry, decoded);
572 assert_eq!(decoded.tables.len(), 0);
573 }
574
575 #[test]
576 fn test_load_fresh_memory_returns_empty_registry() {
577 let mut mm = make_mm();
578 let registry = SchemaRegistry::load(&mut mm).expect("failed to load from fresh memory");
579 assert_eq!(registry.tables.len(), 0);
580 }
581
582 #[test]
583 fn test_save_and_reload() {
584 let mut mm = make_mm();
585 let mut registry = SchemaRegistry::default();
586 registry
587 .register_table::<User>(&mut mm)
588 .expect("failed to register");
589 registry
591 .register_table::<AnotherTable>(&mut mm)
592 .expect("failed to register another");
593 registry.save(&mut mm).expect("failed to save");
594
595 let reloaded = SchemaRegistry::load(&mut mm).expect("failed to reload");
596 assert_eq!(reloaded.tables.len(), 2);
597 assert_eq!(registry, reloaded);
598 }
599
600 #[test]
601 fn test_schema_registry_size() {
602 let mut mm = make_mm();
603 let mut registry = SchemaRegistry::default();
604 assert_eq!(registry.size(), 8);
606 registry
607 .register_table::<User>(&mut mm)
608 .expect("failed to register");
609 assert_eq!(registry.size(), 29);
612 }
613
614 #[test]
615 fn test_should_allocate_autoincrement_page_when_column_has_autoincrement() {
616 let mut mm = make_mm();
617 let mut registry = SchemaRegistry::default();
618
619 let pages = registry
620 .register_table::<AutoincrementTable>(&mut mm)
621 .expect("failed to register autoincrement table");
622
623 assert!(
624 pages.autoincrement_registry_page.is_some(),
625 "autoincrement registry page should be allocated for tables with autoincrement columns"
626 );
627 }
628
629 #[test]
630 fn test_should_not_allocate_autoincrement_page_when_no_autoincrement_column() {
631 let mut mm = make_mm();
632 let mut registry = SchemaRegistry::default();
633
634 let pages = registry
635 .register_table::<User>(&mut mm)
636 .expect("failed to register user table");
637
638 assert!(
639 pages.autoincrement_registry_page.is_none(),
640 "autoincrement registry page should not be allocated for tables without autoincrement columns"
641 );
642 }
643
644 #[test]
645 fn test_schema_registry_size_with_autoincrement() {
646 let mut mm = make_mm();
647 let mut registry = SchemaRegistry::default();
648
649 registry
650 .register_table::<AutoincrementTable>(&mut mm)
651 .expect("failed to register");
652 assert_eq!(registry.size(), 33);
655 }
656
657 #[test]
658 fn test_should_encode_and_decode_registry_with_autoincrement() {
659 let mut mm = make_mm();
660 let mut registry = SchemaRegistry::default();
661
662 registry
663 .register_table::<AutoincrementTable>(&mut mm)
664 .expect("failed to register");
665
666 let encoded = registry.encode();
667 let decoded = SchemaRegistry::decode(encoded).expect("failed to decode");
668 assert_eq!(registry, decoded);
669
670 let page = decoded
671 .table_registry_page::<AutoincrementTable>()
672 .expect("missing autoincrement table");
673 assert!(page.autoincrement_registry_page.is_some());
674 }
675
676 #[derive(Clone, CandidType)]
679 struct AutoincrementTable;
680
681 impl Encode for AutoincrementTable {
682 const SIZE: DataSize = DataSize::Dynamic;
683 const ALIGNMENT: PageOffset = DEFAULT_ALIGNMENT;
684
685 fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
686 std::borrow::Cow::Owned(vec![])
687 }
688
689 fn decode(_data: std::borrow::Cow<[u8]>) -> MemoryResult<Self>
690 where
691 Self: Sized,
692 {
693 Ok(AutoincrementTable)
694 }
695
696 fn size(&self) -> MSize {
697 0
698 }
699 }
700
701 #[derive(Clone, CandidType, Deserialize)]
702 struct AutoincrementTableRecord;
703
704 impl TableRecord for AutoincrementTableRecord {
705 type Schema = AutoincrementTable;
706
707 fn from_values(_values: TableColumns) -> Self {
708 AutoincrementTableRecord
709 }
710
711 fn to_values(&self) -> Vec<(ColumnDef, wasm_dbms_api::prelude::Value)> {
712 vec![]
713 }
714 }
715
716 #[derive(Clone, CandidType, Serialize)]
717 struct AutoincrementTableInsert;
718
719 impl InsertRecord for AutoincrementTableInsert {
720 type Record = AutoincrementTableRecord;
721 type Schema = AutoincrementTable;
722
723 fn from_values(_values: &[(ColumnDef, wasm_dbms_api::prelude::Value)]) -> DbmsResult<Self> {
724 Ok(AutoincrementTableInsert)
725 }
726
727 fn into_values(self) -> Vec<(ColumnDef, wasm_dbms_api::prelude::Value)> {
728 vec![]
729 }
730
731 fn into_record(self) -> Self::Schema {
732 AutoincrementTable
733 }
734 }
735
736 #[derive(Clone, CandidType, Serialize)]
737 struct AutoincrementTableUpdate;
738
739 impl UpdateRecord for AutoincrementTableUpdate {
740 type Record = AutoincrementTableRecord;
741 type Schema = AutoincrementTable;
742
743 fn from_values(
744 _values: &[(ColumnDef, wasm_dbms_api::prelude::Value)],
745 _where_clause: Option<wasm_dbms_api::prelude::Filter>,
746 ) -> Self {
747 AutoincrementTableUpdate
748 }
749
750 fn update_values(&self) -> Vec<(ColumnDef, wasm_dbms_api::prelude::Value)> {
751 vec![]
752 }
753
754 fn where_clause(&self) -> Option<wasm_dbms_api::prelude::Filter> {
755 None
756 }
757 }
758
759 impl TableSchema for AutoincrementTable {
760 type Record = AutoincrementTableRecord;
761 type Insert = AutoincrementTableInsert;
762 type Update = AutoincrementTableUpdate;
763 type ForeignFetcher = NoForeignFetcher;
764
765 fn table_name() -> &'static str {
766 "autoincrement_table"
767 }
768
769 fn columns() -> &'static [ColumnDef] {
770 use wasm_dbms_api::prelude::DataTypeKind;
771
772 &[ColumnDef {
773 name: "id",
774 data_type: DataTypeKind::Uint32,
775 auto_increment: true,
776 nullable: false,
777 primary_key: true,
778 unique: true,
779 foreign_key: None,
780 }]
781 }
782
783 fn primary_key() -> &'static str {
784 "id"
785 }
786
787 fn indexes() -> &'static [IndexDef] {
788 &[IndexDef(&["id"])]
789 }
790
791 fn to_values(self) -> Vec<(ColumnDef, wasm_dbms_api::prelude::Value)> {
792 vec![]
793 }
794
795 fn sanitizer(
796 _column_name: &'static str,
797 ) -> Option<Box<dyn wasm_dbms_api::prelude::Sanitize>> {
798 None
799 }
800
801 fn validator(
802 _column_name: &'static str,
803 ) -> Option<Box<dyn wasm_dbms_api::prelude::Validate>> {
804 None
805 }
806 }
807}