Skip to main content

hawdb_executor/
profile.rs

1// Copyright 2026 Nowledge
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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/// Column names shared by every row of one query result.
26#[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/// One owned handle into a query's immutable row storage.
97#[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/// A borrowed row view over a query's immutable contiguous row buffer.
198#[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
273/// Schema-bearing result rows backed by immutable contiguous row buffers.
274pub 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    /// Builds rows whose columns follow `schema` rather than map key order.
285    ///
286    /// The schema is kept when `rows` is empty. Each row must name exactly the
287    /// schema's columns: a missing column fails instead of reading as NULL.
288    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    /// Returns stable owned row handles for APIs that require slice pattern
408    /// matching or indexing. No map or value is copied.
409    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
472/// Builds immutable query results without retaining one map or vector per row.
473///
474/// The first named row binds column names to stable ordinals. Later rows are
475/// consumed in the same deterministic `BTreeMap` order and their values move
476/// directly into bounded row segments when cardinality is unknown. Explicit
477/// capacity constructors retain the flat-buffer path.
478pub 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    /// Appends one fallible row into immutable-result storage.
518    ///
519    /// A conversion error or width mismatch rolls the partial row back.
520    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/// A row view whose values remain valid only for the current consumer call.
847///
848/// The map representation covers the scalar fallback executor. The columnar
849/// representation lets a downstream consumer pull selected values directly
850/// from an immutable batch without constructing an intermediate row map.
851#[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/// A schema-bound view of native projection values. Payloads belong to the
914/// source batch or plan and are valid only during the consumer invocation.
915#[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    /// Equal-hash candidate pairs examined before domain-specific filtering.
1019    pub candidate_rows: usize,
1020    /// Records decoded from spill runs, including repartition and hot-key replay.
1021    pub replay_rows: usize,
1022    /// Successful spill-run partition splits.
1023    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    /// Query-owned runtime ledger budget across pipeline and blocking state.
1034    pub query_memory_budget_bytes: usize,
1035    /// Highest aggregate tracked resident bytes charged to the query ledger.
1036    pub query_memory_peak_bytes: usize,
1037    /// Tracked bytes still owned at the execution completion boundary.
1038    pub query_memory_completion_bytes: usize,
1039    /// Number of operator or transfer accounts created by the query.
1040    pub query_memory_account_count: usize,
1041    /// Sum of rows emitted at every physical operator boundary.
1042    pub intermediate_rows: usize,
1043    /// Sum of retained payload estimates emitted at every physical operator boundary.
1044    pub intermediate_payload_bytes: usize,
1045    pub peak_batch_rows: usize,
1046    pub peak_batch_payload_bytes: usize,
1047    /// Rows projected from source property references before owned output admission.
1048    /// This counter does not claim source-to-host zero-copy delivery.
1049    pub borrowed_node_projection_rows: usize,
1050    /// Native projected row views delivered without constructing a row map.
1051    /// Owned/validated consumers may still copy admitted values for retention.
1052    pub native_projection_rows: usize,
1053    /// Number of typed columnar batches evaluated by eligible pipeline fragments.
1054    pub columnar_batches: usize,
1055    /// Rows loaded into typed columns before selection.
1056    pub columnar_input_rows: usize,
1057    /// Rows retained by columnar selections.
1058    pub columnar_selected_rows: usize,
1059    /// Morsels consumed by columnar fragments.
1060    pub morsel_count: usize,
1061    /// Highest resource-admitted worker count across morsel pipelines.
1062    pub morsel_max_admitted_workers: usize,
1063    /// Highest worker count actually used by a morsel scheduler.
1064    pub morsel_peak_active_workers: usize,
1065    /// Highest number of completed morsel outputs awaiting or crossing the
1066    /// coordinator's ordered consumer boundary.
1067    pub morsel_peak_buffered_outputs: usize,
1068    /// Highest estimated resident bytes held by those completed outputs.
1069    pub morsel_peak_buffered_output_bytes: usize,
1070    /// Highest number of out-of-order outputs held by the ordinal merger.
1071    pub morsel_peak_reorder_entries: usize,
1072    pub output_rows: usize,
1073    pub output_payload_bytes: usize,
1074    /// Resident memory before execution, when process sampling is supported.
1075    pub start_resident_bytes: Option<u64>,
1076    /// Process high-water resident memory before execution.
1077    pub start_peak_resident_bytes: Option<u64>,
1078    /// Resident memory after the output rows have been materialized.
1079    pub steady_resident_bytes: Option<u64>,
1080    /// Process high-water resident memory at completion.
1081    pub peak_resident_bytes: Option<u64>,
1082    /// Positive resident-memory delta retained at completion.
1083    pub steady_resident_growth_bytes: Option<u64>,
1084    /// Positive process high-water delta observed during execution.
1085    pub lifetime_peak_resident_growth_bytes: Option<u64>,
1086    /// Aggregate page-fault delta when the platform exposes it.
1087    pub total_page_faults: Option<u64>,
1088    /// Split page-fault deltas only on platforms that expose this distinction.
1089    pub minor_page_faults: Option<u64>,
1090    pub major_page_faults: Option<u64>,
1091}
1092
1093/// The observed output cardinality for one physical operator.
1094#[derive(Debug, Clone, PartialEq, Eq)]
1095pub struct OperatorCardinalityProfile {
1096    pub operator_id: PhysicalOperatorId,
1097    pub operator: PhysicalPlanKind,
1098    /// `None` means the operator was not invoked, while `Some(0)` means it ran
1099    /// and produced no output rows.
1100    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    /// True when the physical plan emitted batches directly to the consumer.
1135    /// False means an unsupported operator still materialized bindings before
1136    /// the bounded consumer boundary.
1137    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}