@mikro-orm/knex 7.0.0-dev.7 → 7.0.0-dev.70

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 (51) hide show
  1. package/AbstractSqlConnection.d.ts +11 -5
  2. package/AbstractSqlConnection.js +79 -32
  3. package/AbstractSqlDriver.d.ts +9 -5
  4. package/AbstractSqlDriver.js +268 -220
  5. package/AbstractSqlPlatform.js +3 -3
  6. package/PivotCollectionPersister.d.ts +3 -2
  7. package/PivotCollectionPersister.js +12 -21
  8. package/README.md +3 -2
  9. package/SqlEntityManager.d.ts +9 -2
  10. package/SqlEntityManager.js +2 -2
  11. package/dialects/mssql/MsSqlNativeQueryBuilder.d.ts +2 -0
  12. package/dialects/mssql/MsSqlNativeQueryBuilder.js +43 -2
  13. package/dialects/mysql/MySqlPlatform.js +2 -1
  14. package/dialects/postgresql/PostgreSqlTableCompiler.d.ts +1 -0
  15. package/dialects/postgresql/PostgreSqlTableCompiler.js +1 -0
  16. package/dialects/sqlite/BaseSqliteConnection.d.ts +4 -2
  17. package/dialects/sqlite/BaseSqliteConnection.js +8 -5
  18. package/dialects/sqlite/BaseSqlitePlatform.js +1 -2
  19. package/dialects/sqlite/SqliteSchemaHelper.js +1 -1
  20. package/index.d.ts +1 -1
  21. package/index.js +1 -1
  22. package/package.json +5 -5
  23. package/query/ArrayCriteriaNode.d.ts +1 -0
  24. package/query/ArrayCriteriaNode.js +3 -0
  25. package/query/CriteriaNode.d.ts +4 -2
  26. package/query/CriteriaNode.js +11 -6
  27. package/query/CriteriaNodeFactory.js +12 -7
  28. package/query/NativeQueryBuilder.js +1 -1
  29. package/query/ObjectCriteriaNode.d.ts +1 -0
  30. package/query/ObjectCriteriaNode.js +38 -9
  31. package/query/QueryBuilder.d.ts +59 -7
  32. package/query/QueryBuilder.js +171 -47
  33. package/query/QueryBuilderHelper.d.ts +1 -1
  34. package/query/QueryBuilderHelper.js +15 -8
  35. package/query/ScalarCriteriaNode.d.ts +3 -3
  36. package/query/ScalarCriteriaNode.js +9 -7
  37. package/query/index.d.ts +1 -0
  38. package/query/index.js +1 -0
  39. package/query/raw.d.ts +59 -0
  40. package/query/raw.js +68 -0
  41. package/query/rawKnex.d.ts +58 -0
  42. package/query/rawKnex.js +72 -0
  43. package/schema/DatabaseSchema.js +25 -4
  44. package/schema/DatabaseTable.d.ts +5 -4
  45. package/schema/DatabaseTable.js +67 -33
  46. package/schema/SchemaComparator.js +2 -2
  47. package/schema/SchemaHelper.d.ts +2 -0
  48. package/schema/SchemaHelper.js +8 -4
  49. package/schema/SqlSchemaGenerator.d.ts +13 -6
  50. package/schema/SqlSchemaGenerator.js +38 -17
  51. package/typings.d.ts +85 -3
@@ -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 (prop?.embedded && a === prop.embedded[0]) {
78
+ if (a === prop?.embedded?.[0]) {
79
79
  return aliasPrefix + prop.fieldNames[fkIdx];
80
80
  }
81
- const noPrefix = prop && prop.persist === false;
81
+ const noPrefix = prop?.persist === false;
82
82
  if (prop?.fieldNameRaw) {
83
83
  return raw(this.prefix(field, isTableNameAliasRequired));
84
84
  }
@@ -358,7 +358,7 @@ export class QueryBuilderHelper {
358
358
  return;
359
359
  }
360
360
  parts.push(operator === '$or' ? `(${res.sql})` : res.sql);
361
- params.push(...res.params);
361
+ res.params.forEach(p => params.push(p));
362
362
  }
363
363
  appendQuerySubCondition(type, cond, key) {
364
364
  const parts = [];
@@ -502,7 +502,7 @@ export class QueryBuilderHelper {
502
502
  params.push(item);
503
503
  }
504
504
  else {
505
- params.push(...value);
505
+ value.forEach(v => params.push(v));
506
506
  }
507
507
  return `(${value.map(() => '?').join(', ')})`;
508
508
  }
