@mikro-orm/knex 7.0.0-dev.8 → 7.0.0-dev.80

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 (55) hide show
  1. package/AbstractSqlConnection.d.ts +11 -5
  2. package/AbstractSqlConnection.js +78 -32
  3. package/AbstractSqlDriver.d.ts +9 -5
  4. package/AbstractSqlDriver.js +274 -226
  5. package/AbstractSqlPlatform.js +5 -5
  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 +44 -3
  13. package/dialects/mysql/MySqlExceptionConverter.d.ts +3 -3
  14. package/dialects/mysql/MySqlExceptionConverter.js +4 -5
  15. package/dialects/mysql/MySqlSchemaHelper.js +2 -2
  16. package/dialects/postgresql/PostgreSqlTableCompiler.d.ts +1 -0
  17. package/dialects/postgresql/PostgreSqlTableCompiler.js +1 -0
  18. package/dialects/sqlite/BaseSqliteConnection.d.ts +3 -2
  19. package/dialects/sqlite/BaseSqliteConnection.js +2 -8
  20. package/dialects/sqlite/BaseSqlitePlatform.js +1 -2
  21. package/dialects/sqlite/SqliteExceptionConverter.d.ts +2 -2
  22. package/dialects/sqlite/SqliteExceptionConverter.js +6 -4
  23. package/dialects/sqlite/SqliteSchemaHelper.js +5 -6
  24. package/index.d.ts +1 -1
  25. package/index.js +1 -1
  26. package/package.json +5 -5
  27. package/query/ArrayCriteriaNode.d.ts +1 -0
  28. package/query/ArrayCriteriaNode.js +3 -0
  29. package/query/CriteriaNode.d.ts +4 -2
  30. package/query/CriteriaNode.js +11 -6
  31. package/query/CriteriaNodeFactory.js +12 -7
  32. package/query/NativeQueryBuilder.js +1 -1
  33. package/query/ObjectCriteriaNode.d.ts +1 -0
  34. package/query/ObjectCriteriaNode.js +39 -10
  35. package/query/QueryBuilder.d.ts +59 -7
  36. package/query/QueryBuilder.js +177 -53
  37. package/query/QueryBuilderHelper.d.ts +1 -1
  38. package/query/QueryBuilderHelper.js +18 -11
  39. package/query/ScalarCriteriaNode.d.ts +3 -3
  40. package/query/ScalarCriteriaNode.js +9 -7
  41. package/query/index.d.ts +1 -0
  42. package/query/index.js +1 -0
  43. package/query/raw.d.ts +59 -0
  44. package/query/raw.js +68 -0
  45. package/query/rawKnex.d.ts +58 -0
  46. package/query/rawKnex.js +72 -0
  47. package/schema/DatabaseSchema.js +25 -4
  48. package/schema/DatabaseTable.d.ts +5 -4
  49. package/schema/DatabaseTable.js +68 -34
  50. package/schema/SchemaComparator.js +4 -4
  51. package/schema/SchemaHelper.d.ts +2 -0
  52. package/schema/SchemaHelper.js +14 -10
  53. package/schema/SqlSchemaGenerator.d.ts +13 -6
  54. package/schema/SqlSchemaGenerator.js +40 -19
  55. package/typings.d.ts +85 -3
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;
@@ -1,4 +1,4 @@
1
- import { Cascade, DecimalType, EntitySchema, ReferenceKind, t, Type, UnknownType, Utils, } from '@mikro-orm/core';
1
+ import { Cascade, DecimalType, EntitySchema, RawQueryFragment, ReferenceKind, t, Type, UnknownType, Utils, } from '@mikro-orm/core';
2
2
  /**
3
3
  * @internal
4
4
  */
