From 8e5e0bd00b5038d96b2cc896b04de5eb682b3833 Mon Sep 17 00:00:00 2001 From: shiyuhang <1136742008@qq.com> Date: Tue, 20 Feb 2024 11:34:48 +0800 Subject: [PATCH 1/4] test --- src/decode.ts | 50 ++++++++++++++++++++++++++++++++++++++++++-------- src/format.ts | 10 ++++++++++ test.js | 31 +++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 8 deletions(-) create mode 100644 test.js diff --git a/src/decode.ts b/src/decode.ts index 950bf2a..1bb4112 100644 --- a/src/decode.ts +++ b/src/decode.ts @@ -32,25 +32,59 @@ 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 'DATE': case 'TIME': case 'DATETIME': case 'TIMESTAMP': - case 'BIT': return value + case 'BLOB': + case 'TINYBLOB': + case 'MEDIUMBLOB': + case 'LONGBLOB': + case 'BINARY': + case 'VARBINARY': + case 'TINYTEXT': + case 'BIT': + return uint8Array(value) case 'JSON': return JSON.parse(value) default: return value } } + +export function uint8Array(text: string): Uint8Array { + return Uint8Array.from(bytes(text)) +} + +function bytes(text: string): number[] { + return text.split('').map((c) => c.charCodeAt(0)) +} + +function str2UTF8(str) { + var bytes = new Array(); + var len, c; + len = str.length; + for (var i = 0; i < len; i++) { + c = str.charCodeAt(i); + if (c >= 0x010000 && c <= 0x10FFFF) { + bytes.push(((c >> 18) & 0x07) | 0xF0); + bytes.push(((c >> 12) & 0x3F) | 0x80); + bytes.push(((c >> 6) & 0x3F) | 0x80); + bytes.push((c & 0x3F) | 0x80); + } else if (c >= 0x000800 && c <= 0x00FFFF) { + bytes.push(((c >> 12) & 0x0F) | 0xE0); + bytes.push(((c >> 6) & 0x3F) | 0x80); + bytes.push((c & 0x3F) | 0x80); + } else if (c >= 0x000080 && c <= 0x0007FF) { + bytes.push(((c >> 6) & 0x1F) | 0xC0); + bytes.push((c & 0x3F) | 0x80); + } else { + bytes.push(c & 0xFF); + } + } + return bytes; +} \ No newline at end of file diff --git a/src/format.ts b/src/format.ts index 39ad691..490faf3 100644 --- a/src/format.ts +++ b/src/format.ts @@ -35,6 +35,11 @@ function sanitize(value: Value): string { return value ? 'true' : 'false' } + if (value instanceof Uint8Array) { + console.log("input convert to:"+uint8ArrayToHex(value)) + return uint8ArrayToHex(value) + } + if (typeof value === 'string') { return quote(value) } @@ -84,3 +89,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('')}` +} \ No newline at end of file diff --git a/test.js b/test.js new file mode 100644 index 0000000..d08c16f --- /dev/null +++ b/test.js @@ -0,0 +1,31 @@ +import { connect } from './dist/index.js' +import { fetch } from 'undici' + +async function testblob(){ + + const client = connect({url:'mysql://2cq1CrthsWhifF1.root:xxx@gateway01.us-east-1.dev.shared.aws.tidbcloud.com/test',fetch:fetch,arrayMode:true}) + + const input = 'FSDF' + console.log('input', input) + const inputAsBuffer = Buffer.from(input,'base64') + console.log('inputAsBuffer', inputAsBuffer) + + await client.execute('DELETE FROM `binary_test`') + await client.execute('INSERT INTO `binary_test` (`id`, `bytes`) VALUES (1, ?)', [inputAsBuffer], {debug:true}) + // await client.execute(`INSERT INTO binary_test (id, bytes) VALUES (1, 'a')`, null,{debug:true}) + const result = await client.execute('SELECT `id`, `bytes` FROM `binary_test`',null,{fullResult: true}) + + console.log(result) + let outputRaw = result.rows[0][1] + console.log('outputRaw', outputRaw) + console.log('typeof outputRaw', typeof outputRaw) + + const outputAsBuffer = Buffer.from(outputRaw) + console.log('outputAsBuffer', outputAsBuffer) + + const output = outputAsBuffer.toString('base64') + console.log('output', output) + console.log('`input === output`', input === output) +} + +await testblob() \ No newline at end of file From 54ee66adb527e577b70022b300f83c2b34b23e51 Mon Sep 17 00:00:00 2001 From: shiyuhang <1136742008@qq.com> Date: Wed, 21 Feb 2024 18:24:38 +0800 Subject: [PATCH 2/4] support binary --- .gitignore | 1 + integration-test/type.test.ts | 29 +++++++++++++++++------ src/decode.ts | 43 ++++++++--------------------------- src/format.ts | 1 - test.js | 31 ------------------------- 5 files changed, 32 insertions(+), 73 deletions(-) delete mode 100644 test.js diff --git a/.gitignore b/.gitignore index 3fd294c..69980fc 100644 --- a/.gitignore +++ b/.gitignore @@ -104,3 +104,4 @@ dist .tern-port .idea +out \ No newline at end of file diff --git a/integration-test/type.test.ts b/integration-test/type.test.ts index e862217..a5ef0de 100644 --- a/integration-test/type.test.ts +++ b/integration-test/type.test.ts @@ -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 @@ -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, @@ -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 @@ -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"}') @@ -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', @@ -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' } } @@ -156,11 +160,22 @@ 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) - expect(JSON.stringify(rows[0])).toEqual(JSON.stringify(fullTypeResult)) + // 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)) }) }) diff --git a/src/decode.ts b/src/decode.ts index 1bb4112..682cd8d 100644 --- a/src/decode.ts +++ b/src/decode.ts @@ -1,5 +1,5 @@ -import { Decoders } from './config' -import { Field } from './index.js' +import {Decoders} from './config' +import {Field} from './index.js' export function cast(field: Field, value: string | null, decoder: Decoders): any { if (value === null) { @@ -35,6 +35,7 @@ export function cast(field: Field, value: string | null, decoder: Decoders): any case 'TEXT': case 'MEDIUMTEXT': case 'LONGTEXT': + case 'TINYTEXT': case 'DATE': case 'TIME': case 'DATETIME': @@ -46,9 +47,8 @@ export function cast(field: Field, value: string | null, decoder: Decoders): any case 'LONGBLOB': case 'BINARY': case 'VARBINARY': - case 'TINYTEXT': case 'BIT': - return uint8Array(value) + return hexToUint8Array(value) case 'JSON': return JSON.parse(value) default: @@ -56,35 +56,10 @@ export function cast(field: Field, value: string | null, decoder: Decoders): any } } -export function uint8Array(text: string): Uint8Array { - return Uint8Array.from(bytes(text)) -} - -function bytes(text: string): number[] { - return text.split('').map((c) => c.charCodeAt(0)) -} - -function str2UTF8(str) { - var bytes = new Array(); - var len, c; - len = str.length; - for (var i = 0; i < len; i++) { - c = str.charCodeAt(i); - if (c >= 0x010000 && c <= 0x10FFFF) { - bytes.push(((c >> 18) & 0x07) | 0xF0); - bytes.push(((c >> 12) & 0x3F) | 0x80); - bytes.push(((c >> 6) & 0x3F) | 0x80); - bytes.push((c & 0x3F) | 0x80); - } else if (c >= 0x000800 && c <= 0x00FFFF) { - bytes.push(((c >> 12) & 0x0F) | 0xE0); - bytes.push(((c >> 6) & 0x3F) | 0x80); - bytes.push((c & 0x3F) | 0x80); - } else if (c >= 0x000080 && c <= 0x0007FF) { - bytes.push(((c >> 6) & 0x1F) | 0xC0); - bytes.push((c & 0x3F) | 0x80); - } else { - bytes.push(c & 0xFF); - } +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 bytes; + return uint8Array; } \ No newline at end of file diff --git a/src/format.ts b/src/format.ts index 490faf3..44e5f4e 100644 --- a/src/format.ts +++ b/src/format.ts @@ -36,7 +36,6 @@ function sanitize(value: Value): string { } if (value instanceof Uint8Array) { - console.log("input convert to:"+uint8ArrayToHex(value)) return uint8ArrayToHex(value) } diff --git a/test.js b/test.js deleted file mode 100644 index d08c16f..0000000 --- a/test.js +++ /dev/null @@ -1,31 +0,0 @@ -import { connect } from './dist/index.js' -import { fetch } from 'undici' - -async function testblob(){ - - const client = connect({url:'mysql://2cq1CrthsWhifF1.root:xxx@gateway01.us-east-1.dev.shared.aws.tidbcloud.com/test',fetch:fetch,arrayMode:true}) - - const input = 'FSDF' - console.log('input', input) - const inputAsBuffer = Buffer.from(input,'base64') - console.log('inputAsBuffer', inputAsBuffer) - - await client.execute('DELETE FROM `binary_test`') - await client.execute('INSERT INTO `binary_test` (`id`, `bytes`) VALUES (1, ?)', [inputAsBuffer], {debug:true}) - // await client.execute(`INSERT INTO binary_test (id, bytes) VALUES (1, 'a')`, null,{debug:true}) - const result = await client.execute('SELECT `id`, `bytes` FROM `binary_test`',null,{fullResult: true}) - - console.log(result) - let outputRaw = result.rows[0][1] - console.log('outputRaw', outputRaw) - console.log('typeof outputRaw', typeof outputRaw) - - const outputAsBuffer = Buffer.from(outputRaw) - console.log('outputAsBuffer', outputAsBuffer) - - const output = outputAsBuffer.toString('base64') - console.log('output', output) - console.log('`input === output`', input === output) -} - -await testblob() \ No newline at end of file From cda5d6ed710b2f3f5301536aa91fcb4b4377e218 Mon Sep 17 00:00:00 2001 From: shiyuhang <1136742008@qq.com> Date: Wed, 21 Feb 2024 18:28:03 +0800 Subject: [PATCH 3/4] lint --- integration-test/type.test.ts | 16 ++++++++-------- src/decode.ts | 12 ++++++------ src/format.ts | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/integration-test/type.test.ts b/integration-test/type.test.ts index a5ef0de..e800a28 100644 --- a/integration-test/type.test.ts +++ b/integration-test/type.test.ts @@ -1,7 +1,7 @@ import { connect, Row, FullResult } from '../dist/index' import { fetch } from 'undici' import * as dotenv from 'dotenv' -import {uint8ArrayToHex} from "../src/format"; +import { uint8ArrayToHex } from '../src/format' dotenv.config() const databaseURL = process.env.DATABASE_URL @@ -160,7 +160,7 @@ describe('types', () => { }) test('test all types', async () => { - const con = connect({ url: databaseURL, database: database, fetch}) + 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[] @@ -169,13 +169,13 @@ describe('types', () => { 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() + 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']) + rows[0]['t_bit'] = uint8ArrayToHex(rows[0]['t_bit']) - expect(JSON.stringify(rows[0])).toEqual(JSON.stringify(fullTypeResult)) + expect(JSON.stringify(rows[0])).toEqual(JSON.stringify(fullTypeResult)) }) }) diff --git a/src/decode.ts b/src/decode.ts index 682cd8d..2073503 100644 --- a/src/decode.ts +++ b/src/decode.ts @@ -1,5 +1,5 @@ -import {Decoders} from './config' -import {Field} from './index.js' +import { Decoders } from './config' +import { Field } from './index.js' export function cast(field: Field, value: string | null, decoder: Decoders): any { if (value === null) { @@ -57,9 +57,9 @@ export function cast(field: Field, value: string | null, decoder: Decoders): any } function hexToUint8Array(hexString: string): Uint8Array { - const uint8Array = new Uint8Array(hexString.length / 2); + 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); + uint8Array[i / 2] = parseInt(hexString.substring(i, i + 2), 16) } - return uint8Array; -} \ No newline at end of file + return uint8Array +} diff --git a/src/format.ts b/src/format.ts index 44e5f4e..39ac5c2 100644 --- a/src/format.ts +++ b/src/format.ts @@ -92,4 +92,4 @@ function replacement(text: string): string { export function uint8ArrayToHex(uint8: Uint8Array): string { const digits = Array.from(uint8).map((i) => i.toString(16).padStart(2, '0')) return `0x${digits.join('')}` -} \ No newline at end of file +} From 4c0120d3a99c3b44cdd9965bac9ae557b84a3ed2 Mon Sep 17 00:00:00 2001 From: shiyuhang <1136742008@qq.com> Date: Wed, 21 Feb 2024 18:49:55 +0800 Subject: [PATCH 4/4] add test --- integration-test/type.test.ts | 24 ++++++++++++++++++++++++ test.js | 0 2 files changed, 24 insertions(+) create mode 100644 test.js diff --git a/integration-test/type.test.ts b/integration-test/type.test.ts index e800a28..a5e8689 100644 --- a/integration-test/type.test.ts +++ b/integration-test/type.test.ts @@ -178,4 +178,28 @@ describe('types', () => { 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) + }) }) diff --git a/test.js b/test.js new file mode 100644 index 0000000..e69de29