Skip to content

Commit

Permalink
added readonly mode
Browse files Browse the repository at this point in the history
  • Loading branch information
JoshuaWise committed Apr 8, 2017
1 parent 404c739 commit 550fd8d
Show file tree
Hide file tree
Showing 12 changed files with 79 additions and 23 deletions.
11 changes: 10 additions & 1 deletion lib/database.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
'use strict';
var fs = require('fs');
var path = require('path');
var toDescriptor = require('to-descriptor');
var CPPDatabase = require('bindings')({
Expand Down Expand Up @@ -36,10 +37,18 @@ function Database(filenameGiven, options) {
.replace(/\/\/+/g, '/')
.replace(/#/g, '%23')
+ '?mode=memory&cache=shared';
} else if (!pathExists(path.dirname(filename))) {
throw new TypeError('Cannot open database because the directory does not exist.');
}

return new CPPDatabase(filename, filenameGiven, !!options.memory);
return new CPPDatabase(filename, filenameGiven, !!options.memory, !!options.readonly);
}

function pathExists(path) {
try {fs.accessSync(path); return true;}
catch (ex) {return false;}
}

CPPDatabase.prototype.constructor = Database;
Database.prototype = Object.create(Object.prototype, toDescriptor(CPPDatabase.prototype));
Object.keys(CPPDatabase.prototype).forEach(function (method) {delete CPPDatabase.prototype[method];});
Expand Down
3 changes: 3 additions & 0 deletions src/objects/database/create-statement.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ NAN_METHOD(Database::CreateStatement) {
// Determine if the sqlite3_stmt returns data or not.
int column_count = sqlite3_column_count(stmt->st_handle);
if (!sqlite3_stmt_readonly(stmt->st_handle) || column_count < 1) {
if (db->readonly) {
return Nan::ThrowTypeError("This operation is not available while in readonly mode.");
}
stmt->column_count = 0;
} else {
stmt->column_count = column_count;
Expand Down
3 changes: 3 additions & 0 deletions src/objects/database/create-transaction.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ NAN_METHOD(Database::CreateTransaction) {
if (db->in_each) {
return Nan::ThrowTypeError("This database connection is busy executing a query.");
}
if (db->readonly) {
return Nan::ThrowTypeError("This operation is not available while in readonly mode.");
}

unsigned int len = sources->Length();
v8::Local<v8::Array> digestedSources = Nan::New<v8::Array>(len);
Expand Down
5 changes: 3 additions & 2 deletions src/objects/database/database.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ Nan::Persistent<v8::Function> NullFactory;
#include "checkpoint.cc"
#include "util.cc"

Database::Database() : Nan::ObjectWrap(),
Database::Database(bool readonly) : Nan::ObjectWrap(),
db_handle(NULL),
t_handles(),
open(true),
in_each(false),
safe_ints(false) {}
safe_ints(false),
readonly(readonly) {}
Database::~Database() {
// This is necessary in the case that a database and its statements are
// garbage collected at the same time. The database might be destroyed
Expand Down
3 changes: 2 additions & 1 deletion src/objects/database/database.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ extern Nan::Persistent<v8::Function> NullFactory;
// Class Declaration
class Database : public Nan::ObjectWrap {
public:
explicit Database();
explicit Database(bool);
~Database();
static void Init(v8::Local<v8::Object>, v8::Local<v8::Object>);

Expand Down Expand Up @@ -47,6 +47,7 @@ class Database : public Nan::ObjectWrap {
bool open;
bool in_each;
bool safe_ints;
const bool readonly;

// Associated Statements and Transactions
std::set<Statement*, Statement::Compare> stmts;
Expand Down
3 changes: 3 additions & 0 deletions src/objects/database/exec.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ NAN_METHOD(Database::Exec) {
if (db->in_each) {
return Nan::ThrowTypeError("This database connection is busy executing a query.");
}
if (db->readonly) {
return Nan::ThrowTypeError("This operation is not available while in readonly mode.");
}

// Prepares the SQL string.
Nan::Utf8String utf8(source);
Expand Down
4 changes: 3 additions & 1 deletion src/objects/database/new.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ NAN_METHOD(Database::New) {
REQUIRE_ARGUMENT_STRING(0, filename);
REQUIRE_ARGUMENT_STRING(1, filenameGiven);
REQUIRE_ARGUMENT_BOOLEAN(2, inMemory);
REQUIRE_ARGUMENT_BOOLEAN(3, readonly);

Database* db = new Database();
Database* db = new Database(readonly);
db->Wrap(info.This());
Nan::ForceSet(info.This(), NEW_INTERNAL_STRING_FAST("memory"), inMemory ? Nan::True() : Nan::False(), FROZEN);
Nan::ForceSet(info.This(), NEW_INTERNAL_STRING_FAST("readonly"), readonly ? Nan::True() : Nan::False(), FROZEN);
Nan::ForceSet(info.This(), NEW_INTERNAL_STRING_FAST("name"), filenameGiven, FROZEN);

Nan::Utf8String utf8(filename);
Expand Down
20 changes: 20 additions & 0 deletions test/10.database.open.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ describe('new Database()', function () {
var db = new Database(util.current());
expect(db.name).to.equal(util.current());
expect(db.memory).to.be.false;
expect(db.readonly).to.be.false;
expect(db.open).to.be.true;
fs.accessSync(util.current());
});
Expand All @@ -46,6 +47,25 @@ describe('new Database()', function () {
var db = new Database(util.current(), {memory: true});
expect(db.name).to.equal(util.current());
expect(db.memory).to.be.true;
expect(db.readonly).to.be.false;
expect(db.open).to.be.true;
expect(function () {fs.accessSync(util.current());}).to.throw(Error);
});
it('should allow readonly database connections to be created', function () {
expect(function () {fs.accessSync(util.next());}).to.throw(Error);
var db = new Database(util.current(), {readonly: true});
expect(db.name).to.equal(util.current());
expect(db.memory).to.be.false;
expect(db.readonly).to.be.true;
expect(db.open).to.be.true;
fs.accessSync(util.current());
});
it('should allow the "readonly" and "memory" options on the same connection', function () {
expect(function () {fs.accessSync(util.next());}).to.throw(Error);
var db = new Database(util.current(), {memory: true, readonly: true});
expect(db.name).to.equal(util.current());
expect(db.memory).to.be.true;
expect(db.readonly).to.be.true;
expect(db.open).to.be.true;
expect(function () {fs.accessSync(util.current());}).to.throw(Error);
});
Expand Down
6 changes: 6 additions & 0 deletions test/12.database.pragma.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ describe('Database#pragma()', function () {
db.pragma('cache_size = -8000');
expect(db.pragma('cache_size', true)).to.equal('-8000');
});
it('should be available to readonly connections', function () {
var db = new Database(util.next(), {readonly: true});
expect(db.pragma('cache_size', true)).to.equal('-16000');
db.pragma('cache_size = -8000');
expect(db.pragma('cache_size', true)).to.equal('-8000');
});
it('should return undefined if no rows exist and simpler results are desired', function () {
var db = new Database(util.next());
expect(db.pragma('table_info', true)).to.be.undefined;
Expand Down
36 changes: 18 additions & 18 deletions test/13.database.prepare.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ var util = (function () {
}());

describe('Database#prepare()', function () {
function assertStmt(stmt, source, db, returnsData) {
expect(stmt.source).to.equal(source);
expect(stmt.constructor.name).to.equal('Statement');
expect(stmt.database).to.equal(db);
expect(stmt.returnsData).to.equal(returnsData);
expect(function () {
new stmt.constructor(source);
}).to.throw(TypeError);
}
it('should throw an exception if a string is not provided', function () {
var db = new Database(util.next());
expect(function () {db.prepare(123);}).to.throw(TypeError);
Expand All @@ -38,32 +47,23 @@ describe('Database#prepare()', function () {
expect(function () {db.prepare('CREATE TABLE people (name TEXT);;');}).to.throw(TypeError);
});
it('should create a prepared Statement object', function () {
function assertStmt(stmt, source) {
expect(stmt.source).to.equal(source);
expect(stmt.constructor.name).to.equal('Statement');
expect(stmt.database).to.equal(db);
expect(stmt.returnsData).to.equal(false);
expect(function () {
new stmt.constructor(source);
}).to.throw(TypeError);
}
var db = new Database(util.next());
var stmt1 = db.prepare('CREATE TABLE people (name TEXT)');
var stmt2 = db.prepare('CREATE TABLE people (name TEXT);');
assertStmt(stmt1, 'CREATE TABLE people (name TEXT)');
assertStmt(stmt2, 'CREATE TABLE people (name TEXT);');
assertStmt(stmt1, 'CREATE TABLE people (name TEXT)', db, false);
assertStmt(stmt2, 'CREATE TABLE people (name TEXT);', db, false);
expect(stmt1).to.not.equal(stmt2);
expect(stmt1).to.not.equal(db.prepare('CREATE TABLE people (name TEXT)'));
});
it('should create a prepared Statement object with just an expression', function () {
var db = new Database(util.next());
var stmt = db.prepare('SELECT 555');
expect(stmt.source).to.equal('SELECT 555');
expect(stmt.constructor.name).to.equal('Statement');
expect(stmt.database).to.equal(db);
expect(stmt.returnsData).to.equal(true);
expect(function () {
new stmt.constructor('SELECT 555');
}).to.throw(TypeError);
assertStmt(stmt, 'SELECT 555', db, true);
});
it('should obey the restrictions of readonly mode', function () {
var db = new Database(util.next(), {readonly: true});
expect(function () {db.prepare('CREATE TABLE people (name TEXT)');}).to.throw(TypeError);
var stmt = db.prepare('SELECT 555');
assertStmt(stmt, 'SELECT 555', db, true);
});
});
4 changes: 4 additions & 0 deletions test/14.database.transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ describe('Database#transaction()', function () {
expect(function () {db.transaction(['CREATE TABLE people (name TEXT)', 'COMMIT TRANSACTION']);}).to.throw(TypeError);
expect(function () {db.transaction(['CREATE TABLE people (name TEXT)', 'ROLLBACK TRANSACTION']);}).to.throw(TypeError);
});
it('should throw an exception if used on a readonly database connection', function () {
var db = new Database(util.next(), {readonly: true});
expect(function () {db.transaction(['CREATE TABLE people (name TEXT)']);}).to.throw(TypeError);
});
it('should create a prepared Transaction object', function () {
var db = new Database(util.next());
var trans1 = db.transaction(['CREATE TABLE people (name TEXT)']);
Expand Down
4 changes: 4 additions & 0 deletions test/15.database.exec.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ describe('Database#exec()', function () {
it('should throw an exception if invalid SQL is provided', function () {
expect(function () {db.exec('CREATE TABLE entries (a TEXT, b INTEGER');}).to.throw(Error);
});
it('should throw an exception if used on a readonly database connection', function () {
var readonly = new Database(db.name, {readonly: true});
expect(function () {readonly.exec('CREATE TABLE entries (a TEXT, b INTEGER)');}).to.throw(TypeError);
});
it('should execute the SQL, returning the database object itself', function () {
var returnValues = [];

Expand Down

2 comments on commit 550fd8d

@seanfisher
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JoshuaWise Does using readonly mode give performance enhancements, or is it just a convenience/protection for the developer?

@JoshuaWise
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@seanfisher It's just for the convenience/protection of the developer.

Please sign in to comment.