mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
implemented multiple join columns support and proper multiple primary keys relations
This commit is contained in:
parent
84c7c93c50
commit
94a6ef4c13
@ -7,16 +7,39 @@ import {JoinColumnMetadataArgs} from "../../metadata-args/JoinColumnMetadataArgs
|
||||
* It also can be used on both one-to-one and many-to-one relations to specify custom column name
|
||||
* or custom referenced column.
|
||||
*/
|
||||
export function JoinColumn(options?: JoinColumnOptions): Function {
|
||||
export function JoinColumn(): Function;
|
||||
|
||||
/**
|
||||
* JoinColumn decorator used on one-to-one relations to specify owner side of relationship.
|
||||
* It also can be used on both one-to-one and many-to-one relations to specify custom column name
|
||||
* or custom referenced column.
|
||||
*/
|
||||
export function JoinColumn(options: JoinColumnOptions): Function;
|
||||
|
||||
/**
|
||||
* JoinColumn decorator used on one-to-one relations to specify owner side of relationship.
|
||||
* It also can be used on both one-to-one and many-to-one relations to specify custom column name
|
||||
* or custom referenced column.
|
||||
*/
|
||||
export function JoinColumn(options: JoinColumnOptions[]): Function;
|
||||
|
||||
/**
|
||||
* JoinColumn decorator used on one-to-one relations to specify owner side of relationship.
|
||||
* It also can be used on both one-to-one and many-to-one relations to specify custom column name
|
||||
* or custom referenced column.
|
||||
*/
|
||||
export function JoinColumn(optionsOrOptionsArray?: JoinColumnOptions|JoinColumnOptions[]): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
options = options || {} as JoinColumnOptions;
|
||||
const args: JoinColumnMetadataArgs = {
|
||||
target: object.constructor,
|
||||
propertyName: propertyName,
|
||||
name: options.name,
|
||||
referencedColumnName: options.referencedColumnName
|
||||
};
|
||||
getMetadataArgsStorage().joinColumns.add(args);
|
||||
const options = optionsOrOptionsArray instanceof Array ? optionsOrOptionsArray : [optionsOrOptionsArray || {}];
|
||||
options.forEach(options => {
|
||||
const args: JoinColumnMetadataArgs = {
|
||||
target: object.constructor,
|
||||
propertyName: propertyName,
|
||||
name: options.name,
|
||||
referencedColumnName: options.referencedColumnName
|
||||
};
|
||||
getMetadataArgsStorage().joinColumns.add(args);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -39,7 +39,7 @@ export class LazyRelationsWrapper {
|
||||
.innerJoin(relation.junctionEntityMetadata.table.name, relation.junctionEntityMetadata.table.name,
|
||||
`${relation.junctionEntityMetadata.table.name}.${relation.joinTable.joinColumnName}=:${relation.propertyName}Id AND ` +
|
||||
`${relation.junctionEntityMetadata.table.name}.${relation.joinTable.inverseJoinColumnName}=${relation.propertyName}.${relation.joinTable.referencedColumn.propertyName}`)
|
||||
.setParameter(relation.propertyName + "Id", this[relation.referencedColumn.propertyName]);
|
||||
.setParameter(relation.propertyName + "Id", this[relation.joinTable.referencedColumn.propertyName]);
|
||||
|
||||
} else { // non-owner
|
||||
qb.select(relation.propertyName)
|
||||
@ -47,7 +47,7 @@ export class LazyRelationsWrapper {
|
||||
.innerJoin(relation.junctionEntityMetadata.table.name, relation.junctionEntityMetadata.table.name,
|
||||
`${relation.junctionEntityMetadata.table.name}.${relation.inverseRelation.joinTable.inverseJoinColumnName}=:${relation.propertyName}Id AND ` +
|
||||
`${relation.junctionEntityMetadata.table.name}.${relation.inverseRelation.joinTable.joinColumnName}=${relation.propertyName}.${relation.inverseRelation.joinTable.referencedColumn.propertyName}`)
|
||||
.setParameter(relation.propertyName + "Id", this[relation.inverseRelation.referencedColumn.propertyName]);
|
||||
.setParameter(relation.propertyName + "Id", this[relation.inverseRelation.joinTable.referencedColumn.propertyName]);
|
||||
}
|
||||
|
||||
this[promiseIndex] = qb.getMany().then(results => {
|
||||
@ -78,13 +78,13 @@ export class LazyRelationsWrapper {
|
||||
});
|
||||
return this[promiseIndex];
|
||||
|
||||
} else {
|
||||
} else { // todo: fix issues with joinColumn[0]
|
||||
|
||||
if (relation.hasInverseSide) {
|
||||
qb.select(relation.propertyName)
|
||||
.from(relation.inverseRelation.entityMetadata.target, relation.propertyName)
|
||||
.innerJoin(`${relation.propertyName}.${relation.inverseRelation.propertyName}`, relation.entityMetadata.targetName)
|
||||
.where(relation.entityMetadata.targetName + "." + relation.joinColumn.referencedColumn.fullName + "=:id", { id: relation.entityMetadata.getEntityIdMixedMap(this) }); // is referenced column usage is correct here?
|
||||
.where(relation.entityMetadata.targetName + "." + relation.joinColumns[0].referencedColumn.fullName + "=:id", { id: relation.entityMetadata.getEntityIdMixedMap(this) }); // is referenced column usage is correct here?
|
||||
|
||||
} else {
|
||||
// (ow) post.category<=>category.post
|
||||
@ -94,8 +94,8 @@ export class LazyRelationsWrapper {
|
||||
qb.select(relation.propertyName) // category
|
||||
.from(relation.type, relation.propertyName) // Category, category
|
||||
.innerJoin(relation.entityMetadata.target as Function, relation.entityMetadata.name,
|
||||
`${relation.entityMetadata.name}.${relation.propertyName}=${relation.propertyName}.${relation.referencedColumn.propertyName}`)
|
||||
.where(relation.entityMetadata.name + "." + relation.joinColumn.referencedColumn.fullName + "=:id", { id: relation.entityMetadata.getEntityIdMixedMap(this) }); // is referenced column usage is correct here?
|
||||
`${relation.entityMetadata.name}.${relation.propertyName}=${relation.propertyName}.${relation.isOwning ? relation.joinColumns[0].referencedColumn.propertyName : relation.inverseRelation.joinColumns[0].referencedColumn.propertyName }`)
|
||||
.where(relation.entityMetadata.name + "." + relation.joinColumns[0].referencedColumn.fullName + "=:id", { id: relation.entityMetadata.getEntityIdMixedMap(this) }); // is referenced column usage is correct here?
|
||||
}
|
||||
|
||||
this[promiseIndex] = qb.getOne().then(result => {
|
||||
|
||||
@ -394,7 +394,7 @@ export class EntityMetadataBuilder {
|
||||
|
||||
joinColumn.referencedColumn = referencedColumn;
|
||||
joinColumn.name = joinColumnMetadataArgs.name || namingStrategy.joinColumnInverseSideName(relation.propertyName, joinColumn.referencedColumn.propertyName);
|
||||
relation.joinColumn = joinColumn;
|
||||
relation.joinColumns.push(joinColumn);
|
||||
});
|
||||
});
|
||||
|
||||
@ -426,31 +426,34 @@ export class EntityMetadataBuilder {
|
||||
entityMetadatas.forEach(metadata => {
|
||||
metadata.relationsWithJoinColumns.forEach(relation => {
|
||||
|
||||
// find relational column and if it does not exist - add it
|
||||
const inverseSideColumn = relation.joinColumn.referencedColumn;
|
||||
let relationalColumn = metadata.columns.find(column => column.fullName === relation.name);
|
||||
if (!relationalColumn) {
|
||||
relationalColumn = new ColumnMetadata({
|
||||
target: metadata.target,
|
||||
propertyName: relation.name,
|
||||
// propertyType: inverseSideColumn.propertyType,
|
||||
mode: "virtual",
|
||||
options: <ColumnOptions> {
|
||||
name: relation.name,
|
||||
type: inverseSideColumn.type,
|
||||
nullable: relation.isNullable,
|
||||
primary: relation.isPrimary
|
||||
}
|
||||
});
|
||||
relationalColumn.relationMetadata = relation;
|
||||
metadata.addColumn(relationalColumn);
|
||||
}
|
||||
const columns = relation.joinColumns.map(joinColumn => {
|
||||
|
||||
// find relational column and if it does not exist - add it
|
||||
let relationalColumn = metadata.columns.find(column => column.fullName === joinColumn.name);
|
||||
if (!relationalColumn) {
|
||||
relationalColumn = new ColumnMetadata({
|
||||
target: metadata.target,
|
||||
propertyName: joinColumn.name,
|
||||
mode: "virtual",
|
||||
options: {
|
||||
name: joinColumn.name,
|
||||
type: joinColumn.referencedColumn.type,
|
||||
nullable: relation.isNullable,
|
||||
primary: relation.isPrimary
|
||||
}
|
||||
});
|
||||
relationalColumn.relationMetadata = relation;
|
||||
metadata.addColumn(relationalColumn);
|
||||
}
|
||||
return relationalColumn;
|
||||
});
|
||||
|
||||
// create and add foreign key
|
||||
const inverseSideColumns = relation.joinColumns.map(joinColumn => joinColumn.referencedColumn);
|
||||
const foreignKey = new ForeignKeyMetadata(
|
||||
[relationalColumn],
|
||||
columns,
|
||||
relation.inverseEntityMetadata.table,
|
||||
[inverseSideColumn],
|
||||
inverseSideColumns,
|
||||
relation.onDelete
|
||||
);
|
||||
foreignKey.entityMetadata = metadata;
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
import {UsingJoinTableIsNotAllowedError} from "./error/UsingJoinTableIsNotAllowedError";
|
||||
import {UsingJoinTableOnlyOnOneSideAllowedError} from "./error/UsingJoinTableOnlyOnOneSideAllowedError";
|
||||
import {UsingJoinColumnIsNotAllowedError} from "./error/UsingJoinColumnIsNotAllowedError";
|
||||
import {UsingJoinColumnOnlyOnOneSideAllowedError} from "./error/UsingJoinColumnOnlyOnOneSideAllowedError";
|
||||
import {MissingJoinColumnError} from "./error/MissingJoinColumnError";
|
||||
import {MissingJoinTableError} from "./error/MissingJoinTableError";
|
||||
import {EntityMetadata} from "../metadata/EntityMetadata";
|
||||
import {MissingPrimaryColumnError} from "./error/MissingPrimaryColumnError";
|
||||
@ -76,7 +73,8 @@ export class EntityMetadataValidator {
|
||||
// check join columns:
|
||||
// using JoinColumn is possible only on one side of the relation and on one-to-one, many-to-one relation types
|
||||
// first check if relation is one-to-one or many-to-one
|
||||
if (relation.joinColumn) {
|
||||
// todo(dima): fix
|
||||
/*if (relation.joinColumn) {
|
||||
|
||||
// join column can be applied only on one-to-one and many-to-one relations
|
||||
if (!relation.isOneToOne && !relation.isManyToOne)
|
||||
@ -95,7 +93,7 @@ export class EntityMetadataValidator {
|
||||
// if its a one-to-one relation and JoinColumn is missing on both sides of the relation
|
||||
// or its one-side relation without JoinColumn we should give an error
|
||||
if (!relation.joinColumn && relation.isOneToOne && (!relation.hasInverseSide || !relation.inverseRelation.joinColumn))
|
||||
throw new MissingJoinColumnError(entityMetadata, relation);
|
||||
throw new MissingJoinColumnError(entityMetadata, relation);*/
|
||||
|
||||
// if its a many-to-many relation and JoinTable is missing on both sides of the relation
|
||||
// or its one-side relation without JoinTable we should give an error
|
||||
|
||||
@ -603,10 +603,14 @@ export class EntityMetadata {
|
||||
// if entity id is a relation, then extract referenced column from that relation
|
||||
const columnRelation = this.relations.find(relation => relation.propertyName === column.propertyName);
|
||||
|
||||
if (columnRelation && columnRelation.joinColumn) {
|
||||
map[column.propertyName] = entityValue[columnRelation.joinColumn.referencedColumn.propertyName];
|
||||
} else if (columnRelation && columnRelation.inverseRelation.joinColumn) {
|
||||
map[column.propertyName] = entityValue[columnRelation.inverseRelation.joinColumn.referencedColumn.propertyName];
|
||||
if (columnRelation && columnRelation.joinColumns.length) {
|
||||
const ids = columnRelation.joinColumns.map(joinColumn => entityValue[joinColumn.referencedColumn.propertyName]);
|
||||
map[column.propertyName] = ids.length === 1 ? ids[0] : ids;
|
||||
|
||||
} else if (columnRelation && columnRelation.inverseRelation.joinColumns.length) {
|
||||
const ids = columnRelation.inverseRelation.joinColumns.map(joinColumn => entityValue[joinColumn.referencedColumn.propertyName]);
|
||||
map[column.propertyName] = ids.length === 1 ? ids[0] : ids;
|
||||
|
||||
} else {
|
||||
map[column.propertyName] = entityValue;
|
||||
}
|
||||
@ -618,13 +622,18 @@ export class EntityMetadata {
|
||||
if (entityValue === null || entityValue === undefined)
|
||||
return;
|
||||
|
||||
// this case is real when your entity primary keys are relations
|
||||
// if entity id is a relation, then extract referenced column from that relation
|
||||
const columnRelation = this.relations.find(relation => relation.propertyName === column.propertyName);
|
||||
|
||||
if (columnRelation && columnRelation.joinColumn) {
|
||||
map[column.propertyName] = entityValue[columnRelation.joinColumn.referencedColumn.propertyName];
|
||||
} else if (columnRelation && columnRelation.inverseRelation.joinColumn) {
|
||||
map[column.propertyName] = entityValue[columnRelation.inverseRelation.joinColumn.referencedColumn.propertyName];
|
||||
if (columnRelation && columnRelation.joinColumns.length) {
|
||||
const ids = columnRelation.joinColumns.map(joinColumn => entityValue[joinColumn.referencedColumn.propertyName]);
|
||||
map[column.propertyName] = ids.length === 1 ? ids[0] : ids;
|
||||
|
||||
} else if (columnRelation && columnRelation.inverseRelation.joinColumns.length) {
|
||||
const ids = columnRelation.inverseRelation.joinColumns.map(joinColumn => entityValue[joinColumn.referencedColumn.propertyName]);
|
||||
map[column.propertyName] = ids.length === 1 ? ids[0] : ids;
|
||||
|
||||
} else {
|
||||
map[column.propertyName] = entityValue;
|
||||
}
|
||||
@ -647,10 +656,14 @@ export class EntityMetadata {
|
||||
// if entity id is a relation, then extract referenced column from that relation
|
||||
const columnRelation = this.relations.find(relation => relation.propertyName === column.propertyName);
|
||||
|
||||
if (columnRelation && columnRelation.joinColumn) {
|
||||
map[column.fullName] = entityValue[columnRelation.joinColumn.referencedColumn.propertyName];
|
||||
} else if (columnRelation && columnRelation.inverseRelation.joinColumn) {
|
||||
map[column.fullName] = entityValue[columnRelation.inverseRelation.joinColumn.referencedColumn.propertyName];
|
||||
if (columnRelation && columnRelation.joinColumns.length) {
|
||||
const ids = columnRelation.joinColumns.map(joinColumn => entityValue[joinColumn.referencedColumn.propertyName]);
|
||||
map[column.fullName] = ids.length === 1 ? ids[0] : ids;
|
||||
|
||||
} else if (columnRelation && columnRelation.inverseRelation.joinColumns.length) {
|
||||
const ids = columnRelation.inverseRelation.joinColumns.map(joinColumn => entityValue[joinColumn.referencedColumn.propertyName]);
|
||||
map[column.fullName] = ids.length === 1 ? ids[0] : ids;
|
||||
|
||||
} else {
|
||||
map[column.fullName] = entityValue;
|
||||
}
|
||||
@ -665,10 +678,14 @@ export class EntityMetadata {
|
||||
// if entity id is a relation, then extract referenced column from that relation
|
||||
const columnRelation = this.relations.find(relation => relation.propertyName === column.propertyName);
|
||||
|
||||
if (columnRelation && columnRelation.joinColumn) {
|
||||
map[column.fullName] = entityValue[columnRelation.joinColumn.referencedColumn.propertyName];
|
||||
} else if (columnRelation && columnRelation.inverseRelation.joinColumn) {
|
||||
map[column.fullName] = entityValue[columnRelation.inverseRelation.joinColumn.referencedColumn.propertyName];
|
||||
if (columnRelation && columnRelation.joinColumns.length) {
|
||||
const ids = columnRelation.joinColumns.map(joinColumn => entityValue[joinColumn.referencedColumn.propertyName]);
|
||||
map[column.fullName] = ids.length === 1 ? ids[0] : ids;
|
||||
|
||||
} else if (columnRelation && columnRelation.inverseRelation.joinColumns.length) {
|
||||
const ids = columnRelation.inverseRelation.joinColumns.map(joinColumn => entityValue[joinColumn.referencedColumn.propertyName]);
|
||||
map[column.fullName] = ids.length === 1 ? ids[0] : ids;
|
||||
|
||||
} else {
|
||||
map[column.fullName] = entityValue;
|
||||
}
|
||||
@ -863,8 +880,15 @@ export class EntityMetadata {
|
||||
return false;
|
||||
|
||||
return Object.keys(firstId).every(key => {
|
||||
if (firstId[key] instanceof Object && secondId[key] instanceof Object)
|
||||
|
||||
// ids can be arrays in the case if they are mapped into multiple primary keys
|
||||
if (firstId[key] instanceof Array && secondId[key] instanceof Array) {
|
||||
return firstId[key].length === secondId[key].length &&
|
||||
firstId[key].every((key: any, index: number) => firstId[key][index] === secondId[key][index]);
|
||||
|
||||
} else if (firstId[key] instanceof Object && secondId[key] instanceof Object) { // ids can be objects
|
||||
return firstId[key].equals(secondId[key]);
|
||||
}
|
||||
|
||||
return firstId[key] === secondId[key];
|
||||
});
|
||||
|
||||
@ -13,16 +13,20 @@ export class JoinColumnMetadata {
|
||||
|
||||
/**
|
||||
* Target class to which metadata is applied.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
target: Function|string;
|
||||
|
||||
/**
|
||||
* Target's property name to which this metadata is applied.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
propertyName: string;
|
||||
|
||||
/**
|
||||
* Join column name.
|
||||
* Join column name in the database.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
|
||||
@ -4,7 +4,6 @@ import {OnDeleteType} from "./ForeignKeyMetadata";
|
||||
import {JoinTableMetadata} from "./JoinTableMetadata";
|
||||
import {JoinColumnMetadata} from "./JoinColumnMetadata";
|
||||
import {RelationMetadataArgs} from "../metadata-args/RelationMetadataArgs";
|
||||
import {ColumnMetadata} from "./ColumnMetadata";
|
||||
import {ObjectLiteral} from "../common/ObjectLiteral";
|
||||
|
||||
/**
|
||||
@ -51,8 +50,7 @@ export class RelationMetadata {
|
||||
/**
|
||||
* Relation join columns metadata.
|
||||
*/
|
||||
joinColumn: JoinColumnMetadata;
|
||||
// joinColumns: JoinColumnMetadata[] = [];
|
||||
joinColumns: JoinColumnMetadata[] = [];
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Readonly Properties
|
||||
@ -201,15 +199,15 @@ export class RelationMetadata {
|
||||
if (this.isOwning) {
|
||||
if (this.joinTable) {
|
||||
return this.joinTable.joinColumnName;
|
||||
} else if (this.joinColumn) {
|
||||
return this.joinColumn.name;
|
||||
} else if (this.joinColumns) {
|
||||
return this.joinColumns[0].name;
|
||||
}
|
||||
|
||||
} else if (this.hasInverseSide) {
|
||||
if (this.inverseRelation.joinTable) {
|
||||
return this.inverseRelation.joinTable.inverseJoinColumnName;
|
||||
} else if (this.inverseRelation.joinColumn && this.inverseRelation.joinColumn.referencedColumn) {
|
||||
return this.inverseRelation.joinColumn.referencedColumn.fullName;
|
||||
} else if (this.inverseRelation.joinColumns && this.inverseRelation.joinColumns[0].referencedColumn) {
|
||||
return this.inverseRelation.joinColumns[0].referencedColumn.fullName; // todo: [0] is temporary!!
|
||||
}
|
||||
}
|
||||
|
||||
@ -222,23 +220,8 @@ export class RelationMetadata {
|
||||
* //Also only owning sides of the relations have this property.
|
||||
*
|
||||
* @deprecated Use joinTable or joinColumn directly where needed, because this method it too much confusing
|
||||
*/
|
||||
|
||||
get referencedColumnName(): string {
|
||||
// if (!this.isOwning)
|
||||
// throw new Error(`Only owning side of the relations can have information about referenced column names.`);
|
||||
|
||||
// for many-to-one and owner one-to-one relations we get referenced column from join column
|
||||
/*if (this.joinColumn && this.joinColumn.referencedColumn && this.joinColumn.referencedColumn.name)
|
||||
return this.joinColumn.referencedColumn.name;
|
||||
|
||||
// for many-to-many relation we give referenced column depend of owner side
|
||||
if (this.joinTable) { // need to check if this algorithm works correctly
|
||||
if (this.isOwning) {
|
||||
return this.joinTable.referencedColumn.name;
|
||||
} else {
|
||||
return this.joinTable.inverseReferencedColumn.name;
|
||||
}
|
||||
}*/
|
||||
|
||||
if (this.isOwning) {
|
||||
if (this.joinTable) {
|
||||
@ -258,13 +241,13 @@ export class RelationMetadata {
|
||||
|
||||
// this should not be possible, but anyway throw error
|
||||
throw new Error(`Cannot get referenced column name of the relation ${this.entityMetadata.name}#${this.name}`);
|
||||
}
|
||||
} */
|
||||
|
||||
/**
|
||||
* Gets the column to which this relation is referenced.
|
||||
*
|
||||
* @deprecated Use joinTable or joinColumn directly where needed, because this method it too much confusing
|
||||
*/
|
||||
|
||||
get referencedColumn(): ColumnMetadata {
|
||||
if (this.isOwning) {
|
||||
if (this.joinTable) {
|
||||
@ -284,7 +267,7 @@ export class RelationMetadata {
|
||||
|
||||
// this should not be possible, but anyway throw error
|
||||
throw new Error(`Cannot get referenced column of the relation ${this.entityMetadata.name}#${this.name}`);
|
||||
}
|
||||
} */
|
||||
|
||||
/**
|
||||
* Gets the property's type to which this relation is applied.
|
||||
@ -299,7 +282,7 @@ export class RelationMetadata {
|
||||
get isOwning() {
|
||||
return !!(this.isManyToOne ||
|
||||
(this.isManyToMany && this.joinTable) ||
|
||||
(this.isOneToOne && this.joinColumn));
|
||||
(this.isOneToOne && this.joinColumns.length > 0));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -443,7 +426,9 @@ export class RelationMetadata {
|
||||
*
|
||||
* if from Post relation we are passing Category here,
|
||||
* it should return a post.category
|
||||
*/
|
||||
*
|
||||
* @deprecated
|
||||
|
||||
getOwnEntityRelationId(ownEntity: ObjectLiteral): any {
|
||||
if (this.isManyToManyOwner) {
|
||||
return ownEntity[this.joinTable.referencedColumn.propertyName];
|
||||
@ -457,7 +442,7 @@ export class RelationMetadata {
|
||||
} else if (this.isOneToOneNotOwner || this.isOneToMany) {
|
||||
return ownEntity[this.inverseRelation.joinColumn.referencedColumn.propertyName];
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
/**
|
||||
*
|
||||
@ -473,7 +458,7 @@ export class RelationMetadata {
|
||||
* it should return a category.id
|
||||
*
|
||||
* @deprecated Looks like this method does not make sence and does same as getOwnEntityRelationId ?
|
||||
*/
|
||||
|
||||
getInverseEntityRelationId(inverseEntity: ObjectLiteral): any {
|
||||
if (this.isManyToManyOwner) {
|
||||
return inverseEntity[this.joinTable.inverseReferencedColumn.propertyName];
|
||||
@ -487,7 +472,7 @@ export class RelationMetadata {
|
||||
} else if (this.isOneToOneNotOwner || this.isOneToMany) {
|
||||
return inverseEntity[this.inverseRelation.joinColumn.propertyName];
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Private Methods
|
||||
|
||||
@ -334,10 +334,11 @@ export class SubjectBuilder<Entity extends ObjectLiteral> {
|
||||
// (example) "relation" - is a relation in post with details.
|
||||
// (example) "valueMetadata" - is an entity metadata of the Details object.
|
||||
// (example) "persistValue" - is a detailsId from the persisted entity
|
||||
// todo: fix joinColumns[0] usages
|
||||
|
||||
// note that if databaseEntity has relation, it can only be a relation id,
|
||||
// because of query builder option "RELATION_ID_VALUES" we used
|
||||
const relationIdInDatabaseEntity = subject.databaseEntity[relation.joinColumn.propertyName]; // (example) returns post.detailsId
|
||||
const relationIdInDatabaseEntity = subject.databaseEntity[relation.propertyName]; // (example) returns post.detailsId
|
||||
|
||||
// if database relation id does not exist in the database object then nothing to remove
|
||||
if (relationIdInDatabaseEntity === null || relationIdInDatabaseEntity === undefined)
|
||||
@ -348,7 +349,7 @@ export class SubjectBuilder<Entity extends ObjectLiteral> {
|
||||
if (subject.hasEntity) {
|
||||
const persistValue = relation.getEntityValue(subject.entity);
|
||||
if (persistValue === null) persistValueRelationId = null;
|
||||
if (persistValue) persistValueRelationId = persistValue[relation.joinColumn.referencedColumn.propertyName];
|
||||
if (persistValue) persistValueRelationId = persistValue[relation.joinColumns[0].referencedColumn.propertyName];
|
||||
if (persistValueRelationId === undefined) return; // skip undefined properties
|
||||
}
|
||||
|
||||
@ -366,7 +367,7 @@ export class SubjectBuilder<Entity extends ObjectLiteral> {
|
||||
// (example) here we seek a Details loaded from the database in the subjects
|
||||
// (example) here relatedSubject.databaseEntity is a Details
|
||||
// (example) and we need to compare details.id === post.detailsId
|
||||
return relatedSubject.databaseEntity[relation.joinColumn.referencedColumn.propertyName] === relationIdInDatabaseEntity;
|
||||
return relatedSubject.databaseEntity[relation.joinColumns[0].referencedColumn.propertyName] === relationIdInDatabaseEntity;
|
||||
});
|
||||
|
||||
// if not loaded yet then load it from the database
|
||||
@ -376,7 +377,7 @@ export class SubjectBuilder<Entity extends ObjectLiteral> {
|
||||
const databaseEntity = await this.connection
|
||||
.getRepository<ObjectLiteral>(valueMetadata.target)
|
||||
.createQueryBuilder(qbAlias, this.queryRunnerProvider) // todo: this wont work for mongodb. implement this in some method and call it here instead?
|
||||
.where(qbAlias + "." + relation.joinColumn.referencedColumn.propertyName + "=:id")
|
||||
.where(qbAlias + "." + relation.joinColumns[0].referencedColumn.propertyName + "=:id")
|
||||
.setParameter("id", relationIdInDatabaseEntity) // (example) subject.entity is a post here
|
||||
.enableAutoRelationIdsLoad()
|
||||
.getOne();
|
||||
@ -422,12 +423,12 @@ export class SubjectBuilder<Entity extends ObjectLiteral> {
|
||||
let persistValueRelationId: any = undefined;
|
||||
if (subject.hasEntity && !subject.mustBeRemoved) {
|
||||
const persistValue = relation.getEntityValue(subject.entity);
|
||||
if (persistValue) persistValueRelationId = persistValue[relation.inverseRelation.joinColumn.propertyName];
|
||||
if (persistValue) persistValueRelationId = persistValue[relation.inverseRelation.propertyName];
|
||||
if (persistValueRelationId === undefined) return; // skip undefined properties
|
||||
}
|
||||
|
||||
// (example) returns us referenced column (detail's id)
|
||||
const relationIdInDatabaseEntity = subject.databaseEntity[relation.inverseRelation.joinColumn.referencedColumn.propertyName];
|
||||
const relationIdInDatabaseEntity = subject.databaseEntity[relation.inverseRelation.joinColumns[0].referencedColumn.propertyName];
|
||||
|
||||
// if database relation id does not exist then nothing to remove (but can this be possible?)
|
||||
if (relationIdInDatabaseEntity === null || relationIdInDatabaseEntity === undefined)
|
||||
@ -443,7 +444,7 @@ export class SubjectBuilder<Entity extends ObjectLiteral> {
|
||||
// (example) here we seek a Post loaded from the database in the subjects
|
||||
// (example) here relatedSubject.databaseEntity is a Post
|
||||
// (example) and we need to compare post.detailsId === details.id
|
||||
return relatedSubject.databaseEntity[relation.inverseRelation.joinColumn.propertyName] === relationIdInDatabaseEntity;
|
||||
return relatedSubject.databaseEntity[relation.inverseRelation.propertyName] === relationIdInDatabaseEntity;
|
||||
});
|
||||
|
||||
// if not loaded yet then load it from the database
|
||||
@ -472,7 +473,7 @@ export class SubjectBuilder<Entity extends ObjectLiteral> {
|
||||
// (example) persistValue is a postFromPersistedDetails here
|
||||
// (example) alreadyLoadedRelatedDatabaseSubject.databaseEntity is a postFromDatabaseDetails here
|
||||
// (example) postFromPersistedDetails.id === postFromDatabaseDetails - means nothing changed
|
||||
const inverseEntityRelationId = alreadyLoadedRelatedDatabaseSubject.databaseEntity[relation.inverseRelation.joinColumn.propertyName];
|
||||
const inverseEntityRelationId = alreadyLoadedRelatedDatabaseSubject.databaseEntity[relation.inverseRelation.propertyName];
|
||||
if (persistValueRelationId && persistValueRelationId === inverseEntityRelationId)
|
||||
return;
|
||||
|
||||
@ -569,7 +570,7 @@ export class SubjectBuilder<Entity extends ObjectLiteral> {
|
||||
} else { // this case can only be a oneToMany relation
|
||||
|
||||
// (example) returns us referenced column (detail's id)
|
||||
const relationIdInDatabaseEntity = subject.databaseEntity[relation.inverseRelation.joinColumn.referencedColumn.propertyName];
|
||||
const relationIdInDatabaseEntity = subject.databaseEntity[relation.inverseRelation.joinColumns[0].referencedColumn.propertyName];
|
||||
|
||||
// in this case we need inverse entities not only because of cascade removes
|
||||
// because we also need inverse entities to be able to perform update of entities
|
||||
|
||||
@ -205,65 +205,67 @@ export class SubjectOperationExecutor {
|
||||
// first update relations with join columns (one-to-one owner and many-to-one relations)
|
||||
const updateOptions: ObjectLiteral = {};
|
||||
subject.metadata.relationsWithJoinColumns.forEach(relation => {
|
||||
const referencedColumn = relation.joinColumn.referencedColumn;
|
||||
const relatedEntity = relation.getEntityValue(subject.entity);
|
||||
relation.joinColumns.forEach(joinColumn => {
|
||||
const referencedColumn = joinColumn.referencedColumn;
|
||||
const relatedEntity = relation.getEntityValue(subject.entity);
|
||||
|
||||
// if relation value is not set then nothing to do here
|
||||
if (!relatedEntity)
|
||||
return;
|
||||
// if relation value is not set then nothing to do here
|
||||
if (!relatedEntity)
|
||||
return;
|
||||
|
||||
// check if relation reference column is a relation
|
||||
let relationId: any;
|
||||
const columnRelation = relation.inverseEntityMetadata.relations.find(rel => rel.propertyName === relation.joinColumn.referencedColumn.propertyName);
|
||||
if (columnRelation) { // if referenced column is a relation
|
||||
const insertSubject = this.insertSubjects.find(insertedSubject => insertedSubject.entity === relatedEntity[referencedColumn.propertyName]);
|
||||
// check if relation reference column is a relation
|
||||
let relationId: any;
|
||||
const columnRelation = relation.inverseEntityMetadata.relations.find(rel => rel.propertyName === joinColumn.referencedColumn.propertyName);
|
||||
if (columnRelation) { // if referenced column is a relation
|
||||
const insertSubject = this.insertSubjects.find(insertedSubject => insertedSubject.entity === relatedEntity[referencedColumn.propertyName]);
|
||||
|
||||
// if this relation was just inserted
|
||||
if (insertSubject) {
|
||||
// if this relation was just inserted
|
||||
if (insertSubject) {
|
||||
|
||||
// check if we have this relation id already
|
||||
relationId = relatedEntity[referencedColumn.propertyName][columnRelation.propertyName];
|
||||
if (!relationId) {
|
||||
// check if we have this relation id already
|
||||
relationId = relatedEntity[referencedColumn.propertyName][columnRelation.propertyName];
|
||||
if (!relationId) {
|
||||
|
||||
// if we don't have relation id then use special values
|
||||
if (referencedColumn.isGenerated) {
|
||||
relationId = insertSubject.newlyGeneratedId;
|
||||
// if we don't have relation id then use special values
|
||||
if (referencedColumn.isGenerated) {
|
||||
relationId = insertSubject.newlyGeneratedId;
|
||||
|
||||
} else if (referencedColumn.isObjectId) {
|
||||
relationId = insertSubject.generatedObjectId;
|
||||
} else if (referencedColumn.isObjectId) {
|
||||
relationId = insertSubject.generatedObjectId;
|
||||
|
||||
}
|
||||
// todo: handle other special types too
|
||||
}
|
||||
// todo: handle other special types too
|
||||
}
|
||||
|
||||
} else { // if referenced column is a simple non relational column
|
||||
const insertSubject = this.insertSubjects.find(insertedSubject => insertedSubject.entity === relatedEntity);
|
||||
|
||||
// if this relation was just inserted
|
||||
if (insertSubject) {
|
||||
|
||||
// check if we have this relation id already
|
||||
relationId = relatedEntity[referencedColumn.propertyName];
|
||||
if (!relationId) {
|
||||
|
||||
// if we don't have relation id then use special values
|
||||
if (referencedColumn.isGenerated) {
|
||||
relationId = insertSubject.newlyGeneratedId;
|
||||
|
||||
} else if (referencedColumn.isObjectId) {
|
||||
relationId = insertSubject.generatedObjectId;
|
||||
}
|
||||
// todo: handle other special types too
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else { // if referenced column is a simple non relational column
|
||||
const insertSubject = this.insertSubjects.find(insertedSubject => insertedSubject.entity === relatedEntity);
|
||||
|
||||
// if this relation was just inserted
|
||||
if (insertSubject) {
|
||||
|
||||
// check if we have this relation id already
|
||||
relationId = relatedEntity[referencedColumn.propertyName];
|
||||
if (!relationId) {
|
||||
|
||||
// if we don't have relation id then use special values
|
||||
if (referencedColumn.isGenerated) {
|
||||
relationId = insertSubject.newlyGeneratedId;
|
||||
|
||||
} else if (referencedColumn.isObjectId) {
|
||||
relationId = insertSubject.generatedObjectId;
|
||||
}
|
||||
// todo: handle other special types too
|
||||
}
|
||||
if (relationId) {
|
||||
updateOptions[joinColumn.name] = relationId;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (relationId) {
|
||||
updateOptions[relation.name] = relationId;
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
// if we found relations which we can update - then update them
|
||||
@ -279,23 +281,25 @@ export class SubjectOperationExecutor {
|
||||
// if entity id is a relation, then extract referenced column from that relation
|
||||
const columnRelation = subject.metadata.relations.find(relation => relation.propertyName === column.propertyName);
|
||||
|
||||
if (entityValue && columnRelation && columnRelation.joinColumn) { // not sure if we need handle join column from inverse side
|
||||
let relationIdOfEntityValue = entityValue[columnRelation.joinColumn.referencedColumn.propertyName];
|
||||
if (!relationIdOfEntityValue) {
|
||||
const entityValueInsertSubject = this.insertSubjects.find(subject => subject.entity === entityValue);
|
||||
if (entityValueInsertSubject) {
|
||||
if (columnRelation.joinColumn.referencedColumn.isGenerated) {
|
||||
relationIdOfEntityValue = entityValueInsertSubject.newlyGeneratedId;
|
||||
if (entityValue && columnRelation) { // not sure if we need handle join column from inverse side
|
||||
columnRelation.joinColumns.forEach(joinColumn => {
|
||||
let relationIdOfEntityValue = entityValue[joinColumn.referencedColumn.propertyName];
|
||||
if (!relationIdOfEntityValue) {
|
||||
const entityValueInsertSubject = this.insertSubjects.find(subject => subject.entity === entityValue);
|
||||
if (entityValueInsertSubject) {
|
||||
if (joinColumn.referencedColumn.isGenerated) {
|
||||
relationIdOfEntityValue = entityValueInsertSubject.newlyGeneratedId;
|
||||
|
||||
} else if (columnRelation.joinColumn.referencedColumn.isObjectId) {
|
||||
relationIdOfEntityValue = entityValueInsertSubject.generatedObjectId;
|
||||
} else if (joinColumn.referencedColumn.isObjectId) {
|
||||
relationIdOfEntityValue = entityValueInsertSubject.generatedObjectId;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (relationIdOfEntityValue) {
|
||||
conditions[column.fullName] = relationIdOfEntityValue;
|
||||
}
|
||||
if (relationIdOfEntityValue) {
|
||||
conditions[column.fullName] = relationIdOfEntityValue;
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
if (entityValue) {
|
||||
@ -322,73 +326,80 @@ export class SubjectOperationExecutor {
|
||||
const oneToManyAndOneToOneNonOwnerRelations = subject.metadata.oneToManyRelations.concat(subject.metadata.oneToOneRelations.filter(relation => !relation.isOwning));
|
||||
subject.metadata.extractRelationValuesFromEntity(subject.entity, oneToManyAndOneToOneNonOwnerRelations)
|
||||
.forEach(([relation, subRelatedEntity, inverseEntityMetadata]) => {
|
||||
const referencedColumn = relation.inverseRelation.joinColumn.referencedColumn;
|
||||
const columns = inverseEntityMetadata.parentEntityMetadata ? inverseEntityMetadata.primaryColumnsWithParentIdColumns : inverseEntityMetadata.primaryColumns;
|
||||
const conditions: ObjectLiteral = {};
|
||||
relation.inverseRelation.joinColumns.forEach(joinColumn => {
|
||||
|
||||
columns.forEach(column => {
|
||||
const entityValue = subRelatedEntity[column.propertyName];
|
||||
const referencedColumn = joinColumn.referencedColumn;
|
||||
const columns = inverseEntityMetadata.parentEntityMetadata ? inverseEntityMetadata.primaryColumnsWithParentIdColumns : inverseEntityMetadata.primaryColumns;
|
||||
const conditions: ObjectLiteral = {};
|
||||
|
||||
// if entity id is a relation, then extract referenced column from that relation
|
||||
const columnRelation = inverseEntityMetadata.relations.find(relation => relation.propertyName === column.propertyName);
|
||||
columns.forEach(column => {
|
||||
const entityValue = subRelatedEntity[column.propertyName];
|
||||
|
||||
if (entityValue && columnRelation && columnRelation.joinColumn) { // not sure if we need handle join column from inverse side
|
||||
let relationIdOfEntityValue = entityValue[columnRelation.joinColumn.referencedColumn.propertyName];
|
||||
if (!relationIdOfEntityValue) {
|
||||
const entityValueInsertSubject = this.insertSubjects.find(subject => subject.entity === entityValue);
|
||||
if (entityValueInsertSubject) {
|
||||
if (columnRelation.joinColumn.referencedColumn.isGenerated) {
|
||||
relationIdOfEntityValue = entityValueInsertSubject.newlyGeneratedId;
|
||||
// if entity id is a relation, then extract referenced column from that relation
|
||||
const columnRelation = inverseEntityMetadata.relations.find(relation => relation.propertyName === column.propertyName);
|
||||
|
||||
if (entityValue && columnRelation) { // not sure if we need handle join column from inverse side
|
||||
columnRelation.joinColumns.forEach(columnRelationJoinColumn => {
|
||||
let relationIdOfEntityValue = entityValue[columnRelationJoinColumn.referencedColumn.propertyName];
|
||||
if (!relationIdOfEntityValue) {
|
||||
const entityValueInsertSubject = this.insertSubjects.find(subject => subject.entity === entityValue);
|
||||
if (entityValueInsertSubject) {
|
||||
if (columnRelationJoinColumn.referencedColumn.isGenerated) {
|
||||
relationIdOfEntityValue = entityValueInsertSubject.newlyGeneratedId;
|
||||
|
||||
} else if (columnRelationJoinColumn.referencedColumn.isObjectId) {
|
||||
relationIdOfEntityValue = entityValueInsertSubject.generatedObjectId;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (relationIdOfEntityValue) {
|
||||
conditions[column.fullName] = relationIdOfEntityValue;
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
const entityValueInsertSubject = this.insertSubjects.find(subject => subject.entity === subRelatedEntity);
|
||||
if (entityValue) {
|
||||
conditions[column.fullName] = entityValue;
|
||||
} else {
|
||||
if (entityValueInsertSubject && entityValueInsertSubject.newlyGeneratedId) {
|
||||
conditions[column.fullName] = entityValueInsertSubject.newlyGeneratedId;
|
||||
|
||||
} else if (entityValueInsertSubject && entityValueInsertSubject.generatedObjectId) {
|
||||
conditions[column.fullName] = entityValueInsertSubject.generatedObjectId;
|
||||
|
||||
} else if (columnRelation.joinColumn.referencedColumn.isObjectId) {
|
||||
relationIdOfEntityValue = entityValueInsertSubject.generatedObjectId;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (relationIdOfEntityValue) {
|
||||
conditions[column.fullName] = relationIdOfEntityValue;
|
||||
}
|
||||
});
|
||||
|
||||
if (!Object.keys(conditions).length)
|
||||
return;
|
||||
|
||||
const updateOptions: ObjectLiteral = {};
|
||||
const columnRelation = relation.inverseEntityMetadata.relations.find(rel => rel.propertyName === referencedColumn.propertyName);
|
||||
if (columnRelation) {
|
||||
let id = subject.entity[referencedColumn.propertyName][columnRelation.propertyName];
|
||||
if (!id) {
|
||||
const insertSubject = this.insertSubjects.find(subject => subject.entity === subject.entity[referencedColumn.propertyName]);
|
||||
if (insertSubject) {
|
||||
if (insertSubject.newlyGeneratedId) {
|
||||
id = insertSubject.newlyGeneratedId;
|
||||
|
||||
} else if (insertSubject.generatedObjectId) {
|
||||
id = insertSubject.generatedObjectId;
|
||||
}
|
||||
}
|
||||
}
|
||||
updateOptions[joinColumn.name] = id;
|
||||
} else {
|
||||
const entityValueInsertSubject = this.insertSubjects.find(subject => subject.entity === subRelatedEntity);
|
||||
if (entityValue) {
|
||||
conditions[column.fullName] = entityValue;
|
||||
} else {
|
||||
if (entityValueInsertSubject && entityValueInsertSubject.newlyGeneratedId) {
|
||||
conditions[column.fullName] = entityValueInsertSubject.newlyGeneratedId;
|
||||
|
||||
} else if (entityValueInsertSubject && entityValueInsertSubject.generatedObjectId) {
|
||||
conditions[column.fullName] = entityValueInsertSubject.generatedObjectId;
|
||||
|
||||
}
|
||||
}
|
||||
updateOptions[joinColumn.name] = subject.entity[referencedColumn.propertyName] || subject.newlyGeneratedId || subRelatedEntity.generatedObjectId;
|
||||
}
|
||||
|
||||
const updatePromise = this.queryRunner.update(relation.inverseEntityMetadata.table.name, updateOptions, conditions);
|
||||
updatePromises.push(updatePromise);
|
||||
|
||||
});
|
||||
if (!Object.keys(conditions).length)
|
||||
return;
|
||||
|
||||
const updateOptions: ObjectLiteral = {};
|
||||
const columnRelation = relation.inverseEntityMetadata.relations.find(rel => rel.propertyName === referencedColumn.propertyName);
|
||||
if (columnRelation) {
|
||||
let id = subject.entity[referencedColumn.propertyName][columnRelation.propertyName];
|
||||
if (!id) {
|
||||
const insertSubject = this.insertSubjects.find(subject => subject.entity === subject.entity[referencedColumn.propertyName]);
|
||||
if (insertSubject) {
|
||||
if (insertSubject.newlyGeneratedId) {
|
||||
id = insertSubject.newlyGeneratedId;
|
||||
|
||||
} else if (insertSubject.generatedObjectId) {
|
||||
id = insertSubject.generatedObjectId;
|
||||
}
|
||||
}
|
||||
}
|
||||
updateOptions[relation.inverseRelation.joinColumn.name] = id;
|
||||
} else {
|
||||
updateOptions[relation.inverseRelation.joinColumn.name] = subject.entity[referencedColumn.propertyName] || subject.newlyGeneratedId || subRelatedEntity.generatedObjectId;
|
||||
}
|
||||
|
||||
const updatePromise = this.queryRunner.update(relation.inverseEntityMetadata.table.name, updateOptions, conditions);
|
||||
updatePromises.push(updatePromise);
|
||||
});
|
||||
|
||||
});
|
||||
@ -495,67 +506,70 @@ export class SubjectOperationExecutor {
|
||||
collectFromEmbeddeds(entity, columnsAndValuesMap, metadata.embeddeds);
|
||||
|
||||
metadata.relationsWithJoinColumns.forEach(relation => {
|
||||
relation.joinColumns.forEach(joinColumn => {
|
||||
|
||||
let relationValue: any;
|
||||
const value = relation.getEntityValue(entity);
|
||||
let relationValue: any;
|
||||
const value = relation.getEntityValue(entity);
|
||||
|
||||
if (value) {
|
||||
// if relation value is stored in the entity itself then use it from there
|
||||
const relationId = relation.getInverseEntityRelationId(value); // todo: check it
|
||||
if (relationId) {
|
||||
relationValue = relationId;
|
||||
}
|
||||
|
||||
// otherwise try to find relational value from just inserted subjects
|
||||
const alreadyInsertedSubject = alreadyInsertedSubjects.find(insertedSubject => {
|
||||
return insertedSubject.entity === value;
|
||||
});
|
||||
if (alreadyInsertedSubject) {
|
||||
const referencedColumn = relation.joinColumn.referencedColumn;
|
||||
// if join column references to the primary generated column then seek in the newEntityId of the insertedSubject
|
||||
if (referencedColumn.referencedColumn && referencedColumn.referencedColumn.isGenerated) {
|
||||
if (referencedColumn.isParentId) {
|
||||
relationValue = alreadyInsertedSubject.parentGeneratedId;
|
||||
}
|
||||
// todo: what if reference column is not generated?
|
||||
// todo: what if reference column is not related to table inheritance?
|
||||
if (value) {
|
||||
// if relation value is stored in the entity itself then use it from there
|
||||
const relationId = value[joinColumn.referencedColumn.propertyName]; // relation.getInverseEntityRelationId(value); // todo: check it
|
||||
if (relationId) {
|
||||
relationValue = relationId;
|
||||
}
|
||||
|
||||
if (referencedColumn.isGenerated)
|
||||
relationValue = alreadyInsertedSubject.newlyGeneratedId;
|
||||
if (referencedColumn.isObjectId)
|
||||
relationValue = alreadyInsertedSubject.generatedObjectId;
|
||||
// if it references to create or update date columns
|
||||
if (referencedColumn.isCreateDate || referencedColumn.isUpdateDate)
|
||||
relationValue = this.connection.driver.preparePersistentValue(alreadyInsertedSubject.date, referencedColumn);
|
||||
// if it references to version column
|
||||
if (referencedColumn.isVersion)
|
||||
relationValue = this.connection.driver.preparePersistentValue(1, referencedColumn);
|
||||
}
|
||||
} else if (relation.hasInverseSide) {
|
||||
const inverseSubject = this.allSubjects.find(subject => {
|
||||
if (!subject.hasEntity || subject.entityTarget !== relation.inverseRelation.target)
|
||||
return false;
|
||||
|
||||
const inverseRelationValue = subject.entity[relation.inverseRelation.propertyName];
|
||||
if (inverseRelationValue) {
|
||||
if (inverseRelationValue instanceof Array) {
|
||||
return inverseRelationValue.find(subValue => subValue === subValue);
|
||||
} else {
|
||||
return inverseRelationValue === entity;
|
||||
// otherwise try to find relational value from just inserted subjects
|
||||
const alreadyInsertedSubject = alreadyInsertedSubjects.find(insertedSubject => {
|
||||
return insertedSubject.entity === value;
|
||||
});
|
||||
if (alreadyInsertedSubject) {
|
||||
const referencedColumn = joinColumn.referencedColumn;
|
||||
// if join column references to the primary generated column then seek in the newEntityId of the insertedSubject
|
||||
if (referencedColumn.referencedColumn && referencedColumn.referencedColumn.isGenerated) {
|
||||
if (referencedColumn.isParentId) {
|
||||
relationValue = alreadyInsertedSubject.parentGeneratedId;
|
||||
}
|
||||
// todo: what if reference column is not generated?
|
||||
// todo: what if reference column is not related to table inheritance?
|
||||
}
|
||||
}
|
||||
});
|
||||
if (inverseSubject && inverseSubject.entity[relation.joinColumn.referencedColumn.propertyName]) {
|
||||
relationValue = inverseSubject.entity[relation.joinColumn.referencedColumn.propertyName];
|
||||
}
|
||||
}
|
||||
|
||||
if (relationValue) {
|
||||
columnNames.push(relation.name);
|
||||
columnValues.push(relationValue);
|
||||
columnsAndValuesMap[relation.propertyName] = entity[relation.name];
|
||||
}
|
||||
if (referencedColumn.isGenerated)
|
||||
relationValue = alreadyInsertedSubject.newlyGeneratedId;
|
||||
if (referencedColumn.isObjectId)
|
||||
relationValue = alreadyInsertedSubject.generatedObjectId;
|
||||
// if it references to create or update date columns
|
||||
if (referencedColumn.isCreateDate || referencedColumn.isUpdateDate)
|
||||
relationValue = this.connection.driver.preparePersistentValue(alreadyInsertedSubject.date, referencedColumn);
|
||||
// if it references to version column
|
||||
if (referencedColumn.isVersion)
|
||||
relationValue = this.connection.driver.preparePersistentValue(1, referencedColumn);
|
||||
}
|
||||
} else if (relation.hasInverseSide) {
|
||||
const inverseSubject = this.allSubjects.find(subject => {
|
||||
if (!subject.hasEntity || subject.entityTarget !== relation.inverseRelation.target)
|
||||
return false;
|
||||
|
||||
const inverseRelationValue = subject.entity[relation.inverseRelation.propertyName];
|
||||
if (inverseRelationValue) {
|
||||
if (inverseRelationValue instanceof Array) {
|
||||
return inverseRelationValue.find(subValue => subValue === subValue);
|
||||
} else {
|
||||
return inverseRelationValue === entity;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (inverseSubject && inverseSubject.entity[joinColumn.referencedColumn.propertyName]) {
|
||||
relationValue = inverseSubject.entity[joinColumn.referencedColumn.propertyName];
|
||||
}
|
||||
}
|
||||
|
||||
if (relationValue) {
|
||||
columnNames.push(joinColumn.name);
|
||||
columnValues.push(relationValue);
|
||||
columnsAndValuesMap[relation.propertyName] = entity[relation.name]; // todo: check most probably wont work
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
// add special column and value - date of creation
|
||||
@ -638,7 +652,8 @@ export class SubjectOperationExecutor {
|
||||
// todo: since closure tables do not support compose primary keys - throw an exception?
|
||||
// todo: what if parent entity or parentEntityId is empty?!
|
||||
const tableName = subject.metadata.closureJunctionTable.table.name;
|
||||
const referencedColumn = subject.metadata.treeParentRelation.joinColumn.referencedColumn; // todo: check if joinColumn works
|
||||
const referencedColumn = subject.metadata.treeParentRelation.joinColumns[0].referencedColumn; // todo: check if joinColumn works
|
||||
// todo: fix joinColumns[0] usage
|
||||
|
||||
let newEntityId = subject.entity[referencedColumn.propertyName];
|
||||
if (!newEntityId && referencedColumn.isGenerated) {
|
||||
@ -776,7 +791,9 @@ export class SubjectOperationExecutor {
|
||||
}
|
||||
|
||||
const value = relation.getEntityValue(entity);
|
||||
valueMap.values[relation.name] = value !== null && value !== undefined ? value[relation.inverseEntityMetadata.firstPrimaryColumn.propertyName] : null; // todo: should not have a call to primaryColumn, instead join column metadata should be used
|
||||
relation.joinColumns.forEach(joinColumn => {
|
||||
valueMap!.values[joinColumn.name] = value !== null && value !== undefined ? value[joinColumn.referencedColumn.propertyName] : null; // todo: should not have a call to primaryColumn, instead join column metadata should be used
|
||||
});
|
||||
});
|
||||
|
||||
// if number of updated columns = 0 no need to update updated date and version columns
|
||||
@ -859,8 +876,10 @@ export class SubjectOperationExecutor {
|
||||
private async updateRelations(subject: Subject) {
|
||||
const values: ObjectLiteral = {};
|
||||
subject.relationUpdates.forEach(setRelation => {
|
||||
const value = setRelation.value ? setRelation.value[setRelation.relation.joinColumn.referencedColumn.propertyName] : null;
|
||||
values[setRelation.relation.name] = value; // todo: || fromInsertedSubjects ??
|
||||
setRelation.relation.joinColumns.forEach(joinColumn => {
|
||||
const value = setRelation.value ? setRelation.value[joinColumn.referencedColumn.propertyName] : null;
|
||||
values[joinColumn.name] = value; // todo: || fromInsertedSubjects ??
|
||||
});
|
||||
});
|
||||
|
||||
const idMap = subject.metadata.getDatabaseEntityIdMap(subject.databaseEntity);
|
||||
@ -931,7 +950,14 @@ export class SubjectOperationExecutor {
|
||||
const firstColumn = relation.isOwning ? joinTable.referencedColumn : joinTable.inverseReferencedColumn;
|
||||
const secondColumn = relation.isOwning ? joinTable.inverseReferencedColumn : joinTable.referencedColumn;
|
||||
|
||||
let ownId = relation.getOwnEntityRelationId(subject.entity);
|
||||
let ownId: any;
|
||||
if (relation.isManyToManyOwner) {
|
||||
ownId = subject.entity[relation.joinTable.referencedColumn.propertyName];
|
||||
|
||||
} else if (relation.isManyToManyNotOwner) {
|
||||
ownId = subject.entity[relation.inverseRelation.joinTable.inverseReferencedColumn.propertyName];
|
||||
}
|
||||
|
||||
if (!ownId) {
|
||||
if (firstColumn.isGenerated) {
|
||||
ownId = subject.newlyGeneratedId;
|
||||
@ -1010,7 +1036,15 @@ export class SubjectOperationExecutor {
|
||||
private async removeJunctions(subject: Subject, junctionRemove: JunctionRemove) {
|
||||
const junctionMetadata = junctionRemove.relation.junctionEntityMetadata;
|
||||
const entity = subject.hasEntity ? subject.entity : subject.databaseEntity;
|
||||
const ownId = junctionRemove.relation.getOwnEntityRelationId(entity);
|
||||
|
||||
let ownId: any;
|
||||
if (junctionRemove.relation.isManyToManyOwner) {
|
||||
ownId = entity[junctionRemove.relation.joinTable.referencedColumn.propertyName];
|
||||
|
||||
} else if (junctionRemove.relation.isManyToManyNotOwner) {
|
||||
ownId = entity[junctionRemove.relation.inverseRelation.joinTable.inverseReferencedColumn.propertyName];
|
||||
}
|
||||
|
||||
const ownColumn = junctionRemove.relation.isOwning ? junctionMetadata.columns[0] : junctionMetadata.columns[1];
|
||||
const relateColumn = junctionRemove.relation.isOwning ? junctionMetadata.columns[1] : junctionMetadata.columns[0];
|
||||
const removePromises = junctionRemove.junctionRelationIds.map(relationId => {
|
||||
|
||||
@ -1452,17 +1452,21 @@ export class QueryBuilder<Entity> {
|
||||
|
||||
// if real entity relation is involved
|
||||
if (relation.isManyToOne || relation.isOneToOneOwner) {
|
||||
const destinationTableReferencedColumn = relation.joinColumn.referencedColumn;
|
||||
|
||||
// JOIN `category` `category` ON `category`.`id` = `post`.`categoryId`
|
||||
const condition = ea(destinationTableAlias) + "." + ec(destinationTableReferencedColumn.fullName) + "=" + ea(parentAlias) + "." + ec(relation.name);
|
||||
const condition = relation.joinColumns.map(joinColumn => {
|
||||
return ea(destinationTableAlias) + "." + ec(joinColumn.referencedColumn.fullName) + "=" + ea(parentAlias) + "." + ec(joinColumn.name);
|
||||
}).join(" AND ");
|
||||
|
||||
return " " + joinAttr.direction + " JOIN " + et(destinationTableName) + " " + ea(destinationTableAlias) + " ON " + condition + appendedCondition;
|
||||
|
||||
} else if (relation.isOneToMany || relation.isOneToOneNotOwner) {
|
||||
const relationOwnerReferencedColumn = relation.inverseRelation.joinColumn.referencedColumn;
|
||||
|
||||
// JOIN `post` `post` ON `post`.`categoryId` = `category`.`id`
|
||||
const condition = ea(destinationTableAlias!) + "." + ec(relation.inverseRelation.name) + "=" + ea(parentAlias) + "." + ec(relationOwnerReferencedColumn.fullName);
|
||||
const condition = relation.inverseRelation.joinColumns.map(joinColumn => {
|
||||
return ea(destinationTableAlias!) + "." + ec(joinColumn.name) + "=" + ea(parentAlias) + "." + ec(joinColumn.referencedColumn.fullName);
|
||||
}).join(" AND ");
|
||||
|
||||
return " " + joinAttr.direction + " JOIN " + et(destinationTableName) + " " + ea(destinationTableAlias) + " ON " + condition + appendedCondition;
|
||||
|
||||
} else { // means many-to-many
|
||||
|
||||
@ -29,9 +29,10 @@ export class RelationCountLoader {
|
||||
// loadRelationCountAndMap("post.categoryCount", "post.categories")
|
||||
// we expect it to load array of post ids
|
||||
|
||||
// todo(dima): fix issues wit multiple primary keys and remove joinColumns[0]
|
||||
const relation = relationCountAttr.relation; // "category.posts"
|
||||
const inverseRelation = relation.inverseRelation; // "post.category"
|
||||
const referenceColumnName = inverseRelation.joinColumn.referencedColumn.propertyName; // post id
|
||||
const referenceColumnName = inverseRelation.joinColumns[0].referencedColumn.propertyName; // post id
|
||||
const inverseSideTable = relation.inverseEntityMetadata.table.target; // Post
|
||||
const inverseSideTableName = relation.inverseEntityMetadata.table.name; // post
|
||||
const inverseSideTableAlias = relationCountAttr.alias || inverseSideTableName; // if condition (custom query builder factory) is set then relationIdAttr.alias defined
|
||||
|
||||
@ -99,7 +99,7 @@ export class RelationIdAttribute {
|
||||
return parentAlias + "_" + relationProperty + "_relation_id";
|
||||
}
|
||||
|
||||
get referenceColumnName(): string {
|
||||
/*get referenceColumnName(): string {
|
||||
if (this.relation.isManyToOne || this.relation.isOneToOneOwner) {
|
||||
return this.relation.joinColumn.referencedColumn.fullName;
|
||||
|
||||
@ -109,7 +109,7 @@ export class RelationIdAttribute {
|
||||
} else {
|
||||
return this.relation.isOwning ? this.relation.joinTable.referencedColumn.fullName : this.relation.inverseRelation.joinTable.referencedColumn.fullName;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Metadata of the joined entity.
|
||||
|
||||
@ -55,7 +55,7 @@ export class RelationIdLoader {
|
||||
|
||||
const relation = relationIdAttr.relation; // "category.posts"
|
||||
const inverseRelation = relation.inverseRelation; // "post.category"
|
||||
const referenceColumnName = inverseRelation.joinColumn.referencedColumn.propertyName; // post id
|
||||
const referenceColumnName = inverseRelation.joinColumns[0].referencedColumn.propertyName; // post id
|
||||
const inverseSideTable = relation.inverseEntityMetadata.table.target; // Post
|
||||
const inverseSideTableName = relation.inverseEntityMetadata.table.name; // post
|
||||
const inverseSideTableAlias = relationIdAttr.alias || inverseSideTableName; // if condition (custom query builder factory) is set then relationIdAttr.alias defined
|
||||
|
||||
@ -46,7 +46,9 @@ export class PlainObjectToNewEntityTransformer {
|
||||
// todo: support custom initial fields here
|
||||
if (entity[relation.propertyName] instanceof Array) {
|
||||
const existRelation = entity[relation.propertyName].find((subEntity: any) => {
|
||||
return subEntity[relation.referencedColumnName] === subObject[relation.referencedColumnName];
|
||||
return relation.joinColumns.every(joinColumn => {
|
||||
return subEntity[joinColumn.name] === subObject[joinColumn.name];
|
||||
});
|
||||
});
|
||||
if (existRelation)
|
||||
this.groupAndTransform(subEntity, existRelation, relationMetadata);
|
||||
|
||||
@ -170,8 +170,8 @@ export class RawSqlResultsToEntityTransformer {
|
||||
|
||||
} else {
|
||||
let referenceColumnName: string;
|
||||
if (relation.isOneToMany || relation.isOneToOneNotOwner) {
|
||||
referenceColumnName = relation.inverseRelation.joinColumn.referencedColumn.fullName;
|
||||
if (relation.isOneToMany || relation.isOneToOneNotOwner) { // todo: fix joinColumns[0]
|
||||
referenceColumnName = relation.inverseRelation.joinColumns[0].referencedColumn.fullName;
|
||||
} else {
|
||||
referenceColumnName = relation.isOwning ? relation.joinTable.referencedColumn.fullName : relation.inverseRelation.joinTable.referencedColumn.fullName;
|
||||
}
|
||||
@ -202,7 +202,7 @@ export class RawSqlResultsToEntityTransformer {
|
||||
let referenceColumnName: string;
|
||||
|
||||
if (relation.isOneToMany) {
|
||||
referenceColumnName = relation.inverseRelation.joinColumn.referencedColumn.fullName;
|
||||
referenceColumnName = relation.inverseRelation.joinColumns[0].referencedColumn.fullName; // todo: fix joinColumns[0]
|
||||
|
||||
} else {
|
||||
referenceColumnName = relation.isOwning ? relation.joinTable.referencedColumn.fullName : relation.inverseRelation.joinTable.referencedColumn.fullName;
|
||||
|
||||
@ -9,6 +9,8 @@ import {QueryBuilder} from "../query-builder/QueryBuilder";
|
||||
|
||||
/**
|
||||
* Repository for more specific operations.
|
||||
*
|
||||
* @deprecated Don't use it yet
|
||||
*/
|
||||
export class SpecificRepository<Entity extends ObjectLiteral> {
|
||||
|
||||
@ -55,15 +57,17 @@ export class SpecificRepository<Entity extends ObjectLiteral> {
|
||||
if (relation.isManyToMany)
|
||||
throw new Error(`Many-to-many relation is not supported for this operation. Use #addToRelation method for many-to-many relations.`);
|
||||
|
||||
// todo: fix issues with joinColumns[0]
|
||||
|
||||
let table: string, values: any = {}, conditions: any = {};
|
||||
if (relation.isOwning) {
|
||||
table = relation.entityMetadata.table.name;
|
||||
values[relation.name] = relatedEntityId;
|
||||
conditions[relation.joinColumn.referencedColumn.fullName] = entityId;
|
||||
conditions[relation.joinColumns[0].referencedColumn.fullName] = entityId;
|
||||
} else {
|
||||
table = relation.inverseEntityMetadata.table.name;
|
||||
values[relation.inverseRelation.name] = relatedEntityId;
|
||||
conditions[relation.inverseRelation.joinColumn.referencedColumn.fullName] = entityId;
|
||||
conditions[relation.inverseRelation.joinColumns[0].referencedColumn.fullName] = entityId;
|
||||
}
|
||||
|
||||
|
||||
@ -98,6 +102,8 @@ export class SpecificRepository<Entity extends ObjectLiteral> {
|
||||
if (!this.metadata.hasRelationWithPropertyName(propertyName))
|
||||
throw new Error(`Relation ${propertyName} was not found in the ${this.metadata.name} entity.`);
|
||||
|
||||
// todo: fix issues with joinColumns[0]
|
||||
|
||||
const relation = this.metadata.findRelationWithPropertyName(propertyName);
|
||||
// if (relation.isManyToMany || relation.isOneToMany || relation.isOneToOneNotOwner)
|
||||
// throw new Error(`Only many-to-one and one-to-one with join column are supported for this operation. ${this.metadata.name}#${propertyName} relation type is ${relation.relationType}`);
|
||||
@ -108,11 +114,11 @@ export class SpecificRepository<Entity extends ObjectLiteral> {
|
||||
if (relation.isOwning) {
|
||||
table = relation.inverseEntityMetadata.table.name;
|
||||
values[relation.inverseRelation.name] = relatedEntityId;
|
||||
conditions[relation.inverseRelation.joinColumn.referencedColumn.fullName] = entityId;
|
||||
conditions[relation.inverseRelation.joinColumns[0].referencedColumn.fullName] = entityId;
|
||||
} else {
|
||||
table = relation.entityMetadata.table.name;
|
||||
values[relation.name] = relatedEntityId;
|
||||
conditions[relation.joinColumn.referencedColumn.fullName] = entityId;
|
||||
conditions[relation.joinColumns[0].referencedColumn.fullName] = entityId;
|
||||
}
|
||||
|
||||
const queryRunnerProvider = this.queryRunnerProvider ? this.queryRunnerProvider : new QueryRunnerProvider(this.connection.driver);
|
||||
|
||||
@ -9,7 +9,7 @@ import {Tag} from "./entity/Tag";
|
||||
|
||||
const should = chai.should();
|
||||
|
||||
describe.only("relations > custom-referenced-column-name", () => {
|
||||
describe("relations > custom-referenced-column-name", () => {
|
||||
|
||||
let connections: Connection[];
|
||||
before(async () => connections = await createTestingConnections({
|
||||
|
||||
@ -8,7 +8,7 @@ import {Category} from "./entity/Category";
|
||||
|
||||
const should = chai.should();
|
||||
|
||||
describe.skip("relations > multiple-primary-keys", () => {
|
||||
describe.only("relations > multiple-primary-keys", () => {
|
||||
|
||||
let connections: Connection[];
|
||||
before(async () => connections = await createTestingConnections({
|
||||
@ -28,7 +28,7 @@ describe.skip("relations > multiple-primary-keys", () => {
|
||||
|
||||
const category2 = new Category();
|
||||
category2.name = "airplanes";
|
||||
category1.type = "common-category";
|
||||
category2.type = "common-category";
|
||||
await connection.entityManager.persist(category2);
|
||||
|
||||
const post1 = new Post();
|
||||
@ -53,6 +53,7 @@ describe.skip("relations > multiple-primary-keys", () => {
|
||||
|
||||
let loadedPost = await connection.entityManager
|
||||
.createQueryBuilder(Post, "post")
|
||||
.leftJoinAndSelect("post.category", "category")
|
||||
.where("post.id = :id", { id: 1 })
|
||||
.getOne();
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user