diff --git a/packages/nodes-base/nodes/Postgres/Postgres.node.ts b/packages/nodes-base/nodes/Postgres/Postgres.node.ts index 0e1760faea..a1328bc526 100644 --- a/packages/nodes-base/nodes/Postgres/Postgres.node.ts +++ b/packages/nodes-base/nodes/Postgres/Postgres.node.ts @@ -11,7 +11,7 @@ export class Postgres extends VersionedNodeType { name: 'postgres', icon: 'file:postgres.svg', group: ['input'], - defaultVersion: 2.5, + defaultVersion: 2.6, description: 'Get, add and update data in Postgres', parameterPane: 'wide', }; @@ -24,6 +24,7 @@ export class Postgres extends VersionedNodeType { 2.3: new PostgresV2(baseDescription), 2.4: new PostgresV2(baseDescription), 2.5: new PostgresV2(baseDescription), + 2.6: new PostgresV2(baseDescription), }; super(nodeVersions, baseDescription); diff --git a/packages/nodes-base/nodes/Postgres/test/v2/operations.test.ts b/packages/nodes-base/nodes/Postgres/test/v2/operations.test.ts index cc7906fbd5..81e4dca2b8 100644 --- a/packages/nodes-base/nodes/Postgres/test/v2/operations.test.ts +++ b/packages/nodes-base/nodes/Postgres/test/v2/operations.test.ts @@ -713,6 +713,51 @@ describe('Test PostgresV2, insert operation', () => { expect(hasJsonDataTypeInSchemaSpy).toHaveBeenCalledWith(columnsInfo); }); }); + + it('should insert default values if no values are provided', async () => { + const nodeParameters: IDataObject = { + schema: { + __rl: true, + mode: 'list', + value: 'public', + }, + table: { + __rl: true, + value: 'my_table', + mode: 'list', + }, + dataMode: 'defineBelow', + valuesToSend: { + values: [], + }, + options: { nodeVersion: 2.6 }, + }; + const columnsInfo: ColumnInfo[] = [ + { column_name: 'id', data_type: 'integer', is_nullable: 'NO', udt_name: '' }, + ]; + + const nodeOptions = nodeParameters.options as IDataObject; + + await insert.execute.call( + createMockExecuteFunction(nodeParameters), + runQueries, + items, + nodeOptions, + createMockDb(columnsInfo), + pgPromise(), + ); + + expect(runQueries).toHaveBeenCalledWith( + [ + { + query: 'INSERT INTO $1:name.$2:name DEFAULT VALUES RETURNING *', + values: ['public', 'my_table', {}], + }, + ], + items, + nodeOptions, + ); + }); }); describe('Test PostgresV2, select operation', () => { diff --git a/packages/nodes-base/nodes/Postgres/test/v2/resourceMapping.test.ts b/packages/nodes-base/nodes/Postgres/test/v2/resourceMapping.test.ts new file mode 100644 index 0000000000..8301baed80 --- /dev/null +++ b/packages/nodes-base/nodes/Postgres/test/v2/resourceMapping.test.ts @@ -0,0 +1,88 @@ +import type { MockProxy } from 'jest-mock-extended'; +import { mock } from 'jest-mock-extended'; +import type { ILoadOptionsFunctions } from 'n8n-workflow'; + +import { getMappingColumns } from '../../v2/methods/resourceMapping'; + +jest.mock('../../transport', () => { + const originalModule = jest.requireActual('../../transport'); + return { + ...originalModule, + configurePostgres: jest.fn(async () => ({ db: {} })), + }; +}); + +jest.mock('../../v2/helpers/utils', () => { + const originalModule = jest.requireActual('../../v2/helpers/utils'); + return { + ...originalModule, + getEnums: jest.fn(() => []), + getEnumValues: jest.fn(), + getTableSchema: jest.fn(() => [ + { + column_name: 'id', + data_type: 'bigint', + is_nullable: 'NO', + udt_name: 'int8', + column_default: null, + identity_generation: 'BY DEFAULT', + is_generated: 'NEVER', + }, + { + column_name: 'name', + data_type: 'text', + is_nullable: 'YES', + udt_name: 'text', + column_default: null, + identity_generation: null, + is_generated: 'NEVER', + }, + ]), + }; +}); + +describe('Postgres, resourceMapping', () => { + let loadOptionsFunctions: MockProxy; + + beforeEach(() => { + loadOptionsFunctions = mock(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should mark id as not required if identity_generation is "BY_DEFAULT"', async () => { + loadOptionsFunctions.getCredentials.mockResolvedValue({}); + loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('public'); + loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('test_table'); + loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('insert'); + + const fields = await getMappingColumns.call(loadOptionsFunctions); + + expect(fields).toEqual({ + fields: [ + { + canBeUsedToMatch: true, + defaultMatch: true, + display: true, + displayName: 'id', + id: 'id', + options: undefined, + required: false, + type: 'number', + }, + { + canBeUsedToMatch: true, + defaultMatch: false, + display: true, + displayName: 'name', + id: 'name', + options: undefined, + required: false, + type: 'string', + }, + ], + }); + }); +}); diff --git a/packages/nodes-base/nodes/Postgres/test/v2/utils.test.ts b/packages/nodes-base/nodes/Postgres/test/v2/utils.test.ts index c24d5627de..e5aef35a37 100644 --- a/packages/nodes-base/nodes/Postgres/test/v2/utils.test.ts +++ b/packages/nodes-base/nodes/Postgres/test/v2/utils.test.ts @@ -146,15 +146,15 @@ describe('Test PostgresV2, parsePostgresError', () => { it('should update message with syntax error', () => { // eslint-disable-next-line n8n-local-rules/no-unneeded-backticks - const errorMessage = String.raw`syntax error at or near "seelect"`; + const errorMessage = String.raw`syntax error at or near "select"`; const error = new Error(); error.message = errorMessage; const parsedError = parsePostgresError(node, error, [ - { query: 'seelect * from my_table', values: [] }, + { query: 'select * from my_table', values: [] }, ]); expect(parsedError).toBeDefined(); - expect(parsedError.message).toEqual('Syntax error at line 1 near "seelect"'); + expect(parsedError.message).toEqual('Syntax error at line 1 near "select"'); expect(parsedError instanceof NodeOperationError).toEqual(true); }); }); @@ -201,7 +201,7 @@ describe('Test PostgresV2, addWhereClauses', () => { expect(updatedValues).toEqual(['public', 'my_table', 'id', '1', 'foo', 'select 2']); }); - it('should ignore incorect combine conition ad use AND', () => { + it('should ignore incorrect combine condition ad use AND', () => { const query = 'SELECT * FROM $1:name.$2:name'; const values = ['public', 'my_table']; const whereClauses = [ @@ -246,7 +246,7 @@ describe('Test PostgresV2, addSortRules', () => { expect(updatedQuery).toEqual('SELECT * FROM $1:name.$2:name ORDER BY $3:name DESC'); expect(updatedValues).toEqual(['public', 'my_table', 'id']); }); - it('should ignore incorect direction', () => { + it('should ignore incorrect direction', () => { const query = 'SELECT * FROM $1:name.$2:name'; const values = ['public', 'my_table']; const sortRules = [{ column: 'id', direction: 'SELECT * FROM my_table' }]; @@ -340,7 +340,7 @@ describe('Test PostgresV2, replaceEmptyStringsByNulls', () => { }); describe('Test PostgresV2, prepareItem', () => { - it('should convert fixedColections values to object', () => { + it('should convert fixedCollection values to object', () => { const values = [ { column: 'id', diff --git a/packages/nodes-base/nodes/Postgres/v2/actions/database/insert.operation.ts b/packages/nodes-base/nodes/Postgres/v2/actions/database/insert.operation.ts index 23d221d848..df00fd94b6 100644 --- a/packages/nodes-base/nodes/Postgres/v2/actions/database/insert.operation.ts +++ b/packages/nodes-base/nodes/Postgres/v2/actions/database/insert.operation.ts @@ -241,6 +241,10 @@ export async function execute( const outputColumns = this.getNodeParameter('options.outputColumns', i, ['*']) as string[]; + if (nodeVersion >= 2.6 && Object.keys(item).length === 0) { + query = 'INSERT INTO $1:name.$2:name DEFAULT VALUES'; + } + [query, values] = addReturning(query, outputColumns, values); queries.push({ query, values }); diff --git a/packages/nodes-base/nodes/Postgres/v2/actions/versionDescription.ts b/packages/nodes-base/nodes/Postgres/v2/actions/versionDescription.ts index c5e491159b..be6805aa4b 100644 --- a/packages/nodes-base/nodes/Postgres/v2/actions/versionDescription.ts +++ b/packages/nodes-base/nodes/Postgres/v2/actions/versionDescription.ts @@ -8,7 +8,7 @@ export const versionDescription: INodeTypeDescription = { name: 'postgres', icon: 'file:postgres.svg', group: ['input'], - version: [2, 2.1, 2.2, 2.3, 2.4, 2.5], + version: [2, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6], subtitle: '={{ $parameter["operation"] }}', description: 'Get, add and update data in Postgres', defaults: { diff --git a/packages/nodes-base/nodes/Postgres/v2/methods/resourceMapping.ts b/packages/nodes-base/nodes/Postgres/v2/methods/resourceMapping.ts index 3319bdf2f6..edb8d587ba 100644 --- a/packages/nodes-base/nodes/Postgres/v2/methods/resourceMapping.ts +++ b/packages/nodes-base/nodes/Postgres/v2/methods/resourceMapping.ts @@ -74,7 +74,9 @@ export async function getMappingColumns( const options = type === 'options' ? getEnumValues(enumInfo, col.udt_name as string) : undefined; const hasDefault = Boolean(col.column_default); - const isGenerated = col.is_generated === 'ALWAYS' || col.identity_generation === 'ALWAYS'; + const isGenerated = + col.is_generated === 'ALWAYS' || + ['ALWAYS', 'BY DEFAULT'].includes(col.identity_generation ?? ''); const nullable = col.is_nullable === 'YES'; return { id: col.column_name,