mysql2 3.19.1 → 3.19.2-canary.c06afc25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -28,6 +28,15 @@ const Packets = require('../packets/index.js');
28
28
  const Commands = require('../commands/index.js');
29
29
  const ConnectionConfig = require('../connection_config.js');
30
30
  const CharsetToEncoding = require('../constants/charset_encodings.js');
31
+ const {
32
+ traceCallback,
33
+ tracePromise,
34
+ getServerContext,
35
+ shouldTrace,
36
+ queryChannel,
37
+ executeChannel,
38
+ connectChannel,
39
+ } = require('../tracing.js');
31
40
 
32
41
  let _connectionId = 0;
33
42
 
@@ -141,6 +150,41 @@ class BaseConnection extends EventEmitter {
141
150
  this._notifyError(err);
142
151
  });
143
152
  this.addCommand(handshakeCommand);
153
+
154
+ // Trace the connection handshake
155
+ if (shouldTrace(connectChannel)) {
156
+ const config = this.config;
157
+ tracePromise(
158
+ connectChannel,
159
+ () =>
160
+ new Promise((resolve, reject) => {
161
+ /* eslint-disable prefer-const */
162
+ let onConnect, onError;
163
+ onConnect = (param) => {
164
+ this.removeListener('error', onError);
165
+ resolve(param);
166
+ };
167
+ onError = (err) => {
168
+ this.removeListener('connect', onConnect);
169
+ reject(err);
170
+ };
171
+ /* eslint-enable prefer-const */
172
+ this.once('connect', onConnect);
173
+ this.once('error', onError);
174
+ }),
175
+ () => {
176
+ const server = getServerContext(config);
177
+ return {
178
+ database: config.database || '',
179
+ serverAddress: server.serverAddress,
180
+ serverPort: server.serverPort,
181
+ user: config.user || '',
182
+ };
183
+ }
184
+ ).catch(() => {
185
+ // errors are already handled by the handshake error listener
186
+ });
187
+ }
144
188
  }
145
189
  // in case there was no initial handshake but we need to read sting, assume it utf-8
146
190
  // most common example: "Too many connections" error ( packet is sent immediately on connection attempt, we don't know server encoding yet)
@@ -601,7 +645,62 @@ class BaseConnection extends EventEmitter {
601
645
  cmdQuery.values !== undefined ? cmdQuery.values : []
602
646
  );
603
647
  cmdQuery.sql = rawSql;
604
- return this.addCommand(cmdQuery);
648
+
649
+ if (!shouldTrace(queryChannel)) {
650
+ return this.addCommand(cmdQuery);
651
+ }
652
+
653
+ const config = this.config;
654
+ const origCb = cmdQuery.onResult;
655
+
656
+ if (origCb) {
657
+ // Callback mode: use traceCallback for synchronous wrapping
658
+ traceCallback(
659
+ queryChannel,
660
+ (wrappedCb) => {
661
+ cmdQuery.onResult = wrappedCb;
662
+ this.addCommand(cmdQuery);
663
+ },
664
+ 0,
665
+ () => {
666
+ const server = getServerContext(config);
667
+ return {
668
+ query: cmdQuery.sql,
669
+ values: cmdQuery.values,
670
+ database: config.database || '',
671
+ serverAddress: server.serverAddress,
672
+ serverPort: server.serverPort,
673
+ };
674
+ },
675
+ null,
676
+ origCb
677
+ );
678
+ } else {
679
+ // Event-emitter mode: use tracePromise since there is no callback
680
+ tracePromise(
681
+ queryChannel,
682
+ () =>
683
+ new Promise((resolve, reject) => {
684
+ cmdQuery.once('error', reject);
685
+ cmdQuery.once('end', () => resolve());
686
+ this.addCommand(cmdQuery);
687
+ }),
688
+ () => {
689
+ const server = getServerContext(config);
690
+ return {
691
+ query: cmdQuery.sql,
692
+ values: cmdQuery.values,
693
+ database: config.database || '',
694
+ serverAddress: server.serverAddress,
695
+ serverPort: server.serverPort,
696
+ };
697
+ }
698
+ ).catch(() => {
699
+ // errors are already emitted on the command
700
+ });
701
+ }
702
+
703
+ return cmdQuery;
605
704
  }
606
705
 
607
706
  pause() {
@@ -702,25 +801,108 @@ class BaseConnection extends EventEmitter {
702
801
  });
703
802
  }
704
803
  const executeCommand = new Commands.Execute(options, cb);
