fix(grid): keep large JSON numbers exact in copied SQL
This commit is contained in:
1 parent
a6696f07ed
commit
4a67591641
2 files changed
+34
-6
No files matched your search
@@ -1280,6 +1280,27 @@ describe("useDataGridExport prepared row statements", () => {
|
||||
expect(extractDataGridSelection).toHaveBeenNthCalledWith(2, expect.objectContaining({ rows: [[7, { name: "Ada", tags: ["admin"] }]] }));
|
||||
});
|
||||
|
||||
it("keeps JSON cell text in SQL requests when parsing would round large numbers", async () => {
|
||||
const tableMeta: DataGridTableMeta = {
|
||||
tableName: "events",
|
||||
primaryKeys: ["id"],
|
||||
columns: [
|
||||
{ name: "id", data_type: "int", is_nullable: false, is_primary_key: true },
|
||||
{ name: "payload", data_type: "json", is_nullable: true },
|
||||
],
|
||||
};
|
||||
const payload = '{"gameCategoryId":1968549762545291267,"name":"Ada"}';
|
||||
const matrix: CellSelectionMatrix = { rowIndexes: [0], columnIndexes: [0, 1], columns: ["id", "payload"], rows: [[7, payload]] };
|
||||
vi.mocked(extractDataGridSelection).mockResolvedValue({ text: "copied", mimeType: "application/sql", fileExtension: "sql", rowCount: 1, columnCount: 2 });
|
||||
const state = createExportState(tableMeta, ["id", "payload"], matrix, [7, payload]);
|
||||
|
||||
await expect(state.copyWithExtractor("sql-inserts")).resolves.toBe(true);
|
||||
await expect(state.copyWithExtractor("sql-updates")).resolves.toBe(true);
|
||||
|
||||
expect(extractDataGridSelection).toHaveBeenNthCalledWith(1, expect.objectContaining({ rows: [[7, payload]] }));
|
||||
expect(extractDataGridSelection).toHaveBeenNthCalledWith(2, expect.objectContaining({ rows: [[7, payload]] }));
|
||||
});
|
||||
|
||||
it("resolves large-value previews before building an extractor request", async () => {
|
||||
const matrix: CellSelectionMatrix = {
|
||||
rowIndexes: [0],
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
import type { DataGridTableMeta } from "@/lib/dataGrid/dataGridSql";
|
||||
import { binaryCellClipboardText } from "@/lib/dataGrid/binaryCellDownload";
|
||||
import { formatError } from "@/lib/backend/errorUtils";
|
||||
import { parseJsonPreservingLargeNumbers, stringifyJsonPreservingLargeNumbers } from "@/lib/common/safeJsonFormat";
|
||||
import { tableMetaWithoutOptionalDatabaseQualifier } from "@/lib/table/tableSelectSql";
|
||||
import type { DatabaseType } from "@/types/database";
|
||||
|
||||
@@ -35,6 +36,7 @@ interface ExtractorRequestSource {
|
||||
sourceColumnIndexes: number[];
|
||||
columnTypes: Array<string | undefined>;
|
||||
normalizeValues: boolean;
|
||||
keepUnsafeJsonText: boolean;
|
||||
presentBinaryText: boolean;
|
||||
rawRows: unknown[][];
|
||||
}
|
||||
@@ -76,10 +78,13 @@ export function useDataGridExtractor(options: UseDataGridExtractorOptions) {
|
||||
const hasUnsupportedDiscreteSelection = computed(() => options.hasCellSelection.value && options.selectedCellMatrix.value === null);
|
||||
const requestSources = new WeakMap<DataGridExtractRequest, ExtractorRequestSource>();
|
||||
|
||||
function normalizeCellValue(value: unknown, columnType: string | undefined): unknown {
|
||||
function normalizeCellValue(value: unknown, columnType: string | undefined, keepUnsafeJsonText: boolean): unknown {
|
||||
if (typeof value !== "string" || columnType?.trim().toLowerCase() !== "json") return value;
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
const parsed = JSON.parse(value);
|
||||
// SQL literals must keep numbers JavaScript would round (e.g. 64-bit ids above 2^53), so send the original text.
|
||||
if (keepUnsafeJsonText && stringifyJsonPreservingLargeNumbers(parseJsonPreservingLargeNumbers(value)) !== JSON.stringify(parsed)) return value;
|
||||
return parsed;
|
||||
} catch {
|
||||
return value;
|
||||
}
|
||||
@@ -87,12 +92,12 @@ export function useDataGridExtractor(options: UseDataGridExtractorOptions) {
|
||||
|
||||
// 除 SQL 外的剪贴板展示格式都可把文本型 MySQL VARBINARY 从 `0x<hex>` 还原为原始字符串。
|
||||
// SQL 必须继续持有 hex 才能保证 round-trip;rawRows 则始终保存原值,供 DBX 内部网格回粘使用。
|
||||
function extractorCellValue(value: unknown, columnType: string | undefined, normalizeValues: boolean, presentBinaryText: boolean, columnIndex: number): unknown {
|
||||
function extractorCellValue(value: unknown, columnType: string | undefined, normalizeValues: boolean, keepUnsafeJsonText: boolean, presentBinaryText: boolean, columnIndex: number): unknown {
|
||||
if (presentBinaryText) {
|
||||
const text = binaryCellClipboardText(value, columnType, options.databaseType.value);
|
||||
if (text !== null) return text;
|
||||
}
|
||||
const normalized = normalizeValues ? normalizeCellValue(value, columnType) : value;
|
||||
const normalized = normalizeValues ? normalizeCellValue(value, columnType, keepUnsafeJsonText) : value;
|
||||
const externalValue = options.externalCellValue?.(normalized, columnIndex);
|
||||
return externalValue === undefined ? normalized : externalValue;
|
||||
}
|
||||
@@ -214,9 +219,10 @@ export function useDataGridExtractor(options: UseDataGridExtractorOptions) {
|
||||
const selectedColumnIndexes = selectedSourceIndexes.map((sourceIndex) => compactIndexBySource.get(sourceIndex)).filter((index): index is number => index !== undefined);
|
||||
const columnTypes = requiredSourceIndexes.map((sourceIndex) => columnTypesBySource.get(sourceIndex));
|
||||
const normalizeValues = descriptor.category === "json" || descriptor.category === "sql";
|
||||
const keepUnsafeJsonText = descriptor.category === "sql";
|
||||
const presentBinaryText = descriptor.category !== "sql";
|
||||
const rawRows = sourceRows.map((row) => requiredSourceIndexes.map((sourceIndex) => row[sourceIndex]));
|
||||
const rows = rawRows.map((row) => row.map((value, index) => extractorCellValue(value, columnTypes[index], normalizeValues, presentBinaryText, index)));
|
||||
const rows = rawRows.map((row) => row.map((value, index) => extractorCellValue(value, columnTypes[index], normalizeValues, keepUnsafeJsonText, presentBinaryText, index)));
|
||||
const tableMeta =
|
||||
descriptor.category === "sql"
|
||||
? tableMetaWithoutOptionalDatabaseQualifier(
|
||||
@@ -245,6 +251,7 @@ export function useDataGridExtractor(options: UseDataGridExtractorOptions) {
|
||||
sourceColumnIndexes: requiredSourceIndexes,
|
||||
columnTypes,
|
||||
normalizeValues,
|
||||
keepUnsafeJsonText,
|
||||
presentBinaryText,
|
||||
rawRows,
|
||||
});
|
||||
@@ -279,7 +286,7 @@ export function useDataGridExtractor(options: UseDataGridExtractorOptions) {
|
||||
return sourceIndex !== undefined && values.has(sourceIndex) ? values.get(sourceIndex) : value;
|
||||
});
|
||||
});
|
||||
const rows = rawRows.map((row) => row.map((value, index) => extractorCellValue(value, limitedSource.columnTypes[index], limitedSource.normalizeValues, limitedSource.presentBinaryText, index)));
|
||||
const rows = rawRows.map((row) => row.map((value, index) => extractorCellValue(value, limitedSource.columnTypes[index], limitedSource.normalizeValues, limitedSource.keepUnsafeJsonText, limitedSource.presentBinaryText, index)));
|
||||
const resolvedRequest = { ...request, rows };
|
||||
requestSources.set(resolvedRequest, { ...limitedSource, rawRows });
|
||||
return resolvedRequest;
|
||||
|
||||
Reference in new issue
Block a user