Build a `TxClass` for a dependent-read (OLLP) transaction. For `BulkUpdate`/`BulkDelete` plans that have `ollp_predicted_surrogates` set, the OLLP collection's write set is built from `predicted_surrogates`. All other tasks in the batch are included using static surrogate extraction, exactly as `build_static_tx_class` does. This ensures multi-shard Calvin txns that contain an OLLP bulk operation
(
tasks: &[PhysicalTask],
tenant_id: TenantId,
collection: &str,
predicted_surrogates: &[u32],
)
| 236 | /// |
| 237 | /// Returns `Err` if encoding fails or the resulting TxClass is invalid. |
| 238 | pub fn build_dependent_tx_class( |
| 239 | tasks: &[PhysicalTask], |
| 240 | tenant_id: TenantId, |
| 241 | collection: &str, |
| 242 | predicted_surrogates: &[u32], |
| 243 | ) -> crate::Result<TxClass> { |
| 244 | use std::collections::BTreeMap; |
| 245 | |
| 246 | // Accumulate per-collection surrogate sets. The OLLP collection uses the |
| 247 | // predicted surrogates; all other tasks use static key extraction. |
| 248 | let mut doc_surrogates: BTreeMap<String, Vec<u32>> = BTreeMap::new(); |
| 249 | |
| 250 | // Seed with the OLLP collection's predicted surrogates. |
| 251 | doc_surrogates |
| 252 | .entry(collection.to_owned()) |
| 253 | .or_default() |
| 254 | .extend_from_slice(predicted_surrogates); |
| 255 | |
| 256 | // Add static surrogates for all non-OLLP tasks. |
| 257 | for task in tasks { |
| 258 | let coll = collection_name_from_plan(&task.plan); |
| 259 | if coll.is_empty() || coll == collection { |
| 260 | continue; |
| 261 | } |
| 262 | let surrogate = surrogate_from_plan(&task.plan); |
| 263 | doc_surrogates.entry(coll).or_default().push(surrogate); |
| 264 | } |
| 265 | |
| 266 | let mut write_sets: Vec<EngineKeySet> = doc_surrogates |
| 267 | .into_iter() |
| 268 | .map(|(coll, surrogates)| EngineKeySet::Document { |
| 269 | collection: coll, |
| 270 | surrogates: SortedVec::new(surrogates), |
| 271 | }) |
| 272 | .collect(); |
| 273 | write_sets.sort_by(|a, b| a.collection().cmp(b.collection())); |
| 274 | |
| 275 | let write_set = ReadWriteSet::new(write_sets); |
| 276 | let read_set = ReadWriteSet::new(vec![]); |
| 277 | |
| 278 | let plans: Vec<&PhysicalPlan> = tasks.iter().map(|t| &t.plan).collect(); |
| 279 | let plans_bytes = zerompk::to_msgpack_vec(&plans).map_err(|e| Error::Serialization { |
| 280 | format: "msgpack".to_owned(), |
| 281 | detail: format!("failed to encode PhysicalPlan vec for Calvin dependent TxClass: {e}"), |
| 282 | })?; |
| 283 | |
| 284 | TxClass::new(read_set, write_set, plans_bytes, tenant_id, None).map_err(|e| Error::BadRequest { |
| 285 | detail: format!("invalid dependent TxClass: {e}"), |
| 286 | }) |
| 287 | } |
| 288 | |
| 289 | /// Extract the collection name from a write plan. |
| 290 | fn collection_name_from_plan(plan: &PhysicalPlan) -> String { |
no test coverage detected