@electerm/nedb 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Responsible for sequentially executing actions on the database
3
+ */
4
+
5
+ var async = require('./async')
6
+ ;
7
+
8
+ function Executor () {
9
+ this.buffer = [];
10
+ this.ready = false;
11
+
12
+ // This queue will execute all commands, one-by-one in order
13
+ this.queue = async.queue(function (task, cb) {
14
+ var newArguments = [];
15
+
16
+ // task.arguments is an array-like object on which adding a new field doesn't work, so we transform it into a real array
17
+ for (var i = 0; i < task.arguments.length; i += 1) { newArguments.push(task.arguments[i]); }
18
+ var lastArg = task.arguments[task.arguments.length - 1];
19
+
20
+ // Always tell the queue task is complete. Execute callback if any was given.
21
+ if (typeof lastArg === 'function') {
22
+ // Callback was supplied
23
+ newArguments[newArguments.length - 1] = function () {
24
+ if (typeof setImmediate === 'function') {
25
+ setImmediate(cb);
26
+ } else {
27
+ process.nextTick(cb);
28
+ }
29
+ lastArg.apply(null, arguments);
30
+ };
31
+ } else if (!lastArg && task.arguments.length !== 0) {
32
+ // false/undefined/null supplied as callbback
33
+ newArguments[newArguments.length - 1] = function () { cb(); };
34
+ } else {
35
+ // Nothing supplied as callback
36
+ newArguments.push(function () { cb(); });
37
+ }
38
+
39
+
40
+ task.fn.apply(task.this, newArguments);
41
+ }, 1);
42
+ }
43
+
44
+
45
+ /**
46
+ * If executor is ready, queue task (and process it immediately if executor was idle)
47
+ * If not, buffer task for later processing
48
+ * @param {Object} task
49
+ * task.this - Object to use as this
50
+ * task.fn - Function to execute
51
+ * task.arguments - Array of arguments, IMPORTANT: only the last argument may be a function (the callback)
52
+ * and the last argument cannot be false/undefined/null
53
+ * @param {Boolean} forceQueuing Optional (defaults to false) force executor to queue task even if it is not ready
54
+ */
55
+ Executor.prototype.push = function (task, forceQueuing) {
56
+ if (this.ready || forceQueuing) {
57
+ this.queue.push(task);
58
+ } else {
59
+ this.buffer.push(task);
60
+ }
61
+ };
62
+
63
+
64
+ /**
65
+ * Queue all tasks in buffer (in the same order they came in)
66
+ * Automatically sets executor as ready
67
+ */
68
+ Executor.prototype.processBuffer = function () {
69
+ var i;
70
+ this.ready = true;
71
+ for (i = 0; i < this.buffer.length; i += 1) { this.queue.push(this.buffer[i]); }
72
+ this.buffer = [];
73
+ };
74
+
75
+
76
+
77
+ // Interface
78
+ module.exports = Executor;
package/lib/indexes.js ADDED
@@ -0,0 +1,294 @@
1
+ var BinarySearchTree = require('@yetzt/binary-search-tree').AVLTree
2
+ , model = require('./model')
3
+ , _ = require('./underscore')
4
+ , util = require('util')
5
+ ;
6
+
7
+ /**
8
+ * Two indexed pointers are equal iif they point to the same place
9
+ */
10
+ function checkValueEquality (a, b) {
11
+ return a === b;
12
+ }
13
+
14
+ /**
15
+ * Type-aware projection
16
+ */
17
+ function projectForUnique (elt) {
18
+ if (elt === null) { return '$null'; }
19
+ if (typeof elt === 'string') { return '$string' + elt; }
20
+ if (typeof elt === 'boolean') { return '$boolean' + elt; }
21
+ if (typeof elt === 'number') { return '$number' + elt; }
22
+ if (util.isArray(elt)) { return '$date' + elt.getTime(); }
23
+
24
+ return elt; // Arrays and objects, will check for pointer equality
25
+ }
26
+
27
+
28
+ /**
29
+ * Create a new index
30
+ * All methods on an index guarantee that either the whole operation was successful and the index changed
31
+ * or the operation was unsuccessful and an error is thrown while the index is unchanged
32
+ * @param {String} options.fieldName On which field should the index apply (can use dot notation to index on sub fields)
33
+ * @param {Boolean} options.unique Optional, enforce a unique constraint (default: false)
34
+ * @param {Boolean} options.sparse Optional, allow a sparse index (we can have documents for which fieldName is undefined) (default: false)
35
+ */
36
+ function Index (options) {
37
+ this.fieldName = options.fieldName;
38
+ this.unique = options.unique || false;
39
+ this.sparse = options.sparse || false;
40
+
41
+ this.treeOptions = { unique: this.unique, compareKeys: model.compareThings, checkValueEquality: checkValueEquality };
42
+
43
+ this.reset(); // No data in the beginning
44
+ }
45
+
46
+
47
+ /**
48
+ * Reset an index
49
+ * @param {Document or Array of documents} newData Optional, data to initialize the index with
50
+ * If an error is thrown during insertion, the index is not modified
51
+ */
52
+ Index.prototype.reset = function (newData) {
53
+ this.tree = new BinarySearchTree(this.treeOptions);
54
+
55
+ if (newData) { this.insert(newData); }
56
+ };
57
+
58
+
59
+ /**
60
+ * Insert a new document in the index
61
+ * If an array is passed, we insert all its elements (if one insertion fails the index is not modified)
62
+ * O(log(n))
63
+ */
64
+ Index.prototype.insert = function (doc) {
65
+ var key, self = this
66
+ , keys, i, failingI, error
67
+ ;
68
+
69
+ if (util.isArray(doc)) { this.insertMultipleDocs(doc); return; }
70
+
71
+ key = model.getDotValue(doc, this.fieldName);
72
+
73
+ // We don't index documents that don't contain the field if the index is sparse
74
+ if (key === undefined && this.sparse) { return; }
75
+
76
+ if (!util.isArray(key)) {
77
+ this.tree.insert(key, doc);
78
+ } else {
79
+ // If an insert fails due to a unique constraint, roll back all inserts before it
80
+ keys = _.uniq(key, projectForUnique);
81
+
82
+ for (i = 0; i < keys.length; i += 1) {
83
+ try {
84
+ this.tree.insert(keys[i], doc);
85
+ } catch (e) {
86
+ error = e;
87
+ failingI = i;
88
+ break;
89
+ }
90
+ }
91
+
92
+ if (error) {
93
+ for (i = 0; i < failingI; i += 1) {
94
+ this.tree.delete(keys[i], doc);
95
+ }
96
+
97
+ throw error;
98
+ }
99
+ }
100
+ };
101
+
102
+
103
+ /**
104
+ * Insert an array of documents in the index
105
+ * If a constraint is violated, the changes should be rolled back and an error thrown
106
+ *
107
+ * @API private
108
+ */
109
+ Index.prototype.insertMultipleDocs = function (docs) {
110
+ var i, error, failingI;
111
+
112
+ for (i = 0; i < docs.length; i += 1) {
113
+ try {
114
+ this.insert(docs[i]);
115
+ } catch (e) {
116
+ error = e;
117
+ failingI = i;
118
+ break;
119
+ }
120
+ }
121
+
122
+ if (error) {
123
+ for (i = 0; i < failingI; i += 1) {
124
+ this.remove(docs[i]);
125
+ }
126
+
127
+ throw error;
128
+ }
129
+ };
130
+
131
+
132
+ /**
133
+ * Remove a document from the index
134
+ * If an array is passed, we remove all its elements
135
+ * The remove operation is safe with regards to the 'unique' constraint
136
+ * O(log(n))
137
+ */
138
+ Index.prototype.remove = function (doc) {
139
+ var key, self = this;
140
+
141
+ if (util.isArray(doc)) { doc.forEach(function (d) { self.remove(d); }); return; }
142
+
143
+ key = model.getDotValue(doc, this.fieldName);
144
+
145
+ if (key === undefined && this.sparse) { return; }
146
+
147
+ if (!util.isArray(key)) {
148
+ this.tree.delete(key, doc);
149
+ } else {
150
+ _.uniq(key, projectForUnique).forEach(function (_key) {
151
+ self.tree.delete(_key, doc);
152
+ });
153
+ }
154
+ };
155
+
156
+
157
+ /**
158
+ * Update a document in the index
159
+ * If a constraint is violated, changes are rolled back and an error thrown
160
+ * Naive implementation, still in O(log(n))
161
+ */
162
+ Index.prototype.update = function (oldDoc, newDoc) {
163
+ if (util.isArray(oldDoc)) { this.updateMultipleDocs(oldDoc); return; }
164
+
165
+ this.remove(oldDoc);
166
+
167
+ try {
168
+ this.insert(newDoc);
169
+ } catch (e) {
170
+ this.insert(oldDoc);
171
+ throw e;
172
+ }
173
+ };
174
+
175
+
176
+ /**
177
+ * Update multiple documents in the index
178
+ * If a constraint is violated, the changes need to be rolled back
179
+ * and an error thrown
180
+ * @param {Array of oldDoc, newDoc pairs} pairs
181
+ *
182
+ * @API private
183
+ */
184
+ Index.prototype.updateMultipleDocs = function (pairs) {
185
+ var i, failingI, error;
186
+
187
+ for (i = 0; i < pairs.length; i += 1) {
188
+ this.remove(pairs[i].oldDoc);
189
+ }
190
+
191
+ for (i = 0; i < pairs.length; i += 1) {
192
+ try {
193
+ this.insert(pairs[i].newDoc);
194
+ } catch (e) {
195
+ error = e;
196
+ failingI = i;
197
+ break;
198
+ }
199
+ }
200
+
201
+ // If an error was raised, roll back changes in the inverse order
202
+ if (error) {
203
+ for (i = 0; i < failingI; i += 1) {
204
+ this.remove(pairs[i].newDoc);
205
+ }
206
+
207
+ for (i = 0; i < pairs.length; i += 1) {
208
+ this.insert(pairs[i].oldDoc);
209
+ }
210
+
211
+ throw error;
212
+ }
213
+ };
214
+
215
+
216
+ /**
217
+ * Revert an update
218
+ */
219
+ Index.prototype.revertUpdate = function (oldDoc, newDoc) {
220
+ var revert = [];
221
+
222
+ if (!util.isArray(oldDoc)) {
223
+ this.update(newDoc, oldDoc);
224
+ } else {
225
+ oldDoc.forEach(function (pair) {
226
+ revert.push({ oldDoc: pair.newDoc, newDoc: pair.oldDoc });
227
+ });
228
+ this.update(revert);
229
+ }
230
+ };
231
+
232
+
233
+ /**
234
+ * Get all documents in index whose key match value (if it is a Thing) or one of the elements of value (if it is an array of Things)
235
+ * @param {Thing} value Value to match the key against
236
+ * @return {Array of documents}
237
+ */
238
+ Index.prototype.getMatching = function (value) {
239
+ var self = this;
240
+
241
+ if (!util.isArray(value)) {
242
+ return self.tree.search(value);
243
+ } else {
244
+ var _res = {}, res = [];
245
+
246
+ value.forEach(function (v) {
247
+ self.getMatching(v).forEach(function (doc) {
248
+ _res[doc._id] = doc;
249
+ });
250
+ });
251
+
252
+ Object.keys(_res).forEach(function (_id) {
253
+ res.push(_res[_id]);
254
+ });
255
+
256
+ return res;
257
+ }
258
+ };
259
+
260
+
261
+ /**
262
+ * Get all documents in index whose key is between bounds are they are defined by query
263
+ * Documents are sorted by key
264
+ * @param {Query} query
265
+ * @return {Array of documents}
266
+ */
267
+ Index.prototype.getBetweenBounds = function (query) {
268
+ return this.tree.betweenBounds(query);
269
+ };
270
+
271
+
272
+ /**
273
+ * Get all elements in the index
274
+ * @return {Array of documents}
275
+ */
276
+ Index.prototype.getAll = function () {
277
+ var res = [];
278
+
279
+ this.tree.executeOnEveryNode(function (node) {
280
+ var i;
281
+
282
+ for (i = 0; i < node.data.length; i += 1) {
283
+ res.push(node.data[i]);
284
+ }
285
+ });
286
+
287
+ return res;
288
+ };
289
+
290
+
291
+
292
+
293
+ // Interface
294
+ module.exports = Index;