diff --git a/packages/nodes-base/nodes/Postgres/test/v2/operations.test.ts b/packages/nodes-base/nodes/Postgres/test/v2/operations.test.ts index 330d541544f..c470a9d86dc 100644 --- a/packages/nodes-base/nodes/Postgres/test/v2/operations.test.ts +++ b/packages/nodes-base/nodes/Postgres/test/v2/operations.test.ts @@ -504,6 +504,88 @@ describe('Test PostgresV2, executeQuery operation', () => { expect(utils.isJSON).toHaveBeenCalledTimes(1); expect(utils.stringToArray).toHaveBeenCalledTimes(1); }); + + const createMockExecuteForArrayQuery = ( + nodeParameters: IDataObject, + returnArray: unknown[], + matchString: string, + ) => + ({ + getNodeParameter( + parameterName: string, + _itemIndex: number, + fallbackValue?: IDataObject, + options?: IGetNodeParameterOptions, + ) { + const parameter = options?.extractValue ? `${parameterName}.value` : parameterName; + return get(nodeParameters, parameter, fallbackValue); + }, + getNode() { + node.parameters = { ...node.parameters, ...(nodeParameters as INodeParameters) }; + return node; + }, + evaluateExpression(str: string, _: number) { + if (str.includes(matchString)) { + return returnArray; + } + return str.replace('{{', '').replace('}}', ''); + }, + }) as unknown as IExecuteFunctions; + + it.each([ + { + description: 'spread string array across individual bind values', + query: 'INSERT INTO my_table (col1, col2, col3) VALUES ($1, $2, $3)', + queryReplacement: "={{ ['a', 'b', 'c'] }}", + matchString: "['a', 'b', 'c']", + returnArray: ['a', 'b', 'c'], + expectedValues: ['a', 'b', 'c'], + }, + { + description: 'JSON.stringify object elements in array bind values', + query: 'INSERT INTO my_table (col1, col2) VALUES ($1, $2)', + queryReplacement: '={{ [{id: 1}, {id: 2}] }}', + matchString: '[{id: 1}, {id: 2}]', + returnArray: [{ id: 1 }, { id: 2 }], + expectedValues: ['{"id":1}', '{"id":2}'], + }, + { + description: 'handle null, number, and boolean elements in array bind values', + query: 'INSERT INTO my_table (col1, col2, col3) VALUES ($1, $2, $3)', + queryReplacement: '={{ [null, 42, true] }}', + matchString: '[null, 42, true]', + returnArray: [null, 42, true], + expectedValues: [null, 42, true], + }, + ])( + 'should $description', + async ({ query, queryReplacement, matchString, returnArray, expectedValues }) => { + const nodeParameters: IDataObject = { + operation: 'executeQuery', + query, + options: { + queryReplacement, + nodeVersion: 2.6, + }, + }; + const nodeOptions = nodeParameters.options as IDataObject; + + const mockExecute = createMockExecuteForArrayQuery(nodeParameters, returnArray, matchString); + + await executeQuery.execute.call(mockExecute, runQueries, items, nodeOptions); + + expect(runQueries).toHaveBeenCalledWith( + [ + { + query, + values: expectedValues, + options: { partial: true }, + }, + ], + nodeOptions, + ); + }, + ); }); describe('Test PostgresV2, insert operation', () => { diff --git a/packages/nodes-base/nodes/Postgres/v2/actions/database/executeQuery.operation.ts b/packages/nodes-base/nodes/Postgres/v2/actions/database/executeQuery.operation.ts index 43bce0bd984..49265ac6d6d 100644 --- a/packages/nodes-base/nodes/Postgres/v2/actions/database/executeQuery.operation.ts +++ b/packages/nodes-base/nodes/Postgres/v2/actions/database/executeQuery.operation.ts @@ -12,6 +12,7 @@ import type { PgpDatabase, PostgresNodeOptions, QueriesRunner, + QueryValue, QueryWithValues, } from '../../helpers/interfaces'; import { @@ -68,7 +69,7 @@ export async function execute( query = query.replace(resolvable, this.evaluateExpression(resolvable, index) as string); } - let values: Array = []; + let values: QueryValue[] = []; let queryReplacement = this.getNodeParameter('options.queryReplacement', index, ''); @@ -89,9 +90,19 @@ export async function execute( const resolvables = getResolvables(rawValues); if (resolvables.length) { for (const resolvable of resolvables) { - const evaluatedExpression = evaluateExpression( - this.evaluateExpression(`${resolvable}`, index), - ); + const rawEvaluated = this.evaluateExpression(`${resolvable}`, index); + + if (Array.isArray(rawEvaluated)) { + for (const item of rawEvaluated) { + if (item === undefined) continue; + values.push( + typeof item === 'object' && item !== null ? JSON.stringify(item) : item, + ); + } + continue; + } + + const evaluatedExpression = evaluateExpression(rawEvaluated); const evaluatedValues = isJSON(evaluatedExpression) ? [evaluatedExpression] : stringToArray(evaluatedExpression); diff --git a/packages/nodes-base/nodes/Postgres/v2/helpers/interfaces.ts b/packages/nodes-base/nodes/Postgres/v2/helpers/interfaces.ts index 5f4a5cdaf22..fc2d2385bbd 100644 --- a/packages/nodes-base/nodes/Postgres/v2/helpers/interfaces.ts +++ b/packages/nodes-base/nodes/Postgres/v2/helpers/interfaces.ts @@ -5,7 +5,7 @@ import type pg from 'pg-promise/typescript/pg-subset'; export type QueryMode = 'single' | 'transaction' | 'independently'; -export type QueryValue = string | number | IDataObject | string[]; +export type QueryValue = string | number | boolean | null | IDataObject | string[]; export type QueryValues = QueryValue[]; export type QueryWithValues = { query: string; values?: QueryValues; options?: IFormattingOptions };