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, Serialize, Deserialize)]
249pub struct IndexDef {
250 pub name: String,
251 pub column_id: u16,
252 pub kind: IndexKind,
253 #[serde(default, skip_serializing_if = "Option::is_none")]
257 pub predicate: Option<String>,
258}
259
260#[derive(Debug, Clone, Default, Serialize, Deserialize)]
261pub struct Schema {
262 pub schema_id: u64,
263 pub columns: Vec<ColumnDef>,
264 pub indexes: Vec<IndexDef>,
265 #[serde(default)]
270 pub colocation: Vec<Vec<u16>>,
271 #[serde(default)]
276 pub constraints: TableConstraints,
277 #[serde(default)]
280 pub clustered: bool,
281}
282
283impl Schema {
284 pub fn column(&self, name: &str) -> Option<&ColumnDef> {
285 self.columns.iter().find(|c| c.name == name)
286 }
287
288 pub fn primary_key(&self) -> Option<&ColumnDef> {
289 self.columns
290 .iter()
291 .find(|c| c.flags.contains(ColumnFlags::PRIMARY_KEY))
292 }
293
294 pub fn validate_not_null(&self, columns: &[(u16, Value)]) -> Result<()> {
299 let at = |id: u16| columns.iter().find(|(c, _)| *c == id).map(|(_, v)| v);
302 for col in &self.columns {
303 if !col.flags.contains(ColumnFlags::NULLABLE) {
304 if col.flags.contains(ColumnFlags::AUTO_INCREMENT) {
307 match at(col.id) {
308 None | Some(Value::Null) => continue,
309 Some(_) => {}
310 }
311 }
312 match at(col.id) {
313 None => {
314 return Err(MongrelError::InvalidArgument(format!(
315 "column '{}' ({}) is NOT NULL but was omitted",
316 col.name, col.id
317 )));
318 }
319 Some(Value::Null) => {
320 return Err(MongrelError::InvalidArgument(format!(
321 "column '{}' ({}) is NOT NULL but got NULL",
322 col.name, col.id
323 )));
324 }
325 Some(_) => {}
326 }
327 }
328 if let TypeId::Enum { variants } = &col.ty {
329 match at(col.id) {
330 None | Some(Value::Null) => {}
331 Some(Value::Bytes(value))
332 if variants
333 .iter()
334 .any(|variant| variant.as_bytes() == value.as_slice()) => {}
335 Some(Value::Bytes(value)) => {
336 return Err(MongrelError::InvalidArgument(format!(
337 "column '{}' ({}) enum value {:?} is not one of {:?}",
338 col.name,
339 col.id,
340 String::from_utf8_lossy(value),
341 variants
342 )));
343 }
344 Some(value) => {
345 return Err(MongrelError::InvalidArgument(format!(
346 "column '{}' ({}) enum requires a string/bytes value, got {value:?}",
347 col.name, col.id
348 )));
349 }
350 }
351 }
352 }
353 Ok(())
354 }
355
356 pub fn validate_auto_increment(&self) -> Result<()> {
360 let mut seen: Option<&ColumnDef> = None;
361 for col in &self.columns {
362 if !col.flags.contains(ColumnFlags::AUTO_INCREMENT) {
363 continue;
364 }
365 if let Some(prev) = seen {
366 return Err(MongrelError::Schema(format!(
367 "AUTO_INCREMENT may be set on at most one column; '{}' and '{}' both carry it",
368 prev.name, col.name
369 )));
370 }
371 if col.ty != TypeId::Int64 {
372 return Err(MongrelError::Schema(format!(
373 "AUTO_INCREMENT column '{}' must be Int64, is {:?}",
374 col.name, col.ty
375 )));
376 }
377 if !col.flags.contains(ColumnFlags::PRIMARY_KEY) {
378 return Err(MongrelError::Schema(format!(
379 "AUTO_INCREMENT column '{}' must also be the primary key",
380 col.name
381 )));
382 }
383 if col.flags.contains(ColumnFlags::NULLABLE) {
384 return Err(MongrelError::Schema(format!(
385 "AUTO_INCREMENT column '{}' must not be nullable",
386 col.name
387 )));
388 }
389 seen = Some(col);
390 }
391 Ok(())
392 }
393
394 pub fn auto_increment_column(&self) -> Option<&ColumnDef> {
396 self.columns
397 .iter()
398 .find(|c| c.flags.contains(ColumnFlags::AUTO_INCREMENT))
399 }
400
401 pub fn validate_defaults(&self) -> Result<()> {
405 for col in &self.columns {
406 let Some(expr) = &col.default_value else {
407 continue;
408 };
409 match expr {
410 DefaultExpr::Static(v) => {
411 if !value_matches_type(v, col.ty.clone()) {
412 return Err(MongrelError::Schema(format!(
413 "DEFAULT value for column '{}' ({:?}) does not match type {:?}",
414 col.name, v, col.ty
415 )));
416 }
417 }
418 DefaultExpr::Now => {
419 if !matches!(
420 col.ty,
421 TypeId::Bytes | TypeId::TimestampNanos | TypeId::Date64
422 ) {
423 return Err(MongrelError::Schema(format!(
424 "DEFAULT NOW() on column '{}' requires Bytes/TimestampNanos/Date64, is {:?}",
425 col.name, col.ty
426 )));
427 }
428 }
429 DefaultExpr::Uuid => {
430 if !matches!(col.ty, TypeId::Uuid | TypeId::Bytes) {
431 return Err(MongrelError::Schema(format!(
432 "DEFAULT UUID() on column '{}' requires Uuid/Bytes, is {:?}",
433 col.name, col.ty
434 )));
435 }
436 }
437 }
438 }
439 Ok(())
440 }
441}
442
443pub(crate) fn value_matches_type(v: &Value, ty: TypeId) -> bool {
447 matches!(
448 (v, ty),
449 (Value::Null, _)
450 | (Value::Bool(_), TypeId::Bool)
451 | (
452 Value::Int64(_),
453 TypeId::Int8 | TypeId::Int16 | TypeId::Int32 | TypeId::Int64
454 )
455 | (Value::Float64(_), TypeId::Float32 | TypeId::Float64)
456 | (
457 Value::Bytes(_),
458 TypeId::Bytes
459 | TypeId::Json
460 | TypeId::Uuid
461 | TypeId::Date64
462 | TypeId::Time64
463 | TypeId::Enum { .. }
464 )
465 | (
466 Value::Int64(_),
467 TypeId::TimestampNanos | TypeId::Date32 | TypeId::Date64 | TypeId::Time64
468 )
469 | (Value::Uuid(_), TypeId::Uuid)
470 | (Value::Decimal(_), TypeId::Decimal128 { .. })
471 | (Value::Json(_), TypeId::Json)
472 | (Value::Embedding(_), TypeId::Embedding { .. })
473 | (Value::Interval { .. }, TypeId::Interval)
474 )
475}
476
477#[cfg(test)]
478mod tests {
479 use super::*;
480
481 #[test]
482 fn flag_composition() {
483 let f = ColumnFlags::empty()
484 .with(ColumnFlags::PRIMARY_KEY)
485 .with(ColumnFlags::ENCRYPTED_INDEXABLE);
486 assert!(f.contains(ColumnFlags::PRIMARY_KEY));
487 assert!(f.contains(ColumnFlags::ENCRYPTED_INDEXABLE));
488 assert!(!f.contains(ColumnFlags::ENCRYPTED));
489 }
490
491 #[test]
492 fn fixed_size() {
493 assert_eq!(TypeId::Int64.fixed_size(), Some(8));
494 assert_eq!(TypeId::Bytes.fixed_size(), None);
495 assert_eq!(TypeId::Embedding { dim: 768 }.fixed_size(), None);
496 }
497
498 fn col(id: u16, name: &str, ty: TypeId, flags: ColumnFlags) -> ColumnDef {
499 ColumnDef {
500 id,
501 name: name.into(),
502 ty,
503 flags,
504 default_value: None,
505 }
506 }
507
508 #[test]
509 fn auto_increment_validation_accepts_int64_pk() {
510 let s = Schema {
511 schema_id: 1,
512 columns: vec![col(
513 0,
514 "id",
515 TypeId::Int64,
516 ColumnFlags::empty().with(ColumnFlags::PRIMARY_KEY | ColumnFlags::AUTO_INCREMENT),
517 )],
518 indexes: vec![],
519 colocation: vec![],
520 constraints: Default::default(),
521 clustered: false,
522 };
523 assert!(s.validate_auto_increment().is_ok());
524 assert_eq!(s.auto_increment_column().unwrap().id, 0);
525 }
526
527 #[test]
528 fn auto_increment_validation_rejects_non_pk() {
529 let s = Schema {
530 schema_id: 1,
531 columns: vec![
532 col(
533 0,
534 "id",
535 TypeId::Int64,
536 ColumnFlags::empty().with(ColumnFlags::PRIMARY_KEY),
537 ),
538 col(
539 1,
540 "seq",
541 TypeId::Int64,
542 ColumnFlags::empty().with(ColumnFlags::AUTO_INCREMENT),
543 ),
544 ],
545 indexes: vec![],
546 colocation: vec![],
547 constraints: Default::default(),
548 clustered: false,
549 };
550 assert!(s.validate_auto_increment().is_err());
551 }
552
553 #[test]
554 fn auto_increment_validation_rejects_non_int64() {
555 let s = Schema {
556 schema_id: 1,
557 columns: vec![col(
558 0,
559 "id",
560 TypeId::Bytes,
561 ColumnFlags::empty().with(ColumnFlags::PRIMARY_KEY | ColumnFlags::AUTO_INCREMENT),
562 )],
563 indexes: vec![],
564 colocation: vec![],
565 constraints: Default::default(),
566 clustered: false,
567 };
568 assert!(s.validate_auto_increment().is_err());
569 }
570
571 #[test]
572 fn auto_increment_validation_rejects_two() {
573 let s = Schema {
574 schema_id: 1,
575 columns: vec![
576 col(
577 0,
578 "id",
579 TypeId::Int64,
580 ColumnFlags::empty()
581 .with(ColumnFlags::PRIMARY_KEY | ColumnFlags::AUTO_INCREMENT),
582 ),
583 col(
584 1,
585 "id2",
586 TypeId::Int64,
587 ColumnFlags::empty().with(ColumnFlags::AUTO_INCREMENT),
588 ),
589 ],
590 indexes: vec![],
591 colocation: vec![],
592 constraints: Default::default(),
593 clustered: false,
594 };
595 assert!(s.validate_auto_increment().is_err());
596 }
597
598 #[test]
599 fn auto_increment_exempt_from_not_null_when_omitted() {
600 let s = Schema {
601 schema_id: 1,
602 columns: vec![
603 col(
604 0,
605 "id",
606 TypeId::Int64,
607 ColumnFlags::empty()
608 .with(ColumnFlags::PRIMARY_KEY | ColumnFlags::AUTO_INCREMENT),
609 ),
610 col(1, "name", TypeId::Bytes, ColumnFlags::empty()),
611 ],
612 indexes: vec![],
613 colocation: vec![],
614 constraints: Default::default(),
615 clustered: false,
616 };
617 let cols = vec![(1u16, Value::Bytes(b"x".to_vec()))];
619 assert!(s.validate_not_null(&cols).is_ok());
620 }
621
622 #[test]
623 fn enum_membership_is_enforced_for_nullable_and_required_columns() {
624 let variants: std::sync::Arc<[String]> =
625 vec!["user".to_string(), "admin".to_string()].into();
626 let required = Schema {
627 columns: vec![col(
628 1,
629 "role",
630 TypeId::Enum {
631 variants: variants.clone(),
632 },
633 ColumnFlags::empty(),
634 )],
635 ..Schema::default()
636 };
637 assert!(required
638 .validate_not_null(&[(1, Value::Bytes(b"user".to_vec()))])
639 .is_ok());
640 assert!(required
641 .validate_not_null(&[(1, Value::Bytes(b"owner".to_vec()))])
642 .is_err());
643
644 let nullable = Schema {
645 columns: vec![col(
646 1,
647 "role",
648 TypeId::Enum { variants },
649 ColumnFlags::empty().with(ColumnFlags::NULLABLE),
650 )],
651 ..Schema::default()
652 };
653 assert!(nullable.validate_not_null(&[(1, Value::Null)]).is_ok());
654 assert!(nullable
655 .validate_not_null(&[(1, Value::Bytes(b"owner".to_vec()))])
656 .is_err());
657 }
658
659 fn col_with_default(
660 id: u16,
661 name: &str,
662 ty: TypeId,
663 flags: ColumnFlags,
664 dv: DefaultExpr,
665 ) -> ColumnDef {
666 ColumnDef {
667 id,
668 name: name.into(),
669 ty,
670 flags,
671 default_value: Some(dv),
672 }
673 }
674
675 #[test]
676 fn validate_defaults_accepts_matching_static() {
677 let s = Schema {
678 schema_id: 1,
679 columns: vec![col_with_default(
680 0,
681 "active",
682 TypeId::Bool,
683 ColumnFlags::empty(),
684 DefaultExpr::Static(Value::Bool(true)),
685 )],
686 indexes: vec![],
687 colocation: vec![],
688 constraints: Default::default(),
689 clustered: false,
690 };
691 assert!(s.validate_defaults().is_ok());
692 }
693
694 #[test]
695 fn validate_defaults_rejects_mismatched_static() {
696 let s = Schema {
697 schema_id: 1,
698 columns: vec![col_with_default(
699 0,
700 "count",
701 TypeId::Int64,
702 ColumnFlags::empty(),
703 DefaultExpr::Static(Value::Bytes(b"oops".to_vec())),
704 )],
705 indexes: vec![],
706 colocation: vec![],
707 constraints: Default::default(),
708 clustered: false,
709 };
710 assert!(s.validate_defaults().is_err());
711 }
712
713 #[test]
714 fn validate_defaults_now_requires_temporal_or_bytes() {
715 let ok = Schema {
716 schema_id: 1,
717 columns: vec![col_with_default(
718 0,
719 "ts",
720 TypeId::Bytes,
721 ColumnFlags::empty(),
722 DefaultExpr::Now,
723 )],
724 indexes: vec![],
725 colocation: vec![],
726 constraints: Default::default(),
727 clustered: false,
728 };
729 assert!(ok.validate_defaults().is_ok());
730
731 let bad = Schema {
732 schema_id: 1,
733 columns: vec![col_with_default(
734 0,
735 "ts",
736 TypeId::Int64,
737 ColumnFlags::empty(),
738 DefaultExpr::Now,
739 )],
740 indexes: vec![],
741 colocation: vec![],
742 constraints: Default::default(),
743 clustered: false,
744 };
745 assert!(bad.validate_defaults().is_err());
746 }
747
748 #[test]
749 fn validate_defaults_uuid_requires_uuid_or_bytes() {
750 let ok = Schema {
751 schema_id: 1,
752 columns: vec![col_with_default(
753 0,
754 "id",
755 TypeId::Uuid,
756 ColumnFlags::empty(),
757 DefaultExpr::Uuid,
758 )],
759 indexes: vec![],
760 colocation: vec![],
761 constraints: Default::default(),
762 clustered: false,
763 };
764 assert!(ok.validate_defaults().is_ok());
765
766 let bad = Schema {
767 schema_id: 1,
768 columns: vec![col_with_default(
769 0,
770 "id",
771 TypeId::Bool,
772 ColumnFlags::empty(),
773 DefaultExpr::Uuid,
774 )],
775 indexes: vec![],
776 colocation: vec![],
777 constraints: Default::default(),
778 clustered: false,
779 };
780 assert!(bad.validate_defaults().is_err());
781 }
782
783 #[test]
784 fn serde_roundtrip_column_def_with_default() {
785 let c = col_with_default(
786 0,
787 "x",
788 TypeId::Bytes,
789 ColumnFlags::empty(),
790 DefaultExpr::Static(Value::Bytes(b"hello".to_vec())),
791 );
792 let json = serde_json::to_string(&c).unwrap();
793 let de: ColumnDef = serde_json::from_str(&json).unwrap();
794 assert_eq!(c, de);
795 let old_json = r#"{"id":0,"name":"y","ty":{"kind":"bytes"},"flags":{"bits":0}}"#;
797 let old: ColumnDef = serde_json::from_str(old_json).unwrap();
798 assert!(old.default_value.is_none());
799 }
800}