Skip to main content

sqlmodel_core/
dynamic.rs

1//! Dynamic model creation at runtime.
2//!
3//! Provides `DynamicModel` for working with tables whose schema
4//! is not known at compile time.
5
6use std::collections::HashMap;
7
8use crate::row::Row;
9use crate::types::SqlType;
10use crate::value::Value;
11
12/// A column definition for dynamic models.
13#[derive(Debug, Clone)]
14pub struct ColumnDef {
15    /// Column name in the database.
16    pub name: String,
17    /// SQL type.
18    pub sql_type: SqlType,
19    /// Whether this column is nullable.
20    pub nullable: bool,
21    /// Whether this is a primary key column.
22    pub primary_key: bool,
23    /// Whether this is auto-incrementing.
24    pub auto_increment: bool,
25    /// Default value expression.
26    pub default: Option<String>,
27}
28
29impl ColumnDef {
30    /// Create a new column definition.
31    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    /// Mark as nullable.
43    pub fn nullable(mut self) -> Self {
44        self.nullable = true;
45        self
46    }
47
48    /// Mark as primary key.
49    pub fn primary_key(mut self) -> Self {
50        self.primary_key = true;
51        self
52    }
53
54    /// Mark as auto-incrementing.
55    pub fn auto_increment(mut self) -> Self {
56        self.auto_increment = true;
57        self
58    }
59
60    /// Set default value expression.
61    pub fn with_default(mut self, default: impl Into<String>) -> Self {
62        self.default = Some(default.into());
63        self
64    }
65}
66
67/// A dynamically-defined model for tables whose schema is determined at runtime.
68///
69/// Unlike compile-time `Model` structs, `DynamicModel` stores column definitions
70/// and values in hash maps, trading type safety for flexibility.
71///
72/// # Example
73///
74/// ```
75/// use sqlmodel_core::dynamic::{DynamicModel, ColumnDef};
76/// use sqlmodel_core::types::SqlType;
77/// use sqlmodel_core::value::Value;
78///
79/// let mut model = DynamicModel::new("users");
80/// model.add_column(ColumnDef::new("id", SqlType::BigInt).primary_key().auto_increment());
81/// model.add_column(ColumnDef::new("name", SqlType::Text));
82/// model.add_column(ColumnDef::new("email", SqlType::Text));
83///
84/// model.set("name", Value::Text("Alice".to_string()));
85/// model.set("email", Value::Text("alice@example.com".to_string()));
86///
87/// assert_eq!(model.get("name").unwrap().as_str(), Some("Alice"));
88/// ```
89#[derive(Debug, Clone)]
90pub struct DynamicModel {
91    /// The table name.
92    table_name: String,
93    /// Column definitions in insertion order.
94    columns: Vec<ColumnDef>,
95    /// Current values by column name.
96    values: HashMap<String, Value>,
97}
98
99impl DynamicModel {
100    /// Create a new dynamic model for the given table.
101    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    /// Add a column definition.
110    pub fn add_column(&mut self, column: ColumnDef) {
111        self.columns.push(column);
112    }
113
114    /// Get the table name.
115    pub fn table_name(&self) -> &str {
116        &self.table_name
117    }
118
119    /// Get column definitions.
120    pub fn columns(&self) -> &[ColumnDef] {
121        &self.columns
122    }
123
124    /// Get primary key column names.
125    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    /// Set a value for a column.
134    pub fn set(&mut self, column: impl Into<String>, value: Value) {
135        self.values.insert(column.into(), value);
136    }
137
138    /// Get a value for a column.
139    pub fn get(&self, column: &str) -> Option<&Value> {
140        self.values.get(column)
141    }
142
143    /// Remove a value, returning it.
144    pub fn remove(&mut self, column: &str) -> Option<Value> {
145        self.values.remove(column)
146    }
147
148    /// Check if a column has a value set.
149    pub fn has(&self, column: &str) -> bool {
150        self.values.contains_key(column)
151    }
152
153    /// Get all column-value pairs for non-null, non-auto-increment columns.
154    ///
155    /// Suitable for building INSERT statements.
156    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    /// Get primary key values.
165    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    /// Populate from a database row.
174    #[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    /// Create a new DynamicModel instance from a row.
187    #[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}