Skip to content

Commit

Permalink
fix(Postgres Node): Convert js arrays to postgres type, if column typ…
Browse files Browse the repository at this point in the history
…e is ARRAY (#9160)
  • Loading branch information
michael-radency authored and netroy committed Apr 18, 2024
1 parent 3d6455e commit ee0c685
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 7 deletions.
3 changes: 2 additions & 1 deletion packages/nodes-base/nodes/Postgres/Postgres.node.ts
Expand Up @@ -11,7 +11,7 @@ export class Postgres extends VersionedNodeType {
name: 'postgres',
icon: 'file:postgres.svg',
group: ['input'],
defaultVersion: 2.3,
defaultVersion: 2.4,
description: 'Get, add and update data in Postgres',
parameterPane: 'wide',
};
Expand All @@ -22,6 +22,7 @@ export class Postgres extends VersionedNodeType {
2.1: new PostgresV2(baseDescription),
2.2: new PostgresV2(baseDescription),
2.3: new PostgresV2(baseDescription),
2.4: new PostgresV2(baseDescription),
};

super(nodeVersions, baseDescription);
Expand Down
77 changes: 77 additions & 0 deletions packages/nodes-base/nodes/Postgres/test/v2/utils.test.ts
Expand Up @@ -10,7 +10,9 @@ import {
prepareItem,
replaceEmptyStringsByNulls,
wrapData,
convertArraysToPostgresFormat,
} from '../../v2/helpers/utils';
import type { ColumnInfo } from '../../v2/helpers/interfaces';

const node: INode = {
id: '1',
Expand Down Expand Up @@ -373,3 +375,78 @@ describe('Test PostgresV2, checkItemAgainstSchema', () => {
}
});
});

describe('Test PostgresV2, convertArraysToPostgresFormat', () => {
it('should convert js arrays to postgres format', () => {
const item = {
jsonb_array: [
{
key: 'value44',
},
],
json_array: [
{
key: 'value54',
},
],
int_array: [1, 2, 5],
text_array: ['one', 't"w"o'],
bool_array: [true, false],
};

const schema: ColumnInfo[] = [
{
column_name: 'id',
data_type: 'integer',
is_nullable: 'NO',
udt_name: 'int4',
column_default: "nextval('test_data_array_id_seq'::regclass)",
},
{
column_name: 'jsonb_array',
data_type: 'ARRAY',
is_nullable: 'YES',
udt_name: '_jsonb',
column_default: null,
},
{
column_name: 'json_array',
data_type: 'ARRAY',
is_nullable: 'YES',
udt_name: '_json',
column_default: null,
},
{
column_name: 'int_array',
data_type: 'ARRAY',
is_nullable: 'YES',
udt_name: '_int4',
column_default: null,
},
{
column_name: 'bool_array',
data_type: 'ARRAY',
is_nullable: 'YES',
udt_name: '_bool',
column_default: null,
},
{
column_name: 'text_array',
data_type: 'ARRAY',
is_nullable: 'YES',
udt_name: '_text',
column_default: null,
},
];

convertArraysToPostgresFormat(item, schema, node, 0);

expect(item).toEqual({
jsonb_array: '{"{\\"key\\":\\"value44\\"}"}',
json_array: '{"{\\"key\\":\\"value54\\"}"}',
int_array: '{1,2,5}',
text_array: '{"one","t\\"w\\"o"}',
bool_array: '{"true","false"}',
});
});
});
Expand Up @@ -19,6 +19,7 @@ import {
configureTableSchemaUpdater,
getTableSchema,
prepareItem,
convertArraysToPostgresFormat,
replaceEmptyStringsByNulls,
} from '../../helpers/utils';

