Skip to content
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

Support raw bytes as input & binary/blob/bit will be convert to Uint8Array #55

Merged
merged 4 commits into from
Feb 22, 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
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.
Loading