mythix-orm-sql-base 1.9.2 → 1.9.4

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.
@@ -661,24 +661,37 @@ class SQLConnectionBase extends ConnectionBase {
661
661
  queryEngine = await this.finalizeQuery('read', queryEngine, options);
662
662
  queryEngine = queryEngine.clone();
663
663
 
664
- let queryContext = queryEngine.getOperationContext();
665
- let distinct = queryContext.distinct;
664
+ let queryGenerator = this.getQueryGenerator();
665
+ let queryContext = queryEngine.getOperationContext();
666
+ let distinct = queryContext.distinct;
667
+
666
668
  if (distinct) {
667
- let fullyQualifiedFieldName = distinct.getFullyQualifiedFieldName();
669
+ let distinctField = distinct.getField(this);
670
+ if (distinctField) {
671
+ if (Literals.LiteralBase.isLiteral(distinctField)) {
672
+ let field = distinctField.getField(this);
673
+ if (!field)
674
+ field = distinctField.valueOf();
675
+
676
+ if (field)
677
+ distinctField = field;
678
+ }
679
+
680
+ if (typeof distinctField.isField === 'function' && distinctField.isField(distinctField))
681
+ distinctField = new Literals.Literal(`DISTINCT ${queryGenerator.getEscapedColumnName(distinctField.Model, distinctField)}`, { isAggregate: true });
682
+ else
683
+ distinctField = new Literals.Literal(`DISTINCT ${distinctField.toString(this, { isAggregate: true })}`, { isAggregate: true });
668
684
 
669
- if (fullyQualifiedFieldName) {
670
685
  let LiteralConstructor = literal.constructor;
671
- literal = new LiteralConstructor(fullyQualifiedFieldName);
686
+ literal = new LiteralConstructor(distinctField);
672
687
 
673
- queryEngine = queryEngine.DISTINCT(false).PROJECT(literal);
688
+ queryEngine = queryEngine.DISTINCT(false);
674
689
  }
675
690
  }
676
691
 
677
- let literalStr = literal.toString(this);
678
- let queryGenerator = this.getQueryGenerator();
679
- let query = queryEngine.PROJECT(literal).ORDER(); // TODO: Remove ORDER here once aggregate ORDER BY is fixed
680
- let sqlStr = queryGenerator.generateSelectStatement(query, options);
681
-
692
+ let literalStr = literal.toString(this);
693
+ let query = queryEngine.PROJECT(literal).ORDER(); // TODO: Remove ORDER here once aggregate ORDER BY is fixed
694
+ let sqlStr = queryGenerator.generateSelectStatement(query, this.stackAssign(options, { isAggregate: true }));
682
695
  let result = await this.query(sqlStr, options);
683
696
  let columnIndex = result.columns.indexOf(literalStr);
684
697
  if (columnIndex < 0) {
@@ -1,4 +1,4 @@
1
- import { Field, FieldOrderInfo, JoinTableInfo, Model, ModelClass, PreparedModels, QueryEngine, QueryGeneratorBase } from 'mythix-orm';
1
+ import { Field, Model, ModelClass, PreparedModels, QueryEngine, QueryGeneratorBase } from 'mythix-orm';
2
2
  import { LiteralBase } from 'mythix-orm/lib/connection/literals';
3
3
  import { GenericObject } from 'mythix-orm/lib/interfaces/common';
4
4
  import { Type } from 'mythix-orm/lib/types';
@@ -11,7 +11,61 @@ export declare interface QueryConditionContext {
11
11
  value: any;
12
12
  }
13
13
 
14
+ export declare interface GetEscapedFieldNameOptions {
15
+ fieldNameOnly?: boolean;
16
+ }
17
+
18
+ export declare interface GetEscapedTableNameNameOptions {
19
+ tableNamePrefix?: string;
20
+ }
21
+
22
+ export declare interface GetEscapedColumnNameOptions extends GetEscapedTableNameNameOptions {
23
+ columnNamePrefix?: string
24
+ columnNameOnly?: boolean;
25
+ }
26
+
27
+ export declare interface GetEscapedProjectionNameOptions extends GetEscapedColumnNameOptions, GetEscapedFieldNameOptions {
28
+ noProjectionAliases?: boolean;
29
+ }
30
+
31
+ export declare interface GetEscapedModelFieldsOptions extends GetEscapedProjectionNameOptions {
32
+ asProjection?: boolean;
33
+ asColumn?: boolean;
34
+ }
35
+
36
+ export declare interface JoinTableInfo {
37
+ operator: string;
38
+ joinType: string | LiteralBase;
39
+ rootModelName: string;
40
+ joinModel: ModelClass;
41
+ joinModelName: string;
42
+ leftSideModel: ModelClass;
43
+ leftSideModelName: string;
44
+ leftQueryContext: GenericObject;
45
+ leftSideField: Field;
46
+ rightSideModel: ModelClass;
47
+ rightSideModelName: string;
48
+ rightQueryContext: GenericObject;
49
+ rightSideField: Field;
50
+ }
51
+
14
52
  declare class SQLQueryGeneratorBase extends QueryGeneratorBase {
53
+ public getEscapedFieldName(Model: ModelClass | null | undefined, field: Field, options?: GetEscapedFieldNameOptions): string;
54
+ public getEscapedColumnName(Model: ModelClass | null | undefined, field: Field, options?: GetEscapedColumnNameOptions): string;
55
+ public getEscapedTableName(modelOrField: ModelClass | Field, options?: GetEscapedTableNameNameOptions): string;
56
+ public getEscapedProjectionName(Model: ModelClass | null | undefined, field: Field, options?: GetEscapedProjectionNameOptions): string;
57
+ public getEscapedModelFields(Model: ModelClass, options?: GetEscapedModelFieldsOptions): { [key: string]: string };
58
+ public isFieldIdentifier(value: string): boolean;
59
+ public getProjectedFields(queryEngine: QueryEngine, options?: GenericObject, asMap?: false | undefined): Array<string>;
60
+ public getProjectedFields(queryEngine: QueryEngine, options?: GenericObject, asMap?: true): Map<string, string>;
61
+
62
+ public getJoinTableInfoFromQueryContexts(
63
+ leftQueryContext: GenericObject,
64
+ rightQueryContext: GenericObject,
65
+ joinType: string | LiteralBase,
66
+ options?: GenericObject
67
+ ): JoinTableInfo;
68
+
15
69
  public prepareArrayValuesForSQL(array: Array<any>): Array<any>;
16
70
  public parseFieldProjection(value: string, getRawField: boolean): string | Field | undefined;
17
71
  public parseFieldProjectionToFieldMap(selectStatement: string): Map<string, Field | string>;
@@ -15,6 +15,296 @@ const DefaultHelpers = Types.DefaultHelpers;
15
15
  const LiteralBase = Literals.LiteralBase;
16
16
 
17
17
  class SQLQueryGeneratorBase extends QueryGeneratorBase {
18
+ getEscapedFieldName(_Model, field, options) {
19
+ let isString = Nife.instanceOf(field, 'string');
20
+ let fieldName = (isString) ? field : field.fieldName;
21
+ let Model = _Model;
22
+
23
+ if (!Model && field && !isString)
24
+ Model = field.Model;
25
+
26
+ if (!Model || (options && options.fieldNameOnly === true))
27
+ return this.escapeID(fieldName);
28
+ else
29
+ return `"${field.Model.getModelName()}:${fieldName}"`;
30
+ }
31
+
32
+ getEscapedColumnName(_Model, field, options) {
33
+ let isString = Nife.instanceOf(field, 'string');
34
+ let columnName = (isString) ? field : (field.columnName || field.fieldName);
35
+ let Model = _Model;
36
+
37
+ if (!Model && field && !isString)
38
+ Model = field.Model;
39
+
40
+ if (options && options.columnNamePrefix)
41
+ columnName = `${options.columnNamePrefix}${columnName}`;
42
+
43
+ if (!Model || (options && options.columnNameOnly === true))
44
+ return this.escapeID(columnName);
45
+ else
46
+ return `${this.getEscapedTableName(Model)}.${this.escapeID(columnName)}`;
47
+ }
48
+
49
+ getEscapedTableName(_modelOrField, options) {
50
+ let Model = (_modelOrField.Model) ? _modelOrField.Model : _modelOrField;
51
+ let tableName = Model.getTableName(this.connection);
52
+
53
+ if (options && options.tableNamePrefix)
54
+ tableName = `${options.tableNamePrefix}${tableName}`;
55
+
56
+ return this.escapeID(tableName);
57
+ }
58
+
59
+ // eslint-disable-next-line no-unused-vars
60
+ getEscapedProjectionName(Model, field, options) {
61
+ if (options && options.noProjectionAliases)
62
+ return this.getEscapedColumnName(Model, field, options);
63
+ else
64
+ return `${this.getEscapedColumnName(Model, field, options)} AS ${(options && options.as) ? this.escapeID(options.as) : this.getEscapedFieldName(Model, field, options)}`;
65
+ }
66
+
67
+ // eslint-disable-next-line no-unused-vars
68
+ getEscapedModelFields(Model, options) {
69
+ let fields = {};
70
+ let modelName = Model.getModelName();
71
+
72
+ Model.iterateFields(({ field, fieldName }) => {
73
+ if (field.type.isVirtual())
74
+ return;
75
+
76
+ let result;
77
+
78
+ if (options && options.asProjection)
79
+ result = this.getEscapedProjectionName(Model, field, options);
80
+ else if (options && options.asColumn)
81
+ result = this.getEscapedColumnName(Model, field, options);
82
+ else
83
+ result = this.getEscapedFieldName(Model, field, options);
84
+
85
+ fields[`${modelName}:${fieldName}`] = result;
86
+ }, (options && options.fields));
87
+
88
+ return fields;
89
+ }
90
+
91
+ isFieldIdentifier(str) {
92
+ return (/^"[^"]+"."[^"]+"|"\w+:[\w.]+"/i).test(str);
93
+ }
94
+
95
+ getQueryEngineOrder(queryEngine, _options) {
96
+ let options = _options || {};
97
+ let context = queryEngine.getOperationContext();
98
+ let order = context.order;
99
+
100
+ return (order && order.size) ? order : this.connection.getDefaultOrder(context.rootModel, options);
101
+ }
102
+
103
+ getProjectedFields(queryEngine, _options, asMap) {
104
+ let options = this.stackAssign(_options || {}, { isProjection: true });
105
+ let context = queryEngine.getOperationContext();
106
+ let queryProjection = new Map(context.projection);
107
+ let order = this.getQueryEngineOrder(queryEngine, options);
108
+ let allProjectionFields = new Map();
109
+ let allModelsUsedInQuery = queryEngine.getAllModelsUsedInQuery();
110
+
111
+ if (!options.isSubQuery && order && order.size) {
112
+ let contextOrderSupport = this.connection.isOrderSupportedInContext(options);
113
+ if (contextOrderSupport) {
114
+ for (let [ fullyQualifiedFieldName, orderScope ] of order) {
115
+ if (!queryProjection.has(fullyQualifiedFieldName))
116
+ queryProjection.set(fullyQualifiedFieldName, orderScope);
117
+ }
118
+ }
119
+ }
120
+
121
+ for (let [ fullyQualifiedName, projectedScope ] of queryProjection) {
122
+ let { value } = projectedScope;
123
+
124
+ if (Nife.instanceOf(value, 'string')) {
125
+ // Raw string is treated as a literal
126
+ allProjectionFields.set(fullyQualifiedName, value);
127
+ continue;
128
+ } else if (LiteralBase.isLiteral(value)) {
129
+ let result = value.toString(this.connection, options);
130
+ allProjectionFields.set(result || fullyQualifiedName, result || fullyQualifiedName);
131
+
132
+ continue;
133
+ }
134
+
135
+ if (allModelsUsedInQuery.indexOf(value.Model) < 0)
136
+ continue;
137
+
138
+ let escapedFieldName = this.getEscapedProjectionName(value.Model, value, options);
139
+ allProjectionFields.set(`${value.Model.getModelName()}:${value.fieldName}`, escapedFieldName);
140
+ }
141
+
142
+ if (asMap === true)
143
+ return allProjectionFields;
144
+ else
145
+ return Array.from(allProjectionFields.values());
146
+ }
147
+
148
+ // eslint-disable-next-line no-unused-vars
149
+ getJoinTableInfoFromQueryContexts(leftQueryContext, rightQueryContext, joinType, options) {
150
+ let rootModel = leftQueryContext.rootModel;
151
+ let rootModelName = rootModel.getModelName();
152
+ let leftSideModel = leftQueryContext.Model;
153
+ let leftSideModelName = leftQueryContext.modelName;
154
+ if (!leftSideModel)
155
+ throw new Error(`${this.constructor.name}::getJoinTableInfoFromQueryEngine: Invalid operation: No model found for left-side of join statement.`);
156
+
157
+ let leftSideField = leftQueryContext.Field;
158
+ if (!leftSideField)
159
+ throw new Error(`${this.constructor.name}::getJoinTableInfoFromQueryEngine: Invalid operation: No left-side field found to match on for table join statement.`);
160
+
161
+ let isNot = leftQueryContext.not;
162
+ let operator = (isNot) ? leftQueryContext.inverseOperator : leftQueryContext.operator;
163
+ let rightSideModel = rightQueryContext.Model;
164
+ let rightSideModelName = rightQueryContext.modelName;
165
+ if (!rightSideModel)
166
+ throw new Error(`${this.constructor.name}::getJoinTableInfoFromQueryEngine: Invalid operation: No model found for right-side of join statement.`);
167
+
168
+ let rightSideField = rightQueryContext.Field;
169
+ if (!rightSideField)
170
+ throw new Error(`${this.constructor.name}::getJoinTableInfoFromQueryEngine: Invalid operation: No right-side field found to match on for table join statement.`);
171
+
172
+ let swapJoinRelation = (rightSideModelName === rootModelName);
173
+ let joinModel = (swapJoinRelation) ? leftSideModel : rightSideModel;
174
+ let joinModelName = (swapJoinRelation) ? leftSideModelName : rightSideModelName;
175
+
176
+ return {
177
+ operator,
178
+ joinType,
179
+ rootModelName,
180
+
181
+ joinModel,
182
+ joinModelName,
183
+
184
+ leftSideModel,
185
+ leftSideModelName,
186
+ leftQueryContext,
187
+ leftSideField,
188
+
189
+ rightSideModel,
190
+ rightSideModelName,
191
+ rightQueryContext,
192
+ rightSideField,
193
+ };
194
+ }
195
+
196
+ _getLiteralAlias(literal, options) {
197
+ let as = (literal.options && literal.options.as) || (options && options.as);
198
+ if (Nife.isEmpty(as))
199
+ return '';
200
+
201
+ return ` AS ${this.escapeID(as)}`;
202
+ }
203
+
204
+ _averageLiteralToString(literal, options) {
205
+ if (!literal || !LiteralBase.isLiteral(literal))
206
+ return;
207
+
208
+ let field = literal.getField(this.connection);
209
+ let escapedFieldName;
210
+
211
+ if (LiteralBase.isLiteral(field))
212
+ escapedFieldName = field.toString(this.connection, options);
213
+ else
214
+ escapedFieldName = this.getEscapedColumnName(field.Model, field, this.stackAssign(options, literal.options));
215
+
216
+ return `AVG(${escapedFieldName})${this._getLiteralAlias(literal, options)}`;
217
+ }
218
+
219
+ _countLiteralToString(literal, options) {
220
+ if (!literal || !LiteralBase.isLiteral(literal))
221
+ return;
222
+
223
+ let field = literal.getField(this.connection);
224
+ let escapedFieldName;
225
+
226
+ if (field) {
227
+ if (LiteralBase.isLiteral(field))
228
+ escapedFieldName = field.toString(this.connection, options);
229
+ else
230
+ escapedFieldName = this.getEscapedColumnName(field.Model, field, this.stackAssign(options, literal.options));
231
+ } else {
232
+ escapedFieldName = '*';
233
+ }
234
+
235
+ return `COUNT(${escapedFieldName})${this._getLiteralAlias(literal, options)}`;
236
+ }
237
+
238
+ _distinctLiteralToString(literal, options) {
239
+ if (!literal || !LiteralBase.isLiteral(literal))
240
+ return;
241
+
242
+ let field = literal.getField(this.connection);
243
+ if (!field)
244
+ return 'DISTINCT';
245
+
246
+ if (LiteralBase.isLiteral(field))
247
+ return `DISTINCT ON(${field.toString(this.connection, this.stackAssign(options, { noProjectionAliases: true }))})`;
248
+
249
+ return `DISTINCT ON(${this.getEscapedColumnName(field.Model, field, this.stackAssign(options, literal.options, { noProjectionAliases: true }))})`;
250
+ }
251
+
252
+ _fieldLiteralToString(literal, options) {
253
+ if (!literal || !LiteralBase.isLiteral(literal))
254
+ return;
255
+
256
+ let field = literal.getField(this.connection);
257
+ if (LiteralBase.isLiteral(field))
258
+ return field.toString(this.connection, options);
259
+
260
+ return this.getEscapedProjectionName(field.Model, field, this.stackAssign(options, { noProjectionAliases: (options && !options.isProjection) }, literal.options));
261
+ }
262
+
263
+ _maxLiteralToString(literal, options) {
264
+ if (!literal || !LiteralBase.isLiteral(literal))
265
+ return;
266
+
267
+ let field = literal.getField(this.connection);
268
+ let escapedFieldName;
269
+
270
+ if (LiteralBase.isLiteral(field))
271
+ escapedFieldName = field.toString(this.connection, options);
272
+ else
273
+ escapedFieldName = this.getEscapedColumnName(field.Model, field, this.stackAssign(options, literal.options));
274
+
275
+ return `MAX(${escapedFieldName})${this._getLiteralAlias(literal, options)}`;
276
+ }
277
+
278
+ _minLiteralToString(literal, options) {
279
+ if (!literal || !LiteralBase.isLiteral(literal))
280
+ return;
281
+
282
+ let field = literal.getField(this.connection);
283
+ let escapedFieldName;
284
+
285
+ if (LiteralBase.isLiteral(field))
286
+ escapedFieldName = field.toString(this.connection, options);
287
+ else
288
+ escapedFieldName = this.getEscapedColumnName(field.Model, field, this.stackAssign(options, literal.options));
289
+
290
+ return `MIN(${escapedFieldName})${this._getLiteralAlias(literal, options)}`;
291
+ }
292
+
293
+ _sumLiteralToString(literal, options) {
294
+ if (!literal || !LiteralBase.isLiteral(literal))
295
+ return;
296
+
297
+ let field = literal.getField(this.connection);
298
+ let escapedFieldName;
299
+
300
+ if (LiteralBase.isLiteral(field))
301
+ escapedFieldName = field.toString(this.connection, options);
302
+ else
303
+ escapedFieldName = this.getEscapedColumnName(field.Model, field, this.stackAssign(options, literal.options));
304
+
305
+ return `SUM(${escapedFieldName})${this._getLiteralAlias(literal, options)}`;
306
+ }
307
+
18
308
  prepareArrayValuesForSQL(array) {
19
309
  return this.connection.prepareArrayValuesForSQL(array);
20
310
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mythix-orm-sql-base",
3
- "version": "1.9.2",
3
+ "version": "1.9.4",
4
4
  "description": "SQL base support for Mythix ORM",
5
5
  "main": "lib/index",
6
6
  "type": "commonjs",
@@ -34,7 +34,7 @@
34
34
  },
35
35
  "homepage": "https://github.com/th317erd/mythix-orm-sql-base#readme",
36
36
  "peerDependencies": {
37
- "mythix-orm": "^1.11.3"
37
+ "mythix-orm": "^1.11.5"
38
38
  },
39
39
  "dependencies": {
40
40
  "luxon": "^3.1.0",