@mikro-orm/sql 7.3.0-dev.4 → 7.3.0-dev.6
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/AbstractSqlDriver.js +6 -2
- package/SqlEntityManager.js +2 -1
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +12 -4
- package/package.json +3 -3
- package/plugin/transformer.d.ts +2 -1
- package/plugin/transformer.js +39 -13
- package/query/CriteriaNode.js +1 -1
- package/query/CriteriaNodeFactory.js +1 -1
- package/query/ObjectCriteriaNode.d.ts +2 -0
- package/query/ObjectCriteriaNode.js +28 -3
package/AbstractSqlDriver.js
CHANGED
|
@@ -850,9 +850,13 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
850
850
|
props.forEach(prop => {
|
|
851
851
|
// For STI with conflicting fieldNames, use discriminator to determine which field gets value
|
|
852
852
|
if (prop.stiFieldNames && prop.stiFieldNameMap && meta.discriminatorColumn) {
|
|
853
|
-
|
|
853
|
+
// the map misses subtypes whose props were not initialized yet during discovery (root column owner, grandchildren)
|
|
854
|
+
const discriminator = row[meta.discriminatorColumn];
|
|
855
|
+
const childClass = meta.discriminatorMap?.[discriminator];
|
|
856
|
+
const activeField = prop.stiFieldNameMap[discriminator] ??
|
|
857
|
+
(childClass && this.metadata.find(childClass)?.properties[prop.name]?.fieldNames[0]);
|
|
854
858
|
for (const field of prop.stiFieldNames) {
|
|
855
|
-
params.push(field === activeField ? row[prop.name] : null);
|
|
859
|
+
params.push(field === activeField ? (row[prop.name] ?? null) : null);
|
|
856
860
|
keys.push('?');
|
|
857
861
|
}
|
|
858
862
|
return;
|
package/SqlEntityManager.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EntityManager, raw, Utils, } from '@mikro-orm/core';
|
|
1
|
+
import { EntityManager, QueryFlag, raw, Utils, } from '@mikro-orm/core';
|
|
2
2
|
import { MikroKyselyPlugin } from './plugin/index.js';
|
|
3
3
|
/**
|
|
4
4
|
* @inheritDoc
|
|
@@ -87,6 +87,7 @@ export class SqlEntityManager extends EntityManager {
|
|
|
87
87
|
// match `em.count()` semantics: an active transaction always wins over the requested connection type
|
|
88
88
|
const connectionType = em.getTransactionContext() ? 'write' : options.connectionType;
|
|
89
89
|
const qb = em.createQueryBuilder(meta.class, undefined, connectionType);
|
|
90
|
+
qb.unsetFlag(QueryFlag.CONVERT_CUSTOM_TYPES);
|
|
90
91
|
qb
|
|
91
92
|
.select([...fields, raw('count(*) as cnt')])
|
|
92
93
|
.where(where)
|
|
@@ -387,6 +387,8 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
387
387
|
data_type,
|
|
388
388
|
is_identity,
|
|
389
389
|
identity_generation,
|
|
390
|
+
identity_start,
|
|
391
|
+
identity_increment,
|
|
390
392
|
generation_expression,
|
|
391
393
|
pg_catalog.col_description(pgc.oid, cols.ordinal_position::int) column_comment,
|
|
392
394
|
coll.collname as collation_name
|
|
@@ -433,6 +435,14 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
433
435
|
type += `(${col.numeric_precision},${col.numeric_scale})`;
|
|
434
436
|
}
|
|
435
437
|
const length = this.inferLengthFromColumnType(type) === -1 ? -1 : col.length;
|
|
438
|
+
// a descending sequence starts at -1 by default
|
|
439
|
+
const defaultIdentityStart = col.identity_increment?.startsWith('-') ? '-1' : '1';
|
|
440
|
+
// read back non-default identity options, so `identity (start with 1000)` does not diff against plain `identity`
|
|
441
|
+
const identityOptions = [
|
|
442
|
+
col.identity_start !== defaultIdentityStart && `start with ${col.identity_start}`,
|
|
443
|
+
col.identity_increment !== '1' && `increment by ${col.identity_increment}`,
|
|
444
|
+
].filter(Boolean);
|
|
445
|
+
const identitySuffix = identityOptions.length > 0 ? ` (${identityOptions.join(' ')})` : '';
|
|
436
446
|
const column = {
|
|
437
447
|
name: col.column_name,
|
|
438
448
|
type,
|
|
@@ -445,9 +455,7 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
445
455
|
unsigned: increments,
|
|
446
456
|
autoincrement: increments,
|
|
447
457
|
generated: col.is_identity === 'YES'
|
|
448
|
-
? col.identity_generation === 'BY DEFAULT'
|
|
449
|
-
? 'by default as identity'
|
|
450
|
-
: 'identity'
|
|
458
|
+
? (col.identity_generation === 'BY DEFAULT' ? 'by default as identity' : 'identity') + identitySuffix
|
|
451
459
|
: col.generation_expression
|
|
452
460
|
? col.generation_expression + ' stored'
|
|
453
461
|
: undefined,
|
|
@@ -1032,7 +1040,7 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
1032
1040
|
columnType = this.quote(columnType);
|
|
1033
1041
|
}
|
|
1034
1042
|
}
|
|
1035
|
-
if (column.generated
|
|
1043
|
+
if (column.generated?.startsWith('by default as identity')) {
|
|
1036
1044
|
columnType += ` generated ${column.generated}`;
|
|
1037
1045
|
}
|
|
1038
1046
|
else if (column.generated) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/sql",
|
|
3
|
-
"version": "7.3.0-dev.
|
|
3
|
+
"version": "7.3.0-dev.6",
|
|
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",
|
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
"kysely": "0.29.6"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@mikro-orm/core": "^7.2.
|
|
53
|
+
"@mikro-orm/core": "^7.2.2"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@mikro-orm/core": "7.3.0-dev.
|
|
56
|
+
"@mikro-orm/core": "7.3.0-dev.6"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">= 22.17.0"
|
package/plugin/transformer.d.ts
CHANGED
|
@@ -92,6 +92,7 @@ export declare class MikroTransformer extends OperationNodeTransformer {
|
|
|
92
92
|
* This is called for SELECT queries when columnNamingStrategy is 'property'
|
|
93
93
|
*/
|
|
94
94
|
transformResult(rows: Record<string, any>[] | undefined, entityMap: Map<string, EntityMetadata>): Record<string, any>[] | undefined;
|
|
95
|
+
private getResultMaps;
|
|
95
96
|
buildGlobalFieldMap(entityMap: Map<string, EntityMetadata>): Record<string, EntityProperty>;
|
|
96
97
|
buildGlobalRelationFieldMap(entityMap: Map<string, EntityMetadata>): Record<string, string>;
|
|
97
98
|
/**
|
|
@@ -108,6 +109,6 @@ export declare class MikroTransformer extends OperationNodeTransformer {
|
|
|
108
109
|
/**
|
|
109
110
|
* Transform a single row by mapping column names to property names
|
|
110
111
|
*/
|
|
111
|
-
transformRow(row: Record<string, any>,
|
|
112
|
+
transformRow(row: Record<string, any>, fieldEntries: [string, EntityProperty][], relationEntries: [string, string][]): Record<string, any>;
|
|
112
113
|
prepareOutputValue(prop: EntityProperty | undefined, value: unknown): unknown;
|
|
113
114
|
}
|
package/plugin/transformer.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { ReferenceKind, isRaw, } from '@mikro-orm/core';
|
|
2
2
|
import { AliasNode, ColumnNode, ColumnUpdateNode, IdentifierNode, OperationNodeTransformer, PrimitiveValueListNode, RawNode, ReferenceNode, SchemableIdentifierNode, SelectAllNode, SelectionNode, TableNode, ValueListNode, ValueNode, ValuesNode, } from 'kysely';
|
|
3
|
+
/** Past this many query shapes the cache of one metadata storage starts over, so generated aliases cannot grow it. */
|
|
4
|
+
const RESULT_MAPS_LIMIT = 1000;
|
|
5
|
+
/**
|
|
6
|
+
* Result maps per query shape (the aliases and entities of its entity map). They are derived from metadata alone, so
|
|
7
|
+
* they are built once and shared by every transformer, as `getKysely()` creates a new one per call.
|
|
8
|
+
*/
|
|
9
|
+
const resultMapsCache = new WeakMap();
|
|
3
10
|
const EXPANDABLE_KINDS = new Set([
|
|
4
11
|
ReferenceKind.SCALAR,
|
|
5
12
|
ReferenceKind.EMBEDDED,
|
|
@@ -884,12 +891,7 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
884
891
|
if (byEntity) {
|
|
885
892
|
return byEntity;
|
|
886
893
|
}
|
|
887
|
-
|
|
888
|
-
const byTable = allMetadata.find(m => m.tableName === name);
|
|
889
|
-
if (byTable) {
|
|
890
|
-
return byTable;
|
|
891
|
-
}
|
|
892
|
-
return undefined;
|
|
894
|
+
return this.#metadata.getByTableName(name);
|
|
893
895
|
}
|
|
894
896
|
/**
|
|
895
897
|
* Transform result rows by mapping database column names to property names
|
|
@@ -906,11 +908,35 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
906
908
|
if (entityMap.size === 0) {
|
|
907
909
|
return rows;
|
|
908
910
|
}
|
|
909
|
-
//
|
|
910
|
-
const
|
|
911
|
-
const relationFieldMap = this.buildGlobalRelationFieldMap(entityMap);
|
|
911
|
+
// The global mapping from database field names to property objects, built once per query shape
|
|
912
|
+
const { fields, relations } = this.getResultMaps(entityMap);
|
|
912
913
|
// Transform each row
|
|
913
|
-
return rows.map(row => this.transformRow(row,
|
|
914
|
+
return rows.map(row => this.transformRow(row, fields, relations));
|
|
915
|
+
}
|
|
916
|
+
getResultMaps(entityMap) {
|
|
917
|
+
let byShape = resultMapsCache.get(this.#metadata);
|
|
918
|
+
if (!byShape) {
|
|
919
|
+
byShape = new Map();
|
|
920
|
+
resultMapsCache.set(this.#metadata, byShape);
|
|
921
|
+
}
|
|
922
|
+
let shape = '';
|
|
923
|
+
for (const [alias, meta] of entityMap) {
|
|
924
|
+
shape += `${alias}\0${meta._id}\0`;
|
|
925
|
+
}
|
|
926
|
+
const cached = byShape.get(shape);
|
|
927
|
+
if (cached && Array.from(entityMap.values()).every((meta, i) => meta.props === cached.props[i])) {
|
|
928
|
+
return cached;
|
|
929
|
+
}
|
|
930
|
+
if (byShape.size >= RESULT_MAPS_LIMIT) {
|
|
931
|
+
byShape.clear();
|
|
932
|
+
}
|
|
933
|
+
const maps = {
|
|
934
|
+
props: Array.from(entityMap.values(), meta => meta.props),
|
|
935
|
+
fields: Object.entries(this.buildGlobalFieldMap(entityMap)),
|
|
936
|
+
relations: Object.entries(this.buildGlobalRelationFieldMap(entityMap)),
|
|
937
|
+
};
|
|
938
|
+
byShape.set(shape, maps);
|
|
939
|
+
return maps;
|
|
914
940
|
}
|
|
915
941
|
buildGlobalFieldMap(entityMap) {
|
|
916
942
|
const map = {};
|
|
@@ -986,10 +1012,10 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
986
1012
|
/**
|
|
987
1013
|
* Transform a single row by mapping column names to property names
|
|
988
1014
|
*/
|
|
989
|
-
transformRow(row,
|
|
1015
|
+
transformRow(row, fieldEntries, relationEntries) {
|
|
990
1016
|
const transformed = { ...row };
|
|
991
1017
|
// First pass: map regular fields from fieldName to propertyName and convert values
|
|
992
|
-
for (const [fieldName, prop] of
|
|
1018
|
+
for (const [fieldName, prop] of fieldEntries) {
|
|
993
1019
|
if (!(fieldName in transformed)) {
|
|
994
1020
|
continue;
|
|
995
1021
|
}
|
|
@@ -1011,7 +1037,7 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
1011
1037
|
// Second pass: handle relation fields
|
|
1012
1038
|
// Only run if columnNamingStrategy is 'property', as we don't want to rename FKs otherwise
|
|
1013
1039
|
if (this.#options.columnNamingStrategy === 'property') {
|
|
1014
|
-
for (const [fieldName, relationPropertyName] of
|
|
1040
|
+
for (const [fieldName, relationPropertyName] of relationEntries) {
|
|
1015
1041
|
if (fieldName in transformed && !(relationPropertyName in transformed)) {
|
|
1016
1042
|
// Move the foreign key value to the relation property name
|
|
1017
1043
|
transformed[relationPropertyName] = transformed[fieldName];
|
package/query/CriteriaNode.js
CHANGED
|
@@ -29,7 +29,7 @@ export class CriteriaNode {
|
|
|
29
29
|
}
|
|
30
30
|
for (const k of pks) {
|
|
31
31
|
const [name, target] = QueryHelper.splitPolymorphicKey(k);
|
|
32
|
-
this.prop = meta.props.find(prop => (prop.name === name && (!target ||
|
|
32
|
+
this.prop = meta.props.find(prop => (prop.name === name && (!target || QueryHelper.findTargetMetas(prop, k).length > 0)) ||
|
|
33
33
|
(prop.fieldNames?.length === 1 && prop.fieldNames[0] === k && prop.persist !== false));
|
|
34
34
|
const isProp = this.prop || meta.props.find(prop => (prop.fieldNames || []).includes(k));
|
|
35
35
|
// do not validate if the key is prefixed or type casted (e.g. `k::text`)
|
|
@@ -52,7 +52,7 @@ export class CriteriaNodeFactory {
|
|
|
52
52
|
const rawField = RawQueryFragment.isKnownFragmentSymbol(key);
|
|
53
53
|
const prop = rawField ? null : meta?.properties[QueryHelper.splitPolymorphicKey(key)[0]];
|
|
54
54
|
const target = rawField ? undefined : QueryHelper.findTargetMeta(prop, key);
|
|
55
|
-
const childEntity = prop && prop.kind !== ReferenceKind.SCALAR
|
|
55
|
+
const childEntity = prop && prop.kind !== ReferenceKind.SCALAR && target ? target.class : entityName;
|
|
56
56
|
const isNotEmbedded = rawField || prop?.kind !== ReferenceKind.EMBEDDED;
|
|
57
57
|
const val = payload[key];
|
|
58
58
|
if (isNotEmbedded && prop?.customType instanceof JsonType) {
|
|
@@ -5,6 +5,8 @@ import type { ICriteriaNodeProcessOptions, IQueryBuilder } from '../typings.js';
|
|
|
5
5
|
*/
|
|
6
6
|
export declare class ObjectCriteriaNode<T extends object> extends CriteriaNode<T> {
|
|
7
7
|
process(qb: IQueryBuilder<T>, options?: ICriteriaNodeProcessOptions): any;
|
|
8
|
+
/** Orders by the first non-null value of the property across the joined polymorphic targets. */
|
|
9
|
+
private processPolymorphicOrderBy;
|
|
8
10
|
isStrict(): boolean;
|
|
9
11
|
unwrap(): any;
|
|
10
12
|
willAutoJoin(qb: IQueryBuilder<T>, alias?: string, options?: ICriteriaNodeProcessOptions): boolean;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ALIAS_REPLACEMENT, GroupOperator, QueryFlag, raw, RawQueryFragment, ReferenceKind, Utils, } from '@mikro-orm/core';
|
|
1
|
+
import { ALIAS_REPLACEMENT, GroupOperator, QueryFlag, QueryHelper, raw, RawQueryFragment, ReferenceKind, Utils, } from '@mikro-orm/core';
|
|
2
2
|
import { CriteriaNode } from './CriteriaNode.js';
|
|
3
3
|
import { JoinType, QueryType } from './enums.js';
|
|
4
4
|
const COLLECTION_OPERATORS = ['$some', '$none', '$every', '$size', '$all'];
|
|
@@ -7,6 +7,10 @@ const COLLECTION_OPERATORS = ['$some', '$none', '$every', '$size', '$all'];
|
|
|
7
7
|
*/
|
|
8
8
|
export class ObjectCriteriaNode extends CriteriaNode {
|
|
9
9
|
process(qb, options) {
|
|
10
|
+
const targets = this.isPolymorphicBranch() ? QueryHelper.findTargetMetas(this.prop, this.key) : [];
|
|
11
|
+
if (options?.type === 'orderBy' && targets.length > 1) {
|
|
12
|
+
return this.processPolymorphicOrderBy(qb, targets, options);
|
|
13
|
+
}
|
|
10
14
|
const matchPopulateJoins = options?.matchPopulateJoins ||
|
|
11
15
|
(this.prop && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind));
|
|
12
16
|
const nestedAlias = qb.getAliasForJoinPath(this.getPath(options), { ...options, matchPopulateJoins });
|
|
@@ -150,12 +154,31 @@ export class ObjectCriteriaNode extends CriteriaNode {
|
|
|
150
154
|
return o;
|
|
151
155
|
}, {});
|
|
152
156
|
// the target is left joined, so its condition must only match rows that point to it
|
|
153
|
-
if (this.isPolymorphicBranch() && alias) {
|
|
157
|
+
if (this.isPolymorphicBranch() && alias && options?.type !== 'orderBy') {
|
|
154
158
|
const pk = this.metadata.find(this.entityName).primaryKeys[0];
|
|
155
159
|
this.inlineCondition(`${alias}.${pk}`, ret, { $ne: null });
|
|
156
160
|
}
|
|
157
161
|
return ret;
|
|
158
162
|
}
|
|
163
|
+
/** Orders by the first non-null value of the property across the joined polymorphic targets. */
|
|
164
|
+
processPolymorphicOrderBy(qb, targets, options) {
|
|
165
|
+
const ownerAlias = options.alias || qb.alias;
|
|
166
|
+
const aliases = targets.map(target => {
|
|
167
|
+
const path = `${this.parent.getPath()}.${this.prop.name}[${target.className}]`;
|
|
168
|
+
const alias = qb.getAliasForJoinPath(path, { ...options, matchPopulateJoins: true });
|
|
169
|
+
if (alias) {
|
|
170
|
+
return alias;
|
|
171
|
+
}
|
|
172
|
+
const newAlias = qb.getNextAlias(target.class);
|
|
173
|
+
qb.addPolymorphicJoin(this.prop, target, ownerAlias, newAlias, JoinType.leftJoin, path);
|
|
174
|
+
return newAlias;
|
|
175
|
+
});
|
|
176
|
+
return Utils.getObjectQueryKeys(this.payload).reduce((o, field) => {
|
|
177
|
+
const columns = targets.map((target, i) => `${aliases[i]}.${target.properties[field].fieldNames[0]}`);
|
|
178
|
+
o[raw(`coalesce(${columns.map(() => '??').join(', ')})`, columns)] = this.payload[field].payload;
|
|
179
|
+
return o;
|
|
180
|
+
}, {});
|
|
181
|
+
}
|
|
159
182
|
isStrict() {
|
|
160
183
|
return (this.strict ||
|
|
161
184
|
Utils.getObjectQueryKeys(this.payload).some(key => {
|
|
@@ -345,7 +368,9 @@ export class ObjectCriteriaNode extends CriteriaNode {
|
|
|
345
368
|
else {
|
|
346
369
|
const prev = qb.state.fields?.slice();
|
|
347
370
|
const toOneProperty = [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind);
|
|
348
|
-
|
|
371
|
+
// ordering must not drop rows whose polymorphic relation points to another target
|
|
372
|
+
const orderByPolymorphic = this.prop.polymorphic && options?.type === 'orderBy';
|
|
373
|
+
const joinType = toOneProperty && !this.prop.nullable && !orderByPolymorphic ? JoinType.innerJoin : JoinType.leftJoin;
|
|
349
374
|
qb[method](field, nestedAlias, undefined, joinType, path);
|
|
350
375
|
if (!qb.hasFlag(QueryFlag.INFER_POPULATE)) {
|
|
351
376
|
qb.state.fields = prev;
|