Welcome Guest, Not a member yet? Register   Sign In
Simple JavaScript database
#1

[eluser]Cyclops[/eluser]
Hi, everyone.

I just created a very simple JavaScript database which I will be releasing under the GPL pending your review. I just wanted to show it to someone before releasing it to the public at large.

It is a very simple object, and should be fairly simple to use.

Let me know if you see any way to optimize or otherwise make it better.

There are 2 functions preceding the obect: clone() and print_r(). print_r() is only used in the examples. clone() is actually used by the database.

and then there are a plethora of examples of usage following the object. tweak and play around with those to your hearts content.

Basically, the object supports 4 functions (methods): _insert, _select, _update, _delete.

Each of the functions' only argument should be an object (i.e. an associative array) whose members are arrays (or strings, depending on the case). In the case of _insert, the object should only have one array: 'records' (which is an array of objects itself) which are taken and inserted.

_update, _select, and _delete all take a "criteria" object which can have the following members:
'records' ,
'records_ne' , (not equal)
'records_lt' , (less than)
'records_gt' , (greater than)
'limit' ,
'limit_start' ,
'order_by' , (field name)
'order_dir' , (asc or desc)

The 'records/_ne|_lt|_gt' are all an array of objects, which objects should follow the format
{'name':'database field name', 'value':'value to search for'}. These objects are like the SQL WHERE clause. There is currently no functionality for OR or LIKE or pattern matching.

'records' in this context can actually be a string with the value of "*", saying to search for all records. If present, all other 'record_' members are ignored.

'limit' and 'limit_start' should be integers, and correspond to the SQL LIMIT x,x statement (LIMIT limit_start, limit).

'order_by' and 'order_dir' correspond to the SQL ORDER statement (ORDER BY order_by order_dir).



At any rate, take a look at it, and let me know what you think, before I release it.

-----------------------
Michael
-----------------------


Code:
/**
* clone
*
* @param obj $obj
* @access public
* @return void
*/
function clone(obj){
    if(obj == null || typeof(obj) != 'object')
        return obj;

    if ( obj.reverse ){ // array
        var temp = new Array();
        for(var key in obj)
            temp[key] = clone(obj[key]);
        return temp;
    }

    var temp = {}; // object
    for(var key in obj)
        temp[key] = clone(obj[key]);

    return temp;
}

/**
* print_r
*
* prints an array or object into readable format.
*
* I actually took most of this function from:
* http://www.phpbuilder.com/board/archive/index.php/t-10294264.html
*
* and modified it a little to fit my liking ( such as fiddling
* with the _indent and adding a 'function' case statement ).
*
* @param input $input
* @param _indent $_indent
* @access public
* @return void
*/
function print_r(input, _indent)
{
    if(typeof(_indent) == 'string') {
        var indent = _indent + _indent;
        var paren_indent = _indent;
    } else {
        var indent = '    ';
        var paren_indent = ' ';
    }
    switch(typeof(input)) {
        case 'boolean':
            var output = (input ? 'true' : 'false') + "\n";
            break;
        case 'object':
            if ( input === null ) {
                var output = "null\n";
                break;
            }
            var output = ((input.reverse) ? 'Array' : 'Object') + " (\n";
            for(var i in input) {
                output += indent + "[" + i + "] => " + print_r(input[i], indent);
            }
            output += paren_indent + ")\n";
            break;
        case 'string':
            var output = '"' + input + "\"\n";
            break;
        case 'function':
            if ( input.toString().indexOf('{') != -1 ){
                var output = input.toString().substr(0, input.toString().indexOf('{') + 1) + "}\n";
            }
            else {
                var output = input + "\n";
            }
            break;
        case 'number':
        default:
            var output = "" + input + "\n";
    }
    return output;
}
#2

