feat(sqlserver): support linked server browsing

This commit is contained in:
t8y2 committed 2026-06-18 13:17:44 +08:00
1 parent fc8ae4b69f
commit 9e75f62dbf
20 files changed
+617 -32

No files matched your search

+4
View File
@@ -102,6 +102,10 @@ export const syncSavedSqlDirectory = forward("syncSavedSqlDirectory");
// Schema
export const listDatabases = forward("listDatabases");
export const listSqlServerLinkedServers = forward("listSqlServerLinkedServers");
export const listSqlServerLinkedServerCatalogs = forward("listSqlServerLinkedServerCatalogs");
export const listSqlServerLinkedServerSchemas = forward("listSqlServerLinkedServerSchemas");
export const listSqlServerLinkedServerTables = forward("listSqlServerLinkedServerTables");
export const saveSchemaCache = forward("saveSchemaCache");
export const loadSchemaCache = forward("loadSchemaCache");
export const deleteSchemaCachePrefix = forward("deleteSchemaCachePrefix");
+17
View File
@@ -1,6 +1,7 @@
import type {
ConnectionConfig,
DatabaseInfo,
LinkedServerInfo,
TableInfo,
ObjectInfo,
ObjectSource,
@@ -392,6 +393,22 @@ export async function listDatabases(connectionId: string): Promise<DatabaseInfo[
return get(`/api/schema/databases?${qs({ connection_id: connectionId })}`);
}
export async function listSqlServerLinkedServers(connectionId: string): Promise<LinkedServerInfo[]> {
return get(`/api/schema/sqlserver/linked-servers?${qs({ connection_id: connectionId })}`);
}
export async function listSqlServerLinkedServerCatalogs(connectionId: string, server: string): Promise<DatabaseInfo[]> {
return get(`/api/schema/sqlserver/linked-server-catalogs?${qs({ connection_id: connectionId, server })}`);
}
export async function listSqlServerLinkedServerSchemas(connectionId: string, server: string, catalog: string): Promise<string[]> {
return get(`/api/schema/sqlserver/linked-server-schemas?${qs({ connection_id: connectionId, server, catalog })}`);
}
export async function listSqlServerLinkedServerTables(connectionId: string, server: string, catalog: string, schema: string, filter?: string, limit?: number, offset?: number): Promise<TableInfo[]> {
return get(`/api/schema/sqlserver/linked-server-tables?${qs({ connection_id: connectionId, server, catalog, schema, filter, limit, offset })}`);
}
export async function saveSchemaCache(cacheKey: string, payload: unknown): Promise<void> {
return post("/api/schema/cache", { cacheKey, payload });
}
@@ -0,0 +1,41 @@
import type { TreeNode } from "@/types/database";
const SQLSERVER_LINKED_SCHEMA_PREFIX = "__dbx_sqlserver_linked__:";
export interface SqlServerLinkedSchemaRef {
server: string;
catalog: string;
schema: string;
}
export function encodeSqlServerLinkedSchema(ref: SqlServerLinkedSchemaRef): string {
return `${SQLSERVER_LINKED_SCHEMA_PREFIX}${[ref.server, ref.catalog, ref.schema].map(encodeURIComponent).join("|")}`;
}
export function parseSqlServerLinkedSchema(schema: string | undefined): SqlServerLinkedSchemaRef | null {
if (!schema?.startsWith(SQLSERVER_LINKED_SCHEMA_PREFIX)) return null;
const parts = schema.slice(SQLSERVER_LINKED_SCHEMA_PREFIX.length).split("|").map(decodeURIComponent);
if (parts.length !== 3 || parts.some((part) => !part.trim())) return null;
return { server: parts[0], catalog: parts[1], schema: parts[2] };
}
export function isSqlServerLinkedNode(node: Pick<TreeNode, "linkedServer" | "linkedCatalog" | "linkedSchema" | "schema">): boolean {
return !!node.linkedServer || !!node.linkedCatalog || !!node.linkedSchema || !!parseSqlServerLinkedSchema(node.schema);
}
export function sqlServerLinkedTableSchema(node: Pick<TreeNode, "linkedServer" | "linkedCatalog" | "linkedSchema">): string | undefined {
if (!node.linkedServer || !node.linkedCatalog || !node.linkedSchema) return undefined;
return encodeSqlServerLinkedSchema({
server: node.linkedServer,
catalog: node.linkedCatalog,
schema: node.linkedSchema,
});
}
export function quoteSqlServerIdentifier(name: string): string {
return `[${name.replace(/\]/g, "]]")}]`;
}
export function sqlServerLinkedTableName(ref: SqlServerLinkedSchemaRef, tableName: string): string {
return [ref.server, ref.catalog, ref.schema, tableName].map(quoteSqlServerIdentifier).join(".");
}
+5
View File
@@ -1,6 +1,7 @@
import type { DatabaseType } from "../types/database.ts";
import { isSchemaAware, usesDatabaseObjectTreeMode } from "./databaseCapabilities.ts";
import * as api from "./api.ts";
import { parseSqlServerLinkedSchema, sqlServerLinkedTableName } from "./sqlServerLinkedServers.ts";
export interface BuildTableSelectSqlOptions {
databaseType?: DatabaseType;
@@ -42,6 +43,10 @@ export function qualifiedTableName(options: Pick<BuildTableSelectSqlOptions, "da
return quoteTableIdentifier(databaseType, tableName);
}
if (isSchemaAware(databaseType) && !usesDatabaseObjectTreeMode(databaseType) && schema) {
if (databaseType === "sqlserver") {
const linked = parseSqlServerLinkedSchema(schema);
if (linked) return sqlServerLinkedTableName(linked, tableName);
}
return `${quoteTableIdentifier(databaseType, schema)}.${quoteTableIdentifier(databaseType, tableName)}`;
}
return quoteTableIdentifier(databaseType, tableName);
+17
View File
@@ -3,6 +3,7 @@ import { listen, type UnlistenFn } from "@tauri-apps/api/event";
import type {
ConnectionConfig,
DatabaseInfo,
LinkedServerInfo,
TableInfo,
ObjectInfo,
ObjectSource,
@@ -474,6 +475,22 @@ export async function listDatabases(connectionId: string): Promise<DatabaseInfo[
return invoke("list_databases", { connectionId });
}
export async function listSqlServerLinkedServers(connectionId: string): Promise<LinkedServerInfo[]> {
return invoke("list_sqlserver_linked_servers", { connectionId });
}
export async function listSqlServerLinkedServerCatalogs(connectionId: string, server: string): Promise<DatabaseInfo[]> {
return invoke("list_sqlserver_linked_server_catalogs", { connectionId, server });
}
export async function listSqlServerLinkedServerSchemas(connectionId: string, server: string, catalog: string): Promise<string[]> {
return invoke("list_sqlserver_linked_server_schemas", { connectionId, server, catalog });
}
export async function listSqlServerLinkedServerTables(connectionId: string, server: string, catalog: string, schema: string, filter?: string, limit?: number, offset?: number): Promise<TableInfo[]> {
return invoke("list_sqlserver_linked_server_tables", { connectionId, server, catalog, schema, filter, limit, offset });
}
export async function saveSchemaCache(cacheKey: string, payload: unknown): Promise<void> {
return invoke("save_schema_cache", { cacheKey, payload });
}