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 if let TypeId::Embedding { dim } = &column.ty {
516 let Value::Embedding(values) = value else {
517 if matches!(value, Value::Null) {
518 continue;
519 }
520 return Err(MongrelError::InvalidArgument(format!(
521 "embedding column '{}' requires an embedding value",
522 column.name
523 )));
524 };
525 if values.len() != *dim as usize {
526 return Err(MongrelError::InvalidArgument(format!(
527 "embedding column '{}' dimension must be {}, got {}",
528 column.name,
529 dim,
530 values.len()
531 )));
532 }
533 if values.iter().any(|value| !value.is_finite()) {
534 return Err(MongrelError::InvalidArgument(format!(
535 "embedding column '{}' values must be finite",
536 column.name
537 )));
538 }
539 }
540 if let Value::Bytes(bytes) = value {
541 match self
542 .indexes
543 .iter()
544 .find(|index| {
545 index.column_id == *column_id
546 && matches!(index.kind, IndexKind::Sparse | IndexKind::MinHash)
547 })
548 .map(|index| index.kind)
549 {
550 Some(IndexKind::Sparse) => {
551 let terms: Vec<(u32, f32)> = bincode::deserialize(bytes).map_err(|_| {
552 MongrelError::InvalidArgument(format!(
553 "sparse column '{}' requires an encoded sparse vector",
554 column.name
555 ))
556 })?;
557 if terms.is_empty() || terms.iter().any(|(_, weight)| !weight.is_finite()) {
558 return Err(MongrelError::InvalidArgument(format!(
559 "sparse column '{}' must be non-empty with finite weights",
560 column.name
561 )));
562 }
563 }
564 Some(IndexKind::MinHash) => {
565 let members: serde_json::Value =
566 serde_json::from_slice(bytes).map_err(|_| {
567 MongrelError::InvalidArgument(format!(
568 "MinHash column '{}' requires a JSON array",
569 column.name
570 ))
571 })?;
572 let serde_json::Value::Array(members) = members else {
573 return Err(MongrelError::InvalidArgument(format!(
574 "MinHash column '{}' requires a JSON array",
575 column.name
576 )));
577 };
578 if members.iter().any(|member| {
579 !matches!(
580 member,
581 serde_json::Value::String(_)
582 | serde_json::Value::Number(_)
583 | serde_json::Value::Bool(_)
584 )
585 }) {
586 return Err(MongrelError::InvalidArgument(format!(
587 "MinHash column '{}' members must be scalar",
588 column.name
589 )));
590 }
591 }
592 _ => {}
593 }
594 }
595 }
596 Ok(())
597 }
598
599 pub fn validate_not_null(&self, columns: &[(u16, Value)]) -> Result<()> {
604 let at = |id: u16| columns.iter().find(|(c, _)| *c == id).map(|(_, v)| v);
607 for col in &self.columns {
608 if !col.flags.contains(ColumnFlags::NULLABLE) {
609 if col.flags.contains(ColumnFlags::AUTO_INCREMENT) {
612 match at(col.id) {
613 None | Some(Value::Null) => continue,
614 Some(_) => {}
615 }
616 }
617 match at(col.id) {
618 None => {
619 return Err(MongrelError::InvalidArgument(format!(
620 "column '{}' ({}) is NOT NULL but was omitted",
621 col.name, col.id
622 )));
623 }
624 Some(Value::Null) => {
625 return Err(MongrelError::InvalidArgument(format!(
626 "column '{}' ({}) is NOT NULL but got NULL",
627 col.name, col.id
628 )));
629 }
630 Some(_) => {}
631 }
632 }
633 if let TypeId::Enum { variants } = &col.ty {
634 match at(col.id) {
635 None | Some(Value::Null) => {}
636 Some(Value::Bytes(value))
637 if variants
638 .iter()
639 .any(|variant| variant.as_bytes() == value.as_slice()) => {}
640 Some(Value::Bytes(value)) => {
641 return Err(MongrelError::InvalidArgument(format!(
642 "column '{}' ({}) enum value {:?} is not one of {:?}",
643 col.name,
644 col.id,
645 String::from_utf8_lossy(value),
646 variants
647 )));
648 }
649 Some(value) => {
650 return Err(MongrelError::InvalidArgument(format!(
651 "column '{}' ({}) enum requires a string/bytes value, got {value:?}",
652 col.name, col.id
653 )));
654 }
655 }
656 }
657 }
658 Ok(())
659 }
660
661 pub fn validate_auto_increment(&self) -> Result<()> {
665 let mut seen: Option<&ColumnDef> = None;
666 for col in &self.columns {
667 if !col.flags.contains(ColumnFlags::AUTO_INCREMENT) {
668 continue;
669 }
670 if let Some(prev) = seen {
671 return Err(MongrelError::Schema(format!(
672 "AUTO_INCREMENT may be set on at most one column; '{}' and '{}' both carry it",
673 prev.name, col.name
674 )));
675 }
676 if col.ty != TypeId::Int64 {
677 return Err(MongrelError::Schema(format!(
678 "AUTO_INCREMENT column '{}' must be Int64, is {:?}",
679 col.name, col.ty
680 )));
681 }
682 if !col.flags.contains(ColumnFlags::PRIMARY_KEY) {
683 return Err(MongrelError::Schema(format!(
684 "AUTO_INCREMENT column '{}' must also be the primary key",
685 col.name
686 )));
687 }
688 if col.flags.contains(ColumnFlags::NULLABLE) {
689 return Err(MongrelError::Schema(format!(
690 "AUTO_INCREMENT column '{}' must not be nullable",
691 col.name
692 )));
693 }
694 seen = Some(col);
695 }
696 Ok(())
697 }
698
699 pub fn auto_increment_column(&self) -> Option<&ColumnDef> {
701 self.columns
702 .iter()
703 .find(|c| c.flags.contains(ColumnFlags::AUTO_INCREMENT))
704 }
705
706 pub fn validate_defaults(&self) -> Result<()> {
710 for col in &self.columns {
711 let Some(expr) = &col.default_value else {
712 continue;
713 };
714 match expr {
715 DefaultExpr::Static(v) => {
716 if !value_matches_type(v, col.ty.clone()) {
717 return Err(MongrelError::Schema(format!(
718 "DEFAULT value for column '{}' ({:?}) does not match type {:?}",
719 col.name, v, col.ty
720 )));
721 }
722 }
723 DefaultExpr::Now => {
724 if !matches!(
725 col.ty,
726 TypeId::Bytes | TypeId::TimestampNanos | TypeId::Date64
727 ) {
728 return Err(MongrelError::Schema(format!(
729 "DEFAULT NOW() on column '{}' requires Bytes/TimestampNanos/Date64, is {:?}",
730 col.name, col.ty
731 )));
732 }
733 }
734 DefaultExpr::Uuid => {
735 if !matches!(col.ty, TypeId::Uuid | TypeId::Bytes) {
736 return Err(MongrelError::Schema(format!(
737 "DEFAULT UUID() on column '{}' requires Uuid/Bytes, is {:?}",
738 col.name, col.ty
739 )));
740 }
741 }
742 }
743 }
744 Ok(())
745 }
746}
747
748pub(crate) fn value_matches_type(v: &Value, ty: TypeId) -> bool {
752 matches!(
753 (v, ty),
754 (Value::Null, _)
755 | (Value::Bool(_), TypeId::Bool)
756 | (
757 Value::Int64(_),
758 TypeId::Int8 | TypeId::Int16 | TypeId::Int32 | TypeId::Int64
759 )
760 | (Value::Float64(_), TypeId::Float32 | TypeId::Float64)
761 | (
762 Value::Bytes(_),
763 TypeId::Bytes
764 | TypeId::Json
765 | TypeId::Uuid
766 | TypeId::Date64
767 | TypeId::Time64
768 | TypeId::Enum { .. }
769 )
770 | (
771 Value::Int64(_),
772 TypeId::TimestampNanos | TypeId::Date32 | TypeId::Date64 | TypeId::Time64
773 )
774 | (Value::Uuid(_), TypeId::Uuid)
775 | (Value::Decimal(_), TypeId::Decimal128 { .. })
776 | (Value::Json(_), TypeId::Json)
777 | (Value::Embedding(_), TypeId::Embedding { .. })
778 | (Value::Interval { .. }, TypeId::Interval)
779 )
780}
781
782#[cfg(test)]
783mod tests {
784 use super::*;
785
786 #[test]
787 fn index_options_preserve_defaults_and_validate_bounds() {
788 let defaults = IndexDef {
789 name: "ann".into(),
790 column_id: 1,
791 kind: IndexKind::Ann,
792 predicate: None,
793 options: IndexOptions::default(),
794 };
795 assert!(defaults.validate_options().is_ok());
796 let json = serde_json::to_string(&defaults).unwrap();
797 let restored: IndexDef = serde_json::from_str(&json).unwrap();
798 assert!(restored.options.ann.is_none());
799 let legacy: IndexDef = serde_json::from_value(serde_json::json!({
800 "name": "legacy_ann",
801 "column_id": 1,
802 "kind": "Ann"
803 }))
804 .unwrap();
805 assert!(legacy.options.ann.is_none());
806
807 let invalid = IndexDef {
808 name: "minhash".into(),
809 column_id: 2,
810 kind: IndexKind::MinHash,
811 predicate: None,
812 options: IndexOptions {
813 minhash: Some(MinHashOptions {
814 permutations: 127,
815 bands: 32,
816 }),
817 ..Default::default()
818 },
819 };
820 assert!(invalid.validate_options().is_err());
821 }
822
823 #[test]
824 fn flag_composition() {
825 let f = ColumnFlags::empty()
826 .with(ColumnFlags::PRIMARY_KEY)
827 .with(ColumnFlags::ENCRYPTED_INDEXABLE);
828 assert!(f.contains(ColumnFlags::PRIMARY_KEY));
829 assert!(f.contains(ColumnFlags::ENCRYPTED_INDEXABLE));
830 assert!(!f.contains(ColumnFlags::ENCRYPTED));
831 }
832
833 #[test]
834 fn fixed_size() {
835 assert_eq!(TypeId::Int64.fixed_size(), Some(8));
836 assert_eq!(TypeId::Bytes.fixed_size(), None);
837 assert_eq!(TypeId::Embedding { dim: 768 }.fixed_size(), None);
838 }
839
840 fn col(id: u16, name: &str, ty: TypeId, flags: ColumnFlags) -> ColumnDef {
841 ColumnDef {
842 id,
843 name: name.into(),
844 ty,
845 flags,
846 default_value: None,
847 }
848 }
849
850 #[test]
851 fn auto_increment_validation_accepts_int64_pk() {
852 let s = Schema {
853 schema_id: 1,
854 columns: vec![col(
855 0,
856 "id",
857 TypeId::Int64,
858 ColumnFlags::empty().with(ColumnFlags::PRIMARY_KEY | ColumnFlags::AUTO_INCREMENT),
859 )],
860 indexes: vec![],
861 colocation: vec![],
862 constraints: Default::default(),
863 clustered: false,
864 };
865 assert!(s.validate_auto_increment().is_ok());
866 assert_eq!(s.auto_increment_column().unwrap().id, 0);
867 }
868
869 #[test]
870 fn auto_increment_validation_rejects_non_pk() {
871 let s = Schema {
872 schema_id: 1,
873 columns: vec![
874 col(
875 0,
876 "id",
877 TypeId::Int64,
878 ColumnFlags::empty().with(ColumnFlags::PRIMARY_KEY),
879 ),
880 col(
881 1,
882 "seq",
883 TypeId::Int64,
884 ColumnFlags::empty().with(ColumnFlags::AUTO_INCREMENT),
885 ),
886 ],
887 indexes: vec![],
888 colocation: vec![],
889 constraints: Default::default(),
890 clustered: false,
891 };
892 assert!(s.validate_auto_increment().is_err());
893 }
894
895 #[test]
896 fn auto_increment_validation_rejects_non_int64() {
897 let s = Schema {
898 schema_id: 1,
899 columns: vec![col(
900 0,
901 "id",
902 TypeId::Bytes,
903 ColumnFlags::empty().with(ColumnFlags::PRIMARY_KEY | ColumnFlags::AUTO_INCREMENT),
904 )],
905 indexes: vec![],
906 colocation: vec![],
907 constraints: Default::default(),
908 clustered: false,
909 };
910 assert!(s.validate_auto_increment().is_err());
911 }
912
913 #[test]
914 fn auto_increment_validation_rejects_two() {
915 let s = Schema {
916 schema_id: 1,
917 columns: vec![
918 col(
919 0,
920 "id",
921 TypeId::Int64,
922 ColumnFlags::empty()
923 .with(ColumnFlags::PRIMARY_KEY | ColumnFlags::AUTO_INCREMENT),
924 ),
925 col(
926 1,
927 "id2",
928 TypeId::Int64,
929 ColumnFlags::empty().with(ColumnFlags::AUTO_INCREMENT),
930 ),
931 ],
932 indexes: vec![],
933 colocation: vec![],
934 constraints: Default::default(),
935 clustered: false,
936 };
937 assert!(s.validate_auto_increment().is_err());
938 }
939
940 #[test]
941 fn auto_increment_exempt_from_not_null_when_omitted() {
942 let s = Schema {
943 schema_id: 1,
944 columns: vec![
945 col(
946 0,
947 "id",
948 TypeId::Int64,
949 ColumnFlags::empty()
950 .with(ColumnFlags::PRIMARY_KEY | ColumnFlags::AUTO_INCREMENT),
951 ),
952 col(1, "name", TypeId::Bytes, ColumnFlags::empty()),
953 ],
954 indexes: vec![],
955 colocation: vec![],
956 constraints: Default::default(),
957 clustered: false,
958 };
959 let cols = vec![(1u16, Value::Bytes(b"x".to_vec()))];
961 assert!(s.validate_not_null(&cols).is_ok());
962 }
963
964 #[test]
965 fn enum_membership_is_enforced_for_nullable_and_required_columns() {
966 let variants: std::sync::Arc<[String]> =
967 vec!["user".to_string(), "admin".to_string()].into();
968 let required = Schema {
969 columns: vec![col(
970 1,
971 "role",
972 TypeId::Enum {
973 variants: variants.clone(),
974 },
975 ColumnFlags::empty(),
976 )],
977 ..Schema::default()
978 };
979 assert!(required
980 .validate_not_null(&[(1, Value::Bytes(b"user".to_vec()))])
981 .is_ok());
982 assert!(required
983 .validate_not_null(&[(1, Value::Bytes(b"owner".to_vec()))])
984 .is_err());
985
986 let nullable = Schema {
987 columns: vec![col(
988 1,
989 "role",
990 TypeId::Enum { variants },
991 ColumnFlags::empty().with(ColumnFlags::NULLABLE),
992 )],
993 ..Schema::default()
994 };
995 assert!(nullable.validate_not_null(&[(1, Value::Null)]).is_ok());
996 assert!(nullable
997 .validate_not_null(&[(1, Value::Bytes(b"owner".to_vec()))])
998 .is_err());
999 }
1000
1001 fn col_with_default(
1002 id: u16,
1003 name: &str,
1004 ty: TypeId,
1005 flags: ColumnFlags,
1006 dv: DefaultExpr,
1007 ) -> ColumnDef {
1008 ColumnDef {
1009 id,
1010 name: name.into(),
1011 ty,
1012 flags,
1013 default_value: Some(dv),
1014 }
1015 }
1016
1017 #[test]
1018 fn validate_defaults_accepts_matching_static() {
1019 let s = Schema {
1020 schema_id: 1,
1021 columns: vec![col_with_default(
1022 0,
1023 "active",
1024 TypeId::Bool,
1025 ColumnFlags::empty(),
1026 DefaultExpr::Static(Value::Bool(true)),
1027 )],
1028 indexes: vec![],
1029 colocation: vec![],
1030 constraints: Default::default(),
1031 clustered: false,
1032 };
1033 assert!(s.validate_defaults().is_ok());
1034 }
1035
1036 #[test]
1037 fn validate_defaults_rejects_mismatched_static() {
1038 let s = Schema {
1039 schema_id: 1,
1040 columns: vec![col_with_default(
1041 0,
1042 "count",
1043 TypeId::Int64,
1044 ColumnFlags::empty(),
1045 DefaultExpr::Static(Value::Bytes(b"oops".to_vec())),
1046 )],
1047 indexes: vec![],
1048 colocation: vec![],
1049 constraints: Default::default(),
1050 clustered: false,
1051 };
1052 assert!(s.validate_defaults().is_err());
1053 }
1054
1055 #[test]
1056 fn validate_defaults_now_requires_temporal_or_bytes() {
1057 let ok = Schema {
1058 schema_id: 1,
1059 columns: vec![col_with_default(
1060 0,
1061 "ts",
1062 TypeId::Bytes,
1063 ColumnFlags::empty(),
1064 DefaultExpr::Now,
1065 )],
1066 indexes: vec![],
1067 colocation: vec![],
1068 constraints: Default::default(),
1069 clustered: false,
1070 };
1071 assert!(ok.validate_defaults().is_ok());
1072
1073 let bad = Schema {
1074 schema_id: 1,
1075 columns: vec![col_with_default(
1076 0,
1077 "ts",
1078 TypeId::Int64,
1079 ColumnFlags::empty(),
1080 DefaultExpr::Now,
1081 )],
1082 indexes: vec![],
1083 colocation: vec![],
1084 constraints: Default::default(),
1085 clustered: false,
1086 };
1087 assert!(bad.validate_defaults().is_err());
1088 }
1089
1090 #[test]
1091 fn validate_defaults_uuid_requires_uuid_or_bytes() {
1092 let ok = Schema {
1093 schema_id: 1,
1094 columns: vec![col_with_default(
1095 0,
1096 "id",
1097 TypeId::Uuid,
1098 ColumnFlags::empty(),
1099 DefaultExpr::Uuid,
1100 )],
1101 indexes: vec![],
1102 colocation: vec![],
1103 constraints: Default::default(),
1104 clustered: false,
1105 };
1106 assert!(ok.validate_defaults().is_ok());
1107
1108 let bad = Schema {
1109 schema_id: 1,
1110 columns: vec![col_with_default(
1111 0,
1112 "id",
1113 TypeId::Bool,
1114 ColumnFlags::empty(),
1115 DefaultExpr::Uuid,
1116 )],
1117 indexes: vec![],
1118 colocation: vec![],
1119 constraints: Default::default(),
1120 clustered: false,
1121 };
1122 assert!(bad.validate_defaults().is_err());
1123 }
1124
1125 #[test]
1126 fn serde_roundtrip_column_def_with_default() {
1127 let c = col_with_default(
1128 0,
1129 "x",
1130 TypeId::Bytes,
1131 ColumnFlags::empty(),
1132 DefaultExpr::Static(Value::Bytes(b"hello".to_vec())),
1133 );
1134 let json = serde_json::to_string(&c).unwrap();
1135 let de: ColumnDef = serde_json::from_str(&json).unwrap();
1136 assert_eq!(c, de);
1137 let old_json = r#"{"id":0,"name":"y","ty":{"kind":"bytes"},"flags":{"bits":0}}"#;
1139 let old: ColumnDef = serde_json::from_str(old_json).unwrap();
1140 assert!(old.default_value.is_none());
1141 }
1142}