feat(Postgres Node): Batching warning for executeQuery operation insert query (#14287)

This commit is contained in:
Michael Kret
2025-04-01 10:57:26 +03:00
committed by GitHub
parent c5e2d2dddc
commit f85b851851
4 changed files with 67 additions and 9 deletions

View File

@@ -1,4 +1,4 @@
import type { IDataObject, INode } from 'n8n-workflow'; import type { IDataObject, IExecuteFunctions, INode } from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow';
import pgPromise from 'pg-promise'; import pgPromise from 'pg-promise';
@@ -18,6 +18,7 @@ import {
convertValuesToJsonWithPgp, convertValuesToJsonWithPgp,
hasJsonDataTypeInSchema, hasJsonDataTypeInSchema,
evaluateExpression, evaluateExpression,
addExecutionHints,
} from '../../v2/helpers/utils'; } from '../../v2/helpers/utils';
const node: INode = { const node: INode = {
@@ -535,3 +536,38 @@ describe('Test PostgresV2, convertArraysToPostgresFormat', () => {
}); });
}); });
}); });
describe('Test PostgresV2, addExecutionHints', () => {
it('should add batching insert hint to executeQuery operation', () => {
const context = {
getNodeParameter: (parameterName: string) => {
if (parameterName === 'options.queryBatching') {
return 'single';
}
if (parameterName === 'query') {
return 'INSERT INTO my_test_table VALUES (`{{ $json.name }}`)';
}
},
addExecutionHints: jest.fn(),
} as unknown as IExecuteFunctions;
addExecutionHints(context, [{ json: {} }, { json: {} }], 'executeQuery', false);
expect(context.addExecutionHints).toHaveBeenCalledWith({
message:
"Inserts were batched for performance. If you need to preserve item matching, consider changing 'Query batching' to 'Independent' in the options.",
location: 'outputPane',
});
});
it('should add run per item hint to select operation', () => {
const context = {
addExecutionHints: jest.fn(),
} as unknown as IExecuteFunctions;
addExecutionHints(context, [{ json: {} }, { json: {} }], 'select', false);
expect(context.addExecutionHints).toHaveBeenCalledWith({
location: 'outputPane',
message:
"This node ran 2 times, once for each input item. To run for the first item only, enable 'execute once' in the node settings",
});
});
});

View File

@@ -52,7 +52,7 @@ export const optionsCollection: INodeProperties = {
description: 'A single query for all incoming items', description: 'A single query for all incoming items',
}, },
{ {
name: 'Independently', name: 'Independent',
value: 'independently', value: 'independently',
description: 'Execute one query per incoming item of the run', description: 'Execute one query per incoming item of the run',
}, },

View File

@@ -5,7 +5,7 @@ import * as database from './database/Database.resource';
import type { PostgresType } from './node.type'; import type { PostgresType } from './node.type';
import { configurePostgres } from '../../transport'; import { configurePostgres } from '../../transport';
import type { PostgresNodeCredentials, PostgresNodeOptions } from '../helpers/interfaces'; import type { PostgresNodeCredentials, PostgresNodeOptions } from '../helpers/interfaces';
import { configureQueryRunner } from '../helpers/utils'; import { addExecutionHints, configureQueryRunner } from '../helpers/utils';
export async function router(this: IExecuteFunctions): Promise<INodeExecutionData[][]> { export async function router(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
let returnData: INodeExecutionData[] = []; let returnData: INodeExecutionData[] = [];
@@ -53,12 +53,7 @@ export async function router(this: IExecuteFunctions): Promise<INodeExecutionDat
); );
} }
if (operation === 'select' && items.length > 1 && !node.executeOnce) { addExecutionHints(this, items, operation, node.executeOnce);
this.addExecutionHints({
message: `This node ran ${items.length} times, once for each input item. To run for the first item only, enable 'execute once' in the node settings`,
location: 'outputPane',
});
}
return [returnData]; return [returnData];
} }

View File

@@ -616,3 +616,30 @@ export const convertArraysToPostgresFormat = (
} }
} }
}; };
export function addExecutionHints(
context: IExecuteFunctions,
items: INodeExecutionData[],
operation: string,
executeOnce: boolean | undefined,
) {
if (operation === 'select' && items.length > 1 && !executeOnce) {
context.addExecutionHints({
message: `This node ran ${items.length} times, once for each input item. To run for the first item only, enable 'execute once' in the node settings`,
location: 'outputPane',
});
}
if (
operation === 'executeQuery' &&
items.length > 1 &&
(context.getNodeParameter('options.queryBatching', 0, 'single') as string) === 'single' &&
(context.getNodeParameter('query', 0, '') as string).toLowerCase().startsWith('insert')
) {
context.addExecutionHints({
message:
"Inserts were batched for performance. If you need to preserve item matching, consider changing 'Query batching' to 'Independent' in the options.",
location: 'outputPane',
});
}
}