UNPKG

queryable

Version:

Nosql database that supports subset of mongodb operators

1,249 lines (1,077 loc) 43.2 kB
/** queryable.js A tiny, self-contained, single file database parser that uses clear-text, human readable JSON for storage. It supports a small, useful subset of MongoDB-like commands. The DB is a array of objects, which are stored as a plaintext JSON string. It works in both node and the browser. Its main difference from Mongo is that there are no collections. There is a queryable object, which is used for creating db_objects. You can have as many db_objects as you like, but have to keep track of them yourself. (c) 2014 Greg Naughton greg@naughton.org https://github.com/gmn For more details, documentation, new releases and code see: https://github.com/gmn/queryable */ (function(queryable) { 'use strict'; ////////////////////////////////////////////////// // // utility functions, internal // var p = function(s) { console.log(s); }; function type_of( t ) { var s = typeof t; switch( s ) { case "object": if ( t instanceof Date ) { return "date"; } else if ( t instanceof Array ) { return "array"; } else if ( t instanceof RegExp ) { return "regexp"; } default: return s; } } function classof(o) { if (o === null) return "Null"; if (o === undefined) return "Undefined"; return Object.prototype.toString.call(o).slice(8,-1); } function detect_platform() { var platform = "unknown"; try { if ( exports !== undefined ) platform = "node_module"; } catch(e) { try { if ( window !== undefined ) platform = "browser"; } catch(e) { } } return platform; } function clip_all_leading(str, clip) { while ( str.length && str.charAt(0) === clip ) { str = str.substring(clip.length,str.length); } return str; } function _firstKey( O ) { for ( var i in O ) { if ( O.hasOwnProperty(i) ) return i; } return null; } // converts object {key1:val1,key2:val2,...} // into array [{key:key1,value:val1},{key:key2,value:val2},...] // recurses, so: obj {key1:{key2:val2,key3:val3}} becomes: // [{key:key1,value:[{key:key2,value:val2},{key:key3,value:val3}]}] function _getKeys( O ) { var keys = []; if ( type_of(O) !== "object" ) return null; for ( var i in O ) { if ( O.hasOwnProperty(i) ) { var _val = type_of(O[i]) === "object" ? _getKeys(O[i]) : O[i]; if ( type_of(_val) === "array" && _val.length === 1 ) _val = _val[0]; // ditch the array if only 1 elt keys.push( {key:i,value:_val} ); } } return keys; } // takes an object and sorts it by its keys, alphabetically function sortObjectByKeys( O ) { if ( typeof O !== "object" || (O instanceof Array) ) return O; var keys = []; for ( var i in O ) { if ( O.hasOwnProperty(i) ) { keys.push( {key:i,value:O[i]} ); } } if ( keys.length === 0 ) return O; keys.sort( function(a,b) { return a.key < b.key ? -1 : 1; } ); var nO = {}; keys.forEach( function(item) { nO[item.key] = item.value; }); return nO; } function addToFront( obj, _key, _value ) { if ( typeof obj !== "object" ) return obj; // get existing keys var keys = []; for ( var key in obj ) { if ( obj.hasOwnProperty(key) ) { keys.push(key); } } // empty, return it if ( keys.length === 0 ) return obj; var newObj = {}; newObj[ _key ] = _value; // set in the front // now transfer the rest keys.forEach(function(key) { newObj[key] = obj[key]; }); return newObj; } // sorts in place function sortArrayOfObjectsByKeys( array_of_objs ) { if ( type_of(array_of_objs) !== "array" ) return array_of_objs; if ( array_of_objs.length === 0 ) return array_of_objs; // sort each key array_of_objs.forEach(function(val,index,ary) { ary[index] = sortObjectByKeys(val); }); // sort entire array by firstKey array_of_objs.sort(function(a,b){ return _firstKey(a) < _firstKey(b) ? -1 : 1; }); return array_of_objs; } ////////////////////////////////////////////////// // // Classes // /** * * Class: db_result * */ function db_result( arg ) { this.length = 0; this.rows = []; // sort of a copy-constructor. If Array of Obj is passed in, // we copy (not reference) it into rows if ( arguments.length === 1 && type_of(arg) === "array" ) { for ( var i = 0, l = arg.length; i < l; i++ ) { this.push( arg[i] ); } } } db_result.prototype = { push: function( O ) { if ( type_of(O) === "object" ) this.rows.push(JSON.parse(JSON.stringify(O))); this.length = this.rows.length; return this; }, /* SELECT * FROM users WHERE status = "A" ORDER BY user_id ASC db.users.find( { status: "A" } ).sort( { user_id: 1 } ) SELECT * FROM users WHERE status = "A" ORDER BY user_id DESC db.users.find( { status: "A" } ).sort( { user_id: -1 } ) */ sort: function( O ) { var key = _firstKey(O); var val = O[key]; this.rows.sort(function(a,b) { if ( !a[key] ) return -val; else if ( !b[key] ) return val; if ( typeof a[key] === "string" && typeof b[key] === "string" ) return a[key].localeCompare(b[key]) * val; else return a[key] > b[key] ? val : -val; }); return this; }, limit: function( _l ) { var lim = Number(_l); if ( type_of(lim) !== "number" ) return this; this.rows.splice( lim, this.rows.length - lim ); this.length = this.rows.length; return this; }, skip: function( s ) { var skp = Number(s); if ( type_of(skp) !== "number" ) return this; this.rows.splice( 0, skp ); this.length = this.rows.length; return this; }, count: function() { return this.rows.length; }, getArray: function() { return this.rows; }, get_json: function(fmt) { if ( arguments.length === 0 ) return JSON.stringify(this.rows); return JSON.stringify(this.rows,null,fmt); }, print: function(fmt) { var f = arguments.length === 0 ? ' ' : fmt; console.log(this.get_json(f)); } }; // db_result /** * * Class: db_object * - returned by open() * - contains entire database w/ accessor methods * */ function db_object( config ) { this.platform = config.platform; this.db_path = config.db_path; this.db_dir = config.db_dir; this.db_name = config.db_name; this.use_gzip = config.use_gzip || false; this.master = []; this._id = 0; // can populate db explicitly using a json string // - if {}.data set, will override the other loading methods // - database will still save to named location, normally if ( config.data ) { if ( type_of( config.data ) == "array" ) this.master = config.data ; else if ( type_of( config.data) == "string" ) this.master = JSON.parse( config.data ); else console.log( "queryable: error: Could not determine input data type\n" ); finish_db_setup.call(this); } // // read in db if it's there // // BROWSER else if ( this.platform === "browser" ) { var name = this.db_name.trim(); this.db_name = ( !name || name.length===0 || name === "test.db" ) ? 'queryable' : name; if ( window.localStorage && localStorage.hasOwnProperty( this.db_name ) ) { var string = localStorage[this.db_name]; this.master = JSON.parse( string ); } finish_db_setup.call(this); } // SERVER else if ( this.platform === "node_module" ) { var fs = require('fs'); // presence of .gz extension sets use_gzip if ( this.db_path.lastIndexOf('.gz') === this.db_path.length-3 ) this.use_gzip = true; // if db_path exists, load it if ( fs.existsSync( this.db_path ) ) { var was_gzip = false; // try to open with gzip; note: presence of gzbz is no longer guaranteed if ( this.use_gzip ) { try { var gzbz = require('gzbz'); var gunzip = new gzbz.Gunzip; gunzip.init( {encoding:'utf8'} ); var gzdata = fs.readFileSync(this.db_path,{encoding:"binary",flag:'r'}); var inflated = gunzip.inflate( gzdata, "binary" ); gunzip.end(); // convert into master format this.master = JSON.parse( inflated ); finish_db_setup.call(this); return; } catch(e) { this.use_gzip = false; console.log( "warning: tried to open as gzip without gzbz module present. Trying normal." ); was_gzip = true; } } try { // normal, no gzip var data = fs.readFileSync(this.db_path,{encoding:"utf8",flag:'r'}); // convert into master format this.master = JSON.parse( data ); finish_db_setup.call(this); } catch(e) { var msg = 'error: "' + this.db_path + '" failed to open.'; console.log( msg ); process.exit(-1); } } } function finish_db_setup() { if ( this.master.length > 0 ) { // next _id is 1 greater than highest _id var highest = 0; var any_missing = false; this.master.forEach(function(row) { if ( row['_id'] === undefined ) any_missing = true; else if ( row['_id'] > highest ) { highest = row['_id']; } }); // rows w/o _id need to have one added if ( any_missing ) { for ( var i = 0, l = this.master.length; i < l; i++ ) { if ( this.master[i]['_id'] === undefined ) { this.master[i] = addToFront( this.master[i], '_id', ++highest ); } } } this._id = highest; // sort in place ? // - sort each object // ... // - sort by _id, ensuring consistent state of ascending _id this.master = this.master.sort(function(a,b){return a._id - b._id}); } } // finish_db_setup } db_object.prototype = { ////////////////////////////////////////////////// // // public methods // /** * save() * - return: true on successful write * - takes: * arg1 (Optional): filemode (String or Number), or Callback * arg2 (Optional): callback function (optional) */ save: function(arg1, arg2) { var _mode = undefined; var callback = undefined; // sanity check arguments if ( arguments.length === 1 ) { if ( type_of(arg1) === "function" ) { callback = arg1; } else { var num = Number(arg1); if ( !isNaN(num) ) { _mode = num; } else { throw new Error("save: accepts 0, 1 or 2 arguments, eg.: save(), save(mode), save(mode,callback), or save(callback)" ); } } } else if ( arguments.length === 2 ) { if ( type_of(arg2) !== "function" ) throw new Error("save: usage: save(mode, callback)" ); callback = arg2; var num = Number(arg1); if ( !isNaN(num) ) { _mode = num; } else { throw new Error("save: accepts 0, 1 or 2 arguments, eg.: save(), save(mode), save(mode,callback), or save(callback)" ); } } if ( this.platform === "node_module" ) { var mode = _mode || 438; // 0666; var fs = require('fs'); // if parent application quits suddenly, write may be voided. // writes must be ensured. perhaps a better way to do this? Best possible case: // have both async writes and ensured writes, even on sudden process.exit() var use_async = false; var gzip_lvl = 1; // 5 is middle. bias heavily towards speed since using gzip makes this I/O bound if ( this.use_gzip ) { try { if ( use_async ) { var ostream = fs.createWriteStream( this.db_path ); var zlib = require('zlib'); var Stream = require('stream'); var in_stream = new Stream(); in_stream.pipe(zlib.createGzip()).pipe(ostream); in_stream.emit('data', JSON.stringify(this.master) ); in_stream.emit('end'); } else { var gzbz = require('gzbz'); var gzip = new gzbz.Gzip(); gzip.init( {encoding:"binary", level: gzip_lvl /* 1<=level<=9 */} ); var gz1 = gzip.deflate( JSON.stringify(this.master) ); var gz2 = gzip.end(); // important to capture end! var gzdata = gz1 + gz2; fs.writeFileSync( this.db_path, gzdata, {encoding:"binary",mode:mode,flag:'w'} ); } return this.__return( true, callback ); } catch(e) { return this.__return( e, callback ); } } try { fs.writeFileSync( this.db_path, JSON.stringify(this.master), {encoding:"utf8",mode:mode,flag:'w'} ); } catch(e) { throw new Error( "save: failed writing: \""+this.db_path+'" '+e.toString() ); } } else if ( this.platform === "browser" ) { localStorage[this.db_name] = JSON.stringify(this.master); } return this.__return( true, callback ); }, // save /** * insert() * - return: the number of rows inserted. * - takes: * arg1: Object or Array of Objects to insert * arg2 (Optional): Callback * - throws Error on malformed insert */ insert: function( Arg, callback ) { if ( arguments.length !== 1 && arguments.length !==2 ) throw new Error("insert: accepts 1 or 2 arguments: insert(row [,callback])"); var that = this; function insert_one( obj ) { if ( type_of(obj) !== 'object' ) throw new Error("insert: row element must be Object"); // _id magically placed on the front of new object-rows if ( !obj["_id"] ) obj = addToFront( obj, '_id', ++that._id ); that.master.push(obj); return 1; } var num_rows = 0; if ( type_of( Arg ) === "array" ) { for ( var i = 0, l = Arg.length; i < l; i++ ) { num_rows += insert_one( Arg[i] ); } } else if ( type_of( Arg ) === "object" ) { num_rows += insert_one(Arg); } else { throw new Error("insert: accepts Object or Array of Objects"); } return this.__return(num_rows, callback); }, // insert /** * update() * - return: number of rows altered * - takes: * arg1: query Object * arg2: update Object * arg3 (Optional): options Object * arg4 (Optional): callback Function * - options: * upsert - If true, creates a new row if none matches. * Default is false * multi - If true, updates multiple rows matching query. * Default is to limit update to only one document. */ update: function( query, _update, options, callback ) { if ( arguments.length < 2 ) throw new Error("usage: update(query,update,[options],[callback])"); if ( type_of(query) !== "object" || type_of(_update) !== "object" ) throw new Error("usage: update(query,update,[options],[callback])"); if ( arguments.length === 3 && type_of(options) !== "object" ) throw new Error("usage: update(query,update,[options],[callback])"); var set = _update['$set']; if ( !set ) throw new Error("usage: update(query,update,[options],[callback])"); // these are the rows we're updating var res = this.do_query( query ); var do_multi = false, do_upsert = false; if ( options ) { do_multi = options['multi'] ? options['multi'] : false; do_upsert = options['upsert'] ? options['upsert'] : false; } // chance to upsert if ( res.length === 0 && do_upsert ) { this.insert( set ); return this.__return( 1, callback ); } var rows_altered = 0; // foreach row of the matching result for ( var i = 0, l = res.length; i < l; i++ ) { var row = res[i]; // foreach key/value in $set, update a row var did_change = false; for ( var j in set ) { if ( set.hasOwnProperty(j) ) { var key = j; var value = set[j]; if ( !row[key] || row[key] !== value ) { row[key] = value; did_change = true; } } } if ( did_change ) ++rows_altered; if ( !do_multi ) break; // do 1 row only } return this.__return(rows_altered, callback); }, // update /** * find() * - return: db_result * - takes: * arg1 (Optional): match Object, * arg2 (Optional): callback Function */ find: function( match, callback ) { if ( !match ) match = {}; if ( arguments.length > 2 || type_of(match)!=="object" ) throw new Error( "find: usage: find([match],[callback])" ); var res = this.do_query( match ); var dbres = new db_result( res ); return this.__return( dbres, callback ); }, // find /* * distinct() * - about: eliminates duplicate rows from the result * - return: db_result * - takes: * arg1: key to get distinct set of, String * arg2 (Optional): clause to limit set to match against, Object * arg3 (Optional): callback Function * - examples: distinct('key'), distinct('key',{price:{$gt:10}}) */ distinct: function( str, clause, callback ) { if ( str === undefined ) throw new Error("usage: distinct(key,[clause],[callback])"); if ( clause ) { var res = this.do_query(clause); } else { var res = this.do_query(); } var set_wo = []; var maybe = []; for ( var i = 0, l = res.length; i < l; i++ ) { // every row that has key gets put in an array to be thinned if ( res[i][str] !== undefined ) { maybe.push(res[i]); // every row that doesn't have key goes to set_wo[] /* } else { set_wo.push(res[i]); */ } } var distinct_set = []; for ( var i = 0, l = maybe.length; i < l; i++ ) { if ( !(distinct_set.some(function(x){return x[str] === maybe[i][str]})) ) distinct_set.push( maybe[i] ); } // FIXME: I dont know why you'd put rows in the return set that // dont contain the key... wtf was I thinking? //var dbres = new db_result( set_wo.concat(distinct_set) ); var dbres = new db_result( distinct_set ); res = null; return this.__return(dbres, callback); }, // distinct /** * remove() * - return: number rows altered * - takes: * arg1 (Optional): constraints * arg2 (Optional): callback, Function */ remove: function( constraints, callback ) { if ( arguments.length === 0 ) var constraints = {}; if ( type_of(constraints) !== "object" ) throw new Error("usage: remove( [constraints], [callback] )" ); // get the rows to remove var rows = this.do_query( constraints ); if ( rows.length === 0 ) return this.__return( 0, callback ); var rmids = []; // collect row _id's for ( var i = 0, l = rows.length; i < l; i++ ) { var id = rows[i]['_id']; if ( !id ) continue; rmids.push( id ); } if ( rmids.length === 0 ) return this.__return( 0, callback ); var rows_altered = 0; var new_master = this.master.filter(function(row) { for ( var i = 0, l = rmids.length; i < l; i++ ) { if ( row['_id'] && row['_id'] === rmids[i] ) { ++rows_altered; return false; } } return true; }); if ( rows_altered > 0 ) this.master = new_master; return this.__return( rows_altered, callback ); }, // remove get_json: function() { return JSON.stringify( this.master ); }, // get_json print: function(fmt) { var f = arguments.length === 0 ? ' ' : fmt; console.log( JSON.stringify(this.master,null,f) ); }, // print now: function() { var n = new Date(); if ( n.toISOString && typeof n.toISOString === "function" ) { return n.toISOString(); } return n.getFullYear() + '-' + (n.getMonth()+1) + '-' + n.getDate() + 'T' + n.toUTCString().replace( /.*(\d\d:\d\d:\d\d).*/, "$1" ) + '.000Z'; }, // now // returns date object set to ISO string input toDate: function( isostring ) { return new Date( isostring ); }, // toDate count: function() { return this.master.length; }, // count renormalize: function() { // sort each row elements for ( var i = 0, l = this.master.length; i < l; i++ ) { this.master[i] = sortObjectByKeys(this.master[i]); } // sort entirety by _id this.master.sort( function(a,b){ return a._id - b._id } ); // renumber starting at 1 for ( var i = 0, l = this.master.length; i < l; i++ ) { this.master[i]._id = 1 + i; } }, // renormalize ////////////////////////////////////////////////// // // private methods (not returned in constructor) // __return: function( arg, callback ) { if ( callback ) return callback(arg); return arg; }, // query matching functions detect_clause_type: function( key, value ) { switch ( type_of(value) ) { case "boolean": case "date": case "number": case "string": // NORMAL | SUBDOCUMENT_MATCH case "regexp": return key.indexOf('.') === -1 ? "CLAUSE_NORMAL" : "CLAUSE_SUBDOCUMENT_MATCH"; case "object": // CONDITIONAL | SUBDOCUMENT var fk = _firstKey(value); switch(fk) { case '$gt': case '$gte': case '$lt': case '$lte': case '$exists': case '$ne': return "CLAUSE_CONDITIONAL"; default: return "CLAUSE_SUBDOCUMENT"; } break; case "array": // OR | ARRAY return key === '$or' ? "CLAUSE_OR" : "CLAUSE_ARRAY"; default: break; } return "CLAUSE_UNKNOWN"; }, // this.detect_clause_type matching_rows_NORMAL: function( test, rows ) { var res = []; var i = 0; // for all rows next_row: for ( var l = rows.length; i < l; i++ ) { var row = rows[i]; // for each unique key in the row for ( var key in row ) { // matches our query key if ( row.hasOwnProperty(key) && key === test.key ) { // RegExps: equiv to SQL "like" statement if ( type_of( test.value ) === "regexp" ) { var sval = row[key] + ''; if ( sval.match( test.value ) ) { res.push( row ); continue next_row; } // compare number, date, string statements directly } else { if ( row[key] === test.value ) { res.push( row ); continue next_row; } } } // key match } // each row key } // each row return res; }, // matching_rows_NORMAL matching_rows_CONDITIONAL: function( test, rows ) { var res = []; var i = 0; var cond = _firstKey(test.value); next_row: // foreach row for ( var l = rows.length; i < l; i++ ) { var row = rows[i]; if ( cond === '$exists' ) { if ( test.value[cond] ) { /* true */ if ( row[test.key] ) { res.push( row ); continue next_row; } } else { /* false */ if ( ! row[test.key] ) { res.push( row ); continue next_row; } } continue next_row; } // add rows that don't contain test.key if ( cond === '$ne' ) { // see if row contains test.key at all, if not add it if ( ! row[test.key] ) { res.push(row); continue next_row; // else do -ne test against row's matching key } else if ( row[test.key] !== test.value['$ne'] ) { res.push(row); continue next_row; } } // for every unique key in row for ( var key in row ) { // key matches if ( row.hasOwnProperty(key) && key === test.key ) { switch ( cond ) { case '$lt': if ( row[key] < test.value[cond] ) { res.push(row); continue next_row; } break; case '$lte': if ( row[key] <= test.value[cond] ) { res.push(row); continue next_row; } break; case '$gt': if ( row[key] > test.value[cond] ) { res.push(row); continue next_row; } break; case '$gte': if ( row[key] >= test.value[cond] ) { res.push(row); continue next_row; } break; case '$ne': if ( row[key] !== test.value[cond] ) { res.push(row); continue next_row; } break; default: break; } } // key match } // each key in row } // each row // remove the key:value from test object if ( cond ) delete test.value[cond]; return res; }, // matching_rows_CONDITIONAL matching_rows_OR: function( array, rows ) { var res = []; var i = 0; next_row: for ( var l = rows.length; i < l; i++ ) { var row = rows[i]; for ( var j = 0, la = array.length; j < la; j++ ) { var eltkey = _firstKey( array[j] ); var eltval = array[j][eltkey]; var test = { key:eltkey, value:eltval }; var clausetype = this.detect_clause_type( eltkey, eltval ); switch ( clausetype ) { case "CLAUSE_NORMAL": if ( type_of( test.value ) === "regexp" ) { if ( row[test.key] && row[test.key].match( test.value ) ) { res.push( row ); continue next_row; } } else { if ( row[test.key] === test.value ) { res.push( row ); continue next_row; } } break; case "CLAUSE_CONDITIONAL": switch( _firstKey(test.value) ) { case '$gt': if ( row[test.key] > test.value['$gt'] ) { res.push(row); continue next_row; } break; case '$gte': if ( row[test.key] >= test.value['$gte'] ) { res.push(row); continue next_row; } break; case '$lt': if ( row[test.key] < test.value['$lt'] ) { res.push(row); continue next_row; } break; case '$lte': if ( row[test.key] <= test.value['$lte'] ) { res.push(row); continue next_row; } break; case '$exists': if ( row[test.key] !== undefined && test.value['$exists'] ) { res.push(row); continue next_row; } else if ( row[test.key] === undefined && !test.value['$exists'] ) { res.push(row); continue next_row; } break; } break; default: break; } } } return res; }, // matching_rows_OR do_query: function( clauses ) { var result = this.master; // CLAUSE_EMPTY if ( !clauses || (type_of(clauses)==="object" && _firstKey(clauses)===null) ) { return result; } // next_clause: for ( var clause in clauses ) { if ( ! clauses.hasOwnProperty(clause) ) continue next_clause; var clausetype = this.detect_clause_type(clause,clauses[clause]); switch ( clausetype ) { case "CLAUSE_NORMAL": // simple key/value result = this.matching_rows_NORMAL( { key: clause, value: clauses[clause] }, result ); break; case "CLAUSE_CONDITIONAL": while ( _firstKey(clauses[clause]) !== null ) { result = this.matching_rows_CONDITIONAL( { key: clause, value: clauses[clause] }, result ); } break; case "CLAUSE_OR": result = this.matching_rows_OR( clauses[clause], result ); break; default: break; } } return result; }, // do_query sortMaster: function () { sortArrayOfObjectsByKeys( this.master ); } }; // db_object.prototype /** - MAIN MODULE INTERFACE - opens physical database (new one is created if non-existent) - returns handle to new db_object */ queryable.open = function ( config ) { // private variables var that = this; // object parameters this.platform = detect_platform(); switch ( this.platform ) { case "node_module": var path = require('path'); var fs = require('fs'); this.db_name = 'test.db'; this.db_dir = path.resolve(__dirname); this.db_path = 0; // defaults to off; can be set by either: the useGzip() method or config{} // also: sets to ON automatically if file opened has *.gz extension this.use_gzip = false; return server_open( config ); case "browser": var _name = ''; var data = undefined; if ( type_of(config) === "string" ) _name = config; else if ( type_of(config) === "object" ) { _name = ( config && config.db_name ) ? config.db_name : ''; if ( config.string ) data = config.string; else if ( config.data ) data = config.data; } if ( data ) return new db_object( {"platform":"browser",db_name:_name,data:data} ); else return new db_object( {"platform":"browser",db_name:_name} ); default: p( "unknown platform" ); return queryable; } function server_open( config ) { var parm_list = ['db_name','db_dir','db_path','use_gzip']; // assume it is either (in this order): path, fullpath, filename if ( arguments.length > 0 && typeof config === "string" ) { try { // is file var data = fs.readFileSync(config,{encoding:"utf8",flag:'r'}); // throws if Directory or File doesn't exist // fullpath that.db_path = path.resolve(config); // name that.db_name = clip_all_leading( that.db_path.substring( that.db_path.lastIndexOf('/'), that.db_path.length ), '/' ); // dir that.db_dir = that.db_path.substring(0, that.db_path.lastIndexOf( that.db_name )); } catch(e) { switch ( e.code ) { case "ENOENT": // file not exists: get db_name, db_dir that.db_path = path.resolve(config); that.db_name = clip_all_leading( that.db_path.substring( that.db_path.lastIndexOf('/'), that.db_path.length ), '/' ); that.db_dir = that.db_path.substring(0, that.db_path.lastIndexOf( that.db_name )); break; case "EISDIR": // is a directory: get db_dir that.db_dir = path.resolve(config); break; default: // who knows break; } } } // overwrite from user-supplied config settings else if ( arguments.length > 0 && typeof config === "object" ) { for ( var i = 0; i < parm_list.length; i++ ) { if ( config[parm_list[i]] ) { if ( parm_list[i] === "db_path" ) config[parm_list[i]] = path.resolve( config[parm_list[i]] ); else if ( parm_list[i] === "db_dir" ) config[parm_list[i]] = path.resolve( config[parm_list[i]] ); else if ( parm_list[i] === 'db_name' ) config[parm_list[i]] = clip_all_leading( config[parm_list[i]], '/' ); that[parm_list[i]] = config[parm_list[i]]; } } // if db_path supplied, check that db_name and db_dir match, or else db_name and db_dir override, and path must be reset if ( config.db_path ) { var _n = clip_all_leading( that.db_path.substring( that.db_path.lastIndexOf('/'), that.db_path.length ), '/' ); var _d = that.db_path.substring(0, that.db_path.lastIndexOf( _n )); var _any_changed = 0; if ( _n !== that.db_name ) { that.db_name = _n; ++_any_changed; } if ( _d !== that.db_dir ) { that.db_dir = _d; ++_any_changed; } if ( _any_changed ) { that.db_path = 0; } } } // set db_path, if we didn't get it yet if ( ! that.db_path ) { if ( that.db_dir && that.db_dir[that.db_dir.length-1] === '/' ) that.db_path = that.db_dir + that.db_name; else that.db_path = that.db_dir + '/' + that.db_name; } var rows = config && config.data ? config.data : undefined; return new db_object( {db_path:that.db_path,db_dir:that.db_dir,db_name:that.db_name,"platform":that.platform,use_gzip:that.use_gzip,data:rows} ); } // server_open() }; // queryable.open queryable.useGzip = function() { if ( arguments.length > 0 ) { this.use_gzip = arguments[0]; } } try { if ( window ) window.queryable = queryable; } catch(e) { } return queryable; })(typeof exports === "undefined" ? {} : exports);