use std::collections::BTreeSet;
use std::sync::Arc;
use crate::bridge::envelope::PhysicalPlan;
use crate::control::cluster::calvin::scheduler::driver::core::routing::{PlanRouting, plan_vshard};
use crate::control::cluster::calvin::scheduler::lock_manager::LockKey;
use crate::types::VShardId;
use nodedb_physical::physical_plan::{DocumentOp, GraphOp, KvOp, VectorOp};
pub(crate) fn plan_lock_keys(plan: &PhysicalPlan) -> Option<(VShardId, BTreeSet<LockKey>)> {
let vshard = match plan_vshard(plan) {
PlanRouting::Vshards(v) => match v.as_slice() {
[v] => *v,
_ => return None,
},
PlanRouting::ControlPlaneOnly | PlanRouting::NotAWrite | PlanRouting::Unroutable(_) => {
return None;
}
};
let key = point_lock_key(plan)?;
let mut keys = BTreeSet::new();
keys.insert(key);
Some((vshard, keys))
}
fn point_lock_key(plan: &PhysicalPlan) -> Option<LockKey> {
match plan {
PhysicalPlan::Document(op) => document_point_key(op),
PhysicalPlan::Kv(op) => kv_point_key(op),
PhysicalPlan::Vector(op) => vector_point_key(op),
PhysicalPlan::Graph(op) => graph_point_key(op),
PhysicalPlan::Timeseries(_)
| PhysicalPlan::Columnar(_)
| PhysicalPlan::Crdt(_)
| PhysicalPlan::Array(_)
| PhysicalPlan::Spatial(_)
| PhysicalPlan::Text(_)
| PhysicalPlan::Query(_)
| PhysicalPlan::Meta(_)
| PhysicalPlan::ClusterArray(_) => None,
}
}
fn document_point_key(op: &DocumentOp) -> Option<LockKey> {
match op {
DocumentOp::PointPut {
collection,
surrogate,
..
}
| DocumentOp::PointInsert {
collection,
surrogate,
..
}
| DocumentOp::PointDelete {
collection,
surrogate,
..
}
| DocumentOp::PointUpdate {
collection,
surrogate,
..
}
| DocumentOp::Upsert {
collection,
surrogate,
..
} => Some(LockKey::Surrogate {
collection: Arc::from(collection.as_str()),
surrogate: surrogate.as_u32(),
}),
DocumentOp::BatchInsert { .. }
| DocumentOp::InsertSelect { .. }
| DocumentOp::BulkUpdate { .. }
| DocumentOp::BulkDelete { .. }
| DocumentOp::UpdateFromJoin { .. } => None,
_ => None,
}
}
fn kv_point_key(op: &KvOp) -> Option<LockKey> {
match op {
KvOp::Put {
collection, key, ..
}
| KvOp::Insert {
collection, key, ..
}
| KvOp::InsertIfAbsent {
collection, key, ..
}
| KvOp::InsertOnConflictUpdate {
collection, key, ..
}
| KvOp::Incr {
collection, key, ..
}
| KvOp::IncrFloat {
collection, key, ..
}
| KvOp::Cas {
collection, key, ..
}
| KvOp::GetSet {
collection, key, ..
}
| KvOp::FieldSet {
collection, key, ..
} => Some(LockKey::Kv {
collection: Arc::from(collection.as_str()),
key: Arc::from(key.as_slice()),
}),
KvOp::Delete { collection, keys } => match keys.as_slice() {
[k] => Some(LockKey::Kv {
collection: Arc::from(collection.as_str()),
key: Arc::from(k.as_slice()),
}),
_ => None,
},
KvOp::BatchPut { .. } => None,
_ => None,
}
}
fn vector_point_key(op: &VectorOp) -> Option<LockKey> {
match op {
VectorOp::Insert {
collection,
surrogate,
..
}
| VectorOp::DeleteBySurrogate {
collection,
surrogate,
..
} => Some(LockKey::Surrogate {
collection: Arc::from(collection.as_str()),
surrogate: surrogate.as_u32(),
}),
VectorOp::BatchInsert { .. }
| VectorOp::Delete { .. }
| VectorOp::SparseInsert { .. }
| VectorOp::SparseDelete { .. }
| VectorOp::MultiVectorInsert { .. } => None,
_ => None,
}
}
fn graph_point_key(op: &GraphOp) -> Option<LockKey> {
match op {
GraphOp::EdgePut {
collection,
src_surrogate,
dst_surrogate,
..
}
| GraphOp::EdgeDelete {
collection,
src_surrogate,
dst_surrogate,
..
} => Some(LockKey::Edge {
collection: Arc::from(collection.as_str()),
src: src_surrogate.as_u32(),
dst: dst_surrogate.as_u32(),
}),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use nodedb_types::Surrogate;
fn kv_key(op: KvOp) -> LockKey {
kv_point_key(&op).expect("expected a lock key for this KV op")
}
#[test]
fn kv_incr_yields_kv_lock_key() {
assert_eq!(
kv_key(KvOp::Incr {
collection: "counters".to_owned(),
key: b"k1".to_vec(),
delta: 1,
ttl_ms: 0,
surrogate: Surrogate::new(1),
}),
LockKey::Kv {
collection: Arc::from("counters"),
key: Arc::from(b"k1".as_slice()),
}
);
}
#[test]
fn kv_incr_float_yields_kv_lock_key() {
assert_eq!(
kv_key(KvOp::IncrFloat {
collection: "counters".to_owned(),
key: b"k1".to_vec(),
delta: 1.5,
surrogate: Surrogate::new(1),
}),
LockKey::Kv {
collection: Arc::from("counters"),
key: Arc::from(b"k1".as_slice()),
}
);
}
#[test]
fn kv_cas_yields_kv_lock_key() {
assert_eq!(
kv_key(KvOp::Cas {
collection: "counters".to_owned(),
key: b"k1".to_vec(),
expected: vec![],
new_value: vec![],
surrogate: Surrogate::new(1),
}),
LockKey::Kv {
collection: Arc::from("counters"),
key: Arc::from(b"k1".as_slice()),
}
);
}
#[test]
fn kv_get_set_yields_kv_lock_key() {
assert_eq!(
kv_key(KvOp::GetSet {
collection: "counters".to_owned(),
key: b"k1".to_vec(),
new_value: vec![],
surrogate: Surrogate::new(1),
}),
LockKey::Kv {
collection: Arc::from("counters"),
key: Arc::from(b"k1".as_slice()),
}
);
}
#[test]
fn kv_field_set_yields_kv_lock_key() {
assert_eq!(
kv_key(KvOp::FieldSet {
collection: "counters".to_owned(),
key: b"k1".to_vec(),
updates: vec![],
surrogate: Surrogate::new(1),
}),
LockKey::Kv {
collection: Arc::from("counters"),
key: Arc::from(b"k1".as_slice()),
}
);
}
#[test]
fn kv_batch_put_stays_unfenced() {
assert_eq!(
kv_point_key(&KvOp::BatchPut {
collection: "counters".to_owned(),
entries: vec![(b"k1".to_vec(), vec![]), (b"k2".to_vec(), vec![])],
ttl_ms: 0,
surrogates: vec![],
}),
None
);
}
#[test]
fn kv_multi_key_delete_stays_unfenced() {
assert_eq!(
kv_point_key(&KvOp::Delete {
collection: "counters".to_owned(),
keys: vec![b"k1".to_vec(), b"k2".to_vec()],
}),
None
);
}
#[test]
fn document_upsert_yields_surrogate_lock_key() {
assert_eq!(
document_point_key(&DocumentOp::Upsert {
collection: "docs".to_owned(),
document_id: "d1".to_owned(),
value: vec![],
on_conflict_updates: vec![],
surrogate: Surrogate::new(7),
}),
Some(LockKey::Surrogate {
collection: Arc::from("docs"),
surrogate: 7,
})
);
}
}