UNPKG

16.4 kBJavaScriptView Raw
1'use strict';
2
3const DataTypes = require('./data-types');
4const SqlString = require('./sql-string');
5const _ = require('lodash');
6const uuidv1 = require('uuid').v1;
7const uuidv4 = require('uuid').v4;
8const operators = require('./operators');
9const operatorsSet = new Set(Object.values(operators));
10
11let inflection = require('inflection');
12
13exports.classToInvokable = require('./utils/class-to-invokable').classToInvokable;
14exports.joinSQLFragments = require('./utils/join-sql-fragments').joinSQLFragments;
15
16function useInflection(_inflection) {
17 inflection = _inflection;
18}
19exports.useInflection = useInflection;
20
21function camelizeIf(str, condition) {
22 let result = str;
23
24 if (condition) {
25 result = camelize(str);
26 }
27
28 return result;
29}
30exports.camelizeIf = camelizeIf;
31
32function underscoredIf(str, condition) {
33 let result = str;
34
35 if (condition) {
36 result = underscore(str);
37 }
38
39 return result;
40}
41exports.underscoredIf = underscoredIf;
42
43function isPrimitive(val) {
44 const type = typeof val;
45 return type === 'string' || type === 'number' || type === 'boolean';
46}
47exports.isPrimitive = isPrimitive;
48
49// Same concept as _.merge, but don't overwrite properties that have already been assigned
50function mergeDefaults(a, b) {
51 return _.mergeWith(a, b, (objectValue, sourceValue) => {
52 // If it's an object, let _ handle it this time, we will be called again for each property
53 if (!_.isPlainObject(objectValue) && objectValue !== undefined) {
54 if (_.isFunction(objectValue) && _.isNative(objectValue)) {
55 return sourceValue || objectValue;
56 }
57 return objectValue;
58 }
59 });
60}
61exports.mergeDefaults = mergeDefaults;
62
63// An alternative to _.merge, which doesn't clone its arguments
64// Cloning is a bad idea because options arguments may contain references to sequelize
65// models - which again reference database libs which don't like to be cloned (in particular pg-native)
66function merge() {
67 const result = {};
68
69 for (const obj of arguments) {
70 _.forOwn(obj, (value, key) => {
71 if (value !== undefined) {
72 if (!result[key]) {
73 result[key] = value;
74 } else if (_.isPlainObject(value) && _.isPlainObject(result[key])) {
75 result[key] = merge(result[key], value);
76 } else if (Array.isArray(value) && Array.isArray(result[key])) {
77 result[key] = value.concat(result[key]);
78 } else {
79 result[key] = value;
80 }
81 }
82 });
83 }
84
85 return result;
86}
87exports.merge = merge;
88
89function spliceStr(str, index, count, add) {
90 return str.slice(0, index) + add + str.slice(index + count);
91}
92exports.spliceStr = spliceStr;
93
94function camelize(str) {
95 return str.trim().replace(/[-_\s]+(.)?/g, (match, c) => c.toUpperCase());
96}
97exports.camelize = camelize;
98
99function underscore(str) {
100 return inflection.underscore(str);
101}
102exports.underscore = underscore;
103
104function singularize(str) {
105 return inflection.singularize(str);
106}
107exports.singularize = singularize;
108
109function pluralize(str) {
110 return inflection.pluralize(str);
111}
112exports.pluralize = pluralize;
113
114function format(arr, dialect) {
115 const timeZone = null;
116 // Make a clone of the array beacuse format modifies the passed args
117 return SqlString.format(arr[0], arr.slice(1), timeZone, dialect);
118}
119exports.format = format;
120
121function formatNamedParameters(sql, parameters, dialect) {
122 const timeZone = null;
123 return SqlString.formatNamedParameters(sql, parameters, timeZone, dialect);
124}
125exports.formatNamedParameters = formatNamedParameters;
126
127function cloneDeep(obj, onlyPlain) {
128 obj = obj || {};
129 return _.cloneDeepWith(obj, elem => {
130 // Do not try to customize cloning of arrays or POJOs
131 if (Array.isArray(elem) || _.isPlainObject(elem)) {
132 return undefined;
133 }
134
135 // If we specified to clone only plain objects & arrays, we ignore everyhing else
136 // In any case, don't clone stuff that's an object, but not a plain one - fx example sequelize models and instances
137 if (onlyPlain || typeof elem === 'object') {
138 return elem;
139 }
140
141 // Preserve special data-types like `fn` across clones. _.get() is used for checking up the prototype chain
142 if (elem && typeof elem.clone === 'function') {
143 return elem.clone();
144 }
145 });
146}
147exports.cloneDeep = cloneDeep;
148
149/* Expand and normalize finder options */
150function mapFinderOptions(options, Model) {
151 if (options.attributes && Array.isArray(options.attributes)) {
152 options.attributes = Model._injectDependentVirtualAttributes(options.attributes);
153 options.attributes = options.attributes.filter(v => !Model._virtualAttributes.has(v));
154 }
155
156 mapOptionFieldNames(options, Model);
157
158 return options;
159}
160exports.mapFinderOptions = mapFinderOptions;
161
162/* Used to map field names in attributes and where conditions */
163function mapOptionFieldNames(options, Model) {
164 if (Array.isArray(options.attributes)) {
165 options.attributes = options.attributes.map(attr => {
166 // Object lookups will force any variable to strings, we don't want that for special objects etc
167 if (typeof attr !== 'string') return attr;
168 // Map attributes to aliased syntax attributes
169 if (Model.rawAttributes[attr] && attr !== Model.rawAttributes[attr].field) {
170 return [Model.rawAttributes[attr].field, attr];
171 }
172 return attr;
173 });
174 }
175
176 if (options.where && _.isPlainObject(options.where)) {
177 options.where = mapWhereFieldNames(options.where, Model);
178 }
179
180 return options;
181}
182exports.mapOptionFieldNames = mapOptionFieldNames;
183
184function mapWhereFieldNames(attributes, Model) {
185 if (attributes) {
186 getComplexKeys(attributes).forEach(attribute => {
187 const rawAttribute = Model.rawAttributes[attribute];
188
189 if (rawAttribute && rawAttribute.field !== rawAttribute.fieldName) {
190 attributes[rawAttribute.field] = attributes[attribute];
191 delete attributes[attribute];
192 }
193
194 if (_.isPlainObject(attributes[attribute])
195 && !(rawAttribute && (
196 rawAttribute.type instanceof DataTypes.HSTORE
197 || rawAttribute.type instanceof DataTypes.JSON))) { // Prevent renaming of HSTORE & JSON fields
198 attributes[attribute] = mapOptionFieldNames({
199 where: attributes[attribute]
200 }, Model).where;
201 }
202
203 if (Array.isArray(attributes[attribute])) {
204 attributes[attribute].forEach((where, index) => {
205 if (_.isPlainObject(where)) {
206 attributes[attribute][index] = mapWhereFieldNames(where, Model);
207 }
208 });
209 }
210
211 });
212 }
213
214 return attributes;
215}
216exports.mapWhereFieldNames = mapWhereFieldNames;
217
218/* Used to map field names in values */
219function mapValueFieldNames(dataValues, fields, Model) {
220 const values = {};
221
222 for (const attr of fields) {
223 if (dataValues[attr] !== undefined && !Model._virtualAttributes.has(attr)) {
224 // Field name mapping
225 if (Model.rawAttributes[attr] && Model.rawAttributes[attr].field && Model.rawAttributes[attr].field !== attr) {
226 values[Model.rawAttributes[attr].field] = dataValues[attr];
227 } else {
228 values[attr] = dataValues[attr];
229 }
230 }
231 }
232
233 return values;
234}
235exports.mapValueFieldNames = mapValueFieldNames;
236
237function isColString(value) {
238 return typeof value === 'string' && value[0] === '$' && value[value.length - 1] === '$';
239}
240exports.isColString = isColString;
241
242function canTreatArrayAsAnd(arr) {
243 return arr.some(arg => _.isPlainObject(arg) || arg instanceof Where);
244}
245exports.canTreatArrayAsAnd = canTreatArrayAsAnd;
246
247function combineTableNames(tableName1, tableName2) {
248 return tableName1.toLowerCase() < tableName2.toLowerCase() ? tableName1 + tableName2 : tableName2 + tableName1;
249}
250exports.combineTableNames = combineTableNames;
251
252function toDefaultValue(value, dialect) {
253 if (typeof value === 'function') {
254 const tmp = value();
255 if (tmp instanceof DataTypes.ABSTRACT) {
256 return tmp.toSql();
257 }
258 return tmp;
259 }
260 if (value instanceof DataTypes.UUIDV1) {
261 return uuidv1();
262 }
263 if (value instanceof DataTypes.UUIDV4) {
264 return uuidv4();
265 }
266 if (value instanceof DataTypes.NOW) {
267 return now(dialect);
268 }
269 if (Array.isArray(value)) {
270 return value.slice();
271 }
272 if (_.isPlainObject(value)) {
273 return { ...value };
274 }
275 return value;
276}
277exports.toDefaultValue = toDefaultValue;
278
279/**
280 * Determine if the default value provided exists and can be described
281 * in a db schema using the DEFAULT directive.
282 *
283 * @param {*} value Any default value.
284 * @returns {boolean} yes / no.
285 * @private
286 */
287function defaultValueSchemable(value) {
288 if (value === undefined) { return false; }
289
290 // TODO this will be schemable when all supported db
291 // have been normalized for this case
292 if (value instanceof DataTypes.NOW) { return false; }
293
294 if (value instanceof DataTypes.UUIDV1 || value instanceof DataTypes.UUIDV4) { return false; }
295
296 return typeof value !== 'function';
297}
298exports.defaultValueSchemable = defaultValueSchemable;
299
300function removeNullValuesFromHash(hash, omitNull, options) {
301 let result = hash;
302
303 options = options || {};
304 options.allowNull = options.allowNull || [];
305
306 if (omitNull) {
307 const _hash = {};
308
309 _.forIn(hash, (val, key) => {
310 if (options.allowNull.includes(key) || key.endsWith('Id') || val !== null && val !== undefined) {
311 _hash[key] = val;
312 }
313 });
314
315 result = _hash;
316 }
317
318 return result;
319}
320exports.removeNullValuesFromHash = removeNullValuesFromHash;
321
322const dialects = new Set(['mariadb', 'mysql', 'postgres', 'sqlite', 'mssql']);
323
324function now(dialect) {
325 const d = new Date();
326 if (!dialects.has(dialect)) {
327 d.setMilliseconds(0);
328 }
329 return d;
330}
331exports.now = now;
332
333// Note: Use the `quoteIdentifier()` and `escape()` methods on the
334// `QueryInterface` instead for more portable code.
335
336const TICK_CHAR = '`';
337exports.TICK_CHAR = TICK_CHAR;
338
339function addTicks(s, tickChar) {
340 tickChar = tickChar || TICK_CHAR;
341 return tickChar + removeTicks(s, tickChar) + tickChar;
342}
343exports.addTicks = addTicks;
344
345function removeTicks(s, tickChar) {
346 tickChar = tickChar || TICK_CHAR;
347 return s.replace(new RegExp(tickChar, 'g'), '');
348}
349exports.removeTicks = removeTicks;
350
351/**
352 * Receives a tree-like object and returns a plain object which depth is 1.
353 *
354 * - Input:
355 *
356 * {
357 * name: 'John',
358 * address: {
359 * street: 'Fake St. 123',
360 * coordinates: {
361 * longitude: 55.6779627,
362 * latitude: 12.5964313
363 * }
364 * }
365 * }
366 *
367 * - Output:
368 *
369 * {
370 * name: 'John',
371 * address.street: 'Fake St. 123',
372 * address.coordinates.latitude: 55.6779627,
373 * address.coordinates.longitude: 12.5964313
374 * }
375 *
376 * @param {object} value an Object
377 * @returns {object} a flattened object
378 * @private
379 */
380function flattenObjectDeep(value) {
381 if (!_.isPlainObject(value)) return value;
382 const flattenedObj = {};
383
384 function flattenObject(obj, subPath) {
385 Object.keys(obj).forEach(key => {
386 const pathToProperty = subPath ? `${subPath}.${key}` : key;
387 if (typeof obj[key] === 'object' && obj[key] !== null) {
388 flattenObject(obj[key], pathToProperty);
389 } else {
390 flattenedObj[pathToProperty] = _.get(obj, key);
391 }
392 });
393 return flattenedObj;
394 }
395
396 return flattenObject(value, undefined);
397}
398exports.flattenObjectDeep = flattenObjectDeep;
399
400/**
401 * Utility functions for representing SQL functions, and columns that should be escaped.
402 * Please do not use these functions directly, use Sequelize.fn and Sequelize.col instead.
403 *
404 * @private
405 */
406class SequelizeMethod {}
407exports.SequelizeMethod = SequelizeMethod;
408
409class Fn extends SequelizeMethod {
410 constructor(fn, args) {
411 super();
412 this.fn = fn;
413 this.args = args;
414 }
415 clone() {
416 return new Fn(this.fn, this.args);
417 }
418}
419exports.Fn = Fn;
420
421class Col extends SequelizeMethod {
422 constructor(col, ...args) {
423 super();
424 if (args.length > 0) {
425 col = args;
426 }
427 this.col = col;
428 }
429}
430exports.Col = Col;
431
432class Cast extends SequelizeMethod {
433 constructor(val, type, json) {
434 super();
435 this.val = val;
436 this.type = (type || '').trim();
437 this.json = json || false;
438 }
439}
440exports.Cast = Cast;
441
442class Literal extends SequelizeMethod {
443 constructor(val) {
444 super();
445 this.val = val;
446 }
447}
448exports.Literal = Literal;
449
450class Json extends SequelizeMethod {
451 constructor(conditionsOrPath, value) {
452 super();
453 if (_.isObject(conditionsOrPath)) {
454 this.conditions = conditionsOrPath;
455 } else {
456 this.path = conditionsOrPath;
457 if (value) {
458 this.value = value;
459 }
460 }
461 }
462}
463exports.Json = Json;
464
465class Where extends SequelizeMethod {
466 constructor(attribute, comparator, logic) {
467 super();
468 if (logic === undefined) {
469 logic = comparator;
470 comparator = '=';
471 }
472
473 this.attribute = attribute;
474 this.comparator = comparator;
475 this.logic = logic;
476 }
477}
478exports.Where = Where;
479
480//Collection of helper methods to make it easier to work with symbol operators
481
482/**
483 * getOperators
484 *
485 * @param {object} obj
486 * @returns {Array<symbol>} All operators properties of obj
487 * @private
488 */
489function getOperators(obj) {
490 return Object.getOwnPropertySymbols(obj).filter(s => operatorsSet.has(s));
491}
492exports.getOperators = getOperators;
493
494/**
495 * getComplexKeys
496 *
497 * @param {object} obj
498 * @returns {Array<string|symbol>} All keys including operators
499 * @private
500 */
501function getComplexKeys(obj) {
502 return getOperators(obj).concat(Object.keys(obj));
503}
504exports.getComplexKeys = getComplexKeys;
505
506/**
507 * getComplexSize
508 *
509 * @param {object|Array} obj
510 * @returns {number} Length of object properties including operators if obj is array returns its length
511 * @private
512 */
513function getComplexSize(obj) {
514 return Array.isArray(obj) ? obj.length : getComplexKeys(obj).length;
515}
516exports.getComplexSize = getComplexSize;
517
518/**
519 * Returns true if a where clause is empty, even with Symbols
520 *
521 * @param {object} obj
522 * @returns {boolean}
523 * @private
524 */
525function isWhereEmpty(obj) {
526 return !!obj && _.isEmpty(obj) && getOperators(obj).length === 0;
527}
528exports.isWhereEmpty = isWhereEmpty;
529
530/**
531 * Returns ENUM name by joining table and column name
532 *
533 * @param {string} tableName
534 * @param {string} columnName
535 * @returns {string}
536 * @private
537 */
538function generateEnumName(tableName, columnName) {
539 return `enum_${tableName}_${columnName}`;
540}
541exports.generateEnumName = generateEnumName;
542
543/**
544 * Returns an new Object which keys are camelized
545 *
546 * @param {object} obj
547 * @returns {string}
548 * @private
549 */
550function camelizeObjectKeys(obj) {
551 const newObj = new Object();
552 Object.keys(obj).forEach(key => {
553 newObj[camelize(key)] = obj[key];
554 });
555 return newObj;
556}
557exports.camelizeObjectKeys = camelizeObjectKeys;
558
559/**
560 * Assigns own and inherited enumerable string and symbol keyed properties of source
561 * objects to the destination object.
562 *
563 * https://lodash.com/docs/4.17.4#defaults
564 *
565 * **Note:** This method mutates `object`.
566 *
567 * @param {object} object The destination object.
568 * @param {...object} [sources] The source objects.
569 * @returns {object} Returns `object`.
570 * @private
571 */
572function defaults(object, ...sources) {
573 object = Object(object);
574
575 sources.forEach(source => {
576 if (source) {
577 source = Object(source);
578
579 getComplexKeys(source).forEach(key => {
580 const value = object[key];
581 if (
582 value === undefined ||
583 _.eq(value, Object.prototype[key]) &&
584 !Object.prototype.hasOwnProperty.call(object, key)
585
586 ) {
587 object[key] = source[key];
588 }
589 });
590 }
591 });
592
593 return object;
594}
595exports.defaults = defaults;
596
597/**
598 *
599 * @param {object} index
600 * @param {Array} index.fields
601 * @param {string} [index.name]
602 * @param {string|object} tableName
603 *
604 * @returns {object}
605 * @private
606 */
607function nameIndex(index, tableName) {
608 if (tableName.tableName) tableName = tableName.tableName;
609
610 if (!Object.prototype.hasOwnProperty.call(index, 'name')) {
611 const fields = index.fields.map(
612 field => typeof field === 'string' ? field : field.name || field.attribute
613 );
614 index.name = underscore(`${tableName}_${fields.join('_')}`);
615 }
616
617 return index;
618}
619exports.nameIndex = nameIndex;
620
621/**
622 * Checks if 2 arrays intersect.
623 *
624 * @param {Array} arr1
625 * @param {Array} arr2
626 * @private
627 */
628function intersects(arr1, arr2) {
629 return arr1.some(v => arr2.includes(v));
630}
631exports.intersects = intersects;