import {EntityMetadata} from "../metadata/EntityMetadata"; import {ColumnMetadata} from "../metadata/ColumnMetadata"; import {ForeignKeyMetadata} from "../metadata/ForeignKeyMetadata"; import {IndexMetadata} from "../metadata/IndexMetadata"; import {RelationMetadata} from "../metadata/RelationMetadata"; import {JoinTableMetadataArgs} from "../metadata-args/JoinTableMetadataArgs"; import {Connection} from "../connection/Connection"; /** * Creates EntityMetadata for junction tables. * Junction tables are tables generated by many-to-many relations. */ export class JunctionEntityMetadataBuilder { // ------------------------------------------------------------------------- // Constructor // ------------------------------------------------------------------------- constructor(private connection: Connection) { } // ------------------------------------------------------------------------- // Public Methods // ------------------------------------------------------------------------- /** * Builds EntityMetadata for the junction of the given many-to-many relation. */ build(relation: RelationMetadata, joinTable: JoinTableMetadataArgs): EntityMetadata { const referencedColumns = this.collectReferencedColumns(relation, joinTable); const inverseReferencedColumns = this.collectInverseReferencedColumns(relation, joinTable); const joinTableName = joinTable.name || this.connection.namingStrategy.joinTableName( relation.entityMetadata.tableNameWithoutPrefix, relation.inverseEntityMetadata.tableNameWithoutPrefix, relation.propertyPath, relation.inverseRelation ? relation.inverseRelation.propertyName : "" ); const entityMetadata = new EntityMetadata({ connection: this.connection, args: { target: "", name: joinTableName, type: "junction", database: joinTable.database || relation.entityMetadata.database, schema: joinTable.schema || relation.entityMetadata.schema, } }); entityMetadata.build(); // create original side junction columns const junctionColumns = referencedColumns.map(referencedColumn => { const joinColumn = joinTable.joinColumns ? joinTable.joinColumns.find(joinColumnArgs => { return (!joinColumnArgs.referencedColumnName || joinColumnArgs.referencedColumnName === referencedColumn.propertyName) && !!joinColumnArgs.name; }) : undefined; const columnName = joinColumn && joinColumn.name ? joinColumn.name : this.connection.namingStrategy.joinTableColumnName(relation.entityMetadata.tableNameWithoutPrefix, referencedColumn.propertyName, referencedColumn.databaseName); return new ColumnMetadata({ connection: this.connection, entityMetadata: entityMetadata, referencedColumn: referencedColumn, args: { target: "", mode: "virtual", propertyName: columnName, options: { name: columnName, length: referencedColumn.length, width: referencedColumn.width, type: referencedColumn.type, precision: referencedColumn.precision, scale: referencedColumn.scale, charset: referencedColumn.charset, collation: referencedColumn.collation, zerofill: referencedColumn.zerofill, unsigned: referencedColumn.zerofill ? true : referencedColumn.unsigned, nullable: false, primary: true, } } }); }); // create inverse side junction columns const inverseJunctionColumns = inverseReferencedColumns.map(inverseReferencedColumn => { const joinColumn = joinTable.inverseJoinColumns ? joinTable.inverseJoinColumns.find(joinColumnArgs => { return (!joinColumnArgs.referencedColumnName || joinColumnArgs.referencedColumnName === inverseReferencedColumn.propertyName) && !!joinColumnArgs.name; }) : undefined; const columnName = joinColumn && joinColumn.name ? joinColumn.name : this.connection.namingStrategy.joinTableInverseColumnName(relation.inverseEntityMetadata.tableNameWithoutPrefix, inverseReferencedColumn.propertyName, inverseReferencedColumn.databaseName); return new ColumnMetadata({ connection: this.connection, entityMetadata: entityMetadata, referencedColumn: inverseReferencedColumn, args: { target: "", mode: "virtual", propertyName: columnName, options: { length: inverseReferencedColumn.length, type: inverseReferencedColumn.type, precision: inverseReferencedColumn.precision, scale: inverseReferencedColumn.scale, charset: inverseReferencedColumn.charset, collation: inverseReferencedColumn.collation, zerofill: inverseReferencedColumn.zerofill, unsigned: inverseReferencedColumn.zerofill ? true : inverseReferencedColumn.unsigned, name: columnName, nullable: false, primary: true, } } }); }); this.changeDuplicatedColumnNames(junctionColumns, inverseJunctionColumns); // set junction table columns entityMetadata.ownerColumns = junctionColumns; entityMetadata.inverseColumns = inverseJunctionColumns; entityMetadata.ownColumns = [...junctionColumns, ...inverseJunctionColumns]; entityMetadata.ownColumns.forEach(column => column.relationMetadata = relation); // create junction table foreign keys entityMetadata.foreignKeys = [ new ForeignKeyMetadata({ entityMetadata: entityMetadata, referencedEntityMetadata: relation.entityMetadata, columns: junctionColumns, referencedColumns: referencedColumns, onDelete: "CASCADE" }), new ForeignKeyMetadata({ entityMetadata: entityMetadata, referencedEntityMetadata: relation.inverseEntityMetadata, columns: inverseJunctionColumns, referencedColumns: inverseReferencedColumns, onDelete: "CASCADE" }), ]; // create junction table indices entityMetadata.indices = [ new IndexMetadata({ entityMetadata: entityMetadata, columns: junctionColumns, args: { target: "", unique: false } }), new IndexMetadata({ entityMetadata: entityMetadata, columns: inverseJunctionColumns, args: { target: "", unique: false } }) ]; // finally return entity metadata return entityMetadata; } // ------------------------------------------------------------------------- // Protected Methods // ------------------------------------------------------------------------- /** * Collects referenced columns from the given join column args. */ protected collectReferencedColumns(relation: RelationMetadata, joinTable: JoinTableMetadataArgs): ColumnMetadata[] { const hasAnyReferencedColumnName = joinTable.joinColumns ? joinTable.joinColumns.find(joinColumn => !!joinColumn.referencedColumnName) : false; if (!joinTable.joinColumns || (joinTable.joinColumns && !hasAnyReferencedColumnName)) { return relation.entityMetadata.columns.filter(column => column.isPrimary); } else { return joinTable.joinColumns.map(joinColumn => { const referencedColumn = relation.entityMetadata.columns.find(column => column.propertyName === joinColumn.referencedColumnName); if (!referencedColumn) throw new Error(`Referenced column ${joinColumn.referencedColumnName} was not found in entity ${relation.entityMetadata.name}`); return referencedColumn; }); } } /** * Collects inverse referenced columns from the given join column args. */ protected collectInverseReferencedColumns(relation: RelationMetadata, joinTable: JoinTableMetadataArgs): ColumnMetadata[] { const hasInverseJoinColumns = !!joinTable.inverseJoinColumns; const hasAnyInverseReferencedColumnName = hasInverseJoinColumns ? joinTable.inverseJoinColumns!.find(joinColumn => !!joinColumn.referencedColumnName) : false; if (!hasInverseJoinColumns || (hasInverseJoinColumns && !hasAnyInverseReferencedColumnName)) { return relation.inverseEntityMetadata.primaryColumns; } else { return joinTable.inverseJoinColumns!.map(joinColumn => { const referencedColumn = relation.inverseEntityMetadata.ownColumns.find(column => column.propertyName === joinColumn.referencedColumnName); if (!referencedColumn) throw new Error(`Referenced column ${joinColumn.referencedColumnName} was not found in entity ${relation.inverseEntityMetadata.name}`); return referencedColumn; }); } } protected changeDuplicatedColumnNames(junctionColumns: ColumnMetadata[], inverseJunctionColumns: ColumnMetadata[]) { junctionColumns.forEach(junctionColumn => { inverseJunctionColumns.forEach(inverseJunctionColumn => { if (junctionColumn.givenDatabaseName === inverseJunctionColumn.givenDatabaseName) { const junctionColumnName = this.connection.namingStrategy.joinTableColumnDuplicationPrefix(junctionColumn.propertyName, 1); junctionColumn.propertyName = junctionColumnName; junctionColumn.givenDatabaseName = junctionColumnName; const inverseJunctionColumnName = this.connection.namingStrategy.joinTableColumnDuplicationPrefix(inverseJunctionColumn.propertyName, 2); inverseJunctionColumn.propertyName = inverseJunctionColumnName; inverseJunctionColumn.givenDatabaseName = inverseJunctionColumnName; } }); }); } }