refactoring connection class

This commit is contained in:
Umed Khudoiberdiev 2017-06-08 12:56:32 +05:00
parent 365055c6ff
commit edc6a31b57
37 changed files with 542 additions and 685 deletions

View File

@ -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

View File

@ -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<any>[] = [];
// -------------------------------------------------------------------------
// 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<any>[] = [];
/**
* 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<void> {
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<void> {
@ -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<void> {
@ -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<Entity>;
}
/**
* Gets specific repository for the given entity class.
* SpecificRepository is a special repository that contains specific and non standard repository methods.
*
* @experimental
*/
getSpecificRepository<Entity>(entityClass: ObjectType<Entity>): SpecificRepository<Entity>;
/**
* Gets specific repository for the given entity name.
* SpecificRepository is a special repository that contains specific and non standard repository methods.
*
* @experimental
*/
getSpecificRepository<Entity>(entityName: string): SpecificRepository<Entity>;
/**
* Gets specific repository for the given entity class or name.
* SpecificRepository is a special repository that contains specific and non standard repository methods.
*
* @experimental
*/
getSpecificRepository<Entity>(entityClassOrName: ObjectType<Entity>|string): SpecificRepository<Entity>;
/**
* Gets specific repository for the given entity class or name.
* SpecificRepository is a special repository that contains specific and non standard repository methods.
*
* @experimental
*/
getSpecificRepository<Entity>(entityClassOrName: ObjectType<Entity>|string): SpecificRepository<Entity> {
return this.findRepositoryAggregator(entityClassOrName).specificRepository;
}
/**
* Creates a new entity manager with a single opened connection to the database.
* This may be useful if you want to perform all db queries within one connection.
* After finishing with entity manager, don't forget to release it, to release connection back to pool.
*/
createEntityManagerWithSingleDatabaseConnection(queryRunnerProvider?: QueryRunnerProvider): EntityManager {
if (!queryRunnerProvider)
queryRunnerProvider = new QueryRunnerProvider(this.driver, true);
return new EntityManager(this, queryRunnerProvider);
}
/**
* Gets migration instances that are registered for this connection.
*/
getMigrations(): MigrationInterface[] {
if (this.migrationClasses && this.migrationClasses.length) {
return this.migrationClasses.map(migrationClass => {
return getFromContainer<MigrationInterface>(migrationClass);
});
}
return [];
}
/**
* 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<any>,
queryRunnerProvider?: QueryRunnerProvider): Promise<any> {
if (this instanceof MongoEntityManager)
throw new Error(`Transactions aren't supported by MongoDB.`);
if (queryRunnerProvider && queryRunnerProvider.isReleased)
throw new QueryRunnerProviderAlreadyReleasedError();
const usedQueryRunnerProvider = queryRunnerProvider || new QueryRunnerProvider(this.driver, true);
const queryRunner = await usedQueryRunnerProvider.provide();
const transactionEntityManager = new EntityManager(this, usedQueryRunnerProvider);
try {
await queryRunner.beginTransaction();
const result = await runInTransaction(transactionEntityManager);
await queryRunner.commitTransaction();
return result;
} catch (err) {
await queryRunner.rollbackTransaction();
throw err;
} finally {
await usedQueryRunnerProvider.release(queryRunner);
if (!queryRunnerProvider) // if we used a new query runner provider then release it
await usedQueryRunnerProvider.releaseReused(); // todo: why we don't do same in query method?
}
}
/**
* Executes raw SQL query and returns raw database results.
*/
async query(query: string, parameters?: any[], queryRunnerProvider?: QueryRunnerProvider): Promise<any> {
if (this instanceof MongoEntityManager)
throw new Error(`Queries aren't supported by MongoDB.`);
if (queryRunnerProvider && queryRunnerProvider.isReleased)
throw new QueryRunnerProviderAlreadyReleasedError();
const usedQueryRunnerProvider = queryRunnerProvider || new QueryRunnerProvider(this.driver);
const queryRunner = await usedQueryRunnerProvider.provide();
try {
return await queryRunner.query(query, parameters); // await is needed here because we are using finally
} finally {
await usedQueryRunnerProvider.release(queryRunner);
}
}
/**
* Creates a new query builder that can be used to build a sql query.
*/
createQueryBuilder<Entity>(entityClass: ObjectType<Entity>|Function|string, alias: string, queryRunnerProvider?: QueryRunnerProvider): QueryBuilder<Entity>;
/**
* Creates a new query builder that can be used to build a sql query.
*/
createQueryBuilder(queryRunnerProvider?: QueryRunnerProvider): QueryBuilder<any>;
/**
* Creates a new query builder that can be used to build a sql query.
*/
createQueryBuilder<Entity>(entityClass?: ObjectType<Entity>|Function|string|QueryRunnerProvider, alias?: string, queryRunnerProvider?: QueryRunnerProvider): QueryBuilder<Entity> {
if (this instanceof MongoEntityManager)
throw new Error(`Query Builder is not supported by MongoDB.`);
if (alias) {
const metadata = this.getMetadata(entityClass as Function|string);
return new QueryBuilder(this, queryRunnerProvider)
.select(alias)
.from(metadata.target, alias);
} else {
return new QueryBuilder(this, entityClass as QueryRunnerProvider|undefined);
}
}
// -------------------------------------------------------------------------
// 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<Entity>(entityClass: ObjectType<Entity>): SpecificRepository<Entity>;
/**
* Gets specific repository for the given entity name.
* SpecificRepository is a special repository that contains specific and non standard repository methods.
*
* @deprecated don't use it, it will be refactored or removed in the future versions
*/
getSpecificRepository<Entity>(entityName: string): SpecificRepository<Entity>;
/**
* Gets specific repository for the given entity class or name.
* SpecificRepository is a special repository that contains specific and non standard repository methods.
*
* @deprecated don't use it, it will be refactored or removed in the future versions
*/
getSpecificRepository<Entity>(entityClassOrName: ObjectType<Entity>|string): SpecificRepository<Entity>;
/**
* Gets specific repository for the given entity class or name.
* SpecificRepository is a special repository that contains specific and non standard repository methods.
*
* @deprecated don't use it, it will be refactored or removed in the future versions
*/
getSpecificRepository<Entity>(entityClassOrName: ObjectType<Entity>|string): SpecificRepository<Entity> {
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<any>|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<MigrationInterface>(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);
}
}

