@mikro-orm/sql 7.0.0-rc.1 → 7.0.0-rc.3
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/AbstractSqlConnection.js +2 -1
- package/AbstractSqlDriver.d.ts +18 -12
- package/AbstractSqlDriver.js +187 -38
- package/AbstractSqlPlatform.d.ts +1 -0
- package/AbstractSqlPlatform.js +5 -3
- package/PivotCollectionPersister.js +2 -2
- package/SqlEntityManager.d.ts +5 -4
- package/SqlEntityManager.js +5 -5
- package/dialects/mssql/MsSqlNativeQueryBuilder.d.ts +2 -0
- package/dialects/mssql/MsSqlNativeQueryBuilder.js +8 -4
- package/dialects/mysql/BaseMySqlPlatform.js +1 -2
- package/dialects/mysql/MySqlSchemaHelper.js +21 -10
- package/dialects/postgresql/BasePostgreSqlPlatform.js +38 -30
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +63 -47
- package/dialects/sqlite/BaseSqliteConnection.d.ts +4 -1
- package/dialects/sqlite/BaseSqliteConnection.js +4 -0
- package/dialects/sqlite/NodeSqliteDialect.d.ts +21 -0
- package/dialects/sqlite/NodeSqliteDialect.js +43 -0
- package/dialects/sqlite/SqliteDriver.d.ts +12 -0
- package/dialects/sqlite/SqliteDriver.js +14 -0
- package/dialects/sqlite/{BaseSqlitePlatform.d.ts → SqlitePlatform.d.ts} +5 -2
- package/dialects/sqlite/{BaseSqlitePlatform.js → SqlitePlatform.js} +30 -4
- package/dialects/sqlite/SqliteSchemaHelper.d.ts +5 -0
- package/dialects/sqlite/SqliteSchemaHelper.js +31 -10
- package/dialects/sqlite/index.d.ts +3 -1
- package/dialects/sqlite/index.js +3 -1
- package/package.json +30 -30
- package/plugin/transformer.js +17 -16
- package/query/CriteriaNode.js +28 -10
- package/query/CriteriaNodeFactory.js +5 -1
- package/query/NativeQueryBuilder.d.ts +25 -0
- package/query/NativeQueryBuilder.js +61 -1
- package/query/ObjectCriteriaNode.js +71 -27
- package/query/QueryBuilder.d.ts +177 -48
- package/query/QueryBuilder.js +233 -54
- package/query/QueryBuilderHelper.d.ts +4 -3
- package/query/QueryBuilderHelper.js +47 -17
- package/query/ScalarCriteriaNode.js +14 -7
- package/query/raw.js +1 -1
- package/schema/DatabaseSchema.js +21 -15
- package/schema/DatabaseTable.js +114 -54
- package/schema/SchemaComparator.js +56 -32
- package/schema/SchemaHelper.js +28 -8
- package/schema/SqlSchemaGenerator.d.ts +2 -1
- package/schema/SqlSchemaGenerator.js +15 -8
- package/tsconfig.build.tsbuildinfo +1 -1
- package/typings.d.ts +15 -4
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { AbstractSqlDriver } from '../../AbstractSqlDriver.js';
|
|
2
|
+
import { BaseSqliteConnection } from './BaseSqliteConnection.js';
|
|
3
|
+
import { SqlitePlatform } from './SqlitePlatform.js';
|
|
4
|
+
/**
|
|
5
|
+
* Generic SQLite driver that uses `driverOptions` for the Kysely dialect.
|
|
6
|
+
* Use this with any SQLite library by passing a Kysely dialect via `driverOptions`.
|
|
7
|
+
*
|
|
8
|
+
* For the default better-sqlite3 experience, use `@mikro-orm/sqlite` instead.
|
|
9
|
+
*/
|
|
10
|
+
export class SqliteDriver extends AbstractSqlDriver {
|
|
11
|
+
constructor(config) {
|
|
12
|
+
super(config, new SqlitePlatform(), BaseSqliteConnection, ['kysely']);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -3,7 +3,7 @@ import { AbstractSqlPlatform } from '../../AbstractSqlPlatform.js';
|
|
|
3
3
|
import { SqliteNativeQueryBuilder } from './SqliteNativeQueryBuilder.js';
|
|
4
4
|
import { SqliteSchemaHelper } from './SqliteSchemaHelper.js';
|
|
5
5
|
import { SqliteExceptionConverter } from './SqliteExceptionConverter.js';
|
|
6
|
-
export declare
|
|
6
|
+
export declare class SqlitePlatform extends AbstractSqlPlatform {
|
|
7
7
|
protected readonly schemaHelper: SqliteSchemaHelper;
|
|
8
8
|
protected readonly exceptionConverter: SqliteExceptionConverter;
|
|
9
9
|
/** @internal */
|
|
@@ -71,6 +71,9 @@ export declare abstract class BaseSqlitePlatform extends AbstractSqlPlatform {
|
|
|
71
71
|
supportsSchemas(): boolean;
|
|
72
72
|
getDefaultSchemaName(): string | undefined;
|
|
73
73
|
getFullTextWhereClause(): string;
|
|
74
|
-
|
|
74
|
+
escape(value: any): string;
|
|
75
|
+
convertVersionValue(value: Date | number, prop: EntityProperty): number | {
|
|
76
|
+
$in: (string | number)[];
|
|
77
|
+
};
|
|
75
78
|
quoteValue(value: any): string;
|
|
76
79
|
}
|
|
@@ -2,7 +2,7 @@ import { AbstractSqlPlatform } from '../../AbstractSqlPlatform.js';
|
|
|
2
2
|
import { SqliteNativeQueryBuilder } from './SqliteNativeQueryBuilder.js';
|
|
3
3
|
import { SqliteSchemaHelper } from './SqliteSchemaHelper.js';
|
|
4
4
|
import { SqliteExceptionConverter } from './SqliteExceptionConverter.js';
|
|
5
|
-
export class
|
|
5
|
+
export class SqlitePlatform extends AbstractSqlPlatform {
|
|
6
6
|
schemaHelper = new SqliteSchemaHelper(this);
|
|
7
7
|
exceptionConverter = new SqliteExceptionConverter();
|
|
8
8
|
/** @internal */
|
|
@@ -19,7 +19,7 @@ export class BaseSqlitePlatform extends AbstractSqlPlatform {
|
|
|
19
19
|
return true;
|
|
20
20
|
}
|
|
21
21
|
getCurrentTimestampSQL(length) {
|
|
22
|
-
return
|
|
22
|
+
return `(strftime('%s', 'now') * 1000)`;
|
|
23
23
|
}
|
|
24
24
|
getDateTimeTypeDeclarationSQL(column) {
|
|
25
25
|
return 'datetime';
|
|
@@ -101,9 +101,35 @@ export class BaseSqlitePlatform extends AbstractSqlPlatform {
|
|
|
101
101
|
getFullTextWhereClause() {
|
|
102
102
|
return `:column: match :query`;
|
|
103
103
|
}
|
|
104
|
-
|
|
104
|
+
escape(value) {
|
|
105
|
+
if (value == null) {
|
|
106
|
+
return 'null';
|
|
107
|
+
}
|
|
108
|
+
if (typeof value === 'boolean') {
|
|
109
|
+
return value ? 'true' : 'false';
|
|
110
|
+
}
|
|
111
|
+
if (typeof value === 'number' || typeof value === 'bigint') {
|
|
112
|
+
return '' + value;
|
|
113
|
+
}
|
|
114
|
+
if (value instanceof Date) {
|
|
115
|
+
return '' + +value;
|
|
116
|
+
}
|
|
117
|
+
if (Array.isArray(value)) {
|
|
118
|
+
return value.map(v => this.escape(v)).join(', ');
|
|
119
|
+
}
|
|
120
|
+
if (Buffer.isBuffer(value)) {
|
|
121
|
+
return `X'${value.toString('hex')}'`;
|
|
122
|
+
}
|
|
123
|
+
return `'${String(value).replace(/'/g, "''")}'`;
|
|
124
|
+
}
|
|
125
|
+
convertVersionValue(value, prop) {
|
|
105
126
|
if (prop.runtimeType === 'Date') {
|
|
106
|
-
|
|
127
|
+
const ts = +value;
|
|
128
|
+
const str = new Date(ts)
|
|
129
|
+
.toISOString()
|
|
130
|
+
.replace('T', ' ')
|
|
131
|
+
.replace(/\.\d{3}Z$/, '');
|
|
132
|
+
return { $in: [ts, str] };
|
|
107
133
|
}
|
|
108
134
|
return value;
|
|
109
135
|
}
|
|
@@ -39,6 +39,11 @@ export declare class SqliteSchemaHelper extends SchemaHelper {
|
|
|
39
39
|
*/
|
|
40
40
|
private extractViewDefinition;
|
|
41
41
|
private getColumns;
|
|
42
|
+
/**
|
|
43
|
+
* SQLite strips outer parentheses from expression defaults (`DEFAULT (expr)` → `expr` in pragma).
|
|
44
|
+
* We need to add them back so they match what we generate in DDL.
|
|
45
|
+
*/
|
|
46
|
+
private wrapExpressionDefault;
|
|
42
47
|
private getEnumDefinitions;
|
|
43
48
|
getPrimaryKeys(connection: AbstractSqlConnection, indexes: IndexDef[], tableName: string, schemaName?: string): Promise<string[]>;
|
|
44
49
|
private getIndexes;
|
|
@@ -31,8 +31,8 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
31
31
|
return '';
|
|
32
32
|
}
|
|
33
33
|
getListTablesSQL() {
|
|
34
|
-
return `select name as table_name from sqlite_master where type = 'table' and name != 'sqlite_sequence' and name != 'geometry_columns' and name != 'spatial_ref_sys' `
|
|
35
|
-
|
|
34
|
+
return (`select name as table_name from sqlite_master where type = 'table' and name != 'sqlite_sequence' and name != 'geometry_columns' and name != 'spatial_ref_sys' ` +
|
|
35
|
+
`union all select name as table_name from sqlite_temp_master where type = 'table' order by name`);
|
|
36
36
|
}
|
|
37
37
|
async getAllTables(connection, schemas) {
|
|
38
38
|
const databases = await this.getDatabaseList(connection);
|
|
@@ -183,9 +183,11 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
183
183
|
getDropColumnsSQL(tableName, columns, schemaName) {
|
|
184
184
|
/* v8 ignore next */
|
|
185
185
|
const name = this.quote((schemaName && schemaName !== this.platform.getDefaultSchemaName() ? schemaName + '.' : '') + tableName);
|
|
186
|
-
return columns
|
|
186
|
+
return columns
|
|
187
|
+
.map(column => {
|
|
187
188
|
return `alter table ${name} drop column ${this.quote(column.name)}`;
|
|
188
|
-
})
|
|
189
|
+
})
|
|
190
|
+
.join(';\n');
|
|
189
191
|
}
|
|
190
192
|
getCreateIndexSQL(tableName, index) {
|
|
191
193
|
/* v8 ignore next */
|
|
@@ -286,7 +288,7 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
286
288
|
return {
|
|
287
289
|
name: col.name,
|
|
288
290
|
type: col.type,
|
|
289
|
-
default: col.dflt_value,
|
|
291
|
+
default: this.wrapExpressionDefault(col.dflt_value),
|
|
290
292
|
nullable: !col.notnull,
|
|
291
293
|
primary: !!col.pk,
|
|
292
294
|
mappedType,
|
|
@@ -296,6 +298,25 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
296
298
|
};
|
|
297
299
|
});
|
|
298
300
|
}
|
|
301
|
+
/**
|
|
302
|
+
* SQLite strips outer parentheses from expression defaults (`DEFAULT (expr)` → `expr` in pragma).
|
|
303
|
+
* We need to add them back so they match what we generate in DDL.
|
|
304
|
+
*/
|
|
305
|
+
wrapExpressionDefault(value) {
|
|
306
|
+
if (value == null) {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
// simple values that are returned as-is from pragma (no wrapping needed)
|
|
310
|
+
if (/^-?\d/.test(value) || /^[xX]'/.test(value) || value[0] === "'" || value[0] === '"' || value[0] === '(') {
|
|
311
|
+
return value;
|
|
312
|
+
}
|
|
313
|
+
const lower = value.toLowerCase();
|
|
314
|
+
if (['null', 'true', 'false', 'current_timestamp', 'current_date', 'current_time'].includes(lower)) {
|
|
315
|
+
return value;
|
|
316
|
+
}
|
|
317
|
+
// everything else is an expression that had its outer parens stripped
|
|
318
|
+
return `(${value})`;
|
|
319
|
+
}
|
|
299
320
|
async getEnumDefinitions(connection, tableName, schemaName) {
|
|
300
321
|
const prefix = this.getSchemaPrefix(schemaName);
|
|
301
322
|
const sql = `select sql from ${prefix}sqlite_master where type = ? and name = ?`;
|
|
@@ -434,10 +455,10 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
434
455
|
alterTable(diff, safe) {
|
|
435
456
|
const ret = [];
|
|
436
457
|
const [schemaName, tableName] = this.splitTableName(diff.name);
|
|
437
|
-
if (Utils.hasObjectKeys(diff.removedChecks)
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
458
|
+
if (Utils.hasObjectKeys(diff.removedChecks) ||
|
|
459
|
+
Utils.hasObjectKeys(diff.changedChecks) ||
|
|
460
|
+
Utils.hasObjectKeys(diff.changedForeignKeys) ||
|
|
461
|
+
Utils.hasObjectKeys(diff.changedColumns)) {
|
|
441
462
|
return this.getAlterTempTableSQL(diff);
|
|
442
463
|
}
|
|
443
464
|
for (const index of Object.values(diff.removedIndexes)) {
|
|
@@ -477,7 +498,7 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
477
498
|
return ret;
|
|
478
499
|
}
|
|
479
500
|
getAlterTempTableSQL(changedTable) {
|
|
480
|
-
const tempName = `${
|
|
501
|
+
const tempName = `${changedTable.toTable.name}__temp_alter`;
|
|
481
502
|
const quotedName = this.quote(changedTable.toTable.name);
|
|
482
503
|
const quotedTempName = this.quote(tempName);
|
|
483
504
|
const [first, ...rest] = this.createTable(changedTable.toTable);
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export * from './BaseSqliteConnection.js';
|
|
2
|
-
export * from './
|
|
2
|
+
export * from './NodeSqliteDialect.js';
|
|
3
|
+
export * from './SqliteDriver.js';
|
|
4
|
+
export * from './SqlitePlatform.js';
|
|
3
5
|
export * from './SqliteSchemaHelper.js';
|
|
4
6
|
export * from './SqliteNativeQueryBuilder.js';
|
package/dialects/sqlite/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export * from './BaseSqliteConnection.js';
|
|
2
|
-
export * from './
|
|
2
|
+
export * from './NodeSqliteDialect.js';
|
|
3
|
+
export * from './SqliteDriver.js';
|
|
4
|
+
export * from './SqlitePlatform.js';
|
|
3
5
|
export * from './SqliteSchemaHelper.js';
|
|
4
6
|
export * from './SqliteNativeQueryBuilder.js';
|
package/package.json
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/sql",
|
|
3
|
-
"version": "7.0.0-rc.
|
|
3
|
+
"version": "7.0.0-rc.3",
|
|
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
|
-
"type": "module",
|
|
6
|
-
"exports": {
|
|
7
|
-
"./package.json": "./package.json",
|
|
8
|
-
".": "./index.js"
|
|
9
|
-
},
|
|
10
|
-
"repository": {
|
|
11
|
-
"type": "git",
|
|
12
|
-
"url": "git+ssh://git@github.com/mikro-orm/mikro-orm.git"
|
|
13
|
-
},
|
|
14
5
|
"keywords": [
|
|
15
|
-
"
|
|
6
|
+
"data-mapper",
|
|
7
|
+
"ddd",
|
|
8
|
+
"entity",
|
|
9
|
+
"identity-map",
|
|
10
|
+
"javascript",
|
|
11
|
+
"js",
|
|
12
|
+
"mariadb",
|
|
13
|
+
"mikro-orm",
|
|
16
14
|
"mongo",
|
|
17
15
|
"mongodb",
|
|
18
16
|
"mysql",
|
|
19
|
-
"
|
|
17
|
+
"orm",
|
|
20
18
|
"postgresql",
|
|
21
19
|
"sqlite",
|
|
22
20
|
"sqlite3",
|
|
23
21
|
"ts",
|
|
24
22
|
"typescript",
|
|
25
|
-
"
|
|
26
|
-
"javascript",
|
|
27
|
-
"entity",
|
|
28
|
-
"ddd",
|
|
29
|
-
"mikro-orm",
|
|
30
|
-
"unit-of-work",
|
|
31
|
-
"data-mapper",
|
|
32
|
-
"identity-map"
|
|
23
|
+
"unit-of-work"
|
|
33
24
|
],
|
|
34
|
-
"
|
|
35
|
-
"license": "MIT",
|
|
25
|
+
"homepage": "https://mikro-orm.io",
|
|
36
26
|
"bugs": {
|
|
37
27
|
"url": "https://github.com/mikro-orm/mikro-orm/issues"
|
|
38
28
|
},
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"author": "Martin Adámek",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+ssh://git@github.com/mikro-orm/mikro-orm.git"
|
|
34
|
+
},
|
|
35
|
+
"type": "module",
|
|
36
|
+
"exports": {
|
|
37
|
+
"./package.json": "./package.json",
|
|
38
|
+
".": "./index.js"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "yarn compile && yarn copy",
|
|
@@ -46,16 +46,16 @@
|
|
|
46
46
|
"compile": "yarn run -T tsc -p tsconfig.build.json",
|
|
47
47
|
"copy": "node ../../scripts/copy.mjs"
|
|
48
48
|
},
|
|
49
|
-
"publishConfig": {
|
|
50
|
-
"access": "public"
|
|
51
|
-
},
|
|
52
49
|
"dependencies": {
|
|
53
50
|
"kysely": "0.28.11"
|
|
54
51
|
},
|
|
55
52
|
"devDependencies": {
|
|
56
|
-
"@mikro-orm/core": "^6.6.
|
|
53
|
+
"@mikro-orm/core": "^6.6.8"
|
|
57
54
|
},
|
|
58
55
|
"peerDependencies": {
|
|
59
|
-
"@mikro-orm/core": "7.0.0-rc.
|
|
56
|
+
"@mikro-orm/core": "7.0.0-rc.3"
|
|
57
|
+
},
|
|
58
|
+
"engines": {
|
|
59
|
+
"node": ">= 22.17.0"
|
|
60
60
|
}
|
|
61
61
|
}
|
package/plugin/transformer.js
CHANGED
|
@@ -79,12 +79,8 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
-
const nodeWithHooks = this.options.processOnCreateHooks && entityMeta
|
|
83
|
-
|
|
84
|
-
: node;
|
|
85
|
-
const nodeWithConvertedValues = this.options.convertValues && entityMeta
|
|
86
|
-
? this.processInsertValues(nodeWithHooks, entityMeta)
|
|
87
|
-
: nodeWithHooks;
|
|
82
|
+
const nodeWithHooks = this.options.processOnCreateHooks && entityMeta ? this.processOnCreateHooks(node, entityMeta) : node;
|
|
83
|
+
const nodeWithConvertedValues = this.options.convertValues && entityMeta ? this.processInsertValues(nodeWithHooks, entityMeta) : nodeWithHooks;
|
|
88
84
|
// Handle ON CONFLICT clause
|
|
89
85
|
let finalNode = nodeWithConvertedValues;
|
|
90
86
|
if (node.onConflict?.updates && entityMeta) {
|
|
@@ -150,12 +146,8 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
150
146
|
this.processJoinNode(join, currentContext);
|
|
151
147
|
}
|
|
152
148
|
}
|
|
153
|
-
const nodeWithHooks = this.options.processOnUpdateHooks && entityMeta
|
|
154
|
-
|
|
155
|
-
: node;
|
|
156
|
-
const nodeWithConvertedValues = this.options.convertValues && entityMeta
|
|
157
|
-
? this.processUpdateValues(nodeWithHooks, entityMeta)
|
|
158
|
-
: nodeWithHooks;
|
|
149
|
+
const nodeWithHooks = this.options.processOnUpdateHooks && entityMeta ? this.processOnUpdateHooks(node, entityMeta) : node;
|
|
150
|
+
const nodeWithConvertedValues = this.options.convertValues && entityMeta ? this.processUpdateValues(nodeWithHooks, entityMeta) : nodeWithHooks;
|
|
159
151
|
return super.transformUpdateQuery(nodeWithConvertedValues, queryId);
|
|
160
152
|
}
|
|
161
153
|
finally {
|
|
@@ -217,7 +209,9 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
217
209
|
}
|
|
218
210
|
// Transform column names when columnNamingStrategy is 'property'
|
|
219
211
|
// Support ColumnNode, ColumnUpdateNode, and ReferenceNode (for JOIN conditions)
|
|
220
|
-
if (this.options.columnNamingStrategy === 'property' &&
|
|
212
|
+
if (this.options.columnNamingStrategy === 'property' &&
|
|
213
|
+
parent &&
|
|
214
|
+
(ColumnNode.is(parent) || ColumnUpdateNode.is(parent) || ReferenceNode.is(parent))) {
|
|
221
215
|
const ownerMeta = this.findOwnerEntityInContext();
|
|
222
216
|
if (ownerMeta) {
|
|
223
217
|
const prop = ownerMeta.properties[node.name];
|
|
@@ -480,7 +474,11 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
480
474
|
}
|
|
481
475
|
}
|
|
482
476
|
if (prop.customType && !isRaw(value)) {
|
|
483
|
-
return prop.customType.convertToDatabaseValue(value, this.platform, {
|
|
477
|
+
return prop.customType.convertToDatabaseValue(value, this.platform, {
|
|
478
|
+
fromQuery: true,
|
|
479
|
+
key: prop.name,
|
|
480
|
+
mode: 'query-data',
|
|
481
|
+
});
|
|
484
482
|
}
|
|
485
483
|
if (value instanceof Date) {
|
|
486
484
|
return this.platform.processDateProperty(value);
|
|
@@ -723,7 +721,9 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
723
721
|
*/
|
|
724
722
|
transformResult(rows, entityMap) {
|
|
725
723
|
// Only transform if columnNamingStrategy is 'property' or convertValues is true, and we have data
|
|
726
|
-
if ((this.options.columnNamingStrategy !== 'property' && !this.options.convertValues) ||
|
|
724
|
+
if ((this.options.columnNamingStrategy !== 'property' && !this.options.convertValues) ||
|
|
725
|
+
!rows ||
|
|
726
|
+
rows.length === 0) {
|
|
727
727
|
return rows;
|
|
728
728
|
}
|
|
729
729
|
// If no entities found (e.g. raw query without known tables), return rows as is
|
|
@@ -870,7 +870,8 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
870
870
|
}
|
|
871
871
|
// For non-local timezone, check if value already has timezone info
|
|
872
872
|
// Number (timestamp) doesn't need timezone handling, string needs check
|
|
873
|
-
if (typeof value === 'number' ||
|
|
873
|
+
if (typeof value === 'number' ||
|
|
874
|
+
(typeof value === 'string' && (value.includes('+') || value.lastIndexOf('-') > 10 || value.endsWith('Z')))) {
|
|
874
875
|
return this.platform.parseDate(value);
|
|
875
876
|
}
|
|
876
877
|
// Append timezone if not present (only for string values)
|
package/query/CriteriaNode.js
CHANGED
|
@@ -53,22 +53,34 @@ export class CriteriaNode {
|
|
|
53
53
|
const type = this.prop ? this.prop.kind : null;
|
|
54
54
|
const composite = this.prop?.joinColumns ? this.prop.joinColumns.length > 1 : false;
|
|
55
55
|
const rawField = RawQueryFragment.isKnownFragmentSymbol(this.key);
|
|
56
|
-
const scalar = payload === null ||
|
|
56
|
+
const scalar = payload === null ||
|
|
57
|
+
Utils.isPrimaryKey(payload) ||
|
|
58
|
+
payload instanceof RegExp ||
|
|
59
|
+
payload instanceof Date ||
|
|
60
|
+
rawField;
|
|
57
61
|
const operator = Utils.isPlainObject(payload) && Utils.getObjectQueryKeys(payload).every(k => Utils.isOperator(k, false));
|
|
58
62
|
if (composite) {
|
|
59
63
|
return true;
|
|
60
64
|
}
|
|
61
65
|
switch (type) {
|
|
62
|
-
case ReferenceKind.MANY_TO_ONE:
|
|
63
|
-
|
|
64
|
-
case ReferenceKind.
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
case ReferenceKind.MANY_TO_ONE:
|
|
67
|
+
return false;
|
|
68
|
+
case ReferenceKind.ONE_TO_ONE:
|
|
69
|
+
return !this.prop.owner;
|
|
70
|
+
case ReferenceKind.ONE_TO_MANY:
|
|
71
|
+
return scalar || operator;
|
|
72
|
+
case ReferenceKind.MANY_TO_MANY:
|
|
73
|
+
return scalar || operator;
|
|
74
|
+
default:
|
|
75
|
+
return false;
|
|
67
76
|
}
|
|
68
77
|
}
|
|
69
78
|
renameFieldToPK(qb, ownerAlias) {
|
|
70
79
|
const joinAlias = qb.getAliasForJoinPath(this.getPath(), { matchPopulateJoins: true });
|
|
71
|
-
if (!joinAlias &&
|
|
80
|
+
if (!joinAlias &&
|
|
81
|
+
this.parent &&
|
|
82
|
+
[ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind) &&
|
|
83
|
+
this.prop.owner) {
|
|
72
84
|
const alias = qb.getAliasForJoinPath(this.parent.getPath()) ?? ownerAlias ?? qb.alias;
|
|
73
85
|
return Utils.getPrimaryKeyHash(this.prop.joinColumns.map(col => `${alias}.${col}`));
|
|
74
86
|
}
|
|
@@ -84,7 +96,9 @@ export class CriteriaNode {
|
|
|
84
96
|
const parentPath = opts?.parentPath ?? this.parent?.getPath({ addIndex: addParentIndex }) ?? Utils.className(this.entityName);
|
|
85
97
|
const index = opts?.addIndex && this.index != null ? `[${this.index}]` : '';
|
|
86
98
|
// ignore group operators to allow easier mapping (e.g. for orderBy)
|
|
87
|
-
const key = this.key && !RawQueryFragment.isKnownFragmentSymbol(this.key) && !['$and', '$or', '$not'].includes(this.key)
|
|
99
|
+
const key = this.key && !RawQueryFragment.isKnownFragmentSymbol(this.key) && !['$and', '$or', '$not'].includes(this.key)
|
|
100
|
+
? '.' + this.key
|
|
101
|
+
: '';
|
|
88
102
|
const ret = parentPath + index + key;
|
|
89
103
|
if (this.isPivotJoin()) {
|
|
90
104
|
// distinguish pivot table join from target entity join
|
|
@@ -97,7 +111,11 @@ export class CriteriaNode {
|
|
|
97
111
|
return false;
|
|
98
112
|
}
|
|
99
113
|
const rawField = RawQueryFragment.isKnownFragmentSymbol(this.key);
|
|
100
|
-
const scalar = this.payload === null ||
|
|
114
|
+
const scalar = this.payload === null ||
|
|
115
|
+
Utils.isPrimaryKey(this.payload) ||
|
|
116
|
+
this.payload instanceof RegExp ||
|
|
117
|
+
this.payload instanceof Date ||
|
|
118
|
+
rawField;
|
|
101
119
|
const operator = Utils.isObject(this.payload) && Utils.getObjectQueryKeys(this.payload).every(k => Utils.isOperator(k, false));
|
|
102
120
|
return this.prop.kind === ReferenceKind.MANY_TO_MANY && (scalar || operator);
|
|
103
121
|
}
|
|
@@ -116,7 +134,7 @@ export class CriteriaNode {
|
|
|
116
134
|
const o = {};
|
|
117
135
|
['entityName', 'key', 'index', 'payload']
|
|
118
136
|
.filter(k => this[k] !== undefined)
|
|
119
|
-
.forEach(k => o[k] = this[k]);
|
|
137
|
+
.forEach(k => (o[k] = this[k]));
|
|
120
138
|
return `${this.constructor.name} ${inspect(o)}`;
|
|
121
139
|
}
|
|
122
140
|
}
|
|
@@ -8,7 +8,11 @@ import { ScalarCriteriaNode } from './ScalarCriteriaNode.js';
|
|
|
8
8
|
export class CriteriaNodeFactory {
|
|
9
9
|
static createNode(metadata, entityName, payload, parent, key, validate = true) {
|
|
10
10
|
const rawField = RawQueryFragment.isKnownFragmentSymbol(key);
|
|
11
|
-
const scalar = Utils.isPrimaryKey(payload) ||
|
|
11
|
+
const scalar = Utils.isPrimaryKey(payload) ||
|
|
12
|
+
isRaw(payload) ||
|
|
13
|
+
payload instanceof RegExp ||
|
|
14
|
+
payload instanceof Date ||
|
|
15
|
+
rawField;
|
|
12
16
|
if (Array.isArray(payload) && !scalar) {
|
|
13
17
|
return this.createArrayNode(metadata, entityName, payload, parent, key, validate);
|
|
14
18
|
}
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { type Dictionary, LockMode, type QueryFlag, RawQueryFragment, type Subquery } from '@mikro-orm/core';
|
|
2
2
|
import { QueryType } from './enums.js';
|
|
3
3
|
import type { AbstractSqlPlatform } from '../AbstractSqlPlatform.js';
|
|
4
|
+
export interface CteOptions {
|
|
5
|
+
columns?: string[];
|
|
6
|
+
/** PostgreSQL: MATERIALIZED / NOT MATERIALIZED */
|
|
7
|
+
materialized?: boolean;
|
|
8
|
+
}
|
|
9
|
+
interface CteClause extends CteOptions {
|
|
10
|
+
name: string;
|
|
11
|
+
sql: string;
|
|
12
|
+
params: unknown[];
|
|
13
|
+
recursive?: boolean;
|
|
14
|
+
}
|
|
4
15
|
interface Options {
|
|
5
16
|
tableName?: string | RawQueryFragment;
|
|
6
17
|
indexHint?: string;
|
|
@@ -32,6 +43,7 @@ interface Options {
|
|
|
32
43
|
hintComment?: string[];
|
|
33
44
|
flags?: Set<QueryFlag>;
|
|
34
45
|
wrap?: [prefix: string, suffix: string];
|
|
46
|
+
ctes?: CteClause[];
|
|
35
47
|
}
|
|
36
48
|
interface TableOptions {
|
|
37
49
|
schema?: string;
|
|
@@ -65,6 +77,17 @@ export declare class NativeQueryBuilder implements Subquery {
|
|
|
65
77
|
groupBy(groupBy: (string | RawQueryFragment)[]): this;
|
|
66
78
|
join(sql: string, params: unknown[]): this;
|
|
67
79
|
orderBy(orderBy: string): this;
|
|
80
|
+
/**
|
|
81
|
+
* The sub-query is compiled eagerly at call time — later mutations to the
|
|
82
|
+
* sub-query builder will not be reflected in this CTE.
|
|
83
|
+
*/
|
|
84
|
+
with(name: string, query: NativeQueryBuilder | RawQueryFragment, options?: CteOptions): this;
|
|
85
|
+
/**
|
|
86
|
+
* Adds a recursive CTE (`WITH RECURSIVE` on PostgreSQL/MySQL/SQLite, plain `WITH` on MSSQL).
|
|
87
|
+
* The sub-query is compiled eagerly — later mutations will not be reflected.
|
|
88
|
+
*/
|
|
89
|
+
withRecursive(name: string, query: NativeQueryBuilder | RawQueryFragment, options?: CteOptions): this;
|
|
90
|
+
private addCte;
|
|
68
91
|
toString(): string;
|
|
69
92
|
compile(): {
|
|
70
93
|
sql: string;
|
|
@@ -103,6 +126,8 @@ export declare class NativeQueryBuilder implements Subquery {
|
|
|
103
126
|
protected compileDelete(): void;
|
|
104
127
|
protected compileTruncate(): void;
|
|
105
128
|
protected addHintComment(): void;
|
|
129
|
+
protected compileCtes(): void;
|
|
130
|
+
protected getCteKeyword(hasRecursive: boolean): string;
|
|
106
131
|
protected getTableName(): string;
|
|
107
132
|
protected quote(id: string | RawQueryFragment | NativeQueryBuilder): string;
|
|
108
133
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LockMode, raw, RawQueryFragment, Utils } from '@mikro-orm/core';
|
|
1
|
+
import { LockMode, raw, RawQueryFragment, Utils, } from '@mikro-orm/core';
|
|
2
2
|
import { QueryType } from './enums.js';
|
|
3
3
|
/** @internal */
|
|
4
4
|
export class NativeQueryBuilder {
|
|
@@ -59,6 +59,36 @@ export class NativeQueryBuilder {
|
|
|
59
59
|
this.options.orderBy = orderBy;
|
|
60
60
|
return this;
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* The sub-query is compiled eagerly at call time — later mutations to the
|
|
64
|
+
* sub-query builder will not be reflected in this CTE.
|
|
65
|
+
*/
|
|
66
|
+
with(name, query, options) {
|
|
67
|
+
return this.addCte(name, query, options);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Adds a recursive CTE (`WITH RECURSIVE` on PostgreSQL/MySQL/SQLite, plain `WITH` on MSSQL).
|
|
71
|
+
* The sub-query is compiled eagerly — later mutations will not be reflected.
|
|
72
|
+
*/
|
|
73
|
+
withRecursive(name, query, options) {
|
|
74
|
+
return this.addCte(name, query, options, true);
|
|
75
|
+
}
|
|
76
|
+
addCte(name, query, options, recursive) {
|
|
77
|
+
this.options.ctes ??= [];
|
|
78
|
+
if (this.options.ctes.some(cte => cte.name === name)) {
|
|
79
|
+
throw new Error(`CTE with name '${name}' already exists`);
|
|
80
|
+
}
|
|
81
|
+
const { sql, params } = query instanceof NativeQueryBuilder ? query.compile() : { sql: query.sql, params: [...query.params] };
|
|
82
|
+
this.options.ctes.push({
|
|
83
|
+
name,
|
|
84
|
+
sql,
|
|
85
|
+
params,
|
|
86
|
+
recursive,
|
|
87
|
+
columns: options?.columns,
|
|
88
|
+
materialized: options?.materialized,
|
|
89
|
+
});
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
62
92
|
toString() {
|
|
63
93
|
const { sql, params } = this.compile();
|
|
64
94
|
return this.platform.formatQuery(sql, params);
|
|
@@ -72,6 +102,7 @@ export class NativeQueryBuilder {
|
|
|
72
102
|
if (this.options.comment) {
|
|
73
103
|
this.parts.push(...this.options.comment.map(comment => `/* ${comment} */`));
|
|
74
104
|
}
|
|
105
|
+
this.compileCtes();
|
|
75
106
|
switch (this.type) {
|
|
76
107
|
case QueryType.SELECT:
|
|
77
108
|
case QueryType.COUNT:
|
|
@@ -387,6 +418,35 @@ export class NativeQueryBuilder {
|
|
|
387
418
|
this.parts.push(`/*+ ${this.options.hintComment.join(' ')} */`);
|
|
388
419
|
}
|
|
389
420
|
}
|
|
421
|
+
compileCtes() {
|
|
422
|
+
const ctes = this.options.ctes;
|
|
423
|
+
if (!ctes || ctes.length === 0) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
const hasRecursive = ctes.some(cte => cte.recursive);
|
|
427
|
+
const keyword = this.getCteKeyword(hasRecursive);
|
|
428
|
+
const cteParts = [];
|
|
429
|
+
for (const cte of ctes) {
|
|
430
|
+
let part = this.quote(cte.name);
|
|
431
|
+
if (cte.columns?.length) {
|
|
432
|
+
part += ` (${cte.columns.map(c => this.quote(c)).join(', ')})`;
|
|
433
|
+
}
|
|
434
|
+
part += ' as';
|
|
435
|
+
if (cte.materialized === true) {
|
|
436
|
+
part += ' materialized';
|
|
437
|
+
}
|
|
438
|
+
else if (cte.materialized === false) {
|
|
439
|
+
part += ' not materialized';
|
|
440
|
+
}
|
|
441
|
+
part += ` (${cte.sql})`;
|
|
442
|
+
this.params.push(...cte.params);
|
|
443
|
+
cteParts.push(part);
|
|
444
|
+
}
|
|
445
|
+
this.parts.push(`${keyword} ${cteParts.join(', ')}`);
|
|
446
|
+
}
|
|
447
|
+
getCteKeyword(hasRecursive) {
|
|
448
|
+
return hasRecursive ? 'with recursive' : 'with';
|
|
449
|
+
}
|
|
390
450
|
getTableName() {
|
|
391
451
|
if (!this.options.tableName) {
|
|
392
452
|
throw new Error('No table name provided');
|