1use crate::binding::value_payload_bytes;
16use crate::columnar::ColumnarRowRef;
17use hawdb_core::{HawDBError, Result, Value, ValueRef};
18use hawdb_plan_cypher::{PhysicalOperatorId, PhysicalPlanKind};
19use std::collections::BTreeMap;
20use std::ops::Index;
21use std::sync::{Arc, OnceLock};
22
23pub type Row = BTreeMap<String, Value>;
24
25#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct QuerySchema {
28 columns: Arc<[String]>,
29}
30
31impl QuerySchema {
32 pub fn try_new(columns: impl IntoIterator<Item = String>) -> Result<Self> {
33 let columns = columns.into_iter().collect::<Vec<_>>();
34 let mut unique = std::collections::BTreeSet::new();
35 if let Some(duplicate) = columns
36 .iter()
37 .find(|column| !unique.insert(column.as_str()))
38 {
39 return Err(HawDBError::Semantic(format!(
40 "query result schema contains duplicate column {duplicate}"
41 )));
42 }
43 Ok(Self {
44 columns: Arc::from(columns),
45 })
46 }
47
48 pub fn empty() -> Self {
49 Self {
50 columns: Arc::from([]),
51 }
52 }
53
54 pub fn columns(&self) -> &[String] {
55 &self.columns
56 }
57
58 pub fn len(&self) -> usize {
59 self.columns.len()
60 }
61
62 pub fn is_empty(&self) -> bool {
63 self.columns.is_empty()
64 }
65
66 pub fn position(&self, name: &str) -> Option<usize> {
67 self.columns.iter().position(|column| column == name)
68 }
69}
70
71#[derive(Debug)]
72struct QueryRowStorage {
73 schema: QuerySchema,
74 values: QueryStoredValues,
75 row_count: usize,
76}
77
78#[derive(Debug)]
79enum QueryStoredValues {
80 Flat(Vec<Value>),
81 Segmented(Vec<Vec<Value>>),
82}
83
84impl QueryStoredValues {
85 fn len(&self) -> usize {
86 match self {
87 Self::Flat(values) => values.len(),
88 Self::Segmented(segments) => segments.iter().map(Vec::len).sum(),
89 }
90 }
91}
92
93mod owned_result;
94pub(crate) use owned_result::cloned_row_memory_bytes;
95
96#[derive(Clone)]
98pub struct QueryRow {
99 storage: Arc<QueryRowStorage>,
100 row: usize,
101}
102
103impl QueryRow {
104 pub fn schema(&self) -> &QuerySchema {
105 &self.storage.schema
106 }
107
108 pub fn len(&self) -> usize {
109 self.storage.schema.len()
110 }
111
112 pub fn is_empty(&self) -> bool {
113 self.len() == 0
114 }
115
116 pub fn value(&self, column: usize) -> Option<&Value> {
117 row_values(&self.storage, self.row)?.get(column)
118 }
119
120 pub fn get(&self, column: &str) -> Option<&Value> {
121 self.value(self.storage.schema.position(column)?)
122 }
123
124 pub fn contains_key(&self, column: &str) -> bool {
125 self.storage.schema.position(column).is_some()
126 }
127
128 pub fn iter(&self) -> impl ExactSizeIterator<Item = (&str, &Value)> {
129 self.storage
130 .schema
131 .columns()
132 .iter()
133 .map(String::as_str)
134 .zip(row_values(&self.storage, self.row).unwrap_or_default())
135 }
136
137 pub fn keys(&self) -> impl ExactSizeIterator<Item = &str> {
138 self.storage.schema.columns().iter().map(String::as_str)
139 }
140
141 pub fn values(&self) -> impl ExactSizeIterator<Item = &Value> {
142 row_values(&self.storage, self.row)
143 .unwrap_or_default()
144 .iter()
145 }
146
147 pub fn as_ref(&self) -> QueryRowRef<'_> {
148 QueryRowRef {
149 schema: &self.storage.schema,
150 values: row_values(&self.storage, self.row).unwrap_or_default(),
151 }
152 }
153
154 pub fn to_owned_row(&self) -> Row {
155 self.iter()
156 .map(|(name, value)| (name.to_owned(), value.clone()))
157 .collect()
158 }
159}
160
161impl std::fmt::Debug for QueryRow {
162 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163 f.debug_map().entries(self.iter()).finish()
164 }
165}
166
167impl PartialEq for QueryRow {
168 fn eq(&self, other: &Self) -> bool {
169 self.schema() == other.schema()
170 && row_values(&self.storage, self.row) == row_values(&other.storage, other.row)
171 }
172}
173
174impl Eq for QueryRow {}
175
176impl PartialEq<Row> for QueryRow {
177 fn eq(&self, other: &Row) -> bool {
178 row_equals_map(self.as_ref(), other)
179 }
180}
181
182impl PartialEq<QueryRow> for Row {
183 fn eq(&self, other: &QueryRow) -> bool {
184 other == self
185 }
186}
187
188impl Index<&str> for QueryRow {
189 type Output = Value;
190
191 fn index(&self, column: &str) -> &Self::Output {
192 self.get(column)
193 .unwrap_or_else(|| panic!("query result has no column {column}"))
194 }
195}
196
197#[derive(Debug, Clone, Copy)]
199pub struct QueryRowRef<'a> {
200 schema: &'a QuerySchema,
201 values: &'a [Value],
202}
203
204impl<'a> QueryRowRef<'a> {
205 pub fn schema(self) -> &'a QuerySchema {
206 self.schema
207 }
208
209 pub fn len(self) -> usize {
210 self.values.len()
211 }
212
213 pub fn is_empty(self) -> bool {
214 self.values.is_empty()
215 }
216
217 pub fn value(self, column: usize) -> Option<&'a Value> {
218 self.values.get(column)
219 }
220
221 pub fn get(self, column: &str) -> Option<&'a Value> {
222 self.value(self.schema.position(column)?)
223 }
224
225 pub fn contains_key(self, column: &str) -> bool {
226 self.schema.position(column).is_some()
227 }
228
229 pub fn iter(self) -> impl ExactSizeIterator<Item = (&'a str, &'a Value)> {
230 self.schema
231 .columns()
232 .iter()
233 .map(String::as_str)
234 .zip(self.values)
235 }
236
237 pub fn keys(self) -> impl ExactSizeIterator<Item = &'a str> {
238 self.schema.columns().iter().map(String::as_str)
239 }
240
241 pub fn values(self) -> impl ExactSizeIterator<Item = &'a Value> {
242 self.values.iter()
243 }
244
245 pub fn to_owned_row(self) -> Row {
246 self.iter()
247 .map(|(name, value)| (name.to_owned(), value.clone()))
248 .collect()
249 }
250}
251
252impl PartialEq<Row> for QueryRowRef<'_> {
253 fn eq(&self, other: &Row) -> bool {
254 row_equals_map(*self, other)
255 }
256}
257
258impl PartialEq<QueryRowRef<'_>> for Row {
259 fn eq(&self, other: &QueryRowRef<'_>) -> bool {
260 other == self
261 }
262}
263
264impl Index<&str> for QueryRowRef<'_> {
265 type Output = Value;
266
267 fn index(&self, column: &str) -> &Self::Output {
268 self.get(column)
269 .unwrap_or_else(|| panic!("query result has no column {column}"))
270 }
271}
272
273pub struct QueryRows {
275 storage: Arc<QueryRowStorage>,
276 indexed_rows: OnceLock<Vec<QueryRow>>,
277}
278
279impl QueryRows {
280 pub fn empty() -> Self {
281 Self::from_flat_values_unchecked(QuerySchema::empty(), Vec::new(), 0)
282 }
283
284 pub fn try_from_named_rows(schema: QuerySchema, rows: Vec<Row>) -> Result<Self> {
289 let mut builder = QueryRowsBuilder::with_schema(schema.clone(), rows.len());
290 for (ordinal, mut row) in rows.into_iter().enumerate() {
291 if row.len() != schema.len() {
292 return Err(HawDBError::Execution(format!(
293 "query result row {ordinal} has width {}, expected {}",
294 row.len(),
295 schema.len()
296 )));
297 }
298 builder.try_push_values(schema.columns().iter().map(|column| {
299 row.remove(column).ok_or_else(|| {
300 HawDBError::Execution(format!(
301 "query result row {ordinal} has no column {column}"
302 ))
303 })
304 }))?;
305 }
306 Ok(builder.finish())
307 }
308
309 fn from_flat_values_unchecked(
310 schema: QuerySchema,
311 values: Vec<Value>,
312 row_count: usize,
313 ) -> Self {
314 Self {
315 storage: Arc::new(QueryRowStorage {
316 schema,
317 values: QueryStoredValues::Flat(values),
318 row_count,
319 }),
320 indexed_rows: OnceLock::new(),
321 }
322 }
323
324 pub fn schema(&self) -> &QuerySchema {
325 &self.storage.schema
326 }
327
328 pub fn value_rows(&self) -> QueryValueRows<'_> {
329 QueryValueRows {
330 rows: self,
331 next: 0,
332 }
333 }
334
335 pub fn len(&self) -> usize {
336 self.storage.row_count
337 }
338
339 pub fn is_empty(&self) -> bool {
340 self.storage.row_count == 0
341 }
342
343 pub fn value(&self, row: usize, column: usize) -> Option<ValueRef<'_>> {
344 row_values(&self.storage, row)?
345 .get(column)
346 .map(Value::as_ref)
347 }
348
349 pub fn get(&self, row: usize, column: &str) -> Option<ValueRef<'_>> {
350 self.value(row, self.schema().position(column)?)
351 }
352
353 pub fn into_rows(self) -> Vec<Row> {
354 (0..self.len())
355 .map(|row| {
356 self.row(row)
357 .expect("row ordinal is in range")
358 .to_owned_row()
359 })
360 .collect()
361 }
362
363 pub fn row(&self, row: usize) -> Option<QueryRowRef<'_>> {
364 if row >= self.len() {
365 return None;
366 }
367 Some(QueryRowRef {
368 schema: &self.storage.schema,
369 values: row_values(&self.storage, row)?,
370 })
371 }
372
373 pub fn first(&self) -> Option<QueryRowRef<'_>> {
374 self.row(0)
375 }
376
377 pub fn last(&self) -> Option<QueryRowRef<'_>> {
378 self.len().checked_sub(1).and_then(|row| self.row(row))
379 }
380
381 pub fn iter(&self) -> QueryRowsIter<'_> {
382 QueryRowsIter {
383 rows: self,
384 next: 0,
385 }
386 }
387
388 pub fn retain(&mut self, mut keep: impl FnMut(QueryRowRef<'_>) -> bool) {
389 let width = self.schema().len();
390 let mut values = Vec::with_capacity(self.storage.values.len());
391 let mut row_count = 0usize;
392 for row in self.iter() {
393 if keep(row) {
394 values.extend_from_slice(row.values);
395 row_count = row_count.saturating_add(1);
396 }
397 }
398 let retained_row_count = values.len().checked_div(width).unwrap_or(row_count);
399 self.storage = Arc::new(QueryRowStorage {
400 schema: self.schema().clone(),
401 values: QueryStoredValues::Flat(values),
402 row_count: retained_row_count,
403 });
404 self.indexed_rows = OnceLock::new();
405 }
406
407 pub fn as_slice(&self) -> &[QueryRow] {
410 self.indexed_rows()
411 }
412
413 pub fn payload_bytes(&self) -> usize {
414 let names = self
415 .schema()
416 .columns()
417 .iter()
418 .fold(0usize, |total, name| total.saturating_add(name.len()));
419 stored_values(&self.storage).fold(names, |total, value| {
420 total.saturating_add(value_payload_bytes(value))
421 })
422 }
423
424 fn indexed_rows(&self) -> &[QueryRow] {
425 self.indexed_rows.get_or_init(|| {
426 (0..self.len())
427 .map(|row| QueryRow {
428 storage: Arc::clone(&self.storage),
429 row,
430 })
431 .collect()
432 })
433 }
434}
435
436impl Default for QueryRows {
437 fn default() -> Self {
438 Self::empty()
439 }
440}
441
442impl From<Vec<Row>> for QueryRows {
443 fn from(rows: Vec<Row>) -> Self {
444 let columns = rows
445 .iter()
446 .flat_map(|row| row.keys().cloned())
447 .collect::<std::collections::BTreeSet<_>>()
448 .into_iter()
449 .collect::<Vec<_>>();
450 let schema = QuerySchema::try_new(columns).expect("BTreeSet columns are unique");
451 let mut builder = QueryRowsBuilder::with_schema(schema.clone(), rows.len());
452 for mut row in rows {
453 builder
454 .push_values(
455 schema
456 .columns()
457 .iter()
458 .map(|column| row.remove(column).unwrap_or(Value::Null)),
459 )
460 .expect("row normalization always matches the inferred schema");
461 }
462 builder.finish()
463 }
464}
465
466impl FromIterator<Row> for QueryRows {
467 fn from_iter<T: IntoIterator<Item = Row>>(iter: T) -> Self {
468 iter.into_iter().collect::<Vec<_>>().into()
469 }
470}
471
472pub struct QueryRowsBuilder {
479 schema: Option<QuerySchema>,
480 values: Vec<Value>,
481 row_count: usize,
482 row_capacity_hint: usize,
483 segments: Option<Vec<Vec<Value>>>,
484 input_order: Option<owned_result::InputOrder>,
485}
486
487impl QueryRowsBuilder {
488 pub fn new() -> Self {
489 let mut builder = Self::with_row_capacity(0);
490 builder.segments = Some(Vec::new());
491 builder
492 }
493
494 pub fn with_row_capacity(row_capacity: usize) -> Self {
495 Self {
496 schema: None,
497 values: Vec::new(),
498 row_count: 0,
499 row_capacity_hint: row_capacity,
500 segments: None,
501 input_order: None,
502 }
503 }
504
505 pub fn with_schema(schema: QuerySchema, row_capacity: usize) -> Self {
506 Self {
507 values: Vec::with_capacity(row_capacity.saturating_mul(schema.len())),
508 schema: Some(schema),
509 row_count: 0,
510 row_capacity_hint: row_capacity,
511 segments: None,
512 input_order: None,
513 }
514 }
515
516 pub fn push_values(&mut self, values: impl IntoIterator<Item = Value>) -> Result<()> {
517 self.try_push_values(values.into_iter().map(Ok))
518 }
519
520 pub fn try_push_values(
524 &mut self,
525 values: impl IntoIterator<Item = Result<Value>>,
526 ) -> Result<()> {
527 let schema = self.schema.as_ref().ok_or_else(|| {
528 HawDBError::Execution(
529 "query result values require a schema before ordinal insertion".to_string(),
530 )
531 })?;
532 if self.segments.is_some() {
533 let values = values.into_iter().collect::<Result<Vec<_>>>()?;
534 if values.len() != schema.len() {
535 return Err(HawDBError::Execution(format!(
536 "query result row {} has width {}, expected {}",
537 self.row_count,
538 values.len(),
539 schema.len()
540 )));
541 }
542 return self.append_named_values(values.into_iter());
543 }
544 let start = self.values.len();
545 for value in values {
546 match value {
547 Ok(value) => self.values.push(value),
548 Err(error) => {
549 self.values.truncate(start);
550 return Err(error);
551 }
552 }
553 }
554 let width = self.values.len().saturating_sub(start);
555 if width != schema.len() {
556 self.values.truncate(start);
557 return Err(HawDBError::Execution(format!(
558 "query result row {} has width {width}, expected {}",
559 self.row_count,
560 schema.len()
561 )));
562 }
563 self.row_count = self.row_count.saturating_add(1);
564 Ok(())
565 }
566
567 pub fn push_named_row(&mut self, row: Row) -> Result<()> {
568 if self.schema.is_none() {
569 self.schema = Some(QuerySchema::try_new(row.keys().cloned())?);
570 self.values.reserve(
571 row.len()
572 .saturating_mul(self.row_capacity_hint.max(self.row_count.saturating_add(1))),
573 );
574 }
575 let schema = self.schema.as_ref().expect("query result schema is bound");
576 if row.len() != schema.len() {
577 return Err(HawDBError::Execution(format!(
578 "query result row {} has width {}, expected {}",
579 self.row_count,
580 row.len(),
581 schema.len()
582 )));
583 }
584 if let Some((ordinal, (name, expected))) = row
585 .keys()
586 .zip(schema.columns())
587 .enumerate()
588 .find(|(_, (name, expected))| *name != *expected)
589 {
590 return Err(HawDBError::Execution(format!(
591 "query result row {} column {ordinal} is {name}, expected {expected}",
592 self.row_count
593 )));
594 }
595 self.append_named_values(row.into_values())
596 }
597
598 fn append_named_values(&mut self, values: impl Iterator<Item = Value>) -> Result<()> {
599 if let Some(segments) = &mut self.segments {
600 let width = self
601 .schema
602 .as_ref()
603 .expect("query result schema is bound")
604 .len();
605 let rows_in_segment = self.row_count % OWNED_RESULT_SEGMENT_ROWS;
606 if rows_in_segment == 0 && self.row_count != 0 && width != 0 {
607 segments.push(std::mem::take(&mut self.values));
608 }
609 let needed = (rows_in_segment + 1).checked_mul(width).ok_or_else(|| {
610 HawDBError::Execution("query result segment capacity overflow".into())
611 })?;
612 if needed > self.values.capacity() {
613 let target_rows = (rows_in_segment + 1).next_power_of_two();
614 let target = target_rows.checked_mul(width).ok_or_else(|| {
615 HawDBError::Execution("query result segment capacity overflow".into())
616 })?;
617 self.values.reserve_exact(target - self.values.len());
618 }
619 }
620 self.values.extend(values);
621 self.row_count = self.row_count.saturating_add(1);
622 Ok(())
623 }
624
625 pub fn finish(mut self) -> QueryRows {
626 if let Some(mut segments) = self.segments.take() {
627 if segments.is_empty() {
628 return QueryRows::from_flat_values_unchecked(
629 self.schema.unwrap_or_else(QuerySchema::empty),
630 self.values,
631 self.row_count,
632 );
633 }
634 if !self.values.is_empty() {
635 segments.push(self.values);
636 }
637 return QueryRows {
638 storage: Arc::new(QueryRowStorage {
639 schema: self.schema.unwrap_or_else(QuerySchema::empty),
640 values: QueryStoredValues::Segmented(segments),
641 row_count: self.row_count,
642 }),
643 indexed_rows: OnceLock::new(),
644 };
645 }
646 QueryRows::from_flat_values_unchecked(
647 self.schema.unwrap_or_else(QuerySchema::empty),
648 self.values,
649 self.row_count,
650 )
651 }
652}
653
654impl Default for QueryRowsBuilder {
655 fn default() -> Self {
656 Self::new()
657 }
658}
659
660impl Clone for QueryRows {
661 fn clone(&self) -> Self {
662 Self {
663 storage: Arc::clone(&self.storage),
664 indexed_rows: OnceLock::new(),
665 }
666 }
667}
668
669impl std::fmt::Debug for QueryRows {
670 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
671 f.debug_struct("QueryRows")
672 .field("schema", self.schema())
673 .field("values", &self.storage.values)
674 .finish()
675 }
676}
677
678impl PartialEq for QueryRows {
679 fn eq(&self, other: &Self) -> bool {
680 self.schema() == other.schema()
681 && self.len() == other.len()
682 && stored_values(&self.storage).eq(stored_values(&other.storage))
683 }
684}
685
686impl Eq for QueryRows {}
687
688impl PartialEq<Vec<Row>> for QueryRows {
689 fn eq(&self, other: &Vec<Row>) -> bool {
690 self.len() == other.len() && self.iter().zip(other).all(|(left, right)| left == *right)
691 }
692}
693
694impl PartialEq<QueryRows> for Vec<Row> {
695 fn eq(&self, other: &QueryRows) -> bool {
696 other == self
697 }
698}
699
700impl Index<usize> for QueryRows {
701 type Output = QueryRow;
702
703 fn index(&self, row: usize) -> &Self::Output {
704 &self.indexed_rows()[row]
705 }
706}
707
708impl<'a> IntoIterator for &'a QueryRows {
709 type Item = QueryRowRef<'a>;
710 type IntoIter = QueryRowsIter<'a>;
711
712 fn into_iter(self) -> Self::IntoIter {
713 self.iter()
714 }
715}
716
717impl IntoIterator for QueryRows {
718 type Item = QueryRow;
719 type IntoIter = QueryRowsIntoIter;
720
721 fn into_iter(self) -> Self::IntoIter {
722 QueryRowsIntoIter {
723 storage: self.storage,
724 next: 0,
725 }
726 }
727}
728
729pub struct QueryRowsIter<'a> {
730 rows: &'a QueryRows,
731 next: usize,
732}
733
734impl<'a> Iterator for QueryRowsIter<'a> {
735 type Item = QueryRowRef<'a>;
736
737 fn next(&mut self) -> Option<Self::Item> {
738 let row = self.rows.row(self.next)?;
739 self.next = self.next.saturating_add(1);
740 Some(row)
741 }
742
743 fn size_hint(&self) -> (usize, Option<usize>) {
744 let remaining = self.rows.len().saturating_sub(self.next);
745 (remaining, Some(remaining))
746 }
747}
748
749impl ExactSizeIterator for QueryRowsIter<'_> {}
750
751pub struct QueryValueRows<'a> {
752 rows: &'a QueryRows,
753 next: usize,
754}
755
756impl QueryValueRows<'_> {
757 pub fn is_empty(&self) -> bool {
758 self.len() == 0
759 }
760}
761
762impl<'a> Iterator for QueryValueRows<'a> {
763 type Item = &'a [Value];
764
765 fn next(&mut self) -> Option<Self::Item> {
766 let values = self.rows.row(self.next)?.values;
767 self.next = self.next.saturating_add(1);
768 Some(values)
769 }
770
771 fn size_hint(&self) -> (usize, Option<usize>) {
772 let remaining = self.rows.len().saturating_sub(self.next);
773 (remaining, Some(remaining))
774 }
775}
776
777impl ExactSizeIterator for QueryValueRows<'_> {}
778
779pub struct QueryRowsIntoIter {
780 storage: Arc<QueryRowStorage>,
781 next: usize,
782}
783
784impl Iterator for QueryRowsIntoIter {
785 type Item = QueryRow;
786
787 fn next(&mut self) -> Option<Self::Item> {
788 if self.next >= self.storage.row_count {
789 return None;
790 }
791 let row = QueryRow {
792 storage: Arc::clone(&self.storage),
793 row: self.next,
794 };
795 self.next = self.next.saturating_add(1);
796 Some(row)
797 }
798
799 fn size_hint(&self) -> (usize, Option<usize>) {
800 let remaining = self.storage.row_count.saturating_sub(self.next);
801 (remaining, Some(remaining))
802 }
803}
804
805impl ExactSizeIterator for QueryRowsIntoIter {}
806
807const OWNED_RESULT_SEGMENT_ROWS: usize = 256;
808
809fn stored_values(storage: &QueryRowStorage) -> impl Iterator<Item = &Value> {
810 let (flat, segments) = match &storage.values {
811 QueryStoredValues::Flat(values) => (Some(values), None),
812 QueryStoredValues::Segmented(segments) => (None, Some(segments)),
813 };
814 flat.into_iter().flatten().chain(
815 segments
816 .into_iter()
817 .flatten()
818 .flat_map(|segment| segment.iter()),
819 )
820}
821
822fn row_values(storage: &QueryRowStorage, row: usize) -> Option<&[Value]> {
823 if row >= storage.row_count {
824 return None;
825 }
826 let width = storage.schema.len();
827 if width == 0 {
828 return Some(&[]);
829 }
830 let (values, ordinal) = match &storage.values {
831 QueryStoredValues::Segmented(segments) => (
832 segments.get(row / OWNED_RESULT_SEGMENT_ROWS)?,
833 row % OWNED_RESULT_SEGMENT_ROWS,
834 ),
835 QueryStoredValues::Flat(values) => (values, row),
836 };
837 let start = ordinal.checked_mul(width)?;
838 let end = start.checked_add(width)?;
839 values.get(start..end)
840}
841
842fn row_equals_map(row: QueryRowRef<'_>, other: &Row) -> bool {
843 row.len() == other.len()
844 && row
845 .iter()
846 .all(|(name, value)| other.get(name).is_some_and(|other| other == value))
847}
848
849#[derive(Debug, Clone, Copy)]
855pub enum RowRef<'a> {
856 Map(&'a Row),
857 Columnar(ColumnarRowRef<'a>),
858 Compact(QueryRowRef<'a>),
859 Projected(ProjectedRowRef<'a>),
860}
861
862impl<'a> RowRef<'a> {
863 pub fn len(self) -> usize {
864 match self {
865 Self::Map(row) => row.len(),
866 Self::Columnar(_) => self.iter().count(),
867 Self::Compact(row) => row.len(),
868 Self::Projected(row) => row.columns.len(),
869 }
870 }
871
872 pub fn is_empty(self) -> bool {
873 self.len() == 0
874 }
875
876 pub fn get(self, name: &str) -> Option<ValueRef<'a>> {
877 match self {
878 Self::Map(row) => row.get(name).map(Value::as_ref),
879 Self::Columnar(row) => row.get(name),
880 Self::Compact(row) => row.get(name).map(Value::as_ref),
881 Self::Projected(row) => row.get(name),
882 }
883 }
884
885 pub fn column(self, index: usize) -> Option<(&'a str, ValueRef<'a>)> {
886 match self {
887 Self::Map(row) => row
888 .iter()
889 .nth(index)
890 .map(|(name, value)| (name.as_str(), value.as_ref())),
891 Self::Columnar(row) => row.column(index),
892 Self::Compact(row) => Some((
893 row.schema().columns().get(index)?.as_str(),
894 row.value(index)?.as_ref(),
895 )),
896 Self::Projected(row) => row.column(index),
897 }
898 }
899
900 pub fn iter(self) -> RowRefIter<'a> {
901 match self {
902 Self::Map(row) => RowRefIter::Map(row.iter()),
903 Self::Columnar(row) => RowRefIter::Columnar { row, index: 0 },
904 Self::Compact(row) => RowRefIter::Compact { row, index: 0 },
905 Self::Projected(row) => RowRefIter::Projected { row, index: 0 },
906 }
907 }
908
909 pub fn to_owned_row(self) -> Row {
910 self.iter()
911 .map(|(name, value)| (name.to_owned(), value.to_owned_value()))
912 .collect()
913 }
914}
915
916#[derive(Clone, Copy)]
919pub struct ProjectedRowRef<'a> {
920 pub(crate) columns: &'a [(String, usize)],
921 pub(crate) value: &'a (dyn Fn(usize) -> ValueRef<'a> + Sync),
922}
923
924impl std::fmt::Debug for ProjectedRowRef<'_> {
925 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
926 formatter
927 .debug_map()
928 .entries(RowRef::Projected(*self).iter())
929 .finish()
930 }
931}
932
933impl<'a> ProjectedRowRef<'a> {
934 pub fn column(self, index: usize) -> Option<(&'a str, ValueRef<'a>)> {
935 let (name, projection) = self.columns.get(index)?;
936 Some((name.as_str(), (self.value)(*projection)))
937 }
938
939 pub fn get(self, name: &str) -> Option<ValueRef<'a>> {
940 let index = self
941 .columns
942 .binary_search_by(|(column, _)| column.as_str().cmp(name))
943 .ok()?;
944 self.column(index).map(|(_, value)| value)
945 }
946}
947
948impl<'a> From<&'a Row> for RowRef<'a> {
949 fn from(row: &'a Row) -> Self {
950 Self::Map(row)
951 }
952}
953
954impl<'a> From<ColumnarRowRef<'a>> for RowRef<'a> {
955 fn from(row: ColumnarRowRef<'a>) -> Self {
956 Self::Columnar(row)
957 }
958}
959
960impl<'a> From<QueryRowRef<'a>> for RowRef<'a> {
961 fn from(row: QueryRowRef<'a>) -> Self {
962 Self::Compact(row)
963 }
964}
965
966#[derive(Clone)]
967pub enum RowRefIter<'a> {
968 Map(std::collections::btree_map::Iter<'a, String, Value>),
969 Columnar {
970 row: ColumnarRowRef<'a>,
971 index: usize,
972 },
973 Compact {
974 row: QueryRowRef<'a>,
975 index: usize,
976 },
977 Projected {
978 row: ProjectedRowRef<'a>,
979 index: usize,
980 },
981}
982
983impl<'a> Iterator for RowRefIter<'a> {
984 type Item = (&'a str, ValueRef<'a>);
985
986 fn next(&mut self) -> Option<Self::Item> {
987 match self {
988 Self::Map(iter) => iter
989 .next()
990 .map(|(name, value)| (name.as_str(), value.as_ref())),
991 Self::Columnar { row, index } => loop {
992 let current = *index;
993 *index = index.saturating_add(1);
994 if current >= row.schema().len() {
995 return None;
996 }
997 if let Some(column) = row.column(current) {
998 return Some(column);
999 }
1000 },
1001 Self::Compact { row, index } => {
1002 let column = RowRef::Compact(*row).column(*index)?;
1003 *index += 1;
1004 Some(column)
1005 }
1006 Self::Projected { row, index } => {
1007 let column = row.column(*index)?;
1008 *index += 1;
1009 Some(column)
1010 }
1011 }
1012 }
1013}
1014
1015#[derive(Debug, Clone, PartialEq, Eq)]
1016pub struct BlockingOperatorMemoryReport {
1017 pub operator: String,
1018 pub budget_bytes: usize,
1019 pub peak_tracked_bytes: usize,
1020 pub input_rows: usize,
1021 pub candidate_rows: usize,
1023 pub replay_rows: usize,
1025 pub repartitions: usize,
1027 pub max_spill_bytes: u64,
1028 pub max_spill_runs: usize,
1029 pub spilled_bytes: u64,
1030 pub spill_run_count: usize,
1031 pub spilled_rows: usize,
1032}
1033
1034#[derive(Debug, Clone, PartialEq, Eq, Default)]
1035pub struct PipelineMemoryReport {
1036 pub query_memory_budget_bytes: usize,
1038 pub query_memory_peak_bytes: usize,
1040 pub query_memory_completion_bytes: usize,
1042 pub query_memory_account_count: usize,
1044 pub intermediate_rows: usize,
1046 pub intermediate_payload_bytes: usize,
1048 pub peak_batch_rows: usize,
1049 pub peak_batch_payload_bytes: usize,
1050 pub borrowed_node_projection_rows: usize,
1053 pub simple_case_dispatch_tables: usize,
1055 pub simple_case_dispatch_lookups: usize,
1057 pub native_projection_rows: usize,
1060 pub columnar_batches: usize,
1062 pub columnar_input_rows: usize,
1064 pub columnar_selected_rows: usize,
1066 pub morsel_count: usize,
1068 pub morsel_max_admitted_workers: usize,
1070 pub morsel_peak_active_workers: usize,
1072 pub morsel_peak_buffered_outputs: usize,
1075 pub morsel_peak_buffered_output_bytes: usize,
1077 pub morsel_peak_reorder_entries: usize,
1079 pub output_rows: usize,
1080 pub output_payload_bytes: usize,
1081 pub start_resident_bytes: Option<u64>,
1083 pub start_peak_resident_bytes: Option<u64>,
1085 pub steady_resident_bytes: Option<u64>,
1087 pub peak_resident_bytes: Option<u64>,
1089 pub steady_resident_growth_bytes: Option<u64>,
1091 pub lifetime_peak_resident_growth_bytes: Option<u64>,
1093 pub total_page_faults: Option<u64>,
1095 pub minor_page_faults: Option<u64>,
1097 pub major_page_faults: Option<u64>,
1098}
1099
1100#[derive(Debug, Clone, PartialEq, Eq)]
1102pub struct OperatorCardinalityProfile {
1103 pub operator_id: PhysicalOperatorId,
1104 pub operator: PhysicalPlanKind,
1105 pub actual_rows: Option<usize>,
1108}
1109
1110#[derive(Debug, Clone, PartialEq, Eq)]
1111pub struct ReadExecutionProfile<TScanPruningReport> {
1112 pub max_rows: Option<usize>,
1113 pub detection_row_cap: Option<usize>,
1114 pub row_limit_enforced_before_output: bool,
1115 pub operator_row_cap_enabled: bool,
1116 pub operator_cardinality_profiles: Vec<OperatorCardinalityProfile>,
1117 pub blocking_operator_kinds: Vec<String>,
1118 pub scan_pruning_reports: Vec<TScanPruningReport>,
1119 pub vector_execution_reports: Vec<crate::VectorExecutionReport>,
1120 pub graph_expansion_reports: Vec<crate::GraphExpansionExecutionReport>,
1121 pub text_seed_execution_reports: Vec<crate::TextSeedExecutionReport>,
1122 pub grouped_evidence_reports: Vec<crate::GroupedEvidenceExecutionReport>,
1123 pub blocking_operator_memory_reports: Vec<BlockingOperatorMemoryReport>,
1124 pub pipeline_memory_report: PipelineMemoryReport,
1125}
1126
1127impl<TScanPruningReport> ReadExecutionProfile<TScanPruningReport> {
1128 pub fn blocking_operator_count(&self) -> usize {
1129 self.blocking_operator_kinds.len()
1130 }
1131}
1132
1133#[derive(Debug, Clone, PartialEq, Eq)]
1134pub struct ProfiledQueryRows<TScanPruningReport> {
1135 pub rows: QueryRows,
1136 pub profile: ReadExecutionProfile<TScanPruningReport>,
1137}
1138
1139#[derive(Debug, Clone, PartialEq, Eq)]
1140pub struct ProfiledQueryStream<TScanPruningReport> {
1141 pub fully_streamed: bool,
1145 pub profile: ReadExecutionProfile<TScanPruningReport>,
1146}
1147
1148#[cfg(test)]
1149mod tests {
1150 use super::*;
1151 use crate::{
1152 BindingSchema, ColumnVector, ColumnarBatch, SlotDescriptor, SlotId, SlotType, Validity,
1153 };
1154 use hawdb_core::LogicalType;
1155 use std::sync::Arc;
1156
1157 #[test]
1158 fn segmented_results_preserve_access_ownership_and_flat_equality() {
1159 let expected = (0..1000)
1160 .map(|id| {
1161 Row::from([
1162 ("id".into(), Value::Int(id)),
1163 ("payload".into(), Value::String(format!("value-{id}\0"))),
1164 ])
1165 })
1166 .collect::<Vec<_>>();
1167 let mut builder = QueryRowsBuilder::new();
1168 for row in &expected {
1169 builder.push_named_row(row.clone()).unwrap();
1170 }
1171 let rows = builder.finish();
1172 assert_eq!(rows, QueryRows::from(expected.clone()));
1173 assert_eq!(
1174 rows.payload_bytes(),
1175 QueryRows::from(expected.clone()).payload_bytes()
1176 );
1177 for ordinal in [0, 255, 256, 511, 512, 999] {
1178 assert_eq!(rows.row(ordinal).unwrap(), expected[ordinal]);
1179 assert_eq!(rows[ordinal], expected[ordinal]);
1180 }
1181 let handle = rows[256].clone();
1182 let mut retained = rows.clone();
1183 retained.retain(|row| row["id"] == Value::Int(256));
1184 assert_eq!(retained.len(), 1);
1185 assert_eq!(retained.first().unwrap(), expected[256]);
1186 assert_eq!(rows.clone().into_rows(), expected);
1187 drop(rows);
1188 assert_eq!(handle["id"], Value::Int(256));
1189 }
1190
1191 #[test]
1192 fn named_rows_follow_the_declared_schema_even_when_empty() {
1193 let schema = QuerySchema::try_new(["name".to_string(), "id".to_string()]).unwrap();
1194 let rows = QueryRows::try_from_named_rows(
1195 schema.clone(),
1196 vec![Row::from([
1197 ("id".into(), Value::Int(1)),
1198 ("name".into(), Value::String("one".into())),
1199 ])],
1200 )
1201 .unwrap();
1202 assert_eq!(rows.schema(), &schema);
1203 assert_eq!(
1204 rows.value_rows().collect::<Vec<_>>(),
1205 vec![&[Value::String("one".into()), Value::Int(1)][..]]
1206 );
1207
1208 let empty = QueryRows::try_from_named_rows(schema.clone(), Vec::new()).unwrap();
1209 assert_eq!(empty.schema(), &schema);
1210 assert!(empty.is_empty());
1211
1212 for (row, message) in [
1213 (
1214 Row::from([("id".into(), Value::Int(1))]),
1215 "query result row 0 has width 1, expected 2",
1216 ),
1217 (
1218 Row::from([("id".into(), Value::Int(1)), ("title".into(), Value::Null)]),
1219 "query result row 0 has no column name",
1220 ),
1221 ] {
1222 assert_eq!(
1223 QueryRows::try_from_named_rows(schema.clone(), vec![row]),
1224 Err(HawDBError::Execution(message.to_string()))
1225 );
1226 }
1227 }
1228
1229 #[test]
1230 fn segmented_results_bound_unknown_cardinality_capacity() {
1231 let mut builder = QueryRowsBuilder::new();
1232 for id in 0..100_000 {
1233 builder
1234 .push_named_row(Row::from([
1235 ("id".into(), Value::Int(id)),
1236 ("score".into(), Value::Float(id as f64)),
1237 ("title".into(), Value::String("x".repeat(24))),
1238 ]))
1239 .unwrap();
1240 }
1241 let rows = builder.finish();
1242 let QueryStoredValues::Segmented(segments) = &rows.storage.values else {
1243 panic!("large results require segmented storage");
1244 };
1245 assert_eq!(
1246 segments.len(),
1247 100_000_usize.div_ceil(OWNED_RESULT_SEGMENT_ROWS)
1248 );
1249 let capacity = segments.iter().map(Vec::capacity).sum::<usize>();
1250 assert!(capacity >= 300_000);
1251 assert!(capacity <= 300_000 + 3 * OWNED_RESULT_SEGMENT_ROWS);
1252 assert!(segments
1253 .iter()
1254 .all(|segment| segment.capacity() <= 3 * OWNED_RESULT_SEGMENT_ROWS));
1255 assert_eq!(rows.get(99_999, "id"), Some(ValueRef::Int(99_999)));
1256 }
1257
1258 #[test]
1259 fn segmented_results_handle_empty_rows_and_boundary_rollback() {
1260 let mut empty = QueryRowsBuilder::new();
1261 for _ in 0..513 {
1262 empty.push_named_row(Row::new()).unwrap();
1263 }
1264 let empty = empty.finish();
1265 assert_eq!(empty.len(), 513);
1266 assert!(empty.row(512).unwrap().is_empty());
1267 let mut builder = QueryRowsBuilder::new();
1268 for id in 0..256 {
1269 builder
1270 .push_named_row(Row::from([("id".into(), Value::Int(id))]))
1271 .unwrap();
1272 }
1273 assert!(builder
1274 .push_named_row(Row::from([("wrong".into(), Value::Int(256))]))
1275 .is_err());
1276 assert!(builder
1277 .try_push_values([Err(HawDBError::Execution("failed".into()))])
1278 .is_err());
1279 assert!(builder.push_values([]).is_err());
1280 builder.push_values([Value::Int(256)]).unwrap();
1281 let rows = builder.finish();
1282 assert_eq!(rows.len(), 257);
1283 assert_eq!(rows.get(256, "id"), Some(ValueRef::Int(256)));
1284 }
1285
1286 #[test]
1287 fn counts_blocking_operator_kinds() {
1288 let profile = ReadExecutionProfile::<()> {
1289 max_rows: Some(10),
1290 detection_row_cap: Some(11),
1291 row_limit_enforced_before_output: true,
1292 operator_row_cap_enabled: true,
1293 operator_cardinality_profiles: Vec::new(),
1294 blocking_operator_kinds: vec!["sort".to_string(), "aggregate".to_string()],
1295 scan_pruning_reports: Vec::new(),
1296 vector_execution_reports: Vec::new(),
1297 graph_expansion_reports: Vec::new(),
1298 text_seed_execution_reports: Vec::new(),
1299 grouped_evidence_reports: Vec::new(),
1300 blocking_operator_memory_reports: Vec::new(),
1301 pipeline_memory_report: PipelineMemoryReport::default(),
1302 };
1303 assert_eq!(profile.blocking_operator_count(), 2);
1304 }
1305
1306 #[test]
1307 fn borrowed_map_row_materializes_only_on_request() {
1308 let row = Row::from([
1309 ("id".to_string(), Value::Int(7)),
1310 ("content".to_string(), Value::String("payload".into())),
1311 ]);
1312 let row_ref = RowRef::from(&row);
1313
1314 assert_eq!(row_ref.get("id"), Some(ValueRef::Int(7)));
1315 assert_eq!(row_ref.get("content").unwrap().as_str(), Some("payload"));
1316 assert_eq!(row_ref.to_owned_row(), row);
1317 }
1318
1319 #[test]
1320 fn schema_bearing_rows_keep_names_once_in_a_flat_value_buffer() {
1321 let schema = QuerySchema::try_new(["payload".to_string(), "id".to_string()]).unwrap();
1322 let mut builder = QueryRowsBuilder::with_schema(schema, 2);
1323 builder
1324 .push_values([Value::String("one".to_string()), Value::Int(1)])
1325 .unwrap();
1326 builder
1327 .push_values([Value::String("two".to_string()), Value::Int(2)])
1328 .unwrap();
1329 let rows = builder.finish();
1330
1331 assert_eq!(rows.schema().columns(), ["payload", "id"]);
1332 assert_eq!(rows.get(1, "payload").unwrap().as_str(), Some("two"));
1333 assert_eq!(rows.value(0, 1), Some(ValueRef::Int(1)));
1334 assert_eq!(rows.payload_bytes(), "payload".len() + "id".len() + 6 + 16);
1335 assert_eq!(rows.len(), 2);
1336 assert!(!rows.is_empty());
1337 assert_eq!(rows.storage.values.len(), 4);
1338 assert_eq!(rows[0]["payload"], Value::String("one".to_string()));
1339 assert_eq!(rows.iter().count(), 2);
1340 assert_eq!(rows.storage.values.len(), 4);
1341 }
1342
1343 #[test]
1344 fn schema_bearing_rows_reject_duplicate_columns_and_width_mismatch() {
1345 assert!(QuerySchema::try_new(["id".to_string(), "id".to_string()]).is_err());
1346 let schema = QuerySchema::try_new(["id".to_string()]).unwrap();
1347 let mut invalid = QueryRowsBuilder::with_schema(schema, 1);
1348 assert!(invalid.push_values([]).is_err());
1349
1350 let mut left = QueryRowsBuilder::with_schema(
1351 QuerySchema::try_new(["left".to_string(), "right".to_string()]).unwrap(),
1352 1,
1353 );
1354 left.push_values([Value::Int(1), Value::Int(2)]).unwrap();
1355 let left = left.finish();
1356 let mut right = QueryRowsBuilder::with_schema(
1357 QuerySchema::try_new(["right".to_string(), "left".to_string()]).unwrap(),
1358 1,
1359 );
1360 right.push_values([Value::Int(1), Value::Int(2)]).unwrap();
1361 let right = right.finish();
1362 assert_ne!(left, right);
1363 }
1364
1365 #[test]
1366 fn fallible_flat_row_append_rolls_back_partial_values() {
1367 let schema = QuerySchema::try_new(["left".to_string(), "right".to_string()]).unwrap();
1368 let mut builder = QueryRowsBuilder::with_schema(schema, 1);
1369 assert!(builder
1370 .try_push_values([
1371 Ok(Value::Int(1)),
1372 Err(HawDBError::Execution("projection failed".to_string())),
1373 ])
1374 .is_err());
1375 builder.push_values([Value::Int(2), Value::Int(3)]).unwrap();
1376 let rows = builder.finish();
1377
1378 assert_eq!(rows.len(), 1);
1379 assert_eq!(rows.value(0, 0), Some(ValueRef::Int(2)));
1380 assert_eq!(rows.value(0, 1), Some(ValueRef::Int(3)));
1381 assert_eq!(rows.storage.values.len(), 2);
1382 }
1383
1384 #[test]
1385 fn named_row_builder_binds_schema_once_and_moves_values_by_ordinal() {
1386 let mut builder = QueryRowsBuilder::with_row_capacity(2);
1387 builder
1388 .push_named_row(Row::from([
1389 ("id".to_string(), Value::Int(1)),
1390 ("payload".to_string(), Value::String("one".to_string())),
1391 ]))
1392 .unwrap();
1393 builder
1394 .push_named_row(Row::from([
1395 ("id".to_string(), Value::Int(2)),
1396 ("payload".to_string(), Value::String("two".to_string())),
1397 ]))
1398 .unwrap();
1399 let rows = builder.finish();
1400
1401 assert_eq!(rows.schema().columns(), ["id", "payload"]);
1402 assert_eq!(rows.value(1, 0), Some(ValueRef::Int(2)));
1403 assert_eq!(rows.get(0, "payload").unwrap().as_str(), Some("one"));
1404 assert_eq!(rows.storage.values.len(), 4);
1405 }
1406
1407 #[test]
1408 fn named_row_builder_rejects_schema_drift_without_partial_append() {
1409 let mut builder = QueryRowsBuilder::new();
1410 builder
1411 .push_named_row(Row::from([("id".to_string(), Value::Int(1))]))
1412 .unwrap();
1413 assert!(builder
1414 .push_named_row(Row::from([("name".to_string(), Value::Int(2))]))
1415 .is_err());
1416 let rows = builder.finish();
1417
1418 assert_eq!(rows.len(), 1);
1419 assert_eq!(rows.get(0, "id"), Some(ValueRef::Int(1)));
1420 }
1421
1422 #[test]
1423 fn borrowed_columnar_row_pulls_values_without_a_row_map() {
1424 let schema = Arc::new(
1425 BindingSchema::try_new(vec![
1426 SlotDescriptor {
1427 id: SlotId(0),
1428 name: "id".into(),
1429 slot_type: SlotType::NodeId,
1430 },
1431 SlotDescriptor {
1432 id: SlotId(1),
1433 name: "content".into(),
1434 slot_type: SlotType::logical(LogicalType::Text),
1435 },
1436 ])
1437 .unwrap(),
1438 );
1439 let batch = ColumnarBatch::try_new(
1440 schema,
1441 vec![
1442 Arc::new(ColumnVector::node_ids(vec![7])),
1443 Arc::new(ColumnVector::Utf8 {
1444 values: vec!["payload".to_string()].into(),
1445 validity: Validity::all(1),
1446 }),
1447 ],
1448 )
1449 .unwrap();
1450 let row_ref = RowRef::from(batch.rows().next().unwrap());
1451
1452 assert_eq!(row_ref.get("id"), Some(ValueRef::Int(7)));
1453 assert_eq!(row_ref.get("content").unwrap().as_str(), Some("payload"));
1454 assert_eq!(
1455 row_ref.to_owned_row(),
1456 Row::from([
1457 ("content".into(), Value::String("payload".into())),
1458 ("id".into(), Value::Int(7)),
1459 ])
1460 );
1461 }
1462}