@@ -63,7 +63,7 @@ export class DatabaseTable {
63
63
  const mappedType = this.platform.getMappedType(type);
64
64
  if (mappedType instanceof DecimalType) {
65
65
  const match = prop.columnTypes[idx].match(/\w+\((\d+), ?(\d+)\)/);
66
- /* v8 ignore next 5 */
66
+ /* v8 ignore next */
67
67
  if (match) {
68
68
  prop.precision ??= +match[1];
69
69
  prop.scale ??= +match[2];
@@ -91,47 +91,52 @@ export class DatabaseTable {
91
91
  precision: prop.precision,
92
92
  scale: prop.scale,
93
93
  default: prop.defaultRaw,
94
- enumItems: prop.nativeEnumName || prop.items?.every(Utils.isString) ? prop.items : undefined,
94
+ enumItems: prop.nativeEnumName || prop.items?.every(i => typeof i === 'string') ? prop.items : undefined,
95
95
  comment: prop.comment,
96
96
  extra: prop.extra,
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;
108
111
  }
109
- this.foreignKeys[constraintName] = {
110
- constraintName,
111
- columnNames: prop.fieldNames,
112
- localTableName: this.getShortestName(false),
113
- referencedColumnNames: prop.referencedColumnNames,
114
- referencedTableName: schema ? `${schema}.${prop.referencedTableName}` : prop.referencedTableName,
115
- };
116
- const cascade = prop.cascade.includes(Cascade.REMOVE) || prop.cascade.includes(Cascade.ALL);
117
- if (prop.deleteRule || cascade || prop.nullable) {
118
- this.foreignKeys[constraintName].deleteRule = prop.deleteRule || (cascade ? 'cascade' : 'set null');
119
- }
120
- if (prop.updateRule) {
121
- this.foreignKeys[constraintName].updateRule = prop.updateRule || 'cascade';
122
- }
123
- if ((prop.cascade.includes(Cascade.PERSIST) || prop.cascade.includes(Cascade.ALL))) {
124
- const hasCascadePath = Object.values(this.foreignKeys).some(fk => {
125
- return fk.constraintName !== constraintName
126
- && ((fk.updateRule && fk.updateRule !== 'no action') || (fk.deleteRule && fk.deleteRule !== 'no action'))
127
- && fk.referencedTableName === this.foreignKeys[constraintName].referencedTableName;
128
- });
129
- if (!hasCascadePath || this.platform.supportsMultipleCascadePaths()) {
130
- this.foreignKeys[constraintName].updateRule ??= 'cascade';
112
+ if (prop.createForeignKeyConstraint) {
113
+ this.foreignKeys[constraintName] = {
114
+ constraintName,
115
+ columnNames: prop.fieldNames,
116
+ localTableName: this.getShortestName(false),
117
+ referencedColumnNames: prop.referencedColumnNames,
118
+ referencedTableName: schema ? `${schema}.${prop.referencedTableName}` : prop.referencedTableName,
119
+ };
120
+ const cascade = prop.cascade.includes(Cascade.REMOVE) || prop.cascade.includes(Cascade.ALL);
121
+ if (prop.deleteRule || cascade || prop.nullable) {
122
+ this.foreignKeys[constraintName].deleteRule = prop.deleteRule || (cascade ? 'cascade' : 'set null');
123
+ }
124
+ if (prop.updateRule) {
125
+ this.foreignKeys[constraintName].updateRule = prop.updateRule || 'cascade';
126
+ }
127
+ if ((prop.cascade.includes(Cascade.PERSIST) || prop.cascade.includes(Cascade.ALL))) {
128
+ const hasCascadePath = Object.values(this.foreignKeys).some(fk => {
129
+ return fk.constraintName !== constraintName
130
+ && ((fk.updateRule && fk.updateRule !== 'no action') || (fk.deleteRule && fk.deleteRule !== 'no action'))
131
+ && fk.referencedTableName === this.foreignKeys[constraintName].referencedTableName;
132
+ });
133
+ if (!hasCascadePath || this.platform.supportsMultipleCascadePaths()) {
134
+ this.foreignKeys[constraintName].updateRule ??= 'cascade';
135
+ }
136
+ }
137
+ if (prop.deferMode) {
138
+ this.foreignKeys[constraintName].deferMode = prop.deferMode;
131
139
  }
132
- }
133
- if (prop.deferMode) {
134
- this.foreignKeys[constraintName].deferMode = prop.deferMode;
135
140
  }
136
141
  }
137
142
  if (prop.index) {
@@ -157,7 +162,7 @@ export class DatabaseTable {
157
162
  }
158
163
  }
159
164
  getIndexName(value, columnNames, type) {
160
- if (Utils.isString(value)) {
165
+ if (typeof value === 'string') {
161
166
  return value;
162
167
  }
163
168
  return this.platform.getIndexName(this.name, columnNames, type);
@@ -610,7 +615,7 @@ export class DatabaseTable {
610
615
  fkOptions.deferMode = fk.deferMode;
611
616
  fkOptions.columnTypes = fk.columnNames.map(col => this.getColumn(col).type);
612
617
  }
613
- return {
618
+ const ret = {
614
619
  name: prop,
615
620
  type,
616
621
  runtimeType,
@@ -638,6 +643,11 @@ export class DatabaseTable {
638
643
  persist,
639
644
  ...fkOptions,
640
645
  };
646
+ const nativeEnumName = Object.keys(this.nativeEnums).find(name => name === column.type);
647
+ if (nativeEnumName) {
648
+ ret.nativeEnumName = nativeEnumName;
649
+ }
650
+ return ret;
641
651
  }
642
652
  getReferenceKind(fk, unique) {
643
653
  if (fk && unique) {
@@ -671,10 +681,17 @@ export class DatabaseTable {
671
681
  if (fk) {
672
682
  return this.getPropertyTypeForForeignKey(namingStrategy, fk);
673
683
  }
684
+ const enumMode = this.platform.getConfig().get('entityGenerator').enumMode;
674
685
  // If this column is using an enum.
675
686
  if (column.enumItems?.length) {
676
- // We will create a new enum name for this type and set it as the property type as well.
677
- return namingStrategy.getEnumClassName(column.name, this.name, this.schema);
687
+ const name = column.nativeEnumName ?? column.name;
688
+ const tableName = column.nativeEnumName ? undefined : this.name;
689
+ if (enumMode === 'ts-enum') {
690
+ // We will create a new enum name for this type and set it as the property type as well.
691
+ return namingStrategy.getEnumClassName(name, tableName, this.schema);
692
+ }
693
+ // With other enum strategies, we need to use the type name.
694
+ return namingStrategy.getEnumTypeName(name, tableName, this.schema);
678
695
  }
679
696
  return column.mappedType?.runtimeType ?? 'unknown';
680
697
  }
@@ -697,6 +714,23 @@ export class DatabaseTable {
697
714
  }
698
715
  return '' + val;
699
716
  }
717
+ processIndexExpression(indexName, expression, meta) {
718
+ if (expression instanceof Function) {
719
+ const table = {
720
+ name: this.name,
721
+ schema: this.schema,
722
+ toString() {
723
+ if (this.schema) {
724
+ return `${this.schema}.${this.name}`;
725
+ }
726
+ return this.name;
727
+ },
728
+ };
729
+ const exp = expression(table, meta.createColumnMappingObject(), indexName);
730
+ return exp instanceof RawQueryFragment ? this.platform.formatQuery(exp.sql, exp.params) : exp;
731
+ }
732
+ return expression;
733
+ }
700
734
  addIndex(meta, index, type) {
701
735
  const properties = Utils.unique(Utils.flatten(Utils.asArray(index.properties).map(prop => {
702
736
  const parts = prop.split('.');
@@ -738,7 +772,7 @@ export class DatabaseTable {
738
772
  primary: type === 'primary',
739
773
  unique: type !== 'index',
740
774
  type: index.type,
741
- expression: index.expression,
775
+ expression: this.processIndexExpression(name, index.expression, meta),
742
776
  options: index.options,
743
777
  deferMode: index.deferMode,
744
778
  });
@@ -269,7 +269,7 @@ export class SchemaComparator {
269
269
  }
270
270
  for (const toConstraint of Object.values(toForeignKeys)) {
271
271
  tableDifferences.addedForeignKeys[toConstraint.constraintName] = toConstraint;
272
- this.log(`FK constraint ${toConstraint.constraintName} added from table ${tableDifferences.name}`, { constraint: toConstraint });
272
+ this.log(`FK constraint ${toConstraint.constraintName} added to table ${tableDifferences.name}`, { constraint: toConstraint });
273
273
  changes++;
274
274
  }
275
275
  return changes ? tableDifferences : false;
@@ -308,7 +308,7 @@ export class SchemaComparator {
308
308
  const [removedColumn, addedColumn] = candidateColumns[0];
309
309
  const removedColumnName = removedColumn.name;
310
310
  const addedColumnName = addedColumn.name;
311
- /* v8 ignore next 3 */
311
+ /* v8 ignore next */
312
312
  if (tableDifferences.renamedColumns[removedColumnName]) {
313
313
  continue;
314
314
  }
@@ -511,14 +511,14 @@ 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
  };
518
518
  return simplify(expr1) !== simplify(expr2);
519
519
  }
520
520
  parseJsonDefault(defaultValue) {
521
- /* v8 ignore next 3 */
521
+ /* v8 ignore next */
522
522
  if (!defaultValue) {
523
523
  return null;
524
524
  }
@@ -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;
@@ -74,7 +74,7 @@ export class SchemaHelper {
74
74
  return `alter table ${tableReference} rename column ${oldColumnName} to ${columnName}`;
75
75
  }
76
76
  getCreateIndexSQL(tableName, index) {
77
- /* v8 ignore next 3 */
77
+ /* v8 ignore next */
78
78
  if (index.expression) {
79
79
  return index.expression;
80
80
  }
@@ -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)));
@@ -142,7 +141,7 @@ export class SchemaHelper {
142
141
  for (const check of Object.values(diff.changedChecks)) {
143
142
  ret.push(this.dropConstraint(diff.name, check.name));
144
143
  }
145
- /* v8 ignore next 3 */
144
+ /* v8 ignore next */
146
145
  if (!safe && Object.values(diff.removedColumns).length > 0) {
147
146
  ret.push(this.getDropColumnsSQL(tableName, Object.values(diff.removedColumns), schemaName));
148
147
  }
@@ -225,7 +224,7 @@ export class SchemaHelper {
225
224
  const defaultName = this.platform.getDefaultPrimaryName(table.name, pkIndex.columnNames);
226
225
  return pkIndex?.keyName !== defaultName;
227
226
  }
228
- /* v8 ignore next 3 */
227
+ /* v8 ignore next */
229
228
  castColumn(name, type) {
230
229
  return '';
231
230
  }
@@ -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(' ');
@@ -337,11 +341,11 @@ export class SchemaHelper {
337
341
  getDropDatabaseSQL(name) {
338
342
  return `drop database if exists ${this.quote(name)}`;
339
343
  }
340
- /* v8 ignore next 3 */
344
+ /* v8 ignore next */
341
345
  getCreateNamespaceSQL(name) {
342
346
  return `create schema if not exists ${this.quote(name)}`;
343
347
  }
344
- /* v8 ignore next 3 */
348
+ /* v8 ignore next */
345
349
  getDropNamespaceSQL(name) {
346
350
  return `drop schema if exists ${this.quote(name)}`;
347
351
  }
@@ -463,7 +467,7 @@ export class SchemaHelper {
463
467
  getReferencedTableName(referencedTableName, schema) {
464
468
  const [schemaName, tableName] = this.splitTableName(referencedTableName);
465
469
  schema = schemaName ?? schema ?? this.platform.getConfig().get('schema');
466
- /* v8 ignore next 3 */
470
+ /* v8 ignore next */
467
471
  if (schema && schemaName === '*') {
468
472
  return `${schema}.${referencedTableName.replace(/^\*\./, '')}`;
469
473
  }