mythix-orm 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/.eslintrc.js +94 -0
  2. package/.vscode/launch.json +34 -0
  3. package/.vscode/settings.json +10 -0
  4. package/LICENSE +21 -0
  5. package/README.md +15 -0
  6. package/lib/connection/connection-base.js +877 -0
  7. package/lib/connection/index.js +11 -0
  8. package/lib/connection/literals/average-literal.js +14 -0
  9. package/lib/connection/literals/count-literal.js +18 -0
  10. package/lib/connection/literals/distinct-literal.js +14 -0
  11. package/lib/connection/literals/index.js +23 -0
  12. package/lib/connection/literals/literal-base.js +62 -0
  13. package/lib/connection/literals/literal-field-base.js +45 -0
  14. package/lib/connection/literals/literal.js +11 -0
  15. package/lib/connection/literals/max-literal.js +14 -0
  16. package/lib/connection/literals/min-literal.js +14 -0
  17. package/lib/connection/literals/sum-literal.js +14 -0
  18. package/lib/connection/query-generator-base.js +864 -0
  19. package/lib/errors/base-error.js +14 -0
  20. package/lib/errors/connection/access-denied-error.js +13 -0
  21. package/lib/errors/connection/connection-acquire-timeout-error.js +13 -0
  22. package/lib/errors/connection/connection-refused-error.js +13 -0
  23. package/lib/errors/connection/connection-timed-out-error.js +13 -0
  24. package/lib/errors/connection/host-not-found-error.js +13 -0
  25. package/lib/errors/connection/host-not-reachable-error.js +13 -0
  26. package/lib/errors/connection/index.js +19 -0
  27. package/lib/errors/connection/invalid-connection-error.js +13 -0
  28. package/lib/errors/connection-base-error.js +13 -0
  29. package/lib/errors/database/exclusion-constraint-error.js +13 -0
  30. package/lib/errors/database/foreign-key-constraint-error.js +13 -0
  31. package/lib/errors/database/index.js +13 -0
  32. package/lib/errors/database/timeout-error.js +13 -0
  33. package/lib/errors/database/unknown-constraint-error.js +13 -0
  34. package/lib/errors/database-base-error.js +17 -0
  35. package/lib/errors/index.js +44 -0
  36. package/lib/field.js +18 -0
  37. package/lib/index.js +43 -0
  38. package/lib/model.js +1374 -0
  39. package/lib/proxy-class/index.js +3 -0
  40. package/lib/proxy-class/proxy-class.js +269 -0
  41. package/lib/query-engine/field-scope.js +99 -0
  42. package/lib/query-engine/index.js +13 -0
  43. package/lib/query-engine/model-scope.js +198 -0
  44. package/lib/query-engine/query-engine-base.js +325 -0
  45. package/lib/query-engine/query-engine.js +212 -0
  46. package/lib/types/concrete/bigint-type.js +62 -0
  47. package/lib/types/concrete/blob-type.js +46 -0
  48. package/lib/types/concrete/boolean-type.js +41 -0
  49. package/lib/types/concrete/char-type.js +39 -0
  50. package/lib/types/concrete/date-type.js +87 -0
  51. package/lib/types/concrete/datetime-type.js +92 -0
  52. package/lib/types/concrete/float-type.js +47 -0
  53. package/lib/types/concrete/foreign-key-type.js +208 -0
  54. package/lib/types/concrete/index.js +53 -0
  55. package/lib/types/concrete/integer-type.js +51 -0
  56. package/lib/types/concrete/string-type.js +44 -0
  57. package/lib/types/concrete/text-type.js +44 -0
  58. package/lib/types/concrete/uuid-base.js +77 -0
  59. package/lib/types/concrete/uuid-v1-type.js +99 -0
  60. package/lib/types/concrete/uuid-v3-type.js +108 -0
  61. package/lib/types/concrete/uuid-v4-type.js +90 -0
  62. package/lib/types/concrete/uuid-v5-type.js +108 -0
  63. package/lib/types/concrete/xid-type.js +58 -0
  64. package/lib/types/helpers/default-helpers.js +127 -0
  65. package/lib/types/helpers/index.js +35 -0
  66. package/lib/types/index.js +94 -0
  67. package/lib/types/type.js +244 -0
  68. package/lib/types/virtual/index.js +14 -0
  69. package/lib/types/virtual/model-type.js +141 -0
  70. package/lib/types/virtual/models-type.js +264 -0
  71. package/lib/types/virtual/relational-type-base.js +323 -0
  72. package/lib/utils/index.js +65 -0
  73. package/lib/utils/misc-utils.js +73 -0
  74. package/lib/utils/model-utils.js +704 -0
  75. package/lib/utils/query-utils.js +186 -0
  76. package/package.json +63 -0
  77. package/playground/test01.js +15 -0
  78. package/spec/connection/connection-base-spec.js +126 -0
  79. package/spec/connection/literals/average-literal-spec.js +45 -0
  80. package/spec/connection/literals/count-literal-spec.js +42 -0
  81. package/spec/connection/literals/distinct-literal-spec.js +42 -0
  82. package/spec/connection/literals/literal-spec.js +26 -0
  83. package/spec/connection/literals/max-literal-spec.js +42 -0
  84. package/spec/connection/literals/min-literal-spec.js +42 -0
  85. package/spec/connection/literals/sum-literal-spec.js +42 -0
  86. package/spec/helpers/default-helpers-spec.js +108 -0
  87. package/spec/model-spec.js +736 -0
  88. package/spec/proxy-class/proxy-class-spec.js +173 -0
  89. package/spec/query-engine/__snapshots__/QueryEngine-operations-query_operations_and_chaining-can_chain_query_conditions-001.snapshot +94 -0
  90. package/spec/query-engine/__snapshots__/QueryEngine-operations-query_operations_and_chaining-can_construct_a_query_context_with_a_model_call-001.snapshot +35 -0
  91. package/spec/query-engine/__snapshots__/QueryEngine-operations-query_operations_and_chaining-can_construct_a_query_context_with_a_model_sub-001.snapshot +35 -0
  92. package/spec/query-engine/__snapshots__/QueryEngine-operations-query_operations_and_chaining-can_set_a_default_scope_on_a_model-001.snapshot +57 -0
  93. package/spec/query-engine/__snapshots__/QueryEngine-operations-query_operations_and_chaining-can_unscope_default_scope_on_a_model-001.snapshot +35 -0
  94. package/spec/query-engine/query-engine-spec.js +99 -0
  95. package/spec/support/jasmine.json +13 -0
  96. package/spec/support/models/blob-test-model.js +19 -0
  97. package/spec/support/models/extended-user-model.js +38 -0
  98. package/spec/support/models/index.js +27 -0
  99. package/spec/support/models/number-model.js +24 -0
  100. package/spec/support/models/role-model.js +26 -0
  101. package/spec/support/models/role-thing-model.js +41 -0
  102. package/spec/support/models/scoped-user-model.js +13 -0
  103. package/spec/support/models/time-model.js +36 -0
  104. package/spec/support/models/user-model.js +70 -0
  105. package/spec/support/models/user-role-model.js +36 -0
  106. package/spec/support/models/user-thing-model.js +46 -0
  107. package/spec/support/models/validation-test-model.js +40 -0
  108. package/spec/support/snapshots.js +293 -0
  109. package/spec/support/test-helpers.js +13 -0
  110. package/spec/types/concrete/bigint-type-spec.js +84 -0
  111. package/spec/types/concrete/boolean-type-spec.js +83 -0
  112. package/spec/types/concrete/date-type-spec.js +85 -0
  113. package/spec/types/concrete/datetime-type-spec.js +87 -0
  114. package/spec/types/concrete/float-type-spec.js +71 -0
  115. package/spec/types/concrete/foreign-key-type-spec.js +64 -0
  116. package/spec/types/concrete/integer-type-spec.js +71 -0
  117. package/spec/types/concrete/string-type-spec.js +91 -0
  118. package/spec/types/concrete/uuid-v1-type-spec.js +73 -0
  119. package/spec/types/concrete/uuid-v4-type-spec.js +65 -0
  120. package/spec/types/type-spec.js +101 -0
  121. package/spec/types/virtual/model-types-spec.js +401 -0
  122. package/spec/utils/misc-utils-spec.js +61 -0
  123. package/spec/utils/model-utils-spec.js +55 -0
  124. package/spec/utils/query-utils-spec.js +105 -0
