UNPKG

9.72 kBJavaScriptView Raw
1/**
2 * @copyright Copyright (c) 2019 Maxim Khorin <maksimovichu@gmail.com>
3 */
4'use strict';
5
6const Base = require('./Database');
7const mongodb = require('mongodb');
8const ObjectId = mongodb.ObjectID;
9
10module.exports = class MongoDatabase extends Base {
11
12 static normalizeId (value) {
13 return Array.isArray(value)
14 ? value.map(this.normalizeObjectId, this)
15 : this.normalizeObjectId(value);
16 }
17
18 static normalizeObjectId (value) {
19 return value instanceof ObjectId ? value : ObjectId.isValid(value) ? ObjectId(value) : null;
20 }
21
22 constructor (config) {
23 super({
24 schema: 'mongodb',
25 QueryBuilder: require('./MongoBuilder'),
26 client: mongodb.MongoClient,
27 ...config
28 });
29 this.settings.options = {
30 bufferMaxEntries: 0,
31 keepAlive: true,
32 readPreference: 'primary',
33 useNewUrlParser: true,
34 useUnifiedTopology: true,
35 ...this.settings.options
36 };
37 this._tableMap = {};
38 }
39
40 async openConnection () {
41 this._client = await this.client.connect(this.getUri(true), this.settings.options);
42 return this._client.db();
43 }
44
45 async closeConnection () {
46 if (this._client) {
47 await this._client.close(true);
48 this._client = null;
49 }
50 }
51
52 async isTableExists (name) {
53 for (const {collectionName} of await this._connection.collections()) {
54 if (name === collectionName) {
55 return true;
56 }
57 }
58 }
59
60 async getTableNames () {
61 const names = [];
62 for (const {collectionName} of await this._connection.collections()) {
63 names.push(collectionName);
64 }
65 return names;
66 }
67
68 getTable (name) {
69 if (!this._tableMap[name]) {
70 this._tableMap[name] = this._connection.collection(name);
71 }
72 return this._tableMap[name];
73 }
74
75 startSession () {
76 this.traceCommand('Start session');
77 return this._client.startSession();
78 }
79
80 endSession (session) {
81 this.traceCommand('End session');
82 session.endSession();
83 }
84
85 // OPERATIONS
86
87 create (table) {
88 this.traceCommand('create', {table});
89 return this._connection.createCollection(table);
90 }
91
92 find (table, query, options) {
93 this.traceCommand('find', {table, query});
94 return this.getTable(table).find(query, options).toArray();
95 }
96
97 distinct (table, key, query, options) {
98 this.traceCommand('distinct', {table, key, query});
99 return this.getTable(table).distinct(key, query, options);
100 }
101
102 async insert (table, data, options) {
103 this.traceCommand('insert', {table, data});
104 if (Array.isArray(data)) {
105 const result = await this.getTable(table).insertMany(data, options);
106 return result.insertedIds;
107 }
108 const result = await this.getTable(table).insertOne(data, options);
109 return result.insertedId;
110 }
111
112 upsert (table, query, data, options) {
113 this.traceCommand('upsert', {table, query, data});
114 return this.getTable(table).updateOne(query, {$set: data}, {upsert: true, ...options});
115 }
116
117 update (table, query, data, options) {
118 this.traceCommand('update', {table, query, data});
119 return this.getTable(table).updateOne(query, {$set: data}, options);
120 }
121
122 updateAll (table, query, data, options) {
123 this.traceCommand('updateAll', {table, query, data});
124 return this.getTable(table).updateMany(query, {$set: data}, options);
125 }
126
127 updateAllPull (table, query, data, options) {
128 this.traceCommand('updateAllPull', {table, query, data});
129 return this.getTable(table).updateMany(query, {$pull: data}, options);
130 }
131
132 updateAllPush (table, query, data, options) {
133 this.traceCommand('updateAllPush', {table, query, data});
134 return this.getTable(table).updateMany(query, {$push: data}, options);
135 }
136
137 unset (table, query, data, options) {
138 this.traceCommand('unset', {table, query, data});
139 return this.getTable(table).updateOne(query, {$unset: data}, options);
140 }
141
142 unsetAll (table, query, data, options) {
143 this.traceCommand('unsetAll', {table, query, data});
144 return this.getTable(table).updateMany(query, {$unset: data}, options);
145 }
146
147 delete (table, query = {}, options) {
148 this.traceCommand('delete', {table, query});
149 return this.getTable(table).deleteMany(query, options);
150 }
151
152 async drop (table) {
153 if (await this.isTableExists(table)) {
154 this.traceCommand('drop', {table});
155 return this.getTable(table).drop();
156 }
157 }
158
159 async dropAll () {
160 this.traceCommand('dropAll');
161 for (const name of await this.getTableNames()) {
162 await this.drop(name);
163 }
164 }
165
166 truncate (table) {
167 return this.drop(table);
168 }
169
170 async rename (table, target) {
171 if (await this.isTableExists(table)) {
172 this.traceCommand('rename', {table, target});
173 return this._connection.renameCollection(...arguments);
174 }
175 }
176
177 // AGGREGATE
178
179 count (table, query) {
180 this.traceCommand('count', {table, query});
181 return this.getTable(table).countDocuments(query);
182 }
183
184 // QUERY
185
186 async queryAll (query) {
187 const cmd = await this.buildQuery(query);
188 const cursor = this.getTable(cmd.from).find(cmd.where, query.getOptions());
189 if (cmd.select) {
190 cursor.project(cmd.select);
191 }
192 if (cmd.order) {
193 cursor.sort(cmd.order);
194 }
195 if (cmd.offset) {
196 cursor.skip(cmd.offset);
197 }
198 if (cmd.limit) {
199 cursor.limit(cmd.limit);
200 }
201 this.traceCommand('find', cmd);
202 let docs = await cursor.toArray();
203 if (!cmd.order) {
204 docs = query.sortOrderByKeys(docs);
205 }
206 return query.populate(docs);
207 }
208
209 async queryOne (query) {
210 const docs = await this.queryAll(query.limit(1));
211 return docs.length ? docs[0] : null;
212 }
213
214 async queryColumn (query, key) {
215 const data = await this.queryAll(query.raw().select(key));
216 if (Array.isArray(data)) {
217 return data.map(doc => doc[key]);
218 }
219 for (const name of Object.keys(data)) {
220 data[name] = data[name][key];
221 }
222 return data;
223 }
224
225 async queryDistinct (query, key) {
226 const cmd = await this.buildQuery(query);
227 return this.distinct(cmd.from, key, cmd.where, query.getOptions());
228 }
229
230 async queryScalar (query, key) {
231 const docs = await this.queryAll(query.raw().select(key).limit(1));
232 return docs.length ? docs[0][key] : undefined;
233 }
234
235 async queryInsert (query, data) {
236 const cmd = await this.buildQuery(query);
237 return this.insert(cmd.from, data, query.getOptions());
238 }
239
240 async queryUpdate (query, data) {
241 const cmd = await this.buildQuery(query);
242 return this.update(cmd.from, cmd.where, data, query.getOptions());
243 }
244
245 async queryUpdateAll (query, data) {
246 const cmd = await this.buildQuery(query);
247 return this.updateAll(cmd.from, cmd.where, data, query.getOptions());
248 }
249
250 async queryUpsert (query, data) {
251 const cmd = await this.buildQuery(query);
252 return this.upsert(cmd.from, cmd.where, data, query.getOptions());
253 }
254
255 async queryDelete (query) {
256 const cmd = await this.buildQuery(query);
257 return this.delete(cmd.from, cmd.where, query.getOptions());
258 }
259
260 async queryCount (query) {
261 const cmd = await this.buildQuery(query);
262 return this.count(cmd.from, cmd.where, query.getOptions());
263 }
264
265 // INDEXES
266
267 getIndexes (table, params = {full: true}) {
268 return this.getTable(table).indexInformation(params);
269 }
270
271 /**
272 * @param table
273 * @param data [{key: 1}, { name: [name], unique: true, ... }]
274 */
275 async createIndex (table, data) {
276 if (!await this.isTableExists(table)) {
277 await this.create(table);
278 }
279 this.traceCommand('createIndex', {table, data});
280 return this.getTable(table).createIndex(...data);
281 }
282
283 async dropIndex (table, name) {
284 if (await this.isTableExists(table)) {
285 this.traceCommand('dropIndex', {table, name});
286 return this.getTable(table).dropIndex(name);
287 }
288 }
289
290 async dropIndexes (table) {
291 if (await this.isTableExists(table)) {
292 this.traceCommand('dropIndexes', {table});
293 return this.getTable(table).dropIndexes();
294 }
295 }
296
297 async reindex (table) {
298 if (await this.isTableExists(table)) {
299 this.traceCommand('reindex', {table});
300 return this.getTable(table).reIndex();
301 }
302 }
303
304 async transact (handler) {
305 if (!this.enableTransactions) {
306 return handler();
307 }
308 const session = this.startSession();
309 session.startTransaction();
310 try {
311 await handler({session});
312 } finally {
313 await session.abortTransaction();
314 this.endSession(session);
315 }
316 }
317};
318module.exports.init();
\No newline at end of file