use std::collections::HashMap;
use std::sync::Arc;
use futures::future::join_all;
use crate::bridge::envelope::PhysicalPlan;
use crate::control::gateway::dispatcher::{DispatchRouteParams, dispatch_route};
use crate::control::gateway::version_set::GatewayVersionSet;
use crate::control::gateway::{RouteDecision, TaskRoute};
use crate::control::server::graph_dispatch::match_broadcast::broadcast_match_to_all_cores;
use crate::control::state::SharedState;
use crate::types::{DatabaseId, TenantId, TraceId, TxnId, VShardId};
use nodedb_cluster::distributed_graph::PatternContinuation;
use nodedb_physical::physical_plan::GraphOp;
use crate::control::server::graph_dispatch::cluster_resolve::{gateway_shared, resolve_for_vshard};
use super::coord::{TaggedShardResult, decode_rows};
use super::resume_queue::PendingResume;
use super::round_zero::collect_remote_envelopes;
type DispatchFut<'f> = std::pin::Pin<
Box<dyn std::future::Future<Output = crate::Result<Vec<TaggedShardResult>>> + Send + 'f>,
>;
struct DispatchCtx<'f> {
state: &'f SharedState,
tenant_id: TenantId,
database_id: DatabaseId,
deadline_ms: u64,
txn_id: Option<TxnId>,
shared_arc: Arc<SharedState>,
version_set: GatewayVersionSet,
}
fn push_dispatch_fut<'f>(
futs: &mut Vec<DispatchFut<'f>>,
ctx: &DispatchCtx<'f>,
plan: PhysicalPlan,
remote_coords: Option<(u64, u64)>,
) {
let (state, tenant_id, database_id, deadline_ms, txn_id) = (
ctx.state,
ctx.tenant_id,
ctx.database_id,
ctx.deadline_ms,
ctx.txn_id,
);
match remote_coords {
None => {
futs.push(Box::pin(async move {
let outcome = broadcast_match_to_all_cores(
state,
tenant_id,
database_id,
plan,
TraceId::ZERO,
txn_id,
)
.await?;
Ok::<_, crate::Error>(vec![TaggedShardResult {
emitting_node: state.node_id,
rows: decode_rows(&outcome.rows_payload)?,
frontier: outcome.frontier,
resume: outcome.resume,
}])
}));
}
Some((node_id, vshard_id)) => {
let route = TaskRoute {
plan,
decision: RouteDecision::Remote { node_id, vshard_id },
vshard_id: (vshard_id % VShardId::COUNT as u64) as u32,
};
let shared_arc = ctx.shared_arc.clone();
let version_set = ctx.version_set.clone();
futs.push(Box::pin(async move {
let payloads = dispatch_route(DispatchRouteParams {
route,
shared: &shared_arc,
tenant_id,
database_id,
trace_id: TraceId::ZERO,
deadline_ms,
version_set: &version_set,
txn_id,
})
.await?
.payloads;
collect_remote_envelopes(node_id, payloads)
}));
}
}
}
pub(super) async fn dispatch_continuations(
state: &SharedState,
tenant_id: TenantId,
database_id: DatabaseId,
query_bytes: &[u8],
deadline_ms: u64,
txn_id: Option<TxnId>,
pending: HashMap<u32, Vec<PatternContinuation>>,
) -> crate::Result<Vec<TaggedShardResult>> {
let shared_arc = gateway_shared(state)?;
let ctx = DispatchCtx {
state,
tenant_id,
database_id,
deadline_ms,
txn_id,
shared_arc,
version_set: GatewayVersionSet::from_pairs(Vec::new()),
};
let mut futs: Vec<DispatchFut<'_>> = Vec::new();
for (target_shard, conts) in pending {
let decision = resolve_for_vshard(state, target_shard);
let remote_coords: Option<(u64, u64)> = match decision {
RouteDecision::LeaderUnknown { vshard_id } => {
return Err(crate::Error::NotLeader {
vshard_id: VShardId::new((vshard_id % VShardId::COUNT as u64) as u32),
leader_node: 0,
leader_addr: String::new(),
});
}
RouteDecision::Broadcast { .. } => {
return Err(crate::Error::Internal {
detail: "match scatter: resolve_decision returned Broadcast \
for a single vShard"
.into(),
});
}
RouteDecision::Local => None,
RouteDecision::Remote { node_id, vshard_id } => Some((node_id, vshard_id)),
};
for cont in conts {
let partial_row = zerompk::to_msgpack_vec(&cont.bindings).map_err(|e| {
crate::Error::Serialization {
format: "msgpack".into(),
detail: format!("continuation partial_row: {e}"),
}
})?;
let plan = PhysicalPlan::Graph(GraphOp::MatchContinuation {
query: query_bytes.to_vec(),
resume_triple_idx: cont.next_triple_idx,
partial_row,
source_node: cont.start_node.clone(),
source_binding: cont.start_binding.clone(),
});
push_dispatch_fut(&mut futs, &ctx, plan, remote_coords);
}
}
let results = join_all(futs).await;
let mut out = Vec::new();
for res in results {
out.extend(res?);
}
Ok(out)
}
pub(super) async fn dispatch_resumes(
state: &SharedState,
tenant_id: TenantId,
database_id: DatabaseId,
query_bytes: &[u8],
deadline_ms: u64,
txn_id: Option<TxnId>,
pending_resumes: Vec<PendingResume>,
) -> crate::Result<Vec<TaggedShardResult>> {
let shared_arc = gateway_shared(state)?;
let ctx = DispatchCtx {
state,
tenant_id,
database_id,
deadline_ms,
txn_id,
shared_arc,
version_set: GatewayVersionSet::from_pairs(Vec::new()),
};
let mut futs: Vec<DispatchFut<'_>> = Vec::new();
for pending in pending_resumes {
let PendingResume {
remote_coords,
resume,
} = pending;
let resume_bytes =
zerompk::to_msgpack_vec(&resume).map_err(|e| crate::Error::Serialization {
format: "msgpack".into(),
detail: format!("varlen resume cursor: {e}"),
})?;
let plan = PhysicalPlan::Graph(GraphOp::MatchVarLenResume {
query: query_bytes.to_vec(),
resume: resume_bytes,
});
push_dispatch_fut(&mut futs, &ctx, plan, remote_coords);
}
let results = join_all(futs).await;
let mut out = Vec::new();
for res in results {
out.extend(res?);
}
Ok(out)
}