diff --git a/package-lock.json b/package-lock.json index b0de6be1a8..d4301c6594 100644 --- a/package-lock.json +++ b/package-lock.json @@ -133,7 +133,8 @@ "mssql": "^11.0.1", "mssql-v10": "npm:mssql@^10.0.4", "mysql": "^2.18.1", - "mysql2": "^3.11.4", + "mysql2": "^3.11.5", + "mysql2-v3114": "npm:mysql2@3.11.4", "nan": "^2.22.0", "nats": "^2.28.2", "nats-v1": "npm:nats@^1.4.12", @@ -47183,11 +47184,32 @@ } }, "node_modules/mysql2": { + "version": "3.11.5", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.11.5.tgz", + "integrity": "sha512-0XFu8rUmFN9vC0ME36iBvCUObftiMHItrYFhlCRvFWbLgpNqtC4Br/NmZX1HNCszxT0GGy5QtP+k3Q3eCJPaYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.1", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.3", + "long": "^5.2.1", + "lru.min": "^1.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/mysql2-v3114": { + "name": "mysql2", "version": "3.11.4", "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.11.4.tgz", "integrity": "sha512-Z2o3tY4Z8EvSRDwknaC40MdZ3+m0sKbpnXrShQLdxPrAvcNli7jLrD2Zd2IzsRMw4eK9Yle500FDmlkIqp+krg==", "dev": true, - "license": "MIT", "dependencies": { "aws-ssl-profiles": "^1.1.1", "denque": "^2.1.0", @@ -47203,6 +47225,33 @@ "node": ">= 8.0" } }, + "node_modules/mysql2-v3114/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mysql2-v3114/node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", + "dev": true + }, + "node_modules/mysql2-v3114/node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mysql2/node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", diff --git a/package.json b/package.json index 541a5644bd..9946b4318e 100644 --- a/package.json +++ b/package.json @@ -195,7 +195,8 @@ "mssql": "^11.0.1", "mssql-v10": "npm:mssql@^10.0.4", "mysql": "^2.18.1", - "mysql2": "^3.11.4", + "mysql2": "^3.11.5", + "mysql2-v3114": "npm:mysql2@3.11.4", "nan": "^2.22.0", "nats": "^2.28.2", "nats-v1": "npm:nats@^1.4.12", diff --git a/packages/collector/test/tracing/database/mysql/app.js b/packages/collector/test/tracing/database/mysql/app.js index f7afd45d5a..5dd25fd07c 100644 --- a/packages/collector/test/tracing/database/mysql/app.js +++ b/packages/collector/test/tracing/database/mysql/app.js @@ -14,6 +14,9 @@ process.on('SIGTERM', () => { }); const agentPort = process.env.INSTANA_AGENT_PORT; +if (process.env.MYSQL2_VERSION) { + require('./mockVersion'); +} const instana = require('../../../..')(); const accessFunction = process.env.USE_EXECUTE ? 'execute' : 'query'; diff --git a/packages/collector/test/tracing/database/mysql/mockVersion.js b/packages/collector/test/tracing/database/mysql/mockVersion.js new file mode 100644 index 0000000000..5b2650bf6e --- /dev/null +++ b/packages/collector/test/tracing/database/mysql/mockVersion.js @@ -0,0 +1,13 @@ +/* + * (c) Copyright IBM Corp. 2024 + */ + +'use strict'; + +const mock = require('mock-require'); +const MYSQL2_VERSION = process.env.MYSQL2_VERSION; +const MYSQL2_REQUIRE = process.env.MYSQL2_VERSION === 'latest' ? 'mysql2' : `mysql2-${MYSQL2_VERSION}`; + +if (MYSQL2_REQUIRE !== 'mysql2') { + mock('mysql2', MYSQL2_REQUIRE); +} diff --git a/packages/collector/test/tracing/database/mysql/test.js b/packages/collector/test/tracing/database/mysql/test.js index eb384f3996..73ec445164 100644 --- a/packages/collector/test/tracing/database/mysql/test.js +++ b/packages/collector/test/tracing/database/mysql/test.js @@ -23,23 +23,39 @@ mochaSuiteFn('tracing/mysql', function () { globalAgent.setUpCleanUpHooks(); const agentControls = globalAgent.instance; - ['mysql', 'mysql-cluster', 'mysql2', 'mysql2/promises'].forEach(driverMode => { - [false, true].forEach(function (useExecute) { - // connection.query or connection.execute - registerSuite.bind(this)(agentControls, driverMode, useExecute); - }); + const drivers = ['mysql', 'mysql-cluster', 'mysql2', 'mysql2/promises']; + const mysql2Versions = ['latest', 'v3114']; + const executionModes = [false, true]; + + drivers.forEach(driverMode => { + if (driverMode === 'mysql2') { + // Handling for 'mysql2' with different versions + mysql2Versions.forEach(version => { + executionModes.forEach(useExecute => { + registerSuite.call(this, agentControls, driverMode, useExecute, version); + }); + }); + } else { + // Generic handling for other drivers + executionModes.forEach(useExecute => { + registerSuite.call(this, agentControls, driverMode, useExecute); + }); + } }); }); -function registerSuite(agentControls, driverMode, useExecute) { +function registerSuite(agentControls, driverMode, useExecute, mysql2Version) { if ((driverMode === 'mysql' || driverMode === 'mysql-cluster') && useExecute) { // Not applicable, mysql does not provide an execute function, only the query function whereas mysql2 provides both. return; } - describe(`driver mode: ${driverMode}, access function: ${useExecute ? 'execute' : 'query'}`, () => { + describe(`driver mode: ${driverMode} version: ${mysql2Version || 'default'}, access function: ${ + useExecute ? 'execute' : 'query' + }`, () => { const env = { - DRIVER_MODE: driverMode + DRIVER_MODE: driverMode, + MYSQL2_VERSION: mysql2Version }; if (useExecute) { @@ -51,7 +67,8 @@ function registerSuite(agentControls, driverMode, useExecute) { describe('suppressed', function () { const env = { - DRIVER_MODE: driverMode + DRIVER_MODE: driverMode, + MYSQL2_VERSION: mysql2Version }; if (useExecute) { env.USE_EXECUTE = 'true'; diff --git a/packages/core/src/tracing/instrumentation/database/mysql.js b/packages/core/src/tracing/instrumentation/database/mysql.js index 9bac2e00d4..d8bde74f57 100644 --- a/packages/core/src/tracing/instrumentation/database/mysql.js +++ b/packages/core/src/tracing/instrumentation/database/mysql.js @@ -29,9 +29,26 @@ function instrumentMysql(mysql) { } function instrumentMysql2(mysql) { - instrumentConnection(mysql.Connection.prototype, true); - if (mysql.Pool) { - instrumentPool(mysql.Pool.prototype); + /** + * In mysql2 version 3.11.5 and later, the internal structure of the `Connection` and `Pool` classes was reorganized. + * Methods like `query` and `execute` were moved into the `BaseConnection` class located in `lib/base/connection.js`, + * and similar changes were applied to the `Pool` class. See: https://github.com/sidorares/node-mysql2/pull/3081 + * + * Prior to v3.11.5, the `Connection` and `Pool` prototypes were directly used for instrumentation. + */ + const connectionPrototype = + Object.getPrototypeOf(mysql.Connection.prototype)?.constructor?.name === 'BaseConnection' + ? Object.getPrototypeOf(mysql.Connection.prototype) + : mysql.Connection.prototype; + + const poolPrototype = + mysql.Pool && Object.getPrototypeOf(mysql.Pool.prototype)?.constructor?.name === 'BasePool' + ? Object.getPrototypeOf(mysql.Pool.prototype) + : mysql.Pool?.prototype; + + instrumentConnection(connectionPrototype, true); + if (poolPrototype) { + instrumentPool(poolPrototype); } }