cypherlite_query/executor/operators/
with.rs1use crate::executor::eval::eval;
4use crate::executor::{ExecutionError, Params, Record, ScalarFnLookup};
5use crate::parser::ast::{Expression, ReturnItem};
6use cypherlite_storage::StorageEngine;
7
8pub fn execute_with(
12 source_records: Vec<Record>,
13 items: &[ReturnItem],
14 engine: &StorageEngine,
15 params: &Params,
16 scalar_fns: &dyn ScalarFnLookup,
17) -> Result<Vec<Record>, ExecutionError> {
18 let mut results = Vec::new();
19
20 for record in &source_records {
21 let mut projected = Record::new();
22
23 for item in items {
24 let value = eval(&item.expr, record, engine, params, scalar_fns)?;
25 let column_name = match &item.alias {
26 Some(alias) => alias.clone(),
27 None => expr_display_name(&item.expr),
28 };
29 projected.insert(column_name, value);
30 }
31
32 results.push(projected);
33 }
34
35 Ok(results)
36}
37
38fn expr_display_name(expr: &Expression) -> String {
40 match expr {
41 Expression::Variable(name) => name.clone(),
42 Expression::Property(inner, prop) => {
43 format!("{}.{}", expr_display_name(inner), prop)
44 }
45 Expression::CountStar => "count(*)".to_string(),
46 _ => "expr".to_string(),
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53 use crate::executor::Value;
54 use crate::parser::ast::*;
55 use cypherlite_core::{DatabaseConfig, LabelRegistry, SyncMode};
56 use cypherlite_storage::StorageEngine;
57 use tempfile::tempdir;
58
59 fn test_engine(dir: &std::path::Path) -> StorageEngine {
60 let config = DatabaseConfig {
61 path: dir.join("test.cyl"),
62 wal_sync_mode: SyncMode::Normal,
63 ..Default::default()
64 };
65 StorageEngine::open(config).expect("open")
66 }
67
68 #[test]
70 fn test_with_projects_specified_columns() {
71 let dir = tempdir().expect("tempdir");
72 let engine = test_engine(dir.path());
73
74 let mut record = Record::new();
75 record.insert("x".to_string(), Value::Int64(1));
76 record.insert("y".to_string(), Value::Int64(2));
77 record.insert("z".to_string(), Value::Int64(3));
78
79 let items = vec![ReturnItem {
81 expr: Expression::Variable("x".to_string()),
82 alias: None,
83 }];
84
85 let params = Params::new();
86 let result = execute_with(vec![record], &items, &engine, ¶ms, &());
87 let records = result.expect("should succeed");
88 assert_eq!(records.len(), 1);
89 assert_eq!(records[0].get("x"), Some(&Value::Int64(1)));
90 assert!(!records[0].contains_key("y"));
91 assert!(!records[0].contains_key("z"));
92 }
93
94 #[test]
96 fn test_with_alias_renames_column() {
97 let dir = tempdir().expect("tempdir");
98 let mut engine = test_engine(dir.path());
99
100 let name_key = engine.get_or_create_prop_key("name");
101 let nid = engine.create_node(
102 vec![],
103 vec![(
104 name_key,
105 cypherlite_core::PropertyValue::String("Alice".into()),
106 )],
107 );
108
109 let mut record = Record::new();
110 record.insert("n".to_string(), Value::Node(nid));
111
112 let items = vec![ReturnItem {
114 expr: Expression::Property(
115 Box::new(Expression::Variable("n".to_string())),
116 "name".to_string(),
117 ),
118 alias: Some("person_name".to_string()),
119 }];
120
121 let params = Params::new();
122 let result = execute_with(vec![record], &items, &engine, ¶ms, &());
123 let records = result.expect("should succeed");
124 assert_eq!(records.len(), 1);
125 assert_eq!(
126 records[0].get("person_name"),
127 Some(&Value::String("Alice".into()))
128 );
129 assert!(!records[0].contains_key("n"));
130 }
131
132 #[test]
134 fn test_with_multiple_items() {
135 let dir = tempdir().expect("tempdir");
136 let engine = test_engine(dir.path());
137
138 let mut record = Record::new();
139 record.insert("a".to_string(), Value::Int64(10));
140 record.insert("b".to_string(), Value::Int64(20));
141 record.insert("c".to_string(), Value::Int64(30));
142
143 let items = vec![
145 ReturnItem {
146 expr: Expression::Variable("a".to_string()),
147 alias: None,
148 },
149 ReturnItem {
150 expr: Expression::Variable("b".to_string()),
151 alias: None,
152 },
153 ];
154
155 let params = Params::new();
156 let result = execute_with(vec![record], &items, &engine, ¶ms, &());
157 let records = result.expect("should succeed");
158 assert_eq!(records.len(), 1);
159 assert_eq!(records[0].get("a"), Some(&Value::Int64(10)));
160 assert_eq!(records[0].get("b"), Some(&Value::Int64(20)));
161 assert!(!records[0].contains_key("c"));
162 }
163
164 #[test]
166 fn test_with_produces_duplicates_for_distinct_to_handle() {
167 let dir = tempdir().expect("tempdir");
168 let engine = test_engine(dir.path());
169
170 let mut r1 = Record::new();
171 r1.insert("x".to_string(), Value::String("A".into()));
172 r1.insert("y".to_string(), Value::Int64(1));
173 let mut r2 = Record::new();
174 r2.insert("x".to_string(), Value::String("A".into()));
175 r2.insert("y".to_string(), Value::Int64(2));
176
177 let items = vec![ReturnItem {
179 expr: Expression::Variable("x".to_string()),
180 alias: None,
181 }];
182
183 let params = Params::new();
184 let result = execute_with(vec![r1, r2], &items, &engine, ¶ms, &());
185 let records = result.expect("should succeed");
186 assert_eq!(records.len(), 2);
188 assert_eq!(records[0].get("x"), Some(&Value::String("A".into())));
189 assert_eq!(records[1].get("x"), Some(&Value::String("A".into())));
190 }
191
192 #[test]
194 fn test_with_empty_input() {
195 let dir = tempdir().expect("tempdir");
196 let engine = test_engine(dir.path());
197
198 let items = vec![ReturnItem {
199 expr: Expression::Variable("x".to_string()),
200 alias: None,
201 }];
202
203 let params = Params::new();
204 let result = execute_with(vec![], &items, &engine, ¶ms, &());
205 let records = result.expect("should succeed");
206 assert!(records.is_empty());
207 }
208}