diff --git a/Cargo.toml b/Cargo.toml index eeffcf0..b4a8ebf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hello_rust" -version = "0.1.2" +version = "0.1.3" edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/recipe/meta.yaml b/recipe/meta.yaml index 6593b10..3e6a0c7 100644 --- a/recipe/meta.yaml +++ b/recipe/meta.yaml @@ -1,5 +1,5 @@ {% set name = "hello-rust" %} -{% set version = "0.1.2" %} +{% set version = "0.1.3" %} package: name: {{ name|lower }} @@ -9,7 +9,7 @@ source: path: .. build: - number: 5 + number: 0 script: {{ PYTHON }} -m pip install . -vv requirements: diff --git a/src/lib.rs b/src/lib.rs index c951879..88b5574 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -170,25 +170,20 @@ fn choose_candidate_by_plan( Ok(best_idx) } -fn has_self_blocker_after_start( +fn has_blocker_on_node_after_target( py: Python<'_>, rows: &[Py], self_index_all: &HashMap>, - start_idx: usize, - start_self_key: &str, - start_target: &Bound<'_, PyAny>, + node_key: &str, + ref_target: &Bound<'_, PyAny>, target_col: &str, block_key: &HashMap, ) -> PyResult { - let Some(same_self_indices) = self_index_all.get(start_self_key) else { + let Some(indices) = self_index_all.get(node_key) else { return Ok(false); }; - for idx in same_self_indices { - if *idx == start_idx { - continue; - } - + for idx in indices { let row = rows[*idx].bind(py); if !matches_kv(&row, block_key)? { continue; @@ -201,7 +196,7 @@ fn has_self_blocker_after_start( continue; } - if compare_bool(&block_target, start_target, CompareOp::Gt) { + if compare_bool(&block_target, ref_target, CompareOp::Gt) { return Ok(true); } } @@ -220,11 +215,10 @@ fn has_self_blocker_after_start( /// If no valid candidate is found, the original value is kept unchanged. /// /// block_key: -/// - Optional key/value matcher applied in two places: -/// 1) same-self history check: if the current self already has a blocker event after -/// this row's target_col, propagation is skipped for this row. -/// 2) ancestor traversal: if a selected candidate matches block_key, traversal stops. -/// - In both cases, the original target value remains unchanged. +/// - Optional key/value matcher applied at node level for both self and ancestor nodes. +/// - For the current self and each ancestor node encountered during traversal, if that node +/// has any blocker event with target_col > current reference target, traversal is terminated. +/// - In that case the original target value remains unchanged. fn propagate_target_rows( py: Python<'_>, rows: &[Py], @@ -268,11 +262,10 @@ fn propagate_target_rows( let start_self_key = composite_key(&start_row, self_cols)?; if let Some(kv) = block_key { - if has_self_blocker_after_start( + if has_blocker_on_node_after_target( py, rows, &self_index_all, - start_idx, &start_self_key, &start_target, target_col, @@ -292,6 +285,20 @@ fn propagate_target_rows( break; } + if let Some(kv) = block_key { + if has_blocker_on_node_after_target( + py, + rows, + &self_index_all, + ¤t_parent, + ¤t_target, + target_col, + kv, + )? { + break; + } + } + let Some(candidates) = self_index.get(¤t_parent) else { break; }; @@ -497,7 +504,7 @@ mod tests { } #[test] - fn self_history_future_blocker_prevents_propagation() { + fn node_level_self_blocker_prevents_propagation() { Python::attach(|py| { let rows: Vec> = vec![ [ @@ -561,6 +568,79 @@ mod tests { assert_eq!(leaf_install_ts, "3"); }); } + + #[test] + fn node_level_parent_blocker_prevents_propagation() { + Python::attach(|py| { + let rows: Vec> = vec![ + [ + ("id", "ancestor"), + ("parent_id", "root"), + ("event", "Install"), + ("kind", "target"), + ("ts", "9"), + ], + [ + ("id", "parent"), + ("parent_id", "ancestor"), + ("event", "Install"), + ("kind", "target"), + ("ts", "8"), + ], + [ + ("id", "parent"), + ("parent_id", "ancestor"), + ("event", "Remove"), + ("kind", "other"), + ("ts", "10"), + ], + [ + ("id", "leaf"), + ("parent_id", "parent"), + ("event", "Install"), + ("kind", "target"), + ("ts", "7"), + ], + ] + .into_iter() + .map(|pairs| { + let d = PyDict::new(py); + for (k, v) in pairs { + d.set_item(k, v).expect("set test item"); + } + d.unbind() + }) + .collect(); + + let self_cols = vec!["id".to_string()]; + let parent_cols = vec!["parent_id".to_string()]; + let target_key = HashMap::from([("kind".to_string(), "target".to_string())]); + let blocker = HashMap::from([("event".to_string(), "Remove".to_string())]); + + propagate_target_rows( + py, + &rows, + &self_cols, + &parent_cols, + "ts", + &target_key, + Some(&blocker), + "event", + "Install", + Plan::Backward, + ) + .expect("propagation with parent-node blocker"); + + let leaf_ts: String = rows[3] + .bind(py) + .get_item("ts") + .expect("leaf ts item") + .expect("leaf ts exists") + .extract() + .expect("leaf ts string"); + assert_eq!(leaf_ts, "7"); + }); + } } #[pymodule]