use super::SqlGenContext;
use crate::filter_spec::{
CONTAINS_SPEC, EQ_SPEC, GT_SPEC, GTE_SPEC, ILIKE_SPEC, IN_SPEC, JSON_GET_SPEC,
JSON_GET_TEXT_SPEC, KEY_EXISTS_SPEC, LIKE_SPEC, LT_SPEC, LTE_SPEC, NE_SPEC,
};
use crate::{FilterArg, QError};
use dibs_query_schema::{
FilterValue, Meta, ParamType, Params, Payload, Span, UpdateValue, ValueExpr, Where,
};
use dibs_sql::{BinOp, ColumnName, Expr, TableName};
fn column_name_to_expr(column: &ColumnName) -> Expr {
let s = column.as_str();
if let Some(dot_pos) = s.find('.') {
let table: TableName = s[..dot_pos].into();
let col: ColumnName = s[dot_pos + 1..].into();
Expr::qualified_column(table, col)
} else {
Expr::column(column.clone())
}
}
fn extract_column_name(column: &ColumnName) -> &str {
let s = column.as_str();
if let Some(dot_pos) = s.rfind('.') {
&s[dot_pos + 1..]
} else {
s
}
}
pub fn meta_string_to_expr(meta: &Meta<String>) -> Expr {
let s = &meta.value;
if let Some(param_name) = s.strip_prefix('$') {
Expr::param(param_name.into())
} else {
if let Ok(n) = s.parse::<i64>() {
Expr::int(n)
} else if s == "true" {
Expr::bool(true)
} else if s == "false" {
Expr::bool(false)
} else {
Expr::string(s)
}
}
}
fn filter_arg_to_expr(arg: &FilterArg) -> Expr {
match arg {
FilterArg::Variable(name) => Expr::param(name.as_str().into()),
FilterArg::Literal(lit) => {
if let Ok(n) = lit.parse::<i64>() {
Expr::int(n)
} else if lit == "true" {
Expr::bool(true)
} else if lit == "false" {
Expr::bool(false)
} else {
Expr::string(lit)
}
}
}
}
pub fn filter_value_to_expr_validated(
ctx: &SqlGenContext,
column: &ColumnName,
filter: &FilterValue,
filter_span: Span,
) -> Result<Option<Expr>, QError> {
let col = column_name_to_expr(column);
let unqualified_name = extract_column_name(column);
match filter {
FilterValue::Null => Ok(Some(col.is_null())),
FilterValue::NotNull => Ok(Some(col.is_not_null())),
FilterValue::Eq(args) => {
let parsed = EQ_SPEC.parse_args(ctx.source.clone(), filter_span, args)?;
let arg = &parsed[0];
Ok(Some(col.eq(filter_arg_to_expr(arg))))
}
FilterValue::EqBare(opt_meta) => {
if let Some(meta) = opt_meta {
let arg = FilterArg::parse(&meta.value);
Ok(Some(col.eq(filter_arg_to_expr(&arg))))
} else {
Ok(Some(col.eq(Expr::param(unqualified_name.into()))))
}
}
FilterValue::Ne(args) => {
let parsed = NE_SPEC.parse_args(ctx.source.clone(), filter_span, args)?;
let arg = &parsed[0];
Ok(Some(Expr::BinOp {
left: Box::new(col),
op: BinOp::Ne,
right: Box::new(filter_arg_to_expr(arg)),
}))
}
FilterValue::Lt(args) => {
let parsed = LT_SPEC.parse_args(ctx.source.clone(), filter_span, args)?;
let arg = &parsed[0];
Ok(Some(Expr::BinOp {
left: Box::new(col),
op: BinOp::Lt,
right: Box::new(filter_arg_to_expr(arg)),
}))
}
FilterValue::Lte(args) => {
let parsed = LTE_SPEC.parse_args(ctx.source.clone(), filter_span, args)?;
let arg = &parsed[0];
Ok(Some(Expr::BinOp {
left: Box::new(col),
op: BinOp::Le,
right: Box::new(filter_arg_to_expr(arg)),
}))
}
FilterValue::Gt(args) => {
let parsed = GT_SPEC.parse_args(ctx.source.clone(), filter_span, args)?;
let arg = &parsed[0];
Ok(Some(Expr::BinOp {
left: Box::new(col),
op: BinOp::Gt,
right: Box::new(filter_arg_to_expr(arg)),
}))
}
FilterValue::Gte(args) => {
let parsed = GTE_SPEC.parse_args(ctx.source.clone(), filter_span, args)?;
let arg = &parsed[0];
Ok(Some(Expr::BinOp {
left: Box::new(col),
op: BinOp::Ge,
right: Box::new(filter_arg_to_expr(arg)),
}))
}
FilterValue::Like(args) => {
let parsed = LIKE_SPEC.parse_args(ctx.source.clone(), filter_span, args)?;
let arg = &parsed[0];
Ok(Some(col.like(filter_arg_to_expr(arg))))
}
FilterValue::Ilike(args) => {
let parsed = ILIKE_SPEC.parse_args(ctx.source.clone(), filter_span, args)?;
let arg = &parsed[0];
Ok(Some(col.ilike(filter_arg_to_expr(arg))))
}
FilterValue::In(args) => {
let parsed = IN_SPEC.parse_args(ctx.source.clone(), filter_span, args)?;
let arg = &parsed[0];
Ok(Some(col.any(filter_arg_to_expr(arg))))
}
FilterValue::JsonGet(args) => {
let parsed = JSON_GET_SPEC.parse_args(ctx.source.clone(), filter_span, args)?;
let arg = &parsed[0];
Ok(Some(col.json_get(filter_arg_to_expr(arg))))
}
FilterValue::JsonGetText(args) => {
let parsed = JSON_GET_TEXT_SPEC.parse_args(ctx.source.clone(), filter_span, args)?;
let arg = &parsed[0];
Ok(Some(col.json_get_text(filter_arg_to_expr(arg))))
}
FilterValue::Contains(args) => {
let parsed = CONTAINS_SPEC.parse_args(ctx.source.clone(), filter_span, args)?;
let arg = &parsed[0];
Ok(Some(col.contains(filter_arg_to_expr(arg))))
}
FilterValue::KeyExists(args) => {
let parsed = KEY_EXISTS_SPEC.parse_args(ctx.source.clone(), filter_span, args)?;
let arg = &parsed[0];
Ok(Some(col.key_exists(filter_arg_to_expr(arg))))
}
}
}
pub fn where_to_expr_validated(
ctx: &SqlGenContext,
where_clause: &Where,
) -> Result<Option<Expr>, QError> {
let mut exprs: Vec<Expr> = vec![];
for (col_meta, filter_value) in &where_clause.filters {
let col_name = &col_meta.value;
if let Some(expr) =
filter_value_to_expr_validated(ctx, col_name, filter_value, col_meta.span)?
{
exprs.push(expr);
}
}
let mut iter = exprs.into_iter();
Ok(iter
.next()
.map(|first| iter.fold(first, |acc, expr| acc.and(expr))))
}
pub fn value_expr_to_expr(
column: &ColumnName,
expr: &Option<ValueExpr>,
params: Option<&Params>,
) -> Expr {
let raw = match expr {
None => {
Expr::param(column.as_str().into())
}
Some(ValueExpr::Default) => Expr::Default,
Some(ValueExpr::Other { tag, content }) => match (tag, content) {
(None, Some(Payload::Scalar(s))) => meta_string_to_expr(s),
(Some(name), None) if name.eq_ignore_ascii_case("null") => Expr::Null,
(Some(name), None) => Expr::FnCall {
name: name.to_uppercase(),
args: vec![],
},
(Some(name), Some(Payload::Seq(args))) => {
let sql_args: Vec<Expr> = args
.iter()
.map(|a| value_expr_to_expr(column, &Some(a.clone()), params))
.collect();
Expr::FnCall {
name: name.to_uppercase(),
args: sql_args,
}
}
(Some(name), Some(Payload::Scalar(s))) => Expr::FnCall {
name: name.to_uppercase(),
args: vec![meta_string_to_expr(s)],
},
(None, None) => Expr::Null,
(None, Some(Payload::Seq(_))) => Expr::Null,
},
};
cast_for_jsonb_param(raw, params)
}
pub fn cast_for_jsonb_param(expr: Expr, params: Option<&Params>) -> Expr {
let Some(params) = params else { return expr };
let Expr::Param(name) = &expr else {
return expr;
};
let is_jsonb = params
.params
.iter()
.find(|(k, _)| k.as_str() == name.as_str())
.is_some_and(|(_, ty)| matches!(ty, ParamType::Jsonb));
if is_jsonb {
expr.cast("text".into()).cast("jsonb".into())
} else {
expr
}
}
pub fn update_value_to_expr(
column: &ColumnName,
expr: &Option<UpdateValue>,
params: Option<&Params>,
) -> Expr {
let raw = match expr {
None => {
Expr::excluded(column.clone())
}
Some(UpdateValue::Default) => Expr::Default,
Some(UpdateValue::Other { tag, content }) => match (tag, content) {
(None, Some(Payload::Scalar(s))) => meta_string_to_expr(s),
(Some(name), None) if name.eq_ignore_ascii_case("null") => Expr::Null,
(Some(name), None) => Expr::FnCall {
name: name.to_uppercase(),
args: vec![],
},
(Some(name), Some(Payload::Seq(args))) => {
let sql_args: Vec<Expr> = args
.iter()
.map(|a| value_expr_to_expr(column, &Some(a.clone()), params))
.collect();
Expr::FnCall {
name: name.to_uppercase(),
args: sql_args,
}
}
(Some(name), Some(Payload::Scalar(s))) => Expr::FnCall {
name: name.to_uppercase(),
args: vec![meta_string_to_expr(s)],
},
(None, None) => Expr::Null,
(None, Some(Payload::Seq(_))) => Expr::Null,
},
};
cast_for_jsonb_param(raw, params)
}