fix(query): invalidate persisted object metadata after DDL
This commit is contained in:
1 parent
c6ecd9808f
commit
5918414127
9 files changed
+689
-31
No files matched your search
@@ -2301,6 +2301,7 @@ async function revalidateCachedStructureMetadata(loadRequestId: number, scope: {
|
||||
const revalidationId = ++structureMetadataRevalidationId;
|
||||
const metadataRequest = { connectionId, database, schema, tableName, catalog };
|
||||
try {
|
||||
await store.ensureConnected(connectionId);
|
||||
// Force alone only clears this facet's own key; the web backend keeps its
|
||||
// own backend-columns/backend-comment entries under the same table prefix
|
||||
// and would serve them to the forced re-fetch. Drop the whole table scope
|
||||
@@ -3923,10 +3924,18 @@ onMounted(() => {
|
||||
void loadTableOwnerRoles();
|
||||
void loadMysqlTableEngine(props.draft?.mysqlTableEngine !== undefined);
|
||||
if (props.draft?.initialized) {
|
||||
void hydrateRestoredDraftFromDatabase().then(() => {
|
||||
// A clean persisted editor snapshot is not a live schema cache. After an
|
||||
// MCP DDL, restoring its loaded-facet flags would otherwise bypass the
|
||||
// invalidated backend cache entirely. Legacy/dirty drafts remain intact.
|
||||
const revalidateRestoredColumns = props.draft.dirty === false && !isCreateMode.value && loadedMetadataFacets.has("columns") && !(databaseType.value === "manticoresearch" && tableMetadataCapabilities.value.ddl);
|
||||
void hydrateRestoredDraftFromDatabase().then(async () => {
|
||||
applyInitialStructureTarget();
|
||||
void loadMysqlAutoIncrementCounter(true);
|
||||
void loadActiveTableStructureMetadataIfNeeded();
|
||||
await loadActiveTableStructureMetadataIfNeeded();
|
||||
if (revalidateRestoredColumns) {
|
||||
// The existing revalidation checks again for edits made while loading.
|
||||
void revalidateCachedStructureMetadata(structureLoadRequestId, { columns: true, tableComment: false }, undefined);
|
||||
}
|
||||
});
|
||||
} else if (isCreateMode.value) {
|
||||
markDraftHydratedAndSync();
|
||||
|
||||
Vendored
+62
-1
@@ -252,6 +252,8 @@ vi.mock("@/lib/backend/api", () => ({
|
||||
}));
|
||||
|
||||
import TableStructureEditor from "@/components/structure/TableStructureEditor.vue";
|
||||
import { createColumnDrafts } from "@/lib/table/tableStructureEditorState";
|
||||
import type { TableStructureEditorDraft } from "@/types/database";
|
||||
|
||||
const mountedApps: App[] = [];
|
||||
|
||||
@@ -272,13 +274,14 @@ async function settle() {
|
||||
}
|
||||
}
|
||||
|
||||
async function mountStructureEditor() {
|
||||
async function mountStructureEditor(draft?: TableStructureEditorDraft) {
|
||||
const root = document.createElement("div");
|
||||
document.body.append(root);
|
||||
const app = createApp(TableStructureEditor, {
|
||||
connectionId: mocks.connection.id,
|
||||
database: "test",
|
||||
tableName: "users",
|
||||
draft,
|
||||
});
|
||||
mountedApps.push(app);
|
||||
app.mount(root);
|
||||
@@ -404,3 +407,61 @@ describe("TableStructureEditor metadata revalidation (#8816)", () => {
|
||||
expect(root.textContent).toContain("email");
|
||||
});
|
||||
});
|
||||
|
||||
describe("restored structure snapshots after external DDL", () => {
|
||||
function restoredDraft(dirty: boolean | undefined): TableStructureEditorDraft {
|
||||
return {
|
||||
initialized: true,
|
||||
dirty,
|
||||
activeTab: "columns",
|
||||
newTableName: "",
|
||||
tableComment: "",
|
||||
originalTableComment: "",
|
||||
columns: createColumnDrafts(
|
||||
initialColumns.map((column) => ({ name: column.name, data_type: column.data_type, is_nullable: column.nullable, column_default: column.default_value, is_primary_key: false, extra: null, comment: column.comment })),
|
||||
"mysql",
|
||||
),
|
||||
indexes: [],
|
||||
foreignKeys: [],
|
||||
triggers: [],
|
||||
loadedMetadataFacets: ["columns", "comment"],
|
||||
};
|
||||
}
|
||||
|
||||
it("revalidates an explicitly clean restored snapshot", async () => {
|
||||
mocks.loadObjectMetadataFacet.mockImplementation(async (_request, facet) => ({ value: facet === "columns" ? rebuiltColumns : facet === "comment" ? "" : [], cacheStatus: "remote" }));
|
||||
const root = await mountStructureEditor(restoredDraft(false));
|
||||
await vi.waitFor(() => expect(Array.from(root.querySelectorAll<HTMLInputElement>("[data-column-name-input]")).map((input) => input.value)).toContain("email"));
|
||||
expect(columnsFacetCalls()).toHaveLength(1);
|
||||
});
|
||||
|
||||
it.each([true, undefined])("preserves restored edits when dirty is %s", async (dirty) => {
|
||||
mocks.loadObjectMetadataFacet.mockImplementation(async (_request, facet) => ({ value: facet === "columns" ? rebuiltColumns : facet === "comment" ? "" : [], cacheStatus: "remote" }));
|
||||
const draft = restoredDraft(dirty);
|
||||
draft.columns[0].name = "local_edit";
|
||||
const root = await mountStructureEditor(draft);
|
||||
await settle();
|
||||
expect(root.querySelector<HTMLInputElement>("[data-column-name-input]")?.value).toBe("local_edit");
|
||||
expect(columnsFacetCalls()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("preserves an edit started while restored snapshot validation is pending", async () => {
|
||||
let resolveColumns!: (value: unknown) => void;
|
||||
mocks.loadObjectMetadataFacet.mockImplementation((_request, facet) =>
|
||||
facet === "columns"
|
||||
? new Promise((resolve) => {
|
||||
resolveColumns = resolve;
|
||||
})
|
||||
: Promise.resolve({ value: facet === "comment" ? "" : [], cacheStatus: "remote" }),
|
||||
);
|
||||
const root = await mountStructureEditor(restoredDraft(false));
|
||||
await vi.waitFor(() => expect(resolveColumns).toBeDefined());
|
||||
const input = root.querySelector<HTMLInputElement>("[data-column-name-input]")!;
|
||||
input.value = "local_edit";
|
||||
input.dispatchEvent(new Event("input"));
|
||||
await settle();
|
||||
resolveColumns({ value: rebuiltColumns, cacheStatus: "remote" });
|
||||
await settle();
|
||||
expect(root.querySelector<HTMLInputElement>("[data-column-name-input]")?.value).toBe("local_edit");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user