indurate
Version:
Indurate.js is an asynchronous sqlite3 wrapper for the openDatabase module.
361 lines (312 loc) • 20.5 kB
JavaScript
/* 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)
}
}
}