mirror of
https://github.com/immich-app/immich.git
synced 2025-07-08 18:54:18 -04:00
refactor: sql-tools (#19717)
This commit is contained in:
parent
484529e61e
commit
6044663e26
@ -1,4 +1,4 @@
|
|||||||
import { compareColumns } from 'src/sql-tools/diff/comparers/column.comparer';
|
import { compareColumns } from 'src/sql-tools/comparers/column.comparer';
|
||||||
import { DatabaseColumn, Reason } from 'src/sql-tools/types';
|
import { DatabaseColumn, Reason } from 'src/sql-tools/types';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ describe('compareColumns', () => {
|
|||||||
{
|
{
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
columnName: 'test',
|
columnName: 'test',
|
||||||
type: 'column.drop',
|
type: 'ColumnDrop',
|
||||||
reason: Reason.MissingInSource,
|
reason: Reason.MissingInSource,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
@ -29,7 +29,7 @@ describe('compareColumns', () => {
|
|||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
expect(compareColumns.onMissing(testColumn)).toEqual([
|
expect(compareColumns.onMissing(testColumn)).toEqual([
|
||||||
{
|
{
|
||||||
type: 'column.add',
|
type: 'ColumnAdd',
|
||||||
column: testColumn,
|
column: testColumn,
|
||||||
reason: Reason.MissingInTarget,
|
reason: Reason.MissingInTarget,
|
||||||
},
|
},
|
||||||
@ -50,11 +50,11 @@ describe('compareColumns', () => {
|
|||||||
{
|
{
|
||||||
columnName: 'test',
|
columnName: 'test',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
type: 'column.drop',
|
type: 'ColumnDrop',
|
||||||
reason,
|
reason,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'column.add',
|
type: 'ColumnAdd',
|
||||||
column: source,
|
column: source,
|
||||||
reason,
|
reason,
|
||||||
},
|
},
|
||||||
@ -69,7 +69,7 @@ describe('compareColumns', () => {
|
|||||||
{
|
{
|
||||||
columnName: 'test',
|
columnName: 'test',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
type: 'column.alter',
|
type: 'ColumnAlter',
|
||||||
changes: {
|
changes: {
|
||||||
comment: 'new comment',
|
comment: 'new comment',
|
||||||
},
|
},
|
@ -4,14 +4,14 @@ import { Comparer, DatabaseColumn, Reason, SchemaDiff } from 'src/sql-tools/type
|
|||||||
export const compareColumns: Comparer<DatabaseColumn> = {
|
export const compareColumns: Comparer<DatabaseColumn> = {
|
||||||
onMissing: (source) => [
|
onMissing: (source) => [
|
||||||
{
|
{
|
||||||
type: 'column.add',
|
type: 'ColumnAdd',
|
||||||
column: source,
|
column: source,
|
||||||
reason: Reason.MissingInTarget,
|
reason: Reason.MissingInTarget,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
onExtra: (target) => [
|
onExtra: (target) => [
|
||||||
{
|
{
|
||||||
type: 'column.drop',
|
type: 'ColumnDrop',
|
||||||
tableName: target.tableName,
|
tableName: target.tableName,
|
||||||
columnName: target.name,
|
columnName: target.name,
|
||||||
reason: Reason.MissingInSource,
|
reason: Reason.MissingInSource,
|
||||||
@ -31,7 +31,7 @@ export const compareColumns: Comparer<DatabaseColumn> = {
|
|||||||
const items: SchemaDiff[] = [];
|
const items: SchemaDiff[] = [];
|
||||||
if (source.nullable !== target.nullable) {
|
if (source.nullable !== target.nullable) {
|
||||||
items.push({
|
items.push({
|
||||||
type: 'column.alter',
|
type: 'ColumnAlter',
|
||||||
tableName: source.tableName,
|
tableName: source.tableName,
|
||||||
columnName: source.name,
|
columnName: source.name,
|
||||||
changes: {
|
changes: {
|
||||||
@ -43,7 +43,7 @@ export const compareColumns: Comparer<DatabaseColumn> = {
|
|||||||
|
|
||||||
if (!isDefaultEqual(source, target)) {
|
if (!isDefaultEqual(source, target)) {
|
||||||
items.push({
|
items.push({
|
||||||
type: 'column.alter',
|
type: 'ColumnAlter',
|
||||||
tableName: source.tableName,
|
tableName: source.tableName,
|
||||||
columnName: source.name,
|
columnName: source.name,
|
||||||
changes: {
|
changes: {
|
||||||
@ -55,7 +55,7 @@ export const compareColumns: Comparer<DatabaseColumn> = {
|
|||||||
|
|
||||||
if (source.comment !== target.comment) {
|
if (source.comment !== target.comment) {
|
||||||
items.push({
|
items.push({
|
||||||
type: 'column.alter',
|
type: 'ColumnAlter',
|
||||||
tableName: source.tableName,
|
tableName: source.tableName,
|
||||||
columnName: source.name,
|
columnName: source.name,
|
||||||
changes: {
|
changes: {
|
||||||
@ -72,11 +72,11 @@ export const compareColumns: Comparer<DatabaseColumn> = {
|
|||||||
const dropAndRecreateColumn = (source: DatabaseColumn, target: DatabaseColumn, reason: string): SchemaDiff[] => {
|
const dropAndRecreateColumn = (source: DatabaseColumn, target: DatabaseColumn, reason: string): SchemaDiff[] => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
type: 'column.drop',
|
type: 'ColumnDrop',
|
||||||
tableName: target.tableName,
|
tableName: target.tableName,
|
||||||
columnName: target.name,
|
columnName: target.name,
|
||||||
reason,
|
reason,
|
||||||
},
|
},
|
||||||
{ type: 'column.add', column: source, reason },
|
{ type: 'ColumnAdd', column: source, reason },
|
||||||
];
|
];
|
||||||
};
|
};
|
@ -1,9 +1,9 @@
|
|||||||
import { compareConstraints } from 'src/sql-tools/diff/comparers/constraint.comparer';
|
import { compareConstraints } from 'src/sql-tools/comparers/constraint.comparer';
|
||||||
import { DatabaseConstraint, DatabaseConstraintType, Reason } from 'src/sql-tools/types';
|
import { ConstraintType, DatabaseConstraint, Reason } from 'src/sql-tools/types';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
const testConstraint: DatabaseConstraint = {
|
const testConstraint: DatabaseConstraint = {
|
||||||
type: DatabaseConstraintType.PRIMARY_KEY,
|
type: ConstraintType.PRIMARY_KEY,
|
||||||
name: 'test',
|
name: 'test',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
columnNames: ['column1'],
|
columnNames: ['column1'],
|
||||||
@ -15,7 +15,7 @@ describe('compareConstraints', () => {
|
|||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
expect(compareConstraints.onExtra(testConstraint)).toEqual([
|
expect(compareConstraints.onExtra(testConstraint)).toEqual([
|
||||||
{
|
{
|
||||||
type: 'constraint.drop',
|
type: 'ConstraintDrop',
|
||||||
constraintName: 'test',
|
constraintName: 'test',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
reason: Reason.MissingInSource,
|
reason: Reason.MissingInSource,
|
||||||
@ -28,7 +28,7 @@ describe('compareConstraints', () => {
|
|||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
expect(compareConstraints.onMissing(testConstraint)).toEqual([
|
expect(compareConstraints.onMissing(testConstraint)).toEqual([
|
||||||
{
|
{
|
||||||
type: 'constraint.add',
|
type: 'ConstraintAdd',
|
||||||
constraint: testConstraint,
|
constraint: testConstraint,
|
||||||
reason: Reason.MissingInTarget,
|
reason: Reason.MissingInTarget,
|
||||||
},
|
},
|
||||||
@ -49,11 +49,11 @@ describe('compareConstraints', () => {
|
|||||||
{
|
{
|
||||||
constraintName: 'test',
|
constraintName: 'test',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
type: 'constraint.drop',
|
type: 'ConstraintDrop',
|
||||||
reason,
|
reason,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'constraint.add',
|
type: 'ConstraintAdd',
|
||||||
constraint: source,
|
constraint: source,
|
||||||
reason,
|
reason,
|
||||||
},
|
},
|
@ -2,9 +2,9 @@ import { haveEqualColumns } from 'src/sql-tools/helpers';
|
|||||||
import {
|
import {
|
||||||
CompareFunction,
|
CompareFunction,
|
||||||
Comparer,
|
Comparer,
|
||||||
|
ConstraintType,
|
||||||
DatabaseCheckConstraint,
|
DatabaseCheckConstraint,
|
||||||
DatabaseConstraint,
|
DatabaseConstraint,
|
||||||
DatabaseConstraintType,
|
|
||||||
DatabaseForeignKeyConstraint,
|
DatabaseForeignKeyConstraint,
|
||||||
DatabasePrimaryKeyConstraint,
|
DatabasePrimaryKeyConstraint,
|
||||||
DatabaseUniqueConstraint,
|
DatabaseUniqueConstraint,
|
||||||
@ -15,14 +15,14 @@ import {
|
|||||||
export const compareConstraints: Comparer<DatabaseConstraint> = {
|
export const compareConstraints: Comparer<DatabaseConstraint> = {
|
||||||
onMissing: (source) => [
|
onMissing: (source) => [
|
||||||
{
|
{
|
||||||
type: 'constraint.add',
|
type: 'ConstraintAdd',
|
||||||
constraint: source,
|
constraint: source,
|
||||||
reason: Reason.MissingInTarget,
|
reason: Reason.MissingInTarget,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
onExtra: (target) => [
|
onExtra: (target) => [
|
||||||
{
|
{
|
||||||
type: 'constraint.drop',
|
type: 'ConstraintDrop',
|
||||||
tableName: target.tableName,
|
tableName: target.tableName,
|
||||||
constraintName: target.name,
|
constraintName: target.name,
|
||||||
reason: Reason.MissingInSource,
|
reason: Reason.MissingInSource,
|
||||||
@ -30,19 +30,19 @@ export const compareConstraints: Comparer<DatabaseConstraint> = {
|
|||||||
],
|
],
|
||||||
onCompare: (source, target) => {
|
onCompare: (source, target) => {
|
||||||
switch (source.type) {
|
switch (source.type) {
|
||||||
case DatabaseConstraintType.PRIMARY_KEY: {
|
case ConstraintType.PRIMARY_KEY: {
|
||||||
return comparePrimaryKeyConstraint(source, target as DatabasePrimaryKeyConstraint);
|
return comparePrimaryKeyConstraint(source, target as DatabasePrimaryKeyConstraint);
|
||||||
}
|
}
|
||||||
|
|
||||||
case DatabaseConstraintType.FOREIGN_KEY: {
|
case ConstraintType.FOREIGN_KEY: {
|
||||||
return compareForeignKeyConstraint(source, target as DatabaseForeignKeyConstraint);
|
return compareForeignKeyConstraint(source, target as DatabaseForeignKeyConstraint);
|
||||||
}
|
}
|
||||||
|
|
||||||
case DatabaseConstraintType.UNIQUE: {
|
case ConstraintType.UNIQUE: {
|
||||||
return compareUniqueConstraint(source, target as DatabaseUniqueConstraint);
|
return compareUniqueConstraint(source, target as DatabaseUniqueConstraint);
|
||||||
}
|
}
|
||||||
|
|
||||||
case DatabaseConstraintType.CHECK: {
|
case ConstraintType.CHECK: {
|
||||||
return compareCheckConstraint(source, target as DatabaseCheckConstraint);
|
return compareCheckConstraint(source, target as DatabaseCheckConstraint);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,11 +123,11 @@ const dropAndRecreateConstraint = (
|
|||||||
): SchemaDiff[] => {
|
): SchemaDiff[] => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
type: 'constraint.drop',
|
type: 'ConstraintDrop',
|
||||||
tableName: target.tableName,
|
tableName: target.tableName,
|
||||||
constraintName: target.name,
|
constraintName: target.name,
|
||||||
reason,
|
reason,
|
||||||
},
|
},
|
||||||
{ type: 'constraint.add', constraint: source, reason },
|
{ type: 'ConstraintAdd', constraint: source, reason },
|
||||||
];
|
];
|
||||||
};
|
};
|
@ -1,4 +1,4 @@
|
|||||||
import { compareEnums } from 'src/sql-tools/diff/comparers/enum.comparer';
|
import { compareEnums } from 'src/sql-tools/comparers/enum.comparer';
|
||||||
import { DatabaseEnum, Reason } from 'src/sql-tools/types';
|
import { DatabaseEnum, Reason } from 'src/sql-tools/types';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ describe('compareEnums', () => {
|
|||||||
expect(compareEnums.onExtra(testEnum)).toEqual([
|
expect(compareEnums.onExtra(testEnum)).toEqual([
|
||||||
{
|
{
|
||||||
enumName: 'test',
|
enumName: 'test',
|
||||||
type: 'enum.drop',
|
type: 'EnumDrop',
|
||||||
reason: Reason.MissingInSource,
|
reason: Reason.MissingInSource,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
@ -21,7 +21,7 @@ describe('compareEnums', () => {
|
|||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
expect(compareEnums.onMissing(testEnum)).toEqual([
|
expect(compareEnums.onMissing(testEnum)).toEqual([
|
||||||
{
|
{
|
||||||
type: 'enum.create',
|
type: 'EnumCreate',
|
||||||
enum: testEnum,
|
enum: testEnum,
|
||||||
reason: Reason.MissingInTarget,
|
reason: Reason.MissingInTarget,
|
||||||
},
|
},
|
||||||
@ -40,11 +40,11 @@ describe('compareEnums', () => {
|
|||||||
expect(compareEnums.onCompare(source, target)).toEqual([
|
expect(compareEnums.onCompare(source, target)).toEqual([
|
||||||
{
|
{
|
||||||
enumName: 'test',
|
enumName: 'test',
|
||||||
type: 'enum.drop',
|
type: 'EnumDrop',
|
||||||
reason: 'enum values has changed (foo,bar vs foo,bar,world)',
|
reason: 'enum values has changed (foo,bar vs foo,bar,world)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'enum.create',
|
type: 'EnumCreate',
|
||||||
enum: source,
|
enum: source,
|
||||||
reason: 'enum values has changed (foo,bar vs foo,bar,world)',
|
reason: 'enum values has changed (foo,bar vs foo,bar,world)',
|
||||||
},
|
},
|
@ -3,14 +3,14 @@ import { Comparer, DatabaseEnum, Reason } from 'src/sql-tools/types';
|
|||||||
export const compareEnums: Comparer<DatabaseEnum> = {
|
export const compareEnums: Comparer<DatabaseEnum> = {
|
||||||
onMissing: (source) => [
|
onMissing: (source) => [
|
||||||
{
|
{
|
||||||
type: 'enum.create',
|
type: 'EnumCreate',
|
||||||
enum: source,
|
enum: source,
|
||||||
reason: Reason.MissingInTarget,
|
reason: Reason.MissingInTarget,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
onExtra: (target) => [
|
onExtra: (target) => [
|
||||||
{
|
{
|
||||||
type: 'enum.drop',
|
type: 'EnumDrop',
|
||||||
enumName: target.name,
|
enumName: target.name,
|
||||||
reason: Reason.MissingInSource,
|
reason: Reason.MissingInSource,
|
||||||
},
|
},
|
||||||
@ -21,12 +21,12 @@ export const compareEnums: Comparer<DatabaseEnum> = {
|
|||||||
const reason = `enum values has changed (${source.values} vs ${target.values})`;
|
const reason = `enum values has changed (${source.values} vs ${target.values})`;
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
type: 'enum.drop',
|
type: 'EnumDrop',
|
||||||
enumName: source.name,
|
enumName: source.name,
|
||||||
reason,
|
reason,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'enum.create',
|
type: 'EnumCreate',
|
||||||
enum: source,
|
enum: source,
|
||||||
reason,
|
reason,
|
||||||
},
|
},
|
@ -1,4 +1,4 @@
|
|||||||
import { compareExtensions } from 'src/sql-tools/diff/comparers/extension.comparer';
|
import { compareExtensions } from 'src/sql-tools/comparers/extension.comparer';
|
||||||
import { Reason } from 'src/sql-tools/types';
|
import { Reason } from 'src/sql-tools/types';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ describe('compareExtensions', () => {
|
|||||||
expect(compareExtensions.onExtra(testExtension)).toEqual([
|
expect(compareExtensions.onExtra(testExtension)).toEqual([
|
||||||
{
|
{
|
||||||
extensionName: 'test',
|
extensionName: 'test',
|
||||||
type: 'extension.drop',
|
type: 'ExtensionDrop',
|
||||||
reason: Reason.MissingInSource,
|
reason: Reason.MissingInSource,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
@ -21,7 +21,7 @@ describe('compareExtensions', () => {
|
|||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
expect(compareExtensions.onMissing(testExtension)).toEqual([
|
expect(compareExtensions.onMissing(testExtension)).toEqual([
|
||||||
{
|
{
|
||||||
type: 'extension.create',
|
type: 'ExtensionCreate',
|
||||||
extension: testExtension,
|
extension: testExtension,
|
||||||
reason: Reason.MissingInTarget,
|
reason: Reason.MissingInTarget,
|
||||||
},
|
},
|
@ -3,14 +3,14 @@ import { Comparer, DatabaseExtension, Reason } from 'src/sql-tools/types';
|
|||||||
export const compareExtensions: Comparer<DatabaseExtension> = {
|
export const compareExtensions: Comparer<DatabaseExtension> = {
|
||||||
onMissing: (source) => [
|
onMissing: (source) => [
|
||||||
{
|
{
|
||||||
type: 'extension.create',
|
type: 'ExtensionCreate',
|
||||||
extension: source,
|
extension: source,
|
||||||
reason: Reason.MissingInTarget,
|
reason: Reason.MissingInTarget,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
onExtra: (target) => [
|
onExtra: (target) => [
|
||||||
{
|
{
|
||||||
type: 'extension.drop',
|
type: 'ExtensionDrop',
|
||||||
extensionName: target.name,
|
extensionName: target.name,
|
||||||
reason: Reason.MissingInSource,
|
reason: Reason.MissingInSource,
|
||||||
},
|
},
|
@ -1,4 +1,4 @@
|
|||||||
import { compareFunctions } from 'src/sql-tools/diff/comparers/function.comparer';
|
import { compareFunctions } from 'src/sql-tools/comparers/function.comparer';
|
||||||
import { DatabaseFunction, Reason } from 'src/sql-tools/types';
|
import { DatabaseFunction, Reason } from 'src/sql-tools/types';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ describe('compareFunctions', () => {
|
|||||||
expect(compareFunctions.onExtra(testFunction)).toEqual([
|
expect(compareFunctions.onExtra(testFunction)).toEqual([
|
||||||
{
|
{
|
||||||
functionName: 'test',
|
functionName: 'test',
|
||||||
type: 'function.drop',
|
type: 'FunctionDrop',
|
||||||
reason: Reason.MissingInSource,
|
reason: Reason.MissingInSource,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
@ -25,7 +25,7 @@ describe('compareFunctions', () => {
|
|||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
expect(compareFunctions.onMissing(testFunction)).toEqual([
|
expect(compareFunctions.onMissing(testFunction)).toEqual([
|
||||||
{
|
{
|
||||||
type: 'function.create',
|
type: 'FunctionCreate',
|
||||||
function: testFunction,
|
function: testFunction,
|
||||||
reason: Reason.MissingInTarget,
|
reason: Reason.MissingInTarget,
|
||||||
},
|
},
|
||||||
@ -43,7 +43,7 @@ describe('compareFunctions', () => {
|
|||||||
const target: DatabaseFunction = { ...testFunction, expression: 'SELECT 2' };
|
const target: DatabaseFunction = { ...testFunction, expression: 'SELECT 2' };
|
||||||
expect(compareFunctions.onCompare(source, target)).toEqual([
|
expect(compareFunctions.onCompare(source, target)).toEqual([
|
||||||
{
|
{
|
||||||
type: 'function.create',
|
type: 'FunctionCreate',
|
||||||
reason: 'function expression has changed (SELECT 1 vs SELECT 2)',
|
reason: 'function expression has changed (SELECT 1 vs SELECT 2)',
|
||||||
function: source,
|
function: source,
|
||||||
},
|
},
|
@ -3,14 +3,14 @@ import { Comparer, DatabaseFunction, Reason } from 'src/sql-tools/types';
|
|||||||
export const compareFunctions: Comparer<DatabaseFunction> = {
|
export const compareFunctions: Comparer<DatabaseFunction> = {
|
||||||
onMissing: (source) => [
|
onMissing: (source) => [
|
||||||
{
|
{
|
||||||
type: 'function.create',
|
type: 'FunctionCreate',
|
||||||
function: source,
|
function: source,
|
||||||
reason: Reason.MissingInTarget,
|
reason: Reason.MissingInTarget,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
onExtra: (target) => [
|
onExtra: (target) => [
|
||||||
{
|
{
|
||||||
type: 'function.drop',
|
type: 'FunctionDrop',
|
||||||
functionName: target.name,
|
functionName: target.name,
|
||||||
reason: Reason.MissingInSource,
|
reason: Reason.MissingInSource,
|
||||||
},
|
},
|
||||||
@ -20,7 +20,7 @@ export const compareFunctions: Comparer<DatabaseFunction> = {
|
|||||||
const reason = `function expression has changed (${source.expression} vs ${target.expression})`;
|
const reason = `function expression has changed (${source.expression} vs ${target.expression})`;
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
type: 'function.create',
|
type: 'FunctionCreate',
|
||||||
function: source,
|
function: source,
|
||||||
reason,
|
reason,
|
||||||
},
|
},
|
@ -1,4 +1,4 @@
|
|||||||
import { compareIndexes } from 'src/sql-tools/diff/comparers/index.comparer';
|
import { compareIndexes } from 'src/sql-tools/comparers/index.comparer';
|
||||||
import { DatabaseIndex, Reason } from 'src/sql-tools/types';
|
import { DatabaseIndex, Reason } from 'src/sql-tools/types';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ describe('compareIndexes', () => {
|
|||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
expect(compareIndexes.onExtra(testIndex)).toEqual([
|
expect(compareIndexes.onExtra(testIndex)).toEqual([
|
||||||
{
|
{
|
||||||
type: 'index.drop',
|
type: 'IndexDrop',
|
||||||
indexName: 'test',
|
indexName: 'test',
|
||||||
reason: Reason.MissingInSource,
|
reason: Reason.MissingInSource,
|
||||||
},
|
},
|
||||||
@ -27,7 +27,7 @@ describe('compareIndexes', () => {
|
|||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
expect(compareIndexes.onMissing(testIndex)).toEqual([
|
expect(compareIndexes.onMissing(testIndex)).toEqual([
|
||||||
{
|
{
|
||||||
type: 'index.create',
|
type: 'IndexCreate',
|
||||||
index: testIndex,
|
index: testIndex,
|
||||||
reason: Reason.MissingInTarget,
|
reason: Reason.MissingInTarget,
|
||||||
},
|
},
|
||||||
@ -58,11 +58,11 @@ describe('compareIndexes', () => {
|
|||||||
expect(compareIndexes.onCompare(source, target)).toEqual([
|
expect(compareIndexes.onCompare(source, target)).toEqual([
|
||||||
{
|
{
|
||||||
indexName: 'test',
|
indexName: 'test',
|
||||||
type: 'index.drop',
|
type: 'IndexDrop',
|
||||||
reason: 'columns are different (column1 vs column1,column2)',
|
reason: 'columns are different (column1 vs column1,column2)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'index.create',
|
type: 'IndexCreate',
|
||||||
index: source,
|
index: source,
|
||||||
reason: 'columns are different (column1 vs column1,column2)',
|
reason: 'columns are different (column1 vs column1,column2)',
|
||||||
},
|
},
|
@ -4,14 +4,14 @@ import { Comparer, DatabaseIndex, Reason } from 'src/sql-tools/types';
|
|||||||
export const compareIndexes: Comparer<DatabaseIndex> = {
|
export const compareIndexes: Comparer<DatabaseIndex> = {
|
||||||
onMissing: (source) => [
|
onMissing: (source) => [
|
||||||
{
|
{
|
||||||
type: 'index.create',
|
type: 'IndexCreate',
|
||||||
index: source,
|
index: source,
|
||||||
reason: Reason.MissingInTarget,
|
reason: Reason.MissingInTarget,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
onExtra: (target) => [
|
onExtra: (target) => [
|
||||||
{
|
{
|
||||||
type: 'index.drop',
|
type: 'IndexDrop',
|
||||||
indexName: target.name,
|
indexName: target.name,
|
||||||
reason: Reason.MissingInSource,
|
reason: Reason.MissingInSource,
|
||||||
},
|
},
|
||||||
@ -36,8 +36,8 @@ export const compareIndexes: Comparer<DatabaseIndex> = {
|
|||||||
|
|
||||||
if (reason) {
|
if (reason) {
|
||||||
return [
|
return [
|
||||||
{ type: 'index.drop', indexName: target.name, reason },
|
{ type: 'IndexDrop', indexName: target.name, reason },
|
||||||
{ type: 'index.create', index: source, reason },
|
{ type: 'IndexCreate', index: source, reason },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
import { compareParameters } from 'src/sql-tools/diff/comparers/parameter.comparer';
|
import { compareParameters } from 'src/sql-tools/comparers/parameter.comparer';
|
||||||
import { DatabaseParameter, Reason } from 'src/sql-tools/types';
|
import { DatabaseParameter, Reason } from 'src/sql-tools/types';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ describe('compareParameters', () => {
|
|||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
expect(compareParameters.onExtra(testParameter)).toEqual([
|
expect(compareParameters.onExtra(testParameter)).toEqual([
|
||||||
{
|
{
|
||||||
type: 'parameter.reset',
|
type: 'ParameterReset',
|
||||||
databaseName: 'immich',
|
databaseName: 'immich',
|
||||||
parameterName: 'test',
|
parameterName: 'test',
|
||||||
reason: Reason.MissingInSource,
|
reason: Reason.MissingInSource,
|
||||||
@ -28,7 +28,7 @@ describe('compareParameters', () => {
|
|||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
expect(compareParameters.onMissing(testParameter)).toEqual([
|
expect(compareParameters.onMissing(testParameter)).toEqual([
|
||||||
{
|
{
|
||||||
type: 'parameter.set',
|
type: 'ParameterSet',
|
||||||
parameter: testParameter,
|
parameter: testParameter,
|
||||||
reason: Reason.MissingInTarget,
|
reason: Reason.MissingInTarget,
|
||||||
},
|
},
|
@ -3,14 +3,14 @@ import { Comparer, DatabaseParameter, Reason } from 'src/sql-tools/types';
|
|||||||
export const compareParameters: Comparer<DatabaseParameter> = {
|
export const compareParameters: Comparer<DatabaseParameter> = {
|
||||||
onMissing: (source) => [
|
onMissing: (source) => [
|
||||||
{
|
{
|
||||||
type: 'parameter.set',
|
type: 'ParameterSet',
|
||||||
parameter: source,
|
parameter: source,
|
||||||
reason: Reason.MissingInTarget,
|
reason: Reason.MissingInTarget,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
onExtra: (target) => [
|
onExtra: (target) => [
|
||||||
{
|
{
|
||||||
type: 'parameter.reset',
|
type: 'ParameterReset',
|
||||||
databaseName: target.databaseName,
|
databaseName: target.databaseName,
|
||||||
parameterName: target.name,
|
parameterName: target.name,
|
||||||
reason: Reason.MissingInSource,
|
reason: Reason.MissingInSource,
|
@ -1,4 +1,4 @@
|
|||||||
import { compareTables } from 'src/sql-tools/diff/comparers/table.comparer';
|
import { compareTables } from 'src/sql-tools/comparers/table.comparer';
|
||||||
import { DatabaseTable, Reason } from 'src/sql-tools/types';
|
import { DatabaseTable, Reason } from 'src/sql-tools/types';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ describe('compareParameters', () => {
|
|||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
expect(compareTables.onExtra(testTable)).toEqual([
|
expect(compareTables.onExtra(testTable)).toEqual([
|
||||||
{
|
{
|
||||||
type: 'table.drop',
|
type: 'TableDrop',
|
||||||
tableName: 'test',
|
tableName: 'test',
|
||||||
reason: Reason.MissingInSource,
|
reason: Reason.MissingInSource,
|
||||||
},
|
},
|
||||||
@ -28,7 +28,7 @@ describe('compareParameters', () => {
|
|||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
expect(compareTables.onMissing(testTable)).toEqual([
|
expect(compareTables.onMissing(testTable)).toEqual([
|
||||||
{
|
{
|
||||||
type: 'table.create',
|
type: 'TableCreate',
|
||||||
table: testTable,
|
table: testTable,
|
||||||
reason: Reason.MissingInTarget,
|
reason: Reason.MissingInTarget,
|
||||||
},
|
},
|
@ -1,47 +1,33 @@
|
|||||||
import { compareColumns } from 'src/sql-tools/diff/comparers/column.comparer';
|
import { compareColumns } from 'src/sql-tools/comparers/column.comparer';
|
||||||
import { compareConstraints } from 'src/sql-tools/diff/comparers/constraint.comparer';
|
import { compareConstraints } from 'src/sql-tools/comparers/constraint.comparer';
|
||||||
import { compareIndexes } from 'src/sql-tools/diff/comparers/index.comparer';
|
import { compareIndexes } from 'src/sql-tools/comparers/index.comparer';
|
||||||
import { compareTriggers } from 'src/sql-tools/diff/comparers/trigger.comparer';
|
import { compareTriggers } from 'src/sql-tools/comparers/trigger.comparer';
|
||||||
import { compare } from 'src/sql-tools/helpers';
|
import { compare } from 'src/sql-tools/helpers';
|
||||||
import { Comparer, DatabaseTable, Reason, SchemaDiff } from 'src/sql-tools/types';
|
import { Comparer, DatabaseTable, Reason, SchemaDiff } from 'src/sql-tools/types';
|
||||||
|
|
||||||
|
const newTable = (name: string) => ({
|
||||||
|
name,
|
||||||
|
columns: [],
|
||||||
|
indexes: [],
|
||||||
|
constraints: [],
|
||||||
|
triggers: [],
|
||||||
|
synchronize: true,
|
||||||
|
});
|
||||||
|
|
||||||
export const compareTables: Comparer<DatabaseTable> = {
|
export const compareTables: Comparer<DatabaseTable> = {
|
||||||
onMissing: (source) => [
|
onMissing: (source) => [
|
||||||
{
|
{
|
||||||
type: 'table.create',
|
type: 'TableCreate',
|
||||||
table: source,
|
table: source,
|
||||||
reason: Reason.MissingInTarget,
|
reason: Reason.MissingInTarget,
|
||||||
},
|
},
|
||||||
// TODO merge constraints into table create record when possible
|
// TODO merge constraints into table create record when possible
|
||||||
...compareTable(
|
...compareTable(source, newTable(source.name), { columns: false }),
|
||||||
source,
|
|
||||||
{
|
|
||||||
name: source.name,
|
|
||||||
columns: [],
|
|
||||||
indexes: [],
|
|
||||||
constraints: [],
|
|
||||||
triggers: [],
|
|
||||||
synchronize: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
{ columns: false },
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
onExtra: (target) => [
|
onExtra: (target) => [
|
||||||
...compareTable(
|
...compareTable(newTable(target.name), target, { columns: false }),
|
||||||
{
|
{
|
||||||
name: target.name,
|
type: 'TableDrop',
|
||||||
columns: [],
|
|
||||||
indexes: [],
|
|
||||||
constraints: [],
|
|
||||||
triggers: [],
|
|
||||||
synchronize: true,
|
|
||||||
},
|
|
||||||
target,
|
|
||||||
{ columns: false },
|
|
||||||
),
|
|
||||||
{
|
|
||||||
type: 'table.drop',
|
|
||||||
tableName: target.name,
|
tableName: target.name,
|
||||||
reason: Reason.MissingInSource,
|
reason: Reason.MissingInSource,
|
||||||
},
|
},
|
@ -1,4 +1,4 @@
|
|||||||
import { compareTriggers } from 'src/sql-tools/diff/comparers/trigger.comparer';
|
import { compareTriggers } from 'src/sql-tools/comparers/trigger.comparer';
|
||||||
import { DatabaseTrigger, Reason } from 'src/sql-tools/types';
|
import { DatabaseTrigger, Reason } from 'src/sql-tools/types';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ describe('compareTriggers', () => {
|
|||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
expect(compareTriggers.onExtra(testTrigger)).toEqual([
|
expect(compareTriggers.onExtra(testTrigger)).toEqual([
|
||||||
{
|
{
|
||||||
type: 'trigger.drop',
|
type: 'TriggerDrop',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
triggerName: 'test',
|
triggerName: 'test',
|
||||||
reason: Reason.MissingInSource,
|
reason: Reason.MissingInSource,
|
||||||
@ -30,7 +30,7 @@ describe('compareTriggers', () => {
|
|||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
expect(compareTriggers.onMissing(testTrigger)).toEqual([
|
expect(compareTriggers.onMissing(testTrigger)).toEqual([
|
||||||
{
|
{
|
||||||
type: 'trigger.create',
|
type: 'TriggerCreate',
|
||||||
trigger: testTrigger,
|
trigger: testTrigger,
|
||||||
reason: Reason.MissingInTarget,
|
reason: Reason.MissingInTarget,
|
||||||
},
|
},
|
||||||
@ -47,42 +47,42 @@ describe('compareTriggers', () => {
|
|||||||
const source: DatabaseTrigger = { ...testTrigger, functionName: 'my_new_name' };
|
const source: DatabaseTrigger = { ...testTrigger, functionName: 'my_new_name' };
|
||||||
const target: DatabaseTrigger = { ...testTrigger, functionName: 'my_old_name' };
|
const target: DatabaseTrigger = { ...testTrigger, functionName: 'my_old_name' };
|
||||||
const reason = `function is different (my_new_name vs my_old_name)`;
|
const reason = `function is different (my_new_name vs my_old_name)`;
|
||||||
expect(compareTriggers.onCompare(source, target)).toEqual([{ type: 'trigger.create', trigger: source, reason }]);
|
expect(compareTriggers.onCompare(source, target)).toEqual([{ type: 'TriggerCreate', trigger: source, reason }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should detect a change in actions', () => {
|
it('should detect a change in actions', () => {
|
||||||
const source: DatabaseTrigger = { ...testTrigger, actions: ['delete'] };
|
const source: DatabaseTrigger = { ...testTrigger, actions: ['delete'] };
|
||||||
const target: DatabaseTrigger = { ...testTrigger, actions: ['delete', 'insert'] };
|
const target: DatabaseTrigger = { ...testTrigger, actions: ['delete', 'insert'] };
|
||||||
const reason = `action is different (delete vs delete,insert)`;
|
const reason = `action is different (delete vs delete,insert)`;
|
||||||
expect(compareTriggers.onCompare(source, target)).toEqual([{ type: 'trigger.create', trigger: source, reason }]);
|
expect(compareTriggers.onCompare(source, target)).toEqual([{ type: 'TriggerCreate', trigger: source, reason }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should detect a change in timing', () => {
|
it('should detect a change in timing', () => {
|
||||||
const source: DatabaseTrigger = { ...testTrigger, timing: 'before' };
|
const source: DatabaseTrigger = { ...testTrigger, timing: 'before' };
|
||||||
const target: DatabaseTrigger = { ...testTrigger, timing: 'after' };
|
const target: DatabaseTrigger = { ...testTrigger, timing: 'after' };
|
||||||
const reason = `timing method is different (before vs after)`;
|
const reason = `timing method is different (before vs after)`;
|
||||||
expect(compareTriggers.onCompare(source, target)).toEqual([{ type: 'trigger.create', trigger: source, reason }]);
|
expect(compareTriggers.onCompare(source, target)).toEqual([{ type: 'TriggerCreate', trigger: source, reason }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should detect a change in scope', () => {
|
it('should detect a change in scope', () => {
|
||||||
const source: DatabaseTrigger = { ...testTrigger, scope: 'row' };
|
const source: DatabaseTrigger = { ...testTrigger, scope: 'row' };
|
||||||
const target: DatabaseTrigger = { ...testTrigger, scope: 'statement' };
|
const target: DatabaseTrigger = { ...testTrigger, scope: 'statement' };
|
||||||
const reason = `scope is different (row vs statement)`;
|
const reason = `scope is different (row vs statement)`;
|
||||||
expect(compareTriggers.onCompare(source, target)).toEqual([{ type: 'trigger.create', trigger: source, reason }]);
|
expect(compareTriggers.onCompare(source, target)).toEqual([{ type: 'TriggerCreate', trigger: source, reason }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should detect a change in new table reference', () => {
|
it('should detect a change in new table reference', () => {
|
||||||
const source: DatabaseTrigger = { ...testTrigger, referencingNewTableAs: 'new_table' };
|
const source: DatabaseTrigger = { ...testTrigger, referencingNewTableAs: 'new_table' };
|
||||||
const target: DatabaseTrigger = { ...testTrigger, referencingNewTableAs: undefined };
|
const target: DatabaseTrigger = { ...testTrigger, referencingNewTableAs: undefined };
|
||||||
const reason = `new table reference is different (new_table vs undefined)`;
|
const reason = `new table reference is different (new_table vs undefined)`;
|
||||||
expect(compareTriggers.onCompare(source, target)).toEqual([{ type: 'trigger.create', trigger: source, reason }]);
|
expect(compareTriggers.onCompare(source, target)).toEqual([{ type: 'TriggerCreate', trigger: source, reason }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should detect a change in old table reference', () => {
|
it('should detect a change in old table reference', () => {
|
||||||
const source: DatabaseTrigger = { ...testTrigger, referencingOldTableAs: 'old_table' };
|
const source: DatabaseTrigger = { ...testTrigger, referencingOldTableAs: 'old_table' };
|
||||||
const target: DatabaseTrigger = { ...testTrigger, referencingOldTableAs: undefined };
|
const target: DatabaseTrigger = { ...testTrigger, referencingOldTableAs: undefined };
|
||||||
const reason = `old table reference is different (old_table vs undefined)`;
|
const reason = `old table reference is different (old_table vs undefined)`;
|
||||||
expect(compareTriggers.onCompare(source, target)).toEqual([{ type: 'trigger.create', trigger: source, reason }]);
|
expect(compareTriggers.onCompare(source, target)).toEqual([{ type: 'TriggerCreate', trigger: source, reason }]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -3,14 +3,14 @@ import { Comparer, DatabaseTrigger, Reason } from 'src/sql-tools/types';
|
|||||||
export const compareTriggers: Comparer<DatabaseTrigger> = {
|
export const compareTriggers: Comparer<DatabaseTrigger> = {
|
||||||
onMissing: (source) => [
|
onMissing: (source) => [
|
||||||
{
|
{
|
||||||
type: 'trigger.create',
|
type: 'TriggerCreate',
|
||||||
trigger: source,
|
trigger: source,
|
||||||
reason: Reason.MissingInTarget,
|
reason: Reason.MissingInTarget,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
onExtra: (target) => [
|
onExtra: (target) => [
|
||||||
{
|
{
|
||||||
type: 'trigger.drop',
|
type: 'TriggerDrop',
|
||||||
tableName: target.tableName,
|
tableName: target.tableName,
|
||||||
triggerName: target.name,
|
triggerName: target.name,
|
||||||
reason: Reason.MissingInSource,
|
reason: Reason.MissingInSource,
|
||||||
@ -33,7 +33,7 @@ export const compareTriggers: Comparer<DatabaseTrigger> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (reason) {
|
if (reason) {
|
||||||
return [{ type: 'trigger.create', trigger: source, reason }];
|
return [{ type: 'TriggerCreate', trigger: source, reason }];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
@ -1,4 +1,4 @@
|
|||||||
import { TriggerFunction, TriggerFunctionOptions } from 'src/sql-tools/from-code/decorators/trigger-function.decorator';
|
import { TriggerFunction, TriggerFunctionOptions } from 'src/sql-tools/decorators/trigger-function.decorator';
|
||||||
|
|
||||||
export const AfterDeleteTrigger = (options: Omit<TriggerFunctionOptions, 'timing' | 'actions'>) =>
|
export const AfterDeleteTrigger = (options: Omit<TriggerFunctionOptions, 'timing' | 'actions'>) =>
|
||||||
TriggerFunction({
|
TriggerFunction({
|
@ -1,4 +1,4 @@
|
|||||||
import { TriggerFunction, TriggerFunctionOptions } from 'src/sql-tools/from-code/decorators/trigger-function.decorator';
|
import { TriggerFunction, TriggerFunctionOptions } from 'src/sql-tools/decorators/trigger-function.decorator';
|
||||||
|
|
||||||
export const AfterInsertTrigger = (options: Omit<TriggerFunctionOptions, 'timing' | 'actions'>) =>
|
export const AfterInsertTrigger = (options: Omit<TriggerFunctionOptions, 'timing' | 'actions'>) =>
|
||||||
TriggerFunction({
|
TriggerFunction({
|
@ -1,4 +1,4 @@
|
|||||||
import { TriggerFunction, TriggerFunctionOptions } from 'src/sql-tools/from-code/decorators/trigger-function.decorator';
|
import { TriggerFunction, TriggerFunctionOptions } from 'src/sql-tools/decorators/trigger-function.decorator';
|
||||||
|
|
||||||
export const BeforeUpdateTrigger = (options: Omit<TriggerFunctionOptions, 'timing' | 'actions'>) =>
|
export const BeforeUpdateTrigger = (options: Omit<TriggerFunctionOptions, 'timing' | 'actions'>) =>
|
||||||
TriggerFunction({
|
TriggerFunction({
|
@ -1,4 +1,4 @@
|
|||||||
import { register } from 'src/sql-tools/from-code/register';
|
import { register } from 'src/sql-tools/register';
|
||||||
|
|
||||||
export type CheckOptions = {
|
export type CheckOptions = {
|
||||||
name?: string;
|
name?: string;
|
@ -1,5 +1,5 @@
|
|||||||
import { register } from 'src/sql-tools/from-code/register';
|
|
||||||
import { asOptions } from 'src/sql-tools/helpers';
|
import { asOptions } from 'src/sql-tools/helpers';
|
||||||
|
import { register } from 'src/sql-tools/register';
|
||||||
import { ColumnStorage, ColumnType, DatabaseEnum } from 'src/sql-tools/types';
|
import { ColumnStorage, ColumnType, DatabaseEnum } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export type ColumnValue = null | boolean | string | number | object | Date | (() => string);
|
export type ColumnValue = null | boolean | string | number | object | Date | (() => string);
|
@ -1,5 +1,5 @@
|
|||||||
import { ColumnValue } from 'src/sql-tools/from-code/decorators/column.decorator';
|
import { ColumnValue } from 'src/sql-tools/decorators/column.decorator';
|
||||||
import { register } from 'src/sql-tools/from-code/register';
|
import { register } from 'src/sql-tools/register';
|
||||||
import { ParameterScope } from 'src/sql-tools/types';
|
import { ParameterScope } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export type ConfigurationParameterOptions = {
|
export type ConfigurationParameterOptions = {
|
@ -1,4 +1,4 @@
|
|||||||
import { Column, ColumnOptions } from 'src/sql-tools/from-code/decorators/column.decorator';
|
import { Column, ColumnOptions } from 'src/sql-tools/decorators/column.decorator';
|
||||||
|
|
||||||
export const CreateDateColumn = (options: ColumnOptions = {}): PropertyDecorator => {
|
export const CreateDateColumn = (options: ColumnOptions = {}): PropertyDecorator => {
|
||||||
return Column({
|
return Column({
|
@ -1,4 +1,4 @@
|
|||||||
import { register } from 'src/sql-tools/from-code/register';
|
import { register } from 'src/sql-tools/register';
|
||||||
|
|
||||||
export type DatabaseOptions = {
|
export type DatabaseOptions = {
|
||||||
name?: string;
|
name?: string;
|
@ -1,4 +1,4 @@
|
|||||||
import { Column, ColumnOptions } from 'src/sql-tools/from-code/decorators/column.decorator';
|
import { Column, ColumnOptions } from 'src/sql-tools/decorators/column.decorator';
|
||||||
|
|
||||||
export const DeleteDateColumn = (options: ColumnOptions = {}): PropertyDecorator => {
|
export const DeleteDateColumn = (options: ColumnOptions = {}): PropertyDecorator => {
|
||||||
return Column({
|
return Column({
|
@ -1,5 +1,5 @@
|
|||||||
import { register } from 'src/sql-tools/from-code/register';
|
|
||||||
import { asOptions } from 'src/sql-tools/helpers';
|
import { asOptions } from 'src/sql-tools/helpers';
|
||||||
|
import { register } from 'src/sql-tools/register';
|
||||||
|
|
||||||
export type ExtensionOptions = {
|
export type ExtensionOptions = {
|
||||||
name: string;
|
name: string;
|
@ -1,5 +1,5 @@
|
|||||||
import { register } from 'src/sql-tools/from-code/register';
|
|
||||||
import { asOptions } from 'src/sql-tools/helpers';
|
import { asOptions } from 'src/sql-tools/helpers';
|
||||||
|
import { register } from 'src/sql-tools/register';
|
||||||
|
|
||||||
export type ExtensionsOptions = {
|
export type ExtensionsOptions = {
|
||||||
name: string;
|
name: string;
|
@ -0,0 +1,16 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-function-type */
|
||||||
|
import { ForeignKeyAction } from 'src/sql-tools//decorators/foreign-key-constraint.decorator';
|
||||||
|
import { ColumnBaseOptions } from 'src/sql-tools/decorators/column.decorator';
|
||||||
|
import { register } from 'src/sql-tools/register';
|
||||||
|
|
||||||
|
export type ForeignKeyColumnOptions = ColumnBaseOptions & {
|
||||||
|
onUpdate?: ForeignKeyAction;
|
||||||
|
onDelete?: ForeignKeyAction;
|
||||||
|
constraintName?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ForeignKeyColumn = (target: () => Function, options: ForeignKeyColumnOptions): PropertyDecorator => {
|
||||||
|
return (object: object, propertyName: string | symbol) => {
|
||||||
|
register({ type: 'foreignKeyColumn', item: { object, propertyName, options, target } });
|
||||||
|
};
|
||||||
|
};
|
@ -1,4 +1,4 @@
|
|||||||
import { register } from 'src/sql-tools/from-code/register';
|
import { register } from 'src/sql-tools/register';
|
||||||
|
|
||||||
export type ForeignKeyAction = 'CASCADE' | 'SET NULL' | 'SET DEFAULT' | 'RESTRICT' | 'NO ACTION';
|
export type ForeignKeyAction = 'CASCADE' | 'SET NULL' | 'SET DEFAULT' | 'RESTRICT' | 'NO ACTION';
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
import { Column, ColumnOptions, ColumnValue } from 'src/sql-tools/from-code/decorators/column.decorator';
|
import { Column, ColumnOptions, ColumnValue } from 'src/sql-tools/decorators/column.decorator';
|
||||||
import { ColumnType } from 'src/sql-tools/types';
|
import { ColumnType } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export type GeneratedColumnStrategy = 'uuid' | 'identity';
|
export type GeneratedColumnStrategy = 'uuid' | 'identity';
|
@ -1,5 +1,5 @@
|
|||||||
import { register } from 'src/sql-tools/from-code/register';
|
|
||||||
import { asOptions } from 'src/sql-tools/helpers';
|
import { asOptions } from 'src/sql-tools/helpers';
|
||||||
|
import { register } from 'src/sql-tools/register';
|
||||||
|
|
||||||
export type IndexOptions = {
|
export type IndexOptions = {
|
||||||
name?: string;
|
name?: string;
|
22
server/src/sql-tools/decorators/index.ts
Normal file
22
server/src/sql-tools/decorators/index.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
export * from 'src/sql-tools/decorators/after-delete.decorator';
|
||||||
|
export * from 'src/sql-tools/decorators/after-insert.decorator';
|
||||||
|
export * from 'src/sql-tools/decorators/before-update.decorator';
|
||||||
|
export * from 'src/sql-tools/decorators/check.decorator';
|
||||||
|
export * from 'src/sql-tools/decorators/column.decorator';
|
||||||
|
export * from 'src/sql-tools/decorators/configuration-parameter.decorator';
|
||||||
|
export * from 'src/sql-tools/decorators/create-date-column.decorator';
|
||||||
|
export * from 'src/sql-tools/decorators/database.decorator';
|
||||||
|
export * from 'src/sql-tools/decorators/delete-date-column.decorator';
|
||||||
|
export * from 'src/sql-tools/decorators/extension.decorator';
|
||||||
|
export * from 'src/sql-tools/decorators/extensions.decorator';
|
||||||
|
export * from 'src/sql-tools/decorators/foreign-key-column.decorator';
|
||||||
|
export * from 'src/sql-tools/decorators/foreign-key-constraint.decorator';
|
||||||
|
export * from 'src/sql-tools/decorators/generated-column.decorator';
|
||||||
|
export * from 'src/sql-tools/decorators/index.decorator';
|
||||||
|
export * from 'src/sql-tools/decorators/primary-column.decorator';
|
||||||
|
export * from 'src/sql-tools/decorators/primary-generated-column.decorator';
|
||||||
|
export * from 'src/sql-tools/decorators/table.decorator';
|
||||||
|
export * from 'src/sql-tools/decorators/trigger-function.decorator';
|
||||||
|
export * from 'src/sql-tools/decorators/trigger.decorator';
|
||||||
|
export * from 'src/sql-tools/decorators/unique.decorator';
|
||||||
|
export * from 'src/sql-tools/decorators/update-date-column.decorator';
|
@ -1,3 +1,3 @@
|
|||||||
import { Column, ColumnOptions } from 'src/sql-tools/from-code/decorators/column.decorator';
|
import { Column, ColumnOptions } from 'src/sql-tools/decorators/column.decorator';
|
||||||
|
|
||||||
export const PrimaryColumn = (options: Omit<ColumnOptions, 'primary'> = {}) => Column({ ...options, primary: true });
|
export const PrimaryColumn = (options: Omit<ColumnOptions, 'primary'> = {}) => Column({ ...options, primary: true });
|
@ -1,4 +1,4 @@
|
|||||||
import { GenerateColumnOptions, GeneratedColumn } from 'src/sql-tools/from-code/decorators/generated-column.decorator';
|
import { GenerateColumnOptions, GeneratedColumn } from 'src/sql-tools/decorators/generated-column.decorator';
|
||||||
|
|
||||||
export const PrimaryGeneratedColumn = (options: Omit<GenerateColumnOptions, 'primary'> = {}) =>
|
export const PrimaryGeneratedColumn = (options: Omit<GenerateColumnOptions, 'primary'> = {}) =>
|
||||||
GeneratedColumn({ ...options, primary: true });
|
GeneratedColumn({ ...options, primary: true });
|
@ -1,5 +1,5 @@
|
|||||||
import { register } from 'src/sql-tools/from-code/register';
|
|
||||||
import { asOptions } from 'src/sql-tools/helpers';
|
import { asOptions } from 'src/sql-tools/helpers';
|
||||||
|
import { register } from 'src/sql-tools/register';
|
||||||
|
|
||||||
export type TableOptions = {
|
export type TableOptions = {
|
||||||
name?: string;
|
name?: string;
|
@ -1,4 +1,4 @@
|
|||||||
import { Trigger, TriggerOptions } from 'src/sql-tools/from-code/decorators/trigger.decorator';
|
import { Trigger, TriggerOptions } from 'src/sql-tools/decorators/trigger.decorator';
|
||||||
import { DatabaseFunction } from 'src/sql-tools/types';
|
import { DatabaseFunction } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export type TriggerFunctionOptions = Omit<TriggerOptions, 'functionName'> & { function: DatabaseFunction };
|
export type TriggerFunctionOptions = Omit<TriggerOptions, 'functionName'> & { function: DatabaseFunction };
|
@ -1,4 +1,4 @@
|
|||||||
import { register } from 'src/sql-tools/from-code/register';
|
import { register } from 'src/sql-tools/register';
|
||||||
import { TriggerAction, TriggerScope, TriggerTiming } from 'src/sql-tools/types';
|
import { TriggerAction, TriggerScope, TriggerTiming } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export type TriggerOptions = {
|
export type TriggerOptions = {
|
@ -1,4 +1,4 @@
|
|||||||
import { register } from 'src/sql-tools/from-code/register';
|
import { register } from 'src/sql-tools/register';
|
||||||
|
|
||||||
export type UniqueOptions = {
|
export type UniqueOptions = {
|
||||||
name?: string;
|
name?: string;
|
@ -1,4 +1,4 @@
|
|||||||
import { Column, ColumnOptions } from 'src/sql-tools/from-code/decorators/column.decorator';
|
import { Column, ColumnOptions } from 'src/sql-tools/decorators/column.decorator';
|
||||||
|
|
||||||
export const UpdateDateColumn = (options: ColumnOptions = {}): PropertyDecorator => {
|
export const UpdateDateColumn = (options: ColumnOptions = {}): PropertyDecorator => {
|
||||||
return Column({
|
return Column({
|
@ -1,85 +0,0 @@
|
|||||||
import { compareEnums } from 'src/sql-tools/diff/comparers/enum.comparer';
|
|
||||||
import { compareExtensions } from 'src/sql-tools/diff/comparers/extension.comparer';
|
|
||||||
import { compareFunctions } from 'src/sql-tools/diff/comparers/function.comparer';
|
|
||||||
import { compareParameters } from 'src/sql-tools/diff/comparers/parameter.comparer';
|
|
||||||
import { compareTables } from 'src/sql-tools/diff/comparers/table.comparer';
|
|
||||||
import { compare } from 'src/sql-tools/helpers';
|
|
||||||
import { schemaDiffToSql } from 'src/sql-tools/to-sql';
|
|
||||||
import {
|
|
||||||
DatabaseConstraintType,
|
|
||||||
DatabaseSchema,
|
|
||||||
SchemaDiff,
|
|
||||||
SchemaDiffOptions,
|
|
||||||
SchemaDiffToSqlOptions,
|
|
||||||
} from 'src/sql-tools/types';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute the difference between two database schemas
|
|
||||||
*/
|
|
||||||
export const schemaDiff = (source: DatabaseSchema, target: DatabaseSchema, options: SchemaDiffOptions = {}) => {
|
|
||||||
const items = [
|
|
||||||
...compare(source.parameters, target.parameters, options.parameters, compareParameters),
|
|
||||||
...compare(source.extensions, target.extensions, options.extension, compareExtensions),
|
|
||||||
...compare(source.functions, target.functions, options.functions, compareFunctions),
|
|
||||||
...compare(source.enums, target.enums, options.enums, compareEnums),
|
|
||||||
...compare(source.tables, target.tables, options.tables, compareTables),
|
|
||||||
];
|
|
||||||
|
|
||||||
type SchemaName = SchemaDiff['type'];
|
|
||||||
const itemMap: Record<SchemaName, SchemaDiff[]> = {
|
|
||||||
'enum.create': [],
|
|
||||||
'enum.drop': [],
|
|
||||||
'extension.create': [],
|
|
||||||
'extension.drop': [],
|
|
||||||
'function.create': [],
|
|
||||||
'function.drop': [],
|
|
||||||
'table.create': [],
|
|
||||||
'table.drop': [],
|
|
||||||
'column.add': [],
|
|
||||||
'column.alter': [],
|
|
||||||
'column.drop': [],
|
|
||||||
'constraint.add': [],
|
|
||||||
'constraint.drop': [],
|
|
||||||
'index.create': [],
|
|
||||||
'index.drop': [],
|
|
||||||
'trigger.create': [],
|
|
||||||
'trigger.drop': [],
|
|
||||||
'parameter.set': [],
|
|
||||||
'parameter.reset': [],
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const item of items) {
|
|
||||||
itemMap[item.type].push(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
const constraintAdds = itemMap['constraint.add'].filter((item) => item.type === 'constraint.add');
|
|
||||||
|
|
||||||
const orderedItems = [
|
|
||||||
...itemMap['extension.create'],
|
|
||||||
...itemMap['function.create'],
|
|
||||||
...itemMap['parameter.set'],
|
|
||||||
...itemMap['parameter.reset'],
|
|
||||||
...itemMap['enum.create'],
|
|
||||||
...itemMap['trigger.drop'],
|
|
||||||
...itemMap['index.drop'],
|
|
||||||
...itemMap['constraint.drop'],
|
|
||||||
...itemMap['table.create'],
|
|
||||||
...itemMap['column.alter'],
|
|
||||||
...itemMap['column.add'],
|
|
||||||
...constraintAdds.filter(({ constraint }) => constraint.type === DatabaseConstraintType.PRIMARY_KEY),
|
|
||||||
...constraintAdds.filter(({ constraint }) => constraint.type === DatabaseConstraintType.FOREIGN_KEY),
|
|
||||||
...constraintAdds.filter(({ constraint }) => constraint.type === DatabaseConstraintType.UNIQUE),
|
|
||||||
...constraintAdds.filter(({ constraint }) => constraint.type === DatabaseConstraintType.CHECK),
|
|
||||||
...itemMap['index.create'],
|
|
||||||
...itemMap['trigger.create'],
|
|
||||||
...itemMap['column.drop'],
|
|
||||||
...itemMap['table.drop'],
|
|
||||||
...itemMap['enum.drop'],
|
|
||||||
...itemMap['function.drop'],
|
|
||||||
];
|
|
||||||
|
|
||||||
return {
|
|
||||||
items: orderedItems,
|
|
||||||
asSql: (options?: SchemaDiffToSqlOptions) => schemaDiffToSql(orderedItems, options),
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,15 +0,0 @@
|
|||||||
import { ColumnBaseOptions } from 'src/sql-tools/from-code/decorators/column.decorator';
|
|
||||||
import { ForeignKeyAction } from 'src/sql-tools/from-code/decorators/foreign-key-constraint.decorator';
|
|
||||||
import { register } from 'src/sql-tools/from-code/register';
|
|
||||||
|
|
||||||
export type ForeignKeyColumnOptions = ColumnBaseOptions & {
|
|
||||||
onUpdate?: ForeignKeyAction;
|
|
||||||
onDelete?: ForeignKeyAction;
|
|
||||||
constraintName?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ForeignKeyColumn = (target: () => object, options: ForeignKeyColumnOptions): PropertyDecorator => {
|
|
||||||
return (object: object, propertyName: string | symbol) => {
|
|
||||||
register({ type: 'foreignKeyColumn', item: { object, propertyName, options, target } });
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,36 +0,0 @@
|
|||||||
import { readdirSync } from 'node:fs';
|
|
||||||
import { join } from 'node:path';
|
|
||||||
import { reset, schemaFromCode } from 'src/sql-tools/from-code';
|
|
||||||
import { describe, expect, it } from 'vitest';
|
|
||||||
|
|
||||||
describe(schemaFromCode.name, () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
reset();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should work', () => {
|
|
||||||
expect(schemaFromCode()).toEqual({
|
|
||||||
name: 'postgres',
|
|
||||||
schemaName: 'public',
|
|
||||||
functions: [],
|
|
||||||
enums: [],
|
|
||||||
extensions: [],
|
|
||||||
parameters: [],
|
|
||||||
tables: [],
|
|
||||||
warnings: [],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('test files', () => {
|
|
||||||
const files = readdirSync('test/sql-tools', { withFileTypes: true });
|
|
||||||
for (const file of files) {
|
|
||||||
const filePath = join(file.parentPath, file.name);
|
|
||||||
it(filePath, async () => {
|
|
||||||
const module = await import(filePath);
|
|
||||||
expect(module.description).toBeDefined();
|
|
||||||
expect(module.schema).toBeDefined();
|
|
||||||
expect(schemaFromCode(), module.description).toEqual(module.schema);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,77 +0,0 @@
|
|||||||
import 'reflect-metadata';
|
|
||||||
import { processCheckConstraints } from 'src/sql-tools/from-code/processors/check-constraint.processor';
|
|
||||||
import { processColumns } from 'src/sql-tools/from-code/processors/column.processor';
|
|
||||||
import { processConfigurationParameters } from 'src/sql-tools/from-code/processors/configuration-parameter.processor';
|
|
||||||
import { processDatabases } from 'src/sql-tools/from-code/processors/database.processor';
|
|
||||||
import { processEnums } from 'src/sql-tools/from-code/processors/enum.processor';
|
|
||||||
import { processExtensions } from 'src/sql-tools/from-code/processors/extension.processor';
|
|
||||||
import { processForeignKeyColumns } from 'src/sql-tools/from-code/processors/foreign-key-column.processor';
|
|
||||||
import { processForeignKeyConstraints } from 'src/sql-tools/from-code/processors/foreign-key-constraint.processor';
|
|
||||||
import { processFunctions } from 'src/sql-tools/from-code/processors/function.processor';
|
|
||||||
import { processIndexes } from 'src/sql-tools/from-code/processors/index.processor';
|
|
||||||
import { processPrimaryKeyConstraints } from 'src/sql-tools/from-code/processors/primary-key-contraint.processor';
|
|
||||||
import { processTables } from 'src/sql-tools/from-code/processors/table.processor';
|
|
||||||
import { processTriggers } from 'src/sql-tools/from-code/processors/trigger.processor';
|
|
||||||
import { Processor, SchemaBuilder } from 'src/sql-tools/from-code/processors/type';
|
|
||||||
import { processUniqueConstraints } from 'src/sql-tools/from-code/processors/unique-constraint.processor';
|
|
||||||
import { getRegisteredItems, resetRegisteredItems } from 'src/sql-tools/from-code/register';
|
|
||||||
import { DatabaseSchema } from 'src/sql-tools/types';
|
|
||||||
|
|
||||||
let initialized = false;
|
|
||||||
let schema: DatabaseSchema;
|
|
||||||
|
|
||||||
export const reset = () => {
|
|
||||||
initialized = false;
|
|
||||||
resetRegisteredItems();
|
|
||||||
};
|
|
||||||
|
|
||||||
const processors: Processor[] = [
|
|
||||||
processDatabases,
|
|
||||||
processConfigurationParameters,
|
|
||||||
processEnums,
|
|
||||||
processExtensions,
|
|
||||||
processFunctions,
|
|
||||||
processTables,
|
|
||||||
processColumns,
|
|
||||||
processForeignKeyColumns,
|
|
||||||
processForeignKeyConstraints,
|
|
||||||
processUniqueConstraints,
|
|
||||||
processCheckConstraints,
|
|
||||||
processPrimaryKeyConstraints,
|
|
||||||
processIndexes,
|
|
||||||
processTriggers,
|
|
||||||
];
|
|
||||||
|
|
||||||
export type SchemaFromCodeOptions = {
|
|
||||||
/** automatically create indexes on foreign key columns */
|
|
||||||
createForeignKeyIndexes?: boolean;
|
|
||||||
};
|
|
||||||
export const schemaFromCode = (options: SchemaFromCodeOptions = {}) => {
|
|
||||||
if (!initialized) {
|
|
||||||
const globalOptions = {
|
|
||||||
createForeignKeyIndexes: options.createForeignKeyIndexes ?? true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const builder: SchemaBuilder = {
|
|
||||||
name: 'postgres',
|
|
||||||
schemaName: 'public',
|
|
||||||
tables: [],
|
|
||||||
functions: [],
|
|
||||||
enums: [],
|
|
||||||
extensions: [],
|
|
||||||
parameters: [],
|
|
||||||
warnings: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const items = getRegisteredItems();
|
|
||||||
|
|
||||||
for (const processor of processors) {
|
|
||||||
processor(builder, items, globalOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
schema = { ...builder, tables: builder.tables.map(({ metadata: _, ...table }) => table) };
|
|
||||||
initialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return schema;
|
|
||||||
};
|
|
@ -1,93 +0,0 @@
|
|||||||
import { ColumnOptions } from 'src/sql-tools/from-code/decorators/column.decorator';
|
|
||||||
import { onMissingTable, resolveTable } from 'src/sql-tools/from-code/processors/table.processor';
|
|
||||||
import { Processor, SchemaBuilder } from 'src/sql-tools/from-code/processors/type';
|
|
||||||
import { addWarning, asMetadataKey, fromColumnValue } from 'src/sql-tools/helpers';
|
|
||||||
import { DatabaseColumn } from 'src/sql-tools/types';
|
|
||||||
|
|
||||||
export const processColumns: Processor = (builder, items) => {
|
|
||||||
for (const {
|
|
||||||
type,
|
|
||||||
item: { object, propertyName, options },
|
|
||||||
} of items.filter((item) => item.type === 'column' || item.type === 'foreignKeyColumn')) {
|
|
||||||
const table = resolveTable(builder, object.constructor);
|
|
||||||
if (!table) {
|
|
||||||
onMissingTable(builder, type === 'column' ? '@Column' : '@ForeignKeyColumn', object, propertyName);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const columnName = options.name ?? String(propertyName);
|
|
||||||
const existingColumn = table.columns.find((column) => column.name === columnName);
|
|
||||||
if (existingColumn) {
|
|
||||||
// TODO log warnings if column name is not unique
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tableName = table.name;
|
|
||||||
|
|
||||||
let defaultValue = fromColumnValue(options.default);
|
|
||||||
let nullable = options.nullable ?? false;
|
|
||||||
|
|
||||||
// map `{ default: null }` to `{ nullable: true }`
|
|
||||||
if (defaultValue === null) {
|
|
||||||
nullable = true;
|
|
||||||
defaultValue = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isEnum = !!(options as ColumnOptions).enum;
|
|
||||||
|
|
||||||
const column: DatabaseColumn = {
|
|
||||||
name: columnName,
|
|
||||||
tableName,
|
|
||||||
primary: options.primary ?? false,
|
|
||||||
default: defaultValue,
|
|
||||||
nullable,
|
|
||||||
isArray: (options as ColumnOptions).array ?? false,
|
|
||||||
length: options.length,
|
|
||||||
type: isEnum ? 'enum' : options.type || 'character varying',
|
|
||||||
enumName: isEnum ? (options as ColumnOptions).enum!.name : undefined,
|
|
||||||
comment: options.comment,
|
|
||||||
storage: options.storage,
|
|
||||||
identity: options.identity,
|
|
||||||
synchronize: options.synchronize ?? true,
|
|
||||||
};
|
|
||||||
|
|
||||||
writeMetadata(object, propertyName, { name: column.name, options });
|
|
||||||
|
|
||||||
table.columns.push(column);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
type ColumnMetadata = { name: string; options: ColumnOptions };
|
|
||||||
|
|
||||||
export const resolveColumn = (builder: SchemaBuilder, object: object, propertyName: string | symbol) => {
|
|
||||||
const table = resolveTable(builder, object.constructor);
|
|
||||||
if (!table) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const metadata = readMetadata(object, propertyName);
|
|
||||||
if (!metadata) {
|
|
||||||
return { table };
|
|
||||||
}
|
|
||||||
|
|
||||||
const column = table.columns.find((column) => column.name === metadata.name);
|
|
||||||
return { table, column };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const onMissingColumn = (
|
|
||||||
builder: SchemaBuilder,
|
|
||||||
context: string,
|
|
||||||
object: object,
|
|
||||||
propertyName?: symbol | string,
|
|
||||||
) => {
|
|
||||||
const label = object.constructor.name + (propertyName ? '.' + String(propertyName) : '');
|
|
||||||
addWarning(builder, context, `Unable to find column (${label})`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const METADATA_KEY = asMetadataKey('table-metadata');
|
|
||||||
|
|
||||||
const writeMetadata = (object: object, propertyName: symbol | string, metadata: ColumnMetadata) =>
|
|
||||||
Reflect.defineMetadata(METADATA_KEY, metadata, object, propertyName);
|
|
||||||
|
|
||||||
const readMetadata = (object: object, propertyName: symbol | string): ColumnMetadata | undefined =>
|
|
||||||
Reflect.getMetadata(METADATA_KEY, object, propertyName);
|
|
@ -1,58 +0,0 @@
|
|||||||
import { TableOptions } from 'src/sql-tools/from-code/decorators/table.decorator';
|
|
||||||
import { Processor, SchemaBuilder } from 'src/sql-tools/from-code/processors/type';
|
|
||||||
import { addWarning, asMetadataKey, asSnakeCase } from 'src/sql-tools/helpers';
|
|
||||||
|
|
||||||
export const processTables: Processor = (builder, items) => {
|
|
||||||
for (const {
|
|
||||||
item: { options, object },
|
|
||||||
} of items.filter((item) => item.type === 'table')) {
|
|
||||||
const test = readMetadata(object);
|
|
||||||
if (test) {
|
|
||||||
throw new Error(
|
|
||||||
`Table ${test.name} has already been registered. Does ${object.name} have two @Table() decorators?`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const tableName = options.name || asSnakeCase(object.name);
|
|
||||||
|
|
||||||
writeMetadata(object, { name: tableName, options });
|
|
||||||
|
|
||||||
builder.tables.push({
|
|
||||||
name: tableName,
|
|
||||||
columns: [],
|
|
||||||
constraints: [],
|
|
||||||
indexes: [],
|
|
||||||
triggers: [],
|
|
||||||
synchronize: options.synchronize ?? true,
|
|
||||||
metadata: { options, object },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const resolveTable = (builder: SchemaBuilder, object: object) => {
|
|
||||||
const metadata = readMetadata(object);
|
|
||||||
if (!metadata) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.tables.find((table) => table.name === metadata.name);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const onMissingTable = (
|
|
||||||
builder: SchemaBuilder,
|
|
||||||
context: string,
|
|
||||||
object: object,
|
|
||||||
propertyName?: symbol | string,
|
|
||||||
) => {
|
|
||||||
const label = object.constructor.name + (propertyName ? '.' + String(propertyName) : '');
|
|
||||||
addWarning(builder, context, `Unable to find table (${label})`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const METADATA_KEY = asMetadataKey('table-metadata');
|
|
||||||
|
|
||||||
type TableMetadata = { name: string; options: TableOptions };
|
|
||||||
|
|
||||||
const readMetadata = (object: object): TableMetadata | undefined => Reflect.getMetadata(METADATA_KEY, object);
|
|
||||||
|
|
||||||
const writeMetadata = (object: object, metadata: TableMetadata): void =>
|
|
||||||
Reflect.defineMetadata(METADATA_KEY, metadata, object);
|
|
@ -1,10 +0,0 @@
|
|||||||
import { SchemaFromCodeOptions } from 'src/sql-tools/from-code';
|
|
||||||
import { TableOptions } from 'src/sql-tools/from-code/decorators/table.decorator';
|
|
||||||
import { RegisterItem } from 'src/sql-tools/from-code/register-item';
|
|
||||||
import { DatabaseSchema, DatabaseTable } from 'src/sql-tools/types';
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
||||||
export type TableWithMetadata = DatabaseTable & { metadata: { options: TableOptions; object: Function } };
|
|
||||||
export type SchemaBuilder = Omit<DatabaseSchema, 'tables'> & { tables: TableWithMetadata[] };
|
|
||||||
|
|
||||||
export type Processor = (builder: SchemaBuilder, items: RegisterItem[], options: SchemaFromCodeOptions) => void;
|
|
@ -1,54 +0,0 @@
|
|||||||
import { Kysely } from 'kysely';
|
|
||||||
import { PostgresJSDialect } from 'kysely-postgres-js';
|
|
||||||
import { Sql } from 'postgres';
|
|
||||||
import { readColumns } from 'src/sql-tools/from-database/readers/column.reader';
|
|
||||||
import { readComments } from 'src/sql-tools/from-database/readers/comment.reader';
|
|
||||||
import { readConstraints } from 'src/sql-tools/from-database/readers/constraint.reader';
|
|
||||||
import { readExtensions } from 'src/sql-tools/from-database/readers/extension.reader';
|
|
||||||
import { readFunctions } from 'src/sql-tools/from-database/readers/function.reader';
|
|
||||||
import { readIndexes } from 'src/sql-tools/from-database/readers/index.reader';
|
|
||||||
import { readName } from 'src/sql-tools/from-database/readers/name.reader';
|
|
||||||
import { readParameters } from 'src/sql-tools/from-database/readers/parameter.reader';
|
|
||||||
import { readTables } from 'src/sql-tools/from-database/readers/table.reader';
|
|
||||||
import { readTriggers } from 'src/sql-tools/from-database/readers/trigger.reader';
|
|
||||||
import { DatabaseReader } from 'src/sql-tools/from-database/readers/type';
|
|
||||||
import { DatabaseSchema, LoadSchemaOptions, PostgresDB } from 'src/sql-tools/types';
|
|
||||||
|
|
||||||
const readers: DatabaseReader[] = [
|
|
||||||
//
|
|
||||||
readName,
|
|
||||||
readParameters,
|
|
||||||
readExtensions,
|
|
||||||
readFunctions,
|
|
||||||
readTables,
|
|
||||||
readColumns,
|
|
||||||
readIndexes,
|
|
||||||
readConstraints,
|
|
||||||
readTriggers,
|
|
||||||
readComments,
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the database schema from the database
|
|
||||||
*/
|
|
||||||
export const schemaFromDatabase = async (postgres: Sql, options: LoadSchemaOptions = {}): Promise<DatabaseSchema> => {
|
|
||||||
const schema: DatabaseSchema = {
|
|
||||||
name: 'immich',
|
|
||||||
schemaName: options.schemaName || 'public',
|
|
||||||
parameters: [],
|
|
||||||
functions: [],
|
|
||||||
enums: [],
|
|
||||||
extensions: [],
|
|
||||||
tables: [],
|
|
||||||
warnings: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const db = new Kysely<PostgresDB>({ dialect: new PostgresJSDialect({ postgres }) });
|
|
||||||
for (const reader of readers) {
|
|
||||||
await reader(schema, db);
|
|
||||||
}
|
|
||||||
|
|
||||||
await db.destroy();
|
|
||||||
|
|
||||||
return schema;
|
|
||||||
};
|
|
@ -1,3 +0,0 @@
|
|||||||
import { DatabaseClient, DatabaseSchema } from 'src/sql-tools/types';
|
|
||||||
|
|
||||||
export type DatabaseReader = (schema: DatabaseSchema, db: DatabaseClient) => Promise<void>;
|
|
@ -1,15 +1,6 @@
|
|||||||
import { createHash } from 'node:crypto';
|
import { createHash } from 'node:crypto';
|
||||||
import { ColumnValue } from 'src/sql-tools/from-code/decorators/column.decorator';
|
import { ColumnValue } from 'src/sql-tools/decorators/column.decorator';
|
||||||
import { SchemaBuilder } from 'src/sql-tools/from-code/processors/type';
|
import { Comparer, DatabaseColumn, IgnoreOptions, SchemaDiff } from 'src/sql-tools/types';
|
||||||
import {
|
|
||||||
Comparer,
|
|
||||||
DatabaseColumn,
|
|
||||||
DiffOptions,
|
|
||||||
SchemaDiff,
|
|
||||||
TriggerAction,
|
|
||||||
TriggerScope,
|
|
||||||
TriggerTiming,
|
|
||||||
} from 'src/sql-tools/types';
|
|
||||||
|
|
||||||
export const asMetadataKey = (name: string) => `sql-tools:${name}`;
|
export const asMetadataKey = (name: string) => `sql-tools:${name}`;
|
||||||
|
|
||||||
@ -27,46 +18,6 @@ export const asOptions = <T extends { name?: string }>(options: string | T): T =
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const sha1 = (value: string) => createHash('sha1').update(value).digest('hex');
|
export const sha1 = (value: string) => createHash('sha1').update(value).digest('hex');
|
||||||
export const hasMask = (input: number, mask: number) => (input & mask) === mask;
|
|
||||||
|
|
||||||
export const parseTriggerType = (type: number) => {
|
|
||||||
// eslint-disable-next-line unicorn/prefer-math-trunc
|
|
||||||
const scope: TriggerScope = hasMask(type, 1 << 0) ? 'row' : 'statement';
|
|
||||||
|
|
||||||
let timing: TriggerTiming = 'after';
|
|
||||||
const timingMasks: Array<{ mask: number; value: TriggerTiming }> = [
|
|
||||||
{ mask: 1 << 1, value: 'before' },
|
|
||||||
{ mask: 1 << 6, value: 'instead of' },
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const { mask, value } of timingMasks) {
|
|
||||||
if (hasMask(type, mask)) {
|
|
||||||
timing = value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const actions: TriggerAction[] = [];
|
|
||||||
const actionMasks: Array<{ mask: number; value: TriggerAction }> = [
|
|
||||||
{ mask: 1 << 2, value: 'insert' },
|
|
||||||
{ mask: 1 << 3, value: 'delete' },
|
|
||||||
{ mask: 1 << 4, value: 'update' },
|
|
||||||
{ mask: 1 << 5, value: 'truncate' },
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const { mask, value } of actionMasks) {
|
|
||||||
if (hasMask(type, mask)) {
|
|
||||||
actions.push(value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actions.length === 0) {
|
|
||||||
throw new Error(`Unable to parse trigger type ${type}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { actions, timing, scope };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fromColumnValue = (columnValue?: ColumnValue) => {
|
export const fromColumnValue = (columnValue?: ColumnValue) => {
|
||||||
if (columnValue === undefined) {
|
if (columnValue === undefined) {
|
||||||
@ -108,7 +59,7 @@ export const haveEqualColumns = (sourceColumns?: string[], targetColumns?: strin
|
|||||||
export const compare = <T extends { name: string; synchronize: boolean }>(
|
export const compare = <T extends { name: string; synchronize: boolean }>(
|
||||||
sources: T[],
|
sources: T[],
|
||||||
targets: T[],
|
targets: T[],
|
||||||
options: DiffOptions | undefined,
|
options: IgnoreOptions | undefined,
|
||||||
comparer: Comparer<T>,
|
comparer: Comparer<T>,
|
||||||
) => {
|
) => {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
@ -144,7 +95,7 @@ export const compare = <T extends { name: string; synchronize: boolean }>(
|
|||||||
const isIgnored = (
|
const isIgnored = (
|
||||||
source: { synchronize?: boolean } | undefined,
|
source: { synchronize?: boolean } | undefined,
|
||||||
target: { synchronize?: boolean } | undefined,
|
target: { synchronize?: boolean } | undefined,
|
||||||
options: DiffOptions,
|
options: IgnoreOptions,
|
||||||
) => {
|
) => {
|
||||||
return (options.ignoreExtra && !source) || (options.ignoreMissing && !target);
|
return (options.ignoreExtra && !source) || (options.ignoreMissing && !target);
|
||||||
};
|
};
|
||||||
@ -214,20 +165,3 @@ export const asColumnComment = (tableName: string, columnName: string, comment:
|
|||||||
export const asColumnList = (columns: string[]) => columns.map((column) => `"${column}"`).join(', ');
|
export const asColumnList = (columns: string[]) => columns.map((column) => `"${column}"`).join(', ');
|
||||||
|
|
||||||
export const asForeignKeyConstraintName = (table: string, columns: string[]) => asKey('FK_', table, [...columns]);
|
export const asForeignKeyConstraintName = (table: string, columns: string[]) => asKey('FK_', table, [...columns]);
|
||||||
|
|
||||||
export const asIndexName = (table: string, columns?: string[], where?: string) => {
|
|
||||||
const items: string[] = [];
|
|
||||||
for (const columnName of columns ?? []) {
|
|
||||||
items.push(columnName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (where) {
|
|
||||||
items.push(where);
|
|
||||||
}
|
|
||||||
|
|
||||||
return asKey('IDX_', table, items);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addWarning = (builder: SchemaBuilder, context: string, message: string) => {
|
|
||||||
builder.warnings.push(`[${context}] ${message}`);
|
|
||||||
};
|
|
||||||
|
@ -1,22 +1,20 @@
|
|||||||
import { onMissingTable, resolveTable } from 'src/sql-tools/from-code/processors/table.processor';
|
|
||||||
import { Processor } from 'src/sql-tools/from-code/processors/type';
|
|
||||||
import { asKey } from 'src/sql-tools/helpers';
|
import { asKey } from 'src/sql-tools/helpers';
|
||||||
import { DatabaseConstraintType } from 'src/sql-tools/types';
|
import { ConstraintType, Processor } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export const processCheckConstraints: Processor = (builder, items) => {
|
export const processCheckConstraints: Processor = (builder, items) => {
|
||||||
for (const {
|
for (const {
|
||||||
item: { object, options },
|
item: { object, options },
|
||||||
} of items.filter((item) => item.type === 'checkConstraint')) {
|
} of items.filter((item) => item.type === 'checkConstraint')) {
|
||||||
const table = resolveTable(builder, object);
|
const table = builder.getTableByObject(object);
|
||||||
if (!table) {
|
if (!table) {
|
||||||
onMissingTable(builder, '@Check', object);
|
builder.warnMissingTable('@Check', object);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tableName = table.name;
|
const tableName = table.name;
|
||||||
|
|
||||||
table.constraints.push({
|
table.constraints.push({
|
||||||
type: DatabaseConstraintType.CHECK,
|
type: ConstraintType.CHECK,
|
||||||
name: options.name || asCheckConstraintName(tableName, options.expression),
|
name: options.name || asCheckConstraintName(tableName, options.expression),
|
||||||
tableName,
|
tableName,
|
||||||
expression: options.expression,
|
expression: options.expression,
|
55
server/src/sql-tools/processors/column.processor.ts
Normal file
55
server/src/sql-tools/processors/column.processor.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { ColumnOptions } from 'src/sql-tools/decorators/column.decorator';
|
||||||
|
import { fromColumnValue } from 'src/sql-tools/helpers';
|
||||||
|
import { Processor } from 'src/sql-tools/types';
|
||||||
|
|
||||||
|
export const processColumns: Processor = (builder, items) => {
|
||||||
|
for (const {
|
||||||
|
type,
|
||||||
|
item: { object, propertyName, options },
|
||||||
|
} of items.filter((item) => item.type === 'column' || item.type === 'foreignKeyColumn')) {
|
||||||
|
const table = builder.getTableByObject(object.constructor);
|
||||||
|
if (!table) {
|
||||||
|
builder.warnMissingTable(type === 'column' ? '@Column' : '@ForeignKeyColumn', object, propertyName);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const columnName = options.name ?? String(propertyName);
|
||||||
|
const existingColumn = table.columns.find((column) => column.name === columnName);
|
||||||
|
if (existingColumn) {
|
||||||
|
// TODO log warnings if column name is not unique
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let defaultValue = fromColumnValue(options.default);
|
||||||
|
let nullable = options.nullable ?? false;
|
||||||
|
|
||||||
|
// map `{ default: null }` to `{ nullable: true }`
|
||||||
|
if (defaultValue === null) {
|
||||||
|
nullable = true;
|
||||||
|
defaultValue = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isEnum = !!(options as ColumnOptions).enum;
|
||||||
|
|
||||||
|
builder.addColumn(
|
||||||
|
table,
|
||||||
|
{
|
||||||
|
name: columnName,
|
||||||
|
tableName: table.name,
|
||||||
|
primary: options.primary ?? false,
|
||||||
|
default: defaultValue,
|
||||||
|
nullable,
|
||||||
|
isArray: (options as ColumnOptions).array ?? false,
|
||||||
|
length: options.length,
|
||||||
|
type: isEnum ? 'enum' : options.type || 'character varying',
|
||||||
|
enumName: isEnum ? (options as ColumnOptions).enum!.name : undefined,
|
||||||
|
comment: options.comment,
|
||||||
|
storage: options.storage,
|
||||||
|
identity: options.identity,
|
||||||
|
synchronize: options.synchronize ?? true,
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
propertyName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
@ -1,12 +1,12 @@
|
|||||||
import { Processor } from 'src/sql-tools/from-code/processors/type';
|
|
||||||
import { fromColumnValue } from 'src/sql-tools/helpers';
|
import { fromColumnValue } from 'src/sql-tools/helpers';
|
||||||
|
import { Processor } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export const processConfigurationParameters: Processor = (builder, items) => {
|
export const processConfigurationParameters: Processor = (builder, items) => {
|
||||||
for (const {
|
for (const {
|
||||||
item: { options },
|
item: { options },
|
||||||
} of items.filter((item) => item.type === 'configurationParameter')) {
|
} of items.filter((item) => item.type === 'configurationParameter')) {
|
||||||
builder.parameters.push({
|
builder.parameters.push({
|
||||||
databaseName: builder.name,
|
databaseName: builder.databaseName,
|
||||||
name: options.name,
|
name: options.name,
|
||||||
value: fromColumnValue(options.value),
|
value: fromColumnValue(options.value),
|
||||||
scope: options.scope,
|
scope: options.scope,
|
@ -1,10 +1,10 @@
|
|||||||
import { Processor } from 'src/sql-tools/from-code/processors/type';
|
|
||||||
import { asSnakeCase } from 'src/sql-tools/helpers';
|
import { asSnakeCase } from 'src/sql-tools/helpers';
|
||||||
|
import { Processor } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export const processDatabases: Processor = (builder, items) => {
|
export const processDatabases: Processor = (builder, items) => {
|
||||||
for (const {
|
for (const {
|
||||||
item: { object, options },
|
item: { object, options },
|
||||||
} of items.filter((item) => item.type === 'database')) {
|
} of items.filter((item) => item.type === 'database')) {
|
||||||
builder.name = options.name || asSnakeCase(object.name);
|
builder.databaseName = options.name || asSnakeCase(object.name);
|
||||||
}
|
}
|
||||||
};
|
};
|
@ -1,4 +1,4 @@
|
|||||||
import { Processor } from 'src/sql-tools/from-code/processors/type';
|
import { Processor } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export const processEnums: Processor = (builder, items) => {
|
export const processEnums: Processor = (builder, items) => {
|
||||||
for (const { item } of items.filter((item) => item.type === 'enum')) {
|
for (const { item } of items.filter((item) => item.type === 'enum')) {
|
@ -1,4 +1,4 @@
|
|||||||
import { Processor } from 'src/sql-tools/from-code/processors/type';
|
import { Processor } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export const processExtensions: Processor = (builder, items) => {
|
export const processExtensions: Processor = (builder, items) => {
|
||||||
for (const {
|
for (const {
|
@ -1,28 +1,25 @@
|
|||||||
import { onMissingColumn, resolveColumn } from 'src/sql-tools/from-code/processors/column.processor';
|
|
||||||
import { onMissingTable, resolveTable } from 'src/sql-tools/from-code/processors/table.processor';
|
|
||||||
import { Processor } from 'src/sql-tools/from-code/processors/type';
|
|
||||||
import { asForeignKeyConstraintName, asKey } from 'src/sql-tools/helpers';
|
import { asForeignKeyConstraintName, asKey } from 'src/sql-tools/helpers';
|
||||||
import { DatabaseActionType, DatabaseConstraintType } from 'src/sql-tools/types';
|
import { ActionType, ConstraintType, Processor } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export const processForeignKeyColumns: Processor = (builder, items) => {
|
export const processForeignKeyColumns: Processor = (builder, items) => {
|
||||||
for (const {
|
for (const {
|
||||||
item: { object, propertyName, options, target },
|
item: { object, propertyName, options, target },
|
||||||
} of items.filter((item) => item.type === 'foreignKeyColumn')) {
|
} of items.filter((item) => item.type === 'foreignKeyColumn')) {
|
||||||
const { table, column } = resolveColumn(builder, object, propertyName);
|
const { table, column } = builder.getColumnByObjectAndPropertyName(object, propertyName);
|
||||||
if (!table) {
|
if (!table) {
|
||||||
onMissingTable(builder, '@ForeignKeyColumn', object);
|
builder.warnMissingTable('@ForeignKeyColumn', object);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!column) {
|
if (!column) {
|
||||||
// should be impossible since they are pre-created in `column.processor.ts`
|
// should be impossible since they are pre-created in `column.processor.ts`
|
||||||
onMissingColumn(builder, '@ForeignKeyColumn', object, propertyName);
|
builder.warnMissingColumn('@ForeignKeyColumn', object, propertyName);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const referenceTable = resolveTable(builder, target());
|
const referenceTable = builder.getTableByObject(target());
|
||||||
if (!referenceTable) {
|
if (!referenceTable) {
|
||||||
onMissingTable(builder, '@ForeignKeyColumn', object, propertyName);
|
builder.warnMissingTable('@ForeignKeyColumn', object, propertyName);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,11 +38,11 @@ export const processForeignKeyColumns: Processor = (builder, items) => {
|
|||||||
name,
|
name,
|
||||||
tableName: table.name,
|
tableName: table.name,
|
||||||
columnNames,
|
columnNames,
|
||||||
type: DatabaseConstraintType.FOREIGN_KEY,
|
type: ConstraintType.FOREIGN_KEY,
|
||||||
referenceTableName: referenceTable.name,
|
referenceTableName: referenceTable.name,
|
||||||
referenceColumnNames,
|
referenceColumnNames,
|
||||||
onUpdate: options.onUpdate as DatabaseActionType,
|
onUpdate: options.onUpdate as ActionType,
|
||||||
onDelete: options.onDelete as DatabaseActionType,
|
onDelete: options.onDelete as ActionType,
|
||||||
synchronize: options.synchronize ?? true,
|
synchronize: options.synchronize ?? true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -54,7 +51,7 @@ export const processForeignKeyColumns: Processor = (builder, items) => {
|
|||||||
name: options.uniqueConstraintName || asRelationKeyConstraintName(table.name, columnNames),
|
name: options.uniqueConstraintName || asRelationKeyConstraintName(table.name, columnNames),
|
||||||
tableName: table.name,
|
tableName: table.name,
|
||||||
columnNames,
|
columnNames,
|
||||||
type: DatabaseConstraintType.UNIQUE,
|
type: ConstraintType.UNIQUE,
|
||||||
synchronize: options.synchronize ?? true,
|
synchronize: options.synchronize ?? true,
|
||||||
});
|
});
|
||||||
}
|
}
|
@ -1,23 +1,20 @@
|
|||||||
import { onMissingTable, resolveTable } from 'src/sql-tools/from-code/processors/table.processor';
|
import { asForeignKeyConstraintName } from 'src/sql-tools/helpers';
|
||||||
import { Processor } from 'src/sql-tools/from-code/processors/type';
|
import { ActionType, ConstraintType, Processor } from 'src/sql-tools/types';
|
||||||
import { addWarning, asForeignKeyConstraintName, asIndexName } from 'src/sql-tools/helpers';
|
|
||||||
import { DatabaseActionType, DatabaseConstraintType } from 'src/sql-tools/types';
|
|
||||||
|
|
||||||
export const processForeignKeyConstraints: Processor = (builder, items, config) => {
|
export const processForeignKeyConstraints: Processor = (builder, items, config) => {
|
||||||
for (const {
|
for (const {
|
||||||
item: { object, options },
|
item: { object, options },
|
||||||
} of items.filter((item) => item.type === 'foreignKeyConstraint')) {
|
} of items.filter((item) => item.type === 'foreignKeyConstraint')) {
|
||||||
const table = resolveTable(builder, object);
|
const table = builder.getTableByObject(object);
|
||||||
if (!table) {
|
if (!table) {
|
||||||
onMissingTable(builder, '@ForeignKeyConstraint', { name: 'referenceTable' });
|
builder.warnMissingTable('@ForeignKeyConstraint', { name: 'referenceTable' });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const referenceTable = resolveTable(builder, options.referenceTable());
|
const referenceTable = builder.getTableByObject(options.referenceTable());
|
||||||
if (!referenceTable) {
|
if (!referenceTable) {
|
||||||
const referenceTableName = options.referenceTable()?.name;
|
const referenceTableName = options.referenceTable()?.name;
|
||||||
addWarning(
|
builder.warn(
|
||||||
builder,
|
|
||||||
'@ForeignKeyConstraint.referenceTable',
|
'@ForeignKeyConstraint.referenceTable',
|
||||||
`Unable to find table` + (referenceTableName ? ` (${referenceTableName})` : ''),
|
`Unable to find table` + (referenceTableName ? ` (${referenceTableName})` : ''),
|
||||||
);
|
);
|
||||||
@ -28,21 +25,18 @@ export const processForeignKeyConstraints: Processor = (builder, items, config)
|
|||||||
|
|
||||||
for (const columnName of options.columns) {
|
for (const columnName of options.columns) {
|
||||||
if (!table.columns.some(({ name }) => name === columnName)) {
|
if (!table.columns.some(({ name }) => name === columnName)) {
|
||||||
addWarning(
|
const metadata = builder.getTableMetadata(table);
|
||||||
builder,
|
builder.warn('@ForeignKeyConstraint.columns', `Unable to find column (${metadata.object.name}.${columnName})`);
|
||||||
'@ForeignKeyConstraint.columns',
|
|
||||||
`Unable to find column (${table.metadata.object.name}.${columnName})`,
|
|
||||||
);
|
|
||||||
missingColumn = true;
|
missingColumn = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const columnName of options.referenceColumns || []) {
|
for (const columnName of options.referenceColumns || []) {
|
||||||
if (!referenceTable.columns.some(({ name }) => name === columnName)) {
|
if (!referenceTable.columns.some(({ name }) => name === columnName)) {
|
||||||
addWarning(
|
const metadata = builder.getTableMetadata(referenceTable);
|
||||||
builder,
|
builder.warn(
|
||||||
'@ForeignKeyConstraint.referenceColumns',
|
'@ForeignKeyConstraint.referenceColumns',
|
||||||
`Unable to find column (${referenceTable.metadata.object.name}.${columnName})`,
|
`Unable to find column (${metadata.object.name}.${columnName})`,
|
||||||
);
|
);
|
||||||
missingColumn = true;
|
missingColumn = true;
|
||||||
}
|
}
|
||||||
@ -58,14 +52,14 @@ export const processForeignKeyConstraints: Processor = (builder, items, config)
|
|||||||
const name = options.name || asForeignKeyConstraintName(table.name, options.columns);
|
const name = options.name || asForeignKeyConstraintName(table.name, options.columns);
|
||||||
|
|
||||||
table.constraints.push({
|
table.constraints.push({
|
||||||
type: DatabaseConstraintType.FOREIGN_KEY,
|
type: ConstraintType.FOREIGN_KEY,
|
||||||
name,
|
name,
|
||||||
tableName: table.name,
|
tableName: table.name,
|
||||||
columnNames: options.columns,
|
columnNames: options.columns,
|
||||||
referenceTableName: referenceTable.name,
|
referenceTableName: referenceTable.name,
|
||||||
referenceColumnNames: referenceColumns,
|
referenceColumnNames: referenceColumns,
|
||||||
onUpdate: options.onUpdate as DatabaseActionType,
|
onUpdate: options.onUpdate as ActionType,
|
||||||
onDelete: options.onDelete as DatabaseActionType,
|
onDelete: options.onDelete as ActionType,
|
||||||
synchronize: options.synchronize ?? true,
|
synchronize: options.synchronize ?? true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -75,7 +69,7 @@ export const processForeignKeyConstraints: Processor = (builder, items, config)
|
|||||||
|
|
||||||
if (options.index || options.indexName || config.createForeignKeyIndexes) {
|
if (options.index || options.indexName || config.createForeignKeyIndexes) {
|
||||||
table.indexes.push({
|
table.indexes.push({
|
||||||
name: options.indexName || asIndexName(table.name, options.columns),
|
name: options.indexName || builder.asIndexName(table.name, options.columns),
|
||||||
tableName: table.name,
|
tableName: table.name,
|
||||||
columnNames: options.columns,
|
columnNames: options.columns,
|
||||||
unique: false,
|
unique: false,
|
@ -1,4 +1,4 @@
|
|||||||
import { Processor } from 'src/sql-tools/from-code/processors/type';
|
import { Processor } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export const processFunctions: Processor = (builder, items) => {
|
export const processFunctions: Processor = (builder, items) => {
|
||||||
for (const { item } of items.filter((item) => item.type === 'function')) {
|
for (const { item } of items.filter((item) => item.type === 'function')) {
|
@ -1,20 +1,17 @@
|
|||||||
import { onMissingColumn, resolveColumn } from 'src/sql-tools/from-code/processors/column.processor';
|
import { Processor } from 'src/sql-tools/types';
|
||||||
import { onMissingTable, resolveTable } from 'src/sql-tools/from-code/processors/table.processor';
|
|
||||||
import { Processor } from 'src/sql-tools/from-code/processors/type';
|
|
||||||
import { asIndexName } from 'src/sql-tools/helpers';
|
|
||||||
|
|
||||||
export const processIndexes: Processor = (builder, items, config) => {
|
export const processIndexes: Processor = (builder, items, config) => {
|
||||||
for (const {
|
for (const {
|
||||||
item: { object, options },
|
item: { object, options },
|
||||||
} of items.filter((item) => item.type === 'index')) {
|
} of items.filter((item) => item.type === 'index')) {
|
||||||
const table = resolveTable(builder, object);
|
const table = builder.getTableByObject(object);
|
||||||
if (!table) {
|
if (!table) {
|
||||||
onMissingTable(builder, '@Check', object);
|
builder.warnMissingTable('@Check', object);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.indexes.push({
|
table.indexes.push({
|
||||||
name: options.name || asIndexName(table.name, options.columns, options.where),
|
name: options.name || builder.asIndexName(table.name, options.columns, options.where),
|
||||||
tableName: table.name,
|
tableName: table.name,
|
||||||
unique: options.unique ?? false,
|
unique: options.unique ?? false,
|
||||||
expression: options.expression,
|
expression: options.expression,
|
||||||
@ -31,15 +28,15 @@ export const processIndexes: Processor = (builder, items, config) => {
|
|||||||
type,
|
type,
|
||||||
item: { object, propertyName, options },
|
item: { object, propertyName, options },
|
||||||
} of items.filter((item) => item.type === 'column' || item.type === 'foreignKeyColumn')) {
|
} of items.filter((item) => item.type === 'column' || item.type === 'foreignKeyColumn')) {
|
||||||
const { table, column } = resolveColumn(builder, object, propertyName);
|
const { table, column } = builder.getColumnByObjectAndPropertyName(object, propertyName);
|
||||||
if (!table) {
|
if (!table) {
|
||||||
onMissingTable(builder, '@Column', object);
|
builder.warnMissingTable('@Column', object);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!column) {
|
if (!column) {
|
||||||
// should be impossible since they are created in `column.processor.ts`
|
// should be impossible since they are created in `column.processor.ts`
|
||||||
onMissingColumn(builder, '@Column', object, propertyName);
|
builder.warnMissingColumn('@Column', object, propertyName);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +50,7 @@ export const processIndexes: Processor = (builder, items, config) => {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const indexName = options.indexName || asIndexName(table.name, [column.name]);
|
const indexName = options.indexName || builder.asIndexName(table.name, [column.name]);
|
||||||
|
|
||||||
const isIndexPresent = table.indexes.some((index) => index.name === indexName);
|
const isIndexPresent = table.indexes.some((index) => index.name === indexName);
|
||||||
if (isIndexPresent) {
|
if (isIndexPresent) {
|
32
server/src/sql-tools/processors/index.ts
Normal file
32
server/src/sql-tools/processors/index.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { processCheckConstraints } from 'src/sql-tools/processors/check-constraint.processor';
|
||||||
|
import { processColumns } from 'src/sql-tools/processors/column.processor';
|
||||||
|
import { processConfigurationParameters } from 'src/sql-tools/processors/configuration-parameter.processor';
|
||||||
|
import { processDatabases } from 'src/sql-tools/processors/database.processor';
|
||||||
|
import { processEnums } from 'src/sql-tools/processors/enum.processor';
|
||||||
|
import { processExtensions } from 'src/sql-tools/processors/extension.processor';
|
||||||
|
import { processForeignKeyColumns } from 'src/sql-tools/processors/foreign-key-column.processor';
|
||||||
|
import { processForeignKeyConstraints } from 'src/sql-tools/processors/foreign-key-constraint.processor';
|
||||||
|
import { processFunctions } from 'src/sql-tools/processors/function.processor';
|
||||||
|
import { processIndexes } from 'src/sql-tools/processors/index.processor';
|
||||||
|
import { processPrimaryKeyConstraints } from 'src/sql-tools/processors/primary-key-contraint.processor';
|
||||||
|
import { processTables } from 'src/sql-tools/processors/table.processor';
|
||||||
|
import { processTriggers } from 'src/sql-tools/processors/trigger.processor';
|
||||||
|
import { processUniqueConstraints } from 'src/sql-tools/processors/unique-constraint.processor';
|
||||||
|
import { Processor } from 'src/sql-tools/types';
|
||||||
|
|
||||||
|
export const processors: Processor[] = [
|
||||||
|
processDatabases,
|
||||||
|
processConfigurationParameters,
|
||||||
|
processEnums,
|
||||||
|
processExtensions,
|
||||||
|
processFunctions,
|
||||||
|
processTables,
|
||||||
|
processColumns,
|
||||||
|
processForeignKeyColumns,
|
||||||
|
processForeignKeyConstraints,
|
||||||
|
processUniqueConstraints,
|
||||||
|
processCheckConstraints,
|
||||||
|
processPrimaryKeyConstraints,
|
||||||
|
processIndexes,
|
||||||
|
processTriggers,
|
||||||
|
];
|
@ -1,6 +1,5 @@
|
|||||||
import { Processor } from 'src/sql-tools/from-code/processors/type';
|
|
||||||
import { asKey } from 'src/sql-tools/helpers';
|
import { asKey } from 'src/sql-tools/helpers';
|
||||||
import { DatabaseConstraintType } from 'src/sql-tools/types';
|
import { ConstraintType, Processor } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export const processPrimaryKeyConstraints: Processor = (builder) => {
|
export const processPrimaryKeyConstraints: Processor = (builder) => {
|
||||||
for (const table of builder.tables) {
|
for (const table of builder.tables) {
|
||||||
@ -11,13 +10,15 @@ export const processPrimaryKeyConstraints: Processor = (builder) => {
|
|||||||
columnNames.push(column.name);
|
columnNames.push(column.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (columnNames.length > 0) {
|
if (columnNames.length > 0) {
|
||||||
|
const tableMetadata = builder.getTableMetadata(table);
|
||||||
table.constraints.push({
|
table.constraints.push({
|
||||||
type: DatabaseConstraintType.PRIMARY_KEY,
|
type: ConstraintType.PRIMARY_KEY,
|
||||||
name: table.metadata.options.primaryConstraintName || asPrimaryKeyConstraintName(table.name, columnNames),
|
name: tableMetadata.options.primaryConstraintName || asPrimaryKeyConstraintName(table.name, columnNames),
|
||||||
tableName: table.name,
|
tableName: table.name,
|
||||||
columnNames,
|
columnNames,
|
||||||
synchronize: table.metadata.options.synchronize ?? true,
|
synchronize: tableMetadata.options.synchronize ?? true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
28
server/src/sql-tools/processors/table.processor.ts
Normal file
28
server/src/sql-tools/processors/table.processor.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { asSnakeCase } from 'src/sql-tools/helpers';
|
||||||
|
import { Processor } from 'src/sql-tools/types';
|
||||||
|
|
||||||
|
export const processTables: Processor = (builder, items) => {
|
||||||
|
for (const {
|
||||||
|
item: { options, object },
|
||||||
|
} of items.filter((item) => item.type === 'table')) {
|
||||||
|
const test = builder.getTableByObject(object);
|
||||||
|
if (test) {
|
||||||
|
throw new Error(
|
||||||
|
`Table ${test.name} has already been registered. Does ${object.name} have two @Table() decorators?`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.addTable(
|
||||||
|
{
|
||||||
|
name: options.name || asSnakeCase(object.name),
|
||||||
|
columns: [],
|
||||||
|
constraints: [],
|
||||||
|
indexes: [],
|
||||||
|
triggers: [],
|
||||||
|
synchronize: options.synchronize ?? true,
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
object,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
@ -1,15 +1,14 @@
|
|||||||
import { TriggerOptions } from 'src/sql-tools/from-code/decorators/trigger.decorator';
|
import { TriggerOptions } from 'src/sql-tools/decorators/trigger.decorator';
|
||||||
import { onMissingTable, resolveTable } from 'src/sql-tools/from-code/processors/table.processor';
|
|
||||||
import { Processor } from 'src/sql-tools/from-code/processors/type';
|
|
||||||
import { asKey } from 'src/sql-tools/helpers';
|
import { asKey } from 'src/sql-tools/helpers';
|
||||||
|
import { Processor } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export const processTriggers: Processor = (builder, items) => {
|
export const processTriggers: Processor = (builder, items) => {
|
||||||
for (const {
|
for (const {
|
||||||
item: { object, options },
|
item: { object, options },
|
||||||
} of items.filter((item) => item.type === 'trigger')) {
|
} of items.filter((item) => item.type === 'trigger')) {
|
||||||
const table = resolveTable(builder, object);
|
const table = builder.getTableByObject(object);
|
||||||
if (!table) {
|
if (!table) {
|
||||||
onMissingTable(builder, '@Trigger', object);
|
builder.warnMissingTable('@Trigger', object);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -1,16 +1,13 @@
|
|||||||
import { onMissingColumn, resolveColumn } from 'src/sql-tools/from-code/processors/column.processor';
|
|
||||||
import { onMissingTable, resolveTable } from 'src/sql-tools/from-code/processors/table.processor';
|
|
||||||
import { Processor } from 'src/sql-tools/from-code/processors/type';
|
|
||||||
import { asKey } from 'src/sql-tools/helpers';
|
import { asKey } from 'src/sql-tools/helpers';
|
||||||
import { DatabaseConstraintType } from 'src/sql-tools/types';
|
import { ConstraintType, Processor } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export const processUniqueConstraints: Processor = (builder, items) => {
|
export const processUniqueConstraints: Processor = (builder, items) => {
|
||||||
for (const {
|
for (const {
|
||||||
item: { object, options },
|
item: { object, options },
|
||||||
} of items.filter((item) => item.type === 'uniqueConstraint')) {
|
} of items.filter((item) => item.type === 'uniqueConstraint')) {
|
||||||
const table = resolveTable(builder, object);
|
const table = builder.getTableByObject(object);
|
||||||
if (!table) {
|
if (!table) {
|
||||||
onMissingTable(builder, '@Unique', object);
|
builder.warnMissingTable('@Unique', object);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,7 +15,7 @@ export const processUniqueConstraints: Processor = (builder, items) => {
|
|||||||
const columnNames = options.columns;
|
const columnNames = options.columns;
|
||||||
|
|
||||||
table.constraints.push({
|
table.constraints.push({
|
||||||
type: DatabaseConstraintType.UNIQUE,
|
type: ConstraintType.UNIQUE,
|
||||||
name: options.name || asUniqueConstraintName(tableName, columnNames),
|
name: options.name || asUniqueConstraintName(tableName, columnNames),
|
||||||
tableName,
|
tableName,
|
||||||
columnNames,
|
columnNames,
|
||||||
@ -31,21 +28,21 @@ export const processUniqueConstraints: Processor = (builder, items) => {
|
|||||||
type,
|
type,
|
||||||
item: { object, propertyName, options },
|
item: { object, propertyName, options },
|
||||||
} of items.filter((item) => item.type === 'column' || item.type === 'foreignKeyColumn')) {
|
} of items.filter((item) => item.type === 'column' || item.type === 'foreignKeyColumn')) {
|
||||||
const { table, column } = resolveColumn(builder, object, propertyName);
|
const { table, column } = builder.getColumnByObjectAndPropertyName(object, propertyName);
|
||||||
if (!table) {
|
if (!table) {
|
||||||
onMissingTable(builder, '@Column', object);
|
builder.warnMissingTable('@Column', object);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!column) {
|
if (!column) {
|
||||||
// should be impossible since they are created in `column.processor.ts`
|
// should be impossible since they are created in `column.processor.ts`
|
||||||
onMissingColumn(builder, '@Column', object, propertyName);
|
builder.warnMissingColumn('@Column', object, propertyName);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'column' && !options.primary && (options.unique || options.uniqueConstraintName)) {
|
if (type === 'column' && !options.primary && (options.unique || options.uniqueConstraintName)) {
|
||||||
table.constraints.push({
|
table.constraints.push({
|
||||||
type: DatabaseConstraintType.UNIQUE,
|
type: ConstraintType.UNIQUE,
|
||||||
name: options.uniqueConstraintName || asUniqueConstraintName(table.name, [column.name]),
|
name: options.uniqueConstraintName || asUniqueConstraintName(table.name, [column.name]),
|
||||||
tableName: table.name,
|
tableName: table.name,
|
||||||
columnNames: [column.name],
|
columnNames: [column.name],
|
@ -1,29 +1,7 @@
|
|||||||
export { schemaDiff } from 'src/sql-tools/diff';
|
export * from 'src/sql-tools/decorators';
|
||||||
export { schemaFromCode } from 'src/sql-tools/from-code';
|
export * from 'src/sql-tools/register-enum';
|
||||||
export * from 'src/sql-tools/from-code/decorators/after-delete.decorator';
|
export * from 'src/sql-tools/register-function';
|
||||||
export * from 'src/sql-tools/from-code/decorators/after-insert.decorator';
|
export { schemaDiff, schemaDiffToSql } from 'src/sql-tools/schema-diff';
|
||||||
export * from 'src/sql-tools/from-code/decorators/before-update.decorator';
|
export { schemaFromCode } from 'src/sql-tools/schema-from-code';
|
||||||
export * from 'src/sql-tools/from-code/decorators/check.decorator';
|
export { schemaFromDatabase } from 'src/sql-tools/schema-from-database';
|
||||||
export * from 'src/sql-tools/from-code/decorators/column.decorator';
|
|
||||||
export * from 'src/sql-tools/from-code/decorators/configuration-parameter.decorator';
|
|
||||||
export * from 'src/sql-tools/from-code/decorators/create-date-column.decorator';
|
|
||||||
export * from 'src/sql-tools/from-code/decorators/database.decorator';
|
|
||||||
export * from 'src/sql-tools/from-code/decorators/delete-date-column.decorator';
|
|
||||||
export * from 'src/sql-tools/from-code/decorators/extension.decorator';
|
|
||||||
export * from 'src/sql-tools/from-code/decorators/extensions.decorator';
|
|
||||||
export * from 'src/sql-tools/from-code/decorators/foreign-key-column.decorator';
|
|
||||||
export * from 'src/sql-tools/from-code/decorators/foreign-key-constraint.decorator';
|
|
||||||
export * from 'src/sql-tools/from-code/decorators/generated-column.decorator';
|
|
||||||
export * from 'src/sql-tools/from-code/decorators/index.decorator';
|
|
||||||
export * from 'src/sql-tools/from-code/decorators/primary-column.decorator';
|
|
||||||
export * from 'src/sql-tools/from-code/decorators/primary-generated-column.decorator';
|
|
||||||
export * from 'src/sql-tools/from-code/decorators/table.decorator';
|
|
||||||
export * from 'src/sql-tools/from-code/decorators/trigger-function.decorator';
|
|
||||||
export * from 'src/sql-tools/from-code/decorators/trigger.decorator';
|
|
||||||
export * from 'src/sql-tools/from-code/decorators/unique.decorator';
|
|
||||||
export * from 'src/sql-tools/from-code/decorators/update-date-column.decorator';
|
|
||||||
export * from 'src/sql-tools/from-code/register-enum';
|
|
||||||
export * from 'src/sql-tools/from-code/register-function';
|
|
||||||
export { schemaFromDatabase } from 'src/sql-tools/from-database';
|
|
||||||
export { schemaDiffToSql } from 'src/sql-tools/to-sql';
|
|
||||||
export * from 'src/sql-tools/types';
|
export * from 'src/sql-tools/types';
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { sql } from 'kysely';
|
import { sql } from 'kysely';
|
||||||
import { jsonArrayFrom } from 'kysely/helpers/postgres';
|
import { jsonArrayFrom } from 'kysely/helpers/postgres';
|
||||||
import { DatabaseReader } from 'src/sql-tools/from-database/readers/type';
|
import { ColumnType, DatabaseColumn, DatabaseReader } from 'src/sql-tools/types';
|
||||||
import { ColumnType, DatabaseColumn } from 'src/sql-tools/types';
|
|
||||||
|
|
||||||
export const readColumns: DatabaseReader = async (schema, db) => {
|
export const readColumns: DatabaseReader = async (schema, db) => {
|
||||||
const columns = await db
|
const columns = await db
|
@ -1,4 +1,4 @@
|
|||||||
import { DatabaseReader } from 'src/sql-tools/from-database/readers/type';
|
import { DatabaseReader } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export const readComments: DatabaseReader = async (schema, db) => {
|
export const readComments: DatabaseReader = async (schema, db) => {
|
||||||
const comments = await db
|
const comments = await db
|
@ -1,6 +1,5 @@
|
|||||||
import { sql } from 'kysely';
|
import { sql } from 'kysely';
|
||||||
import { DatabaseReader } from 'src/sql-tools/from-database/readers/type';
|
import { ActionType, ConstraintType, DatabaseReader } from 'src/sql-tools/types';
|
||||||
import { DatabaseActionType, DatabaseConstraintType } from 'src/sql-tools/types';
|
|
||||||
|
|
||||||
export const readConstraints: DatabaseReader = async (schema, db) => {
|
export const readConstraints: DatabaseReader = async (schema, db) => {
|
||||||
const constraints = await db
|
const constraints = await db
|
||||||
@ -60,7 +59,7 @@ export const readConstraints: DatabaseReader = async (schema, db) => {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
table.constraints.push({
|
table.constraints.push({
|
||||||
type: DatabaseConstraintType.PRIMARY_KEY,
|
type: ConstraintType.PRIMARY_KEY,
|
||||||
name: constraintName,
|
name: constraintName,
|
||||||
tableName: constraint.table_name,
|
tableName: constraint.table_name,
|
||||||
columnNames: constraint.column_names,
|
columnNames: constraint.column_names,
|
||||||
@ -79,7 +78,7 @@ export const readConstraints: DatabaseReader = async (schema, db) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
table.constraints.push({
|
table.constraints.push({
|
||||||
type: DatabaseConstraintType.FOREIGN_KEY,
|
type: ConstraintType.FOREIGN_KEY,
|
||||||
name: constraintName,
|
name: constraintName,
|
||||||
tableName: constraint.table_name,
|
tableName: constraint.table_name,
|
||||||
columnNames: constraint.column_names,
|
columnNames: constraint.column_names,
|
||||||
@ -95,7 +94,7 @@ export const readConstraints: DatabaseReader = async (schema, db) => {
|
|||||||
// unique constraint
|
// unique constraint
|
||||||
case 'u': {
|
case 'u': {
|
||||||
table.constraints.push({
|
table.constraints.push({
|
||||||
type: DatabaseConstraintType.UNIQUE,
|
type: ConstraintType.UNIQUE,
|
||||||
name: constraintName,
|
name: constraintName,
|
||||||
tableName: constraint.table_name,
|
tableName: constraint.table_name,
|
||||||
columnNames: constraint.column_names as string[],
|
columnNames: constraint.column_names as string[],
|
||||||
@ -107,7 +106,7 @@ export const readConstraints: DatabaseReader = async (schema, db) => {
|
|||||||
// check constraint
|
// check constraint
|
||||||
case 'c': {
|
case 'c': {
|
||||||
table.constraints.push({
|
table.constraints.push({
|
||||||
type: DatabaseConstraintType.CHECK,
|
type: ConstraintType.CHECK,
|
||||||
name: constraint.constraint_name,
|
name: constraint.constraint_name,
|
||||||
tableName: constraint.table_name,
|
tableName: constraint.table_name,
|
||||||
expression: constraint.expression.replace('CHECK ', ''),
|
expression: constraint.expression.replace('CHECK ', ''),
|
||||||
@ -122,23 +121,23 @@ export const readConstraints: DatabaseReader = async (schema, db) => {
|
|||||||
const asDatabaseAction = (action: string) => {
|
const asDatabaseAction = (action: string) => {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'a': {
|
case 'a': {
|
||||||
return DatabaseActionType.NO_ACTION;
|
return ActionType.NO_ACTION;
|
||||||
}
|
}
|
||||||
case 'c': {
|
case 'c': {
|
||||||
return DatabaseActionType.CASCADE;
|
return ActionType.CASCADE;
|
||||||
}
|
}
|
||||||
case 'r': {
|
case 'r': {
|
||||||
return DatabaseActionType.RESTRICT;
|
return ActionType.RESTRICT;
|
||||||
}
|
}
|
||||||
case 'n': {
|
case 'n': {
|
||||||
return DatabaseActionType.SET_NULL;
|
return ActionType.SET_NULL;
|
||||||
}
|
}
|
||||||
case 'd': {
|
case 'd': {
|
||||||
return DatabaseActionType.SET_DEFAULT;
|
return ActionType.SET_DEFAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
return DatabaseActionType.NO_ACTION;
|
return ActionType.NO_ACTION;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
@ -1,4 +1,4 @@
|
|||||||
import { DatabaseReader } from 'src/sql-tools/from-database/readers/type';
|
import { DatabaseReader } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export const readExtensions: DatabaseReader = async (schema, db) => {
|
export const readExtensions: DatabaseReader = async (schema, db) => {
|
||||||
const extensions = await db
|
const extensions = await db
|
@ -1,5 +1,5 @@
|
|||||||
import { sql } from 'kysely';
|
import { sql } from 'kysely';
|
||||||
import { DatabaseReader } from 'src/sql-tools/from-database/readers/type';
|
import { DatabaseReader } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export const readFunctions: DatabaseReader = async (schema, db) => {
|
export const readFunctions: DatabaseReader = async (schema, db) => {
|
||||||
const routines = await db
|
const routines = await db
|
@ -1,5 +1,5 @@
|
|||||||
import { sql } from 'kysely';
|
import { sql } from 'kysely';
|
||||||
import { DatabaseReader } from 'src/sql-tools/from-database/readers/type';
|
import { DatabaseReader } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export const readIndexes: DatabaseReader = async (schema, db) => {
|
export const readIndexes: DatabaseReader = async (schema, db) => {
|
||||||
const indexes = await db
|
const indexes = await db
|
25
server/src/sql-tools/readers/index.ts
Normal file
25
server/src/sql-tools/readers/index.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { readColumns } from 'src/sql-tools/readers/column.reader';
|
||||||
|
import { readComments } from 'src/sql-tools/readers/comment.reader';
|
||||||
|
import { readConstraints } from 'src/sql-tools/readers/constraint.reader';
|
||||||
|
import { readExtensions } from 'src/sql-tools/readers/extension.reader';
|
||||||
|
import { readFunctions } from 'src/sql-tools/readers/function.reader';
|
||||||
|
import { readIndexes } from 'src/sql-tools/readers/index.reader';
|
||||||
|
import { readName } from 'src/sql-tools/readers/name.reader';
|
||||||
|
import { readParameters } from 'src/sql-tools/readers/parameter.reader';
|
||||||
|
import { readTables } from 'src/sql-tools/readers/table.reader';
|
||||||
|
import { readTriggers } from 'src/sql-tools/readers/trigger.reader';
|
||||||
|
import { DatabaseReader } from 'src/sql-tools/types';
|
||||||
|
|
||||||
|
export const readers: DatabaseReader[] = [
|
||||||
|
//
|
||||||
|
readName,
|
||||||
|
readParameters,
|
||||||
|
readExtensions,
|
||||||
|
readFunctions,
|
||||||
|
readTables,
|
||||||
|
readColumns,
|
||||||
|
readIndexes,
|
||||||
|
readConstraints,
|
||||||
|
readTriggers,
|
||||||
|
readComments,
|
||||||
|
];
|
@ -1,8 +1,8 @@
|
|||||||
import { QueryResult, sql } from 'kysely';
|
import { QueryResult, sql } from 'kysely';
|
||||||
import { DatabaseReader } from 'src/sql-tools/from-database/readers/type';
|
import { DatabaseReader } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export const readName: DatabaseReader = async (schema, db) => {
|
export const readName: DatabaseReader = async (schema, db) => {
|
||||||
const result = (await sql`SELECT current_database() as name`.execute(db)) as QueryResult<{ name: string }>;
|
const result = (await sql`SELECT current_database() as name`.execute(db)) as QueryResult<{ name: string }>;
|
||||||
|
|
||||||
schema.name = result.rows[0].name;
|
schema.databaseName = result.rows[0].name;
|
||||||
};
|
};
|
@ -1,6 +1,5 @@
|
|||||||
import { sql } from 'kysely';
|
import { sql } from 'kysely';
|
||||||
import { DatabaseReader } from 'src/sql-tools/from-database/readers/type';
|
import { DatabaseReader, ParameterScope } from 'src/sql-tools/types';
|
||||||
import { ParameterScope } from 'src/sql-tools/types';
|
|
||||||
|
|
||||||
export const readParameters: DatabaseReader = async (schema, db) => {
|
export const readParameters: DatabaseReader = async (schema, db) => {
|
||||||
const parameters = await db
|
const parameters = await db
|
||||||
@ -13,7 +12,7 @@ export const readParameters: DatabaseReader = async (schema, db) => {
|
|||||||
schema.parameters.push({
|
schema.parameters.push({
|
||||||
name: parameter.name,
|
name: parameter.name,
|
||||||
value: parameter.value,
|
value: parameter.value,
|
||||||
databaseName: schema.name,
|
databaseName: schema.databaseName,
|
||||||
scope: parameter.scope as ParameterScope,
|
scope: parameter.scope as ParameterScope,
|
||||||
synchronize: true,
|
synchronize: true,
|
||||||
});
|
});
|
@ -1,5 +1,5 @@
|
|||||||
import { sql } from 'kysely';
|
import { sql } from 'kysely';
|
||||||
import { DatabaseReader } from 'src/sql-tools/from-database/readers/type';
|
import { DatabaseReader } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export const readTables: DatabaseReader = async (schema, db) => {
|
export const readTables: DatabaseReader = async (schema, db) => {
|
||||||
const tables = await db
|
const tables = await db
|
@ -1,5 +1,4 @@
|
|||||||
import { DatabaseReader } from 'src/sql-tools/from-database/readers/type';
|
import { DatabaseReader, TriggerAction, TriggerScope, TriggerTiming } from 'src/sql-tools/types';
|
||||||
import { parseTriggerType } from 'src/sql-tools/helpers';
|
|
||||||
|
|
||||||
export const readTriggers: DatabaseReader = async (schema, db) => {
|
export const readTriggers: DatabaseReader = async (schema, db) => {
|
||||||
const triggers = await db
|
const triggers = await db
|
||||||
@ -44,3 +43,44 @@ export const readTriggers: DatabaseReader = async (schema, db) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const hasMask = (input: number, mask: number) => (input & mask) === mask;
|
||||||
|
|
||||||
|
export const parseTriggerType = (type: number) => {
|
||||||
|
// eslint-disable-next-line unicorn/prefer-math-trunc
|
||||||
|
const scope: TriggerScope = hasMask(type, 1 << 0) ? 'row' : 'statement';
|
||||||
|
|
||||||
|
let timing: TriggerTiming = 'after';
|
||||||
|
const timingMasks: Array<{ mask: number; value: TriggerTiming }> = [
|
||||||
|
{ mask: 1 << 1, value: 'before' },
|
||||||
|
{ mask: 1 << 6, value: 'instead of' },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const { mask, value } of timingMasks) {
|
||||||
|
if (hasMask(type, mask)) {
|
||||||
|
timing = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions: TriggerAction[] = [];
|
||||||
|
const actionMasks: Array<{ mask: number; value: TriggerAction }> = [
|
||||||
|
{ mask: 1 << 2, value: 'insert' },
|
||||||
|
{ mask: 1 << 3, value: 'delete' },
|
||||||
|
{ mask: 1 << 4, value: 'update' },
|
||||||
|
{ mask: 1 << 5, value: 'truncate' },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const { mask, value } of actionMasks) {
|
||||||
|
if (hasMask(type, mask)) {
|
||||||
|
actions.push(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actions.length === 0) {
|
||||||
|
throw new Error(`Unable to parse trigger type ${type}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { actions, timing, scope };
|
||||||
|
};
|
@ -1,4 +1,4 @@
|
|||||||
import { register } from 'src/sql-tools/from-code/register';
|
import { register } from 'src/sql-tools/register';
|
||||||
import { DatabaseEnum } from 'src/sql-tools/types';
|
import { DatabaseEnum } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export type EnumOptions = {
|
export type EnumOptions = {
|
@ -1,4 +1,4 @@
|
|||||||
import { register } from 'src/sql-tools/from-code/register';
|
import { register } from 'src/sql-tools/register';
|
||||||
import { ColumnType, DatabaseFunction } from 'src/sql-tools/types';
|
import { ColumnType, DatabaseFunction } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export type FunctionOptions = {
|
export type FunctionOptions = {
|
@ -1,17 +1,17 @@
|
|||||||
import { CheckOptions } from 'src/sql-tools/from-code/decorators/check.decorator';
|
/* eslint-disable @typescript-eslint/no-unsafe-function-type */
|
||||||
import { ColumnOptions } from 'src/sql-tools/from-code/decorators/column.decorator';
|
import { CheckOptions } from 'src/sql-tools/decorators/check.decorator';
|
||||||
import { ConfigurationParameterOptions } from 'src/sql-tools/from-code/decorators/configuration-parameter.decorator';
|
import { ColumnOptions } from 'src/sql-tools/decorators/column.decorator';
|
||||||
import { DatabaseOptions } from 'src/sql-tools/from-code/decorators/database.decorator';
|
import { ConfigurationParameterOptions } from 'src/sql-tools/decorators/configuration-parameter.decorator';
|
||||||
import { ExtensionOptions } from 'src/sql-tools/from-code/decorators/extension.decorator';
|
import { DatabaseOptions } from 'src/sql-tools/decorators/database.decorator';
|
||||||
import { ForeignKeyColumnOptions } from 'src/sql-tools/from-code/decorators/foreign-key-column.decorator';
|
import { ExtensionOptions } from 'src/sql-tools/decorators/extension.decorator';
|
||||||
import { ForeignKeyConstraintOptions } from 'src/sql-tools/from-code/decorators/foreign-key-constraint.decorator';
|
import { ForeignKeyColumnOptions } from 'src/sql-tools/decorators/foreign-key-column.decorator';
|
||||||
import { IndexOptions } from 'src/sql-tools/from-code/decorators/index.decorator';
|
import { ForeignKeyConstraintOptions } from 'src/sql-tools/decorators/foreign-key-constraint.decorator';
|
||||||
import { TableOptions } from 'src/sql-tools/from-code/decorators/table.decorator';
|
import { IndexOptions } from 'src/sql-tools/decorators/index.decorator';
|
||||||
import { TriggerOptions } from 'src/sql-tools/from-code/decorators/trigger.decorator';
|
import { TableOptions } from 'src/sql-tools/decorators/table.decorator';
|
||||||
import { UniqueOptions } from 'src/sql-tools/from-code/decorators/unique.decorator';
|
import { TriggerOptions } from 'src/sql-tools/decorators/trigger.decorator';
|
||||||
|
import { UniqueOptions } from 'src/sql-tools/decorators/unique.decorator';
|
||||||
import { DatabaseEnum, DatabaseFunction } from 'src/sql-tools/types';
|
import { DatabaseEnum, DatabaseFunction } from 'src/sql-tools/types';
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
||||||
export type ClassBased<T> = { object: Function } & T;
|
export type ClassBased<T> = { object: Function } & T;
|
||||||
export type PropertyBased<T> = { object: object; propertyName: string | symbol } & T;
|
export type PropertyBased<T> = { object: object; propertyName: string | symbol } & T;
|
||||||
export type RegisterItem =
|
export type RegisterItem =
|
||||||
@ -26,6 +26,6 @@ export type RegisterItem =
|
|||||||
| { type: 'trigger'; item: ClassBased<{ options: TriggerOptions }> }
|
| { type: 'trigger'; item: ClassBased<{ options: TriggerOptions }> }
|
||||||
| { type: 'extension'; item: ClassBased<{ options: ExtensionOptions }> }
|
| { type: 'extension'; item: ClassBased<{ options: ExtensionOptions }> }
|
||||||
| { type: 'configurationParameter'; item: ClassBased<{ options: ConfigurationParameterOptions }> }
|
| { type: 'configurationParameter'; item: ClassBased<{ options: ConfigurationParameterOptions }> }
|
||||||
| { type: 'foreignKeyColumn'; item: PropertyBased<{ options: ForeignKeyColumnOptions; target: () => object }> }
|
| { type: 'foreignKeyColumn'; item: PropertyBased<{ options: ForeignKeyColumnOptions; target: () => Function }> }
|
||||||
| { type: 'foreignKeyConstraint'; item: ClassBased<{ options: ForeignKeyConstraintOptions }> };
|
| { type: 'foreignKeyConstraint'; item: ClassBased<{ options: ForeignKeyConstraintOptions }> };
|
||||||
export type RegisterItemType<T extends RegisterItem['type']> = Extract<RegisterItem, { type: T }>['item'];
|
export type RegisterItemType<T extends RegisterItem['type']> = Extract<RegisterItem, { type: T }>['item'];
|
@ -1,4 +1,4 @@
|
|||||||
import { RegisterItem } from 'src/sql-tools/from-code/register-item';
|
import { RegisterItem } from 'src/sql-tools/register-item';
|
||||||
|
|
||||||
const items: RegisterItem[] = [];
|
const items: RegisterItem[] = [];
|
||||||
|
|
121
server/src/sql-tools/schema-builder.ts
Normal file
121
server/src/sql-tools/schema-builder.ts
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-function-type */
|
||||||
|
import { ColumnOptions, TableOptions } from 'src/sql-tools/decorators';
|
||||||
|
import { asKey } from 'src/sql-tools/helpers';
|
||||||
|
import {
|
||||||
|
DatabaseColumn,
|
||||||
|
DatabaseEnum,
|
||||||
|
DatabaseExtension,
|
||||||
|
DatabaseFunction,
|
||||||
|
DatabaseParameter,
|
||||||
|
DatabaseSchema,
|
||||||
|
DatabaseTable,
|
||||||
|
SchemaFromCodeOptions,
|
||||||
|
} from 'src/sql-tools/types';
|
||||||
|
|
||||||
|
type TableMetadata = { options: TableOptions; object: Function; methodToColumn: Map<string | symbol, DatabaseColumn> };
|
||||||
|
|
||||||
|
export class SchemaBuilder {
|
||||||
|
databaseName: string;
|
||||||
|
schemaName: string;
|
||||||
|
tables: DatabaseTable[] = [];
|
||||||
|
functions: DatabaseFunction[] = [];
|
||||||
|
enums: DatabaseEnum[] = [];
|
||||||
|
extensions: DatabaseExtension[] = [];
|
||||||
|
parameters: DatabaseParameter[] = [];
|
||||||
|
warnings: string[] = [];
|
||||||
|
|
||||||
|
classToTable: WeakMap<Function, DatabaseTable> = new WeakMap();
|
||||||
|
tableToMetadata: WeakMap<DatabaseTable, TableMetadata> = new WeakMap();
|
||||||
|
|
||||||
|
constructor(options: SchemaFromCodeOptions) {
|
||||||
|
this.databaseName = options.databaseName ?? 'postgres';
|
||||||
|
this.schemaName = options.schemaName ?? 'public';
|
||||||
|
}
|
||||||
|
|
||||||
|
getTableByObject(object: Function) {
|
||||||
|
return this.classToTable.get(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTableByName(name: string) {
|
||||||
|
return this.tables.find((table) => table.name === name);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTableMetadata(table: DatabaseTable) {
|
||||||
|
const metadata = this.tableToMetadata.get(table);
|
||||||
|
if (!metadata) {
|
||||||
|
throw new Error(`Table metadata not found for table: ${table.name}`);
|
||||||
|
}
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
addTable(table: DatabaseTable, options: TableOptions, object: Function) {
|
||||||
|
this.tables.push(table);
|
||||||
|
this.classToTable.set(object, table);
|
||||||
|
this.tableToMetadata.set(table, { options, object, methodToColumn: new Map() });
|
||||||
|
}
|
||||||
|
|
||||||
|
getColumnByObjectAndPropertyName(
|
||||||
|
object: object,
|
||||||
|
propertyName: string | symbol,
|
||||||
|
): { table?: DatabaseTable; column?: DatabaseColumn } {
|
||||||
|
const table = this.getTableByObject(object.constructor);
|
||||||
|
if (!table) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const tableMetadata = this.tableToMetadata.get(table);
|
||||||
|
if (!tableMetadata) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const column = tableMetadata.methodToColumn.get(propertyName);
|
||||||
|
|
||||||
|
return { table, column };
|
||||||
|
}
|
||||||
|
|
||||||
|
addColumn(table: DatabaseTable, column: DatabaseColumn, options: ColumnOptions, propertyName: string | symbol) {
|
||||||
|
table.columns.push(column);
|
||||||
|
const tableMetadata = this.getTableMetadata(table);
|
||||||
|
tableMetadata.methodToColumn.set(propertyName, column);
|
||||||
|
}
|
||||||
|
|
||||||
|
asIndexName(table: string, columns?: string[], where?: string) {
|
||||||
|
const items: string[] = [];
|
||||||
|
for (const columnName of columns ?? []) {
|
||||||
|
items.push(columnName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (where) {
|
||||||
|
items.push(where);
|
||||||
|
}
|
||||||
|
|
||||||
|
return asKey('IDX_', table, items);
|
||||||
|
}
|
||||||
|
|
||||||
|
warn(context: string, message: string) {
|
||||||
|
this.warnings.push(`[${context}] ${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
warnMissingTable(context: string, object: object, propertyName?: symbol | string) {
|
||||||
|
const label = object.constructor.name + (propertyName ? '.' + String(propertyName) : '');
|
||||||
|
this.warn(context, `Unable to find table (${label})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
warnMissingColumn(context: string, object: object, propertyName?: symbol | string) {
|
||||||
|
const label = object.constructor.name + (propertyName ? '.' + String(propertyName) : '');
|
||||||
|
this.warn(context, `Unable to find column (${label})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
build(): DatabaseSchema {
|
||||||
|
return {
|
||||||
|
databaseName: this.databaseName,
|
||||||
|
schemaName: this.schemaName,
|
||||||
|
tables: this.tables,
|
||||||
|
functions: this.functions,
|
||||||
|
enums: this.enums,
|
||||||
|
extensions: this.extensions,
|
||||||
|
parameters: this.parameters,
|
||||||
|
warnings: this.warnings,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,10 @@
|
|||||||
import { schemaDiff } from 'src/sql-tools/diff';
|
import { schemaDiff } from 'src/sql-tools/schema-diff';
|
||||||
import {
|
import {
|
||||||
|
ActionType,
|
||||||
ColumnType,
|
ColumnType,
|
||||||
DatabaseActionType,
|
ConstraintType,
|
||||||
DatabaseColumn,
|
DatabaseColumn,
|
||||||
DatabaseConstraint,
|
DatabaseConstraint,
|
||||||
DatabaseConstraintType,
|
|
||||||
DatabaseIndex,
|
DatabaseIndex,
|
||||||
DatabaseSchema,
|
DatabaseSchema,
|
||||||
DatabaseTable,
|
DatabaseTable,
|
||||||
@ -15,7 +15,7 @@ const fromColumn = (column: Partial<Omit<DatabaseColumn, 'tableName'>>): Databas
|
|||||||
const tableName = 'table1';
|
const tableName = 'table1';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: 'postgres',
|
databaseName: 'postgres',
|
||||||
schemaName: 'public',
|
schemaName: 'public',
|
||||||
functions: [],
|
functions: [],
|
||||||
enums: [],
|
enums: [],
|
||||||
@ -49,7 +49,7 @@ const fromConstraint = (constraint?: DatabaseConstraint): DatabaseSchema => {
|
|||||||
const tableName = constraint?.tableName || 'table1';
|
const tableName = constraint?.tableName || 'table1';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: 'postgres',
|
databaseName: 'postgres',
|
||||||
schemaName: 'public',
|
schemaName: 'public',
|
||||||
functions: [],
|
functions: [],
|
||||||
enums: [],
|
enums: [],
|
||||||
@ -82,7 +82,7 @@ const fromIndex = (index?: DatabaseIndex): DatabaseSchema => {
|
|||||||
const tableName = index?.tableName || 'table1';
|
const tableName = index?.tableName || 'table1';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: 'postgres',
|
databaseName: 'postgres',
|
||||||
schemaName: 'public',
|
schemaName: 'public',
|
||||||
functions: [],
|
functions: [],
|
||||||
enums: [],
|
enums: [],
|
||||||
@ -155,7 +155,7 @@ const newSchema = (schema: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: 'immich',
|
databaseName: 'immich',
|
||||||
schemaName: schema?.name || 'public',
|
schemaName: schema?.name || 'public',
|
||||||
functions: [],
|
functions: [],
|
||||||
enums: [],
|
enums: [],
|
||||||
@ -166,14 +166,14 @@ const newSchema = (schema: {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('schemaDiff', () => {
|
describe(schemaDiff.name, () => {
|
||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
const diff = schemaDiff(newSchema({ tables: [] }), newSchema({ tables: [] }));
|
const diff = schemaDiff(newSchema({ tables: [] }), newSchema({ tables: [] }));
|
||||||
expect(diff.items).toEqual([]);
|
expect(diff.items).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('table', () => {
|
describe('table', () => {
|
||||||
describe('table.create', () => {
|
describe('TableCreate', () => {
|
||||||
it('should find a missing table', () => {
|
it('should find a missing table', () => {
|
||||||
const column: DatabaseColumn = {
|
const column: DatabaseColumn = {
|
||||||
type: 'character varying',
|
type: 'character varying',
|
||||||
@ -190,7 +190,7 @@ describe('schemaDiff', () => {
|
|||||||
|
|
||||||
expect(diff.items).toHaveLength(1);
|
expect(diff.items).toHaveLength(1);
|
||||||
expect(diff.items[0]).toEqual({
|
expect(diff.items[0]).toEqual({
|
||||||
type: 'table.create',
|
type: 'TableCreate',
|
||||||
table: {
|
table: {
|
||||||
name: 'table1',
|
name: 'table1',
|
||||||
columns: [column],
|
columns: [column],
|
||||||
@ -204,7 +204,7 @@ describe('schemaDiff', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('table.drop', () => {
|
describe('TableDrop', () => {
|
||||||
it('should find an extra table', () => {
|
it('should find an extra table', () => {
|
||||||
const diff = schemaDiff(
|
const diff = schemaDiff(
|
||||||
newSchema({ tables: [] }),
|
newSchema({ tables: [] }),
|
||||||
@ -216,7 +216,7 @@ describe('schemaDiff', () => {
|
|||||||
|
|
||||||
expect(diff.items).toHaveLength(1);
|
expect(diff.items).toHaveLength(1);
|
||||||
expect(diff.items[0]).toEqual({
|
expect(diff.items[0]).toEqual({
|
||||||
type: 'table.drop',
|
type: 'TableDrop',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
reason: 'missing in source',
|
reason: 'missing in source',
|
||||||
});
|
});
|
||||||
@ -238,7 +238,7 @@ describe('schemaDiff', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('column', () => {
|
describe('column', () => {
|
||||||
describe('column.add', () => {
|
describe('ColumnAdd', () => {
|
||||||
it('should find a new column', () => {
|
it('should find a new column', () => {
|
||||||
const diff = schemaDiff(
|
const diff = schemaDiff(
|
||||||
newSchema({
|
newSchema({
|
||||||
@ -256,7 +256,7 @@ describe('schemaDiff', () => {
|
|||||||
|
|
||||||
expect(diff.items).toEqual([
|
expect(diff.items).toEqual([
|
||||||
{
|
{
|
||||||
type: 'column.add',
|
type: 'ColumnAdd',
|
||||||
column: {
|
column: {
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
isArray: false,
|
isArray: false,
|
||||||
@ -271,7 +271,7 @@ describe('schemaDiff', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('column.drop', () => {
|
describe('ColumnDrop', () => {
|
||||||
it('should find an extra column', () => {
|
it('should find an extra column', () => {
|
||||||
const diff = schemaDiff(
|
const diff = schemaDiff(
|
||||||
newSchema({
|
newSchema({
|
||||||
@ -289,7 +289,7 @@ describe('schemaDiff', () => {
|
|||||||
|
|
||||||
expect(diff.items).toEqual([
|
expect(diff.items).toEqual([
|
||||||
{
|
{
|
||||||
type: 'column.drop',
|
type: 'ColumnDrop',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
columnName: 'column2',
|
columnName: 'column2',
|
||||||
reason: 'missing in source',
|
reason: 'missing in source',
|
||||||
@ -307,7 +307,7 @@ describe('schemaDiff', () => {
|
|||||||
|
|
||||||
expect(diff.items).toEqual([
|
expect(diff.items).toEqual([
|
||||||
{
|
{
|
||||||
type: 'column.alter',
|
type: 'ColumnAlter',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
columnName: 'column1',
|
columnName: 'column1',
|
||||||
changes: {
|
changes: {
|
||||||
@ -326,7 +326,7 @@ describe('schemaDiff', () => {
|
|||||||
|
|
||||||
expect(diff.items).toEqual([
|
expect(diff.items).toEqual([
|
||||||
{
|
{
|
||||||
type: 'column.alter',
|
type: 'ColumnAlter',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
columnName: 'column1',
|
columnName: 'column1',
|
||||||
changes: {
|
changes: {
|
||||||
@ -347,7 +347,7 @@ describe('schemaDiff', () => {
|
|||||||
|
|
||||||
expect(diff.items).toEqual([
|
expect(diff.items).toEqual([
|
||||||
{
|
{
|
||||||
type: 'column.alter',
|
type: 'ColumnAlter',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
columnName: 'column1',
|
columnName: 'column1',
|
||||||
changes: {
|
changes: {
|
||||||
@ -388,12 +388,12 @@ describe('schemaDiff', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('constraint', () => {
|
describe('constraint', () => {
|
||||||
describe('constraint.add', () => {
|
describe('ConstraintAdd', () => {
|
||||||
it('should detect a new constraint', () => {
|
it('should detect a new constraint', () => {
|
||||||
const diff = schemaDiff(
|
const diff = schemaDiff(
|
||||||
fromConstraint({
|
fromConstraint({
|
||||||
name: 'PK_test',
|
name: 'PK_test',
|
||||||
type: DatabaseConstraintType.PRIMARY_KEY,
|
type: ConstraintType.PRIMARY_KEY,
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
columnNames: ['id'],
|
columnNames: ['id'],
|
||||||
synchronize: true,
|
synchronize: true,
|
||||||
@ -403,9 +403,9 @@ describe('schemaDiff', () => {
|
|||||||
|
|
||||||
expect(diff.items).toEqual([
|
expect(diff.items).toEqual([
|
||||||
{
|
{
|
||||||
type: 'constraint.add',
|
type: 'ConstraintAdd',
|
||||||
constraint: {
|
constraint: {
|
||||||
type: DatabaseConstraintType.PRIMARY_KEY,
|
type: ConstraintType.PRIMARY_KEY,
|
||||||
name: 'PK_test',
|
name: 'PK_test',
|
||||||
columnNames: ['id'],
|
columnNames: ['id'],
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
@ -417,13 +417,13 @@ describe('schemaDiff', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('constraint.drop', () => {
|
describe('ConstraintDrop', () => {
|
||||||
it('should detect an extra constraint', () => {
|
it('should detect an extra constraint', () => {
|
||||||
const diff = schemaDiff(
|
const diff = schemaDiff(
|
||||||
fromConstraint(),
|
fromConstraint(),
|
||||||
fromConstraint({
|
fromConstraint({
|
||||||
name: 'PK_test',
|
name: 'PK_test',
|
||||||
type: DatabaseConstraintType.PRIMARY_KEY,
|
type: ConstraintType.PRIMARY_KEY,
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
columnNames: ['id'],
|
columnNames: ['id'],
|
||||||
synchronize: true,
|
synchronize: true,
|
||||||
@ -432,7 +432,7 @@ describe('schemaDiff', () => {
|
|||||||
|
|
||||||
expect(diff.items).toEqual([
|
expect(diff.items).toEqual([
|
||||||
{
|
{
|
||||||
type: 'constraint.drop',
|
type: 'ConstraintDrop',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
constraintName: 'PK_test',
|
constraintName: 'PK_test',
|
||||||
reason: 'missing in source',
|
reason: 'missing in source',
|
||||||
@ -444,7 +444,7 @@ describe('schemaDiff', () => {
|
|||||||
describe('primary key', () => {
|
describe('primary key', () => {
|
||||||
it('should skip identical primary key constraints', () => {
|
it('should skip identical primary key constraints', () => {
|
||||||
const constraint: DatabaseConstraint = {
|
const constraint: DatabaseConstraint = {
|
||||||
type: DatabaseConstraintType.PRIMARY_KEY,
|
type: ConstraintType.PRIMARY_KEY,
|
||||||
name: 'PK_test',
|
name: 'PK_test',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
columnNames: ['id'],
|
columnNames: ['id'],
|
||||||
@ -460,7 +460,7 @@ describe('schemaDiff', () => {
|
|||||||
describe('foreign key', () => {
|
describe('foreign key', () => {
|
||||||
it('should skip identical foreign key constraints', () => {
|
it('should skip identical foreign key constraints', () => {
|
||||||
const constraint: DatabaseConstraint = {
|
const constraint: DatabaseConstraint = {
|
||||||
type: DatabaseConstraintType.FOREIGN_KEY,
|
type: ConstraintType.FOREIGN_KEY,
|
||||||
name: 'FK_test',
|
name: 'FK_test',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
columnNames: ['parentId'],
|
columnNames: ['parentId'],
|
||||||
@ -476,7 +476,7 @@ describe('schemaDiff', () => {
|
|||||||
|
|
||||||
it('should drop and recreate when the column changes', () => {
|
it('should drop and recreate when the column changes', () => {
|
||||||
const constraint: DatabaseConstraint = {
|
const constraint: DatabaseConstraint = {
|
||||||
type: DatabaseConstraintType.FOREIGN_KEY,
|
type: ConstraintType.FOREIGN_KEY,
|
||||||
name: 'FK_test',
|
name: 'FK_test',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
columnNames: ['parentId'],
|
columnNames: ['parentId'],
|
||||||
@ -495,7 +495,7 @@ describe('schemaDiff', () => {
|
|||||||
constraintName: 'FK_test',
|
constraintName: 'FK_test',
|
||||||
reason: 'columns are different (parentId vs parentId2)',
|
reason: 'columns are different (parentId vs parentId2)',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
type: 'constraint.drop',
|
type: 'ConstraintDrop',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
constraint: {
|
constraint: {
|
||||||
@ -508,20 +508,20 @@ describe('schemaDiff', () => {
|
|||||||
type: 'foreign-key',
|
type: 'foreign-key',
|
||||||
},
|
},
|
||||||
reason: 'columns are different (parentId vs parentId2)',
|
reason: 'columns are different (parentId vs parentId2)',
|
||||||
type: 'constraint.add',
|
type: 'ConstraintAdd',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should drop and recreate when the ON DELETE action changes', () => {
|
it('should drop and recreate when the ON DELETE action changes', () => {
|
||||||
const constraint: DatabaseConstraint = {
|
const constraint: DatabaseConstraint = {
|
||||||
type: DatabaseConstraintType.FOREIGN_KEY,
|
type: ConstraintType.FOREIGN_KEY,
|
||||||
name: 'FK_test',
|
name: 'FK_test',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
columnNames: ['parentId'],
|
columnNames: ['parentId'],
|
||||||
referenceTableName: 'table2',
|
referenceTableName: 'table2',
|
||||||
referenceColumnNames: ['id'],
|
referenceColumnNames: ['id'],
|
||||||
onDelete: DatabaseActionType.CASCADE,
|
onDelete: ActionType.CASCADE,
|
||||||
synchronize: true,
|
synchronize: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -532,7 +532,7 @@ describe('schemaDiff', () => {
|
|||||||
constraintName: 'FK_test',
|
constraintName: 'FK_test',
|
||||||
reason: 'ON DELETE action is different (CASCADE vs NO ACTION)',
|
reason: 'ON DELETE action is different (CASCADE vs NO ACTION)',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
type: 'constraint.drop',
|
type: 'ConstraintDrop',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
constraint: {
|
constraint: {
|
||||||
@ -540,13 +540,13 @@ describe('schemaDiff', () => {
|
|||||||
name: 'FK_test',
|
name: 'FK_test',
|
||||||
referenceColumnNames: ['id'],
|
referenceColumnNames: ['id'],
|
||||||
referenceTableName: 'table2',
|
referenceTableName: 'table2',
|
||||||
onDelete: DatabaseActionType.CASCADE,
|
onDelete: ActionType.CASCADE,
|
||||||
synchronize: true,
|
synchronize: true,
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
type: 'foreign-key',
|
type: 'foreign-key',
|
||||||
},
|
},
|
||||||
reason: 'ON DELETE action is different (CASCADE vs NO ACTION)',
|
reason: 'ON DELETE action is different (CASCADE vs NO ACTION)',
|
||||||
type: 'constraint.add',
|
type: 'ConstraintAdd',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@ -555,7 +555,7 @@ describe('schemaDiff', () => {
|
|||||||
describe('unique', () => {
|
describe('unique', () => {
|
||||||
it('should skip identical unique constraints', () => {
|
it('should skip identical unique constraints', () => {
|
||||||
const constraint: DatabaseConstraint = {
|
const constraint: DatabaseConstraint = {
|
||||||
type: DatabaseConstraintType.UNIQUE,
|
type: ConstraintType.UNIQUE,
|
||||||
name: 'UQ_test',
|
name: 'UQ_test',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
columnNames: ['id'],
|
columnNames: ['id'],
|
||||||
@ -571,7 +571,7 @@ describe('schemaDiff', () => {
|
|||||||
describe('check', () => {
|
describe('check', () => {
|
||||||
it('should skip identical check constraints', () => {
|
it('should skip identical check constraints', () => {
|
||||||
const constraint: DatabaseConstraint = {
|
const constraint: DatabaseConstraint = {
|
||||||
type: DatabaseConstraintType.CHECK,
|
type: ConstraintType.CHECK,
|
||||||
name: 'CHK_test',
|
name: 'CHK_test',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
expression: 'column1 > 0',
|
expression: 'column1 > 0',
|
||||||
@ -586,7 +586,7 @@ describe('schemaDiff', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('index', () => {
|
describe('index', () => {
|
||||||
describe('index.create', () => {
|
describe('IndexCreate', () => {
|
||||||
it('should detect a new index', () => {
|
it('should detect a new index', () => {
|
||||||
const diff = schemaDiff(
|
const diff = schemaDiff(
|
||||||
fromIndex({
|
fromIndex({
|
||||||
@ -601,7 +601,7 @@ describe('schemaDiff', () => {
|
|||||||
|
|
||||||
expect(diff.items).toEqual([
|
expect(diff.items).toEqual([
|
||||||
{
|
{
|
||||||
type: 'index.create',
|
type: 'IndexCreate',
|
||||||
index: {
|
index: {
|
||||||
name: 'IDX_test',
|
name: 'IDX_test',
|
||||||
columnNames: ['id'],
|
columnNames: ['id'],
|
||||||
@ -615,7 +615,7 @@ describe('schemaDiff', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('index.drop', () => {
|
describe('IndexDrop', () => {
|
||||||
it('should detect an extra index', () => {
|
it('should detect an extra index', () => {
|
||||||
const diff = schemaDiff(
|
const diff = schemaDiff(
|
||||||
fromIndex(),
|
fromIndex(),
|
||||||
@ -630,7 +630,7 @@ describe('schemaDiff', () => {
|
|||||||
|
|
||||||
expect(diff.items).toEqual([
|
expect(diff.items).toEqual([
|
||||||
{
|
{
|
||||||
type: 'index.drop',
|
type: 'IndexDrop',
|
||||||
indexName: 'IDX_test',
|
indexName: 'IDX_test',
|
||||||
reason: 'missing in source',
|
reason: 'missing in source',
|
||||||
},
|
},
|
||||||
@ -650,12 +650,12 @@ describe('schemaDiff', () => {
|
|||||||
|
|
||||||
expect(diff.items).toEqual([
|
expect(diff.items).toEqual([
|
||||||
{
|
{
|
||||||
type: 'index.drop',
|
type: 'IndexDrop',
|
||||||
indexName: 'IDX_test',
|
indexName: 'IDX_test',
|
||||||
reason: 'uniqueness is different (true vs false)',
|
reason: 'uniqueness is different (true vs false)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'index.create',
|
type: 'IndexCreate',
|
||||||
index,
|
index,
|
||||||
reason: 'uniqueness is different (true vs false)',
|
reason: 'uniqueness is different (true vs false)',
|
||||||
},
|
},
|
121
server/src/sql-tools/schema-diff.ts
Normal file
121
server/src/sql-tools/schema-diff.ts
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import { compareEnums } from 'src/sql-tools/comparers/enum.comparer';
|
||||||
|
import { compareExtensions } from 'src/sql-tools/comparers/extension.comparer';
|
||||||
|
import { compareFunctions } from 'src/sql-tools/comparers/function.comparer';
|
||||||
|
import { compareParameters } from 'src/sql-tools/comparers/parameter.comparer';
|
||||||
|
import { compareTables } from 'src/sql-tools/comparers/table.comparer';
|
||||||
|
import { compare } from 'src/sql-tools/helpers';
|
||||||
|
import { transformers } from 'src/sql-tools/transformers';
|
||||||
|
import {
|
||||||
|
ConstraintType,
|
||||||
|
DatabaseSchema,
|
||||||
|
SchemaDiff,
|
||||||
|
SchemaDiffOptions,
|
||||||
|
SchemaDiffToSqlOptions,
|
||||||
|
} from 'src/sql-tools/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the difference between two database schemas
|
||||||
|
*/
|
||||||
|
export const schemaDiff = (source: DatabaseSchema, target: DatabaseSchema, options: SchemaDiffOptions = {}) => {
|
||||||
|
const items = [
|
||||||
|
...compare(source.parameters, target.parameters, options.parameters, compareParameters),
|
||||||
|
...compare(source.extensions, target.extensions, options.extension, compareExtensions),
|
||||||
|
...compare(source.functions, target.functions, options.functions, compareFunctions),
|
||||||
|
...compare(source.enums, target.enums, options.enums, compareEnums),
|
||||||
|
...compare(source.tables, target.tables, options.tables, compareTables),
|
||||||
|
];
|
||||||
|
|
||||||
|
type SchemaName = SchemaDiff['type'];
|
||||||
|
const itemMap: Record<SchemaName, SchemaDiff[]> = {
|
||||||
|
EnumCreate: [],
|
||||||
|
EnumDrop: [],
|
||||||
|
ExtensionCreate: [],
|
||||||
|
ExtensionDrop: [],
|
||||||
|
FunctionCreate: [],
|
||||||
|
FunctionDrop: [],
|
||||||
|
TableCreate: [],
|
||||||
|
TableDrop: [],
|
||||||
|
ColumnAdd: [],
|
||||||
|
ColumnAlter: [],
|
||||||
|
ColumnDrop: [],
|
||||||
|
ConstraintAdd: [],
|
||||||
|
ConstraintDrop: [],
|
||||||
|
IndexCreate: [],
|
||||||
|
IndexDrop: [],
|
||||||
|
TriggerCreate: [],
|
||||||
|
TriggerDrop: [],
|
||||||
|
ParameterSet: [],
|
||||||
|
ParameterReset: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
itemMap[item.type].push(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
const constraintAdds = itemMap.ConstraintAdd.filter((item) => item.type === 'ConstraintAdd');
|
||||||
|
|
||||||
|
const orderedItems = [
|
||||||
|
...itemMap.ExtensionCreate,
|
||||||
|
...itemMap.FunctionCreate,
|
||||||
|
...itemMap.ParameterSet,
|
||||||
|
...itemMap.ParameterReset,
|
||||||
|
...itemMap.EnumCreate,
|
||||||
|
...itemMap.TriggerDrop,
|
||||||
|
...itemMap.IndexDrop,
|
||||||
|
...itemMap.ConstraintDrop,
|
||||||
|
...itemMap.TableCreate,
|
||||||
|
...itemMap.ColumnAlter,
|
||||||
|
...itemMap.ColumnAdd,
|
||||||
|
...constraintAdds.filter(({ constraint }) => constraint.type === ConstraintType.PRIMARY_KEY),
|
||||||
|
...constraintAdds.filter(({ constraint }) => constraint.type === ConstraintType.FOREIGN_KEY),
|
||||||
|
...constraintAdds.filter(({ constraint }) => constraint.type === ConstraintType.UNIQUE),
|
||||||
|
...constraintAdds.filter(({ constraint }) => constraint.type === ConstraintType.CHECK),
|
||||||
|
...itemMap.IndexCreate,
|
||||||
|
...itemMap.TriggerCreate,
|
||||||
|
...itemMap.ColumnDrop,
|
||||||
|
...itemMap.TableDrop,
|
||||||
|
...itemMap.EnumDrop,
|
||||||
|
...itemMap.FunctionDrop,
|
||||||
|
];
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: orderedItems,
|
||||||
|
asSql: (options?: SchemaDiffToSqlOptions) => schemaDiffToSql(orderedItems, options),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 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];
|
||||||
|
};
|
46
server/src/sql-tools/schema-from-code.spec.ts
Normal file
46
server/src/sql-tools/schema-from-code.spec.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { readdirSync } from 'node:fs';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import { schemaFromCode } from 'src/sql-tools/schema-from-code';
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
describe(schemaFromCode.name, () => {
|
||||||
|
it('should work', () => {
|
||||||
|
expect(schemaFromCode({ reset: true })).toEqual({
|
||||||
|
databaseName: 'postgres',
|
||||||
|
schemaName: 'public',
|
||||||
|
functions: [],
|
||||||
|
enums: [],
|
||||||
|
extensions: [],
|
||||||
|
parameters: [],
|
||||||
|
tables: [],
|
||||||
|
warnings: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('test files', () => {
|
||||||
|
const errorStubs = readdirSync('test/sql-tools/errors', { withFileTypes: true });
|
||||||
|
for (const file of errorStubs) {
|
||||||
|
const filePath = join(file.parentPath, file.name);
|
||||||
|
it(filePath, async () => {
|
||||||
|
const module = await import(filePath);
|
||||||
|
expect(module.message).toBeDefined();
|
||||||
|
expect(() => schemaFromCode({ reset: true })).toThrowError(module.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const stubs = readdirSync('test/sql-tools', { withFileTypes: true });
|
||||||
|
for (const file of stubs) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = join(file.parentPath, file.name);
|
||||||
|
it(filePath, async () => {
|
||||||
|
const module = await import(filePath);
|
||||||
|
expect(module.description).toBeDefined();
|
||||||
|
expect(module.schema).toBeDefined();
|
||||||
|
expect(schemaFromCode({ reset: true }), module.description).toEqual(module.schema);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
29
server/src/sql-tools/schema-from-code.ts
Normal file
29
server/src/sql-tools/schema-from-code.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { processors } from 'src/sql-tools/processors';
|
||||||
|
import { getRegisteredItems, resetRegisteredItems } from 'src/sql-tools/register';
|
||||||
|
import { SchemaBuilder } from 'src/sql-tools/schema-builder';
|
||||||
|
import { SchemaFromCodeOptions } from 'src/sql-tools/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load schema from code (decorators, etc)
|
||||||
|
*/
|
||||||
|
export const schemaFromCode = (options: SchemaFromCodeOptions = {}) => {
|
||||||
|
try {
|
||||||
|
const globalOptions = {
|
||||||
|
createForeignKeyIndexes: options.createForeignKeyIndexes ?? true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const builder = new SchemaBuilder(options);
|
||||||
|
const items = getRegisteredItems();
|
||||||
|
for (const processor of processors) {
|
||||||
|
processor(builder, items, globalOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newSchema = builder.build();
|
||||||
|
|
||||||
|
return newSchema;
|
||||||
|
} finally {
|
||||||
|
if (options.reset) {
|
||||||
|
resetRegisteredItems();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
33
server/src/sql-tools/schema-from-database.ts
Normal file
33
server/src/sql-tools/schema-from-database.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { Kysely } from 'kysely';
|
||||||
|
import { PostgresJSDialect } from 'kysely-postgres-js';
|
||||||
|
import { Sql } from 'postgres';
|
||||||
|
import { readers } from 'src/sql-tools/readers';
|
||||||
|
import { DatabaseSchema, PostgresDB, SchemaFromDatabaseOptions } from 'src/sql-tools/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load schema from a database url
|
||||||
|
*/
|
||||||
|
export const schemaFromDatabase = async (
|
||||||
|
postgres: Sql,
|
||||||
|
options: SchemaFromDatabaseOptions = {},
|
||||||
|
): Promise<DatabaseSchema> => {
|
||||||
|
const schema: DatabaseSchema = {
|
||||||
|
databaseName: 'immich',
|
||||||
|
schemaName: options.schemaName || 'public',
|
||||||
|
parameters: [],
|
||||||
|
functions: [],
|
||||||
|
enums: [],
|
||||||
|
extensions: [],
|
||||||
|
tables: [],
|
||||||
|
warnings: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const db = new Kysely<PostgresDB>({ dialect: new PostgresJSDialect({ postgres }) });
|
||||||
|
for (const reader of readers) {
|
||||||
|
await reader(schema, db);
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.destroy();
|
||||||
|
|
||||||
|
return schema;
|
||||||
|
};
|
@ -1,21 +0,0 @@
|
|||||||
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`]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,59 +0,0 @@
|
|||||||
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];
|
|
||||||
};
|
|
@ -1,12 +1,12 @@
|
|||||||
import { transformColumns } from 'src/sql-tools/to-sql/transformers/column.transformer';
|
import { transformColumns } from 'src/sql-tools/transformers/column.transformer';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
describe(transformColumns.name, () => {
|
describe(transformColumns.name, () => {
|
||||||
describe('column.add', () => {
|
describe('ColumnAdd', () => {
|
||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
expect(
|
expect(
|
||||||
transformColumns({
|
transformColumns({
|
||||||
type: 'column.add',
|
type: 'ColumnAdd',
|
||||||
column: {
|
column: {
|
||||||
name: 'column1',
|
name: 'column1',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
@ -23,7 +23,7 @@ describe(transformColumns.name, () => {
|
|||||||
it('should add a nullable column', () => {
|
it('should add a nullable column', () => {
|
||||||
expect(
|
expect(
|
||||||
transformColumns({
|
transformColumns({
|
||||||
type: 'column.add',
|
type: 'ColumnAdd',
|
||||||
column: {
|
column: {
|
||||||
name: 'column1',
|
name: 'column1',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
@ -40,7 +40,7 @@ describe(transformColumns.name, () => {
|
|||||||
it('should add a column with an enum type', () => {
|
it('should add a column with an enum type', () => {
|
||||||
expect(
|
expect(
|
||||||
transformColumns({
|
transformColumns({
|
||||||
type: 'column.add',
|
type: 'ColumnAdd',
|
||||||
column: {
|
column: {
|
||||||
name: 'column1',
|
name: 'column1',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
@ -58,7 +58,7 @@ describe(transformColumns.name, () => {
|
|||||||
it('should add a column that is an array type', () => {
|
it('should add a column that is an array type', () => {
|
||||||
expect(
|
expect(
|
||||||
transformColumns({
|
transformColumns({
|
||||||
type: 'column.add',
|
type: 'ColumnAdd',
|
||||||
column: {
|
column: {
|
||||||
name: 'column1',
|
name: 'column1',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
@ -73,11 +73,11 @@ describe(transformColumns.name, () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('column.alter', () => {
|
describe('ColumnAlter', () => {
|
||||||
it('should make a column nullable', () => {
|
it('should make a column nullable', () => {
|
||||||
expect(
|
expect(
|
||||||
transformColumns({
|
transformColumns({
|
||||||
type: 'column.alter',
|
type: 'ColumnAlter',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
columnName: 'column1',
|
columnName: 'column1',
|
||||||
changes: { nullable: true },
|
changes: { nullable: true },
|
||||||
@ -89,7 +89,7 @@ describe(transformColumns.name, () => {
|
|||||||
it('should make a column non-nullable', () => {
|
it('should make a column non-nullable', () => {
|
||||||
expect(
|
expect(
|
||||||
transformColumns({
|
transformColumns({
|
||||||
type: 'column.alter',
|
type: 'ColumnAlter',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
columnName: 'column1',
|
columnName: 'column1',
|
||||||
changes: { nullable: false },
|
changes: { nullable: false },
|
||||||
@ -101,7 +101,7 @@ describe(transformColumns.name, () => {
|
|||||||
it('should update the default value', () => {
|
it('should update the default value', () => {
|
||||||
expect(
|
expect(
|
||||||
transformColumns({
|
transformColumns({
|
||||||
type: 'column.alter',
|
type: 'ColumnAlter',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
columnName: 'column1',
|
columnName: 'column1',
|
||||||
changes: { default: 'uuid_generate_v4()' },
|
changes: { default: 'uuid_generate_v4()' },
|
||||||
@ -111,11 +111,11 @@ describe(transformColumns.name, () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('column.drop', () => {
|
describe('ColumnDrop', () => {
|
||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
expect(
|
expect(
|
||||||
transformColumns({
|
transformColumns({
|
||||||
type: 'column.drop',
|
type: 'ColumnDrop',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
columnName: 'column1',
|
columnName: 'column1',
|
||||||
reason: 'unknown',
|
reason: 'unknown',
|
@ -1,18 +1,18 @@
|
|||||||
import { asColumnComment, getColumnModifiers, getColumnType } from 'src/sql-tools/helpers';
|
import { asColumnComment, getColumnModifiers, getColumnType } from 'src/sql-tools/helpers';
|
||||||
import { SqlTransformer } from 'src/sql-tools/to-sql/transformers/types';
|
import { SqlTransformer } from 'src/sql-tools/transformers/types';
|
||||||
import { ColumnChanges, DatabaseColumn, SchemaDiff } from 'src/sql-tools/types';
|
import { ColumnChanges, DatabaseColumn, SchemaDiff } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export const transformColumns: SqlTransformer = (item: SchemaDiff) => {
|
export const transformColumns: SqlTransformer = (item: SchemaDiff) => {
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
case 'column.add': {
|
case 'ColumnAdd': {
|
||||||
return asColumnAdd(item.column);
|
return asColumnAdd(item.column);
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'column.alter': {
|
case 'ColumnAlter': {
|
||||||
return asColumnAlter(item.tableName, item.columnName, item.changes);
|
return asColumnAlter(item.tableName, item.columnName, item.changes);
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'column.drop': {
|
case 'ColumnDrop': {
|
||||||
return asColumnDrop(item.tableName, item.columnName);
|
return asColumnDrop(item.tableName, item.columnName);
|
||||||
}
|
}
|
||||||
|
|
@ -1,16 +1,16 @@
|
|||||||
import { transformConstraints } from 'src/sql-tools/to-sql/transformers/constraint.transformer';
|
import { transformConstraints } from 'src/sql-tools/transformers/constraint.transformer';
|
||||||
import { DatabaseConstraintType } from 'src/sql-tools/types';
|
import { ConstraintType } from 'src/sql-tools/types';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
describe(transformConstraints.name, () => {
|
describe(transformConstraints.name, () => {
|
||||||
describe('constraint.add', () => {
|
describe('ConstraintAdd', () => {
|
||||||
describe('primary keys', () => {
|
describe('primary keys', () => {
|
||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
expect(
|
expect(
|
||||||
transformConstraints({
|
transformConstraints({
|
||||||
type: 'constraint.add',
|
type: 'ConstraintAdd',
|
||||||
constraint: {
|
constraint: {
|
||||||
type: DatabaseConstraintType.PRIMARY_KEY,
|
type: ConstraintType.PRIMARY_KEY,
|
||||||
name: 'PK_test',
|
name: 'PK_test',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
columnNames: ['id'],
|
columnNames: ['id'],
|
||||||
@ -26,9 +26,9 @@ describe(transformConstraints.name, () => {
|
|||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
expect(
|
expect(
|
||||||
transformConstraints({
|
transformConstraints({
|
||||||
type: 'constraint.add',
|
type: 'ConstraintAdd',
|
||||||
constraint: {
|
constraint: {
|
||||||
type: DatabaseConstraintType.FOREIGN_KEY,
|
type: ConstraintType.FOREIGN_KEY,
|
||||||
name: 'FK_test',
|
name: 'FK_test',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
columnNames: ['parentId'],
|
columnNames: ['parentId'],
|
||||||
@ -48,9 +48,9 @@ describe(transformConstraints.name, () => {
|
|||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
expect(
|
expect(
|
||||||
transformConstraints({
|
transformConstraints({
|
||||||
type: 'constraint.add',
|
type: 'ConstraintAdd',
|
||||||
constraint: {
|
constraint: {
|
||||||
type: DatabaseConstraintType.UNIQUE,
|
type: ConstraintType.UNIQUE,
|
||||||
name: 'UQ_test',
|
name: 'UQ_test',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
columnNames: ['id'],
|
columnNames: ['id'],
|
||||||
@ -66,9 +66,9 @@ describe(transformConstraints.name, () => {
|
|||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
expect(
|
expect(
|
||||||
transformConstraints({
|
transformConstraints({
|
||||||
type: 'constraint.add',
|
type: 'ConstraintAdd',
|
||||||
constraint: {
|
constraint: {
|
||||||
type: DatabaseConstraintType.CHECK,
|
type: ConstraintType.CHECK,
|
||||||
name: 'CHK_test',
|
name: 'CHK_test',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
expression: '"id" IS NOT NULL',
|
expression: '"id" IS NOT NULL',
|
||||||
@ -81,11 +81,11 @@ describe(transformConstraints.name, () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('constraint.drop', () => {
|
describe('ConstraintDrop', () => {
|
||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
expect(
|
expect(
|
||||||
transformConstraints({
|
transformConstraints({
|
||||||
type: 'constraint.drop',
|
type: 'ConstraintDrop',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
constraintName: 'PK_test',
|
constraintName: 'PK_test',
|
||||||
reason: 'unknown',
|
reason: 'unknown',
|
@ -1,14 +1,14 @@
|
|||||||
import { asColumnList } from 'src/sql-tools/helpers';
|
import { asColumnList } from 'src/sql-tools/helpers';
|
||||||
import { SqlTransformer } from 'src/sql-tools/to-sql/transformers/types';
|
import { SqlTransformer } from 'src/sql-tools/transformers/types';
|
||||||
import { DatabaseActionType, DatabaseConstraint, DatabaseConstraintType, SchemaDiff } from 'src/sql-tools/types';
|
import { ActionType, ConstraintType, DatabaseConstraint, SchemaDiff } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export const transformConstraints: SqlTransformer = (item: SchemaDiff) => {
|
export const transformConstraints: SqlTransformer = (item: SchemaDiff) => {
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
case 'constraint.add': {
|
case 'ConstraintAdd': {
|
||||||
return asConstraintAdd(item.constraint);
|
return asConstraintAdd(item.constraint);
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'constraint.drop': {
|
case 'ConstraintDrop': {
|
||||||
return asConstraintDrop(item.tableName, item.constraintName);
|
return asConstraintDrop(item.tableName, item.constraintName);
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
@ -17,18 +17,18 @@ export const transformConstraints: SqlTransformer = (item: SchemaDiff) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const withAction = (constraint: { onDelete?: DatabaseActionType; onUpdate?: DatabaseActionType }) =>
|
const withAction = (constraint: { onDelete?: ActionType; onUpdate?: ActionType }) =>
|
||||||
` ON UPDATE ${constraint.onUpdate ?? DatabaseActionType.NO_ACTION} ON DELETE ${constraint.onDelete ?? DatabaseActionType.NO_ACTION}`;
|
` ON UPDATE ${constraint.onUpdate ?? ActionType.NO_ACTION} ON DELETE ${constraint.onDelete ?? ActionType.NO_ACTION}`;
|
||||||
|
|
||||||
export const asConstraintAdd = (constraint: DatabaseConstraint): string | string[] => {
|
export const asConstraintAdd = (constraint: DatabaseConstraint): string | string[] => {
|
||||||
const base = `ALTER TABLE "${constraint.tableName}" ADD CONSTRAINT "${constraint.name}"`;
|
const base = `ALTER TABLE "${constraint.tableName}" ADD CONSTRAINT "${constraint.name}"`;
|
||||||
switch (constraint.type) {
|
switch (constraint.type) {
|
||||||
case DatabaseConstraintType.PRIMARY_KEY: {
|
case ConstraintType.PRIMARY_KEY: {
|
||||||
const columnNames = asColumnList(constraint.columnNames);
|
const columnNames = asColumnList(constraint.columnNames);
|
||||||
return `${base} PRIMARY KEY (${columnNames});`;
|
return `${base} PRIMARY KEY (${columnNames});`;
|
||||||
}
|
}
|
||||||
|
|
||||||
case DatabaseConstraintType.FOREIGN_KEY: {
|
case ConstraintType.FOREIGN_KEY: {
|
||||||
const columnNames = asColumnList(constraint.columnNames);
|
const columnNames = asColumnList(constraint.columnNames);
|
||||||
const referenceColumnNames = asColumnList(constraint.referenceColumnNames);
|
const referenceColumnNames = asColumnList(constraint.referenceColumnNames);
|
||||||
return (
|
return (
|
||||||
@ -38,12 +38,12 @@ export const asConstraintAdd = (constraint: DatabaseConstraint): string | string
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
case DatabaseConstraintType.UNIQUE: {
|
case ConstraintType.UNIQUE: {
|
||||||
const columnNames = asColumnList(constraint.columnNames);
|
const columnNames = asColumnList(constraint.columnNames);
|
||||||
return `${base} UNIQUE (${columnNames});`;
|
return `${base} UNIQUE (${columnNames});`;
|
||||||
}
|
}
|
||||||
|
|
||||||
case DatabaseConstraintType.CHECK: {
|
case ConstraintType.CHECK: {
|
||||||
return `${base} CHECK (${constraint.expression});`;
|
return `${base} CHECK (${constraint.expression});`;
|
||||||
}
|
}
|
||||||
|
|
@ -1,13 +1,13 @@
|
|||||||
import { SqlTransformer } from 'src/sql-tools/to-sql/transformers/types';
|
import { SqlTransformer } from 'src/sql-tools/transformers/types';
|
||||||
import { DatabaseEnum, SchemaDiff } from 'src/sql-tools/types';
|
import { DatabaseEnum, SchemaDiff } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export const transformEnums: SqlTransformer = (item: SchemaDiff) => {
|
export const transformEnums: SqlTransformer = (item: SchemaDiff) => {
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
case 'enum.create': {
|
case 'EnumCreate': {
|
||||||
return asEnumCreate(item.enum);
|
return asEnumCreate(item.enum);
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'enum.drop': {
|
case 'EnumDrop': {
|
||||||
return asEnumDrop(item.enumName);
|
return asEnumDrop(item.enumName);
|
||||||
}
|
}
|
||||||
|
|
@ -1,12 +1,12 @@
|
|||||||
import { transformExtensions } from 'src/sql-tools/to-sql/transformers/extension.transformer';
|
import { transformExtensions } from 'src/sql-tools/transformers/extension.transformer';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
describe(transformExtensions.name, () => {
|
describe(transformExtensions.name, () => {
|
||||||
describe('extension.drop', () => {
|
describe('ExtensionDrop', () => {
|
||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
expect(
|
expect(
|
||||||
transformExtensions({
|
transformExtensions({
|
||||||
type: 'extension.drop',
|
type: 'ExtensionDrop',
|
||||||
extensionName: 'cube',
|
extensionName: 'cube',
|
||||||
reason: 'unknown',
|
reason: 'unknown',
|
||||||
}),
|
}),
|
||||||
@ -14,11 +14,11 @@ describe(transformExtensions.name, () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('extension.create', () => {
|
describe('ExtensionCreate', () => {
|
||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
expect(
|
expect(
|
||||||
transformExtensions({
|
transformExtensions({
|
||||||
type: 'extension.create',
|
type: 'ExtensionCreate',
|
||||||
extension: {
|
extension: {
|
||||||
name: 'cube',
|
name: 'cube',
|
||||||
synchronize: true,
|
synchronize: true,
|
@ -1,13 +1,13 @@
|
|||||||
import { SqlTransformer } from 'src/sql-tools/to-sql/transformers/types';
|
import { SqlTransformer } from 'src/sql-tools/transformers/types';
|
||||||
import { DatabaseExtension, SchemaDiff } from 'src/sql-tools/types';
|
import { DatabaseExtension, SchemaDiff } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export const transformExtensions: SqlTransformer = (item: SchemaDiff) => {
|
export const transformExtensions: SqlTransformer = (item: SchemaDiff) => {
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
case 'extension.create': {
|
case 'ExtensionCreate': {
|
||||||
return asExtensionCreate(item.extension);
|
return asExtensionCreate(item.extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'extension.drop': {
|
case 'ExtensionDrop': {
|
||||||
return asExtensionDrop(item.extensionName);
|
return asExtensionDrop(item.extensionName);
|
||||||
}
|
}
|
||||||
|
|
@ -1,12 +1,12 @@
|
|||||||
import { transformFunctions } from 'src/sql-tools/to-sql/transformers/function.transformer';
|
import { transformFunctions } from 'src/sql-tools/transformers/function.transformer';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
describe(transformFunctions.name, () => {
|
describe(transformFunctions.name, () => {
|
||||||
describe('function.drop', () => {
|
describe('FunctionDrop', () => {
|
||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
expect(
|
expect(
|
||||||
transformFunctions({
|
transformFunctions({
|
||||||
type: 'function.drop',
|
type: 'FunctionDrop',
|
||||||
functionName: 'test_func',
|
functionName: 'test_func',
|
||||||
reason: 'unknown',
|
reason: 'unknown',
|
||||||
}),
|
}),
|
@ -1,13 +1,13 @@
|
|||||||
import { SqlTransformer } from 'src/sql-tools/to-sql/transformers/types';
|
import { SqlTransformer } from 'src/sql-tools/transformers/types';
|
||||||
import { DatabaseFunction, SchemaDiff } from 'src/sql-tools/types';
|
import { DatabaseFunction, SchemaDiff } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export const transformFunctions: SqlTransformer = (item: SchemaDiff) => {
|
export const transformFunctions: SqlTransformer = (item: SchemaDiff) => {
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
case 'function.create': {
|
case 'FunctionCreate': {
|
||||||
return asFunctionCreate(item.function);
|
return asFunctionCreate(item.function);
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'function.drop': {
|
case 'FunctionDrop': {
|
||||||
return asFunctionDrop(item.functionName);
|
return asFunctionDrop(item.functionName);
|
||||||
}
|
}
|
||||||
|
|
@ -1,12 +1,12 @@
|
|||||||
import { transformIndexes } from 'src/sql-tools/to-sql/transformers/index.transformer';
|
import { transformIndexes } from 'src/sql-tools/transformers/index.transformer';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
describe(transformIndexes.name, () => {
|
describe(transformIndexes.name, () => {
|
||||||
describe('index.create', () => {
|
describe('IndexCreate', () => {
|
||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
expect(
|
expect(
|
||||||
transformIndexes({
|
transformIndexes({
|
||||||
type: 'index.create',
|
type: 'IndexCreate',
|
||||||
index: {
|
index: {
|
||||||
name: 'IDX_test',
|
name: 'IDX_test',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
@ -22,7 +22,7 @@ describe(transformIndexes.name, () => {
|
|||||||
it('should create an unique index', () => {
|
it('should create an unique index', () => {
|
||||||
expect(
|
expect(
|
||||||
transformIndexes({
|
transformIndexes({
|
||||||
type: 'index.create',
|
type: 'IndexCreate',
|
||||||
index: {
|
index: {
|
||||||
name: 'IDX_test',
|
name: 'IDX_test',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
@ -38,7 +38,7 @@ describe(transformIndexes.name, () => {
|
|||||||
it('should create an index with a custom expression', () => {
|
it('should create an index with a custom expression', () => {
|
||||||
expect(
|
expect(
|
||||||
transformIndexes({
|
transformIndexes({
|
||||||
type: 'index.create',
|
type: 'IndexCreate',
|
||||||
index: {
|
index: {
|
||||||
name: 'IDX_test',
|
name: 'IDX_test',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
@ -54,7 +54,7 @@ describe(transformIndexes.name, () => {
|
|||||||
it('should create an index with a where clause', () => {
|
it('should create an index with a where clause', () => {
|
||||||
expect(
|
expect(
|
||||||
transformIndexes({
|
transformIndexes({
|
||||||
type: 'index.create',
|
type: 'IndexCreate',
|
||||||
index: {
|
index: {
|
||||||
name: 'IDX_test',
|
name: 'IDX_test',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
@ -71,7 +71,7 @@ describe(transformIndexes.name, () => {
|
|||||||
it('should create an index with a custom expression', () => {
|
it('should create an index with a custom expression', () => {
|
||||||
expect(
|
expect(
|
||||||
transformIndexes({
|
transformIndexes({
|
||||||
type: 'index.create',
|
type: 'IndexCreate',
|
||||||
index: {
|
index: {
|
||||||
name: 'IDX_test',
|
name: 'IDX_test',
|
||||||
tableName: 'table1',
|
tableName: 'table1',
|
||||||
@ -86,11 +86,11 @@ describe(transformIndexes.name, () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('index.drop', () => {
|
describe('IndexDrop', () => {
|
||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
expect(
|
expect(
|
||||||
transformIndexes({
|
transformIndexes({
|
||||||
type: 'index.drop',
|
type: 'IndexDrop',
|
||||||
indexName: 'IDX_test',
|
indexName: 'IDX_test',
|
||||||
reason: 'unknown',
|
reason: 'unknown',
|
||||||
}),
|
}),
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user