mythix-orm 1.8.3 → 1.10.2

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.
@@ -81,6 +81,7 @@ declare class ConnectionBase extends EventEmitter {
81
81
  public getOptions(): ConnectionBaseOptions;
82
82
  public isStarted(): boolean;
83
83
  public toQueryEngine(queryEngineLike: any): QueryEngine | undefined;
84
+ public finalizeQuery(operation: string, query: QueryEngine, options: GenericObject): Promise<QueryEngine>
84
85
  public registerModel<T = ModelClass>(Model: T, options?: GenericObject): T;
85
86
  public registerModels(models: Models | Array<ModelClass>, options?: GenericObject): Models | undefined;
86
87
  public getContextValue(key: any, defaultValue?: any): any;
@@ -489,6 +489,73 @@ class ConnectionBase extends EventEmitter {
489
489
  return queryEngine;
490
490
  }
491
491
 
492
+ async finalizeQuery(crudOperation, queryEngine, options) {
493
+ if (!QueryEngine.isQuery(queryEngine))
494
+ return queryEngine;
495
+
496
+ const finalizeQueryForModel = async (query, parent, contextKey, depth) => {
497
+ let operations = query.getOperationStack();
498
+
499
+ // Has query already been finalized?
500
+ let lastOperation = operations[operations.length - 1];
501
+ if (lastOperation && Object.prototype.hasOwnProperty.call(lastOperation, '_queryFinalized') && lastOperation._queryFinalized)
502
+ return query;
503
+
504
+ let newQuery = query.clone();
505
+ let alreadyVisitedModels = new Set();
506
+
507
+ for (let i = 0, il = operations.length; i < il; i++) {
508
+ let operation = operations[i];
509
+ if (!Object.prototype.hasOwnProperty.call(operation, 'modelName'))
510
+ continue;
511
+
512
+ let modelName = operation.modelName;
513
+ if (alreadyVisitedModels.has(modelName))
514
+ continue;
515
+
516
+ alreadyVisitedModels.add(modelName);
517
+
518
+ let Model = operation.Model;
519
+ if (typeof Model.finalizeQuery !== 'function')
520
+ continue;
521
+
522
+ newQuery = await Model.finalizeQuery({
523
+ type: crudOperation,
524
+ query: newQuery,
525
+ queryDepth: depth,
526
+ operationIndex: i,
527
+ connection: this,
528
+ Model,
529
+ modelName,
530
+ operation,
531
+ operations,
532
+ parent,
533
+ contextKey,
534
+ options,
535
+ });
536
+
537
+ if (parent && contextKey)
538
+ parent[contextKey] = newQuery;
539
+ }
540
+
541
+ // Mark as finalized
542
+ newQuery.getOperationContext()._queryFinalized = true;
543
+
544
+ return newQuery;
545
+ };
546
+
547
+ let promises = [];
548
+
549
+ queryEngine.walk((query, parent, contextKey, depth) => {
550
+ promises.push(finalizeQueryForModel(query, parent, contextKey, depth));
551
+ });
552
+
553
+ if (promises.length > 0)
554
+ await Promise.all(promises);
555
+
556
+ return await finalizeQueryForModel(queryEngine, null, null, 0);
557
+ }
558
+
492
559
  /// Register the provided model class.
493
560
  ///
494
561
  /// This will register the provided model class with this
@@ -124,13 +124,13 @@ class QueryGeneratorBase {
124
124
  // eslint-disable-next-line no-unused-vars
125
125
  getAllModelsUsedInQuery(queryEngine, _options) {
126
126
  let options = _options || {};
127
- let queryEngineContextID = queryEngine._getTopContextID();
127
+ let queryEngineContextID = queryEngine.getQueryID();
128
128
  let cache = this.getOptionsCache(_options, `getAllModelsUsedInQuery.${queryEngineContextID}`);
129
129
  if (cache)
130
130
  return cache;
131
131
 
132
132
  let Models = new Map();
133
- let query = queryEngine._getRawQuery();
133
+ let query = queryEngine.getOperationStack();
134
134
 
135
135
  for (let i = 0, il = query.length; i < il; i++) {
136
136
  let queryPart = query[i];
@@ -257,7 +257,7 @@ class QueryGeneratorBase {
257
257
  }
258
258
 
259
259
  getProjectionFromQueryEngine(queryEngine, options) {
260
- let queryEngineContextID = queryEngine._getTopContextID();
260
+ let queryEngineContextID = queryEngine.getQueryID();
261
261
  let cache = this.getOptionsCache(options, `getProjectionFromQueryEngine.${queryEngineContextID}`);
262
262
  if (cache)
263
263
  return cache;
@@ -413,13 +413,13 @@ class QueryGeneratorBase {
413
413
  return result;
414
414
  };
415
415
 
416
- let rawQueryContext = queryEngine._getRawQueryContext();
416
+ let rawQueryContext = queryEngine.getOperationContext();
417
417
  let RootModel = rawQueryContext.rootModel;
418
418
  if (!RootModel)
419
419
  throw new Error('QueryGeneratorBase::getProjectionFromQueryEngine: No root model found for query. Root model is required to generate a projection.');
420
420
 
421
421
  let hasDistinct = rawQueryContext.distinct;
422
- let projections = collectProjectionValuesFromQuery(RootModel, queryEngine._getRawQuery());
422
+ let projections = collectProjectionValuesFromQuery(RootModel, queryEngine.getOperationStack());
423
423
  let projectedFields = new Map();
424
424
  let allModels = this.getAllModelsUsedInQuery(queryEngine, options);
425
425
  let isAdding = true;
@@ -707,13 +707,13 @@ class QueryGeneratorBase {
707
707
 
708
708
  // eslint-disable-next-line no-unused-vars
709
709
  getOrderLimitOffset(queryEngine, options) {
710
- let queryEngineContextID = queryEngine._getTopContextID();
710
+ let queryEngineContextID = queryEngine.getQueryID();
711
711
  let cache = this.getOptionsCache(options, `getOrderLimitOffset.${queryEngineContextID}`);
712
712
  if (cache)
713
713
  return cache;
714
714
 
715
- let query = queryEngine._getRawQuery();
716
- let rootModel = queryEngine._getRawQueryContext().rootModel;
715
+ let query = queryEngine.getOperationStack();
716
+ let rootModel = queryEngine.getOperationContext().rootModel;
717
717
  let limit;
718
718
  let offset;
719
719
  let order;
@@ -785,10 +785,10 @@ class QueryGeneratorBase {
785
785
  }
786
786
 
787
787
  getQuerySliceFromQueryPart(queryPart) {
788
- let queryRoot = queryPart.queryRoot;
789
- let index = queryRoot.indexOf(queryPart);
788
+ let operationStack = queryPart.operationStack;
789
+ let index = operationStack.indexOf(queryPart);
790
790
 
791
- return queryRoot.slice(index);
791
+ return operationStack.slice(index);
792
792
  }
793
793
 
794
794
  _averageLiteralToString(literal, options) {
package/lib/model.d.ts CHANGED
@@ -42,6 +42,21 @@ export declare interface IterateFieldsContext {
42
42
 
43
43
  export declare type IterateFieldsCallback = (context: IterateFieldsContext) => any;
44
44
 
45
+ export declare interface FinalizeQueryContext {
46
+ type: 'create' | 'read' | 'update' | 'delete';
47
+ query: QueryEngine;
48
+ queryDepth: number;
49
+ connection: ConnectionBase;
50
+ Model: Model;
51
+ modelName: string;
52
+ operationIndex: number;
53
+ operation: GenericObject;
54
+ operations: Array<GenericObject>;
55
+ parent: GenericObject | null;
56
+ contextKey: string | null;
57
+ options: GenericObject | null;
58
+ }
59
+
45
60
  export declare class Model {
46
61
  declare public static fields: LooseFields | undefined;
47
62
  declare public static _sortedFields: Array<Field> | null;
@@ -83,6 +98,8 @@ export declare class Model {
83
98
  public static defaultScope(query: QueryEngine): QueryEngine;
84
99
  public defaultScope(query: QueryEngine): QueryEngine;
85
100
 
101
+ public static finalizeQuery(context: FinalizeQueryContext): Promise<QueryEngine>;
102
+
86
103
  public static getQueryEngine(connection?: ConnectionBase, options?: GenericObject): QueryEngine;
87
104
  public getQueryEngine(connection?: ConnectionBase, options?: GenericObject): QueryEngine;
88
105
 
package/lib/model.js CHANGED
@@ -673,6 +673,11 @@ class Model {
673
673
  return this.constructor.defaultScope(queryEngine);
674
674
  }
675
675
 
676
+ // eslint-disable-next-line no-unused-vars
677
+ static finalizeQuery({ type, query, queryDepth, operationIndex, operation, operations, parent, contextKey, options }) {
678
+ return query;
679
+ }
680
+
676
681
  /// This method is called any time a `Model.where` or
677
682
  /// `Model.$` property is accessed. It returns a query,
678
683
  /// based off this model class (as the root model).
@@ -19,14 +19,14 @@ function addOperatorToQuery(name, inverseName, value, extraOptions) {
19
19
  if (extraOptions)
20
20
  conditionalParams = Object.assign(conditionalParams, extraOptions);
21
21
 
22
- this._addToQuery(conditionalParams);
22
+ this._pushOperationOntoStack(conditionalParams);
23
23
 
24
24
  return this._fetchScope('model');
25
25
  }
26
26
 
27
27
  class FieldScope extends QueryEngineBase {
28
28
  NOT = ProxyClass.autoCall(function() {
29
- this._addToQuery({ logical: true, operator: 'NOT', not: !this.currentContext.not });
29
+ this._pushOperationOntoStack({ logical: true, operator: 'NOT', not: !this.currentContext.not });
30
30
  return this[ProxyClass.PROXY];
31
31
  });
32
32
 
@@ -47,17 +47,17 @@ class ModelScope extends QueryEngineBase {
47
47
  }
48
48
 
49
49
  NOT = ProxyClass.autoCall(function() {
50
- this._addToQuery({ logical: true, operator: 'NOT', not: !this.currentContext.not });
50
+ this._pushOperationOntoStack({ logical: true, operator: 'NOT', not: !this.currentContext.not });
51
51
  return this._fetchScope('model');
52
52
  });
53
53
 
54
54
  AND = ProxyClass.autoCall(function(value) {
55
- this._addToQuery({ logical: true, operator: 'AND', and: true, or: false, not: false, value });
55
+ this._pushOperationOntoStack({ logical: true, operator: 'AND', and: true, or: false, not: false, value });
56
56
  return this._fetchScope('model');
57
57
  });
58
58
 
59
59
  OR = ProxyClass.autoCall(function(value) {
60
- this._addToQuery({ logical: true, operator: 'OR', and: false, or: true, not: false, value });
60
+ this._pushOperationOntoStack({ logical: true, operator: 'OR', and: false, or: true, not: false, value });
61
61
  return this._fetchScope('model');
62
62
  });
63
63
 
@@ -67,7 +67,7 @@ class ModelScope extends QueryEngineBase {
67
67
  throw new Error('QueryEngine::ModelScope::LIMIT: Value provided must be a valid positive number, or Infinity.');
68
68
 
69
69
  value = Math.round(value);
70
- this._addToQuery({ control: true, operator: 'LIMIT', value, limit: value });
70
+ this._pushOperationOntoStack({ control: true, operator: 'LIMIT', value, limit: value });
71
71
 
72
72
  return this._fetchScope('model');
73
73
  }
@@ -78,7 +78,7 @@ class ModelScope extends QueryEngineBase {
78
78
  throw new Error('QueryEngine::ModelScope::OFFSET: Value provided must be a valid positive number.');
79
79
 
80
80
  value = Math.round(value);
81
- this._addToQuery({ control: true, operator: 'OFFSET', value, offset: value });
81
+ this._pushOperationOntoStack({ control: true, operator: 'OFFSET', value, offset: value });
82
82
 
83
83
  return this._fetchScope('model');
84
84
  }
@@ -96,7 +96,7 @@ class ModelScope extends QueryEngineBase {
96
96
  return true;
97
97
  });
98
98
 
99
- this._addToQuery({ control: true, operator: 'ORDER', value: values, order: values });
99
+ this._pushOperationOntoStack({ control: true, operator: 'ORDER', value: values, order: values });
100
100
  return this._fetchScope('model');
101
101
  }
102
102
 
@@ -125,7 +125,7 @@ class ModelScope extends QueryEngineBase {
125
125
  return value;
126
126
  }).filter(Boolean);
127
127
 
128
- this._addToQuery({ control: true, operator: 'PROJECT', value: values });
128
+ this._pushOperationOntoStack({ control: true, operator: 'PROJECT', value: values });
129
129
  return this._fetchScope('model');
130
130
  }
131
131
 
@@ -134,7 +134,7 @@ class ModelScope extends QueryEngineBase {
134
134
  let distinctValue = fullyQualifiedName;
135
135
 
136
136
  if (arguments.length === 0) {
137
- let context = this._getRawQueryContext();
137
+ let context = this.getOperationContext();
138
138
  let rootModel = context.rootModel;
139
139
  if (!rootModel)
140
140
  throw new Error(`${this.constructor.name}::DISTINCT: Attempted to apply DISTINCT to the root model of the query, but no root model was found.`);
@@ -150,32 +150,32 @@ class ModelScope extends QueryEngineBase {
150
150
  currentQuery = this.PROJECT(`-${fullyQualifiedName}`);
151
151
  }
152
152
 
153
- currentQuery._addToQuery({ control: true, operator: 'DISTINCT', value: distinctValue, distinct: distinctValue });
153
+ currentQuery._pushOperationOntoStack({ control: true, operator: 'DISTINCT', value: distinctValue, distinct: distinctValue });
154
154
  return this._fetchScope('model');
155
155
  });
156
156
 
157
157
  INNER_JOIN = ProxyClass.autoCall(function() {
158
- this._addToQuery({ control: true, operator: 'JOIN', value: 'inner', joinType: 'inner', joinOuter: false });
158
+ this._pushOperationOntoStack({ control: true, operator: 'JOIN', value: 'inner', joinType: 'inner', joinOuter: false });
159
159
  return this._fetchScope('model');
160
160
  });
161
161
 
162
162
  LEFT_JOIN = ProxyClass.autoCall(function(outerJoin) {
163
- this._addToQuery({ control: true, operator: 'JOIN', value: 'left', joinType: 'left', joinOuter: !!outerJoin });
163
+ this._pushOperationOntoStack({ control: true, operator: 'JOIN', value: 'left', joinType: 'left', joinOuter: !!outerJoin });
164
164
  return this._fetchScope('model');
165
165
  });
166
166
 
167
167
  RIGHT_JOIN = ProxyClass.autoCall(function(outerJoin) {
168
- this._addToQuery({ control: true, operator: 'JOIN', value: 'right', joinType: 'right', joinOuter: !!outerJoin });
168
+ this._pushOperationOntoStack({ control: true, operator: 'JOIN', value: 'right', joinType: 'right', joinOuter: !!outerJoin });
169
169
  return this._fetchScope('model');
170
170
  });
171
171
 
172
172
  FULL_JOIN = ProxyClass.autoCall(function(outerJoin) {
173
- this._addToQuery({ control: true, operator: 'JOIN', value: 'full', joinType: 'full', joinOuter: !!outerJoin });
173
+ this._pushOperationOntoStack({ control: true, operator: 'JOIN', value: 'full', joinType: 'full', joinOuter: !!outerJoin });
174
174
  return this._fetchScope('model');
175
175
  });
176
176
 
177
177
  CROSS_JOIN = ProxyClass.autoCall(function() {
178
- this._addToQuery({ control: true, operator: 'JOIN', value: 'cross', joinType: 'cross', joinOuter: false });
178
+ this._pushOperationOntoStack({ control: true, operator: 'JOIN', value: 'cross', joinType: 'cross', joinOuter: false });
179
179
  return this._fetchScope('model');
180
180
  });
181
181
 
@@ -183,7 +183,7 @@ class ModelScope extends QueryEngineBase {
183
183
  if (!(Nife.instanceOf(type, 'string') || LiteralBase.isLiteral(type)))
184
184
  throw new Error('QueryEngine::ModelScope::JOIN: Invalid value provided. Value must be a valid string or Literal specifying JOIN type.');
185
185
 
186
- this._addToQuery({ control: true, operator: 'JOIN', value: type, joinType: type, joinOuter: false });
186
+ this._pushOperationOntoStack({ control: true, operator: 'JOIN', value: type, joinType: type, joinOuter: false });
187
187
  return this._fetchScope('model');
188
188
  }
189
189
  }
@@ -27,7 +27,7 @@ class QueryEngineBase extends ProxyClass {
27
27
  if (value._isQueryEngine)
28
28
  return true;
29
29
 
30
- if (typeof value._getRawQueryContext === 'function')
30
+ if (typeof value.getOperationContext === 'function')
31
31
  return true;
32
32
 
33
33
  return false;
@@ -63,6 +63,10 @@ class QueryEngineBase extends ProxyClass {
63
63
  return this.getQueryEngineScope().getFieldScopeClass();
64
64
  }
65
65
 
66
+ getQueryEngineScopeClass() {
67
+ return this.getQueryEngineScope().constructor;
68
+ }
69
+
66
70
  _inheritContext(context, name, ...args) {
67
71
  let newContext = Object.assign(
68
72
  Object.create(context),
@@ -82,7 +86,7 @@ class QueryEngineBase extends ProxyClass {
82
86
  }
83
87
 
84
88
  _fetchScope(...scopeNames) {
85
- let context = this._getRawQueryContext();
89
+ let context = this.getOperationContext();
86
90
 
87
91
  for (let i = 0, il = scopeNames.length; i < il; i++) {
88
92
  let scopeName = scopeNames[i];
@@ -149,7 +153,7 @@ class QueryEngineBase extends ProxyClass {
149
153
  // We shouldn't add this scope if this is
150
154
  // already the current model of the scope
151
155
  if (context.Model !== Model)
152
- context.queryRoot.push(newContext);
156
+ context.operationStack.push(newContext);
153
157
 
154
158
  // But we always need to return the scope
155
159
  // for the proxy to work properly
@@ -173,7 +177,7 @@ class QueryEngineBase extends ProxyClass {
173
177
  // We shouldn't add this scope if this is
174
178
  // already the current field of the scope
175
179
  if (context.Field !== Field)
176
- context.queryRoot.push(newContext);
180
+ context.operationStack.push(newContext);
177
181
 
178
182
  // But we always need to return the scope
179
183
  // for the proxy to work properly
@@ -193,8 +197,8 @@ class QueryEngineBase extends ProxyClass {
193
197
  context.rootContext = context;
194
198
  }
195
199
 
196
- if (!context.queryRoot)
197
- context.queryRoot = [];
200
+ if (!context.operationStack)
201
+ context.operationStack = [];
198
202
 
199
203
  if (!('and' in context))
200
204
  context.and = true;
@@ -204,19 +208,19 @@ class QueryEngineBase extends ProxyClass {
204
208
 
205
209
  // console.log(`Creating new ${this.constructor.name} scope: `, context, Object.getPrototypeOf(context));
206
210
 
207
- let queryRoot = context.queryRoot;
211
+ let operationStack = context.operationStack;
208
212
  Object.defineProperties(this, {
209
- queryRoot: {
213
+ operationStack: {
210
214
  writable: false,
211
215
  enumerable: false,
212
216
  configurable: true,
213
- value: queryRoot,
217
+ value: operationStack,
214
218
  },
215
219
  currentContext: {
216
220
  enumerable: false,
217
221
  configurable: true,
218
222
  get: () => {
219
- let currentContext = queryRoot[queryRoot.length - 1];
223
+ let currentContext = operationStack[operationStack.length - 1];
220
224
  return currentContext || context;
221
225
  },
222
226
  set: () => {},
@@ -224,20 +228,20 @@ class QueryEngineBase extends ProxyClass {
224
228
  });
225
229
  }
226
230
 
227
- _getTopContextID() {
231
+ getQueryID() {
228
232
  return this.currentContext.contextID;
229
233
  }
230
234
 
231
- _getRawQueryContext() {
235
+ getOperationContext() {
232
236
  return this.currentContext;
233
237
  }
234
238
 
235
- _getRawQuery() {
236
- return this.currentContext.queryRoot;
239
+ getOperationStack() {
240
+ return this.currentContext.operationStack;
237
241
  }
238
242
 
239
- _isLastPartControl() {
240
- let queryParts = this._getRawQuery();
243
+ isLastOperationControl() {
244
+ let queryParts = this.getOperationStack();
241
245
  let lastPart = queryParts[queryParts.length - 1];
242
246
 
243
247
  if (Object.prototype.hasOwnProperty.call(lastPart, 'control') && lastPart.control === true)
@@ -246,8 +250,8 @@ class QueryEngineBase extends ProxyClass {
246
250
  return false;
247
251
  }
248
252
 
249
- _isLastPartCondition() {
250
- let queryParts = this._getRawQuery();
253
+ isLastOperationCondition() {
254
+ let queryParts = this.getOperationStack();
251
255
  let lastPart = queryParts[queryParts.length - 1];
252
256
 
253
257
  if (Object.prototype.hasOwnProperty.call(lastPart, 'condition') && lastPart.condition === true)
@@ -256,13 +260,13 @@ class QueryEngineBase extends ProxyClass {
256
260
  return false;
257
261
  }
258
262
 
259
- _queryHasConditions() {
260
- let context = this._getRawQueryContext();
263
+ queryHasConditions() {
264
+ let context = this.getOperationContext();
261
265
  return context.hasCondition;
262
266
  }
263
267
 
264
- _queryHasJoins() {
265
- let queryParts = this._getRawQuery();
268
+ queryHasJoins() {
269
+ let queryParts = this.getOperationStack();
266
270
  for (let i = 0, il = queryParts.length; i < il; i++) {
267
271
  let queryPart = queryParts[i];
268
272
  if (QueryEngineBase.isQuery(queryPart.value) && !queryPart.value.hasCondition)
@@ -272,8 +276,8 @@ class QueryEngineBase extends ProxyClass {
272
276
  return false;
273
277
  }
274
278
 
275
- _debugQuery() {
276
- let query = this._getRawQuery();
279
+ logQueryOperations() {
280
+ let query = this.getOperationStack();
277
281
  for (let i = 0, il = query.length; i < il; i++) {
278
282
  let queryPart = query[i];
279
283
  let operator = queryPart.operator;
@@ -288,11 +292,11 @@ class QueryEngineBase extends ProxyClass {
288
292
 
289
293
  }
290
294
 
291
- _addToQuery(queryPart, _context) {
292
- let context = _context || this._getRawQueryContext();
293
- let queryRoot = context.queryRoot;
295
+ _pushOperationOntoStack(queryPart, _context) {
296
+ let context = _context || this.getOperationContext();
297
+ let operationStack = context.operationStack;
294
298
 
295
- queryRoot.push(
299
+ operationStack.push(
296
300
  this._inheritContext(
297
301
  context,
298
302
  null,
@@ -327,14 +331,83 @@ class QueryEngineBase extends ProxyClass {
327
331
  }
328
332
 
329
333
  clone() {
330
- const Klass = this.constructor;
331
- let context = this._getRawQueryContext();
332
- let queryRootCopy = this._getRawQuery().slice();
333
- let newContext = Object.assign(Object.create(context), { queryRoot: queryRootCopy });
334
+ return this.map((part) => part)._fetchScope('model');
335
+ }
336
+
337
+ filter(callback) {
338
+ const Klass = this.getQueryEngineScopeClass();
339
+ let context = this.getOperationContext();
340
+ let parts = this.getOperationStack();
341
+ let query = new Klass({
342
+ ...context.rootContext,
343
+ connection: this.getConnection(),
344
+ operationStack: [],
345
+ });
346
+
347
+ for (let i = 0, il = parts.length; i < il; i++) {
348
+ let part = parts[i];
349
+ if (!part)
350
+ continue;
351
+
352
+ if (!callback(part, i, parts, query))
353
+ continue;
354
+
355
+ query._pushOperationOntoStack({ ...part });
356
+ }
357
+
358
+ return query;
359
+ }
360
+
361
+ map(callback) {
362
+ const Klass = this.getQueryEngineScopeClass();
363
+ let context = this.getOperationContext();
364
+ let parts = this.getOperationStack();
365
+ let query = new Klass({
366
+ ...context.rootContext,
367
+ connection: this.getConnection(),
368
+ operationStack: [],
369
+ });
334
370
 
335
- queryRootCopy.push(newContext);
371
+ for (let i = 0, il = parts.length; i < il; i++) {
372
+ let part = parts[i];
373
+ if (!part)
374
+ continue;
375
+
376
+ let newPart = callback({ ...part }, i, parts, query);
377
+ if (!newPart || typeof newPart !== 'object')
378
+ continue;
379
+
380
+ query._pushOperationOntoStack(newPart);
381
+ }
382
+
383
+ return query;
384
+ }
385
+
386
+ walk(callback, _checkContextKeys) {
387
+ const walkQueries = (query, parent, contextKey, depth) => {
388
+ let parts = query.getOperationStack();
389
+ for (let i = 0, il = parts.length; i < il; i++) {
390
+ let part = parts[i];
391
+ if (!part)
392
+ continue;
393
+
394
+ for (let j = 0, jl = checkContextKeys.length; j < jl; j++) {
395
+ let contextKey = checkContextKeys[j];
396
+ if (!Object.prototype.hasOwnProperty.call(part, contextKey))
397
+ continue;
398
+
399
+ let value = part[contextKey];
400
+ if (value && this.constructor.isQuery(value))
401
+ walkQueries(value, part, contextKey, depth + 1);
402
+ }
403
+ }
404
+
405
+ if (parent !== null)
406
+ callback(query, parent, contextKey, depth);
407
+ };
336
408
 
337
- return new Klass(newContext);
409
+ let checkContextKeys = _checkContextKeys || [ 'value' ];
410
+ walkQueries(this, null, null, 0);
338
411
  }
339
412
  }
340
413
 
@@ -6,14 +6,14 @@ import Field from '../field';
6
6
 
7
7
  export declare interface QueryEngineOptions {
8
8
  connection: ConnectionBase;
9
- [ key: string ]: any;
9
+ [key: string]: any;
10
10
  }
11
11
 
12
12
  export declare type QueryEngineClass = typeof QueryEngine;
13
13
 
14
14
  export declare interface CallableInterface {
15
15
  (...args: Array<any>): QueryEngine;
16
- [ key: string ]: QueryEngine;
16
+ [key: string]: QueryEngine;
17
17
  }
18
18
 
19
19
  export declare class QueryEngine<T = ConnectionBase> {
@@ -31,33 +31,37 @@ export declare class QueryEngine<T = ConnectionBase> {
31
31
  public _newModelScope(Model: ModelClass): QueryEngine;
32
32
  public _newFieldScope(Field: Field): QueryEngine;
33
33
  public constructor(context: QueryEngineOptions);
34
- public _getTopContextID(): number;
35
- public _getRawQueryContext(): GenericObject;
36
- public _getRawQuery(): Array<GenericObject>;
37
- public _isLastPartControl(): boolean;
38
- public _isLastPartCondition(): boolean;
39
- public _queryHasConditions(): boolean;
40
- public _queryHasJoins(): boolean;
41
- public _debugQuery(): void;
42
- public _addToQuery(queryPart: GenericObject, context: GenericObject): void;
34
+ public getQueryID(): number;
35
+ public getOperationContext(): GenericObject;
36
+ public getOperationStack(): Array<GenericObject>;
37
+ public isLastOperationControl(): boolean;
38
+ public isLastOperationCondition(): boolean;
39
+ public queryHasConditions(): boolean;
40
+ public queryHasJoins(): boolean;
41
+ public logQueryOperations(): void;
42
+ public _pushOperationOntoStack(queryPart: GenericObject, context: GenericObject): void;
43
43
  public getConnection(): ConnectionBase;
44
44
  public getModel(modelName: string): ModelClass | undefined;
45
45
  public getQueryEngineScope(): QueryEngine;
46
46
  public getQueryEngineClass(): QueryEngineClass;
47
47
  public clone(): QueryEngine;
48
+ public filter(callback: (operation: GenericObject, index: number, operations: Array<GenericObject>, query: QueryEngine) => GenericObject): QueryEngine;
49
+ public map(callback: (operation: GenericObject, index: number, operations: Array<GenericObject>, query: QueryEngine) => GenericObject): QueryEngine;
50
+ public walk(callback: (query: QueryEngine, parent: GenericObject | null, contextKey: string, depth: number) => GenericObject, checkContextKeys?: Array<string>): void;
48
51
 
49
52
  // QueryEngine
50
53
  public getModelScopeClass(): QueryEngineClass;
51
54
  public getFieldScopeClass(): QueryEngineClass;
55
+ public getQueryEngineScopeClass(): QueryEngineClass;
52
56
  public Model(modelName: string): QueryEngine;
53
57
  public unscoped(context?: GenericObject): QueryEngine;
54
58
  public toString(options?: GenericObject): string;
55
59
  public MERGE(queryEngine: QueryEngine): QueryEngine;
56
60
  public all<T extends Model = Model>(options?: GenericObject): Promise<Array<T>>;
57
- public fetchAll<T extends Model = Model>(options?: GenericObject): AsyncGenerator<T>;
61
+ public cursor<T extends Model = Model>(options?: GenericObject): AsyncGenerator<T>;
58
62
  public first<T extends Model = Model>(limit?: number | null | undefined, options?: GenericObject): Promise<T | undefined>;
59
63
  public last<T extends Model = Model>(limit?: number | null | undefined, options?: GenericObject): Promise<T | undefined>;
60
- public update<T extends Model = Model>(attributes: T | GenericObject, options?: GenericObject): Promise<number>;
64
+ public updateAll<T extends Model = Model>(attributes: T | GenericObject, options?: GenericObject): Promise<number>;
61
65
  public destroy(options?: GenericObject): Promise<number>;
62
66
  public average(field: Field | string, options?: GenericObject): Promise<number>;
63
67
  public count(field: Field | string, options?: GenericObject): Promise<number>;
@@ -66,6 +70,7 @@ export declare class QueryEngine<T = ConnectionBase> {
66
70
  public sum(field: Field | string, options?: GenericObject): Promise<number>;
67
71
  public pluck(fields: string | Array<string>, options?: GenericObject): Promise<Array<any>>;
68
72
  public exists(options?: GenericObject): Promise<boolean>;
73
+ public finalizeQuery(operation: string, options: GenericObject): Promise<QueryEngine>;
69
74
 
70
75
  // ModelScope
71
76
  public _getField(fieldName: string): Field | undefined;
@@ -80,70 +85,70 @@ export declare class QueryEngine<T = ConnectionBase> {
80
85
  (): QueryEngine;
81
86
 
82
87
  name: QueryEngine;
83
- [ key: string ]: QueryEngine;
88
+ [key: string]: QueryEngine;
84
89
  };
85
90
 
86
91
  declare public AND: {
87
92
  (query: QueryEngine): QueryEngine;
88
93
 
89
94
  name: QueryEngine;
90
- [ key: string ]: QueryEngine;
95
+ [key: string]: QueryEngine;
91
96
  };
92
97
 
93
98
  declare public OR: {
94
99
  (query: QueryEngine): QueryEngine;
95
100
 
96
101
  name: QueryEngine;
97
- [ key: string ]: QueryEngine;
102
+ [key: string]: QueryEngine;
98
103
  };
99
104
 
100
105
  declare public DISTINCT: {
101
106
  (fullyQualifiedName: string | Field): QueryEngine;
102
107
 
103
108
  name: QueryEngine;
104
- [ key: string ]: QueryEngine;
109
+ [key: string]: QueryEngine;
105
110
  };
106
111
 
107
112
  declare public INNER_JOIN: {
108
113
  (): QueryEngine;
109
114
 
110
115
  name: QueryEngine;
111
- [ key: string ]: QueryEngine;
116
+ [key: string]: QueryEngine;
112
117
  };
113
118
 
114
119
  declare public LEFT_JOIN: {
115
120
  (): QueryEngine;
116
121
 
117
122
  name: QueryEngine;
118
- [ key: string ]: QueryEngine;
123
+ [key: string]: QueryEngine;
119
124
  };
120
125
 
121
126
  declare public RIGHT_JOIN: {
122
127
  (): QueryEngine;
123
128
 
124
129
  name: QueryEngine;
125
- [ key: string ]: QueryEngine;
130
+ [key: string]: QueryEngine;
126
131
  };
127
132
 
128
133
  declare public FULL_JOIN: {
129
134
  (): QueryEngine;
130
135
 
131
136
  name: QueryEngine;
132
- [ key: string ]: QueryEngine;
137
+ [key: string]: QueryEngine;
133
138
  };
134
139
 
135
140
  declare public CROSS_JOIN: {
136
141
  (): QueryEngine;
137
142
 
138
143
  name: QueryEngine;
139
- [ key: string ]: QueryEngine;
144
+ [key: string]: QueryEngine;
140
145
  };
141
146
 
142
147
  declare public JOIN: {
143
148
  (type: string | LiteralBase): QueryEngine;
144
149
 
145
150
  name: QueryEngine;
146
- [ key: string ]: QueryEngine;
151
+ [key: string]: QueryEngine;
147
152
  };
148
153
 
149
154
  // FieldScope
@@ -158,7 +163,7 @@ export declare class QueryEngine<T = ConnectionBase> {
158
163
  public NOT_LIKE(value: string, options?: { caseSensitive: boolean }): QueryEngine;
159
164
 
160
165
  name: QueryEngine;
161
- [ key: string ]: any;
166
+ [key: string]: any;
162
167
  }
163
168
 
164
169
  export class ModelScope extends QueryEngine { }
@@ -64,11 +64,11 @@ class QueryEngine extends QueryEngineBase {
64
64
  if (!queryEngine)
65
65
  return this;
66
66
 
67
- let thisQueryContext = this._getRawQueryContext();
67
+ let thisQueryContext = this.getOperationContext();
68
68
  if (!QueryEngine.isQuery(queryEngine) && Nife.instanceOf(queryEngine, 'array', 'object', 'map', 'set'))
69
69
  queryEngine = Utils.generateQueryFromFilter(this.getConnection(), thisQueryContext.rootModel, queryEngine);
70
70
 
71
- let sourceQuery = queryEngine._getRawQuery();
71
+ let sourceQuery = queryEngine.getOperationStack();
72
72
  let currentModel = thisQueryContext.Model;
73
73
  let skipLogicalOperators = true;
74
74
 
@@ -83,10 +83,11 @@ class QueryEngine extends QueryEngineBase {
83
83
  'isQueryContext',
84
84
  'modelContext',
85
85
  'queryEngineScope',
86
- 'queryRoot',
86
+ 'operationStack',
87
87
  'rootContext',
88
88
  'rootModel',
89
89
  'rootModelName',
90
+ '_queryFinalized',
90
91
  ]);
91
92
 
92
93
  // For merges, we want to skip the first logical operators
@@ -114,7 +115,7 @@ class QueryEngine extends QueryEngineBase {
114
115
  }
115
116
  }
116
117
 
117
- this._addToQuery(Object.assign({
118
+ this._pushOperationOntoStack(Object.assign({
118
119
  Model: queryPart.Model,
119
120
  modelName: queryPart.modelName,
120
121
  Field: queryPart.Field,
@@ -125,23 +126,25 @@ class QueryEngine extends QueryEngineBase {
125
126
  return this;
126
127
  }
127
128
 
128
- all(options) {
129
+ async all(options) {
129
130
  let connection = this.getConnection(options && options.connection);
130
131
 
131
132
  if (options && options.stream)
132
- return connection.select(this, options);
133
- else
134
- return Utils.collect(connection.select(this, options));
133
+ throw new TypeError('QueryEngine::all: "stream" option set to true. For streaming, please use the "cursor" method instead.');
134
+
135
+ return await Utils.collect(connection.select(await this.finalizeQuery('read', options), options));
135
136
  }
136
137
 
137
- fetchAll(options) {
138
- return this.all({ ...(options || {}), stream: true });
138
+ async *cursor(_options) {
139
+ let options = _options || {};
140
+ let connection = this.getConnection(options && options.connection);
141
+ return yield *connection.select(await this.finalizeQuery('read', options), { ...options, stream: true });
139
142
  }
140
143
 
141
144
  async first(_limit, options) {
142
145
  let limit = (_limit == null) ? 1 : _limit;
143
146
  let connection = this.getConnection(options && options.connection);
144
- let query = this.clone().LIMIT(limit);
147
+ let query = (await this.finalizeQuery('read', options)).clone().LIMIT(limit);
145
148
 
146
149
  let result = await Utils.collect(connection.select(query, options));
147
150
  return (_limit == null) ? result[0] : result;
@@ -150,55 +153,63 @@ class QueryEngine extends QueryEngineBase {
150
153
  async last(_limit, options) {
151
154
  let limit = (_limit == null) ? 1 : _limit;
152
155
  let connection = this.getConnection(options && options.connection);
153
- let query = this.clone().LIMIT(limit);
156
+ let query = (await this.finalizeQuery('read', options)).clone().LIMIT(limit);
154
157
 
155
158
  let result = await Utils.collect(connection.select(query, Object.assign({}, options || {}, { reverseOrder: true })));
156
159
  return (_limit == null) ? result[0] : result.reverse();
157
160
  }
158
161
 
159
- async update(attributes, options) {
162
+ async updateAll(attributes, options) {
160
163
  let connection = this.getConnection(options && options.connection);
161
- return await connection.updateAll(this, attributes, options);
164
+ return await connection.updateAll(await this.finalizeQuery('update', options), attributes, options);
162
165
  }
163
166
 
164
167
  async destroy(options) {
165
168
  let connection = this.getConnection(options && options.connection);
166
- return await connection.destroy(this, options);
169
+ return await connection.destroy(await this.finalizeQuery('delete', options), options);
167
170
  }
168
171
 
169
172
  async average(field, options) {
170
173
  let connection = this.getConnection(options && options.connection);
171
- return await connection.average(this, field, options);
174
+ return await connection.average(await this.finalizeQuery('read', options), field, options);
172
175
  }
173
176
 
174
177
  async count(field, options) {
175
178
  let connection = this.getConnection(options && options.connection);
176
- return await connection.count(this, field, options);
179
+ return await connection.count(await this.finalizeQuery('read', options), field, options);
177
180
  }
178
181
 
179
182
  async min(field, options) {
180
183
  let connection = this.getConnection(options && options.connection);
181
- return await connection.min(this, field, options);
184
+ return await connection.min(await this.finalizeQuery('read', options), field, options);
182
185
  }
183
186
 
184
187
  async max(field, options) {
185
188
  let connection = this.getConnection(options && options.connection);
186
- return await connection.max(this, field, options);
189
+ return await connection.max(await this.finalizeQuery('read', options), field, options);
187
190
  }
188
191
 
189
192
  async sum(field, options) {
190
193
  let connection = this.getConnection(options && options.connection);
191
- return await connection.sum(this, field, options);
194
+ return await connection.sum(await this.finalizeQuery('read', options), field, options);
192
195
  }
193
196
 
194
197
  async pluck(fields, options) {
195
198
  let connection = this.getConnection(options && options.connection);
196
- return await connection.pluck(this, fields, options);
199
+ return await connection.pluck(await this.finalizeQuery('read', options), fields, options);
197
200
  }
198
201
 
199
202
  async exists(options) {
200
203
  let connection = this.getConnection(options && options.connection);
201
- return await connection.exists(this, options);
204
+ return await connection.exists(await this.finalizeQuery('read', options), options);
205
+ }
206
+
207
+ async finalizeQuery(crudOperation, options) {
208
+ let connection = this.getConnection();
209
+ if (connection && typeof connection.finalizeQuery === 'function')
210
+ return await connection.finalizeQuery(crudOperation, this, options);
211
+
212
+ return this;
202
213
  }
203
214
 
204
215
  [ProxyClass.MISSING](target, prop) {
@@ -125,7 +125,7 @@ const TYPE_OPERATIONS = {
125
125
  'get': function({ field, type }, userQuery, options, ...args) {
126
126
  const doGet = async function*() {
127
127
  let query = await type.prepareQuery({ connection: null, self: this, field, options }, [ userQuery ].concat(args));
128
- let results = query.all(Object.assign({}, options, { stream: true }));
128
+ let results = query.cursor(options);
129
129
  let primaryModelRelationalArray = [];
130
130
 
131
131
  this[field.fieldName] = primaryModelRelationalArray;
@@ -170,7 +170,7 @@ class RelationalTypeBase extends Type {
170
170
  let PrimaryModel = (context.self && context.self.getModel());
171
171
  let primaryModelName = PrimaryModel.getModelName();
172
172
  let query = await this.prepareQuery(Object.assign({ connection }, context), args);
173
- let queryParts = query._getRawQuery();
173
+ let queryParts = query.getOperationStack();
174
174
  let queryContext = (queryParts[queryParts.length - 1] || {});
175
175
  let TargetModel = queryContext.rootModel;
176
176
  let TargetField = queryContext.rootField;
@@ -186,7 +186,7 @@ class RelationalTypeBase extends Type {
186
186
  let rightQueryContext = null;
187
187
 
188
188
  if (QueryEngine.isQuery(conditionValue)) {
189
- rightQueryContext = conditionValue._getRawQueryContext();
189
+ rightQueryContext = conditionValue.getOperationContext();
190
190
  if (rightQueryContext.condition)
191
191
  continue;
192
192
  } else {
@@ -9,8 +9,9 @@ function isUUID(value) {
9
9
  return UUID.validate(value);
10
10
  }
11
11
 
12
- function sanitizeFieldString(str) {
13
- return ('' + str).replace(/[^\w:.]+/g, '').replace(/([:.])+/g, '$1');
12
+ function sanitizeFieldString(_str) {
13
+ let str = (typeof _str === 'symbol') ? (_str.toString()) : (_str + '');
14
+ return str.replace(/[^\w:.]+/g, '').replace(/([:.])+/g, '$1');
14
15
  }
15
16
 
16
17
  /// Parse a fully qualified field name.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mythix-orm",
3
- "version": "1.8.3",
3
+ "version": "1.10.2",
4
4
  "description": "ORM for Mythix framework",
5
5
  "main": "lib/index",
6
6
  "type": "commonjs",