diff --git a/.eslintrc.js b/.eslintrc.js index f730a261..22ef5812 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -54,9 +54,13 @@ module.exports = { // reason - parserOptions is set to es5 language-syntax "prefer-destructuring": "off", // reason - parserOptions is set to es5 language-syntax + "prefer-object-spread": "off", + // reason - parserOptions is set to es5 language-syntax "prefer-spread": "off", // reason - parserOptions is set to es5 language-syntax "prefer-template": "off", + // reason - Closure compiler renames unquoted properties + "quote-props": "off", // reason - sql.js frequently use sql-query-strings containing // single-quotes quotes: ["error", "double"], diff --git a/package-lock.json b/package-lock.json index 346d48ee..7b06628a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "sql.js", - "version": "1.4.0", + "version": "1.5.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/src/api.js b/src/api.js index 52bbab58..1fbdeab4 100644 --- a/src/api.js +++ b/src/api.js @@ -71,8 +71,15 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { var SQLITE_BLOB = 4; // var - Encodings, used for registering functions. var SQLITE_UTF8 = 1; + // var - Open flags + var SQLITE_OPEN_READWRITE = 0x00000002; + var SQLITE_OPEN_CREATE = 0x00000004; // var - cwrap function - var sqlite3_open = cwrap("sqlite3_open", "number", ["string", "number"]); + var sqlite3_open_v2 = cwrap( + "sqlite3_open_v2", + "number", + ["string", "number", "number", "string"] + ); var sqlite3_close_v2 = cwrap("sqlite3_close_v2", "number", ["number"]); var sqlite3_exec = cwrap( "sqlite3_exec", @@ -790,7 +797,12 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { if (data != null) { FS.createDataFile("/", this.filename, data, true, true); } - this.handleError(sqlite3_open(this.filename, apiTemp)); + this.handleError(sqlite3_open_v2( + this.filename, + apiTemp, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, + "unix" + )); this.db = getValue(apiTemp, "i32"); registerExtensionFunctions(this.db); // A list of all prepared statements of the database @@ -1039,6 +1051,9 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { @return {Uint8Array} An array of bytes of the SQLite3 database file */ Database.prototype["export"] = function exportDatabase() { + if (this.filename === ":memory:") { + throw new Error("export on ':memory:' database is not supported"); + } Object.values(this.statements).forEach(function each(stmt) { stmt["free"](); }); @@ -1046,7 +1061,12 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { this.functions = {}; this.handleError(sqlite3_close_v2(this.db)); var binaryDb = FS.readFile(this.filename, { encoding: "binary" }); - this.handleError(sqlite3_open(this.filename, apiTemp)); + this.handleError(sqlite3_open_v2( + this.filename, + apiTemp, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, + "unix" + )); this.db = getValue(apiTemp, "i32"); return binaryDb; }; @@ -1198,6 +1218,33 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { return this; }; + /** + * Open a database accessed with a specified VFS. + * @param {string} vfs Name of a registered VFS + * @param {string} filename Database filename + * @param {number} [flags] Open flags for sqlite3_open_v2, described at + * https://www.sqlite.org/c3ref/c_open_autoproxy.html + * @returns {Database} + */ + Database["with_vfs"] = function with_vfs(vfs, filename, flags) { + var instance = Object.create(Database.prototype); + instance.filename = filename; + instance.vfs = vfs; + instance.statements = {}; + instance.functions = {}; + + instance.handleError(sqlite3_open_v2( + instance.filename, + apiTemp, + flags || (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE), + vfs + )); + instance.db = getValue(apiTemp, "i32"); + registerExtensionFunctions(instance.db); + + return instance; + }; + // export Database to Module Module.Database = Database; }; diff --git a/src/exported_functions.json b/src/exported_functions.json index b93b07d2..29ce8d1e 100644 --- a/src/exported_functions.json +++ b/src/exported_functions.json @@ -1,7 +1,7 @@ [ "_malloc", "_free", -"_sqlite3_open", +"_sqlite3_open_v2", "_sqlite3_exec", "_sqlite3_free", "_sqlite3_errmsg", diff --git a/test/test_with_vfs.js b/test/test_with_vfs.js new file mode 100644 index 00000000..cc51f4a9 --- /dev/null +++ b/test/test_with_vfs.js @@ -0,0 +1,58 @@ +exports.test = async function(sql, assert) { + // Use database with a specified VFS. + async function doVFS(vfs, filename) { + const db = sql.Database.with_vfs(vfs, filename); + try { + db.run("CREATE TABLE tbl (x, y);"); + db.run("INSERT INTO tbl VALUES ('foo', 0), ('bar', 1), ('baz', 2);"); + return db.exec("SELECT SUM(y) FROM tbl;"); + } finally { + db.close(); + } + } + + let result; + result = await doVFS("unix", "db"); + assert.strictEqual(result[0].values[0][0], 3, 'built-in unix VFS executes SQL'); + + result = await doVFS("unix-none", "db"); + assert.strictEqual(result[0].values[0][0], 3, 'built-in unix-none VFS executes SQL'); + + result = await doVFS("unregistered", "db").catch(e => e); + assert.ok(result instanceof Error, 'unregistered VFS throws'); + + // Export database with a specified filename. + async function doExport(vfs, filename) { + const db = sql.Database.with_vfs(vfs, filename); + try { + db.run("CREATE TABLE tbl (x, y);"); + db.run("INSERT INTO tbl VALUES ('foo', 0), ('bar', 1);"); + const exported = db.export(); + return String.fromCharCode.apply(null, exported.subarray(0,6)); + } finally { + db.close(); + } + } + + result = await doExport("unix", "foo"); + assert.strictEqual(result, "SQLite", 'built-in VFS exports'); + + result = await doExport("unix", ":memory:").catch(e => e); + assert.ok(result instanceof Error, ':memory: throws on export'); +}; + +if (module == require.main) { + const target_file = process.argv[2]; + const sql_loader = require('./load_sql_lib'); + sql_loader(target_file).then((sql)=>{ + require('test').run({ + 'test with_vfs': function(assert){ + return exports.test(sql, assert); + } + }); + }) + .catch((e)=>{ + console.error(e); + assert.fail(e); + }); +}