1use std::collections::{BTreeSet, HashMap};
8use std::fmt::{self, Write};
9use std::sync::Arc;
10
11#[cfg(all(test, feature = "resident-graph-tests"))]
12use std::cell::Cell;
13
14use xlog_core::{RelId, Result, ScalarType, Schema};
15use xlog_ir::{ConstValue, ExecutionPlan, Expr, JoinType, ProjectExpr, RirNode};
16
17const RESIDENT_GRAPH_MAX_INTERMEDIATE_ARITY: usize = 17;
18
19#[derive(Debug, Clone, Default)]
21pub struct ResidentGraphSchemaCatalog {
22 by_relation: HashMap<RelId, Vec<(String, Schema)>>,
23}
24
25impl ResidentGraphSchemaCatalog {
26 pub fn from_named_schemas(entries: impl IntoIterator<Item = (String, RelId, Schema)>) -> Self {
28 let mut by_relation: HashMap<RelId, Vec<(String, Schema)>> = HashMap::new();
29 for (name, relation, schema) in entries {
30 by_relation
31 .entry(relation)
32 .or_default()
33 .push((name, schema));
34 }
35 for aliases in by_relation.values_mut() {
36 aliases.sort_by(|left, right| left.0.cmp(&right.0));
37 aliases.dedup();
38 }
39 Self { by_relation }
40 }
41
42 fn descriptor(&self, relation: RelId) -> Option<String> {
43 let aliases = self.by_relation.get(&relation)?;
44 Some(
45 aliases
46 .iter()
47 .map(|(name, schema)| format!("{name}={schema:#?}"))
48 .collect::<Vec<_>>()
49 .join("|"),
50 )
51 }
52
53 pub(crate) fn schema(&self, relation: RelId) -> Option<&Schema> {
54 let aliases = self.by_relation.get(&relation)?;
55 let (_, first) = aliases.first()?;
56 aliases
57 .iter()
58 .all(|(_, schema)| schema == first)
59 .then_some(first)
60 }
61}
62
63#[derive(Debug, Clone, PartialEq, Eq)]
65pub enum ResidentGraphDeclineReason {
66 MissingScanSchema {
68 relation: RelId,
70 },
71 UnsupportedNode {
73 path: String,
75 node: &'static str,
77 },
78 UnsupportedJoin {
80 path: String,
82 join_type: JoinType,
84 },
85 FullStoreRequested,
88 NonOrdinaryPlan,
90 ImportedInputUnsupported {
93 relation: String,
95 },
96 SourceSetUncertified {
98 relation: String,
100 },
101 ConditionalGraphUnavailable {
103 detail: String,
105 },
106 WorkspaceUnbounded {
108 detail: String,
110 },
111}
112
113#[derive(Debug, Clone)]
115pub struct ResidentGraphRouteCertificate {
116 covered_route_descriptors: BTreeSet<String>,
117 declines: Vec<ResidentGraphDeclineReason>,
118 schema_catalog: ResidentGraphSchemaCatalog,
119 plan_fingerprint: u64,
120}
121
122#[derive(Debug, Clone)]
128pub struct ResidentGraphCertifiedPlan {
129 plan: Arc<ExecutionPlan>,
130 certificate: ResidentGraphRouteCertificate,
131}
132
133fn append_resident_expression_descriptor(descriptor: &mut String, expression: &Expr) {
134 fn schedule_binary<'a>(pending: &mut Vec<&'a Expr>, left: &'a Expr, right: &'a Expr) {
135 pending.push(right);
136 pending.push(left);
137 }
138
139 let mut pending = vec![expression];
140 while let Some(expression) = pending.pop() {
141 match expression {
142 Expr::Column(index) => write!(descriptor, "column:{index};").unwrap(),
143 Expr::Const(value) => {
144 descriptor.push_str("constant:");
145 match value {
146 ConstValue::U32(value) => write!(descriptor, "u32:{value};").unwrap(),
147 ConstValue::U64(value) => write!(descriptor, "u64:{value};").unwrap(),
148 ConstValue::I32(value) => write!(descriptor, "i32:{value};").unwrap(),
149 ConstValue::I64(value) => write!(descriptor, "i64:{value};").unwrap(),
150 ConstValue::F32(value) => {
151 write!(descriptor, "f32:{:08x};", value.to_bits()).unwrap()
152 }
153 ConstValue::F64(value) => {
154 write!(descriptor, "f64:{:016x};", value.to_bits()).unwrap()
155 }
156 ConstValue::Bool(value) => write!(descriptor, "bool:{value};").unwrap(),
157 ConstValue::Symbol(value) => {
158 write!(descriptor, "symbol:{}:", value.len()).unwrap();
159 descriptor.push_str(value);
160 descriptor.push(';');
161 }
162 }
163 }
164 Expr::Compare { left, op, right } => {
165 write!(descriptor, "compare:{op:?};").unwrap();
166 schedule_binary(&mut pending, left, right);
167 }
168 Expr::And(items) => {
169 write!(descriptor, "and:{};", items.len()).unwrap();
170 pending.extend(items.iter().rev());
171 }
172 Expr::Or(items) => {
173 write!(descriptor, "or:{};", items.len()).unwrap();
174 pending.extend(items.iter().rev());
175 }
176 Expr::Not(inner) => {
177 descriptor.push_str("not;");
178 pending.push(inner);
179 }
180 Expr::Add(left, right) => {
181 descriptor.push_str("add;");
182 schedule_binary(&mut pending, left, right);
183 }
184 Expr::Sub(left, right) => {
185 descriptor.push_str("subtract;");
186 schedule_binary(&mut pending, left, right);
187 }
188 Expr::Mul(left, right) => {
189 descriptor.push_str("multiply;");
190 schedule_binary(&mut pending, left, right);
191 }
192 Expr::Div(left, right) => {
193 descriptor.push_str("divide;");
194 schedule_binary(&mut pending, left, right);
195 }
196 Expr::Mod(left, right) => {
197 descriptor.push_str("modulo;");
198 schedule_binary(&mut pending, left, right);
199 }
200 Expr::Abs(inner) => {
201 descriptor.push_str("absolute_value;");
202 pending.push(inner);
203 }
204 Expr::Min(left, right) => {
205 descriptor.push_str("minimum;");
206 schedule_binary(&mut pending, left, right);
207 }
208 Expr::Max(left, right) => {
209 descriptor.push_str("maximum;");
210 schedule_binary(&mut pending, left, right);
211 }
212 Expr::Pow(left, right) => {
213 descriptor.push_str("power;");
214 schedule_binary(&mut pending, left, right);
215 }
216 Expr::Cast(inner, target) => {
217 write!(descriptor, "cast:{target:?};").unwrap();
218 pending.push(inner);
219 }
220 Expr::Conditional {
221 condition,
222 then_expr,
223 else_expr,
224 } => {
225 descriptor.push_str("conditional;");
226 pending.push(else_expr);
227 pending.push(then_expr);
228 pending.push(condition);
229 }
230 }
231 }
232}
233
234fn resident_project_columns_descriptor(columns: &[ProjectExpr]) -> String {
235 let mut descriptor = format!("column_count={};", columns.len());
236 for column in columns {
237 match column {
238 ProjectExpr::Column(index) => write!(descriptor, "column:{index};").unwrap(),
239 ProjectExpr::Computed(expression, scalar_type) => {
240 write!(descriptor, "computed:{scalar_type:?};").unwrap();
241 append_resident_expression_descriptor(&mut descriptor, expression);
242 }
243 }
244 }
245 descriptor
246}
247
248fn resident_node_descriptor(node: &RirNode) -> String {
249 match node {
250 RirNode::Unit => "unit".to_owned(),
251 RirNode::Scan { rel } => format!("scan;relation={}", rel.0),
252 RirNode::Filter { predicate, .. } => {
253 let mut descriptor = "filter;predicate=".to_owned();
254 append_resident_expression_descriptor(&mut descriptor, predicate);
255 descriptor
256 }
257 RirNode::Project { columns, .. } => {
258 format!("project;{}", resident_project_columns_descriptor(columns))
259 }
260 RirNode::Join {
261 left_keys,
262 right_keys,
263 join_type,
264 ..
265 } => format!(
266 "join;left_keys={left_keys:?};right_keys={right_keys:?};join_type={join_type:?}"
267 ),
268 RirNode::ChainJoin {
269 left_key,
270 right_key,
271 output_columns,
272 ..
273 } => format!(
274 "chain_join;left_key={left_key};right_key={right_key};output_{}",
275 resident_project_columns_descriptor(output_columns)
276 ),
277 RirNode::GroupBy { key_cols, aggs, .. } => {
278 format!("group_by;key_cols={key_cols:?};aggregates={aggs:?}")
279 }
280 RirNode::Union { inputs } => format!("union;input_count={}", inputs.len()),
281 RirNode::Distinct { key_cols, .. } => format!("distinct;key_cols={key_cols:?}"),
282 RirNode::Diff { .. } => "difference".to_owned(),
283 RirNode::Fixpoint {
284 scc_id,
285 delta_rel,
286 full_rel,
287 ..
288 } => format!(
289 "fixpoint;scc_id={scc_id};delta_relation={};full_relation={}",
290 delta_rel.0, full_rel.0
291 ),
292 RirNode::MultiWayJoin {
293 inputs,
294 slot_vars,
295 output_columns,
296 plan,
297 var_order,
298 ..
299 } => format!(
300 "multi_way_join;input_count={};slot_variables={slot_vars:?};output_{};plan={plan:?};variable_order={var_order:?}",
301 inputs.len(),
302 resident_project_columns_descriptor(output_columns)
303 ),
304 RirNode::TensorMaskedJoin {
305 mask_name,
306 schema_size,
307 left_keys,
308 right_keys,
309 rel_index,
310 head_rel_name,
311 head_rel_id,
312 max_active_rules,
313 head_projection,
314 } => format!(
315 "tensor_masked_join;mask={mask_name:?};schema_size={schema_size};left_keys={left_keys:?};right_keys={right_keys:?};relation_index={rel_index:?};head_relation_name={head_rel_name:?};head_relation_id={};max_active_rules={max_active_rules};head_projection={head_projection:?}",
316 head_rel_id.0
317 ),
318 }
319}
320
321impl ResidentGraphCertifiedPlan {
322 pub fn inspect(plan: Arc<ExecutionPlan>, catalog: &ResidentGraphSchemaCatalog) -> Result<Self> {
324 let certificate = ResidentGraphRouteCertificate::inspect(&plan, catalog)?;
325 Ok(Self { plan, certificate })
326 }
327
328 pub fn plan(&self) -> &ExecutionPlan {
330 &self.plan
331 }
332
333 pub fn certificate(&self) -> &ResidentGraphRouteCertificate {
335 &self.certificate
336 }
337}
338
339#[cfg(all(test, feature = "resident-graph-tests"))]
340thread_local! {
341 static RESIDENT_ROUTE_INSPECTION_COUNT: Cell<usize> = const { Cell::new(0) };
342}
343
344#[cfg(all(test, feature = "resident-graph-tests"))]
345pub(crate) fn reset_resident_route_inspection_count() {
346 RESIDENT_ROUTE_INSPECTION_COUNT.with(|count| count.set(0));
347}
348
349#[cfg(all(test, feature = "resident-graph-tests"))]
350pub(crate) fn resident_route_inspection_count() -> usize {
351 RESIDENT_ROUTE_INSPECTION_COUNT.with(Cell::get)
352}
353
354impl ResidentGraphRouteCertificate {
355 pub fn inspect(plan: &ExecutionPlan, catalog: &ResidentGraphSchemaCatalog) -> Result<Self> {
357 #[cfg(all(test, feature = "resident-graph-tests"))]
358 RESIDENT_ROUTE_INSPECTION_COUNT.with(|count| count.set(count.get() + 1));
359 let mut certificate = Self {
360 covered_route_descriptors: BTreeSet::new(),
361 declines: Vec::new(),
362 schema_catalog: catalog.clone(),
363 plan_fingerprint: 0,
364 };
365 certificate
366 .covered_route_descriptors
367 .insert(format!("plan;scc_count={}", plan.sccs.len()));
368 for (scc_index, scc) in plan.sccs.iter().enumerate() {
369 certificate.covered_route_descriptors.insert(format!(
370 "plan;scc={scc_index};id={};recursive={};predicate_count={}",
371 scc.id,
372 scc.is_recursive,
373 scc.predicates.len()
374 ));
375 for (predicate_index, predicate) in scc.predicates.iter().enumerate() {
376 certificate.covered_route_descriptors.insert(format!(
377 "plan;scc={scc_index};predicate={predicate_index};name={predicate:?}"
378 ));
379 }
380 }
381 certificate
382 .covered_route_descriptors
383 .insert(format!("plan;stratum_count={}", plan.strata.len()));
384 for (stratum_index, stratum) in plan.strata.iter().enumerate() {
385 certificate.covered_route_descriptors.insert(format!(
386 "plan;stratum={stratum_index};id={};scc_count={}",
387 stratum.id,
388 stratum.sccs.len()
389 ));
390 for (scc_position, scc_id) in stratum.sccs.iter().enumerate() {
391 certificate.covered_route_descriptors.insert(format!(
392 "plan;stratum={stratum_index};scc={scc_position};id={scc_id}"
393 ));
394 }
395 }
396 certificate.covered_route_descriptors.insert(format!(
397 "plan;rules_by_scc_count={}",
398 plan.rules_by_scc.len()
399 ));
400 certificate.covered_route_descriptors.insert(format!(
401 "plan;generated_query_rule_count={}",
402 plan.generated_query_rules.len()
403 ));
404 for (position, provenance) in plan.generated_query_rules.iter().enumerate() {
405 certificate.covered_route_descriptors.insert(format!(
406 "plan;generated_query_rule={position};query_index={};scc={};rule={}",
407 provenance.query_index, provenance.scc_index, provenance.rule_index
408 ));
409 }
410 for (scc_index, rules) in plan.rules_by_scc.iter().enumerate() {
411 certificate.covered_route_descriptors.insert(format!(
412 "plan;rules_by_scc={scc_index};rule_count={}",
413 rules.len()
414 ));
415 for (rule_index, rule) in rules.iter().enumerate() {
416 certificate.covered_route_descriptors.insert(format!(
417 "plan;rules_by_scc={scc_index};rule={rule_index};head={:?};meta={:?}",
418 rule.head, rule.meta
419 ));
420 }
421 }
422 certificate
423 .covered_route_descriptors
424 .insert(format!("plan;est_memory_peak={}", plan.est_memory_peak));
425 certificate
426 .covered_route_descriptors
427 .insert(format!("plan;rel_arity_count={}", plan.rel_arities.len()));
428 let mut rel_arities = plan
429 .rel_arities
430 .iter()
431 .map(|(&relation, &arity)| (relation, arity))
432 .collect::<Vec<_>>();
433 rel_arities.sort_unstable_by_key(|(relation, _)| *relation);
434 for (position, (relation, arity)) in rel_arities.into_iter().enumerate() {
435 certificate.covered_route_descriptors.insert(format!(
436 "plan;rel_arity={position};relation={};arity={arity}",
437 relation.0
438 ));
439 }
440 for (scc_index, scc) in plan.sccs.iter().enumerate() {
441 let Some(rules) = plan.rules_by_scc.get(scc_index) else {
442 certificate
443 .declines
444 .push(ResidentGraphDeclineReason::UnsupportedNode {
445 path: format!("scc={scc_index}"),
446 node: "missing_rule_vector",
447 });
448 continue;
449 };
450 for (rule_index, rule) in rules.iter().enumerate() {
451 certificate.visit(
452 catalog,
453 scc_index,
454 rule_index,
455 scc.is_recursive,
456 &rule.body,
457 "primary/root",
458 );
459 let identity = format!(
460 "scc={scc_index};rule={rule_index};head={};schema={:#?}",
461 rule.head, rule.meta.schema
462 );
463 certificate
464 .covered_route_descriptors
465 .insert(format!("{identity};implicit=rule_result_union"));
466 certificate
467 .covered_route_descriptors
468 .insert(format!("{identity};implicit=full_row_dedup"));
469 if scc.is_recursive {
470 certificate
471 .covered_route_descriptors
472 .insert(format!("{identity};implicit=novel_tuple_difference"));
473 certificate
474 .covered_route_descriptors
475 .insert(format!("{identity};implicit=device_convergence"));
476 }
477 }
478 }
479 certificate.plan_fingerprint = stable_descriptor_fingerprint(
480 certificate
481 .covered_route_descriptors
482 .iter()
483 .map(String::as_bytes),
484 );
485 Ok(certificate)
486 }
487
488 pub fn is_supported(&self) -> bool {
490 self.declines.is_empty()
491 }
492
493 pub fn covered_route_descriptors(&self) -> &BTreeSet<String> {
495 &self.covered_route_descriptors
496 }
497
498 pub fn declines(&self) -> &[ResidentGraphDeclineReason] {
500 &self.declines
501 }
502
503 pub fn plan_fingerprint(&self) -> u64 {
506 self.plan_fingerprint
507 }
508
509 pub fn matches_plan(&self, plan: &ExecutionPlan) -> Result<bool> {
512 let inspected = Self::inspect(plan, &self.schema_catalog)?;
513 Ok(inspected.plan_fingerprint == self.plan_fingerprint
514 && inspected.covered_route_descriptors == self.covered_route_descriptors
515 && inspected.declines == self.declines)
516 }
517
518 pub(crate) fn schema_for(&self, relation: RelId) -> Option<&Schema> {
519 self.schema_catalog.schema(relation)
520 }
521
522 pub(crate) fn node_schema(&self, node: &RirNode) -> Option<Schema> {
523 node_schema(&self.schema_catalog, node)
524 }
525
526 fn visit(
527 &mut self,
528 catalog: &ResidentGraphSchemaCatalog,
529 scc_index: usize,
530 rule_index: usize,
531 recursive: bool,
532 node: &RirNode,
533 path: &str,
534 ) {
535 self.validate_node_route(catalog, node, path);
536 let scan_schema = match node {
537 RirNode::Scan { rel } => match catalog.descriptor(*rel) {
538 Some(descriptor) => descriptor,
539 None => {
540 self.declines
541 .push(ResidentGraphDeclineReason::MissingScanSchema { relation: *rel });
542 String::new()
543 }
544 },
545 _ => String::new(),
546 };
547 let node_descriptor = resident_node_descriptor(node);
548 self.covered_route_descriptors.insert(format!(
549 "scc={scc_index};rule={rule_index};recursive={recursive};path={path};node={node_descriptor};scan_schema={scan_schema}"
550 ));
551 match node {
552 RirNode::Unit | RirNode::Scan { .. } => {}
553 RirNode::Filter { input, .. }
554 | RirNode::Project { input, .. }
555 | RirNode::Distinct { input, .. } => self.visit(
556 catalog,
557 scc_index,
558 rule_index,
559 recursive,
560 input,
561 &format!("{path}/input"),
562 ),
563 RirNode::Join {
564 left,
565 right,
566 join_type,
567 ..
568 } => {
569 if !matches!(join_type, JoinType::Inner | JoinType::Semi) {
570 self.declines
571 .push(ResidentGraphDeclineReason::UnsupportedJoin {
572 path: path.to_string(),
573 join_type: *join_type,
574 });
575 }
576 self.visit(
577 catalog,
578 scc_index,
579 rule_index,
580 recursive,
581 left,
582 &format!("{path}/left"),
583 );
584 self.visit(
585 catalog,
586 scc_index,
587 rule_index,
588 recursive,
589 right,
590 &format!("{path}/right"),
591 );
592 }
593 RirNode::ChainJoin {
594 left,
595 right,
596 fallback,
597 ..
598 } => {
599 self.visit(
600 catalog,
601 scc_index,
602 rule_index,
603 recursive,
604 left,
605 &format!("{path}/primary/left"),
606 );
607 self.visit(
608 catalog,
609 scc_index,
610 rule_index,
611 recursive,
612 right,
613 &format!("{path}/primary/right"),
614 );
615 self.visit(
616 catalog,
617 scc_index,
618 rule_index,
619 recursive,
620 fallback,
621 &format!("{path}/alternative/captured_fallback"),
622 );
623 }
624 RirNode::Union { inputs } => {
625 for (index, input) in inputs.iter().enumerate() {
626 self.visit(
627 catalog,
628 scc_index,
629 rule_index,
630 recursive,
631 input,
632 &format!("{path}/input[{index}]"),
633 );
634 }
635 }
636 RirNode::Diff { left, right } => {
637 self.visit(
638 catalog,
639 scc_index,
640 rule_index,
641 recursive,
642 left,
643 &format!("{path}/left"),
644 );
645 self.visit(
646 catalog,
647 scc_index,
648 rule_index,
649 recursive,
650 right,
651 &format!("{path}/right"),
652 );
653 }
654 RirNode::Fixpoint {
655 base,
656 recursive: step,
657 ..
658 } => {
659 self.visit(
660 catalog,
661 scc_index,
662 rule_index,
663 recursive,
664 base,
665 &format!("{path}/base"),
666 );
667 self.visit(
668 catalog,
669 scc_index,
670 rule_index,
671 recursive,
672 step,
673 &format!("{path}/recursive"),
674 );
675 }
676 RirNode::MultiWayJoin {
677 inputs, fallback, ..
678 } => {
679 self.declines
680 .push(ResidentGraphDeclineReason::UnsupportedNode {
681 path: path.to_string(),
682 node: "multi_way_join",
683 });
684 for (index, input) in inputs.iter().enumerate() {
685 self.visit(
686 catalog,
687 scc_index,
688 rule_index,
689 recursive,
690 input,
691 &format!("{path}/primary/input[{index}]"),
692 );
693 }
694 self.visit(
695 catalog,
696 scc_index,
697 rule_index,
698 recursive,
699 fallback,
700 &format!("{path}/alternative/captured_fallback"),
701 );
702 }
703 RirNode::GroupBy { input, .. } => {
704 self.declines
705 .push(ResidentGraphDeclineReason::UnsupportedNode {
706 path: path.to_string(),
707 node: "group_by",
708 });
709 self.visit(
710 catalog,
711 scc_index,
712 rule_index,
713 recursive,
714 input,
715 &format!("{path}/input"),
716 );
717 }
718 RirNode::TensorMaskedJoin { .. } => {
719 self.declines
720 .push(ResidentGraphDeclineReason::UnsupportedNode {
721 path: path.to_string(),
722 node: "tensor_masked_join",
723 })
724 }
725 }
726 }
727
728 fn validate_node_route(
729 &mut self,
730 catalog: &ResidentGraphSchemaCatalog,
731 node: &RirNode,
732 path: &str,
733 ) {
734 let schema = node_schema(catalog, node);
735 let unsupported = schema
736 .as_ref()
737 .is_some_and(|schema| schema.arity() > RESIDENT_GRAPH_MAX_INTERMEDIATE_ARITY)
738 .then_some("intermediate_arity")
739 .or_else(|| {
740 schema
741 .as_ref()
742 .is_some_and(|schema| {
743 schema
744 .columns
745 .iter()
746 .any(|(_, scalar)| !resident_scalar_supported(*scalar))
747 })
748 .then_some("scalar_type")
749 })
750 .or_else(|| match node {
751 RirNode::Filter { input, predicate } => node_schema(catalog, input)
752 .filter(|schema| resident_predicate_supported(predicate, schema))
753 .is_none()
754 .then_some("filter_expression"),
755 RirNode::Project { input, columns } => node_schema(catalog, input)
756 .filter(|schema| resident_projection_supported(columns, schema))
757 .is_none()
758 .then_some("project_expression"),
759 RirNode::Join {
760 left,
761 right,
762 left_keys,
763 right_keys,
764 join_type: JoinType::Inner | JoinType::Semi,
765 } => {
766 let compatible = (|| {
767 let left_schema = node_schema(catalog, left)?;
768 let right_schema = node_schema(catalog, right)?;
769 if left_keys.len() != 1 || right_keys.len() != 1 {
770 return None;
771 }
772 let left_ty = left_schema.column_type(left_keys[0])?;
773 let right_ty = right_schema.column_type(right_keys[0])?;
774 (left_ty == right_ty && resident_scalar_supported(left_ty)).then_some(())
775 })()
776 .is_some();
777 (!compatible).then_some("join_key_layout")
778 }
779 RirNode::ChainJoin {
780 left,
781 right,
782 left_key,
783 right_key,
784 output_columns,
785 ..
786 } => {
787 let compatible = (|| {
788 let left_schema = node_schema(catalog, left)?;
789 let right_schema = node_schema(catalog, right)?;
790 let left_ty = left_schema.column_type(*left_key)?;
791 let right_ty = right_schema.column_type(*right_key)?;
792 if left_ty != right_ty || !resident_scalar_supported(left_ty) {
793 return None;
794 }
795 let joined = joined_schema(&left_schema, &right_schema);
796 resident_projection_supported(output_columns, &joined).then_some(())
797 })()
798 .is_some();
799 (!compatible).then_some("chain_join_layout")
800 }
801 RirNode::Distinct { input, key_cols } => node_schema(catalog, input)
802 .filter(|schema| key_cols.iter().copied().eq(0..schema.arity()))
803 .is_none()
804 .then_some("partial_row_distinct"),
805 _ => None,
806 });
807 if let Some(node) = unsupported {
808 self.declines
809 .push(ResidentGraphDeclineReason::UnsupportedNode {
810 path: path.to_string(),
811 node,
812 });
813 }
814 }
815}
816
817fn resident_scalar_supported(scalar: ScalarType) -> bool {
818 matches!(
819 scalar,
820 ScalarType::Symbol | ScalarType::U32 | ScalarType::U64
821 )
822}
823
824fn constant_scalar(value: &ConstValue) -> ScalarType {
825 match value {
826 ConstValue::U32(_) => ScalarType::U32,
827 ConstValue::U64(_) => ScalarType::U64,
828 ConstValue::I32(_) => ScalarType::I32,
829 ConstValue::I64(_) => ScalarType::I64,
830 ConstValue::F32(_) => ScalarType::F32,
831 ConstValue::F64(_) => ScalarType::F64,
832 ConstValue::Bool(_) => ScalarType::Bool,
833 ConstValue::Symbol(_) => ScalarType::Symbol,
834 }
835}
836
837fn scalar_expression_type(expression: &Expr, schema: &Schema) -> Option<ScalarType> {
838 match expression {
839 Expr::Column(index) => schema.column_type(*index),
840 Expr::Const(value) => Some(constant_scalar(value)),
841 _ => None,
842 }
843}
844
845fn resident_predicate_supported(expression: &Expr, schema: &Schema) -> bool {
846 match expression {
847 Expr::Compare { left, right, .. } => {
848 let Some(left_ty) = scalar_expression_type(left, schema) else {
849 return false;
850 };
851 let Some(right_ty) = scalar_expression_type(right, schema) else {
852 return false;
853 };
854 left_ty == right_ty && resident_scalar_supported(left_ty)
855 }
856 Expr::And(expressions) => {
857 !expressions.is_empty()
858 && expressions
859 .iter()
860 .all(|expression| resident_predicate_supported(expression, schema))
861 }
862 _ => false,
863 }
864}
865
866fn resident_projection_supported(expressions: &[ProjectExpr], schema: &Schema) -> bool {
867 expressions.iter().all(|expression| match expression {
868 ProjectExpr::Column(index) => schema
869 .column_type(*index)
870 .is_some_and(resident_scalar_supported),
871 ProjectExpr::Computed(Expr::Const(value), declared) => {
872 *declared == constant_scalar(value) && resident_scalar_supported(*declared)
873 }
874 _ => false,
875 })
876}
877
878fn joined_schema(left: &Schema, right: &Schema) -> Schema {
879 Schema::new(
880 left.columns
881 .iter()
882 .chain(&right.columns)
883 .enumerate()
884 .map(|(index, (_, ty))| (format!("column_{index}"), *ty))
885 .collect(),
886 )
887}
888
889pub(crate) fn node_schema(catalog: &ResidentGraphSchemaCatalog, node: &RirNode) -> Option<Schema> {
890 match node {
891 RirNode::Unit => Some(Schema::new(Vec::new())),
892 RirNode::Scan { rel } => catalog.schema(*rel).cloned(),
893 RirNode::Filter { input, .. } | RirNode::Distinct { input, .. } => {
894 node_schema(catalog, input)
895 }
896 RirNode::Project { input, columns } => {
897 let input = node_schema(catalog, input)?;
898 let output = columns
899 .iter()
900 .enumerate()
901 .map(|(index, expression)| {
902 let scalar = match expression {
903 ProjectExpr::Column(column) => input.column_type(*column),
904 ProjectExpr::Computed(_, scalar) => Some(*scalar),
905 };
906 scalar.map(|scalar| (format!("column_{index}"), scalar))
907 })
908 .collect::<Option<Vec<_>>>()?;
909 Some(Schema::new(output))
910 }
911 RirNode::Join {
912 left,
913 right,
914 join_type,
915 ..
916 } => {
917 let left = node_schema(catalog, left)?;
918 if matches!(join_type, JoinType::Semi | JoinType::Anti) {
919 Some(left)
920 } else {
921 Some(joined_schema(&left, &node_schema(catalog, right)?))
922 }
923 }
924 RirNode::ChainJoin { fallback, .. } | RirNode::MultiWayJoin { fallback, .. } => {
925 node_schema(catalog, fallback)
926 }
927 RirNode::Union { inputs } => inputs.first().and_then(|input| node_schema(catalog, input)),
928 RirNode::Diff { left, .. } => node_schema(catalog, left),
929 RirNode::Fixpoint { base, .. } => node_schema(catalog, base),
930 RirNode::GroupBy { .. } | RirNode::TensorMaskedJoin { .. } => None,
931 }
932}
933
934fn stable_descriptor_fingerprint<'a>(descriptors: impl IntoIterator<Item = &'a [u8]>) -> u64 {
935 let mut hash = 0xcbf2_9ce4_8422_2325u64;
936 for descriptor in descriptors {
937 for byte in (descriptor.len() as u64)
938 .to_le_bytes()
939 .iter()
940 .chain(descriptor)
941 {
942 hash ^= u64::from(*byte);
943 hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
944 }
945 }
946 hash
947}
948
949#[derive(Debug, Clone, PartialEq, Eq)]
951pub enum ResidentGraphDeviceStatus {
952 Success {
954 iterations: u32,
956 },
957 IterationLimit {
959 limit: u32,
961 completed: u32,
963 },
964 CapacityOverflow {
966 op_id: u32,
968 required: u64,
970 capacity: u64,
972 },
973 ResourceExhausted {
975 op_id: u32,
977 resource: &'static str,
979 required: u64,
981 capacity: u64,
983 },
984}
985
986#[derive(Debug, Clone, PartialEq, Eq)]
988pub enum ResidentGraphExecutionError {
989 Declined(ResidentGraphDeclineReason),
991 IterationLimit {
993 limit: u32,
995 completed: u32,
997 },
998 CapacityOverflow {
1000 op_id: u32,
1002 required: u64,
1004 capacity: u64,
1006 },
1007 ResourceExhausted {
1009 op_id: u32,
1011 resource: &'static str,
1013 required: u64,
1015 capacity: u64,
1017 },
1018 Runtime(String),
1020}
1021
1022impl fmt::Display for ResidentGraphExecutionError {
1023 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1024 write!(formatter, "{self:?}")
1025 }
1026}
1027
1028impl std::error::Error for ResidentGraphExecutionError {}
1029
1030#[derive(Debug, Clone)]
1033pub struct ResidentGraphDeviceStatusTestInjection {
1034 pub(crate) after_op: u32,
1035 pub(crate) status: ResidentGraphDeviceStatus,
1036}
1037
1038impl ResidentGraphDeviceStatusTestInjection {
1039 pub fn device_kernel_after_op(after_op: u32, status: ResidentGraphDeviceStatus) -> Self {
1041 Self { after_op, status }
1042 }
1043}
1044
1045#[derive(Debug, Clone, Default)]
1047pub struct ResidentGraphPrepareOptions {
1048 pub(crate) test_device_status: Option<ResidentGraphDeviceStatusTestInjection>,
1049 pub(crate) latency_diagnostic_sample: Option<u64>,
1050}
1051
1052impl ResidentGraphPrepareOptions {
1053 pub fn with_test_device_status(
1055 mut self,
1056 injection: ResidentGraphDeviceStatusTestInjection,
1057 ) -> Self {
1058 self.test_device_status = Some(injection);
1059 self
1060 }
1061
1062 #[doc(hidden)]
1064 pub fn with_latency_diagnostic_sample(mut self, sample: u64) -> Self {
1065 self.latency_diagnostic_sample = Some(sample);
1066 self
1067 }
1068}
1069
1070#[doc(hidden)]
1072#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1073pub struct ResidentGraphPrepareDiagnosticSnapshot {
1074 pub(crate) sample: u64,
1075 pub(crate) total_ns: u64,
1076 pub(crate) admission_and_source_snapshot_ns: u64,
1077 pub(crate) execution_domain_and_build_setup_ns: u64,
1078 pub(crate) logical_schedule_planning_ns: u64,
1079 pub(crate) manifest_compact_construction_ns: u64,
1080 pub(crate) schedule_lowering_ns: u64,
1081 pub(crate) reservation_ns: u64,
1082 pub(crate) relation_preparation_ns: u64,
1083 pub(crate) count_initialization_ns: u64,
1084 pub(crate) workspace_preparation_ns: u64,
1085 pub(crate) metadata_binding_construction_ns: u64,
1086 pub(crate) metadata_preparation_ns: u64,
1087 pub(crate) reservation_validation_and_release_ns: u64,
1088 pub(crate) pinned_receipt_ns: u64,
1089 pub(crate) graph_body_capture_ns: u64,
1090 pub(crate) graph_instantiate_ns: u64,
1091 pub(crate) validation_owner_assembly_ns: u64,
1092 pub(crate) unattributed_ns: u64,
1093 pub(crate) required_reservation_bytes: u64,
1094 pub(crate) logical_relation_values: u64,
1095 pub(crate) physical_relation_slots: u64,
1096 pub(crate) relation_device_allocation_calls: u64,
1097 pub(crate) relation_reserved_bytes: u64,
1098 pub(crate) relation_slot_preparation_ns_max: u64,
1099 pub(crate) count_memset_calls: u64,
1100 pub(crate) count_memset_bytes: u64,
1101 pub(crate) count_initialization_ns_max: u64,
1102 pub(crate) workspace_provider_calls: u64,
1103 pub(crate) workspace_reserved_bytes: u64,
1104 pub(crate) filter_scratch_preparation_ns: u64,
1105 pub(crate) filter_scratch_reserved_bytes: u64,
1106 pub(crate) set_workspace_preparation_ns: u64,
1107 pub(crate) set_workspace_reserved_bytes: u64,
1108 pub(crate) join_workspace_preparation_ns: u64,
1109 pub(crate) join_workspace_reserved_bytes: u64,
1110 pub(crate) control_preparation_ns: u64,
1111 pub(crate) control_reserved_bytes: u64,
1112 pub(crate) metadata_provider_calls: u64,
1113 pub(crate) metadata_reserved_bytes: u64,
1114 pub(crate) metadata_initial_htod_calls: u64,
1115 pub(crate) metadata_initial_htod_bytes: u64,
1116 pub(crate) device_trace_preparation_ns: u64,
1117 pub(crate) device_trace_reserved_bytes: u64,
1118 pub(crate) device_trace_initial_htod_calls: u64,
1119 pub(crate) device_trace_initial_htod_bytes: u64,
1120 pub(crate) schema_winners_preparation_ns: u64,
1121 pub(crate) schema_winners_reserved_bytes: u64,
1122 pub(crate) schema_winners_initial_htod_calls: u64,
1123 pub(crate) schema_winners_initial_htod_bytes: u64,
1124 pub(crate) receipt_preparation_ns: u64,
1125 pub(crate) receipt_reserved_bytes: u64,
1126 pub(crate) receipt_initial_htod_calls: u64,
1127 pub(crate) receipt_initial_htod_bytes: u64,
1128 pub(crate) schedule_program_preparation_ns: u64,
1129 pub(crate) schedule_program_reserved_bytes: u64,
1130 pub(crate) schedule_program_initial_htod_calls: u64,
1131 pub(crate) schedule_program_initial_htod_bytes: u64,
1132 pub(crate) compact_ops: u64,
1133 pub(crate) compact_waves: u64,
1134 pub(crate) compact_regions: u64,
1135 pub(crate) conditional_regions: u64,
1136 pub(crate) parent_graph_nodes: u64,
1137 pub(crate) conditional_body_nodes: u64,
1138}
1139
1140impl ResidentGraphPrepareDiagnosticSnapshot {
1141 #[doc(hidden)]
1143 pub fn format_line(self) -> String {
1144 let Self {
1145 sample,
1146 total_ns,
1147 admission_and_source_snapshot_ns,
1148 execution_domain_and_build_setup_ns,
1149 logical_schedule_planning_ns,
1150 manifest_compact_construction_ns,
1151 schedule_lowering_ns,
1152 reservation_ns,
1153 relation_preparation_ns,
1154 count_initialization_ns,
1155 workspace_preparation_ns,
1156 metadata_binding_construction_ns,
1157 metadata_preparation_ns,
1158 reservation_validation_and_release_ns,
1159 pinned_receipt_ns,
1160 graph_body_capture_ns,
1161 graph_instantiate_ns,
1162 validation_owner_assembly_ns,
1163 unattributed_ns,
1164 required_reservation_bytes,
1165 logical_relation_values,
1166 physical_relation_slots,
1167 relation_device_allocation_calls,
1168 relation_reserved_bytes,
1169 relation_slot_preparation_ns_max,
1170 count_memset_calls,
1171 count_memset_bytes,
1172 count_initialization_ns_max,
1173 workspace_provider_calls,
1174 workspace_reserved_bytes,
1175 filter_scratch_preparation_ns,
1176 filter_scratch_reserved_bytes,
1177 set_workspace_preparation_ns,
1178 set_workspace_reserved_bytes,
1179 join_workspace_preparation_ns,
1180 join_workspace_reserved_bytes,
1181 control_preparation_ns,
1182 control_reserved_bytes,
1183 metadata_provider_calls,
1184 metadata_reserved_bytes,
1185 metadata_initial_htod_calls,
1186 metadata_initial_htod_bytes,
1187 device_trace_preparation_ns,
1188 device_trace_reserved_bytes,
1189 device_trace_initial_htod_calls,
1190 device_trace_initial_htod_bytes,
1191 schema_winners_preparation_ns,
1192 schema_winners_reserved_bytes,
1193 schema_winners_initial_htod_calls,
1194 schema_winners_initial_htod_bytes,
1195 receipt_preparation_ns,
1196 receipt_reserved_bytes,
1197 receipt_initial_htod_calls,
1198 receipt_initial_htod_bytes,
1199 schedule_program_preparation_ns,
1200 schedule_program_reserved_bytes,
1201 schedule_program_initial_htod_calls,
1202 schedule_program_initial_htod_bytes,
1203 compact_ops,
1204 compact_waves,
1205 compact_regions,
1206 conditional_regions,
1207 parent_graph_nodes,
1208 conditional_body_nodes,
1209 } = self;
1210 format!(
1211 "resident prepare phases: sample={sample} total_ns={total_ns} admission_and_source_snapshot_ns={admission_and_source_snapshot_ns} execution_domain_and_build_setup_ns={execution_domain_and_build_setup_ns} logical_schedule_planning_ns={logical_schedule_planning_ns} manifest_compact_construction_ns={manifest_compact_construction_ns} schedule_lowering_ns={schedule_lowering_ns} reservation_ns={reservation_ns} relation_preparation_ns={relation_preparation_ns} count_initialization_ns={count_initialization_ns} workspace_preparation_ns={workspace_preparation_ns} metadata_binding_construction_ns={metadata_binding_construction_ns} metadata_preparation_ns={metadata_preparation_ns} reservation_validation_and_release_ns={reservation_validation_and_release_ns} pinned_receipt_ns={pinned_receipt_ns} graph_body_capture_ns={graph_body_capture_ns} graph_instantiate_ns={graph_instantiate_ns} validation_owner_assembly_ns={validation_owner_assembly_ns} unattributed_ns={unattributed_ns} required_reservation_bytes={required_reservation_bytes} logical_relation_values={logical_relation_values} physical_relation_slots={physical_relation_slots} relation_device_allocation_calls={relation_device_allocation_calls} relation_reserved_bytes={relation_reserved_bytes} relation_slot_preparation_ns_max={relation_slot_preparation_ns_max} count_memset_calls={count_memset_calls} count_memset_bytes={count_memset_bytes} count_initialization_ns_max={count_initialization_ns_max} workspace_provider_calls={workspace_provider_calls} workspace_reserved_bytes={workspace_reserved_bytes} filter_scratch_preparation_ns={filter_scratch_preparation_ns} filter_scratch_reserved_bytes={filter_scratch_reserved_bytes} set_workspace_preparation_ns={set_workspace_preparation_ns} set_workspace_reserved_bytes={set_workspace_reserved_bytes} join_workspace_preparation_ns={join_workspace_preparation_ns} join_workspace_reserved_bytes={join_workspace_reserved_bytes} control_preparation_ns={control_preparation_ns} control_reserved_bytes={control_reserved_bytes} metadata_provider_calls={metadata_provider_calls} metadata_reserved_bytes={metadata_reserved_bytes} metadata_initial_htod_calls={metadata_initial_htod_calls} metadata_initial_htod_bytes={metadata_initial_htod_bytes} device_trace_preparation_ns={device_trace_preparation_ns} device_trace_reserved_bytes={device_trace_reserved_bytes} device_trace_initial_htod_calls={device_trace_initial_htod_calls} device_trace_initial_htod_bytes={device_trace_initial_htod_bytes} schema_winners_preparation_ns={schema_winners_preparation_ns} schema_winners_reserved_bytes={schema_winners_reserved_bytes} schema_winners_initial_htod_calls={schema_winners_initial_htod_calls} schema_winners_initial_htod_bytes={schema_winners_initial_htod_bytes} receipt_preparation_ns={receipt_preparation_ns} receipt_reserved_bytes={receipt_reserved_bytes} receipt_initial_htod_calls={receipt_initial_htod_calls} receipt_initial_htod_bytes={receipt_initial_htod_bytes} schedule_program_preparation_ns={schedule_program_preparation_ns} schedule_program_reserved_bytes={schedule_program_reserved_bytes} schedule_program_initial_htod_calls={schedule_program_initial_htod_calls} schedule_program_initial_htod_bytes={schedule_program_initial_htod_bytes} compact_ops={compact_ops} compact_waves={compact_waves} compact_regions={compact_regions} conditional_regions={conditional_regions} parent_graph_nodes={parent_graph_nodes} conditional_body_nodes={conditional_body_nodes} deallocation_calls=unavailable"
1212 )
1213 }
1214}
1215
1216#[cfg(test)]
1217fn resident_graph_prepare_diagnostic_line(
1218 snapshot: Option<ResidentGraphPrepareDiagnosticSnapshot>,
1219) -> Option<String> {
1220 snapshot.map(ResidentGraphPrepareDiagnosticSnapshot::format_line)
1221}
1222
1223#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1225pub enum ResidentGraphSelectionKind {
1226 ExistingGpu,
1228 ResidentConditionalGraph,
1230}
1231
1232#[derive(Debug, Clone, Default)]
1234pub struct ResidentGraphCoreTransferStats {
1235 pub tracked_htod_calls: u64,
1237 pub tracked_htod_bytes: u64,
1239 pub tracked_dtoh_calls: u64,
1241 pub tracked_dtoh_bytes: u64,
1243 pub provider_dtoh_calls: u64,
1245 pub untracked_metadata_dtoh_calls: u64,
1247}
1248
1249#[derive(Debug, Clone, Default)]
1251pub struct ResidentGraphFinalObservationStats {
1252 pub dtoh_calls: u64,
1254 pub dtoh_bytes: u64,
1256 pub pinned_receipts: u64,
1258}
1259
1260#[derive(Debug, Clone, Default)]
1262pub struct ResidentGraphDeferredProfile {
1263 pub timed_scan_filter_invocations: u64,
1265 pub device_elapsed_ns: u64,
1267 pub final_sync_misattributed_ns: u64,
1269}
1270
1271#[derive(Debug, Clone)]
1273pub struct ResidentGraphExecutionStats {
1274 pub selection: ResidentGraphSelectionKind,
1276 pub decline: Option<ResidentGraphDeclineReason>,
1278 pub conditional_graph_launches: u64,
1280 pub terminal_synchronizations: u64,
1282 pub host_iterations: u64,
1284 pub host_allocations: u64,
1286 pub host_status_injections: u64,
1288 pub deterministic_d2h_violations: u64,
1290 pub host_dispatched_scan_ops: u64,
1292 pub host_dispatched_filter_ops: u64,
1294 pub device_scan_invocations: u64,
1296 pub device_filter_invocations: u64,
1298 pub semantic_scan_invocations: u64,
1301 pub semantic_filter_invocations: u64,
1304 pub staged_store_mutations: u64,
1306 pub deferred_profile: ResidentGraphDeferredProfile,
1308 pub core_transfers: ResidentGraphCoreTransferStats,
1310 pub final_observation: ResidentGraphFinalObservationStats,
1312}
1313
1314impl ResidentGraphExecutionStats {
1315 pub fn declined(reason: ResidentGraphDeclineReason) -> Self {
1317 Self {
1318 selection: ResidentGraphSelectionKind::ExistingGpu,
1319 decline: Some(reason),
1320 conditional_graph_launches: 0,
1321 terminal_synchronizations: 0,
1322 host_iterations: 0,
1323 host_allocations: 0,
1324 host_status_injections: 0,
1325 deterministic_d2h_violations: 0,
1326 host_dispatched_scan_ops: 0,
1327 host_dispatched_filter_ops: 0,
1328 device_scan_invocations: 0,
1329 device_filter_invocations: 0,
1330 semantic_scan_invocations: 0,
1331 semantic_filter_invocations: 0,
1332 staged_store_mutations: 0,
1333 deferred_profile: ResidentGraphDeferredProfile::default(),
1334 core_transfers: ResidentGraphCoreTransferStats::default(),
1335 final_observation: ResidentGraphFinalObservationStats::default(),
1336 }
1337 }
1338}
1339
1340#[cfg(test)]
1341mod tests {
1342 use super::*;
1343 use std::sync::Arc;
1344 use xlog_ir::{CompiledRule, GeneratedQueryRuleProvenance, RirMeta, Scc, Stratum};
1345
1346 fn schema(columns: &[(&str, ScalarType)]) -> Schema {
1347 Schema::new(
1348 columns
1349 .iter()
1350 .map(|(name, scalar)| ((*name).to_owned(), *scalar))
1351 .collect(),
1352 )
1353 }
1354
1355 fn rule(head: &str, body: RirNode, columns: &[(&str, ScalarType)]) -> CompiledRule {
1356 CompiledRule {
1357 head: head.to_owned(),
1358 body,
1359 meta: RirMeta {
1360 schema: schema(columns),
1361 ..RirMeta::default()
1362 },
1363 }
1364 }
1365
1366 fn chain_join() -> RirNode {
1367 RirNode::ChainJoin {
1368 left: Box::new(RirNode::Scan { rel: RelId(1) }),
1369 right: Box::new(RirNode::Scan { rel: RelId(1) }),
1370 left_key: 0,
1371 right_key: 0,
1372 output_columns: vec![ProjectExpr::Column(0)],
1373 fallback: Box::new(RirNode::Join {
1374 left: Box::new(RirNode::Scan { rel: RelId(1) }),
1375 right: Box::new(RirNode::Scan { rel: RelId(1) }),
1376 left_keys: vec![0],
1377 right_keys: vec![0],
1378 join_type: JoinType::Inner,
1379 }),
1380 }
1381 }
1382
1383 fn representative_plan() -> ExecutionPlan {
1384 let mut rel_arities = HashMap::new();
1385 rel_arities.insert(RelId(9), 0);
1386 rel_arities.insert(RelId(1), 2);
1387 ExecutionPlan {
1388 sccs: vec![
1389 Scc {
1390 id: 10,
1391 predicates: vec!["alpha".to_owned(), "beta".to_owned()],
1392 is_recursive: true,
1393 },
1394 Scc {
1395 id: 20,
1396 predicates: Vec::new(),
1397 is_recursive: false,
1398 },
1399 Scc {
1400 id: 30,
1401 predicates: vec!["gamma".to_owned()],
1402 is_recursive: false,
1403 },
1404 Scc {
1405 id: 40,
1406 predicates: Vec::new(),
1407 is_recursive: false,
1408 },
1409 ],
1410 strata: vec![
1411 Stratum {
1412 id: 0,
1413 sccs: vec![10],
1414 },
1415 Stratum {
1416 id: 1,
1417 sccs: Vec::new(),
1418 },
1419 Stratum {
1420 id: 2,
1421 sccs: vec![20, 30, 40],
1422 },
1423 ],
1424 rules_by_scc: vec![
1425 vec![
1426 rule(
1427 "alpha",
1428 RirNode::Scan { rel: RelId(1) },
1429 &[("left", ScalarType::U32), ("right", ScalarType::Symbol)],
1430 ),
1431 rule("beta", chain_join(), &[("value", ScalarType::U64)]),
1432 ],
1433 Vec::new(),
1434 vec![rule("gamma", RirNode::Unit, &[])],
1435 Vec::new(),
1436 ],
1437 generated_query_rules: vec![],
1438 est_memory_peak: 4_096,
1439 rel_arities,
1440 }
1441 }
1442
1443 fn catalog() -> ResidentGraphSchemaCatalog {
1444 ResidentGraphSchemaCatalog::from_named_schemas([(
1445 "input".to_owned(),
1446 RelId(1),
1447 schema(&[("left", ScalarType::U32), ("right", ScalarType::Symbol)]),
1448 )])
1449 }
1450
1451 fn assert_mutation_rejected(mutate: impl FnOnce(&mut ExecutionPlan)) {
1452 let plan = representative_plan();
1453 let certificate = ResidentGraphRouteCertificate::inspect(&plan, &catalog()).unwrap();
1454 let mut mutated = plan.clone();
1455 mutate(&mut mutated);
1456 assert!(!certificate.matches_plan(&mutated).unwrap());
1457 }
1458
1459 fn assert_generated_query_provenance_mutation_rejected(
1460 mutate: impl FnOnce(&mut GeneratedQueryRuleProvenance),
1461 ) {
1462 let mut plan = representative_plan();
1463 plan.sccs[0].predicates[0] = "__xlog_query_0".to_owned();
1464 plan.rules_by_scc[0][0].head = "__xlog_query_0".to_owned();
1465 plan.generated_query_rules = vec![GeneratedQueryRuleProvenance {
1466 query_index: 0,
1467 scc_index: 0,
1468 rule_index: 0,
1469 }];
1470 let certificate = ResidentGraphRouteCertificate::inspect(&plan, &catalog()).unwrap();
1471 mutate(&mut plan.generated_query_rules[0]);
1472 assert!(!certificate.matches_plan(&plan).unwrap());
1473 }
1474
1475 #[test]
1476 fn resident_node_descriptors_encode_only_local_fields() {
1477 let descendant = RirNode::Project {
1478 input: Box::new(RirNode::Scan { rel: RelId(9) }),
1479 columns: vec![ProjectExpr::Column(0)],
1480 };
1481 let project = RirNode::Project {
1482 input: Box::new(descendant),
1483 columns: vec![ProjectExpr::Computed(
1484 Expr::Const(ConstValue::U32(7)),
1485 ScalarType::U32,
1486 )],
1487 };
1488
1489 let descriptor = resident_node_descriptor(&project);
1490 assert_eq!(
1491 descriptor,
1492 "project;column_count=1;computed:U32;constant:u32:7;"
1493 );
1494 assert!(!descriptor.contains("Scan"));
1495 assert!(!descriptor.contains("input"));
1496 }
1497
1498 #[test]
1499 fn resident_node_descriptors_distinguish_local_semantics() {
1500 let left = resident_node_descriptor(&RirNode::Join {
1501 left: Box::new(RirNode::Unit),
1502 right: Box::new(RirNode::Unit),
1503 left_keys: vec![0],
1504 right_keys: vec![1],
1505 join_type: JoinType::Inner,
1506 });
1507 let right = resident_node_descriptor(&RirNode::Join {
1508 left: Box::new(RirNode::Unit),
1509 right: Box::new(RirNode::Unit),
1510 left_keys: vec![1],
1511 right_keys: vec![0],
1512 join_type: JoinType::Inner,
1513 });
1514 assert_ne!(left, right);
1515
1516 let negative_zero = resident_node_descriptor(&RirNode::Filter {
1517 input: Box::new(RirNode::Unit),
1518 predicate: Expr::Const(ConstValue::F64(-0.0)),
1519 });
1520 let positive_zero = resident_node_descriptor(&RirNode::Filter {
1521 input: Box::new(RirNode::Unit),
1522 predicate: Expr::Const(ConstValue::F64(0.0)),
1523 });
1524 assert_ne!(negative_zero, positive_zero);
1525 }
1526
1527 #[test]
1528 fn certificate_descriptors_remain_bounded_for_deep_unary_plans() {
1529 let mut body = RirNode::Scan { rel: RelId(1) };
1530 for _ in 0..128 {
1531 body = RirNode::Project {
1532 input: Box::new(body),
1533 columns: vec![ProjectExpr::Column(0)],
1534 };
1535 }
1536 let mut plan = representative_plan();
1537 plan.rules_by_scc[0][0].body = body;
1538
1539 let certificate = ResidentGraphRouteCertificate::inspect(&plan, &catalog()).unwrap();
1540 let largest_descriptor = certificate
1541 .covered_route_descriptors
1542 .iter()
1543 .map(String::len)
1544 .max()
1545 .unwrap();
1546 assert!(largest_descriptor < 8_192, "{largest_descriptor}");
1547 }
1548
1549 #[test]
1550 fn certified_plan_seals_the_exact_arc_and_certificate() {
1551 let plan = Arc::new(representative_plan());
1552 let certified = ResidentGraphCertifiedPlan::inspect(Arc::clone(&plan), &catalog()).unwrap();
1553
1554 assert!(std::ptr::eq(Arc::as_ptr(&plan), certified.plan()));
1555 assert!(certified
1556 .certificate()
1557 .matches_plan(certified.plan())
1558 .unwrap());
1559
1560 let mut independent_mutation = (*plan).clone();
1561 independent_mutation.est_memory_peak += 1;
1562 assert!(!certified
1563 .certificate()
1564 .matches_plan(&independent_mutation)
1565 .unwrap());
1566 }
1567
1568 #[test]
1569 fn prepare_latency_diagnostics_are_explicit_and_sample_bound() {
1570 let disabled = ResidentGraphPrepareOptions::default();
1571 assert_eq!(disabled.latency_diagnostic_sample, None);
1572
1573 let enabled = disabled.with_latency_diagnostic_sample(7);
1574 assert_eq!(enabled.latency_diagnostic_sample, Some(7));
1575 }
1576
1577 #[test]
1578 fn prepare_latency_diagnostic_format_is_aligned_and_default_off_is_silent() {
1579 let mut snapshot = ResidentGraphPrepareDiagnosticSnapshot {
1580 sample: 17,
1581 total_ns: 101,
1582 admission_and_source_snapshot_ns: 102,
1583 execution_domain_and_build_setup_ns: 109,
1584 metadata_binding_construction_ns: 110,
1585 reservation_validation_and_release_ns: 111,
1586 relation_device_allocation_calls: 103,
1587 workspace_provider_calls: 104,
1588 metadata_initial_htod_calls: 105,
1589 metadata_initial_htod_bytes: 106,
1590 graph_instantiate_ns: 107,
1591 unattributed_ns: 108,
1592 ..ResidentGraphPrepareDiagnosticSnapshot::default()
1593 };
1594 snapshot.schema_winners_initial_htod_calls = 1;
1595 snapshot.schema_winners_initial_htod_bytes = 12;
1596
1597 let line = resident_graph_prepare_diagnostic_line(Some(snapshot))
1598 .expect("enabled diagnostics produce one line");
1599 for expected in [
1600 "sample=17",
1601 "total_ns=101",
1602 "admission_and_source_snapshot_ns=102",
1603 "execution_domain_and_build_setup_ns=109",
1604 "metadata_binding_construction_ns=110",
1605 "reservation_validation_and_release_ns=111",
1606 "relation_device_allocation_calls=103",
1607 "workspace_provider_calls=104",
1608 "metadata_initial_htod_calls=105",
1609 "metadata_initial_htod_bytes=106",
1610 "schema_winners_initial_htod_calls=1",
1611 "schema_winners_initial_htod_bytes=12",
1612 "graph_instantiate_ns=107",
1613 "unattributed_ns=108",
1614 ] {
1615 assert!(line.split_ascii_whitespace().any(|field| field == expected));
1616 }
1617 assert_eq!(line.matches("sample=17").count(), 1);
1618 assert!(!line.contains("admission_source_validation_ns="));
1619 assert_eq!(resident_graph_prepare_diagnostic_line(None), None);
1620 }
1621
1622 #[test]
1623 fn certificate_binds_strata_vector_length() {
1624 assert_mutation_rejected(|plan| {
1625 plan.strata.push(Stratum {
1626 id: 3,
1627 sccs: Vec::new(),
1628 });
1629 });
1630 }
1631
1632 #[test]
1633 fn certificate_binds_empty_stratum_position() {
1634 assert_mutation_rejected(|plan| plan.strata.swap(1, 2));
1635 }
1636
1637 #[test]
1638 fn certificate_binds_stratum_id() {
1639 assert_mutation_rejected(|plan| plan.strata[1].id = 99);
1640 }
1641
1642 #[test]
1643 fn certificate_binds_ordered_stratum_scc_membership() {
1644 assert_mutation_rejected(|plan| plan.strata[2].sccs.swap(0, 1));
1645 }
1646
1647 #[test]
1648 fn certificate_binds_scc_vector_length_including_empty_sccs() {
1649 assert_mutation_rejected(|plan| {
1650 plan.sccs.pop();
1651 });
1652 }
1653
1654 #[test]
1655 fn certificate_binds_scc_id() {
1656 assert_mutation_rejected(|plan| plan.sccs[1].id = 99);
1657 }
1658
1659 #[test]
1660 fn certificate_binds_ordered_scc_predicate_membership() {
1661 assert_mutation_rejected(|plan| plan.sccs[0].predicates.swap(0, 1));
1662 }
1663
1664 #[test]
1665 fn certificate_binds_scc_recursive_flag() {
1666 assert_mutation_rejected(|plan| plan.sccs[1].is_recursive = true);
1667 }
1668
1669 #[test]
1670 fn certificate_binds_rules_by_scc_vector_length() {
1671 assert_mutation_rejected(|plan| plan.rules_by_scc.push(Vec::new()));
1672 }
1673
1674 #[test]
1675 fn certificate_binds_rule_occurrence_count() {
1676 assert_mutation_rejected(|plan| {
1677 plan.rules_by_scc[0].pop();
1678 });
1679 }
1680
1681 #[test]
1682 fn certificate_binds_rule_order() {
1683 assert_mutation_rejected(|plan| plan.rules_by_scc[0].swap(0, 1));
1684 }
1685
1686 #[test]
1687 fn certificate_binds_generated_query_provenance_omission() {
1688 let mut plan = representative_plan();
1689 plan.generated_query_rules = vec![GeneratedQueryRuleProvenance {
1690 query_index: 0,
1691 scc_index: 0,
1692 rule_index: 0,
1693 }];
1694 let certificate = ResidentGraphRouteCertificate::inspect(&plan, &catalog()).unwrap();
1695 plan.generated_query_rules.clear();
1696 assert!(!certificate.matches_plan(&plan).unwrap());
1697 }
1698
1699 #[test]
1700 fn certificate_binds_generated_query_index() {
1701 assert_generated_query_provenance_mutation_rejected(|provenance| {
1702 provenance.query_index = 1;
1703 });
1704 }
1705
1706 #[test]
1707 fn certificate_binds_generated_query_rule_position() {
1708 assert_generated_query_provenance_mutation_rejected(|provenance| {
1709 provenance.rule_index = 1;
1710 });
1711 }
1712
1713 #[test]
1714 fn certificate_binds_rule_schema_type() {
1715 assert_mutation_rejected(|plan| {
1716 plan.rules_by_scc[0][0].meta.schema =
1717 schema(&[("left", ScalarType::U64), ("right", ScalarType::Symbol)]);
1718 });
1719 }
1720
1721 #[test]
1722 fn certificate_binds_rule_schema_arity() {
1723 assert_mutation_rejected(|plan| {
1724 plan.rules_by_scc[0][0].meta.schema = schema(&[("left", ScalarType::U32)]);
1725 });
1726 }
1727
1728 #[test]
1729 fn certificate_binds_primary_rir() {
1730 assert_mutation_rejected(|plan| plan.rules_by_scc[0][0].body = RirNode::Unit);
1731 }
1732
1733 #[test]
1734 fn certificate_binds_captured_alternative_rir() {
1735 assert_mutation_rejected(|plan| {
1736 let RirNode::ChainJoin { fallback, .. } = &mut plan.rules_by_scc[0][1].body else {
1737 panic!("representative rule must carry a captured alternative");
1738 };
1739 **fallback = RirNode::Unit;
1740 });
1741 }
1742
1743 #[test]
1744 fn certificate_binds_implicit_device_convergence_route() {
1745 let plan = representative_plan();
1746 let mut certificate = ResidentGraphRouteCertificate::inspect(&plan, &catalog()).unwrap();
1747 let convergence = certificate
1748 .covered_route_descriptors
1749 .iter()
1750 .find(|descriptor| {
1751 descriptor.contains("scc=0;rule=0;head=alpha;")
1752 && descriptor.ends_with(";implicit=device_convergence")
1753 })
1754 .cloned()
1755 .expect("recursive rule must certify implicit device convergence");
1756 assert!(certificate.covered_route_descriptors.remove(&convergence));
1757 certificate
1758 .covered_route_descriptors
1759 .insert(format!("{convergence};mutated"));
1760 certificate.plan_fingerprint = stable_descriptor_fingerprint(
1761 certificate
1762 .covered_route_descriptors
1763 .iter()
1764 .map(String::as_bytes),
1765 );
1766 assert!(!certificate.matches_plan(&plan).unwrap());
1767 }
1768
1769 #[test]
1770 fn certificate_binds_rel_arities_vector_length() {
1771 assert_mutation_rejected(|plan| {
1772 plan.rel_arities.insert(RelId(7), 3);
1773 });
1774 }
1775
1776 #[test]
1777 fn certificate_binds_relation_arity() {
1778 assert_mutation_rejected(|plan| {
1779 plan.rel_arities.insert(RelId(1), 3);
1780 });
1781 }
1782
1783 #[test]
1784 fn certificate_rel_arities_are_order_independent() {
1785 let plan = representative_plan();
1786 let certificate = ResidentGraphRouteCertificate::inspect(&plan, &catalog()).unwrap();
1787 let mut reordered = plan.clone();
1788 reordered.rel_arities.clear();
1789 reordered.rel_arities.insert(RelId(1), 2);
1790 reordered.rel_arities.insert(RelId(9), 0);
1791 assert!(certificate.matches_plan(&reordered).unwrap());
1792 }
1793
1794 #[test]
1795 fn certificate_binds_estimated_memory_peak() {
1796 assert_mutation_rejected(|plan| plan.est_memory_peak += 1);
1797 }
1798
1799 #[test]
1800 fn certificate_rejects_intermediate_arity_above_fixed_metadata_envelope() {
1801 let columns = (0..18)
1802 .map(|index| (format!("column_{index}"), ScalarType::U32))
1803 .collect::<Vec<_>>();
1804 let wide_schema = Schema::new(columns);
1805 let plan = ExecutionPlan {
1806 sccs: vec![Scc {
1807 id: 1,
1808 predicates: vec!["wide".to_owned()],
1809 is_recursive: false,
1810 }],
1811 strata: vec![Stratum {
1812 id: 0,
1813 sccs: vec![1],
1814 }],
1815 rules_by_scc: vec![vec![CompiledRule {
1816 head: "wide".to_owned(),
1817 body: RirNode::Scan { rel: RelId(1) },
1818 meta: RirMeta {
1819 schema: wide_schema.clone(),
1820 ..RirMeta::default()
1821 },
1822 }]],
1823 generated_query_rules: vec![],
1824 est_memory_peak: 0,
1825 rel_arities: HashMap::from([(RelId(1), 18)]),
1826 };
1827 let catalog = ResidentGraphSchemaCatalog::from_named_schemas([(
1828 "wide".to_owned(),
1829 RelId(1),
1830 wide_schema,
1831 )]);
1832 let certificate = ResidentGraphRouteCertificate::inspect(&plan, &catalog).unwrap();
1833 assert!(!certificate.is_supported());
1834 assert!(certificate.declines().iter().any(|decline| matches!(
1835 decline,
1836 ResidentGraphDeclineReason::UnsupportedNode {
1837 node: "intermediate_arity",
1838 ..
1839 }
1840 )));
1841 }
1842
1843 #[test]
1844 fn certificate_accepts_intermediate_arity_at_fixed_metadata_envelope() {
1845 let columns = (0..17)
1846 .map(|index| (format!("column_{index}"), ScalarType::U32))
1847 .collect::<Vec<_>>();
1848 let wide_schema = Schema::new(columns);
1849 let plan = ExecutionPlan {
1850 sccs: vec![Scc {
1851 id: 1,
1852 predicates: vec!["wide".to_owned()],
1853 is_recursive: false,
1854 }],
1855 strata: vec![Stratum {
1856 id: 0,
1857 sccs: vec![1],
1858 }],
1859 rules_by_scc: vec![vec![CompiledRule {
1860 head: "wide".to_owned(),
1861 body: RirNode::Scan { rel: RelId(1) },
1862 meta: RirMeta {
1863 schema: wide_schema.clone(),
1864 ..RirMeta::default()
1865 },
1866 }]],
1867 generated_query_rules: vec![],
1868 est_memory_peak: 0,
1869 rel_arities: HashMap::from([(RelId(1), 17)]),
1870 };
1871 let catalog = ResidentGraphSchemaCatalog::from_named_schemas([(
1872 "wide".to_owned(),
1873 RelId(1),
1874 wide_schema,
1875 )]);
1876 let certificate = ResidentGraphRouteCertificate::inspect(&plan, &catalog).unwrap();
1877 assert!(certificate.is_supported(), "{:#?}", certificate.declines());
1878 }
1879}