use std::collections::HashMap;
use serde_json::{Map, Value};
use super::ir::{RowCol, RowShape};
pub fn restore_rows(shape: &RowShape, rows: &[Value]) -> Value {
let mut index: HashMap<Value, usize> = HashMap::new();
let mut buckets: Vec<Vec<&Value>> = Vec::new();
for (i, row) in rows.iter().enumerate() {
let key = root_key(shape, row).unwrap_or_else(|| Value::from(i as u64));
match index.get(&key) {
Some(&idx) => buckets[idx].push(row),
None => {
index.insert(key, buckets.len());
buckets.push(vec![row]);
}
}
}
let cols: Vec<&RowCol> = shape.columns.iter().collect();
let docs: Vec<Value> = buckets
.into_iter()
.map(|group| {
let present = shape
.present_alias
.as_deref()
.and_then(|a| row_val(group[0], a).as_str())
.map(|s| s.to_owned());
Value::Object(build_object(&cols, &group, 0, present.as_deref()))
})
.collect();
Value::Array(docs)
}
fn build_object(
cols: &[&RowCol],
rows: &[&Value],
path_len: usize,
present: Option<&str>,
) -> Map<String, Value> {
let mut obj = Map::new();
for c in cols {
if c.json_path.len() == path_len + 1 || (!c.is_array && c.json_path.len() > path_len + 1) {
let mut val = row_val(rows[0], &c.alias).clone();
if c.is_json {
val = parse_json_col(val);
}
merge_path(
&mut obj,
&c.json_path[path_len..],
val,
present,
c.always,
c.is_bool,
);
}
}
let mut rels: Vec<String> = Vec::new();
for c in cols {
if c.is_array && c.json_path.len() > path_len + 1 {
let r = &c.json_path[path_len];
if !rels.iter().any(|x| x == r) {
rels.push(r.clone());
}
}
}
for r in rels {
let sub: Vec<&RowCol> = cols
.iter()
.copied()
.filter(|c| {
c.is_array && c.json_path.len() > path_len + 1 && c.json_path[path_len] == r
})
.collect();
obj.insert(r.clone(), relation_value(&sub, rows, path_len));
}
obj
}
fn relation_value(cols: &[&RowCol], rows: &[&Value], path_len: usize) -> Value {
let one = cols
.first()
.and_then(|c| c.ones.get(path_len).copied())
.unwrap_or_else(|| cols.first().map(|c| c.one).unwrap_or(false));
let id_col = cols
.iter()
.find(|c| c.json_path.len() == path_len + 2 && c.json_path[path_len + 1] == "_id");
let mut order: Vec<String> = Vec::new();
let mut groups: HashMap<String, Vec<&Value>> = HashMap::new();
for row in rows {
if cols.iter().all(|c| row_val(row, &c.alias).is_null()) {
continue;
}
let key = match id_col.map(|c| row_val(row, &c.alias)) {
Some(v) if !v.is_null() => v.to_string(),
_ => format!("\u{0}{}", order.len()),
};
if !groups.contains_key(&key) {
order.push(key.clone());
}
groups.entry(key).or_default().push(row);
}
let docs: Vec<Value> = order
.into_iter()
.map(|k| Value::Object(build_object(cols, &groups[&k], path_len + 1, None)))
.collect();
if one {
docs.into_iter().next().unwrap_or(Value::Null)
} else {
Value::Array(docs)
}
}
fn row_val<'a>(row: &'a Value, alias: &str) -> &'a Value {
match row {
Value::Object(map) => map.get(alias).unwrap_or(&Value::Null),
_ => &Value::Null,
}
}
fn root_key(shape: &RowShape, row: &Value) -> Option<Value> {
let id_alias = shape
.columns
.iter()
.find(|c| !c.is_array && c.json_path.len() == 1 && c.json_path[0] == "_id")?
.alias
.clone();
row.get(&id_alias).cloned()
}
fn merge_path(
obj: &mut Map<String, Value>,
path: &[String],
val: Value,
present: Option<&str>,
always: bool,
is_bool: bool,
) {
let val = if is_bool { to_json_bool(val) } else { val };
if path.len() <= 1 {
if let Some(k) = path.first() {
if always || !val.is_null() || present_contains(present, k) {
obj.insert(k.clone(), val);
}
}
return;
}
let head = path[0].clone();
let entry = obj
.entry(head.clone())
.or_insert_with(|| Value::Object(Map::new()));
if let Value::Object(m) = entry {
merge_path(m, &path[1..], val, present, always, false);
}
}
fn parse_json_col(v: Value) -> Value {
match v {
Value::String(s) => serde_json::from_str::<Value>(&s).unwrap_or(Value::String(s)),
other => other,
}
}
fn to_json_bool(v: Value) -> Value {
let as_num = match &v {
Value::Number(n) => n.as_f64(),
Value::String(s) => s.parse::<f64>().ok(),
_ => None,
};
match as_num {
Some(1.0) => Value::Bool(true),
Some(0.0) => Value::Bool(false),
_ => v,
}
}
fn present_contains(present: Option<&str>, key: &str) -> bool {
match present {
Some(s) => s.contains(&format!(",{},", key)),
None => false,
}
}
pub fn restore_rows_json(shape: &Value, rows: &Value) -> Result<Value, String> {
let shape = RowShape::from_value(shape)?;
let rows = match rows {
Value::Array(a) => a.clone(),
_ => Vec::new(),
};
Ok(restore_rows(&shape, &rows))
}
impl RowShape {
pub fn from_value(v: &Value) -> Result<RowShape, String> {
let obj = v
.get("columns")
.and_then(|c| c.as_array())
.ok_or("rowShape 缺少 columns")?;
let mut columns = Vec::new();
for c in obj {
let alias = c
.get("alias")
.and_then(|a| a.as_str())
.ok_or("列缺 alias")?
.to_string();
let path: Vec<String> = c
.get("path")
.and_then(|p| p.as_array())
.map(|arr| {
arr.iter()
.filter_map(|s| s.as_str().map(String::from))
.collect()
})
.unwrap_or_default();
let is_array = c.get("isArray").and_then(|b| b.as_bool()).unwrap_or(false);
let one = c.get("one").and_then(|b| b.as_bool()).unwrap_or(false);
let ones: Vec<bool> = match c.get("ones").and_then(|o| o.as_array()) {
Some(a) => a.iter().map(|b| b.as_bool().unwrap_or(false)).collect(),
None if is_array => vec![one],
None => Vec::new(),
};
let sub_shape = c
.get("subShape")
.and_then(|s| {
if s.is_null() {
None
} else {
Some(RowShape::from_value(s))
}
})
.transpose()?;
let always = c.get("always").and_then(|b| b.as_bool()).unwrap_or(false);
let is_bool = c.get("bool").and_then(|b| b.as_bool()).unwrap_or(false);
let is_json = c.get("json").and_then(|b| b.as_bool()).unwrap_or(false);
columns.push(RowCol {
alias,
json_path: path,
is_array,
one,
ones,
sub_shape,
always,
is_bool,
is_json,
});
}
let present_alias = v
.get("present")
.and_then(|p| p.as_str())
.filter(|s| !s.is_empty())
.map(|s| s.to_string());
Ok(RowShape {
columns,
present_alias,
})
}
}