'use strict';
var noop = require('noop-fn');
var Queue = require('tiny-queue');
var immediate = require('immediate');
var WebSQLResultSet = require('./WebSQLResultSet');
function errorUnhandled() {
return true; // a non-truthy return indicates error was handled
}
// WebSQL has some bizarre behavior regarding insertId/rowsAffected. To try
// to match the observed behavior of Chrome/Safari as much as possible, we
// sniff the SQL message to try to massage the returned insertId/rowsAffected.
// This helps us pass the tests, although it's error-prone and should
// probably be revised.
function massageSQLResult(sql, insertId, rowsAffected, rows) {
if (/^\s*UPDATE\b/i.test(sql)) {
// insertId is always undefined for "UPDATE" statements
insertId = void 0;
} else if (/^\s*CREATE\s+TABLE\b/i.test(sql)) {
// WebSQL always returns an insertId of 0 for "CREATE TABLE" statements
insertId = 0;
rowsAffected = 0;
} else if (/^\s*DROP\s+TABLE\b/i.test(sql)) {
// WebSQL always returns insertId=undefined and rowsAffected=0
// for "DROP TABLE" statements. Go figure.
insertId = void 0;
rowsAffected = 0;
} else if (!/^\s*INSERT\b/i.test(sql)) {
// for all non-inserts (deletes, etc.) insertId is always undefined
// ¯\_(ツ)_/¯
insertId = void 0;
}
return new WebSQLResultSet(insertId, rowsAffected, rows);
}
function SQLTask(sql, args, sqlCallback, sqlErrorCallback) {
this.sql = sql;
this.args = args;
this.sqlCallback = sqlCallback;
this.sqlErrorCallback = sqlErrorCallback;
}
function runBatch(self, batch) {
function onDone() {
self._running = false;
runAllSql(self);
}
var readOnly = self._websqlDatabase._currentTask.readOnly;
self._websqlDatabase._db.exec(batch, readOnly, function (err, results) {
/* istanbul ignore next */
if (err) {
self._error = err;
return onDone();
}
for (var i = 0; i < results.length; i++) {
var res = results[i];
var batchTask = batch[i];
if (res.error) {
if (batchTask.sqlErrorCallback(self, res.error)) {
// user didn't handle the error
self._error = res.error;
return onDone();
}
} else {
batchTask.sqlCallback(self, massageSQLResult(
batch[i].sql, res.insertId, res.rowsAffected, res.rows));
}
}
onDone();
});
}
function runAllSql(self) {
if (self._running || self._complete) {
return;
}
if (self._error || !self._sqlQueue.length) {
self._complete = true;
return self._websqlDatabase._onTransactionComplete(self._error);
}
self._running = true;
var batch = [];
var task;
while ((task = self._sqlQueue.shift())) {
batch.push(task);
}
runBatch(self, batch);
}
function executeSql(self, sql, args, sqlCallback, sqlErrorCallback) {
self._sqlQueue.push(new SQLTask(sql, args, sqlCallback, sqlErrorCallback));
if (self._runningTimeout) {
return;
}
self._runningTimeout = true;
immediate(function () {
self._runningTimeout = false;
runAllSql(self);
});
}
function WebSQLTransaction(websqlDatabase) {
this._websqlDatabase = websqlDatabase;
this._error = null;
this._complete = false;
this._runningTimeout = false;
this._sqlQueue = new Queue();
if (!websqlDatabase._currentTask.readOnly) {
// Since we serialize all access to the database, there is no need to
// run read-only tasks in a transaction. This is a perf boost.
this._sqlQueue.push(new SQLTask('BEGIN;', [], noop, noop));
}
}
WebSQLTransaction.prototype.executeSql = function (sql, args, sqlCallback, sqlErrorCallback) {
args = Array.isArray(args) ? args : [];
sqlCallback = typeof sqlCallback === 'function' ? sqlCallback : noop;
sqlErrorCallback = typeof sqlErrorCallback === 'function' ? sqlErrorCallback : errorUnhandled;
executeSql(this, sql, args, sqlCallback, sqlErrorCallback);
};
WebSQLTransaction.prototype._checkDone = function () {
runAllSql(this);
};
module.exports = WebSQLTransaction;