@mikro-orm/sql 7.1.0-dev.3 → 7.1.0-dev.31

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 (54) hide show
  1. package/AbstractSqlConnection.d.ts +1 -1
  2. package/AbstractSqlConnection.js +2 -2
  3. package/AbstractSqlDriver.d.ts +19 -1
  4. package/AbstractSqlDriver.js +215 -16
  5. package/AbstractSqlPlatform.d.ts +15 -3
  6. package/AbstractSqlPlatform.js +25 -7
  7. package/PivotCollectionPersister.js +13 -2
  8. package/README.md +2 -1
  9. package/SqlEntityManager.d.ts +5 -1
  10. package/SqlEntityManager.js +36 -1
  11. package/SqlMikroORM.d.ts +23 -0
  12. package/SqlMikroORM.js +23 -0
  13. package/dialects/mysql/BaseMySqlPlatform.d.ts +1 -0
  14. package/dialects/mysql/BaseMySqlPlatform.js +3 -0
  15. package/dialects/mysql/MySqlSchemaHelper.d.ts +13 -3
  16. package/dialects/mysql/MySqlSchemaHelper.js +145 -21
  17. package/dialects/oracledb/OracleDialect.d.ts +1 -1
  18. package/dialects/oracledb/OracleDialect.js +2 -1
  19. package/dialects/postgresql/BasePostgreSqlEntityManager.d.ts +19 -0
  20. package/dialects/postgresql/BasePostgreSqlEntityManager.js +24 -0
  21. package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +9 -0
  22. package/dialects/postgresql/BasePostgreSqlPlatform.js +72 -6
  23. package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +31 -1
  24. package/dialects/postgresql/PostgreSqlSchemaHelper.js +230 -5
  25. package/dialects/postgresql/index.d.ts +2 -0
  26. package/dialects/postgresql/index.js +2 -0
  27. package/dialects/postgresql/typeOverrides.d.ts +14 -0
  28. package/dialects/postgresql/typeOverrides.js +12 -0
  29. package/dialects/sqlite/SqlitePlatform.d.ts +1 -0
  30. package/dialects/sqlite/SqlitePlatform.js +4 -0
  31. package/dialects/sqlite/SqliteSchemaHelper.d.ts +9 -2
  32. package/dialects/sqlite/SqliteSchemaHelper.js +148 -19
  33. package/index.d.ts +2 -0
  34. package/index.js +2 -0
  35. package/package.json +4 -4
  36. package/plugin/transformer.d.ts +11 -3
  37. package/plugin/transformer.js +138 -29
  38. package/query/CriteriaNode.d.ts +1 -1
  39. package/query/CriteriaNode.js +2 -2
  40. package/query/ObjectCriteriaNode.js +1 -1
  41. package/query/QueryBuilder.d.ts +36 -0
  42. package/query/QueryBuilder.js +63 -1
  43. package/schema/DatabaseSchema.js +26 -4
  44. package/schema/DatabaseTable.d.ts +20 -1
  45. package/schema/DatabaseTable.js +182 -31
  46. package/schema/SchemaComparator.d.ts +10 -0
  47. package/schema/SchemaComparator.js +104 -1
  48. package/schema/SchemaHelper.d.ts +63 -1
  49. package/schema/SchemaHelper.js +235 -6
  50. package/schema/SqlSchemaGenerator.d.ts +2 -2
  51. package/schema/SqlSchemaGenerator.js +16 -9
  52. package/schema/partitioning.d.ts +13 -0
  53. package/schema/partitioning.js +326 -0
  54. package/typings.d.ts +34 -2
@@ -44,13 +44,13 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
44
44
  }
45
45
  return false;
46
46
  }
