reifydb_catalog/materialized/
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_core::{
5	CommitVersion,
6	interface::{NamespaceId, ViewDef, ViewId},
7};
8
9use crate::materialized::{MaterializedCatalog, MultiVersionViewDef};
10
11impl MaterializedCatalog {
12	/// Find a view by ID at a specific version
13	pub fn find_view(&self, view: ViewId, version: CommitVersion) -> Option<ViewDef> {
14		self.views.get(&view).and_then(|entry| {
15			let multi = entry.value();
16			multi.get(version)
17		})
18	}
19
20	/// Find a view by name in a namespace at a specific version
21	pub fn find_view_by_name(&self, namespace: NamespaceId, name: &str, version: CommitVersion) -> Option<ViewDef> {
22		self.views_by_name.get(&(namespace, name.to_string())).and_then(|entry| {
23			let view_id = *entry.value();
24			self.find_view(view_id, version)
25		})
26	}
27
28	pub fn set_view(&self, id: ViewId, version: CommitVersion, view: Option<ViewDef>) {
29		// Look up the current view to update the index
30		if let Some(entry) = self.views.get(&id) {
31			if let Some(pre) = entry.value().get_latest() {
32				self.views_by_name.remove(&(pre.namespace, pre.name.clone()));
33			}
34		}
35
36		let multi = self.views.get_or_insert_with(id, MultiVersionViewDef::new);
37		if let Some(new) = view {
38			self.views_by_name.insert((new.namespace, new.name.clone()), id);
39			multi.value().insert(version, new);
40		} else {
41			multi.value().remove(version);
42		}
43	}
44}
45
46#[cfg(test)]
47mod tests {
48	use reifydb_core::interface::{ColumnDef, ColumnId, ColumnIndex, ViewKind};
49	use reifydb_type::{Type, TypeConstraint};
50
51	use super::*;
52
53	fn create_test_view(id: ViewId, namespace: NamespaceId, name: &str) -> ViewDef {
54		ViewDef {
55			id,
56			namespace,
57			name: name.to_string(),
58			kind: ViewKind::Deferred,
59			columns: vec![
60				ColumnDef {
61					id: ColumnId(1),
62					name: "id".to_string(),
63					constraint: TypeConstraint::unconstrained(Type::Int1),
64					policies: vec![],
65					index: ColumnIndex(0),
66					auto_increment: false,
67					dictionary_id: None,
68				},
69				ColumnDef {
70					id: ColumnId(2),
71					name: "name".to_string(),
72					constraint: TypeConstraint::unconstrained(Type::Utf8),
73					policies: vec![],
74					index: ColumnIndex(1),
75					auto_increment: false,
76					dictionary_id: None,
77				},
78			],
79			primary_key: None,
80		}
81	}
82
83	#[test]
84	fn test_set_and_find_view() {
85		let catalog = MaterializedCatalog::new();
86		let view_id = ViewId(1);
87		let namespace_id = NamespaceId(1);
88		let view = create_test_view(view_id, namespace_id, "test_view");
89
90		// Set view at version 1
91		catalog.set_view(view_id, CommitVersion(1), Some(view.clone()));
92
93		// Find view at version 1
94		let found = catalog.find_view(view_id, CommitVersion(1));
95		assert_eq!(found, Some(view.clone()));
96
97		// Find view at later version (should return same view)
98		let found = catalog.find_view(view_id, CommitVersion(5));
99		assert_eq!(found, Some(view));
100
101		// View shouldn't exist at version 0
102		let found = catalog.find_view(view_id, CommitVersion(0));
103		assert_eq!(found, None);
104	}
105
106	#[test]
107	fn test_find_view_by_name() {
108		let catalog = MaterializedCatalog::new();
109		let view_id = ViewId(1);
110		let namespace_id = NamespaceId(1);
111		let view = create_test_view(view_id, namespace_id, "named_view");
112
113		// Set view
114		catalog.set_view(view_id, CommitVersion(1), Some(view.clone()));
115
116		// Find by name
117		let found = catalog.find_view_by_name(namespace_id, "named_view", CommitVersion(1));
118		assert_eq!(found, Some(view));
119
120		// Shouldn't find with wrong name
121		let found = catalog.find_view_by_name(namespace_id, "wrong_name", CommitVersion(1));
122		assert_eq!(found, None);
123
124		// Shouldn't find in wrong namespace
125		let found = catalog.find_view_by_name(NamespaceId(2), "named_view", CommitVersion(1));
126		assert_eq!(found, None);
127	}
128
129	#[test]
130	fn test_view_rename() {
131		let catalog = MaterializedCatalog::new();
132		let view_id = ViewId(1);
133		let namespace_id = NamespaceId(1);
134
135		// Create and set initial view
136		let view_v1 = create_test_view(view_id, namespace_id, "old_name");
137		catalog.set_view(view_id, CommitVersion(1), Some(view_v1.clone()));
138
139		// Verify initial state
140		assert!(catalog.find_view_by_name(namespace_id, "old_name", CommitVersion(1)).is_some());
141		assert!(catalog.find_view_by_name(namespace_id, "new_name", CommitVersion(1)).is_none());
142
143		// Rename the view
144		let mut view_v2 = view_v1.clone();
145		view_v2.name = "new_name".to_string();
146		catalog.set_view(view_id, CommitVersion(2), Some(view_v2.clone()));
147
148		// Old name should be gone
149		assert!(catalog.find_view_by_name(namespace_id, "old_name", CommitVersion(2)).is_none());
150
151		// New name can be found
152		assert_eq!(
153			catalog.find_view_by_name(namespace_id, "new_name", CommitVersion(2)),
154			Some(view_v2.clone())
155		);
156
157		// Historical query at version 1 should still show old name
158		assert_eq!(catalog.find_view(view_id, CommitVersion(1)), Some(view_v1));
159
160		// Current version should show new name
161		assert_eq!(catalog.find_view(view_id, CommitVersion(2)), Some(view_v2));
162	}
163
164	#[test]
165	fn test_view_move_between_namespaces() {
166		let catalog = MaterializedCatalog::new();
167		let view_id = ViewId(1);
168		let namespace1 = NamespaceId(1);
169		let namespace2 = NamespaceId(2);
170
171		// Create view in namespace1
172		let view_v1 = create_test_view(view_id, namespace1, "movable_view");
173		catalog.set_view(view_id, CommitVersion(1), Some(view_v1.clone()));
174
175		// Verify it's in namespace1
176		assert!(catalog.find_view_by_name(namespace1, "movable_view", CommitVersion(1)).is_some());
177		assert!(catalog.find_view_by_name(namespace2, "movable_view", CommitVersion(1)).is_none());
178
179		// Move to namespace2
180		let mut view_v2 = view_v1.clone();
181		view_v2.namespace = namespace2;
182		catalog.set_view(view_id, CommitVersion(2), Some(view_v2.clone()));
183
184		// Should no longer be in namespace1
185		assert!(catalog.find_view_by_name(namespace1, "movable_view", CommitVersion(2)).is_none());
186
187		// Should now be in namespace2
188		assert!(catalog.find_view_by_name(namespace2, "movable_view", CommitVersion(2)).is_some());
189	}
190
191	#[test]
192	fn test_view_deletion() {
193		let catalog = MaterializedCatalog::new();
194		let view_id = ViewId(1);
195		let namespace_id = NamespaceId(1);
196
197		// Create and set view
198		let view = create_test_view(view_id, namespace_id, "deletable_view");
199		catalog.set_view(view_id, CommitVersion(1), Some(view.clone()));
200
201		// Verify it exists
202		assert_eq!(catalog.find_view(view_id, CommitVersion(1)), Some(view.clone()));
203		assert!(catalog.find_view_by_name(namespace_id, "deletable_view", CommitVersion(1)).is_some());
204
205		// Delete the view
206		catalog.set_view(view_id, CommitVersion(2), None);
207
208		// Should not exist at version 2
209		assert_eq!(catalog.find_view(view_id, CommitVersion(2)), None);
210		assert!(catalog.find_view_by_name(namespace_id, "deletable_view", CommitVersion(2)).is_none());
211
212		// Should still exist at version 1 (historical)
213		assert_eq!(catalog.find_view(view_id, CommitVersion(1)), Some(view));
214	}
215
216	#[test]
217	fn test_multiple_views_in_namespace() {
218		let catalog = MaterializedCatalog::new();
219		let namespace_id = NamespaceId(1);
220
221		let view1 = create_test_view(ViewId(1), namespace_id, "view1");
222		let view2 = create_test_view(ViewId(2), namespace_id, "view2");
223		let view3 = create_test_view(ViewId(3), namespace_id, "view3");
224
225		// Set multiple views
226		catalog.set_view(ViewId(1), CommitVersion(1), Some(view1.clone()));
227		catalog.set_view(ViewId(2), CommitVersion(1), Some(view2.clone()));
228		catalog.set_view(ViewId(3), CommitVersion(1), Some(view3.clone()));
229
230		// All should be findable
231		assert_eq!(catalog.find_view_by_name(namespace_id, "view1", CommitVersion(1)), Some(view1));
232		assert_eq!(catalog.find_view_by_name(namespace_id, "view2", CommitVersion(1)), Some(view2));
233		assert_eq!(catalog.find_view_by_name(namespace_id, "view3", CommitVersion(1)), Some(view3));
234	}
235
236	#[test]
237	fn test_view_versioning() {
238		let catalog = MaterializedCatalog::new();
239		let view_id = ViewId(1);
240		let namespace_id = NamespaceId(1);
241
242		// Create multiple versions
243		let view_v1 = create_test_view(view_id, namespace_id, "view_v1");
244		let mut view_v2 = view_v1.clone();
245		view_v2.name = "view_v2".to_string();
246		let mut view_v3 = view_v2.clone();
247		view_v3.name = "view_v3".to_string();
248
249		// Set at different versions
250		catalog.set_view(view_id, CommitVersion(10), Some(view_v1.clone()));
251		catalog.set_view(view_id, CommitVersion(20), Some(view_v2.clone()));
252		catalog.set_view(view_id, CommitVersion(30), Some(view_v3.clone()));
253
254		// Query at different versions
255		assert_eq!(catalog.find_view(view_id, CommitVersion(5)), None);
256		assert_eq!(catalog.find_view(view_id, CommitVersion(10)), Some(view_v1.clone()));
257		assert_eq!(catalog.find_view(view_id, CommitVersion(15)), Some(view_v1));
258		assert_eq!(catalog.find_view(view_id, CommitVersion(20)), Some(view_v2.clone()));
259		assert_eq!(catalog.find_view(view_id, CommitVersion(25)), Some(view_v2));
260		assert_eq!(catalog.find_view(view_id, CommitVersion(30)), Some(view_v3.clone()));
261		assert_eq!(catalog.find_view(view_id, CommitVersion(100)), Some(view_v3));
262	}
263}