use serde_json::{json, Map, Value};
use super::Backend;
pub fn schema_def_from_rows(rows: &Value) -> Result<Value, String> {
let tables = rows
.get("tables")
.and_then(|t| t.as_array())
.cloned()
.unwrap_or_default();
let columns = rows
.get("columns")
.and_then(|c| c.as_array())
.cloned()
.unwrap_or_default();
let fks = rows
.get("fks")
.and_then(|c| c.as_array())
.cloned()
.unwrap_or_default();
let _indexes = rows
.get("indexes")
.and_then(|c| c.as_array())
.cloned()
.unwrap_or_default();
let mut defs: Vec<Value> = Vec::new();
for t in &tables {
let name = t
.get("name")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
if name.is_empty() {
continue;
}
let table_cols: Vec<&Value> = columns
.iter()
.filter(|c| c.get("table").and_then(|v| v.as_str()) == Some(name.as_str()))
.collect();
let table_fks: Vec<&Value> = fks
.iter()
.filter(|f| f.get("table").and_then(|v| v.as_str()) == Some(name.as_str()))
.collect();
let mut fields_map = Map::new();
let mut relations_map = Map::new();
for c in &table_cols {
let col = c
.get("name")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let db_type = c
.get("type")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_uppercase();
let required = c.get("notnull").and_then(|v| v.as_i64()).unwrap_or(0) != 0;
let is_pk = c.get("pk").and_then(|v| v.as_i64()).unwrap_or(0) != 0;
if is_pk {
fields_map.insert(
"_id".to_string(),
json!({ "type": "string", "required": true }),
);
continue;
}
fields_map.insert(
col.clone(),
json!({ "type": db_field_type(&col, &db_type), "required": required }),
);
}
for f in &table_fks {
let col = f
.get("column")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let ref_table = f
.get("refTable")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let ref_column = f
.get("refColumn")
.and_then(|v| v.as_str())
.unwrap_or("_id")
.to_string();
if col.is_empty() || ref_table.is_empty() {
continue;
}
relations_map.insert(
ref_table.clone(),
json!({
"model": ref_table,
"type": "one",
"localField": col,
"foreignField": ref_column,
}),
);
relations_map.insert(
format!("__rev_{}", ref_table),
json!({
"model": name,
"type": "many",
"localField": ref_column,
"foreignField": col,
}),
);
}
let def = json!({
"name": name,
"collection": name,
"idPrefix": "",
"timestamps": false,
"fields": Value::Object(fields_map),
"relations": Value::Object(relations_map),
});
defs.push(def);
}
let mut by_name: Map<String, Value> = Map::new();
for d in &defs {
let n = d
.get("name")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
by_name.insert(n, d.clone());
}
let mut rev_rels: Vec<(String, String, Value)> = Vec::new(); for d in &defs {
let n = d
.get("name")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let rels = d.get("relations").and_then(|r| r.as_object());
if let Some(rels) = rels {
for (k, v) in rels {
if let Some(t) = k.strip_prefix("__rev_").map(String::from) {
rev_rels.push((t, n.clone(), v.clone()));
}
}
}
}
for (target, rel_name, rel_def) in rev_rels {
if let Some(d) = by_name.get_mut(&target) {
let rels = d.get_mut("relations").and_then(|r| r.as_object_mut());
if let Some(rels) = rels {
rels.insert(rel_name, rel_def);
}
}
}
let mut names: Vec<String> = by_name.keys().cloned().collect();
names.sort();
let out: Vec<Value> = names
.into_iter()
.map(|n| by_name.get(&n).cloned().unwrap_or(Value::Null))
.map(|mut d| {
if let Some(rels) = d.get_mut("relations").and_then(|r| r.as_object_mut()) {
rels.retain(|k, _| !k.starts_with("__rev_"));
}
d
})
.collect();
Ok(Value::Array(out))
}
fn db_field_type(col: &str, db_type: &str) -> &'static str {
if col.ends_with("_object") || db_type.contains("JSON") || db_type.contains("OBJECT") {
return "object";
}
if col.ends_with("_list") || db_type.contains("ARRAY") {
return "array";
}
match db_type {
t if t.contains("INT")
|| t.contains("NUMERIC")
|| t.contains("DECIMAL")
|| t.contains("REAL")
|| t.contains("FLOAT")
|| t.contains("DOUBLE") =>
{
"number"
}
t if t.contains("BOOL") => "boolean",
t if t.contains("DATE") || t.contains("TIME") || t.contains("TIMESTAMP") => "date",
_ => "string",
}
}
pub fn introspect_to_schema_json(rows: &Value, _backend: &Backend) -> Result<Value, String> {
schema_def_from_rows(rows)
}