1use std::{collections::HashMap, sync::Arc};
5
6use postcard::to_stdvec;
7use reifydb_codec::row::{
8 bytes::{EncodedBytes, RowBuilder},
9 queue::EncodedQueueRow,
10 queue_deduplication::EncodedQueueDeduplicationRow,
11 shape::RowShape,
12};
13use reifydb_core::{
14 error::diagnostic::catalog::{
15 namespace_not_found, queue_deduplication_key_not_utf8, queue_not_before_not_datetime, queue_not_found,
16 },
17 interface::{
18 catalog::{
19 config::{ConfigKey, GetConfig},
20 namespace::Namespace,
21 policy::{DataOp, PolicyTargetType},
22 queue::{Queue, decode_queue_deduplication, encode_queue_deduplication},
23 },
24 resolved::{ResolvedColumn, ResolvedNamespace, ResolvedObject, ResolvedQueue},
25 },
26 internal_error,
27 key::{queue::QueueDeduplicationKey, row::RowKey},
28 return_internal_error,
29 value::column::{buffer::ColumnBuffer, columns::Columns},
30};
31use reifydb_evaluate::stack::SymbolTable;
32use reifydb_rql::{
33 expression::Expression,
34 nodes::{InsertQueueNode, QUEUE_CREATED_COLUMN, QUEUE_DEDUPLICATION_KEY_FIELD, QUEUE_NOT_BEFORE_FIELD},
35};
36use reifydb_transaction::transaction::Transaction;
37use reifydb_value::{
38 fragment::Fragment,
39 params::Params,
40 return_error,
41 value::{Value, datetime::DateTime, duration::Duration, identity::IdentityId, row_number::RowNumber},
42};
43use tracing::instrument;
44
45use super::{
46 returning::{decode_returning_dictionaries, decode_rows_to_columns, evaluate_returning},
47 shape::get_or_create_queue_shape,
48};
49use crate::{
50 Result,
51 policy::PolicyEvaluator,
52 queue::partition::{ordered_by_index, placement_of},
53 transaction::operation::{
54 dictionary::DictionaryOperations,
55 queue::{QueueInsertRow, QueueOperations},
56 },
57 vm::{
58 instruction::dml::{coerce::coerce_value_to_column_type, time::resolve_time},
59 services::Services,
60 volcano::{
61 compile::compile,
62 query::{QueryContext, QueryNode, query_budget},
63 },
64 },
65};
66
67struct QueueTarget<'a> {
68 namespace: &'a Namespace,
69 queue: &'a Queue,
70}
71
72#[instrument(name = "mutate::queue::insert", level = "trace", skip_all)]
73pub(crate) fn insert_queue(
74 services: &Arc<Services>,
75 txn: &mut Transaction<'_>,
76 plan: InsertQueueNode,
77 symbols: &mut SymbolTable,
78) -> Result<Columns> {
79 let InsertQueueNode {
80 input,
81 target,
82 has_deduplication,
83 has_not_before,
84 returning,
85 } = plan;
86
87 let (namespace, queue) = resolve_insert_queue_target(services, txn, &target)?;
88 let shape = get_or_create_queue_shape(&services.catalog, &queue, txn)?;
89 let target_data = QueueTarget {
90 namespace: &namespace,
91 queue: &queue,
92 };
93
94 let context = build_insert_queue_query_context(services, &target_data, symbols, txn.identity());
95 let mut input_node = compile(*input, txn, context.clone());
96 input_node.initialize(txn, &context)?;
97
98 let pending = validate_and_encode_input_rows(
99 services,
100 txn,
101 &target_data,
102 &shape,
103 &context,
104 symbols,
105 &mut input_node,
106 has_deduplication,
107 has_not_before,
108 )?;
109
110 if pending.is_empty() {
111 return Ok(insert_queue_result(namespace.name(), &queue.name, 0, 0));
112 }
113
114 let now = services.runtime_context.clock.now();
115 let outcomes = resolve_duplicates(txn, &queue, &shape, &pending, now)?;
116
117 let fresh_count = outcomes.iter().filter(|outcome| matches!(outcome, Outcome::Fresh)).count();
118 let duplicates = outcomes.len() - fresh_count;
119
120 let row_numbers = if fresh_count == 0 {
121 Vec::new()
122 } else {
123 services.catalog.next_row_number_batch_for_queue(txn, queue.id, fresh_count as u64)?
124 };
125
126 let ordered_by_index = ordered_by_index(&queue)?;
127 let mut assigned = row_numbers.into_iter();
128 let mut rows: Vec<QueueInsertRow> = Vec::with_capacity(fresh_count);
129 let mut returned: Vec<ReturnedRow> = Vec::with_capacity(outcomes.len());
130
131 for (item, outcome) in pending.iter().zip(outcomes.into_iter()) {
132 match outcome {
133 Outcome::Fresh => {
134 let row_number = assigned.next().expect("a row number per fresh item");
135 if let Some(key) = &item.deduplication_key {
136 write_deduplication_record(txn, &queue, key, row_number, now)?;
137 }
138 let placement = placement_of(
139 &queue,
140 &shape,
141 EncodedQueueRow::view(&item.encoded),
142 ordered_by_index,
143 row_number,
144 );
145 rows.push(QueueInsertRow {
146 row_number,
147 partition: placement.partition,
148 key_hash: placement.key_hash,
149 not_before: item.not_before,
150 encoded: item.encoded.clone(),
151 });
152 returned.push(ReturnedRow {
153 created: true,
154 row_number,
155 encoded: item.encoded.clone(),
156 });
157 }
158 Outcome::Duplicate {
159 row_number,
160 encoded,
161 } => returned.push(ReturnedRow {
162 created: false,
163 row_number,
164 encoded: encoded.unwrap_or_else(|| shape.allocate_queue().freeze_bytes()),
165 }),
166 Outcome::DuplicateInBatch {
167 origin,
168 } => {
169 let row_number = returned[origin].row_number;
170 let encoded = returned[origin].encoded.clone();
171 returned.push(ReturnedRow {
172 created: false,
173 row_number,
174 encoded,
175 });
176 }
177 }
178 }
179
180 txn.insert_queue(&queue, &rows)?;
181
182 if let Some(returning_exprs) = &returning {
183 return project_returning(services, txn, symbols, &queue, &shape, returning_exprs, &returned);
184 }
185
186 Ok(insert_queue_result(namespace.name(), &queue.name, fresh_count as u64, duplicates as u64))
187}
188
189struct PendingItem {
190 encoded: EncodedBytes,
191 deduplication_key: Option<Vec<u8>>,
192 not_before: Option<DateTime>,
193}
194
195enum Outcome {
196 Fresh,
197 Duplicate {
198 row_number: RowNumber,
199 encoded: Option<EncodedBytes>,
200 },
201 DuplicateInBatch {
202 origin: usize,
203 },
204}
205
206struct ReturnedRow {
207 created: bool,
208 row_number: RowNumber,
209 encoded: EncodedBytes,
210}
211
212fn write_deduplication_record(
213 txn: &mut Transaction<'_>,
214 queue: &Queue,
215 key: &[u8],
216 row_number: RowNumber,
217 now: DateTime,
218) -> Result<()> {
219 let ttl = queue.deduplicate.as_ref().map(|d| d.ttl).unwrap_or(Duration::MAX);
220 let record = encode_queue_deduplication(row_number, now.saturating_add(ttl));
221 txn.set(&QueueDeduplicationKey::new(queue.id, key), record.into_bytes())?;
222 Ok(())
223}
224
225fn resolve_duplicates(
226 txn: &mut Transaction<'_>,
227 queue: &Queue,
228 shape: &RowShape,
229 pending: &[PendingItem],
230 now: DateTime,
231) -> Result<Vec<Outcome>> {
232 let mut outcomes = Vec::with_capacity(pending.len());
233 let mut seen: HashMap<Vec<u8>, usize> = HashMap::new();
234
235 for (index, item) in pending.iter().enumerate() {
236 let Some(key) = &item.deduplication_key else {
237 outcomes.push(Outcome::Fresh);
238 continue;
239 };
240
241 if let Some(&origin) = seen.get(key) {
242 outcomes.push(Outcome::DuplicateInBatch {
243 origin,
244 });
245 continue;
246 }
247
248 let stored = txn.get(&QueueDeduplicationKey::new(queue.id, key.clone()))?;
249 if let Some(stored) = stored {
250 let Some((row_number, expires_at)) =
251 decode_queue_deduplication(EncodedQueueDeduplicationRow::view(&stored.bytes))
252 else {
253 return_internal_error!(
254 "Queue {} deduplication record is {} bytes wide, too short for its header. This indicates a corrupt record.",
255 queue.name,
256 stored.bytes.len()
257 )
258 };
259 if expires_at > now {
260 let encoded = txn.get(&RowKey::new(queue.id, row_number))?.map(|item| item.bytes);
261 outcomes.push(Outcome::Duplicate {
262 row_number,
263 encoded,
264 });
265 continue;
266 }
267 }
268
269 seen.insert(key.clone(), index);
270 outcomes.push(Outcome::Fresh);
271 }
272
273 let _ = shape;
274 Ok(outcomes)
275}
276
277fn project_returning(
278 services: &Arc<Services>,
279 txn: &mut Transaction<'_>,
280 symbols: &mut SymbolTable,
281 queue: &Queue,
282 shape: &RowShape,
283 returning_exprs: &[Expression],
284 returned: &[ReturnedRow],
285) -> Result<Columns> {
286 let rows: Vec<(RowNumber, EncodedBytes)> =
287 returned.iter().map(|row| (row.row_number, row.encoded.clone())).collect();
288 let mut columns = decode_rows_to_columns(shape, &rows);
289 truncate_to_declared(&mut columns, queue.columns.len());
290 decode_returning_dictionaries(services, txn, &queue.columns, &mut columns)?;
291
292 let mut created = ColumnBuffer::bool_with_capacity(returned.len());
293 for row in returned {
294 created.push_value(Value::Boolean(row.created));
295 }
296 columns.columns.push(created);
297 columns.names.push(Fragment::internal(QUEUE_CREATED_COLUMN));
298
299 evaluate_returning(services, symbols, returning_exprs, columns, txn.identity())
300}
301
302fn declared_key_indices(queue: &Queue) -> Result<Option<Vec<usize>>> {
303 let Some(deduplicate) = &queue.deduplicate else {
304 return Ok(None);
305 };
306 let mut indices = Vec::with_capacity(deduplicate.by.len());
307 for column in &deduplicate.by {
308 let index = queue.columns.iter().position(|c| c.name == *column).ok_or_else(|| {
309 internal_error!("queue {} deduplicates by {} which is not a column", queue.name, column)
310 })?;
311 indices.push(index);
312 }
313 Ok(Some(indices))
314}
315
316fn declared_key_bytes(shape: &RowShape, bytes: &EncodedBytes, indices: &[usize]) -> Vec<u8> {
317 let values: Vec<Value> = indices.iter().map(|&index| shape.get_value(bytes, index)).collect();
318 to_stdvec(&values).expect("postcard serialization of a Value list is total")
319}
320
321#[inline]
322fn truncate_to_declared(columns: &mut Columns, declared: usize) {
323 columns.columns.truncate(declared);
324 columns.names.truncate(declared);
325}
326
327#[inline]
328fn resolve_insert_queue_target(
329 services: &Arc<Services>,
330 txn: &mut Transaction<'_>,
331 target: &ResolvedQueue,
332) -> Result<(Namespace, Queue)> {
333 let namespace_name = target.namespace().name();
334 let Some(namespace) = services.catalog.find_namespace_by_name(txn, namespace_name)? else {
335 return_error!(namespace_not_found(Fragment::internal(namespace_name), namespace_name));
336 };
337 let queue_name = target.name();
338 let Some(queue) = services.catalog.find_queue_by_name(txn, namespace.id(), queue_name)? else {
339 return_error!(queue_not_found(target.identifier().clone(), namespace_name, queue_name));
340 };
341 Ok((namespace, queue))
342}
343
344#[inline]
345fn build_insert_queue_query_context(
346 services: &Arc<Services>,
347 target: &QueueTarget<'_>,
348 symbols: &SymbolTable,
349 identity: IdentityId,
350) -> Arc<QueryContext> {
351 let namespace_ident = Fragment::internal(target.namespace.name());
352 let resolved_namespace = ResolvedNamespace::new(namespace_ident, target.namespace.clone());
353 let queue_ident = Fragment::internal(target.queue.name.clone());
354 let resolved_queue = ResolvedQueue::new(queue_ident, resolved_namespace, target.queue.clone());
355 Arc::new(QueryContext {
356 services: services.clone(),
357 source: Some(ResolvedObject::Queue(resolved_queue)),
358 batch_size: services.catalog.get_config_uint2(ConfigKey::QueryRowBatchSize) as u64,
359 params: Params::None,
360 symbols: symbols.clone(),
361 identity,
362 memory: query_budget(services),
363 })
364}
365
366#[allow(clippy::too_many_arguments)]
367fn validate_and_encode_input_rows(
368 services: &Arc<Services>,
369 txn: &mut Transaction<'_>,
370 target: &QueueTarget<'_>,
371 shape: &RowShape,
372 context: &Arc<QueryContext>,
373 symbols: &SymbolTable,
374 input_node: &mut Box<dyn QueryNode>,
375 has_deduplication: bool,
376 has_not_before: bool,
377) -> Result<Vec<PendingItem>> {
378 let mut pending: Vec<PendingItem> = Vec::new();
379 let mut mutable_context = (**context).clone();
380 let declared_key_indices = declared_key_indices(target.queue)?;
381
382 while let Some(columns) = input_node.next(txn, &mut mutable_context)? {
383 PolicyEvaluator::new(services, symbols).enforce_write_policies(
384 txn,
385 target.namespace.name(),
386 &target.queue.name,
387 DataOp::Insert,
388 &columns,
389 PolicyTargetType::Queue,
390 )?;
391
392 let mut column_map: HashMap<&str, usize> = HashMap::new();
393 for (idx, col) in columns.iter().enumerate() {
394 column_map.insert(col.name().text(), idx);
395 }
396
397 for row_idx in 0..columns.row_count() {
398 let declared_key_indices = declared_key_indices.as_deref();
399 let not_before = if has_not_before {
400 read_not_before(target, &columns, &column_map, row_idx)?
401 } else {
402 None
403 };
404
405 let encoded = build_insert_queue_row(
406 services,
407 txn,
408 target,
409 shape,
410 &columns,
411 &column_map,
412 context,
413 row_idx,
414 not_before,
415 )?;
416
417 let deduplication_key = match declared_key_indices {
418 Some(indices) => Some(declared_key_bytes(shape, &encoded, indices)),
419 None if has_deduplication => {
420 read_deduplication_key(target, &columns, &column_map, row_idx)?
421 }
422 None => None,
423 };
424
425 pending.push(PendingItem {
426 encoded,
427 deduplication_key,
428 not_before,
429 });
430 }
431 }
432
433 Ok(pending)
434}
435
436#[inline]
437fn read_deduplication_key(
438 target: &QueueTarget<'_>,
439 columns: &Columns,
440 column_map: &HashMap<&str, usize>,
441 row_idx: usize,
442) -> Result<Option<Vec<u8>>> {
443 let Some(&idx) = column_map.get(QUEUE_DEDUPLICATION_KEY_FIELD) else {
444 return Ok(None);
445 };
446 let value = columns[idx].get_value(row_idx);
447 match value {
448 Value::None {
449 ..
450 }
451 | Value::Utf8(_) => Ok(Some(statement_key_bytes(&value))),
452 other => return_error!(queue_deduplication_key_not_utf8(
453 Fragment::internal(target.queue.name.clone()),
454 other.get_type().to_string().as_str()
455 )),
456 }
457}
458
459fn statement_key_bytes(value: &Value) -> Vec<u8> {
460 to_stdvec(value).expect("postcard serialization of a Value is total")
461}
462
463#[inline]
464fn read_not_before(
465 target: &QueueTarget<'_>,
466 columns: &Columns,
467 column_map: &HashMap<&str, usize>,
468 row_idx: usize,
469) -> Result<Option<DateTime>> {
470 let Some(&idx) = column_map.get(QUEUE_NOT_BEFORE_FIELD) else {
471 return Ok(None);
472 };
473 match columns[idx].get_value(row_idx) {
474 Value::None {
475 ..
476 } => Ok(None),
477 Value::DateTime(instant) => Ok(Some(instant)),
478 other => return_error!(queue_not_before_not_datetime(
479 Fragment::internal(target.queue.name.clone()),
480 other.get_type().to_string().as_str()
481 )),
482 }
483}
484
485#[allow(clippy::too_many_arguments)]
486#[inline]
487fn build_insert_queue_row(
488 services: &Arc<Services>,
489 txn: &mut Transaction<'_>,
490 target: &QueueTarget<'_>,
491 shape: &RowShape,
492 columns: &Columns,
493 column_map: &HashMap<&str, usize>,
494 context: &Arc<QueryContext>,
495 row_idx: usize,
496 not_before: Option<DateTime>,
497) -> Result<EncodedBytes> {
498 let mut row = shape.allocate_queue();
499
500 for (queue_idx, queue_column) in target.queue.columns.iter().enumerate() {
501 let mut value = if let Some(&input_idx) = column_map.get(queue_column.name.as_str()) {
502 columns[input_idx].get_value(row_idx)
503 } else {
504 Value::none()
505 };
506
507 if queue_column.auto_increment && matches!(value, Value::None { .. }) {
508 value = services.catalog.column_sequence_next_value(txn, target.queue.id, queue_column.id)?;
509 }
510
511 let column_ident = column_map
512 .get(queue_column.name.as_str())
513 .map(|&idx| columns.name_at(idx).clone())
514 .unwrap_or_else(|| Fragment::internal(queue_column.name.clone()));
515
516 let resolved_column = ResolvedColumn::new(
517 column_ident.clone(),
518 context.source.clone().unwrap(),
519 queue_column.clone(),
520 );
521
522 value = coerce_value_to_column_type(
523 value,
524 queue_column.constraint.get_type(),
525 resolved_column,
526 context,
527 )?;
528
529 if let Err(mut e) = queue_column.constraint.validate(&value) {
530 e.0.fragment = column_ident.clone();
531 return Err(e);
532 }
533
534 let value = if let Some(dict_id) = queue_column.dictionary_id {
535 let dictionary = services.catalog.find_dictionary(txn, dict_id)?.ok_or_else(|| {
536 internal_error!("Dictionary {:?} not found for column {}", dict_id, queue_column.name)
537 })?;
538 let entry_id = if matches!(value, Value::None { .. }) {
539 dictionary.id_type.none()
540 } else {
541 txn.insert_into_dictionary(&dictionary, &value)?
542 };
543 entry_id.to_value()
544 } else {
545 value
546 };
547
548 shape.set_value(&mut row, queue_idx, &value);
549 }
550
551 if let Some(instant) = not_before {
552 row.set_not_before(instant);
553 }
554
555 let now = services.runtime_context.clock.now();
556 row.set_timestamps(now, now);
557 if let Some(time) =
558 resolve_time(&target.queue.name, &target.queue.columns, &target.queue.time, shape, &row, now)?
559 {
560 row.set_time(time);
561 }
562
563 Ok(row.freeze_bytes())
564}
565
566#[inline]
567fn insert_queue_result(namespace: &str, queue: &str, inserted: u64, duplicates: u64) -> Columns {
568 Columns::single_row([
569 ("namespace", Value::Utf8(namespace.to_string())),
570 ("queue", Value::Utf8(queue.to_string())),
571 ("inserted", Value::Uint8(inserted)),
572 ("duplicates", Value::Uint8(duplicates)),
573 ])
574}