diff --git a/CHANGELOG.md b/CHANGELOG.md index c6cba4d55..158f4a064 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,8 +28,13 @@ each for its own `findOne*` or `find*` methods * 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 options now deprecated in connection options +* `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 diff --git a/src/connection/Connection.ts b/src/connection/Connection.ts index 5f33ab79a..9dc4bc65a 100644 --- a/src/connection/Connection.ts +++ b/src/connection/Connection.ts @@ -35,6 +35,19 @@ 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 {MysqlDriver} from "../driver/mysql/MysqlDriver"; +import {PostgresDriver} from "../driver/postgres/PostgresDriver"; +import {SqliteDriver} from "../driver/sqlite/SqliteDriver"; +import {OracleDriver} from "../driver/oracle/OracleDriver"; +import {SqlServerDriver} from "../driver/sqlserver/SqlServerDriver"; +import {WebsqlDriver} from "../driver/websql/WebsqlDriver"; +import {MissingDriverError} from "../driver/error/MissingDriverError"; +import {OrmUtils} from "../util/OrmUtils"; +import {QueryRunnerProviderAlreadyReleasedError} from "../query-runner/error/QueryRunnerProviderAlreadyReleasedError"; +import {QueryBuilder} from "../query-builder/QueryBuilder"; +import {DriverFactory} from "../driver/DriverFactory"; +import {EntityManagerFactory} from "../entity-manager/EntityManagerFactory"; /** * Connection is a single database ORM connection to a specific DBMS database. @@ -52,6 +65,11 @@ export class Connection { */ readonly name: string; + /** + * Connection options. + */ + readonly options: ConnectionOptions; + /** * Indicates if connection is initialized or not. */ @@ -63,34 +81,29 @@ export class Connection { readonly driver: Driver; /** - * Gets EntityManager of this connection. + * EntityManager of this connection. */ readonly manager: EntityManager; + /** + * Naming strategy used in the connection. + */ + readonly namingStrategy: NamingStrategyInterface; + /** * Logger used to log orm events. */ readonly logger: Logger; /** - * Naming strategy used in the connection. + * Migration instances that are registered for this connection. */ - readonly namingStrategy = new DefaultNamingStrategy(); + readonly migrations: MigrationInterface[] = []; /** - * All entity metadatas that are registered for this connection. + * Entity subscriber instances that are registered for this connection. */ - readonly entityMetadatas: EntityMetadata[] = []; - - /** - * Used to broadcast connection events. - */ - readonly broadcaster: Broadcaster; - - /** - * Used to wrap lazy relations to be able to perform lazy loadings. - */ - readonly lazyRelationsWrapper = new LazyRelationsWrapper(this); + readonly subscribers: EntitySubscriberInterface[] = []; // ------------------------------------------------------------------------- // Private Properties @@ -102,40 +115,21 @@ export class Connection { private repositoryAggregators: RepositoryAggregator[] = []; /** - * Entity subscribers that are registered for this connection. + * All entity metadatas that are registered for this connection. */ - private entitySubscribers: EntitySubscriberInterface[] = []; - - /** - * Registered entity classes to be used for this connection. - */ - private entityClasses: Function[] = []; - - /** - * Registered entity schemas to be used for this connection. - */ - private entitySchemas: EntitySchema[] = []; - - /** - * Registered subscriber classes to be used for this connection. - */ - private subscriberClasses: Function[] = []; - - /** - * Registered migration classes to be used for this connection. - */ - private migrationClasses: Function[] = []; + private entityMetadatas: EntityMetadata[] = []; // ------------------------------------------------------------------------- // Constructor // ------------------------------------------------------------------------- - constructor(name: string, driver: Driver, logger: Logger) { - this.name = name; - this.driver = driver; - this.logger = logger; - this.broadcaster = new Broadcaster(this, this.entitySubscribers); - this.manager = this.createEntityManager(); + constructor(options: ConnectionOptions) { + this.name = options.name || "default"; + this.options = options; + this.logger = new Logger(options.logging || {}); + this.namingStrategy = options.namingStrategy || new DefaultNamingStrategy(); + this.driver = new DriverFactory().create(this); + this.manager = new EntityManagerFactory().create(this); } // ------------------------------------------------------------------------- @@ -204,6 +198,7 @@ export class Connection { /** * 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 */ @@ -227,6 +222,7 @@ export class Connection { /** * 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 { const queryRunner = await this.driver.createQueryRunner(); @@ -235,6 +231,7 @@ export class Connection { /** * Runs all pending migrations. + * Can be used only after connection to the database is established. */ async runMigrations(): Promise { @@ -247,6 +244,7 @@ export class Connection { /** * Reverts last executed migration. + * Can be used only after connection to the database is established. */ async undoLastMigration(): Promise { @@ -258,91 +256,44 @@ export class Connection { } /** - * Imports entities from the given paths (directories) and registers them in the current connection. + * Creates a new entity manager with a single opened connection to the database. + * This may be useful if you want to perform all db queries within one connection. + * After finishing with entity manager, don't forget to release it, to release connection back to pool. */ - importEntitiesFromDirectories(paths: string[]): this { - this.importEntities(importClassesFromDirectories(paths)); - return this; + createIsolatedManager(queryRunnerProvider?: QueryRunnerProvider): EntityManager { + if (!queryRunnerProvider) + queryRunnerProvider = new QueryRunnerProvider(this.driver, true); + + return new EntityManager(this, queryRunnerProvider); } /** - * Imports entity schemas from the given paths (directories) and registers them in the current connection. + * Checks if entity metadata exist for the given entity class. */ - importEntitySchemaFromDirectories(paths: string[]): this { - this.importEntitySchemas(importJsonsFromDirectories(paths)); - return this; - } + hasMetadata(target: Function): boolean; /** - * Imports subscribers from the given paths (directories) and registers them in the current connection. + * Checks if entity metadata exist for the given entity target name or table name. */ - importSubscribersFromDirectories(paths: string[]): this { - this.importSubscribers(importClassesFromDirectories(paths)); - return this; - } + hasMetadata(target: string): boolean; /** - * Imports migrations 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. */ - importMigrationsFromDirectories(paths: string[]): this { - this.importMigrations(importClassesFromDirectories(paths)); - return this; - } + hasMetadata(target: Function|string): boolean; /** - * Imports entities and registers them in the current connection. + * Checks if entity metadata exist for the given entity class, target name or table name. */ - importEntities(entities: Function[]): this { - if (this.isConnected) - throw new CannotImportAlreadyConnectedError("entities", this.name); + hasMetadata(target: Function|string): boolean { + return !!this.entityMetadatas.find(metadata => { + if (metadata.target === target) + return true; + if (typeof target === "string") + return metadata.name === target || metadata.tableName === target; - 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 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(namingStrategy: NamingStrategyInterface): this { - if (this.isConnected) - throw new CannotUseNamingStrategyNotConnectedError(this.name); - - Object.assign(this, { namingStrategy: namingStrategy }); - return this; + return false; + }); } /** @@ -364,7 +315,14 @@ export class Connection { 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.entityMetadatas.find(metadata => { + if (metadata.target === target) + return true; + if (typeof target === "string") + return metadata.name === target || metadata.tableName === target; + + return false; + }); if (!metadata) throw new EntityMetadataNotFound(target); @@ -454,65 +412,6 @@ export class Connection { return this.findRepositoryAggregator(entityClassOrName).repository as MongoRepository; } - /** - * Gets specific repository for the given entity class. - * SpecificRepository is a special repository that contains specific and non standard repository methods. - * - * @experimental - */ - getSpecificRepository(entityClass: ObjectType): SpecificRepository; - - /** - * Gets specific repository for the given entity name. - * SpecificRepository is a special repository that contains specific and non standard repository methods. - * - * @experimental - */ - getSpecificRepository(entityName: string): SpecificRepository; - - /** - * 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(entityClassOrName: ObjectType|string): SpecificRepository; - - /** - * 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(entityClassOrName: ObjectType|string): SpecificRepository { - 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(migrationClass); - }); - } - - return []; - } - /** * Gets custom entity repository marked with @EntityRepository decorator. */ @@ -520,8 +419,89 @@ 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, + queryRunnerProvider?: QueryRunnerProvider): Promise { + 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 { + 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(entityClass: ObjectType|Function|string, alias: string, queryRunnerProvider?: QueryRunnerProvider): QueryBuilder; + + /** + * Creates a new query builder that can be used to build a sql query. + */ + createQueryBuilder(queryRunnerProvider?: QueryRunnerProvider): QueryBuilder; + + /** + * Creates a new query builder that can be used to build a sql query. + */ + createQueryBuilder(entityClass?: ObjectType|Function|string|QueryRunnerProvider, alias?: string, queryRunnerProvider?: QueryRunnerProvider): QueryBuilder { + 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); + } + } + // ------------------------------------------------------------------------- - // Deprecated + // Deprecated Public Methods // ------------------------------------------------------------------------- /** @@ -533,6 +513,40 @@ export class Connection { return this.manager; } + /** + * Gets specific repository for the given entity class. + * 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(entityClass: ObjectType): SpecificRepository; + + /** + * Gets specific repository for the given entity 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(entityName: string): SpecificRepository; + + /** + * 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(entityClassOrName: ObjectType|string): SpecificRepository; + + /** + * 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(entityClassOrName: ObjectType|string): SpecificRepository { + return this.findRepositoryAggregator(entityClassOrName).specificRepository; + } + // ------------------------------------------------------------------------- // Protected Methods // ------------------------------------------------------------------------- @@ -541,10 +555,7 @@ export class Connection { * Finds repository aggregator of the given entity class or name. */ protected findRepositoryAggregator(entityClassOrName: ObjectType|string): RepositoryAggregator { - // if (!this.isConnected) - // throw new NoConnectionForRepositoryError(this.name); - - if (!this.entityMetadatas.find(metadata => metadata.target === entityClassOrName || (typeof entityClassOrName === "string" && metadata.targetName === entityClassOrName))) + if (!this.hasMetadata(entityClassOrName)) throw new RepositoryNotFoundError(this.name, entityClassOrName); const metadata = this.getMetadata(entityClassOrName); @@ -560,56 +571,59 @@ export class Connection { */ public buildMetadatas() { - this.entitySubscribers.length = 0; + // import entity schemas + const [entitySchemaDirectories, entitySchemaClasses] = OrmUtils.splitStringsAndClasses(this.options.entitySchemas || []); + entitySchemaClasses.push(...importJsonsFromDirectories(entitySchemaDirectories)); + + const [entityDirectories, entityClasses] = OrmUtils.splitStringsAndClasses(this.options.entities || []); + entityClasses.push(...importClassesFromDirectories(entityDirectories)); + + const [subscriberDirectories, subscriberClasses] = OrmUtils.splitStringsAndClasses(this.options.subscribers || []); + subscriberClasses.push(...importClassesFromDirectories(subscriberDirectories)); + + const [migrationDirectories, migrationClasses] = OrmUtils.splitStringsAndClasses(this.options.migrations || []); + migrationClasses.push(...importClassesFromDirectories(migrationDirectories)); + + // create migration instances + const migrations = migrationClasses.map(migrationClass => { + return getFromContainer(migrationClass); + }); + Object.assign(this, { migrations }); + + this.subscribers.length = 0; this.repositoryAggregators.length = 0; this.entityMetadatas.length = 0; const entityMetadataValidator = new EntityMetadataValidator(); // take imported event subscribers - if (this.subscriberClasses && - this.subscriberClasses.length && - !PlatformTools.getEnvVariable("SKIP_SUBSCRIBERS_LOADING")) { + if (!PlatformTools.getEnvVariable("SKIP_SUBSCRIBERS_LOADING")) { getMetadataArgsStorage() - .filterSubscribers(this.subscriberClasses) + .filterSubscribers(subscriberClasses) .map(metadata => getFromContainer(metadata.target)) - .forEach(subscriber => this.entitySubscribers.push(subscriber)); + .forEach(subscriber => this.subscribers.push(subscriber)); } // take imported entity listeners - if (this.entityClasses && this.entityClasses.length) { - - // 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)); - }); - } + // build entity metadatas from metadata args storage (collected from decorators) + new EntityMetadataBuilder(this, getMetadataArgsStorage()) + .build(entityClasses) + .forEach(metadata => { + this.entityMetadatas.push(metadata); + this.repositoryAggregators.push(new RepositoryAggregator(this, metadata)); + }); // 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)); - }); - } + const metadataArgsStorage = getFromContainer(EntitySchemaTransformer) + .transform(entitySchemaClasses); + new EntityMetadataBuilder(this, metadataArgsStorage) + .build() + .forEach(metadata => { + this.entityMetadatas.push(metadata); + this.repositoryAggregators.push(new RepositoryAggregator(this, metadata)); + }); entityMetadataValidator.validateMany(this.entityMetadatas); } - /** - * 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); - } - } \ No newline at end of file diff --git a/src/connection/ConnectionManager.ts b/src/connection/ConnectionManager.ts index 56f9700f3..f23b44b68 100644 --- a/src/connection/ConnectionManager.ts +++ b/src/connection/ConnectionManager.ts @@ -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,46 +53,20 @@ export class ConnectionManager { */ create(options: ConnectionOptions): Connection { - const logger = new Logger(options.logging || {}); - const driver = this.createDriver(options.driver || {} as DriverOptions, logger); // || {} is temporary - 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 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.namingStrategy) - connection.useNamingStrategy(options.namingStrategy); - + const connection = new Connection(options); + this.connections.push(connection); return connection; } @@ -380,30 +343,34 @@ export class ConnectionManager { 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(", ")}.` : "")); + let connectionOptions: ConnectionOptions = Object.assign({}, options); // normalize directory paths if (options.entities) { - options.entities = (options.entities as any[]).map(entity => { + 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) { - options.subscribers = (options.subscribers as any[]).map(subscriber => { + 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) { - options.migrations = (options.migrations as any[]).map(migration => { + 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); @@ -433,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(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; - } - } diff --git a/src/connection/ConnectionOptions.ts b/src/connection/ConnectionOptions.ts index 65180ceea..bfe0e6414 100644 --- a/src/connection/ConnectionOptions.ts +++ b/src/connection/ConnectionOptions.ts @@ -15,64 +15,64 @@ export interface ConnectionOptions { * Connection name. If connection name is not given then it will be called "default". * Different connections must have different names. */ - name?: string; + readonly name?: string; /** * Database options of this connection. * * @deprecated Define options right in the connection options section. */ - driver?: DriverOptions; + readonly driver?: DriverOptions; /** * Database type. This value is required. */ - type?: DriverType; + readonly type?: DriverType; /** * Connection url to where perform connection to. */ - url?: string; + readonly url?: string; /** * Database host. */ - host?: string; + readonly host?: string; /** * Database host port. */ - port?: number; + readonly port?: number; /** * Database username. */ - username?: string; + readonly username?: string; /** * Database password. */ - password?: string; + readonly password?: string; /** * Database name to connect to. */ - database?: string; + readonly database?: string; /** * Schema name. By default is "public" (used only in Postgres databases). */ - schemaName?: string; + readonly schemaName?: string; /** * Connection SID (used for Oracle databases). */ - sid?: string; + readonly sid?: string; /** * Storage type or path to the storage (used for SQLite databases). */ - storage?: string; + readonly storage?: string; /** * Indicates if connection pooling should be used or not. @@ -81,64 +81,64 @@ export interface ConnectionOptions { * * @todo: rename to disablePool? What about mongodb pool? */ - usePool?: boolean; + readonly usePool?: boolean; /** * Extra connection options to be passed to the underlying driver. */ - extra?: any; + readonly extra?: any; /** * Prefix to use on all tables (collections) of this connection in the database. * * @todo: rename to entityPrefix */ - tablesPrefix?: string; + readonly tablesPrefix?: string; /** * Naming strategy to be used to name tables and columns in the database. */ - namingStrategy?: NamingStrategyInterface; + readonly namingStrategy?: NamingStrategyInterface; /** * Entities to be loaded for this connection. * Accepts both entity classes and directories where from entities need to be loaded. * Directories support glob patterns. */ - entities?: Function[]|string[]; + readonly entities?: Function[]|string[]; /** * Subscribers to be loaded for this connection. * Accepts both subscriber classes and directories where from subscribers need to be loaded. * Directories support glob patterns. */ - subscribers?: Function[]|string[]; + readonly subscribers?: 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. * Directories support glob patterns. */ - entitySchemas?: EntitySchema[]|string[]; + readonly entitySchemas?: EntitySchema[]|string[]; /** * Migrations to be loaded for this connection. * Accepts both migration classes and directories where from migrations need to be loaded. * Directories support glob patterns. */ - migrations?: Function[]|string[]; + readonly migrations?: Function[]|string[]; /** * Logging options. */ - logging?: LoggerOptions; + readonly logging?: LoggerOptions; /** * Drops the schema each time connection is being established. * Be careful with this option and don't use this in production - otherwise you'll loose all production data. * This option is useful during debug and development. */ - dropSchemaOnConnection?: boolean; + readonly dropSchemaOnConnection?: boolean; /** * Indicates if database schema should be auto created on every application launch. @@ -151,7 +151,7 @@ export interface ConnectionOptions { * * todo: rename it simply to synchronize: boolean ? */ - autoSchemaSync?: boolean; + readonly autoSchemaSync?: boolean; /** * Indicates if migrations should be auto run on every application launch. @@ -159,7 +159,7 @@ export interface ConnectionOptions { * * todo: rename it simply to runMigrations: boolean ? */ - autoMigrationsRun?: boolean; + readonly autoMigrationsRun?: boolean; /** * Environment in which connection will run. @@ -168,27 +168,27 @@ export interface ConnectionOptions { * then this connection will be created. On any other NODE_ENV value it will be skipped. * This option is specific to the configuration in the ormconfig.json file. */ - environment?: string; + readonly environment?: string; /** * CLI settings. */ - cli?: { + readonly cli?: { /** * Directory where entities should be created by default. */ - entitiesDir?: string; + readonly entitiesDir?: string; /** * Directory where migrations should be created by default. */ - migrationsDir?: string; + readonly migrationsDir?: string; /** * Directory where subscribers should be created by default. */ - subscribersDir?: string; + readonly subscribersDir?: string; }; diff --git a/src/driver/DriverFactory.ts b/src/driver/DriverFactory.ts new file mode 100644 index 000000000..a1d69425b --- /dev/null +++ b/src/driver/DriverFactory.ts @@ -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!); + } + } + +} \ No newline at end of file diff --git a/src/driver/DriverOptions.ts b/src/driver/DriverOptions.ts index 69e7deb8e..37cdde2c7 100644 --- a/src/driver/DriverOptions.ts +++ b/src/driver/DriverOptions.ts @@ -2,13 +2,15 @@ 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. */ - type: DriverType; + type?: DriverType; /** * Connection url to where perform connection to. diff --git a/src/connection/error/MissingDriverError.ts b/src/driver/error/MissingDriverError.ts similarity index 100% rename from src/connection/error/MissingDriverError.ts rename to src/driver/error/MissingDriverError.ts diff --git a/src/driver/mongodb/MongoDriver.ts b/src/driver/mongodb/MongoDriver.ts index deb758dfa..e970c2fe2 100644 --- a/src/driver/mongodb/MongoDriver.ts +++ b/src/driver/mongodb/MongoDriver.ts @@ -13,6 +13,7 @@ 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. @@ -57,18 +58,15 @@ export class MongoDriver implements Driver { // Constructor // ------------------------------------------------------------------------- - constructor(options: DriverOptions, logger: Logger, mongodb?: any) { + constructor(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.options = connection.options; + this.logger = connection.logger; + this.mongodb = this.loadDependencies(); } // ------------------------------------------------------------------------- diff --git a/src/driver/mysql/MysqlDriver.ts b/src/driver/mysql/MysqlDriver.ts index 4f422e7ed..ffde84f16 100644 --- a/src/driver/mysql/MysqlDriver.ts +++ b/src/driver/mysql/MysqlDriver.ts @@ -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 MySQL DBMS. @@ -63,11 +64,10 @@ export class MysqlDriver implements Driver { // Constructor // ------------------------------------------------------------------------- - constructor(options: DriverOptions, logger: Logger, mysql?: any) { + constructor(connection: Connection) { - this.options = DriverUtils.buildDriverOptions(options); - this.logger = logger; - this.mysql = mysql; + this.options = DriverUtils.buildDriverOptions(connection.options); + this.logger = connection.logger; // validate options to make sure everything is set if (!(this.options.host || (this.options.extra && this.options.extra.socketPath))) @@ -77,9 +77,8 @@ export class MysqlDriver implements Driver { if (!this.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(); } // ------------------------------------------------------------------------- diff --git a/src/driver/oracle/OracleDriver.ts b/src/driver/oracle/OracleDriver.ts index e4be00dec..9f1baea1c 100644 --- a/src/driver/oracle/OracleDriver.ts +++ b/src/driver/oracle/OracleDriver.ts @@ -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. @@ -65,11 +66,10 @@ export class OracleDriver implements Driver { // Constructor // ------------------------------------------------------------------------- - constructor(options: DriverOptions, logger: Logger, oracle?: any) { + constructor(connection: Connection) { - this.options = DriverUtils.buildDriverOptions(options, { useSid: true }); - this.logger = logger; - this.oracle = oracle; + this.options = connection.options; + this.logger = connection.logger; // validate options to make sure everything is set if (!this.options.host) @@ -79,10 +79,8 @@ export class OracleDriver implements Driver { if (!this.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; } diff --git a/src/driver/postgres/PostgresDriver.ts b/src/driver/postgres/PostgresDriver.ts index f057e073d..697a6c146 100644 --- a/src/driver/postgres/PostgresDriver.ts +++ b/src/driver/postgres/PostgresDriver.ts @@ -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 @@ -75,12 +76,11 @@ export class PostgresDriver implements Driver { // Constructor // ------------------------------------------------------------------------- - constructor(connectionOptions: DriverOptions, logger: Logger, postgres?: any) { + constructor(connection: Connection) { - this.options = DriverUtils.buildDriverOptions(connectionOptions); - this.logger = logger; - this.postgres = postgres; - this.schemaName = connectionOptions.schemaName || "public"; + this.options = DriverUtils.buildDriverOptions(connection.options); + this.logger = connection.logger; + this.schemaName = connection.options.schemaName || "public"; // validate options to make sure everything is set if (!this.options.host) @@ -90,9 +90,8 @@ export class PostgresDriver implements Driver { if (!this.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(); } // ------------------------------------------------------------------------- diff --git a/src/driver/sqlite/SqliteDriver.ts b/src/driver/sqlite/SqliteDriver.ts index 0b79c185d..50fbbfb9c 100644 --- a/src/driver/sqlite/SqliteDriver.ts +++ b/src/driver/sqlite/SqliteDriver.ts @@ -14,6 +14,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 sqlite DBMS. @@ -52,19 +53,17 @@ export class SqliteDriver implements Driver { // Constructor // ------------------------------------------------------------------------- - constructor(connectionOptions: DriverOptions, logger: Logger, sqlite?: any) { + constructor(connection: Connection) { - this.options = connectionOptions; - this.logger = logger; - this.sqlite = sqlite; + this.options = connection.options; + this.logger = connection.logger; // validate options to make sure everything is set if (!this.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(); } // ------------------------------------------------------------------------- diff --git a/src/driver/sqlserver/SqlServerDriver.ts b/src/driver/sqlserver/SqlServerDriver.ts index 298fb02ff..7ecf2e74f 100644 --- a/src/driver/sqlserver/SqlServerDriver.ts +++ b/src/driver/sqlserver/SqlServerDriver.ts @@ -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. @@ -63,11 +64,10 @@ export class SqlServerDriver implements Driver { // Constructor // ------------------------------------------------------------------------- - constructor(options: DriverOptions, logger: Logger, mssql?: any) { + constructor(connection: Connection) { - this.options = DriverUtils.buildDriverOptions(options); - this.logger = logger; - this.mssql = mssql; + this.options = DriverUtils.buildDriverOptions(connection.options); + this.logger = connection.logger; // validate options to make sure everything is set if (!this.options.host) @@ -77,9 +77,8 @@ export class SqlServerDriver implements Driver { if (!this.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(); } // ------------------------------------------------------------------------- diff --git a/src/driver/websql/WebsqlDriver.ts b/src/driver/websql/WebsqlDriver.ts index 89f84f16e..2dbe46389 100644 --- a/src/driver/websql/WebsqlDriver.ts +++ b/src/driver/websql/WebsqlDriver.ts @@ -13,6 +13,7 @@ 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. @@ -51,10 +52,10 @@ export class WebsqlDriver implements Driver { // Constructor // ------------------------------------------------------------------------- - constructor(options: DriverOptions, logger: Logger) { + constructor(connection: Connection) { - this.options = DriverUtils.buildDriverOptions(options); - this.logger = logger; + this.options = DriverUtils.buildDriverOptions(connection.options); + this.logger = connection.logger; // validate options to make sure everything is set // if (!this.options.host) diff --git a/src/entity-manager/EntityManager.ts b/src/entity-manager/EntityManager.ts index e1fd3ae65..207c84b48 100644 --- a/src/entity-manager/EntityManager.ts +++ b/src/entity-manager/EntityManager.ts @@ -63,6 +63,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): Promise { + return this.connection.transaction(runInTransaction, this.queryRunnerProvider); + } + + /** + * Executes raw SQL query and returns raw database results. + */ + async query(query: string, parameters?: any[]): Promise { + return this.connection.query(query, parameters, this.queryRunnerProvider); + } + + /** + * Creates a new query builder that can be used to build a sql query. + */ + createQueryBuilder(entityClass: ObjectType|Function|string, alias: string, queryRunnerProvider?: QueryRunnerProvider): QueryBuilder; + + /** + * Creates a new query builder that can be used to build a sql query. + */ + createQueryBuilder(queryRunnerProvider?: QueryRunnerProvider): QueryBuilder; + + /** + * Creates a new query builder that can be used to build a sql query. + */ + createQueryBuilder(entityClass?: ObjectType|Function|string|QueryRunnerProvider, alias?: string, queryRunnerProvider?: QueryRunnerProvider): QueryBuilder { + 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 +157,6 @@ export class EntityManager { return metadata.getEntityIdMixedMap(entity); } - /** - * Creates a new query builder that can be used to build a sql query. - */ - createQueryBuilder(entityClass: ObjectType|Function|string, alias: string, queryRunnerProvider?: QueryRunnerProvider): QueryBuilder { - 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 +558,6 @@ export class EntityManager { return qb.getOne(); } - /** - * Executes raw SQL query and returns raw database results. - */ - async query(query: string, parameters?: any[]): Promise { - 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): Promise { - 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). */ @@ -741,7 +721,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 +743,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); diff --git a/src/entity-manager/EntityManagerFactory.ts b/src/entity-manager/EntityManagerFactory.ts new file mode 100644 index 000000000..9b580cf04 --- /dev/null +++ b/src/entity-manager/EntityManagerFactory.ts @@ -0,0 +1,21 @@ +import {Connection} from "../connection/Connection"; +import {EntityManager} from "./EntityManager"; +import {MongoEntityManager} from "./MongoEntityManager"; +import {MongoDriver} from "../driver/mongodb/MongoDriver"; + +/** + * Helps to create entity managers. + */ +export class EntityManagerFactory { + + /** + * Creates a new entity manager depend on a given connection's driver. + */ + create(connection: Connection): EntityManager { + if (connection.driver instanceof MongoDriver) + return new MongoEntityManager(connection); + + return new EntityManager(connection); + } + +} \ No newline at end of file diff --git a/src/entity-manager/MongoEntityManager.ts b/src/entity-manager/MongoEntityManager.ts index 5ce9e5c8a..c714bf008 100644 --- a/src/entity-manager/MongoEntityManager.ts +++ b/src/entity-manager/MongoEntityManager.ts @@ -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 { - 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): Promise { - 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(entityClassOrName: ObjectType|string, alias: string, queryRunnerProvider?: QueryRunnerProvider): QueryBuilder { - throw new Error(`Query Builder is not supported by MongoDB.`); - } - /** * Finds entities that match given find options or conditions. */ diff --git a/src/lazy-loading/LazyRelationsWrapper.ts b/src/lazy-loading/LazyRelationsWrapper.ts index 320bd69bf..9f5f117f7 100644 --- a/src/lazy-loading/LazyRelationsWrapper.ts +++ b/src/lazy-loading/LazyRelationsWrapper.ts @@ -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 { - 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 ")) diff --git a/src/metadata-builder/ClosureJunctionEntityMetadataBuilder.ts b/src/metadata-builder/ClosureJunctionEntityMetadataBuilder.ts index fefaab5f1..41e9ba046 100644 --- a/src/metadata-builder/ClosureJunctionEntityMetadataBuilder.ts +++ b/src/metadata-builder/ClosureJunctionEntityMetadataBuilder.ts @@ -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. diff --git a/src/metadata-builder/EntityMetadataBuilder.ts b/src/metadata-builder/EntityMetadataBuilder.ts index 9c602fe97..4e2f414f1 100644 --- a/src/metadata-builder/EntityMetadataBuilder.ts +++ b/src/metadata-builder/EntityMetadataBuilder.ts @@ -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); @@ -206,7 +209,8 @@ export class EntityMetadataBuilder { entityMetadata.relations .filter(relation => relation.isLazy) .forEach(relation => { - this.connection.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; diff --git a/src/metadata-builder/JunctionEntityMetadataBuilder.ts b/src/metadata-builder/JunctionEntityMetadataBuilder.ts index a7f3d0e30..a5488c7a5 100644 --- a/src/metadata-builder/JunctionEntityMetadataBuilder.ts +++ b/src/metadata-builder/JunctionEntityMetadataBuilder.ts @@ -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. diff --git a/src/metadata/EntityMetadata.ts b/src/metadata/EntityMetadata.ts index db9e17edf..a730f15b9 100644 --- a/src/metadata/EntityMetadata.ts +++ b/src/metadata/EntityMetadata.ts @@ -343,8 +343,8 @@ export class EntityMetadata { args: TableMetadataArgs }) { const namingStrategy = options.connection.namingStrategy; - const tablesPrefix = options.connection.driver.options.tablesPrefix; - this.lazyRelationsWrapper = options.connection.lazyRelationsWrapper; + 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; diff --git a/src/migration/MigrationExecutor.ts b/src/migration/MigrationExecutor.ts index 6f0080587..1ae66dfdc 100644 --- a/src/migration/MigrationExecutor.ts +++ b/src/migration/MigrationExecutor.ts @@ -37,7 +37,7 @@ export class MigrationExecutor { */ async executePendingMigrations(): Promise { 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 { 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 { - 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) diff --git a/src/persistence/SubjectOperationExecutor.ts b/src/persistence/SubjectOperationExecutor.ts index 5adec0986..19ab0d92e 100644 --- a/src/persistence/SubjectOperationExecutor.ts +++ b/src/persistence/SubjectOperationExecutor.ts @@ -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) { diff --git a/src/query-builder/JoinAttribute.ts b/src/query-builder/JoinAttribute.ts index f88a2680d..2fd07cb36 100644 --- a/src/query-builder/JoinAttribute.ts +++ b/src/query-builder/JoinAttribute.ts @@ -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; + }*/ } /** diff --git a/src/query-builder/QueryBuilder.ts b/src/query-builder/QueryBuilder.ts index 66da62dbb..d45be48c5 100644 --- a/src/query-builder/QueryBuilder.ts +++ b/src/query-builder/QueryBuilder.ts @@ -25,6 +25,7 @@ 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 {Broadcaster} from "../subscriber/Broadcaster"; // todo: fix problem with long aliases eg getMaxIdentifierLength @@ -196,31 +197,19 @@ export class QueryBuilder { * 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; @@ -900,6 +889,7 @@ export class QueryBuilder { */ 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); @@ -996,8 +986,9 @@ export class QueryBuilder { 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 { @@ -1015,7 +1006,7 @@ export class QueryBuilder { 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, @@ -1152,7 +1143,7 @@ export class QueryBuilder { * Clones query builder as it is. */ clone(options?: { queryRunnerProvider?: QueryRunnerProvider }): QueryBuilder { - 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; } diff --git a/src/query-builder/relation-count/RelationCountLoader.ts b/src/query-builder/relation-count/RelationCountLoader.ts index 19bca3c33..618ffdb5d 100644 --- a/src/query-builder/relation-count/RelationCountLoader.ts +++ b/src/query-builder/relation-count/RelationCountLoader.ts @@ -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); diff --git a/src/query-builder/relation-id/RelationIdLoader.ts b/src/query-builder/relation-id/RelationIdLoader.ts index 500772ac8..e0f9cd79a 100644 --- a/src/query-builder/relation-id/RelationIdLoader.ts +++ b/src/query-builder/relation-id/RelationIdLoader.ts @@ -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); diff --git a/src/query-runner/QueryRunnerProvider.ts b/src/query-runner/QueryRunnerProvider.ts index ec4107222..2d290113b 100644 --- a/src/query-runner/QueryRunnerProvider.ts +++ b/src/query-runner/QueryRunnerProvider.ts @@ -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 { diff --git a/src/repository/SpecificRepository.ts b/src/repository/SpecificRepository.ts index 7ad1d108a..c14b40693 100644 --- a/src/repository/SpecificRepository.ts +++ b/src/repository/SpecificRepository.ts @@ -254,9 +254,10 @@ export class SpecificRepository { 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 { 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 { 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 { 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 { // 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]}); diff --git a/src/subscriber/Broadcaster.ts b/src/subscriber/Broadcaster.ts index 4251581ab..770fb1325 100644 --- a/src/subscriber/Broadcaster.ts +++ b/src/subscriber/Broadcaster.ts @@ -15,8 +15,7 @@ export class Broadcaster { // Constructor // ------------------------------------------------------------------------- - constructor(private connection: Connection, - private subscriberMetadatas: EntitySubscriberInterface[]) { + 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)); diff --git a/src/util/OrmUtils.ts b/src/util/OrmUtils.ts index e8708aa3a..0f1b95da1 100644 --- a/src/util/OrmUtils.ts +++ b/src/util/OrmUtils.ts @@ -2,6 +2,13 @@ import {ObjectLiteral} from "../common/ObjectLiteral"; export class OrmUtils { + static splitStringsAndClasses(strAndClses: string[]|T[]): [string[], T[]] { + return [ + (strAndClses as string[]).filter(str => typeof str === "string"), + (strAndClses as T[]).filter(cls => typeof cls !== "string"), + ]; + } + static groupBy(array: T[], propertyCallback: (item: T) => R): { id: R, items: T[] }[] { return array.reduce((groupedArray, value) => { const key = propertyCallback(value); diff --git a/test/functional/connection/connection.ts b/test/functional/connection/connection.ts index af8835f26..7aa38feda 100644 --- a/test/functional/connection/connection.ts +++ b/test/functional/connection/connection.ts @@ -123,24 +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.importEntitiesFromDirectories([])).to.throw(Error); // CannotImportAlreadyConnectedError - expect(() => connection.importEntitySchemaFromDirectories([])).to.throw(Error); // CannotImportAlreadyConnectedError - expect(() => connection.importSubscribersFromDirectories([])).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(new DefaultNamingStrategy())).to.throw(Error); // CannotUseNamingStrategyNotConnectedError - })); - it("should be able to close a connection", async () => Promise.all(connections.map(connection => { return connection.close(); }))); @@ -228,104 +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.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.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("skip schema generation when skipSchemaSync option is used", function() { let connections: Connection[]; diff --git a/test/functional/query-builder/locking/query-builder-locking.ts b/test/functional/query-builder/locking/query-builder-locking.ts index b13e81604..622d11242 100644 --- a/test/functional/query-builder/locking/query-builder-locking.ts +++ b/test/functional/query-builder/locking/query-builder-locking.ts @@ -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 diff --git a/test/functional/query-builder/select/query-builder-select.ts b/test/functional/query-builder/select/query-builder-select.ts index ffac429ad..e9b0ad190 100644 --- a/test/functional/query-builder/select/query-builder-select.ts +++ b/test/functional/query-builder/select/query-builder-select.ts @@ -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(); diff --git a/test/github-issues/114/issue-114.ts b/test/github-issues/114/issue-114.ts index 1c27d2250..a3c03bf06 100644 --- a/test/github-issues/114/issue-114.ts +++ b/test/github-issues/114/issue-114.ts @@ -2,14 +2,15 @@ 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({ + before(() => driver = new PostgresDriver(new Connection({ type: "postgres", url: "postgres://test:test@localhost:5432/test", - }, new Logger({}))); + }))); it("should not fail in url parser", () => { expect(driver.options.username).to.be.eq("test"); diff --git a/test/utils/test-utils.ts b/test/utils/test-utils.ts index ea3bf6df6..c20d08bda 100644 --- a/test/utils/test-utils.ts +++ b/test/utils/test-utils.ts @@ -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; // ! is temporary + return options.enabledDrivers.indexOf(connectionOptions.driver!.type!) !== -1; // ! is temporary if (connectionOptions.disabledIfNotEnabledImplicitly === true) return false;