icydb_core/db/query/plan/
executable.rs1use crate::{
2 db::query::{
3 QueryMode,
4 plan::{
5 ContinuationSignature, CursorBoundary, ExplainPlan, LogicalPlan, PlanError,
6 PlanFingerprint, continuation::decode_validated_cursor_boundary,
7 },
8 policy::{self, CursorOrderPolicyError},
9 predicate::SchemaInfo,
10 },
11 traits::{EntityKind, FieldValue},
12};
13use std::marker::PhantomData;
14
15#[derive(Debug)]
22pub struct ExecutablePlan<E: EntityKind> {
23 plan: LogicalPlan<E::Key>,
24 _marker: PhantomData<E>,
25}
26
27impl<E: EntityKind> ExecutablePlan<E> {
28 pub(crate) const fn new(plan: LogicalPlan<E::Key>) -> Self {
29 Self {
30 plan,
31 _marker: PhantomData,
32 }
33 }
34
35 #[must_use]
37 pub fn explain(&self) -> ExplainPlan {
38 self.plan.explain()
39 }
40
41 #[must_use]
43 pub fn fingerprint(&self) -> PlanFingerprint {
44 self.plan.fingerprint()
45 }
46
47 #[must_use]
51 pub fn continuation_signature(&self) -> ContinuationSignature {
52 self.plan.continuation_signature(E::PATH)
53 }
54
55 #[cfg_attr(not(test), allow(dead_code))]
60 pub(crate) fn plan_cursor_boundary(
61 &self,
62 cursor: Option<&[u8]>,
63 ) -> Result<Option<CursorBoundary>, PlanError>
64 where
65 E::Key: FieldValue,
66 {
67 let Some(cursor) = cursor else {
68 return Ok(None);
69 };
70 let order =
71 policy::require_cursor_order(self.plan.order.as_ref()).map_err(|err| match err {
72 CursorOrderPolicyError::CursorRequiresOrder => PlanError::CursorRequiresOrder,
73 })?;
74
75 let boundary = decode_validated_cursor_boundary(
76 cursor,
77 E::PATH,
78 E::MODEL,
79 order,
80 self.continuation_signature(),
81 )?;
82
83 let pk_field = E::MODEL.primary_key.name;
85 let pk_index = order
86 .fields
87 .iter()
88 .position(|(field, _)| field == pk_field)
89 .ok_or_else(|| PlanError::MissingPrimaryKeyTieBreak {
90 field: pk_field.to_string(),
91 })?;
92 let expected = SchemaInfo::from_entity_model(E::MODEL)
93 .map_err(PlanError::PredicateInvalid)?
94 .field(pk_field)
95 .expect("primary key exists by model contract")
96 .to_string();
97 let pk_slot = &boundary.slots[pk_index];
98 let invalid_pk = match pk_slot {
99 super::CursorBoundarySlot::Missing => Some(None),
100 super::CursorBoundarySlot::Present(value) => {
101 if E::Key::from_value(value).is_none() {
102 Some(Some(value.clone()))
103 } else {
104 None
105 }
106 }
107 };
108 if let Some(value) = invalid_pk {
109 return Err(PlanError::ContinuationCursorPrimaryKeyTypeMismatch {
110 field: pk_field.to_string(),
111 expected,
112 value,
113 });
114 }
115
116 Ok(Some(boundary))
117 }
118
119 #[must_use]
121 pub(crate) const fn mode(&self) -> QueryMode {
122 self.plan.mode
123 }
124
125 pub(crate) const fn access(&self) -> &crate::db::query::plan::AccessPlan<E::Key> {
126 &self.plan.access
127 }
128
129 pub(crate) fn into_inner(self) -> LogicalPlan<E::Key> {
130 self.plan
131 }
132}