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.
@@ -125,16 +125,106 @@ function wrapOrderClause(func) {
125
125
  return func;
126
126
  }
127
127
 
128
+ /// `ModelScope` is the "model level" of a query.
129
+ /// It manages things like `EXISTS`, `PROJECT`,
130
+ /// `ORDER`, `INNER_JOIN`, etc...
131
+ ///
132
+ /// Being a Proxy, it will "listen" for key access,
133
+ /// and lookup fields if there is a key access where
134
+ /// the key name isn't found on the class instance itself.
135
+ /// In this case, it will check the previous model specified
136
+ /// on the top of the internal "operation stack" to see if it
137
+ /// owns a field with the name of the missing key.
138
+ /// If there is a matching field on the top-most model of the stack,
139
+ /// then it will use the field found to push a <see>FieldScope</see>
140
+ /// onto the "operation stack", and then return that to the user.
141
+ /// Take the following example:
142
+ /// ```javascript
143
+ /// let queryRoot = new QueryEngine({ connection });
144
+ /// let userIDFieldScope = queryRoot.User.id;
145
+ /// ```
146
+ /// When we attempt to access the key `'id'` on
147
+ /// the `User` "model scope", we will find that no such key
148
+ /// exists on the `ModelScope` class. Now that no such property is found
149
+ /// on the `ModelScope` class, the <see>ProxyClass</see> will call the method
150
+ /// `MISSING` on the `ModelScope`, and this `MISSING` method
151
+ /// will check if the current model (`User`) has a field named `'id'`.
152
+ /// The <see>ModelScope._getField</see> method finds this field, and
153
+ /// returns it. The `ModelScope` then takes this field instance,
154
+ /// and uses it to create and return a <see>FieldScope</see>
155
+ /// using <see>QueryEngineBase._newFieldScope</see>. Now
156
+ /// that we have a <see>FieldScope</see>, we can continue
157
+ /// chaining, now using "field level" operators to act on
158
+ /// the field we just looked up.
159
+ ///
160
+ /// See: QueryEngineBase
161
+ ///
162
+ /// See: QueryEngine
163
+ ///
164
+ /// See: FieldScope
165
+ ///
166
+ /// Note:
167
+ /// `ModelScope` is a sub-part of the `QueryEngine`, and so is generally referred to
168
+ /// simply as the `QueryEngine` as a whole. This is also the case for <see>QueryEngineBase</see>,
169
+ /// and <see>FieldScope</see>, which also make up the `QueryEngine` as sub-parts,
170
+ /// and so are also often referred to simply as "the query engine".
128
171
  class ModelScope extends QueryEngineBase {
172
+ /// Get the specified field (by name) from the
173
+ /// top-most model on the internal "operation stack".
174
+ ///
175
+ /// This method is called when a key can not be found
176
+ /// on the `ModelScope` instance. The <see>ProxyClass</see>
177
+ /// will call the `MISSING` method when the key is not
178
+ /// found, and that method in turn calls this `_getField`
179
+ /// method to see if the key specified was actually a field
180
+ /// name on the most recent model in the "operation stack".
181
+ ///
182
+ /// Arguments:
183
+ /// fieldName: string
184
+ /// The field name of the field to fetch from the top-most model on the stack.
185
+ ///
186
+ /// Return: <see>Field</see>
187
+ /// The field found, or `undefined` if no such field is found.
129
188
  _getField(fieldName) {
130
189
  let Model = this.currentContext.Model;
131
190
  return Model.getField(fieldName);
132
191
  }
133
192
 
134
- _getQueryEngineClass() {
135
- return this.currentContext.queryEngine;
136
- }
137
-
193
+ /// Specify a <see>FieldScope</see> directly.
194
+ ///
195
+ /// This can be useful if you have a name collision, or if
196
+ /// you just want to go the direct route to specify a <see>FieldScope</see>.
197
+ ///
198
+ /// This will find the field specified on the most recent (top-most)
199
+ /// model on the internal "operation stack". If the field is found, then
200
+ /// it will be used to create a new <see>FieldScope</see>, which will
201
+ /// then be pushed onto the "operation stack", and returned to the user.
202
+ ///
203
+ /// Example:
204
+ /// // The two queries below are equivalent. The latter
205
+ /// // can be a good way to request a field if the field
206
+ /// // name being specified happens to be a name collision
207
+ /// // (i.e. has the same name as a QueryEngine or ModelScope
208
+ /// // property... such as "ORDER" or "OFFSET" for example).
209
+ /// let query1 = User.where.id.EQ('test');
210
+ /// let query2 = User.where.Field('id').EQ('test');
211
+ ///
212
+ /// Note:
213
+ /// An exception will be thrown in the specified field can not be found.
214
+ ///
215
+ /// Note:
216
+ /// This is one of the rare places in Mythix ORM where a fully qualified field
217
+ /// name **SHOULD NOT** be used. The reason should be clear: The model in the
218
+ /// operation should already be known.
219
+ ///
220
+ /// Arguments:
221
+ /// fieldName: string
222
+ /// A field name (NOT fully qualified), as a string. It must be a field
223
+ /// that exists on the model from the top-most <see>ModelScope</see> on
224
+ /// the internal "operation stack".
225
+ ///
226
+ /// Return: <see>FieldScope</see>
227
+ /// A new <see>FieldScope</see>, targeted to the field specified by `fieldName`.
138
228
  Field(fieldName) {
139
229
  let field = this._getField(fieldName);
140
230
  if (!field)
@@ -155,33 +245,205 @@ class ModelScope extends QueryEngineBase {
155
245
  return lowerScope[prop];
156
246
  }
157
247
 
158
- unscoped() {
159
- return this.currentContext.queryEngineScope.unscoped(this.currentContext);
160
- }
161
-
162
- toString(...args) {
163
- return this.currentContext.queryEngineScope.toString(...args);
164
- }
165
-
248
+ /// Merge fields together, replacing, adding, or subtracting from
249
+ /// the field set.
250
+ ///
251
+ /// This method is used by <see>ModelScope.ORDER</see>, <see>ModelScope.GROUP_BY</see>,
252
+ /// and <see>ModelScope.PROJECT</see>. It works be replacing, adding, or subtracting
253
+ /// from the current set of fields defined by any of these operations.
254
+ ///
255
+ /// If the first field encountered in the provided `incomingFields` isn't prefixed with
256
+ /// a `+` or `-`, and if a `+` or `-` operation doesn't come before the encountered field, then
257
+ /// the operation is considered a "replace" operation. For example, in `query.ORDER('User:id')`
258
+ /// we are "replacing" the order clause with only a single field: `'User:id'`. If however,
259
+ /// we did `query.ORDER('+User:id')` then the `'User:id'` field would be *added* to any current
260
+ /// order clause in the query. We could also prefix the entire operation with a single `'+'`
261
+ /// string, and then all following fields in the list would be in "add" mode. For example, the
262
+ /// following two operations are equivalent: `query.ORDER('+', 'User:firstName', 'User:lastName')`, and
263
+ /// `query.ORDER('+User:firstName', '+User:lastName')`. The subtraction operator `'-'` works the
264
+ /// same, but in reverse, specifying that we want to remove fields instead of add them:
265
+ /// `query.ORDER('-', 'User:firstName', 'User:lastName')`, and `query.ORDER('-User:firstName', '-User:lastName')`.
266
+ /// It is also possible to mix the two together: `query.ORDER('+', 'User:firstName', '-', 'User:lastName')`, or
267
+ /// `query.ORDER('+User:firstName', '-User:lastName')`.
268
+ ///
269
+ /// There is one special operator, `'*'`. This operator will add *all* fields from all models that
270
+ /// are currently in use on the query. For example, if we did: `Role.where.userID(User.where.id).PROJECT('*')`,
271
+ /// this would add *all* `Role` and `User` fields to the projection. Replace, add, and subtract rules still
272
+ /// apply with a wildcard. So in our example above we are "replacing" the projection, since no `+` or `-` was
273
+ /// encountered before the operation. If you instead wanted to *add* all model fields to the projection,
274
+ /// you should instead do: `Role.where.userID(User.where.id).PROJECT('+*')` or `Role.where.userID(User.where.id).PROJECT('+', '*')`.
275
+ /// Generally, it probably wouldn't matter much, since you are replacing "every field" anyhow... but it could
276
+ /// matter if for example you had previously projected a literal... in which case it would be replaced
277
+ /// with all model fields. Subtraction rules also apply to wildcard selectors.
278
+ ///
279
+ /// The only truly important thing to remember here is that if **no** operation is specified (add or subtract),
280
+ /// then the default operation is "replace", so the field set will be replaced entirely for the operation
281
+ /// in question.
282
+ ///
283
+ /// There is one exception to this behavior, for `ORDER.ADD` and `ORDER.REPLACE` **only**. These two operations
284
+ /// allow the field sort direction itself to be defined with `+` and `-`... so a
285
+ /// `query.ORDER.ADD('+User:firstName', '-User:lastName')` is not requesting that we add the `'User:firstName'`
286
+ /// field, and subtract the `'User:lastName'` field... but instead is specifying that we want
287
+ /// `'User:firstName'` in `ASC` sort order, whereas we want `'User:lastName'` in `DESC` sort order.
288
+ /// `ORDER.ADD` and `ORDER.REPLACE` are the only exceptions to the normal logic defined here. There are
289
+ /// two methods so that a user can add to the field set, or replace the field set. A `SUB` (subtract)
290
+ /// operation is not needed, because that can be done with `ORDER` anyway, i.e.
291
+ /// `query.ORDER.ADD('+User:firstName', '+User:lastName').ORDER('-Role')`, which would "add" the
292
+ /// `'User:firstName'` and `'User:lastName'` fields, simultaneously specifying their short direction,
293
+ /// and then `ORDER('-Role')` is subtracting every field from the `Role` model.
294
+ ///
295
+ /// Note:
296
+ /// A solo `+` or `-` operation is a no-op: `query.ORDER('+')` and `query.ORDER('-')`
297
+ /// will do nothing at all, and will not modify the operation in the slightest.
298
+ ///
299
+ /// Note:
300
+ /// This method supports a short-cut for literals. You can specify a string prefixed
301
+ /// with an `@` symbol to specify a literal. For example, `query.PROJECT('@COUNT("users"."firstName") AS "count"')`.
302
+ /// Doing so will create a new <see>Literal</see> instance with the value provided. This
303
+ /// is not the best way to provide a literal however, because the <see>Literal</see> class
304
+ /// defines a "raw" literal, whereas the typed literal classes (such as <see>CountLiteral</see>)
305
+ /// actually store the field they are operating on, and can report that field back to the engine.
306
+ ///
307
+ /// Arguments:
308
+ /// currentFields: Map<string, { value: Field | Literal | string; direction?: '+' | '-'; ... }>
309
+ /// A map of the current fields for the operation. This is the value from the operation
310
+ /// context itself. For example, the `ORDER` method provides this argument via:
311
+ /// `this.getOperationContext().order`, providing any previous "order" operation
312
+ /// as the current fields.
313
+ /// incomingFields: Array<<see>Model</see> | <see>Field</see> | string | literal | '+' | '-' | '*'>
314
+ /// Incoming fields to replace, add, or subtract from the operation in question.
315
+ /// If no fields at all are provided, then this will reset/nullify the operation. For example,
316
+ /// an `ORDER()` would clear any `ORDER BY` clause entirely. If a model is provided, either
317
+ /// as a name, i.e. `'User'`, or as the raw model, i.e. `User`, then *all the fields* from the
318
+ /// specified model will be added or subtracted. A field can be specified as a fully qualified
319
+ /// field name, a raw <see>Field</see> instance, or just the field name itself, i.e. `'firstName'`.
320
+ /// If no model is specified (i.e. a non-fully-qualified field name), then the engine will attempt
321
+ /// to fetch the field specified from the root model of the query. Literals can be used for all
322
+ /// supported operations, `ORDER`, `GROUP_BY`, and `PROJECT`, and they also follow the replace, add
323
+ /// and subtract rules of the engine.
324
+ /// extraData?: object
325
+ /// Extra data to apply to the operation. For example, the `ORDER` operation applies a `direction` property
326
+ /// to each field in the map. The field map is a `Map` instance, where each key is the fully qualified field
327
+ /// name, or expanded literal value. Each value on the map is an object, containing at least a `value` property
328
+ /// that is the field or literal specified. This object can also contain any ancillary operation info, such
329
+ /// as in the case of the `ORDER` operation, which will also add a `direction` property to each field in the
330
+ /// map to specify the field's (or literal's) sort order.
331
+ /// options?: object
332
+ /// An options object to pass off to `Literal.toString` when expanding literals for use as `Map` keys.
333
+ ///
334
+ /// Return: Map<string, { value: Field | Literal | string; direction?: '+' | '-'; ... }>
335
+ /// Return the new field set. A `Map` will always be returned, but it is possible
336
+ /// for the `Map` to be empty.
166
337
  margeFields(currentFields, incomingFields, extraData, options) {
167
338
  return QueryUtils.margeFields(this, currentFields, incomingFields, extraData, options);
168
339
  }
169
340
 
341
+ /// Invert the logic of the following operator.
342
+ ///
343
+ /// This method does not need to be called.
344
+ ///
345
+ /// Unlike `AND` and `OR` operators, this is not a permanent toggle.
346
+ /// As soon as a following operator is encountered, the `NOT` operation
347
+ /// will be "turned off". `NOT` will invert any operation, so for example
348
+ /// if you did a `User.where.id.NOT.EQ('test')` then this is the same as
349
+ /// `User.where.id.NEQ('test')`--selecting everything NOT EQUAL TO `'test'`.
350
+ /// `NOT` can also be used in combination with `EXISTS`, `ANY`, `ALL`, `IN`,
351
+ /// etc... inverting any operator following `NOT`.
352
+ ///
353
+ /// Note:
354
+ /// `NOT` is only ever used once, and then it is toggled back off. For example,
355
+ /// if we did: `User.where.id.NOT.EQ('test').AND.lastName.EQ('Smith')`, then
356
+ /// we would have a query where `id != 'test' AND lastName = 'Smith'`. As you
357
+ /// can see, the `NOT` doesn't apply to the second `lastName` operator, as it
358
+ /// was turned off as soon as the first `EQ` operator was encountered on the `id`
359
+ /// field.
360
+ ///
361
+ /// Return: <see>ModelScope</see>
362
+ /// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query.
363
+ ///
364
+ /// SyntaxType: FunctionDeclaration
170
365
  NOT = ProxyClass.autoCall(function() {
171
- this._pushOperationOntoStack({ logical: true, operator: 'NOT', queryProp: 'NOT', not: !this.currentContext.not });
366
+ this._pushOperationOntoStack({ logical: true, operator: 'NOT', queryProp: 'NOT', not: !this.getOperationContext().not });
172
367
  return this._fetchScope('model');
173
368
  });
174
369
 
370
+ /// Logical `AND`, for ANDing operations together.
371
+ ///
372
+ /// This method does not need to be called, but it can
373
+ /// be called if desired.
374
+ ///
375
+ /// This is a "toggle", so as soon as it is used,
376
+ /// it will continue to be "active" for all following
377
+ /// operations. In Mythix ORM you don't need to specify
378
+ /// `AND` or `OR` unless you actually need them. By default
379
+ /// `AND` is enabled for all queries. So for example you
380
+ /// can do `User.where.id.EQ('test').firstName.EQ('John').lastName.EQ('Smith')`,
381
+ /// which is exactly the same as `User.where.id.EQ('test').AND.firstName.EQ('John').AND.lastName.EQ('Smith')`.
382
+ ///
383
+ /// `AND` can also be called to group conditions. For example, you could create
384
+ /// the following query: `User.where.id.EQ('test').AND(User.where.lastName.EQ('John').OR.lastName.EQ('Brown'))`
385
+ /// to create the following SQL query: `WHERE id = 'test' AND (lastName = 'John' OR lastName = 'Brown')`.
386
+ ///
387
+ /// SyntaxType: FunctionDeclaration
388
+ ///
389
+ /// Arguments:
390
+ /// query?: <see>QueryEngine</see>
391
+ /// An optional query, which if provided, will create a "condition group" as a result.
392
+ ///
393
+ /// Return: <see>ModelScope</see>
394
+ /// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query.
175
395
  AND = ProxyClass.autoCall(function(value) {
176
396
  this._pushOperationOntoStack({ logical: true, operator: 'AND', queryProp: 'AND', and: true, or: false, not: false, value });
177
397
  return this._fetchScope('model');
178
398
  });
179
399
 
400
+ /// Logical `OR`, for ORing operations together.
401
+ ///
402
+ /// This method does not need to be called, but it can
403
+ /// be called if desired.
404
+ ///
405
+ /// This is a "toggle", so as soon as it is used,
406
+ /// it will continue to be "active" for all following
407
+ /// operations. In Mythix ORM you don't need to specify
408
+ /// `AND` or `OR` unless you actually need them. By default
409
+ /// `AND` is enabled for all queries. For example you
410
+ /// can do `User.where.id.EQ('test').OR.firstName.EQ('John').lastName.EQ('Smith')`,
411
+ /// which is exactly the same as `User.where.id.EQ('test').OR.firstName.EQ('John').OR.lastName.EQ('Smith')`.
412
+ ///
413
+ /// `OR` can also be called to group conditions. For example, you could create
414
+ /// the following query: `User.where.id.EQ('test').OR(User.where.firstName.EQ('John').OR.lastName.EQ('Brown'))`
415
+ /// to create the following SQL query: `WHERE id = 'test' OR (firstName = 'John' AND lastName = 'Brown')`.
416
+ ///
417
+ /// SyntaxType: FunctionDeclaration
418
+ ///
419
+ /// Arguments:
420
+ /// query?: <see>QueryEngine</see>
421
+ /// An optional query, which if provided, will create a "condition group" as a result.
422
+ ///
423
+ /// Return: <see>ModelScope</see>
424
+ /// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query.
180
425
  OR = ProxyClass.autoCall(function(value) {
181
426
  this._pushOperationOntoStack({ logical: true, operator: 'OR', queryProp: 'OR', and: false, or: true, not: false, value });
182
427
  return this._fetchScope('model');
183
428
  });
184
429
 
430
+ /// Apply a `LIMIT` clause to the query.
431
+ ///
432
+ /// Any valid positive integer is acceptable, as well as `Infinity`.
433
+ /// If `Infinity` is used, then the `LIMIT` will either turn into its
434
+ /// max possible value (in the billions... depending on the underlying database),
435
+ /// or it will be stripped from the query entirely.
436
+ /// `NaN`, or anything that isn't a valid positive integer will throw an error.
437
+ ///
438
+ /// Note:
439
+ /// Positive floating point numbers are rounded with `Math.round`.
440
+ ///
441
+ /// Arguments:
442
+ /// limit: number
443
+ /// The limit to apply to the query.
444
+ ///
445
+ /// Return: <see>ModelScope</see>
446
+ /// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query.
185
447
  LIMIT(_value) {
186
448
  let value = _value;
187
449
  if (typeof value !== 'number' || isNaN(value) || value < 0)
@@ -193,6 +455,20 @@ class ModelScope extends QueryEngineBase {
193
455
  return this._fetchScope('model');
194
456
  }
195
457
 
458
+ /// Apply an `OFFSET` clause to the query.
459
+ ///
460
+ /// Any valid positive integer is acceptable. `Infinity`, `NaN`,
461
+ /// or anything that isn't a valid positive integer will throw an error.
462
+ ///
463
+ /// Note:
464
+ /// Positive floating point numbers are rounded with `Math.round`.
465
+ ///
466
+ /// Arguments:
467
+ /// offset: number
468
+ /// The offset to apply to the query.
469
+ ///
470
+ /// Return: <see>ModelScope</see>
471
+ /// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query.
196
472
  OFFSET(_value) {
197
473
  let value = _value;
198
474
  if (typeof value !== 'number' || !isFinite(value) || value < 0)
@@ -204,10 +480,52 @@ class ModelScope extends QueryEngineBase {
204
480
  return this._fetchScope('model');
205
481
  }
206
482
 
483
+ /// Apply an `ORDER BY` clause to the query, to sort
484
+ /// the results on the fields specified, in either
485
+ /// `ASC` or `DESC` order.
486
+ ///
487
+ /// There are five variants to this method, `ORDER.ASC`,
488
+ /// `ORDER.DESC`, `ORDER.ADD`, `ORDER.REPLACE`, and `ORDER` (which is an alias for `ORDER.ASC`), .
489
+ /// 1) `ORDER` - Alias for `ORDER.ASC`.
490
+ /// 2) `ORDER.ASC` - Follow the rules of <see>ModelScope.margeFields</see>. Each field/literal added is in `ASC` order.
491
+ /// 3) `ORDER.DESC` - Follow the rules of <see>ModelScope.margeFields</see>. Each field/literal added is in `DESC` order.
492
+ /// 4) `ORDER.ADD` - **DO NOT** follow the rules of <see>ModelScope.margeFields</see>, and instead **add** all fields specified, with their sort order being specified instead by the `+` or `-` prefixes on each field.
493
+ /// 5) `ORDER.REPLACE` - **DO NOT** follow the rules of <see>ModelScope.margeFields</see>, and instead **replace** the operation fields to the fields specified, with their sort order being specified instead by the `+` or `-` prefixes on each field.
494
+ ///
495
+ /// See <see>ModelScope.margeFields</see> to better understand how this method works.
496
+ ///
497
+ /// SyntaxType: FunctionDeclaration
498
+ ///
499
+ /// Note:
500
+ /// This method will flatten all provided arguments into a one dimensional array,
501
+ /// so you can provide arrays or deeply nested arrays for the fields specified.
502
+ ///
503
+ /// Arguments:
504
+ /// ...args: Array<<see>Field</see> | <see>LiteralBase</see> | string | '+' | '-' | '*'>
505
+ /// New "fields" to supply to `ORDER`, replacing, adding, or subtracting the
506
+ /// specified fields from the `ORDER` operation.
507
+ ///
508
+ /// Return: <see>ModelScope</see>
509
+ /// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query.
207
510
  ORDER = wrapOrderClause.call(this, (...args) => {
208
511
  return applyOrderClause.call(this, { direction: '+' }, ...args);
209
512
  });
210
513
 
514
+ /// Apply a `GROUP BY` clause to the query.
515
+ ///
516
+ /// See <see>ModelScope.margeFields</see> to better understand how this method works.
517
+ ///
518
+ /// Note:
519
+ /// This method will flatten all provided arguments into a one dimensional array,
520
+ /// so you can provide arrays or deeply nested arrays for the fields specified.
521
+ ///
522
+ /// Arguments:
523
+ /// ...args: Array<<see>Field</see> | <see>LiteralBase</see> | string | '+' | '-' | '*'>
524
+ /// New "fields" to supply to `GROUP BY`, replacing, adding, or subtracting the
525
+ /// specified fields from the `GROUP_BY` operation.
526
+ ///
527
+ /// Return: <see>ModelScope</see>
528
+ /// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query.
211
529
  GROUP_BY(...args) {
212
530
  let entities = Nife.arrayFlatten(args);
213
531
 
@@ -248,11 +566,52 @@ class ModelScope extends QueryEngineBase {
248
566
  return this._fetchScope('model');
249
567
  }
250
568
 
569
+ /// Apply a `HAVING` clause to the query.
570
+ ///
571
+ /// This will only be applied in the underlying database query
572
+ /// if it is also paired with a <see>ModelScope.GROUP_BY</see> operation,
573
+ /// otherwise it will be ignored.
574
+ ///
575
+ /// Example:
576
+ /// let adultCountByAge = await User.where
577
+ /// .GROUP_BY('User:age')
578
+ /// .HAVING(User.where.age.GTE(18))
579
+ /// .PROJECT('User:age', new CountLiteral('User:age', { as: 'count' }))
580
+ /// .all();
581
+ ///
582
+ /// Arguments:
583
+ /// query: <see>QueryEngine</see>
584
+ /// The query to use to apply conditions to the `HAVING` clause.
585
+ ///
586
+ /// Return: <see>ModelScope</see>
587
+ /// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query.
251
588
  HAVING(query) {
252
589
  this._pushOperationOntoStack({ control: true, operator: 'HAVING', queryProp: 'HAVING', value: query, having: query });
253
590
  return this._fetchScope('model');
254
591
  }
255
592
 
593
+ /// Check if any rows match the query provided.
594
+ ///
595
+ /// This is on the <see>ModelScope</see> itself because
596
+ /// it is never paired with a field, and is an operator that
597
+ /// stands all on its own.
598
+ ///
599
+ /// It can be used to check the existence of any value in the
600
+ /// database. For example, you might want to query on users,
601
+ /// but only if those users have an "admin" role:
602
+ /// `await User.where.email.EQ('test@example.com').EXISTS(Role.where.name.EQ("admin").userID.EQ(new FieldLiteral('User:id')))`
603
+ ///
604
+ /// Note:
605
+ /// You can execute a `NOT EXISTS` operation simply by prefixing `EXISTS` with a `.NOT`
606
+ /// operation, for example `query.NOT.EXISTS(...)`.
607
+ ///
608
+ /// Arguments:
609
+ /// query: <see>QueryEngine</see>
610
+ /// The sub-query to execute to check for existence. Use a <see>FieldLiteral</see>
611
+ /// to pair it with the primary query.
612
+ ///
613
+ /// Return: <see>ModelScope</see>
614
+ /// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query.
256
615
  EXISTS(_query) {
257
616
  let query = _query;
258
617
  let queryContext = (QueryEngineBase.isQuery(query)) ? query.getOperationContext() : null;
@@ -282,6 +641,28 @@ class ModelScope extends QueryEngineBase {
282
641
  return this._fetchScope('model');
283
642
  }
284
643
 
644
+ /// Replace, add to, or subtract from the projection of the query.
645
+ ///
646
+ /// See <see>ModelScope.margeFields</see> to better understand how this method works.
647
+ ///
648
+ /// Note:
649
+ /// This method will flatten all provided arguments into a one dimensional array,
650
+ /// so you can provide arrays or deeply nested arrays for the fields specified.
651
+ ///
652
+ /// Note:
653
+ /// Mythix ORM collects and returns models (or partial models) based on the projection.
654
+ /// By default only the "root model" of a query will be projected and converted into
655
+ /// model instances. If you want to fetch more than just the root model while querying,
656
+ /// make sure to project the models (or fields from other models) that you want to
657
+ /// collect into model instances during load.
658
+ ///
659
+ /// Arguments:
660
+ /// ...args: Array<<see>Field</see> | <see>LiteralBase</see> | string | '+' | '-' | '*'>
661
+ /// New "fields" to supply to the projection, replacing, adding, or subtracting the
662
+ /// specified fields from the `PROJECT` operation.
663
+ ///
664
+ /// Return: <see>ModelScope</see>
665
+ /// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query.
285
666
  PROJECT(...args) {
286
667
  let entities = Nife.arrayFlatten(args);
287
668
 
@@ -328,6 +709,62 @@ class ModelScope extends QueryEngineBase {
328
709
  return this._fetchScope('model');
329
710
  }
330
711
 
712
+ /// Apply a DISTINCT clause to the query.
713
+ ///
714
+ /// This method does not need to be called, but it can
715
+ /// be called if desired.
716
+ ///
717
+ /// A field is required for this operation to keep the interface
718
+ /// consistent across all database drivers... however, the field
719
+ /// specified may not actually be used, depending on database support,
720
+ /// or other operations being carried out in the query.
721
+ ///
722
+ /// If the underlying database supports it (i.e. PostgreSQL), then
723
+ /// this will turn into a `DISTINCT ON(field)` operation. If the
724
+ /// database (or operation being carried out), doesn't support `DISTINCT ON`,
725
+ /// then it will fallback to just a `DISTINCT` across the entire projection.
726
+ /// Any `DISTINCT` operation applied to the query will always be the very
727
+ /// first part of the projection, regardless of whatever else is projected.
728
+ ///
729
+ /// This method is *optionally* callable. If not called, then the primary key
730
+ /// of the model specified will be used (if available). If no primary key exists
731
+ /// on the specified model, then it will fallback to a raw `DISTINCT` clause
732
+ /// prefixing the projection. For example: `User.where.DISTINCT.lastName.EQ('Smith')`
733
+ /// would be distinct on the primary key of `User` (which would be `id` in our example).
734
+ /// If instead we call the operator, then we can supply our own field or literal:
735
+ /// `User.where.DISTINCT('User:firstName').lastName.EQ('Smith')`.
736
+ ///
737
+ /// To turn off any previous `DISTINCT` applied, pass the argument `false` to
738
+ /// `DISTINCT`: `User.where.DISTINCT.lastName.EQ('Smith').DISTINCT(false)`.
739
+ /// In this example the resulting query would have no `DISTINCT` clause at all,
740
+ /// since it was disabled when `DISTINCT(false)` was called.
741
+ ///
742
+ /// `DISTINCT` changes the nature of the query, and might change how it is carried out
743
+ /// by the underlying database driver. For example, a distinct combined with a `count`
744
+ /// call would modify the `count` query: `await User.where.DISTINCT('User:id').count()` would
745
+ /// actually turn into the following SQL: `SELECT COUNT(DISTINCT "users"."id")`. This
746
+ /// is just one example however... just know that the underlying database driver might
747
+ /// alter the query, or take a completely different path to query if a `DISTINCT` operation
748
+ /// is in-play.
749
+ ///
750
+ /// Note:
751
+ /// The support for a `DISTINCT` clause changes wildly across databases. Some might support
752
+ /// `ON` for a specific column, some may not. Some databases might force a certain `ORDER`
753
+ /// when using `DISTINCT`, some may not... `DISTINCT` may be supported in sub-queries, or
754
+ /// it may not... or might require the query be written differently. Mythix ORM does its best
755
+ /// to make a "standard" out of this very non-standard situation (does any SQL actually follow a standard?),
756
+ /// but just be aware that `DISTINCT` might bite you if you are changing database drivers to a
757
+ /// database that behaves differently.
758
+ ///
759
+ /// SyntaxType: FunctionDeclaration
760
+ ///
761
+ /// Arguments:
762
+ /// field: <see>Field</see> | <see>LiteralBase</see> | string | false = Model.getPrimaryKeyField()
763
+ /// The field or literal to be `DISTINCT ON` (if the database supports it). If `false`
764
+ /// is specified, then any previous `DISTINCT` operation is cleared.
765
+ ///
766
+ /// Return: <see>ModelScope</see>
767
+ /// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query.
331
768
  DISTINCT = ProxyClass.autoCall(function(_fullyQualifiedName) {
332
769
  let fullyQualifiedName = _fullyQualifiedName;
333
770
  let currentQuery = this;
@@ -363,31 +800,149 @@ class ModelScope extends QueryEngineBase {
363
800
  return this._fetchScope('model');
364
801
  });
365
802
 
803
+ /// Specify an `INNER JOIN` operation for joining tables.
804
+ ///
805
+ /// This method does not need to be called, but it can
806
+ /// be called if desired.
807
+ ///
808
+ /// All Mythix ORM "join" operations should come immediately
809
+ /// before a table join. For example: `User.where.INNER_JOIN.id.EQ(Role.where.userID)`.
810
+ /// They are "toggles", and so will remain "on" once used. In short, if
811
+ /// you specify an `INNER_JOIN` at the very beginning of your query, then
812
+ /// **ALL** table joins in the query will be `INNER JOIN`, unless you specify
813
+ /// other join types.
814
+ ///
815
+ /// Note:
816
+ /// `INNER_JOIN` is the default table join type in Mythix ORM if no other table
817
+ /// join type is specified in the query.
818
+ ///
819
+ /// SyntaxType: FunctionDeclaration
820
+ ///
821
+ /// Return: <see>ModelScope</see>
822
+ /// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query.
366
823
  INNER_JOIN = ProxyClass.autoCall(function() {
367
824
  this._pushOperationOntoStack({ join: true, operator: 'JOIN', queryProp: 'INNER_JOIN', value: 'inner', joinType: 'inner', joinOuter: false });
368
825
  return this._fetchScope('model');
369
826
  });
370
827
 
828
+ /// Specify a `LEFT JOIN` operation for joining tables.
829
+ ///
830
+ /// This method does not need to be called, but it can
831
+ /// be called if desired.
832
+ ///
833
+ /// All Mythix ORM "join" operations should come immediately
834
+ /// before a table join. For example: `User.where.LEFT_JOIN.id.EQ(Role.where.userID)`.
835
+ /// They are "toggles", and so will remain "on" once used. In short, if
836
+ /// you specify a `LEFT_JOIN` at the very beginning of your query, then
837
+ /// **ALL** table joins in the query will be `LEFT JOIN`, unless you specify
838
+ /// other join types, i.e. `User.where.LEFT_JOIN.id.EQ(Role.where.userID).AND.INNER_JOIN.id.EQ(Organization.where.userID)`.
839
+ ///
840
+ /// SyntaxType: FunctionDeclaration
841
+ ///
842
+ /// Arguments:
843
+ /// outerJoin: boolean = false
844
+ /// If `true`, then this will result in a `LEFT OUTER JOIN` if the database supports it.
845
+ ///
846
+ /// Return: <see>ModelScope</see>
847
+ /// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query.
371
848
  LEFT_JOIN = ProxyClass.autoCall(function(outerJoin) {
372
849
  this._pushOperationOntoStack({ join: true, operator: 'JOIN', queryProp: 'LEFT_JOIN', value: 'left', joinType: 'left', joinOuter: !!outerJoin });
373
850
  return this._fetchScope('model');
374
851
  });
375
852
 
853
+ /// Specify a `RIGHT JOIN` operation for joining tables.
854
+ ///
855
+ /// This method does not need to be called, but it can
856
+ /// be called if desired.
857
+ ///
858
+ /// All Mythix ORM "join" operations should come immediately
859
+ /// before a table join. For example: `User.where.RIGHT_JOIN.id.EQ(Role.where.userID)`.
860
+ /// They are "toggles", and so will remain "on" once used. In short, if
861
+ /// you specify a `RIGHT_JOIN` at the very beginning of your query, then
862
+ /// **ALL** table joins in the query will be `RIGHT JOIN`, unless you specify
863
+ /// other join types, i.e. `User.where.RIGHT_JOIN.id.EQ(Role.where.userID).AND.INNER_JOIN.id.EQ(Organization.where.userID)`.
864
+ ///
865
+ /// SyntaxType: FunctionDeclaration
866
+ ///
867
+ /// Arguments:
868
+ /// outerJoin: boolean = false
869
+ /// If `true`, then this will result in a `RIGHT OUTER JOIN` if the database supports it.
870
+ ///
871
+ /// Return: <see>ModelScope</see>
872
+ /// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query.
376
873
  RIGHT_JOIN = ProxyClass.autoCall(function(outerJoin) {
377
874
  this._pushOperationOntoStack({ join: true, operator: 'JOIN', queryProp: 'RIGHT_JOIN', value: 'right', joinType: 'right', joinOuter: !!outerJoin });
378
875
  return this._fetchScope('model');
379
876
  });
380
877
 
878
+ /// Specify a `FULL JOIN` operation for joining tables.
879
+ ///
880
+ /// This method does not need to be called, but it can
881
+ /// be called if desired.
882
+ ///
883
+ /// All Mythix ORM "join" operations should come immediately
884
+ /// before a table join. For example: `User.where.FULL_JOIN.id.EQ(Role.where.userID)`.
885
+ /// They are "toggles", and so will remain "on" once used. In short, if
886
+ /// you specify a `FULL_JOIN` at the very beginning of your query, then
887
+ /// **ALL** table joins in the query will be `FULL JOIN`, unless you specify
888
+ /// other join types, i.e. `User.where.FULL_JOIN.id.EQ(Role.where.userID).AND.INNER_JOIN.id.EQ(Organization.where.userID)`.
889
+ ///
890
+ /// SyntaxType: FunctionDeclaration
891
+ ///
892
+ /// Arguments:
893
+ /// outerJoin: boolean = false
894
+ /// If `true`, then this will result in a `FULL OUTER JOIN` if the database supports it.
895
+ ///
896
+ /// Return: <see>ModelScope</see>
897
+ /// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query.
381
898
  FULL_JOIN = ProxyClass.autoCall(function(outerJoin) {
382
899
  this._pushOperationOntoStack({ join: true, operator: 'JOIN', queryProp: 'FULL_JOIN', value: 'full', joinType: 'full', joinOuter: !!outerJoin });
383
900
  return this._fetchScope('model');
384
901
  });
385
902
 
903
+ /// Specify a `CROSS JOIN` operation for joining tables.
904
+ ///
905
+ /// This method does not need to be called.
906
+ ///
907
+ /// All Mythix ORM "join" operations should come immediately
908
+ /// before a table join. For example: `User.where.CROSS_JOIN.id.EQ(Role.where.userID)`.
909
+ /// They are "toggles", and so will remain "on" once used. In short, if
910
+ /// you specify a `CROSS_JOIN` at the very beginning of your query, then
911
+ /// **ALL** table joins in the query will be `CROSS JOIN`, unless you specify
912
+ /// other join types, i.e. `User.where.CROSS_JOIN.id.EQ(Role.where.userID).AND.INNER_JOIN.id.EQ(Organization.where.userID)`.
913
+ ///
914
+ /// SyntaxType: FunctionDeclaration
915
+ ///
916
+ /// Return: <see>ModelScope</see>
917
+ /// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query.
386
918
  CROSS_JOIN = ProxyClass.autoCall(function() {
387
919
  this._pushOperationOntoStack({ join: true, operator: 'JOIN', queryProp: 'CROSS_JOIN', value: 'cross', joinType: 'cross', joinOuter: false });
388
920
  return this._fetchScope('model');
389
921
  });
390
922
 
923
+ /// Specify a custom join operation for the underlying database.
924
+ /// It is recommended that you use a literal, though that isn't required.
925
+ ///
926
+ /// All Mythix ORM "join" operations should come immediately
927
+ /// before a table join. For example: `User.where.JOIN('RIGHT INNER JOIN').id.EQ(Role.where.userID)`.
928
+ /// They are "toggles", and so will remain "on" once used.
929
+ ///
930
+ /// Note:
931
+ /// This method should be avoided if at all possible, since it will
932
+ /// likely be database specific, making your code not as portable to
933
+ /// another database driver.
934
+ ///
935
+ /// Note:
936
+ /// Is there a standard join type that Mythix ORM missed, that should be
937
+ /// supported across most or all databases? If so, let us know by opening
938
+ /// an Issue or PR on our GitHub page. Thank you!
939
+ ///
940
+ /// Arguments:
941
+ /// type: <see>Literal</see> | string
942
+ /// The join type to use in the underlying database (a literal value).
943
+ ///
944
+ /// Return: <see>ModelScope</see>
945
+ /// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query.
391
946
  JOIN(type) {
392
947
  if (!(Nife.instanceOf(type, 'string') || LiteralBase.isLiteral(type)))
393
948
  throw new Error('QueryEngine::ModelScope::JOIN: Invalid value provided. Value must be a valid string or Literal specifying JOIN type.');
@@ -395,6 +950,10 @@ class ModelScope extends QueryEngineBase {
395
950
  this._pushOperationOntoStack({ join: true, operator: 'JOIN', queryProp: 'JOIN', value: type, joinType: type, joinOuter: false });
396
951
  return this._fetchScope('model');
397
952
  }
953
+
954
+ toString(...args) {
955
+ return this.getOperationContext().queryEngineScope.toString(...args);
956
+ }
398
957
  }
399
958
 
400
959
  module.exports = ModelScope;