Skip to main content

reifydb_engine/vm/instruction/dml/
dictionary_insert.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::sync::Arc;
5
6use reifydb_core::{
7	error::diagnostic::catalog::{dictionary_not_found, namespace_not_found},
8	interface::catalog::{
9		config::{ConfigKey, GetConfig},
10		policy::{DataOp, PolicyTargetType},
11	},
12	value::column::{ColumnWithName, buffer::ColumnBuffer, columns::Columns},
13};
14use reifydb_evaluate::stack::SymbolTable;
15use reifydb_rql::nodes::InsertDictionaryNode;
16use reifydb_transaction::transaction::Transaction;
17use reifydb_value::{
18	fragment::Fragment,
19	params::Params,
20	return_error,
21	value::{Value, dictionary::DictionaryEntryId, value_type::ValueType},
22};
23
24use super::returning::evaluate_returning;
25use crate::{
26	Result,
27	policy::PolicyEvaluator,
28	transaction::operation::dictionary::DictionaryOperations,
29	vm::{
30		services::Services,
31		volcano::{
32			compile::compile,
33			query::{QueryContext, QueryNode, query_budget},
34		},
35	},
36};
37
38pub(crate) fn insert_dictionary(
39	services: &Arc<Services>,
40	txn: &mut Transaction<'_>,
41	plan: InsertDictionaryNode,
42	symbols: &mut SymbolTable,
43) -> Result<Columns> {
44	let namespace_name = plan.target.namespace().name();
45
46	let Some(namespace) = services.catalog.find_namespace_by_name(txn, namespace_name)? else {
47		return_error!(namespace_not_found(Fragment::internal(namespace_name), namespace_name));
48	};
49
50	let dictionary_name = plan.target.name();
51	let Some(dictionary) = services.catalog.find_dictionary_by_name(txn, namespace.id(), dictionary_name)? else {
52		let fragment = plan.target.identifier().clone();
53		return_error!(dictionary_not_found(fragment.clone(), namespace_name, dictionary_name,));
54	};
55
56	let execution_context = Arc::new(QueryContext {
57		services: services.clone(),
58		source: None,
59		batch_size: services.catalog.get_config_uint2(ConfigKey::QueryRowBatchSize) as u64,
60		params: Params::None,
61		symbols: symbols.clone(),
62		identity: txn.identity(),
63		memory: query_budget(services),
64	});
65
66	let mut input_node = compile(*plan.input, txn, execution_context.clone());
67
68	input_node.initialize(txn, &execution_context)?;
69
70	let mut ids: Vec<Value> = Vec::new();
71	let mut values: Vec<Value> = Vec::new();
72	let mut mutable_context = (*execution_context).clone();
73
74	while let Some(columns) = input_node.next(txn, &mut mutable_context)? {
75		PolicyEvaluator::new(services, symbols).enforce_write_policies(
76			txn,
77			namespace_name,
78			dictionary_name,
79			DataOp::Insert,
80			&columns,
81			PolicyTargetType::Dictionary,
82		)?;
83
84		let row_count = columns.row_count();
85
86		for row_idx in 0..row_count {
87			let value = if let Some(value_column) = columns.iter().find(|col| col.name() == "value") {
88				value_column.data().get_value(row_idx)
89			} else if let Some(first_column) = columns.iter().next() {
90				first_column.data().get_value(row_idx)
91			} else {
92				Value::none()
93			};
94
95			if matches!(value, Value::None { .. }) {
96				continue;
97			}
98
99			let coerced_value = coerce_value_to_dictionary_type(value, dictionary.value_type.clone())?;
100
101			let entry_id = txn.insert_into_dictionary(&dictionary, &coerced_value)?;
102
103			let id_value = match entry_id {
104				DictionaryEntryId::U1(v) => Value::Uint1(v),
105				DictionaryEntryId::U2(v) => Value::Uint2(v),
106				DictionaryEntryId::U4(v) => Value::Uint4(v),
107				DictionaryEntryId::U8(v) => Value::Uint8(v),
108				DictionaryEntryId::U16(v) => Value::Uint16(v),
109			};
110
111			ids.push(id_value);
112			values.push(coerced_value);
113		}
114	}
115
116	if let Some(returning_exprs) = &plan.returning {
117		if ids.is_empty() {
118			return evaluate_returning(
119				services,
120				symbols,
121				returning_exprs,
122				Columns::empty(),
123				txn.identity(),
124			);
125		}
126		let id_column = build_id_column(&ids, dictionary.id_type)?;
127		let value_column = build_value_column(&values, dictionary.value_type)?;
128		let columns = Columns::new(vec![id_column, value_column]);
129		return evaluate_returning(services, symbols, returning_exprs, columns, txn.identity());
130	}
131
132	if ids.is_empty() {
133		return Ok(Columns::new(vec![
134			ColumnWithName::new(
135				Fragment::internal("namespace"),
136				ColumnBuffer::utf8(vec![namespace.name()]),
137			),
138			ColumnWithName::new(
139				Fragment::internal("dictionary"),
140				ColumnBuffer::utf8(vec![dictionary.name.clone()]),
141			),
142			ColumnWithName::new(Fragment::internal("inserted"), ColumnBuffer::uint8(vec![0])),
143		]));
144	}
145
146	let id_column = build_id_column(&ids, dictionary.id_type)?;
147
148	let value_column = build_value_column(&values, dictionary.value_type)?;
149
150	Ok(Columns::new(vec![
151		ColumnWithName::new(
152			Fragment::internal("namespace"),
153			ColumnBuffer::utf8(vec![namespace.name(); ids.len()]),
154		),
155		ColumnWithName::new(
156			Fragment::internal("dictionary"),
157			ColumnBuffer::utf8(vec![dictionary.name.clone(); ids.len()]),
158		),
159		id_column,
160		value_column,
161	]))
162}
163
164fn coerce_value_to_dictionary_type(value: Value, target_type: ValueType) -> Result<Value> {
165	match (&value, target_type) {
166		(Value::Utf8(_), ValueType::Utf8) => Ok(value),
167		(Value::Int1(_), ValueType::Int1) => Ok(value),
168		(Value::Int2(_), ValueType::Int2) => Ok(value),
169		(Value::Int4(_), ValueType::Int4) => Ok(value),
170		(Value::Int8(_), ValueType::Int8) => Ok(value),
171		(Value::Int16(_), ValueType::Int16) => Ok(value),
172		(Value::Uint1(_), ValueType::Uint1) => Ok(value),
173		(Value::Uint2(_), ValueType::Uint2) => Ok(value),
174		(Value::Uint4(_), ValueType::Uint4) => Ok(value),
175		(Value::Uint8(_), ValueType::Uint8) => Ok(value),
176		(Value::Uint16(_), ValueType::Uint16) => Ok(value),
177		(Value::Float4(_), ValueType::Float4) => Ok(value),
178		(Value::Float8(_), ValueType::Float8) => Ok(value),
179		(Value::Boolean(_), ValueType::Boolean) => Ok(value),
180		(Value::Date(_), ValueType::Date) => Ok(value),
181		(Value::DateTime(_), ValueType::DateTime) => Ok(value),
182		(Value::Time(_), ValueType::Time) => Ok(value),
183		(Value::Duration(_), ValueType::Duration) => Ok(value),
184		(Value::Uuid4(_), ValueType::Uuid4) => Ok(value),
185		(Value::Uuid7(_), ValueType::Uuid7) => Ok(value),
186		(Value::Blob(_), ValueType::Blob) => Ok(value),
187		(Value::Decimal(_), ValueType::Decimal) => Ok(value),
188		// TODO: Add more coercion cases as needed
189		_ => Ok(value),
190	}
191}
192
193fn build_id_column(ids: &[Value], id_type: ValueType) -> Result<ColumnWithName> {
194	let data = match id_type {
195		ValueType::Uint1 => {
196			let vals: Vec<u8> = ids
197				.iter()
198				.map(|v| match v {
199					Value::Uint1(n) => *n,
200					_ => 0,
201				})
202				.collect();
203			ColumnBuffer::uint1(vals)
204		}
205		ValueType::Uint2 => {
206			let vals: Vec<u16> = ids
207				.iter()
208				.map(|v| match v {
209					Value::Uint2(n) => *n,
210					_ => 0,
211				})
212				.collect();
213			ColumnBuffer::uint2(vals)
214		}
215		ValueType::Uint4 => {
216			let vals: Vec<u32> = ids
217				.iter()
218				.map(|v| match v {
219					Value::Uint4(n) => *n,
220					_ => 0,
221				})
222				.collect();
223			ColumnBuffer::uint4(vals)
224		}
225		ValueType::Uint8 => {
226			let vals: Vec<u64> = ids
227				.iter()
228				.map(|v| match v {
229					Value::Uint8(n) => *n,
230					_ => 0,
231				})
232				.collect();
233			ColumnBuffer::uint8(vals)
234		}
235		ValueType::Uint16 => {
236			let vals: Vec<u128> = ids
237				.iter()
238				.map(|v| match v {
239					Value::Uint16(n) => *n,
240					_ => 0,
241				})
242				.collect();
243			ColumnBuffer::uint16(vals)
244		}
245		_ => {
246			let vals: Vec<u64> = ids
247				.iter()
248				.map(|v| match v {
249					Value::Uint8(n) => *n,
250					_ => 0,
251				})
252				.collect();
253			ColumnBuffer::uint8(vals)
254		}
255	};
256
257	Ok(ColumnWithName {
258		name: Fragment::internal("id"),
259		data,
260	})
261}
262
263fn build_value_column(values: &[Value], value_type: ValueType) -> Result<ColumnWithName> {
264	let data = match value_type {
265		ValueType::Utf8 => {
266			let vals: Vec<String> = values
267				.iter()
268				.map(|v| match v {
269					Value::Utf8(s) => s.clone(),
270					_ => format!("{:?}", v),
271				})
272				.collect();
273			ColumnBuffer::utf8(vals)
274		}
275		ValueType::Int1 => {
276			let vals: Vec<i8> = values
277				.iter()
278				.map(|v| match v {
279					Value::Int1(n) => *n,
280					_ => 0,
281				})
282				.collect();
283			ColumnBuffer::int1(vals)
284		}
285		ValueType::Int2 => {
286			let vals: Vec<i16> = values
287				.iter()
288				.map(|v| match v {
289					Value::Int2(n) => *n,
290					_ => 0,
291				})
292				.collect();
293			ColumnBuffer::int2(vals)
294		}
295		ValueType::Int4 => {
296			let vals: Vec<i32> = values
297				.iter()
298				.map(|v| match v {
299					Value::Int4(n) => *n,
300					_ => 0,
301				})
302				.collect();
303			ColumnBuffer::int4(vals)
304		}
305		ValueType::Int8 => {
306			let vals: Vec<i64> = values
307				.iter()
308				.map(|v| match v {
309					Value::Int8(n) => *n,
310					_ => 0,
311				})
312				.collect();
313			ColumnBuffer::int8(vals)
314		}
315		ValueType::Uint1 => {
316			let vals: Vec<u8> = values
317				.iter()
318				.map(|v| match v {
319					Value::Uint1(n) => *n,
320					_ => 0,
321				})
322				.collect();
323			ColumnBuffer::uint1(vals)
324		}
325		ValueType::Uint2 => {
326			let vals: Vec<u16> = values
327				.iter()
328				.map(|v| match v {
329					Value::Uint2(n) => *n,
330					_ => 0,
331				})
332				.collect();
333			ColumnBuffer::uint2(vals)
334		}
335		ValueType::Uint4 => {
336			let vals: Vec<u32> = values
337				.iter()
338				.map(|v| match v {
339					Value::Uint4(n) => *n,
340					_ => 0,
341				})
342				.collect();
343			ColumnBuffer::uint4(vals)
344		}
345		ValueType::Uint8 => {
346			let vals: Vec<u64> = values
347				.iter()
348				.map(|v| match v {
349					Value::Uint8(n) => *n,
350					_ => 0,
351				})
352				.collect();
353			ColumnBuffer::uint8(vals)
354		}
355		_ => {
356			let vals: Vec<String> = values.iter().map(|v| format!("{:?}", v)).collect();
357			ColumnBuffer::utf8(vals)
358		}
359	};
360
361	Ok(ColumnWithName {
362		name: Fragment::internal("value"),
363		data,
364	})
365}