1use 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, ¶ms, 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}