MCPcopy Create free account
hub / github.com/NodeDB-Lab/nodedb / compile_for

Function compile_for

nodedb/src/control/planner/procedural/compiler.rs:205–263  ·  view source on GitHub ↗

Compile FOR loop by unrolling into a nested CASE WHEN chain. `FOR i IN 1..3 LOOP RETURN i * 2; END LOOP;` compiles to: `CASE WHEN true THEN (1) * 2 END` Since function FOR loops must contain a RETURN (otherwise value is undefined), and the first iteration's RETURN determines the value, unrolling just evaluates the body with each iteration's variable binding. For FOR loops with conditional RETUR

(
    var: &str,
    start: &SqlExpr,
    end: &SqlExpr,
    reverse: bool,
    body: &[Statement],
    ctx: &mut CompileContext,
)

Source from the content-addressed store, hash-verified

203/// `FOR i IN 1..3 LOOP IF arr[i] > threshold THEN RETURN i; END IF; END LOOP;`
204/// → unrolled as nested CASE WHEN for each iteration.
205fn compile_for(
206 var: &str,
207 start: &SqlExpr,
208 end: &SqlExpr,
209 reverse: bool,
210 body: &[Statement],
211 ctx: &mut CompileContext,
212) -> Result<String, ProceduralError> {
213 let start_val = start.sql.trim().parse::<i64>().map_err(|_| {
214 ProceduralError::compile("FOR loop start must be integer literal in function bodies")
215 })?;
216 let end_val = end.sql.trim().parse::<i64>().map_err(|_| {
217 ProceduralError::compile("FOR loop end must be integer literal in function bodies")
218 })?;
219
220 let iterations: Vec<i64> = if reverse {
221 (end_val..=start_val).rev().collect()
222 } else {
223 (start_val..=end_val).collect()
224 };
225
226 if iterations.len() as u64 > MAX_LOOP_UNROLL {
227 return Err(ProceduralError::compile(format!(
228 "FOR loop has {} iterations, exceeds unrolling threshold ({MAX_LOOP_UNROLL})",
229 iterations.len()
230 )));
231 }
232
233 if iterations.is_empty() {
234 return Ok("NULL".into());
235 }
236
237 // Unroll: for each iteration, bind the variable and compile the body.
238 // Use nested CASE WHEN to try each iteration in order.
239 // The first RETURN hit determines the value.
240 let mut case_parts = Vec::new();
241 for val in &iterations {
242 let mut iter_ctx = ctx.clone_vars();
243 iter_ctx.variables.insert(var.to_string(), val.to_string());
244 let body_sql = compile_statements(body, &mut iter_ctx)?;
245 // Each iteration becomes a WHEN clause. If the body is unconditional
246 // (always returns), it's `WHEN true THEN <body>`. If conditional,
247 // the inner CASE WHEN handles it.
248 if body_sql != "NULL" {
249 case_parts.push(format!("WHEN true THEN {body_sql}"));
250 // For unconditional returns, first iteration wins.
251 if !body.iter().any(|s| matches!(s, Statement::If { .. })) {
252 break;
253 }
254 }
255 }
256
257 if case_parts.is_empty() {
258 return Ok("NULL".into());
259 }
260
261 case_parts.push("ELSE NULL".into());
262 Ok(format!("CASE {} END", case_parts.join(" ")))

Callers 1

compile_statementsFunction · 0.85

Calls 9

compile_statementsFunction · 0.85
collectMethod · 0.80
clone_varsMethod · 0.80
to_stringMethod · 0.80
lenMethod · 0.45
is_emptyMethod · 0.45
insertMethod · 0.45
pushMethod · 0.45
iterMethod · 0.45

Tested by

no test coverage detected