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,864 @@
1
+ 'use strict';
2
+
3
+ const Nife = require('nife');
4
+ const QueryEngine = require('../query-engine/query-engine');
5
+ const Literals = require('./literals');
6
+ const ModelBase = require('../model');
7
+
8
+ const LiteralBase = Literals.LiteralBase;
9
+ const LITERAL_IS_DISTINCT_RE = (/^DISTINCT[\s(]/i);
10
+
11
+ class QueryGeneratorBase {
12
+ constructor(connection) {
13
+ Object.defineProperties(this, {
14
+ 'connection': {
15
+ writable: true,
16
+ enumberable: false,
17
+ configurable: true,
18
+ value: connection,
19
+ },
20
+ });
21
+ }
22
+
23
+ stackAssign(obj, ...args) {
24
+ let newObj = Object.create(obj || {});
25
+
26
+ Object.assign(newObj, ...args);
27
+
28
+ return newObj;
29
+ }
30
+
31
+ getOptionsCache(options, keyPath, initialValue) {
32
+ return Nife.get(options, `_cache.${keyPath}`, (typeof initialValue === 'function') ? initialValue() : initialValue);
33
+ }
34
+
35
+ setOptionsCache(options, keyPath, value) {
36
+ Nife.set(options, `_cache.${keyPath}`, value);
37
+ }
38
+
39
+ escape(...args) {
40
+ return this.connection.escape(...args);
41
+ }
42
+
43
+ escapeID(...args) {
44
+ return this.connection.escapeID(...args);
45
+ }
46
+
47
+ getTableNameFromQueryPart(queryPart) {
48
+ let Model = queryPart.Model;
49
+ return Model.getTableName(this.connection);
50
+ }
51
+
52
+ getEscapedFieldName(_Model, field, options) {
53
+ let isString = Nife.instanceOf(field, 'string');
54
+ let fieldName = (isString) ? field : field.fieldName;
55
+ let Model = _Model;
56
+
57
+ if (!Model && field && !isString)
58
+ Model = field.Model;
59
+
60
+ if (!Model || (options && options.fieldNameOnly === true))
61
+ return this.escapeID(fieldName);
62
+ else
63
+ return `"${field.Model.getModelName()}:${fieldName}"`;
64
+ }
65
+
66
+ getEscapedColumnName(_Model, field, options) {
67
+ let isString = Nife.instanceOf(field, 'string');
68
+ let columnName = (isString) ? field : (field.columnName || field.fieldName);
69
+ let Model = _Model;
70
+
71
+ if (!Model && field && !isString)
72
+ Model = field.Model;
73
+
74
+ if (!Model || (options && options.columnNameOnly === true))
75
+ return this.escapeID(columnName);
76
+ else
77
+ return `${this.escapeID(Model.getTableName(this.connection))}.${this.escapeID(columnName)}`;
78
+ }
79
+
80
+ // eslint-disable-next-line no-unused-vars
81
+ getEscapedProjectionName(Model, field, options) {
82
+ if (options && options.noProjectionAliases)
83
+ return this.getEscapedColumnName(Model, field, options);
84
+ else
85
+ return `${this.getEscapedColumnName(Model, field, options)} AS ${this.getEscapedFieldName(Model, field, options)}`;
86
+ }
87
+
88
+ // eslint-disable-next-line no-unused-vars
89
+ getEscapedModelFields(Model, options) {
90
+ let fields = {};
91
+ let modelName = Model.getModelName();
92
+
93
+ Model.iterateFields(({ field, fieldName }) => {
94
+ if (field.type.isVirtual())
95
+ return;
96
+
97
+ let result;
98
+
99
+ if (options && options.asProjection)
100
+ result = this.getEscapedProjectionName(Model, field, options);
101
+ else if (options && options.asColumn)
102
+ result = this.getEscapedColumnName(Model, field, options);
103
+ else
104
+ result = this.getEscapedFieldName(Model, field, options);
105
+
106
+ fields[`${modelName}:${fieldName}`] = result;
107
+ }, (options && options.fields));
108
+
109
+ return fields;
110
+ }
111
+
112
+ // eslint-disable-next-line no-unused-vars
113
+ getAllModelsUsedInQuery(queryEngine, _options) {
114
+ let options = _options || {};
115
+ let queryEngineContextID = queryEngine._getTopContextID();
116
+ let cache = this.getOptionsCache(_options, `getAllModelsUsedInQuery.${queryEngineContextID}`);
117
+ if (cache)
118
+ return cache;
119
+
120
+ let Models = new Map();
121
+ let query = queryEngine._getRawQuery();
122
+
123
+ for (let i = 0, il = query.length; i < il; i++) {
124
+ let queryPart = query[i];
125
+
126
+ if (Object.prototype.hasOwnProperty.call(queryPart, 'operator') && queryPart.operator === 'MODEL') {
127
+ let Model = queryPart.Model;
128
+ Models.set(Model, Model);
129
+ } else if (Object.prototype.hasOwnProperty.call(queryPart, 'condition') && queryPart.condition === true) {
130
+ let operatorValue = queryPart.value;
131
+ if (!QueryEngine.isQuery(operatorValue))
132
+ continue;
133
+
134
+ let SubModels = this.getAllModelsUsedInQuery(operatorValue, options);
135
+ for (let j = 0, jl = SubModels.length; j < jl; j++) {
136
+ let Model = SubModels[j];
137
+ Models.set(Model, Model);
138
+ }
139
+ }
140
+ }
141
+
142
+ let allModels = Array.from(Models.values());
143
+
144
+ if (_options)
145
+ this.setOptionsCache(_options, `getAllModelsUsedInQuery.${queryEngineContextID}`, allModels);
146
+
147
+ return allModels;
148
+ }
149
+
150
+ getProjectionRequiredFields(queryEngine, options) {
151
+ let { order } = this.getOrderLimitOffset(queryEngine, options);
152
+ if (!order)
153
+ return new Map();
154
+
155
+ let orderFieldMap = new Map();
156
+ for (let i = 0, il = order.length; i < il; i++) {
157
+ let field = order[i];
158
+ if (!field)
159
+ continue;
160
+
161
+ if (field instanceof LiteralBase) {
162
+ let result = field.toString(this.connection);
163
+ let projectionField = this.parseFieldProjection(result, true);
164
+ if (projectionField === result) {
165
+ // not able to parse projection
166
+ continue;
167
+ }
168
+
169
+ field = projectionField;
170
+ }
171
+
172
+ let Field = field.Field || field;
173
+ let Model = Field.Model;
174
+ let modelName = Model.getModelName();
175
+ let fullFieldName = `${modelName}:${Field.fieldName}`;
176
+
177
+ orderFieldMap.set(fullFieldName, {
178
+ projectedName: this.getEscapedProjectionName(Model, Field, options),
179
+ Model: Model,
180
+ modelName: modelName,
181
+ Field: Field,
182
+ fieldName: Field.fieldName,
183
+ direction: '+',
184
+ fullFieldName,
185
+ });
186
+ }
187
+
188
+ return orderFieldMap;
189
+ }
190
+
191
+ sortedProjectedFields(projectedFields) {
192
+ return projectedFields.sort((a, b) => {
193
+ // If either value is a distinct literal
194
+ // then make certain it comes first in
195
+ // the list of projected fields (required
196
+ // by some databases)
197
+ const distinctSortOrder = (a, b) => {
198
+ let x = (a instanceof LiteralBase);
199
+ let y = (b instanceof LiteralBase);
200
+ let xStr = (x) ? a.toString(this.connection) : a;
201
+ let yStr = (y) ? b.toString(this.connection) : b;
202
+ let xIsDistinct = (typeof xStr === 'string' && LITERAL_IS_DISTINCT_RE.test(xStr));
203
+ let yIsDistinct = (typeof yStr === 'string' && LITERAL_IS_DISTINCT_RE.test(yStr));
204
+
205
+ if (xIsDistinct || yIsDistinct) {
206
+ if (xIsDistinct === yIsDistinct)
207
+ return 0;
208
+
209
+ if (xIsDistinct && !yIsDistinct)
210
+ return -1;
211
+
212
+ if (yIsDistinct && !xIsDistinct)
213
+ return 1;
214
+ }
215
+
216
+ // Neither is a literal, so simply
217
+ // return and continue processing
218
+ if (!x && !y)
219
+ return;
220
+
221
+ // We know one of them must be a literal
222
+ // when a literal is encountered, the
223
+ // sort order is not modified
224
+ return 0;
225
+ };
226
+
227
+ let distinctOrder = distinctSortOrder(a, b);
228
+ if (distinctOrder != null)
229
+ return distinctOrder;
230
+
231
+ let x = (typeof a === 'string') ? a : `${a.modelName}:${a.fieldName}`;
232
+ let y = (typeof b === 'string') ? b : `${b.modelName}:${b.fieldName}`;
233
+
234
+ if (x === y)
235
+ return 0;
236
+
237
+ return (x < y) ? -1 : 1;
238
+ });
239
+ }
240
+
241
+ getProjectionFromQueryEngine(queryEngine, options) {
242
+ let queryEngineContextID = queryEngine._getTopContextID();
243
+ let cache = this.getOptionsCache(options, `getProjectionFromQueryEngine.${queryEngineContextID}`);
244
+ if (cache)
245
+ return cache;
246
+
247
+ const shouldResetProjection = (fields) => {
248
+ if (fields.length === 0)
249
+ return false;
250
+
251
+ if (fields.indexOf('+') >= 0)
252
+ return false;
253
+
254
+ for (let i = 0, il = fields.length; i < il; i++) {
255
+ let field = fields[i];
256
+
257
+ if (field instanceof LiteralBase)
258
+ return true;
259
+
260
+ if (ModelBase.isModelClass(field))
261
+ return true;
262
+
263
+ if (!Nife.instanceOf(field, 'string'))
264
+ true;
265
+
266
+ if (field === '-')
267
+ return true;
268
+
269
+ if (field === '*')
270
+ continue;
271
+
272
+ if (!(/^\s*[+-]/).test(field))
273
+ return true;
274
+ }
275
+
276
+ return false;
277
+ };
278
+
279
+ const collectProjectionValuesFromQuery = (RootModel, query) => {
280
+ let projections = [ RootModel ];
281
+
282
+ for (let i = 0, il = query.length; i < il; i++) {
283
+ let queryPart = query[i];
284
+
285
+ if (!Object.prototype.hasOwnProperty.call(queryPart, 'control'))
286
+ continue;
287
+
288
+ if (queryPart.control !== true)
289
+ continue;
290
+
291
+ if (queryPart.operator !== 'PROJECT')
292
+ continue;
293
+
294
+ let fields = queryPart.value;
295
+ if (shouldResetProjection(fields)) {
296
+ fields = Nife.arrayFlatten(fields);
297
+
298
+ let addIndex = fields.indexOf('+');
299
+ let newProjections = fields.slice();
300
+
301
+ if (addIndex >= 0)
302
+ newProjections[addIndex] = Nife.arrayFlatten(projections);
303
+
304
+ projections = newProjections;
305
+ } else {
306
+ projections = projections.concat(fields);
307
+ }
308
+ }
309
+
310
+ return Nife.uniq(Nife.arrayFlatten(projections));
311
+ };
312
+
313
+ const modelFieldsToProjection = (fieldSet, Model, removeFromProjection) => {
314
+ let modelName = Model.getModelName();
315
+
316
+ Model.iterateFields(({ field, fieldName }) => {
317
+ if (field.type.isVirtual())
318
+ return;
319
+
320
+ let fullFieldName = `${modelName}:${fieldName}`;
321
+
322
+ if (removeFromProjection) {
323
+ fieldSet.delete(fullFieldName);
324
+ return;
325
+ }
326
+
327
+ fieldSet.set(fullFieldName, {
328
+ projectedName: this.getEscapedProjectionName(Model, field, options),
329
+ Model: Model,
330
+ Field: field,
331
+ direction: '+',
332
+ fullFieldName,
333
+ fieldName,
334
+ modelName,
335
+ });
336
+ });
337
+ };
338
+
339
+ const addRequiredFieldsToProjection = (fieldSet) => {
340
+ let requiredProjectionFields = this.getProjectionRequiredFields(queryEngine, options);
341
+
342
+ for (let [ key, value ] of requiredProjectionFields) {
343
+ if (fieldSet.has(key))
344
+ continue;
345
+
346
+ fieldSet.set(key, value);
347
+ }
348
+
349
+ return fieldSet;
350
+ };
351
+
352
+ const finalizeProjection = (_projectedFields) => {
353
+ let projectedFields = _projectedFields;
354
+ let result;
355
+
356
+ // If we are sub-selecting then only the last
357
+ // field in the projection should be the projection
358
+ if (options && options.isSubQuery) {
359
+ result = result = Array.from(projectedFields.values());
360
+
361
+ let lastField = result[result.length - 1];
362
+
363
+ if (lastField instanceof LiteralBase)
364
+ result = [ lastField ];
365
+ else if (typeof lastField === 'string')
366
+ result = [ lastField ];
367
+ else
368
+ result = [ new Literals.DistinctLiteral(lastField.fullFieldName) ];
369
+ } else {
370
+ // If distinct specifies a field, then
371
+ // make sure that field isn't specified twice
372
+ if (hasDistinct) {
373
+ let distinctFieldName = hasDistinct.getFullyQualifiedFieldName();
374
+ if (distinctFieldName)
375
+ projectedFields.delete(distinctFieldName);
376
+ } else {
377
+ projectedFields = addRequiredFieldsToProjection(projectedFields);
378
+ }
379
+
380
+ result = Array.from(projectedFields.values());
381
+
382
+ // Convert projection fields to array and sort
383
+ result = this.sortedProjectedFields(result);
384
+
385
+ // Now prefix the projection fields with the distinct
386
+ // literal if one exists on the query
387
+ if (hasDistinct)
388
+ result = [ hasDistinct ].concat(result);
389
+ }
390
+
391
+ return result;
392
+ };
393
+
394
+ let rawQueryContext = queryEngine._getRawQueryContext();
395
+ let RootModel = rawQueryContext.rootModel;
396
+ if (!RootModel)
397
+ throw new Error('QueryGeneratorBase::getProjectionFromQueryEngine: No root model found for query. Root model is required to generate a projection.');
398
+
399
+ let hasDistinct = rawQueryContext.distinct;
400
+ let projections = collectProjectionValuesFromQuery(RootModel, queryEngine._getRawQuery());
401
+ let projectedFields = new Map();
402
+ let allModels = this.getAllModelsUsedInQuery(queryEngine, options);
403
+ let isAdding = true;
404
+
405
+ // If projection is empty, then return
406
+ // the projection of the root model
407
+ if (Nife.isEmpty(projections)) {
408
+ modelFieldsToProjection(projectedFields, RootModel);
409
+ return finalizeProjection(projectedFields);
410
+ }
411
+
412
+ for (let i = 0, il = projections.length; i < il; i++) {
413
+ let projectionValue = projections[i];
414
+ if (!projectionValue)
415
+ continue;
416
+
417
+ if (projectionValue === '+') {
418
+ isAdding = true;
419
+ continue;
420
+ }
421
+
422
+ if (projectionValue === '-') {
423
+ isAdding = false;
424
+ continue;
425
+ }
426
+
427
+ if (projectionValue === '*') {
428
+ for (let i = 0, il = allModels.length; i < il; i++) {
429
+ let Model = allModels[i];
430
+ modelFieldsToProjection(projectedFields, Model);
431
+ }
432
+
433
+ continue;
434
+ }
435
+
436
+ if (projectionValue instanceof LiteralBase) {
437
+ // If we already have distinct specified on the query
438
+ // then skip any distinct values specified by the user
439
+ if (hasDistinct && projectionValue instanceof Literals.DistinctLiteral)
440
+ continue;
441
+
442
+ let key = projectionValue.toString(this.connection);
443
+ if (isAdding)
444
+ projectedFields.set(key, projectionValue);
445
+ else
446
+ projectedFields.delete(key);
447
+
448
+ continue;
449
+ }
450
+
451
+ if (ModelBase.isModelClass(projectionValue)) {
452
+ if (allModels.indexOf(projectionValue) >= 0)
453
+ modelFieldsToProjection(projectedFields, projectionValue, !isAdding);
454
+
455
+ continue;
456
+ }
457
+
458
+ let { fieldName, direction, hasDirection } = this.getFieldDirectionSpecifier(projectionValue);
459
+ if (!fieldName)
460
+ continue;
461
+
462
+ if (!hasDirection) {
463
+ direction = (isAdding) ? '+' : '-';
464
+ hasDirection = true;
465
+ }
466
+
467
+ let def = this.connection.parseQualifiedName(fieldName);
468
+ if (!def.modelName) {
469
+ if (Nife.isNotEmpty(def.fieldNames) && allModels.length > 1)
470
+ throw new Error(`QueryGeneratorBase::getProjectionFromQueryEngine: "${def.fieldNames[0]}" ambiguous. You must use a fully qualified field name for an ORDER clause. Example: "+Model:id".`);
471
+
472
+ def.modelName = RootModel.getModelName();
473
+ }
474
+
475
+ let ProjectionModel = this.connection.getModel(def.modelName);
476
+ if (!ProjectionModel) {
477
+ if (hasDirection && direction === '-') {
478
+ projectedFields.delete(projectionValue);
479
+ continue;
480
+ }
481
+
482
+ // Proceed blindly... as the user may be
483
+ // querying something we are unaware of
484
+ projectedFields.set(projectionValue, projectionValue);
485
+ continue;
486
+ }
487
+
488
+ if (allModels.indexOf(ProjectionModel) < 0)
489
+ continue;
490
+
491
+ if (!def.fieldNames.length) {
492
+ // If there are no field names, but we have a model name
493
+ // then let the projection generator generate all model
494
+ // fields
495
+
496
+ if (ProjectionModel) {
497
+ modelFieldsToProjection(projectedFields, ProjectionModel, (hasDirection && direction === '-'));
498
+ continue;
499
+ }
500
+ }
501
+
502
+ let field = this.connection.getField(def.fieldNames[0], def.modelName);
503
+ let modelName = (field) ? field.Model.getModelName() : ProjectionModel.getModelName();
504
+
505
+ if (!field) {
506
+ let projectionFieldName = def.fieldNames[0];
507
+ let fullFieldName = `${modelName}:${projectionFieldName}`;
508
+
509
+ if (hasDirection && direction === '-') {
510
+ projectedFields.delete(fullFieldName);
511
+ continue;
512
+ }
513
+
514
+ // Proceed blindly... as the user may be
515
+ // querying something we are unaware of
516
+ projectedFields.set(fullFieldName, {
517
+ projectedName: this.getEscapedProjectionName(ProjectionModel, fieldName, options),
518
+ Model: ProjectionModel,
519
+ Field: null,
520
+ fieldName: projectionFieldName,
521
+ direction: '+',
522
+ modelName,
523
+ fullFieldName,
524
+ });
525
+
526
+ continue;
527
+ }
528
+
529
+ let projectionFieldName = field.fieldName;
530
+ let fullFieldName = `${modelName}:${projectionFieldName}`;
531
+
532
+ if (hasDirection && direction === '-') {
533
+ projectedFields.delete(fullFieldName);
534
+ continue;
535
+ }
536
+
537
+ projectedFields.set(fullFieldName, {
538
+ projectedName: this.getEscapedProjectionName(field.Model, field, options),
539
+ Model: field.Model,
540
+ Field: field,
541
+ fieldName: field.fieldName,
542
+ fullFieldName,
543
+ modelName,
544
+ direction,
545
+ });
546
+ }
547
+
548
+ let result = finalizeProjection(projectedFields);
549
+
550
+ if (options)
551
+ this.setOptionsCache(options, `getProjectionFromQueryEngine.${queryEngineContextID}`, result);
552
+
553
+ return result;
554
+ }
555
+
556
+ getProjectedFields(queryEngine, _options, asMap) {
557
+ let queryProjection = this.getProjectionFromQueryEngine(queryEngine, _options);
558
+ let allProjectionFields = new Map();
559
+
560
+ for (let i = 0, il = queryProjection.length; i < il; i++) {
561
+ let projectionField = queryProjection[i];
562
+ if (!projectionField)
563
+ continue;
564
+
565
+ if (projectionField instanceof LiteralBase) {
566
+ let result = projectionField.toString(this.connection);
567
+ let fullFieldName = this.parseFieldProjection(result);
568
+ if (!fullFieldName)
569
+ fullFieldName = result;
570
+
571
+ if (fullFieldName && result)
572
+ allProjectionFields.set(fullFieldName, result);
573
+
574
+ if (result && !this.isFieldProjection(result))
575
+ allProjectionFields.set(result, result);
576
+
577
+ continue;
578
+ }
579
+
580
+ if (projectionField.direction === '-')
581
+ continue;
582
+
583
+ if (Nife.instanceOf(projectionField, 'string')) {
584
+ allProjectionFields.set(projectionField, projectionField);
585
+ continue;
586
+ }
587
+
588
+ let projectedName = projectionField.projectedName;
589
+ allProjectionFields.set(projectionField.fullFieldName, projectedName);
590
+ }
591
+
592
+ if (asMap === true)
593
+ return allProjectionFields;
594
+ else
595
+ return Array.from(allProjectionFields.values());
596
+ }
597
+
598
+ // eslint-disable-next-line no-unused-vars
599
+ getJoinTableInfoFromQueryContexts(leftQueryContext, rightQueryContext, joinType, options) {
600
+ let rootModel = leftQueryContext.rootModel;
601
+ let rootModelName = rootModel.getModelName();
602
+ let leftSideModel = leftQueryContext.Model;
603
+ let leftSideModelName = leftQueryContext.modelName;
604
+ if (!leftSideModel)
605
+ throw new Error(`${this.constructor.name}::getJoinTableInfoFromQueryEngine: Invalid operation: No model found for left-side of join statement.`);
606
+
607
+ let leftSideField = leftQueryContext.Field;
608
+ if (!leftSideField)
609
+ throw new Error(`${this.constructor.name}::getJoinTableInfoFromQueryEngine: Invalid operation: No left-side field found to match on for table join statement.`);
610
+
611
+ let isNot = leftQueryContext.not;
612
+ let operator = (isNot) ? leftQueryContext.inverseOperator : leftQueryContext.operator;
613
+ let rightSideModel = rightQueryContext.Model;
614
+ let rightSideModelName = rightQueryContext.modelName;
615
+ if (!rightSideModel)
616
+ throw new Error(`${this.constructor.name}::getJoinTableInfoFromQueryEngine: Invalid operation: No model found for right-side of join statement.`);
617
+
618
+ let rightSideField = rightQueryContext.Field;
619
+ if (!rightSideField)
620
+ throw new Error(`${this.constructor.name}::getJoinTableInfoFromQueryEngine: Invalid operation: No right-side field found to match on for table join statement.`);
621
+
622
+ let swapJoinRelation = (rightSideModelName === rootModelName);
623
+ let joinModel = (swapJoinRelation) ? leftSideModel : rightSideModel;
624
+ let joinModelName = (swapJoinRelation) ? leftSideModelName : rightSideModelName;
625
+
626
+ return {
627
+ operator,
628
+ joinType,
629
+ rootModelName,
630
+
631
+ joinModel,
632
+ joinModelName,
633
+
634
+ leftSideModel,
635
+ leftSideModelName,
636
+ leftQueryContext,
637
+ leftSideField,
638
+
639
+ rightSideModel,
640
+ rightSideModelName,
641
+ rightQueryContext,
642
+ rightSideField,
643
+ };
644
+ }
645
+
646
+ getFieldDirectionSpecifier(order) {
647
+ if (!order)
648
+ return order;
649
+
650
+ if (order instanceof LiteralBase)
651
+ return order;
652
+
653
+ // Is this a field?
654
+ if (order && order.Model && order.fieldName) {
655
+ return {
656
+ hasDirection: false,
657
+ direction: '+',
658
+ fieldName: order.fieldName,
659
+ };
660
+ }
661
+
662
+ let sign;
663
+
664
+ let fieldName = ('' + order).replace(/^[+-]+/, (m) => {
665
+ sign = m.charAt(0);
666
+ return '';
667
+ });
668
+
669
+ return {
670
+ hasDirection: !!sign,
671
+ direction: (sign === '-') ? '-' : '+',
672
+ fieldName,
673
+ };
674
+ }
675
+
676
+ // eslint-disable-next-line no-unused-vars
677
+ getOrderLimitOffset(queryEngine, options) {
678
+ let queryEngineContextID = queryEngine._getTopContextID();
679
+ let cache = this.getOptionsCache(options, `getOrderLimitOffset.${queryEngineContextID}`);
680
+ if (cache)
681
+ return cache;
682
+
683
+ let query = queryEngine._getRawQuery();
684
+ let rootModel = queryEngine._getRawQueryContext().rootModel;
685
+ let limit;
686
+ let offset;
687
+ let order;
688
+
689
+ for (let i = 0, il = query.length; i < il; i++) {
690
+ let queryPart = query[i];
691
+
692
+ if (!Object.prototype.hasOwnProperty.call(queryPart, 'control'))
693
+ continue;
694
+
695
+ if (queryPart.control !== true)
696
+ continue;
697
+
698
+ let queryOperator = queryPart.operator;
699
+
700
+ if (queryOperator === 'LIMIT')
701
+ limit = queryPart.value;
702
+ else if (queryOperator === 'OFFSET')
703
+ offset = queryPart.value;
704
+ else if (queryOperator === 'ORDER')
705
+ order = queryPart.value;
706
+ }
707
+
708
+ if (Nife.isNotEmpty(order) && !(order instanceof LiteralBase)) {
709
+ let allModels = this.getAllModelsUsedInQuery(queryEngine, options);
710
+
711
+ order = order.map((_fieldName) => {
712
+ if (_fieldName instanceof LiteralBase)
713
+ return _fieldName;
714
+
715
+ let { fieldName, direction } = this.getFieldDirectionSpecifier(_fieldName);
716
+ if (!fieldName)
717
+ return;
718
+
719
+ let def = this.connection.parseQualifiedName(fieldName);
720
+ if (!def.modelName) {
721
+ if (allModels.length > 1)
722
+ throw new Error(`QueryGeneratorBase::getOrderLimitOffset: "${fieldName}" ambiguous. You must use a fully qualified field name for an ORDER clause. Example: "+Model:id".`);
723
+
724
+ def.modelName = allModels[0].getModelName();
725
+ }
726
+
727
+ if (!def.fieldNames.length)
728
+ throw new Error(`QueryGeneratorBase::getOrderLimitOffset: No field names found for "${fieldName}".`);
729
+
730
+ let field = this.connection.getField(def.fieldNames[0], def.modelName);
731
+ if (!field)
732
+ throw new Error(`QueryGeneratorBase::getOrderLimitOffset: Unable to locate field "${def.modelName}"."${def.fieldNames[0]}".`);
733
+
734
+ return {
735
+ Model: field.Model,
736
+ Field: field,
737
+ direction,
738
+ };
739
+ });
740
+ } else if (Nife.isEmpty(order)) {
741
+ if (options && options.selectStatement === true)
742
+ order = this.connection.getDefaultOrder(rootModel, options);
743
+ }
744
+
745
+ let orderLimitOffset = { limit, order, offset };
746
+
747
+ if (options)
748
+ this.setOptionsCache(options, `getOrderLimitOffset.${queryEngineContextID}`, orderLimitOffset);
749
+
750
+ return orderLimitOffset;
751
+ }
752
+
753
+ queryHasConditions(query) {
754
+ for (let i = 0, il = query.length; i < il; i++) {
755
+ let queryPart = query[i];
756
+ if (!Object.prototype.hasOwnProperty.call(queryPart, 'condition'))
757
+ continue;
758
+
759
+ if (queryPart.condition === true)
760
+ return true;
761
+ }
762
+
763
+ return false;
764
+ }
765
+
766
+ getQuerySliceFromQueryPart(queryPart) {
767
+ let queryRoot = queryPart.queryRoot;
768
+ let index = queryRoot.indexOf(queryPart);
769
+
770
+ return queryRoot.slice(index);
771
+ }
772
+
773
+ _averageLiteralToString(literal) {
774
+ if (!literal || !(literal instanceof LiteralBase))
775
+ return;
776
+
777
+ let field = literal.definitionToField(this.connection, literal.definition);
778
+ let escapedFieldName;
779
+
780
+ if (field instanceof LiteralBase)
781
+ escapedFieldName = field.toString(this.connection);
782
+ else
783
+ escapedFieldName = this.getEscapedColumnName(field.Model, field, literal.options);
784
+
785
+ return `AVG(${escapedFieldName})`;
786
+ }
787
+
788
+ _countLiteralToString(literal) {
789
+ if (!literal || !(literal instanceof LiteralBase))
790
+ return;
791
+
792
+ let field = (literal.definition) ? literal.definitionToField(this.connection, literal.definition) : null;
793
+ let escapedFieldName;
794
+
795
+ if (field) {
796
+ if (field instanceof LiteralBase)
797
+ escapedFieldName = field.toString(this.connection);
798
+ else
799
+ escapedFieldName = this.getEscapedColumnName(field.Model, field, literal.options);
800
+ } else {
801
+ escapedFieldName = '*';
802
+ }
803
+
804
+ return `COUNT(${escapedFieldName})`;
805
+ }
806
+
807
+ _distinctLiteralToString(literal) {
808
+ if (!literal || !(literal instanceof LiteralBase))
809
+ return;
810
+
811
+ let field = literal.definitionToField(this.connection, literal.definition);
812
+ if (field instanceof LiteralBase)
813
+ return `DISTINCT ${field.toString(this.connection)}`;
814
+
815
+ return `DISTINCT ${this.getEscapedProjectionName(field.Model, field, literal.options)}`;
816
+ }
817
+
818
+ _maxLiteralToString(literal) {
819
+ if (!literal || !(literal instanceof LiteralBase))
820
+ return;
821
+
822
+ let field = literal.definitionToField(this.connection, literal.definition);
823
+ let escapedFieldName;
824
+
825
+ if (field instanceof LiteralBase)
826
+ escapedFieldName = field.toString(this.connection);
827
+ else
828
+ escapedFieldName = this.getEscapedColumnName(field.Model, field, literal.options);
829
+
830
+ return `MAX(${escapedFieldName})`;
831
+ }
832
+
833
+ _minLiteralToString(literal) {
834
+ if (!literal || !(literal instanceof LiteralBase))
835
+ return;
836
+
837
+ let field = literal.definitionToField(this.connection, literal.definition);
838
+ let escapedFieldName;
839
+
840
+ if (field instanceof LiteralBase)
841
+ escapedFieldName = field.toString(this.connection);
842
+ else
843
+ escapedFieldName = this.getEscapedColumnName(field.Model, field, literal.options);
844
+
845
+ return `MIN(${escapedFieldName})`;
846
+ }
847
+
848
+ _sumLiteralToString(literal) {
849
+ if (!literal || !(literal instanceof LiteralBase))
850
+ return;
851
+
852
+ let field = literal.definitionToField(this.connection, literal.definition);
853
+ let escapedFieldName;
854
+
855
+ if (field instanceof LiteralBase)
856
+ escapedFieldName = field.toString(this.connection);
857
+ else
858
+ escapedFieldName = this.getEscapedColumnName(field.Model, field, literal.options);
859
+
860
+ return `SUM(${escapedFieldName})`;
861
+ }
862
+ }
863
+
864
+ module.exports = QueryGeneratorBase;