@mikro-orm/knex 7.0.0-dev.3 → 7.0.0-dev.30

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.
Files changed (36) hide show
  1. package/AbstractSqlConnection.d.ts +6 -4
  2. package/AbstractSqlConnection.js +33 -19
  3. package/AbstractSqlDriver.d.ts +7 -7
  4. package/AbstractSqlDriver.js +169 -163
  5. package/AbstractSqlPlatform.js +3 -3
  6. package/PivotCollectionPersister.d.ts +3 -2
  7. package/PivotCollectionPersister.js +6 -2
  8. package/README.md +1 -2
  9. package/dialects/mssql/MsSqlNativeQueryBuilder.d.ts +2 -0
  10. package/dialects/mssql/MsSqlNativeQueryBuilder.js +48 -5
  11. package/dialects/mysql/MySqlPlatform.js +2 -1
  12. package/dialects/sqlite/BaseSqliteConnection.d.ts +1 -1
  13. package/dialects/sqlite/BaseSqliteConnection.js +8 -2
  14. package/dialects/sqlite/BaseSqlitePlatform.d.ts +0 -1
  15. package/dialects/sqlite/BaseSqlitePlatform.js +0 -4
  16. package/dialects/sqlite/SqliteSchemaHelper.js +1 -1
  17. package/package.json +4 -4
  18. package/query/CriteriaNode.d.ts +1 -1
  19. package/query/CriteriaNode.js +5 -9
  20. package/query/CriteriaNodeFactory.js +10 -5
  21. package/query/NativeQueryBuilder.js +1 -1
  22. package/query/ObjectCriteriaNode.js +30 -7
  23. package/query/QueryBuilder.d.ts +15 -1
  24. package/query/QueryBuilder.js +92 -17
  25. package/query/QueryBuilderHelper.js +4 -10
  26. package/query/ScalarCriteriaNode.d.ts +3 -3
  27. package/query/ScalarCriteriaNode.js +7 -5
  28. package/schema/DatabaseSchema.js +18 -2
  29. package/schema/DatabaseTable.d.ts +5 -4
  30. package/schema/DatabaseTable.js +39 -6
  31. package/schema/SchemaComparator.js +1 -1
  32. package/schema/SchemaHelper.d.ts +2 -0
  33. package/schema/SchemaHelper.js +9 -5
  34. package/schema/SqlSchemaGenerator.d.ts +6 -1
  35. package/schema/SqlSchemaGenerator.js +25 -5
  36. package/typings.d.ts +7 -2
@@ -179,10 +179,10 @@ export class QueryBuilder {
179
179
  subquery = this.platform.formatQuery(rawFragment.sql, rawFragment.params);
180
180
  field = field[0];
181
181
  }
182
- const prop = this.joinReference(field, alias, cond, type, path, schema, subquery);
182
+ const { prop, key } = this.joinReference(field, alias, cond, type, path, schema, subquery);
183
183
  const [fromAlias] = this.helper.splitField(field);
184
184
  if (subquery) {
185
- this._joins[`${fromAlias}.${prop.name}#${alias}`].subquery = subquery;
185
+ this._joins[key].subquery = subquery;
186
186
  }
187
187
  const populate = this._joinedProps.get(fromAlias);
188
188
  const item = { field: prop.name, strategy: LoadStrategy.JOINED, children: [] };
@@ -240,9 +240,12 @@ export class QueryBuilder {
240
240
  }
241
241
  }
242
242
  prop.targetMeta.props
