@mikro-orm/sql 7.1.0-dev.5 → 7.1.0-dev.50
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 +1 -1
- package/AbstractSqlConnection.js +27 -6
- package/AbstractSqlDriver.d.ts +26 -1
- package/AbstractSqlDriver.js +294 -37
- package/AbstractSqlPlatform.d.ts +15 -3
- package/AbstractSqlPlatform.js +25 -7
- package/PivotCollectionPersister.d.ts +2 -2
- package/PivotCollectionPersister.js +19 -3
- package/README.md +2 -1
- package/SqlEntityManager.d.ts +48 -5
- package/SqlEntityManager.js +77 -7
- package/SqlMikroORM.d.ts +23 -0
- package/SqlMikroORM.js +23 -0
- package/dialects/mysql/BaseMySqlPlatform.d.ts +4 -5
- package/dialects/mysql/BaseMySqlPlatform.js +9 -10
- package/dialects/mysql/MySqlSchemaHelper.d.ts +19 -3
- package/dialects/mysql/MySqlSchemaHelper.js +280 -49
- package/dialects/oracledb/OracleDialect.d.ts +1 -1
- package/dialects/oracledb/OracleDialect.js +2 -1
- package/dialects/postgresql/BasePostgreSqlEntityManager.d.ts +19 -0
- package/dialects/postgresql/BasePostgreSqlEntityManager.js +24 -0
- package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +11 -5
- package/dialects/postgresql/BasePostgreSqlPlatform.js +75 -17
- package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +38 -1
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +362 -28
- package/dialects/postgresql/index.d.ts +2 -0
- package/dialects/postgresql/index.js +2 -0
- package/dialects/postgresql/typeOverrides.d.ts +14 -0
- package/dialects/postgresql/typeOverrides.js +12 -0
- package/dialects/sqlite/SqlitePlatform.d.ts +2 -1
- package/dialects/sqlite/SqlitePlatform.js +4 -0
- package/dialects/sqlite/SqliteSchemaHelper.d.ts +9 -2
- package/dialects/sqlite/SqliteSchemaHelper.js +148 -19
- package/index.d.ts +2 -0
- package/index.js +2 -0
- package/package.json +4 -4
- package/plugin/transformer.d.ts +11 -3
- package/plugin/transformer.js +138 -29
- package/query/CriteriaNode.d.ts +1 -1
- package/query/CriteriaNode.js +2 -2
- package/query/ObjectCriteriaNode.js +1 -1
- package/query/QueryBuilder.d.ts +42 -1
- package/query/QueryBuilder.js +78 -7
- package/schema/DatabaseSchema.d.ts +29 -2
- package/schema/DatabaseSchema.js +145 -4
- package/schema/DatabaseTable.d.ts +20 -1
- package/schema/DatabaseTable.js +182 -31
- package/schema/SchemaComparator.d.ts +19 -0
- package/schema/SchemaComparator.js +250 -1
- package/schema/SchemaHelper.d.ts +77 -1
- package/schema/SchemaHelper.js +297 -25
- package/schema/SqlSchemaGenerator.d.ts +2 -2
- package/schema/SqlSchemaGenerator.js +47 -10
- package/schema/partitioning.d.ts +13 -0
- package/schema/partitioning.js +326 -0
- package/typings.d.ts +72 -5
package/query/QueryBuilder.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AnyEntity, type AutoPath, type Collection, type ConnectionType, type Dictionary, type EntityData, type EntityDTOFlat, type EntityDTOProp, type EntityKey, type EntityManager, EntityMetadata, type EntityName, type EntityProperty, type ExpandProperty, type FilterObject, type FilterOptions, type FilterValue, type FlushMode, type GroupOperator, type Loaded, LockMode, type LoggingOptions, type MetadataStorage, type PrimaryProperty, type ObjectQuery, PopulateHint, type PopulateOptions, type PopulatePath, QueryFlag, type QueryOrderKeysFlat, type QueryOrderMap, type QueryResult, RawQueryFragment, type Raw, type RequiredEntityData, type Scalar, type SerializeDTO, type Subquery, type Transaction } from '@mikro-orm/core';
|
|
1
|
+
import { type AbortQueryOptions, type AnyEntity, type AutoPath, type Collection, type ConnectionType, type Dictionary, type EntityData, type EntityDTOFlat, type EntityDTOProp, type EntityKey, type EntityManager, EntityMetadata, type EntityName, type EntityProperty, type ExpandProperty, type FilterObject, type FilterOptions, type FilterValue, type FlushMode, type GroupOperator, type Loaded, LockMode, type LoggingOptions, type MetadataStorage, type PrimaryProperty, type ObjectQuery, PopulateHint, type PopulateOptions, type PopulatePath, QueryFlag, type QueryOrderKeysFlat, type QueryOrderMap, type QueryResult, RawQueryFragment, type Raw, type RequiredEntityData, type Scalar, type SerializeDTO, type Subquery, type Transaction } from '@mikro-orm/core';
|
|
2
2
|
import { JoinType, QueryType } from './enums.js';
|
|
3
3
|
import type { AbstractSqlDriver } from '../AbstractSqlDriver.js';
|
|
4
4
|
import { type Alias, type OnConflictClause, QueryBuilderHelper } from './QueryBuilderHelper.js';
|
|
@@ -11,6 +11,20 @@ export interface ExecuteOptions {
|
|
|
11
11
|
mergeResults?: boolean;
|
|
12
12
|
}
|
|
13
13
|
export interface QBStreamOptions {
|
|
14
|
+
/**
|
|
15
|
+
* How many rows to fetch in one round-trip.
|
|
16
|
+
* Lower values will result in more queries and network bandwidth, but less memory usage.
|
|
17
|
+
* Higher values will result in fewer queries and network bandwidth, but higher memory usage.
|
|
18
|
+
* Note that the results are iterated one row at a time regardless of this value.
|
|
19
|
+
*
|
|
20
|
+
* Honored on PostgreSQL (cursor-based fetch), MSSQL (tedious stream chunk size)
|
|
21
|
+
* and Oracle (mapped to `fetchArraySize`). Ignored on MySQL, MariaDB, SQLite and
|
|
22
|
+
* libSQL, where the underlying driver already streams row-by-row with no batching
|
|
23
|
+
* knob.
|
|
24
|
+
*
|
|
25
|
+
* @default 100 on dialects that honor it.
|
|
26
|
+
*/
|
|
27
|
+
chunkSize?: number;
|
|
14
28
|
/**
|
|
15
29
|
* Results are mapped to entities, if you set `mapResults: false` you will get POJOs instead.
|
|
16
30
|
*
|
|
@@ -193,6 +207,11 @@ export interface QBState<Entity extends object> {
|
|
|
193
207
|
})[];
|
|
194
208
|
tptJoinsApplied: boolean;
|
|
195
209
|
autoJoinedPaths: string[];
|
|
210
|
+
partitionLimit?: {
|
|
211
|
+
partitionBy: string;
|
|
212
|
+
limit: number;
|
|
213
|
+
offset?: number;
|
|
214
|
+
};
|
|
196
215
|
}
|
|
197
216
|
/**
|
|
198
217
|
* SQL query builder with fluent interface.
|
|
@@ -675,6 +694,12 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
675
694
|
setFlag(flag: QueryFlag): this;
|
|
676
695
|
unsetFlag(flag: QueryFlag): this;
|
|
677
696
|
hasFlag(flag: QueryFlag): boolean;
|
|
697
|
+
/** @internal */
|
|
698
|
+
setPartitionLimit(opts: {
|
|
699
|
+
partitionBy: string;
|
|
700
|
+
limit: number;
|
|
701
|
+
offset?: number;
|
|
702
|
+
}): this;
|
|
678
703
|
cache(config?: boolean | number | [string, number]): this;
|
|
679
704
|
/**
|
|
680
705
|
* Adds index hint to the FROM clause.
|
|
@@ -890,6 +915,11 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
890
915
|
* Gets logger context for this query builder.
|
|
891
916
|
*/
|
|
892
917
|
getLoggerContext<T extends Dictionary & LoggingOptions = Dictionary>(): T;
|
|
918
|
+
/**
|
|
919
|
+
* Configures cancellation behavior for this query builder. The signal is forwarded to the
|
|
920
|
+
* underlying database client; see {@apilink AbortQueryOptions} for the available strategies.
|
|
921
|
+
*/
|
|
922
|
+
setAbortOptions(options: AbortQueryOptions | undefined): void;
|
|
893
923
|
private fromVirtual;
|
|
894
924
|
/**
|
|
895
925
|
* Adds a join from a property object. Used internally for TPT joins where the property
|
|
@@ -952,6 +982,17 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
952
982
|
private processNestedJoins;
|
|
953
983
|
private hasToManyJoins;
|
|
954
984
|
protected wrapPaginateSubQuery(meta: EntityMetadata): void;
|
|
985
|
+
/**
|
|
986
|
+
* Wraps the inner query (which has ROW_NUMBER in SELECT) with an outer query
|
|
987
|
+
* that filters by the __rn column to apply per-parent limiting.
|
|
988
|
+
*/
|
|
989
|
+
protected wrapPartitionLimitSubQuery(innerQb: NativeQueryBuilder): NativeQueryBuilder;
|
|
990
|
+
/**
|
|
991
|
+
* Adds ROW_NUMBER() OVER (PARTITION BY ...) to the SELECT list and prepares
|
|
992
|
+
* the query state for per-parent limiting. The actual wrapping into a subquery
|
|
993
|
+
* with __rn filtering happens in getNativeQuery().
|
|
994
|
+
*/
|
|
995
|
+
protected preparePartitionLimit(): void;
|
|
955
996
|
/**
|
|
956
997
|
* Computes the set of populate paths from the _populate hints.
|
|
957
998
|
*/
|
package/query/QueryBuilder.js
CHANGED
|
@@ -35,6 +35,7 @@ export class QueryBuilder {
|
|
|
35
35
|
#state = _a.createDefaultState();
|
|
36
36
|
#helper;
|
|
37
37
|
#query;
|
|
38
|
+
#abortOptions;
|
|
38
39
|
/** @internal */
|
|
39
40
|
static createDefaultState() {
|
|
40
41
|
return {
|
|
@@ -721,6 +722,12 @@ export class QueryBuilder {
|
|
|
721
722
|
hasFlag(flag) {
|
|
722
723
|
return this.#state.flags.has(flag);
|
|
723
724
|
}
|
|
725
|
+
/** @internal */
|
|
726
|
+
setPartitionLimit(opts) {
|
|
727
|
+
this.ensureNotFinalized();
|
|
728
|
+
this.#state.partitionLimit = opts;
|
|
729
|
+
return this;
|
|
730
|
+
}
|
|
724
731
|
cache(config = true) {
|
|
725
732
|
this.ensureNotFinalized();
|
|
726
733
|
this.#state.cache = config;
|
|
@@ -825,6 +832,9 @@ export class QueryBuilder {
|
|
|
825
832
|
this.helper.getLockSQL(qb, this.#state.lockMode, this.#state.lockTables, this.#state.joins);
|
|
826
833
|
}
|
|
827
834
|
this.processReturningStatement(qb, this.mainAlias.meta, this.#state.insertSubQuery ? undefined : this.#state.data, this.#state.returning);
|
|
835
|
+
if (this.#state.partitionLimit) {
|
|
836
|
+
return (this.#query.qb = this.wrapPartitionLimitSubQuery(qb));
|
|
837
|
+
}
|
|
828
838
|
return (this.#query.qb = qb);
|
|
829
839
|
}
|
|
830
840
|
processReturningStatement(qb, meta, data, returning) {
|
|
@@ -1001,7 +1011,7 @@ export class QueryBuilder {
|
|
|
1001
1011
|
if (cached?.data !== undefined) {
|
|
1002
1012
|
return cached.data;
|
|
1003
1013
|
}
|
|
1004
|
-
const loggerContext = { id: this.em?.id, ...this.loggerContext };
|
|
1014
|
+
const loggerContext = { id: this.em?.id, ...this.loggerContext, ...this.#abortOptions };
|
|
1005
1015
|
const res = await this.getConnection().execute(query.sql, query.params, method, this.context, loggerContext);
|
|
1006
1016
|
const meta = this.mainAlias.meta;
|
|
1007
1017
|
if (!options.mapResults || !meta) {
|
|
@@ -1055,9 +1065,10 @@ export class QueryBuilder {
|
|
|
1055
1065
|
options ??= {};
|
|
1056
1066
|
options.mergeResults ??= true;
|
|
1057
1067
|
options.mapResults ??= true;
|
|
1068
|
+
const chunkSize = options.chunkSize ?? 100;
|
|
1058
1069
|
const query = this.toQuery();
|
|
1059
|
-
const loggerContext = { id: this.em?.id, ...this.loggerContext };
|
|
1060
|
-
const res = this.getConnection().stream(query.sql, query.params, this.context, loggerContext);
|
|
1070
|
+
const loggerContext = { id: this.em?.id, ...this.loggerContext, ...this.#abortOptions };
|
|
1071
|
+
const res = this.getConnection().stream(query.sql, query.params, this.context, loggerContext, chunkSize);
|
|
1061
1072
|
const meta = this.mainAlias.meta;
|
|
1062
1073
|
if (options.rawResults || !meta) {
|
|
1063
1074
|
yield* res;
|
|
@@ -1280,6 +1291,7 @@ export class QueryBuilder {
|
|
|
1280
1291
|
qb.#state.finalized = false;
|
|
1281
1292
|
qb.#query = undefined;
|
|
1282
1293
|
qb.#helper = qb.createQueryBuilderHelper();
|
|
1294
|
+
qb.#abortOptions = this.#abortOptions;
|
|
1283
1295
|
return qb;
|
|
1284
1296
|
}
|
|
1285
1297
|
/**
|
|
@@ -1295,6 +1307,13 @@ export class QueryBuilder {
|
|
|
1295
1307
|
this.loggerContext ??= {};
|
|
1296
1308
|
return this.loggerContext;
|
|
1297
1309
|
}
|
|
1310
|
+
/**
|
|
1311
|
+
* Configures cancellation behavior for this query builder. The signal is forwarded to the
|
|
1312
|
+
* underlying database client; see {@apilink AbortQueryOptions} for the available strategies.
|
|
1313
|
+
*/
|
|
1314
|
+
setAbortOptions(options) {
|
|
1315
|
+
this.#abortOptions = options;
|
|
1316
|
+
}
|
|
1298
1317
|
fromVirtual(meta) {
|
|
1299
1318
|
if (typeof meta.expression === 'string') {
|
|
1300
1319
|
return `(${meta.expression}) as ${this.platform.quoteIdentifier(this.alias)}`;
|
|
@@ -1385,6 +1404,7 @@ export class QueryBuilder {
|
|
|
1385
1404
|
aliased: [QueryType.SELECT, QueryType.COUNT].includes(this.type),
|
|
1386
1405
|
});
|
|
1387
1406
|
const criteriaNode = CriteriaNodeFactory.createNode(this.metadata, prop.targetMeta.class, cond);
|
|
1407
|
+
const joinCountBefore = Object.keys(this.#state.joins).length;
|
|
1388
1408
|
cond = criteriaNode.process(this, { ignoreBranching: true, alias });
|
|
1389
1409
|
let aliasedName = `${fromAlias}.${prop.name}#${alias}`;
|
|
1390
1410
|
path ??= `${Object.values(this.#state.joins).find(j => j.alias === fromAlias)?.path ?? Utils.className(entityName)}.${prop.name}`;
|
|
@@ -1414,6 +1434,20 @@ export class QueryBuilder {
|
|
|
1414
1434
|
this.#state.joins[aliasedName] = this.helper.joinManyToOneReference(prop, ownerAlias, alias, type, cond, schema);
|
|
1415
1435
|
this.#state.joins[aliasedName].path ??= path;
|
|
1416
1436
|
}
|
|
1437
|
+
// auto-joins added by cond processing that depend on the new alias would otherwise produce a
|
|
1438
|
+
// forward reference (the auto-join's ON refers to alias, while alias's ON refers back to it);
|
|
1439
|
+
// fold them into the new join so both aliases share scope in the outer ON clause (issue #7681)
|
|
1440
|
+
const condJoin = this.#state.joins[aliasedName];
|
|
1441
|
+
const joinKeys = Object.keys(this.#state.joins);
|
|
1442
|
+
for (let i = joinCountBefore; i < joinKeys.length; i++) {
|
|
1443
|
+
const j = this.#state.joins[joinKeys[i]];
|
|
1444
|
+
if (j === condJoin || j.ownerAlias !== alias) {
|
|
1445
|
+
continue;
|
|
1446
|
+
}
|
|
1447
|
+
const nested = (condJoin.nested ??= new Set());
|
|
1448
|
+
j.type = j.type === JoinType.innerJoin ? JoinType.nestedInnerJoin : JoinType.nestedLeftJoin;
|
|
1449
|
+
nested.add(j);
|
|
1450
|
+
}
|
|
1417
1451
|
return { prop, key: aliasedName };
|
|
1418
1452
|
}
|
|
1419
1453
|
prepareFields(fields, type = 'where', schema) {
|
|
@@ -1743,13 +1777,13 @@ export class QueryBuilder {
|
|
|
1743
1777
|
}
|
|
1744
1778
|
const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.get(cls));
|
|
1745
1779
|
const children = [];
|
|
1746
|
-
const lookUpChildren = (ret,
|
|
1747
|
-
const children = types.filter(meta2 => meta2.extends ===
|
|
1748
|
-
children.forEach(m => lookUpChildren(ret, m
|
|
1780
|
+
const lookUpChildren = (ret, parent) => {
|
|
1781
|
+
const children = types.filter(meta2 => meta2.extends && this.metadata.find(meta2.extends) === parent);
|
|
1782
|
+
children.forEach(m => lookUpChildren(ret, m));
|
|
1749
1783
|
ret.push(...children.filter(c => c.discriminatorValue));
|
|
1750
1784
|
return children;
|
|
1751
1785
|
};
|
|
1752
|
-
lookUpChildren(children, meta
|
|
1786
|
+
lookUpChildren(children, meta);
|
|
1753
1787
|
this.andWhere({
|
|
1754
1788
|
[meta.root.discriminatorColumn]: children.length > 0
|
|
1755
1789
|
? { $in: [meta.discriminatorValue, ...children.map(c => c.discriminatorValue)] }
|
|
@@ -1899,6 +1933,9 @@ export class QueryBuilder {
|
|
|
1899
1933
|
(this.#state.limit > 0 || this.#state.offset > 0)) {
|
|
1900
1934
|
this.wrapPaginateSubQuery(meta);
|
|
1901
1935
|
}
|
|
1936
|
+
if (this.#state.partitionLimit) {
|
|
1937
|
+
this.preparePartitionLimit();
|
|
1938
|
+
}
|
|
1902
1939
|
if (meta &&
|
|
1903
1940
|
(this.#state.flags.has(QueryFlag.UPDATE_SUB_QUERY) || this.#state.flags.has(QueryFlag.DELETE_SUB_QUERY))) {
|
|
1904
1941
|
this.wrapModifySubQuery(meta);
|
|
@@ -2118,6 +2155,40 @@ export class QueryBuilder {
|
|
|
2118
2155
|
[Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: raw(sql, params) },
|
|
2119
2156
|
});
|
|
2120
2157
|
}
|
|
2158
|
+
/**
|
|
2159
|
+
* Wraps the inner query (which has ROW_NUMBER in SELECT) with an outer query
|
|
2160
|
+
* that filters by the __rn column to apply per-parent limiting.
|
|
2161
|
+
*/
|
|
2162
|
+
wrapPartitionLimitSubQuery(innerQb) {
|
|
2163
|
+
const { limit, offset = 0 } = this.#state.partitionLimit;
|
|
2164
|
+
const rnCol = this.platform.quoteIdentifier('__rn');
|
|
2165
|
+
innerQb.as(this.mainAlias.aliasName);
|
|
2166
|
+
const outerQb = this.platform.createNativeQueryBuilder();
|
|
2167
|
+
outerQb.select('*').from(innerQb);
|
|
2168
|
+
outerQb.where(`${rnCol} > ? and ${rnCol} <= ?`, [offset, offset + limit]);
|
|
2169
|
+
outerQb.orderBy(rnCol);
|
|
2170
|
+
return outerQb;
|
|
2171
|
+
}
|
|
2172
|
+
/**
|
|
2173
|
+
* Adds ROW_NUMBER() OVER (PARTITION BY ...) to the SELECT list and prepares
|
|
2174
|
+
* the query state for per-parent limiting. The actual wrapping into a subquery
|
|
2175
|
+
* with __rn filtering happens in getNativeQuery().
|
|
2176
|
+
*/
|
|
2177
|
+
preparePartitionLimit() {
|
|
2178
|
+
const { partitionBy } = this.#state.partitionLimit;
|
|
2179
|
+
// `partitionBy` is always a declared property name, so mapper returns a string here.
|
|
2180
|
+
const partitionCol = this.helper.mapper(partitionBy, this.type, undefined, null);
|
|
2181
|
+
const quotedPartition = partitionCol
|
|
2182
|
+
.split('.')
|
|
2183
|
+
.map(e => this.platform.quoteIdentifier(e))
|
|
2184
|
+
.join('.');
|
|
2185
|
+
const queryOrder = this.helper.getQueryOrder(this.type, this.#state.orderBy, this.#state.populateMap, this.#state.collation);
|
|
2186
|
+
const orderBySql = queryOrder.length > 0 ? Utils.unique(queryOrder).join(', ') : quotedPartition;
|
|
2187
|
+
const rnAlias = this.platform.quoteIdentifier('__rn');
|
|
2188
|
+
this.#state.fields.push(raw(`row_number() over (partition by ${quotedPartition} order by ${orderBySql}) as ${rnAlias}`));
|
|
2189
|
+
// Moved into the OVER clause; outer query re-applies via wrapPartitionLimitSubQuery
|
|
2190
|
+
this.#state.orderBy = [];
|
|
2191
|
+
}
|
|
2121
2192
|
/**
|
|
2122
2193
|
* Computes the set of populate paths from the _populate hints.
|
|
2123
2194
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type Configuration, type Dictionary, type EntityMetadata, type Transaction } from '@mikro-orm/core';
|
|
1
|
+
import { type Configuration, type Dictionary, type EntityMetadata, type Routine, type Transaction, type Type } from '@mikro-orm/core';
|
|
2
2
|
import { DatabaseTable } from './DatabaseTable.js';
|
|
3
3
|
import type { AbstractSqlConnection } from '../AbstractSqlConnection.js';
|
|
4
|
-
import type { DatabaseView } from '../typings.js';
|
|
4
|
+
import type { DatabaseView, SqlRoutineDef } from '../typings.js';
|
|
5
5
|
import type { AbstractSqlPlatform } from '../AbstractSqlPlatform.js';
|
|
6
6
|
/**
|
|
7
7
|
* @internal
|
|
@@ -24,6 +24,8 @@ export declare class DatabaseSchema {
|
|
|
24
24
|
setViews(views: DatabaseView[]): void;
|
|
25
25
|
getView(name: string): DatabaseView | undefined;
|
|
26
26
|
hasView(name: string): boolean;
|
|
27
|
+
addRoutine(routine: SqlRoutineDef): SqlRoutineDef;
|
|
28
|
+
getRoutines(): SqlRoutineDef[];
|
|
27
29
|
setNativeEnums(nativeEnums: Dictionary<{
|
|
28
30
|
name: string;
|
|
29
31
|
schema?: string;
|
|
@@ -43,7 +45,32 @@ export declare class DatabaseSchema {
|
|
|
43
45
|
hasNativeEnum(name: string): boolean;
|
|
44
46
|
getNamespaces(): string[];
|
|
45
47
|
static create(connection: AbstractSqlConnection, platform: AbstractSqlPlatform, config: Configuration, schemaName?: string, schemas?: string[], takeTables?: (string | RegExp)[], skipTables?: (string | RegExp)[], skipViews?: (string | RegExp)[], ctx?: Transaction): Promise<DatabaseSchema>;
|
|
48
|
+
/** Separate from `create()` so the comparator only pays for routine introspection when the user actually defined routines. SQLite/libSQL helpers return []. */
|
|
49
|
+
loadRoutines(connection: AbstractSqlConnection, platform: AbstractSqlPlatform, schemas?: string[]): Promise<void>;
|
|
46
50
|
static fromMetadata(metadata: EntityMetadata[], platform: AbstractSqlPlatform, config: Configuration, schemaName?: string, em?: any): DatabaseSchema;
|
|
51
|
+
/** Separate from {@link fromMetadata} so the comparator only walks routines when the user defined any. */
|
|
52
|
+
addRoutinesFromMetadata(routines: readonly Routine[], platform: AbstractSqlPlatform, em?: any): void;
|
|
53
|
+
/**
|
|
54
|
+
* Normalises a routine's `returns` config to the `{ type, runtimeType, nullable }` shape the
|
|
55
|
+
* DDL side and introspection-comparator both consume. Supports both `{ type: SomeType }`
|
|
56
|
+
* (Type drives column + runtime types) and `{ runtimeType, columnType, ... }` (explicit).
|
|
57
|
+
*
|
|
58
|
+
* @internal
|
|
59
|
+
*/
|
|
60
|
+
static normaliseRoutineReturns(returns: Routine['returns'], platform: AbstractSqlPlatform): {
|
|
61
|
+
type: string;
|
|
62
|
+
runtimeType: string;
|
|
63
|
+
nullable?: boolean;
|
|
64
|
+
} | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Maps a routine param's declared `type` to a dialect-specific SQL column type. A `Type`
|
|
67
|
+
* instance (when the user passed a `Type` class/instance at `type`) routes through
|
|
68
|
+
* `Type.getColumnType` so its dialect-aware mapping wins. String aliases (`'string'`,
|
|
69
|
+
* `'number'`, …) go through the platform's type registry; literal SQL types pass through.
|
|
70
|
+
*
|
|
71
|
+
* @internal
|
|
72
|
+
*/
|
|
73
|
+
static resolveRoutineColumnType(type: string | Type<unknown>, platform: AbstractSqlPlatform): string;
|
|
47
74
|
private static getViewDefinition;
|
|
48
75
|
private static getSchemaName;
|
|
49
76
|
/**
|
package/schema/DatabaseSchema.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ReferenceKind, isRaw, } from '@mikro-orm/core';
|
|
2
2
|
import { DatabaseTable } from './DatabaseTable.js';
|
|
3
|
+
import { getTablePartitioning } from './partitioning.js';
|
|
3
4
|
/**
|
|
4
5
|
* @internal
|
|
5
6
|
*/
|
|
@@ -7,6 +8,7 @@ export class DatabaseSchema {
|
|
|
7
8
|
name;
|
|
8
9
|
#tables = [];
|
|
9
10
|
#views = [];
|
|
11
|
+
#routines = [];
|
|
10
12
|
#namespaces = new Set();
|
|
11
13
|
#nativeEnums = {}; // for postgres
|
|
12
14
|
#platform;
|
|
@@ -64,6 +66,16 @@ export class DatabaseSchema {
|
|
|
64
66
|
hasView(name) {
|
|
65
67
|
return !!this.getView(name);
|
|
66
68
|
}
|
|
69
|
+
addRoutine(routine) {
|
|
70
|
+
this.#routines.push(routine);
|
|
71
|
+
if (routine.schema != null) {
|
|
72
|
+
this.#namespaces.add(routine.schema);
|
|
73
|
+
}
|
|
74
|
+
return routine;
|
|
75
|
+
}
|
|
76
|
+
getRoutines() {
|
|
77
|
+
return this.#routines;
|
|
78
|
+
}
|
|
67
79
|
setNativeEnums(nativeEnums) {
|
|
68
80
|
this.#nativeEnums = nativeEnums;
|
|
69
81
|
for (const nativeEnum of Object.values(nativeEnums)) {
|
|
@@ -110,6 +122,10 @@ export class DatabaseSchema {
|
|
|
110
122
|
}
|
|
111
123
|
return schema;
|
|
112
124
|
}
|
|
125
|
+
/** Separate from `create()` so the comparator only pays for routine introspection when the user actually defined routines. SQLite/libSQL helpers return []. */
|
|
126
|
+
async loadRoutines(connection, platform, schemas = []) {
|
|
127
|
+
this.#routines = await platform.getSchemaHelper().getAllRoutines(connection, schemas);
|
|
128
|
+
}
|
|
113
129
|
static fromMetadata(metadata, platform, config, schemaName, em) {
|
|
114
130
|
const schema = new DatabaseSchema(platform, schemaName ?? config.get('schema'));
|
|
115
131
|
const nativeEnums = {};
|
|
@@ -173,6 +189,9 @@ export class DatabaseSchema {
|
|
|
173
189
|
}
|
|
174
190
|
const table = schema.addTable(meta.collection, this.getSchemaName(meta, config, schemaName));
|
|
175
191
|
table.comment = meta.comment;
|
|
192
|
+
if (meta.partitionBy) {
|
|
193
|
+
table.setPartitioning(getTablePartitioning(meta, this.getSchemaName(meta, config, schemaName), id => platform.quoteIdentifier(id)));
|
|
194
|
+
}
|
|
176
195
|
// For TPT child entities, only use ownProps (properties defined in this entity only)
|
|
177
196
|
// For all other entities (including TPT root), use all props
|
|
178
197
|
const propsToProcess = meta.inheritanceType === 'tpt' && meta.tptParent && meta.ownProps ? meta.ownProps : meta.props;
|
|
@@ -220,9 +239,127 @@ export class DatabaseSchema {
|
|
|
220
239
|
columnName,
|
|
221
240
|
});
|
|
222
241
|
}
|
|
242
|
+
for (const trigger of meta.triggers) {
|
|
243
|
+
const body = isRaw(trigger.body)
|
|
244
|
+
? platform.formatQuery(trigger.body.sql, trigger.body.params)
|
|
245
|
+
: trigger.body;
|
|
246
|
+
table.addTrigger({
|
|
247
|
+
name: trigger.name,
|
|
248
|
+
timing: trigger.timing,
|
|
249
|
+
events: trigger.events,
|
|
250
|
+
forEach: trigger.forEach ?? 'row',
|
|
251
|
+
body: body ?? '',
|
|
252
|
+
when: trigger.when,
|
|
253
|
+
expression: trigger.expression,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
223
256
|
}
|
|
224
257
|
return schema;
|
|
225
258
|
}
|
|
259
|
+
/** Separate from {@link fromMetadata} so the comparator only walks routines when the user defined any. */
|
|
260
|
+
addRoutinesFromMetadata(routines, platform, em) {
|
|
261
|
+
const resolveBody = (raw) => {
|
|
262
|
+
if (raw == null) {
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
if (typeof raw === 'string') {
|
|
266
|
+
return raw;
|
|
267
|
+
}
|
|
268
|
+
if (isRaw(raw)) {
|
|
269
|
+
return platform.formatQuery(raw.sql, raw.params);
|
|
270
|
+
}
|
|
271
|
+
return undefined;
|
|
272
|
+
};
|
|
273
|
+
const helper = platform.getSchemaHelper();
|
|
274
|
+
for (const routine of routines) {
|
|
275
|
+
const paramMap = routine.params.reduce((o, p) => {
|
|
276
|
+
o[p.name] = helper.routineParamReference(p.name);
|
|
277
|
+
return o;
|
|
278
|
+
}, {});
|
|
279
|
+
const evaluated = typeof routine.body === 'function' ? routine.body(paramMap, em) : routine.body;
|
|
280
|
+
const body = resolveBody(evaluated);
|
|
281
|
+
const returns = DatabaseSchema.normaliseRoutineReturns(routine.returns, platform);
|
|
282
|
+
this.addRoutine({
|
|
283
|
+
name: routine.name,
|
|
284
|
+
// MySQL has no schema namespace for routines, so leave undefined to align with the introspection side.
|
|
285
|
+
schema: routine.schema ?? (platform.getDefaultSchemaName() != null ? this.name : undefined),
|
|
286
|
+
type: routine.type,
|
|
287
|
+
language: routine.language,
|
|
288
|
+
comment: routine.comment,
|
|
289
|
+
security: routine.security,
|
|
290
|
+
definer: routine.definer,
|
|
291
|
+
deterministic: routine.deterministic,
|
|
292
|
+
dataAccess: routine.dataAccess,
|
|
293
|
+
body,
|
|
294
|
+
expression: routine.expression,
|
|
295
|
+
ignoreSchemaChanges: routine.ignoreSchemaChanges,
|
|
296
|
+
params: routine.params.map(p => ({
|
|
297
|
+
name: p.name,
|
|
298
|
+
type: DatabaseSchema.resolveRoutineColumnType(p.type, platform),
|
|
299
|
+
direction: helper.normaliseRoutineParamDirection(p.direction),
|
|
300
|
+
nullable: p.nullable,
|
|
301
|
+
defaultRaw: p.defaultRaw,
|
|
302
|
+
})),
|
|
303
|
+
returns,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Normalises a routine's `returns` config to the `{ type, runtimeType, nullable }` shape the
|
|
309
|
+
* DDL side and introspection-comparator both consume. Supports both `{ type: SomeType }`
|
|
310
|
+
* (Type drives column + runtime types) and `{ runtimeType, columnType, ... }` (explicit).
|
|
311
|
+
*
|
|
312
|
+
* @internal
|
|
313
|
+
*/
|
|
314
|
+
static normaliseRoutineReturns(returns, platform) {
|
|
315
|
+
if (!returns || typeof returns !== 'object') {
|
|
316
|
+
return undefined;
|
|
317
|
+
}
|
|
318
|
+
if ('runtimeType' in returns) {
|
|
319
|
+
return {
|
|
320
|
+
type: (returns.columnType ?? returns.runtimeType),
|
|
321
|
+
runtimeType: returns.runtimeType,
|
|
322
|
+
nullable: returns.nullable,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
if ('type' in returns && returns.type) {
|
|
326
|
+
const instance = typeof returns.type === 'function' ? new returns.type() : returns.type;
|
|
327
|
+
return {
|
|
328
|
+
type: DatabaseSchema.resolveRoutineColumnType(instance, platform),
|
|
329
|
+
runtimeType: instance.runtimeType,
|
|
330
|
+
nullable: returns.nullable,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
return undefined;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Maps a routine param's declared `type` to a dialect-specific SQL column type. A `Type`
|
|
337
|
+
* instance (when the user passed a `Type` class/instance at `type`) routes through
|
|
338
|
+
* `Type.getColumnType` so its dialect-aware mapping wins. String aliases (`'string'`,
|
|
339
|
+
* `'number'`, …) go through the platform's type registry; literal SQL types pass through.
|
|
340
|
+
*
|
|
341
|
+
* @internal
|
|
342
|
+
*/
|
|
343
|
+
static resolveRoutineColumnType(type, platform) {
|
|
344
|
+
if (typeof type !== 'string') {
|
|
345
|
+
return type.getColumnType({ columnTypes: [], runtimeType: 'any' }, platform);
|
|
346
|
+
}
|
|
347
|
+
const lower = type.toLowerCase();
|
|
348
|
+
const aliases = {
|
|
349
|
+
string: 'string',
|
|
350
|
+
number: 'integer',
|
|
351
|
+
bigint: 'bigint',
|
|
352
|
+
boolean: 'boolean',
|
|
353
|
+
date: 'datetime',
|
|
354
|
+
buffer: 'blob',
|
|
355
|
+
};
|
|
356
|
+
const mappedKey = aliases[lower];
|
|
357
|
+
if (!mappedKey) {
|
|
358
|
+
return type;
|
|
359
|
+
}
|
|
360
|
+
const t = platform.getMappedType(mappedKey);
|
|
361
|
+
return t.getColumnType({ type: mappedKey, length: undefined }, platform);
|
|
362
|
+
}
|
|
226
363
|
static getViewDefinition(meta, em, platform) {
|
|
227
364
|
if (typeof meta.expression === 'string') {
|
|
228
365
|
return meta.expression;
|
|
@@ -323,12 +460,16 @@ export class DatabaseSchema {
|
|
|
323
460
|
(prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner));
|
|
324
461
|
}
|
|
325
462
|
toJSON() {
|
|
463
|
+
// locale-independent comparison so the snapshot is stable across machines
|
|
464
|
+
const byString = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
465
|
+
const tableKey = (t) => `${t.schema ?? ''}.${t.name}`;
|
|
466
|
+
const byTable = (a, b) => byString(tableKey(a), tableKey(b));
|
|
326
467
|
return {
|
|
327
468
|
name: this.name,
|
|
328
|
-
namespaces: [...this.#namespaces],
|
|
329
|
-
tables: this.#tables,
|
|
330
|
-
views: this.#views,
|
|
331
|
-
nativeEnums: this.#nativeEnums,
|
|
469
|
+
namespaces: [...this.#namespaces].sort(),
|
|
470
|
+
tables: [...this.#tables].sort(byTable),
|
|
471
|
+
views: [...this.#views].sort(byTable),
|
|
472
|
+
nativeEnums: Object.fromEntries(Object.entries(this.#nativeEnums).sort(([a], [b]) => byString(a, b))),
|
|
332
473
|
};
|
|
333
474
|
}
|
|
334
475
|
prune(schema, wildcardSchemaTables) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Configuration, type DeferMode, type Dictionary, type EntityMetadata, type EntityProperty, type IndexCallback, type NamingStrategy } from '@mikro-orm/core';
|
|
2
2
|
import type { SchemaHelper } from './SchemaHelper.js';
|
|
3
|
-
import type { CheckDef, Column, ForeignKey, IndexDef } from '../typings.js';
|
|
3
|
+
import type { CheckDef, Column, ForeignKey, IndexDef, TablePartitioning, SqlTriggerDef } from '../typings.js';
|
|
4
4
|
import type { AbstractSqlPlatform } from '../AbstractSqlPlatform.js';
|
|
5
5
|
/**
|
|
6
6
|
* @internal
|
|
@@ -15,6 +15,14 @@ export declare class DatabaseTable {
|
|
|
15
15
|
items: string[];
|
|
16
16
|
}>;
|
|
17
17
|
comment?: string;
|
|
18
|
+
partitioning?: TablePartitioning;
|
|
19
|
+
/**
|
|
20
|
+
* Effective collation the column defaults to when no explicit `COLLATE` is set on a column.
|
|
21
|
+
* For MySQL/MariaDB this is the table collation; for PostgreSQL and MSSQL this is the database default;
|
|
22
|
+
* SQLite has no configurable default. Used by `SchemaComparator.diffCollation` to avoid flapping
|
|
23
|
+
* when a property explicitly names the default collation.
|
|
24
|
+
*/
|
|
25
|
+
collation?: string;
|
|
18
26
|
constructor(platform: AbstractSqlPlatform, name: string, schema?: string | undefined);
|
|
19
27
|
getQuotedName(): string;
|
|
20
28
|
getColumns(): Column[];
|
|
@@ -22,11 +30,17 @@ export declare class DatabaseTable {
|
|
|
22
30
|
removeColumn(name: string): void;
|
|
23
31
|
getIndexes(): IndexDef[];
|
|
24
32
|
getChecks(): CheckDef[];
|
|
33
|
+
getPartitioning(): TablePartitioning | undefined;
|
|
34
|
+
/** @internal */
|
|
35
|
+
setPartitioning(partitioning?: TablePartitioning): void;
|
|
36
|
+
getTriggers(): SqlTriggerDef[];
|
|
25
37
|
/** @internal */
|
|
26
38
|
setIndexes(indexes: IndexDef[]): void;
|
|
27
39
|
/** @internal */
|
|
28
40
|
setChecks(checks: CheckDef[]): void;
|
|
29
41
|
/** @internal */
|
|
42
|
+
setTriggers(triggers: SqlTriggerDef[]): void;
|
|
43
|
+
/** @internal */
|
|
30
44
|
setForeignKeys(fks: Dictionary<ForeignKey>): void;
|
|
31
45
|
init(cols: Column[], indexes: IndexDef[] | undefined, checks: CheckDef[] | undefined, pks: string[], fks?: Dictionary<ForeignKey>, enums?: Dictionary<string[]>): void;
|
|
32
46
|
addColumn(column: Column): void;
|
|
@@ -47,6 +61,8 @@ export declare class DatabaseTable {
|
|
|
47
61
|
hasIndex(indexName: string): boolean;
|
|
48
62
|
getCheck(checkName: string): CheckDef | undefined;
|
|
49
63
|
hasCheck(checkName: string): boolean;
|
|
64
|
+
getTrigger(triggerName: string): SqlTriggerDef | undefined;
|
|
65
|
+
hasTrigger(triggerName: string): boolean;
|
|
50
66
|
getPrimaryKey(): IndexDef | undefined;
|
|
51
67
|
hasPrimaryKey(): boolean;
|
|
52
68
|
private getForeignKeyDeclaration;
|
|
@@ -62,6 +78,7 @@ export declare class DatabaseTable {
|
|
|
62
78
|
name?: string;
|
|
63
79
|
type?: string;
|
|
64
80
|
expression?: string | IndexCallback<any>;
|
|
81
|
+
where?: string | Dictionary;
|
|
65
82
|
deferMode?: DeferMode | `${DeferMode}`;
|
|
66
83
|
options?: Dictionary;
|
|
67
84
|
columns?: {
|
|
@@ -77,6 +94,8 @@ export declare class DatabaseTable {
|
|
|
77
94
|
disabled?: boolean;
|
|
78
95
|
clustered?: boolean;
|
|
79
96
|
}, type: 'index' | 'unique' | 'primary'): void;
|
|
97
|
+
private processIndexWhere;
|
|
80
98
|
addCheck(check: CheckDef): void;
|
|
99
|
+
addTrigger(trigger: SqlTriggerDef): void;
|
|
81
100
|
toJSON(): Dictionary;
|
|
82
101
|
}
|