@mikro-orm/sql 7.2.0-dev.8 → 7.2.0
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 +14 -3
- package/AbstractSqlConnection.js +42 -4
- package/AbstractSqlDriver.d.ts +1 -8
- package/AbstractSqlDriver.js +63 -35
- package/AbstractSqlPlatform.d.ts +4 -2
- package/AbstractSqlPlatform.js +35 -1
- package/README.md +1 -0
- package/SqlEntityManager.d.ts +2 -2
- package/SqlEntityManager.js +4 -2
- package/dialects/mysql/BaseMySqlPlatform.d.ts +2 -0
- package/dialects/mysql/BaseMySqlPlatform.js +4 -0
- package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +3 -1
- package/dialects/postgresql/BasePostgreSqlPlatform.js +35 -2
- package/dialects/postgresql/PostgreSqlExceptionConverter.js +8 -1
- package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +18 -1
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +153 -1
- package/dialects/sqlite/SqlitePlatform.d.ts +2 -0
- package/dialects/sqlite/SqlitePlatform.js +4 -0
- package/dialects/sqlite/SqliteSchemaHelper.js +1 -1
- package/package.json +3 -3
- package/plugin/transformer.d.ts +7 -1
- package/plugin/transformer.js +60 -1
- package/query/CriteriaNodeFactory.js +4 -0
- package/query/ObjectCriteriaNode.d.ts +1 -0
- package/query/ObjectCriteriaNode.js +30 -5
- package/query/QueryBuilder.d.ts +26 -3
- package/query/QueryBuilder.js +139 -23
- package/query/QueryBuilderHelper.d.ts +5 -0
- package/query/QueryBuilderHelper.js +33 -8
- package/schema/DatabaseSchema.d.ts +4 -0
- package/schema/DatabaseSchema.js +107 -1
- package/schema/DatabaseTable.d.ts +13 -1
- package/schema/DatabaseTable.js +50 -1
- package/schema/SchemaComparator.d.ts +2 -0
- package/schema/SchemaComparator.js +94 -4
- package/schema/SchemaHelper.d.ts +6 -0
- package/schema/SchemaHelper.js +15 -0
- package/schema/SqlSchemaGenerator.js +8 -0
- package/typings.d.ts +18 -0
|
@@ -328,6 +328,7 @@ export class SchemaComparator {
|
|
|
328
328
|
addedIndexes: {},
|
|
329
329
|
addedChecks: {},
|
|
330
330
|
addedTriggers: {},
|
|
331
|
+
addedPolicies: {},
|
|
331
332
|
changedColumns: {},
|
|
332
333
|
changedForeignKeys: {},
|
|
333
334
|
changedIndexes: {},
|
|
@@ -338,6 +339,7 @@ export class SchemaComparator {
|
|
|
338
339
|
removedIndexes: {},
|
|
339
340
|
removedChecks: {},
|
|
340
341
|
removedTriggers: {},
|
|
342
|
+
removedPolicies: {},
|
|
341
343
|
renamedColumns: {},
|
|
342
344
|
renamedIndexes: {},
|
|
343
345
|
fromTable,
|
|
@@ -517,6 +519,9 @@ export class SchemaComparator {
|
|
|
517
519
|
}
|
|
518
520
|
}
|
|
519
521
|
}
|
|
522
|
+
if (this.#platform.supportsRowLevelSecurity()) {
|
|
523
|
+
changes += this.diffPolicies(fromTable, toTable, tableDifferences);
|
|
524
|
+
}
|
|
520
525
|
const fromForeignKeys = { ...fromTable.getForeignKeys() };
|
|
521
526
|
const toForeignKeys = { ...toTable.getForeignKeys() };
|
|
522
527
|
for (const fromConstraint of Object.values(fromForeignKeys)) {
|
|
@@ -908,6 +913,10 @@ export class SchemaComparator {
|
|
|
908
913
|
// multi word type names in casts, the generic `::\w+` below only covers single word ones
|
|
909
914
|
// the precision is kept, so `timestamptz(3)` and `timestamp(3) with time zone` leave the same residue
|
|
910
915
|
.replace(/::\s*(?:character\s+varying|bit\s+varying|double\s+precision|(?:timestamp|time)\b(\s*\(\d+\))?(?:\s+with(?:out)?\s+time\s+zone)?)/gi, '$1')
|
|
916
|
+
// Protect dots inside string literals before the quote strip below, or the alias-prefix normalization
|
|
917
|
+
// would mangle literal contents — `current_setting('app.tenant')` and `current_setting('req.tenant')`
|
|
918
|
+
// must not both collapse to `current_settingtenant`
|
|
919
|
+
.replace(/'([^']*)'/g, (_, inner) => `'${inner.replaceAll('.', '\u0000')}'`)
|
|
911
920
|
// Remove quotes first so we can process identifiers
|
|
912
921
|
.replace(/['"`]/g, '')
|
|
913
922
|
// MySQL adds table/alias prefixes to columns (e.g., a.name or table_name.column vs just column)
|
|
@@ -963,13 +972,13 @@ export class SchemaComparator {
|
|
|
963
972
|
}
|
|
964
973
|
diffTrigger(from, to) {
|
|
965
974
|
// Raw DDL expression cannot be meaningfully compared to introspected
|
|
966
|
-
// trigger metadata, so
|
|
967
|
-
if (to.expression) {
|
|
975
|
+
// trigger metadata, so it needs special handling when either side uses it.
|
|
976
|
+
if (from.expression || to.expression) {
|
|
968
977
|
// Both sides have expression — compare the raw DDL directly
|
|
969
|
-
if (from.expression) {
|
|
978
|
+
if (from.expression && to.expression) {
|
|
970
979
|
return this.diffExpression(from.expression, to.expression);
|
|
971
980
|
}
|
|
972
|
-
// Only
|
|
981
|
+
// Only one side has expression — the raw DDL cannot be compared to
|
|
973
982
|
// introspected metadata. Changes to the expression value won't be detected;
|
|
974
983
|
// drop and recreate the trigger manually to apply expression changes.
|
|
975
984
|
return false;
|
|
@@ -985,6 +994,87 @@ export class SchemaComparator {
|
|
|
985
994
|
}
|
|
986
995
|
return this.diffExpression(from.body, to.body);
|
|
987
996
|
}
|
|
997
|
+
diffPolicies(fromTable, toTable, diff) {
|
|
998
|
+
let changes = 0;
|
|
999
|
+
// `ignorePolicies` makes RLS create-only: declared policies are still added and RLS is still enabled/forced,
|
|
1000
|
+
// but existing policies are never diffed for drop/alter and RLS is never disabled or unforced — this protects
|
|
1001
|
+
// hand-written policies on databases that adopted RLS before the ORM managed it
|
|
1002
|
+
const ignorePolicies = this.#platform.getConfig().get('schemaGenerator').ignorePolicies;
|
|
1003
|
+
// postgres rejects `alter column ... type` on a column referenced by any policy, so a type change forces us to
|
|
1004
|
+
// drop every still-present policy around the alter (dropped before via `getRlsDropSQL`, recreated after via
|
|
1005
|
+
// `getRlsAlterSQL`) even when the policy itself is otherwise unchanged; a `generated`-only change is emitted
|
|
1006
|
+
// as a drop + re-add of the same column, which a policy's column dependency blocks the same way
|
|
1007
|
+
const hasColumnTypeChange = Object.values(diff.changedColumns).some(c => c.changedProperties.has('type')) ||
|
|
1008
|
+
Object.keys(diff.removedColumns).some(name => name in diff.addedColumns);
|
|
1009
|
+
for (const policy of toTable.getPolicies()) {
|
|
1010
|
+
if (!fromTable.hasPolicy(policy.name)) {
|
|
1011
|
+
diff.addedPolicies[policy.name] = policy;
|
|
1012
|
+
this.log(`policy ${policy.name} added to table ${diff.name}`, { policy });
|
|
1013
|
+
changes++;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
if (fromTable.rlsEnabled !== toTable.rlsEnabled && (!ignorePolicies || toTable.rlsEnabled)) {
|
|
1017
|
+
diff.changedRlsEnabled = toTable.rlsEnabled;
|
|
1018
|
+
changes++;
|
|
1019
|
+
}
|
|
1020
|
+
if (fromTable.rlsForced !== toTable.rlsForced && (!ignorePolicies || toTable.rlsForced)) {
|
|
1021
|
+
diff.changedRlsForced = toTable.rlsForced;
|
|
1022
|
+
changes++;
|
|
1023
|
+
}
|
|
1024
|
+
if (ignorePolicies) {
|
|
1025
|
+
// existing policies are unmanaged here, but a type change still needs them dropped and recreated for the
|
|
1026
|
+
// alter to succeed — recreate each verbatim from introspection so the hand-written definition is preserved
|
|
1027
|
+
if (hasColumnTypeChange) {
|
|
1028
|
+
for (const policy of fromTable.getPolicies()) {
|
|
1029
|
+
diff.removedPolicies[policy.name] = policy;
|
|
1030
|
+
diff.addedPolicies[policy.name] = policy;
|
|
1031
|
+
changes += 2;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
return changes;
|
|
1035
|
+
}
|
|
1036
|
+
for (const policy of fromTable.getPolicies()) {
|
|
1037
|
+
const toPolicy = toTable.getPolicy(policy.name);
|
|
1038
|
+
if (!toPolicy) {
|
|
1039
|
+
diff.removedPolicies[policy.name] = policy;
|
|
1040
|
+
this.log(`policy ${policy.name} removed from table ${diff.name}`);
|
|
1041
|
+
changes++;
|
|
1042
|
+
continue;
|
|
1043
|
+
}
|
|
1044
|
+
// changed policies are always dropped (before column drops, which the old expression can block via its
|
|
1045
|
+
// column dependencies) and recreated (after column adds) — postgres could alter some of the changes in
|
|
1046
|
+
// place, but not a policy's command or type, nor unset an expression
|
|
1047
|
+
if (this.diffPolicy(policy, toPolicy)) {
|
|
1048
|
+
diff.removedPolicies[policy.name] = policy;
|
|
1049
|
+
diff.addedPolicies[policy.name] = toPolicy;
|
|
1050
|
+
this.log(`policy ${policy.name} recreated in table ${diff.name}`, { from: policy, to: toPolicy });
|
|
1051
|
+
changes += 2;
|
|
1052
|
+
continue;
|
|
1053
|
+
}
|
|
1054
|
+
// an unchanged policy still blocks a type change on any column, so drop + recreate it around the alter
|
|
1055
|
+
if (hasColumnTypeChange) {
|
|
1056
|
+
diff.removedPolicies[policy.name] = policy;
|
|
1057
|
+
diff.addedPolicies[policy.name] = toPolicy;
|
|
1058
|
+
this.log(`policy ${policy.name} recreated around a column type change in table ${diff.name}`);
|
|
1059
|
+
changes += 2;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
return changes;
|
|
1063
|
+
}
|
|
1064
|
+
diffPolicy(from, to) {
|
|
1065
|
+
// normalize so an omitted `roles` matches introspected `{public}`
|
|
1066
|
+
const normalizeRoles = (roles) => DatabaseTable.isDefaultPolicyRoles(roles) ? '' : [...roles].sort().join(',');
|
|
1067
|
+
if (from.command !== to.command || from.type !== to.type) {
|
|
1068
|
+
return true;
|
|
1069
|
+
}
|
|
1070
|
+
if (normalizeRoles(from.roles) !== normalizeRoles(to.roles)) {
|
|
1071
|
+
return true;
|
|
1072
|
+
}
|
|
1073
|
+
if (this.diffExpression(from.using ?? '', to.using ?? '')) {
|
|
1074
|
+
return true;
|
|
1075
|
+
}
|
|
1076
|
+
return this.diffExpression(from.check ?? '', to.check ?? '');
|
|
1077
|
+
}
|
|
988
1078
|
parseJsonDefault(defaultValue) {
|
|
989
1079
|
/* v8 ignore next */
|
|
990
1080
|
if (!defaultValue) {
|
package/schema/SchemaHelper.d.ts
CHANGED
|
@@ -154,6 +154,12 @@ export declare abstract class SchemaHelper {
|
|
|
154
154
|
getChangeColumnCommentSQL(tableName: string, to: Column, schemaName?: string): string;
|
|
155
155
|
/** Whether the column comment is part of the column declaration, as opposed to a separate statement. */
|
|
156
156
|
protected hasInlineColumnComment(): boolean;
|
|
157
|
+
/** Row level security DDL for a freshly created table (enable/force + create policies). Postgres only. */
|
|
158
|
+
getRlsCreateSQL(table: DatabaseTable): string[];
|
|
159
|
+
/** Drops removed/changed row level security policies; emitted in the pre-alter phase, before any column drop or type alter a policy expression can block. Postgres only. */
|
|
160
|
+
getRlsDropSQL(diff: TableDifference, safe?: boolean): string[];
|
|
161
|
+
/** Row level security DDL for a table difference (enable/disable/force transitions + policy creation). Postgres only. */
|
|
162
|
+
getRlsAlterSQL(diff: TableDifference, safe?: boolean): string[];
|
|
157
163
|
getNamespaces(connection: AbstractSqlConnection, ctx?: Transaction): Promise<string[]>;
|
|
158
164
|
protected mapIndexes(indexes: IndexDef[]): Promise<IndexDef[]>;
|
|
159
165
|
mapForeignKeys(fks: any[], tableName: string, schemaName?: string): Dictionary;
|
package/schema/SchemaHelper.js
CHANGED
|
@@ -504,6 +504,7 @@ export class SchemaHelper {
|
|
|
504
504
|
if ('changedComment' in diff) {
|
|
505
505
|
ret.push(this.alterTableComment(diff.toTable, diff.changedComment));
|
|
506
506
|
}
|
|
507
|
+
this.append(ret, this.getRlsAlterSQL(diff, safe));
|
|
507
508
|
return ret;
|
|
508
509
|
}
|
|
509
510
|
/** Returns SQL to add columns to an existing table. */
|
|
@@ -646,6 +647,18 @@ export class SchemaHelper {
|
|
|
646
647
|
hasInlineColumnComment() {
|
|
647
648
|
return false;
|
|
648
649
|
}
|
|
650
|
+
/** Row level security DDL for a freshly created table (enable/force + create policies). Postgres only. */
|
|
651
|
+
getRlsCreateSQL(table) {
|
|
652
|
+
return [];
|
|
653
|
+
}
|
|
654
|
+
/** Drops removed/changed row level security policies; emitted in the pre-alter phase, before any column drop or type alter a policy expression can block. Postgres only. */
|
|
655
|
+
getRlsDropSQL(diff, safe) {
|
|
656
|
+
return [];
|
|
657
|
+
}
|
|
658
|
+
/** Row level security DDL for a table difference (enable/disable/force transitions + policy creation). Postgres only. */
|
|
659
|
+
getRlsAlterSQL(diff, safe) {
|
|
660
|
+
return [];
|
|
661
|
+
}
|
|
649
662
|
async getNamespaces(connection, ctx) {
|
|
650
663
|
return [];
|
|
651
664
|
}
|
|
@@ -801,6 +814,8 @@ export class SchemaHelper {
|
|
|
801
814
|
for (const trigger of table.getTriggers()) {
|
|
802
815
|
this.append(ret, this.createTrigger(table, trigger));
|
|
803
816
|
}
|
|
817
|
+
// RLS policies can reference other tables, so they are deferred until every table exists (see the
|
|
818
|
+
// callers of getRlsCreateSQL in SqlSchemaGenerator) rather than emitted inline here
|
|
804
819
|
}
|
|
805
820
|
return ret;
|
|
806
821
|
}
|
|
@@ -94,6 +94,10 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
|
|
|
94
94
|
this.append(ret, fks, true);
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
+
// RLS policies are deferred until every table exists, so a policy expression can reference another table
|
|
98
|
+
for (const table of toSchema.getTables()) {
|
|
99
|
+
this.append(ret, this.helper.getRlsCreateSQL(table));
|
|
100
|
+
}
|
|
97
101
|
const sortedViews = this.sortViewsByDependencies(toSchema.getViews());
|
|
98
102
|
for (const view of sortedViews) {
|
|
99
103
|
this.appendViewCreation(ret, view);
|
|
@@ -351,6 +355,7 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
|
|
|
351
355
|
for (const trigger of newTable.getTriggers()) {
|
|
352
356
|
this.append(sql, this.helper.createTrigger(newTable, trigger));
|
|
353
357
|
}
|
|
358
|
+
this.append(sql, this.helper.getRlsCreateSQL(newTable));
|
|
354
359
|
this.append(ret, sql, true);
|
|
355
360
|
}
|
|
356
361
|
}
|
|
@@ -410,6 +415,9 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
|
|
|
410
415
|
}
|
|
411
416
|
preAlterTable(diff, safe) {
|
|
412
417
|
const ret = [];
|
|
418
|
+
// removed/changed policies must be dropped before any column type alter, including the pre-alter
|
|
419
|
+
// uuid-to-text cast on postgres — a policy expression blocks type changes on the columns it references
|
|
420
|
+
this.append(ret, this.helper.getRlsDropSQL(diff, safe));
|
|
413
421
|
this.append(ret, this.helper.getPreAlterTable(diff, safe));
|
|
414
422
|
for (const foreignKey of Object.values(diff.removedForeignKeys)) {
|
|
415
423
|
ret.push(this.helper.dropForeignKey(diff.toTable.getShortestName(), foreignKey.constraintName));
|
package/typings.d.ts
CHANGED
|
@@ -114,6 +114,15 @@ export interface CheckDef<T = unknown> {
|
|
|
114
114
|
definition?: string;
|
|
115
115
|
columnName?: string;
|
|
116
116
|
}
|
|
117
|
+
/** Resolved row level security policy for schema operations (all callbacks resolved to strings). */
|
|
118
|
+
export interface SqlPolicyDef {
|
|
119
|
+
name: string;
|
|
120
|
+
command: 'select' | 'insert' | 'update' | 'delete' | 'all';
|
|
121
|
+
type: 'permissive' | 'restrictive';
|
|
122
|
+
roles: string[];
|
|
123
|
+
using?: string;
|
|
124
|
+
check?: string;
|
|
125
|
+
}
|
|
117
126
|
/** Resolved trigger definition for schema operations (all callbacks resolved to strings). */
|
|
118
127
|
export interface SqlTriggerDef {
|
|
119
128
|
name: string;
|
|
@@ -191,6 +200,13 @@ export interface TableDifference {
|
|
|
191
200
|
addedTriggers: Dictionary<SqlTriggerDef>;
|
|
192
201
|
changedTriggers: Dictionary<SqlTriggerDef>;
|
|
193
202
|
removedTriggers: Dictionary<SqlTriggerDef>;
|
|
203
|
+
addedPolicies: Dictionary<SqlPolicyDef>;
|
|
204
|
+
/** Changed policies surface as `removedPolicies` + `addedPolicies` pairs (drop + recreate). */
|
|
205
|
+
removedPolicies: Dictionary<SqlPolicyDef>;
|
|
206
|
+
/** New RLS enable state, present only when it changed. */
|
|
207
|
+
changedRlsEnabled?: boolean;
|
|
208
|
+
/** New RLS force state, present only when it changed. */
|
|
209
|
+
changedRlsForced?: boolean;
|
|
194
210
|
addedForeignKeys: Dictionary<ForeignKey>;
|
|
195
211
|
changedForeignKeys: Dictionary<ForeignKey>;
|
|
196
212
|
removedForeignKeys: Dictionary<ForeignKey>;
|
|
@@ -277,6 +293,8 @@ export interface IQueryBuilder<T> {
|
|
|
277
293
|
with(name: string, query: AnyQueryBuilder | NativeQueryBuilder | RawQueryFragment, options?: CteOptions): this;
|
|
278
294
|
withRecursive(name: string, query: AnyQueryBuilder | NativeQueryBuilder | RawQueryFragment, options?: CteOptions): this;
|
|
279
295
|
scheduleFilterCheck(path: string): void;
|
|
296
|
+
/** @internal */
|
|
297
|
+
ensureTPTJoins(): void;
|
|
280
298
|
withSchema(schema: string): this;
|
|
281
299
|
}
|
|
282
300
|
export interface ICriteriaNodeProcessOptions {
|