summaryrefslogtreecommitdiffstats
path: root/javascripts/libs/browser_engines
diff options
context:
space:
mode:
Diffstat (limited to 'javascripts/libs/browser_engines')
-rw-r--r--javascripts/libs/browser_engines/engines.js6
-rw-r--r--javascripts/libs/browser_engines/sqlite_driver.js71
-rw-r--r--javascripts/libs/browser_engines/sqljs_driver.js160
-rw-r--r--javascripts/libs/browser_engines/websql_driver.js268
4 files changed, 505 insertions, 0 deletions
diff --git a/javascripts/libs/browser_engines/engines.js b/javascripts/libs/browser_engines/engines.js
new file mode 100644
index 0000000..ce0dff6
--- /dev/null
+++ b/javascripts/libs/browser_engines/engines.js
@@ -0,0 +1,6 @@
+define(['sqljs_driver', 'websql_driver'], function (SQLjs, WebSQL) {
+ return {
+ sqljs: SQLjs,
+ websql: WebSQL
+ };
+});
diff --git a/javascripts/libs/browser_engines/sqlite_driver.js b/javascripts/libs/browser_engines/sqlite_driver.js
new file mode 100644
index 0000000..1aa6586
--- /dev/null
+++ b/javascripts/libs/browser_engines/sqlite_driver.js
@@ -0,0 +1,71 @@
+define(function () {
+ var SQLite_driver = function () {
+ return this;
+ }
+
+ SQLite_driver.prototype.getSchemaStructure = function (args) {
+ /*
+ We are going to get the schema structure by running a special query to get back the table
+ definitions. We'll use that query to find the column names and types by parsing out the columns
+ from the create table statements.
+ */
+ // this query gets back a result set which contains each table, along with the create table statement used
+ var schema_sql = "select * from sqlite_master where type IN ('table', 'view') and name != '__WebKitDatabaseInfoTable__' order by type,name";
+
+ var localCallback = function (resultSets) {
+ var colMap = {};
+ var schemaStruct = [];
+
+ for (var i in resultSets[0]["RESULTS"]["COLUMNS"])
+ {
+ colMap[resultSets[0]["RESULTS"]["COLUMNS"][i]] = i;
+ }
+
+ for (var j in resultSets[0]["RESULTS"]["DATA"])
+ {
+ var tableDef = resultSets[0]["RESULTS"]["DATA"][j][colMap["sql"]];
+ var tableName = resultSets[0]["RESULTS"]["DATA"][j][colMap["name"]];
+ var tableType = resultSets[0]["RESULTS"]["DATA"][j][colMap["type"]];
+
+ var tableStruct = {
+ "table_name": tableName,
+ "table_type": tableType,
+ "columns": []
+ };
+
+ if (tableType == "table") { // sadly, there is no easy way to get the columns back from views in WebSQL
+ var colsDef = /^[\s\S]*?\(([\s\S]*)\)$/.exec(tableDef)[1].split(/,\s*/);
+
+
+ for (var k in colsDef) {
+ var col_components = colsDef[k].replace(/(^\s*)|(\s*$)/, '').split(/\s+/);
+ tableStruct["columns"].push({
+ "name": col_components.shift(), // first part of the col def
+ "type": col_components.join(' ') // rest of the col def
+ })
+ }
+ }
+ schemaStruct.push(tableStruct);
+ }
+
+ args["callback"](schemaStruct);
+
+ }
+
+
+ // use the method available in child classes to get the results from our special query
+ this.executeQuery({sql: schema_sql, "success": localCallback});
+
+ }
+
+ SQLite_driver.prototype.splitStatement = function (statements, separator)
+ {
+ if (! separator) separator = ";";
+ var escaped_separator = separator.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
+
+ var sArray = (statements ? statements.split(new RegExp(escaped_separator + "\s*\r?(\n|$)")) : []);
+ return sArray;
+ }
+
+ return SQLite_driver;
+}); \ No newline at end of file
diff --git a/javascripts/libs/browser_engines/sqljs_driver.js b/javascripts/libs/browser_engines/sqljs_driver.js
new file mode 100644
index 0000000..89831da
--- /dev/null
+++ b/javascripts/libs/browser_engines/sqljs_driver.js
@@ -0,0 +1,160 @@
+define(["jQuery", "sqlite_driver"], function ($, SQLite_driver) {
+
+ var SQLjs_driver = function () {
+ this.db = null;
+ return this;
+ }
+
+ $.extend(SQLjs_driver.prototype,SQLite_driver.prototype); // inherit from parent class
+
+
+ SQLjs_driver.prototype.buildSchema = function (args) {
+
+ var _this = this; // preserve reference to current object through local closures
+
+ try {
+
+ /*
+ * Closure used to handle both cases of when the sql.js library
+ * has already been loaded, or when it has not yet been.
+ */
+ var jsBuildSchema = function () {
+
+ _this.db = SQL.open();
+ $.each(SQLite_driver.prototype.splitStatement.call(this,args["ddl"],args["statement_separator"]), function (i, statement) {
+ _this.db.exec(statement);
+ });
+
+ args["success"]();
+ }
+
+ // If the sql.js code isn't yet loaded, do it now.
+ if (window.SQL === undefined)
+ {
+ $.getScript("javascripts_static/sql.js", function (script, textStatus, jqXHR) {
+ jsBuildSchema();
+ }).fail(function(jqxhr, settings, exception){
+ args["error"]("Your browser does not work with SQL.js. Try using a different browser (Chrome, Safari, Firefox, IE 10, etc...), or a newer version of your current one.");
+ });
+ }
+ else
+ {
+ if (_this.db)
+ {
+ _this.db.close();
+ }
+
+ jsBuildSchema();
+ }
+
+ }
+ catch (e)
+ {
+ args["error"](e);
+ }
+
+ }
+
+ SQLjs_driver.prototype.executeQuery = function (args) {
+
+ var _this = this; // preserve reference to current object through local closures
+
+ try {
+ if (! _this.db)
+ {
+ throw ("Database Schema not available!");
+ }
+
+ var returnSets = [];
+
+ _this.db.exec("BEGIN TRANSACTION");
+
+ $.each(SQLite_driver.prototype.splitStatement.call(this,args["sql"],args["statement_separator"]), function (i, statement) {
+ if ($.trim(statement).length) {
+ var startTime = new Date();
+
+ var setArray = [];
+
+ try {
+ setArray = _this.db.exec(statement);
+
+ var thisSet = {
+ "SUCCEEDED": true,
+ "EXECUTIONTIME": (new Date()) - startTime,
+ "RESULTS": {
+ "COLUMNS": [],
+ "DATA": []
+ },
+ "EXECUTIONPLAN": {
+ "COLUMNS": [],
+ "DATA": []
+ }
+
+ };
+
+ if (setArray.length) {
+ $.each(setArray, function(rowNumber, row){
+ var rowVals = [];
+ $.each(row, function(columnNumber, col){
+ if (rowNumber == 0) {
+ thisSet["RESULTS"]["COLUMNS"].push(col.column);
+ }
+ rowVals.push(col.value);
+ });
+ thisSet["RESULTS"]["DATA"].push(rowVals);
+ });
+ }
+
+ try {
+
+ exectionPlanArray = _this.db.exec("EXPLAIN QUERY PLAN " + statement);
+
+ if (exectionPlanArray.length) {
+ $.each(exectionPlanArray, function(rowNumber, row){
+ var rowVals = [];
+ $.each(row, function(columnNumber, col){
+ if (rowNumber == 0) {
+ thisSet["EXECUTIONPLAN"]["COLUMNS"].push(col.column);
+ }
+ rowVals.push(col.value);
+ });
+ thisSet["EXECUTIONPLAN"]["DATA"].push(rowVals);
+ });
+ }
+
+ }
+ catch (e) {
+ // if we get an error with the execution plan, just ignore and move on.
+ }
+
+ returnSets.push(thisSet);
+
+
+ }
+ catch (e) {
+ var thisSet = {
+ "SUCCEEDED": false,
+ "EXECUTIONTIME": (new Date()) - startTime,
+ "ERRORMESSAGE": e
+ };
+ returnSets.push(thisSet);
+ return false; // breaks the each loop
+ }
+
+ }
+ });
+
+ _this.db.exec("ROLLBACK TRANSACTION");
+
+ args["success"](returnSets);
+
+ }
+ catch (e)
+ {
+ args["error"](e);
+ }
+
+ }
+
+ return SQLjs_driver;
+});
diff --git a/javascripts/libs/browser_engines/websql_driver.js b/javascripts/libs/browser_engines/websql_driver.js
new file mode 100644
index 0000000..2795b6c
--- /dev/null
+++ b/javascripts/libs/browser_engines/websql_driver.js
@@ -0,0 +1,268 @@
+window.WebSQL_driver = function(){
+ this.db = null;
+ this.ddl = [];
+ this.nativeSQLite = (window.openDatabase !== undefined);
+ return this;
+};
+
+$.extend(window.WebSQL_driver.prototype,window.SQLite_driver.prototype); // inherit from parent class
+
+window.WebSQL_driver.prototype.buildSchema = function (args) {
+
+ var _this = this; // preserve reference to current object through local closures
+
+ try {
+
+ if (_this.nativeSQLite)
+ {
+ _this.db = openDatabase(args["short_code"], '1.0', args["short_code"], args["ddl"].length * 1024);
+
+ _this.db.transaction(function(tx){
+
+ var statements = _this.splitStatement(args["ddl"],args["statement_separator"]);
+ _this.ddl = statements;
+
+ var currentStatement = 0;
+ var statement = statements[currentStatement];
+
+ var sequentiallyExecute = function(tx2, result){
+ if (currentStatement < statements.length-1)
+ {
+ do {
+ currentStatement++;
+ statement = statements[currentStatement];
+ } while (currentStatement < statements.length-1 && statement.match(/^\s*$/));
+
+ if (!statement.match(/^\s*$/)) {
+ tx.executeSql(statement, [], sequentiallyExecute, handleFailure);
+ }
+ else
+ {
+ tx.executeSql("intentional failure used to rollback transaction");
+ args["success"]();
+ }
+
+ }
+ else
+ {
+ tx.executeSql("intentional failure used to rollback transaction");
+ args["success"]();
+ }
+ };
+
+ var handleFailure = function (tx2, result) {
+ if (result.message != "not an error") // thank you safari, for this
+ {
+ args["error"](result.message);
+ }
+ else
+ {
+ args["success"]();
+ }
+
+ return true; // roll back transaction
+ };
+
+ if (statement) {
+ tx.executeSql(statement, [], sequentiallyExecute, handleFailure);
+ }
+ else {
+ args["success"]();
+ }
+ });
+
+ }
+ else
+ {
+ args["error"]("SQLite (WebSQL) not available in your browser. Try either using a webkit-based browser (such as Safari or Chrome) or using the SQLite (SQL.js) database type.")
+ }
+
+ }
+ catch (e)
+ {
+ args["error"](e);
+ }
+
+ }
+
+
+window.WebSQL_driver.prototype.executeQuery = function (args) {
+
+ var _this = this; // preserve reference to current object through local closures
+
+ try {
+
+ if (_this.db == null ) {
+ throw("You need to build the schema before you can run a query.");
+ }
+
+ var returnSets = [];
+
+ _this.db.transaction(function(tx){
+
+ var sequentiallyExecute = function(tx2, result) {
+
+ var thisSet = {
+ "SUCCEEDED": true,
+ "EXECUTIONTIME": (new Date()) - startTime,
+ "RESULTS": {
+ "COLUMNS": [],
+ "DATA": []
+ },
+ "EXECUTIONPLAN": {
+ "COLUMNS": [],
+ "DATA": []
+ }
+
+ };
+
+ for (var i = 0; i < result.rows.length; i++) {
+ var rowVals = [];
+ var item = result.rows.item(i);
+
+ /* We can't be sure about the order of the columns returned, since they are returned as
+ * a simple unordered structure. So, we'll just take the order returned the from the first
+ * request, then just use that order for each row.
+ */
+ if (i == 0) {
+ for (col in item) {
+ thisSet["RESULTS"]["COLUMNS"].push(col);
+ }
+ }
+
+ for (var j = 0; j < thisSet["RESULTS"]["COLUMNS"].length; j++) {
+ rowVals.push(item[thisSet["RESULTS"]["COLUMNS"][j]]);
+ }
+
+ thisSet["RESULTS"]["DATA"].push(rowVals);
+ }
+
+ tx.executeSql("EXPLAIN QUERY PLAN " + statement, [], function (tx3, executionPlanResult) {
+
+ for (var l = 0; l < executionPlanResult.rows.length; l++) {
+ var rowVals = [];
+ var item = executionPlanResult.rows.item(l);
+
+ /* We can't be sure about the order of the columns returned, since they are returned as
+ * a simple unordered structure. So, we'll just take the order returned the from the first
+ * request, then just use that order for each row.
+ */
+ if (l == 0) {
+ for (col in item) {
+ thisSet["EXECUTIONPLAN"]["COLUMNS"].push(col);
+ }
+ }
+
+ for (var j = 0; j < thisSet["EXECUTIONPLAN"]["COLUMNS"].length; j++) {
+ rowVals.push(item[thisSet["EXECUTIONPLAN"]["COLUMNS"][j]]);
+ }
+
+ thisSet["EXECUTIONPLAN"]["DATA"].push(rowVals);
+ }
+
+ if (currentStatement > _this.ddl.length-1)
+ returnSets.push(thisSet);
+
+ // executeSQL runs asynchronously, so we have to make recursive calls to handle subsequent queries in order.
+ if (currentStatement < (statements.length - 1))
+ {
+ do {
+ currentStatement++;
+ statement = statements[currentStatement];
+ } while (currentStatement < statements.length-1 && statement.match(/^\s*$/));
+
+ if (! statement.match(/^\s*$/))
+ tx.executeSql(statement, [], sequentiallyExecute, handleFailure);
+ else
+ {
+ tx.executeSql("intentional failure used to rollback transaction");
+ args["success"](returnSets);
+ }
+
+ }
+ else
+ {
+ tx.executeSql("intentional failure used to rollback transaction");
+ args["success"](returnSets);
+ }
+
+
+ },
+ function(tx3, executionPlanResult){
+ // if the explain failed, then just append the base set to the result and move on....
+
+ if (currentStatement > _this.ddl.length-1)
+ returnSets.push(thisSet);
+
+ // executeSQL runs asynchronously, so we have to make recursive calls to handle subsequent queries in order.
+ if (currentStatement < (statements.length - 1))
+ {
+ do {
+ currentStatement++;
+ statement = statements[currentStatement];
+ } while (currentStatement < statements.length-1 && statement.match(/^\s*$/));
+
+ if (! statement.match(/^\s*$/))
+ tx.executeSql(statement, [], sequentiallyExecute, handleFailure);
+ else
+ {
+ tx.executeSql("intentional failure used to rollback transaction");
+ args["success"](returnSets);
+ }
+ }
+ else
+ {
+ tx.executeSql("intentional failure used to rollback transaction");
+ args["success"](returnSets);
+ }
+
+
+ });
+
+ }
+
+ var handleFailure = function (tx, result) {
+ if (result.message != "not an error") // thank you safari, for this
+ {
+ var thisSet = {
+ "SUCCEEDED": false,
+ "EXECUTIONTIME": (new Date()) - startTime,
+ "ERRORMESSAGE": result.message
+ };
+ returnSets.push(thisSet);
+ }
+
+ args["success"](returnSets); // 'success' - slightly confusing here, but in this context a failed query is still a valid result from the database
+ return true; // roll back transaction
+ }
+
+ var setArray = [], k, stop = false;
+
+ var statements = _this.ddl.slice(0);
+
+ $.each(_this.splitStatement(args["sql"],args["statement_separator"]), function (i, stmt) { statements.push(stmt); });
+
+ var currentStatement = 0;
+ var statement = statements[currentStatement];
+
+ var startTime = new Date();
+
+ /*
+ * executeSql runs asynchronously, so I impose a semblance of synchronous-ness via recusive calls
+ */
+ tx.executeSql(statement, [], sequentiallyExecute, handleFailure);
+
+
+
+ });
+
+ }
+ catch (e)
+ {
+ args["error"](e);
+ }
+
+
+ }
+
+