use std::sync::Arc;
use futures::StreamExt;
use nodedb_cluster::rpc_codec::{ExecuteRequest, RaftRpc};
use tracing::debug;
use crate::Error;
use crate::control::server::result_stream::{ResultStream, RowBatch};
use crate::control::state::SharedState;
use crate::types::{DatabaseId, Lsn, TenantId, TraceId, TxnId, VShardId};
use nodedb_physical::physical_plan::wire as plan_wire;
use super::dispatcher::{DispatchOutcome, map_typed_cluster_error};
use super::version_set::GatewayVersionSet;
pub(super) struct RemoteDispatchArgs<'a> {
pub plan: nodedb_physical::physical_plan::PhysicalPlan,
pub shared: &'a Arc<SharedState>,
pub node_id: u64,
pub vshard_id: u64,
pub tenant_id: TenantId,
pub database_id: DatabaseId,
pub trace_id: TraceId,
pub deadline_ms: u64,
pub version_set: &'a GatewayVersionSet,
pub txn_id: Option<TxnId>,
}
pub(super) async fn dispatch_remote(
args: RemoteDispatchArgs<'_>,
) -> Result<DispatchOutcome, Error> {
let RemoteDispatchArgs {
plan,
shared,
node_id,
vshard_id,
tenant_id,
database_id,
trace_id,
deadline_ms,
version_set,
txn_id,
} = args;
let transport = shared.cluster_transport.as_ref().ok_or(Error::Internal {
detail: "gateway: cluster transport not available for remote dispatch".into(),
})?;
let plan = match Box::pin(crate::control::server::exchange::resolve_exchange_in_plan(
shared,
database_id,
tenant_id,
plan,
trace_id,
None,
))
.await?
{
crate::control::server::exchange::Resolved::Gathered(
resp,
shard_watermarks,
_shuffle_reads,
) => {
return Ok(DispatchOutcome {
payloads: vec![resp.payload.to_vec()],
shard_watermarks,
read_version_lsn: resp.read_version_lsn,
});
}
crate::control::server::exchange::Resolved::Plan(p) => p,
crate::control::server::exchange::Resolved::Stream(s) => {
let (merged, lsn) = crate::control::server::result_stream::materialize(s).await?;
return Ok(DispatchOutcome {
payloads: vec![merged],
shard_watermarks: vec![(VShardId::new(vshard_id as u32), lsn)],
read_version_lsn: Lsn::ZERO,
});
}
};
let plan_bytes = plan_wire::encode(&plan).map_err(|e| Error::Internal {
detail: format!("gateway: plan encode failed: {e}"),
})?;
let descriptor_versions: Vec<nodedb_cluster::rpc_codec::DescriptorVersionEntry> = version_set
.iter()
.map(
|(name, version)| nodedb_cluster::rpc_codec::DescriptorVersionEntry {
collection: name.clone(),
version: *version,
},
)
.collect();
let req = RaftRpc::ExecuteRequest(ExecuteRequest {
plan_bytes,
tenant_id: tenant_id.as_u64(),
database_id: database_id.as_u64(),
deadline_remaining_ms: deadline_ms,
trace_id: trace_id.0,
descriptor_versions,
txn_id,
});
debug!(
node_id,
vshard_id,
tenant_id = tenant_id.as_u64(),
"gateway: dispatching ExecuteRequest to remote node"
);
let resp_rpc = transport.send_rpc(node_id, req).await.map_err(|e| {
Error::NotLeader {
vshard_id: VShardId::new((vshard_id % VShardId::COUNT as u64) as u32),
leader_node: 0,
leader_addr: format!("node-{node_id} (transport error: {e})"),
}
})?;
match resp_rpc {
RaftRpc::ExecuteResponse(resp) => {
if let Some(err) = resp.error {
Err(map_typed_cluster_error(err, vshard_id))
} else {
Ok(DispatchOutcome {
shard_watermarks: vec![(
VShardId::new(vshard_id as u32),
Lsn::new(resp.watermark_lsn),
)],
payloads: resp.payloads,
read_version_lsn: Lsn::new(resp.read_version_lsn),
})
}
}
other => Err(Error::Internal {
detail: format!("gateway: unexpected RPC response variant: {other:?}"),
}),
}
}
pub(super) async fn dispatch_remote_stream(
args: RemoteDispatchArgs<'_>,
) -> Result<ResultStream, Error> {
let RemoteDispatchArgs {
plan,
shared,
node_id,
vshard_id,
tenant_id,
database_id,
trace_id,
deadline_ms,
version_set,
txn_id,
} = args;
let transport = shared.cluster_transport.as_ref().ok_or(Error::Internal {
detail: "gateway: cluster transport not available for remote stream dispatch".into(),
})?;
let plan = match Box::pin(crate::control::server::exchange::resolve_exchange_in_plan(
shared,
database_id,
tenant_id,
plan,
trace_id,
None,
))
.await?
{
crate::control::server::exchange::Resolved::Plan(p) => p,
crate::control::server::exchange::Resolved::Gathered(
resp,
_shard_watermarks,
_shuffle_reads,
) => {
let batch = RowBatch {
payload: resp.payload.to_vec(),
watermark_lsn: resp.watermark_lsn,
read_version_lsn: resp.read_version_lsn,
};
return Ok(Box::pin(futures::stream::once(async move { Ok(batch) })));
}
crate::control::server::exchange::Resolved::Stream(s) => return Ok(s),
};
let plan_bytes = plan_wire::encode(&plan).map_err(|e| Error::Internal {
detail: format!("gateway: plan encode failed: {e}"),
})?;
let descriptor_versions: Vec<nodedb_cluster::rpc_codec::DescriptorVersionEntry> = version_set
.iter()
.map(
|(name, version)| nodedb_cluster::rpc_codec::DescriptorVersionEntry {
collection: name.clone(),
version: *version,
},
)
.collect();
let req = RaftRpc::ExecuteStreamRequest(ExecuteRequest {
plan_bytes,
tenant_id: tenant_id.as_u64(),
database_id: database_id.as_u64(),
deadline_remaining_ms: deadline_ms,
trace_id: trace_id.0,
descriptor_versions,
txn_id,
});
debug!(
node_id,
vshard_id,
tenant_id = tenant_id.as_u64(),
"gateway: dispatching ExecuteStreamRequest to remote node"
);
let stream = transport
.send_rpc_stream(node_id, req)
.await
.map_err(|e| Error::NotLeader {
vshard_id: VShardId::new((vshard_id % VShardId::COUNT as u64) as u32),
leader_node: 0,
leader_addr: format!("node-{node_id} (stream open error: {e})"),
})?;
let mut stream = Box::pin(stream);
let first = match stream.next().await {
Some(Ok((payload, lsn))) => RowBatch {
payload,
watermark_lsn: Lsn::new(lsn),
read_version_lsn: Lsn::ZERO,
},
Some(Err(e)) => return Err(map_stream_cluster_error(e, vshard_id)),
None => return Ok(Box::pin(futures::stream::empty())),
};
let rest = stream.map(move |item| match item {
Ok((payload, lsn)) => Ok(RowBatch {
payload,
watermark_lsn: Lsn::new(lsn),
read_version_lsn: Lsn::ZERO,
}),
Err(e) => Err(Error::Dispatch {
detail: format!("remote stream terminal error: {e}"),
}),
});
let head = futures::stream::once(async move { Ok(first) });
Ok(Box::pin(head.chain(rest)))
}
fn map_stream_cluster_error(err: nodedb_cluster::ClusterError, vshard_id: u64) -> Error {
match err {
nodedb_cluster::ClusterError::StreamTerminal { error, .. } => {
map_typed_cluster_error(error, vshard_id)
}
other => Error::NotLeader {
vshard_id: VShardId::new((vshard_id % VShardId::COUNT as u64) as u32),
leader_node: 0,
leader_addr: format!("stream dispatch error: {other}"),
},
}
}