Skip to content

Support binary data #164

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 1, 2024
Merged
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
5 changes: 5 additions & 0 deletions __tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,11 @@ describe('cast', () => {
expect(cast({ name: 'test', type: 'FLOAT64' }, '2.32')).toEqual(2.32)
})

test('casts binary data to array of 8-bit unsigned integers', () => {
expect(cast({ name: 'test', type: 'BLOB' }, '')).toEqual(new Uint8Array([]))
expect(cast({ name: 'test', type: 'BLOB' }, 'Å')).toEqual(new Uint8Array([197]))
})

test('casts JSON string to JSON object', () => {
expect(cast({ name: 'test', type: 'JSON' }, '{ "foo": "bar" }')).toStrictEqual({ foo: 'bar' })
})
Expand Down
15 changes: 14 additions & 1 deletion __tests__/text.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { decode, hex } from '../src/text'
import { decode, hex, uint8Array, uint8ArrayToHex } from '../src/text'

describe('text', () => {
describe('decode', () => {
Expand Down Expand Up @@ -32,4 +32,17 @@ describe('text', () => {
expect(hex('aa')).toEqual('0x6161')
})
})

describe('uint8Array', () => {
test('converts to an array of 8-bit unsigned integers', () => {
expect(uint8Array('')).toEqual(new Uint8Array([]))
expect(uint8Array('Å')).toEqual(new Uint8Array([197]))
})
})

describe('uint8ArrayToHex', () => {
test('converts an array of 8-bit unsigned integers to hex', () => {
expect(uint8ArrayToHex(new Uint8Array([197]))).toEqual('0xc5')
})
})
})
6 changes: 4 additions & 2 deletions jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default {
testEnvironment: 'node',

// Automatically clear mock calls, instances, contexts and results before every test
clearMocks: true
clearMocks: true,

// Indicates whether the coverage information should be collected while executing the test
// collectCoverage: false,
Expand Down Expand Up @@ -90,7 +90,9 @@ export default {
// ],

// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
// moduleNameMapper: {},
moduleNameMapper: {
'./text.js': './text'
},

// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
// modulePathIgnorePatterns: [],
Expand Down
9 changes: 5 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { format } from './sanitization.js'
export { format } from './sanitization.js'
import { decode, uint8Array } from './text.js'
export { hex } from './text.js'
import { decode } from './text.js'
import { Version } from './version.js'

type Row<T extends ExecuteAs = 'object'> = T extends 'array' ? any[] : T extends 'object' ? Record<string, any> : never
Expand Down Expand Up @@ -379,7 +379,7 @@ function decodeRow(row: QueryResultRow): Array<string | null> {
}

export function cast(field: Field, value: string | null): any {
if (value === '' || value == null) {
if (value == null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we want to handle empty strings when it's binary data and not just return an empty string

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree, see my comment below.

return value
}

Expand All @@ -404,14 +404,15 @@ export function cast(field: Field, value: string | null): any {
case 'TIME':
case 'DATETIME':
case 'TIMESTAMP':
return value
case 'BLOB':
case 'BIT':
case 'VARBINARY':
case 'BINARY':
case 'GEOMETRY':
return value
return uint8Array(value)
case 'JSON':
return JSON.parse(decode(value))
return value ? JSON.parse(decode(value)) : value
default:
return decode(value)
}
Expand Down
6 changes: 6 additions & 0 deletions src/sanitization.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { uint8ArrayToHex } from './text.js'

type Stringable = { toString: () => string }
type Value = null | undefined | number | boolean | string | Array<Value> | Date | Stringable

Expand Down Expand Up @@ -47,6 +49,10 @@ function sanitize(value: Value): string {
return quote(value.toISOString().slice(0, -1))
}

if (value instanceof Uint8Array) {
return uint8ArrayToHex(value)
}

return quote(value.toString())
}

Expand Down
11 changes: 10 additions & 1 deletion src/text.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
const decoder = new TextDecoder('utf-8')

export function decode(text: string | null | undefined): string {
return text ? decoder.decode(Uint8Array.from(bytes(text))) : ''
return text ? decoder.decode(uint8Array(text)) : ''
}

export function hex(text: string): string {
const digits = bytes(text).map((b) => b.toString(16).padStart(2, '0'))
return `0x${digits.join('')}`
}

export function uint8Array(text: string): Uint8Array {
return Uint8Array.from(bytes(text))
}

export function uint8ArrayToHex(uint8: Uint8Array): string {
const digits = Array.from(uint8).map((i) => i.toString(16).padStart(2, '0'))
return `0x${digits.join('')}`
}

function bytes(text: string): number[] {
return text.split('').map((c) => c.charCodeAt(0))
}