mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 20:24:05 -08:00
fix(Postgres Node): Stop marking autogenerated columns as required (#8230)
## Summary Postgres columns can be - [generated as identity](https://www.postgresqltutorial.com/postgresql-tutorial/postgresql-identity-column/) - [generated by a custom expression](https://www.postgresql.org/docs/current/ddl-generated-columns.html) In these 2 cases, the column is not required when inserting a new row. This PR makes sure these types of column are not marked required in n8n. ### How to test 1. Create a Postgres table with all types of generated columns: for version >= 10 ```sql CREATE TABLE "public"."test_table" ( "id" int8 NOT NULL DEFAULT nextval('test_table_id_seq'::regclass), "identity_id" bigint GENERATED ALWAYS AS IDENTITY, "id_plus" numeric GENERATED ALWAYS AS (id + 5) STORED, "title" varchar NOT NULL, "created_at" timestamp DEFAULT now(), PRIMARY KEY ("id") ) ``` Before 10 you have to use serial or bigserial types: ```sql CREATE TABLE distributors ( did serial not null primary key, name varchar(40) NOT NULL CHECK (name <> '') ); ``` 2. Add a postgres node to canvas and try to insert data without the generated columns 3. Should successfully insert More info in Linear/Github issue ⬇️ ## Related tickets and issues - fixes #7084 - https://linear.app/n8n/issue/NODE-816/rmc-not-all-id-fields-should-be-required - https://linear.app/n8n/issue/NODE-681/postgres-cant-map-automatically-if-database-requires-a-field ## Review / Merge checklist - [ ] PR title and summary are descriptive. **Remember, the title automatically goes into the changelog. Use `(no-changelog)` otherwise.** ([conventions](https://github.com/n8n-io/n8n/blob/master/.github/pull_request_title_conventions.md)) - [ ] [Docs updated](https://github.com/n8n-io/n8n-docs) or follow-up ticket created. - [ ] Tests included. > A bug is not considered fixed, unless a test is added to prevent it from happening again. > A feature is not complete without tests. --------- Co-authored-by: Michael Kret <michael.k@radency.com>
This commit is contained in:
parent
048b588852
commit
bed04ec122
|
@ -17,6 +17,8 @@ export type ColumnInfo = {
|
|||
is_nullable: string;
|
||||
udt_name?: string;
|
||||
column_default?: string;
|
||||
is_generated?: 'ALWAYS' | 'NEVER';
|
||||
identity_generation?: 'ALWAYS' | 'NEVER';
|
||||
};
|
||||
export type EnumInfo = {
|
||||
typname: string;
|
||||
|
|
|
@ -376,13 +376,45 @@ export function prepareItem(values: IDataObject[]) {
|
|||
return item;
|
||||
}
|
||||
|
||||
export async function columnFeatureSupport(
|
||||
db: PgpDatabase,
|
||||
): Promise<{ identity_generation: boolean; is_generated: boolean }> {
|
||||
const result = await db.any(
|
||||
`SELECT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns WHERE table_name = 'columns' AND table_schema = 'information_schema' AND column_name = 'is_generated'
|
||||
) as is_generated,
|
||||
EXISTS (
|
||||
SELECT 1 FROM information_schema.columns WHERE table_name = 'columns' AND table_schema = 'information_schema' AND column_name = 'identity_generation'
|
||||
) as identity_generation;`,
|
||||
);
|
||||
|
||||
return result[0];
|
||||
}
|
||||
|
||||
export async function getTableSchema(
|
||||
db: PgpDatabase,
|
||||
schema: string,
|
||||
table: string,
|
||||
options?: { getColumnsForResourceMapper?: boolean },
|
||||
): Promise<ColumnInfo[]> {
|
||||
const select = ['column_name', 'data_type', 'is_nullable', 'udt_name', 'column_default'];
|
||||
|
||||
if (options?.getColumnsForResourceMapper) {
|
||||
// Check if columns exist before querying (identity_generation was added in v10, is_generated in v12)
|
||||
const supported = await columnFeatureSupport(db);
|
||||
|
||||
if (supported.identity_generation) {
|
||||
select.push('identity_generation');
|
||||
}
|
||||
|
||||
if (supported.is_generated) {
|
||||
select.push('is_generated');
|
||||
}
|
||||
}
|
||||
|
||||
const selectString = select.join(', ');
|
||||
const columns = await db.any(
|
||||
'SELECT column_name, data_type, is_nullable, udt_name, column_default FROM information_schema.columns WHERE table_schema = $1 AND table_name = $2',
|
||||
`SELECT ${selectString} FROM information_schema.columns WHERE table_schema = $1 AND table_name = $2`,
|
||||
[schema, table],
|
||||
);
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ export async function getMappingColumns(
|
|||
}) as string;
|
||||
|
||||
try {
|
||||
const columns = await getTableSchema(db, schema, table);
|
||||
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 = await Promise.all(
|
||||
|
@ -72,11 +72,13 @@ export async function getMappingColumns(
|
|||
const type = mapPostgresType(col.data_type);
|
||||
const options =
|
||||
type === 'options' ? getEnumValues(enumInfo, col.udt_name as string) : undefined;
|
||||
const isAutoIncrement = col.column_default?.startsWith('nextval');
|
||||
const hasDefault = Boolean(col.column_default);
|
||||
const isGenerated = col.is_generated === 'ALWAYS' || col.identity_generation === 'ALWAYS';
|
||||
const nullable = col.is_nullable === 'YES';
|
||||
return {
|
||||
id: col.column_name,
|
||||
displayName: col.column_name,
|
||||
required: col.is_nullable !== 'YES' && !isAutoIncrement,
|
||||
required: !nullable && !hasDefault && !isGenerated,
|
||||
defaultMatch: (col.column_name === 'id' && canBeUsedToMatch) || false,
|
||||
display: true,
|
||||
type,
|
||||
|
|
|
@ -1230,6 +1230,11 @@ export const validateResourceMapperParameter = (
|
|||
value: ResourceMapperValue,
|
||||
skipRequiredCheck = false,
|
||||
): Record<string, string[]> => {
|
||||
// No issues to raise in automatic mapping mode, no user input to validate
|
||||
if (value.mappingMode === 'autoMapInputData') {
|
||||
return {};
|
||||
}
|
||||
|
||||
const issues: Record<string, string[]> = {};
|
||||
let fieldWordSingular =
|
||||
nodeProperties.typeOptions?.resourceMapper?.fieldWords?.singular || 'Field';
|
||||
|
|
Loading…
Reference in a new issue