View File

@ -1,21 +1,10 @@
import {Connection} from "./Connection";
import {ConnectionNotFoundError} from "./error/ConnectionNotFoundError";
import {MysqlDriver} from "../driver/mysql/MysqlDriver";
import {ConnectionOptions} from "./ConnectionOptions";
import {DriverOptions} from "../driver/DriverOptions";
import {Driver} from "../driver/Driver";
import {MissingDriverError} from "./error/MissingDriverError";
import {PostgresDriver} from "../driver/postgres/PostgresDriver";
import {AlreadyHasActiveConnectionError} from "./error/AlreadyHasActiveConnectionError";
import {Logger} from "../logger/Logger";
import {SqliteDriver} from "../driver/sqlite/SqliteDriver";
import {OracleDriver} from "../driver/oracle/OracleDriver";
import {SqlServerDriver} from "../driver/sqlserver/SqlServerDriver";
import {OrmUtils} from "../util/OrmUtils";
import {CannotDetermineConnectionOptionsError} from "./error/CannotDetermineConnectionOptionsError";
import {PlatformTools} from "../platform/PlatformTools";
import {WebsqlDriver} from "../driver/websql/WebsqlDriver";
import {MongoDriver} from "../driver/mongodb/MongoDriver";
/**
* ConnectionManager is used to store and manage all these different connections.
@ -64,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<T>(strAndClses: string[]|T[]): [string[], T[]] {
return [
(strAndClses as string[]).filter(str => typeof str === "string"),
(strAndClses as T[]).filter(cls => typeof cls !== "string"),
];
}
/**
* Creates a new driver based on the given driver type and options.
*/
protected createDriver(options: DriverOptions, logger: Logger): Driver {
switch (options.type) {
case "mysql":
return new MysqlDriver(options, logger, undefined);
case "postgres":
return new PostgresDriver(options, logger);
case "mariadb":
return new MysqlDriver(options, logger);
case "sqlite":
return new SqliteDriver(options, logger);
case "oracle":
return new OracleDriver(options, logger);
case "mssql":
return new SqlServerDriver(options, logger);
case "websql":
return new WebsqlDriver(options, logger);
case "mongodb":
return new MongoDriver(options, logger);
default:
throw new MissingDriverError(options.type);
}
}
/**
* Creates a new connection and registers it in the connection manager.
*/
protected createConnection(name: string, driver: Driver, logger: Logger) {
const existConnection = this.connections.find(connection => connection.name === name);
if (existConnection) {
if (existConnection.isConnected)
throw new AlreadyHasActiveConnectionError(name);
this.connections.splice(this.connections.indexOf(existConnection), 1);
}
const connection = new Connection(name, driver, logger);
this.connections.push(connection);
return connection;
}
}

