use std::fs;
use std::path::Path;
use super::{ChunkingStamp, DatabaseConfig, DatabaseError, MigrationFence, StoredConfig};
const POLICY_V1_LABEL: &str = "count-target-v1";
const PHASE_FORWARD: &str = "forward";
const PHASE_ABORT: &str = "abort";
pub(in crate::db) struct MigrationState {
pub config: DatabaseConfig,
pub format_version: Option<u32>,
pub v2_targets: Option<(u64, u64)>,
pub fence: Option<FenceView>,
}
pub(in crate::db) struct FenceView {
pub target_is_v2: bool,
}
pub(in crate::db) fn read_state(data_dir: &Path) -> Result<MigrationState, DatabaseError> {
let bytes =
fs::read(data_dir.join(crate::db::CONFIG_FILE)).map_err(DatabaseError::ConfigRead)?;
let stored: StoredConfig = serde_json::from_slice(&bytes)
.map_err(|error| DatabaseError::ConfigParse(error.to_string()))?;
let v2_targets = stored
.chunking_policy
.as_ref()
.map(|stamp| (stamp.leaf_target_bytes, stamp.internal_target_bytes));
let fence = stored.migration_fence.as_ref().map(|fence| FenceView {
target_is_v2: fence.target_policy == super::CHUNKING_V2_ID,
});
let mut config = stored.config;
config.data_dir = data_dir.to_path_buf();
Ok(MigrationState {
config,
format_version: stored.format_version,
v2_targets,
fence,
})
}
#[derive(serde::Serialize)]
struct MigrationEnvelope<'config> {
format_version: u32,
#[serde(skip_serializing_if = "Option::is_none")]
chunking_policy: Option<ChunkingStamp>,
#[serde(skip_serializing_if = "Option::is_none")]
migration_fence: Option<MigrationFence>,
#[serde(flatten)]
config: &'config DatabaseConfig,
}
fn v2_stamp(leaf_target_bytes: u64, internal_target_bytes: u64) -> ChunkingStamp {
ChunkingStamp {
chunking: super::CHUNKING_V2_ID.to_owned(),
leaf_target_bytes,
internal_target_bytes,
}
}
fn write_envelope(
config: &DatabaseConfig,
envelope: &MigrationEnvelope<'_>,
) -> Result<(), DatabaseError> {
let bytes = serde_json::to_vec_pretty(envelope).map_err(|error| {
DatabaseError::ConfigWrite(std::io::Error::new(std::io::ErrorKind::InvalidData, error))
})?;
super::install_config_atomic(&config.data_dir.join(crate::db::CONFIG_FILE), &bytes)
}
pub(in crate::db) fn install_forward_fence(
config: &DatabaseConfig,
leaf_target_bytes: u64,
internal_target_bytes: u64,
) -> Result<(), DatabaseError> {
write_envelope(
config,
&MigrationEnvelope {
format_version: super::ON_DISK_FORMAT_VERSION,
chunking_policy: Some(v2_stamp(leaf_target_bytes, internal_target_bytes)),
migration_fence: Some(MigrationFence {
source_policy: POLICY_V1_LABEL.to_owned(),
target_policy: super::CHUNKING_V2_ID.to_owned(),
phase: PHASE_FORWARD.to_owned(),
}),
config,
},
)
}
pub(in crate::db) fn flip_to_abort(
config: &DatabaseConfig,
leaf_target_bytes: u64,
internal_target_bytes: u64,
) -> Result<(), DatabaseError> {
write_envelope(
config,
&MigrationEnvelope {
format_version: super::ON_DISK_FORMAT_VERSION,
chunking_policy: Some(v2_stamp(leaf_target_bytes, internal_target_bytes)),
migration_fence: Some(MigrationFence {
source_policy: super::CHUNKING_V2_ID.to_owned(),
target_policy: POLICY_V1_LABEL.to_owned(),
phase: PHASE_ABORT.to_owned(),
}),
config,
},
)
}
pub(in crate::db) fn finalize_v2(
config: &DatabaseConfig,
leaf_target_bytes: u64,
internal_target_bytes: u64,
) -> Result<(), DatabaseError> {
write_envelope(
config,
&MigrationEnvelope {
format_version: super::ON_DISK_FORMAT_VERSION,
chunking_policy: Some(v2_stamp(leaf_target_bytes, internal_target_bytes)),
migration_fence: None,
config,
},
)
}
pub(in crate::db) fn finalize_v1(config: &DatabaseConfig) -> Result<(), DatabaseError> {
write_envelope(
config,
&MigrationEnvelope {
format_version: super::V1_FORMAT_VERSION,
chunking_policy: None,
migration_fence: None,
config,
},
)
}