reifydb_engine/transaction/catalog/
view.rs

1// Copyright (c) reifydb.com 2025
2// This file is licensed under the AGPL-3.0-or-later, see license.md file
3
4use reifydb_catalog::transaction::CatalogTrackViewChangeOperations;
5use reifydb_core::interface::{
6	Change, NamespaceId,
7	OperationType::{Create, Delete, Update},
8	TransactionalViewChanges, ViewDef, ViewId,
9};
10use reifydb_type::IntoFragment;
11
12use crate::{StandardCommandTransaction, StandardQueryTransaction};
13
14impl CatalogTrackViewChangeOperations for StandardCommandTransaction {
15	fn track_view_def_created(&mut self, view: ViewDef) -> reifydb_core::Result<()> {
16		let change = Change {
17			pre: None,
18			post: Some(view),
19			op: Create,
20		};
21		self.changes.add_view_def_change(change);
22		Ok(())
23	}
24
25	fn track_view_def_updated(&mut self, pre: ViewDef, post: ViewDef) -> reifydb_core::Result<()> {
26		let change = Change {
27			pre: Some(pre),
28			post: Some(post),
29			op: Update,
30		};
31		self.changes.add_view_def_change(change);
32		Ok(())
33	}
34
35	fn track_view_def_deleted(&mut self, view: ViewDef) -> reifydb_core::Result<()> {
36		let change = Change {
37			pre: Some(view),
38			post: None,
39			op: Delete,
40		};
41		self.changes.add_view_def_change(change);
42		Ok(())
43	}
44}
45
46impl TransactionalViewChanges for StandardCommandTransaction {
47	fn find_view(&self, id: ViewId) -> Option<&ViewDef> {
48		// Find the last change for this view ID
49		for change in self.changes.view_def.iter().rev() {
50			if let Some(view) = &change.post {
51				if view.id == id {
52					return Some(view);
53				}
54			} else if let Some(view) = &change.pre {
55				if view.id == id && change.op == Delete {
56					// View was deleted
57					return None;
58				}
59			}
60		}
61		None
62	}
63
64	fn find_view_by_name<'a>(&self, namespace: NamespaceId, name: impl IntoFragment<'a>) -> Option<&ViewDef> {
65		let name = name.into_fragment();
66		self.changes.view_def.iter().rev().find_map(|change| {
67			change.post.as_ref().filter(|v| v.namespace == namespace && v.name == name.text())
68		})
69	}
70
71	fn is_view_deleted(&self, id: ViewId) -> bool {
72		self.changes
73			.view_def
74			.iter()
75			.rev()
76			.any(|change| change.op == Delete && change.pre.as_ref().map(|v| v.id) == Some(id))
77	}
78
79	fn is_view_deleted_by_name<'a>(&self, namespace: NamespaceId, name: impl IntoFragment<'a>) -> bool {
80		let name = name.into_fragment();
81		self.changes.view_def.iter().rev().any(|change| {
82			change.op == Delete
83				&& change
84					.pre
85					.as_ref()
86					.map(|v| v.namespace == namespace && v.name == name.text())
87					.unwrap_or(false)
88		})
89	}
90}
91
92impl TransactionalViewChanges for StandardQueryTransaction {
93	fn find_view(&self, _id: ViewId) -> Option<&ViewDef> {
94		None
95	}
96
97	fn find_view_by_name<'a>(&self, _namespace: NamespaceId, _name: impl IntoFragment<'a>) -> Option<&ViewDef> {
98		None
99	}
100
101	fn is_view_deleted(&self, _id: ViewId) -> bool {
102		false
103	}
104
105	fn is_view_deleted_by_name<'a>(&self, _namespace: NamespaceId, _name: impl IntoFragment<'a>) -> bool {
106		false
107	}
108}
109
110#[cfg(test)]
111mod tests {
112	use reifydb_catalog::transaction::CatalogTrackViewChangeOperations;
113	use reifydb_core::interface::{
114		NamespaceId, Operation,
115		OperationType::{Create, Delete, Update},
116		ViewDef, ViewId, ViewKind,
117	};
118
119	use crate::test_utils::create_test_command_transaction;
120
121	// Helper function to create test view definition
122	fn test_view_def(id: u64, namespace_id: u64, name: &str) -> ViewDef {
123		ViewDef {
124			id: ViewId(id),
125			namespace: NamespaceId(namespace_id),
126			name: name.to_string(),
127			columns: vec![],
128			kind: ViewKind::Deferred,
129			primary_key: None,
130		}
131	}
132
133	mod track_view_def_created {
134		use super::*;
135
136		#[test]
137		fn test_successful_creation() {
138			let mut txn = create_test_command_transaction();
139
140			let view = test_view_def(1, 1, "test_view");
141			let result = txn.track_view_def_created(view.clone());
142			assert!(result.is_ok());
143
144			// Verify the change was recorded
145			assert_eq!(txn.changes.view_def.len(), 1);
146			let change = &txn.changes.view_def[0];
147			assert!(change.pre.is_none());
148			assert_eq!(change.post.as_ref().unwrap().name, "test_view");
149			assert_eq!(change.op, Create);
150
151			// Verify operation was logged
152			assert_eq!(txn.changes.log.len(), 1);
153			match &txn.changes.log[0] {
154				Operation::View {
155					id,
156					op,
157				} if *id == view.id && *op == Create => {}
158				_ => panic!("Expected View operation with Create"),
159			}
160		}
161	}
162
163	mod track_view_def_updated {
164		use super::*;
165
166		#[test]
167		fn test_multiple_updates_no_coalescing() {
168			let mut txn = create_test_command_transaction();
169			let view_v1 = test_view_def(1, 1, "view_v1");
170			let view_v2 = test_view_def(1, 1, "view_v2");
171			let view_v3 = test_view_def(1, 1, "view_v3");
172
173			// First update
174			txn.track_view_def_updated(view_v1.clone(), view_v2.clone()).unwrap();
175
176			// Should have one change
177			assert_eq!(txn.changes.view_def.len(), 1);
178			assert_eq!(txn.changes.view_def[0].pre.as_ref().unwrap().name, "view_v1");
179			assert_eq!(txn.changes.view_def[0].post.as_ref().unwrap().name, "view_v2");
180			assert_eq!(txn.changes.view_def[0].op, Update);
181
182			// Second update - should NOT coalesce
183			txn.track_view_def_updated(view_v2, view_v3.clone()).unwrap();
184
185			// Should now have TWO changes
186			assert_eq!(txn.changes.view_def.len(), 2);
187
188			// Second update recorded separately
189			assert_eq!(txn.changes.view_def[1].pre.as_ref().unwrap().name, "view_v2");
190			assert_eq!(txn.changes.view_def[1].post.as_ref().unwrap().name, "view_v3");
191
192			// Should have 2 log entries
193			assert_eq!(txn.changes.log.len(), 2);
194		}
195
196		#[test]
197		fn test_create_then_update_no_coalescing() {
198			let mut txn = create_test_command_transaction();
199			let view_v1 = test_view_def(1, 1, "view_v1");
200			let view_v2 = test_view_def(1, 1, "view_v2");
201
202			// First track creation
203			txn.track_view_def_created(view_v1.clone()).unwrap();
204			assert_eq!(txn.changes.view_def.len(), 1);
205			assert_eq!(txn.changes.view_def[0].op, Create);
206
207			// Then track update - should NOT coalesce
208			txn.track_view_def_updated(view_v1, view_v2.clone()).unwrap();
209
210			// Should have TWO changes now
211			assert_eq!(txn.changes.view_def.len(), 2);
212
213			// First is still Create
214			assert_eq!(txn.changes.view_def[0].op, Create);
215
216			// Second is Update
217			assert_eq!(txn.changes.view_def[1].op, Update);
218
219			// Should have 2 log entries
220			assert_eq!(txn.changes.log.len(), 2);
221		}
222
223		#[test]
224		fn test_normal_update() {
225			let mut txn = create_test_command_transaction();
226			let view_v1 = test_view_def(1, 1, "view_v1");
227			let view_v2 = test_view_def(1, 1, "view_v2");
228
229			let result = txn.track_view_def_updated(view_v1.clone(), view_v2.clone());
230			assert!(result.is_ok());
231
232			// Verify the change was recorded
233			assert_eq!(txn.changes.view_def.len(), 1);
234			let change = &txn.changes.view_def[0];
235			assert_eq!(change.pre.as_ref().unwrap().name, "view_v1");
236			assert_eq!(change.post.as_ref().unwrap().name, "view_v2");
237			assert_eq!(change.op, Update);
238
239			// Verify operation was logged
240			assert_eq!(txn.changes.log.len(), 1);
241			match &txn.changes.log[0] {
242				Operation::View {
243					id,
244					op,
245				} if *id == ViewId(1) && *op == Update => {}
246				_ => panic!("Expected View operation with Update"),
247			}
248		}
249	}
250
251	mod track_view_def_deleted {
252		use super::*;
253
254		#[test]
255		fn test_delete_after_create_no_coalescing() {
256			let mut txn = create_test_command_transaction();
257			let view = test_view_def(1, 1, "test_view");
258
259			// First track creation
260			txn.track_view_def_created(view.clone()).unwrap();
261			assert_eq!(txn.changes.view_def.len(), 1);
262
263			// Then track deletion
264			let result = txn.track_view_def_deleted(view.clone());
265			assert!(result.is_ok());
266
267			// Should have TWO changes now (no coalescing)
268			assert_eq!(txn.changes.view_def.len(), 2);
269
270			// First is Create
271			assert_eq!(txn.changes.view_def[0].op, Create);
272
273			// Second is Delete
274			assert_eq!(txn.changes.view_def[1].op, Delete);
275
276			// Should have 2 log entries
277			assert_eq!(txn.changes.log.len(), 2);
278		}
279
280		#[test]
281		fn test_delete_after_update_no_coalescing() {
282			let mut txn = create_test_command_transaction();
283			let view_v1 = test_view_def(1, 1, "view_v1");
284			let view_v2 = test_view_def(1, 1, "view_v2");
285
286			// First track update
287			txn.track_view_def_updated(view_v1.clone(), view_v2.clone()).unwrap();
288			assert_eq!(txn.changes.view_def.len(), 1);
289
290			// Then track deletion
291			let result = txn.track_view_def_deleted(view_v2);
292			assert!(result.is_ok());
293
294			// Should have TWO changes
295			assert_eq!(txn.changes.view_def.len(), 2);
296
297			// First is Update
298			assert_eq!(txn.changes.view_def[0].op, Update);
299
300			// Second is Delete
301			assert_eq!(txn.changes.view_def[1].op, Delete);
302
303			// Should have 2 log entries
304			assert_eq!(txn.changes.log.len(), 2);
305		}
306
307		#[test]
308		fn test_normal_delete() {
309			let mut txn = create_test_command_transaction();
310			let view = test_view_def(1, 1, "test_view");
311
312			let result = txn.track_view_def_deleted(view.clone());
313			assert!(result.is_ok());
314
315			// Verify the change was recorded
316			assert_eq!(txn.changes.view_def.len(), 1);
317			let change = &txn.changes.view_def[0];
318			assert_eq!(change.pre.as_ref().unwrap().name, "test_view");
319			assert!(change.post.is_none());
320			assert_eq!(change.op, Delete);
321
322			// Verify operation was logged
323			assert_eq!(txn.changes.log.len(), 1);
324			match &txn.changes.log[0] {
325				Operation::View {
326					id,
327					op,
328				} if *id == view.id && *op == Delete => {}
329				_ => panic!("Expected View operation with Delete"),
330			}
331		}
332	}
333}