UNPKG

indurate

Version:

Indurate.js is an asynchronous sqlite3 wrapper for the openDatabase module.

361 lines (312 loc) 20.5 kB
/* Copyright 2013 Robert Edward Steckroth II <RobertSteckroth@gmail.com> Bust0ut, Surgemcgee * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ //.pragma library //^^^^^^^^^^^^^^ Un-comment this for qt/qml applications var IndurateDatabase = function(args, plugin) { args = args || {} if ( typeof openDatabase === 'function' ) { if ( openDatabase.isNode ) this.db = new openDatabase(args.name, args.version, args.description, args.size*1024*1024) else this.db = openDatabase(args.name, args.version, args.description, args.size*1024*1024) this.mode = "Asynchronous" } else if ( plugin && typeof plugin.openDatabaseSync === 'function' ) { // Plugin is the Ubuntu Tocuh LocalStorage plugin. /* ---- Ubuntu Touch usage example ---- Don't forget to uncomment .pragma library at beginning of this file if using Ubuntu Touch platform import QtQuick.LocalStorage 2.0 import "indurate_1.2.js" as Store property var opendatabase: new Store.IndurateDatabase({name: "StogckTickerMobile", version: "1.1", description: "The Stock Ticker core application database", size: 4}, LocalStorage) */ this.db = plugin.openDatabaseSync(args.name, args.version, args.description, args.size*1024*1024) this.mode = "Synchronous" } else if ( typeof openDatabaseSync === 'function' ) { this.db = openDatabaseSync(args.name, args.version, args.description, args.size*1024*1024) this.mode = "Synchronous" } else { console.log("___ERROR___ This platform does not support WebSql/openDatabase. Bummer man..") this.mode = 'Error' } this.use_migrations = false this.truncate = 90 // This will truncate all fields in the pretty out put tables ("describe"). It is annoying when the table is too long and automatically newlines. Then all of my text is shifted downwards and screws up my ability to read the text quickly.. for ( var arg in args ) this[arg] = args[arg] } if ( typeof module !== 'undefined' && typeof module.exports !== 'undefined' ) { module.exports = IndurateDatabase openDatabase = require('opendatabase') // This is a WebSql compliant Nodejs module openDatabase.isNode = true } var log = function(message) { console.log(message) } IndurateDatabase.prototype = { Table: function(master, callback, err) { this.indurate.db = master.db this.indurate.columns = ['primary_key'], this.indurate.columns = this.indurate.columns.concat(master.columns) this.indurate.name = master.name this.indurate.truncate = master.truncate this.indurate.use_migrations = master.use_migrations this.indurate.mode = master.mode if ( !master.from_master ) // only create the table if the from_master flag is not set. Otherwise this is a one time call from the IndurateDatabase parent this.$("_makeIndurateTable", callback, err) }, get $() { return function() { if ( arguments[0] !== 'describe' ) { // Init our table if not a request to show table describing if ( !arguments[1] || !arguments[1][0] ) { // If the user didn't to pass in column names via array if ( typeof arguments[2] === 'function' ) arguments[2].call(this, null, {message: 'Empty columns list in init table. Usage: db.$("my_new_table", ["column_2", "column_2"], func success() , func err())', code: 55}) else log('Empty columns list in init table. Usage: db.$("my_new_table", ["column_1", "column_2"], func success() , func err()) code: 55') return this // This will return to global if error. This is the only time! } return new this.Table({db: this.db, columns: arguments[1], name: arguments[0], use_migrations: this.use_migrations, truncate: this.truncate, mode: this.mode}, arguments[2], arguments[3]) } else { // We dop not need to forward callback and error functions if from_master == true into Table() // If arguments[1] and arguments[2] (callbacks) are undefined, our _set_callback function will automatically add the prototype chain and set it to a default logging function new this.Table({db: this.db, name: 'sqlite_master', from_master: true, truncate: this.truncate, mode: this.mode}).$("describe", arguments[1], arguments[2]) return this // Return IndurateDatabase object. This is not a table entry point. Use a non-database command instead } } }, } IndurateDatabase.prototype.Table.prototype = { // This is the primary getter object with namespace $. All commands will be called as a mock function using this get $() { return function() { this.indurate.plugin = arguments[0] || '' if ( this.indurate[arguments[0]] ) { // If the function exists in our framework this.indurate[arguments[0]].call(this, arguments[1]||null, arguments[2]||null, arguments[3]||null) } else { // Else set an error and message that the user requested a plugin which does not exist var err = this.indurate._set_callback(err) var avail = '' for ( var t in this ) if ( typeof this[t] === 'function' && t[0] !== '_') avail += ' $("'+t+'")' // Get a list of all SQL command plugins err(null, {message: arguments[0]+' is not an Induate plugin type.\n\tAvailible plugins: '+this.indurate.table_plugins, code: 55}) } return this } }, indurate: { _run_transaction: function(sub_obj, success, callback, err) { // This will construct our sub object (table) for every subsequent command call in the chain this.plugin_sql = success.command // Put the current sql command in proper call chain scope callback = this._set_callback.call(sub_obj, callback) // Set the success callback passed in to contain the sub object in the chain err = this._set_callback.call(sub_obj, err) // Set the error callback passed in to contain the sub object default logging messages var success_cb = success.exec.call(sub_obj, callback) // Call the transaction parsing routine wi the chain object this.db.transaction(function(tx) { if ( sub_obj.indurate.mode !== "Synchronous" ) // If the mode is not synchronous, use the callbacks in the execute call tx.executeSql(success.command, [], success_cb, err) else try { var results = tx.executeSql(success.command, []) // Otherwise if synchronous, manually call error callback with try/catch success_cb(tx, results) } catch(e) { err(tx, {message: e, code: 10}) // Error callback data can be set this way too } }) }, _set_callback: function(f) { var newThis = this return f ? function() { f.call(newThis, arguments[0]||null, arguments[1]||null, arguments[2]||null, arguments[3]||null) } : function(tx, data) { if ( arguments[1] && arguments[1].message ) { log('- $('+newThis.indurate.plugin+') - Transaction Failed: '+arguments[1].message+' Code: '+arguments[1].code) } else { if ( newThis.indurate.plugin === 'describe' ) log(arguments[0]) else log('$('+newThis.indurate.plugin+') - Transaction succeeded with: '+newThis.indurate.plugin_sql) } } }, /*---*/ _makeIndurateTable: function(callback, err) { var sql_cmd = 'CREATE TABLE IF NOT EXISTS "'+this.indurate.name+'" ("primary_key" TEXT UNIQUE' for ( var ind = 1; ind < this.indurate.columns.length; ind++ ) sql_cmd += ', "'+this.indurate.columns[ind]+'" TEXT' sql_cmd += ');' this.indurate._run_transaction(this, { command: sql_cmd, exec: function(callback) { var newThis = this // <-- Cheat the scope return function(tx, results) { var cb_len = callback&&callback.length||2 // Get the number of arguments passed into the callback and make some scope resistant internals callback(cb_len>0&&results) // Save memory if user does not request results object // It is ok to toss in paramaters here if no callbacks arguments (cb_len) were passed in (they will go out of scope quickly to the garbage can) } } }, callback, err) }, // These namespaces can be changed at run time on now via your editor. E.g cmd: function(command, callback, err) {, etc.. /*---*/ command: function(command, callback, err) { this.indurate._run_transaction(this, { command: command, exec: function(callback) { var newThis = this // <-- Cheat the scope return function(tx, results) { var cb_len = callback&&callback.length||2 // Get the number of arguments passed into the callback and make some scope resistant internals for ( var a = 0; a < results.rows.length; a++ ) { if ( !newThis[results.rows.item(a)['primary_key']] ) newThis[results.rows.item(a)['primary_key']] = {} // make sure it exists as an object for ( var o in results.rows.item(a) ) newThis[results.rows.item(a)['primary_key']][o] = results.rows.item(a)[o] // We need to loop thgrought the results sience results.rows.item(a) is an object and we want a native assignment } callback(cb_len>0&&results) // Save memory if user does not request results object // It is ok to toss in paramaters here if no callbacks arguments (cb_len) were passed in (they will go out of scope quickly to the garbage can) } } }, callback, err) }, /*---*/ get: function(primary_key, callback, err) { this.indurate._run_transaction(this, { command: 'SELECT * FROM "'+indurate.name+'" WHERE "primary_key"="'+primary_key+'";', exec: function(callback) { var newThis = this // <-- Cheat the scope return function(tx, results) { var cb_len = callback&&callback.length||2 // Get the number of arguments passed into the callback and make some scope resistant internals for ( var a = 0; a < results.rows.length; a++ ) { if ( !newThis[results.rows.item(0)['primary_key']] ) newThis[results.rows.item(0)['primary_key']] = {} // make sure it exists as an object for ( var o in results.rows.item(0) ) newThis[results.rows.item(0)['primary_key']][o] = results.rows.item(0)[o] // We need to loop thgrought the results sience results.rows.item(a) is an object and we want a native assignment } callback(cb_len>1&&results) // It is ok to toss in paramaters here if no callbacks arguments (cb_len) were passed in (they will go out of scope quickly to the garbage can) } } }, callback, err) }, /*---*/ getTable: function(callback, err) { this.indurate._run_transaction(this, { command: 'SELECT * FROM "'+this.indurate.name+'";', exec: function(callback) { var newThis = this // <-- Cheat the scope return function(tx, results) { var cb_len = callback&&callback.length||2 // Get the number of arguments passed into the callback and make some scope resistant internals for ( var a = 0; a < results.rows.length; a++ ) { if ( !newThis[results.rows.item(a)['primary_key']] ) newThis[results.rows.item(a)['primary_key']] = {} // make sure it exists as an object for ( var o in results.rows.item(a) ) newThis[results.rows.item(a)['primary_key']][o] = results.rows.item(a)[o] // We need to loop thgrought the results sience results.rows.item(a) is an object and we want a native assignment } callback(cb_len>1&&results) // It is ok to toss in paramaters here if no callbacks arguments (cb_len) were passed in (they will go out of scope quickly to the garbage can) } } }, callback, err) }, /*---*/ rename: function(name, callback, err) { this.indurate._run_transaction(this, { command: 'ALTER TABLE "'+this.indurate.name+'" RENAME TO "'+name+'";', exec: function(callback) { var newThis = this // <-- Cheat the scope return function(tx, results) { var cb_len = callback&&callback.length||2 newThis.name = name callback(cb_len>0&&results) } } }, callback, err) }, /*---*/ describe: function(callback, err) { this.indurate._run_transaction(this, { command: 'SELECT * FROM "'+this.indurate.name+'";', exec: function(callback) { var newThis = this // <-- Cheat the scope return function(tx, results) { var cb_len = callback&&callback.length||2 // Get the number of arguments passed into the callback and make some scope resistant internals callback(cb_len>0&&newThis.indurate._parseResult(results), cb_len>1&&results) // It is ok to toss in paramaters here if no callbacks arguments (cb_len) were passed in (they will go out of scope quickly to the garbage can) } } }, callback, err) }, /*---*/ set: function(callback, err) { // We are going to roll our own transaction here seince the set functionality is complex /*-------*/ var cb_len = callback&&callback.length||2, indurate = this.indurate, newThis = this // Get the number of arguments passed into the callback and make some scope resistant internals /*-------*/ callback = indurate._set_callback.call(this, callback) // Set the success callback passed in to contian this prototype and default logging messages /*-------*/ err = indurate._set_callback.call(this, err) // Set the error callback passed in to contian this prototype and default logging messages var t_params = indurate.columns, text_params = '" (' t_params.forEach(function(val, ind) { text_params += '"'+val+'", ' }) // Create "?" params from object size text_params = text_params.substr(0, text_params.length-2) + ') ' indurate.db.transaction(function (tx) { for ( var key in newThis ) { if ( key === '$' || key === 'indurate') continue var values = [] var values = ' VALUES("'+key+'"' for ( var x = 1; x < indurate.columns.length; x++ ) values += ', "'+(newThis[key][indurate.columns[x]]||' ')+'"' // An empty string will be used if the column data is not set for this row values += ');' indurate.plugin_sql = 'INSERT OR REPLACE INTO "'+indurate.name+text_params+values // Else look for matching fields and update them if ( indurate.mode !== "Synchronous" ) tx.executeSql(indurate.plugin_sql, [], function(){}, err) else try { tx.executeSql(indurate.plugin_sql, []) } catch(e) { err(tx, {message: e, code: 10}) } } }) callback() }, // I dont like this one /*---*/ returnData: function() { // get the database data object only var obj = {} for ( var key in this ) if ( key === '$' || key === 'indurate') continue // Screw those two! obj[key] = this[key] return obj }, /*---*/ _parseResult: function(results) { // Dont touch this function. It has a static super class with a dynamic namespace var newline = '\n', space = ' ' var info_text = '' if ( this.name === 'sqlite_master' ) info_text = '| Describe Database ['+this.name+'] - Version: '+this._stringify_variant(this.version)+' - Size(bytes) '+(this.size&&(this.size*1024*1024)||'N/A')+' - Description '+this._stringify_variant(this.description) else info_text = '| Describe table - ['+this.name+']' if ( !results.rows.length ) return info_text+'\n --------- Empty ---------' var headerText = {}, output = "", temp = '|', columnText = "|", scoreLine = '' for ( var row = 0; row < results.rows.length; row++) for ( var item in results.rows.item(row) ) if ( !headerText[item] || this._stringify_variant(results.rows.item(row)[item]).length > headerText[item].length ) { headerText[item] = this._stringify_variant(results.rows.item(row)[item].toString().replace(/./g, '_')) if ( headerText[item] && item.toString().length > headerText[item].length ) headerText[item] = item.toString().replace(/./g, '_') } for ( row = 0; row < results.rows.length; row++) { output += '|' for ( item in results.rows.item(row) ) { // <-- this is where we.. ehh, who are we kidding output += this._stringify_variant(results.rows.item(row)[item]) var leftOver = headerText[item].length - this._stringify_variant(results.rows.item(row)[item]).length for ( var sp = 0; sp < leftOver; sp++ ) if ( !!(sp%2) ) output += '.' else output += space output += '|' } output += newline } for ( var name in headerText ) { columnText += name for ( sp = 0; sp < headerText[name].length-name.length; sp++ ) columnText += space columnText += '|' temp += headerText[name]+"|" } scoreLine = temp.replace(/./g, '_') return (scoreLine+newline+info_text+newline+'| Rows '+results.rows.length+' - Columns '+Object.keys(results.rows.item(0)).length+newline+scoreLine+newline+columnText+newline+temp+newline+output+temp+newline) }, _stringify_variant: function(thing) { // meh.. return thing === null && "null" || typeof thing === 'undefined' && "undefined" || thing.toString().substr(0, this.truncate) } } }