Merge remote-tracking branch 'typeorm/master' into feature/extend-lazy-relation-testing

This commit is contained in:
Luchillo 2017-09-17 13:59:16 -05:00
commit 6fa4cb9021
45 changed files with 2046 additions and 469 deletions

View File

@ -120,12 +120,11 @@ You'll also need to enable `es6` in the `lib` section of compiler options, or in
TypeORM was tested with Node.js version 4 and above.
If you have errors during app bootstrap, try to upgrade your Node.js version to the latest version.
#### Usage in the browser with WebSQL (experimental)
#### Usage in the browser with WebSQL (experimental) or in a Cordova/Phonegap/Ionic app
TypeORM works in the browser and has experimental support of WebSQL.
If you want to use TypeORM in the browser then you need to `npm i typeorm-browser` instead of `typeorm`.
More information about it is in [this page](https://typeorm.github.io/usage-in-browser.html).
Also take a look at [this sample](https://github.com/typeorm/browser-example).
TypeORM works in the browser and has experimental support of WebSQL and supports the [cordova-sqlite-storage](https://github.com/litehelpers/Cordova-sqlite-storage) plugin for Cordova/Phonegap/Ionic.
More information about how to install and use TypeORM in the browser or an app can be found in the [docs](docs/usage-in-the-browser-and-cordova.md).
Also take a look at the [browser](https://github.com/typeorm/browser-example) and [cordova](https://github.com/typeorm/cordova-example) example.
## Quick start

View File

@ -548,6 +548,8 @@ Used in some column types.
* `enum: string[]|AnyEnum` - Used in `enum` column type to specify list of allowed enum values.
You can specify array of values or specify a enum class.
* `array: boolean` - Used for postgres column types which can be array (for example int[])
* `transformer: { from(value: DatabaseType): EntityType, to(value: EntityType): DatabaseType }` - Used to
marshal properties of arbitrary type `EntityType` into a type `DatabaseType` supported by the database.
Note: most of those column options are RDBMS-specific and aren't available in `MongoDB`.

View File

@ -6,7 +6,7 @@ Its all possible with TypeORM:
* write code in TypeScript or JavaScript
* works on node.js / browser / ionic / electron platforms [TBD]
* supports mysql / mariadb / postgres / sqlite / sql server / oracle / websql / sqljs [TBD]
* supports mysql / mariadb / postgres / sqlite / sql server / oracle / websql / sqljs
* supports all data types your database support
* schema configuration using decorators / json / xml / yml files
* you produce performant, flexible, clean and maintainable code using it

6
docs/internals.md Normal file
View File

@ -0,0 +1,6 @@
# TypeORM internals
Explaining how things are working in TypeORM.
This guide will be useful for contributors.
TBD.

View File

@ -0,0 +1,19 @@
# Usage in the browser and Cordova
The standard `typeorm` package also supports browser usage and can also be used in cordova based projects (cordova, phonegap, ionic).
The package includes two ways to use in the browser
## SystemJS
If you want to use SystemJS as a module loader than three precompiled files are included for you to use.
```
- typeorm-browser.js
- typeorm-browser.min.js (Minified version)
- typeorm-browser.js.map (Sourcemap)
```
Since these files are generated by the TypeScript compiler they are affected by the [issue, that SystemJS 0.20 does not support them](https://github.com/systemjs/systemjs/issues/1587). When using SystemJS@0.19.47 everything works.
For an example see [typeorm/browser-example](https://github.com/typeorm/browser-example).
## ES2015 Module
In the `browser` folder the package also includes a version compiled as a ES2015 module. If you want to use a different loader this is the point to start. The package is setup in a way that loaders like webpack will automatically use the `browser` folder.
## Cordova/Phonegap
Thanks to the [cordova-sqlite-storage](https://github.com/litehelpers/Cordova-sqlite-storage) plugin, TypeORM can also run in a Cordova or Phonegap app, where you again have the option to choose between module loaders. For an example see [typeorm/cordova-example](https://github.com/typeorm/cordova-example).

View File

@ -141,7 +141,7 @@ export class Gulpfile {
*/
@Task()
browserUglify() {
return gulp.src("./build/browser/typeorm-browser.js")
return gulp.src("./build/package/typeorm-browser.js")
.pipe(uglify())
.pipe(rename("typeorm-browser.min.js"))
.pipe(gulp.dest("./build/package"));

View File

@ -36,7 +36,7 @@
"database": "test"
},
{
"skip": false,
"skip": true,
"name": "mssql",
"type": "mssql",
"host": "localhost",

View File

@ -377,36 +377,18 @@ export class Connection {
}
/**
* Creates a new entity manager with a single opened connection to the database.
* This may be useful if you want to perform all db queries within one connection.
* After finishing with entity manager, don't forget to release it (to release database connection back to pool).
* Gets entity metadata of the junction table (many-to-many table).
*/
/*createIsolatedManager(): EntityManager {
if (this.driver instanceof MongoDriver)
throw new Error(`You can use createIsolatedManager only for non MongoDB connections.`);
getManyToManyMetadata(entityTarget: Function|string, relationPropertyPath: string) {
const relationMetadata = this.getMetadata(entityTarget).findRelationWithPropertyPath(relationPropertyPath);
if (!relationMetadata)
throw new Error(`Relation "${relationPropertyPath}" was not found in ${entityTarget} entity.`);
if (!relationMetadata.isManyToMany)
throw new Error(`Relation "${entityTarget}#${relationPropertyPath}" does not have a many-to-many relationship.` +
`You can use this method only on many-to-many relations.`);
// sqlite has a single query runner and does not support isolated managers
if (this.driver instanceof SqliteDriver)
return this.manager;
return new EntityManagerFactory().create(this, this.driver.createQueryRunner());
}*/
/**
* Creates a new repository with a single opened connection to the database.
* This may be useful if you want to perform all db queries within one connection.
* After finishing with repository, don't forget to release its query runner (to release database connection back to pool).
*/
/*createIsolatedRepository<Entity>(entityClassOrName: ObjectType<Entity>|string): Repository<Entity> {
if (this.driver instanceof MongoDriver)
throw new Error(`You can use createIsolatedRepository only for non MongoDB connections.`);
// sqlite has a single query runner and does not support isolated repositories
if (this.driver instanceof SqliteDriver)
return this.manager.getRepository(entityClassOrName);
return this.createIsolatedManager().getRepository(entityClassOrName);
}*/
return relationMetadata.junctionEntityMetadata;
}
// -------------------------------------------------------------------------
// Deprecated Public Methods

View File

@ -1,3 +1,5 @@
import {ValueTransformer} from "./ValueTransformer";
/**
* Column options specific to all column types.
*/
@ -47,4 +49,10 @@ export interface ColumnCommonOptions {
*/
isArray?: boolean;
}
/**
* Specifies a value transformer that is to be used to (un)marshal
* this column when reading or writing to the database.
*/
transformer?: ValueTransformer;
}

View File

@ -1,4 +1,5 @@
import {ColumnType} from "../../driver/types/ColumnTypes";
import {ValueTransformer} from "./ValueTransformer";
/**
* Describes all column's options.
@ -95,4 +96,10 @@ export interface ColumnOptions {
*/
isArray?: boolean; // todo: rename to array?: boolean
/**
* Specifies a value transformer that is to be used to (un)marshal
* this column when reading or writing to the database.
*/
transformer?: ValueTransformer;
}

View File

@ -0,0 +1,18 @@
/**
* Interface for objects that deal with (un)marshalling data.
*/
export interface ValueTransformer {
/**
* Used to marshal data when writing to
* the database.
*/
to (value: any): any;
/**
* Used to unmarshal data when reading from
* the database.
*/
from (value: any): any;
}

View File

@ -109,6 +109,11 @@ export interface Driver {
*/
normalizeDefault(column: ColumnMetadata): string;
/**
* Normalizes "isUnique" value of the column.
*/
normalizeIsUnique(column: ColumnMetadata): boolean;
/**
* Normalizes "default" value of the column.
*/

View File

@ -31,6 +31,9 @@ export class CordovaDriver extends AbstractSqliteDriver {
if (!this.options.database)
throw new DriverOptionNotSetError("database");
if (!this.options.location)
throw new DriverOptionNotSetError("location");
// load sqlite package
this.loadDependencies();
}

View File

@ -217,6 +217,8 @@ export class MongoDriver implements Driver {
* Prepares given value to a value to be persisted, based on its column type and metadata.
*/
preparePersistentValue(value: any, columnMetadata: ColumnMetadata): any {
if (columnMetadata.transformer)
value = columnMetadata.transformer.to(value);
return value;
}
@ -224,6 +226,8 @@ export class MongoDriver implements Driver {
* Prepares given value to a value to be persisted, based on its column type or metadata.
*/
prepareHydratedValue(value: any, columnMetadata: ColumnMetadata): any {
if (columnMetadata.transformer)
value = columnMetadata.transformer.from(value);
return value;
}
@ -241,6 +245,13 @@ export class MongoDriver implements Driver {
throw new Error(`MongoDB is schema-less, not supported by this driver.`);
}
/**
* Normalizes "isUnique" value of the column.
*/
normalizeIsUnique(column: ColumnMetadata): boolean {
throw new Error(`MongoDB is schema-less, not supported by this driver.`);
}
/**
* Normalizes "default" value of the column.
*/

View File

@ -268,6 +268,9 @@ export class MysqlDriver implements Driver {
* Prepares given value to a value to be persisted, based on its column type and metadata.
*/
preparePersistentValue(value: any, columnMetadata: ColumnMetadata): any {
if (columnMetadata.transformer)
value = columnMetadata.transformer.to(value);
if (value === null || value === undefined)
return value;
@ -300,6 +303,9 @@ export class MysqlDriver implements Driver {
* Prepares given value to a value to be persisted, based on its column type or metadata.
*/
prepareHydratedValue(value: any, columnMetadata: ColumnMetadata): any {
if (columnMetadata.transformer)
value = columnMetadata.transformer.from(value);
if (value === null || value === undefined)
return value;
@ -376,6 +382,14 @@ export class MysqlDriver implements Driver {
}
}
/**
* Normalizes "isUnique" value of the column.
*/
normalizeIsUnique(column: ColumnMetadata): boolean {
return column.isUnique ||
!!column.entityMetadata.indices.find(index => index.isUnique && index.columns.length === 1 && index.columns[0] === column);
}
createFullType(column: ColumnSchema): string {
let type = column.type;

View File

@ -252,6 +252,9 @@ export class OracleDriver implements Driver {
* Prepares given value to a value to be persisted, based on its column type and metadata.
*/
preparePersistentValue(value: any, columnMetadata: ColumnMetadata): any {
if (columnMetadata.transformer)
value = columnMetadata.transformer.to(value);
if (value === null || value === undefined)
return value;
@ -281,6 +284,9 @@ export class OracleDriver implements Driver {
* Prepares given value to a value to be persisted, based on its column type or metadata.
*/
prepareHydratedValue(value: any, columnMetadata: ColumnMetadata): any {
if (columnMetadata.transformer)
value = columnMetadata.transformer.from(value);
if (value === null || value === undefined)
return value;
@ -354,6 +360,13 @@ export class OracleDriver implements Driver {
}
}
/**
* Normalizes "isUnique" value of the column.
*/
normalizeIsUnique(column: ColumnMetadata): boolean {
return column.isUnique;
}
createFullType(column: ColumnSchema): string {
let type = column.type;

View File

@ -258,6 +258,9 @@ export class PostgresDriver implements Driver {
* Prepares given value to a value to be persisted, based on its column type and metadata.
*/
preparePersistentValue(value: any, columnMetadata: ColumnMetadata): any {
if (columnMetadata.transformer)
value = columnMetadata.transformer.to(value);
if (value === null || value === undefined)
return value;
@ -291,6 +294,9 @@ export class PostgresDriver implements Driver {
* Prepares given value to a value to be persisted, based on its column type or metadata.
*/
prepareHydratedValue(value: any, columnMetadata: ColumnMetadata): any {
if (columnMetadata.transformer)
value = columnMetadata.transformer.from(value);
if (value === null || value === undefined)
return value;
@ -448,6 +454,13 @@ export class PostgresDriver implements Driver {
}
}
/**
* Normalizes "isUnique" value of the column.
*/
normalizeIsUnique(column: ColumnMetadata): boolean {
return column.isUnique;
}
/**
* Normalizes "default" value of the column.
*/

View File

@ -191,6 +191,9 @@ export class AbstractSqliteDriver implements Driver {
* Prepares given value to a value to be persisted, based on its column type and metadata.
*/
preparePersistentValue(value: any, columnMetadata: ColumnMetadata): any {
if (columnMetadata.transformer)
value = columnMetadata.transformer.to(value);
if (value === null || value === undefined)
return value;
@ -220,6 +223,9 @@ export class AbstractSqliteDriver implements Driver {
* Prepares given value to a value to be hydrated, based on its column type or metadata.
*/
prepareHydratedValue(value: any, columnMetadata: ColumnMetadata): any {
if (columnMetadata.transformer)
value = columnMetadata.transformer.from(value);
if (value === null || value === undefined)
return value;
@ -326,6 +332,13 @@ export class AbstractSqliteDriver implements Driver {
}
}
/**
* Normalizes "isUnique" value of the column.
*/
normalizeIsUnique(column: ColumnMetadata): boolean {
return column.isUnique;
}
/**
* Normalizes "default" value of the column.
*/

View File

@ -261,6 +261,9 @@ export class SqlServerDriver implements Driver {
* Prepares given value to a value to be persisted, based on its column type and metadata.
*/
preparePersistentValue(value: any, columnMetadata: ColumnMetadata): any {
if (columnMetadata.transformer)
value = columnMetadata.transformer.to(value);
if (value === null || value === undefined)
return value;
@ -296,6 +299,9 @@ export class SqlServerDriver implements Driver {
* Prepares given value to a value to be persisted, based on its column type or metadata.
*/
prepareHydratedValue(value: any, columnMetadata: ColumnMetadata): any {
if (columnMetadata.transformer)
value = columnMetadata.transformer.from(value);
if (value === null || value === undefined)
return value;
@ -386,6 +392,13 @@ export class SqlServerDriver implements Driver {
}
}
/**
* Normalizes "isUnique" value of the column.
*/
normalizeIsUnique(column: ColumnMetadata): boolean {
return column.isUnique;
}
createFullType(column: ColumnSchema): string {
let type = column.type;

View File

@ -120,6 +120,7 @@ export {UpdateEvent} from "./subscriber/event/UpdateEvent";
export {RemoveEvent} from "./subscriber/event/RemoveEvent";
export {EntitySubscriberInterface} from "./subscriber/EntitySubscriberInterface";
export {BaseEntity} from "./repository/BaseEntity";
export {EntitySchema} from "./entity-schema/EntitySchema";
// -------------------------------------------------------------------------
// Deprecated

View File

@ -101,6 +101,8 @@ export class JunctionEntityMetadataBuilder {
});
// set junction table columns
entityMetadata.ownerColumns = junctionColumns;
entityMetadata.inverseColumns = inverseJunctionColumns;
entityMetadata.ownColumns = [...junctionColumns, ...inverseJunctionColumns];
entityMetadata.ownColumns.forEach(column => column.relationMetadata = relation);

View File

@ -6,6 +6,7 @@ import {ObjectLiteral} from "../common/ObjectLiteral";
import {ColumnMetadataArgs} from "../metadata-args/ColumnMetadataArgs";
import {Connection} from "../connection/Connection";
import {OrmUtils} from "../util/OrmUtils";
import {ValueTransformer} from "../decorator/options/ValueTransformer";
/**
* This metadata contains all information about entity's column.
@ -205,6 +206,12 @@ export class ColumnMetadata {
*/
referencedColumn: ColumnMetadata|undefined;
/**
* Specifies a value transformer that is to be used to (un)marshal
* this column when reading or writing to the database.
*/
transformer?: ValueTransformer;
// ---------------------------------------------------------------------
// Constructor
// ---------------------------------------------------------------------
@ -275,6 +282,8 @@ export class ColumnMetadata {
this.isVersion = options.args.mode === "version";
this.isObjectId = options.args.mode === "objectId";
}
if (options.args.options.transformer)
this.transformer = options.args.options.transformer;
if (this.isTreeLevel)
this.type = options.connection.driver.mappedDataTypes.treeLevel;
if (this.isCreateDate) {

View File

@ -138,6 +138,18 @@ export class EntityMetadata {
*/
columns: ColumnMetadata[] = [];
/**
* In the case if this entity metadata is junction table's entity metadata,
* this will contain all referenced columns of owner entity.
*/
ownerColumns: ColumnMetadata[] = [];
/**
* In the case if this entity metadata is junction table's entity metadata,
* this will contain all referenced columns of inverse entity.
*/
inverseColumns: ColumnMetadata[] = [];
/**
* Entity's relation id metadatas.
*/

View File

@ -10,6 +10,7 @@ import {RelationQueryBuilder} from "./RelationQueryBuilder";
import {ObjectType} from "../common/ObjectType";
import {Alias} from "./Alias";
import {Brackets} from "./Brackets";
import {QueryPartialEntity} from "./QueryPartialEntity";
// todo: completely cover query builder with tests
// todo: entityOrProperty can be target name. implement proper behaviour if it is.
@ -167,22 +168,22 @@ export abstract class QueryBuilder<Entity> {
/**
* Creates UPDATE query and applies given update values.
*/
update(updateSet: ObjectLiteral): UpdateQueryBuilder<Entity>;
update(updateSet: QueryPartialEntity<Entity>): UpdateQueryBuilder<Entity>;
/**
* Creates UPDATE query for the given entity and applies given update values.
*/
update<T>(entity: ObjectType<T>, updateSet: ObjectLiteral): UpdateQueryBuilder<T>;
update<T>(entity: ObjectType<T>, updateSet?: QueryPartialEntity<T>): UpdateQueryBuilder<T>;
/**
* Creates UPDATE query for the given entity and applies given update values.
*/
update(entity: string, updateSet: ObjectLiteral): UpdateQueryBuilder<Entity>;
update(entity: Function|string, updateSet?: QueryPartialEntity<Entity>): UpdateQueryBuilder<Entity>;
/**
* Creates UPDATE query for the given table name and applies given update values.
*/
update(tableName: string, updateSet: ObjectLiteral): UpdateQueryBuilder<Entity>;
update(tableName: string, updateSet?: QueryPartialEntity<Entity>): UpdateQueryBuilder<Entity>;
/**
* Creates UPDATE query and applies given update values.
@ -223,11 +224,27 @@ export abstract class QueryBuilder<Entity> {
/**
* Sets entity's relation with which this query builder gonna work.
*/
relation(entityTarget: Function|string, propertyPath: string): RelationQueryBuilder<Entity> {
relation(propertyPath: string): RelationQueryBuilder<Entity>;
/**
* Sets entity's relation with which this query builder gonna work.
*/
relation<T>(entityTarget: ObjectType<T>|string, propertyPath: string): RelationQueryBuilder<T>;
/**
* Sets entity's relation with which this query builder gonna work.
*/
relation(entityTargetOrPropertyPath: Function|string, maybePropertyPath?: string): RelationQueryBuilder<Entity> {
const entityTarget = arguments.length === 2 ? entityTargetOrPropertyPath : undefined;
const propertyPath = arguments.length === 2 ? maybePropertyPath as string : entityTargetOrPropertyPath as string;
this.expressionMap.queryType = "relation";
// qb.expressionMap.propertyPath = propertyPath;
const mainAlias = this.createFromAlias(entityTarget);
this.expressionMap.setMainAlias(mainAlias);
this.expressionMap.relationPropertyPath = propertyPath;
if (entityTarget) {
const mainAlias = this.createFromAlias(entityTarget);
this.expressionMap.setMainAlias(mainAlias);
}
// loading it dynamically because of circular issue
const RelationQueryBuilderCls = require("./RelationQueryBuilder").RelationQueryBuilder;
@ -237,6 +254,31 @@ export abstract class QueryBuilder<Entity> {
return new RelationQueryBuilderCls(this);
}
/**
* Checks if given relation exists in the entity.
* Returns true if relation exists, false otherwise.
*/
hasRelation<T>(target: ObjectType<T>|string, relation: string): boolean;
/**
* Checks if given relations exist in the entity.
* Returns true if relation exists, false otherwise.
*/
hasRelation<T>(target: ObjectType<T>|string, relation: string[]): boolean;
/**
* Checks if given relation or relations exist in the entity.
* Returns true if relation exists, false otherwise.
*/
hasRelation<T>(target: ObjectType<T>|string, relation: string|string[]): boolean {
const entityMetadata = this.connection.getMetadata(target);
const relations = relation instanceof Array ? relation : [relation];
return relations.every(relation => {
return !!entityMetadata.findRelationWithPropertyPath(relation);
});
}
/**
* Sets parameter name and its value.
*/

View File

@ -183,6 +183,17 @@ export class QueryExpressionMap {
*/
cacheId: string;
/**
* Property path of relation to work with.
* Used in relational query builder.
*/
relationPropertyPath: string;
/**
* Entity (target) which relations will be updated.
*/
of: any|any[];
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
@ -310,6 +321,8 @@ export class QueryExpressionMap {
map.cache = this.cache;
map.cacheId = this.cacheId;
map.cacheDuration = this.cacheDuration;
map.relationPropertyPath = this.relationPropertyPath;
map.of = this.of;
return map;
}

View File

@ -1,9 +1,11 @@
import {QueryBuilder} from "./QueryBuilder";
import {RelationMetadata} from "../metadata/RelationMetadata";
import {ObjectLiteral} from "../common/ObjectLiteral";
/**
* Allows to build complex sql queries in a fashion way and execute those queries.
* Allows to work with entity relations and perform specific operations with those relations.
*
* todo: implement all functions using SpecificRepository code.
* todo: add transactions everywhere
*/
export class RelationQueryBuilder<Entity> extends QueryBuilder<Entity> {
@ -22,15 +24,38 @@ export class RelationQueryBuilder<Entity> extends QueryBuilder<Entity> {
// Public Methods
// -------------------------------------------------------------------------
/**
* Sets entity (target) which relations will be updated.
*/
of(entity: any|any[]): this {
this.expressionMap.of = entity;
return this;
}
/**
* Sets entity relation's value.
* Value can be entity, entity id or entity id map (if entity has composite ids).
* Works only for many-to-one and one-to-one relations.
* For many-to-many and one-to-many relations use #add and #remove methods instead.
*/
set(value: any): this {
async set(value: any): Promise<void> {
const relation = this.relationMetadata;
return this;
if (!this.expressionMap.of) // todo: move this check before relation query builder creation?
throw new Error(`Entity whose relation needs to be set is not set. Use .of method to define whose relation you want to set.`);
if (relation.isManyToMany || relation.isOneToMany)
throw new Error(`Set operation is only supported for many-to-one and one-to-one relations. ` +
`However given "${relation.propertyPath}" has ${relation.relationType} relation. ` +
`Use .add() method instead.`);
// if there are multiple join columns then user must send id map as "value" argument. check if he really did it
if (relation.joinColumns &&
relation.joinColumns.length > 1 &&
(!(value instanceof Object) || Object.keys(value).length < relation.joinColumns.length))
throw new Error(`Value to be set into the relation must be a map of relation ids, for example: .set({ firstName: "...", lastName: "..." })`);
return this.updateRelation(value);
}
/**
@ -40,9 +65,24 @@ export class RelationQueryBuilder<Entity> extends QueryBuilder<Entity> {
* Works only for many-to-many and one-to-many relations.
* For many-to-one and one-to-one use #set method instead.
*/
add(value: any|any[]): this {
async add(value: any|any[]): Promise<void> {
const relation = this.relationMetadata;
return this;
if (!this.expressionMap.of) // todo: move this check before relation query builder creation?
throw new Error(`Entity whose relation needs to be set is not set. Use .of method to define whose relation you want to set.`);
if (relation.isManyToOne || relation.isOneToOne)
throw new Error(`Add operation is only supported for many-to-many and one-to-many relations. ` +
`However given "${relation.propertyPath}" has ${relation.relationType} relation. ` +
`Use .set() method instead.`);
// if there are multiple join columns then user must send id map as "value" argument. check if he really did it
if (relation.joinColumns &&
relation.joinColumns.length > 1 &&
(!(value instanceof Object) || Object.keys(value).length < relation.joinColumns.length))
throw new Error(`Value to be set into the relation must be a map of relation ids, for example: .set({ firstName: "...", lastName: "..." })`);
return this.updateRelation(value);
}
/**
@ -52,449 +92,265 @@ export class RelationQueryBuilder<Entity> extends QueryBuilder<Entity> {
* Works only for many-to-many and one-to-many relations.
* For many-to-one and one-to-one use #set method instead.
*/
remove(value: any|any[]): this {
async remove(value: any|any[]): Promise<void> {
const relation = this.relationMetadata;
return this;
if (!this.expressionMap.of) // todo: move this check before relation query builder creation?
throw new Error(`Entity whose relation needs to be set is not set. Use .of method to define whose relation you want to set.`);
if (relation.isManyToOne || relation.isOneToOne)
throw new Error(`Add operation is only supported for many-to-many and one-to-many relations. ` +
`However given "${relation.propertyPath}" has ${relation.relationType} relation. ` +
`Use .set(null) method instead.`);
return this.removeRelation(value);
}
/**
* Adds (binds) and removes (unbinds) given values to/from entity relation.
* Value can be entity, entity id or entity id map (if entity has composite ids).
* Value also can be array of entities, array of entity ids or array of entity id maps (if entity has composite ids).
* Works only for many-to-many and one-to-many relations.
* For many-to-one and one-to-one use #set method instead.
*/
async addAndRemove(added: any|any[], removed: any|any[]): Promise<void> {
await this.remove(removed);
await this.add(added);
}
/**
* Gets entity's relation id.
*/
async getIdOf(): Promise<any> {
async getId(): Promise<any> {
}
/**
* Gets entity's relation ids.
*/
async getIdsOf(): Promise<any[]> {
async getIds(): Promise<any[]> {
return [];
}
}
/**
* Loads a single entity (relational) from the relation.
* You can also provide id of relational entity to filter by.
*/
async loadOne(id?: any): Promise<Entity|undefined> {
return undefined;
}
/**
* Loads many entities (relational) from the relation.
* You can also provide ids of relational entities to filter by.
*/
async loadMany(ids?: any[]): Promise<Entity[]> {
return [];
}
/**
* Repository for more specific operations.
*
* @deprecated Don't use it yet
*
* todo: most of these methods looks like can be part of query builder functionality
* todo: maybe instead of SpecificRepository we should have SpecificQueryBuilder? (better name needed)
* todo: it can be used like createQueryBuilder().specific().setRelation
* todo: or maybe split specific into multiple different purpose QueryBuilders ? For example RelationQueryBuilder
* todo: with methods like createQueryBuilder().relation(Post, "categories").set(value).add(value).remove(value)
* todo: add and remove for many-to-many, set for many-to-one and value can be entity or simply entity id or id map
* todo: also createQueryBuilder().relation(Post, "categories").getIdsOf(postIds)
* todo: also createQueryBuilder().relation(Post, "categories").getCountOf(postIds)
*/
/*export class SpecificRepository<Entity extends ObjectLiteral> {
// -------------------------------------------------------------------------
// Protected Methods
// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
/**
* Gets relation metadata of the relation this query builder works with.
*
* todo: add proper exceptions
*/
protected get relationMetadata(): RelationMetadata {
if (!this.expressionMap.mainAlias)
throw new Error(`Entity to work with is not specified!`); // todo: better message
constructor(protected connection: Connection,
protected metadata: EntityMetadata,
protected queryRunner?: QueryRunner) {
}
const relationMetadata = this.expressionMap.mainAlias.metadata.findRelationWithPropertyPath(this.expressionMap.relationPropertyPath);
if (!relationMetadata)
throw new Error(`Relation ${this.expressionMap.relationPropertyPath} was not found in entity ${this.expressionMap.mainAlias.name}`); // todo: better message
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
*/
/**
* Sets given relatedEntityId to the value of the relation of the entity with entityId id.
* Should be used when you want quickly and efficiently set a relation (for many-to-one and one-to-many) to some entity.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
// async setRelation(relationName: string, entityId: any, relatedEntityId: any): Promise<void>;
return relationMetadata;
}
/**
* Sets given relatedEntityId to the value of the relation of the entity with entityId id.
* Should be used when you want quickly and efficiently set a relation (for many-to-one and one-to-many) to some entity.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
// async setRelation(relationName: ((t: Entity) => string|any), entityId: any, relatedEntityId: any): Promise<void>;
/**
* Performs set or add operation on a relation.
*/
protected async updateRelation(value: any|any[]): Promise<void> {
const relation = this.relationMetadata;
/**
* Sets given relatedEntityId to the value of the relation of the entity with entityId id.
* Should be used when you want quickly and efficiently set a relation (for many-to-one and one-to-many) to some entity.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
if (relation.isManyToOne || relation.isOneToOneOwner) {
async setRelation(relationProperty: string|((t: Entity) => string|any), entityId: any, relatedEntityId: any): Promise<void> {
const propertyPath = this.metadata.computePropertyPath(relationProperty);
const relation = this.metadata.findRelationWithPropertyPath(propertyPath);
if (!relation)
throw new Error(`Relation with property path ${propertyPath} in entity was not found.`);
// if (relation.isManyToMany || relation.isOneToMany || relation.isOneToOneNotOwner)
// throw new Error(`Only many-to-one and one-to-one with join column are supported for this operation. ${this.metadata.name}#${propertyName} relation type is ${relation.relationType}`);
if (relation.isManyToMany)
throw new Error(`Many-to-many relation is not supported for this operation. Use #addToRelation method for many-to-many relations.`);
const updateSet = relation.joinColumns.reduce((updateSet, joinColumn) => {
const relationValue = value instanceof Object ? joinColumn.referencedColumn!.getEntityValue(value) : value;
joinColumn.setEntityValue(updateSet, relationValue);
return updateSet;
}, {} as any);
// todo: fix issues with joinColumns[0]
if (!this.expressionMap.of || (this.expressionMap.of instanceof Array && !this.expressionMap.of.length)) return;
let table: string, values: any = {}, conditions: any = {};
if (relation.isOwning) {
table = relation.entityMetadata.tableName;
values[relation.joinColumns[0].referencedColumn!.databaseName] = relatedEntityId;
conditions[relation.joinColumns[0].referencedColumn!.databaseName] = entityId;
} else {
table = relation.inverseEntityMetadata.tableName;
values[relation.inverseRelation!.joinColumns[0].referencedColumn!.databaseName] = relatedEntityId;
conditions[relation.inverseRelation!.joinColumns[0].referencedColumn!.databaseName] = entityId;
}
await this.createQueryBuilder()
.update(relation.entityMetadata.target)
.set(updateSet)
.whereInIds(this.expressionMap.of)
.execute();
} else if ((relation.isOneToOneNotOwner || relation.isOneToMany) && value === null) { // we handle null a bit different way
const usedQueryRunner = this.queryRunner || this.connection.createQueryRunner();
await usedQueryRunner.update(table, values, conditions);
if (!this.queryRunner) // means created by this method
await usedQueryRunner.release();
}*/
/**
* Sets given relatedEntityId to the value of the relation of the entity with entityId id.
* Should be used when you want quickly and efficiently set a relation (for many-to-one and one-to-many) to some entity.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
// async setInverseRelation(relationName: string, relatedEntityId: any, entityId: any): Promise<void>;
/**
* Sets given relatedEntityId to the value of the relation of the entity with entityId id.
* Should be used when you want quickly and efficiently set a relation (for many-to-one and one-to-many) to some entity.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
// async setInverseRelation(relationName: ((t: Entity) => string|any), relatedEntityId: any, entityId: any): Promise<void>;
/**
* Sets given relatedEntityId to the value of the relation of the entity with entityId id.
* Should be used when you want quickly and efficiently set a relation (for many-to-one and one-to-many) to some entity.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
async setInverseRelation(relationProperty: string|((t: Entity) => string|any), relatedEntityId: any, entityId: any): Promise<void> {
const propertyPath = this.metadata.computePropertyPath(relationProperty);
// todo: fix issues with joinColumns[0]
const relation = this.metadata.findRelationWithPropertyPath(propertyPath);
if (!relation)
throw new Error(`Relation with property path ${propertyPath} in entity was not found.`);
// if (relation.isManyToMany || relation.isOneToMany || relation.isOneToOneNotOwner)
// throw new Error(`Only many-to-one and one-to-one with join column are supported for this operation. ${this.metadata.name}#${propertyName} relation type is ${relation.relationType}`);
if (relation.isManyToMany)
throw new Error(`Many-to-many relation is not supported for this operation. Use #addToRelation method for many-to-many relations.`);
let table: string, values: any = {}, conditions: any = {};
if (relation.isOwning) {
table = relation.inverseEntityMetadata.tableName;
values[relation.inverseRelation!.joinColumns[0].databaseName] = relatedEntityId;
conditions[relation.inverseRelation!.joinColumns[0].referencedColumn!.databaseName] = entityId;
} else {
table = relation.entityMetadata.tableName;
values[relation.joinColumns[0].databaseName] = relatedEntityId;
conditions[relation.joinColumns[0].referencedColumn!.databaseName] = entityId;
}
const usedQueryRunner = this.queryRunner || this.connection.createQueryRunner();
await usedQueryRunner.update(table, values, conditions);
if (!this.queryRunner) // means created by this method
await usedQueryRunner.release();
}*/
/**
* Adds a new relation between two entities into relation's many-to-many table.
* Should be used when you want quickly and efficiently add a relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
// async addToRelation(relationName: string, entityId: any, relatedEntityIds: any[]): Promise<void>;
/**
* Adds a new relation between two entities into relation's many-to-many table.
* Should be used when you want quickly and efficiently add a relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
// async addToRelation(relationName: ((t: Entity) => string|any), entityId: any, relatedEntityIds: any[]): Promise<void>;
/**
* Adds a new relation between two entities into relation's many-to-many table.
* Should be used when you want quickly and efficiently add a relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
async addToRelation(relationProperty: string|((t: Entity) => string|any), entityId: any, relatedEntityIds: any[]): Promise<void> {
const propertyPath = this.metadata.computePropertyPath(relationProperty);
const relation = this.metadata.findRelationWithPropertyPath(propertyPath);
if (!relation)
throw new Error(`Relation with property path ${propertyPath} in entity was not found.`);
if (!relation.isManyToMany)
throw new Error(`Only many-to-many relation supported for this operation. However ${this.metadata.name}#${propertyPath} relation type is ${relation.relationType}`);
const usedQueryRunner = this.queryRunner || this.connection.createQueryRunner();
const insertPromises = relatedEntityIds.map(relatedEntityId => {
const values: any = {};
if (relation.isOwning) {
values[relation.junctionEntityMetadata!.columns[0].databaseName] = entityId;
values[relation.junctionEntityMetadata!.columns[1].databaseName] = relatedEntityId;
} else {
values[relation.junctionEntityMetadata!.columns[1].databaseName] = entityId;
values[relation.junctionEntityMetadata!.columns[0].databaseName] = relatedEntityId;
}
return usedQueryRunner.insert(relation.junctionEntityMetadata!.tableName, values);
});
await Promise.all(insertPromises);
if (!this.queryRunner) // means created by this method
await usedQueryRunner.release();
}*/
/**
* Adds a new relation between two entities into relation's many-to-many table from inverse side of the given relation.
* Should be used when you want quickly and efficiently add a relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
// async addToInverseRelation(relationName: string, relatedEntityId: any, entityIds: any[]): Promise<void>;
/**
* Adds a new relation between two entities into relation's many-to-many table from inverse side of the given relation.
* Should be used when you want quickly and efficiently add a relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
// async addToInverseRelation(relationName: ((t: Entity) => string|any), relatedEntityId: any, entityIds: any[]): Promise<void>;
/**
* Adds a new relation between two entities into relation's many-to-many table from inverse side of the given relation.
* Should be used when you want quickly and efficiently add a relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
async addToInverseRelation(relationProperty: string|((t: Entity) => string|any), relatedEntityId: any, entityIds: any[]): Promise<void> {
const propertyPath = this.metadata.computePropertyPath(relationProperty);
const relation = this.metadata.findRelationWithPropertyPath(propertyPath);
if (!relation)
throw new Error(`Relation with property path ${propertyPath} in entity was not found.`);
if (!relation.isManyToMany)
throw new Error(`Only many-to-many relation supported for this operation. However ${this.metadata.name}#${propertyPath} relation type is ${relation.relationType}`);
const usedQueryRunner = this.queryRunner || this.connection.createQueryRunner();
try {
const insertPromises = entityIds.map(entityId => {
const values: any = {};
if (relation.isOwning) {
values[relation.junctionEntityMetadata!.columns[0].databaseName] = entityId;
values[relation.junctionEntityMetadata!.columns[1].databaseName] = relatedEntityId;
} else {
values[relation.junctionEntityMetadata!.columns[1].databaseName] = entityId;
values[relation.junctionEntityMetadata!.columns[0].databaseName] = relatedEntityId;
}
return usedQueryRunner.insert(relation.junctionEntityMetadata!.tableName, values);
const updateSet: ObjectLiteral = {};
relation.inverseRelation!.joinColumns.forEach(column => {
updateSet[column.propertyName] = null;
});
await Promise.all(insertPromises);
} finally {
if (!this.queryRunner) // means created by this method
await usedQueryRunner.release();
}
}*/
/**
* Removes a relation between two entities from relation's many-to-many table.
* Should be used when you want quickly and efficiently remove a many-to-many relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
// async removeFromRelation(relationName: string, entityId: any, relatedEntityIds: any[]): Promise<void>;
/**
* Removes a relation between two entities from relation's many-to-many table.
* Should be used when you want quickly and efficiently remove a many-to-many relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
// async removeFromRelation(relationName: ((t: Entity) => string|any), entityId: any, relatedEntityIds: any[]): Promise<void>;
/**
* Removes a relation between two entities from relation's many-to-many table.
* Should be used when you want quickly and efficiently remove a many-to-many relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
async removeFromRelation(relationProperty: string|((t: Entity) => string|any), entityId: any, relatedEntityIds: any[]): Promise<void> {
const propertyPath = this.metadata.computePropertyPath(relationProperty);
const relation = this.metadata.findRelationWithPropertyPath(propertyPath);
if (!relation)
throw new Error(`Relation with property path ${propertyPath} in entity was not found.`);
if (!relation.isManyToMany)
throw new Error(`Only many-to-many relation supported for this operation. However ${this.metadata.name}#${propertyPath} relation type is ${relation.relationType}`);
// check if given relation entity ids is empty - then nothing to do here (otherwise next code will remove all ids)
if (!relatedEntityIds || !relatedEntityIds.length)
return Promise.resolve();
const qb = this.connection.manager
.createQueryBuilder(this.queryRunner)
.delete()
.from(relation.junctionEntityMetadata!.tableName, "junctionEntity");
const firstColumnName = this.connection.driver.escapeColumn(relation.isOwning ? relation.junctionEntityMetadata!.columns[0].databaseName : relation.junctionEntityMetadata!.columns[1].databaseName);
const secondColumnName = this.connection.driver.escapeColumn(relation.isOwning ? relation.junctionEntityMetadata!.columns[1].databaseName : relation.junctionEntityMetadata!.columns[0].databaseName);
relatedEntityIds.forEach((relatedEntityId, index) => {
qb.orWhere(`(${firstColumnName}=:entityId AND ${secondColumnName}=:relatedEntity_${index})`)
.setParameter("relatedEntity_" + index, relatedEntityId);
});
await qb
.setParameter("entityId", entityId)
.execute();
}*/
/**
* Removes a relation between two entities from relation's many-to-many table.
* Should be used when you want quickly and efficiently remove a many-to-many relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
// async removeFromInverseRelation(relationName: string, relatedEntityId: any, entityIds: any[]): Promise<void>;
/**
* Removes a relation between two entities from relation's many-to-many table.
* Should be used when you want quickly and efficiently remove a many-to-many relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
// async removeFromInverseRelation(relationName: ((t: Entity) => string|any), relatedEntityId: any, entityIds: any[]): Promise<void>;
/**
* Removes a relation between two entities from relation's many-to-many table.
* Should be used when you want quickly and efficiently remove a many-to-many relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
async removeFromInverseRelation(relationProperty: string|((t: Entity) => string|any), relatedEntityId: any, entityIds: any[]): Promise<void> {
const propertyPath = this.metadata.computePropertyPath(relationProperty);
const relation = this.metadata.findRelationWithPropertyPath(propertyPath);
if (!relation)
throw new Error(`Relation with property path ${propertyPath} in entity was not found.`);
if (!relation.isManyToMany)
throw new Error(`Only many-to-many relation supported for this operation. However ${this.metadata.name}#${propertyPath} relation type is ${relation.relationType}`);
// check if given entity ids is empty - then nothing to do here (otherwise next code will remove all ids)
if (!entityIds || !entityIds.length)
return Promise.resolve();
const qb = this.connection.manager
.createQueryBuilder(this.queryRunner)
.delete()
.from(relation.junctionEntityMetadata!.tableName, "junctionEntity");
const firstColumnName = relation.isOwning ? relation.junctionEntityMetadata!.columns[1].databaseName : relation.junctionEntityMetadata!.columns[0].databaseName;
const secondColumnName = relation.isOwning ? relation.junctionEntityMetadata!.columns[0].databaseName : relation.junctionEntityMetadata!.columns[1].databaseName;
entityIds.forEach((entityId, index) => {
qb.orWhere(`(${firstColumnName}=:relatedEntityId AND ${secondColumnName}=:entity_${index})`)
.setParameter("entity_" + index, entityId);
});
await qb.setParameter("relatedEntityId", relatedEntityId).execute();
}*/
/**
* Performs both #addToRelation and #removeFromRelation operations.
* Should be used when you want quickly and efficiently and and remove a many-to-many relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
// async addAndRemoveFromRelation(relation: string, entityId: any, addRelatedEntityIds: any[], removeRelatedEntityIds: any[]): Promise<void>;
/**
* Performs both #addToRelation and #removeFromRelation operations.
* Should be used when you want quickly and efficiently and and remove a many-to-many relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
// async addAndRemoveFromRelation(relation: ((t: Entity) => string|any), entityId: any, addRelatedEntityIds: any[], removeRelatedEntityIds: any[]): Promise<void>;
/**
* Performs both #addToRelation and #removeFromRelation operations.
* Should be used when you want quickly and efficiently and and remove a many-to-many relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
async addAndRemoveFromRelation(relation: string|((t: Entity) => string|any), entityId: any, addRelatedEntityIds: any[], removeRelatedEntityIds: any[]): Promise<void> {
await Promise.all([
this.addToRelation(relation as any, entityId, addRelatedEntityIds),
this.removeFromRelation(relation as any, entityId, removeRelatedEntityIds)
]);
}*/
/**
* Performs both #addToRelation and #removeFromRelation operations.
* Should be used when you want quickly and efficiently and and remove a many-to-many relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
// async addAndRemoveFromInverseRelation(relation: string, relatedEntityId: any, addEntityIds: any[], removeEntityIds: any[]): Promise<void>;
/**
* Performs both #addToRelation and #removeFromRelation operations.
* Should be used when you want quickly and efficiently and and remove a many-to-many relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
// async addAndRemoveFromInverseRelation(relation: ((t: Entity) => string|any), relatedEntityId: any, addEntityIds: any[], removeEntityIds: any[]): Promise<void>;
/**
* Performs both #addToRelation and #removeFromRelation operations.
* Should be used when you want quickly and efficiently and and remove a many-to-many relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
async addAndRemoveFromInverseRelation(relation: string|((t: Entity) => string|any), relatedEntityId: any, addEntityIds: any[], removeEntityIds: any[]): Promise<void> {
await Promise.all([
this.addToInverseRelation(relation as any, relatedEntityId, addEntityIds),
this.removeFromInverseRelation(relation as any, relatedEntityId, removeEntityIds)
]);
}*/
// -------------------------------------------------------------------------
// Protected Methods
// -------------------------------------------------------------------------
/**
* Converts entity or entities to id or ids map.
protected convertEntityOrEntitiesToIdOrIds(columns: ColumnMetadata[], entityOrEntities: Entity[]|Entity|any|any[]): any|any[] {
if (entityOrEntities instanceof Array) {
return entityOrEntities.map(entity => this.convertEntityOrEntitiesToIdOrIds(columns, entity));
} else {
if (entityOrEntities instanceof Object) {
return columns.reduce((ids, column) => {
ids[column.databaseName] = column.getEntityValue(entityOrEntities);
return ids;
}, {} as ObjectLiteral);
} else {
return entityOrEntities;
}
}
}*/
/**
* Extracts unique objects from given entity and all its downside relations.
protected extractObjectsById(entity: any, metadata: EntityMetadata, entityWithIds: Subject[] = []): Promise<Subject[]> {
const promises = metadata.relations.map(relation => {
const relMetadata = relation.inverseEntityMetadata;
const value = relation.getEntityValue(entity);
if (!value)
return undefined;
if (value instanceof Array) {
const subPromises = value.map((subEntity: any) => {
return this.extractObjectsById(subEntity, relMetadata, entityWithIds);
const ofs = this.expressionMap.of instanceof Array ? this.expressionMap.of : [this.expressionMap.of];
const parameters: ObjectLiteral = {};
const conditions: string[] = [];
ofs.forEach((of, ofIndex) => {
relation.inverseRelation!.joinColumns.map((column, columnIndex) => {
const parameterName = "joinColumn_" + ofIndex + "_" + columnIndex;
parameters[parameterName] = of instanceof Object ? column.referencedColumn!.getEntityValue(of) : of;
conditions.push(`${column.propertyPath} = :${parameterName}`);
});
return Promise.all(subPromises);
});
const condition = conditions.map(str => "(" + str + ")").join(" OR ");
if (!condition) return;
} else {
return this.extractObjectsById(value, relMetadata, entityWithIds);
}
});
await this.createQueryBuilder()
.update(relation.inverseEntityMetadata.target)
.set(updateSet)
.where(condition)
.setParameters(parameters)
.execute();
return Promise.all<any>(promises.filter(result => !!result)).then(() => {
if (!entityWithIds.find(entityWithId => entityWithId.entity === entity)) {
const entityWithId = new Subject(metadata, entity);
entityWithIds.push(entityWithId);
}
} else if (relation.isOneToOneNotOwner || relation.isOneToMany) {
return entityWithIds;
});
} */
if (this.expressionMap.of instanceof Array)
throw new Error(`You cannot update relations of multiple entities with the same related object. Provide a single entity into .of method.`);
// }
const of = this.expressionMap.of;
const updateSet = relation.inverseRelation!.joinColumns.reduce((updateSet, joinColumn) => {
const relationValue = of instanceof Object ? joinColumn.referencedColumn!.getEntityValue(of) : of;
joinColumn.setEntityValue(updateSet, relationValue);
return updateSet;
}, {} as any);
if (!value || (value instanceof Array && !value.length)) return;
await this.createQueryBuilder()
.update(relation.inverseEntityMetadata.target)
.set(updateSet)
.whereInIds(value)
.execute();
} else { // many to many
const junctionMetadata = relation.junctionEntityMetadata!;
const ofs = this.expressionMap.of instanceof Array ? this.expressionMap.of : [this.expressionMap.of];
const values = value instanceof Array ? value : [value];
const firstColumnValues = relation.isManyToManyOwner ? ofs : values;
const secondColumnValues = relation.isManyToManyOwner ? values : ofs;
const bulkInserted: ObjectLiteral[] = [];
firstColumnValues.forEach(firstColumnVal => {
secondColumnValues.forEach(secondColumnVal => {
const inserted: ObjectLiteral = {};
junctionMetadata.ownerColumns.forEach(column => {
inserted[column.databaseName] = firstColumnVal instanceof Object ? column.referencedColumn!.getEntityValue(firstColumnVal) : firstColumnVal;
});
junctionMetadata.inverseColumns.forEach(column => {
inserted[column.databaseName] = secondColumnVal instanceof Object ? column.referencedColumn!.getEntityValue(secondColumnVal) : secondColumnVal;
});
bulkInserted.push(inserted);
});
});
if (!bulkInserted.length) return;
await this.createQueryBuilder()
.insert()
.into(junctionMetadata.tableName)
.values(bulkInserted)
.execute();
}
}
/**
* Performs remove operation on a relation.
*/
protected async removeRelation(value: any|any[]): Promise<void> {
const relation = this.relationMetadata;
if (relation.isOneToMany) {
// if (this.expressionMap.of instanceof Array)
// throw new Error(`You cannot update relations of multiple entities with the same related object. Provide a single entity into .of method.`);
// DELETE FROM post WHERE post.categoryId = of AND post.id = id
const ofs = this.expressionMap.of instanceof Array ? this.expressionMap.of : [this.expressionMap.of];
const values = value instanceof Array ? value : [value];
const updateSet: ObjectLiteral = {};
relation.inverseRelation!.joinColumns.forEach(column => {
updateSet[column.propertyName] = null;
});
const parameters: ObjectLiteral = {};
const conditions: string[] = [];
ofs.forEach((of, ofIndex) => {
conditions.push(...values.map((value, valueIndex) => {
return [
...relation.inverseRelation!.joinColumns.map((column, columnIndex) => {
const parameterName = "joinColumn_" + ofIndex + "_" + valueIndex + "_" + columnIndex;
parameters[parameterName] = of instanceof Object ? column.referencedColumn!.getEntityValue(of) : of;
return `${column.propertyPath} = :${parameterName}`;
}),
...relation.inverseRelation!.entityMetadata.primaryColumns.map((column, columnIndex) => {
const parameterName = "primaryColumn_" + valueIndex + "_" + valueIndex + "_" + columnIndex;
parameters[parameterName] = value instanceof Object ? column.getEntityValue(value) : value;
return `${column.propertyPath} = :${parameterName}`;
})
].join(" AND ");
}));
});
const condition = conditions.map(str => "(" + str + ")").join(" OR ");
if (!condition) return;
await this.createQueryBuilder()
.update(relation.inverseEntityMetadata.target)
.set(updateSet)
.where(condition)
.setParameters(parameters)
.execute();
} else { // many to many
const junctionMetadata = relation.junctionEntityMetadata!;
const ofs = this.expressionMap.of instanceof Array ? this.expressionMap.of : [this.expressionMap.of];
const values = value instanceof Array ? value : [value];
const firstColumnValues = relation.isManyToManyOwner ? ofs : values;
const secondColumnValues = relation.isManyToManyOwner ? values : ofs;
const parameters: ObjectLiteral = {};
const conditions: string[] = [];
firstColumnValues.forEach((firstColumnVal, firstColumnValIndex) => {
conditions.push(...secondColumnValues.map((secondColumnVal, secondColumnValIndex) => {
return [
...junctionMetadata.ownerColumns.map((column, columnIndex) => {
const parameterName = "firstValue_" + firstColumnValIndex + "_" + secondColumnValIndex + "_" + columnIndex;
parameters[parameterName] = firstColumnVal instanceof Object ? column.referencedColumn!.getEntityValue(firstColumnVal) : firstColumnVal;
return `${column.databaseName} = :${parameterName}`;
}),
...junctionMetadata.inverseColumns.map((column, columnIndex) => {
const parameterName = "secondValue_" + firstColumnValIndex + "_" + secondColumnValIndex + "_" + columnIndex;
parameters[parameterName] = firstColumnVal instanceof Object ? column.referencedColumn!.getEntityValue(secondColumnVal) : secondColumnVal;
return `${column.databaseName} = :${parameterName}`;
})
].join(" AND ");
}));
});
const condition = conditions.map(str => "(" + str + ")").join(" OR ");
await this.createQueryBuilder()
.delete()
.from(junctionMetadata.tableName)
.where(condition)
.setParameters(parameters)
.execute();
}
}
}

View File

@ -2,7 +2,6 @@ import {QueryBuilder} from "./QueryBuilder";
import {ObjectLiteral} from "../common/ObjectLiteral";
import {Connection} from "../connection/Connection";
import {QueryRunner} from "../query-runner/QueryRunner";
import {QueryPartialEntity} from "./QueryPartialEntity";
import {SqlServerDriver} from "../driver/sqlserver/SqlServerDriver";
import {PostgresDriver} from "../driver/postgres/PostgresDriver";
import {WhereExpression} from "./WhereExpression";
@ -48,7 +47,7 @@ export class UpdateQueryBuilder<Entity> extends QueryBuilder<Entity> implements
/**
* Values needs to be updated.
*/
set(values: QueryPartialEntity<Entity>): this {
set(values: ObjectLiteral): this {
this.expressionMap.valuesSet = values;
return this;
}
@ -88,7 +87,8 @@ export class UpdateQueryBuilder<Entity> extends QueryBuilder<Entity> implements
/**
* Adds new AND WHERE with conditions for the given ids.
*/
whereInIds(ids: any[]): this {
whereInIds(ids: any|any[]): this {
ids = ids instanceof Array ? ids : [ids];
const [whereExpression, parameters] = this.createWhereIdsExpression(ids);
this.where(whereExpression, parameters);
return this;
@ -97,7 +97,8 @@ export class UpdateQueryBuilder<Entity> extends QueryBuilder<Entity> implements
/**
* Adds new AND WHERE with conditions for the given ids.
*/
andWhereInIds(ids: any[]): this {
andWhereInIds(ids: any|any[]): this {
ids = ids instanceof Array ? ids : [ids];
const [whereExpression, parameters] = this.createWhereIdsExpression(ids);
this.andWhere(whereExpression, parameters);
return this;
@ -106,7 +107,8 @@ export class UpdateQueryBuilder<Entity> extends QueryBuilder<Entity> implements
/**
* Adds new OR WHERE with conditions for the given ids.
*/
orWhereInIds(ids: any[]): this {
orWhereInIds(ids: any|any[]): this {
ids = ids instanceof Array ? ids : [ids];
const [whereExpression, parameters] = this.createWhereIdsExpression(ids);
this.orWhere(whereExpression, parameters);
return this;

View File

@ -223,7 +223,7 @@ export class TableSchema {
columnSchema.comment !== columnMetadata.comment ||
(!columnSchema.isGenerated && !this.compareDefaultValues(driver.normalizeDefault(columnMetadata), columnSchema.default)) || // we included check for generated here, because generated columns already can have default values
columnSchema.isNullable !== columnMetadata.isNullable ||
columnSchema.isUnique !== columnMetadata.isUnique ||
columnSchema.isUnique !== driver.normalizeIsUnique(columnMetadata) ||
// columnSchema.isPrimary !== columnMetadata.isPrimary ||
columnSchema.isGenerated !== columnMetadata.isGenerated;
});

View File

@ -0,0 +1,30 @@
import {Entity} from "../../../../../src/decorator/entity/Entity";
import {Column} from "../../../../../src/decorator/columns/Column";
import {ValueTransformer} from "../../../../../src/decorator/options/ValueTransformer";
import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn";
class TagTransformer implements ValueTransformer {
to (value: string[]): string {
return value.join(", ");
}
from (value: string): string[] {
return value.split(", ");
}
}
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column({ type: String, transformer: new TagTransformer() })
tags: string[];
}

View File

@ -0,0 +1,40 @@
import "reflect-metadata";
import {expect} from "chai";
import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../utils/test-utils";
import {Connection} from "../../../../src/connection/Connection";
import {Post} from "./entity/Post";
describe("columns > value-transformer functionality", () => {
let connections: Connection[];
before(async () => connections = await createTestingConnections({
entities: [Post],
dropSchema: true
}));
beforeEach(() => reloadTestingDatabases(connections));
after(() => closeTestingConnections(connections));
it("should marshal data using the provided value-transformer", () => Promise.all(connections.map(async connection => {
const postRepository = connection.getRepository(Post);
// create and save a post first
const post = new Post();
post.title = "About columns";
post.tags = ["simple", "transformer"];
await postRepository.save(post);
// then update all its properties and save again
post.title = "About columns1";
post.tags = ["very", "simple"];
await postRepository.save(post);
// check if all columns are updated except for readonly columns
const loadedPost = await postRepository.findOneById(1);
expect(loadedPost!.title).to.be.equal("About columns1");
expect(loadedPost!.tags).to.deep.eq(["very", "simple"]);
})));
});

View File

@ -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 { Index } from "../../../../../src/decorator/Index";
@Entity()
export class Person {
@PrimaryGeneratedColumn()
id: number;
@Column()
firstname: string;
@Column()
@Index({ unique: true })
lastname: string;
}

View File

@ -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 { Index } from "../../../../../src/decorator/Index";
@Entity()
@Index("unique_user_twitter_id", (user: User) => [user.twitter_id], { unique: true })
export class User {
@PrimaryGeneratedColumn({ type: "int" })
user_id: number;
@Column()
user_name: string;
@Column({ length: 25 })
twitter_id: string;
}

View File

@ -0,0 +1,29 @@
import "reflect-metadata";
import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../utils/test-utils";
import {Connection} from "../../../../src/connection/Connection";
import {Person} from "./entity/person";
import {User} from "./entity/user";
describe("indices > reading index from entity schema and updating database", () => {
let connections: Connection[];
before(async () => connections = await createTestingConnections({
entities: [Person, User],
schemaCreate: true,
dropSchema: true
}));
beforeEach(() => reloadTestingDatabases(connections));
after(() => closeTestingConnections(connections));
describe("create index", function() {
it("should just work", () => Promise.all(connections.map(async connection => {
await connection.synchronize(false);
})));
});
});

View File

@ -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 {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[];
}

View File

@ -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 {Post} from "./Post";
import {ManyToMany} from "../../../../../../src/decorator/relations/ManyToMany";
@Entity()
export class Image {
@PrimaryGeneratedColumn()
id: number;
@Column()
url: string;
@ManyToMany(type => Post, post => post.images)
posts: Post[];
}

View File

@ -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";
import {ManyToOne} from "../../../../../../src/decorator/relations/ManyToOne";
import {Category} from "./Category";
import {Image} from "./Image";
import {ManyToMany} from "../../../../../../src/decorator/relations/ManyToMany";
import {JoinTable} from "../../../../../../src/decorator/relations/JoinTable";
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@ManyToOne(type => Category, category => category.posts)
category: Category;
@ManyToMany(type => Image, image => image.posts)
@JoinTable()
images: Image[];
}

View File

@ -0,0 +1,227 @@
import "reflect-metadata";
import {Post} from "./entity/Post";
import {Image} from "./entity/Image";
import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../../utils/test-utils";
import {expect} from "chai";
import {Connection} from "../../../../../src/connection/Connection";
describe("query builder > relational query builder > add and remove operations > many to many relation", () => {
let connections: Connection[];
let image1: Image,
image2: Image,
image3: Image,
post1: Post,
post2: Post,
post3: Post,
loadedPost1: Post|undefined,
loadedPost2: Post|undefined,
loadedPost3: Post|undefined;
before(async () => connections = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
dropSchema: true,
}));
beforeEach(() => reloadTestingDatabases(connections));
after(() => closeTestingConnections(connections));
async function prepareData(connection: Connection) {
image1 = new Image();
image1.url = "image #1";
await connection.manager.save(image1);
image2 = new Image();
image2.url = "image #2";
await connection.manager.save(image2);
image3 = new Image();
image3.url = "image #3";
await connection.manager.save(image3);
post1 = new Post();
post1.title = "post #1";
await connection.manager.save(post1);
post2 = new Post();
post2.title = "post #2";
await connection.manager.save(post2);
post3 = new Post();
post3.title = "post #3";
await connection.manager.save(post3);
}
it("should add entity relation of a given entity by entity objects", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
await connection
.createQueryBuilder()
.relation(Image, "posts")
.of(image1)
.add(post1);
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["images"] });
expect(loadedPost1!.images).to.contain({ id: 1, url: "image #1" });
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["images"] });
expect(loadedPost2!.images).to.be.empty;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["images"] });
expect(loadedPost3!.images).to.be.empty;
await connection
.createQueryBuilder()
.relation(Image, "posts")
.of(image1)
.remove(post1);
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["images"] });
expect(loadedPost1!.images).to.not.contain({ id: 1, url: "image #1" });
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["images"] });
expect(loadedPost2!.images).to.be.empty;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["images"] });
expect(loadedPost3!.images).to.be.empty;
})));
it("should add entity relation of a given entity by entity id", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
await connection
.createQueryBuilder()
.relation(Image, "posts")
.of(2) // image id
.add(2); // post id
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["images"] });
expect(loadedPost1!.images).to.be.empty;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["images"] });
expect(loadedPost2!.images).to.contain({ id: 2, url: "image #2" });
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["images"] });
expect(loadedPost3!.images).to.be.empty;
await connection
.createQueryBuilder()
.relation(Image, "posts")
.of(2) // image id
.remove(2); // post id
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["images"] });
expect(loadedPost1!.images).to.be.empty;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["images"] });
expect(loadedPost2!.images).to.not.contain({ id: 2, url: "image #2" });
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["images"] });
expect(loadedPost3!.images).to.be.empty;
})));
it("should add entity relation of a given entity by entity id map", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
await connection
.createQueryBuilder()
.relation(Image, "posts")
.of({ id: 3 }) // image id
.add({ id: 3 }); // post id
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["images"] });
expect(loadedPost1!.images).to.be.empty;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["images"] });
expect(loadedPost2!.images).to.be.empty;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["images"] });
expect(loadedPost3!.images).to.contain({ id: 3, url: "image #3" });
await connection
.createQueryBuilder()
.relation(Image, "posts")
.of({ id: 3 }) // image id
.remove({ id: 3 }); // post id
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["images"] });
expect(loadedPost1!.images).to.be.empty;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["images"] });
expect(loadedPost2!.images).to.be.empty;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["images"] });
expect(loadedPost3!.images).to.not.contain({ id: 3, url: "image #3" });
})));
it("should add entity relation of a multiple entities", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
await connection
.createQueryBuilder()
.relation(Image, "posts")
.of([{ id: 1 }, { id: 3 }]) // images
.add({ id: 3 }); // post
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["images"] });
expect(loadedPost1!.images).to.be.empty;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["images"] });
expect(loadedPost2!.images).to.be.empty;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["images"] });
expect(loadedPost3!.images).to.contain({ id: 1, url: "image #1" });
expect(loadedPost3!.images).to.contain({ id: 3, url: "image #3" });
await connection
.createQueryBuilder()
.relation(Image, "posts")
.of([{ id: 1 }, { id: 3 }]) // images
.remove({ id: 3 }); // post
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["images"] });
expect(loadedPost1!.images).to.be.empty;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["images"] });
expect(loadedPost2!.images).to.be.empty;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["images"] });
expect(loadedPost3!.images).to.not.contain({ id: 1, url: "image #1" });
expect(loadedPost3!.images).to.not.contain({ id: 3, url: "image #3" });
})));
it("should add multiple entities into relation of a multiple entities", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
await connection
.createQueryBuilder()
.relation(Image, "posts")
.of({ id: 3 }) // image
.add([{ id: 1 }, { id: 3 }]); // posts
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["images"] });
expect(loadedPost1!.images).to.contain({ id: 3, url: "image #3" });
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["images"] });
expect(loadedPost2!.images).to.be.empty;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["images"] });
expect(loadedPost3!.images).to.contain({ id: 3, url: "image #3" });
await connection
.createQueryBuilder()
.relation(Image, "posts")
.of({ id: 3 }) // image
.remove([{ id: 1 }, { id: 3 }]); // posts
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["images"] });
expect(loadedPost1!.images).to.not.contain({ id: 3, url: "image #3" });
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["images"] });
expect(loadedPost2!.images).to.be.empty;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["images"] });
expect(loadedPost3!.images).to.not.contain({ id: 3, url: "image #3" });
})));
});

