Skip to content

Commit

Permalink
feat: backport SQLite Busy handler & WAL mode enable (#6588)
Browse files Browse the repository at this point in the history
* added sqlitebusy handling logic

* added sqlite wal mode enable logic

* cleaner if block

* move pragma journal mode setting to driver connection

* add enable-wal test

Co-authored-by: Umed Khudoiberdiev <zarrhost@gmail.com>
  • Loading branch information
imnotjames and Umed Khudoiberdiev committed Sep 12, 2020
1 parent 370442c commit 7a52f18
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 11 deletions.
22 changes: 21 additions & 1 deletion src/driver/sqlite/SqliteConnectionOptions.ts
Expand Up @@ -20,4 +20,24 @@ export interface SqliteConnectionOptions extends BaseConnectionOptions {
*/
readonly key?: string;

}
/**
* 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;

}
4 changes: 4 additions & 0 deletions src/driver/sqlite/SqliteDriver.ts
Expand Up @@ -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;`);
Expand Down
33 changes: 23 additions & 10 deletions 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";

Expand Down Expand Up @@ -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<any[]>(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;
Expand All @@ -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();
});
}
}
}
24 changes: 24 additions & 0 deletions 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'}]);
})));
});

0 comments on commit 7a52f18

Please sign in to comment.