fix(oceanbase): preserve partition DDL and toggle storage options

This commit is contained in:
efeire authored and GitHub committed 2026-09-18 01:59:42 +08:00
1 parent 8eba12ea2f
commit fe0dd02f2b
36 files changed
+870 -125

No files matched your search

@@ -0,0 +1,19 @@
<script setup lang="ts">
import { useId } from "vue";
import { useI18n } from "vue-i18n";
import { useSettingsStore } from "@/stores/settingsStore";
import { Switch } from "@/components/ui/switch";
import type { DatabaseType } from "@/types/database";
defineProps<{ databaseType?: DatabaseType; disabled?: boolean }>();
const settings = useSettingsStore();
const { t } = useI18n();
const id = useId();
</script>
<template>
<div v-if="databaseType === 'oceanbase-oracle'" class="flex shrink-0 items-center gap-2 text-xs" :title="t('contextMenu.excludeDdlStorageHint')">
<Switch :id="id" size="sm" :disabled="disabled" :model-value="settings.editorSettings.excludeDdlStorage !== false" @update:model-value="settings.updateEditorSettings({ excludeDdlStorage: $event })" />
<label :for="id" class="cursor-pointer">{{ t("contextMenu.excludeDdlStorage") }}</label>
</div>
</template>
@@ -1,4 +1,6 @@
<script setup lang="ts">
import { applyDdlStoragePreference } from "@/lib/sql/ddlStorage";
import DdlStorageToggle from "@/components/objects/DdlStorageToggle.vue";
import { computed, nextTick, onUnmounted, ref, shallowRef, watch } from "vue";
import { useI18n } from "vue-i18n";
import { Clipboard, Loader2, RefreshCw } from "@lucide/vue";
@@ -47,7 +49,8 @@ const { toast } = useToast();
const { isDark, themePalette } = useTheme();
const settingsStore = useSettingsStore();
const ddlContent = ref("");
const rawDdlContent = ref("");
const ddlContent = computed(() => applyDdlStoragePreference(rawDdlContent.value, props.databaseType, settingsStore.editorSettings.excludeDdlStorage));
const ddlLoading = ref(false);
const ddlError = ref("");
const ddlEditorContainer = ref<HTMLDivElement>();
@@ -128,7 +131,7 @@ async function loadDdl(force = false) {
);
const formatDialect = props.formatDialect ?? props.dialect;
const formatted = await formatSqlForDisplay(ddl, formatDialect, settingsStore.editorSettings.sqlFormatter);
ddlContent.value = settingsStore.editorSettings.generateSqlQuoteIdentifiers ? formatted : omitDdlIdentifierQuotes(formatted, formatDialect);
rawDdlContent.value = settingsStore.editorSettings.generateSqlQuoteIdentifiers ? formatted : omitDdlIdentifierQuotes(formatted, formatDialect);
} catch (e: any) {
ddlError.value = e?.message || String(e);
} finally {
@@ -144,7 +147,7 @@ watch(
if (!open) return;
const active = document.activeElement;
editorRootToRestoreFocus = active instanceof HTMLElement ? active.closest(".cm-editor") : null;
ddlContent.value = "";
rawDdlContent.value = "";
await loadDdl(settingsStore.editorSettings.refreshDdlOnOpen);
},
{ immediate: true },
@@ -247,6 +250,11 @@ function copyDdlContent() {
}
}
watch(ddlContent, (content) => {
const view = ddlEditorView.value;
if (view) view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: content } });
});
// When DDL finishes loading, create the editor inside the dialog.
watch(ddlLoading, (loading) => {
if (!loading && ddlContent.value && props.open) {
@@ -270,7 +278,7 @@ onUnmounted(() => {
});
function retry() {
ddlContent.value = "";
rawDdlContent.value = "";
void loadDdl(true);
}
@@ -307,6 +315,7 @@ function onClose() {
<EditorSearchPanel v-if="ddlEditorView" ref="ddlSearchPanelRef" :view="ddlEditorView" />
</div>
</div>
<DdlStorageToggle :database-type="props.databaseType" :disabled="ddlLoading || !!ddlError" />
<DialogFooter>
<div class="mr-auto flex items-center gap-2 text-sm text-muted-foreground">
<Switch id="ddl-refresh-on-open" size="sm" :model-value="settingsStore.editorSettings.refreshDdlOnOpen" @update:model-value="setRefreshDdlOnOpen" />
@@ -1,4 +1,7 @@
<script setup lang="ts">
import { applyDdlStoragePreference } from "@/lib/sql/ddlStorage";
import DdlStorageToggle from "@/components/objects/DdlStorageToggle.vue";
import { computed, createApp, nextTick, onActivated, onBeforeUnmount, ref, watch, type Component } from "vue";
import { RecycleScroller } from "vue-virtual-scroller";
import { useSqlHighlighter } from "@/composables/useSqlHighlighter";
@@ -246,7 +249,8 @@ const tableInfoTab = ref<TableInfoTab>("ddl");
const tableColumns = ref<ColumnInfo[]>([]);
const tableColumnsLoading = ref(false);
const tableColumnsLoaded = ref(false);
const tableDdlContent = ref("");
const rawTableDdlContent = ref("");
const tableDdlContent = computed(() => applyDdlStoragePreference(rawTableDdlContent.value, effectiveDatabaseType.value, settingsStore.editorSettings.excludeDdlStorage));
const tableDdlLoading = ref(false);
const tableDdlLoaded = ref(false);
const tableIndexes = ref<IndexInfo[]>([]);
@@ -1134,7 +1138,7 @@ async function openTableInfo(row: ObjectBrowserRow, initialTab?: TableInfoTab) {
sidePanelGuard.bump();
// Reset state
tableColumns.value = [];
tableDdlContent.value = "";
rawTableDdlContent.value = "";
tableIndexes.value = [];
tableForeignKeys.value = [];
tableTriggers.value = [];
@@ -1185,11 +1189,11 @@ async function fetchTableDdl(force = settingsStore.editorSettings.refreshDdlOnOp
const { ddl } = await loadObjectDdl(tableMetadataRequest(row), { force });
if (sidePanelGuard.isStale(epoch)) return;
const formatDialect = sqlFormatDialectForDbType(effectiveDatabaseType.value);
tableDdlContent.value = settingsStore.editorSettings.generateSqlQuoteIdentifiers ? ddl : omitDdlIdentifierQuotes(ddl, formatDialect);
rawTableDdlContent.value = settingsStore.editorSettings.generateSqlQuoteIdentifiers ? ddl : omitDdlIdentifierQuotes(ddl, formatDialect);
loadedSuccessfully = true;
} catch (e: any) {
if (sidePanelGuard.isStale(epoch)) return;
tableDdlContent.value = `-- Error: ${e?.message || e}`;
rawTableDdlContent.value = `-- Error: ${e?.message || e}`;
} finally {
if (sidePanelGuard.isFresh(epoch)) {
tableDdlLoaded.value = loadedSuccessfully;
@@ -1323,7 +1327,7 @@ async function refreshActiveTableInfo() {
sidePanelGuard.bump();
if (tableInfoTab.value === "ddl") {
tableDdlContent.value = "";
rawTableDdlContent.value = "";
tableDdlLoaded.value = false;
await fetchTableDdl(true);
} else if (tableInfoTab.value === "columns") {
@@ -2090,7 +2094,7 @@ async function exportStructure(row: ObjectBrowserRow) {
try {
const schema = row.schema || selectedSchema.value || props.database;
const ddl = await api.getTableDdl(props.connection.id, props.database, schema, row.name, tableDdlObjectType(row.type), props.catalog, true);
await saveFileContent(buildSingleDdlExportFileContent(ddl), `${row.name}.sql`, "SQL", "sql");
await saveFileContent(buildSingleDdlExportFileContent(applyDdlStoragePreference(ddl, effectiveDatabaseType.value, settingsStore.editorSettings.excludeDdlStorage)), `${row.name}.sql`, "SQL", "sql");
} catch (e: any) {
console.error("Export structure failed:", e);
}
@@ -3790,6 +3794,9 @@ function getObjectBrowserMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
<RefreshCw class="h-3.5 w-3.5" :class="{ 'animate-spin': activeTableInfoLoading }" />
</Button>
</div>
<div v-if="tableInfoTab === 'ddl' && effectiveDatabaseType === 'oceanbase-oracle'" class="border-b px-3 py-2">
<DdlStorageToggle :database-type="effectiveDatabaseType" :disabled="tableDdlLoading" />
</div>
<div v-if="tableInfoTab === 'columns'" class="flex-1 min-h-0 overflow-auto">
<div v-if="tableColumnsLoading" class="h-full flex items-center justify-center">
<Loader2 class="w-4 h-4 animate-spin text-muted-foreground" />
@@ -30,7 +30,7 @@ describe("ObjectBrowser table metadata refresh", () => {
expect(refreshTableInfo).toContain('sidePanelMode.value !== "table-info" || !sidePanelRow.value');
expect(refreshTableInfo).toContain("sidePanelGuard.bump();");
expect(refreshTableInfo).toMatch(/tableInfoTab\.value === "ddl"[\s\S]*?tableDdlContent\.value = "";[\s\S]*?await fetchTableDdl\(true\);/);
expect(refreshTableInfo).toMatch(/tableInfoTab\.value === "ddl"[\s\S]*?rawTableDdlContent\.value = "";[\s\S]*?await fetchTableDdl\(true\);/);
expect(refreshTableInfo).toMatch(/tableInfoTab\.value === "columns"[\s\S]*?tableColumns\.value = \[\];[\s\S]*?await fetchTableColumns\(true\);/);
expect(refreshTableInfo).toMatch(/tableInfoTab\.value === "indexes"[\s\S]*?tableIndexes\.value = \[\];[\s\S]*?await fetchTableIndexes\(true\);/);
expect(refreshTableInfo).toMatch(/tableInfoTab\.value === "foreignKeys"[\s\S]*?tableForeignKeys\.value = \[\];[\s\S]*?await fetchTableForeignKeys\(true\);/);