reifydb_engine/vm/volcano/
map.rs1use std::{mem, sync::Arc};
5
6use reifydb_core::{
7 interface::{evaluate::TargetColumn, resolved::ResolvedColumn},
8 value::column::{ColumnWithName, cast::cast_column_data, columns::Columns, headers::ColumnHeaders},
9};
10use reifydb_evaluate::expression::{
11 compile::{CompiledExpr, compile_expression},
12 context::{CompileContext, EvalContext},
13};
14use reifydb_extension::transform::{Transform, context::TransformContext};
15use reifydb_rql::expression::{Expression, name::display_label};
16use reifydb_transaction::transaction::Transaction;
17use reifydb_value::{fragment::Fragment, reifydb_assertions, value::system_columns::SystemColumns};
18use tracing::instrument;
19
20use super::NoopNode;
21use crate::{
22 Result,
23 vm::volcano::{
24 query::{QueryContext, QueryNode, eval_context_from_query, eval_context_from_transform},
25 udf::{UdfEvalNode, evaluate_udfs_no_input, strip_udf_columns},
26 },
27};
28
29pub(crate) struct MapNode {
30 input: Box<dyn QueryNode>,
31 expressions: Vec<Expression>,
32 udf_names: Vec<String>,
33 headers: Option<ColumnHeaders>,
34 context: Option<(Arc<QueryContext>, Vec<CompiledExpr>)>,
35}
36
37impl MapNode {
38 pub fn new(input: Box<dyn QueryNode>, expressions: Vec<Expression>) -> Self {
39 Self {
40 input,
41 expressions,
42 udf_names: Vec::new(),
43 headers: None,
44 context: None,
45 }
46 }
47}
48
49impl QueryNode for MapNode {
50 #[instrument(name = "volcano::map::initialize", level = "trace", skip_all)]
51 fn initialize<'a>(&mut self, rx: &mut Transaction<'a>, ctx: &QueryContext) -> Result<()> {
52 let (input, expressions, udf_names) = UdfEvalNode::wrap_if_needed(
53 mem::replace(&mut self.input, Box::new(NoopNode)),
54 &self.expressions,
55 &ctx.symbols,
56 );
57 self.input = input;
58 self.expressions = expressions;
59 self.udf_names = udf_names;
60
61 let compile_ctx = CompileContext {
62 symbols: &ctx.symbols,
63 };
64 let compiled = self
65 .expressions
66 .iter()
67 .map(|e| compile_expression(&compile_ctx, e).expect("compile"))
68 .collect();
69 self.context = Some((Arc::new(ctx.clone()), compiled));
70 let column_names = self.expressions.iter().map(display_label).collect();
71 self.headers = Some(ColumnHeaders {
72 columns: column_names,
73 });
74 self.input.initialize(rx, ctx)?;
75 Ok(())
76 }
77
78 #[instrument(name = "volcano::map::next", level = "trace", skip_all)]
79 fn next<'a>(&mut self, rx: &mut Transaction<'a>, ctx: &mut QueryContext) -> Result<Option<Columns>> {
80 reifydb_assertions! {
81 assert!(self.context.is_some(), "MapNode::next() called before initialize()");
82 }
83
84 if let Some(columns) = self.input.next(rx, ctx)? {
85 let stored_ctx = &self.context.as_ref().unwrap().0;
86 let transform_ctx = TransformContext {
87 routines: &ctx.services.routines,
88 runtime_context: &stored_ctx.services.runtime_context,
89 params: &stored_ctx.params,
90 };
91 let mut result = self.apply(&transform_ctx, columns)?;
92 strip_udf_columns(&mut result, &self.udf_names);
93
94 Ok(Some(result))
95 } else {
96 Ok(None)
97 }
98 }
99
100 fn headers(&self) -> Option<ColumnHeaders> {
101 self.headers.clone().or(self.input.headers())
102 }
103}
104
105impl Transform for MapNode {
106 fn apply(&self, ctx: &TransformContext, input: Columns) -> Result<Columns> {
107 let (stored_ctx, compiled) =
108 self.context.as_ref().expect("MapNode::apply() called before initialize()");
109
110 let row_count = input.row_count();
111 let session = eval_context_from_transform(ctx, stored_ctx);
112 let mut new_columns = Vec::with_capacity(compiled.len());
113
114 for (expr, compiled_expr) in self.expressions.iter().zip(compiled.iter()) {
115 let mut exec_ctx = Self::eval_context(&session, &input, row_count);
116
117 if let (Expression::Alias(alias_expr), Some(source)) = (expr, &stored_ctx.source) {
118 let alias_name = alias_expr.alias.name();
119 if let Some(table_column) = source.columns().iter().find(|col| col.name == alias_name) {
120 let column_ident = Fragment::internal(&table_column.name);
121 let resolved_column =
122 ResolvedColumn::new(column_ident, source.clone(), table_column.clone());
123 exec_ctx.target = Some(TargetColumn::Resolved(resolved_column));
124 }
125 }
126
127 let mut column = Self::eval_projection(compiled_expr, &exec_ctx)?;
128
129 if let Some(target_type) = exec_ctx.target.as_ref().map(|t| t.column_type())
130 && column.data.get_type() != target_type
131 {
132 let data =
133 cast_column_data(&exec_ctx, &column.data, target_type, &expr.lazy_fragment())?;
134 column = ColumnWithName {
135 name: column.name,
136 data,
137 };
138 }
139
140 new_columns.push(column);
141 }
142
143 Ok(Self::assemble(input.system, new_columns))
144 }
145}
146
147impl MapNode {
148 #[instrument(level = "trace", skip_all, name = "volcano::map::eval_context")]
149 fn eval_context<'e>(session: &EvalContext<'e>, input: &Columns, row_count: usize) -> EvalContext<'e> {
150 session.with_eval(input.clone(), row_count)
151 }
152
153 #[instrument(level = "trace", skip_all, name = "volcano::map::eval")]
154 fn eval_projection(compiled: &CompiledExpr, exec_ctx: &EvalContext) -> Result<ColumnWithName> {
155 compiled.execute(exec_ctx)
156 }
157
158 #[instrument(level = "trace", skip_all, name = "volcano::map::assemble")]
159 fn assemble(system: SystemColumns, new_columns: Vec<ColumnWithName>) -> Columns {
160 let mut names_vec = Vec::with_capacity(new_columns.len());
161 let mut buffers_vec = Vec::with_capacity(new_columns.len());
162 for c in new_columns {
163 names_vec.push(c.name);
164 buffers_vec.push(c.data);
165 }
166 Columns {
167 system,
168 columns: buffers_vec,
169 names: names_vec,
170 }
171 }
172}
173
174pub(crate) struct MapWithoutInputNode {
175 expressions: Vec<Expression>,
176 headers: Option<ColumnHeaders>,
177
178 udf_columns: Option<Columns>,
179 context: Option<(Arc<QueryContext>, Vec<CompiledExpr>)>,
180}
181
182impl MapWithoutInputNode {
183 pub fn new(expressions: Vec<Expression>) -> Self {
184 Self {
185 expressions,
186 headers: None,
187 udf_columns: None,
188 context: None,
189 }
190 }
191}
192
193impl QueryNode for MapWithoutInputNode {
194 #[instrument(name = "volcano::map::noinput::initialize", level = "trace", skip_all)]
195 fn initialize<'a>(&mut self, rx: &mut Transaction<'a>, ctx: &QueryContext) -> Result<()> {
196 if let Some((rewritten, udf_cols)) = evaluate_udfs_no_input(&self.expressions, ctx, rx)? {
197 self.expressions = rewritten;
198 self.udf_columns = Some(udf_cols);
199 }
200
201 let compile_ctx = CompileContext {
202 symbols: &ctx.symbols,
203 };
204 let compiled = self
205 .expressions
206 .iter()
207 .map(|e| compile_expression(&compile_ctx, e).expect("compile"))
208 .collect();
209 self.context = Some((Arc::new(ctx.clone()), compiled));
210 Ok(())
211 }
212
213 #[instrument(name = "volcano::map::noinput::next", level = "trace", skip_all)]
214 fn next<'a>(&mut self, _rx: &mut Transaction<'a>, _ctx: &mut QueryContext) -> Result<Option<Columns>> {
215 reifydb_assertions! {
216 assert!(self.context.is_some(), "MapWithoutInputNode::next() called before initialize()");
217 }
218 let (stored_ctx, compiled) = self.context.as_ref().unwrap();
219
220 if self.headers.is_some() {
221 return Ok(None);
222 }
223
224 let session = eval_context_from_query(stored_ctx);
225 let mut columns = vec![];
226
227 for compiled_expr in compiled {
228 let exec_ctx = match &self.udf_columns {
229 Some(udf_cols) => session.with_eval(udf_cols.clone(), 1),
230 None => session.with_eval_empty(),
231 };
232
233 let column = compiled_expr.execute(&exec_ctx)?;
234
235 columns.push(column);
236 }
237
238 let columns = Columns::new(columns);
239 self.headers = Some(ColumnHeaders::from_columns(&columns));
240 Ok(Some(columns))
241 }
242
243 fn headers(&self) -> Option<ColumnHeaders> {
244 self.headers.clone()
245 }
246}