feat(hint): H2 Phase C batch 2 — DDL tier-3 hints (ADR-0053)

Per-form hints for the schema-shaping commands: create table, create
m:n, add column/index/constraint, drop table/column/relationship/
index/constraint, rename column, change column (add_relationship was
the Phase-B exemplar). Examples verified against the canonical usage
templates. hint_ids wired on CREATE/CREATE_M2N/DROP/RENAME/CHANGE;
catalogue + keys.rs registered. +2 spot tests (incl. multi-form DROP
disambiguation); 2491 pass / 1 ignored, clippy clean.
This commit is contained in:
claude@clouddev1
2026-06-15 16:05:41 +00:00
parent 4bdfce6250
commit 6429b56443
5 changed files with 123 additions and 8 deletions
+20
View File
@@ -5862,6 +5862,26 @@ mod tests {
); );
} }
// ── Phase C batch 2: DDL hints render (incl. multi-form DROP) ──
#[test]
fn f1_on_create_table_renders_its_hint_block() {
let mut app = App::new();
type_str(&mut app, "create table Customers with pk id(serial)");
f1(&mut app);
assert!(output_contains(&app, "Create a new table"));
}
#[test]
fn f1_disambiguates_drop_forms() {
let mut app = App::new();
type_str(&mut app, "drop index idx_email");
f1(&mut app);
// Resolves drop_index, not drop_table/column/etc.
assert!(output_contains(&app, "Remove an index by name"));
assert!(!output_contains(&app, "Remove a table"));
}
#[test] #[test]
fn messages_command_toggles_verbosity_and_reports() { fn messages_command_toggles_verbosity_and_reports() {
let mut app = App::new(); let mut app = App::new();
+11 -5
View File
@@ -968,7 +968,13 @@ pub static DROP: CommandNode = CommandNode {
shape: DROP_SHAPE, shape: DROP_SHAPE,
ast_builder: build_drop, ast_builder: build_drop,
help_id: Some("ddl.drop"), help_id: Some("ddl.drop"),
hint_ids: &[], hint_ids: &[
"drop_table",
"drop_column",
"drop_relationship",
"drop_index",
"drop_constraint",
],
usage_ids: &[ usage_ids: &[
"parse.usage.drop_table", "parse.usage.drop_table",
"parse.usage.drop_column", "parse.usage.drop_column",
@@ -1004,7 +1010,7 @@ pub static RENAME: CommandNode = CommandNode {
shape: RENAME_COLUMN, shape: RENAME_COLUMN,
ast_builder: build_rename_column, ast_builder: build_rename_column,
help_id: Some("ddl.rename"), help_id: Some("ddl.rename"),
hint_ids: &[], hint_ids: &["rename_column"],
usage_ids: &["parse.usage.rename_column"],}; usage_ids: &["parse.usage.rename_column"],};
pub static CHANGE: CommandNode = CommandNode { pub static CHANGE: CommandNode = CommandNode {
@@ -1012,7 +1018,7 @@ pub static CHANGE: CommandNode = CommandNode {
shape: CHANGE_COLUMN, shape: CHANGE_COLUMN,
ast_builder: build_change_column, ast_builder: build_change_column,
help_id: Some("ddl.change"), help_id: Some("ddl.change"),
hint_ids: &[], hint_ids: &["change_column"],
usage_ids: &["parse.usage.change_column"],}; usage_ids: &["parse.usage.change_column"],};
// ================================================================= // =================================================================
@@ -1373,7 +1379,7 @@ pub static CREATE: CommandNode = CommandNode {
shape: CREATE_TABLE, shape: CREATE_TABLE,
ast_builder: build_create_table, ast_builder: build_create_table,
help_id: Some("ddl.create"), help_id: Some("ddl.create"),
hint_ids: &[], hint_ids: &["create_table"],
usage_ids: &["parse.usage.create_table"],}; usage_ids: &["parse.usage.create_table"],};
// ================================================================= // =================================================================
@@ -1442,7 +1448,7 @@ pub static CREATE_M2N: CommandNode = CommandNode {
shape: CREATE_M2N_SHAPE, shape: CREATE_M2N_SHAPE,
ast_builder: build_create_m2n, ast_builder: build_create_m2n,
help_id: Some("ddl.create_m2n"), help_id: Some("ddl.create_m2n"),
hint_ids: &[], hint_ids: &["create_m2n"],
usage_ids: &["parse.usage.create_m2n"], usage_ids: &["parse.usage.create_m2n"],
}; };
+6 -3
View File
@@ -917,9 +917,12 @@ mod hint_key_tests {
hint_key_for_input_in_mode("insert into T values (1)", Mode::Simple), hint_key_for_input_in_mode("insert into T values (1)", Mode::Simple),
Some("insert") Some("insert")
); );
// A node with no hint_ids yet → None (tier-2 fallback). // Multi-form DROP disambiguates to the typed form too.
assert_eq!(hint_key_for_input_in_mode("drop table T", Mode::Simple), None); assert_eq!(
// Unknown entry word → None. hint_key_for_input_in_mode("drop table T", Mode::Simple),
Some("drop_table")
);
// Unknown entry word → None (tier-2 fallback).
assert_eq!(hint_key_for_input_in_mode("zzz", Mode::Simple), None); assert_eq!(hint_key_for_input_in_mode("zzz", Mode::Simple), None);
} }
} }
+37
View File
@@ -269,6 +269,43 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
("hint.cmd.redo.example", &[]), ("hint.cmd.redo.example", &[]),
("hint.cmd.copy.what", &[]), ("hint.cmd.copy.what", &[]),
("hint.cmd.copy.example", &[]), ("hint.cmd.copy.example", &[]),
// Phase C batch 2 — DDL command hints.
("hint.cmd.create_table.what", &[]),
("hint.cmd.create_table.example", &[]),
("hint.cmd.create_table.concept", &[]),
("hint.cmd.create_m2n.what", &[]),
("hint.cmd.create_m2n.example", &[]),
("hint.cmd.create_m2n.concept", &[]),
("hint.cmd.add_column.what", &[]),
("hint.cmd.add_column.example", &[]),
("hint.cmd.add_column.concept", &[]),
("hint.cmd.add_index.what", &[]),
("hint.cmd.add_index.example", &[]),
("hint.cmd.add_index.concept", &[]),
("hint.cmd.add_constraint.what", &[]),
("hint.cmd.add_constraint.example", &[]),
("hint.cmd.add_constraint.concept", &[]),
("hint.cmd.drop_table.what", &[]),
("hint.cmd.drop_table.example", &[]),
("hint.cmd.drop_table.concept", &[]),
("hint.cmd.drop_column.what", &[]),
("hint.cmd.drop_column.example", &[]),
("hint.cmd.drop_column.concept", &[]),
("hint.cmd.drop_relationship.what", &[]),
("hint.cmd.drop_relationship.example", &[]),
("hint.cmd.drop_relationship.concept", &[]),
("hint.cmd.drop_index.what", &[]),
("hint.cmd.drop_index.example", &[]),
("hint.cmd.drop_index.concept", &[]),
("hint.cmd.drop_constraint.what", &[]),
("hint.cmd.drop_constraint.example", &[]),
("hint.cmd.drop_constraint.concept", &[]),
("hint.cmd.rename_column.what", &[]),
("hint.cmd.rename_column.example", &[]),
("hint.cmd.rename_column.concept", &[]),
("hint.cmd.change_column.what", &[]),
("hint.cmd.change_column.example", &[]),
("hint.cmd.change_column.concept", &[]),
( (
"hint.ambient_invalid_ident", "hint.ambient_invalid_ident",
&["kind", "found"], &["kind", "found"],
+49
View File
@@ -456,6 +456,55 @@ hint:
copy: copy:
what: "Copy the output panel to the clipboard — all of it, or just the last command's output." what: "Copy the output panel to the clipboard — all of it, or just the last command's output."
example: "copy last" example: "copy last"
# DDL — schema-shaping commands (Phase C batch 2).
create_table:
what: "Create a new table — its columns, their types, and a primary key."
example: "create table Customers with pk id(serial), name(text), email(text)"
concept: "A table is a set of rows that share the same columns. The primary key uniquely identifies each row; a `serial` key numbers the rows for you."
create_m2n:
what: "Create a junction table linking two tables many-to-many."
example: "create m:n relationship from Students to Courses"
concept: "A many-to-many link (a student takes many courses; a course has many students) can't live in either table, so it gets its own junction table holding a foreign key to each side."
add_column:
what: "Add a new column to an existing table."
example: "add column Customers: phone (text)"
concept: "Existing rows take the column's default, or null. A `not null` column with no default can't be added to a table that already has rows — there'd be nothing to put in them."
add_index:
what: "Create an index on one or more columns to speed up lookups."
example: "add index as idx_email on Customers (email)"
concept: "An index is a sorted side-structure that makes a lookup like `where email = …` fast, at the cost of a little space and slightly slower writes."
add_constraint:
what: "Add a constraint — not null, unique, default, or check — to an existing column."
example: "add constraint not null to Customers.email"
concept: "A constraint is a rule the database enforces on every row. Adding one fails if existing rows already break it, so you fix the data first."
drop_table:
what: "Remove a table and all of its rows."
example: "drop table Customers"
concept: "If other tables reference this one through a relationship, drop those relationships (or their child rows) first — the database won't orphan them."
drop_column:
what: "Remove a column from a table."
example: "drop column Customers: phone"
concept: "The column's values are lost. You can't drop a primary-key column, or one a relationship depends on."
drop_relationship:
what: "Remove a relationship between two tables."
example: "drop relationship customer_orders"
concept: "This drops the foreign-key link and stops the database enforcing it; the tables and their rows stay. The foreign-key column itself remains unless you also drop it."
drop_index:
what: "Remove an index by name."
example: "drop index idx_email"
concept: "Only the lookup shortcut goes — the data is untouched. Queries still work, just without that speed-up."
drop_constraint:
what: "Remove a constraint from a column."
example: "drop constraint not null from Customers.email"
concept: "The rule stops being enforced from now on; rows already stored are left as they are."
rename_column:
what: "Rename a column, keeping its values and type."
example: "rename column Customers: email to contact_email"
concept: "Only the name changes — the stored data is the same. References to the column are reconciled so nothing breaks."
change_column:
what: "Change a column's type, converting the existing values."
example: "change column Customers: status (int)"
concept: "The database converts each stored value to the new type; if a value can't convert it refuses the change, so you don't silently lose data. Flags let you force or skip the conversion."
err: err:
foreign_key: foreign_key:
child_side: child_side: