Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 ? '...' : '')
Expand All @@ -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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if this is a ROW value? Or a Map value?

}
const str = [] as string[]
let i = 0
Expand Down
47 changes: 46 additions & 1 deletion js-packages/web-console/src/lib/functions/sql.spec.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand All @@ -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')
})
})
15 changes: 13 additions & 2 deletions js-packages/web-console/src/lib/functions/sql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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))
}

/**
Expand Down Expand Up @@ -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))
}

/**
Expand Down
Loading