Skip to main content

cynos_query/executor/
project.rs

1//! Project executor.
2
3use crate::executor::{Relation, RelationEntry, SharedTables};
4use alloc::rc::Rc;
5use alloc::vec;
6use alloc::vec::Vec;
7use cynos_core::{Row, Value};
8
9/// Project executor - projects specific columns from rows.
10pub struct ProjectExecutor {
11    /// Column indices to project.
12    column_indices: Vec<usize>,
13}
14
15impl ProjectExecutor {
16    /// Creates a new project executor.
17    pub fn new(column_indices: Vec<usize>) -> Self {
18        Self { column_indices }
19    }
20
21    /// Executes the projection on the input relation.
22    pub fn execute(&self, input: Relation) -> Relation {
23        let tables = input.tables().to_vec();
24        let shared_tables: SharedTables = tables.clone().into();
25        let entries: Vec<RelationEntry> = input
26            .into_iter()
27            .map(|entry| {
28                let values: Vec<Value> = self
29                    .column_indices
30                    .iter()
31                    .map(|&idx| entry.get_field(idx).cloned().unwrap_or(Value::Null))
32                    .collect();
33                RelationEntry::new_combined(Rc::new(Row::new(entry.id(), values)), shared_tables.clone())
34            })
35            .collect();
36
37        // After projection, we have a single combined result with projected columns
38        let table_column_counts = vec![self.column_indices.len()];
39        Relation { entries, tables, table_column_counts }
40    }
41}
42
43/// Projects columns from a relation using a transformation function.
44#[allow(dead_code)]
45pub fn project_relation<F>(input: Relation, transform: F) -> Relation
46where
47    F: Fn(&RelationEntry) -> Vec<Value>,
48{
49    let tables = input.tables().to_vec();
50    let shared_tables: SharedTables = tables.clone().into();
51    let entries: Vec<RelationEntry> = input
52        .into_iter()
53        .map(|entry| {
54            let values = transform(&entry);
55            RelationEntry::new_combined(Rc::new(Row::new(entry.id(), values)), shared_tables.clone())
56        })
57        .collect();
58
59    // After transform, we don't know the exact column count, use entries length as approximation
60    let table_column_counts = if entries.is_empty() {
61        vec![0]
62    } else {
63        vec![entries[0].row.len()]
64    };
65    Relation { entries, tables, table_column_counts }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71    use cynos_core::Row;
72    use alloc::vec;
73
74    #[test]
75    fn test_project_executor() {
76        let rows = vec![
77            Rc::new(Row::new(1, vec![Value::Int64(1), Value::String("Alice".into()), Value::Int64(25)])),
78            Rc::new(Row::new(2, vec![Value::Int64(2), Value::String("Bob".into()), Value::Int64(30)])),
79        ];
80        let input = Relation::from_rows(rows, vec!["users".into()]);
81
82        // Project only columns 0 and 2 (id and age)
83        let executor = ProjectExecutor::new(vec![0, 2]);
84        let result = executor.execute(input);
85
86        assert_eq!(result.len(), 2);
87        let first = &result.entries[0];
88        assert_eq!(first.row.len(), 2);
89        assert_eq!(first.get_field(0), Some(&Value::Int64(1)));
90        assert_eq!(first.get_field(1), Some(&Value::Int64(25)));
91    }
92
93    #[test]
94    fn test_project_relation_transform() {
95        let rows = vec![Rc::new(Row::new(1, vec![Value::Int64(10), Value::Int64(20)]))];
96        let input = Relation::from_rows(rows, vec!["t".into()]);
97
98        let result = project_relation(input, |entry| {
99            let a = entry.get_field(0).and_then(|v| v.as_i64()).unwrap_or(0);
100            let b = entry.get_field(1).and_then(|v| v.as_i64()).unwrap_or(0);
101            vec![Value::Int64(a + b)]
102        });
103
104        assert_eq!(result.len(), 1);
105        assert_eq!(result.entries[0].get_field(0), Some(&Value::Int64(30)));
106    }
107}