1use super::types::DataType;
7use std::collections::HashMap;
8use std::fmt;
9
10#[derive(Debug, Clone)]
12pub struct TableDef {
13 pub name: String,
15 pub columns: Vec<ColumnDef>,
17 pub primary_key: Vec<String>,
19 pub indexes: Vec<IndexDef>,
21 pub constraints: Vec<Constraint>,
23 pub version: u32,
25 pub created_at: u64,
27 pub updated_at: u64,
29}
30
31impl TableDef {
32 pub fn new(name: impl Into<String>) -> Self {
34 let now = std::time::SystemTime::now()
35 .duration_since(std::time::UNIX_EPOCH)
36 .unwrap_or_default()
37 .as_secs();
38
39 Self {
40 name: name.into(),
41 columns: Vec::new(),
42 primary_key: Vec::new(),
43 indexes: Vec::new(),
44 constraints: Vec::new(),
45 version: 1,
46 created_at: now,
47 updated_at: now,
48 }
49 }
50
51 pub fn add_column(mut self, column: ColumnDef) -> Self {
53 self.columns.push(column);
54 self
55 }
56
57 pub fn primary_key(mut self, columns: Vec<String>) -> Self {
59 self.primary_key = columns;
60 self
61 }
62
63 pub fn add_index(mut self, index: IndexDef) -> Self {
65 self.indexes.push(index);
66 self
67 }
68
69 pub fn add_constraint(mut self, constraint: Constraint) -> Self {
71 self.constraints.push(constraint);
72 self
73 }
74
75 pub fn get_column(&self, name: &str) -> Option<&ColumnDef> {
77 self.columns.iter().find(|c| c.name == name)
78 }
79
80 pub fn column_index(&self, name: &str) -> Option<usize> {
82 self.columns.iter().position(|c| c.name == name)
83 }
84
85 pub fn is_primary_key_column(&self, name: &str) -> bool {
87 self.primary_key.iter().any(|pk| pk == name)
88 }
89
90 pub fn validate(&self) -> Result<(), TableDefError> {
92 if self.name.is_empty() {
94 return Err(TableDefError::EmptyTableName);
95 }
96
97 let mut seen = HashMap::new();
99 for col in &self.columns {
100 if seen.insert(&col.name, true).is_some() {
101 return Err(TableDefError::DuplicateColumn(col.name.clone()));
102 }
103 }
104
105 for pk_col in &self.primary_key {
107 if self.get_column(pk_col).is_none() {
108 return Err(TableDefError::InvalidPrimaryKey(pk_col.clone()));
109 }
110 }
111
112 for index in &self.indexes {
114 for col in &index.columns {
115 if self.get_column(col).is_none() {
116 return Err(TableDefError::InvalidIndexColumn(col.clone()));
117 }
118 }
119 }
120
121 for constraint in &self.constraints {
123 for col in &constraint.columns {
124 if self.get_column(col).is_none() {
125 return Err(TableDefError::InvalidConstraintColumn(col.clone()));
126 }
127 }
128 }
129
130 Ok(())
131 }
132
133 pub fn to_bytes(&self) -> Vec<u8> {
135 let mut buf = Vec::new();
136
137 buf.extend_from_slice(b"RTBL");
139
140 buf.extend_from_slice(&self.version.to_le_bytes());
142
143 write_string(&mut buf, &self.name);
145
146 buf.extend_from_slice(&self.created_at.to_le_bytes());
148 buf.extend_from_slice(&self.updated_at.to_le_bytes());
149
150 write_varint(&mut buf, self.columns.len() as u64);
152 for col in &self.columns {
153 col.write_to(&mut buf);
154 }
155
156 write_varint(&mut buf, self.primary_key.len() as u64);
158 for pk in &self.primary_key {
159 write_string(&mut buf, pk);
160 }
161
162 write_varint(&mut buf, self.indexes.len() as u64);
164 for idx in &self.indexes {
165 idx.write_to(&mut buf);
166 }
167
168 write_varint(&mut buf, self.constraints.len() as u64);
170 for constraint in &self.constraints {
171 constraint.write_to(&mut buf);
172 }
173
174 buf
175 }
176
177 pub fn from_bytes(data: &[u8]) -> Result<Self, TableDefError> {
179 if data.len() < 4 {
180 return Err(TableDefError::TruncatedData);
181 }
182
183 if &data[0..4] != b"RTBL" {
185 return Err(TableDefError::InvalidMagic);
186 }
187
188 let mut offset = 4;
189
190 if data.len() < offset + 4 {
192 return Err(TableDefError::TruncatedData);
193 }
194 let version = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap());
195 offset += 4;
196
197 let (name, name_len) = read_string(&data[offset..])?;
199 offset += name_len;
200
201 if data.len() < offset + 16 {
203 return Err(TableDefError::TruncatedData);
204 }
205 let created_at = u64::from_le_bytes(data[offset..offset + 8].try_into().unwrap());
206 offset += 8;
207 let updated_at = u64::from_le_bytes(data[offset..offset + 8].try_into().unwrap());
208 offset += 8;
209
210 let (col_count, varint_len) = read_varint(&data[offset..])?;
212 offset += varint_len;
213 let mut columns = Vec::with_capacity(col_count as usize);
214 for _ in 0..col_count {
215 let (col, col_len) = ColumnDef::read_from(&data[offset..])?;
216 offset += col_len;
217 columns.push(col);
218 }
219
220 let (pk_count, varint_len) = read_varint(&data[offset..])?;
222 offset += varint_len;
223 let mut primary_key = Vec::with_capacity(pk_count as usize);
224 for _ in 0..pk_count {
225 let (pk, pk_len) = read_string(&data[offset..])?;
226 offset += pk_len;
227 primary_key.push(pk);
228 }
229
230 let (idx_count, varint_len) = read_varint(&data[offset..])?;
232 offset += varint_len;
233 let mut indexes = Vec::with_capacity(idx_count as usize);
234 for _ in 0..idx_count {
235 let (idx, idx_len) = IndexDef::read_from(&data[offset..])?;
236 offset += idx_len;
237 indexes.push(idx);
238 }
239
240 let (constraint_count, varint_len) = read_varint(&data[offset..])?;
242 offset += varint_len;
243 let mut constraints = Vec::with_capacity(constraint_count as usize);
244 for _ in 0..constraint_count {
245 let (constraint, constraint_len) = Constraint::read_from(&data[offset..])?;
246 offset += constraint_len;
247 constraints.push(constraint);
248 }
249
250 Ok(Self {
251 name,
252 columns,
253 primary_key,
254 indexes,
255 constraints,
256 version,
257 created_at,
258 updated_at,
259 })
260 }
261}
262
263impl fmt::Display for TableDef {
264 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265 writeln!(f, "TABLE {} (version {})", self.name, self.version)?;
266 writeln!(f, " Columns:")?;
267 for col in &self.columns {
268 writeln!(f, " {}", col)?;
269 }
270 if !self.primary_key.is_empty() {
271 writeln!(f, " Primary Key: ({})", self.primary_key.join(", "))?;
272 }
273 if !self.indexes.is_empty() {
274 writeln!(f, " Indexes:")?;
275 for idx in &self.indexes {
276 writeln!(f, " {}", idx)?;
277 }
278 }
279 Ok(())
280 }
281}
282
283#[derive(Debug, Clone)]
285pub struct ColumnDef {
286 pub name: String,
288 pub data_type: DataType,
290 pub nullable: bool,
292 pub default: Option<Vec<u8>>,
294 pub vector_dim: Option<u32>,
296 pub compress: bool,
298 pub enum_variants: Vec<String>,
300 pub decimal_precision: u8,
302 pub element_type: Option<DataType>,
304 pub metadata: HashMap<String, String>,
306}
307
308impl ColumnDef {
309 pub fn new(name: impl Into<String>, data_type: DataType) -> Self {
311 Self {
312 name: name.into(),
313 data_type,
314 nullable: true,
315 default: None,
316 vector_dim: None,
317 compress: false,
318 enum_variants: Vec::new(),
319 decimal_precision: 4,
320 element_type: None,
321 metadata: HashMap::new(),
322 }
323 }
324
325 pub fn not_null(mut self) -> Self {
327 self.nullable = false;
328 self
329 }
330
331 pub fn with_default(mut self, default: Vec<u8>) -> Self {
333 self.default = Some(default);
334 self
335 }
336
337 pub fn with_vector_dim(mut self, dim: u32) -> Self {
339 self.vector_dim = Some(dim);
340 self
341 }
342
343 pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
345 self.metadata.insert(key.into(), value.into());
346 self
347 }
348
349 pub fn compressed(mut self) -> Self {
351 self.compress = true;
352 self
353 }
354
355 pub fn with_variants(mut self, variants: Vec<String>) -> Self {
357 self.enum_variants = variants;
358 self
359 }
360
361 pub fn with_precision(mut self, precision: u8) -> Self {
363 self.decimal_precision = precision;
364 self
365 }
366
367 pub fn with_element_type(mut self, dt: DataType) -> Self {
369 self.element_type = Some(dt);
370 self
371 }
372
373 fn write_to(&self, buf: &mut Vec<u8>) {
375 write_string(buf, &self.name);
376 buf.push(self.data_type.to_byte());
377 buf.push(if self.nullable { 1 } else { 0 });
378
379 if let Some(ref default) = self.default {
381 buf.push(1);
382 write_varint(buf, default.len() as u64);
383 buf.extend_from_slice(default);
384 } else {
385 buf.push(0);
386 }
387
388 if let Some(dim) = self.vector_dim {
390 buf.push(1);
391 buf.extend_from_slice(&dim.to_le_bytes());
392 } else {
393 buf.push(0);
394 }
395
396 buf.push(if self.compress { 1 } else { 0 });
398
399 write_varint(buf, self.enum_variants.len() as u64);
401 for variant in &self.enum_variants {
402 write_string(buf, variant);
403 }
404
405 buf.push(self.decimal_precision);
407
408 if let Some(et) = self.element_type {
410 buf.push(1);
411 buf.push(et.to_byte());
412 } else {
413 buf.push(0);
414 }
415
416 write_varint(buf, self.metadata.len() as u64);
418 for (k, v) in &self.metadata {
419 write_string(buf, k);
420 write_string(buf, v);
421 }
422 }
423
424 fn read_from(data: &[u8]) -> Result<(Self, usize), TableDefError> {
426 let mut offset = 0;
427
428 let (name, name_len) = read_string(&data[offset..])?;
429 offset += name_len;
430
431 if data.len() < offset + 2 {
432 return Err(TableDefError::TruncatedData);
433 }
434
435 let data_type = DataType::from_byte(data[offset]).ok_or(TableDefError::InvalidDataType)?;
436 offset += 1;
437
438 let nullable = data[offset] != 0;
439 offset += 1;
440
441 if data.len() < offset + 1 {
443 return Err(TableDefError::TruncatedData);
444 }
445 let has_default = data[offset] != 0;
446 offset += 1;
447 let default = if has_default {
448 let (len, varint_len) = read_varint(&data[offset..])?;
449 offset += varint_len;
450 if data.len() < offset + len as usize {
451 return Err(TableDefError::TruncatedData);
452 }
453 let default_data = data[offset..offset + len as usize].to_vec();
454 offset += len as usize;
455 Some(default_data)
456 } else {
457 None
458 };
459
460 if data.len() < offset + 1 {
462 return Err(TableDefError::TruncatedData);
463 }
464 let has_vector_dim = data[offset] != 0;
465 offset += 1;
466 let vector_dim = if has_vector_dim {
467 if data.len() < offset + 4 {
468 return Err(TableDefError::TruncatedData);
469 }
470 let dim = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap());
471 offset += 4;
472 Some(dim)
473 } else {
474 None
475 };
476
477 if data.len() < offset + 1 {
479 return Err(TableDefError::TruncatedData);
480 }
481 let compress = data[offset] != 0;
482 offset += 1;
483
484 let (variant_count, varint_len) = read_varint(&data[offset..])?;
486 offset += varint_len;
487 let mut enum_variants = Vec::with_capacity(variant_count as usize);
488 for _ in 0..variant_count {
489 let (variant, variant_len) = read_string(&data[offset..])?;
490 offset += variant_len;
491 enum_variants.push(variant);
492 }
493
494 if data.len() < offset + 1 {
496 return Err(TableDefError::TruncatedData);
497 }
498 let decimal_precision = data[offset];
499 offset += 1;
500
501 if data.len() < offset + 1 {
503 return Err(TableDefError::TruncatedData);
504 }
505 let has_element_type = data[offset] != 0;
506 offset += 1;
507 let element_type = if has_element_type {
508 if data.len() < offset + 1 {
509 return Err(TableDefError::TruncatedData);
510 }
511 let et = DataType::from_byte(data[offset]).ok_or(TableDefError::InvalidDataType)?;
512 offset += 1;
513 Some(et)
514 } else {
515 None
516 };
517
518 let (meta_count, varint_len) = read_varint(&data[offset..])?;
520 offset += varint_len;
521 let mut metadata = HashMap::with_capacity(meta_count as usize);
522 for _ in 0..meta_count {
523 let (k, k_len) = read_string(&data[offset..])?;
524 offset += k_len;
525 let (v, v_len) = read_string(&data[offset..])?;
526 offset += v_len;
527 metadata.insert(k, v);
528 }
529
530 Ok((
531 Self {
532 name,
533 data_type,
534 nullable,
535 default,
536 vector_dim,
537 compress,
538 enum_variants,
539 decimal_precision,
540 element_type,
541 metadata,
542 },
543 offset,
544 ))
545 }
546}
547
548impl fmt::Display for ColumnDef {
549 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
550 write!(f, "{} {}", self.name, self.data_type)?;
551 if let Some(dim) = self.vector_dim {
552 write!(f, "({})", dim)?;
553 }
554 if !self.nullable {
555 write!(f, " NOT NULL")?;
556 }
557 if self.default.is_some() {
558 write!(f, " DEFAULT <value>")?;
559 }
560 Ok(())
561 }
562}
563
564#[derive(Debug, Clone)]
566pub struct IndexDef {
567 pub name: String,
569 pub columns: Vec<String>,
571 pub index_type: IndexType,
573 pub unique: bool,
575}
576
577impl IndexDef {
578 pub fn new(name: impl Into<String>, columns: Vec<String>) -> Self {
580 Self {
581 name: name.into(),
582 columns,
583 index_type: IndexType::BTree,
584 unique: false,
585 }
586 }
587
588 pub fn unique(mut self) -> Self {
590 self.unique = true;
591 self
592 }
593
594 pub fn with_type(mut self, index_type: IndexType) -> Self {
596 self.index_type = index_type;
597 self
598 }
599
600 fn write_to(&self, buf: &mut Vec<u8>) {
602 write_string(buf, &self.name);
603 buf.push(self.index_type as u8);
604 buf.push(if self.unique { 1 } else { 0 });
605 write_varint(buf, self.columns.len() as u64);
606 for col in &self.columns {
607 write_string(buf, col);
608 }
609 }
610
611 fn read_from(data: &[u8]) -> Result<(Self, usize), TableDefError> {
613 let mut offset = 0;
614
615 let (name, name_len) = read_string(&data[offset..])?;
616 offset += name_len;
617
618 if data.len() < offset + 2 {
619 return Err(TableDefError::TruncatedData);
620 }
621
622 let index_type =
623 IndexType::from_byte(data[offset]).ok_or(TableDefError::InvalidIndexType)?;
624 offset += 1;
625
626 let unique = data[offset] != 0;
627 offset += 1;
628
629 let (col_count, varint_len) = read_varint(&data[offset..])?;
630 offset += varint_len;
631
632 let mut columns = Vec::with_capacity(col_count as usize);
633 for _ in 0..col_count {
634 let (col, col_len) = read_string(&data[offset..])?;
635 offset += col_len;
636 columns.push(col);
637 }
638
639 Ok((
640 Self {
641 name,
642 columns,
643 index_type,
644 unique,
645 },
646 offset,
647 ))
648 }
649}
650
651impl fmt::Display for IndexDef {
652 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
653 if self.unique {
654 write!(f, "UNIQUE ")?;
655 }
656 write!(
657 f,
658 "INDEX {} ({}) USING {:?}",
659 self.name,
660 self.columns.join(", "),
661 self.index_type
662 )
663 }
664}
665
666#[derive(Debug, Clone, Copy, PartialEq, Eq)]
668#[repr(u8)]
669pub enum IndexType {
670 BTree = 1,
672 Hash = 2,
674 IvfFlat = 3,
676 Hnsw = 4,
678}
679
680impl IndexType {
681 fn from_byte(b: u8) -> Option<Self> {
682 match b {
683 1 => Some(IndexType::BTree),
684 2 => Some(IndexType::Hash),
685 3 => Some(IndexType::IvfFlat),
686 4 => Some(IndexType::Hnsw),
687 _ => None,
688 }
689 }
690}
691
692#[derive(Debug, Clone)]
694pub struct Constraint {
695 pub name: String,
697 pub constraint_type: ConstraintType,
699 pub columns: Vec<String>,
701 pub ref_table: Option<String>,
703 pub ref_columns: Option<Vec<String>>,
705}
706
707impl Constraint {
708 pub fn new(name: impl Into<String>, constraint_type: ConstraintType) -> Self {
710 Self {
711 name: name.into(),
712 constraint_type,
713 columns: Vec::new(),
714 ref_table: None,
715 ref_columns: None,
716 }
717 }
718
719 pub fn on_columns(mut self, columns: Vec<String>) -> Self {
721 self.columns = columns;
722 self
723 }
724
725 pub fn references(mut self, table: String, columns: Vec<String>) -> Self {
727 self.ref_table = Some(table);
728 self.ref_columns = Some(columns);
729 self
730 }
731
732 fn write_to(&self, buf: &mut Vec<u8>) {
734 write_string(buf, &self.name);
735 buf.push(self.constraint_type as u8);
736
737 write_varint(buf, self.columns.len() as u64);
738 for col in &self.columns {
739 write_string(buf, col);
740 }
741
742 if let Some(ref table) = self.ref_table {
743 buf.push(1);
744 write_string(buf, table);
745 if let Some(ref cols) = self.ref_columns {
746 write_varint(buf, cols.len() as u64);
747 for col in cols {
748 write_string(buf, col);
749 }
750 } else {
751 write_varint(buf, 0);
752 }
753 } else {
754 buf.push(0);
755 }
756 }
757
758 fn read_from(data: &[u8]) -> Result<(Self, usize), TableDefError> {
760 let mut offset = 0;
761
762 let (name, name_len) = read_string(&data[offset..])?;
763 offset += name_len;
764
765 if data.len() < offset + 1 {
766 return Err(TableDefError::TruncatedData);
767 }
768
769 let constraint_type =
770 ConstraintType::from_byte(data[offset]).ok_or(TableDefError::InvalidConstraintType)?;
771 offset += 1;
772
773 let (col_count, varint_len) = read_varint(&data[offset..])?;
774 offset += varint_len;
775
776 let mut columns = Vec::with_capacity(col_count as usize);
777 for _ in 0..col_count {
778 let (col, col_len) = read_string(&data[offset..])?;
779 offset += col_len;
780 columns.push(col);
781 }
782
783 if data.len() < offset + 1 {
784 return Err(TableDefError::TruncatedData);
785 }
786
787 let has_ref = data[offset] != 0;
788 offset += 1;
789
790 let (ref_table, ref_columns) = if has_ref {
791 let (table, table_len) = read_string(&data[offset..])?;
792 offset += table_len;
793
794 let (ref_col_count, varint_len) = read_varint(&data[offset..])?;
795 offset += varint_len;
796
797 let mut ref_cols = Vec::with_capacity(ref_col_count as usize);
798 for _ in 0..ref_col_count {
799 let (col, col_len) = read_string(&data[offset..])?;
800 offset += col_len;
801 ref_cols.push(col);
802 }
803
804 (Some(table), Some(ref_cols))
805 } else {
806 (None, None)
807 };
808
809 Ok((
810 Self {
811 name,
812 constraint_type,
813 columns,
814 ref_table,
815 ref_columns,
816 },
817 offset,
818 ))
819 }
820}
821
822#[derive(Debug, Clone, Copy, PartialEq, Eq)]
824#[repr(u8)]
825pub enum ConstraintType {
826 PrimaryKey = 1,
828 Unique = 2,
830 ForeignKey = 3,
832 Check = 4,
834 NotNull = 5,
836}
837
838impl ConstraintType {
839 fn from_byte(b: u8) -> Option<Self> {
840 match b {
841 1 => Some(ConstraintType::PrimaryKey),
842 2 => Some(ConstraintType::Unique),
843 3 => Some(ConstraintType::ForeignKey),
844 4 => Some(ConstraintType::Check),
845 5 => Some(ConstraintType::NotNull),
846 _ => None,
847 }
848 }
849}
850
851#[derive(Debug, Clone, PartialEq)]
853pub enum TableDefError {
854 EmptyTableName,
856 DuplicateColumn(String),
858 InvalidPrimaryKey(String),
860 InvalidIndexColumn(String),
862 InvalidConstraintColumn(String),
864 TruncatedData,
866 InvalidMagic,
868 InvalidDataType,
870 InvalidIndexType,
872 InvalidConstraintType,
874 VarintOverflow,
876}
877
878impl fmt::Display for TableDefError {
879 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
880 match self {
881 TableDefError::EmptyTableName => write!(f, "empty table name"),
882 TableDefError::DuplicateColumn(name) => write!(f, "duplicate column: {}", name),
883 TableDefError::InvalidPrimaryKey(name) => {
884 write!(f, "invalid primary key column: {}", name)
885 }
886 TableDefError::InvalidIndexColumn(name) => write!(f, "invalid index column: {}", name),
887 TableDefError::InvalidConstraintColumn(name) => {
888 write!(f, "invalid constraint column: {}", name)
889 }
890 TableDefError::TruncatedData => write!(f, "truncated data"),
891 TableDefError::InvalidMagic => write!(f, "invalid magic bytes"),
892 TableDefError::InvalidDataType => write!(f, "invalid data type"),
893 TableDefError::InvalidIndexType => write!(f, "invalid index type"),
894 TableDefError::InvalidConstraintType => write!(f, "invalid constraint type"),
895 TableDefError::VarintOverflow => write!(f, "varint overflow"),
896 }
897 }
898}
899
900impl std::error::Error for TableDefError {}
901
902fn write_varint(buf: &mut Vec<u8>, mut value: u64) {
904 loop {
905 let mut byte = (value & 0x7F) as u8;
906 value >>= 7;
907 if value != 0 {
908 byte |= 0x80;
909 }
910 buf.push(byte);
911 if value == 0 {
912 break;
913 }
914 }
915}
916
917fn read_varint(data: &[u8]) -> Result<(u64, usize), TableDefError> {
919 let mut result: u64 = 0;
920 let mut shift = 0;
921 let mut offset = 0;
922
923 loop {
924 if offset >= data.len() {
925 return Err(TableDefError::TruncatedData);
926 }
927 let byte = data[offset];
928 offset += 1;
929
930 if shift >= 64 {
931 return Err(TableDefError::VarintOverflow);
932 }
933
934 result |= ((byte & 0x7F) as u64) << shift;
935 shift += 7;
936
937 if byte & 0x80 == 0 {
938 break;
939 }
940 }
941
942 Ok((result, offset))
943}
944
945fn write_string(buf: &mut Vec<u8>, s: &str) {
947 let bytes = s.as_bytes();
948 write_varint(buf, bytes.len() as u64);
949 buf.extend_from_slice(bytes);
950}
951
952fn read_string(data: &[u8]) -> Result<(String, usize), TableDefError> {
954 let (len, varint_len) = read_varint(data)?;
955 let offset = varint_len;
956 if data.len() < offset + len as usize {
957 return Err(TableDefError::TruncatedData);
958 }
959 let s = String::from_utf8(data[offset..offset + len as usize].to_vec())
960 .map_err(|_| TableDefError::TruncatedData)?;
961 Ok((s, offset + len as usize))
962}
963
964#[cfg(test)]
965mod tests {
966 use super::*;
967
968 #[test]
969 fn test_table_def_basic() {
970 let table = TableDef::new("port_scans")
971 .add_column(ColumnDef::new("id", DataType::UnsignedInteger).not_null())
972 .add_column(ColumnDef::new("ip", DataType::IpAddr).not_null())
973 .add_column(ColumnDef::new("port", DataType::UnsignedInteger).not_null())
974 .add_column(ColumnDef::new("status", DataType::Text))
975 .add_column(ColumnDef::new("timestamp", DataType::Timestamp).not_null())
976 .primary_key(vec!["id".to_string()]);
977
978 assert_eq!(table.name, "port_scans");
979 assert_eq!(table.columns.len(), 5);
980 assert_eq!(table.primary_key, vec!["id"]);
981 assert!(table.validate().is_ok());
982 }
983
984 #[test]
985 fn test_table_def_with_indexes() {
986 let table = TableDef::new("subdomains")
987 .add_column(ColumnDef::new("id", DataType::UnsignedInteger).not_null())
988 .add_column(ColumnDef::new("domain", DataType::Text).not_null())
989 .add_column(ColumnDef::new("subdomain", DataType::Text).not_null())
990 .add_column(ColumnDef::new("ip", DataType::IpAddr))
991 .primary_key(vec!["id".to_string()])
992 .add_index(IndexDef::new("idx_domain", vec!["domain".to_string()]))
993 .add_index(IndexDef::new("idx_subdomain", vec!["subdomain".to_string()]).unique());
994
995 assert_eq!(table.indexes.len(), 2);
996 assert!(table.indexes[1].unique);
997 assert!(table.validate().is_ok());
998 }
999
1000 #[test]
1001 fn test_table_def_with_vector() {
1002 let table = TableDef::new("embeddings")
1003 .add_column(ColumnDef::new("id", DataType::UnsignedInteger).not_null())
1004 .add_column(
1005 ColumnDef::new("embedding", DataType::Vector)
1006 .not_null()
1007 .with_vector_dim(384),
1008 )
1009 .add_column(ColumnDef::new("text", DataType::Text))
1010 .primary_key(vec!["id".to_string()])
1011 .add_index(
1012 IndexDef::new("idx_embedding", vec!["embedding".to_string()])
1013 .with_type(IndexType::IvfFlat),
1014 );
1015
1016 let col = table.get_column("embedding").unwrap();
1017 assert_eq!(col.vector_dim, Some(384));
1018 assert!(table.validate().is_ok());
1019 }
1020
1021 #[test]
1022 fn test_table_def_validation_duplicate_column() {
1023 let table = TableDef::new("test")
1024 .add_column(ColumnDef::new("id", DataType::Integer))
1025 .add_column(ColumnDef::new("id", DataType::Text)); assert!(matches!(
1028 table.validate(),
1029 Err(TableDefError::DuplicateColumn(_))
1030 ));
1031 }
1032
1033 #[test]
1034 fn test_table_def_validation_invalid_pk() {
1035 let table = TableDef::new("test")
1036 .add_column(ColumnDef::new("id", DataType::Integer))
1037 .primary_key(vec!["nonexistent".to_string()]);
1038
1039 assert!(matches!(
1040 table.validate(),
1041 Err(TableDefError::InvalidPrimaryKey(_))
1042 ));
1043 }
1044
1045 #[test]
1046 fn test_table_def_roundtrip() {
1047 let table = TableDef::new("hosts")
1048 .add_column(ColumnDef::new("id", DataType::UnsignedInteger).not_null())
1049 .add_column(ColumnDef::new("ip", DataType::IpAddr).not_null())
1050 .add_column(ColumnDef::new("hostname", DataType::Text))
1051 .add_column(ColumnDef::new("last_seen", DataType::Timestamp))
1052 .add_column(ColumnDef::new("fingerprint", DataType::Vector).with_vector_dim(128))
1053 .primary_key(vec!["id".to_string()])
1054 .add_index(IndexDef::new("idx_ip", vec!["ip".to_string()]).unique())
1055 .add_index(
1056 IndexDef::new("idx_fingerprint", vec!["fingerprint".to_string()])
1057 .with_type(IndexType::IvfFlat),
1058 );
1059
1060 let bytes = table.to_bytes();
1061 let recovered = TableDef::from_bytes(&bytes).unwrap();
1062
1063 assert_eq!(table.name, recovered.name);
1064 assert_eq!(table.columns.len(), recovered.columns.len());
1065 assert_eq!(table.primary_key, recovered.primary_key);
1066 assert_eq!(table.indexes.len(), recovered.indexes.len());
1067
1068 for (orig, rec) in table.columns.iter().zip(recovered.columns.iter()) {
1069 assert_eq!(orig.name, rec.name);
1070 assert_eq!(orig.data_type, rec.data_type);
1071 assert_eq!(orig.nullable, rec.nullable);
1072 assert_eq!(orig.vector_dim, rec.vector_dim);
1073 }
1074
1075 for (orig, rec) in table.indexes.iter().zip(recovered.indexes.iter()) {
1076 assert_eq!(orig.name, rec.name);
1077 assert_eq!(orig.columns, rec.columns);
1078 assert_eq!(orig.unique, rec.unique);
1079 assert_eq!(orig.index_type, rec.index_type);
1080 }
1081 }
1082
1083 #[test]
1084 fn test_column_def_metadata() {
1085 let col = ColumnDef::new("ip", DataType::IpAddr)
1086 .not_null()
1087 .with_metadata("description", "Target IP address")
1088 .with_metadata("indexed", "true");
1089
1090 assert_eq!(
1091 col.metadata.get("description"),
1092 Some(&"Target IP address".to_string())
1093 );
1094 assert_eq!(col.metadata.get("indexed"), Some(&"true".to_string()));
1095 }
1096
1097 #[test]
1098 fn test_constraint_foreign_key() {
1099 let constraint = Constraint::new("fk_host", ConstraintType::ForeignKey)
1100 .on_columns(vec!["host_id".to_string()])
1101 .references("hosts".to_string(), vec!["id".to_string()]);
1102
1103 assert_eq!(constraint.constraint_type, ConstraintType::ForeignKey);
1104 assert_eq!(constraint.columns, vec!["host_id"]);
1105 assert_eq!(constraint.ref_table, Some("hosts".to_string()));
1106 assert_eq!(constraint.ref_columns, Some(vec!["id".to_string()]));
1107 }
1108
1109 #[test]
1110 fn test_table_display() {
1111 let table = TableDef::new("test")
1112 .add_column(ColumnDef::new("id", DataType::Integer).not_null())
1113 .add_column(ColumnDef::new("name", DataType::Text))
1114 .primary_key(vec!["id".to_string()]);
1115
1116 let display = format!("{}", table);
1117 assert!(display.contains("TABLE test"));
1118 assert!(display.contains("id INTEGER NOT NULL"));
1119 assert!(display.contains("name TEXT"));
1120 assert!(display.contains("Primary Key: (id)"));
1121 }
1122}