mythix-orm-sql-base 1.7.4 → 1.9.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.
@@ -2,7 +2,7 @@ import { ConnectionBase, Field, ModelClass, QueryEngine, QueryResults, Model } f
2
2
  import { GenericObject } from 'mythix-orm/lib/interfaces/common';
3
3
 
4
4
  export declare interface ModelDataFromQueryResults {
5
- [ key: string ]: Array<GenericObject>;
5
+ [key: string]: Array<GenericObject>;
6
6
  }
7
7
 
8
8
  declare class SQLConnectionBase extends ConnectionBase {
@@ -20,17 +20,19 @@ declare class SQLConnectionBase extends ConnectionBase {
20
20
 
21
21
  public enableForeignKeyConstraints(enable: boolean): Promise<void>;
22
22
 
23
- buildModelsFromModelDataMap(
23
+ public buildModelsFromModelDataMap(
24
24
  queryEngine: QueryEngine,
25
25
  modelDataMap: ModelDataFromQueryResults,
26
26
  callback: (Model: ModelClass, model: Model) => Model,
27
27
  ): Array<Model>;
28
28
 
29
- updateModelsFromResults(
29
+ public updateModelsFromResults(
30
30
  Model: ModelClass,
31
31
  storedModels: Array<Model>,
32
32
  results: QueryResults
33
33
  ): Array<Model>;
34
+
35
+ public getUpdateOrDeleteChangeCount(queryResult: any): number;
34
36
  }
35
37
 
36
38
  export default SQLConnectionBase;
@@ -10,10 +10,14 @@ const {
10
10
  Model: ModelBase,
11
11
  } = require('mythix-orm');
12
12
 
13
+ const SQLQueryGeneratorBase = require('./sql-query-generator-base');
14
+
13
15
  const SAVE_POINT_NAME_CHARS = [ 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P' ];
14
16
  const MODEL_RELATIONS = Symbol.for('_mythixModelRelations');
15
17
 
16
18
  class SQLConnectionBase extends ConnectionBase {
19
+ static DefaultQueryGenerator = SQLQueryGeneratorBase;
20
+
17
21
  prepareArrayValuesForSQL(_array) {
18
22
  let array = Nife.arrayFlatten(_array);
19
23
 
@@ -95,13 +99,16 @@ class SQLConnectionBase extends ConnectionBase {
95
99
  return true;
96
100
  };
97
101
 
98
- let context = queryEngine._getRawQueryContext();
102
+ let context = queryEngine.getOperationContext();
99
103
  let fields = this.findAllFieldsFromFieldProjectionMap(columns);
100
104
  let rootModelName = context.rootModelName;
101
105
  let modelData = {};
102
106
  let alreadyVisited = {};
103
107
 
104
108
  let fieldInfo = fields.map((field) => {
109
+ if (Nife.instanceOf(field, 'string'))
110
+ return field;
111
+
105
112
  let Model = field.Model;
106
113
  let modelName = Model.getModelName();
107
114
  let pkFieldName = Model.getPrimaryKeyFieldName();
@@ -115,6 +122,9 @@ class SQLConnectionBase extends ConnectionBase {
115
122
  });
116
123
 
117
124
  let modelInfo = fieldInfo.reduce((obj, info) => {
125
+ if (Nife.instanceOf(info, 'string'))
126
+ return obj;
127
+
118
128
  obj[info.modelName] = info;
119
129
  return obj;
120
130
  }, {});
@@ -139,10 +149,24 @@ class SQLConnectionBase extends ConnectionBase {
139
149
 
140
150
  // Collect row
141
151
  for (let j = 0, jl = fieldInfo.length; j < jl; j++) {
142
- let {
143
- field,
144
- modelName,
145
- } = fieldInfo[j];
152
+ let thisFieldInfo = fieldInfo[j];
153
+ let fieldName;
154
+ let modelName;
155
+
156
+ if (Nife.instanceOf(thisFieldInfo, 'string')) {
157
+ let def = Utils.parseQualifiedName(thisFieldInfo);
158
+ if (!def.modelName)
159
+ continue;
160
+
161
+ if (Nife.isEmpty(def.fieldNames))
162
+ continue;
163
+
164
+ modelName = def.modelName;
165
+ fieldName = def.fieldNames[0];
166
+ } else {
167
+ modelName = thisFieldInfo.modelName;
168
+ fieldName = thisFieldInfo.field.fieldName;
169
+ }
146
170
 
147
171
  let dataContext = data[modelName];
148
172
  let remoteValue = row[j];
@@ -162,7 +186,7 @@ class SQLConnectionBase extends ConnectionBase {
162
186
  });
163
187
  }
164
188
 
165
- dataContext[field.fieldName] = remoteValue;
189
+ dataContext[fieldName] = remoteValue;
166
190
  }
167
191
 
168
192
  let rootModel;
@@ -234,7 +258,7 @@ class SQLConnectionBase extends ConnectionBase {
234
258
  if (Nife.isEmpty(modelDataMap))
235
259
  return [];
236
260
 
237
- let queryContext = queryEngine._getRawQueryContext();
261
+ let queryContext = queryEngine.getOperationContext();
238
262
  let rootModelName = queryContext.rootModelName;
239
263
  let RootModel = queryContext.rootModel;
240
264
  if (!rootModelName || !RootModel)
@@ -304,6 +328,19 @@ class SQLConnectionBase extends ConnectionBase {
304
328
  return storedModels;
305
329
  }
306
330
 
331
+ getUpdateOrDeleteChangeCount(queryResult) {
332
+ if (!queryResult)
333
+ return 0;
334
+
335
+ if ('rows' in queryResult && Array.isArray(queryResult.rows))
336
+ return queryResult.rows.length;
337
+
338
+ if ('changes' in queryResult)
339
+ return queryResult.changes;
340
+
341
+ return 0;
342
+ }
343
+
307
344
  // --------------------------------------------- //
308
345
 
309
346
  async dropTable(Model, options) {
@@ -378,7 +415,7 @@ class SQLConnectionBase extends ConnectionBase {
378
415
  if (Nife.isEmpty(primaryKeyFieldName))
379
416
  throw new Error(`${this.constructor.name}::update: Model has no primary key field.`);
380
417
 
381
- return await this.bulkModelOperation(
418
+ let result = await this.bulkModelOperation(
382
419
  Model,
383
420
  models,
384
421
  Object.assign({}, options || {}, { isUpdateOperation: true }),
@@ -398,6 +435,7 @@ class SQLConnectionBase extends ConnectionBase {
398
435
  throw new Error(`${this.constructor.name}::update: Model's primary key is empty. Models being updated must have a valid primary key.`);
399
436
 
400
437
  query = query[primaryKeyFieldName].EQ(pkFieldValue);
438
+ query = await this.finalizeQuery('update', query, options);
401
439
 
402
440
  let sqlStr = queryGenerator.generateUpdateStatement(Model, model, query, options);
403
441
  if (!sqlStr)
@@ -412,6 +450,8 @@ class SQLConnectionBase extends ConnectionBase {
412
450
  await this.runSaveHooks(Model, models, 'onAfterUpdate', 'onAfterSave', options);
413
451
  },
414
452
  );
453
+
454
+ return (Array.isArray(result)) ? result.length : 1;
415
455
  }
416
456
 
417
457
  async updateAll(_queryEngine, model, _options) {
@@ -419,17 +459,16 @@ class SQLConnectionBase extends ConnectionBase {
419
459
  if (!queryEngine)
420
460
  throw new Error(`${this.constructor.name}::updateAll: Model class or query is required to update.`);
421
461
 
422
- let rootModel = queryEngine._getRawQueryContext().rootModel;
462
+ let options = Object.assign({}, _options || {}, { isUpdateOperation: true });
463
+ queryEngine = await this.finalizeQuery('update', queryEngine, options);
464
+
465
+ let rootModel = queryEngine.getOperationContext().rootModel;
423
466
  if (!rootModel)
424
467
  throw new Error(`${this.constructor.name}::updateAll: Root model not found, and is required to update.`);
425
468
 
426
- let options = Object.assign({}, _options || {}, { isUpdateOperation: true });
427
469
  let queryGenerator = this.getQueryGenerator();
428
470
  let sqlStr = queryGenerator.generateUpdateStatement(rootModel, model, queryEngine, options);
429
-
430
- // TODO: Use "RETURNING" to return pks of of updated rows
431
-
432
- return await this.query(sqlStr, options);
471
+ return this.getUpdateOrDeleteChangeCount(await this.query(sqlStr, options));
433
472
  }
434
473
 
435
474
  async destroyModels(Model, _models, _options) {
@@ -439,23 +478,24 @@ class SQLConnectionBase extends ConnectionBase {
439
478
  let options = _options || {};
440
479
  if (_models == null) {
441
480
  if (options.truncate !== true)
442
- return;
481
+ return 0;
443
482
 
483
+ let query = await this.finalizeQuery('delete', Model.where(this).unscoped(), options);
444
484
  let queryGenerator = this.getQueryGenerator();
445
- let sqlStr = queryGenerator.generateDeleteStatement(Model, Model.where(this).unscoped(), options);
485
+ let sqlStr = queryGenerator.generateDeleteStatement(Model, query, options);
446
486
 
447
487
  return await this.query(sqlStr, options);
448
488
  }
449
489
 
450
490
  let models = Nife.toArray(_models).filter(Boolean);
451
491
  if (Nife.isEmpty(models))
452
- return;
492
+ return 0;
453
493
 
454
494
  let primaryKeyFieldName = Model.getPrimaryKeyFieldName();
455
495
  if (Nife.isEmpty(primaryKeyFieldName))
456
496
  throw new Error(`${this.constructor.name}::destroyModels: Model has no primary key field. You must supply a query to delete models with no primary key.`);
457
497
 
458
- return await this.bulkModelOperation(
498
+ let result = await this.bulkModelOperation(
459
499
  Model,
460
500
  models,
461
501
  Object.assign({}, options, { isDeleteOperation: true }),
@@ -478,13 +518,16 @@ class SQLConnectionBase extends ConnectionBase {
478
518
  if (Nife.isEmpty(pkIDs))
479
519
  return;
480
520
 
481
- let sqlStr = queryGenerator.generateDeleteStatement(Model, Model.where(this).id.EQ(pkIDs));
521
+ let query = await this.finalizeQuery('delete', Model.where(this).id.EQ(pkIDs), options);
522
+ let sqlStr = queryGenerator.generateDeleteStatement(Model, query);
482
523
  if (!sqlStr)
483
524
  return;
484
525
 
485
526
  await this.query(sqlStr, options);
486
527
  },
487
528
  );
529
+
530
+ return (Array.isArray(result)) ? result.length : 1;
488
531
  }
489
532
 
490
533
  async destroy(_queryEngineOrModel, modelsOrOptions, _options) {
@@ -503,14 +546,40 @@ class SQLConnectionBase extends ConnectionBase {
503
546
  if (!queryEngine)
504
547
  throw new Error(`${this.constructor.name}::destroy: Model class or query is required to destroy.`);
505
548
 
506
- let rootModel = queryEngine._getRawQueryContext().rootModel;
549
+ let options = modelsOrOptions || {};
550
+ queryEngine = await this.finalizeQuery('delete', queryEngine, options);
551
+
552
+ let rootModel = queryEngine.getOperationContext().rootModel;
507
553
  if (!rootModel)
508
554
  throw new Error(`${this.constructor.name}::destroy: Root model not found, and is required to destroy.`);
509
555
 
510
- let options = modelsOrOptions || {};
511
556
  let queryGenerator = this.getQueryGenerator();
512
557
  let sqlStr = queryGenerator.generateDeleteStatement(rootModel, queryEngine, options);
513
- return await this.query(sqlStr, options);
558
+ return this.getUpdateOrDeleteChangeCount(await this.query(sqlStr, options));
559
+ }
560
+
561
+ queryResultRowsToRawData(result) {
562
+ if (!result)
563
+ return [];
564
+
565
+ let { columns, rows } = result;
566
+ if (Nife.isEmpty(columns) || Nife.isEmpty(rows))
567
+ return [];
568
+
569
+ let finalData = [];
570
+ for (let i = 0, il = rows.length; i < il; i++) {
571
+ let row = rows[i];
572
+ let data = {};
573
+
574
+ for (let j = 0, jl = columns.length; j < jl; j++) {
575
+ let column = columns[j];
576
+ data[column] = row[j];
577
+ }
578
+
579
+ finalData.push(data);
580
+ }
581
+
582
+ return finalData;
514
583
  }
515
584
 
516
585
  async *select(_queryEngine, _options) {
@@ -525,12 +594,27 @@ class SQLConnectionBase extends ConnectionBase {
525
594
  throw new TypeError(`${this.constructor.name}::select: First argument must be a model class or a query.`);
526
595
  }
527
596
 
528
- let queryContext = queryEngine._getRawQueryContext();
529
597
  let options = _options || {};
530
- let batchSize = options.batchSize || 500;
531
- let startIndex = queryContext.offset || 0;
532
598
  let queryGenerator = this.getQueryGenerator();
533
599
 
600
+ queryEngine = await this.finalizeQuery('read', queryEngine, options);
601
+
602
+ let queryContext = queryEngine.getOperationContext();
603
+ let groupBy = queryContext.groupBy;
604
+ if (groupBy && groupBy.size > 0) {
605
+ let sqlStatement = queryGenerator.generateSelectStatement(queryEngine, options);
606
+ let result = await this.query(sqlStatement, options);
607
+ let rows = this.queryResultRowsToRawData(result);
608
+
609
+ for (let i = 0, il = rows.length; i < il; i++)
610
+ yield rows[i];
611
+
612
+ return;
613
+ }
614
+
615
+ let batchSize = options.batchSize || 500;
616
+ let startIndex = queryContext.offset || 0;
617
+
534
618
  while (true) {
535
619
  let query = queryEngine.clone().LIMIT(batchSize).OFFSET(startIndex);
536
620
  let sqlStatement = queryGenerator.generateSelectStatement(query, options);
@@ -574,9 +658,10 @@ class SQLConnectionBase extends ConnectionBase {
574
658
  if (!queryEngine)
575
659
  throw new TypeError(`${this.constructor.name}::aggregate: First argument must be a model class or a query.`);
576
660
 
661
+ queryEngine = await this.finalizeQuery('read', queryEngine, options);
577
662
  queryEngine = queryEngine.clone();
578
663
 
579
- let queryContext = queryEngine._getRawQueryContext();
664
+ let queryContext = queryEngine.getOperationContext();
580
665
  let distinct = queryContext.distinct;
581
666
  if (distinct) {
582
667
  let fullyQualifiedFieldName = distinct.getFullyQualifiedFieldName();
@@ -611,7 +696,7 @@ class SQLConnectionBase extends ConnectionBase {
611
696
  if (!queryEngine)
612
697
  throw new TypeError(`${this.constructor.name}::average: First argument must be a model class or a query.`);
613
698
 
614
- let rootModel = queryEngine._getRawQueryContext().rootModel;
699
+ let rootModel = queryEngine.getOperationContext().rootModel;
615
700
  let field = Utils.fieldToFullyQualifiedName(_field, rootModel);
616
701
 
617
702
  return await this.aggregate(queryEngine, new Literals.AverageLiteral(field), options);
@@ -622,7 +707,9 @@ class SQLConnectionBase extends ConnectionBase {
622
707
  if (!queryEngine)
623
708
  throw new TypeError(`${this.constructor.name}::count: First argument must be a model class or a query.`);
624
709
 
625
- let rootModel = queryEngine._getRawQueryContext().rootModel;
710
+ queryEngine = await this.finalizeQuery('read', queryEngine, options);
711
+
712
+ let rootModel = queryEngine.getOperationContext().rootModel;
626
713
  let field = (_field) ? Utils.fieldToFullyQualifiedName(_field, rootModel) : null;
627
714
 
628
715
  return await this.aggregate(queryEngine, new Literals.CountLiteral(field), options);
@@ -633,7 +720,9 @@ class SQLConnectionBase extends ConnectionBase {
633
720
  if (!queryEngine)
634
721
  throw new TypeError(`${this.constructor.name}::min: First argument must be a model class or a query.`);
635
722
 
636
- let rootModel = queryEngine._getRawQueryContext().rootModel;
723
+ queryEngine = await this.finalizeQuery('read', queryEngine, options);
724
+
725
+ let rootModel = queryEngine.getOperationContext().rootModel;
637
726
  let field = Utils.fieldToFullyQualifiedName(_field, rootModel);
638
727
 
639
728
  return await this.aggregate(queryEngine, new Literals.MinLiteral(field), options);
@@ -644,7 +733,9 @@ class SQLConnectionBase extends ConnectionBase {
644
733
  if (!queryEngine)
645
734
  throw new TypeError(`${this.constructor.name}::max: First argument must be a model class or a query.`);
646
735
 
647
- let rootModel = queryEngine._getRawQueryContext().rootModel;
736
+ queryEngine = await this.finalizeQuery('read', queryEngine, options);
737
+
738
+ let rootModel = queryEngine.getOperationContext().rootModel;
648
739
  let field = Utils.fieldToFullyQualifiedName(_field, rootModel);
649
740
 
650
741
  return await this.aggregate(queryEngine, new Literals.MaxLiteral(field), options);
@@ -655,7 +746,9 @@ class SQLConnectionBase extends ConnectionBase {
655
746
  if (!queryEngine)
656
747
  throw new TypeError(`${this.constructor.name}::sum: First argument must be a model class or a query.`);
657
748
 
658
- let rootModel = queryEngine._getRawQueryContext().rootModel;
749
+ queryEngine = await this.finalizeQuery('read', queryEngine, options);
750
+
751
+ let rootModel = queryEngine.getOperationContext().rootModel;
659
752
  let field = Utils.fieldToFullyQualifiedName(_field, rootModel);
660
753
 
661
754
  return await this.aggregate(queryEngine, new Literals.SumLiteral(field), options);
@@ -683,7 +776,9 @@ class SQLConnectionBase extends ConnectionBase {
683
776
  throw new TypeError(`${this.constructor.name}::pluck: First argument must be a model class or a query.`);
684
777
  }
685
778
 
686
- let queryContext = queryEngine._getRawQueryContext();
779
+ queryEngine = await this.finalizeQuery('read', queryEngine, options);
780
+
781
+ let queryContext = queryEngine.getOperationContext();
687
782
  let rootModel = queryContext.rootModel;
688
783
  let rootModelName = rootModel.getModelName();
689
784
 
@@ -70,10 +70,8 @@ declare class SQLQueryGeneratorBase extends QueryGeneratorBase {
70
70
  public sortJoinRelationOrder(joins: Map<string, Array<JoinTableInfo>>): Array<string>;
71
71
  public generateSelectQueryJoinTables(queryEngine: QueryEngine, options?: GenericObject): string;
72
72
  public generateSelectWhereConditions(queryEngine: QueryEngine, options?: GenericObject): string;
73
- public generateOrderClause(
74
- orders: LiteralBase | FieldOrderInfo | Array<LiteralBase | FieldOrderInfo>,
75
- options?: GenericObject
76
- ): string;
73
+ public generateOrderClause(queryEngine: QueryEngine, options?: GenericObject): string;
74
+ public generateGroupByClause(queryEngine: QueryEngine, options?: GenericObject): string;
77
75
 
78
76
  public generateLimitClause(limit: LiteralBase | number | string, options?: GenericObject): string;
79
77
  public generateOffsetClause(offset: LiteralBase | number | string, options?: GenericObject): string;
@@ -158,6 +156,7 @@ declare class SQLQueryGeneratorBase extends QueryGeneratorBase {
158
156
  options?: GenericObject,
159
157
  ): string;
160
158
 
159
+ public generateDeleteStatementReturningClause(Model: ModelClass, queryEngine: QueryEngine, pkField: Field | null, escapedColumnName: string | null, options: GenericObject): string;
161
160
  public generateDeleteStatement(Model: ModelClass, queryEngine: QueryEngine, options?: GenericObject): string;
162
161
  public generateTruncateTableStatement(Model: ModelClass, options?: GenericObject): string;
163
162
  public generateAlterTableStatement(Model: ModelClass, newModelAttributes, options?: GenericObject): string;
@@ -90,27 +90,25 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
90
90
  projectionFieldMap.set(fieldPart, fieldPart);
91
91
  }
92
92
 
93
- let projectedFieldNames = Array.from(projectionFieldMap.keys());
94
- let sortedFieldNames = this.sortedProjectedFields(projectedFieldNames);
95
- let sortedProjectionFieldMap = new Map();
96
-
97
- for (let i = 0, il = sortedFieldNames.length; i < il; i++) {
98
- let sortedFieldName = sortedFieldNames[i];
99
- let value = projectionFieldMap.get(sortedFieldName);
100
-
101
- sortedProjectionFieldMap.set(sortedFieldName, value);
102
- }
103
-
104
- return sortedProjectionFieldMap;
93
+ return projectionFieldMap;
105
94
  }
106
95
 
107
96
  generateSelectQueryFieldProjection(queryEngine, options, asMap) {
108
97
  let projectedFields = this.getProjectedFields(queryEngine, options, asMap);
109
98
 
110
- if (asMap === true)
99
+ if (asMap === true) {
111
100
  return projectedFields;
112
- else
113
- return Array.from(projectedFields.values()).join(',');
101
+ } else {
102
+ let projectedFieldList = Array.from(projectedFields.values()).join(',');
103
+
104
+ let distinct = queryEngine.getOperationContext().distinct;
105
+ if (distinct) {
106
+ let result = distinct.toString(this.connection, { isProjection: true });
107
+ return `${result} ${projectedFieldList}`;
108
+ }
109
+
110
+ return projectedFieldList;
111
+ }
114
112
  }
115
113
 
116
114
  // eslint-disable-next-line no-unused-vars
@@ -120,23 +118,25 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
120
118
 
121
119
  switch (operator) {
122
120
  case 'EQ':
123
- if (!valueIsReference) {
124
- if (value === null || value === true || value === false)
125
- return 'IS';
126
- else if (Array.isArray(value))
127
- return 'IN';
128
- }
121
+ if (valueIsReference)
122
+ return '=';
129
123
 
130
- return '=';
124
+ if (value === null || value === true || value === false)
125
+ return 'IS';
126
+ else if (Array.isArray(value))
127
+ return 'IN';
128
+ else
129
+ return '=';
131
130
  case 'NEQ':
132
- if (!valueIsReference) {
133
- if (value === null || value === true || value === false)
134
- return 'IS NOT';
135
- else if (Array.isArray(value))
136
- return 'NOT IN';
137
- }
131
+ if (valueIsReference)
132
+ return '!=';
138
133
 
139
- return '!=';
134
+ if (value === null || value === true || value === false)
135
+ return 'IS NOT';
136
+ else if (Array.isArray(value))
137
+ return 'NOT IN';
138
+ else
139
+ return '!=';
140
140
  case 'GT':
141
141
  return '>';
142
142
  case 'GTE':
@@ -181,6 +181,9 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
181
181
  let isNot = queryPart.not;
182
182
  let operator = (isNot) ? queryPart.inverseOperator : queryPart.operator;
183
183
 
184
+ if (operator === 'EXISTS' || operator === 'NOT EXISTS')
185
+ return `${operator}(${this.generateSelectStatement(value.clone().PROJECT(new Literals.Literal('1')).LIMIT(1).OFFSET(0), this.stackAssign(options, { isSubQuery: true, subQueryOperator: operator }))})`;
186
+
184
187
  // If the value is an array, then handle the
185
188
  // special "IN" case for an array
186
189
  if (Array.isArray(value)) {
@@ -227,9 +230,12 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
227
230
  let sqlOperator = this.generateSelectQueryOperatorFromQueryEngineOperator(queryPart, operator, value, false, options);
228
231
 
229
232
  if (QueryEngine.isQuery(value)) {
230
- if (!value._queryHasConditions())
233
+ if (!value.queryHasConditions())
231
234
  return '';
232
235
 
236
+ if (Object.prototype.hasOwnProperty.call(queryPart, 'subType') && (queryPart.subType === 'ANY' || queryPart.subType === 'ALL'))
237
+ return `${escapedColumnName} ${sqlOperator} ${queryPart.subType}(${this.generateSelectStatement(value, this.stackAssign(options, { isSubQuery: true, subQueryOperator: queryPart.subType }))})`;
238
+
233
239
  if (sqlOperator === '=')
234
240
  sqlOperator = 'IN';
235
241
  else if (sqlOperator === '!=')
@@ -339,7 +345,7 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
339
345
  items.push(joinInfo);
340
346
  };
341
347
 
342
- let query = queryEngine._getRawQuery();
348
+ let query = queryEngine.getOperationStack();
343
349
  let joins = new Map();
344
350
 
345
351
  for (let i = 0, il = query.length; i < il; i++) {
@@ -353,11 +359,11 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
353
359
 
354
360
  // If the query has a condition, then it is a sub-query
355
361
  // not a join
356
- if (operatorValue._getRawQueryContext().condition)
362
+ if (operatorValue.getOperationContext().condition)
357
363
  continue;
358
364
 
359
365
  let joinType = this.generateSQLJoinTypeFromQueryEngineJoinType(queryPart.joinType, queryPart.joinOuter, options);
360
- let joinInfo = this.getJoinTableInfoFromQueryContexts(queryPart, operatorValue._getRawQueryContext(), joinType, options);
366
+ let joinInfo = this.getJoinTableInfoFromQueryContexts(queryPart, operatorValue.getOperationContext(), joinType, options);
361
367
 
362
368
  addToJoins(joinInfo);
363
369
  }
@@ -391,7 +397,7 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
391
397
  return '';
392
398
  };
393
399
 
394
- let query = queryEngine._getRawQuery();
400
+ let query = queryEngine.getOperationStack();
395
401
  let sqlParts = [];
396
402
  let hasValue = false;
397
403
 
@@ -452,13 +458,15 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
452
458
  return sqlParts.join(' ');
453
459
  }
454
460
 
455
- // eslint-disable-next-line no-unused-vars
456
- generateOrderClause(_orders, _options) {
457
- if (LiteralBase.isLiteral(_orders))
458
- return _orders.toString(this.connection);
461
+ generateOrderClause(queryEngine, _options) {
462
+ if (!queryEngine)
463
+ return '';
459
464
 
460
- let orders = Nife.toArray(_orders).filter(Boolean);
461
- if (Nife.isEmpty(orders))
465
+ if (typeof queryEngine.getOperationContext !== 'function')
466
+ return '';
467
+
468
+ let order = this.getQueryEngineOrder(queryEngine, _options);
469
+ if (!order || !order.size)
462
470
  return '';
463
471
 
464
472
  let options = _options || {};
@@ -466,38 +474,44 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
466
474
  if (contextOrderSupport === false)
467
475
  return '';
468
476
 
469
- let orderByParts = [];
470
- for (let i = 0, il = orders.length; i < il; i++) {
471
- let orderField = orders[i];
477
+ let allModelsUsedInQuery = queryEngine.getAllModelsUsedInQuery();
478
+ let orderByParts = [];
472
479
 
473
- if (LiteralBase.isLiteral(orderField)) {
474
- let fieldStr = orderField.toString(this.connection);
475
- if (!options.projectionFields.has(fieldStr) && contextOrderSupport === 'PROJECTION_ONLY')
476
- continue;
480
+ for (let [ fullyQualifiedFieldName, orderScope ] of order) {
481
+ let { value, direction } = orderScope;
477
482
 
478
- orderByParts.push(fieldStr);
479
- continue;
483
+ // Only allow fields that are in our projection
484
+ if (options.projectionFields && options.onlyProjectedFields !== false) {
485
+ if (!options.projectionFields.has(fullyQualifiedFieldName) && contextOrderSupport === 'PROJECTION_ONLY')
486
+ continue;
480
487
  }
481
488
 
482
- // Only allow fields that are in our projection
483
- if (options.projectionFields) {
484
- let modelName = orderField.Model.getModelName();
485
- let fieldName = orderField.Field.fieldName;
486
- let fqFieldName = `${modelName}:${fieldName}`;
489
+ let finalResult;
490
+
491
+ if (Nife.instanceOf(value, 'string')) {
492
+ // Raw string is treated as a literal
493
+ finalResult = value;
494
+ } else if (LiteralBase.isLiteral(value)) {
495
+ finalResult = value.toString(this.connection, options);
487
496
 
488
- if (!options.projectionFields.has(fqFieldName) && contextOrderSupport === 'PROJECTION_ONLY')
497
+ // fullyQualifiedFieldName is the stringified
498
+ // literal here.
499
+ if (options.projectionFields && !options.projectionFields.has(finalResult) && contextOrderSupport === 'PROJECTION_ONLY')
489
500
  continue;
501
+ } else {
502
+ if (allModelsUsedInQuery.indexOf(value.Model) < 0)
503
+ continue;
504
+
505
+ finalResult = this.getEscapedColumnName(value.Model, value.columnName, options);
490
506
  }
491
507
 
492
- let escapedColumnName = this.getEscapedColumnName(orderField.Model, orderField.Field.columnName, options);
493
508
  let orderStr;
494
-
495
509
  if (options.reverseOrder !== true)
496
- orderStr = (orderField.direction === '-') ? 'DESC' : 'ASC';
510
+ orderStr = (direction === '-') ? 'DESC' : 'ASC';
497
511
  else
498
- orderStr = (orderField.direction === '-') ? 'ASC' : 'DESC';
512
+ orderStr = (direction === '-') ? 'ASC' : 'DESC';
499
513
 
500
- orderByParts.push(`${escapedColumnName} ${orderStr}`);
514
+ orderByParts.push(`${finalResult} ${orderStr}`);
501
515
  }
502
516
 
503
517
  if (Nife.isEmpty(orderByParts))
@@ -506,6 +520,68 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
506
520
  return `ORDER BY ${orderByParts.join(',')}`;
507
521
  }
508
522
 
523
+ generateGroupByClause(queryEngine, _options) {
524
+ if (!queryEngine)
525
+ return '';
526
+
527
+ if (typeof queryEngine.getOperationContext !== 'function')
528
+ return '';
529
+
530
+ let groupBy = queryEngine.getOperationContext().groupBy;
531
+ if (!groupBy || !groupBy.size)
532
+ return '';
533
+
534
+ let options = _options || {};
535
+
536
+ let groupByParts = [];
537
+ for (let groupByScope of groupBy.values()) {
538
+ let { value } = groupByScope;
539
+ let finalResult;
540
+
541
+ if (Nife.instanceOf(value, 'string')) {
542
+ // Raw string is treated as a literal
543
+ finalResult = value;
544
+ } else if (LiteralBase.isLiteral(value)) {
545
+ finalResult = value.toString(this.connection, options);
546
+ } else {
547
+ finalResult = this.getEscapedColumnName(value.Model, value.columnName, options);
548
+ }
549
+
550
+ groupByParts.push(finalResult);
551
+ }
552
+
553
+ if (Nife.isEmpty(groupByParts))
554
+ return '';
555
+
556
+ return `GROUP BY ${groupByParts.join(',')}`;
557
+ }
558
+
559
+ generateHavingClause(queryEngine, options) {
560
+ let where = this.generateSelectWhereConditions(queryEngine, options);
561
+ return (where) ? `HAVING (${where})` : '';
562
+ }
563
+
564
+ generateGroupByAndHavingClause(queryEngine, options) {
565
+ if (!queryEngine)
566
+ return '';
567
+
568
+ let sqlParts = [];
569
+ let groupByStatement = this.generateGroupByClause(queryEngine, options);
570
+ if (groupByStatement)
571
+ sqlParts.push(groupByStatement);
572
+ else
573
+ return '';
574
+
575
+ let having = queryEngine.getOperationContext().having;
576
+ if (having) {
577
+ let havingStatement = this.generateHavingClause(having, options);
578
+ if (havingStatement)
579
+ sqlParts.push(havingStatement);
580
+ }
581
+
582
+ return sqlParts.join(' ');
583
+ }
584
+
509
585
  // eslint-disable-next-line no-unused-vars
510
586
  generateLimitClause(limit, options) {
511
587
  if (LiteralBase.isLiteral(limit))
@@ -523,27 +599,31 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
523
599
  }
524
600
 
525
601
  generateSelectOrderLimitOffset(queryEngine, _options) {
602
+ if (!queryEngine)
603
+ return '';
604
+
526
605
  let options = _options || {};
606
+ let context = queryEngine.getOperationContext();
527
607
  let {
528
608
  order,
529
609
  limit,
530
610
  offset,
531
- } = this.getOrderLimitOffset(queryEngine, options);
611
+ } = context;
612
+
532
613
  let sqlParts = [];
533
614
  let hasOrder = false;
615
+ let hasLimit = (Nife.instanceOf(limit, 'number') && isFinite(limit));
534
616
 
535
- if (Nife.isNotEmpty(order)) {
536
- if (this.connection.isOrderSupportedInContext(options)) {
537
- let result = this.generateOrderClause(order, options);
538
- if (result) {
539
- hasOrder = true;
540
- sqlParts.push(result);
541
- }
617
+ if (options.orderClause !== false && !(options.orderClauseOnlyIfLimited === true && !hasLimit) && this.connection.isOrderSupportedInContext(options)) {
618
+ let result = this.generateOrderClause(queryEngine, options);
619
+ if (result) {
620
+ hasOrder = true;
621
+ sqlParts.push(result);
622
+ }
542
623
 
543
- if (hasOrder && !(Nife.instanceOf(limit, 'number') && isFinite(limit)) && options && options.forceLimit) {
544
- limit = options.forceLimit;
545
- offset = 0;
546
- }
624
+ if (hasOrder && !hasLimit && options && options.forceLimit) {
625
+ limit = options.forceLimit;
626
+ offset = 0;
547
627
  }
548
628
  }
549
629
 
@@ -578,6 +658,9 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
578
658
  if (orderLimitOffset)
579
659
  sqlParts.push(orderLimitOffset);
580
660
 
661
+ if (options.separateWhereAndOrder)
662
+ return { where, orderLimitOffset };
663
+
581
664
  return sqlParts.join(' ');
582
665
  }
583
666
 
@@ -590,7 +673,7 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
590
673
  if (options.includeRelations === true)
591
674
  queryEngine = queryEngine.clone().PROJECT('*');
592
675
 
593
- let rootModel = queryEngine._getRawQueryContext().rootModel;
676
+ let rootModel = queryEngine.getOperationContext().rootModel;
594
677
  if (!rootModel)
595
678
  throw new Error(`${this.constructor.name}::generateSelectStatement: No root model found.`);
596
679
 
@@ -600,11 +683,18 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
600
683
  options.selectStatement = true;
601
684
 
602
685
  projectionFields = this.generateSelectQueryFieldProjection(queryEngine, options, true);
603
- sqlParts.push(Array.from(projectionFields.values()).join(','));
686
+ sqlParts.push(this.generateSelectQueryFieldProjection(queryEngine, options));
604
687
 
605
688
  sqlParts.push(this.generateFromTableOrTableJoin(rootModel, undefined, options));
606
689
  sqlParts.push(this.generateSelectQueryJoinTables(queryEngine, options));
607
- sqlParts.push(this.generateWhereAndOrderLimitOffset(queryEngine, this.stackAssign(options, { projectionFields })));
690
+ let { where, orderLimitOffset } = this.generateWhereAndOrderLimitOffset(queryEngine, this.stackAssign(options, { projectionFields, separateWhereAndOrder: true }));
691
+ if (where)
692
+ sqlParts.push(`WHERE ${where}`);
693
+
694
+ sqlParts.push(this.generateGroupByAndHavingClause(queryEngine, options));
695
+
696
+ if (orderLimitOffset)
697
+ sqlParts.push(orderLimitOffset);
608
698
 
609
699
  let sql = sqlParts.filter(Boolean).join(' ');
610
700
 
@@ -1092,7 +1182,7 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
1092
1182
 
1093
1183
  let where;
1094
1184
  if (queryEngine) {
1095
- where = this.generateWhereAndOrderLimitOffset(queryEngine, options);
1185
+ where = this.generateWhereAndOrderLimitOffset(queryEngine, this.stackAssign(options, { orderClauseOnlyIfLimited: true }));
1096
1186
  if (where) {
1097
1187
  if (options.newlines !== false)
1098
1188
  sqlParts.push('\n');
@@ -1122,6 +1212,13 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
1122
1212
  return sqlParts.join('');
1123
1213
  }
1124
1214
 
1215
+ generateDeleteStatementReturningClause(Model, queryEngine, pkField, escapedColumnName, options) {
1216
+ if (!escapedColumnName)
1217
+ return '';
1218
+
1219
+ return `RETURNING ${escapedColumnName}`;
1220
+ }
1221
+
1125
1222
  generateDeleteStatement(Model, _queryEngine, _options) {
1126
1223
  let queryEngine = _queryEngine;
1127
1224
  let options = _options;
@@ -1137,10 +1234,11 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
1137
1234
  }
1138
1235
  }
1139
1236
 
1140
- let escapedTableName = this.getEscapedTableName(Model, options);
1141
- if (queryEngine && queryEngine._queryHasConditions()) {
1142
- if (queryEngine._queryHasJoins()) {
1143
- let pkField = Model.getPrimaryKeyField();
1237
+ let escapedTableName = this.getEscapedTableName(Model, options);
1238
+ let pkField = Model.getPrimaryKeyField();
1239
+
1240
+ if (queryEngine && queryEngine.queryHasConditions()) {
1241
+ if (queryEngine.queryHasJoins()) {
1144
1242
  if (!pkField)
1145
1243
  throw new Error(`${this.constructor.name}::generateDeleteStatement: Can not delete using table joins on a table with no primary key field.`);
1146
1244
 
@@ -1150,22 +1248,34 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
1150
1248
  queryEngine
1151
1249
  .AND[Model.getModelName(this.connection)][pkField.fieldName]
1152
1250
  .EQ(new Literals.Literal(`${escapedTableNameAlias}.${escapedFieldAlias}`))
1153
- .PROJECT(new Literals.Literal('1')),
1251
+ .PROJECT(new Literals.Literal('1'))
1252
+ .LIMIT(1)
1253
+ .OFFSET(0),
1154
1254
  this.stackAssign(
1155
1255
  options,
1156
1256
  {
1157
- isSubQuery: true,
1158
- subQueryOperator: 'EXISTS',
1159
- noProjectionAliases: true,
1160
- forceLimit: 4294967295,
1257
+ isSubQuery: true,
1258
+ subQueryOperator: 'EXISTS',
1259
+ noProjectionAliases: true,
1260
+ forceLimit: 4294967295,
1161
1261
  },
1162
1262
  ),
1163
1263
  );
1164
1264
 
1165
- return `DELETE FROM ${escapedTableName} AS ${escapedTableNameAlias} WHERE EXISTS (${innerSelect})`;
1265
+ let returningField = `${escapedTableNameAlias}.${this.getEscapedColumnName(pkField.Model, pkField, this.stackAssign(options, { columnNameOnly: true }))}`;
1266
+ let returningClause = this.generateDeleteStatementReturningClause(Model, queryEngine, pkField, returningField, options);
1267
+
1268
+ return `DELETE FROM ${escapedTableName} AS ${escapedTableNameAlias} WHERE EXISTS (${innerSelect})${(returningClause) ? ` ${returningClause}` : ''}`;
1166
1269
  } else {
1167
- let where = this.generateWhereAndOrderLimitOffset(queryEngine, { ...options, forceLimit: 4294967295 });
1168
- return `DELETE FROM ${escapedTableName}${(where) ? ` ${where}` : ''}`;
1270
+ let returningField = (pkField) ? this.getEscapedColumnName(pkField.Model, pkField, options) : '*';
1271
+ let returningClause = this.generateDeleteStatementReturningClause(Model, queryEngine, pkField, returningField, options);
1272
+
1273
+ let {
1274
+ where,
1275
+ orderLimitOffset,
1276
+ } = this.generateWhereAndOrderLimitOffset(queryEngine, { ...options, forceLimit: 4294967295, separateWhereAndOrder: true });
1277
+
1278
+ return `DELETE FROM ${escapedTableName}${(where) ? ` WHERE ${where}` : ''}${(returningClause) ? ` ${returningClause}` : ''}${(orderLimitOffset) ? ` ${orderLimitOffset}` : ''}`;
1169
1279
  }
1170
1280
  } else {
1171
1281
  return `DELETE FROM ${escapedTableName}`;
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "mythix-orm-sql-base",
3
- "version": "1.7.4",
3
+ "version": "1.9.0",
4
4
  "description": "SQL base support for Mythix ORM",
5
5
  "main": "lib/index",
6
6
  "type": "commonjs",
7
7
  "scripts": {
8
8
  "coverage": "clear ; node ./node_modules/.bin/nyc ./node_modules/.bin/jasmine",
9
9
  "test": "node ./node_modules/.bin/jasmine",
10
+ "test-fast": "node ./node_modules/.bin/jasmine --fail-fast",
10
11
  "test-debug": "node --inspect-brk ./node_modules/.bin/jasmine",
11
12
  "test-watch": "watch 'clear ; node ./node_modules/.bin/jasmine' . --wait=2 --interval=1"
12
13
  },
@@ -33,7 +34,7 @@
33
34
  },
34
35
  "homepage": "https://github.com/th317erd/mythix-orm-sql-base#readme",
35
36
  "peerDependencies": {
36
- "mythix-orm": "^1.8.3"
37
+ "mythix-orm": "^1.11.0"
37
38
  },
38
39
  "dependencies": {
39
40
  "luxon": "^3.1.0",