v0.3.3-pre.009-fix.004

This commit is contained in:
2026-08-30 15:05:58 +02:00
parent 5e2f959c01
commit 5f1938c7c3
5 changed files with 182 additions and 8 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-store-postgres-lib/src/schema.rs
// version: 4
// version: 5
/// Immutable V000 physical schema resource inventory.
pub(crate) const V000_RESOURCES: &[SchemaResource] = &[SchemaResource {
@@ -1138,6 +1138,7 @@ fn matches_expected_constraint_definition(table: &str, kind: &str, definition: &
}
fn normalize_catalog_sql(value: &str) -> std::string::String {
let value = normalize_quoted_numeric_cast_literals(value);
return value
.chars()
.filter(|character| return !character.is_whitespace() && *character != '"' && *character != '(' && *character != ')')
@@ -1149,6 +1150,47 @@ fn normalize_catalog_sql(value: &str) -> std::string::String {
.to_ascii_lowercase();
}
fn normalize_quoted_numeric_cast_literals(value: &str) -> std::string::String {
const NUMERIC_CAST: &str = "::numeric";
let mut normalized = std::string::String::with_capacity(value.len());
let mut remaining = value;
loop {
let (before_quote, after_quote) = match remaining.split_once('\'') {
std::option::Option::Some(value) => value,
std::option::Option::None => {
normalized.push_str(remaining);
break;
},
};
normalized.push_str(before_quote);
let (literal, after_literal) = match after_quote.split_once('\'') {
std::option::Option::Some(value) => value,
std::option::Option::None => {
normalized.push('\'');
normalized.push_str(after_quote);
break;
},
};
let has_digit = literal.chars().any(|character| return character.is_ascii_digit());
let numeric_literal = !literal.is_empty()
&& has_digit
&& literal
.chars()
.enumerate()
.all(|(offset, character)| return character.is_ascii_digit() || (offset == 0 && (character == '+' || character == '-')));
if numeric_literal && let std::option::Option::Some(after_numeric_cast) = after_literal.strip_prefix(NUMERIC_CAST) {
normalized.push_str(literal);
remaining = after_numeric_cast;
continue;
}
normalized.push('\'');
normalized.push_str(literal);
normalized.push('\'');
remaining = after_literal;
}
return normalized;
}
fn schema_incompatible<T>(phase: &'static str) -> std::result::Result<T, crate::PostgresBackendError> {
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::MigrationMismatch, phase));
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-store-postgres-lib/unit_tests/schema.rs
// version: 2
// version: 3
fn actual_column(name: &str, udt_name: &str, nullable: bool) -> super::ActualColumn {
return super::ActualColumn {
@@ -99,6 +99,10 @@ fn pre_003_fix_001_required_column_matching_ignores_integer_precision_but_requir
fn pre_003_fix_001_catalog_normalization_and_resource_owned_constraint_definition_are_deterministic() {
let normalized = super::normalize_catalog_sql("CHECK ((slot >= (0)::numeric) AND (slot <= (18446744073709551615)::numeric))");
assert_eq!(normalized, "checkslot>=0andslot<=18446744073709551615");
let postgres_17_normalized = super::normalize_catalog_sql("CHECK ((slot >= '0'::numeric) AND (slot <= '18446744073709551615'::numeric))");
assert_eq!(postgres_17_normalized, normalized);
let text_literal = super::normalize_catalog_sql("CHECK (retention_state = 'full'::text)");
assert_eq!(text_literal, "checkretention_state='full'");
let resource = crate::V001_RESOURCES.iter().find(|resource| return resource.id == "constraints/006_ck_ksp_raw_transactions_slot.sql");
assert!(resource.is_some(), "V001 slot constraint resource must remain embedded");
let resource = match resource {