use nodedb_array::types::ArrayId;
use nodedb_cluster::distributed_array::wire::{ArrayShardDeleteReq, ArrayShardPutReq};
use nodedb_cluster::error::{ClusterError, Result};
use super::executor::{DataPlaneArrayExecutor, LOCAL_DISPATCH_VSHARD};
use crate::control::server::dispatch_utils::{
ChangeFeedOwner, SubmitOutcome, SubmitWrite, WalDurability, WriteOrdering, submit_write,
};
use crate::types::{DatabaseId, TraceId, VShardId};
use nodedb_physical::physical_plan::{ArrayOp, PhysicalPlan};
impl DataPlaneArrayExecutor {
pub(super) async fn put(&self, req: &ArrayShardPutReq) -> Result<u64> {
let array_id: ArrayId =
zerompk::from_msgpack(&req.array_id_msgpack).map_err(|e| ClusterError::Codec {
detail: format!("array_id decode in exec_put: {e}"),
})?;
let cell_blobs: Vec<Vec<u8>> =
zerompk::from_msgpack(&req.cells_msgpack).map_err(|e| ClusterError::Codec {
detail: format!("cell blob-vec decode in exec_put: {e}"),
})?;
let cells: Vec<crate::engine::array::wal::ArrayPutCell> = cell_blobs
.iter()
.map(|blob| {
zerompk::from_msgpack(blob).map_err(|e| ClusterError::Codec {
detail: format!("ArrayPutCell decode in exec_put: {e}"),
})
})
.collect::<Result<Vec<_>>>()?;
let cells_msgpack = zerompk::to_msgpack_vec(&cells).map_err(|e| ClusterError::Codec {
detail: format!("cells re-encode in exec_put: {e}"),
})?;
let plan = PhysicalPlan::Array(ArrayOp::Put {
array_id: array_id.clone(),
cells_msgpack,
wal_lsn: req.wal_lsn,
provenance: None,
});
self.propose_or_dispatch(
&array_id,
plan,
req.representative_hilbert_prefix,
req.prefix_bits,
req.wal_lsn,
"array put",
)
.await
}
pub(super) async fn delete(&self, req: &ArrayShardDeleteReq) -> Result<u64> {
let array_id: ArrayId =
zerompk::from_msgpack(&req.array_id_msgpack).map_err(|e| ClusterError::Codec {
detail: format!("array_id decode in exec_delete: {e}"),
})?;
let plan = PhysicalPlan::Array(ArrayOp::Delete {
array_id: array_id.clone(),
coords_msgpack: req.coords_msgpack.clone(),
wal_lsn: req.wal_lsn,
provenance: None,
});
self.propose_or_dispatch(
&array_id,
plan,
req.representative_hilbert_prefix,
req.prefix_bits,
req.wal_lsn,
"array delete",
)
.await
}
async fn propose_or_dispatch(
&self,
array_id: &ArrayId,
plan: PhysicalPlan,
representative_hilbert_prefix: u64,
prefix_bits: u8,
wal_lsn: u64,
op_label: &str,
) -> Result<u64> {
if let Some(proposer) = self.state.async_raft_proposer.get() {
let vshard = derive_vshard(array_id, representative_hilbert_prefix, prefix_bits)?;
let entry = crate::control::wal_replication::to_replicated_entry(
array_id.tenant_id,
DatabaseId::DEFAULT,
VShardId::new(vshard),
&plan,
)
.ok_or_else(|| ClusterError::Storage {
detail: format!("{op_label}: plan is not encodable as a replicated entry"),
})?;
crate::control::wal_replication::propose_replicated_entry(&self.state, proposer, entry)
.await
.map_err(|e| ClusterError::Storage {
detail: format!("{op_label} raft propose: {e}"),
})?;
return Ok(wal_lsn);
}
let outcome: SubmitOutcome = submit_write(
&self.state,
SubmitWrite {
tenant_id: array_id.tenant_id,
database_id: DatabaseId::DEFAULT,
vshard_id: LOCAL_DISPATCH_VSHARD,
plan,
trace_id: TraceId::generate(),
event_source: crate::event::EventSource::User,
txn_id: None,
user_id: None,
durability: WalDurability::AppendHere { now_override: None },
ordering: WriteOrdering::Gate,
change_feed: ChangeFeedOwner::Funnel,
},
)
.await
.map_err(|e| ClusterError::Storage {
detail: format!("{op_label}: {e}"),
})?;
if outcome.response.status == crate::bridge::envelope::Status::Error {
let detail = outcome
.response
.error_code
.as_ref()
.map(|c| format!("{c:?}"))
.unwrap_or_else(|| "unknown Data Plane error".into());
return Err(ClusterError::Storage {
detail: format!("{op_label} Data Plane error: {detail}"),
});
}
outcome
.wal_lsn
.map(|lsn| lsn.as_u64())
.ok_or_else(|| ClusterError::Storage {
detail: format!(
"{op_label}: applied with no WAL redo record — write is not durable"
),
})
}
}
fn derive_vshard(
array_id: &ArrayId,
representative_hilbert_prefix: u64,
prefix_bits: u8,
) -> Result<u32> {
if prefix_bits == 0 {
return Ok(nodedb_cluster::array_routing::array_vshard_for_name(
&array_id.name,
));
}
nodedb_cluster::distributed_array::routing::array_vshard_for_tile(
representative_hilbert_prefix,
prefix_bits,
)
.map_err(|e| ClusterError::Storage {
detail: format!("array vshard derive: {e}"),
})
}