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
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user