refactored connection

This commit is contained in:
Umed Khudoiberdiev 2017-06-09 00:41:26 +05:00
parent 6232c827b6
commit d2ff2cbd0a
16 changed files with 135 additions and 60 deletions

View File

@ -5,21 +5,18 @@ import {RepositoryNotFoundError} from "./error/RepositoryNotFoundError";
import {ObjectType} from "../common/ObjectType";
import {EntityManager} from "../entity-manager/EntityManager";
import {DefaultNamingStrategy} from "../naming-strategy/DefaultNamingStrategy";
import {CannotCloseNotConnectedError} from "./error/CannotCloseNotConnectedError";
import {CannotExecuteNotConnectedError} from "./error/CannotExecuteNotConnectedError";
import {CannotConnectAlreadyConnectedError} from "./error/CannotConnectAlreadyConnectedError";
import {TreeRepository} from "../repository/TreeRepository";
import {NamingStrategyInterface} from "../naming-strategy/NamingStrategyInterface";
import {RepositoryNotTreeError} from "./error/RepositoryNotTreeError";
import {CannotSyncNotConnectedError} from "./error/CannotSyncNotConnectedError";
import {SpecificRepository} from "../repository/SpecificRepository";
import {EntityMetadata} from "../metadata/EntityMetadata";
import {SchemaBuilder} from "../schema-builder/SchemaBuilder";
import {Logger} from "../logger/Logger";
import {QueryRunnerProvider} from "../query-runner/QueryRunnerProvider";
import {EntityMetadataNotFound} from "../metadata-args/error/EntityMetadataNotFound";
import {MigrationInterface} from "../migration/MigrationInterface";
import {MigrationExecutor} from "../migration/MigrationExecutor";
import {CannotRunMigrationNotConnectedError} from "./error/CannotRunMigrationNotConnectedError";
import {PlatformTools} from "../platform/PlatformTools";
import {MongoRepository} from "../repository/MongoRepository";
import {MongoDriver} from "../driver/mongodb/MongoDriver";
@ -42,7 +39,7 @@ import {ConnectionMetadataBuilder} from "./ConnectionMetadataBuilder";
export class Connection {
// -------------------------------------------------------------------------
// Public Readonly properties
// Public Readonly Properties
// -------------------------------------------------------------------------
/**
@ -166,7 +163,7 @@ export class Connection {
*/
async close(): Promise<void> {
if (!this.isConnected)
throw new CannotCloseNotConnectedError(this.name);
throw new CannotExecuteNotConnectedError(this.name);
await this.driver.disconnect();
Object.assign(this, { isConnected: false });
@ -181,23 +178,17 @@ export class Connection {
async syncSchema(dropBeforeSync: boolean = false): Promise<void> {
if (!this.isConnected)
throw new CannotCloseNotConnectedError(this.name);
throw new CannotExecuteNotConnectedError(this.name);
if (dropBeforeSync)
await this.dropDatabase();
if (this.driver instanceof MongoDriver) { // todo: temporary
await this.driver.syncSchema(this.entityMetadatas);
} else {
const schemaBuilder = new SchemaBuilder(this.driver, this.logger, this.entityMetadatas);
await schemaBuilder.build();
}
await this.driver.syncSchema();
}
/**
* 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.
* Be careful with this method on production since this method will erase all your database tables and their data.
* Can be used only after connection to the database is established.
*/
async dropDatabase(): Promise<void> {
@ -212,7 +203,7 @@ export class Connection {
async runMigrations(): Promise<void> {
if (!this.isConnected)
throw new CannotCloseNotConnectedError(this.name);
throw new CannotExecuteNotConnectedError(this.name);
const migrationExecutor = new MigrationExecutor(this);
await migrationExecutor.executePendingMigrations();
@ -225,7 +216,7 @@ export class Connection {
async undoLastMigration(): Promise<void> {
if (!this.isConnected)
throw new CannotCloseNotConnectedError(this.name);
throw new CannotExecuteNotConnectedError(this.name);
const migrationExecutor = new MigrationExecutor(this);
await migrationExecutor.undoLastMigration();
@ -239,7 +230,7 @@ export class Connection {
}
/**
Gets entity metadata for the given entity class or schema name.
* Gets entity metadata for the given entity class or schema name.
*/
getMetadata(target: Function|string): EntityMetadata {
const metadata = this.findMetadata(target);
@ -276,6 +267,7 @@ export class Connection {
/**
* Gets mongodb-specific repository for the given entity class or name.
* Works only if connection is mongodb-specific.
*/
getMongoRepository<Entity>(target: ObjectType<Entity>|string): MongoRepository<Entity> {
if (!(this.driver instanceof MongoDriver))
@ -295,7 +287,7 @@ export class Connection {
}
/**
* Wraps given function execution (and all operations made there) in a transaction.
* Wraps given function execution (and all operations made there) into a transaction.
* All database operations must be executed using provided entity manager.
*/
async transaction(runInTransaction: (entityManger: EntityManager) => Promise<any>,

View File

@ -1,13 +0,0 @@
/**
* Thrown when consumer tries close not opened connection.
*/
export class CannotCloseNotConnectedError extends Error {
name = "CannotCloseNotConnectedError";
constructor(connectionName: string) {
super();
this.message = `Cannot close "${connectionName}" connection because connection is not yet established.`;
this.stack = new Error().stack;
}
}

View File

@ -0,0 +1,13 @@
/**
* Thrown when consumer tries to execute operation allowed only if connection is opened.
*/
export class CannotExecuteNotConnectedError extends Error {
name = "CannotExecuteNotConnectedError";
constructor(connectionName: string) {
super();
this.message = `Cannot execute operation on "${connectionName}" connection because connection is not yet established.`;
this.stack = new Error().stack;
}
}

View File

@ -22,6 +22,11 @@ export interface Driver {
*/
disconnect(): Promise<void>;
/**
* Synchronizes database schema (creates tables, indices, etc).
*/
syncSchema(): Promise<void>;
/**
* Access to the native implementation of the database.
*/

View File

@ -196,11 +196,13 @@ export class MongoDriver implements Driver {
return value;
}
// todo: make better abstraction
async syncSchema(entityMetadatas: EntityMetadata[]): Promise<void> {
/**
* Synchronizes database schema (creates indices).
*/
async syncSchema(): Promise<void> {
const queryRunner = await this.createQueryRunner() as MongoQueryRunner;
const promises: Promise<any>[] = [];
await Promise.all(entityMetadatas.map(metadata => {
await Promise.all(this.connection.entityMetadatas.map(metadata => {
metadata.indices.forEach(index => {
const options = { name: index.name };
promises.push(queryRunner.createCollectionIndex(metadata.tableName, index.columnNamesWithOrderingMap, options));

View File

@ -16,6 +16,7 @@ import {PlatformTools} from "../../platform/PlatformTools";
import {NamingStrategyInterface} from "../../naming-strategy/NamingStrategyInterface";
import {LazyRelationsWrapper} from "../../lazy-loading/LazyRelationsWrapper";
import {Connection} from "../../connection/Connection";
import {SchemaBuilder} from "../../schema-builder/SchemaBuilder";
/**
* Organizes communication with MySQL DBMS.
@ -130,6 +131,14 @@ export class MysqlDriver implements Driver {
});
}
/**
* Synchronizes database schema (creates tables, indices, etc).
*/
syncSchema(): Promise<void> {
const schemaBuilder = new SchemaBuilder(this.connection);
return schemaBuilder.build();
}
/**
* Creates a query runner used for common queries.
*/

View File

@ -16,6 +16,7 @@ import {PlatformTools} from "../../platform/PlatformTools";
import {NamingStrategyInterface} from "../../naming-strategy/NamingStrategyInterface";
import {LazyRelationsWrapper} from "../../lazy-loading/LazyRelationsWrapper";
import {Connection} from "../../connection/Connection";
import {SchemaBuilder} from "../../schema-builder/SchemaBuilder";
/**
* Organizes communication with Oracle DBMS.
@ -142,6 +143,14 @@ export class OracleDriver implements Driver {
});
}
/**
* Synchronizes database schema (creates tables, indices, etc).
*/
syncSchema(): Promise<void> {
const schemaBuilder = new SchemaBuilder(this.connection);
return schemaBuilder.build();
}
/**
* Creates a query runner used for common queries.
*/

View File

@ -16,6 +16,7 @@ import {PlatformTools} from "../../platform/PlatformTools";
import {NamingStrategyInterface} from "../../naming-strategy/NamingStrategyInterface";
import {LazyRelationsWrapper} from "../../lazy-loading/LazyRelationsWrapper";
import {Connection} from "../../connection/Connection";
import {SchemaBuilder} from "../../schema-builder/SchemaBuilder";
// todo(tests):
// check connection with url
@ -165,6 +166,14 @@ export class PostgresDriver implements Driver {
});
}
/**
* Synchronizes database schema (creates tables, indices, etc).
*/
syncSchema(): Promise<void> {
const schemaBuilder = new SchemaBuilder(this.connection);
return schemaBuilder.build();
}
/**
* Creates a query runner used for common queries.
*/

View File

@ -15,6 +15,7 @@ import {PlatformTools} from "../../platform/PlatformTools";
import {NamingStrategyInterface} from "../../naming-strategy/NamingStrategyInterface";
import {LazyRelationsWrapper} from "../../lazy-loading/LazyRelationsWrapper";
import {Connection} from "../../connection/Connection";
import {SchemaBuilder} from "../../schema-builder/SchemaBuilder";
/**
* Organizes communication with sqlite DBMS.
@ -90,6 +91,14 @@ export class SqliteDriver implements Driver {
});
}
/**
* Synchronizes database schema (creates tables, indices, etc).
*/
syncSchema(): Promise<void> {
const schemaBuilder = new SchemaBuilder(this.connection);
return schemaBuilder.build();
}
/**
* Creates a query runner used for common queries.
*/

View File

@ -16,6 +16,7 @@ import {PlatformTools} from "../../platform/PlatformTools";
import {NamingStrategyInterface} from "../../naming-strategy/NamingStrategyInterface";
import {LazyRelationsWrapper} from "../../lazy-loading/LazyRelationsWrapper";
import {Connection} from "../../connection/Connection";
import {SchemaBuilder} from "../../schema-builder/SchemaBuilder";
/**
* Organizes communication with SQL Server DBMS.
@ -130,6 +131,14 @@ export class SqlServerDriver implements Driver {
this.databaseConnectionPool = [];
}
/**
* Synchronizes database schema (creates tables, indices, etc).
*/
syncSchema(): Promise<void> {
const schemaBuilder = new SchemaBuilder(this.connection);
return schemaBuilder.build();
}
/**
* Creates a query runner used for common queries.
*/

View File

@ -12,6 +12,7 @@ import {DriverOptionNotSetError} from "../error/DriverOptionNotSetError";
import {DataTransformationUtils} from "../../util/DataTransformationUtils";
import {WebsqlQueryRunner} from "./WebsqlQueryRunner";
import {Connection} from "../../connection/Connection";
import {SchemaBuilder} from "../../schema-builder/SchemaBuilder";
/**
* Declare a global function that is only available in browsers that support WebSQL.
@ -96,6 +97,14 @@ export class WebsqlDriver implements Driver {
});
}
/**
* Synchronizes database schema (creates tables, indices, etc).
*/
syncSchema(): Promise<void> {
const schemaBuilder = new SchemaBuilder(this.connection);
return schemaBuilder.build();
}
/**
* Creates a query runner used for common queries.
*/

View File

@ -11,6 +11,7 @@ import {ColumnMetadata} from "../metadata/ColumnMetadata";
import {IndexMetadata} from "../metadata/IndexMetadata";
import {EntityMetadata} from "../metadata/EntityMetadata";
import {PromiseUtils} from "../util/PromiseUtils";
import {Connection} from "../connection/Connection";
/**
* Creates complete tables schemas in the database based on the entity metadatas.
@ -46,14 +47,7 @@ export class SchemaBuilder {
// Constructor
// -------------------------------------------------------------------------
/**
* @param driver Driver needs to create a query runner
* @param logger Used to log schema creation events
* @param entityMetadatas All entities to create schema for
*/
constructor(protected driver: Driver,
protected logger: Logger,
protected entityMetadatas: EntityMetadata[]) {
constructor(protected connection: Connection) {
}
// -------------------------------------------------------------------------
@ -64,7 +58,7 @@ export class SchemaBuilder {
* Creates complete schemas for the given entity metadatas.
*/
async build(): Promise<void> {
this.queryRunner = await this.driver.createQueryRunner();
this.queryRunner = await this.connection.driver.createQueryRunner();
this.tableSchemas = await this.loadTableSchemas();
await this.queryRunner.beginTransaction();
@ -90,11 +84,11 @@ export class SchemaBuilder {
}
// -------------------------------------------------------------------------
// Private Methods
// Protected Methods
// -------------------------------------------------------------------------
protected get entityToSyncMetadatas(): EntityMetadata[] {
return this.entityMetadatas.filter(metadata => !metadata.skipSchemaSync && metadata.tableType !== "single-table-child");
return this.connection.entityMetadatas.filter(metadata => !metadata.skipSchemaSync && metadata.tableType !== "single-table-child");
}
/**
@ -122,7 +116,7 @@ export class SchemaBuilder {
if (foreignKeySchemasToDrop.length === 0)
return;
this.logger.logSchemaBuild(`dropping old foreign keys of ${tableSchema.name}: ${foreignKeySchemasToDrop.map(dbForeignKey => dbForeignKey.name).join(", ")}`);
this.connection.logger.logSchemaBuild(`dropping old foreign keys of ${tableSchema.name}: ${foreignKeySchemasToDrop.map(dbForeignKey => dbForeignKey.name).join(", ")}`);
// remove foreign keys from the table schema
tableSchema.removeForeignKeys(foreignKeySchemasToDrop);
@ -144,7 +138,7 @@ export class SchemaBuilder {
if (existTableSchema)
return;
this.logger.logSchemaBuild(`creating a new table: ${metadata.tableName}`);
this.connection.logger.logSchemaBuild(`creating a new table: ${metadata.tableName}`);
// create a new table schema and sync it in the database
const tableSchema = new TableSchema(metadata.tableName, this.metadataColumnsToColumnSchemas(metadata.columns), true);
@ -179,7 +173,7 @@ export class SchemaBuilder {
return this.dropColumnReferencedIndices(metadata.tableName, droppedColumnSchema.name);
}));
this.logger.logSchemaBuild(`columns dropped in ${tableSchema.name}: ` + droppedColumnSchemas.map(column => column.name).join(", "));
this.connection.logger.logSchemaBuild(`columns dropped in ${tableSchema.name}: ` + droppedColumnSchemas.map(column => column.name).join(", "));
// remove columns from the table schema and primary keys of it if its used in the primary keys
tableSchema.removeColumns(droppedColumnSchemas);
@ -207,7 +201,7 @@ export class SchemaBuilder {
if (newColumnMetadatas.length === 0)
return;
this.logger.logSchemaBuild(`new columns added: ` + newColumnMetadatas.map(column => column.databaseName).join(", "));
this.connection.logger.logSchemaBuild(`new columns added: ` + newColumnMetadatas.map(column => column.databaseName).join(", "));
// create columns in the database
const newColumnSchemas = this.metadataColumnsToColumnSchemas(newColumnMetadatas);
@ -230,7 +224,7 @@ export class SchemaBuilder {
if (updatedColumnSchemas.length === 0)
return;
this.logger.logSchemaBuild(`columns changed in ${tableSchema.name}. updating: ` + updatedColumnSchemas.map(column => column.name).join(", "));
this.connection.logger.logSchemaBuild(`columns changed in ${tableSchema.name}. updating: ` + updatedColumnSchemas.map(column => column.name).join(", "));
// drop all foreign keys that point to this column
const dropRelatedForeignKeysPromises = updatedColumnSchemas
@ -287,7 +281,7 @@ export class SchemaBuilder {
if (addedKeys.length === 0 && droppedKeys.length === 0)
return;
this.logger.logSchemaBuild(`primary keys of ${tableSchema.name} has changed: dropped - ${droppedKeys.map(key => key.columnName).join(", ") || "nothing"}; added - ${addedKeys.map(key => key.columnName).join(", ") || "nothing"}`);
this.connection.logger.logSchemaBuild(`primary keys of ${tableSchema.name} has changed: dropped - ${droppedKeys.map(key => key.columnName).join(", ") || "nothing"}; added - ${addedKeys.map(key => key.columnName).join(", ") || "nothing"}`);
tableSchema.addPrimaryKeys(addedKeys);
tableSchema.removePrimaryKeys(droppedKeys);
await this.queryRunner.updatePrimaryKeys(tableSchema);
@ -310,7 +304,7 @@ export class SchemaBuilder {
return;
const dbForeignKeys = newKeys.map(foreignKeyMetadata => ForeignKeySchema.create(foreignKeyMetadata));
this.logger.logSchemaBuild(`creating a foreign keys: ${newKeys.map(key => key.name).join(", ")}`);
this.connection.logger.logSchemaBuild(`creating a foreign keys: ${newKeys.map(key => key.name).join(", ")}`);
await this.queryRunner.createForeignKeys(tableSchema, dbForeignKeys);
tableSchema.addForeignKeys(dbForeignKeys);
});
@ -321,7 +315,7 @@ export class SchemaBuilder {
* but does not exist in the metadata anymore.
*/
protected createIndices() {
// return Promise.all(this.entityMetadatas.map(metadata => this.createIndices(metadata.table, metadata.indices)));
// return Promise.all(this.connection.entityMetadatas.map(metadata => this.createIndices(metadata.table, metadata.indices)));
return PromiseUtils.runInSequence(this.entityToSyncMetadatas, async metadata => {
const tableSchema = this.tableSchemas.find(table => table.name === metadata.tableName);
if (!tableSchema)
@ -331,7 +325,7 @@ export class SchemaBuilder {
const dropQueries = tableSchema.indices
.filter(indexSchema => !metadata.indices.find(indexMetadata => indexMetadata.name === indexSchema.name))
.map(async indexSchema => {
this.logger.logSchemaBuild(`dropping an index: ${indexSchema.name}`);
this.connection.logger.logSchemaBuild(`dropping an index: ${indexSchema.name}`);
tableSchema.removeIndex(indexSchema);
await this.queryRunner.dropIndex(metadata.tableName, indexSchema.name);
});
@ -342,7 +336,7 @@ export class SchemaBuilder {
.map(async indexMetadata => {
const indexSchema = IndexSchema.create(indexMetadata);
tableSchema.indices.push(indexSchema);
this.logger.logSchemaBuild(`adding new index: ${indexSchema.name}`);
this.connection.logger.logSchemaBuild(`adding new index: ${indexSchema.name}`);
await this.queryRunner.createIndex(indexSchema.tableName, indexSchema);
});
@ -355,7 +349,7 @@ export class SchemaBuilder {
*/
protected async dropColumnReferencedIndices(tableName: string, columnName: string): Promise<void> {
const allIndexMetadatas = this.entityMetadatas.reduce(
const allIndexMetadatas = this.connection.entityMetadatas.reduce(
(all, metadata) => all.concat(metadata.indices),
[] as IndexMetadata[]
);
@ -377,7 +371,7 @@ export class SchemaBuilder {
if (dependIndicesInTable.length === 0)
return;
this.logger.logSchemaBuild(`dropping related indices of ${tableName}#${columnName}: ${dependIndicesInTable.map(index => index.name).join(", ")}`);
this.connection.logger.logSchemaBuild(`dropping related indices of ${tableName}#${columnName}: ${dependIndicesInTable.map(index => index.name).join(", ")}`);
const dropPromises = dependIndicesInTable.map(index => {
tableSchema.removeIndex(index);
@ -392,7 +386,7 @@ export class SchemaBuilder {
*/
protected async dropColumnReferencedForeignKeys(tableName: string, columnName: string): Promise<void> {
const allForeignKeyMetadatas = this.entityMetadatas.reduce(
const allForeignKeyMetadatas = this.connection.entityMetadatas.reduce(
(all, metadata) => all.concat(metadata.foreignKeys),
[] as ForeignKeyMetadata[]
);
@ -423,7 +417,7 @@ export class SchemaBuilder {
if (dependForeignKeyInTable.length === 0)
return;
this.logger.logSchemaBuild(`dropping related foreign keys of ${tableName}#${columnName}: ${dependForeignKeyInTable.map(foreignKey => foreignKey.name).join(", ")}`);
this.connection.logger.logSchemaBuild(`dropping related foreign keys of ${tableName}#${columnName}: ${dependForeignKeyInTable.map(foreignKey => foreignKey.name).join(", ")}`);
const foreignKeySchemas = dependForeignKeyInTable.map(foreignKeyMetadata => ForeignKeySchema.create(foreignKeyMetadata));
tableSchema.removeForeignKeys(foreignKeySchemas);
await this.queryRunner.dropForeignKeys(tableSchema, foreignKeySchemas);

View File

@ -0,0 +1,5 @@
TYPEORM_HOST = localhost
TYPEORM_USERNAME = root
TYPEORM_PASSWORD = admin
TYPEORM_PORT = 3000
TYPEORM_LOGGING = true

View File

@ -0,0 +1,8 @@
export default {
name: "default",
host: "localhost",
username: "root",
password: "admin",
port: 3000,
logging: true
};

View File

@ -0,0 +1,9 @@
<connections>
<connection type="mysql" name="default">
<host>localhost</host>
<username>root</username>
<password>admin</password>
<port>3000</port>
<logging>true</logging>
</connection>
</connections>

View File

@ -0,0 +1,6 @@
default:
host: localhost
username: root
password: admin
port: 3000
logging: true