diff --git a/src/driver/sqlite/SqliteConnectionOptions.ts b/src/driver/sqlite/SqliteConnectionOptions.ts index 9155047e4f..8e092b9c69 100644 --- a/src/driver/sqlite/SqliteConnectionOptions.ts +++ b/src/driver/sqlite/SqliteConnectionOptions.ts @@ -20,4 +20,24 @@ export interface SqliteConnectionOptions extends BaseConnectionOptions { */ readonly key?: string; -} \ No newline at end of file + /** + * In your SQLite application when you perform parallel writes its common to face SQLITE_BUSY error. + * This error indicates that SQLite failed to write to the database file since someone else already writes into it. + * Since SQLite cannot handle parallel saves this error cannot be avoided. + * + * To simplify life's of those who have this error this particular option sets a timeout within which ORM will try + * to perform requested write operation again and again until it receives SQLITE_BUSY error. + * + * Enabling WAL can improve your app performance and face less SQLITE_BUSY issues. + * Time in milliseconds. + */ + readonly busyErrorRetry?: number; + + /** + * Enables WAL mode. By default its disabled. + * + * @see https://www.sqlite.org/wal.html + */ + readonly enableWAL?: boolean; + +} diff --git a/src/driver/sqlite/SqliteDriver.ts b/src/driver/sqlite/SqliteDriver.ts index fd271d72f2..aa27e36544 100644 --- a/src/driver/sqlite/SqliteDriver.ts +++ b/src/driver/sqlite/SqliteDriver.ts @@ -107,6 +107,10 @@ export class SqliteDriver extends AbstractSqliteDriver { }); } + if (this.options.enableWAL) { + await run(`PRAGMA journal_mode = WAL;`); + } + // 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. await run(`PRAGMA foreign_keys = ON;`); diff --git a/src/driver/sqlite/SqliteQueryRunner.ts b/src/driver/sqlite/SqliteQueryRunner.ts index 6e5a55d885..308bb72855 100644 --- a/src/driver/sqlite/SqliteQueryRunner.ts +++ b/src/driver/sqlite/SqliteQueryRunner.ts @@ -1,6 +1,7 @@ import {QueryRunnerAlreadyReleasedError} from "../../error/QueryRunnerAlreadyReleasedError"; import {QueryFailedError} from "../../error/QueryFailedError"; import {AbstractSqliteQueryRunner} from "../sqlite-abstract/AbstractSqliteQueryRunner"; +import {SqliteConnectionOptions} from "./SqliteConnectionOptions"; import {SqliteDriver} from "./SqliteDriver"; import {Broadcaster} from "../../subscriber/Broadcaster"; @@ -36,10 +37,30 @@ export class SqliteQueryRunner extends AbstractSqliteQueryRunner { throw new QueryRunnerAlreadyReleasedError(); const connection = this.driver.connection; + const options = connection.options as SqliteConnectionOptions; return new Promise(async (ok, fail) => { + const databaseConnection = await this.connect(); + this.driver.connection.logger.logQuery(query, parameters, this); + const queryStartTime = +new Date(); + const isInsertQuery = query.substr(0, 11) === "INSERT INTO"; + + const execute = async () => { + if (isInsertQuery) { + databaseConnection.run(query, parameters, handler); + } else { + databaseConnection.all(query, parameters, handler); + } + }; + const handler = function (err: any, result: any) { + if (err && err.toString().indexOf("SQLITE_BUSY:") !== -1) { + if (typeof options.busyErrorRetry === "number" && options.busyErrorRetry > 0) { + setTimeout(execute, options.busyErrorRetry); + return; + } + } // log slow queries if maxQueryExecution time is set const maxQueryExecutionTime = connection.options.maxQueryExecutionTime; @@ -56,15 +77,7 @@ export class SqliteQueryRunner extends AbstractSqliteQueryRunner { } }; - const databaseConnection = await this.connect(); - this.driver.connection.logger.logQuery(query, parameters, this); - const queryStartTime = +new Date(); - const isInsertQuery = query.substr(0, 11) === "INSERT INTO"; - if (isInsertQuery) { - databaseConnection.run(query, parameters, handler); - } else { - databaseConnection.all(query, parameters, handler); - } + await execute(); }); } -} \ No newline at end of file +} diff --git a/test/functional/sqlite/enable-wal.ts b/test/functional/sqlite/enable-wal.ts new file mode 100644 index 0000000000..b1af701ebf --- /dev/null +++ b/test/functional/sqlite/enable-wal.ts @@ -0,0 +1,24 @@ +import "reflect-metadata"; +import {expect} from "chai"; +import {Connection} from "../../../src/connection/Connection"; +import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../utils/test-utils"; + +describe("sqlite driver > enable wal", () => { + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + entities: [], + enabledDrivers: ["sqlite"], + driverSpecific: { + enableWAL: true + } + })); + beforeEach(() => reloadTestingDatabases(connections)); + after(() => closeTestingConnections(connections)); + + it("should set the journal mode as expected", () => Promise.all(connections.map(async connection => { + // if we come this far, test was successful as a connection was established + const result = await connection.query('PRAGMA journal_mode'); + + expect(result).to.eql([{ journal_mode: 'wal'}]); + }))); +});