mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-04 11:43:57 +08:00
fix(database): reject hidden migration source columns
This commit is contained in:
@@ -1061,6 +1061,24 @@ fn source_spec_rejects_extra_tables_and_columns_at_both_entries() -> Result<(),
|
||||
"v17-extra-column",
|
||||
"source column set mismatch",
|
||||
&dump_exact_source(&extra_column)?,
|
||||
)?;
|
||||
|
||||
// `PRAGMA table_info` does not expose generated columns. Replacing an
|
||||
// expected source field with a generated field therefore proves that
|
||||
// recognition uses `table_xinfo` and rejects hidden/generated semantics,
|
||||
// even when the visible name set is otherwise exact.
|
||||
let generated_expected_column = exact_source_connection(super::SCHEMA_VERSION)?;
|
||||
generated_expected_column.execute_batch(
|
||||
"DROP TABLE settings;
|
||||
CREATE TABLE settings (
|
||||
key TEXT,
|
||||
value TEXT GENERATED ALWAYS AS (key) VIRTUAL
|
||||
);",
|
||||
)?;
|
||||
assert_fixture_rejected(
|
||||
"v17-generated-expected-column",
|
||||
"generated_or_hidden",
|
||||
&dump_exact_source(&generated_expected_column)?,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1614,7 +1614,7 @@ impl Database {
|
||||
) -> Result<(), AppError> {
|
||||
let observed_tables = conn
|
||||
.prepare(
|
||||
"SELECT name FROM sqlite_schema
|
||||
"SELECT name FROM main.sqlite_schema
|
||||
WHERE type = 'table' AND name NOT LIKE 'sqlite_%'
|
||||
ORDER BY name",
|
||||
)
|
||||
@@ -1646,12 +1646,18 @@ impl Database {
|
||||
}
|
||||
|
||||
for table in &spec.tables {
|
||||
let pragma = format!("PRAGMA table_info({})", quoted_identifier(table.name));
|
||||
// `table_info` omits generated and virtual-table hidden columns.
|
||||
// `table_xinfo` is required for an exact source column set; a
|
||||
// generated replacement for an expected column must not acquire
|
||||
// authority merely because its name and observed values match.
|
||||
let pragma = format!("PRAGMA main.table_xinfo({})", quoted_identifier(table.name));
|
||||
let observed_columns = conn
|
||||
.prepare(&pragma)
|
||||
.and_then(|mut statement| {
|
||||
statement
|
||||
.query_map([], |row| row.get::<_, String>(1))?
|
||||
.query_map([], |row| {
|
||||
Ok((row.get::<_, String>(1)?, row.get::<_, i64>(6)?))
|
||||
})?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
})
|
||||
.map_err(|error| {
|
||||
@@ -1660,7 +1666,15 @@ impl Database {
|
||||
spec.version, table.name
|
||||
))
|
||||
})?;
|
||||
let observed_by_name: BTreeSet<_> = observed_columns.iter().cloned().collect();
|
||||
let observed_by_name: BTreeSet<_> = observed_columns
|
||||
.iter()
|
||||
.map(|(name, _)| name.clone())
|
||||
.collect();
|
||||
let hidden_or_generated = observed_columns
|
||||
.iter()
|
||||
.filter(|(_, hidden)| *hidden != 0)
|
||||
.map(|(name, hidden)| format!("{name}:{hidden}"))
|
||||
.collect::<Vec<_>>();
|
||||
let expected_by_name: BTreeSet<_> = table
|
||||
.columns
|
||||
.iter()
|
||||
@@ -1668,6 +1682,7 @@ impl Database {
|
||||
.collect();
|
||||
if observed_by_name.len() != observed_columns.len()
|
||||
|| observed_by_name != expected_by_name
|
||||
|| !hidden_or_generated.is_empty()
|
||||
{
|
||||
let missing: Vec<_> = expected_by_name
|
||||
.difference(&observed_by_name)
|
||||
@@ -1678,8 +1693,9 @@ impl Database {
|
||||
.cloned()
|
||||
.collect();
|
||||
return Err(AppError::InvalidInput(format!(
|
||||
"untrusted v{} source column set mismatch for {}; missing={missing:?}, extra={extra:?}",
|
||||
spec.version, table.name
|
||||
"untrusted v{} source column set mismatch for {}; missing={missing:?}, \
|
||||
extra={extra:?}, generated_or_hidden={hidden_or_generated:?}",
|
||||
spec.version, table.name,
|
||||
)));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user