@rabby-wallet/react-native-sqlite-storage
Version:
SQLite3 bindings for React Native (Android & iOS)
856 lines (788 loc) • 23.5 kB
JavaScript
/*
* sqlite.ios.core.js
*
* Created by Andrzej Porebski on 10/29/15.
* Copyright (c) 2015-2016 Andrzej Porebski.
*
* This software is largely based on the SQLLite Storage Cordova Plugin created by Chris Brody & Davide Bertola.
* The implementation was adopted and converted to use React Native bindings.
*
* See https://github.com/litehelpers/Cordova-sqlite-storage
*
* This library is available under the terms of the MIT License (2008).
* See http://opensource.org/licenses/alphabetical for full text.
*/
var NativeModules = require("react-native").NativeModules;
var DB_STATE_INIT, DB_STATE_OPEN, READ_ONLY_REGEX, SQLiteFactory, SQLitePlugin, SQLitePluginTransaction, argsArray, dblocations, newSQLError, txLocks;
var plugin = {};
READ_ONLY_REGEX = /^(\s|;)*(?:alter|create|delete|drop|insert|reindex|replace|update)/i;
DB_STATE_INIT = "INIT";
DB_STATE_OPEN = "OPEN";
txLocks = {};
newSQLError = function(error, code) {
var sqlError;
sqlError = error;
if (!code) {
code = 0;
}
if (!sqlError) {
sqlError = new Error("a plugin had an error but provided no response");
sqlError.code = code;
}
if (typeof sqlError === "string") {
sqlError = new Error(error);
sqlError.code = code;
}
if (!sqlError.code && sqlError.message) {
sqlError.code = code;
}
if (!sqlError.code && !sqlError.message) {
sqlError = new Error("an unknown error was returned: " + JSON.stringify(sqlError));
sqlError.code = code;
}
return sqlError;
};
let nextTick = setImmediate || function(fun) {
setTimeout(fun, 0);
};
if (global.window) {
nextTick = window.setImmediate || function(fun) {
window.setTimeout(fun, 0);
};
}
/*
Utility that avoids leaking the arguments object. See
https://www.npmjs.org/package/argsarray
*/
argsArray = function(fun) {
return function() {
var args, i, len;
len = arguments.length;
if (len) {
args = [];
i = -1;
while (++i < len) {
args[i] = arguments[i];
}
return fun.call(this, args);
} else {
return fun.call(this, []);
}
};
};
plugin.exec = function(method, options, success, error) {
if (plugin.sqlitePlugin.DEBUG){
plugin.log("SQLite." + method + "(" + JSON.stringify(options) + ")");
}
NativeModules["SQLite"][method](options,success,error);
};
plugin.log = function(...messages) {
if (plugin.sqlitePlugin.DEBUG) {
console.log(...messages)
}
}
plugin.warn = function(...messages) {
console.warn(...messages)
}
plugin.error = function(...messages) {
console.error(...messages)
}
SQLitePlugin = function(openargs, openSuccess, openError) {
var dbname;
if (!(openargs && openargs["name"])) {
throw newSQLError("Cannot create a SQLitePlugin db instance without a db name");
}
dbname = openargs.name;
if (typeof dbname !== "string") {
throw newSQLError("sqlite plugin database name must be a string");
}
this.openargs = openargs;
this.dbname = dbname;
this.openSuccess = openSuccess;
this.openError = openError;
this.openSuccess || (this.openSuccess = function() {
plugin.log("DB opened: " + dbname);
});
this.openError || (this.openError = function(e) {
plugin.log(e.message);
});
this.open(this.openSuccess, this.openError);
};
SQLitePlugin.prototype.databaseFeatures = {
isSQLitePluginDatabase: true
};
SQLitePlugin.prototype.openDBs = {};
SQLitePlugin.prototype.addTransaction = function(t) {
if (!txLocks[this.dbname]) {
txLocks[this.dbname] = {
queue: [],
inProgress: false
};
}
txLocks[this.dbname].queue.push(t);
if (this.dbname in this.openDBs && this.openDBs[this.dbname] !== DB_STATE_INIT) {
this.startNextTransaction();
} else {
if (this.dbname in this.openDBs) {
plugin.log("new transaction is waiting for open operation");
} else {
plugin.log("database is closed, new transaction is [stuck] waiting until db is opened again!");
}
}
};
SQLitePlugin.prototype.transaction = function(fn, error, success) {
if (!this.openDBs[this.dbname]) {
error(newSQLError("database not open"));
return;
}
this.addTransaction(new SQLitePluginTransaction(this, fn, error, success, true, false));
};
SQLitePlugin.prototype.readTransaction = function(fn, error, success) {
if (!this.openDBs[this.dbname]) {
error(newSQLError("database not open"));
return;
}
this.addTransaction(new SQLitePluginTransaction(this, fn, error, success, false, true));
};
SQLitePlugin.prototype.startNextTransaction = function() {
var self;
self = this;
nextTick((function(_this) {
return function() {
var txLock;
if (!(_this.dbname in _this.openDBs) || _this.openDBs[_this.dbname] !== DB_STATE_OPEN) {
plugin.log("cannot start next transaction: database not open");
return;
}
txLock = txLocks[self.dbname];
if (!txLock) {
plugin.log("cannot start next transaction: database connection is lost");
} else if (txLock.queue.length > 0 && !txLock.inProgress) {
txLock.inProgress = true;
txLock.queue.shift().start();
}
};
})(this));
};
SQLitePlugin.prototype.abortAllPendingTransactions = function() {
var j, len1, ref, tx, txLock;
txLock = txLocks[this.dbname];
if (!!txLock && txLock.queue.length > 0) {
ref = txLock.queue;
for (j = 0, len1 = ref.length; j < len1; j++) {
tx = ref[j];
tx.abortFromQ(newSQLError("Invalid database handle"));
}
txLock.queue = [];
txLock.inProgress = false;
}
};
SQLitePlugin.prototype.sqlBatch = function(sqlStatements, success, error) {
var batchList, j, len1, myfn, st;
if (!sqlStatements || sqlStatements.constructor !== Array) {
throw newSQLError("sqlBatch expects an array");
}
batchList = [];
for (j = 0, len1 = sqlStatements.length; j < len1; j++) {
st = sqlStatements[j];
if (st.constructor === Array) {
if (st.length === 0) {
throw newSQLError("sqlBatch array element of zero (0) length");
}
batchList.push({
sql: st[0],
params: st.length === 0 ? [] : st[1]
});
} else {
batchList.push({
sql: st,
params: []
});
}
}
myfn = function(tx) {
var elem, k, len2, results;
results = [];
for (k = 0, len2 = batchList.length; k < len2; k++) {
elem = batchList[k];
results.push(tx.addStatement(elem.sql, elem.params, null, null));
}
return results;
};
let mysuccess = function() {
if (!!success) {
return success();
}
};
let myerror = function(e) {
if (!!error) {
return error(e);
} else {
plugin.log("Error handler not provided: ",e);
}
};
this.addTransaction(new SQLitePluginTransaction(this, myfn, myerror, mysuccess, true, false));
};
SQLitePlugin.prototype.open = function(success, error) {
var openerrorcb, opensuccesscb;
if (this.dbname in this.openDBs && this.openDBs[this.dbname] === DB_STATE_OPEN) {
plugin.log("database already open: " + this.dbname);
nextTick((function(_this) {
return function() {
success(_this);
};
})(this));
} else {
plugin.log("OPEN database: " + this.dbname);
opensuccesscb = (function(_this) {
return function() {
var txLock;
if (!_this.openDBs[_this.dbname]) {
plugin.log("database was closed during open operation");
}
if (_this.dbname in _this.openDBs) {
_this.openDBs[_this.dbname] = DB_STATE_OPEN;
}
if (!!success) {
success(_this);
}
txLock = txLocks[_this.dbname];
if (!!txLock && txLock.queue.length > 0 && !txLock.inProgress) {
_this.startNextTransaction();
}
};
})(this);
openerrorcb = (function(_this) {
return function() {
plugin.log("OPEN database: " + _this.dbname + " failed, aborting any pending transactions");
if (!!error) {
error(newSQLError("Could not open database"));
}
delete _this.openDBs[_this.dbname];
_this.abortAllPendingTransactions();
};
})(this);
this.openDBs[this.dbname] = DB_STATE_INIT;
plugin.exec("open",this.openargs,opensuccesscb, openerrorcb);
}
};
SQLitePlugin.prototype.close = function(success, error) {
if (this.dbname in this.openDBs) {
if (txLocks[this.dbname] && txLocks[this.dbname].inProgress) {
plugin.log("cannot close: transaction is in progress");
error(newSQLError("database cannot be closed while a transaction is in progress"));
return;
}
plugin.log("CLOSE database: " + this.dbname);
delete this.openDBs[this.dbname];
if (txLocks[this.dbname]) {
plugin.log("closing db with transaction queue length: " + txLocks[this.dbname].queue.length);
} else {
plugin.log("closing db with no transaction lock state");
}
let mysuccess = function(t, r) {
if (!!success) {
return success(r);
}
};
let myerror = function(t, e) {
if (!!error) {
return error(e);
} else {
plugin.log("Error handler not provided: ",e);
}
};
plugin.exec("close",{path: this.dbname}, mysuccess, myerror);
} else {
var err = "cannot close: database is not open";
plugin.log(err);
if (error) {
nextTick(function() {
return error(err);
});
}
}
};
SQLitePlugin.prototype.attach = function(dbNameToAttach, dbAlias, success, error) {
if (this.dbname in this.openDBs) {
if (txLocks[this.dbname] && txLocks[this.dbname].inProgress) {
plugin.log("cannot attach: transaction is in progress");
error(newSQLError("database cannot be attached while a transaction is in progress"));
return;
}
plugin.log("ATTACH database " + dbNameToAttach + " to " + this.dbname + " with alias " + dbAlias);
let mysuccess = function(t, r) {
if (!!success) {
return success(r);
}
};
let myerror = function(e) {
if (!!error) {
return error(e);
} else {
plugin.log("Error handler not provided: ",e);
}
};
plugin.exec("attach",{path: this.dbname, dbName: dbNameToAttach, dbAlias}, mysuccess, myerror);
} else {
let err = "cannot attach: database is not open";
if (error) {
nextTick(function() {
return error(err);
});
}
}
};
SQLitePlugin.prototype.detach = function(dbAlias, success, error) {
if (this.dbname in this.openDBs) {
if (txLocks[this.dbname] && txLocks[this.dbname].inProgress) {
plugin.log("cannot attach: transaction is in progress");
error(newSQLError("database cannot be attached while a transaction is in progress"));
return;
}
plugin.log("DETACH database " + dbAlias + " from " + this.dbname);
let mysuccess = function(t, r) {
if (!!success) {
return success(r);
}
};
let myerror = function(e) {
plugin.log("ERR", e);
if (!!error) {
return error(e);
} else {
plugin.log("Error handler not provided: ",e);
}
};
this.executeSql("DETACH DATABASE " + dbAlias, [], mysuccess, myerror)
} else {
var err = "cannot attach: database is not open";
plugin.log(err);
if (error) {
nextTick(function() {
return error(err);
});
}
}
};
SQLitePlugin.prototype.executeSql = function(statement, params, success, error) {
var myerror, myfn, mysuccess;
mysuccess = function(t, r) {
if (!!success) {
return success(r);
}
};
myerror = function(t, e) {
if (!!error) {
return error(e);
} else {
plugin.log("Error handler not provided: ",e);
}
};
myfn = function(tx) {
tx.addStatement(statement, params, mysuccess, myerror);
};
this.addTransaction(new SQLitePluginTransaction(this, myfn, null, null, false, false));
};
SQLitePluginTransaction = function(db, fn, error, success, txlock, readOnly) {
if (typeof fn !== "function") {
/*
This is consistent with the implementation in Chrome -- it
throws if you pass anything other than a function. This also
prevents us from stalling our txQueue if somebody passes a
false value for fn.
*/
let err = newSQLError("transaction expected a function");
if (!!error) {
return error(err);
} else {
throw err;
}
}
this.db = db;
this.fn = fn;
this.error = error;
this.success = success;
this.txlock = txlock;
this.readOnly = readOnly;
this.executes = [];
if (txlock) {
this.addStatement("BEGIN", [], null, function(tx, err) {
throw newSQLError("unable to begin transaction: " + err.message, err.code);
});
} else {
this.addStatement("SELECT 1", [], null, null);
}
};
SQLitePluginTransaction.prototype.start = function() {
var err;
try {
this.fn(this);
this.run();
} catch (_error) {
err = _error;
txLocks[this.db.dbname].inProgress = false;
this.db.startNextTransaction();
if (this.error) {
this.error(newSQLError(err));
}
}
};
SQLitePluginTransaction.prototype.executeSql = function(sql, values, success, error) {
var that = this;
if (that.finalized) {
throw {
message: "InvalidStateError: DOM Exception 11: This transaction is already finalized. Transactions are committed" +
" after its success or failure handlers are called. If you are using a Promise to handle callbacks, be aware that" +
" implementations following the A+ standard adhere to run-to-completion semantics and so Promise resolution occurs" +
" on a subsequent tick and therefore after the transaction commits.",
code: 11
};
}
if (that.readOnly && READ_ONLY_REGEX.test(sql)) {
that.handleStatementFailure(error, {
message: "invalid sql for a read-only transaction"
});
return;
}
let mysuccess = function(t, r) {
if (!!success) {
return success(t,r);
}
};
let myerror = function(t, e) {
if (!!error) {
return error(e);
} else {
plugin.log("Error handler not provided: ",e);
}
};
that.addStatement(sql, values, mysuccess, myerror);
};
SQLitePluginTransaction.prototype.addStatement = function(sql, values, success, error) {
var j, len1, params, sqlStatement, t, v;
sqlStatement = typeof sql === "string" ? sql : sql.toString();
params = [];
if (!!values && values.constructor === Array) {
for (j = 0, len1 = values.length; j < len1; j++) {
v = values[j];
t = typeof v;
if (v === null || v === void 0 || t === "number" || t === "string"){
params.push(v);
} else if (t === "boolean") {
//Convert true -> 1 / false -> 0
params.push(~~v);
}
else if (t !== "function") {
params.push(v.toString());
plugin.warn("addStatement - parameter of type <"+t+"> converted to string using toString()")
} else {
let errorMsg = "Unsupported parameter type <"+t+"> found in addStatement()";
plugin.error(errorMsg);
error(newSQLError(errorMsg));
return;
}
}
}
this.executes.push({
success: success,
error: error,
sql: sqlStatement,
params: params
});
};
SQLitePluginTransaction.prototype.handleStatementSuccess = function(handler, response) {
// plugin.log("handler response:",response,response.rows);
var payload, rows;
if (!handler) {
return;
}
rows = response.rows || [];
// plugin.log("handler rows now:",rows);
payload = {
rows: {
item: function(i) {
return rows[i];
},
/**
* non-standard Web SQL Database method to expose a copy of raw results
* @return {Array}
*/
raw: function() {
return rows.slice();
},
length: rows.length
},
rowsAffected: response.rowsAffected || 0,
insertId: response.insertId || void 0
};
// plugin.log("handler response payload:",payload);
handler(this, payload);
};
SQLitePluginTransaction.prototype.handleStatementFailure = function(handler, response) {
if (!handler) {
throw newSQLError("a statement with no error handler failed: " + response.message, response.code);
}
if (handler(this, response) !== false) {
throw newSQLError("a statement error callback did not return false: " + response.message, response.code);
}
};
SQLitePluginTransaction.prototype.run = function() {
var batchExecutes, handlerFor, i, callbacks, request, tropts, tx, txFailure, waiting;
txFailure = null;
tropts = [];
batchExecutes = this.executes;
waiting = batchExecutes.length;
this.executes = [];
tx = this;
handlerFor = function(index, didSucceed) {
return function(response) {
if (!txFailure) {
try {
if (didSucceed) {
tx.handleStatementSuccess(batchExecutes[index].success, response);
} else {
tx.handleStatementFailure(batchExecutes[index].error, newSQLError(response));
}
} catch (err) {
let errorMsg = JSON.stringify(err);
if(errorMsg === "{}") errorMsg = err.toString();
plugin.log("warning - exception while invoking a callback: " + errorMsg);
}
if (!didSucceed) {
txFailure = newSQLError(response);
}
}
if (--waiting === 0) {
if (txFailure) {
tx.executes = [];
tx.abort(txFailure);
} else if (tx.executes.length > 0) {
tx.run();
} else {
tx.finish();
}
}
};
};
i = 0;
callbacks = [];
while (i < batchExecutes.length) {
request = batchExecutes[i];
callbacks.push({
success: handlerFor(i, true),
error: handlerFor(i, false)
});
tropts.push({
qid: 1111,
sql: request.sql,
params: request.params
});
i++;
}
let mysuccess = function(result) {
var j, last, q, r, res, type;
if (result.length === 0){
return;
}
last = result.length - 1;
for (j = 0; j <= last; ++j) {
r = result[j];
type = r.type;
res = r.result;
q = callbacks[j];
if (q) {
if (q[type]) {
q[type](res);
}
}
}
};
var myerror = function(error) {
plugin.log("batch execution error: ",error);
};
plugin.exec("backgroundExecuteSqlBatch",{
dbargs: {
dbname: this.db.dbname
},
executes: tropts
}, mysuccess, myerror);
};
SQLitePluginTransaction.prototype.abort = function(txFailure) {
var failed, succeeded, tx;
if (this.finalized) {
return;
}
tx = this;
succeeded = function(tx) {
txLocks[tx.db.dbname].inProgress = false;
tx.db.startNextTransaction();
if (tx.error) {
tx.error(txFailure);
}
};
failed = function(tx, err) {
txLocks[tx.db.dbname].inProgress = false;
tx.db.startNextTransaction();
if (tx.error) {
tx.error(newSQLError("error while trying to roll back: " + err.message, err.code));
}
};
this.finalized = true;
if (this.txlock) {
this.addStatement("ROLLBACK", [], succeeded, failed);
this.run();
} else {
succeeded(tx);
}
};
SQLitePluginTransaction.prototype.finish = function() {
var failed, succeeded, tx;
if (this.finalized) {
return;
}
tx = this;
succeeded = function(tx) {
txLocks[tx.db.dbname].inProgress = false;
tx.db.startNextTransaction();
if (tx.success) {
tx.success();
}
};
failed = function(tx, err) {
txLocks[tx.db.dbname].inProgress = false;
tx.db.startNextTransaction();
if (tx.error) {
tx.error(newSQLError("error while trying to commit: " + err.message, err.code));
}
};
this.finalized = true;
if (this.txlock) {
this.addStatement("COMMIT", [], succeeded, failed);
this.run();
} else {
succeeded(tx);
}
};
SQLitePluginTransaction.prototype.abortFromQ = function(sqlerror) {
if (this.error) {
this.error(sqlerror);
}
};
dblocations = {
"default" : "nosync",
"Documents" : "docs",
"Library" : "libs",
"Shared" : "shared"
};
SQLiteFactory = function(){};
SQLiteFactory.prototype.DEBUG = function(debug) {
plugin.log("Setting debug to:",debug);
plugin.sqlitePlugin.DEBUG = debug;
};
SQLiteFactory.prototype.sqliteFeatures = function() {
return {
isSQLitePlugin: true
};
};
SQLiteFactory.prototype.openDatabase = argsArray(function(args) {
var errorcb, first, okcb, openargs, readOnly;
if (args.length < 1) {
return null;
}
first = args[0];
openargs = null;
okcb = errorcb = () => {};
if (first.constructor === String) {
openargs = {
name: first,
dblocation : dblocations["default"]
};
if (args.length >= 5) {
okcb = args[4];
if (args.length > 5) {
errorcb = args[5];
}
}
} else {
openargs = first;
readOnly = !!openargs.readOnly;
if (!readOnly && (!openargs.location || openargs.location.constructor !== String || !dblocations.hasOwnProperty(openargs.location))) {
openargs.dblocation = dblocations["default"];
} else {
openargs.dblocation = dblocations[openargs.location];
}
if (!!openargs.createFromLocation) {
if (openargs.createFromLocation === 1) {
openargs.assetFilename = "1";
} else if (typeof openargs.createFromLocation === "string"){
openargs.assetFilename = openargs.createFromLocation;
}
}
if (!!openargs.androidDatabaseImplementation && openargs.androidDatabaseImplementation === 2) {
openargs.androidOldDatabaseImplementation = 1;
}
if (!!openargs.androidLockWorkaround && openargs.androidLockWorkaround === 1) {
openargs.androidBugWorkaround = 1;
}
if (args.length >= 2) {
okcb = args[1];
if (args.length > 2) {
errorcb = args[2];
}
}
}
return new SQLitePlugin(openargs, okcb, errorcb);
});
SQLiteFactory.prototype.echoTest = function(success, error) {
let inputTestValue = "test-string";
let mysuccess = function(testValue) {
if (testValue === inputTestValue) {
return success();
} else {
return error(`Mismatch: got: ${testValue} , expected: ${inputTestValue}`);
}
};
let myerror = function(e) {
return error(e);
};
plugin.exec("echoStringValue",{value: inputTestValue}, mysuccess, myerror);
};
SQLiteFactory.prototype.deleteDatabase = function(first,success, error) {
var args = {};
if (first.constructor === String) {
args.path = first;
args.dblocation = dblocations["default"];
} else {
if (!(first && first["name"])) {
throw new Error("Please specify db name via name property");
}
args.path = first.name;
if (!first.location || first.location.constructor !== String || !dblocations.hasOwnProperty(first.location)) {
args.dblocation = dblocations["default"];
} else {
args.dblocation = dblocations[first.location];
}
}
let mysuccess = function(r) {
delete SQLitePlugin.prototype.openDBs[args.path];
if (!!success) {
return success(r);
}
};
let myerror = function(e) {
if (!!error) {
return error(e);
} else {
plugin.log("deleteDatabase error handler not provided: ",e);
}
};
plugin.exec("delete",args,mysuccess,myerror);
};
plugin.sqlitePlugin = {
SQLiteFactory : SQLiteFactory,
SQLitePluginTransaction : SQLitePluginTransaction,
SQLitePlugin : SQLitePlugin,
log: plugin.log
};
module.exports = plugin.sqlitePlugin;