use std::collections::HashMap;
use crate::row::Row;
use crate::types::SqlType;
use crate::value::Value;
#[derive(Debug, Clone)]
pub struct ColumnDef {
pub name: String,
pub sql_type: SqlType,
pub nullable: bool,
pub primary_key: bool,
pub auto_increment: bool,
pub default: Option<String>,
}
impl ColumnDef {
pub fn new(name: impl Into<String>, sql_type: SqlType) -> Self {
Self {
name: name.into(),
sql_type,
nullable: false,
primary_key: false,
auto_increment: false,
default: None,
}
}
pub fn nullable(mut self) -> Self {
self.nullable = true;
self
}
pub fn primary_key(mut self) -> Self {
self.primary_key = true;
self
}
pub fn auto_increment(mut self) -> Self {
self.auto_increment = true;
self
}
pub fn with_default(mut self, default: impl Into<String>) -> Self {
self.default = Some(default.into());
self
}
}
#[derive(Debug, Clone)]
pub struct DynamicModel {
table_name: String,
columns: Vec<ColumnDef>,
values: HashMap<String, Value>,
}
impl DynamicModel {
pub fn new(table_name: impl Into<String>) -> Self {
Self {
table_name: table_name.into(),
columns: Vec::new(),
values: HashMap::new(),
}
}
pub fn add_column(&mut self, column: ColumnDef) {
self.columns.push(column);
}
pub fn table_name(&self) -> &str {
&self.table_name
}
pub fn columns(&self) -> &[ColumnDef] {
&self.columns
}
pub fn primary_key_columns(&self) -> Vec<&str> {
self.columns
.iter()
.filter(|c| c.primary_key)
.map(|c| c.name.as_str())
.collect()
}
pub fn set(&mut self, column: impl Into<String>, value: Value) {
self.values.insert(column.into(), value);
}
pub fn get(&self, column: &str) -> Option<&Value> {
self.values.get(column)
}
pub fn remove(&mut self, column: &str) -> Option<Value> {
self.values.remove(column)
}
pub fn has(&self, column: &str) -> bool {
self.values.contains_key(column)
}
pub fn to_insert_pairs(&self) -> Vec<(&str, &Value)> {
self.columns
.iter()
.filter(|c| !c.auto_increment || self.values.contains_key(&c.name))
.filter_map(|c| self.values.get(&c.name).map(|v| (c.name.as_str(), v)))
.collect()
}
pub fn primary_key_values(&self) -> Vec<Value> {
self.columns
.iter()
.filter(|c| c.primary_key)
.map(|c| self.values.get(&c.name).cloned().unwrap_or(Value::Null))
.collect()
}
#[allow(clippy::result_large_err)]
pub fn from_row(&mut self, row: &Row) -> crate::Result<()> {
for col in &self.columns {
if let Ok(value) = row.get_named::<Value>(&col.name) {
self.values.insert(col.name.clone(), value);
} else if col.nullable {
self.values.insert(col.name.clone(), Value::Null);
}
}
Ok(())
}
#[allow(clippy::result_large_err)]
pub fn new_from_row(
table_name: impl Into<String>,
columns: Vec<ColumnDef>,
row: &Row,
) -> crate::Result<Self> {
let mut model = Self {
table_name: table_name.into(),
columns,
values: HashMap::new(),
};
model.from_row(row)?;
Ok(model)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dynamic_model_basic() {
let mut model = DynamicModel::new("users");
model.add_column(
ColumnDef::new("id", SqlType::BigInt)
.primary_key()
.auto_increment(),
);
model.add_column(ColumnDef::new("name", SqlType::Text));
model.set("name", Value::Text("Alice".to_string()));
assert_eq!(model.table_name(), "users");
assert_eq!(model.get("name").unwrap().as_str(), Some("Alice"));
assert!(!model.has("id"));
assert!(model.has("name"));
}
#[test]
fn test_primary_key_columns() {
let mut model = DynamicModel::new("users");
model.add_column(ColumnDef::new("id", SqlType::BigInt).primary_key());
model.add_column(ColumnDef::new("name", SqlType::Text));
assert_eq!(model.primary_key_columns(), vec!["id"]);
}
#[test]
fn test_insert_pairs_skip_auto_increment() {
let mut model = DynamicModel::new("users");
model.add_column(
ColumnDef::new("id", SqlType::BigInt)
.primary_key()
.auto_increment(),
);
model.add_column(ColumnDef::new("name", SqlType::Text));
model.set("name", Value::Text("Alice".to_string()));
let pairs = model.to_insert_pairs();
assert_eq!(pairs.len(), 1);
assert_eq!(pairs[0].0, "name");
}
#[test]
fn test_primary_key_values() {
let mut model = DynamicModel::new("users");
model.add_column(ColumnDef::new("id", SqlType::BigInt).primary_key());
model.add_column(ColumnDef::new("name", SqlType::Text));
model.set("id", Value::BigInt(42));
model.set("name", Value::Text("Alice".to_string()));
let pk = model.primary_key_values();
assert_eq!(pk, vec![Value::BigInt(42)]);
}
}