fix(cache): avoid repeated copies when encoding query results

Refs: #9755
This commit is contained in:
t8y2 committed 2026-09-22 17:25:22 +08:00
1 parent 3c0cce8ce2
commit e1fc28ab86
2 files changed
+137 -17

No files matched your search

@@ -0,0 +1,110 @@
import { decode } from "@msgpack/msgpack";
import { describe, expect, it, vi } from "vitest";
import { decodeTabResultSnapshot, encodeTabResultSnapshot } from "@/lib/tabs/tabResultCache";
import type { QueryResult } from "@/types/database";
function createResult(rowCount: number): QueryResult {
return {
columns: ["id", "name", "enabled"],
rows: Array.from({ length: rowCount }, (_, index) => [index, `row-${index}`, index % 2 === 0]),
affected_rows: 0,
execution_time_ms: 1,
};
}
describe("result cache serialization work", () => {
it.each([
{ columns: [], rows: [] },
{ columns: [], rows: [[]] },
{ columns: ["first", "second"], rows: [[1], []] },
])("preserves empty and ragged result shapes: %j", ({ columns, rows }) => {
const encoded = encodeTabResultSnapshot({ result: { columns, rows, affected_rows: 0, execution_time_ms: 0 }, cachedAt: 1 });
expect(decodeTabResultSnapshot(encoded)?.result?.rows).toEqual(rows.map((row) => columns.map((_, columnIndex) => row[columnIndex] ?? null)));
});
it("does not repeatedly copy column vectors while cleaning optional metadata", () => {
const rowCount = 1000;
const result = createResult(rowCount);
let fullLengthMaps = 0;
const originalMap = Array.prototype.map;
const mapSpy = vi.spyOn(Array.prototype, "map").mockImplementation(function (this: unknown[], callback, thisArg) {
if (this.length === rowCount) fullLengthMaps += 1;
return originalMap.call(this, callback, thisArg);
});
let encoded: Uint8Array;
try {
encoded = encodeTabResultSnapshot({ result, cachedAt: 1 });
} finally {
mapSpy.mockRestore();
}
expect(fullLengthMaps).toBeLessThanOrEqual(result.columns.length);
expect(decodeTabResultSnapshot(encoded)?.result?.rows).toEqual(result.rows);
});
it("reads each source row once when converting a wide result to columns", () => {
const result = createResult(100);
let rowReads = 0;
result.rows = new Proxy(result.rows, {
get(target, property, receiver) {
if (typeof property === "string" && /^\d+$/.test(property)) rowReads += 1;
return Reflect.get(target, property, receiver);
},
});
const encoded = encodeTabResultSnapshot({ result, cachedAt: 1 });
expect(rowReads).toBe(result.rows.length);
expect(decodeTabResultSnapshot(encoded)?.result?.rows).toEqual(createResult(100).rows);
});
it("preserves the version-1 payload and metadata cleanup for results and saved runs", () => {
const result = createResult(2);
result.rows = [
[1, "", false],
[2, null, true],
];
result.session_id = "live-session";
result.mongo_documents = [{ retained: null, omitted: undefined, nested: { empty: undefined, values: [null, undefined, 0] }, date: new Date(0) }];
const encoded = encodeTabResultSnapshot({
result,
results: [result],
resultRuns: [{ id: "run-1", title: "Run 1", sequence: 1, sql: "select 1", createdAt: 1, result, results: [result] }],
cachedAt: 1,
});
const envelope = decode(encoded) as any;
const expectedResult = {
columns: ["id", "name", "enabled"],
columnValues: [
[1, 2],
["", null],
[false, true],
],
rowCount: 2,
mongo_documents: [{ retained: null, nested: { values: [null, null, 0] }, date: {} }],
affected_rows: 0,
execution_time_ms: 1,
};
expect(envelope).toEqual({
magic: "DBX_TAB_RESULT_CACHE",
version: 1,
codec: "msgpack-columnar",
cachedAt: 1,
rowCount: 2,
columnCount: 3,
payload: {
result: expectedResult,
results: [expectedResult],
resultRuns: [{ id: "run-1", title: "Run 1", sequence: 1, sql: "select 1", createdAt: 1, result: expectedResult, results: [expectedResult] }],
cachedAt: 1,
},
});
expect(result.rows).toEqual([
[1, "", false],
[2, null, true],
]);
expect(result.mongo_documents[0]).toHaveProperty("omitted", undefined);
expect(result.session_id).toBe("live-session");
});
});
+27 -17
View File
@@ -388,8 +388,19 @@ function stripResultRunSessionIds(resultRuns: QueryTab["resultRuns"]): QueryTab[
function toColumnarResult(result: QueryResult | undefined): ColumnarQueryResult | undefined {
if (!result) return undefined;
const columnValues = result.columns.map((_, colIndex) => result.rows.map((row) => row[colIndex] ?? null));
return removeUndefinedFields({
const rowCount = result.rows.length;
const columnValues = result.columns.map(() => {
const values: CellValue[] = [];
values.length = rowCount;
return values;
});
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
const row = result.rows[rowIndex];
for (let columnIndex = 0; columnIndex < columnValues.length; columnIndex++) {
columnValues[columnIndex][rowIndex] = row?.[columnIndex] ?? null;
}
}
const metadata = removeUndefinedFields({
columns: [...result.columns],
execution_error: result.execution_error,
statement_index: result.statement_index,
@@ -398,8 +409,7 @@ function toColumnarResult(result: QueryResult | undefined): ColumnarQueryResult
spatial_columns: result.spatial_columns?.map((entry) => ({ column_index: entry.column_index, srid: entry.srid })),
spatial_values: result.spatial_values?.map((row) => [...row]),
large_value_cells: result.large_value_cells?.map((cell) => ({ ...cell })),
columnValues,
rowCount: result.rows.length,
rowCount,
mongo_documents: result.mongo_documents ? clonePlain(result.mongo_documents) : undefined,
mongo_copy_documents: result.mongo_copy_documents ? clonePlain(result.mongo_copy_documents) : undefined,
affected_rows: result.affected_rows,
@@ -413,6 +423,7 @@ function toColumnarResult(result: QueryResult | undefined): ColumnarQueryResult
sourceFrom: result.sourceFrom,
sourceTo: result.sourceTo,
});
return { ...metadata, columnValues };
}
function fromColumnarResult(result: ColumnarQueryResult | undefined): QueryResult | undefined {
@@ -445,18 +456,17 @@ function fromColumnarResult(result: ColumnarQueryResult | undefined): QueryResul
}
function snapshotToPayload(snapshot: TabResultSnapshot): TabResultSnapshotPayload {
return removeUndefinedFields({
...snapshot,
result: toColumnarResult(snapshot.result),
results: snapshot.results?.map((result) => toColumnarResult(result)!),
resultRuns: snapshot.resultRuns?.map((run) =>
removeUndefinedFields({
...run,
result: toColumnarResult(run.result),
results: run.results?.map((result) => toColumnarResult(result)!),
}),
),
});
const { result, results, resultRuns, ...metadata } = snapshot;
return {
...removeUndefinedFields(metadata),
result: toColumnarResult(result),
results: results?.map((result) => toColumnarResult(result)!),
resultRuns: resultRuns?.map(({ result, results, ...run }) => ({
...removeUndefinedFields(run),
result: toColumnarResult(result),
results: results?.map((result) => toColumnarResult(result)!),
})),
};
}
function payloadToSnapshot(payload: TabResultSnapshotPayload): TabResultSnapshot {
@@ -725,7 +735,7 @@ export function encodeTabResultSnapshot(snapshot: TabResultSnapshot): Uint8Array
columnCount: stats.columnCount,
payload: snapshotToPayload(snapshot),
};
return encode(removeUndefinedFields(envelope));
return encode(envelope, { ignoreUndefined: true });
}
export function decodeTabResultSnapshot(bytes: Uint8Array | ArrayBuffer): TabResultSnapshot | undefined {