[eluser]Cyclops[/eluser]
Code:
/**
*  js_database
*
*  @author Michael Bahnmiller
*  @author Ezekiel Messenger
*/
var js_database = function() {
    this.records = new Array();
    this.temprecords = new Array();

    this.sortfield = '';
    var me = this;

    /**
     *  _select
     *
     *  @param mixed criteria
     */
    this._select = function(criteria)
    {
        var limit = criteria.limit;
        var limit_start = (criteria.limit_start && typeof(criteria.limit_start) == 'number') ? criteria.limit_start : 0;

        if ( typeof(criteria.records) == 'string' ){
            if ( criteria.records != '*' )
                return false;

            if ( limit == 0 ){
                return new Array();
            }

            this.__doSort(criteria);

            if ( limit > 0 && limit < this.temprecords.length ) {
                if ( limit_start > 0 )
                    this.temprecords.splice(0, limit_start);
                this.temprecords.splice(limit, this.temprecords.length - limit);
                return {"records" : this.temprecords, "count" : this.temprecords.length};
            }

            return {"records" : this.temprecords, "count" : this.temprecords.length};
        }

        var c = 0;
        var d = 0;
        var num_matches = 0;
        var ret_data_Obj = {"records" : [], "count" : ""};

        var len = 0;
        if ( criteria.records)    len += parseInt(criteria.records.length);
        if ( criteria.records_gt) len += parseInt(criteria.records_gt.length);
        if ( criteria.records_lt) len += parseInt(criteria.records_lt.length);
        if ( criteria.records_ne) len += parseInt(criteria.records_ne.length);

        this.__doSort(criteria);

        for (i in this.temprecords){
            c = 0;
            for(j in criteria){
                if ( j == 'records' || j == 'records_lt' || j == 'records_gt' || j == 'records_ne' ) {
                    for(k in criteria[j]) {
                        if ( j == 'records' ){
                            if ( this.temprecords[i][criteria.records[k].name] == criteria.records[k].value )
                                c++;
                            else break;
                        } else if ( j == 'records_lt' ){
                            if ( this.temprecords[i][criteria.records_lt[k].name] < criteria.records_lt[k].value )
                                c++;
                            else break;
                        } else if ( j == 'records_gt' ){
                            if ( this.temprecords[i][criteria.records_gt[k].name] > criteria.records_gt[k].value )
                                c++;
                            else break;
                        } else if ( j == 'records_ne' ){
                            if ( this.temprecords[i][criteria.records_ne[k].name] != criteria.records_ne[k].value )
                                c++;
                            else break;
                        }

                        if ( c == len ){
                            num_matches++;
                            if ( num_matches > limit_start ) {
                                d++;
                                ret_data_Obj.records.push(this.temprecords[i]);
                            }

                            if ( d == parseInt(limit) ){
                                ret_data_Obj.count = num_matches;
                                return ret_data_Obj;
                            }
                        }
                    }
                }
            }
        }

        ret_data_Obj.count = num_matches;
        return ret_data_Obj;
    };

    /**
     *  _insert
     *
     *  @param array rows
     */
    this._insert = function(rows)
    {
        for(i in rows.records){
            this.records[this.records.length] = rows.records[i];
        }
    };
#3

[eluser]Cyclops[/eluser]
Code:
/**
     *  _delete
     *
     *  @param array criteria
     */
    this._delete = function(criteria)
    {
        var limit = criteria.limit;
        var limit_start = (criteria.limit_start && typeof(criteria.limit_start) == 'number') ? criteria.limit_start : 0;

        var c = 0;
        var d = 0;
        var num_matches = 0;
        var ret_data_Obj = {"records" : [], "count" : ""};
        var ids_to_del = new Array();

        var len = 0;
        if ( criteria.records)    len += parseInt(criteria.records.length);
        if ( criteria.records_gt) len += parseInt(criteria.records_gt.length);
        if ( criteria.records_lt) len += parseInt(criteria.records_lt.length);
        if ( criteria.records_ne) len += parseInt(criteria.records_ne.length);

        this.__doSort(criteria, true);

        for (i in this.records){
            c = 0;
            for(j in criteria){
                if ( j == 'records' || j == 'records_lt' || j == 'records_gt' || j == 'records_ne' ) {
                    for(k in criteria[j]) {
                        if ( j == 'records' ){
                            if ( this.records[i][criteria.records[k].name] == criteria.records[k].value )
                                c++;
                            else break;
                        } else if ( j == 'records_lt' ){
                            if ( this.records[i][criteria.records_lt[k].name] < criteria.records_lt[k].value )
                                c++;
                            else break;
                        } else if ( j == 'records_gt' ){
                            if ( this.records[i][criteria.records_gt[k].name] > criteria.records_gt[k].value )
                                c++;
                            else break;
                        } else if ( j == 'records_ne' ){
                            if ( this.records[i][criteria.records_ne[k].name] != criteria.records_ne[k].value )
                                c++;
                            else break;
                        }

                        if ( c == len ){
                            num_matches++;
                            if ( num_matches > limit_start ) {
                                d++;
                                ret_data_Obj.records.push(this.records[i]);
                                ids_to_del[ids_to_del.length] = i;
                            }
                        }

                        if ( d == parseInt(limit) ) break;
                    }
                }
            }

            if ( d == parseInt(limit) ) break;
        }

        ids_to_del.reverse();
        for(k in ids_to_del) {
            this.records.splice(ids_to_del[k], 1);
        }

        ret_data_Obj.count = num_matches;
        return ret_data_Obj;
    };
#4

[eluser]Cyclops[/eluser]
Code:
/**
     *  _update
     *
     *  @param array criteria
     */
    this._update = function(criteria)
    {
        var limit = criteria.limit;
        var limit_start = (criteria.limit_start && typeof(criteria.limit_start) == 'number') ? criteria.limit_start : 0;

        this.__doSort(criteria, true);

        var c = 0;
        var num_matches = 0;
        var ret_data_Obj = {"records" : [], "count" : ""};

        if ( typeof(criteria.records) == 'string' ){
            if ( criteria.records != '*' )
                return false;

            for (i in this.records){
                if ( c >= limit_start ) {
                    for (j in criteria.change){

                        this.records[i][criteria.change[j].name] = criteria.change[j].value;
                        ret_data_Obj.records.push(this.records[i]);
                    }
                    num_matches++;
                }

                c++;
                if ( c == parseInt(limit + limit_start) ){
                    ret_data_Obj.count = num_matches;
                    return ret_data_Obj;
                }
            }

            ret_data_Obj.count = num_matches;
            return ret_data_Obj;
        }

        var d = 0;
        var len = 0;

        if ( criteria.records)    len += parseInt(criteria.records.length);
        if ( criteria.records_gt) len += parseInt(criteria.records_gt.length);
        if ( criteria.records_lt) len += parseInt(criteria.records_lt.length);
        if ( criteria.records_ne) len += parseInt(criteria.records_ne.length);


        for (i in this.records){
            c = 0;
            for(j in criteria){
                if ( j == 'records' || j == 'records_lt' || j == 'records_gt' || j == 'records_ne' ) {
                    for(k in criteria[j]) {
                        if ( j == 'records' ){
                            if ( this.records[i][criteria.records[k].name] == criteria.records[k].value )
                                c++;
                            else break;
                        } else if ( j == 'records_lt' ){
                            if ( this.records[i][criteria.records_lt[k].name] < criteria.records_lt[k].value )
                                c++;
                            else break;
                        } else if ( j == 'records_gt' ){
                            if ( this.records[i][criteria.records_gt[k].name] > criteria.records_gt[k].value )
                                c++;
                            else break;
                        } else if ( j == 'records_ne' ){
                            if ( this.records[i][criteria.records_ne[k].name] != criteria.records_ne[k].value )
                                c++;
                            else break;
                        }

                        if ( c == len ){
                            num_matches++;
                            if ( num_matches > limit_start ) {
                                d++;

                                for (k in criteria.change){
                                    this.records[i][criteria.change[k].name] = criteria.change[k].value;
                                }

                                ret_data_Obj.records.push(this.records[i]);
                            }

                            if ( d == parseInt(limit) ){
                                ret_data_Obj.count = num_matches;
                                return ret_data_Obj;
                            }
                        }
                    }
                }
            }
        }

        ret_data_Obj.count = num_matches;
        return ret_data_Obj;
    };

    /**
     *  __sort
     *
     * @access private
     * @param a
     * @param b
     */
    this.__sort = function(a, b)
    {
        if ( ! (a[me.sortfield] && b[me.sortfield]) )
            return 0;

        if ( typeof(a[me.sortfield]) == 'number' && typeof(b[me.sortfield]) == 'number' ){
            return a[me.sortfield] - b[me.sortfield];
        } else {
            var x = a[me.sortfield].toLowerCase();
            var y = b[me.sortfield].toLowerCase();
            return ((x<y)?-1:((x>y)?1:0));
        }
    }

    /**
     *  __doSort
     *
     * @access private
     * @param criteria
     */
    this.__doSort = function(criteria, sort_main)
    {
        if ( sort_main === true ){

             var var_to_sort = 'records';
        } else {

            var var_to_sort = 'temprecords';

            this.temprecords = new Array();
            this.temprecords = clone(this.records);
        }

        if ( !criteria.order_by ){
            if ( !criteria.order_dir || criteria.order_dir != 'desc')
                return;

            this[var_to_sort].reverse();
            return;
        }

        this.sortfield = criteria.order_by;
        this[var_to_sort].sort(this.__sort);

        if ( criteria.order_dir && criteria.order_dir == 'desc' )
            this[var_to_sort].reverse();
    }
}
#5

