Files
gridline/src/lib/sqlCompletion.ts
T
adrianbonpin 80962d7d11 DB viewer + query editor enhancements (home-screen-ux-query-editor) (#4)
* feat: add query_history table migration (v5) (Task 1)

* feat: add isDestructiveQuery utility (Task 2)

* feat: add tabType discriminator and openQueryTab to dbViewerStore (Task 3)

* feat: add execute_query command with pagination and query history (Task 4)

* feat: add typed wrappers for executeQuery, getQueryHistory, clearQueryHistory (Task 5)

* fix: global search bypasses folder scope when filters active (Task 6)

* feat: add TagFilterDropdown with checkboxes and empty state (Task 7)

* feat: add DbTypeFilterDropdown with checkboxes and clear all (Task 8)

* feat: wire TagFilterDropdown/DbTypeFilterDropdown into ActionRow, add inline tag creation (Task 9)

* feat: add Name input to GeneralTab for connection editing (Task 10)

* feat: add QueryEditor Monaco wrapper with SQL mode and Cmd+Enter (Task 11)

* feat: add DestructiveQueryDialog with SQL preview and confirmation (Task 12)

* feat: integrate query tabs, Monaco editor, destructive guard into DbViewerScreen (Task 13)

* fix: harden moveConnection against race conditions on rapid drags (Task 14)

* docs: update AGENTS.md implementation status for Home Screen UX + Query Editor (Task 15)

* feat: switch tag filter to OR semantics, add environment filter (F-T16)

* feat: add activeEnvironment filter state to uiStore and useFilteredConnections (F-T17)

* feat: add environment filter select to Filters dropdown (F-T18)

* feat: filter folder cards by tag match or contained connections (F-T19)

* fix: keep grid header width to content, border last column

* fix: hide select-all checkbox and empty-state when no table open

* fix: filter folder cards by any active filter, show global search results (F-T20)

* feat: show 'Showing Search Results' breadcrumb with clear button (F-T21)

* docs: update README + AGENTS.md for Query Editor, filters, and planned AI integration (BYOK)

* feat: refresh indicator with spinning icon and pulse, defer auto-refresh on tab switch

* feat: smart default schema selection, refresh schemas on database switch

* fix: auto-refresh waits for in-flight refresh to complete before next tick

* style: shrink db viewer sidebar nav icons from 20px to 16px

* style: shrink db viewer sidebar nav buttons to 32px (8px padding)

* style: make Tables panel title xs, regular weight, muted

* style: bump Tables panel title back to sm, keep regular weight and muted

* feat: export schema diagram as PNG/JPEG/SVG (entire schema or viewport)

* chore: lockfile for html-to-image

* fix: raise schema visualizer toolbar above legend so export menu isn't hidden

* feat: schema export via save dialog, transparent background option, save notification

* fix: render nothing in tab bar when no tabs are open

* style: reduce tab bar height from 40px to 36px

* style: reduce tab bar height to 32px

* style: revert tab bar height to 36px

* feat: split tab bar with fixed +Query and Changes actions on the right

* style: blue play-icon Query button in tab bar

* refactor: remove sidebar New Query button (now in tab bar)

* style: conditional bottom padding in sidebar toolbar when nothing is below

* feat: distinguish table and query tabs with icons

* style: tab icons follow active/inactive state, muted colors

* feat: query tab toolbar (run/format/dialect badge) + bare transparent editor

* style: blue rounded Run Query button in query toolbar

* feat: smart platform-aware shortcut tooltip on Run Query (⌘+⏎ / Ctrl+Enter)

* style: show only the shortcut in the Run Query tooltip

* fix: Cmd+Enter keybinding stale closure; add run pulse to query toolbar; bundle monaco locally (offline)

* feat: show placeholder text in empty query editor

* feat: SQL autocomplete — keywords + table names from active schema

* feat: per-table column autocomplete on 'table.' + docs update

* feat: query-variant result toolbar — export/refresh/columns left, smart-unit execution time right

* fix: populate execution_time_ms on query results so the toolbar can show time taken

* fix: re-measure monaco fonts after async font load to stop cursor drift

* feat: resizable + collapsible query results panel

* refactor: move results caret onto the resize handle (centered), bottom caret when collapsed

* style: thin drag strip with caret on its own centered pill

* refactor: remove Queue button from table toolbar (Changes lives in tab bar)

* style: changes button becomes bordered rounded icon with count badge

* docs: mark tab-bar Changes queue button in AGENTS.md and README
2026-08-01 05:56:47 +08:00

172 lines
3.5 KiB
TypeScript

import type { TableInfo, GraphColumn } from "./types";
import { getSchemaGraph } from "./commands";
export const SQL_KEYWORDS = [
"SELECT",
"FROM",
"WHERE",
"INSERT",
"INTO",
"VALUES",
"UPDATE",
"SET",
"DELETE",
"CREATE",
"TABLE",
"DROP",
"ALTER",
"ADD",
"COLUMN",
"PRIMARY",
"KEY",
"FOREIGN",
"REFERENCES",
"INDEX",
"UNIQUE",
"JOIN",
"INNER",
"LEFT",
"RIGHT",
"OUTER",
"FULL",
"CROSS",
"ON",
"AS",
"AND",
"OR",
"NOT",
"NULL",
"IS",
"IN",
"EXISTS",
"BETWEEN",
"LIKE",
"ILIKE",
"LIMIT",
"OFFSET",
"ORDER",
"BY",
"GROUP",
"HAVING",
"DISTINCT",
"UNION",
"ALL",
"CASE",
"WHEN",
"THEN",
"ELSE",
"END",
"ASC",
"DESC",
"WITH",
"RETURNING",
"BEGIN",
"COMMIT",
"EXPLAIN",
"GRANT",
"REVOKE",
] as const;
export type SqlSuggestionKind = "keyword" | "table" | "column";
export interface SqlSuggestion {
label: string;
insertText: string;
kind: SqlSuggestionKind;
}
/**
* Parse the text before the cursor into a `table.` reference. Returns the
* table (and optional schema qualifier) when the text ends in a dot right
* after an identifier, e.g. `users.` or `public.users.`.
*/
export function parseTableRef(
text: string,
): { schema: string | null; table: string } | null {
const match = text.match(/(?:(\w+)\.)?(\w+)\.\s*$/);
if (!match) return null;
return { schema: match[1] ?? null, table: match[2] };
}
/**
* Build column suggestions (used after a `table.` reference).
*/
export function buildColumnSuggestions(
columns: { name: string }[],
): SqlSuggestion[] {
return columns.map((c) => ({
label: c.name,
insertText: c.name,
kind: "column" as const,
}));
}
// Cache of table columns, keyed by "schema.table".
const columnCache = new Map<string, GraphColumn[]>();
/**
* Synchronously read cached columns for a table, if the schema graph for its
* schema has already been fetched.
*/
export function getCachedColumns(
schema: string,
table: string,
): GraphColumn[] | undefined {
return columnCache.get(`${schema}.${table}`);
}
/**
* Fetch (and cache) the columns of a table via the schema-graph introspection
* command. Caches the whole schema in one round trip. Returns [] on error so
* autocomplete degrades gracefully.
*/
export async function getColumnsForTable(
connectionId: string | null,
schema: string | null,
table: string,
): Promise<GraphColumn[]> {
const resolvedSchema = schema ?? "public";
const key = `${resolvedSchema}.${table}`;
const cached = columnCache.get(key);
if (cached) return cached;
if (!connectionId) return [];
try {
const graph = await getSchemaGraph(connectionId, resolvedSchema);
for (const t of graph.tables) {
columnCache.set(`${resolvedSchema}.${t.name}`, t.columns);
}
} catch {
return [];
}
return columnCache.get(key) ?? [];
}
/**
* Build editor suggestions for the SQL language: reserved keywords plus the
* table names in the currently selected schema.
*/
export function buildSqlSuggestions(
tables: TableInfo[],
schema: string | null,
): SqlSuggestion[] {
const keywords: SqlSuggestion[] = SQL_KEYWORDS.map((kw) => ({
label: kw,
insertText: kw,
kind: "keyword",
}));
const tableNames = new Set(
tables
.filter((t) => !schema || t.schema === schema)
.map((t) => t.name),
);
const tableSuggestions: SqlSuggestion[] = [...tableNames].map((name) => ({
label: name,
insertText: name,
kind: "table",
}));
return [...keywords, ...tableSuggestions];
}