Merge branch 'master' into fix-oracle-limit-offset

This commit is contained in:
hajekj14 2017-06-08 17:04:40 +02:00 committed by GitHub
commit 11c281583d
69 changed files with 1182 additions and 1605 deletions

View File

@ -25,7 +25,16 @@ each for its own `findOne*` or `find*` methods
* `transaction` method has been removed from `Repository`. Use `EntityManager#transaction` method instead
* custom repositories do not support container anymore
* added ActiveRecord support (by extending EntityModel) class
* controller / subscriber / migrations from options tsconfig now appended with a project root directory
* removed naming strategy decorator, naming strategy by name functionality.
Now naming strategy should be registered by passing naming strategy instance directly
* `driver` section in connection options now deprecated. All settings should go directly to connection options root.
* removed `fromTable` from the `QueryBuilder`. Now use regular `from` to select from tables
### OTHER API CHANGES
* moved `query`, `transaction` and `createQueryBuilder` to the `Connection`.
`EntityManager` now simply use them from the connection.
### NEW FEATURES

View File

@ -89,7 +89,7 @@ export class Photo {
id: number;
name: string;
description: string;
fileName: string;
filename: string;
views: number;
}
```
@ -106,7 +106,7 @@ export class Photo {
id: number;
name: string;
description: string;
fileName: string;
filename: string;
views: number;
isPublished: boolean;
}
@ -134,7 +134,7 @@ export class Photo {
description: string;
@Column()
fileName: string;
filename: string;
@Column()
views: number;
@ -165,7 +165,7 @@ export class Photo {
description: string;
@Column()
fileName: string;
filename: string;
@Column()
views: number;
@ -195,7 +195,7 @@ export class Photo {
description: string;
@Column()
fileName: string;
filename: string;
@Column()
views: number;
@ -225,7 +225,7 @@ export class Photo {
description: string;
@Column()
fileName: string;
filename: string;
@Column()
views: number;
@ -258,7 +258,7 @@ export class Photo {
description: string;
@Column()
fileName: string;
filename: string;
@Column("int")
views: number;

View File

@ -30,7 +30,8 @@ Share this library with friends on Twitter and everywhere else you can.
If you notice bug or have something not working please report an issue, we'll try to fix it as soon as possible.
More documentation and features expected to be soon. Feel free to contribute.
> For the latest release changes see [changelog](./CHANGELOG.md).
> Important note: if you want latest stable version install `npm i typeorm@0.0.11`. You can find 0.0.11 version [README here](https://github.com/typeorm/typeorm/tree/0.0.x-version).
> If you want the latest development version simply install `npm i typeorm`. For the latest development release changes see [changelog](./CHANGELOG.md).
TypeORM is an [Object Relational Mapper](1) (ORM) for Node.js written in
TypeScript that can be used with TypeScript or JavaScript (ES5, ES6, ES7).
@ -62,6 +63,10 @@ TypeORM is highly influenced by other ORMs, such as [Hibernate](http://hibernate
1. Install module:
`npm install typeorm --save`
Important note: if you want latest stable version install `npm i typeorm@0.0.11`
If you want the latest development version simply install `npm i typeorm`. For the latest development release changes see [changelog](./CHANGELOG.md).
2. You need to install `reflect-metadata` shim:
@ -137,7 +142,7 @@ export class Photo {
id: number;
name: string;
description: string;
fileName: string;
filename: string;
views: number;
}
````
@ -154,7 +159,7 @@ export class Photo {
id: number;
name: string;
description: string;
fileName: string;
filename: string;
views: number;
isPublished: boolean;
}
@ -182,7 +187,7 @@ export class Photo {
description: string;
@Column()
fileName: string;
filename: string;
@Column()
views: number;
@ -217,7 +222,7 @@ export class Photo {
description: string;
@Column()
fileName: string;
filename: string;
@Column()
views: number;
@ -248,7 +253,7 @@ export class Photo {
description: string;
@Column()
fileName: string;
filename: string;
@Column()
views: number;
@ -281,7 +286,7 @@ export class Photo {
description: string;
@Column()
fileName: string;
filename: string;
@Column()
views: number;
@ -316,7 +321,7 @@ export class Photo {
description: string;
@Column()
fileName: string;
filename: string;
@Column("int")
views: number;

View File

@ -1,7 +1,7 @@
{
"name": "typeorm",
"private": true,
"version": "0.1.0-alpha.3",
"version": "0.1.0-alpha.4",
"description": "Data-Mapper ORM for TypeScript, ES7, ES6, ES5. Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, WebSQL, MongoDB databases.",
"license": "MIT",
"readmeFilename": "README.md",
@ -75,10 +75,13 @@
},
"dependencies": {
"app-root-path": "^2.0.1",
"dotenv": "^4.0.0",
"glob": "^7.1.1",
"js-yaml": "^3.8.4",
"reflect-metadata": "^0.1.10",
"yargonaut": "^1.1.2",
"yargs": "^8.0.1"
"yargs": "^8.0.1",
"xml2js": "^0.4.17"
},
"scripts": {
"test": "node_modules/.bin/gulp tests"

View File

@ -13,9 +13,8 @@ const options: ConnectionOptions = {
database: "test"
},
autoSchemaSync: true,
usedNamingStrategy: "custom_strategy",
entities: [Post],
namingStrategies: [CustomNamingStrategy]
namingStrategy: new CustomNamingStrategy(),
entities: [Post]
};
createConnection(options).then(connection => {

View File

@ -1,9 +1,7 @@
import {NamingStrategyInterface} from "../../../src/naming-strategy/NamingStrategyInterface";
import {NamingStrategy} from "../../../src/decorator/NamingStrategy";
import {DefaultNamingStrategy} from "../../../src/naming-strategy/DefaultNamingStrategy";
import {snakeCase} from "../../../src/util/StringUtils";
@NamingStrategy("custom_strategy")
export class CustomNamingStrategy extends DefaultNamingStrategy implements NamingStrategyInterface {
tableName(targetName: string, userSpecifiedName: string): string {

View File

@ -4,24 +4,14 @@ import {EntitySubscriberInterface} from "../subscriber/EntitySubscriberInterface
import {RepositoryNotFoundError} from "./error/RepositoryNotFoundError";
import {ObjectType} from "../common/ObjectType";
import {EntityManager} from "../entity-manager/EntityManager";
import {importClassesFromDirectories, importJsonsFromDirectories} from "../util/DirectoryExportedClassesLoader";
import {getFromContainer, getMetadataArgsStorage} from "../index";
import {EntityMetadataBuilder} from "../metadata-builder/EntityMetadataBuilder";
import {DefaultNamingStrategy} from "../naming-strategy/DefaultNamingStrategy";
import {CannotImportAlreadyConnectedError} from "./error/CannotImportAlreadyConnectedError";
import {CannotCloseNotConnectedError} from "./error/CannotCloseNotConnectedError";
import {CannotConnectAlreadyConnectedError} from "./error/CannotConnectAlreadyConnectedError";
import {TreeRepository} from "../repository/TreeRepository";
import {NamingStrategyInterface} from "../naming-strategy/NamingStrategyInterface";
import {NamingStrategyNotFoundError} from "./error/NamingStrategyNotFoundError";
import {RepositoryNotTreeError} from "./error/RepositoryNotTreeError";
import {EntitySchema} from "../entity-schema/EntitySchema";
import {CannotSyncNotConnectedError} from "./error/CannotSyncNotConnectedError";
import {CannotUseNamingStrategyNotConnectedError} from "./error/CannotUseNamingStrategyNotConnectedError";
import {Broadcaster} from "../subscriber/Broadcaster";
import {LazyRelationsWrapper} from "../lazy-loading/LazyRelationsWrapper";
import {SpecificRepository} from "../repository/SpecificRepository";
import {RepositoryAggregator} from "../repository/RepositoryAggregator";
import {EntityMetadata} from "../metadata/EntityMetadata";
import {SchemaBuilder} from "../schema-builder/SchemaBuilder";
import {Logger} from "../logger/Logger";
@ -31,18 +21,22 @@ import {MigrationInterface} from "../migration/MigrationInterface";
import {MigrationExecutor} from "../migration/MigrationExecutor";
import {CannotRunMigrationNotConnectedError} from "./error/CannotRunMigrationNotConnectedError";
import {PlatformTools} from "../platform/PlatformTools";
import {AbstractRepository} from "../repository/AbstractRepository";
import {CustomRepositoryNotFoundError} from "../repository/error/CustomRepositoryNotFoundError";
import {CustomRepositoryReusedError} from "../repository/error/CustomRepositoryReusedError";
import {CustomRepositoryCannotInheritRepositoryError} from "../repository/error/CustomRepositoryCannotInheritRepositoryError";
import {MongoRepository} from "../repository/MongoRepository";
import {MongoDriver} from "../driver/mongodb/MongoDriver";
import {MongoEntityManager} from "../entity-manager/MongoEntityManager";
import {EntitySchemaTransformer} from "../entity-schema/EntitySchemaTransformer";
import {EntityMetadataValidator} from "../metadata-builder/EntityMetadataValidator";
import {ConnectionOptions} from "./ConnectionOptions";
import {QueryRunnerProviderAlreadyReleasedError} from "../query-runner/error/QueryRunnerProviderAlreadyReleasedError";
import {QueryBuilder} from "../query-builder/QueryBuilder";
import {EntityManagerFactory} from "../entity-manager/EntityManagerFactory";
import {LoggerFactory} from "../logger/LoggerFactory";
import {RepositoryFactory} from "../repository/RepositoryFactory";
import {DriverFactory} from "../driver/DriverFactory";
import {ConnectionMetadataBuilder} from "./ConnectionMetadataBuilder";
/**
* Connection is a single database connection to a specific database of a database management system.
* Connection is a single database ORM connection to a specific DBMS database.
* Its not required to be a database connection, depend on database type it can create connection pool.
* You can have multiple connections to multiple databases in your application.
*/
export class Connection {
@ -54,122 +48,75 @@ export class Connection {
/**
* Connection name.
*/
public readonly name: string;
readonly name: string;
/**
* Connection options.
*/
readonly options: ConnectionOptions;
/**
* Indicates if connection is initialized or not.
*/
readonly isConnected = false;
/**
* Database driver used by this connection.
*/
public readonly driver: Driver;
readonly driver: Driver;
/**
* Logger used to log orm events.
*/
public readonly logger: Logger;
/**
* All entity metadatas that are registered for this connection.
*/
public readonly entityMetadatas: EntityMetadata[] = [];
/**
* Used to broadcast connection events.
*/
public readonly broadcaster: Broadcaster;
// -------------------------------------------------------------------------
// Private Properties
// -------------------------------------------------------------------------
/**
* Gets EntityManager of this connection.
* EntityManager of this connection.
*/
readonly manager: EntityManager;
/**
* Stores all registered repositories.
* Naming strategy used in the connection.
*/
private readonly repositoryAggregators: RepositoryAggregator[] = [];
readonly namingStrategy: NamingStrategyInterface;
/**
* Stores all entity repository instances.
* Logger used to log orm events.
*/
private readonly entityRepositories: Object[] = [];
readonly logger: Logger;
/**
* Entity subscribers that are registered for this connection.
* Migration instances that are registered for this connection.
*/
private readonly entitySubscribers: EntitySubscriberInterface<any>[] = [];
readonly migrations: MigrationInterface[] = [];
/**
* Registered entity classes to be used for this connection.
* Entity subscriber instances that are registered for this connection.
*/
private readonly entityClasses: Function[] = [];
readonly subscribers: EntitySubscriberInterface<any>[] = [];
/**
* Registered entity schemas to be used for this connection.
* All entity metadatas that are registered for this connection.
*/
private readonly entitySchemas: EntitySchema[] = [];
/**
* Registered subscriber classes to be used for this connection.
*/
private readonly subscriberClasses: Function[] = [];
/**
* Registered naming strategy classes to be used for this connection.
*/
private readonly namingStrategyClasses: Function[] = [];
/**
* Registered migration classes to be used for this connection.
*/
private readonly migrationClasses: Function[] = [];
/**
* Naming strategy to be used in this connection.
*/
private usedNamingStrategy: Function|string;
/**
* Indicates if connection has been done or not.
*/
private _isConnected = false;
readonly entityMetadatas: EntityMetadata[] = [];
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
constructor(name: string, driver: Driver, logger: Logger) {
this.name = name;
this.driver = driver;
this.logger = logger;
this.manager = this.createEntityManager();
this.broadcaster = this.createBroadcaster();
constructor(options: ConnectionOptions) {
this.name = options.name || "default";
this.options = options;
this.logger = new LoggerFactory().create(this.options.logging || {});
this.driver = new DriverFactory().create(this);
this.manager = new EntityManagerFactory().create(this);
this.namingStrategy = options.namingStrategy || new DefaultNamingStrategy();
}
// -------------------------------------------------------------------------
// Accessors
// Public Accessors
// -------------------------------------------------------------------------
/**
* Indicates if connection to the database already established for this connection.
*/
get isConnected(): boolean {
return this._isConnected;
}
/**
* Gets entity manager that allows to perform repository operations with any entity in this connection.
*
* @deprecated use manager instead.
*/
get entityManager(): EntityManager {
return this.manager;
}
/**
* Gets the mongodb entity manager that allows to perform mongodb-specific repository operations
* with any entity in this connection.
*
* Available only in mongodb connections.
*/
get mongoEntityManager(): MongoEntityManager {
if (!(this.manager instanceof MongoEntityManager))
@ -184,6 +131,9 @@ export class Connection {
/**
* Performs connection to the database.
* This method should be called once on application bootstrap.
* This method not necessarily creates database connection (depend on database type),
* but it also can setup a connection pool with database to use.
*/
async connect(): Promise<this> {
if (this.isConnected)
@ -193,7 +143,7 @@ export class Connection {
await this.driver.connect();
// set connected status for the current connection
this._isConnected = true;
Object.assign(this, { isConnected: true });
// build all metadatas registered in the current connection
try {
@ -212,34 +162,26 @@ export class Connection {
/**
* Closes connection with the database.
* Once connection is closed, you cannot use repositories and perform any operations except
* opening connection again.
* Once connection is closed, you cannot use repositories or perform any operations except opening connection again.
*/
async close(): Promise<void> {
if (!this.isConnected)
throw new CannotCloseNotConnectedError(this.name);
await this.driver.disconnect();
this._isConnected = false;
}
/**
* Drops the database and all its data.
*/
async dropDatabase(): Promise<void> {
const queryRunner = await this.driver.createQueryRunner();
await queryRunner.clearDatabase();
Object.assign(this, { isConnected: false });
}
/**
* Creates database schema for all entities registered in this connection.
* Can be used only after connection to the database is established.
*
* @param dropBeforeSync If set to true then it drops the database with all its tables and data
*/
async syncSchema(dropBeforeSync: boolean = false): Promise<void> {
if (!this.isConnected)
return Promise.reject(new CannotSyncNotConnectedError(this.name));
throw new CannotCloseNotConnectedError(this.name);
if (dropBeforeSync)
await this.dropDatabase();
@ -248,17 +190,29 @@ export class Connection {
await this.driver.syncSchema(this.entityMetadatas);
} else {
await this.createSchemaBuilder().build();
const schemaBuilder = new SchemaBuilder(this.driver, this.logger, this.entityMetadatas);
await schemaBuilder.build();
}
}
/**
* Drops the database and all its data.
* Be careful with this method on production since this method will erase all your database tables and data inside them.
* Can be used only after connection to the database is established.
*/
async dropDatabase(): Promise<void> {
const queryRunner = await this.driver.createQueryRunner();
await queryRunner.clearDatabase();
}
/**
* Runs all pending migrations.
* Can be used only after connection to the database is established.
*/
async runMigrations(): Promise<void> {
if (!this.isConnected)
return Promise.reject(new CannotRunMigrationNotConnectedError(this.name));
throw new CannotCloseNotConnectedError(this.name);
const migrationExecutor = new MigrationExecutor(this);
await migrationExecutor.executePendingMigrations();
@ -266,155 +220,29 @@ export class Connection {
/**
* Reverts last executed migration.
* Can be used only after connection to the database is established.
*/
async undoLastMigration(): Promise<void> {
if (!this.isConnected)
return Promise.reject(new CannotRunMigrationNotConnectedError(this.name));
throw new CannotCloseNotConnectedError(this.name);
const migrationExecutor = new MigrationExecutor(this);
await migrationExecutor.undoLastMigration();
}
/**
* Imports entities from the given paths (directories) and registers them in the current connection.
* Checks if entity metadata exist for the given entity class, target name or table name.
*/
importEntitiesFromDirectories(paths: string[]): this {
this.importEntities(importClassesFromDirectories(paths));
return this;
hasMetadata(target: Function|string): boolean {
return !!this.findMetadata(target);
}
/**
* Imports entity schemas from the given paths (directories) and registers them in the current connection.
*/
importEntitySchemaFromDirectories(paths: string[]): this {
this.importEntitySchemas(importJsonsFromDirectories(paths));
return this;
}
/**
* Imports subscribers from the given paths (directories) and registers them in the current connection.
*/
importSubscribersFromDirectories(paths: string[]): this {
this.importSubscribers(importClassesFromDirectories(paths));
return this;
}
/**
* Imports naming strategies from the given paths (directories) and registers them in the current connection.
*/
importNamingStrategiesFromDirectories(paths: string[]): this {
this.importNamingStrategies(importClassesFromDirectories(paths));
return this;
}
/**
* Imports migrations from the given paths (directories) and registers them in the current connection.
*/
importMigrationsFromDirectories(paths: string[]): this {
this.importMigrations(importClassesFromDirectories(paths));
return this;
}
/**
* Imports entities and registers them in the current connection.
*/
importEntities(entities: Function[]): this {
if (this.isConnected)
throw new CannotImportAlreadyConnectedError("entities", this.name);
entities.forEach(cls => this.entityClasses.push(cls));
return this;
}
/**
* Imports schemas and registers them in the current connection.
*/
importEntitySchemas(schemas: EntitySchema[]): this {
if (this.isConnected)
throw new CannotImportAlreadyConnectedError("schemas", this.name);
schemas.forEach(schema => this.entitySchemas.push(schema));
return this;
}
/**
* Imports subscribers and registers them in the current connection.
*/
importSubscribers(subscriberClasses: Function[]): this {
if (this.isConnected)
throw new CannotImportAlreadyConnectedError("entity subscribers", this.name);
subscriberClasses.forEach(cls => this.subscriberClasses.push(cls));
return this;
}
/**
* Imports naming strategies and registers them in the current connection.
*/
importNamingStrategies(strategies: Function[]): this {
if (this.isConnected)
throw new CannotImportAlreadyConnectedError("naming strategies", this.name);
strategies.forEach(cls => this.namingStrategyClasses.push(cls));
return this;
}
/**
* Imports migrations and registers them in the current connection.
*/
importMigrations(migrations: Function[]): this {
if (this.isConnected)
throw new CannotImportAlreadyConnectedError("migrations", this.name);
migrations.forEach(cls => this.migrationClasses.push(cls));
return this;
}
/**
* Sets given naming strategy to be used.
* Naming strategy must be set to be used before connection is established.
*/
useNamingStrategy(name: string): this;
/**
* Sets given naming strategy to be used.
* Naming strategy must be set to be used before connection is established.
*/
useNamingStrategy(strategy: Function): this;
/**
* Sets given naming strategy to be used.
* Naming strategy must be set to be used before connection is established.
*/
useNamingStrategy(strategyClassOrName: string|Function): this {
if (this.isConnected)
throw new CannotUseNamingStrategyNotConnectedError(this.name);
this.usedNamingStrategy = strategyClassOrName;
return this;
}
/**
* Gets the entity metadata of the given entity class.
*/
getMetadata(target: Function): EntityMetadata;
/**
* Gets the entity metadata of the given entity name.
*/
getMetadata(target: string): EntityMetadata;
/**
* Gets the entity metadata of the given entity class or schema name.
*/
getMetadata(target: Function|string): EntityMetadata;
/**
Gets entity metadata for the given entity class or schema name.
*/
getMetadata(target: Function|string): EntityMetadata {
const metadata = this.entityMetadatas.find(metadata => metadata.target === target || (typeof target === "string" && metadata.targetName === target));
const metadata = this.findMetadata(target);
if (!metadata)
throw new EntityMetadataNotFound(target);
@ -422,145 +250,41 @@ export class Connection {
}
/**
* Gets repository for the given entity class.
* Gets repository for the given entity.
*/
getRepository<Entity>(entityClass: ObjectType<Entity>): Repository<Entity>;
/**
* Gets repository for the given entity name.
*/
getRepository<Entity>(entityName: string): Repository<Entity>;
/**
* Gets repository for the given entity name.
*/
getRepository<Entity>(entityClassOrName: ObjectType<Entity>|string): Repository<Entity>;
/**
* Gets repository for the given entity class or name.
*/
getRepository<Entity>(entityClassOrName: ObjectType<Entity>|string): Repository<Entity> {
return this.findRepositoryAggregator(entityClassOrName).repository;
getRepository<Entity>(target: ObjectType<Entity>|string): Repository<Entity> {
return this.getMetadata(target).repository;
}
/**
* Gets tree repository for the given entity class.
* Only tree-type entities can have a TreeRepository,
* like ones decorated with @ClosureEntity decorator.
*/
getTreeRepository<Entity>(entityClass: ObjectType<Entity>): TreeRepository<Entity>;
/**
* Gets tree repository for the given entity class.
* Only tree-type entities can have a TreeRepository,
* like ones decorated with @ClosureEntity decorator.
*/
getTreeRepository<Entity>(entityClassOrName: ObjectType<Entity>|string): TreeRepository<Entity>;
/**
* Gets tree repository for the given entity class.
* Only tree-type entities can have a TreeRepository,
* like ones decorated with @ClosureEntity decorator.
*/
getTreeRepository<Entity>(entityName: string): TreeRepository<Entity>;
/**
* Gets tree repository for the given entity class or name.
* Only tree-type entities can have a TreeRepository,
* like ones decorated with @ClosureEntity decorator.
* Only tree-type entities can have a TreeRepository, like ones decorated with @ClosureEntity decorator.
*/
getTreeRepository<Entity>(entityClassOrName: ObjectType<Entity>|string): TreeRepository<Entity> {
// todo: add checks if tree repository is supported by driver (not supported by mongodb at least)
getTreeRepository<Entity>(target: ObjectType<Entity>|string): TreeRepository<Entity> {
if (this.driver instanceof MongoDriver)
throw new Error(`You cannot use getTreeRepository for MongoDB connections.`);
const repository = this.findRepositoryAggregator(entityClassOrName).treeRepository;
if (!repository)
throw new RepositoryNotTreeError(entityClassOrName);
if (!this.hasMetadata(target))
throw new RepositoryNotFoundError(this.name, target);
const repository = this.getMetadata(target).repository;
if (!(repository instanceof TreeRepository))
throw new RepositoryNotTreeError(target);
return repository;
}
/**
* Gets mongodb-specific repository for the given entity class.
*/
getMongoRepository<Entity>(entityClass: ObjectType<Entity>): MongoRepository<Entity>;
/**
* Gets mongodb-specific repository for the given entity name.
*/
getMongoRepository<Entity>(entityName: string): MongoRepository<Entity>;
/**
* Gets mongodb-specific repository for the given entity name.
*/
getMongoRepository<Entity>(entityClassOrName: ObjectType<Entity>|string): MongoRepository<Entity>;
/**
* Gets mongodb-specific repository for the given entity class or name.
*/
getMongoRepository<Entity>(entityClassOrName: ObjectType<Entity>|string): MongoRepository<Entity> {
getMongoRepository<Entity>(target: ObjectType<Entity>|string): MongoRepository<Entity> {
if (!(this.driver instanceof MongoDriver))
throw new Error(`You can use getMongoRepository only for MongoDB connections.`);
return this.findRepositoryAggregator(entityClassOrName).repository as MongoRepository<Entity>;
}
if (!this.hasMetadata(target))
throw new RepositoryNotFoundError(this.name, target);
/**
* Gets specific repository for the given entity class.
* SpecificRepository is a special repository that contains specific and non standard repository methods.
*
* @experimental
*/
getSpecificRepository<Entity>(entityClass: ObjectType<Entity>): SpecificRepository<Entity>;
/**
* Gets specific repository for the given entity name.
* SpecificRepository is a special repository that contains specific and non standard repository methods.
*
* @experimental
*/
getSpecificRepository<Entity>(entityName: string): SpecificRepository<Entity>;
/**
* Gets specific repository for the given entity class or name.
* SpecificRepository is a special repository that contains specific and non standard repository methods.
*
* @experimental
*/
getSpecificRepository<Entity>(entityClassOrName: ObjectType<Entity>|string): SpecificRepository<Entity>;
/**
* Gets specific repository for the given entity class or name.
* SpecificRepository is a special repository that contains specific and non standard repository methods.
*
* @experimental
*/
getSpecificRepository<Entity>(entityClassOrName: ObjectType<Entity>|string): SpecificRepository<Entity> {
return this.findRepositoryAggregator(entityClassOrName).specificRepository;
}
/**
* 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 connection back to pool.
*/
createEntityManagerWithSingleDatabaseConnection(queryRunnerProvider?: QueryRunnerProvider): EntityManager {
if (!queryRunnerProvider)
queryRunnerProvider = new QueryRunnerProvider(this.driver, true);
return new EntityManager(this, queryRunnerProvider);
}
/**
* Gets migration instances that are registered for this connection.
*/
getMigrations(): MigrationInterface[] {
if (this.migrationClasses && this.migrationClasses.length) {
return this.migrationClasses.map(migrationClass => {
return getFromContainer<MigrationInterface>(migrationClass);
});
}
return [];
return this.getMetadata(target).repository as MongoRepository<Entity>;
}
/**
@ -570,132 +294,199 @@ export class Connection {
return this.manager.getCustomRepository(customRepository);
}
/**
* Wraps given function execution (and all operations made there) in a transaction.
* All database operations must be executed using provided entity manager.
*/
async transaction(runInTransaction: (entityManger: EntityManager) => Promise<any>,
queryRunnerProvider?: QueryRunnerProvider): Promise<any> {
if (this instanceof MongoEntityManager)
throw new Error(`Transactions aren't supported by MongoDB.`);
if (queryRunnerProvider && queryRunnerProvider.isReleased)
throw new QueryRunnerProviderAlreadyReleasedError();
const usedQueryRunnerProvider = queryRunnerProvider || new QueryRunnerProvider(this.driver, true);
const queryRunner = await usedQueryRunnerProvider.provide();
const transactionEntityManager = new EntityManager(this, usedQueryRunnerProvider);
try {
await queryRunner.beginTransaction();
const result = await runInTransaction(transactionEntityManager);
await queryRunner.commitTransaction();
return result;
} catch (err) {
await queryRunner.rollbackTransaction();
throw err;
} finally {
await usedQueryRunnerProvider.release(queryRunner);
if (!queryRunnerProvider) // if we used a new query runner provider then release it
await usedQueryRunnerProvider.releaseReused(); // todo: why we don't do same in query method?
}
}
/**
* Executes raw SQL query and returns raw database results.
*/
async query(query: string, parameters?: any[], queryRunnerProvider?: QueryRunnerProvider): Promise<any> {
if (this instanceof MongoEntityManager)
throw new Error(`Queries aren't supported by MongoDB.`);
if (queryRunnerProvider && queryRunnerProvider.isReleased)
throw new QueryRunnerProviderAlreadyReleasedError();
const usedQueryRunnerProvider = queryRunnerProvider || new QueryRunnerProvider(this.driver);
const queryRunner = await usedQueryRunnerProvider.provide();
try {
return await queryRunner.query(query, parameters); // await is needed here because we are using finally
} finally {
await usedQueryRunnerProvider.release(queryRunner);
}
}
/**
* Creates a new query builder that can be used to build a sql query.
*/
createQueryBuilder<Entity>(entityClass: ObjectType<Entity>|Function|string, alias: string, queryRunnerProvider?: QueryRunnerProvider): QueryBuilder<Entity>;
/**
* Creates a new query builder that can be used to build a sql query.
*/
createQueryBuilder(queryRunnerProvider?: QueryRunnerProvider): QueryBuilder<any>;
/**
* Creates a new query builder that can be used to build a sql query.
*/
createQueryBuilder<Entity>(entityClass?: ObjectType<Entity>|Function|string|QueryRunnerProvider, alias?: string, queryRunnerProvider?: QueryRunnerProvider): QueryBuilder<Entity> {
if (this instanceof MongoEntityManager)
throw new Error(`Query Builder is not supported by MongoDB.`);
if (alias) {
const metadata = this.getMetadata(entityClass as Function|string);
return new QueryBuilder(this, queryRunnerProvider)
.select(alias)
.from(metadata.target, alias);
} else {
return new QueryBuilder(this, entityClass as QueryRunnerProvider|undefined);
}
}
/**
* 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 connection back to pool.
*/
createIsolatedManager(queryRunnerProvider?: QueryRunnerProvider): EntityManager {
if (!queryRunnerProvider)
queryRunnerProvider = new QueryRunnerProvider(this.driver, true);
return new EntityManagerFactory().create(this, queryRunnerProvider);
}
/**
* 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 entity manager, don't forget to release it, to release connection back to pool.
*/
createIsolatedRepository<Entity>(entityClassOrName: ObjectType<Entity>|string, queryRunnerProvider?: QueryRunnerProvider): Repository<Entity> {
if (!queryRunnerProvider)
queryRunnerProvider = new QueryRunnerProvider(this.driver, true);
return new RepositoryFactory().createRepository(this, this.getMetadata(entityClassOrName), queryRunnerProvider);
}
/**
* Creates a new specific 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 entity manager, don't forget to release it, to release connection back to pool.
*/
createIsolatedSpecificRepository<Entity>(entityClassOrName: ObjectType<Entity>|string, queryRunnerProvider?: QueryRunnerProvider): SpecificRepository<Entity> {
if (!queryRunnerProvider)
queryRunnerProvider = new QueryRunnerProvider(this.driver, true);
return new RepositoryFactory().createSpecificRepository(this, this.getMetadata(entityClassOrName), queryRunnerProvider);
}
// -------------------------------------------------------------------------
// Deprecated Public Methods
// -------------------------------------------------------------------------
/**
* Gets entity manager that allows to perform repository operations with any entity in this connection.
*
* @deprecated use manager instead.
*/
get entityManager(): EntityManager {
return this.manager;
}
/**
* Gets specific repository for the given entity class or name.
* SpecificRepository is a special repository that contains specific and non standard repository methods.
*
* @deprecated don't use it, it will be refactored or removed in the future versions
*/
getSpecificRepository<Entity>(entityClassOrName: ObjectType<Entity>|string): SpecificRepository<Entity> {
if (!this.hasMetadata(entityClassOrName))
throw new RepositoryNotFoundError(this.name, entityClassOrName);
return this.getMetadata(entityClassOrName).specificRepository;
}
// -------------------------------------------------------------------------
// Protected Methods
// -------------------------------------------------------------------------
/**
* Finds repository aggregator of the given entity class or name.
* Finds entity metadata exist for the given entity class, target name or table name.
*/
protected findRepositoryAggregator(entityClassOrName: ObjectType<any>|string): RepositoryAggregator {
// if (!this.isConnected)
// throw new NoConnectionForRepositoryError(this.name);
protected findMetadata(target: Function|string): EntityMetadata|undefined {
return this.entityMetadatas.find(metadata => {
if (metadata.target === target)
return true;
if (typeof target === "string")
return metadata.name === target || metadata.tableName === target;
if (!this.entityMetadatas.find(metadata => metadata.target === entityClassOrName || (typeof entityClassOrName === "string" && metadata.targetName === entityClassOrName)))
throw new RepositoryNotFoundError(this.name, entityClassOrName);
const metadata = this.getMetadata(entityClassOrName);
const repositoryAggregator = this.repositoryAggregators.find(repositoryAggregate => repositoryAggregate.metadata === metadata);
if (!repositoryAggregator)
throw new RepositoryNotFoundError(this.name, entityClassOrName);
return repositoryAggregator;
return false;
});
}
/**
* Builds all registered metadatas.
*/
public buildMetadatas() {
protected buildMetadatas(): void {
this.entitySubscribers.length = 0;
this.repositoryAggregators.length = 0;
this.entityMetadatas.length = 0;
this.driver.namingStrategy = this.createNamingStrategy(); // todo: why they are in the driver
this.driver.lazyRelationsWrapper = this.createLazyRelationsWrapper(); // todo: why they are in the driver
const connectionMetadataBuilder = new ConnectionMetadataBuilder(this);
const repositoryFactory = new RepositoryFactory();
const entityMetadataValidator = new EntityMetadataValidator();
// take imported event subscribers
if (this.subscriberClasses && this.subscriberClasses.length && !PlatformTools.getEnvVariable("SKIP_SUBSCRIBERS_LOADING")) {
getMetadataArgsStorage()
.filterSubscribers(this.subscriberClasses)
.map(metadata => getFromContainer(metadata.target))
.forEach(subscriber => this.entitySubscribers.push(subscriber));
// build subscribers if they are not disallowed from high-level (for example they can disallowed from migrations run process)
if (!PlatformTools.getEnvVariable("SKIP_SUBSCRIBERS_LOADING")) {
const subscribers = connectionMetadataBuilder.buildSubscribers(this.options.subscribers || []);
Object.assign(this, { subscribers: subscribers });
}
// take imported entity listeners
if (this.entityClasses && this.entityClasses.length) {
// build entity metadatas
const entityMetadatas = connectionMetadataBuilder.buildEntityMetadatas(this.options.entities || [], this.options.entitySchemas || []);
Object.assign(this, { entityMetadatas: entityMetadatas });
// build entity metadatas from metadata args storage (collected from decorators)
new EntityMetadataBuilder(this, getMetadataArgsStorage())
.build(this.entityClasses)
.forEach(metadata => {
this.entityMetadatas.push(metadata);
this.repositoryAggregators.push(new RepositoryAggregator(this, metadata));
});
}
// create migration instances
const migrations = connectionMetadataBuilder.buildMigrations(this.options.migrations || []);
Object.assign(this, { migrations: migrations });
// build entity metadatas from given entity schemas
if (this.entitySchemas && this.entitySchemas.length) {
const metadataArgsStorage = getFromContainer(EntitySchemaTransformer).transform(this.entitySchemas);
new EntityMetadataBuilder(this, metadataArgsStorage)
.build()
.forEach(metadata => {
this.entityMetadatas.push(metadata);
this.repositoryAggregators.push(new RepositoryAggregator(this, metadata));
});
}
// initialize repositories for all entity metadatas
this.entityMetadatas.forEach(metadata => {
metadata.repository = repositoryFactory.createRepository(this, metadata);
metadata.specificRepository = repositoryFactory.createSpecificRepository(this, metadata);
});
// validate all created entity metadatas to make sure user created entities are valid and correct
entityMetadataValidator.validateMany(this.entityMetadatas);
}
/**
* Creates a naming strategy to be used for this connection.
*/
protected createNamingStrategy(): NamingStrategyInterface {
// if naming strategies are not loaded, or used naming strategy is not set then use default naming strategy
if (!this.namingStrategyClasses || !this.namingStrategyClasses.length || !this.usedNamingStrategy)
return getFromContainer(DefaultNamingStrategy);
// try to find used naming strategy in the list of loaded naming strategies
const namingMetadata = getMetadataArgsStorage()
.filterNamingStrategies(this.namingStrategyClasses)
.find(strategy => {
if (typeof this.usedNamingStrategy === "string") {
return strategy.name === this.usedNamingStrategy;
} else {
return strategy.target === this.usedNamingStrategy;
}
});
// throw an error if not found
if (!namingMetadata)
throw new NamingStrategyNotFoundError(this.usedNamingStrategy, this.name);
// initialize a naming strategy instance
return getFromContainer<NamingStrategyInterface>(namingMetadata.target);
}
/**
* Creates a new default entity manager without single connection setup.
*/
protected createEntityManager() {
if (this.driver instanceof MongoDriver)
return new MongoEntityManager(this);
return new EntityManager(this);
}
/**
* Creates a new entity broadcaster using in this connection.
*/
protected createBroadcaster() {
return new Broadcaster(this, this.entitySubscribers);
}
/**
* Creates a schema builder used to build a database schema for the entities of the current connection.
*/
protected createSchemaBuilder() {
return new SchemaBuilder(this.driver, this.logger, this.entityMetadatas);
}
/**
* Creates a lazy relations wrapper.
*/
protected createLazyRelationsWrapper() {
return new LazyRelationsWrapper(this);
}
}

View File

@ -1,21 +1,10 @@
import {Connection} from "./Connection";
import {ConnectionNotFoundError} from "./error/ConnectionNotFoundError";
import {MysqlDriver} from "../driver/mysql/MysqlDriver";
import {ConnectionOptions} from "./ConnectionOptions";
import {DriverOptions} from "../driver/DriverOptions";
import {Driver} from "../driver/Driver";
import {MissingDriverError} from "./error/MissingDriverError";
import {PostgresDriver} from "../driver/postgres/PostgresDriver";
import {AlreadyHasActiveConnectionError} from "./error/AlreadyHasActiveConnectionError";
import {Logger} from "../logger/Logger";
import {SqliteDriver} from "../driver/sqlite/SqliteDriver";
import {OracleDriver} from "../driver/oracle/OracleDriver";
import {SqlServerDriver} from "../driver/sqlserver/SqlServerDriver";
import {OrmUtils} from "../util/OrmUtils";
import {CannotDetermineConnectionOptionsError} from "./error/CannotDetermineConnectionOptionsError";
import {PlatformTools} from "../platform/PlatformTools";
import {WebsqlDriver} from "../driver/websql/WebsqlDriver";
import {MongoDriver} from "../driver/mongodb/MongoDriver";
/**
* ConnectionManager is used to store and manage all these different connections.
@ -64,54 +53,20 @@ export class ConnectionManager {
*/
create(options: ConnectionOptions): Connection {
const logger = new Logger(options.logging || {});
const driver = this.createDriver(options.driver, logger);
const connection = this.createConnection(options.name || "default", driver, logger);
// (backward compatibility) if options are set in the driver section of connection options then merge them into the option
if (options.driver)
Object.assign(options, options.driver);
// import entity schemas
if (options.entitySchemas) {
const [directories, classes] = this.splitStringsAndClasses(options.entitySchemas);
connection
.importEntitySchemas(classes)
.importEntitySchemaFromDirectories(directories);
const existConnection = this.connections.find(connection => connection.name === (options.name || "default"));
if (existConnection) {
if (existConnection.isConnected)
throw new AlreadyHasActiveConnectionError(options.name || "default");
this.connections.splice(this.connections.indexOf(existConnection), 1);
}
// import entities
if (options.entities) {
const [directories, classes] = this.splitStringsAndClasses(options.entities);
connection
.importEntities(classes)
.importEntitiesFromDirectories(directories);
}
// import subscriber
if (options.subscribers) {
const [directories, classes] = this.splitStringsAndClasses(options.subscribers);
connection
.importSubscribers(classes)
.importSubscribersFromDirectories(directories);
}
// import naming strategies
if (options.namingStrategies) {
const [directories, classes] = this.splitStringsAndClasses(options.namingStrategies);
connection
.importNamingStrategies(classes)
.importNamingStrategiesFromDirectories(directories);
}
// import migrations
if (options.migrations) {
const [directories, classes] = this.splitStringsAndClasses(options.migrations);
connection
.importMigrations(classes)
.importMigrationsFromDirectories(directories);
}
// set naming strategy to be used for this connection
if (options.usedNamingStrategy)
connection.useNamingStrategy(options.usedNamingStrategy as any);
const connection = new Connection(options);
this.connections.push(connection);
return connection;
}
@ -341,8 +296,6 @@ export class ConnectionManager {
entities: PlatformTools.getEnvVariable("TYPEORM_ENTITIES") ? PlatformTools.getEnvVariable("TYPEORM_ENTITIES").split(",") : [],
subscribers: PlatformTools.getEnvVariable("TYPEORM_SUBSCRIBERS") ? PlatformTools.getEnvVariable("TYPEORM_SUBSCRIBERS").split(",") : [],
entitySchemas: PlatformTools.getEnvVariable("TYPEORM_ENTITY_SCHEMAS") ? PlatformTools.getEnvVariable("TYPEORM_ENTITY_SCHEMAS").split(",") : [],
namingStrategies: PlatformTools.getEnvVariable("TYPEORM_NAMING_STRATEGIES") ? PlatformTools.getEnvVariable("TYPEORM_NAMING_STRATEGIES").split(",") : [],
usedNamingStrategy: PlatformTools.getEnvVariable("TYPEORM_USED_NAMING_STRATEGY"),
logging: {
logQueries: OrmUtils.toBoolean(PlatformTools.getEnvVariable("TYPEORM_LOGGING_QUERIES")),
logFailedQueryError: OrmUtils.toBoolean(PlatformTools.getEnvVariable("TYPEORM_LOGGING_FAILED_QUERIES")),
@ -384,13 +337,43 @@ export class ConnectionManager {
throw new Error(`Configuration ${path || "ormconfig.json"} was not found. Add connection configuration inside ormconfig.json file.`);
const environmentLessOptions = optionsArray.filter(options => (options.name || "default") === connectionName);
const options = environmentLessOptions.filter(options => !options.environment || options.environment === PlatformTools.getEnvVariable("NODE_ENV")); // skip connection creation if environment is set in the options, and its not equal to the value in the NODE_ENV variable
const options = environmentLessOptions.find(options => !options.environment || options.environment === PlatformTools.getEnvVariable("NODE_ENV")); // skip connection creation if environment is set in the options, and its not equal to the value in the NODE_ENV variable
if (!options.length)
if (!options)
throw new Error(`Connection "${connectionName}" ${PlatformTools.getEnvVariable("NODE_ENV") ? "for the environment " + PlatformTools.getEnvVariable("NODE_ENV") + " " : ""}was not found in the json configuration file.` +
(environmentLessOptions.length ? ` However there are such configurations for other environments: ${environmentLessOptions.map(options => options.environment).join(", ")}.` : ""));
return this.createAndConnectByConnectionOptions(options[0]);
let connectionOptions: ConnectionOptions = Object.assign({}, options);
// normalize directory paths
if (options.entities) {
const entities = (options.entities as any[]).map(entity => {
if (typeof entity === "string" || entity.substr(0, 1) !== "/")
return PlatformTools.load("app-root-path").path + "/" + entity;
return entity;
});
Object.assign(connectionOptions, { entities: entities });
}
if (options.subscribers) {
const subscribers = (options.subscribers as any[]).map(subscriber => {
if (typeof subscriber === "string" || subscriber.substr(0, 1) !== "/")
return PlatformTools.load("app-root-path").path + "/" + subscriber;
return subscriber;
});
Object.assign(connectionOptions, { subscribers: subscribers });
}
if (options.migrations) {
const migrations = (options.migrations as any[]).map(migration => {
if (typeof migration === "string" || migration.substr(0, 1) !== "/")
return PlatformTools.load("app-root-path").path + "/" + migration;
return migration;
});
Object.assign(connectionOptions, { migrations: migrations });
}
return this.createAndConnectByConnectionOptions(options);
}
/**
@ -417,57 +400,4 @@ export class ConnectionManager {
return connection;
}
/**
* Splits given array of mixed strings and / or functions into two separate array of string and array of functions.
*/
protected splitStringsAndClasses<T>(strAndClses: string[]|T[]): [string[], T[]] {
return [
(strAndClses as string[]).filter(str => typeof str === "string"),
(strAndClses as T[]).filter(cls => typeof cls !== "string"),
];
}
/**
* Creates a new driver based on the given driver type and options.
*/
protected createDriver(options: DriverOptions, logger: Logger): Driver {
switch (options.type) {
case "mysql":
return new MysqlDriver(options, logger, undefined);
case "postgres":
return new PostgresDriver(options, logger);
case "mariadb":
return new MysqlDriver(options, logger);
case "sqlite":
return new SqliteDriver(options, logger);
case "oracle":
return new OracleDriver(options, logger);
case "mssql":
return new SqlServerDriver(options, logger);
case "websql":
return new WebsqlDriver(options, logger);
case "mongodb":
return new MongoDriver(options, logger);
default:
throw new MissingDriverError(options.type);
}
}
/**
* Creates a new connection and registers it in the connection manager.
*/
protected createConnection(name: string, driver: Driver, logger: Logger) {
const existConnection = this.connections.find(connection => connection.name === name);
if (existConnection) {
if (existConnection.isConnected)
throw new AlreadyHasActiveConnectionError(name);
this.connections.splice(this.connections.indexOf(existConnection), 1);
}
const connection = new Connection(name, driver, logger);
this.connections.push(connection);
return connection;
}
}

View File

@ -0,0 +1,53 @@
import {importClassesFromDirectories, importJsonsFromDirectories} from "../util/DirectoryExportedClassesLoader";
import {OrmUtils} from "../util/OrmUtils";
import {getFromContainer} from "../container";
import {MigrationInterface} from "../migration/MigrationInterface";
import {getMetadataArgsStorage} from "../index";
import {EntityMetadataBuilder} from "../metadata-builder/EntityMetadataBuilder";
import {EntitySchemaTransformer} from "../entity-schema/EntitySchemaTransformer";
import {Connection} from "./Connection";
import {EntitySchema} from "../entity-schema/EntitySchema";
import {EntityMetadata} from "../metadata/EntityMetadata";
import {EntitySubscriberInterface} from "../subscriber/EntitySubscriberInterface";
export class ConnectionMetadataBuilder {
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
constructor(protected connection: Connection) {
}
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
buildMigrations(migrations: Function[]|string[]): MigrationInterface[] {
const [migrationClasses, migrationDirectories] = OrmUtils.splitClassesAndStrings(migrations);
const allMigrationClasses = [...migrationClasses, ...importClassesFromDirectories(migrationDirectories)];
return allMigrationClasses.map(migrationClass => getFromContainer<MigrationInterface>(migrationClass));
}
buildSubscribers(subscribers: Function[]|string[]): EntitySubscriberInterface<any>[] {
const [subscriberClasses, subscriberDirectories] = OrmUtils.splitClassesAndStrings(subscribers || []);
const allSubscriberClasses = [...subscriberClasses, ...importClassesFromDirectories(subscriberDirectories)];
return getMetadataArgsStorage()
.filterSubscribers(allSubscriberClasses)
.map(metadata => getFromContainer<EntitySubscriberInterface<any>>(metadata.target));
}
buildEntityMetadatas(entities: Function[]|string[], schemas: EntitySchema[]|string[]): EntityMetadata[] {
const [entityClasses, entityDirectories] = OrmUtils.splitClassesAndStrings(entities || []);
const allEntityClasses = [...entityClasses, ...importClassesFromDirectories(entityDirectories)];
const decoratorEntityMetadatas = new EntityMetadataBuilder(this.connection, getMetadataArgsStorage()).build(allEntityClasses);
const [entitySchemaClasses, entitySchemaDirectories] = OrmUtils.splitClassesAndStrings(schemas || []);
const allEntitySchemaClasses = [...entitySchemaClasses, ...importJsonsFromDirectories(entitySchemaDirectories)];
const metadataArgsStorageFromSchema = new EntitySchemaTransformer().transform(allEntitySchemaClasses);
const schemaEntityMetadatas = new EntityMetadataBuilder(this.connection, metadataArgsStorageFromSchema).build();
return [...decoratorEntityMetadatas, ...schemaEntityMetadatas];
}
}

View File

@ -1,6 +1,11 @@
import {DriverOptions} from "../driver/DriverOptions";
import {EntitySchema} from "../entity-schema/EntitySchema";
import {LoggerOptions} from "../logger/LoggerOptions";
import {NamingStrategyInterface} from "../naming-strategy/NamingStrategyInterface";
import {DriverType} from "../driver/DriverType";
import {LoggerFactory} from "../logger/LoggerFactory";
import {DriverFactory} from "../driver/DriverFactory";
import {EntityManagerFactory} from "../entity-manager/EntityManagerFactory";
/**
* ConnectionOptions is an interface with settings and options for specific connection.
@ -9,11 +14,6 @@ import {LoggerOptions} from "../logger/LoggerOptions";
*/
export interface ConnectionOptions {
/**
* Database options of this connection.
*/
readonly driver: DriverOptions;
/**
* Connection name. If connection name is not given then it will be called "default".
* Different connections must have different names.
@ -21,9 +21,87 @@ export interface ConnectionOptions {
readonly name?: string;
/**
* Name of the naming strategy or target class of the naming strategy to be used for this connection.
* Database options of this connection.
*
* @deprecated Define options right in the connection options section.
*/
readonly usedNamingStrategy?: string|Function;
readonly driver?: DriverOptions;
/**
* Database type. This value is required.
*/
readonly type?: DriverType;
/**
* Connection url to where perform connection to.
*/
readonly url?: string;
/**
* Database host.
*/
readonly host?: string;
/**
* Database host port.
*/
readonly port?: number;
/**
* Database username.
*/
readonly username?: string;
/**
* Database password.
*/
readonly password?: string;
/**
* Database name to connect to.
*/
readonly database?: string;
/**
* Schema name. By default is "public" (used only in Postgres databases).
*/
readonly schemaName?: string;
/**
* Connection SID (used for Oracle databases).
*/
readonly sid?: string;
/**
* Storage type or path to the storage (used for SQLite databases).
*/
readonly storage?: string;
/**
* Indicates if connection pooling should be used or not.
* Be default it is enabled if its supported by a platform.
* Set to false to disable it.
*
* @todo: rename to disablePool? What about mongodb pool?
*/
readonly usePool?: boolean;
/**
* Extra connection options to be passed to the underlying driver.
*/
readonly extra?: any;
/**
* Prefix to use on all tables (collections) of this connection in the database.
*
* @todo: rename to entityPrefix
*/
readonly tablesPrefix?: string;
/**
* Naming strategy to be used to name tables and columns in the database.
*/
readonly namingStrategy?: NamingStrategyInterface;
/**
* Entities to be loaded for this connection.
@ -39,13 +117,6 @@ export interface ConnectionOptions {
*/
readonly subscribers?: Function[]|string[];
/**
* Naming strategies to be loaded for this connection.
* Accepts both naming strategy classes and directories where from naming strategies need to be loaded.
* Directories support glob patterns.
*/
readonly namingStrategies?: Function[]|string[];
/**
* Entity schemas to be loaded for this connection.
* Accepts both entity schema classes and directories where from entity schemas need to be loaded.

View File

@ -1,19 +0,0 @@
import {getMetadataArgsStorage} from "../index";
import {NamingStrategyMetadataArgs} from "../metadata-args/NamingStrategyMetadataArgs";
/**
* Decorator registers a new naming strategy to be used in naming things.
*
* todo: deprecate using naming strategies this way. use it without decorators
* todo: but add multiple default naming strategies for use
*/
export function NamingStrategy(name?: string): Function {
return function (target: Function) {
const strategyName = name ? name : (<any> target).name;
const args: NamingStrategyMetadataArgs = {
target: target,
name: strategyName
};
getMetadataArgsStorage().namingStrategies.push(args);
};
}

View File

@ -10,36 +10,6 @@ import {LazyRelationsWrapper} from "../lazy-loading/LazyRelationsWrapper";
*/
export interface Driver {
/**
* Naming strategy used in the connection where this driver is used.
*/
namingStrategy: NamingStrategyInterface;
/**
* Used to wrap lazy relations to be able to perform lazy loadings.
*/
lazyRelationsWrapper: LazyRelationsWrapper;
/**
* Driver options contains connectivity options used to connection to the database.
*/
readonly options: DriverOptions;
/**
* Creates repository instance of this driver.
*/
// createRepository(connection: Connection, metadata: EntityMetadata, queryRunnerProvider?: QueryRunnerProvider): Repository<any>;
/**
* Creates tree repository instance of this driver.
*/
// createTreeRepository(connection: Connection, metadata: EntityMetadata, queryRunnerProvider?: QueryRunnerProvider): TreeRepository<any>;
/**
* Creates specific repository instance of this driver.
*/
// createSpecificRepository(connection: Connection, metadata: EntityMetadata, queryRunnerProvider?: QueryRunnerProvider): SpecificRepository<any>;
/**
* Performs connection to the database.
* Based on pooling options, it can either create connection immediately,

View File

@ -0,0 +1,43 @@
import {MissingDriverError} from "./error/MissingDriverError";
import {MongoDriver} from "./mongodb/MongoDriver";
import {WebsqlDriver} from "./websql/WebsqlDriver";
import {SqlServerDriver} from "./sqlserver/SqlServerDriver";
import {OracleDriver} from "./oracle/OracleDriver";
import {SqliteDriver} from "./sqlite/SqliteDriver";
import {MysqlDriver} from "./mysql/MysqlDriver";
import {PostgresDriver} from "./postgres/PostgresDriver";
import {Driver} from "./Driver";
import {Connection} from "../connection/Connection";
/**
* Helps to create drivers.
*/
export class DriverFactory {
/**
* Creates a new driver depend on a given connection's driver type.
*/
create(connection: Connection): Driver {
switch (connection.options.type) {
case "mysql":
return new MysqlDriver(connection);
case "postgres":
return new PostgresDriver(connection);
case "mariadb":
return new MysqlDriver(connection);
case "sqlite":
return new SqliteDriver(connection);
case "oracle":
return new OracleDriver(connection);
case "mssql":
return new SqlServerDriver(connection);
case "websql":
return new WebsqlDriver(connection);
case "mongodb":
return new MongoDriver(connection);
default:
throw new MissingDriverError(connection.options.type!);
}
}
}

View File

@ -1,62 +1,61 @@
/**
* Driver type.
*/
export type DriverType = "mysql"|"postgres"|"mariadb"|"sqlite"|"oracle"|"mssql"|"websql"|"mongodb";
import {DriverType} from "./DriverType";
/**
* Connectivity options used to connect to the database, and other database-driver-specific options.
*
* @deprecated
*/
export interface DriverOptions {
/**
* Database type. This value is required.
*/
readonly type: DriverType;
type?: DriverType;
/**
* Connection url to where perform connection to.
*/
readonly url?: string;
url?: string;
/**
* Database host.
*/
readonly host?: string;
host?: string;
/**
* Database host port.
*/
readonly port?: number;
port?: number;
/**
* Database username.
*/
readonly username?: string;
username?: string;
/**
* Database password.
*/
readonly password?: string;
password?: string;
/**
* Database name to connect to.
*/
readonly database?: string;
database?: string;
/**
* Schema name. By default is "public" (used only in Postgres databases).
*/
readonly schemaName?: string;
schemaName?: string;
/**
* Connection SID (used for Oracle databases).
*/
readonly sid?: string;
sid?: string;
/**
* Storage type or path to the storage (used for SQLite databases).
*/
readonly storage?: string;
storage?: string;
/**
* Indicates if connection pooling should be used or not.
@ -65,18 +64,18 @@ export interface DriverOptions {
*
* @todo: rename to disablePool? What about mongodb pool?
*/
readonly usePool?: boolean;
usePool?: boolean;
/**
* Extra connection options to be passed to the underlying driver.
*/
readonly extra?: any;
extra?: any;
/**
* Prefix to use on all tables (collections) of this connection in the database.
*
* @todo: rename to entityPrefix
*/
readonly tablesPrefix?: string;
tablesPrefix?: string;
}

4
src/driver/DriverType.ts Normal file
View File

@ -0,0 +1,4 @@
/**
* Driver type.
*/
export type DriverType = "mysql"|"postgres"|"mariadb"|"sqlite"|"oracle"|"mssql"|"websql"|"mongodb";

View File

@ -3,16 +3,14 @@ import {ConnectionIsNotSetError} from "../error/ConnectionIsNotSetError";
import {DriverOptions} from "../DriverOptions";
import {DatabaseConnection} from "../DatabaseConnection";
import {DriverPackageNotInstalledError} from "../error/DriverPackageNotInstalledError";
import {Logger} from "../../logger/Logger";
import {QueryRunner} from "../../query-runner/QueryRunner";
import {MongoQueryRunner} from "./MongoQueryRunner";
import {ObjectLiteral} from "../../common/ObjectLiteral";
import {ColumnMetadata} from "../../metadata/ColumnMetadata";
import {DriverOptionNotSetError} from "../error/DriverOptionNotSetError";
import {PlatformTools} from "../../platform/PlatformTools";
import {NamingStrategyInterface} from "../../naming-strategy/NamingStrategyInterface";
import {EntityMetadata} from "../../metadata/EntityMetadata";
import {LazyRelationsWrapper} from "../../lazy-loading/LazyRelationsWrapper";
import {Connection} from "../../connection/Connection";
/**
* Organizes communication with MongoDB.
@ -23,27 +21,12 @@ export class MongoDriver implements Driver {
// Public Properties
// -------------------------------------------------------------------------
/**
* Naming strategy used in the connection where this driver is used.
*/
namingStrategy: NamingStrategyInterface;
/**
* Used to wrap lazy relations to be able to perform lazy loadings.
*/
lazyRelationsWrapper: LazyRelationsWrapper;
/**
* Mongodb does not require to dynamically create query runner each time,
* because it does not have a regular pool.
*/
queryRunner: MongoQueryRunner;
/**
* Driver connection options.
*/
readonly options: DriverOptions;
// -------------------------------------------------------------------------
// Protected Properties
// -------------------------------------------------------------------------
@ -58,27 +41,17 @@ export class MongoDriver implements Driver {
*/
protected pool: any;
/**
* Logger used to log queries and errors.
*/
protected logger: Logger;
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
constructor(options: DriverOptions, logger: Logger, mongodb?: any) {
constructor(protected connection: Connection) {
// validate options to make sure everything is correct and driver will be able to establish connection
this.validateOptions(options);
this.validateOptions(connection.options);
// if mongodb package instance was not set explicitly then try to load it
if (!mongodb)
mongodb = this.loadDependencies();
this.options = options;
this.logger = logger;
this.mongodb = mongodb;
// load mongodb package
this.mongodb = this.loadDependencies();
}
// -------------------------------------------------------------------------
@ -90,7 +63,7 @@ export class MongoDriver implements Driver {
*/
connect(): Promise<void> {
return new Promise<void>((ok, fail) => {
this.mongodb.MongoClient.connect(this.buildConnectionUrl(), this.options.extra, (err: any, database: any) => {
this.mongodb.MongoClient.connect(this.buildConnectionUrl(), this.connection.options!.extra, (err: any, database: any) => {
if (err) return fail(err);
this.pool = database;
@ -99,7 +72,7 @@ export class MongoDriver implements Driver {
connection: this.pool,
isTransactionActive: false
};
this.queryRunner = new MongoQueryRunner(databaseConnection, this, this.logger);
this.queryRunner = new MongoQueryRunner(this.connection, databaseConnection);
ok();
});
});
@ -266,10 +239,10 @@ export class MongoDriver implements Driver {
* Builds connection url that is passed to underlying driver to perform connection to the mongodb database.
*/
protected buildConnectionUrl(): string {
if (this.options.url)
return this.options.url;
if (this.connection.options.url)
return this.connection.options.url;
return `mongodb://${this.options.host || "127.0.0.1"}:${this.options.port || "27017"}/${this.options.database}`;
return `mongodb://${this.connection.options.host || "127.0.0.1"}:${this.connection.options.port || "27017"}/${this.connection.options.database}`;
}
}

View File

@ -1,8 +1,6 @@
import {QueryRunner} from "../../query-runner/QueryRunner";
import {DatabaseConnection} from "../DatabaseConnection";
import {ObjectLiteral} from "../../common/ObjectLiteral";
import {Logger} from "../../logger/Logger";
import {MongoDriver} from "./MongoDriver";
import {ColumnSchema} from "../../schema-builder/schema/ColumnSchema";
import {ColumnMetadata} from "../../metadata/ColumnMetadata";
import {TableSchema} from "../../schema-builder/schema/TableSchema";
@ -40,6 +38,7 @@ import {
UpdateWriteOpResult,
CollStats
} from "./typings";
import {Connection} from "../../connection/Connection";
/**
* Runs queries on a single MongoDB connection.
@ -50,9 +49,8 @@ export class MongoQueryRunner implements QueryRunner {
// Constructor
// -------------------------------------------------------------------------
constructor(protected databaseConnection: DatabaseConnection,
protected driver: MongoDriver,
protected logger: Logger) {
constructor(protected connection: Connection,
protected databaseConnection: DatabaseConnection) {
}
// -------------------------------------------------------------------------
@ -678,7 +676,7 @@ export class MongoQueryRunner implements QueryRunner {
* Database name shortcut.
*/
protected get dbName(): string {
return this.driver.options.database as string;
return this.connection.options.database as string;
}
/**

View File

@ -1,5 +1,5 @@
import { EventEmitter } from "events";
import { Readable, Writable } from "stream";
import {EventEmitter} from "events";
import {Readable, Writable} from "stream";
/**
* Creates a new MongoClient instance.

View File

@ -15,31 +15,13 @@ import {DataTransformationUtils} from "../../util/DataTransformationUtils";
import {PlatformTools} from "../../platform/PlatformTools";
import {NamingStrategyInterface} from "../../naming-strategy/NamingStrategyInterface";
import {LazyRelationsWrapper} from "../../lazy-loading/LazyRelationsWrapper";
import {Connection} from "../../connection/Connection";
/**
* Organizes communication with MySQL DBMS.
*/
export class MysqlDriver implements Driver {
// -------------------------------------------------------------------------
// Public Properties
// -------------------------------------------------------------------------
/**
* Naming strategy used in the connection where this driver is used.
*/
namingStrategy: NamingStrategyInterface;
/**
* Used to wrap lazy relations to be able to perform lazy loadings.
*/
lazyRelationsWrapper: LazyRelationsWrapper;
/**
* Driver connection options.
*/
readonly options: DriverOptions;
// -------------------------------------------------------------------------
// Protected Properties
// -------------------------------------------------------------------------
@ -64,32 +46,24 @@ export class MysqlDriver implements Driver {
*/
protected databaseConnectionPool: DatabaseConnection[] = [];
/**
* Logger used to log queries and errors.
*/
protected logger: Logger;
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
constructor(options: DriverOptions, logger: Logger, mysql?: any) {
constructor(protected connection: Connection) {
this.options = DriverUtils.buildDriverOptions(options);
this.logger = logger;
this.mysql = mysql;
Object.assign(connection.options, DriverUtils.buildDriverOptions(connection.options)); // todo: do it better way
// validate options to make sure everything is set
if (!(this.options.host || (this.options.extra && this.options.extra.socketPath)))
if (!(connection.options.host || (connection.options.extra && connection.options.extra.socketPath)))
throw new DriverOptionNotSetError("socketPath and host");
if (!this.options.username)
if (!connection.options.username)
throw new DriverOptionNotSetError("username");
if (!this.options.database)
if (!connection.options.database)
throw new DriverOptionNotSetError("database");
// if mysql package instance was not set explicitly then try to load it
if (!mysql)
this.loadDependencies();
// load mysql package
this.loadDependencies();
}
// -------------------------------------------------------------------------
@ -105,16 +79,16 @@ export class MysqlDriver implements Driver {
// build connection options for the driver
const options = Object.assign({}, {
host: this.options.host,
user: this.options.username,
password: this.options.password,
database: this.options.database,
port: this.options.port
}, this.options.extra || {});
host: this.connection.options.host,
user: this.connection.options.username,
password: this.connection.options.password,
database: this.connection.options.database,
port: this.connection.options.port
}, this.connection.options.extra || {});
// pooling is enabled either when its set explicitly to true,
// either when its not defined at all (e.g. enabled by default)
if (this.options.usePool === undefined || this.options.usePool === true) {
if (this.connection.options.usePool === undefined || this.connection.options.usePool === true) {
this.pool = this.mysql.createPool(options);
return Promise.resolve();
@ -164,7 +138,7 @@ export class MysqlDriver implements Driver {
return Promise.reject(new ConnectionIsNotSetError("mysql"));
const databaseConnection = await this.retrieveDatabaseConnection();
return new MysqlQueryRunner(databaseConnection, this, this.logger);
return new MysqlQueryRunner(this.connection, databaseConnection);
}
/**

View File

@ -14,6 +14,7 @@ import {PrimaryKeySchema} from "../../schema-builder/schema/PrimaryKeySchema";
import {IndexSchema} from "../../schema-builder/schema/IndexSchema";
import {QueryRunnerAlreadyReleasedError} from "../../query-runner/error/QueryRunnerAlreadyReleasedError";
import {ColumnType} from "../../metadata/types/ColumnTypes";
import {Connection} from "../../connection/Connection";
/**
* Runs queries on a single mysql database connection.
@ -34,9 +35,8 @@ export class MysqlQueryRunner implements QueryRunner {
// Constructor
// -------------------------------------------------------------------------
constructor(protected databaseConnection: DatabaseConnection,
protected driver: MysqlDriver,
protected logger: Logger) {
constructor(protected connection: Connection,
protected databaseConnection: DatabaseConnection) {
}
// -------------------------------------------------------------------------
@ -145,11 +145,11 @@ export class MysqlQueryRunner implements QueryRunner {
throw new QueryRunnerAlreadyReleasedError();
return new Promise((ok, fail) => {
this.logger.logQuery(query, parameters);
this.connection.logger.logQuery(query, parameters);
this.databaseConnection.connection.query(query, parameters, (err: any, result: any) => {
if (err) {
this.logger.logFailedQuery(query, parameters);
this.logger.logQueryError(err);
this.connection.logger.logFailedQuery(query, parameters);
this.connection.logger.logQueryError(err);
return fail(err);
}
@ -166,10 +166,10 @@ export class MysqlQueryRunner implements QueryRunner {
throw new QueryRunnerAlreadyReleasedError();
const keys = Object.keys(keyValues);
const columns = keys.map(key => this.driver.escapeColumnName(key)).join(", ");
const columns = keys.map(key => this.connection.driver.escapeColumnName(key)).join(", ");
const values = keys.map(key => "?").join(",");
const parameters = keys.map(key => keyValues[key]);
const sql = `INSERT INTO ${this.driver.escapeTableName(tableName)}(${columns}) VALUES (${values})`;
const sql = `INSERT INTO ${this.connection.driver.escapeTableName(tableName)}(${columns}) VALUES (${values})`;
const result = await this.query(sql, parameters);
return generatedColumn ? result.insertId : undefined;
}
@ -183,7 +183,7 @@ export class MysqlQueryRunner implements QueryRunner {
const updateValues = this.parametrize(valuesMap).join(", ");
const conditionString = this.parametrize(conditions).join(" AND ");
const sql = `UPDATE ${this.driver.escapeTableName(tableName)} SET ${updateValues} ${conditionString ? (" WHERE " + conditionString) : ""}`;
const sql = `UPDATE ${this.connection.driver.escapeTableName(tableName)} SET ${updateValues} ${conditionString ? (" WHERE " + conditionString) : ""}`;
const conditionParams = Object.keys(conditions).map(key => conditions[key]);
const updateParams = Object.keys(valuesMap).map(key => valuesMap[key]);
const allParameters = updateParams.concat(conditionParams);
@ -210,7 +210,7 @@ export class MysqlQueryRunner implements QueryRunner {
const conditionString = typeof conditions === "string" ? conditions : this.parametrize(conditions).join(" AND ");
const parameters = conditions instanceof Object ? Object.keys(conditions).map(key => (conditions as ObjectLiteral)[key]) : maybeParameters;
const sql = `DELETE FROM ${this.driver.escapeTableName(tableName)} WHERE ${conditionString}`;
const sql = `DELETE FROM ${this.connection.driver.escapeTableName(tableName)} WHERE ${conditionString}`;
await this.query(sql, parameters);
}
@ -223,16 +223,16 @@ export class MysqlQueryRunner implements QueryRunner {
let sql = "";
if (hasLevel) {
sql = `INSERT INTO ${this.driver.escapeTableName(tableName)}(ancestor, descendant, level) ` +
`SELECT ancestor, ${newEntityId}, level + 1 FROM ${this.driver.escapeTableName(tableName)} WHERE descendant = ${parentId} ` +
sql = `INSERT INTO ${this.connection.driver.escapeTableName(tableName)}(ancestor, descendant, level) ` +
`SELECT ancestor, ${newEntityId}, level + 1 FROM ${this.connection.driver.escapeTableName(tableName)} WHERE descendant = ${parentId} ` +
`UNION ALL SELECT ${newEntityId}, ${newEntityId}, 1`;
} else {
sql = `INSERT INTO ${this.driver.escapeTableName(tableName)}(ancestor, descendant) ` +
`SELECT ancestor, ${newEntityId} FROM ${this.driver.escapeTableName(tableName)} WHERE descendant = ${parentId} ` +
sql = `INSERT INTO ${this.connection.driver.escapeTableName(tableName)}(ancestor, descendant) ` +
`SELECT ancestor, ${newEntityId} FROM ${this.connection.driver.escapeTableName(tableName)} WHERE descendant = ${parentId} ` +
`UNION ALL SELECT ${newEntityId}, ${newEntityId}`;
}
await this.query(sql);
const results: ObjectLiteral[] = await this.query(`SELECT MAX(level) as level FROM ${this.driver.escapeTableName(tableName)} WHERE descendant = ${parentId}`);
const results: ObjectLiteral[] = await this.query(`SELECT MAX(level) as level FROM ${this.connection.driver.escapeTableName(tableName)} WHERE descendant = ${parentId}`);
return results && results[0] && results[0]["level"] ? parseInt(results[0]["level"]) + 1 : 1;
}
@ -765,7 +765,7 @@ export class MysqlQueryRunner implements QueryRunner {
* Truncates table.
*/
async truncate(tableName: string): Promise<void> {
await this.query(`TRUNCATE TABLE ${this.driver.escapeTableName(tableName)}`);
await this.query(`TRUNCATE TABLE ${this.connection.driver.escapeTableName(tableName)}`);
}
// -------------------------------------------------------------------------
@ -776,14 +776,14 @@ export class MysqlQueryRunner implements QueryRunner {
* Database name shortcut.
*/
protected get dbName(): string {
return this.driver.options.database as string;
return this.connection.options.database as string;
}
/**
* Parametrizes given object of values. Used to create column=value queries.
*/
protected parametrize(objectLiteral: ObjectLiteral): string[] {
return Object.keys(objectLiteral).map(key => this.driver.escapeColumnName(key) + "=?");
return Object.keys(objectLiteral).map(key => this.connection.driver.escapeColumnName(key) + "=?");
}
/**

View File

@ -15,6 +15,7 @@ import {DataTransformationUtils} from "../../util/DataTransformationUtils";
import {PlatformTools} from "../../platform/PlatformTools";
import {NamingStrategyInterface} from "../../naming-strategy/NamingStrategyInterface";
import {LazyRelationsWrapper} from "../../lazy-loading/LazyRelationsWrapper";
import {Connection} from "../../connection/Connection";
/**
* Organizes communication with Oracle DBMS.
@ -23,25 +24,6 @@ import {LazyRelationsWrapper} from "../../lazy-loading/LazyRelationsWrapper";
*/
export class OracleDriver implements Driver {
// -------------------------------------------------------------------------
// Public Properties
// -------------------------------------------------------------------------
/**
* Naming strategy used in the connection where this driver is used.
*/
namingStrategy: NamingStrategyInterface;
/**
* Used to wrap lazy relations to be able to perform lazy loadings.
*/
lazyRelationsWrapper: LazyRelationsWrapper;
/**
* Driver connection options.
*/
readonly options: DriverOptions;
// -------------------------------------------------------------------------
// Protected Properties
// -------------------------------------------------------------------------
@ -66,33 +48,24 @@ export class OracleDriver implements Driver {
*/
protected databaseConnectionPool: DatabaseConnection[] = [];
/**
* Logger used to log queries and errors.
*/
protected logger: Logger;
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
constructor(options: DriverOptions, logger: Logger, oracle?: any) {
constructor(protected connection: Connection) {
this.options = DriverUtils.buildDriverOptions(options, { useSid: true });
this.logger = logger;
this.oracle = oracle;
// Object.assign(connection.options, DriverUtils.buildDriverOptions(connection.options)); // todo: do it better way
// validate options to make sure everything is set
if (!this.options.host)
if (!connection.options.host)
throw new DriverOptionNotSetError("host");
if (!this.options.username)
if (!connection.options.username)
throw new DriverOptionNotSetError("username");
if (!this.options.sid)
if (!connection.options.sid)
throw new DriverOptionNotSetError("sid");
// if oracle package instance was not set explicitly then try to load it
if (!oracle)
this.loadDependencies();
// load oracle package
this.loadDependencies();
this.oracle.outFormat = this.oracle.OBJECT;
}
@ -109,14 +82,14 @@ export class OracleDriver implements Driver {
// build connection options for the driver
const options = Object.assign({}, {
user: this.options.username,
password: this.options.password,
connectString: this.options.host + ":" + this.options.port + "/" + this.options.sid,
}, this.options.extra || {});
user: this.connection.options.username,
password: this.connection.options.password,
connectString: this.connection.options.host + ":" + this.connection.options.port + "/" + this.connection.options.sid,
}, this.connection.options.extra || {});
// pooling is enabled either when its set explicitly to true,
// either when its not defined at all (e.g. enabled by default)
if (this.options.usePool === undefined || this.options.usePool === true) {
if (this.connection.options.usePool === undefined || this.connection.options.usePool === true) {
return new Promise<void>((ok, fail) => {
this.oracle.createPool(options, (err: any, pool: any) => {
if (err)
@ -177,7 +150,7 @@ export class OracleDriver implements Driver {
return Promise.reject(new ConnectionIsNotSetError("oracle"));
const databaseConnection = await this.retrieveDatabaseConnection();
return new OracleQueryRunner(databaseConnection, this, this.logger);
return new OracleQueryRunner(this.connection, databaseConnection);
}
/**

View File

@ -3,8 +3,6 @@ import {DatabaseConnection} from "../DatabaseConnection";
import {ObjectLiteral} from "../../common/ObjectLiteral";
import {TransactionAlreadyStartedError} from "../error/TransactionAlreadyStartedError";
import {TransactionNotStartedError} from "../error/TransactionNotStartedError";
import {Logger} from "../../logger/Logger";
import {OracleDriver} from "./OracleDriver";
import {DataTypeNotSupportedByDriverError} from "../error/DataTypeNotSupportedByDriverError";
import {ColumnSchema} from "../../schema-builder/schema/ColumnSchema";
import {ColumnMetadata} from "../../metadata/ColumnMetadata";
@ -14,6 +12,8 @@ import {PrimaryKeySchema} from "../../schema-builder/schema/PrimaryKeySchema";
import {IndexSchema} from "../../schema-builder/schema/IndexSchema";
import {QueryRunnerAlreadyReleasedError} from "../../query-runner/error/QueryRunnerAlreadyReleasedError";
import {ColumnType} from "../../metadata/types/ColumnTypes";
import {Connection} from "../../connection/Connection";
import {OracleDriver} from "./OracleDriver";
/**
* Runs queries on a single mysql database connection.
@ -36,9 +36,8 @@ export class OracleQueryRunner implements QueryRunner {
// Constructor
// -------------------------------------------------------------------------
constructor(protected databaseConnection: DatabaseConnection,
protected driver: OracleDriver,
protected logger: Logger) {
constructor(protected connection: Connection,
protected databaseConnection: DatabaseConnection) {
}
// -------------------------------------------------------------------------
@ -146,11 +145,11 @@ export class OracleQueryRunner implements QueryRunner {
throw new QueryRunnerAlreadyReleasedError();
return new Promise((ok, fail) => {
this.logger.logQuery(query, parameters);
this.connection.logger.logQuery(query, parameters);
const handler = (err: any, result: any) => {
if (err) {
this.logger.logFailedQuery(query, parameters);
this.logger.logQueryError(err);
this.connection.logger.logFailedQuery(query, parameters);
this.connection.logger.logQueryError(err);
return fail(err);
}
@ -171,18 +170,19 @@ export class OracleQueryRunner implements QueryRunner {
throw new QueryRunnerAlreadyReleasedError();
const keys = Object.keys(keyValues);
const columns = keys.map(key => this.driver.escapeColumnName(key)).join(", ");
const columns = keys.map(key => this.connection.driver.escapeColumnName(key)).join(", ");
const values = keys.map(key => ":" + key).join(", ");
const parameters = keys.map(key => keyValues[key]);
const insertSql = columns.length > 0
? `INSERT INTO ${this.driver.escapeTableName(tableName)}(${columns}) VALUES (${values})`
: `INSERT INTO ${this.driver.escapeTableName(tableName)} DEFAULT VALUES`;
? `INSERT INTO ${this.connection.driver.escapeTableName(tableName)}(${columns}) VALUES (${values})`
: `INSERT INTO ${this.connection.driver.escapeTableName(tableName)} DEFAULT VALUES`;
if (generatedColumn) {
const sql2 = `declare lastId number; begin ${insertSql} returning "id" into lastId; dbms_output.enable; dbms_output.put_line(lastId); dbms_output.get_line(:ln, :st); end;`;
const oracle = (this.connection.driver as OracleDriver).oracle;
const saveResult = await this.query(sql2, parameters.concat([
{ dir: this.driver.oracle.BIND_OUT, type: this.driver.oracle.STRING, maxSize: 32767 },
{ dir: this.driver.oracle.BIND_OUT, type: this.driver.oracle.NUMBER }
{ dir: oracle.BIND_OUT, type: oracle.STRING, maxSize: 32767 },
{ dir: oracle.BIND_OUT, type: oracle.NUMBER }
]));
return parseInt(saveResult[0]);
} else {
@ -199,7 +199,7 @@ export class OracleQueryRunner implements QueryRunner {
const updateValues = this.parametrize(valuesMap).join(", ");
const conditionString = this.parametrize(conditions).join(" AND ");
const sql = `UPDATE ${this.driver.escapeTableName(tableName)} SET ${updateValues} ${conditionString ? (" WHERE " + conditionString) : ""}`;
const sql = `UPDATE ${this.connection.driver.escapeTableName(tableName)} SET ${updateValues} ${conditionString ? (" WHERE " + conditionString) : ""}`;
const conditionParams = Object.keys(conditions).map(key => conditions[key]);
const updateParams = Object.keys(valuesMap).map(key => valuesMap[key]);
const allParameters = updateParams.concat(conditionParams);
@ -226,7 +226,7 @@ export class OracleQueryRunner implements QueryRunner {
const conditionString = typeof conditions === "string" ? conditions : this.parametrize(conditions).join(" AND ");
const parameters = conditions instanceof Object ? Object.keys(conditions).map(key => (conditions as ObjectLiteral)[key]) : maybeParameters;
const sql = `DELETE FROM ${this.driver.escapeTableName(tableName)} WHERE ${conditionString}`;
const sql = `DELETE FROM ${this.connection.driver.escapeTableName(tableName)} WHERE ${conditionString}`;
await this.query(sql, parameters);
}
@ -239,16 +239,16 @@ export class OracleQueryRunner implements QueryRunner {
let sql = "";
if (hasLevel) {
sql = `INSERT INTO ${this.driver.escapeTableName(tableName)}(ancestor, descendant, level) ` +
`SELECT ancestor, ${newEntityId}, level + 1 FROM ${this.driver.escapeTableName(tableName)} WHERE descendant = ${parentId} ` +
sql = `INSERT INTO ${this.connection.driver.escapeTableName(tableName)}(ancestor, descendant, level) ` +
`SELECT ancestor, ${newEntityId}, level + 1 FROM ${this.connection.driver.escapeTableName(tableName)} WHERE descendant = ${parentId} ` +
`UNION ALL SELECT ${newEntityId}, ${newEntityId}, 1`;
} else {
sql = `INSERT INTO ${this.driver.escapeTableName(tableName)}(ancestor, descendant) ` +
`SELECT ancestor, ${newEntityId} FROM ${this.driver.escapeTableName(tableName)} WHERE descendant = ${parentId} ` +
sql = `INSERT INTO ${this.connection.driver.escapeTableName(tableName)}(ancestor, descendant) ` +
`SELECT ancestor, ${newEntityId} FROM ${this.connection.driver.escapeTableName(tableName)} WHERE descendant = ${parentId} ` +
`UNION ALL SELECT ${newEntityId}, ${newEntityId}`;
}
await this.query(sql);
const results: ObjectLiteral[] = await this.query(`SELECT MAX(level) as level FROM ${this.driver.escapeTableName(tableName)} WHERE descendant = ${parentId}`);
const results: ObjectLiteral[] = await this.query(`SELECT MAX(level) as level FROM ${this.connection.driver.escapeTableName(tableName)} WHERE descendant = ${parentId}`);
return results && results[0] && results[0]["level"] ? parseInt(results[0]["level"]) + 1 : 1;
}
@ -836,7 +836,7 @@ AND cons.constraint_name = cols.constraint_name AND cons.owner = cols.owner ORDE
* Truncates table.
*/
async truncate(tableName: string): Promise<void> {
await this.query(`TRUNCATE TABLE ${this.driver.escapeTableName(tableName)}`);
await this.query(`TRUNCATE TABLE ${this.connection.driver.escapeTableName(tableName)}`);
}
// -------------------------------------------------------------------------
@ -847,14 +847,14 @@ AND cons.constraint_name = cols.constraint_name AND cons.owner = cols.owner ORDE
* Database name shortcut.
*/
protected get dbName(): string {
return this.driver.options.schemaName as string;
return this.connection.options.schemaName as string;
}
/**
* Parametrizes given object of values. Used to create column=value queries.
*/
protected parametrize(objectLiteral: ObjectLiteral): string[] {
return Object.keys(objectLiteral).map(key => this.driver.escapeColumnName(key) + "=:" + key);
return Object.keys(objectLiteral).map(key => this.connection.driver.escapeColumnName(key) + "=:" + key);
}
/**

View File

@ -15,6 +15,7 @@ import {DataTransformationUtils} from "../../util/DataTransformationUtils";
import {PlatformTools} from "../../platform/PlatformTools";
import {NamingStrategyInterface} from "../../naming-strategy/NamingStrategyInterface";
import {LazyRelationsWrapper} from "../../lazy-loading/LazyRelationsWrapper";
import {Connection} from "../../connection/Connection";
// todo(tests):
// check connection with url
@ -26,25 +27,6 @@ import {LazyRelationsWrapper} from "../../lazy-loading/LazyRelationsWrapper";
*/
export class PostgresDriver implements Driver {
// -------------------------------------------------------------------------
// Public Properties
// -------------------------------------------------------------------------
/**
* Naming strategy used in the connection where this driver is used.
*/
namingStrategy: NamingStrategyInterface;
/**
* Used to wrap lazy relations to be able to perform lazy loadings.
*/
lazyRelationsWrapper: LazyRelationsWrapper;
/**
* Driver connection options.
*/
readonly options: DriverOptions;
// -------------------------------------------------------------------------
// Protected Properties
// -------------------------------------------------------------------------
@ -79,30 +61,26 @@ export class PostgresDriver implements Driver {
* default: "public"
*/
public schemaName?: string;
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
constructor(connectionOptions: DriverOptions, logger: Logger, postgres?: any) {
constructor(protected connection: Connection) {
this.options = DriverUtils.buildDriverOptions(connectionOptions);
this.logger = logger;
this.postgres = postgres;
this.schemaName = connectionOptions.schemaName || "public";
Object.assign(connection.options, DriverUtils.buildDriverOptions(connection.options)); // todo: do it better way
this.schemaName = connection.options.schemaName || "public";
// validate options to make sure everything is set
if (!this.options.host)
if (!connection.options.host)
throw new DriverOptionNotSetError("host");
if (!this.options.username)
if (!connection.options.username)
throw new DriverOptionNotSetError("username");
if (!this.options.database)
if (!connection.options.database)
throw new DriverOptionNotSetError("database");
// if postgres package instance was not set explicitly then try to load it
if (!postgres)
this.loadDependencies();
// load postgres package
this.loadDependencies();
}
// -------------------------------------------------------------------------
@ -118,16 +96,16 @@ export class PostgresDriver implements Driver {
// build connection options for the driver
const options = Object.assign({}, {
host: this.options.host,
user: this.options.username,
password: this.options.password,
database: this.options.database,
port: this.options.port
}, this.options.extra || {});
host: this.connection.options.host,
user: this.connection.options.username,
password: this.connection.options.password,
database: this.connection.options.database,
port: this.connection.options.port
}, this.connection.options.extra || {});
// pooling is enabled either when its set explicitly to true,
// either when its not defined at all (e.g. enabled by default)
if (this.options.usePool === undefined || this.options.usePool === true) {
if (this.connection.options.usePool === undefined || this.connection.options.usePool === true) {
this.pool = new this.postgres.Pool(options);
return Promise.resolve();
@ -144,8 +122,8 @@ export class PostgresDriver implements Driver {
} else {
this.databaseConnection!.connection.query(`SET search_path TO '${this.schemaName}', 'public';`, (err: any, result: any) => {
if (err) {
this.logger.logFailedQuery(`SET search_path TO '${this.schemaName}', 'public';`);
this.logger.logQueryError(err);
this.connection.logger.logFailedQuery(`SET search_path TO '${this.schemaName}', 'public';`);
this.connection.logger.logQueryError(err);
fail(err);
} else {
ok();
@ -195,7 +173,7 @@ export class PostgresDriver implements Driver {
return Promise.reject(new ConnectionIsNotSetError("postgres"));
const databaseConnection = await this.retrieveDatabaseConnection();
return new PostgresQueryRunner(databaseConnection, this, this.logger);
return new PostgresQueryRunner(this.connection, databaseConnection);
}
/**
@ -356,8 +334,8 @@ export class PostgresDriver implements Driver {
};
dbConnection.connection.query(`SET search_path TO '${this.schemaName}', 'public';`, (err: any) => {
if (err) {
this.logger.logFailedQuery(`SET search_path TO '${this.schemaName}', 'public';`);
this.logger.logQueryError(err);
this.connection.logger.logFailedQuery(`SET search_path TO '${this.schemaName}', 'public';`);
this.connection.logger.logQueryError(err);
fail(err);
} else {
ok(dbConnection);

View File

@ -14,6 +14,7 @@ import {ForeignKeySchema} from "../../schema-builder/schema/ForeignKeySchema";
import {PrimaryKeySchema} from "../../schema-builder/schema/PrimaryKeySchema";
import {QueryRunnerAlreadyReleasedError} from "../../query-runner/error/QueryRunnerAlreadyReleasedError";
import {ColumnType} from "../../metadata/types/ColumnTypes";
import {Connection} from "../../connection/Connection";
/**
* Runs queries on a single postgres database connection.
@ -36,10 +37,9 @@ export class PostgresQueryRunner implements QueryRunner {
// Constructor
// -------------------------------------------------------------------------
constructor(protected databaseConnection: DatabaseConnection,
protected driver: PostgresDriver,
protected logger: Logger) {
this.schemaName = driver.schemaName || "public";
constructor(protected connection: Connection,
protected databaseConnection: DatabaseConnection) {
this.schemaName = connection.options.schemaName || "public";
}
// -------------------------------------------------------------------------
@ -139,11 +139,11 @@ export class PostgresQueryRunner implements QueryRunner {
// console.log("query: ", query);
// console.log("parameters: ", parameters);
return new Promise<any[]>((ok, fail) => {
this.logger.logQuery(query, parameters);
this.connection.logger.logQuery(query, parameters);
this.databaseConnection.connection.query(query, parameters, (err: any, result: any) => {
if (err) {
this.logger.logFailedQuery(query, parameters);
this.logger.logQueryError(err);
this.connection.logger.logFailedQuery(query, parameters);
this.connection.logger.logQueryError(err);
fail(err);
} else {
ok(result.rows);
@ -160,11 +160,11 @@ export class PostgresQueryRunner implements QueryRunner {
throw new QueryRunnerAlreadyReleasedError();
const keys = Object.keys(keyValues);
const columns = keys.map(key => this.driver.escapeColumnName(key)).join(", ");
const columns = keys.map(key => this.connection.driver.escapeColumnName(key)).join(", ");
const values = keys.map((key, index) => "$" + (index + 1)).join(",");
const sql = columns.length > 0
? `INSERT INTO ${this.driver.escapeTableName(tableName)}(${columns}) VALUES (${values}) ${ generatedColumn ? " RETURNING " + this.driver.escapeColumnName(generatedColumn.databaseName) : "" }`
: `INSERT INTO ${this.driver.escapeTableName(tableName)} DEFAULT VALUES ${ generatedColumn ? " RETURNING " + this.driver.escapeColumnName(generatedColumn.databaseName) : "" }`;
? `INSERT INTO ${this.connection.driver.escapeTableName(tableName)}(${columns}) VALUES (${values}) ${ generatedColumn ? " RETURNING " + this.connection.driver.escapeColumnName(generatedColumn.databaseName) : "" }`
: `INSERT INTO ${this.connection.driver.escapeTableName(tableName)} DEFAULT VALUES ${ generatedColumn ? " RETURNING " + this.connection.driver.escapeColumnName(generatedColumn.databaseName) : "" }`;
const parameters = keys.map(key => keyValues[key]);
const result: ObjectLiteral[] = await this.query(sql, parameters);
if (generatedColumn)
@ -182,7 +182,7 @@ export class PostgresQueryRunner implements QueryRunner {
const updateValues = this.parametrize(valuesMap).join(", ");
const conditionString = this.parametrize(conditions, Object.keys(valuesMap).length).join(" AND ");
const query = `UPDATE ${this.driver.escapeTableName(tableName)} SET ${updateValues} ${conditionString ? (" WHERE " + conditionString) : ""}`;
const query = `UPDATE ${this.connection.driver.escapeTableName(tableName)} SET ${updateValues} ${conditionString ? (" WHERE " + conditionString) : ""}`;
const updateParams = Object.keys(valuesMap).map(key => valuesMap[key]);
const conditionParams = Object.keys(conditions).map(key => conditions[key]);
const allParameters = updateParams.concat(conditionParams);
@ -209,7 +209,7 @@ export class PostgresQueryRunner implements QueryRunner {
const conditionString = typeof conditions === "string" ? conditions : this.parametrize(conditions).join(" AND ");
const parameters = conditions instanceof Object ? Object.keys(conditions).map(key => (conditions as ObjectLiteral)[key]) : maybeParameters;
const sql = `DELETE FROM ${this.driver.escapeTableName(tableName)} WHERE ${conditionString}`;
const sql = `DELETE FROM ${this.connection.driver.escapeTableName(tableName)} WHERE ${conditionString}`;
await this.query(sql, parameters);
}
@ -222,12 +222,12 @@ export class PostgresQueryRunner implements QueryRunner {
let sql = "";
if (hasLevel) {
sql = `INSERT INTO ${this.driver.escapeTableName(tableName)}(ancestor, descendant, level) ` +
`SELECT ancestor, ${newEntityId}, level + 1 FROM ${this.driver.escapeTableName(tableName)} WHERE descendant = ${parentId} ` +
sql = `INSERT INTO ${this.connection.driver.escapeTableName(tableName)}(ancestor, descendant, level) ` +
`SELECT ancestor, ${newEntityId}, level + 1 FROM ${this.connection.driver.escapeTableName(tableName)} WHERE descendant = ${parentId} ` +
`UNION ALL SELECT ${newEntityId}, ${newEntityId}, 1`;
} else {
sql = `INSERT INTO ${this.driver.escapeTableName(tableName)}(ancestor, descendant) ` +
`SELECT ancestor, ${newEntityId} FROM ${this.driver.escapeTableName(tableName)} WHERE descendant = ${parentId} ` +
sql = `INSERT INTO ${this.connection.driver.escapeTableName(tableName)}(ancestor, descendant) ` +
`SELECT ancestor, ${newEntityId} FROM ${this.connection.driver.escapeTableName(tableName)} WHERE descendant = ${parentId} ` +
`UNION ALL SELECT ${newEntityId}, ${newEntityId}`;
}
await this.query(sql);
@ -837,7 +837,7 @@ where constraint_type = 'PRIMARY KEY' AND c.table_schema = '${this.schemaName}'
* Truncates table.
*/
async truncate(tableName: string): Promise<void> {
await this.query(`TRUNCATE TABLE ${this.driver.escapeTableName(tableName)}`);
await this.query(`TRUNCATE TABLE ${this.connection.driver.escapeTableName(tableName)}`);
}
// -------------------------------------------------------------------------
@ -848,14 +848,14 @@ where constraint_type = 'PRIMARY KEY' AND c.table_schema = '${this.schemaName}'
* Database name shortcut.
*/
protected get dbName(): string {
return this.driver.options.database as string;
return this.connection.options.database as string;
}
/**
* Parametrizes given object of values. Used to create column=value queries.
*/
protected parametrize(objectLiteral: ObjectLiteral, startIndex: number = 0): string[] {
return Object.keys(objectLiteral).map((key, index) => this.driver.escapeColumnName(key) + "=$" + (startIndex + index + 1));
return Object.keys(objectLiteral).map((key, index) => this.connection.driver.escapeColumnName(key) + "=$" + (startIndex + index + 1));
}
/**

View File

@ -14,31 +14,13 @@ import {DataTransformationUtils} from "../../util/DataTransformationUtils";
import {PlatformTools} from "../../platform/PlatformTools";
import {NamingStrategyInterface} from "../../naming-strategy/NamingStrategyInterface";
import {LazyRelationsWrapper} from "../../lazy-loading/LazyRelationsWrapper";
import {Connection} from "../../connection/Connection";
/**
* Organizes communication with sqlite DBMS.
*/
export class SqliteDriver implements Driver {
// -------------------------------------------------------------------------
// Public Properties
// -------------------------------------------------------------------------
/**
* Naming strategy used in the connection where this driver is used.
*/
namingStrategy: NamingStrategyInterface;
/**
* Used to wrap lazy relations to be able to perform lazy loadings.
*/
lazyRelationsWrapper: LazyRelationsWrapper;
/**
* Driver connection options.
*/
readonly options: DriverOptions;
// -------------------------------------------------------------------------
// Protected Properties
// -------------------------------------------------------------------------
@ -53,28 +35,18 @@ export class SqliteDriver implements Driver {
*/
protected databaseConnection: DatabaseConnection|undefined;
/**
* Logger used to log queries and errors.
*/
protected logger: Logger;
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
constructor(connectionOptions: DriverOptions, logger: Logger, sqlite?: any) {
this.options = connectionOptions;
this.logger = logger;
this.sqlite = sqlite;
constructor(protected connection: Connection) {
// validate options to make sure everything is set
if (!this.options.storage)
if (!connection.options.storage)
throw new DriverOptionNotSetError("storage");
// if sqlite package instance was not set explicitly then try to load it
if (!sqlite)
this.loadDependencies();
// load sqlite package
this.loadDependencies();
}
// -------------------------------------------------------------------------
@ -86,7 +58,7 @@ export class SqliteDriver implements Driver {
*/
connect(): Promise<void> {
return new Promise<void>((ok, fail) => {
const connection = new this.sqlite.Database(this.options.storage, (err: any) => {
const connection = new this.sqlite.Database(this.connection.options.storage, (err: any) => {
if (err)
return fail(err);
@ -126,7 +98,7 @@ export class SqliteDriver implements Driver {
return Promise.reject(new ConnectionIsNotSetError("sqlite"));
const databaseConnection = await this.retrieveDatabaseConnection();
return new SqliteQueryRunner(databaseConnection, this, this.logger);
return new SqliteQueryRunner(this.connection, databaseConnection);
}
/**

View File

@ -14,6 +14,8 @@ import {ForeignKeySchema} from "../../schema-builder/schema/ForeignKeySchema";
import {PrimaryKeySchema} from "../../schema-builder/schema/PrimaryKeySchema";
import {QueryRunnerAlreadyReleasedError} from "../../query-runner/error/QueryRunnerAlreadyReleasedError";
import {ColumnType} from "../../metadata/types/ColumnTypes";
import {RandomGenerator} from "../../util/RandomGenerator";
import {Connection} from "../../connection/Connection";
/**
* Runs queries on a single sqlite database connection.
@ -37,9 +39,8 @@ export class SqliteQueryRunner implements QueryRunner {
// Constructor
// -------------------------------------------------------------------------
constructor(protected databaseConnection: DatabaseConnection,
protected driver: SqliteDriver,
protected logger: Logger) {
constructor(protected connection: Connection,
protected databaseConnection: DatabaseConnection) {
}
// -------------------------------------------------------------------------
@ -138,11 +139,11 @@ export class SqliteQueryRunner implements QueryRunner {
throw new QueryRunnerAlreadyReleasedError();
return new Promise<any[]>((ok, fail) => {
this.logger.logQuery(query, parameters);
this.connection.logger.logQuery(query, parameters);
this.databaseConnection.connection.all(query, parameters, (err: any, result: any) => {
if (err) {
this.logger.logFailedQuery(query, parameters);
this.logger.logQueryError(err);
this.connection.logger.logFailedQuery(query, parameters);
this.connection.logger.logQueryError(err);
fail(err);
} else {
ok(result);
@ -159,18 +160,18 @@ export class SqliteQueryRunner implements QueryRunner {
throw new QueryRunnerAlreadyReleasedError();
const keys = Object.keys(keyValues);
const columns = keys.map(key => this.driver.escapeColumnName(key)).join(", ");
const columns = keys.map(key => this.connection.driver.escapeColumnName(key)).join(", ");
const values = keys.map((key, index) => "$" + (index + 1)).join(",");
const sql = columns.length > 0 ? (`INSERT INTO ${this.driver.escapeTableName(tableName)}(${columns}) VALUES (${values})`) : `INSERT INTO ${this.driver.escapeTableName(tableName)} DEFAULT VALUES`;
const sql = columns.length > 0 ? (`INSERT INTO ${this.connection.driver.escapeTableName(tableName)}(${columns}) VALUES (${values})`) : `INSERT INTO ${this.connection.driver.escapeTableName(tableName)} DEFAULT VALUES`;
const parameters = keys.map(key => keyValues[key]);
return new Promise<any[]>((ok, fail) => {
this.logger.logQuery(sql, parameters);
this.connection.logger.logQuery(sql, parameters);
const __this = this;
this.databaseConnection.connection.run(sql, parameters, function (err: any): void {
if (err) {
__this.logger.logFailedQuery(sql, parameters);
__this.logger.logQueryError(err);
__this.connection.logger.logFailedQuery(sql, parameters);
__this.connection.logger.logQueryError(err);
fail(err);
} else {
if (generatedColumn)
@ -191,7 +192,7 @@ export class SqliteQueryRunner implements QueryRunner {
const updateValues = this.parametrize(valuesMap).join(", ");
const conditionString = this.parametrize(conditions, Object.keys(valuesMap).length).join(" AND ");
const query = `UPDATE ${this.driver.escapeTableName(tableName)} SET ${updateValues} ${conditionString ? (" WHERE " + conditionString) : ""}`;
const query = `UPDATE ${this.connection.driver.escapeTableName(tableName)} SET ${updateValues} ${conditionString ? (" WHERE " + conditionString) : ""}`;
const updateParams = Object.keys(valuesMap).map(key => valuesMap[key]);
const conditionParams = Object.keys(conditions).map(key => conditions[key]);
const allParameters = updateParams.concat(conditionParams);
@ -218,7 +219,7 @@ export class SqliteQueryRunner implements QueryRunner {
const conditionString = typeof conditions === "string" ? conditions : this.parametrize(conditions).join(" AND ");
const parameters = conditions instanceof Object ? Object.keys(conditions).map(key => (conditions as ObjectLiteral)[key]) : maybeParameters;
const sql = `DELETE FROM ${this.driver.escapeTableName(tableName)} WHERE ${conditionString}`;
const sql = `DELETE FROM ${this.connection.driver.escapeTableName(tableName)} WHERE ${conditionString}`;
await this.query(sql, parameters);
}
@ -231,12 +232,12 @@ export class SqliteQueryRunner implements QueryRunner {
let sql = "";
if (hasLevel) {
sql = `INSERT INTO ${this.driver.escapeTableName(tableName)}(ancestor, descendant, level) ` +
`SELECT ancestor, ${newEntityId}, level + 1 FROM ${this.driver.escapeTableName(tableName)} WHERE descendant = ${parentId} ` +
sql = `INSERT INTO ${this.connection.driver.escapeTableName(tableName)}(ancestor, descendant, level) ` +
`SELECT ancestor, ${newEntityId}, level + 1 FROM ${this.connection.driver.escapeTableName(tableName)} WHERE descendant = ${parentId} ` +
`UNION ALL SELECT ${newEntityId}, ${newEntityId}, 1`;
} else {
sql = `INSERT INTO ${this.driver.escapeTableName(tableName)}(ancestor, descendant) ` +
`SELECT ancestor, ${newEntityId} FROM ${this.driver.escapeTableName(tableName)} WHERE descendant = ${parentId} ` +
sql = `INSERT INTO ${this.connection.driver.escapeTableName(tableName)}(ancestor, descendant) ` +
`SELECT ancestor, ${newEntityId} FROM ${this.connection.driver.escapeTableName(tableName)} WHERE descendant = ${parentId} ` +
`UNION ALL SELECT ${newEntityId}, ${newEntityId}`;
}
await this.query(sql);
@ -316,7 +317,10 @@ export class SqliteQueryRunner implements QueryRunner {
const columnForeignKeys = dbForeignKeys
.filter(foreignKey => foreignKey["from"] === dbColumn["name"])
.map(foreignKey => {
const keyName = this.driver.namingStrategy.foreignKeyName(dbTable["name"], [foreignKey["from"]], foreignKey["table"], [foreignKey["to"]]);
// const keyName = this.connection.driver.namingStrategy.foreignKeyName(dbTable["name"], [foreignKey["from"]], foreignKey["table"], [foreignKey["to"]]);
// todo: figure out solution here, name should be same as naming strategy generates!
const key = `${dbTable["name"]}_${[foreignKey["from"]].join("_")}_${foreignKey["table"]}_${[foreignKey["to"]].join("_")}`;
const keyName = "fk_" + RandomGenerator.sha1(key).substr(0, 27);
return new ForeignKeySchema(keyName, [foreignKey["from"]], [foreignKey["to"]], foreignKey["table"], foreignKey["on_delete"]); // todo: how sqlite return from and to when they are arrays? (multiple column foreign keys)
});
tableSchema.addForeignKeys(columnForeignKeys);
@ -811,7 +815,7 @@ export class SqliteQueryRunner implements QueryRunner {
* Truncates table.
*/
async truncate(tableName: string): Promise<void> {
await this.query(`DELETE FROM ${this.driver.escapeTableName(tableName)}`);
await this.query(`DELETE FROM ${this.connection.driver.escapeTableName(tableName)}`);
}
// -------------------------------------------------------------------------
@ -822,7 +826,7 @@ export class SqliteQueryRunner implements QueryRunner {
* Parametrizes given object of values. Used to create column=value queries.
*/
protected parametrize(objectLiteral: ObjectLiteral, startIndex: number = 0): string[] {
return Object.keys(objectLiteral).map((key, index) => this.driver.escapeColumnName(key) + "=$" + (startIndex + index + 1));
return Object.keys(objectLiteral).map((key, index) => this.connection.driver.escapeColumnName(key) + "=$" + (startIndex + index + 1));
}
/**

View File

@ -15,6 +15,7 @@ import {DataTransformationUtils} from "../../util/DataTransformationUtils";
import {PlatformTools} from "../../platform/PlatformTools";
import {NamingStrategyInterface} from "../../naming-strategy/NamingStrategyInterface";
import {LazyRelationsWrapper} from "../../lazy-loading/LazyRelationsWrapper";
import {Connection} from "../../connection/Connection";
/**
* Organizes communication with SQL Server DBMS.
@ -25,21 +26,6 @@ export class SqlServerDriver implements Driver {
// Public Properties
// -------------------------------------------------------------------------
/**
* Naming strategy used in the connection where this driver is used.
*/
namingStrategy: NamingStrategyInterface;
/**
* Used to wrap lazy relations to be able to perform lazy loadings.
*/
lazyRelationsWrapper: LazyRelationsWrapper;
/**
* Driver connection options.
*/
readonly options: DriverOptions;
/**
* SQL Server library.
*/
@ -57,7 +43,7 @@ export class SqlServerDriver implements Driver {
/**
* SQL Server pool.
*/
protected connection: any;
protected connectionPool: any;
/**
* Pool of database connections.
@ -73,23 +59,20 @@ export class SqlServerDriver implements Driver {
// Constructor
// -------------------------------------------------------------------------
constructor(options: DriverOptions, logger: Logger, mssql?: any) {
constructor(protected connection: Connection) {
this.options = DriverUtils.buildDriverOptions(options);
this.logger = logger;
this.mssql = mssql;
Object.assign(connection.options, DriverUtils.buildDriverOptions(connection.options)); // todo: do it better way
// validate options to make sure everything is set
if (!this.options.host)
if (!connection.options.host)
throw new DriverOptionNotSetError("host");
if (!this.options.username)
if (!connection.options.username)
throw new DriverOptionNotSetError("username");
if (!this.options.database)
if (!connection.options.database)
throw new DriverOptionNotSetError("database");
// if mssql package instance was not set explicitly then try to load it
if (!mssql)
this.loadDependencies();
// load mssql package
this.loadDependencies();
}
// -------------------------------------------------------------------------
@ -105,12 +88,12 @@ export class SqlServerDriver implements Driver {
// build connection options for the driver
const options = Object.assign({}, {
server: this.options.host,
user: this.options.username,
password: this.options.password,
database: this.options.database,
port: this.options.port
}, this.options.extra || {});
server: this.connection.options.host,
user: this.connection.options.username,
password: this.connection.options.password,
database: this.connection.options.database,
port: this.connection.options.port
}, this.connection.options.extra || {});
// set default useUTC option if it hasn't been set
if (!options.options) options.options = { useUTC: false };
@ -121,8 +104,8 @@ export class SqlServerDriver implements Driver {
return new Promise<void>((ok, fail) => {
const connection = new this.mssql.Connection(options).connect((err: any) => {
if (err) return fail(err);
this.connection = connection;
if (this.options.usePool === false) {
this.connectionPool = connection;
if (this.connection.options.usePool === false) {
this.databaseConnection = {
id: 1,
connection: new this.mssql.Request(connection),
@ -138,11 +121,11 @@ export class SqlServerDriver implements Driver {
* Closes connection with the database.
*/
async disconnect(): Promise<void> {
if (!this.connection)
if (!this.connectionPool)
throw new ConnectionIsNotSetError("mssql");
this.connection.close();
this.connection = undefined;
this.connectionPool.close();
this.connectionPool = undefined;
this.databaseConnection = undefined;
this.databaseConnectionPool = [];
}
@ -151,11 +134,11 @@ export class SqlServerDriver implements Driver {
* Creates a query runner used for common queries.
*/
async createQueryRunner(): Promise<QueryRunner> {
if (!this.connection)
if (!this.connectionPool)
return Promise.reject(new ConnectionIsNotSetError("mssql"));
const databaseConnection = await this.retrieveDatabaseConnection();
return new SqlServerQueryRunner(databaseConnection, this, this.logger);
return new SqlServerQueryRunner(this.connection, databaseConnection);
}
/**
@ -165,7 +148,7 @@ export class SqlServerDriver implements Driver {
return {
driver: this.mssql,
connection: this.databaseConnection ? this.databaseConnection.connection : undefined,
pool: this.connection
pool: this.connectionPool
};
}
@ -286,7 +269,7 @@ export class SqlServerDriver implements Driver {
*/
protected retrieveDatabaseConnection(): Promise<DatabaseConnection> {
if (!this.connection)
if (!this.connectionPool)
throw new ConnectionIsNotSetError("mssql");
return new Promise((ok, fail) => {
@ -309,7 +292,7 @@ export class SqlServerDriver implements Driver {
// if (!dbConnection) {
let dbConnection: DatabaseConnection = {
id: this.databaseConnectionPool.length,
connection: this.connection,
connection: this.connectionPool,
isTransactionActive: false
};
dbConnection.releaseCallback = () => {

View File

@ -14,6 +14,8 @@ import {PrimaryKeySchema} from "../../schema-builder/schema/PrimaryKeySchema";
import {IndexSchema} from "../../schema-builder/schema/IndexSchema";
import {QueryRunnerAlreadyReleasedError} from "../../query-runner/error/QueryRunnerAlreadyReleasedError";
import {ColumnType} from "../../metadata/types/ColumnTypes";
import {Connection} from "../../connection/Connection";
import {MysqlDriver} from "../mysql/MysqlDriver";
/**
* Runs queries on a single mysql database connection.
@ -34,11 +36,9 @@ export class SqlServerQueryRunner implements QueryRunner {
// Constructor
// -------------------------------------------------------------------------
constructor(protected databaseConnection: DatabaseConnection,
protected driver: SqlServerDriver,
protected logger: Logger) {
constructor(protected connection: Connection,
protected databaseConnection: DatabaseConnection) {
}
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
@ -98,12 +98,12 @@ export class SqlServerQueryRunner implements QueryRunner {
//
// return new Promise<void>((ok, fail) => {
//
// const request = new this.driver.mssql.Request(this.isTransactionActive() ? this.databaseConnection.transaction : this.databaseConnection.connection);
// const request = new this.connection.driver.mssql.Request(this.isTransactionActive() ? this.databaseConnection.transaction : this.databaseConnection.connection);
// request.multiple = true;
// request.query(allQueries, (err: any, result: any) => {
// if (err) {
// this.logger.logFailedQuery(allQueries);
// this.logger.logQueryError(err);
// this.connection.logger.logFailedQuery(allQueries);
// this.connection.logger.logQueryError(err);
// return fail(err);
// }
//
@ -195,8 +195,8 @@ export class SqlServerQueryRunner implements QueryRunner {
return new Promise((ok, fail) => {
this.logger.logQuery(query, parameters);
const request = new this.driver.mssql.Request(this.isTransactionActive() ? this.databaseConnection.transaction : this.databaseConnection.connection);
this.connection.logger.logQuery(query, parameters);
const request = new (this.connection.driver as SqlServerDriver).mssql.Request(this.isTransactionActive() ? this.databaseConnection.transaction : this.databaseConnection.connection);
if (parameters && parameters.length) {
parameters.forEach((parameter, index) => {
request.input(index, parameters![index]);
@ -204,8 +204,8 @@ export class SqlServerQueryRunner implements QueryRunner {
}
request.query(query, (err: any, result: any) => {
if (err) {
this.logger.logFailedQuery(query, parameters);
this.logger.logQueryError(err);
this.connection.logger.logFailedQuery(query, parameters);
this.connection.logger.logQueryError(err);
return fail(err);
}
@ -222,13 +222,13 @@ export class SqlServerQueryRunner implements QueryRunner {
throw new QueryRunnerAlreadyReleasedError();
const keys = Object.keys(keyValues);
const columns = keys.map(key => this.driver.escapeColumnName(key)).join(", ");
const columns = keys.map(key => this.connection.driver.escapeColumnName(key)).join(", ");
const values = keys.map((key, index) => "@" + index).join(",");
const parameters = keys.map(key => keyValues[key]);
const sql = columns.length > 0
? `INSERT INTO ${this.driver.escapeTableName(tableName)}(${columns}) ${ generatedColumn ? "OUTPUT INSERTED." + generatedColumn.databaseName + " " : "" }VALUES (${values})`
: `INSERT INTO ${this.driver.escapeTableName(tableName)} ${ generatedColumn ? "OUTPUT INSERTED." + generatedColumn.databaseName + " " : "" }DEFAULT VALUES `;
? `INSERT INTO ${this.connection.driver.escapeTableName(tableName)}(${columns}) ${ generatedColumn ? "OUTPUT INSERTED." + generatedColumn.databaseName + " " : "" }VALUES (${values})`
: `INSERT INTO ${this.connection.driver.escapeTableName(tableName)} ${ generatedColumn ? "OUTPUT INSERTED." + generatedColumn.databaseName + " " : "" }DEFAULT VALUES `;
const result = await this.query(sql, parameters);
return generatedColumn ? result instanceof Array ? result[0][generatedColumn.databaseName] : result[generatedColumn.databaseName] : undefined;
@ -247,7 +247,7 @@ export class SqlServerQueryRunner implements QueryRunner {
const updateValues = this.parametrize(valuesMap).join(", ");
const conditionString = this.parametrize(conditions, updateParams.length).join(" AND ");
const sql = `UPDATE ${this.driver.escapeTableName(tableName)} SET ${updateValues} ${conditionString ? (" WHERE " + conditionString) : ""}`;
const sql = `UPDATE ${this.connection.driver.escapeTableName(tableName)} SET ${updateValues} ${conditionString ? (" WHERE " + conditionString) : ""}`;
await this.query(sql, allParameters);
}
@ -272,7 +272,7 @@ export class SqlServerQueryRunner implements QueryRunner {
const conditionString = typeof conditions === "string" ? conditions : this.parametrize(conditions).join(" AND ");
const parameters = conditions instanceof Object ? Object.keys(conditions).map(key => (conditions as ObjectLiteral)[key]) : maybeParameters;
const sql = `DELETE FROM ${this.driver.escapeTableName(tableName)} WHERE ${conditionString}`;
const sql = `DELETE FROM ${this.connection.driver.escapeTableName(tableName)} WHERE ${conditionString}`;
await this.query(sql, parameters);
}
@ -285,16 +285,16 @@ export class SqlServerQueryRunner implements QueryRunner {
let sql = "";
if (hasLevel) {
sql = `INSERT INTO ${this.driver.escapeTableName(tableName)}(ancestor, descendant, level) ` +
`SELECT ancestor, ${newEntityId}, level + 1 FROM ${this.driver.escapeTableName(tableName)} WHERE descendant = ${parentId} ` +
sql = `INSERT INTO ${this.connection.driver.escapeTableName(tableName)}(ancestor, descendant, level) ` +
`SELECT ancestor, ${newEntityId}, level + 1 FROM ${this.connection.driver.escapeTableName(tableName)} WHERE descendant = ${parentId} ` +
`UNION ALL SELECT ${newEntityId}, ${newEntityId}, 1`;
} else {
sql = `INSERT INTO ${this.driver.escapeTableName(tableName)}(ancestor, descendant) ` +
`SELECT ancestor, ${newEntityId} FROM ${this.driver.escapeTableName(tableName)} WHERE descendant = ${parentId} ` +
sql = `INSERT INTO ${this.connection.driver.escapeTableName(tableName)}(ancestor, descendant) ` +
`SELECT ancestor, ${newEntityId} FROM ${this.connection.driver.escapeTableName(tableName)} WHERE descendant = ${parentId} ` +
`UNION ALL SELECT ${newEntityId}, ${newEntityId}`;
}
await this.query(sql);
const results: ObjectLiteral[] = await this.query(`SELECT MAX(level) as level FROM ${this.driver.escapeTableName(tableName)} WHERE descendant = ${parentId}`);
const results: ObjectLiteral[] = await this.query(`SELECT MAX(level) as level FROM ${this.connection.driver.escapeTableName(tableName)} WHERE descendant = ${parentId}`);
return results && results[0] && results[0]["level"] ? parseInt(results[0]["level"]) + 1 : 1;
}
@ -870,7 +870,7 @@ WHERE columnUsages.TABLE_CATALOG = '${this.dbName}' AND tableConstraints.TABLE_C
* Truncates table.
*/
async truncate(tableName: string): Promise<void> {
await this.query(`TRUNCATE TABLE ${this.driver.escapeTableName(tableName)}`);
await this.query(`TRUNCATE TABLE ${this.connection.driver.escapeTableName(tableName)}`);
}
// -------------------------------------------------------------------------
@ -881,7 +881,7 @@ WHERE columnUsages.TABLE_CATALOG = '${this.dbName}' AND tableConstraints.TABLE_C
* Database name shortcut.
*/
protected get dbName(): string {
return this.driver.options.database as string;
return this.connection.options.database as string;
}
/**
@ -889,7 +889,7 @@ WHERE columnUsages.TABLE_CATALOG = '${this.dbName}' AND tableConstraints.TABLE_C
*/
protected parametrize(objectLiteral: ObjectLiteral, startFrom: number = 0): string[] {
return Object.keys(objectLiteral).map((key, index) => {
return this.driver.escapeColumnName(key) + "=@" + (startFrom + index);
return this.connection.driver.escapeColumnName(key) + "=@" + (startFrom + index);
});
}

View File

@ -11,8 +11,7 @@ import {ColumnMetadata} from "../../metadata/ColumnMetadata";
import {DriverOptionNotSetError} from "../error/DriverOptionNotSetError";
import {DataTransformationUtils} from "../../util/DataTransformationUtils";
import {WebsqlQueryRunner} from "./WebsqlQueryRunner";
import {NamingStrategyInterface} from "../../naming-strategy/NamingStrategyInterface";
import {LazyRelationsWrapper} from "../../lazy-loading/LazyRelationsWrapper";
import {Connection} from "../../connection/Connection";
/**
* Declare a global function that is only available in browsers that support WebSQL.
@ -24,25 +23,6 @@ declare function openDatabase(...params: any[]): any;
*/
export class WebsqlDriver implements Driver {
// -------------------------------------------------------------------------
// Public Properties
// -------------------------------------------------------------------------
/**
* Naming strategy used in the connection where this driver is used.
*/
namingStrategy: NamingStrategyInterface;
/**
* Used to wrap lazy relations to be able to perform lazy loadings.
*/
lazyRelationsWrapper: LazyRelationsWrapper;
/**
* Driver connection options.
*/
readonly options: DriverOptions;
// -------------------------------------------------------------------------
// Protected Properties
// -------------------------------------------------------------------------
@ -52,26 +32,20 @@ export class WebsqlDriver implements Driver {
*/
protected databaseConnection: DatabaseConnection|undefined;
/**
* Logger used to log queries and errors.
*/
protected logger: Logger;
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
constructor(options: DriverOptions, logger: Logger) {
constructor(protected connection: Connection) {
this.options = DriverUtils.buildDriverOptions(options);
this.logger = logger;
Object.assign(connection.options, DriverUtils.buildDriverOptions(connection.options)); // todo: do it better way
// validate options to make sure everything is set
// if (!this.options.host)
// throw new DriverOptionNotSetError("host");
// if (!this.options.username)
// throw new DriverOptionNotSetError("username");
if (!this.options.database)
if (!connection.options.database)
throw new DriverOptionNotSetError("database");
// todo: what about extra options: version, description, size
}
@ -89,8 +63,8 @@ export class WebsqlDriver implements Driver {
// build connection options for the driver
const options = Object.assign({}, {
database: this.options.database,
}, this.options.extra || {});
database: this.connection.options.database,
}, this.connection.options.extra || {});
return new Promise<void>((ok, fail) => {
const connection = openDatabase(
@ -130,7 +104,7 @@ export class WebsqlDriver implements Driver {
return Promise.reject(new ConnectionIsNotSetError("websql"));
const databaseConnection = await this.retrieveDatabaseConnection();
return new WebsqlQueryRunner(databaseConnection, this, this.logger);
return new WebsqlQueryRunner(this.connection, databaseConnection);
}
/**

View File

@ -13,6 +13,7 @@ import {IndexSchema} from "../../schema-builder/schema/IndexSchema";
import {QueryRunnerAlreadyReleasedError} from "../../query-runner/error/QueryRunnerAlreadyReleasedError";
import {WebsqlDriver} from "./WebsqlDriver";
import {ColumnType} from "../../metadata/types/ColumnTypes";
import {Connection} from "../../connection/Connection";
/**
* Runs queries on a single websql database connection.
@ -33,9 +34,8 @@ export class WebsqlQueryRunner implements QueryRunner {
// Constructor
// -------------------------------------------------------------------------
constructor(protected databaseConnection: DatabaseConnection,
protected driver: WebsqlDriver,
protected logger: Logger) {
constructor(protected connection: Connection,
protected databaseConnection: DatabaseConnection) {
}
// -------------------------------------------------------------------------
@ -139,7 +139,7 @@ export class WebsqlQueryRunner implements QueryRunner {
return new Promise((ok, fail) => {
this.logger.logQuery(query, parameters);
this.connection.logger.logQuery(query, parameters);
const db = this.databaseConnection.connection;
// todo: check if transaction is not active
db.transaction((tx: any) => {
@ -151,8 +151,8 @@ export class WebsqlQueryRunner implements QueryRunner {
ok(rows);
}, (tx: any, err: any) => {
this.logger.logFailedQuery(query, parameters);
this.logger.logQueryError(err);
this.connection.logger.logFailedQuery(query, parameters);
this.connection.logger.logQueryError(err);
return fail(err);
});
});
@ -167,13 +167,13 @@ export class WebsqlQueryRunner implements QueryRunner {
throw new QueryRunnerAlreadyReleasedError();
const keys = Object.keys(keyValues);
const columns = keys.map(key => this.driver.escapeColumnName(key)).join(", ");
const columns = keys.map(key => this.connection.driver.escapeColumnName(key)).join(", ");
const values = keys.map((key, index) => "$" + (index + 1)).join(",");
const sql = columns.length > 0 ? (`INSERT INTO ${this.driver.escapeTableName(tableName)}(${columns}) VALUES (${values})`) : `INSERT INTO ${this.driver.escapeTableName(tableName)} DEFAULT VALUES`;
const sql = columns.length > 0 ? (`INSERT INTO ${this.connection.driver.escapeTableName(tableName)}(${columns}) VALUES (${values})`) : `INSERT INTO ${this.connection.driver.escapeTableName(tableName)} DEFAULT VALUES`;
const parameters = keys.map(key => keyValues[key]);
return new Promise<any[]>((ok, fail) => {
this.logger.logQuery(sql, parameters);
this.connection.logger.logQuery(sql, parameters);
const db = this.databaseConnection.connection;
// todo: check if transaction is not active
@ -184,8 +184,8 @@ export class WebsqlQueryRunner implements QueryRunner {
ok();
}, (tx: any, err: any) => {
this.logger.logFailedQuery(sql, parameters);
this.logger.logQueryError(err);
this.connection.logger.logFailedQuery(sql, parameters);
this.connection.logger.logQueryError(err);
return fail(err);
});
});
@ -201,7 +201,7 @@ export class WebsqlQueryRunner implements QueryRunner {
const updateValues = this.parametrize(valuesMap).join(", ");
const conditionString = this.parametrize(conditions, Object.keys(valuesMap).length).join(" AND ");
const query = `UPDATE ${this.driver.escapeTableName(tableName)} SET ${updateValues} ${conditionString ? (" WHERE " + conditionString) : ""}`;
const query = `UPDATE ${this.connection.driver.escapeTableName(tableName)} SET ${updateValues} ${conditionString ? (" WHERE " + conditionString) : ""}`;
const updateParams = Object.keys(valuesMap).map(key => valuesMap[key]);
const conditionParams = Object.keys(conditions).map(key => conditions[key]);
const allParameters = updateParams.concat(conditionParams);
@ -228,7 +228,7 @@ export class WebsqlQueryRunner implements QueryRunner {
const conditionString = typeof conditions === "string" ? conditions : this.parametrize(conditions).join(" AND ");
const parameters = conditions instanceof Object ? Object.keys(conditions).map(key => (conditions as ObjectLiteral)[key]) : maybeParameters;
const sql = `DELETE FROM ${this.driver.escapeTableName(tableName)} WHERE ${conditionString}`;
const sql = `DELETE FROM ${this.connection.driver.escapeTableName(tableName)} WHERE ${conditionString}`;
await this.query(sql, parameters);
}
@ -241,12 +241,12 @@ export class WebsqlQueryRunner implements QueryRunner {
let sql = "";
if (hasLevel) {
sql = `INSERT INTO ${this.driver.escapeTableName(tableName)}(ancestor, descendant, level) ` +
`SELECT ancestor, ${newEntityId}, level + 1 FROM ${this.driver.escapeTableName(tableName)} WHERE descendant = ${parentId} ` +
sql = `INSERT INTO ${this.connection.driver.escapeTableName(tableName)}(ancestor, descendant, level) ` +
`SELECT ancestor, ${newEntityId}, level + 1 FROM ${this.connection.driver.escapeTableName(tableName)} WHERE descendant = ${parentId} ` +
`UNION ALL SELECT ${newEntityId}, ${newEntityId}, 1`;
} else {
sql = `INSERT INTO ${this.driver.escapeTableName(tableName)}(ancestor, descendant) ` +
`SELECT ancestor, ${newEntityId} FROM ${this.driver.escapeTableName(tableName)} WHERE descendant = ${parentId} ` +
sql = `INSERT INTO ${this.connection.driver.escapeTableName(tableName)}(ancestor, descendant) ` +
`SELECT ancestor, ${newEntityId} FROM ${this.connection.driver.escapeTableName(tableName)} WHERE descendant = ${parentId} ` +
`UNION ALL SELECT ${newEntityId}, ${newEntityId}`;
}
await this.query(sql);
@ -821,7 +821,7 @@ export class WebsqlQueryRunner implements QueryRunner {
* Truncates table.
*/
async truncate(tableName: string): Promise<void> {
await this.query(`DELETE FROM ${this.driver.escapeTableName(tableName)}`);
await this.query(`DELETE FROM ${this.connection.driver.escapeTableName(tableName)}`);
}
// -------------------------------------------------------------------------
@ -832,7 +832,7 @@ export class WebsqlQueryRunner implements QueryRunner {
* Parametrizes given object of values. Used to create column=value queries.
*/
protected parametrize(objectLiteral: ObjectLiteral, startIndex: number = 0): string[] {
return Object.keys(objectLiteral).map((key, index) => this.driver.escapeColumnName(key) + "=$" + (startIndex + index + 1));
return Object.keys(objectLiteral).map((key, index) => this.connection.driver.escapeColumnName(key) + "=$" + (startIndex + index + 1));
}
/**

View File

@ -8,13 +8,11 @@ import {FindOneOptions} from "../find-options/FindOneOptions";
import {DeepPartial} from "../common/DeepPartial";
import {RemoveOptions} from "../repository/RemoveOptions";
import {SaveOptions} from "../repository/SaveOptions";
import {RepositoryAggregator} from "../repository/RepositoryAggregator";
import {NoNeedToReleaseEntityManagerError} from "./error/NoNeedToReleaseEntityManagerError";
import {SpecificRepository} from "../repository/SpecificRepository";
import {MongoRepository} from "../repository/MongoRepository";
import {TreeRepository} from "../repository/TreeRepository";
import {Repository} from "../repository/Repository";
import {RepositoryNotTreeError} from "../connection/error/RepositoryNotTreeError";
import {QueryBuilder} from "../query-builder/QueryBuilder";
import {FindOptionsUtils} from "../find-options/FindOptionsUtils";
import {SubjectBuilder} from "../persistence/SubjectBuilder";
@ -42,11 +40,6 @@ export class EntityManager {
*/
private data: ObjectLiteral = {};
/**
* Stores all registered repositories.
*/
private repositoryAggregators: RepositoryAggregator[] = [];
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
@ -63,6 +56,43 @@ export class EntityManager {
// Public Methods
// -------------------------------------------------------------------------
/**
* Wraps given function execution (and all operations made there) in a transaction.
* All database operations must be executed using provided entity manager.
*/
async transaction(runInTransaction: (entityManger: EntityManager) => Promise<any>): Promise<any> {
return this.connection.transaction(runInTransaction, this.queryRunnerProvider);
}
/**
* Executes raw SQL query and returns raw database results.
*/
async query(query: string, parameters?: any[]): Promise<any> {
return this.connection.query(query, parameters, this.queryRunnerProvider);
}
/**
* Creates a new query builder that can be used to build a sql query.
*/
createQueryBuilder<Entity>(entityClass: ObjectType<Entity>|Function|string, alias: string, queryRunnerProvider?: QueryRunnerProvider): QueryBuilder<Entity>;
/**
* Creates a new query builder that can be used to build a sql query.
*/
createQueryBuilder(queryRunnerProvider?: QueryRunnerProvider): QueryBuilder<any>;
/**
* Creates a new query builder that can be used to build a sql query.
*/
createQueryBuilder<Entity>(entityClass?: ObjectType<Entity>|Function|string|QueryRunnerProvider, alias?: string, queryRunnerProvider?: QueryRunnerProvider): QueryBuilder<Entity> {
if (alias) {
return this.connection.createQueryBuilder(entityClass as Function|string, alias, queryRunnerProvider || this.queryRunnerProvider);
} else {
return this.connection.createQueryBuilder(entityClass as QueryRunnerProvider|undefined || this.queryRunnerProvider);
}
}
/**
* Gets user data by a given key.
* Used get stored data stored in a transactional entity manager.
@ -120,16 +150,6 @@ export class EntityManager {
return metadata.getEntityIdMixedMap(entity);
}
/**
* Creates a new query builder that can be used to build a sql query.
*/
createQueryBuilder<Entity>(entityClass: ObjectType<Entity>|Function|string, alias: string, queryRunnerProvider?: QueryRunnerProvider): QueryBuilder<Entity> {
const metadata = this.connection.getMetadata(entityClass);
return new QueryBuilder(this.connection, queryRunnerProvider || this.queryRunnerProvider)
.select(alias)
.from(metadata.target, alias);
}
/**
* Creates a new entity instance.
*/
@ -531,53 +551,6 @@ export class EntityManager {
return qb.getOne();
}
/**
* Executes raw SQL query and returns raw database results.
*/
async query(query: string, parameters?: any[]): Promise<any> {
if (this.queryRunnerProvider && this.queryRunnerProvider.isReleased)
throw new QueryRunnerProviderAlreadyReleasedError();
const queryRunnerProvider = this.queryRunnerProvider || new QueryRunnerProvider(this.connection.driver);
const queryRunner = await queryRunnerProvider.provide();
try {
return await queryRunner.query(query, parameters); // await is needed here because we are using finally
} finally {
await queryRunnerProvider.release(queryRunner);
}
}
/**
* Wraps given function execution (and all operations made there) in a transaction.
* All database operations must be executed using provided entity manager.
*/
async transaction(runInTransaction: (entityManger: EntityManager) => Promise<any>): Promise<any> {
if (this.queryRunnerProvider && this.queryRunnerProvider.isReleased)
throw new QueryRunnerProviderAlreadyReleasedError();
const queryRunnerProvider = this.queryRunnerProvider || new QueryRunnerProvider(this.connection.driver, true);
const queryRunner = await queryRunnerProvider.provide();
const transactionEntityManager = new EntityManager(this.connection, queryRunnerProvider);
try {
await queryRunner.beginTransaction();
const result = await runInTransaction(transactionEntityManager);
await queryRunner.commitTransaction();
return result;
} catch (err) {
await queryRunner.rollbackTransaction();
throw err;
} finally {
await queryRunnerProvider.release(queryRunner);
if (!this.queryRunnerProvider) // if we used a new query runner provider then release it
await queryRunnerProvider.releaseReused();
}
}
/**
* Clears all the data from the given table (truncates/drops it).
*/
@ -602,8 +575,12 @@ export class EntityManager {
getRepository<Entity>(entityClassOrName: ObjectType<Entity>|string): Repository<Entity> {
// if single db connection is used then create its own repository with reused query runner
if (this.queryRunnerProvider)
return this.obtainRepositoryAggregator(entityClassOrName as any).repository;
if (this.queryRunnerProvider) {
if (this.queryRunnerProvider.isReleased)
throw new QueryRunnerProviderAlreadyReleasedError();
return this.connection.createIsolatedRepository(entityClassOrName, this.queryRunnerProvider);
}
return this.connection.getRepository<Entity>(entityClassOrName as any);
}
@ -618,11 +595,10 @@ export class EntityManager {
// if single db connection is used then create its own repository with reused query runner
if (this.queryRunnerProvider) {
const treeRepository = this.obtainRepositoryAggregator(entityClassOrName).treeRepository;
if (!treeRepository)
throw new RepositoryNotTreeError(entityClassOrName);
if (this.queryRunnerProvider.isReleased)
throw new QueryRunnerProviderAlreadyReleasedError();
return treeRepository;
return this.connection.createIsolatedRepository(entityClassOrName, this.queryRunnerProvider) as TreeRepository<Entity>;
}
return this.connection.getTreeRepository<Entity>(entityClassOrName as any);
@ -644,8 +620,12 @@ export class EntityManager {
getMongoRepository<Entity>(entityClassOrName: ObjectType<Entity>|string): MongoRepository<Entity> {
// if single db connection is used then create its own repository with reused query runner
if (this.queryRunnerProvider)
return this.obtainRepositoryAggregator(entityClassOrName as any).repository as MongoRepository<Entity>;
if (this.queryRunnerProvider) {
if (this.queryRunnerProvider.isReleased)
throw new QueryRunnerProviderAlreadyReleasedError();
return this.connection.createIsolatedRepository(entityClassOrName, this.queryRunnerProvider) as MongoRepository<Entity>;
}
return this.connection.getMongoRepository<Entity>(entityClassOrName as any);
}
@ -681,8 +661,12 @@ export class EntityManager {
getSpecificRepository<Entity>(entityClassOrName: ObjectType<Entity>|string): SpecificRepository<Entity> {
// if single db connection is used then create its own repository with reused query runner
if (this.queryRunnerProvider)
return this.obtainRepositoryAggregator(entityClassOrName).specificRepository;
if (this.queryRunnerProvider) {
if (this.queryRunnerProvider.isReleased)
throw new QueryRunnerProviderAlreadyReleasedError();
return this.connection.createIsolatedSpecificRepository(entityClassOrName, this.queryRunnerProvider);
}
return this.connection.getSpecificRepository<Entity>(entityClassOrName as any);
}
@ -741,7 +725,7 @@ export class EntityManager {
const metadata = this.connection.getMetadata(target);
const queryRunnerProvider = this.queryRunnerProvider || new QueryRunnerProvider(this.connection.driver, true);
try {
const transactionEntityManager = this.connection.createEntityManagerWithSingleDatabaseConnection(queryRunnerProvider);
const transactionEntityManager = this.connection.createIsolatedManager(queryRunnerProvider);
// transactionEntityManager.data =
const databaseEntityLoader = new SubjectBuilder(this.connection, queryRunnerProvider);
@ -763,7 +747,7 @@ export class EntityManager {
const metadata = this.connection.getMetadata(target);
const queryRunnerProvider = this.queryRunnerProvider || new QueryRunnerProvider(this.connection.driver, true);
try {
const transactionEntityManager = this.connection.createEntityManagerWithSingleDatabaseConnection(queryRunnerProvider);
const transactionEntityManager = this.connection.createIsolatedManager(queryRunnerProvider);
const databaseEntityLoader = new SubjectBuilder(this.connection, queryRunnerProvider);
await databaseEntityLoader.remove(entity, metadata);
@ -777,25 +761,4 @@ export class EntityManager {
}
}
/**
* Gets, or if does not exist yet, creates and returns a repository aggregator for a particular entity target.
*/
protected obtainRepositoryAggregator<Entity>(entityClassOrName: ObjectType<Entity>|string): RepositoryAggregator {
if (this.queryRunnerProvider && this.queryRunnerProvider.isReleased)
throw new QueryRunnerProviderAlreadyReleasedError();
const metadata = this.connection.getMetadata(entityClassOrName);
let repositoryAggregator = this.repositoryAggregators.find(repositoryAggregate => repositoryAggregate.metadata === metadata);
if (!repositoryAggregator) {
repositoryAggregator = new RepositoryAggregator(
this.connection,
this.connection.getMetadata(entityClassOrName as any),
this.queryRunnerProvider
);
this.repositoryAggregators.push(repositoryAggregator); // todo: check isnt memory leak here?
}
return repositoryAggregator;
}
}

View File

@ -0,0 +1,22 @@
import {Connection} from "../connection/Connection";
import {EntityManager} from "./EntityManager";
import {MongoEntityManager} from "./MongoEntityManager";
import {MongoDriver} from "../driver/mongodb/MongoDriver";
import {QueryRunnerProvider} from "../query-runner/QueryRunnerProvider";
/**
* Helps to create entity managers.
*/
export class EntityManagerFactory {
/**
* Creates a new entity manager depend on a given connection's driver.
*/
create(connection: Connection, queryRunnerProvider?: QueryRunnerProvider): EntityManager {
if (connection.driver instanceof MongoDriver)
return new MongoEntityManager(connection, queryRunnerProvider);
return new EntityManager(connection, queryRunnerProvider);
}
}

View File

@ -64,29 +64,6 @@ export class MongoEntityManager extends EntityManager {
// Overridden Methods
// -------------------------------------------------------------------------
/**
* Executes raw SQL query and returns raw database results.
*/
query(query: string, parameters?: any[]): Promise<any> {
throw new Error(`Queries aren't supported by MongoDB.`);
}
/**
* Wraps given function execution (and all operations made there) in a transaction.
* All database operations must be executed using provided entity manager.
*/
transaction(runInTransaction: (entityManger: EntityManager) => Promise<any>): Promise<any> {
throw new Error(`Transactions aren't supported by MongoDB.`);
}
/**
* Using Query Builder with MongoDB is not supported yet.
* Calling this method will return an error.
*/
createQueryBuilder<Entity>(entityClassOrName: ObjectType<Entity>|string, alias: string, queryRunnerProvider?: QueryRunnerProvider): QueryBuilder<Entity> {
throw new Error(`Query Builder is not supported by MongoDB.`);
}
/**
* Finds entities that match given find options or conditions.
*/

View File

@ -65,7 +65,6 @@ export * from "./decorator/tree/TreeLevelColumn";
export * from "./decorator/tree/TreeParent";
export * from "./decorator/tree/TreeChildren";
export * from "./decorator/Index";
export * from "./decorator/NamingStrategy";
export * from "./decorator/Embedded";
export * from "./decorator/DiscriminatorValue";
export * from "./decorator/EntityRepository";

View File

@ -100,7 +100,8 @@ export class LazyRelationsWrapper {
return `${relation.entityMetadata.name}.${relation.propertyName} = ${relation.propertyName}.${joinColumn.referencedColumn!.propertyName}`;
}).join(" AND ");
const qb = new QueryBuilder(this.connection)
const qb = this.connection
.createQueryBuilder()
.select(relation.propertyName) // category
.from(relation.type, relation.propertyName) // Category, category
.innerJoin(relation.entityMetadata.target as Function, relation.entityMetadata.name, conditions);
@ -120,7 +121,8 @@ export class LazyRelationsWrapper {
* WHERE post.[joinColumn.name] = entity[joinColumn.referencedColumn]
*/
protected loadOneToManyOrOneToOneNotOwner(relation: RelationMetadata, entity: ObjectLiteral): Promise<any> {
const qb = new QueryBuilder(this.connection)
const qb = this.connection
.createQueryBuilder()
.select(relation.propertyName)
.from(relation.inverseRelation!.entityMetadata.target, relation.propertyName);
@ -154,7 +156,8 @@ export class LazyRelationsWrapper {
return parameters;
}, {} as ObjectLiteral);
return new QueryBuilder(this.connection)
return this.connection
.createQueryBuilder()
.select(mainAlias)
.from(relation.type, mainAlias)
.innerJoin(joinAlias, joinAlias, [...joinColumnConditions, ...inverseJoinColumnConditions].join(" AND "))
@ -185,7 +188,8 @@ export class LazyRelationsWrapper {
return parameters;
}, {} as ObjectLiteral);
return new QueryBuilder(this.connection)
return this.connection
.createQueryBuilder()
.select(mainAlias)
.from(relation.type, mainAlias)
.innerJoin(joinAlias, joinAlias, [...joinColumnConditions, ...inverseJoinColumnConditions].join(" AND "))

View File

@ -0,0 +1,16 @@
import {LoggerOptions} from "./LoggerOptions";
import {Logger} from "./Logger";
/**
* Helps to create logger instances.
*/
export class LoggerFactory {
/**
* Creates a new logger depend on a given connection's driver.
*/
create(options: LoggerOptions): Logger {
return new Logger(options);
}
}

View File

@ -3,6 +3,7 @@ import {ColumnMetadata} from "../metadata/ColumnMetadata";
import {ForeignKeyMetadata} from "../metadata/ForeignKeyMetadata";
import {ColumnTypes} from "../metadata/types/ColumnTypes";
import {Connection} from "../connection/Connection";
import {LazyRelationsWrapper} from "../lazy-loading/LazyRelationsWrapper";
/**
* Creates EntityMetadata for junction tables of the closure entities.

View File

@ -16,6 +16,7 @@ import {Connection} from "../connection/Connection";
import {EntityListenerMetadata} from "../metadata/EntityListenerMetadata";
import {ColumnOptions} from "../decorator/options/ColumnOptions";
import {ForeignKeyMetadata} from "../metadata/ForeignKeyMetadata";
import {LazyRelationsWrapper} from "../lazy-loading/LazyRelationsWrapper";
/**
* Builds EntityMetadata objects and all its sub-metadatas.
@ -45,7 +46,9 @@ export class EntityMetadataBuilder {
// Constructor
// -------------------------------------------------------------------------
constructor(private connection: Connection, private metadataArgsStorage: MetadataArgsStorage) {
constructor(private connection: Connection,
private metadataArgsStorage: MetadataArgsStorage) {
this.junctionEntityMetadataBuilder = new JunctionEntityMetadataBuilder(connection);
this.closureJunctionEntityMetadataBuilder = new ClosureJunctionEntityMetadataBuilder(connection);
this.relationJoinColumnBuilder = new RelationJoinColumnBuilder(connection);
@ -157,7 +160,7 @@ export class EntityMetadataBuilder {
// build all indices (need to do it after relations and their join columns are built)
entityMetadatas.forEach(entityMetadata => {
entityMetadata.indices.forEach(index => index.build(this.connection.driver.namingStrategy));
entityMetadata.indices.forEach(index => index.build(this.connection.namingStrategy));
});
entityMetadatas
@ -165,7 +168,7 @@ export class EntityMetadataBuilder {
.forEach(metadata => {
const parentPrimaryColumns = metadata.parentEntityMetadata.primaryColumns;
const parentRelationColumns = parentPrimaryColumns.map(parentPrimaryColumn => {
const columnName = this.connection.driver.namingStrategy.classTableInheritanceParentColumnName(metadata.parentEntityMetadata.tableName, parentPrimaryColumn.propertyPath);
const columnName = this.connection.namingStrategy.classTableInheritanceParentColumnName(metadata.parentEntityMetadata.tableName, parentPrimaryColumn.propertyPath);
const column = new ColumnMetadata({
entityMetadata: metadata,
referencedColumn: parentPrimaryColumn,
@ -183,7 +186,7 @@ export class EntityMetadataBuilder {
}
});
metadata.registerColumn(column);
column.build(this.connection.driver.namingStrategy);
column.build(this.connection.namingStrategy);
return column;
});
@ -191,7 +194,7 @@ export class EntityMetadataBuilder {
new ForeignKeyMetadata({
entityMetadata: metadata,
referencedEntityMetadata: metadata.parentEntityMetadata,
namingStrategy: this.connection.driver.namingStrategy,
namingStrategy: this.connection.namingStrategy,
columns: parentRelationColumns,
referencedColumns: parentPrimaryColumns,
onDelete: "CASCADE"
@ -206,7 +209,8 @@ export class EntityMetadataBuilder {
entityMetadata.relations
.filter(relation => relation.isLazy)
.forEach(relation => {
this.connection.driver.lazyRelationsWrapper.wrap((entityMetadata.target as Function).prototype, relation);
const lazyRelationsWrapper = new LazyRelationsWrapper(this.connection);
lazyRelationsWrapper.wrap((entityMetadata.target as Function).prototype, relation);
});
});
@ -248,7 +252,10 @@ export class EntityMetadataBuilder {
});
}
const entityMetadata = new EntityMetadata({ connection: this.connection, args: tableArgs });
const entityMetadata = new EntityMetadata({
connection: this.connection,
args: tableArgs
});
const inheritanceType = this.metadataArgsStorage.findInheritanceType(tableArgs.target);
entityMetadata.inheritanceType = inheritanceType ? inheritanceType.type : undefined;
@ -307,12 +314,12 @@ export class EntityMetadataBuilder {
* Computes all entity metadata's computed properties, and all its sub-metadatas (relations, columns, embeds, etc).
*/
protected computeEntityMetadata(entityMetadata: EntityMetadata) {
entityMetadata.embeddeds.forEach(embedded => embedded.build(this.connection.driver.namingStrategy));
entityMetadata.embeddeds.forEach(embedded => embedded.build(this.connection.namingStrategy));
entityMetadata.embeddeds.forEach(embedded => {
embedded.columnsFromTree.forEach(column => column.build(this.connection.driver.namingStrategy));
embedded.columnsFromTree.forEach(column => column.build(this.connection.namingStrategy));
embedded.relationsFromTree.forEach(relation => relation.build());
});
entityMetadata.ownColumns.forEach(column => column.build(this.connection.driver.namingStrategy));
entityMetadata.ownColumns.forEach(column => column.build(this.connection.namingStrategy));
entityMetadata.ownRelations.forEach(relation => relation.build());
entityMetadata.relations = entityMetadata.embeddeds.reduce((relations, embedded) => relations.concat(embedded.relationsFromTree), entityMetadata.ownRelations);
entityMetadata.oneToOneRelations = entityMetadata.relations.filter(relation => relation.isOneToOne);
@ -334,7 +341,7 @@ export class EntityMetadataBuilder {
entityMetadata.treeLevelColumn = entityMetadata.columns.find(column => column.isTreeLevel);
entityMetadata.parentIdColumns = entityMetadata.columns.filter(column => column.isParentId);
entityMetadata.objectIdColumn = entityMetadata.columns.find(column => column.isObjectId);
entityMetadata.foreignKeys.forEach(foreignKey => foreignKey.build(this.connection.driver.namingStrategy));
entityMetadata.foreignKeys.forEach(foreignKey => foreignKey.build(this.connection.namingStrategy));
entityMetadata.propertiesMap = entityMetadata.createPropertiesMap();
entityMetadata.relationIds.forEach(relationId => relationId.build());
entityMetadata.relationCounts.forEach(relationCount => relationCount.build());

View File

@ -5,6 +5,7 @@ import {IndexMetadata} from "../metadata/IndexMetadata";
import {RelationMetadata} from "../metadata/RelationMetadata";
import {JoinTableMetadataArgs} from "../metadata-args/JoinTableMetadataArgs";
import {Connection} from "../connection/Connection";
import {LazyRelationsWrapper} from "../lazy-loading/LazyRelationsWrapper";
/**
* Creates EntityMetadata for junction tables.
@ -30,7 +31,7 @@ export class JunctionEntityMetadataBuilder {
const referencedColumns = this.collectReferencedColumns(relation, joinTable);
const inverseReferencedColumns = this.collectInverseReferencedColumns(relation, joinTable);
const joinTableName = joinTable.name || this.connection.driver.namingStrategy.joinTableName(
const joinTableName = joinTable.name || this.connection.namingStrategy.joinTableName(
relation.entityMetadata.tableNameWithoutPrefix,
relation.inverseEntityMetadata.tableNameWithoutPrefix,
relation.propertyPath,
@ -52,7 +53,7 @@ export class JunctionEntityMetadataBuilder {
return (!joinColumnArgs.referencedColumnName || joinColumnArgs.referencedColumnName === referencedColumn.propertyName) &&
!!joinColumnArgs.name;
}) : undefined;
const columnName = joinColumn && joinColumn.name ? joinColumn.name : this.connection.driver.namingStrategy.joinTableColumnName(relation.entityMetadata.tableNameWithoutPrefix, referencedColumn.propertyName, referencedColumn.databaseName);
const columnName = joinColumn && joinColumn.name ? joinColumn.name : this.connection.namingStrategy.joinTableColumnName(relation.entityMetadata.tableNameWithoutPrefix, referencedColumn.propertyName, referencedColumn.databaseName);
return new ColumnMetadata({
entityMetadata: entityMetadata,
@ -78,7 +79,7 @@ export class JunctionEntityMetadataBuilder {
return (!joinColumnArgs.referencedColumnName || joinColumnArgs.referencedColumnName === inverseReferencedColumn.propertyName) &&
!!joinColumnArgs.name;
}) : undefined;
const columnName = joinColumn && joinColumn.name ? joinColumn.name : this.connection.driver.namingStrategy.joinTableColumnName(relation.inverseEntityMetadata.tableNameWithoutPrefix, inverseReferencedColumn.propertyName, inverseReferencedColumn.databaseName);
const columnName = joinColumn && joinColumn.name ? joinColumn.name : this.connection.namingStrategy.joinTableColumnName(relation.inverseEntityMetadata.tableNameWithoutPrefix, inverseReferencedColumn.propertyName, inverseReferencedColumn.databaseName);
return new ColumnMetadata({
entityMetadata: entityMetadata,

View File

@ -59,7 +59,7 @@ export class RelationJoinColumnBuilder {
return new ForeignKeyMetadata({
entityMetadata: relation.entityMetadata,
referencedEntityMetadata: relation.inverseEntityMetadata,
namingStrategy: this.connection.driver.namingStrategy,
namingStrategy: this.connection.namingStrategy,
columns: columns,
referencedColumns: referencedColumns,
onDelete: relation.onDelete,
@ -102,7 +102,7 @@ export class RelationJoinColumnBuilder {
return (!joinColumn.referencedColumnName || joinColumn.referencedColumnName === referencedColumn.propertyName) &&
!!joinColumn.name;
});
const joinColumnName = joinColumnMetadataArg ? joinColumnMetadataArg.name : this.connection.driver.namingStrategy.joinColumnName(relation.propertyName, referencedColumn.propertyName);
const joinColumnName = joinColumnMetadataArg ? joinColumnMetadataArg.name : this.connection.namingStrategy.joinColumnName(relation.propertyName, referencedColumn.propertyName);
let relationalColumn = relation.entityMetadata.ownColumns.find(column => column.databaseName === joinColumnName);
if (!relationalColumn) {
@ -125,7 +125,7 @@ export class RelationJoinColumnBuilder {
relationalColumn.referencedColumn = referencedColumn; // its important to set it here because we need to set referenced column for user defined join column
relationalColumn.type = referencedColumn.type; // also since types of relational column and join column must be equal we override user defined column type
relationalColumn.relationMetadata = relation;
relationalColumn.build(this.connection.driver.namingStrategy);
relationalColumn.build(this.connection.namingStrategy);
return relationalColumn;
});
}

View File

@ -14,6 +14,8 @@ import {TableMetadataArgs} from "../metadata-args/TableMetadataArgs";
import {Connection} from "../connection/Connection";
import {EntityListenerMetadata} from "./EntityListenerMetadata";
import {PropertyTypeFactory} from "./types/PropertyTypeInFunction";
import {Repository} from "../repository/Repository";
import {SpecificRepository} from "../repository/SpecificRepository";
/**
* Contains all entity metadata.
@ -24,6 +26,16 @@ export class EntityMetadata {
// Properties
// -------------------------------------------------------------------------
/**
* Repository used for this entity metadata.
*/
repository: Repository<any>;
/**
* Specific repository used for this entity metadata.
*/
specificRepository: SpecificRepository<any>;
/**
* Used to wrap lazy relations.
*/
@ -342,9 +354,9 @@ export class EntityMetadata {
parentClosureEntityMetadata?: EntityMetadata,
args: TableMetadataArgs
}) {
const namingStrategy = options.connection.driver.namingStrategy;
const tablesPrefix = options.connection.driver.options.tablesPrefix;
this.lazyRelationsWrapper = options.connection.driver.lazyRelationsWrapper;
const namingStrategy = options.connection.namingStrategy;
const tablesPrefix = options.connection.options.tablesPrefix;
this.lazyRelationsWrapper = new LazyRelationsWrapper(options.connection);
this.parentClosureEntityMetadata = options.parentClosureEntityMetadata!;
this.target = options.args.target;
this.tableType = options.args.type;

View File

@ -37,7 +37,7 @@ export class MigrationExecutor {
*/
async executePendingMigrations(): Promise<void> {
const queryRunner = await this.queryRunnerProvider.provide();
const entityManager = this.connection.createEntityManagerWithSingleDatabaseConnection(this.queryRunnerProvider);
const entityManager = this.connection.createIsolatedManager(this.queryRunnerProvider);
// create migrations table if its not created yet
await this.createMigrationsTableIfNotExist();
@ -116,7 +116,7 @@ export class MigrationExecutor {
*/
async undoLastMigration(): Promise<void> {
const queryRunner = await this.queryRunnerProvider.provide();
const entityManager = this.connection.createEntityManagerWithSingleDatabaseConnection(this.queryRunnerProvider);
const entityManager = this.connection.createIsolatedManager(this.queryRunnerProvider);
// create migrations table if its not created yet
await this.createMigrationsTableIfNotExist();
@ -207,9 +207,10 @@ export class MigrationExecutor {
* Loads all migrations that were executed and saved into the database.
*/
protected async loadExecutedMigrations(): Promise<Migration[]> {
const migrationsRaw: ObjectLiteral[] = await new QueryBuilder(this.connection, this.queryRunnerProvider)
const migrationsRaw: ObjectLiteral[] = await this.connection.manager
.createQueryBuilder(this.queryRunnerProvider)
.select()
.fromTable("migrations", "migrations")
.from("migrations", "migrations")
.getRawMany();
return migrationsRaw.map(migrationRaw => {
@ -221,7 +222,7 @@ export class MigrationExecutor {
* Gets all migrations that setup for this connection.
*/
protected getMigrations(): Migration[] {
const migrations = this.connection.getMigrations().map(migration => {
const migrations = this.connection.migrations.map(migration => {
const migrationClassName = (migration.constructor as any).name;
const migrationTimestamp = parseInt(migrationClassName.substr(-13));
if (!migrationTimestamp)

View File

@ -13,7 +13,7 @@ export class DefaultNamingStrategy implements NamingStrategyInterface {
* @param targetName Name of the target entity that can be used to generate a table name.
* @param userSpecifiedName For example if user specified a table name in a decorator, e.g. @Entity("name")
*/
tableName(targetName: string, userSpecifiedName: string): string {
tableName(targetName: string, userSpecifiedName: string|undefined): string {
return userSpecifiedName ? userSpecifiedName : snakeCase(targetName);
}

View File

@ -10,6 +10,7 @@ import {PromiseUtils} from "../util/PromiseUtils";
import {MongoDriver} from "../driver/mongodb/MongoDriver";
import {ColumnMetadata} from "../metadata/ColumnMetadata";
import {EmbeddedMetadata} from "../metadata/EmbeddedMetadata";
import {Broadcaster} from "../subscriber/Broadcaster";
/**
* Executes all database operations (inserts, updated, deletes) that must be executed
@ -111,7 +112,8 @@ export class SubjectOperationExecutor {
}
// broadcast "before" events before we start updating
await this.connection.broadcaster.broadcastBeforeEventsForAll(this.transactionEntityManager, this.insertSubjects, this.updateSubjects, this.removeSubjects);
const broadcaster = new Broadcaster(this.connection);
await broadcaster.broadcastBeforeEventsForAll(this.transactionEntityManager, this.insertSubjects, this.updateSubjects, this.removeSubjects);
// since events can trigger some internal changes (for example update depend property) we need to perform some re-computations here
this.updateSubjects.forEach(subject => subject.recompute());
@ -133,7 +135,7 @@ export class SubjectOperationExecutor {
// finally broadcast "after" events
// note that we are broadcasting events after commit because we want to have ids of the entities inside them to be available in subscribers
await this.connection.broadcaster.broadcastAfterEventsForAll(this.transactionEntityManager, this.insertSubjects, this.updateSubjects, this.removeSubjects);
await broadcaster.broadcastAfterEventsForAll(this.transactionEntityManager, this.insertSubjects, this.updateSubjects, this.removeSubjects);
} catch (error) {

View File

@ -134,15 +134,17 @@ export class JoinAttribute {
*/
get metadata(): EntityMetadata|undefined {
// entityOrProperty is Entity class
if (this.entityOrProperty instanceof Function)
return this.connection.getMetadata(this.entityOrProperty);
// entityOrProperty is relation, e.g. "post.category"
if (this.relation)
return this.relation.inverseEntityMetadata;
if (typeof this.entityOrProperty === "string") { // entityOrProperty is a custom table
// entityOrProperty is Entity class
if (this.connection.hasMetadata(this.entityOrProperty))
return this.connection.getMetadata(this.entityOrProperty);
return undefined;
/*if (typeof this.entityOrProperty === "string") { // entityOrProperty is a custom table
// first try to find entity with such name, this is needed when entity does not have a target class,
// and its target is a string name (scenario when plain old javascript is used or entity schema is loaded from files)
@ -152,9 +154,7 @@ export class JoinAttribute {
// check if we have entity with such table name, and use its metadata if found
return this.connection.entityMetadatas.find(metadata => metadata.tableName === this.entityOrProperty);
}
return undefined;
}*/
}
/**

View File

@ -25,7 +25,8 @@ import {RelationIdMetadataToAttributeTransformer} from "./relation-id/RelationId
import {RelationCountLoadResult} from "./relation-count/RelationCountLoadResult";
import {RelationCountLoader} from "./relation-count/RelationCountLoader";
import {RelationCountMetadataToAttributeTransformer} from "./relation-count/RelationCountMetadataToAttributeTransformer";
import { OracleDriver } from "../driver/oracle/OracleDriver";
import {OracleDriver} from "../driver/oracle/OracleDriver";
import {Broadcaster} from "../subscriber/Broadcaster";
// todo: fix problem with long aliases eg getMaxIdentifierLength
@ -197,31 +198,19 @@ export class QueryBuilder<Entity> {
* Also sets a main string alias of the selection data.
*/
from(entityTarget: Function|string, aliasName: string): this {
this.expressionMap.createMainAlias({
name: aliasName,
target: entityTarget
});
return this;
}
/**
* Specifies FROM which table select/update/delete will be executed.
* Also sets a main string alias of the selection data.
*/
fromTable(tableName: string, aliasName: string) {
// if table has a metadata then find it to properly escape its properties
const metadata = this.connection.entityMetadatas.find(metadata => metadata.tableName === tableName);
if (metadata) {
// const metadata = this.connection.entityMetadatas.find(metadata => metadata.tableName === tableName);
if (entityTarget instanceof Function || this.connection.hasMetadata(entityTarget)) {
this.expressionMap.createMainAlias({
name: aliasName,
metadata: metadata,
metadata: this.connection.getMetadata(entityTarget),
});
} else {
this.expressionMap.createMainAlias({
name: aliasName,
tableName: tableName,
tableName: entityTarget,
});
}
return this;
@ -904,6 +893,7 @@ export class QueryBuilder<Entity> {
*/
async getEntitiesAndRawResults(): Promise<{ entities: Entity[], rawResults: any[] }> {
const queryRunner = await this.getQueryRunner();
const broadcaster = new Broadcaster(this.connection);
const relationIdLoader = new RelationIdLoader(this.connection, this.queryRunnerProvider, this.expressionMap.relationIdAttributes);
const relationCountLoader = new RelationCountLoader(this.connection, this.queryRunnerProvider, this.expressionMap.relationCountAttributes);
const relationIdMetadataTransformer = new RelationIdMetadataToAttributeTransformer(this.expressionMap);
@ -1000,8 +990,9 @@ export class QueryBuilder<Entity> {
const rawRelationIdResults = await relationIdLoader.load(rawResults);
const rawRelationCountResults = await relationCountLoader.load(rawResults);
entities = this.rawResultsToEntities(rawResults, rawRelationIdResults, rawRelationCountResults);
if (this.expressionMap.mainAlias.hasMetadata)
await this.connection.broadcaster.broadcastLoadEventsForAll(this.expressionMap.mainAlias.target, rawResults);
if (this.expressionMap.mainAlias.hasMetadata) {
await broadcaster.broadcastLoadEventsForAll(this.expressionMap.mainAlias.target, rawResults);
}
}
return {
@ -1019,7 +1010,7 @@ export class QueryBuilder<Entity> {
const rawRelationCountResults = await relationCountLoader.load(rawResults);
const entities = this.rawResultsToEntities(rawResults, rawRelationIdResults, rawRelationCountResults);
if (this.expressionMap.mainAlias.hasMetadata)
await this.connection.broadcaster.broadcastLoadEventsForAll(this.expressionMap.mainAlias.target, rawResults);
await broadcaster.broadcastLoadEventsForAll(this.expressionMap.mainAlias.target, rawResults);
return {
entities: entities,
@ -1156,7 +1147,7 @@ export class QueryBuilder<Entity> {
* Clones query builder as it is.
*/
clone(options?: { queryRunnerProvider?: QueryRunnerProvider }): QueryBuilder<Entity> {
const qb = new QueryBuilder(this.connection, options ? options.queryRunnerProvider : undefined);
const qb = this.connection.createQueryBuilder(options ? options.queryRunnerProvider : undefined);
qb.expressionMap = this.expressionMap.clone();
return qb;
}
@ -1412,11 +1403,11 @@ export class QueryBuilder<Entity> {
const updateSet = Object.keys(this.expressionMap.updateSet).map(key => key + "=:updateSet__" + key);
const params = Object.keys(this.expressionMap.updateSet).reduce((object, key) => {
// todo: map propertyNames to names ?
object["updateSet_" + key] = this.expressionMap.updateSet![key];
object["updateSet__" + key] = this.expressionMap.updateSet![key];
return object;
}, {} as ObjectLiteral);
this.setParameters(params);
return "UPDATE " + tableName + " " + (aliasName ? ea(aliasName) : "") + " SET " + updateSet;
return "UPDATE " + this.escapeTable(tableName) + " " + (aliasName ? ea(aliasName) : "") + " SET " + updateSet;
}
throw new Error("No query builder type is specified.");

View File

@ -49,7 +49,7 @@ export class RelationCountLoader {
// generate query:
// SELECT category.post as parentId, COUNT(category.id) AS cnt FROM category category WHERE category.post IN (1, 2) GROUP BY category.post
const qb = new QueryBuilder(this.connection, this.queryRunnerProvider);
const qb = this.connection.createQueryBuilder(this.queryRunnerProvider);
qb.select(inverseSideTableAlias + "." + inverseSidePropertyName, "parentId")
.addSelect("COUNT(" + qb.escapeAlias(inverseSideTableAlias) + "." + qb.escapeColumn(referenceColumnName) + ")", "cnt")
.from(inverseSideTable, inverseSideTableAlias)
@ -106,10 +106,10 @@ export class RelationCountLoader {
const condition = junctionAlias + "." + firstJunctionColumn.propertyName + " IN (" + referenceColumnValues + ")" +
" AND " + junctionAlias + "." + secondJunctionColumn.propertyName + " = " + inverseSideTableAlias + "." + inverseJoinColumnName;
const qb = new QueryBuilder(this.connection, this.queryRunnerProvider);
const qb = this.connection.createQueryBuilder(this.queryRunnerProvider);
qb.select(junctionAlias + "." + firstJunctionColumn.propertyName, "parentId")
.addSelect("COUNT(" + qb.escapeAlias(inverseSideTableAlias) + "." + qb.escapeColumn(inverseJoinColumnName) + ")", "cnt")
.fromTable(inverseSideTableName, inverseSideTableAlias)
.from(inverseSideTableName, inverseSideTableAlias)
.innerJoin(junctionTableName, junctionAlias, condition)
.addGroupBy(junctionAlias + "." + firstJunctionColumn.propertyName);

View File

@ -77,7 +77,7 @@ export class RelationIdLoader {
// generate query:
// SELECT category.id, category.postId FROM category category ON category.postId = :postId
const qb = new QueryBuilder(this.connection, this.queryRunnerProvider);
const qb = this.connection.createQueryBuilder(this.queryRunnerProvider);
joinColumns.forEach(joinColumn => {
qb.addSelect(tableAlias + "." + joinColumn.propertyPath, joinColumn.databaseName);
@ -143,7 +143,7 @@ export class RelationIdLoader {
return "(" + condition + " AND " + inverseJoinColumnCondition + ")";
}).join(" OR ");
const qb = new QueryBuilder(this.connection, this.queryRunnerProvider);
const qb = this.connection.createQueryBuilder(this.queryRunnerProvider);
inverseJoinColumns.forEach(joinColumn => {
qb.addSelect(junctionAlias + "." + joinColumn.propertyPath, joinColumn.databaseName)
@ -155,7 +155,7 @@ export class RelationIdLoader {
.addOrderBy(junctionAlias + "." + joinColumn.propertyPath);
});
qb.fromTable(inverseSideTableName, inverseSideTableAlias)
qb.from(inverseSideTableName, inverseSideTableAlias)
.innerJoin(junctionTableName, junctionAlias, condition)
.setParameters(parameters);

View File

@ -4,6 +4,8 @@ import {Driver} from "../driver/Driver";
/**
* Represents functionality to provide a new query runners, and release old ones.
* Also can provide always same query runner.
*
* todo: rename to QueryExecutor ?
*/
export class QueryRunnerProvider {

View File

@ -1,57 +0,0 @@
import {Repository} from "./Repository";
import {EntityMetadata} from "../metadata/EntityMetadata";
import {SpecificRepository} from "./SpecificRepository";
import {Connection} from "../connection/Connection";
import {TreeRepository} from "./TreeRepository";
import {QueryRunnerProvider} from "../query-runner/QueryRunnerProvider";
import {RepositoryFactory} from "./RepositoryFactory";
import {getFromContainer} from "../container";
/**
* Aggregates all repositories of the specific metadata.
*/
export class RepositoryAggregator {
// -------------------------------------------------------------------------
// Public Readonly properties
// -------------------------------------------------------------------------
/**
* Entity metadata which owns repositories.
*/
public readonly metadata: EntityMetadata;
/**
* Ordinary repository.
*/
public readonly repository: Repository<any>;
/**
* Tree repository.
*/
public readonly treeRepository?: TreeRepository<any>;
/**
* Repository with specific functions.
*/
public readonly specificRepository: SpecificRepository<any>;
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
constructor(connection: Connection, metadata: EntityMetadata, queryRunnerProvider?: QueryRunnerProvider) {
this.metadata = metadata;
const factory = getFromContainer(RepositoryFactory);
if (metadata.isClosure) {
this.repository = this.treeRepository = factory.createTreeRepository(connection.manager, metadata, queryRunnerProvider);
} else {
this.repository = factory.createRepository(connection.manager, metadata, queryRunnerProvider);
}
this.specificRepository = factory.createSpecificRepository(connection, metadata, queryRunnerProvider);
}
}

View File

@ -20,34 +20,32 @@ export class RepositoryFactory {
/**
* Creates a regular repository.
*/
createRepository(manager: EntityManager, metadata: EntityMetadata, queryRunnerProvider?: QueryRunnerProvider): Repository<any> {
createRepository(connection: Connection, metadata: EntityMetadata, queryRunnerProvider?: QueryRunnerProvider): Repository<any> {
if (metadata.isClosure) {
// NOTE: dynamic access to protected properties. We need this to prevent unwanted properties in those classes to be exposed,
// however we need these properties for internal work of the class
const repository = new TreeRepository<any>();
(repository as any)["manager"] = connection.manager;
(repository as any)["metadata"] = metadata;
(repository as any)["queryRunnerProvider"] = queryRunnerProvider;
return repository;
// NOTE: dynamic access to protected properties. We need this to prevent unwanted properties in those classes to be exposed,
// however we need these properties for internal work of the class
let repository: Repository<any>;
if (manager.connection.driver instanceof MongoDriver) {
repository = new MongoRepository();
} else {
repository = new Repository<any>();
// NOTE: dynamic access to protected properties. We need this to prevent unwanted properties in those classes to be exposed,
// however we need these properties for internal work of the class
let repository: Repository<any>;
if (connection.driver instanceof MongoDriver) {
repository = new MongoRepository();
} else {
repository = new Repository<any>();
}
(repository as any)["manager"] = connection.manager;
(repository as any)["metadata"] = metadata;
(repository as any)["queryRunnerProvider"] = queryRunnerProvider;
return repository;
}
(repository as any)["manager"] = manager;
(repository as any)["metadata"] = metadata;
(repository as any)["queryRunnerProvider"] = queryRunnerProvider;
return repository;
}
/**
* Creates a tree repository.
*/
createTreeRepository(manager: EntityManager, metadata: EntityMetadata, queryRunnerProvider?: QueryRunnerProvider): TreeRepository<any> {
// NOTE: dynamic access to protected properties. We need this to prevent unwanted properties in those classes to be exposed,
// however we need these properties for internal work of the class
const repository = new TreeRepository<any>();
(repository as any)["manager"] = manager;
(repository as any)["metadata"] = metadata;
(repository as any)["queryRunnerProvider"] = queryRunnerProvider;
return repository;
}
/**

View File

@ -254,9 +254,10 @@ export class SpecificRepository<Entity extends ObjectLiteral> {
if (!relatedEntityIds || !relatedEntityIds.length)
return Promise.resolve();
const qb = new QueryBuilder(this.connection, this.queryRunnerProvider)
const qb = this.connection.manager
.createQueryBuilder(this.queryRunnerProvider)
.delete()
.fromTable(relation.junctionEntityMetadata!.tableName, "junctionEntity");
.from(relation.junctionEntityMetadata!.tableName, "junctionEntity");
const firstColumnName = this.connection.driver.escapeColumnName(relation.isOwning ? relation.junctionEntityMetadata!.columns[0].databaseName : relation.junctionEntityMetadata!.columns[1].databaseName);
const secondColumnName = this.connection.driver.escapeColumnName(relation.isOwning ? relation.junctionEntityMetadata!.columns[1].databaseName : relation.junctionEntityMetadata!.columns[0].databaseName);
@ -302,7 +303,8 @@ export class SpecificRepository<Entity extends ObjectLiteral> {
if (!entityIds || !entityIds.length)
return Promise.resolve();
const qb = new QueryBuilder(this.connection, this.queryRunnerProvider)
const qb = this.connection.manager
.createQueryBuilder(this.queryRunnerProvider)
.delete()
.from(relation.junctionEntityMetadata!.tableName, "junctionEntity");
@ -389,7 +391,8 @@ export class SpecificRepository<Entity extends ObjectLiteral> {
parameters["id"] = id;
}
await new QueryBuilder(this.connection, this.queryRunnerProvider)
await this.connection.manager
.createQueryBuilder(this.queryRunnerProvider)
.delete()
.from(this.metadata.target, alias)
.where(condition, parameters)
@ -417,7 +420,8 @@ export class SpecificRepository<Entity extends ObjectLiteral> {
parameters["ids"] = ids;
}
await new QueryBuilder(this.connection, this.queryRunnerProvider)
await this.connection.manager
.createQueryBuilder(this.queryRunnerProvider)
.delete()
.from(this.metadata.target, alias)
.where(condition, parameters)
@ -454,11 +458,11 @@ export class SpecificRepository<Entity extends ObjectLiteral> {
// console.log("entityOrEntities:", entityOrEntities);
// console.log("entityIds:", entityIds);
const promises = (entityIds as any[]).map((entityId: any) => {
const qb = new QueryBuilder(this.connection, this.queryRunnerProvider);
const qb = this.connection.createQueryBuilder(this.queryRunnerProvider);
inverseEntityColumnNames.forEach(columnName => {
qb.select(ea("junction") + "." + ec(columnName) + " AS " + ea(columnName));
});
qb.fromTable(relation.junctionEntityMetadata!.tableName, "junction");
qb.from(relation.junctionEntityMetadata!.tableName, "junction");
Object.keys(entityId).forEach((columnName) => {
const junctionColumnName = ownerEntityColumns.find(joinColumn => joinColumn.referencedColumn!.databaseName === columnName);
qb.andWhere(ea("junction") + "." + ec(junctionColumnName!.databaseName) + "=:" + junctionColumnName!.databaseName + "_entityId", {[junctionColumnName!.databaseName + "_entityId"]: entityId[columnName]});

View File

@ -15,8 +15,7 @@ export class Broadcaster {
// Constructor
// -------------------------------------------------------------------------
constructor(private connection: Connection,
private subscriberMetadatas: EntitySubscriberInterface<any>[]) {
constructor(private connection: Connection) {
}
// -------------------------------------------------------------------------
@ -57,7 +56,7 @@ export class Broadcaster {
.filter(listener => listener.type === EventListenerTypes.BEFORE_INSERT && listener.isAllowed(subject.entity))
.map(entityListener => subject.entity[entityListener.propertyName]()); // getValue() ?
const subscribers = this.subscriberMetadatas
const subscribers = this.connection.subscribers
.filter(subscriber => this.isAllowedSubscriber(subscriber, subject.entityTarget!) && subscriber.beforeInsert)
.map(subscriber => subscriber.beforeInsert!({
manager: manager,
@ -79,7 +78,7 @@ export class Broadcaster {
.filter(listener => listener.type === EventListenerTypes.BEFORE_UPDATE && listener.isAllowed(subject.entity))
.map(entityListener => subject.entity[entityListener.propertyName]());
const subscribers = this.subscriberMetadatas
const subscribers = this.connection.subscribers
.filter(subscriber => this.isAllowedSubscriber(subscriber, subject.entityTarget!) && subscriber.beforeUpdate)
.map(subscriber => subscriber.beforeUpdate!({
manager: manager,
@ -104,7 +103,7 @@ export class Broadcaster {
.filter(listener => listener.type === EventListenerTypes.BEFORE_REMOVE && listener.isAllowed(subject.entity))
.map(entityListener => subject.databaseEntity[entityListener.propertyName]());
const subscribers = this.subscriberMetadatas
const subscribers = this.connection.subscribers
.filter(subscriber => this.isAllowedSubscriber(subscriber, subject.entityTarget!) && subscriber.beforeRemove)
.map(subscriber => subscriber.beforeRemove!({
manager: manager,
@ -128,7 +127,7 @@ export class Broadcaster {
.filter(listener => listener.type === EventListenerTypes.AFTER_INSERT && listener.isAllowed(subject.entity))
.map(entityListener => subject.entity[entityListener.propertyName]());
const subscribers = this.subscriberMetadatas
const subscribers = this.connection.subscribers
.filter(subscriber => this.isAllowedSubscriber(subscriber, subject.entityTarget!) && subscriber.afterInsert)
.map(subscriber => subscriber.afterInsert!({
manager: manager,
@ -150,7 +149,7 @@ export class Broadcaster {
.filter(listener => listener.type === EventListenerTypes.AFTER_UPDATE && listener.isAllowed(subject.entity))
.map(entityListener => subject.entity[entityListener.propertyName]());
const subscribers = this.subscriberMetadatas
const subscribers = this.connection.subscribers
.filter(subscriber => this.isAllowedSubscriber(subscriber, subject.entityTarget!) && subscriber.afterUpdate)
.map(subscriber => subscriber.afterUpdate!({
manager: manager,
@ -175,7 +174,7 @@ export class Broadcaster {
.filter(listener => listener.type === EventListenerTypes.AFTER_REMOVE && listener.isAllowed(subject.entity))
.map(entityListener => subject.entity[entityListener.propertyName]());
const subscribers = this.subscriberMetadatas
const subscribers = this.connection.subscribers
.filter(subscriber => this.isAllowedSubscriber(subscriber, subject.entityTarget!) && subscriber.afterRemove)
.map(subscriber => subscriber.afterRemove!({
manager: manager,
@ -226,7 +225,7 @@ export class Broadcaster {
.filter(listener => listener.type === EventListenerTypes.AFTER_LOAD && listener.isAllowed(entity))
.map(listener => entity[listener.propertyName]());
const subscribers = this.subscriberMetadatas
const subscribers = this.connection.subscribers
.filter(subscriber => this.isAllowedSubscriber(subscriber, target) && subscriber.afterLoad)
.map(subscriber => subscriber.afterLoad!(entity));

View File

@ -2,6 +2,13 @@ import {ObjectLiteral} from "../common/ObjectLiteral";
export class OrmUtils {
static splitClassesAndStrings<T>(clsesAndStrings: T[]|string[]): [T[], string[]] {
return [
(clsesAndStrings as T[]).filter(cls => typeof cls !== "string"),
(clsesAndStrings as string[]).filter(str => typeof str === "string"),
];
}
static groupBy<T, R>(array: T[], propertyCallback: (item: T) => R): { id: R, items: T[] }[] {
return array.reduce((groupedArray, value) => {
const key = propertyCallback(value);

View File

@ -22,6 +22,7 @@ import {Blog} from "./modules/blog/entity/Blog";
import {Question} from "./modules/question/entity/Question";
import {Video} from "./modules/video/entity/Video";
import {ConnectionOptions} from "../../../src/connection/ConnectionOptions";
import {DefaultNamingStrategy} from "../../../src/naming-strategy/DefaultNamingStrategy";
describe("Connection", () => {
const resourceDir = __dirname + "/../../../../../test/functional/connection/";
@ -122,26 +123,10 @@ describe("Connection", () => {
// expect(connection.reactiveEntityManager).to.be.instanceOf(ReactiveEntityManager);
}));
// todo: they aren't promises anymore
it("import entities, entity schemas, subscribers and naming strategies should not be possible once connection is done", () => connections.forEach(connection => {
expect(() => connection.importEntities([Post])).to.throw(Error); // CannotImportAlreadyConnectedError
expect(() => connection.importEntitySchemas([])).to.throw(Error); // CannotImportAlreadyConnectedError
expect(() => connection.importSubscribers([])).to.throw(Error); // CannotImportAlreadyConnectedError
expect(() => connection.importNamingStrategies([])).to.throw(Error); // CannotImportAlreadyConnectedError
expect(() => connection.importEntitiesFromDirectories([])).to.throw(Error); // CannotImportAlreadyConnectedError
expect(() => connection.importEntitySchemaFromDirectories([])).to.throw(Error); // CannotImportAlreadyConnectedError
expect(() => connection.importSubscribersFromDirectories([])).to.throw(Error); // CannotImportAlreadyConnectedError
expect(() => connection.importNamingStrategiesFromDirectories([])).to.throw(Error); // CannotImportAlreadyConnectedError
}));
it("should not be able to connect again", () => connections.forEach(connection => {
return connection.connect().should.be.rejected; // CannotConnectAlreadyConnectedError
}));
it("should not be able to change used naming strategy", () => connections.forEach(connection => {
expect(() => connection.useNamingStrategy("something")).to.throw(Error); // CannotUseNamingStrategyNotConnectedError
}));
it("should be able to close a connection", async () => Promise.all(connections.map(connection => {
return connection.close();
})));
@ -229,149 +214,6 @@ describe("Connection", () => {
});
describe("import entities and entity schemas", function() {
let firstConnection: Connection, secondConnection: Connection;
beforeEach(async () => {
firstConnection = getConnectionManager().create(setupSingleTestingConnection("mysql", {
name: "firstConnection",
entities: []
}));
secondConnection = getConnectionManager().create(setupSingleTestingConnection("mysql", {
name: "secondConnection",
entities: []
}));
});
it("should import first connection's entities only", async () => {
firstConnection.importEntities([Post]);
await firstConnection.connect();
firstConnection.getRepository(Post).should.be.instanceOf(Repository);
firstConnection.getRepository(Post).target.should.be.equal(Post);
expect(() => firstConnection.getRepository(Category)).to.throw(Error); // RepositoryNotFoundError
await firstConnection.close();
});
it("should import second connection's entities only", async () => {
secondConnection.importEntities([Category]);
await secondConnection.connect();
secondConnection.getRepository(Category).should.be.instanceOf(Repository);
secondConnection.getRepository(Category).target.should.be.equal(Category);
expect(() => secondConnection.getRepository(Post)).to.throw(Error); // RepositoryNotFoundError
await secondConnection.close();
});
it("should import first connection's entity schemas only", async () => {
firstConnection.importEntitySchemas([ require(resourceDir + "schema/user.json") ]);
await firstConnection.connect();
firstConnection.getRepository("User").should.be.instanceOf(Repository);
firstConnection.getRepository("User").target.should.be.equal("User");
expect(() => firstConnection.getRepository("Photo")).to.throw(Error); // RepositoryNotFoundError
await firstConnection.close();
});
it("should import second connection's entity schemas only", async () => {
secondConnection.importEntitySchemas([ require(resourceDir + "schema/photo.json") ]);
await secondConnection.connect();
secondConnection.getRepository("Photo").should.be.instanceOf(Repository);
secondConnection.getRepository("Photo").target.should.be.equal("Photo");
expect(() => secondConnection.getRepository("User")).to.throw(Error); // RepositoryNotFoundError
await secondConnection.close();
});
});
describe("import entities / entity schemas / subscribers / naming strategies from directories", function() {
let connection: Connection;
beforeEach(async () => {
connection = getConnectionManager().create(setupSingleTestingConnection("mysql", {
name: "default",
entities: []
}));
});
afterEach(() => connection.isConnected ? connection.close() : {});
it("should successfully load entities / entity schemas / subscribers / naming strategies from directories", async () => {
connection.importEntitiesFromDirectories([__dirname + "/entity/*"]);
connection.importEntitySchemaFromDirectories([resourceDir + "/schema/*"]);
connection.importNamingStrategiesFromDirectories([__dirname + "/naming-strategy/*"]);
connection.importSubscribersFromDirectories([__dirname + "/subscriber/*"]);
await connection.connect();
connection.getRepository(Post).should.be.instanceOf(Repository);
connection.getRepository(Post).target.should.be.equal(Post);
connection.getRepository(Category).should.be.instanceOf(Repository);
connection.getRepository(Category).target.should.be.equal(Category);
connection.getRepository("User").should.be.instanceOf(Repository);
connection.getRepository("User").target.should.be.equal("User");
connection.getRepository("Photo").should.be.instanceOf(Repository);
connection.getRepository("Photo").target.should.be.equal("Photo");
});
it("should successfully load entities / entity schemas / subscribers / naming strategies from glob-patterned directories", async () => {
connection.importEntitiesFromDirectories([__dirname + "/modules/**/entity/*"]);
connection.importEntitySchemaFromDirectories([resourceDir + "/modules/**/schema/*"]);
connection.importNamingStrategiesFromDirectories([__dirname + "/modules/**/naming-strategy/*"]);
connection.importSubscribersFromDirectories([__dirname + "/modules/**/subscriber/*"]);
await connection.connect();
connection.getRepository(Blog).should.be.instanceOf(Repository);
connection.getRepository(Blog).target.should.be.equal(Blog);
connection.getRepository(Question).should.be.instanceOf(Repository);
connection.getRepository(Question).target.should.be.equal(Question);
connection.getRepository(Video).should.be.instanceOf(Repository);
connection.getRepository(Video).target.should.be.equal(Video);
connection.getRepository("BlogCategory").should.be.instanceOf(Repository);
connection.getRepository("BlogCategory").target.should.be.equal("BlogCategory");
connection.getRepository("QuestionCategory").should.be.instanceOf(Repository);
connection.getRepository("QuestionCategory").target.should.be.equal("QuestionCategory");
connection.getRepository("VideoCategory").should.be.instanceOf(Repository);
connection.getRepository("VideoCategory").target.should.be.equal("VideoCategory");
});
});
describe("using naming strategy", function() {
let connection: Connection;
beforeEach(async () => {
connection = getConnectionManager().create(setupSingleTestingConnection("mysql", {
name: "default",
entities: []
}));
});
afterEach(() => connection.isConnected ? connection.close() : {});
it("should use naming strategy when its class passed to useNamingStrategy method", async () => {
connection.importEntities([Post]);
connection.importNamingStrategies([FirstCustomNamingStrategy]);
connection.useNamingStrategy(FirstCustomNamingStrategy);
await connection.connect();
connection.getMetadata(Post).tableName.should.be.equal("POST");
});
it("should use naming strategy when its name passed to useNamingStrategy method", async () => {
connection.importEntities([Category]);
connection.importNamingStrategies([SecondCustomNamingStrategy]);
connection.useNamingStrategy("secondCustomNamingStrategy");
await connection.connect();
connection.getMetadata(Category).tableName.should.be.equal("category");
});
it("should throw an error if not registered naming strategy was used (assert by name)", () => {
connection.importEntities([Category]);
connection.importNamingStrategies([FirstCustomNamingStrategy]);
connection.useNamingStrategy("secondCustomNamingStrategy");
return connection.connect().should.be.rejected; // NamingStrategyNotFoundError
});
it("should throw an error if not registered naming strategy was used (assert by Function)", () => {
connection.importEntities([Category]);
connection.importNamingStrategies([SecondCustomNamingStrategy]);
connection.useNamingStrategy(FirstCustomNamingStrategy);
return connection.connect().should.be.rejected; // NamingStrategyNotFoundError
});
});
describe("skip schema generation when skipSchemaSync option is used", function() {
let connections: Connection[];

View File

@ -1,8 +1,6 @@
import {DefaultNamingStrategy} from "../../../../src/naming-strategy/DefaultNamingStrategy";
import {NamingStrategy} from "../../../../src/decorator/NamingStrategy";
import {NamingStrategyInterface} from "../../../../src/naming-strategy/NamingStrategyInterface";
@NamingStrategy("firstCustomNamingStrategy")
export class FirstCustomNamingStrategy extends DefaultNamingStrategy implements NamingStrategyInterface {
tableName(className: string, customName: string): string {

View File

@ -1,8 +1,6 @@
import {DefaultNamingStrategy} from "../../../../src/naming-strategy/DefaultNamingStrategy";
import {NamingStrategy} from "../../../../src/decorator/NamingStrategy";
import {NamingStrategyInterface} from "../../../../src/naming-strategy/NamingStrategyInterface";
@NamingStrategy("secondCustomNamingStrategy")
export class SecondCustomNamingStrategy extends DefaultNamingStrategy implements NamingStrategyInterface {
tableName(className: string, customName: string): string {

View File

@ -4,19 +4,26 @@ import {expect} from "chai";
import {setupTestingConnections} from "../../utils/test-utils";
import {Connection} from "../../../src/connection/Connection";
import {getConnectionManager} from "../../../src/index";
import {ConnectionMetadataBuilder} from "../../../src/connection/ConnectionMetadataBuilder";
import {EntityMetadataValidator} from "../../../src/metadata-builder/EntityMetadataValidator";
const should = chai.should();
describe("entity-metadata-validator", () => {
let connections: Connection[];
before(() => {
connections = setupTestingConnections({ entities: [__dirname + "/entity/*{.js,.ts}"] })
.map(options => getConnectionManager().create(options));
it("should throw error if relation count decorator used with ManyToOne or OneToOne relations", () => {
const connection = new Connection({ // dummy connection options, connection won't be established anyway
type: "mysql",
host: "localhost",
username: "test",
password: "test",
database: "test",
entities: [__dirname + "/entity/*{.js,.ts}"]
});
const connectionMetadataBuilder = new ConnectionMetadataBuilder(connection);
const entityMetadatas = connectionMetadataBuilder.buildEntityMetadatas([__dirname + "/entity/*{.js,.ts}"], []);
const entityMetadataValidator = new EntityMetadataValidator();
expect(() => entityMetadataValidator.validateMany(entityMetadatas)).to.throw(Error);
});
it("should throw error if relation count decorator used with ManyToOne or OneToOne relations", () => Promise.all(connections.map(async connection => {
expect(() => connection.buildMetadatas()).to.throw(Error);
})));
});

View File

@ -3,7 +3,10 @@ import {Post} from "./entity/Post";
import {Counters} from "./entity/Counters";
import {Connection} from "../../../src/connection/Connection";
import {expect} from "chai";
import {setupTestingConnections} from "../../utils/test-utils";
import {
closeTestingConnections, createTestingConnections, reloadTestingDatabases,
setupTestingConnections
} from "../../utils/test-utils";
import {Subcounters} from "./entity/Subcounters";
import {User} from "./entity/User";
import {getConnectionManager} from "../../../src/index";
@ -11,14 +14,13 @@ import {getConnectionManager} from "../../../src/index";
describe("entity-metadata > property-map", () => {
let connections: Connection[];
before(() => {
connections = setupTestingConnections({ entities: [__dirname + "/entity/*{.js,.ts}"] })
.map(options => getConnectionManager().create(options))
.map(connection => {
connection.buildMetadatas();
return connection;
});
});
before(async () => connections = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
schemaCreate: true,
dropSchemaOnConnection: true,
}));
beforeEach(() => reloadTestingDatabases(connections));
after(() => closeTestingConnections(connections));
it("should create correct property map object", () => Promise.all(connections.map(async connection => {

View File

@ -1,7 +1,10 @@
import "reflect-metadata";
import * as chai from "chai";
import {expect} from "chai";
import {setupTestingConnections} from "../../../utils/test-utils";
import {
closeTestingConnections, createTestingConnections, reloadTestingDatabases,
setupTestingConnections
} from "../../../utils/test-utils";
import {Connection} from "../../../../src/connection/Connection";
import {Post} from "./entity/Post";
import {Counters} from "./entity/Counters";
@ -11,16 +14,15 @@ import {getConnectionManager} from "../../../../src/index";
const should = chai.should();
describe("metadata-builder > ColumnMetadata", () => {
let connections: Connection[];
before(() => {
connections = setupTestingConnections({ entities: [__dirname + "/entity/*{.js,.ts}"] })
.map(options => getConnectionManager().create(options))
.map(connection => {
connection.buildMetadatas();
return connection;
});
});
before(async () => connections = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
schemaCreate: true,
dropSchemaOnConnection: true,
}));
beforeEach(() => reloadTestingDatabases(connections));
after(() => closeTestingConnections(connections));
it("getValue", () => Promise.all(connections.map(async connection => {
const post = new Post();

View File

@ -32,7 +32,7 @@ describe("query builder > locking", () => {
if (connection.driver instanceof SqliteDriver || connection.driver instanceof OracleDriver)
return;
const sql = connection.manager.createQueryBuilder(PostWithVersion, "post")
const sql = connection.createQueryBuilder(PostWithVersion, "post")
.where("post.id = :id", { id: 1 })
.getSql();
@ -52,12 +52,12 @@ describe("query builder > locking", () => {
return;
return Promise.all([
connection.manager.createQueryBuilder(PostWithVersion, "post")
connection.createQueryBuilder(PostWithVersion, "post")
.setLock("pessimistic_read")
.where("post.id = :id", { id: 1 })
.getOne().should.be.rejectedWith(PessimisticLockTransactionRequiredError),
connection.manager.createQueryBuilder(PostWithVersion, "post")
connection.createQueryBuilder(PostWithVersion, "post")
.setLock("pessimistic_write")
.where("post.id = :id", { id: 1 })
.getOne().should.be.rejectedWith(PessimisticLockTransactionRequiredError)
@ -87,7 +87,7 @@ describe("query builder > locking", () => {
if (connection.driver instanceof SqliteDriver || connection.driver instanceof OracleDriver)
return;
const sql = connection.manager.createQueryBuilder(PostWithVersion, "post")
const sql = connection.createQueryBuilder(PostWithVersion, "post")
.setLock("pessimistic_read")
.where("post.id = :id", { id: 1 })
.getSql();
@ -107,7 +107,7 @@ describe("query builder > locking", () => {
if (connection.driver instanceof SqliteDriver || connection.driver instanceof OracleDriver)
return;
const sql = connection.manager.createQueryBuilder(PostWithVersion, "post")
const sql = connection.createQueryBuilder(PostWithVersion, "post")
.where("post.id = :id", { id: 1 })
.getSql();
@ -123,7 +123,7 @@ describe("query builder > locking", () => {
if (connection.driver instanceof SqliteDriver || connection.driver instanceof OracleDriver)
return;
const sql = connection.manager.createQueryBuilder(PostWithVersion, "post")
const sql = connection.createQueryBuilder(PostWithVersion, "post")
.setLock("pessimistic_write")
.where("post.id = :id", { id: 1 })
.getSql();
@ -139,35 +139,35 @@ describe("query builder > locking", () => {
it("should throw error if optimistic lock used with getMany method", () => Promise.all(connections.map(async connection => {
return connection.manager.createQueryBuilder(PostWithVersion, "post")
return connection.createQueryBuilder(PostWithVersion, "post")
.setLock("optimistic", 1)
.getMany().should.be.rejectedWith(OptimisticLockCanNotBeUsedError);
})));
it("should throw error if optimistic lock used with getCount method", () => Promise.all(connections.map(async connection => {
return connection.manager.createQueryBuilder(PostWithVersion, "post")
return connection.createQueryBuilder(PostWithVersion, "post")
.setLock("optimistic", 1)
.getCount().should.be.rejectedWith(OptimisticLockCanNotBeUsedError);
})));
it("should throw error if optimistic lock used with getManyAndCount method", () => Promise.all(connections.map(async connection => {
return connection.manager.createQueryBuilder(PostWithVersion, "post")
return connection.createQueryBuilder(PostWithVersion, "post")
.setLock("optimistic", 1)
.getManyAndCount().should.be.rejectedWith(OptimisticLockCanNotBeUsedError);
})));
it("should throw error if optimistic lock used with getRawMany method", () => Promise.all(connections.map(async connection => {
return connection.manager.createQueryBuilder(PostWithVersion, "post")
return connection.createQueryBuilder(PostWithVersion, "post")
.setLock("optimistic", 1)
.getRawMany().should.be.rejectedWith(OptimisticLockCanNotBeUsedError);
})));
it("should throw error if optimistic lock used with getRawOne method", () => Promise.all(connections.map(async connection => {
return connection.manager.createQueryBuilder(PostWithVersion, "post")
return connection.createQueryBuilder(PostWithVersion, "post")
.setLock("optimistic", 1)
.where("post.id = :id", { id: 1 })
.getRawOne().should.be.rejectedWith(OptimisticLockCanNotBeUsedError);
@ -175,7 +175,7 @@ describe("query builder > locking", () => {
it("should not throw error if optimistic lock used with getOne method", () => Promise.all(connections.map(async connection => {
return connection.manager.createQueryBuilder(PostWithVersion, "post")
return connection.createQueryBuilder(PostWithVersion, "post")
.setLock("optimistic", 1)
.where("post.id = :id", { id: 1 })
.getOne().should.not.be.rejected;
@ -187,7 +187,7 @@ describe("query builder > locking", () => {
post.title = "New post";
await connection.manager.save(post);
return connection.manager.createQueryBuilder(PostWithoutVersionAndUpdateDate, "post")
return connection.createQueryBuilder(PostWithoutVersionAndUpdateDate, "post")
.setLock("optimistic", 1)
.where("post.id = :id", { id: 1 })
.getOne().should.be.rejectedWith(NoVersionOrUpdateDateColumnError);
@ -199,7 +199,7 @@ describe("query builder > locking", () => {
post.title = "New post";
await connection.manager.save(post);
return connection.manager.createQueryBuilder(PostWithVersion, "post")
return connection.createQueryBuilder(PostWithVersion, "post")
.setLock("optimistic", 2)
.where("post.id = :id", { id: 1 })
.getOne().should.be.rejectedWith(OptimisticLockVersionMismatchError);
@ -211,7 +211,7 @@ describe("query builder > locking", () => {
post.title = "New post";
await connection.manager.save(post);
return connection.manager.createQueryBuilder(PostWithVersion, "post")
return connection.createQueryBuilder(PostWithVersion, "post")
.setLock("optimistic", 1)
.where("post.id = :id", { id: 1 })
.getOne().should.not.be.rejected;
@ -223,7 +223,7 @@ describe("query builder > locking", () => {
post.title = "New post";
await connection.manager.save(post);
return connection.manager.createQueryBuilder(PostWithUpdateDate, "post")
return connection.createQueryBuilder(PostWithUpdateDate, "post")
.setLock("optimistic", new Date(2017, 1, 1))
.where("post.id = :id", { id: 1 })
.getOne().should.be.rejectedWith(OptimisticLockVersionMismatchError);
@ -235,7 +235,7 @@ describe("query builder > locking", () => {
post.title = "New post";
await connection.manager.save(post);
return connection.manager.createQueryBuilder(PostWithUpdateDate, "post")
return connection.createQueryBuilder(PostWithUpdateDate, "post")
.setLock("optimistic", post.updateDate)
.where("post.id = :id", { id: 1 })
.getOne().should.not.be.rejected;
@ -248,12 +248,12 @@ describe("query builder > locking", () => {
await connection.manager.save(post);
return Promise.all([
connection.manager.createQueryBuilder(PostWithVersionAndUpdatedDate, "post")
connection.createQueryBuilder(PostWithVersionAndUpdatedDate, "post")
.setLock("optimistic", post.updateDate)
.where("post.id = :id", { id: 1 })
.getOne().should.not.be.rejected,
connection.manager.createQueryBuilder(PostWithVersionAndUpdatedDate, "post")
connection.createQueryBuilder(PostWithVersionAndUpdatedDate, "post")
.setLock("optimistic", 1)
.where("post.id = :id", { id: 1 })
.getOne().should.not.be.rejected

View File

@ -30,7 +30,7 @@ describe("query builder > select", () => {
})));
it("should append all entity mapped columns from both main selection and join selections to select statement", () => Promise.all(connections.map(async connection => {
const sql = connection.manager.createQueryBuilder(Post, "post")
const sql = connection.createQueryBuilder(Post, "post")
.leftJoinAndSelect("category", "category")
.disableEscaping()
.getSql();
@ -49,7 +49,7 @@ describe("query builder > select", () => {
})));
it("should append entity mapped columns from both main alias and join aliases to select statement", () => Promise.all(connections.map(async connection => {
const sql = connection.manager.createQueryBuilder(Post, "post")
const sql = connection.createQueryBuilder(Post, "post")
.select("post.id")
.addSelect("category.name")
.leftJoin("category", "category")
@ -62,7 +62,7 @@ describe("query builder > select", () => {
})));
it("should append entity mapped columns to select statement, if they passed as array", () => Promise.all(connections.map(async connection => {
const sql = connection.manager.createQueryBuilder(Post, "post")
const sql = connection.createQueryBuilder(Post, "post")
.select(["post.id", "post.title"])
.disableEscaping()
.getSql();
@ -71,7 +71,7 @@ describe("query builder > select", () => {
})));
it("should append raw sql to select statement", () => Promise.all(connections.map(async connection => {
const sql = connection.manager.createQueryBuilder(Post, "post")
const sql = connection.createQueryBuilder(Post, "post")
.select("COUNT(*) as cnt")
.disableEscaping()
.getSql();
@ -80,7 +80,7 @@ describe("query builder > select", () => {
})));
it("should append raw sql and entity mapped column to select statement", () => Promise.all(connections.map(async connection => {
const sql = connection.manager.createQueryBuilder(Post, "post")
const sql = connection.createQueryBuilder(Post, "post")
.select(["COUNT(*) as cnt", "post.title"])
.disableEscaping()
.getSql();
@ -89,7 +89,7 @@ describe("query builder > select", () => {
})));
it("should not create alias for selection, which is not entity mapped column", () => Promise.all(connections.map(async connection => {
const sql = connection.manager.createQueryBuilder(Post, "post")
const sql = connection.createQueryBuilder(Post, "post")
.select("post.name")
.disableEscaping()
.getSql();

View File

@ -1,22 +1,25 @@
import "reflect-metadata";
import {PostgresDriver} from "../../../src/driver/postgres/PostgresDriver";
import {Logger} from "../../../src/logger/Logger";
import {expect} from "chai";
import {Connection} from "../../../src/connection/Connection";
describe("github issues > #114 Can not be parsed correctly the URL of pg.", () => {
let driver: PostgresDriver;
before(() => driver = new PostgresDriver({
type: "postgres",
url: "postgres://test:test@localhost:5432/test",
}, new Logger({})));
let driver: PostgresDriver, connection: Connection;
before(() => {
connection = new Connection({
type: "postgres",
url: "postgres://test:test@localhost:5432/test",
});
driver = new PostgresDriver(connection);
});
it("should not fail in url parser", () => {
expect(driver.options.username).to.be.eq("test");
expect(driver.options.password).to.be.eq("test");
expect(driver.options.host).to.be.eq("localhost");
expect(driver.options.port).to.be.eq(5432);
expect(driver.options.database).to.be.eq("test");
expect(connection.options.username).to.be.eq("test");
expect(connection.options.password).to.be.eq("test");
expect(connection.options.host).to.be.eq("localhost");
expect(connection.options.port).to.be.eq(5432);
expect(connection.options.database).to.be.eq("test");
});
});

View File

@ -0,0 +1,17 @@
import {Entity} from "../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Column} from "../../../../src/decorator/columns/Column";
@Entity("Posts")
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column("date")
date: string;
}

View File

@ -0,0 +1,30 @@
import "reflect-metadata";
import {createTestingConnections, closeTestingConnections, reloadTestingDatabases} from "../../utils/test-utils";
import {Connection} from "../../../src/connection/Connection";
import {Post} from "./entity/Post";
import {expect} from "chai";
describe("github issues > #512 Table name escaping in UPDATE in QueryBuilder", () => {
let connections: Connection[];
before(async () => connections = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
schemaCreate: true,
dropSchemaOnConnection: true,
}));
beforeEach(() => reloadTestingDatabases(connections));
after(() => closeTestingConnections(connections));
it("should escape table name using driver's escape function in UPDATE", () => Promise.all(connections.map(async connection => {
const driver = connection.driver;
const queryBuilder = connection.entityManager.createQueryBuilder(Post, "post");
const query = queryBuilder
.update({
title: "Some Title",
})
.getSql();
return query.should.contain(driver.escapeTableName("Posts"));
})));
});

View File

@ -0,0 +1,12 @@
import { Entity } from "../../../../src/decorator/entity/Entity";
import { PrimaryGeneratedColumn } from "../../../../src/decorator/columns/PrimaryGeneratedColumn";
import { Column } from "../../../../src/decorator/columns/Column";
@Entity()
export class Car {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
}

View File

@ -0,0 +1,32 @@
import "reflect-metadata";
import {createTestingConnections, closeTestingConnections, reloadTestingDatabases} from "../../utils/test-utils";
import {Connection} from "../../../src/connection/Connection";
import {expect} from "chai";
import {Car} from "./entity/Car";
describe("github issues > #521 Attributes in UPDATE in QB arent getting replaced", () => {
let connections: Connection[];
before(async () => connections = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
schemaCreate: true,
dropSchemaOnConnection: true,
}));
beforeEach(() => reloadTestingDatabases(connections));
after(() => closeTestingConnections(connections));
it("should replace parameters", () => Promise.all(connections.map(async connection => {
const qb = connection.getRepository(Car).createQueryBuilder("car");
const [query, parameters] = qb
.update({
name: "Honda",
})
.where("name = :name", {
name: "Toyota",
})
.getSqlWithParameters();
return parameters.length.should.eql(2);
})));
});

View File

@ -1,8 +1,8 @@
import {ConnectionOptions} from "../../src/connection/ConnectionOptions";
import {createConnection, createConnections} from "../../src/index";
import {Connection} from "../../src/connection/Connection";
import {DriverType} from "../../src/driver/DriverOptions";
import {EntitySchema} from "../../src/entity-schema/EntitySchema";
import {DriverType} from "../../src/driver/DriverType";
/**
* Interface in which data is stored in ormconfig.json of the project.
@ -128,7 +128,7 @@ export function setupTestingConnections(options?: TestingOptions) {
return false;
if (options && options.enabledDrivers && options.enabledDrivers.length)
return options.enabledDrivers.indexOf(connectionOptions.driver.type) !== -1;
return options.enabledDrivers.indexOf(connectionOptions.driver!.type!) !== -1; // ! is temporary
if (connectionOptions.disabledIfNotEnabledImplicitly === true)
return false;