From 5c46bb09c137023608119093cabdf896555b22b9 Mon Sep 17 00:00:00 2001 From: Michael Kret <88898367+michael-radency@users.noreply.github.com> Date: Fri, 20 Oct 2023 11:09:39 +0300 Subject: [PATCH] fix(MySQL Node): Resolve expressions in v1 (#7464) Github issue / Community forum post (link here to close automatically): --- .../nodes/MySql/test/v1/executeQuery.test.ts | 60 +++++++++ .../MySql/test/v1/executeQuery.workflow.json | 127 ++++++++++++++++++ .../nodes-base/nodes/MySql/v1/MySqlV1.node.ts | 12 +- 3 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 packages/nodes-base/nodes/MySql/test/v1/executeQuery.test.ts create mode 100644 packages/nodes-base/nodes/MySql/test/v1/executeQuery.workflow.json diff --git a/packages/nodes-base/nodes/MySql/test/v1/executeQuery.test.ts b/packages/nodes-base/nodes/MySql/test/v1/executeQuery.test.ts new file mode 100644 index 0000000000..459e347592 --- /dev/null +++ b/packages/nodes-base/nodes/MySql/test/v1/executeQuery.test.ts @@ -0,0 +1,60 @@ +import type { INodeTypes } from 'n8n-workflow'; + +import nock from 'nock'; +import { getResultNodeData, setup, workflowToTests } from '@test/nodes/Helpers'; +import type { WorkflowTestData } from '@test/nodes/types'; +import { executeWorkflow } from '@test/nodes/ExecuteWorkflow'; + +const queryMock = jest.fn(async function () { + return [{ success: true }]; +}); + +jest.mock('../../v1/GenericFunctions', () => { + const originalModule = jest.requireActual('../../v1/GenericFunctions'); + return { + ...originalModule, + createConnection: jest.fn(async function () { + return { + query: queryMock, + end: jest.fn(), + }; + }), + }; +}); + +describe('Test MySqlV1, executeQuery', () => { + const workflows = ['nodes/MySql/test/v1/executeQuery.workflow.json']; + const tests = workflowToTests(workflows); + + beforeAll(() => { + nock.disableNetConnect(); + }); + + afterAll(() => { + nock.restore(); + jest.unmock('../../v1/GenericFunctions'); + }); + + const nodeTypes = setup(tests); + + const testNode = async (testData: WorkflowTestData, types: INodeTypes) => { + const { result } = await executeWorkflow(testData, types); + + const resultNodeData = getResultNodeData(result, testData); + + resultNodeData.forEach(({ nodeName, resultData }) => { + return expect(resultData).toEqual(testData.output.nodeData[nodeName]); + }); + + expect(queryMock).toHaveBeenCalledTimes(1); + expect(queryMock).toHaveBeenCalledWith( + "select * from family_parents where (parent_email = 'parent1@mail.com' or parent_email = 'parent2@mail.com') and parent_email <> '';", + ); + + expect(result.finished).toEqual(true); + }; + + for (const testData of tests) { + test(testData.description, async () => testNode(testData, nodeTypes)); + } +}); diff --git a/packages/nodes-base/nodes/MySql/test/v1/executeQuery.workflow.json b/packages/nodes-base/nodes/MySql/test/v1/executeQuery.workflow.json new file mode 100644 index 0000000000..add3572d40 --- /dev/null +++ b/packages/nodes-base/nodes/MySql/test/v1/executeQuery.workflow.json @@ -0,0 +1,127 @@ +{ + "name": "mysql v1 resolve expression copy", + "nodes": [ + { + "parameters": {}, + "id": "d6d9fbcc-d8bc-4f79-8e00-3acf8ffb12de", + "name": "When clicking \"Execute Workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 460, + 460 + ] + }, + { + "parameters": { + "operation": "executeQuery", + "query": "select * from family_parents where (parent_email = {{ \"'\" + $json['Parent 1 email'] + \"'\" }} or parent_email = {{ \"'\" + $json['Parent 2 email'] + \"'\"}}) and parent_email <> '';\n" + }, + "id": "faefc24c-91b4-4b10-85a6-b3cecbceee08", + "name": "Get matching families", + "type": "n8n-nodes-base.mySql", + "typeVersion": 1, + "position": [ + 900, + 460 + ], + "credentials": { + "mySql": { + "id": "93", + "name": "MySQL account" + } + } + }, + { + "parameters": {}, + "id": "29c30f6e-9f5f-4b3a-80c4-2da762e96bd9", + "name": "No Operation, do nothing1", + "type": "n8n-nodes-base.noOp", + "typeVersion": 1, + "position": [ + 1120, + 460 + ] + }, + { + "parameters": { + "fields": { + "values": [ + { + "name": "Parent 1 email", + "stringValue": "parent1@mail.com" + }, + { + "name": "Parent 2 email", + "stringValue": "parent2@mail.com" + } + ] + }, + "include": "none", + "options": {} + }, + "id": "54c7bbf9-dabc-421b-85b2-7c3006f5ee61", + "name": "Edit Fields", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 680, + 460 + ] + } + ], + "pinData": { + "No Operation, do nothing1": [ + { + "json": { + "success": true + } + } + ] + }, + "connections": { + "When clicking \"Execute Workflow\"": { + "main": [ + [ + { + "node": "Edit Fields", + "type": "main", + "index": 0 + } + ] + ] + }, + "Get matching families": { + "main": [ + [ + { + "node": "No Operation, do nothing1", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields": { + "main": [ + [ + { + "node": "Get matching families", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": false, + "settings": { + "executionOrder": "v1" + }, + "versionId": "aeb01d24-c117-405a-875f-909ea8ccdc16", + "id": "GlTwlHZfQwNjbeqv", + "meta": { + "instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363" + }, + "tags": [] +} diff --git a/packages/nodes-base/nodes/MySql/v1/MySqlV1.node.ts b/packages/nodes-base/nodes/MySql/v1/MySqlV1.node.ts index 4d5d43f831..79d8112ed9 100644 --- a/packages/nodes-base/nodes/MySql/v1/MySqlV1.node.ts +++ b/packages/nodes-base/nodes/MySql/v1/MySqlV1.node.ts @@ -18,6 +18,7 @@ import type mysql2 from 'mysql2/promise'; import { createConnection, searchTables } from './GenericFunctions'; import { oldVersionNotice } from '@utils/descriptions'; +import { getResolvables } from '@utils/utilities'; const versionDescription: INodeTypeDescription = { displayName: 'MySQL', @@ -306,8 +307,15 @@ export class MySqlV1 implements INodeType { // ---------------------------------- try { - const queryQueue = items.map(async (item, index) => { - const rawQuery = this.getNodeParameter('query', index) as string; + const queryQueue = items.map(async (_, index) => { + let rawQuery = (this.getNodeParameter('query', index) as string).trim(); + + for (const resolvable of getResolvables(rawQuery)) { + rawQuery = rawQuery.replace( + resolvable, + this.evaluateExpression(resolvable, index) as string, + ); + } return connection.query(rawQuery); });