View File

@ -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;
};

View File

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

View File

@ -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.

View File

@ -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();
}
// -------------------------------------------------------------------------

View File

@ -15,6 +15,7 @@ import {DataTransformationUtils} from "../../util/DataTransformationUtils";
import {PlatformTools} from "../../platform/PlatformTools";
import {NamingStrategyInterface} from "../../naming-strategy/NamingStrategyInterface";
import {LazyRelationsWrapper} from "../../lazy-loading/LazyRelationsWrapper";
import {Connection} from "../../connection/Connection";
/**
* Organizes communication with 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();
}
// -------------------------------------------------------------------------

View File

@ -15,6 +15,7 @@ import {DataTransformationUtils} from "../../util/DataTransformationUtils";
import {PlatformTools} from "../../platform/PlatformTools";
import {NamingStrategyInterface} from "../../naming-strategy/NamingStrategyInterface";
import {LazyRelationsWrapper} from "../../lazy-loading/LazyRelationsWrapper";
import {Connection} from "../../connection/Connection";
/**
* Organizes communication with Oracle DBMS.
@ -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;
}

View File

@ -15,6 +15,7 @@ import {DataTransformationUtils} from "../../util/DataTransformationUtils";
import {PlatformTools} from "../../platform/PlatformTools";
import {NamingStrategyInterface} from "../../naming-strategy/NamingStrategyInterface";
import {LazyRelationsWrapper} from "../../lazy-loading/LazyRelationsWrapper";
import {Connection} from "../../connection/Connection";
// todo(tests):
// check connection with url
@ -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();
}
// -------------------------------------------------------------------------

View File

@ -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();
}
// -------------------------------------------------------------------------

View File

@ -15,6 +15,7 @@ import {DataTransformationUtils} from "../../util/DataTransformationUtils";
import {PlatformTools} from "../../platform/PlatformTools";
import {NamingStrategyInterface} from "../../naming-strategy/NamingStrategyInterface";
import {LazyRelationsWrapper} from "../../lazy-loading/LazyRelationsWrapper";
import {Connection} from "../../connection/Connection";
/**
* Organizes communication with SQL Server DBMS.
@ -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();
}
// -------------------------------------------------------------------------

View File

@ -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)

View File

@ -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<any>): Promise<any> {
return this.connection.transaction(runInTransaction, this.queryRunnerProvider);
}
/**
* Executes raw SQL query and returns raw database results.
*/
async query(query: string, parameters?: any[]): Promise<any> {
return this.connection.query(query, parameters, this.queryRunnerProvider);
}
/**
* Creates a new query builder that can be used to build a sql query.
*/
createQueryBuilder<Entity>(entityClass: ObjectType<Entity>|Function|string, alias: string, queryRunnerProvider?: QueryRunnerProvider): QueryBuilder<Entity>;
/**
* Creates a new query builder that can be used to build a sql query.
*/
createQueryBuilder(queryRunnerProvider?: QueryRunnerProvider): QueryBuilder<any>;
/**
* Creates a new query builder that can be used to build a sql query.
*/
createQueryBuilder<Entity>(entityClass?: ObjectType<Entity>|Function|string|QueryRunnerProvider, alias?: string, queryRunnerProvider?: QueryRunnerProvider): QueryBuilder<Entity> {
if (alias) {
return this.connection.createQueryBuilder(entityClass as Function|string, alias, queryRunnerProvider || this.queryRunnerProvider);
} else {
return this.connection.createQueryBuilder(entityClass as QueryRunnerProvider|undefined || this.queryRunnerProvider);
}
}
/**
* Gets user data by a given key.
* Used get stored data stored in a transactional entity manager.
@ -120,16 +157,6 @@ export class EntityManager {
return metadata.getEntityIdMixedMap(entity);
}
/**
* Creates a new query builder that can be used to build a sql query.
*/
createQueryBuilder<Entity>(entityClass: ObjectType<Entity>|Function|string, alias: string, queryRunnerProvider?: QueryRunnerProvider): QueryBuilder<Entity> {
const metadata = this.connection.getMetadata(entityClass);
return new QueryBuilder(this.connection, queryRunnerProvider || this.queryRunnerProvider)
.select(alias)
.from(metadata.target, alias);
}
/**
* Creates a new entity instance.
*/
@ -531,53 +558,6 @@ export class EntityManager {
return qb.getOne();
}
/**
* Executes raw SQL query and returns raw database results.
*/
async query(query: string, parameters?: any[]): Promise<any> {
if (this.queryRunnerProvider && this.queryRunnerProvider.isReleased)
throw new QueryRunnerProviderAlreadyReleasedError();
const queryRunnerProvider = this.queryRunnerProvider || new QueryRunnerProvider(this.connection.driver);
const queryRunner = await queryRunnerProvider.provide();
try {
return await queryRunner.query(query, parameters); // await is needed here because we are using finally
} finally {
await queryRunnerProvider.release(queryRunner);
}
}
/**
* Wraps given function execution (and all operations made there) in a transaction.
* All database operations must be executed using provided entity manager.
*/
async transaction(runInTransaction: (entityManger: EntityManager) => Promise<any>): Promise<any> {
if (this.queryRunnerProvider && this.queryRunnerProvider.isReleased)
throw new QueryRunnerProviderAlreadyReleasedError();
const queryRunnerProvider = this.queryRunnerProvider || new QueryRunnerProvider(this.connection.driver, true);
const queryRunner = await queryRunnerProvider.provide();
const transactionEntityManager = new EntityManager(this.connection, queryRunnerProvider);
try {
await queryRunner.beginTransaction();
const result = await runInTransaction(transactionEntityManager);
await queryRunner.commitTransaction();
return result;
} catch (err) {
await queryRunner.rollbackTransaction();
throw err;
} finally {
await queryRunnerProvider.release(queryRunner);
if (!this.queryRunnerProvider) // if we used a new query runner provider then release it
await queryRunnerProvider.releaseReused();
}
}
/**
* Clears all the data from the given table (truncates/drops it).
*/
@ -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);

