feat(hint): H2 Phase C batch 4 — advanced-mode SQL tier-3 hints (ADR-0053)

Distinct SQL-syntax hints for the 11 advanced-mode forms: sql create
table / alter table / create index / drop index / drop table / insert /
update / delete, select, with, explain. hint_ids wired on all 11 nodes.

Hardened hint_key_for_input_in_mode for shared entry words: a bare
multi-form entry word defers to tier-2; when the second token isn't a
form word (insert into / update … set), it falls back to the
mode-primary key — so advanced mode resolves to the SQL form and simple
mode to the DSL form. catalogue + keys.rs registered. +2 spot tests +
grammar mode-disambiguation asserts; 2495 pass / 1 ignored, clippy clean.
This commit is contained in:
claude@clouddev1
2026-06-15 16:14:23 +00:00
parent 9c4d520d5c
commit 97970f2a2c
6 changed files with 150 additions and 12 deletions
+6 -6
View File
@@ -1874,7 +1874,7 @@ pub static EXPLAIN_SQL: CommandNode = CommandNode {
// too). Mirrors the `SQL_INSERT`/`SQL_UPDATE`/`SQL_DELETE`
// precedent; otherwise `note_help` would print `explain` twice.
help_id: None,
hint_ids: &[],
hint_ids: &["explain_sql"],
usage_ids: &[],};
/// SQL `SELECT` (ADR-0030 §6, ADR-0031, ADR-0032).
@@ -1890,7 +1890,7 @@ pub static SELECT: CommandNode = CommandNode {
shape: Node::Subgrammar(&sql_select::SQL_SELECT_TAIL),
ast_builder: build_select,
help_id: None,
hint_ids: &[],
hint_ids: &["select"],
usage_ids: &["parse.usage.select"],};
/// `WITH …` top-level statement (ADR-0032 §4 / sub-phase 2c).
@@ -1905,7 +1905,7 @@ pub static WITH: CommandNode = CommandNode {
shape: Node::Subgrammar(&sql_select::SQL_WITH_TAIL),
ast_builder: build_select,
help_id: None,
hint_ids: &[],
hint_ids: &["with"],
usage_ids: &["parse.usage.with"],};
/// SQL `INSERT` — the `Advanced`-category node of the shared
@@ -1923,7 +1923,7 @@ pub static SQL_INSERT: CommandNode = CommandNode {
shape: Node::Subgrammar(&sql_insert::SQL_INSERT_SHAPE),
ast_builder: build_sql_insert,
help_id: None,
hint_ids: &[],
hint_ids: &["sql_insert"],
usage_ids: &[],
};
@@ -1937,7 +1937,7 @@ pub static SQL_UPDATE: CommandNode = CommandNode {
shape: Node::Subgrammar(&sql_update::SQL_UPDATE_SHAPE),
ast_builder: build_sql_update,
help_id: None,
hint_ids: &[],
hint_ids: &["sql_update"],
usage_ids: &[],
};
@@ -1953,7 +1953,7 @@ pub static SQL_DELETE: CommandNode = CommandNode {
shape: Node::Subgrammar(&sql_delete::SQL_DELETE_SHAPE),
ast_builder: build_sql_delete,
help_id: None,
hint_ids: &[],
hint_ids: &["sql_delete"],
usage_ids: &[],
};
+5 -5
View File
@@ -1879,7 +1879,7 @@ pub static SQL_CREATE_TABLE: CommandNode = CommandNode {
shape: Node::Subgrammar(&super::sql_create_table::SQL_CREATE_TABLE_SHAPE),
ast_builder: build_sql_create_table,
help_id: Some("ddl.sql_create_table"),
hint_ids: &[],
hint_ids: &["sql_create_table"],
usage_ids: &["parse.usage.sql_create_table"],
};
@@ -1899,7 +1899,7 @@ pub static SQL_DROP_TABLE: CommandNode = CommandNode {
shape: SQL_DROP_TABLE_SHAPE,
ast_builder: build_sql_drop_table,
help_id: Some("ddl.sql_drop_table"),
hint_ids: &[],
hint_ids: &["sql_drop_table"],
usage_ids: &["parse.usage.sql_drop_table"],
};
@@ -1919,7 +1919,7 @@ pub static SQL_DROP_INDEX: CommandNode = CommandNode {
shape: SQL_DROP_INDEX_SHAPE,
ast_builder: build_sql_drop_index,
help_id: Some("ddl.sql_drop_index"),
hint_ids: &[],
hint_ids: &["sql_drop_index"],
usage_ids: &["parse.usage.sql_drop_index"],
};
@@ -2001,7 +2001,7 @@ pub static SQL_CREATE_INDEX: CommandNode = CommandNode {
shape: SQL_CREATE_INDEX_SHAPE,
ast_builder: build_sql_create_index,
help_id: Some("ddl.sql_create_index"),
hint_ids: &[],
hint_ids: &["sql_create_index"],
usage_ids: &["parse.usage.sql_create_index"],
};
@@ -2560,7 +2560,7 @@ pub static SQL_ALTER_TABLE: CommandNode = CommandNode {
shape: SQL_ALTER_TABLE_SHAPE,
ast_builder: build_sql_alter_table,
help_id: Some("ddl.sql_alter_table"),
hint_ids: &[],
hint_ids: &["sql_alter_table"],
usage_ids: &["parse.usage.sql_alter_table"],
};
+38 -1
View File
@@ -616,10 +616,13 @@ pub fn usage_keys_for_input_in_mode(
/// form has no tier-3 block yet (the caller falls back to tier-2).
#[must_use]
pub fn hint_key_for_input_in_mode(source: &str, mode: crate::mode::Mode) -> Option<&'static str> {
use crate::dsl::walker::lex_helpers::{consume_ident, skip_whitespace};
let nodes = selected_nodes_for_input_in_mode(source, mode);
if nodes.is_empty() {
return None;
}
// Mode-ordered union (advanced-primary first in advanced mode), so a
// shared entry word resolves to the surface the user is in.
let mut keys: Vec<&'static str> = Vec::new();
for (_, node, _) in &nodes {
for k in node.hint_ids {
@@ -628,7 +631,25 @@ pub fn hint_key_for_input_in_mode(source: &str, mode: crate::mode::Mode) -> Opti
}
}
}
pick_form_key(source, &keys)
if keys.is_empty() {
return None;
}
if keys.len() == 1 {
return Some(keys[0]);
}
// A bare multi-form entry word (no form word yet — `add`⏎) has no
// chosen form: defer to tier-2, which lists the choices.
let start = skip_whitespace(source, 0);
if let Some((_, entry_end)) = consume_ident(source, start)
&& skip_whitespace(source, entry_end) >= source.len()
{
return None;
}
// A form word picks the form (`drop column` → `drop_column`); when
// the second token isn't a form word (`insert into …`, `update …
// set`), fall back to the mode-primary key — in advanced mode the
// SQL form, in simple mode the DSL form.
pick_form_key(source, &keys).or_else(|| keys.first().copied())
}
/// Shared mode-aware command-form selection for the entry word at the
@@ -922,6 +943,22 @@ mod hint_key_tests {
hint_key_for_input_in_mode("drop table T", Mode::Simple),
Some("drop_table")
);
// Mode picks the surface for a shared entry word whose second
// token isn't a form word: SQL form in advanced, DSL in simple.
assert_eq!(
hint_key_for_input_in_mode("insert into T values (1)", Mode::Advanced),
Some("sql_insert")
);
assert_eq!(
hint_key_for_input_in_mode("insert into T values (1)", Mode::Simple),
Some("insert")
);
// `create table` shares a form word — advanced-first ordering
// resolves it to the SQL form in advanced mode.
assert_eq!(
hint_key_for_input_in_mode("create table T (id int)", Mode::Advanced),
Some("sql_create_table")
);
// Unknown entry word → None (tier-2 fallback).
assert_eq!(hint_key_for_input_in_mode("zzz", Mode::Simple), None);
}