Skip to main content

reifydb_engine/vm/instruction/dml/
ringbuffer_update.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::{collections::HashMap, sync::Arc};
5
6use reifydb_codec::row::{
7	bytes::{EncodedBytes, RowBuilder},
8	ringbuffer::EncodedRingBufferRow,
9	shape::RowShape,
10};
11use reifydb_core::{
12	error::diagnostic::{
13		catalog::{namespace_not_found, ringbuffer_not_found},
14		engine,
15	},
16	interface::{
17		catalog::{
18			config::{ConfigKey, GetConfig},
19			namespace::Namespace,
20			object::ObjectId,
21			policy::{DataOp, PolicyTargetType},
22			ringbuffer::{PartitionedMetadata, RingBuffer},
23		},
24		resolved::{ResolvedColumn, ResolvedNamespace, ResolvedObject, ResolvedRingBuffer},
25	},
26	internal_error,
27	key::{
28		any::TaggedKey,
29		row::{PartitionedRowKey, RowKey},
30	},
31	partition::{PartitionError, partition_col_indices},
32	value::column::columns::Columns,
33};
34use reifydb_evaluate::stack::SymbolTable;
35use reifydb_rql::nodes::UpdateRingBufferNode;
36use reifydb_transaction::transaction::Transaction;
37use reifydb_value::{
38	fragment::Fragment,
39	params::Params,
40	return_error,
41	value::{Value, identity::IdentityId, partition::Partition, row_number::RowNumber},
42};
43
44use super::{
45	coerce::coerce_value_to_column_type,
46	context::RingBufferTarget,
47	returning::{decode_returning_dictionaries, decode_rows_to_columns, evaluate_returning, with_pre_image},
48	shape::get_or_create_ringbuffer_shape,
49};
50use crate::{
51	Result,
52	partition::partition_values,
53	policy::PolicyEvaluator,
54	transaction::operation::{dictionary::DictionaryOperations, ringbuffer::RingBufferOperations},
55	vm::{
56		instruction::dml::time::resolve_time_for_update,
57		services::Services,
58		volcano::{
59			compile::compile,
60			query::{QueryContext, QueryNode, query_budget},
61		},
62	},
63};
64
65pub(crate) fn update_ringbuffer(
66	services: &Arc<Services>,
67	txn: &mut Transaction<'_>,
68	plan: UpdateRingBufferNode,
69	params: Params,
70	symbols: &SymbolTable,
71) -> Result<Columns> {
72	let UpdateRingBufferNode {
73		input,
74		target,
75		returning,
76	} = plan;
77	let (namespace, ringbuffer) = resolve_update_ringbuffer_target(services, txn, &target)?;
78	let partitions = services.catalog.list_ringbuffer_partitions(txn, &ringbuffer)?;
79	let shape = get_or_create_ringbuffer_shape(&services.catalog, &ringbuffer, txn)?;
80	let target_data = RingBufferTarget {
81		namespace: &namespace,
82		ringbuffer: &ringbuffer,
83	};
84	let context = build_update_ringbuffer_query_context(services, &target_data, &params, symbols, txn.identity());
85
86	let mut input_node = compile(*input, txn, Arc::new(context.clone()));
87	input_node.initialize(txn, &context)?;
88
89	let mut updated_count = 0u64;
90	let mut returned_rows: Vec<(RowNumber, EncodedBytes)> = Vec::new();
91	let mut pre_rows: Vec<(RowNumber, EncodedBytes)> = Vec::new();
92	let has_returning = returning.is_some();
93
94	let mut mutable_context = context.clone();
95	while let Some(columns) = input_node.next(txn, &mut mutable_context)? {
96		PolicyEvaluator::new(services, symbols).enforce_write_policies(
97			txn,
98			namespace.name(),
99			&ringbuffer.name,
100			DataOp::Update,
101			&columns,
102			PolicyTargetType::RingBuffer,
103		)?;
104		if columns.row_numbers().is_empty() {
105			return_error!(engine::missing_row_number_column());
106		}
107		let row_numbers = columns.row_numbers();
108		let mut column_map: HashMap<&str, usize> = HashMap::new();
109		for (idx, col) in columns.iter().enumerate() {
110			column_map.insert(col.name().text(), idx);
111		}
112		let view = ColumnView {
113			columns: &columns,
114			column_map: &column_map,
115		};
116
117		for (row_idx, &row_number) in row_numbers.iter().enumerate() {
118			let row = build_updated_ringbuffer_row(
119				services,
120				txn,
121				&target_data,
122				&shape,
123				&view,
124				&context,
125				row_idx,
126			)?;
127			let partition = if columns.partitions().is_empty() {
128				None
129			} else {
130				Some(columns.partitions()[row_idx])
131			};
132			let old_row_key = match partition {
133				None => TaggedKey::from(RowKey::new(ringbuffer.id, row_number)),
134				Some(p) => TaggedKey::from(PartitionedRowKey::new(ringbuffer.id, p, row_number)),
135			};
136			let old_row = txn.get(&old_row_key)?.expect("bytes must exist for update").bytes;
137			let pre_row = old_row.clone();
138			let old_row = EncodedRingBufferRow::view(&old_row);
139			let old_created_at = old_row.created_at();
140			let old_time = old_row.time();
141			let now = services.runtime_context.clock.now();
142			let mut builder = EncodedRingBufferRow::from(row).thaw();
143			builder.set_timestamps(old_created_at, now);
144			if let Some(time) = resolve_time_for_update(
145				&ringbuffer.name,
146				&ringbuffer.columns,
147				&ringbuffer.time,
148				&shape,
149				builder.as_slice(),
150				old_time,
151			)? {
152				builder.set_time(time);
153			}
154			let row = builder.freeze_bytes();
155
156			if !row_belongs_to_any_partition(&partitions, row_number) {
157				continue;
158			}
159
160			if !ringbuffer.partition_by.is_empty() {
161				let indices = partition_col_indices(&ringbuffer.columns, &ringbuffer.partition_by);
162				let new_partition = Partition::of(&partition_values(&shape, &row, &indices));
163				if Some(new_partition) != partition {
164					return Err(PartitionError::ImmutablePartitionColumn {
165						object: ObjectId::ringbuffer(ringbuffer.id),
166					}
167					.into());
168				}
169			}
170
171			let stored_row = txn.update_ringbuffer(ringbuffer.clone(), partition, row_number, row)?;
172			if has_returning {
173				returned_rows.push((row_number, stored_row));
174				pre_rows.push((row_number, pre_row));
175			}
176			updated_count += 1;
177		}
178	}
179
180	if let Some(returning_exprs) = &returning {
181		let mut columns = decode_rows_to_columns(&shape, &returned_rows);
182		decode_returning_dictionaries(services, txn, &ringbuffer.columns, &mut columns)?;
183		let mut pre_columns = decode_rows_to_columns(&shape, &pre_rows);
184		decode_returning_dictionaries(services, txn, &ringbuffer.columns, &mut pre_columns)?;
185		let columns = with_pre_image(columns, &pre_columns);
186		return evaluate_returning(services, symbols, returning_exprs, columns, txn.identity());
187	}
188	Ok(update_ringbuffer_result(namespace.name(), &ringbuffer.name, updated_count))
189}
190
191struct ColumnView<'a> {
192	columns: &'a Columns,
193	column_map: &'a HashMap<&'a str, usize>,
194}
195
196#[inline]
197fn resolve_update_ringbuffer_target(
198	services: &Arc<Services>,
199	txn: &mut Transaction<'_>,
200	target: &ResolvedRingBuffer,
201) -> Result<(Namespace, RingBuffer)> {
202	let namespace_name = target.namespace().name();
203	let Some(namespace) = services.catalog.find_namespace_by_name(txn, namespace_name)? else {
204		return_error!(namespace_not_found(Fragment::internal(namespace_name), namespace_name));
205	};
206	let ringbuffer_name = target.name();
207	let Some(ringbuffer) = services.catalog.find_ringbuffer_by_name(txn, namespace.id(), ringbuffer_name)? else {
208		let fragment = Fragment::internal(target.name());
209		return_error!(ringbuffer_not_found(fragment.clone(), namespace_name, ringbuffer_name));
210	};
211	Ok((namespace, ringbuffer))
212}
213
214#[inline]
215fn build_update_ringbuffer_query_context(
216	services: &Arc<Services>,
217	target: &RingBufferTarget<'_>,
218	params: &Params,
219	symbols: &SymbolTable,
220	identity: IdentityId,
221) -> QueryContext {
222	let namespace_ident = Fragment::internal(target.namespace.name());
223	let resolved_namespace = ResolvedNamespace::new(namespace_ident, target.namespace.clone());
224	let rb_ident = Fragment::internal(target.ringbuffer.name.clone());
225	let resolved_rb = ResolvedRingBuffer::new(rb_ident, resolved_namespace, target.ringbuffer.clone());
226	QueryContext {
227		services: services.clone(),
228		source: Some(ResolvedObject::RingBuffer(resolved_rb)),
229		batch_size: services.catalog.get_config_uint2(ConfigKey::QueryRowBatchSize) as u64,
230		params: params.clone(),
231		symbols: symbols.clone(),
232		identity,
233		memory: query_budget(services),
234	}
235}
236
237#[inline]
238fn build_updated_ringbuffer_row(
239	services: &Arc<Services>,
240	txn: &mut Transaction<'_>,
241	target: &RingBufferTarget<'_>,
242	shape: &RowShape,
243	view: &ColumnView<'_>,
244	context: &QueryContext,
245	row_idx: usize,
246) -> Result<EncodedBytes> {
247	let mut row = shape.allocate_ringbuffer();
248	for (rb_idx, rb_column) in target.ringbuffer.columns.iter().enumerate() {
249		let mut value = if let Some(&input_idx) = view.column_map.get(rb_column.name.as_str()) {
250			view.columns[input_idx].get_value(row_idx)
251		} else {
252			Value::none()
253		};
254
255		let column_ident = view
256			.columns
257			.iter()
258			.find(|col| col.name() == rb_column.name)
259			.map(|col| col.name().clone())
260			.unwrap_or_else(|| Fragment::internal(&rb_column.name));
261		let resolved_column =
262			ResolvedColumn::new(column_ident.clone(), context.source.clone().unwrap(), rb_column.clone());
263
264		value = coerce_value_to_column_type(value, rb_column.constraint.get_type(), resolved_column, context)?;
265		if let Err(mut e) = rb_column.constraint.validate(&value) {
266			e.0.fragment = column_ident.clone();
267			return Err(e);
268		}
269
270		let value = if let Some(dict_id) = rb_column.dictionary_id {
271			let dictionary = services.catalog.find_dictionary(txn, dict_id)?.ok_or_else(|| {
272				internal_error!("Dictionary {:?} not found for column {}", dict_id, rb_column.name)
273			})?;
274			let entry_id = txn.insert_into_dictionary(&dictionary, &value)?;
275			entry_id.to_value()
276		} else {
277			value
278		};
279
280		shape.set_value(&mut row, rb_idx, &value);
281	}
282	Ok(row.freeze_bytes())
283}
284
285#[inline]
286fn row_belongs_to_any_partition(partitions: &[PartitionedMetadata], row_number: RowNumber) -> bool {
287	partitions
288		.iter()
289		.any(|p| !p.metadata.is_empty() && row_number.0 >= p.metadata.head && row_number.0 < p.metadata.tail)
290}
291
292#[inline]
293fn update_ringbuffer_result(namespace: &str, ringbuffer: &str, updated: u64) -> Columns {
294	Columns::single_row([
295		("namespace", Value::Utf8(namespace.to_string())),
296		("ringbuffer", Value::Utf8(ringbuffer.to_string())),
297		("updated", Value::Uint8(updated)),
298	])
299}