View File

@ -0,0 +1,227 @@
import "reflect-metadata";
import {Post} from "./entity/Post";
import {Image} from "./entity/Image";
import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../../utils/test-utils";
import {expect} from "chai";
import {Connection} from "../../../../../src/connection/Connection";
describe("query builder > relational query builder > add and remove operations > many to many relation", () => {
let connections: Connection[];
let image1: Image,
image2: Image,
image3: Image,
post1: Post,
post2: Post,
post3: Post,
loadedPost1: Post|undefined,
loadedPost2: Post|undefined,
loadedPost3: Post|undefined;
before(async () => connections = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
dropSchema: true,
}));
beforeEach(() => reloadTestingDatabases(connections));
after(() => closeTestingConnections(connections));
async function prepareData(connection: Connection) {
image1 = new Image();
image1.url = "image #1";
await connection.manager.save(image1);
image2 = new Image();
image2.url = "image #2";
await connection.manager.save(image2);
image3 = new Image();
image3.url = "image #3";
await connection.manager.save(image3);
post1 = new Post();
post1.title = "post #1";
await connection.manager.save(post1);
post2 = new Post();
post2.title = "post #2";
await connection.manager.save(post2);
post3 = new Post();
post3.title = "post #3";
await connection.manager.save(post3);
}
it("should add entity relation of a given entity by entity objects", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
await connection
.createQueryBuilder()
.relation(Post, "images")
.of(post1)
.add(image1);
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["images"] });
expect(loadedPost1!.images).to.contain({ id: 1, url: "image #1" });
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["images"] });
expect(loadedPost2!.images).to.be.empty;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["images"] });
expect(loadedPost3!.images).to.be.empty;
await connection
.createQueryBuilder()
.relation(Post, "images")
.of(post1)
.remove(image1);
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["images"] });
expect(loadedPost1!.images).to.not.contain({ id: 1, url: "image #1" });
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["images"] });
expect(loadedPost2!.images).to.be.empty;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["images"] });
expect(loadedPost3!.images).to.be.empty;
})));
it("should add entity relation of a given entity by entity id", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
await connection
.createQueryBuilder()
.relation(Post, "images")
.of(2) // post id
.add(2); // image id
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["images"] });
expect(loadedPost1!.images).to.be.empty;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["images"] });
expect(loadedPost2!.images).to.contain({ id: 2, url: "image #2" });
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["images"] });
expect(loadedPost3!.images).to.be.empty;
await connection
.createQueryBuilder()
.relation(Post, "images")
.of(2) // post id
.remove(2); // image id
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["images"] });
expect(loadedPost1!.images).to.be.empty;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["images"] });
expect(loadedPost2!.images).to.not.contain({ id: 2, url: "image #2" });
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["images"] });
expect(loadedPost3!.images).to.be.empty;
})));
it("should add entity relation of a given entity by entity id map", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
await connection
.createQueryBuilder()
.relation(Post, "images")
.of({ id: 3 }) // post id
.add({ id: 3 }); // image id
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["images"] });
expect(loadedPost1!.images).to.be.empty;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["images"] });
expect(loadedPost2!.images).to.be.empty;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["images"] });
expect(loadedPost3!.images).to.contain({ id: 3, url: "image #3" });
await connection
.createQueryBuilder()
.relation(Post, "images")
.of({ id: 3 }) // post id
.remove({ id: 3 }); // image id
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["images"] });
expect(loadedPost1!.images).to.be.empty;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["images"] });
expect(loadedPost2!.images).to.be.empty;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["images"] });
expect(loadedPost3!.images).to.not.contain({ id: 3, url: "image #3" });
})));
it("should add entity relation of a multiple entities", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
await connection
.createQueryBuilder()
.relation(Post, "images")
.of([{ id: 1 }, { id: 3 }]) // posts
.add({ id: 3 }); // image
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["images"] });
expect(loadedPost1!.images).to.contain({ id: 3, url: "image #3" });
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["images"] });
expect(loadedPost2!.images).to.be.empty;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["images"] });
expect(loadedPost3!.images).to.contain({ id: 3, url: "image #3" });
await connection
.createQueryBuilder()
.relation(Post, "images")
.of([{ id: 1 }, { id: 3 }]) // posts
.remove({ id: 3 }); // image
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["images"] });
expect(loadedPost1!.images).to.not.contain({ id: 3, url: "image #3" });
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["images"] });
expect(loadedPost2!.images).to.be.empty;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["images"] });
expect(loadedPost3!.images).to.not.not.contain({ id: 3, url: "image #3" });
})));
it("should add multiple entities into relation of a multiple entities", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
await connection
.createQueryBuilder()
.relation(Post, "images")
.of({ id: 3 }) // post
.add([{ id: 1 }, { id: 3 }]); // images
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["images"] });
expect(loadedPost1!.images).to.be.empty;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["images"] });
expect(loadedPost2!.images).to.be.empty;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["images"] });
expect(loadedPost3!.images).to.contain({ id: 1, url: "image #1" });
expect(loadedPost3!.images).to.contain({ id: 3, url: "image #3" });
await connection
.createQueryBuilder()
.relation(Post, "images")
.of({ id: 3 }) // post
.remove([{ id: 1 }, { id: 3 }]); // images
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["images"] });
expect(loadedPost1!.images).to.be.empty;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["images"] });
expect(loadedPost2!.images).to.be.empty;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["images"] });
expect(loadedPost3!.images).to.not.contain({ id: 1, url: "image #1" });
expect(loadedPost3!.images).to.not.contain({ id: 3, url: "image #3" });
})));
});

