@mikro-orm/sql 7.0.7-dev.6 → 7.0.7-dev.8
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/AbstractSqlPlatform.d.ts +2 -0
- package/AbstractSqlPlatform.js +4 -0
- package/dialects/mssql/MsSqlNativeQueryBuilder.js +25 -14
- package/dialects/mysql/BaseMySqlPlatform.d.ts +1 -0
- package/dialects/mysql/BaseMySqlPlatform.js +3 -0
- package/dialects/oracledb/OracleNativeQueryBuilder.js +20 -10
- package/package.json +2 -2
- package/query/NativeQueryBuilder.d.ts +3 -1
- package/query/NativeQueryBuilder.js +32 -11
package/AbstractSqlPlatform.d.ts
CHANGED
|
@@ -36,6 +36,8 @@ export declare abstract class AbstractSqlPlatform extends Platform {
|
|
|
36
36
|
quoteJsonKey(key: string): string;
|
|
37
37
|
getJsonIndexDefinition(index: IndexDef): string[];
|
|
38
38
|
supportsUnionWhere(): boolean;
|
|
39
|
+
/** Whether the platform supports `count(distinct col1, col2)` with multiple columns. If false, a subquery wrapper is used instead. */
|
|
40
|
+
supportsMultiColumnCountDistinct(): boolean;
|
|
39
41
|
supportsSchemas(): boolean;
|
|
40
42
|
/** @inheritDoc */
|
|
41
43
|
generateCustomOrder(escapedColumn: string, values: unknown[]): string;
|
package/AbstractSqlPlatform.js
CHANGED
|
@@ -91,6 +91,10 @@ export class AbstractSqlPlatform extends Platform {
|
|
|
91
91
|
supportsUnionWhere() {
|
|
92
92
|
return true;
|
|
93
93
|
}
|
|
94
|
+
/** Whether the platform supports `count(distinct col1, col2)` with multiple columns. If false, a subquery wrapper is used instead. */
|
|
95
|
+
supportsMultiColumnCountDistinct() {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
94
98
|
supportsSchemas() {
|
|
95
99
|
return false;
|
|
96
100
|
}
|
|
@@ -137,13 +137,18 @@ export class MsSqlNativeQueryBuilder extends NativeQueryBuilder {
|
|
|
137
137
|
this.parts[this.parts.length - 1] += ';';
|
|
138
138
|
}
|
|
139
139
|
compileSelect() {
|
|
140
|
+
const wrapCountSubquery = this.needsCountSubquery();
|
|
141
|
+
if (wrapCountSubquery) {
|
|
142
|
+
this.parts.push(`select count(*) as ${this.quote('count')} from (`);
|
|
143
|
+
}
|
|
140
144
|
this.parts.push('select');
|
|
141
|
-
|
|
145
|
+
// skip top(?) inside the count subquery — it would limit the distinct rows before counting
|
|
146
|
+
if (this.options.limit != null && this.options.offset == null && !wrapCountSubquery) {
|
|
142
147
|
this.parts.push(`top (?)`);
|
|
143
148
|
this.params.push(this.options.limit);
|
|
144
149
|
}
|
|
145
150
|
this.addHintComment();
|
|
146
|
-
this.parts.push(`${this.getFields()} from ${this.getTableName()}`);
|
|
151
|
+
this.parts.push(`${this.getFields(wrapCountSubquery)} from ${this.getTableName()}`);
|
|
147
152
|
this.addLockClause();
|
|
148
153
|
if (this.options.joins) {
|
|
149
154
|
for (const join of this.options.joins) {
|
|
@@ -163,21 +168,27 @@ export class MsSqlNativeQueryBuilder extends NativeQueryBuilder {
|
|
|
163
168
|
this.parts.push(`having ${this.options.having.sql}`);
|
|
164
169
|
this.params.push(...this.options.having.params);
|
|
165
170
|
}
|
|
166
|
-
if (
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (this.options.offset != null) {
|
|
170
|
-
/* v8 ignore next */
|
|
171
|
-
if (!this.options.orderBy) {
|
|
172
|
-
throw new Error('Order by clause is required for pagination');
|
|
171
|
+
if (!wrapCountSubquery) {
|
|
172
|
+
if (this.options.orderBy) {
|
|
173
|
+
this.parts.push(`order by ${this.options.orderBy}`);
|
|
173
174
|
}
|
|
174
|
-
this.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
175
|
+
if (this.options.offset != null) {
|
|
176
|
+
/* v8 ignore next */
|
|
177
|
+
if (!this.options.orderBy) {
|
|
178
|
+
throw new Error('Order by clause is required for pagination');
|
|
179
|
+
}
|
|
180
|
+
this.parts.push(`offset ? rows`);
|
|
181
|
+
this.params.push(this.options.offset);
|
|
182
|
+
if (this.options.limit != null) {
|
|
183
|
+
this.parts.push(`fetch next ? rows only`);
|
|
184
|
+
this.params.push(this.options.limit);
|
|
185
|
+
}
|
|
179
186
|
}
|
|
180
187
|
}
|
|
188
|
+
if (wrapCountSubquery) {
|
|
189
|
+
const asKeyword = this.platform.usesAsKeyword() ? ' as ' : ' ';
|
|
190
|
+
this.parts.push(`)${asKeyword}${this.quote('dcnt')}`);
|
|
191
|
+
}
|
|
181
192
|
}
|
|
182
193
|
addLockClause() {
|
|
183
194
|
if (!this.options.lockMode ||
|
|
@@ -14,6 +14,7 @@ export declare class BaseMySqlPlatform extends AbstractSqlPlatform {
|
|
|
14
14
|
readonly "desc nulls first": "is not null";
|
|
15
15
|
readonly "desc nulls last": "is null";
|
|
16
16
|
};
|
|
17
|
+
supportsMultiColumnCountDistinct(): boolean;
|
|
17
18
|
/** @internal */
|
|
18
19
|
createNativeQueryBuilder(): MySqlNativeQueryBuilder;
|
|
19
20
|
getDefaultCharset(): string;
|
|
@@ -18,6 +18,9 @@ export class BaseMySqlPlatform extends AbstractSqlPlatform {
|
|
|
18
18
|
[QueryOrder.desc_nulls_first]: 'is not null',
|
|
19
19
|
[QueryOrder.desc_nulls_last]: 'is null',
|
|
20
20
|
};
|
|
21
|
+
supportsMultiColumnCountDistinct() {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
21
24
|
/** @internal */
|
|
22
25
|
createNativeQueryBuilder() {
|
|
23
26
|
return new MySqlNativeQueryBuilder(this);
|
|
@@ -213,9 +213,13 @@ export class OracleNativeQueryBuilder extends NativeQueryBuilder {
|
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
215
|
compileSelect() {
|
|
216
|
+
const wrapCountSubquery = this.needsCountSubquery();
|
|
217
|
+
if (wrapCountSubquery) {
|
|
218
|
+
this.parts.push(`select count(*) as ${this.quote('count')} from (`);
|
|
219
|
+
}
|
|
216
220
|
this.parts.push('select');
|
|
217
221
|
this.addHintComment();
|
|
218
|
-
this.parts.push(`${this.getFields()} from ${this.getTableName()}`);
|
|
222
|
+
this.parts.push(`${this.getFields(wrapCountSubquery)} from ${this.getTableName()}`);
|
|
219
223
|
if (this.options.joins) {
|
|
220
224
|
for (const join of this.options.joins) {
|
|
221
225
|
this.parts.push(join.sql);
|
|
@@ -234,16 +238,22 @@ export class OracleNativeQueryBuilder extends NativeQueryBuilder {
|
|
|
234
238
|
this.parts.push(`having ${this.options.having.sql}`);
|
|
235
239
|
this.params.push(...this.options.having.params);
|
|
236
240
|
}
|
|
237
|
-
if (
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
this.
|
|
242
|
-
|
|
241
|
+
if (!wrapCountSubquery) {
|
|
242
|
+
if (this.options.orderBy) {
|
|
243
|
+
this.parts.push(`order by ${this.options.orderBy}`);
|
|
244
|
+
}
|
|
245
|
+
if (this.options.offset != null) {
|
|
246
|
+
this.parts.push(`offset ? rows`);
|
|
247
|
+
this.params.push(this.options.offset);
|
|
248
|
+
}
|
|
249
|
+
if (this.options.limit != null) {
|
|
250
|
+
this.parts.push(`fetch next ? rows only`);
|
|
251
|
+
this.params.push(this.options.limit);
|
|
252
|
+
}
|
|
243
253
|
}
|
|
244
|
-
if (
|
|
245
|
-
this.
|
|
246
|
-
this.
|
|
254
|
+
if (wrapCountSubquery) {
|
|
255
|
+
const asKeyword = this.platform.usesAsKeyword() ? ' as ' : ' ';
|
|
256
|
+
this.parts.push(`)${asKeyword}${this.quote('dcnt')}`);
|
|
247
257
|
}
|
|
248
258
|
}
|
|
249
259
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/sql",
|
|
3
|
-
"version": "7.0.7-dev.
|
|
3
|
+
"version": "7.0.7-dev.8",
|
|
4
4
|
"description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"data-mapper",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"@mikro-orm/core": "^7.0.6"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@mikro-orm/core": "7.0.7-dev.
|
|
56
|
+
"@mikro-orm/core": "7.0.7-dev.8"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">= 22.17.0"
|
|
@@ -121,7 +121,9 @@ export declare class NativeQueryBuilder implements Subquery {
|
|
|
121
121
|
as(alias: string): this;
|
|
122
122
|
toRaw(): RawQueryFragment;
|
|
123
123
|
protected compileSelect(): void;
|
|
124
|
-
|
|
124
|
+
/** Whether this COUNT query needs a subquery wrapper for multi-column distinct. */
|
|
125
|
+
protected needsCountSubquery(): boolean;
|
|
126
|
+
protected getFields(countSubquery?: boolean): string;
|
|
125
127
|
protected compileInsert(): void;
|
|
126
128
|
protected addOutputClause(type: 'inserted' | 'deleted'): void;
|
|
127
129
|
protected processInsertData(): string[];
|
|
@@ -281,9 +281,13 @@ export class NativeQueryBuilder {
|
|
|
281
281
|
return raw(sql, params);
|
|
282
282
|
}
|
|
283
283
|
compileSelect() {
|
|
284
|
+
const wrapCountSubquery = this.needsCountSubquery();
|
|
285
|
+
if (wrapCountSubquery) {
|
|
286
|
+
this.parts.push(`select count(*) as ${this.quote('count')} from (`);
|
|
287
|
+
}
|
|
284
288
|
this.parts.push('select');
|
|
285
289
|
this.addHintComment();
|
|
286
|
-
this.parts.push(`${this.getFields()} from ${this.getTableName()}`);
|
|
290
|
+
this.parts.push(`${this.getFields(wrapCountSubquery)} from ${this.getTableName()}`);
|
|
287
291
|
if (this.options.joins) {
|
|
288
292
|
for (const join of this.options.joins) {
|
|
289
293
|
this.parts.push(join.sql);
|
|
@@ -302,23 +306,40 @@ export class NativeQueryBuilder {
|
|
|
302
306
|
this.parts.push(`having ${this.options.having.sql}`);
|
|
303
307
|
this.params.push(...this.options.having.params);
|
|
304
308
|
}
|
|
305
|
-
if (
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
this.
|
|
310
|
-
|
|
309
|
+
if (!wrapCountSubquery) {
|
|
310
|
+
if (this.options.orderBy) {
|
|
311
|
+
this.parts.push(`order by ${this.options.orderBy}`);
|
|
312
|
+
}
|
|
313
|
+
if (this.options.limit != null) {
|
|
314
|
+
this.parts.push(`limit ?`);
|
|
315
|
+
this.params.push(this.options.limit);
|
|
316
|
+
}
|
|
317
|
+
if (this.options.offset != null) {
|
|
318
|
+
this.parts.push(`offset ?`);
|
|
319
|
+
this.params.push(this.options.offset);
|
|
320
|
+
}
|
|
311
321
|
}
|
|
312
|
-
if (
|
|
313
|
-
this.
|
|
314
|
-
this.
|
|
322
|
+
if (wrapCountSubquery) {
|
|
323
|
+
const asKeyword = this.platform.usesAsKeyword() ? ' as ' : ' ';
|
|
324
|
+
this.parts.push(`)${asKeyword}${this.quote('dcnt')}`);
|
|
315
325
|
}
|
|
316
326
|
}
|
|
317
|
-
|
|
327
|
+
/** Whether this COUNT query needs a subquery wrapper for multi-column distinct. */
|
|
328
|
+
needsCountSubquery() {
|
|
329
|
+
return (this.type === QueryType.COUNT &&
|
|
330
|
+
!!this.options.distinct &&
|
|
331
|
+
this.options.select.length > 1 &&
|
|
332
|
+
!this.platform.supportsMultiColumnCountDistinct());
|
|
333
|
+
}
|
|
334
|
+
getFields(countSubquery) {
|
|
318
335
|
if (!this.options.select || this.options.select.length === 0) {
|
|
319
336
|
throw new Error('No fields selected');
|
|
320
337
|
}
|
|
321
338
|
let fields = this.options.select.map(field => this.quote(field)).join(', ');
|
|
339
|
+
// count subquery emits just `distinct col1, col2` — the outer wrapper adds count(*)
|
|
340
|
+
if (countSubquery) {
|
|
341
|
+
return `distinct ${fields}`;
|
|
342
|
+
}
|
|
322
343
|
if (this.options.distinct) {
|
|
323
344
|
fields = `distinct ${fields}`;
|
|
324
345
|
}
|