fix(mysql): use database-specific catalog switching
This commit is contained in:
1 parent
9571519179
commit
2152f8cde8
31 files changed
+818
-182
No files matched your search
@@ -1525,9 +1525,14 @@ function changeActiveDatabase(database: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function changeActiveCatalog(catalog: string | undefined, database: string) {
|
||||
const tab = activeTab.value;
|
||||
if (tab) queryStore.updateCatalog(tab.id, catalog, database);
|
||||
}
|
||||
|
||||
async function setActiveDatabaseAsDefault() {
|
||||
const tab = activeTab.value;
|
||||
if (!tab || !tab.connectionId || !tab.database) return;
|
||||
if (!tab || !tab.connectionId || !tab.database || tab.catalog) return;
|
||||
await connectionStore.setDefaultDatabase(tab.connectionId, tab.database);
|
||||
}
|
||||
|
||||
@@ -1557,9 +1562,11 @@ function ensureQueryTab(): string {
|
||||
const tab = activeTab.value;
|
||||
if (tab && tab.mode === "query") return tab.id;
|
||||
const connId = connectionStore.activeConnectionId || connectionStore.connections[0]?.id || "";
|
||||
const db = tab?.connectionId === connId ? tab.database : connectionStore.getConfig(connId)?.database || "";
|
||||
const schema = tab?.connectionId === connId ? tab.schema : undefined;
|
||||
return queryStore.createTab(connId, db, undefined, "query", schema);
|
||||
const sameConnectionTab = tab?.connectionId === connId ? tab : undefined;
|
||||
const db = sameConnectionTab?.database || connectionStore.getConfig(connId)?.database || "";
|
||||
const schema = sameConnectionTab?.schema ?? sameConnectionTab?.objectBrowser?.schema ?? sameConnectionTab?.tableMeta?.schema;
|
||||
const catalog = sameConnectionTab?.catalog ?? sameConnectionTab?.objectBrowser?.catalog ?? sameConnectionTab?.tableMeta?.catalog;
|
||||
return queryStore.createTab(connId, db, undefined, "query", schema, undefined, catalog);
|
||||
}
|
||||
|
||||
function routeAiRedisCommand(command: string, execute: boolean): boolean {
|
||||
@@ -2263,6 +2270,7 @@ onUnmounted(() => {
|
||||
@paste-sql-in-condition="pasteClipboardAsSqlInCondition"
|
||||
@change-connection="changeActiveConnection"
|
||||
@change-database="changeActiveDatabase"
|
||||
@change-catalog="changeActiveCatalog"
|
||||
@change-schema="changeActiveSchema"
|
||||
@set-default-database="setActiveDatabaseAsDefault"
|
||||
@clear-default-database="clearActiveDefaultDatabase"
|
||||
|
||||
@@ -9,13 +9,14 @@ import TruncatedTextTooltip from "@/components/ui/TruncatedTextTooltip.vue";
|
||||
import DatabaseIcon from "@/components/icons/DatabaseIcon.vue";
|
||||
import ProductionContextBadge from "@/components/common/ProductionContextBadge.vue";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { useDatabaseOptions } from "@/composables/useDatabaseOptions";
|
||||
import { catalogDatabaseOptionsKey, databaseAfterCatalogChange, normalizedQueryTabCatalog, queryCatalogSelectorVisible, selectedQueryCatalogName, useDatabaseOptions } from "@/composables/useDatabaseOptions";
|
||||
import { useSchemaOptions } from "@/composables/useSchemaOptions";
|
||||
import { connectionIconType } from "@/lib/connection/connectionPresentation";
|
||||
import { formatDatabaseLabel, isDefaultDatabase } from "@/lib/database/defaultDatabase";
|
||||
import { connectionDisplayName } from "@/lib/tabs/tabPresentation";
|
||||
import { useConnectionGroupLabel } from "@/composables/useConnectionGroupLabel";
|
||||
import { isSingleDatabase, supportsClearableQuerySchema, supportsSqlInListPaste, supportsTransaction as supportsTransactionFeature } from "@/lib/database/databaseCapabilities";
|
||||
import { connectionIsDorisFamilyCatalogCapable } from "@/lib/database/databaseFeatureSupport";
|
||||
import { hexToRgba } from "@/lib/common/color";
|
||||
import { productionContextForDatabase } from "@/lib/database/productionSafety";
|
||||
import type { QueryTab, ConnectionConfig } from "@/types/database";
|
||||
@@ -46,6 +47,7 @@ const emit = defineEmits<{
|
||||
importResultArchive: [];
|
||||
pasteSqlInCondition: [];
|
||||
changeConnection: [connectionId: string];
|
||||
changeCatalog: [catalog: string | undefined, database: string];
|
||||
changeDatabase: [database: string];
|
||||
changeSchema: [schema: string | undefined];
|
||||
setDefaultDatabase: [];
|
||||
@@ -59,13 +61,30 @@ const emit = defineEmits<{
|
||||
|
||||
const { t } = useI18n();
|
||||
const connectionStore = useConnectionStore();
|
||||
const { databaseOptions, loadingDatabaseOptions, loadDatabaseOptions } = useDatabaseOptions();
|
||||
const { databaseOptions, loadingDatabaseOptions, loadDatabaseOptions, catalogOptions, loadingCatalogOptions, loadCatalogOptions, catalogDatabaseOptions, loadingCatalogDatabaseOptions, loadCatalogDatabaseOptions } = useDatabaseOptions();
|
||||
const { loadSchemaOptions, getSchemaOptionsForDb, isLoadingSchemas, isSchemaAware } = useSchemaOptions();
|
||||
|
||||
const activeCatalogs = computed(() => {
|
||||
const connection = props.activeConnection;
|
||||
return connection ? (catalogOptions.value[connection.id] ?? []) : [];
|
||||
});
|
||||
const activeCatalogNames = computed(() => activeCatalogs.value.map((catalog) => catalog.name));
|
||||
const showCatalogSelector = computed(() => connectionIsDorisFamilyCatalogCapable(props.activeConnection) && queryCatalogSelectorVisible(activeCatalogs.value));
|
||||
const activeCatalogValue = computed(() => selectedQueryCatalogName(activeCatalogs.value, props.activeTab.catalog));
|
||||
const activeCatalogDatabaseKey = computed(() => (props.activeConnection && props.activeTab.catalog ? catalogDatabaseOptionsKey(props.activeConnection.id, props.activeTab.catalog) : ""));
|
||||
const activeDatabaseOptions = computed(() => {
|
||||
const connection = props.activeConnection;
|
||||
return connection ? (databaseOptions.value[connection.id] ?? []) : [];
|
||||
if (!connection) return [];
|
||||
if (props.activeTab.catalog) return catalogDatabaseOptions.value[activeCatalogDatabaseKey.value] ?? [];
|
||||
return databaseOptions.value[connection.id] ?? [];
|
||||
});
|
||||
const loadingActiveDatabaseOptions = computed(() => {
|
||||
const connection = props.activeConnection;
|
||||
if (!connection) return false;
|
||||
if (props.activeTab.catalog) return loadingCatalogDatabaseOptions.value[activeCatalogDatabaseKey.value] ?? false;
|
||||
return loadingDatabaseOptions.value[connection.id] ?? false;
|
||||
});
|
||||
const switchingCatalog = ref(false);
|
||||
|
||||
const connectionOptionIds = computed(() => connectionStore.connections.map((connection) => connection.id));
|
||||
const { connectionGroupLabel } = useConnectionGroupLabel();
|
||||
@@ -135,6 +154,18 @@ watchEffect(() => {
|
||||
loadSchemaOptions(connection.id, schemaDatabaseKey.value).catch(() => {});
|
||||
}
|
||||
});
|
||||
watchEffect(() => {
|
||||
const connection = props.activeConnection;
|
||||
if (!connection || !connectionIsDorisFamilyCatalogCapable(connection)) return;
|
||||
void loadCatalogOptions(connection.id).catch(() => {});
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
const connection = props.activeConnection;
|
||||
const catalog = props.activeTab.catalog;
|
||||
if (!connection || !catalog) return;
|
||||
void loadCatalogDatabaseOptions(connection.id, catalog).catch(() => {});
|
||||
});
|
||||
|
||||
const isActiveDatabaseDefault = computed(() => isDefaultDatabase(props.activeConnection, activeDatabaseValue.value));
|
||||
const toolbarStyle = computed(() => {
|
||||
@@ -161,6 +192,18 @@ function databaseOptionIsProduction(database: string): boolean {
|
||||
if (!database || props.activeConnection?.is_production) return false;
|
||||
return productionContextForDatabase(props.activeConnection, database).reason === "database";
|
||||
}
|
||||
async function changeCatalog(selectedCatalog: string) {
|
||||
const connection = props.activeConnection;
|
||||
if (!connection) return;
|
||||
switchingCatalog.value = true;
|
||||
try {
|
||||
const catalog = normalizedQueryTabCatalog(activeCatalogs.value, selectedCatalog);
|
||||
const databases = catalog ? await loadCatalogDatabaseOptions(connection.id, selectedCatalog) : await loadDatabaseOptions(connection.id).then(() => databaseOptions.value[connection.id] ?? []);
|
||||
emit("changeCatalog", catalog, databaseAfterCatalogChange(props.activeTab.database, databases));
|
||||
} finally {
|
||||
switchingCatalog.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -364,6 +407,31 @@ function databaseOptionIsProduction(database: string): boolean {
|
||||
</template>
|
||||
</SearchableSelect>
|
||||
</div>
|
||||
<div v-if="showCatalogSelector" class="flex items-center gap-1">
|
||||
<SearchableSelect
|
||||
:model-value="activeCatalogValue"
|
||||
:options="activeCatalogNames"
|
||||
:placeholder="t('editor.selectCatalog')"
|
||||
:search-placeholder="t('editor.searchCatalog')"
|
||||
:empty-text="t('grid.noSearchResults')"
|
||||
:loading-text="t('common.loading')"
|
||||
:loading="loadingCatalogOptions[activeConnection?.id || ''] || switchingCatalog"
|
||||
trigger-variant="ghost"
|
||||
trigger-class="gap-1.5"
|
||||
trigger-icon-class="h-3 w-3"
|
||||
@update:model-value="changeCatalog"
|
||||
@update:open="
|
||||
(open: boolean) => {
|
||||
if (open && activeConnection) loadCatalogOptions(activeConnection.id).catch(() => {});
|
||||
}
|
||||
"
|
||||
>
|
||||
<template #trigger-label="{ label, loading }">
|
||||
<Layers class="h-3.5 w-3.5 shrink-0" />
|
||||
<span class="truncate">{{ loading ? t("common.loading") : label }}</span>
|
||||
</template>
|
||||
</SearchableSelect>
|
||||
</div>
|
||||
<div
|
||||
v-if="activeConnection?.db_type !== 'elasticsearch' && activeConnection?.db_type !== 'qdrant' && activeConnection?.db_type !== 'milvus' && activeConnection?.db_type !== 'weaviate' && activeConnection?.db_type !== 'chromadb' && activeConnection?.db_type !== 'zookeeper' && !isSingleDb"
|
||||
class="flex items-center gap-1"
|
||||
@@ -376,7 +444,7 @@ function databaseOptionIsProduction(database: string): boolean {
|
||||
:search-placeholder="t('editor.searchDatabase')"
|
||||
:empty-text="t('grid.noSearchResults')"
|
||||
:loading-text="t('common.loading')"
|
||||
:loading="loadingDatabaseOptions[activeConnection?.id || '']"
|
||||
:loading="loadingActiveDatabaseOptions"
|
||||
:display-name="databaseDisplayName"
|
||||
trigger-variant="ghost"
|
||||
trigger-class="gap-1.5"
|
||||
@@ -384,7 +452,9 @@ function databaseOptionIsProduction(database: string): boolean {
|
||||
@update:model-value="(database) => emit('changeDatabase', database)"
|
||||
@update:open="
|
||||
(open: boolean) => {
|
||||
if (open && activeConnection) loadDatabaseOptions(activeConnection.id).catch(() => {});
|
||||
if (!open || !activeConnection) return;
|
||||
if (activeTab.catalog) loadCatalogDatabaseOptions(activeConnection.id, activeTab.catalog).catch(() => {});
|
||||
else loadDatabaseOptions(activeConnection.id).catch(() => {});
|
||||
}
|
||||
"
|
||||
>
|
||||
@@ -408,7 +478,7 @@ function databaseOptionIsProduction(database: string): boolean {
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{{ t("editor.clearDatabase") }}</TooltipContent>
|
||||
</Tooltip>
|
||||
<Button v-if="activeDatabaseValue" variant="ghost" size="sm" class="h-6 px-2 text-[11px]" @click="isActiveDatabaseDefault ? emit('clearDefaultDatabase') : emit('setDefaultDatabase')">
|
||||
<Button v-if="activeDatabaseValue && !activeTab.catalog" variant="ghost" size="sm" class="h-6 px-2 text-[11px]" @click="isActiveDatabaseDefault ? emit('clearDefaultDatabase') : emit('setDefaultDatabase')">
|
||||
<Check v-if="isActiveDatabaseDefault" class="h-3 w-3" />
|
||||
{{ isActiveDatabaseDefault ? t("editor.defaultDatabase") : t("editor.setDefaultDatabase") }}
|
||||
</Button>
|
||||
|
||||
@@ -1142,13 +1142,16 @@ async function openSource(row: ObjectBrowserRow) {
|
||||
}
|
||||
|
||||
async function openNewQuery(row: ObjectBrowserRow) {
|
||||
const tabId = queryStore.createTab(props.connection.id, props.database, row.name);
|
||||
const schema = row.schema || selectedSchema.value;
|
||||
const tabId = queryStore.createTab(props.connection.id, props.database, row.name, "query", schema, undefined, props.catalog);
|
||||
queryStore.updateSql(
|
||||
tabId,
|
||||
await buildTableSelectSql({
|
||||
databaseType: effectiveDatabaseType.value,
|
||||
identifierQuote: connectionStore.connectionIdentifierQuote?.(props.connection.id),
|
||||
schema: row.schema || selectedSchema.value,
|
||||
catalog: props.catalog,
|
||||
database: props.database,
|
||||
schema,
|
||||
tableName: row.name,
|
||||
limit: 100,
|
||||
}),
|
||||
@@ -1165,7 +1168,7 @@ function openProcedureExecutionSql(sql: string) {
|
||||
const row = procedureExecutionTarget.value;
|
||||
if (!row || !sql) return;
|
||||
const schema = row.schema || selectedSchema.value;
|
||||
const tabId = queryStore.createTab(props.connection.id, props.database, `Execute - ${row.name}`, "query", schema);
|
||||
const tabId = queryStore.createTab(props.connection.id, props.database, `Execute - ${row.name}`, "query", schema, undefined, props.catalog);
|
||||
queryStore.updateSql(tabId, sql);
|
||||
}
|
||||
|
||||
@@ -1173,7 +1176,7 @@ async function executeProcedureSql(sql: string) {
|
||||
const row = procedureExecutionTarget.value;
|
||||
if (!row || !sql) return;
|
||||
const schema = row.schema || selectedSchema.value;
|
||||
const tabId = queryStore.createTab(props.connection.id, props.database, `Execute - ${row.name}`, "query", schema);
|
||||
const tabId = queryStore.createTab(props.connection.id, props.database, `Execute - ${row.name}`, "query", schema, undefined, props.catalog);
|
||||
queryStore.updateSql(tabId, sql);
|
||||
await queryStore.executeTabSql(tabId, sql);
|
||||
}
|
||||
|
||||
@@ -1316,14 +1316,14 @@ function clearSidebarTableNameFilters() {
|
||||
function openSidebarProcedureSql(sql: string) {
|
||||
const target = sidebarProcedureTarget.value;
|
||||
if (!target?.connectionId || !target.database || !sql) return;
|
||||
const tabId = queryStore.createTab(target.connectionId, target.database, `Execute - ${target.label}`, "query", target.schema);
|
||||
const tabId = queryStore.createTab(target.connectionId, target.database, `Execute - ${target.label}`, "query", target.schema, undefined, target.catalog);
|
||||
queryStore.updateSql(tabId, sql);
|
||||
}
|
||||
|
||||
async function executeSidebarProcedureSql(sql: string) {
|
||||
const target = sidebarProcedureTarget.value;
|
||||
if (!target?.connectionId || !target.database || !sql) return;
|
||||
const tabId = queryStore.createTab(target.connectionId, target.database, `Execute - ${target.label}`, "query", target.schema);
|
||||
const tabId = queryStore.createTab(target.connectionId, target.database, `Execute - ${target.label}`, "query", target.schema, undefined, target.catalog);
|
||||
queryStore.updateSql(tabId, sql);
|
||||
await queryStore.executeTabSql(tabId, sql);
|
||||
}
|
||||
|
||||
@@ -1186,7 +1186,7 @@ async function loadTemplateContext(allowView = false) {
|
||||
let columns: ColumnInfo[] = [];
|
||||
try {
|
||||
const querySchema = connectionObjectTreeQuerySchema(config, node.database, tableSchema);
|
||||
columns = await api.getColumns(node.connectionId, node.database, querySchema, node.label);
|
||||
columns = await api.getColumns(node.connectionId, node.database, querySchema, node.label, node.catalog);
|
||||
} catch (e) {
|
||||
console.warn("[DBX][tableSqlTemplate:getColumns:error]", e);
|
||||
}
|
||||
@@ -1195,7 +1195,7 @@ async function loadTemplateContext(allowView = false) {
|
||||
if (dbType === "tdengine") {
|
||||
try {
|
||||
const querySchema = connectionObjectTreeQuerySchema(config, node.database, tableSchema);
|
||||
const tables = await api.listTables(node.connectionId, node.database, querySchema, node.label, 200);
|
||||
const tables = await api.listTables(node.connectionId, node.database, querySchema, node.label, 200, undefined, undefined, node.catalog);
|
||||
const matched = tables.find((table) => table.name.toLowerCase() === node.label.toLowerCase());
|
||||
if (matched?.table_type) tableType = matched.table_type;
|
||||
} catch (e) {
|
||||
@@ -1206,8 +1206,8 @@ async function loadTemplateContext(allowView = false) {
|
||||
return { node, dbType, tableSchema, columns, tableType };
|
||||
}
|
||||
|
||||
function openSqlTemplateTab(connectionId: string, database: string, schema: string | undefined, sql: string, title?: string) {
|
||||
const tabId = queryStore.createTab(connectionId, database, title, "query", schema);
|
||||
function openSqlTemplateTab(connectionId: string, database: string, schema: string | undefined, catalog: string | undefined, sql: string, title?: string) {
|
||||
const tabId = queryStore.createTab(connectionId, database, title, "query", schema, undefined, catalog);
|
||||
queryStore.updateSql(tabId, sql);
|
||||
}
|
||||
|
||||
@@ -1217,11 +1217,13 @@ async function newSelectTemplate() {
|
||||
if (!context) return;
|
||||
const sql = buildTableSelectTemplate({
|
||||
databaseType: context.dbType,
|
||||
catalog: context.node.catalog,
|
||||
database: context.node.database,
|
||||
schema: context.tableSchema,
|
||||
tableName: context.node.label,
|
||||
columns: context.columns,
|
||||
});
|
||||
openSqlTemplateTab(context.node.connectionId!, context.node.database!, context.node.schema, sql);
|
||||
openSqlTemplateTab(context.node.connectionId!, context.node.database!, context.node.schema, context.node.catalog, sql);
|
||||
} catch (e: any) {
|
||||
toast(t("connection.connectFailed", { message: translateBackendError(t, e?.message || String(e)) }), 5000);
|
||||
}
|
||||
@@ -1233,12 +1235,14 @@ async function newInsertTemplate() {
|
||||
if (!context) return;
|
||||
const sql = buildTableInsertTemplate({
|
||||
databaseType: context.dbType,
|
||||
catalog: context.node.catalog,
|
||||
database: context.node.database,
|
||||
schema: context.tableSchema,
|
||||
tableName: context.node.label,
|
||||
columns: context.columns,
|
||||
tableType: context.tableType,
|
||||
});
|
||||
openSqlTemplateTab(context.node.connectionId!, context.node.database!, context.node.schema, sql);
|
||||
openSqlTemplateTab(context.node.connectionId!, context.node.database!, context.node.schema, context.node.catalog, sql);
|
||||
} catch (e: any) {
|
||||
toast(t("connection.connectFailed", { message: translateBackendError(t, e?.message || String(e)) }), 5000);
|
||||
}
|
||||
@@ -1250,11 +1254,13 @@ async function newUpdateTemplate() {
|
||||
if (!context) return;
|
||||
const sql = buildTableUpdateTemplate({
|
||||
databaseType: context.dbType,
|
||||
catalog: context.node.catalog,
|
||||
database: context.node.database,
|
||||
schema: context.tableSchema,
|
||||
tableName: context.node.label,
|
||||
columns: context.columns,
|
||||
});
|
||||
openSqlTemplateTab(context.node.connectionId!, context.node.database!, context.node.schema, sql);
|
||||
openSqlTemplateTab(context.node.connectionId!, context.node.database!, context.node.schema, context.node.catalog, sql);
|
||||
} catch (e: any) {
|
||||
toast(t("connection.connectFailed", { message: translateBackendError(t, e?.message || String(e)) }), 5000);
|
||||
}
|
||||
@@ -1266,11 +1272,13 @@ async function newDeleteTemplate() {
|
||||
if (!context) return;
|
||||
const sql = buildTableDeleteTemplate({
|
||||
databaseType: context.dbType,
|
||||
catalog: context.node.catalog,
|
||||
database: context.node.database,
|
||||
schema: context.tableSchema,
|
||||
tableName: context.node.label,
|
||||
columns: context.columns,
|
||||
});
|
||||
openSqlTemplateTab(context.node.connectionId!, context.node.database!, context.node.schema, sql);
|
||||
openSqlTemplateTab(context.node.connectionId!, context.node.database!, context.node.schema, context.node.catalog, sql);
|
||||
} catch (e: any) {
|
||||
toast(t("connection.connectFailed", { message: translateBackendError(t, e?.message || String(e)) }), 5000);
|
||||
}
|
||||
@@ -1299,7 +1307,7 @@ async function generateDdlTemplate() {
|
||||
});
|
||||
}
|
||||
const formatted = await formatSqlForDisplay(ddl, sqlFormatDialectForDbType(currentDatabaseType()), settingsStore.editorSettings.sqlFormatter);
|
||||
openSqlTemplateTab(node.connectionId, node.database, node.schema, formatted, `DDL - ${node.label}`);
|
||||
openSqlTemplateTab(node.connectionId, node.database, node.schema, node.catalog, formatted, `DDL - ${node.label}`);
|
||||
} catch (e: any) {
|
||||
toast(e?.message || String(e), 5000);
|
||||
}
|
||||
@@ -3042,7 +3050,7 @@ function createView() {
|
||||
const viewName = "new_view";
|
||||
const effectiveDbType = effectiveDatabaseTypeForConnection(connectionStore.getConfig(node.connectionId));
|
||||
const viewSqlName = effectiveDbType === "informix" || !node.schema ? viewName : `${node.schema}.${viewName}`;
|
||||
const tabId = queryStore.createTab(node.connectionId, node.database, t("contextMenu.createView"), "query", node.schema);
|
||||
const tabId = queryStore.createTab(node.connectionId, node.database, t("contextMenu.createView"), "query", node.schema, undefined, node.catalog);
|
||||
queryStore.updateSql(tabId, `CREATE VIEW ${viewSqlName} AS\nSELECT\n *\nFROM table_name;\n`);
|
||||
queryStore.setObjectSource(tabId, {
|
||||
schema: node.schema,
|
||||
@@ -3077,7 +3085,7 @@ function createMysqlObjectTemplate() {
|
||||
const template = mysqlObjectTemplateForGroup(connectionStore.getConfig(node.connectionId), node);
|
||||
if (!template) return;
|
||||
connectionStore.activeConnectionId = node.connectionId;
|
||||
const tabId = queryStore.createTab(node.connectionId, node.database, t(template.titleKey), "query", node.schema);
|
||||
const tabId = queryStore.createTab(node.connectionId, node.database, t(template.titleKey), "query", node.schema, undefined, node.catalog);
|
||||
queryStore.updateSql(tabId, template.sql);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,30 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { databaseOptionsForConnection, fetchNamespaceOptionsForConnection, fetchSqlFileTargetOptions, namespaceOptionsAreSchemas, useDatabaseOptions } from "@/composables/useDatabaseOptions";
|
||||
import {
|
||||
databaseAfterCatalogChange,
|
||||
databaseOptionsForConnection,
|
||||
fetchNamespaceOptionsForConnection,
|
||||
fetchSqlFileTargetOptions,
|
||||
namespaceOptionsAreSchemas,
|
||||
normalizedQueryTabCatalog,
|
||||
queryCatalogSelectorVisible,
|
||||
selectedQueryCatalogName,
|
||||
useDatabaseOptions,
|
||||
} from "@/composables/useDatabaseOptions";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
ensureConnected: vi.fn(),
|
||||
getConfig: vi.fn(),
|
||||
listDatabases: vi.fn(),
|
||||
listSchemas: vi.fn(),
|
||||
listDorisCatalogs: vi.fn(),
|
||||
listDorisCatalogDatabases: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/backend/api", () => ({
|
||||
listDatabases: mocks.listDatabases,
|
||||
listSchemas: mocks.listSchemas,
|
||||
listDorisCatalogs: mocks.listDorisCatalogs,
|
||||
listDorisCatalogDatabases: mocks.listDorisCatalogDatabases,
|
||||
}));
|
||||
|
||||
vi.mock("@/stores/connectionStore", () => ({
|
||||
@@ -20,6 +34,47 @@ vi.mock("@/stores/connectionStore", () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
const internalCatalog = { name: "default_catalog", catalog_type: "internal", is_current: true };
|
||||
const paimonCatalog = { name: "paimon_catalog", catalog_type: "paimon", is_current: false };
|
||||
|
||||
describe("query catalog options", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("shows the selector only when an external catalog exists", () => {
|
||||
expect(queryCatalogSelectorVisible([internalCatalog])).toBe(false);
|
||||
expect(queryCatalogSelectorVisible([internalCatalog, paimonCatalog])).toBe(true);
|
||||
});
|
||||
|
||||
it("maps the internal catalog to an undefined tab catalog", () => {
|
||||
const catalogs = [internalCatalog, paimonCatalog];
|
||||
|
||||
expect(selectedQueryCatalogName(catalogs)).toBe("default_catalog");
|
||||
expect(normalizedQueryTabCatalog(catalogs, "default_catalog")).toBeUndefined();
|
||||
expect(normalizedQueryTabCatalog(catalogs, "paimon_catalog")).toBe("paimon_catalog");
|
||||
});
|
||||
|
||||
it("keeps a database only when it exists in the selected catalog", () => {
|
||||
expect(databaseAfterCatalogChange("bi", ["bi", "default"])).toBe("bi");
|
||||
expect(databaseAfterCatalogChange("bi", ["analytics"])).toBe("");
|
||||
});
|
||||
|
||||
it("loads and caches catalog-scoped databases", async () => {
|
||||
mocks.getConfig.mockReturnValue({ db_type: "starrocks" });
|
||||
mocks.listDorisCatalogs.mockResolvedValue([internalCatalog, paimonCatalog]);
|
||||
mocks.listDorisCatalogDatabases.mockResolvedValue([{ name: "bi" }, { name: "analytics" }]);
|
||||
const options = useDatabaseOptions();
|
||||
|
||||
await expect(options.loadCatalogOptions("connection-1")).resolves.toEqual([internalCatalog, paimonCatalog]);
|
||||
await expect(options.loadCatalogDatabaseOptions("connection-1", "paimon_catalog")).resolves.toEqual(["bi", "analytics"]);
|
||||
await options.loadCatalogDatabaseOptions("connection-1", "paimon_catalog");
|
||||
|
||||
expect(options.catalogDatabaseOptions.value["connection-1:paimon_catalog"]).toEqual(["bi", "analytics"]);
|
||||
expect(mocks.listDorisCatalogDatabases).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("namespace options", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
@@ -2,10 +2,33 @@ import { ref } from "vue";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { filterDatabaseNamesForConnection, filterSchemaNamesForConnection } from "@/lib/database/visibleDatabases";
|
||||
import { usesTreeSchemaMode } from "@/lib/database/databaseCapabilities";
|
||||
import type { ConnectionConfig } from "@/types/database";
|
||||
import { isInternalDorisCatalog } from "@/lib/database/databaseFeatureSupport";
|
||||
import type { CatalogInfo, ConnectionConfig } from "@/types/database";
|
||||
import * as api from "@/lib/backend/api";
|
||||
|
||||
type NamespaceOptionsConnection = Pick<ConnectionConfig, "database" | "db_type" | "driver_profile" | "visible_databases" | "visible_schemas">;
|
||||
export function catalogDatabaseOptionsKey(connectionId: string, catalog: string): string {
|
||||
return `${connectionId}:${catalog}`;
|
||||
}
|
||||
|
||||
export function queryCatalogSelectorVisible(catalogs: CatalogInfo[]): boolean {
|
||||
return catalogs.some((catalog) => !isInternalDorisCatalog(catalog.catalog_type, catalog.name));
|
||||
}
|
||||
|
||||
export function selectedQueryCatalogName(catalogs: CatalogInfo[], tabCatalog?: string): string {
|
||||
if (tabCatalog) return tabCatalog;
|
||||
return catalogs.find((catalog) => isInternalDorisCatalog(catalog.catalog_type, catalog.name))?.name ?? "";
|
||||
}
|
||||
|
||||
export function normalizedQueryTabCatalog(catalogs: CatalogInfo[], selectedCatalog: string): string | undefined {
|
||||
if (!selectedCatalog) return undefined;
|
||||
const catalog = catalogs.find((candidate) => candidate.name === selectedCatalog);
|
||||
return catalog && isInternalDorisCatalog(catalog.catalog_type, catalog.name) ? undefined : selectedCatalog;
|
||||
}
|
||||
|
||||
export function databaseAfterCatalogChange(currentDatabase: string, databaseOptions: string[]): string {
|
||||
return databaseOptions.includes(currentDatabase) ? currentDatabase : "";
|
||||
}
|
||||
|
||||
export function databaseOptionsForConnection(databaseNames: string[], connection: Pick<ConnectionConfig, "db_type" | "visible_databases"> | undefined): string[] {
|
||||
const names = filterDatabaseNamesForConnection(databaseNames, connection);
|
||||
@@ -42,6 +65,12 @@ export function useDatabaseOptions() {
|
||||
|
||||
const databaseOptions = ref<Record<string, string[]>>({});
|
||||
const loadingDatabaseOptions = ref<Record<string, boolean>>({});
|
||||
const catalogOptions = ref<Record<string, CatalogInfo[]>>({});
|
||||
const loadingCatalogOptions = ref<Record<string, boolean>>({});
|
||||
const catalogDatabaseOptions = ref<Record<string, string[]>>({});
|
||||
const loadingCatalogDatabaseOptions = ref<Record<string, boolean>>({});
|
||||
const catalogRequests = new Map<string, Promise<CatalogInfo[]>>();
|
||||
const catalogDatabaseRequests = new Map<string, Promise<string[]>>();
|
||||
|
||||
async function loadDatabaseOptions(connectionId: string) {
|
||||
const connection = connectionStore.getConfig(connectionId);
|
||||
@@ -70,6 +99,56 @@ export function useDatabaseOptions() {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadCatalogOptions(connectionId: string): Promise<CatalogInfo[]> {
|
||||
const cached = catalogOptions.value[connectionId];
|
||||
if (cached) return cached;
|
||||
const pending = catalogRequests.get(connectionId);
|
||||
if (pending) return pending;
|
||||
|
||||
const request = (async () => {
|
||||
loadingCatalogOptions.value[connectionId] = true;
|
||||
try {
|
||||
await connectionStore.ensureConnected(connectionId);
|
||||
const catalogs = await api.listDorisCatalogs(connectionId);
|
||||
catalogOptions.value[connectionId] = catalogs;
|
||||
return catalogs;
|
||||
} finally {
|
||||
loadingCatalogOptions.value[connectionId] = false;
|
||||
catalogRequests.delete(connectionId);
|
||||
}
|
||||
})();
|
||||
catalogRequests.set(connectionId, request);
|
||||
return request;
|
||||
}
|
||||
|
||||
async function loadCatalogDatabaseOptions(connectionId: string, catalog: string): Promise<string[]> {
|
||||
const key = catalogDatabaseOptionsKey(connectionId, catalog);
|
||||
const cached = catalogDatabaseOptions.value[key];
|
||||
if (cached) return cached;
|
||||
const pending = catalogDatabaseRequests.get(key);
|
||||
if (pending) return pending;
|
||||
|
||||
const request = (async () => {
|
||||
loadingCatalogDatabaseOptions.value[key] = true;
|
||||
try {
|
||||
await connectionStore.ensureConnected(connectionId);
|
||||
const connection = connectionStore.getConfig(connectionId);
|
||||
const databases = await api.listDorisCatalogDatabases(connectionId, catalog);
|
||||
const names = databaseOptionsForConnection(
|
||||
databases.map((database) => database.name),
|
||||
connection,
|
||||
);
|
||||
catalogDatabaseOptions.value[key] = names;
|
||||
return names;
|
||||
} finally {
|
||||
loadingCatalogDatabaseOptions.value[key] = false;
|
||||
catalogDatabaseRequests.delete(key);
|
||||
}
|
||||
})();
|
||||
catalogDatabaseRequests.set(key, request);
|
||||
return request;
|
||||
}
|
||||
|
||||
async function getDatabaseOptions(connectionId: string): Promise<string[]> {
|
||||
if (!databaseOptions.value[connectionId]) {
|
||||
await loadDatabaseOptions(connectionId);
|
||||
@@ -77,5 +156,16 @@ export function useDatabaseOptions() {
|
||||
return databaseOptions.value[connectionId] ?? [];
|
||||
}
|
||||
|
||||
return { databaseOptions, loadingDatabaseOptions, loadDatabaseOptions, getDatabaseOptions };
|
||||
return {
|
||||
databaseOptions,
|
||||
loadingDatabaseOptions,
|
||||
loadDatabaseOptions,
|
||||
getDatabaseOptions,
|
||||
catalogOptions,
|
||||
loadingCatalogOptions,
|
||||
loadCatalogOptions,
|
||||
catalogDatabaseOptions,
|
||||
loadingCatalogDatabaseOptions,
|
||||
loadCatalogDatabaseOptions,
|
||||
};
|
||||
}
|
||||
@@ -720,6 +720,8 @@ export default {
|
||||
saveAllChanges: "Save all",
|
||||
selectConnection: "Select connection",
|
||||
searchConnection: "Search connections...",
|
||||
selectCatalog: "Select catalog",
|
||||
searchCatalog: "Search catalogs...",
|
||||
selectDatabase: "Select database",
|
||||
selectDatabaseRequired: "Select a database first",
|
||||
searchDatabase: "Search databases...",
|
||||
|
||||
@@ -700,6 +700,8 @@ export default withEnglishFallback({
|
||||
saveAllChanges: "Guardar todo",
|
||||
selectConnection: "Seleccionar conexión",
|
||||
searchConnection: "Buscar conexiones...",
|
||||
selectCatalog: "Seleccionar catálogo",
|
||||
searchCatalog: "Buscar catálogos...",
|
||||
selectDatabase: "Seleccionar base de datos",
|
||||
selectDatabaseRequired: "Selecciona una base de datos primero",
|
||||
searchDatabase: "Buscar bases de datos...",
|
||||
|
||||
@@ -698,6 +698,8 @@ export default withEnglishFallback({
|
||||
saveAllChanges: "Salva tutto",
|
||||
selectConnection: "Seleziona connessione",
|
||||
searchConnection: "Cerca connessioni...",
|
||||
selectCatalog: "Seleziona catalogo",
|
||||
searchCatalog: "Cerca cataloghi...",
|
||||
selectDatabase: "Seleziona database",
|
||||
selectDatabaseRequired: "Seleziona prima un database",
|
||||
searchDatabase: "Cerca database...",
|
||||
|
||||
@@ -698,6 +698,8 @@ export default withEnglishFallback({
|
||||
saveAllChanges: "すべて保存",
|
||||
selectConnection: "接続を選択",
|
||||
searchConnection: "接続を検索...",
|
||||
selectCatalog: "カタログを選択",
|
||||
searchCatalog: "カタログを検索...",
|
||||
selectDatabase: "データベースを選択",
|
||||
searchDatabase: "データベースを検索...",
|
||||
selectSchema: "スキーマを選択",
|
||||
|
||||
@@ -699,6 +699,8 @@ export default withEnglishFallback({
|
||||
saveAllChanges: "Salvar tudo",
|
||||
selectConnection: "Selecionar conexão",
|
||||
searchConnection: "Pesquisar conexões...",
|
||||
selectCatalog: "Selecionar catálogo",
|
||||
searchCatalog: "Pesquisar catálogos...",
|
||||
selectDatabase: "Selecionar banco de dados",
|
||||
selectDatabaseRequired: "Selecione um banco de dados primeiro",
|
||||
searchDatabase: "Pesquisar bancos de dados...",
|
||||
|
||||
@@ -721,6 +721,8 @@ export default withEnglishFallback({
|
||||
saveAllChanges: "全部保存",
|
||||
selectConnection: "选择连接",
|
||||
searchConnection: "搜索连接...",
|
||||
selectCatalog: "选择 Catalog",
|
||||
searchCatalog: "搜索 Catalog...",
|
||||
selectDatabase: "选择数据库",
|
||||
selectDatabaseRequired: "请先选择数据库",
|
||||
searchDatabase: "搜索数据库...",
|
||||
|
||||
@@ -698,6 +698,8 @@ export default withEnglishFallback({
|
||||
saveAllChanges: "全部儲存",
|
||||
selectConnection: "選擇連線",
|
||||
searchConnection: "搜尋連線……",
|
||||
selectCatalog: "選擇 Catalog",
|
||||
searchCatalog: "搜尋 Catalog……",
|
||||
selectDatabase: "選擇資料庫",
|
||||
selectDatabaseRequired: "請先選擇資料庫",
|
||||
searchDatabase: "搜尋資料庫……",
|
||||
|
||||
+45
-1
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildSelectAllSql, isNewQueryPrefillSupported, resolveNewQueryInitialSql, resolveNewQueryTable, type ResolveNewQueryTableInput } from "@/lib/sql/newQueryContext";
|
||||
import { buildSelectAllSql, isNewQueryPrefillSupported, resolveNewQueryInitialSql, resolveNewQueryTable, resolveNewQueryTarget } from "@/lib/sql/newQueryContext";
|
||||
import type { ResolveNewQueryTableInput } from "@/lib/sql/newQueryContext";
|
||||
import type { QueryTab, TreeNode } from "@/types/database";
|
||||
|
||||
function dataTab(overrides: Partial<Pick<QueryTab, "mode" | "connectionId" | "database" | "schema" | "tableMeta" | "structureTableName" | "title">> = {}): ResolveNewQueryTableInput["activeTab"] {
|
||||
@@ -18,6 +19,46 @@ function tableNode(overrides: Partial<Pick<TreeNode, "type" | "connectionId" | "
|
||||
return { type: "table", connectionId: "conn-1", database: "app_db", schema: "public", tableName: "orders", label: "orders", ...overrides };
|
||||
}
|
||||
|
||||
describe("resolveNewQueryTarget", () => {
|
||||
it("inherits an external catalog from the active object browser", () => {
|
||||
expect(
|
||||
resolveNewQueryTarget({
|
||||
activeTab: {
|
||||
connectionId: "conn-1",
|
||||
database: "bi",
|
||||
objectBrowser: { catalog: "paimon_catalog" },
|
||||
},
|
||||
connections: [{ id: "conn-1", database: "" }],
|
||||
preferredSource: "tab",
|
||||
}),
|
||||
).toEqual({
|
||||
connectionId: "conn-1",
|
||||
database: "bi",
|
||||
schema: undefined,
|
||||
catalog: "paimon_catalog",
|
||||
shouldRefreshDefaultDatabase: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("inherits an external catalog from active table metadata", () => {
|
||||
expect(
|
||||
resolveNewQueryTarget({
|
||||
activeTab: {
|
||||
connectionId: "conn-1",
|
||||
database: "bi",
|
||||
tableMeta: {
|
||||
catalog: "paimon_catalog",
|
||||
tableName: "events",
|
||||
columns: [],
|
||||
primaryKeys: [],
|
||||
},
|
||||
},
|
||||
connections: [{ id: "conn-1", database: "" }],
|
||||
})?.catalog,
|
||||
).toBe("paimon_catalog");
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveNewQueryTable", () => {
|
||||
it("resolves the table from an active data tab", () => {
|
||||
const table = resolveNewQueryTable({ activeTab: dataTab(), preferredSource: "tab" });
|
||||
@@ -119,6 +160,9 @@ describe("buildSelectAllSql", () => {
|
||||
expect(buildSelectAllSql("mysql", { tableName: "a`b" })).toBe("SELECT * FROM `a``b`");
|
||||
expect(buildSelectAllSql("postgres", { tableName: 'a"b' })).toBe('SELECT * FROM "a""b"');
|
||||
});
|
||||
it("qualifies a StarRocks external-catalog table with catalog and database", () => {
|
||||
expect(buildSelectAllSql("starrocks", { catalog: "paimon_catalog", database: "bi", tableName: "events" })).toBe("SELECT * FROM `paimon_catalog`.`bi`.`events`");
|
||||
});
|
||||
});
|
||||
|
||||
describe("isNewQueryPrefillSupported", () => {
|
||||
|
||||
@@ -813,6 +813,7 @@ export async function executeQuery(
|
||||
executionId?: string,
|
||||
options?: {
|
||||
maxRows?: number;
|
||||
catalog?: string;
|
||||
fetchSize?: number;
|
||||
pageSize?: number;
|
||||
resultSessionId?: string;
|
||||
@@ -839,6 +840,7 @@ export async function executeMulti(
|
||||
executionId?: string,
|
||||
options?: {
|
||||
maxRows?: number;
|
||||
catalog?: string;
|
||||
fetchSize?: number;
|
||||
pageSize?: number;
|
||||
resultSessionId?: string;
|
||||
@@ -874,6 +876,7 @@ export async function executeMultiWithProgress(
|
||||
schema?: string,
|
||||
options?: {
|
||||
maxRows?: number;
|
||||
catalog?: string;
|
||||
fetchSize?: number;
|
||||
pageSize?: number;
|
||||
resultSessionId?: string;
|
||||
@@ -895,20 +898,22 @@ export async function executeMultiWithProgress(
|
||||
return results;
|
||||
}
|
||||
|
||||
export async function closeQuerySession(connectionId: string, database: string, sessionId: string, clientSessionId?: string): Promise<boolean> {
|
||||
export async function closeQuerySession(connectionId: string, database: string, sessionId: string, clientSessionId?: string, catalog?: string): Promise<boolean> {
|
||||
return post("/api/query/close-session", {
|
||||
connectionId,
|
||||
database,
|
||||
sessionId,
|
||||
clientSessionId,
|
||||
catalog,
|
||||
});
|
||||
}
|
||||
|
||||
export async function closeClientConnectionSession(connectionId: string, database: string, clientSessionId: string): Promise<boolean> {
|
||||
export async function closeClientConnectionSession(connectionId: string, database: string, clientSessionId: string, catalog?: string): Promise<boolean> {
|
||||
return post("/api/query/close-client-session", {
|
||||
connectionId,
|
||||
database,
|
||||
clientSessionId,
|
||||
catalog,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -939,16 +944,17 @@ export async function executeScriptWith2pc(connectionId: string, database: strin
|
||||
});
|
||||
}
|
||||
|
||||
export async function executeInTransaction(connectionId: string, database: string, statements: string[], schema?: string): Promise<QueryResult> {
|
||||
export async function executeInTransaction(connectionId: string, database: string, statements: string[], schema?: string, catalog?: string): Promise<QueryResult> {
|
||||
return post("/api/query/execute-in-transaction", {
|
||||
connectionId,
|
||||
database,
|
||||
statements,
|
||||
schema,
|
||||
catalog,
|
||||
});
|
||||
}
|
||||
|
||||
export async function beginManualTransaction(_connectionId: string, _database: string, _schema?: string): Promise<string> {
|
||||
export async function beginManualTransaction(_connectionId: string, _database: string, _schema?: string, _catalog?: string): Promise<string> {
|
||||
throw new Error("Manual transaction management is only available in the desktop app.");
|
||||
}
|
||||
|
||||
|
||||
@@ -1021,6 +1021,7 @@ export async function executeQuery(
|
||||
executionId?: string,
|
||||
options?: {
|
||||
maxRows?: number;
|
||||
catalog?: string;
|
||||
fetchSize?: number;
|
||||
pageSize?: number;
|
||||
resultSessionId?: string;
|
||||
@@ -1047,6 +1048,7 @@ export async function executeMulti(
|
||||
executionId?: string,
|
||||
options?: {
|
||||
maxRows?: number;
|
||||
catalog?: string;
|
||||
fetchSize?: number;
|
||||
pageSize?: number;
|
||||
resultSessionId?: string;
|
||||
@@ -1082,6 +1084,7 @@ export async function executeMultiWithProgress(
|
||||
schema?: string,
|
||||
options?: {
|
||||
maxRows?: number;
|
||||
catalog?: string;
|
||||
fetchSize?: number;
|
||||
pageSize?: number;
|
||||
resultSessionId?: string;
|
||||
@@ -1118,20 +1121,22 @@ export async function cancelQuery(executionId: string): Promise<boolean> {
|
||||
return invoke("cancel_query", { executionId });
|
||||
}
|
||||
|
||||
export async function closeQuerySession(connectionId: string, database: string, sessionId: string, clientSessionId?: string): Promise<boolean> {
|
||||
export async function closeQuerySession(connectionId: string, database: string, sessionId: string, clientSessionId?: string, catalog?: string): Promise<boolean> {
|
||||
return invoke("close_query_session", {
|
||||
connectionId,
|
||||
database,
|
||||
sessionId,
|
||||
clientSessionId,
|
||||
catalog,
|
||||
});
|
||||
}
|
||||
|
||||
export async function closeClientConnectionSession(connectionId: string, database: string, clientSessionId: string): Promise<boolean> {
|
||||
export async function closeClientConnectionSession(connectionId: string, database: string, clientSessionId: string, catalog?: string): Promise<boolean> {
|
||||
return invoke("close_client_connection_session", {
|
||||
connectionId,
|
||||
database,
|
||||
clientSessionId,
|
||||
catalog,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1158,17 +1163,18 @@ export async function executeScriptWith2pc(connectionId: string, database: strin
|
||||
});
|
||||
}
|
||||
|
||||
export async function executeInTransaction(connectionId: string, database: string, statements: string[], schema?: string): Promise<QueryResult> {
|
||||
export async function executeInTransaction(connectionId: string, database: string, statements: string[], schema?: string, catalog?: string): Promise<QueryResult> {
|
||||
return invoke("execute_in_transaction", {
|
||||
connectionId,
|
||||
database,
|
||||
statements,
|
||||
schema,
|
||||
catalog,
|
||||
});
|
||||
}
|
||||
|
||||
export async function beginManualTransaction(connectionId: string, database: string, schema?: string): Promise<string> {
|
||||
return invoke("begin_manual_transaction", { connectionId, database, schema });
|
||||
export async function beginManualTransaction(connectionId: string, database: string, schema?: string, catalog?: string): Promise<string> {
|
||||
return invoke("begin_manual_transaction", { connectionId, database, schema, catalog });
|
||||
}
|
||||
|
||||
export async function executeInManualTransaction(txnSessionId: string, sql: string, database: string, schema?: string, maxRows?: number): Promise<QueryResult[]> {
|
||||
|
||||
@@ -13,7 +13,7 @@ export interface NewQueryTarget {
|
||||
export type NewQueryContextSource = "tab" | "sidebar";
|
||||
|
||||
interface ResolveNewQueryTargetInput {
|
||||
activeTab?: Pick<QueryTab, "connectionId" | "database" | "schema" | "catalog">;
|
||||
activeTab?: Pick<QueryTab, "connectionId" | "database" | "schema" | "catalog" | "objectBrowser" | "tableMeta">;
|
||||
selectedTreeNode?: Pick<TreeNode, "connectionId" | "database" | "schema" | "catalog"> | null;
|
||||
activeConnectionId?: string | null;
|
||||
connections: Pick<ConnectionConfig, "id" | "database">[];
|
||||
@@ -49,16 +49,18 @@ export function resolveNewQueryTarget(input: ResolveNewQueryTargetInput): NewQue
|
||||
: null;
|
||||
}
|
||||
|
||||
function targetFromContext(context: Pick<QueryTab | TreeNode, "connectionId" | "database" | "schema" | "catalog"> | undefined, connections: Pick<ConnectionConfig, "id" | "database">[]): NewQueryTarget | null {
|
||||
function targetFromContext(context: Pick<QueryTab, "connectionId" | "database" | "schema" | "catalog" | "objectBrowser" | "tableMeta"> | Pick<TreeNode, "connectionId" | "database" | "schema" | "catalog"> | undefined, connections: Pick<ConnectionConfig, "id" | "database">[]): NewQueryTarget | null {
|
||||
if (!context?.connectionId) return null;
|
||||
const connection = connections.find((item) => item.id === context.connectionId);
|
||||
if (!connection) return null;
|
||||
const database = context.database || resolveDefaultDatabase(connection, []);
|
||||
const objectBrowser = "objectBrowser" in context ? context.objectBrowser : undefined;
|
||||
const tableMeta = "tableMeta" in context ? context.tableMeta : undefined;
|
||||
return {
|
||||
connectionId: context.connectionId,
|
||||
database,
|
||||
schema: "schema" in context ? (context as { schema?: string }).schema : undefined,
|
||||
catalog: "catalog" in context ? (context as { catalog?: string }).catalog : undefined,
|
||||
schema: context.schema ?? objectBrowser?.schema ?? tableMeta?.schema,
|
||||
catalog: context.catalog ?? objectBrowser?.catalog ?? tableMeta?.catalog,
|
||||
shouldRefreshDefaultDatabase: !context.database,
|
||||
};
|
||||
}
|
||||
@@ -72,7 +74,7 @@ export interface NewQueryTable {
|
||||
}
|
||||
|
||||
export interface ResolveNewQueryTableInput {
|
||||
activeTab?: Pick<QueryTab, "mode" | "connectionId" | "database" | "schema" | "tableMeta" | "structureTableName" | "title"> | null;
|
||||
activeTab?: Pick<QueryTab, "mode" | "connectionId" | "database" | "schema" | "catalog" | "tableMeta" | "structureTableName" | "title"> | null;
|
||||
selectedTreeNode?: Pick<TreeNode, "type" | "connectionId" | "database" | "schema" | "catalog" | "tableName" | "label"> | null;
|
||||
preferredSource?: NewQueryContextSource;
|
||||
}
|
||||
@@ -106,7 +108,7 @@ function tableFromTab(tab: ResolveNewQueryTableInput["activeTab"]): NewQueryTabl
|
||||
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 { connectionId: tab.connectionId, database: tab.database, schema: tab.schema, catalog: tab.catalog, tableName };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -137,8 +139,8 @@ export function resolveNewQueryTable(input: ResolveNewQueryTableInput): NewQuery
|
||||
* 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 });
|
||||
export function buildSelectAllSql(databaseType: DatabaseType | undefined, table: Pick<NewQueryTable, "schema" | "catalog" | "tableName"> & Partial<Pick<NewQueryTable, "database">>): string {
|
||||
const ref = qualifiedTableName({ databaseType, database: table.database, schema: table.schema, catalog: table.catalog, tableName: table.tableName });
|
||||
return `SELECT * FROM ${ref}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import { qualifiedTableName, quoteTableIdentifier } from "@/lib/table/tableSelec
|
||||
export interface TableSqlTemplateOptions {
|
||||
databaseType?: DatabaseType;
|
||||
schema?: string;
|
||||
catalog?: string;
|
||||
database?: string;
|
||||
tableName: string;
|
||||
columns?: ColumnInfo[];
|
||||
tableType?: string;
|
||||
@@ -96,6 +98,8 @@ export function buildTableDeleteTemplate(options: TableSqlTemplateOptions): stri
|
||||
function templateTableName(options: TableSqlTemplateOptions): string {
|
||||
return qualifiedTableName({
|
||||
databaseType: options.databaseType,
|
||||
catalog: options.catalog,
|
||||
database: options.database,
|
||||
schema: options.schema,
|
||||
tableName: options.tableName,
|
||||
});
|
||||
|
||||
@@ -338,6 +338,16 @@ describe("queryStore multi-statement errors", () => {
|
||||
|
||||
expect(mocks.executeMulti).toHaveBeenCalledWith("mysql-1", "app", "SELECT 1", undefined, expect.any(String), expect.objectContaining({ continueOnError: false }));
|
||||
});
|
||||
it("passes the selected external catalog to query execution", async () => {
|
||||
mocks.executeMulti.mockResolvedValue([{ columns: ["value"], rows: [[1]], affected_rows: 0, execution_time_ms: 1 }]);
|
||||
const { useQueryStore } = await import("@/stores/queryStore");
|
||||
const store = useQueryStore();
|
||||
const tabId = store.createTab("mysql-1", "bi", "Query", "query", undefined, undefined, "paimon_catalog");
|
||||
|
||||
await store.executeTabSql(tabId, "SELECT * FROM events");
|
||||
|
||||
expect(mocks.executeMulti).toHaveBeenCalledWith("mysql-1", "bi", "SELECT * FROM events", undefined, expect.any(String), expect.objectContaining({ catalog: "paimon_catalog" }));
|
||||
});
|
||||
|
||||
it("keeps old and new executions as result runs, then lets normal execution replace the active run", async () => {
|
||||
mocks.executeMulti
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createPinia, setActivePinia } from "pinia";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { useQueryStore } from "@/stores/queryStore";
|
||||
|
||||
describe("queryStore switchTab", () => {
|
||||
beforeEach(() => {
|
||||
@@ -74,6 +75,44 @@ describe("queryStore switchTab", () => {
|
||||
expect(settingsStore.settingsPageActive).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps same-named tabs from different catalogs distinct", async () => {
|
||||
const queryStore = useQueryStore();
|
||||
|
||||
const paimonTabId = queryStore.createTab("sr-1", "bi", "events", "query", undefined, undefined, "paimon_catalog");
|
||||
const internalTabId = queryStore.createTab("sr-1", "bi", "events", "query", undefined, undefined, "internal");
|
||||
|
||||
expect(internalTabId).not.toBe(paimonTabId);
|
||||
expect(queryStore.tabs).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("preserves catalog context when duplicating a query tab", async () => {
|
||||
const queryStore = useQueryStore();
|
||||
const tabId = queryStore.createTab("sr-1", "bi", undefined, "query", undefined, "SELECT 1", "paimon_catalog");
|
||||
|
||||
queryStore.duplicateTab(tabId);
|
||||
|
||||
expect(queryStore.tabs).toHaveLength(2);
|
||||
expect(queryStore.tabs[1].catalog).toBe("paimon_catalog");
|
||||
});
|
||||
|
||||
it("switches catalog and database as one query context", () => {
|
||||
const queryStore = useQueryStore();
|
||||
const tabId = queryStore.createTab("sr-1", "internal_db", undefined, "query");
|
||||
const tab = queryStore.tabs.find((candidate) => candidate.id === tabId)!;
|
||||
tab.result = {
|
||||
columns: ["id"],
|
||||
rows: [[1]],
|
||||
affected_rows: 0,
|
||||
execution_time_ms: 1,
|
||||
};
|
||||
|
||||
queryStore.updateCatalog(tabId, "paimon_catalog", "bi");
|
||||
|
||||
expect(tab.catalog).toBe("paimon_catalog");
|
||||
expect(tab.database).toBe("bi");
|
||||
expect(tab.result).toBeUndefined();
|
||||
});
|
||||
|
||||
it("stores local data-grid column filters on the tab result", async () => {
|
||||
const { useQueryStore } = await import("@/stores/queryStore");
|
||||
const queryStore = useQueryStore();
|
||||
|
||||
@@ -538,10 +538,11 @@ export const useQueryStore = defineStore("query", () => {
|
||||
const sessionId = tab?.resultSessionId ?? tab?.result?.session_id;
|
||||
if (!tab || !sessionId || sessionId === preserveSessionId) return;
|
||||
try {
|
||||
const catalog = tab.mode === "data" ? tab.tableMeta?.catalog : undefined;
|
||||
const catalog = tab.mode === "data" ? tab.tableMeta?.catalog : tab.catalog;
|
||||
const connection = catalog ? useConnectionStore().getConfig(tab.connectionId) : undefined;
|
||||
const executionDatabase = dataTabExecutionDatabase(connection, tab.database, catalog);
|
||||
await api.closeQuerySession(tab.connectionId, executionDatabase, sessionId, tab.id);
|
||||
if (catalog) await api.closeQuerySession(tab.connectionId, executionDatabase, sessionId, tab.id, catalog);
|
||||
else await api.closeQuerySession(tab.connectionId, executionDatabase, sessionId, tab.id);
|
||||
} catch (error) {
|
||||
console.warn("[DBX][query-session:close:error]", { tabId: tab.id, sessionId, error });
|
||||
if (throwOnError) throw error;
|
||||
@@ -555,9 +556,10 @@ export const useQueryStore = defineStore("query", () => {
|
||||
}
|
||||
}
|
||||
|
||||
async function closeClientSessionId(connectionId: string, database: string, clientSessionId: string, logContext: Record<string, unknown> = {}, throwOnError = false) {
|
||||
async function closeClientSessionId(connectionId: string, database: string, clientSessionId: string, catalog: string | undefined, logContext: Record<string, unknown> = {}, throwOnError = false) {
|
||||
try {
|
||||
await api.closeClientConnectionSession(connectionId, database, clientSessionId);
|
||||
if (catalog) await api.closeClientConnectionSession(connectionId, database, clientSessionId, catalog);
|
||||
else await api.closeClientConnectionSession(connectionId, database, clientSessionId);
|
||||
} catch (error) {
|
||||
console.warn("[DBX][client-session:close:error]", { ...logContext, clientSessionId, error });
|
||||
if (throwOnError) throw error;
|
||||
@@ -566,12 +568,12 @@ export const useQueryStore = defineStore("query", () => {
|
||||
|
||||
async function closeClientConnectionSession(tab: QueryTab | undefined, throwOnError = false) {
|
||||
if (!tab?.connectionId) return;
|
||||
const catalog = tab.mode === "data" ? tab.tableMeta?.catalog : undefined;
|
||||
const catalog = tab.mode === "data" ? tab.tableMeta?.catalog : tab.catalog;
|
||||
const connection = catalog ? useConnectionStore().getConfig(tab.connectionId) : undefined;
|
||||
const executionDatabase = dataTabExecutionDatabase(connection, tab.database, catalog);
|
||||
const clientSessionIds = [...new Set([tabClientSessionId(tab), ...BACKGROUND_CLIENT_SESSION_SUFFIXES.map((suffix) => tabClientSessionId(tab, suffix)), tab.explainClientSessionId].filter((sessionId): sessionId is string => !!sessionId))];
|
||||
for (const clientSessionId of clientSessionIds) {
|
||||
await closeClientSessionId(tab.connectionId, executionDatabase, clientSessionId, { tabId: tab.id }, throwOnError);
|
||||
await closeClientSessionId(tab.connectionId, executionDatabase, clientSessionId, catalog, { tabId: tab.id }, throwOnError);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1153,13 +1155,13 @@ export const useQueryStore = defineStore("query", () => {
|
||||
return saveTabs(tabs.value, activeTabId.value);
|
||||
}
|
||||
|
||||
function findTabByIdentity(connectionId: string, database: string, title: string, mode: QueryTab["mode"], schema?: string) {
|
||||
return tabs.value.find((tab) => tab.connectionId === connectionId && tab.database === database && tab.title === title && tab.mode === mode && (tab.schema || "") === (schema || ""));
|
||||
function findTabByIdentity(connectionId: string, database: string, title: string, mode: QueryTab["mode"], schema?: string, catalog?: string) {
|
||||
return tabs.value.find((tab) => tab.connectionId === connectionId && tab.database === database && tab.title === title && tab.mode === mode && (tab.schema || "") === (schema || "") && (tab.catalog || "") === (catalog || ""));
|
||||
}
|
||||
|
||||
function createTab(connectionId: string, database: string, title?: string, mode: QueryTab["mode"] = "query", schema?: string, initialSql?: string, catalog?: string, options: { forceNew?: boolean } = {}) {
|
||||
if (title && !options.forceNew) {
|
||||
const existing = findTabByIdentity(connectionId, database, title, mode, schema);
|
||||
const existing = findTabByIdentity(connectionId, database, title, mode, schema, catalog);
|
||||
if (existing) {
|
||||
switchTab(existing.id);
|
||||
return existing.id;
|
||||
@@ -1937,6 +1939,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
connectionId: original.connectionId,
|
||||
database: original.database,
|
||||
schema: original.schema,
|
||||
catalog: original.catalog,
|
||||
sql: original.sql,
|
||||
originalSql: "",
|
||||
savedSqlId: undefined,
|
||||
@@ -2409,6 +2412,24 @@ export const useQueryStore = defineStore("query", () => {
|
||||
tab.tableMeta = undefined;
|
||||
}
|
||||
|
||||
function updateCatalog(id: string, catalog: string | undefined, database: string) {
|
||||
const tab = tabs.value.find((candidate) => candidate.id === id);
|
||||
if (!tab || (tab.catalog === catalog && tab.database === database)) return;
|
||||
rollbackTabTransaction(tab);
|
||||
void closeResultSession(tab);
|
||||
void closeClientConnectionSession(tab);
|
||||
tab.catalog = catalog;
|
||||
tab.database = database;
|
||||
tab.schema = undefined;
|
||||
tab.objectBrowser = undefined;
|
||||
clearResultPayload(tab);
|
||||
tab.lastExecutedSql = undefined;
|
||||
tab.resultBaseSql = undefined;
|
||||
tab.resultSortedSql = undefined;
|
||||
clearExplain(tab);
|
||||
tab.tableMeta = undefined;
|
||||
}
|
||||
|
||||
function updateSchema(id: string, schema: string | undefined) {
|
||||
const tab = tabs.value.find((t) => t.id === id);
|
||||
if (!tab || tab.schema === schema) return;
|
||||
@@ -2434,6 +2455,8 @@ export const useQueryStore = defineStore("query", () => {
|
||||
void closeClientConnectionSession(tab);
|
||||
tab.connectionId = connectionId;
|
||||
tab.database = database;
|
||||
tab.catalog = undefined;
|
||||
tab.objectBrowser = undefined;
|
||||
tab.schema = undefined;
|
||||
clearResultPayload(tab);
|
||||
tab.lastExecutedSql = undefined;
|
||||
@@ -2978,6 +3001,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
connectionId: string;
|
||||
database: string;
|
||||
schema?: string;
|
||||
catalog?: string;
|
||||
countSql?: string;
|
||||
countSqlTarget?: () => Promise<TotalRowCountSqlTarget | undefined>;
|
||||
result: QueryResult;
|
||||
@@ -3022,6 +3046,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
queryExecutionLog("info", "count:start", { traceId: options.traceId, elapsed: options.elapsed() });
|
||||
const countResult = await api.executeQuery(options.connectionId, options.database, countTarget.sql, countTarget.schema, countExecutionId, {
|
||||
clientSessionId,
|
||||
catalog: options.catalog,
|
||||
timeoutSecs: options.timeoutSecs,
|
||||
});
|
||||
const total = Number(countResult.rows?.[0]?.[0] ?? 0);
|
||||
@@ -3043,7 +3068,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
error,
|
||||
});
|
||||
} finally {
|
||||
void closeClientSessionId(options.connectionId, options.database, clientSessionId, { tabId: options.tabId });
|
||||
void closeClientSessionId(options.connectionId, options.database, clientSessionId, options.catalog, { tabId: options.tabId });
|
||||
}
|
||||
})();
|
||||
}
|
||||
@@ -3678,7 +3703,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
if (tab.autoCommit === false) {
|
||||
if (!tab.txnSessionId) {
|
||||
queryExecutionLog("info", "begin-manual-txn:start", { traceId, elapsed: elapsed() });
|
||||
tab.txnSessionId = await api.beginManualTransaction(tab.connectionId, executionDatabase, executionSchema);
|
||||
tab.txnSessionId = await api.beginManualTransaction(tab.connectionId, executionDatabase, executionSchema, tab.catalog);
|
||||
queryExecutionLog("info", "begin-manual-txn:done", { traceId, txnSessionId: tab.txnSessionId, elapsed: elapsed() });
|
||||
}
|
||||
queryExecutionLog("info", "execute-in-txn:invoke", { traceId, txnSessionId: tab.txnSessionId, elapsed: elapsed() });
|
||||
@@ -3704,6 +3729,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
: {}),
|
||||
...(clientSessionId ? { clientSessionId } : {}),
|
||||
timeoutSecs: queryTimeoutSecs,
|
||||
catalog: tab.catalog,
|
||||
continueOnError: settingsStore.editorSettings.continueOnErrorOnBatch,
|
||||
};
|
||||
queryExecutionLog("info", "execute-multi:invoke", {
|
||||
@@ -3839,6 +3865,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
connectionId: current.connectionId,
|
||||
database: executionDatabase,
|
||||
schema: current.schema,
|
||||
catalog: current.catalog,
|
||||
countSql,
|
||||
countSqlTarget: dataCountTarget
|
||||
? async () => ({
|
||||
@@ -4084,6 +4111,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
try {
|
||||
tableResult = await api.executeQuery(tab.connectionId, tab.database, tableSql, tab.schema, executionId, {
|
||||
clientSessionId,
|
||||
catalog: tab.catalog,
|
||||
timeoutSecs: queryTimeoutSecs,
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
@@ -4095,6 +4123,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
try {
|
||||
tableResult = await api.executeQuery(tab.connectionId, tab.database, tableSql, tab.schema, executionId, {
|
||||
clientSessionId,
|
||||
catalog: tab.catalog,
|
||||
timeoutSecs: queryTimeoutSecs,
|
||||
});
|
||||
} catch (fallbackError: unknown) {
|
||||
@@ -4135,6 +4164,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
if (latest?.explainExecutionId === executionId) latest.explainSql = jsonBuilt.sql;
|
||||
const jsonResult = await api.executeQuery(tab.connectionId, tab.database, jsonBuilt.sql, tab.schema, executionId, {
|
||||
clientSessionId,
|
||||
catalog: tab.catalog,
|
||||
timeoutSecs: queryTimeoutSecs,
|
||||
});
|
||||
const current = tabs.value.find((t) => t.id === id);
|
||||
@@ -4158,7 +4188,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
current.explainExecutionId = undefined;
|
||||
}
|
||||
if (current?.explainClientSessionId === clientSessionId) current.explainClientSessionId = undefined;
|
||||
void closeClientSessionId(tab.connectionId, tab.database, clientSessionId, { tabId: tab.id, explainExecutionId: executionId });
|
||||
void closeClientSessionId(tab.connectionId, tab.database, clientSessionId, tab.catalog, { tabId: tab.id, explainExecutionId: executionId });
|
||||
}
|
||||
return { ok: true as const, sql: tab.explainSql ?? tableSql };
|
||||
}
|
||||
@@ -4238,7 +4268,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
current.explainExecutionId = undefined;
|
||||
}
|
||||
if (current?.explainClientSessionId === clientSessionId) current.explainClientSessionId = undefined;
|
||||
await closeClientSessionId(tab.connectionId, tab.database, clientSessionId, { tabId: tab.id, explainExecutionId: executionId });
|
||||
await closeClientSessionId(tab.connectionId, tab.database, clientSessionId, tab.catalog, { tabId: tab.id, explainExecutionId: executionId });
|
||||
}
|
||||
return { ok: true as const, sql: built.sql };
|
||||
}
|
||||
@@ -4257,6 +4287,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
try {
|
||||
const result = await api.executeQuery(tab.connectionId, tab.database, built.sql, tab.schema, executionId, {
|
||||
clientSessionId,
|
||||
catalog: tab.catalog,
|
||||
timeoutSecs: queryTimeoutSecs,
|
||||
});
|
||||
const current = tabs.value.find((t) => t.id === id);
|
||||
@@ -4276,7 +4307,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
current.isExplaining = false;
|
||||
current.explainExecutionId = undefined;
|
||||
}
|
||||
void closeClientSessionId(tab.connectionId, tab.database, clientSessionId, { tabId: tab.id });
|
||||
void closeClientSessionId(tab.connectionId, tab.database, clientSessionId, tab.catalog, { tabId: tab.id });
|
||||
}
|
||||
return { ok: true as const, sql: built.sql };
|
||||
}
|
||||
@@ -4650,6 +4681,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
maxRows: pageLimit,
|
||||
fetchSize: pageLimit,
|
||||
clientSessionId,
|
||||
catalog: tableMeta.catalog,
|
||||
timeoutSecs: queryTimeoutSecs,
|
||||
});
|
||||
const result = results[0];
|
||||
@@ -4662,7 +4694,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
offset += result.rows.length;
|
||||
}
|
||||
} finally {
|
||||
void closeClientSessionId(tab.connectionId, executionDatabase, clientSessionId, { tabId: tab.id });
|
||||
void closeClientSessionId(tab.connectionId, executionDatabase, clientSessionId, tableMeta.catalog, { tabId: tab.id });
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -4722,9 +4754,10 @@ export const useQueryStore = defineStore("query", () => {
|
||||
pageSize: plan.pageLimit,
|
||||
resultSessionId: sessionId,
|
||||
clientSessionId,
|
||||
catalog: tab.catalog,
|
||||
timeoutSecs: queryTimeoutSecs,
|
||||
}
|
||||
: { maxRows: plan.pageLimit, fetchSize: plan.pageLimit, clientSessionId, timeoutSecs: queryTimeoutSecs };
|
||||
: { maxRows: plan.pageLimit, fetchSize: plan.pageLimit, clientSessionId, catalog: tab.catalog, timeoutSecs: queryTimeoutSecs };
|
||||
const results = await api.executeMulti(tab.connectionId, tab.database, plan.sqlToExecute, tab.schema, exportExecutionId, executionOptions);
|
||||
const result = results[0];
|
||||
if (!result) break;
|
||||
@@ -4738,8 +4771,8 @@ export const useQueryStore = defineStore("query", () => {
|
||||
offset += result.rows.length;
|
||||
}
|
||||
} finally {
|
||||
if (sessionId) void api.closeQuerySession(tab.connectionId, tab.database, sessionId, clientSessionId);
|
||||
void closeClientSessionId(tab.connectionId, tab.database, clientSessionId, { tabId: tab.id });
|
||||
if (sessionId) void api.closeQuerySession(tab.connectionId, tab.database, sessionId, clientSessionId, tab.catalog);
|
||||
void closeClientSessionId(tab.connectionId, tab.database, clientSessionId, tab.catalog, { tabId: tab.id });
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -4929,6 +4962,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
togglePinnedTab,
|
||||
reorderTab,
|
||||
updateDatabase,
|
||||
updateCatalog,
|
||||
updateSchema,
|
||||
updateConnection,
|
||||
setTableMeta,
|
||||
|
||||
Reference in new issue
Block a user