ueberdb2 1.4.16

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,201 @@
1
+ 'use strict';
2
+ /**
3
+ * 2012 Max 'Azul' Wiehle
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS-IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ const AbstractDatabase = require('../lib/AbstractDatabase');
19
+ const nano = require('nano');
20
+ const async = require('async');
21
+
22
+ const DESIGN_NAME = 'ueberDb';
23
+ const DESIGN_PATH = `_design/${DESIGN_NAME}`;
24
+
25
+ const handleError = (er) => {
26
+ if (er) throw new Error(er);
27
+ };
28
+
29
+ exports.Database = class extends AbstractDatabase {
30
+ constructor(settings) {
31
+ super();
32
+ this.db = null;
33
+ this.client = null;
34
+ this.settings = settings;
35
+
36
+ // force some settings
37
+ // used by CacheAndBufferLayer.js
38
+ this.settings.cache = 1000;
39
+ this.settings.writeInterval = 100;
40
+ this.settings.json = false;
41
+ }
42
+
43
+ init(callback) {
44
+ const settings = this.settings;
45
+ let client = null;
46
+ let db = null;
47
+
48
+ const config = {
49
+ url: `http://${settings.host}:${settings.port}`,
50
+ requestDefaults: {
51
+ pool: {
52
+ maxSockets: settings.maxListeners || 1,
53
+ },
54
+ auth: {
55
+ user: settings.user,
56
+ pass: settings.password,
57
+ },
58
+ },
59
+ };
60
+
61
+ const createDb = () => {
62
+ client.db.create(settings.database, (er, body) => {
63
+ if (er) return callback(er);
64
+ return setDb();
65
+ });
66
+ };
67
+
68
+ const setDb = () => {
69
+ db = client.use(settings.database);
70
+ checkUeberDbDesignDocument(db);
71
+ this.client = client;
72
+ this.db = db;
73
+ callback();
74
+ };
75
+
76
+ // Always ensure that couchDb has at least an empty design doc for UeberDb use
77
+ // this will be necessary for the `findKeys` method
78
+ const checkUeberDbDesignDocument = () => {
79
+ db.head(DESIGN_PATH, (er, _, header) => {
80
+ if (er && er.statusCode === 404) return db.insert({views: {}}, DESIGN_PATH, handleError);
81
+ if (er) throw new Error(er);
82
+ });
83
+ };
84
+
85
+ client = nano(config);
86
+ client.db.get(settings.database, (er, body) => {
87
+ if (er && er.statusCode === 404) return createDb();
88
+ if (er) return callback(er);
89
+ return setDb();
90
+ });
91
+ }
92
+
93
+ get(key, callback) {
94
+ const db = this.db;
95
+ db.get(key, (er, doc) => {
96
+ if (er && er.statusCode !== 404) {
97
+ console.log('GET');
98
+ console.log(er);
99
+ }
100
+ if (doc == null) return callback(null, null);
101
+ callback(null, doc.value);
102
+ });
103
+ }
104
+
105
+ findKeys(key, notKey, callback) {
106
+ const regex = this.createFindRegex(key, notKey);
107
+ const queryKey = `${key}__${notKey}`;
108
+ const db = this.db;
109
+
110
+ // always look up if the query haven't be done before
111
+ const checkQuery = () => {
112
+ db.get(DESIGN_PATH, (er, doc) => {
113
+ handleError(er);
114
+ const queryExists = queryKey in doc.views;
115
+ if (!queryExists) return createQuery(doc);
116
+ makeQuery();
117
+ });
118
+ };
119
+
120
+ // Cache the query for faster reuse in the future
121
+ const createQuery = (doc) => {
122
+ const mapFunction = {
123
+ map: `function (doc) { if (${regex}.test(doc._id)) { emit(doc._id, null); } }`,
124
+ };
125
+ doc.views[queryKey] = mapFunction;
126
+ db.insert(doc, DESIGN_PATH, (er) => {
127
+ handleError(er);
128
+ makeQuery();
129
+ });
130
+ };
131
+
132
+ // If this is the first time the request is used, this can take a while…
133
+ const makeQuery = (er) => {
134
+ db.view(DESIGN_NAME, queryKey, (er, docs) => {
135
+ handleError(er);
136
+ docs = docs.rows.map((doc) => doc.key);
137
+ callback(null, docs);
138
+ });
139
+ };
140
+
141
+ checkQuery();
142
+ }
143
+
144
+ set(key, value, callback) {
145
+ const db = this.db;
146
+ db.get(key, (er, doc) => {
147
+ if (doc == null) return db.insert({_id: key, value}, callback);
148
+ db.insert({_id: key, _rev: doc._rev, value}, callback);
149
+ });
150
+ }
151
+
152
+ remove(key, callback) {
153
+ const db = this.db;
154
+ db.head(key, (er, _, header) => {
155
+ if (er && er.statusCode === 404) return callback(null);
156
+ if (er) return callback(er);
157
+ // etag has additional quotation marks, remove them
158
+ const etag = JSON.parse(header).etag;
159
+ db.destroy(key, etag, (er, body) => {
160
+ if (er) return callback(er);
161
+ callback(null);
162
+ });
163
+ });
164
+ }
165
+
166
+ doBulk(bulk, callback) {
167
+ const db = this.db;
168
+ const keys = bulk.map((op) => op.key);
169
+ const revs = {};
170
+ const setters = [];
171
+ async.series([
172
+ (callback) => {
173
+ db.fetchRevs({keys}, (er, r) => {
174
+ if (er) throw new Error(JSON.stringify(er));
175
+ const rows = r.rows;
176
+ for (const j in r.rows) {
177
+ // couchDB will return error instead of value if key does not exist
178
+ if (rows[j].value != null) revs[rows[j].key] = rows[j].value.rev;
179
+ }
180
+ callback();
181
+ });
182
+ },
183
+ (callback) => {
184
+ for (const item of bulk) {
185
+ const set = {_id: item.key};
186
+ if (revs[item.key] != null) set._rev = revs[item.key];
187
+ if (item.type === 'set') set.value = item.value;
188
+ if (item.type === 'remove') set._deleted = true;
189
+ setters.push(set);
190
+ }
191
+ callback();
192
+ },
193
+ ], (err) => {
194
+ db.bulk({docs: setters}, callback);
195
+ });
196
+ }
197
+
198
+ close(callback) {
199
+ if (callback) callback();
200
+ }
201
+ };
@@ -0,0 +1,80 @@
1
+ 'use strict';
2
+ /**
3
+ * 2011 Peter 'Pita' Martischka
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS-IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ /*
19
+ *
20
+ * Fair warning that length may not provide the correct value upon load.
21
+ * See https://github.com/ether/etherpad-lite/pull/3984
22
+ *
23
+ */
24
+
25
+ const AbstractDatabase = require('../lib/AbstractDatabase');
26
+ const Dirty = require('dirty');
27
+
28
+ exports.Database = class extends AbstractDatabase {
29
+ constructor(settings) {
30
+ super();
31
+ this.db = null;
32
+
33
+ if (!settings || !settings.filename) {
34
+ settings = {filename: null};
35
+ }
36
+
37
+ this.settings = settings;
38
+
39
+ // set default settings
40
+ this.settings.cache = 0;
41
+ this.settings.writeInterval = 0;
42
+ this.settings.json = false;
43
+ }
44
+
45
+ init(callback) {
46
+ this.db = new Dirty(this.settings.filename);
47
+ this.db.on('load', (err) => {
48
+ callback();
49
+ });
50
+ }
51
+
52
+ get(key, callback) {
53
+ callback(null, this.db.get(key));
54
+ }
55
+
56
+ findKeys(key, notKey, callback) {
57
+ const keys = [];
58
+ const regex = this.createFindRegex(key, notKey);
59
+ this.db.forEach((key, val) => {
60
+ if (key.search(regex) !== -1) {
61
+ keys.push(key);
62
+ }
63
+ });
64
+ callback(null, keys);
65
+ }
66
+
67
+ set(key, value, callback) {
68
+ this.db.set(key, value, callback);
69
+ }
70
+
71
+ remove(key, callback) {
72
+ this.db.rm(key, callback);
73
+ }
74
+
75
+ close(callback) {
76
+ this.db.close();
77
+ this.db = null;
78
+ if (callback) callback();
79
+ }
80
+ };
@@ -0,0 +1,78 @@
1
+ 'use strict';
2
+ /**
3
+ * 2011 Peter 'Pita' Martischka
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS-IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ const AbstractDatabase = require('../lib/AbstractDatabase');
19
+ const Dirty = require('dirty');
20
+
21
+ exports.Database = class extends AbstractDatabase {
22
+ constructor(settings) {
23
+ super();
24
+ this.db = null;
25
+
26
+ if (!settings || !settings.filename) {
27
+ settings = {filename: null};
28
+ }
29
+
30
+ this.settings = settings;
31
+
32
+ // set default settings
33
+ this.settings.cache = 0;
34
+ this.settings.writeInterval = 0;
35
+ this.settings.json = false;
36
+ }
37
+
38
+ init(callback) {
39
+ this.db = new Dirty(this.settings.filename);
40
+ this.db.on('load', (err) => {
41
+ callback();
42
+ });
43
+ }
44
+
45
+ get(key, callback) {
46
+ callback(null, this.db.get(key));
47
+ }
48
+
49
+ findKeys(key, notKey, callback) {
50
+ const keys = [];
51
+ const regex = this.createFindRegex(key, notKey);
52
+ this.db.forEach((key, val) => {
53
+ if (key.search(regex) !== -1) {
54
+ keys.push(key);
55
+ }
56
+ });
57
+ callback(null, keys);
58
+ }
59
+
60
+ set(key, value, callback) {
61
+ this.db.set(key, value, callback);
62
+ const databasePath = require('path').dirname(this.settings.filename);
63
+ require('simple-git')(databasePath)
64
+ .silent(true)
65
+ .add('./*.db')
66
+ .commit('Automated commit...')
67
+ .push(['-u', 'origin', 'master'], () => console.debug('Stored git commit'));
68
+ }
69
+
70
+ remove(key, callback) {
71
+ this.db.rm(key, callback);
72
+ }
73
+
74
+ close(callback) {
75
+ this.db.close();
76
+ if (callback) callback();
77
+ }
78
+ };
@@ -0,0 +1,288 @@
1
+ 'use strict';
2
+ /**
3
+ * 2015 Visionist, Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS-IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ const AbstractDatabase = require('../lib/AbstractDatabase');
19
+ const es = require('elasticsearch');
20
+
21
+ // initialize w/ default settings
22
+ const elasticsearchSettings = {
23
+ hostname: '127.0.0.1',
24
+ port: '9200',
25
+ base_index: 'ueberes',
26
+
27
+ // for a list of valid API values see:
28
+ // https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/configuration.html#config-options
29
+ api: '7.6',
30
+ };
31
+
32
+ let client;
33
+
34
+ exports.Database = class extends AbstractDatabase {
35
+ constructor(settings) {
36
+ super();
37
+ this.db = null;
38
+
39
+ this.settings = settings || {};
40
+
41
+ // update settings if they were provided
42
+ if (this.settings.host) {
43
+ elasticsearchSettings.hostname = this.settings.host;
44
+ }
45
+
46
+ if (this.settings.port) {
47
+ elasticsearchSettings.port = this.settings.port;
48
+ }
49
+
50
+ if (this.settings.base_index) {
51
+ elasticsearchSettings.base_index = this.settings.base_index;
52
+ }
53
+
54
+ if (this.settings.api) {
55
+ elasticsearchSettings.api = this.settings.api;
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Initialize the elasticsearch client, then ping the server to ensure that a
61
+ * connection was made.
62
+ */
63
+ init(callback) {
64
+ // create elasticsearch client
65
+ client = new es.Client({
66
+ host: `${elasticsearchSettings.hostname}:${elasticsearchSettings.port}`,
67
+ apiVersion: elasticsearchSettings.api,
68
+ // log: "trace" // useful for debugging
69
+ });
70
+
71
+ // test the connection
72
+ client.ping({
73
+ requestTimeout: 3000,
74
+ }, (error) => {
75
+ if (error) {
76
+ console.error('unable to communicate with elasticsearch');
77
+ }
78
+
79
+ callback(error);
80
+ });
81
+ }
82
+
83
+ /**
84
+ * This function provides read functionality to the database.
85
+ *
86
+ * @param {String} key Key, of the format "test:test1" or, optionally, of the
87
+ * format "test:test1:check:check1"
88
+ * @param {function} callback Function will be called in the event of an error or
89
+ * upon completion of a successful database retrieval.
90
+ */
91
+ get(key, callback) {
92
+ client.get(getIndexTypeId(key), (error, response) => {
93
+ parseResponse(error, response, callback);
94
+ });
95
+ }
96
+
97
+ /**
98
+ * The three key scenarios for this are:
99
+ * (test:test1, null) ; (test:*, *:*:*) ; (test:*, null)
100
+ *
101
+ * TODO This currently works only for the second implementation above.
102
+ *
103
+ * For more information:
104
+ * - See the #Limitations section of the ueberDB README.
105
+ * - See https://github.com/Pita/ueberDB/wiki/findKeys-functionality, as well
106
+ * as the sqlite and mysql implementations.
107
+ *
108
+ * @param key Search key, which uses an asterisk (*) as the wild card.
109
+ * @param notKey Used to filter the result set
110
+ * @param callback First param is error, second is result
111
+ */
112
+ findKeys(key, notKey, callback) {
113
+ const splitKey = key.split(':');
114
+
115
+ client.search({
116
+ index: elasticsearchSettings.base_index,
117
+ type: splitKey[0],
118
+ size: 100, // this is a pretty random threshold...
119
+ }, (error, response) => {
120
+ if (error) {
121
+ console.error('findkeys', error);
122
+ callback(error);
123
+ return;
124
+ }
125
+
126
+ if (!error && response.hits) {
127
+ const keys = [];
128
+ for (let counter = 0; counter < response.hits.total; counter++) {
129
+ keys.push(`${splitKey[0]}:${response.hits.hits[counter]._id}`);
130
+ }
131
+ callback(null, keys);
132
+ }
133
+ });
134
+ }
135
+
136
+ /**
137
+ * This function provides write functionality to the database.
138
+ *
139
+ * @param {String} key Key, of the format "test:test1" or, optionally, of the
140
+ * format "test:test1:check:check1"
141
+ * @param {JSON|String} value The value to be stored to the database. The value is
142
+ * always converted to {val:value} before being written to the database, to account
143
+ * for situations where the value is just a string.
144
+ * @param {function} callback Function will be called in the event of an error or on
145
+ * completion of a successful database write.
146
+ */
147
+ set(key, value, callback) {
148
+ const options = getIndexTypeId(key);
149
+
150
+ options.body = {
151
+ val: value,
152
+ };
153
+
154
+ client.index(options, (error, response) => {
155
+ parseResponse(error, response, callback);
156
+ });
157
+ }
158
+
159
+ /**
160
+ * This function provides delete functionality to the database.
161
+ *
162
+ * The index, type, and ID will be parsed from the key, and this document will
163
+ * be deleted from the database.
164
+ *
165
+ * @param {String} key Key, of the format "test:test1" or, optionally, of the
166
+ * format "test:test1:check:check1"
167
+ * @param {function} callback Function will be called in the event of an error or on
168
+ * completion of a successful database write.
169
+ */
170
+ remove(key, callback) {
171
+ client.delete(key, (error, response) => {
172
+ parseResponse(error, response, callback);
173
+ });
174
+ }
175
+
176
+ /**
177
+ * This uses the bulk upload functionality of elasticsearch (url:port/_bulk).
178
+ *
179
+ * The CacheAndBufferLayer will periodically (every this.settings.writeInterval)
180
+ * flush writes that have already been done in the local cache out to the database.
181
+ *
182
+ * @param {Array} bulk An array of JSON data in the format:
183
+ * {"type":type, "key":key, "value":value}
184
+ * @param {function} callback This function will be called on an error or upon the
185
+ * successful completion of the database write.
186
+ */
187
+ doBulk(bulk, callback) {
188
+ // bulk is an array of JSON:
189
+ // example: [{"type":"set", "key":"sessionstorage:{id}", "value":{"cookie":{...}}]
190
+
191
+ const operations = [];
192
+
193
+ for (let counter = 0; counter < bulk.length; counter++) {
194
+ const indexTypeId = getIndexTypeId(bulk[counter].key);
195
+ const operationPayload = {
196
+ _index: indexTypeId.index,
197
+ _type: indexTypeId.type,
198
+ _id: indexTypeId.id,
199
+ };
200
+
201
+ switch (bulk[counter].type) {
202
+ case 'set':
203
+ operations.push({index: operationPayload});
204
+ operations.push({val: JSON.parse(bulk[counter].value)});
205
+ break;
206
+ case 'remove':
207
+ operations.push({delete: operationPayload});
208
+ break;
209
+ default:
210
+ continue;
211
+ }
212
+ }
213
+
214
+ // send bulk request
215
+ client.bulk({
216
+ body: operations,
217
+ }, (error, response) => {
218
+ parseResponse(error, response, callback);
219
+ });
220
+ }
221
+
222
+ close(callback) {
223
+ callback(null);
224
+ }
225
+ };
226
+
227
+ /** ************************
228
+ **** Helper functions ****
229
+ **************************/
230
+
231
+ /**
232
+ * This function parses a given key into an object with three
233
+ * fields, .index, .type, and .id. This object can then be
234
+ * used to build an elasticsearch path or to access an object
235
+ * for bulk updates.
236
+ *
237
+ * @param {String} key Key, of the format "test:test1" or, optionally, of the
238
+ * format "test:test1:check:check1"
239
+ */
240
+ const getIndexTypeId = (key) => {
241
+ const returnObject = {};
242
+
243
+ const splitKey = key.split(':');
244
+
245
+ if (splitKey.length === 4) {
246
+ /*
247
+ * This is for keys like test:test1:check:check1.
248
+ * These keys are stored at /base_index-test-check/test1/check1
249
+ */
250
+ returnObject.index = `${elasticsearchSettings.base_index}-${splitKey[0]}-${splitKey[2]}`;
251
+ returnObject.type = encodeURIComponent(splitKey[1]);
252
+ returnObject.id = splitKey[3];
253
+ } else {
254
+ // everything else ('test:test1') is stored /base_index/test/test1
255
+ returnObject.index = elasticsearchSettings.base_index;
256
+ returnObject.type = splitKey[0];
257
+ returnObject.id = encodeURIComponent(splitKey[1]);
258
+ }
259
+
260
+ return returnObject;
261
+ };
262
+
263
+ /**
264
+ * Extract data from elasticsearch responses, handle errors, handle callbacks.
265
+ */
266
+ const parseResponse = (error, response, callback) => {
267
+ if (error) {
268
+ // don't treat not found as an error (is this specific to etherpad?)
269
+ if (error.message === 'Not Found' && !response.found) {
270
+ callback(null, null);
271
+ return;
272
+ } else {
273
+ console.error('elasticsearch_db: ', error);
274
+ }
275
+ }
276
+
277
+ if (!error && response) {
278
+ response = response._source;
279
+
280
+ if (response) {
281
+ response = response.val;
282
+ }
283
+
284
+ response = JSON.stringify(response);
285
+ }
286
+
287
+ callback(error, response);
288
+ };
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+
3
+ const events = require('events');
4
+
5
+ exports.Database = class extends events.EventEmitter {
6
+ constructor(settings) {
7
+ super();
8
+ this.settings = {
9
+ writeInterval: 1,
10
+ ...settings,
11
+ };
12
+ settings.mock = this;
13
+ }
14
+
15
+ close(cb) {
16
+ this.emit('close', cb);
17
+ }
18
+
19
+ doBulk(ops, cb) {
20
+ this.emit('doBulk', ops, cb);
21
+ }
22
+
23
+ findKeys(key, notKey, cb) {
24
+ this.emit('findKeys', key, notKey, cb);
25
+ }
26
+
27
+ get(key, cb) {
28
+ this.emit('get', key, cb);
29
+ }
30
+
31
+ init(cb) {
32
+ this.emit('init', cb);
33
+ }
34
+
35
+ remove(key, cb) {
36
+ this.emit('remove', key, cb);
37
+ }
38
+
39
+ set(key, value, cb) {
40
+ this.emit('set', key, value, cb);
41
+ }
42
+ };