Expand Down Expand Up @@ -135,7 +136,7 @@ const properties: INodeProperties[] = [
},
displayOptions: {
show: {
'@version': [2.2, 2.3],
'@version': [{ _cnd: { gte: 2.2 } }],
},
},
},
Expand Down Expand Up @@ -224,6 +225,10 @@ export async function execute(

tableSchema = await updateTableSchema(db, tableSchema, schema, table);

if (nodeVersion >= 2.4) {
convertArraysToPostgresFormat(item, tableSchema, this.getNode(), i);
}

values.push(checkItemAgainstSchema(this.getNode(), item, tableSchema, i));

const outputColumns = this.getNodeParameter('options.outputColumns', i, ['*']) as string[];
Expand Down
Expand Up @@ -21,6 +21,7 @@ import {
doesRowExist,
getTableSchema,
prepareItem,
convertArraysToPostgresFormat,
replaceEmptyStringsByNulls,
} from '../../helpers/utils';

Expand Down Expand Up @@ -172,7 +173,7 @@ const properties: INodeProperties[] = [
},
displayOptions: {
show: {
'@version': [2.2, 2.3],
'@version': [{ _cnd: { gte: 2.2 } }],
},
},
},
Expand Down Expand Up @@ -301,6 +302,10 @@ export async function execute(

tableSchema = await updateTableSchema(db, tableSchema, schema, table);

if (nodeVersion >= 2.4) {
convertArraysToPostgresFormat(item, tableSchema, this.getNode(), i);
}

item = checkItemAgainstSchema(this.getNode(), item, tableSchema, i);

let values: QueryValues = [schema, table];
Expand Down
Expand Up @@ -21,6 +21,7 @@ import {
prepareItem,
replaceEmptyStringsByNulls,
configureTableSchemaUpdater,
convertArraysToPostgresFormat,
} from '../../helpers/utils';

import { optionsCollection } from '../common.descriptions';
Expand Down Expand Up @@ -171,7 +172,7 @@ const properties: INodeProperties[] = [
},
displayOptions: {
show: {
'@version': [2.2, 2.3],
'@version': [{ _cnd: { gte: 2.2 } }],
},
},
},
Expand Down Expand Up @@ -270,6 +271,10 @@ export async function execute(

tableSchema = await updateTableSchema(db, tableSchema, schema, table);

if (nodeVersion >= 2.4) {
convertArraysToPostgresFormat(item, tableSchema, this.getNode(), i);
}

item = checkItemAgainstSchema(this.getNode(), item, tableSchema, i);

let values: QueryValues = [schema, table];
Expand Down
Expand Up @@ -8,7 +8,7 @@ export const versionDescription: INodeTypeDescription = {
name: 'postgres',
icon: 'file:postgres.svg',
group: ['input'],
version: [2, 2.1, 2.2, 2.3],
version: [2, 2.1, 2.2, 2.3, 2.4],
subtitle: '={{ $parameter["operation"] }}',
description: 'Get, add and update data in Postgres',
defaults: {
Expand Down
Expand Up @@ -16,7 +16,7 @@ export type ColumnInfo = {
data_type: string;
is_nullable: string;
udt_name?: string;
column_default?: string;
column_default?: string | null;
is_generated?: 'ALWAYS' | 'NEVER';
identity_generation?: 'ALWAYS' | 'NEVER';
};
Expand Down
62 changes: 61 additions & 1 deletion packages/nodes-base/nodes/Postgres/v2/helpers/utils.ts
Expand Up @@ -5,7 +5,7 @@ import type {
INodeExecutionData,
INodePropertyOptions,
} from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';
import { NodeOperationError, jsonParse } from 'n8n-workflow';

import { generatePairedItemData } from '../../../../utils/utilities';
import type {
Expand Down Expand Up @@ -510,3 +510,63 @@ export const configureTableSchemaUpdater = (initialSchema: string, initialTable:
return tableSchema;
};
};

/**
* If postgress column type is array we need to convert it to fornmat that postgres understands, original object data would be modified
* @param data the object with keys representing column names and values
* @param schema table schema
* @param node INode
* @param itemIndex the index of the current item
*/
export const convertArraysToPostgresFormat = (
data: IDataObject,
schema: ColumnInfo[],
node: INode,
itemIndex = 0,
) => {
for (const columnInfo of schema) {
//in case column type is array we need to convert it to fornmat that postgres understands
if (columnInfo.data_type.toUpperCase() === 'ARRAY') {
let columnValue = data[columnInfo.column_name];

if (typeof columnValue === 'string') {
columnValue = jsonParse(columnValue);
}

if (Array.isArray(columnValue)) {
const arrayEntries = columnValue.map((entry) => {
if (typeof entry === 'number') {
return entry;
}

if (typeof entry === 'boolean') {
entry = String(entry);
}

if (typeof entry === 'object') {
entry = JSON.stringify(entry);
}

if (typeof entry === 'string') {
return `"${entry.replace(/"/g, '\\"')}"`; //escape double quotes
}

return entry;
});

//wrap in {} instead of [] as postgres does and join with ,
data[columnInfo.column_name] = `{${arrayEntries.join(',')}}`;
} else {
if (columnInfo.is_nullable === 'NO') {
throw new NodeOperationError(
node,
`Column '${columnInfo.column_name}' has to be an array`,
{
itemIndex,
},
);
}
}
}
}
};

0 comments on commit ee0c685

Please sign in to comment.