mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-31 08:46:58 +02:00
feat(core): Add enum check helper to migration DSL (#30900)
This commit is contained in:
parent
d4f9223842
commit
1b9dfb20c4
|
|
@ -2,7 +2,7 @@ import type { Driver, QueryRunner, Table } from '@n8n/typeorm';
|
|||
import { mock } from 'jest-mock-extended';
|
||||
|
||||
import { Column } from '../column';
|
||||
import { AddColumns, CreateTable, DropEnumCheck } from '../table';
|
||||
import { AddColumns, AddEnumCheck, CreateTable, DropEnumCheck } from '../table';
|
||||
|
||||
const createMocks = (escapeChar = '"') => {
|
||||
const driver = mock<Driver>();
|
||||
|
|
@ -201,3 +201,43 @@ describe('AddColumns with column-level enum checks', () => {
|
|||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AddEnumCheck', () => {
|
||||
it('should create the check constraint with correct name and expression', async () => {
|
||||
const { queryRunner } = createMocks();
|
||||
|
||||
await new AddEnumCheck('test_table', 'status', ['active', 'inactive'], 'n8n_', queryRunner);
|
||||
|
||||
expect(queryRunner.createCheckConstraints).toHaveBeenCalledWith('n8n_test_table', [
|
||||
expect.objectContaining({
|
||||
name: 'CHK_n8n_test_table_status',
|
||||
expression: "\"status\" IN ('active', 'inactive')",
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should work without a table prefix', async () => {
|
||||
const { queryRunner } = createMocks();
|
||||
|
||||
await new AddEnumCheck('test_table', 'role', ['admin', 'user', 'guest'], '', queryRunner);
|
||||
|
||||
expect(queryRunner.createCheckConstraints).toHaveBeenCalledWith('test_table', [
|
||||
expect.objectContaining({
|
||||
name: 'CHK_test_table_role',
|
||||
expression: "\"role\" IN ('admin', 'user', 'guest')",
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should escape single quotes in enum values', async () => {
|
||||
const { queryRunner } = createMocks();
|
||||
|
||||
await new AddEnumCheck('test_table', 'label', ["it's", "they're"], '', queryRunner);
|
||||
|
||||
expect(queryRunner.createCheckConstraints).toHaveBeenCalledWith('test_table', [
|
||||
expect.objectContaining({
|
||||
expression: "\"label\" IN ('it''s', 'they''re')",
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Column } from './column';
|
|||
import { CreateIndex, DropIndex } from './indices';
|
||||
import {
|
||||
AddColumns,
|
||||
AddEnumCheck,
|
||||
AddForeignKey,
|
||||
AddNotNull,
|
||||
CreateTable,
|
||||
|
|
@ -95,6 +96,10 @@ export const createSchemaBuilder = (tablePrefix: string, queryRunner: QueryRunne
|
|||
dropNotNull: (tableName: string, columnName: string) =>
|
||||
new DropNotNull(tableName, columnName, tablePrefix, queryRunner),
|
||||
|
||||
/** WARNING: This recreates the entire table on SQLite. */
|
||||
addEnumCheck: (tableName: string, columnName: string, values: string[]) =>
|
||||
new AddEnumCheck(tableName, columnName, values, tablePrefix, queryRunner),
|
||||
/** WARNING: This recreates the entire table on SQLite. */
|
||||
dropEnumCheck: (tableName: string, columnName: string) =>
|
||||
new DropEnumCheck(tableName, columnName, tablePrefix, queryRunner),
|
||||
|
||||
|
|
|
|||
|
|
@ -5,24 +5,32 @@ import LazyPromise from 'p-lazy';
|
|||
|
||||
import { Column } from './column';
|
||||
|
||||
function buildEnumCheck(
|
||||
columnName: string,
|
||||
values: string[],
|
||||
prefix: string,
|
||||
tableName: string,
|
||||
driver: Driver,
|
||||
): TableCheck {
|
||||
const checkName = `CHK_${prefix}${tableName}_${columnName}`;
|
||||
const escapedColumnName = driver.escape(columnName);
|
||||
const escapedValues = values.map((v) => `'${v.replace(/'/g, "''")}'`).join(', ');
|
||||
const expression = `${escapedColumnName} IN (${escapedValues})`;
|
||||
return new TableCheck({ name: checkName, expression });
|
||||
}
|
||||
|
||||
function buildEnumChecks(
|
||||
columns: Column[],
|
||||
prefix: string,
|
||||
tableName: string,
|
||||
driver: Driver,
|
||||
): TableCheck[] {
|
||||
const checks: TableCheck[] = [];
|
||||
for (const column of columns) {
|
||||
const enumCheck = column.getEnumCheck();
|
||||
if (enumCheck) {
|
||||
const checkName = `CHK_${prefix}${tableName}_${enumCheck.columnName}`;
|
||||
const escapedColumnName = driver.escape(enumCheck.columnName);
|
||||
const escapedValues = enumCheck.values.map((v) => `'${v.replace(/'/g, "''")}'`).join(', ');
|
||||
const expression = `${escapedColumnName} IN (${escapedValues})`;
|
||||
checks.push(new TableCheck({ name: checkName, expression }));
|
||||
}
|
||||
}
|
||||
return checks;
|
||||
return columns
|
||||
.map((column) => column.getEnumCheck())
|
||||
.filter((enumCheck) => enumCheck !== undefined)
|
||||
.map((enumCheck) =>
|
||||
buildEnumCheck(enumCheck.columnName, enumCheck.values, prefix, tableName, driver),
|
||||
);
|
||||
}
|
||||
|
||||
abstract class TableOperation<R = void> extends LazyPromise<R> {
|
||||
|
|
@ -286,3 +294,24 @@ export class DropEnumCheck extends TableOperation {
|
|||
return await queryRunner.dropCheckConstraint(fullTableName, checkName);
|
||||
}
|
||||
}
|
||||
|
||||
export class AddEnumCheck extends TableOperation {
|
||||
constructor(
|
||||
tableName: string,
|
||||
protected columnName: string,
|
||||
protected values: string[],
|
||||
prefix: string,
|
||||
queryRunner: QueryRunner,
|
||||
) {
|
||||
super(tableName, prefix, queryRunner);
|
||||
}
|
||||
|
||||
async execute(queryRunner: QueryRunner) {
|
||||
const { tableName, prefix, columnName, values } = this;
|
||||
const { driver } = queryRunner.connection;
|
||||
const fullTableName = `${prefix}${tableName}`;
|
||||
return await queryRunner.createCheckConstraints(fullTableName, [
|
||||
buildEnumCheck(columnName, values, prefix, tableName, driver),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user