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