1use crate::{
7 db::{
8 cursor::ContinuationSignature,
9 direction::Direction,
10 predicate::{CompareOp, MissingRowPolicy, PredicateExecutionModel},
11 query::{
12 builder::scalar_projection::render_scalar_projection_expr_sql_label,
13 plan::{
14 expr::{Expr, FieldId},
15 order_contract::DeterministicSecondaryOrderContract,
16 semantics::LogicalPushdownEligibility,
17 },
18 },
19 },
20 model::field::FieldKind,
21 value::Value,
22};
23
24#[derive(Clone, Copy, Debug, Eq, PartialEq)]
33pub enum QueryMode {
34 Load(LoadSpec),
35 Delete(DeleteSpec),
36}
37
38#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
45pub struct LoadSpec {
46 pub(crate) limit: Option<u32>,
47 pub(crate) offset: u32,
48}
49
50impl LoadSpec {
51 #[must_use]
53 pub const fn limit(&self) -> Option<u32> {
54 self.limit
55 }
56
57 #[must_use]
59 pub const fn offset(&self) -> u32 {
60 self.offset
61 }
62}
63
64#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
72pub struct DeleteSpec {
73 pub(crate) limit: Option<u32>,
74 pub(crate) offset: u32,
75}
76
77impl DeleteSpec {
78 #[must_use]
80 pub const fn limit(&self) -> Option<u32> {
81 self.limit
82 }
83
84 #[must_use]
86 pub const fn offset(&self) -> u32 {
87 self.offset
88 }
89}
90
91#[derive(Clone, Copy, Debug, Eq, PartialEq)]
96pub enum OrderDirection {
97 Asc,
98 Desc,
99}
100
101#[derive(Clone, Eq, PartialEq)]
111pub(crate) struct OrderTerm {
112 pub(crate) expr: Expr,
113 pub(crate) direction: OrderDirection,
114}
115
116impl OrderTerm {
117 #[must_use]
119 pub(crate) const fn new(expr: Expr, direction: OrderDirection) -> Self {
120 Self { expr, direction }
121 }
122
123 #[must_use]
125 pub(crate) fn field(field: impl Into<String>, direction: OrderDirection) -> Self {
126 Self::new(Expr::Field(FieldId::new(field.into())), direction)
127 }
128
129 #[must_use]
131 pub(crate) const fn expr(&self) -> &Expr {
132 &self.expr
133 }
134
135 #[must_use]
137 pub(crate) const fn direct_field(&self) -> Option<&str> {
138 let Expr::Field(field) = &self.expr else {
139 return None;
140 };
141
142 Some(field.as_str())
143 }
144
145 #[must_use]
147 pub(crate) fn rendered_label(&self) -> String {
148 render_scalar_projection_expr_sql_label(&self.expr)
149 }
150
151 #[must_use]
153 pub(crate) const fn direction(&self) -> OrderDirection {
154 self.direction
155 }
156}
157
158impl std::fmt::Debug for OrderTerm {
159 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160 f.debug_struct("OrderTerm")
161 .field("label", &self.rendered_label())
162 .field("expr", &self.expr)
163 .field("direction", &self.direction)
164 .finish()
165 }
166}
167
168impl PartialEq<(String, OrderDirection)> for OrderTerm {
169 fn eq(&self, other: &(String, OrderDirection)) -> bool {
170 self.rendered_label() == other.0 && self.direction == other.1
171 }
172}
173
174impl PartialEq<OrderTerm> for (String, OrderDirection) {
175 fn eq(&self, other: &OrderTerm) -> bool {
176 self.0 == other.rendered_label() && self.1 == other.direction
177 }
178}
179
180#[derive(Clone, Debug, Eq, PartialEq)]
187pub(crate) struct OrderSpec {
188 pub(crate) fields: Vec<OrderTerm>,
189}
190
191#[derive(Clone, Copy, Debug, Eq, PartialEq)]
197pub(crate) struct DeleteLimitSpec {
198 pub(crate) limit: Option<u32>,
199 pub(crate) offset: u32,
200}
201
202#[derive(Clone, Copy, Debug, Eq, PartialEq)]
211pub(crate) enum DistinctExecutionStrategy {
212 None,
213 PreOrdered,
214 HashMaterialize,
215}
216
217impl DistinctExecutionStrategy {
218 #[must_use]
220 pub(crate) const fn is_enabled(self) -> bool {
221 !matches!(self, Self::None)
222 }
223}
224
225#[derive(Clone, Debug, Eq, PartialEq)]
234pub(in crate::db) struct PlannerRouteProfile {
235 continuation_policy: ContinuationPolicy,
236 logical_pushdown_eligibility: LogicalPushdownEligibility,
237 secondary_order_contract: Option<DeterministicSecondaryOrderContract>,
238}
239
240impl PlannerRouteProfile {
241 #[must_use]
243 pub(in crate::db) const fn new(
244 continuation_policy: ContinuationPolicy,
245 logical_pushdown_eligibility: LogicalPushdownEligibility,
246 secondary_order_contract: Option<DeterministicSecondaryOrderContract>,
247 ) -> Self {
248 Self {
249 continuation_policy,
250 logical_pushdown_eligibility,
251 secondary_order_contract,
252 }
253 }
254
255 #[must_use]
258 pub(in crate::db) const fn seeded_unfinalized(is_grouped: bool) -> Self {
259 Self {
260 continuation_policy: ContinuationPolicy::new(true, true, !is_grouped),
261 logical_pushdown_eligibility: LogicalPushdownEligibility::new(false, is_grouped, false),
262 secondary_order_contract: None,
263 }
264 }
265
266 #[must_use]
268 pub(in crate::db) const fn continuation_policy(&self) -> &ContinuationPolicy {
269 &self.continuation_policy
270 }
271
272 #[must_use]
274 pub(in crate::db) const fn logical_pushdown_eligibility(&self) -> LogicalPushdownEligibility {
275 self.logical_pushdown_eligibility
276 }
277
278 #[must_use]
280 pub(in crate::db) const fn secondary_order_contract(
281 &self,
282 ) -> Option<&DeterministicSecondaryOrderContract> {
283 self.secondary_order_contract.as_ref()
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)]
538pub(crate) struct GroupAggregateSpec {
539 pub(crate) kind: AggregateKind,
540 #[cfg(test)]
541 #[allow(dead_code)]
542 #[cfg(test)]
543 pub(crate) target_field: Option<String>,
544 pub(crate) input_expr: Option<Box<Expr>>,
545 pub(crate) filter_expr: Option<Box<Expr>>,
546 pub(crate) distinct: bool,
547}
548
549impl PartialEq for GroupAggregateSpec {
550 fn eq(&self, other: &Self) -> bool {
551 self.kind == other.kind
552 && self.input_expr == other.input_expr
553 && self.filter_expr == other.filter_expr
554 && self.distinct == other.distinct
555 }
556}
557
558impl Eq for GroupAggregateSpec {}
559
560#[derive(Clone, Debug)]
571pub(crate) struct FieldSlot {
572 pub(crate) index: usize,
573 pub(crate) field: String,
574 pub(crate) kind: Option<FieldKind>,
575}
576
577impl PartialEq for FieldSlot {
578 fn eq(&self, other: &Self) -> bool {
579 self.index == other.index && self.field == other.field
580 }
581}
582
583impl Eq for FieldSlot {}
584
585#[derive(Clone, Copy, Debug, Eq, PartialEq)]
594pub(crate) struct GroupedExecutionConfig {
595 pub(crate) max_groups: u64,
596 pub(crate) max_group_bytes: u64,
597}
598
599#[derive(Clone, Debug, Eq, PartialEq)]
608pub(crate) struct GroupSpec {
609 pub(crate) group_fields: Vec<FieldSlot>,
610 pub(crate) aggregates: Vec<GroupAggregateSpec>,
611 pub(crate) execution: GroupedExecutionConfig,
612}
613
614#[derive(Clone, Debug, Eq, PartialEq)]
623pub(crate) enum GroupHavingSymbol {
624 GroupField(FieldSlot),
625 AggregateIndex(usize),
626}
627
628#[derive(Clone, Debug, Eq, PartialEq)]
637pub(crate) struct GroupHavingClause {
638 pub(crate) symbol: GroupHavingSymbol,
639 pub(crate) op: CompareOp,
640 pub(crate) value: Value,
641}
642
643#[derive(Clone, Debug, Eq, PartialEq)]
664pub(crate) struct ScalarPlan {
665 pub(crate) mode: QueryMode,
667
668 pub(crate) predicate: Option<PredicateExecutionModel>,
670
671 pub(crate) order: Option<OrderSpec>,
673
674 pub(crate) distinct: bool,
676
677 pub(crate) delete_limit: Option<DeleteLimitSpec>,
679
680 pub(crate) page: Option<PageSpec>,
682
683 pub(crate) consistency: MissingRowPolicy,
685}
686
687#[derive(Clone, Debug, Eq, PartialEq)]
695pub(crate) struct GroupPlan {
696 pub(crate) scalar: ScalarPlan,
697 pub(crate) group: GroupSpec,
698 pub(crate) having_expr: Option<Expr>,
699}
700
701#[derive(Clone, Debug, Eq, PartialEq)]
711pub(crate) enum LogicalPlan {
712 Scalar(ScalarPlan),
713 Grouped(GroupPlan),
714}