Completion: narrow column candidates to the active table
Two related fixes:
1. \`update MyTable set \` was offering columns from every
table in the project — completion fetched
\`cache.for_source(IdentSource::Columns)\` which returns the
flat \`cache.columns\` (union of every table's columns).
The walker's WalkContext had \`current_table_columns\`
populated (because the update-table-name slot is
\`writes_table: true\`) but the completion engine never
consulted it.
2. \`insert into MyTable (\` was offering nothing — the
value-literal suppression fired because the expected set at
this position contains both Form A column-list candidates
(\`Ident{Columns}\`) and Form C bare-value-list literals
(null/true/false/NumberLit/StringLit). \`is_value_literal_signature\`
matched and the engine returned \`None\` before the column
candidates were considered.
The fix threads the walker's \`current_table_columns\` through
to the completion engine and narrows the suppression rule:
**Walker:**
- New \`walker::CompletionProbe { expected, current_table_columns }\`
struct.
- New \`walker::completion_probe(source, schema) -> CompletionProbe\`
runs one schema-aware walk and reports both the expected
set (or tail_expected on Match) and the resolved table-column
snapshot.
**Completion engine:**
- \`candidates_at_cursor_with\` calls \`completion_probe\` and
reads \`current_table_columns\` for the \`Columns\` ident
source. Schemaless or unknown-table falls back to the flat
\`cache.columns\` (preserves pre-fix behavior).
- Value-literal suppression now gated on
\`!has_schema_ident\` — if the expected set also offers a
schema-listable Ident, the user has actionable candidates
beyond the misleading null/true/false trio and we shouldn't
hide them.
Tests:
- \`update_set_offers_only_current_table_columns\` confirms
Customers' columns appear while Orders' columns don't.
- \`update_where_offers_only_current_table_columns\` covers
the where path.
- \`insert_into_open_paren_offers_current_table_columns\` and
\`insert_into_open_paren_does_not_offer_unrelated_columns\`
cover the Form A column-list position.
- \`drop_column_from_offers_only_current_table_columns\`
documents the DDL fallback (drop-column's table-name slot
doesn't currently \`writes_table\` — falls back to the flat
list).
For the user: \`update MyTable set \` now offers only
MyTable's columns. \`insert into MyTable (\` offers all of
MyTable's columns so Form A is fully discoverable.
Tests: 859 passing, 0 failing, 1 ignored. Clippy clean.
This commit is contained in:
@@ -222,6 +222,63 @@ const fn catalog_key_for_value_type(ty: crate::dsl::types::Type) -> &'static str
|
||||
}
|
||||
}
|
||||
|
||||
/// Completion-engine probe (ADR-0024 §Phase D §column-narrowing).
|
||||
///
|
||||
/// Runs a single schema-aware walk and returns the structured
|
||||
/// pieces the completion engine needs: the expected set plus
|
||||
/// the table-context snapshot the engine reads to narrow
|
||||
/// column candidates to the active table.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CompletionProbe {
|
||||
pub expected: Vec<outcome::Expectation>,
|
||||
/// Columns of `current_table` resolved at the cursor (set
|
||||
/// by an `Ident { source: Tables, writes_table: true }`
|
||||
/// earlier in the walk). `None` when the walker is
|
||||
/// schemaless or the table didn't resolve.
|
||||
pub current_table_columns: Option<Vec<crate::completion::TableColumn>>,
|
||||
}
|
||||
|
||||
/// Run a schema-aware walk and report the completion-engine's
|
||||
/// view (ADR-0024 §Phase D §column-narrowing).
|
||||
#[must_use]
|
||||
pub fn completion_probe(
|
||||
source: &str,
|
||||
schema: &crate::completion::SchemaCache,
|
||||
) -> CompletionProbe {
|
||||
use crate::dsl::grammar::REGISTRY;
|
||||
|
||||
if source.trim().is_empty() {
|
||||
return CompletionProbe {
|
||||
expected: REGISTRY
|
||||
.iter()
|
||||
.map(|c| outcome::Expectation::Word(c.entry.primary))
|
||||
.collect(),
|
||||
current_table_columns: None,
|
||||
};
|
||||
}
|
||||
let mut ctx = context::WalkContext::with_schema(schema);
|
||||
let (result, _cmd) = walk(source, outcome::WalkBound::EndOfInput, &mut ctx);
|
||||
let Some(result) = result else {
|
||||
return CompletionProbe {
|
||||
expected: REGISTRY
|
||||
.iter()
|
||||
.map(|c| outcome::Expectation::Word(c.entry.primary))
|
||||
.collect(),
|
||||
current_table_columns: None,
|
||||
};
|
||||
};
|
||||
let expected = match result.outcome {
|
||||
outcome::WalkOutcome::Match { .. } => result.tail_expected,
|
||||
outcome::WalkOutcome::Incomplete { expected, .. }
|
||||
| outcome::WalkOutcome::Mismatch { expected, .. } => expected,
|
||||
outcome::WalkOutcome::ValidationFailed { .. } => Vec::new(),
|
||||
};
|
||||
CompletionProbe {
|
||||
expected,
|
||||
current_table_columns: ctx.current_table_columns,
|
||||
}
|
||||
}
|
||||
|
||||
/// What the grammar would accept at the end of `source`
|
||||
/// (ADR-0024 §architecture, Phase F walker-driven completion).
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user