1use crate::{
7 db::{
8 cursor::ContinuationSignature,
9 direction::Direction,
10 predicate::{CompareOp, MissingRowPolicy, PredicateExecutionModel},
11 query::plan::semantics::LogicalPushdownEligibility,
12 },
13 value::Value,
14};
15
16#[derive(Clone, Copy, Debug, Eq, PartialEq)]
25pub enum QueryMode {
26 Load(LoadSpec),
27 Delete(DeleteSpec),
28}
29
30#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
37pub struct LoadSpec {
38 pub(crate) limit: Option<u32>,
39 pub(crate) offset: u32,
40}
41
42impl LoadSpec {
43 #[must_use]
45 pub const fn limit(&self) -> Option<u32> {
46 self.limit
47 }
48
49 #[must_use]
51 pub const fn offset(&self) -> u32 {
52 self.offset
53 }
54}
55
56#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
64pub struct DeleteSpec {
65 pub(crate) limit: Option<u32>,
66}
67
68impl DeleteSpec {
69 #[must_use]
71 pub const fn limit(&self) -> Option<u32> {
72 self.limit
73 }
74}
75
76#[derive(Clone, Copy, Debug, Eq, PartialEq)]
81pub enum OrderDirection {
82 Asc,
83 Desc,
84}
85
86#[derive(Clone, Debug, Eq, PartialEq)]
92pub(crate) struct OrderSpec {
93 pub(crate) fields: Vec<(String, OrderDirection)>,
94}
95
96impl OrderSpec {
97 #[must_use]
99 pub(in crate::db) fn single_field(&self) -> Option<(&str, OrderDirection)> {
100 let [(field, direction)] = self.fields.as_slice() else {
101 return None;
102 };
103
104 Some((field.as_str(), *direction))
105 }
106
107 #[must_use]
109 pub(in crate::db) fn primary_key_only_direction(
110 &self,
111 primary_key_name: &str,
112 ) -> Option<OrderDirection> {
113 let (field, direction) = self.single_field()?;
114 (field == primary_key_name).then_some(direction)
115 }
116
117 #[must_use]
119 pub(in crate::db) fn is_primary_key_only(&self, primary_key_name: &str) -> bool {
120 self.primary_key_only_direction(primary_key_name).is_some()
121 }
122
123 #[must_use]
126 pub(in crate::db) fn has_exact_primary_key_tie_break(&self, primary_key_name: &str) -> bool {
127 let pk_count = self
128 .fields
129 .iter()
130 .filter(|(field, _)| field == primary_key_name)
131 .count();
132 let trailing_pk = self
133 .fields
134 .last()
135 .is_some_and(|(field, _)| field == primary_key_name);
136
137 pk_count == 1 && trailing_pk
138 }
139
140 #[must_use]
143 pub(in crate::db) fn deterministic_secondary_order_direction(
144 &self,
145 primary_key_name: &str,
146 ) -> Option<OrderDirection> {
147 let (_, expected_direction) = self.fields.last()?;
148 if !self.has_exact_primary_key_tie_break(primary_key_name) {
149 return None;
150 }
151 if self
152 .fields
153 .iter()
154 .any(|(_, direction)| *direction != *expected_direction)
155 {
156 return None;
157 }
158
159 Some(*expected_direction)
160 }
161
162 #[must_use]
165 pub(in crate::db) fn matches_expected_term_sequence_plus_primary_key<'a, I>(
166 &self,
167 expected_non_pk_terms: I,
168 primary_key_name: &str,
169 ) -> bool
170 where
171 I: IntoIterator<Item = &'a str>,
172 {
173 let expected_non_pk_terms = expected_non_pk_terms.into_iter().collect::<Vec<_>>();
174
175 if !self.has_exact_primary_key_tie_break(primary_key_name) {
176 return false;
177 }
178 if self.fields.len() != expected_non_pk_terms.len().saturating_add(1) {
179 return false;
180 }
181
182 self.fields
183 .iter()
184 .take(expected_non_pk_terms.len())
185 .map(|(field, _)| field.as_str())
186 .zip(expected_non_pk_terms.iter().copied())
187 .all(|(actual, expected)| actual == expected)
188 }
189
190 #[must_use]
193 pub(in crate::db) fn matches_index_suffix_plus_primary_key(
194 &self,
195 index_fields: &[&str],
196 prefix_len: usize,
197 primary_key_name: &str,
198 ) -> bool {
199 if prefix_len > index_fields.len() {
200 return false;
201 }
202
203 self.matches_expected_term_sequence_plus_primary_key(
204 index_fields[prefix_len..].iter().copied(),
205 primary_key_name,
206 )
207 }
208
209 #[must_use]
212 pub(in crate::db) fn matches_index_full_plus_primary_key(
213 &self,
214 index_fields: &[&str],
215 primary_key_name: &str,
216 ) -> bool {
217 self.matches_expected_term_sequence_plus_primary_key(
218 index_fields.iter().copied(),
219 primary_key_name,
220 )
221 }
222}
223
224#[derive(Clone, Copy, Debug, Eq, PartialEq)]
230pub(crate) struct DeleteLimitSpec {
231 pub(crate) max_rows: u32,
232}
233
234#[derive(Clone, Copy, Debug, Eq, PartialEq)]
243pub(crate) enum DistinctExecutionStrategy {
244 None,
245 PreOrdered,
246 HashMaterialize,
247}
248
249impl DistinctExecutionStrategy {
250 #[must_use]
252 pub(crate) const fn is_enabled(self) -> bool {
253 !matches!(self, Self::None)
254 }
255}
256
257#[derive(Clone, Debug, Eq, PartialEq)]
265pub(in crate::db) struct PlannerRouteProfile {
266 continuation_policy: ContinuationPolicy,
267 logical_pushdown_eligibility: LogicalPushdownEligibility,
268}
269
270impl PlannerRouteProfile {
271 #[must_use]
273 pub(in crate::db) const fn new(
274 continuation_policy: ContinuationPolicy,
275 logical_pushdown_eligibility: LogicalPushdownEligibility,
276 ) -> Self {
277 Self {
278 continuation_policy,
279 logical_pushdown_eligibility,
280 }
281 }
282
283 #[must_use]
285 pub(in crate::db) const fn continuation_policy(&self) -> &ContinuationPolicy {
286 &self.continuation_policy
287 }
288
289 #[must_use]
291 pub(in crate::db) const fn logical_pushdown_eligibility(&self) -> LogicalPushdownEligibility {
292 self.logical_pushdown_eligibility
293 }
294}
295
296#[derive(Clone, Copy, Debug, Eq, PartialEq)]
305pub(in crate::db) struct ContinuationPolicy {
306 requires_anchor: bool,
307 requires_strict_advance: bool,
308 is_grouped_safe: bool,
309}
310
311impl ContinuationPolicy {
312 #[must_use]
314 pub(in crate::db) const fn new(
315 requires_anchor: bool,
316 requires_strict_advance: bool,
317 is_grouped_safe: bool,
318 ) -> Self {
319 Self {
320 requires_anchor,
321 requires_strict_advance,
322 is_grouped_safe,
323 }
324 }
325
326 #[must_use]
328 pub(in crate::db) const fn requires_anchor(self) -> bool {
329 self.requires_anchor
330 }
331
332 #[must_use]
334 pub(in crate::db) const fn requires_strict_advance(self) -> bool {
335 self.requires_strict_advance
336 }
337
338 #[must_use]
340 pub(in crate::db) const fn is_grouped_safe(self) -> bool {
341 self.is_grouped_safe
342 }
343}
344
345#[derive(Clone, Copy, Debug, Eq, PartialEq)]
354pub(in crate::db) struct ExecutionShapeSignature {
355 continuation_signature: ContinuationSignature,
356}
357
358impl ExecutionShapeSignature {
359 #[must_use]
361 pub(in crate::db) const fn new(continuation_signature: ContinuationSignature) -> Self {
362 Self {
363 continuation_signature,
364 }
365 }
366
367 #[must_use]
369 pub(in crate::db) const fn continuation_signature(self) -> ContinuationSignature {
370 self.continuation_signature
371 }
372}
373
374#[derive(Clone, Debug, Eq, PartialEq)]
380pub(crate) struct PageSpec {
381 pub(crate) limit: Option<u32>,
382 pub(crate) offset: u32,
383}
384
385#[derive(Clone, Copy, Debug, Eq, PartialEq)]
395pub enum AggregateKind {
396 Count,
397 Sum,
398 Avg,
399 Exists,
400 Min,
401 Max,
402 First,
403 Last,
404}
405
406impl AggregateKind {
407 #[must_use]
409 pub(in crate::db) const fn sql_label(self) -> &'static str {
410 match self {
411 Self::Count => "COUNT",
412 Self::Sum => "SUM",
413 Self::Avg => "AVG",
414 Self::Exists => "EXISTS",
415 Self::First => "FIRST",
416 Self::Last => "LAST",
417 Self::Min => "MIN",
418 Self::Max => "MAX",
419 }
420 }
421
422 #[must_use]
424 pub(crate) const fn is_count(self) -> bool {
425 matches!(self, Self::Count)
426 }
427
428 #[must_use]
430 pub(in crate::db) const fn is_sum(self) -> bool {
431 matches!(self, Self::Sum | Self::Avg)
432 }
433
434 #[must_use]
436 pub(in crate::db) const fn is_extrema(self) -> bool {
437 matches!(self, Self::Min | Self::Max)
438 }
439
440 #[must_use]
442 pub(in crate::db) const fn requires_decoded_id(self) -> bool {
443 !matches!(self, Self::Count | Self::Sum | Self::Avg | Self::Exists)
444 }
445
446 #[must_use]
448 pub(in crate::db) const fn supports_grouped_distinct_v1(self) -> bool {
449 matches!(
450 self,
451 Self::Count | Self::Min | Self::Max | Self::Sum | Self::Avg
452 )
453 }
454
455 #[must_use]
457 pub(in crate::db) const fn supports_global_distinct_without_group_keys(self) -> bool {
458 matches!(self, Self::Count | Self::Sum | Self::Avg)
459 }
460
461 #[must_use]
463 pub(crate) const fn extrema_direction(self) -> Option<Direction> {
464 match self {
465 Self::Min => Some(Direction::Asc),
466 Self::Max => Some(Direction::Desc),
467 Self::Count | Self::Sum | Self::Avg | Self::Exists | Self::First | Self::Last => None,
468 }
469 }
470
471 #[must_use]
473 pub(crate) const fn materialized_fold_direction(self) -> Direction {
474 match self {
475 Self::Min => Direction::Desc,
476 Self::Count
477 | Self::Sum
478 | Self::Avg
479 | Self::Exists
480 | Self::Max
481 | Self::First
482 | Self::Last => Direction::Asc,
483 }
484 }
485
486 #[must_use]
488 pub(crate) const fn supports_bounded_probe_hint(self) -> bool {
489 !self.is_count() && !self.is_sum()
490 }
491
492 #[must_use]
494 pub(crate) fn bounded_probe_fetch_hint(
495 self,
496 direction: Direction,
497 offset: usize,
498 page_limit: Option<usize>,
499 ) -> Option<usize> {
500 match self {
501 Self::Exists | Self::First => Some(offset.saturating_add(1)),
502 Self::Min if direction == Direction::Asc => Some(offset.saturating_add(1)),
503 Self::Max if direction == Direction::Desc => Some(offset.saturating_add(1)),
504 Self::Last => page_limit.map(|limit| offset.saturating_add(limit)),
505 Self::Count | Self::Sum | Self::Avg | Self::Min | Self::Max => None,
506 }
507 }
508
509 #[must_use]
511 pub(in crate::db) const fn explain_projection_mode_label(
512 self,
513 has_projected_field: bool,
514 covering_projection: bool,
515 ) -> &'static str {
516 if has_projected_field {
517 if covering_projection {
518 "field_idx"
519 } else {
520 "field_mat"
521 }
522 } else if matches!(self, Self::Min | Self::Max | Self::First | Self::Last) {
523 "entity_term"
524 } else {
525 "scalar_agg"
526 }
527 }
528
529 #[must_use]
531 pub(in crate::db) const fn supports_covering_existing_rows_terminal(self) -> bool {
532 matches!(self, Self::Count | Self::Exists)
533 }
534}
535
536#[derive(Clone, Debug, Eq, PartialEq)]
545pub(crate) struct GroupAggregateSpec {
546 pub(crate) kind: AggregateKind,
547 pub(crate) target_field: Option<String>,
548 pub(crate) distinct: bool,
549}
550
551#[derive(Clone, Debug, Eq, PartialEq)]
560pub(crate) struct FieldSlot {
561 pub(crate) index: usize,
562 pub(crate) field: String,
563}
564
565#[derive(Clone, Copy, Debug, Eq, PartialEq)]
574pub(crate) struct GroupedExecutionConfig {
575 pub(crate) max_groups: u64,
576 pub(crate) max_group_bytes: u64,
577}
578
579#[derive(Clone, Debug, Eq, PartialEq)]
588pub(crate) struct GroupSpec {
589 pub(crate) group_fields: Vec<FieldSlot>,
590 pub(crate) aggregates: Vec<GroupAggregateSpec>,
591 pub(crate) execution: GroupedExecutionConfig,
592}
593
594#[derive(Clone, Debug, Eq, PartialEq)]
603pub(crate) enum GroupHavingSymbol {
604 GroupField(FieldSlot),
605 AggregateIndex(usize),
606}
607
608#[derive(Clone, Debug, Eq, PartialEq)]
617pub(crate) struct GroupHavingClause {
618 pub(crate) symbol: GroupHavingSymbol,
619 pub(crate) op: CompareOp,
620 pub(crate) value: Value,
621}
622
623#[derive(Clone, Debug, Eq, PartialEq)]
632pub(crate) struct GroupHavingSpec {
633 pub(crate) clauses: Vec<GroupHavingClause>,
634}
635
636#[derive(Clone, Debug, Eq, PartialEq)]
657pub(crate) struct ScalarPlan {
658 pub(crate) mode: QueryMode,
660
661 pub(crate) predicate: Option<PredicateExecutionModel>,
663
664 pub(crate) order: Option<OrderSpec>,
666
667 pub(crate) distinct: bool,
669
670 pub(crate) delete_limit: Option<DeleteLimitSpec>,
672
673 pub(crate) page: Option<PageSpec>,
675
676 pub(crate) consistency: MissingRowPolicy,
678}
679
680#[derive(Clone, Debug, Eq, PartialEq)]
688pub(crate) struct GroupPlan {
689 pub(crate) scalar: ScalarPlan,
690 pub(crate) group: GroupSpec,
691 pub(crate) having: Option<GroupHavingSpec>,
692}
693
694#[derive(Clone, Debug, Eq, PartialEq)]
702pub(crate) enum LogicalPlan {
703 Scalar(ScalarPlan),
704 Grouped(GroupPlan),
705}