'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;