View File

@ -0,0 +1,227 @@
import "reflect-metadata";
import {Post} from "./entity/Post";
import {Category} from "./entity/Category";
import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../../utils/test-utils";
import {expect} from "chai";
import {Connection} from "../../../../../src/connection/Connection";
describe("query builder > relational query builder > add operation > one to many relation", () => {
let connections: Connection[];
let category1: Category,
category2: Category,
category3: Category,
post1: Post,
post2: Post,
post3: Post,
loadedPost1: Post|undefined,
loadedPost2: Post|undefined,
loadedPost3: Post|undefined;
before(async () => connections = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
dropSchema: true,
}));
beforeEach(() => reloadTestingDatabases(connections));
after(() => closeTestingConnections(connections));
async function prepareData(connection: Connection) {
category1 = new Category();
category1.name = "category #1";
await connection.manager.save(category1);
category2 = new Category();
category2.name = "category #2";
await connection.manager.save(category2);
category3 = new Category();
category3.name = "category #3";
await connection.manager.save(category3);
post1 = new Post();
post1.title = "post #1";
await connection.manager.save(post1);
post2 = new Post();
post2.title = "post #2";
await connection.manager.save(post2);
post3 = new Post();
post3.title = "post #3";
await connection.manager.save(post3);
}
it("should add entity relation of a given entity by entity objects", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
await connection
.createQueryBuilder()
.relation(Category, "posts")
.of(category1)
.add(post1);
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["category"] });
expect(loadedPost1!.category).to.be.eql({ id: 1, name: "category #1" });
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["category"] });
expect(loadedPost2!.category).to.be.undefined;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["category"] });
expect(loadedPost3!.category).to.be.undefined;
await connection
.createQueryBuilder()
.relation(Category, "posts")
.of(category1)
.remove(post1);
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["category"] });
expect(loadedPost1!.category).to.be.undefined;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["category"] });
expect(loadedPost2!.category).to.be.undefined;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["category"] });
expect(loadedPost3!.category).to.be.undefined;
})));
it("should add entity relation of a given entity by entity id", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
await connection
.createQueryBuilder()
.relation(Category, "posts")
.of(2) // category id
.add(2); // post id
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["category"] });
expect(loadedPost1!.category).to.be.undefined;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["category"] });
expect(loadedPost2!.category).to.be.eql({ id: 2, name: "category #2" });
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["category"] });
expect(loadedPost3!.category).to.be.undefined;
await connection
.createQueryBuilder()
.relation(Category, "posts")
.of(2) // category id
.remove(2); // post id
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["category"] });
expect(loadedPost1!.category).to.be.undefined;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["category"] });
expect(loadedPost2!.category).to.be.undefined;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["category"] });
expect(loadedPost3!.category).to.be.undefined;
})));
it("should add entity relation of a given entity by entity id map", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
await connection
.createQueryBuilder()
.relation(Category, "posts")
.of({ id: 3 }) // category id
.add({ id: 3 }); // post id
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["category"] });
expect(loadedPost1!.category).to.be.undefined;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["category"] });
expect(loadedPost2!.category).to.be.undefined;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["category"] });
expect(loadedPost3!.category).to.be.eql({ id: 3, name: "category #3" });
await connection
.createQueryBuilder()
.relation(Category, "posts")
.of({ id: 3 }) // category id
.remove({ id: 3 }); // post id
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["category"] });
expect(loadedPost1!.category).to.be.undefined;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["category"] });
expect(loadedPost2!.category).to.be.undefined;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["category"] });
expect(loadedPost3!.category).to.be.undefined;
})));
it("should add multiple entities into relation of a multiple entities", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
await connection
.createQueryBuilder()
.relation(Category, "posts")
.of({ id: 3 }) // category
.add([{ id: 1 }, { id: 3 }]); // posts
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["category"] });
expect(loadedPost1!.category).to.be.eql({ id: 3, name: "category #3" });
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["category"] });
expect(loadedPost2!.category).to.be.undefined;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["category"] });
expect(loadedPost3!.category).to.be.eql({ id: 3, name: "category #3" });
await connection
.createQueryBuilder()
.relation(Category, "posts")
.of({ id: 3 }) // category
.remove([{ id: 1 }, { id: 3 }]); // posts
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["category"] });
expect(loadedPost1!.category).to.be.undefined;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["category"] });
expect(loadedPost2!.category).to.be.undefined;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["category"] });
expect(loadedPost3!.category).to.be.undefined;
})));
it("should handle addAndRemove method as well", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
// add initial data
await connection
.createQueryBuilder()
.relation(Category, "posts")
.of(category3) // category
.add(post2); // post
loadedPost1 = await connection.manager.findOneById(Post, 2, { relations: ["category"] });
expect(loadedPost1!.category).to.be.eql({ id: 3, name: "category #3" });
// when nothing is specified nothing should be performed
await connection
.createQueryBuilder()
.relation(Category, "posts")
.of(category3) // category
.addAndRemove([], []); // post
loadedPost1 = await connection.manager.findOneById(Post, 2, { relations: ["category"] });
expect(loadedPost1!.category).to.be.eql({ id: 3, name: "category #3" });
// now add and remove =)
await connection
.createQueryBuilder()
.relation(Category, "posts")
.of(category3) // category
.addAndRemove([post1, post3], [post2]); // post
const loadedCategory = await connection.manager.findOneById(Category, 3, { relations: ["posts"] });
expect(loadedCategory!.posts).to.contain({ id: 1, title: "post #1" });
expect(loadedCategory!.posts).to.not.contain({ id: 2, title: "post #2" });
expect(loadedCategory!.posts).to.contain({ id: 3, title: "post #3" });
})));
});

