diff --git a/lib/abstract-class.js b/lib/abstract-class.js index 6ad93bee..d5bc85c4 100644 --- a/lib/abstract-class.js +++ b/lib/abstract-class.js @@ -243,8 +243,12 @@ AbstractClass.destroyAll = function destroyAll(cb) { }.bind(this)); }; -AbstractClass.count = function (cb) { - this.schema.adapter.count(this.modelName, cb); +AbstractClass.count = function (where, cb) { + if (typeof where === 'function') { + cb = where; + where = null; + } + this.schema.adapter.count(this.modelName, cb, where); }; AbstractClass.toString = function () { diff --git a/lib/adapters/memory.js b/lib/adapters/memory.js index 2e040c0b..5372a752 100644 --- a/lib/adapters/memory.js +++ b/lib/adapters/memory.js @@ -119,8 +119,21 @@ Memory.prototype.destroyAll = function destroyAll(model, callback) { callback(); }; -Memory.prototype.count = function count(model, callback) { - callback(null, Object.keys(this.cache[model]).length); +Memory.prototype.count = function count(model, callback, where) { + var cache = this.cache[model]; + var data = Object.keys(cache) + if (where) { + data = data.filter(function (id) { + var ok = true; + Object.keys(where).forEach(function (key) { + if (cache[id][key] != where[key]) { + ok = false; + } + }); + return ok; + }); + } + callback(null, data.length); }; Memory.prototype.updateAttributes = function updateAttributes(model, id, data, cb) { diff --git a/lib/adapters/mysql.js b/lib/adapters/mysql.js index f3476b3a..c2d1a88a 100644 --- a/lib/adapters/mysql.js +++ b/lib/adapters/mysql.js @@ -15,7 +15,8 @@ exports.initialize = function initializeSchema(schema, callback) { }); schema.adapter = new MySQL(schema.client); - process.nextTick(callback); + schema.client.query('SET TIME_ZONE = "+04:00"', callback); + // process.nextTick(callback); }; function MySQL(client) { @@ -77,6 +78,19 @@ MySQL.prototype.toFields = function (model, data) { return fields.join(','); }; +function dateToMysql(val) { + return val.getUTCFullYear() + '-' + + fillZeros(val.getUTCMonth() + 1) + '-' + + fillZeros(val.getUTCDate()) + ' ' + + fillZeros(val.getUTCHours()) + ':' + + fillZeros(val.getUTCMinutes()) + ':' + + fillZeros(val.getUTCSeconds()); + + function fillZeros(v) { + return v < 10 ? '0' + v : v; + } +} + MySQL.prototype.toDatabase = function (prop, val) { if (prop.type.name === 'Number') return val; if (val === null) return 'NULL'; @@ -85,15 +99,7 @@ MySQL.prototype.toDatabase = function (prop, val) { if (!val.toUTCString) { val = new Date(val); } - val = [ - val.getFullYear(), - val.getMonth() + 1, - val.getDate(), - val.getHours(), - val.getMinutes(), - val.getSeconds() - ].join('-'); - return '"' + val + '"'; + return '"' + dateToMysql(val) + '"'; } if (prop.type.name == "Boolean") return val ? 1 : 0; return this.client.escape(val.toString()); @@ -105,7 +111,9 @@ MySQL.prototype.fromDatabase = function (model, data) { Object.keys(data).forEach(function (key) { var val = data[key]; if (props[key]) { - // if (props[key]) + if (props[key].type.name === 'Date') { + val = new Date(val.toString().replace(/GMT.*$/, 'GMT')); + } } data[key] = val; }); @@ -165,7 +173,9 @@ MySQL.prototype.all = function all(model, filter, callback) { if (err) { return callback(err, []); } - callback(null, data); + callback(null, data.map(function (obj) { + return self.fromDatabase(model, obj); + })); }.bind(this)); return sql; @@ -203,10 +213,26 @@ MySQL.prototype.destroyAll = function destroyAll(model, callback) { }.bind(this)); }; -MySQL.prototype.count = function count(model, callback) { - this.query('SELECT count(*) as cnt FROM ' + model, function (err, res) { +MySQL.prototype.count = function count(model, callback, where) { + var self = this; + var props = this._models[model].properties; + + this.query('SELECT count(*) as cnt FROM ' + model + buildWhere(where), function (err, res) { callback(err, err ? null : res[0].cnt); }); + + function buildWhere(conds) { + var cs = []; + Object.keys(conds || {}).forEach(function (key) { + var keyEscaped = '`' + key.replace(/\./g, '`.`') + '`' + if (conds[key] === null) { + cs.push(keyEscaped + ' IS NULL'); + } else { + cs.push(keyEscaped + ' = ' + self.toDatabase(props[key], conds[key])); + } + }); + return cs.length ? ' WHERE ' + cs.join(' AND ') : ''; + } }; MySQL.prototype.updateAttributes = function updateAttrs(model, id, data, cb) { diff --git a/lib/adapters/neo4j.js b/lib/adapters/neo4j.js index 18dff009..22be99f8 100644 --- a/lib/adapters/neo4j.js +++ b/lib/adapters/neo4j.js @@ -254,10 +254,21 @@ Neo4j.prototype.destroy = function destroy(model, id, callback) { Neo4j.prototype.all = function all(model, filter, callback) { this.client.queryNodeIndex(model, 'id:*', function (err, nodes) { if (nodes) { - nodes = nodes.map(function (s) { s.data.id = s.id; return s.data }); + nodes = nodes.map(function (obj) { + obj.data.id = obj.id; + return this.readFromDb(model, obj.data); + }.bind(this)); } - callback(err, filter && nodes ? nodes.filter(applyFilter(filter)) : nodes); - }); + if (filter) { + nodes = nodes ? nodes.filter(applyFilter(filter)) : nodes; + if (filter.order) { + nodes = nodes.sort(function (a, b) { + return a[filter.order] > b[filter.order]; + }); + } + } + callback(err, nodes); + }.bind(this)); }; Neo4j.prototype.allNodes = function all(model, callback) { @@ -285,6 +296,10 @@ function applyFilter(filter) { if (typeof value === 'string' && example && example.constructor.name === 'RegExp') { return value.match(example); } + if (typeof value === 'object' && value.constructor.name === 'Date' && typeof example === 'object' && example.constructor.name === 'Date') { + return example.toString() === value.toString(); + } + console.log(example,'==', value, example == value); // not strict equality return example == value; } @@ -308,8 +323,8 @@ Neo4j.prototype.destroyAll = function destroyAll(model, callback) { } }; -Neo4j.prototype.count = function count(model, callback) { - this.all(model, null, function (err, collection) { +Neo4j.prototype.count = function count(model, callback, conds) { + this.all(model, {where: conds}, function (err, collection) { callback(err, collection ? collection.length : 0); }); }; diff --git a/lib/adapters/postgres.js b/lib/adapters/postgres.js index ae163710..c92056ed 100644 --- a/lib/adapters/postgres.js +++ b/lib/adapters/postgres.js @@ -93,6 +93,22 @@ PG.prototype.toFields = function (model, data, forCreate) { } }; +function dateToPostgres(val) { + return [ + val.getUTCFullYear(), + fz(val.getUTCMonth() + 1), + fz(val.getUTCDate()) + ].join('-') + ' ' + [ + fz(val.getUTCHours()), + fz(val.getUTCMinutes()), + fz(val.getUTCSeconds()) + ].join(':'); + + function fz(v) { + return v < 10 ? '0' + v : v; + } +} + PG.prototype.toDatabase = function (prop, val) { if (val === null) return 'NULL'; if (prop.type.name === 'Number') return val; @@ -101,18 +117,10 @@ PG.prototype.toDatabase = function (prop, val) { if (!val.toUTCString) { val = new Date(val); } - val = [ - val.getUTCFullYear(), - val.getUTCMonth() + 1, - val.getUTCDate() - ].join('-') + ' ' + [ - val.getUTCHours(), - val.getUTCMinutes(), - val.getUTCSeconds() - ].join(':'); - return escape(val); + return escape(dateToPostgres(val)); } return escape(val.toString()); + }; PG.prototype.fromDatabase = function (model, data) { @@ -161,7 +169,7 @@ PG.prototype.all = function all(model, filter, callback) { if (err) { return callback(err, []); } - callback(err, filter ? data.items.filter(applyFilter(filter)) : data.items); + callback(err, filter && filter.where ? data.items.filter(applyFilter(filter)) : data.items); }.bind(this)); }; @@ -171,7 +179,7 @@ PG.prototype.toFilter = function (model, filter) { } if (!filter) return ''; var props = this._models[model].properties; - var out=''; + var out = ''; if (filter.where) { var fields = []; Object.keys(filter.where).forEach(function (key) { @@ -188,9 +196,18 @@ PG.prototype.toFilter = function (model, filter) { } }.bind(this)); if (fields.length) { - out += ' where ' + fields.join(' AND '); + out += ' WHERE ' + fields.join(' AND '); } } + + if (filter.order) { + out += ' ORDER BY ' + filter.order; + } + + if (filter.limit) { + out += ' LIMIT ' + filter.limit + ' ' + (filter.offset || ''); + } + return out; }; @@ -228,11 +245,26 @@ PG.prototype.destroyAll = function destroyAll(model, callback) { }.bind(this)); }; -PG.prototype.count = function count(model, callback) { - this.query('SELECT count(*) as cnt FROM "' + model + '"', function (err, res) { +PG.prototype.count = function count(model, callback, where) { + var self = this; + var props = this._models[model].properties; + + this.query('SELECT count(*) as cnt FROM "' + model + '"' + buildWhere(where), function (err, res) { if (err) return callback(err); callback(err, res && res.items[0] && res.items[0].cnt); }); + + function buildWhere(conds) { + var cs = []; + Object.keys(conds || {}).forEach(function (key) { + if (conds[key] === null) { + cs.push(key + ' IS NULL'); + } else { + cs.push(key + ' = ' + self.toDatabase(props[key], conds[key])); + } + }); + return cs.length ? ' WHERE ' + cs.join(' AND ') : ''; + } }; PG.prototype.updateAttributes = function updateAttrs(model, id, data, cb) { diff --git a/lib/adapters/redis.js b/lib/adapters/redis.js index bdd249f9..09f5572a 100644 --- a/lib/adapters/redis.js +++ b/lib/adapters/redis.js @@ -366,13 +366,19 @@ BridgeToRedis.prototype.destroyAll = function destroyAll(model, callback) { }.bind(this)); }; -BridgeToRedis.prototype.count = function count(model, callback) { +BridgeToRedis.prototype.count = function count(model, callback, where) { var keysQuery = model + ':*'; var t1 = Date.now(); - this.client.keys(keysQuery, function (err, keys) { - this.log('KEYS ' + keysQuery, t1); - callback(err, err ? null : keys.length); - }.bind(this)); + if (where && Object.keys(where).length) { + this.all(model, {where: where}, function (err, data) { + callback(err, err ? null : data.length); + }); + } else { + this.client.keys(keysQuery, function (err, keys) { + this.log('KEYS ' + keysQuery, t1); + callback(err, err ? null : keys.length); + }.bind(this)); + } }; BridgeToRedis.prototype.updateAttributes = function updateAttrs(model, id, data, cb) { diff --git a/package.json b/package.json index 9932ec03..73a5d00c 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "main": "index.js", "scripts": { - "test": "ONLY=memory ./support/nodeunit/bin/nodeunit test/*_test.* && ONLY=redis nodeunit test/common_test.js && ONLY=mysql nodeunit test/common_test.js" + "test": "ONLY=memory ./support/nodeunit/bin/nodeunit test/*_test.* && ONLY=redis nodeunit test/common_test.js && ONLY=mysql nodeunit test/common_test.js && ONLY=postgres nodeunit test/common_test.js" }, "engines": [ "node >= 0.4.0" diff --git a/test/common_test.js b/test/common_test.js index fa6e1b03..150f3f81 100644 --- a/test/common_test.js +++ b/test/common_test.js @@ -297,12 +297,15 @@ function testOrm(schema) { }); }); - var countOfposts; + var countOfposts, countOfpostsFiltered; it('should fetch collection', function (test) { Post.all(function (err, posts) { countOfposts = posts.length; test.ok(countOfposts > 0); test.ok(posts[0] instanceof Post); + countOfpostsFiltered = posts.filter(function (p) { + return p.title === 'title'; + }).length; test.done(); }); }); @@ -310,7 +313,10 @@ function testOrm(schema) { it('should fetch count of records in collection', function (test) { Post.count(function (err, count) { test.equal(countOfposts, count); - test.done(); + Post.count({title: 'title'}, function (err, count) { + test.equal(countOfpostsFiltered, count); + test.done(); + }); }); }); @@ -481,6 +487,7 @@ function testOrm(schema) { if (err) console.log(err); test.equal(posts.length, 5); dates.sort(numerically).forEach(function (d, i) { + // fix inappropriated tz convert test.equal(posts[i].date.toString(), d.toString()); }); finished();