UNPKG

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