use crate::error::EFResult;
use crate::metadata::EntityTypeMeta;
use crate::provider::{DbValue, DbValueConvertError};
use super::ast::{BoolExpr, FilterCondition, InSubquerySpec, SubquerySpec};
pub(crate) struct PortablePlaceholderGenerator;
impl crate::provider::ISqlGenerator for PortablePlaceholderGenerator {
fn select(&self, _: &str, _: &[&str]) -> String {
String::new()
}
fn insert(&self, _: &str, _: &[&str], _: bool) -> String {
String::new()
}
fn update(&self, _: &str, _: &[&str], _: &str) -> String {
String::new()
}
fn delete(&self, _: &str, _: &str) -> String {
String::new()
}
fn create_table(&self, _: &str, _: &[(String, String)]) -> String {
String::new()
}
fn drop_table(&self, _: &str) -> String {
String::new()
}
fn pagination(&self, skip: Option<usize>, take: Option<usize>) -> String {
match (skip, take) {
(Some(s), Some(t)) => format!("LIMIT {} OFFSET {}", t, s),
(None, Some(t)) => format!("LIMIT {}", t),
(Some(s), None) => format!("OFFSET {}", s),
(None, None) => String::new(),
}
}
fn parameter_placeholder(&self, _: usize) -> String {
"?".to_string()
}
fn quote_identifier(&self, identifier: &str) -> String {
format!("\"{}\"", identifier)
}
fn auto_increment_syntax(&self) -> &'static str {
"AUTOINCREMENT"
}
}
pub(crate) fn convert_aggregate_cell<V>(rows: Vec<Vec<DbValue>>) -> EFResult<Option<V>>
where
V: TryFrom<DbValue, Error = DbValueConvertError>,
{
match rows.first().and_then(|r| r.first()) {
Some(DbValue::Null) => Ok(None),
Some(cell) => V::try_from(cell.clone())
.map(Some)
.map_err(crate::error::EFError::from),
None => Ok(None),
}
}
pub(crate) fn compile_bool_expr(
expr: &BoolExpr,
gen: &dyn crate::provider::ISqlGenerator,
param_idx: &mut usize,
) -> String {
match expr {
BoolExpr::Filter(f) => {
let placeholders: Vec<String> = (0..f.param_count())
.map(|i| gen.parameter_placeholder(*param_idx + i))
.collect();
*param_idx += f.param_count();
f.to_sql(&placeholders)
}
BoolExpr::Raw(raw) => raw.0.clone(),
BoolExpr::And(a, b) => format!(
"({}) AND ({})",
compile_bool_expr(a, gen, param_idx),
compile_bool_expr(b, gen, param_idx)
),
BoolExpr::Or(a, b) => format!(
"({}) OR ({})",
compile_bool_expr(a, gen, param_idx),
compile_bool_expr(b, gen, param_idx)
),
BoolExpr::Not(inner) => format!("NOT ({})", compile_bool_expr(inner, gen, param_idx)),
BoolExpr::Exists(spec) => compile_subquery(spec, gen, param_idx, false),
BoolExpr::NotExists(spec) => compile_subquery(spec, gen, param_idx, true),
BoolExpr::InSubquery(spec) => compile_in_subquery(spec, gen, param_idx, false),
BoolExpr::NotInSubquery(spec) => compile_in_subquery(spec, gen, param_idx, true),
}
}
fn compile_subquery(
spec: &SubquerySpec,
gen: &dyn crate::provider::ISqlGenerator,
param_idx: &mut usize,
negated: bool,
) -> String {
let related_tbl = gen.quote_identifier(&spec.related_table);
let fk_col = gen.quote_identifier(&spec.fk_column);
let outer_tbl = gen.quote_identifier(&spec.outer_table);
let outer_pk = gen.quote_identifier(&spec.outer_pk_column);
let mut sql = if negated {
format!("NOT EXISTS (SELECT 1 FROM {related_tbl} WHERE {related_tbl}.{fk_col} = {outer_tbl}.{outer_pk}")
} else {
format!("EXISTS (SELECT 1 FROM {related_tbl} WHERE {related_tbl}.{fk_col} = {outer_tbl}.{outer_pk}")
};
if let Some(pred) = &spec.predicate {
let pred_sql = compile_bool_expr(pred, gen, param_idx);
sql.push_str(&format!(" AND {pred_sql}"));
}
sql.push(')');
sql
}
fn compile_in_subquery(
spec: &InSubquerySpec,
gen: &dyn crate::provider::ISqlGenerator,
param_idx: &mut usize,
negated: bool,
) -> String {
let outer_col = gen.quote_identifier(&spec.outer_column);
let src_tbl = gen.quote_identifier(&spec.source_table);
let proj_col = gen.quote_identifier(&spec.projection_column);
let kw = if negated { "NOT IN" } else { "IN" };
let mut sql = format!("{outer_col} {kw} (SELECT {proj_col} FROM {src_tbl}");
if let Some(pred) = &spec.predicate {
let pred_sql = compile_bool_expr(pred, gen, param_idx);
sql.push_str(&format!(" WHERE {pred_sql}"));
}
sql.push(')');
sql
}
pub(crate) fn collect_bool_expr_values(expr: &BoolExpr) -> Vec<DbValue> {
match expr {
BoolExpr::Filter(f) => f.values().to_vec(),
BoolExpr::Raw(_) => Vec::new(),
BoolExpr::And(a, b) | BoolExpr::Or(a, b) => {
let mut v = collect_bool_expr_values(a);
v.extend(collect_bool_expr_values(b));
v
}
BoolExpr::Not(inner) => collect_bool_expr_values(inner),
BoolExpr::Exists(spec) | BoolExpr::NotExists(spec) => spec
.predicate
.as_ref()
.map(|p| collect_bool_expr_values(p))
.unwrap_or_default(),
BoolExpr::InSubquery(spec) | BoolExpr::NotInSubquery(spec) => spec
.predicate
.as_ref()
.map(|p| collect_bool_expr_values(p))
.unwrap_or_default(),
}
}
pub(crate) fn resolve_subqueries(expr: &mut BoolExpr, outer_meta: &EntityTypeMeta) {
match expr {
BoolExpr::And(a, b) | BoolExpr::Or(a, b) => {
resolve_subqueries(a, outer_meta);
resolve_subqueries(b, outer_meta);
}
BoolExpr::Not(inner) => resolve_subqueries(inner, outer_meta),
BoolExpr::Exists(spec) | BoolExpr::NotExists(spec) => {
resolve_subquery_spec(spec, outer_meta);
if let Some(pred) = &mut spec.predicate {
let _ = pred;
}
}
_ => {}
}
}
pub(crate) fn has_subqueries(expr: &BoolExpr) -> bool {
match expr {
BoolExpr::Exists(_) | BoolExpr::NotExists(_) => true,
BoolExpr::And(a, b) | BoolExpr::Or(a, b) => has_subqueries(a) || has_subqueries(b),
BoolExpr::Not(inner) => has_subqueries(inner),
_ => false,
}
}
fn resolve_subquery_spec(spec: &mut SubquerySpec, outer_meta: &EntityTypeMeta) {
let nav = outer_meta
.navigations
.iter()
.find(|n| n.field_name.as_ref() == spec.navigation_field);
if let Some(nav) = nav {
spec.related_table = nav
.related_table
.as_ref()
.map(|s| s.as_ref().to_string())
.unwrap_or_else(|| spec.related_type_name.clone());
if let Some(fk) = &nav.fk_column {
spec.fk_column = fk.as_ref().to_string();
} else if let Some(fk) = &nav.foreign_key_field {
spec.fk_column = fk.as_ref().to_string();
}
if let Some(pk) = &nav.referenced_key_column {
spec.outer_pk_column = pk.as_ref().to_string();
} else if let Some(pk) = outer_meta.primary_keys.first() {
spec.outer_pk_column = pk.as_ref().to_string();
}
spec.outer_table = outer_meta.table_name.to_string();
}
}
pub(crate) fn filters_to_and_expr(filters: &[FilterCondition]) -> BoolExpr {
filters
.iter()
.cloned()
.map(BoolExpr::Filter)
.reduce(|acc, f| BoolExpr::And(Box::new(acc), Box::new(f)))
.unwrap_or(BoolExpr::raw("1=1"))
}
pub(crate) fn build_where_clauses(
filters: &[FilterCondition],
gen: &dyn crate::provider::ISqlGenerator,
) -> String {
build_where_clause_with_offset(filters, gen, 1)
}
pub(crate) fn build_where_clause_with_offset(
filters: &[FilterCondition],
gen: &dyn crate::provider::ISqlGenerator,
start_index: usize,
) -> String {
if filters.is_empty() {
return String::new();
}
let mut param_idx = start_index;
let clauses: Vec<String> = filters
.iter()
.map(|f| {
let placeholders: Vec<String> = (0..f.param_count())
.map(|i| gen.parameter_placeholder(param_idx + i))
.collect();
param_idx += f.param_count();
f.to_sql(&placeholders)
})
.collect();
clauses.join(" AND ")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn raw_sql_fragment_compiles_unchanged() {
let expr = BoolExpr::raw("is_deleted = 0");
let gen = PortablePlaceholderGenerator;
let mut idx = 1;
let sql = compile_bool_expr(&expr, &gen, &mut idx);
assert_eq!(sql, "is_deleted = 0");
assert_eq!(idx, 1);
}
#[test]
fn filters_to_and_expr_empty_falls_back_to_1eq1() {
let filters: Vec<FilterCondition> = vec![];
let expr = filters_to_and_expr(&filters);
let gen = PortablePlaceholderGenerator;
let mut idx = 1;
let sql = compile_bool_expr(&expr, &gen, &mut idx);
assert_eq!(sql, "1=1");
}
}