1use radixdb_core::CompactArc;
57use radixdb_core::{Error, Result, Row, Value};
58use radixdb_executor::result::ExecutionResult;
59use rustc_hash::FxHashMap;
60
61use super::database::FromValue;
62use super::result_adapter::ApiResultCursor;
63
64pub trait FromRow: Sized {
111 fn from_row(row: &ResultRow) -> Result<Self>;
113}
114
115#[derive(Debug, Clone)]
117pub struct ResultRow {
118 row: Row,
119 columns: CompactArc<Vec<String>>,
121 column_lookup: CompactArc<FxHashMap<String, Vec<usize>>>,
123}
124
125impl ResultRow {
126 pub(crate) fn new(
128 row: Row,
129 columns: CompactArc<Vec<String>>,
130 column_lookup: CompactArc<FxHashMap<String, Vec<usize>>>,
131 ) -> Self {
132 Self {
133 row,
134 columns,
135 column_lookup,
136 }
137 }
138
139 pub fn get<T: FromValue>(&self, index: usize) -> Result<T> {
149 let value = self
150 .row
151 .get(index)
152 .ok_or(Error::ColumnIndexOutOfBounds { index })?;
153 T::from_value(value)
154 }
155
156 pub fn get_by_name<T: FromValue>(&self, name: &str) -> Result<T> {
165 let index = self.column_index(name)?;
166 self.get(index)
167 }
168
169 pub fn get_value(&self, index: usize) -> Option<&Value> {
171 self.row.get(index)
172 }
173
174 pub fn into_inner(self) -> Row {
176 self.row
177 }
178
179 pub fn as_row(&self) -> &Row {
181 &self.row
182 }
183
184 pub fn columns(&self) -> &[String] {
186 &self.columns
187 }
188
189 pub fn len(&self) -> usize {
191 self.row.len()
192 }
193
194 pub fn is_empty(&self) -> bool {
196 self.row.len() == 0
197 }
198
199 pub fn is_null(&self, index: usize) -> Result<bool> {
201 self.row
202 .get(index)
203 .map(Value::is_null)
204 .ok_or(Error::ColumnIndexOutOfBounds { index })
205 }
206
207 fn column_index(&self, name: &str) -> Result<usize> {
209 let name_lower = name.to_lowercase();
210 let matches = self
211 .column_lookup
212 .get(&name_lower)
213 .ok_or_else(|| Error::ColumnNotFound(name.to_string()))?;
214 if matches.len() > 1 {
215 Err(Error::AmbiguousColumn(name.to_string()))
216 } else {
217 Ok(matches[0])
218 }
219 }
220}
221
222pub struct Rows {
253 result: ApiResultCursor,
254 columns: CompactArc<Vec<String>>,
256 column_lookup: CompactArc<FxHashMap<String, Vec<usize>>>,
257 closed: bool,
258 positioned: bool,
259 pending_error: Option<radixdb_core::Error>,
261 close_error: Option<radixdb_core::Error>,
262 iterator_close_error_yielded: bool,
263}
264
265impl Rows {
266 pub(crate) fn new(result: ExecutionResult) -> Self {
268 let result = ApiResultCursor::new(result);
269 let columns = result
271 .columns_arc()
272 .unwrap_or_else(|| CompactArc::new(result.columns().to_vec()));
273 let mut column_lookup: FxHashMap<String, Vec<usize>> = FxHashMap::default();
274 for (index, column) in columns.iter().enumerate() {
275 column_lookup
276 .entry(column.to_lowercase())
277 .or_default()
278 .push(index);
279 }
280 Self {
281 result,
282 columns,
283 column_lookup: CompactArc::new(column_lookup),
284 closed: false,
285 positioned: false,
286 pending_error: None,
287 close_error: None,
288 iterator_close_error_yielded: false,
289 }
290 }
291
292 pub fn columns(&self) -> &[String] {
294 &self.columns
295 }
296
297 pub fn column_count(&self) -> usize {
299 self.columns.len()
300 }
301
302 pub fn rows_affected(&self) -> i64 {
304 self.result.rows_affected()
305 }
306
307 pub fn last_insert_id(&self) -> i64 {
309 self.result.last_insert_id()
310 }
311
312 #[inline]
318 pub fn supports_server_column_batches(&self) -> bool {
319 !self.closed && self.result.supports_typed_batches()
320 }
321
322 #[inline]
324 pub fn server_column_batch_fallback(&self) -> Option<super::ServerBatchFallback> {
325 if self.supports_server_column_batches() {
326 None
327 } else if self.closed {
328 Some(super::ServerBatchFallback::RowState)
329 } else {
330 self.result
331 .typed_batch_fallback_reason()
332 .map(super::ServerBatchFallback::from_storage)
333 }
334 }
335
336 pub fn next_server_column_batch(&mut self) -> Result<Option<super::ServerColumnBatch>> {
338 if self.closed {
339 return self.close_error.clone().map_or(Ok(None), Err);
340 }
341 match self.result.next_typed_batch() {
342 Ok(Some(batch)) => super::ServerColumnBatch::from_storage(batch).map(Some),
343 Ok(None) => {
344 self.close_internal()?;
345 Ok(None)
346 }
347 Err(error) => {
348 let _ = self.close_internal();
349 Err(error)
350 }
351 }
352 }
353
354 #[inline]
362 pub fn advance(&mut self) -> bool {
363 if self.closed {
364 return false;
365 }
366 if self.result.next() {
367 self.positioned = true;
368 return true;
369 }
370 self.positioned = false;
371 if let Some(err) = self.result.last_error() {
373 self.pending_error = Some(err);
374 }
375 let _ = self.close_internal();
377 false
378 }
379
380 #[inline]
386 pub fn error(&mut self) -> Option<radixdb_core::Error> {
387 if let Some(error) = self.pending_error.take() {
388 return Some(error);
389 }
390 let error = self.close_error.clone();
391 if error.is_some() {
392 self.iterator_close_error_yielded = true;
393 }
394 error
395 }
396
397 #[inline]
401 pub fn current_row(&self) -> Result<&Row> {
402 if self.closed || !self.positioned {
403 Err(Error::CursorNotPositioned)
404 } else {
405 Ok(self.result.row())
406 }
407 }
408
409 pub fn collect_vec(self) -> Result<Vec<ResultRow>> {
420 self.collect()
421 }
422
423 pub fn close(&mut self) -> Result<()> {
427 let result = self.close_internal();
428 if result.is_err() {
429 self.iterator_close_error_yielded = true;
430 }
431 result
432 }
433
434 fn close_internal(&mut self) -> Result<()> {
435 if !self.closed {
436 let result = self.result.close();
437 self.closed = true;
438 self.positioned = false;
439 if let Err(error) = result {
440 self.close_error = Some(error);
441 }
442 }
443 self.close_error.clone().map_or(Ok(()), Err)
444 }
445}
446
447impl Iterator for Rows {
448 type Item = Result<ResultRow>;
449
450 fn next(&mut self) -> Option<Self::Item> {
451 if self.closed {
452 if !self.iterator_close_error_yielded {
453 if let Some(error) = self.close_error.clone() {
454 self.iterator_close_error_yielded = true;
455 return Some(Err(error));
456 }
457 }
458 return None;
459 }
460
461 self.positioned = false;
462
463 if self.result.next() {
464 let row = self.result.take_row();
466 Some(Ok(ResultRow::new(
468 row,
469 CompactArc::clone(&self.columns),
470 CompactArc::clone(&self.column_lookup),
471 )))
472 } else if let Some(err) = self.result.last_error() {
473 let _ = self.close_internal();
476 Some(Err(err))
477 } else {
478 match self.close_internal() {
479 Ok(()) => None,
480 Err(error) => {
481 self.iterator_close_error_yielded = true;
482 Some(Err(error))
483 }
484 }
485 }
486 }
487}
488
489impl Drop for Rows {
490 fn drop(&mut self) {
491 let _ = self.close_internal();
492 }
493}
494
495#[cfg(test)]
496mod tests {
497 use super::*;
498 use crate::result_adapter::close_failing_result;
499 use radixdb_storage::traits::MemoryResult;
500
501 fn create_test_rows() -> Rows {
502 let columns = vec!["id".to_string(), "name".to_string(), "value".to_string()];
503 let rows = vec![
504 Row::from_values(vec![
505 Value::Integer(1),
506 Value::text("Alice"),
507 Value::Float(10.5),
508 ]),
509 Row::from_values(vec![
510 Value::Integer(2),
511 Value::text("Bob"),
512 Value::Float(20.0),
513 ]),
514 ];
515
516 let result = MemoryResult::with_rows(columns, rows);
517 Rows::new(Box::new(result))
518 }
519
520 #[test]
521 fn test_iterator_for_loop() {
522 let rows = create_test_rows();
523 let mut count = 0;
524
525 for row in rows {
526 let row = row.unwrap();
527 assert!(row.get::<i64>(0).is_ok());
528 count += 1;
529 }
530
531 assert_eq!(count, 2);
532 }
533
534 #[test]
535 fn r6_l01_b_rows_close_surfaces_cleanup_failure() {
536 let mut rows = Rows::new(close_failing_result());
537 let error = rows.close().expect_err("close failure must be observable");
538 assert!(error.to_string().contains("injected close failure"));
539 assert!(rows.close().is_err(), "close failure remains observable");
540 }
541
542 #[test]
543 fn r8_iterator_surfaces_cleanup_failure_at_eof_once() {
544 let mut rows = Rows::new(close_failing_result());
545 let error = rows
546 .next()
547 .expect("terminal cleanup error")
548 .expect_err("cleanup must fail");
549 assert!(error.to_string().contains("injected close failure"));
550 assert!(rows.next().is_none());
551 }
552
553 #[test]
554 fn r8_advance_exposes_terminal_cleanup_failure() {
555 let mut rows = Rows::new(close_failing_result());
556 assert!(!rows.advance());
557 let error = rows.error().expect("cleanup error must be recoverable");
558 assert!(error.to_string().contains("injected close failure"));
559 }
560
561 #[test]
562 fn test_iterator_collect() {
563 let rows = create_test_rows();
564 let collected: Vec<ResultRow> = rows.collect::<std::result::Result<Vec<_>, _>>().unwrap();
565
566 assert_eq!(collected.len(), 2);
567 assert_eq!(collected[0].get::<i64>(0).unwrap(), 1);
568 assert_eq!(collected[1].get::<i64>(0).unwrap(), 2);
569 }
570
571 #[test]
572 fn test_iterator_map() {
573 let rows = create_test_rows();
574 let names: Vec<String> = rows
575 .map(|r| r.and_then(|row| row.get(1)))
576 .collect::<std::result::Result<Vec<_>, _>>()
577 .unwrap();
578
579 assert_eq!(names, vec!["Alice", "Bob"]);
580 }
581
582 #[test]
583 fn test_iterator_filter() {
584 let rows = create_test_rows();
585 let filtered: Vec<ResultRow> = rows
586 .filter_map(|r| {
587 let row = r.ok()?;
588 let id: i64 = row.get(0).ok()?;
589 if id > 1 {
590 Some(row)
591 } else {
592 None
593 }
594 })
595 .collect();
596
597 assert_eq!(filtered.len(), 1);
598 assert_eq!(filtered[0].get::<String>(1).unwrap(), "Bob");
599 }
600
601 #[test]
602 fn test_result_row_get() {
603 let rows = create_test_rows();
604 let row = rows.into_iter().next().unwrap().unwrap();
605
606 assert_eq!(row.get::<i64>(0).unwrap(), 1);
607 assert_eq!(row.get::<String>(1).unwrap(), "Alice");
608 assert_eq!(row.get::<f64>(2).unwrap(), 10.5);
609 }
610
611 #[test]
612 fn test_result_row_get_by_name() {
613 let rows = create_test_rows();
614 let row = rows.into_iter().next().unwrap().unwrap();
615
616 assert_eq!(row.get_by_name::<i64>("id").unwrap(), 1);
617 assert_eq!(row.get_by_name::<String>("name").unwrap(), "Alice");
618 assert_eq!(row.get_by_name::<f64>("value").unwrap(), 10.5);
619
620 assert_eq!(row.get_by_name::<i64>("ID").unwrap(), 1);
622 assert_eq!(row.get_by_name::<String>("NAME").unwrap(), "Alice");
623 }
624
625 #[test]
626 fn r8_l01_batch_j_result_rows_share_case_folded_column_lookup() {
627 let result = MemoryResult::with_rows(
628 vec!["Name".to_string(), "NAME".to_string(), "id".to_string()],
629 vec![
630 Row::from_values(vec![Value::text("a"), Value::text("b"), Value::Integer(1)]),
631 Row::from_values(vec![Value::text("c"), Value::text("d"), Value::Integer(2)]),
632 ],
633 );
634 let mut rows = Rows::new(Box::new(result));
635 let first = rows.next().unwrap().unwrap();
636 let second = rows.next().unwrap().unwrap();
637
638 assert_eq!(first.get_by_name::<i64>("ID").unwrap(), 1);
639 assert_eq!(second.get_by_name::<i64>("id").unwrap(), 2);
640 assert!(matches!(
641 first.get_by_name::<String>("name"),
642 Err(Error::AmbiguousColumn(column)) if column == "name"
643 ));
644 }
645
646 #[test]
647 fn test_result_row_columns() {
648 let rows = create_test_rows();
649 let row = rows.into_iter().next().unwrap().unwrap();
650
651 assert_eq!(row.columns(), &["id", "name", "value"]);
652 assert_eq!(row.len(), 3);
653 assert!(!row.is_empty());
654 }
655
656 #[test]
657 fn test_rows_columns() {
658 let rows = create_test_rows();
659 assert_eq!(rows.columns(), &["id", "name", "value"]);
660 assert_eq!(rows.column_count(), 3);
661 }
662
663 #[test]
664 fn test_collect_vec() {
665 let rows = create_test_rows();
666 let collected = rows.collect_vec().unwrap();
667
668 assert_eq!(collected.len(), 2);
669 }
670
671 #[test]
672 fn test_out_of_bounds() {
673 let rows = create_test_rows();
674 let row = rows.into_iter().next().unwrap().unwrap();
675
676 assert!(row.get::<i64>(10).is_err());
677 }
678
679 #[test]
680 fn test_column_not_found() {
681 let rows = create_test_rows();
682 let row = rows.into_iter().next().unwrap().unwrap();
683
684 assert!(row.get_by_name::<String>("nonexistent").is_err());
685 }
686
687 #[test]
688 fn test_advance_and_current_row() {
689 let mut rows = create_test_rows();
690
691 assert!(rows.advance());
693 let row = rows.current_row().unwrap();
694 assert_eq!(row.get(0), Some(&Value::Integer(1)));
695 assert_eq!(row.get(1), Some(&Value::text("Alice")));
696
697 assert!(rows.advance());
699 let row = rows.current_row().unwrap();
700 assert_eq!(row.get(0), Some(&Value::Integer(2)));
701 assert_eq!(row.get(1), Some(&Value::text("Bob")));
702
703 assert!(!rows.advance());
705 }
706
707 #[test]
708 fn test_advance_on_closed_rows() {
709 let mut rows = create_test_rows();
710 rows.close().unwrap();
711 assert!(!rows.advance());
712 }
713
714 #[test]
715 fn test_advance_full_scan() {
716 let mut rows = create_test_rows();
717 let mut count = 0;
718 while rows.advance() {
719 let _row = rows.current_row();
720 count += 1;
721 }
722 assert_eq!(count, 2);
723 }
724}