use crate::abstract_layer::DbType;
use crate::model::{Model, Row, Value};
use crate::query::filter::FilterExpr;
use std::collections::HashMap;
use std::fmt::Write;
pub fn format_filter(filter: &FilterExpr, sql: &mut String, param_idx: &mut i32, db_type: DbType) {
match filter {
FilterExpr::Comparison {
column,
operator,
value: _,
} => {
match db_type {
DbType::PostgreSQL => {
write!(sql, "{} {} ${}", column, operator, param_idx).unwrap();
}
DbType::Turso | DbType::MySQL => {
write!(sql, "{} {} ?", column, operator).unwrap();
}
}
*param_idx += 1;
}
FilterExpr::ColumnComparison {
left_column,
operator,
right_column,
} => {
write!(sql, "{} {} {}", left_column, operator, right_column).unwrap();
}
FilterExpr::In { column, values } => {
write!(sql, "{} IN (", column).unwrap();
for (i, _) in values.iter().enumerate() {
if i > 0 {
sql.push_str(", ");
}
match db_type {
DbType::PostgreSQL => {
write!(sql, "${}", param_idx).unwrap();
}
DbType::Turso | DbType::MySQL => {
sql.push('?');
}
}
*param_idx += 1;
}
sql.push(')');
}
FilterExpr::InSubquery {
column,
subquery_sql,
subquery_params: _,
} => {
write!(sql, "{} IN ({})", column, subquery_sql).unwrap();
let placeholder_count =
subquery_sql.matches('?').count() + subquery_sql.matches('$').count();
*param_idx += placeholder_count as i32;
}
FilterExpr::And(left, right) => {
format_filter(left, sql, param_idx, db_type);
sql.push_str(" AND ");
format_filter(right, sql, param_idx, db_type);
}
FilterExpr::Or(left, right) => {
format_filter(left, sql, param_idx, db_type);
sql.push_str(" OR ");
format_filter(right, sql, param_idx, db_type);
}
}
}
pub fn format_filter_with_params(
filter: &FilterExpr,
sql: &mut String,
param_idx: &mut usize,
params: &mut Vec<Value>,
db_type: DbType,
) {
match filter {
FilterExpr::Comparison {
column,
operator,
value,
} => {
match db_type {
DbType::PostgreSQL => {
write!(sql, "{} {} ${}", column, operator, param_idx).unwrap();
}
DbType::Turso | DbType::MySQL => {
write!(sql, "{} {} ?", column, operator).unwrap();
}
}
params.push(value.clone().into());
*param_idx += 1;
}
FilterExpr::ColumnComparison {
left_column,
operator,
right_column,
} => {
write!(sql, "{} {} {}", left_column, operator, right_column).unwrap();
}
FilterExpr::In { column, values } => {
write!(sql, "{} IN (", column).unwrap();
for (i, value) in values.iter().enumerate() {
if i > 0 {
sql.push_str(", ");
}
match db_type {
DbType::PostgreSQL => {
write!(sql, "${}", param_idx).unwrap();
}
DbType::Turso | DbType::MySQL => {
sql.push('?');
}
}
params.push(value.clone().into());
*param_idx += 1;
}
sql.push(')');
}
FilterExpr::InSubquery {
column,
subquery_sql,
subquery_params,
} => {
write!(sql, "{} IN ({})", column, subquery_sql).unwrap();
for param in subquery_params {
params.push(param.clone());
*param_idx += 1;
}
}
FilterExpr::And(left, right) => {
format_filter_with_params(left, sql, param_idx, params, db_type);
sql.push_str(" AND ");
format_filter_with_params(right, sql, param_idx, params, db_type);
}
FilterExpr::Or(left, right) => {
format_filter_with_params(left, sql, param_idx, params, db_type);
sql.push_str(" OR ");
format_filter_with_params(right, sql, param_idx, params, db_type);
}
}
}
pub fn extract_model_from_row<T: Model>(
row_data: &HashMap<String, Value>,
) -> Result<T, crate::Error> {
let row = Row::new(row_data.clone());
T::from_row(&row)
}
pub fn convert_column_value(
rust_type: &str,
is_nullable: bool,
get_int: impl FnOnce() -> Option<i64>,
get_string: impl FnOnce() -> Option<String>,
get_real: impl FnOnce() -> Option<f64>,
get_bool: impl FnOnce() -> Option<i8>,
) -> Result<Value, crate::Error> {
if is_nullable {
match rust_type {
"i8" | "i16" | "i32" | "i64" | "u8" | "u16" | "u32" | "u64" => match get_int() {
Some(val) => Ok(Value::Integer(val)),
None => Ok(Value::Null),
},
"String" => match get_string() {
Some(val) => Ok(Value::Text(val)),
None => Ok(Value::Null),
},
"f32" | "f64" => match get_real() {
Some(val) => Ok(Value::Real(val)),
None => Ok(Value::Null),
},
"bool" => match get_bool() {
Some(1) => Ok(Value::Integer(1)),
Some(0) => Ok(Value::Integer(0)),
_ => Ok(Value::Null),
},
_ => Err(crate::Error::Database(format!(
"Unsupported nullable column type: {rust_type}"
))),
}
} else {
match rust_type {
"i8" | "i16" | "i32" | "i64" | "u8" | "u16" | "u32" | "u64" => {
Ok(Value::Integer(get_int().unwrap_or(0)))
}
"String" => Ok(Value::Text(get_string().unwrap_or_default())),
"f32" | "f64" => Ok(Value::Real(get_real().unwrap_or(0.0))),
"bool" => {
let v = get_bool().unwrap_or(0);
Ok(Value::Integer(if v == 1 { 1 } else { 0 }))
}
_ => Err(crate::Error::Database(format!(
"Unsupported column type: {rust_type}"
))),
}
}
}
pub fn build_batch_insert_sql<T: Model>(models_count: usize) -> (String, usize) {
let columns = T::COLUMNS.join(", ");
let col_count = T::COLUMNS.len();
let mut sql = format!("INSERT INTO {} ({columns}) VALUES ", T::TABLE_NAME);
for idx in 0..models_count {
if idx > 0 {
sql.push_str(", ");
}
let placeholders: Vec<String> = (1..=col_count).map(|_| "?".to_string()).collect();
sql.push_str(&format!("({})", placeholders.join(", ")));
}
(sql, col_count)
}
pub fn build_batch_insert_sql_postgresql<T: Model>(models_count: usize) -> (String, usize) {
let columns = T::COLUMNS.join(", ");
let col_count = T::COLUMNS.len();
let mut sql = format!("INSERT INTO {} ({columns}) VALUES ", T::TABLE_NAME);
let mut param_idx = 1;
for idx in 0..models_count {
if idx > 0 {
sql.push_str(", ");
}
let placeholders: Vec<String> = (1..=col_count)
.map(|i| {
let idx = param_idx + i - 1;
format!("${}", idx)
})
.collect();
sql.push_str(&format!("({})", placeholders.join(", ")));
param_idx += col_count;
}
(sql, col_count)
}
pub fn collect_batch_insert_values<T: Model>(models: &[&T]) -> Vec<Value> {
let mut all_values = Vec::new();
for model in models {
let values = model.field_values();
all_values.extend(values);
}
all_values
}