use crate::builders::Join;
use crate::i_where::IWhere;
use crate::mappers::Mapper;
use crate::parameter::Parameter;
use crate::result::Result;
use crate::statements::statement::Statement;
use crate::statements::{OrderBy, SelectStatementOptions, Where};
use crate::value::Value;
use crate::{DeleteStatement, InsertStatement, SelectInsertedIdStatement, SelectStatement, UpdateStatement};
use std::sync::Arc;
#[derive(Clone)]
pub struct StatementMappers {
entity_mappers: Arc<dyn Mapper<String, String> + Send + Sync>,
property_mappers: Arc<dyn Mapper<String, String> + Send + Sync>,
}
impl StatementMappers {
pub fn new(
entity_mappers: Arc<dyn Mapper<String, String> + Send + Sync>,
property_mappers: Arc<dyn Mapper<String, String> + Send + Sync>,
) -> Self {
Self {
entity_mappers,
property_mappers,
}
}
pub(crate) fn column(&self, property: &str) -> String {
Mapper::map(self, property.to_string())
}
pub(crate) fn property(&self, key: &str) -> String {
Mapper::reverse_map(self, key.to_string())
}
fn map_select_field(&self, field: &str) -> String {
if let Some(as_index) = field.to_uppercase().find(" AS ") {
let expression = field[..as_index].trim();
let alias = field[as_index + 4..].trim();
return format!("{} AS {alias}", self.column(expression));
}
self.column(field)
}
pub fn apply_select(
&self,
select: &SelectStatement,
cell: &dyn Fn(&str, &Value) -> Result<Value>,
) -> Result<SelectStatement> {
let table = self.entity(&select.table);
let fields: Vec<String> = select
.fields
.iter()
.map(|field| self.map_select_field(field))
.collect();
let field_refs: Vec<&str> = fields.iter().map(String::as_str).collect();
let group: Vec<String> = select.group_by.iter().map(|column| self.column(column)).collect();
let group_refs: Vec<&str> = group.iter().map(String::as_str).collect();
let order: Vec<OrderBy> = select
.order_by
.iter()
.map(|item| OrderBy {
column: self.column(&item.column),
direction: item.direction,
})
.collect();
let joins: Vec<Join> = select
.joins
.iter()
.map(|join| Join {
kind: join.kind,
table: self.entity(&join.table),
on: join.on.as_ref().map(|on| map_on(on, |name| self.column(name))),
})
.collect();
let mapped_where = self.apply_predicate(select.predicate(), cell)?;
let mapped_having = self.apply_predicate(select.having.as_deref(), cell)?;
let where_clause = mapped_where
.as_ref()
.map(|clause| clause as &dyn IWhere)
.or(select.predicate());
let having = mapped_having
.as_ref()
.map(|clause| clause as &dyn IWhere)
.or(select.having.as_deref());
Ok(SelectStatement::new(SelectStatementOptions {
table: &table,
fields: &field_refs,
where_clause,
joins: &joins,
group_by: &group_refs,
order_by: &order,
limit: select.limit,
distinct: select.distinct,
having,
offset: select.offset,
}))
}
pub fn apply_where(
&self,
clause: &Where,
cell: &dyn Fn(&str, &Value) -> Result<Value>,
) -> Result<Where> {
clause.map(|name| self.column(name), cell)
}
pub fn apply_insert(&self, insert: &InsertStatement) -> InsertStatement {
let fields: Vec<Parameter> = insert
.fields()
.iter()
.map(|field| Parameter {
name: self.column(&field.name),
value: field.value.clone(),
direction: field.direction,
})
.collect();
InsertStatement::new(self.entity(insert.table()), &fields)
}
pub fn apply_update(
&self,
update: &UpdateStatement,
cell: &dyn Fn(&str, &Value) -> Result<Value>,
) -> Result<UpdateStatement> {
let fields: Vec<Parameter> = update
.fields()
.iter()
.map(|field| Parameter {
name: self.column(&field.name),
value: field.value.clone(),
direction: field.direction,
})
.collect();
let mapped = self.apply_predicate(update.where_clause(), cell)?;
let clause: Option<&dyn IWhere> = match mapped.as_ref() {
Some(where_clause) => Some(where_clause),
None => update.where_clause(),
};
Ok(UpdateStatement::new(self.entity(update.table()), &fields, clause))
}
pub fn apply_delete(
&self,
delete: &DeleteStatement,
cell: &dyn Fn(&str, &Value) -> Result<Value>,
) -> Result<DeleteStatement> {
let mapped = self.apply_predicate(delete.where_clause(), cell)?;
let clause: Option<&dyn IWhere> = match mapped.as_ref() {
Some(where_clause) => Some(where_clause),
None => delete.where_clause(),
};
Ok(DeleteStatement::new(self.entity(delete.table()), clause))
}
pub fn apply_inserted(&self, inserted: &SelectInsertedIdStatement) -> SelectInsertedIdStatement {
SelectInsertedIdStatement::new(self.entity(&inserted.table), self.column(&inserted.column))
}
fn apply_predicate(
&self,
predicate: Option<&dyn IWhere>,
cell: &dyn Fn(&str, &Value) -> Result<Value>,
) -> Result<Option<Where>> {
match predicate.and_then(|item| item.as_any().downcast_ref::<Where>()) {
Some(clause) => Ok(Some(self.apply_where(clause, cell)?)),
None => Ok(None),
}
}
fn entity(&self, name: &str) -> String {
Mapper::map(&self.entity_mappers, name.to_string())
}
}
impl Mapper<String, String> for StatementMappers {
fn map(&self, property: String) -> String {
let Some(dot) = last_dot(&property) else {
return Mapper::map(&self.property_mappers, property);
};
let qualifier = &property[..dot];
let name = &property[dot + 1..];
let mapped = Mapper::map(&self.property_mappers, name.to_string());
if mapped != name && mapped.contains('.') {
return mapped;
}
let table = self.entity(qualifier);
if table == qualifier && mapped == name {
property
} else {
format!("{table}.{mapped}")
}
}
fn reverse_map(&self, key: String) -> String {
let reversed = Mapper::reverse_map(&self.property_mappers, key.clone());
if reversed != key && Mapper::map(&self.property_mappers, reversed.clone()) == key {
return reversed;
}
let Some(dot) = last_dot(&key) else {
return reversed;
};
let column = &key[dot + 1..];
let name = Mapper::reverse_map(&self.property_mappers, column.to_string());
if name != column && Mapper::map(&self.property_mappers, name.clone()) == column {
name
} else {
reversed
}
}
}
fn map_on(on: &str, column: impl Fn(&str) -> String) -> String {
match on.split_once(" = ") {
Some((left, right)) if !left.contains(' ') && !right.contains(' ') => {
format!("{} = {}", column(left), column(right))
}
_ => on.to_string(),
}
}
fn last_dot(name: &str) -> Option<usize> {
let mut quoted = false;
let mut dot = None;
for (index, ch) in name.char_indices() {
if ch == '"' {
quoted = !quoted;
}
if ch == '.' && !quoted {
dot = Some(index);
}
}
dot
}