@@ -539,7 +539,7 @@ export class QueryBuilderHelper {
539
539
  const ret = [];
540
540
  for (const key of Object.keys(orderBy)) {
541
541
  const direction = orderBy[key];
542
- const order = Utils.isNumber(direction) ? QueryOrderNumeric[direction] : direction;
542
+ const order = typeof direction === 'number' ? QueryOrderNumeric[direction] : direction;
543
543
  const raw = RawQueryFragment.getKnownFragment(key);
544
544
  if (raw) {
545
545
  ret.push(...this.platform.getOrderByExpression(this.platform.formatQuery(raw.sql, raw.params), order));
@@ -550,10 +550,10 @@ 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 && prop.persist === false && !prop.formula && !prop.embedded) || RawQueryFragment.isKnownFragment(f);
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
- const rawColumn = Utils.isString(column) ? column.split('.').map(e => this.platform.quoteIdentifier(e)).join('.') : column;
556
+ const rawColumn = typeof column === 'string' ? column.split('.').map(e => this.platform.quoteIdentifier(e)).join('.') : column;
557
557
  const customOrder = prop?.customOrder;
558
558
  let colPart = customOrder
559
559
  ? this.platform.generateCustomOrder(rawColumn, customOrder)
@@ -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) {
@@ -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
- import { ReferenceKind, Utils } from '@mikro-orm/core';
1
+ import { ARRAY_OPERATORS, ReferenceKind } 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);
@@ -23,7 +25,7 @@ export class ScalarCriteriaNode extends CriteriaNode {
23
25
  return this.payload.getNativeQuery().toRaw();
24
26
  }
25
27
  if (this.payload && typeof this.payload === 'object') {
26
- const keys = Object.keys(this.payload).filter(key => Utils.isArrayOperator(key) && Array.isArray(this.payload[key]));
28
+ const keys = Object.keys(this.payload).filter(key => ARRAY_OPERATORS.includes(key) && Array.isArray(this.payload[key]));
27
29
  for (const key of keys) {
28
30
  this.payload[key] = JSON.stringify(this.payload[key]);
29
31
  }
@@ -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) {
package/query/index.d.ts CHANGED
@@ -7,3 +7,4 @@ export * from './ObjectCriteriaNode.js';
7
7
  export * from './ScalarCriteriaNode.js';
8
8
  export * from './CriteriaNodeFactory.js';
9
9
  export * from './NativeQueryBuilder.js';
10
+ export * from './raw.js';
package/query/index.js CHANGED
@@ -7,3 +7,4 @@ export * from './ObjectCriteriaNode.js';
7
7
  export * from './ScalarCriteriaNode.js';
8
8
  export * from './CriteriaNodeFactory.js';
9
9
  export * from './NativeQueryBuilder.js';
10
+ export * from './raw.js';
package/query/raw.d.ts ADDED
@@ -0,0 +1,59 @@
1
+ import { type AnyString, type Dictionary, type EntityKey, type RawQueryFragment } from '@mikro-orm/core';
2
+ import type { SelectQueryBuilder } from 'kysely';
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 declare function raw<T extends object = any, R = any>(sql: SelectQueryBuilder<any, any, any> | 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,68 @@
1
+ import { raw as raw_, Utils } 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 function raw(sql, params) {
59
+ if (Utils.isObject(sql) && 'compile' in sql) {
60
+ const query = sql.compile();
61
+ return raw_(query.sql, query.parameters);
62
+ }
63
+ if (sql instanceof QueryBuilder) {
64
+ const query = sql.toQuery();
65
+ return raw_(query.sql, query.params);
66
+ }
67
+ return raw_(sql, params);
68
+ }
@@ -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>;
@@ -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
+ }
@@ -1,4 +1,4 @@
1
- import { ReferenceKind } from '@mikro-orm/core';
1
+ import { ReferenceKind, RawQueryFragment, } from '@mikro-orm/core';
2
2
  import { DatabaseTable } from './DatabaseTable.js';
3
3
  /**
4
4
  * @internal
@@ -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);
@@ -107,9 +108,14 @@ export class DatabaseSchema {
107
108
  table.addIndex(meta, { properties: meta.props.filter(prop => prop.primary).map(prop => prop.name) }, 'primary');
108
109
  for (const check of meta.checks) {
109
110
  const columnName = check.property ? meta.properties[check.property].fieldNames[0] : undefined;
111
+ let expression = check.expression;
112
+ const raw = RawQueryFragment.getKnownFragment(expression);
113
+ if (raw) {
114
+ expression = platform.formatQuery(raw.sql, raw.params);
115
+ }
110
116
  table.addCheck({
111
117
  name: check.name,
112
- expression: check.expression,
118
+ expression,
113
119
  definition: `check (${check.expression})`,
114
120
  columnName,
115
121
  });
@@ -129,10 +135,25 @@ export class DatabaseSchema {
129
135
  return ((takeTables?.some(tableNameToMatch => this.matchName(tableName, tableNameToMatch)) ?? true) &&
130
136
  !(skipTables?.some(tableNameToMatch => this.matchName(tableName, tableNameToMatch)) ?? false));
131
137
  }
132
- static shouldHaveColumn(meta, prop) {
138
+ static shouldHaveColumn(meta, prop, skipColumns) {
133
139
  if (prop.persist === false || (prop.columnTypes?.length ?? 0) === 0) {
134
140
  return false;
135
141
  }
142
+ // Check if column should be skipped
143
+ if (skipColumns) {
144
+ const tableName = meta.tableName;
145
+ const tableSchema = meta.schema;
146
+ const fullTableName = tableSchema ? `${tableSchema}.${tableName}` : tableName;
147
+ // Check for skipColumns by table name or fully qualified table name
148
+ const columnsToSkip = skipColumns[tableName] || skipColumns[fullTableName];
149
+ if (columnsToSkip) {
150
+ for (const fieldName of prop.fieldNames) {
151
+ if (columnsToSkip.some(pattern => this.matchName(fieldName, pattern))) {
152
+ return false;
153
+ }
154
+ }
155
+ }
156
+ }
136
157
  if (prop.kind === ReferenceKind.EMBEDDED && prop.object) {
137
158
  return true;
138
159
  }
@@ -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 IndexCallback, type NamingStrategy } 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;