View File

@ -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 Category {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
}

View File

@ -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 {OneToOne} from "../../../../../../src/decorator/relations/OneToOne";
import {Post} from "./Post";
@Entity()
export class Image {
@PrimaryGeneratedColumn()
id: number;
@Column()
url: string;
@OneToOne(type => Post, post => post.image)
post: Post;
}

View File

@ -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";
import {ManyToOne} from "../../../../../../src/decorator/relations/ManyToOne";
import {Category} from "./Category";
import {Image} from "./Image";
import {OneToOne} from "../../../../../../src/decorator/relations/OneToOne";
import {JoinColumn} from "../../../../../../src/decorator/relations/JoinColumn";
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@ManyToOne(type => Category)
category: Category;
@OneToOne(type => Image, image => image.post)
@JoinColumn()
image: Image;
}

View File

@ -0,0 +1,191 @@
import "reflect-metadata";
import {Post} from "./entity/Post";
import {Category} from "./entity/Category";
import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../../utils/test-utils";
import {expect} from "chai";
import {Connection} from "../../../../../src/connection/Connection";
describe("query builder > relational query builder > set operation > many to one relation", () => {
let connections: Connection[];
let category1: Category,
category2: Category,
category3: Category,
post1: Post,
post2: Post,
post3: Post,
loadedPost1: Post|undefined,
loadedPost2: Post|undefined,
loadedPost3: Post|undefined;
before(async () => connections = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
dropSchema: true,
}));
beforeEach(() => reloadTestingDatabases(connections));
after(() => closeTestingConnections(connections));
async function prepareData(connection: Connection) {
category1 = new Category();
category1.name = "category #1";
await connection.manager.save(category1);
category2 = new Category();
category2.name = "category #2";
await connection.manager.save(category2);
category3 = new Category();
category3.name = "category #3";
await connection.manager.save(category3);
post1 = new Post();
post1.title = "post #1";
await connection.manager.save(post1);
post2 = new Post();
post2.title = "post #2";
await connection.manager.save(post2);
post3 = new Post();
post3.title = "post #3";
await connection.manager.save(post3);
}
it("should set entity relation of a given entity by entity objects", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
await connection
.createQueryBuilder()
.relation(Post, "category")
.of(post1)
.set(category1);
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["category"] });
expect(loadedPost1!.category).to.be.eql({ id: 1, name: "category #1" });
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["category"] });
expect(loadedPost2!.category).to.be.undefined;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["category"] });
expect(loadedPost3!.category).to.be.undefined;
await connection
.createQueryBuilder()
.relation(Post, "category")
.of(post1)
.set(null);
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["category"] });
expect(loadedPost1!.category).to.be.undefined;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["category"] });
expect(loadedPost2!.category).to.be.undefined;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["category"] });
expect(loadedPost3!.category).to.be.undefined;
})));
it("should set entity relation of a given entity by entity id", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
await connection
.createQueryBuilder()
.relation(Post, "category")
.of(2)
.set(2);
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["category"] });
expect(loadedPost1!.category).to.be.undefined;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["category"] });
expect(loadedPost2!.category).to.be.eql({ id: 2, name: "category #2" });
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["category"] });
expect(loadedPost3!.category).to.be.undefined;
await connection
.createQueryBuilder()
.relation(Post, "category")
.of(2)
.set(null);
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["category"] });
expect(loadedPost1!.category).to.be.undefined;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["category"] });
expect(loadedPost2!.category).to.be.undefined;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["category"] });
expect(loadedPost3!.category).to.be.undefined;
})));
it("should set entity relation of a given entity by entity id map", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
await connection
.createQueryBuilder()
.relation(Post, "category")
.of({ id: 3 })
.set({ id: 3 });
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["category"] });
expect(loadedPost1!.category).to.be.undefined;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["category"] });
expect(loadedPost2!.category).to.be.undefined;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["category"] });
expect(loadedPost3!.category).to.be.eql({ id: 3, name: "category #3" });
await connection
.createQueryBuilder()
.relation(Post, "category")
.of({ id: 3 })
.set(null);
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["category"] });
expect(loadedPost1!.category).to.be.undefined;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["category"] });
expect(loadedPost2!.category).to.be.undefined;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["category"] });
expect(loadedPost3!.category).to.be.undefined;
})));
it("should set entity relation of a multiple entities", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
await connection
.createQueryBuilder()
.relation(Post, "category")
.of([{ id: 1 }, { id: 3 }])
.set({ id: 3 });
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["category"] });
expect(loadedPost1!.category).to.be.eql({ id: 3, name: "category #3" });
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["category"] });
expect(loadedPost2!.category).to.be.undefined;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["category"] });
expect(loadedPost3!.category).to.be.eql({ id: 3, name: "category #3" });
await connection
.createQueryBuilder()
.relation(Post, "category")
.of([{ id: 1 }, { id: 3 }])
.set(null);
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["category"] });
expect(loadedPost1!.category).to.be.undefined;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["category"] });
expect(loadedPost2!.category).to.be.undefined;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["category"] });
expect(loadedPost3!.category).to.be.undefined;
})));
});

