@mikro-orm/sql 7.0.17 → 7.0.18-dev.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 +1 -1
- package/AbstractSqlConnection.js +27 -6
- package/AbstractSqlDriver.d.ts +25 -1
- package/AbstractSqlDriver.js +356 -20
- package/AbstractSqlPlatform.d.ts +13 -2
- package/AbstractSqlPlatform.js +16 -3
- package/PivotCollectionPersister.d.ts +2 -2
- package/PivotCollectionPersister.js +19 -3
- package/README.md +2 -1
- package/SqlEntityManager.d.ts +46 -3
- package/SqlEntityManager.js +77 -7
- package/SqlMikroORM.d.ts +4 -4
- package/dialects/mssql/MsSqlNativeQueryBuilder.js +4 -0
- package/dialects/mysql/BaseMySqlPlatform.d.ts +1 -0
- package/dialects/mysql/BaseMySqlPlatform.js +3 -0
- package/dialects/mysql/MySqlNativeQueryBuilder.js +11 -0
- package/dialects/mysql/MySqlSchemaHelper.d.ts +19 -3
- package/dialects/mysql/MySqlSchemaHelper.js +254 -21
- 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 +8 -0
- package/dialects/postgresql/BasePostgreSqlPlatform.js +50 -0
- package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +38 -1
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +341 -6
- 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/SqliteSchemaHelper.d.ts +7 -1
- package/dialects/sqlite/SqliteSchemaHelper.js +131 -2
- package/package.json +2 -2
- package/query/NativeQueryBuilder.d.ts +6 -0
- package/query/NativeQueryBuilder.js +16 -1
- package/query/QueryBuilder.d.ts +83 -1
- package/query/QueryBuilder.js +181 -8
- package/schema/DatabaseSchema.d.ts +29 -2
- package/schema/DatabaseSchema.js +137 -0
- package/schema/DatabaseTable.d.ts +20 -1
- package/schema/DatabaseTable.js +62 -3
- package/schema/SchemaComparator.d.ts +19 -0
- package/schema/SchemaComparator.js +250 -1
- package/schema/SchemaHelper.d.ts +77 -1
- package/schema/SchemaHelper.js +279 -5
- 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 +69 -2
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 {
|
|
@@ -159,6 +160,41 @@ export class QueryBuilder {
|
|
|
159
160
|
insert(data) {
|
|
160
161
|
return this.init(QueryType.INSERT, data);
|
|
161
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Creates an INSERT ... SELECT query that copies rows from the source query.
|
|
165
|
+
*
|
|
166
|
+
* Column resolution (3 tiers):
|
|
167
|
+
* 1. No explicit select on source, no explicit columns → all cloneable columns derived from entity metadata
|
|
168
|
+
* 2. Explicit select on source, no explicit columns → columns derived from selected field names
|
|
169
|
+
* 3. Explicit `columns` option → user-provided column list
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```ts
|
|
173
|
+
* // Clone all fields (columns auto-derived from metadata)
|
|
174
|
+
* const source = em.createQueryBuilder(User).where({ id: 1 });
|
|
175
|
+
* await em.createQueryBuilder(User).insertFrom(source).execute();
|
|
176
|
+
*
|
|
177
|
+
* // Clone with overrides via raw() aliases
|
|
178
|
+
* const source = em.createQueryBuilder(User)
|
|
179
|
+
* .select(['name', raw("'new@email.com'").as('email')])
|
|
180
|
+
* .where({ id: 1 });
|
|
181
|
+
* await em.createQueryBuilder(User).insertFrom(source).execute();
|
|
182
|
+
*
|
|
183
|
+
* // Explicit columns for full control
|
|
184
|
+
* await em.createQueryBuilder(User)
|
|
185
|
+
* .insertFrom(source, { columns: ['name', 'email'] })
|
|
186
|
+
* .execute();
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
insertFrom(subQuery, options) {
|
|
190
|
+
this.ensureNotFinalized();
|
|
191
|
+
this.#state.type = QueryType.INSERT;
|
|
192
|
+
this.#state.insertSubQuery = subQuery;
|
|
193
|
+
if (options?.columns) {
|
|
194
|
+
this.#state.insertColumns = Utils.asArray(options.columns);
|
|
195
|
+
}
|
|
196
|
+
return this;
|
|
197
|
+
}
|
|
162
198
|
/**
|
|
163
199
|
* Creates an UPDATE query with the given data.
|
|
164
200
|
* Use `where()` to specify which rows to update.
|
|
@@ -686,6 +722,12 @@ export class QueryBuilder {
|
|
|
686
722
|
hasFlag(flag) {
|
|
687
723
|
return this.#state.flags.has(flag);
|
|
688
724
|
}
|
|
725
|
+
/** @internal */
|
|
726
|
+
setPartitionLimit(opts) {
|
|
727
|
+
this.ensureNotFinalized();
|
|
728
|
+
this.#state.partitionLimit = opts;
|
|
729
|
+
return this;
|
|
730
|
+
}
|
|
689
731
|
cache(config = true) {
|
|
690
732
|
this.ensureNotFinalized();
|
|
691
733
|
this.#state.cache = config;
|
|
@@ -789,12 +831,18 @@ export class QueryBuilder {
|
|
|
789
831
|
if (this.#state.lockMode) {
|
|
790
832
|
this.helper.getLockSQL(qb, this.#state.lockMode, this.#state.lockTables, this.#state.joins);
|
|
791
833
|
}
|
|
792
|
-
this.processReturningStatement(qb, this.mainAlias.meta, this.#state.data, this.#state.returning);
|
|
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
|
+
}
|
|
793
838
|
return (this.#query.qb = qb);
|
|
794
839
|
}
|
|
795
840
|
processReturningStatement(qb, meta, data, returning) {
|
|
796
841
|
const usesReturningStatement = this.platform.usesReturningStatement() || this.platform.usesOutputStatement();
|
|
797
|
-
if (!meta || !
|
|
842
|
+
if (!meta || !usesReturningStatement) {
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
if (!data && !this.#state.insertSubQuery) {
|
|
798
846
|
return;
|
|
799
847
|
}
|
|
800
848
|
// always respect explicit returning hint
|
|
@@ -805,13 +853,13 @@ export class QueryBuilder {
|
|
|
805
853
|
if (this.type === QueryType.INSERT) {
|
|
806
854
|
const returningProps = meta.hydrateProps
|
|
807
855
|
.filter(prop => prop.returning || (prop.persist !== false && ((prop.primary && prop.autoincrement) || prop.defaultRaw)))
|
|
808
|
-
.filter(prop => !(prop.name in data));
|
|
856
|
+
.filter(prop => !data || !(prop.name in data));
|
|
809
857
|
if (returningProps.length > 0) {
|
|
810
858
|
qb.returning(Utils.flatten(returningProps.map(prop => prop.fieldNames)));
|
|
811
859
|
}
|
|
812
860
|
return;
|
|
813
861
|
}
|
|
814
|
-
if (this.type === QueryType.UPDATE) {
|
|
862
|
+
if (this.type === QueryType.UPDATE && data) {
|
|
815
863
|
const returningProps = meta.hydrateProps.filter(prop => prop.fieldNames && isRaw(data[prop.fieldNames[0]]));
|
|
816
864
|
if (returningProps.length > 0) {
|
|
817
865
|
qb.returning(returningProps.flatMap((prop) => {
|
|
@@ -963,7 +1011,7 @@ export class QueryBuilder {
|
|
|
963
1011
|
if (cached?.data !== undefined) {
|
|
964
1012
|
return cached.data;
|
|
965
1013
|
}
|
|
966
|
-
const loggerContext = { id: this.em?.id, ...this.loggerContext };
|
|
1014
|
+
const loggerContext = { id: this.em?.id, ...this.loggerContext, ...this.#abortOptions };
|
|
967
1015
|
const res = await this.getConnection().execute(query.sql, query.params, method, this.context, loggerContext);
|
|
968
1016
|
const meta = this.mainAlias.meta;
|
|
969
1017
|
if (!options.mapResults || !meta) {
|
|
@@ -1017,9 +1065,10 @@ export class QueryBuilder {
|
|
|
1017
1065
|
options ??= {};
|
|
1018
1066
|
options.mergeResults ??= true;
|
|
1019
1067
|
options.mapResults ??= true;
|
|
1068
|
+
const chunkSize = options.chunkSize ?? 100;
|
|
1020
1069
|
const query = this.toQuery();
|
|
1021
|
-
const loggerContext = { id: this.em?.id, ...this.loggerContext };
|
|
1022
|
-
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);
|
|
1023
1072
|
const meta = this.mainAlias.meta;
|
|
1024
1073
|
if (options.rawResults || !meta) {
|
|
1025
1074
|
yield* res;
|
|
@@ -1242,6 +1291,7 @@ export class QueryBuilder {
|
|
|
1242
1291
|
qb.#state.finalized = false;
|
|
1243
1292
|
qb.#query = undefined;
|
|
1244
1293
|
qb.#helper = qb.createQueryBuilderHelper();
|
|
1294
|
+
qb.#abortOptions = this.#abortOptions;
|
|
1245
1295
|
return qb;
|
|
1246
1296
|
}
|
|
1247
1297
|
/**
|
|
@@ -1257,6 +1307,13 @@ export class QueryBuilder {
|
|
|
1257
1307
|
this.loggerContext ??= {};
|
|
1258
1308
|
return this.loggerContext;
|
|
1259
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
|
+
}
|
|
1260
1317
|
fromVirtual(meta) {
|
|
1261
1318
|
if (typeof meta.expression === 'string') {
|
|
1262
1319
|
return `(${meta.expression}) as ${this.platform.quoteIdentifier(this.alias)}`;
|
|
@@ -1618,7 +1675,14 @@ export class QueryBuilder {
|
|
|
1618
1675
|
break;
|
|
1619
1676
|
}
|
|
1620
1677
|
case QueryType.INSERT:
|
|
1621
|
-
|
|
1678
|
+
if (this.#state.insertSubQuery) {
|
|
1679
|
+
const columns = this.resolveInsertFromColumns();
|
|
1680
|
+
const compiled = this.#state.insertSubQuery.toQuery();
|
|
1681
|
+
qb.insertSelect(columns, raw(compiled.sql, compiled.params));
|
|
1682
|
+
}
|
|
1683
|
+
else {
|
|
1684
|
+
qb.insert(this.#state.data);
|
|
1685
|
+
}
|
|
1622
1686
|
break;
|
|
1623
1687
|
case QueryType.UPDATE:
|
|
1624
1688
|
qb.update(this.#state.data);
|
|
@@ -1634,6 +1698,78 @@ export class QueryBuilder {
|
|
|
1634
1698
|
}
|
|
1635
1699
|
return qb;
|
|
1636
1700
|
}
|
|
1701
|
+
/**
|
|
1702
|
+
* Resolves the INSERT column list for `insertFrom()`.
|
|
1703
|
+
*
|
|
1704
|
+
* Tier 1: Explicit `insertColumns` from `options.columns` → map property names to field names
|
|
1705
|
+
* Tier 2: Source QB has explicit select fields → derive from those
|
|
1706
|
+
* Tier 3: Derive from target entity metadata (all cloneable columns), auto-populate source select
|
|
1707
|
+
*/
|
|
1708
|
+
resolveInsertFromColumns() {
|
|
1709
|
+
const meta = this.mainAlias.meta;
|
|
1710
|
+
const subQuery = this.#state.insertSubQuery;
|
|
1711
|
+
// Tier 1: explicit columns
|
|
1712
|
+
if (this.#state.insertColumns?.length) {
|
|
1713
|
+
return this.#state.insertColumns.flatMap(col => {
|
|
1714
|
+
const prop = meta?.properties[col];
|
|
1715
|
+
return prop?.fieldNames ?? [col];
|
|
1716
|
+
});
|
|
1717
|
+
}
|
|
1718
|
+
// Tier 2: source QB has explicit select fields
|
|
1719
|
+
const sourceFields = subQuery.state.fields;
|
|
1720
|
+
if (sourceFields && subQuery.state.type === QueryType.SELECT) {
|
|
1721
|
+
return sourceFields
|
|
1722
|
+
.filter((field) => typeof field === 'string' || isRaw(field))
|
|
1723
|
+
.flatMap((field) => {
|
|
1724
|
+
if (typeof field === 'string') {
|
|
1725
|
+
// Strip alias prefix like 'a0.'
|
|
1726
|
+
const bare = field.replace(/^\w+\./, '');
|
|
1727
|
+
const prop = meta?.properties[bare];
|
|
1728
|
+
return prop?.fieldNames ?? [bare];
|
|
1729
|
+
}
|
|
1730
|
+
// RawQueryFragment with alias: raw('...').as('name')
|
|
1731
|
+
const alias = String(field.params[field.params.length - 1]);
|
|
1732
|
+
const prop = meta?.properties[alias];
|
|
1733
|
+
return prop?.fieldNames ?? [alias];
|
|
1734
|
+
});
|
|
1735
|
+
}
|
|
1736
|
+
// Tier 3: derive from metadata — all cloneable columns
|
|
1737
|
+
const cloneableProps = this.getCloneableProps(meta);
|
|
1738
|
+
const selectFields = [];
|
|
1739
|
+
const columns = [];
|
|
1740
|
+
for (const prop of cloneableProps) {
|
|
1741
|
+
for (const fieldName of prop.fieldNames) {
|
|
1742
|
+
columns.push(fieldName);
|
|
1743
|
+
selectFields.push(fieldName);
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
// Auto-populate source select with matching fields
|
|
1747
|
+
if (!sourceFields) {
|
|
1748
|
+
subQuery.select(selectFields);
|
|
1749
|
+
}
|
|
1750
|
+
return columns;
|
|
1751
|
+
}
|
|
1752
|
+
/** Returns properties that are safe to clone (persistable, non-PK, non-generated). */
|
|
1753
|
+
getCloneableProps(meta) {
|
|
1754
|
+
return meta.props.filter(prop => {
|
|
1755
|
+
if (prop.persist === false) {
|
|
1756
|
+
return false;
|
|
1757
|
+
}
|
|
1758
|
+
if (prop.primary) {
|
|
1759
|
+
return false;
|
|
1760
|
+
}
|
|
1761
|
+
if (!prop.fieldNames?.length) {
|
|
1762
|
+
return false;
|
|
1763
|
+
}
|
|
1764
|
+
if ([ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(prop.kind)) {
|
|
1765
|
+
return false;
|
|
1766
|
+
}
|
|
1767
|
+
if (prop.kind === ReferenceKind.EMBEDDED && !prop.object) {
|
|
1768
|
+
return false;
|
|
1769
|
+
}
|
|
1770
|
+
return true;
|
|
1771
|
+
});
|
|
1772
|
+
}
|
|
1637
1773
|
applyDiscriminatorCondition() {
|
|
1638
1774
|
const meta = this.mainAlias.meta;
|
|
1639
1775
|
if (meta.root.inheritanceType !== 'sti' || !meta.discriminatorValue) {
|
|
@@ -1797,6 +1933,9 @@ export class QueryBuilder {
|
|
|
1797
1933
|
(this.#state.limit > 0 || this.#state.offset > 0)) {
|
|
1798
1934
|
this.wrapPaginateSubQuery(meta);
|
|
1799
1935
|
}
|
|
1936
|
+
if (this.#state.partitionLimit) {
|
|
1937
|
+
this.preparePartitionLimit();
|
|
1938
|
+
}
|
|
1800
1939
|
if (meta &&
|
|
1801
1940
|
(this.#state.flags.has(QueryFlag.UPDATE_SUB_QUERY) || this.#state.flags.has(QueryFlag.DELETE_SUB_QUERY))) {
|
|
1802
1941
|
this.wrapModifySubQuery(meta);
|
|
@@ -2016,6 +2155,40 @@ export class QueryBuilder {
|
|
|
2016
2155
|
[Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: raw(sql, params) },
|
|
2017
2156
|
});
|
|
2018
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
|
+
}
|
|
2019
2192
|
/**
|
|
2020
2193
|
* Computes the set of populate paths from the _populate hints.
|
|
2021
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;
|
|
@@ -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
|
}
|