feat(Merge Node): Option in combineBySql operation to return either confirmation of succes or empty result (#15509)

This commit is contained in:
Michael Kret
2025-05-22 19:39:17 +03:00
committed by GitHub
parent 9938e63a66
commit a86bc43f50
4 changed files with 103 additions and 5 deletions

View File

@@ -14,7 +14,7 @@ export class Merge extends VersionedNodeType {
group: ['transform'],
subtitle: '={{$parameter["mode"]}}',
description: 'Merges data of multiple streams once data from both is available',
defaultVersion: 3.1,
defaultVersion: 3.2,
};
const nodeVersions: IVersionedNodeType['nodeVersions'] = {
@@ -23,6 +23,7 @@ export class Merge extends VersionedNodeType {
2.1: new MergeV2(baseDescription),
3: new MergeV3(baseDescription),
3.1: new MergeV3(baseDescription),
3.2: new MergeV3(baseDescription),
};
super(nodeVersions, baseDescription);

View File

@@ -396,6 +396,59 @@ describe('Test MergeV3, combineBySql operation', () => {
],
]);
});
it('Empty successful query should return [{ success: true }] at version <= 3.1', async () => {
const nodeParameters: IDataObject = {
operation: 'combineBySql',
query: 'SELECT id from input1',
};
const returnData = await mode.combineBySql.execute.call(
createMockExecuteFunction(nodeParameters, { ...node, typeVersion: 3.1 }),
[[]],
);
expect(returnData.length).toEqual(1);
expect(returnData[0].length).toEqual(1);
expect(returnData[0][0].json).toEqual({
success: true,
});
});
it('Empty successful query should return [] at version >= 3.2 if no option set', async () => {
const nodeParameters: IDataObject = {
operation: 'combineBySql',
query: 'SELECT id from input1',
};
const returnData = await mode.combineBySql.execute.call(
createMockExecuteFunction(nodeParameters, { ...node, typeVersion: 3.2 }),
[[]],
);
expect(returnData).toEqual([[]]);
});
it('Empty successful query should return [{ success: true }] at version >= 3.2 if option set', async () => {
const nodeParameters: IDataObject = {
operation: 'combineBySql',
query: 'SELECT id from input1',
options: {
emptyQueryResult: 'success',
},
};
const returnData = await mode.combineBySql.execute.call(
createMockExecuteFunction(nodeParameters, { ...node, typeVersion: 3.2 }),
[[]],
);
expect(returnData.length).toEqual(1);
expect(returnData[0].length).toEqual(1);
expect(returnData[0][0].json).toEqual({
success: true,
});
});
});
describe('Test MergeV3, append operation', () => {

View File

@@ -17,6 +17,10 @@ import { getResolvables, updateDisplayOptions } from '@utils/utilities';
import { numberInputsProperty } from '../../helpers/descriptions';
import { modifySelectQuery, rowToExecutionData } from '../../helpers/utils';
type OperationOptions = {
emptyQueryResult: 'success' | 'empty';
};
export const properties: INodeProperties[] = [
numberInputsProperty,
{
@@ -33,6 +37,37 @@ export const properties: INodeProperties[] = [
editor: 'sqlEditor',
},
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add option',
default: {},
options: [
{
displayName: 'Empty Query Result',
name: 'emptyQueryResult',
type: 'options',
description: 'What to return if the query executed successfully but returned no results',
options: [
{
name: 'Success',
value: 'success',
},
{
name: 'Empty Result',
value: 'empty',
},
],
default: 'empty',
},
],
displayOptions: {
show: {
'@version': [3.2],
},
},
},
];
const displayOptions = {
@@ -61,6 +96,7 @@ async function executeSelectWithMappedPairedItems(
node: INode,
inputsData: INodeExecutionData[][],
query: string,
returnSuccessItemIfEmpty: boolean,
): Promise<INodeExecutionData[][]> {
const returnData: INodeExecutionData[] = [];
@@ -95,7 +131,7 @@ async function executeSelectWithMappedPairedItems(
}
}
if (!returnData.length) {
if (!returnData.length && returnSuccessItemIfEmpty) {
returnData.push({ json: { success: true } });
}
} catch (error) {
@@ -114,6 +150,7 @@ export async function execute(
const node = this.getNode();
const returnData: INodeExecutionData[] = [];
const pairedItem: IPairedItemData[] = [];
const options = this.getNodeParameter('options', 0, {}) as OperationOptions;
let query = this.getNodeParameter('query', 0) as string;
@@ -122,10 +159,17 @@ export async function execute(
}
const isSelectQuery = node.typeVersion >= 3.1 ? query.toLowerCase().startsWith('select') : false;
const returnSuccessItemIfEmpty =
node.typeVersion <= 3.1 ? true : options.emptyQueryResult === 'success';
if (isSelectQuery) {
try {
return await executeSelectWithMappedPairedItems(node, inputsData, query);
return await executeSelectWithMappedPairedItems(
node,
inputsData,
query,
returnSuccessItemIfEmpty,
);
} catch (error) {
Container.get(ErrorReporter).error(error, {
extra: {
@@ -199,7 +243,7 @@ export async function execute(
}
}
if (!returnData.length) {
if (!returnData.length && returnSuccessItemIfEmpty) {
returnData.push({ json: { success: true }, pairedItem });
}
} catch (error) {

View File

@@ -9,7 +9,7 @@ export const versionDescription: INodeTypeDescription = {
name: 'merge',
group: ['transform'],
description: 'Merges data of multiple streams once data from both is available',
version: [3, 3.1],
version: [3, 3.1, 3.2],
defaults: {
name: 'Merge',
},