Skip to content

feat: FROM-aware autocompletion — alias/scope resolution + debounced column loading (#84)#146

Merged
BorisTyshkevich merged 2 commits into
mainfrom
feat/from-scope-completions-84
Jul 4, 2026
Merged

feat: FROM-aware autocompletion — alias/scope resolution + debounced column loading (#84)#146
BorisTyshkevich merged 2 commits into
mainfrom
feat/from-scope-completions-84

Conversation

@BorisTyshkevich

Copy link
Copy Markdown
Collaborator

What & why

Implements #84 — schema-aware, FROM-driven autocompletion on the CM6 editor (#21). Column completion previously almost never fired: columns were only fetched when a table was expanded in the sidebar, aliases didn't resolve, and unqualified columns competed against every loaded table globally.

Now completion is FROM-aware and columns load while you type:

  • src/core/from-scope.js (new, pure, 100%) — fromScopeAt(text, pos[, toks]) resolves the caret's statement into its base tables ({db, table, alias}[]), reusing the SQL tokenizer so a FROM/; inside a string or comment can't fool it. Handles db.table, table alias, table AS alias, comma joins, JOIN; skips ARRAY JOIN, table functions, and FROM-position subqueries. pendingColumnLoads(scope, schema) diffs the in-scope tables against the loaded schema.
  • src/core/completions.jsrankCompletions resolves ctx.parent through the FROM-scope aliases (e.events) and scopes/boosts unqualified columns to the statement's tables (an unrelated loaded table's columns are no longer suggested). With no scope in view it degrades to today's global pool, so existing behavior is unchanged. completionContext/openBacktickStart now accept a shared token list so the completion path lexes the caret prefix once, not twice.
  • src/editor/codemirror-adapter.js — the completion source attaches the scope; a debounced idle tick (300 ms, never on the keystroke path) prefetches unloaded FROM columns via the existing app.loadColumns (the 'loading' sentinel dedupes; per-connection cache) and refreshes an open dropdown when they arrive — guarded against a destroy-between-tick-and-resolve race with this file's view === v idiom.

No src/ui/app.js change (the adapter reuses app.actions.loadColumns / app.state.schema / app.completions). No new runtime dependency — startCompletion/completionStatus come from the already-bundled @codemirror/autocomplete.

Acceptance (all met)

  • FROM db.table (unexpanded) offers that table's columns, unqualified, scoped to the statement ✅
  • e. after FROM events e (and … AS e) offers events' columns ✅
  • db.table. / table. qualification still works ✅
  • Columns load on a debounced idle tick, deduped + cached, never on the keystroke path; the open dropdown refreshes on arrival ✅
  • Multi-table FROM/JOIN scopes to all joined tables; unrelated loaded tables aren't suggested unqualified ✅
  • npm test green at the per-file gate (from-scope.js + completions.js at 100%) ✅

Testing

  • npm test — 1386 pass; from-scope.js and completions.js at 100/100/100/100.
  • npm run build — succeeds.
  • npm run test:e2e (chromium + webkit) — all editor specs pass, including a new cross-engine test asserting e.events columns with out-of-scope suppression. (Firefox skipped — known sandbox EPERM in this environment.)
  • Self-reviewed with /code-review --high (correctness + cleanup fan-out): fixed a NUL-byte-in-dedup-key issue, removed a double-tokenization on the completion path, dropped a dead token field, and closed the async-after-destroy refresh race.

Checklist

  • npm test passes (the per-file coverage gate is non-negotiable)
  • Tests added/updated in the same change as the code
  • npm run build succeeds (single-file dist/sql.html)
  • Layers kept honest: pure logic in src/core/, network in src/net/ (injected fetch), DOM in src/ui/ / editor behind the EditorPort seam
  • No new runtime dependency
  • README / CHANGELOG.md ([Unreleased]) updated
  • Reconciled affected tracked work (roadmap Roadmap to 1.0.0 #68 Phase 4, CHANGELOG); ADR-0001 unaffected (reactivity, not touched)

Closes #84

🤖 Generated with Claude Code

…column loading (#84)

Column completion now fires while typing, driven by the statement's FROM/JOIN
clause, without expanding a table in the sidebar first.

- src/core/from-scope.js (new, pure, 100%): fromScopeAt(text, pos[, toks]) →
  {db, table, alias}[] for the caret's statement, reusing the SQL tokenizer so
  strings/comments/`;` can't fool it; pendingColumnLoads(scope, schema) diffs
  the in-scope tables against the loaded schema.
- src/core/completions.js: rankCompletions resolves `ctx.parent` through the
  FROM-scope aliases (`e.` → events) and scopes/boosts unqualified columns to
  the statement's tables; absent scope keeps today's global behavior.
  completionContext/openBacktickStart accept a shared token list so the
  completion path lexes the caret prefix once.
- src/editor/codemirror-adapter.js: the completion source attaches the scope;
  a debounced idle tick (300ms, off the keystroke path) prefetches unloaded
  FROM columns via the existing app.loadColumns ('loading' sentinel dedupes,
  per-connection cache) and refreshes an open dropdown — guarded against a
  destroy-between-tick-and-resolve race with the file's `view === v` idiom.

Acceptance criteria covered; from-scope.js + completions.js at 100/100/100/100.
Unit tests (real CM6 under happy-dom) + a cross-engine e2e for alias completion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GyLqZGyUkm7mP6WhZCkodj
@BorisTyshkevich BorisTyshkevich mentioned this pull request Jul 4, 2026
31 tasks
@BorisTyshkevich BorisTyshkevich merged commit 6410a06 into main Jul 4, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant