1use std::collections::HashMap;
7
8use crate::row::Row;
9use crate::types::SqlType;
10use crate::value::Value;
11
12#[derive(Debug, Clone)]
14pub struct ColumnDef {
15 pub name: String,
17 pub sql_type: SqlType,
19 pub nullable: bool,
21 pub primary_key: bool,
23 pub auto_increment: bool,
25 pub default: Option<String>,
27}
28
29impl ColumnDef {
30 pub fn new(name: impl Into<String>, sql_type: SqlType) -> Self {
32 Self {
33 name: name.into(),
34 sql_type,
35 nullable: false,
36 primary_key: false,
37 auto_increment: false,
38 default: None,
39 }
40 }
41
42 pub fn nullable(mut self) -> Self {
44 self.nullable = true;
45 self
46 }
47
48 pub fn primary_key(mut self) -> Self {
50 self.primary_key = true;
51 self
52 }
53
54 pub fn auto_increment(mut self) -> Self {
56 self.auto_increment = true;
57 self
58 }
59
60 pub fn with_default(mut self, default: impl Into<String>) -> Self {
62 self.default = Some(default.into());
63 self
64 }
65}
66
67#[derive(Debug, Clone)]
90pub struct DynamicModel {
91 table_name: String,
93 columns: Vec<ColumnDef>,
95 values: HashMap<String, Value>,
97}
98
99impl DynamicModel {
100 pub fn new(table_name: impl Into<String>) -> Self {
102 Self {
103 table_name: table_name.into(),
104 columns: Vec::new(),
105 values: HashMap::new(),
106 }
107 }
108
109 pub fn add_column(&mut self, column: ColumnDef) {
111 self.columns.push(column);
112 }
113
114 pub fn table_name(&self) -> &str {
116 &self.table_name
117 }
118
119 pub fn columns(&self) -> &[ColumnDef] {
121 &self.columns
122 }
123
124 pub fn primary_key_columns(&self) -> Vec<&str> {
126 self.columns
127 .iter()
128 .filter(|c| c.primary_key)
129 .map(|c| c.name.as_str())
130 .collect()
131 }
132
133 pub fn set(&mut self, column: impl Into<String>, value: Value) {
135 self.values.insert(column.into(), value);
136 }
137
138 pub fn get(&self, column: &str) -> Option<&Value> {
140 self.values.get(column)
141 }
142
143 pub fn remove(&mut self, column: &str) -> Option<Value> {
145 self.values.remove(column)
146 }
147
148 pub fn has(&self, column: &str) -> bool {
150 self.values.contains_key(column)
151 }
152
153 pub fn to_insert_pairs(&self) -> Vec<(&str, &Value)> {
157 self.columns
158 .iter()
159 .filter(|c| !c.auto_increment || self.values.contains_key(&c.name))
160 .filter_map(|c| self.values.get(&c.name).map(|v| (c.name.as_str(), v)))
161 .collect()
162 }
163
164 pub fn primary_key_values(&self) -> Vec<Value> {
166 self.columns
167 .iter()
168 .filter(|c| c.primary_key)
169 .map(|c| self.values.get(&c.name).cloned().unwrap_or(Value::Null))
170 .collect()
171 }
172
173 #[allow(clippy::result_large_err)]
175 pub fn from_row(&mut self, row: &Row) -> crate::Result<()> {
176 for col in &self.columns {
177 if let Ok(value) = row.get_named::<Value>(&col.name) {
178 self.values.insert(col.name.clone(), value);
179 } else if col.nullable {
180 self.values.insert(col.name.clone(), Value::Null);
181 }
182 }
183 Ok(())
184 }
185
186 #[allow(clippy::result_large_err)]
188 pub fn new_from_row(
189 table_name: impl Into<String>,
190 columns: Vec<ColumnDef>,
191 row: &Row,
192 ) -> crate::Result<Self> {
193 let mut model = Self {
194 table_name: table_name.into(),
195 columns,
196 values: HashMap::new(),
197 };
198 model.from_row(row)?;
199 Ok(model)
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 #[test]
208 fn test_dynamic_model_basic() {
209 let mut model = DynamicModel::new("users");
210 model.add_column(
211 ColumnDef::new("id", SqlType::BigInt)
212 .primary_key()
213 .auto_increment(),
214 );
215 model.add_column(ColumnDef::new("name", SqlType::Text));
216
217 model.set("name", Value::Text("Alice".to_string()));
218
219 assert_eq!(model.table_name(), "users");
220 assert_eq!(model.get("name").unwrap().as_str(), Some("Alice"));
221 assert!(!model.has("id"));
222 assert!(model.has("name"));
223 }
224
225 #[test]
226 fn test_primary_key_columns() {
227 let mut model = DynamicModel::new("users");
228 model.add_column(ColumnDef::new("id", SqlType::BigInt).primary_key());
229 model.add_column(ColumnDef::new("name", SqlType::Text));
230
231 assert_eq!(model.primary_key_columns(), vec!["id"]);
232 }
233
234 #[test]
235 fn test_insert_pairs_skip_auto_increment() {
236 let mut model = DynamicModel::new("users");
237 model.add_column(
238 ColumnDef::new("id", SqlType::BigInt)
239 .primary_key()
240 .auto_increment(),
241 );
242 model.add_column(ColumnDef::new("name", SqlType::Text));
243
244 model.set("name", Value::Text("Alice".to_string()));
245
246 let pairs = model.to_insert_pairs();
247 assert_eq!(pairs.len(), 1);
248 assert_eq!(pairs[0].0, "name");
249 }
250
251 #[test]
252 fn test_primary_key_values() {
253 let mut model = DynamicModel::new("users");
254 model.add_column(ColumnDef::new("id", SqlType::BigInt).primary_key());
255 model.add_column(ColumnDef::new("name", SqlType::Text));
256
257 model.set("id", Value::BigInt(42));
258 model.set("name", Value::Text("Alice".to_string()));
259
260 let pk = model.primary_key_values();
261 assert_eq!(pk, vec![Value::BigInt(42)]);
262 }
263}