705
- const prepareCommand = new Commands.Prepare(options, (err, stmt) => {
706
- if (err) {
707
- // skip execute command if prepare failed, we have main
708
- // combined callback here
709
- executeCommand.start = function () {
710
- return null;
711
- };
712
- if (cb) {
713
- cb(err);
714
- } else {
715
- executeCommand.emit('error', err);
804
+
805
+ if (!shouldTrace(executeChannel)) {
806
+ const prepareCommand = new Commands.Prepare(options, (err, stmt) => {
807
+ if (err) {
808
+ // skip execute command if prepare failed, we have main
809
+ // combined callback here
810
+ executeCommand.start = function () {
811
+ return null;
812
+ };
813
+ if (cb) {
814
+ cb(err);
815
+ } else {
816
+ executeCommand.emit('error', err);
817
+ }
818
+ executeCommand.emit('end');
819
+ return;
716
820
  }
717
- executeCommand.emit('end');
718
- return;
719
- }
720
- executeCommand.statement = stmt;
721
- });
722
- this.addCommand(prepareCommand);
723
- this.addCommand(executeCommand);
821
+ executeCommand.statement = stmt;
822
+ });
823
+ this.addCommand(prepareCommand);
824
+ this.addCommand(executeCommand);
825
+ return executeCommand;
826
+ }
827
+
828
+ const config = this.config;
829
+ const origExecCb = executeCommand.onResult;
830
+
831
+ if (origExecCb) {
832
+ // Callback mode: use traceCallback for synchronous wrapping
833
+ traceCallback(
834
+ executeChannel,
835
+ (wrappedCb) => {
836
+ const prepareCommand = new Commands.Prepare(options, (err, stmt) => {
837
+ if (err) {
838
+ executeCommand.start = function () {
839
+ return null;
840
+ };
841
+ executeCommand.emit('end');
842
+ wrappedCb(err);
843
+ return;
844
+ }
845
+ executeCommand.statement = stmt;
846
+ });
847
+ executeCommand.onResult = wrappedCb;
848
+ this.addCommand(prepareCommand);
849
+ this.addCommand(executeCommand);
850
+ },
851
+ 0,
852
+ () => {
853
+ const server = getServerContext(config);
854
+ return {
855
+ query: options.sql,
856
+ values: options.values,
857
+ database: config.database || '',
858
+ serverAddress: server.serverAddress,
859
+ serverPort: server.serverPort,
860
+ };
861
+ },
862
+ null,
863
+ origExecCb
864
+ );
865
+ } else {
866
+ // Event-emitter mode: use tracePromise since there is no callback
867
+ tracePromise(
868
+ executeChannel,
869
+ () =>
870
+ new Promise((resolve, reject) => {
871
+ const prepareCommand = new Commands.Prepare(
872
+ options,
873
+ (err, stmt) => {
874
+ if (err) {
875
+ executeCommand.start = function () {
876
+ return null;
877
+ };
878
+ executeCommand.emit('error', err);
879
+ executeCommand.emit('end');
880
+ reject(err);
881
+ return;
882
+ }
883
+ executeCommand.statement = stmt;
884
+ }
885
+ );
886
+ executeCommand.once('error', reject);
887
+ executeCommand.once('end', () => resolve());
888
+ this.addCommand(prepareCommand);
889
+ this.addCommand(executeCommand);
890
+ }),
891
+ () => {
892
+ const server = getServerContext(config);
893
+ return {
894
+ query: options.sql,
895
+ values: options.values,
896
+ database: config.database || '',
897
+ serverAddress: server.serverAddress,
898
+ serverPort: server.serverPort,
899
+ };
900
+ }
901
+ ).catch(() => {
902
+ // errors are already emitted on the command
903
+ });
904
+ }
905
+
724
906
  return executeCommand;
725
907
  }
726
908
 
package/lib/base/pool.js CHANGED
@@ -7,6 +7,12 @@ const PoolConnection = require('../pool_connection.js');
7
7
  const Queue = require('denque');
8
8
  const BaseConnection = require('./connection.js');
9
9
  const Errors = require('../constants/errors.js');
10
+ const {
11
+ traceCallback,
12
+ getServerContext,
13
+ shouldTrace,
14
+ poolConnectChannel,
15
+ } = require('../tracing.js');
10
16
 
11
17
  // Source: https://github.com/go-sql-driver/mysql/blob/76c00e35a8d48f8f70f0e7dffe584692bd3fa612/packets.go#L598-L613