View File

@ -0,0 +1,191 @@
import "reflect-metadata";
import {Post} from "./entity/Post";
import {Image} from "./entity/Image";
import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../../utils/test-utils";
import {expect} from "chai";
import {Connection} from "../../../../../src/connection/Connection";
describe("query builder > relational query builder > set operation > one-to-one non owner side", () => {
let connections: Connection[];
let image1: Image,
image2: Image,
image3: Image,
post1: Post,
post2: Post,
post3: Post,
loadedPost1: Post|undefined,
loadedPost2: Post|undefined,
loadedPost3: Post|undefined;
before(async () => connections = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
dropSchema: true,
}));
beforeEach(() => reloadTestingDatabases(connections));
after(() => closeTestingConnections(connections));
async function prepareData(connection: Connection) {
image1 = new Image();
image1.url = "image #1";
await connection.manager.save(image1);
image2 = new Image();
image2.url = "image #2";
await connection.manager.save(image2);
image3 = new Image();
image3.url = "image #3";
await connection.manager.save(image3);
post1 = new Post();
post1.title = "post #1";
await connection.manager.save(post1);
post2 = new Post();
post2.title = "post #2";
await connection.manager.save(post2);
post3 = new Post();
post3.title = "post #3";
await connection.manager.save(post3);
}
it("should set entity relation of a given entity by entity objects", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
await connection
.createQueryBuilder()
.relation(Image, "post")
.of(image1)
.set(post1);
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["image"] });
expect(loadedPost1!.image).to.be.eql({ id: 1, url: "image #1" });
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["image"] });
expect(loadedPost2!.image).to.be.undefined;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["image"] });
expect(loadedPost3!.image).to.be.undefined;
await connection
.createQueryBuilder()
.relation(Image, "post")
.of(image1)
.set(null);
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["image"] });
expect(loadedPost1!.image).to.be.undefined;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["image"] });
expect(loadedPost2!.image).to.be.undefined;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["image"] });
expect(loadedPost3!.image).to.be.undefined;
})));
it("should set entity relation of a given entity by entity id", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
await connection
.createQueryBuilder()
.relation(Image, "post")
.of(2)
.set(2);
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["image"] });
expect(loadedPost1!.image).to.be.undefined;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["image"] });
expect(loadedPost2!.image).to.be.eql({ id: 2, url: "image #2" });
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["image"] });
expect(loadedPost3!.image).to.be.undefined;
await connection
.createQueryBuilder()
.relation(Image, "post")
.of(2)
.set(null);
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["image"] });
expect(loadedPost1!.image).to.be.undefined;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["image"] });
expect(loadedPost2!.image).to.be.undefined;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["image"] });
expect(loadedPost3!.image).to.be.undefined;
})));
it("should set entity relation of a given entity by entity id map", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
await connection
.createQueryBuilder()
.relation(Image, "post")
.of({ id: 3 })
.set({ id: 3 });
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["image"] });
expect(loadedPost1!.image).to.be.undefined;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["image"] });
expect(loadedPost2!.image).to.be.undefined;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["image"] });
expect(loadedPost3!.image).to.be.eql({ id: 3, url: "image #3" });
await connection
.createQueryBuilder()
.relation(Image, "post")
.of({ id: 3 })
.set(null);
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["image"] });
expect(loadedPost1!.image).to.be.undefined;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["image"] });
expect(loadedPost2!.image).to.be.undefined;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["image"] });
expect(loadedPost3!.image).to.be.undefined;
})));
it("should set entity relation of a multiple entities", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
await connection
.createQueryBuilder()
.relation(Image, "post")
.of({ id: 3 })
.set([{ id: 1 }, { id: 3 }]);
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["image"] });
expect(loadedPost1!.image).to.be.eql({ id: 3, url: "image #3" });
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["image"] });
expect(loadedPost2!.image).to.be.undefined;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["image"] });
expect(loadedPost3!.image).to.be.eql({ id: 3, url: "image #3" });
await connection
.createQueryBuilder()
.relation(Image, "post")
.of({ id: 3 })
.set(null);
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["image"] });
expect(loadedPost1!.image).to.be.undefined;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["image"] });
expect(loadedPost2!.image).to.be.undefined;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["image"] });
expect(loadedPost3!.image).to.be.undefined;
})));
});

