diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b8357a47..d33f1374c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ More env variable names you can find in `ConnectionOptionsEnvReader` class. * `setLimit` and `setOffset` in `QueryBuilder` were renamed into `limit` and `offset` * `nativeInterface` has been removed from a driver interface and implementations. Now +* now typeorm works with the latest version of mssql (version 4) ### DEPRECATIONS @@ -69,6 +70,7 @@ Now * moved `query`, `transaction` and `createQueryBuilder` to the `Connection`. `EntityManager` now simply use them from the connection. * refactored how query runner works, removed query runner provider +* fixed some issues with sqlite, sqlite now strongly works on a single connection ### BUG FIXES diff --git a/package.json b/package.json index b66667b35..c2d503dfc 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "gulpclass": "^0.1.2", "mocha": "^2.5.3", "mongodb": "^2.2.26", - "mssql": "^3.3.0", + "mssql": "^4.0.4", "mysql": "^2.12.0", "mysql2": "^1.2.0", "pg": "^6.1.5", diff --git a/src/connection/Connection.ts b/src/connection/Connection.ts index 786683a8e..5477a8c79 100644 --- a/src/connection/Connection.ts +++ b/src/connection/Connection.ts @@ -208,6 +208,7 @@ export class Connection { async dropDatabase(): Promise { const queryRunner = await this.driver.createQueryRunner(); await queryRunner.clearDatabase(); + await queryRunner.release(); } /** diff --git a/src/driver/mysql/MysqlQueryRunner.ts b/src/driver/mysql/MysqlQueryRunner.ts index 7bf73ff1b..a25c4bdd2 100644 --- a/src/driver/mysql/MysqlQueryRunner.ts +++ b/src/driver/mysql/MysqlQueryRunner.ts @@ -113,9 +113,6 @@ export class MysqlQueryRunner implements QueryRunner { } catch (error) { await this.rollbackTransaction(); throw error; - - } finally { - await this.release(); } } diff --git a/src/driver/oracle/OracleQueryRunner.ts b/src/driver/oracle/OracleQueryRunner.ts index 3ad872a47..7e966fd00 100644 --- a/src/driver/oracle/OracleQueryRunner.ts +++ b/src/driver/oracle/OracleQueryRunner.ts @@ -121,9 +121,6 @@ export class OracleQueryRunner implements QueryRunner { } catch (error) { await this.rollbackTransaction(); throw error; - - } finally { - await this.release(); } } diff --git a/src/driver/postgres/PostgresQueryRunner.ts b/src/driver/postgres/PostgresQueryRunner.ts index 68d3b2463..177d8fabe 100644 --- a/src/driver/postgres/PostgresQueryRunner.ts +++ b/src/driver/postgres/PostgresQueryRunner.ts @@ -127,9 +127,6 @@ export class PostgresQueryRunner implements QueryRunner { } catch (error) { await this.rollbackTransaction(); throw error; - - } finally { - await this.release(); } } diff --git a/src/driver/sqlite/SqliteDriver.ts b/src/driver/sqlite/SqliteDriver.ts index 96df2ed7c..462c4710f 100644 --- a/src/driver/sqlite/SqliteDriver.ts +++ b/src/driver/sqlite/SqliteDriver.ts @@ -11,6 +11,7 @@ import {RdbmsSchemaBuilder} from "../../schema-builder/RdbmsSchemaBuilder"; import {SqliteConnectionOptions} from "./SqliteConnectionOptions"; import {MappedColumnTypes} from "../types/MappedColumnTypes"; import {ColumnType} from "../types/ColumnTypes"; +import {QueryRunner} from "../../query-runner/QueryRunner"; /** * Organizes communication with sqlite DBMS. @@ -36,6 +37,16 @@ export class SqliteDriver implements Driver { */ sqlite: any; + /** + * Sqlite has a single QueryRunner because it works on a single database connection. + */ + queryRunner?: QueryRunner; + + /** + * Real database connection with sqlite database. + */ + databaseConnection: any; + // ------------------------------------------------------------------------- // Public Implemented Properties // ------------------------------------------------------------------------- @@ -114,22 +125,18 @@ export class SqliteDriver implements Driver { /** * Performs connection to the database. */ - connect(): Promise { - return Promise.resolve(); + async connect(): Promise { + this.databaseConnection = await this.createDatabaseConnection(); } /** * Closes connection with database. */ - disconnect(): Promise { - return Promise.resolve(); - // todo: what to do with this function? - // return new Promise((ok, fail) => { - // const handler = (err: any) => err ? fail(err) : ok(); - // if (!this.databaseConnection) - // return fail(new ConnectionIsNotSetError("sqlite")); - // this.databaseConnection.connection.close(handler); - // }); + async disconnect(): Promise { + return new Promise((ok, fail) => { + this.queryRunner = undefined; + this.databaseConnection.close((err: any) => err ? fail(err) : ok()); + }); } /** @@ -143,7 +150,10 @@ export class SqliteDriver implements Driver { * Creates a query runner used to execute database queries. */ createQueryRunner() { - return new SqliteQueryRunner(this); + if (!this.queryRunner) + this.queryRunner = new SqliteQueryRunner(this); + + return this.queryRunner; } /** @@ -300,6 +310,24 @@ export class SqliteDriver implements Driver { // Protected Methods // ------------------------------------------------------------------------- + /** + * Creates connection with the database. + */ + protected createDatabaseConnection() { + return new Promise((ok, fail) => { + const databaseConnection = new this.sqlite.Database(this.options.database, (err: any) => { + if (err) return fail(err); + + // we need to enable foreign keys in sqlite to make sure all foreign key related features + // working properly. this also makes onDelete to work with sqlite. + databaseConnection.run(`PRAGMA foreign_keys = ON;`, (err: any, result: any) => { + if (err) return fail(err); + ok(databaseConnection); + }); + }); + }); + } + /** * If driver dependency is not given explicitly, then try to load it via "require". */ @@ -307,7 +335,7 @@ export class SqliteDriver implements Driver { try { this.sqlite = PlatformTools.load("sqlite3").verbose(); - } catch (e) { // todo: better error for browser env + } catch (e) { throw new DriverPackageNotInstalledError("SQLite", "sqlite3"); } } diff --git a/src/driver/sqlite/SqliteQueryRunner.ts b/src/driver/sqlite/SqliteQueryRunner.ts index d7346adbe..8a97e4792 100644 --- a/src/driver/sqlite/SqliteQueryRunner.ts +++ b/src/driver/sqlite/SqliteQueryRunner.ts @@ -65,44 +65,15 @@ export class SqliteQueryRunner implements QueryRunner { * Returns obtained database connection. */ connect(): Promise { - if (this.databaseConnection) - return Promise.resolve(this.databaseConnection); - - if (this.databaseConnectionPromise) - return this.databaseConnectionPromise; - - this.databaseConnectionPromise = new Promise((ok, fail) => { - this.databaseConnection = new this.driver.sqlite.Database(this.driver.options.database, (err: any) => { - if (err) { - this.databaseConnection = null; - return fail(err); - } - - // we need to enable foreign keys in sqlite to make sure all foreign key related features - // working properly. this also makes onDelete to work with sqlite. - this.databaseConnection.run(`PRAGMA foreign_keys = ON;`, (err: any, result: any) => { - if (err) - return fail(err); - - ok(this.databaseConnection); - }); - }); - }); - - return this.databaseConnectionPromise; + return Promise.resolve(this.driver.databaseConnection); } /** * Releases used database connection. - * You cannot use query runner methods once its released. + * We don't do anything here because sqlite do not support multiple connections thus query runners. */ release(): Promise { - return new Promise((ok, fail) => { - const handler = (err: any) => err ? fail(err) : ok(); - if (this.databaseConnection) - this.databaseConnection.close(handler); - this.isReleased = true; - }); + return Promise.resolve(); } /** @@ -126,7 +97,6 @@ export class SqliteQueryRunner implements QueryRunner { } finally { await this.query(`PRAGMA foreign_keys = ON;`); - await this.release(); } } diff --git a/src/driver/sqlserver/SqlServerDriver.ts b/src/driver/sqlserver/SqlServerDriver.ts index 1371b46be..8aebbc784 100644 --- a/src/driver/sqlserver/SqlServerDriver.ts +++ b/src/driver/sqlserver/SqlServerDriver.ts @@ -149,7 +149,7 @@ export class SqlServerDriver implements Driver { // pooling is enabled either when its set explicitly to true, // either when its not defined at all (e.g. enabled by default) return new Promise((ok, fail) => { - const connection = new this.mssql.Connection(options).connect((err: any) => { + const connection = new this.mssql.ConnectionPool(options).connect((err: any) => { if (err) return fail(err); this.connectionPool = connection; ok(); diff --git a/src/driver/sqlserver/SqlServerQueryRunner.ts b/src/driver/sqlserver/SqlServerQueryRunner.ts index 4152bdc19..4b457472b 100644 --- a/src/driver/sqlserver/SqlServerQueryRunner.ts +++ b/src/driver/sqlserver/SqlServerQueryRunner.ts @@ -41,14 +41,13 @@ export class SqlServerQueryRunner implements QueryRunner { protected databaseConnection: any; /** - * Transaction instance opened for this query executor. + * Last executed query in a transaction. + * This is needed because in transaction mode mssql cannot execute parallel queries, + * that's why we store last executed query promise to wait it when we execute next query. + * + * @see https://github.com/patriksimek/node-mssql/issues/491 */ - protected transaction: any; - - /** - * Special callback provided by a driver used to release a created connection. - */ - protected databaseConnectionPromise: Promise; + protected queryResponsibilityChain: Promise[] = []; // ------------------------------------------------------------------------- // Constructor @@ -66,19 +65,7 @@ export class SqlServerQueryRunner implements QueryRunner { * Returns obtained database connection. */ connect(): Promise { - if (this.databaseConnection) - return Promise.resolve(this.databaseConnection); - - if (this.databaseConnectionPromise) - return this.databaseConnectionPromise; - - this.databaseConnectionPromise = new Promise((ok, fail) => { - const driver = this.driver as SqlServerDriver; - this.databaseConnection = driver.connectionPool; // todo: fix it - ok(this.databaseConnection); - }); - - return this.databaseConnectionPromise; + return Promise.resolve(); } /** @@ -87,7 +74,6 @@ export class SqlServerQueryRunner implements QueryRunner { */ release(): Promise { this.isReleased = true; - // todo return Promise.resolve(); } @@ -120,36 +106,7 @@ export class SqlServerQueryRunner implements QueryRunner { } catch (error) { await this.rollbackTransaction(); throw error; - - } finally { - await this.release(); } - - // const selectDropsQuery = `SELECT 'DROP TABLE "' + TABLE_NAME + '"' as query FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE';`; - // const dropQueries: ObjectLiteral[] = await this.query(selectDropsQuery); - // const allQueries = [`EXEC sp_msforeachtable "ALTER TABLE ? NOCHECK CONSTRAINT all"`] - // .concat(dropQueries.map(q => this.query(q["query"])).join("; ")); - // - // return new Promise((ok, fail) => { - // - // const request = new this.driver.mssql.Request(this.isTransactionActive() ? this.databaseConnection.transaction : this.databaseConnection.connection); - // request.multiple = true; - // request.query(allQueries, (err: any, result: any) => { - // if (err) { - // this.connection.logger.logFailedQuery(allQueries); - // this.connection.logger.logQueryError(err); - // return fail(err); - // } - // - // ok(); - // }); - // }); - - // const selectDropsQuery = `SELECT 'DROP TABLE "' + TABLE_NAME + '";' as query FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE';`; - // const dropQueries: ObjectLiteral[] = await this.query(selectDropsQuery); - // await this.query(`EXEC sp_msforeachtable "ALTER TABLE ? NOCHECK CONSTRAINT all"`); - // await Promise.all(dropQueries.map(q => this.query(q["query"]))); - // await this.query(`EXEC sp_msforeachtable 'drop table [?]'`); } /** @@ -164,9 +121,8 @@ export class SqlServerQueryRunner implements QueryRunner { return new Promise(async (ok, fail) => { this.isTransactionActive = true; - const dbConnection = await this.connect(); - this.transaction = dbConnection.transaction(); - this.transaction.begin((err: any) => { + this.databaseConnection = this.driver.connectionPool.transaction(); + this.databaseConnection.begin((err: any) => { if (err) { this.isTransactionActive = false; return fail(err); @@ -188,9 +144,10 @@ export class SqlServerQueryRunner implements QueryRunner { throw new TransactionNotStartedError(); return new Promise((ok, fail) => { - this.transaction.commit((err: any) => { + this.databaseConnection.commit((err: any) => { if (err) return fail(err); this.isTransactionActive = false; + this.databaseConnection = null; ok(); }); }); @@ -208,9 +165,10 @@ export class SqlServerQueryRunner implements QueryRunner { throw new TransactionNotStartedError(); return new Promise((ok, fail) => { - this.transaction.rollback((err: any) => { + this.databaseConnection.rollback((err: any) => { if (err) return fail(err); this.isTransactionActive = false; + this.databaseConnection = null; ok(); }); }); @@ -219,31 +177,54 @@ export class SqlServerQueryRunner implements QueryRunner { /** * Executes a given SQL query. */ - query(query: string, parameters?: any[]): Promise { + async query(query: string, parameters?: any[]): Promise { if (this.isReleased) throw new QueryRunnerAlreadyReleasedError(); - return new Promise(async (ok, fail) => { + let waitingOkay: Function; + const waitingPromise = new Promise((ok) => waitingOkay = ok); + if (this.queryResponsibilityChain.length) { + const otherWaitingPromises = [...this.queryResponsibilityChain]; + this.queryResponsibilityChain.push(waitingPromise); + await Promise.all(otherWaitingPromises); + } + + const promise = new Promise(async (ok, fail) => { this.driver.connection.logger.logQuery(query, parameters); - const mssql = this.driver.mssql; - const dbConnection = await this.connect(); - const request = new mssql.Request(this.isTransactionActive ? this.transaction : dbConnection); + const request = new this.driver.mssql.Request(this.isTransactionActive ? this.databaseConnection : this.driver.connectionPool); if (parameters && parameters.length) { parameters.forEach((parameter, index) => { request.input(index, parameters![index]); }); } request.query(query, (err: any, result: any) => { + + const resolveChain = () => { + if (promiseIndex !== -1) + this.queryResponsibilityChain.splice(promiseIndex, 1); + if (waitingPromiseIndex !== -1) + this.queryResponsibilityChain.splice(waitingPromiseIndex, 1); + waitingOkay(); + }; + + let promiseIndex = this.queryResponsibilityChain.indexOf(promise); + let waitingPromiseIndex = this.queryResponsibilityChain.indexOf(waitingPromise); if (err) { this.driver.connection.logger.logFailedQuery(query, parameters); this.driver.connection.logger.logQueryError(err); + resolveChain(); return fail(err); } - ok(result); + ok(result.recordset); + resolveChain(); }); }); + if (this.isTransactionActive) + this.queryResponsibilityChain.push(promise); + + return promise; } /** diff --git a/src/driver/websql/WebsqlQueryRunner.ts b/src/driver/websql/WebsqlQueryRunner.ts index abed1471a..d43c91f8d 100644 --- a/src/driver/websql/WebsqlQueryRunner.ts +++ b/src/driver/websql/WebsqlQueryRunner.ts @@ -117,8 +117,6 @@ export class WebsqlQueryRunner implements QueryRunner { await this.rollbackTransaction(); throw error; - } finally { - await this.release(); // await this.query(`PRAGMA foreign_keys = ON;`); } }