use crate::{
db::{
cursor::{
CursorBoundary, effective_keep_count_for_limit as continuation_keep_count_for_limit,
effective_page_offset_for_window as continuation_page_offset_for_window,
},
direction::Direction,
executor::{
AccessScanContinuationInput, ContinuationMode, RouteContinuationPlan,
planning::route::LoadOrderRouteMode,
},
query::plan::{AccessPlannedQuery, ContinuationPolicy},
},
error::InternalError,
};
use std::rc::Rc;
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db) struct ScalarContinuationContext {
cursor_boundary: Option<Rc<CursorBoundary>>,
physical_primary_key_boundary: Option<Rc<CursorBoundary>>,
}
impl ScalarContinuationContext {
#[must_use]
pub(in crate::db) const fn initial() -> Self {
Self {
cursor_boundary: None,
physical_primary_key_boundary: None,
}
}
#[must_use]
pub(in crate::db) fn resumed(cursor_boundary: CursorBoundary) -> Self {
Self {
cursor_boundary: Some(Rc::new(cursor_boundary)),
physical_primary_key_boundary: None,
}
}
#[must_use]
pub(in crate::db) fn resumed_with_primary_progress(
cursor_boundary: Option<CursorBoundary>,
physical_primary_key_boundary: CursorBoundary,
) -> Self {
Self {
cursor_boundary: cursor_boundary.map(Rc::new),
physical_primary_key_boundary: Some(Rc::new(physical_primary_key_boundary)),
}
}
#[must_use]
pub(in crate::db::executor) fn cursor_boundary(&self) -> Option<&CursorBoundary> {
self.cursor_boundary.as_deref()
}
#[must_use]
pub(in crate::db) const fn has_progress(&self) -> bool {
self.cursor_boundary.is_some() || self.physical_primary_key_boundary.is_some()
}
#[must_use]
pub(in crate::db::executor) const fn route_continuation_mode(&self) -> ContinuationMode {
if self.has_progress() {
ContinuationMode::CursorBoundary
} else {
ContinuationMode::Initial
}
}
#[must_use]
pub(in crate::db::executor) fn route_continuation_plan(
&self,
plan: &AccessPlannedQuery,
continuation_policy: ContinuationPolicy,
) -> RouteContinuationPlan {
RouteContinuationPlan::from_scalar_access_window_plan(
self.route_continuation_mode(),
continuation_policy,
plan.scalar_access_window_plan(self.has_progress()),
)
}
#[must_use]
pub(in crate::db::executor) fn access_scan_input(
&self,
direction: Direction,
plan: &AccessPlannedQuery,
) -> AccessScanContinuationInput<'_> {
let primary_key_ordered = plan
.primary_key_names()
.ok()
.is_some_and(|primary_key_names| {
plan.scalar_plan().order.as_ref().is_some_and(|order| {
order
.primary_key_only_direction_fields(primary_key_names.as_slice())
.is_some()
})
});
AccessScanContinuationInput::with_primary_key_boundary(
None,
direction,
primary_key_ordered
.then_some(
self.physical_primary_key_boundary
.as_deref()
.or_else(|| self.cursor_boundary()),
)
.flatten(),
)
}
pub(in crate::db::executor) fn debug_assert_route_continuation_invariants(
&self,
plan: &AccessPlannedQuery,
route_continuation: RouteContinuationPlan,
) {
debug_assert!(
route_continuation.strict_advance_required_when_applied(),
"route invariant: continuation executions must enforce strict advancement policy",
);
debug_assert_eq!(
route_continuation.effective_offset(),
continuation_page_offset_for_window(plan, self.has_progress()),
"route window effective offset must match logical plan offset semantics",
);
}
#[must_use]
pub(in crate::db::executor) fn keep_count_for_limit_window(
&self,
plan: &AccessPlannedQuery,
limit: u32,
) -> usize {
continuation_keep_count_for_limit(plan, self.has_progress(), limit)
}
pub(in crate::db::executor) fn validate_load_scan_budget_hint(
&self,
scan_budget_hint: Option<usize>,
load_order_route_mode: LoadOrderRouteMode,
) -> Result<(), InternalError> {
if scan_budget_hint.is_some() && self.has_progress() {
return Err(InternalError::query_executor_invariant());
}
if scan_budget_hint.is_some() && !load_order_route_mode.allows_streaming_load() {
return Err(InternalError::query_executor_invariant());
}
Ok(())
}
}