(
&self,
relation: &mut MirRelationExpr,
ctx: &mut TransformCtx,
)
| 457 | fields(path.segment = self.name) |
| 458 | )] |
| 459 | fn actually_perform_transform( |
| 460 | &self, |
| 461 | relation: &mut MirRelationExpr, |
| 462 | ctx: &mut TransformCtx, |
| 463 | ) -> Result<(), TransformError> { |
| 464 | // The number of iterations for a relation to settle depends on the |
| 465 | // number of nodes in the relation. Instead of picking an arbitrary |
| 466 | // hard limit on the number of iterations, we use a soft limit and |
| 467 | // check whether the relation has become simpler after reaching it. |
| 468 | // If so, we perform another pass of transforms. Otherwise, there is |
| 469 | // a bug somewhere that prevents the relation from settling on a |
| 470 | // stable shape. |
| 471 | let mut iter_no = 0; |
| 472 | let mut seen = BTreeMap::new(); |
| 473 | seen.insert(relation.hash_to_u64(), iter_no); |
| 474 | let original = relation.clone(); |
| 475 | loop { |
| 476 | let prev_size = relation.size(); |
| 477 | for i in iter_no..iter_no + self.limit { |
| 478 | let prev = relation.clone(); |
| 479 | self.apply_transforms(relation, ctx, format!("{i:04}"))?; |
| 480 | if *relation == prev { |
| 481 | if prev_size > 100000 { |
| 482 | tracing::warn!(%prev_size, "Very big MIR plan"); |
| 483 | } |
| 484 | mz_repr::explain::trace_plan(relation); |
| 485 | return Ok(()); |
| 486 | } |
| 487 | let seen_i = seen.insert(relation.hash_to_u64(), i); |
| 488 | if let Some(seen_i) = seen_i { |
| 489 | // Let's see whether this is just a hash collision, or a real loop: Run the |
| 490 | // whole thing from the beginning up until `seen_i`, and compare all the plans |
| 491 | // to the current plan from the outer `for`. |
| 492 | // (It would not be enough to compare only the plan at `seen_i`, because |
| 493 | // then we could miss a real loop if there is also a hash collision somewhere |
| 494 | // in the middle of the loop, because then we'd compare the last plan of the |
| 495 | // loop not with its actual match, but with the colliding plan.) |
| 496 | let mut again = original.clone(); |
| 497 | // The `+2` is because: |
| 498 | // - one `+1` is to finally get to the plan at `seen_i`, |
| 499 | // - another `+1` is because we are comparing to `relation` only _before_ |
| 500 | // calling `apply_transforms`. |
| 501 | for j in 0..(seen_i + 2) { |
| 502 | if again == *relation { |
| 503 | // We really got into an infinite loop (e.g., we are oscillating between |
| 504 | // two plans). This is not catastrophic, because we can just say we are |
| 505 | // done now, but it would be great to eventually find a way to prevent |
| 506 | // these loops from happening in the first place. We have several |
| 507 | // relevant issues, see |
| 508 | // https://github.com/MaterializeInc/database-issues/issues/8197#issuecomment-2200172227 |
| 509 | mz_repr::explain::trace_plan(relation); |
| 510 | error!( |
| 511 | "Fixpoint `{}` detected a loop of length {} after {} iterations", |
| 512 | self.name, |
| 513 | i - seen_i, |
| 514 | i |
| 515 | ); |
| 516 | return Ok(()); |
no test coverage detected