diff --git a/src/decorator/options/JoinColumnOptions.ts b/src/decorator/options/JoinColumnOptions.ts index c19e6086c..2663e961e 100644 --- a/src/decorator/options/JoinColumnOptions.ts +++ b/src/decorator/options/JoinColumnOptions.ts @@ -11,6 +11,6 @@ export interface JoinColumnOptions { /** * Name of the column in the entity to which this column is referenced. */ - readonly referencedColumnName?: string; + readonly referencedColumnName?: string; // TODO rename to referencedColumn } \ No newline at end of file diff --git a/src/metadata/ColumnMetadata.ts b/src/metadata/ColumnMetadata.ts index 49046b33d..074dbd081 100644 --- a/src/metadata/ColumnMetadata.ts +++ b/src/metadata/ColumnMetadata.ts @@ -239,16 +239,7 @@ export class ColumnMetadata { if (this.embeddedMetadata) { // because embedded can be inside other embedded we need to go recursively and collect all prefix name - const prefixes: string[] = []; - const buildPrefixRecursively = (embeddedMetadata: EmbeddedMetadata) => { - if (embeddedMetadata.parentEmbeddedMetadata) - buildPrefixRecursively(embeddedMetadata.parentEmbeddedMetadata); - - prefixes.push(embeddedMetadata.prefix); - }; - buildPrefixRecursively(this.embeddedMetadata); - - return this.entityMetadata.namingStrategy.embeddedColumnName(prefixes, this.propertyName, this._name); + return this.entityMetadata.namingStrategy.embeddedColumnName(this.embeddedMetadata.prefix, this.propertyName, this._name); } // if there is a naming strategy then use it to normalize propertyName as column name diff --git a/src/metadata/EntityMetadata.ts b/src/metadata/EntityMetadata.ts index c2d26aba8..02ab612a7 100644 --- a/src/metadata/EntityMetadata.ts +++ b/src/metadata/EntityMetadata.ts @@ -4,7 +4,6 @@ import {IndexMetadata} from "./IndexMetadata"; import {RelationTypes} from "./types/RelationTypes"; import {ForeignKeyMetadata} from "./ForeignKeyMetadata"; import {NamingStrategyInterface} from "../naming-strategy/NamingStrategyInterface"; -import {EntityMetadataArgs} from "../metadata-args/EntityMetadataArgs"; import {EmbeddedMetadata} from "./EmbeddedMetadata"; import {ObjectLiteral} from "../common/ObjectLiteral"; import {LazyRelationsWrapper} from "../lazy-loading/LazyRelationsWrapper"; @@ -209,7 +208,6 @@ export class EntityMetadata { /** * Columns of the entity, including columns that are coming from the embeddeds of this entity. - * @deprecated */ get columns(): ColumnMetadata[] { return this.embeddeds.reduce((columns, embedded) => columns.concat(embedded.columnsFromTree), this.ownColumns); diff --git a/src/naming-strategy/DefaultNamingStrategy.ts b/src/naming-strategy/DefaultNamingStrategy.ts index 752969230..2b9edddc4 100644 --- a/src/naming-strategy/DefaultNamingStrategy.ts +++ b/src/naming-strategy/DefaultNamingStrategy.ts @@ -1,7 +1,6 @@ import {NamingStrategyInterface} from "./NamingStrategyInterface"; import {RandomGenerator} from "../util/RandomGenerator"; import {camelCase, snakeCase} from "../util/StringUtils"; -import {TableType} from "../metadata/types/TableTypes"; /** * Naming strategy that is used by default. @@ -31,11 +30,9 @@ export class DefaultNamingStrategy implements NamingStrategyInterface { return customName ? customName : propertyName; } - embeddedColumnName(prefixes: string[], columnPropertyName: string, columnCustomName?: string): string { + embeddedColumnName(prefix: string, columnPropertyName: string, columnCustomName?: string): string { // todo: need snake case property name but only if its a property name and not a custom embedded prefix - prefixes = prefixes.filter(prefix => !!prefix); - const embeddedPropertyName = prefixes.length ? prefixes.join("_") + "_" : ""; - return camelCase(embeddedPropertyName + (columnCustomName ? columnCustomName : columnPropertyName)); + return camelCase(prefix + "_" + (columnCustomName ? columnCustomName : columnPropertyName)); } relationName(propertyName: string): string { diff --git a/src/naming-strategy/NamingStrategyInterface.ts b/src/naming-strategy/NamingStrategyInterface.ts index 9c472f5fa..846eebbf4 100644 --- a/src/naming-strategy/NamingStrategyInterface.ts +++ b/src/naming-strategy/NamingStrategyInterface.ts @@ -32,7 +32,7 @@ export interface NamingStrategyInterface { /** * Gets the embedded's column name from the given property name. */ - embeddedColumnName(prefixes: string[], columnPropertyName: string, columnCustomName?: string): string; + embeddedColumnName(prefix: string, columnPropertyName: string, columnCustomName?: string): string; /** * Gets the table's relation name from the given property name. diff --git a/src/persistence/SubjectBuilder.ts b/src/persistence/SubjectBuilder.ts index e5b7e87ae..13677e5cd 100644 --- a/src/persistence/SubjectBuilder.ts +++ b/src/persistence/SubjectBuilder.ts @@ -5,6 +5,7 @@ import {Subject} from "./Subject"; import {QueryRunnerProvider} from "../query-runner/QueryRunnerProvider"; import {SpecificRepository} from "../repository/SpecificRepository"; import {MongoDriver} from "../driver/mongodb/MongoDriver"; +import {OrmUtils} from "../util/OrmUtils"; /** * To be able to execute persistence operations we need to load all entities from the database we need. @@ -777,6 +778,7 @@ export class SubjectBuilder { const specificRepository = new SpecificRepository(this.connection, subject.metadata, this.queryRunnerProvider); existInverseEntityRelationIds = await specificRepository .findRelationIds(relation, subject.databaseEntity); + // console.log(existInverseEntityRelationIds); } // get all inverse entities relation ids that are "bind" to the currently persisted entity @@ -784,8 +786,7 @@ export class SubjectBuilder { .map(subRelationValue => { const joinColumns = relation.isOwning ? relation.inverseJoinColumns : relation.inverseRelation.joinColumns; return joinColumns.reduce((ids, joinColumn) => { - ids[joinColumn.referencedColumn!.propertyName] = subRelationValue[joinColumn.referencedColumn!.propertyName]; - return ids; + return OrmUtils.mergeDeep(ids, joinColumn.createValueMap(joinColumn.referencedColumn!.getEntityValue(subRelationValue))); // todo: duplicate. relation.createJoinColumnsIdMap(entity) ? }, {} as ObjectLiteral); }) .filter(subRelationValue => subRelationValue !== undefined && subRelationValue !== null); @@ -804,8 +805,7 @@ export class SubjectBuilder { const joinColumns = relation.isOwning ? relation.inverseJoinColumns : relation.inverseRelation.joinColumns; const ids = joinColumns.reduce((ids, joinColumn) => { - ids[joinColumn.referencedColumn!.propertyName] = subRelatedValue[joinColumn.referencedColumn!.propertyName]; - return ids; + return OrmUtils.mergeDeep(ids, joinColumn.createValueMap(joinColumn.referencedColumn!.getEntityValue(subRelatedValue))); // todo: duplicate. relation.createJoinColumnsIdMap(entity) ? }, {} as ObjectLiteral); return !existInverseEntityRelationIds.find(relationId => { return relation.inverseEntityMetadata.compareIds(relationId, ids); diff --git a/src/persistence/SubjectOperationExecutor.ts b/src/persistence/SubjectOperationExecutor.ts index a71d4d04a..7e469f91c 100644 --- a/src/persistence/SubjectOperationExecutor.ts +++ b/src/persistence/SubjectOperationExecutor.ts @@ -8,7 +8,6 @@ import {QueryRunnerProvider} from "../query-runner/QueryRunnerProvider"; import {EntityManager} from "../entity-manager/EntityManager"; import {PromiseUtils} from "../util/PromiseUtils"; import {MongoDriver} from "../driver/mongodb/MongoDriver"; -import {EmbeddedMetadata} from "../metadata/EmbeddedMetadata"; import {ColumnMetadata} from "../metadata/ColumnMetadata"; /** @@ -318,6 +317,8 @@ export class SubjectOperationExecutor { if (!Object.keys(conditions).length) return; + + const updatePromise = this.queryRunner.update(subject.metadata.tableName, updateOptions, conditions); updatePromises.push(updatePromise); } diff --git a/src/query-builder/relation-id/RelationIdAttribute.ts b/src/query-builder/relation-id/RelationIdAttribute.ts index b37f1ab30..d057674e6 100644 --- a/src/query-builder/relation-id/RelationIdAttribute.ts +++ b/src/query-builder/relation-id/RelationIdAttribute.ts @@ -37,8 +37,8 @@ export class RelationIdAttribute { // Constructor // ------------------------------------------------------------------------- - constructor(private expressionMap: QueryExpressionMap, - relationIdAttribute?: Partial) { + constructor(private queryExpressionMap: QueryExpressionMap, + relationIdAttribute?: Partial) { Object.assign(this, relationIdAttribute || {}); } @@ -60,7 +60,7 @@ export class RelationIdAttribute { if (!QueryBuilderUtils.isAliasProperty(this.relationName)) throw new Error(`Given value must be a string representation of alias property`); - return this.relationName.split(".")[0]; + return this.relationName.substr(0, this.relationName.indexOf(".")); } /** @@ -70,11 +70,11 @@ export class RelationIdAttribute { * This value is extracted from entityOrProperty value. * This is available when join was made using "post.category" syntax. */ - get relationProperty(): string { + get relationPropertyPath(): string { if (!QueryBuilderUtils.isAliasProperty(this.relationName)) throw new Error(`Given value must be a string representation of alias property`); - return this.relationName.split(".")[1]; + return this.relationName.substr(this.relationName.indexOf(".") + 1); } /** @@ -86,9 +86,8 @@ export class RelationIdAttribute { if (!QueryBuilderUtils.isAliasProperty(this.relationName)) throw new Error(`Given value must be a string representation of alias property`); - const [parentAlias, relationProperty] = this.relationName.split("."); - const relationOwnerSelection = this.expressionMap.findAliasByName(parentAlias); - return relationOwnerSelection.metadata.findRelationWithPropertyName(relationProperty); + const relationOwnerSelection = this.queryExpressionMap.findAliasByName(this.parentAlias!); + return relationOwnerSelection.metadata.findRelationWithPropertyPath(this.relationPropertyPath!); } /** @@ -99,18 +98,6 @@ export class RelationIdAttribute { return parentAlias + "_" + relationProperty + "_relation_id"; } - /*get referenceColumnName(): string { - if (this.relation.isManyToOne || this.relation.isOneToOneOwner) { - return this.relation.joinColumn.referencedColumn.fullName; - - } else if (this.relation.isOneToMany || this.relation.isOneToOneNotOwner) { - return this.relation.inverseRelation.joinColumn.referencedColumn.fullName; - - } else { - return this.relation.isOwning ? this.relation.joinTable.referencedColumn.fullName : this.relation.inverseRelation.joinTable.referencedColumn.fullName; - } - }*/ - /** * Metadata of the joined entity. * If extra condition without entity was joined, then it will return undefined. @@ -120,11 +107,11 @@ export class RelationIdAttribute { } get mapToPropertyParentAlias(): string { - return this.mapToProperty!.split(".")[0]; + return this.mapToProperty.substr(0, this.mapToProperty.indexOf(".")); } - get mapToPropertyPropertyName(): string { - return this.mapToProperty!.split(".")[1]; + get mapToPropertyPropertyPath(): string { + return this.mapToProperty.substr(this.mapToProperty.indexOf(".") + 1); } } \ No newline at end of file diff --git a/src/query-builder/relation-id/RelationIdLoadResult.ts b/src/query-builder/relation-id/RelationIdLoadResult.ts index 08eae7882..10ba54ee6 100644 --- a/src/query-builder/relation-id/RelationIdLoadResult.ts +++ b/src/query-builder/relation-id/RelationIdLoadResult.ts @@ -2,5 +2,5 @@ import {RelationIdAttribute} from "./RelationIdAttribute"; export interface RelationIdLoadResult { relationIdAttribute: RelationIdAttribute; - results: { id: any, parentId: any, manyToManyId?: any }[]; + results: any[]; } \ No newline at end of file diff --git a/src/query-builder/relation-id/RelationIdLoader.ts b/src/query-builder/relation-id/RelationIdLoader.ts index de428c38b..55f517c88 100644 --- a/src/query-builder/relation-id/RelationIdLoader.ts +++ b/src/query-builder/relation-id/RelationIdLoader.ts @@ -47,85 +47,54 @@ export class RelationIdLoader { } else if (relationIdAttr.relation.isOneToMany || relationIdAttr.relation.isOneToOneNotOwner) { // example: Post and Category - // loadRelationIdAndMap("category.postIds", "category.posts") - // we expect it to load array of post ids + // loadRelationIdAndMap("post.categoryIds", "post.categories") + // we expect it to load array of category ids - // todo: take post ids - they can be multiple - // todo: create test with multiple primary columns usage + const relation = relationIdAttr.relation; // "post.categories" + const inverseRelation = relation.inverseRelation; // "category.post" + const joinColumns = relation.isOwning ? relation.joinColumns : inverseRelation.joinColumns; + const table = relation.inverseEntityMetadata.target; // category + const tableName = relation.inverseEntityMetadata.tableName; // category + const tableAlias = relationIdAttr.alias || tableName; // if condition (custom query builder factory) is set then relationIdAttr.alias defined - const relation = relationIdAttr.relation; // "category.posts" - const inverseRelation = relation.inverseRelation; // "post.category" - const referenceColumnName = inverseRelation.joinColumns[0].referencedColumn!.propertyName; // post id - const inverseSideTable = relation.inverseEntityMetadata.target; // Post - const inverseSideTableName = relation.inverseEntityMetadata.tableName; // post - const inverseSideTableAlias = relationIdAttr.alias || inverseSideTableName; // if condition (custom query builder factory) is set then relationIdAttr.alias defined - const inverseSidePropertyName = inverseRelation.propertyPath; // "category" from "post.category" - - const referenceColumnValues = rawEntities - .map(rawEntity => rawEntity[relationIdAttr.parentAlias + "_" + referenceColumnName]) - .filter(value => !!value); - - /*const idMaps = rawEntities.map(rawEntity => { - return this.createIdMap(relationIdAttr.relation.entityMetadata.primaryColumns, relationIdAttr.parentAlias, rawEntity); - });*/ + const parameters: ObjectLiteral = {}; + const condition = rawEntities.map((rawEntity, index) => { + return joinColumns.map(joinColumn => { + const parameterName = joinColumn.databaseName + index; + parameters[parameterName] = rawEntity[relationIdAttr.parentAlias + "_" + joinColumn.referencedColumn!.databaseName]; + return tableAlias + "." + joinColumn.databaseName + " = :" + parameterName; + }).join(" AND "); + }).map(condition => "(" + condition + ")") + .join(" OR "); // ensure we won't perform redundant queries for joined data which was not found in selection // example: if post.category was not found in db then no need to execute query for category.imageIds - if (referenceColumnValues.length === 0) + if (!condition) return { relationIdAttribute: relationIdAttr, results: [] }; - // const joinParameters: ObjectLiteral = {}; - /*const joinCondition = idMaps.map((idMap, idMapIndex) => { - return "(" + Object.keys(idMap).map((idName, idIndex) => { - const parameterName = `var${idMapIndex}_${idIndex}`; - joinParameters[parameterName] = idMap[idName]; - return `${inverseSidePropertyName}.${idName} = :${parameterName}` - }).join(" AND ") + ")"; - }).join(" OR ");*/ - // generate query: - // SELECT post.id AS id, category.id AS parentId FROM post post INNER JOIN category category ON category.id=post.category AND category.id IN [:categoryIds] + // SELECT category.id, category.postId FROM category category ON category.postId = :postId const qb = new QueryBuilder(this.connection, this.queryRunnerProvider); - qb.select(inverseSideTableAlias + "." + inverseSidePropertyName, "manyToManyId"); - inverseRelation.entityMetadata.primaryColumns.forEach(primaryColumn => { - qb.addSelect(inverseSideTableAlias + "." + primaryColumn.databaseName, inverseSideTableAlias + "_" + primaryColumn.databaseName); + joinColumns.forEach(joinColumn => { + qb.addSelect(tableAlias + "." + joinColumn.databaseName, joinColumn.databaseName); }); - qb.from(inverseSideTable, inverseSideTableAlias) - .where(inverseSideTableAlias + "." + inverseSidePropertyName + " IN (:ids)") - .setParameter("ids", referenceColumnValues); + inverseRelation.entityMetadata.primaryColumns.forEach(primaryColumn => { + qb.addSelect(tableAlias + "." + primaryColumn.databaseName, primaryColumn.databaseName); + }); + + qb.from(table, tableAlias) + .where("(" + condition + ")") // need brackets because if we have additional condition and no brackets, it looks like (a = 1) OR (a = 2) AND b = 1, that is incorrect + .setParameters(parameters); // apply condition (custom query builder factory) if (relationIdAttr.queryBuilderFactory) relationIdAttr.queryBuilderFactory(qb); - const relationIdRawResults: any[] = await qb.getRawMany(); - - const results: { id: any[], parentId: any, manyToManyId?: any }[] = []; - relationIdRawResults.forEach(rawResult => { - let result = results.find(result => result.manyToManyId === rawResult["manyToManyId"]); - if (!result) { - result = { id: [], parentId: "", manyToManyId: rawResult["manyToManyId"] }; - results.push(result); - } - - if (inverseRelation.entityMetadata.primaryColumns.length === 1) { - result.id.push(rawResult[inverseSideTableAlias + "_" + inverseRelation.entityMetadata.firstPrimaryColumn.databaseName]); - } else { - result.id.push(inverseRelation.entityMetadata.primaryColumns.reduce((ids, primaryColumn) => { - ids[primaryColumn.propertyName] = rawResult[inverseSideTableAlias + "_" + primaryColumn.databaseName]; - return ids; - }, {} as ObjectLiteral)); - } - if (inverseRelation.isOneToOne) { - result.id = result.id[0]; - } - }); - return { relationIdAttribute: relationIdAttr, - results: results + results: await qb.getRawMany() }; } else { @@ -134,66 +103,60 @@ export class RelationIdLoader { // inverse side: loadRelationIdAndMap("category.postIds", "category.posts") // we expect it to load array of post ids - let joinTableColumnName: string; - let inverseJoinColumnName: string; - let firstJunctionColumn: ColumnMetadata; - let secondJunctionColumn: ColumnMetadata; - - if (relationIdAttr.relation.isOwning) { // todo fix joinColumns[0] - joinTableColumnName = relationIdAttr.relation.joinColumns[0].referencedColumn!.databaseName; - inverseJoinColumnName = relationIdAttr.relation.joinColumns[0].referencedColumn!.databaseName; - firstJunctionColumn = relationIdAttr.relation.junctionEntityMetadata.columns[0]; - secondJunctionColumn = relationIdAttr.relation.junctionEntityMetadata.columns[1]; - - } else { - joinTableColumnName = relationIdAttr.relation.inverseRelation.joinColumns[0].referencedColumn!.databaseName; - inverseJoinColumnName = relationIdAttr.relation.inverseRelation.joinColumns[0].referencedColumn!.databaseName; - firstJunctionColumn = relationIdAttr.relation.junctionEntityMetadata.columns[1]; - secondJunctionColumn = relationIdAttr.relation.junctionEntityMetadata.columns[0]; - } - - const referenceColumnValues = rawEntities - .map(rawEntity => rawEntity[relationIdAttr.parentAlias + "_" + joinTableColumnName]) - .filter(value => value); - - // ensure we won't perform redundant queries for joined data which was not found in selection - // example: if post.category was not found in db then no need to execute query for category.imageIds - if (referenceColumnValues.length === 0) - return { relationIdAttribute: relationIdAttr, results: [] }; - + const relation = relationIdAttr.relation; + const joinColumns = relation.isOwning ? relation.joinColumns : relation.inverseRelation.inverseJoinColumns; + const inverseJoinColumns = relation.isOwning ? relation.inverseJoinColumns : relation.inverseRelation.joinColumns; const junctionAlias = relationIdAttr.junctionAlias; const inverseSideTableName = relationIdAttr.joinInverseSideMetadata.tableName; const inverseSideTableAlias = relationIdAttr.alias || inverseSideTableName; - const junctionTableName = relationIdAttr.relation.junctionEntityMetadata.tableName; - const condition = junctionAlias + "." + firstJunctionColumn.propertyPath + " IN (" + referenceColumnValues + ")" + - " AND " + junctionAlias + "." + secondJunctionColumn.propertyPath + " = " + inverseSideTableAlias + "." + inverseJoinColumnName; + const junctionTableName = relation.junctionEntityMetadata.tableName; - const qb = new QueryBuilder(this.connection, this.queryRunnerProvider) - .select(inverseSideTableAlias + "." + inverseJoinColumnName, "id") - .addSelect(junctionAlias + "." + firstJunctionColumn.propertyPath, "manyToManyId") - .fromTable(inverseSideTableName, inverseSideTableAlias) + const mappedColumns = rawEntities.map(rawEntity => { + return joinColumns.reduce((map, joinColumn) => { + map[joinColumn.databaseName] = rawEntity[relationIdAttr.parentAlias + "_" + joinColumn.referencedColumn!.databaseName]; + return map; + }, {} as ObjectLiteral); + }); + + // ensure we won't perform redundant queries for joined data which was not found in selection + // example: if post.category was not found in db then no need to execute query for category.imageIds + if (mappedColumns.length === 0) + return { relationIdAttribute: relationIdAttr, results: [] }; + + const joinColumnConditions = mappedColumns.map(mappedColumn => { + return Object.keys(mappedColumn).map(key => { + return junctionAlias + "." + key + " = " + mappedColumn[key]; + }).join(" AND "); + }); + + const inverseJoinColumnCondition = inverseJoinColumns.map(joinColumn => { + return junctionAlias + "." + joinColumn.databaseName + " = " + inverseSideTableAlias + "." + joinColumn.referencedColumn!.databaseName; + }).join(" AND "); + + const condition = joinColumnConditions.map(condition => { + return "(" + condition + " AND " + inverseJoinColumnCondition + ")"; + }).join(" OR "); + + const qb = new QueryBuilder(this.connection, this.queryRunnerProvider); + + inverseJoinColumns.forEach(joinColumn => { + qb.addSelect(junctionAlias + "." + joinColumn.databaseName, joinColumn.databaseName); + }); + + joinColumns.forEach(joinColumn => { + qb.addSelect(junctionAlias + "." + joinColumn.databaseName, joinColumn.databaseName); + }); + + qb.fromTable(inverseSideTableName, inverseSideTableAlias) .innerJoin(junctionTableName, junctionAlias, condition); // apply condition (custom query builder factory) if (relationIdAttr.queryBuilderFactory) relationIdAttr.queryBuilderFactory(qb); - const relationIdRawResults: any[] = await qb.getRawMany(); - - const results: { id: any[], parentId: any, manyToManyId?: any }[] = []; - relationIdRawResults.forEach(rawResult => { - let result = results.find(result => result.manyToManyId === rawResult["manyToManyId"]); - if (!result) { - result = { id: [], parentId: "", manyToManyId: rawResult["manyToManyId"] }; - results.push(result); - } - - result.id.push(rawResult["id"]); - }); - return { relationIdAttribute: relationIdAttr, - results: results + results: await qb.getRawMany() }; } }); diff --git a/src/query-builder/relation-id/RelationIdLoaderOld.ts b/src/query-builder/relation-id/RelationIdLoaderOld.ts new file mode 100644 index 000000000..7887a31e4 --- /dev/null +++ b/src/query-builder/relation-id/RelationIdLoaderOld.ts @@ -0,0 +1,218 @@ +import {RelationIdAttribute} from "./RelationIdAttribute"; +import {ColumnMetadata} from "../../metadata/ColumnMetadata"; +import {QueryBuilder} from "../QueryBuilder"; +import {Connection} from "../../connection/Connection"; +import {QueryRunnerProvider} from "../../query-runner/QueryRunnerProvider"; +import {RelationIdLoadResult} from "./RelationIdLoadResult"; +import {ObjectLiteral} from "../../common/ObjectLiteral"; + +export class RelationIdLoaderOld { + + // ------------------------------------------------------------------------- + // Constructor + // ------------------------------------------------------------------------- + + constructor(protected connection: Connection, + protected queryRunnerProvider: QueryRunnerProvider|undefined, + protected relationIdAttributes: RelationIdAttribute[]) { + } + + // ------------------------------------------------------------------------- + // Public Methods + // ------------------------------------------------------------------------- + + async load(rawEntities: any[]): Promise { + + const promises = this.relationIdAttributes.map(async relationIdAttr => { + + if (relationIdAttr.relation.isManyToOne || relationIdAttr.relation.isOneToOneOwner) { + // example: Post and Tag + // loadRelationIdAndMap("post.tagId", "post.tag") post_tag + // we expect it to load id of tag + + if (relationIdAttr.queryBuilderFactory) + throw new Error(""); // todo: fix + + const results = rawEntities.map(rawEntity => { + return { + id: rawEntity[relationIdAttr.parentAlias + "_" + relationIdAttr.relation.name], + parentId: this.createIdMap(relationIdAttr.relation.entityMetadata.primaryColumns, relationIdAttr.parentAlias, rawEntity) + }; + }); + + return { + relationIdAttribute: relationIdAttr, + results: results + }; + + } else if (relationIdAttr.relation.isOneToMany || relationIdAttr.relation.isOneToOneNotOwner) { + // example: Post and Category + // loadRelationIdAndMap("category.postIds", "category.posts") + // we expect it to load array of post ids + + // todo: take post ids - they can be multiple + // todo: create test with multiple primary columns usage + + const relation = relationIdAttr.relation; // "category.posts" + const inverseRelation = relation.inverseRelation; // "post.category" + const referenceColumnName = inverseRelation.joinColumns[0].referencedColumn!.propertyName; // post id + const inverseSideTable = relation.inverseEntityMetadata.target; // Post + const inverseSideTableName = relation.inverseEntityMetadata.tableName; // post + const inverseSideTableAlias = relationIdAttr.alias || inverseSideTableName; // if condition (custom query builder factory) is set then relationIdAttr.alias defined + const inverseSidePropertyName = inverseRelation.propertyPath; // "category" from "post.category" + + const referenceColumnValues = rawEntities + .map(rawEntity => rawEntity[relationIdAttr.parentAlias + "_" + referenceColumnName]) + .filter(value => !!value); + + /*const idMaps = rawEntities.map(rawEntity => { + return this.createIdMap(relationIdAttr.relation.entityMetadata.primaryColumns, relationIdAttr.parentAlias, rawEntity); + });*/ + + // ensure we won't perform redundant queries for joined data which was not found in selection + // example: if post.category was not found in db then no need to execute query for category.imageIds + if (referenceColumnValues.length === 0) + return { relationIdAttribute: relationIdAttr, results: [] }; + + // const joinParameters: ObjectLiteral = {}; + /*const joinCondition = idMaps.map((idMap, idMapIndex) => { + return "(" + Object.keys(idMap).map((idName, idIndex) => { + const parameterName = `var${idMapIndex}_${idIndex}`; + joinParameters[parameterName] = idMap[idName]; + return `${inverseSidePropertyName}.${idName} = :${parameterName}` + }).join(" AND ") + ")"; + }).join(" OR ");*/ + + // generate query: + // SELECT post.id AS id, category.id AS parentId FROM post post INNER JOIN category category ON category.id=post.category AND category.id IN [:categoryIds] + const qb = new QueryBuilder(this.connection, this.queryRunnerProvider); + qb.select(inverseSideTableAlias + "." + inverseSidePropertyName, "manyToManyId"); + + inverseRelation.entityMetadata.primaryColumns.forEach(primaryColumn => { + qb.addSelect(inverseSideTableAlias + "." + primaryColumn.databaseName, inverseSideTableAlias + "_" + primaryColumn.databaseName); + }); + + qb.from(inverseSideTable, inverseSideTableAlias) + .where(inverseSideTableAlias + "." + inverseSidePropertyName + " IN (:ids)") + .setParameter("ids", referenceColumnValues); + + // apply condition (custom query builder factory) + if (relationIdAttr.queryBuilderFactory) + relationIdAttr.queryBuilderFactory(qb); + + const relationIdRawResults: any[] = await qb.getRawMany(); + + const results: { id: any[], parentId: any, manyToManyId?: any }[] = []; + relationIdRawResults.forEach(rawResult => { + let result = results.find(result => result.manyToManyId === rawResult["manyToManyId"]); + if (!result) { + result = { id: [], parentId: "", manyToManyId: rawResult["manyToManyId"] }; + results.push(result); + } + + if (inverseRelation.entityMetadata.primaryColumns.length === 1) { + result.id.push(rawResult[inverseSideTableAlias + "_" + inverseRelation.entityMetadata.firstPrimaryColumn.databaseName]); + } else { + result.id.push(inverseRelation.entityMetadata.primaryColumns.reduce((ids, primaryColumn) => { + ids[primaryColumn.propertyName] = rawResult[inverseSideTableAlias + "_" + primaryColumn.databaseName]; + return ids; + }, {} as ObjectLiteral)); + } + if (inverseRelation.isOneToOne) { + result.id = result.id[0]; + } + }); + + return { + relationIdAttribute: relationIdAttr, + results: results + }; + + } else { + // example: Post and Category + // owner side: loadRelationIdAndMap("post.categoryIds", "post.categories") + // inverse side: loadRelationIdAndMap("category.postIds", "category.posts") + // we expect it to load array of post ids + + let joinTableColumnName: string; + let inverseJoinColumnName: string; + let firstJunctionColumn: ColumnMetadata; + let secondJunctionColumn: ColumnMetadata; + + if (relationIdAttr.relation.isOwning) { // todo fix joinColumns[0] + joinTableColumnName = relationIdAttr.relation.joinColumns[0].referencedColumn!.databaseName; + inverseJoinColumnName = relationIdAttr.relation.joinColumns[0].referencedColumn!.databaseName; + firstJunctionColumn = relationIdAttr.relation.junctionEntityMetadata.columns[0]; + secondJunctionColumn = relationIdAttr.relation.junctionEntityMetadata.columns[1]; + + } else { + joinTableColumnName = relationIdAttr.relation.inverseRelation.joinColumns[0].referencedColumn!.databaseName; + inverseJoinColumnName = relationIdAttr.relation.inverseRelation.joinColumns[0].referencedColumn!.databaseName; + firstJunctionColumn = relationIdAttr.relation.junctionEntityMetadata.columns[1]; + secondJunctionColumn = relationIdAttr.relation.junctionEntityMetadata.columns[0]; + } + + const referenceColumnValues = rawEntities + .map(rawEntity => rawEntity[relationIdAttr.parentAlias + "_" + joinTableColumnName]) + .filter(value => value); + + // ensure we won't perform redundant queries for joined data which was not found in selection + // example: if post.category was not found in db then no need to execute query for category.imageIds + if (referenceColumnValues.length === 0) + return { relationIdAttribute: relationIdAttr, results: [] }; + + const junctionAlias = relationIdAttr.junctionAlias; + const inverseSideTableName = relationIdAttr.joinInverseSideMetadata.tableName; + const inverseSideTableAlias = relationIdAttr.alias || inverseSideTableName; + const junctionTableName = relationIdAttr.relation.junctionEntityMetadata.tableName; + const condition = junctionAlias + "." + firstJunctionColumn.propertyPath + " IN (" + referenceColumnValues + ")" + + " AND " + junctionAlias + "." + secondJunctionColumn.propertyPath + " = " + inverseSideTableAlias + "." + inverseJoinColumnName; + + /* const firstJunctionColumnConditions = firstJunctionColumns.map(joinColumn => { + return `${junctionAlias}.${joinColumn.propertyPath} = :${joinColumn.propertyPath}`; + }); + const secondJunctionColumnConditions = secondJunctionColumns.map(joinColumn => { + return `${junctionAlias}.${joinColumn.propertyPath} = :${joinColumn.propertyPath}`; + });*/ + + const qb = new QueryBuilder(this.connection, this.queryRunnerProvider) + .select(inverseSideTableAlias + "." + inverseJoinColumnName, "id") + .addSelect(junctionAlias + "." + firstJunctionColumn.propertyPath, "manyToManyId") + .fromTable(inverseSideTableName, inverseSideTableAlias) + .innerJoin(junctionTableName, junctionAlias, condition); + + // apply condition (custom query builder factory) + if (relationIdAttr.queryBuilderFactory) + relationIdAttr.queryBuilderFactory(qb); + + const relationIdRawResults: any[] = await qb.getRawMany(); + + const results: { id: any[], parentId: any, manyToManyId?: any }[] = []; + relationIdRawResults.forEach(rawResult => { + let result = results.find(result => result.manyToManyId === rawResult["manyToManyId"]); + if (!result) { + result = { id: [], parentId: "", manyToManyId: rawResult["manyToManyId"] }; + results.push(result); + } + + result.id.push(rawResult["id"]); + }); + + return { + relationIdAttribute: relationIdAttr, + results: results + }; + } + }); + + return Promise.all(promises); + } + + protected createIdMap(columns: ColumnMetadata[], parentAlias: string, rawEntity: any) { + return columns.reduce((idMap, primaryColumn) => { + idMap[primaryColumn.propertyName] = rawEntity[parentAlias + "_" + primaryColumn.databaseName]; + return idMap; + }, {} as ObjectLiteral); + } + +} \ No newline at end of file diff --git a/src/query-builder/transformer/RawSqlResultsToEntityTransformer.ts b/src/query-builder/transformer/RawSqlResultsToEntityTransformer.ts index 4f537eba3..608554ed4 100644 --- a/src/query-builder/transformer/RawSqlResultsToEntityTransformer.ts +++ b/src/query-builder/transformer/RawSqlResultsToEntityTransformer.ts @@ -5,6 +5,7 @@ import {ColumnMetadata} from "../../metadata/ColumnMetadata"; import {Alias} from "../Alias"; import {JoinAttribute} from "../JoinAttribute"; import {RelationCountLoadResult} from "../relation-count/RelationCountLoadResult"; +import {RelationMetadata} from "../../metadata/RelationMetadata"; /** * Transforms raw sql results returned from the database into entity object. @@ -142,7 +143,9 @@ export class RawSqlResultsToEntityTransformer { return; const relation = rawRelationIdResult.relationIdAttribute.relation; - let idMap: any, referenceColumnValue: any; + let idMap: any; + let valueMap: ObjectLiteral; + if (relation.isManyToOne || relation.isOneToOneOwner) { idMap = relation.entityMetadata.primaryColumns.reduce((idMap, primaryColumn) => { idMap[primaryColumn.propertyName] = rawSqlResults[0][alias.name + "_" + primaryColumn.databaseName]; @@ -150,26 +153,68 @@ export class RawSqlResultsToEntityTransformer { }, {} as ObjectLiteral); } else { - let referenceColumnName: string; - if (relation.isOneToMany || relation.isOneToOneNotOwner) { // todo: fix joinColumns[0] - referenceColumnName = relation.inverseRelation.joinColumns[0].referencedColumn!.databaseName; + if (relation.isOneToMany || relation.isOneToOneNotOwner) { + valueMap = this.createValueMapFromJoinColumns(relation, entity); } else { - referenceColumnName = relation.isOwning ? relation.joinColumns[0].referencedColumn!.databaseName : relation.inverseRelation.joinColumns[0].referencedColumn!.databaseName; + valueMap = this.createValueMapFromJoinColumns(relation, entity); } - referenceColumnValue = rawSqlResults[0][alias.name + "_" + referenceColumnName]; + /* referenceColumnValue = rawSqlResults[0][alias.name + "_" + referenceColumnName]; if (referenceColumnValue === undefined || referenceColumnValue === null) + return;*/ + if (valueMap === undefined || valueMap === null) return; } - rawRelationIdResult.results.forEach(result => { - if (result.parentId && !alias.metadata.compareIds(result.parentId, idMap)) + const referencedColumnResults = rawRelationIdResult.results.map(result => { + /* if (result.parentId && !alias.metadata.compareIds(result.parentId, idMap)) return; if (result.manyToManyId && result.manyToManyId !== referenceColumnValue) + return;*/ + + const entityPrimaryIds = this.extractEntityPrimaryIds(relation, result); + if (!alias.metadata.compareIds(entityPrimaryIds, valueMap)) return; - entity[rawRelationIdResult.relationIdAttribute.mapToPropertyPropertyName] = result.id; - hasData = true; - }); + let joinColumns: ColumnMetadata[]; + if (relation.isOneToMany || relation.isOneToOneNotOwner) { + joinColumns = relation.inverseEntityMetadata.primaryColumns.map(joinColumn => joinColumn); + } else { + if (relation.isManyToManyOwner) { + joinColumns = relation.inverseJoinColumns.map(joinColumn => joinColumn); + } else { + joinColumns = relation.inverseRelation.joinColumns.map(joinColumn => joinColumn); + } + } + + return joinColumns.reduce((referencedColumnResult, joinColumn) => { + if (joinColumns.length > 1) { + if (relation.isOneToMany || relation.isOneToOneNotOwner) { + referencedColumnResult[joinColumn.propertyName] = result[joinColumn.databaseName]; + } else { + referencedColumnResult[joinColumn.referencedColumn!.propertyName] = result[joinColumn.databaseName]; + } + } else { + referencedColumnResult = result[joinColumn.databaseName]; + } + return referencedColumnResult; + }, {} as ObjectLiteral); + }).filter(result => result); + + const properties = rawRelationIdResult.relationIdAttribute.mapToPropertyPropertyPath.split("."); + const mapToProperty = (properties: string[], map: ObjectLiteral, value: any): any => { + + const property = properties.shift(); + if (property && properties.length === 0) { + map[property] = value; + return map; + } else if (property && properties.length > 0) { + mapToProperty(properties, map[property], value); + } else { + return map; + } + }; + mapToProperty(properties, entity, referencedColumnResults); + hasData = true; }); return hasData; } @@ -204,4 +249,39 @@ export class RawSqlResultsToEntityTransformer { return hasData; } + private createValueMapFromJoinColumns(relation: RelationMetadata, entity: ObjectLiteral): ObjectLiteral { + let joinColumns: ColumnMetadata[]; + if (relation.isOneToMany || relation.isOneToOneNotOwner) { + joinColumns = relation.inverseRelation.joinColumns.map(joinColumn => joinColumn); + } else { + if (relation.isOwning) { + joinColumns = relation.joinColumns.map(joinColumn => joinColumn); + } else { + joinColumns = relation.inverseRelation.inverseJoinColumns.map(joinColumn => joinColumn); + } + } + return joinColumns.reduce((valueMap, joinColumn) => { + valueMap[joinColumn.databaseName] = joinColumn.referencedColumn!.getEntityValue(entity); + return valueMap; + }, {} as ObjectLiteral); + + } + + private extractEntityPrimaryIds(relation: RelationMetadata, relationIdRawResult: any) { + let joinColumns: ColumnMetadata[]; + if (relation.isOneToMany || relation.isOneToOneNotOwner) { + joinColumns = relation.inverseRelation.joinColumns.map(joinColumn => joinColumn); + } else { + if (relation.isOwning) { + joinColumns = relation.joinColumns.map(joinColumn => joinColumn); + } else { + joinColumns = relation.inverseRelation.inverseJoinColumns.map(joinColumn => joinColumn); + } + } + return joinColumns.reduce((data, joinColumn) => { + data[joinColumn.databaseName] = relationIdRawResult[joinColumn.databaseName]; + return data; + }, {} as ObjectLiteral); + } + } \ No newline at end of file diff --git a/src/repository/SpecificRepository.ts b/src/repository/SpecificRepository.ts index 0ae070841..f7b7ffa30 100644 --- a/src/repository/SpecificRepository.ts +++ b/src/repository/SpecificRepository.ts @@ -6,6 +6,7 @@ import {Subject} from "../persistence/Subject"; import {RelationMetadata} from "../metadata/RelationMetadata"; import {ColumnMetadata} from "../metadata/ColumnMetadata"; import {QueryBuilder} from "../query-builder/QueryBuilder"; +import {OrmUtils} from "../util/OrmUtils"; /** * Repository for more specific operations. @@ -439,6 +440,8 @@ export class SpecificRepository { const ec = (column: string) => this.connection.driver.escapeColumnName(column); let ids: any[] = []; + console.log("entityOrEntities:", entityOrEntities); + // console.log("entityIds:", entityIds); const promises = (entityIds as any[]).map((entityId: any) => { const qb = new QueryBuilder(this.connection, this.queryRunnerProvider); inverseEntityColumnNames.forEach(columnName => { @@ -460,12 +463,14 @@ export class SpecificRepository { // if (notInIds && notInIds.length > 0) // qb.andWhere(ea("junction") + "." + ec(inverseEntityColumnNames.fullName) + " NOT IN (:notInIds)", {notInIds: notInIds}); + // console.log(qb.getSql()); return qb.getRawMany() .then((results: any[]) => { + // console.log(results); results.forEach(result => { ids.push(Object.keys(result).reduce((id, key) => { const junctionColumnName = inverseEntityColumns.find(joinColumn => joinColumn.databaseName === key)!; - id[junctionColumnName.referencedColumn!.propertyName] = result[key]; + OrmUtils.mergeDeep(id, junctionColumnName.referencedColumn!.createValueMap(result[key])); return id; }, {} as ObjectLiteral)); }); // todo: prepare result? @@ -490,7 +495,7 @@ export class SpecificRepository { } else { if (entityOrEntities instanceof Object) { return columns.reduce((ids, column) => { - ids[column.databaseName] = entityOrEntities[column.propertyName]; + ids[column.databaseName] = column.getEntityValue(entityOrEntities); return ids; }, {} as ObjectLiteral); } else { diff --git a/test/benchmark/bulk-save/bulk-save.ts b/test/benchmark/bulk-save/bulk-save.ts new file mode 100644 index 000000000..2524404b8 --- /dev/null +++ b/test/benchmark/bulk-save/bulk-save.ts @@ -0,0 +1,39 @@ +import "reflect-metadata"; +import * as chai from "chai"; +import {Connection} from "../../../src/connection/Connection"; +import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../utils/test-utils"; +import {Post} from "./entity/Post"; + +const should = chai.should(); + +describe.skip("benchmark > bulk-save", () => { + + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + entities: [__dirname + "/entity/*{.js,.ts}"], + schemaCreate: true, + dropSchemaOnConnection: true, + })); + beforeEach(() => reloadTestingDatabases(connections)); + after(() => closeTestingConnections(connections)); + + + it("testing bulk save of 100 objects", () => Promise.all(connections.map(async connection => { + + const posts: Post[] = []; + + for(let i = 1; i <= 100; i++) { + const post = new Post(); + post.title = `Post #${i}`; + post.text = `Post #${i} text`; + post.likesCount = i; + post.commentsCount = i; + post.watchesCount = i; + posts.push(post); + } + + await connection.manager.persist(posts); + + }))); + +}); \ No newline at end of file diff --git a/test/benchmark/bulk-save/entity/Post.ts b/test/benchmark/bulk-save/entity/Post.ts new file mode 100644 index 000000000..2741ee43f --- /dev/null +++ b/test/benchmark/bulk-save/entity/Post.ts @@ -0,0 +1,26 @@ +import {Entity} from "../../../../src/decorator/entity/Entity"; +import {PrimaryGeneratedColumn} from "../../../../src/decorator/columns/PrimaryGeneratedColumn"; +import {Column} from "../../../../src/decorator/columns/Column"; + +@Entity() +export class Post { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + title: string; + + @Column({ type: "text" }) + text: string; + + @Column({ type: "int" }) + likesCount: number; + + @Column({ type: "int" }) + commentsCount: number; + + @Column({ type: "int" }) + watchesCount: number; + +} \ No newline at end of file diff --git a/test/functional/decorators/relation-id/relation-id-many-to-many/relation-id-decorator-many-to-many.ts b/test/functional/decorators/relation-id/relation-id-many-to-many/relation-id-decorator-many-to-many.ts index dcc2a9da9..c62700faf 100644 --- a/test/functional/decorators/relation-id/relation-id-many-to-many/relation-id-decorator-many-to-many.ts +++ b/test/functional/decorators/relation-id/relation-id-many-to-many/relation-id-decorator-many-to-many.ts @@ -24,27 +24,27 @@ describe("decorators > relation-id-decorator > many-to-many", () => { const category1 = new Category(); category1.name = "kids"; - await connection.entityManager.persist(category1); + await connection.manager.persist(category1); const category2 = new Category(); category2.name = "future"; - await connection.entityManager.persist(category2); + await connection.manager.persist(category2); const category3 = new Category(); category3.name = "cars"; - await connection.entityManager.persist(category3); + await connection.manager.persist(category3); const post = new Post(); post.title = "about kids"; post.categories = [category1, category2]; - await connection.entityManager.persist(post); + await connection.manager.persist(post); const post2 = new Post(); post2.title = "about BMW"; post2.categories = [category3]; - await connection.entityManager.persist(post2); + await connection.manager.persist(post2); - let loadedPosts = await connection.entityManager + let loadedPosts = await connection.manager .createQueryBuilder(Post, "post") .getMany(); @@ -54,7 +54,7 @@ describe("decorators > relation-id-decorator > many-to-many", () => { expect(loadedPosts![1].categoryIds).to.not.be.empty; expect(loadedPosts![1].categoryIds[0]).to.be.equal(3); - let loadedPost = await connection.entityManager + let loadedPost = await connection.manager .createQueryBuilder(Post, "post") .where("post.id = :id", { id: post.id }) .getOne(); @@ -69,29 +69,29 @@ describe("decorators > relation-id-decorator > many-to-many", () => { const category1 = new Category(); category1.name = "kids"; - await connection.entityManager.persist(category1); + await connection.manager.persist(category1); const category2 = new Category(); category2.name = "future"; category2.isRemoved = true; - await connection.entityManager.persist(category2); + await connection.manager.persist(category2); const category3 = new Category(); category3.name = "cars"; category3.isRemoved = true; - await connection.entityManager.persist(category3); + await connection.manager.persist(category3); const post = new Post(); post.title = "about kids"; post.categories = [category1, category2]; - await connection.entityManager.persist(post); + await connection.manager.persist(post); const post2 = new Post(); post2.title = "about BMW"; post2.categories = [category3]; - await connection.entityManager.persist(post2); + await connection.manager.persist(post2); - let loadedPosts = await connection.entityManager + let loadedPosts = await connection.manager .createQueryBuilder(Post, "post") .getMany(); @@ -101,7 +101,7 @@ describe("decorators > relation-id-decorator > many-to-many", () => { expect(loadedPosts![1].removedCategoryIds).to.not.be.empty; expect(loadedPosts![1].removedCategoryIds[0]).to.be.equal(3); - let loadedPost = await connection.entityManager + let loadedPost = await connection.manager .createQueryBuilder(Post, "post") .where("post.id = :id", { id: 1 }) .getOne(); @@ -116,18 +116,18 @@ describe("decorators > relation-id-decorator > many-to-many", () => { const category1 = new Category(); category1.name = "kids"; - await connection.entityManager.persist(category1); + await connection.manager.persist(category1); const category2 = new Category(); category2.name = "future"; - await connection.entityManager.persist(category2); + await connection.manager.persist(category2); const post = new Post(); post.title = "about kids"; post.subcategories = [category1, category2]; - await connection.entityManager.persist(post); + await connection.manager.persist(post); - let loadedPost = await connection.entityManager + let loadedPost = await connection.manager .createQueryBuilder(Post, "post") .where("post.id = :id", { id: post.id }) .getOne(); @@ -142,19 +142,19 @@ describe("decorators > relation-id-decorator > many-to-many", () => { const category1 = new Category(); category1.name = "kids"; - await connection.entityManager.persist(category1); + await connection.manager.persist(category1); const category2 = new Category(); category2.name = "future"; category2.isRemoved = true; - await connection.entityManager.persist(category2); + await connection.manager.persist(category2); const post = new Post(); post.title = "about kids"; post.subcategories = [category1, category2]; - await connection.entityManager.persist(post); + await connection.manager.persist(post); - let loadedPost = await connection.entityManager + let loadedPost = await connection.manager .createQueryBuilder(Post, "post") .where("post.id = :id", { id: post.id }) .getOne(); @@ -169,19 +169,19 @@ describe("decorators > relation-id-decorator > many-to-many", () => { const category = new Category(); category.name = "cars"; - await connection.entityManager.persist(category); + await connection.manager.persist(category); const post1 = new Post(); post1.title = "about BMW"; post1.categories = [category]; - await connection.entityManager.persist(post1); + await connection.manager.persist(post1); const post2 = new Post(); post2.title = "about Audi"; post2.categories = [category]; - await connection.entityManager.persist(post2); + await connection.manager.persist(post2); - let loadedCategory = await connection.entityManager + let loadedCategory = await connection.manager .createQueryBuilder(Category, "category") .where("category.id = :id", { id: category.id }) .getOne(); @@ -196,20 +196,20 @@ describe("decorators > relation-id-decorator > many-to-many", () => { const category = new Category(); category.name = "cars"; - await connection.entityManager.persist(category); + await connection.manager.persist(category); const post1 = new Post(); post1.title = "about BMW"; post1.categories = [category]; - await connection.entityManager.persist(post1); + await connection.manager.persist(post1); const post2 = new Post(); post2.title = "about Audi"; post2.isRemoved = true; post2.categories = [category]; - await connection.entityManager.persist(post2); + await connection.manager.persist(post2); - let loadedCategory = await connection.entityManager + let loadedCategory = await connection.manager .createQueryBuilder(Category, "category") .where("category.id = :id", { id: category.id }) .getOne(); @@ -224,41 +224,41 @@ describe("decorators > relation-id-decorator > many-to-many", () => { const image1 = new Image(); image1.name = "photo1"; - await connection.entityManager.persist(image1); + await connection.manager.persist(image1); const image2 = new Image(); image2.name = "photo2"; - await connection.entityManager.persist(image2); + await connection.manager.persist(image2); const image3 = new Image(); image3.name = "photo2"; - await connection.entityManager.persist(image3); + await connection.manager.persist(image3); const category1 = new Category(); category1.name = "cars"; category1.images = [image1, image2]; - await connection.entityManager.persist(category1); + await connection.manager.persist(category1); const category2 = new Category(); category2.name = "BMW"; - await connection.entityManager.persist(category2); + await connection.manager.persist(category2); const category3 = new Category(); category3.name = "Audi"; category3.images = [image3]; - await connection.entityManager.persist(category3); + await connection.manager.persist(category3); const post = new Post(); post.title = "about BMW"; post.categories = [category1, category2]; - await connection.entityManager.persist(post); + await connection.manager.persist(post); const post2 = new Post(); post2.title = "about Audi"; post2.categories = [category3]; - await connection.entityManager.persist(post2); + await connection.manager.persist(post2); - let loadedPosts = await connection.entityManager + let loadedPosts = await connection.manager .createQueryBuilder(Post, "post") .leftJoinAndSelect("post.categories", "categories") .addOrderBy("post.id, categories.id") @@ -281,7 +281,7 @@ describe("decorators > relation-id-decorator > many-to-many", () => { expect(loadedPosts![1].categories[0].imageIds.length).to.be.equal(1); expect(loadedPosts![1].categories[0].imageIds[0]).to.be.equal(3); - let loadedPost = await connection.entityManager + let loadedPost = await connection.manager .createQueryBuilder(Post, "post") .leftJoinAndSelect("post.categories", "categories") .addOrderBy("post.id, categories.id") @@ -304,23 +304,23 @@ describe("decorators > relation-id-decorator > many-to-many", () => { const image1 = new Image(); image1.name = "photo1"; - await connection.entityManager.persist(image1); + await connection.manager.persist(image1); const image2 = new Image(); image2.name = "photo2"; - await connection.entityManager.persist(image2); + await connection.manager.persist(image2); const category1 = new Category(); category1.name = "cars"; category1.images = [image1, image2]; - await connection.entityManager.persist(category1); + await connection.manager.persist(category1); const post = new Post(); post.title = "about BMW"; post.categories = [category1]; - await connection.entityManager.persist(post); + await connection.manager.persist(post); - let loadedPost = await connection.entityManager + let loadedPost = await connection.manager .createQueryBuilder(Post, "post") .leftJoinAndSelect("post.categories", "categories", "categories.id = :categoryId") .where("post.id = :id", { id: post.id }) @@ -336,45 +336,45 @@ describe("decorators > relation-id-decorator > many-to-many", () => { const image1 = new Image(); image1.name = "photo1"; - await connection.entityManager.persist(image1); + await connection.manager.persist(image1); const image2 = new Image(); image2.name = "photo2"; image2.isRemoved = true; - await connection.entityManager.persist(image2); + await connection.manager.persist(image2); const image3 = new Image(); image3.name = "photo2"; image3.isRemoved = true; - await connection.entityManager.persist(image3); + await connection.manager.persist(image3); const category1 = new Category(); category1.name = "cars"; category1.images = [image1, image2]; - await connection.entityManager.persist(category1); + await connection.manager.persist(category1); const category2 = new Category(); category2.name = "BMW"; category2.isRemoved = true; - await connection.entityManager.persist(category2); + await connection.manager.persist(category2); const category3 = new Category(); category3.name = "BMW"; category3.isRemoved = true; category3.images = [image3]; - await connection.entityManager.persist(category3); + await connection.manager.persist(category3); const post = new Post(); post.title = "about BMW"; post.categories = [category1, category2]; - await connection.entityManager.persist(post); + await connection.manager.persist(post); const post2 = new Post(); post2.title = "about BMW"; post2.categories = [category3]; - await connection.entityManager.persist(post2); + await connection.manager.persist(post2); - let loadedPosts = await connection.entityManager + let loadedPosts = await connection.manager .createQueryBuilder(Post, "post") .leftJoinAndSelect("post.categories", "categories") .addOrderBy("post.id, categories.id") @@ -395,7 +395,7 @@ describe("decorators > relation-id-decorator > many-to-many", () => { expect(loadedPosts![1].categories[0].removedImageIds.length).to.be.equal(1); expect(loadedPosts![1].categories[0].removedImageIds[0]).to.be.equal(3); - let loadedPost = await connection.entityManager + let loadedPost = await connection.manager .createQueryBuilder(Post, "post") .leftJoinAndSelect("post.categories", "categories") .addOrderBy("post.id, categories.id") diff --git a/test/functional/decorators/relation-id/relation-id-many-to-one/relation-id-decorator-many-to-one.ts b/test/functional/decorators/relation-id/relation-id-many-to-one/relation-id-decorator-many-to-one.ts index 60b1413d9..10b62da32 100644 --- a/test/functional/decorators/relation-id/relation-id-many-to-one/relation-id-decorator-many-to-one.ts +++ b/test/functional/decorators/relation-id/relation-id-many-to-one/relation-id-decorator-many-to-one.ts @@ -1,7 +1,7 @@ import "reflect-metadata"; import * as chai from "chai"; import {expect} from "chai"; -import {createTestingConnections, closeTestingConnections, reloadTestingDatabases} from "../../../../utils/test-utils"; +import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../../utils/test-utils"; import {Connection} from "../../../../../src/connection/Connection"; import {Post} from "./entity/Post"; import {Category} from "./entity/Category"; @@ -23,33 +23,33 @@ describe("decorators > relation-id-decorator > many-to-one", () => { const category1 = new Category(); category1.name = "cars"; - await connection.entityManager.persist(category1); + await connection.manager.persist(category1); const category2 = new Category(); category2.name = "airplanes"; - await connection.entityManager.persist(category2); + await connection.manager.persist(category2); const categoryByName1 = new Category(); categoryByName1.name = "BMW"; - await connection.entityManager.persist(categoryByName1); + await connection.manager.persist(categoryByName1); const categoryByName2 = new Category(); categoryByName2.name = "Boeing"; - await connection.entityManager.persist(categoryByName2); + await connection.manager.persist(categoryByName2); const post1 = new Post(); post1.title = "about BWM"; post1.category = category1; post1.categoryByName = categoryByName1; - await connection.entityManager.persist(post1); + await connection.manager.persist(post1); const post2 = new Post(); post2.title = "about Boeing"; post2.category = category2; post2.categoryByName = categoryByName2; - await connection.entityManager.persist(post2); + await connection.manager.persist(post2); - let loadedPosts = await connection.entityManager + let loadedPosts = await connection.manager .createQueryBuilder(Post, "post") .getMany(); @@ -62,7 +62,7 @@ describe("decorators > relation-id-decorator > many-to-one", () => { expect(loadedPosts![1].categoryName).to.not.be.empty; expect(loadedPosts![1].categoryName).to.be.equal("Boeing"); - let loadedPost = await connection.entityManager + let loadedPost = await connection.manager .createQueryBuilder(Post, "post") .where("post.id = :id", { id: 1 }) .getOne(); diff --git a/test/functional/decorators/relation-id/relation-id-one-to-many/relation-id-decorator-one-to-many.ts b/test/functional/decorators/relation-id/relation-id-one-to-many/relation-id-decorator-one-to-many.ts index 936785722..7352ebdaf 100644 --- a/test/functional/decorators/relation-id/relation-id-one-to-many/relation-id-decorator-one-to-many.ts +++ b/test/functional/decorators/relation-id/relation-id-one-to-many/relation-id-decorator-one-to-many.ts @@ -1,7 +1,7 @@ import "reflect-metadata"; import * as chai from "chai"; import {expect} from "chai"; -import {createTestingConnections, closeTestingConnections, reloadTestingDatabases} from "../../../../utils/test-utils"; +import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../../utils/test-utils"; import {Connection} from "../../../../../src/connection/Connection"; import {Category} from "./entity/Category"; import {Post} from "./entity/Post"; @@ -23,28 +23,28 @@ describe("decorators > relation-id > one-to-many", () => { const category = new Category(); category.name = "cars"; - await connection.entityManager.persist(category); + await connection.manager.persist(category); const category2 = new Category(); category2.name = "airplanes"; - await connection.entityManager.persist(category2); + await connection.manager.persist(category2); const post1 = new Post(); post1.title = "about BMW"; post1.category = category; - await connection.entityManager.persist(post1); + await connection.manager.persist(post1); const post2 = new Post(); post2.title = "about Audi"; post2.category = category; - await connection.entityManager.persist(post2); + await connection.manager.persist(post2); const post3 = new Post(); post3.title = "about Boeing"; post3.category = category2; - await connection.entityManager.persist(post3); + await connection.manager.persist(post3); - let loadedCategories = await connection.entityManager + let loadedCategories = await connection.manager .createQueryBuilder(Category, "category") .getMany(); @@ -54,7 +54,7 @@ describe("decorators > relation-id > one-to-many", () => { expect(loadedCategories![1].postIds.length).to.be.equal(1); expect(loadedCategories![1].postIds[0]).to.be.equal(3); - let loadedCategory = await connection.entityManager + let loadedCategory = await connection.manager .createQueryBuilder(Category, "category") .where("category.id = :id", { id: 1 }) .getOne(); @@ -68,30 +68,30 @@ describe("decorators > relation-id > one-to-many", () => { const category = new Category(); category.name = "cars"; - await connection.entityManager.persist(category); + await connection.manager.persist(category); const category2 = new Category(); category2.name = "airplanes"; - await connection.entityManager.persist(category2); + await connection.manager.persist(category2); const post1 = new Post(); post1.title = "about BMW"; post1.category = category; - await connection.entityManager.persist(post1); + await connection.manager.persist(post1); const post2 = new Post(); post2.title = "about Audi"; post2.category = category; post2.isRemoved = true; - await connection.entityManager.persist(post2); + await connection.manager.persist(post2); const post3 = new Post(); post3.title = "about Boeing"; post3.category = category2; post3.isRemoved = true; - await connection.entityManager.persist(post3); + await connection.manager.persist(post3); - let loadedCategories = await connection.entityManager + let loadedCategories = await connection.manager .createQueryBuilder(Category, "category") .getMany(); @@ -100,7 +100,7 @@ describe("decorators > relation-id > one-to-many", () => { expect(loadedCategories![0].removedPostIds[0]).to.be.equal(2); expect(loadedCategories![1].removedPostIds[0]).to.be.equal(3); - let loadedCategory = await connection.entityManager + let loadedCategory = await connection.manager .createQueryBuilder(Category, "category") .where("category.id = :id", { id: 1 }) .getOne(); diff --git a/test/functional/decorators/relation-id/relation-id-one-to-one/relation-id-decorator-one-to-one.ts b/test/functional/decorators/relation-id/relation-id-one-to-one/relation-id-decorator-one-to-one.ts index bc76da517..7c54dfdc0 100644 --- a/test/functional/decorators/relation-id/relation-id-one-to-one/relation-id-decorator-one-to-one.ts +++ b/test/functional/decorators/relation-id/relation-id-one-to-one/relation-id-decorator-one-to-one.ts @@ -1,7 +1,7 @@ import "reflect-metadata"; import * as chai from "chai"; import {expect} from "chai"; -import {createTestingConnections, closeTestingConnections, reloadTestingDatabases} from "../../../../utils/test-utils"; +import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../../utils/test-utils"; import {Connection} from "../../../../../src/connection/Connection"; import {Category} from "./entity/Category"; import {Post} from "./entity/Post"; @@ -23,33 +23,33 @@ describe("decorators > relation-id > one-to-one", () => { const category1 = new Category(); category1.name = "cars"; - await connection.entityManager.persist(category1); + await connection.manager.persist(category1); const category2 = new Category(); category2.name = "airplanes"; - await connection.entityManager.persist(category2); + await connection.manager.persist(category2); const categoryByName1 = new Category(); categoryByName1.name = "BMW"; - await connection.entityManager.persist(categoryByName1); + await connection.manager.persist(categoryByName1); const categoryByName2 = new Category(); categoryByName2.name = "Boeing"; - await connection.entityManager.persist(categoryByName2); + await connection.manager.persist(categoryByName2); const post1 = new Post(); post1.title = "about BMW"; post1.category = category1; post1.categoryByName = categoryByName1; - await connection.entityManager.persist(post1); + await connection.manager.persist(post1); const post2 = new Post(); post2.title = "about Boeing"; post2.category = category2; post2.categoryByName = categoryByName2; - await connection.entityManager.persist(post2); + await connection.manager.persist(post2); - let loadedPosts = await connection.entityManager + let loadedPosts = await connection.manager .createQueryBuilder(Post, "post") .addOrderBy("post.id") .getMany(); @@ -63,7 +63,7 @@ describe("decorators > relation-id > one-to-one", () => { expect(loadedPosts![1].categoryName).to.not.be.empty; expect(loadedPosts![1].categoryName).to.be.equal("Boeing"); - let loadedPost = await connection.entityManager + let loadedPost = await connection.manager .createQueryBuilder(Post, "post") .where("post.id = :id", { id: 1 }) .getOne(); @@ -78,23 +78,23 @@ describe("decorators > relation-id > one-to-one", () => { const category1 = new Category(); category1.name = "cars"; - await connection.entityManager.persist(category1); + await connection.manager.persist(category1); const category2 = new Category(); category2.name = "airplanes"; - await connection.entityManager.persist(category2); + await connection.manager.persist(category2); const post1 = new Post(); post1.title = "about BMW"; post1.category2 = category1; - await connection.entityManager.persist(post1); + await connection.manager.persist(post1); const post2 = new Post(); post2.title = "about Boeing"; post2.category2 = category2; - await connection.entityManager.persist(post2); + await connection.manager.persist(post2); - let loadedCategories = await connection.entityManager + let loadedCategories = await connection.manager .createQueryBuilder(Category, "category") .addOrderBy("category.id") .getMany(); @@ -104,7 +104,7 @@ describe("decorators > relation-id > one-to-one", () => { expect(loadedCategories![1].postId).to.not.be.empty; expect(loadedCategories![1].postId).to.be.equal(2); - let loadedCategory = await connection.entityManager + let loadedCategory = await connection.manager .createQueryBuilder(Category, "category") .where("category.id = :id", { id: 1 }) .getOne(); diff --git a/test/functional/embedded/embedded-many-to-many-case3/embedded-many-to-many-case3.ts b/test/functional/embedded/embedded-many-to-many-case3/embedded-many-to-many-case3.ts index 29763e45a..8d0cbaf4f 100644 --- a/test/functional/embedded/embedded-many-to-many-case3/embedded-many-to-many-case3.ts +++ b/test/functional/embedded/embedded-many-to-many-case3/embedded-many-to-many-case3.ts @@ -20,7 +20,7 @@ describe.skip("embedded > embedded-many-to-many-case3", () => { describe("owner side", () => { - it("should insert, load, update and remove entities with embeddeds when embedded entity having ManyToMany relation with multiple primary keys (one PK en each embed)", () => Promise.all(connections.map(async connection => { + it("should insert, load, update and remove entities with embeddeds when embedded entity having ManyToMany relation with multiple primary keys (one PK in each embed)", () => Promise.all(connections.map(async connection => { const user1 = new User(); user1.name = "Alice"; @@ -197,7 +197,7 @@ describe.skip("embedded > embedded-many-to-many-case3", () => { describe("inverse side", () => { - it("should insert, load, update and remove entities with embeddeds when embedded entity having ManyToMany relation with multiple primary keys (one PK en each embed)", () => Promise.all(connections.map(async connection => { + it("should insert, load, update and remove entities with embeddeds when embedded entity having ManyToMany relation with multiple primary keys (one PK in each embed)", () => Promise.all(connections.map(async connection => { const post1 = new Post(); post1.id = 1; diff --git a/test/functional/embedded/embedded-many-to-one-case3/embedded-many-to-one-case3.ts b/test/functional/embedded/embedded-many-to-one-case3/embedded-many-to-one-case3.ts index d15c2ddb3..7ca83a7eb 100644 --- a/test/functional/embedded/embedded-many-to-one-case3/embedded-many-to-one-case3.ts +++ b/test/functional/embedded/embedded-many-to-one-case3/embedded-many-to-one-case3.ts @@ -20,7 +20,7 @@ describe("embedded > embedded-many-to-one-case3", () => { describe("owner side", () => { - it("should insert, load, update and remove entities with embeddeds when embedded entity having ManyToOne relation with multiple primary keys (one PK en each embed)", () => Promise.all(connections.map(async connection => { + it("should insert, load, update and remove entities with embeddeds when embedded entity having ManyToOne relation with multiple primary keys (one PK in each embed)", () => Promise.all(connections.map(async connection => { const user1 = new User(); user1.name = "Alice"; @@ -168,7 +168,7 @@ describe("embedded > embedded-many-to-one-case3", () => { describe("inverse side", () => { - it("should insert, load, update and remove entities with embeddeds when embedded entity having ManyToOne relation with multiple primary keys (one PK en each embed)", () => Promise.all(connections.map(async connection => { + it("should insert, load, update and remove entities with embeddeds when embedded entity having ManyToOne relation with multiple primary keys (one PK in each embed)", () => Promise.all(connections.map(async connection => { const post1 = new Post(); post1.id = 1; diff --git a/test/functional/embedded/embedded-many-to-one-case5/embedded-many-to-one-case5.ts b/test/functional/embedded/embedded-many-to-one-case5/embedded-many-to-one-case5.ts new file mode 100644 index 000000000..c8156c5d2 --- /dev/null +++ b/test/functional/embedded/embedded-many-to-one-case5/embedded-many-to-one-case5.ts @@ -0,0 +1,389 @@ +import "reflect-metadata"; +import {Post} from "./entity/Post"; +import {Counters} from "./entity/Counters"; +import {Connection} from "../../../../src/connection/Connection"; +import {expect} from "chai"; +import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../utils/test-utils"; +import {Subcounters} from "./entity/Subcounters"; +import {User} from "./entity/User"; + +describe("embedded > embedded-many-to-one-case5", () => { + + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + entities: [__dirname + "/entity/*{.js,.ts}"], + schemaCreate: true, + dropSchemaOnConnection: true, + })); + beforeEach(() => reloadTestingDatabases(connections)); + after(() => closeTestingConnections(connections)); + + describe("owner side", () => { + + it("should insert, load, update and remove entities with embeddeds when embedded entity having ManyToOne relation with multiple primary keys (multiple keys in both sides)", () => Promise.all(connections.map(async connection => { + + const user1 = new User(); + user1.id = 1; + user1.personId = 1; + user1.name = "Alice"; + await connection.getRepository(User).persist(user1); + + const user2 = new User(); + user2.id = 2; + user2.personId = 2; + user2.name = "Bob"; + await connection.getRepository(User).persist(user2); + + const user3 = new User(); + user3.id = 3; + user3.personId = 3; + user3.name = "Clara"; + await connection.getRepository(User).persist(user3); + + const postRepository = connection.getRepository(Post); + + const post1 = new Post(); + post1.id = 1; + post1.title = "About cars"; + post1.counters = new Counters(); + post1.counters.code = 1; + post1.counters.comments = 1; + post1.counters.favorites = 2; + post1.counters.likes = 3; + post1.counters.likedUser = user1; + post1.counters.subcounters = new Subcounters(); + post1.counters.subcounters.version = 1; + post1.counters.subcounters.watches = 5; + await postRepository.persist(post1); + + const post2 = new Post(); + post2.id = 2; + post2.title = "About airplanes"; + post2.counters = new Counters(); + post2.counters.code = 2; + post2.counters.comments = 2; + post2.counters.favorites = 3; + post2.counters.likes = 4; + post2.counters.likedUser = user2; + post2.counters.subcounters = new Subcounters(); + post2.counters.subcounters.version = 1; + post2.counters.subcounters.watches = 10; + await postRepository.persist(post2); + + let loadedPosts = await connection.manager + .createQueryBuilder(Post, "post") + .leftJoinAndSelect("post.counters.likedUser", "likedUser") + .orderBy("post.id") + .getMany(); + + expect(loadedPosts[0].should.be.eql( + { + id: 1, + title: "About cars", + counters: { + code: 1, + comments: 1, + favorites: 2, + likes: 3, + likedUser: { id: 1, personId: 1, name: "Alice" }, + subcounters: { + version: 1, + watches: 5 + } + } + } + )); + expect(loadedPosts[1].should.be.eql( + { + id: 2, + title: "About airplanes", + counters: { + code: 2, + comments: 2, + favorites: 3, + likes: 4, + likedUser: { id: 2, personId: 2, name: "Bob" }, + subcounters: { + version: 1, + watches: 10 + } + } + } + )); + + let loadedPost = await connection.manager + .createQueryBuilder(Post, "post") + .leftJoinAndSelect("post.counters.likedUser", "likedUser") + .where("post.id = :id", { id: 1 }) + .getOne(); + + expect(loadedPost!.should.be.eql( + { + id: 1, + title: "About cars", + counters: { + code: 1, + comments: 1, + favorites: 2, + likes: 3, + likedUser: { id: 1, personId: 1, name: "Alice" }, + subcounters: { + version: 1, + watches: 5 + } + } + } + )); + + loadedPost!.counters.favorites += 1; + loadedPost!.counters.subcounters.watches += 1; + loadedPost!.counters.likedUser = user3; + await postRepository.persist(loadedPost!); + + loadedPost = await connection.manager + .createQueryBuilder(Post, "post") + .leftJoinAndSelect("post.counters.likedUser", "likedUser") + .where("post.id = :id", { id: 1 }) + .getOne(); + + expect(loadedPost!.should.be.eql( + { + id: 1, + title: "About cars", + counters: { + code: 1, + comments: 1, + favorites: 3, + likes: 3, + likedUser: { id: 3, personId: 3, name: "Clara" }, + subcounters: { + version: 1, + watches: 6 + } + } + } + )); + + await postRepository.remove(loadedPost!); + + loadedPosts = (await postRepository.find())!; + expect(loadedPosts.length).to.be.equal(1); + expect(loadedPosts[0].title).to.be.equal("About airplanes"); + }))); + }); + + describe("inverse side", () => { + + it("should insert, load, update and remove entities with embeddeds when embedded entity having ManyToOne relation with multiple primary keys (multiple keys in both sides)", () => Promise.all(connections.map(async connection => { + + const post1 = new Post(); + post1.id = 1; + post1.title = "About cars"; + post1.counters = new Counters(); + post1.counters.code = 1; + post1.counters.comments = 1; + post1.counters.favorites = 2; + post1.counters.likes = 3; + post1.counters.subcounters = new Subcounters(); + post1.counters.subcounters.version = 1; + post1.counters.subcounters.watches = 5; + await connection.getRepository(Post).persist(post1); + + const post2 = new Post(); + post2.id = 2; + post2.title = "About airplanes"; + post2.counters = new Counters(); + post2.counters.code = 2; + post2.counters.comments = 2; + post2.counters.favorites = 3; + post2.counters.likes = 4; + post2.counters.subcounters = new Subcounters(); + post2.counters.subcounters.version = 1; + post2.counters.subcounters.watches = 10; + await connection.getRepository(Post).persist(post2); + + const post3 = new Post(); + post3.id = 3; + post3.title = "About horses"; + post3.counters = new Counters(); + post3.counters.code = 3; + post3.counters.comments = 5; + post3.counters.favorites = 10; + post3.counters.likes = 15; + post3.counters.subcounters = new Subcounters(); + post3.counters.subcounters.version = 1; + post3.counters.subcounters.watches = 30; + await connection.getRepository(Post).persist(post3); + + const user1 = new User(); + user1.id = 1; + user1.personId = 1; + user1.name = "Alice"; + user1.likedPosts = [post1, post2]; + await connection.getRepository(User).persist(user1); + + const user2 = new User(); + user2.id = 2; + user2.personId = 2; + user2.name = "Bob"; + user2.likedPosts = [post3]; + await connection.getRepository(User).persist(user2); + + let loadedUsers = await connection.manager + .createQueryBuilder(User, "user") + .leftJoinAndSelect("user.likedPosts", "likedPost") + .orderBy("user.id, likedPost.id") + .getMany(); + + expect(loadedUsers[0].should.be.eql( + { + id: 1, + personId: 1, + name: "Alice", + likedPosts: [ + { + id: 1, + title: "About cars", + counters: { + code: 1, + comments: 1, + favorites: 2, + likes: 3, + subcounters: { + version: 1, + watches: 5 + } + } + }, + { + id: 2, + title: "About airplanes", + counters: { + code: 2, + comments: 2, + favorites: 3, + likes: 4, + subcounters: { + version: 1, + watches: 10 + } + } + } + ] + } + )); + expect(loadedUsers[1].should.be.eql( + { + id: 2, + personId: 2, + name: "Bob", + likedPosts: [ + { + id: 3, + title: "About horses", + counters: { + code: 3, + comments: 5, + favorites: 10, + likes: 15, + subcounters: { + version: 1, + watches: 30 + } + } + } + ] + } + )); + + let loadedUser = await connection.manager + .createQueryBuilder(User, "user") + .leftJoinAndSelect("user.likedPosts", "likedPost") + .orderBy("likedPost.id") + .where("user.id = :id", { id: 1 }) + .getOne(); + + expect(loadedUser!.should.be.eql( + { + id: 1, + personId: 1, + name: "Alice", + likedPosts: [ + { + id: 1, + title: "About cars", + counters: { + code: 1, + comments: 1, + favorites: 2, + likes: 3, + subcounters: { + version: 1, + watches: 5 + } + } + }, + { + id: 2, + title: "About airplanes", + counters: { + code: 2, + comments: 2, + favorites: 3, + likes: 4, + subcounters: { + version: 1, + watches: 10 + } + } + } + ] + } + )); + + loadedUser!.name = "Anna"; + loadedUser!.likedPosts = [post1]; + await connection.getRepository(User).persist(loadedUser!); + + loadedUser = await connection.manager + .createQueryBuilder(User, "user") + .leftJoinAndSelect("user.likedPosts", "likedPost") + .orderBy("likedPost.id") + .where("user.id = :id", { id: 1 }) + .getOne(); + + expect(loadedUser!.should.be.eql( + { + id: 1, + personId: 1, + name: "Anna", + likedPosts: [ + { + id: 1, + title: "About cars", + counters: { + code: 1, + comments: 1, + favorites: 2, + likes: 3, + subcounters: { + version: 1, + watches: 5 + } + } + } + ] + } + )); + + const loadedPost = await connection.manager + .createQueryBuilder(Post, "post") + .leftJoinAndSelect("post.counters.likedUser", "likedUser") + .where("post.id = :id", { id: 2 }) + .getOne(); + + expect(loadedPost!.counters.likedUser).to.be.empty; + }))); + + }); +}); diff --git a/test/functional/embedded/embedded-many-to-one-case5/entity/Counters.ts b/test/functional/embedded/embedded-many-to-one-case5/entity/Counters.ts new file mode 100644 index 000000000..0698247a3 --- /dev/null +++ b/test/functional/embedded/embedded-many-to-one-case5/entity/Counters.ts @@ -0,0 +1,30 @@ +import {Column} from "../../../../../src/decorator/columns/Column"; +import {JoinColumn} from "../../../../../src/decorator/relations/JoinColumn"; +import {ManyToOne} from "../../../../../src/decorator/relations/ManyToOne"; +import {Embedded} from "../../../../../src/decorator/Embedded"; +import {PrimaryColumn} from "../../../../../src/decorator/columns/PrimaryColumn"; +import {User} from "./User"; +import {Subcounters} from "./Subcounters"; + +export class Counters { + + @PrimaryColumn() + code: number; + + @Column() + likes: number; + + @Column() + comments: number; + + @Column() + favorites: number; + + @Embedded(() => Subcounters) + subcounters: Subcounters; + + @ManyToOne(type => User) + @JoinColumn() + likedUser: User; + +} \ No newline at end of file diff --git a/test/functional/embedded/embedded-many-to-one-case5/entity/Post.ts b/test/functional/embedded/embedded-many-to-one-case5/entity/Post.ts new file mode 100644 index 000000000..77121bb5c --- /dev/null +++ b/test/functional/embedded/embedded-many-to-one-case5/entity/Post.ts @@ -0,0 +1,21 @@ +import {Entity} from "../../../../../src/decorator/entity/Entity"; +import {Column} from "../../../../../src/decorator/columns/Column"; +import {Embedded} from "../../../../../src/decorator/Embedded"; +import {Counters} from "./Counters"; +import {Index} from "../../../../../src/decorator/Index"; +import {PrimaryColumn} from "../../../../../src/decorator/columns/PrimaryColumn"; + +@Entity() +@Index(["id", "counters.code", "counters.subcounters.version"]) +export class Post { + + @PrimaryColumn() + id: number; + + @Column() + title: string; + + @Embedded(() => Counters) + counters: Counters; + +} \ No newline at end of file diff --git a/test/functional/embedded/embedded-many-to-one-case5/entity/Subcounters.ts b/test/functional/embedded/embedded-many-to-one-case5/entity/Subcounters.ts new file mode 100644 index 000000000..c0aa97f1f --- /dev/null +++ b/test/functional/embedded/embedded-many-to-one-case5/entity/Subcounters.ts @@ -0,0 +1,12 @@ +import {Column} from "../../../../../src/decorator/columns/Column"; +import {PrimaryColumn} from "../../../../../src/decorator/columns/PrimaryColumn"; + +export class Subcounters { + + @PrimaryColumn() + version: number; + + @Column() + watches: number; + +} \ No newline at end of file diff --git a/test/functional/embedded/embedded-many-to-one-case5/entity/User.ts b/test/functional/embedded/embedded-many-to-one-case5/entity/User.ts new file mode 100644 index 000000000..2d88e3c52 --- /dev/null +++ b/test/functional/embedded/embedded-many-to-one-case5/entity/User.ts @@ -0,0 +1,24 @@ +import {Entity} from "../../../../../src/decorator/entity/Entity"; +import {OneToMany} from "../../../../../src/decorator/relations/OneToMany"; +import {Index} from "../../../../../src/decorator/Index"; +import {PrimaryColumn} from "../../../../../src/decorator/columns/PrimaryColumn"; +import {Column} from "../../../../../src/decorator/columns/Column"; +import {Post} from "./Post"; + +@Entity() +@Index(["id", "personId"]) +export class User { + + @PrimaryColumn() + id: number; + + @PrimaryColumn() + personId: number; + + @Column() + name: string; + + @OneToMany(type => Post, post => post.counters.likedUser) + likedPosts: Post[]; + +} \ No newline at end of file diff --git a/test/functional/embedded/embedded-one-to-one/embedded-one-to-one.ts b/test/functional/embedded/embedded-one-to-one/embedded-one-to-one.ts index 20c8df5a0..0113e8075 100644 --- a/test/functional/embedded/embedded-one-to-one/embedded-one-to-one.ts +++ b/test/functional/embedded/embedded-one-to-one/embedded-one-to-one.ts @@ -216,7 +216,7 @@ describe("embedded > embedded-one-to-one", () => { user2.likedPost = post2; await connection.getRepository(User).persist(user2); - const loadedUsers = await connection.manager + let loadedUsers = await connection.manager .createQueryBuilder(User, "user") .leftJoinAndSelect("user.likedPost", "likedPost") .orderBy("user.id") @@ -263,7 +263,7 @@ describe("embedded > embedded-one-to-one", () => { } )); - const loadedUser = await connection.manager + let loadedUser = await connection.manager .createQueryBuilder(User, "user") .leftJoinAndSelect("user.likedPost", "likedPost") .where("user.id = :id", { id: 1 }) @@ -292,15 +292,16 @@ describe("embedded > embedded-one-to-one", () => { loadedUser!.name = "Anna"; loadedUser!.likedPost = post3; + console.log(loadedUser); await connection.getRepository(User).persist(loadedUser!); - const loadedUser2 = await connection.manager + loadedUser = await connection.manager .createQueryBuilder(User, "user") .leftJoinAndSelect("user.likedPost", "likedPost") .where("user.id = :id", { id: 1 }) .getOne(); - expect(loadedUser2!.should.be.eql( + expect(loadedUser!.should.be.eql( { id: 1, name: "Anna", @@ -321,11 +322,11 @@ describe("embedded > embedded-one-to-one", () => { } )); - await connection.getRepository(User).remove(loadedUser2!); + await connection.getRepository(User).remove(loadedUser!); - const loadedUsers2 = (await connection.getRepository(User).find())!; - expect(loadedUsers2.length).to.be.equal(1); - expect(loadedUsers2[0].name).to.be.equal("Bob"); + loadedUsers = (await connection.getRepository(User).find())!; + expect(loadedUsers.length).to.be.equal(1); + expect(loadedUsers[0].name).to.be.equal("Bob"); }))); }); }); diff --git a/test/functional/embedded/embedded-with-special-columns/embedded-with-special-columns.ts b/test/functional/embedded/embedded-with-special-columns/embedded-with-special-columns.ts new file mode 100644 index 000000000..a0d75b691 --- /dev/null +++ b/test/functional/embedded/embedded-with-special-columns/embedded-with-special-columns.ts @@ -0,0 +1,80 @@ +import "reflect-metadata"; +import {Post} from "./entity/Post"; +import {Counters} from "./entity/Counters"; +import {Connection} from "../../../../src/connection/Connection"; +import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../utils/test-utils"; +import {Subcounters} from "../embedded-many-to-one-case2/entity/Subcounters"; + +describe.skip("embedded > embedded-with-special-columns", () => { + + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + entities: [__dirname + "/entity/*{.js,.ts}"], + schemaCreate: true, + dropSchemaOnConnection: true, + })); + beforeEach(() => reloadTestingDatabases(connections)); + after(() => closeTestingConnections(connections)); + + it("should insert, load, update and remove entities with embeddeds when embeds contains special columns (e.g. CreateDateColumn, UpdateDateColumn, VersionColumn", () => Promise.all(connections.map(async connection => { + + const post1 = new Post(); + post1.id = 1; + post1.title = "About cars"; + post1.counters = new Counters(); + post1.counters.comments = 1; + post1.counters.favorites = 2; + post1.counters.likes = 3; + post1.counters.subcounters = new Subcounters(); + post1.counters.subcounters.watches = 5; + await connection.getRepository(Post).persist(post1); + + const post2 = new Post(); + post2.id = 2; + post2.title = "About airplanes"; + post2.counters = new Counters(); + post2.counters.comments = 2; + post2.counters.favorites = 3; + post2.counters.likes = 4; + post2.counters.subcounters = new Subcounters(); + post2.counters.subcounters.watches = 10; + await connection.getRepository(Post).persist(post2); + + let loadedPosts = await connection.manager + .createQueryBuilder(Post, "post") + .orderBy("post.id") + .getMany(); + console.log(loadedPosts); + /*expect(loadedPosts[0].should.be.eql( + { + id: 1, + title: "About cars", + counters: { + comments: 1, + favorites: 2, + likes: 3, + subcounters: { + version: 1, + watches: 5 + } + } + } + )); + expect(loadedPosts[1].should.be.eql( + { + id: 2, + title: "About airplanes", + counters: { + comments: 2, + favorites: 3, + likes: 4, + subcounters: { + version: 1, + watches: 10 + } + } + } + ));*/ + }))); + +}); diff --git a/test/functional/embedded/embedded-with-special-columns/entity/Counters.ts b/test/functional/embedded/embedded-with-special-columns/entity/Counters.ts new file mode 100644 index 000000000..ced44ad7a --- /dev/null +++ b/test/functional/embedded/embedded-with-special-columns/entity/Counters.ts @@ -0,0 +1,27 @@ +import {Column} from "../../../../../src/decorator/columns/Column"; +import {Embedded} from "../../../../../src/decorator/Embedded"; +import {CreateDateColumn} from "../../../../../src/decorator/columns/CreateDateColumn"; +import {UpdateDateColumn} from "../../../../../src/decorator/columns/UpdateDateColumn"; +import {Subcounters} from "./Subcounters"; + +export class Counters { + + @Column() + likes: number; + + @Column() + comments: number; + + @Column() + favorites: number; + + @Embedded(() => Subcounters) + subcounters: Subcounters; + + @CreateDateColumn() + createdDate: Date; + + @UpdateDateColumn() + updatedDate: Date; + +} \ No newline at end of file diff --git a/test/functional/embedded/embedded-with-special-columns/entity/Post.ts b/test/functional/embedded/embedded-with-special-columns/entity/Post.ts new file mode 100644 index 000000000..89ee6f4eb --- /dev/null +++ b/test/functional/embedded/embedded-with-special-columns/entity/Post.ts @@ -0,0 +1,19 @@ +import {Entity} from "../../../../../src/decorator/entity/Entity"; +import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn"; +import {Column} from "../../../../../src/decorator/columns/Column"; +import {Embedded} from "../../../../../src/decorator/Embedded"; +import {Counters} from "./Counters"; + +@Entity() +export class Post { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + title: string; + + @Embedded(() => Counters) + counters: Counters; + +} \ No newline at end of file diff --git a/test/functional/embedded/embedded-with-special-columns/entity/Subcounters.ts b/test/functional/embedded/embedded-with-special-columns/entity/Subcounters.ts new file mode 100644 index 000000000..fc171a8f3 --- /dev/null +++ b/test/functional/embedded/embedded-with-special-columns/entity/Subcounters.ts @@ -0,0 +1,12 @@ +import {Column} from "../../../../../src/decorator/columns/Column"; +import {VersionColumn} from "../../../../../src/decorator/columns/VersionColumn"; + +export class Subcounters { + + @VersionColumn() + version: number; + + @Column() + watches: number; + +} \ No newline at end of file diff --git a/test/functional/query-builder/relation-id/relation-id-many-to-many/load-relation-id-and-map-many-to-many.ts b/test/functional/query-builder/relation-id/many-to-many/basic-functionality/basic-functionality.ts similarity index 80% rename from test/functional/query-builder/relation-id/relation-id-many-to-many/load-relation-id-and-map-many-to-many.ts rename to test/functional/query-builder/relation-id/many-to-many/basic-functionality/basic-functionality.ts index fa3eb225f..5b56e0a8c 100644 --- a/test/functional/query-builder/relation-id/relation-id-many-to-many/load-relation-id-and-map-many-to-many.ts +++ b/test/functional/query-builder/relation-id/many-to-many/basic-functionality/basic-functionality.ts @@ -1,8 +1,12 @@ import "reflect-metadata"; import * as chai from "chai"; import {expect} from "chai"; -import {createTestingConnections, closeTestingConnections, reloadTestingDatabases} from "../../../../utils/test-utils"; -import {Connection} from "../../../../../src/connection/Connection"; +import { + closeTestingConnections, + createTestingConnections, + reloadTestingDatabases +} from "../../../../../utils/test-utils"; +import {Connection} from "../../../../../../src/connection/Connection"; import {Tag} from "./entity/Tag"; import {Post} from "./entity/Post"; import {Category} from "./entity/Category"; @@ -10,7 +14,7 @@ import {Image} from "./entity/Image"; const should = chai.should(); -describe("query builder > load-relation-id-and-map > many-to-many", () => { +describe("query builder > relation-id > many-to-many > basic-functionality", () => { let connections: Connection[]; before(async () => connections = await createTestingConnections({ @@ -25,33 +29,33 @@ describe("query builder > load-relation-id-and-map > many-to-many", () => { const tag = new Tag(); tag.name = "kids"; - await connection.entityManager.persist(tag); + await connection.manager.persist(tag); const category1 = new Category(); category1.name = "kids"; - await connection.entityManager.persist(category1); + await connection.manager.persist(category1); const category2 = new Category(); category2.name = "future"; - await connection.entityManager.persist(category2); + await connection.manager.persist(category2); const category3 = new Category(); category3.name = "cars"; - await connection.entityManager.persist(category3); + await connection.manager.persist(category3); const post = new Post(); post.title = "about kids"; post.categories = [category1, category2]; post.tag = tag; - await connection.entityManager.persist(post); + await connection.manager.persist(post); const post2 = new Post(); post2.title = "about BMW"; post2.categories = [category3]; post2.tag = tag; - await connection.entityManager.persist(post2); + await connection.manager.persist(post2); - let loadedPosts = await connection.entityManager + let loadedPosts = await connection.manager .createQueryBuilder(Post, "post") .leftJoinAndSelect("post.tag", "tag") .leftJoinAndSelect("post.categories", "categories") @@ -67,7 +71,7 @@ describe("query builder > load-relation-id-and-map > many-to-many", () => { expect(loadedPosts![1].categories).to.not.be.empty; expect(loadedPosts![1].categoryIds).to.be.empty; - let loadedPost = await connection.entityManager + let loadedPost = await connection.manager .createQueryBuilder(Post, "post") .leftJoinAndSelect("post.tag", "tag") .leftJoinAndSelect("post.categories", "categories") @@ -86,23 +90,23 @@ describe("query builder > load-relation-id-and-map > many-to-many", () => { const category1 = new Category(); category1.name = "kids"; - await connection.entityManager.persist(category1); + await connection.manager.persist(category1); const category2 = new Category(); category2.name = "future"; - await connection.entityManager.persist(category2); + await connection.manager.persist(category2); const post = new Post(); post.title = "about kids"; post.categories = [category1, category2]; - await connection.entityManager.persist(post); + await connection.manager.persist(post); const post2 = new Post(); post2.title = "about kids"; post2.categories = [category1, category2]; - await connection.entityManager.persist(post2); + await connection.manager.persist(post2); - let loadedPosts = await connection.entityManager + let loadedPosts = await connection.manager .createQueryBuilder(Post, "post") .loadRelationIdAndMap("post.categoryIds", "post.categories") .getMany(); @@ -114,7 +118,7 @@ describe("query builder > load-relation-id-and-map > many-to-many", () => { expect(loadedPosts![1].categoryIds[0]).to.be.equal(1); expect(loadedPosts![1].categoryIds[1]).to.be.equal(2); - let loadedPost = await connection.entityManager + let loadedPost = await connection.manager .createQueryBuilder(Post, "post") .loadRelationIdAndMap("post.categoryIds", "post.categories") .where("post.id = :id", { id: post.id }) @@ -135,16 +139,16 @@ describe("query builder > load-relation-id-and-map > many-to-many", () => { category2.name = "future"; await Promise.all([ - connection.entityManager.persist(category1), - connection.entityManager.persist(category2) + connection.manager.persist(category1), + connection.manager.persist(category2) ]); const post = new Post(); post.title = "about kids"; post.subcategories = [category1, category2]; - await connection.entityManager.persist(post); + await connection.manager.persist(post); - let loadedPost = await connection.entityManager + let loadedPost = await connection.manager .createQueryBuilder(Post, "post") .loadRelationIdAndMap("post.categoryIds", "post.subcategories") .where("post.id = :id", { id: post.id }) @@ -160,7 +164,7 @@ describe("query builder > load-relation-id-and-map > many-to-many", () => { const category = new Category(); category.name = "cars"; - await connection.entityManager.persist(category); + await connection.manager.persist(category); const post1 = new Post(); post1.title = "about BMW"; @@ -171,11 +175,11 @@ describe("query builder > load-relation-id-and-map > many-to-many", () => { post2.categories = [category]; await Promise.all([ - connection.entityManager.persist(post1), - connection.entityManager.persist(post2) + connection.manager.persist(post1), + connection.manager.persist(post2) ]); - let loadedCategory = await connection.entityManager + let loadedCategory = await connection.manager .createQueryBuilder(Category, "category") .loadRelationIdAndMap("category.postIds", "category.posts") .where("category.id = :id", { id: category.id }) @@ -196,16 +200,16 @@ describe("query builder > load-relation-id-and-map > many-to-many", () => { category2.name = "future"; await Promise.all([ - connection.entityManager.persist(category1), - connection.entityManager.persist(category2) + connection.manager.persist(category1), + connection.manager.persist(category2) ]); const post = new Post(); post.title = "about kids"; post.categories = [category1, category2]; - await connection.entityManager.persist(post); + await connection.manager.persist(post); - let loadedPost = await connection.entityManager + let loadedPost = await connection.manager .createQueryBuilder(Post, "post") .loadRelationIdAndMap("post.categoryIds", "post.categories", "categories", qb => qb.andWhere("categories.id = :categoryId", { categoryId: 1 })) .getOne(); @@ -225,16 +229,16 @@ describe("query builder > load-relation-id-and-map > many-to-many", () => { category2.name = "future"; await Promise.all([ - connection.entityManager.persist(category1), - connection.entityManager.persist(category2) + connection.manager.persist(category1), + connection.manager.persist(category2) ]); const post = new Post(); post.title = "about kids"; post.subcategories = [category1, category2]; - await connection.entityManager.persist(post); + await connection.manager.persist(post); - let loadedPost = await connection.entityManager + let loadedPost = await connection.manager .createQueryBuilder(Post, "post") .loadRelationIdAndMap("post.categoryIds", "post.subcategories", "subCategories", qb => qb.andWhere("subCategories.id = :categoryId", { categoryId: 1 })) .getOne(); @@ -249,7 +253,7 @@ describe("query builder > load-relation-id-and-map > many-to-many", () => { const category = new Category(); category.name = "cars"; - await connection.entityManager.persist(category); + await connection.manager.persist(category); const post1 = new Post(); post1.title = "about BMW"; @@ -260,11 +264,11 @@ describe("query builder > load-relation-id-and-map > many-to-many", () => { post2.categories = [category]; await Promise.all([ - connection.entityManager.persist(post1), - connection.entityManager.persist(post2) + connection.manager.persist(post1), + connection.manager.persist(post2) ]); - let loadedCategory = await connection.entityManager + let loadedCategory = await connection.manager .createQueryBuilder(Category, "category") .loadRelationIdAndMap("category.postIds", "category.posts", "posts", qb => qb.andWhere("posts.id = :postId", { postId: 1 })) .where("category.id = :id", { id: category.id }) @@ -285,25 +289,25 @@ describe("query builder > load-relation-id-and-map > many-to-many", () => { image2.name = "photo2"; await Promise.all([ - connection.entityManager.persist(image1), - connection.entityManager.persist(image2) + connection.manager.persist(image1), + connection.manager.persist(image2) ]); const category1 = new Category(); category1.name = "cars"; category1.images = [image1, image2]; - await connection.entityManager.persist(category1); + await connection.manager.persist(category1); const category2 = new Category(); category2.name = "BMW"; - await connection.entityManager.persist(category2); + await connection.manager.persist(category2); const post = new Post(); post.title = "about BMW"; post.categories = [category1, category2]; - await connection.entityManager.persist(post); + await connection.manager.persist(post); - let loadedPost = await connection.entityManager + let loadedPost = await connection.manager .createQueryBuilder(Post, "post") .leftJoinAndSelect("post.categories", "categories") .loadRelationIdAndMap("post.categoryIds", "post.categories") @@ -333,25 +337,25 @@ describe("query builder > load-relation-id-and-map > many-to-many", () => { image2.name = "photo2"; await Promise.all([ - connection.entityManager.persist(image1), - connection.entityManager.persist(image2) + connection.manager.persist(image1), + connection.manager.persist(image2) ]); const category1 = new Category(); category1.name = "cars"; category1.images = [image1, image2]; - await connection.entityManager.persist(category1); + await connection.manager.persist(category1); const category2 = new Category(); category2.name = "BMW"; - await connection.entityManager.persist(category2); + await connection.manager.persist(category2); const post = new Post(); post.title = "about BMW"; post.categories = [category1, category2]; - await connection.entityManager.persist(post); + await connection.manager.persist(post); - let loadedPost = await connection.entityManager + let loadedPost = await connection.manager .createQueryBuilder(Post, "post") .leftJoinAndSelect("post.categories", "categories") .loadRelationIdAndMap("post.categoryIds", "post.categories", "categories2", qb => qb.andWhere("categories2.id = :categoryId", { categoryId: 1 })) @@ -379,21 +383,21 @@ describe("query builder > load-relation-id-and-map > many-to-many", () => { image2.name = "photo2"; await Promise.all([ - connection.entityManager.persist(image1), - connection.entityManager.persist(image2) + connection.manager.persist(image1), + connection.manager.persist(image2) ]); const category1 = new Category(); category1.name = "cars"; category1.images = [image1, image2]; - await connection.entityManager.persist(category1); + await connection.manager.persist(category1); const post = new Post(); post.title = "about BMW"; post.categories = [category1]; - await connection.entityManager.persist(post); + await connection.manager.persist(post); - let loadedPost = await connection.entityManager + let loadedPost = await connection.manager .createQueryBuilder(Post, "post") .leftJoinAndSelect("post.categories", "categories", "categories.id = :categoryId") .loadRelationIdAndMap("categories.imageIds", "categories.images") diff --git a/test/functional/query-builder/relation-id/many-to-many/basic-functionality/entity/Category.ts b/test/functional/query-builder/relation-id/many-to-many/basic-functionality/entity/Category.ts new file mode 100644 index 000000000..b5c5f2839 --- /dev/null +++ b/test/functional/query-builder/relation-id/many-to-many/basic-functionality/entity/Category.ts @@ -0,0 +1,29 @@ +import {Entity} from "../../../../../../../src/decorator/entity/Entity"; +import {PrimaryGeneratedColumn} from "../../../../../../../src/decorator/columns/PrimaryGeneratedColumn"; +import {Column} from "../../../../../../../src/decorator/columns/Column"; +import {ManyToMany} from "../../../../../../../src/decorator/relations/ManyToMany"; +import {JoinTable} from "../../../../../../../src/decorator/relations/JoinTable"; +import {Post} from "./Post"; +import {Image} from "./Image"; + +@Entity() +export class Category { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + name: string; + + @ManyToMany(type => Post, post => post.categories) + posts: Post[]; + + @ManyToMany(type => Image) + @JoinTable() + images: Image[]; + + imageIds: number[]; + + postIds: number[]; + +} \ No newline at end of file diff --git a/test/functional/query-builder/relation-id/many-to-many/basic-functionality/entity/Image.ts b/test/functional/query-builder/relation-id/many-to-many/basic-functionality/entity/Image.ts new file mode 100644 index 000000000..fac8ce01c --- /dev/null +++ b/test/functional/query-builder/relation-id/many-to-many/basic-functionality/entity/Image.ts @@ -0,0 +1,14 @@ +import {Entity} from "../../../../../../../src/decorator/entity/Entity"; +import {PrimaryGeneratedColumn} from "../../../../../../../src/decorator/columns/PrimaryGeneratedColumn"; +import {Column} from "../../../../../../../src/decorator/columns/Column"; + +@Entity() +export class Image { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + name: string; + +} \ No newline at end of file diff --git a/test/functional/query-builder/relation-id/many-to-many/basic-functionality/entity/Post.ts b/test/functional/query-builder/relation-id/many-to-many/basic-functionality/entity/Post.ts new file mode 100644 index 000000000..976633c56 --- /dev/null +++ b/test/functional/query-builder/relation-id/many-to-many/basic-functionality/entity/Post.ts @@ -0,0 +1,34 @@ +import {ManyToMany} from "../../../../../../../src/decorator/relations/ManyToMany"; +import {Entity} from "../../../../../../../src/decorator/entity/Entity"; +import {PrimaryGeneratedColumn} from "../../../../../../../src/decorator/columns/PrimaryGeneratedColumn"; +import {Column} from "../../../../../../../src/decorator/columns/Column"; +import {ManyToOne} from "../../../../../../../src/decorator/relations/ManyToOne"; +import {JoinTable} from "../../../../../../../src/decorator/relations/JoinTable"; +import {Category} from "./Category"; +import {Tag} from "./Tag"; + +@Entity() +export class Post { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + title: string; + + @ManyToOne(type => Tag) + tag: Tag; + + tagId: number; + + @ManyToMany(type => Category, category => category.posts) + @JoinTable() + categories: Category[]; + + @ManyToMany(type => Category) + @JoinTable() + subcategories: Category[]; + + categoryIds: number[]; + +} \ No newline at end of file diff --git a/test/functional/query-builder/relation-id/many-to-many/basic-functionality/entity/Tag.ts b/test/functional/query-builder/relation-id/many-to-many/basic-functionality/entity/Tag.ts new file mode 100644 index 000000000..613919d9f --- /dev/null +++ b/test/functional/query-builder/relation-id/many-to-many/basic-functionality/entity/Tag.ts @@ -0,0 +1,14 @@ +import {Entity} from "../../../../../../../src/decorator/entity/Entity"; +import {PrimaryGeneratedColumn} from "../../../../../../../src/decorator/columns/PrimaryGeneratedColumn"; +import {Column} from "../../../../../../../src/decorator/columns/Column"; + +@Entity() +export class Tag { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + name: string; + +} \ No newline at end of file diff --git a/test/functional/query-builder/relation-id/many-to-many/embedded/embedded.ts b/test/functional/query-builder/relation-id/many-to-many/embedded/embedded.ts new file mode 100644 index 000000000..8d713090b --- /dev/null +++ b/test/functional/query-builder/relation-id/many-to-many/embedded/embedded.ts @@ -0,0 +1,96 @@ +import "reflect-metadata"; +import * as chai from "chai"; +import {expect} from "chai"; +import { + closeTestingConnections, + createTestingConnections, + reloadTestingDatabases +} from "../../../../../utils/test-utils"; +import {Connection} from "../../../../../../src/connection/Connection"; +import {Post} from "./entity/Post"; +import {Category} from "./entity/Category"; +import {Counters} from "./entity/Counters"; + +const should = chai.should(); + +describe("query builder > relation-id > many-to-many > embedded", () => { + + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + entities: [__dirname + "/entity/*{.js,.ts}"], + schemaCreate: true, + dropSchemaOnConnection: true, + })); + beforeEach(() => reloadTestingDatabases(connections)); + after(() => closeTestingConnections(connections)); + + it("should load ids when RelationId decorator used in embedded table", () => Promise.all(connections.map(async connection => { + + const category1 = new Category(); + category1.name = "cars"; + await connection.manager.persist(category1); + + const category2 = new Category(); + category2.name = "BMW"; + await connection.manager.persist(category2); + + const category3 = new Category(); + category3.name = "airplanes"; + await connection.manager.persist(category3); + + const category4 = new Category(); + category4.name = "Boeing"; + await connection.manager.persist(category4); + + const post1 = new Post(); + post1.title = "About BMW"; + post1.counters = new Counters(); + post1.counters.likes = 1; + post1.counters.comments = 2; + post1.counters.favorites = 3; + post1.counters.categories = [category1, category2]; + await connection.manager.persist(post1); + + const post2 = new Post(); + post2.title = "About Boeing"; + post2.counters = new Counters(); + post2.counters.likes = 3; + post2.counters.comments = 4; + post2.counters.favorites = 5; + post2.counters.categories = [category3, category4]; + await connection.manager.persist(post2); + + const loadedPosts = await connection.manager + .createQueryBuilder(Post, "post") + .loadRelationIdAndMap("post.counters.categoryIds", "post.counters.categories") + .orderBy("post.id") + .getMany(); + + expect(loadedPosts[0].should.be.eql( + { + id: 1, + title: "About BMW", + counters: { + likes: 1, + comments: 2, + favorites: 3, + categoryIds: [1, 2] + } + } + )); + expect(loadedPosts[1].should.be.eql( + { + id: 2, + title: "About Boeing", + counters: { + likes: 3, + comments: 4, + favorites: 5, + categoryIds: [3, 4] + } + } + )); + + }))); + +}); \ No newline at end of file diff --git a/test/functional/query-builder/relation-id/many-to-many/embedded/entity/Category.ts b/test/functional/query-builder/relation-id/many-to-many/embedded/entity/Category.ts new file mode 100644 index 000000000..bef170a51 --- /dev/null +++ b/test/functional/query-builder/relation-id/many-to-many/embedded/entity/Category.ts @@ -0,0 +1,21 @@ +import {Entity} from "../../../../../../../src/decorator/entity/Entity"; +import {Column} from "../../../../../../../src/decorator/columns/Column"; +import {ManyToMany} from "../../../../../../../src/decorator/relations/ManyToMany"; +import {PrimaryGeneratedColumn} from "../../../../../../../src/decorator/columns/PrimaryGeneratedColumn"; +import {Post} from "./Post"; + +@Entity() +export class Category { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + name: string; + + @ManyToMany(type => Post, post => post.counters.categories) + posts: Post[]; + + postIds: number[]; + +} \ No newline at end of file diff --git a/test/functional/query-builder/relation-id/many-to-many/embedded/entity/Counters.ts b/test/functional/query-builder/relation-id/many-to-many/embedded/entity/Counters.ts new file mode 100644 index 000000000..7095f798f --- /dev/null +++ b/test/functional/query-builder/relation-id/many-to-many/embedded/entity/Counters.ts @@ -0,0 +1,23 @@ +import {Column} from "../../../../../../../src/decorator/columns/Column"; +import {ManyToMany} from "../../../../../../../src/decorator/relations/ManyToMany"; +import {JoinTable} from "../../../../../../../src/decorator/relations/JoinTable"; +import {Category} from "./Category"; + +export class Counters { + + @Column() + likes: number; + + @Column() + comments: number; + + @Column() + favorites: number; + + @ManyToMany(type => Category, category => category.posts) + @JoinTable() + categories: Category[]; + + categoryIds: number[]; + +} \ No newline at end of file diff --git a/test/functional/query-builder/relation-id/many-to-many/embedded/entity/Post.ts b/test/functional/query-builder/relation-id/many-to-many/embedded/entity/Post.ts new file mode 100644 index 000000000..e7a5ac7be --- /dev/null +++ b/test/functional/query-builder/relation-id/many-to-many/embedded/entity/Post.ts @@ -0,0 +1,19 @@ +import {Entity} from "../../../../../../../src/decorator/entity/Entity"; +import {Column} from "../../../../../../../src/decorator/columns/Column"; +import {PrimaryGeneratedColumn} from "../../../../../../../src/decorator/columns/PrimaryGeneratedColumn"; +import {Embedded} from "../../../../../../../src/decorator/Embedded"; +import {Counters} from "./Counters"; + +@Entity() +export class Post { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + title: string; + + @Embedded(() => Counters) + counters: Counters; + +} \ No newline at end of file diff --git a/test/functional/query-builder/relation-id/many-to-many/multiple-pk/entity/Category.ts b/test/functional/query-builder/relation-id/many-to-many/multiple-pk/entity/Category.ts new file mode 100644 index 000000000..9dee0bed3 --- /dev/null +++ b/test/functional/query-builder/relation-id/many-to-many/multiple-pk/entity/Category.ts @@ -0,0 +1,37 @@ +import {Entity} from "../../../../../../../src/decorator/entity/Entity"; +import {Column} from "../../../../../../../src/decorator/columns/Column"; +import {ManyToMany} from "../../../../../../../src/decorator/relations/ManyToMany"; +import {PrimaryColumn} from "../../../../../../../src/decorator/columns/PrimaryColumn"; +import {Index} from "../../../../../../../src/decorator/Index"; +import {JoinTable} from "../../../../../../../src/decorator/relations/JoinTable"; +import {Post} from "./Post"; +import {Image} from "./Image"; + +@Entity() +@Index(["id", "code"]) +export class Category { + + @PrimaryColumn() + id: number; + + @PrimaryColumn() + code: number; + + @Column() + name: string; + + @Column() + isRemoved: boolean = false; + + @ManyToMany(type => Post, post => post.categories) + posts: Post[]; + + @ManyToMany(type => Image, image => image.categories) + @JoinTable() + images: Image[]; + + postIds: number[]; + + imageIds: number[]; + +} \ No newline at end of file diff --git a/test/functional/query-builder/relation-id/many-to-many/multiple-pk/entity/Image.ts b/test/functional/query-builder/relation-id/many-to-many/multiple-pk/entity/Image.ts new file mode 100644 index 000000000..d781b2631 --- /dev/null +++ b/test/functional/query-builder/relation-id/many-to-many/multiple-pk/entity/Image.ts @@ -0,0 +1,21 @@ +import {Entity} from "../../../../../../../src/decorator/entity/Entity"; +import {PrimaryGeneratedColumn} from "../../../../../../../src/decorator/columns/PrimaryGeneratedColumn"; +import {Column} from "../../../../../../../src/decorator/columns/Column"; +import {ManyToMany} from "../../../../../../../src/decorator/relations/ManyToMany"; +import {Category} from "./Category"; + +@Entity() +export class Image { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + name: string; + + @ManyToMany(type => Category, category => category.images) + categories: Category[]; + + categoryIds: number[]; + +} \ No newline at end of file diff --git a/test/functional/query-builder/relation-id/many-to-many/multiple-pk/entity/Post.ts b/test/functional/query-builder/relation-id/many-to-many/multiple-pk/entity/Post.ts new file mode 100644 index 000000000..6201727bc --- /dev/null +++ b/test/functional/query-builder/relation-id/many-to-many/multiple-pk/entity/Post.ts @@ -0,0 +1,35 @@ +import {ManyToMany} from "../../../../../../../src/decorator/relations/ManyToMany"; +import {Entity} from "../../../../../../../src/decorator/entity/Entity"; +import {Column} from "../../../../../../../src/decorator/columns/Column"; +import {JoinTable} from "../../../../../../../src/decorator/relations/JoinTable"; +import {PrimaryColumn} from "../../../../../../../src/decorator/columns/PrimaryColumn"; +import {Index} from "../../../../../../../src/decorator/Index"; +import {Category} from "./Category"; + +@Entity() +@Index(["id", "authorId"]) +export class Post { + + @PrimaryColumn() + id: number; + + @PrimaryColumn() + authorId: number; + + @Column() + title: string; + + @Column() + isRemoved: boolean = false; + + @ManyToMany(type => Category, category => category.posts) + @JoinTable() + categories: Category[]; + + @ManyToMany(type => Category) + @JoinTable() + subcategories: Category[]; + + categoryIds: number[]; + +} \ No newline at end of file diff --git a/test/functional/query-builder/relation-id/many-to-many/multiple-pk/multiple-pk.ts b/test/functional/query-builder/relation-id/many-to-many/multiple-pk/multiple-pk.ts new file mode 100644 index 000000000..a4c64d151 --- /dev/null +++ b/test/functional/query-builder/relation-id/many-to-many/multiple-pk/multiple-pk.ts @@ -0,0 +1,731 @@ +import "reflect-metadata"; +import * as chai from "chai"; +import {expect} from "chai"; +import { + closeTestingConnections, + createTestingConnections, + reloadTestingDatabases +} from "../../../../../utils/test-utils"; +import {Connection} from "../../../../../../src/connection/Connection"; +import {Post} from "./entity/Post"; +import {Category} from "./entity/Category"; +import {Image} from "./entity/Image"; + +const should = chai.should(); + +describe("query builder > relation-id > many-to-many > multiple-pk", () => { + + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + entities: [__dirname + "/entity/*{.js,.ts}"], + schemaCreate: true, + dropSchemaOnConnection: true, + })); + beforeEach(() => reloadTestingDatabases(connections)); + after(() => closeTestingConnections(connections)); + + describe("owner side", () => { + + it("should load ids when both entities have multiple primary keys", () => Promise.all(connections.map(async connection => { + + const category1 = new Category(); + category1.id = 1; + category1.code = 1; + category1.name = "cars"; + await connection.manager.persist(category1); + + const category2 = new Category(); + category2.id = 2; + category2.code = 2; + category2.name = "BMW"; + await connection.manager.persist(category2); + + const category3 = new Category(); + category3.id = 3; + category3.code = 1; + category3.name = "airplanes"; + await connection.manager.persist(category3); + + const category4 = new Category(); + category4.id = 4; + category4.code = 2; + category4.name = "Boeing"; + await connection.manager.persist(category4); + + const post1 = new Post(); + post1.id = 1; + post1.authorId = 1; + post1.title = "About BMW"; + post1.categories = [category1, category2]; + await connection.manager.persist(post1); + + const post2 = new Post(); + post2.id = 2; + post2.authorId = 1; + post2.title = "About Boeing"; + post2.categories = [category3, category4]; + await connection.manager.persist(post2); + + const loadedPosts = await connection.manager + .createQueryBuilder(Post, "post") + .loadRelationIdAndMap("post.categoryIds", "post.categories") + .getMany(); + + expect(loadedPosts[0].categoryIds).to.not.be.empty; + expect(loadedPosts[0].categoryIds[0]).to.be.eql({ id: 1, code: 1 }); + expect(loadedPosts[0].categoryIds[1]).to.be.eql({ id: 2, code: 2 }); + expect(loadedPosts[1].categoryIds).to.not.be.empty; + expect(loadedPosts[1].categoryIds[0]).to.be.eql({ id: 3, code: 1 }); + expect(loadedPosts[1].categoryIds[1]).to.be.eql({ id: 4, code: 2 }); + + const loadedPost = await connection.manager + .createQueryBuilder(Post, "post") + .loadRelationIdAndMap("post.categoryIds", "post.categories") + .where("post.id = :id", { id: 1 }) + .andWhere("post.authorId = :authorId", { authorId: 1 }) + .getOne(); + + expect(loadedPost!.categoryIds).to.not.be.empty; + expect(loadedPost!.categoryIds[0]).to.be.eql({ id: 1, code: 1 }); + expect(loadedPost!.categoryIds[1]).to.be.eql({ id: 2, code: 2 }); + + }))); + + it("should load ids when only one entity have multiple primary keys", () => Promise.all(connections.map(async connection => { + + const image1 = new Image(); + image1.name = "Image #1"; + await connection.manager.persist(image1); + + const image2 = new Image(); + image2.name = "Image #2"; + await connection.manager.persist(image2); + + const image3 = new Image(); + image3.name = "Image #3"; + await connection.manager.persist(image3); + + const image4 = new Image(); + image4.name = "Image #4"; + await connection.manager.persist(image4); + + const category1 = new Category(); + category1.id = 1; + category1.code = 1; + category1.name = "cars"; + category1.images = [image1, image2]; + await connection.manager.persist(category1); + + const category2 = new Category(); + category2.id = 2; + category2.code = 2; + category2.name = "airplanes"; + category2.images = [image3, image4]; + await connection.manager.persist(category2); + + const loadedCategories = await connection.manager + .createQueryBuilder(Category, "category") + .loadRelationIdAndMap("category.imageIds", "category.images") + .getMany(); + + expect(loadedCategories[0].imageIds).to.not.be.empty; + expect(loadedCategories[0].imageIds[0]).to.be.equal(1); + expect(loadedCategories[0].imageIds[1]).to.be.equal(2); + expect(loadedCategories[1].imageIds).to.not.be.empty; + expect(loadedCategories[1].imageIds[0]).to.be.equal(3); + expect(loadedCategories[1].imageIds[1]).to.be.equal(4); + + const loadedCategory = await connection.manager + .createQueryBuilder(Category, "category") + .loadRelationIdAndMap("category.imageIds", "category.images") + .where("category.id = :id", { id: 1 }) + .andWhere("category.code = :code", { code: 1 }) + .getOne(); + + expect(loadedCategory!.imageIds).to.not.be.empty; + expect(loadedCategory!.imageIds[0]).to.be.equal(1); + expect(loadedCategory!.imageIds[1]).to.be.equal(2); + + }))); + + it("should load ids when both entities have multiple primary keys and additional condition used", () => Promise.all(connections.map(async connection => { + + const category1 = new Category(); + category1.id = 1; + category1.code = 1; + category1.name = "cars"; + await connection.manager.persist(category1); + + const category2 = new Category(); + category2.id = 2; + category2.code = 1; + category2.isRemoved = true; + category2.name = "BMW"; + await connection.manager.persist(category2); + + const category3 = new Category(); + category3.id = 3; + category3.code = 1; + category3.name = "airplanes"; + await connection.manager.persist(category3); + + const category4 = new Category(); + category4.id = 4; + category4.code = 2; + category4.isRemoved = true; + category4.name = "Boeing"; + await connection.manager.persist(category4); + + const post1 = new Post(); + post1.id = 1; + post1.authorId = 1; + post1.title = "About BMW"; + post1.categories = [category1, category2]; + await connection.manager.persist(post1); + + const post2 = new Post(); + post2.id = 2; + post2.authorId = 1; + post2.title = "About Boeing"; + post2.categories = [category3, category4]; + await connection.manager.persist(post2); + + let loadedPosts = await connection.manager + .createQueryBuilder(Post, "post") + .loadRelationIdAndMap("post.categoryIds", "post.categories", "category", qb => qb.andWhere("category.id = :id AND category.code = :code", { id: 1, code: 1 })) + .getMany(); + + expect(loadedPosts[0].categoryIds).to.not.be.empty; + expect(loadedPosts[0].categoryIds[0]).to.be.eql({ id: 1, code: 1 }); + expect(loadedPosts[1].categoryIds).to.be.empty; + + loadedPosts = await connection.manager + .createQueryBuilder(Post, "post") + .loadRelationIdAndMap("post.categoryIds", "post.categories", "category", qb => qb.andWhere("category.code = :code", { code: 1 })) + .getMany(); + + expect(loadedPosts[0].categoryIds).to.not.be.empty; + expect(loadedPosts[0].categoryIds[0]).to.be.eql({ id: 1, code: 1 }); + expect(loadedPosts[0].categoryIds[1]).to.be.eql({ id: 2, code: 1 }); + expect(loadedPosts[1].categoryIds).to.not.be.empty; + expect(loadedPosts[1].categoryIds[0]).to.be.eql({ id: 3, code: 1 }); + + loadedPosts = await connection.manager + .createQueryBuilder(Post, "post") + .loadRelationIdAndMap("post.categoryIds", "post.categories", "category", qb => qb.andWhere("category.isRemoved = :isRemoved", { isRemoved: true })) + .getMany(); + + expect(loadedPosts[0].categoryIds).to.not.be.empty; + expect(loadedPosts[0].categoryIds[0]).to.be.eql({ id: 2, code: 1 }); + expect(loadedPosts[1].categoryIds).to.not.be.empty; + expect(loadedPosts[1].categoryIds[0]).to.be.eql({ id: 4, code: 2 }); + + const loadedPost = await connection.manager + .createQueryBuilder(Post, "post") + .loadRelationIdAndMap("post.categoryIds", "post.categories", "category", qb => qb.andWhere("category.isRemoved = :isRemoved", { isRemoved: true })) + .where("post.id = :id", { id: 1 }) + .andWhere("post.authorId = :authorId", { authorId: 1 }) + .getOne(); + + expect(loadedPost!.categoryIds).to.not.be.empty; + expect(loadedPost!.categoryIds[0]).to.be.eql({ id: 2, code: 1 }); + + }))); + + it("should load ids when both entities have multiple primary keys and related entity does not have inverse side", () => Promise.all(connections.map(async connection => { + + const category1 = new Category(); + category1.id = 1; + category1.code = 1; + category1.name = "cars"; + await connection.manager.persist(category1); + + const category2 = new Category(); + category2.id = 2; + category2.code = 2; + category2.name = "BMW"; + await connection.manager.persist(category2); + + const category3 = new Category(); + category3.id = 3; + category3.code = 1; + category3.name = "airplanes"; + await connection.manager.persist(category3); + + const category4 = new Category(); + category4.id = 4; + category4.code = 2; + category4.name = "Boeing"; + await connection.manager.persist(category4); + + const post1 = new Post(); + post1.id = 1; + post1.authorId = 1; + post1.title = "About BMW"; + post1.subcategories = [category1, category2]; + await connection.manager.persist(post1); + + const post2 = new Post(); + post2.id = 2; + post2.authorId = 1; + post2.title = "About Boeing"; + post2.subcategories = [category3, category4]; + await connection.manager.persist(post2); + + const loadedPosts = await connection.manager + .createQueryBuilder(Post, "post") + .loadRelationIdAndMap("post.categoryIds", "post.subcategories") + .getMany(); + + expect(loadedPosts[0].categoryIds).to.not.be.empty; + expect(loadedPosts[0].categoryIds[0]).to.be.eql({ id: 1, code: 1 }); + expect(loadedPosts[0].categoryIds[1]).to.be.eql({ id: 2, code: 2 }); + expect(loadedPosts[1].categoryIds).to.not.be.empty; + expect(loadedPosts[1].categoryIds[0]).to.be.eql({ id: 3, code: 1 }); + expect(loadedPosts[1].categoryIds[1]).to.be.eql({ id: 4, code: 2 }); + + const loadedPost = await connection.manager + .createQueryBuilder(Post, "post") + .loadRelationIdAndMap("post.categoryIds", "post.subcategories") + .where("post.id = :id", { id: 1 }) + .andWhere("post.authorId = :authorId", { authorId: 1 }) + .getOne(); + + expect(loadedPost!.categoryIds).to.not.be.empty; + expect(loadedPost!.categoryIds[0]).to.be.eql({ id: 1, code: 1 }); + expect(loadedPost!.categoryIds[1]).to.be.eql({ id: 2, code: 2 }); + + }))); + + it("should load ids when loadRelationIdAndMap used on nested relation", () => Promise.all(connections.map(async connection => { + + const image1 = new Image(); + image1.name = "Image #1"; + await connection.manager.persist(image1); + + const image2 = new Image(); + image2.name = "Image #2"; + await connection.manager.persist(image2); + + const image3 = new Image(); + image3.name = "Image #3"; + await connection.manager.persist(image3); + + const image4 = new Image(); + image4.name = "Image #4"; + await connection.manager.persist(image4); + + const image5 = new Image(); + image5.name = "Image #5"; + await connection.manager.persist(image5); + + const category1 = new Category(); + category1.id = 1; + category1.code = 1; + category1.name = "cars"; + category1.images = [image1, image2, image3]; + await connection.manager.persist(category1); + + const category2 = new Category(); + category2.id = 2; + category2.code = 1; + category2.name = "airplanes"; + category2.images = [image4, image5]; + await connection.manager.persist(category2); + + const category3 = new Category(); + category3.id = 3; + category3.code = 2; + category3.name = "Boeing"; + category3.images = [image5]; + await connection.manager.persist(category3); + + const post1 = new Post(); + post1.id = 1; + post1.authorId = 1; + post1.title = "About BMW"; + post1.categories = [category1]; + await connection.manager.persist(post1); + + const post2 = new Post(); + post2.id = 2; + post2.authorId = 1; + post2.title = "About Boeing"; + post2.categories = [category2, category3]; + await connection.manager.persist(post2); + + const loadedPosts = await connection.manager + .createQueryBuilder(Post, "post") + .loadRelationIdAndMap("post.categoryIds", "post.categories") + .leftJoinAndSelect("post.categories", "category") + .loadRelationIdAndMap("category.imageIds", "category.images") + .getMany(); + + expect(loadedPosts[0].categoryIds).to.not.be.empty; + expect(loadedPosts[0].categoryIds.length).to.be.equal(1); + expect(loadedPosts[0].categoryIds[0]).to.be.eql({ id: 1, code: 1 }); + expect(loadedPosts[0].categories).to.not.be.empty; + expect(loadedPosts[0].categories[0].imageIds).to.not.be.empty; + expect(loadedPosts[0].categories[0].imageIds.length).to.be.equal(3); + expect(loadedPosts[0].categories[0].imageIds[0]).to.be.equal(1); + expect(loadedPosts[0].categories[0].imageIds[1]).to.be.equal(2); + expect(loadedPosts[0].categories[0].imageIds[2]).to.be.equal(3); + expect(loadedPosts[1].categoryIds).to.not.be.empty; + expect(loadedPosts[1].categoryIds.length).to.be.equal(2); + expect(loadedPosts[1].categoryIds[0]).to.be.eql({ id: 2, code: 1 }); + expect(loadedPosts[1].categoryIds[1]).to.be.eql({ id: 3, code: 2 }); + expect(loadedPosts[1].categories).to.not.be.empty; + expect(loadedPosts[1].categories.length).to.be.equal(2); + expect(loadedPosts[1].categories[0].imageIds).to.not.be.empty; + expect(loadedPosts[1].categories[0].imageIds.length).to.be.equal(2); + expect(loadedPosts[1].categories[0].imageIds[0]).to.be.equal(4); + expect(loadedPosts[1].categories[0].imageIds[1]).to.be.equal(5); + expect(loadedPosts[1].categories[1].imageIds).to.not.be.empty; + expect(loadedPosts[1].categories[1].imageIds.length).to.be.equal(1); + expect(loadedPosts[1].categories[1].imageIds[0]).to.be.equal(5); + + const loadedPost = await connection.manager + .createQueryBuilder(Post, "post") + .loadRelationIdAndMap("post.categoryIds", "post.categories") + .leftJoinAndSelect("post.categories", "category") + .loadRelationIdAndMap("category.imageIds", "category.images") + .where("post.id = :id", { id: 1 }) + .andWhere("post.authorId = :authorId", { authorId: 1 }) + .getOne(); + + expect(loadedPost!.categoryIds).to.not.be.empty; + expect(loadedPost!.categoryIds[0]).to.be.eql({ id: 1, code: 1 }); + expect(loadedPost!.categories).to.not.be.empty; + expect(loadedPost!.categories[0].imageIds).to.not.be.empty; + expect(loadedPost!.categories[0].imageIds.length).to.be.equal(3); + expect(loadedPost!.categories[0].imageIds[0]).to.be.equal(1); + expect(loadedPost!.categories[0].imageIds[1]).to.be.equal(2); + expect(loadedPost!.categories[0].imageIds[2]).to.be.equal(3); + + }))); + }); + + describe("inverse side", () => { + + it("should load ids when both entities have multiple primary keys", () => Promise.all(connections.map(async connection => { + + const post1 = new Post(); + post1.id = 1; + post1.authorId = 1; + post1.title = "About BMW"; + await connection.manager.persist(post1); + + const post2 = new Post(); + post2.id = 2; + post2.authorId = 2; + post2.title = "About Audi"; + await connection.manager.persist(post2); + + const post3 = new Post(); + post3.id = 3; + post3.authorId = 1; + post3.title = "About Boeing"; + await connection.manager.persist(post3); + + const post4 = new Post(); + post4.id = 4; + post4.authorId = 2; + post4.title = "About Airbus"; + await connection.manager.persist(post4); + + const category1 = new Category(); + category1.id = 1; + category1.code = 1; + category1.name = "cars"; + category1.posts = [post1, post2]; + await connection.manager.persist(category1); + + const category2 = new Category(); + category2.id = 2; + category2.code = 1; + category2.name = "airplanes"; + category2.posts = [post3, post4]; + await connection.manager.persist(category2); + + const loadedCategories = await connection.manager + .createQueryBuilder(Category, "category") + .loadRelationIdAndMap("category.postIds", "category.posts") + .getMany(); + + expect(loadedCategories[0].postIds).to.not.be.empty; + expect(loadedCategories[0].postIds[0]).to.be.eql({ id: 1, authorId: 1 }); + expect(loadedCategories[0].postIds[1]).to.be.eql({ id: 2, authorId: 2 }); + expect(loadedCategories[1].postIds).to.not.be.empty; + expect(loadedCategories[1].postIds[0]).to.be.eql({ id: 3, authorId: 1 }); + expect(loadedCategories[1].postIds[1]).to.be.eql({ id: 4, authorId: 2 }); + + const loadedCategory = await connection.manager + .createQueryBuilder(Category, "category") + .loadRelationIdAndMap("category.postIds", "category.posts") + .where("category.id = :id", { id: 1 }) + .andWhere("category.code = :code", { code: 1 }) + .getOne(); + + expect(loadedCategory!.postIds).to.not.be.empty; + expect(loadedCategory!.postIds[0]).to.be.eql({ id: 1, authorId: 1 }); + expect(loadedCategory!.postIds[1]).to.be.eql({ id: 2, authorId: 2 }); + + }))); + + it("should load ids when only one entity have multiple primary keys", () => Promise.all(connections.map(async connection => { + + const category1 = new Category(); + category1.id = 1; + category1.code = 1; + category1.name = "category #1"; + await connection.manager.persist(category1); + + const category2 = new Category(); + category2.id = 2; + category2.code = 1; + category2.name = "category #2"; + await connection.manager.persist(category2); + + const category3 = new Category(); + category3.id = 3; + category3.code = 2; + category3.name = "category #3"; + await connection.manager.persist(category3); + + const category4 = new Category(); + category4.id = 4; + category4.code = 2; + category4.name = "category #4"; + await connection.manager.persist(category4); + + const image1 = new Image(); + image1.name = "Image #1"; + image1.categories = [category1, category2]; + await connection.manager.persist(image1); + + const image2 = new Image(); + image2.name = "Image #2"; + image2.categories = [category3, category4]; + await connection.manager.persist(image2); + + const loadedImages = await connection.manager + .createQueryBuilder(Image, "image") + .loadRelationIdAndMap("image.categoryIds", "image.categories") + .getMany(); + + expect(loadedImages[0].categoryIds).to.not.be.empty; + expect(loadedImages[0].categoryIds[0]).to.be.eql({ id: 1, code: 1 }); + expect(loadedImages[0].categoryIds[1]).to.be.eql({ id: 2, code: 1 }); + expect(loadedImages[1].categoryIds).to.not.be.empty; + expect(loadedImages[1].categoryIds[0]).to.be.eql({ id: 3, code: 2 }); + expect(loadedImages[1].categoryIds[1]).to.be.eql({ id: 4, code: 2 }); + + const loadedImage = await connection.manager + .createQueryBuilder(Image, "image") + .loadRelationIdAndMap("image.categoryIds", "image.categories") + .where("image.id = :id", { id: 1 }) + .getOne(); + + expect(loadedImage!.categoryIds).to.not.be.empty; + expect(loadedImage!.categoryIds[0]).to.be.eql({ id: 1, code: 1 }); + expect(loadedImage!.categoryIds[1]).to.be.eql({ id: 2, code: 1 }); + + }))); + + it("should load ids when both entities have multiple primary keys and additional condition used", () => Promise.all(connections.map(async connection => { + + const post1 = new Post(); + post1.id = 1; + post1.authorId = 1; + post1.title = "About BMW"; + await connection.manager.persist(post1); + + const post2 = new Post(); + post2.id = 2; + post2.authorId = 1; + post2.isRemoved = true; + post2.title = "About Audi"; + await connection.manager.persist(post2); + + const post3 = new Post(); + post3.id = 3; + post3.authorId = 1; + post3.title = "About Boeing"; + await connection.manager.persist(post3); + + const post4 = new Post(); + post4.id = 4; + post4.authorId = 2; + post4.isRemoved = true; + post4.title = "About Airbus"; + await connection.manager.persist(post4); + + const category1 = new Category(); + category1.id = 1; + category1.code = 1; + category1.name = "cars"; + category1.posts = [post1, post2]; + await connection.manager.persist(category1); + + const category2 = new Category(); + category2.id = 2; + category2.code = 1; + category2.name = "airplanes"; + category2.posts = [post3, post4]; + await connection.manager.persist(category2); + + let loadedCategories = await connection.manager + .createQueryBuilder(Category, "category") + .loadRelationIdAndMap("category.postIds", "category.posts", "post", qb => qb.andWhere("post.id = :id AND post.authorId = :authorId", { id: 1, authorId: 1 })) + .getMany(); + + expect(loadedCategories[0].postIds).to.not.be.empty; + expect(loadedCategories[0].postIds[0]).to.be.eql({ id: 1, authorId: 1 }); + expect(loadedCategories[1].postIds).to.be.empty; + + loadedCategories = await connection.manager + .createQueryBuilder(Category, "category") + .loadRelationIdAndMap("category.postIds", "category.posts", "post", qb => qb.andWhere("post.authorId = :authorId", { authorId: 1 })) + .getMany(); + + expect(loadedCategories[0].postIds).to.not.be.empty; + expect(loadedCategories[0].postIds[0]).to.be.eql({ id: 1, authorId: 1 }); + expect(loadedCategories[0].postIds[1]).to.be.eql({ id: 2, authorId: 1 }); + expect(loadedCategories[1].postIds).to.not.be.empty; + expect(loadedCategories[1].postIds[0]).to.be.eql({ id: 3, authorId: 1 }); + + loadedCategories = await connection.manager + .createQueryBuilder(Category, "category") + .loadRelationIdAndMap("category.postIds", "category.posts", "post", qb => qb.andWhere("post.isRemoved = :isRemoved", { isRemoved: true })) + .getMany(); + + expect(loadedCategories[0].postIds).to.not.be.empty; + expect(loadedCategories[0].postIds[0]).to.be.eql({ id: 2, authorId: 1 }); + expect(loadedCategories[1].postIds).to.not.be.empty; + expect(loadedCategories[1].postIds[0]).to.be.eql({ id: 4, authorId: 2 }); + + const loadedCategory = await connection.manager + .createQueryBuilder(Category, "category") + .loadRelationIdAndMap("category.postIds", "category.posts", "post", qb => qb.andWhere("post.isRemoved = :isRemoved", { isRemoved: true })) + .where("category.id = :id", { id: 1 }) + .andWhere("category.code = :code", { code: 1 }) + .getOne(); + + expect(loadedCategory!.postIds).to.not.be.empty; + expect(loadedCategory!.postIds[0]).to.be.eql({ id: 2, authorId: 1 }); + + }))); + + it("should load ids when loadRelationIdAndMap used on nested relation", () => Promise.all(connections.map(async connection => { + + const post1 = new Post(); + post1.id = 1; + post1.authorId = 1; + post1.title = "About BMW"; + await connection.manager.persist(post1); + + const post2 = new Post(); + post2.id = 2; + post2.authorId = 1; + post2.title = "About Audi"; + await connection.manager.persist(post2); + + const post3 = new Post(); + post3.id = 3; + post3.authorId = 1; + post3.title = "About Boeing"; + await connection.manager.persist(post3); + + const post4 = new Post(); + post4.id = 4; + post4.authorId = 2; + post4.title = "About Airbus"; + await connection.manager.persist(post4); + + const category1 = new Category(); + category1.id = 1; + category1.code = 1; + category1.name = "cars"; + category1.posts = [post1, post2]; + await connection.manager.persist(category1); + + const category2 = new Category(); + category2.id = 2; + category2.code = 1; + category2.name = "BMW"; + category2.posts = [post1]; + await connection.manager.persist(category2); + + const category3 = new Category(); + category3.id = 3; + category3.code = 2; + category3.name = "airplanes"; + category3.posts = [post3, post4]; + await connection.manager.persist(category3); + + const image1 = new Image(); + image1.name = "Image #1"; + image1.categories = [category1, category2]; + await connection.manager.persist(image1); + + const image2 = new Image(); + image2.name = "Image #2"; + image2.categories = [category3]; + await connection.manager.persist(image2); + + const loadedImages = await connection.manager + .createQueryBuilder(Image, "image") + .loadRelationIdAndMap("image.categoryIds", "image.categories") + .leftJoinAndSelect("image.categories", "category") + .loadRelationIdAndMap("category.postIds", "category.posts") + .getMany(); + + expect(loadedImages[0].categoryIds).to.not.be.empty; + expect(loadedImages[0].categoryIds.length).to.be.equal(2); + expect(loadedImages[0].categoryIds[0]).to.be.eql({ id: 1, code: 1 }); + expect(loadedImages[0].categoryIds[1]).to.be.eql({ id: 2, code: 1 }); + expect(loadedImages[0].categories).to.not.be.empty; + expect(loadedImages[0].categories.length).to.be.equal(2); + expect(loadedImages[0].categories[0].postIds).to.not.be.empty; + expect(loadedImages[0].categories[0].postIds.length).to.be.equal(2); + expect(loadedImages[0].categories[0].postIds[0]).to.be.eql({ id: 1, authorId: 1 }); + expect(loadedImages[0].categories[0].postIds[1]).to.be.eql({ id: 2, authorId: 1 }); + expect(loadedImages[0].categories[1].postIds).to.not.be.empty; + expect(loadedImages[0].categories[1].postIds.length).to.be.equal(1); + expect(loadedImages[0].categories[1].postIds[0]).to.be.eql({ id: 1, authorId: 1 }); + expect(loadedImages[1].categoryIds).to.not.be.empty; + expect(loadedImages[1].categoryIds.length).to.be.equal(1); + expect(loadedImages[1].categoryIds[0]).to.be.eql({ id: 3, code: 2 }); + expect(loadedImages[1].categories).to.not.be.empty; + expect(loadedImages[1].categories[0].postIds).to.not.be.empty; + expect(loadedImages[1].categories[0].postIds.length).to.be.equal(2); + expect(loadedImages[1].categories[0].postIds[0]).to.be.eql({ id: 3, authorId: 1 }); + expect(loadedImages[1].categories[0].postIds[1]).to.be.eql({ id: 4, authorId: 2 }); + + const loadedImage = await connection.manager + .createQueryBuilder(Image, "image") + .loadRelationIdAndMap("image.categoryIds", "image.categories") + .leftJoinAndSelect("image.categories", "category") + .loadRelationIdAndMap("category.postIds", "category.posts") + .where("image.id = :id", { id: 1 }) + .getOne(); + + expect(loadedImage!.categoryIds).to.not.be.empty; + expect(loadedImage!.categoryIds.length).to.be.equal(2); + expect(loadedImage!.categoryIds[0]).to.be.eql({ id: 1, code: 1 }); + expect(loadedImage!.categoryIds[1]).to.be.eql({ id: 2, code: 1 }); + expect(loadedImage!.categories).to.not.be.empty; + expect(loadedImage!.categories.length).to.be.equal(2); + expect(loadedImage!.categories[0].postIds).to.not.be.empty; + expect(loadedImage!.categories[0].postIds.length).to.be.equal(2); + expect(loadedImage!.categories[0].postIds[0]).to.be.eql({ id: 1, authorId: 1 }); + expect(loadedImage!.categories[0].postIds[1]).to.be.eql({ id: 2, authorId: 1 }); + expect(loadedImage!.categories[1].postIds).to.not.be.empty; + expect(loadedImage!.categories[1].postIds.length).to.be.equal(1); + expect(loadedImage!.categories[1].postIds[0]).to.be.eql({ id: 1, authorId: 1 }); + + }))); + + }); + +}); \ No newline at end of file diff --git a/test/functional/query-builder/relation-id/one-to-many/basic-functionality/basic-functionality.ts b/test/functional/query-builder/relation-id/one-to-many/basic-functionality/basic-functionality.ts new file mode 100644 index 000000000..8acb21f2e --- /dev/null +++ b/test/functional/query-builder/relation-id/one-to-many/basic-functionality/basic-functionality.ts @@ -0,0 +1,240 @@ +import "reflect-metadata"; +import * as chai from "chai"; +import {expect} from "chai"; +import { + closeTestingConnections, + createTestingConnections, + reloadTestingDatabases +} from "../../../../../utils/test-utils"; +import {Connection} from "../../../../../../src/connection/Connection"; +import {Category} from "./entity/Category"; +import {Post} from "./entity/Post"; +import {Image} from "./entity/Image"; + +const should = chai.should(); + +describe.only("query builder > load-relation-id-and-map > one-to-many", () => { + + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + entities: [__dirname + "/entity/*{.js,.ts}"], + schemaCreate: true, + dropSchemaOnConnection: true, + })); + beforeEach(() => reloadTestingDatabases(connections)); + after(() => closeTestingConnections(connections)); + + it("should load id when loadRelationIdAndMap used with OneToMany relation", () => Promise.all(connections.map(async connection => { + + const category1 = new Category(); + category1.name = "cars"; + await connection.manager.persist(category1); + + const category2 = new Category(); + category2.name = "BMW"; + await connection.manager.persist(category2); + + const category3 = new Category(); + category3.name = "airplanes"; + await connection.manager.persist(category3); + + const category4 = new Category(); + category4.name = "Boeing"; + await connection.manager.persist(category4); + + const post1 = new Post(); + post1.title = "about BMW"; + post1.categories = [category1, category2]; + await connection.manager.persist(post1); + + const post2 = new Post(); + post2.title = "about Audi"; + post2.categories = [category3, category4]; + await connection.manager.persist(post2); + + const loadedPosts = await connection.manager + .createQueryBuilder(Post, "post") + .loadRelationIdAndMap("post.categoryIds", "post.categories") + .getMany(); + + expect(loadedPosts[0].categoryIds).to.not.be.empty; + expect(loadedPosts[0].categoryIds.length).to.be.equal(2); + expect(loadedPosts[0].categoryIds[0]).to.be.equal(1); + expect(loadedPosts[0].categoryIds[1]).to.be.equal(2); + expect(loadedPosts[1].categoryIds).to.not.be.empty; + expect(loadedPosts[1].categoryIds.length).to.be.equal(2); + expect(loadedPosts[1].categoryIds[0]).to.be.equal(3); + expect(loadedPosts[1].categoryIds[1]).to.be.equal(4); + + const loadedPost = await connection.manager + .createQueryBuilder(Post, "post") + .loadRelationIdAndMap("post.categoryIds", "post.categories") + .where("post.id = :id", { id: 1 }) + .getOne(); + + expect(loadedPost!.categoryIds).to.not.be.empty; + expect(loadedPost!.categoryIds.length).to.be.equal(2); + expect(loadedPost!.categoryIds[0]).to.be.equal(1); + expect(loadedPost!.categoryIds[1]).to.be.equal(2); + }))); + + it("should load id when loadRelationIdAndMap used with additional condition", () => Promise.all(connections.map(async connection => { + + const category1 = new Category(); + category1.name = "cars"; + await connection.manager.persist(category1); + + const category2 = new Category(); + category2.name = "BMW"; + category2.isRemoved = true; + await connection.manager.persist(category2); + + const category3 = new Category(); + category3.name = "airplanes"; + await connection.manager.persist(category3); + + const category4 = new Category(); + category4.name = "Boeing"; + category4.isRemoved = true; + await connection.manager.persist(category4); + + const post1 = new Post(); + post1.title = "about BMW"; + post1.categories = [category1, category2]; + await connection.manager.persist(post1); + + const post2 = new Post(); + post2.title = "about Audi"; + post2.categories = [category3, category4]; + await connection.manager.persist(post2); + + const loadedPosts = await connection.manager + .createQueryBuilder(Post, "post") + .loadRelationIdAndMap("post.categoryIds", "post.categories", "category", qb => qb.andWhere("category.isRemoved = :isRemoved", { isRemoved: true })) + .getMany(); + + expect(loadedPosts[0].categoryIds).to.not.be.empty; + expect(loadedPosts[0].categoryIds.length).to.be.equal(1); + expect(loadedPosts[0].categoryIds[0]).to.be.equal(2); + expect(loadedPosts[1].categoryIds).to.not.be.empty; + expect(loadedPosts[1].categoryIds.length).to.be.equal(1); + expect(loadedPosts[1].categoryIds[0]).to.be.equal(4); + + const loadedPost = await connection.manager + .createQueryBuilder(Post, "post") + .loadRelationIdAndMap("post.categoryIds", "post.categories", "category", qb => qb.andWhere("category.id = :categoryId", { categoryId: 1 })) + .where("post.id = :id", { id: 1 }) + .getOne(); + + expect(loadedPost!.categoryIds).to.not.be.empty; + expect(loadedPost!.categoryIds.length).to.be.equal(1); + expect(loadedPost!.categoryIds[0]).to.be.equal(1); + }))); + + it("should load id when loadRelationIdAndMap used on nested relation", () => Promise.all(connections.map(async connection => { + + const image1 = new Image(); + image1.name = "Image #1"; + await connection.manager.persist(image1); + + const image2 = new Image(); + image2.name = "Image #2"; + await connection.manager.persist(image2); + + const image3 = new Image(); + image3.name = "Image #3"; + await connection.manager.persist(image3); + + const image4 = new Image(); + image4.name = "Image #4"; + await connection.manager.persist(image4); + + const image5 = new Image(); + image5.name = "Image #5"; + await connection.manager.persist(image5); + + const category1 = new Category(); + category1.name = "cars"; + category1.images = [image1, image2]; + await connection.manager.persist(category1); + + const category2 = new Category(); + category2.name = "BMW"; + category2.images = [image3]; + await connection.manager.persist(category2); + + const category3 = new Category(); + category3.name = "airplanes"; + category3.images = [image4, image5]; + await connection.manager.persist(category3); + + const category4 = new Category(); + category4.name = "Boeing"; + await connection.manager.persist(category4); + + const post1 = new Post(); + post1.title = "about BMW"; + post1.categories = [category1, category2]; + await connection.manager.persist(post1); + + const post2 = new Post(); + post2.title = "about Audi"; + post2.categories = [category3, category4]; + await connection.manager.persist(post2); + + const loadedPosts = await connection.manager + .createQueryBuilder(Post, "post") + .loadRelationIdAndMap("post.categoryIds", "post.categories") + .leftJoinAndSelect("post.categories", "category") + .loadRelationIdAndMap("category.imageIds", "category.images") + .getMany(); + + expect(loadedPosts[0].categoryIds).to.not.be.empty; + expect(loadedPosts[0].categoryIds.length).to.be.equal(2); + expect(loadedPosts[0].categoryIds[0]).to.be.equal(1); + expect(loadedPosts[0].categoryIds[1]).to.be.equal(2); + expect(loadedPosts[0].categories).to.not.be.empty; + expect(loadedPosts[0].categories.length).to.be.equal(2); + expect(loadedPosts[0].categories[0].imageIds).to.not.be.empty; + expect(loadedPosts[0].categories[0].imageIds.length).to.be.equal(2); + expect(loadedPosts[0].categories[0].imageIds[0]).to.be.equal(1); + expect(loadedPosts[0].categories[0].imageIds[1]).to.be.equal(2); + expect(loadedPosts[0].categories[1].imageIds).to.not.be.empty; + expect(loadedPosts[0].categories[1].imageIds.length).to.be.equal(1); + expect(loadedPosts[0].categories[1].imageIds[0]).to.be.equal(3); + expect(loadedPosts[1].categoryIds).to.not.be.empty; + expect(loadedPosts[1].categoryIds.length).to.be.equal(2); + expect(loadedPosts[1].categoryIds[0]).to.be.equal(3); + expect(loadedPosts[1].categoryIds[1]).to.be.equal(4); + expect(loadedPosts[1].categories).to.not.be.empty; + expect(loadedPosts[1].categories.length).to.be.equal(2); + expect(loadedPosts[1].categories[0].imageIds).to.not.be.empty; + expect(loadedPosts[1].categories[0].imageIds.length).to.be.equal(2); + expect(loadedPosts[1].categories[0].imageIds[0]).to.be.equal(4); + expect(loadedPosts[1].categories[0].imageIds[1]).to.be.equal(5); + expect(loadedPosts[1].categories[1].imageIds).to.be.empty; + + const loadedPost = await connection.manager + .createQueryBuilder(Post, "post") + .loadRelationIdAndMap("post.categoryIds", "post.categories") + .leftJoinAndSelect("post.categories", "category") + .loadRelationIdAndMap("category.imageIds", "category.images") + .where("post.id = :id", { id: 1 }) + .getOne(); + + expect(loadedPost!.categoryIds).to.not.be.empty; + expect(loadedPost!.categoryIds.length).to.be.equal(2); + expect(loadedPost!.categoryIds[0]).to.be.equal(1); + expect(loadedPost!.categoryIds[1]).to.be.equal(2); + expect(loadedPost!.categories).to.not.be.empty; + expect(loadedPost!.categories.length).to.be.equal(2); + expect(loadedPost!.categories[0].imageIds).to.not.be.empty; + expect(loadedPost!.categories[0].imageIds.length).to.be.equal(2); + expect(loadedPost!.categories[0].imageIds[0]).to.be.equal(1); + expect(loadedPost!.categories[0].imageIds[1]).to.be.equal(2); + expect(loadedPost!.categories[1].imageIds).to.not.be.empty; + expect(loadedPost!.categories[1].imageIds.length).to.be.equal(1); + expect(loadedPost!.categories[1].imageIds[0]).to.be.equal(3); + }))); + +}); \ No newline at end of file diff --git a/test/functional/query-builder/relation-id/one-to-many/basic-functionality/entity/Category.ts b/test/functional/query-builder/relation-id/one-to-many/basic-functionality/entity/Category.ts new file mode 100644 index 000000000..6d45e4d37 --- /dev/null +++ b/test/functional/query-builder/relation-id/one-to-many/basic-functionality/entity/Category.ts @@ -0,0 +1,31 @@ +import {Entity} from "../../../../../../../src/decorator/entity/Entity"; +import {PrimaryGeneratedColumn} from "../../../../../../../src/decorator/columns/PrimaryGeneratedColumn"; +import {Column} from "../../../../../../../src/decorator/columns/Column"; +import {ManyToOne} from "../../../../../../../src/decorator/relations/ManyToOne"; +import {OneToMany} from "../../../../../../../src/decorator/relations/OneToMany"; +import {Image} from "./Image"; +import {Post} from "./Post"; + +@Entity() +export class Category { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + name: string; + + @Column() + isRemoved: boolean = false; + + @OneToMany(type => Image, image => image.category) + images: Image[]; + + imageIds: number[]; + + @ManyToOne(type => Post, post => post.categories) + post: Post; + + postId: number; + +} \ No newline at end of file diff --git a/test/functional/query-builder/relation-id/one-to-many/basic-functionality/entity/Image.ts b/test/functional/query-builder/relation-id/one-to-many/basic-functionality/entity/Image.ts new file mode 100644 index 000000000..1c2f6eb2b --- /dev/null +++ b/test/functional/query-builder/relation-id/one-to-many/basic-functionality/entity/Image.ts @@ -0,0 +1,21 @@ +import {Entity} from "../../../../../../../src/decorator/entity/Entity"; +import {PrimaryGeneratedColumn} from "../../../../../../../src/decorator/columns/PrimaryGeneratedColumn"; +import {Column} from "../../../../../../../src/decorator/columns/Column"; +import {ManyToOne} from "../../../../../../../src/decorator/relations/ManyToOne"; +import {Category} from "./Category"; + +@Entity() +export class Image { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + name: string; + + @ManyToOne(type => Category, category => category.images) + category: Category; + + categoryId: number; + +} \ No newline at end of file diff --git a/test/functional/query-builder/relation-id/one-to-many/basic-functionality/entity/Post.ts b/test/functional/query-builder/relation-id/one-to-many/basic-functionality/entity/Post.ts new file mode 100644 index 000000000..9d0e6cc71 --- /dev/null +++ b/test/functional/query-builder/relation-id/one-to-many/basic-functionality/entity/Post.ts @@ -0,0 +1,21 @@ +import {Entity} from "../../../../../../../src/decorator/entity/Entity"; +import {PrimaryGeneratedColumn} from "../../../../../../../src/decorator/columns/PrimaryGeneratedColumn"; +import {Column} from "../../../../../../../src/decorator/columns/Column"; +import {OneToMany} from "../../../../../../../src/decorator/relations/OneToMany"; +import {Category} from "./Category"; + +@Entity() +export class Post { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + title: string; + + @OneToMany(type => Category, category => category.post) + categories: Category[]; + + categoryIds: number[]; + +} \ No newline at end of file diff --git a/test/functional/query-builder/relation-id/one-to-many/multiple-pk/entity/Category.ts b/test/functional/query-builder/relation-id/one-to-many/multiple-pk/entity/Category.ts new file mode 100644 index 000000000..3a1f6e628 --- /dev/null +++ b/test/functional/query-builder/relation-id/one-to-many/multiple-pk/entity/Category.ts @@ -0,0 +1,26 @@ +import {Entity} from "../../../../../../../src/decorator/entity/Entity"; +import {Column} from "../../../../../../../src/decorator/columns/Column"; +import {Post} from "./Post"; +import {Index} from "../../../../../../../src/decorator/Index"; +import {PrimaryColumn} from "../../../../../../../src/decorator/columns/PrimaryColumn"; +import {ManyToOne} from "../../../../../../../src/decorator/relations/ManyToOne"; + +@Entity() +@Index(["id", "code"]) +export class Category { + + @PrimaryColumn() + id: number; + + @PrimaryColumn() + code: number; + + @Column() + name: string; + + @ManyToOne(type => Post, post => post.categories) + post: Post; + + postId: number; + +} \ No newline at end of file diff --git a/test/functional/query-builder/relation-id/one-to-many/multiple-pk/entity/Post.ts b/test/functional/query-builder/relation-id/one-to-many/multiple-pk/entity/Post.ts new file mode 100644 index 000000000..05cfd1d63 --- /dev/null +++ b/test/functional/query-builder/relation-id/one-to-many/multiple-pk/entity/Post.ts @@ -0,0 +1,26 @@ +import {Entity} from "../../../../../../../src/decorator/entity/Entity"; +import {Column} from "../../../../../../../src/decorator/columns/Column"; +import {Index} from "../../../../../../../src/decorator/Index"; +import {PrimaryColumn} from "../../../../../../../src/decorator/columns/PrimaryColumn"; +import {Category} from "./Category"; +import {OneToMany} from "../../../../../../../src/decorator/relations/OneToMany"; + +@Entity() +@Index(["id", "authorId"]) +export class Post { + + @PrimaryColumn() + id: number; + + @PrimaryColumn() + authorId: number; + + @Column() + title: string; + + @OneToMany(type => Category, category => category.post) + categories: Category[]; + + categoryIds: number[]; + +} \ No newline at end of file diff --git a/test/functional/query-builder/relation-id/one-to-many/multiple-pk/multiple-pk.ts b/test/functional/query-builder/relation-id/one-to-many/multiple-pk/multiple-pk.ts new file mode 100644 index 000000000..1773304e8 --- /dev/null +++ b/test/functional/query-builder/relation-id/one-to-many/multiple-pk/multiple-pk.ts @@ -0,0 +1,91 @@ +import "reflect-metadata"; +import * as chai from "chai"; +import {expect} from "chai"; +import { + closeTestingConnections, + createTestingConnections, + reloadTestingDatabases +} from "../../../../../utils/test-utils"; +import {Connection} from "../../../../../../src/connection/Connection"; +import {Post} from "./entity/Post"; +import {Category} from "./entity/Category"; + +const should = chai.should(); + +describe("query builder > relation-id > one-to-many > multiple-pk", () => { + + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + entities: [__dirname + "/entity/*{.js,.ts}"], + schemaCreate: true, + dropSchemaOnConnection: true, + })); + beforeEach(() => reloadTestingDatabases(connections)); + after(() => closeTestingConnections(connections)); + + it("should load ids when both entities have multiple primary keys", () => Promise.all(connections.map(async connection => { + + const category1 = new Category(); + category1.id = 1; + category1.code = 1; + category1.name = "cars"; + await connection.manager.persist(category1); + + const category2 = new Category(); + category2.id = 2; + category2.code = 2; + category2.name = "BMW"; + await connection.manager.persist(category2); + + const category3 = new Category(); + category3.id = 3; + category3.code = 1; + category3.name = "airplanes"; + await connection.manager.persist(category3); + + const category4 = new Category(); + category4.id = 4; + category4.code = 2; + category4.name = "Boeing"; + await connection.manager.persist(category4); + + const post1 = new Post(); + post1.id = 1; + post1.authorId = 1; + post1.title = "About BMW"; + post1.categories = [category1, category2]; + await connection.manager.persist(post1); + + const post2 = new Post(); + post2.id = 2; + post2.authorId = 1; + post2.title = "About Boeing"; + post2.categories = [category3, category4]; + await connection.manager.persist(post2); + + const loadedPosts = await connection.manager + .createQueryBuilder(Post, "post") + .loadRelationIdAndMap("post.categoryIds", "post.categories") + .getMany(); + + expect(loadedPosts[0].categoryIds).to.not.be.empty; + expect(loadedPosts[0].categoryIds[0]).to.be.eql({ id: 1, code: 1 }); + expect(loadedPosts[0].categoryIds[1]).to.be.eql({ id: 2, code: 2 }); + expect(loadedPosts[1].categoryIds).to.not.be.empty; + expect(loadedPosts[1].categoryIds[0]).to.be.eql({ id: 3, code: 1 }); + expect(loadedPosts[1].categoryIds[1]).to.be.eql({ id: 4, code: 2 }); + + const loadedPost = await connection.manager + .createQueryBuilder(Post, "post") + .loadRelationIdAndMap("post.categoryIds", "post.categories") + .where("post.id = :id", { id: 1 }) + .andWhere("post.authorId = :authorId", { authorId: 1 }) + .getOne(); + + expect(loadedPost!.categoryIds).to.not.be.empty; + expect(loadedPost!.categoryIds[0]).to.be.eql({ id: 1, code: 1 }); + expect(loadedPost!.categoryIds[1]).to.be.eql({ id: 2, code: 2 }); + + }))); + +}); \ No newline at end of file diff --git a/test/functional/query-builder/relation-id/relation-id-many-to-many/entity/Category.ts b/test/functional/query-builder/relation-id/relation-id-many-to-many/entity/Category.ts deleted file mode 100644 index 0c0b715c5..000000000 --- a/test/functional/query-builder/relation-id/relation-id-many-to-many/entity/Category.ts +++ /dev/null @@ -1,29 +0,0 @@ -import {Entity} from "../../../../../../src/decorator/entity/Entity"; -import {PrimaryGeneratedColumn} from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn"; -import {Column} from "../../../../../../src/decorator/columns/Column"; -import {ManyToMany} from "../../../../../../src/decorator/relations/ManyToMany"; -import {JoinTable} from "../../../../../../src/decorator/relations/JoinTable"; -import {Post} from "./Post"; -import {Image} from "./Image"; - -@Entity() -export class Category { - - @PrimaryGeneratedColumn() - id: number; - - @Column() - name: string; - - @ManyToMany(type => Post, post => post.categories) - posts: Post[]; - - @ManyToMany(type => Image) - @JoinTable() - images: Image[]; - - imageIds: number[]; - - postIds: number[]; - -} \ No newline at end of file diff --git a/test/functional/query-builder/relation-id/relation-id-many-to-many/entity/Image.ts b/test/functional/query-builder/relation-id/relation-id-many-to-many/entity/Image.ts deleted file mode 100644 index 76b95f0f3..000000000 --- a/test/functional/query-builder/relation-id/relation-id-many-to-many/entity/Image.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {Entity} from "../../../../../../src/decorator/entity/Entity"; -import {PrimaryGeneratedColumn} from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn"; -import {Column} from "../../../../../../src/decorator/columns/Column"; - -@Entity() -export class Image { - - @PrimaryGeneratedColumn() - id: number; - - @Column() - name: string; - -} \ No newline at end of file diff --git a/test/functional/query-builder/relation-id/relation-id-many-to-many/entity/Post.ts b/test/functional/query-builder/relation-id/relation-id-many-to-many/entity/Post.ts deleted file mode 100644 index db2e170e3..000000000 --- a/test/functional/query-builder/relation-id/relation-id-many-to-many/entity/Post.ts +++ /dev/null @@ -1,34 +0,0 @@ -import {ManyToMany} from "../../../../../../src/decorator/relations/ManyToMany"; -import {Entity} from "../../../../../../src/decorator/entity/Entity"; -import {PrimaryGeneratedColumn} from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn"; -import {Column} from "../../../../../../src/decorator/columns/Column"; -import {ManyToOne} from "../../../../../../src/decorator/relations/ManyToOne"; -import {JoinTable} from "../../../../../../src/decorator/relations/JoinTable"; -import {Category} from "./Category"; -import {Tag} from "./Tag"; - -@Entity() -export class Post { - - @PrimaryGeneratedColumn() - id: number; - - @Column() - title: string; - - @ManyToOne(type => Tag) - tag: Tag; - - tagId: number; - - @ManyToMany(type => Category, category => category.posts) - @JoinTable() - categories: Category[]; - - @ManyToMany(type => Category) - @JoinTable() - subcategories: Category[]; - - categoryIds: number[]; - -} \ No newline at end of file diff --git a/test/functional/query-builder/relation-id/relation-id-many-to-many/entity/Tag.ts b/test/functional/query-builder/relation-id/relation-id-many-to-many/entity/Tag.ts deleted file mode 100644 index 54bef445c..000000000 --- a/test/functional/query-builder/relation-id/relation-id-many-to-many/entity/Tag.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {Entity} from "../../../../../../src/decorator/entity/Entity"; -import {PrimaryGeneratedColumn} from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn"; -import {Column} from "../../../../../../src/decorator/columns/Column"; - -@Entity() -export class Tag { - - @PrimaryGeneratedColumn() - id: number; - - @Column() - name: string; - -} \ No newline at end of file diff --git a/test/functional/query-builder/relation-id/relation-id-many-to-one/load-relation-id-and-map-many-to-one.ts b/test/functional/query-builder/relation-id/relation-id-many-to-one/load-relation-id-and-map-many-to-one.ts index 2ecb2fe17..ca50facb9 100644 --- a/test/functional/query-builder/relation-id/relation-id-many-to-one/load-relation-id-and-map-many-to-one.ts +++ b/test/functional/query-builder/relation-id/relation-id-many-to-one/load-relation-id-and-map-many-to-one.ts @@ -1,7 +1,7 @@ import "reflect-metadata"; import * as chai from "chai"; import {expect} from "chai"; -import {createTestingConnections, closeTestingConnections, reloadTestingDatabases} from "../../../../utils/test-utils"; +import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../../utils/test-utils"; import {Connection} from "../../../../../src/connection/Connection"; import {Post} from "./entity/Post"; import {Category} from "./entity/Category"; @@ -25,33 +25,33 @@ describe("query builder > load-relation-id-and-map > many-to-one", () => { const category1 = new Category(); category1.name = "cars"; - await connection.entityManager.persist(category1); + await connection.manager.persist(category1); const category2 = new Category(); category2.name = "airplanes"; - await connection.entityManager.persist(category2); + await connection.manager.persist(category2); const categoryByName1 = new Category(); categoryByName1.name = "BMW"; - await connection.entityManager.persist(categoryByName1); + await connection.manager.persist(categoryByName1); const categoryByName2 = new Category(); categoryByName2.name = "Boeing"; - await connection.entityManager.persist(categoryByName2); + await connection.manager.persist(categoryByName2); const post1 = new Post(); post1.title = "about BWM"; post1.category = category1; post1.categoryByName = categoryByName1; - await connection.entityManager.persist(post1); + await connection.manager.persist(post1); const post2 = new Post(); post2.title = "about Boeing"; post2.category = category2; post2.categoryByName = categoryByName2; - await connection.entityManager.persist(post2); + await connection.manager.persist(post2); - let loadedPosts = await connection.entityManager + let loadedPosts = await connection.manager .createQueryBuilder(Post, "post") .loadRelationIdAndMap("post.categoryId", "post.category") .loadRelationIdAndMap("post.categoryName", "post.categoryByName") @@ -66,7 +66,7 @@ describe("query builder > load-relation-id-and-map > many-to-one", () => { expect(loadedPosts![1].categoryName).to.not.be.empty; expect(loadedPosts![1].categoryName).to.be.equal("Boeing"); - let loadedPost = await connection.entityManager + let loadedPost = await connection.manager .createQueryBuilder(Post, "post") .loadRelationIdAndMap("post.categoryId", "post.category") .loadRelationIdAndMap("post.categoryName", "post.categoryByName") @@ -83,18 +83,18 @@ describe("query builder > load-relation-id-and-map > many-to-one", () => { const category = new Category(); category.name = "cars"; - await connection.entityManager.persist(category); + await connection.manager.persist(category); const post = new Post(); post.title = "about cars"; - await connection.entityManager.persist(post); + await connection.manager.persist(post); const postCategory = new PostCategory(); postCategory.category = category; postCategory.post = post; - await connection.entityManager.persist(postCategory); + await connection.manager.persist(postCategory); - let loadedPostCategory = await connection.entityManager + let loadedPostCategory = await connection.manager .createQueryBuilder(PostCategory, "postCategory") .loadRelationIdAndMap("postCategory.postId", "postCategory.post") .loadRelationIdAndMap("postCategory.categoryId", "postCategory.category") @@ -110,23 +110,23 @@ describe("query builder > load-relation-id-and-map > many-to-one", () => { const category = new Category(); category.name = "cars"; - await connection.entityManager.persist(category); + await connection.manager.persist(category); const post = new Post(); post.title = "about cars"; - await connection.entityManager.persist(post); + await connection.manager.persist(post); const image = new Image(); image.name = "image #1"; - await connection.entityManager.persist(image); + await connection.manager.persist(image); const postCategory = new PostCategory(); postCategory.category = category; postCategory.post = post; postCategory.image = image; - await connection.entityManager.persist(postCategory); + await connection.manager.persist(postCategory); - let loadedPostCategory = await connection.entityManager + let loadedPostCategory = await connection.manager .createQueryBuilder(PostCategory, "postCategory") .loadRelationIdAndMap("postCategory.imageId", "postCategory.image") .getOne(); diff --git a/test/functional/query-builder/relation-id/relation-id-one-to-many/entity/Category.ts b/test/functional/query-builder/relation-id/relation-id-one-to-many/entity/Category.ts deleted file mode 100644 index f83f23df3..000000000 --- a/test/functional/query-builder/relation-id/relation-id-one-to-many/entity/Category.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {Entity} from "../../../../../../src/decorator/entity/Entity"; -import {PrimaryGeneratedColumn} from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn"; -import {Column} from "../../../../../../src/decorator/columns/Column"; -import {OneToMany} from "../../../../../../src/decorator/relations/OneToMany"; -import {Post} from "./Post"; - -@Entity() -export class Category { - - @PrimaryGeneratedColumn() - id: number; - - @Column() - name: string; - - @OneToMany(type => Post, post => post.category) - posts: Post[]; - - postIds: number[]; - -} \ No newline at end of file diff --git a/test/functional/query-builder/relation-id/relation-id-one-to-many/entity/Post.ts b/test/functional/query-builder/relation-id/relation-id-one-to-many/entity/Post.ts deleted file mode 100644 index 0621a5057..000000000 --- a/test/functional/query-builder/relation-id/relation-id-one-to-many/entity/Post.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {Entity} from "../../../../../../src/decorator/entity/Entity"; -import {PrimaryGeneratedColumn} from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn"; -import {Column} from "../../../../../../src/decorator/columns/Column"; -import {ManyToOne} from "../../../../../../src/decorator/relations/ManyToOne"; -import {Category} from "./Category"; - -@Entity() -export class Post { - - @PrimaryGeneratedColumn() - id: number; - - @Column() - title: string; - - @ManyToOne(type => Category, category => category.posts) - category: Category; - - categoryId: number; - -} \ No newline at end of file diff --git a/test/functional/query-builder/relation-id/relation-id-one-to-many/load-relation-id-and-map-one-to-many.ts b/test/functional/query-builder/relation-id/relation-id-one-to-many/load-relation-id-and-map-one-to-many.ts deleted file mode 100644 index af6caf31e..000000000 --- a/test/functional/query-builder/relation-id/relation-id-one-to-many/load-relation-id-and-map-one-to-many.ts +++ /dev/null @@ -1,75 +0,0 @@ -import "reflect-metadata"; -import * as chai from "chai"; -import {expect} from "chai"; -import {createTestingConnections, closeTestingConnections, reloadTestingDatabases} from "../../../../utils/test-utils"; -import {Connection} from "../../../../../src/connection/Connection"; -import {Category} from "./entity/Category"; -import {Post} from "./entity/Post"; - -const should = chai.should(); - -describe("query builder > load-relation-id-and-map > one-to-many", () => { - - let connections: Connection[]; - before(async () => connections = await createTestingConnections({ - entities: [__dirname + "/entity/*{.js,.ts}"], - schemaCreate: true, - dropSchemaOnConnection: true, - })); - beforeEach(() => reloadTestingDatabases(connections)); - after(() => closeTestingConnections(connections)); - - it("should load id when loadRelationIdAndMap used with OneToMany relation", () => Promise.all(connections.map(async connection => { - - const category = new Category(); - category.name = "cars"; - await connection.entityManager.persist(category); - - const post1 = new Post(); - post1.title = "about BMW"; - post1.category = category; - await connection.entityManager.persist(post1); - - const post2 = new Post(); - post2.title = "about Audi"; - post2.category = category; - await connection.entityManager.persist(post2); - - let loadedCategory = await connection.entityManager - .createQueryBuilder(Category, "category") - .loadRelationIdAndMap("category.postIds", "category.posts") - .getOne(); - - expect(loadedCategory!.postIds).to.not.be.empty; - expect(loadedCategory!.postIds.length).to.be.equal(2); - expect(loadedCategory!.postIds[0]).to.be.equal(1); - expect(loadedCategory!.postIds[1]).to.be.equal(2); - }))); - - it("should load id when loadRelationIdAndMap used with OneToMany relation and additional condition", () => Promise.all(connections.map(async connection => { - - const category = new Category(); - category.name = "cars"; - await connection.entityManager.persist(category); - - const post1 = new Post(); - post1.title = "about BMW"; - post1.category = category; - await connection.entityManager.persist(post1); - - const post2 = new Post(); - post2.title = "about Audi"; - post2.category = category; - await connection.entityManager.persist(post2); - - let loadedCategory = await connection.entityManager - .createQueryBuilder(Category, "category") - .loadRelationIdAndMap("category.postIds", "category.posts", "posts", qb => qb.andWhere("posts.id = :postId", { postId: 1 })) - .getOne(); - - expect(loadedCategory!.postIds).to.not.be.empty; - expect(loadedCategory!.postIds.length).to.be.equal(1); - expect(loadedCategory!.postIds[0]).to.be.equal(1); - }))); - -}); \ No newline at end of file diff --git a/test/functional/query-builder/relation-id/relation-id-one-to-one/load-relation-id-and-map-one-to-one.ts b/test/functional/query-builder/relation-id/relation-id-one-to-one/load-relation-id-and-map-one-to-one.ts index 9bb0ee562..e7ee7e329 100644 --- a/test/functional/query-builder/relation-id/relation-id-one-to-one/load-relation-id-and-map-one-to-one.ts +++ b/test/functional/query-builder/relation-id/relation-id-one-to-one/load-relation-id-and-map-one-to-one.ts @@ -1,7 +1,7 @@ import "reflect-metadata"; import * as chai from "chai"; import {expect} from "chai"; -import {createTestingConnections, closeTestingConnections, reloadTestingDatabases} from "../../../../utils/test-utils"; +import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../../utils/test-utils"; import {Connection} from "../../../../../src/connection/Connection"; import {Category} from "./entity/Category"; import {Post} from "./entity/Post"; @@ -24,23 +24,23 @@ describe("query builder > load-relation-id-and-map > one-to-one", () => { const category = new Category(); category.name = "kids"; - await connection.entityManager.persist(category); + await connection.manager.persist(category); const post = new Post(); post.title = "about kids"; post.category = category; - await connection.entityManager.persist(post); + await connection.manager.persist(post); const category2 = new Category(); category2.name = "cars"; - await connection.entityManager.persist(category2); + await connection.manager.persist(category2); const post2 = new Post(); post2.title = "about cars"; post2.category = category2; - await connection.entityManager.persist(post2); + await connection.manager.persist(post2); - let loadedPosts = await connection.entityManager + let loadedPosts = await connection.manager .createQueryBuilder(Post, "post") .loadRelationIdAndMap("post.categoryId", "post.category") .getMany(); @@ -50,7 +50,7 @@ describe("query builder > load-relation-id-and-map > one-to-one", () => { expect(loadedPosts![1].categoryId).to.not.be.empty; expect(loadedPosts![1].categoryId).to.be.equal(2); - let loadedPost = await connection.entityManager + let loadedPost = await connection.manager .createQueryBuilder(Post, "post") .loadRelationIdAndMap("post.categoryId", "post.category") .where("post.id = :id", { id: post.id }) @@ -64,23 +64,23 @@ describe("query builder > load-relation-id-and-map > one-to-one", () => { const category = new Category(); category.name = "kids"; - await connection.entityManager.persist(category); + await connection.manager.persist(category); const post = new Post(); post.title = "about kids"; post.category2 = category; - await connection.entityManager.persist(post); + await connection.manager.persist(post); const category2 = new Category(); category2.name = "cars"; - await connection.entityManager.persist(category2); + await connection.manager.persist(category2); const post2 = new Post(); post2.title = "about cars"; post2.category2 = category2; - await connection.entityManager.persist(post2); + await connection.manager.persist(post2); - let loadedCategories = await connection.entityManager + let loadedCategories = await connection.manager .createQueryBuilder(Category, "category") .loadRelationIdAndMap("category.postId", "category.post") .getMany(); @@ -90,7 +90,7 @@ describe("query builder > load-relation-id-and-map > one-to-one", () => { expect(loadedCategories![1].postId).to.not.be.empty; expect(loadedCategories![1].postId).to.be.equal(2); - let loadedCategory = await connection.entityManager + let loadedCategory = await connection.manager .createQueryBuilder(Category, "category") .loadRelationIdAndMap("category.postId", "category.post") .getOne(); diff --git a/test/utils/test-utils.ts b/test/utils/test-utils.ts index 336b374da..b696251d2 100644 --- a/test/utils/test-utils.ts +++ b/test/utils/test-utils.ts @@ -191,4 +191,17 @@ export function setupConnection(callback: (connection: Connection) => any, entit return connection; }); }; +} + +/** + * Generates random text array with custom length. + */ +export function generateRandomText(length: number): string { + let text = ""; + const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + for(let i = 0; i <= length; i++ ) + text += characters.charAt(Math.floor(Math.random() * characters.length)); + + return text; } \ No newline at end of file