1use crate::{
18 Distribution, MemoryBudgetClass, PhysicalProperties, PlanCost, PlanCostBreakdown,
19 RelationalAccessPathDescriptor, RelationalAccessPathKind, RequiredProperties,
20 ScanPruningSupport,
21};
22use hawdb_expression::{BindingId, BindingSet};
23use std::{collections::BTreeMap, error::Error, fmt};
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
26pub struct RelationalJoinPredicateId(u32);
27
28impl RelationalJoinPredicateId {
29 pub const fn new(value: u32) -> Self {
30 Self(value)
31 }
32
33 pub const fn get(self) -> u32 {
34 self.0
35 }
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct RelationalJoinAccessPath {
40 pub descriptor: RelationalAccessPathDescriptor,
41 pub required_bindings: BindingSet,
42 pub properties: PhysicalProperties,
43 pub applicability: RelationalJoinAccessApplicability,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum RelationalJoinAccessApplicability {
48 Base,
49 Probe,
50 BaseAndProbe,
51}
52
53impl RelationalJoinAccessPath {
54 pub fn base(descriptor: RelationalAccessPathDescriptor) -> Self {
55 Self::new(
56 descriptor,
57 BindingSet::new(),
58 RelationalJoinAccessApplicability::Base,
59 )
60 }
61
62 pub fn probe(
63 descriptor: RelationalAccessPathDescriptor,
64 required_bindings: BindingSet,
65 ) -> Self {
66 Self::new(
67 descriptor,
68 required_bindings,
69 RelationalJoinAccessApplicability::Probe,
70 )
71 }
72
73 pub fn base_and_probe(descriptor: RelationalAccessPathDescriptor) -> Self {
74 Self::new(
75 descriptor,
76 BindingSet::new(),
77 RelationalJoinAccessApplicability::BaseAndProbe,
78 )
79 }
80
81 fn new(
82 descriptor: RelationalAccessPathDescriptor,
83 required_bindings: BindingSet,
84 applicability: RelationalJoinAccessApplicability,
85 ) -> Self {
86 let properties = access_path_properties(&descriptor);
87 Self {
88 descriptor,
89 required_bindings,
90 properties,
91 applicability,
92 }
93 }
94
95 pub fn with_properties(mut self, properties: PhysicalProperties) -> Self {
96 self.properties = properties;
97 self
98 }
99
100 pub(crate) fn supports_base(&self) -> bool {
101 matches!(
102 self.applicability,
103 RelationalJoinAccessApplicability::Base
104 | RelationalJoinAccessApplicability::BaseAndProbe
105 )
106 }
107
108 pub(crate) fn supports_probe(&self) -> bool {
109 matches!(
110 self.applicability,
111 RelationalJoinAccessApplicability::Probe
112 | RelationalJoinAccessApplicability::BaseAndProbe
113 )
114 }
115
116 pub(crate) fn validate_usage(&self) -> Result<(), &'static str> {
117 match self.applicability {
118 RelationalJoinAccessApplicability::Base
119 | RelationalJoinAccessApplicability::BaseAndProbe
120 if !self.required_bindings.is_empty() =>
121 {
122 Err("base access path cannot require another binding")
123 }
124 RelationalJoinAccessApplicability::Probe if self.required_bindings.is_empty() => {
125 Err("probe access path must require another binding")
126 }
127 _ => Ok(()),
128 }
129 }
130}
131
132#[derive(Debug, Clone, PartialEq, Eq)]
133pub struct RelationalJoinRelation {
134 pub binding: BindingId,
135 pub access_paths: Vec<RelationalJoinAccessPath>,
136}
137
138#[derive(Debug, Clone, PartialEq, Eq)]
139pub struct RelationalJoinPredicate {
140 pub id: RelationalJoinPredicateId,
141 pub bindings: BindingSet,
142}
143
144#[derive(Debug, Clone, PartialEq, Eq)]
145pub struct RelationalJoinGraph {
146 pub relations: Vec<RelationalJoinRelation>,
147 pub predicates: Vec<RelationalJoinPredicate>,
148}
149
150#[derive(Debug, Clone, Copy, PartialEq, Eq)]
151pub struct RelationalJoinEnumerationConfig {
152 pub max_groups: usize,
153 pub max_expressions: usize,
154}
155
156impl Default for RelationalJoinEnumerationConfig {
157 fn default() -> Self {
158 Self {
159 max_groups: 4_095,
160 max_expressions: 32_768,
161 }
162 }
163}
164
165#[derive(Debug, Clone, PartialEq, Eq)]
166pub struct RelationalJoinStep {
167 pub binding: BindingId,
168 pub access_path: RelationalJoinAccessPath,
169 pub activated_predicates: Vec<RelationalJoinPredicateId>,
170}
171
172#[derive(Debug, Clone, PartialEq, Eq)]
173pub struct RelationalJoinPlan {
174 pub base_binding: BindingId,
175 pub base_access_path: RelationalJoinAccessPath,
176 pub steps: Vec<RelationalJoinStep>,
177 pub cost_breakdown: PlanCostBreakdown,
178 pub properties: PhysicalProperties,
179}
180
181impl RelationalJoinPlan {
182 pub fn cost(&self) -> PlanCost {
183 self.cost_breakdown.as_plan_cost()
184 }
185
186 pub fn binding_order(&self) -> Vec<BindingId> {
187 std::iter::once(self.base_binding)
188 .chain(self.steps.iter().map(|step| step.binding))
189 .collect()
190 }
191}
192
193#[derive(Debug, Clone, PartialEq, Eq)]
194pub struct RelationalInnerJoinEnumeration {
195 pub plan: RelationalJoinPlan,
196 pub memo_groups: usize,
197 pub memo_expressions: usize,
198}
199
200#[derive(Debug, Clone, PartialEq, Eq)]
201pub enum RelationalJoinEnumerationError {
202 EmptyGraph,
203 DuplicateBinding(BindingId),
204 DuplicatePredicate(RelationalJoinPredicateId),
205 PredicateHasFewerThanTwoBindings(RelationalJoinPredicateId),
206 UnknownPredicateBinding {
207 predicate: RelationalJoinPredicateId,
208 binding: BindingId,
209 },
210 UnknownAccessBinding {
211 relation: BindingId,
212 binding: BindingId,
213 },
214 AccessWithoutPredicate {
215 relation: BindingId,
216 binding: BindingId,
217 },
218 SelfDependentAccess(BindingId),
219 MissingBaseAccess(BindingId),
220 InvalidAccessPath {
221 binding: BindingId,
222 reason: &'static str,
223 },
224 GroupBudgetExceeded {
225 required_groups: usize,
226 max_groups: usize,
227 },
228 ExpressionBudgetExceeded {
229 required_expressions: usize,
230 max_expressions: usize,
231 },
232 Disconnected,
233 RequiredPropertiesUnsatisfied,
234}
235
236impl fmt::Display for RelationalJoinEnumerationError {
237 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
238 match self {
239 Self::EmptyGraph => write!(formatter, "relational join graph is empty"),
240 Self::DuplicateBinding(binding) => {
241 write!(formatter, "duplicate relational binding {}", binding.get())
242 }
243 Self::DuplicatePredicate(predicate) => {
244 write!(formatter, "duplicate relational predicate {}", predicate.get())
245 }
246 Self::PredicateHasFewerThanTwoBindings(predicate) => write!(
247 formatter,
248 "relational join predicate {} references fewer than two bindings",
249 predicate.get()
250 ),
251 Self::UnknownPredicateBinding { predicate, binding } => write!(
252 formatter,
253 "relational join predicate {} references unknown binding {}",
254 predicate.get(),
255 binding.get()
256 ),
257 Self::UnknownAccessBinding { relation, binding } => write!(
258 formatter,
259 "relational binding {} access path requires unknown binding {}",
260 relation.get(),
261 binding.get()
262 ),
263 Self::AccessWithoutPredicate { relation, binding } => write!(
264 formatter,
265 "relational binding {} access path depends on binding {} without a connecting predicate",
266 relation.get(),
267 binding.get()
268 ),
269 Self::SelfDependentAccess(binding) => write!(
270 formatter,
271 "relational binding {} access path depends on itself",
272 binding.get()
273 ),
274 Self::MissingBaseAccess(binding) => write!(
275 formatter,
276 "relational binding {} has no unbound access path",
277 binding.get()
278 ),
279 Self::InvalidAccessPath { binding, reason } => write!(
280 formatter,
281 "relational binding {} has an invalid access path: {reason}",
282 binding.get()
283 ),
284 Self::GroupBudgetExceeded {
285 required_groups,
286 max_groups,
287 } => write!(
288 formatter,
289 "relational join enumeration requires {required_groups} groups, exceeding max_groups {max_groups}"
290 ),
291 Self::ExpressionBudgetExceeded {
292 required_expressions,
293 max_expressions,
294 } => write!(
295 formatter,
296 "relational join enumeration requires at least {required_expressions} expressions, exceeding max_expressions {max_expressions}"
297 ),
298 Self::Disconnected => write!(
299 formatter,
300 "relational join graph has no connected left-deep enumeration"
301 ),
302 Self::RequiredPropertiesUnsatisfied => write!(
303 formatter,
304 "no relational join order satisfies the required physical properties"
305 ),
306 }
307 }
308}
309
310impl Error for RelationalJoinEnumerationError {}
311
312pub fn enumerate_relational_inner_joins(
313 graph: &RelationalJoinGraph,
314 required_properties: &RequiredProperties,
315 config: RelationalJoinEnumerationConfig,
316) -> Result<RelationalInnerJoinEnumeration, RelationalJoinEnumerationError> {
317 enumerate_relational_inner_joins_with_cost_contexts(
318 graph,
319 required_properties,
320 config,
321 &crate::RelationalJoinCostContexts::default(),
322 )
323}
324
325pub fn enumerate_relational_inner_joins_with_cost_contexts(
326 graph: &RelationalJoinGraph,
327 required_properties: &RequiredProperties,
328 config: RelationalJoinEnumerationConfig,
329 cost_contexts: &crate::RelationalJoinCostContexts,
330) -> Result<RelationalInnerJoinEnumeration, RelationalJoinEnumerationError> {
331 validate_graph(graph)?;
332 crate::relational_join_hypergraph::enumerate_inner_graph(
333 graph,
334 required_properties,
335 config,
336 cost_contexts,
337 )
338}
339
340fn validate_graph(graph: &RelationalJoinGraph) -> Result<(), RelationalJoinEnumerationError> {
341 if graph.relations.is_empty() {
342 return Err(RelationalJoinEnumerationError::EmptyGraph);
343 }
344 let relation_bindings: BindingSet = graph
345 .relations
346 .iter()
347 .map(|relation| relation.binding)
348 .collect();
349 if relation_bindings.len() != graph.relations.len() {
350 let mut seen = BindingSet::new();
351 let duplicate = graph
352 .relations
353 .iter()
354 .map(|relation| relation.binding)
355 .find(|binding| !seen.insert(*binding))
356 .expect("binding count mismatch requires a duplicate");
357 return Err(RelationalJoinEnumerationError::DuplicateBinding(duplicate));
358 }
359 for relation in &graph.relations {
360 if !relation
361 .access_paths
362 .iter()
363 .any(RelationalJoinAccessPath::supports_base)
364 {
365 return Err(RelationalJoinEnumerationError::MissingBaseAccess(
366 relation.binding,
367 ));
368 }
369 for access in &relation.access_paths {
370 access.descriptor.validate().map_err(|reason| {
371 RelationalJoinEnumerationError::InvalidAccessPath {
372 binding: relation.binding,
373 reason,
374 }
375 })?;
376 access.validate_usage().map_err(|reason| {
377 RelationalJoinEnumerationError::InvalidAccessPath {
378 binding: relation.binding,
379 reason,
380 }
381 })?;
382 if access.required_bindings.contains(relation.binding) {
383 return Err(RelationalJoinEnumerationError::SelfDependentAccess(
384 relation.binding,
385 ));
386 }
387 if let Some(binding) = access
388 .required_bindings
389 .iter()
390 .find(|binding| !relation_bindings.contains(*binding))
391 {
392 return Err(RelationalJoinEnumerationError::UnknownAccessBinding {
393 relation: relation.binding,
394 binding,
395 });
396 }
397 if let Some(binding) = access.required_bindings.iter().find(|binding| {
398 !graph.predicates.iter().any(|predicate| {
399 predicate.bindings.contains(relation.binding)
400 && predicate.bindings.contains(*binding)
401 })
402 }) {
403 return Err(RelationalJoinEnumerationError::AccessWithoutPredicate {
404 relation: relation.binding,
405 binding,
406 });
407 }
408 }
409 }
410
411 let mut predicate_ids = BTreeMap::new();
412 for predicate in &graph.predicates {
413 if predicate_ids.insert(predicate.id, ()).is_some() {
414 return Err(RelationalJoinEnumerationError::DuplicatePredicate(
415 predicate.id,
416 ));
417 }
418 if predicate.bindings.len() < 2 {
419 return Err(
420 RelationalJoinEnumerationError::PredicateHasFewerThanTwoBindings(predicate.id),
421 );
422 }
423 if let Some(binding) = predicate
424 .bindings
425 .iter()
426 .find(|binding| !relation_bindings.contains(*binding))
427 {
428 return Err(RelationalJoinEnumerationError::UnknownPredicateBinding {
429 predicate: predicate.id,
430 binding,
431 });
432 }
433 }
434 Ok(())
435}
436
437fn access_path_properties(descriptor: &RelationalAccessPathDescriptor) -> PhysicalProperties {
438 let ordering_start = descriptor.equality_prefix_len;
439 let ordering_end = ordering_start
440 .saturating_add(descriptor.order_prefix_len)
441 .min(descriptor.index_columns.len());
442 PhysicalProperties {
443 distribution: Distribution::Single,
444 ordering: descriptor.index_columns[ordering_start..ordering_end].to_vec(),
445 covering_fields: if descriptor.covering {
446 descriptor.index_columns.clone()
447 } else {
448 Vec::new()
449 },
450 scan_pruning: match descriptor.kind {
451 RelationalAccessPathKind::FullScan => ScanPruningSupport::None,
452 RelationalAccessPathKind::PrimaryKey | RelationalAccessPathKind::Index => {
453 ScanPruningSupport::Index
454 }
455 },
456 memory_budget: MemoryBudgetClass::Constant,
457 ..PhysicalProperties::default()
458 }
459}
460
461#[cfg(test)]
462mod tests {
463 use super::*;
464 use std::collections::BTreeSet;
465
466 const A: BindingId = BindingId::new(1);
467 const B: BindingId = BindingId::new(2);
468 const C: BindingId = BindingId::new(3);
469
470 fn descriptor(
471 name: &str,
472 kind: RelationalAccessPathKind,
473 rows: usize,
474 unique: bool,
475 ) -> RelationalAccessPathDescriptor {
476 let index_columns = if kind != RelationalAccessPathKind::FullScan {
477 vec!["id".to_string()]
478 } else {
479 Vec::new()
480 };
481 RelationalAccessPathDescriptor {
482 kind,
483 name: name.to_string(),
484 access_columns: index_columns.iter().cloned().collect::<BTreeSet<_>>(),
485 equality_prefix_len: index_columns.len(),
486 index_columns,
487 order_prefix_len: 0,
488 exclusive_range: false,
489 reverse_order: false,
490 unique_point: unique,
491 covering: false,
492 requires_row_fetch: kind == RelationalAccessPathKind::Index,
493 estimated_rows: rows,
494 }
495 }
496
497 fn scan(rows: usize) -> RelationalJoinAccessPath {
498 RelationalJoinAccessPath::base_and_probe(descriptor(
499 "__full_scan",
500 RelationalAccessPathKind::FullScan,
501 rows,
502 false,
503 ))
504 }
505
506 fn probe(name: &str, required: BindingId, rows: usize) -> RelationalJoinAccessPath {
507 RelationalJoinAccessPath::probe(
508 descriptor(name, RelationalAccessPathKind::Index, rows, rows == 1),
509 required.into(),
510 )
511 }
512
513 fn relation_with_probes(
514 binding: BindingId,
515 rows: usize,
516 probes: Vec<RelationalJoinAccessPath>,
517 ) -> RelationalJoinRelation {
518 let mut access_paths = vec![scan(rows)];
519 access_paths.extend(probes);
520 RelationalJoinRelation {
521 binding,
522 access_paths,
523 }
524 }
525
526 fn predicate(id: u32, bindings: impl Into<BindingSet>) -> RelationalJoinPredicate {
527 RelationalJoinPredicate {
528 id: RelationalJoinPredicateId::new(id),
529 bindings: bindings.into(),
530 }
531 }
532
533 #[test]
534 fn memo_enumeration_prefers_selective_outer_and_index_probes() {
535 let graph = RelationalJoinGraph {
536 relations: vec![
537 relation_with_probes(A, 1_000, vec![probe("a_by_b", B, 1)]),
538 relation_with_probes(B, 100, vec![probe("b_by_a", A, 100), probe("b_by_c", C, 1)]),
539 relation_with_probes(C, 1, vec![probe("c_by_b", B, 100)]),
540 ],
541 predicates: vec![predicate(1, [A, B]), predicate(2, [B, C])],
542 };
543
544 let result = enumerate_relational_inner_joins(
545 &graph,
546 &RequiredProperties::default(),
547 RelationalJoinEnumerationConfig::default(),
548 )
549 .unwrap();
550
551 assert_eq!(result.plan.binding_order(), [C, B, A]);
552 assert_eq!(
553 result.plan.cost(),
554 PlanCost {
555 estimated_rows: 1,
556 cost: 21
557 }
558 );
559 assert_eq!(
560 result.plan.steps[0].activated_predicates,
561 [RelationalJoinPredicateId::new(2)]
562 );
563 assert_eq!(
564 result.plan.steps[1].activated_predicates,
565 [RelationalJoinPredicateId::new(1)]
566 );
567 assert!(result.memo_groups >= 5);
568 assert!(result.memo_expressions > result.memo_groups);
569 }
570
571 #[test]
572 fn memo_group_contains_equivalent_join_rewrites() {
573 let graph = RelationalJoinGraph {
574 relations: vec![
575 relation_with_probes(A, 10, vec![probe("a_by_b", B, 1), probe("a_by_c", C, 1)]),
576 relation_with_probes(B, 10, vec![probe("b_by_a", A, 1), probe("b_by_c", C, 1)]),
577 relation_with_probes(C, 10, vec![probe("c_by_a", A, 1), probe("c_by_b", B, 1)]),
578 ],
579 predicates: vec![
580 predicate(1, [A, B]),
581 predicate(2, [B, C]),
582 predicate(3, [A, C]),
583 ],
584 };
585
586 let result = enumerate_relational_inner_joins(
587 &graph,
588 &RequiredProperties::default(),
589 RelationalJoinEnumerationConfig::default(),
590 )
591 .unwrap();
592
593 assert_eq!(result.memo_groups, 7);
594 assert_eq!(result.memo_expressions, 12);
595 assert_eq!(result.plan.binding_order(), [A, B, C]);
596 }
597
598 #[test]
599 fn required_ordering_is_part_of_the_best_plan_cache_key() {
600 let ordered = scan(100).with_properties(PhysicalProperties {
601 distribution: Distribution::Single,
602 ordering: vec!["created_at".to_string()],
603 memory_budget: MemoryBudgetClass::Constant,
604 ..PhysicalProperties::default()
605 });
606 let graph = RelationalJoinGraph {
607 relations: vec![
608 RelationalJoinRelation {
609 binding: A,
610 access_paths: vec![ordered, probe("a_by_b", B, 1)],
611 },
612 relation_with_probes(B, 1, vec![probe("b_by_a", A, 1)]),
613 ],
614 predicates: vec![predicate(1, [A, B])],
615 };
616 let required = RequiredProperties {
617 distribution: Distribution::Single,
618 ordering: vec!["created_at".to_string()],
619 ..RequiredProperties::default()
620 };
621
622 let result = enumerate_relational_inner_joins(
623 &graph,
624 &required,
625 RelationalJoinEnumerationConfig::default(),
626 )
627 .unwrap();
628
629 assert_eq!(result.plan.binding_order(), [A, B]);
630 assert_eq!(result.plan.properties.ordering, ["created_at".to_string()]);
631 }
632
633 #[test]
634 fn base_only_access_paths_are_not_costed_as_inner_probes() {
635 let selective_base = RelationalJoinAccessPath::base(descriptor(
636 "a_constant_filter",
637 RelationalAccessPathKind::Index,
638 5,
639 false,
640 ));
641 let graph = RelationalJoinGraph {
642 relations: vec![
643 RelationalJoinRelation {
644 binding: A,
645 access_paths: vec![scan(100), selective_base, probe("a_by_b", B, 100)],
646 },
647 relation_with_probes(B, 1, Vec::new()),
648 ],
649 predicates: vec![predicate(1, [A, B])],
650 };
651
652 let result = enumerate_relational_inner_joins(
653 &graph,
654 &RequiredProperties::default(),
655 RelationalJoinEnumerationConfig::default(),
656 )
657 .unwrap();
658
659 assert_eq!(result.plan.binding_order(), [A, B]);
660 assert_eq!(
661 result.plan.base_access_path.descriptor.name,
662 "a_constant_filter"
663 );
664 assert_eq!(
665 result.plan.cost(),
666 PlanCost {
667 estimated_rows: 5,
668 cost: 67
669 }
670 );
671 }
672
673 #[test]
674 fn disconnected_graph_does_not_return_a_partial_enumeration() {
675 let graph = RelationalJoinGraph {
676 relations: vec![
677 relation_with_probes(A, 10, Vec::new()),
678 relation_with_probes(B, 10, Vec::new()),
679 ],
680 predicates: Vec::new(),
681 };
682
683 assert_eq!(
684 enumerate_relational_inner_joins(
685 &graph,
686 &RequiredProperties::default(),
687 RelationalJoinEnumerationConfig::default(),
688 ),
689 Err(RelationalJoinEnumerationError::Disconnected)
690 );
691 }
692
693 #[test]
694 fn access_dependencies_require_a_matching_join_predicate() {
695 let graph = RelationalJoinGraph {
696 relations: vec![
697 relation_with_probes(A, 10, vec![probe("a_by_b", B, 1)]),
698 relation_with_probes(B, 10, Vec::new()),
699 relation_with_probes(C, 10, Vec::new()),
700 ],
701 predicates: vec![predicate(1, [A, C])],
702 };
703
704 assert_eq!(
705 enumerate_relational_inner_joins(
706 &graph,
707 &RequiredProperties::default(),
708 RelationalJoinEnumerationConfig::default(),
709 ),
710 Err(RelationalJoinEnumerationError::AccessWithoutPredicate {
711 relation: A,
712 binding: B,
713 })
714 );
715 }
716
717 #[test]
718 fn group_budget_rejects_the_next_actual_connected_group() {
719 let graph = RelationalJoinGraph {
720 relations: vec![
721 relation_with_probes(A, 10, Vec::new()),
722 relation_with_probes(B, 10, Vec::new()),
723 relation_with_probes(C, 10, Vec::new()),
724 ],
725 predicates: vec![predicate(1, [A, B]), predicate(2, [B, C])],
726 };
727
728 assert_eq!(
729 enumerate_relational_inner_joins(
730 &graph,
731 &RequiredProperties::default(),
732 RelationalJoinEnumerationConfig {
733 max_groups: 5,
734 max_expressions: 100,
735 },
736 ),
737 Err(RelationalJoinEnumerationError::GroupBudgetExceeded {
738 required_groups: 6,
739 max_groups: 5,
740 })
741 );
742 }
743}