use serde_json::Value;
use crate::dialect::filter::build_filter;
use crate::dialect::ir::SqlStmt;
use crate::dialect::Backend;
use crate::schema::Schema;
use super::upsert::translate_upsert;
use super::{col_map, q, returning_cols, returning_shape, tname, Binder};
pub(super) fn build_assignments(
binder: &mut Binder,
schema: &Schema,
update: &Value,
present_qualifier: Option<&str>,
) -> Result<Vec<String>, String> {
let Some(obj) = update.as_object() else {
return Err("update 必须是对象".to_string());
};
if obj.is_empty() {
return Err("没有提供要更新的字段".to_string());
}
let mut assigns: Vec<String> = Vec::new();
let mut set_fields: Vec<String> = Vec::new();
let mut unset_fields: Vec<String> = Vec::new();
for (op, val) in obj {
match op.as_str() {
"$set" | "$inc" | "$unset" => {
op_assignments(
binder,
schema,
op,
val,
&mut assigns,
&mut set_fields,
&mut unset_fields,
)?;
}
"$setOnInsert" => continue,
_ => {
return Err(format!(
"translate: 不支持的操作符 {}(写路径仅支持 $set/$inc/$unset)",
op
))
}
}
}
if let Some(present) = present_expr(
binder.backend,
&set_fields,
&unset_fields,
present_qualifier,
) {
assigns.push(present);
}
Ok(assigns)
}
fn present_expr(
backend: Backend,
set_fields: &[String],
unset_fields: &[String],
qualifier: Option<&str>,
) -> Option<String> {
if set_fields.is_empty() && unset_fields.is_empty() {
return None;
}
let col = q(backend, "__present");
let col_ref = match qualifier {
Some(tbl) => format!("{}.{}", tbl, col),
None => col.clone(),
};
let mut expr = format!("COALESCE({}, ',')", col_ref);
for k in unset_fields {
expr = format!("REPLACE({}, ',{},,', ',')", expr, k);
}
for k in set_fields {
let token = format!(",{},", k);
expr = match backend {
Backend::Mysql => format!("CONCAT({}, '{}')", expr, token),
_ => format!("{} || '{}'", expr, token),
};
}
Some(format!("{} = {}", col, expr))
}
fn op_assignments(
binder: &mut Binder,
schema: &Schema,
op: &str,
val: &Value,
assigns: &mut Vec<String>,
set_fields: &mut Vec<String>,
unset_fields: &mut Vec<String>,
) -> Result<(), String> {
let Some(fields) = val.as_object() else {
return Ok(());
};
for (k, v) in fields {
let Some(col) = super::writable_col(schema, k) else {
continue;
};
let ftype = schema.fields.get(&col).map(|f| f.field_type.as_str());
if op == "$inc" && matches!(ftype, Some("object") | Some("array") | Some("vector")) {
return Err(format!(
"字段 {k} 的类型为 {},不支持 $inc(仅数值字段可自增)",
ftype.unwrap_or("")
));
}
let qc = binder.backend.pcol(&col);
match op {
"$set" => {
if v.is_null() {
continue;
}
let ph = super::bind_col(binder, schema, &col, v)?;
assigns.push(format!("{} = {}", qc, ph));
if !set_fields.contains(&col) {
set_fields.push(col);
}
}
"$inc" => {
let ph = binder.bind(v.clone());
assigns.push(format!("{} = COALESCE({}, 0) + {}", qc, qc, ph));
if !set_fields.contains(&col) {
set_fields.push(col);
}
}
"$unset" => {
assigns.push(format!("{} = NULL", qc));
if !unset_fields.contains(&col) {
unset_fields.push(col);
}
}
_ => {}
}
}
Ok(())
}
pub(super) fn where_of(
binder: &mut Binder,
schema: &Schema,
filter: &Value,
) -> Result<String, String> {
let mut seq = binder.seq();
let wh = build_filter(
filter,
binder.backend,
"t",
&col_map(schema),
&mut seq,
None,
)?;
binder.params.extend(wh.params);
Ok(if wh.text.is_empty() {
String::new()
} else {
format!(" WHERE {}", wh.text)
})
}
pub(super) fn translate_update_many(
backend: Backend,
schema: &Schema,
filter: &Value,
update: &Value,
) -> Result<Vec<SqlStmt>, String> {
let mut binder = Binder::new(backend);
let assigns = build_assignments(&mut binder, schema, update, None)?;
if assigns.is_empty() {
return Err("没有提供要更新的字段".to_string());
}
let where_sql = where_of(&mut binder, schema, filter)?;
let text = format!(
"UPDATE {} AS t SET {}{}",
tname(backend, schema),
assigns.join(", "),
where_sql,
);
Ok(vec![SqlStmt::write(text, binder.params)])
}
pub(super) fn translate_find_one_and_update(
backend: Backend,
schema: &Schema,
filter: &Value,
update: &Value,
options: &Value,
) -> Result<Vec<SqlStmt>, String> {
let upsert = options
.get("upsert")
.and_then(|v| v.as_bool())
.unwrap_or(false);
if upsert {
return translate_upsert(backend, schema, filter, update);
}
let mut binder = Binder::new(backend);
let assigns = build_assignments(&mut binder, schema, update, None)?;
if assigns.is_empty() {
return Err("没有提供要更新的字段".to_string());
}
let where_sql = where_of(&mut binder, schema, filter)?;
let update_text = format!(
"UPDATE {} AS t SET {}{}",
tname(backend, schema),
assigns.join(", "),
where_sql,
);
let cols = returning_cols(schema);
Ok(if backend.supports_returning() {
vec![write_with_returning(
backend,
schema,
update_text,
&cols,
binder.params,
)]
} else {
vec![
SqlStmt::write(update_text, binder.params),
read_back(backend, schema, filter, &cols)?,
]
})
}
pub(super) fn write_with_returning(
backend: Backend,
schema: &Schema,
write_text: String,
cols: &[String],
params: Vec<Value>,
) -> SqlStmt {
let ret = cols
.iter()
.map(|c| backend.pcol(c))
.collect::<Vec<_>>()
.join(", ");
let mut stmt = SqlStmt::write(format!("{} RETURNING {}", write_text, ret), params);
stmt.is_write = true;
stmt.row_shape = Some(returning_shape(schema, cols));
stmt.returning = cols.to_vec();
stmt
}
pub(super) fn read_back(
backend: Backend,
schema: &Schema,
filter: &Value,
cols: &[String],
) -> Result<SqlStmt, String> {
let select_list = cols
.iter()
.map(|c| format!("t.{}", backend.pcol(c)))
.collect::<Vec<_>>()
.join(", ");
let mut read_binder = Binder::new(backend);
let where_sql = where_of(&mut read_binder, schema, filter)?;
let text = format!(
"SELECT {} FROM {} t{}",
select_list,
tname(backend, schema),
where_sql,
);
let mut stmt = SqlStmt::write(text, read_binder.params);
stmt.is_write = false;
stmt.row_shape = Some(returning_shape(schema, cols));
Ok(stmt)
}