use super::{
RebuildRequirement, SchemaExpressionIndexRebuildTarget, SchemaFieldPathIndexRebuildTarget,
SchemaFieldPathIndexStagedValidation, SchemaMutationExecutionPlan,
SchemaMutationRunnerContract, SchemaSecondaryIndexDropCleanupTarget, runtime_epoch_fingerprint,
};
use crate::{
db::schema::{PersistedSchemaSnapshot, SchemaVersion},
error::InternalError,
};
#[allow(
dead_code,
reason = "0.152 stages execution-boundary contracts before physical runners consume them"
)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db::schema) enum SchemaMutationExecutionReadiness {
PublishableNow,
RequiresPhysicalRunner(RebuildRequirement),
Unsupported(RebuildRequirement),
}
#[allow(
dead_code,
reason = "0.152 stages execution-boundary contracts before physical runners consume them"
)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db::schema) enum SchemaMutationExecutionStep {
BuildFieldPathIndex {
target: SchemaFieldPathIndexRebuildTarget,
},
BuildExpressionIndex {
target: SchemaExpressionIndexRebuildTarget,
},
DropSecondaryIndex {
target: SchemaSecondaryIndexDropCleanupTarget,
},
ValidatePhysicalWork,
InvalidateRuntimeState,
RewriteAllRows,
Unsupported {
reason: &'static str,
},
}
#[allow(
dead_code,
reason = "0.152 stages runner capability contracts before physical runners consume them"
)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db::schema) enum SchemaMutationRunnerCapability {
BuildFieldPathIndex,
BuildExpressionIndex,
DropSecondaryIndex,
ValidatePhysicalWork,
InvalidateRuntimeState,
RewriteAllRows,
}
#[allow(
dead_code,
reason = "0.152 stages runner admission contracts before physical runners consume them"
)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db::schema) enum SchemaMutationExecutionAdmission {
PublishableNow,
RunnerReady {
required: Vec<SchemaMutationRunnerCapability>,
},
MissingRunnerCapabilities {
missing: Vec<SchemaMutationRunnerCapability>,
},
Rejected {
requirement: RebuildRequirement,
},
}
#[allow(
dead_code,
reason = "0.152 stages runner preflight contracts before physical runners consume them"
)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db::schema) enum SchemaMutationRunnerPreflight {
NoPhysicalWork,
Ready {
step_count: usize,
required: Vec<SchemaMutationRunnerCapability>,
},
MissingCapabilities {
missing: Vec<SchemaMutationRunnerCapability>,
},
Rejected {
requirement: RebuildRequirement,
},
}
#[allow(
dead_code,
reason = "0.153 stages runner outcome diagnostics before physical runners consume them"
)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db::schema) enum SchemaMutationRunnerPhase {
Preflight,
StageStores,
BuildPhysicalState,
ValidatePhysicalState,
InvalidateRuntimeState,
PublishSnapshot,
PublishPhysicalStore,
Diagnostics,
}
#[allow(
dead_code,
reason = "0.153 stages staged-store visibility contracts before physical runners consume them"
)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db::schema) enum SchemaMutationStoreVisibility {
StagedOnly,
Published,
}
#[allow(
dead_code,
reason = "0.153 stages runner rejection diagnostics before physical runners consume them"
)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db::schema) enum SchemaMutationRunnerRejectionKind {
MissingCapabilities,
UnsupportedRequirement,
UnsupportedExecutionStep,
ValidationFailed,
RollbackFailed,
PublicationFailed,
}
#[allow(
dead_code,
reason = "0.153 stages runner rejection diagnostics before physical runners consume them"
)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db::schema) struct SchemaMutationRunnerRejection {
phase: SchemaMutationRunnerPhase,
kind: SchemaMutationRunnerRejectionKind,
requirement: Option<RebuildRequirement>,
missing_capabilities: Vec<SchemaMutationRunnerCapability>,
}
#[allow(
dead_code,
reason = "0.153 stages runner rejection diagnostics before physical runners consume them"
)]
impl SchemaMutationRunnerRejection {
#[must_use]
pub(super) const fn unsupported_requirement(requirement: RebuildRequirement) -> Self {
Self {
phase: SchemaMutationRunnerPhase::Preflight,
kind: SchemaMutationRunnerRejectionKind::UnsupportedRequirement,
requirement: Some(requirement),
missing_capabilities: Vec::new(),
}
}
#[must_use]
pub(super) const fn missing_runner_capabilities(
requirement: Option<RebuildRequirement>,
missing_capabilities: Vec<SchemaMutationRunnerCapability>,
) -> Self {
Self {
phase: SchemaMutationRunnerPhase::Preflight,
kind: SchemaMutationRunnerRejectionKind::MissingCapabilities,
requirement,
missing_capabilities,
}
}
#[must_use]
pub(super) const fn validation_failed(requirement: RebuildRequirement) -> Self {
Self {
phase: SchemaMutationRunnerPhase::ValidatePhysicalState,
kind: SchemaMutationRunnerRejectionKind::ValidationFailed,
requirement: Some(requirement),
missing_capabilities: Vec::new(),
}
}
#[must_use]
pub(in crate::db::schema) const fn phase(&self) -> SchemaMutationRunnerPhase {
self.phase
}
#[must_use]
pub(in crate::db::schema) const fn kind(&self) -> SchemaMutationRunnerRejectionKind {
self.kind
}
#[must_use]
pub(in crate::db::schema) const fn requirement(&self) -> Option<RebuildRequirement> {
self.requirement
}
#[must_use]
pub(in crate::db::schema) const fn missing_capabilities(
&self,
) -> &[SchemaMutationRunnerCapability] {
self.missing_capabilities.as_slice()
}
}
#[allow(
dead_code,
reason = "0.153 stages runner diagnostics before physical runners consume them"
)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db::schema) struct SchemaMutationRunnerReport {
step_count: usize,
required_capabilities: Vec<SchemaMutationRunnerCapability>,
completed_phases: Vec<SchemaMutationRunnerPhase>,
store_visibility: Option<SchemaMutationStoreVisibility>,
rows_scanned: usize,
rows_skipped: usize,
index_keys_written: usize,
}
#[allow(
dead_code,
reason = "0.153 stages runner diagnostics before physical runners consume them"
)]
impl SchemaMutationRunnerReport {
#[must_use]
pub(super) fn preflight_ready(
step_count: usize,
required_capabilities: Vec<SchemaMutationRunnerCapability>,
store_visibility: Option<SchemaMutationStoreVisibility>,
) -> Self {
Self {
step_count,
required_capabilities,
completed_phases: vec![SchemaMutationRunnerPhase::Preflight],
store_visibility,
rows_scanned: 0,
rows_skipped: 0,
index_keys_written: 0,
}
}
#[must_use]
pub(super) fn field_path_index_staged(
step_count: usize,
required_capabilities: Vec<SchemaMutationRunnerCapability>,
validation: SchemaFieldPathIndexStagedValidation,
) -> Self {
Self {
step_count,
required_capabilities,
completed_phases: vec![
SchemaMutationRunnerPhase::Preflight,
SchemaMutationRunnerPhase::StageStores,
SchemaMutationRunnerPhase::BuildPhysicalState,
SchemaMutationRunnerPhase::ValidatePhysicalState,
],
store_visibility: Some(validation.store_visibility()),
rows_scanned: validation.source_rows(),
rows_skipped: validation.skipped_rows(),
index_keys_written: validation.entry_count(),
}
}
#[must_use]
fn with_completed_phase(&self, phase: SchemaMutationRunnerPhase) -> Self {
let mut next = self.clone();
if !next.completed_phases.contains(&phase) {
next.completed_phases.push(phase);
}
next
}
#[must_use]
pub(in crate::db::schema) fn with_runtime_state_invalidated(&self) -> Self {
self.with_completed_phase(SchemaMutationRunnerPhase::InvalidateRuntimeState)
}
#[must_use]
pub(in crate::db::schema) fn with_snapshot_published(&self) -> Self {
self.with_completed_phase(SchemaMutationRunnerPhase::PublishSnapshot)
}
#[must_use]
pub(in crate::db::schema) fn with_physical_store_published(&self) -> Self {
let mut next = self.with_completed_phase(SchemaMutationRunnerPhase::PublishPhysicalStore);
next.store_visibility = Some(SchemaMutationStoreVisibility::Published);
next
}
#[must_use]
pub(in crate::db::schema) const fn step_count(&self) -> usize {
self.step_count
}
#[must_use]
pub(in crate::db::schema) const fn required_capabilities(
&self,
) -> &[SchemaMutationRunnerCapability] {
self.required_capabilities.as_slice()
}
#[must_use]
pub(in crate::db::schema) const fn completed_phases(&self) -> &[SchemaMutationRunnerPhase] {
self.completed_phases.as_slice()
}
#[must_use]
pub(in crate::db::schema) fn has_completed_phase(
&self,
phase: SchemaMutationRunnerPhase,
) -> bool {
self.completed_phases.contains(&phase)
}
#[must_use]
pub(in crate::db::schema) const fn store_visibility(
&self,
) -> Option<SchemaMutationStoreVisibility> {
self.store_visibility
}
#[must_use]
pub(in crate::db::schema) const fn rows_scanned(&self) -> usize {
self.rows_scanned
}
#[must_use]
pub(in crate::db::schema) const fn rows_skipped(&self) -> usize {
self.rows_skipped
}
#[must_use]
pub(in crate::db::schema) const fn index_keys_written(&self) -> usize {
self.index_keys_written
}
#[must_use]
pub(in crate::db::schema) fn physical_work_allows_publication(&self) -> bool {
self.store_visibility == Some(SchemaMutationStoreVisibility::Published)
&& self.has_completed_phase(SchemaMutationRunnerPhase::ValidatePhysicalState)
&& self.has_completed_phase(SchemaMutationRunnerPhase::InvalidateRuntimeState)
&& self.has_completed_phase(SchemaMutationRunnerPhase::PublishSnapshot)
&& self.has_completed_phase(SchemaMutationRunnerPhase::PublishPhysicalStore)
}
}
#[allow(
dead_code,
reason = "0.153 stages runner outcomes before physical runners consume them"
)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db::schema) enum SchemaMutationRunnerOutcome {
NoPhysicalWork(SchemaMutationRunnerReport),
ReadyForPhysicalWork(SchemaMutationRunnerReport),
Rejected(SchemaMutationRunnerRejection),
}
#[allow(
dead_code,
reason = "0.153 stages checked runner inputs before physical runners consume them"
)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db::schema) enum SchemaMutationRunnerInputError {
EntityPath,
EntityName,
PrimaryKeyField,
}
#[allow(
dead_code,
reason = "0.153 stages checked runner inputs before physical runners consume them"
)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db::schema) struct SchemaMutationRunnerInput<'a> {
accepted_before: &'a PersistedSchemaSnapshot,
accepted_after: &'a PersistedSchemaSnapshot,
execution_plan: SchemaMutationExecutionPlan,
}
#[allow(
dead_code,
reason = "0.153 stages checked runner inputs before physical runners consume them"
)]
impl<'a> SchemaMutationRunnerInput<'a> {
pub(in crate::db::schema) fn new(
accepted_before: &'a PersistedSchemaSnapshot,
accepted_after: &'a PersistedSchemaSnapshot,
execution_plan: SchemaMutationExecutionPlan,
) -> Result<Self, SchemaMutationRunnerInputError> {
if accepted_before.entity_path() != accepted_after.entity_path() {
return Err(SchemaMutationRunnerInputError::EntityPath);
}
if accepted_before.entity_name() != accepted_after.entity_name() {
return Err(SchemaMutationRunnerInputError::EntityName);
}
if accepted_before.primary_key_field_id() != accepted_after.primary_key_field_id() {
return Err(SchemaMutationRunnerInputError::PrimaryKeyField);
}
Ok(Self {
accepted_before,
accepted_after,
execution_plan,
})
}
#[must_use]
pub(in crate::db::schema) const fn accepted_before(&self) -> &PersistedSchemaSnapshot {
self.accepted_before
}
#[must_use]
pub(in crate::db::schema) const fn accepted_after(&self) -> &PersistedSchemaSnapshot {
self.accepted_after
}
#[must_use]
pub(in crate::db::schema) const fn execution_plan(&self) -> &SchemaMutationExecutionPlan {
&self.execution_plan
}
#[must_use]
pub(in crate::db::schema) fn outcome(
&self,
runner: &SchemaMutationRunnerContract,
) -> SchemaMutationRunnerOutcome {
runner.outcome(&self.execution_plan)
}
}
#[allow(
dead_code,
reason = "0.153 stages the no-op runner adapter before physical runners consume inputs"
)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db::schema) struct SchemaMutationNoopRunner {
contract: SchemaMutationRunnerContract,
}
#[allow(
dead_code,
reason = "0.153 stages the no-op runner adapter before physical runners consume inputs"
)]
impl SchemaMutationNoopRunner {
#[must_use]
pub(in crate::db::schema) fn new() -> Self {
Self {
contract: SchemaMutationRunnerContract::new(&[]),
}
}
#[must_use]
pub(in crate::db::schema) fn run(
&self,
input: &SchemaMutationRunnerInput<'_>,
) -> SchemaMutationRunnerOutcome {
input.outcome(&self.contract)
}
}
impl Default for SchemaMutationNoopRunner {
fn default() -> Self {
Self::new()
}
}
#[allow(
dead_code,
reason = "0.153 stages runtime epoch identity before physical runners publish snapshots"
)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db::schema) struct SchemaMutationRuntimeEpoch {
entity_path: String,
schema_version: SchemaVersion,
snapshot_fingerprint: [u8; 16],
}
#[allow(
dead_code,
reason = "0.153 stages runtime epoch identity before physical runners publish snapshots"
)]
impl SchemaMutationRuntimeEpoch {
pub(in crate::db::schema) fn from_snapshot(
snapshot: &PersistedSchemaSnapshot,
) -> Result<Self, InternalError> {
Ok(Self {
entity_path: snapshot.entity_path().to_string(),
schema_version: snapshot.version(),
snapshot_fingerprint: runtime_epoch_fingerprint(snapshot)?,
})
}
#[must_use]
pub(in crate::db::schema) const fn entity_path(&self) -> &str {
self.entity_path.as_str()
}
#[must_use]
pub(in crate::db::schema) const fn schema_version(&self) -> SchemaVersion {
self.schema_version
}
#[must_use]
pub(in crate::db::schema) const fn snapshot_fingerprint(&self) -> [u8; 16] {
self.snapshot_fingerprint
}
}
#[allow(
dead_code,
reason = "0.153 stages runtime publication identity before physical runners publish snapshots"
)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db::schema) struct SchemaMutationPublicationIdentity {
before: SchemaMutationRuntimeEpoch,
after: SchemaMutationRuntimeEpoch,
store_visibility: SchemaMutationStoreVisibility,
}
#[allow(
dead_code,
reason = "0.153 stages runtime publication identity before physical runners publish snapshots"
)]
impl SchemaMutationPublicationIdentity {
pub(in crate::db::schema) fn from_input(
input: &SchemaMutationRunnerInput<'_>,
store_visibility: SchemaMutationStoreVisibility,
) -> Result<Self, InternalError> {
Ok(Self {
before: SchemaMutationRuntimeEpoch::from_snapshot(input.accepted_before())?,
after: SchemaMutationRuntimeEpoch::from_snapshot(input.accepted_after())?,
store_visibility,
})
}
#[must_use]
pub(in crate::db::schema) const fn before_epoch(&self) -> &SchemaMutationRuntimeEpoch {
&self.before
}
#[must_use]
pub(in crate::db::schema) const fn after_epoch(&self) -> &SchemaMutationRuntimeEpoch {
&self.after
}
#[must_use]
pub(in crate::db::schema) const fn store_visibility(&self) -> SchemaMutationStoreVisibility {
self.store_visibility
}
#[must_use]
pub(in crate::db::schema) const fn visible_epoch(&self) -> &SchemaMutationRuntimeEpoch {
match self.store_visibility {
SchemaMutationStoreVisibility::StagedOnly => &self.before,
SchemaMutationStoreVisibility::Published => &self.after,
}
}
#[must_use]
pub(in crate::db::schema) const fn published_epoch(
&self,
) -> Option<&SchemaMutationRuntimeEpoch> {
match self.store_visibility {
SchemaMutationStoreVisibility::StagedOnly => None,
SchemaMutationStoreVisibility::Published => Some(&self.after),
}
}
#[must_use]
pub(in crate::db::schema) fn changes_epoch(&self) -> bool {
self.before != self.after
}
}