use std::collections::BTreeSet;
use std::sync::Arc;
use nodedb_cluster::calvin::sequencer::inbox::Inbox;
use nodedb_types::TenantId;
use crate::Error;
use crate::control::cluster::calvin::executor::ollp::orchestrator::OllpOrchestrator;
use crate::control::planner::calvin::cross_shard_mode::CrossShardTxnMode;
use crate::control::planner::calvin::tx_class::build_static_tx_class;
use crate::control::planner::calvin::types::{DispatchClass, DispatchOutcome};
use crate::control::server::shared::session::TransactionState;
use crate::control::server::shared::session::read_set::ReadSetEntry;
use crate::types::VShardId;
use nodedb_physical::physical_plan::{DocumentOp, PhysicalPlan};
use nodedb_physical::physical_task::PhysicalTask;
pub use crate::control::planner::calvin::predicate::predicate_class;
pub use crate::control::planner::calvin::write_class::is_write_plan;
pub fn is_dependent_predicate(plan: &PhysicalPlan) -> bool {
matches!(
plan,
PhysicalPlan::Document(DocumentOp::BulkUpdate { .. })
| PhysicalPlan::Document(DocumentOp::BulkDelete { .. })
)
}
pub fn read_vshards_of(reads: &[ReadSetEntry]) -> BTreeSet<u32> {
reads
.iter()
.filter(|e| !e.collection.is_empty())
.map(|e| VShardId::from_collection_in_database(e.database_id, &e.collection).as_u32())
.collect()
}
pub fn classify_dispatch(tasks: &[PhysicalTask], read_vshards: &BTreeSet<u32>) -> DispatchClass {
let mut vshards: BTreeSet<u32> = BTreeSet::new();
let mut last_vshard = None;
for task in tasks {
if is_write_plan(&task.plan) {
let id = task.vshard_id.as_u32();
vshards.insert(id);
last_vshard = Some(task.vshard_id);
}
}
vshards.extend(read_vshards.iter().copied());
match vshards.len() {
0 => DispatchClass::SingleShard {
vshard: tasks
.first()
.map(|t| t.vshard_id)
.unwrap_or(VShardId::new(0)),
},
1 => DispatchClass::SingleShard {
vshard: last_vshard.unwrap_or_else(|| VShardId::new(0)),
},
_ => DispatchClass::MultiShard { vshards },
}
}
pub async fn dispatch_calvin_or_fast(
tasks: &[PhysicalTask],
mode: CrossShardTxnMode,
tx_state: TransactionState,
inbox: Option<&Inbox>,
_orchestrator: Option<&Arc<OllpOrchestrator>>,
tenant_id: TenantId,
reads: &[ReadSetEntry],
) -> crate::Result<DispatchOutcome> {
let read_vshards = read_vshards_of(reads);
let class = classify_dispatch(tasks, &read_vshards);
match &class {
DispatchClass::MultiShard { .. } => {
if tx_state == TransactionState::InBlock {
return Err(Error::CrossShardInExplicitTransaction);
}
match mode {
CrossShardTxnMode::Strict => {
let inbox = inbox.ok_or(Error::SequencerUnavailable)?;
let tx_class = build_static_tx_class(tasks, tenant_id, reads)?;
let inbox_seq = inbox.submit(tx_class).map_err(|e| Error::BadRequest {
detail: format!("Calvin sequencer rejected transaction: {e}"),
})?;
Ok(DispatchOutcome::CalvinStatic { inbox_seq })
}
CrossShardTxnMode::BestEffortNonAtomic => Ok(DispatchOutcome::BestEffortNonAtomic),
}
}
DispatchClass::SingleShard { .. } => Ok(DispatchOutcome::SingleShard),
}
}
#[cfg(test)]
#[path = "dispatch_tests.rs"]
mod tests;