12
18
  function isReadOnlyError(err) {
@@ -49,6 +55,28 @@ class BasePool extends EventEmitter {
49
55
  }
50
56
 
51
57
  getConnection(cb) {
58
+ if (!shouldTrace(poolConnectChannel)) {
59
+ return this._getConnection(cb);
60
+ }
61
+ const config = this.config.connectionConfig;
62
+ traceCallback(
63
+ poolConnectChannel,
64
+ this._getConnection.bind(this),
65
+ 0,
66
+ () => {
67
+ const server = getServerContext(config);
68
+ return {
69
+ database: config.database || '',
70
+ serverAddress: server.serverAddress,
71
+ serverPort: server.serverPort,
72
+ };
73
+ },
74
+ null,
75
+ cb
76
+ );
77
+ }
78
+
79
+ _getConnection(cb) {
52
80
  if (this._closed) {
53
81
  return process.nextTick(() => cb(new Error('Pool is closed.')));
54
82
  }
@@ -0,0 +1,71 @@
1
+ import type { TracingChannel } from 'node:diagnostics_channel';
2
+
3
+ export interface QueryTraceContext {
4
+ query: string;
5
+ values: any;
6
+ database: string;
7
+ serverAddress: string;
8
+ serverPort: number | undefined;
9
+ }
10
+
11
+ export interface ExecuteTraceContext {
12
+ query: string;
13
+ values: any;
14
+ database: string;
15
+ serverAddress: string;
16
+ serverPort: number | undefined;
17
+ }
18
+
19
+ export interface ConnectTraceContext {
20
+ database: string;
21
+ serverAddress: string;
22
+ serverPort: number | undefined;
23
+ user: string;
24
+ }
25
+
26
+ export interface PoolConnectTraceContext {
27
+ database: string;
28
+ serverAddress: string;
29
+ serverPort: number | undefined;
30
+ }
31
+
32
+ export declare const dc: typeof import('node:diagnostics_channel') | undefined;
33
+ export declare const hasTracingChannel: boolean;
34
+
35
+ export declare function shouldTrace(
36
+ channel: TracingChannel<object> | undefined | null
37
+ ): boolean;
38
+
39
+ export declare function traceCallback<T extends object>(
40
+ channel: TracingChannel<T> | undefined | null,
41
+ fn: (...args: any[]) => any,
42
+ position: number,
43
+ contextFactory: () => T,
44
+ thisArg: any,
45
+ ...args: any[]
46
+ ): any;
47
+
48
+ export declare function tracePromise<T extends object, R>(
49
+ channel: TracingChannel<T> | undefined | null,
50
+ fn: () => Promise<R>,
51
+ contextFactory: () => T
52
+ ): Promise<R>;
53
+
54
+ export declare const queryChannel:
55
+ | TracingChannel<QueryTraceContext>
56
+ | undefined;
57
+ export declare const executeChannel:
58
+ | TracingChannel<ExecuteTraceContext>
59
+ | undefined;
60
+ export declare const connectChannel:
61
+ | TracingChannel<ConnectTraceContext>
62
+ | undefined;
63
+ export declare const poolConnectChannel:
64
+ | TracingChannel<PoolConnectTraceContext>
65
+ | undefined;
66
+
67
+ export declare function getServerContext(config: {
68
+ socketPath?: string;
69
+ host?: string;
70
+ port?: number;
71
+ }): { serverAddress: string; serverPort: number | undefined };
package/lib/tracing.js ADDED
@@ -0,0 +1,81 @@
1
+ 'use strict';
2
+
3
+ const process = require('process');
4
+
5
+ // Safe load: use getBuiltinModule if available, fallback to require, catch if unavailable
6
+ const dc = (() => {
7
+ try {
8
+ return 'getBuiltinModule' in process
9
+ ? process.getBuiltinModule('node:diagnostics_channel')
10
+ : require('node:diagnostics_channel');
11
+ } catch {
12
+ return undefined;
13
+ }
14
+ })();
15
+
16
+ const hasTracingChannel = typeof dc?.tracingChannel === 'function';
17
+
18
+ const queryChannel = hasTracingChannel
19
+ ? dc.tracingChannel('mysql2:query')
20
+ : undefined;
21
+
22
+ const executeChannel = hasTracingChannel
23
+ ? dc.tracingChannel('mysql2:execute')
24
+ : undefined;
25
+
26
+ const connectChannel = hasTracingChannel
27
+ ? dc.tracingChannel('mysql2:connect')
28
+ : undefined;
29
+
30
+ const poolConnectChannel = hasTracingChannel
31
+ ? dc.tracingChannel('mysql2:pool:connect')
32
+ : undefined;
33
+
34
+ function getServerContext(config) {
35
+ if (config.socketPath) {
36
+ return { serverAddress: config.socketPath, serverPort: undefined };
37
+ }
38
+ return {
39
+ serverAddress: config.host || 'localhost',
40
+ serverPort: config.port || 3306,
41
+ };
42
+ }
43
+
44
+ // Node 20+: TracingChannel has an aggregated hasSubscribers getter.
45
+ // Node 18.x: that getter is missing (undefined), fall back to start sub-channel.
46
+ function shouldTrace(channel) {
47
+ if (channel === undefined || channel === null) {
48
+ return false;
49
+ }
50
+ return channel.hasSubscribers ?? channel.start?.hasSubscribers ?? false;
51
+ }
52
+
53
+ // Generic traceCallback wrapper — calls fn synchronously, wraps the callback
54
+ // at args[position] to emit asyncStart/asyncEnd/error. No promises involved.
55
+ function traceCallback(channel, fn, position, context, thisArg, ...args) {
56
+ if (shouldTrace(channel)) {
57
+ return channel.traceCallback(fn, position, context(), thisArg, ...args);
58
+ }
59
+ return fn.apply(thisArg, args);
60
+ }
61
+
62
+ // tracePromise for operations that are inherently async (connection handshake)
63
+ function tracePromise(channel, fn, contextFactory) {
64
+ if (shouldTrace(channel)) {
65
+ return channel.tracePromise(fn, contextFactory());
66
+ }
67
+ return fn();
68
+ }
69
+
70
+ module.exports = {
71
+ dc,
72
+ hasTracingChannel,
73
+ shouldTrace,
74
+ queryChannel,
75
+ executeChannel,
76
+ connectChannel,
77
+ poolConnectChannel,
78
+ getServerContext,
79
+ traceCallback,
80
+ tracePromise,
81
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mysql2",
3
- "version": "3.19.1",
3
+ "version": "3.19.2-canary.c06afc25",
4
4
  "description": "fast mysql driver. Implements core protocol, prepared statements, ssl and compression in native JS",
5
5
  "main": "index.js",
6
6
  "typings": "typings/mysql/index",
@@ -8,15 +8,9 @@
8
8
  "scripts": {
9
9
  "lint": "eslint . && prettier --check .",
10
10
  "lint:fix": "eslint . --fix && prettier --write .",
11
- "test": "npm run test:parallel && npm run test:global",
12
- "test:parallel": "poku -c=\"poku.config.mjs\" test",
13
- "test:global": "cross-env SUITE=global poku -c=\"poku.config.mjs\" test/global",
14
- "test:bun": "npm run test:bun:parallel && npm run test:bun:global",
15
- "test:bun:parallel": "bun poku -c=\"poku.config.mjs\" test",
16
- "test:bun:global": "cross-env SUITE=global bun poku -c=\"poku.config.mjs\" test/global",
17
- "test:deno": "npm run test:deno:parallel && npm run test:deno:global",
18
- "test:deno:parallel": "deno run --allow-read --allow-env --allow-run npm:poku -c=\"poku.config.mjs\" test",
19
- "test:deno:global": "cross-env SUITE=global deno run --allow-read --allow-env --allow-run npm:poku -c=\"poku.config.mjs\" test/global",
11
+ "test": "poku",
12
+ "test:bun": "bun poku",
13
+ "test:deno": "deno run -A npm:poku",
20
14
  "test:docker:up": "docker compose -f test/docker-compose.yml up --abort-on-container-exit --remove-orphans",
21
15
  "test:docker:down": "docker compose -f test/docker-compose.yml down",
22
16
  "test:docker:node": "npm run test:docker:up -- node && npm run test:docker:down",
@@ -75,19 +69,19 @@
75
69
  "@eslint/js": "^9.39.2",
76
70
  "@eslint/markdown": "^7.5.1",
77
71
  "@ianvs/prettier-plugin-sort-imports": "^4.7.1",
72
+ "@pokujs/multi-suite": "^1.0.0",
78
73
  "@types/node": "^25.3.0",
79
74
  "@typescript-eslint/eslint-plugin": "^8.56.0",
80
75
  "@typescript-eslint/parser": "^8.56.0",
81
76
  "assert-diff": "^3.0.4",
82
77
  "benchmark": "^2.1.4",
83
78
  "c8": "^11.0.0",
84
- "cross-env": "^10.1.0",
85
79
  "error-stack-parser": "^2.1.4",
86
80
  "eslint-config-prettier": "^10.1.8",
87
81
  "eslint-plugin-async-await": "^0.0.0",
88
82
  "eslint-plugin-prettier": "^5.5.5",
89
83
  "globals": "^17.3.0",
90
- "poku": "^4.0.0",
84
+ "poku": "^4.1.0",
91
85
  "portfinder": "^1.0.38",
92
86
  "prettier": "^3.8.1",
93
87
  "tsx": "^4.21.0",
@@ -82,3 +82,10 @@ export interface ConnectionConfig extends ConnectionOptions {
82
82
  }
83
83
 
84
84
  export function createServer(handler: (conn: BaseConnection) => any): Server;
85
+
86
+ export type {
87
+ QueryTraceContext,
88
+ ExecuteTraceContext,
89
+ ConnectTraceContext,
90
+ PoolConnectTraceContext,
91
+ } from '../../lib/tracing.js';