2013-06-24 22:21:59 +00:00
|
|
|
var geo = require('../geo');
|
2013-07-17 00:53:52 +00:00
|
|
|
var utils = require('../utils');
|
2013-06-24 22:21:59 +00:00
|
|
|
|
2013-07-23 18:16:43 +00:00
|
|
|
exports.initialize = function initializeSchema(dataSource, callback) {
|
|
|
|
dataSource.connector = new Memory();
|
|
|
|
dataSource.connector.connect(callback);
|
2011-10-03 13:36:43 +00:00
|
|
|
};
|
|
|
|
|
2013-06-26 03:31:00 +00:00
|
|
|
exports.Memory = Memory;
|
|
|
|
|
2013-04-01 13:49:12 +00:00
|
|
|
function Memory(m) {
|
|
|
|
if (m) {
|
|
|
|
this.isTransaction = true;
|
|
|
|
this.cache = m.cache;
|
|
|
|
this.ids = m.ids;
|
|
|
|
this._models = m._models;
|
|
|
|
} else {
|
|
|
|
this.isTransaction = false;
|
|
|
|
this.cache = {};
|
|
|
|
this.ids = {};
|
|
|
|
this._models = {};
|
|
|
|
}
|
2011-10-03 13:36:43 +00:00
|
|
|
}
|
|
|
|
|
2013-04-01 13:49:12 +00:00
|
|
|
Memory.prototype.connect = function(callback) {
|
|
|
|
if (this.isTransaction) {
|
|
|
|
this.onTransactionExec = callback;
|
|
|
|
} else {
|
|
|
|
process.nextTick(callback);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2011-10-03 13:36:43 +00:00
|
|
|
Memory.prototype.define = function defineModel(descr) {
|
|
|
|
var m = descr.model.modelName;
|
|
|
|
this._models[m] = descr;
|
|
|
|
this.cache[m] = {};
|
|
|
|
this.ids[m] = 1;
|
|
|
|
};
|
|
|
|
|
2013-08-28 05:32:01 +00:00
|
|
|
/**
|
|
|
|
* Get the id property name for the given model
|
|
|
|
* @param {String} model The model name
|
|
|
|
* @returns {String} The id property name
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Memory.prototype.idName = function(model) {
|
|
|
|
return (this.dataSource && this.dataSource.idName && this.dataSource.idName(model)) || 'id';
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the id value for the given model
|
|
|
|
* @param {String} model The model name
|
|
|
|
* @param {Object} data The model instance data
|
|
|
|
* @returns {*} The id value
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Memory.prototype.getIdValue = function(model, data) {
|
|
|
|
return data && data[this.idName(model)];
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the id value for the given model
|
|
|
|
* @param {String} model The model name
|
|
|
|
* @param {Object} data The model instance data
|
|
|
|
* @param {*} value The id value
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Memory.prototype.setIdValue = function(model, data, value) {
|
|
|
|
if(data) {
|
|
|
|
data[this.idName(model)] = value;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2011-10-03 13:36:43 +00:00
|
|
|
Memory.prototype.create = function create(model, data, callback) {
|
2013-08-28 05:32:01 +00:00
|
|
|
// FIXME: [rfeng] We need to generate unique ids based on the id type
|
|
|
|
// FIXME: [rfeng] We don't support composite ids yet
|
2013-08-22 23:44:02 +00:00
|
|
|
var currentId = this.ids[model];
|
|
|
|
if(currentId === undefined) {
|
|
|
|
// First time
|
|
|
|
this.ids[model] = 1;
|
|
|
|
currentId = 1;
|
|
|
|
}
|
2013-08-28 05:32:01 +00:00
|
|
|
var id = this.getIdValue(model, data) || currentId;
|
2013-08-22 23:44:02 +00:00
|
|
|
if(id > currentId) {
|
|
|
|
// If the id is passed in and the value is greater than the current id
|
|
|
|
currentId = id;
|
|
|
|
}
|
2013-08-28 05:32:01 +00:00
|
|
|
this.ids[model] = Number(currentId) + 1;
|
2013-08-22 23:44:02 +00:00
|
|
|
|
2013-08-28 05:32:01 +00:00
|
|
|
var props = this._models[model].properties;
|
2013-08-29 04:39:59 +00:00
|
|
|
var idName = this.idName(model);
|
|
|
|
id = (props[idName] && props[idName].type && props[idName].type(id)) || id;
|
2013-08-28 05:32:01 +00:00
|
|
|
this.setIdValue(model, data, id);
|
2013-03-27 13:10:13 +00:00
|
|
|
this.cache[model][id] = JSON.stringify(data);
|
2013-04-01 13:49:12 +00:00
|
|
|
process.nextTick(function() {
|
2013-01-21 18:21:43 +00:00
|
|
|
callback(null, id);
|
|
|
|
});
|
2011-10-03 13:36:43 +00:00
|
|
|
};
|
|
|
|
|
2012-03-22 19:46:16 +00:00
|
|
|
Memory.prototype.updateOrCreate = function (model, data, callback) {
|
2013-08-28 05:32:01 +00:00
|
|
|
var self = this;
|
|
|
|
this.exists(model, self.getIdValue(model, data), function (err, exists) {
|
2012-03-22 19:46:16 +00:00
|
|
|
if (exists) {
|
2013-08-28 05:32:01 +00:00
|
|
|
self.save(model, data, callback);
|
2012-03-22 19:46:16 +00:00
|
|
|
} else {
|
2013-08-28 05:32:01 +00:00
|
|
|
self.create(model, data, function (err, id) {
|
|
|
|
self.setIdValue(model, data, id);
|
2012-03-22 19:46:16 +00:00
|
|
|
callback(err, data);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2011-10-03 13:36:43 +00:00
|
|
|
Memory.prototype.save = function save(model, data, callback) {
|
2013-08-28 05:32:01 +00:00
|
|
|
this.cache[model][this.getIdValue(model, data)] = JSON.stringify(data);
|
2013-01-21 18:21:43 +00:00
|
|
|
process.nextTick(function () {
|
|
|
|
callback(null, data);
|
|
|
|
});
|
2011-10-03 13:36:43 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Memory.prototype.exists = function exists(model, id, callback) {
|
2013-01-21 18:21:43 +00:00
|
|
|
process.nextTick(function () {
|
2013-08-28 05:32:01 +00:00
|
|
|
callback(null, this.cache[model] && this.cache[model].hasOwnProperty(id));
|
2013-01-21 18:21:43 +00:00
|
|
|
}.bind(this));
|
2011-10-03 13:36:43 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Memory.prototype.find = function find(model, id, callback) {
|
2013-08-28 05:32:01 +00:00
|
|
|
var self = this;
|
2013-01-21 18:21:43 +00:00
|
|
|
process.nextTick(function () {
|
2013-04-06 10:50:23 +00:00
|
|
|
callback(null, id in this.cache[model] && this.fromDb(model, this.cache[model][id]));
|
2013-01-21 18:21:43 +00:00
|
|
|
}.bind(this));
|
2011-10-03 13:36:43 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Memory.prototype.destroy = function destroy(model, id, callback) {
|
|
|
|
delete this.cache[model][id];
|
2013-01-21 18:21:43 +00:00
|
|
|
process.nextTick(callback);
|
2011-10-03 13:36:43 +00:00
|
|
|
};
|
|
|
|
|
2013-04-06 10:50:23 +00:00
|
|
|
Memory.prototype.fromDb = function(model, data) {
|
|
|
|
if (!data) return null;
|
|
|
|
data = JSON.parse(data);
|
|
|
|
var props = this._models[model].properties;
|
2013-07-01 20:16:51 +00:00
|
|
|
Object.keys(data).forEach(function (key) {
|
|
|
|
var val = data[key];
|
|
|
|
if (typeof val === 'undefined' || val === null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (props[key]) {
|
|
|
|
switch(props[key].type.name) {
|
|
|
|
case 'Date':
|
2013-08-28 05:32:01 +00:00
|
|
|
val = new Date(val.toString().replace(/GMT.*$/, 'GMT'));
|
|
|
|
break;
|
2013-07-01 20:16:51 +00:00
|
|
|
case 'Boolean':
|
2013-08-28 05:32:01 +00:00
|
|
|
val = Boolean(val);
|
|
|
|
break;
|
|
|
|
case 'Number':
|
|
|
|
val = Number(val);
|
|
|
|
break;
|
2013-07-01 20:16:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
data[key] = val;
|
|
|
|
});
|
|
|
|
return data;
|
2013-04-06 10:50:23 +00:00
|
|
|
};
|
|
|
|
|
2011-10-03 13:36:43 +00:00
|
|
|
Memory.prototype.all = function all(model, filter, callback) {
|
2013-03-27 13:10:13 +00:00
|
|
|
var self = this;
|
2011-10-03 13:36:43 +00:00
|
|
|
var nodes = Object.keys(this.cache[model]).map(function (key) {
|
2013-04-06 10:50:23 +00:00
|
|
|
return this.fromDb(model, this.cache[model][key]);
|
2011-10-03 13:36:43 +00:00
|
|
|
}.bind(this));
|
2012-01-19 13:44:11 +00:00
|
|
|
|
2013-06-24 22:21:59 +00:00
|
|
|
if (filter) {
|
2012-01-19 13:44:11 +00:00
|
|
|
// do we need some sorting?
|
|
|
|
if (filter.order) {
|
|
|
|
var orders = filter.order;
|
|
|
|
if (typeof filter.order === "string") {
|
|
|
|
orders = [filter.order];
|
|
|
|
}
|
2012-03-01 19:57:48 +00:00
|
|
|
orders.forEach(function (key, i) {
|
2012-09-12 11:26:20 +00:00
|
|
|
var reverse = 1;
|
2012-03-01 19:57:48 +00:00
|
|
|
var m = key.match(/\s+(A|DE)SC$/i);
|
|
|
|
if (m) {
|
|
|
|
key = key.replace(/\s+(A|DE)SC/i, '');
|
2013-03-26 19:33:40 +00:00
|
|
|
if (m[1].toLowerCase() === 'de') reverse = -1;
|
2012-01-19 13:44:11 +00:00
|
|
|
}
|
2012-09-12 11:26:20 +00:00
|
|
|
orders[i] = {"key": key, "reverse": reverse};
|
2012-01-19 13:44:11 +00:00
|
|
|
});
|
2012-09-12 11:26:20 +00:00
|
|
|
nodes = nodes.sort(sorting.bind(orders));
|
2012-01-19 13:44:11 +00:00
|
|
|
}
|
2013-06-24 22:21:59 +00:00
|
|
|
|
2013-06-24 23:38:50 +00:00
|
|
|
var nearFilter = geo.nearFilter(filter.where);
|
2013-06-24 22:21:59 +00:00
|
|
|
|
|
|
|
// geo sorting
|
|
|
|
if(nearFilter) {
|
2013-06-26 03:31:00 +00:00
|
|
|
nodes = geo.filter(nodes, nearFilter);
|
2013-06-24 22:21:59 +00:00
|
|
|
}
|
2013-06-26 03:31:00 +00:00
|
|
|
|
2013-03-26 20:50:13 +00:00
|
|
|
// do we need some filtration?
|
|
|
|
if (filter.where) {
|
|
|
|
nodes = nodes ? nodes.filter(applyFilter(filter)) : nodes;
|
|
|
|
}
|
2013-07-17 00:53:52 +00:00
|
|
|
|
|
|
|
// field selection
|
|
|
|
if(filter.fields) {
|
2013-08-28 05:32:01 +00:00
|
|
|
nodes = nodes.map(utils.selectFields(filter.fields));
|
2013-07-17 00:53:52 +00:00
|
|
|
}
|
2013-03-26 20:50:13 +00:00
|
|
|
|
2013-04-18 20:33:57 +00:00
|
|
|
// limit/skip
|
|
|
|
filter.skip = filter.skip || 0;
|
|
|
|
filter.limit = filter.limit || nodes.length;
|
|
|
|
nodes = nodes.slice(filter.skip, filter.skip + filter.limit);
|
2012-01-19 13:44:11 +00:00
|
|
|
}
|
|
|
|
|
2011-10-03 13:36:43 +00:00
|
|
|
process.nextTick(function () {
|
2013-03-27 13:10:13 +00:00
|
|
|
if (filter && filter.include) {
|
|
|
|
self._models[model].model.include(nodes, filter.include, callback);
|
|
|
|
} else {
|
|
|
|
callback(null, nodes);
|
|
|
|
}
|
2011-10-03 13:36:43 +00:00
|
|
|
});
|
2012-01-19 13:44:11 +00:00
|
|
|
|
2012-09-12 11:26:20 +00:00
|
|
|
function sorting(a, b) {
|
|
|
|
for (var i=0, l=this.length; i<l; i++) {
|
|
|
|
if (a[this[i].key] > b[this[i].key]) {
|
|
|
|
return 1*this[i].reverse;
|
|
|
|
} else if (a[this[i].key] < b[this[i].key]) {
|
|
|
|
return -1*this[i].reverse;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
2013-03-26 20:50:13 +00:00
|
|
|
}
|
2011-10-03 13:36:43 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
function applyFilter(filter) {
|
2011-11-04 07:30:25 +00:00
|
|
|
if (typeof filter.where === 'function') {
|
|
|
|
return filter.where;
|
2011-10-03 13:36:43 +00:00
|
|
|
}
|
2011-11-04 07:30:25 +00:00
|
|
|
var keys = Object.keys(filter.where);
|
2011-10-03 13:36:43 +00:00
|
|
|
return function (obj) {
|
|
|
|
var pass = true;
|
|
|
|
keys.forEach(function (key) {
|
2011-11-04 07:30:25 +00:00
|
|
|
if (!test(filter.where[key], obj[key])) {
|
2011-10-03 13:36:43 +00:00
|
|
|
pass = false;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return pass;
|
|
|
|
}
|
|
|
|
|
|
|
|
function test(example, value) {
|
|
|
|
if (typeof value === 'string' && example && example.constructor.name === 'RegExp') {
|
|
|
|
return value.match(example);
|
|
|
|
}
|
2013-03-24 21:28:08 +00:00
|
|
|
if (typeof example === 'undefined') return undefined;
|
|
|
|
if (typeof value === 'undefined') return undefined;
|
2013-03-27 13:10:13 +00:00
|
|
|
if (typeof example === 'object') {
|
2013-06-26 03:31:00 +00:00
|
|
|
// ignore geo near filter
|
|
|
|
if(example.near) return true;
|
|
|
|
|
2013-03-27 13:10:13 +00:00
|
|
|
if (example.inq) {
|
|
|
|
if (!value) return false;
|
|
|
|
for (var i = 0; i < example.inq.length; i += 1) {
|
|
|
|
if (example.inq[i] == value) return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2013-06-24 22:21:59 +00:00
|
|
|
|
2013-06-12 22:45:31 +00:00
|
|
|
if(isNum(example.gt) && example.gt < value) return true;
|
|
|
|
if(isNum(example.gte) && example.gte <= value) return true;
|
|
|
|
if(isNum(example.lt) && example.lt > value) return true;
|
|
|
|
if(isNum(example.lte) && example.lte >= value) return true;
|
2013-03-27 13:10:13 +00:00
|
|
|
}
|
2011-10-03 13:36:43 +00:00
|
|
|
// not strict equality
|
2012-09-09 13:17:08 +00:00
|
|
|
return (example !== null ? example.toString() : example) == (value !== null ? value.toString() : value);
|
2011-10-03 13:36:43 +00:00
|
|
|
}
|
2013-06-12 22:45:31 +00:00
|
|
|
|
|
|
|
function isNum(n) {
|
|
|
|
return typeof n === 'number';
|
|
|
|
}
|
2011-10-03 13:36:43 +00:00
|
|
|
}
|
|
|
|
|
2013-08-18 17:58:53 +00:00
|
|
|
Memory.prototype.destroyAll = function destroyAll(model, where, callback) {
|
|
|
|
if(!callback && 'function' === typeof where) {
|
|
|
|
callback = where;
|
|
|
|
where = undefined;
|
|
|
|
}
|
|
|
|
var cache = this.cache[model];
|
|
|
|
var filter = null;
|
|
|
|
if (where) {
|
|
|
|
filter = applyFilter({where: where});
|
|
|
|
}
|
|
|
|
Object.keys(cache).forEach(function (id) {
|
|
|
|
if(!filter || filter(this.fromDb(model, cache[id]))) {
|
|
|
|
delete cache[id];
|
|
|
|
}
|
2011-10-03 13:36:43 +00:00
|
|
|
}.bind(this));
|
2013-08-18 17:58:53 +00:00
|
|
|
if(!where) {
|
|
|
|
this.cache[model] = {};
|
|
|
|
}
|
2013-01-21 18:21:43 +00:00
|
|
|
process.nextTick(callback);
|
2011-10-03 13:36:43 +00:00
|
|
|
};
|
|
|
|
|
2012-01-30 13:27:26 +00:00
|
|
|
Memory.prototype.count = function count(model, callback, where) {
|
|
|
|
var cache = this.cache[model];
|
2013-08-28 05:32:01 +00:00
|
|
|
var data = Object.keys(cache);
|
2012-01-30 13:27:26 +00:00
|
|
|
if (where) {
|
2013-06-12 22:45:31 +00:00
|
|
|
var filter = {where: where};
|
|
|
|
data = data.map(function (id) {
|
|
|
|
return this.fromDb(model, cache[id]);
|
|
|
|
}.bind(this));
|
|
|
|
data = data.filter(applyFilter(filter));
|
2012-01-30 13:27:26 +00:00
|
|
|
}
|
2013-01-21 18:21:43 +00:00
|
|
|
process.nextTick(function () {
|
|
|
|
callback(null, data.length);
|
|
|
|
});
|
2011-10-03 13:36:43 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Memory.prototype.updateAttributes = function updateAttributes(model, id, data, cb) {
|
2013-06-21 21:56:21 +00:00
|
|
|
if(!id) {
|
|
|
|
var err = new Error('You must provide an id when updating attributes!');
|
|
|
|
if(cb) {
|
|
|
|
return cb(err);
|
|
|
|
} else {
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-28 05:32:01 +00:00
|
|
|
this.setIdValue(model, data, id);
|
2013-06-21 21:56:21 +00:00
|
|
|
|
|
|
|
var cachedModels = this.cache[model];
|
|
|
|
var modelAsString = cachedModels && this.cache[model][id];
|
|
|
|
var modelData = modelAsString && JSON.parse(modelAsString);
|
|
|
|
|
|
|
|
if(modelData) {
|
2013-06-21 22:01:40 +00:00
|
|
|
this.save(model, merge(modelData, data), cb);
|
2013-06-21 21:56:21 +00:00
|
|
|
} else {
|
|
|
|
cb(new Error('Could not update attributes. Object with id ' + id + ' does not exist!'));
|
|
|
|
}
|
2011-10-03 13:36:43 +00:00
|
|
|
};
|
|
|
|
|
2013-04-01 13:49:12 +00:00
|
|
|
Memory.prototype.transaction = function () {
|
|
|
|
return new Memory(this);
|
|
|
|
};
|
|
|
|
|
|
|
|
Memory.prototype.exec = function(callback) {
|
|
|
|
this.onTransactionExec();
|
|
|
|
setTimeout(callback, 50);
|
|
|
|
};
|
|
|
|
|
2013-06-26 03:31:00 +00:00
|
|
|
Memory.prototype.buildNearFilter = function (filter) {
|
|
|
|
// noop
|
|
|
|
}
|
|
|
|
|
2011-10-03 13:36:43 +00:00
|
|
|
function merge(base, update) {
|
2012-03-22 19:46:16 +00:00
|
|
|
if (!base) return update;
|
2011-10-03 13:36:43 +00:00
|
|
|
Object.keys(update).forEach(function (key) {
|
|
|
|
base[key] = update[key];
|
|
|
|
});
|
|
|
|
return base;
|
2013-06-26 03:31:00 +00:00
|
|
|
}
|