1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
5pub struct SchemaTree {
6 pub databases: Vec<Database>,
7}
8
9impl SchemaTree {
10 pub fn find_table(&self, catalog: &str, schema: &str, table: &str) -> Option<&Table> {
16 self.databases
17 .iter()
18 .find(|db| db.name.eq_ignore_ascii_case(catalog))
19 .and_then(|db| {
20 db.schemas
21 .iter()
22 .find(|s| s.name.eq_ignore_ascii_case(schema))
23 })
24 .and_then(|s| s.tables.iter().find(|t| t.name.eq_ignore_ascii_case(table)))
25 }
26}
27
28#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
29pub struct Database {
30 pub name: String,
31 #[serde(default)]
32 pub schemas: Vec<Schema>,
33}
34
35#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
36pub struct Schema {
37 pub name: String,
38 #[serde(default)]
39 pub tables: Vec<Table>,
40}
41
42#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
43pub struct Table {
44 pub name: String,
45 #[serde(default)]
46 pub columns: Vec<Column>,
47}
48
49#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
50pub struct Column {
51 pub name: String,
52 pub data_type: String,
53 pub nullable: bool,
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59
60 fn tree_with(table: &str) -> SchemaTree {
61 SchemaTree {
62 databases: vec![Database {
63 name: "analytics".into(),
64 schemas: vec![Schema {
65 name: "public".into(),
66 tables: vec![Table {
67 name: table.into(),
68 columns: vec![Column {
69 name: "id".into(),
70 data_type: "bigint".into(),
71 nullable: false,
72 }],
73 }],
74 }],
75 }],
76 }
77 }
78
79 #[test]
80 fn find_table_resolves_case_insensitively() {
81 let tree = tree_with("orders");
82 let found = tree
83 .find_table("Analytics", "PUBLIC", "Orders")
84 .expect("case-insensitive on every part");
85 assert_eq!(found.name, "orders");
86 }
87
88 #[test]
89 fn find_table_none_when_any_part_absent() {
90 let tree = tree_with("orders");
91 assert!(tree.find_table("analytics", "public", "ghost").is_none());
92 assert!(tree.find_table("analytics", "private", "orders").is_none());
93 assert!(tree.find_table("other", "public", "orders").is_none());
94 }
95
96 #[test]
97 fn find_table_none_on_empty_tree() {
98 let tree = SchemaTree::default();
99 assert!(tree.find_table("analytics", "public", "orders").is_none());
100 }
101}