use nodedb_types::Value;
use nodedb_types::conversion::json_to_value_display;
use nodedb_types::protocol::NativeResponse;
use crate::control::server::response_shape::types::ShapedRows;
use crate::control::server::shared::ddl::{DdlError, DdlResult};
pub(crate) fn error_to_native(seq: u64, e: &crate::Error) -> NativeResponse {
let (code, message) = match e {
crate::Error::BadRequest { detail } => ("42601", detail.clone()),
crate::Error::RejectedAuthz { resource, .. } => ("42501", resource.clone()),
crate::Error::RateExceeded { .. } => (
nodedb_types::error::sqlstate::TOO_MANY_CONNECTIONS,
format!("{e}"),
),
crate::Error::DeadlineExceeded { .. } => ("57014", "query cancelled due to timeout".into()),
crate::Error::CollectionNotFound { collection, .. } => {
("42P01", format!("collection '{collection}' not found"))
}
crate::Error::CalvinSerializationConflict => (
nodedb_types::error::sqlstate::SERIALIZATION_FAILURE,
format!("{e}"),
),
other => ("XX000", format!("{other}")),
};
NativeResponse::error(seq, code, message)
}
pub(crate) fn shape_error_to_native(seq: u64, e: &nodedb_types::NodeDbError) -> NativeResponse {
NativeResponse::error(seq, "XX000", e.message().to_string())
}
pub(crate) fn ddl_result_to_native(
seq: u64,
result: Result<Vec<DdlResult>, DdlError>,
) -> NativeResponse {
match result {
Err(DdlError { sqlstate, message }) => NativeResponse::error(seq, sqlstate, message),
Ok(results) => match results.into_iter().next() {
Some(DdlResult::Status { command, .. }) => NativeResponse::status_row(seq, command),
Some(DdlResult::Rows(shaped)) => {
let (columns, rows) = to_native_columns_rows(&shaped);
NativeResponse {
seq,
status: nodedb_types::protocol::ResponseStatus::Ok,
columns: Some(columns),
rows: Some(rows),
rows_affected: None,
watermark_lsn: 0,
error: None,
auth: None,
warnings: Vec::new(),
}
}
Some(DdlResult::Empty) | None => NativeResponse::ok(seq),
},
}
}
pub(crate) fn calvin_native_response(
seq: u64,
apply_result: Option<crate::bridge::envelope::Response>,
returning_plan: Option<&crate::bridge::envelope::PhysicalPlan>,
fallback_affected: u64,
state: &crate::control::state::SharedState,
database_id: nodedb_types::DatabaseId,
tenant_id: nodedb_types::TenantId,
) -> NativeResponse {
use crate::control::server::response_shape::compose::{
ShapeOutcome, shape_response_materialized,
};
use crate::control::server::response_shape::types::{PlanKind, describe_plan};
if let (Some(resp), Some(plan)) = (apply_result.as_ref(), returning_plan)
&& matches!(describe_plan(plan), PlanKind::ReturningRows)
&& let Ok(ShapeOutcome::Rows(shaped)) = shape_response_materialized(
resp.payload.as_bytes(),
plan,
PlanKind::ReturningRows,
None,
state,
database_id,
tenant_id,
)
{
let (cols, rows) = to_native_columns_rows(&shaped);
let mut r = NativeResponse::ok(seq);
r.watermark_lsn = resp.watermark_lsn.as_u64();
if !cols.is_empty() {
r.columns = Some(cols);
}
r.rows = Some(rows);
return r;
}
let mut r = NativeResponse::ok(seq);
if let Some(resp) = &apply_result {
r.watermark_lsn = resp.watermark_lsn.as_u64();
r.rows_affected = Some(
crate::control::server::shared::sql::staging_predicates::extract_affected_count(
resp.payload.as_bytes(),
)
.unwrap_or(fallback_affected),
);
} else {
r.rows_affected = Some(fallback_affected);
}
r
}
pub(crate) fn to_native_columns_rows(shaped: &ShapedRows) -> (Vec<String>, Vec<Vec<Value>>) {
let rows = shaped
.rows
.iter()
.map(|row| {
shaped
.columns
.iter()
.map(|col| {
row.get(col.as_str())
.map(json_to_value_display)
.unwrap_or(Value::Null)
})
.collect()
})
.collect();
(shaped.columns.clone(), rows)
}