[eluser]Cyclops[/eluser]
Code:
///////////////////////////////////////////////////////////////////////////
// EXAMPLES OF USAGE:
///////////////////////////////////////////////////////////////////////////

    //-----------------------------
    //-----------------------------
    // Create a new database
    //
        newdb = new js_database();
    //
    //-----------------------------
    //-----------------------------

    //--------------------------------------------------------------------
    //--------------------------------------------------------------------
    // INSERT records into the new database
    //
        insert = {
            'records' : [
                {'name':'Fred' , 'occupation':'doctor' , 'children':2  },
                {'name':'Jim'  , 'occupation':'doctor' , 'children':4  },
                {'name':'Bob'  , 'occupation':'pastor' , 'children':2  },
                {'name':'Mary' , 'occupation':'nurse'  , 'children':3  }
            ]
        };

        alert( 'NEWDB: '     + print_r(newdb ) );
        alert( 'INSERTING: ' + print_r(insert) );
        newdb._insert(insert);

        alert( 'NEWDB: '    + print_r(newdb) );
        alert( 'SELECT *: ' + print_r(newdb._select({'records':'*'})));
    //
    //--------------------------------------------------------------------
    //--------------------------------------------------------------------

    //------------------------------------------------------------------------
    //------------------------------------------------------------------------
    // INSERT _additional_ records into the database
    //
        insert = {
            'records' : [
                {'name':'Kami' , 'occupation':'homemaker'  , 'children':2  },
                {'name':'Mike' , 'occupation':'programmer' , 'children':2  },
                {'name':'Mike' , 'occupation':'super dude' , 'children':12 }
            ]
        };

        alert( 'INSERTING: ' + print_r(insert ) );
        newdb._insert(insert);

        alert( 'NEWDB: '    + print_r(newdb) );
        alert( 'SELECT *: ' + print_r(newdb._select({'records':'*'})));
    //
    //------------------------------------------------------------------------
    //------------------------------------------------------------------------

    //------------------------------------------------------------------------
    //------------------------------------------------------------------------
    // DELETE from the database (with various criteria
    //
        del = {
            //'records' : '*',
            'records' : [
                //{'name':'occupation', 'value':'doctor'}
            ],
            'records_gt' : [
                //{'name':'children', 'value':2}
            ],
            'records_lt' : [
                //{'name':'children', 'value':4}
            ],
            'records_ne' : [
                //{'name':'name', 'value':'Bob'}
            ]
        };

        del.limit       = 2;      // with a limit
        del.limit_start = 1;      // with a limit
        del.order_by    = 'name'; // with an ORDER BY
        del.order_dir   = 'desc'; // with an order by direction

        alert( 'DELETING: ' + print_r(del) );
        deleted = newdb._delete(del);
        alert( 'DELETED: ' + print_r(deleted) );

        alert( 'SELECT *: ' + print_r(newdb._select({'records':'*'})));
    //
    //------------------------------------------------------------------------
    //------------------------------------------------------------------------

    //------------------------------------------------------------------------
    //------------------------------------------------------------------------
    // perform SELECTs with various criteria
    //
        select = {
            //'records' : '*',
            'records' : [
                //{'name':'occupation', 'value':'doctor'}
            ],
            'records_gt' : [
                //{'name':'children', 'value':3}
            ],
            'records_lt' : [
                //{'name':'children', 'value':4}
            ],
            'records_ne' : [
                //{'name':'children', 'value':2}
            ]
        };
        select.limit       = 2;      // with a limit
        select.limit_start = 1;      // with a limit start
        select.order_by    = 'name'; // with an ORDER BY
        select.order_dir   = 'asc';  // with an order by direction
        alert( 'SELECTING: ' + print_r(select) );
        recs = newdb._select(select);
        alert( 'SELECTED: ' + print_r(recs) );

        select.limit       = 2;      // with a limit
        select.limit_start = 1;      // with a limit start
        select.order_by    = 'name'; // with an ORDER BY
        select.order_dir   = 'asc';  // with an order by direction
        alert( 'SELECTING: ' + print_r(select) );
        recs = newdb._select(select);
        //alert( 'SELECTED: ' + print_r(recs) );
    //
    //------------------------------------------------------------------------
    //------------------------------------------------------------------------

    //------------------------------------------------------------------------
    //------------------------------------------------------------------------
    // yet another SELECT
    //
        select = {
            'records' : [
                {'name':'children'   , 'value':'2'},
                {'name':'occupation' , 'value':'homemaker'}
            ]
        };
        alert( 'SELECTING: ' + print_r(select) );
        recs = newdb._select(select);
        alert( 'SELECTED: ' + print_r(recs) );
    //
    //------------------------------------------------------------------------
    //------------------------------------------------------------------------
