UNPKG

20.6 kBJavaScriptView Raw
1"use strict";
2var __importDefault = (this && this.__importDefault) || function (mod) {
3 return (mod && mod.__esModule) ? mod : { "default": mod };
4};
5Object.defineProperty(exports, "__esModule", { value: true });
6exports.Query = void 0;
7const stream_1 = __importDefault(require("stream"));
8const lodash_1 = __importDefault(require("lodash"));
9/**
10 * Collects conditions to query
11 */
12class Query {
13 /**
14 * Creates a query instance
15 */
16 constructor(model) {
17 this._find_single_id = false;
18 this._used = false;
19 this._model = model;
20 this._name = model._name;
21 this._connection = model._connection;
22 this._adapter = model._connection._adapter;
23 this._ifs = [];
24 this._current_if = true;
25 this._conditions = [];
26 this._includes = [];
27 this._options = {
28 conditions_of_group: [],
29 lean: model.lean_query,
30 node: 'master',
31 select_single: false,
32 };
33 }
34 clone() {
35 const cloned = new Query(this._model);
36 cloned._ifs = lodash_1.default.cloneDeep(this._ifs);
37 cloned._current_if = this._current_if;
38 cloned._conditions = lodash_1.default.cloneDeep(this._conditions);
39 cloned._includes = lodash_1.default.cloneDeep(this._includes);
40 cloned._options = lodash_1.default.cloneDeep(this._options);
41 cloned._find_single_id = this._find_single_id;
42 cloned._used = false;
43 return cloned;
44 }
45 find(id) {
46 if (!this._current_if) {
47 return this;
48 }
49 if (Array.isArray(id)) {
50 this._id = lodash_1.default.uniq(id);
51 this._find_single_id = false;
52 }
53 else {
54 this._id = id;
55 this._find_single_id = true;
56 }
57 return this;
58 }
59 /**
60 * Finds records by ids while preserving order.
61 */
62 findPreserve(ids) {
63 if (!this._current_if) {
64 return this;
65 }
66 this._id = lodash_1.default.uniq(ids);
67 this._find_single_id = false;
68 this._preserve_order_ids = ids;
69 return this;
70 }
71 /**
72 * Finds records near target
73 */
74 near(target) {
75 if (!this._current_if) {
76 return this;
77 }
78 this._options.near = target;
79 return this;
80 }
81 /**
82 * Finds records by condition
83 */
84 where(condition) {
85 if (!this._current_if) {
86 return this;
87 }
88 if (Array.isArray(condition)) {
89 condition.forEach((cond) => {
90 this._addCondition(cond);
91 });
92 }
93 else if (condition != null) {
94 this._addCondition(condition);
95 }
96 return this;
97 }
98 select(columns) {
99 if (!this._current_if) {
100 return this;
101 }
102 this._options.select_columns = undefined;
103 this._options.select_single = false;
104 if (columns != null) {
105 if (typeof columns === 'string') {
106 columns = columns.split(/\s+/);
107 columns.push('id');
108 }
109 if (columns.length > 0) {
110 this._options.select_columns = columns;
111 }
112 }
113 return this;
114 }
115 selectSingle(column) {
116 if (!this._current_if) {
117 return this;
118 }
119 this._options.select_columns = [column];
120 this._options.select_single = true;
121 return this;
122 }
123 /**
124 * Specifies orders of result
125 */
126 order(orders) {
127 if (!this._current_if) {
128 return this;
129 }
130 this._options.orders = orders;
131 return this;
132 }
133 group(group_by, fields) {
134 if (!this._current_if) {
135 return this;
136 }
137 this._options.group_by = undefined;
138 if (group_by) {
139 if (typeof group_by === 'string') {
140 group_by = group_by.split(/\s+/);
141 }
142 this._options.group_by = group_by;
143 }
144 this._options.group_fields = fields;
145 return this;
146 }
147 /**
148 * Returns only one record (or null if does not exists).
149 *
150 * This is different from limit(1). limit(1) returns array of length 1 while this returns an instance.
151 */
152 one() {
153 if (!this._current_if) {
154 return this;
155 }
156 this._options.limit = 1;
157 this._options.one = true;
158 return this;
159 }
160 /**
161 * Sets limit of query
162 */
163 limit(limit) {
164 if (!this._current_if) {
165 return this;
166 }
167 this._options.limit = limit;
168 return this;
169 }
170 /**
171 * Sets skip of query
172 */
173 skip(skip) {
174 if (!this._current_if) {
175 return this;
176 }
177 this._options.skip = skip;
178 return this;
179 }
180 /**
181 * Returns raw instances instead of model instances
182 * @see Query::exec
183 */
184 lean(lean = true) {
185 if (!this._current_if) {
186 return this;
187 }
188 this._options.lean = lean;
189 return this;
190 }
191 /**
192 * Makes a part of the query chain conditional
193 * @see Query::endif
194 */
195 if(condition) {
196 this._ifs.push(condition);
197 this._current_if = this._current_if && condition;
198 return this;
199 }
200 /**
201 * Ends last if
202 * @chainable
203 * @see Query::if
204 */
205 endif() {
206 this._ifs.pop();
207 this._current_if = true;
208 for (const condition of this._ifs) {
209 this._current_if = this._current_if && condition;
210 }
211 return this;
212 }
213 /**
214 * Cache result.
215 *
216 * If cache of key exists, actual query does not performed.
217 * If cache does not exist, query result will be saved in cache.
218 *
219 * Redis is used to cache.
220 */
221 cache(options) {
222 if (!this._current_if) {
223 return this;
224 }
225 this._options.cache = options;
226 return this;
227 }
228 /**
229 * Returns associated objects also
230 */
231 include(column, select) {
232 if (!this._current_if) {
233 return this;
234 }
235 this._includes.push({ column, select });
236 return this;
237 }
238 transaction(transaction) {
239 this._options.transaction = transaction;
240 return this;
241 }
242 using(node) {
243 this._options.node = node;
244 return this;
245 }
246 index_hint(hint) {
247 this._options.index_hint = hint;
248 return this;
249 }
250 /**
251 * Executes the query
252 * @see AdapterBase::findById
253 * @see AdapterBase::find
254 */
255 async exec(options) {
256 this._setUsed();
257 await this._model._checkReady();
258 if (this._options.cache && this._options.cache.key) {
259 try {
260 // try cache
261 return await this._model._loadFromCache(this._options.cache.key, this._options.cache.refresh);
262 }
263 catch (error) {
264 // no cache, execute query
265 const records = await this._execAndInclude(options);
266 // save result to cache
267 await this._model._saveToCache(this._options.cache.key, this._options.cache.ttl, records);
268 return records;
269 }
270 }
271 else {
272 return await this._execAndInclude(options);
273 }
274 }
275 /**
276 * Executes the query and returns a readable stream
277 * @see AdapterBase::findById
278 * @see AdapterBase::find
279 */
280 stream() {
281 this._setUsed();
282 const transformer = new stream_1.default.Transform({ objectMode: true });
283 transformer._transform = function (chunk, encoding, callback) {
284 this.push(chunk);
285 callback();
286 };
287 this._model._checkReady().then(() => {
288 this._adapter.stream(this._name, this._conditions, this._getAdapterFindOptions())
289 .on('error', (error) => {
290 transformer.emit('error', error);
291 }).pipe(transformer);
292 });
293 return transformer;
294 }
295 /**
296 * Explains the query
297 */
298 async explain() {
299 this._options.cache = undefined;
300 this._options.explain = true;
301 this._includes = [];
302 return await this.exec({ skip_log: true });
303 }
304 then(onfulfilled, onrejected) {
305 return this.exec().then(onfulfilled, onrejected);
306 }
307 /**
308 * Executes the query as a count operation
309 * @see AdapterBase::count
310 */
311 async count() {
312 this._setUsed();
313 await this._model._checkReady();
314 if (this._id || this._find_single_id) {
315 this._conditions.push({ id: this._id });
316 delete this._id;
317 }
318 return await this._adapter.count(this._name, this._conditions, this._options);
319 }
320 /**
321 * Executes the query as a update operation
322 * @see AdapterBase::update
323 */
324 async update(updates) {
325 this._setUsed();
326 await this._model._checkReady();
327 const errors = [];
328 const data = {};
329 this._validateAndBuildSaveData(errors, data, updates, '', updates);
330 if (errors.length > 0) {
331 throw new Error(errors.join(','));
332 }
333 if (this._id || this._find_single_id) {
334 this._conditions.push({ id: this._id });
335 delete this._id;
336 }
337 this._connection.log(this._name, 'update', { data, conditions: this._conditions, options: this._options });
338 return await this._adapter.updatePartial(this._name, data, this._conditions, this._options);
339 }
340 /**
341 * Executes the query as an insert or update operation
342 * @see AdapterBase::upsert
343 */
344 async upsert(updates) {
345 this._setUsed();
346 await this._model._checkReady();
347 const errors = [];
348 const data = {};
349 this._validateAndBuildSaveData(errors, data, updates, '', updates);
350 if (errors.length > 0) {
351 throw new Error(errors.join(','));
352 }
353 if (this._id || this._find_single_id) {
354 this._conditions.push({ id: this._id });
355 delete this._id;
356 }
357 this._connection.log(this._name, 'upsert', { data, conditions: this._conditions, options: this._options });
358 return await this._adapter.upsert(this._name, data, this._conditions, this._options);
359 }
360 /**
361 * Executes the query as a delete operation
362 * @see AdapterBase::delete
363 */
364 async delete(options) {
365 this._setUsed();
366 await this._model._checkReady();
367 if (this._id || this._find_single_id) {
368 this._conditions.push({ id: this._id });
369 delete this._id;
370 }
371 if (!(options && options.skip_log)) {
372 this._connection.log(this._name, 'delete', { conditions: this._conditions });
373 }
374 await this._doArchiveAndIntegrity(options);
375 return await this._adapter.delete(this._name, this._conditions, { transaction: this._options.transaction });
376 }
377 async _exec(find_options, options) {
378 if (this._find_single_id && this._conditions.length === 0) {
379 if (!(options && options.skip_log)) {
380 this._connection.log(this._name, 'find by id', { id: this._id, options: find_options });
381 }
382 if (!this._id) {
383 throw new Error('not found');
384 }
385 let record;
386 try {
387 record = await this._adapter.findById(this._name, this._id, find_options);
388 }
389 catch (error) {
390 throw new Error('not found');
391 }
392 if (!record) {
393 throw new Error('not found');
394 }
395 return record;
396 }
397 let expected_count;
398 if (this._id || this._find_single_id) {
399 if (Array.isArray(this._id)) {
400 if (this._id.length === 0) {
401 return [];
402 }
403 this._conditions.push({ id: { $in: this._id } });
404 expected_count = this._id.length;
405 }
406 else {
407 this._conditions.push({ id: this._id });
408 expected_count = 1;
409 }
410 }
411 if (!(options && options.skip_log)) {
412 this._connection.log(this._name, 'find', { conditions: this._conditions, options: find_options });
413 }
414 let records = await this._adapter.find(this._name, this._conditions, find_options);
415 if (expected_count != null) {
416 if (records.length !== expected_count) {
417 throw new Error('not found');
418 }
419 }
420 if (this._preserve_order_ids) {
421 records = this._preserve_order_ids.map((id) => {
422 for (const record of records) {
423 if (record.id === id) {
424 return record;
425 }
426 }
427 });
428 }
429 if (this._options.one) {
430 if (records.length > 1) {
431 throw new Error('unknown error');
432 }
433 if (records.length === 1) {
434 return records[0];
435 }
436 else {
437 return null;
438 }
439 }
440 else {
441 return records;
442 }
443 }
444 _getAdapterFindOptions() {
445 const select = [];
446 const select_raw = [];
447 if (this._options.select_columns) {
448 const schema_columns = Object.keys(this._model._schema);
449 const intermediate_paths = this._model._intermediate_paths;
450 this._options.select_columns.forEach((column) => {
451 if (schema_columns.indexOf(column) >= 0) {
452 select.push(column);
453 select_raw.push(column);
454 }
455 else if (intermediate_paths[column]) {
456 // select all nested columns
457 select_raw.push(column);
458 column += '.';
459 schema_columns.forEach((sc) => {
460 if (sc.startsWith(column)) {
461 select.push(sc);
462 }
463 });
464 }
465 });
466 }
467 let group_by;
468 if (this._options.group_by) {
469 group_by = this._options.group_by.map((column) => {
470 return this._model._schema[column]._dbname_us;
471 }).filter((column) => column != null);
472 }
473 const orders = [];
474 if (typeof this._options.orders === 'string') {
475 const avaliable_columns = ['id'];
476 avaliable_columns.push(...Object.keys(this._model._schema));
477 if (this._options.group_fields) {
478 avaliable_columns.push(...Object.keys(this._options.group_fields));
479 }
480 this._options.orders.split(/\s+/).forEach((order) => {
481 let asc = true;
482 if (order.startsWith('-')) {
483 asc = false;
484 order = order.slice(1);
485 }
486 if (avaliable_columns.indexOf(order) >= 0) {
487 orders.push(asc ? order : '-' + order);
488 }
489 });
490 }
491 return Object.assign({ conditions_of_group: this._options.conditions_of_group, explain: this._options.explain, group_by, group_fields: this._options.group_fields, lean: this._options.lean, limit: this._options.limit, near: this._options.near, node: this._options.node, index_hint: this._options.index_hint, orders, skip: this._options.skip, transaction: this._options.transaction }, (select_raw.length > 0 && { select, select_raw }));
492 }
493 async _execAndInclude(options) {
494 const records = await this._exec(this._getAdapterFindOptions(), options);
495 if (this._options.select_single) {
496 if (Array.isArray(records)) {
497 return lodash_1.default.map(records, this._options.select_columns[0]);
498 }
499 else {
500 if (records) {
501 return records[this._options.select_columns[0]];
502 }
503 else {
504 return null;
505 }
506 }
507 }
508 await Promise.all(this._includes.map(async (include) => {
509 await this._connection.fetchAssociated(records, include.column, include.select, {
510 lean: this._options.lean,
511 model: this._model,
512 transaction: this._options.transaction,
513 });
514 }));
515 return records;
516 }
517 _validateAndBuildSaveData(errors, data, updates, path, object) {
518 const model = this._model;
519 const schema = model._schema;
520 for (let column in object) {
521 const property = schema[path + column];
522 if (property) {
523 try {
524 model._validateColumn(updates, path + column, property, true);
525 }
526 catch (error) {
527 errors.push(error.message);
528 }
529 model._buildSaveDataColumn(data, updates, path + column, property, true);
530 }
531 else if (!object[column] && model._intermediate_paths[column]) {
532 // set all nested columns null
533 column += '.';
534 const temp = {};
535 Object.keys(schema).forEach((sc) => {
536 if (sc.startsWith(column)) {
537 temp[sc.substr(column.length)] = null;
538 }
539 });
540 this._validateAndBuildSaveData(errors, data, updates, path + column, temp);
541 }
542 else if (typeof object[column] === 'object') {
543 this._validateAndBuildSaveData(errors, data, updates, path + column + '.', object[column]);
544 }
545 }
546 }
547 async _doIntegrityActions(integrities, ids) {
548 const promises = integrities.map(async (integrity) => {
549 if (integrity.type === 'parent_nullify') {
550 await integrity.child.update(lodash_1.default.zipObject([integrity.column], [null]), lodash_1.default.zipObject([integrity.column], [ids]));
551 }
552 else if (integrity.type === 'parent_restrict') {
553 const count = (await integrity.child.count(lodash_1.default.zipObject([integrity.column], [ids])));
554 if (count > 0) {
555 throw new Error('rejected');
556 }
557 }
558 else if (integrity.type === 'parent_delete') {
559 await integrity.child.delete(lodash_1.default.zipObject([integrity.column], [ids]));
560 }
561 });
562 await Promise.all(promises);
563 }
564 async _doArchiveAndIntegrity(options) {
565 const need_archive = this._model.archive;
566 const integrities = this._model._integrities.filter((integrity) => integrity.type.substr(0, 7) === 'parent_');
567 const need_child_archive = integrities.some((integrity) => integrity.child.archive);
568 const need_integrity = need_child_archive || (integrities.length > 0 && !this._adapter.native_integrity);
569 if (!need_archive && !need_integrity) {
570 return;
571 }
572 // find all records to be deleted
573 const query = this._model.where(this._conditions);
574 if (!need_archive) { // we need only id field for integrity
575 query.select('');
576 }
577 const records = await query.exec({ skip_log: options && options.skip_log });
578 if (need_archive) {
579 const archive_records = records.map((record) => {
580 return { model: this._name, data: record };
581 });
582 await this._connection.models._Archive.createBulk(archive_records);
583 }
584 if (!need_integrity) {
585 return;
586 }
587 if (records.length === 0) {
588 return;
589 }
590 const ids = records.map((record) => record.id);
591 await this._doIntegrityActions(integrities, ids);
592 }
593 _addCondition(condition) {
594 if (this._options.group_fields) {
595 const keys = Object.keys(condition);
596 if (keys.length === 1 && Object.prototype.hasOwnProperty.call(this._options.group_fields, keys[0])) {
597 this._options.conditions_of_group.push(condition);
598 }
599 }
600 else {
601 this._conditions.push(condition);
602 }
603 }
604 _setUsed() {
605 if (this._used) {
606 throw new Error('Query object is already used');
607 }
608 this._used = true;
609 }
610}
611exports.Query = Query;