feat(types)!: drop the blob column type (ADR-0005 Amendment 2)
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:
@@ -25,8 +25,6 @@
|
||||
|
||||
use std::io::Write as _;
|
||||
|
||||
use base64::Engine as _;
|
||||
|
||||
use crate::dsl::types::Type;
|
||||
|
||||
use super::{CellValue, TableSnapshot};
|
||||
@@ -149,12 +147,6 @@ fn encode_cell(ty: Type, value: &CellValue) -> Result<Cell, String> {
|
||||
CellValue::Text(s) => Ok(Cell::Plain(s.clone())),
|
||||
other => Err(format!("expected date/datetime (text), got {other:?}")),
|
||||
},
|
||||
Type::Blob => match value {
|
||||
CellValue::Blob(bytes) => Ok(Cell::Plain(
|
||||
base64::engine::general_purpose::STANDARD.encode(bytes),
|
||||
)),
|
||||
other => Err(format!("expected blob, got {other:?}")),
|
||||
},
|
||||
Type::Serial => match value {
|
||||
CellValue::Integer(n) => Ok(Cell::Plain(n.to_string())),
|
||||
other => Err(format!("expected serial (int), got {other:?}")),
|
||||
@@ -362,10 +354,6 @@ pub(crate) fn decode_cell(ty: Type, cell: &RawCell) -> Result<CellValue, String>
|
||||
"false" => Ok(CellValue::Integer(0)),
|
||||
other => Err(format!("expected `true` or `false`, got `{other}`")),
|
||||
},
|
||||
Type::Blob => base64::engine::general_purpose::STANDARD
|
||||
.decode(cell.content.as_bytes())
|
||||
.map(CellValue::Blob)
|
||||
.map_err(|e| format!("invalid base64 blob: {e}")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -467,18 +455,6 @@ mod tests {
|
||||
assert_eq!(s, "b\ntrue\nfalse\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blobs_use_base64() {
|
||||
let body = serialize_table(&TableSnapshot {
|
||||
name: "T".to_string(),
|
||||
columns: vec![col("blob", Type::Blob)],
|
||||
rows: vec![vec![CellValue::Blob(b"hello".to_vec())]],
|
||||
})
|
||||
.unwrap();
|
||||
let s = String::from_utf8(body).unwrap();
|
||||
assert!(s.contains("aGVsbG8="));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dates_and_datetimes_pass_through() {
|
||||
let body = serialize_table(&TableSnapshot {
|
||||
@@ -548,13 +524,11 @@ mod tests {
|
||||
col("n", Type::Int),
|
||||
col("r", Type::Real),
|
||||
col("b", Type::Bool),
|
||||
col("blob", Type::Blob),
|
||||
],
|
||||
rows: vec![vec![
|
||||
CellValue::Integer(42),
|
||||
CellValue::Real(std::f64::consts::PI),
|
||||
CellValue::Integer(1),
|
||||
CellValue::Blob(b"hi".to_vec()),
|
||||
]],
|
||||
};
|
||||
let body = serialize_table(&table).unwrap();
|
||||
@@ -572,9 +546,6 @@ mod tests {
|
||||
decode_cell(Type::Bool, &row[2]).unwrap(),
|
||||
CellValue::Integer(1)
|
||||
));
|
||||
assert!(
|
||||
matches!(decode_cell(Type::Blob, &row[3]).unwrap(), CellValue::Blob(b) if b == b"hi")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user