Skip to main content

wasm_dbms_memory/
table_registry.rs

1// Rust guideline compliant 2026-02-28
2
3mod autoincrement_ledger;
4mod free_segments_ledger;
5mod index_ledger;
6mod page_ledger;
7mod raw_record;
8mod raw_table_reader;
9mod record_address;
10mod schema_snapshot_ledger;
11mod table_reader;
12mod write_at;
13
14use wasm_dbms_api::prelude::{Encode, MSize, MemoryResult, PageOffset, Value};
15
16pub use self::autoincrement_ledger::AutoincrementLedger;
17use self::free_segments_ledger::FreeSegmentsLedger;
18pub use self::index_ledger::{IndexLedger, IndexTreeWalker};
19use self::page_ledger::PageLedger;
20use self::raw_record::RawRecord;
21pub use self::raw_table_reader::{RawRecordBytes, RawTableReader};
22pub use self::record_address::RecordAddress;
23pub use self::schema_snapshot_ledger::SchemaSnapshotLedger;
24pub use self::table_reader::{NextRecord, TableReader};
25use self::write_at::WriteAt;
26use crate::{MemoryAccess, TableRegistryPage, align_up};
27
28/// The table registry takes care of storing the records for each table,
29/// using the [`FreeSegmentsLedger`] and [`PageLedger`] to derive exactly where to read/write.
30///
31/// A registry is generic over a record, which must implement [`Encode`].
32///
33/// The CRUD operations provided by the table registry do NOT perform any logical checks,
34/// but just allow to read/write records from/to memory.
35/// So CRUD checks must be performed by a higher layer, prior to calling these methods.
36pub struct TableRegistry {
37    schema_snapshot_ledger: SchemaSnapshotLedger,
38    pub(crate) page_ledger: PageLedger,
39    free_segments_ledger: FreeSegmentsLedger,
40    index_ledger: IndexLedger,
41    auto_increment_ledger: Option<AutoincrementLedger>,
42}
43
44impl TableRegistry {
45    /// Loads the table registry from memory.
46    pub fn load(table_pages: TableRegistryPage, mm: &mut impl MemoryAccess) -> MemoryResult<Self> {
47        Ok(Self {
48            schema_snapshot_ledger: SchemaSnapshotLedger::load(
49                table_pages.schema_snapshot_page,
50                mm,
51            )?,
52            page_ledger: PageLedger::load(table_pages.pages_list_page, mm)?,
53            free_segments_ledger: FreeSegmentsLedger::load(table_pages.free_segments_page, mm)?,
54            index_ledger: IndexLedger::load(table_pages.index_registry_page, mm)?,
55            auto_increment_ledger: if let Some(page) = table_pages.autoincrement_registry_page {
56                Some(AutoincrementLedger::load(page, mm)?)
57            } else {
58                None
59            },
60        })
61    }
62
63    /// Inserts a new record into the table registry.
64    ///
65    /// Returns the address where the record was inserted, which can be used to read it back or to update/delete it.
66    ///
67    /// NOTE: this function does NOT make any logical checks on the record being inserted.
68    pub fn insert<E>(
69        &mut self,
70        record: E,
71        mm: &mut impl MemoryAccess,
72    ) -> MemoryResult<RecordAddress>
73    where
74        E: Encode,
75    {
76        // get position to write the record
77        let raw_record = RawRecord::new(record);
78        let write_at = self.get_write_position(&raw_record, mm)?;
79
80        // align insert to RawRecord<E> alignment (includes the 2-byte header)
81        let aligned_offset = align_up::<RawRecord<E>>(write_at.offset() as usize) as PageOffset;
82
83        // write record
84        mm.write_at(write_at.page(), aligned_offset, &raw_record)?;
85
86        let pointer = RecordAddress {
87            page: write_at.page(),
88            offset: aligned_offset,
89        };
90
91        // commit post-write actions
92        self.post_write(write_at, &raw_record, mm)?;
93
94        Ok(pointer)
95    }
96
97    /// Creates a [`TableReader`] to read records from the table registry.
98    ///
99    /// Use [`TableReader::try_next`] to read records one by one.
100    pub fn read<'a, E, MA>(&'a self, mm: &'a mut MA) -> TableReader<'a, E, MA>
101    where
102        E: Encode,
103        MA: MemoryAccess,
104    {
105        TableReader::new(&self.page_ledger, mm)
106    }
107
108    /// Reads a single record at the given address.
109    pub fn read_at<E, MA>(&self, address: RecordAddress, mm: &mut MA) -> MemoryResult<E>
110    where
111        E: Encode,
112        MA: MemoryAccess,
113    {
114        let raw_record: RawRecord<E> = mm.read_at(address.page, address.offset)?;
115        Ok(raw_record.data)
116    }
117
118    /// Deletes a record at the given page and offset.
119    ///
120    /// The space occupied by the record is marked as free and zeroed.
121    pub fn delete(
122        &mut self,
123        record: impl Encode,
124        address: RecordAddress,
125        mm: &mut impl MemoryAccess,
126    ) -> MemoryResult<()> {
127        let raw_record = RawRecord::new(record);
128
129        // zero the record in memory
130        mm.zero(address.page, address.offset, &raw_record)?;
131
132        // insert a free segment for the deleted record
133        self.free_segments_ledger
134            .insert_free_segment(address.page, address.offset, &raw_record, mm)
135    }
136
137    /// Updates a record at the given page and offset.
138    ///
139    /// The [`RecordAddress`] of the new record is returned, which can be different from the old one if the record was reallocated.
140    ///
141    /// The logic is the following:
142    ///
143    /// 1. If the new record has exactly the same size of the old record, overwrite it in place.
144    /// 2. If the new record does not fit, delete the old record and insert the new record.
145    pub fn update(
146        &mut self,
147        new_record: impl Encode,
148        old_record: impl Encode,
149        old_address: RecordAddress,
150        mm: &mut impl MemoryAccess,
151    ) -> MemoryResult<RecordAddress> {
152        if new_record.size() == old_record.size() {
153            self.update_in_place(new_record, old_address, mm)
154        } else {
155            self.update_by_realloc(new_record, old_record, old_address, mm)
156        }
157    }
158
159    /// Iterate the table's records as raw bytes under the given alignment.
160    ///
161    /// Used by the migration apply pipeline to read records under the stored
162    /// snapshot. `alignment` must match the on-disk layout (i.e. the
163    /// `TableSchemaSnapshot::alignment` of the snapshot the records were
164    /// inserted under).
165    pub fn iter_raw<'a, MA>(
166        &'a self,
167        alignment: PageOffset,
168        mm: &'a mut MA,
169    ) -> RawTableReader<'a, MA>
170    where
171        MA: MemoryAccess,
172    {
173        RawTableReader::new(&self.page_ledger, alignment, mm)
174    }
175
176    /// Insert pre-encoded record bytes under the given alignment.
177    ///
178    /// Used by the migration apply pipeline. `bytes` is the body of the
179    /// record (without the 2-byte length header — the registry writes the
180    /// header). `alignment` comes from the target snapshot.
181    pub fn insert_raw(
182        &mut self,
183        bytes: &[u8],
184        alignment: PageOffset,
185        mm: &mut impl MemoryAccess,
186    ) -> MemoryResult<RecordAddress> {
187        use self::raw_record::RAW_RECORD_HEADER_SIZE;
188
189        let length = bytes.len() as MSize;
190        let total = (RAW_RECORD_HEADER_SIZE + length) as u64;
191        let physical_size_msize = align_up_msize(RAW_RECORD_HEADER_SIZE + length, alignment);
192        let physical_size_u64 = physical_size_msize as u64;
193
194        // Reuse a free segment if one fits.
195        let (page, offset) = if let Some(segment) = self
196            .free_segments_ledger
197            .find_reusable_segment_raw(length, mm)?
198        {
199            let page = segment.segment.page;
200            let offset = segment.segment.offset;
201            self.free_segments_ledger
202                .commit_reused_space_raw(physical_size_msize, segment, mm)?;
203            (page, offset)
204        } else {
205            let (page, offset) =
206                self.page_ledger
207                    .get_page_and_offset_raw(physical_size_u64, alignment, mm)?;
208            self.page_ledger.commit_raw(page, total, alignment, mm)?;
209            (page, offset)
210        };
211        let mut full = Vec::with_capacity(total as usize);
212        full.extend_from_slice(&length.to_le_bytes());
213        full.extend_from_slice(bytes);
214        mm.write_at_raw(page, offset, &full)?;
215        let physical_size = align_up_msize(RAW_RECORD_HEADER_SIZE + length, alignment);
216        let padding = physical_size.saturating_sub(RAW_RECORD_HEADER_SIZE + length);
217        if padding > 0 {
218            let padding_offset = offset + RAW_RECORD_HEADER_SIZE + length;
219            mm.zero_raw(page, padding_offset, padding)?;
220        }
221
222        Ok(RecordAddress { page, offset })
223    }
224
225    /// Read raw record bytes at the given address (header stripped).
226    pub fn read_raw_at(
227        &self,
228        address: RecordAddress,
229        mm: &mut impl MemoryAccess,
230    ) -> MemoryResult<Vec<u8>> {
231        use self::raw_record::RAW_RECORD_HEADER_SIZE;
232
233        let mut header = [0u8; RAW_RECORD_HEADER_SIZE as usize];
234        mm.read_at_raw(address.page, address.offset, &mut header)?;
235        let length = u16::from_le_bytes(header) as usize;
236        let mut body = vec![0u8; length];
237        mm.read_at_raw(
238            address.page,
239            address.offset + RAW_RECORD_HEADER_SIZE,
240            &mut body,
241        )?;
242        Ok(body)
243    }
244
245    /// Delete a raw record at the given address. Mirrors [`Self::delete`]
246    /// but parameterises size + alignment rather than reading them from a
247    /// compile-time `Encode` impl.
248    pub fn delete_raw(
249        &mut self,
250        address: RecordAddress,
251        body_len: MSize,
252        alignment: PageOffset,
253        mm: &mut impl MemoryAccess,
254    ) -> MemoryResult<()> {
255        use self::raw_record::RAW_RECORD_HEADER_SIZE;
256
257        let physical_size = align_up_msize(RAW_RECORD_HEADER_SIZE + body_len, alignment);
258        mm.zero_raw(address.page, address.offset, physical_size)?;
259        self.free_segments_ledger.insert_free_segment_raw(
260            address.page,
261            address.offset,
262            physical_size,
263            mm,
264        )
265    }
266
267    /// Releases every page owned by this table back to the unclaimed-pages
268    /// ledger.
269    ///
270    /// Walks the page ledger, free-segments ledger, every B-tree in the
271    /// index ledger, plus the dedicated schema-snapshot and
272    /// autoincrement-registry pages, and hands each one to
273    /// [`MemoryAccess::unclaim_page`]. Used by `MigrationOp::DropTable`.
274    ///
275    /// `table_pages` must be the [`TableRegistryPage`] this registry was
276    /// loaded from — the schema-snapshot and autoincrement pages are not
277    /// stored inside the registry itself.
278    ///
279    /// After this call the table's pages are reclaimable by future
280    /// [`MemoryAccess::claim_page`] calls. The caller is responsible for
281    /// removing the table's entry from the schema registry.
282    ///
283    /// # Errors
284    ///
285    /// Propagates any [`wasm_dbms_api::prelude::MemoryError`] surfaced
286    /// while walking ledgers or releasing pages.
287    pub fn release_pages(
288        self,
289        table_pages: TableRegistryPage,
290        mm: &mut impl MemoryAccess,
291    ) -> MemoryResult<()> {
292        self.page_ledger.release_pages(mm)?;
293        self.free_segments_ledger.release_pages(mm)?;
294        self.index_ledger.release_pages(mm)?;
295        mm.unclaim_page(table_pages.schema_snapshot_page)?;
296        if let Some(page) = table_pages.autoincrement_registry_page {
297            mm.unclaim_page(page)?;
298        }
299        Ok(())
300    }
301
302    /// Returns how many pages dropping this table would release.
303    pub fn releasable_pages_count(
304        &self,
305        table_pages: TableRegistryPage,
306        mm: &mut impl MemoryAccess,
307    ) -> MemoryResult<usize> {
308        let mut count = self.page_ledger.releasable_pages_count();
309        count += self.free_segments_ledger.releasable_pages_count();
310        count += self.index_ledger.releasable_pages_count(mm)?;
311        count += 1; // schema snapshot page
312        if table_pages.autoincrement_registry_page.is_some() {
313            count += 1;
314        }
315        Ok(count)
316    }
317
318    /// Get a reference to the index ledger, allowing to read the indexes.
319    pub fn index_ledger(&self) -> &IndexLedger {
320        &self.index_ledger
321    }
322
323    /// Get a mutable reference to the index ledger, allowing to modify the indexes.
324    pub fn index_ledger_mut(&mut self) -> &mut IndexLedger {
325        &mut self.index_ledger
326    }
327
328    /// Get a reference to the [`SchemaSnapshotLedger`], allowing to read the schema snapshot.
329    pub fn schema_snapshot_ledger(&self) -> &SchemaSnapshotLedger {
330        &self.schema_snapshot_ledger
331    }
332
333    /// Get a mutable reference to the [`SchemaSnapshotLedger`], allowing to modify the schema snapshot.
334    pub fn schema_snapshot_ledger_mut(&mut self) -> &mut SchemaSnapshotLedger {
335        &mut self.schema_snapshot_ledger
336    }
337
338    /// Get next value for an autoincrement column of the given type, and increment it in the ledger.
339    pub fn next_autoincrement(
340        &mut self,
341        column_name: &str,
342        mm: &mut impl MemoryAccess,
343    ) -> MemoryResult<Option<Value>> {
344        if let Some(ledger) = &mut self.auto_increment_ledger {
345            ledger.next(column_name, mm).map(Some)
346        } else {
347            Ok(None)
348        }
349    }
350
351    /// Update a [`RawRecord`] in place at the given page and offset.
352    ///
353    /// The [`RecordAddress`] of the record is returned, which is the same as the old one.
354    ///
355    /// This must be used IF AND ONLY if the new record has the SAME size as the old record.
356    fn update_in_place(
357        &mut self,
358        record: impl Encode,
359        address: RecordAddress,
360        mm: &mut impl MemoryAccess,
361    ) -> MemoryResult<RecordAddress> {
362        let raw_record = RawRecord::new(record);
363        mm.write_at(address.page, address.offset, &raw_record)?;
364
365        Ok(address)
366    }
367
368    /// Updates a record by reallocating it.
369    ///
370    /// The old record is deleted and the new record is inserted.
371    ///
372    /// The [`RecordAddress`] of the new record is returned, which can be different from the old one.
373    fn update_by_realloc(
374        &mut self,
375        new_record: impl Encode,
376        old_record: impl Encode,
377        old_address: RecordAddress,
378        mm: &mut impl MemoryAccess,
379    ) -> MemoryResult<RecordAddress> {
380        // delete old record
381        self.delete(old_record, old_address, mm)?;
382
383        // insert new record
384        self.insert(new_record, mm)
385    }
386
387    /// Gets the position where to write a record of the given size.
388    fn get_write_position<E>(
389        &mut self,
390        record: &RawRecord<E>,
391        mm: &mut impl MemoryAccess,
392    ) -> MemoryResult<WriteAt>
393    where
394        E: Encode,
395    {
396        // check if there is a free segment that can hold the record
397        if let Some(segment) = self
398            .free_segments_ledger
399            .find_reusable_segment(record, mm)?
400        {
401            return Ok(WriteAt::ReusedSegment(segment));
402        }
403
404        // otherwise, write at the end of the table
405        self.page_ledger
406            .get_page_and_offset_for_record(record, mm)
407            .map(|(page, offset)| WriteAt::End(page, offset))
408    }
409
410    /// Commits the post-write actions after writing a record at the given position.
411    ///
412    /// - If the record was a [`WriteAt::ReusedSegment`], the free segment is marked as used.
413    /// - If the record was a [`WriteAt::End`], the page ledger is updated.
414    fn post_write<E>(
415        &mut self,
416        write_at: WriteAt,
417        record: &RawRecord<E>,
418        mm: &mut impl MemoryAccess,
419    ) -> MemoryResult<()>
420    where
421        E: Encode,
422    {
423        match write_at {
424            WriteAt::ReusedSegment(free_segment) => {
425                // mark segment as used
426                self.free_segments_ledger
427                    .commit_reused_space(record, free_segment, mm)
428            }
429            WriteAt::End(page, ..) => {
430                // update page ledger
431                self.page_ledger.commit(page, record, mm)
432            }
433        }
434    }
435}
436
437/// Round `value` up to the next multiple of `alignment`. Runtime sibling
438/// to the const [`align_up`](crate::align_up) helper. Used by the raw
439/// insert/delete path where alignment comes from a stored snapshot.
440fn align_up_msize(value: MSize, alignment: PageOffset) -> MSize {
441    if alignment == 0 {
442        return value;
443    }
444    value.div_ceil(alignment) * alignment
445}
446
447/// Test utilities shared across the table_registry submodules.
448#[cfg(test)]
449pub(crate) mod test_utils {
450    use wasm_dbms_api::prelude::{
451        DEFAULT_ALIGNMENT, DataSize, DecodeError, Encode, MSize, MemoryError, MemoryResult, Page,
452        PageOffset, TableSchemaSnapshot,
453    };
454
455    use crate::MemoryAccess;
456
457    /// Writes a minimal valid [`TableSchemaSnapshot`] to `page` so subsequent
458    /// `TableRegistry::load` calls can decode it.
459    ///
460    /// Required because the table-registry unit tests allocate raw pages and
461    /// load the registry directly, bypassing the
462    /// [`SchemaRegistry::register_table`](crate::SchemaRegistry::register_table)
463    /// path that normally writes the snapshot for them.
464    pub fn write_dummy_schema_snapshot(page: Page, mm: &mut impl MemoryAccess) {
465        let dummy = TableSchemaSnapshot {
466            version: TableSchemaSnapshot::latest_version(),
467            name: "dummy".to_string(),
468            primary_key: "id".to_string(),
469            alignment: 8,
470            columns: vec![],
471            indexes: vec![],
472        };
473        mm.write_at(page, 0, &dummy)
474            .expect("failed to write dummy snapshot");
475    }
476
477    /// A simple user struct for testing purposes (no macro dependencies).
478    #[derive(Debug, Clone, PartialEq, Eq)]
479    pub struct User {
480        pub id: u32,
481        pub name: String,
482        pub email: String,
483        pub age: u32,
484    }
485
486    impl Encode for User {
487        const SIZE: DataSize = DataSize::Dynamic;
488
489        const ALIGNMENT: PageOffset = DEFAULT_ALIGNMENT;
490
491        fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
492            let mut buf = Vec::new();
493            // id: 4 bytes
494            buf.extend_from_slice(&self.id.to_le_bytes());
495            // name length: 2 bytes + name bytes
496            buf.extend_from_slice(&(self.name.len() as u16).to_le_bytes());
497            buf.extend_from_slice(self.name.as_bytes());
498            // email length: 2 bytes + email bytes
499            buf.extend_from_slice(&(self.email.len() as u16).to_le_bytes());
500            buf.extend_from_slice(self.email.as_bytes());
501            // age: 4 bytes
502            buf.extend_from_slice(&self.age.to_le_bytes());
503            std::borrow::Cow::Owned(buf)
504        }
505
506        fn decode(data: std::borrow::Cow<[u8]>) -> MemoryResult<Self>
507        where
508            Self: Sized,
509        {
510            if data.len() < 12 {
511                return Err(MemoryError::DecodeError(DecodeError::TooShort));
512            }
513            let mut offset = 0;
514            // id
515            let id = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap());
516            offset += 4;
517            // name
518            let name_len =
519                u16::from_le_bytes(data[offset..offset + 2].try_into().unwrap()) as usize;
520            offset += 2;
521            let name = String::from_utf8_lossy(&data[offset..offset + name_len]).to_string();
522            offset += name_len;
523            // email
524            let email_len =
525                u16::from_le_bytes(data[offset..offset + 2].try_into().unwrap()) as usize;
526            offset += 2;
527            let email = String::from_utf8_lossy(&data[offset..offset + email_len]).to_string();
528            offset += email_len;
529            // age
530            let age = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap());
531
532            Ok(User {
533                id,
534                name,
535                email,
536                age,
537            })
538        }
539
540        fn size(&self) -> MSize {
541            (4 + 2 + self.name.len() + 2 + self.email.len() + 4) as MSize
542        }
543    }
544}
545
546#[cfg(test)]
547mod tests {
548
549    use self::test_utils::User;
550    use super::free_segments_ledger::FreeSegment;
551    use super::table_reader::NextRecord;
552    use super::*;
553    use crate::{HeapMemoryProvider, MemoryManager};
554
555    #[test]
556    fn test_should_create_table_registry() {
557        let mut mm = MemoryManager::init(HeapMemoryProvider::default());
558        let schema_snapshot_page = mm.claim_page().expect("failed to get page");
559        let page_ledger_page = mm.claim_page().expect("failed to get page");
560        let free_segments_page = mm.claim_page().expect("failed to get page");
561        let index_registry_page = mm.claim_page().expect("failed to get page");
562        let autoincrement_page = mm.claim_page().expect("failed to get page");
563        super::test_utils::write_dummy_schema_snapshot(schema_snapshot_page, &mut mm);
564        let table_pages = TableRegistryPage {
565            schema_snapshot_page,
566            pages_list_page: page_ledger_page,
567            free_segments_page,
568            index_registry_page,
569            autoincrement_registry_page: Some(autoincrement_page),
570        };
571
572        let registry: MemoryResult<TableRegistry> = TableRegistry::load(table_pages, &mut mm);
573        assert!(registry.is_ok());
574    }
575
576    #[test]
577    fn test_should_get_write_at_end() {
578        let mut mm = MemoryManager::init(HeapMemoryProvider::default());
579        let mut registry = registry(&mut mm);
580
581        let record = RawRecord::new(User {
582            id: 1,
583            name: "Test".to_string(),
584            email: "new_user@example.com".to_string(),
585            age: 25,
586        });
587        let write_at = registry
588            .get_write_position(&record, &mut mm)
589            .expect("failed to get write at");
590
591        assert!(matches!(write_at, WriteAt::End(_, 0)));
592    }
593
594    #[test]
595    fn test_should_get_write_at_free_segment() {
596        let mut mm = MemoryManager::init(HeapMemoryProvider::default());
597        let mut registry = registry(&mut mm);
598
599        let record = RawRecord::new(User {
600            id: 1,
601            name: "Test".to_string(),
602            email: "new_user@example.com".to_string(),
603            age: 25,
604        });
605        // allocate a page to insert a free segment
606        let (page, _) = registry
607            .page_ledger
608            .get_page_and_offset_for_record(&record, &mut mm)
609            .expect("failed to get page and offset");
610        registry
611            .page_ledger
612            .commit(page, &record, &mut mm)
613            .expect("failed to commit page ledger");
614        // insert data about a free segment
615        registry
616            .free_segments_ledger
617            .insert_free_segment(page, 256, &record, &mut mm)
618            .expect("failed to insert free segment");
619
620        let write_at = registry
621            .get_write_position(&record, &mut mm)
622            .expect("failed to get write at");
623
624        let reused_segment = match write_at {
625            WriteAt::ReusedSegment(segment) => segment.segment,
626            _ => panic!("expected reused segment"),
627        };
628
629        assert_eq!(
630            reused_segment,
631            FreeSegment {
632                page,
633                offset: 256,
634                size: 64, // padded size
635            }
636        );
637    }
638
639    #[test]
640    fn test_should_insert_record_into_table_registry() {
641        let mut mm = MemoryManager::init(HeapMemoryProvider::default());
642        let mut registry = registry(&mut mm);
643
644        let record = User {
645            id: 1,
646            name: "Test".to_string(),
647            email: "new_user@example.com".to_string(),
648            age: 25,
649        };
650
651        // insert record
652        assert!(registry.insert(record, &mut mm).is_ok());
653    }
654
655    #[test]
656    fn test_should_manage_to_insert_users_to_exceed_one_page() {
657        let mut mm = MemoryManager::init(HeapMemoryProvider::default());
658        let mut registry = registry(&mut mm);
659
660        for id in 0..4000 {
661            let record = User {
662                id,
663                name: format!("User {}", id),
664                email: "new_user@example.com".to_string(),
665                age: 20 + id,
666            };
667            registry
668                .insert(record, &mut mm)
669                .expect("failed to insert record");
670        }
671    }
672
673    #[test]
674    fn test_should_delete_record() {
675        let mut mm = MemoryManager::init(HeapMemoryProvider::default());
676        let mut registry = registry(&mut mm);
677
678        let record = User {
679            id: 1,
680            name: "Test".to_string(),
681            email: "new_user@example.com".to_string(),
682            age: 25,
683        };
684
685        // insert record
686        registry
687            .insert(record.clone(), &mut mm)
688            .expect("failed to insert");
689
690        // find where it was written
691        let mut reader = registry.read(&mut mm);
692        let next_record: NextRecord<User> = reader
693            .try_next()
694            .expect("failed to read")
695            .expect("no record");
696        let page = next_record.page;
697        let offset = next_record.offset;
698        let record = next_record.record;
699        let raw_user = RawRecord::new(record.clone());
700        let raw_user_size = raw_user.size();
701
702        // delete record
703        assert!(
704            registry
705                .delete(record, RecordAddress { page, offset }, &mut mm)
706                .is_ok()
707        );
708
709        // should have been deleted
710        let mut reader = registry.read::<User, _>(&mut mm);
711        assert!(reader.try_next().expect("failed to read").is_none());
712
713        // should have a free segment
714        let free_segment = registry
715            .free_segments_ledger
716            .find_reusable_segment(
717                &User {
718                    id: 2,
719                    name: "Test".to_string(),
720                    email: "new_user@example.com".to_string(),
721                    age: 25,
722                },
723                &mut mm,
724            )
725            .expect("failed to find free segment")
726            .expect("could not find the free segment after free")
727            .segment;
728        assert_eq!(free_segment.page, page);
729        assert_eq!(free_segment.offset, offset);
730        assert_eq!(free_segment.size, 64); // padded
731
732        // should have zeroed the memory
733        let mut buffer = vec![0u8; raw_user_size as usize];
734        mm.read_at_raw(page, offset, &mut buffer)
735            .expect("failed to read memory");
736        assert!(buffer.iter().all(|&b| b == 0));
737    }
738
739    #[test]
740    fn test_read_at_returns_record_at_address() {
741        let mut mm = MemoryManager::init(HeapMemoryProvider::default());
742        let mut registry = registry(&mut mm);
743        let record = User {
744            id: 1,
745            name: "Alice".to_string(),
746            email: "alice@example.com".to_string(),
747            age: 30,
748        };
749
750        let address = registry
751            .insert(record.clone(), &mut mm)
752            .expect("failed to insert record");
753
754        let stored: User = registry
755            .read_at(address, &mut mm)
756            .expect("failed to read record");
757        assert_eq!(stored, record);
758    }
759
760    #[test]
761    fn test_read_at_after_update_returns_updated_record() {
762        let mut mm = MemoryManager::init(HeapMemoryProvider::default());
763        let mut registry = registry(&mut mm);
764        let old_record = User {
765            id: 1,
766            name: "Alice".to_string(),
767            email: "alice@example.com".to_string(),
768            age: 30,
769        };
770        let new_record = User {
771            id: 1,
772            name: "Alice Updated".to_string(),
773            email: "alice.updated@example.com".to_string(),
774            age: 31,
775        };
776
777        let old_address = registry
778            .insert(old_record.clone(), &mut mm)
779            .expect("failed to insert record");
780        let new_address = registry
781            .update(new_record.clone(), old_record, old_address, &mut mm)
782            .expect("failed to update record");
783
784        let stored: User = registry
785            .read_at(new_address, &mut mm)
786            .expect("failed to read updated record");
787        assert_eq!(stored, new_record);
788    }
789
790    #[test]
791    fn test_should_update_record_in_place() {
792        let mut mm = MemoryManager::init(HeapMemoryProvider::default());
793        let mut registry = registry(&mut mm);
794
795        let old_record = User {
796            id: 1,
797            name: "John".to_string(),
798            email: "new_user@example.com".to_string(),
799            age: 28,
800        };
801        let new_record = User {
802            id: 1,
803            name: "Mark".to_string(), // same length as "John"
804            email: "new_user@example.com".to_string(),
805            age: 30,
806        };
807
808        // insert old record
809        registry
810            .insert(old_record.clone(), &mut mm)
811            .expect("failed to insert");
812
813        // find where it was written
814        let mut reader = registry.read::<User, _>(&mut mm);
815        let next_record = reader
816            .try_next()
817            .expect("failed to read")
818            .expect("no record");
819        let page = next_record.page;
820        let offset = next_record.offset;
821
822        // update in place
823        let old_address = RecordAddress { page, offset };
824        let new_location = registry
825            .update(
826                new_record.clone(),
827                next_record.record.clone(),
828                old_address,
829                &mut mm,
830            )
831            .expect("failed to update record");
832        assert_eq!(new_location, old_address); // should be same address
833
834        // read back the record
835        let mut reader = registry.read::<User, _>(&mut mm);
836        let next_record = reader
837            .try_next()
838            .expect("failed to read")
839            .expect("no record");
840        assert_eq!(next_record.page, page); // should be same page
841        assert_eq!(next_record.offset, offset); // should be same offset
842        assert_eq!(next_record.record, new_record);
843    }
844
845    #[test]
846    fn test_should_update_record_reallocating() {
847        let mut mm = MemoryManager::init(HeapMemoryProvider::default());
848        let mut registry = registry(&mut mm);
849
850        let old_record = User {
851            id: 1,
852            name: "John".to_string(),
853            email: "new_user@example.com".to_string(),
854            age: 28,
855        };
856        // this user creates a record with same size as old_record to avoid reusing the free segment
857        let extra_user = User {
858            id: 2,
859            name: "Extra".to_string(),
860            email: "new_user@example.com".to_string(),
861            age: 25,
862        };
863        let new_record = User {
864            id: 1,
865            name: "Alexanderejruwgjowergjioewrgjioewrigjewriogjweoirgjiowerjgoiwerjiogewirogjowejrgiwer".to_string(), // must exceed padding
866            email: "new_user@example.com".to_string(),
867            age: 30,
868        };
869
870        // insert old record
871        registry
872            .insert(old_record.clone(), &mut mm)
873            .expect("failed to insert");
874        // insert extra record to avoid reusing the free segment
875        registry
876            .insert(extra_user.clone(), &mut mm)
877            .expect("failed to insert extra user");
878
879        // find where it was written
880        let mut reader = registry.read::<User, _>(&mut mm);
881        let old_record_from_db = reader
882            .try_next()
883            .expect("failed to read")
884            .expect("no record");
885        assert_eq!(old_record_from_db.record, old_record);
886        let page = old_record_from_db.page;
887        let offset = old_record_from_db.offset;
888
889        // update by reallocating
890        let old_address = RecordAddress { page, offset };
891        let new_location = registry
892            .update(
893                new_record.clone(),
894                old_record_from_db.record.clone(),
895                old_address,
896                &mut mm,
897            )
898            .expect("failed to update record");
899        assert_ne!(new_location, old_address); // should be different page
900
901        // read back the record
902        let mut reader = registry.read::<User, _>(&mut mm);
903
904        // find extra record first
905        let _ = reader
906            .try_next()
907            .expect("failed to read")
908            .expect("no record");
909
910        let updated_record = reader
911            .try_next()
912            .expect("failed to read")
913            .expect("no record");
914        assert_ne!(updated_record.offset, offset); // should be different offset
915        assert_eq!(updated_record.record, new_record);
916    }
917
918    #[test]
919    fn test_should_insert_delete_insert_many() {
920        const COUNT: u32 = 1_000;
921        let mut mm = MemoryManager::init(HeapMemoryProvider::default());
922        let mut registry = registry(&mut mm);
923        for id in 0..COUNT {
924            let record = User {
925                id,
926                name: format!("User {id}"),
927                email: format!("user_{id}@example.com"),
928                age: 20,
929            };
930
931            // insert record
932            registry
933                .insert(record.clone(), &mut mm)
934                .expect("failed to insert");
935        }
936
937        // delete odd records
938        for id in (0..COUNT).filter(|id| id % 2 == 1) {
939            let record = User {
940                id,
941                name: format!("User {id}"),
942                email: format!("user_{id}@example.com"),
943                age: 20,
944            };
945            // find where it was written
946            let mut reader = registry.read::<User, _>(&mut mm);
947            let mut deleted = false;
948            while let Some(next_record) = reader.try_next().expect("failed to read") {
949                if next_record.record.id == id {
950                    registry
951                        .delete(
952                            record.clone(),
953                            RecordAddress {
954                                page: next_record.page,
955                                offset: next_record.offset,
956                            },
957                            &mut mm,
958                        )
959                        .expect("failed to delete");
960                    deleted = true;
961                    break;
962                }
963            }
964            assert!(deleted, "record with id {} was not found", id);
965        }
966
967        // now delete also the others
968        for id in (0..COUNT).filter(|id| id % 2 == 0) {
969            let record = User {
970                id,
971                name: format!("User {id}"),
972                email: format!("user_{id}@example.com"),
973                age: 20,
974            };
975            // find where it was written
976            let mut reader = registry.read::<User, _>(&mut mm);
977            let mut deleted = false;
978            while let Some(next_record) = reader.try_next().expect("failed to read") {
979                if next_record.record.id == id {
980                    registry
981                        .delete(
982                            record.clone(),
983                            RecordAddress {
984                                page: next_record.page,
985                                offset: next_record.offset,
986                            },
987                            &mut mm,
988                        )
989                        .expect("failed to delete");
990                    deleted = true;
991                    break;
992                }
993            }
994            assert!(deleted, "record with id {} was not found", id);
995        }
996
997        // insert back
998        for id in 0..COUNT {
999            let record = User {
1000                id,
1001                name: format!("User {id}"),
1002                email: format!("user_{id}@example.com"),
1003                age: 20,
1004            };
1005
1006            // insert record
1007            registry
1008                .insert(record.clone(), &mut mm)
1009                .expect("failed to insert");
1010        }
1011    }
1012
1013    #[test]
1014    fn test_should_reduce_free_segment_size_with_padding() {
1015        let mut mm = MemoryManager::init(HeapMemoryProvider::default());
1016        let mut registry = registry(&mut mm);
1017
1018        // first insert a user
1019        let long_name = vec!['A'; 1024].into_iter().collect::<String>();
1020        let record = User {
1021            id: 1,
1022            name: "Test User".to_string(),
1023            email: long_name,
1024            age: 30,
1025        };
1026        registry
1027            .insert(record.clone(), &mut mm)
1028            .expect("failed to insert");
1029        // get record page
1030        let mut reader = registry.read::<User, _>(&mut mm);
1031        let next_record = reader
1032            .try_next()
1033            .expect("failed to read")
1034            .expect("no record");
1035        // delete user
1036        registry
1037            .delete(
1038                next_record.record,
1039                RecordAddress {
1040                    page: next_record.page,
1041                    offset: next_record.offset,
1042                },
1043                &mut mm,
1044            )
1045            .expect("failed to delete");
1046
1047        // get the free segment
1048        let raw_record = RawRecord::new(record.clone());
1049        let free_segment = registry
1050            .free_segments_ledger
1051            .find_reusable_segment(&raw_record, &mut mm)
1052            .expect("failed to find reusable segment")
1053            .expect("could not find the free segment after free")
1054            .segment;
1055        // size should be at least 1024
1056        assert!(free_segment.size >= 1024);
1057        let previous_size = free_segment.size;
1058
1059        // now insert a small user at 0
1060        let small_record = User {
1061            id: 2,
1062            name: "Bob The Builder".to_string(),
1063            email: "bob@hotmail.com".to_string(),
1064            age: 22,
1065        };
1066        registry
1067            .insert(small_record.clone(), &mut mm)
1068            .expect("failed to insert small user");
1069
1070        // get free segment
1071        let free_segment_after = registry
1072            .free_segments_ledger
1073            .find_reusable_segment(&small_record, &mut mm)
1074            .expect("failed to find reusable segment")
1075            .expect("could not find the free segment after inserting small user")
1076            .segment;
1077
1078        // size should be reduced
1079        assert_eq!(
1080            free_segment_after.offset, 64,
1081            "expected offset to be 64, but had: {}",
1082            free_segment_after.offset
1083        ); // which is the padding
1084        assert_eq!(
1085            free_segment_after.size,
1086            previous_size - 64,
1087            "Expected free segment to have size: {} but got: {}",
1088            previous_size - 64,
1089            free_segment_after.size
1090        );
1091    }
1092
1093    fn registry(mm: &mut MemoryManager<HeapMemoryProvider>) -> TableRegistry {
1094        let schema_snapshot_page = mm.claim_page().expect("failed to get page");
1095        let page_ledger_page = mm.claim_page().expect("failed to get page");
1096        let free_segments_page = mm.claim_page().expect("failed to get page");
1097        let index_registry_page = mm.claim_page().expect("failed to get page");
1098        let autoincrement_page = mm.claim_page().expect("failed to get page");
1099        super::test_utils::write_dummy_schema_snapshot(schema_snapshot_page, mm);
1100        let table_pages = TableRegistryPage {
1101            schema_snapshot_page,
1102            pages_list_page: page_ledger_page,
1103            free_segments_page,
1104            index_registry_page,
1105            autoincrement_registry_page: Some(autoincrement_page),
1106        };
1107
1108        TableRegistry::load(table_pages, mm).expect("failed to load")
1109    }
1110
1111    /// Creates a [`TableRegistry`] with a properly initialized autoincrement ledger
1112    /// via [`SchemaRegistry::register_table`].
1113    fn registry_with_autoincrement(mm: &mut MemoryManager<HeapMemoryProvider>) -> TableRegistry {
1114        use crate::SchemaRegistry;
1115
1116        let mut schema = SchemaRegistry::load(mm).expect("failed to load schema");
1117        let pages = schema
1118            .register_table::<AutoincUser>(mm)
1119            .expect("failed to register table");
1120        TableRegistry::load(pages, mm).expect("failed to load")
1121    }
1122
1123    /// Creates a [`TableRegistry`] without an autoincrement ledger.
1124    fn registry_without_autoincrement(mm: &mut MemoryManager<HeapMemoryProvider>) -> TableRegistry {
1125        let schema_snapshot_page = mm.claim_page().expect("failed to get page");
1126        let page_ledger_page = mm.claim_page().expect("failed to get page");
1127        let free_segments_page = mm.claim_page().expect("failed to get page");
1128        let index_registry_page = mm.claim_page().expect("failed to get page");
1129        super::test_utils::write_dummy_schema_snapshot(schema_snapshot_page, mm);
1130        let table_pages = TableRegistryPage {
1131            schema_snapshot_page,
1132            pages_list_page: page_ledger_page,
1133            free_segments_page,
1134            index_registry_page,
1135            autoincrement_registry_page: None,
1136        };
1137
1138        TableRegistry::load(table_pages, mm).expect("failed to load")
1139    }
1140
1141    // -- AutoincUser mock: a table with an autoincrement Uint32 column --
1142
1143    use candid::CandidType;
1144    use serde::{Deserialize, Serialize};
1145    use wasm_dbms_api::prelude::{
1146        ColumnDef, DbmsResult, IndexDef, InsertRecord, NoForeignFetcher, TableColumns, TableRecord,
1147        TableSchema, UpdateRecord,
1148    };
1149
1150    #[derive(Clone, CandidType)]
1151    struct AutoincUser;
1152
1153    impl Encode for AutoincUser {
1154        const SIZE: wasm_dbms_api::prelude::DataSize = wasm_dbms_api::prelude::DataSize::Dynamic;
1155        const ALIGNMENT: PageOffset = wasm_dbms_api::prelude::DEFAULT_ALIGNMENT;
1156
1157        fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
1158            std::borrow::Cow::Owned(vec![])
1159        }
1160
1161        fn decode(_data: std::borrow::Cow<[u8]>) -> MemoryResult<Self>
1162        where
1163            Self: Sized,
1164        {
1165            Ok(Self)
1166        }
1167
1168        fn size(&self) -> wasm_dbms_api::prelude::MSize {
1169            0
1170        }
1171    }
1172
1173    #[derive(Clone, CandidType, Deserialize)]
1174    struct AutoincUserRecord;
1175
1176    impl TableRecord for AutoincUserRecord {
1177        type Schema = AutoincUser;
1178
1179        fn from_values(_values: TableColumns) -> Self {
1180            Self
1181        }
1182
1183        fn to_values(&self) -> Vec<(ColumnDef, Value)> {
1184            vec![]
1185        }
1186    }
1187
1188    #[derive(Clone, CandidType, Serialize)]
1189    struct AutoincUserInsert;
1190
1191    impl InsertRecord for AutoincUserInsert {
1192        type Record = AutoincUserRecord;
1193        type Schema = AutoincUser;
1194
1195        fn from_values(_values: &[(ColumnDef, Value)]) -> DbmsResult<Self> {
1196            Ok(Self)
1197        }
1198
1199        fn into_values(self) -> Vec<(ColumnDef, Value)> {
1200            vec![]
1201        }
1202
1203        fn into_record(self) -> Self::Schema {
1204            AutoincUser
1205        }
1206    }
1207
1208    #[derive(Clone, CandidType, Serialize)]
1209    struct AutoincUserUpdate;
1210
1211    impl UpdateRecord for AutoincUserUpdate {
1212        type Record = AutoincUserRecord;
1213        type Schema = AutoincUser;
1214
1215        fn from_values(
1216            _values: &[(ColumnDef, Value)],
1217            _where_clause: Option<wasm_dbms_api::prelude::Filter>,
1218        ) -> Self {
1219            Self
1220        }
1221
1222        fn update_values(&self) -> Vec<(ColumnDef, Value)> {
1223            vec![]
1224        }
1225
1226        fn where_clause(&self) -> Option<wasm_dbms_api::prelude::Filter> {
1227            None
1228        }
1229    }
1230
1231    impl TableSchema for AutoincUser {
1232        type Record = AutoincUserRecord;
1233        type Insert = AutoincUserInsert;
1234        type Update = AutoincUserUpdate;
1235        type ForeignFetcher = NoForeignFetcher;
1236
1237        fn table_name() -> &'static str {
1238            "autoinc_users"
1239        }
1240
1241        fn columns() -> &'static [ColumnDef] {
1242            use wasm_dbms_api::prelude::DataTypeKind;
1243
1244            &[ColumnDef {
1245                name: "id",
1246                data_type: DataTypeKind::Uint32,
1247                auto_increment: true,
1248                nullable: false,
1249                primary_key: true,
1250                unique: true,
1251                foreign_key: None,
1252                default: None,
1253                renamed_from: &[],
1254            }]
1255        }
1256
1257        fn primary_key() -> &'static str {
1258            "id"
1259        }
1260
1261        fn indexes() -> &'static [IndexDef] {
1262            &[IndexDef(&["id"])]
1263        }
1264
1265        fn to_values(self) -> Vec<(ColumnDef, Value)> {
1266            vec![]
1267        }
1268
1269        fn sanitizer(
1270            _column_name: &'static str,
1271        ) -> Option<Box<dyn wasm_dbms_api::prelude::Sanitize>> {
1272            None
1273        }
1274
1275        fn validator(
1276            _column_name: &'static str,
1277        ) -> Option<Box<dyn wasm_dbms_api::prelude::Validate>> {
1278            None
1279        }
1280    }
1281
1282    // -- next_autoincrement tests --
1283
1284    #[test]
1285    fn test_next_autoincrement_returns_sequential_values() {
1286        let mut mm = MemoryManager::init(HeapMemoryProvider::default());
1287        let mut registry = registry_with_autoincrement(&mut mm);
1288
1289        let v1 = registry
1290            .next_autoincrement("id", &mut mm)
1291            .expect("failed")
1292            .expect("expected Some");
1293        let v2 = registry
1294            .next_autoincrement("id", &mut mm)
1295            .expect("failed")
1296            .expect("expected Some");
1297        let v3 = registry
1298            .next_autoincrement("id", &mut mm)
1299            .expect("failed")
1300            .expect("expected Some");
1301
1302        assert_eq!(v1, Value::Uint32(1u32.into()));
1303        assert_eq!(v2, Value::Uint32(2u32.into()));
1304        assert_eq!(v3, Value::Uint32(3u32.into()));
1305    }
1306
1307    #[test]
1308    fn test_next_autoincrement_returns_none_without_ledger() {
1309        let mut mm = MemoryManager::init(HeapMemoryProvider::default());
1310        let mut registry = registry_without_autoincrement(&mut mm);
1311
1312        let result = registry.next_autoincrement("id", &mut mm).expect("failed");
1313        assert!(result.is_none());
1314    }
1315
1316    #[test]
1317    fn test_next_autoincrement_persists_across_reload() {
1318        let mut mm = MemoryManager::init(HeapMemoryProvider::default());
1319
1320        use crate::SchemaRegistry;
1321        let mut schema = SchemaRegistry::load(&mut mm).expect("failed to load schema");
1322        let pages = schema
1323            .register_table::<AutoincUser>(&mut mm)
1324            .expect("failed to register table");
1325
1326        // advance 5 times
1327        let mut registry = TableRegistry::load(pages, &mut mm).expect("failed to load");
1328        for _ in 0..5 {
1329            let _ = registry
1330                .next_autoincrement("id", &mut mm)
1331                .expect("next failed");
1332        }
1333
1334        // reload the registry from the same pages
1335        let mut reloaded = TableRegistry::load(pages, &mut mm).expect("failed to reload");
1336        let value = reloaded
1337            .next_autoincrement("id", &mut mm)
1338            .expect("failed")
1339            .expect("expected Some");
1340        assert_eq!(value, Value::Uint32(6u32.into()));
1341    }
1342
1343    #[test]
1344    fn test_next_autoincrement_overflow_returns_error() {
1345        let mut mm = MemoryManager::init(HeapMemoryProvider::default());
1346
1347        // manually set up a Uint8 autoincrement to hit overflow quickly
1348        let schema_snapshot_page = mm.claim_page().expect("failed to get page");
1349        let page_ledger_page = mm.claim_page().expect("failed to get page");
1350        let free_segments_page = mm.claim_page().expect("failed to get page");
1351        let index_registry_page = mm.claim_page().expect("failed to get page");
1352        let autoinc_page = mm.claim_page().expect("failed to get page");
1353
1354        // Use the Uint8AutoincTable from the autoincrement_ledger tests — we replicate the
1355        // TableSchema inline since it's in a sibling test module.
1356        // Instead, just init the ledger page directly with a Uint8 value.
1357        {
1358            let mut registry_data = super::autoincrement_ledger::AutoincrementLedger::init::<
1359                Uint8AutoincSchema,
1360            >(autoinc_page, &mut mm)
1361            .expect("failed to init autoinc ledger");
1362
1363            // advance to 255
1364            for _ in 0..255 {
1365                let _ = registry_data.next("val", &mut mm).expect("next failed");
1366            }
1367        }
1368
1369        // init index ledger
1370        IndexLedger::init(index_registry_page, &[], &mut mm).expect("failed to init index ledger");
1371
1372        super::test_utils::write_dummy_schema_snapshot(schema_snapshot_page, &mut mm);
1373
1374        let table_pages = TableRegistryPage {
1375            schema_snapshot_page,
1376            pages_list_page: page_ledger_page,
1377            free_segments_page,
1378            index_registry_page,
1379            autoincrement_registry_page: Some(autoinc_page),
1380        };
1381
1382        let mut registry = TableRegistry::load(table_pages, &mut mm).expect("failed to load");
1383        let result = registry.next_autoincrement("val", &mut mm);
1384        assert!(result.is_err());
1385        assert!(matches!(
1386            result.unwrap_err(),
1387            wasm_dbms_api::prelude::MemoryError::AutoincrementOverflow(_)
1388        ));
1389    }
1390
1391    // Minimal Uint8 autoincrement schema for overflow test
1392
1393    #[derive(Clone, CandidType)]
1394    struct Uint8AutoincSchema;
1395
1396    impl Encode for Uint8AutoincSchema {
1397        const SIZE: wasm_dbms_api::prelude::DataSize = wasm_dbms_api::prelude::DataSize::Dynamic;
1398        const ALIGNMENT: PageOffset = wasm_dbms_api::prelude::DEFAULT_ALIGNMENT;
1399
1400        fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
1401            std::borrow::Cow::Owned(vec![])
1402        }
1403
1404        fn decode(_data: std::borrow::Cow<[u8]>) -> MemoryResult<Self>
1405        where
1406            Self: Sized,
1407        {
1408            Ok(Self)
1409        }
1410
1411        fn size(&self) -> wasm_dbms_api::prelude::MSize {
1412            0
1413        }
1414    }
1415
1416    #[derive(Clone, CandidType, Deserialize)]
1417    struct Uint8AutoincSchemaRecord;
1418
1419    impl TableRecord for Uint8AutoincSchemaRecord {
1420        type Schema = Uint8AutoincSchema;
1421
1422        fn from_values(_values: TableColumns) -> Self {
1423            Self
1424        }
1425
1426        fn to_values(&self) -> Vec<(ColumnDef, Value)> {
1427            vec![]
1428        }
1429    }
1430
1431    #[derive(Clone, CandidType, Serialize)]
1432    struct Uint8AutoincSchemaInsert;
1433
1434    impl InsertRecord for Uint8AutoincSchemaInsert {
1435        type Record = Uint8AutoincSchemaRecord;
1436        type Schema = Uint8AutoincSchema;
1437
1438        fn from_values(_values: &[(ColumnDef, Value)]) -> DbmsResult<Self> {
1439            Ok(Self)
1440        }
1441
1442        fn into_values(self) -> Vec<(ColumnDef, Value)> {
1443            vec![]
1444        }
1445
1446        fn into_record(self) -> Self::Schema {
1447            Uint8AutoincSchema
1448        }
1449    }
1450
1451    #[derive(Clone, CandidType, Serialize)]
1452    struct Uint8AutoincSchemaUpdate;
1453
1454    impl UpdateRecord for Uint8AutoincSchemaUpdate {
1455        type Record = Uint8AutoincSchemaRecord;
1456        type Schema = Uint8AutoincSchema;
1457
1458        fn from_values(
1459            _values: &[(ColumnDef, Value)],
1460            _where_clause: Option<wasm_dbms_api::prelude::Filter>,
1461        ) -> Self {
1462            Self
1463        }
1464
1465        fn update_values(&self) -> Vec<(ColumnDef, Value)> {
1466            vec![]
1467        }
1468
1469        fn where_clause(&self) -> Option<wasm_dbms_api::prelude::Filter> {
1470            None
1471        }
1472    }
1473
1474    impl TableSchema for Uint8AutoincSchema {
1475        type Record = Uint8AutoincSchemaRecord;
1476        type Insert = Uint8AutoincSchemaInsert;
1477        type Update = Uint8AutoincSchemaUpdate;
1478        type ForeignFetcher = NoForeignFetcher;
1479
1480        fn table_name() -> &'static str {
1481            "uint8_autoinc_schema"
1482        }
1483
1484        fn columns() -> &'static [ColumnDef] {
1485            use wasm_dbms_api::prelude::DataTypeKind;
1486
1487            &[ColumnDef {
1488                name: "val",
1489                data_type: DataTypeKind::Uint8,
1490                auto_increment: true,
1491                nullable: false,
1492                primary_key: true,
1493                unique: true,
1494                foreign_key: None,
1495                default: None,
1496                renamed_from: &[],
1497            }]
1498        }
1499
1500        fn primary_key() -> &'static str {
1501            "val"
1502        }
1503
1504        fn indexes() -> &'static [IndexDef] {
1505            &[IndexDef(&["val"])]
1506        }
1507
1508        fn to_values(self) -> Vec<(ColumnDef, Value)> {
1509            vec![]
1510        }
1511
1512        fn sanitizer(
1513            _column_name: &'static str,
1514        ) -> Option<Box<dyn wasm_dbms_api::prelude::Sanitize>> {
1515            None
1516        }
1517
1518        fn validator(
1519            _column_name: &'static str,
1520        ) -> Option<Box<dyn wasm_dbms_api::prelude::Validate>> {
1521            None
1522        }
1523    }
1524
1525    use wasm_dbms_api::prelude::{DataSize, DecodeError, MSize, MemoryError};
1526
1527    /// A fixed-size record for regression testing (issue #80).
1528    ///
1529    /// Layout: u64 (8) + u64 (8) + u8 (1) = 17 bytes, all fixed-size.
1530    #[derive(Debug, Clone, PartialEq, Eq)]
1531    struct FixedSizeRecord {
1532        id: u64,
1533        timestamp: u64,
1534        tag: u8,
1535    }
1536
1537    impl Encode for FixedSizeRecord {
1538        const SIZE: DataSize = DataSize::Fixed(17);
1539        const ALIGNMENT: PageOffset = 17;
1540
1541        fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
1542            let mut buf = Vec::with_capacity(17);
1543            buf.extend_from_slice(&self.id.to_le_bytes());
1544            buf.extend_from_slice(&self.timestamp.to_le_bytes());
1545            buf.push(self.tag);
1546            std::borrow::Cow::Owned(buf)
1547        }
1548
1549        fn decode(data: std::borrow::Cow<[u8]>) -> MemoryResult<Self>
1550        where
1551            Self: Sized,
1552        {
1553            if data.len() < 17 {
1554                return Err(MemoryError::DecodeError(DecodeError::TooShort));
1555            }
1556            let id = u64::from_le_bytes(data[0..8].try_into().unwrap());
1557            let timestamp = u64::from_le_bytes(data[8..16].try_into().unwrap());
1558            let tag = data[16];
1559            Ok(Self { id, timestamp, tag })
1560        }
1561
1562        fn size(&self) -> MSize {
1563            17
1564        }
1565    }
1566
1567    #[test]
1568    fn test_should_insert_multiple_fixed_size_records() {
1569        let mut mm = MemoryManager::init(HeapMemoryProvider::default());
1570        let mut registry = registry(&mut mm);
1571
1572        for i in 0..10 {
1573            let record = FixedSizeRecord {
1574                id: i,
1575                timestamp: 1000 + i,
1576                tag: (i % 2) as u8,
1577            };
1578            registry
1579                .insert(record, &mut mm)
1580                .unwrap_or_else(|e| panic!("failed to insert record {i}: {e}"));
1581        }
1582
1583        // verify all records can be read back
1584        let mut reader = registry.read::<FixedSizeRecord, _>(&mut mm);
1585        let mut count = 0;
1586        while let Some(next) = reader.try_next().expect("failed to read") {
1587            assert_eq!(next.record.id, count);
1588            count += 1;
1589        }
1590        assert_eq!(count, 10);
1591    }
1592}