feat(editor): prefill new queries from focused tables
This commit is contained in:
1 parent
c2c4860a28
commit
9e1b0c478e
16 files changed
+421
-6
No files matched your search
@@ -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);
|
||||
}
|
||||
Reference in new issue
Block a user