Skip to main content

wasm_dbms_memory/table_registry/
autoincrement_ledger.rs

1//! Ledger for autoincrement values, used by tables with autoincrement columns.
2
3mod registry;
4
5use wasm_dbms_api::memory::{MemoryError, MemoryResult, Page};
6use wasm_dbms_api::prelude::{
7    DataTypeKind, DataTypeSnapshot, TableSchema, TableSchemaSnapshot, Value,
8};
9
10use self::registry::AutoincrementRegistry;
11use crate::MemoryAccess;
12
13/// Ledger for autoincrement values, used by tables with autoincrement columns.
14#[derive(Debug)]
15pub struct AutoincrementLedger {
16    /// page used to store autoincrement values
17    page: Page,
18    /// mapping between the column name and the current autoincrement value for that column
19    registry: AutoincrementRegistry,
20}
21
22impl AutoincrementLedger {
23    /// Initialize the [`AutoincrementLedger`] for the given table schema, and write it to the given page.
24    ///
25    /// Each autoincrement column in the table schema will be initialized in the registry with the appropriate zero value.
26    ///
27    /// # Errors
28    ///
29    /// Returns [`MemoryError::ConstraintViolation`] if the autoincrement
30    /// columns do not fit the registry encoding (see [`Self::validate`]), or
31    /// any error raised while writing the registry page.
32    pub fn init<TS>(page: Page, mm: &mut impl MemoryAccess) -> MemoryResult<Self>
33    where
34        TS: TableSchema,
35    {
36        Self::validate::<TS>()?;
37
38        let mut registry = AutoincrementRegistry::default();
39        // init each autoincrement column in the registry with the appropriate zero value
40        for auto_increment_column in TS::columns().iter().filter(|c| c.auto_increment) {
41            let zero = Self::zero(auto_increment_column.data_type);
42            registry.init(auto_increment_column.name, zero);
43        }
44        // write the registry to the page
45        mm.write_at(page, 0, &registry)?;
46
47        Ok(Self { page, registry })
48    }
49
50    /// Initialize the [`AutoincrementLedger`] for the table described by
51    /// `snapshot`, and write it to the given page.
52    ///
53    /// Snapshot counterpart of [`Self::init`], used when a table is created
54    /// from a stored [`TableSchemaSnapshot`] rather than a compiled
55    /// [`TableSchema`].
56    ///
57    /// # Errors
58    ///
59    /// Returns [`MemoryError::ConstraintViolation`] if an autoincrement
60    /// column is not an integer or does not fit the registry encoding (see
61    /// [`Self::validate_snapshot`]), or any error raised while writing the
62    /// registry page.
63    pub fn init_from_snapshot(
64        page: Page,
65        snapshot: &TableSchemaSnapshot,
66        mm: &mut impl MemoryAccess,
67    ) -> MemoryResult<Self> {
68        Self::validate_snapshot(snapshot)?;
69
70        let mut registry = AutoincrementRegistry::default();
71        for column in snapshot.columns.iter().filter(|c| c.auto_increment) {
72            if let Some(zero) = Self::snapshot_zero(&column.data_type) {
73                registry.init(&column.name, zero);
74            }
75        }
76        mm.write_at(page, 0, &registry)?;
77
78        Ok(Self { page, registry })
79    }
80
81    /// Checks that the autoincrement columns of `TS` fit the registry
82    /// encoding, so [`Self::init`] cannot persist truncated metadata.
83    ///
84    /// Callers should run this before allocating any page for the table.
85    ///
86    /// # Errors
87    ///
88    /// Returns [`MemoryError::ConstraintViolation`] if there are more than
89    /// 255 autoincrement columns or a column name is longer than 255 bytes.
90    pub fn validate<TS>() -> MemoryResult<()>
91    where
92        TS: TableSchema,
93    {
94        AutoincrementRegistry::validate_columns(
95            TS::columns()
96                .iter()
97                .filter(|c| c.auto_increment)
98                .map(|c| c.name),
99        )
100    }
101
102    /// Checks that every autoincrement column of `snapshot` has an integer
103    /// type and fits the registry encoding, so [`Self::init_from_snapshot`]
104    /// cannot fail on it.
105    ///
106    /// Callers should run this before allocating any page for the table.
107    ///
108    /// # Errors
109    ///
110    /// Returns [`MemoryError::ConstraintViolation`] if an autoincrement
111    /// column is not an integer, there are more than 255 autoincrement
112    /// columns, or a column name is longer than 255 bytes.
113    pub fn validate_snapshot(snapshot: &TableSchemaSnapshot) -> MemoryResult<()> {
114        if let Some(column) = snapshot
115            .columns
116            .iter()
117            .filter(|c| c.auto_increment)
118            .find(|c| Self::snapshot_zero(&c.data_type).is_none())
119        {
120            return Err(MemoryError::ConstraintViolation(format!(
121                "unsupported autoincrement type for column `{}`: {:?}",
122                column.name, column.data_type
123            )));
124        }
125
126        AutoincrementRegistry::validate_columns(
127            snapshot
128                .columns
129                .iter()
130                .filter(|c| c.auto_increment)
131                .map(|c| c.name.as_str()),
132        )
133    }
134
135    /// Load the [`AutoincrementLedger`] from the given page.
136    pub fn load(page: Page, mm: &mut impl MemoryAccess) -> MemoryResult<Self> {
137        Ok(Self {
138            page,
139            registry: mm.read_at(page, 0)?,
140        })
141    }
142
143    /// Returns the next autoincrement [`Value`] for the given column, and persists the updated registry to memory.
144    ///
145    /// # Errors
146    ///
147    /// Returns `MemoryError::AutoincrementOverflow` if the column has reached its maximum value.
148    pub fn next(&mut self, column: &str, mm: &mut impl MemoryAccess) -> MemoryResult<Value> {
149        let value = self.registry.next(column)?;
150        mm.write_at(self.page, 0, &self.registry)?;
151        Ok(value)
152    }
153
154    /// Returns the zero [`Value`] based on the column [`DataTypeKind`].
155    fn zero(data_type: DataTypeKind) -> Value {
156        match data_type {
157            DataTypeKind::Int8 => Value::Int8(0.into()),
158            DataTypeKind::Int16 => Value::Int16(0.into()),
159            DataTypeKind::Int32 => Value::Int32(0.into()),
160            DataTypeKind::Int64 => Value::Int64(0.into()),
161            DataTypeKind::Uint8 => Value::Uint8(0.into()),
162            DataTypeKind::Uint16 => Value::Uint16(0.into()),
163            DataTypeKind::Uint32 => Value::Uint32(0.into()),
164            DataTypeKind::Uint64 => Value::Uint64(0.into()),
165            data_type => panic!("unsupported autoincrement type: {data_type:?}"),
166        }
167    }
168
169    /// Returns the zero [`Value`] for a snapshot column type, or `None` if
170    /// the type cannot autoincrement.
171    fn snapshot_zero(data_type: &DataTypeSnapshot) -> Option<Value> {
172        match data_type {
173            DataTypeSnapshot::Int8 => Some(Value::Int8(0.into())),
174            DataTypeSnapshot::Int16 => Some(Value::Int16(0.into())),
175            DataTypeSnapshot::Int32 => Some(Value::Int32(0.into())),
176            DataTypeSnapshot::Int64 => Some(Value::Int64(0.into())),
177            DataTypeSnapshot::Uint8 => Some(Value::Uint8(0.into())),
178            DataTypeSnapshot::Uint16 => Some(Value::Uint16(0.into())),
179            DataTypeSnapshot::Uint32 => Some(Value::Uint32(0.into())),
180            DataTypeSnapshot::Uint64 => Some(Value::Uint64(0.into())),
181            _ => None,
182        }
183    }
184}
185
186#[cfg(test)]
187mod tests {
188
189    use candid::CandidType;
190    use serde::{Deserialize, Serialize};
191    use wasm_dbms_api::prelude::{
192        ColumnDef, DEFAULT_ALIGNMENT, DataSize, DbmsResult, Encode, IndexDef, InsertRecord, MSize,
193        MemoryError, MemoryResult, NoForeignFetcher, PageOffset, TableColumns, TableRecord,
194        TableSchema, UpdateRecord, Value,
195    };
196
197    use super::*;
198    use crate::{HeapMemoryProvider, MemoryManager};
199
200    fn make_mm() -> MemoryManager<HeapMemoryProvider> {
201        MemoryManager::init(HeapMemoryProvider::default())
202    }
203
204    // -- Mock: single Uint32 autoincrement column --
205
206    #[derive(Clone, CandidType)]
207    struct SingleAutoincTable;
208
209    impl Encode for SingleAutoincTable {
210        const SIZE: DataSize = DataSize::Dynamic;
211        const ALIGNMENT: PageOffset = DEFAULT_ALIGNMENT;
212
213        fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
214            std::borrow::Cow::Owned(vec![])
215        }
216
217        fn decode(_data: std::borrow::Cow<[u8]>) -> MemoryResult<Self>
218        where
219            Self: Sized,
220        {
221            Ok(Self)
222        }
223
224        fn size(&self) -> MSize {
225            0
226        }
227    }
228
229    #[derive(Clone, CandidType, Deserialize)]
230    struct SingleAutoincTableRecord;
231
232    impl TableRecord for SingleAutoincTableRecord {
233        type Schema = SingleAutoincTable;
234
235        fn from_values(_values: TableColumns) -> Self {
236            Self
237        }
238
239        fn to_values(&self) -> Vec<(ColumnDef, Value)> {
240            vec![]
241        }
242    }
243
244    #[derive(Clone, CandidType, Serialize)]
245    struct SingleAutoincTableInsert;
246
247    impl InsertRecord for SingleAutoincTableInsert {
248        type Record = SingleAutoincTableRecord;
249        type Schema = SingleAutoincTable;
250
251        fn from_values(_values: &[(ColumnDef, Value)]) -> DbmsResult<Self> {
252            Ok(Self)
253        }
254
255        fn into_values(self) -> Vec<(ColumnDef, Value)> {
256            vec![]
257        }
258
259        fn into_record(self) -> Self::Schema {
260            SingleAutoincTable
261        }
262    }
263
264    #[derive(Clone, CandidType, Serialize)]
265    struct SingleAutoincTableUpdate;
266
267    impl UpdateRecord for SingleAutoincTableUpdate {
268        type Record = SingleAutoincTableRecord;
269        type Schema = SingleAutoincTable;
270
271        fn from_values(
272            _values: &[(ColumnDef, Value)],
273            _where_clause: Option<wasm_dbms_api::prelude::Filter>,
274        ) -> wasm_dbms_api::prelude::DbmsResult<Self> {
275            Ok(Self)
276        }
277
278        fn update_values(&self) -> Vec<(ColumnDef, Value)> {
279            vec![]
280        }
281
282        fn where_clause(&self) -> Option<wasm_dbms_api::prelude::Filter> {
283            None
284        }
285    }
286
287    impl TableSchema for SingleAutoincTable {
288        type Record = SingleAutoincTableRecord;
289        type Insert = SingleAutoincTableInsert;
290        type Update = SingleAutoincTableUpdate;
291        type ForeignFetcher = NoForeignFetcher;
292
293        fn table_name() -> &'static str {
294            "single_autoinc"
295        }
296
297        fn columns() -> &'static [ColumnDef] {
298            &[ColumnDef {
299                name: "id",
300                data_type: DataTypeKind::Uint32,
301                auto_increment: true,
302                nullable: false,
303                primary_key: true,
304                unique: true,
305                foreign_key: None,
306                default: None,
307                renamed_from: &[],
308            }]
309        }
310
311        fn primary_key() -> &'static str {
312            "id"
313        }
314
315        fn indexes() -> &'static [IndexDef] {
316            &[IndexDef(&["id"])]
317        }
318
319        fn to_values(self) -> Vec<(ColumnDef, Value)> {
320            vec![]
321        }
322
323        fn sanitizer(
324            _column_name: &'static str,
325        ) -> Option<Box<dyn wasm_dbms_api::prelude::Sanitize>> {
326            None
327        }
328
329        fn validator(
330            _column_name: &'static str,
331        ) -> Option<Box<dyn wasm_dbms_api::prelude::Validate>> {
332            None
333        }
334    }
335
336    // -- Mock: two autoincrement columns --
337
338    #[derive(Clone, CandidType)]
339    struct MultiAutoincTable;
340
341    impl Encode for MultiAutoincTable {
342        const SIZE: DataSize = DataSize::Dynamic;
343        const ALIGNMENT: PageOffset = DEFAULT_ALIGNMENT;
344
345        fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
346            std::borrow::Cow::Owned(vec![])
347        }
348
349        fn decode(_data: std::borrow::Cow<[u8]>) -> MemoryResult<Self>
350        where
351            Self: Sized,
352        {
353            Ok(Self)
354        }
355
356        fn size(&self) -> MSize {
357            0
358        }
359    }
360
361    #[derive(Clone, CandidType, Deserialize)]
362    struct MultiAutoincTableRecord;
363
364    impl TableRecord for MultiAutoincTableRecord {
365        type Schema = MultiAutoincTable;
366
367        fn from_values(_values: TableColumns) -> Self {
368            Self
369        }
370
371        fn to_values(&self) -> Vec<(ColumnDef, Value)> {
372            vec![]
373        }
374    }
375
376    #[derive(Clone, CandidType, Serialize)]
377    struct MultiAutoincTableInsert;
378
379    impl InsertRecord for MultiAutoincTableInsert {
380        type Record = MultiAutoincTableRecord;
381        type Schema = MultiAutoincTable;
382
383        fn from_values(_values: &[(ColumnDef, Value)]) -> DbmsResult<Self> {
384            Ok(Self)
385        }
386
387        fn into_values(self) -> Vec<(ColumnDef, Value)> {
388            vec![]
389        }
390
391        fn into_record(self) -> Self::Schema {
392            MultiAutoincTable
393        }
394    }
395
396    #[derive(Clone, CandidType, Serialize)]
397    struct MultiAutoincTableUpdate;
398
399    impl UpdateRecord for MultiAutoincTableUpdate {
400        type Record = MultiAutoincTableRecord;
401        type Schema = MultiAutoincTable;
402
403        fn from_values(
404            _values: &[(ColumnDef, Value)],
405            _where_clause: Option<wasm_dbms_api::prelude::Filter>,
406        ) -> wasm_dbms_api::prelude::DbmsResult<Self> {
407            Ok(Self)
408        }
409
410        fn update_values(&self) -> Vec<(ColumnDef, Value)> {
411            vec![]
412        }
413
414        fn where_clause(&self) -> Option<wasm_dbms_api::prelude::Filter> {
415            None
416        }
417    }
418
419    impl TableSchema for MultiAutoincTable {
420        type Record = MultiAutoincTableRecord;
421        type Insert = MultiAutoincTableInsert;
422        type Update = MultiAutoincTableUpdate;
423        type ForeignFetcher = NoForeignFetcher;
424
425        fn table_name() -> &'static str {
426            "multi_autoinc"
427        }
428
429        fn columns() -> &'static [ColumnDef] {
430            &[
431                ColumnDef {
432                    name: "id",
433                    data_type: DataTypeKind::Uint32,
434                    auto_increment: true,
435                    nullable: false,
436                    primary_key: true,
437                    unique: true,
438                    foreign_key: None,
439                    default: None,
440                    renamed_from: &[],
441                },
442                ColumnDef {
443                    name: "seq",
444                    data_type: DataTypeKind::Uint64,
445                    auto_increment: true,
446                    nullable: false,
447                    primary_key: false,
448                    unique: false,
449                    foreign_key: None,
450                    default: None,
451                    renamed_from: &[],
452                },
453            ]
454        }
455
456        fn primary_key() -> &'static str {
457            "id"
458        }
459
460        fn indexes() -> &'static [IndexDef] {
461            &[IndexDef(&["id"])]
462        }
463
464        fn to_values(self) -> Vec<(ColumnDef, Value)> {
465            vec![]
466        }
467
468        fn sanitizer(
469            _column_name: &'static str,
470        ) -> Option<Box<dyn wasm_dbms_api::prelude::Sanitize>> {
471            None
472        }
473
474        fn validator(
475            _column_name: &'static str,
476        ) -> Option<Box<dyn wasm_dbms_api::prelude::Validate>> {
477            None
478        }
479    }
480
481    // -- Mock: no autoincrement columns --
482
483    #[derive(Clone, CandidType)]
484    struct NoAutoincTable;
485
486    impl Encode for NoAutoincTable {
487        const SIZE: DataSize = DataSize::Dynamic;
488        const ALIGNMENT: PageOffset = DEFAULT_ALIGNMENT;
489
490        fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
491            std::borrow::Cow::Owned(vec![])
492        }
493
494        fn decode(_data: std::borrow::Cow<[u8]>) -> MemoryResult<Self>
495        where
496            Self: Sized,
497        {
498            Ok(Self)
499        }
500
501        fn size(&self) -> MSize {
502            0
503        }
504    }
505
506    #[derive(Clone, CandidType, Deserialize)]
507    struct NoAutoincTableRecord;
508
509    impl TableRecord for NoAutoincTableRecord {
510        type Schema = NoAutoincTable;
511
512        fn from_values(_values: TableColumns) -> Self {
513            Self
514        }
515
516        fn to_values(&self) -> Vec<(ColumnDef, Value)> {
517            vec![]
518        }
519    }
520
521    #[derive(Clone, CandidType, Serialize)]
522    struct NoAutoincTableInsert;
523
524    impl InsertRecord for NoAutoincTableInsert {
525        type Record = NoAutoincTableRecord;
526        type Schema = NoAutoincTable;
527
528        fn from_values(_values: &[(ColumnDef, Value)]) -> DbmsResult<Self> {
529            Ok(Self)
530        }
531
532        fn into_values(self) -> Vec<(ColumnDef, Value)> {
533            vec![]
534        }
535
536        fn into_record(self) -> Self::Schema {
537            NoAutoincTable
538        }
539    }
540
541    #[derive(Clone, CandidType, Serialize)]
542    struct NoAutoincTableUpdate;
543
544    impl UpdateRecord for NoAutoincTableUpdate {
545        type Record = NoAutoincTableRecord;
546        type Schema = NoAutoincTable;
547
548        fn from_values(
549            _values: &[(ColumnDef, Value)],
550            _where_clause: Option<wasm_dbms_api::prelude::Filter>,
551        ) -> wasm_dbms_api::prelude::DbmsResult<Self> {
552            Ok(Self)
553        }
554
555        fn update_values(&self) -> Vec<(ColumnDef, Value)> {
556            vec![]
557        }
558
559        fn where_clause(&self) -> Option<wasm_dbms_api::prelude::Filter> {
560            None
561        }
562    }
563
564    impl TableSchema for NoAutoincTable {
565        type Record = NoAutoincTableRecord;
566        type Insert = NoAutoincTableInsert;
567        type Update = NoAutoincTableUpdate;
568        type ForeignFetcher = NoForeignFetcher;
569
570        fn table_name() -> &'static str {
571            "no_autoinc"
572        }
573
574        fn columns() -> &'static [ColumnDef] {
575            &[ColumnDef {
576                name: "id",
577                data_type: DataTypeKind::Uint32,
578                auto_increment: false,
579                nullable: false,
580                primary_key: true,
581                unique: true,
582                foreign_key: None,
583                default: None,
584                renamed_from: &[],
585            }]
586        }
587
588        fn primary_key() -> &'static str {
589            "id"
590        }
591
592        fn indexes() -> &'static [IndexDef] {
593            &[IndexDef(&["id"])]
594        }
595
596        fn to_values(self) -> Vec<(ColumnDef, Value)> {
597            vec![]
598        }
599
600        fn sanitizer(
601            _column_name: &'static str,
602        ) -> Option<Box<dyn wasm_dbms_api::prelude::Sanitize>> {
603            None
604        }
605
606        fn validator(
607            _column_name: &'static str,
608        ) -> Option<Box<dyn wasm_dbms_api::prelude::Validate>> {
609            None
610        }
611    }
612
613    // -- Tests --
614
615    #[test]
616    fn test_init_single_autoincrement_column() {
617        let mut mm = make_mm();
618        let page = mm.claim_page().expect("failed to allocate page");
619
620        let ledger = AutoincrementLedger::init::<SingleAutoincTable>(page, &mut mm)
621            .expect("failed to init ledger");
622
623        // verify the page was stored
624        assert_eq!(ledger.page, page);
625    }
626
627    #[test]
628    fn test_init_multiple_autoincrement_columns() {
629        let mut mm = make_mm();
630        let page = mm.claim_page().expect("failed to allocate page");
631
632        let mut ledger = AutoincrementLedger::init::<MultiAutoincTable>(page, &mut mm)
633            .expect("failed to init ledger");
634
635        // both columns should produce their first value
636        let id_val = ledger.next("id", &mut mm).expect("failed to get next id");
637        let seq_val = ledger.next("seq", &mut mm).expect("failed to get next seq");
638
639        assert_eq!(id_val, Value::Uint32(1u32.into()));
640        assert_eq!(seq_val, Value::Uint64(1u64.into()));
641    }
642
643    #[test]
644    fn test_init_no_autoincrement_columns() {
645        let mut mm = make_mm();
646        let page = mm.claim_page().expect("failed to allocate page");
647
648        // should succeed but the registry is empty
649        let ledger = AutoincrementLedger::init::<NoAutoincTable>(page, &mut mm)
650            .expect("failed to init ledger");
651
652        assert_eq!(ledger.page, page);
653    }
654
655    #[test]
656    fn test_init_from_snapshot_rejects_non_integer_autoincrement() {
657        use wasm_dbms_api::prelude::{ColumnSnapshot, DataTypeSnapshot, TableSchemaSnapshot};
658
659        let mut mm = make_mm();
660        let page = mm.claim_page().expect("claim");
661        let snapshot = TableSchemaSnapshot {
662            version: TableSchemaSnapshot::latest_version(),
663            name: "texts".to_string(),
664            primary_key: "id".to_string(),
665            alignment: 32,
666            columns: vec![ColumnSnapshot {
667                name: "id".to_string(),
668                data_type: DataTypeSnapshot::Text,
669                nullable: false,
670                auto_increment: true,
671                unique: true,
672                primary_key: true,
673                foreign_key: None,
674                default: None,
675            }],
676            indexes: vec![],
677        };
678
679        let result = AutoincrementLedger::init_from_snapshot(page, &snapshot, &mut mm);
680        assert!(matches!(result, Err(MemoryError::ConstraintViolation(_))));
681    }
682
683    #[test]
684    fn test_load_after_init() {
685        let mut mm = make_mm();
686        let page = mm.claim_page().expect("failed to allocate page");
687
688        let mut ledger =
689            AutoincrementLedger::init::<SingleAutoincTable>(page, &mut mm).expect("failed to init");
690
691        // advance once
692        let _ = ledger.next("id", &mut mm).expect("next failed");
693
694        // load from memory and continue
695        let mut reloaded = AutoincrementLedger::load(page, &mut mm).expect("failed to load ledger");
696
697        let value = reloaded
698            .next("id", &mut mm)
699            .expect("next after reload failed");
700        assert_eq!(value, Value::Uint32(2u32.into()));
701    }
702
703    #[test]
704    fn test_next_returns_sequential_values() {
705        let mut mm = make_mm();
706        let page = mm.claim_page().expect("failed to allocate page");
707
708        let mut ledger =
709            AutoincrementLedger::init::<SingleAutoincTable>(page, &mut mm).expect("failed to init");
710
711        for expected in 1u32..=100 {
712            let value = ledger.next("id", &mut mm).expect("next failed");
713            assert_eq!(value, Value::Uint32(expected.into()));
714        }
715    }
716
717    #[test]
718    fn test_next_persists_to_memory() {
719        let mut mm = make_mm();
720        let page = mm.claim_page().expect("failed to allocate page");
721
722        let mut ledger =
723            AutoincrementLedger::init::<SingleAutoincTable>(page, &mut mm).expect("failed to init");
724
725        // advance 5 times
726        for _ in 0..5 {
727            let _ = ledger.next("id", &mut mm).expect("next failed");
728        }
729
730        // reload and verify the counter continued from where it was
731        let mut reloaded = AutoincrementLedger::load(page, &mut mm).expect("failed to load ledger");
732        let value = reloaded
733            .next("id", &mut mm)
734            .expect("next after reload failed");
735        assert_eq!(value, Value::Uint32(6u32.into()));
736    }
737
738    #[test]
739    fn test_next_overflow_returns_error() {
740        let mut mm = make_mm();
741        let page = mm.claim_page().expect("failed to allocate page");
742
743        // init with Uint8 to quickly reach max
744        let mut ledger =
745            AutoincrementLedger::init::<Uint8AutoincTable>(page, &mut mm).expect("failed to init");
746
747        // advance to 255
748        for _ in 0..255 {
749            let _ = ledger.next("counter", &mut mm).expect("next failed");
750        }
751
752        // next should fail with overflow
753        let result = ledger.next("counter", &mut mm);
754        assert!(result.is_err());
755        assert!(matches!(
756            result.unwrap_err(),
757            MemoryError::AutoincrementOverflow(_)
758        ));
759    }
760
761    #[test]
762    fn test_multi_column_independence_across_reload() {
763        let mut mm = make_mm();
764        let page = mm.claim_page().expect("failed to allocate page");
765
766        let mut ledger =
767            AutoincrementLedger::init::<MultiAutoincTable>(page, &mut mm).expect("failed to init");
768
769        // advance id 3 times, seq 1 time
770        for _ in 0..3 {
771            let _ = ledger.next("id", &mut mm).expect("id next failed");
772        }
773        let _ = ledger.next("seq", &mut mm).expect("seq next failed");
774
775        // reload and verify both columns preserved their independent state
776        let mut reloaded = AutoincrementLedger::load(page, &mut mm).expect("failed to load ledger");
777
778        let id_val = reloaded
779            .next("id", &mut mm)
780            .expect("id next after reload failed");
781        let seq_val = reloaded
782            .next("seq", &mut mm)
783            .expect("seq next after reload failed");
784
785        assert_eq!(id_val, Value::Uint32(4u32.into()));
786        assert_eq!(seq_val, Value::Uint64(2u64.into()));
787    }
788
789    // -- Mock: Uint8 autoincrement column (for overflow testing) --
790
791    #[derive(Clone, CandidType)]
792    struct Uint8AutoincTable;
793
794    impl Encode for Uint8AutoincTable {
795        const SIZE: DataSize = DataSize::Dynamic;
796        const ALIGNMENT: PageOffset = DEFAULT_ALIGNMENT;
797
798        fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
799            std::borrow::Cow::Owned(vec![])
800        }
801
802        fn decode(_data: std::borrow::Cow<[u8]>) -> MemoryResult<Self>
803        where
804            Self: Sized,
805        {
806            Ok(Self)
807        }
808
809        fn size(&self) -> MSize {
810            0
811        }
812    }
813
814    #[derive(Clone, CandidType, Deserialize)]
815    struct Uint8AutoincTableRecord;
816
817    impl TableRecord for Uint8AutoincTableRecord {
818        type Schema = Uint8AutoincTable;
819
820        fn from_values(_values: TableColumns) -> Self {
821            Self
822        }
823
824        fn to_values(&self) -> Vec<(ColumnDef, Value)> {
825            vec![]
826        }
827    }
828
829    #[derive(Clone, CandidType, Serialize)]
830    struct Uint8AutoincTableInsert;
831
832    impl InsertRecord for Uint8AutoincTableInsert {
833        type Record = Uint8AutoincTableRecord;
834        type Schema = Uint8AutoincTable;
835
836        fn from_values(_values: &[(ColumnDef, Value)]) -> DbmsResult<Self> {
837            Ok(Self)
838        }
839
840        fn into_values(self) -> Vec<(ColumnDef, Value)> {
841            vec![]
842        }
843
844        fn into_record(self) -> Self::Schema {
845            Uint8AutoincTable
846        }
847    }
848
849    #[derive(Clone, CandidType, Serialize)]
850    struct Uint8AutoincTableUpdate;
851
852    impl UpdateRecord for Uint8AutoincTableUpdate {
853        type Record = Uint8AutoincTableRecord;
854        type Schema = Uint8AutoincTable;
855
856        fn from_values(
857            _values: &[(ColumnDef, Value)],
858            _where_clause: Option<wasm_dbms_api::prelude::Filter>,
859        ) -> wasm_dbms_api::prelude::DbmsResult<Self> {
860            Ok(Self)
861        }
862
863        fn update_values(&self) -> Vec<(ColumnDef, Value)> {
864            vec![]
865        }
866
867        fn where_clause(&self) -> Option<wasm_dbms_api::prelude::Filter> {
868            None
869        }
870    }
871
872    impl TableSchema for Uint8AutoincTable {
873        type Record = Uint8AutoincTableRecord;
874        type Insert = Uint8AutoincTableInsert;
875        type Update = Uint8AutoincTableUpdate;
876        type ForeignFetcher = NoForeignFetcher;
877
878        fn table_name() -> &'static str {
879            "uint8_autoinc"
880        }
881
882        fn columns() -> &'static [ColumnDef] {
883            &[ColumnDef {
884                name: "counter",
885                data_type: DataTypeKind::Uint8,
886                auto_increment: true,
887                nullable: false,
888                primary_key: true,
889                unique: true,
890                foreign_key: None,
891                default: None,
892                renamed_from: &[],
893            }]
894        }
895
896        fn primary_key() -> &'static str {
897            "counter"
898        }
899
900        fn indexes() -> &'static [IndexDef] {
901            &[IndexDef(&["counter"])]
902        }
903
904        fn to_values(self) -> Vec<(ColumnDef, Value)> {
905            vec![]
906        }
907
908        fn sanitizer(
909            _column_name: &'static str,
910        ) -> Option<Box<dyn wasm_dbms_api::prelude::Sanitize>> {
911            None
912        }
913
914        fn validator(
915            _column_name: &'static str,
916        ) -> Option<Box<dyn wasm_dbms_api::prelude::Validate>> {
917            None
918        }
919    }
920
921    #[test]
922    fn test_zero_values_for_all_integer_types() {
923        assert_eq!(
924            AutoincrementLedger::zero(DataTypeKind::Int8),
925            Value::Int8(0.into())
926        );
927        assert_eq!(
928            AutoincrementLedger::zero(DataTypeKind::Int16),
929            Value::Int16(0.into())
930        );
931        assert_eq!(
932            AutoincrementLedger::zero(DataTypeKind::Int32),
933            Value::Int32(0.into())
934        );
935        assert_eq!(
936            AutoincrementLedger::zero(DataTypeKind::Int64),
937            Value::Int64(0.into())
938        );
939        assert_eq!(
940            AutoincrementLedger::zero(DataTypeKind::Uint8),
941            Value::Uint8(0.into())
942        );
943        assert_eq!(
944            AutoincrementLedger::zero(DataTypeKind::Uint16),
945            Value::Uint16(0.into())
946        );
947        assert_eq!(
948            AutoincrementLedger::zero(DataTypeKind::Uint32),
949            Value::Uint32(0.into())
950        );
951        assert_eq!(
952            AutoincrementLedger::zero(DataTypeKind::Uint64),
953            Value::Uint64(0.into())
954        );
955    }
956
957    #[test]
958    #[should_panic(expected = "unsupported autoincrement type")]
959    fn test_zero_panics_on_unsupported_type() {
960        let _ = AutoincrementLedger::zero(DataTypeKind::Text);
961    }
962
963    fn snapshot_with_autoincrement_columns(
964        names: Vec<String>,
965    ) -> wasm_dbms_api::prelude::TableSchemaSnapshot {
966        use wasm_dbms_api::prelude::{ColumnSnapshot, DataTypeSnapshot, TableSchemaSnapshot};
967
968        TableSchemaSnapshot {
969            version: TableSchemaSnapshot::latest_version(),
970            name: "counters".to_string(),
971            primary_key: names[0].clone(),
972            alignment: 8,
973            columns: names
974                .into_iter()
975                .map(|name| ColumnSnapshot {
976                    name,
977                    data_type: DataTypeSnapshot::Uint32,
978                    nullable: false,
979                    auto_increment: true,
980                    unique: false,
981                    primary_key: false,
982                    foreign_key: None,
983                    default: None,
984                })
985                .collect(),
986            indexes: vec![],
987        }
988    }
989
990    #[test]
991    fn test_init_from_snapshot_accepts_255_autoincrement_columns() {
992        let mut mm = make_mm();
993        let page = mm.claim_page().expect("claim");
994        let names = (0..255).map(|i| format!("c{i}")).collect::<Vec<_>>();
995        let snapshot = snapshot_with_autoincrement_columns(names);
996
997        AutoincrementLedger::init_from_snapshot(page, &snapshot, &mut mm).expect("init");
998
999        let mut reloaded = AutoincrementLedger::load(page, &mut mm).expect("load");
1000        assert_eq!(
1001            reloaded.next("c254", &mut mm).expect("next"),
1002            Value::Uint32(1u32.into())
1003        );
1004    }
1005
1006    #[test]
1007    fn test_init_from_snapshot_rejects_256_autoincrement_columns() {
1008        let mut mm = make_mm();
1009        let page = mm.claim_page().expect("claim");
1010        let names = (0..256).map(|i| format!("c{i}")).collect::<Vec<_>>();
1011        let snapshot = snapshot_with_autoincrement_columns(names);
1012
1013        let result = AutoincrementLedger::init_from_snapshot(page, &snapshot, &mut mm);
1014        assert!(matches!(result, Err(MemoryError::ConstraintViolation(_))));
1015    }
1016
1017    #[test]
1018    fn test_init_from_snapshot_accepts_255_byte_autoincrement_column_name() {
1019        let mut mm = make_mm();
1020        let page = mm.claim_page().expect("claim");
1021        let name = "x".repeat(255);
1022        let snapshot = snapshot_with_autoincrement_columns(vec![name.clone()]);
1023
1024        AutoincrementLedger::init_from_snapshot(page, &snapshot, &mut mm).expect("init");
1025
1026        let mut reloaded = AutoincrementLedger::load(page, &mut mm).expect("load");
1027        assert_eq!(
1028            reloaded.next(&name, &mut mm).expect("next"),
1029            Value::Uint32(1u32.into())
1030        );
1031    }
1032
1033    #[test]
1034    fn test_init_from_snapshot_rejects_256_byte_autoincrement_column_name() {
1035        let mut mm = make_mm();
1036        let page = mm.claim_page().expect("claim");
1037        let snapshot = snapshot_with_autoincrement_columns(vec!["x".repeat(256)]);
1038
1039        let result = AutoincrementLedger::init_from_snapshot(page, &snapshot, &mut mm);
1040        assert!(matches!(result, Err(MemoryError::ConstraintViolation(_))));
1041    }
1042}