1use crate::dialects::DialectType;
11use crate::expressions::DataType;
12use crate::trie::{Trie, TrieResult};
13use std::collections::{HashMap, HashSet};
14use thiserror::Error;
15
16#[derive(Debug, Error, Clone)]
18pub enum SchemaError {
19 #[error("Table not found: {0}")]
20 TableNotFound(String),
21
22 #[error("Ambiguous table: {table} matches multiple tables: {matches}")]
23 AmbiguousTable { table: String, matches: String },
24
25 #[error("Column not found: {column} in table {table}")]
26 ColumnNotFound { table: String, column: String },
27
28 #[error("Schema nesting depth mismatch: expected {expected}, got {actual}")]
29 DepthMismatch { expected: usize, actual: usize },
30
31 #[error("Invalid schema structure: {0}")]
32 InvalidStructure(String),
33}
34
35pub type SchemaResult<T> = Result<T, SchemaError>;
37
38pub const TABLE_PARTS: &[&str] = &["this", "db", "catalog"];
40
41pub trait Schema {
43 fn dialect(&self) -> Option<DialectType>;
45
46 fn add_table(
48 &mut self,
49 table: &str,
50 columns: &[(String, DataType)],
51 dialect: Option<DialectType>,
52 ) -> SchemaResult<()>;
53
54 fn column_names(&self, table: &str) -> SchemaResult<Vec<String>>;
56
57 fn get_column_type(&self, table: &str, column: &str) -> SchemaResult<DataType>;
59
60 fn has_column(&self, table: &str, column: &str) -> bool;
62
63 fn supported_table_args(&self) -> &[&str];
65
66 fn is_empty(&self) -> bool;
68
69 fn depth(&self) -> usize;
71
72 fn find_tables_for_column(&self, column: &str) -> Vec<String>;
75}
76
77#[derive(Debug, Clone)]
79pub struct ColumnInfo {
80 pub data_type: DataType,
81 pub visible: bool,
82}
83
84impl ColumnInfo {
85 pub fn new(data_type: DataType) -> Self {
86 Self {
87 data_type,
88 visible: true,
89 }
90 }
91
92 pub fn with_visibility(data_type: DataType, visible: bool) -> Self {
93 Self { data_type, visible }
94 }
95}
96
97#[derive(Debug, Clone)]
104pub struct MappingSchema {
105 mapping: HashMap<String, SchemaNode>,
107 mapping_trie: Trie<()>,
109 dialect: Option<DialectType>,
111 normalize: bool,
113 visible: HashMap<String, HashSet<String>>,
115 column_order: HashMap<String, Vec<String>>,
117 cached_depth: usize,
119}
120
121#[derive(Debug, Clone)]
123pub enum SchemaNode {
124 Namespace(HashMap<String, SchemaNode>),
126 Table(HashMap<String, ColumnInfo>),
128}
129
130impl Default for MappingSchema {
131 fn default() -> Self {
132 Self::new()
133 }
134}
135
136impl MappingSchema {
137 pub fn new() -> Self {
139 Self {
140 mapping: HashMap::new(),
141 mapping_trie: Trie::new(),
142 dialect: None,
143 normalize: true,
144 visible: HashMap::new(),
145 column_order: HashMap::new(),
146 cached_depth: 0,
147 }
148 }
149
150 pub fn with_dialect(dialect: DialectType) -> Self {
152 Self {
153 dialect: Some(dialect),
154 ..Self::new()
155 }
156 }
157
158 pub fn without_normalization(mut self) -> Self {
160 self.normalize = false;
161 self
162 }
163
164 pub fn set_visible_columns(&mut self, table: &str, columns: &[&str]) {
166 let key = self.normalize_name(table, true);
167 let cols: HashSet<String> = columns
168 .iter()
169 .map(|c| self.normalize_name(c, false))
170 .collect();
171 self.visible.insert(key, cols);
172 }
173
174 fn normalize_name(&self, name: &str, is_table: bool) -> String {
176 if !self.normalize {
177 return name.to_string();
178 }
179
180 let identifier = crate::binding::schema_identifier(name);
181 if identifier.quoted {
182 return crate::optimizer::normalize_identifiers::normalize_identifier(
183 identifier,
184 crate::optimizer::normalize_identifiers::get_normalization_strategy(self.dialect),
185 )
186 .name;
187 }
188 match self.dialect {
191 Some(DialectType::BigQuery) if is_table => {
192 name.to_string()
194 }
195 Some(DialectType::Snowflake) => {
196 name.to_uppercase()
198 }
199 _ => {
200 name.to_lowercase()
202 }
203 }
204 }
205
206 fn parse_table_parts(&self, table: &str) -> Vec<String> {
208 table
209 .split('.')
210 .map(|s| self.normalize_name(s.trim(), true))
211 .collect()
212 }
213
214 fn find_table(&self, table: &str) -> SchemaResult<&HashMap<String, ColumnInfo>> {
216 let parts = self.parse_table_parts(table);
217
218 let reversed_parts: Vec<_> = parts.iter().rev().map(|s| s.as_str()).collect();
220 let key: String = reversed_parts.join(".");
221
222 let (result, _) = self.mapping_trie.in_trie(&key);
223
224 match result {
225 TrieResult::Failed => Err(SchemaError::TableNotFound(table.to_string())),
226 TrieResult::Prefix => {
227 Err(SchemaError::AmbiguousTable {
229 table: table.to_string(),
230 matches: "multiple matches".to_string(),
231 })
232 }
233 TrieResult::Exists => {
234 self.navigate_to_table(&parts)
236 }
237 }
238 }
239
240 fn navigate_to_table(&self, parts: &[String]) -> SchemaResult<&HashMap<String, ColumnInfo>> {
242 let mut current = &self.mapping;
243
244 for (i, part) in parts.iter().enumerate() {
245 match current.get(part) {
246 Some(SchemaNode::Namespace(inner)) => {
247 current = inner;
248 }
249 Some(SchemaNode::Table(cols)) => {
250 if i == parts.len() - 1 {
251 return Ok(cols);
252 } else {
253 return Err(SchemaError::InvalidStructure(format!(
254 "Found table at {} but expected more levels",
255 parts[..=i].join(".")
256 )));
257 }
258 }
259 None => {
260 return Err(SchemaError::TableNotFound(parts.join(".")));
261 }
262 }
263 }
264
265 Err(SchemaError::TableNotFound(parts.join(".")))
267 }
268
269 fn add_table_internal(
271 &mut self,
272 parts: &[String],
273 columns: HashMap<String, ColumnInfo>,
274 ) -> SchemaResult<()> {
275 if parts.is_empty() {
276 return Err(SchemaError::InvalidStructure(
277 "Table name cannot be empty".to_string(),
278 ));
279 }
280
281 let trie_key: String = parts.iter().rev().cloned().collect::<Vec<_>>().join(".");
283 self.mapping_trie.insert(&trie_key, ());
284
285 let mut current = &mut self.mapping;
287
288 for (i, part) in parts.iter().enumerate() {
289 let is_last = i == parts.len() - 1;
290
291 if is_last {
292 current.insert(part.clone(), SchemaNode::Table(columns));
294 return Ok(());
295 } else {
296 let entry = current
298 .entry(part.clone())
299 .or_insert_with(|| SchemaNode::Namespace(HashMap::new()));
300
301 match entry {
302 SchemaNode::Namespace(inner) => {
303 current = inner;
304 }
305 SchemaNode::Table(_) => {
306 return Err(SchemaError::InvalidStructure(format!(
307 "Expected namespace at {} but found table",
308 parts[..=i].join(".")
309 )));
310 }
311 }
312 }
313 }
314
315 Ok(())
316 }
317
318 fn update_depth(&mut self) {
320 self.cached_depth = self.calculate_depth(&self.mapping);
321 }
322
323 fn calculate_depth(&self, mapping: &HashMap<String, SchemaNode>) -> usize {
324 if mapping.is_empty() {
325 return 0;
326 }
327
328 let mut max_depth = 1;
329 for node in mapping.values() {
330 match node {
331 SchemaNode::Namespace(inner) => {
332 let d = 1 + self.calculate_depth(inner);
333 if d > max_depth {
334 max_depth = d;
335 }
336 }
337 SchemaNode::Table(_) => {
338 }
340 }
341 }
342 max_depth
343 }
344}
345
346impl Schema for MappingSchema {
347 fn dialect(&self) -> Option<DialectType> {
348 self.dialect
349 }
350
351 fn add_table(
352 &mut self,
353 table: &str,
354 columns: &[(String, DataType)],
355 _dialect: Option<DialectType>,
356 ) -> SchemaResult<()> {
357 let parts = self.parse_table_parts(table);
358
359 let cols: HashMap<String, ColumnInfo> = columns
360 .iter()
361 .map(|(name, dtype)| {
362 let normalized_name = self.normalize_name(name, false);
363 (normalized_name, ColumnInfo::new(dtype.clone()))
364 })
365 .collect();
366 let column_order: Vec<String> = columns
367 .iter()
368 .map(|(name, _)| self.normalize_name(name, false))
369 .collect();
370
371 self.add_table_internal(&parts, cols)?;
372 self.column_order.insert(parts.join("."), column_order);
373 self.update_depth();
374 Ok(())
375 }
376
377 fn column_names(&self, table: &str) -> SchemaResult<Vec<String>> {
378 let cols = self.find_table(table)?;
379 let table_key = self.normalize_name(table, true);
380 let ordered_columns = self
381 .column_order
382 .get(&self.parse_table_parts(table).join("."));
383
384 if let Some(visible_cols) = self.visible.get(&table_key) {
386 Ok(ordered_columns
387 .map(|columns| {
388 columns
389 .iter()
390 .filter(|column| {
391 cols.contains_key(*column) && visible_cols.contains(*column)
392 })
393 .cloned()
394 .collect()
395 })
396 .unwrap_or_else(|| {
397 cols.keys()
398 .filter(|column| visible_cols.contains(*column))
399 .cloned()
400 .collect()
401 }))
402 } else if let Some(columns) = ordered_columns {
403 Ok(columns
404 .iter()
405 .filter(|column| cols.contains_key(*column))
406 .cloned()
407 .collect())
408 } else {
409 Ok(cols.keys().cloned().collect())
410 }
411 }
412
413 fn get_column_type(&self, table: &str, column: &str) -> SchemaResult<DataType> {
414 let cols = self.find_table(table)?;
415 let normalized_col = self.normalize_name(column, false);
416
417 cols.get(&normalized_col)
418 .map(|info| info.data_type.clone())
419 .ok_or_else(|| SchemaError::ColumnNotFound {
420 table: table.to_string(),
421 column: column.to_string(),
422 })
423 }
424
425 fn has_column(&self, table: &str, column: &str) -> bool {
426 self.get_column_type(table, column).is_ok()
427 }
428
429 fn supported_table_args(&self) -> &[&str] {
430 let depth = self.depth();
431 if depth == 0 {
432 &[]
433 } else if depth <= 3 {
434 &TABLE_PARTS[..depth]
435 } else {
436 TABLE_PARTS
437 }
438 }
439
440 fn is_empty(&self) -> bool {
441 self.mapping.is_empty()
442 }
443
444 fn depth(&self) -> usize {
445 self.cached_depth
446 }
447
448 fn find_tables_for_column(&self, column: &str) -> Vec<String> {
449 let normalized = normalize_name(column, self.dialect, false, self.normalize);
450 let mut result = Vec::new();
451 for table_name in self.mapping.keys() {
452 if self.has_column(table_name, &normalized) {
453 result.push(table_name.clone());
454 }
455 }
456 result
457 }
458}
459
460pub fn normalize_name(
462 name: &str,
463 dialect: Option<DialectType>,
464 is_table: bool,
465 normalize: bool,
466) -> String {
467 if !normalize {
468 return name.to_string();
469 }
470 let identifier = crate::binding::schema_identifier(name);
471 if identifier.quoted {
472 return crate::optimizer::normalize_identifiers::normalize_identifier(
473 identifier,
474 crate::optimizer::normalize_identifiers::get_normalization_strategy(dialect),
475 )
476 .name;
477 }
478
479 match dialect {
480 Some(DialectType::BigQuery) if is_table => name.to_string(),
481 Some(DialectType::Snowflake) => name.to_uppercase(),
482 _ => name.to_lowercase(),
483 }
484}
485
486pub fn ensure_schema(schema: Option<MappingSchema>) -> MappingSchema {
488 schema.unwrap_or_default()
489}
490
491pub fn from_simple_map(tables: &[(&str, &[(&str, DataType)])]) -> MappingSchema {
507 let mut schema = MappingSchema::new();
508
509 for (table_name, columns) in tables {
510 let cols: Vec<(String, DataType)> = columns
511 .iter()
512 .map(|(name, dtype)| (name.to_string(), dtype.clone()))
513 .collect();
514
515 schema.add_table(table_name, &cols, None).ok();
516 }
517
518 schema
519}
520
521pub fn flatten_schema_paths(schema: &MappingSchema) -> Vec<Vec<String>> {
523 let mut paths = Vec::new();
524 flatten_schema_paths_recursive(&schema.mapping, Vec::new(), &mut paths);
525 paths
526}
527
528fn flatten_schema_paths_recursive(
529 mapping: &HashMap<String, SchemaNode>,
530 prefix: Vec<String>,
531 paths: &mut Vec<Vec<String>>,
532) {
533 for (key, node) in mapping {
534 let mut path = prefix.clone();
535 path.push(key.clone());
536
537 match node {
538 SchemaNode::Namespace(inner) => {
539 flatten_schema_paths_recursive(inner, path, paths);
540 }
541 SchemaNode::Table(_) => {
542 paths.push(path);
543 }
544 }
545 }
546}
547
548pub fn nested_set<V: Clone>(
550 map: &mut HashMap<String, HashMap<String, V>>,
551 keys: &[String],
552 value: V,
553) {
554 if keys.is_empty() {
555 return;
556 }
557
558 if keys.len() == 1 {
559 return;
561 }
562
563 let outer_key = &keys[0];
564 let inner_key = &keys[1];
565
566 map.entry(outer_key.clone())
567 .or_insert_with(HashMap::new)
568 .insert(inner_key.clone(), value);
569}
570
571pub fn nested_get<'a, V>(
573 map: &'a HashMap<String, HashMap<String, V>>,
574 keys: &[String],
575) -> Option<&'a V> {
576 if keys.len() != 2 {
577 return None;
578 }
579
580 map.get(&keys[0])?.get(&keys[1])
581}
582
583#[cfg(test)]
584mod tests {
585 use super::*;
586
587 #[test]
588 fn test_empty_schema() {
589 let schema = MappingSchema::new();
590 assert!(schema.is_empty());
591 assert_eq!(schema.depth(), 0);
592 }
593
594 #[test]
595 fn test_add_table() {
596 let mut schema = MappingSchema::new();
597 let columns = vec![
598 (
599 "id".to_string(),
600 DataType::Int {
601 length: None,
602 integer_spelling: false,
603 },
604 ),
605 (
606 "name".to_string(),
607 DataType::VarChar {
608 length: Some(255),
609 parenthesized_length: false,
610 },
611 ),
612 ];
613
614 schema.add_table("users", &columns, None).unwrap();
615
616 assert!(!schema.is_empty());
617 assert_eq!(schema.depth(), 1);
618 assert!(schema.has_column("users", "id"));
619 assert!(schema.has_column("users", "name"));
620 assert!(!schema.has_column("users", "email"));
621 }
622
623 #[test]
624 fn test_qualified_table_names() {
625 let mut schema = MappingSchema::new();
626 let columns = vec![(
627 "id".to_string(),
628 DataType::Int {
629 length: None,
630 integer_spelling: false,
631 },
632 )];
633
634 schema.add_table("mydb.users", &columns, None).unwrap();
635
636 assert!(schema.has_column("mydb.users", "id"));
637 assert_eq!(schema.depth(), 2);
638 }
639
640 #[test]
641 fn test_catalog_db_table() {
642 let mut schema = MappingSchema::new();
643 let columns = vec![(
644 "id".to_string(),
645 DataType::Int {
646 length: None,
647 integer_spelling: false,
648 },
649 )];
650
651 schema
652 .add_table("catalog.mydb.users", &columns, None)
653 .unwrap();
654
655 assert!(schema.has_column("catalog.mydb.users", "id"));
656 assert_eq!(schema.depth(), 3);
657 }
658
659 #[test]
660 fn test_get_column_type() {
661 let mut schema = MappingSchema::new();
662 let columns = vec![
663 (
664 "id".to_string(),
665 DataType::Int {
666 length: None,
667 integer_spelling: false,
668 },
669 ),
670 (
671 "name".to_string(),
672 DataType::VarChar {
673 length: Some(255),
674 parenthesized_length: false,
675 },
676 ),
677 ];
678
679 schema.add_table("users", &columns, None).unwrap();
680
681 let id_type = schema.get_column_type("users", "id").unwrap();
682 assert!(matches!(id_type, DataType::Int { .. }));
683
684 let name_type = schema.get_column_type("users", "name").unwrap();
685 assert!(matches!(
686 name_type,
687 DataType::VarChar {
688 length: Some(255),
689 parenthesized_length: false
690 }
691 ));
692 }
693
694 #[test]
695 fn test_column_names() {
696 let mut schema = MappingSchema::new();
697 let columns = vec![
698 (
699 "id".to_string(),
700 DataType::Int {
701 length: None,
702 integer_spelling: false,
703 },
704 ),
705 (
706 "name".to_string(),
707 DataType::VarChar {
708 length: None,
709 parenthesized_length: false,
710 },
711 ),
712 ];
713
714 schema.add_table("users", &columns, None).unwrap();
715
716 let names = schema.column_names("users").unwrap();
717 assert_eq!(names, vec!["id", "name"]);
718 }
719
720 #[test]
721 fn test_table_not_found() {
722 let schema = MappingSchema::new();
723 let result = schema.column_names("nonexistent");
724 assert!(matches!(result, Err(SchemaError::TableNotFound(_))));
725 }
726
727 #[test]
728 fn test_column_not_found() {
729 let mut schema = MappingSchema::new();
730 let columns = vec![(
731 "id".to_string(),
732 DataType::Int {
733 length: None,
734 integer_spelling: false,
735 },
736 )];
737 schema.add_table("users", &columns, None).unwrap();
738
739 let result = schema.get_column_type("users", "nonexistent");
740 assert!(matches!(result, Err(SchemaError::ColumnNotFound { .. })));
741 }
742
743 #[test]
744 fn test_normalize_name_default() {
745 let name = normalize_name("MyTable", None, true, true);
746 assert_eq!(name, "mytable");
747 }
748
749 #[test]
750 fn test_normalize_name_snowflake() {
751 let name = normalize_name("MyTable", Some(DialectType::Snowflake), true, true);
752 assert_eq!(name, "MYTABLE");
753 }
754
755 #[test]
756 fn test_normalize_disabled() {
757 let name = normalize_name("MyTable", None, true, false);
758 assert_eq!(name, "MyTable");
759 }
760
761 #[test]
762 fn test_from_simple_map() {
763 let schema = from_simple_map(&[
764 (
765 "users",
766 &[
767 (
768 "id",
769 DataType::Int {
770 length: None,
771 integer_spelling: false,
772 },
773 ),
774 (
775 "name",
776 DataType::VarChar {
777 length: None,
778 parenthesized_length: false,
779 },
780 ),
781 ],
782 ),
783 (
784 "orders",
785 &[
786 (
787 "id",
788 DataType::Int {
789 length: None,
790 integer_spelling: false,
791 },
792 ),
793 (
794 "user_id",
795 DataType::Int {
796 length: None,
797 integer_spelling: false,
798 },
799 ),
800 ],
801 ),
802 ]);
803
804 assert!(schema.has_column("users", "id"));
805 assert!(schema.has_column("users", "name"));
806 assert!(schema.has_column("orders", "id"));
807 assert!(schema.has_column("orders", "user_id"));
808 }
809
810 #[test]
811 fn test_flatten_schema_paths() {
812 let mut schema = MappingSchema::new();
813 schema
814 .add_table(
815 "db1.table1",
816 &[(
817 "id".to_string(),
818 DataType::Int {
819 length: None,
820 integer_spelling: false,
821 },
822 )],
823 None,
824 )
825 .unwrap();
826 schema
827 .add_table(
828 "db1.table2",
829 &[(
830 "id".to_string(),
831 DataType::Int {
832 length: None,
833 integer_spelling: false,
834 },
835 )],
836 None,
837 )
838 .unwrap();
839 schema
840 .add_table(
841 "db2.table1",
842 &[(
843 "id".to_string(),
844 DataType::Int {
845 length: None,
846 integer_spelling: false,
847 },
848 )],
849 None,
850 )
851 .unwrap();
852
853 let paths = flatten_schema_paths(&schema);
854 assert_eq!(paths.len(), 3);
855 }
856
857 #[test]
858 fn test_visible_columns() {
859 let mut schema = MappingSchema::new();
860 let columns = vec![
861 (
862 "id".to_string(),
863 DataType::Int {
864 length: None,
865 integer_spelling: false,
866 },
867 ),
868 (
869 "name".to_string(),
870 DataType::VarChar {
871 length: None,
872 parenthesized_length: false,
873 },
874 ),
875 (
876 "password".to_string(),
877 DataType::VarChar {
878 length: None,
879 parenthesized_length: false,
880 },
881 ),
882 ];
883 schema.add_table("users", &columns, None).unwrap();
884 schema.set_visible_columns("users", &["id", "name"]);
885
886 let names = schema.column_names("users").unwrap();
887 assert_eq!(names.len(), 2);
888 assert!(names.contains(&"id".to_string()));
889 assert!(names.contains(&"name".to_string()));
890 assert!(!names.contains(&"password".to_string()));
891 }
892}