Skip to main content

wasm_dbms_memory/
table_registry.rs

1// Rust guideline compliant 2026-02-28
2
3mod free_segments_ledger;
4mod page_ledger;
5mod raw_record;
6mod table_reader;
7mod write_at;
8
9use wasm_dbms_api::prelude::{Encode, MemoryResult, Page, PageOffset};
10
11use self::free_segments_ledger::FreeSegmentsLedger;
12use self::page_ledger::PageLedger;
13use self::raw_record::RawRecord;
14pub use self::table_reader::{NextRecord, TableReader};
15use self::write_at::WriteAt;
16use crate::{MemoryAccess, TableRegistryPage, align_up};
17
18/// The table registry takes care of storing the records for each table,
19/// using the [`FreeSegmentsLedger`] and [`PageLedger`] to derive exactly where to read/write.
20///
21/// A registry is generic over a record, which must implement [`Encode`].
22///
23/// The CRUD operations provided by the table registry do NOT perform any logical checks,
24/// but just allow to read/write records from/to memory.
25/// So CRUD checks must be performed by a higher layer, prior to calling these methods.
26pub struct TableRegistry {
27    free_segments_ledger: FreeSegmentsLedger,
28    pub(crate) page_ledger: PageLedger,
29}
30
31impl TableRegistry {
32    /// Loads the table registry from memory.
33    pub fn load(table_pages: TableRegistryPage, mm: &impl MemoryAccess) -> MemoryResult<Self> {
34        Ok(Self {
35            free_segments_ledger: FreeSegmentsLedger::load(table_pages.free_segments_page, mm)?,
36            page_ledger: PageLedger::load(table_pages.pages_list_page, mm)?,
37        })
38    }
39
40    /// Inserts a new record into the table registry.
41    ///
42    /// NOTE: this function does NOT make any logical checks on the record being inserted.
43    pub fn insert<E>(&mut self, record: E, mm: &mut impl MemoryAccess) -> MemoryResult<()>
44    where
45        E: Encode,
46    {
47        // get position to write the record
48        let raw_record = RawRecord::new(record);
49        let write_at = self.get_write_position(&raw_record, mm)?;
50
51        // align insert
52        let aligned_offset = align_up::<E>(write_at.offset() as usize) as PageOffset;
53
54        // write record
55        mm.write_at(write_at.page(), aligned_offset, &raw_record)?;
56
57        // commit post-write actions
58        self.post_write(write_at, &raw_record, mm)
59    }
60
61    /// Creates a [`TableReader`] to read records from the table registry.
62    ///
63    /// Use [`TableReader::try_next`] to read records one by one.
64    pub fn read<'a, E, MA>(&'a self, mm: &'a MA) -> TableReader<'a, E, MA>
65    where
66        E: Encode,
67        MA: MemoryAccess,
68    {
69        TableReader::new(&self.page_ledger, mm)
70    }
71
72    /// Deletes a record at the given page and offset.
73    ///
74    /// The space occupied by the record is marked as free and zeroed.
75    pub fn delete(
76        &mut self,
77        record: impl Encode,
78        page: Page,
79        offset: PageOffset,
80        mm: &mut impl MemoryAccess,
81    ) -> MemoryResult<()> {
82        let raw_record = RawRecord::new(record);
83
84        // zero the record in memory
85        mm.zero(page, offset, &raw_record)?;
86
87        // insert a free segment for the deleted record
88        self.free_segments_ledger
89            .insert_free_segment(page, offset, &raw_record, mm)
90    }
91
92    /// Updates a record at the given page and offset.
93    ///
94    /// The logic is the following:
95    ///
96    /// 1. If the new record has exactly the same size of the old record, overwrite it in place.
97    /// 2. If the new record does not fit, delete the old record and insert the new record.
98    pub fn update(
99        &mut self,
100        new_record: impl Encode,
101        old_record: impl Encode,
102        old_page: Page,
103        old_offset: PageOffset,
104        mm: &mut impl MemoryAccess,
105    ) -> MemoryResult<()> {
106        if new_record.size() == old_record.size() {
107            self.update_in_place(new_record, old_page, old_offset, mm)
108        } else {
109            self.update_by_realloc(new_record, old_record, old_page, old_offset, mm)
110        }
111    }
112
113    /// Update a [`RawRecord`] in place at the given page and offset.
114    ///
115    /// This must be used IF AND ONLY if the new record has the SAME size as the old record.
116    fn update_in_place(
117        &mut self,
118        record: impl Encode,
119        page: Page,
120        offset: PageOffset,
121        mm: &mut impl MemoryAccess,
122    ) -> MemoryResult<()> {
123        let raw_record = RawRecord::new(record);
124        mm.write_at(page, offset, &raw_record)
125    }
126
127    /// Updates a record by reallocating it.
128    ///
129    /// The old record is deleted and the new record is inserted.
130    fn update_by_realloc(
131        &mut self,
132        new_record: impl Encode,
133        old_record: impl Encode,
134        old_page: Page,
135        old_offset: PageOffset,
136        mm: &mut impl MemoryAccess,
137    ) -> MemoryResult<()> {
138        // delete old record
139        self.delete(old_record, old_page, old_offset, mm)?;
140
141        // insert new record
142        self.insert(new_record, mm)
143    }
144
145    /// Gets the position where to write a record of the given size.
146    fn get_write_position<E>(
147        &mut self,
148        record: &RawRecord<E>,
149        mm: &mut impl MemoryAccess,
150    ) -> MemoryResult<WriteAt>
151    where
152        E: Encode,
153    {
154        // check if there is a free segment that can hold the record
155        if let Some(segment) = self
156            .free_segments_ledger
157            .find_reusable_segment(record, mm)?
158        {
159            return Ok(WriteAt::ReusedSegment(segment));
160        }
161
162        // otherwise, write at the end of the table
163        self.page_ledger
164            .get_page_and_offset_for_record(record, mm)
165            .map(|(page, offset)| WriteAt::End(page, offset))
166    }
167
168    /// Commits the post-write actions after writing a record at the given position.
169    ///
170    /// - If the record was a [`WriteAt::ReusedSegment`], the free segment is marked as used.
171    /// - If the record was a [`WriteAt::End`], the page ledger is updated.
172    fn post_write<E>(
173        &mut self,
174        write_at: WriteAt,
175        record: &RawRecord<E>,
176        mm: &mut impl MemoryAccess,
177    ) -> MemoryResult<()>
178    where
179        E: Encode,
180    {
181        match write_at {
182            WriteAt::ReusedSegment(free_segment) => {
183                // mark segment as used
184                self.free_segments_ledger
185                    .commit_reused_space(record, free_segment, mm)
186            }
187            WriteAt::End(page, ..) => {
188                // update page ledger
189                self.page_ledger.commit(page, record, mm)
190            }
191        }
192    }
193}
194
195/// Test utilities shared across the table_registry submodules.
196#[cfg(test)]
197pub(crate) mod test_utils {
198    use wasm_dbms_api::prelude::{
199        DEFAULT_ALIGNMENT, DataSize, DecodeError, Encode, MSize, MemoryError, MemoryResult,
200        PageOffset,
201    };
202
203    /// A simple user struct for testing purposes (no macro dependencies).
204    #[derive(Debug, Clone, PartialEq, Eq)]
205    pub struct User {
206        pub id: u32,
207        pub name: String,
208        pub email: String,
209        pub age: u32,
210    }
211
212    impl Encode for User {
213        const SIZE: DataSize = DataSize::Dynamic;
214
215        const ALIGNMENT: PageOffset = DEFAULT_ALIGNMENT;
216
217        fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
218            let mut buf = Vec::new();
219            // id: 4 bytes
220            buf.extend_from_slice(&self.id.to_le_bytes());
221            // name length: 2 bytes + name bytes
222            buf.extend_from_slice(&(self.name.len() as u16).to_le_bytes());
223            buf.extend_from_slice(self.name.as_bytes());
224            // email length: 2 bytes + email bytes
225            buf.extend_from_slice(&(self.email.len() as u16).to_le_bytes());
226            buf.extend_from_slice(self.email.as_bytes());
227            // age: 4 bytes
228            buf.extend_from_slice(&self.age.to_le_bytes());
229            std::borrow::Cow::Owned(buf)
230        }
231
232        fn decode(data: std::borrow::Cow<[u8]>) -> MemoryResult<Self>
233        where
234            Self: Sized,
235        {
236            if data.len() < 12 {
237                return Err(MemoryError::DecodeError(DecodeError::TooShort));
238            }
239            let mut offset = 0;
240            // id
241            let id = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap());
242            offset += 4;
243            // name
244            let name_len =
245                u16::from_le_bytes(data[offset..offset + 2].try_into().unwrap()) as usize;
246            offset += 2;
247            let name = String::from_utf8_lossy(&data[offset..offset + name_len]).to_string();
248            offset += name_len;
249            // email
250            let email_len =
251                u16::from_le_bytes(data[offset..offset + 2].try_into().unwrap()) as usize;
252            offset += 2;
253            let email = String::from_utf8_lossy(&data[offset..offset + email_len]).to_string();
254            offset += email_len;
255            // age
256            let age = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap());
257
258            Ok(User {
259                id,
260                name,
261                email,
262                age,
263            })
264        }
265
266        fn size(&self) -> MSize {
267            (4 + 2 + self.name.len() + 2 + self.email.len() + 4) as MSize
268        }
269    }
270}
271
272#[cfg(test)]
273mod tests {
274
275    use self::test_utils::User;
276    use super::free_segments_ledger::FreeSegment;
277    use super::table_reader::NextRecord;
278    use super::*;
279    use crate::{HeapMemoryProvider, MemoryManager};
280
281    #[test]
282    fn test_should_create_table_registry() {
283        let mut mm = MemoryManager::init(HeapMemoryProvider::default());
284        let page_ledger_page = mm.allocate_page().expect("failed to get page");
285        let free_segments_page = mm.allocate_page().expect("failed to get page");
286        let table_pages = TableRegistryPage {
287            pages_list_page: page_ledger_page,
288            free_segments_page,
289        };
290
291        let registry: MemoryResult<TableRegistry> = TableRegistry::load(table_pages, &mm);
292        assert!(registry.is_ok());
293    }
294
295    #[test]
296    fn test_should_get_write_at_end() {
297        let mut mm = MemoryManager::init(HeapMemoryProvider::default());
298        let mut registry = registry(&mut mm);
299
300        let record = RawRecord::new(User {
301            id: 1,
302            name: "Test".to_string(),
303            email: "new_user@example.com".to_string(),
304            age: 25,
305        });
306        let write_at = registry
307            .get_write_position(&record, &mut mm)
308            .expect("failed to get write at");
309
310        assert!(matches!(write_at, WriteAt::End(_, 0)));
311    }
312
313    #[test]
314    fn test_should_get_write_at_free_segment() {
315        let mut mm = MemoryManager::init(HeapMemoryProvider::default());
316        let mut registry = registry(&mut mm);
317
318        let record = RawRecord::new(User {
319            id: 1,
320            name: "Test".to_string(),
321            email: "new_user@example.com".to_string(),
322            age: 25,
323        });
324        // allocate a page to insert a free segment
325        let (page, _) = registry
326            .page_ledger
327            .get_page_and_offset_for_record(&record, &mut mm)
328            .expect("failed to get page and offset");
329        registry
330            .page_ledger
331            .commit(page, &record, &mut mm)
332            .expect("failed to commit page ledger");
333        // insert data about a free segment
334        registry
335            .free_segments_ledger
336            .insert_free_segment(page, 256, &record, &mut mm)
337            .expect("failed to insert free segment");
338
339        let write_at = registry
340            .get_write_position(&record, &mut mm)
341            .expect("failed to get write at");
342
343        let reused_segment = match write_at {
344            WriteAt::ReusedSegment(segment) => segment.segment,
345            _ => panic!("expected reused segment"),
346        };
347
348        assert_eq!(
349            reused_segment,
350            FreeSegment {
351                page,
352                offset: 256,
353                size: 64, // padded size
354            }
355        );
356    }
357
358    #[test]
359    fn test_should_insert_record_into_table_registry() {
360        let mut mm = MemoryManager::init(HeapMemoryProvider::default());
361        let mut registry = registry(&mut mm);
362
363        let record = User {
364            id: 1,
365            name: "Test".to_string(),
366            email: "new_user@example.com".to_string(),
367            age: 25,
368        };
369
370        // insert record
371        assert!(registry.insert(record, &mut mm).is_ok());
372    }
373
374    #[test]
375    fn test_should_manage_to_insert_users_to_exceed_one_page() {
376        let mut mm = MemoryManager::init(HeapMemoryProvider::default());
377        let mut registry = registry(&mut mm);
378
379        for id in 0..4000 {
380            let record = User {
381                id,
382                name: format!("User {}", id),
383                email: "new_user@example.com".to_string(),
384                age: 20 + id,
385            };
386            registry
387                .insert(record, &mut mm)
388                .expect("failed to insert record");
389        }
390    }
391
392    #[test]
393    fn test_should_delete_record() {
394        let mut mm = MemoryManager::init(HeapMemoryProvider::default());
395        let mut registry = registry(&mut mm);
396
397        let record = User {
398            id: 1,
399            name: "Test".to_string(),
400            email: "new_user@example.com".to_string(),
401            age: 25,
402        };
403
404        // insert record
405        registry
406            .insert(record.clone(), &mut mm)
407            .expect("failed to insert");
408
409        // find where it was written
410        let mut reader = registry.read(&mm);
411        let next_record: NextRecord<User> = reader
412            .try_next()
413            .expect("failed to read")
414            .expect("no record");
415        let page = next_record.page;
416        let offset = next_record.offset;
417        let record = next_record.record;
418        let raw_user = RawRecord::new(record.clone());
419        let raw_user_size = raw_user.size();
420
421        // delete record
422        assert!(registry.delete(record, page, offset, &mut mm).is_ok());
423
424        // should have been deleted
425        let mut reader = registry.read::<User, _>(&mm);
426        assert!(reader.try_next().expect("failed to read").is_none());
427
428        // should have a free segment
429        let free_segment = registry
430            .free_segments_ledger
431            .find_reusable_segment(
432                &User {
433                    id: 2,
434                    name: "Test".to_string(),
435                    email: "new_user@example.com".to_string(),
436                    age: 25,
437                },
438                &mm,
439            )
440            .expect("failed to find free segment")
441            .expect("could not find the free segment after free")
442            .segment;
443        assert_eq!(free_segment.page, page);
444        assert_eq!(free_segment.offset, offset);
445        assert_eq!(free_segment.size, 64); // padded
446
447        // should have zeroed the memory
448        let mut buffer = vec![0u8; raw_user_size as usize];
449        mm.read_at_raw(page, offset, &mut buffer)
450            .expect("failed to read memory");
451        assert!(buffer.iter().all(|&b| b == 0));
452    }
453
454    #[test]
455    fn test_should_update_record_in_place() {
456        let mut mm = MemoryManager::init(HeapMemoryProvider::default());
457        let mut registry = registry(&mut mm);
458
459        let old_record = User {
460            id: 1,
461            name: "John".to_string(),
462            email: "new_user@example.com".to_string(),
463            age: 28,
464        };
465        let new_record = User {
466            id: 1,
467            name: "Mark".to_string(), // same length as "John"
468            email: "new_user@example.com".to_string(),
469            age: 30,
470        };
471
472        // insert old record
473        registry
474            .insert(old_record.clone(), &mut mm)
475            .expect("failed to insert");
476
477        // find where it was written
478        let mut reader = registry.read::<User, _>(&mm);
479        let next_record = reader
480            .try_next()
481            .expect("failed to read")
482            .expect("no record");
483        let page = next_record.page;
484        let offset = next_record.offset;
485
486        // update in place
487        assert!(
488            registry
489                .update(
490                    new_record.clone(),
491                    next_record.record.clone(),
492                    page,
493                    offset,
494                    &mut mm,
495                )
496                .is_ok()
497        );
498
499        // read back the record
500        let mut reader = registry.read::<User, _>(&mm);
501        let next_record = reader
502            .try_next()
503            .expect("failed to read")
504            .expect("no record");
505        assert_eq!(next_record.page, page); // should be same page
506        assert_eq!(next_record.offset, offset); // should be same offset
507        assert_eq!(next_record.record, new_record);
508    }
509
510    #[test]
511    fn test_should_update_record_reallocating() {
512        let mut mm = MemoryManager::init(HeapMemoryProvider::default());
513        let mut registry = registry(&mut mm);
514
515        let old_record = User {
516            id: 1,
517            name: "John".to_string(),
518            email: "new_user@example.com".to_string(),
519            age: 28,
520        };
521        // this user creates a record with same size as old_record to avoid reusing the free segment
522        let extra_user = User {
523            id: 2,
524            name: "Extra".to_string(),
525            email: "new_user@example.com".to_string(),
526            age: 25,
527        };
528        let new_record = User {
529            id: 1,
530            name: "Alexanderejruwgjowergjioewrgjioewrigjewriogjweoirgjiowerjgoiwerjiogewirogjowejrgiwer".to_string(), // must exceed padding
531            email: "new_user@example.com".to_string(),
532            age: 30,
533        };
534
535        // insert old record
536        registry
537            .insert(old_record.clone(), &mut mm)
538            .expect("failed to insert");
539        // insert extra record to avoid reusing the free segment
540        registry
541            .insert(extra_user.clone(), &mut mm)
542            .expect("failed to insert extra user");
543
544        // find where it was written
545        let mut reader = registry.read::<User, _>(&mm);
546        let old_record_from_db = reader
547            .try_next()
548            .expect("failed to read")
549            .expect("no record");
550        assert_eq!(old_record_from_db.record, old_record);
551        let page = old_record_from_db.page;
552        let offset = old_record_from_db.offset;
553
554        // update by reallocating
555        assert!(
556            registry
557                .update(
558                    new_record.clone(),
559                    old_record_from_db.record.clone(),
560                    page,
561                    offset,
562                    &mut mm,
563                )
564                .is_ok()
565        );
566
567        // read back the record
568        let mut reader = registry.read::<User, _>(&mm);
569
570        // find extra record first
571        let _ = reader
572            .try_next()
573            .expect("failed to read")
574            .expect("no record");
575
576        let updated_record = reader
577            .try_next()
578            .expect("failed to read")
579            .expect("no record");
580        assert_ne!(updated_record.offset, offset); // should be different offset
581        assert_eq!(updated_record.record, new_record);
582    }
583
584    #[test]
585    fn test_should_insert_delete_insert_many() {
586        const COUNT: u32 = 1_000;
587        let mut mm = MemoryManager::init(HeapMemoryProvider::default());
588        let mut registry = registry(&mut mm);
589        for id in 0..COUNT {
590            let record = User {
591                id,
592                name: format!("User {id}"),
593                email: format!("user_{id}@example.com"),
594                age: 20,
595            };
596
597            // insert record
598            registry
599                .insert(record.clone(), &mut mm)
600                .expect("failed to insert");
601        }
602
603        // delete odd records
604        for id in (0..COUNT).filter(|id| id % 2 == 1) {
605            let record = User {
606                id,
607                name: format!("User {id}"),
608                email: format!("user_{id}@example.com"),
609                age: 20,
610            };
611            // find where it was written
612            let mut reader = registry.read::<User, _>(&mm);
613            let mut deleted = false;
614            while let Some(next_record) = reader.try_next().expect("failed to read") {
615                if next_record.record.id == id {
616                    registry
617                        .delete(
618                            record.clone(),
619                            next_record.page,
620                            next_record.offset,
621                            &mut mm,
622                        )
623                        .expect("failed to delete");
624                    deleted = true;
625                    break;
626                }
627            }
628            assert!(deleted, "record with id {} was not found", id);
629        }
630
631        // now delete also the others
632        for id in (0..COUNT).filter(|id| id % 2 == 0) {
633            let record = User {
634                id,
635                name: format!("User {id}"),
636                email: format!("user_{id}@example.com"),
637                age: 20,
638            };
639            // find where it was written
640            let mut reader = registry.read::<User, _>(&mm);
641            let mut deleted = false;
642            while let Some(next_record) = reader.try_next().expect("failed to read") {
643                if next_record.record.id == id {
644                    registry
645                        .delete(
646                            record.clone(),
647                            next_record.page,
648                            next_record.offset,
649                            &mut mm,
650                        )
651                        .expect("failed to delete");
652                    deleted = true;
653                    break;
654                }
655            }
656            assert!(deleted, "record with id {} was not found", id);
657        }
658
659        // insert back
660        for id in 0..COUNT {
661            let record = User {
662                id,
663                name: format!("User {id}"),
664                email: format!("user_{id}@example.com"),
665                age: 20,
666            };
667
668            // insert record
669            registry
670                .insert(record.clone(), &mut mm)
671                .expect("failed to insert");
672        }
673    }
674
675    #[test]
676    fn test_should_reduce_free_segment_size_with_padding() {
677        let mut mm = MemoryManager::init(HeapMemoryProvider::default());
678        let mut registry = registry(&mut mm);
679
680        // first insert a user
681        let long_name = vec!['A'; 1024].into_iter().collect::<String>();
682        let record = User {
683            id: 1,
684            name: "Test User".to_string(),
685            email: long_name,
686            age: 30,
687        };
688        registry
689            .insert(record.clone(), &mut mm)
690            .expect("failed to insert");
691        // get record page
692        let mut reader = registry.read::<User, _>(&mm);
693        let next_record = reader
694            .try_next()
695            .expect("failed to read")
696            .expect("no record");
697        // delete user
698        registry
699            .delete(
700                next_record.record,
701                next_record.page,
702                next_record.offset,
703                &mut mm,
704            )
705            .expect("failed to delete");
706
707        // get the free segment
708        let raw_record = RawRecord::new(record.clone());
709        let free_segment = registry
710            .free_segments_ledger
711            .find_reusable_segment(&raw_record, &mm)
712            .expect("failed to find reusable segment")
713            .expect("could not find the free segment after free")
714            .segment;
715        // size should be at least 1024
716        assert!(free_segment.size >= 1024);
717        let previous_size = free_segment.size;
718
719        // now insert a small user at 0
720        let small_record = User {
721            id: 2,
722            name: "Bob The Builder".to_string(),
723            email: "bob@hotmail.com".to_string(),
724            age: 22,
725        };
726        registry
727            .insert(small_record.clone(), &mut mm)
728            .expect("failed to insert small user");
729
730        // get free segment
731        let free_segment_after = registry
732            .free_segments_ledger
733            .find_reusable_segment(&small_record, &mm)
734            .expect("failed to find reusable segment")
735            .expect("could not find the free segment after inserting small user")
736            .segment;
737
738        // size should be reduced
739        assert_eq!(
740            free_segment_after.offset, 64,
741            "expected offset to be 64, but had: {}",
742            free_segment_after.offset
743        ); // which is the padding
744        assert_eq!(
745            free_segment_after.size,
746            previous_size - 64,
747            "Expected free segment to have size: {} but got: {}",
748            previous_size - 64,
749            free_segment_after.size
750        );
751    }
752
753    fn registry(mm: &mut MemoryManager<HeapMemoryProvider>) -> TableRegistry {
754        let page_ledger_page = mm.allocate_page().expect("failed to get page");
755        let free_segments_page = mm.allocate_page().expect("failed to get page");
756        let table_pages = TableRegistryPage {
757            pages_list_page: page_ledger_page,
758            free_segments_page,
759        };
760
761        TableRegistry::load(table_pages, mm).expect("failed to load")
762    }
763}