Write a change to the graph and add it to a view's change log. This function handles two cases: 1. **New change**: The change hasn't been applied to the graph yet. We apply all hunks and add it to the view's change log. 2. **Existing change**: The change is already in the graph (applied via another view). We skip hunk application and just add it to the view's change log. This distinction is cruc
(
txn: &mut WriteTxn<'_>,
view_name: &str,
change_id: NodeId,
change_hash: &Hash,
change: &Change,
options: &InsertOptions,
already_in_graph: bool,
)
| 227 | /// |
| 228 | /// The result of the insertion including new state and conflict info. |
| 229 | pub fn write_change_to_graph( |
| 230 | txn: &mut WriteTxn<'_>, |
| 231 | view_name: &str, |
| 232 | change_id: NodeId, |
| 233 | change_hash: &Hash, |
| 234 | change: &Change, |
| 235 | options: &InsertOptions, |
| 236 | already_in_graph: bool, |
| 237 | ) -> InsertResult<InsertOutcome> { |
| 238 | let mut workspace = Workspace::new(); |
| 239 | let mut conflict_tracker = ConflictTracker::new(); |
| 240 | let mut stats = InsertStats::new(); |
| 241 | let trace_record = std::env::var_os("ATOMIC_TRACE_RECORD").is_some() |
| 242 | || std::env::var_os("ATOMIC_TRACE_INSERT").is_some(); |
| 243 | |
| 244 | // Get the current view |
| 245 | let mut view = txn |
| 246 | .open_or_create_view(view_name) |
| 247 | .map_err(|e| InsertError::Database(e.to_string()))?; |
| 248 | |
| 249 | // Validate we can apply (unless caller explicitly skipped validation, |
| 250 | // e.g. rebuild_change_graph re-applying an existing change with new hunks). |
| 251 | if !options.skip_validation { |
| 252 | validate_can_apply(txn, &view, change_id, change_hash, change)?; |
| 253 | } |
| 254 | |
| 255 | // Only apply hunks if the change isn't already in the graph. |
| 256 | // All edges go to the global GRAPH + INODE_GRAPH tables. |
| 257 | let should_apply_hunks = !already_in_graph; |
| 258 | |
| 259 | log::debug!( |
| 260 | "write_change_to_graph: change_id={:?} hash={} should_apply_hunks={} view_kind={:?}", |
| 261 | change_id, |
| 262 | change_hash.to_base32(), |
| 263 | should_apply_hunks, |
| 264 | view.kind |
| 265 | ); |
| 266 | |
| 267 | if should_apply_hunks { |
| 268 | let hunks = change.hunks(); |
| 269 | let total_hunks = hunks.len(); |
| 270 | let hunk_phase_start = std::time::Instant::now(); |
| 271 | // Open GRAPH + INODE_GRAPH once for the whole hunk pass. All graph |
| 272 | // reads and writes flow through this cached handle, so the tables are |
| 273 | // opened twice per change instead of ~6 times per hunk. The scope ends |
| 274 | // before the FileOps/view-update phase, which needs `&mut txn` again. |
| 275 | { |
| 276 | let mut cached = CachedWriteGraphTxn::new(&*txn) |
| 277 | .map_err(|e| InsertError::Database(e.to_string()))?; |
| 278 | for (hunk_idx, graph_op) in hunks.iter().enumerate() { |
| 279 | if trace_record && (hunk_idx == 0 || (hunk_idx + 1) % 1_000 == 0) { |
| 280 | eprintln!( |
| 281 | "[write_change_to_graph] applying hunk {}/{} elapsed={:?}", |
| 282 | hunk_idx + 1, |
| 283 | total_hunks, |
| 284 | hunk_phase_start.elapsed() |
| 285 | ); |
| 286 | } |
no test coverage detected