1mod 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#[derive(Debug)]
13pub struct AutoincrementLedger {
14 page: Page,
16 registry: AutoincrementRegistry,
18}
19
20impl AutoincrementLedger {
21 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 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 mm.write_at(page, 0, ®istry)?;
36
37 Ok(Self { page, registry })
38 }
39
40 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 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 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 #[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 #[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 #[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 #[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 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 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 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 let _ = ledger.next("id", &mut mm).expect("next failed");
546
547 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 for _ in 0..5 {
580 let _ = ledger.next("id", &mut mm).expect("next failed");
581 }
582
583 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 let mut ledger =
598 AutoincrementLedger::init::<Uint8AutoincTable>(page, &mut mm).expect("failed to init");
599
600 for _ in 0..255 {
602 let _ = ledger.next("counter", &mut mm).expect("next failed");
603 }
604
605 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 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 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 #[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}