reifydb_catalog/materialized/
dictionary.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::{DictionaryDef, DictionaryId, NamespaceId},
7};
8
9use crate::materialized::{MaterializedCatalog, MultiVersionDictionaryDef};
10
11impl MaterializedCatalog {
12	/// Find a dictionary by ID at a specific version
13	pub fn find_dictionary(&self, dictionary: DictionaryId, version: CommitVersion) -> Option<DictionaryDef> {
14		self.dictionaries.get(&dictionary).and_then(|entry| {
15			let multi = entry.value();
16			multi.get(version)
17		})
18	}
19
20	/// Find a dictionary by name in a namespace at a specific version
21	pub fn find_dictionary_by_name(
22		&self,
23		namespace: NamespaceId,
24		name: &str,
25		version: CommitVersion,
26	) -> Option<DictionaryDef> {
27		self.dictionaries_by_name.get(&(namespace, name.to_string())).and_then(|entry| {
28			let dictionary_id = *entry.value();
29			self.find_dictionary(dictionary_id, version)
30		})
31	}
32
33	pub fn set_dictionary(&self, id: DictionaryId, version: CommitVersion, dictionary: Option<DictionaryDef>) {
34		if let Some(entry) = self.dictionaries.get(&id) {
35			if let Some(pre) = entry.value().get_latest() {
36				// Remove old name from index
37				self.dictionaries_by_name.remove(&(pre.namespace, pre.name.clone()));
38			}
39		}
40
41		let multi = self.dictionaries.get_or_insert_with(id, MultiVersionDictionaryDef::new);
42		if let Some(new) = dictionary {
43			self.dictionaries_by_name.insert((new.namespace, new.name.clone()), id);
44			multi.value().insert(version, new);
45		} else {
46			multi.value().remove(version);
47		}
48	}
49}
50
51#[cfg(test)]
52mod tests {
53	use reifydb_type::Type;
54
55	use super::*;
56
57	fn create_test_dictionary(id: DictionaryId, namespace: NamespaceId, name: &str) -> DictionaryDef {
58		DictionaryDef {
59			id,
60			namespace,
61			name: name.to_string(),
62			value_type: Type::Utf8,
63			id_type: Type::Uint4,
64		}
65	}
66
67	#[test]
68	fn test_set_and_find_dictionary() {
69		let catalog = MaterializedCatalog::new();
70		let dict_id = DictionaryId(1);
71		let namespace_id = NamespaceId(1);
72		let dict = create_test_dictionary(dict_id, namespace_id, "test_dict");
73
74		// Set dictionary at version 1
75		catalog.set_dictionary(dict_id, CommitVersion(1), Some(dict.clone()));
76
77		// Find dictionary at version 1
78		let found = catalog.find_dictionary(dict_id, CommitVersion(1));
79		assert_eq!(found, Some(dict.clone()));
80
81		// Find dictionary at later version (should return same dictionary)
82		let found = catalog.find_dictionary(dict_id, CommitVersion(5));
83		assert_eq!(found, Some(dict));
84
85		// Dictionary shouldn't exist at version 0
86		let found = catalog.find_dictionary(dict_id, CommitVersion(0));
87		assert_eq!(found, None);
88	}
89
90	#[test]
91	fn test_find_dictionary_by_name() {
92		let catalog = MaterializedCatalog::new();
93		let dict_id = DictionaryId(1);
94		let namespace_id = NamespaceId(1);
95		let dict = create_test_dictionary(dict_id, namespace_id, "named_dict");
96
97		// Set dictionary
98		catalog.set_dictionary(dict_id, CommitVersion(1), Some(dict.clone()));
99
100		// Find by name
101		let found = catalog.find_dictionary_by_name(namespace_id, "named_dict", CommitVersion(1));
102		assert_eq!(found, Some(dict));
103
104		// Shouldn't find with wrong name
105		let found = catalog.find_dictionary_by_name(namespace_id, "wrong_name", CommitVersion(1));
106		assert_eq!(found, None);
107
108		// Shouldn't find in wrong namespace
109		let found = catalog.find_dictionary_by_name(NamespaceId(2), "named_dict", CommitVersion(1));
110		assert_eq!(found, None);
111	}
112
113	#[test]
114	fn test_dictionary_rename() {
115		let catalog = MaterializedCatalog::new();
116		let dict_id = DictionaryId(1);
117		let namespace_id = NamespaceId(1);
118
119		// Create and set initial dictionary
120		let dict_v1 = create_test_dictionary(dict_id, namespace_id, "old_name");
121		catalog.set_dictionary(dict_id, CommitVersion(1), Some(dict_v1.clone()));
122
123		// Verify initial state
124		assert!(catalog.find_dictionary_by_name(namespace_id, "old_name", CommitVersion(1)).is_some());
125		assert!(catalog.find_dictionary_by_name(namespace_id, "new_name", CommitVersion(1)).is_none());
126
127		// Rename the dictionary
128		let mut dict_v2 = dict_v1.clone();
129		dict_v2.name = "new_name".to_string();
130		catalog.set_dictionary(dict_id, CommitVersion(2), Some(dict_v2.clone()));
131
132		// Old name should be gone
133		assert!(catalog.find_dictionary_by_name(namespace_id, "old_name", CommitVersion(2)).is_none());
134
135		// New name can be found
136		assert_eq!(
137			catalog.find_dictionary_by_name(namespace_id, "new_name", CommitVersion(2)),
138			Some(dict_v2.clone())
139		);
140
141		// Historical query at version 1 should still show old name
142		assert_eq!(catalog.find_dictionary(dict_id, CommitVersion(1)), Some(dict_v1));
143
144		// Current version should show new name
145		assert_eq!(catalog.find_dictionary(dict_id, CommitVersion(2)), Some(dict_v2));
146	}
147
148	#[test]
149	fn test_dictionary_move_between_namespaces() {
150		let catalog = MaterializedCatalog::new();
151		let dict_id = DictionaryId(1);
152		let namespace1 = NamespaceId(1);
153		let namespace2 = NamespaceId(2);
154
155		// Create dictionary in namespace1
156		let dict_v1 = create_test_dictionary(dict_id, namespace1, "movable_dict");
157		catalog.set_dictionary(dict_id, CommitVersion(1), Some(dict_v1.clone()));
158
159		// Verify it's in namespace1
160		assert!(catalog.find_dictionary_by_name(namespace1, "movable_dict", CommitVersion(1)).is_some());
161		assert!(catalog.find_dictionary_by_name(namespace2, "movable_dict", CommitVersion(1)).is_none());
162
163		// Move to namespace2
164		let mut dict_v2 = dict_v1.clone();
165		dict_v2.namespace = namespace2;
166		catalog.set_dictionary(dict_id, CommitVersion(2), Some(dict_v2.clone()));
167
168		// Should no longer be in namespace1
169		assert!(catalog.find_dictionary_by_name(namespace1, "movable_dict", CommitVersion(2)).is_none());
170
171		// Should now be in namespace2
172		assert!(catalog.find_dictionary_by_name(namespace2, "movable_dict", CommitVersion(2)).is_some());
173	}
174
175	#[test]
176	fn test_dictionary_deletion() {
177		let catalog = MaterializedCatalog::new();
178		let dict_id = DictionaryId(1);
179		let namespace_id = NamespaceId(1);
180
181		// Create and set dictionary
182		let dict = create_test_dictionary(dict_id, namespace_id, "deletable_dict");
183		catalog.set_dictionary(dict_id, CommitVersion(1), Some(dict.clone()));
184
185		// Verify it exists
186		assert_eq!(catalog.find_dictionary(dict_id, CommitVersion(1)), Some(dict.clone()));
187		assert!(catalog.find_dictionary_by_name(namespace_id, "deletable_dict", CommitVersion(1)).is_some());
188
189		// Delete the dictionary
190		catalog.set_dictionary(dict_id, CommitVersion(2), None);
191
192		// Should not exist at version 2
193		assert_eq!(catalog.find_dictionary(dict_id, CommitVersion(2)), None);
194		assert!(catalog.find_dictionary_by_name(namespace_id, "deletable_dict", CommitVersion(2)).is_none());
195
196		// Should still exist at version 1 (historical)
197		assert_eq!(catalog.find_dictionary(dict_id, CommitVersion(1)), Some(dict));
198	}
199
200	#[test]
201	fn test_multiple_dictionaries_in_namespace() {
202		let catalog = MaterializedCatalog::new();
203		let namespace_id = NamespaceId(1);
204
205		let dict1 = create_test_dictionary(DictionaryId(1), namespace_id, "dict1");
206		let dict2 = create_test_dictionary(DictionaryId(2), namespace_id, "dict2");
207		let dict3 = create_test_dictionary(DictionaryId(3), namespace_id, "dict3");
208
209		// Set multiple dictionaries
210		catalog.set_dictionary(DictionaryId(1), CommitVersion(1), Some(dict1.clone()));
211		catalog.set_dictionary(DictionaryId(2), CommitVersion(1), Some(dict2.clone()));
212		catalog.set_dictionary(DictionaryId(3), CommitVersion(1), Some(dict3.clone()));
213
214		// All should be findable
215		assert_eq!(catalog.find_dictionary_by_name(namespace_id, "dict1", CommitVersion(1)), Some(dict1));
216		assert_eq!(catalog.find_dictionary_by_name(namespace_id, "dict2", CommitVersion(1)), Some(dict2));
217		assert_eq!(catalog.find_dictionary_by_name(namespace_id, "dict3", CommitVersion(1)), Some(dict3));
218	}
219
220	#[test]
221	fn test_dictionary_versioning() {
222		let catalog = MaterializedCatalog::new();
223		let dict_id = DictionaryId(1);
224		let namespace_id = NamespaceId(1);
225
226		// Create multiple versions
227		let dict_v1 = create_test_dictionary(dict_id, namespace_id, "dict_v1");
228		let mut dict_v2 = dict_v1.clone();
229		dict_v2.name = "dict_v2".to_string();
230		let mut dict_v3 = dict_v2.clone();
231		dict_v3.name = "dict_v3".to_string();
232
233		// Set at different versions
234		catalog.set_dictionary(dict_id, CommitVersion(10), Some(dict_v1.clone()));
235		catalog.set_dictionary(dict_id, CommitVersion(20), Some(dict_v2.clone()));
236		catalog.set_dictionary(dict_id, CommitVersion(30), Some(dict_v3.clone()));
237
238		// Query at different versions
239		assert_eq!(catalog.find_dictionary(dict_id, CommitVersion(5)), None);
240		assert_eq!(catalog.find_dictionary(dict_id, CommitVersion(10)), Some(dict_v1.clone()));
241		assert_eq!(catalog.find_dictionary(dict_id, CommitVersion(15)), Some(dict_v1));
242		assert_eq!(catalog.find_dictionary(dict_id, CommitVersion(20)), Some(dict_v2.clone()));
243		assert_eq!(catalog.find_dictionary(dict_id, CommitVersion(25)), Some(dict_v2));
244		assert_eq!(catalog.find_dictionary(dict_id, CommitVersion(30)), Some(dict_v3.clone()));
245		assert_eq!(catalog.find_dictionary(dict_id, CommitVersion(100)), Some(dict_v3));
246	}
247}