diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index f85b366e3f01c..bee0190a07a81 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -2709,6 +2709,24 @@ const getBinaryHelperFunctions = ( }, }); +/** + * Returns a copy of the items which only contains the json data and + * of that only the defined properties + */ +export function copyInputItems(items: INodeExecutionData[], properties: string[]): IDataObject[] { + return items.map((item) => { + const newItem: IDataObject = {}; + for (const property of properties) { + if (item.json[property] === undefined) { + newItem[property] = null; + } else { + newItem[property] = deepCopy(item.json[property]); + } + } + return newItem; + }); +} + /** * Returns the execute functions the poll nodes have access to. */ @@ -3239,6 +3257,7 @@ export function getExecuteFunctions( }, helpers: { createDeferredPromise, + copyInputItems, ...getRequestHelperFunctions(workflow, node, additionalData), ...getFileSystemHelperFunctions(node), ...getBinaryHelperFunctions(additionalData, workflow.id), diff --git a/packages/core/test/NodeExecuteFunctions.test.ts b/packages/core/test/NodeExecuteFunctions.test.ts index bba705ab8f324..5009159921def 100644 --- a/packages/core/test/NodeExecuteFunctions.test.ts +++ b/packages/core/test/NodeExecuteFunctions.test.ts @@ -1,4 +1,5 @@ import { + copyInputItems, getBinaryDataBuffer, parseIncomingMessage, proxyRequestToAxios, @@ -297,4 +298,52 @@ describe('NodeExecuteFunctions', () => { ]); }); }); + + describe('copyInputItems', () => { + it('should pick only selected properties', () => { + const output = copyInputItems( + [ + { + json: { + a: 1, + b: true, + c: {}, + }, + }, + ], + ['a'], + ); + expect(output).toEqual([{ a: 1 }]); + }); + + it('should convert undefined to null', () => { + const output = copyInputItems( + [ + { + json: { + a: undefined, + }, + }, + ], + ['a'], + ); + expect(output).toEqual([{ a: null }]); + }); + + it('should clone objects', () => { + const input = { + a: { b: 5 }, + }; + const output = copyInputItems( + [ + { + json: input, + }, + ], + ['a'], + ); + expect(output[0].a).toEqual(input.a); + expect(output[0].a === input.a).toEqual(false); + }); + }); }); diff --git a/packages/nodes-base/nodes/MySql/v1/GenericFunctions.ts b/packages/nodes-base/nodes/MySql/v1/GenericFunctions.ts index fd2c9ba9345a5..0f9976abcadcd 100644 --- a/packages/nodes-base/nodes/MySql/v1/GenericFunctions.ts +++ b/packages/nodes-base/nodes/MySql/v1/GenericFunctions.ts @@ -2,35 +2,10 @@ import type { ICredentialDataDecryptedObject, IDataObject, ILoadOptionsFunctions, - INodeExecutionData, INodeListSearchResult, } from 'n8n-workflow'; -import { deepCopy } from 'n8n-workflow'; import mysql2 from 'mysql2/promise'; -/** - * Returns of copy of the items which only contains the json data and - * of that only the define properties - * - * @param {INodeExecutionData[]} items The items to copy - * @param {string[]} properties The properties it should include - */ -export function copyInputItems(items: INodeExecutionData[], properties: string[]): IDataObject[] { - // Prepare the data to insert and copy it to be returned - let newItem: IDataObject; - return items.map((item) => { - newItem = {}; - for (const property of properties) { - if (item.json[property] === undefined) { - newItem[property] = null; - } else { - newItem[property] = deepCopy(item.json[property]); - } - } - return newItem; - }); -} - export async function createConnection( credentials: ICredentialDataDecryptedObject, ): Promise { diff --git a/packages/nodes-base/nodes/MySql/v1/MySqlV1.node.ts b/packages/nodes-base/nodes/MySql/v1/MySqlV1.node.ts index 2b0f681fa663a..4d5d43f831a33 100644 --- a/packages/nodes-base/nodes/MySql/v1/MySqlV1.node.ts +++ b/packages/nodes-base/nodes/MySql/v1/MySqlV1.node.ts @@ -15,7 +15,7 @@ import { NodeOperationError } from 'n8n-workflow'; import type mysql2 from 'mysql2/promise'; -import { copyInputItems, createConnection, searchTables } from './GenericFunctions'; +import { createConnection, searchTables } from './GenericFunctions'; import { oldVersionNotice } from '@utils/descriptions'; @@ -344,7 +344,7 @@ export class MySqlV1 implements INodeType { const table = this.getNodeParameter('table', 0, '', { extractValue: true }) as string; const columnString = this.getNodeParameter('columns', 0) as string; const columns = columnString.split(',').map((column) => column.trim()); - const insertItems = copyInputItems(items, columns); + const insertItems = this.helpers.copyInputItems(items, columns); const insertPlaceholder = `(${columns.map((_column) => '?').join(',')})`; const options = this.getNodeParameter('options', 0); const insertIgnore = options.ignore as boolean; @@ -387,7 +387,7 @@ export class MySqlV1 implements INodeType { columns.unshift(updateKey); } - const updateItems = copyInputItems(items, columns); + const updateItems = this.helpers.copyInputItems(items, columns); const updateSQL = `UPDATE ${table} SET ${columns .map((column) => `${column} = ?`) .join(',')} WHERE ${updateKey} = ?;`; diff --git a/packages/nodes-base/nodes/MySql/v2/actions/database/insert.operation.ts b/packages/nodes-base/nodes/MySql/v2/actions/database/insert.operation.ts index 14588ef1df5c9..8773b597f1410 100644 --- a/packages/nodes-base/nodes/MySql/v2/actions/database/insert.operation.ts +++ b/packages/nodes-base/nodes/MySql/v2/actions/database/insert.operation.ts @@ -16,7 +16,7 @@ import { AUTO_MAP, BATCH_MODE, DATA_MODE } from '../../helpers/interfaces'; import { updateDisplayOptions } from '@utils/utilities'; -import { copyInputItems, replaceEmptyStringsByNulls } from '../../helpers/utils'; +import { replaceEmptyStringsByNulls } from '../../helpers/utils'; import { optionsCollection } from '../common.descriptions'; @@ -146,7 +146,7 @@ export async function execute( }, [] as string[]), ), ]; - insertItems = copyInputItems(items, columns); + insertItems = this.helpers.copyInputItems(items, columns); } if (dataMode === DATA_MODE.MANUAL) { diff --git a/packages/nodes-base/nodes/MySql/v2/helpers/utils.ts b/packages/nodes-base/nodes/MySql/v2/helpers/utils.ts index e2b5e5fbdef96..42f59bdf0c07f 100644 --- a/packages/nodes-base/nodes/MySql/v2/helpers/utils.ts +++ b/packages/nodes-base/nodes/MySql/v2/helpers/utils.ts @@ -7,7 +7,7 @@ import type { NodeExecutionWithMetadata, } from 'n8n-workflow'; -import { NodeOperationError, deepCopy } from 'n8n-workflow'; +import { NodeOperationError } from 'n8n-workflow'; import type { Mysql2Pool, @@ -20,22 +20,6 @@ import type { import { BATCH_MODE } from './interfaces'; -export function copyInputItems(items: INodeExecutionData[], properties: string[]): IDataObject[] { - // Prepare the data to insert and copy it to be returned - let newItem: IDataObject; - return items.map((item) => { - newItem = {}; - for (const property of properties) { - if (item.json[property] === undefined) { - newItem[property] = null; - } else { - newItem[property] = deepCopy(item.json[property]); - } - } - return newItem; - }); -} - export const prepareQueryAndReplacements = (rawQuery: string, replacements?: QueryValues) => { if (replacements === undefined) { return { query: rawQuery, values: [] }; diff --git a/packages/nodes-base/nodes/Snowflake/GenericFunctions.ts b/packages/nodes-base/nodes/Snowflake/GenericFunctions.ts index efa66cf4bd032..52bad217525c1 100644 --- a/packages/nodes-base/nodes/Snowflake/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Snowflake/GenericFunctions.ts @@ -1,31 +1,14 @@ -import type { IDataObject, INodeExecutionData } from 'n8n-workflow'; -import { deepCopy } from 'n8n-workflow'; - import type snowflake from 'snowflake-sdk'; export async function connect(conn: snowflake.Connection) { - return new Promise((resolve, reject) => { - conn.connect((err, _conn) => { - if (!err) { - // @ts-ignore - resolve(); - } else { - reject(err); - } - }); + return new Promise((resolve, reject) => { + conn.connect((error) => (error ? reject(error) : resolve())); }); } export async function destroy(conn: snowflake.Connection) { - return new Promise((resolve, reject) => { - conn.destroy((err, _conn) => { - if (!err) { - // @ts-ignore - resolve(); - } else { - reject(err); - } - }); + return new Promise((resolve, reject) => { + conn.destroy((error) => (error ? reject(error) : resolve())); }); } @@ -34,33 +17,11 @@ export async function execute( sqlText: string, binds: snowflake.InsertBinds, ) { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { conn.execute({ sqlText, binds, - complete: (err, stmt, rows) => { - if (!err) { - resolve(rows); - } else { - reject(err); - } - }, + complete: (error, stmt, rows) => (error ? reject(error) : resolve(rows)), }); }); } - -export function copyInputItems(items: INodeExecutionData[], properties: string[]): IDataObject[] { - // Prepare the data to insert and copy it to be returned - let newItem: IDataObject; - return items.map((item) => { - newItem = {}; - for (const property of properties) { - if (item.json[property] === undefined) { - newItem[property] = null; - } else { - newItem[property] = deepCopy(item.json[property]); - } - } - return newItem; - }); -} diff --git a/packages/nodes-base/nodes/Snowflake/Snowflake.node.ts b/packages/nodes-base/nodes/Snowflake/Snowflake.node.ts index cb69884fd28b2..a2edd9d368cb8 100644 --- a/packages/nodes-base/nodes/Snowflake/Snowflake.node.ts +++ b/packages/nodes-base/nodes/Snowflake/Snowflake.node.ts @@ -6,7 +6,7 @@ import type { INodeTypeDescription, } from 'n8n-workflow'; -import { connect, copyInputItems, destroy, execute } from './GenericFunctions'; +import { connect, destroy, execute } from './GenericFunctions'; import snowflake from 'snowflake-sdk'; import { getResolvables } from '@utils/utilities'; @@ -207,7 +207,7 @@ export class Snowflake implements INodeType { const query = `INSERT INTO ${table}(${columns.join(',')}) VALUES (${columns .map((_column) => '?') .join(',')})`; - const data = copyInputItems(items, columns); + const data = this.helpers.copyInputItems(items, columns); const binds = data.map((element) => Object.values(element)); await execute(connection, query, binds as unknown as snowflake.InsertBinds); data.forEach((d, i) => { @@ -236,7 +236,7 @@ export class Snowflake implements INodeType { const query = `UPDATE ${table} SET ${columns .map((column) => `${column} = ?`) .join(',')} WHERE ${updateKey} = ?;`; - const data = copyInputItems(items, columns); + const data = this.helpers.copyInputItems(items, columns); const binds = data.map((element) => Object.values(element).concat(element[updateKey])); for (let i = 0; i < binds.length; i++) { await execute(connection, query, binds[i] as unknown as snowflake.InsertBinds); diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 0bfd7f65a1228..7f2aad82822c6 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -806,6 +806,7 @@ export type IExecuteFunctions = ExecuteFunctions.GetNodeParameterFn & ): NodeExecutionWithMetadata[]; assertBinaryData(itemIndex: number, propertyName: string): IBinaryData; getBinaryDataBuffer(itemIndex: number, propertyName: string): Promise; + copyInputItems(items: INodeExecutionData[], properties: string[]): IDataObject[]; }; };