243
- .filter(prop => explicitFields
244
- ? explicitFields.includes(prop.name) || explicitFields.includes(`${alias}.${prop.name}`) || prop.primary
245
- : this.platform.shouldHaveColumn(prop, populate))
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
+ /* v8 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');
@@ -845,7 +882,8 @@ export class QueryBuilder {
845
882
  if (field instanceof RawQueryFragment) {
846
883
  field = this.platform.formatQuery(field.sql, field.params);
847
884
  }
848
- this._joins[`${this.alias}.${prop.name}#${alias}`] = {
885
+ const key = `${this.alias}.${prop.name}#${alias}`;
886
+ this._joins[key] = {
849
887
  prop,
850
888
  alias,
851
889
  type,
@@ -854,7 +892,7 @@ export class QueryBuilder {
854
892
  subquery: field.toString(),
855
893
  ownerAlias: this.alias,
856
894
  };
857
- return prop;
895
+ return { prop, key };
858
896
  }
859
897
  if (!subquery && type.includes('lateral')) {
860
898
  throw new Error(`Lateral join can be used only with a sub-query.`);
@@ -879,10 +917,13 @@ export class QueryBuilder {
879
917
  aliasMap: this.getAliasMap(),
880
918
  aliased: [QueryType.SELECT, QueryType.COUNT].includes(this.type),
881
919
  });
920
+ const criteriaNode = CriteriaNodeFactory.createNode(this.metadata, prop.targetMeta.className, cond);
921
+ cond = criteriaNode.process(this, { ignoreBranching: true, alias });
882
922
  let aliasedName = `${fromAlias}.${prop.name}#${alias}`;
883
923
  path ??= `${(Object.values(this._joins).find(j => j.alias === fromAlias)?.path ?? entityName)}.${prop.name}`;
884
924
  if (prop.kind === ReferenceKind.ONE_TO_MANY) {
885
925
  this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
926
+ this._joins[aliasedName].path ??= path;
886
927
  }
887
928
  else if (prop.kind === ReferenceKind.MANY_TO_MANY) {
888
929
  let pivotAlias = alias;
@@ -894,17 +935,18 @@ export class QueryBuilder {
894
935
  const joins = this.helper.joinManyToManyReference(prop, fromAlias, alias, pivotAlias, type, cond, path, schema);
895
936
  Object.assign(this._joins, joins);
896
937
  this.createAlias(prop.pivotEntity, pivotAlias);
938
+ this._joins[aliasedName].path ??= path;
939
+ aliasedName = Object.keys(joins)[1];
897
940
  }
898
941
  else if (prop.kind === ReferenceKind.ONE_TO_ONE) {
899
942
  this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
943
+ this._joins[aliasedName].path ??= path;
900
944
  }
901
945
  else { // MANY_TO_ONE
902
946
  this._joins[aliasedName] = this.helper.joinManyToOneReference(prop, fromAlias, alias, type, cond, schema);
947
+ this._joins[aliasedName].path ??= path;
903
948
  }
904
- if (!this._joins[aliasedName].path && path) {
905
- this._joins[aliasedName].path = path;
906
- }
907
- return prop;
949
+ return { prop, key: aliasedName };
908
950
  }
909
951
  prepareFields(fields, type = 'where') {
910
952
  const ret = [];
@@ -1079,6 +1121,7 @@ export class QueryBuilder {
1079
1121
  const meta = this.mainAlias.metadata;
1080
1122
  this.applyDiscriminatorCondition();
1081
1123
  this.processPopulateHint();
1124
+ this.processNestedJoins();
1082
1125
  if (meta && (this._fields?.includes('*') || this._fields?.includes(`${this.mainAlias.aliasName}.*`))) {
1083
1126
  meta.props
1084
1127
  .filter(prop => prop.formula && (!prop.lazy || this.flags.has(QueryFlag.INCLUDE_LAZY_FORMULAS)))
@@ -1138,6 +1181,7 @@ export class QueryBuilder {
1138
1181
  this._joins[aliasedName] = this.helper.joinOneToReference(prop, this.mainAlias.aliasName, alias, JoinType.leftJoin);
1139
1182
  this._joins[aliasedName].path = `${(Object.values(this._joins).find(j => j.alias === fromAlias)?.path ?? meta.className)}.${prop.name}`;
1140
1183
  this._populateMap[aliasedName] = this._joins[aliasedName].alias;
1184
+ this.createAlias(prop.type, alias);
1141
1185
  }
1142
1186
  });
1143
1187
  this.processPopulateWhere(false);
@@ -1152,12 +1196,12 @@ export class QueryBuilder {
1152
1196
  let joins = Object.values(this._joins);
1153
1197
  for (const join of joins) {
1154
1198
  join.cond_ ??= join.cond;
1155
- join.cond = filter ? { ...join.cond } : {};
1199
+ join.cond = { ...join.cond };
1156
1200
  }
1157
1201
  if (typeof this[key] === 'object') {
1158
1202
  const cond = CriteriaNodeFactory
1159
1203
  .createNode(this.metadata, this.mainAlias.entityName, this[key])
1160
- .process(this, { matchPopulateJoins: true, ignoreBranching: true, preferNoBranch: true });
1204
+ .process(this, { matchPopulateJoins: true, ignoreBranching: true, preferNoBranch: true, filter });
1161
1205
  // there might be new joins created by processing the `populateWhere` object
1162
1206
  joins = Object.values(this._joins);
1163
1207
  this.mergeOnConditions(joins, cond, filter);
@@ -1198,10 +1242,41 @@ export class QueryBuilder {
1198
1242
  }
1199
1243
  }
1200
1244
  }
1245
+ /**
1246
+ * When adding an inner join on a left joined relation, we need to nest them,
1247
+ * otherwise the inner join could discard rows of the root table.
1248
+ */
1249
+ processNestedJoins() {
1250
+ if (this.flags.has(QueryFlag.DISABLE_NESTED_INNER_JOIN)) {
1251
+ return;
1252
+ }
1253
+ const joins = Object.values(this._joins);
1254
+ const lookupParentGroup = (j) => {
1255
+ return j.nested ?? (j.parent ? lookupParentGroup(j.parent) : undefined);
1256
+ };
1257
+ for (const join of joins) {
1258
+ if (join.type === JoinType.innerJoin) {
1259
+ join.parent = joins.find(j => j.alias === join.ownerAlias);
1260
+ // https://stackoverflow.com/a/56815807/3665878
1261
+ if (join.parent?.type === JoinType.leftJoin || join.parent?.type === JoinType.nestedLeftJoin) {
1262
+ const nested = ((join.parent).nested ??= new Set());
1263
+ join.type = join.type === JoinType.innerJoin
1264
+ ? JoinType.nestedInnerJoin
1265
+ : JoinType.nestedLeftJoin;
1266
+ nested.add(join);
1267
+ }
1268
+ else if (join.parent?.type === JoinType.nestedInnerJoin) {
1269
+ const group = lookupParentGroup(join.parent);
1270
+ join.type = join.type === JoinType.innerJoin
1271
+ ? JoinType.nestedInnerJoin
1272
+ : JoinType.nestedLeftJoin;
1273
+ group?.add(join);
1274
+ }
1275
+ }
1276
+ }
1277
+ }
1201
1278
  hasToManyJoins() {
1202
- // console.log(this._joins);
1203
1279
  return Object.values(this._joins).some(join => {
1204
- // console.log(join.prop.name, join.prop.kind, [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(join.prop.kind));
1205
1280
  return [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(join.prop.kind);
1206
1281
  });
1207
1282
  }
@@ -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);
@@ -364,7 +358,7 @@ export class QueryBuilderHelper {
364
358
  return;
365
359
  }
