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