Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

83 changes: 72 additions & 11 deletions src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -776,6 +783,19 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
};
}

/**
* @typedef {Object} Database.Options
* @property {string} [filename] Database filename. Can be ":memory:"
* to bypass the filesystem but "export" method should not be used.
* If unset, a random name will be generated.
* @property {number} [flags] Any combination of SQLite open flags. See
* https://www.sqlite.org/c3ref/c_open_autoproxy.html (default:
* SQLITE3_OPEN_CREATE | SQLITE3_OPEN_READWRITE).
* @property {string} [vfs] VFS name. If specified, an initialization
* array cannot be provided to the constructor and the "export" method
* should not be used.
*/

/** @classdesc
* Represents an SQLite database
* @constructs Database
Expand All @@ -784,13 +804,34 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
* one stored in the byte array passed in first argument
* @param {number[]} data An array of bytes representing
* an SQLite database file
* @param {Database.Options=} options
*/
function Database(data) {
this.filename = "dbfile_" + (0xffffffff * Math.random() >>> 0);
if (data != null) {
FS.createDataFile("/", this.filename, data, true, true);
function Database(data, options) {
this.options = Object.assign(
{
"filename": "dbfile_" + (0xffffffff * Math.random() >>> 0),
"flags": SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE,
"vfs": undefined
},
options
);
if (data) {
if (this.options["vfs"]) {
throw new Error(
"Loading data is not supported if a VFS is specified"
);
}
FS.createDataFile(
"/",
this.options["filename"], data, true, true
);
}
this.handleError(sqlite3_open(this.filename, apiTemp));
this.handleError(sqlite3_open_v2(
this.options["filename"],
apiTemp,
this.options["flags"],
this.options["vfs"] || "unix"
));
this.db = getValue(apiTemp, "i32");
registerExtensionFunctions(this.db);
// A list of all prepared statements of the database
Expand Down Expand Up @@ -1035,18 +1076,36 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
return new StatementIterator(sql, this);
};

/** Exports the contents of the database to a binary array
@return {Uint8Array} An array of bytes of the SQLite3 database file
/** Exports the contents of the database to a binary array.
* Throws an error if the database filename is ":memory:" or
* a VFS is specified.
* @return {Uint8Array} An array of bytes of the SQLite3 database file
*/
Database.prototype["export"] = function exportDatabase() {
if (this.options["filename"] === ":memory:") {
throw new Error("Exporting a \":memory:\" DB is not supported");
}
if (this.options["vfs"]) {
throw new Error(
"Exporting is not supported if a VFS is specified"
);
}
Object.values(this.statements).forEach(function each(stmt) {
stmt["free"]();
});
Object.values(this.functions).forEach(removeFunction);
this.functions = {};
this.handleError(sqlite3_close_v2(this.db));
var binaryDb = FS.readFile(this.filename, { encoding: "binary" });
this.handleError(sqlite3_open(this.filename, apiTemp));
var binaryDb = FS.readFile(
this.options["filename"],
{ encoding: "binary" }
);
this.handleError(sqlite3_open_v2(
this.options["filename"],
apiTemp,
this.options["flags"],
this.options["vfs"] || "unix"
));
this.db = getValue(apiTemp, "i32");
return binaryDb;
};
Expand All @@ -1072,7 +1131,9 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
Object.values(this.functions).forEach(removeFunction);
this.functions = {};
this.handleError(sqlite3_close_v2(this.db));
FS.unlink("/" + this.filename);
if (this.options["filename"] !== ":memory:" && !this.options["vfs"]) {
FS.unlink("/" + this.options["filename"]);
}
this.db = null;
};

Expand Down
2 changes: 1 addition & 1 deletion src/exported_functions.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[
"_malloc",
"_free",
"_sqlite3_open",
"_sqlite3_open_v2",
"_sqlite3_exec",
"_sqlite3_free",
"_sqlite3_errmsg",
Expand Down
58 changes: 58 additions & 0 deletions test/test_database_options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
exports.test = async function(sql, assert) {
// Use database with a specified VFS.
async function doVFS(vfs) {
var db = new sql.Database(null, { vfs });
try {
db.run("CREATE TABLE tbl (x, y);");
db.run("INSERT INTO tbl VALUES ('foo', 0), ('bar', 1);");
return db.exec("SELECT * FROM tbl;");
} finally {
db.close();
}
}

const vfsNone = await doVFS();
assert.ok(vfsNone, 'no VFS executes SQL');

const vfsValid = await doVFS("unix-none")
assert.ok(vfsValid, 'valid VFS executes SQL');

const vfsInvalid = await doVFS("not a vfs").catch(e => e);
assert.ok(vfsInvalid instanceof Error, 'invalid VFS throws');

// Attempt export of database with specified filename.
async function doExport(filename) {
var db = new sql.Database(null, { filename });
try {
db.run("CREATE TABLE tbl (x, y);");
db.run("INSERT INTO tbl VALUES ('foo', 0), ('bar', 1);");
return db.export();
} finally {
db.close();
}
}

const exportFile = await doExport('foo');
assert.strictEqual(
String.fromCharCode.apply(null, exportFile.subarray(0,6)), 'SQLite',
'export custom filename valid');

const exportMem = await doExport(':memory:').catch(e => e);
assert.strictEqual(exportMem instanceof Error, true, 'export :memory: throws');
};

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 database options': function(assert){
return exports.test(sql, assert);
}
});
})
.catch((e)=>{
console.error(e);
assert.fail(e);
});
}