first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled

This commit is contained in:
2026-03-17 16:22:57 +03:30
commit 3d5eaf9445
15349 changed files with 2847338 additions and 0 deletions
@@ -0,0 +1,50 @@
import type {
ICredentialsDecrypted,
ICredentialTestFunctions,
INodeCredentialTestResult,
} from 'n8n-workflow';
import { configurePostgres } from '../../transport';
import type { PgpConnection, PostgresNodeCredentials } from '../helpers/interfaces';
export async function postgresConnectionTest(
this: ICredentialTestFunctions,
credential: ICredentialsDecrypted,
): Promise<INodeCredentialTestResult> {
const credentials = credential.data as PostgresNodeCredentials;
let connection: PgpConnection | undefined;
try {
const { db } = await configurePostgres.call(this, credentials, {});
connection = await db.connect();
} catch (error) {
let message = error.message as string;
if (error.message.includes('ECONNREFUSED')) {
message = 'Connection refused';
}
if (error.message.includes('ENOTFOUND')) {
message = 'Host not found, please check your host name';
}
if (error.message.includes('ETIMEDOUT')) {
message = 'Connection timed out';
}
return {
status: 'Error',
message,
};
} finally {
if (connection) {
await connection.done();
}
}
return {
status: 'OK',
message: 'Connection successful!',
};
}
@@ -0,0 +1,4 @@
export * as credentialTest from './credentialTest';
export * as listSearch from './listSearch';
export * as loadOptions from './loadOptions';
export * as resourceMapping from './resourceMapping';
@@ -0,0 +1,42 @@
import type { ILoadOptionsFunctions, INodeListSearchResult } from 'n8n-workflow';
import { configurePostgres } from '../../transport';
import type { PostgresNodeCredentials } from '../helpers/interfaces';
export async function schemaSearch(this: ILoadOptionsFunctions): Promise<INodeListSearchResult> {
const credentials = await this.getCredentials<PostgresNodeCredentials>('postgres');
const options = { nodeVersion: this.getNode().typeVersion };
const { db } = await configurePostgres.call(this, credentials, options);
const response = await db.any('SELECT schema_name FROM information_schema.schemata');
return {
results: response.map((schema) => ({
name: schema.schema_name as string,
value: schema.schema_name as string,
})),
};
}
export async function tableSearch(this: ILoadOptionsFunctions): Promise<INodeListSearchResult> {
const credentials = await this.getCredentials<PostgresNodeCredentials>('postgres');
const options = { nodeVersion: this.getNode().typeVersion };
const { db } = await configurePostgres.call(this, credentials, options);
const schema = this.getNodeParameter('schema', 0, {
extractValue: true,
}) as string;
const response = await db.any(
'SELECT table_name FROM information_schema.tables WHERE table_schema=$1',
[schema],
);
return {
results: response.map((table) => ({
name: table.table_name as string,
value: table.table_name as string,
})),
};
}
@@ -0,0 +1,44 @@
import type { ILoadOptionsFunctions, INodePropertyOptions } from 'n8n-workflow';
import { configurePostgres } from '../../transport';
import type { PostgresNodeCredentials } from '../helpers/interfaces';
import { getTableSchema } from '../helpers/utils';
export async function getColumns(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const credentials = await this.getCredentials<PostgresNodeCredentials>('postgres');
const options = { nodeVersion: this.getNode().typeVersion };
const { db } = await configurePostgres.call(this, credentials, options);
const schema = this.getNodeParameter('schema', 0, {
extractValue: true,
}) as string;
const table = this.getNodeParameter('table', 0, {
extractValue: true,
}) as string;
const columns = await getTableSchema(db, schema, table);
return columns.map((column) => ({
name: column.column_name,
value: column.column_name,
description: `Type: ${column.data_type.toUpperCase()}, Nullable: ${column.is_nullable}`,
}));
}
export async function getColumnsMultiOptions(
this: ILoadOptionsFunctions,
): Promise<INodePropertyOptions[]> {
const returnData = await getColumns.call(this);
const returnAll = { name: '*', value: '*', description: 'All columns' };
return [returnAll, ...returnData];
}
export async function getColumnsWithoutColumnToMatchOn(
this: ILoadOptionsFunctions,
): Promise<INodePropertyOptions[]> {
const columnToMatchOn = this.getNodeParameter('columnToMatchOn') as string;
const returnData = await getColumns.call(this);
return returnData.filter((column) => column.value !== columnToMatchOn);
}
@@ -0,0 +1,121 @@
import type { ILoadOptionsFunctions, ResourceMapperFields, FieldType } from 'n8n-workflow';
import { configurePostgres } from '../../transport';
import type { PostgresNodeCredentials } from '../helpers/interfaces';
import { getEnumValues, getEnums, getTableSchema, uniqueColumns } from '../helpers/utils';
const postgresTypeToFieldType = new Map(
Object.entries({
text: 'string',
varchar: 'string',
'character varying': 'string',
character: 'string',
char: 'string',
integer: 'number',
smallint: 'number',
bigint: 'number',
decimal: 'number',
numeric: 'number',
real: 'number',
'double precision': 'number',
smallserial: 'number',
serial: 'number',
bigserial: 'number',
// eslint-disable-next-line id-denylist
boolean: 'boolean',
timestamp: 'dateTime',
date: 'dateTime',
timestampz: 'dateTime',
'timestamp without time zone': 'dateTime',
'timestamp with time zone': 'dateTime',
time: 'time',
'time without time zone': 'time',
'time with time zone': 'time',
json: 'object',
jsonb: 'object',
enum: 'options',
ARRAY: 'array',
// PostgreSQL extensions
citext: 'string',
uuid: 'string',
geometry: 'string',
geography: 'string',
inet: 'string',
cidr: 'string',
macaddr: 'string',
macaddr8: 'string',
int4range: 'string',
int8range: 'string',
numrange: 'string',
tsrange: 'string',
tstzrange: 'string',
daterange: 'string',
tsvector: 'string',
tsquery: 'string',
hstore: 'object',
ltree: 'string',
} as const),
);
function mapPostgresType(
postgresType: string,
userDefinedType?: string,
enumInfo?: Map<string, string[]>,
): FieldType {
if (postgresType === 'USER-DEFINED' && userDefinedType) {
if (enumInfo?.has(userDefinedType)) {
return 'options';
}
return postgresTypeToFieldType.get(userDefinedType) ?? 'string';
}
return postgresTypeToFieldType.get(postgresType) ?? 'string';
}
export async function getMappingColumns(
this: ILoadOptionsFunctions,
): Promise<ResourceMapperFields> {
const credentials = await this.getCredentials<PostgresNodeCredentials>('postgres');
const { db } = await configurePostgres.call(this, credentials);
const schema = this.getNodeParameter('schema', 0, {
extractValue: true,
}) as string;
const table = this.getNodeParameter('table', 0, {
extractValue: true,
}) as string;
const operation = this.getNodeParameter('operation', 0, {
extractValue: true,
}) as string;
const columns = await getTableSchema(db, schema, table, { getColumnsForResourceMapper: true });
const unique = operation === 'upsert' ? await uniqueColumns(db, table, schema) : [];
const enumInfo = await getEnums(db);
const fields = columns.map((col) => {
const canBeUsedToMatch =
operation === 'upsert' ? unique.some((u) => u.attname === col.column_name) : true;
const type = mapPostgresType(col.data_type, col.udt_name, enumInfo);
const options =
type === 'options' ? getEnumValues(enumInfo, col.udt_name as string) : undefined;
const hasDefault = Boolean(col.column_default);
const isGenerated =
col.is_generated === 'ALWAYS' ||
['ALWAYS', 'BY DEFAULT'].includes(col.identity_generation ?? '');
const nullable = col.is_nullable === 'YES';
return {
id: col.column_name,
displayName: col.column_name,
required: !nullable && !hasDefault && !isGenerated,
defaultMatch: (col.column_name === 'id' && canBeUsedToMatch) || false,
display: true,
type,
canBeUsedToMatch,
options,
};
});
return { fields };
}