feat(types)!: drop the blob column type (ADR-0005 Amendment 2)
ci / gate (push) Failing after 35s
ci / manifests (push) Successful in 3s
website / deploy (push) Successful in 35s

blob was a dead-end: declarable but never fillable (no literal in either
mode, seed-unsupported), so a blob column could only ever hold NULL. Remove
Type::Blob + Value/CellValue::Blob + the base64 CSV path + the grammar slot
+ completion/render/type-change/seed handling + the binder refusal (whose
message also carried a user-facing "DSL" copy-rule bug). The vocabulary is
now nine types; base64 stays (clipboard OSC-52).

Backward compat is the ADR-0015 migration framework's first real use: a
v1->v2 format bump (CURRENT_SCHEMA_VERSION across serializer/parser/skeleton)
with a migrator that rewrites `type: blob` -> `type: text`, and a forced .db
rebuild from the migrated text when a blob column was actually converted
(the stale .db keeps a STRICT BLOB engine column). Conversion to text is
non-destructive and CSV-free. Covered by a full-stack integration test and a
Tier-4 PTY test that opens a real legacy v1-blob project.

Also sweeps the nine-type vocabulary through CLAUDE.md, requirements.md, the
website (type reference, seed doc, highlight grammar), and the ADR-0030/0033/
0035 cross-references; CHANGELOG Removed entry; handoff-79.

BREAKING CHANGE: the `blob` column type is removed. Existing projects that
declared a blob column are migrated on first open (the column becomes text;
the original project.yaml is kept as a .v1.bak).
This commit is contained in:
claude@clouddev1
2026-06-22 21:25:38 +00:00
parent 1a2002dbf6
commit 6b4c4dcea4
41 changed files with 506 additions and 390 deletions
+4 -31
View File
@@ -575,11 +575,7 @@ fn database_run_select_type_recovery_works_on_empty_table() {
// when no row matches.
//
// This test pins that invariant: a fresh table with no
// rows still yields the right `column_types` entry. It
// also justifies the all-types test below using NULL for
// col_blob (the DSL Value enum has no Blob variant, but
// since metadata doesn't read row values, a NULL cell
// doesn't compromise the recovery).
// rows still yields the right `column_types` entry.
let (_p, db, _dir) = open_project_db();
let rt = rt();
rt.block_on(async {
@@ -588,7 +584,6 @@ fn database_run_select_type_recovery_works_on_empty_table() {
vec![
ColumnSpec::new("id", Type::Serial),
ColumnSpec::new("col_text", Type::Text),
ColumnSpec::new("col_blob", Type::Blob),
],
vec!["id".to_string()],
None,
@@ -602,32 +597,16 @@ fn database_run_select_type_recovery_works_on_empty_table() {
.expect("SELECT runs even on empty table");
assert!(data_text.rows.is_empty());
assert_eq!(data_text.column_types, vec![Some(Type::Text)]);
let data_blob = rt
.block_on(db.run_select("select col_blob from Empty".to_string()))
.expect("SELECT runs even on empty table");
assert!(data_blob.rows.is_empty());
assert_eq!(
data_blob.column_types,
vec![Some(Type::Blob)],
"Blob metadata must be recoverable even with no row data",
);
}
#[test]
fn database_run_select_recovers_all_ten_playground_types() {
fn database_run_select_recovers_all_nine_playground_types() {
// ADR-0032 §12 + Amendment 1 — every playground type
// round-trips through column-origin metadata on a bare
// projection ref. One table holds one column of each
// type; a SELECT of that column produces the right
// `column_types[0]` entry.
//
// `serial` and `shortid` are auto-generated. `col_blob`
// is left NULL in the inserted row because the DSL Value
// enum has no Blob variant — but per
// `database_run_select_type_recovery_works_on_empty_table`
// above, column-origin metadata is row-independent, so
// the NULL cell doesn't compromise this test's correctness.
// `column_types[0]` entry. `serial` and `shortid` are
// auto-generated.
let (_p, db, _dir) = open_project_db();
let rt = rt();
rt.block_on(async {
@@ -642,7 +621,6 @@ fn database_run_select_recovers_all_ten_playground_types() {
ColumnSpec::new("col_bool", Type::Bool),
ColumnSpec::new("col_date", Type::Date),
ColumnSpec::new("col_datetime", Type::DateTime),
ColumnSpec::new("col_blob", Type::Blob),
ColumnSpec::new("col_shortid", Type::ShortId),
],
vec!["pk".to_string()],
@@ -650,10 +628,6 @@ fn database_run_select_recovers_all_ten_playground_types() {
)
.await
.expect("create table");
// Blob has no DSL literal form, so col_blob takes the
// default NULL on insert. Column-origin metadata is
// based on the column DEFINITION, not the row value
// (Amendment 1), so the type recovery still succeeds.
db.insert(
"AllTypes".to_string(),
Some(vec![
@@ -691,7 +665,6 @@ fn database_run_select_recovers_all_ten_playground_types() {
("col_bool", Type::Bool),
("col_date", Type::Date),
("col_datetime", Type::DateTime),
("col_blob", Type::Blob),
("col_shortid", Type::ShortId),
];
for (col, expected_type) in cases {