Skip to content

Commit

Permalink
Added support for custom ID generation
Browse files Browse the repository at this point in the history
Also modified the ID generation integrity check to work via an iterative loop rather than via recursion

Fixes #10
Closes louischatriot/nedb#212
  • Loading branch information
JamesMGreene committed Sep 5, 2017
1 parent 4f74dbd commit 11c12c3
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 9 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ You can use NestDB as an in-memory only datastore or as a persistent datastore.
* `timestampData` (optional, defaults to `false`): timestamp the insertion and last update of all documents, with the fields `createdAt` and `updatedAt`. User-specified values override automatic generation, usually useful for testing.
* `autoload` (optional, defaults to `false`): if used, the datastore will automatically be loaded from the datafile upon creation (you don't need to call `load`). Any command issued before load is finished is buffered and will be executed when load is done.
* `onload` (optional): if you use autoloading, this is the handler called after the `load`. It takes one `error` argument. If you use autoloading without specifying this handler, and an error happens during load, an error will be thrown.
* `idGenerator` (optional): if set, this function will be used for generating IDs. It takes no arguments and should return a unique string.
* `afterSerialization` (optional): hook you can use to transform data after it was serialized and before it is written to disk. Can be used for example to encrypt data before writing datastore to disk. This function takes a string as parameter (one line of an NestDB data file) and outputs the transformed string, **which must absolutely not contain a `\n` character** (or data will be lost).
* `beforeDeserialization` (optional): inverse of `afterSerialization`. Make sure to include both and not just one or you risk data loss. For the same reason, make sure both functions are inverses of one another. Some failsafe mechanisms are in place to prevent data loss if you misuse the serialization hooks: NestDB checks that never one is declared without the other, and checks that they are reverse of one another by testing on random strings of various lengths. In addition, if too much data is detected as corrupt, NestDB will refuse to start as it could mean you're not using the deserialization hook corresponding to the serialization hook used before (see below).
* `corruptAlertThreshold` (optional): between 0 (0%) and 1 (100%), defaults to 0.1 (10%). NestDB will refuse to start if more than this percentage of the datafile is corrupt. 0 means you don't tolerate any corruption, 1 means you don't care.
Expand Down
25 changes: 21 additions & 4 deletions browser-version/out/nestdb.js
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,9 @@ var util = require('util')
* @param {Boolean} options.inMemoryOnly Optional, defaults to false
* @param {Boolean} options.autoload Optional, defaults to false
* @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 {Function} options.idGenerator Optional, if set, this function will be used for generating IDs. It takes no arguments and should return a unique string.
* @param {Function} options.afterSerialization Optional, serialization hook. Must be symmetrical to `options.beforeDeserialization`.
* @param {Function} options.beforeDeserialization Optional, deserialization hook. Must be symmetrical to options.afterSerialization`.
* @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
* @param {Object} options.storage Optional, custom storage engine for the database files. Must implement all methods exported by the standard "storage" module included in NestDB
Expand Down Expand Up @@ -370,6 +372,9 @@ function Datastore(options) {
this.inMemoryOnly = options.inMemoryOnly || false;
this.autoload = options.autoload || false;
this.timestampData = options.timestampData || false;
if (typeof options.idGenerator === 'function') {
this._idGenerator = options.idGenerator;
}

// Determine whether in memory or persistent
if (!filename || typeof filename !== 'string' || filename.length === 0) {
Expand Down Expand Up @@ -750,15 +755,27 @@ Datastore.prototype._insert = function (newDoc, cb) {
};


/**
* Default implementation for generating a unique _id
* @protected
*/
Datastore.prototype._idGenerator = function () {
return customUtils.uid(16);
};


/**
* Create a new _id that's not already in use
*/
Datastore.prototype.createNewId = function () {
var tentativeId = customUtils.uid(16);
var tentativeId;

// Try as many times as needed to get an unused _id. As explained in customUtils, the probability of this ever happening is extremely small, so this is O(1)
if (this.indexes._id.getMatching(tentativeId).length > 0) {
tentativeId = this.createNewId();
do {
tentativeId = this._idGenerator();
}
while (this.indexes._id.getMatching(tentativeId).length > 0);

return tentativeId;
};

Expand Down
2 changes: 1 addition & 1 deletion browser-version/out/nestdb.min.js

Large diffs are not rendered by default.

25 changes: 21 additions & 4 deletions lib/datastore.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ var util = require('util')
* @param {Boolean} options.inMemoryOnly Optional, defaults to false
* @param {Boolean} options.autoload Optional, defaults to false
* @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 {Function} options.idGenerator Optional, if set, this function will be used for generating IDs. It takes no arguments and should return a unique string.
* @param {Function} options.afterSerialization Optional, serialization hook. Must be symmetrical to `options.beforeDeserialization`.
* @param {Function} options.beforeDeserialization Optional, deserialization hook. Must be symmetrical to options.afterSerialization`.
* @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
* @param {Object} options.storage Optional, custom storage engine for the database files. Must implement all methods exported by the standard "storage" module included in NestDB
Expand Down Expand Up @@ -79,6 +81,9 @@ function Datastore(options) {
this.inMemoryOnly = options.inMemoryOnly || false;
this.autoload = options.autoload || false;
this.timestampData = options.timestampData || false;
if (typeof options.idGenerator === 'function') {
this._idGenerator = options.idGenerator;
}

// Determine whether in memory or persistent
if (!filename || typeof filename !== 'string' || filename.length === 0) {
Expand Down Expand Up @@ -459,15 +464,27 @@ Datastore.prototype._insert = function (newDoc, cb) {
};


/**
* Default implementation for generating a unique _id
* @protected
*/
Datastore.prototype._idGenerator = function () {
return customUtils.uid(16);
};


/**
* Create a new _id that's not already in use
*/
Datastore.prototype.createNewId = function () {
var tentativeId = customUtils.uid(16);
var tentativeId;

// Try as many times as needed to get an unused _id. As explained in customUtils, the probability of this ever happening is extremely small, so this is O(1)
if (this.indexes._id.getMatching(tentativeId).length > 0) {
tentativeId = this.createNewId();
do {
tentativeId = this._idGenerator();
}
while (this.indexes._id.getMatching(tentativeId).length > 0);

return tentativeId;
};

Expand Down
22 changes: 22 additions & 0 deletions test/datastore.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,28 @@ describe('Datastore', function () {
});
});

it('Custom ID Generator', function (done) {
var lastId
, db = new Datastore({ idGenerator: function() { lastId = '' + Date.now(); return lastId; } })
;

db.insert({ foo: 'bar' }, function (err1, doc1) {
assert.isNull(err1);
doc1._id.should.equal(lastId);
doc1._id.should.match(/^\d+$/);

// No delay is needed here since, if the ID already exists, it will just keep generating a new one until it differs
db.insert({ foo: 'baz' }, function (err2, doc2) {
assert.isNull(err2);
doc2._id.should.equal(lastId);
doc2._id.should.match(/^\d+$/);
doc2._id.should.not.equal(doc1._id);
parseInt(doc2._id, 10).should.be.greaterThan(parseInt(doc1._id, 10));
done();
});
});
});

describe('Autoloading', function () {

it('Can autoload a database and query it right away', function (done) {
Expand Down

0 comments on commit 11c12c3

Please sign in to comment.