1use serde::{Deserialize, Serialize};
2use std::sync::Arc;
3
4use crate::constraint::TableConstraints;
5use crate::error::{MongrelError, Result};
6use crate::memtable::Value;
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(tag = "kind", rename_all = "lowercase")]
12pub enum TypeId {
13 Bool,
14 Int8,
15 Int16,
16 Int32,
17 Int64,
18 UInt8,
19 UInt16,
20 UInt32,
21 UInt64,
22 Float32,
23 Float64,
24 TimestampNanos,
25 Date32,
26 Date64,
29 Time64,
31 Interval,
34 Uuid,
36 Json,
39 Array {
44 element_type: u8,
45 },
46 Bytes,
48 Embedding {
50 dim: u32,
51 },
52 Decimal128 {
55 precision: u8,
56 scale: i8,
57 },
58 Enum {
64 variants: Arc<[String]>,
65 },
66}
67
68impl TypeId {
69 pub fn fixed_size(&self) -> Option<usize> {
71 match self {
72 TypeId::Bool => Some(1),
73 TypeId::Int8 | TypeId::UInt8 => Some(1),
74 TypeId::Int16 | TypeId::UInt16 => Some(2),
75 TypeId::Int32 | TypeId::UInt32 | TypeId::Float32 | TypeId::Date32 => Some(4),
76 TypeId::Int64
77 | TypeId::UInt64
78 | TypeId::Float64
79 | TypeId::TimestampNanos
80 | TypeId::Date64
81 | TypeId::Time64 => Some(8),
82 TypeId::Bytes | TypeId::Embedding { .. } | TypeId::Enum { .. } => None,
83 TypeId::Decimal128 { .. } => Some(16),
84 TypeId::Uuid => Some(16),
85 TypeId::Json | TypeId::Array { .. } => None,
86 TypeId::Interval => Some(20), }
88 }
89}
90
91#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
93pub struct ColumnFlags {
94 bits: u32,
95}
96
97impl ColumnFlags {
98 pub const NULLABLE: u32 = 1 << 0;
99 pub const PRIMARY_KEY: u32 = 1 << 1;
100 pub const ENCRYPTED: u32 = 1 << 2;
101 pub const ENCRYPTED_INDEXABLE: u32 = 1 << 3;
104 pub const EMBEDDING_BINARY_QUANTIZED: u32 = 1 << 4;
106 pub const AUTO_INCREMENT: u32 = 1 << 5;
113
114 #[inline]
115 pub const fn empty() -> Self {
116 Self { bits: 0 }
117 }
118
119 #[inline]
120 pub const fn with(mut self, flag: u32) -> Self {
121 self.bits |= flag;
122 self
123 }
124
125 #[inline]
126 pub const fn without(mut self, flag: u32) -> Self {
127 self.bits &= !flag;
128 self
129 }
130
131 #[inline]
132 pub const fn contains(&self, flag: u32) -> bool {
133 self.bits & flag != 0
134 }
135
136 #[inline]
137 pub const fn bits(&self) -> u32 {
138 self.bits
139 }
140}
141
142#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
147pub enum DefaultExpr {
148 Static(Value),
150 Now,
153 Uuid,
155}
156
157#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
159pub struct ColumnDef {
160 pub id: u16,
161 pub name: String,
162 pub ty: TypeId,
163 pub flags: ColumnFlags,
164 #[serde(default, skip_serializing_if = "Option::is_none")]
168 pub default_value: Option<DefaultExpr>,
169}
170
171#[derive(Debug, Clone, Default, Serialize, Deserialize)]
173pub struct AlterColumn {
174 pub name: Option<String>,
175 pub ty: Option<TypeId>,
176 pub flags: Option<ColumnFlags>,
177 #[serde(default, skip_serializing_if = "Option::is_none")]
180 pub default_value: Option<Option<DefaultExpr>>,
181}
182
183impl AlterColumn {
184 pub fn rename(name: impl Into<String>) -> Self {
185 Self {
186 name: Some(name.into()),
187 ty: None,
188 flags: None,
189 default_value: None,
190 }
191 }
192
193 pub fn set_type(ty: TypeId) -> Self {
194 Self {
195 name: None,
196 ty: Some(ty),
197 flags: None,
198 default_value: None,
199 }
200 }
201
202 pub fn set_flags(flags: ColumnFlags) -> Self {
203 Self {
204 name: None,
205 ty: None,
206 flags: Some(flags),
207 default_value: None,
208 }
209 }
210
211 pub fn set_default(expr: DefaultExpr) -> Self {
212 Self {
213 name: None,
214 ty: None,
215 flags: None,
216 default_value: Some(Some(expr)),
217 }
218 }
219
220 pub fn drop_default() -> Self {
221 Self {
222 name: None,
223 ty: None,
224 flags: None,
225 default_value: Some(None),
226 }
227 }
228}
229
230#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
233pub enum IndexKind {
234 Bitmap,
236 FmIndex,
238 Ann,
240 LearnedRange,
242 MinHash,
244 Sparse,
246}
247
248#[derive(Debug, Clone, Default, Serialize, Deserialize)]
249pub struct IndexOptions {
250 #[serde(default, skip_serializing_if = "Option::is_none")]
251 pub ann: Option<AnnOptions>,
252 #[serde(default, skip_serializing_if = "Option::is_none")]
253 pub minhash: Option<MinHashOptions>,
254 #[serde(default, skip_serializing_if = "Option::is_none")]
255 pub learned_range: Option<LearnedRangeOptions>,
256}
257
258#[derive(Debug, Clone, Serialize, Deserialize)]
259pub struct AnnOptions {
260 #[serde(default = "default_ann_m")]
261 pub m: usize,
262 #[serde(default = "default_ann_ef_construction")]
263 pub ef_construction: usize,
264 #[serde(default = "default_ann_ef_search")]
265 pub ef_search: usize,
266 #[serde(default)]
267 pub quantization: AnnQuantization,
268}
269
270impl Default for AnnOptions {
271 fn default() -> Self {
272 Self {
273 m: default_ann_m(),
274 ef_construction: default_ann_ef_construction(),
275 ef_search: default_ann_ef_search(),
276 quantization: AnnQuantization::BinarySign,
277 }
278 }
279}
280
281#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
282#[serde(rename_all = "snake_case")]
283pub enum AnnQuantization {
284 #[default]
285 BinarySign,
286}
287
288const fn default_ann_m() -> usize {
289 16
290}
291const fn default_ann_ef_construction() -> usize {
292 64
293}
294const fn default_ann_ef_search() -> usize {
295 64
296}
297
298#[derive(Debug, Clone, Serialize, Deserialize)]
299pub struct MinHashOptions {
300 #[serde(default = "default_minhash_permutations")]
301 pub permutations: usize,
302 #[serde(default = "default_minhash_bands")]
303 pub bands: usize,
304}
305
306impl Default for MinHashOptions {
307 fn default() -> Self {
308 Self {
309 permutations: default_minhash_permutations(),
310 bands: default_minhash_bands(),
311 }
312 }
313}
314
315const fn default_minhash_permutations() -> usize {
316 128
317}
318const fn default_minhash_bands() -> usize {
319 32
320}
321
322#[derive(Debug, Clone, Serialize, Deserialize)]
323pub struct LearnedRangeOptions {
324 #[serde(default = "default_learned_range_epsilon")]
325 pub epsilon: usize,
326}
327
328impl Default for LearnedRangeOptions {
329 fn default() -> Self {
330 Self {
331 epsilon: default_learned_range_epsilon(),
332 }
333 }
334}
335
336const fn default_learned_range_epsilon() -> usize {
337 16
338}
339
340#[derive(Debug, Clone, Serialize, Deserialize)]
341pub struct IndexDef {
342 pub name: String,
343 pub column_id: u16,
344 pub kind: IndexKind,
345 #[serde(default, skip_serializing_if = "Option::is_none")]
349 pub predicate: Option<String>,
350 #[serde(default)]
351 pub options: IndexOptions,
352}
353
354impl IndexDef {
355 pub fn validate_options(&self) -> Result<()> {
356 if self.options.ann.is_some() && self.kind != IndexKind::Ann
357 || self.options.minhash.is_some() && self.kind != IndexKind::MinHash
358 || self.options.learned_range.is_some() && self.kind != IndexKind::LearnedRange
359 {
360 return Err(MongrelError::Schema(format!(
361 "index {} has options for a different index kind",
362 self.name
363 )));
364 }
365 if let Some(options) = &self.options.ann {
366 if options.m == 0
367 || options.ef_construction < options.m
368 || options.ef_search == 0
369 || options.m > 256
370 || options.ef_construction > 65_536
371 || options.ef_search > 65_536
372 {
373 return Err(MongrelError::Schema(format!(
374 "invalid ANN options for index {}",
375 self.name
376 )));
377 }
378 }
379 if let Some(options) = &self.options.minhash {
380 if options.permutations == 0
381 || options.bands == 0
382 || options.permutations % options.bands != 0
383 || options.permutations > 4096
384 || options.bands > 1024
385 {
386 return Err(MongrelError::Schema(format!(
387 "invalid MinHash options for index {}",
388 self.name
389 )));
390 }
391 }
392 if self
393 .options
394 .learned_range
395 .as_ref()
396 .is_some_and(|options| options.epsilon == 0 || options.epsilon > 1_048_576)
397 {
398 return Err(MongrelError::Schema(format!(
399 "invalid learned-range options for index {}",
400 self.name
401 )));
402 }
403 Ok(())
404 }
405}
406
407#[derive(Debug, Clone, Default, Serialize, Deserialize)]
408pub struct Schema {
409 pub schema_id: u64,
410 pub columns: Vec<ColumnDef>,
411 pub indexes: Vec<IndexDef>,
412 #[serde(default)]
417 pub colocation: Vec<Vec<u16>>,
418 #[serde(default)]
423 pub constraints: TableConstraints,
424 #[serde(default)]
427 pub clustered: bool,
428}
429
430impl Schema {
431 pub const MAX_EMBEDDING_DIM: u32 = 65_536;
432
433 pub fn column(&self, name: &str) -> Option<&ColumnDef> {
434 self.columns.iter().find(|c| c.name == name)
435 }
436
437 pub fn primary_key(&self) -> Option<&ColumnDef> {
438 self.columns
439 .iter()
440 .find(|c| c.flags.contains(ColumnFlags::PRIMARY_KEY))
441 }
442
443 pub fn validate_ai(&self) -> Result<()> {
445 for column in &self.columns {
446 if let TypeId::Embedding { dim } = column.ty {
447 if dim == 0 || dim > Self::MAX_EMBEDDING_DIM {
448 return Err(MongrelError::Schema(format!(
449 "embedding column '{}' dimension must be between 1 and {}",
450 column.name,
451 Self::MAX_EMBEDDING_DIM
452 )));
453 }
454 }
455 }
456 for index in &self.indexes {
457 let column = self
458 .columns
459 .iter()
460 .find(|column| column.id == index.column_id)
461 .ok_or_else(|| {
462 MongrelError::Schema(format!(
463 "index '{}' references unknown column {}",
464 index.name, index.column_id
465 ))
466 })?;
467 let expected = match index.kind {
468 IndexKind::Ann => Some("Embedding"),
469 IndexKind::Sparse | IndexKind::MinHash | IndexKind::FmIndex => Some("Bytes"),
470 _ => None,
471 };
472 if let Some(expected) = expected {
473 let valid = match index.kind {
474 IndexKind::Ann => matches!(column.ty, TypeId::Embedding { .. }),
475 _ => column.ty == TypeId::Bytes,
476 };
477 if !valid {
478 return Err(MongrelError::Schema(format!(
479 "{:?} index '{}' requires a {expected} column",
480 index.kind, index.name
481 )));
482 }
483 if self
484 .indexes
485 .iter()
486 .filter(|other| {
487 other.column_id == index.column_id
488 && matches!(
489 other.kind,
490 IndexKind::Ann
491 | IndexKind::Sparse
492 | IndexKind::MinHash
493 | IndexKind::FmIndex
494 )
495 })
496 .count()
497 > 1
498 {
499 return Err(MongrelError::Schema(format!(
500 "column '{}' may have only one ANN, Sparse, MinHash, or FM representation index",
501 column.name
502 )));
503 }
504 }
505 }
506 Ok(())
507 }
508
509 pub fn validate_values(&self, columns: &[(u16, Value)]) -> Result<()> {
510 self.validate_not_null(columns)?;
511 for (column_id, value) in columns {
512 let Some(column) = self.columns.iter().find(|column| column.id == *column_id) else {
513 return Err(MongrelError::ColumnNotFound(column_id.to_string()));
514 };
515 let representation = self
516 .indexes
517 .iter()
518 .find(|index| {
519 index.column_id == *column_id
520 && matches!(
521 index.kind,
522 IndexKind::Sparse | IndexKind::MinHash | IndexKind::FmIndex
523 )
524 })
525 .map(|index| index.kind);
526 match representation {
527 Some(IndexKind::Sparse) => match value {
528 Value::Null if column.flags.contains(ColumnFlags::NULLABLE) => {}
529 Value::Bytes(bytes) => {
530 let terms: Vec<(u32, f32)> = bincode::deserialize(bytes).map_err(|_| {
531 MongrelError::InvalidArgument(format!(
532 "sparse column '{}' requires an encoded sparse vector",
533 column.name
534 ))
535 })?;
536 if terms.is_empty() || terms.iter().any(|(_, weight)| !weight.is_finite()) {
537 return Err(MongrelError::InvalidArgument(format!(
538 "sparse column '{}' must be non-empty with finite weights",
539 column.name
540 )));
541 }
542 }
543 _ => {
544 return Err(MongrelError::InvalidArgument(format!(
545 "sparse column '{}' requires bytes or NULL",
546 column.name
547 )));
548 }
549 },
550 Some(IndexKind::MinHash) => match value {
551 Value::Null if column.flags.contains(ColumnFlags::NULLABLE) => {}
552 Value::Bytes(bytes) => {
553 let members: serde_json::Value =
554 serde_json::from_slice(bytes).map_err(|_| {
555 MongrelError::InvalidArgument(format!(
556 "MinHash column '{}' requires a JSON array",
557 column.name
558 ))
559 })?;
560 let serde_json::Value::Array(members) = members else {
561 return Err(MongrelError::InvalidArgument(format!(
562 "MinHash column '{}' requires a JSON array",
563 column.name
564 )));
565 };
566 if members.iter().any(|member| {
567 !matches!(
568 member,
569 serde_json::Value::String(_)
570 | serde_json::Value::Number(_)
571 | serde_json::Value::Bool(_)
572 )
573 }) {
574 return Err(MongrelError::InvalidArgument(format!(
575 "MinHash column '{}' members must be scalar",
576 column.name
577 )));
578 }
579 }
580 _ => {
581 return Err(MongrelError::InvalidArgument(format!(
582 "MinHash column '{}' requires bytes or NULL",
583 column.name
584 )));
585 }
586 },
587 Some(IndexKind::FmIndex) => match value {
588 Value::Null if column.flags.contains(ColumnFlags::NULLABLE) => {}
589 Value::Bytes(_) => {}
590 _ => {
591 return Err(MongrelError::InvalidArgument(format!(
592 "FM text column '{}' requires bytes or NULL",
593 column.name
594 )));
595 }
596 },
597 _ => {}
598 }
599 if let TypeId::Embedding { dim } = &column.ty {
600 let Value::Embedding(values) = value else {
601 if matches!(value, Value::Null) {
602 continue;
603 }
604 return Err(MongrelError::InvalidArgument(format!(
605 "embedding column '{}' requires an embedding value",
606 column.name
607 )));
608 };
609 if values.len() != *dim as usize {
610 return Err(MongrelError::InvalidArgument(format!(
611 "embedding column '{}' dimension must be {}, got {}",
612 column.name,
613 dim,
614 values.len()
615 )));
616 }
617 if values.iter().any(|value| !value.is_finite()) {
618 return Err(MongrelError::InvalidArgument(format!(
619 "embedding column '{}' values must be finite",
620 column.name
621 )));
622 }
623 }
624 }
625 Ok(())
626 }
627
628 pub fn validate_not_null(&self, columns: &[(u16, Value)]) -> Result<()> {
633 let at = |id: u16| columns.iter().find(|(c, _)| *c == id).map(|(_, v)| v);
636 for col in &self.columns {
637 if !col.flags.contains(ColumnFlags::NULLABLE) {
638 if col.flags.contains(ColumnFlags::AUTO_INCREMENT) {
641 match at(col.id) {
642 None | Some(Value::Null) => continue,
643 Some(_) => {}
644 }
645 }
646 match at(col.id) {
647 None => {
648 return Err(MongrelError::InvalidArgument(format!(
649 "column '{}' ({}) is NOT NULL but was omitted",
650 col.name, col.id
651 )));
652 }
653 Some(Value::Null) => {
654 return Err(MongrelError::InvalidArgument(format!(
655 "column '{}' ({}) is NOT NULL but got NULL",
656 col.name, col.id
657 )));
658 }
659 Some(_) => {}
660 }
661 }
662 if let TypeId::Enum { variants } = &col.ty {
663 match at(col.id) {
664 None | Some(Value::Null) => {}
665 Some(Value::Bytes(value))
666 if variants
667 .iter()
668 .any(|variant| variant.as_bytes() == value.as_slice()) => {}
669 Some(Value::Bytes(value)) => {
670 return Err(MongrelError::InvalidArgument(format!(
671 "column '{}' ({}) enum value {:?} is not one of {:?}",
672 col.name,
673 col.id,
674 String::from_utf8_lossy(value),
675 variants
676 )));
677 }
678 Some(value) => {
679 return Err(MongrelError::InvalidArgument(format!(
680 "column '{}' ({}) enum requires a string/bytes value, got {value:?}",
681 col.name, col.id
682 )));
683 }
684 }
685 }
686 }
687 Ok(())
688 }
689
690 pub fn validate_auto_increment(&self) -> Result<()> {
694 let mut seen: Option<&ColumnDef> = None;
695 for col in &self.columns {
696 if !col.flags.contains(ColumnFlags::AUTO_INCREMENT) {
697 continue;
698 }
699 if let Some(prev) = seen {
700 return Err(MongrelError::Schema(format!(
701 "AUTO_INCREMENT may be set on at most one column; '{}' and '{}' both carry it",
702 prev.name, col.name
703 )));
704 }
705 if col.ty != TypeId::Int64 {
706 return Err(MongrelError::Schema(format!(
707 "AUTO_INCREMENT column '{}' must be Int64, is {:?}",
708 col.name, col.ty
709 )));
710 }
711 if !col.flags.contains(ColumnFlags::PRIMARY_KEY) {
712 return Err(MongrelError::Schema(format!(
713 "AUTO_INCREMENT column '{}' must also be the primary key",
714 col.name
715 )));
716 }
717 if col.flags.contains(ColumnFlags::NULLABLE) {
718 return Err(MongrelError::Schema(format!(
719 "AUTO_INCREMENT column '{}' must not be nullable",
720 col.name
721 )));
722 }
723 seen = Some(col);
724 }
725 Ok(())
726 }
727
728 pub fn auto_increment_column(&self) -> Option<&ColumnDef> {
730 self.columns
731 .iter()
732 .find(|c| c.flags.contains(ColumnFlags::AUTO_INCREMENT))
733 }
734
735 pub fn validate_defaults(&self) -> Result<()> {
739 for col in &self.columns {
740 let Some(expr) = &col.default_value else {
741 continue;
742 };
743 match expr {
744 DefaultExpr::Static(v) => {
745 if !value_matches_type(v, col.ty.clone()) {
746 return Err(MongrelError::Schema(format!(
747 "DEFAULT value for column '{}' ({:?}) does not match type {:?}",
748 col.name, v, col.ty
749 )));
750 }
751 }
752 DefaultExpr::Now => {
753 if !matches!(
754 col.ty,
755 TypeId::Bytes | TypeId::TimestampNanos | TypeId::Date64
756 ) {
757 return Err(MongrelError::Schema(format!(
758 "DEFAULT NOW() on column '{}' requires Bytes/TimestampNanos/Date64, is {:?}",
759 col.name, col.ty
760 )));
761 }
762 }
763 DefaultExpr::Uuid => {
764 if !matches!(col.ty, TypeId::Uuid | TypeId::Bytes) {
765 return Err(MongrelError::Schema(format!(
766 "DEFAULT UUID() on column '{}' requires Uuid/Bytes, is {:?}",
767 col.name, col.ty
768 )));
769 }
770 }
771 }
772 }
773 Ok(())
774 }
775}
776
777pub(crate) fn value_matches_type(v: &Value, ty: TypeId) -> bool {
781 matches!(
782 (v, ty),
783 (Value::Null, _)
784 | (Value::Bool(_), TypeId::Bool)
785 | (
786 Value::Int64(_),
787 TypeId::Int8 | TypeId::Int16 | TypeId::Int32 | TypeId::Int64
788 )
789 | (Value::Float64(_), TypeId::Float32 | TypeId::Float64)
790 | (
791 Value::Bytes(_),
792 TypeId::Bytes
793 | TypeId::Json
794 | TypeId::Uuid
795 | TypeId::Date64
796 | TypeId::Time64
797 | TypeId::Enum { .. }
798 )
799 | (
800 Value::Int64(_),
801 TypeId::TimestampNanos | TypeId::Date32 | TypeId::Date64 | TypeId::Time64
802 )
803 | (Value::Uuid(_), TypeId::Uuid)
804 | (Value::Decimal(_), TypeId::Decimal128 { .. })
805 | (Value::Json(_), TypeId::Json)
806 | (Value::Embedding(_), TypeId::Embedding { .. })
807 | (Value::Interval { .. }, TypeId::Interval)
808 )
809}
810
811#[cfg(test)]
812mod tests {
813 use super::*;
814
815 #[test]
816 fn index_options_preserve_defaults_and_validate_bounds() {
817 let defaults = IndexDef {
818 name: "ann".into(),
819 column_id: 1,
820 kind: IndexKind::Ann,
821 predicate: None,
822 options: IndexOptions::default(),
823 };
824 assert!(defaults.validate_options().is_ok());
825 let json = serde_json::to_string(&defaults).unwrap();
826 let restored: IndexDef = serde_json::from_str(&json).unwrap();
827 assert!(restored.options.ann.is_none());
828 let legacy: IndexDef = serde_json::from_value(serde_json::json!({
829 "name": "legacy_ann",
830 "column_id": 1,
831 "kind": "Ann"
832 }))
833 .unwrap();
834 assert!(legacy.options.ann.is_none());
835
836 let invalid = IndexDef {
837 name: "minhash".into(),
838 column_id: 2,
839 kind: IndexKind::MinHash,
840 predicate: None,
841 options: IndexOptions {
842 minhash: Some(MinHashOptions {
843 permutations: 127,
844 bands: 32,
845 }),
846 ..Default::default()
847 },
848 };
849 assert!(invalid.validate_options().is_err());
850 }
851
852 #[test]
853 fn flag_composition() {
854 let f = ColumnFlags::empty()
855 .with(ColumnFlags::PRIMARY_KEY)
856 .with(ColumnFlags::ENCRYPTED_INDEXABLE);
857 assert!(f.contains(ColumnFlags::PRIMARY_KEY));
858 assert!(f.contains(ColumnFlags::ENCRYPTED_INDEXABLE));
859 assert!(!f.contains(ColumnFlags::ENCRYPTED));
860 }
861
862 #[test]
863 fn fixed_size() {
864 assert_eq!(TypeId::Int64.fixed_size(), Some(8));
865 assert_eq!(TypeId::Bytes.fixed_size(), None);
866 assert_eq!(TypeId::Embedding { dim: 768 }.fixed_size(), None);
867 }
868
869 fn col(id: u16, name: &str, ty: TypeId, flags: ColumnFlags) -> ColumnDef {
870 ColumnDef {
871 id,
872 name: name.into(),
873 ty,
874 flags,
875 default_value: None,
876 }
877 }
878
879 #[test]
880 fn auto_increment_validation_accepts_int64_pk() {
881 let s = Schema {
882 schema_id: 1,
883 columns: vec![col(
884 0,
885 "id",
886 TypeId::Int64,
887 ColumnFlags::empty().with(ColumnFlags::PRIMARY_KEY | ColumnFlags::AUTO_INCREMENT),
888 )],
889 indexes: vec![],
890 colocation: vec![],
891 constraints: Default::default(),
892 clustered: false,
893 };
894 assert!(s.validate_auto_increment().is_ok());
895 assert_eq!(s.auto_increment_column().unwrap().id, 0);
896 }
897
898 #[test]
899 fn auto_increment_validation_rejects_non_pk() {
900 let s = Schema {
901 schema_id: 1,
902 columns: vec![
903 col(
904 0,
905 "id",
906 TypeId::Int64,
907 ColumnFlags::empty().with(ColumnFlags::PRIMARY_KEY),
908 ),
909 col(
910 1,
911 "seq",
912 TypeId::Int64,
913 ColumnFlags::empty().with(ColumnFlags::AUTO_INCREMENT),
914 ),
915 ],
916 indexes: vec![],
917 colocation: vec![],
918 constraints: Default::default(),
919 clustered: false,
920 };
921 assert!(s.validate_auto_increment().is_err());
922 }
923
924 #[test]
925 fn auto_increment_validation_rejects_non_int64() {
926 let s = Schema {
927 schema_id: 1,
928 columns: vec![col(
929 0,
930 "id",
931 TypeId::Bytes,
932 ColumnFlags::empty().with(ColumnFlags::PRIMARY_KEY | ColumnFlags::AUTO_INCREMENT),
933 )],
934 indexes: vec![],
935 colocation: vec![],
936 constraints: Default::default(),
937 clustered: false,
938 };
939 assert!(s.validate_auto_increment().is_err());
940 }
941
942 #[test]
943 fn auto_increment_validation_rejects_two() {
944 let s = Schema {
945 schema_id: 1,
946 columns: vec![
947 col(
948 0,
949 "id",
950 TypeId::Int64,
951 ColumnFlags::empty()
952 .with(ColumnFlags::PRIMARY_KEY | ColumnFlags::AUTO_INCREMENT),
953 ),
954 col(
955 1,
956 "id2",
957 TypeId::Int64,
958 ColumnFlags::empty().with(ColumnFlags::AUTO_INCREMENT),
959 ),
960 ],
961 indexes: vec![],
962 colocation: vec![],
963 constraints: Default::default(),
964 clustered: false,
965 };
966 assert!(s.validate_auto_increment().is_err());
967 }
968
969 #[test]
970 fn auto_increment_exempt_from_not_null_when_omitted() {
971 let s = Schema {
972 schema_id: 1,
973 columns: vec![
974 col(
975 0,
976 "id",
977 TypeId::Int64,
978 ColumnFlags::empty()
979 .with(ColumnFlags::PRIMARY_KEY | ColumnFlags::AUTO_INCREMENT),
980 ),
981 col(1, "name", TypeId::Bytes, ColumnFlags::empty()),
982 ],
983 indexes: vec![],
984 colocation: vec![],
985 constraints: Default::default(),
986 clustered: false,
987 };
988 let cols = vec![(1u16, Value::Bytes(b"x".to_vec()))];
990 assert!(s.validate_not_null(&cols).is_ok());
991 }
992
993 #[test]
994 fn enum_membership_is_enforced_for_nullable_and_required_columns() {
995 let variants: std::sync::Arc<[String]> =
996 vec!["user".to_string(), "admin".to_string()].into();
997 let required = Schema {
998 columns: vec![col(
999 1,
1000 "role",
1001 TypeId::Enum {
1002 variants: variants.clone(),
1003 },
1004 ColumnFlags::empty(),
1005 )],
1006 ..Schema::default()
1007 };
1008 assert!(required
1009 .validate_not_null(&[(1, Value::Bytes(b"user".to_vec()))])
1010 .is_ok());
1011 assert!(required
1012 .validate_not_null(&[(1, Value::Bytes(b"owner".to_vec()))])
1013 .is_err());
1014
1015 let nullable = Schema {
1016 columns: vec![col(
1017 1,
1018 "role",
1019 TypeId::Enum { variants },
1020 ColumnFlags::empty().with(ColumnFlags::NULLABLE),
1021 )],
1022 ..Schema::default()
1023 };
1024 assert!(nullable.validate_not_null(&[(1, Value::Null)]).is_ok());
1025 assert!(nullable
1026 .validate_not_null(&[(1, Value::Bytes(b"owner".to_vec()))])
1027 .is_err());
1028 }
1029
1030 fn col_with_default(
1031 id: u16,
1032 name: &str,
1033 ty: TypeId,
1034 flags: ColumnFlags,
1035 dv: DefaultExpr,
1036 ) -> ColumnDef {
1037 ColumnDef {
1038 id,
1039 name: name.into(),
1040 ty,
1041 flags,
1042 default_value: Some(dv),
1043 }
1044 }
1045
1046 #[test]
1047 fn validate_defaults_accepts_matching_static() {
1048 let s = Schema {
1049 schema_id: 1,
1050 columns: vec![col_with_default(
1051 0,
1052 "active",
1053 TypeId::Bool,
1054 ColumnFlags::empty(),
1055 DefaultExpr::Static(Value::Bool(true)),
1056 )],
1057 indexes: vec![],
1058 colocation: vec![],
1059 constraints: Default::default(),
1060 clustered: false,
1061 };
1062 assert!(s.validate_defaults().is_ok());
1063 }
1064
1065 #[test]
1066 fn validate_defaults_rejects_mismatched_static() {
1067 let s = Schema {
1068 schema_id: 1,
1069 columns: vec![col_with_default(
1070 0,
1071 "count",
1072 TypeId::Int64,
1073 ColumnFlags::empty(),
1074 DefaultExpr::Static(Value::Bytes(b"oops".to_vec())),
1075 )],
1076 indexes: vec![],
1077 colocation: vec![],
1078 constraints: Default::default(),
1079 clustered: false,
1080 };
1081 assert!(s.validate_defaults().is_err());
1082 }
1083
1084 #[test]
1085 fn validate_defaults_now_requires_temporal_or_bytes() {
1086 let ok = Schema {
1087 schema_id: 1,
1088 columns: vec![col_with_default(
1089 0,
1090 "ts",
1091 TypeId::Bytes,
1092 ColumnFlags::empty(),
1093 DefaultExpr::Now,
1094 )],
1095 indexes: vec![],
1096 colocation: vec![],
1097 constraints: Default::default(),
1098 clustered: false,
1099 };
1100 assert!(ok.validate_defaults().is_ok());
1101
1102 let bad = Schema {
1103 schema_id: 1,
1104 columns: vec![col_with_default(
1105 0,
1106 "ts",
1107 TypeId::Int64,
1108 ColumnFlags::empty(),
1109 DefaultExpr::Now,
1110 )],
1111 indexes: vec![],
1112 colocation: vec![],
1113 constraints: Default::default(),
1114 clustered: false,
1115 };
1116 assert!(bad.validate_defaults().is_err());
1117 }
1118
1119 #[test]
1120 fn validate_defaults_uuid_requires_uuid_or_bytes() {
1121 let ok = Schema {
1122 schema_id: 1,
1123 columns: vec![col_with_default(
1124 0,
1125 "id",
1126 TypeId::Uuid,
1127 ColumnFlags::empty(),
1128 DefaultExpr::Uuid,
1129 )],
1130 indexes: vec![],
1131 colocation: vec![],
1132 constraints: Default::default(),
1133 clustered: false,
1134 };
1135 assert!(ok.validate_defaults().is_ok());
1136
1137 let bad = Schema {
1138 schema_id: 1,
1139 columns: vec![col_with_default(
1140 0,
1141 "id",
1142 TypeId::Bool,
1143 ColumnFlags::empty(),
1144 DefaultExpr::Uuid,
1145 )],
1146 indexes: vec![],
1147 colocation: vec![],
1148 constraints: Default::default(),
1149 clustered: false,
1150 };
1151 assert!(bad.validate_defaults().is_err());
1152 }
1153
1154 #[test]
1155 fn serde_roundtrip_column_def_with_default() {
1156 let c = col_with_default(
1157 0,
1158 "x",
1159 TypeId::Bytes,
1160 ColumnFlags::empty(),
1161 DefaultExpr::Static(Value::Bytes(b"hello".to_vec())),
1162 );
1163 let json = serde_json::to_string(&c).unwrap();
1164 let de: ColumnDef = serde_json::from_str(&json).unwrap();
1165 assert_eq!(c, de);
1166 let old_json = r#"{"id":0,"name":"y","ty":{"kind":"bytes"},"flags":{"bits":0}}"#;
1168 let old: ColumnDef = serde_json::from_str(old_json).unwrap();
1169 assert!(old.default_value.is_none());
1170 }
1171}