Skip to main content

alopex_sql/executor/query/
mod.rs

1use alopex_core::kv::KVStore;
2
3use crate::ast::LITERAL_TABLE;
4use crate::catalog::{Catalog, StorageType};
5use crate::executor::evaluator::EvalContext;
6use crate::executor::memory::MemoryPolicy;
7use crate::executor::{ExecutionResult, ExecutorError, QueryResult, QueryRowIterator, Result};
8use crate::planner::logical_plan::LogicalPlan;
9use crate::planner::typed_expr::{Projection, SortExpr};
10use crate::storage::{SqlTxn, SqlValue};
11
12use super::{ColumnInfo, Row};
13
14pub mod aggregate;
15pub mod columnar_scan;
16pub mod iterator;
17pub mod join;
18mod knn;
19mod project;
20mod scan;
21pub mod subquery;
22
23pub use columnar_scan::{ColumnarScanIterator, create_columnar_scan_iterator};
24pub use iterator::{FilterIterator, LimitIterator, RowIterator, ScanIterator, SortIterator};
25pub use scan::create_scan_iterator;
26
27/// Execute a SELECT logical plan and return a query result.
28///
29/// This function uses an iterator-based execution model that processes rows
30/// through a pipeline of operators. This approach:
31/// - Enables early termination for LIMIT queries
32/// - Provides streaming execution after the initial scan
33/// - Allows composable query operators
34///
35/// Note: The Scan stage reads all matching rows into memory, but subsequent
36/// operators (Filter, Sort, Limit) process rows through an iterator pipeline.
37/// Sort operations additionally require materializing all input rows.
38pub fn execute_query<'txn, S: KVStore + 'txn, C: Catalog + ?Sized, T: SqlTxn<'txn, S>>(
39    txn: &mut T,
40    catalog: &C,
41    plan: LogicalPlan,
42) -> Result<ExecutionResult> {
43    execute_query_with_policy(txn, catalog, plan, None)
44}
45
46pub fn execute_query_with_policy<
47    'txn,
48    S: KVStore + 'txn,
49    C: Catalog + ?Sized,
50    T: SqlTxn<'txn, S>,
51>(
52    txn: &mut T,
53    catalog: &C,
54    plan: LogicalPlan,
55    memory: Option<&MemoryPolicy>,
56) -> Result<ExecutionResult> {
57    if let Some((pattern, projection, filter)) = knn::extract_knn_context(&plan) {
58        return knn::execute_knn_query(txn, catalog, &pattern, &projection, filter.as_ref());
59    }
60
61    let result = execute_query_result_with_outer_and_policy(txn, catalog, plan, None, memory)?;
62    Ok(ExecutionResult::Query(result))
63}
64
65pub(crate) fn execute_query_result_with_outer<
66    'txn,
67    S: KVStore + 'txn,
68    C: Catalog + ?Sized,
69    T: SqlTxn<'txn, S>,
70>(
71    txn: &mut T,
72    catalog: &C,
73    plan: LogicalPlan,
74    outer: Option<&Row>,
75) -> Result<QueryResult> {
76    execute_query_result_with_outer_and_policy(txn, catalog, plan, outer, None)
77}
78
79fn execute_query_result_with_outer_and_policy<
80    'txn,
81    S: KVStore + 'txn,
82    C: Catalog + ?Sized,
83    T: SqlTxn<'txn, S>,
84>(
85    txn: &mut T,
86    catalog: &C,
87    plan: LogicalPlan,
88    outer: Option<&Row>,
89    memory: Option<&MemoryPolicy>,
90) -> Result<QueryResult> {
91    let (mut iter, projection, schema) =
92        build_iterator_pipeline_with_outer(txn, catalog, plan, memory, outer)?;
93    let mut rows = Vec::new();
94    while let Some(result) = iter.next_row() {
95        rows.push(result?);
96    }
97    execute_project_with_subqueries(txn, catalog, rows, &projection, &schema, outer)
98}
99
100/// Execute a SELECT logical plan and return a streaming query result.
101///
102/// This function returns a `QueryRowIterator` that yields rows one at a time,
103/// enabling true streaming output without materializing all rows upfront.
104///
105/// # FR-7 Streaming Output
106///
107/// This function implements the FR-7 requirement for streaming output.
108/// Rows are yielded through an iterator interface, and projection is applied
109/// on-the-fly as each row is consumed.
110///
111/// # Note
112///
113/// KNN queries currently fall back to the non-streaming path as they require
114/// specialized handling.
115pub fn execute_query_streaming<'txn, S: KVStore + 'txn, C: Catalog + ?Sized, T: SqlTxn<'txn, S>>(
116    txn: &mut T,
117    catalog: &C,
118    plan: LogicalPlan,
119) -> Result<QueryRowIterator<'static>> {
120    execute_query_streaming_with_policy(txn, catalog, plan, None)
121}
122
123pub fn execute_query_streaming_with_policy<
124    'txn,
125    S: KVStore + 'txn,
126    C: Catalog + ?Sized,
127    T: SqlTxn<'txn, S>,
128>(
129    txn: &mut T,
130    catalog: &C,
131    plan: LogicalPlan,
132    memory: Option<&MemoryPolicy>,
133) -> Result<QueryRowIterator<'static>> {
134    // KNN queries not yet supported for streaming - fall back would need different handling
135    if knn::extract_knn_context(&plan).is_some() {
136        // For KNN, we materialize and wrap in VecIterator
137        let result = execute_query_with_policy(txn, catalog, plan, memory)?;
138        if let ExecutionResult::Query(qr) = result {
139            let (iter, projection, schema) = materialize_query_result(qr);
140            return Ok(QueryRowIterator::new(iter, projection, schema));
141        }
142        return Err(ExecutorError::InvalidOperation {
143            operation: "execute_query_streaming".into(),
144            reason: "KNN query did not return Query result".into(),
145        });
146    }
147
148    // Subqueries need transaction access during evaluation, which streaming
149    // iterators borrow exclusively. Execute through the materializing path
150    // (the same one used by `execute_query`) so results are identical to the
151    // non-streaming API instead of failing or silently dropping rows.
152    if subquery::plan_contains_subquery(&plan) {
153        let result = execute_query_result_with_outer_and_policy(txn, catalog, plan, None, memory)?;
154        let (iter, projection, schema) = materialize_query_result(result);
155        return Ok(QueryRowIterator::new(iter, projection, schema));
156    }
157
158    let (iter, projection, schema) = build_iterator_pipeline(txn, catalog, plan, memory)?;
159
160    Ok(QueryRowIterator::new(iter, projection, schema))
161}
162
163/// Convert a materialized `QueryResult` into pipeline outputs.
164///
165/// The resulting rows are already fully projected, so the returned projection
166/// is `Projection::All` over the output column names.
167fn materialize_query_result(
168    result: QueryResult,
169) -> (
170    Box<dyn RowIterator>,
171    Projection,
172    Vec<crate::catalog::ColumnMetadata>,
173) {
174    let column_names: Vec<String> = result.columns.iter().map(|c| c.name.clone()).collect();
175    let schema: Vec<crate::catalog::ColumnMetadata> = result
176        .columns
177        .iter()
178        .map(|c| crate::catalog::ColumnMetadata::new(&c.name, c.data_type.clone()))
179        .collect();
180    let rows: Vec<Row> = result
181        .rows
182        .into_iter()
183        .enumerate()
184        .map(|(i, values)| Row::new(i as u64, values))
185        .collect();
186    let iter = iterator::VecIterator::new(rows, schema.clone());
187    (Box::new(iter), Projection::All(column_names), schema)
188}
189
190/// Build an iterator pipeline from a logical plan.
191///
192/// This recursively constructs a tree of iterators that mirrors the logical plan
193/// structure. The scan phase reads rows into memory, then subsequent operators
194/// process them through an iterator pipeline enabling streaming execution and
195/// early termination.
196fn build_iterator_pipeline<'txn, S: KVStore + 'txn, C: Catalog + ?Sized, T: SqlTxn<'txn, S>>(
197    txn: &mut T,
198    catalog: &C,
199    plan: LogicalPlan,
200    memory: Option<&MemoryPolicy>,
201) -> Result<(
202    Box<dyn RowIterator>,
203    Projection,
204    Vec<crate::catalog::ColumnMetadata>,
205)> {
206    build_iterator_pipeline_with_outer(txn, catalog, plan, memory, None)
207}
208
209fn build_iterator_pipeline_with_outer<
210    'txn,
211    S: KVStore + 'txn,
212    C: Catalog + ?Sized,
213    T: SqlTxn<'txn, S>,
214>(
215    txn: &mut T,
216    catalog: &C,
217    plan: LogicalPlan,
218    memory: Option<&MemoryPolicy>,
219    outer: Option<&Row>,
220) -> Result<(
221    Box<dyn RowIterator>,
222    Projection,
223    Vec<crate::catalog::ColumnMetadata>,
224)> {
225    match plan {
226        LogicalPlan::Scan { table, projection } => {
227            if table == LITERAL_TABLE {
228                let schema = Vec::new();
229                let rows = vec![Row::new(0, Vec::new())];
230                let iter = iterator::VecIterator::new(rows, schema.clone());
231                return Ok((Box::new(iter), projection, schema));
232            }
233            let table_meta = catalog
234                .get_table(&table)
235                .cloned()
236                .ok_or_else(|| ExecutorError::TableNotFound(table.clone()))?;
237
238            if table_meta.storage_options.storage_type == StorageType::Columnar {
239                let columnar_scan = columnar_scan::build_columnar_scan(&table_meta, &projection);
240                let rows = columnar_scan::execute_columnar_scan(txn, &table_meta, &columnar_scan)?;
241                let schema = table_meta.columns.clone();
242                let iter = iterator::VecIterator::new(rows, schema.clone());
243                return Ok((Box::new(iter), projection, schema));
244            }
245
246            // TODO: 現状は Scan で一度全件をメモリに載せてから iterator に渡しています。
247            // 将来ストリーミングを徹底する場合は、ScanIterator を活用できるよう
248            // トランザクションのライフタイム設計を見直すとよいです。
249            let rows = scan::execute_scan(txn, &table_meta)?;
250            let schema = table_meta.columns.clone();
251
252            // Wrap in VecIterator for consistent iterator-based processing
253            let iter = iterator::VecIterator::new(rows, schema.clone());
254            Ok((Box::new(iter), projection, schema))
255        }
256        LogicalPlan::Filter { input, predicate } => {
257            if let LogicalPlan::Scan { table, projection } = input.as_ref()
258                && let Some(table_meta) = catalog.get_table(table)
259                && table_meta.storage_options.storage_type == StorageType::Columnar
260            {
261                let columnar_scan = columnar_scan::build_columnar_scan_for_filter(
262                    table_meta,
263                    projection.clone(),
264                    &predicate,
265                );
266                let rows = columnar_scan::execute_columnar_scan(txn, table_meta, &columnar_scan)?;
267                let schema = table_meta.columns.clone();
268                let iter = iterator::VecIterator::new(rows, schema.clone());
269                return Ok((Box::new(iter), projection.clone(), schema));
270            }
271            let (mut input_iter, projection, schema) =
272                build_iterator_pipeline_with_outer(txn, catalog, *input, memory, outer)?;
273            if outer.is_some() || subquery::contains_subquery(&predicate) {
274                let mut rows = Vec::new();
275                while let Some(result) = input_iter.next_row() {
276                    let row = result?;
277                    let eval_row = combine_outer_for_eval(&row, outer);
278                    if let SqlValue::Boolean(true) = subquery::evaluate_expr_with_subqueries(
279                        txn, catalog, &predicate, &eval_row,
280                    )? {
281                        rows.push(row);
282                    }
283                }
284                let iter = iterator::VecIterator::new(rows, schema.clone());
285                return Ok((Box::new(iter), projection, schema));
286            }
287            let filter_iter = FilterIterator::new(input_iter, predicate);
288            Ok((Box::new(filter_iter), projection, schema))
289        }
290        LogicalPlan::Project { input, projection } => {
291            let (mut input_iter, _input_projection, schema) =
292                build_iterator_pipeline_with_outer(txn, catalog, *input, memory, outer)?;
293            let mut rows = Vec::new();
294            while let Some(result) = input_iter.next_row() {
295                rows.push(result?);
296            }
297            let projected =
298                execute_project_with_subqueries(txn, catalog, rows, &projection, &schema, outer)?;
299            let output_schema = projected
300                .columns
301                .iter()
302                .map(|col| crate::catalog::ColumnMetadata::new(&col.name, col.data_type.clone()))
303                .collect::<Vec<_>>();
304            let rows = projected
305                .rows
306                .into_iter()
307                .enumerate()
308                .map(|(idx, values)| Row::new(idx as u64, values))
309                .collect::<Vec<_>>();
310            let output_projection =
311                Projection::All(output_schema.iter().map(|col| col.name.clone()).collect());
312            let iter = iterator::VecIterator::new(rows, output_schema.clone());
313            Ok((Box::new(iter), output_projection, output_schema))
314        }
315        LogicalPlan::Join {
316            left,
317            right,
318            join_type,
319            condition,
320            using: _,
321        } => {
322            let (mut left_iter, _left_projection, left_schema) =
323                build_iterator_pipeline_with_outer(txn, catalog, *left, memory, outer)?;
324            let (mut right_iter, _right_projection, right_schema) =
325                build_iterator_pipeline_with_outer(txn, catalog, *right, memory, outer)?;
326            let mut left_rows = Vec::new();
327            while let Some(result) = left_iter.next_row() {
328                left_rows.push(result?);
329            }
330            let mut right_rows = Vec::new();
331            while let Some(result) = right_iter.next_row() {
332                right_rows.push(result?);
333            }
334            let left_width = left_schema.len();
335            let right_width = right_schema.len();
336            let rows = join::execute_join_with_widths(
337                left_rows,
338                right_rows,
339                join_type,
340                condition.as_ref(),
341                left_width,
342                right_width,
343            )?;
344            let mut schema = left_schema;
345            schema.extend(right_schema);
346            let projection = Projection::All(schema.iter().map(|col| col.name.clone()).collect());
347            let iter = iterator::VecIterator::new(rows, schema.clone());
348            Ok((Box::new(iter), projection, schema))
349        }
350        LogicalPlan::Aggregate {
351            input,
352            group_keys,
353            aggregates,
354            having,
355            projection,
356        } => {
357            let (input_iter, _projection, _schema) =
358                build_iterator_pipeline_with_outer(txn, catalog, *input, memory, outer)?;
359            let schema = aggregate::build_aggregate_schema(&group_keys, &aggregates);
360            if let Some(policy) = memory
361                && policy.spill_directory().is_some()
362            {
363                if group_keys.is_empty() {
364                    let iter = aggregate::StreamingAggregateIterator::new(
365                        input_iter,
366                        group_keys,
367                        aggregates,
368                        having,
369                        schema.clone(),
370                    );
371                    return Ok((Box::new(iter), projection, schema));
372                }
373                let order_by = group_keys
374                    .iter()
375                    .cloned()
376                    .map(|expr| SortExpr {
377                        expr,
378                        asc: true,
379                        nulls_first: false,
380                    })
381                    .collect::<Vec<_>>();
382                let sort_iter =
383                    SortIterator::new_with_policy(input_iter, &order_by, Some(policy.clone()))?;
384                let iter = aggregate::StreamingAggregateIterator::new(
385                    Box::new(sort_iter),
386                    group_keys,
387                    aggregates,
388                    having,
389                    schema.clone(),
390                );
391                return Ok((Box::new(iter), projection, schema));
392            }
393
394            let mut iter = aggregate::AggregateIterator::new(
395                input_iter,
396                group_keys,
397                aggregates,
398                having,
399                schema.clone(),
400            );
401            if let Some(policy) = memory {
402                iter = iter.with_memory_policy(Some(policy.clone()));
403            }
404            Ok((Box::new(iter), projection, schema))
405        }
406        LogicalPlan::Sort { input, order_by } => {
407            let (input_iter, projection, schema) =
408                build_iterator_pipeline_with_outer(txn, catalog, *input, memory, outer)?;
409            let sort_iter = if let Some(policy) = memory {
410                SortIterator::new_with_policy(input_iter, &order_by, Some(policy.clone()))?
411            } else {
412                SortIterator::new(input_iter, &order_by)?
413            };
414            Ok((Box::new(sort_iter), projection, schema))
415        }
416        LogicalPlan::Limit {
417            input,
418            limit,
419            offset,
420        } => {
421            let (input_iter, projection, schema) =
422                build_iterator_pipeline_with_outer(txn, catalog, *input, memory, outer)?;
423            let limit_iter = LimitIterator::new(input_iter, limit, offset);
424            Ok((Box::new(limit_iter), projection, schema))
425        }
426        other => Err(ExecutorError::UnsupportedOperation(format!(
427            "unsupported query plan: {other:?}"
428        ))),
429    }
430}
431
432/// Build a streaming iterator pipeline from a logical plan (FR-7).
433///
434/// This version uses `ScanIterator` for row-based tables to enable true
435/// streaming without materializing all rows upfront. The returned iterator
436/// has lifetime `'a` tied to the transaction borrow.
437///
438/// # Limitations
439///
440/// - Columnar storage still materializes rows (uses VecIterator)
441/// - Sort operations materialize all input rows
442/// - KNN queries are not supported (use `build_iterator_pipeline` instead)
443pub fn build_streaming_pipeline<
444    'a,
445    'txn: 'a,
446    S: KVStore + 'txn,
447    C: Catalog + ?Sized,
448    T: SqlTxn<'txn, S>,
449>(
450    txn: &'a mut T,
451    catalog: &C,
452    plan: LogicalPlan,
453) -> Result<(
454    Box<dyn RowIterator + 'a>,
455    Projection,
456    Vec<crate::catalog::ColumnMetadata>,
457)> {
458    build_streaming_pipeline_with_policy(txn, catalog, plan, None)
459}
460
461pub fn build_streaming_pipeline_with_policy<
462    'a,
463    'txn: 'a,
464    S: KVStore + 'txn,
465    C: Catalog + ?Sized,
466    T: SqlTxn<'txn, S>,
467>(
468    txn: &'a mut T,
469    catalog: &C,
470    plan: LogicalPlan,
471    memory: Option<&MemoryPolicy>,
472) -> Result<(
473    Box<dyn RowIterator + 'a>,
474    Projection,
475    Vec<crate::catalog::ColumnMetadata>,
476)> {
477    // Subqueries need transaction access during evaluation, which streaming
478    // iterators borrow exclusively. Execute through the materializing path
479    // (the same one used by `execute_query`) so results are identical to the
480    // non-streaming API instead of failing or silently dropping rows
481    // (GitHub issues #23 / #24).
482    if subquery::plan_contains_subquery(&plan) {
483        let result = execute_query_result_with_outer_and_policy(txn, catalog, plan, None, memory)?;
484        return Ok(materialize_query_result(result));
485    }
486
487    build_streaming_pipeline_inner(txn, catalog, plan, memory)
488}
489
490/// Inner implementation of streaming pipeline builder.
491fn build_streaming_pipeline_inner<
492    'a,
493    'txn: 'a,
494    S: KVStore + 'txn,
495    C: Catalog + ?Sized,
496    T: SqlTxn<'txn, S>,
497>(
498    txn: &'a mut T,
499    catalog: &C,
500    plan: LogicalPlan,
501    memory: Option<&MemoryPolicy>,
502) -> Result<(
503    Box<dyn RowIterator + 'a>,
504    Projection,
505    Vec<crate::catalog::ColumnMetadata>,
506)> {
507    match plan {
508        LogicalPlan::Scan { table, projection } => {
509            if table == LITERAL_TABLE {
510                let schema = Vec::new();
511                let rows = vec![Row::new(0, Vec::new())];
512                let iter = iterator::VecIterator::new(rows, schema.clone());
513                return Ok((Box::new(iter), projection, schema));
514            }
515            let table_meta = catalog
516                .get_table(&table)
517                .cloned()
518                .ok_or_else(|| ExecutorError::TableNotFound(table.clone()))?;
519
520            if table_meta.storage_options.storage_type == StorageType::Columnar {
521                // Columnar storage: use ColumnarScanIterator for FR-7 streaming
522                let columnar_scan = columnar_scan::build_columnar_scan(&table_meta, &projection);
523                let schema = table_meta.columns.clone();
524                let iter =
525                    columnar_scan::create_columnar_scan_iterator(txn, &table_meta, &columnar_scan)?;
526                return Ok((Box::new(iter), projection, schema));
527            }
528
529            // Row-based storage: use ScanIterator for true streaming (FR-7)
530            let schema = table_meta.columns.clone();
531            let scan_iter = scan::create_scan_iterator(txn, &table_meta)?;
532            Ok((Box::new(scan_iter), projection, schema))
533        }
534        LogicalPlan::Filter { input, predicate } => {
535            if let LogicalPlan::Scan { table, projection } = input.as_ref()
536                && let Some(table_meta) = catalog.get_table(table)
537                && table_meta.storage_options.storage_type == StorageType::Columnar
538            {
539                // Columnar storage with filter: use ColumnarScanIterator for FR-7 streaming
540                let columnar_scan = columnar_scan::build_columnar_scan_for_filter(
541                    table_meta,
542                    projection.clone(),
543                    &predicate,
544                );
545                let schema = table_meta.columns.clone();
546                let iter =
547                    columnar_scan::create_columnar_scan_iterator(txn, table_meta, &columnar_scan)?;
548                return Ok((Box::new(iter), projection.clone(), schema));
549            }
550            let (input_iter, projection, schema) =
551                build_streaming_pipeline_inner(txn, catalog, *input, memory)?;
552            let filter_iter = FilterIterator::new(input_iter, predicate);
553            Ok((Box::new(filter_iter), projection, schema))
554        }
555        LogicalPlan::Project { input, projection } => {
556            let (mut input_iter, _input_projection, schema) =
557                build_streaming_pipeline_inner(txn, catalog, *input, memory)?;
558            let mut rows = Vec::new();
559            while let Some(result) = input_iter.next_row() {
560                rows.push(result?);
561            }
562            let projected = project::execute_project(rows, &projection, &schema)?;
563            let output_schema = projected
564                .columns
565                .iter()
566                .map(|col| crate::catalog::ColumnMetadata::new(&col.name, col.data_type.clone()))
567                .collect::<Vec<_>>();
568            let rows = projected
569                .rows
570                .into_iter()
571                .enumerate()
572                .map(|(idx, values)| Row::new(idx as u64, values))
573                .collect::<Vec<_>>();
574            let output_projection =
575                Projection::All(output_schema.iter().map(|col| col.name.clone()).collect());
576            let iter = iterator::VecIterator::new(rows, output_schema.clone());
577            Ok((Box::new(iter), output_projection, output_schema))
578        }
579        LogicalPlan::Join {
580            left,
581            right,
582            join_type,
583            condition,
584            using: _,
585        } => {
586            let (mut left_iter, _left_projection, left_schema) =
587                build_streaming_pipeline_inner(txn, catalog, *left, memory)?;
588            let mut left_rows = Vec::new();
589            while let Some(result) = left_iter.next_row() {
590                left_rows.push(result?);
591            }
592            drop(left_iter);
593            let (mut right_iter, _right_projection, right_schema) =
594                build_streaming_pipeline_inner(txn, catalog, *right, memory)?;
595            let mut right_rows = Vec::new();
596            while let Some(result) = right_iter.next_row() {
597                right_rows.push(result?);
598            }
599            let rows = join::execute_join_with_widths(
600                left_rows,
601                right_rows,
602                join_type,
603                condition.as_ref(),
604                left_schema.len(),
605                right_schema.len(),
606            )?;
607            let mut schema = left_schema;
608            schema.extend(right_schema);
609            let projection = Projection::All(schema.iter().map(|col| col.name.clone()).collect());
610            let iter = iterator::VecIterator::new(rows, schema.clone());
611            Ok((Box::new(iter), projection, schema))
612        }
613        LogicalPlan::Aggregate {
614            input,
615            group_keys,
616            aggregates,
617            having,
618            projection,
619        } => {
620            let (input_iter, _projection, _schema) =
621                build_streaming_pipeline_inner(txn, catalog, *input, memory)?;
622            let schema = aggregate::build_aggregate_schema(&group_keys, &aggregates);
623            if let Some(policy) = memory
624                && policy.spill_directory().is_some()
625            {
626                if group_keys.is_empty() {
627                    let iter = aggregate::StreamingAggregateIterator::new(
628                        input_iter,
629                        group_keys,
630                        aggregates,
631                        having,
632                        schema.clone(),
633                    );
634                    return Ok((Box::new(iter), projection, schema));
635                }
636                let order_by = group_keys
637                    .iter()
638                    .cloned()
639                    .map(|expr| SortExpr {
640                        expr,
641                        asc: true,
642                        nulls_first: false,
643                    })
644                    .collect::<Vec<_>>();
645                let sort_iter =
646                    SortIterator::new_with_policy(input_iter, &order_by, Some(policy.clone()))?;
647                let iter = aggregate::StreamingAggregateIterator::new(
648                    Box::new(sort_iter),
649                    group_keys,
650                    aggregates,
651                    having,
652                    schema.clone(),
653                );
654                return Ok((Box::new(iter), projection, schema));
655            }
656
657            let mut iter = aggregate::AggregateIterator::new(
658                input_iter,
659                group_keys,
660                aggregates,
661                having,
662                schema.clone(),
663            );
664            if let Some(policy) = memory {
665                iter = iter.with_memory_policy(Some(policy.clone()));
666            }
667            Ok((Box::new(iter), projection, schema))
668        }
669        LogicalPlan::Sort { input, order_by } => {
670            let (input_iter, projection, schema) =
671                build_streaming_pipeline_inner(txn, catalog, *input, memory)?;
672            let sort_iter = if let Some(policy) = memory {
673                SortIterator::new_with_policy(input_iter, &order_by, Some(policy.clone()))?
674            } else {
675                SortIterator::new(input_iter, &order_by)?
676            };
677            Ok((Box::new(sort_iter), projection, schema))
678        }
679        LogicalPlan::Limit {
680            input,
681            limit,
682            offset,
683        } => {
684            let (input_iter, projection, schema) =
685                build_streaming_pipeline_inner(txn, catalog, *input, memory)?;
686            let limit_iter = LimitIterator::new(input_iter, limit, offset);
687            Ok((Box::new(limit_iter), projection, schema))
688        }
689        other => Err(ExecutorError::UnsupportedOperation(format!(
690            "unsupported query plan: {other:?}"
691        ))),
692    }
693}
694
695/// Evaluate a typed expression against a row, returning SqlValue.
696fn eval_expr(expr: &crate::planner::typed_expr::TypedExpr, row: &Row) -> Result<SqlValue> {
697    let ctx = EvalContext::new(&row.values);
698    crate::executor::evaluator::evaluate(expr, &ctx)
699}
700
701fn combine_outer_for_eval(row: &Row, outer: Option<&Row>) -> Row {
702    let Some(outer) = outer else {
703        return row.clone();
704    };
705    let mut values = Vec::with_capacity(row.len() + outer.len());
706    values.extend(row.values.clone());
707    values.extend(outer.values.clone());
708    Row::new(row.row_id, values)
709}
710
711fn execute_project_with_subqueries<
712    'txn,
713    S: KVStore + 'txn,
714    C: Catalog + ?Sized,
715    T: SqlTxn<'txn, S>,
716>(
717    txn: &mut T,
718    catalog: &C,
719    rows: Vec<Row>,
720    projection: &Projection,
721    schema: &[crate::catalog::ColumnMetadata],
722    outer: Option<&Row>,
723) -> Result<QueryResult> {
724    match projection {
725        Projection::All(_) => project::execute_project(rows, projection, schema),
726        Projection::Columns(cols)
727            if outer.is_some() || cols.iter().any(|c| subquery::contains_subquery(&c.expr)) =>
728        {
729            let columns: Vec<_> = cols
730                .iter()
731                .enumerate()
732                .map(|(i, c)| column_info_from_projection(c, i))
733                .collect();
734            let mut projected_rows = Vec::with_capacity(rows.len());
735            for row in rows {
736                let eval_row = combine_outer_for_eval(&row, outer);
737                let mut values = Vec::with_capacity(cols.len());
738                for col in cols {
739                    values.push(subquery::evaluate_expr_with_subqueries(
740                        txn, catalog, &col.expr, &eval_row,
741                    )?);
742                }
743                projected_rows.push(values);
744            }
745            Ok(QueryResult::new(columns, projected_rows))
746        }
747        Projection::Columns(_) => project::execute_project(rows, projection, schema),
748    }
749}
750
751/// Build column info name using alias fallback.
752fn column_name_from_projection(
753    projected: &crate::planner::typed_expr::ProjectedColumn,
754    idx: usize,
755) -> String {
756    projected
757        .alias
758        .clone()
759        .or_else(|| match &projected.expr.kind {
760            crate::planner::typed_expr::TypedExprKind::ColumnRef { column, .. } => {
761                Some(column.clone())
762            }
763            _ => None,
764        })
765        .unwrap_or_else(|| format!("col_{idx}"))
766}
767
768/// Build ColumnInfo from projection.
769fn column_info_from_projection(
770    projected: &crate::planner::typed_expr::ProjectedColumn,
771    idx: usize,
772) -> ColumnInfo {
773    ColumnInfo::new(
774        column_name_from_projection(projected, idx),
775        projected.expr.resolved_type.clone(),
776    )
777}
778
779/// Build ColumnInfo for Projection::All using schema.
780fn column_infos_from_all(
781    schema: &[crate::catalog::ColumnMetadata],
782    names: &[String],
783) -> Result<Vec<ColumnInfo>> {
784    names
785        .iter()
786        .map(|name| {
787            let col = schema
788                .iter()
789                .find(|c| &c.name == name)
790                .ok_or_else(|| ExecutorError::ColumnNotFound(name.clone()))?;
791            Ok(ColumnInfo::new(name.clone(), col.data_type.clone()))
792        })
793        .collect()
794}
795
796#[cfg(test)]
797mod tests {
798    use super::*;
799    use crate::catalog::{ColumnMetadata, MemoryCatalog, TableMetadata};
800    use crate::executor::ddl::create_table::execute_create_table;
801    use crate::planner::typed_expr::TypedExpr;
802    use crate::planner::types::ResolvedType;
803    use crate::storage::TxnBridge;
804    use alopex_core::kv::memory::MemoryKV;
805    use std::sync::Arc;
806
807    #[test]
808    fn execute_query_scan_only_returns_rows() {
809        let bridge = TxnBridge::new(Arc::new(MemoryKV::new()));
810        let mut catalog = MemoryCatalog::new();
811        let table = TableMetadata::new(
812            "users",
813            vec![
814                ColumnMetadata::new("id", ResolvedType::Integer),
815                ColumnMetadata::new("name", ResolvedType::Text),
816            ],
817        );
818        let mut ddl_txn = bridge.begin_write().unwrap();
819        execute_create_table(&mut ddl_txn, &mut catalog, table.clone(), vec![], false).unwrap();
820        ddl_txn.commit().unwrap();
821
822        let mut txn = bridge.begin_write().unwrap();
823        crate::executor::dml::execute_insert(
824            &mut txn,
825            &catalog,
826            "users",
827            vec!["id".into(), "name".into()],
828            vec![vec![
829                TypedExpr::literal(
830                    crate::ast::expr::Literal::Number("1".into()),
831                    ResolvedType::Integer,
832                    crate::Span::default(),
833                ),
834                TypedExpr::literal(
835                    crate::ast::expr::Literal::String("alice".into()),
836                    ResolvedType::Text,
837                    crate::Span::default(),
838                ),
839            ]],
840        )
841        .unwrap();
842
843        let result = execute_query(
844            &mut txn,
845            &catalog,
846            LogicalPlan::scan(
847                "users".into(),
848                Projection::All(vec!["id".into(), "name".into()]),
849            ),
850        )
851        .unwrap();
852
853        match result {
854            ExecutionResult::Query(q) => {
855                assert_eq!(q.rows.len(), 1);
856                assert_eq!(q.columns.len(), 2);
857                assert_eq!(
858                    q.rows[0],
859                    vec![SqlValue::Integer(1), SqlValue::Text("alice".into())]
860                );
861            }
862            other => panic!("unexpected result {other:?}"),
863        }
864    }
865}