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: support connection flags #735

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
7 changes: 7 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ Various options are accepted:

- `options.fileMustExist`: if the database does not exist, an `Error` will be thrown instead of creating a new file. This option is ignored for in-memory, temporary, or readonly database connections (default: `false`).

- `options.flags`: flags to use when opening the database connection (default: `0`). Supported flag constants are available as properties of the `Database.Constants` object with the `OPEN_` prefix.

```js
const Database = require('better-sqlite3');
const db = new Database('file:foobar.db', { flags: Database.Constants.OPEN_URI });
```

- `options.timeout`: the number of milliseconds to wait when executing queries on a locked database, before throwing a `SQLITE_BUSY` error (default: `5000`).

- `options.verbose`: provide a function that gets called with every SQL string executed by the database connection (default: `null`).
Expand Down
10 changes: 8 additions & 2 deletions lib/database.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ const path = require('path');
const util = require('./util');

const {
Constants,
Database: CPPDatabase,
setErrorConstructor,
} = require('bindings')('better_sqlite3.node');
const { OPEN_URI } = Constants;

function Database(filenameGiven, options) {
if (new.target == null) {
Expand Down Expand Up @@ -35,20 +37,22 @@ function Database(filenameGiven, options) {
const fileMustExist = util.getBooleanOption(options, 'fileMustExist');
const timeout = 'timeout' in options ? options.timeout : 5000;
const verbose = 'verbose' in options ? options.verbose : null;
const flags = 'flags' in options ? options.flags : 0;

// Validate interpreted options
if (readonly && anonymous && !buffer) throw new TypeError('In-memory/temporary databases cannot be readonly');
if (!Number.isInteger(timeout) || timeout < 0) throw new TypeError('Expected the "timeout" option to be a positive integer');
if (timeout > 0x7fffffff) throw new RangeError('Option "timeout" cannot be greater than 2147483647');
if (verbose != null && typeof verbose !== 'function') throw new TypeError('Expected the "verbose" option to be a function');
if (!Number.isInteger(flags)) throw new TypeError('Expected the "flags" option to be an integer');

// Make sure the specified directory exists
if (!anonymous && !fs.existsSync(path.dirname(filename))) {
if (!anonymous && (flags & OPEN_URI) !== OPEN_URI && !fs.existsSync(path.dirname(filename))) {
throw new TypeError('Cannot open database because the directory does not exist');
}

Object.defineProperties(this, {
[util.cppdb]: { value: new CPPDatabase(filename, filenameGiven, anonymous, readonly, fileMustExist, timeout, verbose || null, buffer || null) },
[util.cppdb]: { value: new CPPDatabase(filename, filenameGiven, anonymous, readonly, fileMustExist, timeout, verbose || null, buffer || null, flags) },
...wrappers.getters,
});
}
Expand All @@ -69,5 +73,7 @@ Database.prototype.defaultSafeIntegers = wrappers.defaultSafeIntegers;
Database.prototype.unsafeMode = wrappers.unsafeMode;
Database.prototype[util.inspect] = require('./methods/inspect');

Database.Constants = Constants;

module.exports = Database;
setErrorConstructor(require('./sqlite-error'));
16 changes: 14 additions & 2 deletions src/better_sqlite3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ NODE_MODULE_INIT(/* exports, context */) {
exports->Set(context, InternalizedFromLatin1(isolate, "Backup"), Backup::Init(isolate, data)).FromJust();
exports->Set(context, InternalizedFromLatin1(isolate, "setErrorConstructor"), v8::FunctionTemplate::New(isolate, Addon::JS_setErrorConstructor, data)->GetFunction(context).ToLocalChecked()).FromJust();

v8::Local<v8::Object> constants = v8::Object::New(isolate);
constants->Set(context, InternalizedFromLatin1(isolate, "OPEN_FULLMUTEX"), v8::Integer::New(isolate, SQLITE_OPEN_FULLMUTEX)).FromJust();
constants->Set(context, InternalizedFromLatin1(isolate, "OPEN_MEMORY"), v8::Integer::New(isolate, SQLITE_OPEN_MEMORY)).FromJust();
constants->Set(context, InternalizedFromLatin1(isolate, "OPEN_NOFOLLOW"), v8::Integer::New(isolate, SQLITE_OPEN_NOFOLLOW)).FromJust();
constants->Set(context, InternalizedFromLatin1(isolate, "OPEN_NOMUTEX"), v8::Integer::New(isolate, SQLITE_OPEN_NOMUTEX)).FromJust();
constants->Set(context, InternalizedFromLatin1(isolate, "OPEN_PRIVATECACHE"), v8::Integer::New(isolate, SQLITE_OPEN_PRIVATECACHE)).FromJust();
constants->Set(context, InternalizedFromLatin1(isolate, "OPEN_SHAREDCACHE"), v8::Integer::New(isolate, SQLITE_OPEN_SHAREDCACHE)).FromJust();
constants->Set(context, InternalizedFromLatin1(isolate, "OPEN_URI"), v8::Integer::New(isolate, SQLITE_OPEN_URI)).FromJust();
exports->Set(context, InternalizedFromLatin1(isolate, "Constants"), constants).FromJust();

// Store addon instance data.
addon->Statement.Reset(isolate, exports->Get(context, InternalizedFromLatin1(isolate, "Statement")).ToLocalChecked().As<v8::Function>());
addon->StatementIterator.Reset(isolate, exports->Get(context, InternalizedFromLatin1(isolate, "StatementIterator")).ToLocalChecked().As<v8::Function>());
Expand Down Expand Up @@ -417,16 +427,18 @@ void Database::JS_new (v8::FunctionCallbackInfo <v8 :: Value> const & info)
if ( info . Length ( ) <= ( 5 ) || ! info [ 5 ] -> IsInt32 ( ) ) return ThrowTypeError ( "Expected " "sixth" " argument to be " "a 32-bit signed integer" ) ; int timeout = ( info [ 5 ] . As < v8 :: Int32 > ( ) ) -> Value ( ) ;
if ( info . Length ( ) <= ( 6 ) ) return ThrowTypeError ( "Expected a " "seventh" " argument" ) ; v8 :: Local < v8 :: Value > logger = info [ 6 ] ;
if ( info . Length ( ) <= ( 7 ) ) return ThrowTypeError ( "Expected a " "eighth" " argument" ) ; v8 :: Local < v8 :: Value > buffer = info [ 7 ] ;
if ( info . Length ( ) <= ( 8 ) || ! info [ 8 ] -> IsInt32 ( ) ) return ThrowTypeError ( "Expected " "ninth" " argument to be " "a 32-bit signed integer" ) ; int extra_flags = ( info [ 8 ] . As < v8 :: Int32 > ( ) ) -> Value ( ) ;

Addon * addon = static_cast < Addon * > ( info . Data ( ) . As < v8 :: External > ( ) -> Value ( ) ) ;
v8 :: Isolate * isolate = info . GetIsolate ( ) ;
sqlite3* db_handle;
v8::String::Utf8Value utf8(isolate, filename);
int mask = readonly ? SQLITE_OPEN_READONLY
int flags = readonly ? SQLITE_OPEN_READONLY
: must_exist ? SQLITE_OPEN_READWRITE
: (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
flags |= extra_flags;

if (sqlite3_open_v2(*utf8, &db_handle, mask, NULL) != SQLITE_OK) {
if (sqlite3_open_v2(*utf8, &db_handle, flags, NULL) != SQLITE_OK) {
ThrowSqliteError(addon, db_handle);
int status = sqlite3_close(db_handle);
assert(status == SQLITE_OK); ((void)status);
Expand Down