v0.3.4-pre.003
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-postgres-lib/src/migration.rs
|
||||
// version: 8
|
||||
// version: 9
|
||||
|
||||
use sha2::Digest; // rust-rules: trait-import
|
||||
|
||||
@@ -9,6 +9,7 @@ const EMBEDDED_MIGRATIONS: &[EmbeddedMigration] = &[
|
||||
checksum: MigrationChecksum::LegacySql(include_str!("../migrations/v000_bootstrap/tables/001_ksp_store_schema_migrations.sql")),
|
||||
hook: MigrationHook::None,
|
||||
name: "bootstrap",
|
||||
previous_checksums: &[],
|
||||
resources: crate::V000_RESOURCES,
|
||||
version: 0,
|
||||
},
|
||||
@@ -16,6 +17,7 @@ const EMBEDDED_MIGRATIONS: &[EmbeddedMigration] = &[
|
||||
checksum: MigrationChecksum::Resources,
|
||||
hook: MigrationHook::StoreIdentity,
|
||||
name: "raw_transaction",
|
||||
previous_checksums: &[],
|
||||
resources: crate::V001_RESOURCES,
|
||||
version: 1,
|
||||
},
|
||||
@@ -23,12 +25,14 @@ const EMBEDDED_MIGRATIONS: &[EmbeddedMigration] = &[
|
||||
checksum: MigrationChecksum::Resources,
|
||||
hook: MigrationHook::None,
|
||||
name: "raw_account_state",
|
||||
previous_checksums: &[V002_PROVISIONAL_CHECKSUM_PRE_002],
|
||||
resources: crate::V002_RESOURCES,
|
||||
version: 2,
|
||||
},
|
||||
];
|
||||
const HEX_LOWER: &[u8; 16] = b"0123456789abcdef";
|
||||
const HISTORY_INSERT_SQL: &str = "INSERT INTO ksp_store_schema_migrations (version, name, checksum, applied_at) VALUES ($1, $2, $3, CURRENT_TIMESTAMP)";
|
||||
const HISTORY_UPDATE_CHECKSUM_SQL: &str = "UPDATE ksp_store_schema_migrations SET checksum = $1 WHERE version = $2 AND name = $3 AND checksum = $4";
|
||||
const HISTORY_LOAD_SQL: &str = "SELECT version, name, checksum FROM ksp_store_schema_migrations ORDER BY version";
|
||||
const IDENTITY_INSERT_SQL: &str = "INSERT INTO ksp_store_identity (singleton, network) VALUES (1, $1)";
|
||||
const IDENTITY_LOAD_SQL: &str = "SELECT singleton, network FROM ksp_store_identity ORDER BY singleton LIMIT 2";
|
||||
@@ -40,6 +44,7 @@ const METADATA_EXISTS_SQL: &str = r#"SELECT EXISTS (
|
||||
AND table_type = 'BASE TABLE'
|
||||
)"#;
|
||||
const SET_STATEMENT_TIMEOUT_SQL: &str = "SELECT set_config('statement_timeout', $1, true)";
|
||||
const V002_PROVISIONAL_CHECKSUM_PRE_002: &str = "30ac87496f1bb3805d816660891d7eab2127c599a636eb40c17ade5926311f55";
|
||||
|
||||
struct AppliedMigration {
|
||||
checksum: std::string::String,
|
||||
@@ -58,6 +63,7 @@ struct EmbeddedMigration {
|
||||
checksum: MigrationChecksum,
|
||||
hook: MigrationHook,
|
||||
name: &'static str,
|
||||
previous_checksums: &'static [&'static str],
|
||||
resources: &'static [crate::SchemaResource],
|
||||
version: i64,
|
||||
}
|
||||
@@ -140,7 +146,7 @@ async fn bootstrap_inner(
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let (next_index, mutation_mode) = if metadata_exists {
|
||||
let (next_index, mutation_mode, applied_history) = if metadata_exists {
|
||||
let metadata_result = crate::inspect_resource(&transaction, &crate::V000_RESOURCES[0]).await;
|
||||
match metadata_result {
|
||||
std::result::Result::Ok(crate::SchemaResourceState::Compatible) => {},
|
||||
@@ -155,18 +161,18 @@ async fn bootstrap_inner(
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let validation_result = validate_history(history.as_slice(), EMBEDDED_MIGRATIONS);
|
||||
let validation_result = validate_history(history.as_slice(), EMBEDDED_MIGRATIONS, schema_autoupdate);
|
||||
let index = match validation_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
(index, SchemaMutationMode::Update)
|
||||
(index, SchemaMutationMode::Update, std::option::Option::Some(history))
|
||||
} else {
|
||||
if !schema_autocreate {
|
||||
log_schema_block("schema_autocreate_disabled");
|
||||
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::MigrationFailed, "schema_autocreate_disabled"));
|
||||
}
|
||||
let managed_result = crate::managed_v001_objects_exist(&transaction).await;
|
||||
let managed_result = crate::managed_schema_objects_exist(&transaction).await;
|
||||
let managed_objects_exist = match managed_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
@@ -175,12 +181,18 @@ async fn bootstrap_inner(
|
||||
log_schema_block("schema_adoption_disabled");
|
||||
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::MigrationFailed, "schema_adoption_disabled"));
|
||||
}
|
||||
(0, SchemaMutationMode::Create)
|
||||
(0, SchemaMutationMode::Create, std::option::Option::None)
|
||||
};
|
||||
let existing_schema_result = verify_or_repair_applied_migrations(&transaction, next_index, schema_autoupdate).await;
|
||||
if let std::result::Result::Err(error) = existing_schema_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
if let std::option::Option::Some(history) = applied_history.as_deref() {
|
||||
let checksum_result = reconcile_applied_history_checksums(&transaction, history, next_index, schema_autoupdate).await;
|
||||
if let std::result::Result::Err(error) = checksum_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
}
|
||||
let existing_hook_result = run_applied_migration_hooks(&transaction, network, next_index).await;
|
||||
if let std::result::Result::Err(error) = existing_hook_result {
|
||||
return std::result::Result::Err(error);
|
||||
@@ -528,12 +540,14 @@ async fn set_statement_timeout(
|
||||
}
|
||||
|
||||
async fn verify_migration_contract(transaction: &deadpool_postgres::Transaction<'_>, version: i64) -> std::result::Result<(), crate::PostgresBackendError> {
|
||||
if version == 1 {
|
||||
let result = crate::verify_v001_external_compatibility(transaction).await;
|
||||
if let std::result::Result::Err(error) = result {
|
||||
log_schema_block(error.phase());
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let result = match version {
|
||||
1 => crate::verify_v001_external_compatibility(transaction).await,
|
||||
2 => crate::verify_v002_external_compatibility(transaction).await,
|
||||
_ => std::result::Result::Ok(()),
|
||||
};
|
||||
if let std::result::Result::Err(error) = result {
|
||||
log_schema_block(error.phase());
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
@@ -582,6 +596,41 @@ async fn verify_or_repair_applied_migrations(
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
async fn reconcile_applied_history_checksums(
|
||||
transaction: &deadpool_postgres::Transaction<'_>,
|
||||
history: &[AppliedMigration],
|
||||
applied_count: usize,
|
||||
schema_autoupdate: bool,
|
||||
) -> std::result::Result<(), crate::PostgresBackendError> {
|
||||
if !schema_autoupdate {
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
for (index, applied) in history.iter().take(applied_count).enumerate() {
|
||||
let migration = &EMBEDDED_MIGRATIONS[index];
|
||||
let expected_checksum = migration_checksum(migration);
|
||||
if applied.checksum == expected_checksum {
|
||||
continue;
|
||||
}
|
||||
if !migration.previous_checksums.contains(&applied.checksum.as_str()) {
|
||||
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::MigrationMismatch, "history_diverged"));
|
||||
}
|
||||
let result = transaction.execute(HISTORY_UPDATE_CHECKSUM_SQL, &[&expected_checksum, &migration.version, &migration.name, &applied.checksum]).await;
|
||||
match result {
|
||||
std::result::Result::Ok(1) => {
|
||||
ksp_logging_lib::warn!(
|
||||
target: crate::TRACING_TARGET,
|
||||
migration_version = migration.version,
|
||||
"upgraded known prerelease PostgreSQL Store migration checksum after schema reconciliation"
|
||||
);
|
||||
},
|
||||
std::result::Result::Ok(_) | std::result::Result::Err(_) => {
|
||||
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::MigrationFailed, "history_checksum_update"));
|
||||
},
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn schema_autoupdate_disabled_error() -> crate::PostgresBackendError {
|
||||
return crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::MigrationMismatch, "schema_autoupdate_disabled");
|
||||
}
|
||||
@@ -615,7 +664,11 @@ fn validate_embedded_registry(migrations: &[EmbeddedMigration]) -> std::result::
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn validate_history(history: &[AppliedMigration], migrations: &[EmbeddedMigration]) -> std::result::Result<usize, crate::PostgresBackendError> {
|
||||
fn validate_history(
|
||||
history: &[AppliedMigration],
|
||||
migrations: &[EmbeddedMigration],
|
||||
allow_previous_checksums: bool,
|
||||
) -> std::result::Result<usize, crate::PostgresBackendError> {
|
||||
if history.is_empty() {
|
||||
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::MigrationMismatch, "history_missing"));
|
||||
}
|
||||
@@ -630,7 +683,8 @@ fn validate_history(history: &[AppliedMigration], migrations: &[EmbeddedMigratio
|
||||
}
|
||||
let expected = &migrations[index];
|
||||
let expected_checksum = migration_checksum(expected);
|
||||
if applied.version != expected.version || applied.name != expected.name || applied.checksum != expected_checksum {
|
||||
let previous_checksum_matches = allow_previous_checksums && expected.previous_checksums.contains(&applied.checksum.as_str());
|
||||
if applied.version != expected.version || applied.name != expected.name || (applied.checksum != expected_checksum && !previous_checksum_matches) {
|
||||
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::MigrationMismatch, "history_diverged"));
|
||||
}
|
||||
index += 1;
|
||||
|
||||
Reference in New Issue
Block a user