1mod group;
4mod planner;
5#[cfg(test)]
6mod tests;
7pub(crate) mod validate;
8
9use crate::{
10 db::{
11 access::{
12 AccessPath, AccessPlan, PushdownApplicability, SecondaryOrderPushdownEligibility,
13 SecondaryOrderPushdownRejection,
14 },
15 contracts::{PredicateExecutionModel, ReadConsistency},
16 cursor::CursorBoundary,
17 direction::Direction,
18 query::explain::ExplainAccessPath,
19 },
20 error::InternalError,
21 model::entity::{EntityModel, resolve_field_slot},
22 traits::{EntityKind, EntityValue},
23 value::Value,
24};
25use std::ops::Bound;
26#[cfg(test)]
27use std::ops::{Deref, DerefMut};
28
29pub(in crate::db) use crate::db::query::fingerprint::canonical;
30pub(in crate::db) use group::{GroupedExecutorHandoff, grouped_executor_handoff};
31pub use validate::PlanError;
32pub(crate) use validate::{GroupPlanError, validate_group_query_semantics};
33
34#[derive(Clone, Copy, Debug, Eq, PartialEq)]
43pub enum QueryMode {
44 Load(LoadSpec),
45 Delete(DeleteSpec),
46}
47
48impl QueryMode {
49 #[must_use]
51 pub const fn is_load(&self) -> bool {
52 match self {
53 Self::Load(_) => true,
54 Self::Delete(_) => false,
55 }
56 }
57
58 #[must_use]
60 pub const fn is_delete(&self) -> bool {
61 match self {
62 Self::Delete(_) => true,
63 Self::Load(_) => false,
64 }
65 }
66}
67
68#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
75pub struct LoadSpec {
76 pub limit: Option<u32>,
77 pub offset: u32,
78}
79
80impl LoadSpec {
81 #[must_use]
83 pub const fn new() -> Self {
84 Self {
85 limit: None,
86 offset: 0,
87 }
88 }
89}
90
91#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
99pub struct DeleteSpec {
100 pub limit: Option<u32>,
101}
102
103impl DeleteSpec {
104 #[must_use]
106 pub const fn new() -> Self {
107 Self { limit: None }
108 }
109}
110
111#[derive(Clone, Copy, Debug, Eq, PartialEq)]
116pub enum OrderDirection {
117 Asc,
118 Desc,
119}
120
121impl OrderDirection {
122 #[must_use]
124 pub(in crate::db) const fn as_direction(self) -> Direction {
125 match self {
126 Self::Asc => Direction::Asc,
127 Self::Desc => Direction::Desc,
128 }
129 }
130
131 #[must_use]
133 pub(in crate::db) const fn from_direction(direction: Direction) -> Self {
134 match direction {
135 Direction::Asc => Self::Asc,
136 Direction::Desc => Self::Desc,
137 }
138 }
139}
140
141#[derive(Clone, Debug, Eq, PartialEq)]
147pub(crate) struct OrderSpec {
148 pub(crate) fields: Vec<(String, OrderDirection)>,
149}
150
151#[derive(Clone, Copy, Debug, Eq, PartialEq)]
157pub(crate) struct DeleteLimitSpec {
158 pub max_rows: u32,
159}
160
161#[derive(Clone, Debug, Eq, PartialEq)]
167pub(crate) struct PageSpec {
168 pub limit: Option<u32>,
169 pub offset: u32,
170}
171
172#[derive(Clone, Copy, Debug, Eq, PartialEq)]
181pub(crate) enum GroupAggregateKind {
182 Count,
183 Exists,
184 Min,
185 Max,
186 First,
187 Last,
188}
189
190#[derive(Clone, Debug, Eq, PartialEq)]
199pub(crate) struct GroupAggregateSpec {
200 pub(crate) kind: GroupAggregateKind,
201 pub(crate) target_field: Option<String>,
202}
203
204#[derive(Clone, Debug, Eq, PartialEq)]
213pub(crate) struct FieldSlot {
214 index: usize,
215 field: String,
216}
217
218impl FieldSlot {
219 #[must_use]
221 pub(crate) fn resolve(model: &EntityModel, field: &str) -> Option<Self> {
222 let index = resolve_field_slot(model, field)?;
223 let canonical = model
224 .fields
225 .get(index)
226 .map_or(field, |model_field| model_field.name);
227
228 Some(Self {
229 index,
230 field: canonical.to_string(),
231 })
232 }
233
234 #[cfg(test)]
236 #[must_use]
237 pub(crate) fn from_parts_for_test(index: usize, field: impl Into<String>) -> Self {
238 Self {
239 index,
240 field: field.into(),
241 }
242 }
243
244 #[must_use]
246 pub(crate) const fn index(&self) -> usize {
247 self.index
248 }
249
250 #[must_use]
252 pub(crate) fn field(&self) -> &str {
253 &self.field
254 }
255
256 #[must_use]
258 pub(crate) fn canonical_name<'a>(&self, model: &'a EntityModel) -> Option<&'a str> {
259 model.fields.get(self.index).map(|field| field.name)
260 }
261}
262
263#[derive(Clone, Copy, Debug, Eq, PartialEq)]
272pub(crate) struct GroupedExecutionConfig {
273 pub(crate) max_groups: u64,
274 pub(crate) max_group_bytes: u64,
275}
276
277impl GroupedExecutionConfig {
278 #[must_use]
280 pub(crate) const fn with_hard_limits(max_groups: u64, max_group_bytes: u64) -> Self {
281 Self {
282 max_groups,
283 max_group_bytes,
284 }
285 }
286
287 #[must_use]
289 pub(crate) const fn unbounded() -> Self {
290 Self::with_hard_limits(u64::MAX, u64::MAX)
291 }
292
293 #[must_use]
295 pub(crate) const fn max_groups(&self) -> u64 {
296 self.max_groups
297 }
298
299 #[must_use]
301 pub(crate) const fn max_group_bytes(&self) -> u64 {
302 self.max_group_bytes
303 }
304}
305
306impl Default for GroupedExecutionConfig {
307 fn default() -> Self {
308 Self::unbounded()
309 }
310}
311
312#[derive(Clone, Debug, Eq, PartialEq)]
321pub(crate) struct GroupSpec {
322 pub(crate) group_fields: Vec<FieldSlot>,
323 pub(crate) aggregates: Vec<GroupAggregateSpec>,
324 pub(crate) execution: GroupedExecutionConfig,
325}
326
327#[derive(Clone, Debug, Eq, PartialEq)]
348pub(crate) struct ScalarPlan {
349 pub(crate) mode: QueryMode,
351
352 pub(crate) predicate: Option<PredicateExecutionModel>,
354
355 pub(crate) order: Option<OrderSpec>,
357
358 pub(crate) distinct: bool,
360
361 pub(crate) delete_limit: Option<DeleteLimitSpec>,
363
364 pub(crate) page: Option<PageSpec>,
366
367 pub(crate) consistency: ReadConsistency,
369}
370
371#[derive(Clone, Debug, Eq, PartialEq)]
379pub(crate) struct GroupPlan {
380 pub(crate) scalar: ScalarPlan,
381 pub(crate) group: GroupSpec,
382}
383
384#[derive(Clone, Debug, Eq, PartialEq)]
392pub(crate) enum LogicalPlan {
393 Scalar(ScalarPlan),
394 Grouped(GroupPlan),
395}
396
397impl LogicalPlan {
398 #[must_use]
400 pub(in crate::db) const fn scalar_semantics(&self) -> &ScalarPlan {
401 match self {
402 Self::Scalar(plan) => plan,
403 Self::Grouped(plan) => &plan.scalar,
404 }
405 }
406
407 #[must_use]
409 #[cfg(test)]
410 pub(in crate::db) const fn scalar_semantics_mut(&mut self) -> &mut ScalarPlan {
411 match self {
412 Self::Scalar(plan) => plan,
413 Self::Grouped(plan) => &mut plan.scalar,
414 }
415 }
416}
417
418#[cfg(test)]
419impl Deref for LogicalPlan {
420 type Target = ScalarPlan;
421
422 fn deref(&self) -> &Self::Target {
423 self.scalar_semantics()
424 }
425}
426
427#[cfg(test)]
428impl DerefMut for LogicalPlan {
429 fn deref_mut(&mut self) -> &mut Self::Target {
430 self.scalar_semantics_mut()
431 }
432}
433
434#[derive(Clone, Debug, Eq, PartialEq)]
442pub(crate) struct AccessPlannedQuery<K> {
443 pub(crate) logical: LogicalPlan,
444 pub(crate) access: AccessPlan<K>,
445}
446
447impl<K> AccessPlannedQuery<K> {
448 #[must_use]
450 pub(crate) const fn from_parts(logical: LogicalPlan, access: AccessPlan<K>) -> Self {
451 Self { logical, access }
452 }
453
454 #[must_use]
456 pub(crate) fn into_parts(self) -> (LogicalPlan, AccessPlan<K>) {
457 (self.logical, self.access)
458 }
459
460 #[must_use]
462 pub(in crate::db) fn into_grouped(self, group: GroupSpec) -> Self {
463 let Self { logical, access } = self;
464 let scalar = match logical {
465 LogicalPlan::Scalar(plan) => plan,
466 LogicalPlan::Grouped(plan) => plan.scalar,
467 };
468
469 Self {
470 logical: LogicalPlan::Grouped(GroupPlan { scalar, group }),
471 access,
472 }
473 }
474
475 #[must_use]
477 pub(in crate::db) const fn scalar_plan(&self) -> &ScalarPlan {
478 self.logical.scalar_semantics()
479 }
480
481 #[must_use]
483 pub(in crate::db) const fn grouped_plan(&self) -> Option<&GroupPlan> {
484 match &self.logical {
485 LogicalPlan::Scalar(_) => None,
486 LogicalPlan::Grouped(plan) => Some(plan),
487 }
488 }
489
490 #[must_use]
492 #[cfg(test)]
493 pub(in crate::db) const fn scalar_plan_mut(&mut self) -> &mut ScalarPlan {
494 self.logical.scalar_semantics_mut()
495 }
496
497 #[cfg(test)]
501 pub(crate) fn new(
502 access: crate::db::access::AccessPath<K>,
503 consistency: ReadConsistency,
504 ) -> Self {
505 Self {
506 logical: LogicalPlan::Scalar(crate::db::query::plan::ScalarPlan {
507 mode: QueryMode::Load(LoadSpec::new()),
508 predicate: None,
509 order: None,
510 distinct: false,
511 delete_limit: None,
512 page: None,
513 consistency,
514 }),
515 access: AccessPlan::path(access),
516 }
517 }
518
519 pub(in crate::db) fn cursor_boundary_from_entity<E>(
521 &self,
522 entity: &E,
523 ) -> Result<CursorBoundary, InternalError>
524 where
525 E: EntityKind<Key = K> + EntityValue,
526 {
527 let order = match &self.logical {
528 LogicalPlan::Scalar(logical) => logical.order.as_ref(),
529 LogicalPlan::Grouped(_) => {
530 return Err(InternalError::query_executor_invariant(
531 "cannot build cursor boundary for grouped logical plans",
532 ));
533 }
534 };
535 let Some(order) = order else {
536 return Err(InternalError::query_executor_invariant(
537 "cannot build cursor boundary without ordering",
538 ));
539 };
540
541 Ok(CursorBoundary::from_ordered_entity(entity, order))
542 }
543}
544
545#[cfg(test)]
546impl<K> Deref for AccessPlannedQuery<K> {
547 type Target = ScalarPlan;
548
549 fn deref(&self) -> &Self::Target {
550 self.scalar_plan()
551 }
552}
553
554#[cfg(test)]
555impl<K> DerefMut for AccessPlannedQuery<K> {
556 fn deref_mut(&mut self) -> &mut Self::Target {
557 self.scalar_plan_mut()
558 }
559}
560
561fn order_fields_as_direction_refs(
562 order_fields: &[(String, OrderDirection)],
563) -> Vec<(&str, Direction)> {
564 order_fields
565 .iter()
566 .map(|(field, direction)| (field.as_str(), direction.as_direction()))
567 .collect()
568}
569
570fn applicability_from_eligibility(
571 eligibility: SecondaryOrderPushdownEligibility,
572) -> PushdownApplicability {
573 match eligibility {
574 SecondaryOrderPushdownEligibility::Rejected(
575 SecondaryOrderPushdownRejection::NoOrderBy
576 | SecondaryOrderPushdownRejection::AccessPathNotSingleIndexPrefix,
577 ) => PushdownApplicability::NotApplicable,
578 other => PushdownApplicability::Applicable(other),
579 }
580}
581
582fn match_secondary_order_pushdown_core(
584 model: &EntityModel,
585 order_fields: &[(&str, Direction)],
586 index_name: &'static str,
587 index_fields: &[&'static str],
588 prefix_len: usize,
589) -> SecondaryOrderPushdownEligibility {
590 let Some((last_field, last_direction)) = order_fields.last() else {
591 return SecondaryOrderPushdownEligibility::Rejected(
592 SecondaryOrderPushdownRejection::NoOrderBy,
593 );
594 };
595 if *last_field != model.primary_key.name {
596 return SecondaryOrderPushdownEligibility::Rejected(
597 SecondaryOrderPushdownRejection::MissingPrimaryKeyTieBreak {
598 field: model.primary_key.name.to_string(),
599 },
600 );
601 }
602
603 let expected_direction = *last_direction;
604 for (field, direction) in order_fields {
605 if *direction != expected_direction {
606 return SecondaryOrderPushdownEligibility::Rejected(
607 SecondaryOrderPushdownRejection::MixedDirectionNotEligible {
608 field: (*field).to_string(),
609 },
610 );
611 }
612 }
613
614 let actual_non_pk_len = order_fields.len().saturating_sub(1);
615 let matches_expected_suffix = actual_non_pk_len
616 == index_fields.len().saturating_sub(prefix_len)
617 && order_fields
618 .iter()
619 .take(actual_non_pk_len)
620 .map(|(field, _)| *field)
621 .zip(index_fields.iter().skip(prefix_len).copied())
622 .all(|(actual, expected)| actual == expected);
623 let matches_expected_full = actual_non_pk_len == index_fields.len()
624 && order_fields
625 .iter()
626 .take(actual_non_pk_len)
627 .map(|(field, _)| *field)
628 .zip(index_fields.iter().copied())
629 .all(|(actual, expected)| actual == expected);
630 if matches_expected_suffix || matches_expected_full {
631 return SecondaryOrderPushdownEligibility::Eligible {
632 index: index_name,
633 prefix_len,
634 };
635 }
636
637 SecondaryOrderPushdownEligibility::Rejected(
638 SecondaryOrderPushdownRejection::OrderFieldsDoNotMatchIndex {
639 index: index_name,
640 prefix_len,
641 expected_suffix: index_fields
642 .iter()
643 .skip(prefix_len)
644 .map(|field| (*field).to_string())
645 .collect(),
646 expected_full: index_fields
647 .iter()
648 .map(|field| (*field).to_string())
649 .collect(),
650 actual: order_fields
651 .iter()
652 .take(actual_non_pk_len)
653 .map(|(field, _)| (*field).to_string())
654 .collect(),
655 },
656 )
657}
658
659fn assess_secondary_order_pushdown_for_applicable_shape(
661 model: &EntityModel,
662 order_fields: &[(&str, Direction)],
663 index_name: &'static str,
664 index_fields: &[&'static str],
665 prefix_len: usize,
666) -> SecondaryOrderPushdownEligibility {
667 match_secondary_order_pushdown_core(model, order_fields, index_name, index_fields, prefix_len)
668}
669
670fn assess_secondary_order_pushdown_for_plan<K>(
672 model: &EntityModel,
673 order_fields: Option<&[(&str, Direction)]>,
674 access_plan: &AccessPlan<K>,
675) -> SecondaryOrderPushdownEligibility {
676 let Some(order_fields) = order_fields else {
677 return SecondaryOrderPushdownEligibility::Rejected(
678 SecondaryOrderPushdownRejection::NoOrderBy,
679 );
680 };
681 if order_fields.is_empty() {
682 return SecondaryOrderPushdownEligibility::Rejected(
683 SecondaryOrderPushdownRejection::NoOrderBy,
684 );
685 }
686
687 let Some(access) = access_plan.as_path() else {
688 if let Some((index, prefix_len)) = access_plan.first_index_range_details() {
689 return SecondaryOrderPushdownEligibility::Rejected(
690 SecondaryOrderPushdownRejection::AccessPathIndexRangeUnsupported {
691 index: index.name,
692 prefix_len,
693 },
694 );
695 }
696
697 return SecondaryOrderPushdownEligibility::Rejected(
698 SecondaryOrderPushdownRejection::AccessPathNotSingleIndexPrefix,
699 );
700 };
701 if let Some((index, values)) = access.as_index_prefix() {
702 if values.len() > index.fields.len() {
703 return SecondaryOrderPushdownEligibility::Rejected(
704 SecondaryOrderPushdownRejection::InvalidIndexPrefixBounds {
705 prefix_len: values.len(),
706 index_field_len: index.fields.len(),
707 },
708 );
709 }
710
711 return assess_secondary_order_pushdown_for_applicable_shape(
712 model,
713 order_fields,
714 index.name,
715 index.fields,
716 values.len(),
717 );
718 }
719 if let Some((index, prefix_len)) = access.index_range_details() {
720 return SecondaryOrderPushdownEligibility::Rejected(
721 SecondaryOrderPushdownRejection::AccessPathIndexRangeUnsupported {
722 index: index.name,
723 prefix_len,
724 },
725 );
726 }
727
728 SecondaryOrderPushdownEligibility::Rejected(
729 SecondaryOrderPushdownRejection::AccessPathNotSingleIndexPrefix,
730 )
731}
732
733pub(crate) fn assess_secondary_order_pushdown_from_parts<K>(
735 model: &EntityModel,
736 logical: &ScalarPlan,
737 access: &AccessPlan<K>,
738) -> SecondaryOrderPushdownEligibility {
739 let order_fields = logical
740 .order
741 .as_ref()
742 .map(|order| order_fields_as_direction_refs(&order.fields));
743
744 assess_secondary_order_pushdown_for_plan(model, order_fields.as_deref(), access)
745}
746
747pub(crate) fn assess_secondary_order_pushdown<K>(
749 model: &EntityModel,
750 plan: &AccessPlannedQuery<K>,
751) -> SecondaryOrderPushdownEligibility {
752 assess_secondary_order_pushdown_from_parts(model, plan.scalar_plan(), &plan.access)
753}
754
755pub(in crate::db) fn derive_secondary_pushdown_applicability_validated<K>(
757 model: &EntityModel,
758 plan: &AccessPlannedQuery<K>,
759) -> PushdownApplicability {
760 let logical = plan.scalar_plan();
761 debug_assert!(
762 !matches!(logical.order.as_ref(), Some(order) if order.fields.is_empty()),
763 "validated plan must not contain an empty ORDER BY specification",
764 );
765
766 applicability_from_eligibility(assess_secondary_order_pushdown(model, plan))
767}
768
769#[cfg(test)]
770pub(crate) fn assess_secondary_order_pushdown_if_applicable<K>(
772 model: &EntityModel,
773 plan: &AccessPlannedQuery<K>,
774) -> PushdownApplicability {
775 derive_secondary_pushdown_applicability_validated(model, plan)
776}
777
778#[cfg(test)]
781pub(crate) fn assess_secondary_order_pushdown_if_applicable_validated<K>(
782 model: &EntityModel,
783 plan: &AccessPlannedQuery<K>,
784) -> PushdownApplicability {
785 derive_secondary_pushdown_applicability_validated(model, plan)
786}
787
788pub(crate) use planner::{PlannerError, plan_access};
789
790pub(crate) trait AccessPlanProjection<K> {
798 type Output;
799
800 fn by_key(&mut self, key: &K) -> Self::Output;
801 fn by_keys(&mut self, keys: &[K]) -> Self::Output;
802 fn key_range(&mut self, start: &K, end: &K) -> Self::Output;
803 fn index_prefix(
804 &mut self,
805 index_name: &'static str,
806 index_fields: &[&'static str],
807 prefix_len: usize,
808 values: &[Value],
809 ) -> Self::Output;
810 fn index_range(
811 &mut self,
812 index_name: &'static str,
813 index_fields: &[&'static str],
814 prefix_len: usize,
815 prefix: &[Value],
816 lower: &Bound<Value>,
817 upper: &Bound<Value>,
818 ) -> Self::Output;
819 fn full_scan(&mut self) -> Self::Output;
820 fn union(&mut self, children: Vec<Self::Output>) -> Self::Output;
821 fn intersection(&mut self, children: Vec<Self::Output>) -> Self::Output;
822}
823
824pub(crate) fn project_access_plan<K, P>(plan: &AccessPlan<K>, projection: &mut P) -> P::Output
826where
827 P: AccessPlanProjection<K>,
828{
829 plan.project(projection)
830}
831
832impl<K> AccessPlan<K> {
833 fn project<P>(&self, projection: &mut P) -> P::Output
835 where
836 P: AccessPlanProjection<K>,
837 {
838 match self {
839 Self::Path(path) => path.project(projection),
840 Self::Union(children) => {
841 let children = children
842 .iter()
843 .map(|child| child.project(projection))
844 .collect();
845 projection.union(children)
846 }
847 Self::Intersection(children) => {
848 let children = children
849 .iter()
850 .map(|child| child.project(projection))
851 .collect();
852 projection.intersection(children)
853 }
854 }
855 }
856}
857
858impl<K> AccessPath<K> {
859 fn project<P>(&self, projection: &mut P) -> P::Output
861 where
862 P: AccessPlanProjection<K>,
863 {
864 match self {
865 Self::ByKey(key) => projection.by_key(key),
866 Self::ByKeys(keys) => projection.by_keys(keys),
867 Self::KeyRange { start, end } => projection.key_range(start, end),
868 Self::IndexPrefix { index, values } => {
869 projection.index_prefix(index.name, index.fields, values.len(), values)
870 }
871 Self::IndexRange { spec } => projection.index_range(
872 spec.index().name,
873 spec.index().fields,
874 spec.prefix_values().len(),
875 spec.prefix_values(),
876 spec.lower(),
877 spec.upper(),
878 ),
879 Self::FullScan => projection.full_scan(),
880 }
881 }
882}
883
884pub(crate) fn project_explain_access_path<P>(
885 access: &ExplainAccessPath,
886 projection: &mut P,
887) -> P::Output
888where
889 P: AccessPlanProjection<Value>,
890{
891 match access {
892 ExplainAccessPath::ByKey { key } => projection.by_key(key),
893 ExplainAccessPath::ByKeys { keys } => projection.by_keys(keys),
894 ExplainAccessPath::KeyRange { start, end } => projection.key_range(start, end),
895 ExplainAccessPath::IndexPrefix {
896 name,
897 fields,
898 prefix_len,
899 values,
900 } => projection.index_prefix(name, fields, *prefix_len, values),
901 ExplainAccessPath::IndexRange {
902 name,
903 fields,
904 prefix_len,
905 prefix,
906 lower,
907 upper,
908 } => projection.index_range(name, fields, *prefix_len, prefix, lower, upper),
909 ExplainAccessPath::FullScan => projection.full_scan(),
910 ExplainAccessPath::Union(children) => {
911 let children = children
912 .iter()
913 .map(|child| project_explain_access_path(child, projection))
914 .collect();
915 projection.union(children)
916 }
917 ExplainAccessPath::Intersection(children) => {
918 let children = children
919 .iter()
920 .map(|child| project_explain_access_path(child, projection))
921 .collect();
922 projection.intersection(children)
923 }
924 }
925}
926
927#[cfg(test)]
932mod access_projection_tests {
933 use super::*;
934 use crate::{model::index::IndexModel, value::Value};
935
936 const TEST_INDEX_FIELDS: [&str; 2] = ["group", "rank"];
937 const TEST_INDEX: IndexModel = IndexModel::new(
938 "tests::group_rank",
939 "tests::store",
940 &TEST_INDEX_FIELDS,
941 false,
942 );
943
944 #[derive(Default)]
945 struct AccessPlanEventProjection {
946 events: Vec<&'static str>,
947 union_child_counts: Vec<usize>,
948 intersection_child_counts: Vec<usize>,
949 seen_index: Option<(&'static str, usize, usize, usize)>,
950 }
951
952 impl AccessPlanProjection<u64> for AccessPlanEventProjection {
953 type Output = ();
954
955 fn by_key(&mut self, _key: &u64) -> Self::Output {
956 self.events.push("by_key");
957 }
958
959 fn by_keys(&mut self, keys: &[u64]) -> Self::Output {
960 self.events.push("by_keys");
961 assert_eq!(keys, [2, 3].as_slice());
962 }
963
964 fn key_range(&mut self, start: &u64, end: &u64) -> Self::Output {
965 self.events.push("key_range");
966 assert_eq!((*start, *end), (4, 9));
967 }
968
969 fn index_prefix(
970 &mut self,
971 index_name: &'static str,
972 index_fields: &[&'static str],
973 prefix_len: usize,
974 values: &[Value],
975 ) -> Self::Output {
976 self.events.push("index_prefix");
977 self.seen_index = Some((index_name, index_fields.len(), prefix_len, values.len()));
978 }
979
980 fn index_range(
981 &mut self,
982 index_name: &'static str,
983 index_fields: &[&'static str],
984 prefix_len: usize,
985 prefix: &[Value],
986 lower: &Bound<Value>,
987 upper: &Bound<Value>,
988 ) -> Self::Output {
989 self.events.push("index_range");
990 self.seen_index = Some((index_name, index_fields.len(), prefix_len, prefix.len()));
991 assert_eq!(lower, &Bound::Included(Value::Uint(8)));
992 assert_eq!(upper, &Bound::Excluded(Value::Uint(12)));
993 }
994
995 fn full_scan(&mut self) -> Self::Output {
996 self.events.push("full_scan");
997 }
998
999 fn union(&mut self, children: Vec<Self::Output>) -> Self::Output {
1000 self.events.push("union");
1001 self.union_child_counts.push(children.len());
1002 }
1003
1004 fn intersection(&mut self, children: Vec<Self::Output>) -> Self::Output {
1005 self.events.push("intersection");
1006 self.intersection_child_counts.push(children.len());
1007 }
1008 }
1009
1010 #[test]
1011 fn project_access_plan_walks_canonical_access_variants() {
1012 let plan: AccessPlan<u64> = AccessPlan::Union(vec![
1013 AccessPlan::path(AccessPath::ByKey(1)),
1014 AccessPlan::path(AccessPath::ByKeys(vec![2, 3])),
1015 AccessPlan::path(AccessPath::KeyRange { start: 4, end: 9 }),
1016 AccessPlan::path(AccessPath::IndexPrefix {
1017 index: TEST_INDEX,
1018 values: vec![Value::Uint(7)],
1019 }),
1020 AccessPlan::path(AccessPath::index_range(
1021 TEST_INDEX,
1022 vec![Value::Uint(7)],
1023 Bound::Included(Value::Uint(8)),
1024 Bound::Excluded(Value::Uint(12)),
1025 )),
1026 AccessPlan::Intersection(vec![
1027 AccessPlan::path(AccessPath::FullScan),
1028 AccessPlan::path(AccessPath::ByKey(11)),
1029 ]),
1030 ]);
1031
1032 let mut projection = AccessPlanEventProjection::default();
1033 project_access_plan(&plan, &mut projection);
1034
1035 assert_eq!(projection.union_child_counts, vec![6]);
1036 assert_eq!(projection.intersection_child_counts, vec![2]);
1037 assert_eq!(projection.seen_index, Some((TEST_INDEX.name, 2, 1, 1)));
1038 assert!(
1039 projection.events.contains(&"by_key"),
1040 "projection must visit by-key variants"
1041 );
1042 assert!(
1043 projection.events.contains(&"by_keys"),
1044 "projection must visit by-keys variants"
1045 );
1046 assert!(
1047 projection.events.contains(&"key_range"),
1048 "projection must visit key-range variants"
1049 );
1050 assert!(
1051 projection.events.contains(&"index_prefix"),
1052 "projection must visit index-prefix variants"
1053 );
1054 assert!(
1055 projection.events.contains(&"index_range"),
1056 "projection must visit index-range variants"
1057 );
1058 assert!(
1059 projection.events.contains(&"full_scan"),
1060 "projection must visit full-scan variants"
1061 );
1062 }
1063
1064 #[derive(Default)]
1065 struct ExplainAccessEventProjection {
1066 events: Vec<&'static str>,
1067 union_child_counts: Vec<usize>,
1068 intersection_child_counts: Vec<usize>,
1069 seen_index: Option<(&'static str, usize, usize, usize)>,
1070 }
1071
1072 impl AccessPlanProjection<Value> for ExplainAccessEventProjection {
1073 type Output = ();
1074
1075 fn by_key(&mut self, key: &Value) -> Self::Output {
1076 self.events.push("by_key");
1077 assert_eq!(key, &Value::Uint(10));
1078 }
1079
1080 fn by_keys(&mut self, keys: &[Value]) -> Self::Output {
1081 self.events.push("by_keys");
1082 assert_eq!(keys, [Value::Uint(20), Value::Uint(30)].as_slice());
1083 }
1084
1085 fn key_range(&mut self, start: &Value, end: &Value) -> Self::Output {
1086 self.events.push("key_range");
1087 assert_eq!((start, end), (&Value::Uint(40), &Value::Uint(90)));
1088 }
1089
1090 fn index_prefix(
1091 &mut self,
1092 index_name: &'static str,
1093 index_fields: &[&'static str],
1094 prefix_len: usize,
1095 values: &[Value],
1096 ) -> Self::Output {
1097 self.events.push("index_prefix");
1098 self.seen_index = Some((index_name, index_fields.len(), prefix_len, values.len()));
1099 }
1100
1101 fn index_range(
1102 &mut self,
1103 index_name: &'static str,
1104 index_fields: &[&'static str],
1105 prefix_len: usize,
1106 prefix: &[Value],
1107 lower: &Bound<Value>,
1108 upper: &Bound<Value>,
1109 ) -> Self::Output {
1110 self.events.push("index_range");
1111 self.seen_index = Some((index_name, index_fields.len(), prefix_len, prefix.len()));
1112 assert_eq!(lower, &Bound::Included(Value::Uint(8)));
1113 assert_eq!(upper, &Bound::Excluded(Value::Uint(12)));
1114 }
1115
1116 fn full_scan(&mut self) -> Self::Output {
1117 self.events.push("full_scan");
1118 }
1119
1120 fn union(&mut self, children: Vec<Self::Output>) -> Self::Output {
1121 self.events.push("union");
1122 self.union_child_counts.push(children.len());
1123 }
1124
1125 fn intersection(&mut self, children: Vec<Self::Output>) -> Self::Output {
1126 self.events.push("intersection");
1127 self.intersection_child_counts.push(children.len());
1128 }
1129 }
1130
1131 #[test]
1132 fn project_explain_access_path_walks_canonical_access_variants() {
1133 let access = ExplainAccessPath::Union(vec![
1134 ExplainAccessPath::ByKey {
1135 key: Value::Uint(10),
1136 },
1137 ExplainAccessPath::ByKeys {
1138 keys: vec![Value::Uint(20), Value::Uint(30)],
1139 },
1140 ExplainAccessPath::KeyRange {
1141 start: Value::Uint(40),
1142 end: Value::Uint(90),
1143 },
1144 ExplainAccessPath::IndexPrefix {
1145 name: TEST_INDEX.name,
1146 fields: vec!["group", "rank"],
1147 prefix_len: 1,
1148 values: vec![Value::Uint(7)],
1149 },
1150 ExplainAccessPath::IndexRange {
1151 name: TEST_INDEX.name,
1152 fields: vec!["group", "rank"],
1153 prefix_len: 1,
1154 prefix: vec![Value::Uint(7)],
1155 lower: Bound::Included(Value::Uint(8)),
1156 upper: Bound::Excluded(Value::Uint(12)),
1157 },
1158 ExplainAccessPath::Intersection(vec![
1159 ExplainAccessPath::FullScan,
1160 ExplainAccessPath::ByKey {
1161 key: Value::Uint(10),
1162 },
1163 ]),
1164 ]);
1165
1166 let mut projection = ExplainAccessEventProjection::default();
1167 project_explain_access_path(&access, &mut projection);
1168
1169 assert_eq!(projection.union_child_counts, vec![6]);
1170 assert_eq!(projection.intersection_child_counts, vec![2]);
1171 assert_eq!(projection.seen_index, Some((TEST_INDEX.name, 2, 1, 1)));
1172 assert!(
1173 projection.events.contains(&"by_key"),
1174 "projection must visit by-key variants"
1175 );
1176 assert!(
1177 projection.events.contains(&"by_keys"),
1178 "projection must visit by-keys variants"
1179 );
1180 assert!(
1181 projection.events.contains(&"key_range"),
1182 "projection must visit key-range variants"
1183 );
1184 assert!(
1185 projection.events.contains(&"index_prefix"),
1186 "projection must visit index-prefix variants"
1187 );
1188 assert!(
1189 projection.events.contains(&"index_range"),
1190 "projection must visit index-range variants"
1191 );
1192 assert!(
1193 projection.events.contains(&"full_scan"),
1194 "projection must visit full-scan variants"
1195 );
1196 }
1197}