feat: ADR-0035 4f — ALTER TABLE … ALTER COLUMN TYPE

Fourth AlterTableAction (AlterColumnType), runtime-decomposed to the
existing change_column_type executor with ForceConversion — which IS the
§7 advanced policy: lossy converts with a note (no force flag),
incompatible + the ADR-0017 static refusals (↔blob, same-type,
date↔datetime, non-int→serial) still refuse, while int→serial is allowed
(auto-fills nulls + UNIQUE, ADR-0018 §8). No new mode/note/persistence;
undo is the advanced safety net.

Grammar adds a fourth action branch leading on `alter`, discriminated in
the builder by the `type` keyword (unique — ADD COLUMN's type is an
ident); the type slot reuses SQL_TYPE. The internal-__rdbms_* guard was
folded into do_change_column_type (user-confirmed), closing the simple
`change column` exposure.

Tests: 7 Tier-3 e2e via run_replay + 4 Tier-1 parse (incl. a column-named-
`type` discriminator probe) + the simple-surface guard. Help/usage
refreshed; ADR-0035 §13 4f + README + requirements.md in lockstep.
This commit is contained in:
claude@clouddev1
2026-05-25 21:16:37 +00:00
parent a2fc3c9e57
commit 5b76315d1e
11 changed files with 479 additions and 36 deletions
+27 -3
View File
@@ -10,7 +10,7 @@
//! rename-drift bug that would break a later rebuild).
use rdbms_playground::db::Database;
use rdbms_playground::dsl::{ColumnSpec, Type};
use rdbms_playground::dsl::{ChangeColumnMode, ColumnSpec, Type};
use rdbms_playground::persistence::Persistence;
use rdbms_playground::project;
@@ -72,10 +72,34 @@ fn simple_column_ops_refuse_internal_tables() {
"drop column on an internal table is refused"
);
assert!(
r.block_on(db.rename_column(internal, "table_name".to_string(), "tn".to_string(), None))
.is_err(),
r.block_on(db.rename_column(
internal.clone(),
"table_name".to_string(),
"tn".to_string(),
None
))
.is_err(),
"rename column on an internal table is refused"
);
// `change column` (the simple surface; also the SQL `ALTER COLUMN
// TYPE` decomposition target — ADR-0035 §4f) is refused too: the
// guard lives in `do_change_column_type`. It refuses up-front as
// "no such table" (the sibling-executor contract), not via the
// incidental "no user-facing type metadata" path internal tables
// happen to hit.
let err = r
.block_on(db.change_column_type(
internal,
"table_name".to_string(),
Type::Int,
ChangeColumnMode::Default,
None,
))
.expect_err("change column type on an internal table is refused");
assert!(
format!("{err:?}").contains("NoSuchTable"),
"expected a no-such-table refusal from the internal-table guard, got: {err:?}"
);
}
#[test]