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 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 #[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 #[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 #[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 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 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 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 let _ = ledger.next("id", &mut mm).expect("next failed");
554
555 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 for _ in 0..5 {
588 let _ = ledger.next("id", &mut mm).expect("next failed");
589 }
590
591 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 let mut ledger =
606 AutoincrementLedger::init::<Uint8AutoincTable>(page, &mut mm).expect("failed to init");
607
608 for _ in 0..255 {
610 let _ = ledger.next("counter", &mut mm).expect("next failed");
611 }
612
613 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 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 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 #[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}