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::{MemoryResult, Page};
6use wasm_dbms_api::prelude::{DataTypeKind, TableSchema, Value};
7
8use self::registry::AutoincrementRegistry;
9use crate::MemoryAccess;
10
11/// Ledger for autoincrement values, used by tables with autoincrement columns.
12#[derive(Debug)]
13pub struct AutoincrementLedger {
14    /// page used to store autoincrement values
15    page: Page,
16    /// mapping between the column name and the current autoincrement value for that column
17    registry: AutoincrementRegistry,
18}
19
20impl AutoincrementLedger {
21    /// Initialize the [`AutoincrementLedger`] for the given table schema, and write it to the given page.
22    ///
23    /// Each autoincrement column in the table schema will be initialized in the registry with the appropriate zero value.
24    pub fn init<TS>(page: Page, mm: &mut impl MemoryAccess) -> MemoryResult<Self>
25    where
26        TS: TableSchema,
27    {
28        let mut registry = AutoincrementRegistry::default();
29        // init each autoincrement column in the registry with the appropriate zero value
30        for auto_increment_column in TS::columns().iter().filter(|c| c.auto_increment) {
31            let zero = Self::zero(auto_increment_column.data_type);
32            registry.init(auto_increment_column.name, zero);
33        }
34        // write the registry to the page
35        mm.write_at(page, 0, &registry)?;
36
37        Ok(Self { page, registry })
38    }
39
40    /// Load the [`AutoincrementLedger`] from the given page.
41    pub fn load(page: Page, mm: &mut impl MemoryAccess) -> MemoryResult<Self> {
42        Ok(Self {
43            page,
44            registry: mm.read_at(page, 0)?,
45        })
46    }
47
48    /// Returns the next autoincrement [`Value`] for the given column, and persists the updated registry to memory.
49    ///
50    /// # Errors
51    ///
52    /// Returns [`MemoryError::AutoincrementOverflow`] if the column has reached its maximum value.
53    pub fn next(&mut self, column: &str, mm: &mut impl MemoryAccess) -> MemoryResult<Value> {
54        let value = self.registry.next(column)?;
55        mm.write_at(self.page, 0, &self.registry)?;
56        Ok(value)
57    }
58
59    /// Returns the zero [`Value`] based on the column [`DataTypeKind`].
60    fn zero(data_type: DataTypeKind) -> Value {
61        match data_type {
62            DataTypeKind::Int8 => Value::Int8(0.into()),
63            DataTypeKind::Int16 => Value::Int16(0.into()),
64            DataTypeKind::Int32 => Value::Int32(0.into()),
65            DataTypeKind::Int64 => Value::Int64(0.into()),
66            DataTypeKind::Uint8 => Value::Uint8(0.into()),
67            DataTypeKind::Uint16 => Value::Uint16(0.into()),
68            DataTypeKind::Uint32 => Value::Uint32(0.into()),
69            DataTypeKind::Uint64 => Value::Uint64(0.into()),
70            data_type => panic!("unsupported autoincrement type: {data_type:?}"),
71        }
72    }
73}
74
75#[cfg(test)]
76mod tests {
77
78    use candid::CandidType;
79    use serde::{Deserialize, Serialize};
80    use wasm_dbms_api::prelude::{
81        ColumnDef, DEFAULT_ALIGNMENT, DataSize, DbmsResult, Encode, IndexDef, InsertRecord, MSize,
82        MemoryError, MemoryResult, NoForeignFetcher, PageOffset, TableColumns, TableRecord,
83        TableSchema, UpdateRecord, Value,
84    };
85
86    use super::*;
87    use crate::{HeapMemoryProvider, MemoryManager};
88
89    fn make_mm() -> MemoryManager<HeapMemoryProvider> {
90        MemoryManager::init(HeapMemoryProvider::default())
91    }
92
93    // -- Mock: single Uint32 autoincrement column --
94
95    #[derive(Clone, CandidType)]
96    struct SingleAutoincTable;
97
98    impl Encode for SingleAutoincTable {
99        const SIZE: DataSize = DataSize::Dynamic;
100        const ALIGNMENT: PageOffset = DEFAULT_ALIGNMENT;
101
102        fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
103            std::borrow::Cow::Owned(vec![])
104        }
105
106        fn decode(_data: std::borrow::Cow<[u8]>) -> MemoryResult<Self>
107        where
108            Self: Sized,
109        {
110            Ok(Self)
111        }
112
113        fn size(&self) -> MSize {
114            0
115        }
116    }
117
118    #[derive(Clone, CandidType, Deserialize)]
119    struct SingleAutoincTableRecord;
120
121    impl TableRecord for SingleAutoincTableRecord {
122        type Schema = SingleAutoincTable;
123
124        fn from_values(_values: TableColumns) -> Self {
125            Self
126        }
127
128        fn to_values(&self) -> Vec<(ColumnDef, Value)> {
129            vec![]
130        }
131    }
132
133    #[derive(Clone, CandidType, Serialize)]
134    struct SingleAutoincTableInsert;
135
136    impl InsertRecord for SingleAutoincTableInsert {
137        type Record = SingleAutoincTableRecord;
138        type Schema = SingleAutoincTable;
139
140        fn from_values(_values: &[(ColumnDef, Value)]) -> DbmsResult<Self> {
141            Ok(Self)
142        }
143
144        fn into_values(self) -> Vec<(ColumnDef, Value)> {
145            vec![]
146        }
147
148        fn into_record(self) -> Self::Schema {
149            SingleAutoincTable
150        }
151    }
152
153    #[derive(Clone, CandidType, Serialize)]
154    struct SingleAutoincTableUpdate;
155
156    impl UpdateRecord for SingleAutoincTableUpdate {
157        type Record = SingleAutoincTableRecord;
158        type Schema = SingleAutoincTable;
159
160        fn from_values(
161            _values: &[(ColumnDef, Value)],
162            _where_clause: Option<wasm_dbms_api::prelude::Filter>,
163        ) -> Self {
164            Self
165        }
166
167        fn update_values(&self) -> Vec<(ColumnDef, Value)> {
168            vec![]
169        }
170
171        fn where_clause(&self) -> Option<wasm_dbms_api::prelude::Filter> {
172            None
173        }
174    }
175
176    impl TableSchema for SingleAutoincTable {
177        type Record = SingleAutoincTableRecord;
178        type Insert = SingleAutoincTableInsert;
179        type Update = SingleAutoincTableUpdate;
180        type ForeignFetcher = NoForeignFetcher;
181
182        fn table_name() -> &'static str {
183            "single_autoinc"
184        }
185
186        fn columns() -> &'static [ColumnDef] {
187            &[ColumnDef {
188                name: "id",
189                data_type: DataTypeKind::Uint32,
190                auto_increment: true,
191                nullable: false,
192                primary_key: true,
193                unique: true,
194                foreign_key: None,
195            }]
196        }
197
198        fn primary_key() -> &'static str {
199            "id"
200        }
201
202        fn indexes() -> &'static [IndexDef] {
203            &[IndexDef(&["id"])]
204        }
205
206        fn to_values(self) -> Vec<(ColumnDef, Value)> {
207            vec![]
208        }
209
210        fn sanitizer(
211            _column_name: &'static str,
212        ) -> Option<Box<dyn wasm_dbms_api::prelude::Sanitize>> {
213            None
214        }
215
216        fn validator(
217            _column_name: &'static str,
218        ) -> Option<Box<dyn wasm_dbms_api::prelude::Validate>> {
219            None
220        }
221    }
222
223    // -- Mock: two autoincrement columns --
224
225    #[derive(Clone, CandidType)]
226    struct MultiAutoincTable;
227
228    impl Encode for MultiAutoincTable {
229        const SIZE: DataSize = DataSize::Dynamic;
230        const ALIGNMENT: PageOffset = DEFAULT_ALIGNMENT;
231
232        fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
233            std::borrow::Cow::Owned(vec![])
234        }
235
236        fn decode(_data: std::borrow::Cow<[u8]>) -> MemoryResult<Self>
237        where
238            Self: Sized,
239        {
240            Ok(Self)
241        }
242
243        fn size(&self) -> MSize {
244            0
245        }
246    }
247
248    #[derive(Clone, CandidType, Deserialize)]
249    struct MultiAutoincTableRecord;
250
251    impl TableRecord for MultiAutoincTableRecord {
252        type Schema = MultiAutoincTable;
253
254        fn from_values(_values: TableColumns) -> Self {
255            Self
256        }
257
258        fn to_values(&self) -> Vec<(ColumnDef, Value)> {
259            vec![]
260        }
261    }
262
263    #[derive(Clone, CandidType, Serialize)]
264    struct MultiAutoincTableInsert;
265
266    impl InsertRecord for MultiAutoincTableInsert {
267        type Record = MultiAutoincTableRecord;
268        type Schema = MultiAutoincTable;
269
270        fn from_values(_values: &[(ColumnDef, Value)]) -> DbmsResult<Self> {
271            Ok(Self)
272        }
273
274        fn into_values(self) -> Vec<(ColumnDef, Value)> {
275            vec![]
276        }
277
278        fn into_record(self) -> Self::Schema {
279            MultiAutoincTable
280        }
281    }
282
283    #[derive(Clone, CandidType, Serialize)]
284    struct MultiAutoincTableUpdate;
285
286    impl UpdateRecord for MultiAutoincTableUpdate {
287        type Record = MultiAutoincTableRecord;
288        type Schema = MultiAutoincTable;
289
290        fn from_values(
291            _values: &[(ColumnDef, Value)],
292            _where_clause: Option<wasm_dbms_api::prelude::Filter>,
293        ) -> Self {
294            Self
295        }
296
297        fn update_values(&self) -> Vec<(ColumnDef, Value)> {
298            vec![]
299        }
300
301        fn where_clause(&self) -> Option<wasm_dbms_api::prelude::Filter> {
302            None
303        }
304    }
305
306    impl TableSchema for MultiAutoincTable {
307        type Record = MultiAutoincTableRecord;
308        type Insert = MultiAutoincTableInsert;
309        type Update = MultiAutoincTableUpdate;
310        type ForeignFetcher = NoForeignFetcher;
311
312        fn table_name() -> &'static str {
313            "multi_autoinc"
314        }
315
316        fn columns() -> &'static [ColumnDef] {
317            &[
318                ColumnDef {
319                    name: "id",
320                    data_type: DataTypeKind::Uint32,
321                    auto_increment: true,
322                    nullable: false,
323                    primary_key: true,
324                    unique: true,
325                    foreign_key: None,
326                },
327                ColumnDef {
328                    name: "seq",
329                    data_type: DataTypeKind::Uint64,
330                    auto_increment: true,
331                    nullable: false,
332                    primary_key: false,
333                    unique: false,
334                    foreign_key: None,
335                },
336            ]
337        }
338
339        fn primary_key() -> &'static str {
340            "id"
341        }
342
343        fn indexes() -> &'static [IndexDef] {
344            &[IndexDef(&["id"])]
345        }
346
347        fn to_values(self) -> Vec<(ColumnDef, Value)> {
348            vec![]
349        }
350
351        fn sanitizer(
352            _column_name: &'static str,
353        ) -> Option<Box<dyn wasm_dbms_api::prelude::Sanitize>> {
354            None
355        }
356
357        fn validator(
358            _column_name: &'static str,
359        ) -> Option<Box<dyn wasm_dbms_api::prelude::Validate>> {
360            None
361        }
362    }
363
364    // -- Mock: no autoincrement columns --
365
366    #[derive(Clone, CandidType)]
367    struct NoAutoincTable;
368
369    impl Encode for NoAutoincTable {
370        const SIZE: DataSize = DataSize::Dynamic;
371        const ALIGNMENT: PageOffset = DEFAULT_ALIGNMENT;
372
373        fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
374            std::borrow::Cow::Owned(vec![])
375        }
376
377        fn decode(_data: std::borrow::Cow<[u8]>) -> MemoryResult<Self>
378        where
379            Self: Sized,
380        {
381            Ok(Self)
382        }
383
384        fn size(&self) -> MSize {
385            0
386        }
387    }
388
389    #[derive(Clone, CandidType, Deserialize)]
390    struct NoAutoincTableRecord;
391
392    impl TableRecord for NoAutoincTableRecord {
393        type Schema = NoAutoincTable;
394
395        fn from_values(_values: TableColumns) -> Self {
396            Self
397        }
398
399        fn to_values(&self) -> Vec<(ColumnDef, Value)> {
400            vec![]
401        }
402    }
403
404    #[derive(Clone, CandidType, Serialize)]
405    struct NoAutoincTableInsert;
406
407    impl InsertRecord for NoAutoincTableInsert {
408        type Record = NoAutoincTableRecord;
409        type Schema = NoAutoincTable;
410
411        fn from_values(_values: &[(ColumnDef, Value)]) -> DbmsResult<Self> {
412            Ok(Self)
413        }
414
415        fn into_values(self) -> Vec<(ColumnDef, Value)> {
416            vec![]
417        }
418
419        fn into_record(self) -> Self::Schema {
420            NoAutoincTable
421        }
422    }
423
424    #[derive(Clone, CandidType, Serialize)]
425    struct NoAutoincTableUpdate;
426
427    impl UpdateRecord for NoAutoincTableUpdate {
428        type Record = NoAutoincTableRecord;
429        type Schema = NoAutoincTable;
430
431        fn from_values(
432            _values: &[(ColumnDef, Value)],
433            _where_clause: Option<wasm_dbms_api::prelude::Filter>,
434        ) -> Self {
435            Self
436        }
437
438        fn update_values(&self) -> Vec<(ColumnDef, Value)> {
439            vec![]
440        }
441
442        fn where_clause(&self) -> Option<wasm_dbms_api::prelude::Filter> {
443            None
444        }
445    }
446
447    impl TableSchema for NoAutoincTable {
448        type Record = NoAutoincTableRecord;
449        type Insert = NoAutoincTableInsert;
450        type Update = NoAutoincTableUpdate;
451        type ForeignFetcher = NoForeignFetcher;
452
453        fn table_name() -> &'static str {
454            "no_autoinc"
455        }
456
457        fn columns() -> &'static [ColumnDef] {
458            &[ColumnDef {
459                name: "id",
460                data_type: DataTypeKind::Uint32,
461                auto_increment: false,
462                nullable: false,
463                primary_key: true,
464                unique: true,
465                foreign_key: None,
466            }]
467        }
468
469        fn primary_key() -> &'static str {
470            "id"
471        }
472
473        fn indexes() -> &'static [IndexDef] {
474            &[IndexDef(&["id"])]
475        }
476
477        fn to_values(self) -> Vec<(ColumnDef, Value)> {
478            vec![]
479        }
480
481        fn sanitizer(
482            _column_name: &'static str,
483        ) -> Option<Box<dyn wasm_dbms_api::prelude::Sanitize>> {
484            None
485        }
486
487        fn validator(
488            _column_name: &'static str,
489        ) -> Option<Box<dyn wasm_dbms_api::prelude::Validate>> {
490            None
491        }
492    }
493
494    // -- Tests --
495
496    #[test]
497    fn test_init_single_autoincrement_column() {
498        let mut mm = make_mm();
499        let page = mm.allocate_page().expect("failed to allocate page");
500
501        let ledger = AutoincrementLedger::init::<SingleAutoincTable>(page, &mut mm)
502            .expect("failed to init ledger");
503
504        // verify the page was stored
505        assert_eq!(ledger.page, page);
506    }
507
508    #[test]
509    fn test_init_multiple_autoincrement_columns() {
510        let mut mm = make_mm();
511        let page = mm.allocate_page().expect("failed to allocate page");
512
513        let mut ledger = AutoincrementLedger::init::<MultiAutoincTable>(page, &mut mm)
514            .expect("failed to init ledger");
515
516        // both columns should produce their first value
517        let id_val = ledger.next("id", &mut mm).expect("failed to get next id");
518        let seq_val = ledger.next("seq", &mut mm).expect("failed to get next seq");
519
520        assert_eq!(id_val, Value::Uint32(1u32.into()));
521        assert_eq!(seq_val, Value::Uint64(1u64.into()));
522    }
523
524    #[test]
525    fn test_init_no_autoincrement_columns() {
526        let mut mm = make_mm();
527        let page = mm.allocate_page().expect("failed to allocate page");
528
529        // should succeed but the registry is empty
530        let ledger = AutoincrementLedger::init::<NoAutoincTable>(page, &mut mm)
531            .expect("failed to init ledger");
532
533        assert_eq!(ledger.page, page);
534    }
535
536    #[test]
537    fn test_load_after_init() {
538        let mut mm = make_mm();
539        let page = mm.allocate_page().expect("failed to allocate page");
540
541        let mut ledger =
542            AutoincrementLedger::init::<SingleAutoincTable>(page, &mut mm).expect("failed to init");
543
544        // advance once
545        let _ = ledger.next("id", &mut mm).expect("next failed");
546
547        // load from memory and continue
548        let mut reloaded = AutoincrementLedger::load(page, &mut mm).expect("failed to load ledger");
549
550        let value = reloaded
551            .next("id", &mut mm)
552            .expect("next after reload failed");
553        assert_eq!(value, Value::Uint32(2u32.into()));
554    }
555
556    #[test]
557    fn test_next_returns_sequential_values() {
558        let mut mm = make_mm();
559        let page = mm.allocate_page().expect("failed to allocate page");
560
561        let mut ledger =
562            AutoincrementLedger::init::<SingleAutoincTable>(page, &mut mm).expect("failed to init");
563
564        for expected in 1u32..=100 {
565            let value = ledger.next("id", &mut mm).expect("next failed");
566            assert_eq!(value, Value::Uint32(expected.into()));
567        }
568    }
569
570    #[test]
571    fn test_next_persists_to_memory() {
572        let mut mm = make_mm();
573        let page = mm.allocate_page().expect("failed to allocate page");
574
575        let mut ledger =
576            AutoincrementLedger::init::<SingleAutoincTable>(page, &mut mm).expect("failed to init");
577
578        // advance 5 times
579        for _ in 0..5 {
580            let _ = ledger.next("id", &mut mm).expect("next failed");
581        }
582
583        // reload and verify the counter continued from where it was
584        let mut reloaded = AutoincrementLedger::load(page, &mut mm).expect("failed to load ledger");
585        let value = reloaded
586            .next("id", &mut mm)
587            .expect("next after reload failed");
588        assert_eq!(value, Value::Uint32(6u32.into()));
589    }
590
591    #[test]
592    fn test_next_overflow_returns_error() {
593        let mut mm = make_mm();
594        let page = mm.allocate_page().expect("failed to allocate page");
595
596        // init with Uint8 to quickly reach max
597        let mut ledger =
598            AutoincrementLedger::init::<Uint8AutoincTable>(page, &mut mm).expect("failed to init");
599
600        // advance to 255
601        for _ in 0..255 {
602            let _ = ledger.next("counter", &mut mm).expect("next failed");
603        }
604
605        // next should fail with overflow
606        let result = ledger.next("counter", &mut mm);
607        assert!(result.is_err());
608        assert!(matches!(
609            result.unwrap_err(),
610            MemoryError::AutoincrementOverflow(_)
611        ));
612    }
613
614    #[test]
615    fn test_multi_column_independence_across_reload() {
616        let mut mm = make_mm();
617        let page = mm.allocate_page().expect("failed to allocate page");
618
619        let mut ledger =
620            AutoincrementLedger::init::<MultiAutoincTable>(page, &mut mm).expect("failed to init");
621
622        // advance id 3 times, seq 1 time
623        for _ in 0..3 {
624            let _ = ledger.next("id", &mut mm).expect("id next failed");
625        }
626        let _ = ledger.next("seq", &mut mm).expect("seq next failed");
627
628        // reload and verify both columns preserved their independent state
629        let mut reloaded = AutoincrementLedger::load(page, &mut mm).expect("failed to load ledger");
630
631        let id_val = reloaded
632            .next("id", &mut mm)
633            .expect("id next after reload failed");
634        let seq_val = reloaded
635            .next("seq", &mut mm)
636            .expect("seq next after reload failed");
637
638        assert_eq!(id_val, Value::Uint32(4u32.into()));
639        assert_eq!(seq_val, Value::Uint64(2u64.into()));
640    }
641
642    // -- Mock: Uint8 autoincrement column (for overflow testing) --
643
644    #[derive(Clone, CandidType)]
645    struct Uint8AutoincTable;
646
647    impl Encode for Uint8AutoincTable {
648        const SIZE: DataSize = DataSize::Dynamic;
649        const ALIGNMENT: PageOffset = DEFAULT_ALIGNMENT;
650
651        fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
652            std::borrow::Cow::Owned(vec![])
653        }
654
655        fn decode(_data: std::borrow::Cow<[u8]>) -> MemoryResult<Self>
656        where
657            Self: Sized,
658        {
659            Ok(Self)
660        }
661
662        fn size(&self) -> MSize {
663            0
664        }
665    }
666
667    #[derive(Clone, CandidType, Deserialize)]
668    struct Uint8AutoincTableRecord;
669
670    impl TableRecord for Uint8AutoincTableRecord {
671        type Schema = Uint8AutoincTable;
672
673        fn from_values(_values: TableColumns) -> Self {
674            Self
675        }
676
677        fn to_values(&self) -> Vec<(ColumnDef, Value)> {
678            vec![]
679        }
680    }
681
682    #[derive(Clone, CandidType, Serialize)]
683    struct Uint8AutoincTableInsert;
684
685    impl InsertRecord for Uint8AutoincTableInsert {
686        type Record = Uint8AutoincTableRecord;
687        type Schema = Uint8AutoincTable;
688
689        fn from_values(_values: &[(ColumnDef, Value)]) -> DbmsResult<Self> {
690            Ok(Self)
691        }
692
693        fn into_values(self) -> Vec<(ColumnDef, Value)> {
694            vec![]
695        }
696
697        fn into_record(self) -> Self::Schema {
698            Uint8AutoincTable
699        }
700    }
701
702    #[derive(Clone, CandidType, Serialize)]
703    struct Uint8AutoincTableUpdate;
704
705    impl UpdateRecord for Uint8AutoincTableUpdate {
706        type Record = Uint8AutoincTableRecord;
707        type Schema = Uint8AutoincTable;
708
709        fn from_values(
710            _values: &[(ColumnDef, Value)],
711            _where_clause: Option<wasm_dbms_api::prelude::Filter>,
712        ) -> Self {
713            Self
714        }
715
716        fn update_values(&self) -> Vec<(ColumnDef, Value)> {
717            vec![]
718        }
719
720        fn where_clause(&self) -> Option<wasm_dbms_api::prelude::Filter> {
721            None
722        }
723    }
724
725    impl TableSchema for Uint8AutoincTable {
726        type Record = Uint8AutoincTableRecord;
727        type Insert = Uint8AutoincTableInsert;
728        type Update = Uint8AutoincTableUpdate;
729        type ForeignFetcher = NoForeignFetcher;
730
731        fn table_name() -> &'static str {
732            "uint8_autoinc"
733        }
734
735        fn columns() -> &'static [ColumnDef] {
736            &[ColumnDef {
737                name: "counter",
738                data_type: DataTypeKind::Uint8,
739                auto_increment: true,
740                nullable: false,
741                primary_key: true,
742                unique: true,
743                foreign_key: None,
744            }]
745        }
746
747        fn primary_key() -> &'static str {
748            "counter"
749        }
750
751        fn indexes() -> &'static [IndexDef] {
752            &[IndexDef(&["counter"])]
753        }
754
755        fn to_values(self) -> Vec<(ColumnDef, Value)> {
756            vec![]
757        }
758
759        fn sanitizer(
760            _column_name: &'static str,
761        ) -> Option<Box<dyn wasm_dbms_api::prelude::Sanitize>> {
762            None
763        }
764
765        fn validator(
766            _column_name: &'static str,
767        ) -> Option<Box<dyn wasm_dbms_api::prelude::Validate>> {
768            None
769        }
770    }
771
772    #[test]
773    fn test_zero_values_for_all_integer_types() {
774        assert_eq!(
775            AutoincrementLedger::zero(DataTypeKind::Int8),
776            Value::Int8(0.into())
777        );
778        assert_eq!(
779            AutoincrementLedger::zero(DataTypeKind::Int16),
780            Value::Int16(0.into())
781        );
782        assert_eq!(
783            AutoincrementLedger::zero(DataTypeKind::Int32),
784            Value::Int32(0.into())
785        );
786        assert_eq!(
787            AutoincrementLedger::zero(DataTypeKind::Int64),
788            Value::Int64(0.into())
789        );
790        assert_eq!(
791            AutoincrementLedger::zero(DataTypeKind::Uint8),
792            Value::Uint8(0.into())
793        );
794        assert_eq!(
795            AutoincrementLedger::zero(DataTypeKind::Uint16),
796            Value::Uint16(0.into())
797        );
798        assert_eq!(
799            AutoincrementLedger::zero(DataTypeKind::Uint32),
800            Value::Uint32(0.into())
801        );
802        assert_eq!(
803            AutoincrementLedger::zero(DataTypeKind::Uint64),
804            Value::Uint64(0.into())
805        );
806    }
807
808    #[test]
809    #[should_panic(expected = "unsupported autoincrement type")]
810    fn test_zero_panics_on_unsupported_type() {
811        let _ = AutoincrementLedger::zero(DataTypeKind::Text);
812    }
813}