Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redis oplog compatibility #711

Open
wants to merge 17 commits into
base: v2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .versions
4 changes: 4 additions & 0 deletions lib/core/class.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import _has from "lodash/has";
import _includes from "lodash/includes";
import _intersection from "lodash/intersection";
import _isNumber from "lodash/isNumber";
import _isUndefined from "lodash/isUndefined";
import config from "./config";
import throwParseError from "../modules/core/utils/throw_parse_error";
import cloneDefinition from "../modules/core/utils/cloneDefinition";
Expand Down Expand Up @@ -75,6 +76,9 @@ class Class {
// Set values in a document.
_each(Class.getFieldsNames(), fieldName => {
doc.set(fieldName, rawDoc[fieldName], options);
if (_isUndefined(doc.get(fieldName))) {
delete doc[fieldName];
}
});

// Trigger the "afterInit" event handlers.
Expand Down
10 changes: 10 additions & 0 deletions lib/core/ejson.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@ import Event from '../modules/events/event.js';

EJSON.addType('Astronomy', function(json) {
let Class = AstroClass.get(json.class);

// This sometimes happens on the client if a document arrives from a sub before all
// classes have been initialized in the app.
// One such sub can be the null-sub for user document, which sometimes can arrive before the
// app is fully ready.
if (!Class) {
console.warn(`[Astronomy] a document of class ${json.class} arrived before class was initialized`);
return EJSON.parse(json.values);
}

let doc = new Class();

// Trigger the "fromJSONValue" event handlers.
Expand Down
4 changes: 3 additions & 1 deletion lib/modules/storage/class_prototype_methods/remove.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ function remove(args = {}, callback) {
}
// Get variables from the first argument.
let {
simulation = true
simulation = true,
...removeOptions
} = args;

// If we are dealing with a remote collection and we are not on the server.
Expand Down Expand Up @@ -57,6 +58,7 @@ function remove(args = {}, callback) {
let methodArgs = {
doc,
simulation,
options: removeOptions,
trusted: true
};
let result = documentRemove(methodArgs);
Expand Down
5 changes: 3 additions & 2 deletions lib/modules/storage/class_prototype_methods/save.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,15 @@ function save(options = {}, callback) {
}
}

const { forceUpdate, ...methodOptions } = options;

// If we can just insert a document without calling the meteor method. We may
// be on the server or the collection may be local.
try {
// Prepare arguments.
let methodArgs = {
doc,
stopOnFirstError: options.stopOnFirstError,
simulation: options.simulation,
options: methodOptions,
trusted: true
};
if (inserting) {
Expand Down
13 changes: 11 additions & 2 deletions lib/modules/storage/class_static_methods/insert.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,23 @@ import isRemote from '../utils/is_remote.js';
import callMeteorMethod from '../utils/call_meteor_method.js';
import classInsert from '../utils/class_insert.js';

function insert(rawDoc, callback) {
function insert(rawDoc, options, callback) {
const Class = this;
const Collection = Class.getCollection();

// If we omit options argument then it may be a callback function.
if (options instanceof Function) {
callback = options;
options = {};
}
// Make sure that options is at least an empty object.
options = options || {};

// Prepare arguments.
const args = {
className: Class.getName(),
rawDoc
rawDoc,
options,
};

// Generate ID if not provided.
Expand Down
2 changes: 2 additions & 0 deletions lib/modules/storage/utils/class_insert.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ function classInsert(args = {}) {
rawDoc,
stopOnFirstError,
fields,
options = {},
simulation = true,
trusted = false,
} = args;
Expand All @@ -27,6 +28,7 @@ function classInsert(args = {}) {
stopOnFirstError,
simulation,
trusted,
options,
});
};

Expand Down
3 changes: 2 additions & 1 deletion lib/modules/storage/utils/class_remove.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ function classRemove(args = {}) {
result += documentRemove({
doc,
simulation,
trusted
trusted,
options,
});
});

Expand Down
1 change: 1 addition & 0 deletions lib/modules/storage/utils/class_update.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ function classUpdate(args = {}) {
simulation,
fields,
trusted,
options,
oldDoc
});
});
Expand Down
1 change: 1 addition & 0 deletions lib/modules/storage/utils/class_upsert.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ function classUpsert(args = {}) {
// Insert a document.
result.insertedId = documentInsert({
doc,
options,
stopOnFirstError,
simulation,
trusted,
Expand Down
21 changes: 20 additions & 1 deletion lib/modules/storage/utils/document_insert.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ function documentInsert(args = {}) {
doc,
stopOnFirstError,
fields,
options,
simulation = true,
trusted = false
} = args;
Expand Down Expand Up @@ -69,14 +70,32 @@ function documentInsert(args = {}) {
});
values = _omitBy(values, (value) => value === undefined);

const { multi, clone, isInsert, ...insertOptions } = options;

// Insert a document.
try {
// There is a difference in what the insert method returns depending on the
// environment. On the client it returns an inserted document id, on the
// server it returns array of inserted documents. So we always return the
// generated id. We can't send an entire document because it could be a
// security issue if we are not subscribed to all fields of a document.
Collection._collection.insert(values);
if (Meteor.isServer) {
// Use mongo's normal collection methods, to allow interop with other packages
// that override it.

if (Object.keys(insertOptions).length) {
// If we are providing extra options to insert, pass them on.
// This is not supported by the original mongo insert, so it
// assumes an overriding package that adds this option.
Collection.insert(values, insertOptions);
} else {
Collection.insert(values);
}
} else {
// Avoid Meteor calling insert method from client,
// we do this ourselves
Collection._collection.insert(values);
}

// Change the "_isNew" flag to "false". Mark a document as not new.
doc._isNew = false;
Expand Down
28 changes: 25 additions & 3 deletions lib/modules/storage/utils/document_remove.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import triggerAfterRemove from './trigger_after_remove.js';
function documentRemove(args = {}) {
const {
doc,
options = {},
simulation = true,
trusted = false
} = args;
Expand All @@ -30,11 +31,32 @@ function documentRemove(args = {}) {
// Trigger before events.
triggerBeforeRemove(args);

const { multi, ...removeOptions } = options;

// Remove a document.
try {
const result = Collection._collection.remove({
_id: doc._id
});
let result;

if (Meteor.isServer) {
// Use mongo's normal collection methods, to allow interop with other packages
// that override it.

if (Object.keys(removeOptions).length) {
result = Collection.remove({
_id: doc._id
}, removeOptions);
} else {
result = Collection.remove({
_id: doc._id
});
}
} else {
// Avoid Meteor calling insert method from client,
// we do this ourselves
result = Collection._collection.remove({
_id: doc._id
});
}

// Mark a document as new, so it will be possible to save it again.
doc._isNew = true;
Expand Down
30 changes: 24 additions & 6 deletions lib/modules/storage/utils/document_update.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import _omit from "lodash/omit";
import _size from "lodash/size";
import _isUndefined from "lodash/isUndefined";
import castNested from "../../fields/utils/castNested";
import triggerBeforeSave from "./trigger_before_save";
import triggerBeforeUpdate from "./trigger_before_update";
Expand All @@ -17,6 +18,7 @@ function documentUpdate(args = {}) {
simulation = true,
forceUpdate = false,
trusted = false,
options = {},
oldDoc
} = args;

Expand Down Expand Up @@ -97,12 +99,28 @@ function documentUpdate(args = {}) {
}
// Update a document.
try {
const result = Collection._collection.update(
{
_id: doc._id
},
modifier
);
let result;

if (Meteor.isServer) {
// Use mongo's normal collection methods, to allow interop with other packages
// that override it.
result = Collection.update(
{
_id: doc._id
},
modifier,
options
);
} else {
// Avoid Meteor calling insert method from client,
// we do this ourselves
result = Collection._collection.update(
{
_id: doc._id
},
modifier
);
}

// Trigger after events.
triggerAfterUpdate(args);
Expand Down