@mikro-orm/sql 7.0.0-rc.2 → 7.0.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.
- package/AbstractSqlConnection.d.ts +5 -4
- package/AbstractSqlConnection.js +20 -6
- package/AbstractSqlDriver.d.ts +19 -13
- package/AbstractSqlDriver.js +225 -47
- package/AbstractSqlPlatform.d.ts +35 -0
- package/AbstractSqlPlatform.js +51 -5
- package/PivotCollectionPersister.d.ts +2 -11
- package/PivotCollectionPersister.js +59 -59
- package/README.md +5 -4
- package/SqlEntityManager.d.ts +2 -2
- package/SqlEntityManager.js +5 -5
- package/dialects/index.d.ts +1 -0
- package/dialects/index.js +1 -0
- package/dialects/mssql/MsSqlNativeQueryBuilder.d.ts +2 -0
- package/dialects/mssql/MsSqlNativeQueryBuilder.js +8 -4
- package/dialects/mysql/BaseMySqlPlatform.d.ts +6 -0
- package/dialects/mysql/BaseMySqlPlatform.js +18 -2
- package/dialects/mysql/MySqlSchemaHelper.d.ts +1 -1
- package/dialects/mysql/MySqlSchemaHelper.js +25 -14
- package/dialects/oracledb/OracleDialect.d.ts +78 -0
- package/dialects/oracledb/OracleDialect.js +166 -0
- package/dialects/oracledb/OracleNativeQueryBuilder.d.ts +19 -0
- package/dialects/oracledb/OracleNativeQueryBuilder.js +249 -0
- package/dialects/oracledb/index.d.ts +2 -0
- package/dialects/oracledb/index.js +2 -0
- package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +6 -0
- package/dialects/postgresql/BasePostgreSqlPlatform.js +49 -37
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +75 -59
- package/dialects/sqlite/BaseSqliteConnection.js +2 -2
- package/dialects/sqlite/NodeSqliteDialect.js +3 -1
- package/dialects/sqlite/SqlitePlatform.d.ts +1 -0
- package/dialects/sqlite/SqlitePlatform.js +7 -1
- package/dialects/sqlite/SqliteSchemaHelper.js +23 -17
- package/index.d.ts +1 -1
- package/index.js +0 -1
- package/package.json +30 -30
- package/plugin/index.d.ts +1 -14
- package/plugin/index.js +13 -13
- package/plugin/transformer.d.ts +6 -22
- package/plugin/transformer.js +91 -82
- package/query/ArrayCriteriaNode.d.ts +1 -1
- package/query/CriteriaNode.js +28 -10
- package/query/CriteriaNodeFactory.js +20 -4
- package/query/NativeQueryBuilder.d.ts +28 -3
- package/query/NativeQueryBuilder.js +65 -3
- package/query/ObjectCriteriaNode.js +75 -31
- package/query/QueryBuilder.d.ts +199 -100
- package/query/QueryBuilder.js +544 -358
- package/query/QueryBuilderHelper.d.ts +18 -14
- package/query/QueryBuilderHelper.js +364 -147
- package/query/ScalarCriteriaNode.js +17 -8
- package/query/enums.d.ts +2 -0
- package/query/enums.js +2 -0
- package/query/raw.js +1 -1
- package/schema/DatabaseSchema.d.ts +7 -5
- package/schema/DatabaseSchema.js +68 -45
- package/schema/DatabaseTable.d.ts +8 -6
- package/schema/DatabaseTable.js +191 -107
- package/schema/SchemaComparator.d.ts +1 -3
- package/schema/SchemaComparator.js +76 -50
- package/schema/SchemaHelper.d.ts +2 -13
- package/schema/SchemaHelper.js +30 -9
- package/schema/SqlSchemaGenerator.d.ts +4 -14
- package/schema/SqlSchemaGenerator.js +26 -12
- package/typings.d.ts +10 -5
- package/tsconfig.build.tsbuildinfo +0 -1
package/AbstractSqlPlatform.js
CHANGED
|
@@ -3,6 +3,7 @@ import { SqlEntityRepository } from './SqlEntityRepository.js';
|
|
|
3
3
|
import { SqlSchemaGenerator } from './schema/SqlSchemaGenerator.js';
|
|
4
4
|
import { NativeQueryBuilder } from './query/NativeQueryBuilder.js';
|
|
5
5
|
export class AbstractSqlPlatform extends Platform {
|
|
6
|
+
static #JSON_PROPERTY_NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
6
7
|
schemaHelper;
|
|
7
8
|
usesPivotTable() {
|
|
8
9
|
return true;
|
|
@@ -64,15 +65,21 @@ export class AbstractSqlPlatform extends Platform {
|
|
|
64
65
|
}
|
|
65
66
|
getSearchJsonPropertyKey(path, type, aliased, value) {
|
|
66
67
|
const [a, ...b] = path;
|
|
67
|
-
const quoteKey = (key) => key.match(/^[a-z]\w*$/i) ? key : `"${key}"`;
|
|
68
68
|
if (aliased) {
|
|
69
|
-
return raw(alias => `json_extract(${this.quoteIdentifier(`${alias}.${a}`)}, '$.${b.map(
|
|
69
|
+
return raw(alias => `json_extract(${this.quoteIdentifier(`${alias}.${a}`)}, '$.${b.map(this.quoteJsonKey).join('.')}')`);
|
|
70
70
|
}
|
|
71
|
-
return raw(`json_extract(${this.quoteIdentifier(a)}, '$.${b.map(
|
|
71
|
+
return raw(`json_extract(${this.quoteIdentifier(a)}, '$.${b.map(this.quoteJsonKey).join('.')}')`);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Quotes a key for use inside a JSON path expression (e.g. `$.key`).
|
|
75
|
+
* Simple alphanumeric keys are left unquoted; others are wrapped in double quotes.
|
|
76
|
+
* @internal
|
|
77
|
+
*/
|
|
78
|
+
quoteJsonKey(key) {
|
|
79
|
+
return /^[a-z]\w*$/i.exec(key) ? key : `"${key}"`;
|
|
72
80
|
}
|
|
73
81
|
getJsonIndexDefinition(index) {
|
|
74
|
-
return index.columnNames
|
|
75
|
-
.map(column => {
|
|
82
|
+
return index.columnNames.map(column => {
|
|
76
83
|
if (!column.includes('.')) {
|
|
77
84
|
return column;
|
|
78
85
|
}
|
|
@@ -80,6 +87,9 @@ export class AbstractSqlPlatform extends Platform {
|
|
|
80
87
|
return `(json_extract(${root}, '$.${path.join('.')}'))`;
|
|
81
88
|
});
|
|
82
89
|
}
|
|
90
|
+
supportsUnionWhere() {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
83
93
|
supportsSchemas() {
|
|
84
94
|
return false;
|
|
85
95
|
}
|
|
@@ -114,4 +124,40 @@ export class AbstractSqlPlatform extends Platform {
|
|
|
114
124
|
throw new Error(`Invalid collation name: '${collation}'. Collation names must contain only word characters.`);
|
|
115
125
|
}
|
|
116
126
|
}
|
|
127
|
+
/** @internal */
|
|
128
|
+
validateJsonPropertyName(name) {
|
|
129
|
+
if (!AbstractSqlPlatform.#JSON_PROPERTY_NAME_RE.test(name)) {
|
|
130
|
+
throw new Error(`Invalid JSON property name: '${name}'. JSON property names must contain only alphanumeric characters and underscores.`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Returns FROM clause for JSON array iteration.
|
|
135
|
+
* @internal
|
|
136
|
+
*/
|
|
137
|
+
getJsonArrayFromSQL(column, alias, _properties) {
|
|
138
|
+
return `json_each(${column}) as ${this.quoteIdentifier(alias)}`;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Returns SQL expression to access an element's property within a JSON array iteration.
|
|
142
|
+
* @internal
|
|
143
|
+
*/
|
|
144
|
+
getJsonArrayElementPropertySQL(alias, property, _type) {
|
|
145
|
+
return `${this.quoteIdentifier(alias)}.${this.quoteIdentifier(property)}`;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Wraps JSON array FROM clause and WHERE condition into a full EXISTS condition.
|
|
149
|
+
* MySQL overrides this because `json_table` doesn't support correlated subqueries.
|
|
150
|
+
* @internal
|
|
151
|
+
*/
|
|
152
|
+
getJsonArrayExistsSQL(from, where) {
|
|
153
|
+
return `exists (select 1 from ${from} where ${where})`;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Maps a runtime type name (e.g. 'string', 'number') to a driver-specific bind type constant.
|
|
157
|
+
* Used by NativeQueryBuilder for output bindings.
|
|
158
|
+
* @internal
|
|
159
|
+
*/
|
|
160
|
+
mapToBindType(type) {
|
|
161
|
+
return type;
|
|
162
|
+
}
|
|
117
163
|
}
|
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
import { type Dictionary, type EntityMetadata, type EntityProperty, type Primary, type Transaction } from '@mikro-orm/core';
|
|
2
2
|
import { type AbstractSqlDriver } from './AbstractSqlDriver.js';
|
|
3
3
|
export declare class PivotCollectionPersister<Entity extends object> {
|
|
4
|
-
private
|
|
5
|
-
|
|
6
|
-
private readonly ctx?;
|
|
7
|
-
private readonly schema?;
|
|
8
|
-
private readonly loggerContext?;
|
|
9
|
-
private readonly inserts;
|
|
10
|
-
private readonly upserts;
|
|
11
|
-
private readonly deletes;
|
|
12
|
-
private readonly batchSize;
|
|
13
|
-
private order;
|
|
14
|
-
constructor(meta: EntityMetadata<Entity>, driver: AbstractSqlDriver, ctx?: Transaction | undefined, schema?: string | undefined, loggerContext?: Dictionary | undefined);
|
|
4
|
+
#private;
|
|
5
|
+
constructor(meta: EntityMetadata<Entity>, driver: AbstractSqlDriver, ctx?: Transaction, schema?: string, loggerContext?: Dictionary);
|
|
15
6
|
enqueueUpdate(prop: EntityProperty<Entity>, insertDiff: Primary<Entity>[][], deleteDiff: Primary<Entity>[][] | boolean, pks: Primary<Entity>[], isInitialized?: boolean): void;
|
|
16
7
|
private enqueueInsert;
|
|
17
8
|
private enqueueUpsert;
|
|
@@ -1,55 +1,55 @@
|
|
|
1
1
|
class InsertStatement {
|
|
2
|
-
keys;
|
|
3
|
-
data;
|
|
4
2
|
order;
|
|
3
|
+
#keys;
|
|
4
|
+
#data;
|
|
5
5
|
constructor(keys, data, order) {
|
|
6
|
-
this.keys = keys;
|
|
7
|
-
this.data = data;
|
|
8
6
|
this.order = order;
|
|
7
|
+
this.#keys = keys;
|
|
8
|
+
this.#data = data;
|
|
9
9
|
}
|
|
10
10
|
getHash() {
|
|
11
|
-
return JSON.stringify(this
|
|
11
|
+
return JSON.stringify(this.#data);
|
|
12
12
|
}
|
|
13
13
|
getData() {
|
|
14
14
|
const data = {};
|
|
15
|
-
this
|
|
15
|
+
this.#keys.forEach((key, idx) => (data[key] = this.#data[idx]));
|
|
16
16
|
return data;
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
class DeleteStatement {
|
|
20
|
-
keys;
|
|
21
|
-
cond;
|
|
20
|
+
#keys;
|
|
21
|
+
#cond;
|
|
22
22
|
constructor(keys, cond) {
|
|
23
|
-
this
|
|
24
|
-
this
|
|
23
|
+
this.#keys = keys;
|
|
24
|
+
this.#cond = cond;
|
|
25
25
|
}
|
|
26
26
|
getHash() {
|
|
27
|
-
return JSON.stringify(this
|
|
27
|
+
return JSON.stringify(this.#cond);
|
|
28
28
|
}
|
|
29
29
|
getCondition() {
|
|
30
30
|
const cond = {};
|
|
31
|
-
this
|
|
31
|
+
this.#keys.forEach((key, idx) => (cond[key] = this.#cond[idx]));
|
|
32
32
|
return cond;
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
export class PivotCollectionPersister {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
36
|
+
#inserts = new Map();
|
|
37
|
+
#upserts = new Map();
|
|
38
|
+
#deletes = new Map();
|
|
39
|
+
#batchSize;
|
|
40
|
+
#order = 0;
|
|
41
|
+
#meta;
|
|
42
|
+
#driver;
|
|
43
|
+
#ctx;
|
|
44
|
+
#schema;
|
|
45
|
+
#loggerContext;
|
|
46
46
|
constructor(meta, driver, ctx, schema, loggerContext) {
|
|
47
|
-
this
|
|
48
|
-
this
|
|
49
|
-
this
|
|
50
|
-
this
|
|
51
|
-
this
|
|
52
|
-
this
|
|
47
|
+
this.#meta = meta;
|
|
48
|
+
this.#driver = driver;
|
|
49
|
+
this.#ctx = ctx;
|
|
50
|
+
this.#schema = schema;
|
|
51
|
+
this.#loggerContext = loggerContext;
|
|
52
|
+
this.#batchSize = this.#driver.config.get('batchSize');
|
|
53
53
|
}
|
|
54
54
|
enqueueUpdate(prop, insertDiff, deleteDiff, pks, isInitialized = true) {
|
|
55
55
|
if (insertDiff.length) {
|
|
@@ -68,8 +68,8 @@ export class PivotCollectionPersister {
|
|
|
68
68
|
for (const fks of insertDiff) {
|
|
69
69
|
const statement = this.createInsertStatement(prop, fks, pks);
|
|
70
70
|
const hash = statement.getHash();
|
|
71
|
-
if (prop.owner || !this
|
|
72
|
-
this
|
|
71
|
+
if (prop.owner || !this.#inserts.has(hash)) {
|
|
72
|
+
this.#inserts.set(hash, statement);
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
}
|
|
@@ -77,26 +77,26 @@ export class PivotCollectionPersister {
|
|
|
77
77
|
for (const fks of insertDiff) {
|
|
78
78
|
const statement = this.createInsertStatement(prop, fks, pks);
|
|
79
79
|
const hash = statement.getHash();
|
|
80
|
-
if (prop.owner || !this
|
|
81
|
-
this
|
|
80
|
+
if (prop.owner || !this.#upserts.has(hash)) {
|
|
81
|
+
this.#upserts.set(hash, statement);
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
createInsertStatement(prop, fks, pks) {
|
|
86
86
|
const { data, keys } = this.buildPivotKeysAndData(prop, fks, pks);
|
|
87
|
-
return new InsertStatement(keys, data, this
|
|
87
|
+
return new InsertStatement(keys, data, this.#order++);
|
|
88
88
|
}
|
|
89
89
|
enqueueDelete(prop, deleteDiff, pks) {
|
|
90
90
|
if (deleteDiff === true) {
|
|
91
91
|
const { data, keys } = this.buildPivotKeysAndData(prop, [], pks, true);
|
|
92
92
|
const statement = new DeleteStatement(keys, data);
|
|
93
|
-
this
|
|
93
|
+
this.#deletes.set(statement.getHash(), statement);
|
|
94
94
|
return;
|
|
95
95
|
}
|
|
96
96
|
for (const fks of deleteDiff) {
|
|
97
97
|
const { data, keys } = this.buildPivotKeysAndData(prop, fks, pks);
|
|
98
98
|
const statement = new DeleteStatement(keys, data);
|
|
99
|
-
this
|
|
99
|
+
this.#deletes.set(statement.getHash(), statement);
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
/**
|
|
@@ -130,46 +130,46 @@ export class PivotCollectionPersister {
|
|
|
130
130
|
return items.filter(Boolean);
|
|
131
131
|
}
|
|
132
132
|
async execute() {
|
|
133
|
-
if (this
|
|
134
|
-
const deletes = [...this
|
|
135
|
-
for (let i = 0; i < deletes.length; i += this
|
|
136
|
-
const chunk = deletes.slice(i, i + this
|
|
133
|
+
if (this.#deletes.size > 0) {
|
|
134
|
+
const deletes = [...this.#deletes.values()];
|
|
135
|
+
for (let i = 0; i < deletes.length; i += this.#batchSize) {
|
|
136
|
+
const chunk = deletes.slice(i, i + this.#batchSize);
|
|
137
137
|
const cond = { $or: [] };
|
|
138
138
|
for (const item of chunk) {
|
|
139
139
|
cond.$or.push(item.getCondition());
|
|
140
140
|
}
|
|
141
|
-
await this
|
|
142
|
-
ctx: this
|
|
143
|
-
schema: this
|
|
144
|
-
loggerContext: this
|
|
141
|
+
await this.#driver.nativeDelete(this.#meta.class, cond, {
|
|
142
|
+
ctx: this.#ctx,
|
|
143
|
+
schema: this.#schema,
|
|
144
|
+
loggerContext: this.#loggerContext,
|
|
145
145
|
});
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
|
-
if (this
|
|
149
|
-
const filtered = this.collectStatements(this
|
|
150
|
-
for (let i = 0; i < filtered.length; i += this
|
|
151
|
-
const chunk = filtered.slice(i, i + this
|
|
152
|
-
await this
|
|
153
|
-
ctx: this
|
|
154
|
-
schema: this
|
|
148
|
+
if (this.#inserts.size > 0) {
|
|
149
|
+
const filtered = this.collectStatements(this.#inserts);
|
|
150
|
+
for (let i = 0; i < filtered.length; i += this.#batchSize) {
|
|
151
|
+
const chunk = filtered.slice(i, i + this.#batchSize);
|
|
152
|
+
await this.#driver.nativeInsertMany(this.#meta.class, chunk, {
|
|
153
|
+
ctx: this.#ctx,
|
|
154
|
+
schema: this.#schema,
|
|
155
155
|
convertCustomTypes: false,
|
|
156
156
|
processCollections: false,
|
|
157
|
-
loggerContext: this
|
|
157
|
+
loggerContext: this.#loggerContext,
|
|
158
158
|
});
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
|
-
if (this
|
|
162
|
-
const filtered = this.collectStatements(this
|
|
163
|
-
for (let i = 0; i < filtered.length; i += this
|
|
164
|
-
const chunk = filtered.slice(i, i + this
|
|
165
|
-
await this
|
|
166
|
-
ctx: this
|
|
167
|
-
schema: this
|
|
161
|
+
if (this.#upserts.size > 0) {
|
|
162
|
+
const filtered = this.collectStatements(this.#upserts);
|
|
163
|
+
for (let i = 0; i < filtered.length; i += this.#batchSize) {
|
|
164
|
+
const chunk = filtered.slice(i, i + this.#batchSize);
|
|
165
|
+
await this.#driver.nativeUpdateMany(this.#meta.class, [], chunk, {
|
|
166
|
+
ctx: this.#ctx,
|
|
167
|
+
schema: this.#schema,
|
|
168
168
|
convertCustomTypes: false,
|
|
169
169
|
processCollections: false,
|
|
170
170
|
upsert: true,
|
|
171
171
|
onConflictAction: 'ignore',
|
|
172
|
-
loggerContext: this
|
|
172
|
+
loggerContext: this.#loggerContext,
|
|
173
173
|
});
|
|
174
174
|
}
|
|
175
175
|
}
|
package/README.md
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
<a href="https://mikro-orm.io"><img src="https://raw.githubusercontent.com/mikro-orm/mikro-orm/master/docs/static/img/logo-readme.svg?sanitize=true" alt="MikroORM" /></a>
|
|
3
3
|
</h1>
|
|
4
4
|
|
|
5
|
-
TypeScript ORM for Node.js based on Data Mapper, [Unit of Work](https://mikro-orm.io/docs/unit-of-work/) and [Identity Map](https://mikro-orm.io/docs/identity-map/) patterns. Supports MongoDB, MySQL, MariaDB, PostgreSQL
|
|
5
|
+
TypeScript ORM for Node.js based on Data Mapper, [Unit of Work](https://mikro-orm.io/docs/unit-of-work/) and [Identity Map](https://mikro-orm.io/docs/identity-map/) patterns. Supports MongoDB, MySQL, MariaDB, PostgreSQL, SQLite (including libSQL), MSSQL and Oracle databases.
|
|
6
6
|
|
|
7
7
|
> Heavily inspired by [Doctrine](https://www.doctrine-project.org/) and [Hibernate](https://hibernate.org/).
|
|
8
8
|
|
|
9
|
-
[](https://
|
|
10
|
-
[](https://
|
|
9
|
+
[](https://npmx.dev/package/@mikro-orm/core)
|
|
10
|
+
[](https://npmx.dev/package/@mikro-orm/core)
|
|
11
11
|
[](https://discord.gg/w8bjxFHS7X)
|
|
12
|
-
[](https://
|
|
12
|
+
[](https://npmx.dev/package/@mikro-orm/core)
|
|
13
13
|
[](https://coveralls.io/r/mikro-orm/mikro-orm?branch=master)
|
|
14
14
|
[](https://github.com/mikro-orm/mikro-orm/actions?workflow=tests)
|
|
15
15
|
|
|
@@ -181,6 +181,7 @@ yarn add @mikro-orm/core @mikro-orm/mysql # for mysql/mariadb
|
|
|
181
181
|
yarn add @mikro-orm/core @mikro-orm/mariadb # for mysql/mariadb
|
|
182
182
|
yarn add @mikro-orm/core @mikro-orm/postgresql # for postgresql
|
|
183
183
|
yarn add @mikro-orm/core @mikro-orm/mssql # for mssql
|
|
184
|
+
yarn add @mikro-orm/core @mikro-orm/oracledb # for oracle
|
|
184
185
|
yarn add @mikro-orm/core @mikro-orm/sqlite # for sqlite
|
|
185
186
|
yarn add @mikro-orm/core @mikro-orm/libsql # for libsql
|
|
186
187
|
```
|
package/SqlEntityManager.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export declare class SqlEntityManager<Driver extends AbstractSqlDriver = Abstrac
|
|
|
20
20
|
/**
|
|
21
21
|
* Shortcut for `createQueryBuilder()`
|
|
22
22
|
*/
|
|
23
|
-
qb<Entity extends object, RootAlias extends string = never>(entityName: EntityName<Entity>, alias?: RootAlias, type?: ConnectionType, loggerContext?: LoggingOptions): QueryBuilder<Entity, RootAlias
|
|
23
|
+
qb<Entity extends object, RootAlias extends string = never>(entityName: EntityName<Entity>, alias?: RootAlias, type?: ConnectionType, loggerContext?: LoggingOptions): QueryBuilder<Entity, RootAlias>;
|
|
24
24
|
/**
|
|
25
25
|
* Returns configured Kysely instance.
|
|
26
26
|
*/
|
|
@@ -29,6 +29,6 @@ export declare class SqlEntityManager<Driver extends AbstractSqlDriver = Abstrac
|
|
|
29
29
|
getRepository<T extends object, U extends EntityRepository<T> = SqlEntityRepository<T>>(entityName: EntityName<T>): GetRepository<T, U>;
|
|
30
30
|
protected applyDiscriminatorCondition<Entity extends object>(entityName: EntityName<Entity>, where: FilterQuery<Entity>): FilterQuery<Entity>;
|
|
31
31
|
}
|
|
32
|
-
type EntitiesFromManager<TEntityManager extends EntityManager<any>> = NonNullable<TEntityManager['~entities']> extends any[] ?
|
|
32
|
+
type EntitiesFromManager<TEntityManager extends EntityManager<any>> = NonNullable<TEntityManager['~entities']> extends any[] ? Extract<NonNullable<TEntityManager['~entities']>[number], EntitySchemaWithMeta> : never;
|
|
33
33
|
type AllEntitiesFromManager<TEntityManager extends EntityManager<any>> = NonNullable<TEntityManager['~entities']> extends any[] ? NonNullable<TEntityManager['~entities']>[number] : never;
|
|
34
34
|
export {};
|
package/SqlEntityManager.js
CHANGED
|
@@ -22,11 +22,11 @@ export class SqlEntityManager extends EntityManager {
|
|
|
22
22
|
*/
|
|
23
23
|
getKysely(options = {}) {
|
|
24
24
|
let kysely = this.getConnection(options.type).getClient();
|
|
25
|
-
if (options.columnNamingStrategy != null
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
if (options.columnNamingStrategy != null ||
|
|
26
|
+
options.tableNamingStrategy != null ||
|
|
27
|
+
options.processOnCreateHooks != null ||
|
|
28
|
+
options.processOnUpdateHooks != null ||
|
|
29
|
+
options.convertValues != null) {
|
|
30
30
|
kysely = kysely.withPlugin(new MikroKyselyPlugin(this, options));
|
|
31
31
|
}
|
|
32
32
|
return kysely;
|
package/dialects/index.d.ts
CHANGED
package/dialects/index.js
CHANGED
|
@@ -11,4 +11,6 @@ export declare class MsSqlNativeQueryBuilder extends NativeQueryBuilder {
|
|
|
11
11
|
protected compileSelect(): void;
|
|
12
12
|
protected addLockClause(): void;
|
|
13
13
|
protected compileTruncate(): void;
|
|
14
|
+
/** MSSQL has no RECURSIVE keyword — CTEs are implicitly recursive. */
|
|
15
|
+
protected getCteKeyword(_hasRecursive: boolean): string;
|
|
14
16
|
}
|
|
@@ -19,6 +19,7 @@ export class MsSqlNativeQueryBuilder extends NativeQueryBuilder {
|
|
|
19
19
|
if (this.options.comment) {
|
|
20
20
|
this.parts.push(...this.options.comment.map(comment => `/* ${comment} */`));
|
|
21
21
|
}
|
|
22
|
+
this.compileCtes();
|
|
22
23
|
if (this.options.onConflict && !Utils.isEmpty(Utils.asArray(this.options.data)[0])) {
|
|
23
24
|
this.compileUpsert();
|
|
24
25
|
}
|
|
@@ -77,9 +78,7 @@ export class MsSqlNativeQueryBuilder extends NativeQueryBuilder {
|
|
|
77
78
|
return { prefix: '', suffix: '' };
|
|
78
79
|
}
|
|
79
80
|
const returningFields = this.options.returning;
|
|
80
|
-
const selections = returningFields
|
|
81
|
-
.map(field => `[t].${this.platform.quoteIdentifier(field)}`)
|
|
82
|
-
.join(',');
|
|
81
|
+
const selections = returningFields.map(field => `[t].${this.platform.quoteIdentifier(field)}`).join(',');
|
|
83
82
|
return {
|
|
84
83
|
prefix: `select top(0) ${selections} into #out from ${this.getTableName()} as t left join ${this.getTableName()} on 0 = 1;`,
|
|
85
84
|
suffix: `select ${selections} from #out as t; drop table #out`,
|
|
@@ -181,7 +180,8 @@ export class MsSqlNativeQueryBuilder extends NativeQueryBuilder {
|
|
|
181
180
|
}
|
|
182
181
|
}
|
|
183
182
|
addLockClause() {
|
|
184
|
-
if (!this.options.lockMode ||
|
|
183
|
+
if (!this.options.lockMode ||
|
|
184
|
+
![LockMode.PESSIMISTIC_READ, LockMode.PESSIMISTIC_WRITE].includes(this.options.lockMode)) {
|
|
185
185
|
return;
|
|
186
186
|
}
|
|
187
187
|
const map = {
|
|
@@ -197,4 +197,8 @@ export class MsSqlNativeQueryBuilder extends NativeQueryBuilder {
|
|
|
197
197
|
const sql = `delete from ${tableName}; declare @count int = case @@rowcount when 0 then 1 else 0 end; dbcc checkident ('${tableName.replace(/[[\]]/g, '')}', reseed, @count)`;
|
|
198
198
|
this.parts.push(sql);
|
|
199
199
|
}
|
|
200
|
+
/** MSSQL has no RECURSIVE keyword — CTEs are implicitly recursive. */
|
|
201
|
+
getCteKeyword(_hasRecursive) {
|
|
202
|
+
return 'with';
|
|
203
|
+
}
|
|
200
204
|
}
|
|
@@ -5,6 +5,7 @@ import { AbstractSqlPlatform } from '../../AbstractSqlPlatform.js';
|
|
|
5
5
|
import type { IndexDef } from '../../typings.js';
|
|
6
6
|
import { MySqlNativeQueryBuilder } from './MySqlNativeQueryBuilder.js';
|
|
7
7
|
export declare class BaseMySqlPlatform extends AbstractSqlPlatform {
|
|
8
|
+
#private;
|
|
8
9
|
protected readonly schemaHelper: MySqlSchemaHelper;
|
|
9
10
|
protected readonly exceptionConverter: MySqlExceptionConverter;
|
|
10
11
|
protected readonly ORDER_BY_NULLS_TRANSLATE: {
|
|
@@ -42,5 +43,10 @@ export declare class BaseMySqlPlatform extends AbstractSqlPlatform {
|
|
|
42
43
|
getFullTextWhereClause(): string;
|
|
43
44
|
getFullTextIndexExpression(indexName: string, schemaName: string | undefined, tableName: string, columns: SimpleColumnMeta[]): string;
|
|
44
45
|
getOrderByExpression(column: string, direction: string, collation?: string): string[];
|
|
46
|
+
getJsonArrayFromSQL(column: string, alias: string, properties: {
|
|
47
|
+
name: string;
|
|
48
|
+
type: string;
|
|
49
|
+
}[]): string;
|
|
50
|
+
getJsonArrayExistsSQL(from: string, where: string): string;
|
|
45
51
|
getDefaultClientUrl(): string;
|
|
46
52
|
}
|
|
@@ -6,6 +6,12 @@ import { MySqlNativeQueryBuilder } from './MySqlNativeQueryBuilder.js';
|
|
|
6
6
|
export class BaseMySqlPlatform extends AbstractSqlPlatform {
|
|
7
7
|
schemaHelper = new MySqlSchemaHelper(this);
|
|
8
8
|
exceptionConverter = new MySqlExceptionConverter();
|
|
9
|
+
#jsonTypeCasts = {
|
|
10
|
+
string: 'text',
|
|
11
|
+
number: 'double',
|
|
12
|
+
bigint: 'bigint',
|
|
13
|
+
boolean: 'unsigned',
|
|
14
|
+
};
|
|
9
15
|
ORDER_BY_NULLS_TRANSLATE = {
|
|
10
16
|
[QueryOrder.asc_nulls_first]: 'is not null',
|
|
11
17
|
[QueryOrder.asc_nulls_last]: 'is null',
|
|
@@ -44,8 +50,7 @@ export class BaseMySqlPlatform extends AbstractSqlPlatform {
|
|
|
44
50
|
return JSON.stringify(value);
|
|
45
51
|
}
|
|
46
52
|
getJsonIndexDefinition(index) {
|
|
47
|
-
return index.columnNames
|
|
48
|
-
.map(column => {
|
|
53
|
+
return index.columnNames.map(column => {
|
|
49
54
|
if (!column.includes('.')) {
|
|
50
55
|
return column;
|
|
51
56
|
}
|
|
@@ -115,6 +120,17 @@ export class BaseMySqlPlatform extends AbstractSqlPlatform {
|
|
|
115
120
|
ret.push(`${col} ${dir.replace(/(\s|nulls|first|last)*/gi, '')}`);
|
|
116
121
|
return ret;
|
|
117
122
|
}
|
|
123
|
+
getJsonArrayFromSQL(column, alias, properties) {
|
|
124
|
+
const columns = properties
|
|
125
|
+
.map(p => `${this.quoteIdentifier(p.name)} ${this.#jsonTypeCasts[p.type] ?? 'text'} path '$.${this.quoteJsonKey(p.name)}'`)
|
|
126
|
+
.join(', ');
|
|
127
|
+
return `json_table(${column}, '$[*]' columns (${columns})) as ${this.quoteIdentifier(alias)}`;
|
|
128
|
+
}
|
|
129
|
+
// MySQL does not support correlated json_table inside EXISTS subqueries,
|
|
130
|
+
// so we use a semi-join via the comma-join pattern instead.
|
|
131
|
+
getJsonArrayExistsSQL(from, where) {
|
|
132
|
+
return `(select 1 from ${from} where ${where} limit 1) is not null`;
|
|
133
|
+
}
|
|
118
134
|
getDefaultClientUrl() {
|
|
119
135
|
return 'mysql://root@127.0.0.1:3306';
|
|
120
136
|
}
|
|
@@ -5,7 +5,7 @@ import { SchemaHelper } from '../../schema/SchemaHelper.js';
|
|
|
5
5
|
import type { DatabaseSchema } from '../../schema/DatabaseSchema.js';
|
|
6
6
|
import type { DatabaseTable } from '../../schema/DatabaseTable.js';
|
|
7
7
|
export declare class MySqlSchemaHelper extends SchemaHelper {
|
|
8
|
-
private
|
|
8
|
+
#private;
|
|
9
9
|
static readonly DEFAULT_VALUES: {
|
|
10
10
|
'now()': string[];
|
|
11
11
|
'current_timestamp(?)': string[];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { EnumType, StringType, TextType } from '@mikro-orm/core';
|
|
2
2
|
import { SchemaHelper } from '../../schema/SchemaHelper.js';
|
|
3
3
|
export class MySqlSchemaHelper extends SchemaHelper {
|
|
4
|
-
|
|
4
|
+
#cache = {};
|
|
5
5
|
static DEFAULT_VALUES = {
|
|
6
6
|
'now()': ['now()', 'current_timestamp'],
|
|
7
7
|
'current_timestamp(?)': ['current_timestamp(?)'],
|
|
@@ -46,7 +46,7 @@ export class MySqlSchemaHelper extends SchemaHelper {
|
|
|
46
46
|
const createView = await connection.execute(`show create view \`${view.view_name}\``);
|
|
47
47
|
if (createView[0]?.['Create View']) {
|
|
48
48
|
// Extract SELECT statement from CREATE VIEW ... AS SELECT ...
|
|
49
|
-
const match = createView[0]['Create View']
|
|
49
|
+
const match = /\bAS\s+(.+)$/is.exec(createView[0]['Create View']);
|
|
50
50
|
definition = match?.[1]?.trim();
|
|
51
51
|
}
|
|
52
52
|
}
|
|
@@ -89,11 +89,13 @@ export class MySqlSchemaHelper extends SchemaHelper {
|
|
|
89
89
|
};
|
|
90
90
|
// Capture column options (prefix length, sort order)
|
|
91
91
|
if (index.sub_part != null || index.sort_order === 'D') {
|
|
92
|
-
indexDef.columns = [
|
|
92
|
+
indexDef.columns = [
|
|
93
|
+
{
|
|
93
94
|
name: index.column_name,
|
|
94
95
|
...(index.sub_part != null && { length: index.sub_part }),
|
|
95
96
|
...(index.sort_order === 'D' && { sort: 'DESC' }),
|
|
96
|
-
}
|
|
97
|
+
},
|
|
98
|
+
];
|
|
97
99
|
}
|
|
98
100
|
// Capture index type for fulltext and spatial indexes
|
|
99
101
|
if (index.index_type === 'FULLTEXT') {
|
|
@@ -149,7 +151,8 @@ export class MySqlSchemaHelper extends SchemaHelper {
|
|
|
149
151
|
*/
|
|
150
152
|
getIndexColumns(index) {
|
|
151
153
|
if (index.columns?.length) {
|
|
152
|
-
return index.columns
|
|
154
|
+
return index.columns
|
|
155
|
+
.map(col => {
|
|
153
156
|
const quotedName = this.quote(col.name);
|
|
154
157
|
// MySQL supports collation via expression: (column_name COLLATE collation_name)
|
|
155
158
|
// When collation is specified, wrap in parentheses as an expression
|
|
@@ -173,7 +176,8 @@ export class MySqlSchemaHelper extends SchemaHelper {
|
|
|
173
176
|
colDef += ` ${col.sort}`;
|
|
174
177
|
}
|
|
175
178
|
return colDef;
|
|
176
|
-
})
|
|
179
|
+
})
|
|
180
|
+
.join(', ');
|
|
177
181
|
}
|
|
178
182
|
return index.columnNames.map(c => this.quote(c)).join(', ');
|
|
179
183
|
}
|
|
@@ -205,20 +209,24 @@ export class MySqlSchemaHelper extends SchemaHelper {
|
|
|
205
209
|
from information_schema.columns where table_schema = database() and table_name in (${tables.map(t => this.platform.quoteValue(t.table_name))})
|
|
206
210
|
order by ordinal_position`;
|
|
207
211
|
const allColumns = await connection.execute(sql);
|
|
208
|
-
const str = (val) => val != null ? '' + val : val;
|
|
212
|
+
const str = (val) => (val != null ? '' + val : val);
|
|
209
213
|
const extra = (val) => val.replace(/auto_increment|default_generated|(stored|virtual) generated/i, '').trim() || undefined;
|
|
210
214
|
const ret = {};
|
|
211
215
|
for (const col of allColumns) {
|
|
212
216
|
const mappedType = this.platform.getMappedType(col.column_type);
|
|
213
|
-
const defaultValue = str(this.normalizeDefaultValue(
|
|
217
|
+
const defaultValue = str(this.normalizeDefaultValue(mappedType.compareAsType() === 'boolean' && ['0', '1'].includes(col.column_default)
|
|
214
218
|
? ['false', 'true'][+col.column_default]
|
|
215
219
|
: col.column_default, col.length));
|
|
216
220
|
const key = this.getTableKey(col);
|
|
217
|
-
const generated = col.generation_expression
|
|
221
|
+
const generated = col.generation_expression
|
|
222
|
+
? `(${col.generation_expression.replaceAll(`\\'`, `'`)}) ${col.extra.match(/stored generated/i) ? 'stored' : 'virtual'}`
|
|
223
|
+
: undefined;
|
|
218
224
|
ret[key] ??= [];
|
|
219
225
|
ret[key].push({
|
|
220
226
|
name: col.column_name,
|
|
221
|
-
type: this.platform.isNumericColumn(mappedType)
|
|
227
|
+
type: this.platform.isNumericColumn(mappedType)
|
|
228
|
+
? col.column_type.replace(/ unsigned$/, '').replace(/\(\d+\)$/, '')
|
|
229
|
+
: col.column_type,
|
|
222
230
|
mappedType,
|
|
223
231
|
unsigned: col.column_type.endsWith(' unsigned'),
|
|
224
232
|
length: col.length,
|
|
@@ -328,17 +336,20 @@ export class MySqlSchemaHelper extends SchemaHelper {
|
|
|
328
336
|
const enums = await connection.execute(sql);
|
|
329
337
|
return enums.reduce((o, item) => {
|
|
330
338
|
o[item.table_name] ??= {};
|
|
331
|
-
o[item.table_name][item.column_name] = item.column_type
|
|
339
|
+
o[item.table_name][item.column_name] = item.column_type
|
|
340
|
+
.match(/enum\((.*)\)/)[1]
|
|
341
|
+
.split(',')
|
|
342
|
+
.map((item) => /'(.*)'/.exec(item)[1]);
|
|
332
343
|
return o;
|
|
333
344
|
}, {});
|
|
334
345
|
}
|
|
335
346
|
async supportsCheckConstraints(connection) {
|
|
336
|
-
if (this.
|
|
337
|
-
return this.
|
|
347
|
+
if (this.#cache.supportsCheckConstraints != null) {
|
|
348
|
+
return this.#cache.supportsCheckConstraints;
|
|
338
349
|
}
|
|
339
350
|
const sql = `select 1 from information_schema.tables where table_name = 'CHECK_CONSTRAINTS' and table_schema = 'information_schema'`;
|
|
340
351
|
const res = await connection.execute(sql);
|
|
341
|
-
return this.
|
|
352
|
+
return (this.#cache.supportsCheckConstraints = res.length > 0);
|
|
342
353
|
}
|
|
343
354
|
getChecksSQL(tables) {
|
|
344
355
|
return `select cc.constraint_schema as table_schema, tc.table_name as table_name, cc.constraint_name as name, cc.check_clause as expression
|