View File

@ -0,0 +1,191 @@
import "reflect-metadata";
import {Post} from "./entity/Post";
import {Image} from "./entity/Image";
import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../../utils/test-utils";
import {expect} from "chai";
import {Connection} from "../../../../../src/connection/Connection";
describe("query builder > relational query builder > set operation > one-to-one relation", () => {
let connections: Connection[];
let image1: Image,
image2: Image,
image3: Image,
post1: Post,
post2: Post,
post3: Post,
loadedPost1: Post|undefined,
loadedPost2: Post|undefined,
loadedPost3: Post|undefined;
before(async () => connections = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
dropSchema: true,
}));
beforeEach(() => reloadTestingDatabases(connections));
after(() => closeTestingConnections(connections));
async function prepareData(connection: Connection) {
image1 = new Image();
image1.url = "image #1";
await connection.manager.save(image1);
image2 = new Image();
image2.url = "image #2";
await connection.manager.save(image2);
image3 = new Image();
image3.url = "image #3";
await connection.manager.save(image3);
post1 = new Post();
post1.title = "post #1";
await connection.manager.save(post1);
post2 = new Post();
post2.title = "post #2";
await connection.manager.save(post2);
post3 = new Post();
post3.title = "post #3";
await connection.manager.save(post3);
}
it("should set entity relation of a given entity by entity objects", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
await connection
.createQueryBuilder()
.relation(Post, "image")
.of(post1)
.set(image1);
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["image"] });
expect(loadedPost1!.image).to.be.eql({ id: 1, url: "image #1" });
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["image"] });
expect(loadedPost2!.image).to.be.undefined;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["image"] });
expect(loadedPost3!.image).to.be.undefined;
await connection
.createQueryBuilder()
.relation(Post, "image")
.of(post1)
.set(null);
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["image"] });
expect(loadedPost1!.image).to.be.undefined;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["image"] });
expect(loadedPost2!.image).to.be.undefined;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["image"] });
expect(loadedPost3!.image).to.be.undefined;
})));
it("should set entity relation of a given entity by entity id", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
await connection
.createQueryBuilder()
.relation(Post, "image")
.of(2)
.set(2);
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["image"] });
expect(loadedPost1!.image).to.be.undefined;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["image"] });
expect(loadedPost2!.image).to.be.eql({ id: 2, url: "image #2" });
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["image"] });
expect(loadedPost3!.image).to.be.undefined;
await connection
.createQueryBuilder()
.relation(Post, "image")
.of(2)
.set(null);
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["image"] });
expect(loadedPost1!.image).to.be.undefined;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["image"] });
expect(loadedPost2!.image).to.be.undefined;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["image"] });
expect(loadedPost3!.image).to.be.undefined;
})));
it("should set entity relation of a given entity by entity id map", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
await connection
.createQueryBuilder()
.relation(Post, "image")
.of({ id: 3 })
.set({ id: 3 });
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["image"] });
expect(loadedPost1!.image).to.be.undefined;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["image"] });
expect(loadedPost2!.image).to.be.undefined;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["image"] });
expect(loadedPost3!.image).to.be.eql({ id: 3, url: "image #3" });
await connection
.createQueryBuilder()
.relation(Post, "image")
.of({ id: 3 })
.set(null);
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["image"] });
expect(loadedPost1!.image).to.be.undefined;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["image"] });
expect(loadedPost2!.image).to.be.undefined;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["image"] });
expect(loadedPost3!.image).to.be.undefined;
})));
it("should set entity relation of a multiple entities", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
await connection
.createQueryBuilder()
.relation(Post, "image")
.of([{ id: 1 }, { id: 3 }])
.set({ id: 3 });
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["image"] });
expect(loadedPost1!.image).to.be.eql({ id: 3, url: "image #3" });
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["image"] });
expect(loadedPost2!.image).to.be.undefined;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["image"] });
expect(loadedPost3!.image).to.be.eql({ id: 3, url: "image #3" });
await connection
.createQueryBuilder()
.relation(Post, "image")
.of([{ id: 1 }, { id: 3 }])
.set(null);
loadedPost1 = await connection.manager.findOneById(Post, 1, { relations: ["image"] });
expect(loadedPost1!.image).to.be.undefined;
loadedPost2 = await connection.manager.findOneById(Post, 2, { relations: ["image"] });
expect(loadedPost2!.image).to.be.undefined;
loadedPost3 = await connection.manager.findOneById(Post, 3, { relations: ["image"] });
expect(loadedPost3!.image).to.be.undefined;
})));
});