366
360
  parts.push(operator === '$or' ? `(${res.sql})` : res.sql);
367
- params.push(...res.params);
361
+ res.params.forEach(p => params.push(p));
368
362
  }
369
363
  appendQuerySubCondition(type, cond, key) {
370
364
  const parts = [];
@@ -473,8 +467,8 @@ export class QueryBuilderHelper {
473
467
  parts.push(sql);
474
468
  params.push(...params2);
475
469
  }
476
- else if (op === '$in' && Array.isArray(value[op]) && value[op].length === 0) {
477
- parts.push(`1 = 0`);
470
+ else if (['$in', '$nin'].includes(op) && Array.isArray(value[op]) && value[op].length === 0) {
471
+ parts.push(`1 = ${op === '$in' ? 0 : 1}`);
478
472
  }
479
473
  else if (value[op] instanceof RawQueryFragment || value[op] instanceof NativeQueryBuilder) {
480
474
  const query = value[op] instanceof NativeQueryBuilder ? value[op].toRaw() : value[op];
@@ -508,7 +502,7 @@ export class QueryBuilderHelper {
508
502
  params.push(item);
509
503
  }
510
504
  else {
511
- params.push(...value);
505
+ value.forEach(v => params.push(v));
512
506
  }
513
507
  return `(${value.map(() => '?').join(', ')})`;
514
508
  }
@@ -1,10 +1,10 @@
1
1
  import { CriteriaNode } from './CriteriaNode.js';
2
- import type { IQueryBuilder, ICriteriaNodeProcessOptions } from '../typings.js';
2
+ import type { ICriteriaNodeProcessOptions, IQueryBuilder } from '../typings.js';
3
3
  /**
4
4
  * @internal
5
5
  */
6
6
  export declare class ScalarCriteriaNode<T extends object> extends CriteriaNode<T> {
7
7
  process(qb: IQueryBuilder<T>, options?: ICriteriaNodeProcessOptions): any;
8
- willAutoJoin<T>(qb: IQueryBuilder<T>, alias?: string, options?: ICriteriaNodeProcessOptions): boolean;
9
- shouldJoin(): boolean;
8
+ willAutoJoin(qb: IQueryBuilder<T>, alias?: string, options?: ICriteriaNodeProcessOptions): boolean;
9
+ private shouldJoin;
10
10
  }
@@ -1,13 +1,15 @@
1
1
  import { ReferenceKind, Utils } from '@mikro-orm/core';
2
2
  import { CriteriaNode } from './CriteriaNode.js';
3
- import { JoinType } from './enums.js';
3
+ import { JoinType, QueryType } from './enums.js';
4
4
  import { QueryBuilder } from './QueryBuilder.js';
5
5
  /**
6
6
  * @internal
7
7
  */
8
8
  export class ScalarCriteriaNode extends CriteriaNode {
9
9
  process(qb, options) {
10
- if (this.shouldJoin()) {
10
+ const matchPopulateJoins = options?.matchPopulateJoins || (this.prop && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind));
11
+ const nestedAlias = qb.getAliasForJoinPath(this.getPath(), { ...options, matchPopulateJoins });
12
+ if (this.shouldJoin(qb, nestedAlias)) {
11
13
  const path = this.getPath();
12
14
  const parentPath = this.parent.getPath(); // the parent is always there, otherwise `shouldJoin` would return `false`
13
15
  const nestedAlias = qb.getAliasForJoinPath(path) || qb.getNextAlias(this.prop?.pivotTable ?? this.entityName);
@@ -31,10 +33,10 @@ export class ScalarCriteriaNode extends CriteriaNode {
31
33
  return this.payload;
32
34
  }
33
35
  willAutoJoin(qb, alias, options) {
34
- return this.shouldJoin();
36
+ return this.shouldJoin(qb, alias);
35
37
  }
36
- shouldJoin() {
37
- if (!this.parent || !this.prop) {
38
+ shouldJoin(qb, nestedAlias) {
39
+ if (!this.parent || !this.prop || (nestedAlias && [QueryType.SELECT, QueryType.COUNT].includes(qb.type ?? QueryType.SELECT))) {
38
40
  return false;
39
41
  }
40
42
  switch (this.prop.kind) {
@@ -69,6 +69,7 @@ export class DatabaseSchema {
69
69
  static fromMetadata(metadata, platform, config, schemaName) {
70
70
  const schema = new DatabaseSchema(platform, schemaName ?? config.get('schema'));
71
71
  const nativeEnums = {};
72
+ const skipColumns = config.get('schemaGenerator').skipColumns || {};
72
73
  for (const meta of metadata) {
73
74
  for (const prop of meta.props) {
74
75
  if (prop.nativeEnumName) {
@@ -97,7 +98,7 @@ export class DatabaseSchema {
97
98
  const table = schema.addTable(meta.collection, this.getSchemaName(meta, config, schemaName));
98
99
  table.comment = meta.comment;
99
100
  for (const prop of meta.props) {
100
- if (!this.shouldHaveColumn(meta, prop)) {
101
+ if (!this.shouldHaveColumn(meta, prop, skipColumns)) {
101
102
  continue;
102
103
  }
103
104
  table.addColumnFromProperty(prop, meta, config);
@@ -129,10 +130,25 @@ export class DatabaseSchema {
129
130
  return ((takeTables?.some(tableNameToMatch => this.matchName(tableName, tableNameToMatch)) ?? true) &&
130
131
  !(skipTables?.some(tableNameToMatch => this.matchName(tableName, tableNameToMatch)) ?? false));
131
132
  }
132
- static shouldHaveColumn(meta, prop) {
133
+ static shouldHaveColumn(meta, prop, skipColumns) {
133
134
  if (prop.persist === false || (prop.columnTypes?.length ?? 0) === 0) {
134
135
  return false;
135
136
  }
137
+ // Check if column should be skipped
138
+ if (skipColumns) {
139
+ const tableName = meta.tableName;
140
+ const tableSchema = meta.schema;
141
+ const fullTableName = tableSchema ? `${tableSchema}.${tableName}` : tableName;
142
+ // Check for skipColumns by table name or fully qualified table name
143
+ const columnsToSkip = skipColumns[tableName] || skipColumns[fullTableName];
144
+ if (columnsToSkip) {
145
+ for (const fieldName of prop.fieldNames) {
146
+ if (columnsToSkip.some(pattern => this.matchName(fieldName, pattern))) {
147
+ return false;
148
+ }
149
+ }
150
+ }
151
+ }
136
152
  if (prop.kind === ReferenceKind.EMBEDDED && prop.object) {
137
153
  return true;
138
154
  }
@@ -1,4 +1,4 @@
1
- import { type Configuration, type DeferMode, type Dictionary, type EntityMetadata, type EntityProperty, type NamingStrategy } from '@mikro-orm/core';
1
+ import { type Configuration, type DeferMode, type Dictionary, type EntityMetadata, type EntityProperty, type NamingStrategy, type IndexCallback } from '@mikro-orm/core';
2
2
  import type { SchemaHelper } from './SchemaHelper.js';
3
3
  import type { CheckDef, Column, ForeignKey, IndexDef } from '../typings.js';
4
4
  import type { AbstractSqlPlatform } from '../AbstractSqlPlatform.js';
@@ -54,12 +54,13 @@ export declare class DatabaseTable {
54
54
  private getPropertyTypeForForeignKey;
55
55
  private getPropertyTypeForColumn;
56
56
  private getPropertyDefaultValue;
57
+ private processIndexExpression;
57
58
  addIndex(meta: EntityMetadata, index: {
58
- properties: string | string[];
59
+ properties?: string | string[];
59
60
  name?: string;
60
61
  type?: string;
61
- expression?: string;
62
- deferMode?: DeferMode;
62
+ expression?: string | IndexCallback<any>;
63
+ deferMode?: DeferMode | `${DeferMode}`;
63
64
  options?: Dictionary;
64
65
  }, type: 'index' | 'unique' | 'primary'): void;
65
66
  addCheck(check: CheckDef): void;
@@ -1,4 +1,4 @@
1
- import { Cascade, DecimalType, EntitySchema, ReferenceKind, t, Type, UnknownType, Utils, } from '@mikro-orm/core';
1
+ import { Cascade, DecimalType, EntitySchema, ReferenceKind, t, Type, UnknownType, Utils, RawQueryFragment, } from '@mikro-orm/core';
2
2
  /**
3
3
  * @internal
4
4
  */
@@ -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;
@@ -112,6 +115,7 @@ export class DatabaseTable {
112
115
  localTableName: this.getShortestName(false),
113
116
  referencedColumnNames: prop.referencedColumnNames,
114
117
  referencedTableName: schema ? `${schema}.${prop.referencedTableName}` : prop.referencedTableName,
118
+ createForeignKeyConstraint: prop.createForeignKeyConstraint,
115
119
  };
116
120
  const cascade = prop.cascade.includes(Cascade.REMOVE) || prop.cascade.includes(Cascade.ALL);
117
121
  if (prop.deleteRule || cascade || prop.nullable) {
@@ -697,18 +701,47 @@ export class DatabaseTable {
697
701
  }
698
702
  return '' + val;
699
703
  }
704
+ processIndexExpression(indexName, expression, meta) {
705
+ if (expression instanceof Function) {
706
+ const table = {
707
+ name: this.name,
708
+ schema: this.schema,
709
+ toString() {
710
+ if (this.schema) {
711
+ return `${this.schema}.${this.name}`;
712
+ }
713
+ return this.name;
714
+ },
715
+ };
716
+ const exp = expression(table, meta.createColumnMappingObject(), indexName);
717
+ return exp instanceof RawQueryFragment ? this.platform.formatQuery(exp.sql, exp.params) : exp;
718
+ }
719
+ return expression;
720
+ }
700
721
  addIndex(meta, index, type) {
701
722
  const properties = Utils.unique(Utils.flatten(Utils.asArray(index.properties).map(prop => {
702
- const root = prop.replace(/\..+$/, '');
723
+ const parts = prop.split('.');
724
+ const root = parts[0];
703
725
  if (meta.properties[prop]) {
704
726
  if (meta.properties[prop].embeddedPath) {
705
727
  return [meta.properties[prop].embeddedPath.join('.')];
706
728
  }
707
729
  return meta.properties[prop].fieldNames;
708
730
  }
731
+ const rootProp = meta.properties[root];
732
+ // inline embedded property index, we need to find the field name of the child property
733
+ if (rootProp?.embeddable && !rootProp.object && parts.length > 1) {
734
+ const expand = (p, i) => {
735
+ if (parts.length === i) {
736
+ return p.fieldNames[0];
737
+ }
738
+ return expand(p.embeddedProps[parts[i]], i + 1);
739
+ };
740
+ return [expand(rootProp, 1)];
741
+ }
709
742
  // json index, we need to rename the column only
710
- if (meta.properties[root]) {
711
- return [prop.replace(root, meta.properties[root].fieldNames[0])];
743
+ if (rootProp) {
744
+ return [prop.replace(root, rootProp.fieldNames[0])];
712
745
  }
713
746
  /* v8 ignore next */
714
747
  return [prop];
@@ -726,7 +759,7 @@ export class DatabaseTable {
726
759
  primary: type === 'primary',
727
760
  unique: type !== 'index',
728
761
  type: index.type,
729
- expression: index.expression,
762
+ expression: this.processIndexExpression(name, index.expression, meta),
730
763
  options: index.options,
731
764
  deferMode: index.deferMode,
732
765
  });
@@ -511,7 +511,7 @@ export class SchemaComparator {
511
511
  return str
512
512
  ?.replace(/_\w+'(.*?)'/g, '$1')
513
513
  .replace(/in\s*\((.*?)\)/ig, '= any (array[$1])')
514
- .replace(/['"`()]|::\w+| +/g, '')
514
+ .replace(/['"`()\n[\]]|::\w+| +/g, '')
515
515
  .replace(/anyarray\[(.*)]/ig, '$1')
516
516
  .toLowerCase();
517
517
  };
@@ -63,6 +63,8 @@ export declare abstract class SchemaHelper {
63
63
  disableForeignKeys?: boolean;
64
64
  createForeignKeyConstraints?: boolean;
65
65
  ignoreSchema?: string[];
66
+ skipTables?: (string | RegExp)[];
67
+ skipColumns?: Dictionary<(string | RegExp)[]>;
66
68
  managementDbName?: string;
67
69
  };
68
70
  protected processComment(comment: string): string;
@@ -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, the values are case insensitive
123
- itemsOld = itemsOld.map(v => v.toLowerCase());
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)));
@@ -264,7 +263,12 @@ export class SchemaHelper {
264
263
  if (column.autoincrement && !column.generated && !compositePK && (!changedProperties || changedProperties.has('autoincrement') || changedProperties.has('type'))) {
265
264
  Utils.runIfNotEmpty(() => col.push('primary key'), primaryKey && column.primary);
266
265
  }
267
- Utils.runIfNotEmpty(() => col.push(`default ${column.default}`), useDefault);
266
+ if (useDefault) {
267
+ // https://dev.mysql.com/doc/refman/9.0/en/data-type-defaults.html
268
+ const needsExpression = ['blob', 'text', 'json', 'point', 'linestring', 'polygon', 'multipoint', 'multilinestring', 'multipolygon', 'geometrycollection'].some(type => column.type.toLowerCase().startsWith(type));
269
+ const defaultSql = needsExpression && !column.default.startsWith('(') ? `(${column.default})` : column.default;
270
+ col.push(`default ${defaultSql}`);
271
+ }
268
272
  Utils.runIfNotEmpty(() => col.push(column.extra), column.extra);
269
273
  Utils.runIfNotEmpty(() => col.push(`comment ${this.platform.quoteValue(column.comment)}`), column.comment);
270
274
  return col.join(' ');
@@ -422,7 +426,7 @@ export class SchemaHelper {
422
426
  return `alter table ${table.getQuotedName()} comment = ${this.platform.quoteValue(comment ?? '')}`;
423
427
  }
424
428
  createForeignKey(table, foreignKey, alterTable = true, inline = false) {
425
- if (!this.options.createForeignKeyConstraints) {
429
+ if (!this.options.createForeignKeyConstraints || !foreignKey.createForeignKeyConstraint) {
426
430
  return '';
427
431
  }
428
432
  const constraintName = this.quote(foreignKey.constraintName);
@@ -1,4 +1,4 @@
1
- import { AbstractSchemaGenerator, type ClearDatabaseOptions, type CreateSchemaOptions, type DropSchemaOptions, type EnsureDatabaseOptions, type ISchemaGenerator, type MikroORM, type Transaction, type UpdateSchemaOptions } from '@mikro-orm/core';
1
+ import { AbstractSchemaGenerator, type ClearDatabaseOptions, type CreateSchemaOptions, type Dictionary, type DropSchemaOptions, type EnsureDatabaseOptions, type EntityMetadata, type ISchemaGenerator, type MikroORM, type Transaction, type UpdateSchemaOptions } from '@mikro-orm/core';
2
2
  import type { SchemaDifference } from '../typings.js';
3
3
  import { DatabaseSchema } from './DatabaseSchema.js';
4
4
  import type { AbstractSqlDriver } from '../AbstractSqlDriver.js';
@@ -8,6 +8,8 @@ export declare class SqlSchemaGenerator extends AbstractSchemaGenerator<Abstract
8
8
  disableForeignKeys?: boolean;
9
9
  createForeignKeyConstraints?: boolean;
10
10
  ignoreSchema?: string[];
11
+ skipTables?: (string | RegExp)[];
12
+ skipColumns?: Dictionary<(string | RegExp)[]>;
11
13
  managementDbName?: string;
12
14
  };
13
15
  protected lastEnsuredDatabase?: string;
@@ -18,6 +20,7 @@ export declare class SqlSchemaGenerator extends AbstractSchemaGenerator<Abstract
18
20
  */
19
21
  ensureDatabase(options?: EnsureDatabaseOptions): Promise<boolean>;
20
22
  getTargetSchema(schema?: string): DatabaseSchema;
23
+ protected getOrderedMetadata(schema?: string): EntityMetadata[];
21
24
  getCreateSchemaSQL(options?: CreateSchemaOptions): Promise<string>;
22
25
  dropSchema(options?: DropSchemaOptions): Promise<void>;
23
26
  createNamespace(name: string): Promise<void>;
@@ -54,5 +57,7 @@ export declare class SqlSchemaGenerator extends AbstractSchemaGenerator<Abstract
54
57
  dropTableIfExists(name: string, schema?: string): Promise<void>;
55
58
  private wrapSchema;
56
59
  private append;
60
+ private matchName;
61
+ private isTableSkipped;
57
62
  }
58
63
  export { SqlSchemaGenerator as SchemaGenerator };
@@ -47,6 +47,15 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
47
47
  const schemaName = schema ?? this.config.get('schema') ?? this.platform.getDefaultSchemaName();
48
48
  return DatabaseSchema.fromMetadata(metadata, this.platform, this.config, schemaName);
49
49
  }
50
+ getOrderedMetadata(schema) {
51
+ const metadata = super.getOrderedMetadata(schema);
52
+ // Filter out skipped tables
53
+ return metadata.filter(meta => {
54
+ const tableName = meta.tableName;
55
+ const tableSchema = meta.schema ?? schema ?? this.config.get('schema');
56
+ return !this.isTableSkipped(tableName, tableSchema);
57
+ });
58
+ }
50
59
  async getCreateSchemaSQL(options = {}) {
51
60
  const toSchema = this.getTargetSchema(options.schema);
52
61
  const ret = [];
@@ -103,9 +112,10 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
103
112
  return super.clearDatabase(options);
104
113
  }
105
114
  await this.execute(this.helper.disableForeignKeysSQL());
106
- for (const meta of this.getOrderedMetadata(options?.schema).reverse()) {
115
+ const schema = options?.schema ?? this.config.get('schema', this.platform.getDefaultSchemaName());
116
+ for (const meta of this.getOrderedMetadata(schema).reverse()) {
107
117
  await this.driver.createQueryBuilder(meta.className, this.em?.getTransactionContext(), 'write', false)
108
- .withSchema(options?.schema)
118
+ .withSchema(schema)
109
119
  .truncate()
110
120
  .execute();
111
121
  }
@@ -283,7 +293,6 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
283
293
  name ??= this.config.get('dbName');
284
294
  const sql = this.helper.getCreateDatabaseSQL('' + this.platform.quoteIdentifier(name));
285
295
  if (sql) {
286
- // console.log(sql);
287
296
  await this.execute(sql);
288
297
  }
289
298
  this.config.set('dbName', name);
@@ -317,12 +326,10 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
317
326
  if (this.platform.supportsMultipleStatements()) {
318
327
  for (const group of groups) {
319
328
  const query = group.join('\n');
320
- // console.log(query);
321
329
  await this.driver.execute(query);
322
330
  }
323
331
  return;
324
332
  }
325
- // console.log(groups);
326
333
  await Utils.runSerial(groups.flat(), line => this.driver.execute(line));
327
334
  }
328
335
  async dropTableIfExists(name, schema) {
@@ -348,6 +355,19 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
348
355
  append(array, sql, pad) {
349
356
  return this.helper.append(array, sql, pad);
350
357
  }
358
+ matchName(name, nameToMatch) {
359
+ return typeof nameToMatch === 'string'
360
+ ? name.toLocaleLowerCase() === nameToMatch.toLocaleLowerCase()
361
+ : nameToMatch.test(name);
362
+ }
363
+ isTableSkipped(tableName, schemaName) {
364
+ const skipTables = this.options.skipTables;
365
+ if (!skipTables || skipTables.length === 0) {
366
+ return false;
367
+ }
368
+ const fullTableName = schemaName ? `${schemaName}.${tableName}` : tableName;
369
+ return skipTables.some(pattern => this.matchName(tableName, pattern) || this.matchName(fullTableName, pattern));
370
+ }
351
371
  }
352
372
  // for back compatibility
353
373
  export { SqlSchemaGenerator as SchemaGenerator };