use serde_json::{json, Map, Value};
use crate::dialect::filter::{build_filter, build_filter_raw, Warnings, WhereClause};
use crate::dialect::{scalar_column, Backend, ColumnRef};
use crate::pipeline::{merge_and, nested_rel_name_from_as, rel_name_from_as};
use crate::schema::{Registry, Schema};
use super::aggregate::lookup_extra_condition;
use super::{col_fn, count_field_pattern, tname};
pub(super) struct SqlNested {
pub as_name: String,
pub rel_name: String,
pub model: String,
pub local_field: String,
pub foreign_field: String,
pub extra: Option<Value>,
pub filter: Option<Value>,
}
pub(super) struct SqlRelPredicate {
pub as_name: String,
pub rel_name: String,
pub model: String,
pub local_field: String,
pub foreign_field: String,
pub extra: Option<Value>,
pub nested: Vec<SqlNested>,
pub having: Value,
pub aggs: Vec<(String, String)>,
}
pub(super) fn parse(
lo: &Value,
root_schema: &Schema,
registry: &Registry,
backend: Backend,
) -> Result<SqlRelPredicate, String> {
let as_name = lo
.get("as")
.and_then(|v| v.as_str())
.ok_or("关系聚合谓词 $lookup 缺少 as")?
.to_string();
let rel_name = rel_name_from_as(&as_name)
.ok_or_else(|| format!("关系聚合谓词 $lookup.as \"{as_name}\" 形态非法"))?
.to_string();
let rel_def = root_schema.relations.get(&rel_name).ok_or_else(|| {
format!(
"关系聚合谓词引用的关系 \"{rel_name}\" 未在 schema \"{}\" 中定义",
root_schema.name
)
})?;
let rel_schema = registry.get(&rel_def.model)?;
let pipeline: Vec<Value> = lo
.get("pipeline")
.and_then(|v| v.as_array())
.cloned()
.unwrap_or_default();
let group_idx = pipeline
.iter()
.position(|s| s.get("$group").is_some())
.ok_or("关系聚合谓词 $lookup 子 pipeline 缺少 $group")?;
let mut nested: Vec<SqlNested> = Vec::new();
for stage in &pipeline[..group_idx] {
let Some(n_lo) = stage.get("$lookup") else {
continue;
};
let n_as = n_lo.get("as").and_then(|v| v.as_str()).unwrap_or("");
let n_rel = nested_rel_name_from_as(n_as).ok_or_else(|| {
format!(
"关系聚合谓词内层 $lookup.as \"{n_as}\" 形态非法(应为 __rn{{序号}}__{{关系名}})"
)
})?;
let n_rel_def = rel_schema.relations.get(n_rel).ok_or_else(|| {
format!(
"关系聚合谓词嵌套关系 \"{n_rel}\" 未在 schema \"{}\" 中定义",
rel_schema.name
)
})?;
let n_pipeline: Vec<Value> = n_lo
.get("pipeline")
.and_then(|v| v.as_array())
.cloned()
.unwrap_or_default();
nested.push(SqlNested {
as_name: n_as.to_string(),
rel_name: n_rel.to_string(),
model: n_rel_def.model.clone(),
local_field: n_rel_def.local_field.clone(),
foreign_field: n_rel_def.foreign_field.clone(),
extra: lookup_extra_condition(&n_pipeline),
filter: None,
});
}
let extra = split_nested_keys(lookup_extra_condition(&pipeline[..group_idx]), &mut nested)?;
let group = pipeline[group_idx]
.get("$group")
.ok_or("关系聚合谓词 $group 阶段缺失(内部不变量破裂)")?;
let having = pipeline
.iter()
.skip_while(|s| s.get("$group").is_none())
.skip(1)
.find_map(|s| s.get("$match"))
.cloned()
.ok_or("关系聚合谓词 $lookup 子 pipeline 缺少 $group 之后的 $match(having)")?;
let mut aggs: Vec<(String, String)> = Vec::new();
let gmap = group
.as_object()
.ok_or("关系聚合谓词 $group 阶段必须是对象")?;
for (alias, acc) in gmap {
if alias == "_id" {
continue;
}
aggs.push((alias.clone(), acc_to_sql(backend, rel_schema, acc)?));
}
if aggs.is_empty() {
return Err("关系聚合谓词 $group 没有聚合列".to_string());
}
Ok(SqlRelPredicate {
as_name,
rel_name,
model: rel_def.model.clone(),
local_field: rel_def.local_field.clone(),
foreign_field: rel_def.foreign_field.clone(),
extra,
nested,
having,
aggs,
})
}
fn split_nested_keys(
extra: Option<Value>,
nested: &mut [SqlNested],
) -> Result<Option<Value>, String> {
let Some(e) = extra else {
return Ok(None);
};
let mut leaves: Vec<Value> = Vec::new();
flatten_and(e, &mut leaves);
let mut scalar: Vec<Value> = Vec::new();
for leaf in leaves {
let hit = leaf.as_object().filter(|m| m.len() == 1).and_then(|m| {
let (k, v) = m.iter().next()?;
let i = nested
.iter()
.position(|n| k.starts_with(&format!("{}.", n.as_name)))?;
Some((i, k.clone(), v.clone()))
});
match hit {
Some((i, key, val)) => {
let rest = key[nested[i].as_name.len() + 1..].to_string();
let mut cm = Map::new();
cm.insert(rest, val);
let cond = Value::Object(cm);
nested[i].filter = Some(match nested[i].filter.take() {
Some(prev) => merge_and(&prev, &cond),
None => cond,
});
}
None => {
if let Some(bad) = find_nested_key(&leaf, nested) {
return Err(format!(
"嵌套关系条件 \"{bad}\" 出现在逻辑组($or/$nor)内,SQL 下推无法与 Mongo 语义\
逐字节对齐(内部不变量破裂)"
));
}
scalar.push(leaf);
}
}
}
Ok(match scalar.len() {
0 => None,
1 => Some(scalar.remove(0)),
_ => Some(json!({ "$and": scalar })),
})
}
fn flatten_and(v: Value, out: &mut Vec<Value>) {
if let Some(m) = v.as_object().filter(|m| m.len() == 1) {
if let Some(arr) = m.get("$and").and_then(|a| a.as_array()) {
for it in arr {
flatten_and(it.clone(), out);
}
return;
}
}
out.push(v);
}
fn find_nested_key(v: &Value, nested: &[SqlNested]) -> Option<String> {
match v {
Value::Object(m) => m.iter().find_map(|(k, val)| {
if nested
.iter()
.any(|n| k.starts_with(&format!("{}.", n.as_name)))
{
Some(k.clone())
} else {
find_nested_key(val, nested)
}
}),
Value::Array(a) => a.iter().find_map(|it| find_nested_key(it, nested)),
_ => None,
}
}
fn acc_to_sql(backend: Backend, rel_schema: &Schema, acc: &Value) -> Result<String, String> {
let o = acc
.as_object()
.ok_or("关系聚合谓词累积器必须恰有一个算子键")?;
let mut it = o.iter();
let (op, arg) = match (it.next(), it.next()) {
(Some(kv), None) => kv,
_ => return Err("关系聚合谓词累积器必须恰有一个算子键(内部不变量破裂)".to_string()),
};
let col = |f: &str| -> Result<String, String> {
let f = f.trim_start_matches('$');
let c = scalar_column(rel_schema, f)
.ok_or_else(|| format!("关系聚合谓词聚合字段 \"{f}\" 无法映射到标量列"))?;
Ok(format!("c.{}", backend.pcol(&c)))
};
match op.as_str() {
"$sum" => {
if arg.as_i64() == Some(1) {
return Ok("COUNT(*)".to_string());
}
if let Some(f) = arg.as_str() {
return Ok(format!("SUM({})", col(f)?));
}
if let Some(f) = count_field_pattern(arg) {
return Ok(format!("COUNT({})", col(&f)?));
}
Err(format!("关系聚合谓词无法翻译 $sum 累积器 {arg}"))
}
"$avg" | "$min" | "$max" => {
let f = arg
.as_str()
.ok_or_else(|| format!("关系聚合谓词 {op} 累积器必须带字段引用"))?;
Ok(if op == "$avg" {
format!("AVG(CAST({} AS {}))", col(f)?, backend.double_type())
} else {
let fn_name = if op == "$min" { "MIN" } else { "MAX" };
format!("{}({})", fn_name, col(f)?)
})
}
other => Err(format!(
"关系聚合谓词不支持的累积器 {other}(白名单 $count/$sum/$avg/$min/$max)"
)),
}
}
#[allow(clippy::too_many_arguments)]
pub(super) fn exists_clause(
backend: Backend,
root_alias: &str,
root_schema: &Schema,
registry: &Registry,
p: &SqlRelPredicate,
negated: bool,
param_seq: &mut usize,
mut warnings: Warnings,
) -> Result<WhereClause, String> {
let rel_schema = registry.get(&p.model)?;
let local_col = scalar_column(root_schema, &p.local_field).ok_or_else(|| {
format!(
"关系聚合谓词 \"{}\" 的本地键 \"{}\" 无法映射到根表标量列",
p.rel_name, p.local_field
)
})?;
let fk_col = scalar_column(rel_schema, &p.foreign_field).ok_or_else(|| {
format!(
"关系聚合谓词 \"{}\" 的外键 \"{}\" 无法映射到子表标量列",
p.rel_name, p.foreign_field
)
})?;
let mut params: Vec<Value> = Vec::new();
let mut extra_sql = String::new();
if let Some(extra) = &p.extra {
let wh = build_filter(
extra,
backend,
"c",
&col_fn(rel_schema),
param_seq,
warnings.as_deref_mut(),
)?;
if !wh.text.is_empty() {
extra_sql = format!(" AND {}", wh.text);
params.extend(wh.params);
}
}
let mut nested_sql = String::new();
for (i, n) in p.nested.iter().enumerate() {
let n_schema = registry.get(&n.model)?;
let n_local_col = scalar_column(rel_schema, &n.local_field).ok_or_else(|| {
format!(
"关系聚合谓词 \"{}\" 的嵌套关系 \"{}\" 本地键 \"{}\" 无法映射到子表标量列",
p.rel_name, n.rel_name, n.local_field
)
})?;
let n_fk_col = scalar_column(n_schema, &n.foreign_field).ok_or_else(|| {
format!(
"关系聚合谓词 \"{}\" 的嵌套关系 \"{}\" 外键 \"{}\" 无法映射到孙表标量列",
p.rel_name, n.rel_name, n.foreign_field
)
})?;
let alias = format!("n{i}");
let mut conds: Vec<String> = vec![format!(
"{}.{} = c.{}",
alias,
backend.pcol(&n_fk_col),
backend.pcol(&n_local_col)
)];
if let Some(ne) = &n.extra {
let wh = build_filter(
ne,
backend,
&alias,
&col_fn(n_schema),
param_seq,
warnings.as_deref_mut(),
)?;
if !wh.text.is_empty() {
conds.push(wh.text);
params.extend(wh.params);
}
}
if let Some(nf) = &n.filter {
let wh = build_filter(
nf,
backend,
&alias,
&col_fn(n_schema),
param_seq,
warnings.as_deref_mut(),
)?;
if !wh.text.is_empty() {
conds.push(wh.text);
params.extend(wh.params);
}
}
nested_sql.push_str(&format!(
" AND EXISTS (SELECT 1 FROM {} {} WHERE {})",
tname(backend, n_schema),
alias,
conds.join(" AND ")
));
}
let expr_of = |name: &str| -> Option<ColumnRef> {
p.aggs
.iter()
.find(|(a, _)| a == name)
.map(|(_, e)| ColumnRef::Scalar(e.clone()))
};
let hw = build_filter_raw(&p.having, backend, &expr_of, param_seq, warnings)?;
if hw.text.is_empty() {
return Err(format!(
"关系聚合谓词 \"{}\" 的 having 未翻译出任何条件(规划与翻译口径不一致)",
p.rel_name
));
}
params.extend(hw.params);
let having_sql = hw.text;
let text = format!(
"{}EXISTS (SELECT 1 FROM {} c WHERE c.{} = {}.{}{}{} GROUP BY c.{} HAVING {})",
if negated { "NOT " } else { "" },
tname(backend, rel_schema),
backend.pcol(&fk_col),
root_alias,
backend.pcol(&local_col),
extra_sql,
nested_sql,
backend.pcol(&fk_col),
having_sql,
);
Ok(WhereClause { text, params })
}