feat(editor): prefill new queries from focused tables

This commit is contained in:
Abeautifulsnow authored and GitHub committed 2026-07-13 16:15:09 +08:00
1 parent c2c4860a28
commit 9e1b0c478e
16 files changed
+421 -6

No files matched your search

+95 -1
View File
@@ -1,5 +1,6 @@
import { resolveDefaultDatabase } from "@/lib/database/defaultDatabase";
import type { ConnectionConfig, QueryTab, TreeNode } from "@/types/database";
import { qualifiedTableName } from "@/lib/table/tableSelectSql";
import type { ConnectionConfig, DatabaseType, QueryTab, TreeNode } from "@/types/database";
export interface NewQueryTarget {
connectionId: string;
@@ -59,3 +60,96 @@ function targetFromContext(context: Pick<QueryTab | TreeNode, "connectionId" | "
shouldRefreshDefaultDatabase: !context.database,
};
}
export interface NewQueryTable {
connectionId: string;
database: string;
schema?: string;
catalog?: string;
tableName: string;
}
export interface ResolveNewQueryTableInput {
activeTab?: Pick<QueryTab, "mode" | "connectionId" | "database" | "schema" | "tableMeta" | "structureTableName" | "title"> | null;
selectedTreeNode?: Pick<TreeNode, "type" | "connectionId" | "database" | "schema" | "catalog" | "tableName" | "label"> | null;
preferredSource?: NewQueryContextSource;
}
export interface ResolveNewQueryInitialSqlInput extends ResolveNewQueryTableInput {
prefillEnabled: boolean;
targetConnectionId: string;
targetDatabase: string;
databaseType?: DatabaseType;
}
// Database types whose "table" view does not use standard SQL `SELECT * FROM <table>`
// (e.g. Neo4j uses Cypher). The new-query prefill is skipped for these.
const NEW_QUERY_PREFILL_DISABLED_TYPES: ReadonlySet<DatabaseType | undefined> = new Set<DatabaseType | undefined>(["neo4j"]);
export function isNewQueryPrefillSupported(databaseType: DatabaseType | undefined): boolean {
return !NEW_QUERY_PREFILL_DISABLED_TYPES.has(databaseType);
}
function tableFromTab(tab: ResolveNewQueryTableInput["activeTab"]): NewQueryTable | null {
if (!tab?.connectionId) return null;
if (tab.mode === "data") {
// Require the loaded tableMeta: a data tab's title is schema/catalog-qualified
// (e.g. "public.users"), so using it as a bare table name while tableMeta is
// still loading or errored would yield an invalid double-qualified reference.
const meta = tab.tableMeta;
const tableName = meta?.tableName?.trim();
if (!tableName) return null;
return { connectionId: tab.connectionId, database: tab.database, schema: meta?.schema ?? tab.schema, catalog: meta?.catalog, tableName };
}
if (tab.mode === "structure") {
const tableName = (tab.structureTableName || "").trim();
if (!tableName) return null;
return { connectionId: tab.connectionId, database: tab.database, schema: tab.schema, tableName };
}
return null;
}
function tableFromNode(node: ResolveNewQueryTableInput["selectedTreeNode"]): NewQueryTable | null {
if (!node?.connectionId) return null;
if (node.type !== "table" && node.type !== "view" && node.type !== "materialized_view") return null;
const tableName = (node.tableName || node.label || "").trim();
if (!tableName) return null;
return { connectionId: node.connectionId, database: node.database || "", schema: node.schema, catalog: node.catalog, tableName };
}
/**
* Resolves the "focused table" for a new query, mirroring the primary/secondary
* selection of {@link resolveNewQueryTarget}: when `preferredSource` is `"sidebar"`
* the selected tree node wins, otherwise the active tab wins; the other is the
* fallback. Returns null when no table context is available.
*/
export function resolveNewQueryTable(input: ResolveNewQueryTableInput): NewQueryTable | null {
const tabFirst = input.preferredSource !== "sidebar";
const primary = tabFirst ? tableFromTab(input.activeTab) : tableFromNode(input.selectedTreeNode);
if (primary) return primary;
return tabFirst ? tableFromNode(input.selectedTreeNode) : tableFromTab(input.activeTab);
}
/**
* Builds a `SELECT * FROM <table>` statement for the new-query prefill, reusing
* the same per-dialect identifier quoting and schema/catalog qualification used
* by the table-data view.
*/
export function buildSelectAllSql(databaseType: DatabaseType | undefined, table: Pick<NewQueryTable, "schema" | "catalog" | "tableName">): string {
const ref = qualifiedTableName({ databaseType, schema: table.schema, catalog: table.catalog, tableName: table.tableName });
return `SELECT * FROM ${ref}`;
}
/**
* Resolves the optional initial SQL for a new query tab. A table from another
* connection or database is intentionally ignored because it cannot safely run
* in the execution context selected for the new tab.
*/
export function resolveNewQueryInitialSql(input: ResolveNewQueryInitialSqlInput): string | undefined {
if (!input.prefillEnabled || !isNewQueryPrefillSupported(input.databaseType)) return undefined;
const table = resolveNewQueryTable(input);
if (!table || table.connectionId !== input.targetConnectionId || table.database !== input.targetDatabase) return undefined;
return buildSelectAllSql(input.databaseType, table);
}