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_index_suffix_plus_primary_key(
166 &self,
167 index_fields: &[&str],
168 prefix_len: usize,
169 primary_key_name: &str,
170 ) -> bool {
171 if prefix_len > index_fields.len() {
172 return false;
173 }
174
175 self.matches_index_field_sequence_plus_primary_key(
176 &index_fields[prefix_len..],
177 primary_key_name,
178 )
179 }
180
181 #[must_use]
184 pub(in crate::db) fn matches_index_full_plus_primary_key(
185 &self,
186 index_fields: &[&str],
187 primary_key_name: &str,
188 ) -> bool {
189 self.matches_index_field_sequence_plus_primary_key(index_fields, primary_key_name)
190 }
191
192 fn matches_index_field_sequence_plus_primary_key(
193 &self,
194 expected_non_pk_fields: &[&str],
195 primary_key_name: &str,
196 ) -> bool {
197 if !self.has_exact_primary_key_tie_break(primary_key_name) {
200 return false;
201 }
202 if self.fields.len() != expected_non_pk_fields.len().saturating_add(1) {
203 return false;
204 }
205
206 self.fields
207 .iter()
208 .take(expected_non_pk_fields.len())
209 .map(|(field, _)| field.as_str())
210 .zip(expected_non_pk_fields.iter().copied())
211 .all(|(actual, expected)| actual == expected)
212 }
213}
214
215#[derive(Clone, Copy, Debug, Eq, PartialEq)]
221pub(crate) struct DeleteLimitSpec {
222 pub(crate) max_rows: u32,
223}
224
225#[derive(Clone, Copy, Debug, Eq, PartialEq)]
234pub(crate) enum DistinctExecutionStrategy {
235 None,
236 PreOrdered,
237 HashMaterialize,
238}
239
240impl DistinctExecutionStrategy {
241 #[must_use]
243 pub(crate) const fn is_enabled(self) -> bool {
244 !matches!(self, Self::None)
245 }
246}
247
248#[derive(Clone, Debug, Eq, PartialEq)]
256pub(in crate::db) struct PlannerRouteProfile {
257 continuation_policy: ContinuationPolicy,
258 logical_pushdown_eligibility: LogicalPushdownEligibility,
259}
260
261impl PlannerRouteProfile {
262 #[must_use]
264 pub(in crate::db) const fn new(
265 continuation_policy: ContinuationPolicy,
266 logical_pushdown_eligibility: LogicalPushdownEligibility,
267 ) -> Self {
268 Self {
269 continuation_policy,
270 logical_pushdown_eligibility,
271 }
272 }
273
274 #[must_use]
276 pub(in crate::db) const fn continuation_policy(&self) -> &ContinuationPolicy {
277 &self.continuation_policy
278 }
279
280 #[must_use]
282 pub(in crate::db) const fn logical_pushdown_eligibility(&self) -> LogicalPushdownEligibility {
283 self.logical_pushdown_eligibility
284 }
285}
286
287#[derive(Clone, Copy, Debug, Eq, PartialEq)]
296pub(in crate::db) struct ContinuationPolicy {
297 requires_anchor: bool,
298 requires_strict_advance: bool,
299 is_grouped_safe: bool,
300}
301
302impl ContinuationPolicy {
303 #[must_use]
305 pub(in crate::db) const fn new(
306 requires_anchor: bool,
307 requires_strict_advance: bool,
308 is_grouped_safe: bool,
309 ) -> Self {
310 Self {
311 requires_anchor,
312 requires_strict_advance,
313 is_grouped_safe,
314 }
315 }
316
317 #[must_use]
319 pub(in crate::db) const fn requires_anchor(self) -> bool {
320 self.requires_anchor
321 }
322
323 #[must_use]
325 pub(in crate::db) const fn requires_strict_advance(self) -> bool {
326 self.requires_strict_advance
327 }
328
329 #[must_use]
331 pub(in crate::db) const fn is_grouped_safe(self) -> bool {
332 self.is_grouped_safe
333 }
334}
335
336#[derive(Clone, Copy, Debug, Eq, PartialEq)]
345pub(in crate::db) struct ExecutionShapeSignature {
346 continuation_signature: ContinuationSignature,
347}
348
349impl ExecutionShapeSignature {
350 #[must_use]
352 pub(in crate::db) const fn new(continuation_signature: ContinuationSignature) -> Self {
353 Self {
354 continuation_signature,
355 }
356 }
357
358 #[must_use]
360 pub(in crate::db) const fn continuation_signature(self) -> ContinuationSignature {
361 self.continuation_signature
362 }
363}
364
365#[derive(Clone, Debug, Eq, PartialEq)]
371pub(crate) struct PageSpec {
372 pub(crate) limit: Option<u32>,
373 pub(crate) offset: u32,
374}
375
376#[derive(Clone, Copy, Debug, Eq, PartialEq)]
386pub enum AggregateKind {
387 Count,
388 Sum,
389 Avg,
390 Exists,
391 Min,
392 Max,
393 First,
394 Last,
395}
396
397impl AggregateKind {
398 #[must_use]
400 pub(in crate::db) const fn sql_label(self) -> &'static str {
401 match self {
402 Self::Count => "COUNT",
403 Self::Sum => "SUM",
404 Self::Avg => "AVG",
405 Self::Exists => "EXISTS",
406 Self::First => "FIRST",
407 Self::Last => "LAST",
408 Self::Min => "MIN",
409 Self::Max => "MAX",
410 }
411 }
412
413 #[must_use]
415 pub(crate) const fn is_count(self) -> bool {
416 matches!(self, Self::Count)
417 }
418
419 #[must_use]
421 pub(in crate::db) const fn is_sum(self) -> bool {
422 matches!(self, Self::Sum | Self::Avg)
423 }
424
425 #[must_use]
427 pub(in crate::db) const fn is_extrema(self) -> bool {
428 matches!(self, Self::Min | Self::Max)
429 }
430
431 #[must_use]
433 pub(in crate::db) const fn requires_decoded_id(self) -> bool {
434 !matches!(self, Self::Count | Self::Sum | Self::Avg | Self::Exists)
435 }
436
437 #[must_use]
439 pub(in crate::db) const fn supports_grouped_distinct_v1(self) -> bool {
440 matches!(
441 self,
442 Self::Count | Self::Min | Self::Max | Self::Sum | Self::Avg
443 )
444 }
445
446 #[must_use]
448 pub(in crate::db) const fn supports_global_distinct_without_group_keys(self) -> bool {
449 matches!(self, Self::Count | Self::Sum | Self::Avg)
450 }
451
452 #[must_use]
454 pub(crate) const fn extrema_direction(self) -> Option<Direction> {
455 match self {
456 Self::Min => Some(Direction::Asc),
457 Self::Max => Some(Direction::Desc),
458 Self::Count | Self::Sum | Self::Avg | Self::Exists | Self::First | Self::Last => None,
459 }
460 }
461
462 #[must_use]
464 pub(crate) const fn materialized_fold_direction(self) -> Direction {
465 match self {
466 Self::Min => Direction::Desc,
467 Self::Count
468 | Self::Sum
469 | Self::Avg
470 | Self::Exists
471 | Self::Max
472 | Self::First
473 | Self::Last => Direction::Asc,
474 }
475 }
476
477 #[must_use]
479 pub(crate) const fn supports_bounded_probe_hint(self) -> bool {
480 !self.is_count() && !self.is_sum()
481 }
482
483 #[must_use]
485 pub(crate) fn bounded_probe_fetch_hint(
486 self,
487 direction: Direction,
488 offset: usize,
489 page_limit: Option<usize>,
490 ) -> Option<usize> {
491 match self {
492 Self::Exists | Self::First => Some(offset.saturating_add(1)),
493 Self::Min if direction == Direction::Asc => Some(offset.saturating_add(1)),
494 Self::Max if direction == Direction::Desc => Some(offset.saturating_add(1)),
495 Self::Last => page_limit.map(|limit| offset.saturating_add(limit)),
496 Self::Count | Self::Sum | Self::Avg | Self::Min | Self::Max => None,
497 }
498 }
499
500 #[must_use]
502 pub(in crate::db) const fn explain_projection_mode_label(
503 self,
504 has_projected_field: bool,
505 covering_projection: bool,
506 ) -> &'static str {
507 if has_projected_field {
508 if covering_projection {
509 "field_idx"
510 } else {
511 "field_mat"
512 }
513 } else if matches!(self, Self::Min | Self::Max | Self::First | Self::Last) {
514 "entity_term"
515 } else {
516 "scalar_agg"
517 }
518 }
519
520 #[must_use]
522 pub(in crate::db) const fn supports_covering_existing_rows_terminal(self) -> bool {
523 matches!(self, Self::Count | Self::Exists)
524 }
525}
526
527#[derive(Clone, Debug, Eq, PartialEq)]
536pub(crate) struct GroupAggregateSpec {
537 pub(crate) kind: AggregateKind,
538 pub(crate) target_field: Option<String>,
539 pub(crate) distinct: bool,
540}
541
542#[derive(Clone, Debug, Eq, PartialEq)]
551pub(crate) struct FieldSlot {
552 pub(crate) index: usize,
553 pub(crate) field: String,
554}
555
556#[derive(Clone, Copy, Debug, Eq, PartialEq)]
565pub(crate) struct GroupedExecutionConfig {
566 pub(crate) max_groups: u64,
567 pub(crate) max_group_bytes: u64,
568}
569
570#[derive(Clone, Debug, Eq, PartialEq)]
579pub(crate) struct GroupSpec {
580 pub(crate) group_fields: Vec<FieldSlot>,
581 pub(crate) aggregates: Vec<GroupAggregateSpec>,
582 pub(crate) execution: GroupedExecutionConfig,
583}
584
585#[derive(Clone, Debug, Eq, PartialEq)]
594pub(crate) enum GroupHavingSymbol {
595 GroupField(FieldSlot),
596 AggregateIndex(usize),
597}
598
599#[derive(Clone, Debug, Eq, PartialEq)]
608pub(crate) struct GroupHavingClause {
609 pub(crate) symbol: GroupHavingSymbol,
610 pub(crate) op: CompareOp,
611 pub(crate) value: Value,
612}
613
614#[derive(Clone, Debug, Eq, PartialEq)]
623pub(crate) struct GroupHavingSpec {
624 pub(crate) clauses: Vec<GroupHavingClause>,
625}
626
627#[derive(Clone, Debug, Eq, PartialEq)]
648pub(crate) struct ScalarPlan {
649 pub(crate) mode: QueryMode,
651
652 pub(crate) predicate: Option<PredicateExecutionModel>,
654
655 pub(crate) order: Option<OrderSpec>,
657
658 pub(crate) distinct: bool,
660
661 pub(crate) delete_limit: Option<DeleteLimitSpec>,
663
664 pub(crate) page: Option<PageSpec>,
666
667 pub(crate) consistency: MissingRowPolicy,
669}
670
671#[derive(Clone, Debug, Eq, PartialEq)]
679pub(crate) struct GroupPlan {
680 pub(crate) scalar: ScalarPlan,
681 pub(crate) group: GroupSpec,
682 pub(crate) having: Option<GroupHavingSpec>,
683}
684
685#[derive(Clone, Debug, Eq, PartialEq)]
693pub(crate) enum LogicalPlan {
694 Scalar(ScalarPlan),
695 Grouped(GroupPlan),
696}