1use crate::{
7 db::{
8 cursor::ContinuationSignature,
9 direction::Direction,
10 predicate::{CompareOp, MissingRowPolicy, PredicateExecutionModel},
11 query::plan::{
12 expr::{BinaryOp, Expr, Function},
13 order_contract::DeterministicSecondaryOrderContract,
14 semantics::LogicalPushdownEligibility,
15 },
16 },
17 model::field::FieldKind,
18 value::Value,
19};
20
21#[derive(Clone, Copy, Debug, Eq, PartialEq)]
30pub enum QueryMode {
31 Load(LoadSpec),
32 Delete(DeleteSpec),
33}
34
35#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
42pub struct LoadSpec {
43 pub(crate) limit: Option<u32>,
44 pub(crate) offset: u32,
45}
46
47impl LoadSpec {
48 #[must_use]
50 pub const fn limit(&self) -> Option<u32> {
51 self.limit
52 }
53
54 #[must_use]
56 pub const fn offset(&self) -> u32 {
57 self.offset
58 }
59}
60
61#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
69pub struct DeleteSpec {
70 pub(crate) limit: Option<u32>,
71 pub(crate) offset: u32,
72}
73
74impl DeleteSpec {
75 #[must_use]
77 pub const fn limit(&self) -> Option<u32> {
78 self.limit
79 }
80
81 #[must_use]
83 pub const fn offset(&self) -> u32 {
84 self.offset
85 }
86}
87
88#[derive(Clone, Copy, Debug, Eq, PartialEq)]
93pub enum OrderDirection {
94 Asc,
95 Desc,
96}
97
98#[derive(Clone, Debug, Eq, PartialEq)]
104pub(crate) struct OrderSpec {
105 pub(crate) fields: Vec<(String, OrderDirection)>,
106}
107
108#[derive(Clone, Copy, Debug, Eq, PartialEq)]
114pub(crate) struct DeleteLimitSpec {
115 pub(crate) limit: Option<u32>,
116 pub(crate) offset: u32,
117}
118
119#[derive(Clone, Copy, Debug, Eq, PartialEq)]
128pub(crate) enum DistinctExecutionStrategy {
129 None,
130 PreOrdered,
131 HashMaterialize,
132}
133
134impl DistinctExecutionStrategy {
135 #[must_use]
137 pub(crate) const fn is_enabled(self) -> bool {
138 !matches!(self, Self::None)
139 }
140}
141
142#[derive(Clone, Debug, Eq, PartialEq)]
151pub(in crate::db) struct PlannerRouteProfile {
152 continuation_policy: ContinuationPolicy,
153 logical_pushdown_eligibility: LogicalPushdownEligibility,
154 secondary_order_contract: Option<DeterministicSecondaryOrderContract>,
155}
156
157impl PlannerRouteProfile {
158 #[must_use]
160 pub(in crate::db) const fn new(
161 continuation_policy: ContinuationPolicy,
162 logical_pushdown_eligibility: LogicalPushdownEligibility,
163 secondary_order_contract: Option<DeterministicSecondaryOrderContract>,
164 ) -> Self {
165 Self {
166 continuation_policy,
167 logical_pushdown_eligibility,
168 secondary_order_contract,
169 }
170 }
171
172 #[must_use]
175 pub(in crate::db) const fn seeded_unfinalized(is_grouped: bool) -> Self {
176 Self {
177 continuation_policy: ContinuationPolicy::new(true, true, !is_grouped),
178 logical_pushdown_eligibility: LogicalPushdownEligibility::new(false, is_grouped, false),
179 secondary_order_contract: None,
180 }
181 }
182
183 #[must_use]
185 pub(in crate::db) const fn continuation_policy(&self) -> &ContinuationPolicy {
186 &self.continuation_policy
187 }
188
189 #[must_use]
191 pub(in crate::db) const fn logical_pushdown_eligibility(&self) -> LogicalPushdownEligibility {
192 self.logical_pushdown_eligibility
193 }
194
195 #[must_use]
197 pub(in crate::db) const fn secondary_order_contract(
198 &self,
199 ) -> Option<&DeterministicSecondaryOrderContract> {
200 self.secondary_order_contract.as_ref()
201 }
202}
203
204#[derive(Clone, Copy, Debug, Eq, PartialEq)]
213pub(in crate::db) struct ContinuationPolicy {
214 requires_anchor: bool,
215 requires_strict_advance: bool,
216 is_grouped_safe: bool,
217}
218
219impl ContinuationPolicy {
220 #[must_use]
222 pub(in crate::db) const fn new(
223 requires_anchor: bool,
224 requires_strict_advance: bool,
225 is_grouped_safe: bool,
226 ) -> Self {
227 Self {
228 requires_anchor,
229 requires_strict_advance,
230 is_grouped_safe,
231 }
232 }
233
234 #[must_use]
236 pub(in crate::db) const fn requires_anchor(self) -> bool {
237 self.requires_anchor
238 }
239
240 #[must_use]
242 pub(in crate::db) const fn requires_strict_advance(self) -> bool {
243 self.requires_strict_advance
244 }
245
246 #[must_use]
248 pub(in crate::db) const fn is_grouped_safe(self) -> bool {
249 self.is_grouped_safe
250 }
251}
252
253#[derive(Clone, Copy, Debug, Eq, PartialEq)]
262pub(in crate::db) struct ExecutionShapeSignature {
263 continuation_signature: ContinuationSignature,
264}
265
266impl ExecutionShapeSignature {
267 #[must_use]
269 pub(in crate::db) const fn new(continuation_signature: ContinuationSignature) -> Self {
270 Self {
271 continuation_signature,
272 }
273 }
274
275 #[must_use]
277 pub(in crate::db) const fn continuation_signature(self) -> ContinuationSignature {
278 self.continuation_signature
279 }
280}
281
282#[derive(Clone, Debug, Eq, PartialEq)]
288pub(crate) struct PageSpec {
289 pub(crate) limit: Option<u32>,
290 pub(crate) offset: u32,
291}
292
293#[derive(Clone, Copy, Debug, Eq, PartialEq)]
303pub enum AggregateKind {
304 Count,
305 Sum,
306 Avg,
307 Exists,
308 Min,
309 Max,
310 First,
311 Last,
312}
313
314impl AggregateKind {
315 #[must_use]
317 pub(in crate::db) const fn sql_label(self) -> &'static str {
318 match self {
319 Self::Count => "COUNT",
320 Self::Sum => "SUM",
321 Self::Avg => "AVG",
322 Self::Exists => "EXISTS",
323 Self::First => "FIRST",
324 Self::Last => "LAST",
325 Self::Min => "MIN",
326 Self::Max => "MAX",
327 }
328 }
329
330 #[must_use]
332 pub(crate) const fn is_count(self) -> bool {
333 matches!(self, Self::Count)
334 }
335
336 #[must_use]
338 pub(in crate::db) const fn is_sum(self) -> bool {
339 matches!(self, Self::Sum | Self::Avg)
340 }
341
342 #[must_use]
344 pub(in crate::db) const fn is_extrema(self) -> bool {
345 matches!(self, Self::Min | Self::Max)
346 }
347
348 #[must_use]
350 pub(in crate::db) const fn requires_decoded_id(self) -> bool {
351 !matches!(self, Self::Count | Self::Sum | Self::Avg | Self::Exists)
352 }
353
354 #[must_use]
356 pub(in crate::db) const fn supports_grouped_distinct_v1(self) -> bool {
357 matches!(
358 self,
359 Self::Count | Self::Min | Self::Max | Self::Sum | Self::Avg
360 )
361 }
362
363 #[must_use]
365 pub(in crate::db) const fn supports_global_distinct_without_group_keys(self) -> bool {
366 matches!(self, Self::Count | Self::Sum | Self::Avg)
367 }
368
369 #[must_use]
371 pub(crate) const fn extrema_direction(self) -> Option<Direction> {
372 match self {
373 Self::Min => Some(Direction::Asc),
374 Self::Max => Some(Direction::Desc),
375 Self::Count | Self::Sum | Self::Avg | Self::Exists | Self::First | Self::Last => None,
376 }
377 }
378
379 #[must_use]
381 pub(crate) const fn materialized_fold_direction(self) -> Direction {
382 match self {
383 Self::Min => Direction::Desc,
384 Self::Count
385 | Self::Sum
386 | Self::Avg
387 | Self::Exists
388 | Self::Max
389 | Self::First
390 | Self::Last => Direction::Asc,
391 }
392 }
393
394 #[must_use]
396 pub(crate) const fn supports_bounded_probe_hint(self) -> bool {
397 !self.is_count() && !self.is_sum()
398 }
399
400 #[must_use]
402 pub(crate) fn bounded_probe_fetch_hint(
403 self,
404 direction: Direction,
405 offset: usize,
406 page_limit: Option<usize>,
407 ) -> Option<usize> {
408 match self {
409 Self::Exists | Self::First => Some(offset.saturating_add(1)),
410 Self::Min if direction == Direction::Asc => Some(offset.saturating_add(1)),
411 Self::Max if direction == Direction::Desc => Some(offset.saturating_add(1)),
412 Self::Last => page_limit.map(|limit| offset.saturating_add(limit)),
413 Self::Count | Self::Sum | Self::Avg | Self::Min | Self::Max => None,
414 }
415 }
416
417 #[must_use]
419 pub(in crate::db) const fn explain_projection_mode_label(
420 self,
421 has_projected_field: bool,
422 covering_projection: bool,
423 ) -> &'static str {
424 if has_projected_field {
425 if covering_projection {
426 "field_idx"
427 } else {
428 "field_mat"
429 }
430 } else if matches!(self, Self::Min | Self::Max | Self::First | Self::Last) {
431 "entity_term"
432 } else {
433 "scalar_agg"
434 }
435 }
436
437 #[must_use]
439 pub(in crate::db) const fn supports_covering_existing_rows_terminal(self) -> bool {
440 matches!(self, Self::Count | Self::Exists)
441 }
442}
443
444#[derive(Clone, Debug, Eq, PartialEq)]
456pub(crate) struct GroupAggregateSpec {
457 pub(crate) kind: AggregateKind,
458 pub(crate) target_field: Option<String>,
459 pub(crate) input_expr: Option<Box<Expr>>,
460 pub(crate) distinct: bool,
461}
462
463#[derive(Clone, Debug)]
474pub(crate) struct FieldSlot {
475 pub(crate) index: usize,
476 pub(crate) field: String,
477 pub(crate) kind: Option<FieldKind>,
478}
479
480impl PartialEq for FieldSlot {
481 fn eq(&self, other: &Self) -> bool {
482 self.index == other.index && self.field == other.field
483 }
484}
485
486impl Eq for FieldSlot {}
487
488#[derive(Clone, Copy, Debug, Eq, PartialEq)]
497pub(crate) struct GroupedExecutionConfig {
498 pub(crate) max_groups: u64,
499 pub(crate) max_group_bytes: u64,
500}
501
502#[derive(Clone, Debug, Eq, PartialEq)]
511pub(crate) struct GroupSpec {
512 pub(crate) group_fields: Vec<FieldSlot>,
513 pub(crate) aggregates: Vec<GroupAggregateSpec>,
514 pub(crate) execution: GroupedExecutionConfig,
515}
516
517#[derive(Clone, Debug, Eq, PartialEq)]
526pub(crate) enum GroupHavingSymbol {
527 GroupField(FieldSlot),
528 AggregateIndex(usize),
529}
530
531#[derive(Clone, Debug, Eq, PartialEq)]
540pub(crate) struct GroupHavingClause {
541 pub(crate) symbol: GroupHavingSymbol,
542 pub(crate) op: CompareOp,
543 pub(crate) value: Value,
544}
545
546#[derive(Clone, Debug, Eq, PartialEq)]
555pub(crate) enum GroupHavingValueExpr {
556 GroupField(FieldSlot),
557 AggregateIndex(usize),
558 Literal(Value),
559 FunctionCall {
560 function: Function,
561 args: Vec<Self>,
562 },
563 Binary {
564 op: BinaryOp,
565 left: Box<Self>,
566 right: Box<Self>,
567 },
568}
569
570#[expect(clippy::large_enum_variant)]
581#[derive(Clone, Debug, Eq, PartialEq)]
582pub(crate) enum GroupHavingExpr {
583 Compare {
584 left: GroupHavingValueExpr,
585 op: CompareOp,
586 right: GroupHavingValueExpr,
587 },
588 And(Vec<Self>),
589}
590
591#[derive(Clone, Debug, Eq, PartialEq)]
612pub(crate) struct ScalarPlan {
613 pub(crate) mode: QueryMode,
615
616 pub(crate) predicate: Option<PredicateExecutionModel>,
618
619 pub(crate) order: Option<OrderSpec>,
621
622 pub(crate) distinct: bool,
624
625 pub(crate) delete_limit: Option<DeleteLimitSpec>,
627
628 pub(crate) page: Option<PageSpec>,
630
631 pub(crate) consistency: MissingRowPolicy,
633}
634
635#[derive(Clone, Debug, Eq, PartialEq)]
643pub(crate) struct GroupPlan {
644 pub(crate) scalar: ScalarPlan,
645 pub(crate) group: GroupSpec,
646 pub(crate) having_expr: Option<GroupHavingExpr>,
647}
648
649#[expect(clippy::large_enum_variant)]
659#[derive(Clone, Debug, Eq, PartialEq)]
660pub(crate) enum LogicalPlan {
661 Scalar(ScalarPlan),
662 Grouped(GroupPlan),
663}