Skip to main content

reifydb_engine/vm/instruction/dml/
table_insert.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::{
5	collections::{HashMap, HashSet},
6	sync::Arc,
7};
8
9use reifydb_codec::row::{
10	bytes::{EncodedBytes, RowBuilder},
11	pod::EncodedPodRow,
12	shape::RowShape,
13	table::{EncodedTableRow, EncodedTableRowBuilder},
14};
15use reifydb_core::{
16	error::diagnostic::{
17		catalog::{namespace_not_found, table_not_found},
18		index::primary_key_violation,
19	},
20	interface::{
21		catalog::{
22			config::{ConfigKey, GetConfig},
23			id::IndexId,
24			key::PrimaryKey,
25			namespace::Namespace,
26			object::ObjectId,
27			policy::{DataOp, PolicyTargetType},
28			table::Table,
29		},
30		resolved::{ResolvedColumn, ResolvedNamespace, ResolvedObject, ResolvedTable},
31	},
32	internal_error,
33	key::catalog::IndexEntryKey,
34	partition::partition_col_indices,
35	value::column::columns::Columns,
36};
37use reifydb_evaluate::stack::SymbolTable;
38use reifydb_rql::nodes::InsertTableNode;
39use reifydb_transaction::transaction::Transaction;
40use reifydb_value::{
41	fragment::Fragment,
42	params::Params,
43	return_error,
44	value::{Value, identity::IdentityId, partition::Partition, row_number::RowNumber},
45};
46use tracing::instrument;
47
48use super::{
49	context::TableTarget,
50	primary_key,
51	returning::{decode_returning_dictionaries, decode_rows_to_columns, evaluate_returning},
52	shape::get_or_create_table_shape,
53};
54use crate::{
55	Result,
56	partition::{partition_values, resolve_partition},
57	policy::PolicyEvaluator,
58	transaction::operation::{dictionary::DictionaryOperations, table::TableOperations},
59	vm::{
60		instruction::dml::{coerce::coerce_value_to_column_type, time::resolve_time},
61		services::Services,
62		volcano::{
63			compile::compile,
64			query::{QueryContext, QueryNode, query_budget},
65		},
66	},
67};
68
69#[instrument(name = "mutate::table::insert", level = "trace", skip_all)]
70pub(crate) fn insert_table(
71	services: &Arc<Services>,
72	txn: &mut Transaction<'_>,
73	plan: InsertTableNode,
74	symbols: &mut SymbolTable,
75) -> Result<Columns> {
76	let InsertTableNode {
77		input,
78		target,
79		returning,
80	} = plan;
81	let (namespace, table) = resolve_insert_table_target(services, txn, &target)?;
82	let shape = get_or_create_table_shape(&services.catalog, &table, txn)?;
83	let target_data = TableTarget {
84		namespace: &namespace,
85		table: &table,
86		fragment: target.identifier(),
87	};
88	let context = build_insert_table_query_context(services, &target_data, symbols, txn.identity());
89	let mut input_node = compile(*input, txn, context.clone());
90	input_node.initialize(txn, &context)?;
91
92	let validated = validate_and_encode_input_rows(
93		services,
94		txn,
95		&target_data,
96		&shape,
97		&context,
98		symbols,
99		&mut input_node,
100	)?;
101
102	if !table.partition_by.is_empty() {
103		let indices = partition_col_indices(&table.columns, &table.partition_by);
104		let mut verified = HashSet::new();
105		for row in &validated {
106			let values = partition_values(&shape, row, &indices);
107			let partition = Partition::of(&values);
108			resolve_partition(txn, ObjectId::Table(table.id), partition, &values, &mut verified)?;
109		}
110	}
111
112	let total_rows = validated.len();
113	if total_rows == 0 {
114		return Ok(insert_table_result(namespace.name(), &table.name, 0));
115	}
116
117	let row_numbers = services.catalog.next_row_number_batch(txn, table.id, total_rows as u64)?;
118	assert_eq!(row_numbers.len(), validated.len());
119
120	let pk_def = primary_key::get_primary_key(&services.catalog, txn, &table)?;
121	let pk_ctx = pk_def.as_ref().map(|pk| PkContext {
122		pk_def: pk,
123	});
124	let returned_rows = insert_validated_table_rows(
125		txn,
126		&target_data,
127		&shape,
128		&validated,
129		&row_numbers,
130		returning.is_some(),
131		pk_ctx.as_ref(),
132	)?;
133
134	if let Some(returning_exprs) = &returning {
135		let mut columns = decode_rows_to_columns(&shape, &returned_rows);
136		decode_returning_dictionaries(services, txn, &table.columns, &mut columns)?;
137		return evaluate_returning(services, symbols, returning_exprs, columns, txn.identity());
138	}
139	Ok(insert_table_result(namespace.name(), &table.name, total_rows as u64))
140}
141
142struct PkContext<'a> {
143	pk_def: &'a PrimaryKey,
144}
145
146struct ColumnView<'a> {
147	columns: &'a Columns,
148	column_map: &'a HashMap<&'a str, usize>,
149}
150
151#[inline]
152fn resolve_insert_table_target(
153	services: &Arc<Services>,
154	txn: &mut Transaction<'_>,
155	target: &ResolvedTable,
156) -> Result<(Namespace, Table)> {
157	let namespace_name = target.namespace().name();
158	let Some(namespace) = services.catalog.find_namespace_by_name(txn, namespace_name)? else {
159		return_error!(namespace_not_found(Fragment::internal(namespace_name), namespace_name));
160	};
161	let table_name = target.name();
162	let Some(table) = services.catalog.find_table_by_name(txn, namespace.id(), table_name)? else {
163		let fragment = target.identifier().clone();
164		return_error!(table_not_found(fragment.clone(), namespace_name, table_name,));
165	};
166	Ok((namespace, table))
167}
168
169#[inline]
170fn build_insert_table_query_context(
171	services: &Arc<Services>,
172	target: &TableTarget<'_>,
173	symbols: &SymbolTable,
174	identity: IdentityId,
175) -> Arc<QueryContext> {
176	let namespace_ident = Fragment::internal(target.namespace.name());
177	let resolved_namespace = ResolvedNamespace::new(namespace_ident, target.namespace.clone());
178	let table_ident = Fragment::internal(target.table.name.clone());
179	let resolved_table = ResolvedTable::new(table_ident, resolved_namespace, target.table.clone());
180	Arc::new(QueryContext {
181		services: services.clone(),
182		source: Some(ResolvedObject::Table(resolved_table)),
183		batch_size: services.catalog.get_config_uint2(ConfigKey::QueryRowBatchSize) as u64,
184		params: Params::None,
185		symbols: symbols.clone(),
186		identity,
187		memory: query_budget(services),
188	})
189}
190
191fn validate_and_encode_input_rows(
192	services: &Arc<Services>,
193	txn: &mut Transaction<'_>,
194	target: &TableTarget<'_>,
195	shape: &RowShape,
196	context: &Arc<QueryContext>,
197	symbols: &SymbolTable,
198	input_node: &mut Box<dyn QueryNode>,
199) -> Result<Vec<EncodedBytes>> {
200	let mut validated: Vec<EncodedBytes> = Vec::new();
201	let mut mutable_context = (**context).clone();
202	while let Some(columns) = input_node.next(txn, &mut mutable_context)? {
203		PolicyEvaluator::new(services, symbols).enforce_write_policies(
204			txn,
205			target.namespace.name(),
206			&target.table.name,
207			DataOp::Insert,
208			&columns,
209			PolicyTargetType::Table,
210		)?;
211		let mut column_map: HashMap<&str, usize> = HashMap::new();
212		for (idx, col) in columns.iter().enumerate() {
213			column_map.insert(col.name().text(), idx);
214		}
215		let view = ColumnView {
216			columns: &columns,
217			column_map: &column_map,
218		};
219		let row_count = columns.row_count();
220		for row_idx in 0..row_count {
221			validated.push(build_insert_table_row(services, txn, target, shape, &view, context, row_idx)?);
222		}
223	}
224	Ok(validated)
225}
226
227#[inline]
228fn build_insert_table_row(
229	services: &Arc<Services>,
230	txn: &mut Transaction<'_>,
231	target: &TableTarget<'_>,
232	shape: &RowShape,
233	view: &ColumnView<'_>,
234	context: &Arc<QueryContext>,
235	row_idx: usize,
236) -> Result<EncodedBytes> {
237	let mut row = shape.allocate_table();
238	for (table_idx, table_column) in target.table.columns.iter().enumerate() {
239		let mut value = if let Some(&input_idx) = view.column_map.get(table_column.name.as_str()) {
240			view.columns[input_idx].get_value(row_idx)
241		} else {
242			Value::none()
243		};
244		if table_column.auto_increment && matches!(value, Value::None { .. }) {
245			value = services.catalog.column_sequence_next_value(txn, target.table.id, table_column.id)?;
246		}
247		let column_ident = view
248			.column_map
249			.get(table_column.name.as_str())
250			.map(|&idx| view.columns.name_at(idx).clone())
251			.unwrap_or_else(|| Fragment::internal(table_column.name.clone()));
252		let resolved_column = ResolvedColumn::new(
253			column_ident.clone(),
254			context.source.clone().unwrap(),
255			table_column.clone(),
256		);
257		value = coerce_value_to_column_type(
258			value,
259			table_column.constraint.get_type(),
260			resolved_column,
261			context,
262		)?;
263		if let Err(mut e) = table_column.constraint.validate(&value) {
264			e.0.fragment = column_ident.clone();
265			return Err(e);
266		}
267		let value = if let Some(dict_id) = table_column.dictionary_id {
268			let dictionary = services.catalog.find_dictionary(txn, dict_id)?.ok_or_else(|| {
269				internal_error!("Dictionary {:?} not found for column {}", dict_id, table_column.name)
270			})?;
271			let entry_id = if matches!(value, Value::None { .. }) {
272				dictionary.id_type.none()
273			} else {
274				txn.insert_into_dictionary(&dictionary, &value)?
275			};
276			entry_id.to_value()
277		} else {
278			value
279		};
280		shape.set_value(&mut row, table_idx, &value);
281	}
282	let now = services.runtime_context.clock.now();
283	row.set_timestamps(now, now);
284	if let Some(time) =
285		resolve_time(&target.table.name, &target.table.columns, &target.table.time, shape, &row, now)?
286	{
287		row.set_time(time);
288	}
289	Ok(row.freeze_bytes())
290}
291
292fn insert_validated_table_rows(
293	txn: &mut Transaction<'_>,
294	target: &TableTarget<'_>,
295	shape: &RowShape,
296	validated: &[EncodedBytes],
297	row_numbers: &[RowNumber],
298	has_returning: bool,
299	pk: Option<&PkContext<'_>>,
300) -> Result<Vec<(RowNumber, EncodedBytes)>> {
301	let mut owned_rows: Vec<EncodedTableRowBuilder> =
302		validated.iter().map(|r| EncodedTableRow::from(r.clone()).thaw()).collect();
303	txn.insert_table(target.table, shape, row_numbers, &mut owned_rows)?;
304
305	if let Some(pk) = pk {
306		for (row, &row_number) in owned_rows.iter().zip(row_numbers.iter()) {
307			write_insert_table_pk_index(txn, target, shape, pk, row, row_number)?;
308		}
309	}
310
311	if has_returning {
312		Ok(row_numbers.iter().copied().zip(owned_rows.into_iter().map(|r| r.freeze_bytes())).collect())
313	} else {
314		Ok(Vec::new())
315	}
316}
317
318#[inline]
319fn write_insert_table_pk_index(
320	txn: &mut Transaction<'_>,
321	target: &TableTarget<'_>,
322	shape: &RowShape,
323	pk: &PkContext<'_>,
324	row: &[u8],
325	row_number: RowNumber,
326) -> Result<()> {
327	let index_key = primary_key::encode_primary_key(pk.pk_def, row, target.table, shape)?;
328	let index_entry_key = IndexEntryKey::new(target.table.id, IndexId::primary(pk.pk_def.id), index_key.clone());
329	if txn.contains(&index_entry_key)? {
330		let key_columns = pk.pk_def.columns.iter().map(|c| c.name.clone()).collect();
331		return_error!(primary_key_violation(target.fragment.clone(), target.table.name.clone(), key_columns,));
332	}
333	txn.set(&index_entry_key, EncodedPodRow::new(&u64::from(row_number).to_be_bytes()).into_bytes())?;
334	Ok(())
335}
336
337#[inline]
338fn insert_table_result(namespace: &str, table: &str, inserted: u64) -> Columns {
339	Columns::single_row([
340		("namespace", Value::Utf8(namespace.to_string())),
341		("table", Value::Utf8(table.to_string())),
342		("inserted", Value::Uint8(inserted)),
343	])
344}