use serde_json::{json, Value};
use crate::schema::{Registry, Schema};
use super::filter::build_filter;
use super::ir::{RowCol, RowShape, SqlStmt};
use super::{field_is_bool, physical_of, Backend};
mod insert;
mod update;
mod upsert;
pub fn translate_write(
backend: Backend,
cmd: &Value,
registry: &Registry,
) -> Result<Vec<SqlStmt>, String> {
let kind = cmd.get("kind").and_then(|v| v.as_str()).unwrap_or("");
let collection = cmd.get("collection").and_then(|v| v.as_str()).unwrap_or("");
let source = cmd
.get("source")
.and_then(|v| v.as_str())
.unwrap_or(crate::datasource::DEFAULT_SOURCE);
let database = cmd.get("database").and_then(|v| v.as_str());
let schema_q = cmd.get("schema").and_then(|v| v.as_str());
let mut schema = registry
.get_for_command(source, database, schema_q, collection)?
.clone();
schema.database = database.filter(|s| !s.is_empty()).map(String::from);
schema.schema = schema_q.filter(|s| !s.is_empty()).map(String::from);
let schema = &schema;
match kind {
"insertOne" => {
let doc = cmd.get("doc").cloned().unwrap_or(json!({}));
Ok(vec![insert::build_insert(backend, schema, &doc)])
}
"insertMany" => {
let docs = cmd
.get("docs")
.and_then(|v| v.as_array())
.cloned()
.unwrap_or_default();
if docs.is_empty() {
return Err("insertMany 无文档".to_string());
}
let upsert_by_id = cmd
.get("upsertById")
.and_then(|v| v.as_bool())
.unwrap_or(false);
insert::build_insert_many(backend, schema, &docs, upsert_by_id)
}
"updateMany" => {
let filter = cmd.get("filter").cloned().unwrap_or(json!({}));
let update = cmd.get("update").cloned().unwrap_or(json!({}));
update::translate_update_many(backend, schema, &filter, &update)
}
"findOneAndUpdate" => {
let filter = cmd.get("filter").cloned().unwrap_or(json!({}));
let update = cmd.get("update").cloned().unwrap_or(json!({}));
let options = cmd.get("options").cloned().unwrap_or(json!({}));
update::translate_find_one_and_update(backend, schema, &filter, &update, &options)
}
"deleteMany" => {
let filter = cmd.get("filter").cloned().unwrap_or(json!({}));
let mut seq = 0usize;
let wh = build_filter(&filter, backend, "t", &col_map(schema), &mut seq, None)?;
let where_sql = if wh.text.is_empty() {
String::new()
} else {
format!(" WHERE {}", wh.text)
};
let text = format!("DELETE FROM {} AS t{}", tname(backend, schema), where_sql);
Ok(vec![SqlStmt::write(text, wh.params)])
}
_ => Err(format!("translate: 未知写命令 kind = {}", kind)),
}
}
struct Binder {
backend: Backend,
params: Vec<Value>,
}
impl Binder {
fn new(backend: Backend) -> Self {
Binder {
backend,
params: Vec::new(),
}
}
fn bind(&mut self, v: Value) -> String {
let ph = self.backend.placeholder(self.params.len());
self.params.push(v);
ph
}
fn seq(&self) -> usize {
self.params.len()
}
}
fn scalar_col(schema: &Schema, field: &str) -> Option<String> {
super::scalar_column(schema, field)
}
fn writable_col(_schema: &Schema, field: &str) -> Option<String> {
if field.contains('.') {
return None;
}
Some(field.to_string())
}
pub(super) fn bind_value(schema: &Schema, col: &str, v: &Value) -> Value {
let is_json = matches!(
schema.fields.get(col).map(|f| f.field_type.as_str()),
Some("object") | Some("array")
);
if is_json && !v.is_null() {
Value::String(serde_json::to_string(v).unwrap_or_default())
} else {
v.clone()
}
}
fn col_map(schema: &Schema) -> impl Fn(&str) -> Option<super::ColumnRef> + '_ {
move |field: &str| super::field_column_ref(schema, field)
}
fn returning_cols(schema: &Schema) -> Vec<String> {
let mut cols: Vec<String> = schema
.fields
.keys()
.filter(|f| f.as_str() != "_id")
.cloned()
.collect();
cols.sort();
cols.insert(0, "_id".to_string());
cols.push("__present".to_string());
cols
}
fn returning_shape(schema: &Schema, cols: &[String]) -> RowShape {
RowShape {
columns: cols
.iter()
.filter(|c| c.as_str() != "__present")
.map(|c| {
let is_json = matches!(
schema.fields.get(c.as_str()).map(|f| f.field_type.as_str()),
Some("object") | Some("array")
);
if is_json {
RowCol::json(&physical_of(c), &[c.as_str()])
} else {
RowCol::scalar_bool(&physical_of(c), &[c.as_str()], field_is_bool(schema, c))
}
})
.collect(),
present_alias: Some("__present".to_string()),
}
}
fn q(backend: Backend, ident: &str) -> String {
backend.quote_ident(ident)
}
fn tname(backend: Backend, schema: &Schema) -> String {
backend.qualified_table(schema.database(), schema.schema(), &schema.collection)
}