mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +00:00
feat(MySQL Node): Hints for executeQuery and select operations (#16753)
This commit is contained in:
@@ -70,7 +70,7 @@ export const optionsCollection: INodeProperties = {
|
||||
description: 'A single query for all incoming items',
|
||||
},
|
||||
{
|
||||
name: 'Independently',
|
||||
name: 'Independent',
|
||||
value: BATCH_MODE.INDEPENDENTLY,
|
||||
description: 'Execute one query per incoming item of the run',
|
||||
},
|
||||
|
||||
@@ -3,6 +3,7 @@ import { NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
import * as database from './database/Database.resource';
|
||||
import type { MySqlType } from './node.type';
|
||||
import { addExecutionHints } from '../../../../utils/utilities';
|
||||
import type { MysqlNodeCredentials, QueryRunner } from '../helpers/interfaces';
|
||||
import { configureQueryRunner } from '../helpers/utils';
|
||||
import { createPool } from '../transport';
|
||||
@@ -10,11 +11,13 @@ import { createPool } from '../transport';
|
||||
export async function router(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
let returnData: INodeExecutionData[] = [];
|
||||
|
||||
const items = this.getInputData();
|
||||
const resource = this.getNodeParameter<MySqlType>('resource', 0);
|
||||
const operation = this.getNodeParameter('operation', 0);
|
||||
const nodeOptions = this.getNodeParameter('options', 0);
|
||||
const node = this.getNode();
|
||||
|
||||
nodeOptions.nodeVersion = this.getNode().typeVersion;
|
||||
nodeOptions.nodeVersion = node.typeVersion;
|
||||
|
||||
const credentials = await this.getCredentials<MysqlNodeCredentials>('mySql');
|
||||
|
||||
@@ -30,8 +33,6 @@ export async function router(this: IExecuteFunctions): Promise<INodeExecutionDat
|
||||
try {
|
||||
switch (mysqlNodeData.resource) {
|
||||
case 'database':
|
||||
const items = this.getInputData();
|
||||
|
||||
returnData = await database[mysqlNodeData.operation].execute.call(
|
||||
this,
|
||||
items,
|
||||
@@ -49,5 +50,7 @@ export async function router(this: IExecuteFunctions): Promise<INodeExecutionDat
|
||||
await pool.end();
|
||||
}
|
||||
|
||||
addExecutionHints(this, node, items, operation, node.executeOnce);
|
||||
|
||||
return [returnData];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { IDataObject, IExecuteFunctions, INode } from 'n8n-workflow';
|
||||
import type { IDataObject, INode } from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
import pgPromise from 'pg-promise';
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
convertValuesToJsonWithPgp,
|
||||
hasJsonDataTypeInSchema,
|
||||
evaluateExpression,
|
||||
addExecutionHints,
|
||||
} from '../../v2/helpers/utils';
|
||||
|
||||
const node: INode = {
|
||||
@@ -547,38 +546,3 @@ 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",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,9 +3,10 @@ import { NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
import * as database from './database/Database.resource';
|
||||
import type { PostgresType } from './node.type';
|
||||
import { addExecutionHints } from '../../../../utils/utilities';
|
||||
import { configurePostgres } from '../../transport';
|
||||
import type { PostgresNodeCredentials, PostgresNodeOptions } from '../helpers/interfaces';
|
||||
import { addExecutionHints, configureQueryRunner } from '../helpers/utils';
|
||||
import { configureQueryRunner } from '../helpers/utils';
|
||||
|
||||
export async function router(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
let returnData: INodeExecutionData[] = [];
|
||||
@@ -53,7 +54,7 @@ export async function router(this: IExecuteFunctions): Promise<INodeExecutionDat
|
||||
);
|
||||
}
|
||||
|
||||
addExecutionHints(this, items, operation, node.executeOnce);
|
||||
addExecutionHints(this, node, items, operation, node.executeOnce);
|
||||
|
||||
return [returnData];
|
||||
}
|
||||
|
||||
@@ -619,30 +619,3 @@ 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',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import type { INodeExecutionData } from 'n8n-workflow';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import type { INode, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
|
||||
import { MYSQL_NODE_TYPE, POSTGRES_NODE_TYPE } from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
addExecutionHints,
|
||||
compareItems,
|
||||
flattenKeys,
|
||||
fuzzyCompare,
|
||||
@@ -323,3 +326,89 @@ describe('removeTrailingSlash', () => {
|
||||
expect(removeTrailingSlash('https://example.com')).toBe('https://example.com');
|
||||
});
|
||||
});
|
||||
|
||||
describe('addExecutionHints', () => {
|
||||
const executeQueryOperationContext = {
|
||||
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;
|
||||
|
||||
const insertHint = {
|
||||
message:
|
||||
"Inserts were batched for performance. If you need to preserve item matching, consider changing 'Query batching' to 'Independent' in the options.",
|
||||
location: 'outputPane',
|
||||
};
|
||||
|
||||
const selectHint = {
|
||||
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",
|
||||
};
|
||||
|
||||
it('should add batching insert hint to Postgres executeQuery operation', () => {
|
||||
addExecutionHints(
|
||||
executeQueryOperationContext,
|
||||
mock<INode>({
|
||||
type: POSTGRES_NODE_TYPE,
|
||||
}),
|
||||
[{ json: {} }, { json: {} }],
|
||||
'executeQuery',
|
||||
false,
|
||||
);
|
||||
expect(executeQueryOperationContext.addExecutionHints).toHaveBeenCalledWith(insertHint);
|
||||
});
|
||||
|
||||
it('should add batching insert hint to MySql executeQuery operation', () => {
|
||||
addExecutionHints(
|
||||
executeQueryOperationContext,
|
||||
mock<INode>({
|
||||
type: MYSQL_NODE_TYPE,
|
||||
}),
|
||||
[{ json: {} }, { json: {} }],
|
||||
'executeQuery',
|
||||
false,
|
||||
);
|
||||
expect(executeQueryOperationContext.addExecutionHints).toHaveBeenCalledWith(insertHint);
|
||||
});
|
||||
|
||||
it('should add run per item hint to Postgres select operation ', () => {
|
||||
const context = {
|
||||
addExecutionHints: jest.fn(),
|
||||
} as unknown as IExecuteFunctions;
|
||||
|
||||
addExecutionHints(
|
||||
context,
|
||||
mock<INode>({
|
||||
type: POSTGRES_NODE_TYPE,
|
||||
}),
|
||||
[{ json: {} }, { json: {} }],
|
||||
'select',
|
||||
false,
|
||||
);
|
||||
expect(context.addExecutionHints).toHaveBeenCalledWith(selectHint);
|
||||
});
|
||||
|
||||
it('should add run per item hint to MySQL select operation', () => {
|
||||
const context = {
|
||||
addExecutionHints: jest.fn(),
|
||||
} as unknown as IExecuteFunctions;
|
||||
|
||||
addExecutionHints(
|
||||
context,
|
||||
mock<INode>({
|
||||
type: MYSQL_NODE_TYPE,
|
||||
}),
|
||||
[{ json: {} }, { json: {} }],
|
||||
'select',
|
||||
false,
|
||||
);
|
||||
expect(context.addExecutionHints).toHaveBeenCalledWith(selectHint);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,11 +7,19 @@ import reduce from 'lodash/reduce';
|
||||
import type {
|
||||
IDataObject,
|
||||
IDisplayOptions,
|
||||
IExecuteFunctions,
|
||||
INode,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
IPairedItemData,
|
||||
} from 'n8n-workflow';
|
||||
import { ApplicationError, jsonParse, randomInt } from 'n8n-workflow';
|
||||
import {
|
||||
ApplicationError,
|
||||
jsonParse,
|
||||
MYSQL_NODE_TYPE,
|
||||
POSTGRES_NODE_TYPE,
|
||||
randomInt,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
/**
|
||||
* Creates an array of elements split into groups the length of `size`.
|
||||
@@ -479,3 +487,50 @@ export const removeTrailingSlash = (url: string) => {
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
export function addExecutionHints(
|
||||
context: IExecuteFunctions,
|
||||
node: INode,
|
||||
items: INodeExecutionData[],
|
||||
operation: string,
|
||||
executeOnce: boolean | undefined,
|
||||
) {
|
||||
if (
|
||||
(node.type === POSTGRES_NODE_TYPE || node.type === MYSQL_NODE_TYPE) &&
|
||||
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 (
|
||||
node.type === POSTGRES_NODE_TYPE &&
|
||||
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',
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
node.type === MYSQL_NODE_TYPE &&
|
||||
operation === 'executeQuery' &&
|
||||
(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',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,8 @@ export const CHAT_TRIGGER_NODE_TYPE = '@n8n/n8n-nodes-langchain.chatTrigger';
|
||||
export const WAIT_NODE_TYPE = 'n8n-nodes-base.wait';
|
||||
export const HTML_NODE_TYPE = 'n8n-nodes-base.html';
|
||||
export const MAILGUN_NODE_TYPE = 'n8n-nodes-base.mailgun';
|
||||
export const POSTGRES_NODE_TYPE = 'n8n-nodes-base.postgres';
|
||||
export const MYSQL_NODE_TYPE = 'n8n-nodes-base.mySql';
|
||||
|
||||
export const STARTING_NODE_TYPES = [
|
||||
MANUAL_TRIGGER_NODE_TYPE,
|
||||
|
||||
Reference in New Issue
Block a user