fix(oracle): reduce query metadata preflight delay
This commit is contained in:
parent
c31d403af3
commit
9410e9d906
|
|
@ -47,6 +47,7 @@ const tableMetadataCache = new MetadataResultCache<TableMetadata>({
|
|||
const tableMetadataCoordinator = new MetadataLoadCoordinator((event) => {
|
||||
console.debug("[DBX][metadata-load:table-coordinator]", event);
|
||||
});
|
||||
const tableIndexesLoads = new Map<string, { parts: ReturnType<typeof metadataScopeParts>; promise: Promise<IndexInfo[]>; expiresAt: number }>();
|
||||
|
||||
// 失效代数(按 scope key 隔离):跨越失效边界的旧加载完成后不得写缓存——
|
||||
// 结构变更后 force 拉到的新值可能被保存前启动、最后返回的在途加载回填覆盖。
|
||||
|
|
@ -114,6 +115,26 @@ export function tableMetadataToDataTabMeta(metadata: TableMetadata, schema = met
|
|||
};
|
||||
}
|
||||
|
||||
export async function loadTableIndexes(request: TableMetadataRequest): Promise<IndexInfo[]> {
|
||||
const scope = tableMetadataScope(request);
|
||||
const scopeKey = metadataScopeKey(scope);
|
||||
const cached = tableIndexesLoads.get(scopeKey);
|
||||
if (!request.force && cached && cached.expiresAt > Date.now()) return cached.promise;
|
||||
if (cached) tableIndexesLoads.delete(scopeKey);
|
||||
while (tableIndexesLoads.size >= TABLE_METADATA_CACHE_MAX_ENTRIES) {
|
||||
const oldestKey = tableIndexesLoads.keys().next().value;
|
||||
if (!oldestKey) break;
|
||||
tableIndexesLoads.delete(oldestKey);
|
||||
}
|
||||
const promise = api.listIndexes(request.connectionId, request.database, request.schema ?? "", request.tableName, request.catalog);
|
||||
const entry = { parts: metadataScopeParts(scope), promise, expiresAt: Date.now() + TABLE_METADATA_CACHE_TTL_MS };
|
||||
tableIndexesLoads.set(scopeKey, entry);
|
||||
void promise.catch(() => {
|
||||
if (tableIndexesLoads.get(scopeKey) === entry) tableIndexesLoads.delete(scopeKey);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
export async function loadTableMetadata(request: TableMetadataRequest): Promise<TableMetadataLoadResult> {
|
||||
const scope = tableMetadataScope(request);
|
||||
const trace = createMetadataLoadTrace(scope);
|
||||
|
|
@ -138,8 +159,12 @@ export async function loadTableMetadata(request: TableMetadataRequest): Promise<
|
|||
metadata = await tableMetadataCoordinator.run(
|
||||
scope,
|
||||
async () => {
|
||||
const columns = await api.getColumns(request.connectionId, request.database, request.schema ?? "", request.tableName, request.catalog);
|
||||
const indexes = columns.length > 0 ? await api.listIndexes(request.connectionId, request.database, request.schema ?? "", request.tableName, request.catalog).catch((): IndexInfo[] => []) : [];
|
||||
// Column discovery can be especially slow on Oracle. Start row-identity
|
||||
// discovery independently so query preflight can reuse it without waiting.
|
||||
const columnsPromise = api.getColumns(request.connectionId, request.database, request.schema ?? "", request.tableName, request.catalog);
|
||||
const indexesPromise = loadTableIndexes(request).catch((): IndexInfo[] => []);
|
||||
const columns = await columnsPromise;
|
||||
const indexes = columns.length > 0 ? await indexesPromise : [];
|
||||
const primaryKeys = editableRowIdentifierColumns(request.databaseType as DatabaseType, columns, indexes, request.tableType);
|
||||
return {
|
||||
schema: request.schema || undefined,
|
||||
|
|
@ -182,6 +207,9 @@ export function invalidateTableMetadataCache(match: MetadataCacheInvalidation):
|
|||
bumpTableMetadataInvalidationStamp(scopeKey);
|
||||
tableMetadataCoordinator.clear(scopeKey);
|
||||
}
|
||||
for (const [scopeKey, entry] of tableIndexesLoads) {
|
||||
if (matches(entry.parts)) tableIndexesLoads.delete(scopeKey);
|
||||
}
|
||||
return tableMetadataCache.invalidate(match);
|
||||
}
|
||||
|
||||
|
|
@ -190,5 +218,6 @@ export function clearTableMetadataCache(): void {
|
|||
bumpTableMetadataInvalidationStamp(scopeKey);
|
||||
}
|
||||
tableMetadataCoordinator.clear();
|
||||
tableIndexesLoads.clear();
|
||||
tableMetadataCache.clear();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,14 @@ const editorSettings = {
|
|||
autoCalculateTotalRows: false,
|
||||
};
|
||||
|
||||
function deferred<T>() {
|
||||
let resolve!: (value: T) => void;
|
||||
const promise = new Promise<T>((done) => {
|
||||
resolve = done;
|
||||
});
|
||||
return { promise, resolve };
|
||||
}
|
||||
|
||||
vi.mock("@/lib/backend/api", () => ({
|
||||
analyzeEditableQueryEditability,
|
||||
buildDataGridCountSql,
|
||||
|
|
@ -291,11 +299,50 @@ describe("queryStore hidden primary key editing", () => {
|
|||
await store.executeTabSql(tabId, "SELECT * FROM aa");
|
||||
|
||||
expect(getColumns).toHaveBeenCalledWith("oracle-1", "ORCL", "", "AA", undefined);
|
||||
expect(listIndexes).not.toHaveBeenCalled();
|
||||
expect(listIndexes).toHaveBeenCalledWith("oracle-1", "ORCL", "", "AA", undefined);
|
||||
expect(listObjects).not.toHaveBeenCalled();
|
||||
expect(executeMulti).toHaveBeenCalledWith("oracle-1", "ORCL", "SELECT * FROM aa", undefined, expect.any(String), expect.objectContaining({ timeoutSecs: 30 }));
|
||||
});
|
||||
|
||||
it("starts an Oracle primary-key star query before slow column metadata finishes", async () => {
|
||||
const columnsGate = deferred<Awaited<ReturnType<typeof getColumns>>>();
|
||||
getConnectionConfig.mockReturnValue({ id: "oracle-1", name: "Oracle", db_type: "oracle", database: "ORCL", query_timeout_secs: 30 });
|
||||
getColumns.mockReturnValue(columnsGate.promise);
|
||||
listIndexes.mockResolvedValue([{ name: "PK_WIDE_TABLE", columns: ["ID"], is_unique: true, is_primary: true }]);
|
||||
analyzeEditableQueryEditability.mockImplementation(async () => ({
|
||||
editable: true,
|
||||
analysis: {
|
||||
schema: "APP",
|
||||
tableName: "WIDE_TABLE",
|
||||
tableAlias: "t",
|
||||
selectStar: true,
|
||||
columns: [],
|
||||
},
|
||||
}));
|
||||
executeMulti.mockResolvedValue([
|
||||
{
|
||||
columns: ["ID", "NAME"],
|
||||
rows: [[1, "Alice"]],
|
||||
affected_rows: 0,
|
||||
execution_time_ms: 312,
|
||||
},
|
||||
]);
|
||||
|
||||
const { useQueryStore } = await import("@/stores/queryStore");
|
||||
const store = useQueryStore();
|
||||
const tabId = store.createTab("oracle-1", "ORCL", "Query");
|
||||
|
||||
const execution = store.executeTabSql(tabId, "SELECT t.* FROM APP.WIDE_TABLE t");
|
||||
await vi.waitFor(() => expect(executeMulti).toHaveBeenCalled());
|
||||
expect(executeMulti).toHaveBeenCalledWith("oracle-1", "ORCL", "SELECT t.* FROM APP.WIDE_TABLE t", undefined, expect.any(String), expect.objectContaining({ timeoutSecs: 30 }));
|
||||
|
||||
columnsGate.resolve([
|
||||
{ name: "ID", data_type: "NUMBER", is_nullable: false, column_default: null, is_primary_key: true, extra: null },
|
||||
{ name: "NAME", data_type: "VARCHAR2(100)", is_nullable: true, column_default: null, is_primary_key: false, extra: null },
|
||||
]);
|
||||
await execution;
|
||||
});
|
||||
|
||||
it("keeps a keyless Oracle query editable when its WHERE clause reads another table", async () => {
|
||||
getConnectionConfig.mockReturnValue({ id: "oracle-1", name: "Oracle", db_type: "oracle", database: "ORCL", query_timeout_secs: 30 });
|
||||
getColumns.mockResolvedValue([
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { defineStore } from "pinia";
|
|||
import { uuid } from "@/lib/common/utils";
|
||||
import { computed, markRaw, onScopeDispose, ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import type { ConnectionConfig, DatabaseType, ObjectBrowserViewport, QueryResult, QueryTab, TableInfoTab, TableStructureEditorTarget } from "@/types/database";
|
||||
import type { ConnectionConfig, DatabaseType, IndexInfo, ObjectBrowserViewport, QueryResult, QueryTab, TableInfoTab, TableStructureEditorTarget } from "@/types/database";
|
||||
import { orderPinnedFirst } from "@/lib/app/pinnedItems";
|
||||
import { canCancelQueryExecution } from "@/lib/sql/queryExecutionState";
|
||||
import { buildExplainSql, parseExplainResult, parseDamengExplainText, parseOracleExplainText, sqlServerExplainResult, type BuildExplainSqlResult } from "@/lib/diagram/explainPlan";
|
||||
|
|
@ -36,7 +36,7 @@ import { TABLE_DATA_EXPORT_PAGE_SIZE } from "@/lib/table/tableDataExport";
|
|||
import { tableMetaForDataTab } from "@/lib/table/tableDataTabMeta";
|
||||
import { dataTabExecutionDatabase } from "@/lib/table/dataTabExecutionDatabase";
|
||||
import { tableOpenPageLimit } from "@/lib/table/tableOpenPageLimit";
|
||||
import { loadTableMetadata } from "@/lib/metadata/tableMetadataCache";
|
||||
import { getCachedTableMetadata, loadTableIndexes, loadTableMetadata, type TableMetadataRequest } from "@/lib/metadata/tableMetadataCache";
|
||||
import { buildTableSelectSql, quoteTableDataIdentifier } from "@/lib/table/tableSelectSql";
|
||||
import { connectionQueryExecutionSchema, connectionUsesDatabaseObjectTreeMode, effectiveDatabaseTypeForConnection, metadataSchemaForConnection } from "@/lib/database/jdbcDialect";
|
||||
import { frontendQueryTimeoutSecsForSql, queryTimeoutSecsForConnection } from "@/lib/sql/queryTimeout";
|
||||
|
|
@ -2420,6 +2420,12 @@ export const useQueryStore = defineStore("query", () => {
|
|||
tableMeta: NonNullable<QueryTab["tableMeta"]>;
|
||||
};
|
||||
|
||||
type EditableSourceMetadataTarget = {
|
||||
source: EditableQuerySource;
|
||||
analysis: EditableQueryInfo;
|
||||
request: TableMetadataRequest;
|
||||
};
|
||||
|
||||
interface EditableQueryExecutionPreparation {
|
||||
sql: string;
|
||||
metadataSql: string;
|
||||
|
|
@ -2434,7 +2440,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
tab.tableMeta = patch.tableMeta;
|
||||
}
|
||||
|
||||
async function loadEditableQuerySource(tab: QueryTab, analysis: EditableQueryInfo, source: EditableQuerySource, conn: ConnectionConfig | undefined, dbType: string, executionDatabase: string, traceId?: string, elapsed?: () => string): Promise<LoadedEditableSource> {
|
||||
function resolveEditableSourceMetadataTarget(tab: QueryTab, analysis: EditableQueryInfo, source: EditableQuerySource, conn: ConnectionConfig | undefined, dbType: string, executionDatabase: string): EditableSourceMetadataTarget {
|
||||
// Metadata must resolve in the same namespace as the query execution. An
|
||||
// empty query-tab database still executes in the connection's default DB,
|
||||
// while database-tree dialects may override it with a qualified source.
|
||||
|
|
@ -2458,22 +2464,49 @@ export const useQueryStore = defineStore("query", () => {
|
|||
tableName: metadataTableName,
|
||||
};
|
||||
const knownTableType = tab.tableMeta?.tableName.toLowerCase() === metadataTableName.toLowerCase() && normalizeOptionalSchema(tab.tableMeta.schema) === normalizeOptionalSchema(metadataSchema) ? tab.tableMeta.tableType : undefined;
|
||||
return {
|
||||
source: metadataSource,
|
||||
analysis: normalizeOracleLikeQueryAnalysis(dbType, cloneAnalysisForSource(analysis, metadataSource), metadataSchema || undefined, metadataTableName),
|
||||
request: {
|
||||
connectionId: tab.connectionId!,
|
||||
database: metadataDatabase,
|
||||
schema: metadataSchema,
|
||||
tableName: metadataTableName,
|
||||
tableType: knownTableType,
|
||||
databaseType: dbType,
|
||||
driverProfile: conn?.driver_profile || conn?.db_type,
|
||||
catalog: metadataCatalog,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function loadedEditableSourceFromMetadata(target: EditableSourceMetadataTarget, metadata: Awaited<ReturnType<typeof loadTableMetadata>>["metadata"]): LoadedEditableSource {
|
||||
return {
|
||||
source: target.source,
|
||||
analysis: target.analysis,
|
||||
tableMeta: {
|
||||
catalog: target.request.catalog,
|
||||
database: target.request.database,
|
||||
schema: target.request.schema || undefined,
|
||||
tableName: target.request.tableName,
|
||||
tableType: metadata.tableType,
|
||||
columns: metadata.columns,
|
||||
primaryKeys: metadata.primaryKeys,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function loadEditableQuerySource(tab: QueryTab, analysis: EditableQueryInfo, source: EditableQuerySource, conn: ConnectionConfig | undefined, dbType: string, executionDatabase: string, traceId?: string, elapsed?: () => string): Promise<LoadedEditableSource> {
|
||||
const target = resolveEditableSourceMetadataTarget(tab, analysis, source, conn, dbType, executionDatabase);
|
||||
queryExecutionLog("info", "metadata:table:start", {
|
||||
traceId,
|
||||
schema: metadataSchema,
|
||||
table: metadataTableName,
|
||||
schema: target.request.schema,
|
||||
table: target.request.tableName,
|
||||
alias: source.alias,
|
||||
elapsed: elapsed?.(),
|
||||
});
|
||||
const loadedMetadata = await loadTableMetadata({
|
||||
connectionId: tab.connectionId,
|
||||
database: metadataDatabase,
|
||||
schema: metadataSchema,
|
||||
tableName: metadataTableName,
|
||||
tableType: knownTableType,
|
||||
databaseType: dbType,
|
||||
driverProfile: conn?.driver_profile || conn?.db_type,
|
||||
catalog: metadataCatalog,
|
||||
...target.request,
|
||||
traceLogger: (event) => queryExecutionLog("debug", "metadata:table-trace", { sourceTraceId: traceId, ...event }),
|
||||
});
|
||||
const columns = loadedMetadata.metadata.columns;
|
||||
|
|
@ -2486,19 +2519,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
ageMs: Math.round(loadedMetadata.ageMs),
|
||||
elapsed: elapsed?.(),
|
||||
});
|
||||
return {
|
||||
source: metadataSource,
|
||||
analysis: normalizeOracleLikeQueryAnalysis(dbType, cloneAnalysisForSource(analysis, metadataSource), metadataSchema || undefined, metadataTableName),
|
||||
tableMeta: {
|
||||
catalog: metadataCatalog,
|
||||
database: metadataDatabase,
|
||||
schema: metadataSchema || undefined,
|
||||
tableName: metadataTableName,
|
||||
tableType: loadedMetadata.metadata.tableType,
|
||||
columns,
|
||||
primaryKeys,
|
||||
},
|
||||
};
|
||||
return loadedEditableSourceFromMetadata(target, loadedMetadata.metadata);
|
||||
}
|
||||
|
||||
function missingPrimaryKeysForSource(primaryKeys: string[], analysis: EditableQueryInfo, sourceKey: string): string[] {
|
||||
|
|
@ -2515,6 +2536,36 @@ export const useQueryStore = defineStore("query", () => {
|
|||
return matching?.object_type.trim().toUpperCase() === "TABLE";
|
||||
}
|
||||
|
||||
function primaryKeyIndex(indexes: IndexInfo[]): IndexInfo | undefined {
|
||||
return indexes.find((index) => !index.filter && index.columns.length > 0 && index.is_primary);
|
||||
}
|
||||
|
||||
function buildHiddenPrimaryKeyPreparation(sql: string, databaseType: DatabaseType, loaded: LoadedEditableSource, primaryKeys: string[], declaredPrimaryKeys: string[], traceId: string, elapsed: () => string): EditableQueryExecutionPreparation {
|
||||
const unchanged = { sql, metadataSql: sql, hiddenPrimaryKeys: [] };
|
||||
const metadataAnalysis = expandStarProjectionColumnsForSource(bindColumnsForSource(databaseType, loaded.analysis, loaded.source, loaded.tableMeta.columns), loaded.source, loaded.tableMeta.columns);
|
||||
const missingPrimaryKeys = declaredPrimaryKeys.length === 0 ? primaryKeys : missingPrimaryKeysForSource(primaryKeys, metadataAnalysis, loaded.source.key);
|
||||
if (missingPrimaryKeys.length === 0) return unchanged;
|
||||
const primaryKeySet = new Set(primaryKeys);
|
||||
const hasWritableProjection = metadataAnalysis.selectStar ? loaded.tableMeta.columns.some((column) => !primaryKeySet.has(column.name)) : metadataAnalysis.columns.some((column) => column.sourceName && column.sourceKey === loaded.source.key && !primaryKeySet.has(column.sourceName));
|
||||
if (!hasWritableProjection) return unchanged;
|
||||
|
||||
const rewritten = buildQueryWithHiddenPrimaryKeys({
|
||||
sql,
|
||||
databaseType,
|
||||
primaryKeys: missingPrimaryKeys,
|
||||
existingResultNames: metadataAnalysis.selectStar ? loaded.tableMeta.columns.map((column) => column.name) : metadataAnalysis.columns.map((column) => column.resultName),
|
||||
sourceExpressions: databaseType === "oracle" && missingPrimaryKeys.includes(DBX_ROWID_COLUMN) ? { [DBX_ROWID_COLUMN]: "ROWIDTOCHAR(ROWID)" } : undefined,
|
||||
});
|
||||
if (!rewritten) return unchanged;
|
||||
queryExecutionLog("info", "hidden-primary-keys", {
|
||||
traceId,
|
||||
table: loaded.tableMeta.tableName,
|
||||
keyCount: rewritten.projections.length,
|
||||
elapsed: elapsed(),
|
||||
});
|
||||
return { sql: rewritten.sql, metadataSql: rewritten.sql, hiddenPrimaryKeys: rewritten.projections };
|
||||
}
|
||||
|
||||
async function prepareEditableQueryExecution(tab: QueryTab, sql: string, conn: ConnectionConfig | undefined, databaseType: DatabaseType | undefined, executionDatabase: string, traceId: string, elapsed: () => string): Promise<EditableQueryExecutionPreparation> {
|
||||
const unchanged = { sql, metadataSql: sql, hiddenPrimaryKeys: [] };
|
||||
if (!databaseType || !HIDDEN_QUERY_KEY_DATABASE_TYPES.has(databaseType) || !tab.connectionId) return unchanged;
|
||||
|
|
@ -2526,38 +2577,36 @@ export const useQueryStore = defineStore("query", () => {
|
|||
const sources = editableQuerySources(analysis);
|
||||
if (sources.length !== 1 || analysis.distinct) return unchanged;
|
||||
|
||||
const loaded = await loadEditableQuerySource(tab, analysis, sources[0]!, conn, databaseType, executionDatabase, traceId, elapsed);
|
||||
const target = resolveEditableSourceMetadataTarget(tab, analysis, sources[0]!, conn, databaseType, executionDatabase);
|
||||
const cached = getCachedTableMetadata(target.request);
|
||||
let loaded = cached ? loadedEditableSourceFromMetadata(target, cached.metadata) : undefined;
|
||||
if (!cached && databaseType === "oracle") {
|
||||
// Oracle column discovery can be slow. A star projection over a table
|
||||
// with a declared primary key already returns the complete row identity,
|
||||
// so SQL can start while the full metadata needed for editing loads.
|
||||
const fullMetadataPromise = loadTableMetadata({
|
||||
...target.request,
|
||||
traceLogger: (event) => queryExecutionLog("debug", "metadata:table-trace", { sourceTraceId: traceId, ...event }),
|
||||
});
|
||||
void fullMetadataPromise.catch((error) => queryExecutionLog("warn", "metadata:table-prefetch:failed", { traceId, error, elapsed: elapsed() }));
|
||||
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) && projectsAllColumns) {
|
||||
return unchanged;
|
||||
}
|
||||
loaded = loadedEditableSourceFromMetadata(target, (await fullMetadataPromise).metadata);
|
||||
}
|
||||
|
||||
loaded ??= await loadEditableQuerySource(tab, analysis, sources[0]!, conn, databaseType, executionDatabase, traceId, elapsed);
|
||||
if (loaded.tableMeta.columns.length === 0) return unchanged;
|
||||
if (loaded.tableMeta.tableType?.toUpperCase().includes("VIEW")) return unchanged;
|
||||
const metadataAnalysis = expandStarProjectionColumnsForSource(bindColumnsForSource(databaseType, loaded.analysis, loaded.source, loaded.tableMeta.columns), loaded.source, loaded.tableMeta.columns);
|
||||
const declaredPrimaryKeys = loaded.tableMeta.columns.filter((column) => column.is_primary_key).map((column) => column.name);
|
||||
// Oracle base tables without declared keys use the same ROWID identity as
|
||||
// table-data tabs. Confirm the object is a base table because selecting
|
||||
// ROWID from a view can fail with ORA-01445.
|
||||
if (databaseType === "oracle" && declaredPrimaryKeys.length === 0 && !(await oracleRowIdIsSafeForQuery(tab, loaded))) return unchanged;
|
||||
const primaryKeys = editablePrimaryKeys(databaseType, loaded.tableMeta.columns, loaded.tableMeta.tableType);
|
||||
|
||||
const missingPrimaryKeys = declaredPrimaryKeys.length === 0 ? primaryKeys : missingPrimaryKeysForSource(primaryKeys, metadataAnalysis, loaded.source.key);
|
||||
if (missingPrimaryKeys.length === 0) return unchanged;
|
||||
const primaryKeySet = new Set(primaryKeys);
|
||||
const hasWritableProjection = metadataAnalysis.selectStar ? loaded.tableMeta.columns.some((column) => !primaryKeySet.has(column.name)) : metadataAnalysis.columns.some((column) => column.sourceName && column.sourceKey === loaded.source.key && !primaryKeySet.has(column.sourceName));
|
||||
if (!hasWritableProjection) return unchanged;
|
||||
|
||||
const rewritten = buildQueryWithHiddenPrimaryKeys({
|
||||
sql,
|
||||
databaseType,
|
||||
primaryKeys: missingPrimaryKeys,
|
||||
existingResultNames: metadataAnalysis.selectStar ? loaded.tableMeta.columns.map((column) => column.name) : metadataAnalysis.columns.map((column) => column.resultName),
|
||||
sourceExpressions: databaseType === "oracle" && missingPrimaryKeys.includes(DBX_ROWID_COLUMN) ? { [DBX_ROWID_COLUMN]: "ROWIDTOCHAR(ROWID)" } : undefined,
|
||||
});
|
||||
if (!rewritten) return unchanged;
|
||||
queryExecutionLog("info", "hidden-primary-keys", {
|
||||
traceId,
|
||||
table: loaded.tableMeta.tableName,
|
||||
keyCount: rewritten.projections.length,
|
||||
elapsed: elapsed(),
|
||||
});
|
||||
return { sql: rewritten.sql, metadataSql: rewritten.sql, hiddenPrimaryKeys: rewritten.projections };
|
||||
return buildHiddenPrimaryKeyPreparation(sql, databaseType, loaded, primaryKeys, declaredPrimaryKeys, traceId, elapsed);
|
||||
} catch (error) {
|
||||
// Metadata enrichment is optional. Query execution must retain its prior
|
||||
// behavior when metadata is unavailable or the SQL cannot be rewritten.
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ test("table metadata loader deduplicates equivalent in-flight requests and cache
|
|||
};
|
||||
const first = loadTableMetadata(request);
|
||||
const second = loadTableMetadata(request);
|
||||
await Promise.resolve();
|
||||
await vi.waitFor(() => assert.equal(listIndexes.mock.calls.length, 1));
|
||||
assert.equal(getColumns.mock.calls.length, 1);
|
||||
|
||||
columnsGate.resolve([
|
||||
|
|
|
|||
Loading…
Reference in New Issue