47
- getTargetSchema(schema) {
48
- const metadata = this.getOrderedMetadata(schema);
47
+ getTargetSchema(schema, includeWildcardSchema = false) {
48
+ const metadata = this.getOrderedMetadata(schema, includeWildcardSchema);
49
49
  const schemaName = schema ?? this.config.get('schema') ?? this.platform.getDefaultSchemaName();
50
50
  return DatabaseSchema.fromMetadata(metadata, this.platform, this.config, schemaName, this.em);
51
51
  }
52
- getOrderedMetadata(schema) {
53
- const metadata = super.getOrderedMetadata(schema);
52
+ getOrderedMetadata(schema, includeWildcardSchema = false) {
53
+ const metadata = super.getOrderedMetadata(schema, includeWildcardSchema);
54
54
  // Filter out skipped tables
55
55
  return metadata.filter(meta => {
56
56
  const tableName = meta.tableName;
@@ -59,7 +59,7 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
59
59
  });
60
60
  }
61
61
  async getCreateSchemaSQL(options = {}) {
62
- const toSchema = this.getTargetSchema(options.schema);
62
+ const toSchema = this.getTargetSchema(options.schema, options.includeWildcardSchema);
63
63
  const ret = [];
64
64
  for (const namespace of toSchema.getNamespaces()) {
65
65
  if (namespace === this.platform.getDefaultSchemaName()) {
@@ -225,13 +225,13 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
225
225
  async prepareSchemaForComparison(options) {
226
226
  options.safe ??= false;
227
227
  options.dropTables ??= true;
228
- const toSchema = this.getTargetSchema(options.schema);
228
+ const toSchema = this.getTargetSchema(options.schema, options.includeWildcardSchema);
229
229
  const schemas = toSchema.getNamespaces();
230
230
  const fromSchema = options.fromSchema ??
231
231
  (await DatabaseSchema.create(this.connection, this.platform, this.config, options.schema, schemas, undefined, this.options.skipTables, this.options.skipViews));
232
- const wildcardSchemaTables = [...this.metadata.getAll().values()]
233
- .filter(meta => meta.schema === '*')
234
- .map(meta => meta.tableName);
232
+ const wildcardSchemaTables = options.includeWildcardSchema
233
+ ? []
234
+ : [...this.metadata.getAll().values()].filter(meta => meta.schema === '*').map(meta => meta.tableName);
235
235
  fromSchema.prune(options.schema, wildcardSchemaTables);
236
236
  toSchema.prune(options.schema, wildcardSchemaTables);
237
237
  return { fromSchema, toSchema };
@@ -300,11 +300,18 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
300
300
  for (const check of newTable.getChecks()) {
301
301
  this.append(sql, this.helper.createCheck(newTable, check));
302
302
  }
303
+ for (const trigger of newTable.getTriggers()) {
304
+ this.append(sql, this.helper.createTrigger(newTable, trigger));
305
+ }
303
306
  this.append(ret, sql, true);
304
307
  }
305
308
  }
306
309
  if (options.dropTables && !options.safe) {
307
310
  for (const table of Object.values(schemaDiff.removedTables)) {
311
+ // Drop triggers before the table so driver-specific cleanup runs (e.g. PostgreSQL function removal)
312
+ for (const trigger of table.getTriggers()) {
313
+ this.append(ret, this.helper.dropTrigger(table, trigger));
314
+ }
308
315
  this.append(ret, this.helper.dropTableIfExists(table.name, table.schema));
309
316
  }
310
317
  if (Utils.hasObjectKeys(schemaDiff.removedTables)) {
@@ -0,0 +1,13 @@
1
+ import { splitCommaSeparatedIdentifiers, type EntityMetadata, type EntityPartitionBy } from '@mikro-orm/core';
2
+ import type { TablePartitioning } from '../typings.js';
3
+ export { splitCommaSeparatedIdentifiers };
4
+ /** @internal */
5
+ export declare function normalizePartitionDefinition(value: string): string;
6
+ /** @internal */
7
+ export declare function normalizePartitionBound(value: string): string;
8
+ /** @internal */
9
+ export declare const getTablePartitioning: (meta: EntityMetadata, tableSchema: string | undefined, quoteIdentifier?: (id: string) => string) => TablePartitioning | undefined;
10
+ /** @internal */
11
+ export declare const diffPartitioning: (from: TablePartitioning | undefined, to: TablePartitioning | undefined, defaultSchema: string | undefined) => boolean;
12
+ /** @internal */
13
+ export declare const toEntityPartitionBy: (partitioning: TablePartitioning | undefined, parentTableName?: string, parentSchema?: string) => EntityPartitionBy | undefined;
@@ -0,0 +1,326 @@
1
+ import { splitCommaSeparatedIdentifiers } from '@mikro-orm/core';
2
+ export { splitCommaSeparatedIdentifiers };
3
+ const skipQuotedLiteral = (value, start) => {
4
+ let i = start + 1;
5
+ while (i < value.length) {
6
+ if (value[i] === "'") {
7
+ if (value[i + 1] === "'") {
8
+ i += 2;
9
+ continue;
10
+ }
11
+ return i;
12
+ }
13
+ i++;
14
+ }
15
+ // Unterminated literal — point past the end so callers' `slice(start, end + 1)` includes
16
+ // the full remaining tail instead of dropping its last character.
17
+ return value.length;
18
+ };
19
+ /**
20
+ * Apply `transform` only to segments of `value` that lie outside single-quoted
21
+ * SQL literals, leaving literal content (including escaped `''`) untouched.
22
+ */
23
+ const mapOutsideLiterals = (value, transform) => {
24
+ let ret = '';
25
+ let buffer = '';
26
+ let i = 0;
27
+ while (i < value.length) {
28
+ if (value[i] === "'") {
29
+ ret += transform(buffer);
30
+ buffer = '';
31
+ const end = skipQuotedLiteral(value, i);
32
+ ret += value.slice(i, end + 1);
33
+ i = end + 1;
34
+ continue;
35
+ }
36
+ buffer += value[i];
37
+ i++;
38
+ }
39
+ return ret + transform(buffer);
40
+ };
41
+ const collapseWhitespace = (value) => value.replace(/\s+/g, ' ');
42
+ const normalizeWhitespace = (value) => mapOutsideLiterals(value, collapseWhitespace).trim();
43
+ const stripDoubleQuotes = (value) => mapOutsideLiterals(value, s => s.replaceAll('"', ''));
44
+ const normalizeQuotedIdentifiers = (value) => stripDoubleQuotes(normalizeWhitespace(value));
45
+ const findMatchingParenthesis = (value, start) => {
46
+ let depth = 0;
47
+ for (let i = start; i < value.length; i++) {
48
+ if (value[i] === "'") {
49
+ i = skipQuotedLiteral(value, i);
50
+ continue;
51
+ }
52
+ if (value[i] === '(') {
53
+ depth++;
54
+ continue;
55
+ }
56
+ if (value[i] === ')') {
57
+ depth--;
58
+ if (depth === 0) {
59
+ return i;
60
+ }
61
+ }
62
+ }
63
+ return -1;
64
+ };
65
+ const normalizePartitionLiterals = (value) => value
66
+ // PG pg_get_expr output often tacks `::text` onto string literals inside expressions; drop it
67
+ // so the catalog shape matches user-provided bounds. This applies symmetrically to both
68
+ // user metadata and catalog reads, so diffing converges. If a user intentionally writes a
69
+ // `::text` cast in a bound literal it will be stripped on both sides as well.
70
+ .replace(/('(?:[^']|'')*')::text\b/gi, '$1')
71
+ // Strip the `00:00:00` time component so catalog round-trips (timestamp[tz] bounds formatted
72
+ // via the session TimeZone) match user metadata that omitted the time part. Only collapse
73
+ // when we can confidently attribute the literal to a timestamp column: either a numeric
74
+ // offset is present (timestamptz catalog output) or an explicit `::timestamp[tz]` cast
75
+ // follows the literal. Bare `'YYYY-MM-DD 00:00:00'` without offset/cast could just as easily
76
+ // be a text/varchar list-partition value, and collapsing it would produce false-negative
77
+ // diffs.
78
+ .replace(/'(\d{4}-\d{2}-\d{2}) 00:00:00[+-]\d{2}(?::\d{2})?'/g, "'$1'")
79
+ .replace(/'(\d{4}-\d{2}-\d{2}) 00:00:00'(?=\s*::\s*timestamp(?:tz)?(?:\s+(?:with|without)\s+time\s+zone)?\b)/gi, "'$1'");
80
+ const unwrapOuterParentheses = (value) => {
81
+ const trimmed = value.trim();
82
+ if (!trimmed.startsWith('(') || !trimmed.endsWith(')')) {
83
+ return trimmed;
84
+ }
85
+ if (findMatchingParenthesis(trimmed, 0) !== trimmed.length - 1) {
86
+ return trimmed;
87
+ }
88
+ return trimmed.slice(1, -1).trim();
89
+ };
90
+ const unwrapAllOuterParentheses = (value) => {
91
+ let current = value.trim();
92
+ while (current.startsWith('(')) {
93
+ const unwrapped = unwrapOuterParentheses(current);
94
+ if (unwrapped === current) {
95
+ break;
96
+ }
97
+ current = unwrapped;
98
+ }
99
+ return current;
100
+ };
101
+ const normalizePartitionSqlFragment = (value) => {
102
+ const normalized = stripDoubleQuotes(normalizeWhitespace(normalizePartitionLiterals(value)));
103
+ let ret = '';
104
+ for (let i = 0; i < normalized.length; i++) {
105
+ if (normalized[i] === "'") {
106
+ const end = skipQuotedLiteral(normalized, i);
107
+ ret += normalized.slice(i, end + 1);
108
+ i = end;
109
+ continue;
110
+ }
111
+ if (normalized[i] === '(') {
112
+ const end = findMatchingParenthesis(normalized, i);
113
+ if (end === -1) {
114
+ ret += normalized.slice(i);
115
+ break;
116
+ }
117
+ const inner = unwrapAllOuterParentheses(normalizePartitionSqlFragment(normalized.slice(i + 1, end)));
118
+ ret += `(${inner})`;
119
+ i = end;
120
+ continue;
121
+ }
122
+ ret += normalized[i];
123
+ }
124
+ return normalizeWhitespace(unwrapAllOuterParentheses(ret));
125
+ };
126
+ const unquoteIdentifier = (value) => {
127
+ const trimmed = value.trim();
128
+ if (trimmed.length >= 2 && trimmed.startsWith('"') && trimmed.endsWith('"')) {
129
+ return trimmed.slice(1, -1).replaceAll('""', '"');
130
+ }
131
+ return trimmed;
132
+ };
133
+ /**
134
+ * Split a user-supplied partition name into `{ schema, name }`. Supports bare (`child`),
135
+ * schema-qualified (`schema.child`), and quoted (`"my.schema"."child"`) forms. Dots inside
136
+ * double-quoted identifiers are part of the identifier and do not split.
137
+ */
138
+ const splitPartitionName = (name) => {
139
+ let depth = 0;
140
+ for (let i = 0; i < name.length; i++) {
141
+ const ch = name[i];
142
+ if (ch === '"') {
143
+ if (name[i + 1] === '"') {
144
+ i++;
145
+ continue;
146
+ }
147
+ depth = depth === 0 ? 1 : 0;
148
+ continue;
149
+ }
150
+ if (ch === '.' && depth === 0) {
151
+ return {
152
+ schema: unquoteIdentifier(name.slice(0, i)),
153
+ name: unquoteIdentifier(name.slice(i + 1)),
154
+ };
155
+ }
156
+ }
157
+ return { name: unquoteIdentifier(name) };
158
+ };
159
+ const resolvePartitionKey = (meta, key, quoteIdentifier) => {
160
+ const trimmed = key.trim().replaceAll('"', '');
161
+ if (!trimmed) {
162
+ throw new Error(`Entity ${meta.className} has invalid partitionBy option: empty partition key`);
163
+ }
164
+ const prop = meta.root.properties[trimmed] ??
165
+ Object.values(meta.root.properties).find(candidate => candidate.fieldNames?.length === 1 && candidate.fieldNames[0] === trimmed);
166
+ if (!prop) {
167
+ throw new Error(`Entity ${meta.className} has invalid partitionBy option: unknown partition key '${key.trim()}'`);
168
+ }
169
+ if (prop.fieldNames?.length !== 1) {
170
+ throw new Error(`Entity ${meta.className} has invalid partitionBy option: partition key '${key.trim()}' maps to multiple columns ('${prop.fieldNames?.join("', '")}'); list them explicitly as partition keys`);
171
+ }
172
+ return quoteIdentifier(prop.fieldNames[0]);
173
+ };
174
+ /**
175
+ * Resolve the partition expression to a SQL fragment. Column-reference forms (array of keys
176
+ * or a clean comma-list of identifiers) are rewritten to the backing `fieldNames` and passed
177
+ * through `quoteIdentifier`. The callback form and the raw-SQL fallback (anything that isn't
178
+ * a clean identifier list, e.g. `date_trunc('day', created_at)`) are emitted verbatim — the
179
+ * user owns identifier quoting inside a raw expression.
180
+ */
181
+ const resolvePartitionExpression = (meta, expression, quoteIdentifier) => {
182
+ if (typeof expression === 'function') {
183
+ return normalizeWhitespace(expression(meta.createSchemaColumnMappingObject()));
184
+ }
185
+ if (Array.isArray(expression)) {
186
+ return expression.map(key => resolvePartitionKey(meta, key, quoteIdentifier)).join(', ');
187
+ }
188
+ const trimmed = expression.trim();
189
+ const keys = splitCommaSeparatedIdentifiers(trimmed);
190
+ if (keys) {
191
+ return keys.map(key => resolvePartitionKey(meta, key, quoteIdentifier)).join(', ');
192
+ }
193
+ return trimmed;
194
+ };
195
+ const createPartitionDefinition = (type, expression) => `${type.toLowerCase()} (${normalizeWhitespace(expression)})`;
196
+ /** @internal */
197
+ export function normalizePartitionDefinition(value) {
198
+ const normalized = normalizeWhitespace(value);
199
+ const match = /^(\w+)\s*(.*)$/.exec(normalized);
200
+ const rawType = match ? match[1] : normalized;
201
+ const type = rawType.toLowerCase();
202
+ const expression = match ? match[2].trim() : '';
203
+ if (!expression) {
204
+ return type;
205
+ }
206
+ if (!expression.startsWith('(')) {
207
+ return `${type} ${normalizePartitionSqlFragment(expression)}`;
208
+ }
209
+ return `${type} (${normalizePartitionSqlFragment(unwrapAllOuterParentheses(expression))})`;
210
+ }
211
+ const PARTITION_BOUND_KEYWORDS = /\b(for values|with|in|from|to|minvalue|maxvalue|null)\b/gi;
212
+ /** @internal */
213
+ export function normalizePartitionBound(value) {
214
+ const normalized = normalizeWhitespace(value);
215
+ if (!normalized) {
216
+ return '';
217
+ }
218
+ if (/^default$/i.test(normalized)) {
219
+ return 'default';
220
+ }
221
+ // Prepend `for values` if the caller passed a bare `with/in/from … to …` clause, then lowercase
222
+ // PG bound keywords outside quoted literals (so `FROM (MINVALUE) TO ('hello TO world')` becomes
223
+ // `from (minvalue) to ('hello TO world')` with the inner TO inside the literal preserved).
224
+ // PG's `pg_get_expr` emits `MINVALUE`/`MAXVALUE`/`NULL` in uppercase, so case-folding them here
225
+ // prevents a perpetual diff against user-supplied lowercase bounds.
226
+ const prefixed = /^for values\b/i.test(normalized) ? normalized : `for values ${normalized}`;
227
+ const lowered = mapOutsideLiterals(prefixed, segment => segment.replace(PARTITION_BOUND_KEYWORDS, match => match.toLowerCase()));
228
+ return normalizePartitionSqlFragment(lowered);
229
+ }
230
+ const createPartitionBound = (value) => normalizePartitionBound(value);
231
+ const createHashPartitions = (tableName, tableSchema, partitions) => {
232
+ const count = typeof partitions === 'number' ? partitions : partitions.length;
233
+ return Array.from({ length: count }, (_, remainder) => {
234
+ const bound = normalizePartitionBound(`with (modulus ${count}, remainder ${remainder})`);
235
+ if (typeof partitions === 'number') {
236
+ return { name: `${tableName}_${remainder}`, schema: tableSchema, bound };
237
+ }
238
+ const { name, schema } = splitPartitionName(partitions[remainder]);
239
+ return { name, schema: schema ?? tableSchema, bound };
240
+ });
241
+ };
242
+ const createExplicitPartitions = (tableName, tableSchema, partitions) => partitions.map((partition, index) => {
243
+ const resolvedName = partition.name ?? `${tableName}_${index}`;
244
+ const { name, schema } = splitPartitionName(resolvedName);
245
+ return {
246
+ name,
247
+ schema: schema ?? tableSchema,
248
+ bound: createPartitionBound(partition.values),
249
+ };
250
+ });
251
+ /** @internal */
252
+ export const getTablePartitioning = (meta, tableSchema, quoteIdentifier = id => id) => {
253
+ if (!meta.partitionBy) {
254
+ return undefined;
255
+ }
256
+ const definition = createPartitionDefinition(meta.partitionBy.type, resolvePartitionExpression(meta, meta.partitionBy.expression, quoteIdentifier));
257
+ const partitions = meta.partitionBy.type === 'hash'
258
+ ? createHashPartitions(meta.tableName, tableSchema, meta.partitionBy.partitions)
259
+ : createExplicitPartitions(meta.tableName, tableSchema, meta.partitionBy.partitions);
260
+ return { definition, partitions };
261
+ };
262
+ /** @internal */
263
+ export const diffPartitioning = (from, to, defaultSchema) => {
264
+ if (!from && !to) {
265
+ return false;
266
+ }
267
+ if (!from || !to) {
268
+ return true;
269
+ }
270
+ if (normalizeQuotedIdentifiers(normalizePartitionDefinition(from.definition)) !==
271
+ normalizeQuotedIdentifiers(normalizePartitionDefinition(to.definition))) {
272
+ return true;
273
+ }
274
+ if (from.partitions.length !== to.partitions.length) {
275
+ return true;
276
+ }
277
+ const normalizeSchema = (schema) => (schema && schema !== defaultSchema ? schema : '');
278
+ const serializePartition = (partition) => `${normalizeSchema(partition.schema)}.${partition.name}:${normalizeQuotedIdentifiers(normalizePartitionBound(partition.bound))}`;
279
+ const fromPartitions = from.partitions.map(serializePartition).sort();
280
+ const toPartitions = to.partitions.map(serializePartition).sort();
281
+ return fromPartitions.some((partition, index) => partition !== toPartitions[index]);
282
+ };
283
+ const SUPPORTED_PARTITION_TYPES = ['hash', 'list', 'range'];
284
+ const isSupportedPartitionType = (value) => SUPPORTED_PARTITION_TYPES.includes(value);
285
+ /** @internal */
286
+ export const toEntityPartitionBy = (partitioning, parentTableName, parentSchema) => {
287
+ if (!partitioning) {
288
+ return undefined;
289
+ }
290
+ const normalizedDefinition = normalizePartitionDefinition(partitioning.definition);
291
+ const normalizedPartitions = partitioning.partitions.map(partition => ({
292
+ ...partition,
293
+ bound: normalizePartitionBound(partition.bound),
294
+ }));
295
+ // Split the leading type keyword off of the definition without using `split(' ')`, which would
296
+ // shatter quoted literals containing spaces. Match a bareword prefix followed by whitespace.
297
+ const [, rawType = normalizedDefinition, rawExpression = ''] = /^(\S+)(?:\s+([\s\S]*))?$/.exec(normalizeWhitespace(normalizedDefinition)) ?? [];
298
+ const type = rawType.toLowerCase();
299
+ if (!isSupportedPartitionType(type)) {
300
+ throw new Error(`Unsupported partition type '${rawType}' in definition '${partitioning.definition}'`);
301
+ }
302
+ const expression = unwrapOuterParentheses(rawExpression);
303
+ const qualify = (partition) => partition.schema && partition.schema !== parentSchema ? `${partition.schema}.${partition.name}` : partition.name;
304
+ if (type === 'hash') {
305
+ // Collapse to a bare count when catalog names follow the default
306
+ // `${parentTableName}_${remainder}` pattern and live in the parent's schema, or when we have
307
+ // no parent context to compare against (backwards-compatible behavior for callers that pass
308
+ // just the `TablePartitioning`). Otherwise preserve the explicit name array so the next DDL
309
+ // generation reproduces the same children.
310
+ const usesDefaultShape = parentTableName == null ||
311
+ normalizedPartitions.every((p, i) => p.name === `${parentTableName}_${i}` && (!p.schema || p.schema === parentSchema));
312
+ return {
313
+ type,
314
+ expression,
315
+ partitions: usesDefaultShape ? normalizedPartitions.length : normalizedPartitions.map(qualify),
316
+ };
317
+ }
318
+ return {
319
+ type,
320
+ expression,
321
+ partitions: normalizedPartitions.map(partition => ({
322
+ name: qualify(partition),
323
+ values: partition.bound === 'default' ? 'default' : partition.bound.replace(/^for values\s+/i, ''),
324
+ })),
325
+ };
326
+ };
package/typings.d.ts CHANGED
@@ -44,6 +44,7 @@ export interface Column {
44
44
  default?: string | null;
45
45
  defaultConstraint?: string;
46
46
  comment?: string;
47
+ collation?: string;
47
48
  generated?: string;
48
49
  nativeEnumName?: string;
49
50
  enumItems?: string[];
@@ -51,7 +52,7 @@ export interface Column {
51
52
  unique?: boolean;
52
53
  /** mysql only */
53
54
  extra?: string;
54
- ignoreSchemaChanges?: ('type' | 'extra' | 'default')[];
55
+ ignoreSchemaChanges?: ('type' | 'extra' | 'default' | 'collation')[];
55
56
  }
56
57
  export interface ForeignKey {
57
58
  columnNames: string[];
@@ -71,6 +72,11 @@ export interface IndexDef {
71
72
  primary: boolean;
72
73
  composite?: boolean;
73
74
  expression?: string;
75
+ /**
76
+ * WHERE predicate for partial indexes, normalized to a SQL fragment after metadata
77
+ * resolution and introspection. Mutually exclusive with `expression`.
78
+ */
79
+ where?: string;
74
80
  options?: Dictionary;
75
81
  type?: string | Readonly<{
76
82
  indexType?: string;
@@ -108,6 +114,25 @@ export interface CheckDef<T = unknown> {
108
114
  definition?: string;
109
115
  columnName?: string;
110
116
  }
117
+ /** Resolved trigger definition for schema operations (all callbacks resolved to strings). */
118
+ export interface SqlTriggerDef {
119
+ name: string;
120
+ timing: 'before' | 'after' | 'instead of';
121
+ events: ('insert' | 'update' | 'delete' | 'truncate')[];
122
+ forEach: 'row' | 'statement';
123
+ body: string;
124
+ when?: string;
125
+ expression?: string;
126
+ }
127
+ export interface TablePartition {
128
+ name: string;
129
+ schema?: string;
130
+ bound: string;
131
+ }
132
+ export interface TablePartitioning {
133
+ definition: string;
134
+ partitions: TablePartition[];
135
+ }
111
136
  export interface ColumnDifference {
112
137
  oldColumnName: string;
113
138
  column: Column;
@@ -117,6 +142,10 @@ export interface ColumnDifference {
117
142
  export interface TableDifference {
118
143
  name: string;
119
144
  changedComment?: string;
145
+ changedPartitioning?: {
146
+ from?: TablePartitioning;
147
+ to?: TablePartitioning;
148
+ };
120
149
  fromTable: DatabaseTable;
121
150
  toTable: DatabaseTable;
122
151
  addedColumns: Dictionary<Column>;
@@ -130,6 +159,9 @@ export interface TableDifference {
130
159
  addedChecks: Dictionary<CheckDef>;
131
160
  changedChecks: Dictionary<CheckDef>;
132
161
  removedChecks: Dictionary<CheckDef>;
162
+ addedTriggers: Dictionary<SqlTriggerDef>;
163
+ changedTriggers: Dictionary<SqlTriggerDef>;
164
+ removedTriggers: Dictionary<SqlTriggerDef>;
133
165
  addedForeignKeys: Dictionary<ForeignKey>;
134
166
  changedForeignKeys: Dictionary<ForeignKey>;
135
167
  removedForeignKeys: Dictionary<ForeignKey>;
@@ -233,7 +265,7 @@ export interface ICriteriaNode<T extends object> {
233
265
  shouldInline(payload: any): boolean;
234
266
  willAutoJoin(qb: IQueryBuilder<T>, alias?: string, options?: ICriteriaNodeProcessOptions): boolean;
235
267
  shouldRename(payload: any): boolean;
236
- renameFieldToPK<T>(qb: IQueryBuilder<T>, ownerAlias?: string): string;
268
+ renameFieldToPK<T>(qb: IQueryBuilder<T>, ownerAlias?: string, options?: ICriteriaNodeProcessOptions): string;
237
269
  getPath(opts?: {
238
270
  addIndex?: boolean;
239
271
  }): string;