From cd80806d24faba96df42c932be21d23c68572b7b Mon Sep 17 00:00:00 2001 From: Louis Chatriot Date: Tue, 12 Jan 2016 17:24:36 +0100 Subject: [PATCH] CCan specify custom string comparison function --- lib/cursor.js | 2 +- lib/datastore.js | 4 ++ lib/model.js | 11 ++++-- test/cursor.test.js | 91 +++++++++++++++++++++++++++++---------------- test/model.test.js | 5 +++ 5 files changed, 75 insertions(+), 38 deletions(-) diff --git a/lib/cursor.js b/lib/cursor.js index 430a9b4..bd1c4e3 100755 --- a/lib/cursor.js +++ b/lib/cursor.js @@ -145,7 +145,7 @@ Cursor.prototype._exec = function(callback) { var criterion, compare, i; for (i = 0; i < criteria.length; i++) { criterion = criteria[i]; - compare = criterion.direction * model.compareThings(model.getDotValue(a, criterion.key), model.getDotValue(b, criterion.key)); + compare = criterion.direction * model.compareThings(model.getDotValue(a, criterion.key), model.getDotValue(b, criterion.key), self.db.compareStrings); if (compare !== 0) { return compare; } diff --git a/lib/datastore.js b/lib/datastore.js index c5856f1..1b3a70d 100755 --- a/lib/datastore.js +++ b/lib/datastore.js @@ -21,6 +21,7 @@ var customUtils = require('./customUtils') * @param {Function} options.onload Optional, if autoload is used this will be called after the load database with the error object as parameter. If you don't pass it the error will be thrown * @param {Function} options.afterSerialization/options.beforeDeserialization Optional, serialization hooks * @param {Number} options.corruptAlertThreshold Optional, threshold after which an alert is thrown if too much data is corrupt + * @param {Function} options.compareStrings Optional, string comparison function that overrides default for sorting */ function Datastore (options) { var filename; @@ -45,6 +46,9 @@ function Datastore (options) { this.filename = filename; } + // String comparison function + this.compareStrings = options.compareStrings; + // Persistence handling this.persistence = new Persistence({ db: this, nodeWebkitAppName: options.nodeWebkitAppName , afterSerialization: options.afterSerialization diff --git a/lib/model.js b/lib/model.js index 6d7b950..94b566e 100755 --- a/lib/model.js +++ b/lib/model.js @@ -183,9 +183,12 @@ function compareArrays (a, b) { * In the case of objects and arrays, we deep-compare * If two objects dont have the same type, the (arbitrary) type hierarchy is: undefined, null, number, strings, boolean, dates, arrays, objects * Return -1 if a < b, 1 if a > b and 0 if a = b (note that equality here is NOT the same as defined in areThingsEqual!) + * + * @param {Function} _compareStrings String comparing function, returning -1, 0 or 1, overriding default string comparison (useful for languages with accented letters) */ -function compareThings (a, b) { - var aKeys, bKeys, comp, i; +function compareThings (a, b, _compareStrings) { + var aKeys, bKeys, comp, i + , compareStrings = _compareStrings || compareNSB; // undefined if (a === undefined) { return b === undefined ? 0 : -1; } @@ -200,8 +203,8 @@ function compareThings (a, b) { if (typeof b === 'number') { return typeof a === 'number' ? compareNSB(a, b) : 1; } // Strings - if (typeof a === 'string') { return typeof b === 'string' ? compareNSB(a, b) : -1; } - if (typeof b === 'string') { return typeof a === 'string' ? compareNSB(a, b) : 1; } + if (typeof a === 'string') { return typeof b === 'string' ? compareStrings(a, b) : -1; } + if (typeof b === 'string') { return typeof a === 'string' ? compareStrings(a, b) : 1; } // Booleans if (typeof a === 'boolean') { return typeof b === 'boolean' ? compareNSB(a, b) : -1; } diff --git a/test/cursor.test.js b/test/cursor.test.js index 103161f..f5daca5 100755 --- a/test/cursor.test.js +++ b/test/cursor.test.js @@ -177,7 +177,7 @@ describe('Cursor', function () { for (i = 0; i < docs.length - 1; i += 1) { assert(docs[i].age < docs[i + 1].age) } - + cursor.sort({ age: -1 }); cursor.exec(function (err, docs) { assert.isNull(err); @@ -187,29 +187,54 @@ describe('Cursor', function () { } done(); - }); + }); }); }); - + + it("Sorting strings with custom string comparison function", function (done) { + var db = new Datastore({ inMemoryOnly: true, autoload: true + , compareStrings: function (a, b) { return a.length - b.length; } + }); + + db.insert({ name: 'alpha' }); + db.insert({ name: 'charlie' }); + db.insert({ name: 'zulu' }); + + db.find({}).sort({ name: 1 }).exec(function (err, docs) { + _.pluck(docs, 'name')[0].should.equal('zulu'); + _.pluck(docs, 'name')[1].should.equal('alpha'); + _.pluck(docs, 'name')[2].should.equal('charlie'); + + delete db.compareStrings; + db.find({}).sort({ name: 1 }).exec(function (err, docs) { + _.pluck(docs, 'name')[0].should.equal('alpha'); + _.pluck(docs, 'name')[1].should.equal('charlie'); + _.pluck(docs, 'name')[2].should.equal('zulu'); + + done(); + }); + }); + }); + it('With an empty collection', function (done) { async.waterfall([ function (cb) { d.remove({}, { multi: true }, function(err) { return cb(err); }) } , function (cb) { - var cursor = new Cursor(d); - cursor.sort({ age: 1 }); - cursor.exec(function (err, docs) { - assert.isNull(err); - docs.length.should.equal(0); - cb(); - }); - } + var cursor = new Cursor(d); + cursor.sort({ age: 1 }); + cursor.exec(function (err, docs) { + assert.isNull(err); + docs.length.should.equal(0); + cb(); + }); + } ], done); }); - + it('Ability to chain sorting and exec', function (done) { - var i; + var i; async.waterfall([ function (cb) { var cursor = new Cursor(d); @@ -223,21 +248,21 @@ describe('Cursor', function () { }); } , function (cb) { - var cursor = new Cursor(d); - cursor.sort({ age: -1 }).exec(function (err, docs) { - assert.isNull(err); - // Results are in descending order - for (i = 0; i < docs.length - 1; i += 1) { - assert(docs[i].age > docs[i + 1].age) - } - cb(); - }); - } + var cursor = new Cursor(d); + cursor.sort({ age: -1 }).exec(function (err, docs) { + assert.isNull(err); + // Results are in descending order + for (i = 0; i < docs.length - 1; i += 1) { + assert(docs[i].age > docs[i + 1].age) + } + cb(); + }); + } ], done); }); it('Using limit and sort', function (done) { - var i; + var i; async.waterfall([ function (cb) { var cursor = new Cursor(d); @@ -251,15 +276,15 @@ describe('Cursor', function () { }); } , function (cb) { - var cursor = new Cursor(d); - cursor.sort({ age: -1 }).limit(2).exec(function (err, docs) { - assert.isNull(err); - docs.length.should.equal(2); - docs[0].age.should.equal(89); - docs[1].age.should.equal(57); - cb(); - }); - } + var cursor = new Cursor(d); + cursor.sort({ age: -1 }).limit(2).exec(function (err, docs) { + assert.isNull(err); + docs.length.should.equal(2); + docs[0].age.should.equal(89); + docs[1].age.should.equal(57); + cb(); + }); + } ], done); }); diff --git a/test/model.test.js b/test/model.test.js index 7e6a3cd..651b2bb 100755 --- a/test/model.test.js +++ b/test/model.test.js @@ -813,6 +813,11 @@ describe('Model', function () { model.compareThings({ a: 42, b: 312, c: 54 }, { b: 313, a: 42 }).should.equal(-1); }); + it('Can specify custom string comparison function', function () { + model.compareThings('hello', 'bloup', function (a, b) { return a < b ? -1 : 1; }).should.equal(1); + model.compareThings('hello', 'bloup', function (a, b) { return a > b ? -1 : 1; }).should.equal(-1); + }); + }); // ==== End of 'Comparing things' ==== //