fix(editor): avoid metadata wait for wildcard queries
This commit is contained in:
1 parent
e381aa4ee1
commit
a8592af97a
2 files changed
+85
-2
No files matched your search
@@ -131,6 +131,44 @@ describe("queryStore hidden primary key editing", () => {
|
|||||||
expect(tab.queryEditabilityReason).toBeUndefined();
|
expect(tab.queryEditabilityReason).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("starts a qualified MySQL star query before slow column metadata finishes", async () => {
|
||||||
|
const columnsGate = deferred<Awaited<ReturnType<typeof getColumns>>>();
|
||||||
|
getColumns.mockReturnValue(columnsGate.promise);
|
||||||
|
analyzeEditableQueryEditability.mockResolvedValue({
|
||||||
|
editable: true,
|
||||||
|
analysis: {
|
||||||
|
schema: undefined,
|
||||||
|
tableName: "sys_dept",
|
||||||
|
selectStar: true,
|
||||||
|
columns: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
executeMulti.mockResolvedValue([
|
||||||
|
{
|
||||||
|
columns: ["dept_id", "dept_name"],
|
||||||
|
rows: [[1, "Headquarters"]],
|
||||||
|
affected_rows: 0,
|
||||||
|
execution_time_ms: 12,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { useQueryStore } = await import("@/stores/queryStore");
|
||||||
|
const store = useQueryStore();
|
||||||
|
const tabId = store.createTab("mysql-1", "app", "Query");
|
||||||
|
|
||||||
|
const execution = store.executeTabSql(tabId, "SELECT sys_dept.* FROM sys_dept");
|
||||||
|
await vi.waitFor(() => expect(executeMulti).toHaveBeenCalled());
|
||||||
|
expect(executeMulti).toHaveBeenCalledWith("mysql-1", "app", "SELECT sys_dept.* FROM sys_dept", undefined, expect.any(String), expect.objectContaining({ timeoutSecs: 30 }));
|
||||||
|
|
||||||
|
columnsGate.resolve([
|
||||||
|
{ name: "dept_id", data_type: "bigint", is_nullable: false, column_default: null, is_primary_key: true, extra: null },
|
||||||
|
{ name: "dept_name", data_type: "varchar", is_nullable: true, column_default: null, is_primary_key: false, extra: null },
|
||||||
|
]);
|
||||||
|
await execution;
|
||||||
|
const tab = store.tabs.find((item) => item.id === tabId)!;
|
||||||
|
await vi.waitFor(() => expect(tab.tableMeta?.tableName).toBe("sys_dept"));
|
||||||
|
});
|
||||||
|
|
||||||
it("loads metadata from the connection default database when the query tab database is empty", async () => {
|
it("loads metadata from the connection default database when the query tab database is empty", async () => {
|
||||||
const { useQueryStore } = await import("@/stores/queryStore");
|
const { useQueryStore } = await import("@/stores/queryStore");
|
||||||
const store = useQueryStore();
|
const store = useQueryStore();
|
||||||
@@ -208,6 +246,45 @@ describe("queryStore hidden primary key editing", () => {
|
|||||||
expect(tab.queryEditabilityReason).toBeUndefined();
|
expect(tab.queryEditabilityReason).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("starts a MySQL JDBC star query before slow column metadata finishes", async () => {
|
||||||
|
const columnsGate = deferred<Awaited<ReturnType<typeof getColumns>>>();
|
||||||
|
getConnectionConfig.mockReturnValue({ id: "jdbc-1", name: "JDBC MySQL", db_type: "jdbc", connection_string: "jdbc:mysql://localhost:3306/app", database: "app", query_timeout_secs: 30 });
|
||||||
|
getColumns.mockReturnValue(columnsGate.promise);
|
||||||
|
analyzeEditableQueryEditability.mockResolvedValue({
|
||||||
|
editable: true,
|
||||||
|
analysis: {
|
||||||
|
schema: undefined,
|
||||||
|
tableName: "sys_dept",
|
||||||
|
selectStar: true,
|
||||||
|
columns: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
executeMulti.mockResolvedValue([
|
||||||
|
{
|
||||||
|
columns: ["dept_id", "dept_name"],
|
||||||
|
rows: [[1, "Headquarters"]],
|
||||||
|
affected_rows: 0,
|
||||||
|
execution_time_ms: 12,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { useQueryStore } = await import("@/stores/queryStore");
|
||||||
|
const store = useQueryStore();
|
||||||
|
const tabId = store.createTab("jdbc-1", "", "Query");
|
||||||
|
|
||||||
|
const execution = store.executeTabSql(tabId, "SELECT * FROM sys_dept");
|
||||||
|
await vi.waitFor(() => expect(executeMulti).toHaveBeenCalled());
|
||||||
|
expect(executeMulti).toHaveBeenCalledWith("jdbc-1", "", "SELECT * FROM sys_dept", undefined, expect.any(String), expect.objectContaining({ timeoutSecs: 30 }));
|
||||||
|
|
||||||
|
columnsGate.resolve([
|
||||||
|
{ name: "dept_id", data_type: "bigint", is_nullable: false, column_default: null, is_primary_key: true, extra: null },
|
||||||
|
{ name: "dept_name", data_type: "varchar", is_nullable: true, column_default: null, is_primary_key: false, extra: null },
|
||||||
|
]);
|
||||||
|
await execution;
|
||||||
|
const tab = store.tabs.find((item) => item.id === tabId)!;
|
||||||
|
await vi.waitFor(() => expect(tab.tableMeta?.tableName).toBe("sys_dept"));
|
||||||
|
});
|
||||||
|
|
||||||
it("keeps an explicitly selected database instead of falling back to the connection default", async () => {
|
it("keeps an explicitly selected database instead of falling back to the connection default", async () => {
|
||||||
const { useQueryStore } = await import("@/stores/queryStore");
|
const { useQueryStore } = await import("@/stores/queryStore");
|
||||||
const store = useQueryStore();
|
const store = useQueryStore();
|
||||||
|
|||||||
@@ -339,6 +339,10 @@ function editableQuerySources(analysis: EditableQueryInfo): EditableQuerySource[
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function projectsAllColumnsForSource(analysis: EditableQueryInfo, sourceKey: string): boolean {
|
||||||
|
return analysis.selectStar || analysis.columns.some((column) => column.star && (!column.sourceKey || column.sourceKey === sourceKey));
|
||||||
|
}
|
||||||
|
|
||||||
function cloneAnalysisForSource(analysis: EditableQueryInfo, source: EditableQuerySource): EditableQueryInfo {
|
function cloneAnalysisForSource(analysis: EditableQueryInfo, source: EditableQuerySource): EditableQueryInfo {
|
||||||
return {
|
return {
|
||||||
...analysis,
|
...analysis,
|
||||||
@@ -2729,6 +2733,9 @@ export const useQueryStore = defineStore("query", () => {
|
|||||||
const analysis = editability.analysis;
|
const analysis = editability.analysis;
|
||||||
const sources = editableQuerySources(analysis);
|
const sources = editableQuerySources(analysis);
|
||||||
if (sources.length !== 1 || analysis.distinct) return unchanged;
|
if (sources.length !== 1 || analysis.distinct) return unchanged;
|
||||||
|
// Whole-source projections already include declared primary keys. Only
|
||||||
|
// Oracle needs preflight metadata here to add ROWID for a keyless table.
|
||||||
|
if (databaseType !== "oracle" && projectsAllColumnsForSource(analysis, sources[0]!.key)) return unchanged;
|
||||||
|
|
||||||
const target = resolveEditableSourceMetadataTarget(tab, analysis, sources[0]!, conn, databaseType, executionDatabase);
|
const target = resolveEditableSourceMetadataTarget(tab, analysis, sources[0]!, conn, databaseType, executionDatabase);
|
||||||
const cached = getCachedTableMetadata(target.request);
|
const cached = getCachedTableMetadata(target.request);
|
||||||
@@ -2743,8 +2750,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||||||
});
|
});
|
||||||
void fullMetadataPromise.catch((error) => queryExecutionLog("warn", "metadata:table-prefetch:failed", { traceId, error, elapsed: elapsed() }));
|
void fullMetadataPromise.catch((error) => queryExecutionLog("warn", "metadata:table-prefetch:failed", { traceId, error, elapsed: elapsed() }));
|
||||||
const indexes = await loadTableIndexes(target.request);
|
const indexes = await loadTableIndexes(target.request);
|
||||||
const projectsAllColumns = target.analysis.selectStar || target.analysis.columns.some((column) => column.star && (!column.sourceKey || column.sourceKey === target.source.key));
|
if (primaryKeyIndex(indexes) && projectsAllColumnsForSource(target.analysis, target.source.key)) {
|
||||||
if (primaryKeyIndex(indexes) && projectsAllColumns) {
|
|
||||||
return unchanged;
|
return unchanged;
|
||||||
}
|
}
|
||||||
loaded = loadedEditableSourceFromMetadata(target, (await fullMetadataPromise).metadata);
|
loaded = loadedEditableSourceFromMetadata(target, (await fullMetadataPromise).metadata);
|
||||||
|
|||||||
Reference in new issue
Block a user