Skip to content

Commit 234384e

Browse files
committed
Add skipPoll support for json1 ops, instead of throwing errors for them
1 parent e72c6bb commit 234384e

File tree

2 files changed

+87
-3
lines changed

2 files changed

+87
-3
lines changed

index.js

+41-3
Original file line numberDiff line numberDiff line change
@@ -1044,10 +1044,44 @@ function getInnerFields(params, fields) {
10441044
function opContainsAnyField(op, fields) {
10451045
for (var i = 0; i < op.length; i++) {
10461046
var component = op[i];
1047-
if (component.p.length === 0) {
1048-
return true;
1049-
} else if (fields[component.p[0]]) {
1047+
if (Array.isArray(component.p)) {
1048+
// json0 op component:
1049+
//
1050+
// Each op component has its own array of `p` path segments from doc root.
1051+
if (component.p.length === 0) {
1052+
return true;
1053+
} else if (fields[component.p[0]]) {
1054+
return true;
1055+
}
1056+
} else if (typeof component === 'string') {
1057+
// json1 field descent from root:
1058+
//
1059+
// First string in top-level array means all subsequent operations will be
1060+
// underneath that top-level field, no need to continue iterating.
1061+
return fields[component];
1062+
} else if (hasOwnProperty(component, 'p') ||
1063+
hasOwnProperty(component, 'r') ||
1064+
hasOwnProperty(component, 'd') ||
1065+
hasOwnProperty(component, 'i') ||
1066+
hasOwnProperty(component, 'e')) {
1067+
// json1 root-level operation:
1068+
//
1069+
// If we encounter an operation at top level prior to encountering a string,
1070+
// (field descent) then the operation affects the entire document.
10501071
return true;
1072+
} else if (Array.isArray(component)) {
1073+
// json1 child operation list:
1074+
//
1075+
// In a canonical json1 op, if we encounter a child op prior to encountering
1076+
// a string (field descent), then the child should start with a field descent.
1077+
// If that weren't the case, the op would be pulled up to the top-level array.
1078+
var descendant = component;
1079+
while (typeof descendant[0] !== 'string') {
1080+
descendant = descendant[0];
1081+
}
1082+
if (fields[descendant[0]]) {
1083+
return true;
1084+
}
10511085
}
10521086
}
10531087
return false;
@@ -1457,6 +1491,10 @@ function isPlainObject(value) {
14571491
);
14581492
}
14591493

1494+
function hasOwnProperty(obj, prop) {
1495+
return Object.prototype.hasOwnProperty.call(obj, prop);
1496+
}
1497+
14601498
// Convert a simple map of fields that we want into a mongo projection. This
14611499
// depends on the data being stored at the top level of the document. It will
14621500
// only work properly for json documents--which are the only types for which

test/test_skip_poll.js

+46
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
var expect = require('chai').expect;
2+
var json1 = require('ot-json1');
23
var ShareDbMongo = require('../index');
34

45
describe('skipPoll', function() {
@@ -111,6 +112,51 @@ describe('skipPoll', function() {
111112
assertNotSkips({op: [{p: ['a'], dummyOp: 1}, {p: [], dummyOp: 1}]}, query);
112113
assertNotSkips({op: [{p: [], dummyOp: 1}, {p: ['x'], dummyOp: 1}]}, query);
113114
});
115+
116+
it('json1 root-level changes', function() {
117+
// Root-level doc changes should always cause a query poll.
118+
assertNotSkips({op: json1.insertOp('', {brandNew: 'value'})}, query);
119+
assertNotSkips({op: json1.removeOp('')}, query);
120+
});
121+
122+
it('json1 ops not affecting queried fields', function() {
123+
assertSkips({op: json1.insertOp(['notQueried'], 'hello')}, query);
124+
assertSkips({op: json1.moveOp(['notQueried'], ['alsoNotQueried'])}, query);
125+
assertSkips({op: json1.removeOp(['notQueried'], 'hello')}, query);
126+
});
127+
128+
it('json1 insert ops', function() {
129+
assertIfSkips({op: json1.insertOp(['a'], 'hello')}, query, !has(fields, 'a'));
130+
assertIfSkips({op: json1.insertOp(['a', 'b'], 'hello')}, query, !has(fields, 'a'));
131+
assertIfSkips({op: json1.insertOp(['a', 0], 'hello')}, query, !has(fields, 'a'));
132+
});
133+
134+
it('json1 remove ops', function() {
135+
assertIfSkips({op: json1.removeOp(['a'], 'hello')}, query, !has(fields, 'a'));
136+
assertIfSkips({op: json1.removeOp(['a', 'b'], 'hello')}, query, !has(fields, 'a'));
137+
assertIfSkips({op: json1.removeOp(['a', 0], 'hello')}, query, !has(fields, 'a'));
138+
});
139+
140+
it('json1 move ops', function() {
141+
assertIfSkips({op: json1.moveOp(['a'], ['x'])}, query, !has(fields, 'a') && !has(fields, 'x'));
142+
assertIfSkips({op: json1.moveOp(['x'], ['a'])}, query, !has(fields, 'x') && !has(fields, 'a'));
143+
assertIfSkips({op: json1.moveOp(['a'], ['notQueried'])}, query, !has(fields, 'a'));
144+
assertIfSkips({op: json1.moveOp(['notQueried'], ['a'])}, query, !has(fields, 'a'));
145+
assertSkips({op: json1.moveOp(['notQueried'], ['alsoNotQueried'])}, query);
146+
});
147+
148+
it('json1 composed ops', function() {
149+
var compositeNotQueried = json1.type.compose(
150+
json1.insertOp(['notQueried1'], 'hello'),
151+
json1.moveOp(['notQueried2'], ['notQueried3'])
152+
);
153+
assertSkips({op: compositeNotQueried}, query);
154+
var compositeQueried = json1.type.compose(
155+
json1.insertOp(['notQueried1'], 'hello'),
156+
json1.moveOp(['notQueried2'], ['a'])
157+
);
158+
assertIfSkips({op: compositeQueried}, query, !has(fields, 'a'));
159+
});
114160
});
115161
}
116162
});

0 commit comments

Comments
 (0)