mythix-orm-sql-base 1.9.6 → 1.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,7 +14,33 @@ const {
14
14
  const DefaultHelpers = Types.DefaultHelpers;
15
15
  const LiteralBase = Literals.LiteralBase;
16
16
 
17
+ /// The "base" SQL generator for all SQL-type databases.
17
18
  class SQLQueryGeneratorBase extends QueryGeneratorBase {
19
+ /// Escape a field name, usually for a projection alias.
20
+ /// This method is primarily used for generating aliases
21
+ /// for projected fields.
22
+ ///
23
+ /// This method will take the field it is given, and turn
24
+ /// it into a fully qualified field name, escaped as an identifier
25
+ /// for the underlying database.
26
+ ///
27
+ /// For example, given the field `User.fields.id`, this method will return
28
+ /// `"User:id"`--assuming that double quotes are used in the underlying
29
+ /// database to escape an identifier.
30
+ ///
31
+ /// Arguments:
32
+ /// Model: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
33
+ /// The model that owns the `field` provided.
34
+ /// field: [Field](https://github.com/th317erd/mythix-orm/wiki/Field)
35
+ /// The field to operate on.
36
+ /// options?: object
37
+ /// Options for the operation.
38
+ /// | Option | Type | Default Value | Description |
39
+ /// | ------ | ---- | ------------- | ----------- |
40
+ /// | `fieldNameOnly` | `boolean` | `false` | If `true`, then only use the `fieldName` of the field, ignoring the model name in the operation (resulting in a non-fully-qualified field name) |
41
+ ///
42
+ /// Return: string
43
+ /// The field's name, escaped for the underlying database. i.e. `"User:id"`.
18
44
  getEscapedFieldName(_Model, field, options) {
19
45
  let isString = Nife.instanceOf(field, 'string');
20
46
  let fieldName = (isString) ? field : field.fieldName;
@@ -29,6 +55,34 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
29
55
  return `"${field.Model.getModelName()}:${fieldName}"`;
30
56
  }
31
57
 
58
+ /// Get the escaped column name for the field provided.
59
+ ///
60
+ /// Given a field, return the full column name (including the table)
61
+ /// of that field, escaped for the underlying databases.
62
+ /// For example, given the field `User.fields.id`, return `"users"."id"`--assuming
63
+ /// the underlying database uses double quotes for escaping identifiers.
64
+ ///
65
+ /// This method will use the `columnName` defined on the field for the name
66
+ /// of the column, if defined, or will use `fieldName` as defined on the field
67
+ /// as the column name if no `columnName` is defined. To get the name of the table,
68
+ /// this method will use [getTableName](https://github.com/th317erd/mythix-orm/wiki/Model#method-static-getTableName)
69
+ /// on the model that owns the `field` provided.
70
+ ///
71
+ /// Arguments:
72
+ /// Model: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
73
+ /// The model that owns the `field` provided.
74
+ /// field: [Field](https://github.com/th317erd/mythix-orm/wiki/Field)
75
+ /// The field to operate on.
76
+ /// options?: object
77
+ /// Options for the operation.
78
+ /// | Option | Type | Default Value | Description |
79
+ /// | ------ | ---- | ------------- | ----------- |
80
+ /// | `columnNameOnly` | `boolean` | `false` | If `true`, then escape only the column name, and don't include the name of the table. |
81
+ /// | `columnNamePrefix` | `string` | `''` | Prefix the column name with this value before escaping it. |
82
+ /// | `tableNamePrefix` | `string` | `''` | Only applicable if `columnNameOnly` is `false`. Used to prefix the table name before escaping it. |
83
+ ///
84
+ /// Return: string
85
+ /// The fully escaped column name, including the table the column exists on. i.e. `"users"."id"`.
32
86
  getEscapedColumnName(_Model, field, options) {
33
87
  let isString = Nife.instanceOf(field, 'string');
34
88
  let columnName = (isString) ? field : (field.columnName || field.fieldName);
@@ -46,6 +100,27 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
46
100
  return `${this.getEscapedTableName(Model)}.${this.escapeID(columnName)}`;
47
101
  }
48
102
 
103
+ /// Get the escaped table name for the model or field provided.
104
+ ///
105
+ /// Give a model or field, access the model to get the table name
106
+ /// for the model, and escape it for the underlying database.
107
+ /// If given a field, then the parent model for that field will be
108
+ /// retrieved via `field.Model`. Once a model is ascertained, then
109
+ /// call [getTableName](https://github.com/th317erd/mythix-orm/wiki/Model#method-static-getTableName)
110
+ /// on the model to get the table name in the underlying database for this
111
+ /// model, escaping it before it is returned.
112
+ ///
113
+ /// Arguments:
114
+ /// modelOrField: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model) | [Field](https://github.com/th317erd/mythix-orm/wiki/Field)
115
+ /// A model class, or a field to fetch the model class from.
116
+ /// options?: object
117
+ /// Options for the operation.
118
+ /// | Option | Type | Default Value | Description |
119
+ /// | ------ | ---- | ------------- | ----------- |
120
+ /// | `tableNamePrefix` | `string` | `''` | Used to prefix the table name before escaping it. |
121
+ ///
122
+ /// Return: string
123
+ /// The table name for the given model, escaped for the underlying database. i.e. `"users"`.
49
124
  getEscapedTableName(_modelOrField, options) {
50
125
  let Model = (_modelOrField.Model) ? _modelOrField.Model : _modelOrField;
51
126
  let tableName = Model.getTableName(this.connection);
@@ -56,6 +131,33 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
56
131
  return this.escapeID(tableName);
57
132
  }
58
133
 
134
+ /// Given a field, escape it for the projection,
135
+ /// optionally including an alias for the field.
136
+ ///
137
+ /// For example, given the field `User.fields.id`, return
138
+ /// the projected field with an alias: `"users"."id" AS "User:id"`.
139
+ /// By default, this will use <see>SQLQueryGeneratorBase.getEscapedFieldName</see>
140
+ /// for the alias, unless the `as` `options` is provided. If `as`
141
+ /// is provided to the `options`, then use that as the literal field
142
+ /// alias instead.
143
+ ///
144
+ /// Arguments:
145
+ /// Model: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
146
+ /// The model that owns the `field` provided.
147
+ /// field: [Field](https://github.com/th317erd/mythix-orm/wiki/Field)
148
+ /// The field to operate on.
149
+ /// options?: object
150
+ /// Options for the operation.
151
+ /// | Option | Type | Default Value | Description |
152
+ /// | ------ | ---- | ------------- | ----------- |
153
+ /// | `noProjectionAliases` | `boolean` | `false` | If `true`, then escape only the column name, and don't include the alias for the projection. |
154
+ /// | `columnNameOnly` | `boolean` | `false` | If `true`, then escape only the column name, and don't include the name of the table. |
155
+ /// | `columnNamePrefix` | `string` | `''` | Prefix the column name with this value before escaping it. |
156
+ /// | `tableNamePrefix` | `string` | `''` | Only applicable if `columnNameOnly` is `false`. Used to prefix the table name before escaping it. |
157
+ /// | `as` | `string` | `undefined` | If set to a valid string, then this will be used for the `AS` alias of the column instead of the default. |
158
+ ///
159
+ /// Return: string
160
+ /// The fully escaped column name, for use in the projection. i.e. `"users"."id" AS "User:id"`.
59
161
  // eslint-disable-next-line no-unused-vars
60
162
  getEscapedProjectionName(Model, field, options) {
61
163
  if (options && options.noProjectionAliases)
@@ -64,8 +166,39 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
64
166
  return `${this.getEscapedColumnName(Model, field, options)} AS ${(options && options.as) ? this.escapeID(options.as) : this.getEscapedFieldName(Model, field, options)}`;
65
167
  }
66
168
 
169
+ /// Pass all non-virtual model fields through
170
+ /// <see>SQLQueryGeneratorBase.getEscapedFieldName</see>,
171
+ /// <see>SQLQueryGeneratorBase.getEscapedColumnName</see>,
172
+ /// or <see>SQLQueryGeneratorBase.getEscapedProjectionName</see>.
173
+ ///
174
+ /// This method is used to bulk-escape all model fields, using one
175
+ /// of the methods listed above. If the `asProjection` `options` is
176
+ /// used, then all fields will be escaped using <see>SQLQueryGeneratorBase.getEscapedProjectionName</see>.
177
+ /// If the `asColumn` `options` is used, then escape all fields using
178
+ /// <see>SQLQueryGeneratorBase.getEscapedColumnName</see>. Otherwise,
179
+ /// if no `options` are specified, then escape all model fields using
180
+ /// <see>SQLQueryGeneratorBase.getEscapedFieldName</see> instead.
181
+ ///
182
+ /// Arguments:
183
+ /// Model: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
184
+ /// The model whose fields are to be escaped. Only non-virtual fields are operated upon.
185
+ /// options?: object
186
+ /// Options for the operation.
187
+ /// | Option | Type | Default Value | Description |
188
+ /// | ------ | ---- | ------------- | ----------- |
189
+ /// | `asProjection` | `boolean` | `false` | If `true`, then use <see>SQLQueryGeneratorBase.getEscapedProjectionName</see> to escape the model's fields. |
190
+ /// | `asColumn` | `boolean` | `false` | If `true`, then use <see>SQLQueryGeneratorBase.getEscapedColumnName</see> to escape the model's fields. |
191
+ /// | `noProjectionAliases` | `boolean` | `false` | If `true`, then escape only the column name, and don't include the alias for the projection. |
192
+ /// | `columnNameOnly` | `boolean` | `false` | If `true`, then escape only the column name, and don't include the name of the table. |
193
+ /// | `columnNamePrefix` | `string` | `''` | Prefix the column name with this value before escaping it. |
194
+ /// | `tableNamePrefix` | `string` | `''` | Only applicable if `columnNameOnly` is `false`. Used to prefix the table name before escaping it. |
195
+ ///
196
+ /// Return: object
197
+ /// Return an object that maps all escaped model fields. Each key will be the fully qualified name of the field,
198
+ /// each value will be the escaped field as a string.
67
199
  // eslint-disable-next-line no-unused-vars
68
- getEscapedModelFields(Model, options) {
200
+ getEscapedModelFields(Model, _options) {
201
+ let options = Object.assign(_options || {}, { as: null });
69
202
  let fields = {};
70
203
  let modelName = Model.getModelName();
71
204
 
@@ -88,10 +221,21 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
88
221
  return fields;
89
222
  }
90
223
 
91
- isFieldIdentifier(str) {
92
- return (/^"[^"]+"."[^"]+"|"\w+:[\w.]+"/i).test(str);
93
- }
94
-
224
+ /// Get the `ORDER` clause from the provided [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine).
225
+ /// If none is found, then call [Connection.getDefaultOrder](https://github.com/th317erd/mythix-orm/wiki/ConnectionBase#method-getDefaultOrder)
226
+ /// to get the default order for the operation.
227
+ ///
228
+ /// Arguments:
229
+ /// queryEngine: [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine)
230
+ /// The query engine to fetch the `ORDER` clause from.
231
+ /// options?: object
232
+ /// The options object provided to the operation that is taking place. This isn't used
233
+ /// by this method, but instead is passed off to [Connection.getDefaultOrder](https://github.com/th317erd/mythix-orm/wiki/ConnectionBase#method-getDefaultOrder)
234
+ /// in case the connection (or user) needs the options to produce a default ordering.
235
+ ///
236
+ /// Return: Map<string, { value: Field | Literal | string; direction?: '+' | '-'; ... }>
237
+ /// Return the field-set for the default ordering to apply to the operation taking place.
238
+ /// This `Map` should have the same format as is returned by [ModelScope.mergeFields](https://github.com/th317erd/mythix-orm/wiki/ModelScope#method-mergeFields).
95
239
  getQueryEngineOrder(queryEngine, _options) {
96
240
  let options = _options || {};
97
241
  let context = queryEngine.getOperationContext();
@@ -100,6 +244,35 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
100
244
  return (order && order.size) ? order : this.connection.getDefaultOrder(context.rootModel, options);
101
245
  }
102
246
 
247
+ /// Get the field projection for the operation taking place.
248
+ ///
249
+ /// This will prepare all fields in the projection, along with
250
+ /// any literals, and will also merge any `ORDER BY` clause fields
251
+ /// into the projection. The result will be either a `Map`, containing
252
+ /// all projected fields and literals, or will be an `Array` of just
253
+ /// the projected fields, escaped and prepared for the underlying database.
254
+ /// A `Map` of the fields will be returned instead of an array if the `asMap`
255
+ /// argument is set to `true`.
256
+ ///
257
+ /// Arguments:
258
+ /// queryEngine: [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine)
259
+ /// The query engine to fetch the field projection (and order) from.
260
+ /// options?: object
261
+ /// Options for the operation. These options aren't used directly by this method,
262
+ /// but instead are passed off to the sub-methods that are called to complete the
263
+ /// operation, such as the `toString` method for literals, the <see>SQLQueryGeneratorBase.getQueryEngineOrder</see>
264
+ /// to get the field ordering, and to the <see>SQLQueryGeneratorBase.getEscapedProjectionName</see> to
265
+ /// project the fields.
266
+ /// asMap: boolean
267
+ /// If `true`, then return a `Map` of the projected fields, instead of an `Array`. This
268
+ /// is used for example internally by `SELECT` operations to know which fields were
269
+ /// projected. Each key in this `Map` is a fully qualified field name (to link the projection
270
+ /// back to its field), or a fully expanded literal string for literals. The value for each
271
+ /// property in this map in the projected field or literal value itself, as a string.
272
+ ///
273
+ /// Return: Map<string, string> | Array<string>
274
+ /// A `Map` of the projected fields if the `asMap` argument is `true`, or an `Array` of the
275
+ /// projected fields otherwise.
103
276
  getProjectedFields(queryEngine, _options, asMap) {
104
277
  let options = this.stackAssign(_options || {}, { isProjection: true });
105
278
  let context = queryEngine.getOperationContext();
@@ -145,6 +318,50 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
145
318
  return Array.from(allProjectionFields.values());
146
319
  }
147
320
 
321
+ /// Given two "operation contexts" from a [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine)
322
+ /// instance, collection the information required to join two tables.
323
+ ///
324
+ /// Using the "left side" context of the operation being calculated, find the "root model"
325
+ /// of the query, and compile table-join information with the "right side" context of
326
+ /// the operation, joining the "root model" on the left-side with the model and field specified on
327
+ /// the right-side of the operation.
328
+ ///
329
+ /// A table-join looks like `User.where.id.EQ(Role.where.userID)`. In this context, the `User.where.id`
330
+ /// would be the "left side", and `Role.where.userID` would be the "right side".
331
+ ///
332
+ /// Interface:
333
+ /// interface TableJoinInformation {
334
+ /// operator: string; // The operator used for the table-join, i.e. "EQ"
335
+ /// joinType: string; // The database specific join-type for the table-join, i.e. "INNER JOIN"
336
+ /// rootModelName: string; // The name of the "root model" for the query
337
+ /// joinModel: class Model; // The the model being joined to the "root model"
338
+ /// joinModelName: string; // The name of the model being joined to the "root model"
339
+ /// leftSideModel: class Model; // The model on the left-side of the table-join
340
+ /// leftSideModelName: string; // The name of the model on the left-side of the table-join
341
+ /// leftQueryContext: object; // The "operation context" of the left-side of the operation
342
+ /// leftSideField: Field; // The field on the left side of the table-join
343
+ /// rightSideModel: class Model; // The model on the right-side of the table-join
344
+ /// rightSideModelName: string; // The model name of the model on the right-side of the table-join
345
+ /// rightQueryContext: object; // The "operation context" of the right-side of the operation
346
+ /// rightSideField: Field; // The field on the right-side of the table-join
347
+ /// }
348
+ ///
349
+ /// Arguments:
350
+ /// leftQueryContext: object
351
+ /// The left-side "operation context" of the query engine to compile the "root model" information
352
+ /// from.
353
+ /// rightQueryContext: object
354
+ /// The right-side "operation context" of the query engine to compile the "join model" information
355
+ /// from.
356
+ /// joinType: string
357
+ /// The join-type (LEFT, RIGHT, INNER, CROSS, etc...) for the join-table operation.
358
+ /// options?: object
359
+ /// The options for the operation. These aren't used by default by Mythix ORM, and instead are
360
+ /// provided for the user if the user should overload this method. These will be the options for
361
+ /// the operation taking place (i.e. a `SELECT` operation).
362
+ ///
363
+ /// Return: TableJoinInformation
364
+ /// Return an object containing all information needed to generate a table-join query.
148
365
  // eslint-disable-next-line no-unused-vars
149
366
  getJoinTableInfoFromQueryContexts(leftQueryContext, rightQueryContext, joinType, options) {
150
367
  let rootModel = leftQueryContext.rootModel;
@@ -193,6 +410,30 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
193
410
  };
194
411
  }
195
412
 
413
+ /// Get an `AS` projection alias for the literal being stringified,
414
+ /// but only if a projection alias is applicable.
415
+ ///
416
+ /// If the literal is a "sub-field" (i.e. the `FieldLiteral` in `new CountLiteral(new FieldLiteral(...))`)
417
+ /// then don't return a projection alias (i.e. the alias needs to be on `CountLiteral` in this example, not
418
+ /// on the sub-field `FieldLiteral`).
419
+ ///
420
+ /// Also don't return an alias if the `noProjectionAliases` `options` is set to `true`. This might
421
+ /// be the case for example if we are "projecting" the literal for an `ORDER BY` clause.
422
+ ///
423
+ /// Arguments:
424
+ /// literal: inherits from [LiteralBase](https://github.com/th317erd/mythix-orm/wiki/LiteralBase)
425
+ /// The literal that is being stringified.
426
+ /// options?: object
427
+ /// Options for the operation.
428
+ /// | Option | Type | Default Value | Description |
429
+ /// | ------ | ---- | ------------- | ----------- |
430
+ /// | `isSubField` | `boolean` | `false` | If `true`, the engine is reporting that this is a "sub-field", or literal inside a literal... if this is the case, then don't return an `AS` field alias. |
431
+ /// | `noProjectionAliases` | `boolean` | `false` | If `true`, the engine is reporting that this is part of the query that shouldn't have field aliases, such as an `ORDER BY` clause. |
432
+ /// | `as` | `string` | `false` | If `true`, the engine is reporting that this is part of the query that shouldn't have field aliases, such as an `ORDER BY` clause. |
433
+ ///
434
+ /// Return: string
435
+ /// An empty string if a field alias isn't allowed, or an ` AS ...` postfix string
436
+ /// to apply to the stringified `literal` provided if an alias is allowed and requested.
196
437
  _getLiteralAlias(literal, options) {
197
438
  if (options && options.isSubField)
198
439
  return '';
@@ -207,6 +448,23 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
207
448
  return ` AS ${this.escapeID(as)}`;
208
449
  }
209
450
 
451
+ /// Convert an [AverageLiteral](https://github.com/th317erd/mythix-orm/wiki/AverageLiteral) to
452
+ /// a string for use in the underlying database.
453
+ ///
454
+ /// Arguments:
455
+ /// literal: [AverageLiteral](https://github.com/th317erd/mythix-orm/wiki/AverageLiteral)
456
+ /// The [AverageLiteral](https://github.com/th317erd/mythix-orm/wiki/AverageLiteral) to stringify.
457
+ /// options?: object
458
+ /// Options for the operation. Listed below are the common options for all literals. There may also be
459
+ /// literal or connection specific options that can be supplied.
460
+ /// | Option | Type | Default Value | Description |
461
+ /// | ------ | ---- | ------------- | ----------- |
462
+ /// | `isSubField` | `boolean` | `false` | If `true`, the engine is reporting that this is a "sub-field", or literal inside a literal... if this is the case, then don't return an `AS` field alias. |
463
+ /// | `noProjectionAliases` | `boolean` | `false` | If `true`, the engine is reporting that this is part of the query that shouldn't have field aliases, such as an `ORDER BY` clause. |
464
+ /// | `as` | `string` | `undefined` | If set to a valid string, then this will be used for the `AS` alias of the column instead of the default. |
465
+ ///
466
+ /// Return: string
467
+ /// The literal provided, stringified for use in the underlying database.
210
468
  _averageLiteralToString(literal, options) {
211
469
  if (!literal || !LiteralBase.isLiteral(literal))
212
470
  return;
@@ -222,6 +480,23 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
222
480
  return `AVG(${escapedColumnName})${this._getLiteralAlias(literal, options)}`;
223
481
  }
224
482
 
483
+ /// Convert an [CountLiteral](https://github.com/th317erd/mythix-orm/wiki/CountLiteral) to
484
+ /// a string for use in the underlying database.
485
+ ///
486
+ /// Arguments:
487
+ /// literal: [CountLiteral](https://github.com/th317erd/mythix-orm/wiki/CountLiteral)
488
+ /// The [CountLiteral](https://github.com/th317erd/mythix-orm/wiki/CountLiteral) to stringify.
489
+ /// options?: object
490
+ /// Options for the operation. Listed below are the common options for all literals. There may also be
491
+ /// literal or connection specific options that can be supplied.
492
+ /// | Option | Type | Default Value | Description |
493
+ /// | ------ | ---- | ------------- | ----------- |
494
+ /// | `isSubField` | `boolean` | `false` | If `true`, the engine is reporting that this is a "sub-field", or literal inside a literal... if this is the case, then don't return an `AS` field alias. |
495
+ /// | `noProjectionAliases` | `boolean` | `false` | If `true`, the engine is reporting that this is part of the query that shouldn't have field aliases, such as an `ORDER BY` clause. |
496
+ /// | `as` | `string` | `undefined` | If set to a valid string, then this will be used for the `AS` alias of the column instead of the default. |
497
+ ///
498
+ /// Return: string
499
+ /// The literal provided, stringified for use in the underlying database.
225
500
  _countLiteralToString(literal, options) {
226
501
  if (!literal || !LiteralBase.isLiteral(literal))
227
502
  return;
@@ -241,6 +516,23 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
241
516
  return `COUNT(${escapedColumnName})${this._getLiteralAlias(literal, options)}`;
242
517
  }
243
518
 
519
+ /// Convert an [DistinctLiteral](https://github.com/th317erd/mythix-orm/wiki/DistinctLiteral) to
520
+ /// a string for use in the underlying database.
521
+ ///
522
+ /// Arguments:
523
+ /// literal: [DistinctLiteral](https://github.com/th317erd/mythix-orm/wiki/DistinctLiteral)
524
+ /// The [DistinctLiteral](https://github.com/th317erd/mythix-orm/wiki/DistinctLiteral) to stringify.
525
+ /// options?: object
526
+ /// Options for the operation. Listed below are the common options for all literals. There may also be
527
+ /// literal or connection specific options that can be supplied.
528
+ /// | Option | Type | Default Value | Description |
529
+ /// | ------ | ---- | ------------- | ----------- |
530
+ /// | `isSubField` | `boolean` | `false` | If `true`, the engine is reporting that this is a "sub-field", or literal inside a literal... if this is the case, then don't return an `AS` field alias. |
531
+ /// | `noProjectionAliases` | `boolean` | `false` | If `true`, the engine is reporting that this is part of the query that shouldn't have field aliases, such as an `ORDER BY` clause. |
532
+ /// | `as` | `string` | `undefined` | If set to a valid string, then this will be used for the `AS` alias of the column instead of the default. |
533
+ ///
534
+ /// Return: string
535
+ /// The literal provided, stringified for use in the underlying database.
244
536
  _distinctLiteralToString(literal, options) {
245
537
  if (!literal || !LiteralBase.isLiteral(literal))
246
538
  return;
@@ -270,6 +562,23 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
270
562
  return `DISTINCT ON(${escapedColumnName})`;
271
563
  }
272
564
 
565
+ /// Convert an [FieldLiteral](https://github.com/th317erd/mythix-orm/wiki/FieldLiteral) to
566
+ /// a string for use in the underlying database.
567
+ ///
568
+ /// Arguments:
569
+ /// literal: [FieldLiteral](https://github.com/th317erd/mythix-orm/wiki/FieldLiteral)
570
+ /// The [FieldLiteral](https://github.com/th317erd/mythix-orm/wiki/FieldLiteral) to stringify.
571
+ /// options?: object
572
+ /// Options for the operation. Listed below are the common options for all literals. There may also be
573
+ /// literal or connection specific options that can be supplied.
574
+ /// | Option | Type | Default Value | Description |
575
+ /// | ------ | ---- | ------------- | ----------- |
576
+ /// | `isSubField` | `boolean` | `false` | If `true`, the engine is reporting that this is a "sub-field", or literal inside a literal... if this is the case, then don't return an `AS` field alias. |
577
+ /// | `noProjectionAliases` | `boolean` | `false` | If `true`, the engine is reporting that this is part of the query that shouldn't have field aliases, such as an `ORDER BY` clause. |
578
+ /// | `as` | `string` | `undefined` | If set to a valid string, then this will be used for the `AS` alias of the column instead of the default. |
579
+ ///
580
+ /// Return: string
581
+ /// The literal provided, stringified for use in the underlying database.
273
582
  _fieldLiteralToString(literal, options) {
274
583
  if (!literal || !LiteralBase.isLiteral(literal))
275
584
  return;
@@ -282,6 +591,23 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
282
591
  return this.getEscapedProjectionName(field.Model, field, this.stackAssign(options, { noProjectionAliases }, literal.options));
283
592
  }
284
593
 
594
+ /// Convert an [MaxLiteral](https://github.com/th317erd/mythix-orm/wiki/MaxLiteral) to
595
+ /// a string for use in the underlying database.
596
+ ///
597
+ /// Arguments:
598
+ /// literal: [MaxLiteral](https://github.com/th317erd/mythix-orm/wiki/MaxLiteral)
599
+ /// The [MaxLiteral](https://github.com/th317erd/mythix-orm/wiki/MaxLiteral) to stringify.
600
+ /// options?: object
601
+ /// Options for the operation. Listed below are the common options for all literals. There may also be
602
+ /// literal or connection specific options that can be supplied.
603
+ /// | Option | Type | Default Value | Description |
604
+ /// | ------ | ---- | ------------- | ----------- |
605
+ /// | `isSubField` | `boolean` | `false` | If `true`, the engine is reporting that this is a "sub-field", or literal inside a literal... if this is the case, then don't return an `AS` field alias. |
606
+ /// | `noProjectionAliases` | `boolean` | `false` | If `true`, the engine is reporting that this is part of the query that shouldn't have field aliases, such as an `ORDER BY` clause. |
607
+ /// | `as` | `string` | `undefined` | If set to a valid string, then this will be used for the `AS` alias of the column instead of the default. |
608
+ ///
609
+ /// Return: string
610
+ /// The literal provided, stringified for use in the underlying database.
285
611
  _maxLiteralToString(literal, options) {
286
612
  if (!literal || !LiteralBase.isLiteral(literal))
287
613
  return;
@@ -297,6 +623,23 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
297
623
  return `MAX(${escapedColumnName})${this._getLiteralAlias(literal, options)}`;
298
624
  }
299
625
 
626
+ /// Convert an [MinLiteral](https://github.com/th317erd/mythix-orm/wiki/MinLiteral) to
627
+ /// a string for use in the underlying database.
628
+ ///
629
+ /// Arguments:
630
+ /// literal: [MinLiteral](https://github.com/th317erd/mythix-orm/wiki/MinLiteral)
631
+ /// The [MinLiteral](https://github.com/th317erd/mythix-orm/wiki/MinLiteral) to stringify.
632
+ /// options?: object
633
+ /// Options for the operation. Listed below are the common options for all literals. There may also be
634
+ /// literal or connection specific options that can be supplied.
635
+ /// | Option | Type | Default Value | Description |
636
+ /// | ------ | ---- | ------------- | ----------- |
637
+ /// | `isSubField` | `boolean` | `false` | If `true`, the engine is reporting that this is a "sub-field", or literal inside a literal... if this is the case, then don't return an `AS` field alias. |
638
+ /// | `noProjectionAliases` | `boolean` | `false` | If `true`, the engine is reporting that this is part of the query that shouldn't have field aliases, such as an `ORDER BY` clause. |
639
+ /// | `as` | `string` | `undefined` | If set to a valid string, then this will be used for the `AS` alias of the column instead of the default. |
640
+ ///
641
+ /// Return: string
642
+ /// The literal provided, stringified for use in the underlying database.
300
643
  _minLiteralToString(literal, options) {
301
644
  if (!literal || !LiteralBase.isLiteral(literal))
302
645
  return;
@@ -312,6 +655,23 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
312
655
  return `MIN(${escapedColumnName})${this._getLiteralAlias(literal, options)}`;
313
656
  }
314
657
 
658
+ /// Convert an [SumLiteral](https://github.com/th317erd/mythix-orm/wiki/SumLiteral) to
659
+ /// a string for use in the underlying database.
660
+ ///
661
+ /// Arguments:
662
+ /// literal: [SumLiteral](https://github.com/th317erd/mythix-orm/wiki/SumLiteral)
663
+ /// The [SumLiteral](https://github.com/th317erd/mythix-orm/wiki/SumLiteral) to stringify.
664
+ /// options?: object
665
+ /// Options for the operation. Listed below are the common options for all literals. There may also be
666
+ /// literal or connection specific options that can be supplied.
667
+ /// | Option | Type | Default Value | Description |
668
+ /// | ------ | ---- | ------------- | ----------- |
669
+ /// | `isSubField` | `boolean` | `false` | If `true`, the engine is reporting that this is a "sub-field", or literal inside a literal... if this is the case, then don't return an `AS` field alias. |
670
+ /// | `noProjectionAliases` | `boolean` | `false` | If `true`, the engine is reporting that this is part of the query that shouldn't have field aliases, such as an `ORDER BY` clause. |
671
+ /// | `as` | `string` | `undefined` | If set to a valid string, then this will be used for the `AS` alias of the column instead of the default. |
672
+ ///
673
+ /// Return: string
674
+ /// The literal provided, stringified for use in the underlying database.
315
675
  _sumLiteralToString(literal, options) {
316
676
  if (!literal || !LiteralBase.isLiteral(literal))
317
677
  return;
@@ -327,106 +687,86 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
327
687
  return `SUM(${escapedColumnName})${this._getLiteralAlias(literal, options)}`;
328
688
  }
329
689
 
690
+ /// A convenience method that proxies to <see>SQLConnectionBase.prepareArrayValuesForSQL</see>.
691
+ ///
692
+ /// See: SQLConnectionBase.prepareArrayValuesForSQL
330
693
  prepareArrayValuesForSQL(array) {
331
694
  return this.connection.prepareArrayValuesForSQL(array);
332
695
  }
333
696
 
334
- parseFieldProjection = (str, getRawField) => {
335
- let modelName;
336
- let fieldName;
337
- let projectionField;
338
-
339
- str.replace(/(AS\s+)?"(\w+):([\w.]+)"/i, (m, as, _modelName, _fieldName) => {
340
- modelName = _modelName;
341
- fieldName = _fieldName;
342
- });
343
-
344
- if (!modelName || !fieldName) {
345
- // Reverse search model and field name
346
- // based on table and column name
347
- str.replace(/"([^"]+)"."([^"]+)"/i, (m, _tableName, _columnName) => {
348
- this.connection.findModelField(({ field, stop }) => {
349
- if (field.columnName !== _columnName)
350
- return;
351
-
352
- let tableName = field.Model.getTableName(this.connection);
353
- if (tableName !== _tableName)
354
- return;
355
-
356
- if (getRawField) {
357
- projectionField = field;
358
-
359
- stop();
360
-
361
- return;
362
- }
363
-
364
- modelName = field.Model.getModelName();
365
- fieldName = field.fieldName;
366
-
367
- stop();
368
- });
369
- });
370
-
371
- if (getRawField && projectionField)
372
- return projectionField;
373
- }
374
-
375
- if (getRawField && modelName && fieldName) {
376
- let field = this.connection.getField(fieldName, modelName);
377
- if (field)
378
- return field;
379
- } else if (!modelName || !fieldName) {
380
- return str;
381
- }
382
-
383
- return `${modelName}:${fieldName}`;
384
- };
385
-
386
- parseFieldProjectionToFieldMap(selectStatement) {
387
- let firstPart = selectStatement.replace(/[\r\n]/g, ' ').split(/\s+FROM\s+/i)[0].replace(/^SELECT\s+/i, '').trim();
388
- let fieldParts = firstPart.split(',');
389
- let projectionFieldMap = new Map();
390
-
391
- for (let i = 0, il = fieldParts.length; i < il; i++) {
392
- let fieldPart = fieldParts[i].trim();
393
- let field = this.parseFieldProjection(fieldPart, true);
394
-
395
- if (field !== fieldPart)
396
- projectionFieldMap.set(`${field.Model.getModelName()}:${field.fieldName}`, this.getEscapedProjectionName(field.Model, field));
397
- else
398
- projectionFieldMap.set(field, field);
399
-
400
- // If this isn't a field, then add it
401
- if (!this.isFieldIdentifier(fieldPart))
402
- projectionFieldMap.set(fieldPart, fieldPart);
403
- }
404
-
405
- return projectionFieldMap;
406
- }
407
-
408
- generateSelectQueryFieldProjection(queryEngine, options, asMap) {
409
- let projectedFields = this.getProjectedFields(queryEngine, options, asMap);
410
-
411
- if (asMap === true) {
412
- return projectedFields;
413
- } else {
414
- let projectedFieldList = Array.from(projectedFields.values()).join(',');
415
-
416
- let distinct = queryEngine.getOperationContext().distinct;
417
- if (distinct) {
418
- let result = distinct.toString(this.connection, { isProjection: true });
419
- return `${result} ${projectedFieldList}`;
420
- }
421
-
422
- return projectedFieldList;
697
+ /// Generate a field projection for a `SELECT` statement.
698
+ /// If a `DISTINCT` operation is in-use, then this will always
699
+ /// prefix any and all fields projected.
700
+ ///
701
+ /// Arguments:
702
+ /// queryEngine: [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine)
703
+ /// The query engine to use to generate the field projection from.
704
+ /// options?: object
705
+ /// Options that are passed through the operation. These options are
706
+ /// simply passed to all other sub-methods used in this operation, including
707
+ /// when stringifying literals.
708
+ /// projectedFields?: Map<string, string>
709
+ /// Provide the projected fields if they are already known, instead of compiling them
710
+ /// again via a call to <see>SQLQueryGeneratorBase.getProjectedFields</see>.
711
+ ///
712
+ /// Return: string
713
+ /// Return a comma-separated list of projected fields. If any `DISTINCT`
714
+ /// clause is set on the `queryEngine`, then that will always come first
715
+ /// in the list of projected fields.
716
+ generateSelectQueryFieldProjection(queryEngine, options, _projectedFields) {
717
+ let projectedFields = (_projectedFields) ? _projectedFields : this.getProjectedFields(queryEngine, options, false);
718
+ let projectedFieldList = Array.from(projectedFields.values()).join(',');
719
+
720
+ let distinct = queryEngine.getOperationContext().distinct;
721
+ if (distinct) {
722
+ let result = distinct.toString(this.connection, { isProjection: true });
723
+ return `${result} ${projectedFieldList}`;
423
724
  }
424
- }
425
725
 
426
- // eslint-disable-next-line no-unused-vars
726
+ return projectedFieldList;
727
+ }
728
+
729
+ /// Convert a [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine) conditional
730
+ /// operator into the equivalent SQL conditional operator. For example, `EQ` will be converted
731
+ /// to `=` for most values, `IS` (in the case of `null`, `true`, and `false`), or an `IN` operator
732
+ /// if an array of values is provided.
733
+ ///
734
+ /// If a literal is provided, then it will be converted to a string using the literal's `toString`
735
+ /// method and returned.
736
+ ///
737
+ /// Standard conversion table for most SQL connections (results might differ based on the SQL
738
+ /// connection being used):
739
+ /// | `QueryEngine` Operator | `Array<any>` | `null` | `true` | `false` | Table Join | Other |
740
+ /// | -------------------- | ------------ | ------ | ------ | ------- | ---------- | ----- |
741
+ /// | `EQ` | `IN` | `IS NULL` | `IS TRUE` | `IS FALSE` | `=` | `=` |
742
+ /// | `NEQ` | `NOT IN` | `IS NOT NULL` | `IS NOT TRUE` | `IS NOT FALSE` | `!=` | `!=` |
743
+ /// | `GT` | `throw TypeError` | `>` | `>` | `>` | `>` | `>` |
744
+ /// | `GTE` | `throw TypeError` | `>=` | `>=` | `>=` | `>=` | `>=` |
745
+ /// | `LT` | `throw TypeError` | `<` | `<` | `<` | `<` | `<` |
746
+ /// | `LTE` | `throw TypeError` | `<=` | `<=` | `<=` | `<=` | `<=` |
747
+ /// | `LIKE` | `throw TypeError` | `throw TypeError` | `throw TypeError` | `throw TypeError` | `throw TypeError` | `LIKE` |
748
+ /// | `NOT_LIKE` | `throw TypeError` | `throw TypeError` | `throw TypeError` | `throw TypeError` | `throw TypeError` | `NOT LIKE` |
749
+ ///
750
+ /// Arguments:
751
+ /// queryPart: object
752
+ /// The `QueryEngine` "operation frame" for this conditional operation.
753
+ /// operator: string | Literal
754
+ /// The `QueryEngine` operator to convert to SQL syntax. If a literal is provided,
755
+ /// then it will simply be stringified and returned (as the literal operator the user
756
+ /// specified... whatever that might be).
757
+ /// value: any
758
+ /// The right-hand-side value for this conditional operator.
759
+ /// valueIsReference: boolean
760
+ /// If `true`, then this operator is being converted for a table-join operation. If this is
761
+ /// the case, then `value` will be the "operation context" for the right-side of the table-join.
762
+ /// options?: object
763
+ /// Options for the operation. This is only used by this method for converting provided literals to strings.
764
+ ///
765
+ /// Return: string
766
+ /// Return SQL syntax for this context-specific conditional operator.
427
767
  generateSelectQueryOperatorFromQueryEngineOperator(queryPart, operator, value, valueIsReference, options) {
428
768
  if (LiteralBase.isLiteral(operator))
429
- return operator.toString(this.connection);
769
+ return operator.toString(this.connection, options);
430
770
 
431
771
  switch (operator) {
432
772
  case 'EQ':
@@ -450,12 +790,24 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
450
790
  else
451
791
  return '!=';
452
792
  case 'GT':
793
+ if (Array.isArray(value))
794
+ throw new TypeError(`${this.constructor.name}::generateSelectQueryOperatorFromQueryEngineOperator: Array of values provided to "GT" (greater than) operator.`);
795
+
453
796
  return '>';
454
797
  case 'GTE':
798
+ if (Array.isArray(value))
799
+ throw new TypeError(`${this.constructor.name}::generateSelectQueryOperatorFromQueryEngineOperator: Array of values provided to "GTE" (greater than or equal to) operator.`);
800
+
455
801
  return '>=';
456
802
  case 'LT':
803
+ if (Array.isArray(value))
804
+ throw new TypeError(`${this.constructor.name}::generateSelectQueryOperatorFromQueryEngineOperator: Array of values provided to "LT" (less than) operator.`);
805
+
457
806
  return '<';
458
807
  case 'LTE':
808
+ if (Array.isArray(value))
809
+ throw new TypeError(`${this.constructor.name}::generateSelectQueryOperatorFromQueryEngineOperator: Array of values provided to "LTE" (less than or equal to) operator.`);
810
+
459
811
  return '<=';
460
812
  case 'LIKE':
461
813
  if (valueIsReference)
@@ -478,15 +830,89 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
478
830
  }
479
831
  }
480
832
 
833
+ /// Format a `LIKE` or `NOT LIKE` value.
834
+ ///
835
+ /// Mythix ORM requires that `%` and `_` characters be used for
836
+ /// wildcard characters in `LIKE` and `NOT_LIKE` operations. This is to
837
+ /// create a unified standard for `LIKE` operations across databases.
838
+ /// If the underlying database uses different wildcard characters for
839
+ /// a `LIKE` operation, then it is required to convert them to its native
840
+ /// format here in this method.
841
+ ///
842
+ /// Interface:
843
+ /// interface QueryConditionContext {
844
+ /// queryPart: object; // The "operation frame" or "operation context" for this condition
845
+ /// field: Field; // The field for this conditional operator
846
+ /// sqlOperator: string; // The SQL operator that matches "operator" in the given context
847
+ /// operator: string; // The QueryEngine operator for this condition, i.e. "EQ"
848
+ /// value: any; // The conditional operator's value (right-hand-side value)
849
+ /// }
850
+ ///
851
+ /// Arguments:
852
+ /// context: QueryConditionContext
853
+ /// The "conditional operator" context for this `LIKE` or `NOT LIKE` condition.
854
+ ///
855
+ /// Return: string
856
+ /// By default simply return `context.value`. Other database drivers may
857
+ /// format `context.value` before returning it, in a format suitable for
858
+ /// the underlying database.
481
859
  formatLikeValue({ value }) {
482
860
  return value;
483
861
  }
484
862
 
863
+ /// Generate an optional postfix for any given conditional operator.
864
+ ///
865
+ /// This might be used by any given underlying database should it need
866
+ /// it. It is most commonly used by `LIKE` conditional operators to tag
867
+ /// on an `ESCAPE` clause, specifying the escape character for the `LIKE`
868
+ /// operator. However, it may be used for any operator where the database
869
+ /// needs a "postfix" for the condition.
870
+ ///
871
+ /// Interface:
872
+ /// interface QueryConditionContext {
873
+ /// queryPart: object; // The "operation frame" or "operation context" for this condition
874
+ /// field: Field; // The field for this conditional operator
875
+ /// sqlOperator: string; // The SQL operator that matches "operator" in the given context
876
+ /// operator: string; // The QueryEngine operator for this condition, i.e. "EQ"
877
+ /// value: any; // The conditional operator's value (right-hand-side value)
878
+ /// }
879
+ ///
880
+ /// Arguments:
881
+ /// context: QueryConditionContext
882
+ /// The "conditional operator" context for the current operator being generated.
883
+ ///
884
+ /// Return: string
885
+ /// By default, simply return an empty string, meaning that there is no postfix
886
+ /// for the condition being generated. Optionally, the database driver can return any postfix
887
+ /// for the conditional operator being generated.
485
888
  // eslint-disable-next-line no-unused-vars
486
889
  generateConditionPostfix(context) {
487
890
  return '';
488
891
  }
489
892
 
893
+ /// Generate a SQL "conditional operator" for a `WHERE` clause.
894
+ ///
895
+ /// Even though this is generally used for `SELECT` statements, it
896
+ /// may also be used for `UPDATE WHERE ...`, or `DELETE WHERE ...`
897
+ /// statements as well.
898
+ ///
899
+ /// This method should return a fully formatted "conditional" statement
900
+ /// for the query's `WHERE` clause.
901
+ ///
902
+ /// Arguments:
903
+ /// queryPart: object
904
+ /// The "operation frame" or "operation context" of the conditional
905
+ /// operation as found on the `QueryEngine` being used to generate the
906
+ /// query.
907
+ /// value: any
908
+ /// The value (right-hand-side) of the conditional operator being generated.
909
+ /// options?: object
910
+ /// Options for the current database operation.
911
+ ///
912
+ /// Return: string
913
+ /// A fully formatted SQL conditional statement. This may be an `EXISTS`,
914
+ /// an `ANY` or `ALL`, an `IN`, or some other valid condition for the query.
915
+ /// i.e. `"users"."id" = 1`.
490
916
  generateSelectQueryCondition(queryPart, _value, options) {
491
917
  let value = _value;
492
918
  let field = queryPart.Field;
@@ -565,7 +991,24 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
565
991
  return `${escapedColumnName} ${sqlOperator} ${this.escape(field, value)}${(conditionPostfix) ? ` ${conditionPostfix}` : ''}`;
566
992
  }
567
993
 
568
- // eslint-disable-next-line no-unused-vars
994
+ /// Generate a table join statement, such as
995
+ /// `INNER JOIN "table_name"`, or a `FROM` statement
996
+ /// if `joinType` is falsy, such as `FROM "table_name"`.
997
+ ///
998
+ /// Arguments:
999
+ /// Model: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
1000
+ /// The model that defines the table for the operation.
1001
+ /// joinType: string | null
1002
+ /// If a falsy value, then generate a `FROM "table_name"` statement, using
1003
+ /// the provided `Model` to know the table name. If a string, then this will
1004
+ /// be a join-type, such as `INNER JOIN`, which will be used instead of `FROM`,
1005
+ /// i.e. `INNER JOIN "table_name"`.
1006
+ /// options?: object
1007
+ /// Options to provide to <see>SQLQueryGeneratorBase.getEscapedTableName</see> for the operation.
1008
+ ///
1009
+ /// Return: string
1010
+ /// A `FROM "table_name"` statement, or a table join statement, such as
1011
+ /// `INNER JOIN "table_name"`.
569
1012
  generateFromTableOrTableJoin(Model, _joinType, options) {
570
1013
  if (!Model)
571
1014
  throw new Error(`${this.constructor.name}::generateFromTableOrTableJoin: No valid model provided.`);
@@ -578,14 +1021,63 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
578
1021
  return (joinType) ? `${joinType} ${escapedTableName}` : `FROM ${escapedTableName}`;
579
1022
  }
580
1023
 
1024
+ /// Given a table-join query operation, such as `User.where.id.EQ(Role.where.userID)`,
1025
+ /// generate the table-join statement.
1026
+ ///
1027
+ /// Continuing with the example provided, this would generate the following SQL,
1028
+ /// `"users"."id" = "roles"."userID"`.
1029
+ ///
1030
+ /// Arguments:
1031
+ /// leftQueryPart: object
1032
+ /// The "operation frame" or "operation context" for the left-side of the statement.
1033
+ /// In this example this would be the `EQ` "operation context" of `User.where.id.EQ`.
1034
+ /// rightQueryPart: object
1035
+ /// The "operation frame" or "operation context" for the right-side of the statement.
1036
+ /// In this example this would be the `userID` "operation context" of `Role.where.userID`.
1037
+ /// leftField: [Field](https://github.com/th317erd/mythix-orm/wiki/Field)
1038
+ /// The left-hand side field for the condition. In this example this would be `User.fields.id`.
1039
+ /// rightField: [Field](https://github.com/th317erd/mythix-orm/wiki/Field)
1040
+ /// The right-hand side field for the condition. In this example this would be `Role.fields.userID`.
1041
+ /// operator: string
1042
+ /// The [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine) operator to join the
1043
+ /// table columns on... i.e. `EQ`. This will be passed through <see>SQLQueryGeneratorBase.generateSelectQueryOperatorFromQueryEngineOperator</see>
1044
+ /// to get the related SQL operator for the condition.
1045
+ /// options?: object
1046
+ /// Options for the operation.
1047
+ ///
1048
+ /// Return: string
1049
+ /// Return the table-join condition, i.e. `"users"."id" = "roles"."userID"`.
581
1050
  generateSelectJoinOnTableQueryCondition(leftQueryPart, rightQueryPart, leftField, rightField, operator, options) {
582
1051
  let leftSideEscapedColumnName = this.getEscapedColumnName(leftField.Model, leftField, options);
583
1052
  let rightSideEscapedColumnName = this.getEscapedColumnName(rightField.Model, rightField, options);
584
- let sqlOperator = this.generateSelectQueryOperatorFromQueryEngineOperator(leftQueryPart, operator, undefined, true, options);
1053
+ let sqlOperator = this.generateSelectQueryOperatorFromQueryEngineOperator(leftQueryPart, operator, rightQueryPart, true, options);
585
1054
 
586
1055
  return `${leftSideEscapedColumnName} ${sqlOperator} ${rightSideEscapedColumnName}`;
587
1056
  }
588
1057
 
1058
+ /// Take the join table information from a call to
1059
+ /// <see>SQLQueryGeneratorBase.getJoinTableInfoFromQueryContexts</see>
1060
+ /// and generate all table-join conditions for the two
1061
+ /// tables being joined.
1062
+ ///
1063
+ /// The will generate one or more conditions for the table-join,
1064
+ /// using `AND` or `OR` to combine the conditions.
1065
+ /// For example, this might generate the following SQL code:
1066
+ /// `INNER JOIN "roles" ON "users"."id" = "roles"."userID" OR "users"."fullName" = "roles"."userFullName"`.
1067
+ ///
1068
+ /// Arguments:
1069
+ /// joinInfos: Array<TableJoinInformation>
1070
+ /// All collected table-join information for joining tables. The join table
1071
+ /// information at index zero `joinInfos[0]` is the table being joined against,
1072
+ /// which is commonly the "root model" of the query... but it doesn't have to be.
1073
+ /// options?: object
1074
+ /// Options for the operation.
1075
+ ///
1076
+ /// Return: string
1077
+ /// The full list of conditions for joining the tables together, i.e.
1078
+ /// `INNER JOIN "roles" ON "users"."id" = "roles"."userID" OR "users"."fullName" = "roles"."userFullName"`.
1079
+ ///
1080
+ /// See: SQLQueryGeneratorBase.getJoinTableInfoFromQueryContexts
589
1081
  generateJoinOnTableQueryConditions(joinInfos, options) {
590
1082
  if (Nife.isEmpty(joinInfos))
591
1083
  return '';
@@ -608,7 +1100,23 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
608
1100
  return sqlParts.join(' ');
609
1101
  }
610
1102
 
611
- // TODO: Needs to take a join type object
1103
+ /// Take a join type from a [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine)
1104
+ /// instance, and convert it to the proper join type for the underlying database.
1105
+ ///
1106
+ /// Arguments:
1107
+ /// joinType: string
1108
+ /// The join type, as specified by the [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine). This
1109
+ /// will generally be one of `'inner'`, `'left'`, `'right'`, `'full'`, or `'cross'`, but
1110
+ /// it might also be a user-defined table join type (in which case is is expected to already
1111
+ /// be in the correct format for the underlying database).
1112
+ /// outer: boolean
1113
+ /// If `true`, then this is an "outer join", instead of an "inner join". i.e. a `'left'` join
1114
+ /// type would be converted to a `LEFT OUTER JOIN` instead of a `LEFT INNER JOIN`.
1115
+ /// options?: object
1116
+ /// Options for the operation.
1117
+ ///
1118
+ /// Return: string
1119
+ /// The SQL join type, used to join tables. i.e. `LEFT JOIN`.
612
1120
  // eslint-disable-next-line no-unused-vars
613
1121
  generateSQLJoinTypeFromQueryEngineJoinType(joinType, outer, options) {
614
1122
  if (!joinType || joinType === 'inner')
@@ -625,6 +1133,32 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
625
1133
  return joinType;
626
1134
  }
627
1135
 
1136
+ /// Sort the table joins into a predictable order.
1137
+ ///
1138
+ /// Since table-join operations can exist anywhere in a query,
1139
+ /// this will take all table-join operations for the query, and
1140
+ /// sort them so that they are in a predictable order.
1141
+ /// By default the order will be in "dependency order", so if for
1142
+ /// example there is a join against the `Role` table, and the `Role`
1143
+ /// table "depends on" the `User` table, then the `Role` table will
1144
+ /// come first in the list of table joins.
1145
+ ///
1146
+ /// Why sort the table joins at all? Mythix ORM always does its best to
1147
+ /// keep all queries consistent, if for no other reason than for caching
1148
+ /// purposes (so the query itself can be used as a cache key).
1149
+ ///
1150
+ /// Arguments:
1151
+ /// joins: Map<string, Array<TableJoinInformation>>
1152
+ /// A map of all table-joins taking place. Each key in this map is the name
1153
+ /// of a model being joined. Each value is an array of `TableJoinInformation`
1154
+ /// that defines how two tables will be joined to each other. This array is
1155
+ /// what is sorted with this operation.
1156
+ ///
1157
+ /// Return: Array<string>
1158
+ /// An array of model names, sorted in order, used as "ordered keys"
1159
+ /// into the provided `Map` to generate the table-joins for the query.
1160
+ ///
1161
+ /// See: SQLQueryGeneratorBase.getJoinTableInfoFromQueryContexts
628
1162
  sortJoinRelationOrder(joins) {
629
1163
  let modelNames = Array.from(joins.keys());
630
1164
 
@@ -646,6 +1180,22 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
646
1180
  });
647
1181
  }
648
1182
 
1183
+ /// Generate the SQL syntax needed to join all tables in the operation.
1184
+ ///
1185
+ /// For example, this might generate the following SQL
1186
+ /// `INNER JOIN "roles" ON "users"."id" = "roles"."userID" RIGHT JOIN "organizations" ON "organization"."id" = "roles"."organizationID"`.
1187
+ ///
1188
+ /// Arguments:
1189
+ /// queryEngine: [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine)
1190
+ /// The query engine to pull table-join operations from.
1191
+ /// options?: object
1192
+ /// Options for the operation.
1193
+ ///
1194
+ /// Return: string
1195
+ /// A complete join of all tables in the operation that are being joined, including
1196
+ /// the conditions used to join each table.
1197
+ ///
1198
+ /// See: SQLQueryGeneratorBase.getJoinTableInfoFromQueryContexts
649
1199
  generateSelectQueryJoinTables(queryEngine, options) {
650
1200
  const addToJoins = (joinInfo) => {
651
1201
  let items = joins.get(joinInfo.joinModelName);
@@ -696,6 +1246,33 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
696
1246
  return sqlParts.join(' ');
697
1247
  }
698
1248
 
1249
+ /// Generate all `WHERE` conditions using the provided
1250
+ /// [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine).
1251
+ ///
1252
+ /// This will take all conditional operations defined by the query engine,
1253
+ /// and generate a full `WHERE` condition, `AND`ing or `OR`ing all conditions
1254
+ /// together, including grouping and sub-grouping conditions where needed.
1255
+ ///
1256
+ /// Note:
1257
+ /// This method can be recursively called if a sub-query is encountered that
1258
+ /// also has `WHERE` conditions.
1259
+ ///
1260
+ /// Note:
1261
+ /// Even though this method's name implies generating `WHERE` conditions for a `SELECT`
1262
+ /// statement, it is also used for non-SELECT statements, such as `UPDATE WHERE ...`,
1263
+ /// and `DELETE WHERE ...`.
1264
+ ///
1265
+ /// Arguments:
1266
+ /// queryEngine: [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine)
1267
+ /// The query engine to pull conditions from.
1268
+ /// options?: object
1269
+ /// Options for the operation.
1270
+ ///
1271
+ /// Return: string
1272
+ /// All SQL conditional operators for a `WHERE` statement, not including the
1273
+ /// `WHERE` prefix. i.e. `"users"."id" = 1 OR "users"."firstName" = 'Bob'`. An
1274
+ /// empty string will be returned if there are no conditional operators used in
1275
+ /// the provided query engine.
699
1276
  generateSelectWhereConditions(queryEngine, options) {
700
1277
  const logicalCondition = (sqlParts, queryPart) => {
701
1278
  if (sqlParts.length === 0)
@@ -770,6 +1347,27 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
770
1347
  return sqlParts.join(' ');
771
1348
  }
772
1349
 
1350
+ /// Generate an `ORDER BY` clause, listing all columns
1351
+ /// and their sort-direction.
1352
+ ///
1353
+ /// Arguments:
1354
+ /// queryEngine: [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine)
1355
+ /// The query engine to pull the "order by" fields from.
1356
+ /// options?: object
1357
+ /// Options for the operation. Though these might be database specific, there are some
1358
+ /// common options that can be supplied to this method:
1359
+ /// | Option | Type | Default Value | Description |
1360
+ /// | ------ | ---- | ------------- | ----------- |
1361
+ /// | `onlyProjectedFields` | `boolean` | `true` | If `true`, then only list fields that are also in the projection. |
1362
+ /// | `projectionFields` | `Map<string, object>` | Result of `getProjectedFields` | The fields that have been projected, to be used in combination with the `onlyProjectedFields` option. |
1363
+ /// | `rawOrder` | `boolean` | `false` | If `true`, then return the order fields (and literals) as a raw Array instead of a comma-separated list of fields. |
1364
+ /// | `reverseOrder` | `boolean` | `false` | If `true`, then reverse the sort order of all fields. |
1365
+ ///
1366
+ /// Return: string | Array<string>
1367
+ /// If the `rawOrder` `options` is `false`, then return a fully completed `ORDER BY` clause,
1368
+ /// listing all the fields and their sort order. If no order has been specified by the query engine
1369
+ /// (or the connection), then return an empty string instead. If the `rawOrder` `options` is `true`,
1370
+ /// then return an array of the ordered fields instead.
773
1371
  generateOrderClause(queryEngine, _options) {
774
1372
  if (!queryEngine || typeof queryEngine.getOperationContext !== 'function')
775
1373
  return (_options && _options.rawOrder) ? [] : '';
@@ -829,6 +1427,24 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
829
1427
  return (options.rawOrder) ? orderByParts : `ORDER BY ${orderByParts.join(',')}`;
830
1428
  }
831
1429
 
1430
+ /// Generate a `GROUP BY` clause, listing all columns
1431
+ /// to group by.
1432
+ ///
1433
+ /// Arguments:
1434
+ /// queryEngine: [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine)
1435
+ /// The query engine to pull the "group by" fields from.
1436
+ /// options?: object
1437
+ /// Options for the operation. Though these might be database specific, there are some
1438
+ /// common options that can be supplied to this method:
1439
+ /// | Option | Type | Default Value | Description |
1440
+ /// | ------ | ---- | ------------- | ----------- |
1441
+ /// | `rawGroupBy` | `boolean` | `false` | If `true`, then return the group by fields (and literals) as a raw Array instead of a comma-separated list of fields. |
1442
+ ///
1443
+ /// Return: string | Array<string>
1444
+ /// If the `rawGroupBy` `options` is `false`, then return a fully completed `GROUP BY` clause,
1445
+ /// listing all the columns and literals to group by. If no "group by" has been specified by the query engine
1446
+ /// then return an empty string instead. If the `rawGroupBy` `options` is `true`,
1447
+ /// then return an array of the "group by" fields instead.
832
1448
  generateGroupByClause(queryEngine, _options) {
833
1449
  if (!queryEngine || typeof queryEngine.getOperationContext !== 'function')
834
1450
  return (_options && _options.rawGroupBy) ? [] : '';
@@ -862,11 +1478,54 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
862
1478
  return (options.rawGroupBy) ? groupByParts : `GROUP BY ${groupByParts.join(',')}`;
863
1479
  }
864
1480
 
1481
+ /// Generate a `HAVING` clause to be used in combination with a
1482
+ /// `GROUP BY` clause.
1483
+ ///
1484
+ /// This simple calls <see>SQLQueryGeneratorBase.generateSelectWhereConditions</see>
1485
+ /// on the provided `queryEngine`, and if there is a result, wraps the conditions generated
1486
+ /// inside a `HAVING (...)` clause. If no conditions are generated, then an empty string will
1487
+ /// be returned instead.
1488
+ ///
1489
+ /// Arguments:
1490
+ /// queryEngine: [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine)
1491
+ /// The query engine to pull the "group by" conditions from.
1492
+ /// options?: object
1493
+ /// Options for the operation. These options are simply passed through to the
1494
+ /// <see>SQLQueryGeneratorBase.generateSelectWhereConditions</see> call.
1495
+ ///
1496
+ /// Return: string
1497
+ /// A `HAVING (...)` clause if conditions were found on the provided `queryEngine`, or
1498
+ /// an empty string if no conditions were found.
1499
+ ///
1500
+ /// See: SQLQueryGeneratorBase.generateSelectWhereConditions
865
1501
  generateHavingClause(queryEngine, options) {
866
1502
  let where = this.generateSelectWhereConditions(queryEngine, options);
867
1503
  return (where) ? `HAVING (${where})` : '';
868
1504
  }
869
1505
 
1506
+ /// Generate both a `GROUP BY` and `HAVING` clause
1507
+ /// together. If no `GROUP_BY` operation is set on the
1508
+ /// provided `queryEngine`, then nothing will be generated,
1509
+ /// and an empty string will be returned instead. The `HAVING`
1510
+ /// clause will be automatically skipped if there is no
1511
+ /// `GROUP_BY` operation to work off of.
1512
+ ///
1513
+ /// Arguments:
1514
+ /// queryEngine: [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine)
1515
+ /// The query engine to pull the "group by" clause, and "having" conditions from.
1516
+ /// options?: object
1517
+ /// Options for the operation. These options are simply passed through to the
1518
+ /// <see>SQLQueryGeneratorBase.generateGroupByClause</see> and <see>SQLQueryGeneratorBase.generateHavingClause</see>
1519
+ /// internal calls this method makes.
1520
+ ///
1521
+ /// Return: string
1522
+ /// A full `GROUP BY` clause, including any `HAVING` clause specified. An empty string
1523
+ /// will be returned if there is no `GROUP_BY` operation specified on the `queryEngine`
1524
+ /// provided.
1525
+ ///
1526
+ /// See: SQLQueryGeneratorBase.generateGroupByClause
1527
+ ///
1528
+ /// See: SQLQueryGeneratorBase.generateHavingClause
870
1529
  generateGroupByAndHavingClause(queryEngine, options) {
871
1530
  if (!queryEngine)
872
1531
  return '';
@@ -888,22 +1547,61 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
888
1547
  return sqlParts.join(' ');
889
1548
  }
890
1549
 
891
- // eslint-disable-next-line no-unused-vars
1550
+ /// Generate a `LIMIT` clause.
1551
+ ///
1552
+ /// Arguments:
1553
+ /// limit: number | Literal
1554
+ /// If `limit` is a literal, simply stringify and return it. Otherwise, if `limit`
1555
+ /// is a `number`, generate a `LIMIT` clause and return it, using the `limit` provided.
1556
+ /// options?: object
1557
+ /// Options for the operation. These are passed to `toString` for stringifying literals.
1558
+ ///
1559
+ /// Return: string
1560
+ /// A `LIMIT` clause to apply to the query.
892
1561
  generateLimitClause(limit, options) {
893
1562
  if (LiteralBase.isLiteral(limit))
894
- return limit.toString(this.connection);
1563
+ return limit.toString(this.connection, options);
895
1564
 
896
1565
  return `LIMIT ${limit}`;
897
1566
  }
898
1567
 
899
- // eslint-disable-next-line no-unused-vars
1568
+ /// Generate an `OFFSET` clause.
1569
+ ///
1570
+ /// Arguments:
1571
+ /// offset: number | Literal
1572
+ /// If `offset` is a literal, simply stringify and return it. Otherwise, if `offset`
1573
+ /// is a `number`, generate an `OFFSET` clause and return it, using the `offset` provided.
1574
+ /// options?: object
1575
+ /// Options for the operation. These are passed to `toString` for stringifying literals.
1576
+ ///
1577
+ /// Return: string
1578
+ /// An `OFFSET` clause to apply to the query.
900
1579
  generateOffsetClause(offset, options) {
901
1580
  if (LiteralBase.isLiteral(offset))
902
- return offset.toString(this.connection);
1581
+ return offset.toString(this.connection, options);
903
1582
 
904
1583
  return `OFFSET ${offset}`;
905
1584
  }
906
1585
 
1586
+ /// Generate the `ORDER`, `LIMIT`, and `OFFSET` clauses
1587
+ /// to apply to the query. If any one of these clauses is
1588
+ /// blank, then it will be skipped. If there is no order,
1589
+ /// limit, or offset applied to the query, then an empty
1590
+ /// string will be returned.
1591
+ ///
1592
+ /// Arguments:
1593
+ /// queryEngine: [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine)
1594
+ /// The query engine to pull the "order by", "limit", and "offset" clauses from.
1595
+ /// options?: object
1596
+ /// Options for the operation. These options are simply passed through to the
1597
+ /// the respective calls that generate the sub-parts of this operation.
1598
+ ///
1599
+ /// Return: string
1600
+ /// A combo `ORDER BY ... LIMIT ... OFFSET ...` clause to apply to the query.
1601
+ /// If any one of these sub-parts is empty or invalid, they will be skipped.
1602
+ /// i.e. if there is no `LIMIT` applied to the query, then the `LIMIT` and `OFFSET`
1603
+ /// will be skipped. If there is no output because all sub-parts (clauses) were skipped,
1604
+ /// then an empty string will be returned instead.
907
1605
  generateSelectOrderLimitOffset(queryEngine, _options) {
908
1606
  if (!queryEngine)
909
1607
  return '';
@@ -952,6 +1650,29 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
952
1650
  return sqlParts.join(' ');
953
1651
  }
954
1652
 
1653
+ /// Generate the `WHERE`, `ORDER`, `LIMIT`, and `OFFSET` clauses
1654
+ /// to apply to the query. If any one of these clauses is
1655
+ /// blank, then it will be skipped. If there is no output because
1656
+ /// all clauses were skipped, then an empty string will be returned instead.
1657
+ ///
1658
+ /// Arguments:
1659
+ /// queryEngine: [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine)
1660
+ /// The query engine to pull the conditions, "order by", "limit", and "offset" clauses from.
1661
+ /// options?: object
1662
+ /// Options for the operation. These options are simply passed through to the
1663
+ /// the respective calls that generate the sub-parts of this operation. One
1664
+ /// common option that can be used here for all connections is the `separateWhereAndOrder`
1665
+ /// option. If `true`, then an object with the following shape will be returned:
1666
+ /// `{ where: string; orderLimitOffset: string; }`, splitting the clauses apart and
1667
+ /// returning them separately.
1668
+ ///
1669
+ /// Return: string
1670
+ /// A combo `WHERE ... ORDER BY ... LIMIT ... OFFSET ...` clause to apply to the query.
1671
+ /// If any one of these sub-parts is empty or invalid, they will be skipped.
1672
+ /// i.e. if there is no `LIMIT` applied to the query, then the `LIMIT` and `OFFSET`
1673
+ /// will be skipped. If there are no conditions for the query, then the `WHERE` clause
1674
+ /// will be skipped. If there is no output because all sub-parts (clauses) were skipped,
1675
+ /// then an empty string will be returned instead.
955
1676
  generateWhereAndOrderLimitOffset(queryEngine, _options) {
956
1677
  let options = _options || {};
957
1678
  let sqlParts = [];
@@ -970,6 +1691,34 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
970
1691
  return sqlParts.join(' ');
971
1692
  }
972
1693
 
1694
+ /// Generate a full `SELECT` statement using the provided
1695
+ /// `queryEngine`.
1696
+ ///
1697
+ /// This will generate a `SELECT` statement for the underlying
1698
+ /// database that will include the field projection, table joins,
1699
+ /// any `GROUP BY` clause that is applied, all the `WHERE` conditions,
1700
+ /// any sub-queries involved, and an `ORDER BY`, `LIMIT`, and `OFFSET`
1701
+ /// if those are in-use in the query. This method will be recursively
1702
+ /// called for any sub-queries encountered.
1703
+ ///
1704
+ /// Arguments:
1705
+ /// queryEngine: [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine)
1706
+ /// The query engine to use to generate the `SELECT` statement.
1707
+ /// options?: object
1708
+ /// Options for the operation. These options are passed through all generation calls
1709
+ /// invoked inside this method, and so impact all methods used to generate the statement,
1710
+ /// including methods used for column escaping, literal conversion, etc...
1711
+ /// Though these options are often connection-specific,
1712
+ /// the following options are available across all connections:
1713
+ /// | Option | Type | Default Value | Description |
1714
+ /// | ------ | ---- | ------------- | ----------- |
1715
+ /// | `includeRelations` | `boolean` | `false` | If `true`, then a `.PROJECT('*')` will be applied for you, including all tables used in the operation in the output. |
1716
+ /// | `isSubQuery` | `boolean` | `false` | Though often not used directly by the user, if this option is `true`, then it will alter how the `SELECT` statement is generated... for example, the `ORDER BY` clause might be skipped entirely, or the field projection might be altered. |
1717
+ /// | `returnFieldProjection` | `boolean` | `false` | If `true`, then return an object with the shape `{ sql, projectionFields }`, where `sql` is the `SELECT` statement, and `projectionFields` are the fields that were projected. |
1718
+ ///
1719
+ /// Return: string
1720
+ /// A fully generated `SELECT` statement that can be used directly in the underlying database
1721
+ /// to query data.
973
1722
  generateSelectStatement(_queryEngine, _options) {
974
1723
  let queryEngine = _queryEngine;
975
1724
  if (!QueryEngine.isQuery(queryEngine))
@@ -988,8 +1737,8 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
988
1737
 
989
1738
  options.selectStatement = true;
990
1739
 
991
- projectionFields = this.generateSelectQueryFieldProjection(queryEngine, options, true);
992
- sqlParts.push(this.generateSelectQueryFieldProjection(queryEngine, options));
1740
+ projectionFields = this.getProjectedFields(queryEngine, options, true);
1741
+ sqlParts.push(this.generateSelectQueryFieldProjection(queryEngine, options, projectionFields));
993
1742
 
994
1743
  sqlParts.push(this.generateFromTableOrTableJoin(rootModel, undefined, options));
995
1744
  sqlParts.push(this.generateSelectQueryJoinTables(queryEngine, options));
@@ -1010,6 +1759,51 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
1010
1759
  return sql;
1011
1760
  }
1012
1761
 
1762
+ /// Get the default value for a field.
1763
+ ///
1764
+ /// This method is used for `CREATE TABLE`, `ALTER TABLE`, `UPDATE`, and `INSERT`
1765
+ /// statements. It uses the `defaultValue` of the `field` provided
1766
+ /// to fetch any `DEFAULT` that might be applicable to the field
1767
+ /// at the database level for a `CREATE TABLE` or `ALTER TABLE` statement. It might
1768
+ /// also be used for things like `created_at` and `updated_at` fields during `UPDATE`
1769
+ /// or `INSERT` statements.
1770
+ ///
1771
+ /// Most default values for fields are applied client-side by Mythix ORM
1772
+ /// immediately before an `INSERT` or `UPDATE` statement is executed.
1773
+ /// However, some of them are set directly at the database level for field
1774
+ /// defaults, such as `AUTOINCREMENT`, and some date and times types (i.e. `NOW`).
1775
+ /// This method will return a string representing the default value that
1776
+ /// should be applied to a column (i.e. `DEFAULT NOW()`), or it will return
1777
+ /// a literal defining the default value that might change the entire statement.
1778
+ /// For example, `AUTOINCREMENT` in PostgreSQL is actually handled by converting
1779
+ /// the data type of the field to a `SERIAL` type.
1780
+ ///
1781
+ /// This method is also used to fetch the default value for columns during an
1782
+ /// `UPDATE` or `INSERT` statement. For example, for `created_at` and `updated_at`
1783
+ /// fields, the default might be set during `INSERT` or always set on `UPDATE` operations.
1784
+ ///
1785
+ /// Arguments:
1786
+ /// field: [Field](https://github.com/th317erd/mythix-orm/wiki/Field)
1787
+ /// The field to fetch the `defaultValue` from, if any.
1788
+ /// fieldName: string
1789
+ /// The name of the field the default value is being fetched for. This should always be
1790
+ /// the same as `field.fieldName`.
1791
+ /// options?: object
1792
+ /// Options for the operation.
1793
+ /// | Option | Type | Default Value | Description |
1794
+ /// | ------ | ---- | ------------- | ----------- |
1795
+ /// | `escape` | `boolean` | `true` | If `true`, then Mythix ORM will escape the default value found (i.e. with single quotes) so it is treated as a "value" in the underlying database. |
1796
+ /// | `isInsertOperation` | `boolean` | `false` | If `true`, then Mythix ORM is reporting that this is for an `INSERT` operation. |
1797
+ /// | `isUpdateOperation` | `boolean` | `false` | If `true`, then Mythix ORM is reporting that this is for an `UPDATE` operation. |
1798
+ /// | `rawLiterals` | `boolean` | `false` | If `true`, then Mythix ORM will return the literal raw without stringifying it--if the default value is a literal. |
1799
+ /// | `remoteOnly` | `boolean` | `false` | If `true`, then Mythix ORM will only return a default for the field if it is flagged as a "remote" or "literal" default value (i.e. an `AUTOINCREMENT` default would be flagged "remote"). |
1800
+ /// | `useDefaultKeyword` | `boolean` | `true` | If `true`, then Mythix ORM will prefix any default value found with a `DEFAULT` statement (for use in `CREATE TABLE` statements). |
1801
+ ///
1802
+ /// Return: undefined | string | Literal
1803
+ /// Return a string representing the escaped default value, a Literal if the `rawLiterals` option is `true`,
1804
+ /// or `undefined` if the field has no default value. A default value will not always be returned from this
1805
+ /// method simply because the provided `field` has a `defaultValue` property. Only default values applicable
1806
+ /// at the database level, or applicable to the operation being carried out will be returned.
1013
1807
  getFieldDefaultValue(field, fieldName, _options) {
1014
1808
  let options = _options || {};
1015
1809
  let defaultValue = field.defaultValue;
@@ -1060,6 +1854,38 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
1060
1854
  return `${defaultValue}`;
1061
1855
  }
1062
1856
 
1857
+ /// Generate an index name for creating an index on
1858
+ /// one or more columns.
1859
+ ///
1860
+ /// This will take one or more field names from the `Model`
1861
+ /// provided and generate an index name for creating an index
1862
+ /// in the underlying database. This method can generate an index
1863
+ /// name for indexing a single column (if a single field name is provided),
1864
+ /// or it can generate an index name for indexing multiple columns
1865
+ /// as a "combo index".
1866
+ ///
1867
+ /// Arguments:
1868
+ /// Model: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
1869
+ /// The model to use for the table to index on. This model should also be
1870
+ /// the model that owns all fields to be indexed. It isn't possible to index
1871
+ /// columns across different tables, so the fields provided must all be from
1872
+ /// this same `Model`.
1873
+ /// indexFieldNames: Array<string>
1874
+ /// The field names that will be used to create the single or combo index. A
1875
+ /// single field name will generate an index name for a single column, whereas
1876
+ /// more than one field name will generate an index name for a combo-index that
1877
+ /// indexes all columns requested. These can be fully qualified field names, but
1878
+ /// they don't have to be, since the owning `Model` is already known. If fully qualified
1879
+ /// field names are used, then the model name for each field must match the `Model`
1880
+ /// provided (making it pointless to use fully qualified field names).
1881
+ /// options?: object
1882
+ /// Options for the operation. These are not used by this method, and instead are
1883
+ /// just provided for the user--should they overload this method and need the options.
1884
+ ///
1885
+ /// Return: string
1886
+ /// Return an index name, in the format `'idx_tableName_column1_column2_column3_...'`. If
1887
+ /// the `indexFieldNames` provided is empty--or result in an empty set of field names after
1888
+ /// filtering out invalid field names--then an empty string will be returned instead.
1063
1889
  // eslint-disable-next-line no-unused-vars
1064
1890
  generateIndexName(Model, _indexFieldNames, options) {
1065
1891
  let indexFieldNames = Nife.toArray(_indexFieldNames).filter((index) => {
@@ -1081,6 +1907,40 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
1081
1907
  return this.escapeID(`idx_${tableName}_${columnNames.sort().join('_')}`);
1082
1908
  }
1083
1909
 
1910
+ /// Generate a `CREATE INDEX` statement.
1911
+ ///
1912
+ /// This will generate a `CREATE INDEX` statement, indexing
1913
+ /// all fields (columns) provided. If a single field name is
1914
+ /// provided, then a `CREATE INDEX` statement for a single column
1915
+ /// will be generated. If more than one field name is provided, then
1916
+ /// a `CREATE INDEX` statement for a combo-index (indexing across more
1917
+ /// than one column at once) will be generated instead.
1918
+ ///
1919
+ /// Arguments:
1920
+ /// Model: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
1921
+ /// The model to use for the table to index on. This model should also be
1922
+ /// the model that owns all fields to be indexed. It isn't possible to index
1923
+ /// columns across different tables, so the fields provided must all be from
1924
+ /// this same `Model`.
1925
+ /// indexFieldNames: Array<string>
1926
+ /// The field names that will be used to create the single or combo index. A
1927
+ /// single field name will generate a statement for a single column, whereas
1928
+ /// more than one field name will generate a statement for a combo-index that
1929
+ /// indexes all columns requested. These can be fully qualified field names, but
1930
+ /// they don't have to be, since the owning `Model` is already known. If fully qualified
1931
+ /// field names are used, then the model name for each field must match the `Model`
1932
+ /// provided (making it pointless to use fully qualified field names).
1933
+ /// options?: object
1934
+ /// Options for the operation.
1935
+ /// | Option | Type | Default Value | Description |
1936
+ /// | ------ | ---- | ------------- | ----------- |
1937
+ /// | `concurrently` | `boolean` | `false` | If `true`, then add a `CONCURRENTLY` clause to the `CREATE INDEX` statement (if the database supports it). |
1938
+ /// | `ifNotExists` | `boolean` | `false` | If `true`, then add an `IF NOT EXISTS` clause to the `CREATE INDEX` statement. |
1939
+ ///
1940
+ /// Return: string
1941
+ /// Return a fully formatted `CREATE INDEX` statement for the fields (columns)
1942
+ /// requested. An empty string will be returned if `indexFieldNames` is empty,
1943
+ /// or contains no valid field names.
1084
1944
  generateCreateIndexStatement(Model, _indexFieldNames, _options) {
1085
1945
  let indexFieldNames = Nife.toArray(_indexFieldNames).filter((fieldName) => {
1086
1946
  if (Nife.isEmpty(fieldName))
@@ -1116,6 +1976,43 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
1116
1976
  return `CREATE INDEX${(flags) ? ` ${flags}` : ''} ${indexName} ON ${escapedTableName} (${escapedColumnNames.join(',')})`;
1117
1977
  }
1118
1978
 
1979
+ /// Generate a `DROP INDEX` statement.
1980
+ ///
1981
+ /// This will generate a `DROP INDEX` statement, using the provided
1982
+ /// `indexFieldNames` to generate the name of the index to be dropped.
1983
+ /// The provided `indexFieldNames` are passed off to <see>SQLQueryGeneratorBase.generateIndexName</see>
1984
+ /// to get the name of the index to drop.
1985
+ ///
1986
+ /// Arguments:
1987
+ /// Model: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
1988
+ /// The model to use for the table to index on. This model should also be
1989
+ /// the model that owns all fields to be indexed. It isn't possible to index
1990
+ /// columns across different tables, so the fields provided must all be from
1991
+ /// this same `Model`.
1992
+ /// indexFieldNames: Array<string>
1993
+ /// The field names that will be used to create the single or combo index. A
1994
+ /// single field name will generate a statement for a single column, whereas
1995
+ /// more than one field name will generate a statement for a combo-index that
1996
+ /// indexes all columns requested. These can be fully qualified field names, but
1997
+ /// they don't have to be, since the owning `Model` is already known. If fully qualified
1998
+ /// field names are used, then the model name for each field must match the `Model`
1999
+ /// provided (making it pointless to use fully qualified field names).
2000
+ /// options?: object
2001
+ /// Options for the operation.
2002
+ /// | Option | Type | Default Value | Description |
2003
+ /// | ------ | ---- | ------------- | ----------- |
2004
+ /// | `concurrently` | `boolean` | `false` | If `true`, then add a `CONCURRENTLY` clause to the `DROP INDEX` statement (if the database supports it). |
2005
+ /// | `ifExists` | `boolean` | `false` | If `true`, then add an `IF EXISTS` clause to the `DROP INDEX` statement. |
2006
+ /// | `cascade` | `boolean` | `true` | If `true`, then add a `CASCADE` clause to the `DROP INDEX` statement (if the database supports it). |
2007
+ ///
2008
+ /// Return: string
2009
+ /// Return a fully formatted `DROP INDEX` statement for the fields (columns)
2010
+ /// requested. An empty string will be returned if `indexFieldNames` is empty,
2011
+ /// or contains no valid field names. <see>SQLQueryGeneratorBase.generateIndexName</see> is called
2012
+ /// with the provided `indexFieldNames` to generate the name of the index that should
2013
+ /// be dropped.
2014
+ ///
2015
+ /// See: SQLQueryGeneratorBase.generateIndexName
1119
2016
  generateDropIndexStatement(Model, _indexFieldNames, _options) {
1120
2017
  let indexFieldNames = Nife.toArray(_indexFieldNames).filter((fieldName) => {
1121
2018
  if (Nife.isEmpty(fieldName))
@@ -1149,6 +2046,31 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
1149
2046
  return `DROP INDEX${(flags) ? ` ${flags}` : ''} ${indexName}${(postFlags) ? ` ${postFlags}` : ''}`;
1150
2047
  }
1151
2048
 
2049
+ /// Generate zero or more `CREATE INDEX` statements,
2050
+ /// using `field.index` to generate the statements.
2051
+ ///
2052
+ /// A [Field](https://github.com/th317erd/mythix-orm/wiki/Field) in Mythix ORM
2053
+ /// can have an `index` property (see [Field.index](https://github.com/th317erd/mythix-orm/wiki/Field#property-index))
2054
+ /// that defines the indexes to be created for the field. A `true` value is short for
2055
+ /// "index this field". Other field names in the `index` array mean
2056
+ /// "index this field combined with the fields specified, creating a combined index".
2057
+ ///
2058
+ /// This method will turn the `index` property on the provided `field` into one or more `CREATE INDEX`
2059
+ /// statements. If the `index` property on the `field` is falsy, or empty, then an empty array
2060
+ /// will be returned instead.
2061
+ ///
2062
+ /// Arguments:
2063
+ /// Model: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
2064
+ /// The model that owns the `field` provided.
2065
+ /// field: [Field](https://github.com/th317erd/mythix-orm/wiki/Field)
2066
+ /// The field to generate indexes for, using the `index` property of this field.
2067
+ /// options?: object
2068
+ /// Options for the operation. These options are passed off to <see>SQLQueryGeneratorBase.generateCreateIndexStatement</see>
2069
+ /// to generate each `CREATE INDEX` statement.
2070
+ ///
2071
+ /// Return: Array<string>
2072
+ /// Return an array of `CREATE INDEX` statements. If the `index` property on the provided
2073
+ /// `field` is falsy or empty, then an empty array will be returned instead.
1152
2074
  generateColumnIndexes(Model, field, _options) {
1153
2075
  let indexes = Nife.toArray(field.index).filter((index) => {
1154
2076
  if (index === true)
@@ -1170,6 +2092,20 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
1170
2092
  });
1171
2093
  }
1172
2094
 
2095
+ /// Generate a `DROP TABLE` statement.
2096
+ ///
2097
+ /// Arguments:
2098
+ /// Model: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
2099
+ /// The model that defines the table to be dropped.
2100
+ /// options?: object
2101
+ /// Options for the operation.
2102
+ /// | Option | Type | Default Value | Description |
2103
+ /// | ------ | ---- | ------------- | ----------- |
2104
+ /// | `ifExists` | `boolean` | `false` | If `true`, then add an `IF EXISTS` clause to the `DROP TABLE` statement. |
2105
+ ///
2106
+ /// Return: string
2107
+ /// Return a fully formatted `DROP TABLE` statement, using the `Model` provided
2108
+ /// to define which table should be dropped.
1173
2109
  generateDropTableStatement(Model, _options) {
1174
2110
  let options = _options || {};
1175
2111
  let escapedTableName = this.getEscapedTableName(Model, options);
@@ -1183,6 +2119,30 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
1183
2119
  return `DROP TABLE ${flags} ${escapedTableName}${(options.cascade !== false) ? ' CASCADE' : ''}`;
1184
2120
  }
1185
2121
 
2122
+ /// Generate foreign key constraints for a column
2123
+ /// in a `CREATE TABLE` statement.
2124
+ ///
2125
+ /// This method will generate database specific syntax
2126
+ /// for foreign key constraints to apply to a column
2127
+ /// in a `CREATE TABLE` statement.
2128
+ ///
2129
+ /// Arguments:
2130
+ /// field: [Field](https://github.com/th317erd/mythix-orm/wiki/Field)
2131
+ /// The field to generate foreign key constraints for. This field should
2132
+ /// have a [ForeignKeyType](https://github.com/th317erd/mythix-orm/wiki/ForeignKeyType)
2133
+ /// `type`.
2134
+ /// type: [ForeignKeyType](https://github.com/th317erd/mythix-orm/wiki/ForeignKeyType)
2135
+ /// The `type` of the `field` provided, which should always be a [ForeignKeyType](https://github.com/th317erd/mythix-orm/wiki/ForeignKeyType).
2136
+ /// options?: object
2137
+ /// Options for the operation. These options are simply passed through to any
2138
+ /// <see>SQLQueryGeneratorBase.getEscapedColumnName</see>, or <see>SQLQueryGeneratorBase.getEscapedTableName</see>
2139
+ /// calls that are made internally by this method.
2140
+ ///
2141
+ /// Return: string
2142
+ /// A database specific string for defining foreign key constraints for a column.
2143
+ /// Any `ON DELETE` or `ON UPDATE` clauses will be generated from the `onDelete`
2144
+ /// and `onUpdate` properties set on the [ForeignKeyType](https://github.com/th317erd/mythix-orm/wiki/ForeignKeyType)
2145
+ /// `type` for the field.
1186
2146
  generateForeignKeyConstraint(field, type, options) {
1187
2147
  let typeOptions = type.getOptions();
1188
2148
  let targetModel = type.getTargetModel(this.connection);
@@ -1216,6 +2176,39 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
1216
2176
  return sqlParts.join('');
1217
2177
  }
1218
2178
 
2179
+ /// Generate an "inner tail" for a `CREATE TABLE`
2180
+ /// statement.
2181
+ ///
2182
+ /// An "inner tail" is the trailing part of the `CREATE TABLE`
2183
+ /// statement that is still inside the parenthesis of the statement,
2184
+ /// after any columns have been defined. For example:
2185
+ /// ```sql
2186
+ /// CREATE TABLE table_name (
2187
+ /// column1 datatype(length) column_contraint,
2188
+ /// column2 datatype(length) column_contraint,
2189
+ /// ... // <---- this is the "inner tail"
2190
+ /// );
2191
+ ///
2192
+ /// ... // <---- this is the "outer tail"
2193
+ /// ```
2194
+ ///
2195
+ /// This is often database specific, and by default will be
2196
+ /// used to generate any foreign key constraints used by columns
2197
+ /// in the table.
2198
+ ///
2199
+ /// Arguments:
2200
+ /// Model: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
2201
+ /// The model that defines the table being created.
2202
+ /// options?: object
2203
+ /// Any options for the `CREATE TABLE` operation being carried out.
2204
+ ///
2205
+ /// Return: Array<string>
2206
+ /// Return an "inner tail" for the `CREATE TABLE` statement, or an empty
2207
+ /// array if there should be no "inner tail". An array of SQL statements
2208
+ /// is returned by this method, which will be added to the `CREATE TABLE`
2209
+ /// statement (inside its parenthesis).
2210
+ ///
2211
+ /// See: SQLQueryGeneratorBase.generateForeignKeyConstraint
1219
2212
  // eslint-disable-next-line no-unused-vars
1220
2213
  generateCreateTableStatementInnerTail(Model, options) {
1221
2214
  let fieldParts = [];
@@ -1236,6 +2229,40 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
1236
2229
  return fieldParts;
1237
2230
  }
1238
2231
 
2232
+ /// Generate an "outer tail" for a `CREATE TABLE`
2233
+ /// statement.
2234
+ ///
2235
+ /// An "outer tail" is the trailing part of the `CREATE TABLE`
2236
+ /// statement that is outside the parenthesis of the statement,
2237
+ /// after the column list. For example:
2238
+ /// ```sql
2239
+ /// CREATE TABLE table_name (
2240
+ /// column1 datatype(length) column_contraint,
2241
+ /// column2 datatype(length) column_contraint,
2242
+ /// ... // <---- this is the "inner tail"
2243
+ /// );
2244
+ ///
2245
+ /// ... // <---- this is the "outer tail"
2246
+ /// ```
2247
+ ///
2248
+ /// This is often database specific, and by default will be
2249
+ /// used to generate any `CREATE INDEX` statements for columns
2250
+ /// that have indexes.
2251
+ ///
2252
+ /// Arguments:
2253
+ /// Model: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
2254
+ /// The model that defines the table being created.
2255
+ /// options?: object
2256
+ /// Any options for the `CREATE TABLE` operation being carried out.
2257
+ ///
2258
+ /// Return: Array<string>
2259
+ /// Return an "outer tail" for the `CREATE TABLE` statement, or an empty
2260
+ /// array if there should be no "outer tail". An array of SQL statements
2261
+ /// is returned by this method, which will either be added to the `CREATE TABLE`
2262
+ /// statement, or executed separately after the `CREATE TABLE` statement is
2263
+ /// executed--depending on the underlying database.
2264
+ ///
2265
+ /// See: SQLQueryGeneratorBase.generateColumnIndexes
1239
2266
  // eslint-disable-next-line no-unused-vars
1240
2267
  generateCreateTableStatementOuterTail(Model, options) {
1241
2268
  let fieldParts = [];
@@ -1257,6 +2284,27 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
1257
2284
  return Nife.uniq(fieldParts);
1258
2285
  }
1259
2286
 
2287
+ /// Generate a column definition for use inside a `CREATE TABLE`
2288
+ /// statement, or an `ALTER TABLE` statement. The generated
2289
+ /// column definition will include any `DEFAULT` defined, as well
2290
+ /// as any constraints that should be applied to the column, and
2291
+ /// will define at least the column's name and type.
2292
+ ///
2293
+ /// Arguments:
2294
+ /// Model: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
2295
+ /// The model that defines the table the column definition is for.
2296
+ /// field: [Field](https://github.com/th317erd/mythix-orm/wiki/Field)
2297
+ /// The field that defines the column whose definition is being generated.
2298
+ /// options?: object
2299
+ /// Options for the operation. These are simply passed off to other generate
2300
+ /// methods that are used internally, such as <see>SQLQueryGeneratorBase.getEscapedColumnName</see>,
2301
+ /// and [Type.toConnectionType](https://github.com/th317erd/mythix-orm/wiki/Type#method-toConnectionType)
2302
+ /// for stringifying the column's type. There may be connection-specific options that
2303
+ /// can be supplied as well.
2304
+ ///
2305
+ /// Return: string
2306
+ /// A fully formatted "column definition" statement, for use in
2307
+ /// a `CREATE TABLE` or `ALTER TABLE` statement. i.e. `"id" BIGINT PRIMARY KEY AUTOINCREMENT`.
1260
2308
  generateColumnDeclarationStatement(Model, field, _options) {
1261
2309
  let options = _options || {};
1262
2310
  let constraintParts = [];
@@ -1292,6 +2340,26 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
1292
2340
  return `${this.getEscapedColumnName(Model, field, { ...options, columnNameOnly: true })} ${field.type.toConnectionType(this.connection, { ...options, createTable: true, defaultValue })}${constraintParts}`;
1293
2341
  }
1294
2342
 
2343
+ /// Generate a full `CREATE TABLE` statement using
2344
+ /// the provided `Model` and its fields.
2345
+ ///
2346
+ /// Arguments:
2347
+ /// Model: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
2348
+ /// The model that defines the table being created.
2349
+ /// options?: object
2350
+ /// Options for the operation. Though these might contain connection-specific
2351
+ /// options, the following options are common across all connections:
2352
+ /// | Option | Type | Default Value | Description |
2353
+ /// | ------ | ---- | ------------- | ----------- |
2354
+ /// | `ifNotExists` | `boolean` | `false` | If `true`, then add an `IF NOT EXISTS` clause to the `CREATE TABLE` statement. |
2355
+ ///
2356
+ /// Return: string
2357
+ /// Return a fully formatted `CREATE TABLE` statement, to create
2358
+ /// the table defined by the provided `Model`.
2359
+ ///
2360
+ /// See: SQLQueryGeneratorBase.generateColumnDeclarationStatement
2361
+ ///
2362
+ /// See: SQLQueryGeneratorBase.generateCreateTableStatementInnerTail
1295
2363
  generateCreateTableStatement(Model, _options) {
1296
2364
  let options = _options || {};
1297
2365
  let fieldParts = [];
@@ -1315,6 +2383,42 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
1315
2383
  return finalStatement;
1316
2384
  }
1317
2385
 
2386
+ /// Generate a comma-separated list of values for
2387
+ /// use in an `INSERT` statement.
2388
+ ///
2389
+ /// Only "dirty" fields are inserted into the table, which might seem
2390
+ /// odd at first. However, Mythix ORM model instances have all (or most) their
2391
+ /// fields set to dirty when they are first instantiated, except fields
2392
+ /// such as auto-incrementing ids. This makes sense, because we would want
2393
+ /// to insert all columns for a given model instance, but not columns such
2394
+ /// as an auto-incrementing "id" that we would want the database to provide
2395
+ /// a value for. Any field on a model that should be inserted should already
2396
+ /// be marked "dirty", and any field that isn't dirty should instead be provided
2397
+ /// the "default value" that is already defined for the column.
2398
+ ///
2399
+ /// The `dirtyFields` option that can be provided is to provide the fields that
2400
+ /// are marked as dirty across **all** model instances being inserted (for bulk-inserts).
2401
+ /// If provide, this option will override the "dirty" fields reported by each model
2402
+ /// instance, since the values for insertion must be aligned across all rows.
2403
+ ///
2404
+ /// Arguments:
2405
+ /// model: [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
2406
+ /// The model instance to pull field values from. Only "dirty" values
2407
+ /// will be pulled from the model and compiled into output.
2408
+ /// options?: object
2409
+ /// Options for the operation. Though these might contain connection-specific
2410
+ /// options, the following options are common across all connections:
2411
+ /// | Option | Type | Default Value | Description |
2412
+ /// | ------ | ---- | ------------- | ----------- |
2413
+ /// | `dirtyFields` | `Array<Field>` | `undefined` | If more than one row is being inserted, then define all columns being inserted for the operation. |
2414
+ ///
2415
+ /// Return: undefined | { modelChanges: object; rowValues: string; }
2416
+ /// Return `undefined` if no model instance is provided, or if the model
2417
+ /// instance is marked as "clean" (meaning no insert needs to happen).
2418
+ /// Otherwise, return an object with the shape: `{ modelChanges: object; rowValues: string; }`,
2419
+ /// where `modelChanges` is an object, where each key is a field name, and each value is the
2420
+ /// value that will be inserted into the database. `rowValues` is a string, that is a
2421
+ /// comma-separated list of all values to be inserted for this row.
1318
2422
  generateInsertFieldValuesFromModel(model, _options) {
1319
2423
  if (!model)
1320
2424
  return;
@@ -1359,6 +2463,29 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
1359
2463
  return { modelChanges, rowValues: sqlParts.join(',') };
1360
2464
  }
1361
2465
 
2466
+ /// Generate multiple rows of comma-separated values for
2467
+ /// a bulk `INSERT` operation.
2468
+ ///
2469
+ /// Arguments:
2470
+ /// Model: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
2471
+ /// The model class of all `models` being inserted.
2472
+ /// models: Array<[Model](https://github.com/th317erd/mythix-orm/wiki/Model)> | [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
2473
+ /// An array of model instances, or a single model instance to generate insertion
2474
+ /// values for. Each model (if dirty) will have a single row created for it in the
2475
+ /// underlying database table defined by the provided `Model`.
2476
+ /// options?: object
2477
+ /// Options for the operation. The only option that is really useful here is the
2478
+ /// `newlines` option. If `false`, then newlines won't be used to separate the rows
2479
+ /// of values. By default, newlines will be used to separate each row of values.
2480
+ ///
2481
+ /// Return: undefined | { modelChanges: Array<object>; values: string; }
2482
+ /// Return `undefined` if the provided `models` is empty. Otherwise,
2483
+ /// return an object with the shape `{ modelChanges: Array<object>; values: string; }`,
2484
+ /// where `modelChanges` are the fields being inserted for each model (key = field name, value = field value),
2485
+ /// and where `values` is the list of row-values to insert into the table, i.e.
2486
+ /// `(value1,value2,value3),(value1,value2,value3),...`.
2487
+ ///
2488
+ /// See: SQLQueryGeneratorBase.generateInsertFieldValuesFromModel
1362
2489
  generateInsertValuesFromModels(Model, _models, _options) {
1363
2490
  let options = _options || {};
1364
2491
  let preparedModels = this.connection.prepareAllModelsForOperation(Model, _models, options);
@@ -1388,10 +2515,55 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
1388
2515
  };
1389
2516
  }
1390
2517
 
2518
+ /// Generate a "tail" for an `INSERT` statement.
2519
+ ///
2520
+ /// This method is provided to allow the connection
2521
+ /// itself for any given database to "tack on extra" to
2522
+ /// an `INSERT` statement. This is commonly used by databases
2523
+ /// to add on a `RETURNING` clause to the `INSERT` statement.
2524
+ /// However, its primary purpose is just to allow the engine
2525
+ /// (or the user via overloading) to add on any "extra" to
2526
+ /// an `INSERT` statement.
2527
+ ///
2528
+ /// Arguments:
2529
+ /// Model: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
2530
+ /// The model class of all `models` being inserted.
2531
+ /// models: Array<[Model](https://github.com/th317erd/mythix-orm/wiki/Model)> | [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
2532
+ /// An array of model instances, or a single model instance. These are the models
2533
+ /// that are being inserted into the database.
2534
+ /// options: object
2535
+ /// Options for the operation.
2536
+ /// context: object
2537
+ /// Useful information about the insert operation taking place. This is an
2538
+ /// object with the shape: `{ escapedTableName: string; modelChanges: Array<object>; dirtyFields: Array<Field>; }`.
2539
+ ///
2540
+ /// Return: string
2541
+ /// Any "extra" SQL to add onto the end of the `INSERT` statement.
2542
+ /// Most connection drivers will usually use this for a `RETURNING` clause.
1391
2543
  // eslint-disable-next-line no-unused-vars
1392
- generateInsertStatementTail(Model, model, options, context) {
1393
- }
1394
-
2544
+ generateInsertStatementTail(Model, models, options, context) {
2545
+ }
2546
+
2547
+ /// Generate an `INSERT` statement, for inserting
2548
+ /// one or more model instances into the database.
2549
+ ///
2550
+ /// Note:
2551
+ /// "clean" models will be skipped, and won't result
2552
+ /// in any output.
2553
+ ///
2554
+ /// Arguments:
2555
+ /// Model: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
2556
+ /// The model class of all `models` being inserted.
2557
+ /// models: Array<[Model](https://github.com/th317erd/mythix-orm/wiki/Model)> | [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
2558
+ /// An array of model instances, or a single model instance. These are the models
2559
+ /// that are being inserted into the database.
2560
+ /// options: object
2561
+ /// Options for the operation.
2562
+ ///
2563
+ /// Return: string
2564
+ /// If all models are clean, or no model instances are provided,
2565
+ /// then an empty string will be returned. Otherwise, a fully
2566
+ /// formatted `INSERT` statement will be returned.
1395
2567
  generateInsertStatement(Model, _models, _options) {
1396
2568
  let options = _options || {};
1397
2569
  let preparedModels = this.connection.prepareAllModelsForOperation(Model, _models, options);
@@ -1430,10 +2602,92 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
1430
2602
  return `INSERT INTO ${escapedTableName} (${escapedFieldNames}) VALUES ${values}`;
1431
2603
  }
1432
2604
 
2605
+ /// Generate a "tail" for an `UPDATE` statement.
2606
+ ///
2607
+ /// This method is provided to allow the connection
2608
+ /// itself for any given database to "tack on extra" to
2609
+ /// an `UPDATE` statement. This is commonly used by databases
2610
+ /// to add on a `RETURNING` clause to the `UPDATE` statement.
2611
+ /// However, its primary purpose is just to allow the engine
2612
+ /// (or the user via overloading) to add on any "extra" to
2613
+ /// an `UPDATE` statement.
2614
+ ///
2615
+ /// Arguments:
2616
+ /// Model: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
2617
+ /// The model class of all `models` being updated.
2618
+ /// model: object | [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
2619
+ /// The model instance being updated. Bulk-updates aren't really supported
2620
+ /// well by any SQL database, so Mythix ORM takes the long route and
2621
+ /// updates model instances one-by-one. If `queryEngine` is a [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine) instance,
2622
+ /// then this should be a raw object listing the attributes (field names and values)
2623
+ /// that should be applied across all matching rows.
2624
+ /// queryEngine: null | [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine)
2625
+ /// The query engine used for the update statement--if any. Updates have
2626
+ /// two primary paths: 1) update a single model instance, or 2) update across
2627
+ /// multiple rows at once. For the latter, a query engine will be used to
2628
+ /// select which rows to update. In this case, the `queryEngine` argument
2629
+ /// here will be a [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine) instance.
2630
+ /// In the case that we are updating a single model instance, then this
2631
+ /// `queryEngine` will be `null`.
2632
+ /// options: object
2633
+ /// Options for the operation.
2634
+ /// context: object
2635
+ /// Useful information about the update operation taking place. This is an
2636
+ /// object with the shape:
2637
+ /// ```javascript
2638
+ /// {
2639
+ /// queryEngine: QueryEngine | null;
2640
+ /// escapedTableName: string;
2641
+ /// modelChanges: Array<object>;
2642
+ /// dirtyFields: Array<Field>;
2643
+ /// where: string | null;
2644
+ /// }
2645
+ /// ```
2646
+ ///
2647
+ /// Return: string
2648
+ /// Any "extra" SQL to add onto the end of the `UPDATE` statement.
2649
+ /// Most connection drivers will usually use this for a `RETURNING` clause.
1433
2650
  // eslint-disable-next-line no-unused-vars
1434
2651
  generateUpdateStatementTail(Model, model, queryEngine, options, context) {
1435
2652
  }
1436
2653
 
2654
+ /// Generate an `UPDATE` statement.
2655
+ ///
2656
+ /// Update statements in Mythix ORM generally take one of
2657
+ /// two forms: 1) Update a single model instance, or 2) Update
2658
+ /// one or more columns across multiple rows at once.
2659
+ ///
2660
+ /// When updating a single model instance, the provided `model`
2661
+ /// argument should the model instance to update, and is expected
2662
+ /// to have dirty fields. If on the other hand the `queryEngine`
2663
+ /// argument is provided, and is a [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine) instance,
2664
+ /// then the provided `model` argument should be a raw object
2665
+ /// of attributes (field names) to update across all matching rows.
2666
+ ///
2667
+ /// Arguments:
2668
+ /// Model: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
2669
+ /// The model class of the model being updated.
2670
+ /// model: object | [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
2671
+ /// The model instance being updated. Bulk-updates aren't really supported
2672
+ /// well by any SQL database, so Mythix ORM takes the long route and
2673
+ /// updates model instances one-by-one. If `queryEngine` is a [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine) instance,
2674
+ /// then this should be a raw object listing the attributes (field names and values)
2675
+ /// that should be applied across all matching rows.
2676
+ /// queryEngine: null | [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine)
2677
+ /// The query engine used for the update statement--if any. Updates have
2678
+ /// two primary paths: 1) update a single model instance, or 2) update across
2679
+ /// multiple rows at once. For the latter, a query engine will be used to
2680
+ /// select which rows to update. In this case, the `queryEngine` argument
2681
+ /// here will be a [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine) instance.
2682
+ /// In the case that we are updating a single model instance, then this
2683
+ /// `queryEngine` will be `null`.
2684
+ /// options: object
2685
+ /// Options for the operation.
2686
+ ///
2687
+ /// Return: string
2688
+ /// Return a fully formatted `UPDATE` statement, either for
2689
+ /// updating a single model instance, or for updating multiple
2690
+ /// rows at once using the provided attributes.
1437
2691
  generateUpdateStatement(Model, _model, _queryEngine, _options) {
1438
2692
  if (!_model)
1439
2693
  return '';
@@ -1518,6 +2772,30 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
1518
2772
  return sqlParts.join('');
1519
2773
  }
1520
2774
 
2775
+ /// Generate a `RETURNING` clause for a `DELETE` statement.
2776
+ ///
2777
+ /// Arguments:
2778
+ /// Model: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
2779
+ /// The model class defining the table that is being deleted from.
2780
+ /// queryEngine: [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine)
2781
+ /// The query engine being used to specify which rows to delete.
2782
+ /// pkField: undefined | [Field](https://github.com/th317erd/mythix-orm/wiki/Field)
2783
+ /// The primary key field of the provided `Model`, if it has one. If the model has
2784
+ /// no primary key field, then this will be `undefined`.
2785
+ /// escapedColumnName: string
2786
+ /// The full column name (usually including the table name) of the column to use for the `RETURNING`
2787
+ /// clause. If the provided `Model` has a primary key field, then this should be that column name
2788
+ /// (though the name might be an alias of that column name, depending on how the `DELETE` statement
2789
+ /// is generated). If the provided `Model` has no primary key field, then this will be `*`.
2790
+ /// This column name might differ from the field's column name, because the `DELETE` statement
2791
+ /// might be constructed such that an alias name is needed for the column name.
2792
+ /// options?: object
2793
+ /// Options for the operation.
2794
+ ///
2795
+ /// Return: string
2796
+ /// Return a `RETURNING` clause to apply to the end of a `DELETE` statement. If
2797
+ /// `escapedColumnName` is empty, then an empty string will be returned.
2798
+ // eslint-disable-next-line no-unused-vars
1521
2799
  generateDeleteStatementReturningClause(Model, queryEngine, pkField, escapedColumnName, options) {
1522
2800
  if (!escapedColumnName)
1523
2801
  return '';
@@ -1525,8 +2803,43 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
1525
2803
  return `RETURNING ${escapedColumnName}`;
1526
2804
  }
1527
2805
 
1528
- generateDeleteStatement(Model, _queryEngine, _options) {
1529
- let queryEngine = _queryEngine;
2806
+ /// Generate a `DELETE` statement to delete rows from
2807
+ /// the table defined by `Model`, either by using a
2808
+ /// query provided by the user, or by generating a query
2809
+ /// based on the provided model instances. If no query
2810
+ /// or model instances are provided, then generate a
2811
+ /// `DELETE` statement that will delete every row from
2812
+ /// the table, truncating the table.
2813
+ ///
2814
+ /// Arguments:
2815
+ /// Model: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
2816
+ /// The model class defining the table that is being deleted from.
2817
+ /// modelsOrQueryEngine?: Array<[Model](https://github.com/th317erd/mythix-orm/wiki/Model)> | [Model](https://github.com/th317erd/mythix-orm/wiki/Model) | [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine)
2818
+ /// Model instance(s) to delete, or a query engine specifying which rows to delete.
2819
+ /// If model instances are provided, then their primary key field will be used to
2820
+ /// create a [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine)
2821
+ /// used to delete those specific primary keys from the database. If the `Model` provided
2822
+ /// has no primary key field, then an exception will be thrown, as deleting model instances
2823
+ /// this way requires the model have a primary key field. If your model has no primary key
2824
+ /// field, then it is required that the user generate their own query to select which rows
2825
+ /// to delete from the underlying table. Instead of provided model instances, the user
2826
+ /// can provide a raw [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine) instance
2827
+ /// that will be used to select which rows to delete instead. If neither is provided, then
2828
+ /// the entire table will be truncated.
2829
+ /// options?: object
2830
+ /// Options for the operation. These are simply passed through to any sub-calls
2831
+ /// this method makes internally.
2832
+ ///
2833
+ /// Return: string
2834
+ /// Return a fully formatted `DELETE` statement to delete rows from the
2835
+ /// table defined by the provided `Model`. If no model instances or [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine)
2836
+ /// are provided, then a simple `DELETE FROM "table_name";` statement will be
2837
+ /// generated, truncating the entire table. If model instances or a [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine)
2838
+ /// are provided, then a `DELETE` statement in the form of
2839
+ /// `DELETE FROM "table_name" WHERE EXISTS(SELECT ...)` will be returned,
2840
+ /// selecting which rows to delete with the provided query.
2841
+ generateDeleteStatement(Model, _modelsOrQueryEngine, _options) {
2842
+ let queryEngine = _modelsOrQueryEngine;
1530
2843
  let options = _options;
1531
2844
 
1532
2845
  if (queryEngine) {
@@ -1588,7 +2901,34 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
1588
2901
  }
1589
2902
  }
1590
2903
 
1591
- _collectRemoteReturningFields(Model) {
2904
+ /// Iterate all fields of the provided `Model`, and
2905
+ /// collect and return all fields that have a `defaultValue`
2906
+ /// with the `remote` flag set. The `remote` flag on
2907
+ /// the `defaultValue` of a field tells Mythix ORM that the
2908
+ /// default value is provided by the database itself. This
2909
+ /// would be the case for example for `AUTOINCREMENT` ids,
2910
+ /// and for `NOW()` date columns--among others.
2911
+ ///
2912
+ /// Any field that is marked with a `remote` default (a value
2913
+ /// provided by the database itself) should always be part of any
2914
+ /// `RETURNING` clause in-play, so this method is used to ensure
2915
+ /// all `remote` fields are part of the `RETURNING` clause.
2916
+ /// See [Helpers](https://github.com/th317erd/mythix-orm/wiki/Helpers) in
2917
+ /// the Mythix ORM documentation for a better explanation of "remote" fields
2918
+ /// and flags.
2919
+ ///
2920
+ /// Arguments:
2921
+ /// Model: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
2922
+ /// The model whose "remote" fields should be collected.
2923
+ /// options?: object
2924
+ /// Options for the operation.
2925
+ ///
2926
+ /// Return: Array<string>
2927
+ /// Return an array of escaped column names for direct use on
2928
+ /// a `RETURNING` clause. If the provided `Model` has no "remote"
2929
+ /// fields, then an empty array will be returned.
2930
+ // eslint-disable-next-line no-unused-vars
2931
+ _collectRemoteReturningFields(Model, options) {
1592
2932
  let remoteFieldNames = [];
1593
2933
 
1594
2934
  Model.iterateFields(({ field }) => {
@@ -1607,7 +2947,40 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
1607
2947
  return remoteFieldNames;
1608
2948
  }
1609
2949
 
1610
- _collectReturningFields(Model, model, options, context) {
2950
+ /// Generate a `RETURNING` clause for `UPDATE`
2951
+ /// and `INSERT` statements.
2952
+ ///
2953
+ /// This will generate a `RETURNING` clause that
2954
+ /// will always includes all "remote" fields for the `Model`
2955
+ /// provided, will always include the primary key of the
2956
+ /// model (if the model has one), and will include all
2957
+ /// fields marked as "dirty" across all models being inserted
2958
+ /// or updated.
2959
+ ///
2960
+ /// Arguments:
2961
+ /// Model: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
2962
+ /// The model that defines the table for the insert or update operation.
2963
+ /// models: Array<[Model](https://github.com/th317erd/mythix-orm/wiki/Model)> | [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
2964
+ /// An array of model instances, or a single model instance. These are the models
2965
+ /// that are being inserted or updated.
2966
+ /// options: object
2967
+ /// Options for the operation.
2968
+ /// context: object
2969
+ /// The same `context` that is provided to <see>SQLQueryGeneratorBase.generateInsertStatementTail</see> or
2970
+ /// <see>SQLQueryGeneratorBase.generateUpdateStatementTail</see>, depending on if this is an
2971
+ /// insert or update operation.
2972
+ ///
2973
+ /// Return: string
2974
+ /// Return a `RETURNING` clause to apply to the end of an `UPDATE`
2975
+ /// or `INSERT` statement. If no fields are found for this clause,
2976
+ /// then an empty string will be returned.
2977
+ ///
2978
+ /// See: SQLQueryGeneratorBase.generateInsertStatementTail
2979
+ ///
2980
+ /// See: SQLQueryGeneratorBase.generateUpdateStatementTail
2981
+ ///
2982
+ /// See: SQLQueryGeneratorBase._collectRemoteReturningFields
2983
+ generateReturningClause(Model, models, options, context) {
1611
2984
  let {
1612
2985
  modelChanges,
1613
2986
  dirtyFields,
@@ -1631,7 +3004,7 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
1631
3004
  if (!fieldValue.options.remote)
1632
3005
  continue;
1633
3006
 
1634
- let escapedColumnName = this.getEscapedColumnName(dirtyField.Model, dirtyField);
3007
+ let escapedColumnName = this.getEscapedColumnName(dirtyField.Model, dirtyField, options);
1635
3008
  returnFieldsMap[escapedColumnName] = true;
1636
3009
 
1637
3010
  break;
@@ -1651,11 +3024,21 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
1651
3024
 
1652
3025
  let returnFields = Object.keys(returnFieldsMap);
1653
3026
  if (!returnFields.length)
1654
- return;
3027
+ return '';
1655
3028
 
1656
3029
  return `RETURNING ${returnFields.join(',')}`;
1657
3030
  }
1658
3031
 
3032
+ /// Generate a `TRUNCATE TABLE` statement.
3033
+ ///
3034
+ /// Arguments:
3035
+ /// Model: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
3036
+ /// The model that defines the table being truncated.
3037
+ /// options?: object
3038
+ /// Options for the operation.
3039
+ ///
3040
+ /// Return: string
3041
+ /// Return a fully formatted `TRUNCATE TABLE` statement.
1659
3042
  // eslint-disable-next-line no-unused-vars
1660
3043
  generateTruncateTableStatement(Model, _options) {
1661
3044
  let escapedTableName = this.escapeID(Model.getTableName(this.connection));
@@ -1847,7 +3230,7 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
1847
3230
  let options = _options || {};
1848
3231
  let prefix = `ALTER TABLE ${this.getEscapedTableName(Model, options)}`;
1849
3232
 
1850
- return `${prefix} ADD COLUMN ${this.generateColumnDeclarationStatement(Model, field, options)}`;
3233
+ return `${prefix} ADD COLUMN${(options.ifNotExists) ? ' IF NOT EXISTS' : ''} ${this.generateColumnDeclarationStatement(Model, field, options)}`;
1851
3234
  }
1852
3235
 
1853
3236
  toConnectionString(queryEngine, options) {