@mikro-orm/sql 7.1.6-dev.8 → 7.1.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 +5 -0
- package/dialects/mysql/BaseMySqlPlatform.d.ts +4 -4
- package/dialects/mysql/MySqlSchemaHelper.d.ts +1 -0
- package/dialects/mysql/MySqlSchemaHelper.js +1 -0
- package/package.json +4 -4
- package/query/CriteriaNode.d.ts +2 -2
- package/query/QueryBuilder.d.ts +18 -2
- package/query/QueryBuilder.js +82 -34
- package/schema/DatabaseSchema.js +6 -0
- package/schema/SchemaComparator.js +2 -1
- package/typings.d.ts +4 -1
package/AbstractSqlDriver.js
CHANGED
|
@@ -494,6 +494,11 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
494
494
|
if (prop.fieldNames.every(name => typeof root[`${relationAlias}__${name}`] === 'undefined')) {
|
|
495
495
|
return;
|
|
496
496
|
}
|
|
497
|
+
// inline embeddables are mapped via their flattened child props; mapping the embedded root prop
|
|
498
|
+
// here would leak a raw column value into the snapshot and produce a phantom changeset
|
|
499
|
+
if (prop.kind === ReferenceKind.EMBEDDED && !prop.object && !meta.embeddable) {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
497
502
|
if (prop.polymorphic && prop.kind !== ReferenceKind.EMBEDDED) {
|
|
498
503
|
const discriminatorAlias = `${relationAlias}__${prop.fieldNames[0]}`;
|
|
499
504
|
const discriminatorValue = root[discriminatorAlias];
|
|
@@ -9,10 +9,10 @@ export declare class BaseMySqlPlatform extends AbstractSqlPlatform {
|
|
|
9
9
|
protected readonly schemaHelper: MySqlSchemaHelper;
|
|
10
10
|
protected readonly exceptionConverter: MySqlExceptionConverter;
|
|
11
11
|
protected readonly ORDER_BY_NULLS_TRANSLATE: {
|
|
12
|
-
readonly "asc nulls first":
|
|
13
|
-
readonly "asc nulls last":
|
|
14
|
-
readonly "desc nulls first":
|
|
15
|
-
readonly "desc nulls last":
|
|
12
|
+
readonly "asc nulls first": 'is not null';
|
|
13
|
+
readonly "asc nulls last": 'is null';
|
|
14
|
+
readonly "desc nulls first": 'is not null';
|
|
15
|
+
readonly "desc nulls last": 'is null';
|
|
16
16
|
};
|
|
17
17
|
supportsMultiColumnCountDistinct(): boolean;
|
|
18
18
|
/** @internal */
|
|
@@ -5,6 +5,7 @@ export class MySqlSchemaHelper extends SchemaHelper {
|
|
|
5
5
|
static DEFAULT_VALUES = {
|
|
6
6
|
'now()': ['now()', 'current_timestamp'],
|
|
7
7
|
'current_timestamp(?)': ['current_timestamp(?)'],
|
|
8
|
+
'curdate()': ['(current_date)', 'curdate()'],
|
|
8
9
|
'0': ['0', 'false'],
|
|
9
10
|
};
|
|
10
11
|
// Greedy `(.+)` so nested CASE expressions inside the predicate don't trip the match on
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/sql",
|
|
3
|
-
"version": "7.1.6
|
|
3
|
+
"version": "7.1.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",
|
|
@@ -47,13 +47,13 @@
|
|
|
47
47
|
"copy": "node ../../scripts/copy.mjs"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"kysely": "0.29.
|
|
50
|
+
"kysely": "0.29.3"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@mikro-orm/core": "^7.1.
|
|
53
|
+
"@mikro-orm/core": "^7.1.6"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@mikro-orm/core": "7.1.6
|
|
56
|
+
"@mikro-orm/core": "7.1.6"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">= 22.17.0"
|
package/query/CriteriaNode.d.ts
CHANGED
|
@@ -9,13 +9,13 @@ export declare class CriteriaNode<T extends object> implements ICriteriaNode<T>
|
|
|
9
9
|
protected readonly metadata: MetadataStorage;
|
|
10
10
|
readonly entityName: EntityName<T>;
|
|
11
11
|
readonly parent?: ICriteriaNode<T> | undefined;
|
|
12
|
-
readonly key?:
|
|
12
|
+
readonly key?: EntityKey<T> | RawQueryFragmentSymbol | undefined;
|
|
13
13
|
readonly validate: boolean;
|
|
14
14
|
readonly strict: boolean;
|
|
15
15
|
payload: any;
|
|
16
16
|
prop?: EntityProperty<T>;
|
|
17
17
|
index?: number;
|
|
18
|
-
constructor(metadata: MetadataStorage, entityName: EntityName<T>, parent?: ICriteriaNode<T> | undefined, key?:
|
|
18
|
+
constructor(metadata: MetadataStorage, entityName: EntityName<T>, parent?: ICriteriaNode<T> | undefined, key?: EntityKey<T> | RawQueryFragmentSymbol | undefined, validate?: boolean, strict?: boolean);
|
|
19
19
|
process(qb: IQueryBuilder<T>, options?: ICriteriaNodeProcessOptions): any;
|
|
20
20
|
unwrap(): any;
|
|
21
21
|
shouldInline(payload: any): boolean;
|
package/query/QueryBuilder.d.ts
CHANGED
|
@@ -236,7 +236,7 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
236
236
|
#private;
|
|
237
237
|
protected readonly metadata: MetadataStorage;
|
|
238
238
|
protected readonly driver: AbstractSqlDriver;
|
|
239
|
-
protected readonly context?: Transaction
|
|
239
|
+
protected readonly context?: Transaction;
|
|
240
240
|
protected connectionType?: ConnectionType | undefined;
|
|
241
241
|
protected em?: SqlEntityManager | undefined;
|
|
242
242
|
protected loggerContext?: (LoggingOptions & Dictionary) | undefined;
|
|
@@ -253,7 +253,7 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
253
253
|
/**
|
|
254
254
|
* @internal
|
|
255
255
|
*/
|
|
256
|
-
constructor(entityName: EntityName<Entity> | QueryBuilder<Entity, any, any, any>, metadata: MetadataStorage, driver: AbstractSqlDriver, context?: Transaction
|
|
256
|
+
constructor(entityName: EntityName<Entity> | QueryBuilder<Entity, any, any, any>, metadata: MetadataStorage, driver: AbstractSqlDriver, context?: Transaction, alias?: string, connectionType?: ConnectionType | undefined, em?: SqlEntityManager | undefined, loggerContext?: (LoggingOptions & Dictionary) | undefined);
|
|
257
257
|
/**
|
|
258
258
|
* Creates a SELECT query, specifying the fields to retrieve.
|
|
259
259
|
*
|
|
@@ -982,6 +982,22 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
982
982
|
*/
|
|
983
983
|
private processNestedJoins;
|
|
984
984
|
private hasToManyJoins;
|
|
985
|
+
/**
|
|
986
|
+
* Resolves a `persist: false` virtual field referenced in `orderBy` (e.g. `qb.as('reviewCount')` or a
|
|
987
|
+
* `raw()` fragment aliased in the select) to the alias and SQL expression it was selected under. The
|
|
988
|
+
* alias is used to order the outer query; the raw expression is inlined into `min(...)` inside the
|
|
989
|
+
* pagination sub-query, where dialects like PostgreSQL cannot reference a select alias.
|
|
990
|
+
*/
|
|
991
|
+
protected resolveVirtualField(propName: string, fieldName: string): {
|
|
992
|
+
alias: string;
|
|
993
|
+
expr: string;
|
|
994
|
+
} | undefined;
|
|
995
|
+
/**
|
|
996
|
+
* Rewrites `orderBy` keys that reference a `persist: false` virtual field selected via `qb.as(alias)` so
|
|
997
|
+
* they point at the actual select alias. The mapper would otherwise resolve such a key to the property's
|
|
998
|
+
* naming-strategy column name, which does not exist when the alias literal differs (e.g. `reviewCount`).
|
|
999
|
+
*/
|
|
1000
|
+
private rewriteVirtualFieldOrderBy;
|
|
985
1001
|
protected wrapPaginateSubQuery(meta: EntityMetadata): void;
|
|
986
1002
|
/**
|
|
987
1003
|
* Wraps the inner query (which has ROW_NUMBER in SELECT) with an outer query
|
package/query/QueryBuilder.js
CHANGED
|
@@ -4,6 +4,12 @@ import { JoinType, QueryType } from './enums.js';
|
|
|
4
4
|
import { QueryBuilderHelper } from './QueryBuilderHelper.js';
|
|
5
5
|
import { CriteriaNodeFactory } from './CriteriaNodeFactory.js';
|
|
6
6
|
import { NativeQueryBuilder } from './NativeQueryBuilder.js';
|
|
7
|
+
/**
|
|
8
|
+
* Tags a {@link NativeQueryBuilder} produced by `qb.as(alias)` with the alias literal, so it can be
|
|
9
|
+
* matched back to its `orderBy`/pagination usage. A symbol is used (rather than a string key) so the
|
|
10
|
+
* tag survives `clone()` — {@link Utils.copy} preserves non-enumerable symbols but drops string keys.
|
|
11
|
+
*/
|
|
12
|
+
const VirtualFieldAlias = Symbol('virtualFieldAlias');
|
|
7
13
|
/** Matches 'path as alias' — safe because ORM property names are JS identifiers (no spaces). */
|
|
8
14
|
const FIELD_ALIAS_RE = /^(.+?)\s+as\s+(\w+)$/i;
|
|
9
15
|
/**
|
|
@@ -816,7 +822,7 @@ export class QueryBuilder {
|
|
|
816
822
|
Utils.runIfNotEmpty(() => qb.groupBy(this.prepareFields(this.#state.groupBy, 'groupBy', schema)), isNotEmptyObject(this.#state.groupBy));
|
|
817
823
|
Utils.runIfNotEmpty(() => this.helper.appendQueryCondition(this.type, this.#state.having, qb, undefined, 'having'), isNotEmptyObject(this.#state.having));
|
|
818
824
|
Utils.runIfNotEmpty(() => {
|
|
819
|
-
const queryOrder = this.helper.getQueryOrder(this.type, this.#state.orderBy, this.#state.populateMap, this.#state.collation);
|
|
825
|
+
const queryOrder = this.helper.getQueryOrder(this.type, this.rewriteVirtualFieldOrderBy(this.#state.orderBy), this.#state.populateMap, this.#state.collation);
|
|
820
826
|
if (queryOrder.length > 0) {
|
|
821
827
|
const sql = Utils.unique(queryOrder).join(', ');
|
|
822
828
|
qb.orderBy(sql);
|
|
@@ -1195,8 +1201,8 @@ export class QueryBuilder {
|
|
|
1195
1201
|
finalAlias = meta.properties[alias]?.fieldNames[0] ?? alias;
|
|
1196
1202
|
}
|
|
1197
1203
|
qb.as(finalAlias);
|
|
1198
|
-
// tag the instance, so it is possible to detect it easily
|
|
1199
|
-
Object.defineProperty(qb,
|
|
1204
|
+
// tag the instance, so it is possible to detect it easily (symbol survives clone, see VirtualFieldAlias)
|
|
1205
|
+
Object.defineProperty(qb, VirtualFieldAlias, { value: finalAlias });
|
|
1200
1206
|
return qb;
|
|
1201
1207
|
}
|
|
1202
1208
|
/**
|
|
@@ -2070,6 +2076,72 @@ export class QueryBuilder {
|
|
|
2070
2076
|
return [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(join.prop.kind);
|
|
2071
2077
|
});
|
|
2072
2078
|
}
|
|
2079
|
+
/**
|
|
2080
|
+
* Resolves a `persist: false` virtual field referenced in `orderBy` (e.g. `qb.as('reviewCount')` or a
|
|
2081
|
+
* `raw()` fragment aliased in the select) to the alias and SQL expression it was selected under. The
|
|
2082
|
+
* alias is used to order the outer query; the raw expression is inlined into `min(...)` inside the
|
|
2083
|
+
* pagination sub-query, where dialects like PostgreSQL cannot reference a select alias.
|
|
2084
|
+
*/
|
|
2085
|
+
resolveVirtualField(propName, fieldName) {
|
|
2086
|
+
const field = this.#state.fields?.find(f => {
|
|
2087
|
+
if (f instanceof NativeQueryBuilder) {
|
|
2088
|
+
const as = f[VirtualFieldAlias];
|
|
2089
|
+
return as === propName || as === fieldName;
|
|
2090
|
+
}
|
|
2091
|
+
// not perfect, but should work most of the time, ideally we should check only the alias (`... as alias`)
|
|
2092
|
+
return isRaw(f) && (f.sql.includes(propName) || f.sql.includes(fieldName));
|
|
2093
|
+
});
|
|
2094
|
+
if (field instanceof NativeQueryBuilder) {
|
|
2095
|
+
const alias = field[VirtualFieldAlias];
|
|
2096
|
+
const rendered = field.toString();
|
|
2097
|
+
// strip the known trailing alias via the platform quoting, so it works on every dialect (mssql uses `[...]`)
|
|
2098
|
+
const suffix = ` as ${this.platform.quoteIdentifier(alias)}`;
|
|
2099
|
+
return { alias, expr: rendered.endsWith(suffix) ? rendered.slice(0, -suffix.length) : rendered };
|
|
2100
|
+
}
|
|
2101
|
+
if (isRaw(field)) {
|
|
2102
|
+
const compiled = this.platform.formatQuery(field.sql, field.params);
|
|
2103
|
+
const close = compiled[compiled.length - 1];
|
|
2104
|
+
const open = close === ']' ? '[' : close === '"' ? '"' : close === '`' ? '`' : undefined;
|
|
2105
|
+
// scan back for the trailing `as <quoted alias>` (linear, avoids regex backtracking on user SQL)
|
|
2106
|
+
const start = open ? compiled.lastIndexOf(open, compiled.length - 2) : -1;
|
|
2107
|
+
if (start >= 4 && compiled.slice(start - 4, start) === ' as ') {
|
|
2108
|
+
return { alias: compiled.slice(start + 1, -1), expr: compiled.slice(0, start - 4) };
|
|
2109
|
+
}
|
|
2110
|
+
return { alias: fieldName, expr: compiled };
|
|
2111
|
+
}
|
|
2112
|
+
return undefined;
|
|
2113
|
+
}
|
|
2114
|
+
/**
|
|
2115
|
+
* Rewrites `orderBy` keys that reference a `persist: false` virtual field selected via `qb.as(alias)` so
|
|
2116
|
+
* they point at the actual select alias. The mapper would otherwise resolve such a key to the property's
|
|
2117
|
+
* naming-strategy column name, which does not exist when the alias literal differs (e.g. `reviewCount`).
|
|
2118
|
+
*/
|
|
2119
|
+
rewriteVirtualFieldOrderBy(orderBy) {
|
|
2120
|
+
// only aliased sub-queries (`qb.as(...)`) or `raw()` fragments can back a virtual field, skip otherwise
|
|
2121
|
+
if (!this.#state.fields?.some(f => f instanceof NativeQueryBuilder || isRaw(f))) {
|
|
2122
|
+
return orderBy;
|
|
2123
|
+
}
|
|
2124
|
+
return orderBy.map(orderMap => {
|
|
2125
|
+
const out = {};
|
|
2126
|
+
for (const key of Utils.getObjectQueryKeys(orderMap)) {
|
|
2127
|
+
const direction = orderMap[key];
|
|
2128
|
+
if (!RawQueryFragment.isKnownFragmentSymbol(key)) {
|
|
2129
|
+
const [a, f] = this.helper.splitField(key);
|
|
2130
|
+
const prop = this.helper.getProperty(f, a);
|
|
2131
|
+
if (prop?.persist === false && !prop.formula && !prop.embedded) {
|
|
2132
|
+
const fieldName = this.helper.mapper(key, this.type, undefined, null);
|
|
2133
|
+
const virtual = this.resolveVirtualField(f, fieldName);
|
|
2134
|
+
if (virtual) {
|
|
2135
|
+
out[raw(this.platform.quoteIdentifier(virtual.alias))] = direction;
|
|
2136
|
+
continue;
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
out[key] = direction;
|
|
2141
|
+
}
|
|
2142
|
+
return out;
|
|
2143
|
+
});
|
|
2144
|
+
}
|
|
2073
2145
|
wrapPaginateSubQuery(meta) {
|
|
2074
2146
|
const schema = this.getSchema(this.mainAlias);
|
|
2075
2147
|
const pks = this.prepareFields(meta.primaryKeys, 'sub-query', schema);
|
|
@@ -2086,7 +2158,6 @@ export class QueryBuilder {
|
|
|
2086
2158
|
if (this.#state.offset) {
|
|
2087
2159
|
subQuery.offset(this.#state.offset);
|
|
2088
2160
|
}
|
|
2089
|
-
const addToSelect = [];
|
|
2090
2161
|
if (this.#state.orderBy.length > 0) {
|
|
2091
2162
|
const orderBy = [];
|
|
2092
2163
|
for (const orderMap of this.#state.orderBy) {
|
|
@@ -2100,42 +2171,19 @@ export class QueryBuilder {
|
|
|
2100
2171
|
const prop = this.helper.getProperty(f, a);
|
|
2101
2172
|
const type = this.platform.castColumn(prop);
|
|
2102
2173
|
const fieldName = this.helper.mapper(field, this.type, undefined, null);
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2174
|
+
// virtual fields (e.g. `qb.as(...)`) have no column to reference inside `min()`; inline their
|
|
2175
|
+
// expression instead, as a select alias is not resolvable there on some dialects (e.g. PostgreSQL)
|
|
2176
|
+
const virtual = !prop?.persist && !prop?.formula && !prop?.hasConvertToJSValueSQL && !pks.includes(fieldName)
|
|
2177
|
+
? this.resolveVirtualField(f, fieldName)
|
|
2178
|
+
: undefined;
|
|
2179
|
+
const expr = virtual ? virtual.expr : this.platform.quoteIdentifier(fieldName);
|
|
2180
|
+
orderBy.push({ [raw(`min(${expr}${type})`)]: direction });
|
|
2109
2181
|
}
|
|
2110
2182
|
}
|
|
2111
2183
|
subQuery.orderBy(orderBy);
|
|
2112
2184
|
}
|
|
2113
2185
|
subQuery.#state.finalized = true;
|
|
2114
2186
|
const innerQuery = subQuery.as(this.mainAlias.aliasName).clear('select').select(pks);
|
|
2115
|
-
if (addToSelect.length > 0) {
|
|
2116
|
-
addToSelect.forEach(prop => {
|
|
2117
|
-
const field = this.#state.fields.find(field => {
|
|
2118
|
-
if (typeof field === 'object' && field && '__as' in field) {
|
|
2119
|
-
return field.__as === prop;
|
|
2120
|
-
}
|
|
2121
|
-
if (isRaw(field)) {
|
|
2122
|
-
// not perfect, but should work most of the time, ideally we should check only the alias (`... as alias`)
|
|
2123
|
-
return field.sql.includes(prop);
|
|
2124
|
-
}
|
|
2125
|
-
return false;
|
|
2126
|
-
});
|
|
2127
|
-
/* v8 ignore next */
|
|
2128
|
-
if (isRaw(field)) {
|
|
2129
|
-
innerQuery.select(field);
|
|
2130
|
-
}
|
|
2131
|
-
else if (field instanceof NativeQueryBuilder) {
|
|
2132
|
-
innerQuery.select(field.toRaw());
|
|
2133
|
-
}
|
|
2134
|
-
else if (field) {
|
|
2135
|
-
innerQuery.select(field);
|
|
2136
|
-
}
|
|
2137
|
-
});
|
|
2138
|
-
}
|
|
2139
2187
|
// multiple sub-queries are needed to get around mysql limitations with order by + limit + where in + group by (o.O)
|
|
2140
2188
|
// https://stackoverflow.com/questions/17892762/mysql-this-version-of-mysql-doesnt-yet-support-limit-in-all-any-some-subqu
|
|
2141
2189
|
const subSubQuery = this.platform.createNativeQueryBuilder();
|
package/schema/DatabaseSchema.js
CHANGED
|
@@ -230,6 +230,12 @@ export class DatabaseSchema {
|
|
|
230
230
|
table.addIndex(meta, { properties: pkPropsForIndex.map(prop => prop.name) }, 'primary');
|
|
231
231
|
for (const check of meta.checks) {
|
|
232
232
|
const columnName = check.property ? meta.properties[check.property].fieldNames[0] : undefined;
|
|
233
|
+
// In TPT, inherited columns live on the parent table only, so a check
|
|
234
|
+
// that references such a column must not be emitted onto the child
|
|
235
|
+
// table, where that column does not exist.
|
|
236
|
+
if (meta.inheritanceType === 'tpt' && meta.tptParent && columnName && !table.hasColumn(columnName)) {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
233
239
|
const expression = isRaw(check.expression)
|
|
234
240
|
? platform.formatQuery(check.expression.sql, check.expression.params)
|
|
235
241
|
: check.expression;
|
|
@@ -894,7 +894,8 @@ export class SchemaComparator {
|
|
|
894
894
|
// e.g. quotes might be added (https://github.com/mikro-orm/mikro-orm/issues/3827)
|
|
895
895
|
const simplify = (str) => {
|
|
896
896
|
return (str
|
|
897
|
-
|
|
897
|
+
// lookbehind: only strip a real charset introducer, never an underscore inside a literal like 'a_b'
|
|
898
|
+
?.replace(/(?<![\w'])_\w+'(.*?)'/g, '$1')
|
|
898
899
|
.replace(/!=/g, '<>')
|
|
899
900
|
.replace(/in\s*\((.*?)\)/gi, '= any (array[$1])')
|
|
900
901
|
// MySQL normalizes count(*) to count(0)
|
package/typings.d.ts
CHANGED
|
@@ -359,7 +359,10 @@ type InferColumnValue<TBuilder, TProcessOnCreate extends boolean> = TBuilder ext
|
|
|
359
359
|
value: infer Value;
|
|
360
360
|
};
|
|
361
361
|
'~options': infer TOptions;
|
|
362
|
-
} ? MaybeNever<MaybeGenerated<MaybeJoinKey<Value, TOptions>, TOptions, TProcessOnCreate>, TOptions> : never;
|
|
362
|
+
} ? MaybeNever<MaybeGenerated<MaybeJoinKey<MaybeArray<Value, TOptions>, TOptions>, TOptions, TProcessOnCreate>, TOptions> : never;
|
|
363
|
+
type MaybeArray<TValue, TOptions> = TOptions extends {
|
|
364
|
+
array: true;
|
|
365
|
+
} ? TValue[] : TValue;
|
|
363
366
|
type MaybeGenerated<TValue, TOptions, TProcessOnCreate extends boolean> = TOptions extends {
|
|
364
367
|
nullable: true;
|
|
365
368
|
} ? TValue | null : TOptions extends {
|