Skip to content

Commit

Permalink
Support raw bytes as input & binary/blob/bit will be convert to Uint8…
Browse files Browse the repository at this point in the history
…Array (#55)

* test

* support binary

* lint

* add test
  • Loading branch information
shiyuhang0 committed Feb 22, 2024
1 parent a99d0ef commit 98b2f1e
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 14 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,4 @@ dist
.tern-port

.idea
out
51 changes: 45 additions & 6 deletions integration-test/type.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { connect, Row, FullResult } from '../dist/index'
import { fetch } from 'undici'
import * as dotenv from 'dotenv'
import { uint8ArrayToHex } from '../src/format'

dotenv.config()
const databaseURL = process.env.DATABASE_URL
Expand All @@ -24,7 +25,7 @@ const multiDataTable = `
t_decimal DECIMAL(38, 19),
t_char CHAR,
t_varchar VARCHAR(10),
c_binary binary(16),
c_binary binary(3),
c_varbinary varbinary(16),
t_tinytext TINYTEXT,
t_text TEXT,
Expand Down Expand Up @@ -84,6 +85,9 @@ const nullResult = {
t_json: null
}

// binary: x'1520c5' is the hex of 'FSDF' decoded from base64 (1520c5 has 3 bytes)
// blob : assume tidb serverless decode them with utf8
// bit: b'01010101' convert to hex is 55 (85 in 10 base)
const insertSQL = `
INSERT INTO ${database}.${table}( t_tinyint, t_tinyint_unsigned, t_smallint, t_smallint_unsigned, t_mediumint
, t_mediumint_unsigned, t_int, t_int_unsigned, t_bigint, t_bigint_unsigned
Expand All @@ -94,7 +98,7 @@ INSERT INTO ${database}.${table}( t_tinyint, t_tinyint_unsigned, t_smallint, t_s
, t_enum,t_bit, t_set, t_json)
VALUES ( -128, 255, -32768, 65535, -8388608, 16777215, -2147483648, 1, -9223372036854775808, 18446744073709551615
, true, 123.456, 123.123, 123456789012.123456789012
, '测', '测试', x'89504E470D0A1A0A', x'89504E470D0A1A0A', '测试tinytext', '0', '测试mediumtext', '测试longtext'
, '测', '测试', x'1520c5', x'1520c5', '测试tinytext', '0', '测试mediumtext', '测试longtext'
, 'tinyblob', 'blob', 'mediumblob', 'longblob'
, '1977-01-01', '9999-12-31 23:59:59', '19731230153000', '23:59:59', '2154'
, 'enum2',b'01010101', 'a,b','{"a":1,"b":"2"}')
Expand All @@ -117,8 +121,8 @@ const fullTypeResult = {
t_decimal: '123456789012.1234567890120000000',
t_char: '测',
t_varchar: '测试',
c_binary: '�PNG\r\n\x1A\n\x00\x00\x00\x00\x00\x00\x00\x00',
c_varbinary: '�PNG\r\n\x1A\n',
c_binary: 'FSDF',
c_varbinary: 'FSDF',
t_tinytext: '测试tinytext',
t_text: '0',
t_mediumtext: '测试mediumtext',
Expand All @@ -134,7 +138,7 @@ const fullTypeResult = {
t_year: 2154,
t_enum: 'enum2',
t_set: 'a,b',
t_bit: '\x00\x00\x00\x00\x00\x00\x00U',
t_bit: '0x0000000000000055',
t_json: { a: 1, b: '2' }
}

Expand All @@ -156,11 +160,46 @@ describe('types', () => {
})

test('test all types', async () => {
const con = connect({ url: databaseURL, database: database, fetch, debug: true })
const con = connect({ url: databaseURL, database: database, fetch })
await con.execute(`delete from ${table}`)
await con.execute(insertSQL)
const rows = (await con.execute('select * from multi_data_type')) as Row[]
expect(rows.length).toEqual(1)
// binary type returns Uint8Array, encode with base64
rows[0]['c_binary'] = Buffer.from(rows[0]['c_binary']).toString('base64')
rows[0]['c_varbinary'] = Buffer.from(rows[0]['c_varbinary']).toString('base64')
// blob type returns Uint8Array, encode with utf8
rows[0]['t_tinyblob'] = Buffer.from(rows[0]['t_tinyblob']).toString()
rows[0]['t_blob'] = Buffer.from(rows[0]['t_blob']).toString()
rows[0]['t_mediumblob'] = Buffer.from(rows[0]['t_mediumblob']).toString()
rows[0]['t_longblob'] = Buffer.from(rows[0]['t_longblob']).toString()
// bit type returns Uint8Array, get it with hex
rows[0]['t_bit'] = uint8ArrayToHex(rows[0]['t_bit'])

expect(JSON.stringify(rows[0])).toEqual(JSON.stringify(fullTypeResult))
})

test('test raw bytes as input', async () => {
const con = connect({ url: databaseURL, database: database, fetch })
const tableName = 'raw_bytes'
const tableDDL = `
create table ${tableName} (
bytes blob
)`
await con.execute(`DROP table IF EXISTS ${tableName}`)
await con.execute(tableDDL)

const input = 'FSDF'
const inputAsBuffer = Buffer.from(input, 'base64')
await con.execute(`insert into ${tableName} values (?)`, [inputAsBuffer])
const rows = (await con.execute(`select * from ${tableName}`)) as Row[]

console.log(rows)
expect(rows.length).toEqual(1)
const outputRaw = rows[0]['bytes']
const outputAsBuffer = Buffer.from(outputRaw)
const output = outputAsBuffer.toString('base64')

expect(input).toEqual(output)
})
})
25 changes: 17 additions & 8 deletions src/decode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,34 @@ export function cast(field: Field, value: string | null, decoder: Decoders): any
case 'DECIMAL':
case 'CHAR':
case 'VARCHAR':
case 'BINARY':
case 'VARBINARY':
case 'TINYTEXT':
case 'TEXT':
case 'MEDIUMTEXT':
case 'LONGTEXT':
case 'TINYBLOB':
case 'BLOB':
case 'MEDIUMBLOB':
case 'LONGBLOB':
case 'TINYTEXT':
case 'DATE':
case 'TIME':
case 'DATETIME':
case 'TIMESTAMP':
case 'BIT':
return value
case 'BLOB':
case 'TINYBLOB':
case 'MEDIUMBLOB':
case 'LONGBLOB':
case 'BINARY':
case 'VARBINARY':
case 'BIT':
return hexToUint8Array(value)
case 'JSON':
return JSON.parse(value)
default:
return value
}
}

function hexToUint8Array(hexString: string): Uint8Array {
const uint8Array = new Uint8Array(hexString.length / 2)
for (let i = 0; i < hexString.length; i += 2) {
uint8Array[i / 2] = parseInt(hexString.substring(i, i + 2), 16)
}
return uint8Array
}
9 changes: 9 additions & 0 deletions src/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ function sanitize(value: Value): string {
return value ? 'true' : 'false'
}

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

if (typeof value === 'string') {
return quote(value)
}
Expand Down Expand Up @@ -84,3 +88,8 @@ function replacement(text: string): string {
return ''
}
}

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

0 comments on commit 98b2f1e

Please sign in to comment.