mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
fix(Postgres Node): Account for JSON expressions (#12012)
Co-authored-by: Shireen Missi <94372015+ShireenMissi@users.noreply.github.com>
This commit is contained in:
@@ -14,6 +14,7 @@ import * as select from '../../v2/actions/database/select.operation';
|
|||||||
import * as update from '../../v2/actions/database/update.operation';
|
import * as update from '../../v2/actions/database/update.operation';
|
||||||
import * as upsert from '../../v2/actions/database/upsert.operation';
|
import * as upsert from '../../v2/actions/database/upsert.operation';
|
||||||
import type { ColumnInfo, PgpDatabase, QueriesRunner } from '../../v2/helpers/interfaces';
|
import type { ColumnInfo, PgpDatabase, QueriesRunner } from '../../v2/helpers/interfaces';
|
||||||
|
import * as utils from '../../v2/helpers/utils';
|
||||||
|
|
||||||
const runQueries: QueriesRunner = jest.fn();
|
const runQueries: QueriesRunner = jest.fn();
|
||||||
|
|
||||||
@@ -360,6 +361,99 @@ describe('Test PostgresV2, executeQuery operation', () => {
|
|||||||
nodeOptions,
|
nodeOptions,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should execute queries with multiple json key/value pairs', async () => {
|
||||||
|
const nodeParameters: IDataObject = {
|
||||||
|
operation: 'executeQuery',
|
||||||
|
query: 'SELECT *\nFROM users\nWHERE username IN ($1, $2, $3)',
|
||||||
|
options: {
|
||||||
|
queryReplacement:
|
||||||
|
'={{ JSON.stringify({id: "7",id2: "848da11d-e72e-44c5-yyyy-c6fb9f17d366"}) }}',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const nodeOptions = nodeParameters.options as IDataObject;
|
||||||
|
|
||||||
|
expect(async () => {
|
||||||
|
await executeQuery.execute.call(
|
||||||
|
createMockExecuteFunction(nodeParameters),
|
||||||
|
runQueries,
|
||||||
|
items,
|
||||||
|
nodeOptions,
|
||||||
|
);
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should execute queries with single json key/value pair', async () => {
|
||||||
|
const nodeParameters: IDataObject = {
|
||||||
|
operation: 'executeQuery',
|
||||||
|
query: 'SELECT *\nFROM users\nWHERE username IN ($1, $2, $3)',
|
||||||
|
options: {
|
||||||
|
queryReplacement: '={{ {"id": "7"} }}',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const nodeOptions = nodeParameters.options as IDataObject;
|
||||||
|
|
||||||
|
expect(async () => {
|
||||||
|
await executeQuery.execute.call(
|
||||||
|
createMockExecuteFunction(nodeParameters),
|
||||||
|
runQueries,
|
||||||
|
items,
|
||||||
|
nodeOptions,
|
||||||
|
);
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not parse out expressions if there are valid JSON query parameters', async () => {
|
||||||
|
const query = 'SELECT *\nFROM users\nWHERE username IN ($1, $2, $3)';
|
||||||
|
const nodeParameters: IDataObject = {
|
||||||
|
operation: 'executeQuery',
|
||||||
|
query,
|
||||||
|
options: {
|
||||||
|
queryReplacement: '={{ {"id": "7"} }}',
|
||||||
|
nodeVersion: 2.6,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const nodeOptions = nodeParameters.options as IDataObject;
|
||||||
|
|
||||||
|
jest.spyOn(utils, 'isJSON');
|
||||||
|
jest.spyOn(utils, 'stringToArray');
|
||||||
|
|
||||||
|
await executeQuery.execute.call(
|
||||||
|
createMockExecuteFunction(nodeParameters),
|
||||||
|
runQueries,
|
||||||
|
items,
|
||||||
|
nodeOptions,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(utils.isJSON).toHaveBeenCalledTimes(1);
|
||||||
|
expect(utils.stringToArray).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse out expressions if is invalid JSON in query parameters', async () => {
|
||||||
|
const query = 'SELECT *\nFROM users\nWHERE username IN ($1, $2, $3)';
|
||||||
|
const nodeParameters: IDataObject = {
|
||||||
|
operation: 'executeQuery',
|
||||||
|
query,
|
||||||
|
options: {
|
||||||
|
queryReplacement: '={{ JSON.stringify({"id": "7"}}) }}',
|
||||||
|
nodeVersion: 2.6,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const nodeOptions = nodeParameters.options as IDataObject;
|
||||||
|
|
||||||
|
jest.spyOn(utils, 'isJSON');
|
||||||
|
jest.spyOn(utils, 'stringToArray');
|
||||||
|
|
||||||
|
await executeQuery.execute.call(
|
||||||
|
createMockExecuteFunction(nodeParameters),
|
||||||
|
runQueries,
|
||||||
|
items,
|
||||||
|
nodeOptions,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(utils.isJSON).toHaveBeenCalledTimes(1);
|
||||||
|
expect(utils.stringToArray).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Test PostgresV2, insert operation', () => {
|
describe('Test PostgresV2, insert operation', () => {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
replaceEmptyStringsByNulls,
|
replaceEmptyStringsByNulls,
|
||||||
wrapData,
|
wrapData,
|
||||||
convertArraysToPostgresFormat,
|
convertArraysToPostgresFormat,
|
||||||
|
isJSON,
|
||||||
} from '../../v2/helpers/utils';
|
} from '../../v2/helpers/utils';
|
||||||
|
|
||||||
const node: INode = {
|
const node: INode = {
|
||||||
@@ -26,6 +27,15 @@ const node: INode = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
describe('Test PostgresV2, isJSON', () => {
|
||||||
|
it('should return true for valid JSON', () => {
|
||||||
|
expect(isJSON('{"key": "value"}')).toEqual(true);
|
||||||
|
});
|
||||||
|
it('should return false for invalid JSON', () => {
|
||||||
|
expect(isJSON('{"key": "value"')).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Test PostgresV2, wrapData', () => {
|
describe('Test PostgresV2, wrapData', () => {
|
||||||
it('should wrap object in json', () => {
|
it('should wrap object in json', () => {
|
||||||
const data = {
|
const data = {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import type {
|
|||||||
IExecuteFunctions,
|
IExecuteFunctions,
|
||||||
INodeExecutionData,
|
INodeExecutionData,
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
NodeParameterValueType,
|
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { NodeOperationError } from 'n8n-workflow';
|
import { NodeOperationError } from 'n8n-workflow';
|
||||||
|
|
||||||
@@ -15,7 +14,7 @@ import type {
|
|||||||
QueriesRunner,
|
QueriesRunner,
|
||||||
QueryWithValues,
|
QueryWithValues,
|
||||||
} from '../../helpers/interfaces';
|
} from '../../helpers/interfaces';
|
||||||
import { replaceEmptyStringsByNulls } from '../../helpers/utils';
|
import { isJSON, replaceEmptyStringsByNulls, stringToArray } from '../../helpers/utils';
|
||||||
import { optionsCollection } from '../common.descriptions';
|
import { optionsCollection } from '../common.descriptions';
|
||||||
|
|
||||||
const properties: INodeProperties[] = [
|
const properties: INodeProperties[] = [
|
||||||
@@ -54,20 +53,19 @@ export async function execute(
|
|||||||
nodeOptions: PostgresNodeOptions,
|
nodeOptions: PostgresNodeOptions,
|
||||||
_db?: PgpDatabase,
|
_db?: PgpDatabase,
|
||||||
): Promise<INodeExecutionData[]> {
|
): Promise<INodeExecutionData[]> {
|
||||||
items = replaceEmptyStringsByNulls(items, nodeOptions.replaceEmptyStrings as boolean);
|
const queries: QueryWithValues[] = replaceEmptyStringsByNulls(
|
||||||
|
items,
|
||||||
const queries: QueryWithValues[] = [];
|
nodeOptions.replaceEmptyStrings as boolean,
|
||||||
|
).map((_, index) => {
|
||||||
for (let i = 0; i < items.length; i++) {
|
let query = this.getNodeParameter('query', index) as string;
|
||||||
let query = this.getNodeParameter('query', i) as string;
|
|
||||||
|
|
||||||
for (const resolvable of getResolvables(query)) {
|
for (const resolvable of getResolvables(query)) {
|
||||||
query = query.replace(resolvable, this.evaluateExpression(resolvable, i) as string);
|
query = query.replace(resolvable, this.evaluateExpression(resolvable, index) as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
let values: Array<IDataObject | string> = [];
|
let values: Array<IDataObject | string> = [];
|
||||||
|
|
||||||
let queryReplacement = this.getNodeParameter('options.queryReplacement', i, '');
|
let queryReplacement = this.getNodeParameter('options.queryReplacement', index, '');
|
||||||
|
|
||||||
if (typeof queryReplacement === 'number') {
|
if (typeof queryReplacement === 'number') {
|
||||||
queryReplacement = String(queryReplacement);
|
queryReplacement = String(queryReplacement);
|
||||||
@@ -78,14 +76,6 @@ export async function execute(
|
|||||||
|
|
||||||
const rawReplacements = (node.parameters.options as IDataObject)?.queryReplacement as string;
|
const rawReplacements = (node.parameters.options as IDataObject)?.queryReplacement as string;
|
||||||
|
|
||||||
const stringToArray = (str: NodeParameterValueType | undefined) => {
|
|
||||||
if (str === undefined) return [];
|
|
||||||
return String(str)
|
|
||||||
.split(',')
|
|
||||||
.filter((entry) => entry)
|
|
||||||
.map((entry) => entry.trim());
|
|
||||||
};
|
|
||||||
|
|
||||||
if (rawReplacements) {
|
if (rawReplacements) {
|
||||||
const nodeVersion = nodeOptions.nodeVersion as number;
|
const nodeVersion = nodeOptions.nodeVersion as number;
|
||||||
|
|
||||||
@@ -94,7 +84,12 @@ export async function execute(
|
|||||||
const resolvables = getResolvables(rawValues);
|
const resolvables = getResolvables(rawValues);
|
||||||
if (resolvables.length) {
|
if (resolvables.length) {
|
||||||
for (const resolvable of resolvables) {
|
for (const resolvable of resolvables) {
|
||||||
const evaluatedValues = stringToArray(this.evaluateExpression(`${resolvable}`, i));
|
const evaluatedExpression =
|
||||||
|
this.evaluateExpression(`${resolvable}`, index)?.toString() ?? '';
|
||||||
|
const evaluatedValues = isJSON(evaluatedExpression)
|
||||||
|
? [evaluatedExpression]
|
||||||
|
: stringToArray(evaluatedExpression);
|
||||||
|
|
||||||
if (evaluatedValues.length) values.push(...evaluatedValues);
|
if (evaluatedValues.length) values.push(...evaluatedValues);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -112,7 +107,7 @@ export async function execute(
|
|||||||
|
|
||||||
if (resolvables.length) {
|
if (resolvables.length) {
|
||||||
for (const resolvable of resolvables) {
|
for (const resolvable of resolvables) {
|
||||||
values.push(this.evaluateExpression(`${resolvable}`, i) as IDataObject);
|
values.push(this.evaluateExpression(`${resolvable}`, index) as IDataObject);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
values.push(rawValue);
|
values.push(rawValue);
|
||||||
@@ -127,7 +122,7 @@ export async function execute(
|
|||||||
throw new NodeOperationError(
|
throw new NodeOperationError(
|
||||||
this.getNode(),
|
this.getNode(),
|
||||||
'Query Parameters must be a string of comma-separated values or an array of values',
|
'Query Parameters must be a string of comma-separated values or an array of values',
|
||||||
{ itemIndex: i },
|
{ itemIndex: index },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,8 +137,8 @@ export async function execute(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
queries.push({ query, values, options: { partial: true } });
|
return { query, values, options: { partial: true } };
|
||||||
}
|
});
|
||||||
|
|
||||||
return await runQueries(queries, items, nodeOptions);
|
return await runQueries(queries, items, nodeOptions);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type {
|
|||||||
INode,
|
INode,
|
||||||
INodeExecutionData,
|
INodeExecutionData,
|
||||||
INodePropertyOptions,
|
INodePropertyOptions,
|
||||||
|
NodeParameterValueType,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { NodeOperationError, jsonParse } from 'n8n-workflow';
|
import { NodeOperationError, jsonParse } from 'n8n-workflow';
|
||||||
|
|
||||||
@@ -20,6 +21,23 @@ import type {
|
|||||||
} from './interfaces';
|
} from './interfaces';
|
||||||
import { generatePairedItemData } from '../../../../utils/utilities';
|
import { generatePairedItemData } from '../../../../utils/utilities';
|
||||||
|
|
||||||
|
export function isJSON(str: string) {
|
||||||
|
try {
|
||||||
|
JSON.parse(str.trim());
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stringToArray(str: NodeParameterValueType | undefined) {
|
||||||
|
if (str === undefined) return [];
|
||||||
|
return String(str)
|
||||||
|
.split(',')
|
||||||
|
.filter((entry) => entry)
|
||||||
|
.map((entry) => entry.trim());
|
||||||
|
}
|
||||||
|
|
||||||
export function wrapData(data: IDataObject | IDataObject[]): INodeExecutionData[] {
|
export function wrapData(data: IDataObject | IDataObject[]): INodeExecutionData[] {
|
||||||
if (!Array.isArray(data)) {
|
if (!Array.isArray(data)) {
|
||||||
return [{ json: data }];
|
return [{ json: data }];
|
||||||
|
|||||||
Reference in New Issue
Block a user