mirror of
https://github.com/immich-app/immich.git
synced 2025-12-25 09:14:58 +03:00
refactor: sql-tools (#19717)
This commit is contained in:
126
server/src/sql-tools/transformers/column.transformer.spec.ts
Normal file
126
server/src/sql-tools/transformers/column.transformer.spec.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { transformColumns } from 'src/sql-tools/transformers/column.transformer';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
describe(transformColumns.name, () => {
|
||||
describe('ColumnAdd', () => {
|
||||
it('should work', () => {
|
||||
expect(
|
||||
transformColumns({
|
||||
type: 'ColumnAdd',
|
||||
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: 'ColumnAdd',
|
||||
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: 'ColumnAdd',
|
||||
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: 'ColumnAdd',
|
||||
column: {
|
||||
name: 'column1',
|
||||
tableName: 'table1',
|
||||
type: 'boolean',
|
||||
nullable: true,
|
||||
isArray: true,
|
||||
synchronize: true,
|
||||
},
|
||||
reason: 'unknown',
|
||||
}),
|
||||
).toEqual('ALTER TABLE "table1" ADD "column1" boolean[];');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ColumnAlter', () => {
|
||||
it('should make a column nullable', () => {
|
||||
expect(
|
||||
transformColumns({
|
||||
type: 'ColumnAlter',
|
||||
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: 'ColumnAlter',
|
||||
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: 'ColumnAlter',
|
||||
tableName: 'table1',
|
||||
columnName: 'column1',
|
||||
changes: { default: 'uuid_generate_v4()' },
|
||||
reason: 'unknown',
|
||||
}),
|
||||
).toEqual([`ALTER TABLE "table1" ALTER COLUMN "column1" SET DEFAULT uuid_generate_v4();`]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ColumnDrop', () => {
|
||||
it('should work', () => {
|
||||
expect(
|
||||
transformColumns({
|
||||
type: 'ColumnDrop',
|
||||
tableName: 'table1',
|
||||
columnName: 'column1',
|
||||
reason: 'unknown',
|
||||
}),
|
||||
).toEqual(`ALTER TABLE "table1" DROP COLUMN "column1";`);
|
||||
});
|
||||
});
|
||||
});
|
||||
55
server/src/sql-tools/transformers/column.transformer.ts
Normal file
55
server/src/sql-tools/transformers/column.transformer.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { asColumnComment, getColumnModifiers, getColumnType } from 'src/sql-tools/helpers';
|
||||
import { SqlTransformer } from 'src/sql-tools/transformers/types';
|
||||
import { ColumnChanges, DatabaseColumn, SchemaDiff } from 'src/sql-tools/types';
|
||||
|
||||
export const transformColumns: SqlTransformer = (item: SchemaDiff) => {
|
||||
switch (item.type) {
|
||||
case 'ColumnAdd': {
|
||||
return asColumnAdd(item.column);
|
||||
}
|
||||
|
||||
case 'ColumnAlter': {
|
||||
return asColumnAlter(item.tableName, item.columnName, item.changes);
|
||||
}
|
||||
|
||||
case 'ColumnDrop': {
|
||||
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;
|
||||
};
|
||||
@@ -0,0 +1,96 @@
|
||||
import { transformConstraints } from 'src/sql-tools/transformers/constraint.transformer';
|
||||
import { ConstraintType } from 'src/sql-tools/types';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
describe(transformConstraints.name, () => {
|
||||
describe('ConstraintAdd', () => {
|
||||
describe('primary keys', () => {
|
||||
it('should work', () => {
|
||||
expect(
|
||||
transformConstraints({
|
||||
type: 'ConstraintAdd',
|
||||
constraint: {
|
||||
type: ConstraintType.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: 'ConstraintAdd',
|
||||
constraint: {
|
||||
type: ConstraintType.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: 'ConstraintAdd',
|
||||
constraint: {
|
||||
type: ConstraintType.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: 'ConstraintAdd',
|
||||
constraint: {
|
||||
type: ConstraintType.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('ConstraintDrop', () => {
|
||||
it('should work', () => {
|
||||
expect(
|
||||
transformConstraints({
|
||||
type: 'ConstraintDrop',
|
||||
tableName: 'table1',
|
||||
constraintName: 'PK_test',
|
||||
reason: 'unknown',
|
||||
}),
|
||||
).toEqual(`ALTER TABLE "table1" DROP CONSTRAINT "PK_test";`);
|
||||
});
|
||||
});
|
||||
});
|
||||
58
server/src/sql-tools/transformers/constraint.transformer.ts
Normal file
58
server/src/sql-tools/transformers/constraint.transformer.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { asColumnList } from 'src/sql-tools/helpers';
|
||||
import { SqlTransformer } from 'src/sql-tools/transformers/types';
|
||||
import { ActionType, ConstraintType, DatabaseConstraint, SchemaDiff } from 'src/sql-tools/types';
|
||||
|
||||
export const transformConstraints: SqlTransformer = (item: SchemaDiff) => {
|
||||
switch (item.type) {
|
||||
case 'ConstraintAdd': {
|
||||
return asConstraintAdd(item.constraint);
|
||||
}
|
||||
|
||||
case 'ConstraintDrop': {
|
||||
return asConstraintDrop(item.tableName, item.constraintName);
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const withAction = (constraint: { onDelete?: ActionType; onUpdate?: ActionType }) =>
|
||||
` ON UPDATE ${constraint.onUpdate ?? ActionType.NO_ACTION} ON DELETE ${constraint.onDelete ?? ActionType.NO_ACTION}`;
|
||||
|
||||
export const asConstraintAdd = (constraint: DatabaseConstraint): string | string[] => {
|
||||
const base = `ALTER TABLE "${constraint.tableName}" ADD CONSTRAINT "${constraint.name}"`;
|
||||
switch (constraint.type) {
|
||||
case ConstraintType.PRIMARY_KEY: {
|
||||
const columnNames = asColumnList(constraint.columnNames);
|
||||
return `${base} PRIMARY KEY (${columnNames});`;
|
||||
}
|
||||
|
||||
case ConstraintType.FOREIGN_KEY: {
|
||||
const columnNames = asColumnList(constraint.columnNames);
|
||||
const referenceColumnNames = asColumnList(constraint.referenceColumnNames);
|
||||
return (
|
||||
`${base} FOREIGN KEY (${columnNames}) REFERENCES "${constraint.referenceTableName}" (${referenceColumnNames})` +
|
||||
withAction(constraint) +
|
||||
';'
|
||||
);
|
||||
}
|
||||
|
||||
case ConstraintType.UNIQUE: {
|
||||
const columnNames = asColumnList(constraint.columnNames);
|
||||
return `${base} UNIQUE (${columnNames});`;
|
||||
}
|
||||
|
||||
case ConstraintType.CHECK: {
|
||||
return `${base} CHECK (${constraint.expression});`;
|
||||
}
|
||||
|
||||
default: {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const asConstraintDrop = (tableName: string, constraintName: string): string => {
|
||||
return `ALTER TABLE "${tableName}" DROP CONSTRAINT "${constraintName}";`;
|
||||
};
|
||||
26
server/src/sql-tools/transformers/enum.transformer.ts
Normal file
26
server/src/sql-tools/transformers/enum.transformer.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { SqlTransformer } from 'src/sql-tools/transformers/types';
|
||||
import { DatabaseEnum, SchemaDiff } from 'src/sql-tools/types';
|
||||
|
||||
export const transformEnums: SqlTransformer = (item: SchemaDiff) => {
|
||||
switch (item.type) {
|
||||
case 'EnumCreate': {
|
||||
return asEnumCreate(item.enum);
|
||||
}
|
||||
|
||||
case 'EnumDrop': {
|
||||
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}";`;
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { transformExtensions } from 'src/sql-tools/transformers/extension.transformer';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
describe(transformExtensions.name, () => {
|
||||
describe('ExtensionDrop', () => {
|
||||
it('should work', () => {
|
||||
expect(
|
||||
transformExtensions({
|
||||
type: 'ExtensionDrop',
|
||||
extensionName: 'cube',
|
||||
reason: 'unknown',
|
||||
}),
|
||||
).toEqual(`DROP EXTENSION "cube";`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ExtensionCreate', () => {
|
||||
it('should work', () => {
|
||||
expect(
|
||||
transformExtensions({
|
||||
type: 'ExtensionCreate',
|
||||
extension: {
|
||||
name: 'cube',
|
||||
synchronize: true,
|
||||
},
|
||||
reason: 'unknown',
|
||||
}),
|
||||
).toEqual(`CREATE EXTENSION IF NOT EXISTS "cube";`);
|
||||
});
|
||||
});
|
||||
});
|
||||
26
server/src/sql-tools/transformers/extension.transformer.ts
Normal file
26
server/src/sql-tools/transformers/extension.transformer.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { SqlTransformer } from 'src/sql-tools/transformers/types';
|
||||
import { DatabaseExtension, SchemaDiff } from 'src/sql-tools/types';
|
||||
|
||||
export const transformExtensions: SqlTransformer = (item: SchemaDiff) => {
|
||||
switch (item.type) {
|
||||
case 'ExtensionCreate': {
|
||||
return asExtensionCreate(item.extension);
|
||||
}
|
||||
|
||||
case 'ExtensionDrop': {
|
||||
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}";`;
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
import { transformFunctions } from 'src/sql-tools/transformers/function.transformer';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
describe(transformFunctions.name, () => {
|
||||
describe('FunctionDrop', () => {
|
||||
it('should work', () => {
|
||||
expect(
|
||||
transformFunctions({
|
||||
type: 'FunctionDrop',
|
||||
functionName: 'test_func',
|
||||
reason: 'unknown',
|
||||
}),
|
||||
).toEqual(`DROP FUNCTION test_func;`);
|
||||
});
|
||||
});
|
||||
});
|
||||
26
server/src/sql-tools/transformers/function.transformer.ts
Normal file
26
server/src/sql-tools/transformers/function.transformer.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { SqlTransformer } from 'src/sql-tools/transformers/types';
|
||||
import { DatabaseFunction, SchemaDiff } from 'src/sql-tools/types';
|
||||
|
||||
export const transformFunctions: SqlTransformer = (item: SchemaDiff) => {
|
||||
switch (item.type) {
|
||||
case 'FunctionCreate': {
|
||||
return asFunctionCreate(item.function);
|
||||
}
|
||||
|
||||
case 'FunctionDrop': {
|
||||
return asFunctionDrop(item.functionName);
|
||||
}
|
||||
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const asFunctionCreate = (func: DatabaseFunction): string => {
|
||||
return func.expression;
|
||||
};
|
||||
|
||||
const asFunctionDrop = (functionName: string): string => {
|
||||
return `DROP FUNCTION ${functionName};`;
|
||||
};
|
||||
100
server/src/sql-tools/transformers/index.transformer.spec.ts
Normal file
100
server/src/sql-tools/transformers/index.transformer.spec.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { transformIndexes } from 'src/sql-tools/transformers/index.transformer';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
describe(transformIndexes.name, () => {
|
||||
describe('IndexCreate', () => {
|
||||
it('should work', () => {
|
||||
expect(
|
||||
transformIndexes({
|
||||
type: 'IndexCreate',
|
||||
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: 'IndexCreate',
|
||||
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: 'IndexCreate',
|
||||
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: 'IndexCreate',
|
||||
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: 'IndexCreate',
|
||||
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('IndexDrop', () => {
|
||||
it('should work', () => {
|
||||
expect(
|
||||
transformIndexes({
|
||||
type: 'IndexDrop',
|
||||
indexName: 'IDX_test',
|
||||
reason: 'unknown',
|
||||
}),
|
||||
).toEqual(`DROP INDEX "IDX_test";`);
|
||||
});
|
||||
});
|
||||
});
|
||||
56
server/src/sql-tools/transformers/index.transformer.ts
Normal file
56
server/src/sql-tools/transformers/index.transformer.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { asColumnList } from 'src/sql-tools/helpers';
|
||||
import { SqlTransformer } from 'src/sql-tools/transformers/types';
|
||||
import { DatabaseIndex, SchemaDiff } from 'src/sql-tools/types';
|
||||
|
||||
export const transformIndexes: SqlTransformer = (item: SchemaDiff) => {
|
||||
switch (item.type) {
|
||||
case 'IndexCreate': {
|
||||
return asIndexCreate(item.index);
|
||||
}
|
||||
|
||||
case 'IndexDrop': {
|
||||
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}";`;
|
||||
};
|
||||
22
server/src/sql-tools/transformers/index.ts
Normal file
22
server/src/sql-tools/transformers/index.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { transformColumns } from 'src/sql-tools/transformers/column.transformer';
|
||||
import { transformConstraints } from 'src/sql-tools/transformers/constraint.transformer';
|
||||
import { transformEnums } from 'src/sql-tools/transformers/enum.transformer';
|
||||
import { transformExtensions } from 'src/sql-tools/transformers/extension.transformer';
|
||||
import { transformFunctions } from 'src/sql-tools/transformers/function.transformer';
|
||||
import { transformIndexes } from 'src/sql-tools/transformers/index.transformer';
|
||||
import { transformParameters } from 'src/sql-tools/transformers/parameter.transformer';
|
||||
import { transformTables } from 'src/sql-tools/transformers/table.transformer';
|
||||
import { transformTriggers } from 'src/sql-tools/transformers/trigger.transformer';
|
||||
import { SqlTransformer } from 'src/sql-tools/transformers/types';
|
||||
|
||||
export const transformers: SqlTransformer[] = [
|
||||
transformColumns,
|
||||
transformConstraints,
|
||||
transformEnums,
|
||||
transformExtensions,
|
||||
transformFunctions,
|
||||
transformIndexes,
|
||||
transformParameters,
|
||||
transformTables,
|
||||
transformTriggers,
|
||||
];
|
||||
33
server/src/sql-tools/transformers/parameter.transformer.ts
Normal file
33
server/src/sql-tools/transformers/parameter.transformer.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { SqlTransformer } from 'src/sql-tools/transformers/types';
|
||||
import { DatabaseParameter, SchemaDiff } from 'src/sql-tools/types';
|
||||
|
||||
export const transformParameters: SqlTransformer = (item: SchemaDiff) => {
|
||||
switch (item.type) {
|
||||
case 'ParameterSet': {
|
||||
return asParameterSet(item.parameter);
|
||||
}
|
||||
|
||||
case 'ParameterReset': {
|
||||
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}"`;
|
||||
};
|
||||
150
server/src/sql-tools/transformers/table.transformer.spec.ts
Normal file
150
server/src/sql-tools/transformers/table.transformer.spec.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import { transformTables } from 'src/sql-tools/transformers/table.transformer';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
describe(transformTables.name, () => {
|
||||
describe('TableDrop', () => {
|
||||
it('should work', () => {
|
||||
expect(
|
||||
transformTables({
|
||||
type: 'TableDrop',
|
||||
tableName: 'table1',
|
||||
reason: 'unknown',
|
||||
}),
|
||||
).toEqual(`DROP TABLE "table1";`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('TableCreate', () => {
|
||||
it('should work', () => {
|
||||
expect(
|
||||
transformTables({
|
||||
type: 'TableCreate',
|
||||
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: 'TableCreate',
|
||||
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: 'TableCreate',
|
||||
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: 'TableCreate',
|
||||
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: 'TableCreate',
|
||||
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[]);`]);
|
||||
});
|
||||
});
|
||||
});
|
||||
44
server/src/sql-tools/transformers/table.transformer.ts
Normal file
44
server/src/sql-tools/transformers/table.transformer.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { asColumnComment, getColumnModifiers, getColumnType } from 'src/sql-tools/helpers';
|
||||
import { asColumnAlter } from 'src/sql-tools/transformers/column.transformer';
|
||||
import { SqlTransformer } from 'src/sql-tools/transformers/types';
|
||||
import { DatabaseTable, SchemaDiff } from 'src/sql-tools/types';
|
||||
|
||||
export const transformTables: SqlTransformer = (item: SchemaDiff) => {
|
||||
switch (item.type) {
|
||||
case 'TableCreate': {
|
||||
return asTableCreate(item.table);
|
||||
}
|
||||
|
||||
case 'TableDrop': {
|
||||
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}";`;
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
import { transformTriggers } from 'src/sql-tools/transformers/trigger.transformer';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
describe(transformTriggers.name, () => {
|
||||
describe('TriggerCreate', () => {
|
||||
it('should work', () => {
|
||||
expect(
|
||||
transformTriggers({
|
||||
type: 'TriggerCreate',
|
||||
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: 'TriggerCreate',
|
||||
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: 'TriggerCreate',
|
||||
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('TriggerDrop', () => {
|
||||
it('should work', () => {
|
||||
expect(
|
||||
transformTriggers({
|
||||
type: 'TriggerDrop',
|
||||
tableName: 'table1',
|
||||
triggerName: 'trigger1',
|
||||
reason: 'unknown',
|
||||
}),
|
||||
).toEqual(`DROP TRIGGER "trigger1" ON "table1";`);
|
||||
});
|
||||
});
|
||||
});
|
||||
52
server/src/sql-tools/transformers/trigger.transformer.ts
Normal file
52
server/src/sql-tools/transformers/trigger.transformer.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { SqlTransformer } from 'src/sql-tools/transformers/types';
|
||||
import { DatabaseTrigger, SchemaDiff } from 'src/sql-tools/types';
|
||||
|
||||
export const transformTriggers: SqlTransformer = (item: SchemaDiff) => {
|
||||
switch (item.type) {
|
||||
case 'TriggerCreate': {
|
||||
return asTriggerCreate(item.trigger);
|
||||
}
|
||||
|
||||
case 'TriggerDrop': {
|
||||
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}";`;
|
||||
};
|
||||
3
server/src/sql-tools/transformers/types.ts
Normal file
3
server/src/sql-tools/transformers/types.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { SchemaDiff } from 'src/sql-tools/types';
|
||||
|
||||
export type SqlTransformer = (item: SchemaDiff) => string | string[] | false;
|
||||
Reference in New Issue
Block a user