fix(Postgres Node): Empty return data fix for Postgres and MySQL (#7016)

This commit is contained in:
Michael Kret 2023-08-25 18:38:09 +03:00 committed by GitHub
parent f02f6b659a
commit 176ccd62bc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 86 additions and 22 deletions

View file

@ -11,7 +11,7 @@ export class MySql extends VersionedNodeType {
name: 'mySql', name: 'mySql',
icon: 'file:mysql.svg', icon: 'file:mysql.svg',
group: ['input'], group: ['input'],
defaultVersion: 2.1, defaultVersion: 2.2,
description: 'Get, add and update data in MySQL', description: 'Get, add and update data in MySQL',
}; };
@ -19,6 +19,7 @@ export class MySql extends VersionedNodeType {
1: new MySqlV1(baseDescription), 1: new MySqlV1(baseDescription),
2: new MySqlV2(baseDescription), 2: new MySqlV2(baseDescription),
2.1: new MySqlV2(baseDescription), 2.1: new MySqlV2(baseDescription),
2.2: new MySqlV2(baseDescription),
}; };
super(nodeVersions, baseDescription); super(nodeVersions, baseDescription);

View file

@ -305,7 +305,7 @@ describe('Test MySql V2, operations', () => {
const runQueries: QueryRunner = configureQueryRunner.call( const runQueries: QueryRunner = configureQueryRunner.call(
fakeExecuteFunction, fakeExecuteFunction,
nodeOptions, { ...nodeOptions, nodeVersion: 2 },
pool, pool,
); );

View file

@ -45,7 +45,7 @@ describe('Test MySql V2, runQueries', () => {
}); });
it('should execute in "Single" mode, should return success true', async () => { it('should execute in "Single" mode, should return success true', async () => {
const nodeOptions: IDataObject = { queryBatching: BATCH_MODE.SINGLE }; const nodeOptions: IDataObject = { queryBatching: BATCH_MODE.SINGLE, nodeVersion: 2 };
const pool = createFakePool(fakeConnection); const pool = createFakePool(fakeConnection);
const fakeExecuteFunction = createMockExecuteFunction({}, mySqlMockNode); const fakeExecuteFunction = createMockExecuteFunction({}, mySqlMockNode);
@ -81,7 +81,7 @@ describe('Test MySql V2, runQueries', () => {
}); });
it('should execute in "independently" mode, should return success true', async () => { it('should execute in "independently" mode, should return success true', async () => {
const nodeOptions: IDataObject = { queryBatching: BATCH_MODE.INDEPENDENTLY }; const nodeOptions: IDataObject = { queryBatching: BATCH_MODE.INDEPENDENTLY, nodeVersion: 2 };
const pool = createFakePool(fakeConnection); const pool = createFakePool(fakeConnection);
@ -126,7 +126,7 @@ describe('Test MySql V2, runQueries', () => {
}); });
it('should execute in "transaction" mode, should return success true', async () => { it('should execute in "transaction" mode, should return success true', async () => {
const nodeOptions: IDataObject = { queryBatching: BATCH_MODE.TRANSACTION }; const nodeOptions: IDataObject = { queryBatching: BATCH_MODE.TRANSACTION, nodeVersion: 2 };
const pool = createFakePool(fakeConnection); const pool = createFakePool(fakeConnection);

View file

@ -8,7 +8,7 @@ export const versionDescription: INodeTypeDescription = {
name: 'mySql', name: 'mySql',
icon: 'file:mysql.svg', icon: 'file:mysql.svg',
group: ['input'], group: ['input'],
version: [2, 2.1], version: [2, 2.1, 2.2],
subtitle: '={{ $parameter["operation"] }}', subtitle: '={{ $parameter["operation"] }}',
description: 'Get, add and update data in MySQL', description: 'Get, add and update data in MySQL',
defaults: { defaults: {

View file

@ -167,7 +167,23 @@ export function prepareOutput(
} }
if (!returnData.length) { if (!returnData.length) {
returnData.push({ json: { success: true } }); if ((options?.nodeVersion as number) < 2.2) {
returnData.push({ json: { success: true } });
} else {
const isSelectQuery = statements
.filter((statement) => !statement.startsWith('--'))
.every((statement) =>
statement
.replace(/\/\*.*?\*\//g, '') // remove multiline comments
.replace(/\n/g, '')
.toLowerCase()
.startsWith('select'),
);
if (!isSelectQuery) {
returnData.push({ json: { success: true } });
}
}
} }
return returnData; return returnData;

View file

@ -11,7 +11,7 @@ export class Postgres extends VersionedNodeType {
name: 'postgres', name: 'postgres',
icon: 'file:postgres.svg', icon: 'file:postgres.svg',
group: ['input'], group: ['input'],
defaultVersion: 2.2, defaultVersion: 2.3,
description: 'Get, add and update data in Postgres', description: 'Get, add and update data in Postgres',
}; };
@ -20,6 +20,7 @@ export class Postgres extends VersionedNodeType {
2: new PostgresV2(baseDescription), 2: new PostgresV2(baseDescription),
2.1: new PostgresV2(baseDescription), 2.1: new PostgresV2(baseDescription),
2.2: new PostgresV2(baseDescription), 2.2: new PostgresV2(baseDescription),
2.3: new PostgresV2(baseDescription),
}; };
super(nodeVersions, baseDescription); super(nodeVersions, baseDescription);

View file

@ -44,7 +44,9 @@ describe('Test PostgresV2, runQueries', () => {
const thisArg = mock<IExecuteFunctions>(); const thisArg = mock<IExecuteFunctions>();
const runQueries = configureQueryRunner.call(thisArg, node, false, pgp, db); const runQueries = configureQueryRunner.call(thisArg, node, false, pgp, db);
const result = await runQueries([{ query: 'SELECT * FROM table', values: [] }], [], {}); const result = await runQueries([{ query: 'SELECT * FROM table', values: [] }], [], {
nodeVersion: 2.2,
});
expect(result).toBeDefined(); expect(result).toBeDefined();
expect(result).toHaveLength(1); expect(result).toHaveLength(1);

View file

@ -8,7 +8,7 @@ export const versionDescription: INodeTypeDescription = {
name: 'postgres', name: 'postgres',
icon: 'file:postgres.svg', icon: 'file:postgres.svg',
group: ['input'], group: ['input'],
version: [2, 2.1, 2.2], version: [2, 2.1, 2.2, 2.3],
subtitle: '={{ $parameter["operation"] }}', subtitle: '={{ $parameter["operation"] }}',
description: 'Get, add and update data in Postgres', description: 'Get, add and update data in Postgres',
defaults: { defaults: {

View file

@ -199,6 +199,15 @@ export function addReturning(
return [`${query} RETURNING $${replacementIndex}:name`, [...replacements, outputColumns]]; return [`${query} RETURNING $${replacementIndex}:name`, [...replacements, outputColumns]];
} }
const isSelectQuery = (query: string) => {
return query
.replace(/\/\*.*?\*\//g, '') // remove multiline comments
.replace(/\n/g, '')
.split(';')
.filter((statement) => statement && !statement.startsWith('--')) // remove comments and empty statements
.every((statement) => statement.trim().toLowerCase().startsWith('select'));
};
export function configureQueryRunner( export function configureQueryRunner(
this: IExecuteFunctions, this: IExecuteFunctions,
node: INode, node: INode,
@ -221,7 +230,16 @@ export function configureQueryRunner(
}); });
}) })
.flat(); .flat();
returnData = returnData.length ? returnData : emptyReturnData;
if (!returnData.length) {
if ((options?.nodeVersion as number) < 2.3) {
returnData = emptyReturnData;
} else {
returnData = queries.every((query) => isSelectQuery(query.query))
? []
: [{ json: { success: true } }];
}
}
} catch (err) { } catch (err) {
const error = parsePostgresError(node, err, queries); const error = parsePostgresError(node, err, queries);
if (!continueOnFail) throw error; if (!continueOnFail) throw error;
@ -242,13 +260,26 @@ export function configureQueryRunner(
const result: INodeExecutionData[] = []; const result: INodeExecutionData[] = [];
for (let i = 0; i < queries.length; i++) { for (let i = 0; i < queries.length; i++) {
try { try {
const transactionResult: IDataObject[] = await transaction.any( const query = queries[i].query;
queries[i].query, const values = queries[i].values;
queries[i].values,
); let transactionResults;
if ((options?.nodeVersion as number) < 2.3) {
transactionResults = await transaction.any(query, values);
} else {
transactionResults = (await transaction.multi(query, values)).flat();
}
if (!transactionResults.length) {
if ((options?.nodeVersion as number) < 2.3) {
transactionResults = emptyReturnData;
} else {
transactionResults = isSelectQuery(query) ? [] : [{ success: true }];
}
}
const executionData = this.helpers.constructExecutionMetaData( const executionData = this.helpers.constructExecutionMetaData(
wrapData(transactionResult.length ? transactionResult : emptyReturnData), wrapData(transactionResults),
{ itemData: { item: i } }, { itemData: { item: i } },
); );
@ -265,17 +296,30 @@ export function configureQueryRunner(
} }
if (queryBatching === 'independently') { if (queryBatching === 'independently') {
returnData = await db.task(async (t) => { returnData = await db.task(async (task) => {
const result: INodeExecutionData[] = []; const result: INodeExecutionData[] = [];
for (let i = 0; i < queries.length; i++) { for (let i = 0; i < queries.length; i++) {
try { try {
const transactionResult: IDataObject[] = await t.any( const query = queries[i].query;
queries[i].query, const values = queries[i].values;
queries[i].values,
); let transactionResults;
if ((options?.nodeVersion as number) < 2.3) {
transactionResults = await task.any(query, values);
} else {
transactionResults = (await task.multi(query, values)).flat();
}
if (!transactionResults.length) {
if ((options?.nodeVersion as number) < 2.3) {
transactionResults = emptyReturnData;
} else {
transactionResults = isSelectQuery(query) ? [] : [{ success: true }];
}
}
const executionData = this.helpers.constructExecutionMetaData( const executionData = this.helpers.constructExecutionMetaData(
wrapData(transactionResult.length ? transactionResult : emptyReturnData), wrapData(transactionResults),
{ itemData: { item: i } }, { itemData: { item: i } },
); );