hyperdb_compile_check/
registry.rs1use std::collections::HashMap;
17use std::sync::OnceLock;
18
19use parking_lot::Mutex;
20
21#[derive(Debug, Clone)]
23pub struct TableEntry {
24 pub create_sql: String,
26 pub fields: Vec<String>,
29}
30
31static REGISTRY: OnceLock<Mutex<HashMap<String, TableEntry>>> = OnceLock::new();
38
39static STRUCT_TO_TABLE: OnceLock<Mutex<HashMap<String, String>>> = OnceLock::new();
41
42fn registry() -> &'static Mutex<HashMap<String, TableEntry>> {
43 REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
44}
45
46fn struct_to_table() -> &'static Mutex<HashMap<String, String>> {
47 STRUCT_TO_TABLE.get_or_init(|| Mutex::new(HashMap::new()))
48}
49
50pub fn register(
59 struct_name: impl Into<String>,
60 table_name: impl Into<String>,
61 create_sql: impl Into<String>,
62 fields: Vec<String>,
63) {
64 let struct_name = struct_name.into();
65 let table_name = table_name.into();
66 let entry = TableEntry {
67 create_sql: create_sql.into(),
68 fields,
69 };
70 registry().lock().insert(table_name.clone(), entry.clone());
71 struct_to_table()
72 .lock()
73 .insert(struct_name, table_name.clone());
74}
75
76pub fn get_by_table(table_name: &str) -> Option<TableEntry> {
78 registry().lock().get(table_name).cloned()
79}
80
81pub fn get_by_struct(struct_name: &str) -> Option<(String, TableEntry)> {
84 let table_name = struct_to_table().lock().get(struct_name).cloned()?;
85 let entry = registry().lock().get(&table_name).cloned()?;
86 Some((table_name, entry))
87}
88
89pub fn contains(table_name: &str) -> bool {
91 registry().lock().contains_key(table_name)
92}
93
94pub fn registered_names() -> Vec<String> {
96 registry().lock().keys().cloned().collect()
97}
98
99#[derive(Debug)]
103pub struct Registry;
104
105impl Registry {
106 pub fn seed_if_known(
114 table_name: &str,
115 db: &mut crate::db::CompileTimeDb,
116 ) -> hyperdb_api::Result<bool> {
117 let Some(entry) = get_by_table(table_name) else {
118 return Ok(false);
119 };
120 db.conn.execute_command(&entry.create_sql)?;
121 Ok(true)
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
133 fn register_and_retrieve_by_table() {
134 register(
135 "RegTestUser",
136 "reg_test_users",
137 "CREATE TABLE reg_test_users (id BIGINT, name TEXT)",
138 vec!["id".into(), "name".into()],
139 );
140 let entry = get_by_table("reg_test_users").expect("lookup by table name");
141 assert_eq!(entry.fields, &["id", "name"]);
142 assert!(entry.create_sql.contains("reg_test_users"));
143 }
144
145 #[test]
146 fn register_and_retrieve_by_struct() {
147 register(
148 "RegTestProfile",
149 "reg_test_profiles",
150 "CREATE TABLE reg_test_profiles (id BIGINT, bio TEXT)",
151 vec!["id".into(), "bio".into()],
152 );
153 let (table_name, entry) = get_by_struct("RegTestProfile").expect("lookup by struct name");
154 assert_eq!(table_name, "reg_test_profiles");
155 assert_eq!(entry.fields, &["id", "bio"]);
156 }
157
158 #[test]
159 fn contains_returns_false_for_unknown() {
160 assert!(!contains("reg_test_nonexistent_xyzzy"));
161 }
162
163 #[test]
164 fn registration_ordering_independent() {
165 register(
166 "RegTestOrder",
167 "reg_test_orders",
168 "CREATE TABLE reg_test_orders (id BIGINT, user_id BIGINT)",
169 vec!["id".into(), "user_id".into()],
170 );
171 register(
172 "RegTestCustomer",
173 "reg_test_customers",
174 "CREATE TABLE reg_test_customers (id BIGINT)",
175 vec!["id".into()],
176 );
177 assert!(contains("reg_test_orders"));
178 assert!(contains("reg_test_customers"));
179 assert!(get_by_struct("RegTestOrder").is_some());
180 assert!(get_by_struct("RegTestCustomer").is_some());
181 }
182}