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                default: None,
196                renamed_from: &[],
197            }]
198        }
199
200        fn primary_key() -> &'static str {
201            "id"
202        }
203
204        fn indexes() -> &'static [IndexDef] {
205            &[IndexDef(&["id"])]
206        }
207
208        fn to_values(self) -> Vec<(ColumnDef, Value)> {
209            vec![]
210        }
211
212        fn sanitizer(
213            _column_name: &'static str,
214        ) -> Option<Box<dyn wasm_dbms_api::prelude::Sanitize>> {
215            None
216        }
217
218        fn validator(
219            _column_name: &'static str,
220        ) -> Option<Box<dyn wasm_dbms_api::prelude::Validate>> {
221            None
222        }
223    }
224
225    // -- Mock: two autoincrement columns --
226
227    #[derive(Clone, CandidType)]
228    struct MultiAutoincTable;
229
230    impl Encode for MultiAutoincTable {
231        const SIZE: DataSize = DataSize::Dynamic;
232        const ALIGNMENT: PageOffset = DEFAULT_ALIGNMENT;
233
234        fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
235            std::borrow::Cow::Owned(vec![])
236        }
237
238        fn decode(_data: std::borrow::Cow<[u8]>) -> MemoryResult<Self>
239        where
240            Self: Sized,
241        {
242            Ok(Self)
243        }
244
245        fn size(&self) -> MSize {
246            0
247        }
248    }
249
250    #[derive(Clone, CandidType, Deserialize)]
251    struct MultiAutoincTableRecord;
252
253    impl TableRecord for MultiAutoincTableRecord {
254        type Schema = MultiAutoincTable;
255
256        fn from_values(_values: TableColumns) -> Self {
257            Self
258        }
259
260        fn to_values(&self) -> Vec<(ColumnDef, Value)> {
261            vec![]
262        }
263    }
264
265    #[derive(Clone, CandidType, Serialize)]
266    struct MultiAutoincTableInsert;
267
268    impl InsertRecord for MultiAutoincTableInsert {
269        type Record = MultiAutoincTableRecord;
270        type Schema = MultiAutoincTable;
271
272        fn from_values(_values: &[(ColumnDef, Value)]) -> DbmsResult<Self> {
273            Ok(Self)
274        }
275
276        fn into_values(self) -> Vec<(ColumnDef, Value)> {
277            vec![]
278        }
279
280        fn into_record(self) -> Self::Schema {
281            MultiAutoincTable
282        }
283    }
284
285    #[derive(Clone, CandidType, Serialize)]
286    struct MultiAutoincTableUpdate;
287
288    impl UpdateRecord for MultiAutoincTableUpdate {
289        type Record = MultiAutoincTableRecord;
290        type Schema = MultiAutoincTable;
291
292        fn from_values(
293            _values: &[(ColumnDef, Value)],
294            _where_clause: Option<wasm_dbms_api::prelude::Filter>,
295        ) -> Self {
296            Self
297        }
298
299        fn update_values(&self) -> Vec<(ColumnDef, Value)> {
300            vec![]
301        }
302
303        fn where_clause(&self) -> Option<wasm_dbms_api::prelude::Filter> {
304            None
305        }
306    }
307
308    impl TableSchema for MultiAutoincTable {
309        type Record = MultiAutoincTableRecord;
310        type Insert = MultiAutoincTableInsert;
311        type Update = MultiAutoincTableUpdate;
312        type ForeignFetcher = NoForeignFetcher;
313
314        fn table_name() -> &'static str {
315            "multi_autoinc"
316        }
317
318        fn columns() -> &'static [ColumnDef] {
319            &[
320                ColumnDef {
321                    name: "id",
322                    data_type: DataTypeKind::Uint32,
323                    auto_increment: true,
324                    nullable: false,
325                    primary_key: true,
326                    unique: true,
327                    foreign_key: None,
328                    default: None,
329                    renamed_from: &[],
330                },
331                ColumnDef {
332                    name: "seq",
333                    data_type: DataTypeKind::Uint64,
334                    auto_increment: true,
335                    nullable: false,
336                    primary_key: false,
337                    unique: false,
338                    foreign_key: None,
339                    default: None,
340                    renamed_from: &[],
341                },
342            ]
343        }
344
345        fn primary_key() -> &'static str {
346            "id"
347        }
348
349        fn indexes() -> &'static [IndexDef] {
350            &[IndexDef(&["id"])]
351        }
352
353        fn to_values(self) -> Vec<(ColumnDef, Value)> {
354            vec![]
355        }
356
357        fn sanitizer(
358            _column_name: &'static str,
359        ) -> Option<Box<dyn wasm_dbms_api::prelude::Sanitize>> {
360            None
361        }
362
363        fn validator(
364            _column_name: &'static str,
365        ) -> Option<Box<dyn wasm_dbms_api::prelude::Validate>> {
366            None
367        }
368    }
369
370    // -- Mock: no autoincrement columns --
371
372    #[derive(Clone, CandidType)]
373    struct NoAutoincTable;
374
375    impl Encode for NoAutoincTable {
376        const SIZE: DataSize = DataSize::Dynamic;
377        const ALIGNMENT: PageOffset = DEFAULT_ALIGNMENT;
378
379        fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
380            std::borrow::Cow::Owned(vec![])
381        }
382
383        fn decode(_data: std::borrow::Cow<[u8]>) -> MemoryResult<Self>
384        where
385            Self: Sized,
386        {
387            Ok(Self)
388        }
389
390        fn size(&self) -> MSize {
391            0
392        }
393    }
394
395    #[derive(Clone, CandidType, Deserialize)]
396    struct NoAutoincTableRecord;
397
398    impl TableRecord for NoAutoincTableRecord {
399        type Schema = NoAutoincTable;
400
401        fn from_values(_values: TableColumns) -> Self {
402            Self
403        }
404
405        fn to_values(&self) -> Vec<(ColumnDef, Value)> {
406            vec![]
407        }
408    }
409
410    #[derive(Clone, CandidType, Serialize)]
411    struct NoAutoincTableInsert;
412
413    impl InsertRecord for NoAutoincTableInsert {
414        type Record = NoAutoincTableRecord;
415        type Schema = NoAutoincTable;
416
417        fn from_values(_values: &[(ColumnDef, Value)]) -> DbmsResult<Self> {
418            Ok(Self)
419        }
420
421        fn into_values(self) -> Vec<(ColumnDef, Value)> {
422            vec![]
423        }
424
425        fn into_record(self) -> Self::Schema {
426            NoAutoincTable
427        }
428    }
429
430    #[derive(Clone, CandidType, Serialize)]
431    struct NoAutoincTableUpdate;
432
433    impl UpdateRecord for NoAutoincTableUpdate {
434        type Record = NoAutoincTableRecord;
435        type Schema = NoAutoincTable;
436
437        fn from_values(
438            _values: &[(ColumnDef, Value)],
439            _where_clause: Option<wasm_dbms_api::prelude::Filter>,
440        ) -> Self {
441            Self
442        }
443
444        fn update_values(&self) -> Vec<(ColumnDef, Value)> {
445            vec![]
446        }
447
448        fn where_clause(&self) -> Option<wasm_dbms_api::prelude::Filter> {
449            None
450        }
451    }
452
453    impl TableSchema for NoAutoincTable {
454        type Record = NoAutoincTableRecord;
455        type Insert = NoAutoincTableInsert;
456        type Update = NoAutoincTableUpdate;
457        type ForeignFetcher = NoForeignFetcher;
458
459        fn table_name() -> &'static str {
460            "no_autoinc"
461        }
462
463        fn columns() -> &'static [ColumnDef] {
464            &[ColumnDef {
465                name: "id",
466                data_type: DataTypeKind::Uint32,
467                auto_increment: false,
468                nullable: false,
469                primary_key: true,
470                unique: true,
471                foreign_key: None,
472                default: None,
473                renamed_from: &[],
474            }]
475        }
476
477        fn primary_key() -> &'static str {
478            "id"
479        }
480
481        fn indexes() -> &'static [IndexDef] {
482            &[IndexDef(&["id"])]
483        }
484
485        fn to_values(self) -> Vec<(ColumnDef, Value)> {
486            vec![]
487        }
488
489        fn sanitizer(
490            _column_name: &'static str,
491        ) -> Option<Box<dyn wasm_dbms_api::prelude::Sanitize>> {
492            None
493        }
494
495        fn validator(
496            _column_name: &'static str,
497        ) -> Option<Box<dyn wasm_dbms_api::prelude::Validate>> {
498            None
499        }
500    }
501
502    // -- Tests --
503
504    #[test]
505    fn test_init_single_autoincrement_column() {
506        let mut mm = make_mm();
507        let page = mm.claim_page().expect("failed to allocate page");
508
509        let ledger = AutoincrementLedger::init::<SingleAutoincTable>(page, &mut mm)
510            .expect("failed to init ledger");
511
512        // verify the page was stored
513        assert_eq!(ledger.page, page);
514    }
515
516    #[test]
517    fn test_init_multiple_autoincrement_columns() {
518        let mut mm = make_mm();
519        let page = mm.claim_page().expect("failed to allocate page");
520
521        let mut ledger = AutoincrementLedger::init::<MultiAutoincTable>(page, &mut mm)
522            .expect("failed to init ledger");
523
524        // both columns should produce their first value
525        let id_val = ledger.next("id", &mut mm).expect("failed to get next id");
526        let seq_val = ledger.next("seq", &mut mm).expect("failed to get next seq");
527
528        assert_eq!(id_val, Value::Uint32(1u32.into()));
529        assert_eq!(seq_val, Value::Uint64(1u64.into()));
530    }
531
532    #[test]
533    fn test_init_no_autoincrement_columns() {
534        let mut mm = make_mm();
535        let page = mm.claim_page().expect("failed to allocate page");
536
537        // should succeed but the registry is empty
538        let ledger = AutoincrementLedger::init::<NoAutoincTable>(page, &mut mm)
539            .expect("failed to init ledger");
540
541        assert_eq!(ledger.page, page);
542    }
543
544    #[test]
545    fn test_load_after_init() {
546        let mut mm = make_mm();
547        let page = mm.claim_page().expect("failed to allocate page");
548
549        let mut ledger =
550            AutoincrementLedger::init::<SingleAutoincTable>(page, &mut mm).expect("failed to init");
551
552        // advance once
553        let _ = ledger.next("id", &mut mm).expect("next failed");
554
555        // load from memory and continue
556        let mut reloaded = AutoincrementLedger::load(page, &mut mm).expect("failed to load ledger");
557
558        let value = reloaded
559            .next("id", &mut mm)
560            .expect("next after reload failed");
561        assert_eq!(value, Value::Uint32(2u32.into()));
562    }
563
564    #[test]
565    fn test_next_returns_sequential_values() {
566        let mut mm = make_mm();
567        let page = mm.claim_page().expect("failed to allocate page");
568
569        let mut ledger =
570            AutoincrementLedger::init::<SingleAutoincTable>(page, &mut mm).expect("failed to init");
571
572        for expected in 1u32..=100 {
573            let value = ledger.next("id", &mut mm).expect("next failed");
574            assert_eq!(value, Value::Uint32(expected.into()));
575        }
576    }
577
578    #[test]
579    fn test_next_persists_to_memory() {
580        let mut mm = make_mm();
581        let page = mm.claim_page().expect("failed to allocate page");
582
583        let mut ledger =
584            AutoincrementLedger::init::<SingleAutoincTable>(page, &mut mm).expect("failed to init");
585
586        // advance 5 times
587        for _ in 0..5 {
588            let _ = ledger.next("id", &mut mm).expect("next failed");
589        }
590
591        // reload and verify the counter continued from where it was
592        let mut reloaded = AutoincrementLedger::load(page, &mut mm).expect("failed to load ledger");
593        let value = reloaded
594            .next("id", &mut mm)
595            .expect("next after reload failed");
596        assert_eq!(value, Value::Uint32(6u32.into()));
597    }
598
599    #[test]
600    fn test_next_overflow_returns_error() {
601        let mut mm = make_mm();
602        let page = mm.claim_page().expect("failed to allocate page");
603
604        // init with Uint8 to quickly reach max
605        let mut ledger =
606            AutoincrementLedger::init::<Uint8AutoincTable>(page, &mut mm).expect("failed to init");
607
608        // advance to 255
609        for _ in 0..255 {
610            let _ = ledger.next("counter", &mut mm).expect("next failed");
611        }
612
613        // next should fail with overflow
614        let result = ledger.next("counter", &mut mm);
615        assert!(result.is_err());
616        assert!(matches!(
617            result.unwrap_err(),
618            MemoryError::AutoincrementOverflow(_)
619        ));
620    }
621
622    #[test]
623    fn test_multi_column_independence_across_reload() {
624        let mut mm = make_mm();
625        let page = mm.claim_page().expect("failed to allocate page");
626
627        let mut ledger =
628            AutoincrementLedger::init::<MultiAutoincTable>(page, &mut mm).expect("failed to init");
629
630        // advance id 3 times, seq 1 time
631        for _ in 0..3 {
632            let _ = ledger.next("id", &mut mm).expect("id next failed");
633        }
634        let _ = ledger.next("seq", &mut mm).expect("seq next failed");
635
636        // reload and verify both columns preserved their independent state
637        let mut reloaded = AutoincrementLedger::load(page, &mut mm).expect("failed to load ledger");
638
639        let id_val = reloaded
640            .next("id", &mut mm)
641            .expect("id next after reload failed");
642        let seq_val = reloaded
643            .next("seq", &mut mm)
644            .expect("seq next after reload failed");
645
646        assert_eq!(id_val, Value::Uint32(4u32.into()));
647        assert_eq!(seq_val, Value::Uint64(2u64.into()));
648    }
649
650    // -- Mock: Uint8 autoincrement column (for overflow testing) --
651
652    #[derive(Clone, CandidType)]
653    struct Uint8AutoincTable;
654
655    impl Encode for Uint8AutoincTable {
656        const SIZE: DataSize = DataSize::Dynamic;
657        const ALIGNMENT: PageOffset = DEFAULT_ALIGNMENT;
658
659        fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
660            std::borrow::Cow::Owned(vec![])
661        }
662
663        fn decode(_data: std::borrow::Cow<[u8]>) -> MemoryResult<Self>
664        where
665            Self: Sized,
666        {
667            Ok(Self)
668        }
669
670        fn size(&self) -> MSize {
671            0
672        }
673    }
674
675    #[derive(Clone, CandidType, Deserialize)]
676    struct Uint8AutoincTableRecord;
677
678    impl TableRecord for Uint8AutoincTableRecord {
679        type Schema = Uint8AutoincTable;
680
681        fn from_values(_values: TableColumns) -> Self {
682            Self
683        }
684
685        fn to_values(&self) -> Vec<(ColumnDef, Value)> {
686            vec![]
687        }
688    }
689
690    #[derive(Clone, CandidType, Serialize)]
691    struct Uint8AutoincTableInsert;
692
693    impl InsertRecord for Uint8AutoincTableInsert {
694        type Record = Uint8AutoincTableRecord;
695        type Schema = Uint8AutoincTable;
696
697        fn from_values(_values: &[(ColumnDef, Value)]) -> DbmsResult<Self> {
698            Ok(Self)
699        }
700
701        fn into_values(self) -> Vec<(ColumnDef, Value)> {
702            vec![]
703        }
704
705        fn into_record(self) -> Self::Schema {
706            Uint8AutoincTable
707        }
708    }
709
710    #[derive(Clone, CandidType, Serialize)]
711    struct Uint8AutoincTableUpdate;
712
713    impl UpdateRecord for Uint8AutoincTableUpdate {
714        type Record = Uint8AutoincTableRecord;
715        type Schema = Uint8AutoincTable;
716
717        fn from_values(
718            _values: &[(ColumnDef, Value)],
719            _where_clause: Option<wasm_dbms_api::prelude::Filter>,
720        ) -> Self {
721            Self
722        }
723
724        fn update_values(&self) -> Vec<(ColumnDef, Value)> {
725            vec![]
726        }
727
728        fn where_clause(&self) -> Option<wasm_dbms_api::prelude::Filter> {
729            None
730        }
731    }
732
733    impl TableSchema for Uint8AutoincTable {
734        type Record = Uint8AutoincTableRecord;
735        type Insert = Uint8AutoincTableInsert;
736        type Update = Uint8AutoincTableUpdate;
737        type ForeignFetcher = NoForeignFetcher;
738
739        fn table_name() -> &'static str {
740            "uint8_autoinc"
741        }
742
743        fn columns() -> &'static [ColumnDef] {
744            &[ColumnDef {
745                name: "counter",
746                data_type: DataTypeKind::Uint8,
747                auto_increment: true,
748                nullable: false,
749                primary_key: true,
750                unique: true,
751                foreign_key: None,
752                default: None,
753                renamed_from: &[],
754            }]
755        }
756
757        fn primary_key() -> &'static str {
758            "counter"
759        }
760
761        fn indexes() -> &'static [IndexDef] {
762            &[IndexDef(&["counter"])]
763        }
764
765        fn to_values(self) -> Vec<(ColumnDef, Value)> {
766            vec![]
767        }
768
769        fn sanitizer(
770            _column_name: &'static str,
771        ) -> Option<Box<dyn wasm_dbms_api::prelude::Sanitize>> {
772            None
773        }
774
775        fn validator(
776            _column_name: &'static str,
777        ) -> Option<Box<dyn wasm_dbms_api::prelude::Validate>> {
778            None
779        }
780    }
781
782    #[test]
783    fn test_zero_values_for_all_integer_types() {
784        assert_eq!(
785            AutoincrementLedger::zero(DataTypeKind::Int8),
786            Value::Int8(0.into())
787        );
788        assert_eq!(
789            AutoincrementLedger::zero(DataTypeKind::Int16),
790            Value::Int16(0.into())
791        );
792        assert_eq!(
793            AutoincrementLedger::zero(DataTypeKind::Int32),
794            Value::Int32(0.into())
795        );
796        assert_eq!(
797            AutoincrementLedger::zero(DataTypeKind::Int64),
798            Value::Int64(0.into())
799        );
800        assert_eq!(
801            AutoincrementLedger::zero(DataTypeKind::Uint8),
802            Value::Uint8(0.into())
803        );
804        assert_eq!(
805            AutoincrementLedger::zero(DataTypeKind::Uint16),
806            Value::Uint16(0.into())
807        );
808        assert_eq!(
809            AutoincrementLedger::zero(DataTypeKind::Uint32),
810            Value::Uint32(0.into())
811        );
812        assert_eq!(
813            AutoincrementLedger::zero(DataTypeKind::Uint64),
814            Value::Uint64(0.into())
815        );
816    }
817
818    #[test]
819    #[should_panic(expected = "unsupported autoincrement type")]
820    fn test_zero_panics_on_unsupported_type() {
821        let _ = AutoincrementLedger::zero(DataTypeKind::Text);
822    }
823}