mythix-orm 1.11.6 → 1.12.0

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.
@@ -8,15 +8,67 @@ function generateID() {
8
8
  return uuid++;
9
9
  }
10
10
 
11
+ /// `QueryEngineBase` is the class that all other
12
+ /// query engine classes inherit from. It provides
13
+ /// "proxy" support for all class instances.
14
+ /// `QueryEngine`, `ModelScope`, and `FieldScope` all
15
+ /// inherit from this class.
16
+ ///
17
+ /// Note:
18
+ /// `QueryEngineBase` is a sub-part of the `QueryEngine`, and so is generally referred to
19
+ /// simply as the `QueryEngine` as a whole. This is also the case for <see>ModelScope</see>,
20
+ /// and <see>FieldScope</see>, which also make up the `QueryEngine` as sub-parts,
21
+ /// and so are also often referred to simply as "the query engine".
11
22
  class QueryEngineBase extends ProxyClass {
23
+ /// Used to generate unique "operation ids"
24
+ /// for the query operation stack.
25
+ ///
26
+ /// Return: number
27
+ /// A unique id.
12
28
  static generateID() {
13
29
  return uuid++;
14
30
  }
15
31
 
32
+ /// Check if the provided `value` is an "operation context".
33
+ ///
34
+ /// The query engine works by creating an "operation stack",
35
+ /// that is an array of "operation contexts". Any time an
36
+ /// operation is carried out on the query engine, such as
37
+ /// selecting a new model for example `new QueryEngine({ connection }).Model('User')`
38
+ /// then an "operation" will be pushed onto the operation stack.
39
+ /// Each operation on the stack sets its `prototype` to the previous
40
+ /// operation on the stack using `Object.create`. This means that
41
+ /// all "operation contexts" on the stack also include all attributes
42
+ /// from all previous operations on the stack via the operation
43
+ /// `prototype`.
44
+ ///
45
+ /// Use this method to check if any object is a valid "operation context"
46
+ /// from the query engine.
47
+ ///
48
+ /// Arguments:
49
+ /// value: object
50
+ /// The value to check.
51
+ ///
52
+ /// Return: boolean
53
+ /// Return `true` if the provided `value` is a query engine "operation context",
54
+ /// or `false` otherwise.
16
55
  static isQueryOperationContext(value) {
17
56
  return !!(value && value.isQueryOperationContext);
18
57
  }
19
58
 
59
+ /// Check to see if the provided value is
60
+ /// an *instance* of a Mythix ORM <see>QueryEngineBase</see>.
61
+ /// It will return `true` if the provided value is an `instanceof`
62
+ /// <see>QueryEngineBase</see>, if the value's `constructor`
63
+ /// property has a truthy `_isMythixQueryEngine` property
64
+ /// (`value.constructor._isMythixQueryEngine`), or if the
65
+ /// provided value has a `getOperationContext` method.
66
+ ///
67
+ /// Return: boolean
68
+ ///
69
+ /// Arguments:
70
+ /// value: any
71
+ /// Value to check
20
72
  static isQuery(value) {
21
73
  if (!value)
22
74
  return false;
@@ -24,7 +76,7 @@ class QueryEngineBase extends ProxyClass {
24
76
  if (value instanceof QueryEngineBase)
25
77
  return true;
26
78
 
27
- if (value._isQueryEngine)
79
+ if (value._isMythixQueryEngine)
28
80
  return true;
29
81
 
30
82
  if (typeof value.getOperationContext === 'function')
@@ -33,16 +85,64 @@ class QueryEngineBase extends ProxyClass {
33
85
  return false;
34
86
  }
35
87
 
36
- static queryOperationInfo(queryContext) {
88
+ /// Get information about the query.
89
+ ///
90
+ /// This method will return an object
91
+ /// containing certain keys that will
92
+ /// report on the status of the query.
93
+ ///
94
+ /// Interface:
95
+ /// interface QueryInfo {
96
+ /// hasDistinct: boolean;
97
+ /// hasOrder: boolean;
98
+ /// hasProjection: boolean;
99
+ /// hasGroupBy: boolean;
100
+ /// hasHaving: boolean;
101
+ /// hasCondition: boolean;
102
+ /// hasTableJoins: boolean;
103
+ /// hasField: boolean;
104
+ /// hasModel: boolean;
105
+ /// }
106
+ ///
107
+ /// Arguments:
108
+ /// query: <see>QueryEngine</see>
109
+ /// The query to get information on.
110
+ ///
111
+ /// Return: QueryInfo
112
+ /// Return information relating to the query.
113
+ static getQueryOperationInfo(query) {
37
114
  let contextParams = {
38
- hasCondition: false,
39
- hasField: false,
40
- hasModel: false,
115
+ hasDistinct: false,
116
+ hasOrder: false,
117
+ hasProjection: false,
118
+ hasGroupBy: false,
119
+ hasHaving: false,
120
+ hasCondition: false,
121
+ hasTableJoins: false,
122
+ hasField: false,
123
+ hasModel: false,
41
124
  };
42
125
 
43
- if (!queryContext)
126
+ if (!query)
44
127
  return contextParams;
45
128
 
129
+ let queryContext = query.getOperationContext();
130
+
131
+ if (queryContext.distinct)
132
+ contextParams.hasDistinct = true;
133
+
134
+ if (queryContext.order)
135
+ contextParams.hasOrder = true;
136
+
137
+ if (queryContext.projection)
138
+ contextParams.hasProjection = true;
139
+
140
+ if (queryContext.groupBy)
141
+ contextParams.hasGroupBy = true;
142
+
143
+ if (queryContext.having)
144
+ contextParams.hasHaving = true;
145
+
46
146
  if (queryContext.condition)
47
147
  contextParams.hasCondition = true;
48
148
 
@@ -52,21 +152,126 @@ class QueryEngineBase extends ProxyClass {
52
152
  if (queryContext.Model)
53
153
  contextParams.hasModel = true;
54
154
 
155
+ contextParams.hasTableJoins = query.queryHasJoins();
156
+
55
157
  return contextParams;
56
158
  }
57
159
 
160
+ /// Get the `ModelScope` class that should be used
161
+ /// for the query. By default this will be <see>ModelScope</see>.
162
+ ///
163
+ /// This is provided so that the end-user can overload the
164
+ /// query engine, adding custom functionality. You could for
165
+ /// example inherit from the default <see>ModelScope</see> class
166
+ /// and add extra functionality to the query engine. To do so,
167
+ /// you would overload this method on your own custom implementation
168
+ /// of `QueryEngine`, and would return your own custom `ModelScope`
169
+ /// class for use in the query engine.
170
+ ///
171
+ /// A "model scope" is a sub-scope of the `QueryEngine` that defines
172
+ /// "model level operations". A model scope is returned by the query
173
+ /// engine as soon as a model is accessed, i.e. `User.where` would
174
+ /// return a `ModelScope` relating to the `User` model. A conditional
175
+ /// operation, such as `EQ` will also return a `ModelScope`... ready
176
+ /// for the next field to be specified. Scopes are used in the Mythix ORM
177
+ /// query engine so that you can't do anything funky... such as `User.where.EQ('value')`...
178
+ /// how can a model be equal to a value? It can't... so scopes are used
179
+ /// in the engine to ensure you can't do anything silly. This scope defines
180
+ /// all operations that can be applied to models.
181
+ ///
182
+ /// Return: class inherits <see>ModelScope</see>
183
+ /// The class to use for "model scopes".
58
184
  getModelScopeClass() {
59
185
  return this.getQueryEngineScope().getModelScopeClass();
60
186
  }
61
187
 
188
+ /// Get the `FieldScope` class that should be used
189
+ /// for the query. By default this will be <see>FieldScope</see>.
190
+ ///
191
+ /// This is provided so that the end-user can overload the
192
+ /// query engine, adding custom functionality. You could for
193
+ /// example inherit from the default <see>FieldScope</see> class
194
+ /// and add extra functionality to the query engine. To do so,
195
+ /// you would overload this method on your own custom implementation
196
+ /// of `QueryEngine`, and would return your own custom `FieldScope`
197
+ /// class for use in the query engine.
198
+ ///
199
+ /// A "field scope" is a sub-scope of the `QueryEngine` that defines
200
+ /// "field level operations". A field scope is returned by the query
201
+ /// engine as soon as a field is accessed, i.e. `User.where.id` would
202
+ /// return a `FieldScope` relating to the `User` model (the `id` field).
203
+ /// Scopes are used in the Mythix ORM query engine so that you can't do
204
+ /// anything funky... such as `User.where.EQ('value')`...
205
+ /// how can a model be equal to a value? It can't... so scopes are used
206
+ /// in the engine to ensure you can't do anything silly. This scope defines
207
+ /// all operations that can be applied to model fields.
208
+ ///
209
+ /// Return: class inherits <see>FieldScope</see>
210
+ /// The class to use for "field scopes".
62
211
  getFieldScopeClass() {
63
212
  return this.getQueryEngineScope().getFieldScopeClass();
64
213
  }
65
214
 
215
+ /// Get the `QueryEngine` class that should be used
216
+ /// for the query. By default this will be <see>QueryEngine</see>.
217
+ ///
218
+ /// This is provided so that the end-user can overload the
219
+ /// query engine, adding custom functionality. You could for
220
+ /// example inherit from the default <see>QueryEngine</see> class
221
+ /// and add extra functionality to the query engine. To do so,
222
+ /// you would overload this method on your own custom implementation
223
+ /// of `QueryEngine`, and would return your own custom `QueryEngine`
224
+ /// class for use in the query engine.
225
+ ///
226
+ /// A "query scope" is the "root scope" of a `QueryEngine` that defines
227
+ /// "root level operations". A root scope is returned by the query
228
+ /// engine as soon as the query is created, i.e. `new QueryEngine({ connection })` would
229
+ /// return a `QueryEngine`. Scopes are used in the Mythix ORM query engine so that
230
+ /// you can't do anything funky... such as `User.where.EQ('value')`...
231
+ /// how can a model be equal to a value? It can't... so scopes are used
232
+ /// in the engine to ensure you can't do anything silly. This scope defines
233
+ /// all operations that can be applied to the "root query", such as fetching
234
+ /// the operation stack, the operation context, mapping or filtering the query, etc...
235
+ ///
236
+ /// Return: class inherits <see>QueryEngine</see>
237
+ /// The class to use for "root scopes". By default this method
238
+ /// will return the class used to initially create the query engine.
239
+ /// So for example if you create the query with your own custom class,
240
+ /// such as `new MyCustomQueryEngine({ connection })`, then this will
241
+ /// automatically return your class for you (`MyCustomQueryEngine` in this example).
66
242
  getQueryEngineScopeClass() {
67
243
  return this.getQueryEngineScope().constructor;
68
244
  }
69
245
 
246
+ /// This "stacks" an incoming "operation context" on top
247
+ /// the previous context in the "operation stack". It will
248
+ /// also reset the `value` of the context to `undefined`, and
249
+ /// will generate a new `contextID` for this new context.
250
+ ///
251
+ /// Note:
252
+ /// You probably should not use this method directly unless you know
253
+ /// exactly what you are doing. Instead you should use the <see>QueryEngineBase._pushOperationOntoStack</see>,
254
+ /// <see>QueryEngineBase._newQueryEngineScope</see>, <see>QueryEngineBase._newModelScope</see>, or
255
+ /// <see>QueryEngineBase._newFieldScope</see> to push operations onto the stack.
256
+ ///
257
+ /// Arguments:
258
+ /// context: object
259
+ /// The previous (top-most) "operation context" on top the "operation stack".
260
+ /// name?: string
261
+ /// The name for this operation context. This is only ever used when the "scope"
262
+ /// changes by using one of <see>QueryEngineBase._newQueryEngineScope</see>,
263
+ /// <see>QueryEngineBase._newModelScope</see>, or <see>QueryEngineBase._newFieldScope</see>.
264
+ /// These methods will set a "scope name" so that it can later be fetched using
265
+ /// <see>QueryEngineBase._fetchScope</see>. Scope names will only ever be one of
266
+ /// `'queryEngine'`, `'model'`, or `'field'`.
267
+ /// ...args?: Array<object>
268
+ /// Other objects to merge into the context.
269
+ ///
270
+ /// Return: object
271
+ /// Return the new "operation context", with the `args` objects merged in. This new
272
+ /// context will have a `prototype` that is set to the `context` provided.
273
+ ///
274
+ /// See: QueryEngineBase._fetchScope
70
275
  _inheritContext(context, name, ...args) {
71
276
  let newContext = Object.assign(
72
277
  Object.create(context),
@@ -85,6 +290,35 @@ class QueryEngineBase extends ProxyClass {
85
290
  return newContext;
86
291
  }
87
292
 
293
+ /// Fetch a previous named scope.
294
+ ///
295
+ /// This is useful when you want to change scopes during query operations.
296
+ /// For example, if you just did a field-level operation inside a <see>FieldScope</see>,
297
+ /// such as `query.field.EQ('test')`, then you need to return a "model scope" so
298
+ /// the user can continue chaining operations. Do to so, you would call: `return this._fetchScope('model');`
299
+ /// which will fetch and return the *current* "model scope".
300
+ ///
301
+ /// If the specified scope is not found (as a previous scope in the "operation stack"),
302
+ /// then this method will simply return `this`. Because this isn't often desired, and could
303
+ /// be a problem, this method takes `scopeNames` as an array of scopes to "fall back" to
304
+ /// if the specified scope can not be found. For example, one could do: `return this._fetchScope('model', 'queryEngine');`
305
+ /// to request the `'model'` scope, but to fall-back to the `'queryEngine'` scope if there
306
+ /// is no `'model'` scope to return.
307
+ ///
308
+ /// Arguments:
309
+ /// ...scopeNames: Array<string>
310
+ /// Specify the scopes you wish to fetch, in order, from left-to-right. The first
311
+ /// scope found will be returned.
312
+ ///
313
+ /// Return: <see>QueryEngine</see> | <see>ModelScope</see> | <see>FieldScope</see>
314
+ /// Return the specified scope, the first found in the list of provided scopes. If
315
+ /// no scope specified is found, then return `this` instead.
316
+ ///
317
+ /// See: QueryEngineBase._newQueryEngineScope
318
+ ///
319
+ /// See: QueryEngineBase._newModelScope
320
+ ///
321
+ /// See: QueryEngineBase._newFieldScope
88
322
  _fetchScope(...scopeNames) {
89
323
  let context = this.getOperationContext();
90
324
 
@@ -110,19 +344,43 @@ class QueryEngineBase extends ProxyClass {
110
344
  return this;
111
345
  }
112
346
 
347
+ /// Create a new "root scope" (`QueryEngine` scope), push it onto the
348
+ /// "operation stack", and return the newly created scope.
349
+ ///
350
+ /// Return: <see>QueryEngine</see>
351
+ /// Return a new "root scope" (`QueryEngine` instance), after pushing
352
+ /// it onto the internal "operation stack". This also "names" the operation
353
+ /// on the stack, so a call to `this._fetchScope('queryEngine')` after this
354
+ /// will return this newly created scope.
355
+ ///
356
+ /// See: QueryEngineBase._fetchScope
113
357
  _newQueryEngineScope() {
114
358
  const QueryEngineClass = this.getQueryEngineClass();
115
- let context = this.currentContext;
359
+ let context = this.getOperationContext();
116
360
  let newContext = this._inheritContext(context, 'queryEngine');
117
361
  let newScope = new QueryEngineClass(newContext);
118
362
 
119
363
  return newScope;
120
364
  }
121
365
 
366
+ /// Create a new "model scope" (`ModelScope` scope), push it onto the
367
+ /// "operation stack", and return the newly created scope.
368
+ ///
369
+ /// Arguments:
370
+ /// Model: class <see>Model</see>
371
+ /// The model class for this "model scope".
372
+ ///
373
+ /// Return: <see>ModelScope</see>
374
+ /// Return a new "model scope" (`ModelScope` instance), after pushing
375
+ /// it onto the internal "operation stack". This also "names" the operation
376
+ /// on the stack, so a call to `this._fetchScope('model')` after this
377
+ /// will return this newly created scope.
378
+ ///
379
+ /// See: QueryEngineBase._fetchScope
122
380
  _newModelScope(Model) {
123
381
  let ModelScopeClass = this.getModelScopeClass();
124
382
  let modelName = Model.getModelName();
125
- let context = this.currentContext;
383
+ let context = this.getOperationContext();
126
384
  let extra = {};
127
385
  let isFirst = !context.rootModel;
128
386
  let connection = this.getConnection();
@@ -162,9 +420,23 @@ class QueryEngineBase extends ProxyClass {
162
420
  return newScope;
163
421
  }
164
422
 
423
+ /// Create a new "field scope" (`FieldScope` scope), push it onto the
424
+ /// "operation stack", and return the newly created scope.
425
+ ///
426
+ /// Arguments:
427
+ /// Field: <see>Field</see>
428
+ /// The field for this "field scope".
429
+ ///
430
+ /// Return: <see>FieldScope</see>
431
+ /// Return a new "field scope" (`FieldScope` instance), after pushing
432
+ /// it onto the internal "operation stack". This also "names" the operation
433
+ /// on the stack, so a call to `this._fetchScope('field')` after this
434
+ /// will return this newly created scope.
435
+ ///
436
+ /// See: QueryEngineBase._fetchScope
165
437
  _newFieldScope(Field) {
166
438
  let fieldName = Field.fieldName;
167
- let context = this.currentContext;
439
+ let context = this.getOperationContext();
168
440
  let FieldScopeClass = this.getFieldScopeClass();
169
441
  let extra = {};
170
442
 
@@ -186,19 +458,47 @@ class QueryEngineBase extends ProxyClass {
186
458
  return newScope;
187
459
  }
188
460
 
189
- constructor(context) {
461
+ /// Construct a new QueryEngine instance.
462
+ ///
463
+ /// Arguments:
464
+ /// context: object
465
+ /// The "root" "operation context" for the query. This is required to contain a
466
+ /// `connection` property, with a valid <see>Connection</see> instance as a value. Besides that, you can apply
467
+ /// any properties you want here, and they will be available on all "operation contexts".
468
+ constructor(_context) {
190
469
  super();
191
470
 
192
- this._isQueryEngine = true;
471
+ let context = Object.create(_context || null);
472
+
473
+ Object.defineProperties(this, {
474
+ '_isMythixQueryEngine': {
475
+ writable: true,
476
+ enumerable: false,
477
+ configurable: true,
478
+ value: true,
479
+ },
480
+ });
193
481
 
194
482
  if (!context)
195
483
  throw new TypeError('QueryEngineBase::constructor: "context" required.');
196
484
 
197
485
  if (!context.rootContext) {
198
- context._isQueryEngine = true;
199
- context.rootContext = context;
486
+ context._isMythixQueryEngine = true;
487
+ context.rootContext = Object.assign(
488
+ {},
489
+ context,
490
+ {
491
+ contextID: QueryEngineBase.generateID(),
492
+ },
493
+ );
494
+
495
+ context = Object.create(context);
200
496
  }
201
497
 
498
+ context.isQueryOperationContext = true;
499
+ if (!context.contextID)
500
+ context.contextID = QueryEngineBase.generateID();
501
+
202
502
  if (!context.operationStack)
203
503
  context.operationStack = [];
204
504
 
@@ -230,18 +530,99 @@ class QueryEngineBase extends ProxyClass {
230
530
  });
231
531
  }
232
532
 
533
+ /// Fetch the current (top most) context id of the query.
534
+ ///
535
+ /// Each "operation context" gets its own unique ID. This
536
+ /// is primarily for caching and comparison operations.
537
+ /// Since a new context id is generated for each operation
538
+ /// of the query, one can detect if two queries are identical
539
+ /// simply by comparing their ids. This id can also be used for
540
+ /// cache... since the same id always equates to the exact same
541
+ /// query.
542
+ ///
543
+ /// Return: number
544
+ /// The unique "operation context" id for this query. This will always
545
+ /// be the id assigned to the top-most "operation context" of the "operation stack".
233
546
  getQueryID() {
234
- return this.currentContext.contextID;
235
- }
236
-
547
+ return this.getOperationContext().contextID;
548
+ }
549
+
550
+ /// Return the top-most "operation context" for the query.
551
+ ///
552
+ /// The internal operation stack looks like this `[ root context, <- context1, <- context2, <- context3, ... ]`
553
+ ///
554
+ /// Operation contexts are simple objects defining the query
555
+ /// operations. Each new context added to the query is pushed
556
+ /// on top the "operation stack" internal to the query. Each
557
+ /// "operation context" also has its `prototype` set to the
558
+ /// previous "frame" in the stack. This means from the top-most
559
+ /// context, you can access attributes from the bottom-most
560
+ /// context--assuming the property you are trying to access
561
+ /// hasn't also be re-set in a higher-level "frame". For example,
562
+ /// you could access a `distinct`, `order`, or `projections`
563
+ /// attribute from the top-most operation context, which will
564
+ /// always be the "most current" value for the operation you
565
+ /// are requesting data for.
566
+ ///
567
+ /// Operation contexts always have at least the following properties:
568
+ /// `operator`, `value`, and `queryProp` (used for replaying query operations).
569
+ /// Each context might also have custom properties... for example, a `DISTINCT`
570
+ /// operation will also have a custom `distinct` property it sets that is
571
+ /// the distinct literal itself.
572
+ ///
573
+ /// Return: object
574
+ /// The top-most "operation context" on the "operation stack".
237
575
  getOperationContext() {
238
576
  return this.currentContext;
239
577
  }
240
578
 
579
+ /// Return the entire "operation stack" for the query.
580
+ ///
581
+ /// The internal operation stack looks like this `[ root context, <- context1, <- context2, <- context3, ... ]`
582
+ ///
583
+ /// Each "frame" on the stack is itself an "operation context". Each
584
+ /// "frame" defines an operation for the query, for example a `MODEL`,
585
+ /// `EQ`, or `DISTINCT` operation. Essentially a Mythix ORM query is
586
+ /// just an array of operations internally--in order. When the query
587
+ /// is being used to interface with the underlying database, the "operation stack"
588
+ /// is walked, and a part generated for each operation in the stack. For example,
589
+ /// a query such as `User.where.id.EQ(1)` would contain the following operations:
590
+ /// `[ { MODEL = User } <- { FIELD = id } <- { operator = EQ, value = 1 } ]`.
591
+ ///
592
+ /// Note:
593
+ /// You can dynamically alter the operation stack of a query by using one of
594
+ /// <see>QueryEngineBase.filter</see>, or <see>QueryEngineBase.map</see>. Since
595
+ /// queries are essentially just arrays of operations, they can be treated much
596
+ /// like arrays.
597
+ ///
598
+ /// Return: Array<context>
599
+ /// The entire "operation stack" for the query. This **is not** a copy of the stack,
600
+ /// but the entire stack directly... so **do not** mutate this value unless you know
601
+ /// exactly what you are doing.
241
602
  getOperationStack() {
242
- return this.currentContext.operationStack;
243
- }
244
-
603
+ return this.getOperationContext().operationStack;
604
+ }
605
+
606
+ /// Check if the very last operation in the internal "operation stack"
607
+ /// is a "control" operation. "control" operations are operations that
608
+ /// change how the query behaves, and include `LIMIT`, `OFFSET`, `ORDER`,
609
+ /// `GROUP_BY`, `HAVING`, and `PROJECT`.
610
+ ///
611
+ /// Return: boolean
612
+ /// Return `true` if the very last operation on the "operation stack"
613
+ /// is categorized as a `control` level operation.
614
+ ///
615
+ /// See: ModelScope.LIMIT
616
+ ///
617
+ /// See: ModelScope.OFFSET
618
+ ///
619
+ /// See: ModelScope.ORDER
620
+ ///
621
+ /// See: ModelScope.GROUP_BY
622
+ ///
623
+ /// See: ModelScope.HAVING
624
+ ///
625
+ /// See: ModelScope.PROJECT
245
626
  isLastOperationControl() {
246
627
  let queryParts = this.getOperationStack();
247
628
  let lastPart = queryParts[queryParts.length - 1];
@@ -252,6 +633,13 @@ class QueryEngineBase extends ProxyClass {
252
633
  return false;
253
634
  }
254
635
 
636
+ /// Check if the very last operation in the internal "operation stack"
637
+ /// is a "condition" operation. "condition" operations are operations that
638
+ /// are conditions for the query, for example `EQ`, `GT`, `LT`, etc...
639
+ ///
640
+ /// Return: boolean
641
+ /// Return `true` if the very last operation on the "operation stack"
642
+ /// is categorized as a `condition` level operation.
255
643
  isLastOperationCondition() {
256
644
  let queryParts = this.getOperationStack();
257
645
  let lastPart = queryParts[queryParts.length - 1];
@@ -262,11 +650,22 @@ class QueryEngineBase extends ProxyClass {
262
650
  return false;
263
651
  }
264
652
 
653
+ /// Check if the query has any conditions.
654
+ ///
655
+ /// A query might not have conditions... if for example
656
+ /// it is defining a table-join.
657
+ ///
658
+ /// Return: boolean
659
+ /// Return `true` if the query has any conditions, i.e. `EQ`, or `GT`.
265
660
  queryHasConditions() {
266
661
  let context = this.getOperationContext();
267
662
  return context.hasCondition;
268
663
  }
269
664
 
665
+ /// Check if the query has any table joins.
666
+ ///
667
+ /// Return: boolean
668
+ /// Return `true` if the query is joining on tables, or `false` otherwise.
270
669
  queryHasJoins() {
271
670
  let queryParts = this.getOperationStack();
272
671
  for (let i = 0, il = queryParts.length; i < il; i++) {
@@ -278,6 +677,11 @@ class QueryEngineBase extends ProxyClass {
278
677
  return false;
279
678
  }
280
679
 
680
+ /// Debug a query.
681
+ ///
682
+ /// This will call `console.log` for every operation
683
+ /// on the internal "operation stack", allowing you to
684
+ /// debug each query part--in order.
281
685
  logQueryOperations() {
282
686
  let query = this.getOperationStack();
283
687
  for (let i = 0, il = query.length; i < il; i++) {
@@ -293,6 +697,20 @@ class QueryEngineBase extends ProxyClass {
293
697
  }
294
698
  }
295
699
 
700
+ /// Push a new operation onto the internal "operation stack"
701
+ /// for the query.
702
+ ///
703
+ /// Arguments:
704
+ /// frame: object
705
+ /// The new "frame" to push onto the stack. This should just be a simple
706
+ /// object containing the correct properties for the operation being added.
707
+ /// This method will then ensure your new object is added as an "operation context",
708
+ /// setting the `prototype` to the previous "operation context" (frame) in
709
+ /// the stack.
710
+ /// context?: object
711
+ /// The context to set as the `prototype` for this new frame. If not provided,
712
+ /// then this will default to the top-most "operation context" already on
713
+ /// top of the internal "operation stack".
296
714
  _pushOperationOntoStack(queryPart, _context) {
297
715
  let context = _context || this.getOperationContext();
298
716
  let operationStack = context.operationStack;
@@ -306,16 +724,28 @@ class QueryEngineBase extends ProxyClass {
306
724
  );
307
725
  }
308
726
 
727
+ /// Get the `connection` supplied to the query
728
+ /// engine when it was first created.
729
+ ///
730
+ /// Return: <see>Connection</see>
731
+ /// The `connection` that was supplied to the query engine when it was created.
309
732
  getConnection() {
310
- return this.currentContext.connection;
733
+ return this.getOperationContext().connection;
311
734
  }
312
735
 
736
+ /// Get a model class by name.
737
+ ///
738
+ /// This will fetch the `connection` using <see>QueryEngineBase.getConnection</see>,
739
+ /// and then will request the model by name from the connection itself.
740
+ ///
741
+ /// Return: class <see>Model</see>
742
+ /// Return the named model class.
313
743
  getModel(modelName) {
314
744
  let connection = this.getConnection();
315
745
  let Model = (connection && connection.getModel(modelName));
316
746
 
317
747
  if (!Model) {
318
- let models = this.currentContext.models;
748
+ let models = this.getOperationContext().models;
319
749
  if (models)
320
750
  Model = models[modelName];
321
751
  }
@@ -323,18 +753,57 @@ class QueryEngineBase extends ProxyClass {
323
753
  return Model;
324
754
  }
325
755
 
756
+ /// Fetch the top-most "root scope" or "queryEngine scope".
757
+ ///
758
+ /// Return: <see>QueryEngine</see>
759
+ /// The top-most "query engine" scope.
326
760
  getQueryEngineScope() {
327
- return this.currentContext.queryEngineScope;
328
- }
329
-
761
+ return this.getOperationContext().queryEngineScope;
762
+ }
763
+
764
+ /// Get the `QueryEngine` class that is being used for the query.
765
+ ///
766
+ /// This works by calling <see>QueryEngineBase.getQueryEngineScope</see>
767
+ /// and returning the `constructor` property used by this scope. The
768
+ /// `constructor` property will be the `QueryEngine` class itself.
769
+ ///
770
+ /// Return: class <see>QueryEngine</see>
771
+ /// The custom `QueryEngine` class being used for the query, or
772
+ /// the `QueryEngine` class Mythix ORM uses (the default).
330
773
  getQueryEngineClass() {
331
- return this.currentContext.queryEngineScope.constructor;
774
+ return this.getOperationContext().queryEngineScope.constructor;
332
775
  }
333
776
 
777
+ /// Clone this query.
778
+ ///
779
+ /// Return: <see>ModelScope</see> | <see>QueryEngine</see>
780
+ /// The query, cloned. By default this will return the
781
+ /// most recent "model scope" from the cloned query... if one is found.
782
+
334
783
  clone() {
335
784
  return this.map((part) => part)._fetchScope('model');
336
785
  }
337
786
 
787
+ /// Clone this query, filtering the internal "operation stack"
788
+ /// while doing so. This allows the user to entirely alter the
789
+ /// nature of the query. You can filter out any operations you
790
+ /// want to filter out. For example, you could choose to filter
791
+ /// out all `ORDER` operations to ensure a query never has any
792
+ /// order specified.
793
+ ///
794
+ /// The signature for the provided `callback` nearly matches the
795
+ /// signature for Javascript's `Array.prototype.filter` method:
796
+ /// `callback(operationContext: object, index: number, operationStack: Array<object>, query: QueryEngine)`
797
+ ///
798
+ /// Arguments:
799
+ /// callback: (operationContext: object, index: number, parts: Array<object>, query: QueryEngine) => boolean;
800
+ /// The callback to use for filtering. If this callback returns a "falsy" value, then the operation
801
+ /// will be filtered out.
802
+ ///
803
+ /// Return: <see>FieldScope</see> | <see>ModelScope</see> | <see>QueryEngine</see>
804
+ /// Return the last (top-most) scope of the original query... unless it was filtered out.
805
+ /// The returned query will be identical to the original query... minus
806
+ /// any operation that was filtered out.
338
807
  filter(callback) {
339
808
  const Klass = this.getQueryEngineScopeClass();
340
809
  let context = this.getOperationContext();
@@ -359,6 +828,28 @@ class QueryEngineBase extends ProxyClass {
359
828
  return query;
360
829
  }
361
830
 
831
+ /// Clone this query, mapping the internal "operation stack"
832
+ /// while doing so. This allows the user to entirely alter the
833
+ /// nature of the query. You can map any operations you
834
+ /// want to alter. For example, you could choose to alter
835
+ /// all `ORDER` operations, forcing a different order for the query.
836
+ ///
837
+ /// The signature for the provided `callback` nearly matches the
838
+ /// signature for Javascript's `Array.prototype.map` method:
839
+ /// `callback(operationContext: object, index: number, operationStack: Array<object>, query: QueryEngine)`
840
+ /// The `operationContext` here is a copy of the original operation context, so it has
841
+ /// its `prototype` disconnected from the context chain, and you can feel free to modify it
842
+ /// (without effecting the original query).
843
+ ///
844
+ /// Arguments:
845
+ /// callback: (operationContext: object, index: number, parts: Array<object>, query: QueryEngine) => object;
846
+ /// The callback used to map each "operation context". If the return value is "falsy", or a non-object, then
847
+ /// it will be discarded (narrowing the "operation stack" of the final mapped query).
848
+ ///
849
+ /// Return: <see>FieldScope</see> | <see>ModelScope</see> | <see>QueryEngine</see>
850
+ /// Return the last (top-most) scope of the original query.
851
+ /// The returned query will be identical to the original query... excepting
852
+ /// any operation that was modified.
362
853
  map(callback) {
363
854
  const Klass = this.getQueryEngineScopeClass();
364
855
  let context = this.getOperationContext();
@@ -384,6 +875,33 @@ class QueryEngineBase extends ProxyClass {
384
875
  return query;
385
876
  }
386
877
 
878
+ /// This method recursively walks the query, calling the provided
879
+ /// `callback` for every sub-query encountered. The provided `callback`
880
+ /// will never be called with `this` query (the root query being walked).
881
+ ///
882
+ /// The callback signature is `callback(subQuery: QueryEngine, parentOperationContext: object, contextKey: string, depth: number): undefined`
883
+ /// where the `subQuery` is the query found, the `parentOperationContext` is the parent "operation context" that the sub-query was found on,
884
+ /// `contextKey` is the key the sub-query was found on (usually `'value'`) inside the `parentOperationContext`, and `depth` is the
885
+ /// depth at which the sub-query was found, which will always be greater than or equal to `1`
886
+ /// (`0` is reserved for the root query itself).
887
+ ///
888
+ /// Note:
889
+ /// Though you wouldn't generally modify the query while walking it
890
+ /// (for that you should instead use <see>Connection.finalizeQuery</see>)
891
+ /// it is possible by setting a new sub-query on the `contextKey` of
892
+ /// the `parentOperationContext`. i.e. `parentOperationContext[contextKey] = newSubQuery`.
893
+ ///
894
+ /// Arguments:
895
+ /// callback: (subQuery: QueryEngine, parentOperationContext: object, contextKey: string, depth: number) => void;
896
+ /// The callback that will be called for each sub-query encountered.
897
+ /// contextKeys?: Array<string> = [ 'value' ]
898
+ /// The "operation context" keys to check for sub-queries. This will almost always be "value"
899
+ /// for each operation... however, if you add custom operations that store sub-queries on
900
+ /// other operation context property names, then you would want to supply the names of those
901
+ /// properties here.
902
+ ///
903
+ /// Return: undefined
904
+ /// Nothing is returned from this method.
387
905
  walk(callback, _checkContextKeys) {
388
906
  const walkQueries = (query, parent, contextKey, depth) => {
389
907
  let parts = query.getOperationStack();
@@ -411,7 +929,24 @@ class QueryEngineBase extends ProxyClass {
411
929
  walkQueries(this, null, null, 0);
412
930
  }
413
931
 
414
- getAllModelsUsedInQuery() {
932
+ /// Fetch all models used in the query.
933
+ ///
934
+ /// By default, this will return all unique models
935
+ /// used across the root query, including models used
936
+ /// for table-joining.
937
+ /// You can however pass the `options` `{ subQueries: true }`
938
+ /// to return all models used in the query, including those
939
+ /// used in sub-queries.
940
+ ///
941
+ /// Arguments:
942
+ /// options?: object
943
+ /// Options for the operation. The only option supported is `{ subQueries: true }`,
944
+ /// which if enabled, will request that this method also walk sub-queries.
945
+ ///
946
+ /// Return: Array<class <see>Model</see>>
947
+ /// An array of all model classes used in the query.
948
+ getAllModelsUsedInQuery(_options) {
949
+ let options = _options || {};
415
950
  let Models = new Set();
416
951
  let query = this.getOperationStack();
417
952
 
@@ -423,10 +958,13 @@ class QueryEngineBase extends ProxyClass {
423
958
  Models.add(Model);
424
959
  } else if (Object.prototype.hasOwnProperty.call(queryPart, 'condition') && queryPart.condition === true) {
425
960
  let operatorValue = queryPart.value;
426
- if (!QueryEngineBase.isQuery(operatorValue) || operatorValue.queryHasConditions())
961
+ if (!QueryEngineBase.isQuery(operatorValue))
962
+ continue;
963
+
964
+ if (options.subQueries !== true && operatorValue.queryHasConditions())
427
965
  continue;
428
966
 
429
- let SubModels = operatorValue.getAllModelsUsedInQuery();
967
+ let SubModels = operatorValue.getAllModelsUsedInQuery(options);
430
968
  for (let j = 0, jl = SubModels.length; j < jl; j++) {
431
969
  let Model = SubModels[j];
432
970
  Models.add(Model);
@@ -438,8 +976,30 @@ class QueryEngineBase extends ProxyClass {
438
976
  return allModels;
439
977
  }
440
978
 
441
- isModelUsedInQuery(Model) {
442
- let allModels = this.getAllModelsUsedInQuery();
979
+ /// Check if the model specified is used in the query.
980
+ ///
981
+ /// By default, this will check if the provided `Model`
982
+ /// is used in the root query, or any table-joins in the
983
+ /// root query. You can optionally pass the `options`
984
+ /// `{ subQueries: true }` to also check if the provided
985
+ /// `Model` is used in any sub-queries.
986
+ ///
987
+ /// Note:
988
+ /// This internally calls <see>QueryEngineBase.getAllModelsUsedInQuery</see>
989
+ /// an then checks for the existence of the provided `Model` in the result.
990
+ ///
991
+ /// Arguments:
992
+ /// Model: class <see>Model</see>
993
+ /// The model class to check for.
994
+ /// options?: object
995
+ /// Options for the operation. The only option supported is `{ subQueries: true }`,
996
+ /// which if enabled, will request that this method also walk sub-queries.
997
+ ///
998
+ /// Return: boolean
999
+ /// Return `true` if the specified `Model` is used in the query, or
1000
+ /// any table-joins... `false` otherwise.
1001
+ isModelUsedInQuery(Model, options) {
1002
+ let allModels = this.getAllModelsUsedInQuery(options);
443
1003
  return (allModels.indexOf(Model) >= 0);
444
1004
  }
445
1005
  }