diff --git a/packages/nodes-base/nodes/MySql/test/v2/mysql.integration.test.ts b/packages/nodes-base/nodes/MySql/test/v2/mysql.integration.test.ts index a7e9ffad928..c824bdf85ca 100644 --- a/packages/nodes-base/nodes/MySql/test/v2/mysql.integration.test.ts +++ b/packages/nodes-base/nodes/MySql/test/v2/mysql.integration.test.ts @@ -101,9 +101,7 @@ describe('MySQL Integration - NODE-4174', () => { expect(result[0][0].json).toHaveProperty('message'); }); - // NODE-4174: createPool() is outside try-catch, so connection errors bypass continueOnFail. - // Remove .fails when fixed. - test.fails('bug: connection error should return error item with continueOnFail', async () => { + test('connection error should return error item with continueOnFail', async () => { const params = { resource: 'database', operation: 'executeQuery', diff --git a/packages/nodes-base/nodes/MySql/test/v2/runQueries.test.ts b/packages/nodes-base/nodes/MySql/test/v2/runQueries.test.ts index 3b07456a96c..39e694351cf 100644 --- a/packages/nodes-base/nodes/MySql/test/v2/runQueries.test.ts +++ b/packages/nodes-base/nodes/MySql/test/v2/runQueries.test.ts @@ -207,4 +207,40 @@ describe('Test MySql V2, runQueries', () => { expect(connectionReleaseSpy).toBeCalledTimes(1); }); + + it('should return error item with continueOnFail = true for connection error', async () => { + const nodeOptions: IDataObject = { queryBatching: BATCH_MODE.SINGLE, nodeVersion: 2 }; + const pool = createFakePool(fakeConnection); + pool.getConnection = jest.fn(() => { + throw new Error('ECONNREFUSED'); + }); + const fakeExecuteFunction = createMockExecuteFunction({}, mySqlMockNode); + fakeExecuteFunction.continueOnFail = () => true; + + const result = await configureQueryRunner.call( + fakeExecuteFunction, + nodeOptions, + pool, + )([{ query: 'SELECT * FROM my_table WHERE id = ?', values: [55] }]); + + expect(result).toEqual([{ json: expect.objectContaining({ message: 'Connection refused' }) }]); + }); + + it('should throw error when continueOnFail = false for connection error', async () => { + const nodeOptions: IDataObject = { queryBatching: BATCH_MODE.SINGLE, nodeVersion: 2 }; + const pool = createFakePool(fakeConnection); + pool.getConnection = jest.fn(() => { + throw new Error('ECONNREFUSED'); + }); + const fakeExecuteFunction = createMockExecuteFunction({}, mySqlMockNode); + fakeExecuteFunction.continueOnFail = () => false; + + await expect( + configureQueryRunner.call( + fakeExecuteFunction, + nodeOptions, + pool, + )([{ query: 'SELECT * FROM my_table WHERE id = ?', values: [55] }]), + ).rejects.toThrow('Connection refused'); + }); }); diff --git a/packages/nodes-base/nodes/MySql/v2/helpers/interfaces.ts b/packages/nodes-base/nodes/MySql/v2/helpers/interfaces.ts index ef95f650321..36f5189b8c3 100644 --- a/packages/nodes-base/nodes/MySql/v2/helpers/interfaces.ts +++ b/packages/nodes-base/nodes/MySql/v2/helpers/interfaces.ts @@ -4,6 +4,7 @@ import type { IDataObject, INodeExecutionData, SSHCredentials } from 'n8n-workfl export type Mysql2Connection = mysql2.Connection; export type Mysql2Pool = mysql2.Pool; export type Mysql2OkPacket = mysql2.OkPacket; +export type Mysql2PoolConnection = mysql2.PoolConnection; export type QueryValues = Array; export type QueryWithValues = { query: string; values: QueryValues }; diff --git a/packages/nodes-base/nodes/MySql/v2/helpers/utils.ts b/packages/nodes-base/nodes/MySql/v2/helpers/utils.ts index 95af0e0e718..333f3f523a1 100644 --- a/packages/nodes-base/nodes/MySql/v2/helpers/utils.ts +++ b/packages/nodes-base/nodes/MySql/v2/helpers/utils.ts @@ -10,6 +10,7 @@ import { NodeOperationError } from 'n8n-workflow'; import type { Mysql2Pool, + Mysql2PoolConnection, ParameterMatch, QueryMode, QueryValues, @@ -320,7 +321,17 @@ export function configureQueryRunner( let returnData: INodeExecutionData[] = []; const mode = (options.queryBatching as QueryMode) || BATCH_MODE.SINGLE; - const connection = await pool.getConnection(); + let connection: Mysql2PoolConnection; + try { + connection = await pool.getConnection(); + } catch (e) { + const error = parseMySqlError.call(this, e); + if (!this.continueOnFail()) { + throw error; + } + + return [{ json: { message: error.message, error: { ...error } } }]; + } if (mode === BATCH_MODE.SINGLE) { const formattedQueries = queries.map(({ query, values }) => connection.format(query, values)); @@ -533,7 +544,7 @@ export function addWhereClauses( }${valueReplacement}${operator}`; }); - return [`${query}${whereQuery}`, replacements.concat(...values)]; + return [`${query}${whereQuery}`, replacements.concat.apply(replacements, values)]; } export function addSortRules( @@ -552,7 +563,7 @@ export function addSortRules( orderByQuery += ` ${escapeSqlIdentifier(rule.column)} ${direction}${endWith}`; }); - return [`${query}${orderByQuery}`, replacements.concat(...values)]; + return [`${query}${orderByQuery}`, replacements.concat.apply(replacements, values)]; } export function replaceEmptyStringsByNulls(