use serde_json::{Map, Value as JsonValue};
use crate::bridge::envelope::PhysicalPlan;
use crate::control::server::response_translate::dispatch::translate_search_response;
use crate::control::state::SharedState;
use crate::data::executor::response_codec::{
ArraySliceResponse, RowsPayload, decode_payload_to_json,
};
use nodedb_types::columnar::schema::is_reserved_bitemporal_column;
use nodedb_types::{DatabaseId, NodeDbError, TenantId};
use super::kv::apply_kv_wrap;
use super::project::push_flat_rows;
use super::schema::OutputSchema;
use super::types::{DdlColType, PlanKind, ShapedRows};
const TRUNCATED_BEFORE_HORIZON_NOTICE: &str = "AS OF SYSTEM TIME cutoff is older than the oldest retained tile version; \
results may be incomplete";
pub enum ShapeOutcome {
Rows(ShapedRows),
Passthrough,
}
pub fn shape_response_materialized(
payload: &[u8],
plan: &PhysicalPlan,
plan_kind: PlanKind,
projection: Option<&OutputSchema>,
state: &SharedState,
database_id: DatabaseId,
tenant_id: TenantId,
) -> Result<ShapeOutcome, NodeDbError> {
match plan_kind {
PlanKind::Execution | PlanKind::DmlResult(_) => return Ok(ShapeOutcome::Passthrough),
PlanKind::ArraySlice
| PlanKind::ReturningRows
| PlanKind::SingleDocument
| PlanKind::MultiRow => {}
}
let wrapped = apply_kv_wrap(plan, payload);
let translated = translate_search_response(&wrapped, plan, state, database_id, tenant_id);
let shaped = match plan_kind {
PlanKind::ArraySlice => shape_array_slice(&translated),
PlanKind::ReturningRows => shape_returning_rows(&translated),
PlanKind::SingleDocument | PlanKind::MultiRow => {
shape_generic_rows(&translated, projection)
}
PlanKind::Execution | PlanKind::DmlResult(_) => return Ok(ShapeOutcome::Passthrough),
};
Ok(ShapeOutcome::Rows(shaped))
}
pub fn shape_payload_no_plan(
payload: &[u8],
plan_kind: PlanKind,
projection: Option<&OutputSchema>,
) -> ShapeOutcome {
match plan_kind {
PlanKind::Execution | PlanKind::DmlResult(_) => ShapeOutcome::Passthrough,
PlanKind::ArraySlice => ShapeOutcome::Rows(shape_array_slice(payload)),
PlanKind::ReturningRows => ShapeOutcome::Rows(shape_returning_rows(payload)),
PlanKind::SingleDocument | PlanKind::MultiRow => {
ShapeOutcome::Rows(shape_generic_rows(payload, projection))
}
}
}
fn shape_array_slice(payload: &[u8]) -> ShapedRows {
if payload.is_empty() {
return empty_shaped();
}
let (rows_json, truncated) =
if let Ok(resp) = zerompk::from_msgpack::<ArraySliceResponse>(payload) {
(
decode_payload_to_json(&resp.rows_msgpack),
resp.truncated_before_horizon,
)
} else {
(decode_payload_to_json(payload), false)
};
let notice = truncated.then(|| TRUNCATED_BEFORE_HORIZON_NOTICE.to_string());
let mut shaped = match sonic_rs::from_str::<JsonValue>(&rows_json) {
Ok(value) => shape_decoded_rows(&value, None),
Err(_) => empty_shaped(),
};
shaped.notice = notice;
shaped
}
fn shape_returning_rows(payload: &[u8]) -> ShapedRows {
if payload.is_empty() {
return single_result_column_empty();
}
match zerompk::from_msgpack::<RowsPayload>(payload) {
Ok(rp) => {
if rp.rows.is_empty() {
let columns = if rp.columns.is_empty() {
vec!["result".to_string()]
} else {
rp.columns
};
let column_types = ShapedRows::text_types(columns.len());
return ShapedRows {
columns,
column_types,
rows: Vec::new(),
notice: None,
};
}
let rows = rp
.rows
.iter()
.map(|row_vals| {
let mut map = Map::new();
for (col, cell) in rp.columns.iter().zip(row_vals.iter()) {
let v = match cell {
Some(s) => JsonValue::String(s.clone()),
None => JsonValue::Null,
};
map.insert(col.clone(), v);
}
map
})
.collect();
let column_types = ShapedRows::text_types(rp.columns.len());
ShapedRows {
columns: rp.columns,
column_types,
rows,
notice: None,
}
}
Err(e) => {
tracing::warn!(
error = %e,
payload_len = payload.len(),
"ReturningRows msgpack decode failed; falling back to single-column JSON"
);
let text = decode_payload_to_json(payload);
single_result_row(text)
}
}
}
fn shape_generic_rows(payload: &[u8], projection: Option<&OutputSchema>) -> ShapedRows {
if payload.is_empty() {
return empty_shaped();
}
let text = decode_payload_to_json(payload);
match sonic_rs::from_str::<JsonValue>(&text) {
Ok(value) => shape_decoded_rows(&value, projection),
Err(_) => single_result_row(text),
}
}
pub fn shape_decoded_rows(decoded: &JsonValue, projection: Option<&OutputSchema>) -> ShapedRows {
let mut rows = Vec::new();
push_flat_rows(decoded.clone(), &mut rows);
match projection {
Some(s) if !s.is_star && !s.columns.is_empty() => {
let lookup_keys: Vec<String> = s.columns.iter().map(|c| c.lookup_key.clone()).collect();
let display_names: Vec<String> =
s.columns.iter().map(|c| c.display_name.clone()).collect();
let projected_rows = rows
.iter()
.map(|row| project_row(row, &lookup_keys, &display_names))
.collect();
let column_types: Vec<DdlColType> = s.columns.iter().map(|c| c.ty).collect();
ShapedRows {
columns: display_names,
column_types,
rows: projected_rows,
notice: None,
}
}
_ => {
let columns = derive_columns(&rows);
let column_types = ShapedRows::text_types(columns.len());
ShapedRows {
columns,
column_types,
rows,
notice: None,
}
}
}
}
fn project_row(
row: &Map<String, JsonValue>,
lookup_keys: &[String],
display_names: &[String],
) -> Map<String, JsonValue> {
let mut out = Map::new();
for (i, lookup_key) in lookup_keys.iter().enumerate() {
let bare = lookup_key
.rfind('.')
.map(|dot_pos| &lookup_key[dot_pos + 1..])
.unwrap_or(lookup_key.as_str());
let display_name = display_names
.get(i)
.map(String::as_str)
.unwrap_or(lookup_key.as_str());
let value = row
.get(lookup_key.as_str())
.or_else(|| {
if bare != lookup_key {
row.get(bare)
} else {
None
}
})
.or_else(|| {
if display_name != lookup_key.as_str() && display_name != bare {
row.get(display_name)
} else {
None
}
})
.cloned()
.unwrap_or(JsonValue::Null);
out.insert(display_name.to_string(), value);
}
out
}
fn derive_columns(rows: &[Map<String, JsonValue>]) -> Vec<String> {
let mut cols: Vec<String> = Vec::new();
if let Some(first) = rows.first() {
if first.contains_key("id") {
cols.push("id".to_string());
}
for key in first.keys() {
if key != "id" && !is_reserved_bitemporal_column(key) {
cols.push(key.clone());
}
}
}
for row in rows.iter().skip(1) {
for key in row.keys() {
if !is_reserved_bitemporal_column(key) && !cols.contains(key) {
cols.push(key.clone());
}
}
}
cols
}
fn empty_shaped() -> ShapedRows {
ShapedRows {
columns: Vec::new(),
column_types: Vec::new(),
rows: Vec::new(),
notice: None,
}
}
fn single_result_column_empty() -> ShapedRows {
ShapedRows {
columns: vec!["result".to_string()],
column_types: ShapedRows::text_types(1),
rows: Vec::new(),
notice: None,
}
}
fn single_result_row(text: String) -> ShapedRows {
let mut map = Map::new();
map.insert("result".to_string(), JsonValue::String(text));
ShapedRows {
columns: vec!["result".to_string()],
column_types: ShapedRows::text_types(1),
rows: vec![map],
notice: None,
}
}