feat: ADR-0035 Amendment 1 — drop composite UNIQUE; friendlier drop-column + generic-error wording
F1/F2/F3 from the whole-Phase-4 /runda (handoff-42 §3):
- F3: drop an anonymous composite UNIQUE via a derived, engine-neutral
name `unique_<cols>` — recomputed live, nothing persisted, reusing the
existing `DROP CONSTRAINT <name>` grammar (no new syntax/metadata, the
§4g anonymity decision intact). A name matching more than one UNIQUE is
refused as ambiguous, never guessed. One undo step. `describe`
annotates each composite UNIQUE with its name.
- F1: dropping a column a composite UNIQUE covers is refused up-front
with the derived name + the actionable drop command (was an unhelpful
generic engine refusal).
- F2: contextless friendly_message() no longer leaks a literal `{table}`
in the generic hint (new `error.generic.hint_no_table`, selected when
no table is in context). The table-ful path is unchanged.
Docs: ADR-0035 Amendment 1 + Status + README index + plan
docs/plans/20260526-adr-0035-composite-unique-drop-f1f2f3.md.
Tests: +5 (drop-by-name, ambiguous-refused, one-undo-step, F1 guard,
F2 no-leak) + a describe-render assertion. 1922 pass / 0 fail / 0 skip;
clippy clean.
This commit is contained in:
@@ -109,6 +109,7 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
|
||||
// ---- Generic engine refusal ----
|
||||
("error.generic.headline", &["operation"]),
|
||||
("error.generic.hint", &["table"]),
|
||||
("error.generic.hint_no_table", &[]),
|
||||
// ---- Invalid-value errors (pre-engine, single-line) ----
|
||||
(
|
||||
"error.invalid_value.arity.headline",
|
||||
|
||||
@@ -142,6 +142,10 @@ error:
|
||||
generic:
|
||||
headline: "the database refused this `{operation}`."
|
||||
hint: "The operation could not be completed against the current state of `{table}`."
|
||||
# Used when no table is in context (e.g. contextless `friendly_message()`
|
||||
# callsites: replay, undo, rebuild, export) so the hint never leaks a
|
||||
# literal `{table}` placeholder.
|
||||
hint_no_table: "The operation could not be completed against the current database state."
|
||||
|
||||
# Errors that are specifically about value validation
|
||||
# (DbError::InvalidValue) — wrong arity, wrong literal
|
||||
|
||||
@@ -659,10 +659,17 @@ fn translate_generic(message: &str, ctx: &TranslateContext) -> FriendlyError {
|
||||
let operation = ctx
|
||||
.operation
|
||||
.map_or("operation", Operation::keyword);
|
||||
let table = ctx_table(ctx);
|
||||
// F2 (ADR-0035 Amendment 1): when no table is in context, use the
|
||||
// table-less hint so a contextless `friendly_message()` (replay, undo,
|
||||
// rebuild, export) never renders a literal `{table}` placeholder.
|
||||
let hint = if ctx.table.is_some() {
|
||||
t!("error.generic.hint", table = ctx_table(ctx))
|
||||
} else {
|
||||
t!("error.generic.hint_no_table")
|
||||
};
|
||||
fe(
|
||||
t!("error.generic.headline", operation = operation),
|
||||
verbose_hint(ctx, t!("error.generic.hint", table = table)),
|
||||
verbose_hint(ctx, hint),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1034,6 +1041,28 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generic_hint_has_no_unsubstituted_table_without_context() {
|
||||
// F2 (ADR-0035 Amendment 1 plan): `friendly_message()` renders
|
||||
// with a default, table-less context at the default Verbose
|
||||
// verbosity, so a generic-bucket error must not leak a literal
|
||||
// `{table}` in its hint.
|
||||
let err = sqlite("some unclassified engine failure", SqliteErrorKind::Other);
|
||||
let rendered = translate(&err, &TranslateContext::default()).render();
|
||||
assert!(
|
||||
!rendered.contains("{table}"),
|
||||
"no unsubstituted placeholder in the table-less generic hint; got:\n{rendered}"
|
||||
);
|
||||
// The table-ful path is unchanged: a table in context still names it.
|
||||
let mut ctx = TranslateContext::for_op(Operation::Delete);
|
||||
ctx.table = Some("Orders".to_string());
|
||||
let with_table = translate(&err, &ctx).render();
|
||||
assert!(
|
||||
with_table.contains("Orders"),
|
||||
"the table-ful generic hint still names the table; got:\n{with_table}"
|
||||
);
|
||||
}
|
||||
|
||||
// ---- passthrough variants ----
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user