Skip to content

Commit

Permalink
Merge pull request #582 from ChristopherTotty/master
Browse files Browse the repository at this point in the history
#578: Add optional flag for Changes API to limit update operation out…
  • Loading branch information
obeliskos authored Jul 19, 2017
2 parents 54da6d2 + eda80c5 commit b43c8ef
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 14 deletions.
39 changes: 39 additions & 0 deletions spec/generic/changesApi.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,43 @@ describe('changesApi', function () {

expect(users.getChanges().length).toEqual(0);
});

it('works with delta mode', function () {
var db = new loki(),
options = {
asyncListeners: false,
disableChangesApi: false,
disableDeltaChangesApi: false
},
items = db.addCollection('items', options );

// Add some documents to the collection
items.insert({ name : 'mjolnir', owner: 'thor', maker: { name: 'dwarves', count: 1 } });
items.insert({ name : 'gungnir', owner: 'odin', maker: { name: 'elves', count: 1 } });
items.insert({ name : 'tyrfing', owner: 'Svafrlami', maker: { name: 'dwarves', count: 1 } });
items.insert({ name : 'draupnir', owner: 'odin', maker: { name: 'elves', count: 1 } });

// Find and update an existing document
var tyrfing = items.findOne({'name': 'tyrfing'});
tyrfing.owner = 'arngrim';
items.update(tyrfing);
tyrfing.maker.count = 4;
items.update(tyrfing);

var changes = db.serializeChanges(['items']);
changes = JSON.parse(changes);

expect(changes.length).toEqual(6);

var firstUpdate = changes[4];
expect(firstUpdate.operation).toEqual('U');
expect(firstUpdate.obj.owner).toEqual('arngrim');
expect(firstUpdate.obj.name).toBeUndefined();

var secondUpdate = changes[5];
expect(secondUpdate.operation).toEqual('U');
expect(secondUpdate.obj.owner).toBeUndefined();
expect(secondUpdate.obj.maker).toEqual({ count: 4 });

});
});
80 changes: 66 additions & 14 deletions src/lokijs.js
Original file line number Diff line number Diff line change
Expand Up @@ -658,16 +658,17 @@
* @param {object=} data - optional object passed with the event
* @memberof LokiEventEmitter
*/
LokiEventEmitter.prototype.emit = function (eventName, data) {
LokiEventEmitter.prototype.emit = function (eventName) {
var self = this;
var selfArgs = Array.prototype.slice.call(arguments, 1);
if (eventName && this.events[eventName]) {
this.events[eventName].forEach(function (listener) {
if (self.asyncListeners) {
setTimeout(function () {
listener(data);
listener.apply(self, selfArgs);
}, 1);
} else {
listener(data);
listener.apply(self, selfArgs);
}

});
Expand Down Expand Up @@ -1537,7 +1538,7 @@
for (i; i < len; i += 1) {
coll = dbObject.collections[i];

copyColl = this.addCollection(coll.name, { disableChangesApi: coll.disableChangesApi });
copyColl = this.addCollection(coll.name, { disableChangesApi: coll.disableChangesApi, disableDeltaChangesApi: coll.disableDeltaChangesApi });

copyColl.adaptiveBinaryIndices = coll.hasOwnProperty('adaptiveBinaryIndices')?(coll.adaptiveBinaryIndices === true): false;
copyColl.transactional = coll.transactional;
Expand Down Expand Up @@ -3350,6 +3351,12 @@
options.forceClones = true;
options.forceCloneMethod = options.forceCloneMethod || 'shallow';
}

// if collection has delta changes active, then force clones and use 'parse-stringify' for effective change tracking of nested objects
if (!this.collection.disableDeltaChangesApi) {
options.forceClones = true;
options.forceCloneMethod = 'parse-stringify';
}

// if this has no filters applied, just return collection.data
if (!this.filterInitialized) {
Expand Down Expand Up @@ -4310,6 +4317,7 @@
* @param {boolean} [options.adaptiveBinaryIndices=true] - collection indices will be actively rebuilt rather than lazily
* @param {boolean} [options.asyncListeners=false] - whether listeners are invoked asynchronously
* @param {boolean} [options.disableChangesApi=true] - set to false to enable Changes API
* @param {boolean} [options.disableDeltaChangesApi=true] - set to false to enable Delta Changes API (requires Changes API, forces cloning)
* @param {boolean} [options.autoupdate=false] - use Object.observe to update objects automatically
* @param {boolean} [options.clone=false] - specify whether inserts and queries clone to/from user
* @param {boolean} [options.serializableIndices=true[]] - converts date values on binary indexed properties to epoch time
Expand Down Expand Up @@ -4391,6 +4399,10 @@
// disable track changes
this.disableChangesApi = options.hasOwnProperty('disableChangesApi') ? options.disableChangesApi : true;

// disable delta update object style on changes
this.disableDeltaChangesApi = options.hasOwnProperty('disableDeltaChangesApi') ? options.disableDeltaChangesApi : true;
if (this.disableChangesApi) { this.disableDeltaChangesApi = true; }

// option to observe objects and update them automatically, ignored if Object.observe is not supported
this.autoupdate = options.hasOwnProperty('autoupdate') ? options.autoupdate : false;

Expand Down Expand Up @@ -4476,14 +4488,53 @@
* This method creates a clone of the current status of an object and associates operation and collection name,
* so the parent db can aggregate and generate a changes object for the entire db
*/
function createChange(name, op, obj) {
function createChange(name, op, obj, old) {
self.changes.push({
name: name,
operation: op,
obj: JSON.parse(JSON.stringify(obj))
obj: op == 'U' && !self.disableDeltaChangesApi ? getChangeDelta(obj, old) : JSON.parse(JSON.stringify(obj))
});
}

//Compare changed object (which is a forced clone) with existing object and return the delta
function getChangeDelta(obj, old) {
if (old) {
return getObjectDelta(old, obj);
}
else {
return JSON.parse(JSON.stringify(obj));
}
}

this.getChangeDelta = getChangeDelta;

function getObjectDelta(oldObject, newObject) {
var propertyNames = newObject !== null && typeof newObject === 'object' ? Object.keys(newObject) : null;
if (propertyNames && propertyNames.length && ['string', 'boolean', 'number'].indexOf(typeof(newObject)) < 0) {
var delta = {};
for (var i = 0; i < propertyNames.length; i++) {
var propertyName = propertyNames[i];
if (newObject.hasOwnProperty(propertyName)) {
if (!oldObject.hasOwnProperty(propertyName) || self.uniqueNames.indexOf(propertyName) >= 0 || propertyName == '$loki' || propertyName == 'meta') {
delta[propertyName] = newObject[propertyName];
}
else {
var propertyDelta = getObjectDelta(oldObject[propertyName], newObject[propertyName]);
if (typeof propertyDelta !== "undefined" && propertyDelta != {}) {
delta[propertyName] = propertyDelta;
}
}
}
}
return Object.keys(delta).length === 0 ? undefined : delta;
}
else {
return oldObject === newObject ? undefined : newObject;
}
}

this.getObjectDelta = getObjectDelta;

// clear all the changes
function flushChanges() {
self.changes = [];
Expand Down Expand Up @@ -4542,18 +4593,18 @@
createChange(self.name, 'I', obj);
}

function createUpdateChange(obj) {
createChange(self.name, 'U', obj);
function createUpdateChange(obj, old) {
createChange(self.name, 'U', obj, old);
}

function insertMetaWithChange(obj) {
insertMeta(obj);
createInsertChange(obj);
}

function updateMetaWithChange(obj) {
function updateMetaWithChange(obj, old) {
updateMeta(obj);
createUpdateChange(obj);
createUpdateChange(obj, old);
}


Expand All @@ -4569,6 +4620,7 @@

this.setChangesApi = function (enabled) {
self.disableChangesApi = !enabled;
if (!enabled) { self.disableDeltaChangesApi = false; }
setHandlers();
};
/**
Expand All @@ -4578,8 +4630,8 @@
insertHandler(obj);
});

this.on('update', function updateCallback(obj) {
updateHandler(obj);
this.on('update', function updateCallback(obj, old) {
updateHandler(obj, old);
});

this.on('delete', function deleteCallback(obj) {
Expand Down Expand Up @@ -5154,7 +5206,7 @@
position = arr[1]; // position in data array

// if configured to clone, do so now... otherwise just use same obj reference
newInternal = this.cloneObjects ? clone(doc, this.cloneMethod) : doc;
newInternal = this.cloneObjects || !this.disableDeltaChangesApi ? clone(doc, this.cloneMethod) : doc;

this.emit('pre-update', doc);

Expand Down Expand Up @@ -5193,7 +5245,7 @@
this.commit();
this.dirty = true; // for autosave scenarios

this.emit('update', doc, this.cloneObjects ? clone(oldInternal, this.cloneMethod) : null);
this.emit('update', doc, this.cloneObjects || !this.disableDeltaChangesApi ? clone(oldInternal, this.cloneMethod) : null);
return doc;
} catch (err) {
this.rollback();
Expand Down

0 comments on commit b43c8ef

Please sign in to comment.