1use anyhow::{Result, anyhow};
16use mangle_ir::physical::{self, Aggregate, CmpOp, Condition, DataSource, Expr, Op, Operand};
17use mangle_ir::{Inst, InstId, Ir, NameId};
18use rustc_hash::FxHashSet;
19
20pub struct Planner<'a> {
21 ir: &'a mut Ir,
22 delta_pred: Option<NameId>,
23 fresh_counter: usize,
24 hash_join: bool,
28}
29
30fn hash_join_env_default() -> bool {
34 std::env::var("MANGLE_HASHJOIN")
35 .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
36 .unwrap_or(false)
37}
38
39fn splice_hash_join_body(op: Op, body: Op) -> Op {
42 match op {
43 Op::HashJoin {
44 build_source,
45 probe_source,
46 join_keys,
47 ..
48 } => Op::HashJoin {
49 build_source,
50 probe_source,
51 join_keys,
52 body: Box::new(body),
53 },
54 other => other,
55 }
56}
57
58impl<'a> Planner<'a> {
59 pub fn new(ir: &'a mut Ir) -> Self {
60 Self {
61 ir,
62 delta_pred: None,
63 fresh_counter: 0,
64 hash_join: hash_join_env_default(),
65 }
66 }
67
68 pub fn with_delta(mut self, delta_pred: NameId) -> Self {
69 self.delta_pred = Some(delta_pred);
70 self
71 }
72
73 pub fn with_hash_join(mut self, enabled: bool) -> Self {
77 self.hash_join = enabled;
78 self
79 }
80
81 pub fn plan_rule(mut self, rule_id: InstId) -> Result<Op> {
82 let (head, premises, transform) = match self.ir.get(rule_id) {
83 Inst::Rule {
84 head,
85 premises,
86 transform,
87 } => (*head, premises.clone(), transform.clone()),
88 _ => return Err(anyhow!("Not a rule")),
89 };
90
91 let blocks = self.split_transforms(transform);
93 let num_blocks = blocks.len();
94
95 let mut ops = Vec::new();
96 let mut current_source: Option<(NameId, Vec<NameId>)> = None;
97 let mut bound_vars = FxHashSet::default();
98
99 for (i, block) in blocks.into_iter().enumerate() {
100 let is_last = i == num_blocks - 1;
101
102 if i == 0 {
103 if is_last {
105 let op = self.plan_join_sequence(
107 premises.clone(),
108 &mut bound_vars,
109 |planner, vars| {
110 planner.plan_transforms_sequence(&block, vars, |p, v| {
111 p.plan_head_insert(head, v)
112 })
113 },
114 )?;
115 ops.push(op);
116 } else {
117 let temp_rel = self.fresh_var("temp_grp");
119 let mut capture_vars: Vec<NameId> = Vec::new(); let op = self.plan_join_sequence(
122 premises.clone(),
123 &mut bound_vars,
124 |planner, vars| {
125 planner.plan_transforms_sequence(&block, vars, |_, v| {
126 let mut sorted_vars: Vec<NameId> = v.iter().cloned().collect();
127 sorted_vars.sort();
128 capture_vars = sorted_vars.clone();
129 let args =
130 sorted_vars.iter().map(|&var| Operand::Var(var)).collect();
131 Ok(Op::Insert {
132 relation: temp_rel,
133 args,
134 })
135 })
136 },
137 )?;
138 ops.push(op);
139 current_source = Some((temp_rel, capture_vars));
140 }
141 } else {
142 let (src_rel, src_vars) = current_source.take().expect("No source for aggregation");
144
145 if is_last {
146 let op = self.plan_block_k(src_rel, src_vars, &block, |p, v| {
147 p.plan_head_insert(head, v)
148 })?;
149 ops.push(op);
150 } else {
151 let next_temp = self.fresh_var("temp_grp");
152 let mut next_vars: Vec<NameId> = Vec::new();
153
154 let op = self.plan_block_k(src_rel, src_vars, &block, |_, v| {
155 let mut sorted_vars: Vec<NameId> = v.iter().cloned().collect();
156 sorted_vars.sort();
157 next_vars = sorted_vars.clone();
158 let args = sorted_vars.iter().map(|&var| Operand::Var(var)).collect();
159 Ok(Op::Insert {
160 relation: next_temp,
161 args,
162 })
163 })?;
164 ops.push(op);
165 current_source = Some((next_temp, next_vars));
166 }
167 }
168 }
169
170 if ops.len() == 1 {
171 Ok(ops.remove(0))
172 } else {
173 Ok(Op::Seq(ops))
174 }
175 }
176
177 fn split_transforms(&self, transforms: Vec<InstId>) -> Vec<Vec<InstId>> {
178 let mut blocks = Vec::new();
179 let mut current = Vec::new();
180 for t in transforms {
181 let inst = self.ir.get(t);
182 if let Inst::Transform { var: None, .. } = inst {
183 blocks.push(current);
184 current = Vec::new();
185 }
186 current.push(t);
187 }
188 blocks.push(current);
189 blocks
190 }
191
192 fn plan_block_k<F>(
193 &mut self,
194 source_rel: NameId,
195 source_vars: Vec<NameId>,
196 block: &[InstId],
197 continuation: F,
198 ) -> Result<Op>
199 where
200 F: FnOnce(&mut Self, &mut FxHashSet<NameId>) -> Result<Op>,
201 {
202 let do_stmt = block[0];
203 let rest = &block[1..];
204
205 let keys_insts = self.get_transform_app_args(do_stmt)?;
206 let mut keys = Vec::new();
207 for k in keys_insts {
208 if let Inst::Var(v) = self.ir.get(k) {
209 keys.push(*v);
210 } else {
211 return Err(anyhow!("GroupBy keys must be variables"));
212 }
213 }
214
215 let mut aggregates = Vec::new();
216 let mut lets = Vec::new();
217 for &t in rest {
218 if let Some(agg) = self.try_parse_aggregate(t)? {
219 aggregates.push(agg);
220 } else {
221 lets.push(t);
222 }
223 }
224
225 let mut inner_vars = FxHashSet::default();
226 for &k in &keys {
227 inner_vars.insert(k);
228 }
229 for agg in &aggregates {
230 inner_vars.insert(agg.var);
231 }
232
233 let body = self.plan_transforms_sequence(&lets, &mut inner_vars, continuation)?;
234
235 Ok(Op::GroupBy {
236 source: source_rel,
237 vars: source_vars,
238 keys,
239 aggregates,
240 body: Box::new(body),
241 })
242 }
243
244 fn plan_transforms_sequence<F>(
245 &mut self,
246 transforms: &[InstId],
247 bound_vars: &mut FxHashSet<NameId>,
248 continuation: F,
249 ) -> Result<Op>
250 where
251 F: FnOnce(&mut Self, &mut FxHashSet<NameId>) -> Result<Op>,
252 {
253 if transforms.is_empty() {
254 return continuation(self, bound_vars);
255 }
256
257 let t_id = transforms[0];
258 let rest = &transforms[1..];
259
260 let inst = self.ir.get(t_id).clone();
261 if let Inst::Transform {
262 var: Some(var),
263 app,
264 } = inst
265 {
266 self.inst_to_expr(app, |planner, expr| {
267 bound_vars.insert(var);
268 let body = planner.plan_transforms_sequence(rest, bound_vars, continuation)?;
269 Ok(Op::Let {
270 var,
271 expr,
272 body: Box::new(body),
273 })
274 })
275 } else {
276 Err(anyhow!("Unexpected transform in sequence"))
278 }
279 }
280
281 fn fresh_var(&mut self, prefix: &str) -> NameId {
282 let id = self.fresh_counter;
283 self.fresh_counter += 1;
284 let name = format!("${}_{}", prefix, id);
285 self.ir.intern_name(name)
286 }
287
288 fn try_plan_hash_join(
294 &self,
295 predicate: NameId,
296 args: &[InstId],
297 premises: &mut Vec<InstId>,
298 bound_vars: &mut FxHashSet<NameId>,
299 ) -> Result<Option<Op>> {
300 let Some(build_vars) = self.all_fresh_distinct_vars(args, bound_vars) else {
301 return Ok(None);
302 };
303 let next_id = *premises.first().unwrap();
304 let next_inst = self.ir.get(next_id).clone();
305 let Inst::Atom {
306 predicate: next_pred,
307 args: next_args,
308 } = next_inst
309 else {
310 return Ok(None);
311 };
312 if Some(next_pred) == self.delta_pred {
313 return Ok(None);
314 }
315 let Some(probe_vars) = self.all_fresh_distinct_vars(&next_args, bound_vars) else {
316 return Ok(None);
317 };
318 let join_keys: Vec<NameId> = build_vars
319 .iter()
320 .filter(|v| probe_vars.contains(v))
321 .copied()
322 .collect();
323 if join_keys.is_empty() {
324 return Ok(None);
325 }
326
327 premises.remove(0);
329 for v in build_vars.iter().chain(probe_vars.iter()) {
330 bound_vars.insert(*v);
331 }
332
333 Ok(Some(Op::HashJoin {
334 build_source: DataSource::Scan {
335 relation: predicate,
336 vars: build_vars,
337 },
338 probe_source: DataSource::Scan {
339 relation: next_pred,
340 vars: probe_vars,
341 },
342 join_keys,
343 body: Box::new(Op::Nop),
344 }))
345 }
346
347 fn all_fresh_distinct_vars(
352 &self,
353 args: &[InstId],
354 bound_vars: &FxHashSet<NameId>,
355 ) -> Option<Vec<NameId>> {
356 let mut out: Vec<NameId> = Vec::with_capacity(args.len());
357 for arg in args {
358 let Inst::Var(v) = self.ir.get(*arg) else {
359 return None;
360 };
361 if bound_vars.contains(v) || out.contains(v) {
362 return None;
363 }
364 out.push(*v);
365 }
366 Some(out)
367 }
368
369 fn plan_join_sequence<F>(
370 &mut self,
371 mut premises: Vec<InstId>,
372 bound_vars: &mut FxHashSet<NameId>,
373 continuation: F,
374 ) -> Result<Op>
375 where
376 F: FnOnce(&mut Self, &mut FxHashSet<NameId>) -> Result<Op>,
377 {
378 if premises.is_empty() {
379 return continuation(self, bound_vars);
380 }
381
382 let current_premise = premises.remove(0);
383 let inst = self.ir.get(current_premise).clone();
384
385 match inst {
386 Inst::Atom { predicate, args }
387 if matches!(
388 self.ir.resolve_name(predicate),
389 ":lt"
390 | ":le"
391 | ":gt"
392 | ":ge"
393 | ":time:lt"
394 | ":time:le"
395 | ":time:gt"
396 | ":time:ge"
397 | ":duration:lt"
398 | ":duration:le"
399 | ":duration:gt"
400 | ":duration:ge"
401 ) =>
402 {
403 let cmp_op = match self.ir.resolve_name(predicate) {
404 ":lt" | ":time:lt" | ":duration:lt" => CmpOp::Lt,
405 ":le" | ":time:le" | ":duration:le" => CmpOp::Le,
406 ":gt" | ":time:gt" | ":duration:gt" => CmpOp::Gt,
407 ":ge" | ":time:ge" | ":duration:ge" => CmpOp::Ge,
408 _ => unreachable!(),
409 };
410 if args.len() != 2 {
411 return Err(anyhow!("Comparison predicate requires exactly 2 arguments"));
412 }
413 let body = self.plan_join_sequence(premises, bound_vars, continuation)?;
414 self.with_eval(args[0], |this, left_op| {
415 this.with_eval(args[1], |_this, right_op| {
416 Ok(Op::Filter {
417 cond: Condition::Cmp {
418 op: cmp_op,
419 left: left_op.clone(),
420 right: right_op,
421 },
422 body: Box::new(body),
423 })
424 })
425 })
426 }
427 Inst::Atom { predicate, args }
428 if matches!(
429 self.ir.resolve_name(predicate),
430 ":string:starts_with"
431 | ":string:ends_with"
432 | ":string:contains"
433 | ":match_prefix"
434 ) =>
435 {
436 if args.len() != 2 {
437 return Err(anyhow!("Built-in predicate requires exactly 2 arguments"));
438 }
439 let body = self.plan_join_sequence(premises, bound_vars, continuation)?;
440 self.with_eval(args[0], |this, left_op| {
441 this.with_eval(args[1], |_this, right_op| {
442 Ok(Op::Filter {
443 cond: Condition::Call {
444 function: predicate,
445 args: vec![left_op.clone(), right_op],
446 },
447 body: Box::new(body),
448 })
449 })
450 })
451 }
452 Inst::Atom { predicate, args } if self.ir.resolve_name(predicate) == ":match_field" => {
453 if args.len() != 3 {
454 return Err(anyhow!(":match_field requires exactly 3 arguments"));
455 }
456 let field_name_id = match self.ir.get(args[1]) {
457 Inst::Name(n) => *n,
458 _ => {
459 return Err(anyhow!(
460 ":match_field second argument must be a name constant"
461 ));
462 }
463 };
464 let unbound_var = match self.ir.get(args[2]) {
466 Inst::Var(v) if !bound_vars.contains(v) => Some(*v),
467 _ => None,
468 };
469 if let Some(out_var) = unbound_var {
470 bound_vars.insert(out_var);
471 let body = self.plan_join_sequence(premises, bound_vars, continuation)?;
472 self.with_eval(args[0], |_this, struct_op| {
473 Ok(Op::MatchField {
474 struct_op,
475 field: field_name_id,
476 var: out_var,
477 body: Box::new(body),
478 })
479 })
480 } else {
481 let fresh = self.fresh_var("mf");
483 let body = self.plan_join_sequence(premises, bound_vars, continuation)?;
484 self.with_eval(args[0], |this, struct_op| {
485 this.with_eval(args[2], |_this, expected_op| {
486 Ok(Op::MatchField {
487 struct_op,
488 field: field_name_id,
489 var: fresh,
490 body: Box::new(Op::Filter {
491 cond: Condition::Cmp {
492 op: CmpOp::Eq,
493 left: Operand::Var(fresh),
494 right: expected_op,
495 },
496 body: Box::new(body),
497 }),
498 })
499 })
500 })
501 }
502 }
503 Inst::Atom { predicate, args } if self.ir.resolve_name(predicate) == ":list:member" => {
504 if args.len() != 2 {
505 return Err(anyhow!(":list:member requires exactly 2 arguments"));
506 }
507 let unbound_elem_var = match self.ir.get(args[0]) {
509 Inst::Var(v) if !bound_vars.contains(v) => Some(*v),
510 _ => None,
511 };
512 if let Some(elem_var) = unbound_elem_var {
513 bound_vars.insert(elem_var);
514 let body = self.plan_join_sequence(premises, bound_vars, continuation)?;
515 self.with_eval(args[1], |_this, list_op| {
516 Ok(Op::IterateList {
517 source: list_op,
518 var: elem_var,
519 body: Box::new(body),
520 })
521 })
522 } else {
523 let fresh = self.fresh_var("listmem");
525 let body = self.plan_join_sequence(premises, bound_vars, continuation)?;
526 self.with_eval(args[1], |this, list_op| {
527 this.with_eval(args[0], |_this, elem_op| {
528 Ok(Op::IterateList {
529 source: list_op,
530 var: fresh,
531 body: Box::new(Op::Filter {
532 cond: Condition::Cmp {
533 op: CmpOp::Eq,
534 left: Operand::Var(fresh),
535 right: elem_op,
536 },
537 body: Box::new(body),
538 }),
539 })
540 })
541 })
542 }
543 }
544 Inst::Atom { predicate, args } => {
545 if self.hash_join
556 && self.delta_pred.is_none()
557 && !premises.is_empty()
558 && let Some(op) =
559 self.try_plan_hash_join(predicate, &args, &mut premises, bound_vars)?
560 {
561 let body_op = self.plan_join_sequence(premises, bound_vars, continuation)?;
562 return Ok(splice_hash_join_body(op, body_op));
563 }
564
565 let mut scan_vars = Vec::new();
566 let mut new_bindings = Vec::new();
567
568 let mut index_lookup: Option<(usize, Operand)> = None;
570
571 for (i, arg) in args.iter().enumerate() {
572 let arg_inst = self.ir.get(*arg).clone();
573 match arg_inst {
574 Inst::Var(v) if bound_vars.contains(&v) => {
575 if index_lookup.is_none() {
576 index_lookup = Some((i, Operand::Var(v)));
577 }
578 }
579 Inst::Number(n) => {
580 if index_lookup.is_none() {
581 index_lookup =
582 Some((i, Operand::Const(physical::Constant::Number(n))));
583 }
584 }
585 Inst::String(s) => {
586 if index_lookup.is_none() {
587 index_lookup =
588 Some((i, Operand::Const(physical::Constant::String(s))));
589 }
590 }
591 Inst::Name(n) => {
592 if index_lookup.is_none() {
593 index_lookup =
594 Some((i, Operand::Const(physical::Constant::Name(n))));
595 }
596 }
597 Inst::Float(fl) => {
598 if index_lookup.is_none() {
599 index_lookup =
600 Some((i, Operand::Const(physical::Constant::Float(fl))));
601 }
602 }
603 Inst::Time(t) => {
604 if index_lookup.is_none() {
605 index_lookup =
606 Some((i, Operand::Const(physical::Constant::Time(t))));
607 }
608 }
609 Inst::Duration(d) if index_lookup.is_none() => {
610 index_lookup =
611 Some((i, Operand::Const(physical::Constant::Duration(d))));
612 }
613 _ => {}
614 }
615 }
616
617 for arg in &args {
618 if let Inst::Var(v) = self.ir.get(*arg)
619 && !bound_vars.contains(v)
620 {
621 scan_vars.push(*v);
622 new_bindings.push(*v);
623 continue;
624 }
625 let tmp = self.fresh_var("scan");
626 scan_vars.push(tmp);
627 new_bindings.push(tmp);
628 }
629
630 for v in &new_bindings {
631 bound_vars.insert(*v);
632 }
633
634 let body = self.plan_join_sequence(premises, bound_vars, continuation)?;
635 let wrapped_body = self.apply_constraints(&args, &scan_vars, body)?;
636
637 let source = if let Some((col_idx, key)) = index_lookup {
638 DataSource::IndexLookup {
639 relation: predicate,
640 col_idx,
641 key,
642 vars: scan_vars,
643 }
644 } else if Some(predicate) == self.delta_pred {
645 DataSource::ScanDelta {
646 relation: predicate,
647 vars: scan_vars,
648 }
649 } else {
650 DataSource::Scan {
651 relation: predicate,
652 vars: scan_vars,
653 }
654 };
655 Ok(Op::Iterate {
656 source,
657 body: Box::new(wrapped_body),
658 })
659 }
660 Inst::Eq(l, r) => {
661 let binding = self
666 .eq_as_binding(l, r, bound_vars)
667 .or_else(|| self.eq_as_binding(r, l, bound_vars));
668 if let Some((var, expr_inst)) = binding {
669 self.inst_to_expr(expr_inst, |planner, expr| {
670 bound_vars.insert(var);
671 let body =
672 planner.plan_join_sequence(premises, bound_vars, continuation)?;
673 Ok(Op::Let {
674 var,
675 expr,
676 body: Box::new(body),
677 })
678 })
679 } else {
680 let body = self.plan_join_sequence(premises, bound_vars, continuation)?;
681 self.wrap_eq_check(l, r, body)
682 }
683 }
684 Inst::Ineq(l, r) => {
685 let body = self.plan_join_sequence(premises, bound_vars, continuation)?;
686 self.with_eval(l, |this, left_op| {
687 this.with_eval(r, |_this, right_op| {
688 Ok(Op::Filter {
689 cond: Condition::Cmp {
690 op: CmpOp::Neq,
691 left: left_op.clone(),
692 right: right_op,
693 },
694 body: Box::new(body),
695 })
696 })
697 })
698 }
699 Inst::NegAtom(inner) => {
700 let inner_inst = self.ir.get(inner).clone();
701 if let Inst::Atom { predicate, args } = inner_inst {
702 let pred_name = self.ir.resolve_name(predicate).to_string();
707 match pred_name.as_str() {
708 ":list:member" => {
709 if args.len() != 2 {
710 return Err(anyhow!(":list:member requires exactly 2 arguments"));
711 }
712 self.require_bound_args(&args, bound_vars, "!:list:member")?;
713 let body =
714 self.plan_join_sequence(premises, bound_vars, continuation)?;
715 return self.with_eval(args[0], |this, elem_op| {
716 this.with_eval(args[1], |_this, list_op| {
717 Ok(Op::Filter {
718 cond: Condition::Not(Box::new(Condition::Call {
719 function: predicate,
720 args: vec![elem_op, list_op],
721 })),
722 body: Box::new(body),
723 })
724 })
725 });
726 }
727 ":match_field" => {
728 if args.len() != 3 {
729 return Err(anyhow!(":match_field requires exactly 3 arguments"));
730 }
731 let field_id = match self.ir.get(args[1]) {
732 Inst::Name(n) => *n,
733 _ => {
734 return Err(anyhow!(
735 ":match_field second argument must be a name constant"
736 ));
737 }
738 };
739 self.require_bound_args(&args, bound_vars, "!:match_field")?;
740 let field_op = Operand::Const(physical::Constant::Name(field_id));
741 let body =
742 self.plan_join_sequence(premises, bound_vars, continuation)?;
743 return self.with_eval(args[0], |this, struct_op| {
744 this.with_eval(args[2], |_this, value_op| {
745 Ok(Op::Filter {
746 cond: Condition::Not(Box::new(Condition::Call {
747 function: predicate,
748 args: vec![struct_op, field_op.clone(), value_op],
749 })),
750 body: Box::new(body),
751 })
752 })
753 });
754 }
755 ":match_prefix"
756 | ":string:starts_with"
757 | ":string:ends_with"
758 | ":string:contains" => {
759 if args.len() != 2 {
760 return Err(anyhow!(
761 "Built-in predicate requires exactly 2 arguments"
762 ));
763 }
764 self.require_bound_args(&args, bound_vars, &format!("!{pred_name}"))?;
765 let body =
766 self.plan_join_sequence(premises, bound_vars, continuation)?;
767 return self.with_eval(args[0], |this, left_op| {
768 this.with_eval(args[1], |_this, right_op| {
769 Ok(Op::Filter {
770 cond: Condition::Not(Box::new(Condition::Call {
771 function: predicate,
772 args: vec![left_op.clone(), right_op],
773 })),
774 body: Box::new(body),
775 })
776 })
777 });
778 }
779 ":lt" | ":le" | ":gt" | ":ge" | ":time:lt" | ":time:le" | ":time:gt"
780 | ":time:ge" | ":duration:lt" | ":duration:le" | ":duration:gt"
781 | ":duration:ge" => {
782 if args.len() != 2 {
783 return Err(anyhow!(
784 "Comparison predicate requires exactly 2 arguments"
785 ));
786 }
787 let cmp_op = match pred_name.as_str() {
788 ":lt" | ":time:lt" | ":duration:lt" => CmpOp::Lt,
789 ":le" | ":time:le" | ":duration:le" => CmpOp::Le,
790 ":gt" | ":time:gt" | ":duration:gt" => CmpOp::Gt,
791 ":ge" | ":time:ge" | ":duration:ge" => CmpOp::Ge,
792 _ => unreachable!(),
793 };
794 self.require_bound_args(&args, bound_vars, &format!("!{pred_name}"))?;
795 let body =
796 self.plan_join_sequence(premises, bound_vars, continuation)?;
797 return self.with_eval(args[0], |this, left_op| {
798 this.with_eval(args[1], |_this, right_op| {
799 Ok(Op::Filter {
800 cond: Condition::Not(Box::new(Condition::Cmp {
801 op: cmp_op,
802 left: left_op.clone(),
803 right: right_op,
804 })),
805 body: Box::new(body),
806 })
807 })
808 });
809 }
810 other if other.starts_with(':') => {
811 return Err(anyhow!(
812 "negation of built-in predicate '{other}' is not supported"
813 ));
814 }
815 _ => {}
816 }
817 let body = self.plan_join_sequence(premises, bound_vars, continuation)?;
818 let mut neg_args = Vec::new();
819 for arg in &args {
820 let arg_inst = self.ir.get(*arg).clone();
821 match arg_inst {
822 Inst::Var(v) => neg_args.push(Operand::Var(v)),
823 Inst::Number(n) => {
824 neg_args.push(Operand::Const(physical::Constant::Number(n)))
825 }
826 Inst::String(s) => {
827 neg_args.push(Operand::Const(physical::Constant::String(s)))
828 }
829 Inst::Name(n) => {
830 neg_args.push(Operand::Const(physical::Constant::Name(n)))
831 }
832 Inst::Float(fl) => {
833 neg_args.push(Operand::Const(physical::Constant::Float(fl)))
834 }
835 Inst::Time(t) => {
836 neg_args.push(Operand::Const(physical::Constant::Time(t)))
837 }
838 Inst::Duration(d) => {
839 neg_args.push(Operand::Const(physical::Constant::Duration(d)))
840 }
841 _ => return Err(anyhow!("Complex expression in negated atom")),
842 }
843 }
844 Ok(Op::Filter {
845 cond: Condition::Negation {
846 relation: predicate,
847 args: neg_args,
848 },
849 body: Box::new(body),
850 })
851 } else {
852 Err(anyhow!("NegAtom wraps non-atom"))
853 }
854 }
855 _ => Err(anyhow!("Unsupported premise type: {:?}", inst)),
856 }
857 }
858
859 fn require_bound_args(
863 &self,
864 args: &[InstId],
865 bound_vars: &FxHashSet<NameId>,
866 context: &str,
867 ) -> Result<()> {
868 for arg in args {
869 if let Inst::Var(v) = self.ir.get(*arg)
870 && !bound_vars.contains(v)
871 {
872 return Err(anyhow!(
873 "{context}: all arguments must be bound by earlier premises"
874 ));
875 }
876 }
877 Ok(())
878 }
879
880 fn apply_constraints(
881 &mut self,
882 args: &[InstId],
883 scan_vars: &[NameId],
884 mut body: Op,
885 ) -> Result<Op> {
886 for (i, arg) in args.iter().enumerate().rev() {
887 let scan_var = scan_vars[i];
888 let arg_inst = self.ir.get(*arg).clone();
889 match arg_inst {
890 Inst::Var(v) => {
891 if v == scan_var {
892 continue;
893 }
894 body = Op::Filter {
895 cond: Condition::Cmp {
896 op: CmpOp::Eq,
897 left: Operand::Var(scan_var),
898 right: Operand::Var(v),
899 },
900 body: Box::new(body),
901 };
902 }
903 _ => {
904 body = self.wrap_eval_check(*arg, Operand::Var(scan_var), body)?;
905 }
906 }
907 }
908 Ok(body)
909 }
910
911 fn eq_as_binding(
914 &self,
915 lhs: InstId,
916 rhs: InstId,
917 bound_vars: &FxHashSet<NameId>,
918 ) -> Option<(NameId, InstId)> {
919 if let Inst::Var(v) = self.ir.get(lhs)
920 && !bound_vars.contains(v)
921 {
922 return Some((*v, rhs));
923 }
924 None
925 }
926
927 fn wrap_eq_check(&mut self, l: InstId, r: InstId, body: Op) -> Result<Op> {
928 self.with_eval(l, |this, op_l| {
929 this.with_eval(r, |_this, op_r| {
930 Ok(Op::Filter {
931 cond: Condition::Cmp {
932 op: CmpOp::Eq,
933 left: op_l,
934 right: op_r,
935 },
936 body: Box::new(body),
937 })
938 })
939 })
940 }
941
942 fn wrap_eval_check(&mut self, inst: InstId, target: Operand, body: Op) -> Result<Op> {
943 self.with_eval(inst, |_this, op| {
944 Ok(Op::Filter {
945 cond: Condition::Cmp {
946 op: CmpOp::Eq,
947 left: target,
948 right: op,
949 },
950 body: Box::new(body),
951 })
952 })
953 }
954
955 fn with_eval<F>(&mut self, inst: InstId, f: F) -> Result<Op>
956 where
957 F: FnOnce(&mut Self, Operand) -> Result<Op>,
958 {
959 let i = self.ir.get(inst).clone();
960 match i {
961 Inst::Var(v) => f(self, Operand::Var(v)),
962 Inst::String(s) => f(self, Operand::Const(physical::Constant::String(s))),
963 Inst::Number(n) => f(self, Operand::Const(physical::Constant::Number(n))),
964 Inst::Name(n) => f(self, Operand::Const(physical::Constant::Name(n))),
965 Inst::Float(fl) => f(self, Operand::Const(physical::Constant::Float(fl))),
966 Inst::Time(t) => f(self, Operand::Const(physical::Constant::Time(t))),
967 Inst::Duration(d) => f(self, Operand::Const(physical::Constant::Duration(d))),
968 Inst::ApplyFn { function, args } => self.with_eval_args(
969 &args,
970 0,
971 Vec::new(),
972 Box::new(|this, ops| {
973 let tmp = this.fresh_var("call");
974 let inner = f(this, Operand::Var(tmp))?;
975 Ok(Op::Let {
976 var: tmp,
977 expr: Expr::Call {
978 function,
979 args: ops,
980 },
981 body: Box::new(inner),
982 })
983 }),
984 ),
985 Inst::List(args) => {
987 let fn_name = self.ir.intern_name("fn:list".to_string());
988 self.with_eval_args(
989 &args,
990 0,
991 Vec::new(),
992 Box::new(|this, ops| {
993 let tmp = this.fresh_var("list");
994 let inner = f(this, Operand::Var(tmp))?;
995 Ok(Op::Let {
996 var: tmp,
997 expr: Expr::Call {
998 function: fn_name,
999 args: ops,
1000 },
1001 body: Box::new(inner),
1002 })
1003 }),
1004 )
1005 }
1006 Inst::Map { keys, values } => {
1007 let mut interleaved = Vec::with_capacity(keys.len() + values.len());
1009 for (k, v) in keys.iter().zip(values.iter()) {
1010 interleaved.push(*k);
1011 interleaved.push(*v);
1012 }
1013 let fn_name = self.ir.intern_name("fn:map".to_string());
1014 self.with_eval_args(
1015 &interleaved,
1016 0,
1017 Vec::new(),
1018 Box::new(|this, ops| {
1019 let tmp = this.fresh_var("map");
1020 let inner = f(this, Operand::Var(tmp))?;
1021 Ok(Op::Let {
1022 var: tmp,
1023 expr: Expr::Call {
1024 function: fn_name,
1025 args: ops,
1026 },
1027 body: Box::new(inner),
1028 })
1029 }),
1030 )
1031 }
1032 Inst::Struct { fields, values } => {
1033 let mut interleaved = Vec::with_capacity(fields.len() + values.len());
1035 for (field, val) in fields.iter().zip(values.iter()) {
1036 let name_inst = self.ir.add_inst(Inst::Name(*field));
1038 interleaved.push(name_inst);
1039 interleaved.push(*val);
1040 }
1041 let fn_name = self.ir.intern_name("fn:struct".to_string());
1042 self.with_eval_args(
1043 &interleaved,
1044 0,
1045 Vec::new(),
1046 Box::new(|this, ops| {
1047 let tmp = this.fresh_var("struct");
1048 let inner = f(this, Operand::Var(tmp))?;
1049 Ok(Op::Let {
1050 var: tmp,
1051 expr: Expr::Call {
1052 function: fn_name,
1053 args: ops,
1054 },
1055 body: Box::new(inner),
1056 })
1057 }),
1058 )
1059 }
1060 _ => Err(anyhow!("Unsupported expression in evaluation")),
1061 }
1062 }
1063
1064 fn inst_to_expr<F>(&mut self, inst: InstId, f: F) -> Result<Op>
1065 where
1066 F: FnOnce(&mut Self, Expr) -> Result<Op>,
1067 {
1068 let i = self.ir.get(inst).clone();
1069 match i {
1070 Inst::ApplyFn { function, args } => self.with_eval_args(
1071 &args,
1072 0,
1073 Vec::new(),
1074 Box::new(|this, ops| {
1075 f(
1076 this,
1077 Expr::Call {
1078 function,
1079 args: ops,
1080 },
1081 )
1082 }),
1083 ),
1084 _ => self.with_eval(inst, |this, op| f(this, Expr::Value(op))),
1085 }
1086 }
1087
1088 fn with_eval_args(
1089 &mut self,
1090 args: &[InstId],
1091 index: usize,
1092 mut acc: Vec<Operand>,
1093 f: Box<dyn FnOnce(&mut Self, Vec<Operand>) -> Result<Op> + '_>,
1094 ) -> Result<Op> {
1095 if index >= args.len() {
1096 return f(self, acc);
1097 }
1098 self.with_eval(args[index], |this, op| {
1099 acc.push(op);
1100 this.with_eval_args(args, index + 1, acc, f)
1101 })
1102 }
1103
1104 fn plan_head_insert(
1105 &mut self,
1106 head: InstId,
1107 _bound_vars: &mut FxHashSet<NameId>,
1108 ) -> Result<Op> {
1109 let inst = self.ir.get(head).clone();
1110 if let Inst::Atom { predicate, args } = inst {
1111 self.with_eval_args(
1112 &args,
1113 0,
1114 Vec::new(),
1115 Box::new(|_this, ops| {
1116 Ok(Op::Insert {
1117 relation: predicate,
1118 args: ops,
1119 })
1120 }),
1121 )
1122 } else {
1123 Err(anyhow!("Head must be an atom"))
1124 }
1125 }
1126
1127 fn get_transform_app_args(&self, t_id: InstId) -> Result<Vec<InstId>> {
1128 if let Inst::Transform { app, .. } = self.ir.get(t_id)
1129 && let Inst::ApplyFn { args, .. } = self.ir.get(*app)
1130 {
1131 return Ok(args.clone());
1132 }
1133 Err(anyhow!("Invalid transform structure"))
1134 }
1135
1136 fn try_parse_aggregate(&mut self, t_id: InstId) -> Result<Option<Aggregate>> {
1137 let inst = self.ir.get(t_id).clone();
1138 if let Inst::Transform {
1139 var: Some(var),
1140 app,
1141 } = inst
1142 && let Inst::ApplyFn { function, args } = self.ir.get(app).clone()
1143 {
1144 let func_name = self.ir.resolve_name(function);
1145 if matches!(
1146 func_name,
1147 "fn:sum"
1148 | "fn:count"
1149 | "fn:max"
1150 | "fn:min"
1151 | "fn:collect"
1152 | "fn:collect_distinct"
1153 | "fn:float:sum"
1154 | "fn:float:max"
1155 | "fn:float:min"
1156 ) {
1157 let mut op_args = Vec::new();
1158 for arg in args {
1159 let arg_inst = self.ir.get(arg).clone();
1160 match arg_inst {
1161 Inst::Var(v) => op_args.push(Operand::Var(v)),
1162 Inst::Number(n) => {
1163 op_args.push(Operand::Const(physical::Constant::Number(n)))
1164 }
1165 Inst::Float(fl) => {
1166 op_args.push(Operand::Const(physical::Constant::Float(fl)))
1167 }
1168 _ => {
1169 return Err(anyhow!(
1170 "Complex expressions in aggregates not supported yet"
1171 ));
1172 }
1173 }
1174 }
1175 return Ok(Some(Aggregate {
1176 var,
1177 func: function,
1178 args: op_args,
1179 }));
1180 }
1181 }
1182 Ok(None)
1183 }
1184}