| 442 | } |
| 443 | |
| 444 | fn create_view( |
| 445 | &mut self, |
| 446 | name: &str, |
| 447 | kind: ViewScope, |
| 448 | parent: Option<u64>, |
| 449 | ) -> PristineResult<ViewState> { |
| 450 | // Check if view already exists |
| 451 | { |
| 452 | let table = self.txn.open_table(VIEWS)?; |
| 453 | if table.get(name)?.is_some() { |
| 454 | return Err(PristineError::ViewAlreadyExists { |
| 455 | name: name.to_string(), |
| 456 | }); |
| 457 | } |
| 458 | } |
| 459 | |
| 460 | // Validate parent exists if specified, and detect cycles |
| 461 | if let Some(parent_id) = parent { |
| 462 | let parent_view = ViewTxnT::get_view_by_id(self, parent_id)?.ok_or_else(|| { |
| 463 | PristineError::ViewNotFound { |
| 464 | name: format!("parent view id={}", parent_id), |
| 465 | } |
| 466 | })?; |
| 467 | |
| 468 | // Cycle detection: walk the parent chain from the proposed parent |
| 469 | // upward. If we ever encounter our own (not-yet-allocated) name, |
| 470 | // there's a cycle. Since the view doesn't exist yet, we only need |
| 471 | // to check that the parent chain terminates without revisiting |
| 472 | // `parent_id` — which is guaranteed as long as the existing graph |
| 473 | // is acyclic and we're adding a leaf. |
| 474 | // |
| 475 | // However, we also guard against the degenerate case where someone |
| 476 | // passes parent == self (once IDs are known). Since we haven't |
| 477 | // allocated an ID yet, the only risk is the parent chain itself |
| 478 | // being cyclic (which would be a pre-existing bug). We do a bounded |
| 479 | // walk as a safety check. |
| 480 | let mut visited = std::collections::HashSet::new(); |
| 481 | visited.insert(parent_id); |
| 482 | let mut cursor = parent_view.parent; |
| 483 | while let Some(ancestor_id) = cursor { |
| 484 | if !visited.insert(ancestor_id) { |
| 485 | // We've seen this ID before — cycle detected in existing chain |
| 486 | return Err(PristineError::ViewCycleDetected { |
| 487 | name: name.to_string(), |
| 488 | parent_name: parent_view.name.clone(), |
| 489 | }); |
| 490 | } |
| 491 | match ViewTxnT::get_view_by_id(self, ancestor_id)? { |
| 492 | Some(ancestor) => cursor = ancestor.parent, |
| 493 | None => break, // Broken chain — parent doesn't exist (shouldn't happen) |
| 494 | } |
| 495 | } |
| 496 | } |
| 497 | |
| 498 | // Allocate ID and create state |
| 499 | let id = self.next_view_id.fetch_add(1, Ordering::SeqCst); |
| 500 | let state = ViewState::with_scope(id, name.to_string(), kind, parent); |
| 501 | |