From 7689e9af7b666a843ae07720f074c3bc117ed749 Mon Sep 17 00:00:00 2001 From: Karakatiza666 Date: Mon, 15 Jun 2026 12:16:51 +0000 Subject: [PATCH] [web-console] ad-hoc: format invalid number values as-is not as 'null' Signed-off-by: Karakatiza666 --- .../components/relationData/SQLValue.svelte | 4 +- .../web-console/src/lib/functions/sql.spec.ts | 47 ++++++++++++++++++- .../web-console/src/lib/functions/sql.ts | 15 +++++- 3 files changed, 61 insertions(+), 5 deletions(-) diff --git a/js-packages/web-console/src/lib/components/relationData/SQLValue.svelte b/js-packages/web-console/src/lib/components/relationData/SQLValue.svelte index 87a6e571c29..b7beb15c849 100644 --- a/js-packages/web-console/src/lib/components/relationData/SQLValue.svelte +++ b/js-packages/web-console/src/lib/components/relationData/SQLValue.svelte @@ -2,7 +2,7 @@ import { BigNumber } from 'bignumber.js' import type { HTMLTdAttributes } from 'svelte/elements' import JSONbig from 'true-json-bigint' - import { bytesToHex } from '$lib/functions/sql' + import { bytesToHex, formatNonFiniteNumber } from '$lib/functions/sql' import type { SQLValueJS } from '$lib/types/sql' const trim = (str: string) => str.slice(0, 50) + (str.length >= 50 ? '...' : '') @@ -21,7 +21,7 @@ return value.toFixed(3, BigNumber.ROUND_DOWN).replace(/\.?0+$/, '') } if (!Array.isArray(value)) { - return JSONbig.stringify(value, undefined, 1) + return formatNonFiniteNumber(value) ?? JSONbig.stringify(value, undefined, 1) } const str = [] as string[] let i = 0 diff --git a/js-packages/web-console/src/lib/functions/sql.spec.ts b/js-packages/web-console/src/lib/functions/sql.spec.ts index 29120816c13..86aa5ab1ecf 100644 --- a/js-packages/web-console/src/lib/functions/sql.spec.ts +++ b/js-packages/web-console/src/lib/functions/sql.spec.ts @@ -1,6 +1,11 @@ import { BigNumber } from 'bignumber.js' import { describe, expect, it } from 'vitest' -import { bytesToHex, displaySQLValue } from '$lib/functions/sql' +import { + bytesToHex, + displaySQLValue, + formatNonFiniteNumber, + serializeSQLValue +} from '$lib/functions/sql' describe('bytesToHex', () => { it('renders bytes as zero-padded lowercase hex', () => { @@ -23,4 +28,44 @@ describe('displaySQLValue', () => { '123456789012345678.9012345678' ) }) + + // Regression: JSON has no encoding for these IEEE-754 values, so the + // `JSONbig.stringify` fallback used to collapse every one of them to the + // literal `null` — indistinguishable from a SQL NULL. DOUBLE/REAL results + // such as `1.0 / 0.0` must render as their actual value instead. + it('renders non-finite floats rather than collapsing them to null', () => { + expect(displaySQLValue(Infinity)).toBe('Infinity') + expect(displaySQLValue(-Infinity)).toBe('-Infinity') + expect(displaySQLValue(NaN)).toBe('NaN') + }) +}) + +describe('formatNonFiniteNumber', () => { + it('spells out NaN and the infinities', () => { + expect(formatNonFiniteNumber(NaN)).toBe('NaN') + expect(formatNonFiniteNumber(Infinity)).toBe('Infinity') + expect(formatNonFiniteNumber(-Infinity)).toBe('-Infinity') + }) + + it('leaves finite numbers and non-numbers for the normal path', () => { + expect(formatNonFiniteNumber(0)).toBeUndefined() + expect(formatNonFiniteNumber(-3.5)).toBeUndefined() + expect(formatNonFiniteNumber(null)).toBeUndefined() + expect(formatNonFiniteNumber('NaN')).toBeUndefined() + expect(formatNonFiniteNumber(new BigNumber('1'))).toBeUndefined() + }) +}) + +describe('serializeSQLValue', () => { + it('exports non-finite floats as their textual value, not null', () => { + expect(serializeSQLValue(Infinity)).toBe('Infinity') + expect(serializeSQLValue(-Infinity)).toBe('-Infinity') + expect(serializeSQLValue(NaN)).toBe('NaN') + }) + + it('serializes ordinary values as JSON', () => { + expect(serializeSQLValue(null)).toBe('null') + expect(serializeSQLValue('hello')).toBe('"hello"') + expect(serializeSQLValue(42)).toBe('42') + }) }) diff --git a/js-packages/web-console/src/lib/functions/sql.ts b/js-packages/web-console/src/lib/functions/sql.ts index f0161bf57e8..982f15f215c 100644 --- a/js-packages/web-console/src/lib/functions/sql.ts +++ b/js-packages/web-console/src/lib/functions/sql.ts @@ -96,6 +96,17 @@ export const bytesToHex = (bytes: Uint8Array): string => { return hex } +/** + * Render a non-finite IEEE-754 value (`NaN`, `Infinity`, `-Infinity`) as text, + * or return `undefined` for finite numbers and non-numbers. + * + * JSON cannot represent these values, so `JSON.stringify` + * collapses each of them to the literal `null` — + * indistinguishable from a SQL NULL. + */ +export const formatNonFiniteNumber = (value: unknown): string | undefined => + typeof value === 'number' && !Number.isFinite(value) ? String(value) : undefined + export const displaySQLValue = (value: SQLValueJS) => { return value === null ? 'NULL' @@ -105,7 +116,7 @@ export const displaySQLValue = (value: SQLValueJS) => { ? bytesToHex(value) : BigNumber.isBigNumber(value) ? value.toFixed() - : JSONbig.stringify(value, undefined, 1) + : (formatNonFiniteNumber(value) ?? JSONbig.stringify(value, undefined, 1)) } /** @@ -140,7 +151,7 @@ const toJSONValue = (value: SQLValueJS): unknown => { * Serialize SQLValueJS to a JSON string representation suitable for CSV export */ export const serializeSQLValue = (value: SQLValueJS): string => { - return JSONbig.stringify(toJSONValue(value)) + return formatNonFiniteNumber(value) ?? JSONbig.stringify(toJSONValue(value)) } /**