mythix-orm-sql-base 1.8.0 → 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;
@@ -106,6 +106,9 @@ class SQLConnectionBase extends ConnectionBase {
106
106
  let alreadyVisited = {};
107
107
 
108
108
  let fieldInfo = fields.map((field) => {
109
+ if (Nife.instanceOf(field, 'string'))
110
+ return field;
111
+
109
112
  let Model = field.Model;
110
113
  let modelName = Model.getModelName();
111
114
  let pkFieldName = Model.getPrimaryKeyFieldName();
@@ -119,6 +122,9 @@ class SQLConnectionBase extends ConnectionBase {
119
122
  });
120
123
 
121
124
  let modelInfo = fieldInfo.reduce((obj, info) => {
125
+ if (Nife.instanceOf(info, 'string'))
126
+ return obj;
127
+
122
128
  obj[info.modelName] = info;
123
129
  return obj;
124
130
  }, {});
@@ -143,10 +149,24 @@ class SQLConnectionBase extends ConnectionBase {
143
149
 
144
150
  // Collect row
145
151
  for (let j = 0, jl = fieldInfo.length; j < jl; j++) {
146
- let {
147
- field,
148
- modelName,
149
- } = 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
+ }
150
170
 
151
171
  let dataContext = data[modelName];
152
172
  let remoteValue = row[j];
@@ -166,7 +186,7 @@ class SQLConnectionBase extends ConnectionBase {
166
186
  });
167
187
  }
168
188
 
169
- dataContext[field.fieldName] = remoteValue;
189
+ dataContext[fieldName] = remoteValue;
170
190
  }
171
191
 
172
192
  let rootModel;
@@ -308,6 +328,19 @@ class SQLConnectionBase extends ConnectionBase {
308
328
  return storedModels;
309
329
  }
310
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
+
311
344
  // --------------------------------------------- //
312
345
 
313
346
  async dropTable(Model, options) {
@@ -382,7 +415,7 @@ class SQLConnectionBase extends ConnectionBase {
382
415
  if (Nife.isEmpty(primaryKeyFieldName))
383
416
  throw new Error(`${this.constructor.name}::update: Model has no primary key field.`);
384
417
 
385
- return await this.bulkModelOperation(
418
+ let result = await this.bulkModelOperation(
386
419
  Model,
387
420
  models,
388
421
  Object.assign({}, options || {}, { isUpdateOperation: true }),
@@ -417,6 +450,8 @@ class SQLConnectionBase extends ConnectionBase {
417
450
  await this.runSaveHooks(Model, models, 'onAfterUpdate', 'onAfterSave', options);
418
451
  },
419
452
  );
453
+
454
+ return (Array.isArray(result)) ? result.length : 1;
420
455
  }
421
456
 
422
457
  async updateAll(_queryEngine, model, _options) {
@@ -433,10 +468,7 @@ class SQLConnectionBase extends ConnectionBase {
433
468
 
434
469
  let queryGenerator = this.getQueryGenerator();
435
470
  let sqlStr = queryGenerator.generateUpdateStatement(rootModel, model, queryEngine, options);
436
-
437
- // TODO: Use "RETURNING" to return pks of of updated rows
438
-
439
- return await this.query(sqlStr, options);
471
+ return this.getUpdateOrDeleteChangeCount(await this.query(sqlStr, options));
440
472
  }
441
473
 
442
474
  async destroyModels(Model, _models, _options) {
@@ -446,7 +478,7 @@ class SQLConnectionBase extends ConnectionBase {
446
478
  let options = _options || {};
447
479
  if (_models == null) {
448
480
  if (options.truncate !== true)
449
- return;
481
+ return 0;
450
482
 
451
483
  let query = await this.finalizeQuery('delete', Model.where(this).unscoped(), options);
452
484
  let queryGenerator = this.getQueryGenerator();
@@ -457,13 +489,13 @@ class SQLConnectionBase extends ConnectionBase {
457
489
 
458
490
  let models = Nife.toArray(_models).filter(Boolean);
459
491
  if (Nife.isEmpty(models))
460
- return;
492
+ return 0;
461
493
 
462
494
  let primaryKeyFieldName = Model.getPrimaryKeyFieldName();
463
495
  if (Nife.isEmpty(primaryKeyFieldName))
464
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.`);
465
497
 
466
- return await this.bulkModelOperation(
498
+ let result = await this.bulkModelOperation(
467
499
  Model,
468
500
  models,
469
501
  Object.assign({}, options, { isDeleteOperation: true }),
@@ -494,6 +526,8 @@ class SQLConnectionBase extends ConnectionBase {
494
526
  await this.query(sqlStr, options);
495
527
  },
496
528
  );
529
+
530
+ return (Array.isArray(result)) ? result.length : 1;
497
531
  }
498
532
 
499
533
  async destroy(_queryEngineOrModel, modelsOrOptions, _options) {
@@ -521,7 +555,31 @@ class SQLConnectionBase extends ConnectionBase {
521
555
 
522
556
  let queryGenerator = this.getQueryGenerator();
523
557
  let sqlStr = queryGenerator.generateDeleteStatement(rootModel, queryEngine, options);
524
- 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;
525
583
  }
526
584
 
527
585
  async *select(_queryEngine, _options) {
@@ -536,13 +594,26 @@ class SQLConnectionBase extends ConnectionBase {
536
594
  throw new TypeError(`${this.constructor.name}::select: First argument must be a model class or a query.`);
537
595
  }
538
596
 
539
- let options = _options || {};
597
+ let options = _options || {};
598
+ let queryGenerator = this.getQueryGenerator();
599
+
540
600
  queryEngine = await this.finalizeQuery('read', queryEngine, options);
541
601
 
542
- let queryContext = queryEngine.getOperationContext();
543
- let batchSize = options.batchSize || 500;
544
- let startIndex = queryContext.offset || 0;
545
- let queryGenerator = this.getQueryGenerator();
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;
546
617
 
547
618
  while (true) {
548
619
  let query = queryEngine.clone().LIMIT(batchSize).OFFSET(startIndex);
@@ -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)) {
@@ -230,6 +233,9 @@ class SQLQueryGeneratorBase extends QueryGeneratorBase {
230
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 === '!=')
@@ -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
 
@@ -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);
1237
+ let escapedTableName = this.getEscapedTableName(Model, options);
1238
+ let pkField = Model.getPrimaryKeyField();
1239
+
1141
1240
  if (queryEngine && queryEngine.queryHasConditions()) {
1142
1241
  if (queryEngine.queryHasJoins()) {
1143
- let pkField = Model.getPrimaryKeyField();
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.8.0",
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.10.2"
37
+ "mythix-orm": "^1.11.0"
37
38
  },
38
39
  "dependencies": {
39
40
  "luxon": "^3.1.0",