Update Fix

This commit is contained in:
2026-03-15 12:30:40 +01:00
parent 311ba5e7f3
commit 50be8e25f3
176 changed files with 4075 additions and 3013 deletions

2
node_modules/mysql2/index.js generated vendored
View File

@@ -1,6 +1,6 @@
'use strict';
const SqlString = require('sqlstring');
const SqlString = require('sql-escaper');
const ConnectionConfig = require('./lib/connection_config.js');
const parserCache = require('./lib/parsers/parser_cache.js');

View File

@@ -42,7 +42,7 @@ function encrypt(password, scramble, key) {
);
}
module.exports =
const pluginFactory =
(pluginOptions = {}) =>
({ connection }) => {
let state = 0;
@@ -106,3 +106,9 @@ module.exports =
);
};
};
// Export the plugin factory as default
module.exports = pluginFactory;
// Export calculateToken for reuse in initial handshake optimization
module.exports.calculateToken = calculateToken;

View File

@@ -21,7 +21,7 @@ const Timers = require('timers');
const EventEmitter = require('events').EventEmitter;
const Readable = require('stream').Readable;
const Queue = require('denque');
const SqlString = require('sqlstring');
const SqlString = require('sql-escaper');
const { createLRU } = require('lru.min');
const PacketParser = require('../packet_parser.js');
const Packets = require('../packets/index.js');
@@ -835,27 +835,31 @@ class BaseConnection extends EventEmitter {
if (!cb) {
return;
}
if (this._fatalError || this._protocolError) {
return cb(this._fatalError || this._protocolError);
}
if (this._handshakePacket) {
return cb(null, this);
}
let connectCalled = 0;
function callbackOnce(isErrorHandler) {
return function (param) {
if (!connectCalled) {
if (isErrorHandler) {
cb(param);
} else {
cb(null, param);
}
}
connectCalled = 1;
};
}
this.once('error', callbackOnce(true));
this.once('connect', callbackOnce(false));
/* eslint-disable prefer-const */
let onError, onConnect;
onError = (param) => {
this.removeListener('connect', onConnect);
cb(param);
};
onConnect = (param) => {
this.removeListener('error', onError);
cb(null, param);
};
/* eslint-enable prefer-const */
this.once('error', onError);
this.once('connect', onConnect);
}
// ===================================
@@ -922,6 +926,12 @@ class BaseConnection extends EventEmitter {
return this.addCommand(new Commands.ServerHandshake(args));
}
[Symbol.dispose]() {
if (!this._closing) {
this.end();
}
}
// ===============================================================
end(callback) {
if (this.config.isServer) {

55
node_modules/mysql2/lib/base/pool.js generated vendored
View File

@@ -1,11 +1,27 @@
'use strict';
const process = require('process');
const SqlString = require('sqlstring');
const SqlString = require('sql-escaper');
const EventEmitter = require('events').EventEmitter;
const PoolConnection = require('../pool_connection.js');
const Queue = require('denque');
const BaseConnection = require('./connection.js');
const Errors = require('../constants/errors.js');
// Source: https://github.com/go-sql-driver/mysql/blob/76c00e35a8d48f8f70f0e7dffe584692bd3fa612/packets.go#L598-L613
function isReadOnlyError(err) {
if (!err || !err.errno) {
return false;
}
// 1792: ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION
// 1290: ER_OPTION_PREVENTS_STATEMENT (returned by Aurora during failover)
// 1836: ER_READ_ONLY_MODE
return (
err.errno === Errors.ER_OPTION_PREVENTS_STATEMENT ||
err.errno === Errors.ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION ||
err.errno === Errors.ER_READ_ONLY_MODE
);
}
function spliceConnection(queue, connection) {
const len = queue.length;
@@ -92,6 +108,12 @@ class BasePool extends EventEmitter {
}
}
[Symbol.dispose]() {
if (!this._closed) {
this.end();
}
}
end(cb) {
this._closed = true;
clearTimeout(this._removeIdleTimeoutConnectionsTimer);
@@ -146,8 +168,24 @@ class BasePool extends EventEmitter {
return;
}
try {
let queryError = null;
const origOnResult = cmdQuery.onResult;
if (origOnResult) {
cmdQuery.onResult = function (err, rows, fields) {
queryError = err || null;
origOnResult(err, rows, fields);
};
} else {
cmdQuery.once('error', (err) => {
queryError = err;
});
}
conn.query(cmdQuery).once('end', () => {
conn.release();
if (isReadOnlyError(queryError)) {
conn.destroy();
} else {
conn.release();
}
});
} catch (e) {
conn.release();
@@ -169,9 +207,16 @@ class BasePool extends EventEmitter {
return cb(err);
}
try {
conn.execute(sql, values, cb).once('end', () => {
conn.release();
});
conn
.execute(sql, values, (err, rows, fields) => {
if (isReadOnlyError(err)) {
conn.destroy();
}
cb(err, rows, fields);
})
.once('end', () => {
conn.release();
});
} catch (e) {
conn.release();
return cb(e);

View File

@@ -29,10 +29,14 @@ class BasePoolConnection extends BaseConnection {
this._pool.releaseConnection(this);
}
end() {
[Symbol.dispose]() {
this.release();
}
end(callback) {
if (this.config.gracefulEnd) {
this._removeFromPool();
super.end();
super.end(callback);
return;
}
@@ -46,6 +50,9 @@ class BasePoolConnection extends BaseConnection {
this.emit('warn', err);
console.warn(err.message);
this.release();
if (typeof callback === 'function') {
callback();
}
}
destroy() {

View File

@@ -11,12 +11,31 @@ const caching_sha2_password = require('../auth_plugins/caching_sha2_password.js'
const mysql_native_password = require('../auth_plugins/mysql_native_password.js');
const mysql_clear_password = require('../auth_plugins/mysql_clear_password.js');
const standardAuthPlugins = {
// Use Object.create(null) to avoid prototype pollution
// This prevents server-controlled pluginName values like "toString" or "__proto__"
// from resolving to prototype properties
const standardAuthPlugins = Object.assign(Object.create(null), {
sha256_password: sha256_password({}),
caching_sha2_password: caching_sha2_password({}),
mysql_native_password: mysql_native_password({}),
mysql_clear_password: mysql_clear_password({}),
};
});
// Helper function to get auth plugin (custom or standard)
function getAuthPlugin(pluginName, connection) {
const customPlugins = connection.config.authPlugins;
// Check custom plugins with hasOwnProperty for safety
if (
customPlugins &&
Object.prototype.hasOwnProperty.call(customPlugins, pluginName)
) {
return customPlugins[pluginName];
}
// Safe to access standardAuthPlugins directly since it has no prototype
return standardAuthPlugins[pluginName];
}
function warnLegacyAuthSwitch() {
console.warn(
@@ -35,8 +54,6 @@ function authSwitchPluginError(error, command) {
function authSwitchRequest(packet, connection, command) {
const { pluginName, pluginData } =
Packets.AuthSwitchRequest.fromPacket(packet);
let authPlugin =
connection.config.authPlugins && connection.config.authPlugins[pluginName];
// legacy plugin api don't allow to override mysql_native_password
// if pluginName is mysql_native_password it's using standard auth4.1 auth
@@ -54,9 +71,8 @@ function authSwitchRequest(packet, connection, command) {
});
return;
}
if (!authPlugin) {
authPlugin = standardAuthPlugins[pluginName];
}
const authPlugin = getAuthPlugin(pluginName, connection);
if (!authPlugin) {
throw new Error(
`Server requests authentication using unknown plugin ${pluginName}. See ${'TODO: add plugins doco here'} on how to configure or author authentication plugins.`
@@ -108,4 +124,6 @@ function authSwitchRequestMoreData(packet, connection, command) {
module.exports = {
authSwitchRequest,
authSwitchRequestMoreData,
getAuthPlugin,
standardAuthPlugins,
};

View File

@@ -15,6 +15,10 @@ const Packets = require('../packets/index.js');
const ClientConstants = require('../constants/client.js');
const CharsetToEncoding = require('../constants/charset_encodings.js');
const auth41 = require('../auth_41.js');
const { getAuthPlugin } = require('./auth_switch.js');
const {
calculateToken: calculateSha2Token,
} = require('../auth_plugins/caching_sha2_password.js');
function flagNames(flags) {
const res = [];
@@ -67,6 +71,61 @@ class ClientHandshake extends Command {
this.passwordSha1 = connection.config.passwordSha1;
this.database = connection.config.database;
this.authPluginName = this.handshake.authPluginName;
// Optimization: Try to use the server's preferred authentication method
// to avoid an unnecessary auth switch roundtrip
const serverAuthMethod = this.handshake.authPluginName;
const isSecureConnection =
connection.config.ssl || connection.config.socketPath;
// Combine auth plugin data for easier handling
// Note: authPluginData2 can include a trailing NUL byte when PLUGIN_AUTH is set
// We must ensure exactly 20 bytes for the scramble
const authPluginData =
this.handshake.authPluginData1 && this.handshake.authPluginData2
? Buffer.concat([
this.handshake.authPluginData1,
this.handshake.authPluginData2,
]).slice(0, 20)
: Buffer.alloc(20);
// Check if user has custom auth plugin or legacy handler for the server-advertised method
// If so, we must not bypass the auth switch flow with our built-in implementation
const hasCustomAuthPlugin =
connection.config.authPlugins &&
Object.prototype.hasOwnProperty.call(
connection.config.authPlugins,
serverAuthMethod
);
const hasLegacyAuthSwitchHandler =
typeof connection.config.authSwitchHandler === 'function';
// Determine which auth method to use
// Try to use server's preferred method if we can, otherwise fallback to native
const canUseDirectAuth =
!hasCustomAuthPlugin &&
!hasLegacyAuthSwitchHandler &&
this.canUseAuthMethodDirectly(serverAuthMethod, isSecureConnection);
const clientAuthMethod = canUseDirectAuth
? serverAuthMethod
: 'mysql_native_password';
// Calculate the auth token for the chosen method
const authToken = this.calculateAuthToken(
clientAuthMethod,
this.password,
authPluginData
);
if (connection.config.debug) {
console.log(
'Server auth method: %s, Using auth method: %s',
serverAuthMethod,
clientAuthMethod
);
}
const handshakeResponse = new Packets.HandshakeResponse({
flags: this.clientFlags,
user: this.user,
@@ -78,8 +137,17 @@ class ClientHandshake extends Command {
authPluginData2: this.handshake.authPluginData2,
compress: connection.config.compress,
connectAttributes: connection.config.connectAttributes,
authToken: authToken,
authPluginName: clientAuthMethod,
});
connection.writePacket(handshakeResponse.toPacket());
// If we used a non-native auth method in the initial handshake response,
// we need to prepare for potential AuthMoreData packets by creating
// the appropriate auth plugin instance
if (clientAuthMethod !== 'mysql_native_password') {
this.initializeAuthPlugin(clientAuthMethod, authPluginData, connection);
}
}
calculateNativePasswordAuthToken(authPluginData) {
@@ -103,6 +171,82 @@ class ClientHandshake extends Command {
return authToken;
}
calculateSha256Token(password, scramble) {
// Reuse the token calculation from caching_sha2_password plugin
// to avoid code duplication and ensure consistency
return calculateSha2Token(password, scramble);
}
// Helper: Calculate auth token for a specific auth method
calculateAuthToken(authMethod, password, authPluginData) {
switch (authMethod) {
case 'mysql_native_password':
return this.calculateNativePasswordAuthToken(authPluginData);
case 'caching_sha2_password':
return this.calculateSha256Token(password, authPluginData);
case 'sha256_password':
case 'mysql_clear_password':
// These methods send plaintext password over secure connections
return password
? Buffer.from(`${password}\0`, 'utf8')
: Buffer.alloc(0);
default:
// Unknown method - use native password as fallback
return this.calculateNativePasswordAuthToken(authPluginData);
}
}
// Helper: Determine if we can use a specific auth method directly
canUseAuthMethodDirectly(authMethod, isSecureConnection) {
switch (authMethod) {
case 'mysql_native_password':
case 'caching_sha2_password':
// These methods work with or without SSL
return true;
case 'sha256_password':
case 'mysql_clear_password':
// These methods require secure connection for direct use
return isSecureConnection;
default:
// Unknown methods - fallback to native password
return false;
}
}
// Helper: Initialize auth plugin for handling subsequent AuthMoreData packets
initializeAuthPlugin(authMethod, authPluginData, connection) {
const authPlugin = getAuthPlugin(authMethod, connection);
if (!authPlugin) {
return; // Plugin not found, will fallback to auth switch if needed
}
// Initialize the plugin with connection and command context
const pluginHandler = authPlugin({ connection, command: this });
connection._authPlugin = pluginHandler;
// Prime the plugin by calling it with the scramble data
// This advances the plugin's state machine (e.g., to STATE_TOKEN_SENT)
// We don't send the result because we already included it in the handshake response
try {
Promise.resolve(pluginHandler(authPluginData)).catch((err) => {
// Ignore errors during initialization since we already sent the token
if (connection.config.debug) {
console.log('Auth plugin initialization:', err.message);
}
});
} catch (err) {
// Ignore synchronous errors during initialization
if (connection.config.debug) {
console.log('Auth plugin initialization error:', err.message);
}
}
}
handshakeInit(helloPacket, connection) {
this.on('error', (e) => {
connection._fatalError = e;

View File

@@ -6,6 +6,32 @@
const zlib = require('zlib');
const PacketParser = require('./packet_parser.js');
class Queue {
constructor() {
this._queue = [];
this._running = false;
}
push(fn) {
this._queue.push(fn);
if (!this._running) {
this._running = true;
process.nextTick(() => this._next());
}
}
_next() {
const task = this._queue.shift();
if (!task) {
this._running = false;
return;
}
task({
done: () => process.nextTick(() => this._next()),
});
}
}
function handleCompressedPacket(packet) {
// eslint-disable-next-line consistent-this, no-invalid-this
const connection = this;
@@ -117,11 +143,11 @@ function enableCompression(connection) {
connection.writeUncompressed = connection.write;
connection.write = writeCompressed;
const seqqueue = require('seq-queue');
connection.inflateQueue = seqqueue.createQueue();
connection.deflateQueue = seqqueue.createQueue();
connection.inflateQueue = new Queue();
connection.deflateQueue = new Queue();
}
module.exports = {
enableCompression: enableCompression,
Queue: Queue,
};

View File

@@ -277,7 +277,10 @@ class ConnectionConfig {
user: decodeURIComponent(parsedUrl.username),
password: decodeURIComponent(parsedUrl.password),
};
parsedUrl.searchParams.forEach((value, key) => {
for (const [key, value] of parsedUrl.searchParams) {
if (key in options) {
continue;
}
try {
// Try to parse this as a JSON expression first
options[key] = JSON.parse(value);
@@ -285,7 +288,7 @@ class ConnectionConfig {
// Otherwise assume it is a plain string
options[key] = value;
}
});
}
return options;
}
}

View File

@@ -313,4 +313,5 @@ module.exports = [
'utf8',
'utf8',
'utf8',
'utf8',
];

View File

@@ -16,22 +16,47 @@ class HandshakeResponse {
this.authPluginData2 = handshake.authPluginData2;
this.compress = handshake.compress;
this.clientFlags = handshake.flags;
// TODO: pre-4.1 auth support
let authToken;
if (this.passwordSha1) {
authToken = auth41.calculateTokenFromPasswordSha(
this.passwordSha1,
this.authPluginData1,
this.authPluginData2
);
// Accept pre-calculated authToken and authPluginName from caller
// This allows the caller to optimize by using the server's preferred auth method
if (
handshake.authToken !== undefined &&
handshake.authPluginName !== undefined
) {
// Validate types to fail fast with clear errors
if (!Buffer.isBuffer(handshake.authToken)) {
throw new TypeError(
'HandshakeResponse authToken must be a Buffer when provided'
);
}
if (typeof handshake.authPluginName !== 'string') {
throw new TypeError(
'HandshakeResponse authPluginName must be a string when provided'
);
}
this.authToken = handshake.authToken;
this.authPluginName = handshake.authPluginName;
} else {
authToken = auth41.calculateToken(
this.password,
this.authPluginData1,
this.authPluginData2
);
// Fallback to legacy behavior: calculate mysql_native_password token
// TODO: pre-4.1 auth support
let authToken;
if (this.passwordSha1) {
authToken = auth41.calculateTokenFromPasswordSha(
this.passwordSha1,
this.authPluginData1,
this.authPluginData2
);
} else {
authToken = auth41.calculateToken(
this.password,
this.authPluginData1,
this.authPluginData2
);
}
this.authToken = authToken;
this.authPluginName = 'mysql_native_password';
}
this.authToken = authToken;
this.charsetNumber = handshake.charsetNumber;
this.encoding = CharsetToEncoding[handshake.charsetNumber];
this.connectAttributes = handshake.connectAttributes;
@@ -62,8 +87,12 @@ class HandshakeResponse {
packet.writeNullTerminatedString(this.database, encoding);
}
if (isSet('PLUGIN_AUTH')) {
// TODO: pass from config
packet.writeNullTerminatedString('mysql_native_password', 'latin1');
// Use the auth plugin name specified by the caller (optimized for server's preference)
// or fall back to mysql_native_password for backward compatibility
packet.writeNullTerminatedString(
this.authPluginName || 'mysql_native_password',
'latin1'
);
}
if (isSet('CONNECT_ATTRS')) {
const connectAttributes = this.connectAttributes || {};

View File

@@ -406,8 +406,8 @@ class Packet {
readNullTerminatedString(encoding) {
const start = this.offset;
let end = this.offset;
while (this.buffer[end]) {
end = end + 1; // TODO: handle OOB check
while (end < this.end && this.buffer[end] !== 0x00) {
end = end + 1;
}
this.offset = end + 1;
return StringParser.decode(this.buffer, encoding, start, end);
@@ -516,16 +516,20 @@ class Packet {
return result * sign;
}
// copy-paste from https://github.com/mysqljs/mysql/blob/master/lib/protocol/Parser.js
// adapted from https://github.com/mysqljs/mysql/blob/dc9c152a87ec51a1f647447268917243d2eab1fd/lib/protocol/Parser.js
parseGeometryValue() {
const buffer = this.readLengthCodedBuffer();
let offset = 4;
if (buffer === null || !buffer.length) {
return null;
}
const bufferLength = buffer.length;
function parseGeometry() {
let x, y, i, j, numPoints, line;
let x, y, i, j, numPoints, numRings, num, line;
let result = null;
if (offset + 5 > bufferLength) {
return null;
}
const byteOrder = buffer.readUInt8(offset);
offset += 1;
const wkbType = byteOrder
@@ -534,6 +538,9 @@ class Packet {
offset += 4;
switch (wkbType) {
case 1: // WKBPoint
if (offset + 16 > bufferLength) {
return null;
}
x = byteOrder
? buffer.readDoubleLE(offset)
: buffer.readDoubleBE(offset);
@@ -545,12 +552,21 @@ class Packet {
result = { x: x, y: y };
break;
case 2: // WKBLineString
if (offset + 4 > bufferLength) {
return null;
}
numPoints = byteOrder
? buffer.readUInt32LE(offset)
: buffer.readUInt32BE(offset);
offset += 4;
if (numPoints > (bufferLength - offset) / 16) {
return null;
}
result = [];
for (i = numPoints; i > 0; i--) {
if (offset + 16 > bufferLength) {
break;
}
x = byteOrder
? buffer.readDoubleLE(offset)
: buffer.readDoubleBE(offset);
@@ -563,19 +579,30 @@ class Packet {
}
break;
case 3: // WKBPolygon
// eslint-disable-next-line no-case-declarations
const numRings = byteOrder
if (offset + 4 > bufferLength) {
return null;
}
numRings = byteOrder
? buffer.readUInt32LE(offset)
: buffer.readUInt32BE(offset);
offset += 4;
if (numRings > (bufferLength - offset) / 4) {
return null;
}
result = [];
for (i = numRings; i > 0; i--) {
if (offset + 4 > bufferLength) {
break;
}
numPoints = byteOrder
? buffer.readUInt32LE(offset)
: buffer.readUInt32BE(offset);
offset += 4;
line = [];
for (j = numPoints; j > 0; j--) {
if (offset + 16 > bufferLength) {
break;
}
x = byteOrder
? buffer.readDoubleLE(offset)
: buffer.readDoubleBE(offset);
@@ -593,11 +620,16 @@ class Packet {
case 5: // WKBMultiLineString
case 6: // WKBMultiPolygon
case 7: // WKBGeometryCollection
// eslint-disable-next-line no-case-declarations
const num = byteOrder
if (offset + 4 > bufferLength) {
return null;
}
num = byteOrder
? buffer.readUInt32LE(offset)
: buffer.readUInt32BE(offset);
offset += 4;
if (num > (bufferLength - offset) / 9) {
return null;
}
result = [];
for (i = num; i > 0; i--) {
result.push(parseGeometry());
@@ -660,14 +692,27 @@ class Packet {
if (len === null) {
return null;
}
if (len === 0) {
return 0; // TODO: assert? exception?
}
// For numbers with many digits (>17), use built-in parseFloat to avoid
// precision loss from accumulated rounding errors in repeated *10 operations.
// This fixes issues #2928 (MAX_VALUE doubles) and #3690 (DECIMAL(36,18))
// where very large numbers or numbers with many fractional digits lose precision.
// The threshold of 17 is based on IEEE 754 double precision (~15-17 significant digits).
// Testing shows minimal performance impact as most real-world numbers are shorter.
if (len > 17) {
const str = this.buffer.toString('utf8', this.offset, this.offset + len);
this.offset += len;
return Number.parseFloat(str);
}
let result = 0;
const end = this.offset + len;
let factor = 1;
let pastDot = false;
let charCode = 0;
if (len === 0) {
return 0; // TODO: assert? exception?
}
if (this.buffer[this.offset] === minus) {
this.offset++;
factor = -1;
@@ -681,9 +726,13 @@ class Packet {
pastDot = true;
this.offset++;
} else if (charCode === exponent || charCode === exponentCapital) {
this.offset++;
const exponentValue = this.parseInt(end - this.offset);
return (result / factor) * Math.pow(10, exponentValue);
// Scientific notation detected - bail out to parseFloat for exact match.
// Manual calculation with Math.pow(10, exp) cannot match parseFloat()
// exactly for most non-zero exponents due to accumulated rounding errors.
const start = end - len;
const str = this.buffer.toString('utf8', start, end);
this.offset = end;
return Number.parseFloat(str);
} else {
result *= 10;
result += this.buffer[this.offset] - 48;
@@ -831,11 +880,10 @@ class Packet {
if (n === null) {
return this.writeInt8(0xfb);
}
// TODO: check that n is out of int precision
this.writeInt8(0xfe);
this.buffer.writeUInt32LE(n, this.offset);
this.buffer.writeUInt32LE(n >>> 0, this.offset);
this.offset += 4;
this.buffer.writeUInt32LE(n >> 32, this.offset);
this.buffer.writeUInt32LE(Math.floor(n / 0x100000000), this.offset);
this.offset += 4;
return this.offset;
}

View File

@@ -229,6 +229,12 @@ class PoolCluster extends EventEmitter {
namespace.getConnection(cb);
}
[Symbol.dispose]() {
if (!this._closed) {
this.end();
}
}
end(callback) {
const cb =
callback !== undefined

View File

@@ -66,6 +66,12 @@ class PromiseConnection extends EventEmitter {
});
}
async [Symbol.asyncDispose]() {
if (!this.connection._closing) {
await this.end();
}
}
beginTransaction() {
const c = this.connection;
const localErr = new Error();

View File

@@ -85,6 +85,12 @@ class PromisePool extends EventEmitter {
});
});
}
async [Symbol.asyncDispose]() {
if (!this.pool._closed) {
await this.end();
}
}
}
(function (functionsToWrap) {

View File

@@ -14,6 +14,10 @@ class PromisePoolConnection extends PromiseConnection {
arguments
);
}
async [Symbol.asyncDispose]() {
this.release();
}
}
module.exports = PromisePoolConnection;

44
node_modules/mysql2/package.json generated vendored
View File

@@ -1,6 +1,6 @@
{
"name": "mysql2",
"version": "3.16.3",
"version": "3.19.1",
"description": "fast mysql driver. Implements core protocol, prepared statements, ssl and compression in native JS",
"main": "index.js",
"typings": "typings/mysql/index",
@@ -8,17 +8,23 @@
"scripts": {
"lint": "eslint . && prettier --check .",
"lint:fix": "eslint . --fix && prettier --write .",
"test": "poku -d -r=verbose --sequential test/esm test/unit test/integration",
"test:bun": "bun poku -d --sequential test/esm test/unit test/integration",
"test:deno": "deno run --allow-read --allow-env --allow-run npm:poku -d --sequential --denoAllow=\"read,env,net,sys\" test/esm test/unit test/integration",
"test:tsc-build": "cd \"test/tsc-build\" && npx tsc -p \"tsconfig.json\"",
"test": "npm run test:parallel && npm run test:global",
"test:parallel": "poku -c=\"poku.config.mjs\" test",
"test:global": "cross-env SUITE=global poku -c=\"poku.config.mjs\" test/global",
"test:bun": "npm run test:bun:parallel && npm run test:bun:global",
"test:bun:parallel": "bun poku -c=\"poku.config.mjs\" test",
"test:bun:global": "cross-env SUITE=global bun poku -c=\"poku.config.mjs\" test/global",
"test:deno": "npm run test:deno:parallel && npm run test:deno:global",
"test:deno:parallel": "deno run --allow-read --allow-env --allow-run npm:poku -c=\"poku.config.mjs\" test",
"test:deno:global": "cross-env SUITE=global deno run --allow-read --allow-env --allow-run npm:poku -c=\"poku.config.mjs\" test/global",
"test:docker:up": "docker compose -f test/docker-compose.yml up --abort-on-container-exit --remove-orphans",
"test:docker:down": "docker compose -f test/docker-compose.yml down",
"test:docker:node": "npm run test:docker:up -- node && npm run test:docker:down",
"test:docker:bun": "npm run test:docker:up -- bun && npm run test:docker:down",
"test:docker:deno": "npm run test:docker:up -- deno && npm run test:docker:down",
"test:docker:coverage": "npm run test:docker:up -- coverage && npm run test:docker:down",
"coverage-test": "c8 npm run test",
"test:coverage": "c8 npm test",
"typecheck": "cd \"test/tsc-build\" && tsc -p \"tsconfig.json\" && cd .. && tsc -p \"tsconfig.json\" --noEmit",
"benchmark": "node ./benchmarks/benchmark.js",
"wait-port": "wait-on"
},
@@ -57,30 +63,34 @@
"generate-function": "^2.3.1",
"iconv-lite": "^0.7.2",
"long": "^5.3.2",
"lru.min": "^1.1.3",
"lru.min": "^1.1.4",
"named-placeholders": "^1.1.6",
"seq-queue": "^0.0.5",
"sqlstring": "^2.3.3"
"sql-escaper": "^1.3.3"
},
"peerDependencies": {
"@types/node": ">= 8"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.3",
"@eslint/js": "^9.39.2",
"@eslint/markdown": "^7.5.1",
"@types/node": "^25.0.9",
"@typescript-eslint/eslint-plugin": "^8.53.0",
"@typescript-eslint/parser": "^8.53.0",
"@ianvs/prettier-plugin-sort-imports": "^4.7.1",
"@types/node": "^25.3.0",
"@typescript-eslint/eslint-plugin": "^8.56.0",
"@typescript-eslint/parser": "^8.56.0",
"assert-diff": "^3.0.4",
"benchmark": "^2.1.4",
"c8": "^10.1.3",
"c8": "^11.0.0",
"cross-env": "^10.1.0",
"error-stack-parser": "^2.1.4",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-async-await": "^0.0.0",
"eslint-plugin-markdown": "^5.1.0",
"eslint-plugin-prettier": "^5.5.5",
"globals": "^17.0.0",
"poku": "^3.0.2",
"globals": "^17.3.0",
"poku": "^4.0.0",
"portfinder": "^1.0.38",
"prettier": "^3.8.0",
"prettier": "^3.8.1",
"tsx": "^4.21.0",
"typescript": "^5.9.3"
}
}

5
node_modules/mysql2/promise.d.ts generated vendored
View File

@@ -65,6 +65,8 @@ export interface Connection extends QueryableAndExecutableBase {
end(options?: any): Promise<void>;
[Symbol.asyncDispose](): Promise<void>;
destroy(): void;
pause(): void;
@@ -82,6 +84,7 @@ export interface Connection extends QueryableAndExecutableBase {
export interface PoolConnection extends Connection {
release(): void;
connection: Connection;
[Symbol.asyncDispose](): Promise<void>;
}
export interface Pool extends Connection {
@@ -112,6 +115,8 @@ export interface PoolCluster extends EventEmitter {
end(): Promise<void>;
[Symbol.asyncDispose](): Promise<void>;
getConnection(): Promise<PoolConnection>;
getConnection(group: string): Promise<PoolConnection>;
getConnection(group: string, selector: string): Promise<PoolConnection>;

8
node_modules/mysql2/promise.js generated vendored
View File

@@ -1,6 +1,6 @@
'use strict';
const SqlString = require('sqlstring');
const SqlString = require('sql-escaper');
const EventEmitter = require('events').EventEmitter;
const parserCache = require('./lib/parsers/parser_cache.js');
const PoolCluster = require('./lib/pool_cluster.js');
@@ -134,6 +134,12 @@ class PromisePoolCluster extends EventEmitter {
});
});
}
async [Symbol.asyncDispose]() {
if (!this.poolCluster._closed) {
await this.end();
}
}
}
/**

View File

@@ -1,6 +1,3 @@
/**
* sqlstring types are based on https://www.npmjs.com/package/@types/sqlstring, version 2.3.2
*/
import { Pool as BasePool, PoolOptions } from './lib/Pool.js';
import {
Connection as BaseConnection,
@@ -23,6 +20,13 @@ import {
PrepareStatementInfo,
} from './lib/protocol/sequences/Prepare.js';
import { Server } from './lib/Server.js';
import {
escape as SqlStringEscape,
escapeId as SqlStringEscapeId,
format as SqlStringFormat,
raw as SqlStringRaw,
} from 'sql-escaper';
export type { Raw, SqlValue, Timezone } from 'sql-escaper';
export {
ConnectionOptions,
@@ -57,26 +61,10 @@ export function createPool(config: PoolOptions): BasePool;
export function createPoolCluster(config?: PoolClusterOptions): PoolCluster;
type TimeZone = 'local' | 'Z' | (string & NonNullable<unknown>);
export function escape(
value: any,
stringifyObjects?: boolean,
timeZone?: TimeZone
): string;
export function escapeId(value: any, forbidQualified?: boolean): string;
export function format(sql: string): string;
export function format(
sql: string,
values: any | any[],
stringifyObjects?: boolean,
timeZone?: TimeZone
): string;
export function raw(sql: string): {
toSqlString: () => string;
};
export const escape: typeof SqlStringEscape;
export const escapeId: typeof SqlStringEscapeId;
export const format: typeof SqlStringFormat;
export const raw: typeof SqlStringRaw;
export interface ConnectionConfig extends ConnectionOptions {
mergeFlags(defaultFlags: string[], userFlags: string[] | string): number;

View File

@@ -5,6 +5,7 @@
import { EventEmitter } from 'events';
import { Readable } from 'stream';
import { Timezone } from 'sql-escaper';
import { Query, QueryError } from './protocol/sequences/Query.js';
import { Prepare, PrepareStatementInfo } from './protocol/sequences/Prepare.js';
import {
@@ -150,7 +151,7 @@ export interface ConnectionOptions {
/**
* The timezone used to store local dates. (Default: 'local')
*/
timezone?: string | 'local';
timezone?: Timezone;
/**
* The milliseconds before a timeout occurs during the initial connection to the MySQL server. (Default: 10 seconds)
@@ -393,6 +394,8 @@ declare class Connection extends QueryableBase(ExecutableBase(EventEmitter)) {
end(callback?: (err: QueryError | null) => void): void;
end(options: any, callback?: (err: QueryError | null) => void): void;
[Symbol.dispose](): void;
destroy(): void;
pause(): void;

View File

@@ -53,6 +53,8 @@ declare class Pool extends QueryableBase(ExecutableBase(EventEmitter)) {
callback?: (err: NodeJS.ErrnoException | null, ...args: any[]) => any
): void;
[Symbol.dispose](): void;
on(event: string, listener: (...args: any[]) => void): this;
on(event: 'connection', listener: (connection: PoolConnection) => any): this;
on(event: 'acquire', listener: (connection: PoolConnection) => any): this;

View File

@@ -56,6 +56,8 @@ declare class PoolCluster extends EventEmitter {
end(callback?: (err: NodeJS.ErrnoException | null) => void): void;
[Symbol.dispose](): void;
getConnection(
callback: (
err: NodeJS.ErrnoException | null,

View File

@@ -4,6 +4,7 @@ import { Pool as PromisePool } from '../../../promise.js';
declare class PoolConnection extends Connection {
connection: Connection;
release(): void;
[Symbol.dispose](): void;
promise(promiseImpl?: PromiseConstructor): PromisePool;
}

View File

@@ -3,6 +3,7 @@ import {
Query,
QueryError,
QueryOptions,
ExecuteValues,
QueryableConstructor,
} from './Query.js';
@@ -18,7 +19,7 @@ export declare function ExecutableBase<T extends QueryableConstructor>(
): Query;
execute<T extends QueryResult>(
sql: string,
values: any,
values: ExecuteValues,
callback?:
| ((err: QueryError | null, result: T, fields: FieldPacket[]) => any)
| undefined
@@ -26,12 +27,12 @@ export declare function ExecutableBase<T extends QueryableConstructor>(
execute<T extends QueryResult>(
options: QueryOptions,
callback?:
| ((err: QueryError | null, result: T, fields?: FieldPacket[]) => any)
| ((err: QueryError | null, result: T, fields: FieldPacket[]) => any)
| undefined
): Query;
execute<T extends QueryResult>(
options: QueryOptions,
values: any,
values: ExecuteValues,
callback?:
| ((err: QueryError | null, result: T, fields: FieldPacket[]) => any)
| undefined

View File

@@ -1,8 +1,37 @@
import { Sequence } from './Sequence.js';
import { OkPacket, RowDataPacket, FieldPacket } from '../packets/index.js';
import { Readable } from 'stream';
import { Raw, Timezone } from 'sql-escaper';
import { TypeCast } from '../../parsers/typeCast.js';
export type ExecuteValues =
| string
| number
| bigint
| boolean
| Date
| null
| Blob
| Buffer
| Uint8Array
| ExecuteValues[]
| { [key: string]: ExecuteValues };
export type QueryValues =
| string
| number
| bigint
| boolean
| Date
| null
| undefined
| Blob
| Buffer
| Uint8Array
| Raw
| ({} | null | undefined)[]
| { [key: string]: QueryValues };
export interface QueryOptions {
/**
* The SQL for the query
@@ -12,7 +41,7 @@ export interface QueryOptions {
/**
* The values for the query
*/
values?: any | any[] | { [param: string]: any };
values?: QueryValues;
/**
* This overrides the namedPlaceholders option set at the connection level.
@@ -83,6 +112,35 @@ export interface QueryOptions {
* By specifying a function that returns a readable stream, an arbitrary stream can be sent when sending a local fs file.
*/
infileStreamFactory?: (path: string) => Readable;
/**
* When dealing with big numbers (BIGINT and DECIMAL columns) in the database, you should enable this option
* (Default: false)
*/
supportBigNumbers?: boolean;
/**
* Enabling both supportBigNumbers and bigNumberStrings forces big numbers (BIGINT and DECIMAL columns) to be
* always returned as JavaScript String objects (Default: false). Enabling supportBigNumbers but leaving
* bigNumberStrings disabled will return big numbers as String objects only when they cannot be accurately
* represented with JavaScript Number objects (which happens when they exceed the [-2^53, +2^53] range),
* otherwise they will be returned as Number objects.
* This option is ignored if supportBigNumbers is disabled.
*/
bigNumberStrings?: boolean;
/**
* Force date types (TIMESTAMP, DATETIME, DATE) to be returned as strings rather then inflated into JavaScript Date
* objects. Can be true/false or an array of type names to keep as strings.
*
* (Default: false)
*/
dateStrings?: boolean | Array<'TIMESTAMP' | 'DATETIME' | 'DATE'>;
/**
* The timezone used to store local dates. (Default: 'local')
*/
timezone?: Timezone;
}
export interface StreamOptions {

View File

@@ -3,6 +3,7 @@ import {
Query,
QueryError,
QueryOptions,
QueryValues,
QueryableConstructor,
} from './Query.js';
@@ -18,7 +19,7 @@ export declare function QueryableBase<T extends QueryableConstructor>(
): Query;
query<T extends QueryResult>(
sql: string,
values: any,
values: QueryValues,
callback?:
| ((err: QueryError | null, result: T, fields: FieldPacket[]) => any)
| undefined
@@ -26,12 +27,12 @@ export declare function QueryableBase<T extends QueryableConstructor>(
query<T extends QueryResult>(
options: QueryOptions,
callback?:
| ((err: QueryError | null, result: T, fields?: FieldPacket[]) => any)
| ((err: QueryError | null, result: T, fields: FieldPacket[]) => any)
| undefined
): Query;
query<T extends QueryResult>(
options: QueryOptions,
values: any,
values: QueryValues,
callback?:
| ((err: QueryError | null, result: T, fields: FieldPacket[]) => any)
| undefined

View File

@@ -1,21 +1,17 @@
import { FieldPacket, QueryResult } from '../../packets/index.js';
import { QueryOptions, QueryableConstructor } from '../Query.js';
import { QueryOptions, QueryableConstructor, ExecuteValues } from '../Query.js';
export declare function ExecutableBase<T extends QueryableConstructor>(
Base?: T
): {
new (...args: any[]): {
execute<T extends QueryResult>(sql: string): Promise<[T, FieldPacket[]]>;
execute<T extends QueryResult>(
sql: string,
values: any
): Promise<[T, FieldPacket[]]>;
execute<T extends QueryResult>(
options: QueryOptions
values?: ExecuteValues
): Promise<[T, FieldPacket[]]>;
execute<T extends QueryResult>(
options: QueryOptions,
values: any
values?: ExecuteValues
): Promise<[T, FieldPacket[]]>;
};
} & T;

View File

@@ -1,21 +1,18 @@
import { FieldPacket, QueryResult } from '../../packets/index.js';
import { QueryOptions, QueryableConstructor } from '../Query.js';
import { QueryOptions, QueryValues, QueryableConstructor } from '../Query.js';
export declare function QueryableBase<T extends QueryableConstructor>(
Base?: T
): {
new (...args: any[]): {
query<T extends QueryResult>(sql: string): Promise<[T, FieldPacket[]]>;
query<T extends QueryResult>(
sql: string,
values: any
): Promise<[T, FieldPacket[]]>;
query<T extends QueryResult>(
options: QueryOptions
values?: QueryValues
): Promise<[T, FieldPacket[]]>;
query<T extends QueryResult>(
options: QueryOptions,
values: any
values?: QueryValues
): Promise<[T, FieldPacket[]]>;
};
} & T;