reifydb_catalog/store/dictionary/
create.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	diagnostic::catalog::dictionary_already_exists,
6	interface::{CommandTransaction, DictionaryDef, DictionaryId, NamespaceId},
7	return_error,
8};
9use reifydb_type::{Fragment, Type};
10
11use crate::{CatalogStore, store::sequence::SystemSequence};
12
13#[derive(Debug, Clone)]
14pub struct DictionaryToCreate {
15	pub fragment: Option<Fragment>,
16	pub dictionary: String,
17	pub namespace: NamespaceId,
18	pub value_type: Type,
19	pub id_type: Type,
20}
21
22impl CatalogStore {
23	pub async fn create_dictionary(
24		txn: &mut impl CommandTransaction,
25		to_create: DictionaryToCreate,
26	) -> crate::Result<DictionaryDef> {
27		let namespace_id = to_create.namespace;
28
29		// Check if dictionary already exists
30		if let Some(dictionary) =
31			CatalogStore::find_dictionary_by_name(txn, namespace_id, &to_create.dictionary).await?
32		{
33			let namespace = CatalogStore::get_namespace(txn, namespace_id).await?;
34			return_error!(dictionary_already_exists(
35				to_create.fragment.unwrap_or_else(|| Fragment::None),
36				&namespace.name,
37				&dictionary.name
38			));
39		}
40
41		// Allocate new dictionary ID
42		let dictionary_id = SystemSequence::next_dictionary_id(txn).await?;
43
44		// Store the dictionary
45		Self::store_dictionary(txn, dictionary_id, namespace_id, &to_create).await?;
46
47		// Link dictionary to namespace
48		Self::link_dictionary_to_namespace(txn, namespace_id, dictionary_id, &to_create.dictionary).await?;
49
50		// Initialize dictionary sequence counter to 0
51		Self::initialize_dictionary_sequence(txn, dictionary_id).await?;
52
53		Ok(Self::get_dictionary(txn, dictionary_id).await?)
54	}
55
56	async fn store_dictionary(
57		txn: &mut impl CommandTransaction,
58		dictionary: DictionaryId,
59		namespace: NamespaceId,
60		to_create: &DictionaryToCreate,
61	) -> crate::Result<()> {
62		use reifydb_core::key::DictionaryKey;
63
64		use crate::store::dictionary::layout::dictionary;
65
66		let mut row = dictionary::LAYOUT.allocate();
67		dictionary::LAYOUT.set_u64(&mut row, dictionary::ID, dictionary);
68		dictionary::LAYOUT.set_u64(&mut row, dictionary::NAMESPACE, namespace);
69		dictionary::LAYOUT.set_utf8(&mut row, dictionary::NAME, &to_create.dictionary);
70		dictionary::LAYOUT.set_u8(&mut row, dictionary::VALUE_TYPE, to_create.value_type.to_u8());
71		dictionary::LAYOUT.set_u8(&mut row, dictionary::ID_TYPE, to_create.id_type.to_u8());
72
73		txn.set(&DictionaryKey::encoded(dictionary), row).await?;
74
75		Ok(())
76	}
77
78	async fn link_dictionary_to_namespace(
79		txn: &mut impl CommandTransaction,
80		namespace: NamespaceId,
81		dictionary: DictionaryId,
82		name: &str,
83	) -> crate::Result<()> {
84		use reifydb_core::key::NamespaceDictionaryKey;
85
86		use crate::store::dictionary::layout::dictionary_namespace;
87
88		let mut row = dictionary_namespace::LAYOUT.allocate();
89		dictionary_namespace::LAYOUT.set_u64(&mut row, dictionary_namespace::ID, dictionary);
90		dictionary_namespace::LAYOUT.set_utf8(&mut row, dictionary_namespace::NAME, name);
91
92		txn.set(&NamespaceDictionaryKey::encoded(namespace, dictionary), row).await?;
93
94		Ok(())
95	}
96
97	async fn initialize_dictionary_sequence(
98		txn: &mut impl CommandTransaction,
99		dictionary: DictionaryId,
100	) -> crate::Result<()> {
101		use reifydb_core::{key::DictionarySequenceKey, util::CowVec, value::encoded::EncodedValues};
102
103		// Initialize sequence counter to 0
104		// This ensures StorageTracker begins tracking the dictionary immediately
105		let seq_key = DictionarySequenceKey::encoded(dictionary);
106		let initial_value = 0u128.to_be_bytes().to_vec();
107
108		txn.set(&seq_key, EncodedValues(CowVec::new(initial_value))).await?;
109
110		Ok(())
111	}
112}
113
114#[cfg(test)]
115mod tests {
116	use reifydb_core::interface::{MultiVersionQueryTransaction, NamespaceDictionaryKey};
117	use reifydb_engine::test_utils::create_test_command_transaction;
118	use reifydb_type::Type;
119
120	use super::*;
121	use crate::{store::dictionary::layout::dictionary_namespace, test_utils::ensure_test_namespace};
122
123	#[tokio::test]
124	async fn test_create_simple_dictionary() {
125		let mut txn = create_test_command_transaction().await;
126		let test_namespace = ensure_test_namespace(&mut txn).await;
127
128		let to_create = DictionaryToCreate {
129			namespace: test_namespace.id,
130			dictionary: "token_mints".to_string(),
131			value_type: Type::Utf8,
132			id_type: Type::Uint2,
133			fragment: None,
134		};
135
136		let result = CatalogStore::create_dictionary(&mut txn, to_create).await.unwrap();
137
138		assert!(result.id.0 > 0);
139		assert_eq!(result.namespace, test_namespace.id);
140		assert_eq!(result.name, "token_mints");
141		assert_eq!(result.value_type, Type::Utf8);
142		assert_eq!(result.id_type, Type::Uint2);
143	}
144
145	#[tokio::test]
146	async fn test_create_duplicate_dictionary() {
147		let mut txn = create_test_command_transaction().await;
148		let test_namespace = ensure_test_namespace(&mut txn).await;
149
150		let to_create = DictionaryToCreate {
151			namespace: test_namespace.id,
152			dictionary: "test_dict".to_string(),
153			value_type: Type::Utf8,
154			id_type: Type::Uint4,
155			fragment: None,
156		};
157
158		// First creation should succeed
159		let result = CatalogStore::create_dictionary(&mut txn, to_create.clone()).await.unwrap();
160		assert!(result.id.0 > 0);
161		assert_eq!(result.namespace, test_namespace.id);
162		assert_eq!(result.name, "test_dict");
163
164		// Second creation should fail with duplicate error
165		let err = CatalogStore::create_dictionary(&mut txn, to_create).await.unwrap_err();
166		assert_eq!(err.diagnostic().code, "CA_006");
167	}
168
169	#[tokio::test]
170	async fn test_dictionary_linked_to_namespace() {
171		let mut txn = create_test_command_transaction().await;
172		let test_namespace = ensure_test_namespace(&mut txn).await;
173
174		let to_create1 = DictionaryToCreate {
175			namespace: test_namespace.id,
176			dictionary: "dict1".to_string(),
177			value_type: Type::Utf8,
178			id_type: Type::Uint1,
179			fragment: None,
180		};
181
182		CatalogStore::create_dictionary(&mut txn, to_create1).await.unwrap();
183
184		let to_create2 = DictionaryToCreate {
185			namespace: test_namespace.id,
186			dictionary: "dict2".to_string(),
187			value_type: Type::Uint8,
188			id_type: Type::Uint2,
189			fragment: None,
190		};
191
192		CatalogStore::create_dictionary(&mut txn, to_create2).await.unwrap();
193
194		// Check namespace links
195		let links = txn
196			.range(NamespaceDictionaryKey::full_scan(test_namespace.id))
197			.await
198			.unwrap()
199			.items
200			.into_iter()
201			.collect::<Vec<_>>();
202		assert_eq!(links.len(), 2);
203
204		// Check first link (descending order, so dict2 comes first)
205		let link = &links[0];
206		let row = &link.values;
207		let id2 = dictionary_namespace::LAYOUT.get_u64(row, dictionary_namespace::ID);
208		assert!(id2 > 0);
209		assert_eq!(dictionary_namespace::LAYOUT.get_utf8(row, dictionary_namespace::NAME), "dict2");
210
211		// Check second link (dict1 comes second)
212		let link = &links[1];
213		let row = &link.values;
214		let id1 = dictionary_namespace::LAYOUT.get_u64(row, dictionary_namespace::ID);
215		assert!(id2 > id1);
216		assert_eq!(dictionary_namespace::LAYOUT.get_utf8(row, dictionary_namespace::NAME), "dict1");
217	}
218
219	#[tokio::test]
220	async fn test_create_dictionary_with_various_types() {
221		let mut txn = create_test_command_transaction().await;
222		let test_namespace = ensure_test_namespace(&mut txn).await;
223
224		// Test with Uint1 ID type
225		let to_create = DictionaryToCreate {
226			namespace: test_namespace.id,
227			dictionary: "small_dict".to_string(),
228			value_type: Type::Utf8,
229			id_type: Type::Uint1,
230			fragment: None,
231		};
232		let result = CatalogStore::create_dictionary(&mut txn, to_create).await.unwrap();
233		assert_eq!(result.id_type, Type::Uint1);
234
235		// Test with Uint8 ID type
236		let to_create = DictionaryToCreate {
237			namespace: test_namespace.id,
238			dictionary: "large_dict".to_string(),
239			value_type: Type::Blob,
240			id_type: Type::Uint8,
241			fragment: None,
242		};
243		let result = CatalogStore::create_dictionary(&mut txn, to_create).await.unwrap();
244		assert_eq!(result.id_type, Type::Uint8);
245		assert_eq!(result.value_type, Type::Blob);
246	}
247}