#6

[eluser]Cyclops[/eluser]
Code:
//------------------------------------------------------------------------
    //------------------------------------------------------------------------
    // let's try an UPDATE on _everything_
    //
        update = {
            'records' : '*',
            'change' : [
                {'name':'new_field', 'value':'new_value'}
            ]
        }
        //alert( 'UPDATING: ' + print_r(update) );
        updated = newdb._update(update);
        //alert( 'UPDATED: '  + print_r(updated) );
        //alert( 'SELECT *: ' + print_r(newdb._select({'records':'*'})));
    //
    //------------------------------------------------------------------------
    //------------------------------------------------------------------------




    //------------------------------------------------------------------------
    //------------------------------------------------------------------------
    // an UPDATE with various criteria
    //
        update = {
            //'records' : '*',
            'records' : [
                //{'name':'name', 'value':'Mike'},
                //{'name':'children' , 'value':'2'}

                //{'name':'occupation' , 'value':'doctor'}
            ],
            'records_gt' : [
                //{'name':'children', 'value':2}

                //{'name':'name', 'value':'Mike'}
            ],
            'records_lt' : [
                //{'name':'children', 'value':4}
            ],
            'records_ne' : [
                //{'name':'children', 'value':2}
            ],
            'change' : [
                //{'name':'desired_children', 'value':'12'}//,
                //{'name':'name', 'value':'I am Cool'}
            ]
        }

        //update.limit       = 3;      // with a limit
        //update.limit_start = 2;      // with a limit_start
        //update.order_by    = 'name'; // with an ORDER BY
        //update.order_dir   = 'asc';  // with an order by direction
        //alert( 'UPDATING: ' + print_r(update) );
        updated = newdb._update(update);
        //alert( 'UPDATED: '  + print_r(updated) );
        //alert( 'SELECT *: ' + print_r(newdb._select({'records':'*'})));
    //
    //------------------------------------------------------------------------
    //------------------------------------------------------------------------
#7

[eluser]usmc[/eluser]
Very nice. Thanks Cyclops I will test it out.
#8

[eluser]demosthenes[/eluser]
This is very impressive. Do you know what the record limit is?
#9

[eluser]Cyclops[/eluser]
I sure don't. However many objects a JavaScript array will hold, I imagine. There is no limit inherent in the database engine itself.

Thanks for the complement. Feel free to use it anywhere you want.
#10

[eluser]Cyclops[/eluser]
Oh... I forgot to mention in the documentation that any time an ORDER BY is performed, the actual contents of the database are changed (not just the return or the search), so that if you do a _select, _update, or _delete and add an order_by, the actual this.records array of the database is reordered.




Theme © iAndrew 2016 - Forum software by © MyBB