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}
485
486impl QueryRowsBuilder {
487 pub fn new() -> Self {
488 let mut builder = Self::with_row_capacity(0);
489 builder.segments = Some(Vec::new());
490 builder
491 }
492
493 pub fn with_row_capacity(row_capacity: usize) -> Self {
494 Self {
495 schema: None,
496 values: Vec::new(),
497 row_count: 0,
498 row_capacity_hint: row_capacity,
499 segments: None,
500 }
501 }
502
503 pub fn with_schema(schema: QuerySchema, row_capacity: usize) -> Self {
504 Self {
505 values: Vec::with_capacity(row_capacity.saturating_mul(schema.len())),
506 schema: Some(schema),
507 row_count: 0,
508 row_capacity_hint: row_capacity,
509 segments: None,
510 }
511 }
512
513 pub fn push_values(&mut self, values: impl IntoIterator<Item = Value>) -> Result<()> {
514 self.try_push_values(values.into_iter().map(Ok))
515 }
516
517 pub fn try_push_values(
521 &mut self,
522 values: impl IntoIterator<Item = Result<Value>>,
523 ) -> Result<()> {
524 let schema = self.schema.as_ref().ok_or_else(|| {
525 HawDBError::Execution(
526 "query result values require a schema before ordinal insertion".to_string(),
527 )
528 })?;
529 if self.segments.is_some() {
530 let values = values.into_iter().collect::<Result<Vec<_>>>()?;
531 if values.len() != schema.len() {
532 return Err(HawDBError::Execution(format!(
533 "query result row {} has width {}, expected {}",
534 self.row_count,
535 values.len(),
536 schema.len()
537 )));
538 }
539 return self.append_named_values(values.into_iter());
540 }
541 let start = self.values.len();
542 for value in values {
543 match value {
544 Ok(value) => self.values.push(value),
545 Err(error) => {
546 self.values.truncate(start);
547 return Err(error);
548 }
549 }
550 }
551 let width = self.values.len().saturating_sub(start);
552 if width != schema.len() {
553 self.values.truncate(start);
554 return Err(HawDBError::Execution(format!(
555 "query result row {} has width {width}, expected {}",
556 self.row_count,
557 schema.len()
558 )));
559 }
560 self.row_count = self.row_count.saturating_add(1);
561 Ok(())
562 }
563
564 pub fn push_named_row(&mut self, row: Row) -> Result<()> {
565 if self.schema.is_none() {
566 self.schema = Some(QuerySchema::try_new(row.keys().cloned())?);
567 self.values.reserve(
568 row.len()
569 .saturating_mul(self.row_capacity_hint.max(self.row_count.saturating_add(1))),
570 );
571 }
572 let schema = self.schema.as_ref().expect("query result schema is bound");
573 if row.len() != schema.len() {
574 return Err(HawDBError::Execution(format!(
575 "query result row {} has width {}, expected {}",
576 self.row_count,
577 row.len(),
578 schema.len()
579 )));
580 }
581 if let Some((ordinal, (name, expected))) = row
582 .keys()
583 .zip(schema.columns())
584 .enumerate()
585 .find(|(_, (name, expected))| *name != *expected)
586 {
587 return Err(HawDBError::Execution(format!(
588 "query result row {} column {ordinal} is {name}, expected {expected}",
589 self.row_count
590 )));
591 }
592 self.append_named_values(row.into_values())
593 }
594
595 fn append_named_values(&mut self, values: impl Iterator<Item = Value>) -> Result<()> {
596 if let Some(segments) = &mut self.segments {
597 let width = self
598 .schema
599 .as_ref()
600 .expect("query result schema is bound")
601 .len();
602 let rows_in_segment = self.row_count % OWNED_RESULT_SEGMENT_ROWS;
603 if rows_in_segment == 0 && self.row_count != 0 && width != 0 {
604 segments.push(std::mem::take(&mut self.values));
605 }
606 let needed = (rows_in_segment + 1).checked_mul(width).ok_or_else(|| {
607 HawDBError::Execution("query result segment capacity overflow".into())
608 })?;
609 if needed > self.values.capacity() {
610 let target_rows = (rows_in_segment + 1).next_power_of_two();
611 let target = target_rows.checked_mul(width).ok_or_else(|| {
612 HawDBError::Execution("query result segment capacity overflow".into())
613 })?;
614 self.values.reserve_exact(target - self.values.len());
615 }
616 }
617 self.values.extend(values);
618 self.row_count = self.row_count.saturating_add(1);
619 Ok(())
620 }
621
622 pub fn finish(mut self) -> QueryRows {
623 if let Some(mut segments) = self.segments.take() {
624 if segments.is_empty() {
625 return QueryRows::from_flat_values_unchecked(
626 self.schema.unwrap_or_else(QuerySchema::empty),
627 self.values,
628 self.row_count,
629 );
630 }
631 if !self.values.is_empty() {
632 segments.push(self.values);
633 }
634 return QueryRows {
635 storage: Arc::new(QueryRowStorage {
636 schema: self.schema.unwrap_or_else(QuerySchema::empty),
637 values: QueryStoredValues::Segmented(segments),
638 row_count: self.row_count,
639 }),
640 indexed_rows: OnceLock::new(),
641 };
642 }
643 QueryRows::from_flat_values_unchecked(
644 self.schema.unwrap_or_else(QuerySchema::empty),
645 self.values,
646 self.row_count,
647 )
648 }
649}
650
651impl Default for QueryRowsBuilder {
652 fn default() -> Self {
653 Self::new()
654 }
655}
656
657impl Clone for QueryRows {
658 fn clone(&self) -> Self {
659 Self {
660 storage: Arc::clone(&self.storage),
661 indexed_rows: OnceLock::new(),
662 }
663 }
664}
665
666impl std::fmt::Debug for QueryRows {
667 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
668 f.debug_struct("QueryRows")
669 .field("schema", self.schema())
670 .field("values", &self.storage.values)
671 .finish()
672 }
673}
674
675impl PartialEq for QueryRows {
676 fn eq(&self, other: &Self) -> bool {
677 self.schema() == other.schema()
678 && self.len() == other.len()
679 && stored_values(&self.storage).eq(stored_values(&other.storage))
680 }
681}
682
683impl Eq for QueryRows {}
684
685impl PartialEq<Vec<Row>> for QueryRows {
686 fn eq(&self, other: &Vec<Row>) -> bool {
687 self.len() == other.len() && self.iter().zip(other).all(|(left, right)| left == *right)
688 }
689}
690
691impl PartialEq<QueryRows> for Vec<Row> {
692 fn eq(&self, other: &QueryRows) -> bool {
693 other == self
694 }
695}
696
697impl Index<usize> for QueryRows {
698 type Output = QueryRow;
699
700 fn index(&self, row: usize) -> &Self::Output {
701 &self.indexed_rows()[row]
702 }
703}
704
705impl<'a> IntoIterator for &'a QueryRows {
706 type Item = QueryRowRef<'a>;
707 type IntoIter = QueryRowsIter<'a>;
708
709 fn into_iter(self) -> Self::IntoIter {
710 self.iter()
711 }
712}
713
714impl IntoIterator for QueryRows {
715 type Item = QueryRow;
716 type IntoIter = QueryRowsIntoIter;
717
718 fn into_iter(self) -> Self::IntoIter {
719 QueryRowsIntoIter {
720 storage: self.storage,
721 next: 0,
722 }
723 }
724}
725
726pub struct QueryRowsIter<'a> {
727 rows: &'a QueryRows,
728 next: usize,
729}
730
731impl<'a> Iterator for QueryRowsIter<'a> {
732 type Item = QueryRowRef<'a>;
733
734 fn next(&mut self) -> Option<Self::Item> {
735 let row = self.rows.row(self.next)?;
736 self.next = self.next.saturating_add(1);
737 Some(row)
738 }
739
740 fn size_hint(&self) -> (usize, Option<usize>) {
741 let remaining = self.rows.len().saturating_sub(self.next);
742 (remaining, Some(remaining))
743 }
744}
745
746impl ExactSizeIterator for QueryRowsIter<'_> {}
747
748pub struct QueryValueRows<'a> {
749 rows: &'a QueryRows,
750 next: usize,
751}
752
753impl QueryValueRows<'_> {
754 pub fn is_empty(&self) -> bool {
755 self.len() == 0
756 }
757}
758
759impl<'a> Iterator for QueryValueRows<'a> {
760 type Item = &'a [Value];
761
762 fn next(&mut self) -> Option<Self::Item> {
763 let values = self.rows.row(self.next)?.values;
764 self.next = self.next.saturating_add(1);
765 Some(values)
766 }
767
768 fn size_hint(&self) -> (usize, Option<usize>) {
769 let remaining = self.rows.len().saturating_sub(self.next);
770 (remaining, Some(remaining))
771 }
772}
773
774impl ExactSizeIterator for QueryValueRows<'_> {}
775
776pub struct QueryRowsIntoIter {
777 storage: Arc<QueryRowStorage>,
778 next: usize,
779}
780
781impl Iterator for QueryRowsIntoIter {
782 type Item = QueryRow;
783
784 fn next(&mut self) -> Option<Self::Item> {
785 if self.next >= self.storage.row_count {
786 return None;
787 }
788 let row = QueryRow {
789 storage: Arc::clone(&self.storage),
790 row: self.next,
791 };
792 self.next = self.next.saturating_add(1);
793 Some(row)
794 }
795
796 fn size_hint(&self) -> (usize, Option<usize>) {
797 let remaining = self.storage.row_count.saturating_sub(self.next);
798 (remaining, Some(remaining))
799 }
800}
801
802impl ExactSizeIterator for QueryRowsIntoIter {}
803
804const OWNED_RESULT_SEGMENT_ROWS: usize = 256;
805
806fn stored_values(storage: &QueryRowStorage) -> impl Iterator<Item = &Value> {
807 let (flat, segments) = match &storage.values {
808 QueryStoredValues::Flat(values) => (Some(values), None),
809 QueryStoredValues::Segmented(segments) => (None, Some(segments)),
810 };
811 flat.into_iter().flatten().chain(
812 segments
813 .into_iter()
814 .flatten()
815 .flat_map(|segment| segment.iter()),
816 )
817}
818
819fn row_values(storage: &QueryRowStorage, row: usize) -> Option<&[Value]> {
820 if row >= storage.row_count {
821 return None;
822 }
823 let width = storage.schema.len();
824 if width == 0 {
825 return Some(&[]);
826 }
827 let (values, ordinal) = match &storage.values {
828 QueryStoredValues::Segmented(segments) => (
829 segments.get(row / OWNED_RESULT_SEGMENT_ROWS)?,
830 row % OWNED_RESULT_SEGMENT_ROWS,
831 ),
832 QueryStoredValues::Flat(values) => (values, row),
833 };
834 let start = ordinal.checked_mul(width)?;
835 let end = start.checked_add(width)?;
836 values.get(start..end)
837}
838
839fn row_equals_map(row: QueryRowRef<'_>, other: &Row) -> bool {
840 row.len() == other.len()
841 && row
842 .iter()
843 .all(|(name, value)| other.get(name).is_some_and(|other| other == value))
844}
845
846#[derive(Debug, Clone, Copy)]
852pub enum RowRef<'a> {
853 Map(&'a Row),
854 Columnar(ColumnarRowRef<'a>),
855 Compact(QueryRowRef<'a>),
856 Projected(ProjectedRowRef<'a>),
857}
858
859impl<'a> RowRef<'a> {
860 pub fn len(self) -> usize {
861 match self {
862 Self::Map(row) => row.len(),
863 Self::Columnar(_) => self.iter().count(),
864 Self::Compact(row) => row.len(),
865 Self::Projected(row) => row.columns.len(),
866 }
867 }
868
869 pub fn is_empty(self) -> bool {
870 self.len() == 0
871 }
872
873 pub fn get(self, name: &str) -> Option<ValueRef<'a>> {
874 match self {
875 Self::Map(row) => row.get(name).map(Value::as_ref),
876 Self::Columnar(row) => row.get(name),
877 Self::Compact(row) => row.get(name).map(Value::as_ref),
878 Self::Projected(row) => row.get(name),
879 }
880 }
881
882 pub fn column(self, index: usize) -> Option<(&'a str, ValueRef<'a>)> {
883 match self {
884 Self::Map(row) => row
885 .iter()
886 .nth(index)
887 .map(|(name, value)| (name.as_str(), value.as_ref())),
888 Self::Columnar(row) => row.column(index),
889 Self::Compact(row) => Some((
890 row.schema().columns().get(index)?.as_str(),
891 row.value(index)?.as_ref(),
892 )),
893 Self::Projected(row) => row.column(index),
894 }
895 }
896
897 pub fn iter(self) -> RowRefIter<'a> {
898 match self {
899 Self::Map(row) => RowRefIter::Map(row.iter()),
900 Self::Columnar(row) => RowRefIter::Columnar { row, index: 0 },
901 Self::Compact(row) => RowRefIter::Compact { row, index: 0 },
902 Self::Projected(row) => RowRefIter::Projected { row, index: 0 },
903 }
904 }
905
906 pub fn to_owned_row(self) -> Row {
907 self.iter()
908 .map(|(name, value)| (name.to_owned(), value.to_owned_value()))
909 .collect()
910 }
911}
912
913#[derive(Clone, Copy)]
916pub struct ProjectedRowRef<'a> {
917 pub(crate) columns: &'a [(String, usize)],
918 pub(crate) value: &'a (dyn Fn(usize) -> ValueRef<'a> + Sync),
919}
920
921impl std::fmt::Debug for ProjectedRowRef<'_> {
922 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
923 formatter
924 .debug_map()
925 .entries(RowRef::Projected(*self).iter())
926 .finish()
927 }
928}
929
930impl<'a> ProjectedRowRef<'a> {
931 pub fn column(self, index: usize) -> Option<(&'a str, ValueRef<'a>)> {
932 let (name, projection) = self.columns.get(index)?;
933 Some((name.as_str(), (self.value)(*projection)))
934 }
935
936 pub fn get(self, name: &str) -> Option<ValueRef<'a>> {
937 let index = self
938 .columns
939 .binary_search_by(|(column, _)| column.as_str().cmp(name))
940 .ok()?;
941 self.column(index).map(|(_, value)| value)
942 }
943}
944
945impl<'a> From<&'a Row> for RowRef<'a> {
946 fn from(row: &'a Row) -> Self {
947 Self::Map(row)
948 }
949}
950
951impl<'a> From<ColumnarRowRef<'a>> for RowRef<'a> {
952 fn from(row: ColumnarRowRef<'a>) -> Self {
953 Self::Columnar(row)
954 }
955}
956
957impl<'a> From<QueryRowRef<'a>> for RowRef<'a> {
958 fn from(row: QueryRowRef<'a>) -> Self {
959 Self::Compact(row)
960 }
961}
962
963#[derive(Clone)]
964pub enum RowRefIter<'a> {
965 Map(std::collections::btree_map::Iter<'a, String, Value>),
966 Columnar {
967 row: ColumnarRowRef<'a>,
968 index: usize,
969 },
970 Compact {
971 row: QueryRowRef<'a>,
972 index: usize,
973 },
974 Projected {
975 row: ProjectedRowRef<'a>,
976 index: usize,
977 },
978}
979
980impl<'a> Iterator for RowRefIter<'a> {
981 type Item = (&'a str, ValueRef<'a>);
982
983 fn next(&mut self) -> Option<Self::Item> {
984 match self {
985 Self::Map(iter) => iter
986 .next()
987 .map(|(name, value)| (name.as_str(), value.as_ref())),
988 Self::Columnar { row, index } => loop {
989 let current = *index;
990 *index = index.saturating_add(1);
991 if current >= row.schema().len() {
992 return None;
993 }
994 if let Some(column) = row.column(current) {
995 return Some(column);
996 }
997 },
998 Self::Compact { row, index } => {
999 let column = RowRef::Compact(*row).column(*index)?;
1000 *index += 1;
1001 Some(column)
1002 }
1003 Self::Projected { row, index } => {
1004 let column = row.column(*index)?;
1005 *index += 1;
1006 Some(column)
1007 }
1008 }
1009 }
1010}
1011
1012#[derive(Debug, Clone, PartialEq, Eq)]
1013pub struct BlockingOperatorMemoryReport {
1014 pub operator: String,
1015 pub budget_bytes: usize,
1016 pub peak_tracked_bytes: usize,
1017 pub input_rows: usize,
1018 pub candidate_rows: usize,
1020 pub replay_rows: usize,
1022 pub repartitions: usize,
1024 pub max_spill_bytes: u64,
1025 pub max_spill_runs: usize,
1026 pub spilled_bytes: u64,
1027 pub spill_run_count: usize,
1028 pub spilled_rows: usize,
1029}
1030
1031#[derive(Debug, Clone, PartialEq, Eq, Default)]
1032pub struct PipelineMemoryReport {
1033 pub query_memory_budget_bytes: usize,
1035 pub query_memory_peak_bytes: usize,
1037 pub query_memory_completion_bytes: usize,
1039 pub query_memory_account_count: usize,
1041 pub intermediate_rows: usize,
1043 pub intermediate_payload_bytes: usize,
1045 pub peak_batch_rows: usize,
1046 pub peak_batch_payload_bytes: usize,
1047 pub borrowed_node_projection_rows: usize,
1050 pub native_projection_rows: usize,
1053 pub columnar_batches: usize,
1055 pub columnar_input_rows: usize,
1057 pub columnar_selected_rows: usize,
1059 pub morsel_count: usize,
1061 pub morsel_max_admitted_workers: usize,
1063 pub morsel_peak_active_workers: usize,
1065 pub morsel_peak_buffered_outputs: usize,
1068 pub morsel_peak_buffered_output_bytes: usize,
1070 pub morsel_peak_reorder_entries: usize,
1072 pub output_rows: usize,
1073 pub output_payload_bytes: usize,
1074 pub start_resident_bytes: Option<u64>,
1076 pub start_peak_resident_bytes: Option<u64>,
1078 pub steady_resident_bytes: Option<u64>,
1080 pub peak_resident_bytes: Option<u64>,
1082 pub steady_resident_growth_bytes: Option<u64>,
1084 pub lifetime_peak_resident_growth_bytes: Option<u64>,
1086 pub total_page_faults: Option<u64>,
1088 pub minor_page_faults: Option<u64>,
1090 pub major_page_faults: Option<u64>,
1091}
1092
1093#[derive(Debug, Clone, PartialEq, Eq)]
1095pub struct OperatorCardinalityProfile {
1096 pub operator_id: PhysicalOperatorId,
1097 pub operator: PhysicalPlanKind,
1098 pub actual_rows: Option<usize>,
1101}
1102
1103#[derive(Debug, Clone, PartialEq, Eq)]
1104pub struct ReadExecutionProfile<TScanPruningReport> {
1105 pub max_rows: Option<usize>,
1106 pub detection_row_cap: Option<usize>,
1107 pub row_limit_enforced_before_output: bool,
1108 pub operator_row_cap_enabled: bool,
1109 pub operator_cardinality_profiles: Vec<OperatorCardinalityProfile>,
1110 pub blocking_operator_kinds: Vec<String>,
1111 pub scan_pruning_reports: Vec<TScanPruningReport>,
1112 pub vector_execution_reports: Vec<crate::VectorExecutionReport>,
1113 pub graph_expansion_reports: Vec<crate::GraphExpansionExecutionReport>,
1114 pub text_seed_execution_reports: Vec<crate::TextSeedExecutionReport>,
1115 pub grouped_evidence_reports: Vec<crate::GroupedEvidenceExecutionReport>,
1116 pub blocking_operator_memory_reports: Vec<BlockingOperatorMemoryReport>,
1117 pub pipeline_memory_report: PipelineMemoryReport,
1118}
1119
1120impl<TScanPruningReport> ReadExecutionProfile<TScanPruningReport> {
1121 pub fn blocking_operator_count(&self) -> usize {
1122 self.blocking_operator_kinds.len()
1123 }
1124}
1125
1126#[derive(Debug, Clone, PartialEq, Eq)]
1127pub struct ProfiledQueryRows<TScanPruningReport> {
1128 pub rows: QueryRows,
1129 pub profile: ReadExecutionProfile<TScanPruningReport>,
1130}
1131
1132#[derive(Debug, Clone, PartialEq, Eq)]
1133pub struct ProfiledQueryStream<TScanPruningReport> {
1134 pub fully_streamed: bool,
1138 pub profile: ReadExecutionProfile<TScanPruningReport>,
1139}
1140
1141#[cfg(test)]
1142mod tests {
1143 use super::*;
1144 use crate::{
1145 BindingSchema, ColumnVector, ColumnarBatch, SlotDescriptor, SlotId, SlotType, Validity,
1146 };
1147 use hawdb_core::LogicalType;
1148 use std::sync::Arc;
1149
1150 #[test]
1151 fn segmented_results_preserve_access_ownership_and_flat_equality() {
1152 let expected = (0..1000)
1153 .map(|id| {
1154 Row::from([
1155 ("id".into(), Value::Int(id)),
1156 ("payload".into(), Value::String(format!("value-{id}\0"))),
1157 ])
1158 })
1159 .collect::<Vec<_>>();
1160 let mut builder = QueryRowsBuilder::new();
1161 for row in &expected {
1162 builder.push_named_row(row.clone()).unwrap();
1163 }
1164 let rows = builder.finish();
1165 assert_eq!(rows, QueryRows::from(expected.clone()));
1166 assert_eq!(
1167 rows.payload_bytes(),
1168 QueryRows::from(expected.clone()).payload_bytes()
1169 );
1170 for ordinal in [0, 255, 256, 511, 512, 999] {
1171 assert_eq!(rows.row(ordinal).unwrap(), expected[ordinal]);
1172 assert_eq!(rows[ordinal], expected[ordinal]);
1173 }
1174 let handle = rows[256].clone();
1175 let mut retained = rows.clone();
1176 retained.retain(|row| row["id"] == Value::Int(256));
1177 assert_eq!(retained.len(), 1);
1178 assert_eq!(retained.first().unwrap(), expected[256]);
1179 assert_eq!(rows.clone().into_rows(), expected);
1180 drop(rows);
1181 assert_eq!(handle["id"], Value::Int(256));
1182 }
1183
1184 #[test]
1185 fn named_rows_follow_the_declared_schema_even_when_empty() {
1186 let schema = QuerySchema::try_new(["name".to_string(), "id".to_string()]).unwrap();
1187 let rows = QueryRows::try_from_named_rows(
1188 schema.clone(),
1189 vec![Row::from([
1190 ("id".into(), Value::Int(1)),
1191 ("name".into(), Value::String("one".into())),
1192 ])],
1193 )
1194 .unwrap();
1195 assert_eq!(rows.schema(), &schema);
1196 assert_eq!(
1197 rows.value_rows().collect::<Vec<_>>(),
1198 vec![&[Value::String("one".into()), Value::Int(1)][..]]
1199 );
1200
1201 let empty = QueryRows::try_from_named_rows(schema.clone(), Vec::new()).unwrap();
1202 assert_eq!(empty.schema(), &schema);
1203 assert!(empty.is_empty());
1204
1205 for (row, message) in [
1206 (
1207 Row::from([("id".into(), Value::Int(1))]),
1208 "query result row 0 has width 1, expected 2",
1209 ),
1210 (
1211 Row::from([("id".into(), Value::Int(1)), ("title".into(), Value::Null)]),
1212 "query result row 0 has no column name",
1213 ),
1214 ] {
1215 assert_eq!(
1216 QueryRows::try_from_named_rows(schema.clone(), vec![row]),
1217 Err(HawDBError::Execution(message.to_string()))
1218 );
1219 }
1220 }
1221
1222 #[test]
1223 fn segmented_results_bound_unknown_cardinality_capacity() {
1224 let mut builder = QueryRowsBuilder::new();
1225 for id in 0..100_000 {
1226 builder
1227 .push_named_row(Row::from([
1228 ("id".into(), Value::Int(id)),
1229 ("score".into(), Value::Float(id as f64)),
1230 ("title".into(), Value::String("x".repeat(24))),
1231 ]))
1232 .unwrap();
1233 }
1234 let rows = builder.finish();
1235 let QueryStoredValues::Segmented(segments) = &rows.storage.values else {
1236 panic!("large results require segmented storage");
1237 };
1238 assert_eq!(
1239 segments.len(),
1240 100_000_usize.div_ceil(OWNED_RESULT_SEGMENT_ROWS)
1241 );
1242 let capacity = segments.iter().map(Vec::capacity).sum::<usize>();
1243 assert!(capacity >= 300_000);
1244 assert!(capacity <= 300_000 + 3 * OWNED_RESULT_SEGMENT_ROWS);
1245 assert!(segments
1246 .iter()
1247 .all(|segment| segment.capacity() <= 3 * OWNED_RESULT_SEGMENT_ROWS));
1248 assert_eq!(rows.get(99_999, "id"), Some(ValueRef::Int(99_999)));
1249 }
1250
1251 #[test]
1252 fn segmented_results_handle_empty_rows_and_boundary_rollback() {
1253 let mut empty = QueryRowsBuilder::new();
1254 for _ in 0..513 {
1255 empty.push_named_row(Row::new()).unwrap();
1256 }
1257 let empty = empty.finish();
1258 assert_eq!(empty.len(), 513);
1259 assert!(empty.row(512).unwrap().is_empty());
1260 let mut builder = QueryRowsBuilder::new();
1261 for id in 0..256 {
1262 builder
1263 .push_named_row(Row::from([("id".into(), Value::Int(id))]))
1264 .unwrap();
1265 }
1266 assert!(builder
1267 .push_named_row(Row::from([("wrong".into(), Value::Int(256))]))
1268 .is_err());
1269 assert!(builder
1270 .try_push_values([Err(HawDBError::Execution("failed".into()))])
1271 .is_err());
1272 assert!(builder.push_values([]).is_err());
1273 builder.push_values([Value::Int(256)]).unwrap();
1274 let rows = builder.finish();
1275 assert_eq!(rows.len(), 257);
1276 assert_eq!(rows.get(256, "id"), Some(ValueRef::Int(256)));
1277 }
1278
1279 #[test]
1280 fn counts_blocking_operator_kinds() {
1281 let profile = ReadExecutionProfile::<()> {
1282 max_rows: Some(10),
1283 detection_row_cap: Some(11),
1284 row_limit_enforced_before_output: true,
1285 operator_row_cap_enabled: true,
1286 operator_cardinality_profiles: Vec::new(),
1287 blocking_operator_kinds: vec!["sort".to_string(), "aggregate".to_string()],
1288 scan_pruning_reports: Vec::new(),
1289 vector_execution_reports: Vec::new(),
1290 graph_expansion_reports: Vec::new(),
1291 text_seed_execution_reports: Vec::new(),
1292 grouped_evidence_reports: Vec::new(),
1293 blocking_operator_memory_reports: Vec::new(),
1294 pipeline_memory_report: PipelineMemoryReport::default(),
1295 };
1296 assert_eq!(profile.blocking_operator_count(), 2);
1297 }
1298
1299 #[test]
1300 fn borrowed_map_row_materializes_only_on_request() {
1301 let row = Row::from([
1302 ("id".to_string(), Value::Int(7)),
1303 ("content".to_string(), Value::String("payload".into())),
1304 ]);
1305 let row_ref = RowRef::from(&row);
1306
1307 assert_eq!(row_ref.get("id"), Some(ValueRef::Int(7)));
1308 assert_eq!(row_ref.get("content").unwrap().as_str(), Some("payload"));
1309 assert_eq!(row_ref.to_owned_row(), row);
1310 }
1311
1312 #[test]
1313 fn schema_bearing_rows_keep_names_once_in_a_flat_value_buffer() {
1314 let schema = QuerySchema::try_new(["payload".to_string(), "id".to_string()]).unwrap();
1315 let mut builder = QueryRowsBuilder::with_schema(schema, 2);
1316 builder
1317 .push_values([Value::String("one".to_string()), Value::Int(1)])
1318 .unwrap();
1319 builder
1320 .push_values([Value::String("two".to_string()), Value::Int(2)])
1321 .unwrap();
1322 let rows = builder.finish();
1323
1324 assert_eq!(rows.schema().columns(), ["payload", "id"]);
1325 assert_eq!(rows.get(1, "payload").unwrap().as_str(), Some("two"));
1326 assert_eq!(rows.value(0, 1), Some(ValueRef::Int(1)));
1327 assert_eq!(rows.payload_bytes(), "payload".len() + "id".len() + 6 + 16);
1328 assert_eq!(rows.len(), 2);
1329 assert!(!rows.is_empty());
1330 assert_eq!(rows.storage.values.len(), 4);
1331 assert_eq!(rows[0]["payload"], Value::String("one".to_string()));
1332 assert_eq!(rows.iter().count(), 2);
1333 assert_eq!(rows.storage.values.len(), 4);
1334 }
1335
1336 #[test]
1337 fn schema_bearing_rows_reject_duplicate_columns_and_width_mismatch() {
1338 assert!(QuerySchema::try_new(["id".to_string(), "id".to_string()]).is_err());
1339 let schema = QuerySchema::try_new(["id".to_string()]).unwrap();
1340 let mut invalid = QueryRowsBuilder::with_schema(schema, 1);
1341 assert!(invalid.push_values([]).is_err());
1342
1343 let mut left = QueryRowsBuilder::with_schema(
1344 QuerySchema::try_new(["left".to_string(), "right".to_string()]).unwrap(),
1345 1,
1346 );
1347 left.push_values([Value::Int(1), Value::Int(2)]).unwrap();
1348 let left = left.finish();
1349 let mut right = QueryRowsBuilder::with_schema(
1350 QuerySchema::try_new(["right".to_string(), "left".to_string()]).unwrap(),
1351 1,
1352 );
1353 right.push_values([Value::Int(1), Value::Int(2)]).unwrap();
1354 let right = right.finish();
1355 assert_ne!(left, right);
1356 }
1357
1358 #[test]
1359 fn fallible_flat_row_append_rolls_back_partial_values() {
1360 let schema = QuerySchema::try_new(["left".to_string(), "right".to_string()]).unwrap();
1361 let mut builder = QueryRowsBuilder::with_schema(schema, 1);
1362 assert!(builder
1363 .try_push_values([
1364 Ok(Value::Int(1)),
1365 Err(HawDBError::Execution("projection failed".to_string())),
1366 ])
1367 .is_err());
1368 builder.push_values([Value::Int(2), Value::Int(3)]).unwrap();
1369 let rows = builder.finish();
1370
1371 assert_eq!(rows.len(), 1);
1372 assert_eq!(rows.value(0, 0), Some(ValueRef::Int(2)));
1373 assert_eq!(rows.value(0, 1), Some(ValueRef::Int(3)));
1374 assert_eq!(rows.storage.values.len(), 2);
1375 }
1376
1377 #[test]
1378 fn named_row_builder_binds_schema_once_and_moves_values_by_ordinal() {
1379 let mut builder = QueryRowsBuilder::with_row_capacity(2);
1380 builder
1381 .push_named_row(Row::from([
1382 ("id".to_string(), Value::Int(1)),
1383 ("payload".to_string(), Value::String("one".to_string())),
1384 ]))
1385 .unwrap();
1386 builder
1387 .push_named_row(Row::from([
1388 ("id".to_string(), Value::Int(2)),
1389 ("payload".to_string(), Value::String("two".to_string())),
1390 ]))
1391 .unwrap();
1392 let rows = builder.finish();
1393
1394 assert_eq!(rows.schema().columns(), ["id", "payload"]);
1395 assert_eq!(rows.value(1, 0), Some(ValueRef::Int(2)));
1396 assert_eq!(rows.get(0, "payload").unwrap().as_str(), Some("one"));
1397 assert_eq!(rows.storage.values.len(), 4);
1398 }
1399
1400 #[test]
1401 fn named_row_builder_rejects_schema_drift_without_partial_append() {
1402 let mut builder = QueryRowsBuilder::new();
1403 builder
1404 .push_named_row(Row::from([("id".to_string(), Value::Int(1))]))
1405 .unwrap();
1406 assert!(builder
1407 .push_named_row(Row::from([("name".to_string(), Value::Int(2))]))
1408 .is_err());
1409 let rows = builder.finish();
1410
1411 assert_eq!(rows.len(), 1);
1412 assert_eq!(rows.get(0, "id"), Some(ValueRef::Int(1)));
1413 }
1414
1415 #[test]
1416 fn borrowed_columnar_row_pulls_values_without_a_row_map() {
1417 let schema = Arc::new(
1418 BindingSchema::try_new(vec![
1419 SlotDescriptor {
1420 id: SlotId(0),
1421 name: "id".into(),
1422 slot_type: SlotType::NodeId,
1423 },
1424 SlotDescriptor {
1425 id: SlotId(1),
1426 name: "content".into(),
1427 slot_type: SlotType::logical(LogicalType::Text),
1428 },
1429 ])
1430 .unwrap(),
1431 );
1432 let batch = ColumnarBatch::try_new(
1433 schema,
1434 vec![
1435 Arc::new(ColumnVector::node_ids(vec![7])),
1436 Arc::new(ColumnVector::Utf8 {
1437 values: vec!["payload".to_string()].into(),
1438 validity: Validity::all(1),
1439 }),
1440 ],
1441 )
1442 .unwrap();
1443 let row_ref = RowRef::from(batch.rows().next().unwrap());
1444
1445 assert_eq!(row_ref.get("id"), Some(ValueRef::Int(7)));
1446 assert_eq!(row_ref.get("content").unwrap().as_str(), Some("payload"));
1447 assert_eq!(
1448 row_ref.to_owned_row(),
1449 Row::from([
1450 ("content".into(), Value::String("payload".into())),
1451 ("id".into(), Value::Int(7)),
1452 ])
1453 );
1454 }
1455}