mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
Merge remote-tracking branch 'typeorm/master' into feature/extend-lazy-relation-testing
This commit is contained in:
commit
6fa4cb9021
@ -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
|
||||
|
||||
|
||||
@ -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`.
|
||||
|
||||
|
||||
@ -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
6
docs/internals.md
Normal file
@ -0,0 +1,6 @@
|
||||
# TypeORM internals
|
||||
|
||||
Explaining how things are working in TypeORM.
|
||||
This guide will be useful for contributors.
|
||||
|
||||
TBD.
|
||||
19
docs/usage-in-the-browser-and-cordova.md
Normal file
19
docs/usage-in-the-browser-and-cordova.md
Normal 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).
|
||||
@ -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"));
|
||||
|
||||
@ -36,7 +36,7 @@
|
||||
"database": "test"
|
||||
},
|
||||
{
|
||||
"skip": false,
|
||||
"skip": true,
|
||||
"name": "mssql",
|
||||
"type": "mssql",
|
||||
"host": "localhost",
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
}
|
||||
|
||||
18
src/decorator/options/ValueTransformer.ts
Normal file
18
src/decorator/options/ValueTransformer.ts
Normal 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;
|
||||
|
||||
}
|
||||
@ -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.
|
||||
*/
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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.
|
||||
*/
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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.
|
||||
*/
|
||||
|
||||
@ -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.
|
||||
*/
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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.
|
||||
*/
|
||||
|
||||
@ -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.
|
||||
*/
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
});
|
||||
|
||||
30
test/functional/columns/value-transformer/entity/Post.ts
Normal file
30
test/functional/columns/value-transformer/entity/Post.ts
Normal 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[];
|
||||
|
||||
}
|
||||
@ -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"]);
|
||||
|
||||
})));
|
||||
|
||||
|
||||
});
|
||||
@ -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;
|
||||
|
||||
}
|
||||
19
test/functional/database-schema/unique-index/entity/user.ts
Normal file
19
test/functional/database-schema/unique-index/entity/user.ts
Normal 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;
|
||||
|
||||
}
|
||||
@ -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);
|
||||
|
||||
})));
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@ -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[];
|
||||
|
||||
}
|
||||
@ -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[];
|
||||
|
||||
}
|
||||
@ -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[];
|
||||
|
||||
}
|
||||
@ -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" });
|
||||
})));
|
||||
|
||||
});
|
||||
@ -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" });
|
||||
})));
|
||||
|
||||
});
|
||||
@ -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" });
|
||||
})));
|
||||
|
||||
});
|
||||
@ -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;
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
}
|
||||
@ -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;
|
||||
})));
|
||||
|
||||
});
|
||||
@ -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;
|
||||
})));
|
||||
|
||||
});
|
||||
@ -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;
|
||||
})));
|
||||
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user