ADR-0017 implementation: per-cell type-change with override flags

Replaces the placeholder "trust STRICT" body of do_change_column_type
with the per-cell transformer matrix from ADR-0017. Adds:

- src/type_change.rs: CellOutcome { Clean / Lossy / Incompatible }
  + transform_cell + static_refusal covering every matrix pair
  from §3 (54 unit tests).
- --force-conversion and --dont-convert flags on `change column`
  (mutually exclusive at parse time per §5).
- Refined PK rule (§4.1): refused only when the column has an
  inbound FK and fk_target_type would change. Outbound-FK columns
  still refused outright (§4.2). PK / shortid uniqueness checked
  post-transformation (§4.3).
- Bordered diagnostic tables (lossy / incompatible / collision)
  via the pretty-table renderer (§7) — uses ADR-0016's primitives.
- [client-side] success note (§6) when any cell was rewritten.
- Friendly wrapper for engine-level errors under --dont-convert
  so no engine vocabulary leaks (ADR-0002 user-facing posture).

ADR-0017 §3 + §7 amended in place (with user sign-off): serial->int
added explicitly to the always-clean matrix, and diagnostic rows
identify themselves by PK value(s) rather than positional indices
(SQLite returns rows unordered without ORDER BY, so positional
"row 5" is unaddressable).

Tests: 449 -> 517 (+68). Clippy clean with nursery lints.
This commit is contained in:
claude@clouddev1
2026-05-08 13:21:07 +00:00
parent 545cbf5c0e
commit 00947b928c
12 changed files with 2598 additions and 137 deletions
+15 -4
View File
@@ -29,7 +29,8 @@ use crate::action::Action;
use crate::app::App;
use crate::cli::Args;
use crate::db::{
DataResult, Database, DbError, DeleteResult, InsertResult, TableDescription, UpdateResult,
ChangeColumnTypeResult, DataResult, Database, DbError, DeleteResult, InsertResult,
TableDescription, UpdateResult,
};
use crate::dsl::Command;
use crate::event::AppEvent;
@@ -946,6 +947,10 @@ fn spawn_dsl_dispatch(
command: command.clone(),
result,
},
Ok(CommandOutcome::ChangeColumn(result)) => AppEvent::DslChangeColumnSucceeded {
command: command.clone(),
result,
},
Err(DbError::PersistenceFatal {
operation,
path,
@@ -982,6 +987,7 @@ enum CommandOutcome {
Insert(InsertResult),
Update(UpdateResult),
Delete(DeleteResult),
ChangeColumn(ChangeColumnTypeResult),
}
/// Execute a parsed user command and return either a typed
@@ -1019,10 +1025,15 @@ async fn execute_command_typed(
.rename_column(table, old, new, src)
.await
.map(|d| CommandOutcome::Schema(Some(d))),
Command::ChangeColumnType { table, column, ty } => database
.change_column_type(table, column, ty, src)
Command::ChangeColumnType {
table,
column,
ty,
mode,
} => database
.change_column_type(table, column, ty, mode, src)
.await
.map(|d| CommandOutcome::Schema(Some(d))),
.map(CommandOutcome::ChangeColumn),
Command::AddRelationship {
name,
parent_table,