@mikro-orm/knex 7.0.0-dev.2 → 7.0.0-dev.21
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 +2 -2
- package/AbstractSqlConnection.js +14 -10
- package/AbstractSqlDriver.d.ts +5 -5
- package/AbstractSqlDriver.js +25 -8
- package/dialects/mssql/MsSqlNativeQueryBuilder.js +8 -4
- package/dialects/sqlite/BaseSqliteConnection.d.ts +1 -1
- package/dialects/sqlite/BaseSqliteConnection.js +8 -2
- package/dialects/sqlite/BaseSqlitePlatform.d.ts +0 -1
- package/dialects/sqlite/BaseSqlitePlatform.js +0 -4
- package/package.json +4 -4
- package/query/CriteriaNode.js +0 -4
- package/query/ObjectCriteriaNode.js +8 -4
- package/query/QueryBuilder.d.ts +10 -8
- package/query/QueryBuilder.js +44 -28
- package/query/QueryBuilderHelper.js +101 -8
- package/schema/DatabaseTable.js +19 -4
- package/schema/SchemaHelper.js +2 -3
- package/schema/SqlSchemaGenerator.js +5 -15
- package/typings.d.ts +2 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { type ControlledTransaction, type Dialect, Kysely } from 'kysely';
|
|
2
|
-
import { type AnyEntity, Connection, type Dictionary, type EntityData, type IsolationLevel, type LoggingOptions, type QueryResult, RawQueryFragment, type Transaction, type TransactionEventBroadcaster } from '@mikro-orm/core';
|
|
2
|
+
import { type AnyEntity, Connection, type Dictionary, type EntityData, type IsolationLevel, type LoggingOptions, type MaybePromise, type QueryResult, RawQueryFragment, type Transaction, type TransactionEventBroadcaster } from '@mikro-orm/core';
|
|
3
3
|
import type { AbstractSqlPlatform } from './AbstractSqlPlatform.js';
|
|
4
4
|
import { NativeQueryBuilder } from './query/NativeQueryBuilder.js';
|
|
5
5
|
export declare abstract class AbstractSqlConnection extends Connection {
|
|
6
6
|
protected platform: AbstractSqlPlatform;
|
|
7
7
|
protected client: Kysely<any>;
|
|
8
|
-
abstract createKyselyDialect(overrides: Dictionary): Dialect
|
|
8
|
+
abstract createKyselyDialect(overrides: Dictionary): MaybePromise<Dialect>;
|
|
9
9
|
connect(): Promise<void>;
|
|
10
10
|
/**
|
|
11
11
|
* @inheritDoc
|
package/AbstractSqlConnection.js
CHANGED
|
@@ -19,8 +19,7 @@ export class AbstractSqlConnection extends Connection {
|
|
|
19
19
|
}
|
|
20
20
|
else {
|
|
21
21
|
this.client = new Kysely({
|
|
22
|
-
dialect: this.createKyselyDialect(driverOptions),
|
|
23
|
-
// log: m => console.log(m),
|
|
22
|
+
dialect: await this.createKyselyDialect(driverOptions),
|
|
24
23
|
});
|
|
25
24
|
}
|
|
26
25
|
this.connected = true;
|
|
@@ -73,14 +72,17 @@ export class AbstractSqlConnection extends Connection {
|
|
|
73
72
|
async begin(options = {}) {
|
|
74
73
|
if (options.ctx) {
|
|
75
74
|
const ctx = options.ctx;
|
|
75
|
+
await options.eventBroadcaster?.dispatchEvent(EventType.beforeTransactionStart, ctx);
|
|
76
76
|
ctx.index ??= 0;
|
|
77
77
|
const savepointName = `trx${ctx.index + 1}`;
|
|
78
78
|
const trx = await options.ctx.savepoint(savepointName).execute();
|
|
79
79
|
Reflect.defineProperty(trx, 'index', { value: ctx.index + 1 });
|
|
80
80
|
Reflect.defineProperty(trx, 'savepointName', { value: savepointName });
|
|
81
81
|
this.logQuery(this.platform.getSavepointSQL(savepointName));
|
|
82
|
+
await options.eventBroadcaster?.dispatchEvent(EventType.afterTransactionStart, trx);
|
|
82
83
|
return trx;
|
|
83
84
|
}
|
|
85
|
+
await this.ensureConnection();
|
|
84
86
|
await options.eventBroadcaster?.dispatchEvent(EventType.beforeTransactionStart);
|
|
85
87
|
let trxBuilder = this.client.startTransaction();
|
|
86
88
|
if (options.isolationLevel) {
|
|
@@ -100,25 +102,27 @@ export class AbstractSqlConnection extends Connection {
|
|
|
100
102
|
if (ctx.isRolledBack) {
|
|
101
103
|
return;
|
|
102
104
|
}
|
|
105
|
+
await eventBroadcaster?.dispatchEvent(EventType.beforeTransactionCommit, ctx);
|
|
103
106
|
if ('savepointName' in ctx) {
|
|
104
107
|
await ctx.releaseSavepoint(ctx.savepointName).execute();
|
|
105
108
|
this.logQuery(this.platform.getReleaseSavepointSQL(ctx.savepointName));
|
|
106
|
-
return;
|
|
107
109
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
else {
|
|
111
|
+
await ctx.commit().execute();
|
|
112
|
+
this.logQuery(this.platform.getCommitTransactionSQL());
|
|
113
|
+
}
|
|
111
114
|
await eventBroadcaster?.dispatchEvent(EventType.afterTransactionCommit, ctx);
|
|
112
115
|
}
|
|
113
116
|
async rollback(ctx, eventBroadcaster) {
|
|
117
|
+
await eventBroadcaster?.dispatchEvent(EventType.beforeTransactionRollback, ctx);
|
|
114
118
|
if ('savepointName' in ctx) {
|
|
115
119
|
await ctx.rollbackToSavepoint(ctx.savepointName).execute();
|
|
116
120
|
this.logQuery(this.platform.getRollbackToSavepointSQL(ctx.savepointName));
|
|
117
|
-
return;
|
|
118
121
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
+
else {
|
|
123
|
+
await ctx.rollback().execute();
|
|
124
|
+
this.logQuery(this.platform.getRollbackTransactionSQL());
|
|
125
|
+
}
|
|
122
126
|
await eventBroadcaster?.dispatchEvent(EventType.afterTransactionRollback, ctx);
|
|
123
127
|
}
|
|
124
128
|
async execute(query, params = [], method = 'all', ctx, loggerContext) {
|
package/AbstractSqlDriver.d.ts
CHANGED
|
@@ -14,12 +14,12 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
|
|
|
14
14
|
protected constructor(config: Configuration, platform: Platform, connection: Constructor<Connection>, connector: string[]);
|
|
15
15
|
getPlatform(): Platform;
|
|
16
16
|
createEntityManager(useContext?: boolean): this[typeof EntityManagerType];
|
|
17
|
-
find<T extends object, P extends string = never, F extends string = PopulatePath.ALL, E extends string = never>(entityName: string, where:
|
|
18
|
-
findOne<T extends object, P extends string = never, F extends string = PopulatePath.ALL, E extends string = never>(entityName: string, where:
|
|
17
|
+
find<T extends object, P extends string = never, F extends string = PopulatePath.ALL, E extends string = never>(entityName: string, where: ObjectQuery<T>, options?: FindOptions<T, P, F, E>): Promise<EntityData<T>[]>;
|
|
18
|
+
findOne<T extends object, P extends string = never, F extends string = PopulatePath.ALL, E extends string = never>(entityName: string, where: ObjectQuery<T>, options?: FindOneOptions<T, P, F, E>): Promise<EntityData<T> | null>;
|
|
19
19
|
protected hasToManyJoins<T extends object>(hint: PopulateOptions<T>, meta: EntityMetadata<T>): boolean;
|
|
20
|
-
findVirtual<T extends object>(entityName: string, where:
|
|
21
|
-
countVirtual<T extends object>(entityName: string, where:
|
|
22
|
-
protected findFromVirtual<T extends object>(entityName: string, where:
|
|
20
|
+
findVirtual<T extends object>(entityName: string, where: ObjectQuery<T>, options: FindOptions<T, any, any, any>): Promise<EntityData<T>[]>;
|
|
21
|
+
countVirtual<T extends object>(entityName: string, where: ObjectQuery<T>, options: CountOptions<T, any>): Promise<number>;
|
|
22
|
+
protected findFromVirtual<T extends object>(entityName: string, where: ObjectQuery<T>, options: FindOptions<T, any> | CountOptions<T, any>, type: QueryType): Promise<EntityData<T>[] | number>;
|
|
23
23
|
protected wrapVirtualExpressionInSubquery<T extends object>(meta: EntityMetadata<T>, expression: string, where: FilterQuery<T>, options: FindOptions<T, any>, type: QueryType): Promise<T[] | number>;
|
|
24
24
|
mapResult<T extends object>(result: EntityData<T>, meta: EntityMetadata<T>, populate?: PopulateOptions<T>[], qb?: QueryBuilder<T, any, any, any>, map?: Dictionary): EntityData<T> | null;
|
|
25
25
|
private mapJoinedProps;
|
package/AbstractSqlDriver.js
CHANGED
|
@@ -63,6 +63,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
63
63
|
if (options.lockMode) {
|
|
64
64
|
qb.setLockMode(options.lockMode, options.lockTableAliases);
|
|
65
65
|
}
|
|
66
|
+
if (options.em) {
|
|
67
|
+
await qb.applyJoinedFilters(options.em, options.filters);
|
|
68
|
+
}
|
|
66
69
|
const result = await this.rethrow(qb.execute('all'));
|
|
67
70
|
if (isCursorPagination && !first && !!last) {
|
|
68
71
|
result.reverse();
|
|
@@ -252,6 +255,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
252
255
|
: meta2.props.filter(prop => this.platform.shouldHaveColumn(prop, hint.children || []));
|
|
253
256
|
const tz = this.platform.getTimezone();
|
|
254
257
|
for (const prop of targetProps) {
|
|
258
|
+
if (prop.fieldNames.every(name => typeof root[`${relationAlias}__${name}`] === 'undefined')) {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
255
261
|
if (prop.fieldNames.length > 1) { // composite keys
|
|
256
262
|
const fk = prop.fieldNames.map(name => root[`${relationAlias}__${name}`]);
|
|
257
263
|
const pk = Utils.mapFlatCompositePrimaryKey(fk, prop);
|
|
@@ -434,7 +440,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
434
440
|
else {
|
|
435
441
|
const field = prop.fieldNames[0];
|
|
436
442
|
if (!duplicates.includes(field) || !usedDups.includes(field)) {
|
|
437
|
-
if (prop.customType && !prop.object && 'convertToDatabaseValueSQL' in prop.customType && !isRaw(row[prop.name])) {
|
|
443
|
+
if (prop.customType && !prop.object && 'convertToDatabaseValueSQL' in prop.customType && row[prop.name] != null && !isRaw(row[prop.name])) {
|
|
438
444
|
keys.push(prop.customType.convertToDatabaseValueSQL('?', this.platform));
|
|
439
445
|
}
|
|
440
446
|
else {
|
|
@@ -585,7 +591,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
585
591
|
if (key in data[idx]) {
|
|
586
592
|
const pks = Utils.getOrderedPrimaryKeys(cond, meta);
|
|
587
593
|
sql += ` when (${pkCond}) then `;
|
|
588
|
-
if (prop.customType && !prop.object && 'convertToDatabaseValueSQL' in prop.customType && !isRaw(data[idx][key])) {
|
|
594
|
+
if (prop.customType && !prop.object && 'convertToDatabaseValueSQL' in prop.customType && data[idx][prop.name] != null && !isRaw(data[idx][key])) {
|
|
589
595
|
sql += prop.customType.convertToDatabaseValueSQL('?', this.platform);
|
|
590
596
|
}
|
|
591
597
|
else {
|
|
@@ -725,8 +731,14 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
725
731
|
const pivotMeta = this.metadata.find(coll.property.pivotEntity);
|
|
726
732
|
let schema = pivotMeta.schema;
|
|
727
733
|
if (schema === '*') {
|
|
728
|
-
|
|
729
|
-
|
|
734
|
+
if (coll.property.owner) {
|
|
735
|
+
schema = wrapped.getSchema() === '*' ? options?.schema ?? this.config.get('schema') : wrapped.getSchema();
|
|
736
|
+
}
|
|
737
|
+
else {
|
|
738
|
+
const targetMeta = coll.property.targetMeta;
|
|
739
|
+
const targetSchema = (coll[0] ?? snap?.[0]) && helper(coll[0] ?? snap?.[0]).getSchema();
|
|
740
|
+
schema = targetMeta.schema === '*' ? options?.schema ?? targetSchema ?? this.config.get('schema') : targetMeta.schema;
|
|
741
|
+
}
|
|
730
742
|
}
|
|
731
743
|
else if (schema == null) {
|
|
732
744
|
schema = this.config.get('schema');
|
|
@@ -777,13 +789,15 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
777
789
|
const targetSchema = this.getSchemaName(prop.targetMeta, options) ?? this.platform.getDefaultSchemaName();
|
|
778
790
|
qb.innerJoin(pivotProp1.name, targetAlias, {}, targetSchema);
|
|
779
791
|
const targetFields = this.buildFields(prop.targetMeta, (options.populate ?? []), [], qb, targetAlias, options);
|
|
792
|
+
const additionalFields = [];
|
|
780
793
|
for (const field of targetFields) {
|
|
781
794
|
const f = field.toString();
|
|
782
|
-
|
|
795
|
+
additionalFields.push(f.includes('.') ? field : `${targetAlias}.${f}`);
|
|
783
796
|
if (RawQueryFragment.isKnownFragment(field)) {
|
|
784
797
|
qb.rawFragments.add(f);
|
|
785
798
|
}
|
|
786
799
|
}
|
|
800
|
+
fields.unshift(...additionalFields);
|
|
787
801
|
// we need to handle 1:1 owner auto-joins explicitly, as the QB type is the pivot table, not the target
|
|
788
802
|
populate.forEach(hint => {
|
|
789
803
|
const alias = qb.getNextAlias(prop.targetMeta.tableName);
|
|
@@ -927,11 +941,11 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
927
941
|
const fields = [];
|
|
928
942
|
const populate = options.populate ?? [];
|
|
929
943
|
const joinedProps = this.joinedProps(meta, populate, options);
|
|
930
|
-
const shouldHaveColumn = (prop, populate, fields) => {
|
|
944
|
+
const shouldHaveColumn = (meta, prop, populate, fields) => {
|
|
931
945
|
if (!this.platform.shouldHaveColumn(prop, populate, options.exclude)) {
|
|
932
946
|
return false;
|
|
933
947
|
}
|
|
934
|
-
if (!fields || fields.includes('*') || prop.primary) {
|
|
948
|
+
if (!fields || fields.includes('*') || prop.primary || meta.root.discriminatorColumn === prop.name) {
|
|
935
949
|
return true;
|
|
936
950
|
}
|
|
937
951
|
return fields.some(f => f === prop.name || f.toString().startsWith(prop.name + '.'));
|
|
@@ -941,7 +955,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
941
955
|
if (options.parentJoinPath) {
|
|
942
956
|
// alias all fields in the primary table
|
|
943
957
|
meta.props
|
|
944
|
-
.filter(prop => shouldHaveColumn(prop, populate, options.explicitFields))
|
|
958
|
+
.filter(prop => shouldHaveColumn(meta, prop, populate, options.explicitFields))
|
|
945
959
|
.forEach(prop => fields.push(...this.mapPropToFieldNames(qb, prop, options.parentTableAlias)));
|
|
946
960
|
}
|
|
947
961
|
for (const hint of joinedProps) {
|
|
@@ -1289,6 +1303,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1289
1303
|
if (!options.fields.includes('*') && !options.fields.includes(`${qb.alias}.*`)) {
|
|
1290
1304
|
ret.unshift(...meta.primaryKeys.filter(pk => !options.fields.includes(pk)));
|
|
1291
1305
|
}
|
|
1306
|
+
if (meta.root.discriminatorColumn && !options.fields.includes(`${qb.alias}.${meta.root.discriminatorColumn}`)) {
|
|
1307
|
+
ret.push(meta.root.discriminatorColumn);
|
|
1308
|
+
}
|
|
1292
1309
|
}
|
|
1293
1310
|
else if (!Utils.isEmpty(options.exclude) || lazyProps.some(p => !p.formula && (p.kind !== '1:1' || p.owner))) {
|
|
1294
1311
|
const props = meta.props.filter(prop => this.platform.shouldHaveColumn(prop, populate, options.exclude, false));
|
|
@@ -65,9 +65,11 @@ export class MsSqlNativeQueryBuilder extends NativeQueryBuilder {
|
|
|
65
65
|
this.params.push(...clause.fields.params);
|
|
66
66
|
}
|
|
67
67
|
else if (clause.fields.length > 0) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
const fields = clause.fields.map(field => {
|
|
69
|
+
const col = this.quote(field);
|
|
70
|
+
return `${this.getTableName()}.${col} = tsource.${col}`;
|
|
71
|
+
});
|
|
72
|
+
this.parts.push(`on ${fields.join(' and ')}`);
|
|
71
73
|
}
|
|
72
74
|
const sourceColumns = keys.map(field => `tsource.${this.quote(field)}`).join(', ');
|
|
73
75
|
const destinationColumns = keys.map(field => this.quote(field)).join(', ');
|
|
@@ -80,7 +82,9 @@ export class MsSqlNativeQueryBuilder extends NativeQueryBuilder {
|
|
|
80
82
|
}
|
|
81
83
|
this.parts.push('then update set');
|
|
82
84
|
if (!clause.merge || Array.isArray(clause.merge)) {
|
|
83
|
-
const parts =
|
|
85
|
+
const parts = (clause.merge || keys)
|
|
86
|
+
.filter(field => !Array.isArray(clause.fields) || !clause.fields.includes(field))
|
|
87
|
+
.map((column) => `${this.quote(column)} = tsource.${this.quote(column)}`);
|
|
84
88
|
this.parts.push(parts.join(', '));
|
|
85
89
|
}
|
|
86
90
|
else if (typeof clause.merge === 'object') {
|
|
@@ -3,9 +3,15 @@ import { CompiledQuery } from 'kysely';
|
|
|
3
3
|
import { Utils } from '@mikro-orm/core';
|
|
4
4
|
import { AbstractSqlConnection } from '../../AbstractSqlConnection.js';
|
|
5
5
|
export class BaseSqliteConnection extends AbstractSqlConnection {
|
|
6
|
-
async connect() {
|
|
6
|
+
async connect(simple = false) {
|
|
7
7
|
await super.connect();
|
|
8
|
-
|
|
8
|
+
if (simple) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const dbName = this.config.get('dbName');
|
|
12
|
+
if (dbName && dbName !== ':memory:') {
|
|
13
|
+
Utils.ensureDir(dirname(this.config.get('dbName')));
|
|
14
|
+
}
|
|
9
15
|
await this.client.executeQuery(CompiledQuery.raw('pragma foreign_keys = on'));
|
|
10
16
|
}
|
|
11
17
|
getClientUrl() {
|
|
@@ -63,7 +63,6 @@ export declare abstract class BaseSqlitePlatform extends AbstractSqlPlatform {
|
|
|
63
63
|
*/
|
|
64
64
|
processDateProperty(value: unknown): string | number | Date;
|
|
65
65
|
getIndexName(tableName: string, columns: string[], type: 'index' | 'unique' | 'foreign' | 'primary' | 'sequence'): string;
|
|
66
|
-
supportsDownMigrations(): boolean;
|
|
67
66
|
supportsDeferredUniqueConstraints(): boolean;
|
|
68
67
|
getFullTextWhereClause(): string;
|
|
69
68
|
quoteVersionValue(value: Date | number, prop: EntityProperty): Date | string | number;
|
|
@@ -84,10 +84,6 @@ export class BaseSqlitePlatform extends AbstractSqlPlatform {
|
|
|
84
84
|
}
|
|
85
85
|
return super.getIndexName(tableName, columns, type);
|
|
86
86
|
}
|
|
87
|
-
// TODO enable once tests are green
|
|
88
|
-
supportsDownMigrations() {
|
|
89
|
-
return false;
|
|
90
|
-
}
|
|
91
87
|
supportsDeferredUniqueConstraints() {
|
|
92
88
|
return false;
|
|
93
89
|
}
|
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.21",
|
|
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": {
|
|
@@ -50,13 +50,13 @@
|
|
|
50
50
|
"access": "public"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"kysely": "
|
|
53
|
+
"kysely": "0.28.2",
|
|
54
54
|
"sqlstring": "2.3.3"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@mikro-orm/core": "^6.4.
|
|
57
|
+
"@mikro-orm/core": "^6.4.15"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
|
-
"@mikro-orm/core": "7.0.0-dev.
|
|
60
|
+
"@mikro-orm/core": "7.0.0-dev.21"
|
|
61
61
|
}
|
|
62
62
|
}
|
package/query/CriteriaNode.js
CHANGED
|
@@ -75,10 +75,6 @@ export class CriteriaNode {
|
|
|
75
75
|
if (this.prop.kind === ReferenceKind.MANY_TO_MANY) {
|
|
76
76
|
return Utils.getPrimaryKeyHash(this.prop.inverseJoinColumns.map(col => `${alias}.${col}`));
|
|
77
77
|
}
|
|
78
|
-
// if we found a matching join, we need to use the target table column names, as we use that alias instead of the root
|
|
79
|
-
if (!joinAlias && this.prop.owner && this.prop.joinColumns.length > 1) {
|
|
80
|
-
return Utils.getPrimaryKeyHash(this.prop.joinColumns.map(col => `${alias}.${col}`));
|
|
81
|
-
}
|
|
82
78
|
return Utils.getPrimaryKeyHash(this.prop.referencedColumnNames.map(col => `${alias}.${col}`));
|
|
83
79
|
}
|
|
84
80
|
getPath(addIndex = false) {
|
|
@@ -55,7 +55,7 @@ export class ObjectCriteriaNode extends CriteriaNode {
|
|
|
55
55
|
}
|
|
56
56
|
return { $and };
|
|
57
57
|
}
|
|
58
|
-
alias = this.autoJoin(qb, ownerAlias);
|
|
58
|
+
alias = this.autoJoin(qb, ownerAlias, options);
|
|
59
59
|
}
|
|
60
60
|
return keys.reduce((o, field) => {
|
|
61
61
|
const childNode = this.payload[field];
|
|
@@ -193,23 +193,27 @@ export class ObjectCriteriaNode extends CriteriaNode {
|
|
|
193
193
|
});
|
|
194
194
|
return !primaryKeys && !nestedAlias && !operatorKeys && !embeddable;
|
|
195
195
|
}
|
|
196
|
-
autoJoin(qb, alias) {
|
|
196
|
+
autoJoin(qb, alias, options) {
|
|
197
197
|
const nestedAlias = qb.getNextAlias(this.prop?.pivotTable ?? this.entityName);
|
|
198
198
|
const customExpression = RawQueryFragment.isKnownFragment(this.key);
|
|
199
199
|
const scalar = Utils.isPrimaryKey(this.payload) || this.payload instanceof RegExp || this.payload instanceof Date || customExpression;
|
|
200
200
|
const operator = Utils.isPlainObject(this.payload) && Object.keys(this.payload).every(k => Utils.isOperator(k, false));
|
|
201
201
|
const field = `${alias}.${this.prop.name}`;
|
|
202
202
|
const method = qb.hasFlag(QueryFlag.INFER_POPULATE) ? 'joinAndSelect' : 'join';
|
|
203
|
+
const path = this.getPath();
|
|
203
204
|
if (this.prop.kind === ReferenceKind.MANY_TO_MANY && (scalar || operator)) {
|
|
204
|
-
qb.join(field, nestedAlias, undefined, JoinType.pivotJoin,
|
|
205
|
+
qb.join(field, nestedAlias, undefined, JoinType.pivotJoin, path);
|
|
205
206
|
}
|
|
206
207
|
else {
|
|
207
208
|
const prev = qb._fields?.slice();
|
|
208
|
-
qb[method](field, nestedAlias, undefined, JoinType.leftJoin,
|
|
209
|
+
qb[method](field, nestedAlias, undefined, JoinType.leftJoin, path);
|
|
209
210
|
if (!qb.hasFlag(QueryFlag.INFER_POPULATE)) {
|
|
210
211
|
qb._fields = prev;
|
|
211
212
|
}
|
|
212
213
|
}
|
|
214
|
+
if (!options || options.type !== 'orderBy') {
|
|
215
|
+
qb.scheduleFilterCheck(path);
|
|
216
|
+
}
|
|
213
217
|
return nestedAlias;
|
|
214
218
|
}
|
|
215
219
|
isPrefixed(field) {
|
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 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 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';
|
|
@@ -141,6 +141,15 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
141
141
|
* Apply filters to the QB where condition.
|
|
142
142
|
*/
|
|
143
143
|
applyFilters(filterOptions?: Dictionary<boolean | Dictionary> | string[] | boolean): Promise<void>;
|
|
144
|
+
private readonly autoJoinedPaths;
|
|
145
|
+
/**
|
|
146
|
+
* @internal
|
|
147
|
+
*/
|
|
148
|
+
scheduleFilterCheck(path: string): void;
|
|
149
|
+
/**
|
|
150
|
+
* @internal
|
|
151
|
+
*/
|
|
152
|
+
applyJoinedFilters(em: EntityManager, filterOptions?: Dictionary<boolean | Dictionary> | string[] | boolean): Promise<void>;
|
|
144
153
|
withSubQuery(subQuery: RawQueryFragment | NativeQueryBuilder, alias: string): this;
|
|
145
154
|
where(cond: QBFilterQuery<Entity>, operator?: keyof typeof GroupOperator): this;
|
|
146
155
|
where(cond: string, params?: any[], operator?: keyof typeof GroupOperator): this;
|
|
@@ -256,10 +265,6 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
256
265
|
* Executes the query, returning both array of results and total count query (without offset and limit).
|
|
257
266
|
*/
|
|
258
267
|
getResultAndCount(): Promise<[Entity[], number]>;
|
|
259
|
-
/**
|
|
260
|
-
* Provides promise-like interface so we can await the QB instance.
|
|
261
|
-
*/
|
|
262
|
-
then<TResult1 = any, TResult2 = never>(onfulfilled?: ((value: any) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<Loaded<Entity, Hint>[] | number | QueryResult<Entity>>;
|
|
263
268
|
/**
|
|
264
269
|
* Returns native query builder instance with sub-query aliased with given alias.
|
|
265
270
|
* You can provide `EntityName.propName` as alias, then the field name will be used based on the metadata
|
|
@@ -303,14 +308,12 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
303
308
|
export interface RunQueryBuilder<Entity extends object> extends Omit<QueryBuilder<Entity, any, any>, 'getResult' | 'getSingleResult' | 'getResultList' | 'where'> {
|
|
304
309
|
where(cond: QBFilterQuery<Entity> | string, params?: keyof typeof GroupOperator | any[], operator?: keyof typeof GroupOperator): this;
|
|
305
310
|
execute<Result = QueryResult<Entity>>(method?: 'all' | 'get' | 'run', mapResults?: boolean): Promise<Result>;
|
|
306
|
-
then<TResult1 = QueryResult<Entity>, TResult2 = never>(onfulfilled?: ((value: QueryResult<Entity>) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<QueryResult<Entity>>;
|
|
307
311
|
}
|
|
308
312
|
export interface SelectQueryBuilder<Entity extends object = AnyEntity, RootAlias extends string = never, Hint extends string = never, Context extends object = never> extends QueryBuilder<Entity, RootAlias, Hint, Context> {
|
|
309
313
|
execute<Result = Entity[]>(method?: 'all' | 'get' | 'run', mapResults?: boolean): Promise<Result>;
|
|
310
314
|
execute<Result = Entity[]>(method: 'all', mapResults?: boolean): Promise<Result>;
|
|
311
315
|
execute<Result = Entity>(method: 'get', mapResults?: boolean): Promise<Result>;
|
|
312
316
|
execute<Result = QueryResult<Entity>>(method: 'run', mapResults?: boolean): Promise<Result>;
|
|
313
|
-
then<TResult1 = Entity[], TResult2 = never>(onfulfilled?: ((value: Loaded<Entity, Hint>[]) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<Loaded<Entity, Hint>[]>;
|
|
314
317
|
}
|
|
315
318
|
export interface CountQueryBuilder<Entity extends object> extends QueryBuilder<Entity, any, any> {
|
|
316
319
|
execute<Result = {
|
|
@@ -325,7 +328,6 @@ export interface CountQueryBuilder<Entity extends object> extends QueryBuilder<E
|
|
|
325
328
|
execute<Result = QueryResult<{
|
|
326
329
|
count: number;
|
|
327
330
|
}>>(method: 'run', mapResults?: boolean): Promise<Result>;
|
|
328
|
-
then<TResult1 = number, TResult2 = never>(onfulfilled?: ((value: number) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<number>;
|
|
329
331
|
}
|
|
330
332
|
export interface InsertQueryBuilder<T extends object> extends RunQueryBuilder<T> {
|
|
331
333
|
}
|
package/query/QueryBuilder.js
CHANGED
|
@@ -240,9 +240,12 @@ export class QueryBuilder {
|
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
242
|
prop.targetMeta.props
|
|
243
|
-
.filter(prop =>
|
|
244
|
-
|
|
245
|
-
|
|
243
|
+
.filter(prop => {
|
|
244
|
+
if (!explicitFields) {
|
|
245
|
+
return this.platform.shouldHaveColumn(prop, populate);
|
|
246
|
+
}
|
|
247
|
+
return prop.primary && !explicitFields.includes(prop.name) && !explicitFields.includes(`${alias}.${prop.name}`);
|
|
248
|
+
})
|
|
246
249
|
.forEach(prop => fields.push(...this.driver.mapPropToFieldNames(this, prop, alias)));
|
|
247
250
|
return fields;
|
|
248
251
|
}
|
|
@@ -257,6 +260,40 @@ export class QueryBuilder {
|
|
|
257
260
|
const cond = await this.em.applyFilters(this.mainAlias.entityName, {}, filterOptions, 'read');
|
|
258
261
|
this.andWhere(cond);
|
|
259
262
|
}
|
|
263
|
+
autoJoinedPaths = [];
|
|
264
|
+
/**
|
|
265
|
+
* @internal
|
|
266
|
+
*/
|
|
267
|
+
scheduleFilterCheck(path) {
|
|
268
|
+
this.autoJoinedPaths.push(path);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* @internal
|
|
272
|
+
*/
|
|
273
|
+
async applyJoinedFilters(em, filterOptions = {}) {
|
|
274
|
+
for (const path of this.autoJoinedPaths) {
|
|
275
|
+
const join = this.getJoinForPath(path);
|
|
276
|
+
if (join.type === JoinType.pivotJoin) {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
const cond = await em.applyFilters(join.prop.type, join.cond, filterOptions, 'read');
|
|
280
|
+
if (Utils.hasObjectKeys(cond)) {
|
|
281
|
+
// remove nested filters, we only care about scalars here, nesting would require another join branch
|
|
282
|
+
for (const key of Object.keys(cond)) {
|
|
283
|
+
if (Utils.isPlainObject(cond[key]) && Object.keys(cond[key]).every(k => !(Utils.isOperator(k) && !['$some', '$none', '$every'].includes(k)))) {
|
|
284
|
+
delete cond[key];
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (Utils.hasObjectKeys(join.cond)) {
|
|
288
|
+
/* istanbul ignore next */
|
|
289
|
+
join.cond = { $and: [join.cond, cond] };
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
join.cond = { ...cond };
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
260
297
|
withSubQuery(subQuery, alias) {
|
|
261
298
|
this.ensureNotFinalized();
|
|
262
299
|
if (subQuery instanceof RawQueryFragment) {
|
|
@@ -335,7 +372,7 @@ export class QueryBuilder {
|
|
|
335
372
|
convertCustomTypes: false,
|
|
336
373
|
type: 'orderBy',
|
|
337
374
|
});
|
|
338
|
-
this._orderBy.push(CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, processed).process(this, { matchPopulateJoins: true }));
|
|
375
|
+
this._orderBy.push(CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, processed).process(this, { matchPopulateJoins: true, type: 'orderBy' }));
|
|
339
376
|
});
|
|
340
377
|
return this;
|
|
341
378
|
}
|
|
@@ -647,7 +684,7 @@ export class QueryBuilder {
|
|
|
647
684
|
}
|
|
648
685
|
const query = this.toQuery();
|
|
649
686
|
const cached = await this.em?.tryCache(this.mainAlias.entityName, this._cache, ['qb.execute', query.sql, query.params, method]);
|
|
650
|
-
if (cached?.data) {
|
|
687
|
+
if (cached?.data !== undefined) {
|
|
651
688
|
return cached.data;
|
|
652
689
|
}
|
|
653
690
|
const write = method === 'run' || !this.platform.getConfig().get('preferReadReplicas');
|
|
@@ -753,26 +790,6 @@ export class QueryBuilder {
|
|
|
753
790
|
await this.clone().getCount(),
|
|
754
791
|
];
|
|
755
792
|
}
|
|
756
|
-
/**
|
|
757
|
-
* Provides promise-like interface so we can await the QB instance.
|
|
758
|
-
*/
|
|
759
|
-
then(onfulfilled, onrejected) {
|
|
760
|
-
let type = this.type;
|
|
761
|
-
if (this.flags.has(QueryFlag.UPDATE_SUB_QUERY) || this.flags.has(QueryFlag.DELETE_SUB_QUERY)) {
|
|
762
|
-
type = QueryType.UPDATE;
|
|
763
|
-
}
|
|
764
|
-
switch (type) {
|
|
765
|
-
case QueryType.INSERT:
|
|
766
|
-
case QueryType.UPDATE:
|
|
767
|
-
case QueryType.DELETE:
|
|
768
|
-
case QueryType.UPSERT:
|
|
769
|
-
case QueryType.TRUNCATE:
|
|
770
|
-
return this.execute('run').then(onfulfilled, onrejected);
|
|
771
|
-
case QueryType.COUNT:
|
|
772
|
-
return this.getCount().then(onfulfilled, onrejected);
|
|
773
|
-
case QueryType.SELECT: return this.getResultList().then(onfulfilled, onrejected);
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
793
|
/**
|
|
777
794
|
* Returns native query builder instance with sub-query aliased with given alias.
|
|
778
795
|
* You can provide `EntityName.propName` as alias, then the field name will be used based on the metadata
|
|
@@ -1158,6 +1175,7 @@ export class QueryBuilder {
|
|
|
1158
1175
|
this._joins[aliasedName] = this.helper.joinOneToReference(prop, this.mainAlias.aliasName, alias, JoinType.leftJoin);
|
|
1159
1176
|
this._joins[aliasedName].path = `${(Object.values(this._joins).find(j => j.alias === fromAlias)?.path ?? meta.className)}.${prop.name}`;
|
|
1160
1177
|
this._populateMap[aliasedName] = this._joins[aliasedName].alias;
|
|
1178
|
+
this.createAlias(prop.type, alias);
|
|
1161
1179
|
}
|
|
1162
1180
|
});
|
|
1163
1181
|
this.processPopulateWhere(false);
|
|
@@ -1172,7 +1190,7 @@ export class QueryBuilder {
|
|
|
1172
1190
|
let joins = Object.values(this._joins);
|
|
1173
1191
|
for (const join of joins) {
|
|
1174
1192
|
join.cond_ ??= join.cond;
|
|
1175
|
-
join.cond =
|
|
1193
|
+
join.cond = { ...join.cond };
|
|
1176
1194
|
}
|
|
1177
1195
|
if (typeof this[key] === 'object') {
|
|
1178
1196
|
const cond = CriteriaNodeFactory
|
|
@@ -1219,9 +1237,7 @@ export class QueryBuilder {
|
|
|
1219
1237
|
}
|
|
1220
1238
|
}
|
|
1221
1239
|
hasToManyJoins() {
|
|
1222
|
-
// console.log(this._joins);
|
|
1223
1240
|
return Object.values(this._joins).some(join => {
|
|
1224
|
-
// console.log(join.prop.name, join.prop.kind, [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(join.prop.kind));
|
|
1225
1241
|
return [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(join.prop.kind);
|
|
1226
1242
|
});
|
|
1227
1243
|
}
|
|
@@ -39,9 +39,6 @@ export class QueryBuilderHelper {
|
|
|
39
39
|
const prop = this.getProperty(f, a);
|
|
40
40
|
const fkIdx2 = prop?.fieldNames.findIndex(name => name === f) ?? -1;
|
|
41
41
|
if (fkIdx2 !== -1) {
|
|
42
|
-
if (prop?.ownColumns && !prop.ownColumns.includes(f)) {
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
42
|
parts.push(this.mapper(a !== this.alias ? `${a}.${prop.fieldNames[fkIdx2]}` : prop.fieldNames[fkIdx2], type, value, alias));
|
|
46
43
|
}
|
|
47
44
|
else if (prop) {
|
|
@@ -62,9 +59,6 @@ export class QueryBuilderHelper {
|
|
|
62
59
|
}
|
|
63
60
|
});
|
|
64
61
|
}
|
|
65
|
-
if (parts.length === 1) {
|
|
66
|
-
return parts[0];
|
|
67
|
-
}
|
|
68
62
|
return raw('(' + parts.map(part => this.platform.quoteIdentifier(part)).join(', ') + ')');
|
|
69
63
|
}
|
|
70
64
|
const rawField = RawQueryFragment.getKnownFragment(field);
|
|
@@ -257,6 +251,105 @@ export class QueryBuilderHelper {
|
|
|
257
251
|
}
|
|
258
252
|
return { sql, params };
|
|
259
253
|
}
|
|
254
|
+
// <<<<<<< HEAD
|
|
255
|
+
// private processJoinClause(key: string, value: unknown, alias: string, params: Knex.Value[], operator = '$eq'): string {
|
|
256
|
+
// if (Utils.isGroupOperator(key) && Array.isArray(value)) {
|
|
257
|
+
// const parts = value.map(sub => {
|
|
258
|
+
// return this.wrapQueryGroup(Object.keys(sub).map(k => this.processJoinClause(k, sub[k], alias, params)));
|
|
259
|
+
// });
|
|
260
|
+
// return this.wrapQueryGroup(parts, key);
|
|
261
|
+
// }
|
|
262
|
+
//
|
|
263
|
+
// const rawField = RawQueryFragment.getKnownFragment(key);
|
|
264
|
+
//
|
|
265
|
+
// if (!rawField && !Utils.isOperator(key, false) && !this.isPrefixed(key)) {
|
|
266
|
+
// key = `${alias}.${key}`;
|
|
267
|
+
// }
|
|
268
|
+
//
|
|
269
|
+
// if (this.isSimpleRegExp(value)) {
|
|
270
|
+
// params.push(this.getRegExpParam(value));
|
|
271
|
+
// return `${this.knex.ref(this.mapper(key))} like ?`;
|
|
272
|
+
// }
|
|
273
|
+
//
|
|
274
|
+
// if (value instanceof RegExp) {
|
|
275
|
+
// value = this.platform.getRegExpValue(value);
|
|
276
|
+
// }
|
|
277
|
+
//
|
|
278
|
+
// if (Utils.isOperator(key, false) && Utils.isPlainObject(value)) {
|
|
279
|
+
// const parts = Object.keys(value).map(k => this.processJoinClause(k, (value as Dictionary)[k], alias, params, key));
|
|
280
|
+
//
|
|
281
|
+
// return key === '$not' ? `not ${this.wrapQueryGroup(parts)}` : this.wrapQueryGroup(parts);
|
|
282
|
+
// }
|
|
283
|
+
//
|
|
284
|
+
// if (Utils.isPlainObject(value) && Object.keys(value).every(k => Utils.isOperator(k, false))) {
|
|
285
|
+
// const parts = Object.keys(value).map(op => this.processJoinClause(key, (value as Dictionary)[op], alias, params, op));
|
|
286
|
+
//
|
|
287
|
+
// return this.wrapQueryGroup(parts);
|
|
288
|
+
// }
|
|
289
|
+
//
|
|
290
|
+
// const [fromAlias, fromField] = this.splitField(key as EntityKey);
|
|
291
|
+
// const prop = this.getProperty(fromField, fromAlias);
|
|
292
|
+
// operator = operator === '$not' ? '$eq' : operator;
|
|
293
|
+
// const column = this.mapper(key, undefined, undefined, null);
|
|
294
|
+
//
|
|
295
|
+
// if (value === null) {
|
|
296
|
+
// return `${this.knex.ref(column)} is ${operator === '$ne' ? 'not ' : ''}null`;
|
|
297
|
+
// }
|
|
298
|
+
//
|
|
299
|
+
// if (operator === '$fulltext' && prop) {
|
|
300
|
+
// const query = this.knex.raw(this.platform.getFullTextWhereClause(prop), {
|
|
301
|
+
// column,
|
|
302
|
+
// query: this.knex.raw('?'),
|
|
303
|
+
// }).toSQL().toNative();
|
|
304
|
+
// params.push(value as Knex.Value);
|
|
305
|
+
//
|
|
306
|
+
// return query.sql;
|
|
307
|
+
// }
|
|
308
|
+
//
|
|
309
|
+
// const replacement = this.getOperatorReplacement(operator, { [operator]: value });
|
|
310
|
+
//
|
|
311
|
+
// if (['$in', '$nin'].includes(operator) && Array.isArray(value)) {
|
|
312
|
+
// params.push(...value as Knex.Value[]);
|
|
313
|
+
// return `${this.knex.ref(column)} ${replacement} (${value.map(() => '?').join(', ')})`;
|
|
314
|
+
// }
|
|
315
|
+
//
|
|
316
|
+
// if (operator === '$exists') {
|
|
317
|
+
// value = null;
|
|
318
|
+
// }
|
|
319
|
+
//
|
|
320
|
+
// if (rawField) {
|
|
321
|
+
// let sql = rawField.sql.replaceAll(ALIAS_REPLACEMENT, alias);
|
|
322
|
+
// params.push(...rawField.params as Knex.Value[]);
|
|
323
|
+
// params.push(...Utils.asArray(value) as Knex.Value[]);
|
|
324
|
+
//
|
|
325
|
+
// if ((Utils.asArray(value) as Knex.Value[]).length > 0) {
|
|
326
|
+
// sql += ' = ?';
|
|
327
|
+
// }
|
|
328
|
+
//
|
|
329
|
+
// return sql;
|
|
330
|
+
// }
|
|
331
|
+
//
|
|
332
|
+
// if (value !== null) {
|
|
333
|
+
// if (prop?.customType) {
|
|
334
|
+
// value = prop.customType.convertToDatabaseValue(value, this.platform, { fromQuery: true, key, mode: 'query' });
|
|
335
|
+
// }
|
|
336
|
+
//
|
|
337
|
+
// params.push(value as Knex.Value);
|
|
338
|
+
// }
|
|
339
|
+
//
|
|
340
|
+
// return `${this.knex.ref(column)} ${replacement} ${value === null ? 'null' : '?'}`;
|
|
341
|
+
// }
|
|
342
|
+
//
|
|
343
|
+
// private wrapQueryGroup(parts: string[], operator = '$and') {
|
|
344
|
+
// if (parts.length === 1) {
|
|
345
|
+
// return parts[0];
|
|
346
|
+
// }
|
|
347
|
+
//
|
|
348
|
+
// return `(${parts.join(` ${GroupOperator[operator as keyof typeof GroupOperator]} `)})`;
|
|
349
|
+
// }
|
|
350
|
+
//
|
|
351
|
+
// mapJoinColumns(type: QueryType, join: JoinOptions): (string | Knex.Raw)[] {
|
|
352
|
+
// =======
|
|
260
353
|
mapJoinColumns(type, join) {
|
|
261
354
|
if (join.prop && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(join.prop.kind)) {
|
|
262
355
|
return join.prop.fieldNames.map((_fieldName, idx) => {
|
|
@@ -473,8 +566,8 @@ export class QueryBuilderHelper {
|
|
|
473
566
|
parts.push(sql);
|
|
474
567
|
params.push(...params2);
|
|
475
568
|
}
|
|
476
|
-
else if (
|
|
477
|
-
parts.push(`1 = 0`);
|
|
569
|
+
else if (['$in', '$nin'].includes(op) && Array.isArray(value[op]) && value[op].length === 0) {
|
|
570
|
+
parts.push(`1 = ${op === '$in' ? 0 : 1}`);
|
|
478
571
|
}
|
|
479
572
|
else if (value[op] instanceof RawQueryFragment || value[op] instanceof NativeQueryBuilder) {
|
|
480
573
|
const query = value[op] instanceof NativeQueryBuilder ? value[op].toRaw() : value[op];
|
package/schema/DatabaseTable.js
CHANGED
|
@@ -97,11 +97,14 @@ export class DatabaseTable {
|
|
|
97
97
|
ignoreSchemaChanges: prop.ignoreSchemaChanges,
|
|
98
98
|
};
|
|
99
99
|
this.columns[field].unsigned ??= this.columns[field].autoincrement;
|
|
100
|
+
if (this.nativeEnums[type]) {
|
|
101
|
+
this.columns[field].enumItems ??= this.nativeEnums[type].items;
|
|
102
|
+
}
|
|
100
103
|
const defaultValue = this.platform.getSchemaHelper().normalizeDefaultValue(prop.defaultRaw, prop.length);
|
|
101
104
|
this.columns[field].default = defaultValue;
|
|
102
105
|
});
|
|
103
106
|
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
104
|
-
const constraintName = this.getIndexName(true, prop.fieldNames, 'foreign');
|
|
107
|
+
const constraintName = this.getIndexName(prop.foreignKeyName ?? true, prop.fieldNames, 'foreign');
|
|
105
108
|
let schema = prop.targetMeta.root.schema === '*' ? this.schema : (prop.targetMeta.root.schema ?? config.get('schema', this.platform.getDefaultSchemaName()));
|
|
106
109
|
if (prop.referencedTableName.includes('.')) {
|
|
107
110
|
schema = undefined;
|
|
@@ -699,16 +702,28 @@ export class DatabaseTable {
|
|
|
699
702
|
}
|
|
700
703
|
addIndex(meta, index, type) {
|
|
701
704
|
const properties = Utils.unique(Utils.flatten(Utils.asArray(index.properties).map(prop => {
|
|
702
|
-
const
|
|
705
|
+
const parts = prop.split('.');
|
|
706
|
+
const root = parts[0];
|
|
703
707
|
if (meta.properties[prop]) {
|
|
704
708
|
if (meta.properties[prop].embeddedPath) {
|
|
705
709
|
return [meta.properties[prop].embeddedPath.join('.')];
|
|
706
710
|
}
|
|
707
711
|
return meta.properties[prop].fieldNames;
|
|
708
712
|
}
|
|
713
|
+
const rootProp = meta.properties[root];
|
|
714
|
+
// inline embedded property index, we need to find the field name of the child property
|
|
715
|
+
if (rootProp?.embeddable && !rootProp.object && parts.length > 1) {
|
|
716
|
+
const expand = (p, i) => {
|
|
717
|
+
if (parts.length === i) {
|
|
718
|
+
return p.fieldNames[0];
|
|
719
|
+
}
|
|
720
|
+
return expand(p.embeddedProps[parts[i]], i + 1);
|
|
721
|
+
};
|
|
722
|
+
return [expand(rootProp, 1)];
|
|
723
|
+
}
|
|
709
724
|
// json index, we need to rename the column only
|
|
710
|
-
if (
|
|
711
|
-
return [prop.replace(root,
|
|
725
|
+
if (rootProp) {
|
|
726
|
+
return [prop.replace(root, rootProp.fieldNames[0])];
|
|
712
727
|
}
|
|
713
728
|
/* v8 ignore next */
|
|
714
729
|
return [prop];
|
package/schema/SchemaHelper.js
CHANGED
|
@@ -119,9 +119,8 @@ export class SchemaHelper {
|
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
Utils.removeDuplicates(changedNativeEnums).forEach(([enumName, itemsNew, itemsOld]) => {
|
|
122
|
-
// postgres allows only adding new items
|
|
123
|
-
|
|
124
|
-
const newItems = itemsNew.filter(val => !itemsOld.includes(val.toLowerCase()));
|
|
122
|
+
// postgres allows only adding new items
|
|
123
|
+
const newItems = itemsNew.filter(val => !itemsOld.includes(val));
|
|
125
124
|
if (enumName.includes('.')) {
|
|
126
125
|
const [enumSchemaName, rawEnumName] = enumName.split('.');
|
|
127
126
|
ret.push(...newItems.map(val => this.getAlterNativeEnumSQL(rawEnumName, enumSchemaName, val, itemsNew, itemsOld)));
|
|
@@ -103,24 +103,14 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
|
|
|
103
103
|
return super.clearDatabase(options);
|
|
104
104
|
}
|
|
105
105
|
await this.execute(this.helper.disableForeignKeysSQL());
|
|
106
|
-
|
|
106
|
+
const schema = options?.schema ?? this.config.get('schema', this.platform.getDefaultSchemaName());
|
|
107
|
+
for (const meta of this.getOrderedMetadata(schema).reverse()) {
|
|
107
108
|
await this.driver.createQueryBuilder(meta.className, this.em?.getTransactionContext(), 'write', false)
|
|
108
|
-
.withSchema(
|
|
109
|
-
.truncate()
|
|
109
|
+
.withSchema(schema)
|
|
110
|
+
.truncate()
|
|
111
|
+
.execute();
|
|
110
112
|
}
|
|
111
113
|
await this.execute(this.helper.enableForeignKeysSQL());
|
|
112
|
-
// const parts: string[] = [this.helper.disableForeignKeysSQL()];
|
|
113
|
-
//
|
|
114
|
-
// for (const meta of this.getOrderedMetadata(options?.schema).reverse()) {
|
|
115
|
-
// const query = this.driver.createQueryBuilder(meta.className, this.em?.getTransactionContext(), 'write', false)
|
|
116
|
-
// .withSchema(options?.schema)
|
|
117
|
-
// .truncate()
|
|
118
|
-
// .getFormattedQuery();
|
|
119
|
-
// parts.push(query);
|
|
120
|
-
// }
|
|
121
|
-
//
|
|
122
|
-
// parts.push(this.helper.enableForeignKeysSQL());
|
|
123
|
-
// await this.execute(parts.join(';\n'));
|
|
124
114
|
this.clearIdentityMap();
|
|
125
115
|
}
|
|
126
116
|
async getDropSchemaSQL(options = {}) {
|
package/typings.d.ts
CHANGED
|
@@ -163,12 +163,14 @@ export interface IQueryBuilder<T> {
|
|
|
163
163
|
setFlag(flag: QueryFlag): this;
|
|
164
164
|
unsetFlag(flag: QueryFlag): this;
|
|
165
165
|
hasFlag(flag: QueryFlag): boolean;
|
|
166
|
+
scheduleFilterCheck(path: string): void;
|
|
166
167
|
}
|
|
167
168
|
export interface ICriteriaNodeProcessOptions {
|
|
168
169
|
alias?: string;
|
|
169
170
|
matchPopulateJoins?: boolean;
|
|
170
171
|
ignoreBranching?: boolean;
|
|
171
172
|
preferNoBranch?: boolean;
|
|
173
|
+
type?: 'orderBy';
|
|
172
174
|
}
|
|
173
175
|
export interface ICriteriaNode<T extends object> {
|
|
174
176
|
readonly entityName: string;
|