feat: advanced ALTER COLUMN SET/DROP NOT NULL & DEFAULT, SET DATA TYPE (ADR-0035 Am2)
The standard-first ALTER COLUMN constraint gap-fill advanced mode lacked: - ALTER COLUMN <c> SET DATA TYPE <ty> — ISO canonical synonym for the PostgreSQL TYPE shorthand (same AlterColumnType action + executor). - SET NOT NULL / DROP NOT NULL — reuse the ADR-0029 do_add_constraint / do_drop_constraint executors (dry-run + internal-table guards free). - SET DEFAULT <expr> / DROP DEFAULT — SET DEFAULT uses a dedicated raw-SQL executor (do_set_column_default); sql_expr yields no typed Value, so it can't go through do_add_constraint. DROP DEFAULT reuses do_drop_constraint. Grammar: AT_ALTER_COLUMN gains a tail Choice (type / set / drop), reusing SQL_TYPE and the CREATE TABLE DEFAULT_NODES; builder dispatch routes the new column-attribute forms; runtime decomposes to the executors. ADR-0035 Am2 corrected in-place: SET DEFAULT decomposes to do_set_column_default, not do_add_constraint (Value-based) — found during build. Tests (test-first): 6 parse + 7 Tier-3 execution via run_replay. Suite 1962/0/1; clippy clean.
This commit is contained in:
@@ -608,6 +608,17 @@ enum Request {
|
||||
source: Option<String>,
|
||||
reply: oneshot::Sender<Result<TableDescription, DbError>>,
|
||||
},
|
||||
/// `ALTER TABLE … ALTER COLUMN <col> SET DEFAULT <expr>` — set a
|
||||
/// column's default to raw SQL text (ADR-0035 Amendment 2). Distinct
|
||||
/// from `AddConstraint(Default(Value))` because the advanced default
|
||||
/// is raw `sql_expr` text with no typed `Value`.
|
||||
SetColumnDefault {
|
||||
table: String,
|
||||
column: String,
|
||||
default_sql: String,
|
||||
source: Option<String>,
|
||||
reply: oneshot::Sender<Result<TableDescription, DbError>>,
|
||||
},
|
||||
/// `ALTER TABLE … ADD [CONSTRAINT <name>] CHECK (<expr>)` — a
|
||||
/// table-level CHECK, named or unnamed (ADR-0035 §4g).
|
||||
AlterAddTableCheck {
|
||||
@@ -1131,6 +1142,27 @@ impl Database {
|
||||
recv.await.map_err(|_| DbError::WorkerGone)?
|
||||
}
|
||||
|
||||
/// `ALTER TABLE … ALTER COLUMN <col> SET DEFAULT <expr>` — set the
|
||||
/// column's default to raw SQL text (ADR-0035 Amendment 2).
|
||||
pub async fn set_column_default(
|
||||
&self,
|
||||
table: String,
|
||||
column: String,
|
||||
default_sql: String,
|
||||
source: Option<String>,
|
||||
) -> Result<TableDescription, DbError> {
|
||||
let (reply, recv) = oneshot::channel();
|
||||
self.send(Request::SetColumnDefault {
|
||||
table,
|
||||
column,
|
||||
default_sql,
|
||||
source,
|
||||
reply,
|
||||
})
|
||||
.await?;
|
||||
recv.await.map_err(|_| DbError::WorkerGone)?
|
||||
}
|
||||
|
||||
/// `ALTER TABLE … ADD [CONSTRAINT <name>] CHECK (<expr>)` — a
|
||||
/// table-level CHECK (ADR-0035 §4g).
|
||||
pub async fn alter_add_table_check(
|
||||
@@ -2366,6 +2398,24 @@ fn handle_request(
|
||||
kind,
|
||||
));
|
||||
}
|
||||
Request::SetColumnDefault {
|
||||
table,
|
||||
column,
|
||||
default_sql,
|
||||
source,
|
||||
reply,
|
||||
} => {
|
||||
snapshot_then(snap, batch, conn, source.as_deref(), reply, || {
|
||||
do_set_column_default(
|
||||
conn,
|
||||
persistence,
|
||||
source.as_deref(),
|
||||
&table,
|
||||
&column,
|
||||
&default_sql,
|
||||
)
|
||||
});
|
||||
}
|
||||
Request::AlterAddTableCheck {
|
||||
table,
|
||||
name,
|
||||
@@ -4034,6 +4084,72 @@ fn do_drop_constraint(
|
||||
do_describe_table(conn, table)
|
||||
}
|
||||
|
||||
/// `ALTER TABLE … ALTER COLUMN <col> SET DEFAULT <expr>` — set the
|
||||
/// column's default to raw SQL text (ADR-0035 Amendment 2). Mirrors
|
||||
/// `do_add_constraint`'s `Default` branch (the §6 `serial`/`shortid`
|
||||
/// refusal; no dry-run — a default never touches existing rows) but
|
||||
/// takes raw `sql_expr` text instead of a typed `Value` (advanced
|
||||
/// `SET DEFAULT` has no AST). DEFAULT is recoverable from the engine
|
||||
/// catalog, so there is no metadata write — the rebuilt table's DDL
|
||||
/// carries it (ADR-0029 §7).
|
||||
fn do_set_column_default(
|
||||
conn: &Connection,
|
||||
persistence: Option<&Persistence>,
|
||||
source: Option<&str>,
|
||||
table: &str,
|
||||
column: &str,
|
||||
default_sql: &str,
|
||||
) -> Result<TableDescription, DbError> {
|
||||
let canonical_table = require_canonical_table(conn, table)?;
|
||||
let table = canonical_table.as_str();
|
||||
let old_schema = read_schema(conn, table)?;
|
||||
let col_user_type = {
|
||||
let col = old_schema
|
||||
.columns
|
||||
.iter()
|
||||
.find(|c| c.name == column)
|
||||
.ok_or_else(|| DbError::Sqlite {
|
||||
message: format!("no such column: {table}.{column}"),
|
||||
kind: SqliteErrorKind::NoSuchColumn,
|
||||
})?;
|
||||
col.user_type
|
||||
};
|
||||
|
||||
// ADR-0029 §6 — an auto-generated column fills its own values, so a
|
||||
// `default` would be a second, ambiguous source of "the value when
|
||||
// none is given" (mirrors do_add_constraint).
|
||||
if matches!(col_user_type, Some(Type::Serial | Type::ShortId)) {
|
||||
return Err(DbError::Unsupported(format!(
|
||||
"`{table}.{column}` is a {ty} column — it auto-fills its own \
|
||||
values, so it cannot also carry a `default`.",
|
||||
ty = col_user_type.expect("matched Some above").keyword(),
|
||||
)));
|
||||
}
|
||||
|
||||
let mut new_schema = old_schema.clone();
|
||||
{
|
||||
let target = new_schema
|
||||
.columns
|
||||
.iter_mut()
|
||||
.find(|c| c.name == column)
|
||||
.expect("column existence checked above");
|
||||
target.default_sql = Some(default_sql.to_string());
|
||||
}
|
||||
|
||||
let metadata_updates = |tx: &rusqlite::Transaction<'_>| -> Result<(), DbError> {
|
||||
let changes = Changes {
|
||||
schema_dirty: true,
|
||||
rewritten_tables: vec![table.to_string()],
|
||||
..Changes::default()
|
||||
};
|
||||
finalize_persistence(tx, persistence, source, &changes)?;
|
||||
Ok(())
|
||||
};
|
||||
|
||||
rebuild_table(conn, table, &old_schema, &new_schema, metadata_updates)?;
|
||||
do_describe_table(conn, table)
|
||||
}
|
||||
|
||||
/// A row's primary-key cell values paired with its value in
|
||||
/// the column under test — the unit an ADR-0029 §5 dry-run
|
||||
/// scans.
|
||||
|
||||
Reference in New Issue
Block a user