1use std::fmt;
47
48use radixdb_core::value::NULL_VALUE;
49use radixdb_core::{CompactArc, CompactVec};
50use radixdb_core::{Error, Result, Row, Value};
51use radixdb_storage::{DeferredColumnSource, DeferredRow};
52
53#[derive(Debug, Clone)]
55pub struct ColumnInfo {
56 pub name: String,
58 pub table_alias: Option<String>,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Default)]
68pub enum OrderingProperty {
69 #[default]
71 Unknown,
72 AscendingNullsLast(Vec<usize>),
74}
75
76impl OrderingProperty {
77 pub fn ascending_nulls_last(key_indices: Vec<usize>) -> Self {
79 if key_indices.is_empty() {
80 Self::Unknown
81 } else {
82 Self::AscendingNullsLast(key_indices)
83 }
84 }
85
86 pub fn proves_ascending_nulls_last(&self, required_keys: &[usize]) -> bool {
88 if required_keys.is_empty() {
89 return false;
90 }
91 match self {
92 Self::AscendingNullsLast(keys) => keys.starts_with(required_keys),
93 Self::Unknown => false,
94 }
95 }
96
97 pub fn remap_outer_projection(&self, columns: &[ColumnSource]) -> Self {
102 let Self::AscendingNullsLast(keys) = self else {
103 return Self::Unknown;
104 };
105 let mut remapped = Vec::with_capacity(keys.len());
106 for key in keys {
107 let Some(output) = columns
108 .iter()
109 .position(|source| matches!(source, ColumnSource::Outer(index) if index == key))
110 else {
111 return Self::Unknown;
112 };
113 remapped.push(output);
114 }
115 Self::ascending_nulls_last(remapped)
116 }
117}
118
119impl ColumnInfo {
120 pub fn new(name: impl Into<String>) -> Self {
122 Self {
123 name: name.into(),
124 table_alias: None,
125 }
126 }
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub enum ColumnSource {
132 Outer(usize),
134 Inner(usize),
136}
137
138#[derive(Debug, Clone)]
140pub struct JoinProjection {
141 pub columns: Vec<ColumnSource>,
143}
144
145impl JoinProjection {
146 pub fn validate(
150 &self,
151 left_columns: usize,
152 right_columns: usize,
153 output_columns: usize,
154 ) -> Result<()> {
155 if self.columns.len() != output_columns {
156 return Err(Error::invalid_argument(format!(
157 "join projection has {} values but output schema has {} columns",
158 self.columns.len(),
159 output_columns
160 )));
161 }
162 for source in &self.columns {
163 let (side, index, width) = match source {
164 ColumnSource::Outer(index) => ("left", *index, left_columns),
165 ColumnSource::Inner(index) => ("right", *index, right_columns),
166 };
167 if index >= width {
168 return Err(Error::invalid_argument(format!(
169 "join projection {side} column index {index} is outside width {width}"
170 )));
171 }
172 }
173 Ok(())
174 }
175}
176
177pub trait Operator: Send {
191 fn open(&mut self) -> Result<()>;
197
198 fn next(&mut self) -> Result<Option<RowRef>>;
207
208 fn close(&mut self) -> Result<()>;
214
215 fn schema(&self) -> &[ColumnInfo];
217
218 fn estimated_rows(&self) -> Option<usize> {
223 None
224 }
225
226 fn ordering(&self) -> OrderingProperty {
232 OrderingProperty::Unknown
233 }
234
235 fn name(&self) -> &str;
237}
238
239#[derive(Debug, Clone)]
252pub enum RowRef {
253 Owned(Row),
255
256 Composite(CompositeRow),
259
260 DirectBuildComposite(DirectBuildCompositeRow),
264
265 Shared(SharedRow),
270
271 Projected(ProjectedRow),
277
278 Deferred(DeferredRow),
280}
281
282impl RowRef {
283 #[inline]
285 pub fn owned(row: Row) -> Self {
286 RowRef::Owned(row)
287 }
288
289 #[inline]
291 pub fn composite(left: Row, right: Row) -> Self {
292 RowRef::Composite(CompositeRow::new(left, right))
293 }
294
295 #[inline]
297 pub fn len(&self) -> usize {
298 match self {
299 RowRef::Owned(row) => row.len(),
300 RowRef::Composite(comp) => comp.len(),
301 RowRef::DirectBuildComposite(direct) => direct.len(),
302 RowRef::Shared(shared) => shared.len(),
303 RowRef::Projected(projected) => projected.len(),
304 RowRef::Deferred(deferred) => deferred.len(),
305 }
306 }
307
308 #[inline]
310 pub fn is_empty(&self) -> bool {
311 self.len() == 0
312 }
313
314 #[inline]
316 pub fn get(&self, idx: usize) -> Option<&Value> {
317 match self {
318 RowRef::Owned(row) => row.get(idx),
319 RowRef::Composite(comp) => comp.get(idx),
320 RowRef::DirectBuildComposite(direct) => direct.get(idx),
321 RowRef::Shared(shared) => shared.get(idx),
322 RowRef::Projected(projected) => projected.get(idx),
323 RowRef::Deferred(deferred) => deferred.get(idx),
324 }
325 }
326
327 #[inline]
332 pub fn into_owned(self) -> Row {
333 match self {
334 RowRef::Owned(row) => row,
335 RowRef::Composite(comp) => comp.materialize_owned(),
337 RowRef::DirectBuildComposite(direct) => direct.materialize_owned(),
338 RowRef::Shared(shared) => shared.materialize_owned(),
339 RowRef::Projected(projected) => projected.materialize_owned(),
340 RowRef::Deferred(deferred) => deferred.into_owned(),
341 }
342 }
343
344 pub fn to_owned(&self) -> Row {
348 match self {
349 RowRef::Owned(row) => row.clone(),
350 RowRef::Composite(comp) => comp.materialize(),
351 RowRef::DirectBuildComposite(direct) => direct.materialize(),
352 RowRef::Shared(shared) => shared.materialize(),
353 RowRef::Projected(projected) => projected.materialize(),
354 RowRef::Deferred(deferred) => deferred.to_owned(),
355 }
356 }
357
358 #[inline]
360 pub fn as_row(&self) -> Option<&Row> {
361 match self {
362 RowRef::Owned(row) => Some(row),
363 RowRef::Shared(shared) => Some(shared.row()),
364 RowRef::Composite(_)
365 | RowRef::DirectBuildComposite(_)
366 | RowRef::Projected(_)
367 | RowRef::Deferred(_) => None,
368 }
369 }
370
371 #[inline]
376 pub fn direct_build_composite(
377 probe: Row,
378 build_rows: CompactArc<Vec<Row>>,
379 build_idx: usize,
380 probe_is_left: bool,
381 ) -> Self {
382 RowRef::DirectBuildComposite(DirectBuildCompositeRow::new(
383 probe,
384 build_rows,
385 build_idx,
386 probe_is_left,
387 ))
388 }
389
390 #[inline]
392 pub fn shared(rows: CompactArc<Vec<Row>>, row_idx: usize) -> Self {
393 RowRef::Shared(SharedRow::new(rows, row_idx))
394 }
395
396 #[inline]
398 pub fn projected(left: RowRef, right: RowRef, columns: CompactArc<[ColumnSource]>) -> Self {
399 RowRef::Projected(ProjectedRow::new(left, right, columns))
400 }
401
402 #[inline]
404 pub fn deferred(row: DeferredRow) -> Self {
405 RowRef::Deferred(row)
406 }
407
408 pub fn into_deferred(self) -> DeferredRow {
410 match self {
411 RowRef::Owned(row) => DeferredRow::owned(row),
412 RowRef::Shared(SharedRow { rows, row_idx }) => DeferredRow::shared(rows, row_idx),
413 RowRef::Projected(ProjectedRow {
414 left,
415 right,
416 columns,
417 }) => {
418 let columns = columns
419 .iter()
420 .map(|source| match source {
421 ColumnSource::Outer(index) => DeferredColumnSource::Left(*index),
422 ColumnSource::Inner(index) => DeferredColumnSource::Right(*index),
423 })
424 .collect::<Vec<_>>();
425 DeferredRow::projected(
426 left.into_deferred(),
427 right.into_deferred(),
428 CompactArc::from(columns),
429 )
430 }
431 RowRef::Composite(composite) => DeferredRow::owned(composite.materialize_owned()),
435 RowRef::DirectBuildComposite(composite) => {
436 DeferredRow::owned(composite.materialize_owned())
437 }
438 RowRef::Deferred(row) => row,
439 }
440 }
441
442 #[inline]
444 pub fn is_deferred(&self) -> bool {
445 match self {
446 RowRef::Owned(_) => false,
447 RowRef::Deferred(row) => row.is_deferred(),
448 _ => true,
449 }
450 }
451
452 pub fn estimated_retained_bytes(&self) -> usize {
456 fn row_bytes(row: &Row) -> usize {
457 row.iter().fold(std::mem::size_of::<Row>(), |total, value| {
458 let payload = match value {
459 Value::Text(text) => text.len(),
460 Value::Extension(bytes) => bytes.len(),
461 _ => 0,
462 };
463 total
464 .saturating_add(std::mem::size_of::<Value>())
465 .saturating_add(payload)
466 })
467 }
468
469 match self {
470 Self::Owned(row) => row_bytes(row),
471 Self::Shared(_) => std::mem::size_of::<Self>(),
472 Self::Composite(row) => std::mem::size_of::<Self>()
473 .saturating_add(row_bytes(&row.left))
474 .saturating_add(row_bytes(&row.right)),
475 Self::DirectBuildComposite(row) => {
476 std::mem::size_of::<Self>().saturating_add(row_bytes(&row.probe))
477 }
478 Self::Projected(row) => std::mem::size_of::<Self>()
479 .saturating_add(row.left.estimated_retained_bytes())
480 .saturating_add(row.right.estimated_retained_bytes())
481 .saturating_add(
482 row.columns
483 .len()
484 .saturating_mul(std::mem::size_of::<ColumnSource>()),
485 ),
486 Self::Deferred(row) => row.estimated_retained_bytes(),
487 }
488 }
489}
490
491#[derive(Debug, Clone)]
493pub struct SharedRow {
494 rows: CompactArc<Vec<Row>>,
495 row_idx: usize,
496}
497
498impl SharedRow {
499 #[inline]
500 pub fn new(rows: CompactArc<Vec<Row>>, row_idx: usize) -> Self {
501 assert!(
502 row_idx < rows.len(),
503 "shared row index outside immutable batch"
504 );
505 Self { rows, row_idx }
506 }
507
508 #[inline]
509 fn row(&self) -> &Row {
510 &self.rows[self.row_idx]
511 }
512
513 #[inline]
514 pub fn len(&self) -> usize {
515 self.row().len()
516 }
517
518 #[inline]
519 pub fn is_empty(&self) -> bool {
520 self.row().is_empty()
521 }
522
523 #[inline]
524 pub fn get(&self, idx: usize) -> Option<&Value> {
525 self.row().get(idx)
526 }
527
528 #[inline]
529 pub fn materialize(&self) -> Row {
530 self.row().clone()
531 }
532
533 #[inline]
534 pub fn materialize_owned(self) -> Row {
535 self.row().clone()
536 }
537}
538
539#[derive(Debug, Clone)]
545pub struct ProjectedRow {
546 left: Box<RowRef>,
547 right: Box<RowRef>,
548 columns: CompactArc<[ColumnSource]>,
549}
550
551impl ProjectedRow {
552 #[inline]
553 pub fn new(left: RowRef, right: RowRef, columns: CompactArc<[ColumnSource]>) -> Self {
554 Self {
555 left: Box::new(left),
556 right: Box::new(right),
557 columns,
558 }
559 }
560
561 #[inline]
562 pub fn len(&self) -> usize {
563 self.columns.len()
564 }
565
566 #[inline]
567 pub fn is_empty(&self) -> bool {
568 self.columns.is_empty()
569 }
570
571 #[inline]
572 pub fn get(&self, idx: usize) -> Option<&Value> {
573 match self.columns.get(idx)? {
574 ColumnSource::Outer(index) => self.left.get(*index),
575 ColumnSource::Inner(index) => self.right.get(*index),
576 }
577 }
578
579 pub fn materialize(&self) -> Row {
580 let mut values = CompactVec::with_capacity(self.columns.len());
581 for index in 0..self.columns.len() {
582 values.push(self.get(index).cloned().unwrap_or(NULL_VALUE));
583 }
584 Row::from_compact_vec(values)
585 }
586
587 #[inline]
588 pub fn materialize_owned(self) -> Row {
589 self.materialize()
593 }
594}
595
596impl fmt::Display for ProjectedRow {
597 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
598 write!(f, "(")?;
599 for index in 0..self.len() {
600 if index > 0 {
601 write!(f, ", ")?;
602 }
603 match self.get(index) {
604 Some(value) => write!(f, "{value}")?,
605 None => write!(f, "NULL")?,
606 }
607 }
608 write!(f, ")")
609 }
610}
611
612#[derive(Debug, Clone)]
630pub struct CompositeRow {
631 left: Row,
633 right: Row,
635 left_cols: usize,
637}
638
639impl CompositeRow {
640 #[inline]
642 pub fn new(left: Row, right: Row) -> Self {
643 let left_cols = left.len();
644 Self {
645 left,
646 right,
647 left_cols,
648 }
649 }
650
651 #[inline]
653 pub fn len(&self) -> usize {
654 self.left_cols + self.right.len()
655 }
656
657 #[inline]
659 pub fn is_empty(&self) -> bool {
660 self.left.is_empty() && self.right.is_empty()
661 }
662
663 #[inline]
668 pub fn get(&self, idx: usize) -> Option<&Value> {
669 if idx < self.left_cols {
670 self.left.get(idx)
671 } else {
672 self.right.get(idx - self.left_cols)
673 }
674 }
675
676 #[inline]
678 pub fn left(&self) -> &Row {
679 &self.left
680 }
681
682 #[inline]
684 pub fn right(&self) -> &Row {
685 &self.right
686 }
687
688 pub fn materialize(&self) -> Row {
694 let total = self.len();
695 let mut values: CompactVec<Value> = CompactVec::with_capacity(total);
696
697 values.extend_clone(self.left.as_slice());
699
700 values.extend_clone(self.right.as_slice());
702
703 Row::from_compact_vec(values)
704 }
705
706 #[inline]
711 pub fn materialize_owned(self) -> Row {
712 Row::from_combined_owned(self.left, self.right)
713 }
714
715 #[inline]
717 pub fn into_parts(self) -> (Row, Row) {
718 (self.left, self.right)
719 }
720}
721
722impl fmt::Display for CompositeRow {
723 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
724 write!(f, "(")?;
725 for i in 0..self.len() {
726 if i > 0 {
727 write!(f, ", ")?;
728 }
729 if let Some(v) = self.get(i) {
730 write!(f, "{}", v)?;
731 } else {
732 write!(f, "NULL")?;
733 }
734 }
735 write!(f, ")")
736 }
737}
738
739#[derive(Debug)]
745pub struct DirectBuildCompositeRow {
746 probe: Row,
748 build_rows: CompactArc<Vec<Row>>,
750 build_idx: usize,
752 probe_cols: usize,
754 probe_is_left: bool,
756}
757
758impl DirectBuildCompositeRow {
759 #[inline]
761 pub fn new(
762 probe: Row,
763 build_rows: CompactArc<Vec<Row>>,
764 build_idx: usize,
765 probe_is_left: bool,
766 ) -> Self {
767 debug_assert!(
768 build_idx < build_rows.len(),
769 "build_idx {} out of bounds (len={})",
770 build_idx,
771 build_rows.len()
772 );
773 let probe_cols = probe.len();
774 Self {
775 probe,
776 build_rows,
777 build_idx,
778 probe_cols,
779 probe_is_left,
780 }
781 }
782
783 #[inline]
785 pub fn len(&self) -> usize {
786 self.probe_cols + self.build_rows[self.build_idx].len()
787 }
788
789 #[inline]
791 pub fn is_empty(&self) -> bool {
792 self.probe.is_empty() && self.build_rows[self.build_idx].is_empty()
793 }
794
795 #[inline]
797 pub fn get(&self, idx: usize) -> Option<&Value> {
798 let build_row = &self.build_rows[self.build_idx];
799 if self.probe_is_left {
800 if idx < self.probe_cols {
802 self.probe.get(idx)
803 } else {
804 build_row.get(idx - self.probe_cols)
805 }
806 } else {
807 let build_cols = build_row.len();
809 if idx < build_cols {
810 build_row.get(idx)
811 } else {
812 self.probe.get(idx - build_cols)
813 }
814 }
815 }
816
817 pub fn materialize(&self) -> Row {
819 let build_row = &self.build_rows[self.build_idx];
820 let total = self.probe_cols + build_row.len();
821 let mut values: CompactVec<Value> = CompactVec::with_capacity(total);
822
823 if self.probe_is_left {
824 values.extend_clone(self.probe.as_slice());
825 values.extend_clone(build_row.as_slice());
826 } else {
827 values.extend_clone(build_row.as_slice());
828 values.extend_clone(self.probe.as_slice());
829 }
830
831 Row::from_compact_vec(values)
832 }
833
834 #[inline]
840 pub fn materialize_owned(self) -> Row {
841 let build_row = &self.build_rows[self.build_idx];
842 let total = self.probe_cols + build_row.len();
843
844 let mut values: CompactVec<Value> = CompactVec::with_capacity(total);
845 if self.probe_is_left {
846 self.probe.extend_into_compact_vec(&mut values);
848 values.extend_clone(build_row.as_slice());
849 } else {
850 values.extend_clone(build_row.as_slice());
851 self.probe.extend_into_compact_vec(&mut values);
853 }
854 Row::from_compact_vec(values)
855 }
856}
857
858impl Clone for DirectBuildCompositeRow {
859 fn clone(&self) -> Self {
860 Self {
861 probe: self.probe.clone(),
862 build_rows: CompactArc::clone(&self.build_rows),
863 build_idx: self.build_idx,
864 probe_cols: self.probe_cols,
865 probe_is_left: self.probe_is_left,
866 }
867 }
868}
869
870impl fmt::Display for DirectBuildCompositeRow {
871 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
872 write!(f, "(")?;
873 for i in 0..self.len() {
874 if i > 0 {
875 write!(f, ", ")?;
876 }
877 if let Some(v) = self.get(i) {
878 write!(f, "{}", v)?;
879 } else {
880 write!(f, "NULL")?;
881 }
882 }
883 write!(f, ")")
884 }
885}
886
887pub struct EmptyOperator {
895 schema: Vec<ColumnInfo>,
896 opened: bool,
897}
898
899impl EmptyOperator {
900 pub fn new() -> Self {
902 Self {
903 schema: Vec::new(),
904 opened: false,
905 }
906 }
907}
908
909impl Default for EmptyOperator {
910 fn default() -> Self {
911 Self::new()
912 }
913}
914
915impl Operator for EmptyOperator {
916 fn open(&mut self) -> Result<()> {
917 self.opened = true;
918 Ok(())
919 }
920
921 fn next(&mut self) -> Result<Option<RowRef>> {
922 Ok(None)
923 }
924
925 fn close(&mut self) -> Result<()> {
926 Ok(())
927 }
928
929 fn schema(&self) -> &[ColumnInfo] {
930 &self.schema
931 }
932
933 fn name(&self) -> &str {
934 "Empty"
935 }
936}
937
938pub struct MaterializedOperator {
945 rows: Vec<Row>,
946 schema: Vec<ColumnInfo>,
947 ordering: OrderingProperty,
948 current_idx: usize,
949 opened: bool,
950}
951
952impl MaterializedOperator {
953 pub fn new(rows: Vec<Row>, schema: Vec<ColumnInfo>) -> Self {
955 Self {
956 rows,
957 schema,
958 ordering: OrderingProperty::Unknown,
959 current_idx: 0,
960 opened: false,
961 }
962 }
963
964 pub fn with_ordering(mut self, ordering: OrderingProperty) -> Self {
969 self.ordering = ordering;
970 self
971 }
972
973 pub fn from_arc(arc_rows: CompactArc<Vec<Row>>, schema: Vec<ColumnInfo>) -> Self {
976 let rows = CompactArc::try_unwrap(arc_rows).unwrap_or_else(|arc| (*arc).clone());
977 Self::new(rows, schema)
978 }
979}
980
981impl Operator for MaterializedOperator {
982 fn open(&mut self) -> Result<()> {
983 self.current_idx = 0;
984 self.opened = true;
985 Ok(())
986 }
987
988 fn next(&mut self) -> Result<Option<RowRef>> {
989 if self.current_idx >= self.rows.len() {
990 return Ok(None);
991 }
992
993 let row = std::mem::take(&mut self.rows[self.current_idx]);
997 self.current_idx += 1;
998 Ok(Some(RowRef::Owned(row)))
999 }
1000
1001 fn close(&mut self) -> Result<()> {
1002 Ok(())
1003 }
1004
1005 fn schema(&self) -> &[ColumnInfo] {
1006 &self.schema
1007 }
1008
1009 fn estimated_rows(&self) -> Option<usize> {
1010 Some(self.rows.len())
1011 }
1012
1013 fn ordering(&self) -> OrderingProperty {
1014 self.ordering.clone()
1015 }
1016
1017 fn name(&self) -> &str {
1018 "Materialized"
1019 }
1020}
1021
1022use radixdb_storage::QueryResult as StorageQueryResult;
1027
1028pub struct QueryResultOperator {
1040 result: Box<dyn StorageQueryResult>,
1041 schema: Vec<ColumnInfo>,
1042 ordering: OrderingProperty,
1043 opened: bool,
1044}
1045
1046impl QueryResultOperator {
1047 pub fn new(result: Box<dyn StorageQueryResult>, columns: Vec<String>) -> Self {
1049 let ordering = result.ascending_nulls_last_ordering().map_or(
1050 OrderingProperty::Unknown,
1051 OrderingProperty::ascending_nulls_last,
1052 );
1053 let schema = columns.into_iter().map(ColumnInfo::new).collect();
1054 Self {
1055 result,
1056 schema,
1057 ordering,
1058 opened: false,
1059 }
1060 }
1061}
1062
1063impl Operator for QueryResultOperator {
1064 fn open(&mut self) -> Result<()> {
1065 radixdb_storage::instrumentation::record_join_source_open(self.opened);
1066 self.opened = true;
1067 Ok(())
1068 }
1069
1070 fn next(&mut self) -> Result<Option<RowRef>> {
1071 if !self.opened {
1072 return Ok(None);
1073 }
1074
1075 if self.result.next() {
1076 Ok(Some(RowRef::deferred(self.result.take_deferred_row())))
1080 } else if let Some(error) = self.result.last_error() {
1081 Err(error)
1086 } else {
1087 Ok(None)
1088 }
1089 }
1090
1091 fn close(&mut self) -> Result<()> {
1092 self.result.close()
1093 }
1094
1095 fn schema(&self) -> &[ColumnInfo] {
1096 &self.schema
1097 }
1098
1099 fn estimated_rows(&self) -> Option<usize> {
1100 None
1102 }
1103
1104 fn ordering(&self) -> OrderingProperty {
1105 self.ordering.clone()
1106 }
1107
1108 fn name(&self) -> &str {
1109 "QueryResultScan"
1110 }
1111}
1112
1113#[cfg(test)]
1114#[allow(clippy::approx_constant)]
1115mod tests {
1116 use super::*;
1117
1118 #[test]
1119 fn join_projection_rejects_shape_and_side_indices_before_execution() {
1120 let wrong_width = JoinProjection {
1121 columns: vec![ColumnSource::Outer(0)],
1122 };
1123 assert!(wrong_width.validate(1, 1, 2).is_err());
1124
1125 let bad_left = JoinProjection {
1126 columns: vec![ColumnSource::Outer(1)],
1127 };
1128 assert!(bad_left.validate(1, 1, 1).is_err());
1129
1130 let bad_right = JoinProjection {
1131 columns: vec![ColumnSource::Inner(1)],
1132 };
1133 assert!(bad_right.validate(1, 1, 1).is_err());
1134
1135 let valid = JoinProjection {
1136 columns: vec![ColumnSource::Inner(0), ColumnSource::Outer(0)],
1137 };
1138 valid.validate(1, 1, 2).unwrap();
1139 }
1140
1141 #[test]
1142 fn test_composite_row_basic() {
1143 let left = Row::from_values(vec![Value::integer(1), Value::text("hello")]);
1144 let right = Row::from_values(vec![Value::float(3.14), Value::boolean(true)]);
1145
1146 let comp = CompositeRow::new(left, right);
1147
1148 assert_eq!(comp.len(), 4);
1149 assert_eq!(comp.get(0), Some(&Value::integer(1)));
1150 assert_eq!(comp.get(1), Some(&Value::text("hello")));
1151 assert_eq!(comp.get(2), Some(&Value::float(3.14)));
1152 assert_eq!(comp.get(3), Some(&Value::boolean(true)));
1153 assert_eq!(comp.get(4), None);
1154 }
1155
1156 #[test]
1157 fn test_composite_row_materialize() {
1158 let left = Row::from_values(vec![Value::integer(1)]);
1159 let right = Row::from_values(vec![Value::integer(2)]);
1160
1161 let comp = CompositeRow::new(left, right);
1162 let materialized = comp.materialize();
1163
1164 assert_eq!(materialized.len(), 2);
1165 assert_eq!(materialized.get(0), Some(&Value::integer(1)));
1166 assert_eq!(materialized.get(1), Some(&Value::integer(2)));
1167 }
1168
1169 #[test]
1170 fn test_row_ref_owned() {
1171 let row = Row::from_values(vec![Value::integer(42)]);
1172 let row_ref = RowRef::owned(row);
1173
1174 assert_eq!(row_ref.len(), 1);
1175 assert_eq!(row_ref.get(0), Some(&Value::integer(42)));
1176
1177 let owned = row_ref.into_owned();
1178 assert_eq!(owned.get(0), Some(&Value::integer(42)));
1179 }
1180
1181 #[test]
1182 fn test_row_ref_composite() {
1183 let left = Row::from_values(vec![Value::integer(1)]);
1184 let right = Row::from_values(vec![Value::integer(2)]);
1185 let row_ref = RowRef::composite(left, right);
1186
1187 assert_eq!(row_ref.len(), 2);
1188 assert_eq!(row_ref.get(0), Some(&Value::integer(1)));
1189 assert_eq!(row_ref.get(1), Some(&Value::integer(2)));
1190 }
1191
1192 #[test]
1193 fn projected_row_ref_keeps_transitive_join_slots_deferred() {
1194 let first = RowRef::projected(
1195 RowRef::owned(Row::from_values(vec![
1196 Value::integer(1),
1197 Value::text("payload"),
1198 ])),
1199 RowRef::owned(Row::from_values(vec![
1200 Value::integer(10),
1201 Value::text("dictionary"),
1202 ])),
1203 CompactArc::from(vec![
1204 ColumnSource::Outer(0),
1205 ColumnSource::Outer(1),
1206 ColumnSource::Inner(1),
1207 ]),
1208 );
1209 let second = RowRef::projected(
1210 first,
1211 RowRef::owned(Row::from_values(vec![
1212 Value::integer(20),
1213 Value::text("leaf"),
1214 ])),
1215 CompactArc::from(vec![
1216 ColumnSource::Outer(1),
1217 ColumnSource::Inner(1),
1218 ColumnSource::Outer(2),
1219 ]),
1220 );
1221
1222 assert!(second.is_deferred());
1223 assert_eq!(second.len(), 3);
1224 assert_eq!(second.get(0), Some(&Value::text("payload")));
1225 assert_eq!(second.get(1), Some(&Value::text("leaf")));
1226 assert_eq!(second.get(2), Some(&Value::text("dictionary")));
1227
1228 let materialized = second.into_owned();
1229 assert_eq!(
1230 materialized,
1231 Row::from_values(vec![
1232 Value::text("payload"),
1233 Value::text("leaf"),
1234 Value::text("dictionary"),
1235 ])
1236 );
1237 }
1238
1239 #[test]
1240 fn test_empty_operator() {
1241 let mut op = EmptyOperator::new();
1242 op.open().unwrap();
1243
1244 assert!(op.next().unwrap().is_none());
1245 assert!(op.next().unwrap().is_none());
1246
1247 op.close().unwrap();
1248 }
1249
1250 #[test]
1251 fn test_materialized_operator() {
1252 let rows = vec![
1253 Row::from_values(vec![Value::integer(1)]),
1254 Row::from_values(vec![Value::integer(2)]),
1255 Row::from_values(vec![Value::integer(3)]),
1256 ];
1257 let schema = vec![ColumnInfo::new("id")];
1258
1259 let mut op = MaterializedOperator::new(rows, schema);
1260 op.open().unwrap();
1261
1262 let row1 = op.next().unwrap().unwrap();
1263 assert_eq!(row1.get(0), Some(&Value::integer(1)));
1264
1265 let row2 = op.next().unwrap().unwrap();
1266 assert_eq!(row2.get(0), Some(&Value::integer(2)));
1267
1268 let row3 = op.next().unwrap().unwrap();
1269 assert_eq!(row3.get(0), Some(&Value::integer(3)));
1270
1271 assert!(op.next().unwrap().is_none());
1272
1273 op.close().unwrap();
1274 }
1275}