@@ -0,0 +1,877 @@
1
+ 'use strict';
2
+
3
+ const moment = require('moment');
4
+ const EventEmitter = require('events');
5
+ const Nife = require('nife');
6
+ const SqlString = require('sqlstring');
7
+ const { QueryEngine } = require('../query-engine');
8
+ const Utils = require('../utils');
9
+ const ModelBase = require('../model');
10
+ const Literals = require('./literals');
11
+ const QueryGeneratorBase = require('./query-generator-base');
12
+ const Types = require('../types');
13
+
14
+ const LiteralBase = Literals.LiteralBase;
15
+
16
+ class ConnectionBase extends EventEmitter {
17
+ static dialect = 'none';
18
+
19
+ static _isMythixConnection = true;
20
+
21
+ static isConnectionClass(value) {
22
+ if (!value)
23
+ return false;
24
+
25
+ if (value.prototype instanceof ConnectionBase)
26
+ return true;
27
+
28
+ if (value._isMythixConnection)
29
+ return true;
30
+
31
+ return false;
32
+ }
33
+
34
+ static isConnection(value) {
35
+ if (!value)
36
+ return false;
37
+
38
+ if (value instanceof ConnectionBase)
39
+ return true;
40
+
41
+ if (value.constructor && value.constructor._isMythixConnection)
42
+ return true;
43
+
44
+ return false;
45
+ }
46
+
47
+ constructor(_options) {
48
+ super();
49
+
50
+ this.setMaxListeners(0);
51
+
52
+ let options = Object.assign({
53
+ QueryEngine,
54
+ }, _options || {});
55
+
56
+ if (!options.queryGenerator)
57
+ options.queryGenerator = new QueryGeneratorBase(this);
58
+
59
+ Object.defineProperties(this, {
60
+ 'dialect': {
61
+ enumberable: false,
62
+ configurable: true,
63
+ get: () => {
64
+ return this.constructor.dialect;
65
+ },
66
+ set: () => {
67
+ },
68
+ },
69
+ '_models': {
70
+ writable: true,
71
+ enumberable: false,
72
+ configurable: true,
73
+ value: {},
74
+ },
75
+ '_options': {
76
+ writable: true,
77
+ enumberable: false,
78
+ configurable: true,
79
+ value: options,
80
+ },
81
+ '_modelCache': {
82
+ writable: true,
83
+ enumberable: false,
84
+ configurable: true,
85
+ value: new Map(),
86
+ },
87
+ 'queryGenerator': {
88
+ writable: true,
89
+ enumberable: false,
90
+ configurable: true,
91
+ value: options.queryGenerator,
92
+ },
93
+ });
94
+
95
+ this.registerModels(options.models);
96
+ }
97
+
98
+ _getFromModelCache(Model, key, defaultValue) {
99
+ let cache = this._modelCache.get(Model);
100
+ if (!cache)
101
+ return defaultValue;
102
+
103
+ let value = cache.get(key);
104
+ if (value == null)
105
+ return defaultValue;
106
+
107
+ return value;
108
+ }
109
+
110
+ _setToModelCache(Model, key, value) {
111
+ let cache = this._modelCache.get(Model);
112
+ if (!cache) {
113
+ cache = new Map();
114
+ this._modelCache.set(Model, cache);
115
+ }
116
+
117
+ cache.set(key, value);
118
+
119
+ return value;
120
+ }
121
+
122
+ getOptions() {
123
+ return this._options;
124
+ }
125
+
126
+ isStarted() {
127
+ return true;
128
+ }
129
+
130
+ toQueryEngine(_queryEngine) {
131
+ let queryEngine = _queryEngine;
132
+ if (!queryEngine)
133
+ return;
134
+
135
+ if (!QueryEngine.isQuery(queryEngine)) {
136
+ if (Object.prototype.hasOwnProperty.call(queryEngine, 'where'))
137
+ queryEngine = queryEngine.where(this);
138
+ else
139
+ queryEngine = undefined;
140
+ }
141
+
142
+ return queryEngine;
143
+ }
144
+
145
+ registerModel(_Model) {
146
+ let Model = _Model.bindConnection(this);
147
+
148
+ this._models[Model.getModelName()] = Model;
149
+
150
+ return Model;
151
+ }
152
+
153
+ registerModels(models) {
154
+ if (!models)
155
+ return;
156
+
157
+ let keys = Object.keys(models);
158
+
159
+ for (let i = 0, il = keys.length; i < il; i++) {
160
+ let key = keys[i];
161
+ let Model = models[key];
162
+
163
+ this.registerModel(Model);
164
+ }
165
+
166
+ return this._models;
167
+ }
168
+
169
+ findModelField(finder) {
170
+ let modelMap = this.getModels();
171
+ let modelNames = Object.keys(modelMap);
172
+ let results = [];
173
+
174
+ for (let i = 0, il = modelNames.length; i < il; i++) {
175
+ let modelName = modelNames[i];
176
+ let Model = modelMap[modelName];
177
+
178
+ results = results.concat(Model.iterateFields(finder));
179
+ }
180
+
181
+ return results;
182
+ }
183
+
184
+ parseQualifiedName(str) {
185
+ return Utils.parseQualifiedName(str);
186
+ }
187
+
188
+ getModels() {
189
+ return this._models;
190
+ }
191
+
192
+ getModel(modelName) {
193
+ if (typeof modelName === 'symbol')
194
+ return;
195
+
196
+ let def = this.parseQualifiedName(modelName);
197
+ return this._models[def.modelName];
198
+ }
199
+
200
+ getField(fieldName, modelName) {
201
+ let def = this.parseQualifiedName(fieldName);
202
+ if (def.modelName == null)
203
+ def.modelName = modelName;
204
+
205
+ let Model = this.getModel(def.modelName);
206
+ if (!Model)
207
+ return;
208
+
209
+ return Model.getField(def.fieldNames[0]);
210
+ }
211
+
212
+ getQueryEngineClass() {
213
+ let options = this.getOptions();
214
+ return options.QueryEngine;
215
+ }
216
+
217
+ getQueryGenerator() {
218
+ return this.queryGenerator;
219
+ }
220
+
221
+ setQueryGenerator(queryGenerator) {
222
+ this.queryGenerator = queryGenerator;
223
+ }
224
+
225
+ _escape(value) {
226
+ return SqlString.escape(value);
227
+ }
228
+
229
+ escape(field, _value) {
230
+ var value = _value;
231
+ if (value instanceof Literals.LiteralBase)
232
+ return value.toString(this);
233
+
234
+ value = field.type.serialize(value, this);
235
+
236
+ if (value === true) {
237
+ return 'TRUE';
238
+ } else if (value === false) {
239
+ return 'FALSE';
240
+ } else if (typeof value === 'bigint') {
241
+ return value.toString();
242
+ } else if (Array.isArray(value)) {
243
+ let arrayValue = this.prepareArrayValuesForSQL(value);
244
+ if (Nife.isEmpty(arrayValue))
245
+ return '';
246
+
247
+ return `(${arrayValue.map((item) => this.escape(field, item)).join(',')})`;
248
+ }
249
+
250
+ return this._escape(value);
251
+ }
252
+
253
+ _escapeID(value) {
254
+ let parts = value.replace(/['"`]/g, '').split(/\.+/g);
255
+ return parts.map((part) => SqlString.escapeId(part).replace(/^`/, '"').replace(/`$/, '"')).join('.');
256
+ }
257
+
258
+ escapeID(value) {
259
+ if (value instanceof Literals.LiteralBase)
260
+ return value.toString(this.connection);
261
+
262
+ return this._escapeID(value);
263
+ }
264
+
265
+ _averageLiteralToString(literal) {
266
+ if (!literal || !(literal instanceof LiteralBase))
267
+ return;
268
+
269
+ let queryGenerator = this.getQueryGenerator();
270
+ if (!queryGenerator)
271
+ return;
272
+
273
+ return queryGenerator._averageLiteralToString(literal);
274
+ }
275
+
276
+ _countLiteralToString(literal) {
277
+ if (!literal || !(literal instanceof LiteralBase))
278
+ return;
279
+
280
+ let queryGenerator = this.getQueryGenerator();
281
+ if (!queryGenerator)
282
+ return;
283
+
284
+ return queryGenerator._countLiteralToString(literal);
285
+ }
286
+
287
+ _distinctLiteralToString(literal) {
288
+ if (!literal || !(literal instanceof LiteralBase))
289
+ return;
290
+
291
+ let queryGenerator = this.getQueryGenerator();
292
+ if (!queryGenerator)
293
+ return;
294
+
295
+ return queryGenerator._distinctLiteralToString(literal);
296
+ }
297
+
298
+ _maxLiteralToString(literal) {
299
+ if (!literal || !(literal instanceof LiteralBase))
300
+ return;
301
+
302
+ let queryGenerator = this.getQueryGenerator();
303
+ if (!queryGenerator)
304
+ return;
305
+
306
+ return queryGenerator._maxLiteralToString(literal);
307
+ }
308
+
309
+ _minLiteralToString(literal) {
310
+ if (!literal || !(literal instanceof LiteralBase))
311
+ return;
312
+
313
+ let queryGenerator = this.getQueryGenerator();
314
+ if (!queryGenerator)
315
+ return;
316
+
317
+ return queryGenerator._minLiteralToString(literal);
318
+ }
319
+
320
+ _sumLiteralToString(literal) {
321
+ if (!literal || !(literal instanceof LiteralBase))
322
+ return;
323
+
324
+ let queryGenerator = this.getQueryGenerator();
325
+ if (!queryGenerator)
326
+ return;
327
+
328
+ return queryGenerator._sumLiteralToString(literal);
329
+ }
330
+
331
+ literalToString(literal) {
332
+ if (literal instanceof Literals.AverageLiteral)
333
+ return this._averageLiteralToString(literal);
334
+ else if (literal instanceof Literals.CountLiteral)
335
+ return this._countLiteralToString(literal);
336
+ else if (literal instanceof Literals.DistinctLiteral)
337
+ return this._distinctLiteralToString(literal);
338
+ else if (literal instanceof Literals.MaxLiteral)
339
+ return this._maxLiteralToString(literal);
340
+ else if (literal instanceof Literals.MinLiteral)
341
+ return this._minLiteralToString(literal);
342
+ else if (literal instanceof Literals.SumLiteral)
343
+ return this._sumLiteralToString(literal);
344
+ else if (literal instanceof Literals.Literal)
345
+ return literal.toString(this);
346
+
347
+ throw new Error(`${this.constructor.name}::literalToString: Unsupported literal ${literal}.`);
348
+ }
349
+
350
+ // eslint-disable-next-line no-unused-vars
351
+ _bigintTypeToString(type) {
352
+ return 'BIGINT';
353
+ }
354
+
355
+ // eslint-disable-next-line no-unused-vars
356
+ _blobTypeToString(type) {
357
+ return 'BLOB';
358
+ }
359
+
360
+ // eslint-disable-next-line no-unused-vars
361
+ _booleanTypeToString(type) {
362
+ return 'BOOLEAN';
363
+ }
364
+
365
+ // eslint-disable-next-line no-unused-vars
366
+ _charTypeToString(type) {
367
+ return 'CHAR';
368
+ }
369
+
370
+ // eslint-disable-next-line no-unused-vars
371
+ _dateTypeToString(type) {
372
+ return 'DATE';
373
+ }
374
+
375
+ // eslint-disable-next-line no-unused-vars
376
+ _datetimeTypeToString(type) {
377
+ return 'DATETIME';
378
+ }
379
+
380
+ // eslint-disable-next-line no-unused-vars
381
+ _floatTypeToString(type) {
382
+ return 'FLOAT';
383
+ }
384
+
385
+ // eslint-disable-next-line no-unused-vars
386
+ _integerTypeToString(type) {
387
+ return 'INTEGER';
388
+ }
389
+
390
+ // eslint-disable-next-line no-unused-vars
391
+ _stringTypeToString(type) {
392
+ return `VARCHAR(${type.length})`;
393
+ }
394
+
395
+ // eslint-disable-next-line no-unused-vars
396
+ _textTypeToString(type) {
397
+ return 'TEXT';
398
+ }
399
+
400
+ // eslint-disable-next-line no-unused-vars
401
+ _uuidV1TypeToString(type) {
402
+ return `VARCHAR(${type.getTotalLength()})`;
403
+ }
404
+
405
+ // eslint-disable-next-line no-unused-vars
406
+ _uuidV3TypeToString(type) {
407
+ return `VARCHAR(${type.getTotalLength()})`;
408
+ }
409
+
410
+ // eslint-disable-next-line no-unused-vars
411
+ _uuidV4TypeToString(type) {
412
+ return `VARCHAR(${type.getTotalLength()})`;
413
+ }
414
+
415
+ // eslint-disable-next-line no-unused-vars
416
+ _uuidV5TypeToString(type) {
417
+ return `VARCHAR(${type.getTotalLength()})`;
418
+ }
419
+
420
+ // eslint-disable-next-line no-unused-vars
421
+ _xidTypeToString(type) {
422
+ return `VARCHAR(${type.getTotalLength()})`;
423
+ }
424
+
425
+ typeToString(type, options) {
426
+ if (type instanceof Types.BigIntType)
427
+ return this._bigintTypeToString(type, options);
428
+ else if (type instanceof Types.BlobType)
429
+ return this._blobTypeToString(type, options);
430
+ else if (type instanceof Types.BooleanType)
431
+ return this._booleanTypeToString(type, options);
432
+ else if (type instanceof Types.CharType)
433
+ return this._charTypeToString(type, options);
434
+ else if (type instanceof Types.DateType)
435
+ return this._dateTypeToString(type, options);
436
+ else if (type instanceof Types.DateTimeType)
437
+ return this._datetimeTypeToString(type, options);
438
+ else if (type instanceof Types.FloatType)
439
+ return this._floatTypeToString(type, options);
440
+ else if (type instanceof Types.IntegerType)
441
+ return this._integerTypeToString(type, options);
442
+ else if (type instanceof Types.StringType)
443
+ return this._stringTypeToString(type, options);
444
+ else if (type instanceof Types.TextType)
445
+ return this._textTypeToString(type, options);
446
+ else if (type instanceof Types.UUIDV1Type)
447
+ return this._uuidV1TypeToString(type, options);
448
+ else if (type instanceof Types.UUIDV3Type)
449
+ return this._uuidV3TypeToString(type, options);
450
+ else if (type instanceof Types.UUIDV4Type)
451
+ return this._uuidV4TypeToString(type, options);
452
+ else if (type instanceof Types.UUIDV5Type)
453
+ return this._uuidV5TypeToString(type, options);
454
+ else if (type instanceof Types.XIDType)
455
+ return this._xidTypeToString(type, options);
456
+
457
+ throw new Error(`${this.constructor.name}::typeToString: Unsupported type ${type}.`);
458
+ }
459
+
460
+ convertDateToDBTime(value) {
461
+ if (value instanceof Date || (value && value.constructor && value.constructor.name === 'Date'))
462
+ return value;
463
+ else if (moment.isMoment(value))
464
+ return value.toDate();
465
+
466
+ return value;
467
+ }
468
+
469
+ ensureAllModelsAreInstances(Model, _models, options) {
470
+ if (!_models)
471
+ return [];
472
+
473
+ if (_models._mythixPreparedModels)
474
+ return _models._mythixPreparedModels.models;
475
+
476
+ let instantiatedModels = [];
477
+ let startIndex = options.startIndex || 0;
478
+ let models = Nife.toArray(_models);
479
+ let endIndex = models.length;
480
+
481
+ if (options.endIndex)
482
+ endIndex = Math.min(options.endIndex, endIndex);
483
+ else if (options.batchSize)
484
+ endIndex = Math.min(startIndex + options.batchSize, endIndex);
485
+
486
+ for (let i = startIndex; i < endIndex; i++) {
487
+ let model = models[i];
488
+
489
+ if (!ModelBase.isModel(model))
490
+ model = new Model(model, { connection: this });
491
+
492
+ instantiatedModels.push(model);
493
+ }
494
+
495
+ return instantiatedModels;
496
+ }
497
+
498
+ prepareAllModelsForOperation(Model, _models, _options) {
499
+ if (!_models)
500
+ return {};
501
+
502
+ if (_models._mythixPreparedModels)
503
+ return _models._mythixPreparedModels;
504
+
505
+ if (Array.isArray(_models) && !_models.length)
506
+ return {};
507
+
508
+ let options = _options || {};
509
+ let finalizedModels = [];
510
+ let dirtyModels = [];
511
+ let dirtyFieldNames = {};
512
+ let dirtyFields = [];
513
+ let hasAllFieldNames = false;
514
+ let totalFieldCount = Model.getConcreteFieldCount();
515
+ let startIndex = options.startIndex || 0;
516
+ let models = Nife.toArray(_models);
517
+ let endIndex = models.length;
518
+
519
+ if (options.endIndex)
520
+ endIndex = Math.min(options.endIndex, endIndex);
521
+ else if (options.batchSize)
522
+ endIndex = Math.min(startIndex + options.batchSize, endIndex);
523
+
524
+ // Make sure all items are models,
525
+ // and find all the dirty fields
526
+ for (let i = startIndex; i < endIndex; i++) {
527
+ let model = models[i];
528
+
529
+ if (!ModelBase.isModel(model)) {
530
+ model = new Model(model, { connection: this });
531
+ } else if (model.isPersisted() && options.skipPersisted) {
532
+ if (model.isDirty())
533
+ dirtyModels.push(model);
534
+
535
+ continue;
536
+ }
537
+
538
+ if (!hasAllFieldNames) {
539
+ Object.assign(
540
+ dirtyFieldNames,
541
+ model._getDirtyFields({
542
+ update: options.isUpdateOperation,
543
+ insert: options.isInsertOperation,
544
+ }),
545
+ );
546
+
547
+ if (Object.keys(dirtyFieldNames).length >= totalFieldCount)
548
+ hasAllFieldNames = true;
549
+ }
550
+
551
+ finalizedModels.push(model);
552
+ }
553
+
554
+ let fieldNames = Object.keys(dirtyFieldNames);
555
+ for (let i = 0, il = fieldNames.length; i < il; i++) {
556
+ let fieldName = fieldNames[i];
557
+ let field = Model.getField(fieldName);
558
+ if (!field)
559
+ continue;
560
+
561
+ dirtyFields.push(field);
562
+ }
563
+
564
+ let finalResult = { models: finalizedModels, dirtyFields, dirtyModels };
565
+ Object.defineProperties(finalResult, {
566
+ '_mythixPreparedModels': {
567
+ writable: true,
568
+ enumberable: false,
569
+ configurable: true,
570
+ value: finalResult,
571
+ },
572
+ });
573
+
574
+ return finalResult;
575
+ }
576
+
577
+ splitModelAndSubModels(Model, primaryModel, _relationMap) {
578
+ const addModelInstance = (modelName, self) => {
579
+ let relatedModels = relationMap.get(modelName);
580
+ if (!relatedModels) {
581
+ relatedModels = new Set();
582
+ relationMap.set(modelName, relatedModels);
583
+ }
584
+
585
+ relatedModels.add(self);
586
+ };
587
+
588
+ const splitSubModels = (Model, model) => {
589
+ if (alreadyVisitedMap.get(model))
590
+ return;
591
+
592
+ alreadyVisitedMap.set(model, true);
593
+
594
+ let modelName = Model.getModelName();
595
+
596
+ Model.iterateFields(({ field, fieldName }) => {
597
+ let fieldType = field.type;
598
+ if (!fieldType.isRelational())
599
+ return;
600
+
601
+ // We don't deal with multi-relational fields
602
+ if (fieldType.isManyRelation())
603
+ return;
604
+
605
+ let TargetModel = fieldType.getTargetModel(this);
606
+ let targetModelName = TargetModel.getModelName();
607
+ if (targetModelName === modelName || targetModelName === primaryModelName)
608
+ return;
609
+
610
+ let modelInstance = model[fieldName];
611
+ if (modelInstance == null)
612
+ return;
613
+
614
+ if (!(modelInstance instanceof TargetModel))
615
+ modelInstance = new TargetModel(modelInstance, { connection: this });
616
+
617
+ addModelInstance(targetModelName, modelInstance);
618
+
619
+ // Sub model data may have another
620
+ // model to create
621
+ splitSubModels(TargetModel, modelInstance, relationMap);
622
+ });
623
+ };
624
+
625
+ let alreadyVisitedMap = new Map();
626
+ let relationMap = _relationMap || new Map();
627
+ let primaryModelName = Model.getModelName();
628
+
629
+ splitSubModels(Model, primaryModel);
630
+
631
+ return relationMap;
632
+ }
633
+
634
+ // eslint-disable-next-line no-unused-vars
635
+ prepareAllModelsAndSubModelsForOperation(Model, models, _options) {
636
+ let primaryModelRelationMap = new Map();
637
+ let groupedModelMap = new Map();
638
+ let primaryModelName = Model.getModelName();
639
+
640
+ const addModelToGroup = (modelName, self) => {
641
+ let group = groupedModelMap.get(modelName);
642
+ if (!group) {
643
+ group = new Set();
644
+ groupedModelMap.set(modelName, group);
645
+ }
646
+
647
+ group.add(self);
648
+ };
649
+
650
+ // Collect all primary models and sub-models
651
+ for (let i = 0, il = models.length; i < il; i++) {
652
+ let model = models[i];
653
+ if (!ModelBase.isModel(model))
654
+ model = new Model(model, { connection: this });
655
+
656
+ let subModels = this.splitModelAndSubModels(Model, model);
657
+
658
+ primaryModelRelationMap.set(model, subModels);
659
+ addModelToGroup(primaryModelName, model);
660
+
661
+ for (let [ modelName, models ] of subModels.entries()) {
662
+ for (let subModel of models.values())
663
+ addModelToGroup(modelName, subModel);
664
+ }
665
+ }
666
+
667
+ // Sort group map by model creation order
668
+ let sortedGroupedModelMap = new Map();
669
+ let sortedModelNames = Utils.sortModelNamesByCreationOrder(this, Array.from(groupedModelMap.keys()));
670
+ for (let i = 0, il = sortedModelNames.length; i < il; i++) {
671
+ let groupModelName = sortedModelNames[i];
672
+ let subModels = Array.from(groupedModelMap.get(groupModelName).values());
673
+
674
+ sortedGroupedModelMap.set(groupModelName, subModels);
675
+ }
676
+
677
+ // Now copy related field values between all instantiated models
678
+ for (let [ primaryModel, subModelMap ] of primaryModelRelationMap.entries()) {
679
+ for (let [ targetModelName, targetModels ] of subModelMap.entries()) {
680
+ let TargetModel = this.getModel(targetModelName);
681
+
682
+ for (let targetModel of targetModels.values()) {
683
+ if (targetModel !== primaryModel) {
684
+ Utils.setRelationalValues(this, TargetModel, targetModel, Model, primaryModel);
685
+ Utils.setRelationalValues(this, Model, primaryModel, TargetModel, targetModel);
686
+ }
687
+
688
+ for (let [ sourceModelName, sourceModels ] of subModelMap.entries()) {
689
+ if (sourceModelName === targetModelName)
690
+ continue;
691
+
692
+ if (!TargetModel.isForeignKeyTargetModel(this, sourceModelName))
693
+ continue;
694
+
695
+ let SourceModel = this.getModel(sourceModelName);
696
+ for (let sourceModel of sourceModels.values()) {
697
+ if (sourceModel === primaryModel)
698
+ continue;
699
+
700
+ Utils.setRelationalValues(this, TargetModel, targetModel, SourceModel, sourceModel);
701
+ }
702
+ }
703
+ }
704
+ }
705
+ }
706
+
707
+ return sortedGroupedModelMap;
708
+ }
709
+
710
+ async bulkModelOperation(Model, _models, _options, beforeCallback, callback, afterCallback, afterOperationCallback) {
711
+ let models = _models;
712
+ if (!models)
713
+ return;
714
+
715
+ let inputIsArray = false;
716
+ if (!Array.isArray(models)) {
717
+ if (!models._mythixPreparedModels) {
718
+ if (models instanceof Map || models instanceof Set)
719
+ inputIsArray = true;
720
+
721
+ models = Nife.toArray(models).filter(Boolean);
722
+ } else {
723
+ inputIsArray = true;
724
+ }
725
+ } else {
726
+ inputIsArray = true;
727
+ }
728
+
729
+ if (Nife.isEmpty(models))
730
+ return (inputIsArray) ? [] : undefined;
731
+
732
+ let queryGenerator = this.getQueryGenerator();
733
+ let options = Object.assign({}, _options || {});
734
+ let batchSize = options.batchSize || 500;
735
+ if (batchSize < 1)
736
+ throw new Error(`${this.constructor.name}::bulkModelOperation: "batchSize" can not be less than 1.`);
737
+
738
+ const computeBulkModels = async (Model, _models, options) => {
739
+ let models = Nife.toArray(_models);
740
+ let totalModels = models.length;
741
+ if (totalModels === 0)
742
+ return { results: finalResults, dirtyModels };
743
+
744
+ let offset = 0;
745
+ let finalResults = [];
746
+ let dirtyModels = [];
747
+
748
+ options.endIndex = 0;
749
+
750
+ while (options.endIndex < totalModels) {
751
+ options.startIndex = offset;
752
+ options.endIndex = offset + batchSize;
753
+
754
+ if (options.endIndex >= totalModels)
755
+ options.endIndex = totalModels;
756
+
757
+ // We need to run before callbacks first
758
+ // because this might change the fields
759
+ // that are dirty
760
+ let batchModelInstances = this.ensureAllModelsAreInstances(Model, models, options);
761
+ if (typeof beforeCallback === 'function')
762
+ await beforeCallback.call(this, Model, batchModelInstances, options, queryGenerator);
763
+
764
+ let preparedModels = this.prepareAllModelsForOperation(Model, batchModelInstances, Object.assign({}, options, { startIndex: 0, endIndex: batchModelInstances.length }));
765
+ if (preparedModels.models.length === 0) {
766
+ // no dirty models in this batch
767
+ offset += batchSize;
768
+ continue;
769
+ }
770
+
771
+ await callback.call(this, Model, preparedModels, options, queryGenerator);
772
+
773
+ if (options.isInsertOperation)
774
+ dirtyModels = dirtyModels.concat(preparedModels.dirtyModels);
775
+
776
+ let batchModels = preparedModels.models;
777
+ if (batchModels) {
778
+ for (let i = 0, il = batchModels.length; i < il; i++) {
779
+ let batchModel = batchModels[i];
780
+ batchModel.clearDirty();
781
+ finalResults.push(batchModel);
782
+ }
783
+ }
784
+
785
+ offset += batchSize;
786
+ }
787
+
788
+ return { results: finalResults, dirtyModels };
789
+ };
790
+
791
+ if (options.isDeleteOperation) {
792
+ // For delete we DO NOT want to collect
793
+ // related models... just delete what we have
794
+ let { results } = await computeBulkModels(Model, models, options);
795
+ return (inputIsArray || !results) ? results : results[0];
796
+ }
797
+
798
+ let primaryModelName = Model.getModelName();
799
+ let groupedModelMap = this.prepareAllModelsAndSubModelsForOperation(Model, models, options);
800
+ let alreadyStored = {};
801
+ let allDirtyModels = new Set();
802
+ let primaryResult;
803
+
804
+ for (let [ modelName, models ] of groupedModelMap) {
805
+ let GroupModel = this.getModel(modelName);
806
+ let { results, dirtyModels } = await computeBulkModels(GroupModel, models, options);
807
+
808
+ // If prepareAllModelsForOperation found persisted
809
+ // models that were dirty, then add them here
810
+ if (dirtyModels && dirtyModels.length > 0) {
811
+ for (let i = 0, il = dirtyModels.length; i < il; i++)
812
+ allDirtyModels.add(dirtyModels[i]);
813
+ }
814
+
815
+ alreadyStored[modelName] = true;
816
+
817
+ if (modelName === primaryModelName)
818
+ primaryResult = results;
819
+
820
+ for (let storedModel of results) {
821
+ for (let [ groupModelName, groupModels ] of groupedModelMap) {
822
+ if (groupModelName === modelName)
823
+ continue;
824
+
825
+ if (!alreadyStored[groupModelName])
826
+ continue;
827
+
828
+ let TargetModel = this.getModel(groupModelName);
829
+ if (!TargetModel.isForeignKeyTargetModel(this, modelName))
830
+ continue;
831
+
832
+ for (let targetModel of groupModels) {
833
+ Utils.setRelationalValues(this, TargetModel, targetModel, GroupModel, storedModel);
834
+
835
+ if (targetModel.isDirty())
836
+ allDirtyModels.add(targetModel);
837
+ }
838
+ }
839
+ }
840
+
841
+ if (typeof afterCallback === 'function')
842
+ await afterCallback.call(this, GroupModel, results, options, queryGenerator);
843
+ }
844
+
845
+ if (allDirtyModels.size > 0 && typeof afterOperationCallback === 'function')
846
+ await afterOperationCallback.call(this, Model, allDirtyModels, options, queryGenerator);
847
+
848
+ return (inputIsArray || !primaryResult) ? primaryResult : primaryResult[0];
849
+ }
850
+
851
+ setPersisted(_models, value) {
852
+ let models = _models;
853
+ if (models._mythixPreparedModels)
854
+ models = models.models;
855
+
856
+ if (Nife.isEmpty(models))
857
+ return;
858
+
859
+ for (let i = 0, il = models.length; i < il; i++) {
860
+ let model = models[i];
861
+ if (!model || !model._mythixModelInstance)
862
+ continue;
863
+
864
+ model._persisted = value;
865
+ }
866
+ }
867
+
868
+ async start() {
869
+ throw new Error(`${this.constructor.name}::start: Child class is required to implement "start".`);
870
+ }
871
+
872
+ async stop() {
873
+ this.removeAllListeners();
874
+ }
875
+ }
876
+
877
+ module.exports = ConnectionBase;