@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.
- package/AbstractSqlConnection.d.ts +11 -5
- package/AbstractSqlConnection.js +79 -32
- package/AbstractSqlDriver.d.ts +9 -5
- package/AbstractSqlDriver.js +268 -220
- package/AbstractSqlPlatform.js +3 -3
- package/PivotCollectionPersister.d.ts +3 -2
- package/PivotCollectionPersister.js +12 -21
- package/README.md +3 -2
- package/SqlEntityManager.d.ts +9 -2
- package/SqlEntityManager.js +2 -2
- package/dialects/mssql/MsSqlNativeQueryBuilder.d.ts +2 -0
- package/dialects/mssql/MsSqlNativeQueryBuilder.js +43 -2
- package/dialects/mysql/MySqlPlatform.js +2 -1
- package/dialects/postgresql/PostgreSqlTableCompiler.d.ts +1 -0
- package/dialects/postgresql/PostgreSqlTableCompiler.js +1 -0
- package/dialects/sqlite/BaseSqliteConnection.d.ts +4 -2
- package/dialects/sqlite/BaseSqliteConnection.js +8 -5
- package/dialects/sqlite/BaseSqlitePlatform.js +1 -2
- package/dialects/sqlite/SqliteSchemaHelper.js +1 -1
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/package.json +5 -5
- package/query/ArrayCriteriaNode.d.ts +1 -0
- package/query/ArrayCriteriaNode.js +3 -0
- package/query/CriteriaNode.d.ts +4 -2
- package/query/CriteriaNode.js +11 -6
- package/query/CriteriaNodeFactory.js +12 -7
- package/query/NativeQueryBuilder.js +1 -1
- package/query/ObjectCriteriaNode.d.ts +1 -0
- package/query/ObjectCriteriaNode.js +38 -9
- package/query/QueryBuilder.d.ts +59 -7
- package/query/QueryBuilder.js +171 -47
- package/query/QueryBuilderHelper.d.ts +1 -1
- package/query/QueryBuilderHelper.js +15 -8
- 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 +67 -33
- package/schema/SchemaComparator.js +2 -2
- package/schema/SchemaHelper.d.ts +2 -0
- package/schema/SchemaHelper.js +8 -4
- package/schema/SqlSchemaGenerator.d.ts +13 -6
- package/schema/SqlSchemaGenerator.js +38 -17
- 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 (
|
|
78
|
+
if (a === prop?.embedded?.[0]) {
|
|
79
79
|
return aliasPrefix + prop.fieldNames[fkIdx];
|
|
80
80
|
}
|
|
81
|
-
const noPrefix = prop
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
|
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 =
|
|
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 {
|
|
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
|
|
9
|
-
shouldJoin
|
|
8
|
+
willAutoJoin(qb: IQueryBuilder<T>, alias?: string, options?: ICriteriaNodeProcessOptions): boolean;
|
|
9
|
+
private shouldJoin;
|
|
10
10
|
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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 =>
|
|
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
package/query/index.js
CHANGED
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;
|