API Docs for: 1.0b
Show:

File: src/drivers/WebSQL.js

var Bucket = Bucket || {};

!function (ns, utils) {
    /**
     * @module Driver.WebSQL
     */

    var driver;

    /**
     * @class Driver.WebSQL
     *
     * @extends Driver
     * @constructor
     */
    driver = Bucket.registerDriver('WebSQL', {
        name: 'WebSQL',

        /**
         * queries commands to the database
         *
         * @method query
         *
         * @param opts
         *  @param {Function} opts.onSuccess callback to execute when query is successful
         *  @param {Function} opts.onError   callback to execute when query initiated an error
         *  @param {String}   opts.sql       sql to execute
         *  @param {Array}    opts.sqlArgs   arguments to send with the query
         */
        query: function (opts) {
            var db = this.db,
                $this = this;

            this.addEvent('load', function(){
                var timeout = $this.initTimeout(opts.onError),
                    error  = opts.onError,
                    success = opts.onSuccess;

                opts.onSuccess = function(trans, res){
                    $this.clearTimeout(timeout);
                    success(trans,res);
                };

                opts.onError = function(e){
                    $this.clearTimeout(timeout);
                    error(e);
                };

                db.transaction(
                    function (trans) {
                        trans.executeSql(opts.sql, opts.sqlArgs, opts.onSuccess, opts.onError);
                    }
                );
            });
        },

        openDB: function (callback) {
            this.logger.log('openDB initiated');

            var $this = this,
                db_size = 50 * 1024 * 1024; //in bytes

            function onSuccess() {
                callback && callback(null);
            }

            function onError(e) {
                callback && callback($this.generateError(e));
            }

            try {
                // right now i disregard version change in WebSQL
                this.db = openDatabase(this.db_name, '', this.db_name, db_size);
                // open transaction and try to create a table.
                this.db.transaction(
                    function (trans) {
                        trans.executeSql(
                            'CREATE TABLE IF NOT EXISTS ' + $this.table_name + ' (key PRIMARY KEY, value)', 
                            [], 
                            onSuccess, 
                            onError
                        );
                    }
                );
            } catch (e) {
                callback && callback($this.generateError(e));
            }
        },

        init: function (options) {
            // Database properties
            this.db_name = 'Bucket';
            this.table_name = this.options.db_name + '_' + this.options.table_name;

            // Init instance's logger
            this.logger = ns.Logger.getLogger(this.name + " " + this.db_name + "_" + this.table_name, ns.Logger.logLevels.ERROR);
            this.logger.log('init');

            this.openDB(function (error) {
                if (error === null) {
                    this.state = driver.STATES.CONNECTED;
                    this.logger.log('openDB success fireEvent load:latched');
                    this.fireEvent('load:latched');
                } else {
                    this.state = driver.STATES.DISCONNECTED;
                    this.logger.log('openDB callback with error:', error);
                }
            }.bind(this));
        },

        clear: function (callback) {
            var $this = this;

            this.logger.log('clear');

            try {
                $this.query({
                    sql: 'DELETE FROM ' + this.table_name,
                    sqlArgs: [],
                    onSuccess: function (trans, res) {
                        callback && callback(null);
                    },
                    onError: function (e) {
                        callback && callback($this.generateError(e));
                    }
                });
            } catch (e) {
                callback && callback($this.generateError(e));
            }

            return this.$parent('clear', arguments);
        },

        each: function (callback) {
            this.logger.log('each');

            callback && this.fetchAllByRange(callback, {each: true});

            return this.$parent('each', arguments);
        },

        exists: function (key, callback) {
            this.logger.log('exists', key);

            var $this = this;

            this.fetchAllByRange(function (e, values) {
                if(!e){
                    callback && callback(null, values !== null && values.length > 0);
                } else {
                    callback && callback($this.generateError(e));
                }
            }, {
                keys_only: true,
                where: key
            });
            return this.$parent('exists', arguments);
        },

        fetchAllByRange: function (callback, options) {
            var opts = options || {},
                $this = this,
                columns = opts.keys_only ? 'key' : '*',
                sql,
                keys = [],
                sqlArgs;

            if (opts.count) {
                columns = 'COUNT (' + columns + ') AS count';
            }

            sql = 'SELECT ' + columns + ' FROM ' + this.table_name;

            if (opts.where) {
                sql += ' WHERE key = ?';

                if (typeof opts.where === 'string' || typeof opts.where === 'number') {
                    keys[0] = opts.where;
                } else {
                    keys = opts.where;
                }

                sqlArgs = keys;
            }

            if (opts.where_in) {
                sql += ' WHERE key IN ( ' + opts.where_in + ' )';
            }

            try {
                $this.query({
                    sql: sql,
                    sqlArgs: sqlArgs,
                    onSuccess: function (trans, res) {
                        var values = opts.keys_only ? [] : {},
                            item, i, value;

                        if (opts.count) {
                            callback(null, res.rows.item(0).count);
                            return;
                        }

                        if (res.rows.length === 0) {
                            callback(null, null);
                            return;
                        }

                        if (opts.singular) {
                            callback(null, JSON.parse(res.rows.item(0).value));
                            return;
                        }

                        for (i = 0; i < res.rows.length; i++) {
                            item = res.rows.item(i);

                            value = item.value && JSON.parse(item.value);

                            if (opts.each) {
                                callback(null, item.key, value);
                            }

                            if (opts.keys_only) {
                                values.push(item.key);
                            } else {
                                values[item.key] = value;
                            }
                        }

                        if (!opts.each) {
                            callback(null, values);
                        }
                    },
                    onError: function (e) {
                        callback($this.generateError(e));
                    }
                });
            } catch (e) {
                callback($this.generateError(e));
            }
        },

        get: function (key, callback) {
            this.logger.log('get', key);

            var keys = [],
                singular = false;

            if (typeof key === 'string' || typeof key === 'number') {
                keys[0] = key;
                singular = true;
            } else {
                keys = key;
            }

            keys = "'" + keys.join("', '") + "'";

            callback && this.fetchAllByRange(callback, {where_in: keys, singular: singular});

            return this.$parent('get', arguments);
        },

        getAll: function (callback) {
            this.logger.log('getAll');

            callback && this.fetchAllByRange(callback);

            return this.$parent('getAll', arguments);
        },

        getKeys: function (callback) {
            this.logger.log('getKeys');

            callback && this.fetchAllByRange(callback, {keys_only: true});

            return this.$parent('getKeys', arguments);
        },

        remove: function (key, callback) {
            var $this = this,
                keys = [];

            if (typeof key === 'string' || typeof key === 'number') {
                keys[0] = key;
            } else {
                keys = key;
            }

            keys = "'" + keys.join("', '") + "'";

            try {
                $this.query({
                    sql: 'DELETE FROM ' + this.table_name + ' WHERE key IN ( ' + keys + ' )',
                    sqlArgs: [],
                    onSuccess: function (trans, res) {
                        callback && callback(null);
                    },
                    onError: function (e) {
                        callback && callback($this.generateError(e));
                    }
                });
            } catch (e) {
                callback && callback($this.generateError(e));
            }

            return this.$parent('remove', arguments);
        },

        set: function (key, value, callback) {
            this.logger.log('set', key, value);

            var $this = this,
                map;

            function buildSQL(map) {
                var k,
                    first = true,
                    sql = '',
                    sqlArgs = [];

                for (k in map) {
                    if (map.hasOwnProperty(k)) {
                        if (first) {
                            sql = 'INSERT OR REPLACE INTO ' + $this.table_name + ' (key, value) ';
                            first = false;
                        } else {
                            sql += ' UNION ';
                        }
                        sql += ' SELECT ?, ?';
                        sqlArgs.push(k, JSON.stringify(map[k]));
                    }
                }

                return {sql: sql, sqlArgs: sqlArgs};
            }

            function runQuery(opts) {
                $this.query({
                    sql: opts.sql,
                    sqlArgs: opts.sqlArgs,
                    onSuccess: function () {
                        callback && callback(null);
                    },
                    onError: function (e) {
                        callback && callback($this.generateError(e));
                    }
                });
            }

            if (typeof key === 'string' || typeof key === 'number') {
                map = {};
                map[key] = value;
            } else {
                map = key;
            }

            try {
                runQuery(buildSQL(map));
            } catch (e) {
                callback && callback($this.generateError(e));
            }

            return this.$parent('set', arguments);
        },

        test: function () {
            return !!window.openDatabase;
        },

        getLength: function (callback) {
            callback && this.fetchAllByRange(callback, {count: true});

            return this.$parent('getLength', arguments);
        },

        generateError : function(e){
            if (e.name == 'Bucket Error'){
                return e;
            }

            return this.$parent('generateError',[null, e.message, e]);
        }
    });

    driver.STATES = {
        DISCONNECTED: 0,
        CONNECTING: 1,
        CONNECTED: 2
    };

}.apply(Bucket, [Bucket, Bucket.utils]);