@mikro-orm/knex 7.0.0-dev.39 → 7.0.0-dev.40
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 +2 -2
- package/README.md +2 -0
- package/dialects/postgresql/PostgreSqlTableCompiler.d.ts +1 -0
- package/dialects/postgresql/PostgreSqlTableCompiler.js +1 -0
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/package.json +3 -3
- package/query/ArrayCriteriaNode.d.ts +1 -0
- package/query/ArrayCriteriaNode.js +3 -0
- package/query/CriteriaNode.d.ts +3 -1
- package/query/CriteriaNode.js +6 -1
- package/query/CriteriaNodeFactory.js +1 -1
- package/query/ObjectCriteriaNode.d.ts +1 -0
- package/query/ObjectCriteriaNode.js +22 -14
- package/query/QueryBuilder.d.ts +3 -3
- package/query/QueryBuilder.js +4 -3
- package/query/QueryBuilderHelper.d.ts +1 -1
- package/query/QueryBuilderHelper.js +11 -4
- package/query/index.d.ts +1 -0
- package/query/index.js +1 -0
- package/query/raw.d.ts +58 -0
- package/query/raw.js +72 -0
- package/schema/DatabaseTable.js +15 -3
- package/typings.d.ts +2 -1
package/AbstractSqlDriver.js
CHANGED
|
@@ -900,10 +900,10 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
900
900
|
*/
|
|
901
901
|
joinedProps(meta, populate, options) {
|
|
902
902
|
return populate.filter(hint => {
|
|
903
|
-
const [propName] = hint.field.split(':', 2);
|
|
903
|
+
const [propName, ref] = hint.field.split(':', 2);
|
|
904
904
|
const prop = meta.properties[propName] || {};
|
|
905
905
|
const strategy = getLoadingStrategy(hint.strategy || prop.strategy || options?.strategy || this.config.get('loadStrategy'), prop.kind);
|
|
906
|
-
if (
|
|
906
|
+
if (ref && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
|
|
907
907
|
return true;
|
|
908
908
|
}
|
|
909
909
|
// skip redundant joins for 1:1 owner population hints when using `mapToPk`
|
package/README.md
CHANGED
|
@@ -381,6 +381,8 @@ See also the list of contributors who [participated](https://github.com/mikro-or
|
|
|
381
381
|
|
|
382
382
|
Please ⭐️ this repository if this project helped you!
|
|
383
383
|
|
|
384
|
+
> If you'd like to support my open-source work, consider sponsoring me directly at [github.com/sponsors/b4nan](https://github.com/sponsors/b4nan).
|
|
385
|
+
|
|
384
386
|
## 📝 License
|
|
385
387
|
|
|
386
388
|
Copyright © 2018 [Martin Adámek](https://github.com/b4nan).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/index.d.ts
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* @packageDocumentation
|
|
3
3
|
* @module knex
|
|
4
4
|
*/
|
|
5
|
-
/** @ignore */
|
|
6
5
|
export { Kysely } from 'kysely';
|
|
7
6
|
export * from '@mikro-orm/core';
|
|
8
7
|
export * from './AbstractSqlConnection.js';
|
|
@@ -11,6 +10,7 @@ export * from './AbstractSqlPlatform.js';
|
|
|
11
10
|
export * from './SqlEntityManager.js';
|
|
12
11
|
export * from './SqlEntityRepository.js';
|
|
13
12
|
export * from './query/index.js';
|
|
13
|
+
export { raw } from './query/index.js';
|
|
14
14
|
export * from './schema/index.js';
|
|
15
15
|
export * from './dialects/index.js';
|
|
16
16
|
export * from './typings.js';
|
package/index.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* @packageDocumentation
|
|
3
3
|
* @module knex
|
|
4
4
|
*/
|
|
5
|
-
/** @ignore */
|
|
6
5
|
export { Kysely } from 'kysely';
|
|
7
6
|
export * from '@mikro-orm/core';
|
|
8
7
|
export * from './AbstractSqlConnection.js';
|
|
@@ -11,6 +10,7 @@ export * from './AbstractSqlPlatform.js';
|
|
|
11
10
|
export * from './SqlEntityManager.js';
|
|
12
11
|
export * from './SqlEntityRepository.js';
|
|
13
12
|
export * from './query/index.js';
|
|
13
|
+
export { raw } from './query/index.js';
|
|
14
14
|
export * from './schema/index.js';
|
|
15
15
|
export * from './dialects/index.js';
|
|
16
16
|
export * from './typings.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/knex",
|
|
3
|
-
"version": "7.0.0-dev.
|
|
3
|
+
"version": "7.0.0-dev.40",
|
|
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
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
"sqlstring": "2.3.3"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@mikro-orm/core": "^6.
|
|
57
|
+
"@mikro-orm/core": "^6.6.0"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
|
-
"@mikro-orm/core": "7.0.0-dev.
|
|
60
|
+
"@mikro-orm/core": "7.0.0-dev.40"
|
|
61
61
|
}
|
|
62
62
|
}
|
|
@@ -7,4 +7,5 @@ export declare class ArrayCriteriaNode<T extends object> extends CriteriaNode<T>
|
|
|
7
7
|
process(qb: IQueryBuilder<T>, options?: ICriteriaNodeProcessOptions): any;
|
|
8
8
|
unwrap(): any;
|
|
9
9
|
willAutoJoin(qb: IQueryBuilder<T>, alias?: string, options?: ICriteriaNodeProcessOptions): any;
|
|
10
|
+
isStrict(): boolean;
|
|
10
11
|
}
|
package/query/CriteriaNode.d.ts
CHANGED
|
@@ -11,10 +11,11 @@ export declare class CriteriaNode<T extends object> implements ICriteriaNode<T>
|
|
|
11
11
|
readonly entityName: string;
|
|
12
12
|
readonly parent?: ICriteriaNode<T> | undefined;
|
|
13
13
|
readonly key?: EntityKey<T> | undefined;
|
|
14
|
+
readonly strict: boolean;
|
|
14
15
|
payload: any;
|
|
15
16
|
prop?: EntityProperty<T>;
|
|
16
17
|
index?: number;
|
|
17
|
-
constructor(metadata: MetadataStorage, entityName: string, parent?: ICriteriaNode<T> | undefined, key?: EntityKey<T> | undefined, validate?: boolean);
|
|
18
|
+
constructor(metadata: MetadataStorage, entityName: string, parent?: ICriteriaNode<T> | undefined, key?: EntityKey<T> | undefined, validate?: boolean, strict?: boolean);
|
|
18
19
|
process(qb: IQueryBuilder<T>, options?: ICriteriaNodeProcessOptions): any;
|
|
19
20
|
unwrap(): any;
|
|
20
21
|
shouldInline(payload: any): boolean;
|
|
@@ -25,6 +26,7 @@ export declare class CriteriaNode<T extends object> implements ICriteriaNode<T>
|
|
|
25
26
|
private isPivotJoin;
|
|
26
27
|
getPivotPath(path: string): string;
|
|
27
28
|
aliased(field: string, alias?: string): string;
|
|
29
|
+
isStrict(): boolean;
|
|
28
30
|
/** @ignore */
|
|
29
31
|
[inspect.custom](): string;
|
|
30
32
|
}
|
package/query/CriteriaNode.js
CHANGED
|
@@ -10,14 +10,16 @@ export class CriteriaNode {
|
|
|
10
10
|
entityName;
|
|
11
11
|
parent;
|
|
12
12
|
key;
|
|
13
|
+
strict;
|
|
13
14
|
payload;
|
|
14
15
|
prop;
|
|
15
16
|
index;
|
|
16
|
-
constructor(metadata, entityName, parent, key, validate = true) {
|
|
17
|
+
constructor(metadata, entityName, parent, key, validate = true, strict = false) {
|
|
17
18
|
this.metadata = metadata;
|
|
18
19
|
this.entityName = entityName;
|
|
19
20
|
this.parent = parent;
|
|
20
21
|
this.key = key;
|
|
22
|
+
this.strict = strict;
|
|
21
23
|
const meta = parent && metadata.find(parent.entityName);
|
|
22
24
|
if (meta && key) {
|
|
23
25
|
const pks = Utils.splitPrimaryKeys(key);
|
|
@@ -106,6 +108,9 @@ export class CriteriaNode {
|
|
|
106
108
|
aliased(field, alias) {
|
|
107
109
|
return alias ? `${alias}.${field}` : field;
|
|
108
110
|
}
|
|
111
|
+
isStrict() {
|
|
112
|
+
return this.strict;
|
|
113
|
+
}
|
|
109
114
|
/** @ignore */
|
|
110
115
|
[inspect.custom]() {
|
|
111
116
|
const o = {};
|
|
@@ -36,7 +36,7 @@ export class CriteriaNodeFactory {
|
|
|
36
36
|
}
|
|
37
37
|
static createObjectNode(metadata, entityName, payload, parent, key) {
|
|
38
38
|
const meta = metadata.find(entityName);
|
|
39
|
-
const node = new ObjectCriteriaNode(metadata, entityName, parent, key);
|
|
39
|
+
const node = new ObjectCriteriaNode(metadata, entityName, parent, key, true, payload.__strict);
|
|
40
40
|
node.payload = {};
|
|
41
41
|
for (const key of Object.keys(payload)) {
|
|
42
42
|
node.payload[key] = this.createObjectItemNode(metadata, entityName, node, payload, key, meta);
|
|
@@ -5,6 +5,7 @@ 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
|
+
isStrict(): boolean;
|
|
8
9
|
unwrap(): any;
|
|
9
10
|
willAutoJoin(qb: IQueryBuilder<T>, alias?: string, options?: ICriteriaNodeProcessOptions): boolean;
|
|
10
11
|
shouldInline(payload: any): boolean;
|
|
@@ -58,6 +58,22 @@ export class ObjectCriteriaNode extends CriteriaNode {
|
|
|
58
58
|
}
|
|
59
59
|
alias = this.autoJoin(qb, ownerAlias, options);
|
|
60
60
|
}
|
|
61
|
+
if (this.prop && nestedAlias) {
|
|
62
|
+
const toOneProperty = [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind);
|
|
63
|
+
// if the property is nullable and the filter is strict, we need to use left join, so we mimic the inner join behaviour
|
|
64
|
+
// with an exclusive condition on the join columns:
|
|
65
|
+
// - if the owning column is null, the row is missing, we don't apply the filter
|
|
66
|
+
// - if the target column is not null, the row is matched, we apply the filter
|
|
67
|
+
if (toOneProperty && this.prop.nullable && this.isStrict()) {
|
|
68
|
+
const key = this.prop.owner ? this.prop.name : this.prop.referencedPKs;
|
|
69
|
+
qb.andWhere({
|
|
70
|
+
$or: [
|
|
71
|
+
{ [ownerAlias + '.' + key]: null },
|
|
72
|
+
{ [nestedAlias + '.' + Utils.getPrimaryKeyHash(this.prop.referencedPKs)]: { $ne: null } },
|
|
73
|
+
],
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
61
77
|
return keys.reduce((o, field) => {
|
|
62
78
|
const childNode = this.payload[field];
|
|
63
79
|
const payload = childNode.process(qb, { ...options, alias: this.prop ? alias : ownerAlias });
|
|
@@ -89,6 +105,11 @@ export class ObjectCriteriaNode extends CriteriaNode {
|
|
|
89
105
|
return o;
|
|
90
106
|
}, {});
|
|
91
107
|
}
|
|
108
|
+
isStrict() {
|
|
109
|
+
return this.strict || Object.keys(this.payload).some(key => {
|
|
110
|
+
return this.payload[key].isStrict();
|
|
111
|
+
});
|
|
112
|
+
}
|
|
92
113
|
unwrap() {
|
|
93
114
|
return Object.keys(this.payload).reduce((o, field) => {
|
|
94
115
|
o[field] = this.payload[field].unwrap();
|
|
@@ -213,24 +234,11 @@ export class ObjectCriteriaNode extends CriteriaNode {
|
|
|
213
234
|
? JoinType.innerJoin
|
|
214
235
|
: JoinType.leftJoin;
|
|
215
236
|
qb[method](field, nestedAlias, undefined, joinType, path);
|
|
216
|
-
// if the property is nullable, we need to use left join, so we mimic the inner join behaviour
|
|
217
|
-
// with an exclusive condition on the join columns:
|
|
218
|
-
// - if the owning column is null, the row is missing, we don't apply the filter
|
|
219
|
-
// - if the target column is not null, the row is matched, we apply the filter
|
|
220
|
-
if (toOneProperty && this.prop.nullable && options?.filter) {
|
|
221
|
-
const key = this.prop.owner ? this.prop.name : this.prop.referencedPKs;
|
|
222
|
-
qb.andWhere({
|
|
223
|
-
$or: [
|
|
224
|
-
{ [alias + '.' + key]: null },
|
|
225
|
-
{ [nestedAlias + '.' + Utils.getPrimaryKeyHash(this.prop.referencedPKs)]: { $ne: null } },
|
|
226
|
-
],
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
237
|
if (!qb.hasFlag(QueryFlag.INFER_POPULATE)) {
|
|
230
238
|
qb._fields = prev;
|
|
231
239
|
}
|
|
232
240
|
}
|
|
233
|
-
if (
|
|
241
|
+
if (options?.type !== 'orderBy') {
|
|
234
242
|
qb.scheduleFilterCheck(path);
|
|
235
243
|
}
|
|
236
244
|
return nestedAlias;
|
package/query/QueryBuilder.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { inspect } from 'node:util';
|
|
2
|
-
import { type AnyEntity, type ConnectionType, type Dictionary, type EntityData, type EntityKey, type EntityManager, type EntityMetadata, type EntityName, type EntityProperty, type ExpandProperty, type FlushMode, type GroupOperator, type Loaded, LockMode, type LoggingOptions, type MetadataStorage, type ObjectQuery, PopulateHint, type PopulateOptions, type QBFilterQuery, type QBQueryOrderMap, QueryFlag, type QueryOrderMap, type QueryResult, RawQueryFragment, type RequiredEntityData, type Transaction } from '@mikro-orm/core';
|
|
2
|
+
import { type AnyEntity, type ConnectionType, type Dictionary, type EntityData, type EntityKey, type EntityManager, type EntityMetadata, type EntityName, type EntityProperty, type ExpandProperty, type FilterOptions, type FlushMode, type GroupOperator, type Loaded, LockMode, type LoggingOptions, type MetadataStorage, type ObjectQuery, PopulateHint, type PopulateOptions, type QBFilterQuery, type QBQueryOrderMap, QueryFlag, type QueryOrderMap, type QueryResult, RawQueryFragment, type RequiredEntityData, type Transaction } from '@mikro-orm/core';
|
|
3
3
|
import { JoinType, QueryType } from './enums.js';
|
|
4
4
|
import type { AbstractSqlDriver } from '../AbstractSqlDriver.js';
|
|
5
5
|
import { type Alias, type OnConflictClause, QueryBuilderHelper } from './QueryBuilderHelper.js';
|
|
@@ -164,7 +164,7 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
164
164
|
/**
|
|
165
165
|
* Apply filters to the QB where condition.
|
|
166
166
|
*/
|
|
167
|
-
applyFilters(filterOptions?:
|
|
167
|
+
applyFilters(filterOptions?: FilterOptions): Promise<void>;
|
|
168
168
|
private readonly autoJoinedPaths;
|
|
169
169
|
/**
|
|
170
170
|
* @internal
|
|
@@ -173,7 +173,7 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
173
173
|
/**
|
|
174
174
|
* @internal
|
|
175
175
|
*/
|
|
176
|
-
applyJoinedFilters(em: EntityManager, filterOptions
|
|
176
|
+
applyJoinedFilters(em: EntityManager, filterOptions: FilterOptions | undefined): Promise<void>;
|
|
177
177
|
withSubQuery(subQuery: RawQueryFragment | NativeQueryBuilder, alias: string): this;
|
|
178
178
|
where(cond: QBFilterQuery<Entity>, operator?: keyof typeof GroupOperator): this;
|
|
179
179
|
where(cond: string, params?: any[], operator?: keyof typeof GroupOperator): this;
|
package/query/QueryBuilder.js
CHANGED
|
@@ -270,12 +270,13 @@ export class QueryBuilder {
|
|
|
270
270
|
/**
|
|
271
271
|
* @internal
|
|
272
272
|
*/
|
|
273
|
-
async applyJoinedFilters(em, filterOptions
|
|
273
|
+
async applyJoinedFilters(em, filterOptions) {
|
|
274
274
|
for (const path of this.autoJoinedPaths) {
|
|
275
275
|
const join = this.getJoinForPath(path);
|
|
276
276
|
if (join.type === JoinType.pivotJoin) {
|
|
277
277
|
continue;
|
|
278
278
|
}
|
|
279
|
+
filterOptions = QueryHelper.mergePropertyFilters(join.prop.filters, filterOptions);
|
|
279
280
|
const cond = await em.applyFilters(join.prop.type, join.cond, filterOptions, 'read');
|
|
280
281
|
if (Utils.hasObjectKeys(cond)) {
|
|
281
282
|
// remove nested filters, we only care about scalars here, nesting would require another join branch
|
|
@@ -476,7 +477,7 @@ export class QueryBuilder {
|
|
|
476
477
|
}
|
|
477
478
|
setLockMode(mode, tables) {
|
|
478
479
|
this.ensureNotFinalized();
|
|
479
|
-
if (mode != null &&
|
|
480
|
+
if (mode != null && ![LockMode.OPTIMISTIC, LockMode.NONE].includes(mode) && !this.context) {
|
|
480
481
|
throw ValidationError.transactionRequired();
|
|
481
482
|
}
|
|
482
483
|
this.lockMode = mode;
|
|
@@ -570,7 +571,7 @@ export class QueryBuilder {
|
|
|
570
571
|
Utils.runIfNotEmpty(() => qb.hintComment(this._hintComments), this._hintComments);
|
|
571
572
|
Utils.runIfNotEmpty(() => this.helper.appendOnConflictClause(QueryType.UPSERT, this._onConflict, qb), this._onConflict);
|
|
572
573
|
if (this.lockMode) {
|
|
573
|
-
this.helper.getLockSQL(qb, this.lockMode, this.lockTables);
|
|
574
|
+
this.helper.getLockSQL(qb, this.lockMode, this.lockTables, this._joins);
|
|
574
575
|
}
|
|
575
576
|
this.helper.finalize(this.type, qb, this.mainAlias.metadata, this._data, this._returning);
|
|
576
577
|
this.clearRawFragmentsCache();
|
|
@@ -49,7 +49,7 @@ export declare class QueryBuilderHelper {
|
|
|
49
49
|
getQueryOrderFromObject(type: QueryType, orderBy: FlatQueryOrderMap, populate: Dictionary<string>): string[];
|
|
50
50
|
finalize(type: QueryType, qb: NativeQueryBuilder, meta?: EntityMetadata, data?: Dictionary, returning?: Field<any>[]): void;
|
|
51
51
|
splitField<T>(field: EntityKey<T>, greedyAlias?: boolean): [string, EntityKey<T>, string | undefined];
|
|
52
|
-
getLockSQL(qb: NativeQueryBuilder, lockMode: LockMode, lockTables?: string[]): void;
|
|
52
|
+
getLockSQL(qb: NativeQueryBuilder, lockMode: LockMode, lockTables?: string[], joinsMap?: Dictionary<JoinOptions>): void;
|
|
53
53
|
updateVersionProperty(qb: NativeQueryBuilder, data: Dictionary): void;
|
|
54
54
|
private prefix;
|
|
55
55
|
private appendGroupCondition;
|
|
@@ -75,10 +75,10 @@ export class QueryBuilderHelper {
|
|
|
75
75
|
if (prop?.name === a && prop.embeddedProps[f]) {
|
|
76
76
|
return aliasPrefix + prop.fieldNames[fkIdx];
|
|
77
77
|
}
|
|
78
|
-
if (
|
|
78
|
+
if (a === prop?.embedded?.[0]) {
|
|
79
79
|
return aliasPrefix + prop.fieldNames[fkIdx];
|
|
80
80
|
}
|
|
81
|
-
const noPrefix = prop
|
|
81
|
+
const noPrefix = prop?.persist === false;
|
|
82
82
|
if (prop?.fieldNameRaw) {
|
|
83
83
|
return raw(this.prefix(field, isTableNameAliasRequired));
|
|
84
84
|
}
|
|
@@ -550,7 +550,7 @@ export class QueryBuilderHelper {
|
|
|
550
550
|
let [alias, field] = this.splitField(f, true);
|
|
551
551
|
alias = populate[alias] || alias;
|
|
552
552
|
const prop = this.getProperty(field, alias);
|
|
553
|
-
const noPrefix = (prop
|
|
553
|
+
const noPrefix = (prop?.persist === false && !prop.formula && !prop.embedded) || RawQueryFragment.isKnownFragment(f);
|
|
554
554
|
const column = this.mapper(noPrefix ? field : `${alias}.${field}`, type, undefined, null);
|
|
555
555
|
/* v8 ignore next */
|
|
556
556
|
const rawColumn = Utils.isString(column) ? column.split('.').map(e => this.platform.quoteIdentifier(e)).join('.') : column;
|
|
@@ -622,11 +622,18 @@ export class QueryBuilderHelper {
|
|
|
622
622
|
const fromField = parts.join('.');
|
|
623
623
|
return [fromAlias, fromField, ref];
|
|
624
624
|
}
|
|
625
|
-
getLockSQL(qb, lockMode, lockTables = []) {
|
|
625
|
+
getLockSQL(qb, lockMode, lockTables = [], joinsMap) {
|
|
626
626
|
const meta = this.metadata.find(this.entityName);
|
|
627
627
|
if (lockMode === LockMode.OPTIMISTIC && meta && !meta.versionProperty) {
|
|
628
628
|
throw OptimisticLockError.lockFailed(this.entityName);
|
|
629
629
|
}
|
|
630
|
+
if (lockMode !== LockMode.OPTIMISTIC && lockTables.length === 0 && joinsMap) {
|
|
631
|
+
const joins = Object.values(joinsMap);
|
|
632
|
+
const innerJoins = joins.filter(join => [JoinType.innerJoin, JoinType.innerJoinLateral, JoinType.nestedInnerJoin].includes(join.type));
|
|
633
|
+
if (joins.length > innerJoins.length) {
|
|
634
|
+
lockTables.push(this.alias, ...innerJoins.map(join => join.alias));
|
|
635
|
+
}
|
|
636
|
+
}
|
|
630
637
|
qb.lockMode(lockMode, lockTables);
|
|
631
638
|
}
|
|
632
639
|
updateVersionProperty(qb, data) {
|
package/query/index.d.ts
CHANGED
package/query/index.js
CHANGED
package/query/raw.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type AnyString, type Dictionary, type EntityKey, type RawQueryFragment } from '@mikro-orm/core';
|
|
2
|
+
import { QueryBuilder } from './QueryBuilder.js';
|
|
3
|
+
/**
|
|
4
|
+
* Creates raw SQL query fragment that can be assigned to a property or part of a filter. This fragment is represented
|
|
5
|
+
* by `RawQueryFragment` class instance that can be serialized to a string, so it can be used both as an object value
|
|
6
|
+
* and key. When serialized, the fragment key gets cached and only such cached key will be recognized by the ORM.
|
|
7
|
+
* This adds a runtime safety to the raw query fragments.
|
|
8
|
+
*
|
|
9
|
+
* > **`raw()` helper is required since v6 to use a raw fragment in your query, both through EntityManager and QueryBuilder.**
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* // as a value
|
|
13
|
+
* await em.find(User, { time: raw('now()') });
|
|
14
|
+
*
|
|
15
|
+
* // as a key
|
|
16
|
+
* await em.find(User, { [raw('lower(name)')]: name.toLowerCase() });
|
|
17
|
+
*
|
|
18
|
+
* // value can be empty array
|
|
19
|
+
* await em.find(User, { [raw('(select 1 = 1)')]: [] });
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* The `raw` helper supports several signatures, you can pass in a callback that receives the current property alias:
|
|
23
|
+
*
|
|
24
|
+
* ```ts
|
|
25
|
+
* await em.find(User, { [raw(alias => `lower(${alias}.name)`)]: name.toLowerCase() });
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* You can also use the `sql` tagged template function, which works the same, but supports only the simple string signature:
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* await em.find(User, { [sql`lower(name)`]: name.toLowerCase() });
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* When using inside filters, you might have to use a callback signature to create new raw instance for every filter usage.
|
|
35
|
+
*
|
|
36
|
+
* ```ts
|
|
37
|
+
* @Filter({ name: 'long', cond: () => ({ [raw('length(perex)')]: { $gt: 10000 } }) })
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* The `raw` helper can be used within indexes and uniques to write database-agnostic SQL expressions. In that case, you can use `'??'` to tag your database identifiers (table name, column names, index name, ...) inside your expression, and pass those identifiers as a second parameter to the `raw` helper. Internally, those will automatically be quoted according to the database in use:
|
|
41
|
+
*
|
|
42
|
+
* ```ts
|
|
43
|
+
* // On postgres, will produce: create index "index custom_idx_on_name" on "library.author" ("country")
|
|
44
|
+
* // On mysql, will produce: create index `index custom_idx_on_name` on `library.author` (`country`)
|
|
45
|
+
* @Index({ name: 'custom_idx_on_name', expression: (table, columns) => raw(`create index ?? on ?? (??)`, ['custom_idx_on_name', table, columns.name]) })
|
|
46
|
+
* @Entity({ schema: 'library' })
|
|
47
|
+
* export class Author { ... }
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* You can also use the `quote` tag function to write database-agnostic SQL expressions. The end-result is the same as using the `raw` function regarding database identifiers quoting, only to have a more elegant expression syntax:
|
|
51
|
+
*
|
|
52
|
+
* ```ts
|
|
53
|
+
* @Index({ name: 'custom_idx_on_name', expression: (table, columns) => quote`create index ${'custom_idx_on_name'} on ${table} (${columns.name})` })
|
|
54
|
+
* @Entity({ schema: 'library' })
|
|
55
|
+
* export class Author { ... }
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export declare function rawKnex<T extends object = any, R = any>(sql: QueryBuilder<T> | EntityKey<T> | EntityKey<T>[] | AnyString | ((alias: string) => string) | RawQueryFragment, params?: readonly unknown[] | Dictionary<unknown>): NoInfer<R>;
|
package/query/raw.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { raw } from '@mikro-orm/core';
|
|
2
|
+
// import type { Knex } from 'knex';
|
|
3
|
+
import { QueryBuilder } from './QueryBuilder.js';
|
|
4
|
+
/**
|
|
5
|
+
* Creates raw SQL query fragment that can be assigned to a property or part of a filter. This fragment is represented
|
|
6
|
+
* by `RawQueryFragment` class instance that can be serialized to a string, so it can be used both as an object value
|
|
7
|
+
* and key. When serialized, the fragment key gets cached and only such cached key will be recognized by the ORM.
|
|
8
|
+
* This adds a runtime safety to the raw query fragments.
|
|
9
|
+
*
|
|
10
|
+
* > **`raw()` helper is required since v6 to use a raw fragment in your query, both through EntityManager and QueryBuilder.**
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* // as a value
|
|
14
|
+
* await em.find(User, { time: raw('now()') });
|
|
15
|
+
*
|
|
16
|
+
* // as a key
|
|
17
|
+
* await em.find(User, { [raw('lower(name)')]: name.toLowerCase() });
|
|
18
|
+
*
|
|
19
|
+
* // value can be empty array
|
|
20
|
+
* await em.find(User, { [raw('(select 1 = 1)')]: [] });
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* The `raw` helper supports several signatures, you can pass in a callback that receives the current property alias:
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* await em.find(User, { [raw(alias => `lower(${alias}.name)`)]: name.toLowerCase() });
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* You can also use the `sql` tagged template function, which works the same, but supports only the simple string signature:
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
32
|
+
* await em.find(User, { [sql`lower(name)`]: name.toLowerCase() });
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* When using inside filters, you might have to use a callback signature to create new raw instance for every filter usage.
|
|
36
|
+
*
|
|
37
|
+
* ```ts
|
|
38
|
+
* @Filter({ name: 'long', cond: () => ({ [raw('length(perex)')]: { $gt: 10000 } }) })
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* The `raw` helper can be used within indexes and uniques to write database-agnostic SQL expressions. In that case, you can use `'??'` to tag your database identifiers (table name, column names, index name, ...) inside your expression, and pass those identifiers as a second parameter to the `raw` helper. Internally, those will automatically be quoted according to the database in use:
|
|
42
|
+
*
|
|
43
|
+
* ```ts
|
|
44
|
+
* // On postgres, will produce: create index "index custom_idx_on_name" on "library.author" ("country")
|
|
45
|
+
* // On mysql, will produce: create index `index custom_idx_on_name` on `library.author` (`country`)
|
|
46
|
+
* @Index({ name: 'custom_idx_on_name', expression: (table, columns) => raw(`create index ?? on ?? (??)`, ['custom_idx_on_name', table, columns.name]) })
|
|
47
|
+
* @Entity({ schema: 'library' })
|
|
48
|
+
* export class Author { ... }
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* You can also use the `quote` tag function to write database-agnostic SQL expressions. The end-result is the same as using the `raw` function regarding database identifiers quoting, only to have a more elegant expression syntax:
|
|
52
|
+
*
|
|
53
|
+
* ```ts
|
|
54
|
+
* @Index({ name: 'custom_idx_on_name', expression: (table, columns) => quote`create index ${'custom_idx_on_name'} on ${table} (${columns.name})` })
|
|
55
|
+
* @Entity({ schema: 'library' })
|
|
56
|
+
* export class Author { ... }
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export function rawKnex(sql, params) {
|
|
60
|
+
// export function rawKnex<T extends object = any, R = any>(sql: Knex.QueryBuilder | Knex.Raw | QueryBuilder<T> | EntityKey<T> | EntityKey<T>[] | AnyString | ((alias: string) => string) | RawQueryFragment, params?: readonly unknown[] | Dictionary<unknown>): NoInfer<R> {
|
|
61
|
+
// if (Utils.isObject<Knex.QueryBuilder | Knex.Raw>(sql) && 'toSQL' in sql) {
|
|
62
|
+
// const query = sql.toSQL();
|
|
63
|
+
// return raw(query.sql, query.bindings);
|
|
64
|
+
// }
|
|
65
|
+
if (sql instanceof QueryBuilder) {
|
|
66
|
+
// FIXME this should live in the `knex` compat package, while what we have now should live in `sql` package
|
|
67
|
+
// @ts-ignore
|
|
68
|
+
const query = sql.toQuery()._sql;
|
|
69
|
+
return raw(query.sql, query.bindings);
|
|
70
|
+
}
|
|
71
|
+
return raw(sql, params);
|
|
72
|
+
}
|
package/schema/DatabaseTable.js
CHANGED
|
@@ -615,7 +615,7 @@ export class DatabaseTable {
|
|
|
615
615
|
fkOptions.deferMode = fk.deferMode;
|
|
616
616
|
fkOptions.columnTypes = fk.columnNames.map(col => this.getColumn(col).type);
|
|
617
617
|
}
|
|
618
|
-
|
|
618
|
+
const ret = {
|
|
619
619
|
name: prop,
|
|
620
620
|
type,
|
|
621
621
|
runtimeType,
|
|
@@ -643,6 +643,11 @@ export class DatabaseTable {
|
|
|
643
643
|
persist,
|
|
644
644
|
...fkOptions,
|
|
645
645
|
};
|
|
646
|
+
const nativeEnumName = Object.keys(this.nativeEnums).find(name => name === column.type);
|
|
647
|
+
if (nativeEnumName) {
|
|
648
|
+
ret.nativeEnumName = nativeEnumName;
|
|
649
|
+
}
|
|
650
|
+
return ret;
|
|
646
651
|
}
|
|
647
652
|
getReferenceKind(fk, unique) {
|
|
648
653
|
if (fk && unique) {
|
|
@@ -676,10 +681,17 @@ export class DatabaseTable {
|
|
|
676
681
|
if (fk) {
|
|
677
682
|
return this.getPropertyTypeForForeignKey(namingStrategy, fk);
|
|
678
683
|
}
|
|
684
|
+
const enumMode = this.platform.getConfig().get('entityGenerator').enumMode;
|
|
679
685
|
// If this column is using an enum.
|
|
680
686
|
if (column.enumItems?.length) {
|
|
681
|
-
|
|
682
|
-
|
|
687
|
+
const name = column.nativeEnumName ?? column.name;
|
|
688
|
+
const tableName = column.nativeEnumName ? undefined : this.name;
|
|
689
|
+
if (enumMode === 'ts-enum') {
|
|
690
|
+
// We will create a new enum name for this type and set it as the property type as well.
|
|
691
|
+
return namingStrategy.getEnumClassName(name, tableName, this.schema);
|
|
692
|
+
}
|
|
693
|
+
// With other enum strategies, we need to use the type name.
|
|
694
|
+
return namingStrategy.getEnumTypeName(name, tableName, this.schema);
|
|
683
695
|
}
|
|
684
696
|
return column.mappedType?.runtimeType ?? 'unknown';
|
|
685
697
|
}
|
package/typings.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import type { Generated, Kysely } from 'kysely';
|
|
1
2
|
import type { DeferMode, CheckCallback, Dictionary, EntityProperty, GroupOperator, RawQueryFragment, QBFilterQuery, QueryOrderMap, Type, QueryFlag, AnyEntity, EntityName, EntitySchemaWithMeta, Primary, PrimaryProperty, Opt } from '@mikro-orm/core';
|
|
2
3
|
import type { JoinType, QueryType } from './query/enums.js';
|
|
3
4
|
import type { DatabaseSchema } from './schema/DatabaseSchema.js';
|
|
4
5
|
import type { DatabaseTable } from './schema/DatabaseTable.js';
|
|
5
6
|
import type { QueryBuilder } from './query/QueryBuilder.js';
|
|
6
7
|
import type { NativeQueryBuilder } from './query/NativeQueryBuilder.js';
|
|
7
|
-
import type { Generated, Kysely } from 'kysely';
|
|
8
8
|
export interface Table {
|
|
9
9
|
table_name: string;
|
|
10
10
|
schema_name?: string;
|
|
@@ -179,6 +179,7 @@ export interface ICriteriaNode<T extends object> {
|
|
|
179
179
|
readonly entityName: string;
|
|
180
180
|
readonly parent?: ICriteriaNode<T> | undefined;
|
|
181
181
|
readonly key?: string | undefined;
|
|
182
|
+
readonly strict?: boolean;
|
|
182
183
|
payload: any;
|
|
183
184
|
prop?: EntityProperty;
|
|
184
185
|
index?: number;
|