Skip to content

Column-name autocompletion: FROM-driven column loading + alias/scope awareness #84

Description

@BorisTyshkevich

⚠️ Reconciled 2026-06-30 (#88 / #21). With the editor moving to CodeMirror 6 behind an EditorPort seam (#21), this lands on CM6, not the textarea: completion is hosted by a CM6 completion source (@codemirror/lang-sql schemaCompletionSource + a custom source) instead of editor-complete.js + the textarea dropdown. Depends on #21 (sequence: signals → #21 → this; #68 Phase 4). The pure src/core/from-scope.js below (alias / FROM-scope resolution + unqualified-column scoping) is editor-agnostic, feeds the CM6 source, and remains the core of this issue. The textarea-hosting details in Proposed solution / Scope (editor-complete.js, dropdown refresh) are superseded by the CM6 adapter; the FROM-scope/alias/scoping logic and the Acceptance criteria stand.


Part of #68 (Roadmap to 1.0.0) — Editor intelligence track.

Problem

Column-name autocompletion technically exists but almost never fires in practice. The engine already emits column candidates for any loaded table (src/core/completions.js:102), already filters qualified table. to that table's columns (completions.js:195), and the dropdown already renders columns with their type. But columns are only fetched when the user manually expands that table in the schema sidebar (src/ui/schema.js:116src/ui/app.js:375 loadColumns) — nobody does that mid-typing, so the candidate pool is almost always empty. On top of that:

  • Aliases don't resolve. The qualified filter matches ctx.parent literally, so events. works but e. from FROM events e does not (parent is "e", no such table).
  • Unqualified columns aren't scoped. A bare word competes against columns from every loaded table globally with a flat boost (completions.js:215) — no notion of which tables are in the current statement's FROM.

Proposed solution

Make columns available while typing, driven by the statement's FROM/JOIN clause, and make completion FROM-aware. Two settled decisions:

  • FROM-driven lazy loading. Parse the current statement's FROM/JOIN tables; load their system.columns on a debounced idle tick — metadata-only, deduped via the existing 'loading' sentinel, cached per connection, and never on the keystroke path (our standing editor rule). Refresh the open dropdown when columns arrive.
  • Full FROM awareness. Resolve aliases (e.events) and scope/boost unqualified column suggestions to the statement's in-scope tables.

Scope

  • src/core/from-scope.js (new, 100% covered) — pure: given editor text + caret, return the { db, table, alias }[] for the statement containing the caret (handle db.table, table alias, table AS alias, multiple comma joins and JOINs; reuse the existing SQL tokenizer so strings/comments don't fool it). This single module feeds all three gaps.
  • src/core/completions.js — qualified path resolves ctx.parent through the alias map before filtering; unqualified path scopes/boosts columns to in-scope tables instead of the global pool. Keep current behavior when no FROM scope is available.
  • Editor-driven column loading in src/ui/app.js + src/ui/editor-complete.js — on a debounced idle tick (not per keystroke), diff the statement's in-scope tables against schema and call the existing ch.loadColumns for any whose columns are still null; mark 'loading' to dedupe; on completion rebuildCompletions() and refresh the open dropdown. No change to src/net/ch-client.js (reuses loadColumns).
  • State — no new fields needed; columns continue to live on tableObj.columns (src/state.js:53 schema). The debounce timer/handle lives with the editor-complete host.

Non-goals (v1)

  • CTE / subquery-derived column scopes, USING/correlated-subquery resolution, SELECT * expansion — keep to real base tables in FROM/JOIN.
  • Prefetch-all-on-connect (rejected: large payload on big catalogs); revisit as a hybrid later if needed.

Acceptance

  • Typing in a query with FROM db.table (without sidebar-expanding it) offers that table's columns, unqualified, scoped to the statement.
  • e. after FROM events e (and ... events AS e) offers events' columns.
  • db.table. and table. qualification still work.
  • Column metadata loads on a debounced idle tick, deduped and cached — never fetched on the keystroke path; the open dropdown refreshes when columns arrive.
  • Multi-table FROM/JOIN scopes columns to all joined tables; unrelated loaded tables are not suggested unqualified.
  • npm test green at the per-file coverage gate (from-scope.js + completions.js at 100%).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions