diff --git a/server/src/sql-tools/from-code/decorators/foreign-key-column.decorator.ts b/server/src/sql-tools/from-code/decorators/foreign-key-column.decorator.ts index beb3aa6fd6..d2b7d623a7 100644 --- a/server/src/sql-tools/from-code/decorators/foreign-key-column.decorator.ts +++ b/server/src/sql-tools/from-code/decorators/foreign-key-column.decorator.ts @@ -1,11 +1,10 @@ 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'; -type Action = 'CASCADE' | 'SET NULL' | 'SET DEFAULT' | 'RESTRICT' | 'NO ACTION'; - export type ForeignKeyColumnOptions = ColumnBaseOptions & { - onUpdate?: Action; - onDelete?: Action; + onUpdate?: ForeignKeyAction; + onDelete?: ForeignKeyAction; constraintName?: string; }; diff --git a/server/src/sql-tools/from-code/decorators/foreign-key-constraint.decorator.ts b/server/src/sql-tools/from-code/decorators/foreign-key-constraint.decorator.ts new file mode 100644 index 0000000000..7d18a9fda0 --- /dev/null +++ b/server/src/sql-tools/from-code/decorators/foreign-key-constraint.decorator.ts @@ -0,0 +1,23 @@ +import { register } from 'src/sql-tools/from-code/register'; + +export type ForeignKeyAction = 'CASCADE' | 'SET NULL' | 'SET DEFAULT' | 'RESTRICT' | 'NO ACTION'; + +export type ForeignKeyConstraintOptions = { + name?: string; + index?: boolean; + indexName?: string; + columns: string[]; + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type + referenceTable: () => Function; + referenceColumns?: string[]; + onUpdate?: ForeignKeyAction; + onDelete?: ForeignKeyAction; + synchronize?: boolean; +}; + +export const ForeignKeyConstraint = (options: ForeignKeyConstraintOptions): ClassDecorator => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type + return (target: Function) => { + register({ type: 'foreignKeyConstraint', item: { object: target, options } }); + }; +}; diff --git a/server/src/sql-tools/from-code/index.ts b/server/src/sql-tools/from-code/index.ts index 95f1dbb22d..d820f236df 100644 --- a/server/src/sql-tools/from-code/index.ts +++ b/server/src/sql-tools/from-code/index.ts @@ -5,7 +5,8 @@ import { processConfigurationParameters } from 'src/sql-tools/from-code/processo 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 { processForeignKeyConstraints } from 'src/sql-tools/from-code/processors/foreign-key-constriant.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'; @@ -32,10 +33,11 @@ const processors: Processor[] = [ processFunctions, processTables, processColumns, + processForeignKeyColumns, + processForeignKeyConstraints, processUniqueConstraints, processCheckConstraints, processPrimaryKeyConstraints, - processForeignKeyConstraints, processIndexes, processTriggers, ]; diff --git a/server/src/sql-tools/from-code/processors/column.processor.ts b/server/src/sql-tools/from-code/processors/column.processor.ts index e8c2544f87..6fff3070e3 100644 --- a/server/src/sql-tools/from-code/processors/column.processor.ts +++ b/server/src/sql-tools/from-code/processors/column.processor.ts @@ -1,7 +1,7 @@ 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 { asMetadataKey, fromColumnValue } from 'src/sql-tools/helpers'; +import { addWarning, asMetadataKey, fromColumnValue } from 'src/sql-tools/helpers'; import { DatabaseColumn } from 'src/sql-tools/types'; export const processColumns: Processor = (builder, items) => { @@ -81,7 +81,7 @@ export const onMissingColumn = ( propertyName?: symbol | string, ) => { const label = object.constructor.name + (propertyName ? '.' + String(propertyName) : ''); - builder.warnings.push(`[${context}] Unable to find column (${label})`); + addWarning(builder, context, `Unable to find column (${label})`); }; const METADATA_KEY = asMetadataKey('table-metadata'); diff --git a/server/src/sql-tools/from-code/processors/foreign-key-constriant.processor.ts b/server/src/sql-tools/from-code/processors/foreign-key-column.processor.ts similarity index 84% rename from server/src/sql-tools/from-code/processors/foreign-key-constriant.processor.ts rename to server/src/sql-tools/from-code/processors/foreign-key-column.processor.ts index 612b74c30f..d706763d1d 100644 --- a/server/src/sql-tools/from-code/processors/foreign-key-constriant.processor.ts +++ b/server/src/sql-tools/from-code/processors/foreign-key-column.processor.ts @@ -1,10 +1,10 @@ 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 { asForeignKeyConstraintName, asKey } from 'src/sql-tools/helpers'; import { DatabaseActionType, DatabaseConstraintType } from 'src/sql-tools/types'; -export const processForeignKeyConstraints: Processor = (builder, items) => { +export const processForeignKeyColumns: Processor = (builder, items) => { for (const { item: { object, propertyName, options, target }, } of items.filter((item) => item.type === 'foreignKeyColumn')) { @@ -34,13 +34,16 @@ export const processForeignKeyConstraints: Processor = (builder, items) => { column.type = referenceColumns[0].type; } + const referenceColumnNames = referenceColumns.map((column) => column.name); + const name = options.constraintName || asForeignKeyConstraintName(table.name, columnNames); + table.constraints.push({ - name: options.constraintName || asForeignKeyConstraintName(table.name, columnNames), + name, tableName: table.name, columnNames, type: DatabaseConstraintType.FOREIGN_KEY, referenceTableName: referenceTable.name, - referenceColumnNames: referenceColumns.map((column) => column.name), + referenceColumnNames, onUpdate: options.onUpdate as DatabaseActionType, onDelete: options.onDelete as DatabaseActionType, synchronize: options.synchronize ?? true, @@ -58,5 +61,4 @@ export const processForeignKeyConstraints: Processor = (builder, items) => { } }; -const asForeignKeyConstraintName = (table: string, columns: string[]) => asKey('FK_', table, columns); const asRelationKeyConstraintName = (table: string, columns: string[]) => asKey('REL_', table, columns); diff --git a/server/src/sql-tools/from-code/processors/foreign-key-constraint.processor.ts b/server/src/sql-tools/from-code/processors/foreign-key-constraint.processor.ts new file mode 100644 index 0000000000..e88297b6c6 --- /dev/null +++ b/server/src/sql-tools/from-code/processors/foreign-key-constraint.processor.ts @@ -0,0 +1,86 @@ +import { onMissingTable, resolveTable } from 'src/sql-tools/from-code/processors/table.processor'; +import { Processor } from 'src/sql-tools/from-code/processors/type'; +import { addWarning, asForeignKeyConstraintName, asIndexName } from 'src/sql-tools/helpers'; +import { DatabaseActionType, DatabaseConstraintType } from 'src/sql-tools/types'; + +export const processForeignKeyConstraints: Processor = (builder, items, config) => { + for (const { + item: { object, options }, + } of items.filter((item) => item.type === 'foreignKeyConstraint')) { + const table = resolveTable(builder, object); + if (!table) { + onMissingTable(builder, '@ForeignKeyConstraint', { name: 'referenceTable' }); + continue; + } + + const referenceTable = resolveTable(builder, options.referenceTable()); + if (!referenceTable) { + const referenceTableName = options.referenceTable()?.name; + addWarning( + builder, + '@ForeignKeyConstraint.referenceTable', + `Unable to find table` + (referenceTableName ? ` (${referenceTableName})` : ''), + ); + continue; + } + + let missingColumn = false; + + for (const columnName of options.columns) { + if (!table.columns.some(({ name }) => name === columnName)) { + addWarning( + builder, + '@ForeignKeyConstraint.columns', + `Unable to find column (${table.metadata.object.name}.${columnName})`, + ); + missingColumn = true; + } + } + + for (const columnName of options.referenceColumns || []) { + if (!referenceTable.columns.some(({ name }) => name === columnName)) { + addWarning( + builder, + '@ForeignKeyConstraint.referenceColumns', + `Unable to find column (${referenceTable.metadata.object.name}.${columnName})`, + ); + missingColumn = true; + } + } + + if (missingColumn) { + continue; + } + + const referenceColumns = + options.referenceColumns || referenceTable.columns.filter(({ primary }) => primary).map(({ name }) => name); + + const name = options.name || asForeignKeyConstraintName(table.name, options.columns); + + table.constraints.push({ + type: DatabaseConstraintType.FOREIGN_KEY, + name, + tableName: table.name, + columnNames: options.columns, + referenceTableName: referenceTable.name, + referenceColumnNames: referenceColumns, + onUpdate: options.onUpdate as DatabaseActionType, + onDelete: options.onDelete as DatabaseActionType, + synchronize: options.synchronize ?? true, + }); + + if (options.index === false) { + continue; + } + + if (options.index || options.indexName || config.createForeignKeyIndexes) { + table.indexes.push({ + name: options.indexName || asIndexName(table.name, options.columns), + tableName: table.name, + columnNames: options.columns, + unique: false, + synchronize: options.synchronize ?? true, + }); + } + } +}; diff --git a/server/src/sql-tools/from-code/processors/index.processor.ts b/server/src/sql-tools/from-code/processors/index.processor.ts index f4c9c7cec1..4de8914231 100644 --- a/server/src/sql-tools/from-code/processors/index.processor.ts +++ b/server/src/sql-tools/from-code/processors/index.processor.ts @@ -1,7 +1,7 @@ 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 { asIndexName } from 'src/sql-tools/helpers'; export const processIndexes: Processor = (builder, items, config) => { for (const { @@ -75,16 +75,3 @@ export const processIndexes: Processor = (builder, items, config) => { }); } }; - -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); -}; diff --git a/server/src/sql-tools/from-code/processors/table.processor.ts b/server/src/sql-tools/from-code/processors/table.processor.ts index 4ef4e82020..e96f858266 100644 --- a/server/src/sql-tools/from-code/processors/table.processor.ts +++ b/server/src/sql-tools/from-code/processors/table.processor.ts @@ -1,6 +1,6 @@ import { TableOptions } from 'src/sql-tools/from-code/decorators/table.decorator'; import { Processor, SchemaBuilder } from 'src/sql-tools/from-code/processors/type'; -import { asMetadataKey, asSnakeCase } from 'src/sql-tools/helpers'; +import { addWarning, asMetadataKey, asSnakeCase } from 'src/sql-tools/helpers'; export const processTables: Processor = (builder, items) => { for (const { @@ -45,7 +45,7 @@ export const onMissingTable = ( propertyName?: symbol | string, ) => { const label = object.constructor.name + (propertyName ? '.' + String(propertyName) : ''); - builder.warnings.push(`[${context}] Unable to find table (${label})`); + addWarning(builder, context, `Unable to find table (${label})`); }; const METADATA_KEY = asMetadataKey('table-metadata'); diff --git a/server/src/sql-tools/from-code/register-item.ts b/server/src/sql-tools/from-code/register-item.ts index 4889ae34b9..2f394cf9c1 100644 --- a/server/src/sql-tools/from-code/register-item.ts +++ b/server/src/sql-tools/from-code/register-item.ts @@ -4,6 +4,7 @@ import { ConfigurationParameterOptions } from 'src/sql-tools/from-code/decorator import { DatabaseOptions } from 'src/sql-tools/from-code/decorators/database.decorator'; import { ExtensionOptions } from 'src/sql-tools/from-code/decorators/extension.decorator'; import { ForeignKeyColumnOptions } from 'src/sql-tools/from-code/decorators/foreign-key-column.decorator'; +import { ForeignKeyConstraintOptions } from 'src/sql-tools/from-code/decorators/foreign-key-constraint.decorator'; import { IndexOptions } from 'src/sql-tools/from-code/decorators/index.decorator'; import { TableOptions } from 'src/sql-tools/from-code/decorators/table.decorator'; import { TriggerOptions } from 'src/sql-tools/from-code/decorators/trigger.decorator'; @@ -25,5 +26,6 @@ export type RegisterItem = | { type: 'trigger'; item: ClassBased<{ options: TriggerOptions }> } | { type: 'extension'; item: ClassBased<{ options: ExtensionOptions }> } | { type: 'configurationParameter'; item: ClassBased<{ options: ConfigurationParameterOptions }> } - | { type: 'foreignKeyColumn'; item: PropertyBased<{ options: ForeignKeyColumnOptions; target: () => object }> }; + | { type: 'foreignKeyColumn'; item: PropertyBased<{ options: ForeignKeyColumnOptions; target: () => object }> } + | { type: 'foreignKeyConstraint'; item: ClassBased<{ options: ForeignKeyConstraintOptions }> }; export type RegisterItemType = Extract['item']; diff --git a/server/src/sql-tools/helpers.ts b/server/src/sql-tools/helpers.ts index 2802407ea6..015bbe4d9c 100644 --- a/server/src/sql-tools/helpers.ts +++ b/server/src/sql-tools/helpers.ts @@ -1,5 +1,6 @@ import { createHash } from 'node:crypto'; import { ColumnValue } from 'src/sql-tools/from-code/decorators/column.decorator'; +import { SchemaBuilder } from 'src/sql-tools/from-code/processors/type'; import { Comparer, DatabaseColumn, @@ -211,3 +212,22 @@ export const asColumnComment = (tableName: string, columnName: string, comment: }; export const asColumnList = (columns: string[]) => columns.map((column) => `"${column}"`).join(', '); + +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}`); +}; diff --git a/server/src/sql-tools/public_api.ts b/server/src/sql-tools/public_api.ts index c7a3023a4d..61e4a3e431 100644 --- a/server/src/sql-tools/public_api.ts +++ b/server/src/sql-tools/public_api.ts @@ -12,6 +12,7 @@ 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'; diff --git a/server/test/sql-tools/foreign-key-constraint-column-order.stub.ts b/server/test/sql-tools/foreign-key-constraint-column-order.stub.ts new file mode 100644 index 0000000000..b211620343 --- /dev/null +++ b/server/test/sql-tools/foreign-key-constraint-column-order.stub.ts @@ -0,0 +1,124 @@ +import { + Column, + DatabaseConstraintType, + DatabaseSchema, + ForeignKeyConstraint, + PrimaryColumn, + Table, +} from 'src/sql-tools'; + +@Table() +export class Table1 { + @PrimaryColumn({ type: 'uuid' }) + id1!: string; + + @PrimaryColumn({ type: 'uuid' }) + id2!: string; +} + +@Table() +@ForeignKeyConstraint({ + columns: ['parentId1', 'parentId2'], + referenceTable: () => Table1, + referenceColumns: ['id2', 'id1'], +}) +export class Table2 { + @Column({ type: 'uuid' }) + parentId1!: string; + + @Column({ type: 'uuid' }) + parentId2!: string; +} + +export const description = 'should create a foreign key constraint to the target table'; +export const schema: DatabaseSchema = { + name: 'postgres', + schemaName: 'public', + functions: [], + enums: [], + extensions: [], + parameters: [], + tables: [ + { + name: 'table1', + columns: [ + { + name: 'id1', + tableName: 'table1', + type: 'uuid', + nullable: false, + isArray: false, + primary: true, + synchronize: true, + }, + { + name: 'id2', + tableName: 'table1', + type: 'uuid', + nullable: false, + isArray: false, + primary: true, + synchronize: true, + }, + ], + indexes: [], + triggers: [], + constraints: [ + { + type: DatabaseConstraintType.PRIMARY_KEY, + name: 'PK_e457e8b1301b7bc06ef78188ee4', + tableName: 'table1', + columnNames: ['id1', 'id2'], + synchronize: true, + }, + ], + synchronize: true, + }, + { + name: 'table2', + columns: [ + { + name: 'parentId1', + tableName: 'table2', + type: 'uuid', + nullable: false, + isArray: false, + primary: false, + synchronize: true, + }, + { + name: 'parentId2', + tableName: 'table2', + type: 'uuid', + nullable: false, + isArray: false, + primary: false, + synchronize: true, + }, + ], + indexes: [ + { + name: 'IDX_aed36d04470eba20161aa8b1dc', + tableName: 'table2', + columnNames: ['parentId1', 'parentId2'], + unique: false, + synchronize: true, + }, + ], + triggers: [], + constraints: [ + { + type: DatabaseConstraintType.FOREIGN_KEY, + name: 'FK_aed36d04470eba20161aa8b1dc6', + tableName: 'table2', + columnNames: ['parentId1', 'parentId2'], + referenceColumnNames: ['id2', 'id1'], + referenceTableName: 'table1', + synchronize: true, + }, + ], + synchronize: true, + }, + ], + warnings: [], +}; diff --git a/server/test/sql-tools/foreign-key-constraint-missing-column.stub.ts b/server/test/sql-tools/foreign-key-constraint-missing-column.stub.ts new file mode 100644 index 0000000000..5bb335030a --- /dev/null +++ b/server/test/sql-tools/foreign-key-constraint-missing-column.stub.ts @@ -0,0 +1,78 @@ +import { + Column, + DatabaseConstraintType, + DatabaseSchema, + ForeignKeyConstraint, + PrimaryColumn, + Table, +} from 'src/sql-tools'; + +@Table() +export class Table1 { + @PrimaryColumn({ type: 'uuid' }) + id!: string; +} + +@Table() +@ForeignKeyConstraint({ columns: ['parentId2'], referenceTable: () => Table1 }) +export class Table2 { + @Column({ type: 'uuid' }) + parentId!: string; +} + +export const description = 'should warn against missing column in foreign key constraint'; +export const schema: DatabaseSchema = { + name: 'postgres', + schemaName: 'public', + functions: [], + enums: [], + extensions: [], + parameters: [], + tables: [ + { + name: 'table1', + columns: [ + { + name: 'id', + tableName: 'table1', + type: 'uuid', + nullable: false, + isArray: false, + primary: true, + synchronize: true, + }, + ], + indexes: [], + triggers: [], + constraints: [ + { + type: DatabaseConstraintType.PRIMARY_KEY, + name: 'PK_b249cc64cf63b8a22557cdc8537', + tableName: 'table1', + columnNames: ['id'], + synchronize: true, + }, + ], + synchronize: true, + }, + { + name: 'table2', + columns: [ + { + name: 'parentId', + tableName: 'table2', + type: 'uuid', + nullable: false, + isArray: false, + primary: false, + synchronize: true, + }, + ], + indexes: [], + triggers: [], + constraints: [], + synchronize: true, + }, + ], + warnings: ['[@ForeignKeyConstraint.columns] Unable to find column (Table2.parentId2)'], +}; diff --git a/server/test/sql-tools/foreign-key-constraint-missing-reference-column.stub.ts b/server/test/sql-tools/foreign-key-constraint-missing-reference-column.stub.ts new file mode 100644 index 0000000000..83c3adeb6d --- /dev/null +++ b/server/test/sql-tools/foreign-key-constraint-missing-reference-column.stub.ts @@ -0,0 +1,78 @@ +import { + Column, + DatabaseConstraintType, + DatabaseSchema, + ForeignKeyConstraint, + PrimaryColumn, + Table, +} from 'src/sql-tools'; + +@Table() +export class Table1 { + @PrimaryColumn({ type: 'uuid' }) + id!: string; +} + +@Table() +@ForeignKeyConstraint({ columns: ['parentId'], referenceTable: () => Table1, referenceColumns: ['foo'] }) +export class Table2 { + @Column({ type: 'uuid' }) + parentId!: string; +} + +export const description = 'should warn against missing reference column in foreign key constraint'; +export const schema: DatabaseSchema = { + name: 'postgres', + schemaName: 'public', + functions: [], + enums: [], + extensions: [], + parameters: [], + tables: [ + { + name: 'table1', + columns: [ + { + name: 'id', + tableName: 'table1', + type: 'uuid', + nullable: false, + isArray: false, + primary: true, + synchronize: true, + }, + ], + indexes: [], + triggers: [], + constraints: [ + { + type: DatabaseConstraintType.PRIMARY_KEY, + name: 'PK_b249cc64cf63b8a22557cdc8537', + tableName: 'table1', + columnNames: ['id'], + synchronize: true, + }, + ], + synchronize: true, + }, + { + name: 'table2', + columns: [ + { + name: 'parentId', + tableName: 'table2', + type: 'uuid', + nullable: false, + isArray: false, + primary: false, + synchronize: true, + }, + ], + indexes: [], + triggers: [], + constraints: [], + synchronize: true, + }, + ], + warnings: ['[@ForeignKeyConstraint.referenceColumns] Unable to find column (Table1.foo)'], +}; diff --git a/server/test/sql-tools/foreign-key-constraint-missing-reference-table.stub.ts b/server/test/sql-tools/foreign-key-constraint-missing-reference-table.stub.ts new file mode 100644 index 0000000000..54cf731479 --- /dev/null +++ b/server/test/sql-tools/foreign-key-constraint-missing-reference-table.stub.ts @@ -0,0 +1,44 @@ +import { Column, DatabaseSchema, ForeignKeyConstraint, Table } from 'src/sql-tools'; + +class Foo {} + +@Table() +@ForeignKeyConstraint({ + columns: ['parentId'], + referenceTable: () => Foo, +}) +export class Table1 { + @Column() + parentId!: string; +} + +export const description = 'should warn against missing reference table'; +export const schema: DatabaseSchema = { + name: 'postgres', + schemaName: 'public', + functions: [], + enums: [], + extensions: [], + parameters: [], + tables: [ + { + name: 'table1', + columns: [ + { + name: 'parentId', + tableName: 'table1', + type: 'character varying', + nullable: false, + isArray: false, + primary: false, + synchronize: true, + }, + ], + indexes: [], + triggers: [], + constraints: [], + synchronize: true, + }, + ], + warnings: ['[@ForeignKeyConstraint.referenceTable] Unable to find table (Foo)'], +}; diff --git a/server/test/sql-tools/foreign-key-constraint-multiple-columns.stub.ts b/server/test/sql-tools/foreign-key-constraint-multiple-columns.stub.ts new file mode 100644 index 0000000000..30f18eaf9d --- /dev/null +++ b/server/test/sql-tools/foreign-key-constraint-multiple-columns.stub.ts @@ -0,0 +1,120 @@ +import { + Column, + DatabaseConstraintType, + DatabaseSchema, + ForeignKeyConstraint, + PrimaryColumn, + Table, +} from 'src/sql-tools'; + +@Table() +export class Table1 { + @PrimaryColumn({ type: 'uuid' }) + id1!: string; + + @PrimaryColumn({ type: 'uuid' }) + id2!: string; +} + +@Table() +@ForeignKeyConstraint({ columns: ['parentId1', 'parentId2'], referenceTable: () => Table1 }) +export class Table2 { + @Column({ type: 'uuid' }) + parentId1!: string; + + @Column({ type: 'uuid' }) + parentId2!: string; +} + +export const description = 'should create a foreign key constraint to the target table'; +export const schema: DatabaseSchema = { + name: 'postgres', + schemaName: 'public', + functions: [], + enums: [], + extensions: [], + parameters: [], + tables: [ + { + name: 'table1', + columns: [ + { + name: 'id1', + tableName: 'table1', + type: 'uuid', + nullable: false, + isArray: false, + primary: true, + synchronize: true, + }, + { + name: 'id2', + tableName: 'table1', + type: 'uuid', + nullable: false, + isArray: false, + primary: true, + synchronize: true, + }, + ], + indexes: [], + triggers: [], + constraints: [ + { + type: DatabaseConstraintType.PRIMARY_KEY, + name: 'PK_e457e8b1301b7bc06ef78188ee4', + tableName: 'table1', + columnNames: ['id1', 'id2'], + synchronize: true, + }, + ], + synchronize: true, + }, + { + name: 'table2', + columns: [ + { + name: 'parentId1', + tableName: 'table2', + type: 'uuid', + nullable: false, + isArray: false, + primary: false, + synchronize: true, + }, + { + name: 'parentId2', + tableName: 'table2', + type: 'uuid', + nullable: false, + isArray: false, + primary: false, + synchronize: true, + }, + ], + indexes: [ + { + name: 'IDX_aed36d04470eba20161aa8b1dc', + tableName: 'table2', + columnNames: ['parentId1', 'parentId2'], + unique: false, + synchronize: true, + }, + ], + triggers: [], + constraints: [ + { + type: DatabaseConstraintType.FOREIGN_KEY, + name: 'FK_aed36d04470eba20161aa8b1dc6', + tableName: 'table2', + columnNames: ['parentId1', 'parentId2'], + referenceColumnNames: ['id1', 'id2'], + referenceTableName: 'table1', + synchronize: true, + }, + ], + synchronize: true, + }, + ], + warnings: [], +}; diff --git a/server/test/sql-tools/foreign-key-constraint-no-index.stub.ts b/server/test/sql-tools/foreign-key-constraint-no-index.stub.ts new file mode 100644 index 0000000000..5ad0aa7a6b --- /dev/null +++ b/server/test/sql-tools/foreign-key-constraint-no-index.stub.ts @@ -0,0 +1,88 @@ +import { + Column, + DatabaseConstraintType, + DatabaseSchema, + ForeignKeyConstraint, + PrimaryColumn, + Table, +} from 'src/sql-tools'; + +@Table() +export class Table1 { + @PrimaryColumn({ type: 'uuid' }) + id!: string; +} + +@Table() +@ForeignKeyConstraint({ columns: ['parentId'], referenceTable: () => Table1, index: false }) +export class Table2 { + @Column({ type: 'uuid' }) + parentId!: string; +} + +export const description = 'should create a foreign key constraint to the target table without an index'; +export const schema: DatabaseSchema = { + name: 'postgres', + schemaName: 'public', + functions: [], + enums: [], + extensions: [], + parameters: [], + tables: [ + { + name: 'table1', + columns: [ + { + name: 'id', + tableName: 'table1', + type: 'uuid', + nullable: false, + isArray: false, + primary: true, + synchronize: true, + }, + ], + indexes: [], + triggers: [], + constraints: [ + { + type: DatabaseConstraintType.PRIMARY_KEY, + name: 'PK_b249cc64cf63b8a22557cdc8537', + tableName: 'table1', + columnNames: ['id'], + synchronize: true, + }, + ], + synchronize: true, + }, + { + name: 'table2', + columns: [ + { + name: 'parentId', + tableName: 'table2', + type: 'uuid', + nullable: false, + isArray: false, + primary: false, + synchronize: true, + }, + ], + indexes: [], + triggers: [], + constraints: [ + { + type: DatabaseConstraintType.FOREIGN_KEY, + name: 'FK_3fcca5cc563abf256fc346e3ff4', + tableName: 'table2', + columnNames: ['parentId'], + referenceColumnNames: ['id'], + referenceTableName: 'table1', + synchronize: true, + }, + ], + synchronize: true, + }, + ], + warnings: [], +}; diff --git a/server/test/sql-tools/foreign-key-constraint-no-primary.stub.ts b/server/test/sql-tools/foreign-key-constraint-no-primary.stub.ts new file mode 100644 index 0000000000..645b0e76f2 --- /dev/null +++ b/server/test/sql-tools/foreign-key-constraint-no-primary.stub.ts @@ -0,0 +1,85 @@ +import { Column, DatabaseConstraintType, DatabaseSchema, ForeignKeyConstraint, Table } from 'src/sql-tools'; + +@Table() +export class Table1 { + @Column() + foo!: string; +} + +@Table() +@ForeignKeyConstraint({ + columns: ['bar'], + referenceTable: () => Table1, + referenceColumns: ['foo'], +}) +export class Table2 { + @Column() + bar!: string; +} + +export const description = 'should create a foreign key constraint to the target table without a primary key'; +export const schema: DatabaseSchema = { + name: 'postgres', + schemaName: 'public', + functions: [], + enums: [], + extensions: [], + parameters: [], + tables: [ + { + name: 'table1', + columns: [ + { + name: 'foo', + tableName: 'table1', + type: 'character varying', + nullable: false, + isArray: false, + primary: false, + synchronize: true, + }, + ], + indexes: [], + triggers: [], + constraints: [], + synchronize: true, + }, + { + name: 'table2', + columns: [ + { + name: 'bar', + tableName: 'table2', + type: 'character varying', + nullable: false, + isArray: false, + primary: false, + synchronize: true, + }, + ], + indexes: [ + { + name: 'IDX_7d9c784c98d12365d198d52e4e', + tableName: 'table2', + columnNames: ['bar'], + unique: false, + synchronize: true, + }, + ], + triggers: [], + constraints: [ + { + type: DatabaseConstraintType.FOREIGN_KEY, + name: 'FK_7d9c784c98d12365d198d52e4e6', + tableName: 'table2', + columnNames: ['bar'], + referenceTableName: 'table1', + referenceColumnNames: ['foo'], + synchronize: true, + }, + ], + synchronize: true, + }, + ], + warnings: [], +}; diff --git a/server/test/sql-tools/foreign-key-constraint.stub.ts b/server/test/sql-tools/foreign-key-constraint.stub.ts new file mode 100644 index 0000000000..c8117bd96d --- /dev/null +++ b/server/test/sql-tools/foreign-key-constraint.stub.ts @@ -0,0 +1,96 @@ +import { + Column, + DatabaseConstraintType, + DatabaseSchema, + ForeignKeyConstraint, + PrimaryColumn, + Table, +} from 'src/sql-tools'; + +@Table() +export class Table1 { + @PrimaryColumn({ type: 'uuid' }) + id!: string; +} + +@Table() +@ForeignKeyConstraint({ columns: ['parentId'], referenceTable: () => Table1 }) +export class Table2 { + @Column({ type: 'uuid' }) + parentId!: string; +} + +export const description = 'should create a foreign key constraint to the target table'; +export const schema: DatabaseSchema = { + name: 'postgres', + schemaName: 'public', + functions: [], + enums: [], + extensions: [], + parameters: [], + tables: [ + { + name: 'table1', + columns: [ + { + name: 'id', + tableName: 'table1', + type: 'uuid', + nullable: false, + isArray: false, + primary: true, + synchronize: true, + }, + ], + indexes: [], + triggers: [], + constraints: [ + { + type: DatabaseConstraintType.PRIMARY_KEY, + name: 'PK_b249cc64cf63b8a22557cdc8537', + tableName: 'table1', + columnNames: ['id'], + synchronize: true, + }, + ], + synchronize: true, + }, + { + name: 'table2', + columns: [ + { + name: 'parentId', + tableName: 'table2', + type: 'uuid', + nullable: false, + isArray: false, + primary: false, + synchronize: true, + }, + ], + indexes: [ + { + name: 'IDX_3fcca5cc563abf256fc346e3ff', + tableName: 'table2', + columnNames: ['parentId'], + unique: false, + synchronize: true, + }, + ], + triggers: [], + constraints: [ + { + type: DatabaseConstraintType.FOREIGN_KEY, + name: 'FK_3fcca5cc563abf256fc346e3ff4', + tableName: 'table2', + columnNames: ['parentId'], + referenceColumnNames: ['id'], + referenceTableName: 'table1', + synchronize: true, + }, + ], + synchronize: true, + }, + ], + warnings: [], +};