forge_core/schema/
registry.rs1use std::collections::BTreeMap;
7use std::sync::RwLock;
8
9use super::function::FunctionDef;
10use super::model::TableDef;
11
12const LOCK_POISONED: &str = "schema registry lock poisoned";
13
14pub struct SchemaRegistry {
16 tables: RwLock<BTreeMap<String, TableDef>>,
17 enums: RwLock<BTreeMap<String, EnumDef>>,
18 functions: RwLock<BTreeMap<String, FunctionDef>>,
19}
20
21impl SchemaRegistry {
22 pub fn new() -> Self {
24 Self {
25 tables: RwLock::new(BTreeMap::new()),
26 enums: RwLock::new(BTreeMap::new()),
27 functions: RwLock::new(BTreeMap::new()),
28 }
29 }
30
31 pub fn register_table(&self, table: TableDef) {
33 let mut tables = self.tables.write().expect(LOCK_POISONED);
34 tables.insert(table.name.clone(), table);
35 }
36
37 pub fn register_enum(&self, enum_def: EnumDef) {
39 let mut enums = self.enums.write().expect(LOCK_POISONED);
40 enums.insert(enum_def.name.clone(), enum_def);
41 }
42
43 pub fn register_function(&self, func: FunctionDef) {
45 let mut functions = self.functions.write().expect(LOCK_POISONED);
46 functions.insert(func.name.clone(), func);
47 }
48
49 pub fn get_table(&self, name: &str) -> Option<TableDef> {
51 let tables = self.tables.read().expect(LOCK_POISONED);
52 tables.get(name).cloned()
53 }
54
55 pub fn get_enum(&self, name: &str) -> Option<EnumDef> {
57 let enums = self.enums.read().expect(LOCK_POISONED);
58 enums.get(name).cloned()
59 }
60
61 pub fn get_function(&self, name: &str) -> Option<FunctionDef> {
63 let functions = self.functions.read().expect(LOCK_POISONED);
64 functions.get(name).cloned()
65 }
66
67 pub fn all_tables(&self) -> Vec<TableDef> {
69 let tables = self.tables.read().expect(LOCK_POISONED);
70 tables.values().cloned().collect()
71 }
72
73 pub fn all_enums(&self) -> Vec<EnumDef> {
75 let enums = self.enums.read().expect(LOCK_POISONED);
76 enums.values().cloned().collect()
77 }
78
79 pub fn all_functions(&self) -> Vec<FunctionDef> {
81 let functions = self.functions.read().expect(LOCK_POISONED);
82 functions.values().cloned().collect()
83 }
84
85 pub fn clear(&self) {
87 self.tables.write().expect(LOCK_POISONED).clear();
88 self.enums.write().expect(LOCK_POISONED).clear();
89 self.functions.write().expect(LOCK_POISONED).clear();
90 }
91}
92
93impl Default for SchemaRegistry {
94 fn default() -> Self {
95 Self::new()
96 }
97}
98
99#[derive(Debug, Clone)]
101pub struct EnumDef {
102 pub name: String,
104
105 pub sql_name: String,
107
108 pub variants: Vec<EnumVariant>,
110
111 pub doc: Option<String>,
113}
114
115impl EnumDef {
116 pub fn new(name: &str) -> Self {
118 Self {
119 name: name.to_string(),
120 sql_name: to_snake_case(name),
121 variants: Vec::new(),
122 doc: None,
123 }
124 }
125
126 pub fn to_create_type_sql(&self) -> String {
128 let values: Vec<String> = self
129 .variants
130 .iter()
131 .map(|v| format!("'{}'", v.sql_value))
132 .collect();
133
134 format!(
135 "CREATE TYPE {} AS ENUM (\n {}\n);",
136 self.sql_name,
137 values.join(",\n ")
138 )
139 }
140}
141
142#[derive(Debug, Clone)]
144pub struct EnumVariant {
145 pub name: String,
147
148 pub sql_value: String,
150
151 pub int_value: Option<i32>,
153
154 pub doc: Option<String>,
156}
157
158impl EnumVariant {
159 pub fn new(name: &str) -> Self {
161 Self {
162 name: name.to_string(),
163 sql_value: to_snake_case(name),
164 int_value: None,
165 doc: None,
166 }
167 }
168}
169
170use crate::util::to_snake_case;
171
172#[cfg(test)]
173#[allow(clippy::unwrap_used, clippy::indexing_slicing)]
174mod tests {
175 use super::*;
176 use crate::schema::field::FieldDef;
177 use crate::schema::model::TableDef;
178 use crate::schema::types::RustType;
179
180 #[test]
181 fn test_registry_basic() {
182 let registry = SchemaRegistry::new();
183
184 let mut table = TableDef::new("users", "User");
185 table.fields.push(FieldDef::new("id", RustType::Uuid));
186
187 registry.register_table(table.clone());
188
189 let retrieved = registry.get_table("users").unwrap();
190 assert_eq!(retrieved.name, "users");
191 assert_eq!(retrieved.struct_name, "User");
192 }
193
194 #[test]
195 fn test_enum_def() {
196 let mut enum_def = EnumDef::new("ProjectStatus");
197 enum_def.variants.push(EnumVariant::new("Draft"));
198 enum_def.variants.push(EnumVariant::new("Active"));
199 enum_def.variants.push(EnumVariant::new("Completed"));
200
201 let sql = enum_def.to_create_type_sql();
202 assert!(sql.contains("CREATE TYPE project_status AS ENUM"));
203 assert!(sql.contains("'draft'"));
204 assert!(sql.contains("'active'"));
205 }
206}