@mikro-orm/core 7.2.2-dev.0 → 7.2.2-dev.10
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/EntityManager.js +50 -10
- package/entity/defineEntity.d.ts +1 -1
- package/entity/defineEntity.js +1 -1
- package/metadata/MetadataDiscovery.d.ts +2 -0
- package/metadata/MetadataDiscovery.js +11 -0
- package/metadata/types.d.ts +1 -1
- package/package.json +1 -1
- package/utils/QueryHelper.d.ts +2 -0
- package/utils/QueryHelper.js +20 -3
- package/utils/Utils.js +1 -1
- package/utils/upsert-utils.d.ts +2 -1
- package/utils/upsert-utils.js +9 -5
package/EntityManager.js
CHANGED
|
@@ -889,7 +889,7 @@ export class EntityManager {
|
|
|
889
889
|
const name = Utils.className(entityName);
|
|
890
890
|
/* v8 ignore next */
|
|
891
891
|
where = Utils.isEntity(where) ? helper(where).getPrimaryKey() : where;
|
|
892
|
-
throw failHandler(name, where);
|
|
892
|
+
throw failHandler(name, QueryHelper.processParams(where));
|
|
893
893
|
}
|
|
894
894
|
return entity;
|
|
895
895
|
}
|
|
@@ -994,10 +994,14 @@ export class EntityManager {
|
|
|
994
994
|
});
|
|
995
995
|
const uniqueFields = options.onConflictFields ??
|
|
996
996
|
(Utils.isPlainObject(where) ? Object.keys(where) : meta.primaryKeys);
|
|
997
|
-
const
|
|
997
|
+
const platform = this.getPlatform();
|
|
998
|
+
const returning = getOnConflictReturningFields(meta, data, uniqueFields, options, platform);
|
|
999
|
+
if (options.onConflictWhere && !ret.row) {
|
|
1000
|
+
returning.push(...Object.keys(data));
|
|
1001
|
+
}
|
|
998
1002
|
if (options.onConflictAction === 'ignore' ||
|
|
999
1003
|
!helper(entity).hasPrimaryKey() ||
|
|
1000
|
-
(returning.length > 0 && !(
|
|
1004
|
+
(returning.length > 0 && !((platform.usesReturningStatement() || platform.usesOutputStatement()) && ret.row))) {
|
|
1001
1005
|
const where = {};
|
|
1002
1006
|
if (Array.isArray(uniqueFields)) {
|
|
1003
1007
|
for (const prop of uniqueFields) {
|
|
@@ -1088,6 +1092,32 @@ export class EntityManager {
|
|
|
1088
1092
|
}
|
|
1089
1093
|
return ret;
|
|
1090
1094
|
}
|
|
1095
|
+
// merge fields are derived from the first row, rows of a different shape need their own statement
|
|
1096
|
+
if (!options.onConflictMergeFields && options.onConflictAction !== 'ignore') {
|
|
1097
|
+
const pks = this.metadata.get(entityName).primaryKeys;
|
|
1098
|
+
const shapes = new Map();
|
|
1099
|
+
data.forEach((row, i) => {
|
|
1100
|
+
// entity instances carry their unset properties as `undefined` keys, while in plain rows those are merged as `null`
|
|
1101
|
+
const shape = Object.keys(row)
|
|
1102
|
+
.filter(key => !pks.includes(key) && !(Utils.isEntity(row) && row[key] === undefined))
|
|
1103
|
+
.sort()
|
|
1104
|
+
.join();
|
|
1105
|
+
const idx = shapes.get(shape) ?? [];
|
|
1106
|
+
shapes.set(shape, idx);
|
|
1107
|
+
idx.push(i);
|
|
1108
|
+
});
|
|
1109
|
+
if (shapes.size > 1) {
|
|
1110
|
+
const ret = [];
|
|
1111
|
+
for (const idx of shapes.values()) {
|
|
1112
|
+
const res = await this.upsertMany(entityName, idx.map(i => data[i]), options);
|
|
1113
|
+
idx.forEach((i, j) => (ret[i] = res[j]));
|
|
1114
|
+
}
|
|
1115
|
+
// rows resolving to the same entity are returned once, as in the single statement path
|
|
1116
|
+
return [...new Set(ret.filter(entity => entity))];
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
// the rows are replaced with their processed copies below, keep the caller's array intact
|
|
1120
|
+
data = [...data];
|
|
1091
1121
|
const meta = this.metadata.get(entityName);
|
|
1092
1122
|
const convertCustomTypes = !Utils.isEntity(data[0]);
|
|
1093
1123
|
const generatedFields = new Set();
|
|
@@ -1147,6 +1177,10 @@ export class EntityManager {
|
|
|
1147
1177
|
platform: this.getPlatform(),
|
|
1148
1178
|
});
|
|
1149
1179
|
row = QueryHelper.processObjectParams(row);
|
|
1180
|
+
// hooks and the result merging need `data[i]` to be the same object as the processed row
|
|
1181
|
+
if (!Utils.isEntity(data[i])) {
|
|
1182
|
+
data[i] = row;
|
|
1183
|
+
}
|
|
1150
1184
|
validateParams(row, 'insert data');
|
|
1151
1185
|
allData.push(row);
|
|
1152
1186
|
allWhere.push(where);
|
|
@@ -1177,9 +1211,9 @@ export class EntityManager {
|
|
|
1177
1211
|
entitiesByData.clear();
|
|
1178
1212
|
const loadPK = new Map();
|
|
1179
1213
|
allData.forEach((row, i) => {
|
|
1180
|
-
em.#unitOfWork
|
|
1181
|
-
|
|
1182
|
-
|
|
1214
|
+
em.#unitOfWork.getChangeSetPersister().mapReturnedValues(Utils.isEntity(data[i]) ? data[i] : null, Utils.isEntity(data[i]) ? {} : data[i],
|
|
1215
|
+
// A suppressed conflict omits its row; positional mapping would hydrate a different entity.
|
|
1216
|
+
res.rows?.length === allData.length ? res.rows[i] : undefined, meta, true);
|
|
1183
1217
|
const entity = Utils.isEntity(data[i])
|
|
1184
1218
|
? data[i]
|
|
1185
1219
|
: em.#entityFactory.create(entityName, row, {
|
|
@@ -1197,8 +1231,13 @@ export class EntityManager {
|
|
|
1197
1231
|
// (onConflictWhere can suppress writes, leaving some rows out)
|
|
1198
1232
|
// oxfmt-ignore
|
|
1199
1233
|
const uniqueFields = options.onConflictFields ?? (Utils.isPlainObject(allWhere[0]) ? Object.keys(allWhere[0]).flatMap(key => Utils.splitPrimaryKeys(key)) : meta.primaryKeys);
|
|
1200
|
-
const
|
|
1201
|
-
const
|
|
1234
|
+
const platform = this.getPlatform();
|
|
1235
|
+
const returning = getOnConflictReturningFields(meta, data[0], uniqueFields, options, platform);
|
|
1236
|
+
if (options.onConflictWhere && res.rows?.length !== allData.length) {
|
|
1237
|
+
returning.push(...meta.comparableProps.filter(p => !p.lazy && !p.embeddable).map(p => p.name));
|
|
1238
|
+
}
|
|
1239
|
+
const reloadFields = returning.length > 0 &&
|
|
1240
|
+
!((platform.usesReturningStatement() || platform.usesOutputStatement()) && res.rows?.length === data.length);
|
|
1202
1241
|
if (options.onConflictAction === 'ignore' || (!res.rows?.length && loadPK.size > 0) || reloadFields) {
|
|
1203
1242
|
const unique = meta.hydrateProps.filter(p => !p.lazy).map(p => p.name);
|
|
1204
1243
|
const add = new Set(propIndex !== false && propIndex >= 0
|
|
@@ -1229,8 +1268,9 @@ export class EntityManager {
|
|
|
1229
1268
|
const row = data2.find(row => {
|
|
1230
1269
|
const tmp = {};
|
|
1231
1270
|
add.forEach(k => {
|
|
1232
|
-
|
|
1233
|
-
|
|
1271
|
+
const prop = meta.properties[k];
|
|
1272
|
+
if (!prop?.primary) {
|
|
1273
|
+
tmp[k] = prop?.customType ? prop.customType.convertToDatabaseValue(row[k], this.getPlatform()) : row[k];
|
|
1234
1274
|
}
|
|
1235
1275
|
});
|
|
1236
1276
|
return this.#comparator.matching(entityName, cond, tmp);
|
package/entity/defineEntity.d.ts
CHANGED
|
@@ -267,7 +267,7 @@ export declare class UniversalPropertyOptionsBuilder<in out Value, in out Option
|
|
|
267
267
|
autoincrement: false;
|
|
268
268
|
}, IncludeKeys>, IncludeKeys>;
|
|
269
269
|
/**
|
|
270
|
-
* Add the property to the `returning`
|
|
270
|
+
* Add the property to the `returning` clause of inserts, updates and upserts, reloading the database value into the entity.
|
|
271
271
|
*/
|
|
272
272
|
returning(returning?: boolean): Pick<UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys>, IncludeKeys>;
|
|
273
273
|
/**
|
package/entity/defineEntity.js
CHANGED
|
@@ -88,7 +88,7 @@ export class UniversalPropertyOptionsBuilder {
|
|
|
88
88
|
return this.assignOptions({ autoincrement });
|
|
89
89
|
}
|
|
90
90
|
/**
|
|
91
|
-
* Add the property to the `returning`
|
|
91
|
+
* Add the property to the `returning` clause of inserts, updates and upserts, reloading the database value into the entity.
|
|
92
92
|
*/
|
|
93
93
|
returning(returning = true) {
|
|
94
94
|
return this.assignOptions({ returning });
|
|
@@ -128,6 +128,8 @@ export declare class MetadataDiscovery {
|
|
|
128
128
|
private initRelation;
|
|
129
129
|
/** Resolves the `through` option of a virtual to-one relation into a read-only formula property. */
|
|
130
130
|
private initThroughRelation;
|
|
131
|
+
/** `returning` is only honoured for physical columns that can be hydrated back into the entity. */
|
|
132
|
+
private initReturning;
|
|
131
133
|
private initColumnType;
|
|
132
134
|
private getMappedType;
|
|
133
135
|
private getPrefix;
|
|
@@ -174,6 +174,7 @@ export class MetadataDiscovery {
|
|
|
174
174
|
this.initRelation(p);
|
|
175
175
|
this.initThroughRelation(m, p);
|
|
176
176
|
this.initColumnType(p);
|
|
177
|
+
this.initReturning(p);
|
|
177
178
|
});
|
|
178
179
|
forEachProp((m, p) => this.initIndexes(m, p));
|
|
179
180
|
filtered.forEach(meta => this.autoWireBidirectionalProperties(meta));
|
|
@@ -2129,6 +2130,16 @@ export class MetadataDiscovery {
|
|
|
2129
2130
|
prop.persist = false;
|
|
2130
2131
|
prop.formula = columns => this.#platform.getThroughRelationFormula(prop, columns);
|
|
2131
2132
|
}
|
|
2133
|
+
/** `returning` is only honoured for physical columns that can be hydrated back into the entity. */
|
|
2134
|
+
initReturning(prop) {
|
|
2135
|
+
const owning = prop.kind === ReferenceKind.SCALAR ||
|
|
2136
|
+
prop.kind === ReferenceKind.MANY_TO_ONE ||
|
|
2137
|
+
(prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner) ||
|
|
2138
|
+
(prop.kind === ReferenceKind.EMBEDDED && prop.object);
|
|
2139
|
+
if (prop.returning && (!owning || prop.persist === false || prop.hydrate === false || prop.formula)) {
|
|
2140
|
+
prop.returning = false;
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2132
2143
|
initColumnType(prop) {
|
|
2133
2144
|
this.initUnsigned(prop);
|
|
2134
2145
|
// Get the target properties for FK relations - use targetKey property if specified, otherwise PKs
|
package/metadata/types.d.ts
CHANGED
|
@@ -175,7 +175,7 @@ export interface PropertyOptions<Owner> {
|
|
|
175
175
|
*/
|
|
176
176
|
autoincrement?: boolean;
|
|
177
177
|
/**
|
|
178
|
-
* Add the property to the `returning`
|
|
178
|
+
* Add the property to the `returning` clause of inserts, updates and upserts, reloading the database value into the entity.
|
|
179
179
|
*/
|
|
180
180
|
returning?: boolean;
|
|
181
181
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/core",
|
|
3
|
-
"version": "7.2.2-dev.
|
|
3
|
+
"version": "7.2.2-dev.10",
|
|
4
4
|
"description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"data-mapper",
|
package/utils/QueryHelper.d.ts
CHANGED
|
@@ -35,6 +35,8 @@ export declare class QueryHelper {
|
|
|
35
35
|
*/
|
|
36
36
|
static liftGroupOperators<T extends object>(where: Dictionary, meta: EntityMetadata<T>, metadata: MetadataStorage, key?: string): string | undefined;
|
|
37
37
|
static inlinePrimaryKeyObjects<T extends object>(where: Dictionary, meta: EntityMetadata<T>, metadata: MetadataStorage, key?: string): boolean;
|
|
38
|
+
/** Copies the plain object and array structure of the condition, keeping the leaf values (e.g. entities) by reference. */
|
|
39
|
+
private static cloneWhere;
|
|
38
40
|
static processWhere<T extends object>(options: ProcessWhereOptions<T>): FilterQuery<T>;
|
|
39
41
|
static getActiveFilters<T>(meta: EntityMetadata<T>, options: FilterOptions | undefined, filters: Dictionary<FilterDef>): FilterDef[];
|
|
40
42
|
/** @internal Sentinel wrapping for arguments accessed while statically resolving an `rls` filter condition. */
|
package/utils/QueryHelper.js
CHANGED
|
@@ -56,15 +56,16 @@ export class QueryHelper {
|
|
|
56
56
|
return params.map(item => QueryHelper.processParams(item));
|
|
57
57
|
}
|
|
58
58
|
if (Utils.isPlainObject(params)) {
|
|
59
|
-
QueryHelper.processObjectParams(params);
|
|
59
|
+
return QueryHelper.processObjectParams(params);
|
|
60
60
|
}
|
|
61
61
|
return params;
|
|
62
62
|
}
|
|
63
63
|
static processObjectParams(params = {}) {
|
|
64
|
+
const ret = (Array.isArray(params) ? [...params] : { ...params });
|
|
64
65
|
Utils.getObjectQueryKeys(params).forEach(k => {
|
|
65
|
-
|
|
66
|
+
ret[k] = QueryHelper.processParams(params[k]);
|
|
66
67
|
});
|
|
67
|
-
return
|
|
68
|
+
return ret;
|
|
68
69
|
}
|
|
69
70
|
/**
|
|
70
71
|
* converts `{ account: { $or: [ [Object], [Object] ] } }`
|
|
@@ -167,10 +168,26 @@ export class QueryHelper {
|
|
|
167
168
|
});
|
|
168
169
|
return false;
|
|
169
170
|
}
|
|
171
|
+
/** Copies the plain object and array structure of the condition, keeping the leaf values (e.g. entities) by reference. */
|
|
172
|
+
static cloneWhere(where) {
|
|
173
|
+
if (Array.isArray(where)) {
|
|
174
|
+
return where.map(item => QueryHelper.cloneWhere(item));
|
|
175
|
+
}
|
|
176
|
+
if (!Utils.isPlainObject(where)) {
|
|
177
|
+
return where;
|
|
178
|
+
}
|
|
179
|
+
const ret = {};
|
|
180
|
+
Utils.getObjectQueryKeys(where).forEach(k => (ret[k] = QueryHelper.cloneWhere(where[k])));
|
|
181
|
+
return ret;
|
|
182
|
+
}
|
|
170
183
|
static processWhere(options) {
|
|
171
184
|
// eslint-disable-next-line prefer-const
|
|
172
185
|
let { where, entityName, metadata, platform, aliased = true, convertCustomTypes = true, root = true } = options;
|
|
173
186
|
const meta = metadata.find(entityName);
|
|
187
|
+
// the condition is normalized in place, so work on a copy to keep the one the caller passed in intact
|
|
188
|
+
if (root) {
|
|
189
|
+
where = QueryHelper.cloneWhere(where);
|
|
190
|
+
}
|
|
174
191
|
// inline PK-only objects in M:N queries, so we don't join the target entity when not needed
|
|
175
192
|
if (meta && root) {
|
|
176
193
|
QueryHelper.liftGroupOperators(where, meta, metadata);
|
package/utils/Utils.js
CHANGED
|
@@ -153,7 +153,7 @@ export function parseJsonSafe(value) {
|
|
|
153
153
|
/** Collection of general-purpose utility methods used throughout the ORM. */
|
|
154
154
|
export class Utils {
|
|
155
155
|
static PK_SEPARATOR = '~~~';
|
|
156
|
-
static #ORM_VERSION = '7.2.2-dev.
|
|
156
|
+
static #ORM_VERSION = '7.2.2-dev.10';
|
|
157
157
|
/** Default session variable name backing an RLS filter argument (`current_setting('mikro.<filter>.<arg>')`). */
|
|
158
158
|
static getRlsSettingName(filterName, argName) {
|
|
159
159
|
return `mikro.${filterName}.${argName}`;
|
package/utils/upsert-utils.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { EntityData, EntityKey, EntityMetadata, FilterQuery } from '../typings.js';
|
|
2
2
|
import type { UpsertOptions } from '../drivers/IDatabaseDriver.js';
|
|
3
3
|
import { type Raw } from '../utils/RawQueryFragment.js';
|
|
4
|
+
import type { Platform } from '../platforms/Platform.js';
|
|
4
5
|
/** @internal */
|
|
5
6
|
export declare function getOnConflictFields<T>(meta: EntityMetadata<T> | undefined, data: EntityData<T>, uniqueFields: (keyof T)[] | Raw, options: UpsertOptions<T>): (keyof T)[];
|
|
6
7
|
/**
|
|
@@ -12,7 +13,7 @@ export declare function getOnConflictFields<T>(meta: EntityMetadata<T> | undefin
|
|
|
12
13
|
*/
|
|
13
14
|
export declare function getOnCreateGeneratedFields<T extends object>(meta: EntityMetadata<T>, data: T | EntityData<T>): EntityKey<T>[];
|
|
14
15
|
/** @internal */
|
|
15
|
-
export declare function getOnConflictReturningFields<T, P extends string>(meta: EntityMetadata<T> | undefined, data: EntityData<T>, uniqueFields: (keyof T)[] | Raw, options: UpsertOptions<T, P
|
|
16
|
+
export declare function getOnConflictReturningFields<T, P extends string>(meta: EntityMetadata<T> | undefined, data: EntityData<T>, uniqueFields: (keyof T)[] | Raw, options: UpsertOptions<T, P>, platform?: Platform): (keyof T)[] | '*';
|
|
16
17
|
/** @internal */
|
|
17
18
|
export declare function getWhereCondition<T extends object>(meta: EntityMetadata<T>, onConflictFields: (keyof T)[] | Raw | undefined, data: EntityData<T>, where: FilterQuery<T>): {
|
|
18
19
|
where: FilterQuery<T>;
|
package/utils/upsert-utils.js
CHANGED
|
@@ -89,11 +89,15 @@ export function getOnCreateGeneratedFields(meta, data) {
|
|
|
89
89
|
.map(prop => prop.name);
|
|
90
90
|
}
|
|
91
91
|
/** @internal */
|
|
92
|
-
export function getOnConflictReturningFields(meta, data, uniqueFields, options) {
|
|
92
|
+
export function getOnConflictReturningFields(meta, data, uniqueFields, options, platform) {
|
|
93
93
|
/* v8 ignore next */
|
|
94
94
|
if (!meta) {
|
|
95
95
|
return '*';
|
|
96
96
|
}
|
|
97
|
+
// props with explicit `returning` hint are reloaded even when supplied, as the database might normalize them
|
|
98
|
+
const returning = platform?.usesReturningStatement() || platform?.usesOutputStatement()
|
|
99
|
+
? meta.props.filter(p => p.returning).map(p => p.name)
|
|
100
|
+
: [];
|
|
97
101
|
const keys = meta.comparableProps
|
|
98
102
|
.filter(p => {
|
|
99
103
|
if (p.lazy || p.embeddable) {
|
|
@@ -112,17 +116,17 @@ export function getOnConflictReturningFields(meta, data, uniqueFields, options)
|
|
|
112
116
|
keys.push(meta.versionProperty);
|
|
113
117
|
}
|
|
114
118
|
if (options.onConflictAction === 'ignore') {
|
|
115
|
-
return keys;
|
|
119
|
+
return Utils.unique([...keys, ...returning]);
|
|
116
120
|
}
|
|
117
121
|
if (options.onConflictMergeFields) {
|
|
118
122
|
const onConflictMergeFields = expandFields(meta, options.onConflictMergeFields);
|
|
119
|
-
return keys.filter(key => !onConflictMergeFields.includes(key));
|
|
123
|
+
return Utils.unique([...keys.filter(key => !onConflictMergeFields.includes(key)), ...returning]);
|
|
120
124
|
}
|
|
121
125
|
if (options.onConflictExcludeFields) {
|
|
122
126
|
const onConflictExcludeFields = expandFields(meta, options.onConflictExcludeFields);
|
|
123
|
-
return [...
|
|
127
|
+
return Utils.unique([...keys, ...onConflictExcludeFields, ...returning]);
|
|
124
128
|
}
|
|
125
|
-
return keys.filter(key => !(key in data));
|
|
129
|
+
return Utils.unique([...keys.filter(key => !(key in data)), ...returning]);
|
|
126
130
|
}
|
|
127
131
|
function getPropertyValue(obj, key) {
|
|
128
132
|
if (!key.includes('.')) {
|