diff --git a/docs/api.md b/docs/api.md index 49b94ede3..8a3cb2214 100644 --- a/docs/api.md +++ b/docs/api.md @@ -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`). diff --git a/lib/database.js b/lib/database.js index d271adc05..9ac83c0bd 100644 --- a/lib/database.js +++ b/lib/database.js @@ -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) { @@ -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, }); } @@ -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')); diff --git a/src/better_sqlite3.cpp b/src/better_sqlite3.cpp index 93fc14b76..cd8e25bfa 100644 --- a/src/better_sqlite3.cpp +++ b/src/better_sqlite3.cpp @@ -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 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()); addon->StatementIterator.Reset(isolate, exports->Get(context, InternalizedFromLatin1(isolate, "StatementIterator")).ToLocalChecked().As()); @@ -417,16 +427,18 @@ void Database::JS_new (v8::FunctionCallbackInfo 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);