@mikro-orm/knex 7.0.0-dev.9 → 7.0.0-dev.91
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AbstractSqlConnection.d.ts +11 -5
- package/AbstractSqlConnection.js +78 -32
- package/AbstractSqlDriver.d.ts +9 -5
- package/AbstractSqlDriver.js +267 -227
- package/AbstractSqlPlatform.js +5 -5
- package/PivotCollectionPersister.d.ts +8 -4
- package/PivotCollectionPersister.js +55 -31
- package/README.md +3 -2
- package/SqlEntityManager.d.ts +10 -2
- package/SqlEntityManager.js +11 -2
- package/dialects/mssql/MsSqlNativeQueryBuilder.d.ts +2 -0
- package/dialects/mssql/MsSqlNativeQueryBuilder.js +42 -3
- package/dialects/mysql/MySqlExceptionConverter.d.ts +3 -3
- package/dialects/mysql/MySqlExceptionConverter.js +4 -5
- package/dialects/mysql/MySqlSchemaHelper.js +2 -2
- package/dialects/postgresql/PostgreSqlTableCompiler.d.ts +1 -0
- package/dialects/postgresql/PostgreSqlTableCompiler.js +1 -0
- package/dialects/sqlite/BaseSqliteConnection.d.ts +3 -2
- package/dialects/sqlite/BaseSqliteConnection.js +2 -14
- package/dialects/sqlite/BaseSqlitePlatform.js +1 -2
- package/dialects/sqlite/SqliteExceptionConverter.d.ts +2 -2
- package/dialects/sqlite/SqliteExceptionConverter.js +6 -4
- package/dialects/sqlite/SqliteSchemaHelper.js +5 -6
- package/index.d.ts +2 -1
- package/index.js +2 -1
- package/package.json +5 -5
- package/plugin/index.d.ts +53 -0
- package/plugin/index.js +42 -0
- package/plugin/transformer.d.ts +115 -0
- package/plugin/transformer.js +883 -0
- package/query/ArrayCriteriaNode.d.ts +1 -0
- package/query/ArrayCriteriaNode.js +3 -0
- package/query/CriteriaNode.d.ts +4 -5
- package/query/CriteriaNode.js +13 -9
- package/query/CriteriaNodeFactory.js +12 -7
- package/query/NativeQueryBuilder.js +1 -1
- package/query/ObjectCriteriaNode.d.ts +1 -0
- package/query/ObjectCriteriaNode.js +35 -8
- package/query/QueryBuilder.d.ts +59 -10
- package/query/QueryBuilder.js +166 -50
- package/query/QueryBuilderHelper.d.ts +1 -1
- package/query/QueryBuilderHelper.js +20 -14
- package/query/ScalarCriteriaNode.d.ts +3 -3
- package/query/ScalarCriteriaNode.js +9 -7
- package/query/index.d.ts +1 -0
- package/query/index.js +1 -0
- package/query/raw.d.ts +59 -0
- package/query/raw.js +68 -0
- package/query/rawKnex.d.ts +58 -0
- package/query/rawKnex.js +72 -0
- package/schema/DatabaseSchema.js +25 -4
- package/schema/DatabaseTable.d.ts +5 -4
- package/schema/DatabaseTable.js +65 -34
- package/schema/SchemaComparator.js +5 -6
- package/schema/SchemaHelper.d.ts +2 -0
- package/schema/SchemaHelper.js +14 -10
- package/schema/SqlSchemaGenerator.d.ts +13 -6
- package/schema/SqlSchemaGenerator.js +41 -20
- 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>;
|
package/query/rawKnex.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { raw } from '@mikro-orm/core';
|
|
2
|
+
// import type { Knex } from 'knex';
|
|
3
|
+
import { QueryBuilder } from './QueryBuilder.js';
|
|
4
|
+
/**
|
|
5
|
+
* Creates raw SQL query fragment that can be assigned to a property or part of a filter. This fragment is represented
|
|
6
|
+
* by `RawQueryFragment` class instance that can be serialized to a string, so it can be used both as an object value
|
|
7
|
+
* and key. When serialized, the fragment key gets cached and only such cached key will be recognized by the ORM.
|
|
8
|
+
* This adds a runtime safety to the raw query fragments.
|
|
9
|
+
*
|
|
10
|
+
* > **`raw()` helper is required since v6 to use a raw fragment in your query, both through EntityManager and QueryBuilder.**
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* // as a value
|
|
14
|
+
* await em.find(User, { time: raw('now()') });
|
|
15
|
+
*
|
|
16
|
+
* // as a key
|
|
17
|
+
* await em.find(User, { [raw('lower(name)')]: name.toLowerCase() });
|
|
18
|
+
*
|
|
19
|
+
* // value can be empty array
|
|
20
|
+
* await em.find(User, { [raw('(select 1 = 1)')]: [] });
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* The `raw` helper supports several signatures, you can pass in a callback that receives the current property alias:
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* await em.find(User, { [raw(alias => `lower(${alias}.name)`)]: name.toLowerCase() });
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* You can also use the `sql` tagged template function, which works the same, but supports only the simple string signature:
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
32
|
+
* await em.find(User, { [sql`lower(name)`]: name.toLowerCase() });
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* When using inside filters, you might have to use a callback signature to create new raw instance for every filter usage.
|
|
36
|
+
*
|
|
37
|
+
* ```ts
|
|
38
|
+
* @Filter({ name: 'long', cond: () => ({ [raw('length(perex)')]: { $gt: 10000 } }) })
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* The `raw` helper can be used within indexes and uniques to write database-agnostic SQL expressions. In that case, you can use `'??'` to tag your database identifiers (table name, column names, index name, ...) inside your expression, and pass those identifiers as a second parameter to the `raw` helper. Internally, those will automatically be quoted according to the database in use:
|
|
42
|
+
*
|
|
43
|
+
* ```ts
|
|
44
|
+
* // On postgres, will produce: create index "index custom_idx_on_name" on "library.author" ("country")
|
|
45
|
+
* // On mysql, will produce: create index `index custom_idx_on_name` on `library.author` (`country`)
|
|
46
|
+
* @Index({ name: 'custom_idx_on_name', expression: (table, columns) => raw(`create index ?? on ?? (??)`, ['custom_idx_on_name', table, columns.name]) })
|
|
47
|
+
* @Entity({ schema: 'library' })
|
|
48
|
+
* export class Author { ... }
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* You can also use the `quote` tag function to write database-agnostic SQL expressions. The end-result is the same as using the `raw` function regarding database identifiers quoting, only to have a more elegant expression syntax:
|
|
52
|
+
*
|
|
53
|
+
* ```ts
|
|
54
|
+
* @Index({ name: 'custom_idx_on_name', expression: (table, columns) => quote`create index ${'custom_idx_on_name'} on ${table} (${columns.name})` })
|
|
55
|
+
* @Entity({ schema: 'library' })
|
|
56
|
+
* export class Author { ... }
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export function rawKnex(sql, params) {
|
|
60
|
+
// export function rawKnex<T extends object = any, R = any>(sql: Knex.QueryBuilder | Knex.Raw | QueryBuilder<T> | EntityKey<T> | EntityKey<T>[] | AnyString | ((alias: string) => string) | RawQueryFragment, params?: readonly unknown[] | Dictionary<unknown>): NoInfer<R> {
|
|
61
|
+
// if (Utils.isObject<Knex.QueryBuilder | Knex.Raw>(sql) && 'toSQL' in sql) {
|
|
62
|
+
// const query = sql.toSQL();
|
|
63
|
+
// return raw(query.sql, query.bindings);
|
|
64
|
+
// }
|
|
65
|
+
if (sql instanceof QueryBuilder) {
|
|
66
|
+
// FIXME this should live in the `knex` compat package, while what we have now should live in `sql` package
|
|
67
|
+
// @ts-ignore
|
|
68
|
+
const query = sql.toQuery()._sql;
|
|
69
|
+
return raw(query.sql, query.bindings);
|
|
70
|
+
}
|
|
71
|
+
return raw(sql, params);
|
|
72
|
+
}
|
package/schema/DatabaseSchema.js
CHANGED
|
@@ -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
|
|
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
|
|
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;
|
package/schema/DatabaseTable.js
CHANGED
|
@@ -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
|
|
66
|
+
/* v8 ignore next */
|
|
67
67
|
if (match) {
|
|
68
68
|
prop.precision ??= +match[1];
|
|
69
69
|
prop.scale ??= +match[2];
|
|
@@ -91,7 +91,7 @@ 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(
|
|
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,
|
|
@@ -104,37 +104,39 @@ export class DatabaseTable {
|
|
|
104
104
|
this.columns[field].default = defaultValue;
|
|
105
105
|
});
|
|
106
106
|
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
107
|
-
const constraintName = this.getIndexName(true, prop.fieldNames, 'foreign');
|
|
107
|
+
const constraintName = this.getIndexName(prop.foreignKeyName ?? true, prop.fieldNames, 'foreign');
|
|
108
108
|
let schema = prop.targetMeta.root.schema === '*' ? this.schema : (prop.targetMeta.root.schema ?? config.get('schema', this.platform.getDefaultSchemaName()));
|
|
109
109
|
if (prop.referencedTableName.includes('.')) {
|
|
110
110
|
schema = undefined;
|
|
111
111
|
}
|
|
112
|
-
|
|
113
|
-
constraintName
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
this.
|
|
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;
|
|
134
139
|
}
|
|
135
|
-
}
|
|
136
|
-
if (prop.deferMode) {
|
|
137
|
-
this.foreignKeys[constraintName].deferMode = prop.deferMode;
|
|
138
140
|
}
|
|
139
141
|
}
|
|
140
142
|
if (prop.index) {
|
|
@@ -160,7 +162,7 @@ export class DatabaseTable {
|
|
|
160
162
|
}
|
|
161
163
|
}
|
|
162
164
|
getIndexName(value, columnNames, type) {
|
|
163
|
-
if (
|
|
165
|
+
if (typeof value === 'string') {
|
|
164
166
|
return value;
|
|
165
167
|
}
|
|
166
168
|
return this.platform.getIndexName(this.name, columnNames, type);
|
|
@@ -613,7 +615,7 @@ export class DatabaseTable {
|
|
|
613
615
|
fkOptions.deferMode = fk.deferMode;
|
|
614
616
|
fkOptions.columnTypes = fk.columnNames.map(col => this.getColumn(col).type);
|
|
615
617
|
}
|
|
616
|
-
|
|
618
|
+
const ret = {
|
|
617
619
|
name: prop,
|
|
618
620
|
type,
|
|
619
621
|
runtimeType,
|
|
@@ -641,6 +643,11 @@ export class DatabaseTable {
|
|
|
641
643
|
persist,
|
|
642
644
|
...fkOptions,
|
|
643
645
|
};
|
|
646
|
+
const nativeEnumName = Object.keys(this.nativeEnums).find(name => name === column.type);
|
|
647
|
+
if (nativeEnumName) {
|
|
648
|
+
ret.nativeEnumName = nativeEnumName;
|
|
649
|
+
}
|
|
650
|
+
return ret;
|
|
644
651
|
}
|
|
645
652
|
getReferenceKind(fk, unique) {
|
|
646
653
|
if (fk && unique) {
|
|
@@ -674,10 +681,17 @@ export class DatabaseTable {
|
|
|
674
681
|
if (fk) {
|
|
675
682
|
return this.getPropertyTypeForForeignKey(namingStrategy, fk);
|
|
676
683
|
}
|
|
684
|
+
const enumMode = this.platform.getConfig().get('entityGenerator').enumMode;
|
|
677
685
|
// If this column is using an enum.
|
|
678
686
|
if (column.enumItems?.length) {
|
|
679
|
-
|
|
680
|
-
|
|
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);
|
|
681
695
|
}
|
|
682
696
|
return column.mappedType?.runtimeType ?? 'unknown';
|
|
683
697
|
}
|
|
@@ -700,6 +714,23 @@ export class DatabaseTable {
|
|
|
700
714
|
}
|
|
701
715
|
return '' + val;
|
|
702
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
|
+
}
|
|
703
734
|
addIndex(meta, index, type) {
|
|
704
735
|
const properties = Utils.unique(Utils.flatten(Utils.asArray(index.properties).map(prop => {
|
|
705
736
|
const parts = prop.split('.');
|
|
@@ -741,7 +772,7 @@ export class DatabaseTable {
|
|
|
741
772
|
primary: type === 'primary',
|
|
742
773
|
unique: type !== 'index',
|
|
743
774
|
type: index.type,
|
|
744
|
-
expression: index.expression,
|
|
775
|
+
expression: this.processIndexExpression(name, index.expression, meta),
|
|
745
776
|
options: index.options,
|
|
746
777
|
deferMode: index.deferMode,
|
|
747
778
|
});
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { inspect } from '
|
|
2
|
-
import { ArrayType, BooleanType, DateTimeType, JsonType, parseJsonSafe, Utils, } from '@mikro-orm/core';
|
|
1
|
+
import { ArrayType, BooleanType, DateTimeType, JsonType, parseJsonSafe, Utils, inspect, } from '@mikro-orm/core';
|
|
3
2
|
/**
|
|
4
3
|
* Compares two Schemas and return an instance of SchemaDifference.
|
|
5
4
|
*/
|
|
@@ -269,7 +268,7 @@ export class SchemaComparator {
|
|
|
269
268
|
}
|
|
270
269
|
for (const toConstraint of Object.values(toForeignKeys)) {
|
|
271
270
|
tableDifferences.addedForeignKeys[toConstraint.constraintName] = toConstraint;
|
|
272
|
-
this.log(`FK constraint ${toConstraint.constraintName} added
|
|
271
|
+
this.log(`FK constraint ${toConstraint.constraintName} added to table ${tableDifferences.name}`, { constraint: toConstraint });
|
|
273
272
|
changes++;
|
|
274
273
|
}
|
|
275
274
|
return changes ? tableDifferences : false;
|
|
@@ -308,7 +307,7 @@ export class SchemaComparator {
|
|
|
308
307
|
const [removedColumn, addedColumn] = candidateColumns[0];
|
|
309
308
|
const removedColumnName = removedColumn.name;
|
|
310
309
|
const addedColumnName = addedColumn.name;
|
|
311
|
-
/* v8 ignore next
|
|
310
|
+
/* v8 ignore next */
|
|
312
311
|
if (tableDifferences.renamedColumns[removedColumnName]) {
|
|
313
312
|
continue;
|
|
314
313
|
}
|
|
@@ -511,14 +510,14 @@ export class SchemaComparator {
|
|
|
511
510
|
return str
|
|
512
511
|
?.replace(/_\w+'(.*?)'/g, '$1')
|
|
513
512
|
.replace(/in\s*\((.*?)\)/ig, '= any (array[$1])')
|
|
514
|
-
.replace(/['"`()]|::\w+| +/g, '')
|
|
513
|
+
.replace(/['"`()\n[\]]|::\w+| +/g, '')
|
|
515
514
|
.replace(/anyarray\[(.*)]/ig, '$1')
|
|
516
515
|
.toLowerCase();
|
|
517
516
|
};
|
|
518
517
|
return simplify(expr1) !== simplify(expr2);
|
|
519
518
|
}
|
|
520
519
|
parseJsonDefault(defaultValue) {
|
|
521
|
-
/* v8 ignore next
|
|
520
|
+
/* v8 ignore next */
|
|
522
521
|
if (!defaultValue) {
|
|
523
522
|
return null;
|
|
524
523
|
}
|
package/schema/SchemaHelper.d.ts
CHANGED
|
@@ -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;
|
package/schema/SchemaHelper.js
CHANGED
|
@@ -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
|
|
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
|
|
123
|
-
|
|
124
|
-
const newItems = itemsNew.filter(val => !itemsOld.includes(val.toLowerCase()));
|
|
122
|
+
// postgres allows only adding new items
|
|
123
|
+
const newItems = itemsNew.filter(val => !itemsOld.includes(val));
|
|
125
124
|
if (enumName.includes('.')) {
|
|
126
125
|
const [enumSchemaName, rawEnumName] = enumName.split('.');
|
|
127
126
|
ret.push(...newItems.map(val => this.getAlterNativeEnumSQL(rawEnumName, enumSchemaName, val, itemsNew, itemsOld)));
|
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
470
|
+
/* v8 ignore next */
|
|
467
471
|
if (schema && schemaName === '*') {
|
|
468
472
|
return `${schema}.${referencedTableName.replace(/^\*\./, '')}`;
|
|
469
473
|
}
|