fix(doris): preserve catalog in metadata views
This commit is contained in:
parent
8254cfd948
commit
a9e76dc130
|
|
@ -162,7 +162,7 @@ const cursorPos = ref(0);
|
|||
const formatSqlRequest = ref<{ id: number; tabId: string } | null>(null);
|
||||
const activeOutputView = ref<"result" | "summary" | "explain" | "chart">("result");
|
||||
const newQueryContextSource = ref<"tab" | "sidebar">("tab");
|
||||
const queryEditorDdlTarget = ref<{ connectionId: string; database: string; schema?: string; tableName: string; objectType?: ObjectSourceKind } | null>(null);
|
||||
const queryEditorDdlTarget = ref<{ connectionId: string; database: string; catalog?: string; schema?: string; tableName: string; objectType?: ObjectSourceKind } | null>(null);
|
||||
const queryEditorObjectSourceTarget = ref<{ connectionId: string; database: string; schema?: string; name: string; objectType: ObjectSourceKind; initialEditing: boolean } | null>(null);
|
||||
const showSaveSqlDialog = ref(false);
|
||||
const saveSqlName = ref("");
|
||||
|
|
@ -1250,11 +1250,13 @@ function tableTargetFromActiveTab(table: string | SqlObjectNavigationTarget) {
|
|||
const tab = activeTab.value;
|
||||
if (!tab) return null;
|
||||
const connectionId = tab.connectionId;
|
||||
const catalog = tab.tableMeta?.catalog || tab.catalog;
|
||||
if (typeof table !== "string") {
|
||||
// Structured targets already separate qualifiers; reparsing would corrupt quoted object names that contain dots.
|
||||
return {
|
||||
connectionId,
|
||||
database: table.database || tab.database,
|
||||
catalog,
|
||||
schema: table.schema || tab.schema,
|
||||
tableName: table.name,
|
||||
tableType: table.type ? sqlObjectNavigationTableType(table) : undefined,
|
||||
|
|
@ -1280,7 +1282,7 @@ function tableTargetFromActiveTab(table: string | SqlObjectNavigationTarget) {
|
|||
}
|
||||
}
|
||||
|
||||
return { connectionId, database, schema, tableName: rawTableName, tableType: undefined };
|
||||
return { connectionId, database, catalog, schema, tableName: rawTableName, tableType: undefined };
|
||||
}
|
||||
|
||||
async function onClickTable(table: SqlObjectNavigationTarget) {
|
||||
|
|
@ -1321,7 +1323,7 @@ function onEditTableStructure(table: SqlObjectNavigationTarget) {
|
|||
const target = tableTargetFromActiveTab(table);
|
||||
// Keep view-like objects out of the table editor even if a stale menu dispatches this event.
|
||||
if (!target || sqlObjectNavigationSourceKind(table)) return;
|
||||
queryStore.openTableStructure(target.connectionId, target.database, target.schema, target.tableName);
|
||||
queryStore.openTableStructure(target.connectionId, target.database, target.schema, target.tableName, undefined, undefined, target.catalog);
|
||||
}
|
||||
|
||||
async function onOpenObjectSource(table: SqlObjectNavigationTarget, initialEditing: boolean) {
|
||||
|
|
@ -2286,6 +2288,7 @@ onUnmounted(() => {
|
|||
v-model:open="showQueryEditorDdlDialog"
|
||||
:connection-id="queryEditorDdlTarget.connectionId"
|
||||
:database="queryEditorDdlTarget.database"
|
||||
:catalog="queryEditorDdlTarget.catalog"
|
||||
:schema="queryEditorDdlTarget.schema"
|
||||
:table-name="queryEditorDdlTarget.tableName"
|
||||
:object-type="queryEditorDdlTarget.objectType"
|
||||
|
|
|
|||
|
|
@ -6744,7 +6744,7 @@ function copyDdl() {
|
|||
|
||||
function openTableStructureEditor() {
|
||||
if (!props.connectionId || !props.database || !props.tableMeta?.tableName || !canOpenTableStructureEditor.value) return;
|
||||
queryStore.openTableStructure(props.connectionId, props.database, props.tableMeta.schema, props.tableMeta.tableName, activeTableInfoTab.value);
|
||||
queryStore.openTableStructure(props.connectionId, props.database, props.tableMeta.schema, props.tableMeta.tableName, activeTableInfoTab.value, undefined, props.tableMeta.catalog);
|
||||
}
|
||||
|
||||
function toggleDdlWrap() {
|
||||
|
|
|
|||
|
|
@ -1475,7 +1475,6 @@ defineExpose({ focusSearch, refreshData, refreshQueryEditorCompletionCache, hand
|
|||
:database-type="activeEffectiveDatabaseType"
|
||||
:connection-id="activeTab.connectionId"
|
||||
:database="activeTab.database"
|
||||
:catalog="activeTab.objectBrowser?.catalog"
|
||||
:execution-database="activeDataTabExecutionDatabase"
|
||||
:table-meta="activeDataTabTableMeta"
|
||||
:table-info-tab="activeTab.tableInfoTab"
|
||||
|
|
@ -1587,6 +1586,7 @@ defineExpose({ focusSearch, refreshData, refreshQueryEditorCompletionCache, hand
|
|||
:key="`${activeTab.id}-${activeTab.objectBrowser?.schema || ''}`"
|
||||
:connection="activeConnection"
|
||||
:database="activeTab.database"
|
||||
:catalog="activeTab.objectBrowser?.catalog"
|
||||
:schema="activeTab.objectBrowser?.schema"
|
||||
:viewport="activeTab.objectBrowser?.viewport"
|
||||
@open-table="emit('openObjectTable', $event)"
|
||||
|
|
@ -1603,6 +1603,7 @@ defineExpose({ focusSearch, refreshData, refreshQueryEditorCompletionCache, hand
|
|||
:key="activeTab.id"
|
||||
:connection-id="activeTab.connectionId"
|
||||
:database="activeTab.database"
|
||||
:catalog="activeTab.catalog"
|
||||
:schema="activeTab.schema"
|
||||
:table-name="activeTab.structureTableName || ''"
|
||||
:initial-tab="activeTab.structureInitialTab"
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ const props = withDefaults(
|
|||
open: boolean;
|
||||
connectionId: string;
|
||||
database: string;
|
||||
catalog?: string;
|
||||
schema?: string;
|
||||
tableName: string;
|
||||
objectType?: ObjectSourceKind;
|
||||
|
|
@ -60,7 +61,7 @@ watch(
|
|||
ddlLoading.value = true;
|
||||
try {
|
||||
const schema = props.schema || props.database;
|
||||
const ddl = await api.getTableDdl(props.connectionId, props.database, schema, props.tableName, props.objectType);
|
||||
const ddl = await api.getTableDdl(props.connectionId, props.database, schema, props.tableName, props.objectType, props.catalog);
|
||||
ddlContent.value = await formatSqlForDisplay(ddl, props.formatDialect ?? props.dialect, settingsStore.editorSettings.sqlFormatter);
|
||||
} catch (e: any) {
|
||||
ddlError.value = e?.message || String(e);
|
||||
|
|
|
|||
|
|
@ -1416,6 +1416,7 @@ defineExpose({ focusSearch, createNewGroup, collapseAllTreeNodes });
|
|||
v-model:open="sidebarDdlOpen"
|
||||
:connection-id="sidebarDdlTarget.connectionId!"
|
||||
:database="sidebarDdlTarget.database!"
|
||||
:catalog="sidebarDdlTarget.catalog"
|
||||
:schema="sidebarDdlTarget.schema"
|
||||
:table-name="sidebarDdlTarget.label"
|
||||
:object-type="tableDdlObjectTypeForSidebarNode(sidebarDdlTarget.type)"
|
||||
|
|
|
|||
|
|
@ -1802,9 +1802,9 @@ async function generateDdlTemplate() {
|
|||
const schema = node.schema || node.database;
|
||||
let ddl: string;
|
||||
if (node.type === "table") {
|
||||
ddl = await api.getTableDdl(node.connectionId, node.database, schema, node.label);
|
||||
ddl = await api.getTableDdl(node.connectionId, node.database, schema, node.label, undefined, node.catalog);
|
||||
} else if (node.type === "materialized_view") {
|
||||
ddl = await api.getTableDdl(node.connectionId, node.database, schema, node.label, "MATERIALIZED_VIEW");
|
||||
ddl = await api.getTableDdl(node.connectionId, node.database, schema, node.label, "MATERIALIZED_VIEW", node.catalog);
|
||||
} else {
|
||||
const result = await api.getObjectSource(node.connectionId, node.database, schema, node.label, "VIEW");
|
||||
ddl = await buildViewDdl({
|
||||
|
|
@ -3707,7 +3707,7 @@ async function exportStructure() {
|
|||
const parts: string[] = [];
|
||||
for (const target of targets) {
|
||||
await connectionStore.ensureConnected(target.connectionId);
|
||||
const ddl = await api.getTableDdl(target.connectionId, target.database, target.schema || target.database, target.label, tableDdlObjectTypeForNode(target.type));
|
||||
const ddl = await api.getTableDdl(target.connectionId, target.database, target.schema || target.database, target.label, tableDdlObjectTypeForNode(target.type), target.catalog);
|
||||
parts.push(ddl.trim());
|
||||
}
|
||||
structurePreviewSql.value = joinExportedDdls(parts);
|
||||
|
|
@ -4204,19 +4204,19 @@ function openStructureEditor() {
|
|||
const node = props.node;
|
||||
if (!node.connectionId || !node.database) return;
|
||||
if (node.type === "table") {
|
||||
queryStore.openTableStructure(node.connectionId, node.database, node.schema, node.label);
|
||||
queryStore.openTableStructure(node.connectionId, node.database, node.schema, node.label, undefined, undefined, node.catalog);
|
||||
return;
|
||||
}
|
||||
if (node.type === "column" && node.tableName) {
|
||||
const columnName = tableChildDropObjectName(node).trim();
|
||||
if (!columnName) return;
|
||||
queryStore.openTableStructure(node.connectionId, node.database, node.schema, node.tableName, "columns", { kind: "column", name: columnName });
|
||||
queryStore.openTableStructure(node.connectionId, node.database, node.schema, node.tableName, "columns", { kind: "column", name: columnName }, node.catalog);
|
||||
return;
|
||||
}
|
||||
if (node.type === "index" && node.tableName) {
|
||||
const indexName = tableChildDropObjectName(node).trim();
|
||||
if (!indexName) return;
|
||||
queryStore.openTableStructure(node.connectionId, node.database, node.schema, node.tableName, "indexes", { kind: "index", name: indexName });
|
||||
queryStore.openTableStructure(node.connectionId, node.database, node.schema, node.tableName, "indexes", { kind: "index", name: indexName }, node.catalog);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ const previewSqlText = computed(() => joinSqlStatementsForScript(pendingStatemen
|
|||
const props = defineProps<{
|
||||
connectionId: string;
|
||||
database: string;
|
||||
catalog?: string;
|
||||
schema?: string;
|
||||
tableName: string;
|
||||
initialTab?: TableInfoTab;
|
||||
|
|
@ -153,7 +154,7 @@ async function fetchDdl() {
|
|||
if (!props.connectionId || !props.database || !props.tableName || ddlFetched.value || !tableMetadataCapabilities.value.ddl) return;
|
||||
ddlLoading.value = true;
|
||||
try {
|
||||
const ddl = await api.getTableDdl(props.connectionId, props.database, metadataSchema.value, props.tableName);
|
||||
const ddl = await api.getTableDdl(props.connectionId, props.database, metadataSchema.value, props.tableName, undefined, props.catalog);
|
||||
ddlContent.value = await formatSqlForDisplay(ddl, sqlFormatDialectForDbType(databaseType.value), settingsStore.editorSettings.sqlFormatter);
|
||||
ddlFetched.value = true;
|
||||
} catch (e: any) {
|
||||
|
|
@ -958,6 +959,7 @@ async function hydrateRestoredDraftFromDatabase() {
|
|||
if (!needsColumnDraftMetadataHydration() || hydratingRestoredDraft) return;
|
||||
const connectionId = props.connectionId;
|
||||
const database = props.database;
|
||||
const catalog = props.catalog;
|
||||
const schema = metadataSchema.value;
|
||||
const tableName = props.tableName;
|
||||
if (!connectionId || !database || !tableName) return;
|
||||
|
|
@ -966,10 +968,10 @@ async function hydrateRestoredDraftFromDatabase() {
|
|||
let shouldRefreshPreview = false;
|
||||
try {
|
||||
await store.ensureConnected(connectionId);
|
||||
let nextColumns = await api.getColumns(connectionId, database, schema, tableName);
|
||||
let nextColumns = await api.getColumns(connectionId, database, schema, tableName, catalog);
|
||||
if (databaseType.value === "manticoresearch" && tableMetadataCapabilities.value.ddl) {
|
||||
try {
|
||||
const ddl = await api.getTableDdl(connectionId, database, schema, tableName);
|
||||
const ddl = await api.getTableDdl(connectionId, database, schema, tableName, undefined, catalog);
|
||||
ddlContent.value = await formatSqlForDisplay(ddl, sqlFormatDialectForDbType(databaseType.value), settingsStore.editorSettings.sqlFormatter);
|
||||
ddlFetched.value = true;
|
||||
nextColumns = applyManticoreDdlColumnExtras(nextColumns, ddl);
|
||||
|
|
@ -1220,12 +1222,12 @@ function setSecondaryMetadataLoading(scope: StructureRefreshScope, value: boolea
|
|||
if (scope.triggers && tableMetadataCapabilities.value.triggers) triggersLoading.value = value;
|
||||
}
|
||||
|
||||
async function fetchTableCommentValue(connectionId: string, database: string, schema: string, tableName: string): Promise<string | undefined> {
|
||||
async function fetchTableCommentValue(connectionId: string, database: string, schema: string, tableName: string, catalog?: string): Promise<string | undefined> {
|
||||
try {
|
||||
return (await api.getTableComment(connectionId, database, schema, tableName)) || "";
|
||||
return (await api.getTableComment(connectionId, database, schema, tableName, catalog)) || "";
|
||||
} catch {
|
||||
try {
|
||||
const tables = await api.listTables(connectionId, database, schema);
|
||||
const tables = await api.listTables(connectionId, database, schema, undefined, undefined, undefined, undefined, catalog);
|
||||
const table = tables.find((t) => t.name.toLowerCase() === tableName.toLowerCase() && t.table_type !== "VIEW");
|
||||
return table?.comment || "";
|
||||
} catch {
|
||||
|
|
@ -1237,6 +1239,7 @@ async function fetchTableCommentValue(connectionId: string, database: string, sc
|
|||
async function loadStructure(silent = false, scope: StructureRefreshScope = FULL_STRUCTURE_REFRESH_SCOPE, showErrors = true, options: { blockSecondaryMetadata?: boolean; preserveDraft?: boolean; damengLengthUnitsAfterSave?: ReadonlyMap<string, string> } = {}) {
|
||||
const connectionId = props.connectionId;
|
||||
const database = props.database;
|
||||
const catalog = props.catalog;
|
||||
const schema = metadataSchema.value;
|
||||
const tableName = props.tableName;
|
||||
if (!connectionId || !database || !tableName) return;
|
||||
|
|
@ -1249,17 +1252,17 @@ async function loadStructure(silent = false, scope: StructureRefreshScope = FULL
|
|||
try {
|
||||
await store.ensureConnected(connectionId);
|
||||
|
||||
const columnsPromise = scope.columns ? api.getColumns(connectionId, database, schema, tableName) : Promise.resolve(undefined);
|
||||
const indexesPromise = scope.indexes ? (tableMetadataCapabilities.value.indexes ? api.listIndexes(connectionId, database, schema, tableName).catch(() => []) : Promise.resolve([])) : Promise.resolve(undefined);
|
||||
const foreignKeysPromise = scope.foreignKeys ? (tableMetadataCapabilities.value.foreignKeys ? api.listForeignKeys(connectionId, database, schema, tableName).catch(() => []) : Promise.resolve([])) : Promise.resolve(undefined);
|
||||
const triggersPromise = scope.triggers ? (tableMetadataCapabilities.value.triggers ? api.listTriggers(connectionId, database, schema, tableName).catch(() => []) : Promise.resolve([])) : Promise.resolve(undefined);
|
||||
const tableCommentPromise = scope.tableComment && structureCapabilities.value.comment ? fetchTableCommentValue(connectionId, database, schema, tableName) : Promise.resolve(undefined);
|
||||
const columnsPromise = scope.columns ? api.getColumns(connectionId, database, schema, tableName, catalog) : Promise.resolve(undefined);
|
||||
const indexesPromise = scope.indexes ? (tableMetadataCapabilities.value.indexes ? api.listIndexes(connectionId, database, schema, tableName, catalog).catch(() => []) : Promise.resolve([])) : Promise.resolve(undefined);
|
||||
const foreignKeysPromise = scope.foreignKeys ? (tableMetadataCapabilities.value.foreignKeys ? api.listForeignKeys(connectionId, database, schema, tableName, catalog).catch(() => []) : Promise.resolve([])) : Promise.resolve(undefined);
|
||||
const triggersPromise = scope.triggers ? (tableMetadataCapabilities.value.triggers ? api.listTriggers(connectionId, database, schema, tableName, catalog).catch(() => []) : Promise.resolve([])) : Promise.resolve(undefined);
|
||||
const tableCommentPromise = scope.tableComment && structureCapabilities.value.comment ? fetchTableCommentValue(connectionId, database, schema, tableName, catalog) : Promise.resolve(undefined);
|
||||
|
||||
let nextColumns = await columnsPromise;
|
||||
if (nextColumns) {
|
||||
if (databaseType.value === "manticoresearch" && tableMetadataCapabilities.value.ddl) {
|
||||
try {
|
||||
const ddl = await api.getTableDdl(connectionId, database, schema, tableName);
|
||||
const ddl = await api.getTableDdl(connectionId, database, schema, tableName, undefined, catalog);
|
||||
ddlContent.value = await formatSqlForDisplay(ddl, sqlFormatDialectForDbType(databaseType.value), settingsStore.editorSettings.sqlFormatter);
|
||||
ddlFetched.value = true;
|
||||
nextColumns = applyManticoreDdlColumnExtras(nextColumns, ddl);
|
||||
|
|
|
|||
28
apps/desktop/src/lib/__tests__/table/contentAreaObjectBrowserCatalog.spec.ts
vendored
Normal file
28
apps/desktop/src/lib/__tests__/table/contentAreaObjectBrowserCatalog.spec.ts
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const contentAreaSource = readFileSync(new URL("../../../components/layout/ContentArea.vue", import.meta.url), "utf8");
|
||||
const connectionTreeSource = readFileSync(new URL("../../../components/sidebar/ConnectionTree.vue", import.meta.url), "utf8");
|
||||
const ddlViewDialogSource = readFileSync(new URL("../../../components/objects/DdlViewDialog.vue", import.meta.url), "utf8");
|
||||
|
||||
function openingTag(source: string, componentName: string): string {
|
||||
return source.match(new RegExp(`<${componentName}\\b[\\s\\S]*?>`))?.[0] ?? "";
|
||||
}
|
||||
|
||||
describe("ContentArea external catalog wiring", () => {
|
||||
it("passes the object browser catalog to ObjectBrowser", () => {
|
||||
expect(openingTag(contentAreaSource, "ObjectBrowser")).toContain(':catalog="activeTab.objectBrowser?.catalog"');
|
||||
});
|
||||
|
||||
it("does not attach object browser state to DataGrid", () => {
|
||||
expect(openingTag(contentAreaSource, "DataGrid")).not.toContain("activeTab.objectBrowser?.catalog");
|
||||
});
|
||||
|
||||
it("passes the sidebar catalog to the DDL dialog", () => {
|
||||
expect(openingTag(connectionTreeSource, "SidebarDdlViewDialog")).toContain(':catalog="sidebarDdlTarget.catalog"');
|
||||
});
|
||||
|
||||
it("forwards the DDL dialog catalog to the metadata API", () => {
|
||||
expect(ddlViewDialogSource).toMatch(/api\.getTableDdl\([\s\S]*?props\.objectType, props\.catalog\)/);
|
||||
});
|
||||
});
|
||||
|
|
@ -69,6 +69,19 @@ describe("queryStore database open state", () => {
|
|||
expect(store.openObjectBrowser("doris-1", "default", undefined, "iceberg_catalog")).toBe(icebergTabId);
|
||||
});
|
||||
|
||||
it("keeps external catalog structure editors isolated", async () => {
|
||||
const { useQueryStore } = await import("@/stores/queryStore");
|
||||
const store = useQueryStore();
|
||||
|
||||
const icebergTabId = store.openTableStructure("doris-1", "sales", undefined, "orders", undefined, undefined, "iceberg_catalog");
|
||||
const hiveTabId = store.openTableStructure("doris-1", "sales", undefined, "orders", undefined, undefined, "hive_catalog");
|
||||
|
||||
expect(hiveTabId).not.toBe(icebergTabId);
|
||||
expect(store.tabs.find((tab) => tab.id === icebergTabId)?.catalog).toBe("iceberg_catalog");
|
||||
expect(store.tabs.find((tab) => tab.id === hiveTabId)?.catalog).toBe("hive_catalog");
|
||||
expect(store.openTableStructure("doris-1", "sales", undefined, "orders", undefined, undefined, "iceberg_catalog")).toBe(icebergTabId);
|
||||
});
|
||||
|
||||
it("closes data and structure tabs for a dropped table object", async () => {
|
||||
const { useQueryStore } = await import("@/stores/queryStore");
|
||||
const store = useQueryStore();
|
||||
|
|
|
|||
|
|
@ -1344,7 +1344,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
function openTableStructure(connectionId: string, database: string, schema?: string, tableName?: string, initialTab?: TableInfoTab, initialTarget?: TableStructureEditorTarget, catalog?: string) {
|
||||
const resolvedTableName = tableName || "";
|
||||
if (resolvedTableName) {
|
||||
const existing = tabs.value.find((tab) => tab.mode === "structure" && tab.connectionId === connectionId && tab.database === database && (tab.structureTableName || "") === resolvedTableName);
|
||||
const existing = tabs.value.find((tab) => tab.mode === "structure" && tab.connectionId === connectionId && tab.database === database && (tab.catalog || "") === (catalog || "") && (tab.structureTableName || "") === resolvedTableName);
|
||||
if (existing) {
|
||||
applyTableStructureInitialTab(existing, initialTab, initialTarget);
|
||||
switchTab(existing.id);
|
||||
|
|
|
|||
Loading…
Reference in New Issue