Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: backport SQLite Busy handler & WAL mode enable #6588

Merged
merged 5 commits into from Sep 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@ -105,6 +105,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'}]);
})));
});