From 78ce4953e9c66d6cf40ffc2d252fa3701a2d4fe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Weslley=20Ara=C3=BAjo?= <46850407+wellwelwel@users.noreply.github.com> Date: Tue, 23 Jan 2024 16:41:46 -0300 Subject: [PATCH] fix(types): add missing types to TypeCast (#2390) * fix(types): add missing types to TypeCast * chore: add comment on typeCast for execute * ci: create strict tests for typeCast option --- test/tsc-build/strict-checks/typeCast.ts | 154 ++++++++++++++++++ typings/mysql/lib/Connection.d.ts | 49 ++++-- typings/mysql/lib/parsers/typeCast.d.ts | 53 ++++++ .../mysql/lib/protocol/sequences/Query.d.ts | 53 ++++-- 4 files changed, 281 insertions(+), 28 deletions(-) create mode 100644 test/tsc-build/strict-checks/typeCast.ts create mode 100644 typings/mysql/lib/parsers/typeCast.d.ts diff --git a/test/tsc-build/strict-checks/typeCast.ts b/test/tsc-build/strict-checks/typeCast.ts new file mode 100644 index 0000000000..a4ffee3396 --- /dev/null +++ b/test/tsc-build/strict-checks/typeCast.ts @@ -0,0 +1,154 @@ +import { QueryOptions, ConnectionOptions } from '../../../index.js'; +import { + QueryOptions as QueryOptionsP, + ConnectionOptions as ConnectionOptionsP, +} from '../../../promise.js'; +import { access, sql } from '../promise/baseConnection.js'; + +// Callback: QueryOptions +{ + const options1: QueryOptions = { + sql, + typeCast: true, + }; + + const options2: QueryOptions = { + sql, + typeCast: false, + }; + + const options3: QueryOptions = { + sql, + typeCast: (field, next) => { + const db: string = field.db; + const length: number = field.length; + const name: string = field.name; + const table: string = field.table; + const type: string = field.type; + const buffer: Buffer | null = field.buffer(); + const string: string | null = field.string(); + const geometry: + | { x: number; y: number } + | { x: number; y: number }[] + | null = field.geometry(); + + console.log(db, length, name, table, type); + console.log(buffer, string, geometry); + + return next(); + }, + }; + + console.log(options1, options2, options3); +} + +// Callback: ConnectionOptions +{ + const options1: ConnectionOptions = { + ...access, + typeCast: true, + }; + + const options2: ConnectionOptions = { + ...access, + typeCast: false, + }; + + const options3: ConnectionOptions = { + ...access, + typeCast: (field, next) => { + const db: string = field.db; + const length: number = field.length; + const name: string = field.name; + const table: string = field.table; + const type: string = field.type; + const buffer: Buffer | null = field.buffer(); + const string: string | null = field.string(); + const geometry: + | { x: number; y: number } + | { x: number; y: number }[] + | null = field.geometry(); + + console.log(db, length, name, table, type); + console.log(buffer, string, geometry); + + return next(); + }, + }; + + console.log(options1, options2, options3); +} + +// Promise: QueryOptions +{ + const options1: QueryOptionsP = { + sql, + typeCast: true, + }; + + const options2: QueryOptionsP = { + sql, + typeCast: false, + }; + + const options3: QueryOptionsP = { + sql, + typeCast: (field, next) => { + const db: string = field.db; + const length: number = field.length; + const name: string = field.name; + const table: string = field.table; + const type: string = field.type; + const buffer: Buffer | null = field.buffer(); + const string: string | null = field.string(); + const geometry: + | { x: number; y: number } + | { x: number; y: number }[] + | null = field.geometry(); + + console.log(db, length, name, table, type); + console.log(buffer, string, geometry); + + return next(); + }, + }; + + console.log(options1, options2, options3); +} + +// Promise: ConnectionOptions +{ + const options1: ConnectionOptionsP = { + ...access, + typeCast: true, + }; + + const options2: ConnectionOptionsP = { + ...access, + typeCast: false, + }; + + const options3: ConnectionOptionsP = { + ...access, + typeCast: (field, next) => { + const db: string = field.db; + const length: number = field.length; + const name: string = field.name; + const table: string = field.table; + const type: string = field.type; + const buffer: Buffer | null = field.buffer(); + const string: string | null = field.string(); + const geometry: + | { x: number; y: number } + | { x: number; y: number }[] + | null = field.geometry(); + + console.log(db, length, name, table, type); + console.log(buffer, string, geometry); + + return next(); + }, + }; + + console.log(options1, options2, options3); +} diff --git a/typings/mysql/lib/Connection.d.ts b/typings/mysql/lib/Connection.d.ts index f6691f6485..334a1437f5 100644 --- a/typings/mysql/lib/Connection.d.ts +++ b/typings/mysql/lib/Connection.d.ts @@ -19,6 +19,7 @@ import { Connection as PromiseConnection } from '../../../promise.js'; import { AuthPlugin } from './Auth.js'; import { QueryableBase } from './protocol/sequences/QueryableBase.js'; import { ExecutableBase } from './protocol/sequences/ExecutableBase.js'; +import { TypeCast } from './parsers/typeCast.js'; export interface SslOptions { /** @@ -172,26 +173,48 @@ export interface ConnectionOptions { infileStreamFactory?: (path: string) => Readable; /** - * Determines if column values should be converted to native JavaScript types. It is not recommended (and may go away / change in the future) - * to disable type casting, but you can currently do so on either the connection or query level. (Default: true) + * Determines if column values should be converted to native JavaScript types. * - * You can also specify a function (field: any, next: () => void) => {} to do the type casting yourself. + * @default true * - * WARNING: YOU MUST INVOKE the parser using one of these three field functions in your custom typeCast callback. They can only be called once. + * It is not recommended (and may go away / change in the future) to disable type casting, but you can currently do so on either the connection or query level. * - * field.string() - * field.buffer() - * field.geometry() + * --- * - * are aliases for + * You can also specify a function to do the type casting yourself: + * ```ts + * (field: Field, next: () => void) => { + * return next(); + * } + * ``` * - * parser.parseLengthCodedString() - * parser.parseLengthCodedBuffer() - * parser.parseGeometryValue() + * --- * - * You can find which field function you need to use by looking at: RowDataPacket.prototype._typeCast + * **WARNING:** + * + * YOU MUST INVOKE the parser using one of these three field functions in your custom typeCast callback. They can only be called once: + * + * ```js + * field.string(); + * field.buffer(); + * field.geometry(); + * ``` + + * Which are aliases for: + * + * ```js + * parser.parseLengthCodedString(); + * parser.parseLengthCodedBuffer(); + * parser.parseGeometryValue(); + * ``` + * + * You can find which field function you need to use by looking at `RowDataPacket.prototype._typeCast`. + * + * --- + * + * For `execute`, please see: [typeCast not supported with .execute #649](https://github.com/sidorares/node-mysql2/issues/649). */ - typeCast?: boolean | ((field: any, next: () => void) => any); + typeCast?: TypeCast; /** * A custom query format function diff --git a/typings/mysql/lib/parsers/typeCast.d.ts b/typings/mysql/lib/parsers/typeCast.d.ts new file mode 100644 index 0000000000..67aad03f2a --- /dev/null +++ b/typings/mysql/lib/parsers/typeCast.d.ts @@ -0,0 +1,53 @@ +type Geometry = { + x: number; + y: number; +}; + +type Type = { + type: + | 'DECIMAL' + | 'TINY' + | 'SHORT' + | 'LONG' + | 'FLOAT' + | 'DOUBLE' + | 'NULL' + | 'TIMESTAMP' + | 'TIMESTAMP2' + | 'LONGLONG' + | 'INT24' + | 'DATE' + | 'TIME' + | 'TIME2' + | 'DATETIME' + | 'DATETIME2' + | 'YEAR' + | 'NEWDATE' + | 'VARCHAR' + | 'BIT' + | 'JSON' + | 'NEWDECIMAL' + | 'ENUM' + | 'SET' + | 'TINY_BLOB' + | 'MEDIUM_BLOB' + | 'LONG_BLOB' + | 'BLOB' + | 'VAR_STRING' + | 'STRING' + | 'GEOMETRY'; +}; + +type Field = Type & { + length: number; + db: string; + table: string; + name: string; + string: () => string | null; + buffer: () => Buffer | null; + geometry: () => Geometry | Geometry[] | null; +}; + +type Next = () => void; + +export type TypeCast = ((field: Field, next: Next) => any) | boolean; diff --git a/typings/mysql/lib/protocol/sequences/Query.d.ts b/typings/mysql/lib/protocol/sequences/Query.d.ts index 00b2bcb2c5..5c4cb65084 100644 --- a/typings/mysql/lib/protocol/sequences/Query.d.ts +++ b/typings/mysql/lib/protocol/sequences/Query.d.ts @@ -1,6 +1,7 @@ import { Sequence } from './Sequence.js'; import { OkPacket, RowDataPacket, FieldPacket } from '../packets/index.js'; import { Readable } from 'stream'; +import { TypeCast } from '../../parsers/typeCast.js'; export interface QueryOptions { /** @@ -33,26 +34,48 @@ export interface QueryOptions { nestTables?: any; /** - * Determines if column values should be converted to native JavaScript types. It is not recommended (and may go away / change in the future) - * to disable type casting, but you can currently do so on either the connection or query level. (Default: true) + * Determines if column values should be converted to native JavaScript types. * - * You can also specify a function (field: any, next: () => void) => {} to do the type casting yourself. + * @default true * - * WARNING: YOU MUST INVOKE the parser using one of these three field functions in your custom typeCast callback. They can only be called once. + * It is not recommended (and may go away / change in the future) to disable type casting, but you can currently do so on either the connection or query level. * - * field.string() - * field.buffer() - * field.geometry() + * --- * - * are aliases for + * You can also specify a function to do the type casting yourself: + * ```ts + * (field: Field, next: () => void) => { + * return next(); + * } + * ``` * - * parser.parseLengthCodedString() - * parser.parseLengthCodedBuffer() - * parser.parseGeometryValue() + * --- * - * You can find which field function you need to use by looking at: RowDataPacket.prototype._typeCast + * **WARNING:** + * + * YOU MUST INVOKE the parser using one of these three field functions in your custom typeCast callback. They can only be called once: + * + * ```js + * field.string(); + * field.buffer(); + * field.geometry(); + * ``` + + * Which are aliases for: + * + * ```js + * parser.parseLengthCodedString(); + * parser.parseLengthCodedBuffer(); + * parser.parseGeometryValue(); + * ``` + * + * You can find which field function you need to use by looking at `RowDataPacket.prototype._typeCast`. + * + * --- + * + * For `execute`, please see: [typeCast not supported with .execute #649](https://github.com/sidorares/node-mysql2/issues/649). */ - typeCast?: any; + typeCast?: TypeCast; /** * This overrides the same option set at the connection level. @@ -137,11 +160,11 @@ declare class Query extends Sequence { on(event: 'error', listener: (err: QueryError) => any): this; on( event: 'fields', - listener: (fields: FieldPacket, index: number) => any + listener: (fields: FieldPacket, index: number) => any, ): this; on( event: 'result', - listener: (result: RowDataPacket | OkPacket, index: number) => any + listener: (result: RowDataPacket | OkPacket, index: number) => any, ): this; on(event: 'end', listener: () => any): this; }