feat: extension, triggers, functions, comments, parameters management in sql-tools (#17269)

feat: sql-tools extension, triggers, functions, comments, parameters
This commit is contained in:
Jason Rasmussen 2025-04-07 15:12:12 -04:00 committed by GitHub
parent 51c2c60231
commit e7a5b96ed0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
170 changed files with 5205 additions and 2295 deletions

View file

@ -0,0 +1,21 @@
import { schemaDiffToSql } from 'src/sql-tools';
import { describe, expect, it } from 'vitest';
describe(schemaDiffToSql.name, () => {
describe('comments', () => {
it('should include the reason in a SQL comment', () => {
expect(
schemaDiffToSql(
[
{
type: 'index.drop',
indexName: 'IDX_test',
reason: 'unknown',
},
],
{ comments: true },
),
).toEqual([`DROP INDEX "IDX_test"; -- unknown`]);
});
});
});

View file

@ -0,0 +1,59 @@
import { transformColumns } from 'src/sql-tools/to-sql/transformers/column.transformer';
import { transformConstraints } from 'src/sql-tools/to-sql/transformers/constraint.transformer';
import { transformEnums } from 'src/sql-tools/to-sql/transformers/enum.transformer';
import { transformExtensions } from 'src/sql-tools/to-sql/transformers/extension.transformer';
import { transformFunctions } from 'src/sql-tools/to-sql/transformers/function.transformer';
import { transformIndexes } from 'src/sql-tools/to-sql/transformers/index.transformer';
import { transformParameters } from 'src/sql-tools/to-sql/transformers/parameter.transformer';
import { transformTables } from 'src/sql-tools/to-sql/transformers/table.transformer';
import { transformTriggers } from 'src/sql-tools/to-sql/transformers/trigger.transformer';
import { SqlTransformer } from 'src/sql-tools/to-sql/transformers/types';
import { SchemaDiff, SchemaDiffToSqlOptions } from 'src/sql-tools/types';
/**
* Convert schema diffs into SQL statements
*/
export const schemaDiffToSql = (items: SchemaDiff[], options: SchemaDiffToSqlOptions = {}): string[] => {
return items.flatMap((item) => asSql(item).map((result) => result + withComments(options.comments, item)));
};
const transformers: SqlTransformer[] = [
transformColumns,
transformConstraints,
transformEnums,
transformExtensions,
transformFunctions,
transformIndexes,
transformParameters,
transformTables,
transformTriggers,
];
const asSql = (item: SchemaDiff): string[] => {
for (const transform of transformers) {
const result = transform(item);
if (!result) {
continue;
}
return asArray(result);
}
throw new Error(`Unhandled schema diff type: ${item.type}`);
};
const withComments = (comments: boolean | undefined, item: SchemaDiff): string => {
if (!comments) {
return '';
}
return ` -- ${item.reason}`;
};
const asArray = <T>(items: T | T[]): T[] => {
if (Array.isArray(items)) {
return items;
}
return [items];
};

View file

@ -0,0 +1,126 @@
import { transformColumns } from 'src/sql-tools/to-sql/transformers/column.transformer';
import { describe, expect, it } from 'vitest';
describe(transformColumns.name, () => {
describe('column.add', () => {
it('should work', () => {
expect(
transformColumns({
type: 'column.add',
column: {
name: 'column1',
tableName: 'table1',
type: 'character varying',
nullable: false,
isArray: false,
synchronize: true,
},
reason: 'unknown',
}),
).toEqual('ALTER TABLE "table1" ADD "column1" character varying NOT NULL;');
});
it('should add a nullable column', () => {
expect(
transformColumns({
type: 'column.add',
column: {
name: 'column1',
tableName: 'table1',
type: 'character varying',
nullable: true,
isArray: false,
synchronize: true,
},
reason: 'unknown',
}),
).toEqual('ALTER TABLE "table1" ADD "column1" character varying;');
});
it('should add a column with an enum type', () => {
expect(
transformColumns({
type: 'column.add',
column: {
name: 'column1',
tableName: 'table1',
type: 'character varying',
enumName: 'table1_column1_enum',
nullable: true,
isArray: false,
synchronize: true,
},
reason: 'unknown',
}),
).toEqual('ALTER TABLE "table1" ADD "column1" table1_column1_enum;');
});
it('should add a column that is an array type', () => {
expect(
transformColumns({
type: 'column.add',
column: {
name: 'column1',
tableName: 'table1',
type: 'boolean',
nullable: true,
isArray: true,
synchronize: true,
},
reason: 'unknown',
}),
).toEqual('ALTER TABLE "table1" ADD "column1" boolean[];');
});
});
describe('column.alter', () => {
it('should make a column nullable', () => {
expect(
transformColumns({
type: 'column.alter',
tableName: 'table1',
columnName: 'column1',
changes: { nullable: true },
reason: 'unknown',
}),
).toEqual([`ALTER TABLE "table1" ALTER COLUMN "column1" DROP NOT NULL;`]);
});
it('should make a column non-nullable', () => {
expect(
transformColumns({
type: 'column.alter',
tableName: 'table1',
columnName: 'column1',
changes: { nullable: false },
reason: 'unknown',
}),
).toEqual([`ALTER TABLE "table1" ALTER COLUMN "column1" SET NOT NULL;`]);
});
it('should update the default value', () => {
expect(
transformColumns({
type: 'column.alter',
tableName: 'table1',
columnName: 'column1',
changes: { default: 'uuid_generate_v4()' },
reason: 'unknown',
}),
).toEqual([`ALTER TABLE "table1" ALTER COLUMN "column1" SET DEFAULT uuid_generate_v4();`]);
});
});
describe('column.drop', () => {
it('should work', () => {
expect(
transformColumns({
type: 'column.drop',
tableName: 'table1',
columnName: 'column1',
reason: 'unknown',
}),
).toEqual(`ALTER TABLE "table1" DROP COLUMN "column1";`);
});
});
});

View file

@ -0,0 +1,55 @@
import { asColumnComment, getColumnModifiers, getColumnType } from 'src/sql-tools/helpers';
import { SqlTransformer } from 'src/sql-tools/to-sql/transformers/types';
import { ColumnChanges, DatabaseColumn, SchemaDiff } from 'src/sql-tools/types';
export const transformColumns: SqlTransformer = (item: SchemaDiff) => {
switch (item.type) {
case 'column.add': {
return asColumnAdd(item.column);
}
case 'column.alter': {
return asColumnAlter(item.tableName, item.columnName, item.changes);
}
case 'column.drop': {
return asColumnDrop(item.tableName, item.columnName);
}
default: {
return false;
}
}
};
const asColumnAdd = (column: DatabaseColumn): string => {
return (
`ALTER TABLE "${column.tableName}" ADD "${column.name}" ${getColumnType(column)}` + getColumnModifiers(column) + ';'
);
};
const asColumnDrop = (tableName: string, columnName: string): string => {
return `ALTER TABLE "${tableName}" DROP COLUMN "${columnName}";`;
};
export const asColumnAlter = (tableName: string, columnName: string, changes: ColumnChanges): string[] => {
const base = `ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}"`;
const items: string[] = [];
if (changes.nullable !== undefined) {
items.push(changes.nullable ? `${base} DROP NOT NULL;` : `${base} SET NOT NULL;`);
}
if (changes.default !== undefined) {
items.push(`${base} SET DEFAULT ${changes.default};`);
}
if (changes.storage !== undefined) {
items.push(`${base} SET STORAGE ${changes.storage.toUpperCase()};`);
}
if (changes.comment !== undefined) {
items.push(asColumnComment(tableName, columnName, changes.comment));
}
return items;
};

View file

@ -0,0 +1,96 @@
import { transformConstraints } from 'src/sql-tools/to-sql/transformers/constraint.transformer';
import { DatabaseConstraintType } from 'src/sql-tools/types';
import { describe, expect, it } from 'vitest';
describe(transformConstraints.name, () => {
describe('constraint.add', () => {
describe('primary keys', () => {
it('should work', () => {
expect(
transformConstraints({
type: 'constraint.add',
constraint: {
type: DatabaseConstraintType.PRIMARY_KEY,
name: 'PK_test',
tableName: 'table1',
columnNames: ['id'],
synchronize: true,
},
reason: 'unknown',
}),
).toEqual('ALTER TABLE "table1" ADD CONSTRAINT "PK_test" PRIMARY KEY ("id");');
});
});
describe('foreign keys', () => {
it('should work', () => {
expect(
transformConstraints({
type: 'constraint.add',
constraint: {
type: DatabaseConstraintType.FOREIGN_KEY,
name: 'FK_test',
tableName: 'table1',
columnNames: ['parentId'],
referenceColumnNames: ['id'],
referenceTableName: 'table2',
synchronize: true,
},
reason: 'unknown',
}),
).toEqual(
'ALTER TABLE "table1" ADD CONSTRAINT "FK_test" FOREIGN KEY ("parentId") REFERENCES "table2" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION;',
);
});
});
describe('unique', () => {
it('should work', () => {
expect(
transformConstraints({
type: 'constraint.add',
constraint: {
type: DatabaseConstraintType.UNIQUE,
name: 'UQ_test',
tableName: 'table1',
columnNames: ['id'],
synchronize: true,
},
reason: 'unknown',
}),
).toEqual('ALTER TABLE "table1" ADD CONSTRAINT "UQ_test" UNIQUE ("id");');
});
});
describe('check', () => {
it('should work', () => {
expect(
transformConstraints({
type: 'constraint.add',
constraint: {
type: DatabaseConstraintType.CHECK,
name: 'CHK_test',
tableName: 'table1',
expression: '"id" IS NOT NULL',
synchronize: true,
},
reason: 'unknown',
}),
).toEqual('ALTER TABLE "table1" ADD CONSTRAINT "CHK_test" CHECK ("id" IS NOT NULL);');
});
});
});
describe('constraint.drop', () => {
it('should work', () => {
expect(
transformConstraints({
type: 'constraint.drop',
tableName: 'table1',
constraintName: 'PK_test',
reason: 'unknown',
}),
).toEqual(`ALTER TABLE "table1" DROP CONSTRAINT "PK_test";`);
});
});
});

View file

@ -0,0 +1,58 @@
import { asColumnList } from 'src/sql-tools/helpers';
import { SqlTransformer } from 'src/sql-tools/to-sql/transformers/types';
import { DatabaseActionType, DatabaseConstraint, DatabaseConstraintType, SchemaDiff } from 'src/sql-tools/types';
export const transformConstraints: SqlTransformer = (item: SchemaDiff) => {
switch (item.type) {
case 'constraint.add': {
return asConstraintAdd(item.constraint);
}
case 'constraint.drop': {
return asConstraintDrop(item.tableName, item.constraintName);
}
default: {
return false;
}
}
};
const withAction = (constraint: { onDelete?: DatabaseActionType; onUpdate?: DatabaseActionType }) =>
` ON UPDATE ${constraint.onUpdate ?? DatabaseActionType.NO_ACTION} ON DELETE ${constraint.onDelete ?? DatabaseActionType.NO_ACTION}`;
export const asConstraintAdd = (constraint: DatabaseConstraint): string | string[] => {
const base = `ALTER TABLE "${constraint.tableName}" ADD CONSTRAINT "${constraint.name}"`;
switch (constraint.type) {
case DatabaseConstraintType.PRIMARY_KEY: {
const columnNames = asColumnList(constraint.columnNames);
return `${base} PRIMARY KEY (${columnNames});`;
}
case DatabaseConstraintType.FOREIGN_KEY: {
const columnNames = asColumnList(constraint.columnNames);
const referenceColumnNames = asColumnList(constraint.referenceColumnNames);
return (
`${base} FOREIGN KEY (${columnNames}) REFERENCES "${constraint.referenceTableName}" (${referenceColumnNames})` +
withAction(constraint) +
';'
);
}
case DatabaseConstraintType.UNIQUE: {
const columnNames = asColumnList(constraint.columnNames);
return `${base} UNIQUE (${columnNames});`;
}
case DatabaseConstraintType.CHECK: {
return `${base} CHECK (${constraint.expression});`;
}
default: {
return [];
}
}
};
export const asConstraintDrop = (tableName: string, constraintName: string): string => {
return `ALTER TABLE "${tableName}" DROP CONSTRAINT "${constraintName}";`;
};

View file

@ -0,0 +1,26 @@
import { SqlTransformer } from 'src/sql-tools/to-sql/transformers/types';
import { DatabaseEnum, SchemaDiff } from 'src/sql-tools/types';
export const transformEnums: SqlTransformer = (item: SchemaDiff) => {
switch (item.type) {
case 'enum.create': {
return asEnumCreate(item.enum);
}
case 'enum.drop': {
return asEnumDrop(item.enumName);
}
default: {
return false;
}
}
};
const asEnumCreate = ({ name, values }: DatabaseEnum): string => {
return `CREATE TYPE "${name}" AS ENUM (${values.map((value) => `'${value}'`)});`;
};
const asEnumDrop = (enumName: string): string => {
return `DROP TYPE "${enumName}";`;
};

View file

@ -0,0 +1,31 @@
import { transformExtensions } from 'src/sql-tools/to-sql/transformers/extension.transformer';
import { describe, expect, it } from 'vitest';
describe(transformExtensions.name, () => {
describe('extension.drop', () => {
it('should work', () => {
expect(
transformExtensions({
type: 'extension.drop',
extensionName: 'cube',
reason: 'unknown',
}),
).toEqual(`DROP EXTENSION "cube";`);
});
});
describe('extension.create', () => {
it('should work', () => {
expect(
transformExtensions({
type: 'extension.create',
extension: {
name: 'cube',
synchronize: true,
},
reason: 'unknown',
}),
).toEqual(`CREATE EXTENSION IF NOT EXISTS "cube";`);
});
});
});

View file

@ -0,0 +1,26 @@
import { SqlTransformer } from 'src/sql-tools/to-sql/transformers/types';
import { DatabaseExtension, SchemaDiff } from 'src/sql-tools/types';
export const transformExtensions: SqlTransformer = (item: SchemaDiff) => {
switch (item.type) {
case 'extension.create': {
return asExtensionCreate(item.extension);
}
case 'extension.drop': {
return asExtensionDrop(item.extensionName);
}
default: {
return false;
}
}
};
const asExtensionCreate = (extension: DatabaseExtension): string => {
return `CREATE EXTENSION IF NOT EXISTS "${extension.name}";`;
};
const asExtensionDrop = (extensionName: string): string => {
return `DROP EXTENSION "${extensionName}";`;
};

View file

@ -0,0 +1,16 @@
import { transformFunctions } from 'src/sql-tools/to-sql/transformers/function.transformer';
import { describe, expect, it } from 'vitest';
describe(transformFunctions.name, () => {
describe('function.drop', () => {
it('should work', () => {
expect(
transformFunctions({
type: 'function.drop',
functionName: 'test_func',
reason: 'unknown',
}),
).toEqual(`DROP FUNCTION test_func;`);
});
});
});

View file

@ -0,0 +1,26 @@
import { SqlTransformer } from 'src/sql-tools/to-sql/transformers/types';
import { DatabaseFunction, SchemaDiff } from 'src/sql-tools/types';
export const transformFunctions: SqlTransformer = (item: SchemaDiff) => {
switch (item.type) {
case 'function.create': {
return asFunctionCreate(item.function);
}
case 'function.drop': {
return asFunctionDrop(item.functionName);
}
default: {
return false;
}
}
};
const asFunctionCreate = (func: DatabaseFunction): string => {
return func.expression;
};
const asFunctionDrop = (functionName: string): string => {
return `DROP FUNCTION ${functionName};`;
};

View file

@ -0,0 +1,100 @@
import { transformIndexes } from 'src/sql-tools/to-sql/transformers/index.transformer';
import { describe, expect, it } from 'vitest';
describe(transformIndexes.name, () => {
describe('index.create', () => {
it('should work', () => {
expect(
transformIndexes({
type: 'index.create',
index: {
name: 'IDX_test',
tableName: 'table1',
columnNames: ['column1'],
unique: false,
synchronize: true,
},
reason: 'unknown',
}),
).toEqual('CREATE INDEX "IDX_test" ON "table1" ("column1")');
});
it('should create an unique index', () => {
expect(
transformIndexes({
type: 'index.create',
index: {
name: 'IDX_test',
tableName: 'table1',
columnNames: ['column1'],
unique: true,
synchronize: true,
},
reason: 'unknown',
}),
).toEqual('CREATE UNIQUE INDEX "IDX_test" ON "table1" ("column1")');
});
it('should create an index with a custom expression', () => {
expect(
transformIndexes({
type: 'index.create',
index: {
name: 'IDX_test',
tableName: 'table1',
unique: false,
expression: '"id" IS NOT NULL',
synchronize: true,
},
reason: 'unknown',
}),
).toEqual('CREATE INDEX "IDX_test" ON "table1" ("id" IS NOT NULL)');
});
it('should create an index with a where clause', () => {
expect(
transformIndexes({
type: 'index.create',
index: {
name: 'IDX_test',
tableName: 'table1',
columnNames: ['id'],
unique: false,
where: '("id" IS NOT NULL)',
synchronize: true,
},
reason: 'unknown',
}),
).toEqual('CREATE INDEX "IDX_test" ON "table1" ("id") WHERE ("id" IS NOT NULL)');
});
it('should create an index with a custom expression', () => {
expect(
transformIndexes({
type: 'index.create',
index: {
name: 'IDX_test',
tableName: 'table1',
unique: false,
using: 'gin',
expression: '"id" IS NOT NULL',
synchronize: true,
},
reason: 'unknown',
}),
).toEqual('CREATE INDEX "IDX_test" ON "table1" USING gin ("id" IS NOT NULL)');
});
});
describe('index.drop', () => {
it('should work', () => {
expect(
transformIndexes({
type: 'index.drop',
indexName: 'IDX_test',
reason: 'unknown',
}),
).toEqual(`DROP INDEX "IDX_test";`);
});
});
});

View file

@ -0,0 +1,56 @@
import { asColumnList } from 'src/sql-tools/helpers';
import { SqlTransformer } from 'src/sql-tools/to-sql/transformers/types';
import { DatabaseIndex, SchemaDiff } from 'src/sql-tools/types';
export const transformIndexes: SqlTransformer = (item: SchemaDiff) => {
switch (item.type) {
case 'index.create': {
return asIndexCreate(item.index);
}
case 'index.drop': {
return asIndexDrop(item.indexName);
}
default: {
return false;
}
}
};
export const asIndexCreate = (index: DatabaseIndex): string => {
let sql = `CREATE`;
if (index.unique) {
sql += ' UNIQUE';
}
sql += ` INDEX "${index.name}" ON "${index.tableName}"`;
if (index.columnNames) {
const columnNames = asColumnList(index.columnNames);
sql += ` (${columnNames})`;
}
if (index.using && index.using !== 'btree') {
sql += ` USING ${index.using}`;
}
if (index.expression) {
sql += ` (${index.expression})`;
}
if (index.with) {
sql += ` WITH (${index.with})`;
}
if (index.where) {
sql += ` WHERE ${index.where}`;
}
return sql;
};
export const asIndexDrop = (indexName: string): string => {
return `DROP INDEX "${indexName}";`;
};

View file

@ -0,0 +1,33 @@
import { SqlTransformer } from 'src/sql-tools/to-sql/transformers/types';
import { DatabaseParameter, SchemaDiff } from 'src/sql-tools/types';
export const transformParameters: SqlTransformer = (item: SchemaDiff) => {
switch (item.type) {
case 'parameter.set': {
return asParameterSet(item.parameter);
}
case 'parameter.reset': {
return asParameterReset(item.databaseName, item.parameterName);
}
default: {
return false;
}
}
};
const asParameterSet = (parameter: DatabaseParameter): string => {
let sql = '';
if (parameter.scope === 'database') {
sql += `ALTER DATABASE "${parameter.databaseName}" `;
}
sql += `SET ${parameter.name} TO ${parameter.value}`;
return sql;
};
const asParameterReset = (databaseName: string, parameterName: string): string => {
return `ALTER DATABASE "${databaseName}" RESET "${parameterName}"`;
};

View file

@ -0,0 +1,150 @@
import { transformTables } from 'src/sql-tools/to-sql/transformers/table.transformer';
import { describe, expect, it } from 'vitest';
describe(transformTables.name, () => {
describe('table.drop', () => {
it('should work', () => {
expect(
transformTables({
type: 'table.drop',
tableName: 'table1',
reason: 'unknown',
}),
).toEqual(`DROP TABLE "table1";`);
});
});
describe('table.create', () => {
it('should work', () => {
expect(
transformTables({
type: 'table.create',
table: {
name: 'table1',
columns: [
{
tableName: 'table1',
name: 'column1',
type: 'character varying',
nullable: true,
isArray: false,
synchronize: true,
},
],
indexes: [],
constraints: [],
triggers: [],
synchronize: true,
},
reason: 'unknown',
}),
).toEqual([`CREATE TABLE "table1" ("column1" character varying);`]);
});
it('should handle a non-nullable column', () => {
expect(
transformTables({
type: 'table.create',
table: {
name: 'table1',
columns: [
{
tableName: 'table1',
name: 'column1',
type: 'character varying',
isArray: false,
nullable: false,
synchronize: true,
},
],
indexes: [],
constraints: [],
triggers: [],
synchronize: true,
},
reason: 'unknown',
}),
).toEqual([`CREATE TABLE "table1" ("column1" character varying NOT NULL);`]);
});
it('should handle a default value', () => {
expect(
transformTables({
type: 'table.create',
table: {
name: 'table1',
columns: [
{
tableName: 'table1',
name: 'column1',
type: 'character varying',
isArray: false,
nullable: true,
default: 'uuid_generate_v4()',
synchronize: true,
},
],
indexes: [],
constraints: [],
triggers: [],
synchronize: true,
},
reason: 'unknown',
}),
).toEqual([`CREATE TABLE "table1" ("column1" character varying DEFAULT uuid_generate_v4());`]);
});
it('should handle a string with a fixed length', () => {
expect(
transformTables({
type: 'table.create',
table: {
name: 'table1',
columns: [
{
tableName: 'table1',
name: 'column1',
type: 'character varying',
length: 2,
isArray: false,
nullable: true,
synchronize: true,
},
],
indexes: [],
constraints: [],
triggers: [],
synchronize: true,
},
reason: 'unknown',
}),
).toEqual([`CREATE TABLE "table1" ("column1" character varying(2));`]);
});
it('should handle an array type', () => {
expect(
transformTables({
type: 'table.create',
table: {
name: 'table1',
columns: [
{
tableName: 'table1',
name: 'column1',
type: 'character varying',
isArray: true,
nullable: true,
synchronize: true,
},
],
indexes: [],
constraints: [],
triggers: [],
synchronize: true,
},
reason: 'unknown',
}),
).toEqual([`CREATE TABLE "table1" ("column1" character varying[]);`]);
});
});
});

View file

@ -0,0 +1,44 @@
import { asColumnComment, getColumnModifiers, getColumnType } from 'src/sql-tools/helpers';
import { asColumnAlter } from 'src/sql-tools/to-sql/transformers/column.transformer';
import { SqlTransformer } from 'src/sql-tools/to-sql/transformers/types';
import { DatabaseTable, SchemaDiff } from 'src/sql-tools/types';
export const transformTables: SqlTransformer = (item: SchemaDiff) => {
switch (item.type) {
case 'table.create': {
return asTableCreate(item.table);
}
case 'table.drop': {
return asTableDrop(item.tableName);
}
default: {
return false;
}
}
};
const asTableCreate = (table: DatabaseTable): string[] => {
const tableName = table.name;
const columnsTypes = table.columns
.map((column) => `"${column.name}" ${getColumnType(column)}` + getColumnModifiers(column))
.join(', ');
const items = [`CREATE TABLE "${tableName}" (${columnsTypes});`];
for (const column of table.columns) {
if (column.comment) {
items.push(asColumnComment(tableName, column.name, column.comment));
}
if (column.storage) {
items.push(...asColumnAlter(tableName, column.name, { storage: column.storage }));
}
}
return items;
};
const asTableDrop = (tableName: string): string => {
return `DROP TABLE "${tableName}";`;
};

View file

@ -0,0 +1,91 @@
import { transformTriggers } from 'src/sql-tools/to-sql/transformers/trigger.transformer';
import { describe, expect, it } from 'vitest';
describe(transformTriggers.name, () => {
describe('trigger.create', () => {
it('should work', () => {
expect(
transformTriggers({
type: 'trigger.create',
trigger: {
name: 'trigger1',
tableName: 'table1',
timing: 'before',
actions: ['update'],
scope: 'row',
functionName: 'function1',
synchronize: true,
},
reason: 'unknown',
}),
).toEqual(
`CREATE OR REPLACE TRIGGER "trigger1"
BEFORE UPDATE ON "table1"
FOR EACH ROW
EXECUTE FUNCTION function1();`,
);
});
it('should work with multiple actions', () => {
expect(
transformTriggers({
type: 'trigger.create',
trigger: {
name: 'trigger1',
tableName: 'table1',
timing: 'before',
actions: ['update', 'delete'],
scope: 'row',
functionName: 'function1',
synchronize: true,
},
reason: 'unknown',
}),
).toEqual(
`CREATE OR REPLACE TRIGGER "trigger1"
BEFORE UPDATE OR DELETE ON "table1"
FOR EACH ROW
EXECUTE FUNCTION function1();`,
);
});
it('should work with old/new reference table aliases', () => {
expect(
transformTriggers({
type: 'trigger.create',
trigger: {
name: 'trigger1',
tableName: 'table1',
timing: 'before',
actions: ['update'],
referencingNewTableAs: 'new',
referencingOldTableAs: 'old',
scope: 'row',
functionName: 'function1',
synchronize: true,
},
reason: 'unknown',
}),
).toEqual(
`CREATE OR REPLACE TRIGGER "trigger1"
BEFORE UPDATE ON "table1"
REFERENCING OLD TABLE AS "old" NEW TABLE AS "new"
FOR EACH ROW
EXECUTE FUNCTION function1();`,
);
});
});
describe('trigger.drop', () => {
it('should work', () => {
expect(
transformTriggers({
type: 'trigger.drop',
tableName: 'table1',
triggerName: 'trigger1',
reason: 'unknown',
}),
).toEqual(`DROP TRIGGER "trigger1" ON "table1";`);
});
});
});

View file

@ -0,0 +1,52 @@
import { SqlTransformer } from 'src/sql-tools/to-sql/transformers/types';
import { DatabaseTrigger, SchemaDiff } from 'src/sql-tools/types';
export const transformTriggers: SqlTransformer = (item: SchemaDiff) => {
switch (item.type) {
case 'trigger.create': {
return asTriggerCreate(item.trigger);
}
case 'trigger.drop': {
return asTriggerDrop(item.tableName, item.triggerName);
}
default: {
return false;
}
}
};
export const asTriggerCreate = (trigger: DatabaseTrigger): string => {
const sql: string[] = [
`CREATE OR REPLACE TRIGGER "${trigger.name}"`,
`${trigger.timing.toUpperCase()} ${trigger.actions.map((action) => action.toUpperCase()).join(' OR ')} ON "${trigger.tableName}"`,
];
if (trigger.referencingOldTableAs || trigger.referencingNewTableAs) {
let statement = `REFERENCING`;
if (trigger.referencingOldTableAs) {
statement += ` OLD TABLE AS "${trigger.referencingOldTableAs}"`;
}
if (trigger.referencingNewTableAs) {
statement += ` NEW TABLE AS "${trigger.referencingNewTableAs}"`;
}
sql.push(statement);
}
if (trigger.scope) {
sql.push(`FOR EACH ${trigger.scope.toUpperCase()}`);
}
if (trigger.when) {
sql.push(`WHEN (${trigger.when})`);
}
sql.push(`EXECUTE FUNCTION ${trigger.functionName}();`);
return sql.join('\n ');
};
export const asTriggerDrop = (tableName: string, triggerName: string): string => {
return `DROP TRIGGER "${triggerName}" ON "${tableName}";`;
};

View file

@ -0,0 +1,3 @@
import { SchemaDiff } from 'src/sql-tools/types';
export type SqlTransformer = (item: SchemaDiff) => string | string[] | false;