1 | mongopatch [![Build Status](https://travis-ci.org/e-conomic/mongopatch.png?branch=master)](https://travis-ci.org/e-conomic/mongopatch)
|
2 | ==========
|
3 |
|
4 | MongoDB patching tool. Update and log mongodb documents.
|
5 |
|
6 | npm install -g mongopatch
|
7 |
|
8 |
|
9 | Writing patches
|
10 | ---------------
|
11 |
|
12 | Patches are written as separate modules, exposing a single patching function.
|
13 |
|
14 | ```javascript
|
15 |
|
16 | module.exports = function(patch) {
|
17 | // Specify which patching system version to use for this patch (required)
|
18 | patch.version('0.1.0');
|
19 |
|
20 | // Update all users that match the provided query.
|
21 | // The query is optional, if not provided all the documents
|
22 | // in the collection are processed.
|
23 | patch.update('users', { name: 'e-conomic' }, function(document, callback) {
|
24 | // The callback function should be called with the update to apply,
|
25 | // this can be any valid mongodb update query.
|
26 | callback(null, { $set: { email: 'e-conomic@e-conomic.com', associates: 'unknown' } });
|
27 | });
|
28 |
|
29 | // Register an after callback, to be run after each update.
|
30 | patch.after(function(update, callback) {
|
31 | var isValid = update.after.email === 'e-conomic@e-conomic.com';
|
32 |
|
33 | // Call the callback function with an error to abort the patching process.
|
34 | // Use this to guard against corrupted updates.
|
35 | callback(isValid ? null : new Error('Update failed'));
|
36 | });
|
37 | }
|
38 | ```
|
39 |
|
40 | The after callback gets an options map, containing the `before` and `after` documents, a `modfifed` flag (telling if there any changes between the two documents) and a `diff` object (the diff between the two documents).
|
41 |
|
42 | Another example where we process all users.
|
43 |
|
44 | ```javascript
|
45 | function shouldUpdate(document) {
|
46 | // ...
|
47 | }
|
48 |
|
49 | function update(document) {
|
50 | // ...
|
51 | }
|
52 |
|
53 | function isValid(document) {
|
54 | // ...
|
55 | }
|
56 |
|
57 | module.exports = function(patch) {
|
58 | patch.version('0.1.0');
|
59 |
|
60 | // All users are processed, since no filter query provided.
|
61 | patch.update('users', function(document, callback) {
|
62 | if(!shouldUpdate(document)) {
|
63 | // Calling the callback with no arguments, skips the document in the update process.
|
64 | return callback();
|
65 | }
|
66 |
|
67 | update(document);
|
68 |
|
69 | if(!isValid(document)) {
|
70 | // Validate document before performing the actual update in the database.
|
71 | // Passing an error as first argument, aborts the patching process,
|
72 | // and can leave the database in inconsistent state.
|
73 | return callback(new Error('Invalid document'));
|
74 | }
|
75 |
|
76 | // Apply the update, by overwritting the whole document
|
77 | callback(null, document);
|
78 | });
|
79 | }
|
80 | ```
|
81 |
|
82 | Runing patches
|
83 | --------------
|
84 |
|
85 | Run patches using the `mongopatch` command-line tool. Basic usage:
|
86 |
|
87 | mongopatch path/to/patch.js --db databaseConnectionString --dry-run --log-db logDatabaseConnectionString
|
88 |
|
89 | Available options (too see a full list of options, run `mongopatch` without any arguments).
|
90 |
|
91 | - **db**: MongoDB connection string (e.g. `user:password@localhost:27017/development` or `development`).
|
92 | - **log-db**: MongoDB connection string for the log database. When provided a version of the document is stored before and after the update.
|
93 | - **dry-run**: Do not perform any changes in the database. Changes are performed on copy of the documents and stored in the log db (if available).
|
94 | - **parallel**: Run the patch with given parallelism. It may run the patch faster.
|
95 |
|
96 | ![mongopatch](/mongopatch.png)
|
97 |
|
98 | Running the tool, outputs the above interface, where it is possible to track progress and accumulated changes done to the documents. The diff shows how many times a property has been added, updated or removed between the original and the updated documents (note that all array changes are grouped).
|
99 |
|
100 | When running on a live database, where external changes can occur, the progress indicator may be incorrect, as documents can be added or removed. Also skipping documents in `patch.update` causes the progress to fall behind.
|
101 |
|
102 | Log Database
|
103 | ------------
|
104 |
|
105 | When a log database is available, a collection is created for every patch run. A document in the patch collection, contains data about the applied update. The `before` key points to the original document, `after` to the updated document, `modified` is a boolean flag telling if there were any changes and `diff` the difference between the `before` and `after` document (if `modified` is false, this is going to be an empty object). It also includes additional meta data.
|
106 |
|
107 | ```javascript
|
108 | {
|
109 | "before": {
|
110 | "_id": ObjectId("507d2a650ea37a02000001ae"),
|
111 | "name": "e-conomic",
|
112 | "associates": "debitoor"
|
113 | },
|
114 | "after": {
|
115 | "_id": ObjectId("507d2a650ea37a02000001ae"),
|
116 | "name": "e-conomic",
|
117 | "associates": "unknown",
|
118 | "email": "e-conomic@e-conomic.com"
|
119 | },
|
120 | "modified": true,
|
121 | "diff": { // diff is a nested object, where leafs can have one of the three values added, updated, removed
|
122 | "associates": "updated",
|
123 | "email": "added"
|
124 | },
|
125 | "createdAt": ISODate("2013-12-17T15:28:14.737Z"), // when was the log document created
|
126 | "collection": "development.users", // full collection name
|
127 | "modifier": "{ \"$set\": { \"email\": \"e-conomic@e-conomic.com\" } }", // stringified modifier (passed to the callback in path.update)
|
128 | "query": "{ \"name\": \"e-conomic\" }" // stringified query (passed to patch.update function)
|
129 | }
|
130 | ```
|
131 |
|
132 | In some cases if an error occures during the patching, an `error` object is added to the log document, containing the error message and stack.
|