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 range: None,
490 reverse_order: false,
491 unique_point: unique,
492 covering: false,
493 requires_row_fetch: kind == RelationalAccessPathKind::Index,
494 estimated_rows: rows,
495 }
496 }
497
498 fn scan(rows: usize) -> RelationalJoinAccessPath {
499 RelationalJoinAccessPath::base_and_probe(descriptor(
500 "__full_scan",
501 RelationalAccessPathKind::FullScan,
502 rows,
503 false,
504 ))
505 }
506
507 fn probe(name: &str, required: BindingId, rows: usize) -> RelationalJoinAccessPath {
508 RelationalJoinAccessPath::probe(
509 descriptor(name, RelationalAccessPathKind::Index, rows, rows == 1),
510 required.into(),
511 )
512 }
513
514 fn relation_with_probes(
515 binding: BindingId,
516 rows: usize,
517 probes: Vec<RelationalJoinAccessPath>,
518 ) -> RelationalJoinRelation {
519 let mut access_paths = vec![scan(rows)];
520 access_paths.extend(probes);
521 RelationalJoinRelation {
522 binding,
523 access_paths,
524 }
525 }
526
527 fn predicate(id: u32, bindings: impl Into<BindingSet>) -> RelationalJoinPredicate {
528 RelationalJoinPredicate {
529 id: RelationalJoinPredicateId::new(id),
530 bindings: bindings.into(),
531 }
532 }
533
534 #[test]
535 fn memo_enumeration_prefers_selective_outer_and_index_probes() {
536 let graph = RelationalJoinGraph {
537 relations: vec![
538 relation_with_probes(A, 1_000, vec![probe("a_by_b", B, 1)]),
539 relation_with_probes(B, 100, vec![probe("b_by_a", A, 100), probe("b_by_c", C, 1)]),
540 relation_with_probes(C, 1, vec![probe("c_by_b", B, 100)]),
541 ],
542 predicates: vec![predicate(1, [A, B]), predicate(2, [B, C])],
543 };
544
545 let result = enumerate_relational_inner_joins(
546 &graph,
547 &RequiredProperties::default(),
548 RelationalJoinEnumerationConfig::default(),
549 )
550 .unwrap();
551
552 assert_eq!(result.plan.binding_order(), [C, B, A]);
553 assert_eq!(
554 result.plan.cost(),
555 PlanCost {
556 estimated_rows: 1,
557 cost: 21
558 }
559 );
560 assert_eq!(
561 result.plan.steps[0].activated_predicates,
562 [RelationalJoinPredicateId::new(2)]
563 );
564 assert_eq!(
565 result.plan.steps[1].activated_predicates,
566 [RelationalJoinPredicateId::new(1)]
567 );
568 assert!(result.memo_groups >= 5);
569 assert!(result.memo_expressions > result.memo_groups);
570 }
571
572 #[test]
573 fn memo_group_contains_equivalent_join_rewrites() {
574 let graph = RelationalJoinGraph {
575 relations: vec![
576 relation_with_probes(A, 10, vec![probe("a_by_b", B, 1), probe("a_by_c", C, 1)]),
577 relation_with_probes(B, 10, vec![probe("b_by_a", A, 1), probe("b_by_c", C, 1)]),
578 relation_with_probes(C, 10, vec![probe("c_by_a", A, 1), probe("c_by_b", B, 1)]),
579 ],
580 predicates: vec![
581 predicate(1, [A, B]),
582 predicate(2, [B, C]),
583 predicate(3, [A, C]),
584 ],
585 };
586
587 let result = enumerate_relational_inner_joins(
588 &graph,
589 &RequiredProperties::default(),
590 RelationalJoinEnumerationConfig::default(),
591 )
592 .unwrap();
593
594 assert_eq!(result.memo_groups, 7);
595 assert_eq!(result.memo_expressions, 12);
596 assert_eq!(result.plan.binding_order(), [A, B, C]);
597 }
598
599 #[test]
600 fn required_ordering_is_part_of_the_best_plan_cache_key() {
601 let ordered = scan(100).with_properties(PhysicalProperties {
602 distribution: Distribution::Single,
603 ordering: vec!["created_at".to_string()],
604 memory_budget: MemoryBudgetClass::Constant,
605 ..PhysicalProperties::default()
606 });
607 let graph = RelationalJoinGraph {
608 relations: vec![
609 RelationalJoinRelation {
610 binding: A,
611 access_paths: vec![ordered, probe("a_by_b", B, 1)],
612 },
613 relation_with_probes(B, 1, vec![probe("b_by_a", A, 1)]),
614 ],
615 predicates: vec![predicate(1, [A, B])],
616 };
617 let required = RequiredProperties {
618 distribution: Distribution::Single,
619 ordering: vec!["created_at".to_string()],
620 ..RequiredProperties::default()
621 };
622
623 let result = enumerate_relational_inner_joins(
624 &graph,
625 &required,
626 RelationalJoinEnumerationConfig::default(),
627 )
628 .unwrap();
629
630 assert_eq!(result.plan.binding_order(), [A, B]);
631 assert_eq!(result.plan.properties.ordering, ["created_at".to_string()]);
632 }
633
634 #[test]
635 fn base_only_access_paths_are_not_costed_as_inner_probes() {
636 let selective_base = RelationalJoinAccessPath::base(descriptor(
637 "a_constant_filter",
638 RelationalAccessPathKind::Index,
639 5,
640 false,
641 ));
642 let graph = RelationalJoinGraph {
643 relations: vec![
644 RelationalJoinRelation {
645 binding: A,
646 access_paths: vec![scan(100), selective_base, probe("a_by_b", B, 100)],
647 },
648 relation_with_probes(B, 1, Vec::new()),
649 ],
650 predicates: vec![predicate(1, [A, B])],
651 };
652
653 let result = enumerate_relational_inner_joins(
654 &graph,
655 &RequiredProperties::default(),
656 RelationalJoinEnumerationConfig::default(),
657 )
658 .unwrap();
659
660 assert_eq!(result.plan.binding_order(), [A, B]);
661 assert_eq!(
662 result.plan.base_access_path.descriptor.name,
663 "a_constant_filter"
664 );
665 assert_eq!(
666 result.plan.cost(),
667 PlanCost {
668 estimated_rows: 5,
669 cost: 67
670 }
671 );
672 }
673
674 #[test]
675 fn disconnected_graph_does_not_return_a_partial_enumeration() {
676 let graph = RelationalJoinGraph {
677 relations: vec![
678 relation_with_probes(A, 10, Vec::new()),
679 relation_with_probes(B, 10, Vec::new()),
680 ],
681 predicates: Vec::new(),
682 };
683
684 assert_eq!(
685 enumerate_relational_inner_joins(
686 &graph,
687 &RequiredProperties::default(),
688 RelationalJoinEnumerationConfig::default(),
689 ),
690 Err(RelationalJoinEnumerationError::Disconnected)
691 );
692 }
693
694 #[test]
695 fn access_dependencies_require_a_matching_join_predicate() {
696 let graph = RelationalJoinGraph {
697 relations: vec![
698 relation_with_probes(A, 10, vec![probe("a_by_b", B, 1)]),
699 relation_with_probes(B, 10, Vec::new()),
700 relation_with_probes(C, 10, Vec::new()),
701 ],
702 predicates: vec![predicate(1, [A, C])],
703 };
704
705 assert_eq!(
706 enumerate_relational_inner_joins(
707 &graph,
708 &RequiredProperties::default(),
709 RelationalJoinEnumerationConfig::default(),
710 ),
711 Err(RelationalJoinEnumerationError::AccessWithoutPredicate {
712 relation: A,
713 binding: B,
714 })
715 );
716 }
717
718 #[test]
719 fn group_budget_rejects_the_next_actual_connected_group() {
720 let graph = RelationalJoinGraph {
721 relations: vec![
722 relation_with_probes(A, 10, Vec::new()),
723 relation_with_probes(B, 10, Vec::new()),
724 relation_with_probes(C, 10, Vec::new()),
725 ],
726 predicates: vec![predicate(1, [A, B]), predicate(2, [B, C])],
727 };
728
729 assert_eq!(
730 enumerate_relational_inner_joins(
731 &graph,
732 &RequiredProperties::default(),
733 RelationalJoinEnumerationConfig {
734 max_groups: 5,
735 max_expressions: 100,
736 },
737 ),
738 Err(RelationalJoinEnumerationError::GroupBudgetExceeded {
739 required_groups: 6,
740 max_groups: 5,
741 })
742 );
743 }
744}