-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathsharedb-mongo-utils.js
172 lines (163 loc) · 5.2 KB
/
sharedb-mongo-utils.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// These functions are taken straight from sharedb-mongo.
exports.makeQuerySafe = makeQuerySafe;
// Call on a query after it gets parsed to make it safe against
// matching deleted documents.
function makeQuerySafe(query) {
// Don't modify the query if the user explicitly sets _type already
if (query.hasOwnProperty('_type')) return;
// Deleted documents are kept around so that we can start their version from
// the last version if they get recreated. When docs are deleted, their data
// properties are cleared and _type is set to null. Filter out deleted docs
// by requiring that _type is a string if the query does not naturally
// restrict the results with other keys
if (deletedDocCouldSatisfyQuery(query)) {
query._type = {$type: 2};
}
};
// Could a deleted doc (one that contains {_type: null} and no other
// fields) satisfy a query?
//
// Return true if it definitely can, or if we're not sure. (This
// function is used as an optimization to see whether we can avoid
// augmenting the query to ignore deleted documents)
function deletedDocCouldSatisfyQuery(query) {
// Any query with `{foo: value}` with non-null `value` will never
// match deleted documents (that are empty other than the `_type`
// field).
//
// This generalizes to additional classes of queries. Here’s a
// recursive description of queries that can't match a deleted doc:
// In general, a query with `{foo: X}` can't match a deleted doc
// if `X` is guaranteed to not match null or undefined. In addition
// to non-null values, the following clauses are guaranteed to not
// match null or undefined:
//
// * `{$in: [A, B, C]}}` where all of A, B, C are non-null.
// * `{$ne: null}`
// * `{$exists: true}`
// * `{$gt: not null}`, `{gte: not null}`, `{$lt: not null}`, `{$lte: not null}`
//
// In addition, some queries that have `$and` or `$or` at the
// top-level can't match deleted docs:
// * `{$and: [A, B, C]}`, where at least one of A, B, C are queries
// guaranteed to not match `{_type: null}`
// * `{$or: [A, B, C]}`, where all of A, B, C are queries guaranteed
// to not match `{_type: null}`
//
// There are more queries that can't match deleted docs but they
// aren’t that common, e.g. ones using `$type` or bit-wise
// operators.
if (query.hasOwnProperty('$and')) {
if (Array.isArray(query.$and)) {
for (var i = 0; i < query.$and.length; i++) {
if (!deletedDocCouldSatisfyQuery(query.$and[i])) {
return false;
}
}
} else {
// Malformed? Play it safe.
return true;
}
}
for (var prop in query) {
// Ignore fields that remain set on deleted docs
if (
prop === '_id' ||
prop === '_v' ||
prop === '_o' ||
prop === '_m' || (
prop[0] === '_' &&
prop[1] === 'm' &&
prop[2] === '.'
)
) {
continue;
}
// Top-level operators with special handling in this function
if (prop === '$and' || prop === '$or') {
continue;
}
// When using top-level operators that we don't understand, play
// it safe
if (prop[0] === '$') {
return true;
}
if (!couldMatchNull(query[prop])) {
return false;
}
}
if (query.hasOwnProperty('$or')) {
if (Array.isArray(query.$or)) {
for (var i = 0; i < query.$or.length; i++) {
if (deletedDocCouldSatisfyQuery(query.$or[i])) {
return true;
}
}
return false;
} else {
// Malformed? Play it safe.
return true;
}
}
return true;
}
function couldMatchNull(clause) {
if (
typeof clause === 'number' ||
typeof clause === 'boolean' ||
typeof clause === 'string'
) {
return false;
} else if (clause === null) {
return true;
} else if (isPlainObject(clause)) {
// Mongo interprets clauses with multiple properties with an
// implied 'and' relationship, e.g. {$gt: 3, $lt: 6}. If every
// part of the clause could match null then the full clause could
// match null.
for (var prop in clause) {
var value = clause[prop];
if (prop === '$in' && Array.isArray(value)) {
var partCouldMatchNull = false;
for (var i = 0; i < value.length; i++) {
if (value[i] === null) {
partCouldMatchNull = true;
break;
}
}
if (!partCouldMatchNull) {
return false;
}
} else if (prop === '$ne') {
if (value === null) {
return false;
}
} else if (prop === '$exists') {
if (value) {
return false;
}
} else if (prop === '$gt' || prop === '$gte' || prop === '$lt' || prop === '$lte') {
if (value !== null) {
return false;
}
} else {
// Not sure what to do with this part of the clause; assume it
// could match null.
}
}
// All parts of the clause could match null.
return true;
} else {
// Not a POJO, string, number, or boolean. Not sure what it is,
// but play it safe.
return true;
}
}
function isPlainObject(value) {
return (
typeof value === 'object' && (
Object.getPrototypeOf(value) === Object.prototype ||
Object.getPrototypeOf(value) === null
)
);
}