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.
@@ -7,30 +7,98 @@ const ModelScope = require('./model-scope');
7
7
  const FieldScope = require('./field-scope');
8
8
  const Utils = require('../utils');
9
9
 
10
+ /// QueryEngine is the "root level" of a query.
11
+ /// It manages things like `MERGE`, `unscoped`,
12
+ /// and all the database interfaces, such as `all`,
13
+ /// `first`, `pluck`, etc...
14
+ ///
15
+ /// Being a Proxy, it will "listen" for key access,
16
+ /// and lookup models if there is a key access where
17
+ /// the key name starts with an upper-case letter. In this
18
+ /// case, it will check the `connection` it owns to see
19
+ /// if such a model (by name) is registered with the
20
+ /// connection. If there is, then it will use the model
21
+ /// found to push a <see>ModelScope</see> onto the "operation stack",
22
+ /// and then return that to the user. Take the following example:
23
+ /// ```javascript
24
+ /// let queryRoot = new QueryEngine({ connection });
25
+ /// let userModelScope = queryRoot.User;
26
+ /// ```
27
+ /// When we attempt to access the key `'User'` on
28
+ /// the QueryEngine scope, we will find that no such key
29
+ /// exists on the `QueryEngine` class. Now that no such property is found
30
+ /// on the `QueryEngine` class, the <see>ProxyClass</see> will call the method
31
+ /// `MISSING` on the `QueryEngine`, and this `MISSING` method
32
+ /// will check the `connection` to see if there is a model
33
+ /// named `'User'`. The `connection` finds this model, and
34
+ /// returns it. The `QueryEngine` then takes this model class,
35
+ /// and uses it to create and return a <see>ModelScope</see>
36
+ /// using <see>QueryEngineBase._newModelScope</see>. Now
37
+ /// that we have a <see>ModelScope</see>, we can continue
38
+ /// chaining by looking up a field `queryRoot.User.id`, and
39
+ /// the process repeats on `ModelScope`, but this time looking
40
+ /// for a field from the `connection` instead of a model. The
41
+ /// model owning the field is already known, because we already
42
+ /// have a `Model` class on our "operation stack" for the query.
43
+ /// So the field `id` is looked up on the `User` model, and if
44
+ /// found, returned as a <see>FieldScope</see> using <see>QueryEngineBase._newFieldScope</see>.
45
+ ///
46
+ /// See: QueryEngineBase
47
+ ///
48
+ /// See: ModelScope
49
+ ///
50
+ /// See: FieldScope
51
+ ///
52
+ /// Note:
53
+ /// `QueryEngine` is the primary part "the query engine", and so is generally referred to
54
+ /// simply as the `QueryEngine` as a whole. This is also the case for <see>ModelScope</see>,
55
+ /// and <see>FieldScope</see>, and <see>QueryEngineBase</see> which also make up the `QueryEngine`
56
+ /// as sub-parts, and so are also often referred to simply as "the query engine".
10
57
  class QueryEngine extends QueryEngineBase {
58
+ /// Get the `ModelScope` class for the
59
+ /// query engine.
60
+ ///
61
+ /// See: QueryEngineBase.getModelScopeClass
11
62
  getModelScopeClass() {
12
63
  return ModelScope;
13
64
  }
14
65
 
66
+ /// Get the `FieldScope` class for the
67
+ /// query engine.
68
+ ///
69
+ /// See: QueryEngineBase.getFieldScopeClass
15
70
  getFieldScopeClass() {
16
71
  return FieldScope;
17
72
  }
18
73
 
19
- constructor(_context) {
20
- let context = Object.assign(
21
- Object.create(_context || {}),
22
- {
23
- currentScopeName: 'queryEngine',
24
- isQueryOperationContext: true,
25
- contextID: QueryEngineBase.generateID(),
26
- },
27
- );
28
-
74
+ /// Construct a new QueryEngine instance.
75
+ ///
76
+ /// Arguments:
77
+ /// context: object
78
+ /// The "root" "operation context" for the query. This is required to contain a
79
+ /// `connection` property, with a valid <see>Connection</see> instance as a value. Besides that, you can apply
80
+ /// any properties you want here, and they will be available on all "operation contexts".
81
+ constructor(context) {
29
82
  super(context);
30
83
 
31
- context.queryEngineScope = this;
84
+ let operationContext = this.getOperationContext();
85
+ operationContext.currentScopeName = 'queryEngine';
86
+ operationContext.queryEngineScope = this;
32
87
  }
33
88
 
89
+ /// This method will directly fetch a model by
90
+ /// name and return a <see>ModelScope</see>.
91
+ ///
92
+ /// This can be useful when you have a name collision,
93
+ /// or when you just want to go the direct route to get
94
+ /// a model operation in place.
95
+ ///
96
+ /// Arguments:
97
+ /// modelName: string
98
+ /// The model name to fetch and use for the `ModelScope`.
99
+ ///
100
+ /// Return: <see>ModelScope</see>
101
+ /// A new `ModelScope` using the model found by the name provided.
34
102
  Model(modelName) {
35
103
  let model = this.getModel(modelName);
36
104
  if (!model)
@@ -39,19 +107,55 @@ class QueryEngine extends QueryEngineBase {
39
107
  return this._newModelScope(model);
40
108
  }
41
109
 
110
+ /// This method will reset the query "operation stack"
111
+ /// back to the root context--and, if possible, to the root
112
+ /// model scope and root model projection.
113
+ /// All other operations will be wiped, including
114
+ /// the any <see>Model.static defaultScope</see> that has been
115
+ /// applied. One would generally call this as the very
116
+ /// first thing on an query to ensure it has been reset
117
+ /// back to its primary parts *before* you would
118
+ /// apply any other operations. For example: `let users = await User.where.unscoped().lastName.EQ('Smith').all();`,
119
+ /// would "ignore" any <see>Model.static defaultScope</see> applied to
120
+ /// the query, including any default `ORDER` that `defaultScope`
121
+ /// applied. `User.where.lastName.EQ('Smith').unscoped()` would
122
+ /// be equivalent to `User.where` (assuming no <see>Model.static defaultScope</see>
123
+ /// is in-play).
124
+ ///
125
+ /// Return: <see>QueryEngine</see>
126
+ /// A new query, reset back to only its root context, or the root model
127
+ /// scope (if there is a root model available on the query). If there
128
+ /// is a root model available, then its projection will also be applied
129
+ /// as usual.
42
130
  unscoped() {
43
131
  let context = this.getOperationContext();
44
- let QueryEngineClass = this.constructor;
45
- let queryEngine = new QueryEngineClass({
46
- connection: this.getConnection(),
47
- });
132
+ let QueryEngineClass = this.getQueryEngineScopeClass();
133
+ let queryEngine = new QueryEngineClass(context.rootContext);
48
134
 
49
- if (context.rootModelName)
50
- queryEngine = queryEngine[context.rootModelName];
135
+ if (context.rootModel)
136
+ queryEngine = queryEngine._newModelScope(context.rootModel);
51
137
 
52
138
  return queryEngine;
53
139
  }
54
140
 
141
+ /// Stringify this query, using the underlying database
142
+ /// connection. This is similar to a `toSQL` method in other ORMs.
143
+ /// It isn't exactly that though... because for non-SQL
144
+ /// database drivers, it might stringify the query into
145
+ /// something that is non-SQL. By default the query will
146
+ /// be turned into a string that represents a `SELECT`
147
+ /// statement (or other type of fetch statement) for the
148
+ /// underlying database driver.
149
+ ///
150
+ /// Arguments:
151
+ /// ...args?: Array<any>
152
+ /// Any arguments you want to pass off to the underlying
153
+ /// <see>QueryGenerator.toConnectionString</see> method that
154
+ /// is called to stringify the query.
155
+ ///
156
+ /// Return: string
157
+ /// The query, turned into a "fetch" representation for the underlying
158
+ /// database driver. For SQL-based databases this would be a `SELECT` statement.
55
159
  toString(...args) {
56
160
  let connection = this.getConnection();
57
161
  let queryGenerator = connection.getQueryGenerator();
@@ -59,6 +163,62 @@ class QueryEngine extends QueryEngineBase {
59
163
  return queryGenerator.toConnectionString(this, ...args);
60
164
  }
61
165
 
166
+ /// Merge one query onto another one.
167
+ ///
168
+ /// This method will merge two queries together
169
+ /// by "replaying" the provided one onto `this`
170
+ /// one. For example:
171
+ /// ```javascript
172
+ /// let userQuery = User.where.id.EQ(1);
173
+ /// let roleQuery = Role.where.userID.EQ(User.where.id).name.EQ('admin');
174
+ /// let finalQuery = userQuery.AND.MERGE(roleQuery);
175
+ /// // finalQuery == User.where.id.EQ(1).AND.Role.where.userID.EQ(User.where.id).name.EQ('admin');
176
+ /// ```
177
+ ///
178
+ /// This is almost like a "concatenate" operation, but there are a few notable differences:
179
+ /// 1) The first "logical" operator encountered is always skipped
180
+ /// 2) `PROJECT` operations are skipped by default (but can be merged with the use of `options`)
181
+ /// 3) Any `GROUP_BY`, `ORDER`, or `PROJECT` are *merged* together, instead of replacing the previous operation.
182
+ ///
183
+ /// The first logical operator is skipped for a good reason. It is to allow one to `OR` merge
184
+ /// the query, or `AND` merge it instead. If we were to simply "concatenate" the stacks together,
185
+ /// then we might have contexts that look like (pseudo/concept code):
186
+ /// `[ User, AND, id, EQ(1) ].OR.MERGE([ Role, AND, userID, EQ(...), ... ])`.
187
+ /// The problem here should be obvious: The `OR` is (nearly) immediately followed by an `AND` in
188
+ /// the query we are merging with. If we didn't skip the first logical operator, then this would
189
+ /// nearly always be the case, especially since the `QueryEngine` turns on the `AND` operator
190
+ /// by default for all new queries. This is why we skip the first logical operator, so that we
191
+ /// are able to `.AND.MERGE(...)`, or `.OR.MERGE(...)` a query together... a distinction which
192
+ /// sometimes can be vitally important.
193
+ ///
194
+ /// `PROJECT` operations are skipped by default unless you specify the `options` of `{ projections: true }`.
195
+ /// This is simply because it often isn't desired to have projections merge over from sub-queries that
196
+ /// are being merged in... causing extra data to be pulled from the database. Projections in Mythix ORM
197
+ /// should **always** be deliberate and direct.
198
+ /// On the other hand, `ORDER` and `GROUP_BY` operations are automatically merged for you by default. The
199
+ /// reason should be fairly obvious, if two parts of the query need a specific order, or group-by fields,
200
+ /// then they probably shouldn't be skipped. They can always be skipped if desired by passing the `options`
201
+ /// of `{ orders: false }`, or `{ groupBys: false }` (or both).
202
+ ///
203
+ /// Note:
204
+ /// Because merges (and other operations) can get quite complex, it is recommend that you always
205
+ /// apply a projection, order, limit, and offset immediately before you use your query to interact
206
+ /// with the database.
207
+ ///
208
+ /// Arguments:
209
+ /// incomingQuery: <see>QueryEngine</see>
210
+ /// The query to merge/replay on-top a clone of `this` query.
211
+ /// options?: object
212
+ /// Options for the operation. See the table below for a list of possible options:
213
+ /// | Option | Type | Default Value | Description |
214
+ /// | ------------- | ---- | ------------- | ----------- |
215
+ /// | `projections` | `boolean` | `false` | If `true`, then also merge projections together. |
216
+ /// | `orders` | `boolean` | `true` | If `true`, then also merge order clauses together. |
217
+ /// | `groupBys` | `boolean` | `true` | If `true`, then also merge group-by clauses together. |
218
+ /// | `connection` | `Connection` | `this.getConnection()` | The connection to supply to the newly created `QueryEngine`. If none is supplied, then `this.getConnection()` is used to retrieve the connection. |
219
+ ///
220
+ /// Return: <see>QueryEngine</see>
221
+ /// Return the newly merged query.
62
222
  MERGE(_incomingQueryEngine, _options) {
63
223
  let incomingQueryEngine = _incomingQueryEngine;
64
224
  if (!incomingQueryEngine)
@@ -119,6 +279,33 @@ class QueryEngine extends QueryEngineBase {
119
279
  return queryEngine;
120
280
  }
121
281
 
282
+ /// Fetch all rows matching the query from the database.
283
+ ///
284
+ /// By default all rows fetched will be converted into
285
+ /// <see>Model</see> instances.
286
+ ///
287
+ /// This method will fetch in batches. By default the batch
288
+ /// size is `500`, but can be modified with the `{ batchSize: number; }`
289
+ /// `options`. Even though it fetches from the database in batches,
290
+ /// it won't return until all data has been fetched from the database.
291
+ ///
292
+ /// Example:
293
+ /// let smithFamilyUsers = await User.where.lastName.EQ('Smith').all();
294
+ ///
295
+ /// Note:
296
+ /// To stream from the database instead, use the
297
+ /// <see>QueryEngine.cursor</see> method.
298
+ ///
299
+ /// Arguments:
300
+ /// options?: object
301
+ /// Options to pass into the operation. These are generally connection
302
+ /// specific. However, one option that is always available is the `connection`
303
+ /// option, which can define the <see>Connection</see> to use for the
304
+ /// operation. This specific operation also has a `batchSize: number;`
305
+ /// option that can be used to specify the batch size for fetching.
306
+ ///
307
+ /// Return: Array<Model>
308
+ /// An array of all matching root model instances found by the query.
122
309
  async all(options) {
123
310
  let connection = this.getConnection(options && options.connection);
124
311
 
@@ -128,12 +315,70 @@ class QueryEngine extends QueryEngineBase {
128
315
  return await Utils.collect(connection.select(await this.finalizeQuery('read', options), options));
129
316
  }
130
317
 
318
+ /// Fetch all rows matching the query from the database,
319
+ /// by streaming them, using an async generator.
320
+ ///
321
+ /// By default all rows fetched will be converted into
322
+ /// <see>Model</see> instances.
323
+ ///
324
+ /// This method will fetch in batches. By default the batch
325
+ /// size is `500`, but can be modified with the `{ batchSize: number; }`
326
+ /// `options`. All data being fetched will be "streamed" to the caller
327
+ /// via an async generator return value.
328
+ ///
329
+ /// Example:
330
+ /// for await (let smithFamilyUser of User.where.lastName.EQ('Smith').cursor()) {
331
+ /// console.log(smithFamilyUser);
332
+ /// }
333
+ ///
334
+ /// Note:
335
+ /// To collect all results at once instead of streaming,
336
+ /// use the <see>QueryEngine.all</see> method instead.
337
+ ///
338
+ /// Arguments:
339
+ /// options?: object
340
+ /// Options to pass into the operation. These are generally connection
341
+ /// specific. However, one option that is always available is the `connection`
342
+ /// option, which can define the <see>Connection</see> to use for the
343
+ /// operation. This specific operation also has a `batchSize: number;`
344
+ /// option that can be used to specify the batch size for fetching.
345
+ ///
346
+ /// Return: Array<Model>
347
+ /// An array of all matching root model instances found by the query.
131
348
  async *cursor(_options) {
132
349
  let options = _options || {};
133
350
  let connection = this.getConnection(options && options.connection);
134
351
  return yield *connection.select(await this.finalizeQuery('read', options), { ...options, stream: true });
135
352
  }
136
353
 
354
+ /// Fetch the first matching row(s) from the database.
355
+ ///
356
+ /// By default all rows fetched will be converted into
357
+ /// <see>Model</see> instances.
358
+ ///
359
+ /// This method will always apply a `LIMIT` of `limit`, but it
360
+ /// won't modify the `OFFSET`... so it is possible to get
361
+ /// the nth item(s) by supplying your own `OFFSET` on the query.
362
+ ///
363
+ /// Example:
364
+ /// let firstUser = await User.where.lastName.EQ('Smith').ORDER.ASC('User:lastName').first();
365
+ ///
366
+ /// Note:
367
+ /// An `ORDER` should be applied on the query before
368
+ /// calling this method... or you might get funky results.
369
+ ///
370
+ /// Arguments:
371
+ /// limit: number = 1
372
+ /// The number of rows to fetch from the database. The default is `1`.
373
+ /// options?: object
374
+ /// Options to pass into the operation. These are generally connection
375
+ /// specific. However, one option that is always available is the `connection`
376
+ /// option, which can define the <see>Connection</see> to use for the
377
+ /// operation.
378
+ ///
379
+ /// Return: Array<Model> | Model
380
+ /// Return just one `Model` if the `limit` is set to `1`, otherwise
381
+ /// return an `Array` of `Model`.
137
382
  async first(_limit, options) {
138
383
  let limit = (_limit == null) ? 1 : _limit;
139
384
  let connection = this.getConnection(options && options.connection);
@@ -143,6 +388,39 @@ class QueryEngine extends QueryEngineBase {
143
388
  return (_limit == null) ? result[0] : result;
144
389
  }
145
390
 
391
+ /// Fetch the last matching row(s) from the database.
392
+ ///
393
+ /// By default all rows fetched will be converted into
394
+ /// <see>Model</see> instances.
395
+ ///
396
+ /// This method will always apply a `LIMIT` of `limit`, but it
397
+ /// won't modify the `OFFSET`... so it is possible to get
398
+ /// the nth item(s) by supplying your own `OFFSET` on the query.
399
+ ///
400
+ /// This method works by forcing the query `ORDER` to invert itself,
401
+ /// and then it selects the first `limit` rows from the top.
402
+ ///
403
+ /// Example:
404
+ /// // The `ORDER` operation here will be forcefully inverted
405
+ /// // to select the "last" row from the top.
406
+ /// let firstUser = await User.where.lastName.EQ('Smith').ORDER.ASC('User:lastName').last();
407
+ ///
408
+ /// Note:
409
+ /// An `ORDER` should be applied on the query before
410
+ /// calling this method... or you might get funky results.
411
+ ///
412
+ /// Arguments:
413
+ /// limit: number = 1
414
+ /// The number of rows to fetch from the database. The default is `1`.
415
+ /// options?: object
416
+ /// Options to pass into the operation. These are generally connection
417
+ /// specific. However, one option that is always available is the `connection`
418
+ /// option, which can define the <see>Connection</see> to use for the
419
+ /// operation.
420
+ ///
421
+ /// Return: Array<Model> | Model
422
+ /// Return just one `Model` if the `limit` is set to `1`, otherwise
423
+ /// return an `Array` of `Model`.
146
424
  async last(_limit, options) {
147
425
  let limit = (_limit == null) ? 1 : _limit;
148
426
  let connection = this.getConnection(options && options.connection);
@@ -152,51 +430,280 @@ class QueryEngine extends QueryEngineBase {
152
430
  return (_limit == null) ? result[0] : result.reverse();
153
431
  }
154
432
 
433
+ /// Update all matching rows in the database with
434
+ /// the attributes provided.
435
+ ///
436
+ /// This method will bulk-write to the rows matching
437
+ /// the query, using the attributes provided to update
438
+ /// matching rows. The attributes provided can be a model instance,
439
+ /// but will generally just be an object of attributes
440
+ /// (where the key names must match model `fieldName`s).
441
+ ///
442
+ /// Example:
443
+ /// // Update all users who are 'inactive' to now be 'active'
444
+ /// await User.where.status.EQ('inactive').updateAll({ status: 'active' });
445
+ ///
446
+ /// Note:
447
+ /// **This will not call onBefore or onAfter update hooks
448
+ /// on the model instances**. It is a direct bulk-write
449
+ /// operation to the underlying database.
450
+ ///
451
+ /// Note:
452
+ /// This operation will only ever update the "root model"
453
+ /// table. The "root model" of a query is the first model
454
+ /// used in the query. For example, `User` is the root model
455
+ /// in the query `User.where`.
456
+ ///
457
+ /// Arguments:
458
+ /// attributes: object | <see>Model</see>
459
+ /// The attributes to update across all rows. The key names of
460
+ /// this object must be `fieldName`s from the root model of the
461
+ /// query (**NOT** column names).
462
+ /// options?: object
463
+ /// Options to pass into the operation. These are generally connection
464
+ /// specific. However, one option that is always available is the `connection`
465
+ /// option, which can define the <see>Connection</see> to use for the
466
+ /// operation.
467
+ ///
468
+ /// Return: number
469
+ /// Return the number of rows that were updated.
155
470
  async updateAll(attributes, options) {
156
471
  let connection = this.getConnection(options && options.connection);
157
472
  return await connection.updateAll(await this.finalizeQuery('update', options), attributes, options);
158
473
  }
159
474
 
475
+ /// Delete matching rows in the database.
476
+ ///
477
+ /// This method will delete all rows matching the query
478
+ /// from the database (for only the root model).
479
+ ///
480
+ /// Example:
481
+ /// await QueueEntry.where.status.EQ('completed').destroy();
482
+ ///
483
+ /// Note:
484
+ /// This operation will only ever update the "root model"
485
+ /// table. The "root model" of a query is the first model
486
+ /// used in the query. For example, `User` is the root model
487
+ /// in the query `User.where`.
488
+ ///
489
+ /// Arguments:
490
+ /// options?: object
491
+ /// Options to pass into the operation. These are generally connection
492
+ /// specific. However, one option that is always available is the `connection`
493
+ /// option, which can define the <see>Connection</see> to use for the
494
+ /// operation.
495
+ ///
496
+ /// Return: number
497
+ /// Return the number of rows that were deleted.
160
498
  async destroy(options) {
161
499
  let connection = this.getConnection(options && options.connection);
162
500
  return await connection.destroy(await this.finalizeQuery('delete', options), options);
163
501
  }
164
502
 
503
+ /// Calculate the "average" of all matching rows for
504
+ /// a single column in the database.
505
+ ///
506
+ /// Example:
507
+ /// let averageUserAge = await User.where.average('User:age');
508
+ ///
509
+ /// Arguments:
510
+ /// field: <see>Field</see> | string | <see>Literal</see>
511
+ /// A field, fully qualified field name, or literal to average across.
512
+ /// options?: object
513
+ /// Options to pass into the operation. These are generally connection
514
+ /// specific. However, one option that is always available is the `connection`
515
+ /// option, which can define the <see>Connection</see> to use for the
516
+ /// operation.
517
+ ///
518
+ /// Return: number
519
+ /// The "average" of all matching rows for a single column.
165
520
  async average(field, options) {
166
521
  let connection = this.getConnection(options && options.connection);
167
522
  return await connection.average(await this.finalizeQuery('read', options), field, options);
168
523
  }
169
524
 
525
+ /// Count the number of matching rows.
526
+ ///
527
+ /// A `field` can be provided, which is used as the column to count on.
528
+ /// If a `DISTINCT` operation is used in the query, then the `DISTINCT`
529
+ /// field will be used for counting, regardless of what `field`
530
+ /// you specify. If no `field` is provided, then all fields (`*`)
531
+ /// is assumed.
532
+ ///
533
+ /// Example:
534
+ /// let smithFamilySize = await User.where.lastName.EQ('Smith').count();
535
+ ///
536
+ /// Note:
537
+ /// Due to database limitations, it can sometimes be difficult or
538
+ /// impossible in the underlying database to use an `ORDER`, `LIMIT`, or `OFFSET`,
539
+ /// with this operation. Because of this, any `ORDER` on the query
540
+ /// is simply ignored for now (help solving this problem would be
541
+ /// greatly appreciated!).
542
+ ///
543
+ /// Arguments:
544
+ /// field?: <see>Field</see> | string | literal
545
+ /// A field, fully qualified field name, or literal to count on. If
546
+ /// a `DISTINCT` operation is used in the query, then this argument
547
+ /// will be ignored, and the field defined on the `DISTINCT` operation
548
+ /// will be used instead.
549
+ /// options?: object
550
+ /// Options to pass into the operation. These are generally connection
551
+ /// specific. However, one option that is always available is the `connection`
552
+ /// option, which can define the <see>Connection</see> to use for the
553
+ /// operation.
554
+ ///
555
+ /// Return: number
556
+ /// The number of rows that match the query.
170
557
  async count(field, options) {
171
558
  let connection = this.getConnection(options && options.connection);
172
559
  return await connection.count(await this.finalizeQuery('read', options), field, options);
173
560
  }
174
561
 
562
+ /// Calculate the "minimum" of all matching rows for
563
+ /// a single column in the database.
564
+ ///
565
+ /// Example:
566
+ /// let youngestUserAge = await User.where.min('User:age');
567
+ ///
568
+ /// Arguments:
569
+ /// field: <see>Field</see> | string | <see>Literal</see>
570
+ /// A field, fully qualified field name, or literal to calculate on.
571
+ /// options?: object
572
+ /// Options to pass into the operation. These are generally connection
573
+ /// specific. However, one option that is always available is the `connection`
574
+ /// option, which can define the <see>Connection</see> to use for the
575
+ /// operation.
576
+ ///
577
+ /// Return: number
578
+ /// The "minimum" value of all matching rows for a single column.
175
579
  async min(field, options) {
176
580
  let connection = this.getConnection(options && options.connection);
177
581
  return await connection.min(await this.finalizeQuery('read', options), field, options);
178
582
  }
179
583
 
584
+ /// Calculate the "maximum" of all matching rows for
585
+ /// a single column in the database.
586
+ ///
587
+ /// Example:
588
+ /// let oldestUserAge = await User.where.max('User:age');
589
+ ///
590
+ /// Arguments:
591
+ /// field: <see>Field</see> | string | <see>Literal</see>
592
+ /// A field, fully qualified field name, or literal to calculate on.
593
+ /// options?: object
594
+ /// Options to pass into the operation. These are generally connection
595
+ /// specific. However, one option that is always available is the `connection`
596
+ /// option, which can define the <see>Connection</see> to use for the
597
+ /// operation.
598
+ ///
599
+ /// Return: number
600
+ /// The "maximum" value of all matching rows for a single column.
180
601
  async max(field, options) {
181
602
  let connection = this.getConnection(options && options.connection);
182
603
  return await connection.max(await this.finalizeQuery('read', options), field, options);
183
604
  }
184
605
 
606
+ /// Calculate the "sum" of all matching rows for
607
+ /// a single column in the database.
608
+ ///
609
+ /// Example:
610
+ /// let totalInventoryPrice = await Product.where.sum('Product:price');
611
+ ///
612
+ /// Arguments:
613
+ /// field: <see>Field</see> | string | <see>Literal</see>
614
+ /// A field, fully qualified field name, or literal to average across.
615
+ /// options?: object
616
+ /// Options to pass into the operation. These are generally connection
617
+ /// specific. However, one option that is always available is the `connection`
618
+ /// option, which can define the <see>Connection</see> to use for the
619
+ /// operation.
620
+ ///
621
+ /// Return: number
622
+ /// The "sum" of all matching rows for a single column.
185
623
  async sum(field, options) {
186
624
  let connection = this.getConnection(options && options.connection);
187
625
  return await connection.sum(await this.finalizeQuery('read', options), field, options);
188
626
  }
189
627
 
628
+ /// Pluck specific fields directly from the database.
629
+ /// These plucked fields will not be converted into
630
+ /// model instances, but instead will be returned raw
631
+ /// to the caller.
632
+ ///
633
+ /// If only a single field is provided for `fields` then
634
+ /// an array of column values is returned, i.e. `[ ... ]`. If
635
+ /// however more than one field is specified for `fields`,
636
+ /// then an array of arrays containing column values is
637
+ /// returned, i.e. `[ [ ... ], [ ... ], ... ]`.
638
+ ///
639
+ /// Example:
640
+ /// let allSmithFamilyHobbies = await User.where.lastName.EQ('Smith').pluck('User:hobby');
641
+ ///
642
+ /// Arguments:
643
+ /// fields: <see>Field</see> | string | Array<<see>Field</see>> | Array<string>
644
+ /// The field(s) to pluck from the database. A single field may be specified.
645
+ /// options?: object
646
+ /// Options to pass into the operation. These are generally connection
647
+ /// specific. However, one option that is always available is the `connection`
648
+ /// option, which can define the <see>Connection</see> to use for the
649
+ /// operation. The option `{ mapToObjects: true }` can be specified to
650
+ /// map the results to objects, instead of returning arrays of raw values.
651
+ ///
652
+ /// Return: Array<any> | Array<Array<any>>
653
+ /// If a single field is specified for `fields`, then an array of column values
654
+ /// will be returned. If more than one field is specified, then an array of arrays
655
+ /// of column values will be returned. It matters not if you specify the single field
656
+ /// directly, or as an array, i.e. `.pluck('User:firstName')` is the same as `.pluck([ 'User:firstName' ])`.
657
+ /// The engine only cares if "one field" is used... it doesn't care how it receives
658
+ /// that single field.
190
659
  async pluck(fields, options) {
191
660
  let connection = this.getConnection(options && options.connection);
192
661
  return await connection.pluck(await this.finalizeQuery('read', options), fields, options);
193
662
  }
194
663
 
664
+ /// Check if any rows match the query.
665
+ ///
666
+ /// Example:
667
+ /// let smithFamilyHasMembers = await User.where.lastName.EQ('Smith').exists();
668
+ ///
669
+ /// Arguments:
670
+ /// options?: object
671
+ /// Options to pass into the operation. These are generally connection
672
+ /// specific. However, one option that is always available is the `connection`
673
+ /// option, which can define the <see>Connection</see> to use for the
674
+ /// operation.
675
+ ///
676
+ /// Return: boolean
677
+ /// Return `true` if at least one row matches the query, or `false` otherwise.
195
678
  async exists(options) {
196
679
  let connection = this.getConnection(options && options.connection);
197
680
  return await connection.exists(await this.finalizeQuery('read', options), options);
198
681
  }
199
682
 
683
+ /// Finalize the query before a database operation.
684
+ ///
685
+ /// This method simply proxies the request to <see>Connection.finalizeQuery</see>.
686
+ /// If no connection can be found (for whatever reason), then `this`
687
+ /// (the current query) is simply returned.
688
+ ///
689
+ /// Note:
690
+ /// This system was designed for "row level permissions" systems,
691
+ /// or simply to allow models themselves to control how queries are
692
+ /// carried out against them.
693
+ ///
694
+ /// Arguments:
695
+ /// type: string
696
+ /// The CRUD operation type being performed. Can be one of `'create'`, `'read'`,
697
+ /// `'update'`, or `'delete'`. The `'create'` operation is currently never used
698
+ /// by Mythix ORM, since no query is ever involved in an `INSERT` operation; however
699
+ /// it has been reserved for future use.
700
+ /// options?: object
701
+ /// The options that were provided by the user to the operation being executed.
702
+ /// These are the operation options themselves, but can contain properties
703
+ /// specifically targeted for custom down-stream code.
704
+ ///
705
+ /// Return: <see>QueryEngine</see>
706
+ /// Return `this` query, possibly altered by down-stream code.
200
707
  async finalizeQuery(crudOperation, options) {
201
708
  let connection = this.getConnection();
202
709
  if (connection && typeof connection.finalizeQuery === 'function')
@@ -207,7 +714,7 @@ class QueryEngine extends QueryEngineBase {
207
714
 
208
715
  [ProxyClass.MISSING](target, prop) {
209
716
  if (prop === 'debug') {
210
- this.currentContext.rootContext.debug = true;
717
+ this.getOperationContext().rootContext.debug = true;
211
718
  return this._fetchScope('model', 'queryEngine');
212
719
  }
213
720