| 916 | } |
| 917 | |
| 918 | fn create_view( |
| 919 | &mut self, |
| 920 | name: &str, |
| 921 | kind: ViewScope, |
| 922 | parent: Option<u64>, |
| 923 | ) -> PristineResult<ViewState> { |
| 924 | // Check if view already exists |
| 925 | { |
| 926 | let table = self.txn.open_table(VIEWS)?; |
| 927 | if table.get(name)?.is_some() { |
| 928 | return Err(PristineError::ViewAlreadyExists { |
| 929 | name: name.to_string(), |
| 930 | }); |
| 931 | } |
| 932 | } |
| 933 | |
| 934 | // Validate parent exists if specified, and detect cycles |
| 935 | if let Some(parent_id) = parent { |
| 936 | let parent_view = ViewTxnT::get_view_by_id(self, parent_id)?.ok_or_else(|| { |
| 937 | PristineError::ViewNotFound { |
| 938 | name: format!("parent view id={}", parent_id), |
| 939 | } |
| 940 | })?; |
| 941 | |
| 942 | // Cycle detection: walk the parent chain from the proposed parent |
| 943 | // upward. If we ever encounter our own (not-yet-allocated) name, |
| 944 | // there's a cycle. Since the view doesn't exist yet, we only need |
| 945 | // to check that the parent chain terminates without revisiting |
| 946 | // `parent_id` — which is guaranteed as long as the existing graph |
| 947 | // is acyclic and we're adding a leaf. |
| 948 | // |
| 949 | // However, we also guard against the degenerate case where someone |
| 950 | // passes parent == self (once IDs are known). Since we haven't |
| 951 | // allocated an ID yet, the only risk is the parent chain itself |
| 952 | // being cyclic (which would be a pre-existing bug). We do a bounded |
| 953 | // walk as a safety check. |
| 954 | let mut visited = std::collections::HashSet::new(); |
| 955 | visited.insert(parent_id); |
| 956 | let mut cursor = parent_view.parent; |
| 957 | while let Some(ancestor_id) = cursor { |
| 958 | if !visited.insert(ancestor_id) { |
| 959 | // We've seen this ID before — cycle detected in existing chain |
| 960 | return Err(PristineError::ViewCycleDetected { |
| 961 | name: name.to_string(), |
| 962 | parent_name: parent_view.name.clone(), |
| 963 | }); |
| 964 | } |
| 965 | match ViewTxnT::get_view_by_id(self, ancestor_id)? { |
| 966 | Some(ancestor) => cursor = ancestor.parent, |
| 967 | None => break, // Broken chain — parent doesn't exist (shouldn't happen) |
| 968 | } |
| 969 | } |
| 970 | } |
| 971 | |
| 972 | // Allocate ID and create state |
| 973 | let id = self.next_view_id.fetch_add(1, Ordering::SeqCst); |
| 974 | let state = ViewState::with_scope(id, name.to_string(), kind, parent); |
| 975 | |