mythix-orm-sql-base 1.9.5 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.biblorc.js ADDED
@@ -0,0 +1,30 @@
1
+ 'use strict';
2
+
3
+ /* global __dirname */
4
+
5
+ const Path = require('path');
6
+
7
+ module.exports = {
8
+ rootDir: __dirname,
9
+ inputDir: Path.resolve(__dirname),
10
+ outputDir: Path.resolve(__dirname, '..', 'mythix-orm-sql-base.wiki'),
11
+ files: [
12
+ {
13
+ include: /\/lib\/.*\.js$/,
14
+ parser: 'typescript',
15
+ compiler: 'typescript',
16
+ },
17
+ {
18
+ include: /\/docs\/.*\.md$/,
19
+ parser: 'markdown',
20
+ compiler: 'markdown',
21
+ },
22
+ ],
23
+ exclude: [
24
+ /node_modules|\/spec\//
25
+ ],
26
+ generatorOptions: {
27
+ repositoryURL: 'https://github.com/th317erd/mythix-orm-sql-base',
28
+ baseURL: './',
29
+ },
30
+ };
package/README.md CHANGED
@@ -1,7 +1,5 @@
1
1
  # mythix-orm-sql-base
2
2
 
3
- SQL base support for Mythix ORM
3
+ SQL base support for [Mythix ORM](https://www.npmjs.com/package/mythix-orm).
4
4
 
5
- Mythix ORM aims to replace Sequelize and the few other terrible solutions that the poor destitute Node community has to work with. Mythix ORM is not yet quite ready for prime time however, so please check back soon!
6
-
7
- This module isn't intended to be used by itself. It is a support module for other SQL-based database drivers.
5
+ This module isn't intended to be used by itself. It is a support module for other SQL database drivers.
package/docs/Home.md ADDED
@@ -0,0 +1,3 @@
1
+ SQL base support for [Mythix ORM](https://www.npmjs.com/package/mythix-orm).
2
+
3
+ This module isn't intended to be used by itself. It is a support module for other SQL database drivers.
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const SqlString = require('sqlstring');
3
4
  const Nife = require('nife');
4
5
  const UUID = require('uuid');
5
6
  const {
@@ -13,11 +14,88 @@ const {
13
14
  const SQLQueryGeneratorBase = require('./sql-query-generator-base');
14
15
 
15
16
  const SAVE_POINT_NAME_CHARS = [ 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P' ];
16
- const MODEL_RELATIONS = Symbol.for('_mythixModelRelations');
17
-
17
+ const MODEL_RELATIONS = Symbol.for('@_mythix/orm-sql-base/SQLConnectionBase/ModelRelations');
18
+
19
+ /// `SQLConnectionBase` is a support class for all other
20
+ /// SQL connection drivers built for Mythix ORM. It isn't
21
+ /// intended to be used on its own, but rather to add SQL
22
+ /// support to all other Mythix ORM SQL connections.
23
+ ///
24
+ /// Extends: [ConnectionBase](https://github.com/th317erd/mythix-orm/wiki/ConnectionBase)
18
25
  class SQLConnectionBase extends ConnectionBase {
19
26
  static DefaultQueryGenerator = SQLQueryGeneratorBase;
20
27
 
28
+ /// The low-level DB interface for escaping a
29
+ /// value. By default this function uses the
30
+ /// [sqlstring](https://www.npmjs.com/package/sqlstring)
31
+ /// module to escape values. However, the `escape`
32
+ /// method for whatever database the connection is
33
+ /// using should be used instead of this. This is
34
+ /// a "default implementation" that is meant as a
35
+ /// fallback when a connection doesn't provide its
36
+ /// own, but each connection should provide its own
37
+ /// when it is able.
38
+ ///
39
+ /// Note:
40
+ /// This method escapes "values" that are given in
41
+ /// the underlying query language of the database.
42
+ /// To escape identifiers, use the <see>ConnectionBase._escapeID</see>
43
+ /// instead.
44
+ ///
45
+ /// Return: string
46
+ /// The value provided, escaped for the specific
47
+ /// underlying database driver.
48
+ ///
49
+ /// Arguments:
50
+ /// value: any
51
+ /// The value to escape. This could be a number, a boolean,
52
+ /// a string, or anything else that can be provided to your
53
+ /// specific database.
54
+ _escape(value) {
55
+ if (Nife.instanceOf(value, 'string'))
56
+ return `'${value.replace(/'/g, '\'\'')}'`;
57
+
58
+ return SqlString.escape(value);
59
+ }
60
+
61
+ /// Low-level database method for escaping an identifier.
62
+ /// Each database driver should provide its own version of
63
+ /// this method. This is the "default" method Mythix ORM
64
+ /// provides as a "fallback" to database drivers that don't
65
+ /// supply their own.
66
+ ///
67
+ /// It works by first stripping all quotes (single `'`, double `"`, and backtick `` ` ``)
68
+ /// from the provided `value`. After this, it will split on the period (dot) character
69
+ /// `.`, and then will map each resulting part through [sqlstring](https://www.npmjs.com/package/sqlstring)
70
+ /// `escapeId` method, finally re-joining the parts with a period `.` character.
71
+ ///
72
+ /// The extra processing is to allow for already escaped identifiers to not be double-escaped.
73
+ ///
74
+ /// Return: string
75
+ /// The provided identifier, escaped for the underlying database.
76
+ ///
77
+ /// Arguments:
78
+ /// value: string
79
+ /// The identifier to escape.
80
+ _escapeID(value) {
81
+ let parts = value.replace(/['"`]/g, '').split(/\.+/g);
82
+ return parts.map((part) => SqlString.escapeId(part).replace(/^`/, '"').replace(/`$/, '"')).join('.');
83
+ }
84
+
85
+ /// Prepare an array of values for use in an `IN` statement.
86
+ /// This method will flatten the provided array, and then
87
+ /// will filter out all non-primitive values from the array.
88
+ /// Only `null`, `boolean`, `number`, `bigint`, and `string`
89
+ /// values will remain in the resulting array. The array is
90
+ /// also reduced to only unique values, with duplicates removed.
91
+ ///
92
+ /// Arguments:
93
+ /// array: Array<any>
94
+ /// The array to flatten, filter, and remove duplicates from.
95
+ ///
96
+ /// Return: Array<any>
97
+ /// Return a copy of the array provided, after being flattened, filtered,
98
+ /// and duplicates removed.
21
99
  prepareArrayValuesForSQL(_array) {
22
100
  let array = Nife.arrayFlatten(_array);
23
101
 
@@ -37,6 +115,13 @@ class SQLConnectionBase extends ConnectionBase {
37
115
  return Nife.uniq(array);
38
116
  }
39
117
 
118
+ /// Generate a `SAVEPOINT` name for use in sub-transactions.
119
+ /// This will generate a UUID v4 ID, and then mutate it so
120
+ /// that the name is SQL-safe. Finally, it will add an `SP`
121
+ /// prefix to the `SAVEPOINT` name.
122
+ ///
123
+ /// Return: string
124
+ /// A random SQL-safe `SAVEPOINT` name.
40
125
  generateSavePointName() {
41
126
  let id = UUID.v4();
42
127
 
@@ -48,6 +133,23 @@ class SQLConnectionBase extends ConnectionBase {
48
133
  return `SP${id}`;
49
134
  }
50
135
 
136
+ /// Given a `Map` from a projection field-set, find all matching
137
+ /// projected fields through this connection, and return
138
+ /// them as an array.
139
+ ///
140
+ /// Arguments:
141
+ /// projectionFieldMap: Map<string, string>
142
+ /// A set of projected fields, as a `Map`. This `Map` will usually be generated
143
+ /// by <see>SQLQueryGeneratorBase.getProjectedFields</see>. The `Map` is expected
144
+ /// to have fully qualified field names and expanded literal strings as keys, with
145
+ /// the projected database field (or literal) in database format for values.
146
+ ///
147
+ /// Return: Array<[Field](https://github.com/th317erd/mythix-orm/wiki/Field) | string>
148
+ /// All fields found from the projection `Map`. Any field that wasn't able to be
149
+ /// found on this `connection` will instead just be returned as its raw `string` form.
150
+ /// Any raw strings returned are likely literals, and so couldn't be matched to fields.
151
+ /// However, they may not always be literals, but instead may be a custom field or field
152
+ /// alias that the user requested on the projection.
51
153
  findAllFieldsFromFieldProjectionMap(projectionFieldMap) {
52
154
  let fullFieldNames = (Array.isArray(projectionFieldMap)) ? projectionFieldMap : Array.from(projectionFieldMap.keys());
53
155
 
@@ -64,6 +166,26 @@ class SQLConnectionBase extends ConnectionBase {
64
166
  }).filter(Boolean);
65
167
  }
66
168
 
169
+ /// This method will take a `{ rows: Array<any>; columns: Array<string>; }` result from
170
+ /// a `SELECT` statement, and build a map of model data and sub-model data
171
+ /// to be later turned into model instances by <see>SQLConnectionBase.buildModelsFromModelDataMap</see>.
172
+ ///
173
+ /// Arguments:
174
+ /// queryEngine: [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine)
175
+ /// The query engine that was used to generate the `SELECT` statement that returned these results.
176
+ /// result: { rows: Array<any>; columns: Array<string>; }
177
+ /// The results as returned by the database, used to build the data map to later construct model instances.
178
+ ///
179
+ /// Return: { [key: string]: Array<object> }
180
+ /// Return `result` from the database, mapped to the models that were projected. Each key in
181
+ /// this object will be a model name, and each value an array of model attributes. The model
182
+ /// attributes in each array of models represents a model instance that will be created later
183
+ /// using these attributes. There is a special key `Symbol.for('@_mythix/orm-sql-base/SQLConnectionBase/ModelRelations')`
184
+ /// that can be present on any object of model attributes (a model). This special key--if present--will be
185
+ /// another mapped object that represents "sub-models" that are "owned" by the model that has this
186
+ /// special key. This is used when projecting and loading "related models" during a load operation.
187
+ ///
188
+ /// See: SQLConnectionBase.buildModelsFromModelDataMap
67
189
  buildModelDataMapFromSelectResults(queryEngine, result) {
68
190
  if (!result)
69
191
  return {};
@@ -249,11 +371,26 @@ class SQLConnectionBase extends ConnectionBase {
249
371
  return modelData;
250
372
  }
251
373
 
252
- // eslint-disable-next-line no-unused-vars
253
- async enableForeignKeyConstraints(enable) {
254
- throw new Error(`${this.constructor.name}::enableForeignKeyConstraints: This operation is not supported for this connection type.`);
255
- }
256
-
374
+ /// Take the result from a <see>SQLConnectionBase.buildModelDataMapFromSelectResults</see> call
375
+ /// and instantiate all models defined by the model attribute map.
376
+ ///
377
+ /// Arguments:
378
+ /// queryEngine: [QueryEngine](https://github.com/th317erd/mythix-orm/wiki/QueryEngine)
379
+ /// The query engine that was used to generate the `SELECT` statement that returned these results.
380
+ /// modelDataMap: { [key: string]: Array<object> }
381
+ /// The result from a call to <see>SQLConnectionBase.buildModelDataMapFromSelectResults</see>.
382
+ /// callback?: (RootModel: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model), model: [Model](https://github.com/th317erd/mythix-orm/wiki/Model)) => [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
383
+ /// A callback that is called for each *root model* instance created from the provided `modelDataMap`.
384
+ /// Sub-models, or other models projected for the operation will not be passed through this callback.
385
+ /// Only instances of the "root model" (or "target model") of the query will be passed through this
386
+ /// callback. This callback **must** return the original model instance provided to it, or an equivalent
387
+ /// model instance (possibly the same instance modified by the callback).
388
+ ///
389
+ /// Return: Array<[Model](https://github.com/th317erd/mythix-orm/wiki/Model)>
390
+ /// An array of all fully-instantiated "root models" returned from the `SELECT` query, including
391
+ /// any "sub-models" that were loaded along-side them. All models will be marked "clean".
392
+ ///
393
+ /// See: SQLConnectionBase.buildModelDataMapFromSelectResults
257
394
  buildModelsFromModelDataMap(queryEngine, modelDataMap, callback) {
258
395
  if (Nife.isEmpty(modelDataMap))
259
396
  return [];
@@ -305,6 +442,26 @@ class SQLConnectionBase extends ConnectionBase {
305
442
  return rootModels;
306
443
  }
307
444
 
445
+ /// Take the `{ rows: Array<any>; columns: Array<string>; }` result from
446
+ /// the database for a `RETURNING` statement, and update the effected models
447
+ /// with the results.
448
+ ///
449
+ /// This is used by `UPDATE` and `INSERT` statements to sync model attributes
450
+ /// with the database update/insert operation that just took place.
451
+ ///
452
+ /// This will also set all models provided to the call as "persisted".
453
+ ///
454
+ /// Arguments:
455
+ /// Model: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
456
+ /// The model class of the array of `storedModels` that are being operated upon.
457
+ /// storedModels: Array<[Model](https://github.com/th317erd/mythix-orm/wiki/Model)>
458
+ /// The models that were just persisted.
459
+ /// results: { rows: Array<any>; columns: Array<string>; }
460
+ /// The raw results from the database, used to update all persisted models.
461
+ ///
462
+ /// Return: Array<[Model](https://github.com/th317erd/mythix-orm/wiki/Model)>
463
+ /// Return `storedModels`, with each model being updated with the returned results
464
+ /// from the database, and each model marked as "persisted".
308
465
  updateModelsFromResults(Model, storedModels, results) {
309
466
  let {
310
467
  rows,
@@ -328,6 +485,21 @@ class SQLConnectionBase extends ConnectionBase {
328
485
  return storedModels;
329
486
  }
330
487
 
488
+ /// Parse the database-specific return value for an
489
+ /// `UPDATE` or `DELETE` operation to retrieve the
490
+ /// number of rows that were updated or deleted.
491
+ ///
492
+ /// Note:
493
+ /// Each database driver should overload this method to
494
+ /// properly parse the results of an `UPDATE` or `DELETE`
495
+ /// statement to return the number of rows affected.
496
+ ///
497
+ /// Arguments:
498
+ /// queryResult: any
499
+ /// The raw database response for an `UPDATE` or `DELETE` statement.
500
+ ///
501
+ /// Return: number
502
+ /// The number of rows that were affected by the operation.
331
503
  getUpdateOrDeleteChangeCount(queryResult) {
332
504
  if (!queryResult)
333
505
  return 0;
@@ -343,20 +515,68 @@ class SQLConnectionBase extends ConnectionBase {
343
515
 
344
516
  // --------------------------------------------- //
345
517
 
518
+ /// For databases that support it, enable or disable foreign key constraints.
519
+ ///
520
+ /// Arguments:
521
+ /// enable: boolean
522
+ /// If `true`, enable foreign key constraints in the underlying database, otherwise
523
+ /// disable foreign key constraints in the underlying database.
524
+ // eslint-disable-next-line no-unused-vars
525
+ async enableForeignKeyConstraints(enable) {
526
+ throw new Error(`${this.constructor.name}::enableForeignKeyConstraints: This operation is not supported for this connection type.`);
527
+ }
528
+
529
+ /// Drop the table/bucket defined by `Model`.
530
+ ///
531
+ /// Note:
532
+ /// Mythix ORM always refers to data-spaces as "tables", even though
533
+ /// they might actually be "buckets" for example for no-SQL databases.
534
+ ///
535
+ /// Arguments:
536
+ /// Model: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
537
+ /// The model that defines the table to be dropped.
538
+ /// options?: object
539
+ /// Though these options can be database specific, they are commonly just
540
+ /// the following options.
541
+ /// | Option | Type | Default Value | Description |
542
+ /// | ------ | ---- | ------------- | ----------- |
543
+ /// | `ifExists` | `boolean` | `false` | Add an `IF EXISTS` clause to the drop table statement. |
544
+ /// | `cascade` | `boolean` | `true` | Add a `CASCADE` clause to the drop table statement (destroying rows in other tables defined by foreign key constraints). |
545
+ ///
546
+ /// Return: undefined
547
+ /// This method returns nothing.
346
548
  async dropTable(Model, options) {
347
549
  let queryGenerator = this.getQueryGenerator();
348
550
  let createTableSQL = queryGenerator.generateDropTableStatement(Model, options);
349
551
 
350
552
  // Drop table
351
- return await this.query(createTableSQL, options);
553
+ await this.query(createTableSQL, options);
352
554
  }
353
555
 
556
+ /// Create the table/bucket defined by `Model`.
557
+ ///
558
+ /// Note:
559
+ /// Mythix ORM always refers to data-spaces as "tables", even though
560
+ /// they might actually be "buckets" for example for no-SQL databases.
561
+ ///
562
+ /// Arguments:
563
+ /// Model: class [Model](https://github.com/th317erd/mythix-orm/wiki/Model)
564
+ /// The model that defines the table to be created.
565
+ /// options?: object
566
+ /// Though these options can be database specific, they are commonly just
567
+ /// the following options.
568
+ /// | Option | Type | Default Value | Description |
569
+ /// | ------ | ---- | ------------- | ----------- |
570
+ /// | `ifNotExists` | `boolean` | `false` | Add an `IF NOT EXISTS` clause to the create table statement. |
571
+ ///
572
+ /// Return: undefined
573
+ /// This method returns nothing.
354
574
  async createTable(Model, options) {
355
575
  let queryGenerator = this.getQueryGenerator();
356
576
  let createTableSQL = queryGenerator.generateCreateTableStatement(Model, options);
357
577
 
358
578
  // Create table
359
- let result = await this.query(createTableSQL, options);
579
+ await this.query(createTableSQL, options);
360
580
 
361
581
  // Create indexes and constraints
362
582
  let trailingStatements = Nife.toArray(queryGenerator.generateCreateTableStatementOuterTail(Model, options)).filter(Boolean);
@@ -366,8 +586,6 @@ class SQLConnectionBase extends ConnectionBase {
366
586
  await this.query(trailingStatement, options);
367
587
  }
368
588
  }
369
-
370
- return result;
371
589
  }
372
590
 
373
591
  async insert(Model, models, _options) {
@@ -558,6 +776,19 @@ class SQLConnectionBase extends ConnectionBase {
558
776
  return this.getUpdateOrDeleteChangeCount(await this.query(sqlStr, options));
559
777
  }
560
778
 
779
+ /// Convert the raw `{ rows: Array<any>; columns: Array<string>; }` results
780
+ /// from a database into an array of mapped objects.
781
+ ///
782
+ /// This method simply takes the rows and columns reported by the database
783
+ /// for a `SELECT` operation, and maps them to objects, where each key is
784
+ /// a column name, and each value is a column from one of the returned rows.
785
+ ///
786
+ /// Arguments:
787
+ /// result: { rows: Array<any>; columns: Array<string>; }
788
+ /// The raw results as returned by the database.
789
+ ///
790
+ /// Return: Array<object>
791
+ /// The raw results from the database, with each row mapped to an object.
561
792
  queryResultRowsToRawData(result) {
562
793
  if (!result)
563
794
  return [];
@@ -14,6 +14,7 @@ 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 {
18
19
  getEscapedFieldName(_Model, field, options) {
19
20
  let isString = Nife.instanceOf(field, 'string');
@@ -194,6 +195,12 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
194
195
  }
195
196
 
196
197
  _getLiteralAlias(literal, options) {
198
+ if (options && options.isSubField)
199
+ return '';
200
+
201
+ if (options && options.noProjectionAliases)
202
+ return '';
203
+
197
204
  let as = (literal.options && literal.options.as) || (options && options.as);
198
205
  if (Nife.isEmpty(as))
199
206
  return '';
@@ -206,14 +213,14 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
206
213
  return;
207
214
 
208
215
  let field = literal.getField(this.connection);
209
- let escapedFieldName;
216
+ let escapedColumnName;
210
217
 
211
218
  if (LiteralBase.isLiteral(field))
212
- escapedFieldName = field.toString(this.connection, options);
219
+ escapedColumnName = field.toString(this.connection, this.stackAssign(options, { isSubField: true }));
213
220
  else
214
- escapedFieldName = this.getEscapedColumnName(field.Model, field, this.stackAssign(options, literal.options));
221
+ escapedColumnName = this.getEscapedColumnName(field.Model, field, this.stackAssign(options, literal.options));
215
222
 
216
- return `AVG(${escapedFieldName})${this._getLiteralAlias(literal, options)}`;
223
+ return `AVG(${escapedColumnName})${this._getLiteralAlias(literal, options)}`;
217
224
  }
218
225
 
219
226
  _countLiteralToString(literal, options) {
@@ -221,18 +228,18 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
221
228
  return;
222
229
 
223
230
  let field = literal.getField(this.connection);
224
- let escapedFieldName;
231
+ let escapedColumnName;
225
232
 
226
233
  if (field) {
227
234
  if (LiteralBase.isLiteral(field))
228
- escapedFieldName = field.toString(this.connection, options);
235
+ escapedColumnName = field.toString(this.connection, this.stackAssign(options, { isSubField: true }));
229
236
  else
230
- escapedFieldName = this.getEscapedColumnName(field.Model, field, this.stackAssign(options, literal.options));
237
+ escapedColumnName = this.getEscapedColumnName(field.Model, field, this.stackAssign(options, literal.options));
231
238
  } else {
232
- escapedFieldName = '*';
239
+ escapedColumnName = '*';
233
240
  }
234
241
 
235
- return `COUNT(${escapedFieldName})${this._getLiteralAlias(literal, options)}`;
242
+ return `COUNT(${escapedColumnName})${this._getLiteralAlias(literal, options)}`;
236
243
  }
237
244
 
238
245
  _distinctLiteralToString(literal, options) {
@@ -240,13 +247,28 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
240
247
  return;
241
248
 
242
249
  let field = literal.getField(this.connection);
250
+ if (!field)
251
+ field = literal.valueOf();
252
+
243
253
  if (!field)
244
254
  return 'DISTINCT';
245
255
 
246
- if (LiteralBase.isLiteral(field))
247
- return `DISTINCT ON(${field.toString(this.connection, this.stackAssign(options, { noProjectionAliases: true }))})`;
256
+ if (LiteralBase.isLiteral(field)) {
257
+ let fieldStr = field.toString(this.connection, this.stackAssign(options, { noProjectionAliases: true }));
258
+ if (!fieldStr)
259
+ return '';
260
+
261
+ if (options && options.isSubField)
262
+ return `DISTINCT ${fieldStr}`;
263
+
264
+ return `DISTINCT ON(${fieldStr})`;
265
+ }
266
+
267
+ let escapedColumnName = this.getEscapedColumnName(field.Model, field, this.stackAssign(options, literal.options, { noProjectionAliases: true }));
268
+ if (options && options.isSubField)
269
+ return `DISTINCT ${escapedColumnName}`;
248
270
 
249
- return `DISTINCT ON(${this.getEscapedColumnName(field.Model, field, this.stackAssign(options, literal.options, { noProjectionAliases: true }))})`;
271
+ return `DISTINCT ON(${escapedColumnName})`;
250
272
  }
251
273
 
252
274
  _fieldLiteralToString(literal, options) {
@@ -257,7 +279,8 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
257
279
  if (LiteralBase.isLiteral(field))
258
280
  return field.toString(this.connection, options);
259
281
 
260
- return this.getEscapedProjectionName(field.Model, field, this.stackAssign(options, { noProjectionAliases: (options && !options.isProjection) }, literal.options));
282
+ let noProjectionAliases = (options && (options.isSubField || !options.isProjection || options.as === false));
283
+ return this.getEscapedProjectionName(field.Model, field, this.stackAssign(options, { noProjectionAliases }, literal.options));
261
284
  }
262
285
 
263
286
  _maxLiteralToString(literal, options) {
@@ -265,14 +288,14 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
265
288
  return;
266
289
 
267
290
  let field = literal.getField(this.connection);
268
- let escapedFieldName;
291
+ let escapedColumnName;
269
292
 
270
293
  if (LiteralBase.isLiteral(field))
271
- escapedFieldName = field.toString(this.connection, options);
294
+ escapedColumnName = field.toString(this.connection, this.stackAssign(options, { isSubField: true }));
272
295
  else
273
- escapedFieldName = this.getEscapedColumnName(field.Model, field, this.stackAssign(options, literal.options));
296
+ escapedColumnName = this.getEscapedColumnName(field.Model, field, this.stackAssign(options, literal.options));
274
297
 
275
- return `MAX(${escapedFieldName})${this._getLiteralAlias(literal, options)}`;
298
+ return `MAX(${escapedColumnName})${this._getLiteralAlias(literal, options)}`;
276
299
  }
277
300
 
278
301
  _minLiteralToString(literal, options) {
@@ -280,14 +303,14 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
280
303
  return;
281
304
 
282
305
  let field = literal.getField(this.connection);
283
- let escapedFieldName;
306
+ let escapedColumnName;
284
307
 
285
308
  if (LiteralBase.isLiteral(field))
286
- escapedFieldName = field.toString(this.connection, options);
309
+ escapedColumnName = field.toString(this.connection, this.stackAssign(options, { isSubField: true }));
287
310
  else
288
- escapedFieldName = this.getEscapedColumnName(field.Model, field, this.stackAssign(options, literal.options));
311
+ escapedColumnName = this.getEscapedColumnName(field.Model, field, this.stackAssign(options, literal.options));
289
312
 
290
- return `MIN(${escapedFieldName})${this._getLiteralAlias(literal, options)}`;
313
+ return `MIN(${escapedColumnName})${this._getLiteralAlias(literal, options)}`;
291
314
  }
292
315
 
293
316
  _sumLiteralToString(literal, options) {
@@ -295,14 +318,14 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
295
318
  return;
296
319
 
297
320
  let field = literal.getField(this.connection);
298
- let escapedFieldName;
321
+ let escapedColumnName;
299
322
 
300
323
  if (LiteralBase.isLiteral(field))
301
- escapedFieldName = field.toString(this.connection, options);
324
+ escapedColumnName = field.toString(this.connection, this.stackAssign(options, { isSubField: true }));
302
325
  else
303
- escapedFieldName = this.getEscapedColumnName(field.Model, field, this.stackAssign(options, literal.options));
326
+ escapedColumnName = this.getEscapedColumnName(field.Model, field, this.stackAssign(options, literal.options));
304
327
 
305
- return `SUM(${escapedFieldName})${this._getLiteralAlias(literal, options)}`;
328
+ return `SUM(${escapedColumnName})${this._getLiteralAlias(literal, options)}`;
306
329
  }
307
330
 
308
331
  prepareArrayValuesForSQL(array) {
@@ -504,7 +527,7 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
504
527
  if (arrayValues.length > 0)
505
528
  subParts.push(this.generateSelectQueryCondition(queryPart, arrayValues, options));
506
529
 
507
- return `(${subParts.join(' OR ')})`;
530
+ return `(${subParts.join((operator === 'NEQ') ? ' AND ' : ' OR ')})`;
508
531
  }
509
532
 
510
533
  // If no values left in array, then
@@ -1825,7 +1848,7 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
1825
1848
  let options = _options || {};
1826
1849
  let prefix = `ALTER TABLE ${this.getEscapedTableName(Model, options)}`;
1827
1850
 
1828
- return `${prefix} ADD COLUMN ${this.generateColumnDeclarationStatement(Model, field, options)}`;
1851
+ return `${prefix} ADD COLUMN${(options.ifNotExists) ? ' IF NOT EXISTS' : ''} ${this.generateColumnDeclarationStatement(Model, field, options)}`;
1829
1852
  }
1830
1853
 
1831
1854
  toConnectionString(queryEngine, options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mythix-orm-sql-base",
3
- "version": "1.9.5",
3
+ "version": "1.10.0",
4
4
  "description": "SQL base support for Mythix ORM",
5
5
  "main": "lib/index",
6
6
  "type": "commonjs",
@@ -34,11 +34,12 @@
34
34
  },
35
35
  "homepage": "https://github.com/th317erd/mythix-orm-sql-base#readme",
36
36
  "peerDependencies": {
37
- "mythix-orm": "^1.11.6"
37
+ "mythix-orm": "^1.12.0"
38
38
  },
39
39
  "dependencies": {
40
40
  "luxon": "^3.1.0",
41
41
  "nife": "^1.12.1",
42
+ "sqlstring": "^2.3.3",
42
43
  "uuid": "^9.0.0"
43
44
  },
44
45
  "devDependencies": {