1 | 'use strict';
|
2 |
|
3 | const assert = require('assert');
|
4 | const _ = require('lodash');
|
5 | const Dottie = require('dottie');
|
6 |
|
7 | const Utils = require('./utils');
|
8 | const { logger } = require('./utils/logger');
|
9 | const BelongsTo = require('./associations/belongs-to');
|
10 | const BelongsToMany = require('./associations/belongs-to-many');
|
11 | const InstanceValidator = require('./instance-validator');
|
12 | const QueryTypes = require('./query-types');
|
13 | const sequelizeErrors = require('./errors');
|
14 | const Promise = require('./promise');
|
15 | const Association = require('./associations/base');
|
16 | const HasMany = require('./associations/has-many');
|
17 | const DataTypes = require('./data-types');
|
18 | const Hooks = require('./hooks');
|
19 | const associationsMixin = require('./associations/mixin');
|
20 | const Op = require('./operators');
|
21 | const { noDoubleNestedGroup } = require('./utils/deprecations');
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 | const validQueryKeywords = new Set(['where', 'attributes', 'paranoid', 'include', 'order', 'limit', 'offset',
|
28 | 'transaction', 'lock', 'raw', 'logging', 'benchmark', 'having', 'searchPath', 'rejectOnEmpty', 'plain',
|
29 | 'scope', 'group', 'through', 'defaults', 'distinct', 'primary', 'exception', 'type', 'hooks', 'force',
|
30 | 'name']);
|
31 |
|
32 |
|
33 | const nonCascadingOptions = ['include', 'attributes', 'originalAttributes', 'order', 'where', 'limit', 'offset', 'plain', 'group', 'having'];
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 | class Model {
|
55 | static get QueryInterface() {
|
56 | return this.sequelize.getQueryInterface();
|
57 | }
|
58 |
|
59 | static get QueryGenerator() {
|
60 | return this.QueryInterface.QueryGenerator;
|
61 | }
|
62 |
|
63 | |
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 | get sequelize() {
|
74 | return this.constructor.sequelize;
|
75 | }
|
76 |
|
77 | |
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 | constructor(values = {}, options = {}) {
|
87 | options = Object.assign({
|
88 | isNewRecord: true,
|
89 | _schema: this.constructor._schema,
|
90 | _schemaDelimiter: this.constructor._schemaDelimiter
|
91 | }, options || {});
|
92 |
|
93 | if (options.attributes) {
|
94 | options.attributes = options.attributes.map(attribute => Array.isArray(attribute) ? attribute[1] : attribute);
|
95 | }
|
96 |
|
97 | if (!options.includeValidated) {
|
98 | this.constructor._conformIncludes(options, this.constructor);
|
99 | if (options.include) {
|
100 | this.constructor._expandIncludeAll(options);
|
101 | this.constructor._validateIncludedElements(options);
|
102 | }
|
103 | }
|
104 |
|
105 | this.dataValues = {};
|
106 | this._previousDataValues = {};
|
107 | this._changed = {};
|
108 | this._modelOptions = this.constructor.options;
|
109 | this._options = options || {};
|
110 |
|
111 | |
112 |
|
113 |
|
114 |
|
115 |
|
116 | this.isNewRecord = options.isNewRecord;
|
117 |
|
118 | this._initValues(values, options);
|
119 | }
|
120 |
|
121 | _initValues(values, options) {
|
122 | let defaults;
|
123 | let key;
|
124 |
|
125 | values = values && _.clone(values) || {};
|
126 |
|
127 | if (options.isNewRecord) {
|
128 | defaults = {};
|
129 |
|
130 | if (this.constructor._hasDefaultValues) {
|
131 | defaults = _.mapValues(this.constructor._defaultValues, valueFn => {
|
132 | const value = valueFn();
|
133 | return value && value instanceof Utils.SequelizeMethod ? value : _.cloneDeep(value);
|
134 | });
|
135 | }
|
136 |
|
137 |
|
138 |
|
139 |
|
140 | if (this.constructor.primaryKeyAttributes.length) {
|
141 | this.constructor.primaryKeyAttributes.forEach(primaryKeyAttribute => {
|
142 | if (!Object.prototype.hasOwnProperty.call(defaults, primaryKeyAttribute)) {
|
143 | defaults[primaryKeyAttribute] = null;
|
144 | }
|
145 | });
|
146 | }
|
147 |
|
148 | if (this.constructor._timestampAttributes.createdAt && defaults[this.constructor._timestampAttributes.createdAt]) {
|
149 | this.dataValues[this.constructor._timestampAttributes.createdAt] = Utils.toDefaultValue(defaults[this.constructor._timestampAttributes.createdAt], this.sequelize.options.dialect);
|
150 | delete defaults[this.constructor._timestampAttributes.createdAt];
|
151 | }
|
152 |
|
153 | if (this.constructor._timestampAttributes.updatedAt && defaults[this.constructor._timestampAttributes.updatedAt]) {
|
154 | this.dataValues[this.constructor._timestampAttributes.updatedAt] = Utils.toDefaultValue(defaults[this.constructor._timestampAttributes.updatedAt], this.sequelize.options.dialect);
|
155 | delete defaults[this.constructor._timestampAttributes.updatedAt];
|
156 | }
|
157 |
|
158 | if (this.constructor._timestampAttributes.deletedAt && defaults[this.constructor._timestampAttributes.deletedAt]) {
|
159 | this.dataValues[this.constructor._timestampAttributes.deletedAt] = Utils.toDefaultValue(defaults[this.constructor._timestampAttributes.deletedAt], this.sequelize.options.dialect);
|
160 | delete defaults[this.constructor._timestampAttributes.deletedAt];
|
161 | }
|
162 |
|
163 | if (Object.keys(defaults).length) {
|
164 | for (key in defaults) {
|
165 | if (values[key] === undefined) {
|
166 | this.set(key, Utils.toDefaultValue(defaults[key], this.sequelize.options.dialect), { raw: true });
|
167 | delete values[key];
|
168 | }
|
169 | }
|
170 | }
|
171 | }
|
172 |
|
173 | this.set(values, options);
|
174 | }
|
175 |
|
176 |
|
177 | static _paranoidClause(model, options = {}) {
|
178 |
|
179 |
|
180 |
|
181 | if (options.include) {
|
182 | for (const include of options.include) {
|
183 | this._paranoidClause(include.model, include);
|
184 | }
|
185 | }
|
186 |
|
187 |
|
188 | if (_.get(options, 'groupedLimit.on.options.paranoid')) {
|
189 | const throughModel = _.get(options, 'groupedLimit.on.through.model');
|
190 | if (throughModel) {
|
191 | options.groupedLimit.through = this._paranoidClause(throughModel, options.groupedLimit.through);
|
192 | }
|
193 | }
|
194 |
|
195 | if (!model.options.timestamps || !model.options.paranoid || options.paranoid === false) {
|
196 |
|
197 | return options;
|
198 | }
|
199 |
|
200 | const deletedAtCol = model._timestampAttributes.deletedAt;
|
201 | const deletedAtAttribute = model.rawAttributes[deletedAtCol];
|
202 | const deletedAtObject = {};
|
203 |
|
204 | let deletedAtDefaultValue = Object.prototype.hasOwnProperty.call(deletedAtAttribute, 'defaultValue') ? deletedAtAttribute.defaultValue : null;
|
205 |
|
206 | deletedAtDefaultValue = deletedAtDefaultValue || {
|
207 | [Op.eq]: null
|
208 | };
|
209 |
|
210 | deletedAtObject[deletedAtAttribute.field || deletedAtCol] = deletedAtDefaultValue;
|
211 |
|
212 | if (Utils.isWhereEmpty(options.where)) {
|
213 | options.where = deletedAtObject;
|
214 | } else {
|
215 | options.where = { [Op.and]: [deletedAtObject, options.where] };
|
216 | }
|
217 |
|
218 | return options;
|
219 | }
|
220 |
|
221 | static _addDefaultAttributes() {
|
222 | const tail = {};
|
223 | let head = {};
|
224 |
|
225 |
|
226 |
|
227 | if (!_.some(this.rawAttributes, 'primaryKey')) {
|
228 | if ('id' in this.rawAttributes) {
|
229 |
|
230 | throw new Error(`A column called 'id' was added to the attributes of '${this.tableName}' but not marked with 'primaryKey: true'`);
|
231 | }
|
232 |
|
233 | head = {
|
234 | id: {
|
235 | type: new DataTypes.INTEGER(),
|
236 | allowNull: false,
|
237 | primaryKey: true,
|
238 | autoIncrement: true,
|
239 | _autoGenerated: true
|
240 | }
|
241 | };
|
242 | }
|
243 |
|
244 | if (this._timestampAttributes.createdAt) {
|
245 | tail[this._timestampAttributes.createdAt] = {
|
246 | type: DataTypes.DATE,
|
247 | allowNull: false,
|
248 | _autoGenerated: true
|
249 | };
|
250 | }
|
251 |
|
252 | if (this._timestampAttributes.updatedAt) {
|
253 | tail[this._timestampAttributes.updatedAt] = {
|
254 | type: DataTypes.DATE,
|
255 | allowNull: false,
|
256 | _autoGenerated: true
|
257 | };
|
258 | }
|
259 |
|
260 | if (this._timestampAttributes.deletedAt) {
|
261 | tail[this._timestampAttributes.deletedAt] = {
|
262 | type: DataTypes.DATE,
|
263 | _autoGenerated: true
|
264 | };
|
265 | }
|
266 |
|
267 | if (this._versionAttribute) {
|
268 | tail[this._versionAttribute] = {
|
269 | type: DataTypes.INTEGER,
|
270 | allowNull: false,
|
271 | defaultValue: 0,
|
272 | _autoGenerated: true
|
273 | };
|
274 | }
|
275 |
|
276 | const existingAttributes = _.clone(this.rawAttributes);
|
277 | this.rawAttributes = {};
|
278 |
|
279 | _.each(head, (value, attr) => {
|
280 | this.rawAttributes[attr] = value;
|
281 | });
|
282 |
|
283 | _.each(existingAttributes, (value, attr) => {
|
284 | this.rawAttributes[attr] = value;
|
285 | });
|
286 |
|
287 | _.each(tail, (value, attr) => {
|
288 | if (this.rawAttributes[attr] === undefined) {
|
289 | this.rawAttributes[attr] = value;
|
290 | }
|
291 | });
|
292 |
|
293 | if (!Object.keys(this.primaryKeys).length) {
|
294 | this.primaryKeys.id = this.rawAttributes.id;
|
295 | }
|
296 | }
|
297 |
|
298 | static _findAutoIncrementAttribute() {
|
299 | this.autoIncrementAttribute = null;
|
300 |
|
301 | for (const name in this.rawAttributes) {
|
302 | if (Object.prototype.hasOwnProperty.call(this.rawAttributes, name)) {
|
303 | const definition = this.rawAttributes[name];
|
304 | if (definition && definition.autoIncrement) {
|
305 | if (this.autoIncrementAttribute) {
|
306 | throw new Error('Invalid Instance definition. Only one autoincrement field allowed.');
|
307 | }
|
308 | this.autoIncrementAttribute = name;
|
309 | }
|
310 | }
|
311 | }
|
312 | }
|
313 |
|
314 | static _conformIncludes(options, self) {
|
315 | if (!options.include) return;
|
316 |
|
317 |
|
318 | if (!Array.isArray(options.include)) {
|
319 | options.include = [options.include];
|
320 | } else if (!options.include.length) {
|
321 | delete options.include;
|
322 | return;
|
323 | }
|
324 |
|
325 |
|
326 | options.include = options.include.map(include => this._conformInclude(include, self));
|
327 | }
|
328 |
|
329 | static _transformStringAssociation(include, self) {
|
330 | if (self && typeof include === 'string') {
|
331 | if (!Object.prototype.hasOwnProperty.call(self.associations, include)) {
|
332 | throw new Error(`Association with alias "${include}" does not exist on ${self.name}`);
|
333 | }
|
334 | return self.associations[include];
|
335 | }
|
336 | return include;
|
337 | }
|
338 |
|
339 | static _conformInclude(include, self) {
|
340 | if (include) {
|
341 | let model;
|
342 |
|
343 | if (include._pseudo) return include;
|
344 |
|
345 | include = this._transformStringAssociation(include, self);
|
346 |
|
347 | if (include instanceof Association) {
|
348 | if (self && include.target.name === self.name) {
|
349 | model = include.source;
|
350 | } else {
|
351 | model = include.target;
|
352 | }
|
353 |
|
354 | return { model, association: include, as: include.as };
|
355 | }
|
356 |
|
357 | if (include.prototype && include.prototype instanceof Model) {
|
358 | return { model: include };
|
359 | }
|
360 |
|
361 | if (_.isPlainObject(include)) {
|
362 | if (include.association) {
|
363 | include.association = this._transformStringAssociation(include.association, self);
|
364 |
|
365 | if (self && include.association.target.name === self.name) {
|
366 | model = include.association.source;
|
367 | } else {
|
368 | model = include.association.target;
|
369 | }
|
370 |
|
371 | if (!include.model) include.model = model;
|
372 | if (!include.as) include.as = include.association.as;
|
373 |
|
374 | this._conformIncludes(include, model);
|
375 | return include;
|
376 | }
|
377 |
|
378 | if (include.model) {
|
379 | this._conformIncludes(include, include.model);
|
380 | return include;
|
381 | }
|
382 |
|
383 | if (include.all) {
|
384 | this._conformIncludes(include);
|
385 | return include;
|
386 | }
|
387 | }
|
388 | }
|
389 |
|
390 | throw new Error('Include unexpected. Element has to be either a Model, an Association or an object.');
|
391 | }
|
392 |
|
393 | static _expandIncludeAllElement(includes, include) {
|
394 |
|
395 | let all = include.all;
|
396 | delete include.all;
|
397 |
|
398 | if (all !== true) {
|
399 | if (!Array.isArray(all)) {
|
400 | all = [all];
|
401 | }
|
402 |
|
403 | const validTypes = {
|
404 | BelongsTo: true,
|
405 | HasOne: true,
|
406 | HasMany: true,
|
407 | One: ['BelongsTo', 'HasOne'],
|
408 | Has: ['HasOne', 'HasMany'],
|
409 | Many: ['HasMany']
|
410 | };
|
411 |
|
412 | for (let i = 0; i < all.length; i++) {
|
413 | const type = all[i];
|
414 | if (type === 'All') {
|
415 | all = true;
|
416 | break;
|
417 | }
|
418 |
|
419 | const types = validTypes[type];
|
420 | if (!types) {
|
421 | throw new sequelizeErrors.EagerLoadingError(`include all '${type}' is not valid - must be BelongsTo, HasOne, HasMany, One, Has, Many or All`);
|
422 | }
|
423 |
|
424 | if (types !== true) {
|
425 |
|
426 | all.splice(i, 1);
|
427 | i--;
|
428 | for (let j = 0; j < types.length; j++) {
|
429 | if (!all.includes(types[j])) {
|
430 | all.unshift(types[j]);
|
431 | i++;
|
432 | }
|
433 | }
|
434 | }
|
435 | }
|
436 | }
|
437 |
|
438 |
|
439 | const nested = include.nested;
|
440 | if (nested) {
|
441 | delete include.nested;
|
442 |
|
443 | if (!include.include) {
|
444 | include.include = [];
|
445 | } else if (!Array.isArray(include.include)) {
|
446 | include.include = [include.include];
|
447 | }
|
448 | }
|
449 |
|
450 | const used = [];
|
451 | (function addAllIncludes(parent, includes) {
|
452 | _.forEach(parent.associations, association => {
|
453 | if (all !== true && !all.includes(association.associationType)) {
|
454 | return;
|
455 | }
|
456 |
|
457 |
|
458 | const model = association.target;
|
459 | const as = association.options.as;
|
460 |
|
461 | const predicate = { model };
|
462 | if (as) {
|
463 |
|
464 | predicate.as = as;
|
465 | }
|
466 |
|
467 | if (_.some(includes, predicate)) {
|
468 | return;
|
469 | }
|
470 |
|
471 |
|
472 | if (nested && used.includes(model)) {
|
473 | return;
|
474 | }
|
475 | used.push(parent);
|
476 |
|
477 |
|
478 | const thisInclude = Utils.cloneDeep(include);
|
479 | thisInclude.model = model;
|
480 | if (as) {
|
481 | thisInclude.as = as;
|
482 | }
|
483 | includes.push(thisInclude);
|
484 |
|
485 |
|
486 | if (nested) {
|
487 | addAllIncludes(model, thisInclude.include);
|
488 | if (thisInclude.include.length === 0) delete thisInclude.include;
|
489 | }
|
490 | });
|
491 | used.pop();
|
492 | })(this, includes);
|
493 | }
|
494 |
|
495 | static _validateIncludedElements(options, tableNames) {
|
496 | if (!options.model) options.model = this;
|
497 |
|
498 | tableNames = tableNames || {};
|
499 | options.includeNames = [];
|
500 | options.includeMap = {};
|
501 |
|
502 |
|
503 | options.hasSingleAssociation = false;
|
504 | options.hasMultiAssociation = false;
|
505 |
|
506 | if (!options.parent) {
|
507 | options.topModel = options.model;
|
508 | options.topLimit = options.limit;
|
509 | }
|
510 |
|
511 | options.include = options.include.map(include => {
|
512 | include = this._conformInclude(include);
|
513 | include.parent = options;
|
514 | include.topLimit = options.topLimit;
|
515 |
|
516 | this._validateIncludedElement.call(options.model, include, tableNames, options);
|
517 |
|
518 | if (include.duplicating === undefined) {
|
519 | include.duplicating = include.association.isMultiAssociation;
|
520 | }
|
521 |
|
522 | include.hasDuplicating = include.hasDuplicating || include.duplicating;
|
523 | include.hasRequired = include.hasRequired || include.required;
|
524 |
|
525 | options.hasDuplicating = options.hasDuplicating || include.hasDuplicating;
|
526 | options.hasRequired = options.hasRequired || include.required;
|
527 |
|
528 | options.hasWhere = options.hasWhere || include.hasWhere || !!include.where;
|
529 | return include;
|
530 | });
|
531 |
|
532 | for (const include of options.include) {
|
533 | include.hasParentWhere = options.hasParentWhere || !!options.where;
|
534 | include.hasParentRequired = options.hasParentRequired || !!options.required;
|
535 |
|
536 | if (include.subQuery !== false && options.hasDuplicating && options.topLimit) {
|
537 | if (include.duplicating) {
|
538 | include.subQuery = false;
|
539 | include.subQueryFilter = include.hasRequired;
|
540 | } else {
|
541 | include.subQuery = include.hasRequired;
|
542 | include.subQueryFilter = false;
|
543 | }
|
544 | } else {
|
545 | include.subQuery = include.subQuery || false;
|
546 | if (include.duplicating) {
|
547 | include.subQueryFilter = include.subQuery;
|
548 | include.subQuery = false;
|
549 | } else {
|
550 | include.subQueryFilter = false;
|
551 | include.subQuery = include.subQuery || include.hasParentRequired && include.hasRequired;
|
552 | }
|
553 | }
|
554 |
|
555 | options.includeMap[include.as] = include;
|
556 | options.includeNames.push(include.as);
|
557 |
|
558 |
|
559 | if (options.topModel === options.model && options.subQuery === undefined && options.topLimit) {
|
560 | if (include.subQuery) {
|
561 | options.subQuery = include.subQuery;
|
562 | } else if (include.hasDuplicating) {
|
563 | options.subQuery = true;
|
564 | }
|
565 | }
|
566 |
|
567 |
|
568 | options.hasIncludeWhere = options.hasIncludeWhere || include.hasIncludeWhere || !!include.where;
|
569 | options.hasIncludeRequired = options.hasIncludeRequired || include.hasIncludeRequired || !!include.required;
|
570 |
|
571 | if (include.association.isMultiAssociation || include.hasMultiAssociation) {
|
572 | options.hasMultiAssociation = true;
|
573 | }
|
574 | if (include.association.isSingleAssociation || include.hasSingleAssociation) {
|
575 | options.hasSingleAssociation = true;
|
576 | }
|
577 | }
|
578 |
|
579 | if (options.topModel === options.model && options.subQuery === undefined) {
|
580 | options.subQuery = false;
|
581 | }
|
582 | return options;
|
583 | }
|
584 |
|
585 | static _validateIncludedElement(include, tableNames, options) {
|
586 | tableNames[include.model.getTableName()] = true;
|
587 |
|
588 | if (include.attributes && !options.raw) {
|
589 | include.model._expandAttributes(include);
|
590 |
|
591 | include.originalAttributes = this._injectDependentVirtualAttributes(include.attributes);
|
592 |
|
593 | include = Utils.mapFinderOptions(include, include.model);
|
594 |
|
595 | if (include.attributes.length) {
|
596 | _.each(include.model.primaryKeys, (attr, key) => {
|
597 |
|
598 | if (!include.attributes.some(includeAttr => {
|
599 | if (attr.field !== key) {
|
600 | return Array.isArray(includeAttr) && includeAttr[0] === attr.field && includeAttr[1] === key;
|
601 | }
|
602 | return includeAttr === key;
|
603 | })) {
|
604 | include.attributes.unshift(key);
|
605 | }
|
606 | });
|
607 | }
|
608 | } else {
|
609 | include = Utils.mapFinderOptions(include, include.model);
|
610 | }
|
611 |
|
612 |
|
613 | if (include._pseudo) {
|
614 | include.attributes = Object.keys(include.model.tableAttributes);
|
615 | return Utils.mapFinderOptions(include, include.model);
|
616 | }
|
617 |
|
618 |
|
619 | const association = include.association || this._getIncludedAssociation(include.model, include.as);
|
620 |
|
621 | include.association = association;
|
622 | include.as = association.as;
|
623 |
|
624 |
|
625 | if (include.association.through && Object(include.association.through.model) === include.association.through.model) {
|
626 | if (!include.include) include.include = [];
|
627 | const through = include.association.through;
|
628 |
|
629 | include.through = _.defaults(include.through || {}, {
|
630 | model: through.model,
|
631 | as: through.model.name,
|
632 | association: {
|
633 | isSingleAssociation: true
|
634 | },
|
635 | _pseudo: true,
|
636 | parent: include
|
637 | });
|
638 |
|
639 |
|
640 | if (through.scope) {
|
641 | include.through.where = include.through.where ? { [Op.and]: [include.through.where, through.scope] } : through.scope;
|
642 | }
|
643 |
|
644 | include.include.push(include.through);
|
645 | tableNames[through.tableName] = true;
|
646 | }
|
647 |
|
648 |
|
649 | let model;
|
650 | if (include.model.scoped === true) {
|
651 |
|
652 | model = include.model;
|
653 | } else {
|
654 |
|
655 | model = include.association.target.name === include.model.name ? include.association.target : include.association.source;
|
656 | }
|
657 |
|
658 | model._injectScope(include);
|
659 |
|
660 |
|
661 | if (!include.attributes) {
|
662 | include.attributes = Object.keys(include.model.tableAttributes);
|
663 | }
|
664 |
|
665 | include = Utils.mapFinderOptions(include, include.model);
|
666 |
|
667 | if (include.required === undefined) {
|
668 | include.required = !!include.where;
|
669 | }
|
670 |
|
671 | if (include.association.scope) {
|
672 | include.where = include.where ? { [Op.and]: [include.where, include.association.scope] } : include.association.scope;
|
673 | }
|
674 |
|
675 | if (include.limit && include.separate === undefined) {
|
676 | include.separate = true;
|
677 | }
|
678 |
|
679 | if (include.separate === true) {
|
680 | if (!(include.association instanceof HasMany)) {
|
681 | throw new Error('Only HasMany associations support include.separate');
|
682 | }
|
683 |
|
684 | include.duplicating = false;
|
685 |
|
686 | if (
|
687 | options.attributes
|
688 | && options.attributes.length
|
689 | && !_.flattenDepth(options.attributes, 2).includes(association.sourceKey)
|
690 | ) {
|
691 | options.attributes.push(association.sourceKey);
|
692 | }
|
693 |
|
694 | if (
|
695 | include.attributes
|
696 | && include.attributes.length
|
697 | && !_.flattenDepth(include.attributes, 2).includes(association.foreignKey)
|
698 | ) {
|
699 | include.attributes.push(association.foreignKey);
|
700 | }
|
701 | }
|
702 |
|
703 |
|
704 | if (Object.prototype.hasOwnProperty.call(include, 'include')) {
|
705 | this._validateIncludedElements.call(include.model, include, tableNames);
|
706 | }
|
707 |
|
708 | return include;
|
709 | }
|
710 |
|
711 | static _getIncludedAssociation(targetModel, targetAlias) {
|
712 | const associations = this.getAssociations(targetModel);
|
713 | let association = null;
|
714 | if (associations.length === 0) {
|
715 | throw new sequelizeErrors.EagerLoadingError(`${targetModel.name} is not associated to ${this.name}!`);
|
716 | }
|
717 | if (associations.length === 1) {
|
718 | association = this.getAssociationForAlias(targetModel, targetAlias);
|
719 | if (association) {
|
720 | return association;
|
721 | }
|
722 | if (targetAlias) {
|
723 | const existingAliases = this.getAssociations(targetModel).map(association => association.as);
|
724 | throw new sequelizeErrors.EagerLoadingError(`${targetModel.name} is associated to ${this.name} using an alias. ` +
|
725 | `You've included an alias (${targetAlias}), but it does not match the alias(es) defined in your association (${existingAliases.join(', ')}).`);
|
726 | }
|
727 | throw new sequelizeErrors.EagerLoadingError(`${targetModel.name} is associated to ${this.name} using an alias. ` +
|
728 | 'You must use the \'as\' keyword to specify the alias within your include statement.');
|
729 | }
|
730 | association = this.getAssociationForAlias(targetModel, targetAlias);
|
731 | if (!association) {
|
732 | throw new sequelizeErrors.EagerLoadingError(`${targetModel.name} is associated to ${this.name} multiple times. ` +
|
733 | 'To identify the correct association, you must use the \'as\' keyword to specify the alias of the association you want to include.');
|
734 | }
|
735 | return association;
|
736 | }
|
737 |
|
738 |
|
739 | static _expandIncludeAll(options) {
|
740 | const includes = options.include;
|
741 | if (!includes) {
|
742 | return;
|
743 | }
|
744 |
|
745 | for (let index = 0; index < includes.length; index++) {
|
746 | const include = includes[index];
|
747 |
|
748 | if (include.all) {
|
749 | includes.splice(index, 1);
|
750 | index--;
|
751 |
|
752 | this._expandIncludeAllElement(includes, include);
|
753 | }
|
754 | }
|
755 |
|
756 | includes.forEach(include => {
|
757 | this._expandIncludeAll.call(include.model, include);
|
758 | });
|
759 | }
|
760 |
|
761 | static _conformIndex(index) {
|
762 | if (!index.fields) {
|
763 | throw new Error('Missing "fields" property for index definition');
|
764 | }
|
765 |
|
766 | index = _.defaults(index, {
|
767 | type: '',
|
768 | parser: null
|
769 | });
|
770 |
|
771 | if (index.type && index.type.toLowerCase() === 'unique') {
|
772 | index.unique = true;
|
773 | delete index.type;
|
774 | }
|
775 |
|
776 | return index;
|
777 | }
|
778 |
|
779 |
|
780 | static _uniqIncludes(options) {
|
781 | if (!options.include) return;
|
782 |
|
783 | options.include = _(options.include)
|
784 | .groupBy(include => `${include.model && include.model.name}-${include.as}`)
|
785 | .map(includes => this._assignOptions(...includes))
|
786 | .value();
|
787 | }
|
788 |
|
789 | static _baseMerge(...args) {
|
790 | _.assignWith(...args);
|
791 | this._conformIncludes(args[0], this);
|
792 | this._uniqIncludes(args[0]);
|
793 | return args[0];
|
794 | }
|
795 |
|
796 | static _mergeFunction(objValue, srcValue, key) {
|
797 | if (Array.isArray(objValue) && Array.isArray(srcValue)) {
|
798 | return _.union(objValue, srcValue);
|
799 | }
|
800 | if (key === 'where' || key === 'having') {
|
801 | if (srcValue instanceof Utils.SequelizeMethod) {
|
802 | srcValue = { [Op.and]: srcValue };
|
803 | }
|
804 | if (_.isPlainObject(objValue) && _.isPlainObject(srcValue)) {
|
805 | return Object.assign(objValue, srcValue);
|
806 | }
|
807 | } else if (key === 'attributes' && _.isPlainObject(objValue) && _.isPlainObject(srcValue)) {
|
808 | return _.assignWith(objValue, srcValue, (objValue, srcValue) => {
|
809 | if (Array.isArray(objValue) && Array.isArray(srcValue)) {
|
810 | return _.union(objValue, srcValue);
|
811 | }
|
812 | });
|
813 | }
|
814 |
|
815 |
|
816 |
|
817 | if (srcValue) {
|
818 | return Utils.cloneDeep(srcValue, true);
|
819 | }
|
820 | return srcValue === undefined ? objValue : srcValue;
|
821 | }
|
822 |
|
823 | static _assignOptions(...args) {
|
824 | return this._baseMerge(...args, this._mergeFunction);
|
825 | }
|
826 |
|
827 | static _defaultsOptions(target, opts) {
|
828 | return this._baseMerge(target, opts, (srcValue, objValue, key) => {
|
829 | return this._mergeFunction(objValue, srcValue, key);
|
830 | });
|
831 | }
|
832 |
|
833 | |
834 |
|
835 |
|
836 |
|
837 |
|
838 |
|
839 |
|
840 |
|
841 |
|
842 |
|
843 |
|
844 |
|
845 |
|
846 |
|
847 |
|
848 |
|
849 |
|
850 |
|
851 |
|
852 |
|
853 |
|
854 |
|
855 |
|
856 |
|
857 |
|
858 |
|
859 |
|
860 |
|
861 |
|
862 |
|
863 |
|
864 |
|
865 |
|
866 |
|
867 |
|
868 |
|
869 |
|
870 |
|
871 |
|
872 |
|
873 |
|
874 |
|
875 |
|
876 |
|
877 |
|
878 |
|
879 |
|
880 |
|
881 |
|
882 |
|
883 |
|
884 |
|
885 |
|
886 |
|
887 |
|
888 |
|
889 |
|
890 |
|
891 |
|
892 |
|
893 |
|
894 |
|
895 |
|
896 |
|
897 |
|
898 |
|
899 |
|
900 |
|
901 |
|
902 |
|
903 |
|
904 |
|
905 |
|
906 |
|
907 |
|
908 |
|
909 |
|
910 |
|
911 |
|
912 |
|
913 |
|
914 |
|
915 |
|
916 |
|
917 |
|
918 |
|
919 |
|
920 |
|
921 |
|
922 |
|
923 |
|
924 | static init(attributes, options = {}) {
|
925 | if (!options.sequelize) {
|
926 | throw new Error('No Sequelize instance passed');
|
927 | }
|
928 |
|
929 | this.sequelize = options.sequelize;
|
930 |
|
931 | const globalOptions = this.sequelize.options;
|
932 |
|
933 | options = Utils.merge(_.cloneDeep(globalOptions.define), options);
|
934 |
|
935 | if (!options.modelName) {
|
936 | options.modelName = this.name;
|
937 | }
|
938 |
|
939 | options = Utils.merge({
|
940 | name: {
|
941 | plural: Utils.pluralize(options.modelName),
|
942 | singular: Utils.singularize(options.modelName)
|
943 | },
|
944 | indexes: [],
|
945 | omitNull: globalOptions.omitNull,
|
946 | schema: globalOptions.schema
|
947 | }, options);
|
948 |
|
949 | this.sequelize.runHooks('beforeDefine', attributes, options);
|
950 |
|
951 | if (options.modelName !== this.name) {
|
952 | Object.defineProperty(this, 'name', { value: options.modelName });
|
953 | }
|
954 | delete options.modelName;
|
955 |
|
956 | this.options = Object.assign({
|
957 | timestamps: true,
|
958 | validate: {},
|
959 | freezeTableName: false,
|
960 | underscored: false,
|
961 | paranoid: false,
|
962 | rejectOnEmpty: false,
|
963 | whereCollection: null,
|
964 | schema: null,
|
965 | schemaDelimiter: '',
|
966 | defaultScope: {},
|
967 | scopes: {},
|
968 | indexes: []
|
969 | }, options);
|
970 |
|
971 |
|
972 | if (this.sequelize.isDefined(this.name)) {
|
973 | this.sequelize.modelManager.removeModel(this.sequelize.modelManager.getModel(this.name));
|
974 | }
|
975 |
|
976 | this.associations = {};
|
977 | this._setupHooks(options.hooks);
|
978 |
|
979 | this.underscored = this.options.underscored;
|
980 |
|
981 | if (!this.options.tableName) {
|
982 | this.tableName = this.options.freezeTableName ? this.name : Utils.underscoredIf(Utils.pluralize(this.name), this.underscored);
|
983 | } else {
|
984 | this.tableName = this.options.tableName;
|
985 | }
|
986 |
|
987 | this._schema = this.options.schema;
|
988 | this._schemaDelimiter = this.options.schemaDelimiter;
|
989 |
|
990 |
|
991 | _.each(options.validate, (validator, validatorType) => {
|
992 | if (Object.prototype.hasOwnProperty.call(attributes, validatorType)) {
|
993 | throw new Error(`A model validator function must not have the same name as a field. Model: ${this.name}, field/validation name: ${validatorType}`);
|
994 | }
|
995 |
|
996 | if (typeof validator !== 'function') {
|
997 | throw new Error(`Members of the validate option must be functions. Model: ${this.name}, error with validate member ${validatorType}`);
|
998 | }
|
999 | });
|
1000 |
|
1001 | this.rawAttributes = _.mapValues(attributes, (attribute, name) => {
|
1002 | attribute = this.sequelize.normalizeAttribute(attribute);
|
1003 |
|
1004 | if (attribute.type === undefined) {
|
1005 | throw new Error(`Unrecognized datatype for attribute "${this.name}.${name}"`);
|
1006 | }
|
1007 |
|
1008 | if (attribute.allowNull !== false && _.get(attribute, 'validate.notNull')) {
|
1009 | throw new Error(`Invalid definition for "${this.name}.${name}", "notNull" validator is only allowed with "allowNull:false"`);
|
1010 | }
|
1011 |
|
1012 | if (_.get(attribute, 'references.model.prototype') instanceof Model) {
|
1013 | attribute.references.model = attribute.references.model.getTableName();
|
1014 | }
|
1015 |
|
1016 | return attribute;
|
1017 | });
|
1018 |
|
1019 | const tableName = this.getTableName();
|
1020 | this._indexes = this.options.indexes
|
1021 | .map(index => Utils.nameIndex(this._conformIndex(index), tableName));
|
1022 |
|
1023 | this.primaryKeys = {};
|
1024 | this._readOnlyAttributes = new Set();
|
1025 | this._timestampAttributes = {};
|
1026 |
|
1027 |
|
1028 | if (this.options.timestamps) {
|
1029 | if (this.options.createdAt !== false) {
|
1030 | this._timestampAttributes.createdAt = this.options.createdAt || 'createdAt';
|
1031 | this._readOnlyAttributes.add(this._timestampAttributes.createdAt);
|
1032 | }
|
1033 | if (this.options.updatedAt !== false) {
|
1034 | this._timestampAttributes.updatedAt = this.options.updatedAt || 'updatedAt';
|
1035 | this._readOnlyAttributes.add(this._timestampAttributes.updatedAt);
|
1036 | }
|
1037 | if (this.options.paranoid && this.options.deletedAt !== false) {
|
1038 | this._timestampAttributes.deletedAt = this.options.deletedAt || 'deletedAt';
|
1039 | this._readOnlyAttributes.add(this._timestampAttributes.deletedAt);
|
1040 | }
|
1041 | }
|
1042 |
|
1043 |
|
1044 | if (this.options.version) {
|
1045 | this._versionAttribute = typeof this.options.version === 'string' ? this.options.version : 'version';
|
1046 | this._readOnlyAttributes.add(this._versionAttribute);
|
1047 | }
|
1048 |
|
1049 | this._hasReadOnlyAttributes = this._readOnlyAttributes.size > 0;
|
1050 |
|
1051 |
|
1052 | this._addDefaultAttributes();
|
1053 | this.refreshAttributes();
|
1054 | this._findAutoIncrementAttribute();
|
1055 |
|
1056 | this._scope = this.options.defaultScope;
|
1057 | this._scopeNames = ['defaultScope'];
|
1058 |
|
1059 | this.sequelize.modelManager.addModel(this);
|
1060 | this.sequelize.runHooks('afterDefine', this);
|
1061 |
|
1062 | return this;
|
1063 | }
|
1064 |
|
1065 | static refreshAttributes() {
|
1066 | const attributeManipulation = {};
|
1067 |
|
1068 | this.prototype._customGetters = {};
|
1069 | this.prototype._customSetters = {};
|
1070 |
|
1071 | ['get', 'set'].forEach(type => {
|
1072 | const opt = `${type}terMethods`;
|
1073 | const funcs = _.clone(_.isObject(this.options[opt]) ? this.options[opt] : {});
|
1074 | const _custom = type === 'get' ? this.prototype._customGetters : this.prototype._customSetters;
|
1075 |
|
1076 | _.each(funcs, (method, attribute) => {
|
1077 | _custom[attribute] = method;
|
1078 |
|
1079 | if (type === 'get') {
|
1080 | funcs[attribute] = function() {
|
1081 | return this.get(attribute);
|
1082 | };
|
1083 | }
|
1084 | if (type === 'set') {
|
1085 | funcs[attribute] = function(value) {
|
1086 | return this.set(attribute, value);
|
1087 | };
|
1088 | }
|
1089 | });
|
1090 |
|
1091 | _.each(this.rawAttributes, (options, attribute) => {
|
1092 | if (Object.prototype.hasOwnProperty.call(options, type)) {
|
1093 | _custom[attribute] = options[type];
|
1094 | }
|
1095 |
|
1096 | if (type === 'get') {
|
1097 | funcs[attribute] = function() {
|
1098 | return this.get(attribute);
|
1099 | };
|
1100 | }
|
1101 | if (type === 'set') {
|
1102 | funcs[attribute] = function(value) {
|
1103 | return this.set(attribute, value);
|
1104 | };
|
1105 | }
|
1106 | });
|
1107 |
|
1108 | _.each(funcs, (fct, name) => {
|
1109 | if (!attributeManipulation[name]) {
|
1110 | attributeManipulation[name] = {
|
1111 | configurable: true
|
1112 | };
|
1113 | }
|
1114 | attributeManipulation[name][type] = fct;
|
1115 | });
|
1116 | });
|
1117 |
|
1118 | this._dataTypeChanges = {};
|
1119 | this._dataTypeSanitizers = {};
|
1120 |
|
1121 | this._hasBooleanAttributes = false;
|
1122 | this._hasDateAttributes = false;
|
1123 | this._jsonAttributes = new Set();
|
1124 | this._virtualAttributes = new Set();
|
1125 | this._defaultValues = {};
|
1126 | this.prototype.validators = {};
|
1127 |
|
1128 | this.fieldRawAttributesMap = {};
|
1129 |
|
1130 | this.primaryKeys = {};
|
1131 | this.uniqueKeys = {};
|
1132 |
|
1133 | _.each(this.rawAttributes, (definition, name) => {
|
1134 | definition.type = this.sequelize.normalizeDataType(definition.type);
|
1135 |
|
1136 | definition.Model = this;
|
1137 | definition.fieldName = name;
|
1138 | definition._modelAttribute = true;
|
1139 |
|
1140 | if (definition.field === undefined) {
|
1141 | definition.field = Utils.underscoredIf(name, this.underscored);
|
1142 | }
|
1143 |
|
1144 | if (definition.primaryKey === true) {
|
1145 | this.primaryKeys[name] = definition;
|
1146 | }
|
1147 |
|
1148 | this.fieldRawAttributesMap[definition.field] = definition;
|
1149 |
|
1150 | if (definition.type._sanitize) {
|
1151 | this._dataTypeSanitizers[name] = definition.type._sanitize;
|
1152 | }
|
1153 |
|
1154 | if (definition.type._isChanged) {
|
1155 | this._dataTypeChanges[name] = definition.type._isChanged;
|
1156 | }
|
1157 |
|
1158 | if (definition.type instanceof DataTypes.BOOLEAN) {
|
1159 | this._hasBooleanAttributes = true;
|
1160 | } else if (definition.type instanceof DataTypes.DATE || definition.type instanceof DataTypes.DATEONLY) {
|
1161 | this._hasDateAttributes = true;
|
1162 | } else if (definition.type instanceof DataTypes.JSON) {
|
1163 | this._jsonAttributes.add(name);
|
1164 | } else if (definition.type instanceof DataTypes.VIRTUAL) {
|
1165 | this._virtualAttributes.add(name);
|
1166 | }
|
1167 |
|
1168 | if (Object.prototype.hasOwnProperty.call(definition, 'defaultValue')) {
|
1169 | this._defaultValues[name] = () => Utils.toDefaultValue(definition.defaultValue, this.sequelize.options.dialect);
|
1170 | }
|
1171 |
|
1172 | if (Object.prototype.hasOwnProperty.call(definition, 'unique') && definition.unique) {
|
1173 | let idxName;
|
1174 | if (
|
1175 | typeof definition.unique === 'object' &&
|
1176 | Object.prototype.hasOwnProperty.call(definition.unique, 'name')
|
1177 | ) {
|
1178 | idxName = definition.unique.name;
|
1179 | } else if (typeof definition.unique === 'string') {
|
1180 | idxName = definition.unique;
|
1181 | } else {
|
1182 | idxName = `${this.tableName}_${name}_unique`;
|
1183 | }
|
1184 |
|
1185 | const idx = this.uniqueKeys[idxName] || { fields: [] };
|
1186 |
|
1187 | idx.fields.push(definition.field);
|
1188 | idx.msg = idx.msg || definition.unique.msg || null;
|
1189 | idx.name = idxName || false;
|
1190 | idx.column = name;
|
1191 | idx.customIndex = definition.unique !== true;
|
1192 |
|
1193 | this.uniqueKeys[idxName] = idx;
|
1194 | }
|
1195 |
|
1196 | if (Object.prototype.hasOwnProperty.call(definition, 'validate')) {
|
1197 | this.prototype.validators[name] = definition.validate;
|
1198 | }
|
1199 |
|
1200 | if (definition.index === true && definition.type instanceof DataTypes.JSONB) {
|
1201 | this._indexes.push(
|
1202 | Utils.nameIndex(
|
1203 | this._conformIndex({
|
1204 | fields: [definition.field || name],
|
1205 | using: 'gin'
|
1206 | }),
|
1207 | this.getTableName()
|
1208 | )
|
1209 | );
|
1210 |
|
1211 | delete definition.index;
|
1212 | }
|
1213 | });
|
1214 |
|
1215 |
|
1216 | this.fieldAttributeMap = _.reduce(this.fieldRawAttributesMap, (map, value, key) => {
|
1217 | if (key !== value.fieldName) {
|
1218 | map[key] = value.fieldName;
|
1219 | }
|
1220 | return map;
|
1221 | }, {});
|
1222 |
|
1223 | this._hasJsonAttributes = !!this._jsonAttributes.size;
|
1224 |
|
1225 | this._hasVirtualAttributes = !!this._virtualAttributes.size;
|
1226 |
|
1227 | this._hasDefaultValues = !_.isEmpty(this._defaultValues);
|
1228 |
|
1229 | this.tableAttributes = _.omitBy(this.rawAttributes, (_a, key) => this._virtualAttributes.has(key));
|
1230 |
|
1231 | this.prototype._hasCustomGetters = Object.keys(this.prototype._customGetters).length;
|
1232 | this.prototype._hasCustomSetters = Object.keys(this.prototype._customSetters).length;
|
1233 |
|
1234 | for (const key of Object.keys(attributeManipulation)) {
|
1235 | if (Object.prototype.hasOwnProperty.call(Model.prototype, key)) {
|
1236 | this.sequelize.log(`Not overriding built-in method from model attribute: ${key}`);
|
1237 | continue;
|
1238 | }
|
1239 | Object.defineProperty(this.prototype, key, attributeManipulation[key]);
|
1240 | }
|
1241 |
|
1242 | this.prototype.rawAttributes = this.rawAttributes;
|
1243 | this.prototype._isAttribute = key => Object.prototype.hasOwnProperty.call(this.prototype.rawAttributes, key);
|
1244 |
|
1245 |
|
1246 | this.primaryKeyAttributes = Object.keys(this.primaryKeys);
|
1247 | this.primaryKeyAttribute = this.primaryKeyAttributes[0];
|
1248 | if (this.primaryKeyAttribute) {
|
1249 | this.primaryKeyField = this.rawAttributes[this.primaryKeyAttribute].field || this.primaryKeyAttribute;
|
1250 | }
|
1251 |
|
1252 | this._hasPrimaryKeys = this.primaryKeyAttributes.length > 0;
|
1253 | this._isPrimaryKey = key => this.primaryKeyAttributes.includes(key);
|
1254 | }
|
1255 |
|
1256 | |
1257 |
|
1258 |
|
1259 |
|
1260 |
|
1261 | static removeAttribute(attribute) {
|
1262 | delete this.rawAttributes[attribute];
|
1263 | this.refreshAttributes();
|
1264 | }
|
1265 |
|
1266 | |
1267 |
|
1268 |
|
1269 |
|
1270 |
|
1271 |
|
1272 |
|
1273 |
|
1274 |
|
1275 |
|
1276 | static sync(options) {
|
1277 | options = Object.assign({}, this.options, options);
|
1278 | options.hooks = options.hooks === undefined ? true : !!options.hooks;
|
1279 |
|
1280 | const attributes = this.tableAttributes;
|
1281 | const rawAttributes = this.fieldRawAttributesMap;
|
1282 |
|
1283 | return Promise.try(() => {
|
1284 | if (options.hooks) {
|
1285 | return this.runHooks('beforeSync', options);
|
1286 | }
|
1287 | }).then(() => {
|
1288 | if (options.force) {
|
1289 | return this.drop(options);
|
1290 | }
|
1291 | })
|
1292 | .then(() => this.QueryInterface.createTable(this.getTableName(options), attributes, options, this))
|
1293 | .then(() => {
|
1294 | if (!options.alter) {
|
1295 | return;
|
1296 | }
|
1297 | return Promise.all([
|
1298 | this.QueryInterface.describeTable(this.getTableName(options)),
|
1299 | this.QueryInterface.getForeignKeyReferencesForTable(this.getTableName(options))
|
1300 | ])
|
1301 | .then(tableInfos => {
|
1302 | const columns = tableInfos[0];
|
1303 |
|
1304 | const foreignKeyReferences = tableInfos[1];
|
1305 |
|
1306 | const changes = [];
|
1307 | const removedConstraints = {};
|
1308 |
|
1309 | _.each(attributes, (columnDesc, columnName) => {
|
1310 | if (!columns[columnName] && !columns[attributes[columnName].field]) {
|
1311 | changes.push(() => this.QueryInterface.addColumn(this.getTableName(options), attributes[columnName].field || columnName, attributes[columnName]));
|
1312 | }
|
1313 | });
|
1314 | _.each(columns, (columnDesc, columnName) => {
|
1315 | const currentAttribute = rawAttributes[columnName];
|
1316 | if (!currentAttribute) {
|
1317 | changes.push(() => this.QueryInterface.removeColumn(this.getTableName(options), columnName, options));
|
1318 | } else if (!currentAttribute.primaryKey) {
|
1319 |
|
1320 | const references = currentAttribute.references;
|
1321 | if (currentAttribute.references) {
|
1322 | const database = this.sequelize.config.database;
|
1323 | const schema = this.sequelize.config.schema;
|
1324 |
|
1325 | _.each(foreignKeyReferences, foreignKeyReference => {
|
1326 | const constraintName = foreignKeyReference.constraintName;
|
1327 | if (!!constraintName
|
1328 | && foreignKeyReference.tableCatalog === database
|
1329 | && (schema ? foreignKeyReference.tableSchema === schema : true)
|
1330 | && foreignKeyReference.referencedTableName === references.model
|
1331 | && foreignKeyReference.referencedColumnName === references.key
|
1332 | && (schema ? foreignKeyReference.referencedTableSchema === schema : true)
|
1333 | && !removedConstraints[constraintName]) {
|
1334 |
|
1335 | changes.push(() => this.QueryInterface.removeConstraint(this.getTableName(options), constraintName, options));
|
1336 | removedConstraints[constraintName] = true;
|
1337 | }
|
1338 | });
|
1339 | }
|
1340 | changes.push(() => this.QueryInterface.changeColumn(this.getTableName(options), columnName, currentAttribute));
|
1341 | }
|
1342 | });
|
1343 | return Promise.each(changes, f => f());
|
1344 | });
|
1345 | })
|
1346 | .then(() => this.QueryInterface.showIndex(this.getTableName(options), options))
|
1347 | .then(indexes => {
|
1348 | indexes = this._indexes.filter(item1 =>
|
1349 | !indexes.some(item2 => item1.name === item2.name)
|
1350 | ).sort((index1, index2) => {
|
1351 | if (this.sequelize.options.dialect === 'postgres') {
|
1352 |
|
1353 | if (index1.concurrently === true) return 1;
|
1354 | if (index2.concurrently === true) return -1;
|
1355 | }
|
1356 |
|
1357 | return 0;
|
1358 | });
|
1359 |
|
1360 | return Promise.each(indexes, index => this.QueryInterface.addIndex(
|
1361 | this.getTableName(options),
|
1362 | Object.assign({
|
1363 | logging: options.logging,
|
1364 | benchmark: options.benchmark,
|
1365 | transaction: options.transaction,
|
1366 | schema: options.schema
|
1367 | }, index),
|
1368 | this.tableName
|
1369 | ));
|
1370 | }).then(() => {
|
1371 | if (options.hooks) {
|
1372 | return this.runHooks('afterSync', options);
|
1373 | }
|
1374 | }).return(this);
|
1375 | }
|
1376 |
|
1377 | |
1378 |
|
1379 |
|
1380 |
|
1381 |
|
1382 |
|
1383 |
|
1384 |
|
1385 |
|
1386 |
|
1387 | static drop(options) {
|
1388 | return this.QueryInterface.dropTable(this.getTableName(options), options);
|
1389 | }
|
1390 |
|
1391 | static dropSchema(schema) {
|
1392 | return this.QueryInterface.dropSchema(schema);
|
1393 | }
|
1394 |
|
1395 | |
1396 |
|
1397 |
|
1398 |
|
1399 |
|
1400 |
|
1401 |
|
1402 |
|
1403 |
|
1404 |
|
1405 |
|
1406 |
|
1407 |
|
1408 |
|
1409 |
|
1410 |
|
1411 |
|
1412 |
|
1413 |
|
1414 |
|
1415 |
|
1416 | static schema(schema, options) {
|
1417 |
|
1418 | const clone = class extends this {};
|
1419 | Object.defineProperty(clone, 'name', { value: this.name });
|
1420 |
|
1421 | clone._schema = schema;
|
1422 |
|
1423 | if (options) {
|
1424 | if (typeof options === 'string') {
|
1425 | clone._schemaDelimiter = options;
|
1426 | } else if (options.schemaDelimiter) {
|
1427 | clone._schemaDelimiter = options.schemaDelimiter;
|
1428 | }
|
1429 | }
|
1430 |
|
1431 | return clone;
|
1432 | }
|
1433 |
|
1434 | |
1435 |
|
1436 |
|
1437 |
|
1438 |
|
1439 |
|
1440 | static getTableName() {
|
1441 | return this.QueryGenerator.addSchema(this);
|
1442 | }
|
1443 |
|
1444 | |
1445 |
|
1446 |
|
1447 |
|
1448 |
|
1449 | static unscoped() {
|
1450 | return this.scope();
|
1451 | }
|
1452 |
|
1453 | |
1454 |
|
1455 |
|
1456 |
|
1457 |
|
1458 |
|
1459 |
|
1460 |
|
1461 |
|
1462 |
|
1463 | static addScope(name, scope, options) {
|
1464 | options = Object.assign({
|
1465 | override: false
|
1466 | }, options);
|
1467 |
|
1468 | if ((name === 'defaultScope' && Object.keys(this.options.defaultScope).length > 0 || name in this.options.scopes) && options.override === false) {
|
1469 | throw new Error(`The scope ${name} already exists. Pass { override: true } as options to silence this error`);
|
1470 | }
|
1471 |
|
1472 | if (name === 'defaultScope') {
|
1473 | this.options.defaultScope = this._scope = scope;
|
1474 | } else {
|
1475 | this.options.scopes[name] = scope;
|
1476 | }
|
1477 | }
|
1478 |
|
1479 | |
1480 |
|
1481 |
|
1482 |
|
1483 |
|
1484 |
|
1485 |
|
1486 |
|
1487 |
|
1488 |
|
1489 |
|
1490 |
|
1491 |
|
1492 |
|
1493 |
|
1494 |
|
1495 |
|
1496 |
|
1497 |
|
1498 |
|
1499 |
|
1500 |
|
1501 |
|
1502 |
|
1503 |
|
1504 |
|
1505 |
|
1506 |
|
1507 |
|
1508 |
|
1509 |
|
1510 |
|
1511 |
|
1512 |
|
1513 |
|
1514 |
|
1515 |
|
1516 |
|
1517 |
|
1518 |
|
1519 |
|
1520 |
|
1521 |
|
1522 |
|
1523 |
|
1524 | static scope(option) {
|
1525 | const self = class extends this {};
|
1526 | let scope;
|
1527 | let scopeName;
|
1528 |
|
1529 | Object.defineProperty(self, 'name', { value: this.name });
|
1530 |
|
1531 | self._scope = {};
|
1532 | self._scopeNames = [];
|
1533 | self.scoped = true;
|
1534 |
|
1535 | if (!option) {
|
1536 | return self;
|
1537 | }
|
1538 |
|
1539 | const options = _.flatten(arguments);
|
1540 |
|
1541 | for (const option of options) {
|
1542 | scope = null;
|
1543 | scopeName = null;
|
1544 |
|
1545 | if (_.isPlainObject(option)) {
|
1546 | if (option.method) {
|
1547 | if (Array.isArray(option.method) && !!self.options.scopes[option.method[0]]) {
|
1548 | scopeName = option.method[0];
|
1549 | scope = self.options.scopes[scopeName].apply(self, option.method.slice(1));
|
1550 | }
|
1551 | else if (self.options.scopes[option.method]) {
|
1552 | scopeName = option.method;
|
1553 | scope = self.options.scopes[scopeName].apply(self);
|
1554 | }
|
1555 | } else {
|
1556 | scope = option;
|
1557 | }
|
1558 | } else if (option === 'defaultScope' && _.isPlainObject(self.options.defaultScope)) {
|
1559 | scope = self.options.defaultScope;
|
1560 | } else {
|
1561 | scopeName = option;
|
1562 | scope = self.options.scopes[scopeName];
|
1563 | if (typeof scope === 'function') {
|
1564 | scope = scope();
|
1565 | }
|
1566 | }
|
1567 |
|
1568 | if (scope) {
|
1569 | this._conformIncludes(scope, this);
|
1570 | this._assignOptions(self._scope, scope);
|
1571 | self._scopeNames.push(scopeName ? scopeName : 'defaultScope');
|
1572 | } else {
|
1573 | throw new sequelizeErrors.SequelizeScopeError(`Invalid scope ${scopeName} called.`);
|
1574 | }
|
1575 | }
|
1576 |
|
1577 | return self;
|
1578 | }
|
1579 |
|
1580 | |
1581 |
|
1582 |
|
1583 |
|
1584 |
|
1585 |
|
1586 |
|
1587 |
|
1588 |
|
1589 |
|
1590 |
|
1591 |
|
1592 |
|
1593 |
|
1594 |
|
1595 |
|
1596 |
|
1597 |
|
1598 |
|
1599 |
|
1600 |
|
1601 |
|
1602 |
|
1603 |
|
1604 |
|
1605 |
|
1606 |
|
1607 |
|
1608 |
|
1609 |
|
1610 |
|
1611 |
|
1612 |
|
1613 |
|
1614 |
|
1615 |
|
1616 |
|
1617 |
|
1618 |
|
1619 |
|
1620 |
|
1621 |
|
1622 |
|
1623 |
|
1624 |
|
1625 |
|
1626 |
|
1627 |
|
1628 |
|
1629 |
|
1630 |
|
1631 |
|
1632 |
|
1633 |
|
1634 |
|
1635 |
|
1636 |
|
1637 |
|
1638 |
|
1639 |
|
1640 |
|
1641 |
|
1642 |
|
1643 |
|
1644 |
|
1645 |
|
1646 |
|
1647 |
|
1648 |
|
1649 |
|
1650 |
|
1651 |
|
1652 |
|
1653 |
|
1654 |
|
1655 |
|
1656 |
|
1657 |
|
1658 |
|
1659 |
|
1660 |
|
1661 |
|
1662 |
|
1663 |
|
1664 |
|
1665 |
|
1666 |
|
1667 |
|
1668 |
|
1669 |
|
1670 |
|
1671 |
|
1672 |
|
1673 |
|
1674 |
|
1675 |
|
1676 |
|
1677 |
|
1678 |
|
1679 |
|
1680 | static findAll(options) {
|
1681 | if (options !== undefined && !_.isPlainObject(options)) {
|
1682 | throw new sequelizeErrors.QueryError('The argument passed to findAll must be an options object, use findByPk if you wish to pass a single primary key value');
|
1683 | }
|
1684 |
|
1685 | if (options !== undefined && options.attributes) {
|
1686 | if (!Array.isArray(options.attributes) && !_.isPlainObject(options.attributes)) {
|
1687 | throw new sequelizeErrors.QueryError('The attributes option must be an array of column names or an object');
|
1688 | }
|
1689 | }
|
1690 |
|
1691 | this.warnOnInvalidOptions(options, Object.keys(this.rawAttributes));
|
1692 |
|
1693 | const tableNames = {};
|
1694 |
|
1695 | tableNames[this.getTableName(options)] = true;
|
1696 | options = Utils.cloneDeep(options);
|
1697 |
|
1698 | _.defaults(options, { hooks: true });
|
1699 |
|
1700 |
|
1701 | options.rejectOnEmpty = Object.prototype.hasOwnProperty.call(options, 'rejectOnEmpty')
|
1702 | ? options.rejectOnEmpty
|
1703 | : this.options.rejectOnEmpty;
|
1704 |
|
1705 | return Promise.try(() => {
|
1706 | this._injectScope(options);
|
1707 |
|
1708 | if (options.hooks) {
|
1709 | return this.runHooks('beforeFind', options);
|
1710 | }
|
1711 | }).then(() => {
|
1712 | this._conformIncludes(options, this);
|
1713 | this._expandAttributes(options);
|
1714 | this._expandIncludeAll(options);
|
1715 |
|
1716 | if (options.hooks) {
|
1717 | return this.runHooks('beforeFindAfterExpandIncludeAll', options);
|
1718 | }
|
1719 | }).then(() => {
|
1720 | options.originalAttributes = this._injectDependentVirtualAttributes(options.attributes);
|
1721 |
|
1722 | if (options.include) {
|
1723 | options.hasJoin = true;
|
1724 |
|
1725 | this._validateIncludedElements(options, tableNames);
|
1726 |
|
1727 |
|
1728 | if (
|
1729 | options.attributes
|
1730 | && !options.raw
|
1731 | && this.primaryKeyAttribute
|
1732 | && !options.attributes.includes(this.primaryKeyAttribute)
|
1733 | && (!options.group || !options.hasSingleAssociation || options.hasMultiAssociation)
|
1734 | ) {
|
1735 | options.attributes = [this.primaryKeyAttribute].concat(options.attributes);
|
1736 | }
|
1737 | }
|
1738 |
|
1739 | if (!options.attributes) {
|
1740 | options.attributes = Object.keys(this.rawAttributes);
|
1741 | options.originalAttributes = this._injectDependentVirtualAttributes(options.attributes);
|
1742 | }
|
1743 |
|
1744 |
|
1745 | this.options.whereCollection = options.where || null;
|
1746 |
|
1747 | Utils.mapFinderOptions(options, this);
|
1748 |
|
1749 | options = this._paranoidClause(this, options);
|
1750 |
|
1751 | if (options.hooks) {
|
1752 | return this.runHooks('beforeFindAfterOptions', options);
|
1753 | }
|
1754 | }).then(() => {
|
1755 | const selectOptions = Object.assign({}, options, { tableNames: Object.keys(tableNames) });
|
1756 | return this.QueryInterface.select(this, this.getTableName(selectOptions), selectOptions);
|
1757 | }).tap(results => {
|
1758 | if (options.hooks) {
|
1759 | return this.runHooks('afterFind', results, options);
|
1760 | }
|
1761 | }).then(results => {
|
1762 |
|
1763 |
|
1764 | if (_.isEmpty(results) && options.rejectOnEmpty) {
|
1765 | if (typeof options.rejectOnEmpty === 'function') {
|
1766 | throw new options.rejectOnEmpty();
|
1767 | }
|
1768 | if (typeof options.rejectOnEmpty === 'object') {
|
1769 | throw options.rejectOnEmpty;
|
1770 | }
|
1771 | throw new sequelizeErrors.EmptyResultError();
|
1772 | }
|
1773 |
|
1774 | return Model._findSeparate(results, options);
|
1775 | });
|
1776 | }
|
1777 |
|
1778 | static warnOnInvalidOptions(options, validColumnNames) {
|
1779 | if (!_.isPlainObject(options)) {
|
1780 | return;
|
1781 | }
|
1782 |
|
1783 | const unrecognizedOptions = Object.keys(options).filter(k => !validQueryKeywords.has(k));
|
1784 | const unexpectedModelAttributes = _.intersection(unrecognizedOptions, validColumnNames);
|
1785 | if (!options.where && unexpectedModelAttributes.length > 0) {
|
1786 | logger.warn(`Model attributes (${unexpectedModelAttributes.join(', ')}) passed into finder method options of model ${this.name}, but the options.where object is empty. Did you forget to use options.where?`);
|
1787 | }
|
1788 | }
|
1789 |
|
1790 | static _injectDependentVirtualAttributes(attributes) {
|
1791 | if (!this._hasVirtualAttributes) return attributes;
|
1792 | if (!attributes || !Array.isArray(attributes)) return attributes;
|
1793 |
|
1794 | for (const attribute of attributes) {
|
1795 | if (
|
1796 | this._virtualAttributes.has(attribute)
|
1797 | && this.rawAttributes[attribute].type.fields
|
1798 | ) {
|
1799 | attributes = attributes.concat(this.rawAttributes[attribute].type.fields);
|
1800 | }
|
1801 | }
|
1802 |
|
1803 | attributes = _.uniq(attributes);
|
1804 |
|
1805 | return attributes;
|
1806 | }
|
1807 |
|
1808 | static _findSeparate(results, options) {
|
1809 | if (!options.include || options.raw || !results) return Promise.resolve(results);
|
1810 |
|
1811 | const original = results;
|
1812 | if (options.plain) results = [results];
|
1813 |
|
1814 | if (!results.length) return original;
|
1815 |
|
1816 | return Promise.map(options.include, include => {
|
1817 | if (!include.separate) {
|
1818 | return Model._findSeparate(
|
1819 | results.reduce((memo, result) => {
|
1820 | let associations = result.get(include.association.as);
|
1821 |
|
1822 |
|
1823 | if (!associations) return memo;
|
1824 |
|
1825 |
|
1826 | if (!Array.isArray(associations)) associations = [associations];
|
1827 |
|
1828 | for (let i = 0, len = associations.length; i !== len; ++i) {
|
1829 | memo.push(associations[i]);
|
1830 | }
|
1831 | return memo;
|
1832 | }, []),
|
1833 | Object.assign(
|
1834 | {},
|
1835 | _.omit(options, 'include', 'attributes', 'order', 'where', 'limit', 'offset', 'plain', 'scope'),
|
1836 | { include: include.include || [] }
|
1837 | )
|
1838 | );
|
1839 | }
|
1840 |
|
1841 | return include.association.get(results, Object.assign(
|
1842 | {},
|
1843 | _.omit(options, nonCascadingOptions),
|
1844 | _.omit(include, ['parent', 'association', 'as', 'originalAttributes'])
|
1845 | )).then(map => {
|
1846 | for (const result of results) {
|
1847 | result.set(
|
1848 | include.association.as,
|
1849 | map[result.get(include.association.sourceKey)],
|
1850 | { raw: true }
|
1851 | );
|
1852 | }
|
1853 | });
|
1854 | }).return(original);
|
1855 | }
|
1856 |
|
1857 | |
1858 |
|
1859 |
|
1860 |
|
1861 |
|
1862 |
|
1863 |
|
1864 |
|
1865 |
|
1866 |
|
1867 |
|
1868 |
|
1869 |
|
1870 | static findByPk(param, options) {
|
1871 |
|
1872 | if ([null, undefined].includes(param)) {
|
1873 | return Promise.resolve(null);
|
1874 | }
|
1875 |
|
1876 | options = Utils.cloneDeep(options) || {};
|
1877 |
|
1878 | if (typeof param === 'number' || typeof param === 'string' || Buffer.isBuffer(param)) {
|
1879 | options.where = {
|
1880 | [this.primaryKeyAttribute]: param
|
1881 | };
|
1882 | } else {
|
1883 | throw new Error(`Argument passed to findByPk is invalid: ${param}`);
|
1884 | }
|
1885 |
|
1886 |
|
1887 | return this.findOne(options);
|
1888 | }
|
1889 |
|
1890 | |
1891 |
|
1892 |
|
1893 |
|
1894 |
|
1895 |
|
1896 |
|
1897 |
|
1898 |
|
1899 |
|
1900 |
|
1901 |
|
1902 |
|
1903 |
|
1904 | static findOne(options) {
|
1905 | if (options !== undefined && !_.isPlainObject(options)) {
|
1906 | throw new Error('The argument passed to findOne must be an options object, use findByPk if you wish to pass a single primary key value');
|
1907 | }
|
1908 | options = Utils.cloneDeep(options);
|
1909 |
|
1910 | if (options.limit === undefined) {
|
1911 | const uniqueSingleColumns = _.chain(this.uniqueKeys).values().filter(c => c.fields.length === 1).map('column').value();
|
1912 |
|
1913 |
|
1914 | if (!options.where || !_.some(options.where, (value, key) =>
|
1915 | (key === this.primaryKeyAttribute || uniqueSingleColumns.includes(key)) &&
|
1916 | (Utils.isPrimitive(value) || Buffer.isBuffer(value))
|
1917 | )) {
|
1918 | options.limit = 1;
|
1919 | }
|
1920 | }
|
1921 |
|
1922 |
|
1923 | return this.findAll(_.defaults(options, {
|
1924 | plain: true
|
1925 | }));
|
1926 | }
|
1927 |
|
1928 | |
1929 |
|
1930 |
|
1931 |
|
1932 |
|
1933 |
|
1934 |
|
1935 |
|
1936 |
|
1937 |
|
1938 |
|
1939 |
|
1940 |
|
1941 |
|
1942 |
|
1943 |
|
1944 | static aggregate(attribute, aggregateFunction, options) {
|
1945 | options = Utils.cloneDeep(options);
|
1946 |
|
1947 |
|
1948 | const prevAttributes = options.attributes;
|
1949 | this._injectScope(options);
|
1950 | options.attributes = prevAttributes;
|
1951 | this._conformIncludes(options, this);
|
1952 |
|
1953 | if (options.include) {
|
1954 | this._expandIncludeAll(options);
|
1955 | this._validateIncludedElements(options);
|
1956 | }
|
1957 |
|
1958 | const attrOptions = this.rawAttributes[attribute];
|
1959 | const field = attrOptions && attrOptions.field || attribute;
|
1960 | let aggregateColumn = this.sequelize.col(field);
|
1961 |
|
1962 | if (options.distinct) {
|
1963 | aggregateColumn = this.sequelize.fn('DISTINCT', aggregateColumn);
|
1964 | }
|
1965 |
|
1966 | let { group } = options;
|
1967 | if (Array.isArray(group) && Array.isArray(group[0])) {
|
1968 | noDoubleNestedGroup();
|
1969 | group = _.flatten(group);
|
1970 | }
|
1971 | options.attributes = _.unionBy(
|
1972 | options.attributes,
|
1973 | group,
|
1974 | [[this.sequelize.fn(aggregateFunction, aggregateColumn), aggregateFunction]],
|
1975 | a => Array.isArray(a) ? a[1] : a
|
1976 | );
|
1977 |
|
1978 | if (!options.dataType) {
|
1979 | if (attrOptions) {
|
1980 | options.dataType = attrOptions.type;
|
1981 | } else {
|
1982 |
|
1983 | options.dataType = new DataTypes.FLOAT();
|
1984 | }
|
1985 | } else {
|
1986 | options.dataType = this.sequelize.normalizeDataType(options.dataType);
|
1987 | }
|
1988 |
|
1989 | Utils.mapOptionFieldNames(options, this);
|
1990 | options = this._paranoidClause(this, options);
|
1991 |
|
1992 | return this.QueryInterface.rawSelect(this.getTableName(options), options, aggregateFunction, this).then( value => {
|
1993 | if (value === null) {
|
1994 | return 0;
|
1995 | }
|
1996 | return value;
|
1997 | });
|
1998 | }
|
1999 |
|
2000 | |
2001 |
|
2002 |
|
2003 |
|
2004 |
|
2005 |
|
2006 |
|
2007 |
|
2008 |
|
2009 |
|
2010 |
|
2011 |
|
2012 |
|
2013 |
|
2014 |
|
2015 |
|
2016 |
|
2017 |
|
2018 |
|
2019 |
|
2020 | static count(options) {
|
2021 | return Promise.try(() => {
|
2022 | options = Utils.cloneDeep(options);
|
2023 | options = _.defaults(options, { hooks: true });
|
2024 | options.raw = true;
|
2025 | if (options.hooks) {
|
2026 | return this.runHooks('beforeCount', options);
|
2027 | }
|
2028 | }).then(() => {
|
2029 | let col = options.col || '*';
|
2030 | if (options.include) {
|
2031 | col = `${this.name}.${options.col || this.primaryKeyField}`;
|
2032 | }
|
2033 |
|
2034 | options.plain = !options.group;
|
2035 | options.dataType = new DataTypes.INTEGER();
|
2036 | options.includeIgnoreAttributes = false;
|
2037 |
|
2038 |
|
2039 |
|
2040 | options.limit = null;
|
2041 | options.offset = null;
|
2042 | options.order = null;
|
2043 |
|
2044 | return this.aggregate(col, 'count', options);
|
2045 | });
|
2046 | }
|
2047 |
|
2048 | |
2049 |
|
2050 |
|
2051 |
|
2052 |
|
2053 |
|
2054 |
|
2055 |
|
2056 |
|
2057 |
|
2058 |
|
2059 |
|
2060 |
|
2061 |
|
2062 |
|
2063 |
|
2064 |
|
2065 |
|
2066 |
|
2067 |
|
2068 |
|
2069 |
|
2070 |
|
2071 |
|
2072 |
|
2073 |
|
2074 |
|
2075 |
|
2076 |
|
2077 |
|
2078 |
|
2079 |
|
2080 |
|
2081 |
|
2082 |
|
2083 |
|
2084 | static findAndCountAll(options) {
|
2085 | if (options !== undefined && !_.isPlainObject(options)) {
|
2086 | throw new Error('The argument passed to findAndCountAll must be an options object, use findByPk if you wish to pass a single primary key value');
|
2087 | }
|
2088 |
|
2089 | const countOptions = Utils.cloneDeep(options);
|
2090 |
|
2091 | if (countOptions.attributes) {
|
2092 | countOptions.attributes = undefined;
|
2093 | }
|
2094 |
|
2095 | return Promise.all([
|
2096 | this.count(countOptions),
|
2097 | this.findAll(options)
|
2098 | ])
|
2099 | .then(([count, rows]) => ({
|
2100 | count,
|
2101 | rows: count === 0 ? [] : rows
|
2102 | }));
|
2103 | }
|
2104 |
|
2105 | |
2106 |
|
2107 |
|
2108 |
|
2109 |
|
2110 |
|
2111 |
|
2112 |
|
2113 |
|
2114 |
|
2115 |
|
2116 | static max(field, options) {
|
2117 | return this.aggregate(field, 'max', options);
|
2118 | }
|
2119 |
|
2120 | |
2121 |
|
2122 |
|
2123 |
|
2124 |
|
2125 |
|
2126 |
|
2127 |
|
2128 |
|
2129 |
|
2130 |
|
2131 | static min(field, options) {
|
2132 | return this.aggregate(field, 'min', options);
|
2133 | }
|
2134 |
|
2135 | |
2136 |
|
2137 |
|
2138 |
|
2139 |
|
2140 |
|
2141 |
|
2142 |
|
2143 |
|
2144 |
|
2145 |
|
2146 | static sum(field, options) {
|
2147 | return this.aggregate(field, 'sum', options);
|
2148 | }
|
2149 |
|
2150 | |
2151 |
|
2152 |
|
2153 |
|
2154 |
|
2155 |
|
2156 |
|
2157 |
|
2158 |
|
2159 |
|
2160 |
|
2161 | static build(values, options) {
|
2162 | if (Array.isArray(values)) {
|
2163 | return this.bulkBuild(values, options);
|
2164 | }
|
2165 |
|
2166 | return new this(values, options);
|
2167 | }
|
2168 |
|
2169 | static bulkBuild(valueSets, options) {
|
2170 | options = Object.assign({
|
2171 | isNewRecord: true
|
2172 | }, options || {});
|
2173 |
|
2174 | if (!options.includeValidated) {
|
2175 | this._conformIncludes(options, this);
|
2176 | if (options.include) {
|
2177 | this._expandIncludeAll(options);
|
2178 | this._validateIncludedElements(options);
|
2179 | }
|
2180 | }
|
2181 |
|
2182 | if (options.attributes) {
|
2183 | options.attributes = options.attributes.map(attribute => Array.isArray(attribute) ? attribute[1] : attribute);
|
2184 | }
|
2185 |
|
2186 | return valueSets.map(values => this.build(values, options));
|
2187 | }
|
2188 |
|
2189 | |
2190 |
|
2191 |
|
2192 |
|
2193 |
|
2194 |
|
2195 |
|
2196 |
|
2197 |
|
2198 |
|
2199 |
|
2200 |
|
2201 |
|
2202 |
|
2203 |
|
2204 |
|
2205 |
|
2206 |
|
2207 |
|
2208 |
|
2209 |
|
2210 |
|
2211 |
|
2212 |
|
2213 |
|
2214 |
|
2215 |
|
2216 | static create(values, options) {
|
2217 | options = Utils.cloneDeep(options || {});
|
2218 |
|
2219 | return this.build(values, {
|
2220 | isNewRecord: true,
|
2221 | attributes: options.fields,
|
2222 | include: options.include,
|
2223 | raw: options.raw,
|
2224 | silent: options.silent
|
2225 | }).save(options);
|
2226 | }
|
2227 |
|
2228 | |
2229 |
|
2230 |
|
2231 |
|
2232 |
|
2233 |
|
2234 |
|
2235 |
|
2236 |
|
2237 |
|
2238 |
|
2239 | static findOrBuild(options) {
|
2240 | if (!options || !options.where || arguments.length > 1) {
|
2241 | throw new Error(
|
2242 | 'Missing where attribute in the options parameter passed to findOrBuild. ' +
|
2243 | 'Please note that the API has changed, and is now options only (an object with where, defaults keys, transaction etc.)'
|
2244 | );
|
2245 | }
|
2246 |
|
2247 | let values;
|
2248 |
|
2249 | return this.findOne(options).then(instance => {
|
2250 | if (instance === null) {
|
2251 | values = _.clone(options.defaults) || {};
|
2252 | if (_.isPlainObject(options.where)) {
|
2253 | values = Utils.defaults(values, options.where);
|
2254 | }
|
2255 |
|
2256 | instance = this.build(values, options);
|
2257 |
|
2258 | return Promise.resolve([instance, true]);
|
2259 | }
|
2260 |
|
2261 | return Promise.resolve([instance, false]);
|
2262 | });
|
2263 | }
|
2264 |
|
2265 | |
2266 |
|
2267 |
|
2268 |
|
2269 |
|
2270 |
|
2271 |
|
2272 |
|
2273 |
|
2274 |
|
2275 |
|
2276 |
|
2277 |
|
2278 |
|
2279 |
|
2280 |
|
2281 |
|
2282 |
|
2283 | static findOrCreate(options) {
|
2284 | if (!options || !options.where || arguments.length > 1) {
|
2285 | throw new Error(
|
2286 | 'Missing where attribute in the options parameter passed to findOrCreate. ' +
|
2287 | 'Please note that the API has changed, and is now options only (an object with where, defaults keys, transaction etc.)'
|
2288 | );
|
2289 | }
|
2290 |
|
2291 | options = Object.assign({}, options);
|
2292 |
|
2293 | if (options.defaults) {
|
2294 | const defaults = Object.keys(options.defaults);
|
2295 | const unknownDefaults = defaults.filter(name => !this.rawAttributes[name]);
|
2296 |
|
2297 | if (unknownDefaults.length) {
|
2298 | logger.warn(`Unknown attributes (${unknownDefaults}) passed to defaults option of findOrCreate`);
|
2299 | }
|
2300 | }
|
2301 |
|
2302 | if (options.transaction === undefined && this.sequelize.constructor._cls) {
|
2303 | const t = this.sequelize.constructor._cls.get('transaction');
|
2304 | if (t) {
|
2305 | options.transaction = t;
|
2306 | }
|
2307 | }
|
2308 |
|
2309 | const internalTransaction = !options.transaction;
|
2310 | let values;
|
2311 | let transaction;
|
2312 |
|
2313 |
|
2314 | return this.sequelize.transaction(options).then(t => {
|
2315 | transaction = t;
|
2316 | options.transaction = t;
|
2317 |
|
2318 | return this.findOne(Utils.defaults({ transaction }, options));
|
2319 | }).then(instance => {
|
2320 | if (instance !== null) {
|
2321 | return [instance, false];
|
2322 | }
|
2323 |
|
2324 | values = _.clone(options.defaults) || {};
|
2325 | if (_.isPlainObject(options.where)) {
|
2326 | values = Utils.defaults(values, options.where);
|
2327 | }
|
2328 |
|
2329 | options.exception = true;
|
2330 |
|
2331 | return this.create(values, options).then(instance => {
|
2332 | if (instance.get(this.primaryKeyAttribute, { raw: true }) === null) {
|
2333 |
|
2334 | throw new sequelizeErrors.UniqueConstraintError();
|
2335 | }
|
2336 |
|
2337 | return [instance, true];
|
2338 | }).catch(sequelizeErrors.UniqueConstraintError, err => {
|
2339 | const flattenedWhere = Utils.flattenObjectDeep(options.where);
|
2340 | const flattenedWhereKeys = Object.keys(flattenedWhere).map(name => _.last(name.split('.')));
|
2341 | const whereFields = flattenedWhereKeys.map(name => _.get(this.rawAttributes, `${name}.field`, name));
|
2342 | const defaultFields = options.defaults && Object.keys(options.defaults)
|
2343 | .filter(name => this.rawAttributes[name])
|
2344 | .map(name => this.rawAttributes[name].field || name);
|
2345 |
|
2346 | const errFieldKeys = Object.keys(err.fields);
|
2347 | const errFieldsWhereIntersects = Utils.intersects(errFieldKeys, whereFields);
|
2348 | if (defaultFields && !errFieldsWhereIntersects && Utils.intersects(errFieldKeys, defaultFields)) {
|
2349 | throw err;
|
2350 | }
|
2351 |
|
2352 | if (errFieldsWhereIntersects) {
|
2353 | _.each(err.fields, (value, key) => {
|
2354 | const name = this.fieldRawAttributesMap[key].fieldName;
|
2355 | if (value.toString() !== options.where[name].toString()) {
|
2356 | throw new Error(`${this.name}#findOrCreate: value used for ${name} was not equal for both the find and the create calls, '${options.where[name]}' vs '${value}'`);
|
2357 | }
|
2358 | });
|
2359 | }
|
2360 |
|
2361 |
|
2362 | return this.findOne(Utils.defaults({
|
2363 | transaction: internalTransaction ? null : transaction
|
2364 | }, options)).then(instance => {
|
2365 |
|
2366 |
|
2367 | if (instance === null) throw err;
|
2368 | return [instance, false];
|
2369 | });
|
2370 | });
|
2371 | }).finally(() => {
|
2372 | if (internalTransaction && transaction) {
|
2373 |
|
2374 | return transaction.commit();
|
2375 | }
|
2376 | });
|
2377 | }
|
2378 |
|
2379 | |
2380 |
|
2381 |
|
2382 |
|
2383 |
|
2384 |
|
2385 |
|
2386 |
|
2387 |
|
2388 |
|
2389 |
|
2390 |
|
2391 |
|
2392 | static findCreateFind(options) {
|
2393 | if (!options || !options.where) {
|
2394 | throw new Error(
|
2395 | 'Missing where attribute in the options parameter passed to findCreateFind.'
|
2396 | );
|
2397 | }
|
2398 |
|
2399 | let values = _.clone(options.defaults) || {};
|
2400 | if (_.isPlainObject(options.where)) {
|
2401 | values = Utils.defaults(values, options.where);
|
2402 | }
|
2403 |
|
2404 |
|
2405 | return this.findOne(options).then(result => {
|
2406 | if (result) return [result, false];
|
2407 |
|
2408 | return this.create(values, options)
|
2409 | .then(result => [result, true])
|
2410 | .catch(sequelizeErrors.UniqueConstraintError, () => this.findOne(options).then(result => [result, false]));
|
2411 | });
|
2412 | }
|
2413 |
|
2414 | |
2415 |
|
2416 |
|
2417 |
|
2418 |
|
2419 |
|
2420 |
|
2421 |
|
2422 |
|
2423 |
|
2424 |
|
2425 |
|
2426 |
|
2427 |
|
2428 |
|
2429 |
|
2430 |
|
2431 |
|
2432 |
|
2433 |
|
2434 |
|
2435 |
|
2436 |
|
2437 |
|
2438 | static upsert(values, options) {
|
2439 | options = Object.assign({
|
2440 | hooks: true,
|
2441 | returning: false,
|
2442 | validate: true
|
2443 | }, Utils.cloneDeep(options || {}));
|
2444 |
|
2445 | options.model = this;
|
2446 |
|
2447 | const createdAtAttr = this._timestampAttributes.createdAt;
|
2448 | const updatedAtAttr = this._timestampAttributes.updatedAt;
|
2449 | const hasPrimary = this.primaryKeyField in values || this.primaryKeyAttribute in values;
|
2450 | const instance = this.build(values);
|
2451 |
|
2452 | if (!options.fields) {
|
2453 | options.fields = Object.keys(instance._changed);
|
2454 | }
|
2455 |
|
2456 | return Promise.try(() => {
|
2457 | if (options.validate) {
|
2458 | return instance.validate(options);
|
2459 | }
|
2460 | }).then(() => {
|
2461 |
|
2462 | const updatedDataValues = _.pick(instance.dataValues, Object.keys(instance._changed));
|
2463 | const insertValues = Utils.mapValueFieldNames(instance.dataValues, Object.keys(instance.rawAttributes), this);
|
2464 | const updateValues = Utils.mapValueFieldNames(updatedDataValues, options.fields, this);
|
2465 | const now = Utils.now(this.sequelize.options.dialect);
|
2466 |
|
2467 |
|
2468 | if (createdAtAttr && !updateValues[createdAtAttr]) {
|
2469 | const field = this.rawAttributes[createdAtAttr].field || createdAtAttr;
|
2470 | insertValues[field] = this._getDefaultTimestamp(createdAtAttr) || now;
|
2471 | }
|
2472 | if (updatedAtAttr && !insertValues[updatedAtAttr]) {
|
2473 | const field = this.rawAttributes[updatedAtAttr].field || updatedAtAttr;
|
2474 | insertValues[field] = updateValues[field] = this._getDefaultTimestamp(updatedAtAttr) || now;
|
2475 | }
|
2476 |
|
2477 |
|
2478 |
|
2479 | if (!hasPrimary && this.primaryKeyAttribute && !this.rawAttributes[this.primaryKeyAttribute].defaultValue) {
|
2480 | delete insertValues[this.primaryKeyField];
|
2481 | delete updateValues[this.primaryKeyField];
|
2482 | }
|
2483 |
|
2484 | return Promise.try(() => {
|
2485 | if (options.hooks) {
|
2486 | return this.runHooks('beforeUpsert', values, options);
|
2487 | }
|
2488 | })
|
2489 | .then(() => {
|
2490 | return this.QueryInterface.upsert(this.getTableName(options), insertValues, updateValues, instance.where(), this, options);
|
2491 | })
|
2492 | .then(([created, primaryKey]) => {
|
2493 | if (options.returning === true && primaryKey) {
|
2494 | return this.findByPk(primaryKey, options).then(record => [record, created]);
|
2495 | }
|
2496 |
|
2497 | return created;
|
2498 | })
|
2499 | .tap(result => {
|
2500 | if (options.hooks) {
|
2501 | return this.runHooks('afterUpsert', result, options);
|
2502 | }
|
2503 | });
|
2504 | });
|
2505 | }
|
2506 |
|
2507 | |
2508 |
|
2509 |
|
2510 |
|
2511 |
|
2512 |
|
2513 |
|
2514 |
|
2515 |
|
2516 |
|
2517 |
|
2518 |
|
2519 |
|
2520 |
|
2521 |
|
2522 |
|
2523 |
|
2524 |
|
2525 |
|
2526 |
|
2527 |
|
2528 |
|
2529 |
|
2530 |
|
2531 |
|
2532 | static bulkCreate(records, options = {}) {
|
2533 | if (!records.length) {
|
2534 | return Promise.resolve([]);
|
2535 | }
|
2536 |
|
2537 | const dialect = this.sequelize.options.dialect;
|
2538 | const now = Utils.now(this.sequelize.options.dialect);
|
2539 |
|
2540 | options.model = this;
|
2541 |
|
2542 | if (!options.includeValidated) {
|
2543 | this._conformIncludes(options, this);
|
2544 | if (options.include) {
|
2545 | this._expandIncludeAll(options);
|
2546 | this._validateIncludedElements(options);
|
2547 | }
|
2548 | }
|
2549 |
|
2550 | const instances = records.map(values => this.build(values, { isNewRecord: true, include: options.include }));
|
2551 |
|
2552 | const recursiveBulkCreate = (instances, options) => {
|
2553 | options = Object.assign({
|
2554 | validate: false,
|
2555 | hooks: true,
|
2556 | individualHooks: false,
|
2557 | ignoreDuplicates: false
|
2558 | }, options);
|
2559 |
|
2560 | if (options.returning === undefined) {
|
2561 | if (options.association) {
|
2562 | options.returning = false;
|
2563 | } else {
|
2564 | options.returning = true;
|
2565 | }
|
2566 | }
|
2567 |
|
2568 | if (options.ignoreDuplicates && ['mssql'].includes(dialect)) {
|
2569 | return Promise.reject(new Error(`${dialect} does not support the ignoreDuplicates option.`));
|
2570 | }
|
2571 | if (options.updateOnDuplicate && (dialect !== 'mysql' && dialect !== 'mariadb' && dialect !== 'sqlite' && dialect !== 'postgres')) {
|
2572 | return Promise.reject(new Error(`${dialect} does not support the updateOnDuplicate option.`));
|
2573 | }
|
2574 |
|
2575 | const model = options.model;
|
2576 |
|
2577 | options.fields = options.fields || Object.keys(model.rawAttributes);
|
2578 | const createdAtAttr = model._timestampAttributes.createdAt;
|
2579 | const updatedAtAttr = model._timestampAttributes.updatedAt;
|
2580 |
|
2581 | if (options.updateOnDuplicate !== undefined) {
|
2582 | if (Array.isArray(options.updateOnDuplicate) && options.updateOnDuplicate.length) {
|
2583 | options.updateOnDuplicate = _.intersection(
|
2584 | _.without(Object.keys(model.tableAttributes), createdAtAttr),
|
2585 | options.updateOnDuplicate
|
2586 | );
|
2587 | } else {
|
2588 | return Promise.reject(new Error('updateOnDuplicate option only supports non-empty array.'));
|
2589 | }
|
2590 | }
|
2591 |
|
2592 | return Promise.try(() => {
|
2593 |
|
2594 | if (options.hooks) {
|
2595 | return model.runHooks('beforeBulkCreate', instances, options);
|
2596 | }
|
2597 | }).then(() => {
|
2598 |
|
2599 | if (options.validate) {
|
2600 | const errors = new Promise.AggregateError();
|
2601 | const validateOptions = _.clone(options);
|
2602 | validateOptions.hooks = options.individualHooks;
|
2603 |
|
2604 | return Promise.map(instances, instance =>
|
2605 | instance.validate(validateOptions).catch(err => {
|
2606 | errors.push(new sequelizeErrors.BulkRecordError(err, instance));
|
2607 | })
|
2608 | ).then(() => {
|
2609 | delete options.skip;
|
2610 | if (errors.length) {
|
2611 | throw errors;
|
2612 | }
|
2613 | });
|
2614 | }
|
2615 | }).then(() => {
|
2616 | if (options.individualHooks) {
|
2617 |
|
2618 | return Promise.map(instances, instance => {
|
2619 | const individualOptions = _.clone(options);
|
2620 | delete individualOptions.fields;
|
2621 | delete individualOptions.individualHooks;
|
2622 | delete individualOptions.ignoreDuplicates;
|
2623 | individualOptions.validate = false;
|
2624 | individualOptions.hooks = true;
|
2625 |
|
2626 | return instance.save(individualOptions);
|
2627 | });
|
2628 | }
|
2629 |
|
2630 | return Promise.resolve().then(() => {
|
2631 | if (!options.include || !options.include.length) return;
|
2632 |
|
2633 |
|
2634 | return Promise.map(options.include.filter(include => include.association instanceof BelongsTo), include => {
|
2635 | const associationInstances = [];
|
2636 | const associationInstanceIndexToInstanceMap = [];
|
2637 |
|
2638 | for (const instance of instances) {
|
2639 | const associationInstance = instance.get(include.as);
|
2640 | if (associationInstance) {
|
2641 | associationInstances.push(associationInstance);
|
2642 | associationInstanceIndexToInstanceMap.push(instance);
|
2643 | }
|
2644 | }
|
2645 |
|
2646 | if (!associationInstances.length) {
|
2647 | return;
|
2648 | }
|
2649 |
|
2650 | const includeOptions = _(Utils.cloneDeep(include))
|
2651 | .omit(['association'])
|
2652 | .defaults({
|
2653 | transaction: options.transaction,
|
2654 | logging: options.logging
|
2655 | }).value();
|
2656 |
|
2657 | return recursiveBulkCreate(associationInstances, includeOptions).then(associationInstances => {
|
2658 | for (const idx in associationInstances) {
|
2659 | const associationInstance = associationInstances[idx];
|
2660 | const instance = associationInstanceIndexToInstanceMap[idx];
|
2661 |
|
2662 | instance[include.association.accessors.set](associationInstance, { save: false, logging: options.logging });
|
2663 | }
|
2664 | });
|
2665 | });
|
2666 | }).then(() => {
|
2667 |
|
2668 |
|
2669 | records = instances.map(instance => {
|
2670 | const values = instance.dataValues;
|
2671 |
|
2672 |
|
2673 | if (createdAtAttr && !values[createdAtAttr]) {
|
2674 | values[createdAtAttr] = now;
|
2675 | if (!options.fields.includes(createdAtAttr)) {
|
2676 | options.fields.push(createdAtAttr);
|
2677 | }
|
2678 | }
|
2679 | if (updatedAtAttr && !values[updatedAtAttr]) {
|
2680 | values[updatedAtAttr] = now;
|
2681 | if (!options.fields.includes(updatedAtAttr)) {
|
2682 | options.fields.push(updatedAtAttr);
|
2683 | }
|
2684 | }
|
2685 |
|
2686 | const out = Object.assign({}, Utils.mapValueFieldNames(values, options.fields, model));
|
2687 | for (const key of model._virtualAttributes) {
|
2688 | delete out[key];
|
2689 | }
|
2690 | return out;
|
2691 | });
|
2692 |
|
2693 |
|
2694 | const fieldMappedAttributes = {};
|
2695 | for (const attr in model.tableAttributes) {
|
2696 | fieldMappedAttributes[model.rawAttributes[attr].field || attr] = model.rawAttributes[attr];
|
2697 | }
|
2698 |
|
2699 |
|
2700 | if (options.updateOnDuplicate) {
|
2701 | options.updateOnDuplicate = options.updateOnDuplicate.map(attr => model.rawAttributes[attr].field || attr);
|
2702 |
|
2703 | options.upsertKeys = _.chain(model.primaryKeys).values().map('field').value();
|
2704 | if (Object.keys(model.uniqueKeys).length > 0) {
|
2705 | options.upsertKeys = _.chain(model.uniqueKeys).values().filter(c => c.fields.length === 1).map('column').value();
|
2706 | }
|
2707 | }
|
2708 |
|
2709 |
|
2710 | if (options.returning && Array.isArray(options.returning)) {
|
2711 | options.returning = options.returning.map(attr => model.rawAttributes[attr].field || attr);
|
2712 | }
|
2713 |
|
2714 | return model.QueryInterface.bulkInsert(model.getTableName(options), records, options, fieldMappedAttributes).then(results => {
|
2715 | if (Array.isArray(results)) {
|
2716 | results.forEach((result, i) => {
|
2717 | const instance = instances[i];
|
2718 |
|
2719 | for (const key in result) {
|
2720 | if (!instance || key === model.primaryKeyAttribute &&
|
2721 | instance.get(model.primaryKeyAttribute) &&
|
2722 | ['mysql', 'mariadb', 'sqlite'].includes(dialect)) {
|
2723 |
|
2724 |
|
2725 |
|
2726 | continue;
|
2727 | }
|
2728 | if (Object.prototype.hasOwnProperty.call(result, key)) {
|
2729 | const record = result[key];
|
2730 |
|
2731 | const attr = _.find(model.rawAttributes, attribute => attribute.fieldName === key || attribute.field === key);
|
2732 |
|
2733 | instance.dataValues[attr && attr.fieldName || key] = record;
|
2734 | }
|
2735 | }
|
2736 | });
|
2737 | }
|
2738 | return results;
|
2739 | });
|
2740 | });
|
2741 | }).then(() => {
|
2742 | if (!options.include || !options.include.length) return;
|
2743 |
|
2744 |
|
2745 | return Promise.map(options.include.filter(include => !(include.association instanceof BelongsTo ||
|
2746 | include.parent && include.parent.association instanceof BelongsToMany)), include => {
|
2747 | const associationInstances = [];
|
2748 | const associationInstanceIndexToInstanceMap = [];
|
2749 |
|
2750 | for (const instance of instances) {
|
2751 | let associated = instance.get(include.as);
|
2752 | if (!Array.isArray(associated)) associated = [associated];
|
2753 |
|
2754 | for (const associationInstance of associated) {
|
2755 | if (associationInstance) {
|
2756 | if (!(include.association instanceof BelongsToMany)) {
|
2757 | associationInstance.set(include.association.foreignKey, instance.get(include.association.sourceKey || instance.constructor.primaryKeyAttribute, { raw: true }), { raw: true });
|
2758 | Object.assign(associationInstance, include.association.scope);
|
2759 | }
|
2760 | associationInstances.push(associationInstance);
|
2761 | associationInstanceIndexToInstanceMap.push(instance);
|
2762 | }
|
2763 | }
|
2764 | }
|
2765 |
|
2766 | if (!associationInstances.length) {
|
2767 | return;
|
2768 | }
|
2769 |
|
2770 | const includeOptions = _(Utils.cloneDeep(include))
|
2771 | .omit(['association'])
|
2772 | .defaults({
|
2773 | transaction: options.transaction,
|
2774 | logging: options.logging
|
2775 | }).value();
|
2776 |
|
2777 | return recursiveBulkCreate(associationInstances, includeOptions).then(associationInstances => {
|
2778 | if (include.association instanceof BelongsToMany) {
|
2779 | const valueSets = [];
|
2780 |
|
2781 | for (const idx in associationInstances) {
|
2782 | const associationInstance = associationInstances[idx];
|
2783 | const instance = associationInstanceIndexToInstanceMap[idx];
|
2784 |
|
2785 | const values = {};
|
2786 | values[include.association.foreignKey] = instance.get(instance.constructor.primaryKeyAttribute, { raw: true });
|
2787 | values[include.association.otherKey] = associationInstance.get(associationInstance.constructor.primaryKeyAttribute, { raw: true });
|
2788 |
|
2789 |
|
2790 | Object.assign(values, include.association.through.scope);
|
2791 | if (associationInstance[include.association.through.model.name]) {
|
2792 | for (const attr of Object.keys(include.association.through.model.rawAttributes)) {
|
2793 | if (include.association.through.model.rawAttributes[attr]._autoGenerated ||
|
2794 | attr === include.association.foreignKey ||
|
2795 | attr === include.association.otherKey ||
|
2796 | typeof associationInstance[include.association.through.model.name][attr] === undefined) {
|
2797 | continue;
|
2798 | }
|
2799 | values[attr] = associationInstance[include.association.through.model.name][attr];
|
2800 | }
|
2801 | }
|
2802 |
|
2803 | valueSets.push(values);
|
2804 | }
|
2805 |
|
2806 | const throughOptions = _(Utils.cloneDeep(include))
|
2807 | .omit(['association', 'attributes'])
|
2808 | .defaults({
|
2809 | transaction: options.transaction,
|
2810 | logging: options.logging
|
2811 | }).value();
|
2812 | throughOptions.model = include.association.throughModel;
|
2813 | const throughInstances = include.association.throughModel.bulkBuild(valueSets, throughOptions);
|
2814 |
|
2815 | return recursiveBulkCreate(throughInstances, throughOptions);
|
2816 | }
|
2817 | });
|
2818 | });
|
2819 | }).then(() => {
|
2820 |
|
2821 | instances.forEach(instance => {
|
2822 | for (const attr in model.rawAttributes) {
|
2823 | if (model.rawAttributes[attr].field &&
|
2824 | instance.dataValues[model.rawAttributes[attr].field] !== undefined &&
|
2825 | model.rawAttributes[attr].field !== attr
|
2826 | ) {
|
2827 | instance.dataValues[attr] = instance.dataValues[model.rawAttributes[attr].field];
|
2828 | delete instance.dataValues[model.rawAttributes[attr].field];
|
2829 | }
|
2830 | instance._previousDataValues[attr] = instance.dataValues[attr];
|
2831 | instance.changed(attr, false);
|
2832 | }
|
2833 | instance.isNewRecord = false;
|
2834 | });
|
2835 |
|
2836 |
|
2837 | if (options.hooks) {
|
2838 | return model.runHooks('afterBulkCreate', instances, options);
|
2839 | }
|
2840 | }).then(() => instances);
|
2841 | };
|
2842 |
|
2843 | return recursiveBulkCreate(instances, options);
|
2844 | }
|
2845 |
|
2846 | |
2847 |
|
2848 |
|
2849 |
|
2850 |
|
2851 |
|
2852 |
|
2853 |
|
2854 |
|
2855 |
|
2856 |
|
2857 |
|
2858 |
|
2859 |
|
2860 |
|
2861 |
|
2862 | static truncate(options) {
|
2863 | options = Utils.cloneDeep(options) || {};
|
2864 | options.truncate = true;
|
2865 | return this.destroy(options);
|
2866 | }
|
2867 |
|
2868 | |
2869 |
|
2870 |
|
2871 |
|
2872 |
|
2873 |
|
2874 |
|
2875 |
|
2876 |
|
2877 |
|
2878 |
|
2879 |
|
2880 |
|
2881 |
|
2882 |
|
2883 |
|
2884 |
|
2885 |
|
2886 | static destroy(options) {
|
2887 | options = Utils.cloneDeep(options);
|
2888 |
|
2889 | this._injectScope(options);
|
2890 |
|
2891 | if (!options || !(options.where || options.truncate)) {
|
2892 | throw new Error('Missing where or truncate attribute in the options parameter of model.destroy.');
|
2893 | }
|
2894 |
|
2895 | if (!options.truncate && !_.isPlainObject(options.where) && !Array.isArray(options.where) && !(options.where instanceof Utils.SequelizeMethod)) {
|
2896 | throw new Error('Expected plain object, array or sequelize method in the options.where parameter of model.destroy.');
|
2897 | }
|
2898 |
|
2899 | options = _.defaults(options, {
|
2900 | hooks: true,
|
2901 | individualHooks: false,
|
2902 | force: false,
|
2903 | cascade: false,
|
2904 | restartIdentity: false
|
2905 | });
|
2906 |
|
2907 | options.type = QueryTypes.BULKDELETE;
|
2908 |
|
2909 | Utils.mapOptionFieldNames(options, this);
|
2910 | options.model = this;
|
2911 |
|
2912 | let instances;
|
2913 |
|
2914 | return Promise.try(() => {
|
2915 |
|
2916 | if (options.hooks) {
|
2917 | return this.runHooks('beforeBulkDestroy', options);
|
2918 | }
|
2919 | }).then(() => {
|
2920 |
|
2921 | if (options.individualHooks) {
|
2922 | return this.findAll({ where: options.where, transaction: options.transaction, logging: options.logging, benchmark: options.benchmark })
|
2923 | .map(instance => this.runHooks('beforeDestroy', instance, options).then(() => instance))
|
2924 | .then(_instances => {
|
2925 | instances = _instances;
|
2926 | });
|
2927 | }
|
2928 | }).then(() => {
|
2929 |
|
2930 | if (this._timestampAttributes.deletedAt && !options.force) {
|
2931 |
|
2932 | options.type = QueryTypes.BULKUPDATE;
|
2933 |
|
2934 | const attrValueHash = {};
|
2935 | const deletedAtAttribute = this.rawAttributes[this._timestampAttributes.deletedAt];
|
2936 | const field = this.rawAttributes[this._timestampAttributes.deletedAt].field;
|
2937 | const where = {
|
2938 | [field]: Object.prototype.hasOwnProperty.call(deletedAtAttribute, 'defaultValue') ? deletedAtAttribute.defaultValue : null
|
2939 | };
|
2940 |
|
2941 |
|
2942 | attrValueHash[field] = Utils.now(this.sequelize.options.dialect);
|
2943 | return this.QueryInterface.bulkUpdate(this.getTableName(options), attrValueHash, Object.assign(where, options.where), options, this.rawAttributes);
|
2944 | }
|
2945 | return this.QueryInterface.bulkDelete(this.getTableName(options), options.where, options, this);
|
2946 | }).tap(() => {
|
2947 |
|
2948 | if (options.individualHooks) {
|
2949 | return Promise.map(instances, instance => this.runHooks('afterDestroy', instance, options));
|
2950 | }
|
2951 | }).tap(() => {
|
2952 |
|
2953 | if (options.hooks) {
|
2954 | return this.runHooks('afterBulkDestroy', options);
|
2955 | }
|
2956 | });
|
2957 | }
|
2958 |
|
2959 | |
2960 |
|
2961 |
|
2962 |
|
2963 |
|
2964 |
|
2965 |
|
2966 |
|
2967 |
|
2968 |
|
2969 |
|
2970 |
|
2971 |
|
2972 |
|
2973 | static restore(options) {
|
2974 | if (!this._timestampAttributes.deletedAt) throw new Error('Model is not paranoid');
|
2975 |
|
2976 | options = Object.assign({
|
2977 | hooks: true,
|
2978 | individualHooks: false
|
2979 | }, options || {});
|
2980 |
|
2981 | options.type = QueryTypes.RAW;
|
2982 | options.model = this;
|
2983 |
|
2984 | let instances;
|
2985 |
|
2986 | Utils.mapOptionFieldNames(options, this);
|
2987 |
|
2988 | return Promise.try(() => {
|
2989 |
|
2990 | if (options.hooks) {
|
2991 | return this.runHooks('beforeBulkRestore', options);
|
2992 | }
|
2993 | }).then(() => {
|
2994 |
|
2995 | if (options.individualHooks) {
|
2996 | return this.findAll({ where: options.where, transaction: options.transaction, logging: options.logging, benchmark: options.benchmark, paranoid: false })
|
2997 | .map(instance => this.runHooks('beforeRestore', instance, options).then(() => instance))
|
2998 | .then(_instances => {
|
2999 | instances = _instances;
|
3000 | });
|
3001 | }
|
3002 | }).then(() => {
|
3003 |
|
3004 | const attrValueHash = {};
|
3005 | const deletedAtCol = this._timestampAttributes.deletedAt;
|
3006 | const deletedAtAttribute = this.rawAttributes[deletedAtCol];
|
3007 | const deletedAtDefaultValue = Object.prototype.hasOwnProperty.call(deletedAtAttribute, 'defaultValue') ? deletedAtAttribute.defaultValue : null;
|
3008 |
|
3009 | attrValueHash[deletedAtAttribute.field || deletedAtCol] = deletedAtDefaultValue;
|
3010 | options.omitNull = false;
|
3011 | return this.QueryInterface.bulkUpdate(this.getTableName(options), attrValueHash, options.where, options, this.rawAttributes);
|
3012 | }).tap(() => {
|
3013 |
|
3014 | if (options.individualHooks) {
|
3015 | return Promise.map(instances, instance => this.runHooks('afterRestore', instance, options));
|
3016 | }
|
3017 | }).tap(() => {
|
3018 |
|
3019 | if (options.hooks) {
|
3020 | return this.runHooks('afterBulkRestore', options);
|
3021 | }
|
3022 | });
|
3023 | }
|
3024 |
|
3025 | |
3026 |
|
3027 |
|
3028 |
|
3029 |
|
3030 |
|
3031 |
|
3032 |
|
3033 |
|
3034 |
|
3035 |
|
3036 |
|
3037 |
|
3038 |
|
3039 |
|
3040 |
|
3041 |
|
3042 |
|
3043 |
|
3044 |
|
3045 |
|
3046 |
|
3047 |
|
3048 | static update(values, options) {
|
3049 | options = Utils.cloneDeep(options);
|
3050 |
|
3051 | this._injectScope(options);
|
3052 | this._optionsMustContainWhere(options);
|
3053 |
|
3054 | options = this._paranoidClause(this, _.defaults(options, {
|
3055 | validate: true,
|
3056 | hooks: true,
|
3057 | individualHooks: false,
|
3058 | returning: false,
|
3059 | force: false,
|
3060 | sideEffects: true
|
3061 | }));
|
3062 |
|
3063 | options.type = QueryTypes.BULKUPDATE;
|
3064 |
|
3065 |
|
3066 | values = _.omitBy(values, value => value === undefined);
|
3067 |
|
3068 |
|
3069 | if (options.fields && options.fields instanceof Array) {
|
3070 | for (const key of Object.keys(values)) {
|
3071 | if (!options.fields.includes(key)) {
|
3072 | delete values[key];
|
3073 | }
|
3074 | }
|
3075 | } else {
|
3076 | const updatedAtAttr = this._timestampAttributes.updatedAt;
|
3077 | options.fields = _.intersection(Object.keys(values), Object.keys(this.tableAttributes));
|
3078 | if (updatedAtAttr && !options.fields.includes(updatedAtAttr)) {
|
3079 | options.fields.push(updatedAtAttr);
|
3080 | }
|
3081 | }
|
3082 |
|
3083 | if (this._timestampAttributes.updatedAt && !options.silent) {
|
3084 | values[this._timestampAttributes.updatedAt] = this._getDefaultTimestamp(this._timestampAttributes.updatedAt) || Utils.now(this.sequelize.options.dialect);
|
3085 | }
|
3086 |
|
3087 | options.model = this;
|
3088 |
|
3089 | let instances;
|
3090 | let valuesUse;
|
3091 |
|
3092 | return Promise.try(() => {
|
3093 |
|
3094 | if (options.validate) {
|
3095 | const build = this.build(values);
|
3096 | build.set(this._timestampAttributes.updatedAt, values[this._timestampAttributes.updatedAt], { raw: true });
|
3097 |
|
3098 | if (options.sideEffects) {
|
3099 | values = Object.assign(values, _.pick(build.get(), build.changed()));
|
3100 | options.fields = _.union(options.fields, Object.keys(values));
|
3101 | }
|
3102 |
|
3103 |
|
3104 | options.skip = _.difference(Object.keys(this.rawAttributes), Object.keys(values));
|
3105 | return build.validate(options).then(attributes => {
|
3106 | options.skip = undefined;
|
3107 | if (attributes && attributes.dataValues) {
|
3108 | values = _.pick(attributes.dataValues, Object.keys(values));
|
3109 | }
|
3110 | });
|
3111 | }
|
3112 | return null;
|
3113 | }).then(() => {
|
3114 |
|
3115 | if (options.hooks) {
|
3116 | options.attributes = values;
|
3117 | return this.runHooks('beforeBulkUpdate', options).then(() => {
|
3118 | values = options.attributes;
|
3119 | delete options.attributes;
|
3120 | });
|
3121 | }
|
3122 | return null;
|
3123 | }).then(() => {
|
3124 | valuesUse = values;
|
3125 |
|
3126 |
|
3127 | if (options.individualHooks) {
|
3128 | return this.findAll({
|
3129 | where: options.where,
|
3130 | transaction: options.transaction,
|
3131 | logging: options.logging,
|
3132 | benchmark: options.benchmark,
|
3133 | paranoid: options.paranoid
|
3134 | }).then(_instances => {
|
3135 | instances = _instances;
|
3136 | if (!instances.length) {
|
3137 | return [];
|
3138 | }
|
3139 |
|
3140 |
|
3141 |
|
3142 | let changedValues;
|
3143 | let different = false;
|
3144 |
|
3145 | return Promise.map(instances, instance => {
|
3146 |
|
3147 | Object.assign(instance.dataValues, values);
|
3148 |
|
3149 | _.forIn(valuesUse, (newValue, attr) => {
|
3150 | if (newValue !== instance._previousDataValues[attr]) {
|
3151 | instance.setDataValue(attr, newValue);
|
3152 | }
|
3153 | });
|
3154 |
|
3155 |
|
3156 | return this.runHooks('beforeUpdate', instance, options).then(() => {
|
3157 | if (!different) {
|
3158 | const thisChangedValues = {};
|
3159 | _.forIn(instance.dataValues, (newValue, attr) => {
|
3160 | if (newValue !== instance._previousDataValues[attr]) {
|
3161 | thisChangedValues[attr] = newValue;
|
3162 | }
|
3163 | });
|
3164 |
|
3165 | if (!changedValues) {
|
3166 | changedValues = thisChangedValues;
|
3167 | } else {
|
3168 | different = !_.isEqual(changedValues, thisChangedValues);
|
3169 | }
|
3170 | }
|
3171 |
|
3172 | return instance;
|
3173 | });
|
3174 | }).then(_instances => {
|
3175 | instances = _instances;
|
3176 |
|
3177 | if (!different) {
|
3178 | const keys = Object.keys(changedValues);
|
3179 |
|
3180 | if (keys.length) {
|
3181 |
|
3182 | valuesUse = changedValues;
|
3183 | options.fields = _.union(options.fields, keys);
|
3184 | }
|
3185 | return;
|
3186 | }
|
3187 |
|
3188 |
|
3189 | return Promise.map(instances, instance => {
|
3190 | const individualOptions = _.clone(options);
|
3191 | delete individualOptions.individualHooks;
|
3192 | individualOptions.hooks = false;
|
3193 | individualOptions.validate = false;
|
3194 |
|
3195 | return instance.save(individualOptions);
|
3196 | }).tap(_instances => {
|
3197 | instances = _instances;
|
3198 | });
|
3199 | });
|
3200 | });
|
3201 | }
|
3202 | }).then(results => {
|
3203 |
|
3204 | if (results) {
|
3205 | return [results.length, results];
|
3206 | }
|
3207 |
|
3208 |
|
3209 | if (
|
3210 | _.isEmpty(valuesUse)
|
3211 | || Object.keys(valuesUse).length === 1 && valuesUse[this._timestampAttributes.updatedAt]
|
3212 | ) {
|
3213 | return [0];
|
3214 | }
|
3215 |
|
3216 | valuesUse = Utils.mapValueFieldNames(valuesUse, options.fields, this);
|
3217 | options = Utils.mapOptionFieldNames(options, this);
|
3218 | options.hasTrigger = this.options ? this.options.hasTrigger : false;
|
3219 |
|
3220 |
|
3221 | return this.QueryInterface.bulkUpdate(this.getTableName(options), valuesUse, options.where, options, this.tableAttributes).then(affectedRows => {
|
3222 | if (options.returning) {
|
3223 | instances = affectedRows;
|
3224 | return [affectedRows.length, affectedRows];
|
3225 | }
|
3226 |
|
3227 | return [affectedRows];
|
3228 | });
|
3229 | }).tap(result => {
|
3230 | if (options.individualHooks) {
|
3231 | return Promise.map(instances, instance => {
|
3232 | return this.runHooks('afterUpdate', instance, options);
|
3233 | }).then(() => {
|
3234 | result[1] = instances;
|
3235 | });
|
3236 | }
|
3237 | }).tap(() => {
|
3238 |
|
3239 | if (options.hooks) {
|
3240 | options.attributes = values;
|
3241 | return this.runHooks('afterBulkUpdate', options).then(() => {
|
3242 | delete options.attributes;
|
3243 | });
|
3244 | }
|
3245 | });
|
3246 | }
|
3247 |
|
3248 | |
3249 |
|
3250 |
|
3251 |
|
3252 |
|
3253 |
|
3254 |
|
3255 |
|
3256 | static describe(schema, options) {
|
3257 | return this.QueryInterface.describeTable(this.tableName, Object.assign({ schema: schema || this._schema || undefined }, options));
|
3258 | }
|
3259 |
|
3260 | static _getDefaultTimestamp(attr) {
|
3261 | if (!!this.rawAttributes[attr] && !!this.rawAttributes[attr].defaultValue) {
|
3262 | return Utils.toDefaultValue(this.rawAttributes[attr].defaultValue, this.sequelize.options.dialect);
|
3263 | }
|
3264 | return undefined;
|
3265 | }
|
3266 |
|
3267 | static _expandAttributes(options) {
|
3268 | if (!_.isPlainObject(options.attributes)) {
|
3269 | return;
|
3270 | }
|
3271 | let attributes = Object.keys(this.rawAttributes);
|
3272 |
|
3273 | if (options.attributes.exclude) {
|
3274 | attributes = attributes.filter(elem => !options.attributes.exclude.includes(elem));
|
3275 | }
|
3276 |
|
3277 | if (options.attributes.include) {
|
3278 | attributes = attributes.concat(options.attributes.include);
|
3279 | }
|
3280 |
|
3281 | options.attributes = attributes;
|
3282 | }
|
3283 |
|
3284 |
|
3285 | static _injectScope(options) {
|
3286 | const scope = Utils.cloneDeep(this._scope);
|
3287 | this._defaultsOptions(options, scope);
|
3288 | }
|
3289 |
|
3290 | static [Symbol.for('nodejs.util.inspect.custom')]() {
|
3291 | return this.name;
|
3292 | }
|
3293 |
|
3294 | static inspect() {
|
3295 | return this.name;
|
3296 | }
|
3297 |
|
3298 | static hasAlias(alias) {
|
3299 | return Object.prototype.hasOwnProperty.call(this.associations, alias);
|
3300 | }
|
3301 |
|
3302 | |
3303 |
|
3304 |
|
3305 |
|
3306 |
|
3307 |
|
3308 |
|
3309 |
|
3310 |
|
3311 |
|
3312 |
|
3313 |
|
3314 |
|
3315 |
|
3316 |
|
3317 |
|
3318 |
|
3319 |
|
3320 |
|
3321 |
|
3322 |
|
3323 |
|
3324 |
|
3325 |
|
3326 |
|
3327 |
|
3328 |
|
3329 |
|
3330 | static increment(fields, options) {
|
3331 | options = options || {};
|
3332 |
|
3333 | this._injectScope(options);
|
3334 | this._optionsMustContainWhere(options);
|
3335 |
|
3336 | const updatedAtAttr = this._timestampAttributes.updatedAt;
|
3337 | const versionAttr = this._versionAttribute;
|
3338 | const updatedAtAttribute = this.rawAttributes[updatedAtAttr];
|
3339 | options = Utils.defaults({}, options, {
|
3340 | by: 1,
|
3341 | attributes: {},
|
3342 | where: {},
|
3343 | increment: true
|
3344 | });
|
3345 |
|
3346 | Utils.mapOptionFieldNames(options, this);
|
3347 |
|
3348 | const where = Object.assign({}, options.where);
|
3349 | let values = {};
|
3350 |
|
3351 | if (typeof fields === 'string') {
|
3352 | values[fields] = options.by;
|
3353 | } else if (Array.isArray(fields)) {
|
3354 | fields.forEach(field => {
|
3355 | values[field] = options.by;
|
3356 | });
|
3357 | } else {
|
3358 | values = fields;
|
3359 | }
|
3360 |
|
3361 | if (!options.silent && updatedAtAttr && !values[updatedAtAttr]) {
|
3362 | options.attributes[updatedAtAttribute.field || updatedAtAttr] = this._getDefaultTimestamp(updatedAtAttr) || Utils.now(this.sequelize.options.dialect);
|
3363 | }
|
3364 | if (versionAttr) {
|
3365 | values[versionAttr] = options.increment ? 1 : -1;
|
3366 | }
|
3367 |
|
3368 | for (const attr of Object.keys(values)) {
|
3369 |
|
3370 | if (this.rawAttributes[attr] && this.rawAttributes[attr].field && this.rawAttributes[attr].field !== attr) {
|
3371 | values[this.rawAttributes[attr].field] = values[attr];
|
3372 | delete values[attr];
|
3373 | }
|
3374 | }
|
3375 |
|
3376 | let promise;
|
3377 | if (!options.increment) {
|
3378 | promise = this.QueryInterface.decrement(this, this.getTableName(options), values, where, options);
|
3379 | } else {
|
3380 | promise = this.QueryInterface.increment(this, this.getTableName(options), values, where, options);
|
3381 | }
|
3382 |
|
3383 | return promise.then(affectedRows => {
|
3384 | if (options.returning) {
|
3385 | return [affectedRows, affectedRows.length];
|
3386 | }
|
3387 |
|
3388 | return [affectedRows];
|
3389 | });
|
3390 | }
|
3391 |
|
3392 | |
3393 |
|
3394 |
|
3395 |
|
3396 |
|
3397 |
|
3398 |
|
3399 |
|
3400 |
|
3401 |
|
3402 |
|
3403 |
|
3404 |
|
3405 |
|
3406 |
|
3407 |
|
3408 |
|
3409 |
|
3410 |
|
3411 |
|
3412 |
|
3413 |
|
3414 |
|
3415 |
|
3416 |
|
3417 | static decrement(fields, options) {
|
3418 | options = _.defaults({ increment: false }, options, {
|
3419 | by: 1
|
3420 | });
|
3421 |
|
3422 | return this.increment(fields, options);
|
3423 | }
|
3424 |
|
3425 | static _optionsMustContainWhere(options) {
|
3426 | assert(options && options.where, 'Missing where attribute in the options parameter');
|
3427 | assert(_.isPlainObject(options.where) || Array.isArray(options.where) || options.where instanceof Utils.SequelizeMethod,
|
3428 | 'Expected plain object, array or sequelize method in the options.where parameter');
|
3429 | }
|
3430 |
|
3431 | |
3432 |
|
3433 |
|
3434 |
|
3435 |
|
3436 |
|
3437 |
|
3438 | where(checkVersion) {
|
3439 | const where = this.constructor.primaryKeyAttributes.reduce((result, attribute) => {
|
3440 | result[attribute] = this.get(attribute, { raw: true });
|
3441 | return result;
|
3442 | }, {});
|
3443 |
|
3444 | if (_.size(where) === 0) {
|
3445 | return this._modelOptions.whereCollection;
|
3446 | }
|
3447 | const versionAttr = this.constructor._versionAttribute;
|
3448 | if (checkVersion && versionAttr) {
|
3449 | where[versionAttr] = this.get(versionAttr, { raw: true });
|
3450 | }
|
3451 | return Utils.mapWhereFieldNames(where, this.constructor);
|
3452 | }
|
3453 |
|
3454 | toString() {
|
3455 | return `[object SequelizeInstance:${this.constructor.name}]`;
|
3456 | }
|
3457 |
|
3458 | |
3459 |
|
3460 |
|
3461 |
|
3462 |
|
3463 |
|
3464 |
|
3465 | getDataValue(key) {
|
3466 | return this.dataValues[key];
|
3467 | }
|
3468 |
|
3469 | |
3470 |
|
3471 |
|
3472 |
|
3473 |
|
3474 |
|
3475 |
|
3476 | setDataValue(key, value) {
|
3477 | const originalValue = this._previousDataValues[key];
|
3478 |
|
3479 | if (!Utils.isPrimitive(value) && value !== null || value !== originalValue) {
|
3480 | this.changed(key, true);
|
3481 | }
|
3482 |
|
3483 | this.dataValues[key] = value;
|
3484 | }
|
3485 |
|
3486 | |
3487 |
|
3488 |
|
3489 |
|
3490 |
|
3491 |
|
3492 |
|
3493 |
|
3494 |
|
3495 |
|
3496 |
|
3497 |
|
3498 | get(key, options) {
|
3499 | if (options === undefined && typeof key === 'object') {
|
3500 | options = key;
|
3501 | key = undefined;
|
3502 | }
|
3503 |
|
3504 | options = options || {};
|
3505 |
|
3506 | if (key) {
|
3507 | if (Object.prototype.hasOwnProperty.call(this._customGetters, key) && !options.raw) {
|
3508 | return this._customGetters[key].call(this, key, options);
|
3509 | }
|
3510 |
|
3511 | if (options.plain && this._options.include && this._options.includeNames.includes(key)) {
|
3512 | if (Array.isArray(this.dataValues[key])) {
|
3513 | return this.dataValues[key].map(instance => instance.get(options));
|
3514 | }
|
3515 | if (this.dataValues[key] instanceof Model) {
|
3516 | return this.dataValues[key].get(options);
|
3517 | }
|
3518 | return this.dataValues[key];
|
3519 | }
|
3520 |
|
3521 | return this.dataValues[key];
|
3522 | }
|
3523 |
|
3524 | if (
|
3525 | this._hasCustomGetters
|
3526 | || options.plain && this._options.include
|
3527 | || options.clone
|
3528 | ) {
|
3529 | const values = {};
|
3530 | let _key;
|
3531 |
|
3532 | if (this._hasCustomGetters) {
|
3533 | for (_key in this._customGetters) {
|
3534 | if (
|
3535 | this._options.attributes
|
3536 | && !this._options.attributes.includes(_key)
|
3537 | ) {
|
3538 | continue;
|
3539 | }
|
3540 |
|
3541 | if (Object.prototype.hasOwnProperty.call(this._customGetters, _key)) {
|
3542 | values[_key] = this.get(_key, options);
|
3543 | }
|
3544 | }
|
3545 | }
|
3546 |
|
3547 | for (_key in this.dataValues) {
|
3548 | if (
|
3549 | !Object.prototype.hasOwnProperty.call(values, _key)
|
3550 | && Object.prototype.hasOwnProperty.call(this.dataValues, _key)
|
3551 | ) {
|
3552 | values[_key] = this.get(_key, options);
|
3553 | }
|
3554 | }
|
3555 |
|
3556 | return values;
|
3557 | }
|
3558 |
|
3559 | return this.dataValues;
|
3560 | }
|
3561 |
|
3562 | |
3563 |
|
3564 |
|
3565 |
|
3566 |
|
3567 |
|
3568 |
|
3569 |
|
3570 |
|
3571 |
|
3572 |
|
3573 |
|
3574 |
|
3575 |
|
3576 |
|
3577 |
|
3578 |
|
3579 |
|
3580 |
|
3581 |
|
3582 |
|
3583 |
|
3584 |
|
3585 |
|
3586 |
|
3587 |
|
3588 |
|
3589 | set(key, value, options) {
|
3590 | let values;
|
3591 | let originalValue;
|
3592 |
|
3593 | if (typeof key === 'object' && key !== null) {
|
3594 | values = key;
|
3595 | options = value || {};
|
3596 |
|
3597 | if (options.reset) {
|
3598 | this.dataValues = {};
|
3599 | for (const key in values) {
|
3600 | this.changed(key, false);
|
3601 | }
|
3602 | }
|
3603 |
|
3604 |
|
3605 | if (options.raw && !(this._options && this._options.include) && !(options && options.attributes) && !this.constructor._hasDateAttributes && !this.constructor._hasBooleanAttributes) {
|
3606 | if (Object.keys(this.dataValues).length) {
|
3607 | this.dataValues = Object.assign(this.dataValues, values);
|
3608 | } else {
|
3609 | this.dataValues = values;
|
3610 | }
|
3611 |
|
3612 | this._previousDataValues = _.clone(this.dataValues);
|
3613 | } else {
|
3614 |
|
3615 | if (options.attributes) {
|
3616 | const setKeys = data => {
|
3617 | for (const k of data) {
|
3618 | if (values[k] === undefined) {
|
3619 | continue;
|
3620 | }
|
3621 | this.set(k, values[k], options);
|
3622 | }
|
3623 | };
|
3624 | setKeys(options.attributes);
|
3625 | if (this.constructor._hasVirtualAttributes) {
|
3626 | setKeys(this.constructor._virtualAttributes);
|
3627 | }
|
3628 | if (this._options.includeNames) {
|
3629 | setKeys(this._options.includeNames);
|
3630 | }
|
3631 | } else {
|
3632 | for (const key in values) {
|
3633 | this.set(key, values[key], options);
|
3634 | }
|
3635 | }
|
3636 |
|
3637 | if (options.raw) {
|
3638 |
|
3639 | this._previousDataValues = _.clone(this.dataValues);
|
3640 | }
|
3641 | }
|
3642 | return this;
|
3643 | }
|
3644 | if (!options)
|
3645 | options = {};
|
3646 | if (!options.raw) {
|
3647 | originalValue = this.dataValues[key];
|
3648 | }
|
3649 |
|
3650 |
|
3651 | if (!options.raw && this._customSetters[key]) {
|
3652 | this._customSetters[key].call(this, value, key);
|
3653 |
|
3654 |
|
3655 | const newValue = this.dataValues[key];
|
3656 | if (!Utils.isPrimitive(newValue) && newValue !== null || newValue !== originalValue) {
|
3657 | this._previousDataValues[key] = originalValue;
|
3658 | this.changed(key, true);
|
3659 | }
|
3660 | } else {
|
3661 |
|
3662 | if (this._options && this._options.include && this._options.includeNames.includes(key)) {
|
3663 |
|
3664 | this._setInclude(key, value, options);
|
3665 | return this;
|
3666 | }
|
3667 |
|
3668 | if (!options.raw) {
|
3669 |
|
3670 | if (!this._isAttribute(key)) {
|
3671 | if (key.includes('.') && this.constructor._jsonAttributes.has(key.split('.')[0])) {
|
3672 | const previousNestedValue = Dottie.get(this.dataValues, key);
|
3673 | if (!_.isEqual(previousNestedValue, value)) {
|
3674 | Dottie.set(this.dataValues, key, value);
|
3675 | this.changed(key.split('.')[0], true);
|
3676 | }
|
3677 | }
|
3678 | return this;
|
3679 | }
|
3680 |
|
3681 |
|
3682 | if (this.constructor._hasPrimaryKeys && originalValue && this.constructor._isPrimaryKey(key)) {
|
3683 | return this;
|
3684 | }
|
3685 |
|
3686 |
|
3687 | if (!this.isNewRecord && this.constructor._hasReadOnlyAttributes && this.constructor._readOnlyAttributes.has(key)) {
|
3688 | return this;
|
3689 | }
|
3690 | }
|
3691 |
|
3692 |
|
3693 | if (
|
3694 | !(value instanceof Utils.SequelizeMethod)
|
3695 | && Object.prototype.hasOwnProperty.call(this.constructor._dataTypeSanitizers, key)
|
3696 | ) {
|
3697 | value = this.constructor._dataTypeSanitizers[key].call(this, value, options);
|
3698 | }
|
3699 |
|
3700 |
|
3701 | if (
|
3702 | !options.raw &&
|
3703 | (
|
3704 |
|
3705 | value instanceof Utils.SequelizeMethod ||
|
3706 |
|
3707 | !(value instanceof Utils.SequelizeMethod) && this.constructor._dataTypeChanges[key] && this.constructor._dataTypeChanges[key].call(this, value, originalValue, options) ||
|
3708 |
|
3709 | !this.constructor._dataTypeChanges[key] && (!Utils.isPrimitive(value) && value !== null || value !== originalValue)
|
3710 | )
|
3711 | ) {
|
3712 | this._previousDataValues[key] = originalValue;
|
3713 | this.changed(key, true);
|
3714 | }
|
3715 |
|
3716 |
|
3717 | this.dataValues[key] = value;
|
3718 | }
|
3719 | return this;
|
3720 | }
|
3721 |
|
3722 | setAttributes(updates) {
|
3723 | return this.set(updates);
|
3724 | }
|
3725 |
|
3726 | |
3727 |
|
3728 |
|
3729 |
|
3730 |
|
3731 |
|
3732 |
|
3733 |
|
3734 |
|
3735 |
|
3736 |
|
3737 |
|
3738 | changed(key, value) {
|
3739 | if (key) {
|
3740 | if (value !== undefined) {
|
3741 | this._changed[key] = value;
|
3742 | return this;
|
3743 | }
|
3744 | return this._changed[key] || false;
|
3745 | }
|
3746 |
|
3747 | const changed = Object.keys(this.dataValues).filter(key => this.changed(key));
|
3748 |
|
3749 | return changed.length ? changed : false;
|
3750 | }
|
3751 |
|
3752 | |
3753 |
|
3754 |
|
3755 |
|
3756 |
|
3757 |
|
3758 |
|
3759 |
|
3760 |
|
3761 | previous(key) {
|
3762 | if (key) {
|
3763 | return this._previousDataValues[key];
|
3764 | }
|
3765 |
|
3766 | return _.pickBy(this._previousDataValues, (value, key) => this.changed(key));
|
3767 | }
|
3768 |
|
3769 | _setInclude(key, value, options) {
|
3770 | if (!Array.isArray(value)) value = [value];
|
3771 | if (value[0] instanceof Model) {
|
3772 | value = value.map(instance => instance.dataValues);
|
3773 | }
|
3774 |
|
3775 | const include = this._options.includeMap[key];
|
3776 | const association = include.association;
|
3777 | const accessor = key;
|
3778 | const primaryKeyAttribute = include.model.primaryKeyAttribute;
|
3779 | let childOptions;
|
3780 | let isEmpty;
|
3781 |
|
3782 | if (!isEmpty) {
|
3783 | childOptions = {
|
3784 | isNewRecord: this.isNewRecord,
|
3785 | include: include.include,
|
3786 | includeNames: include.includeNames,
|
3787 | includeMap: include.includeMap,
|
3788 | includeValidated: true,
|
3789 | raw: options.raw,
|
3790 | attributes: include.originalAttributes
|
3791 | };
|
3792 | }
|
3793 | if (include.originalAttributes === undefined || include.originalAttributes.length) {
|
3794 | if (association.isSingleAssociation) {
|
3795 | if (Array.isArray(value)) {
|
3796 | value = value[0];
|
3797 | }
|
3798 | isEmpty = value && value[primaryKeyAttribute] === null || value === null;
|
3799 | this[accessor] = this.dataValues[accessor] = isEmpty ? null : include.model.build(value, childOptions);
|
3800 | } else {
|
3801 | isEmpty = value[0] && value[0][primaryKeyAttribute] === null;
|
3802 | this[accessor] = this.dataValues[accessor] = isEmpty ? [] : include.model.bulkBuild(value, childOptions);
|
3803 | }
|
3804 | }
|
3805 | }
|
3806 |
|
3807 | |
3808 |
|
3809 |
|
3810 |
|
3811 |
|
3812 |
|
3813 |
|
3814 |
|
3815 |
|
3816 |
|
3817 |
|
3818 |
|
3819 |
|
3820 |
|
3821 |
|
3822 |
|
3823 |
|
3824 |
|
3825 | save(options) {
|
3826 | if (arguments.length > 1) {
|
3827 | throw new Error('The second argument was removed in favor of the options object.');
|
3828 | }
|
3829 |
|
3830 | options = Utils.cloneDeep(options);
|
3831 | options = _.defaults(options, {
|
3832 | hooks: true,
|
3833 | validate: true
|
3834 | });
|
3835 |
|
3836 | if (!options.fields) {
|
3837 | if (this.isNewRecord) {
|
3838 | options.fields = Object.keys(this.constructor.rawAttributes);
|
3839 | } else {
|
3840 | options.fields = _.intersection(this.changed(), Object.keys(this.constructor.rawAttributes));
|
3841 | }
|
3842 |
|
3843 | options.defaultFields = options.fields;
|
3844 | }
|
3845 |
|
3846 | if (options.returning === undefined) {
|
3847 | if (options.association) {
|
3848 | options.returning = false;
|
3849 | } else if (this.isNewRecord) {
|
3850 | options.returning = true;
|
3851 | }
|
3852 | }
|
3853 |
|
3854 | const primaryKeyName = this.constructor.primaryKeyAttribute;
|
3855 | const primaryKeyAttribute = primaryKeyName && this.constructor.rawAttributes[primaryKeyName];
|
3856 | const createdAtAttr = this.constructor._timestampAttributes.createdAt;
|
3857 | const versionAttr = this.constructor._versionAttribute;
|
3858 | const hook = this.isNewRecord ? 'Create' : 'Update';
|
3859 | const wasNewRecord = this.isNewRecord;
|
3860 | const now = Utils.now(this.sequelize.options.dialect);
|
3861 | let updatedAtAttr = this.constructor._timestampAttributes.updatedAt;
|
3862 |
|
3863 | if (updatedAtAttr && options.fields.length >= 1 && !options.fields.includes(updatedAtAttr)) {
|
3864 | options.fields.push(updatedAtAttr);
|
3865 | }
|
3866 | if (versionAttr && options.fields.length >= 1 && !options.fields.includes(versionAttr)) {
|
3867 | options.fields.push(versionAttr);
|
3868 | }
|
3869 |
|
3870 | if (options.silent === true && !(this.isNewRecord && this.get(updatedAtAttr, { raw: true }))) {
|
3871 |
|
3872 | _.remove(options.fields, val => val === updatedAtAttr);
|
3873 | updatedAtAttr = false;
|
3874 | }
|
3875 |
|
3876 | if (this.isNewRecord === true) {
|
3877 | if (createdAtAttr && !options.fields.includes(createdAtAttr)) {
|
3878 | options.fields.push(createdAtAttr);
|
3879 | }
|
3880 |
|
3881 | if (primaryKeyAttribute && primaryKeyAttribute.defaultValue && !options.fields.includes(primaryKeyName)) {
|
3882 | options.fields.unshift(primaryKeyName);
|
3883 | }
|
3884 | }
|
3885 |
|
3886 | if (this.isNewRecord === false) {
|
3887 | if (primaryKeyName && this.get(primaryKeyName, { raw: true }) === undefined) {
|
3888 | throw new Error('You attempted to save an instance with no primary key, this is not allowed since it would result in a global update');
|
3889 | }
|
3890 | }
|
3891 |
|
3892 | if (updatedAtAttr && !options.silent && options.fields.includes(updatedAtAttr)) {
|
3893 | this.dataValues[updatedAtAttr] = this.constructor._getDefaultTimestamp(updatedAtAttr) || now;
|
3894 | }
|
3895 |
|
3896 | if (this.isNewRecord && createdAtAttr && !this.dataValues[createdAtAttr]) {
|
3897 | this.dataValues[createdAtAttr] = this.constructor._getDefaultTimestamp(createdAtAttr) || now;
|
3898 | }
|
3899 |
|
3900 | return Promise.try(() => {
|
3901 |
|
3902 | if (options.validate) {
|
3903 | return this.validate(options);
|
3904 | }
|
3905 | }).then(() => {
|
3906 |
|
3907 | if (options.hooks) {
|
3908 | const beforeHookValues = _.pick(this.dataValues, options.fields);
|
3909 | let ignoreChanged = _.difference(this.changed(), options.fields);
|
3910 | let hookChanged;
|
3911 | let afterHookValues;
|
3912 |
|
3913 | if (updatedAtAttr && options.fields.includes(updatedAtAttr)) {
|
3914 | ignoreChanged = _.without(ignoreChanged, updatedAtAttr);
|
3915 | }
|
3916 |
|
3917 | return this.constructor.runHooks(`before${hook}`, this, options)
|
3918 | .then(() => {
|
3919 | if (options.defaultFields && !this.isNewRecord) {
|
3920 | afterHookValues = _.pick(this.dataValues, _.difference(this.changed(), ignoreChanged));
|
3921 |
|
3922 | hookChanged = [];
|
3923 | for (const key of Object.keys(afterHookValues)) {
|
3924 | if (afterHookValues[key] !== beforeHookValues[key]) {
|
3925 | hookChanged.push(key);
|
3926 | }
|
3927 | }
|
3928 |
|
3929 | options.fields = _.uniq(options.fields.concat(hookChanged));
|
3930 | }
|
3931 |
|
3932 | if (hookChanged) {
|
3933 | if (options.validate) {
|
3934 |
|
3935 |
|
3936 | options.skip = _.difference(Object.keys(this.constructor.rawAttributes), hookChanged);
|
3937 | return this.validate(options).then(() => {
|
3938 | delete options.skip;
|
3939 | });
|
3940 | }
|
3941 | }
|
3942 | });
|
3943 | }
|
3944 | }).then(() => {
|
3945 | if (!options.fields.length) return this;
|
3946 | if (!this.isNewRecord) return this;
|
3947 | if (!this._options.include || !this._options.include.length) return this;
|
3948 |
|
3949 |
|
3950 | return Promise.map(this._options.include.filter(include => include.association instanceof BelongsTo), include => {
|
3951 | const instance = this.get(include.as);
|
3952 | if (!instance) return Promise.resolve();
|
3953 |
|
3954 | const includeOptions = _(Utils.cloneDeep(include))
|
3955 | .omit(['association'])
|
3956 | .defaults({
|
3957 | transaction: options.transaction,
|
3958 | logging: options.logging,
|
3959 | parentRecord: this
|
3960 | }).value();
|
3961 |
|
3962 | return instance.save(includeOptions).then(() => this[include.association.accessors.set](instance, { save: false, logging: options.logging }));
|
3963 | });
|
3964 | }).then(() => {
|
3965 | const realFields = options.fields.filter(field => !this.constructor._virtualAttributes.has(field));
|
3966 | if (!realFields.length) return this;
|
3967 | if (!this.changed() && !this.isNewRecord) return this;
|
3968 |
|
3969 | const versionFieldName = _.get(this.constructor.rawAttributes[versionAttr], 'field') || versionAttr;
|
3970 | let values = Utils.mapValueFieldNames(this.dataValues, options.fields, this.constructor);
|
3971 | let query = null;
|
3972 | let args = [];
|
3973 | let where;
|
3974 |
|
3975 | if (this.isNewRecord) {
|
3976 | query = 'insert';
|
3977 | args = [this, this.constructor.getTableName(options), values, options];
|
3978 | } else {
|
3979 | where = this.where(true);
|
3980 | if (versionAttr) {
|
3981 | values[versionFieldName] = parseInt(values[versionFieldName], 10) + 1;
|
3982 | }
|
3983 | query = 'update';
|
3984 | args = [this, this.constructor.getTableName(options), values, where, options];
|
3985 | }
|
3986 |
|
3987 | return this.constructor.QueryInterface[query](...args)
|
3988 | .then(([result, rowsUpdated])=> {
|
3989 | if (versionAttr) {
|
3990 |
|
3991 | if (rowsUpdated < 1) {
|
3992 | throw new sequelizeErrors.OptimisticLockError({
|
3993 | modelName: this.constructor.name,
|
3994 | values,
|
3995 | where
|
3996 | });
|
3997 | } else {
|
3998 | result.dataValues[versionAttr] = values[versionFieldName];
|
3999 | }
|
4000 | }
|
4001 |
|
4002 |
|
4003 | for (const attr of Object.keys(this.constructor.rawAttributes)) {
|
4004 | if (this.constructor.rawAttributes[attr].field &&
|
4005 | values[this.constructor.rawAttributes[attr].field] !== undefined &&
|
4006 | this.constructor.rawAttributes[attr].field !== attr
|
4007 | ) {
|
4008 | values[attr] = values[this.constructor.rawAttributes[attr].field];
|
4009 | delete values[this.constructor.rawAttributes[attr].field];
|
4010 | }
|
4011 | }
|
4012 | values = Object.assign(values, result.dataValues);
|
4013 |
|
4014 | result.dataValues = Object.assign(result.dataValues, values);
|
4015 | return result;
|
4016 | })
|
4017 | .tap(() => {
|
4018 | if (!wasNewRecord) return this;
|
4019 | if (!this._options.include || !this._options.include.length) return this;
|
4020 |
|
4021 |
|
4022 | return Promise.map(this._options.include.filter(include => !(include.association instanceof BelongsTo ||
|
4023 | include.parent && include.parent.association instanceof BelongsToMany)), include => {
|
4024 | let instances = this.get(include.as);
|
4025 |
|
4026 | if (!instances) return Promise.resolve();
|
4027 | if (!Array.isArray(instances)) instances = [instances];
|
4028 | if (!instances.length) return Promise.resolve();
|
4029 |
|
4030 | const includeOptions = _(Utils.cloneDeep(include))
|
4031 | .omit(['association'])
|
4032 | .defaults({
|
4033 | transaction: options.transaction,
|
4034 | logging: options.logging,
|
4035 | parentRecord: this
|
4036 | }).value();
|
4037 |
|
4038 |
|
4039 | return Promise.map(instances, instance => {
|
4040 | if (include.association instanceof BelongsToMany) {
|
4041 | return instance.save(includeOptions).then(() => {
|
4042 | const values = {};
|
4043 | values[include.association.foreignKey] = this.get(this.constructor.primaryKeyAttribute, { raw: true });
|
4044 | values[include.association.otherKey] = instance.get(instance.constructor.primaryKeyAttribute, { raw: true });
|
4045 |
|
4046 |
|
4047 | Object.assign(values, include.association.through.scope);
|
4048 | if (instance[include.association.through.model.name]) {
|
4049 | for (const attr of Object.keys(include.association.through.model.rawAttributes)) {
|
4050 | if (include.association.through.model.rawAttributes[attr]._autoGenerated ||
|
4051 | attr === include.association.foreignKey ||
|
4052 | attr === include.association.otherKey ||
|
4053 | typeof instance[include.association.through.model.name][attr] === undefined) {
|
4054 | continue;
|
4055 | }
|
4056 | values[attr] = instance[include.association.through.model.name][attr];
|
4057 | }
|
4058 | }
|
4059 |
|
4060 | return include.association.throughModel.create(values, includeOptions);
|
4061 | });
|
4062 | }
|
4063 | instance.set(include.association.foreignKey, this.get(include.association.sourceKey || this.constructor.primaryKeyAttribute, { raw: true }), { raw: true });
|
4064 | Object.assign(instance, include.association.scope);
|
4065 | return instance.save(includeOptions);
|
4066 | });
|
4067 | });
|
4068 | })
|
4069 | .tap(result => {
|
4070 |
|
4071 | if (options.hooks) {
|
4072 | return this.constructor.runHooks(`after${hook}`, result, options);
|
4073 | }
|
4074 | })
|
4075 | .then(result => {
|
4076 | for (const field of options.fields) {
|
4077 | result._previousDataValues[field] = result.dataValues[field];
|
4078 | this.changed(field, false);
|
4079 | }
|
4080 | this.isNewRecord = false;
|
4081 | return result;
|
4082 | });
|
4083 | });
|
4084 | }
|
4085 |
|
4086 | |
4087 |
|
4088 |
|
4089 |
|
4090 |
|
4091 |
|
4092 |
|
4093 |
|
4094 |
|
4095 |
|
4096 |
|
4097 |
|
4098 |
|
4099 | reload(options) {
|
4100 | options = Utils.defaults({}, options, {
|
4101 | where: this.where(),
|
4102 | include: this._options.include || null
|
4103 | });
|
4104 |
|
4105 | return this.constructor.findOne(options)
|
4106 | .tap(reload => {
|
4107 | if (!reload) {
|
4108 | throw new sequelizeErrors.InstanceError(
|
4109 | 'Instance could not be reloaded because it does not exist anymore (find call returned null)'
|
4110 | );
|
4111 | }
|
4112 | })
|
4113 | .then(reload => {
|
4114 |
|
4115 | this._options = reload._options;
|
4116 |
|
4117 | this.set(reload.dataValues, {
|
4118 | raw: true,
|
4119 | reset: true && !options.attributes
|
4120 | });
|
4121 | return this;
|
4122 | });
|
4123 | }
|
4124 |
|
4125 | |
4126 |
|
4127 |
|
4128 |
|
4129 |
|
4130 |
|
4131 |
|
4132 |
|
4133 |
|
4134 |
|
4135 |
|
4136 |
|
4137 | validate(options) {
|
4138 | return new InstanceValidator(this, options).validate();
|
4139 | }
|
4140 |
|
4141 | |
4142 |
|
4143 |
|
4144 |
|
4145 |
|
4146 |
|
4147 |
|
4148 |
|
4149 |
|
4150 |
|
4151 |
|
4152 |
|
4153 |
|
4154 |
|
4155 | update(values, options) {
|
4156 |
|
4157 | values = _.omitBy(values, value => value === undefined);
|
4158 |
|
4159 | const changedBefore = this.changed() || [];
|
4160 |
|
4161 | options = options || {};
|
4162 | if (Array.isArray(options)) options = { fields: options };
|
4163 |
|
4164 | options = Utils.cloneDeep(options);
|
4165 | const setOptions = Utils.cloneDeep(options);
|
4166 | setOptions.attributes = options.fields;
|
4167 | this.set(values, setOptions);
|
4168 |
|
4169 |
|
4170 | const sideEffects = _.without(this.changed(), ...changedBefore);
|
4171 | const fields = _.union(Object.keys(values), sideEffects);
|
4172 |
|
4173 | if (!options.fields) {
|
4174 | options.fields = _.intersection(fields, this.changed());
|
4175 | options.defaultFields = options.fields;
|
4176 | }
|
4177 |
|
4178 | return this.save(options);
|
4179 | }
|
4180 |
|
4181 | |
4182 |
|
4183 |
|
4184 |
|
4185 |
|
4186 |
|
4187 |
|
4188 |
|
4189 |
|
4190 |
|
4191 |
|
4192 | destroy(options) {
|
4193 | options = Object.assign({
|
4194 | hooks: true,
|
4195 | force: false
|
4196 | }, options);
|
4197 |
|
4198 | return Promise.try(() => {
|
4199 |
|
4200 | if (options.hooks) {
|
4201 | return this.constructor.runHooks('beforeDestroy', this, options);
|
4202 | }
|
4203 | }).then(() => {
|
4204 | const where = this.where(true);
|
4205 |
|
4206 | if (this.constructor._timestampAttributes.deletedAt && options.force === false) {
|
4207 | const attributeName = this.constructor._timestampAttributes.deletedAt;
|
4208 | const attribute = this.constructor.rawAttributes[attributeName];
|
4209 | const defaultValue = Object.prototype.hasOwnProperty.call(attribute, 'defaultValue')
|
4210 | ? attribute.defaultValue
|
4211 | : null;
|
4212 | const currentValue = this.getDataValue(attributeName);
|
4213 | const undefinedOrNull = currentValue == null && defaultValue == null;
|
4214 | if (undefinedOrNull || _.isEqual(currentValue, defaultValue)) {
|
4215 |
|
4216 | this.setDataValue(attributeName, new Date());
|
4217 | }
|
4218 |
|
4219 | return this.save(_.defaults({ hooks: false }, options));
|
4220 | }
|
4221 | return this.constructor.QueryInterface.delete(this, this.constructor.getTableName(options), where, Object.assign({ type: QueryTypes.DELETE, limit: null }, options));
|
4222 | }).tap(() => {
|
4223 |
|
4224 | if (options.hooks) {
|
4225 | return this.constructor.runHooks('afterDestroy', this, options);
|
4226 | }
|
4227 | });
|
4228 | }
|
4229 |
|
4230 | |
4231 |
|
4232 |
|
4233 |
|
4234 |
|
4235 |
|
4236 |
|
4237 | isSoftDeleted() {
|
4238 | if (!this.constructor._timestampAttributes.deletedAt) {
|
4239 | throw new Error('Model is not paranoid');
|
4240 | }
|
4241 |
|
4242 | const deletedAtAttribute = this.constructor.rawAttributes[this.constructor._timestampAttributes.deletedAt];
|
4243 | const defaultValue = Object.prototype.hasOwnProperty.call(deletedAtAttribute, 'defaultValue') ? deletedAtAttribute.defaultValue : null;
|
4244 | const deletedAt = this.get(this.constructor._timestampAttributes.deletedAt) || null;
|
4245 | const isSet = deletedAt !== defaultValue;
|
4246 |
|
4247 | return isSet;
|
4248 | }
|
4249 |
|
4250 | |
4251 |
|
4252 |
|
4253 |
|
4254 |
|
4255 |
|
4256 |
|
4257 |
|
4258 |
|
4259 | restore(options) {
|
4260 | if (!this.constructor._timestampAttributes.deletedAt) throw new Error('Model is not paranoid');
|
4261 |
|
4262 | options = Object.assign({
|
4263 | hooks: true,
|
4264 | force: false
|
4265 | }, options);
|
4266 |
|
4267 | return Promise.try(() => {
|
4268 |
|
4269 | if (options.hooks) {
|
4270 | return this.constructor.runHooks('beforeRestore', this, options);
|
4271 | }
|
4272 | }).then(() => {
|
4273 | const deletedAtCol = this.constructor._timestampAttributes.deletedAt;
|
4274 | const deletedAtAttribute = this.constructor.rawAttributes[deletedAtCol];
|
4275 | const deletedAtDefaultValue = Object.prototype.hasOwnProperty.call(deletedAtAttribute, 'defaultValue') ? deletedAtAttribute.defaultValue : null;
|
4276 |
|
4277 | this.setDataValue(deletedAtCol, deletedAtDefaultValue);
|
4278 | return this.save(Object.assign({}, options, { hooks: false, omitNull: false }));
|
4279 | }).tap(() => {
|
4280 |
|
4281 | if (options.hooks) {
|
4282 | return this.constructor.runHooks('afterRestore', this, options);
|
4283 | }
|
4284 | });
|
4285 | }
|
4286 |
|
4287 | |
4288 |
|
4289 |
|
4290 |
|
4291 |
|
4292 |
|
4293 |
|
4294 |
|
4295 |
|
4296 |
|
4297 |
|
4298 |
|
4299 |
|
4300 |
|
4301 |
|
4302 |
|
4303 |
|
4304 |
|
4305 |
|
4306 |
|
4307 |
|
4308 |
|
4309 |
|
4310 |
|
4311 |
|
4312 |
|
4313 |
|
4314 |
|
4315 |
|
4316 |
|
4317 |
|
4318 | increment(fields, options) {
|
4319 | const identifier = this.where();
|
4320 |
|
4321 | options = Utils.cloneDeep(options);
|
4322 | options.where = Object.assign({}, options.where, identifier);
|
4323 | options.instance = this;
|
4324 |
|
4325 | return this.constructor.increment(fields, options).return(this);
|
4326 | }
|
4327 |
|
4328 | |
4329 |
|
4330 |
|
4331 |
|
4332 |
|
4333 |
|
4334 |
|
4335 |
|
4336 |
|
4337 |
|
4338 |
|
4339 |
|
4340 |
|
4341 |
|
4342 |
|
4343 |
|
4344 |
|
4345 |
|
4346 |
|
4347 |
|
4348 |
|
4349 |
|
4350 |
|
4351 |
|
4352 |
|
4353 |
|
4354 |
|
4355 |
|
4356 |
|
4357 | decrement(fields, options) {
|
4358 | options = _.defaults({ increment: false }, options, {
|
4359 | by: 1
|
4360 | });
|
4361 |
|
4362 | return this.increment(fields, options);
|
4363 | }
|
4364 |
|
4365 | |
4366 |
|
4367 |
|
4368 |
|
4369 |
|
4370 |
|
4371 |
|
4372 | equals(other) {
|
4373 | if (!other || !other.constructor) {
|
4374 | return false;
|
4375 | }
|
4376 |
|
4377 | if (!(other instanceof this.constructor)) {
|
4378 | return false;
|
4379 | }
|
4380 |
|
4381 | return this.constructor.primaryKeyAttributes.every(attribute => this.get(attribute, { raw: true }) === other.get(attribute, { raw: true }));
|
4382 | }
|
4383 |
|
4384 | |
4385 |
|
4386 |
|
4387 |
|
4388 |
|
4389 |
|
4390 |
|
4391 | equalsOneOf(others) {
|
4392 | return others.some(other => this.equals(other));
|
4393 | }
|
4394 |
|
4395 | setValidators(attribute, validators) {
|
4396 | this.validators[attribute] = validators;
|
4397 | }
|
4398 |
|
4399 | |
4400 |
|
4401 |
|
4402 |
|
4403 |
|
4404 |
|
4405 |
|
4406 |
|
4407 |
|
4408 |
|
4409 | toJSON() {
|
4410 | return _.cloneDeep(
|
4411 | this.get({
|
4412 | plain: true
|
4413 | })
|
4414 | );
|
4415 | }
|
4416 |
|
4417 | |
4418 |
|
4419 |
|
4420 |
|
4421 |
|
4422 |
|
4423 |
|
4424 |
|
4425 |
|
4426 |
|
4427 |
|
4428 |
|
4429 |
|
4430 |
|
4431 |
|
4432 |
|
4433 |
|
4434 |
|
4435 |
|
4436 |
|
4437 | static hasMany(target, options) {}
|
4438 |
|
4439 | |
4440 |
|
4441 |
|
4442 |
|
4443 |
|
4444 |
|
4445 |
|
4446 |
|
4447 |
|
4448 |
|
4449 |
|
4450 |
|
4451 |
|
4452 |
|
4453 |
|
4454 |
|
4455 |
|
4456 |
|
4457 |
|
4458 |
|
4459 |
|
4460 |
|
4461 |
|
4462 |
|
4463 |
|
4464 |
|
4465 |
|
4466 |
|
4467 |
|
4468 |
|
4469 |
|
4470 |
|
4471 |
|
4472 | static belongsToMany(target, options) {}
|
4473 |
|
4474 | |
4475 |
|
4476 |
|
4477 |
|
4478 |
|
4479 |
|
4480 |
|
4481 |
|
4482 |
|
4483 |
|
4484 |
|
4485 |
|
4486 |
|
4487 |
|
4488 |
|
4489 |
|
4490 |
|
4491 |
|
4492 |
|
4493 | static hasOne(target, options) {}
|
4494 |
|
4495 | |
4496 |
|
4497 |
|
4498 |
|
4499 |
|
4500 |
|
4501 |
|
4502 |
|
4503 |
|
4504 |
|
4505 |
|
4506 |
|
4507 |
|
4508 |
|
4509 |
|
4510 |
|
4511 |
|
4512 |
|
4513 | static belongsTo(target, options) {}
|
4514 | }
|
4515 |
|
4516 | Object.assign(Model, associationsMixin);
|
4517 | Hooks.applyTo(Model, true);
|
4518 |
|
4519 | module.exports = Model;
|