fix(oracle): lazy load table triggers

This commit is contained in:
t8y2 committed 2026-07-24 00:51:26 +08:00
1 parent 132ac8a2ec
commit 5600872565
4 files changed
+116 -25

No files matched your search

@@ -32,6 +32,7 @@ import { getMysqlDataTypeHelp } from "@/lib/table/mysqlDataTypeHelp";
import { getPostgresDataTypeHelp } from "@/lib/table/postgresDataTypeHelp";
import { getSqliteDataTypeHelp } from "@/lib/table/sqliteDataTypeHelp";
import { getTableMetadataCapabilities, firstStructureMetadataTab, isStructureMetadataTabSupported } from "@/lib/table/tableMetadataCapabilities";
import { shouldLoadTableStructureTriggers, TRIGGERS_ONLY_REFRESH_SCOPE, visibleTableStructureRefreshScope, type TableStructureRefreshScope } from "@/lib/table/tableStructureMetadataLoading";
import { canAddTableStructureColumn, getTableStructureCapabilities, hasLocalTableColumnOrderChange, isPhysicalTableColumnOrderChange, supportsLocalTableColumnReorder } from "@/lib/table/tableStructureCapabilities";
import { orderedColumnIndexes, uniqueDataGridColumnOrderKeys } from "@/lib/dataGrid/dataGridColumnOrder";
import { loadTableDataGridColumnOrder, notifyTableDataGridColumnOrderChanged, removeTableDataGridColumnOrder, saveTableDataGridColumnOrder, tableDataGridColumnOrderScopeKey } from "@/lib/dataGrid/dataGridColumnLayoutStorage";
@@ -172,24 +173,9 @@ const warnings = ref<string[]>([]);
const sqliteSchemaRevision = ref<string>();
const foreignKeys = ref<EditableStructureForeignKey[]>([]);
const triggers = ref<EditableStructureTrigger[]>([]);
const triggersLoaded = ref(false);
const secondaryMetadataLoading = computed(() => indexesLoading.value || foreignKeysLoading.value || triggersLoading.value);
interface StructureRefreshScope {
columns: boolean;
indexes: boolean;
foreignKeys: boolean;
triggers: boolean;
tableComment: boolean;
}
const FULL_STRUCTURE_REFRESH_SCOPE: StructureRefreshScope = {
columns: true,
indexes: true,
foreignKeys: true,
triggers: true,
tableComment: true,
};
function sameList(left: string[] | null | undefined, right: string[] | null | undefined): boolean {
const a = left ?? [];
const b = right ?? [];
@@ -251,7 +237,7 @@ function triggerChanged(trigger: EditableStructureTrigger): boolean {
return trigger.name !== original.name || trigger.timing !== original.timing || trigger.event !== original.event || !sameText(trigger.statement, original.statement);
}
function captureStructureRefreshScope(): StructureRefreshScope {
function captureStructureRefreshScope(): TableStructureRefreshScope {
return {
columns: columns.value.some(columnChanged),
indexes: indexes.value.some(indexChanged),
@@ -921,6 +907,7 @@ function createCurrentDraft(initialized = true): TableStructureEditorDraft {
indexes: cloneDraftValue(indexes.value),
foreignKeys: cloneDraftValue(foreignKeys.value),
triggers: cloneDraftValue(triggers.value),
triggersLoaded: triggersLoaded.value,
scrollPositions: cloneDraftValue(structureScrollPositions.value),
initialized,
};
@@ -945,6 +932,8 @@ function restoreDraft(draft: TableStructureEditorDraft) {
indexes.value = cloneDraftValue(draft.indexes || []);
foreignKeys.value = cloneDraftValue(draft.foreignKeys || []);
triggers.value = cloneDraftValue(draft.triggers || []);
// Drafts created before lazy trigger loading always contained live trigger metadata.
triggersLoaded.value = draft.triggersLoaded ?? true;
structureScrollPositions.value = cloneDraftValue(draft.scrollPositions || {});
restoringDraft = false;
draftHydrated = !needsColumnDraftMetadataHydration();
@@ -1195,6 +1184,7 @@ function resetState() {
sqliteSchemaRevision.value = undefined;
foreignKeys.value = [];
triggers.value = [];
triggersLoaded.value = false;
selectedColumnId.value = null;
ddlContent.value = "";
ddlFetched.value = false;
@@ -1213,10 +1203,14 @@ function resetState() {
async function reloadStructureFromDatabase() {
if (isCreateMode.value) return;
draftHydrated = false;
await loadStructure(false, FULL_STRUCTURE_REFRESH_SCOPE, true, { blockSecondaryMetadata: true });
if (activeTab.value !== "triggers") {
triggers.value = [];
triggersLoaded.value = false;
}
await loadStructure(false, visibleTableStructureRefreshScope(activeTab.value), true, { blockSecondaryMetadata: true });
}
function setSecondaryMetadataLoading(scope: StructureRefreshScope, value: boolean) {
function setSecondaryMetadataLoading(scope: TableStructureRefreshScope, value: boolean) {
if (scope.indexes && tableMetadataCapabilities.value.indexes) indexesLoading.value = value;
if (scope.foreignKeys && tableMetadataCapabilities.value.foreignKeys) foreignKeysLoading.value = value;
if (scope.triggers && tableMetadataCapabilities.value.triggers) triggersLoading.value = value;
@@ -1236,7 +1230,7 @@ async function fetchTableCommentValue(connectionId: string, database: string, sc
}
}
async function loadStructure(silent = false, scope: StructureRefreshScope = FULL_STRUCTURE_REFRESH_SCOPE, showErrors = true, options: { blockSecondaryMetadata?: boolean; preserveDraft?: boolean; damengLengthUnitsAfterSave?: ReadonlyMap<string, string> } = {}) {
async function loadStructure(silent = false, scope: TableStructureRefreshScope = visibleTableStructureRefreshScope(activeTab.value), showErrors = true, options: { blockSecondaryMetadata?: boolean; preserveDraft?: boolean; damengLengthUnitsAfterSave?: ReadonlyMap<string, string> } = {}) {
const connectionId = props.connectionId;
const database = props.database;
const catalog = props.catalog;
@@ -1289,7 +1283,10 @@ async function loadStructure(silent = false, scope: StructureRefreshScope = FULL
if (requestId !== structureLoadRequestId) return;
if (nextIndexes) indexes.value = createIndexDrafts(nextIndexes);
if (nextForeignKeys) foreignKeys.value = createForeignKeyDrafts(nextForeignKeys);
if (nextTriggers) triggers.value = createTriggerDrafts(nextTriggers);
if (nextTriggers) {
triggers.value = createTriggerDrafts(nextTriggers);
triggersLoaded.value = true;
}
};
secondaryMetadataScheduled = true;
@@ -1321,7 +1318,7 @@ async function loadStructure(silent = false, scope: StructureRefreshScope = FULL
}
}
async function refreshStructureAfterSave(scope: StructureRefreshScope, damengLengthUnitsAfterSave: ReadonlyMap<string, string>) {
async function refreshStructureAfterSave(scope: TableStructureRefreshScope, damengLengthUnitsAfterSave: ReadonlyMap<string, string>) {
try {
await loadStructure(true, scope, false, { blockSecondaryMetadata: true, damengLengthUnitsAfterSave });
} catch (e) {
@@ -2294,7 +2291,7 @@ function addItemForActiveTab(): boolean {
addForeignKey();
return true;
}
if (activeTab.value === "triggers" && canEditTriggers.value) {
if (activeTab.value === "triggers" && canEditTriggers.value && !triggersLoading.value) {
addTrigger();
return true;
}
@@ -2350,7 +2347,7 @@ onMounted(() => {
} else if (isCreateMode.value) {
markDraftHydratedAndSync();
} else {
void loadStructure(false, FULL_STRUCTURE_REFRESH_SCOPE, true, { blockSecondaryMetadata: true }).then(() => applyInitialStructureTarget());
void loadStructure(false, visibleTableStructureRefreshScope(activeTab.value), true, { blockSecondaryMetadata: true }).then(() => applyInitialStructureTarget());
}
});
@@ -2466,6 +2463,25 @@ watch(activeTab, () => {
syncDraftToParent();
});
async function loadTriggersIfNeeded() {
if (
!shouldLoadTableStructureTriggers({
activeTab: activeTab.value,
isCreateMode: isCreateMode.value,
supported: tableMetadataCapabilities.value.triggers,
loaded: triggersLoaded.value,
loading: triggersLoading.value,
structureLoading: loading.value,
})
)
return;
await loadStructure(true, TRIGGERS_ONLY_REFRESH_SCOPE, true, { blockSecondaryMetadata: true, preserveDraft: true });
}
watch([activeTab, loading], () => {
void loadTriggersIfNeeded();
});
watch(
columns,
(items) => {
@@ -2493,7 +2509,11 @@ watch(refreshVersion, (version, previous) => {
skipNextRefreshVersion = false;
return;
}
void loadStructure(true);
if (activeTab.value !== "triggers") {
triggers.value = [];
triggersLoaded.value = false;
}
void loadStructure(true, visibleTableStructureRefreshScope(activeTab.value));
});
watch(
@@ -0,0 +1,37 @@
import { describe, expect, it } from "vitest";
import { shouldLoadTableStructureTriggers, visibleTableStructureRefreshScope } from "@/lib/table/tableStructureMetadataLoading";
describe("table structure metadata loading", () => {
it("does not request triggers while opening the default columns tab", () => {
expect(visibleTableStructureRefreshScope("columns").triggers).toBe(false);
});
it("requests triggers when the structure editor opens on the trigger tab", () => {
expect(visibleTableStructureRefreshScope("triggers").triggers).toBe(true);
});
it("loads trigger metadata once when the trigger tab becomes visible", () => {
const base = {
activeTab: "triggers" as const,
isCreateMode: false,
supported: true,
loading: false,
structureLoading: false,
};
expect(shouldLoadTableStructureTriggers({ ...base, loaded: false })).toBe(true);
expect(shouldLoadTableStructureTriggers({ ...base, loaded: true })).toBe(false);
});
it("waits for the initial structure load and skips create mode", () => {
const base = {
activeTab: "triggers" as const,
supported: true,
loaded: false,
loading: false,
};
expect(shouldLoadTableStructureTriggers({ ...base, isCreateMode: false, structureLoading: true })).toBe(false);
expect(shouldLoadTableStructureTriggers({ ...base, isCreateMode: true, structureLoading: false })).toBe(false);
});
});
@@ -0,0 +1,33 @@
import type { TableInfoTab } from "@/types/database";
export interface TableStructureRefreshScope {
columns: boolean;
indexes: boolean;
foreignKeys: boolean;
triggers: boolean;
tableComment: boolean;
}
export function visibleTableStructureRefreshScope(activeTab: TableInfoTab): TableStructureRefreshScope {
return {
columns: true,
indexes: true,
foreignKeys: true,
// Trigger definitions can contain large source bodies, so defer them until
// the trigger editor is actually visible.
triggers: activeTab === "triggers",
tableComment: true,
};
}
export const TRIGGERS_ONLY_REFRESH_SCOPE: TableStructureRefreshScope = {
columns: false,
indexes: false,
foreignKeys: false,
triggers: true,
tableComment: false,
};
export function shouldLoadTableStructureTriggers(options: { activeTab: TableInfoTab; isCreateMode: boolean; supported: boolean; loaded: boolean; loading: boolean; structureLoading: boolean }): boolean {
return options.activeTab === "triggers" && !options.isCreateMode && options.supported && !options.loaded && !options.loading && !options.structureLoading;
}
+1
View File
@@ -754,6 +754,7 @@ export interface TableStructureEditorDraft {
indexes: import("@/lib/table/tableStructureEditorSql").EditableStructureIndex[];
foreignKeys: import("@/lib/table/tableStructureEditorSql").EditableStructureForeignKey[];
triggers: import("@/lib/table/tableStructureEditorSql").EditableStructureTrigger[];
triggersLoaded?: boolean;
scrollPositions?: Partial<Record<TableInfoTab, TableStructureEditorViewport>>;
initialized: boolean;
}