View File

@ -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);
}
}

View File

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

View File

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

View File

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

View File

@ -16,6 +16,7 @@ import {Connection} from "../connection/Connection";
import {EntityListenerMetadata} from "../metadata/EntityListenerMetadata";
import {ColumnOptions} from "../decorator/options/ColumnOptions";
import {ForeignKeyMetadata} from "../metadata/ForeignKeyMetadata";
import {LazyRelationsWrapper} from "../lazy-loading/LazyRelationsWrapper";
/**
* Builds EntityMetadata objects and all its sub-metadatas.
@ -45,7 +46,9 @@ export class EntityMetadataBuilder {
// Constructor
// -------------------------------------------------------------------------
constructor(private connection: Connection, private metadataArgsStorage: MetadataArgsStorage) {
constructor(private connection: Connection,
private metadataArgsStorage: MetadataArgsStorage) {
this.junctionEntityMetadataBuilder = new JunctionEntityMetadataBuilder(connection);
this.closureJunctionEntityMetadataBuilder = new ClosureJunctionEntityMetadataBuilder(connection);
this.relationJoinColumnBuilder = new RelationJoinColumnBuilder(connection);
@ -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;

View File

@ -5,6 +5,7 @@ import {IndexMetadata} from "../metadata/IndexMetadata";
import {RelationMetadata} from "../metadata/RelationMetadata";
import {JoinTableMetadataArgs} from "../metadata-args/JoinTableMetadataArgs";
import {Connection} from "../connection/Connection";
import {LazyRelationsWrapper} from "../lazy-loading/LazyRelationsWrapper";
/**
* Creates EntityMetadata for junction tables.

View File

@ -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;

View File

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

View File

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

View File

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

View File

@ -25,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<Entity> {
* Also sets a main string alias of the selection data.
*/
from(entityTarget: Function|string, aliasName: string): this {
this.expressionMap.createMainAlias({
name: aliasName,
target: entityTarget
});
return this;
}
/**
* Specifies FROM which table select/update/delete will be executed.
* Also sets a main string alias of the selection data.
*/
fromTable(tableName: string, aliasName: string) {
// if table has a metadata then find it to properly escape its properties
const metadata = this.connection.entityMetadatas.find(metadata => metadata.tableName === tableName);
if (metadata) {
// const metadata = this.connection.entityMetadatas.find(metadata => metadata.tableName === tableName);
if (entityTarget instanceof Function || this.connection.hasMetadata(entityTarget)) {
this.expressionMap.createMainAlias({
name: aliasName,
metadata: metadata,
metadata: this.connection.getMetadata(entityTarget),
});
} else {
this.expressionMap.createMainAlias({
name: aliasName,
tableName: tableName,
tableName: entityTarget,
});
}
return this;
@ -900,6 +889,7 @@ export class QueryBuilder<Entity> {
*/
async getEntitiesAndRawResults(): Promise<{ entities: Entity[], rawResults: any[] }> {
const queryRunner = await this.getQueryRunner();
const broadcaster = new Broadcaster(this.connection);
const relationIdLoader = new RelationIdLoader(this.connection, this.queryRunnerProvider, this.expressionMap.relationIdAttributes);
const relationCountLoader = new RelationCountLoader(this.connection, this.queryRunnerProvider, this.expressionMap.relationCountAttributes);
const relationIdMetadataTransformer = new RelationIdMetadataToAttributeTransformer(this.expressionMap);
@ -996,8 +986,9 @@ export class QueryBuilder<Entity> {
const rawRelationIdResults = await relationIdLoader.load(rawResults);
const rawRelationCountResults = await relationCountLoader.load(rawResults);
entities = this.rawResultsToEntities(rawResults, rawRelationIdResults, rawRelationCountResults);
if (this.expressionMap.mainAlias.hasMetadata)
await this.connection.broadcaster.broadcastLoadEventsForAll(this.expressionMap.mainAlias.target, rawResults);
if (this.expressionMap.mainAlias.hasMetadata) {
await broadcaster.broadcastLoadEventsForAll(this.expressionMap.mainAlias.target, rawResults);
}
}
return {
@ -1015,7 +1006,7 @@ export class QueryBuilder<Entity> {
const rawRelationCountResults = await relationCountLoader.load(rawResults);
const entities = this.rawResultsToEntities(rawResults, rawRelationIdResults, rawRelationCountResults);
if (this.expressionMap.mainAlias.hasMetadata)
await this.connection.broadcaster.broadcastLoadEventsForAll(this.expressionMap.mainAlias.target, rawResults);
await broadcaster.broadcastLoadEventsForAll(this.expressionMap.mainAlias.target, rawResults);
return {
entities: entities,
@ -1152,7 +1143,7 @@ export class QueryBuilder<Entity> {
* Clones query builder as it is.
*/
clone(options?: { queryRunnerProvider?: QueryRunnerProvider }): QueryBuilder<Entity> {
const qb = new QueryBuilder(this.connection, options ? options.queryRunnerProvider : undefined);
const qb = this.connection.createQueryBuilder(options ? options.queryRunnerProvider : undefined);
qb.expressionMap = this.expressionMap.clone();
return qb;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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");

View File

@ -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;