1mod 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#[derive(Debug)]
15pub struct AutoincrementLedger {
16 page: Page,
18 registry: AutoincrementRegistry,
20}
21
22impl AutoincrementLedger {
23 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 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 mm.write_at(page, 0, ®istry)?;
46
47 Ok(Self { page, registry })
48 }
49
50 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, ®istry)?;
77
78 Ok(Self { page, registry })
79 }
80
81 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 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 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 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 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 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 #[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 #[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 #[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 #[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 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 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 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 let _ = ledger.next("id", &mut mm).expect("next failed");
693
694 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 for _ in 0..5 {
727 let _ = ledger.next("id", &mut mm).expect("next failed");
728 }
729
730 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 let mut ledger =
745 AutoincrementLedger::init::<Uint8AutoincTable>(page, &mut mm).expect("failed to init");
746
747 for _ in 0..255 {
749 let _ = ledger.next("counter", &mut mm).expect("next failed");
750 }
751
752 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 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 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 #[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}