mirror of
https://github.com/n8n-io/n8n.git
synced 2025-02-21 02:56:40 -08:00
fix(Postgres Node): Convert js arrays to postgres type, if column type is ARRAY (#9160)
This commit is contained in:
parent
d756609826
commit
08e35027f1
|
@ -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',
|
||||
};
|
||||
|
@ -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);
|
||||
|
|
|
@ -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',
|
||||
|
@ -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"}',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
configureTableSchemaUpdater,
|
||||
getTableSchema,
|
||||
prepareItem,
|
||||
convertArraysToPostgresFormat,
|
||||
replaceEmptyStringsByNulls,
|
||||
} from '../../helpers/utils';
|
||||
|
||||
|
@ -135,7 +136,7 @@ const properties: INodeProperties[] = [
|
|||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
'@version': [2.2, 2.3],
|
||||
'@version': [{ _cnd: { gte: 2.2 } }],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -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[];
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
doesRowExist,
|
||||
getTableSchema,
|
||||
prepareItem,
|
||||
convertArraysToPostgresFormat,
|
||||
replaceEmptyStringsByNulls,
|
||||
} from '../../helpers/utils';
|
||||
|
||||
|
@ -172,7 +173,7 @@ const properties: INodeProperties[] = [
|
|||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
'@version': [2.2, 2.3],
|
||||
'@version': [{ _cnd: { gte: 2.2 } }],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -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];
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
prepareItem,
|
||||
replaceEmptyStringsByNulls,
|
||||
configureTableSchemaUpdater,
|
||||
convertArraysToPostgresFormat,
|
||||
} from '../../helpers/utils';
|
||||
|
||||
import { optionsCollection } from '../common.descriptions';
|
||||
|
@ -171,7 +172,7 @@ const properties: INodeProperties[] = [
|
|||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
'@version': [2.2, 2.3],
|
||||
'@version': [{ _cnd: { gte: 2.2 } }],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -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];
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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';
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
|
@ -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,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue