This commit is contained in:
Umed Khudoiberdiev 2016-11-22 15:37:14 +05:00
parent c7aa4468c1
commit d8d5aaf3d4
16 changed files with 257 additions and 91 deletions

View File

@ -195,7 +195,7 @@ export class Gulpfile {
*/
@SequenceTask()
tests() {
return [/*"compile", */"tslint", "coveragePost", "coverageRemap"];
return ["compile", "tslint", "coveragePost", "coverageRemap"];
}
}

View File

@ -37,9 +37,9 @@
"gulp-mocha": "^3.0.1",
"gulp-replace": "^0.5.4",
"gulp-shell": "^0.5.1",
"gulp-sourcemaps": "^1.6.0",
"gulp-tslint": "^6.1.1",
"gulp-typescript": "^2.14.0",
"gulp-sourcemaps": "^1.9.1",
"gulp-tslint": "^7.0.1",
"gulp-typescript": "^3.1.3",
"gulpclass": "0.1.1",
"mariasql": "^0.2.6",
"mocha": "^3.0.2",
@ -48,30 +48,30 @@
"mysql2": "^1.1.0",
"oracledb": "^1.11.0",
"pg": "^6.1.0",
"remap-istanbul": "^0.6.4",
"remap-istanbul": "^0.7.0",
"sinon": "^1.17.6",
"sinon-chai": "^2.8.0",
"sqlite3": "^3.1.4",
"ts-node": "^1.3.0",
"tslint": "^3.15.0-dev.0",
"ts-node": "^1.7.0",
"tslint": "^4.0.1",
"tslint-stylish": "^2.1.0-beta",
"typescript": "^2.1.0-dev.20160919"
"typescript": "^2.1.1"
},
"dependencies": {
"@types/lodash": "4.14.35",
"@types/node": "^6.0.39",
"app-root-path": "^2.0.1",
"dependency-graph": "^0.5.0",
"glob": "^7.1.0",
"glob": "^7.1.1",
"lodash": "^4.16.1",
"moment": "^2.15.0",
"moment": "~2.15.0",
"path": "^0.12.7",
"reflect-metadata": "^0.1.8",
"require-all": "^2.0.0",
"sha1": "^1.1.1",
"url": "^0.11.0",
"yargonaut": "^1.1.2",
"yargs": "^5.0.0"
"yargs": "^6.4.0"
},
"scripts": {
"test": "node_modules/.bin/gulp tests"

View File

@ -18,7 +18,7 @@ export class EverythingSubscriber implements EntitySubscriberInterface<any> {
* Called before entity insertion.
*/
beforeUpdate(event: UpdateEvent<any>) {
console.log(`BEFORE ENTITY UPDATED: `, event.entity);
console.log(`BEFORE ENTITY UPDATED: `, event.persistEntity);
}
/**
@ -39,7 +39,7 @@ export class EverythingSubscriber implements EntitySubscriberInterface<any> {
* Called after entity insertion.
*/
afterUpdate(event: UpdateEvent<any>) {
console.log(`AFTER ENTITY UPDATED: `, event.entity);
console.log(`AFTER ENTITY UPDATED: `, event.persistEntity);
}
/**

View File

@ -104,6 +104,10 @@ export class EntityMetadataValidator {
if (relation.hasInverseSide && relation.inverseRelation.joinColumn && relation.isOneToOne)
throw new UsingJoinColumnOnlyOnOneSideAllowedError(entityMetadata, relation);
// check if join column really has referenced column
if (relation.joinColumn && !relation.joinColumn.referencedColumn)
throw new Error(`Join column does not have referenced column set`);
}
// if its a one-to-one relation and JoinColumn is missing on both sides of the relation

View File

@ -110,7 +110,7 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
// when executing insert/update operations we need to exclude entities scheduled for remove
// for junction operations we only get insert and update operations
console.log("subjects: ", this.loadedSubjects);
// console.log("subjects: ", this.loadedSubjects);
}
async remove(entity: Entity, metadata: EntityMetadata): Promise<void> {
@ -275,16 +275,13 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
* BIG NOTE: objects are being removed by cascades not only when relation is removed, but also when
* relation is replaced (e.g. changed with different object).
*/
protected async findCascadeRemovedEntitiesToLoad(subject: Subject): Promise<void> {
protected async findCascadeRemovedEntitiesToLoad(subject: Subject): Promise<void> { // todo: rename to findRemovedEntities ?
// note: we can't use extractRelationValuesFromEntity here because it does not handle empty arrays
const promises = subject.metadata.relations.map(async relation => {
const valueMetadata = relation.inverseEntityMetadata;
const qbAlias = valueMetadata.table.name;
// we only need relations that has cascade remove set
if (!relation.isCascadeRemove) return;
// todo: added here because of type safety. theoretically it should not be empty, but what to do? throw exception if its empty?
if (!subject.databaseEntity) return;
@ -293,6 +290,9 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
// and understand if relation was removed or not
if (relation.isOneToOneOwner || relation.isManyToOne) {
// we only work with cascade removes here
if (!relation.isCascadeRemove) return;
/**
* By example (one-to-one owner). Let's say we have a one-to-one relation between Post and Details.
* Post contains detailsId. It means he owns relation. Post has cascade remove with details.
@ -382,6 +382,9 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
// since column value that indicates relation is stored on inverse side
if (relation.isOneToOneNotOwner) {
// we only work with cascade removes here
if (!relation.isCascadeRemove) return; // todo: no
/**
* By example. Let's say we have a one-to-one relation between Post and Details.
* Post contains detailsId. It means he owns relation. Details has cascade remove with post.
@ -466,6 +469,9 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
// since column value that indicates relation is stored on inverse side
if (relation.isOneToMany || relation.isManyToMany) {
// we only work with cascade removes here
// if (!relation.isCascadeRemove && !relation.isCascadeUpdate) return;
/**
* By example. Let's say we have a one-to-many relation between Post and Details.
* Post contains detailsId. It means he owns relation.
@ -484,8 +490,6 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
// (example) "valueMetadata" - is an entity metadata of the Post object.
// (example) "subject.databaseEntity" - is a details object
console.log("one to many. I shall remove");
// (example) returns us referenced column (detail's id)
const relationIdInDatabaseEntity = relation.getOwnEntityRelationId(subject.databaseEntity);
@ -507,24 +511,41 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
let databaseEntities: ObjectLiteral[] = [];
if (relation.isManyToManyOwner) {
// we only need to load inverse entities if cascade removes are set
// because remove by cascades is the only reason we need relational entities here
if (!relation.isCascadeRemove) return;
databaseEntities = await this.connection
.getRepository<ObjectLiteral>(valueMetadata.target)
.createQueryBuilder(qbAlias)
.innerJoin(relation.junctionEntityMetadata.table.name, "persistenceJoinedRelation", "ON", "persistenceJoinedRelation." + relation.joinTable.joinColumnName + "=:id") // todo: need to escape alias and propertyName?
.innerJoin(relation.junctionEntityMetadata.table.name, "persistenceJoinedRelation", "ON",
"persistenceJoinedRelation." + relation.joinTable.joinColumnName + "=:id") // todo: need to escape alias and propertyName?
.setParameter("id", relationIdInDatabaseEntity)
.enableOption("RELATION_ID_VALUES")
.getResults();
} else if (relation.isManyToManyNotOwner) {
// we only need to load inverse entities if cascade removes are set
// because remove by cascades is the only reason we need relational entities here
if (!relation.isCascadeRemove) return;
databaseEntities = await this.connection
.getRepository<ObjectLiteral>(valueMetadata.target)
.createQueryBuilder(qbAlias)
.innerJoin(relation.junctionEntityMetadata.table.name, "persistenceJoinedRelation", "ON", "persistenceJoinedRelation." + relation.inverseRelation.joinTable.inverseJoinColumnName + "=:id") // todo: need to escape alias and propertyName?
.innerJoin(relation.junctionEntityMetadata.table.name, "persistenceJoinedRelation", "ON",
"persistenceJoinedRelation." + relation.inverseRelation.joinTable.inverseJoinColumnName + "=:id") // todo: need to escape alias and propertyName?
.setParameter("id", relationIdInDatabaseEntity)
.enableOption("RELATION_ID_VALUES")
.getResults();
} else {
} else { // this case can only be a oneToMany relation
// 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
// in the inverse side when entities is detached from one-to-many relation
databaseEntities = await this.connection
.getRepository<ObjectLiteral>(valueMetadata.target)
.createQueryBuilder(qbAlias)
@ -546,29 +567,46 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
}
});
//
// iterate throw loaded inverse entities to find out removed entities and inverse updated entities (only for one-to-many relation)
const promises = databaseEntities.map(async databaseEntity => {
// find a subject object of the related database entity
const relatedEntitySubject = this.loadedSubjects.findByDatabaseEntityLike(valueMetadata.target, databaseEntity);
if (!relatedEntitySubject) return; // should not be possible, anyway add it for type-safety
// if object is already marked as removed then no need to proceed because it already was proceed
// if we remove this it will cause a recursion
if (relatedEntitySubject.mustBeRemoved) return;
// if we remove this check it will cause a recursion
if (relatedEntitySubject.mustBeRemoved) return; // todo: add another check for entity in unsetRelations?
if (persistValue === null) {
relatedEntitySubject.mustBeRemoved = true;
await this.findCascadeRemovedEntitiesToLoad(relatedEntitySubject);
return;
}
const relatedValue = (persistValue as ObjectLiteral[]).find(persistedRelatedValue => {
// check if in persisted value there is a database value to understand if it was removed or not
let relatedValue = ((persistValue || []) as ObjectLiteral[]).find(persistedRelatedValue => {
const relatedId = relation.getInverseEntityRelationId(persistedRelatedValue);
return relatedId === relation.getInverseEntityRelationId(relatedEntitySubject!.databaseEntity!);
});
if (!relatedValue) {
relatedEntitySubject.mustBeRemoved = true;
await this.findCascadeRemovedEntitiesToLoad(relatedEntitySubject);
// if relation value is set to undefined then we don't do anything - simply skip any check and remove
// but if relation value is set to null then it means user wants to remove each entity in this relation
// OR
// value was removed from persisted value - means we need to mark it as removed
// and check if mark as removed all underlying entities that has cascade remove
if (persistValue === null || !relatedValue) {
// if cascade remove option is set then need to remove related entity
if (relation.isCascadeRemove) {
relatedEntitySubject.mustBeRemoved = true;
// mark as removed all underlying entities that has cascade remove
await this.findCascadeRemovedEntitiesToLoad(relatedEntitySubject);
return;
// if cascade remove option is not set then it means we simply need to remove
// reference to this entity from inverse side (from loaded database entity)
// this applies only on one-to-many relationship
} else if (relation.isOneToMany && relation.inverseRelation) {
relatedEntitySubject.unsetRelations.push(relation.inverseRelation);
return;
}
}
});

View File

@ -42,7 +42,7 @@ export class PersistOperationExecutor {
// persistOperation.log();
return Promise.resolve()
.then(() => this.broadcastBeforeEvents(persistOperation))
// .then(() => this.broadcastBeforeEvents(persistOperation))
.then(() => {
if (!this.queryRunner.isTransactionActive()) {
isTransactionStartedByItself = true;
@ -70,7 +70,7 @@ export class PersistOperationExecutor {
.then(() => this.updateIdsOfInsertedEntities(persistOperation))
.then(() => this.updateIdsOfRemovedEntities(persistOperation))
.then(() => this.updateSpecialColumnsInEntities(persistOperation))
.then(() => this.broadcastAfterEvents(persistOperation))
// .then(() => this.broadcastAfterEvents(persistOperation))
.catch(error => {
if (isTransactionStartedByItself === true) {
return this.queryRunner.rollbackTransaction()
@ -91,7 +91,7 @@ export class PersistOperationExecutor {
/**
* Broadcast all before persistment events - beforeInsert, beforeUpdate and beforeRemove events.
*/
private broadcastBeforeEvents(persistOperation: PersistOperation) {
const insertEvents = persistOperation.inserts.map(insertOperation => {
@ -117,11 +117,11 @@ export class PersistOperationExecutor {
return Promise.all(insertEvents)
.then(() => Promise.all(updateEvents))
.then(() => Promise.all(removeEvents)); // todo: do we really should send it in order?
}
} */
/**
* Broadcast all after persistment events - afterInsert, afterUpdate and afterRemove events.
*/
private broadcastAfterEvents(persistOperation: PersistOperation) {
const insertEvents = persistOperation.inserts.map(insertOperation => {
@ -147,7 +147,7 @@ export class PersistOperationExecutor {
return Promise.all(insertEvents)
.then(() => Promise.all(updateEvents))
.then(() => Promise.all(removeEvents)); // todo: do we really should send it in order?
}
} */
/**
* Executes insert operations.

View File

@ -1,4 +1,3 @@
import {PersistOperation} from "./operation/PersistOperation";
import {InsertOperation} from "./operation/InsertOperation";
import {UpdateByRelationOperation} from "./operation/UpdateByRelationOperation";
import {UpdateByInverseSideOperation} from "./operation/UpdateByInverseSideOperation";
@ -37,6 +36,7 @@ export class PersistSubjectExecutor {
const insertSubjects = subjects.filter(subject => subject.mustBeInserted);
const updateSubjects = subjects.filter(subject => subject.mustBeUpdated);
const removeSubjects = subjects.filter(subject => subject.mustBeRemoved);
const unsetRelationSubjects = subjects.filter(subject => subject.hasUnsetRelations);
// validation
// check if remove subject also must be inserted or updated - then we throw an exception
@ -70,6 +70,7 @@ export class PersistSubjectExecutor {
// await this.executeUpdateRelationsOperations(persistOperation); // todo: merge these operations with update operations?
// await this.executeUpdateInverseRelationsOperations(persistOperation); // todo: merge these operations with update operations?
await this.executeUpdateOperations(updateSubjects);
await this.executeUnsetRelationOperations(unsetRelationSubjects);
await this.executeRemoveOperations(removeSubjects);
// commit transaction if it was started by us
@ -141,15 +142,17 @@ export class PersistSubjectExecutor {
subject.metadata.relationsWithJoinColumns.forEach(relation => {
const referencedColumn = relation.joinColumn.referencedColumn;
insertSubjects.forEach(insertedSubject => {
if (subject.entity[relation.propertyName] === insertedSubject.entity) {
if (referencedColumn.isGenerated)
updateOptions[relation.name] = insertedSubject.newlyGeneratedId;
// todo: implement other special referenced column types (update date, create date, version, discriminator column, etc.)
if (subject.entity[relation.propertyName]) {
const relationId = subject.entity[relation.propertyName][referencedColumn.propertyName];
if (relationId) {
updateOptions[relation.name] = relationId;
}
});
}
const insertSubject = insertSubjects.find(insertedSubject => subject.entity[relation.propertyName] === insertedSubject.entity);
if (insertSubject && referencedColumn.isGenerated)
updateOptions[relation.name] = insertSubject.newlyGeneratedId;
});
if (Object.keys(updateOptions).length > 0) {
const conditions = subject.metadata.getDatabaseEntityIdMap(subject.entity) || subject.metadata.createSimpleDatabaseIdMap(subject.newlyGeneratedId);
@ -164,19 +167,22 @@ export class PersistSubjectExecutor {
if (value instanceof Array) {
value.forEach(subValue => {
insertSubjects.forEach(insertedSubject => {
if (subValue === insertedSubject.entity) {
if (referencedColumn.isGenerated) {
const conditions = insertedSubject.metadata.getDatabaseEntityIdMap(insertedSubject.entity) || insertedSubject.metadata.createSimpleDatabaseIdMap(insertedSubject.newlyGeneratedId);
const updateOptions = { [relation.inverseRelation.joinColumn.name]: subject.newlyGeneratedId };
const updatePromise = this.queryRunner.update(relation.inverseRelation.entityMetadata.table.name, updateOptions, conditions);
updatePromises.push(updatePromise);
}
let relationId = subValue[referencedColumn.propertyName];
if (!relationId) {
const insertSubject = insertSubjects.find(insertedSubject => subValue === insertedSubject.entity);
// todo: implement other special referenced column types (update date, create date, version, discriminator column, etc.)
}
});
if (insertSubject && referencedColumn.isGenerated)
relationId = insertSubject.newlyGeneratedId;
// todo: implement other special referenced column types (update date, create date, version, discriminator column, etc.)
}
const id = subject.entity[referencedColumn.propertyName] || subject.newlyGeneratedId;
const conditions = relation.inverseEntityMetadata.getDatabaseEntityIdMap(subValue) || relation.inverseEntityMetadata.createSimpleDatabaseIdMap(relationId);
const updateOptions = { [relation.inverseRelation.joinColumn.name]: id }; // todo: what if subject's id is not generated?
const updatePromise = this.queryRunner.update(relation.inverseEntityMetadata.table.name, updateOptions, conditions);
updatePromises.push(updatePromise);
});
}
@ -248,22 +254,38 @@ export class PersistSubjectExecutor {
/**
* Executes update relations operations.
*/
private executeUpdateRelationsOperations(updateSubjects: Subject[], insertSubjects: Subject[]) {
return Promise.all(updateSubjects.map(subject => {
return this.updateByRelation(subject, insertSubjects);
}));
}
} */
/**
* Executes update relations operations.
*/
private executeUpdateInverseRelationsOperations(persistOperation: PersistOperation) {
return Promise.all(persistOperation.updatesByInverseRelations.map(updateInverseOperation => {
return this.updateInverseRelation(updateInverseOperation, persistOperation.inserts);
private executeUnsetRelationOperations(subjects: Subject[]) {
return Promise.all(subjects.map(subject => {
return this.unsetRelations(subject);
}));
}
private async unsetRelations(subject: Subject) {
const values: ObjectLiteral = {};
subject.unsetRelations.forEach(relation => {
values[relation.name] = null;
});
if (!subject.databaseEntity)
throw new Error(`Internal error. Cannot unset relation of subject that does not have database entity.`);
const idMap = subject.metadata.getDatabaseEntityIdMap(subject.databaseEntity);
if (!idMap)
throw new Error(`Internal error. Cannot get id of the updating entity.`);
return this.queryRunner.update(subject.metadata.table.name, values, idMap);
}
/**
* Executes update operations.
*/
@ -273,7 +295,7 @@ export class PersistSubjectExecutor {
/**
* Executes remove relations operations.
*/
private executeRemoveRelationOperations(removeSubjects: Subject[]) {
return Promise.all(removeSubjects
// .filter(operation => {
@ -281,7 +303,7 @@ export class PersistSubjectExecutor {
// })
.map(subject => this.updateDeletedRelations(subject))
);
}
} */
/**
* Executes remove operations.
@ -509,7 +531,7 @@ export class PersistSubjectExecutor {
valueMaps.push(valueMap);
}
valueMap.values[subject.metadata.versionColumn.name] = this.connection.driver.preparePersistentValue(entity[subject.metadata.versionColumn.propertyName] + 1, metadata.versionColumn);
valueMap.values[subject.metadata.versionColumn.name] = this.connection.driver.preparePersistentValue(entity[subject.metadata.versionColumn.propertyName] + 1, subject.metadata.versionColumn);
}
if (subject.metadata.parentEntityMetadata) {
@ -535,22 +557,15 @@ export class PersistSubjectExecutor {
}
await Promise.all(valueMaps.map(valueMap => {
const conditions: ObjectLiteral = {};
if (valueMap.metadata.parentEntityMetadata && valueMap.metadata.parentEntityMetadata.inheritanceType === "class-table") { // valueMap.metadata.getEntityIdMap(entity)!
valueMap.metadata.parentIdColumns.forEach(column => {
conditions[column.name] = entity[column.propertyName];
});
const idMap = valueMap.metadata.getDatabaseEntityIdMap(entity);
if (!idMap)
throw new Error(`Internal error. Cannot get id of the updating entity.`);
} else {
valueMap.metadata.primaryColumns.forEach(column => {
conditions[column.name] = entity[column.propertyName];
});
}
return this.queryRunner.update(valueMap.tableName, valueMap.values, conditions);
return this.queryRunner.update(valueMap.tableName, valueMap.values, idMap);
}));
}
private updateDeletedRelations(subject: Subject) { // todo: check if both many-to-one deletions work too
/*private updateDeletedRelations(subject: Subject) { // todo: check if both many-to-one deletions work too
if (!subject.fromEntityId)
throw new Error(`remove operation does not have entity id`);
@ -563,7 +578,7 @@ export class PersistSubjectExecutor {
}
throw new Error("Remove operation relation is not set"); // todo: find out how its possible
}
}*/
private async remove(subject: Subject): Promise<void> {
if (subject.metadata.parentEntityMetadata) {
@ -734,7 +749,7 @@ export class PersistSubjectExecutor {
if (!parentEntityId && referencedColumn.isGenerated) {
const parentInsertedSubject = insertedSubjects.find(subject => subject.entity === parentEntity);
// todo: throw exception if parentInsertedSubject is not set
parentEntityId = parentInsertedSubject.newlyGeneratedId;
parentEntityId = parentInsertedSubject!.newlyGeneratedId;
} // todo: implement other special column types too
subject.treeLevel = await this.queryRunner.insertIntoClosureTable(tableName, newEntityId, parentEntityId, subject.metadata.hasTreeLevelColumn);

View File

@ -19,6 +19,12 @@ export class Subject { // todo: move entity with id creation into metadata? // t
canBeUpdated: boolean = false;
mustBeRemoved: boolean = false;
/**
* List of relations which need to be unset.
* This is used to update relation from inverse side.
*/
unsetRelations: RelationMetadata[] = [];
diffColumns: ColumnMetadata[] = [];
diffRelations: RelationMetadata[] = [];
@ -96,6 +102,10 @@ export class Subject { // todo: move entity with id creation into metadata? // t
return this.canBeUpdated && (this.diffColumns.length > 0 || this.diffRelations.length > 0);
}
get hasUnsetRelations(): boolean {
return this.unsetRelations.length > 0;
}
get databaseEntity(): ObjectLiteral|undefined {
return this._databaseEntity;
}

View File

@ -4,7 +4,7 @@ import {Connection} from "../../../../src/connection/Connection";
import {Post} from "./entity/Post";
import {Category} from "./entity/Category";
describe.only("persistence > cascade operations with custom name", () => {
describe("persistence > cascade operations with custom name", () => {
let connections: Connection[];
before(async () => connections = await setupTestingConnections({

View File

@ -13,7 +13,7 @@ export class User {
@Column()
name: string;
@ManyToOne(type => Post)
@ManyToOne(type => Post, { cascadeUpdate: true })
post: Post;
}

View File

@ -0,0 +1,20 @@
import {Table} from "../../../../../src/decorator/tables/Table";
import {PrimaryColumn} from "../../../../../src/decorator/columns/PrimaryColumn";
import {Column} from "../../../../../src/decorator/columns/Column";
import {Post} from "./Post";
import {OneToMany} from "../../../../../src/decorator/relations/OneToMany";
@Table()
export class Category {
@PrimaryColumn("int")
categoryId: number;
@Column()
name: string;
@OneToMany(type => Post, post => post.category)
posts: Post[];
}

View File

@ -0,0 +1,22 @@
import {Table} from "../../../../../src/decorator/tables/Table";
import {PrimaryColumn} from "../../../../../src/decorator/columns/PrimaryColumn";
import {Column} from "../../../../../src/decorator/columns/Column";
import {ManyToOne} from "../../../../../src/decorator/relations/ManyToOne";
import {Category} from "./Category";
@Table()
export class Post {
@PrimaryColumn("int")
firstId: number;
@PrimaryColumn("int")
secondId: number;
@Column()
title: string;
@ManyToOne(type => Category, category => category.posts)
category: Category;
}

View File

@ -0,0 +1,58 @@
import "reflect-metadata";
import {setupTestingConnections, closeConnections, reloadDatabases} from "../../../utils/test-utils";
import {Connection} from "../../../../src/connection/Connection";
import {Post} from "./entity/Post";
import {Category} from "./entity/Category";
describe("persistence > multi primary keys", () => {
let connections: Connection[];
before(async () => connections = await setupTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
schemaCreate: true,
dropSchemaOnConnection: true
}));
beforeEach(() => reloadDatabases(connections));
after(() => closeConnections(connections));
describe("insert", function () {
it("should insert entity when when there are multi column primary keys", () => Promise.all(connections.map(async connection => {
const post1 = new Post();
post1.title = "Hello Post #1";
post1.firstId = 1;
post1.secondId = 2;
await connection.entityManager.persist(post1);
// create first category and post and save them
const category1 = new Category();
category1.categoryId = 123;
category1.name = "Category saved by cascades #1";
category1.posts = [post1];
await connection.entityManager.persist(category1);
// now check
const posts = await connection.entityManager.find(Post, {
alias: "post",
innerJoinAndSelect: {
category: "post.category"
},
orderBy: {
"post.firstId": "ASC"
}
});
posts.should.be.eql([{
firstId: 1,
secondId: 2,
title: "Hello Post #1",
category: {
categoryId: 123,
name: "Category saved by cascades #1"
}
}]);
})));
});
});

View File

@ -4,7 +4,7 @@ import {Connection} from "../../../../src/connection/Connection";
import {Post} from "./entity/Post";
import {Category} from "./entity/Category";
describe("persistence > mutli primary keys", () => {
describe("persistence > multi primary keys", () => {
let connections: Connection[];
before(async () => connections = await setupTestingConnections({
@ -15,9 +15,9 @@ describe("persistence > mutli primary keys", () => {
beforeEach(() => reloadDatabases(connections));
after(() => closeConnections(connections));
describe("insertt", function () {
describe("insert", function () {
it("should insert entity when when there are mutli column primary keys", () => Promise.all(connections.map(async connection => {
it("should insert entity when when there are multi column primary keys", () => Promise.all(connections.map(async connection => {
const post1 = new Post();
post1.title = "Hello Post #1";
post1.firstId = 1;

View File

@ -356,7 +356,6 @@ describe("one-to-one", function() {
.getSingleResult();
}).then(loadedPost => {
console.log("loadedPost: ", loadedPost);
loadedPost.image.url = "new-logo.png";
return postRepository.persist(loadedPost);

View File

@ -19,7 +19,7 @@ describe("many-to-one", function() {
// -------------------------------------------------------------------------
const options: ConnectionOptions = {
driver: createTestingConnectionOptions("postgres"),
driver: createTestingConnectionOptions("mysql"),
entities: [Post, PostDetails, PostCategory, PostMetadata, PostImage, PostInformation, PostAuthor]
};