Skip to main content

rust_store_core/dialect/
row.rs

1//! 平铺 JOIN 行 → 嵌套 Mongo 文档还原
2
3use std::collections::HashMap;
4
5use serde_json::{Map, Value};
6
7use super::ir::{RowCol, RowShape};
8
9/// 把一组平铺行(`Vec<Map<String, Value>>`)按 row_shape 还原为嵌套 Mongo 文档数组。
10///
11/// 规则:
12/// - 标量列(`is_array=false`)写入其 `json_path` 指定的嵌套位置;
13/// - 关系列(`is_array=true`)按 `json_path` 逐级归并:每一级按该关系的主键分组去重,
14///   基数由 `ones[level]` 决定(`true`→对象或 `null`,`false`→数组)。
15pub fn restore_rows(shape: &RowShape, rows: &[Value]) -> Value {
16    // 先按「根标量键」分组,同根聚为一篇文档;无根键列(如 `$group` 分组结果,
17    // 其输出没有 `_id` 列)→ 每行独立成篇(分组结果每行本就是一个文档)。
18    // 用 HashMap 索引根键 → 桶下标(`buckets` 保持插入顺序),避免线性 `find` 的 O(行数 × 根数)。
19    let mut index: HashMap<Value, usize> = HashMap::new();
20    let mut buckets: Vec<Vec<&Value>> = Vec::new();
21    for (i, row) in rows.iter().enumerate() {
22        let key = root_key(shape, row).unwrap_or_else(|| Value::from(i as u64));
23        match index.get(&key) {
24            Some(&idx) => buckets[idx].push(row),
25            None => {
26                index.insert(key, buckets.len());
27                buckets.push(vec![row]);
28            }
29        }
30    }
31
32    let cols: Vec<&RowCol> = shape.columns.iter().collect();
33    let docs: Vec<Value> = buckets
34        .into_iter()
35        .map(|group| {
36            // 每行「显式存在字段集合」(`__present` 哨兵,缺失 vs null 三态);
37            // 仅作用于根层标量(哨兵列来自根表)
38            let present = shape
39                .present_alias
40                .as_deref()
41                .and_then(|a| row_val(group[0], a).as_str())
42                .map(|s| s.to_owned());
43            Value::Object(build_object(&cols, &group, 0, present.as_deref()))
44        })
45        .collect();
46    Value::Array(docs)
47}
48
49/// 由若干平铺行构造**一层**对象:
50/// - `path_len` 是本层的 `json_path` 前缀长度(根 = 0,`lessons` 内 = 1,…);
51/// - 标量列(`json_path.len() == path_len + 1`,或**非关系列**的多段点号路径,
52///   如 `$group` 的 by 键 `meta.level`)取首行值写入(点号路径还原为嵌套对象);
53/// - 更深的**关系列**(`is_array`,`json_path` 更深)= 本级关系,按关系名分组递归(`relation_value`)。
54fn build_object(
55    cols: &[&RowCol],
56    rows: &[&Value],
57    path_len: usize,
58    present: Option<&str>,
59) -> Map<String, Value> {
60    let mut obj = Map::new();
61    for c in cols {
62        if c.json_path.len() == path_len + 1 || (!c.is_array && c.json_path.len() > path_len + 1) {
63            let mut val = row_val(rows[0], &c.alias).clone();
64            // JSON 列(object/array 单列存 JSON 文本):先解析回嵌套值再落位
65            if c.is_json {
66                val = parse_json_col(val);
67            }
68            merge_path(
69                &mut obj,
70                &c.json_path[path_len..],
71                val,
72                present,
73                c.always,
74                c.is_bool,
75            );
76        }
77    }
78    // 本级关系名(仅关系列 `is_array`,保留出现顺序)
79    let mut rels: Vec<String> = Vec::new();
80    for c in cols {
81        if c.is_array && c.json_path.len() > path_len + 1 {
82            let r = &c.json_path[path_len];
83            if !rels.iter().any(|x| x == r) {
84                rels.push(r.clone());
85            }
86        }
87    }
88    for r in rels {
89        let sub: Vec<&RowCol> = cols
90            .iter()
91            .copied()
92            .filter(|c| {
93                c.is_array && c.json_path.len() > path_len + 1 && c.json_path[path_len] == r
94            })
95            .collect();
96        obj.insert(r.clone(), relation_value(&sub, rows, path_len));
97    }
98    obj
99}
100
101/// 还原 `path_len` 这一级的关系值:按该关系主键分组去重后递归构造子文档。
102/// 基数 `ones[path_len]`:`true` → 至多一条(对象或 `null`),`false` → 数组。
103fn relation_value(cols: &[&RowCol], rows: &[&Value], path_len: usize) -> Value {
104    let one = cols
105        .first()
106        .and_then(|c| c.ones.get(path_len).copied())
107        .unwrap_or_else(|| cols.first().map(|c| c.one).unwrap_or(false));
108    let id_col = cols
109        .iter()
110        .find(|c| c.json_path.len() == path_len + 2 && c.json_path[path_len + 1] == "_id");
111    // 每个 SQL 行 = 一个 (父, 子) 对;多关系 LEFT JOIN 会形成笛卡尔积,同一子行重复出现。
112    // 按子文档主键分组(Mongo `$lookup` 每个子文档只出现一次),无主键列时退化为逐行独立。
113    let mut order: Vec<String> = Vec::new();
114    let mut groups: HashMap<String, Vec<&Value>> = HashMap::new();
115    for row in rows {
116        // 该关系在本行完全缺失(LEFT JOIN 全 NULL)→ 不是子文档
117        if cols.iter().all(|c| row_val(row, &c.alias).is_null()) {
118            continue;
119        }
120        let key = match id_col.map(|c| row_val(row, &c.alias)) {
121            Some(v) if !v.is_null() => v.to_string(),
122            _ => format!("\u{0}{}", order.len()),
123        };
124        if !groups.contains_key(&key) {
125            order.push(key.clone());
126        }
127        groups.entry(key).or_default().push(row);
128    }
129    let docs: Vec<Value> = order
130        .into_iter()
131        .map(|k| Value::Object(build_object(cols, &groups[&k], path_len + 1, None)))
132        .collect();
133    if one {
134        docs.into_iter().next().unwrap_or(Value::Null)
135    } else {
136        Value::Array(docs)
137    }
138}
139
140/// 从行取值(按 alias 直接取列)
141fn row_val<'a>(row: &'a Value, alias: &str) -> &'a Value {
142    match row {
143        Value::Object(map) => map.get(alias).unwrap_or(&Value::Null),
144        _ => &Value::Null,
145    }
146}
147
148/// 根分组键:根表 `_id` 标量列(`json_path == ["_id"]`)。无该列(如 `$group`
149/// 分组结果)→ `None`,调用方按行独立成篇。
150fn root_key(shape: &RowShape, row: &Value) -> Option<Value> {
151    let id_alias = shape
152        .columns
153        .iter()
154        .find(|c| !c.is_array && c.json_path.len() == 1 && c.json_path[0] == "_id")?
155        .alias
156        .clone();
157    row.get(&id_alias).cloned()
158}
159
160/// 把 value 写到 obj 的嵌套路径(创建中间 object)
161///
162/// 缺失 vs null 三态(F-07/H-01):标量值为 null 时,仅当字段在该行「显式存在集合」
163/// (`present`)中才写入 `key: null`,否则(字段缺失)不产出该键 —— 对齐 Mongo
164/// 「显式 null 有键、缺失无键」。`present == None` 表示本语句未查哨兵列(count/关系聚合),
165/// 退化为旧语义:null 一律不产出键。
166///
167/// `always`(§9.2(2) 归一聚合计算列):为 `true` 时即使 `null` 也强制写入,
168/// 与 Mongo `$addFields` 对空集聚合产出显式 `null`(§9.7)逐行对齐。
169///
170/// `is_bool`(§9.7 布尔归一):为 `true` 时先把 SQL 的 `0/1` 归一为 JSON `bool`,
171/// 使「同一逻辑布尔字段」在 MySQL/SQLite(`TINYINT(1)` / `INTEGER`)与
172/// PostgreSQL(原生 `BOOLEAN`)、MongoDB(`true/false`)间**类型一致**。
173fn merge_path(
174    obj: &mut Map<String, Value>,
175    path: &[String],
176    val: Value,
177    present: Option<&str>,
178    always: bool,
179    is_bool: bool,
180) {
181    let val = if is_bool { to_json_bool(val) } else { val };
182    if path.len() <= 1 {
183        if let Some(k) = path.first() {
184            if always || !val.is_null() || present_contains(present, k) {
185                obj.insert(k.clone(), val);
186            }
187        }
188        return;
189    }
190    let head = path[0].clone();
191    let entry = obj
192        .entry(head.clone())
193        .or_insert_with(|| Value::Object(Map::new()));
194    if let Value::Object(m) = entry {
195        merge_path(m, &path[1..], val, present, always, false);
196    }
197}
198
199/// JSON 列还原:列值为 JSON 文本字符串 → 解析为嵌套值;已是 JSON 值(部分驱动直接
200/// 反序列化)或解析失败 → 原样返回(绝不静默把数据改判为 null)。
201fn parse_json_col(v: Value) -> Value {
202    match v {
203        Value::String(s) => serde_json::from_str::<Value>(&s).unwrap_or(Value::String(s)),
204        other => other,
205    }
206}
207
208/// §9.7 布尔归一:`1/0`(数值或数值字符串)→ `true/false`;其余(含 `null`、已是 bool、
209/// PG 原生 bool)原样透传 —— 绝不把非 0/1 的值静默改判为布尔。
210fn to_json_bool(v: Value) -> Value {
211    let as_num = match &v {
212        Value::Number(n) => n.as_f64(),
213        Value::String(s) => s.parse::<f64>().ok(),
214        _ => None,
215    };
216    match as_num {
217        Some(1.0) => Value::Bool(true),
218        Some(0.0) => Value::Bool(false),
219        _ => v,
220    }
221}
222
223/// `present` 集合形如 `,field1,field2,`,判定 `key` 是否显式存在(用 `,key,` 匹配)
224fn present_contains(present: Option<&str>, key: &str) -> bool {
225    match present {
226        Some(s) => s.contains(&format!(",{},", key)),
227        None => false,
228    }
229}
230
231/// 便捷序列化:把 `rowShape + rows` 两步合成一次调用(绑定层常用)
232pub fn restore_rows_json(shape: &Value, rows: &Value) -> Result<Value, String> {
233    let shape = RowShape::from_value(shape)?;
234    let rows = match rows {
235        Value::Array(a) => a.clone(),
236        _ => Vec::new(),
237    };
238    Ok(restore_rows(&shape, &rows))
239}
240
241impl RowShape {
242    /// 从绑定层传入的 JSON 解析(与 `to_value` 对称)
243    pub fn from_value(v: &Value) -> Result<RowShape, String> {
244        let obj = v
245            .get("columns")
246            .and_then(|c| c.as_array())
247            .ok_or("rowShape 缺少 columns")?;
248        let mut columns = Vec::new();
249        for c in obj {
250            let alias = c
251                .get("alias")
252                .and_then(|a| a.as_str())
253                .ok_or("列缺 alias")?
254                .to_string();
255            let path: Vec<String> = c
256                .get("path")
257                .and_then(|p| p.as_array())
258                .map(|arr| {
259                    arr.iter()
260                        .filter_map(|s| s.as_str().map(String::from))
261                        .collect()
262                })
263                .unwrap_or_default();
264            let is_array = c.get("isArray").and_then(|b| b.as_bool()).unwrap_or(false);
265            let one = c.get("one").and_then(|b| b.as_bool()).unwrap_or(false);
266            // 每级关系基数:新契约显式携带 `ones`;旧 shape 缺省时按顶层 `one` 退化
267            let ones: Vec<bool> = match c.get("ones").and_then(|o| o.as_array()) {
268                Some(a) => a.iter().map(|b| b.as_bool().unwrap_or(false)).collect(),
269                None if is_array => vec![one],
270                None => Vec::new(),
271            };
272            let sub_shape = c
273                .get("subShape")
274                .and_then(|s| {
275                    if s.is_null() {
276                        None
277                    } else {
278                        Some(RowShape::from_value(s))
279                    }
280                })
281                .transpose()?;
282            let always = c.get("always").and_then(|b| b.as_bool()).unwrap_or(false);
283            let is_bool = c.get("bool").and_then(|b| b.as_bool()).unwrap_or(false);
284            let is_json = c.get("json").and_then(|b| b.as_bool()).unwrap_or(false);
285            columns.push(RowCol {
286                alias,
287                json_path: path,
288                is_array,
289                one,
290                ones,
291                sub_shape,
292                always,
293                is_bool,
294                is_json,
295            });
296        }
297        let present_alias = v
298            .get("present")
299            .and_then(|p| p.as_str())
300            .filter(|s| !s.is_empty())
301            .map(|s| s.to_string());
302        Ok(RowShape {
303            columns,
304            present_alias,
305        })
306    }
307}