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