@mikro-orm/oracledb 7.0.0-dev.316

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.
@@ -0,0 +1,591 @@
1
+ import { DateTimeType, EnumType, SchemaHelper, StringType, TextType, Utils, } from '@mikro-orm/sql';
2
+ export class OracleSchemaHelper extends SchemaHelper {
3
+ static DEFAULT_VALUES = {
4
+ true: ['1'],
5
+ false: ['0'],
6
+ systimestamp: ['current_timestamp'],
7
+ sysdate: ['current_timestamp'],
8
+ };
9
+ getDatabaseExistsSQL(name) {
10
+ return `select 1 from all_users where username = ${this.platform.quoteValue(name)}`;
11
+ }
12
+ async getAllTables(connection, schemas) {
13
+ if (!schemas || schemas.length === 0) {
14
+ return connection.execute(this.getListTablesSQL());
15
+ }
16
+ const conditions = schemas.map(s => `at.owner = ${this.platform.quoteValue(s)}`).join(' or ');
17
+ const sql = `select at.table_name, at.owner as schema_name, atc.comments as table_comment
18
+ from all_tables at
19
+ left join all_tab_comments atc on at.owner = atc.owner and at.table_name = atc.table_name
20
+ where (${conditions})
21
+ order by schema_name, at.table_name`;
22
+ return connection.execute(sql);
23
+ }
24
+ getListTablesSQL(schemaName) {
25
+ /* v8 ignore next: nullish coalescing chain */
26
+ const schema = schemaName ?? this.platform.getDefaultSchemaName() ?? '';
27
+ /* v8 ignore next 7: Oracle always has a default schema from dbName */
28
+ if (!schema) {
29
+ return `select at.table_name, at.owner as schema_name, atc.comments as table_comment
30
+ from all_tables at
31
+ left join all_tab_comments atc on at.owner = atc.owner and at.table_name = atc.table_name
32
+ where ${this.getIgnoredNamespacesConditionSQL('at.owner')}
33
+ order by schema_name, at.table_name`;
34
+ }
35
+ return `select at.table_name, at.owner as schema_name, atc.comments as table_comment
36
+ from all_tables at
37
+ left join all_tab_comments atc on at.owner = atc.owner and at.table_name = atc.table_name
38
+ where at.owner = ${this.platform.quoteValue(schema)}
39
+ order by schema_name, at.table_name`;
40
+ }
41
+ getListViewsSQL() {
42
+ /* v8 ignore next: schema fallback */
43
+ const schema = this.platform.getDefaultSchemaName() ?? '';
44
+ return `select view_name, owner as schema_name, text as view_definition
45
+ from all_views
46
+ where owner = ${this.platform.quoteValue(schema)}
47
+ order by view_name`;
48
+ }
49
+ async loadViews(schema, connection) {
50
+ const views = await connection.execute(this.getListViewsSQL());
51
+ for (const view of views) {
52
+ const definition = view.view_definition?.trim();
53
+ /* v8 ignore next 4: empty view definition edge case */
54
+ if (definition) {
55
+ const schemaName = view.schema_name === this.platform.getDefaultSchemaName() ? undefined : view.schema_name;
56
+ schema.addView(view.view_name, schemaName, definition);
57
+ }
58
+ }
59
+ }
60
+ async getNamespaces(connection) {
61
+ const sql = `select username as schema_name from all_users where ${this.getIgnoredNamespacesConditionSQL()} order by username`;
62
+ const res = await connection.execute(sql);
63
+ return res.map(row => row.schema_name);
64
+ }
65
+ getIgnoredNamespacesConditionSQL(column = 'username') {
66
+ const ignored = [
67
+ 'PDBADMIN',
68
+ 'ORDS_METADATA',
69
+ 'ORDS_PUBLIC_USER',
70
+ /* v8 ignore next */
71
+ ...(this.platform.getConfig().get('schemaGenerator').ignoreSchema ?? []),
72
+ ]
73
+ .map(s => this.platform.quoteValue(s))
74
+ .join(', ');
75
+ return `${column} not in (${ignored}) and oracle_maintained = 'N'`;
76
+ }
77
+ getDefaultEmptyString() {
78
+ return 'null';
79
+ }
80
+ normalizeDefaultValue(defaultValue, length, defaultValues = {}, stripQuotes = false) {
81
+ if (defaultValue == null) {
82
+ return defaultValue;
83
+ }
84
+ // Trim whitespace that Oracle sometimes adds
85
+ defaultValue = defaultValue.trim();
86
+ let match = /^\((.*)\)$/.exec(defaultValue);
87
+ if (match) {
88
+ defaultValue = match[1];
89
+ }
90
+ match = /^\((.*)\)$/.exec(defaultValue);
91
+ if (match) {
92
+ defaultValue = match[1];
93
+ }
94
+ match = /^'(.*)'$/.exec(defaultValue);
95
+ if (stripQuotes && match) {
96
+ defaultValue = match[1];
97
+ }
98
+ // Normalize current_timestamp variants (Oracle uses CURRENT_TIMESTAMP, SYSTIMESTAMP, etc.)
99
+ const lowerDefault = defaultValue.toLowerCase();
100
+ if (lowerDefault === 'current_timestamp' || lowerDefault.startsWith('current_timestamp(')) {
101
+ // Keep the precision if present
102
+ return defaultValue.toLowerCase();
103
+ }
104
+ return super.normalizeDefaultValue(defaultValue, length, OracleSchemaHelper.DEFAULT_VALUES);
105
+ }
106
+ async getAllColumns(connection, tablesBySchemas) {
107
+ const sql = `select
108
+ atc.owner as schema_name,
109
+ atc.table_name as table_name,
110
+ atc.column_name as column_name,
111
+ atc.data_default as column_default,
112
+ acc.comments as column_comment,
113
+ atc.nullable as is_nullable,
114
+ lower(atc.data_type) as data_type,
115
+ case when atc.virtual_column = 'YES' then atc.data_default else null end as generation_expression,
116
+ case when atc.virtual_column = 'YES' then 'NO' else 'YES' end as is_persisted,
117
+ atc.data_precision as numeric_precision,
118
+ atc.data_scale as numeric_scale,
119
+ case when atc.data_type like 'TIMESTAMP%' then atc.data_scale else null end as datetime_precision,
120
+ atc.char_length as character_maximum_length,
121
+ atc.data_length as data_length,
122
+ atc.identity_column as is_identity,
123
+ atc.column_id as ordinal_position
124
+ from all_tab_cols atc
125
+ left join all_col_comments acc on atc.owner = acc.owner and atc.table_name = acc.table_name and atc.column_name = acc.column_name
126
+ where atc.hidden_column = 'NO'
127
+ and (${[...tablesBySchemas.entries()].map(([schema, tables]) => `(atc.table_name in (${tables.map(t => this.platform.quoteValue(t.table_name)).join(', ')}) and atc.owner = ${this.platform.quoteValue(schema)})`).join(' or ')})
128
+ order by atc.owner, atc.table_name, atc.column_id`;
129
+ const allColumns = await connection.execute(sql);
130
+ const str = (val) => (val != null ? '' + val : val);
131
+ const ret = {};
132
+ for (const col of allColumns) {
133
+ const mappedType = this.platform.getMappedType(col.data_type);
134
+ const defaultValue = str(this.normalizeDefaultValue(col.column_default, col.length, {}));
135
+ const increments = col.is_identity === 'YES' && connection.getPlatform().isNumericColumn(mappedType);
136
+ const key = this.getTableKey(col);
137
+ /* v8 ignore next */
138
+ const generated = col.generation_expression
139
+ ? `${col.generation_expression}${col.is_persisted ? ' persisted' : ''}`
140
+ : undefined;
141
+ let type = col.data_type;
142
+ // Set length based on column type
143
+ if (['varchar', 'varchar2', 'char'].includes(col.data_type)) {
144
+ col.length = col.character_maximum_length;
145
+ }
146
+ else if (col.data_type === 'raw') {
147
+ // RAW columns use data_length for their size (e.g., raw(16) for UUIDs)
148
+ col.length = col.data_length;
149
+ }
150
+ if (mappedType instanceof DateTimeType) {
151
+ col.length = col.datetime_precision;
152
+ }
153
+ /* v8 ignore next 2: length formatting branch */
154
+ if (col.length != null && !type.endsWith(`(${col.length})`) && !['text', 'date'].includes(type)) {
155
+ type += `(${col.length === -1 ? 'max' : col.length})`;
156
+ }
157
+ // Oracle uses 'number' for numeric types (not 'numeric')
158
+ if (type === 'number' && col.numeric_precision != null && col.numeric_scale != null) {
159
+ type += `(${col.numeric_precision}, ${col.numeric_scale})`;
160
+ }
161
+ if (type === 'float' && col.numeric_precision != null) {
162
+ type += `(${col.numeric_precision})`;
163
+ }
164
+ ret[key] ??= [];
165
+ ret[key].push({
166
+ name: col.column_name,
167
+ type: this.platform.isNumericColumn(mappedType)
168
+ ? col.data_type.replace(/ unsigned$/, '').replace(/\(\d+\)$/, '')
169
+ : type,
170
+ mappedType,
171
+ unsigned: col.data_type.endsWith(' unsigned'),
172
+ length: col.length,
173
+ default: increments ? undefined : this.wrap(defaultValue, mappedType),
174
+ nullable: col.is_nullable === 'Y',
175
+ autoincrement: increments,
176
+ precision: col.numeric_precision,
177
+ scale: col.numeric_scale,
178
+ comment: col.column_comment,
179
+ generated,
180
+ });
181
+ }
182
+ return ret;
183
+ }
184
+ async getAllIndexes(connection, tablesBySchemas) {
185
+ // Query indexes and join with constraints to identify which indexes back PRIMARY KEY or UNIQUE constraints
186
+ // Also join with all_ind_expressions to get function-based index expressions
187
+ const sql = `select ind.table_owner as schema_name, ind.table_name, ind.index_name, aic.column_name,
188
+ case ind.uniqueness when 'UNIQUE' then 'YES' else 'NO' end as is_unique,
189
+ case when con.constraint_type = 'P' then 'YES' else 'NO' end as is_primary_key,
190
+ con.constraint_type,
191
+ con.constraint_name,
192
+ aie.column_expression as expression
193
+ from all_indexes ind
194
+ join all_ind_columns aic on ind.owner = aic.index_owner and ind.index_name = aic.index_name
195
+ left join all_constraints con on con.owner = ind.table_owner and con.index_name = ind.index_name and con.constraint_type in ('P', 'U')
196
+ left join all_ind_expressions aie on ind.owner = aie.index_owner and ind.index_name = aie.index_name and aic.column_position = aie.column_position
197
+ where (${[...tablesBySchemas.entries()].map(([schema, tables]) => `(ind.table_name in (${tables.map(t => this.platform.quoteValue(t.table_name)).join(', ')}) and ind.table_owner = ${this.platform.quoteValue(schema)})`).join(' or ')})
198
+ order by ind.table_name, ind.index_name, aic.column_position`;
199
+ const allIndexes = await connection.execute(sql);
200
+ const ret = {};
201
+ for (const index of allIndexes) {
202
+ const key = this.getTableKey(index);
203
+ // If this index backs a PRIMARY KEY or UNIQUE constraint, mark it appropriately
204
+ const isPrimary = index.constraint_type === 'P';
205
+ const isUniqueConstraint = index.constraint_type === 'U';
206
+ const isConstraintIndex = isPrimary || isUniqueConstraint;
207
+ // Skip indexes that back PRIMARY KEY constraints - they're handled as part of the table definition
208
+ // and should not be managed as separate indexes
209
+ if (isPrimary) {
210
+ continue;
211
+ }
212
+ const indexDef = {
213
+ columnNames: [index.column_name],
214
+ keyName: index.index_name,
215
+ unique: index.is_unique === 'YES',
216
+ primary: false, // We skip PK indexes above, so this is always false
217
+ constraint: isConstraintIndex || index.is_unique === 'YES',
218
+ };
219
+ // Handle function-based indexes (expression indexes)
220
+ /* v8 ignore start: expression index branches */
221
+ if (index.expression) {
222
+ indexDef.expression = index.expression;
223
+ }
224
+ else if (index.column_name?.match(/[(): ,"'`]/)) {
225
+ indexDef.expression = this.getCreateIndexSQL(index.table_name, indexDef, true);
226
+ }
227
+ /* v8 ignore stop */
228
+ ret[key] ??= [];
229
+ ret[key].push(indexDef);
230
+ }
231
+ for (const key of Object.keys(ret)) {
232
+ ret[key] = await this.mapIndexes(ret[key]);
233
+ }
234
+ return ret;
235
+ }
236
+ mapForeignKeys(fks, tableName, schemaName) {
237
+ const ret = super.mapForeignKeys(fks, tableName, schemaName);
238
+ for (const fk of Utils.values(ret)) {
239
+ fk.columnNames = Utils.unique(fk.columnNames);
240
+ fk.referencedColumnNames = Utils.unique(fk.referencedColumnNames);
241
+ }
242
+ return ret;
243
+ }
244
+ createForeignKey(table, foreignKey, alterTable = true, inline = false) {
245
+ // Oracle supports ON DELETE CASCADE and ON DELETE SET NULL, but not ON UPDATE
246
+ const supportedDeleteRules = ['cascade', 'set null'];
247
+ return super.createForeignKey(table, {
248
+ ...foreignKey,
249
+ updateRule: undefined,
250
+ deleteRule: supportedDeleteRules.includes(foreignKey.deleteRule ?? '') ? foreignKey.deleteRule : undefined,
251
+ }, alterTable, inline);
252
+ }
253
+ async getAllForeignKeys(connection, tablesBySchemas) {
254
+ const sql = `select fk_cons.constraint_name, fk_cons.table_name, fk_cons.owner as schema_name, fk_cols.column_name,
255
+ fk_cons.r_owner as referenced_schema_name,
256
+ pk_cols.column_name as referenced_column_name,
257
+ pk_cons.table_name as referenced_table_name,
258
+ 'NO ACTION' as update_rule,
259
+ fk_cons.delete_rule
260
+ from all_constraints fk_cons
261
+ join all_cons_columns fk_cols on fk_cons.owner = fk_cols.owner and fk_cons.constraint_name = fk_cols.constraint_name
262
+ join all_constraints pk_cons on fk_cons.r_owner = pk_cons.owner and fk_cons.r_constraint_name = pk_cons.constraint_name
263
+ join all_cons_columns pk_cols on pk_cons.owner = pk_cols.owner and pk_cons.constraint_name = pk_cols.constraint_name and fk_cols.position = pk_cols.position
264
+ where fk_cons.constraint_type = 'R'
265
+ and (${[...tablesBySchemas.entries()].map(([schema, tables]) => `(fk_cons.table_name in (${tables.map(t => this.platform.quoteValue(t.table_name)).join(', ')}) and fk_cons.owner = ${this.platform.quoteValue(schema)})`).join(' or ')})
266
+ order by fk_cons.owner, fk_cons.table_name, fk_cons.constraint_name, pk_cols.position`;
267
+ const allFks = await connection.execute(sql);
268
+ const ret = {};
269
+ for (const fk of allFks) {
270
+ // Oracle returns schema names in uppercase - normalize to lowercase for consistency
271
+ fk.schema_name = fk.schema_name?.toLowerCase();
272
+ fk.referenced_schema_name = fk.referenced_schema_name?.toLowerCase();
273
+ const key = this.getTableKey(fk);
274
+ ret[key] ??= [];
275
+ ret[key].push(fk);
276
+ }
277
+ Object.keys(ret).forEach(key => {
278
+ const [schemaName, tableName] = key.split('.');
279
+ ret[key] = this.mapForeignKeys(ret[key], tableName, schemaName);
280
+ });
281
+ return ret;
282
+ }
283
+ getEnumDefinitions(checks) {
284
+ return checks.reduce((o, item, index) => {
285
+ // check constraints are defined as
286
+ // `([type]='owner' OR [type]='manager' OR [type]='employee')`
287
+ const m1 = item.definition?.match(/^check \((.*)\)/);
288
+ let items = m1?.[1].split(' OR ');
289
+ /* v8 ignore next */
290
+ const hasItems = (items?.length ?? 0) > 0;
291
+ /* v8 ignore next: enum parsing branch */
292
+ if (item.columnName && hasItems) {
293
+ items = items
294
+ .map(val => /^\(?'(.*)'/.exec(val.trim().replace(`"${item.columnName}"=`, ''))?.[1])
295
+ .filter(Boolean);
296
+ if (items.length > 0) {
297
+ o[item.columnName] = items;
298
+ }
299
+ }
300
+ return o;
301
+ }, {});
302
+ }
303
+ getChecksSQL(tablesBySchemas) {
304
+ // Filter out NOT NULL constraints using search_condition_vc (Oracle 12c+)
305
+ // NOT NULL constraints have expressions like '"column_name" IS NOT NULL'
306
+ return `select con.constraint_name as name,
307
+ con.owner schema_name,
308
+ con.table_name table_name,
309
+ (select case when count(acc.column_name) = 1 then min(acc.column_name) else null end
310
+ from all_cons_columns acc
311
+ where
312
+ acc.owner = con.owner
313
+ and acc.constraint_name = con.constraint_name
314
+ and acc.table_name = con.table_name
315
+ ) as column_name,
316
+ con.search_condition_vc expression
317
+ from all_constraints con
318
+ where con.constraint_type = 'C'
319
+ and con.search_condition_vc not like '%IS NOT NULL'
320
+ and (${[...tablesBySchemas.entries()].map(([schema, tables]) => `(con.table_name in (${tables.map(t => this.platform.quoteValue(t.table_name)).join(', ')}) and con.owner = ${this.platform.quoteValue(schema)})`).join(' or ')})
321
+ order by con.constraint_name`;
322
+ }
323
+ async getAllChecks(connection, tablesBySchemas) {
324
+ const sql = this.getChecksSQL(tablesBySchemas);
325
+ const allChecks = await connection.execute(sql);
326
+ const ret = {};
327
+ for (const check of allChecks) {
328
+ const key = this.getTableKey(check);
329
+ ret[key] ??= [];
330
+ const expression = check.expression.replace(/^\((.*)\)$/, '$1');
331
+ ret[key].push({
332
+ name: check.name,
333
+ columnName: check.column_name,
334
+ definition: `check (${expression})`,
335
+ expression,
336
+ });
337
+ }
338
+ return ret;
339
+ }
340
+ async loadInformationSchema(schema, connection, tables) {
341
+ if (tables.length === 0) {
342
+ return;
343
+ }
344
+ const tablesBySchema = this.getTablesGroupedBySchemas(tables);
345
+ const columns = await this.getAllColumns(connection, tablesBySchema);
346
+ const indexes = await this.getAllIndexes(connection, tablesBySchema);
347
+ const checks = await this.getAllChecks(connection, tablesBySchema);
348
+ const fks = await this.getAllForeignKeys(connection, tablesBySchema);
349
+ for (const t of tables) {
350
+ const key = this.getTableKey(t);
351
+ const table = schema.addTable(t.table_name, t.schema_name, t.table_comment);
352
+ const pks = await this.getPrimaryKeys(connection, indexes[key], table.name, table.schema);
353
+ const enums = this.getEnumDefinitions(checks[key] ?? []);
354
+ table.init(columns[key], indexes[key], checks[key], pks, fks[key], enums);
355
+ }
356
+ }
357
+ getPreAlterTable(tableDiff, safe) {
358
+ const ret = [];
359
+ const indexes = tableDiff.fromTable.getIndexes();
360
+ const parts = tableDiff.name.split('.');
361
+ const tableName = parts.pop();
362
+ const schemaName = parts.pop();
363
+ const name = (schemaName && schemaName !== this.platform.getDefaultSchemaName() ? schemaName + '.' : '') + tableName;
364
+ const quotedName = this.quote(name);
365
+ // indexes need to be first dropped to be able to change a column type
366
+ const changedTypes = Object.values(tableDiff.changedColumns).filter(col => col.changedProperties.has('type'));
367
+ for (const col of changedTypes) {
368
+ for (const index of indexes) {
369
+ if (index.columnNames.includes(col.column.name)) {
370
+ ret.push(this.getDropIndexSQL(name, index));
371
+ }
372
+ }
373
+ }
374
+ return ret;
375
+ }
376
+ getPostAlterTable(tableDiff, safe) {
377
+ const ret = [];
378
+ const indexes = tableDiff.fromTable.getIndexes();
379
+ const parts = tableDiff.name.split('.');
380
+ const tableName = parts.pop();
381
+ const schemaName = parts.pop();
382
+ const name = (schemaName && schemaName !== this.platform.getDefaultSchemaName() ? schemaName + '.' : '') + tableName;
383
+ // indexes need to be first dropped to be able to change a column type
384
+ const changedTypes = Object.values(tableDiff.changedColumns).filter(col => col.changedProperties.has('type'));
385
+ for (const col of changedTypes) {
386
+ for (const index of indexes) {
387
+ if (index.columnNames.includes(col.column.name)) {
388
+ this.append(ret, this.getCreateIndexSQL(name, index));
389
+ }
390
+ }
391
+ }
392
+ return ret;
393
+ }
394
+ getCreateNamespaceSQL(name) {
395
+ const rawPassword = this.platform.getConfig().get('password');
396
+ /* v8 ignore start: password type and tableSpace fallback */
397
+ const password = typeof rawPassword === 'string' ? rawPassword : 'Schema_' + Math.random().toString(36).slice(2);
398
+ const tableSpace = this.platform.getConfig().get('schemaGenerator').tableSpace ?? 'users';
399
+ /* v8 ignore stop */
400
+ return [
401
+ `create user ${this.quote(name)}`,
402
+ `identified by ${this.quote(password)}`,
403
+ `default tablespace ${this.quote(tableSpace)}`,
404
+ `quota unlimited on ${this.quote(tableSpace)}`,
405
+ ].join(' ');
406
+ }
407
+ getDropNamespaceSQL(name) {
408
+ return `drop user ${this.quote(name)} cascade`;
409
+ }
410
+ getDropIndexSQL(tableName, index) {
411
+ return `drop index ${this.quote(index.keyName)}`;
412
+ }
413
+ dropIndex(table, index, oldIndexName = index.keyName) {
414
+ if (index.primary) {
415
+ return `alter table ${this.quote(table)} drop constraint ${this.quote(oldIndexName)}`;
416
+ }
417
+ return `drop index ${this.quote(oldIndexName)}`;
418
+ }
419
+ getManagementDbName() {
420
+ /* v8 ignore next: managementDbName fallback */
421
+ return this.platform.getConfig().get('schemaGenerator', {}).managementDbName ?? 'system';
422
+ }
423
+ getDatabaseNotExistsError(dbName) {
424
+ return 'ORA-01918';
425
+ }
426
+ getCreateDatabaseSQL(name) {
427
+ return `create user ${this.quote(name)}`;
428
+ }
429
+ getDropDatabaseSQL(name) {
430
+ return `drop user ${this.quote(name)} cascade`;
431
+ }
432
+ getDropColumnsSQL(tableName, columns, schemaName) {
433
+ /* v8 ignore next 3: schema prefix branch */
434
+ const tableNameRaw = this.quote((schemaName && schemaName !== this.platform.getDefaultSchemaName() ? schemaName + '.' : '') + tableName);
435
+ const drops = [];
436
+ for (const column of columns) {
437
+ drops.push(this.quote(column.name));
438
+ }
439
+ return `alter table ${tableNameRaw} drop (${drops.join(', ')})`;
440
+ }
441
+ getRenameColumnSQL(tableName, oldColumnName, to, schemaName) {
442
+ /* v8 ignore next 2: schema prefix branch */
443
+ const tableNameRaw = (schemaName && schemaName !== this.platform.getDefaultSchemaName() ? schemaName + '.' : '') + tableName;
444
+ return `alter table ${this.quote(tableNameRaw)} rename column ${this.quote(oldColumnName)} to ${this.quote(to.name)}`;
445
+ }
446
+ createTableColumn(column, table, changedProperties) {
447
+ const compositePK = table.getPrimaryKey()?.composite;
448
+ const primaryKey = !changedProperties && !this.hasNonDefaultPrimaryKeyName(table);
449
+ /* v8 ignore next: generated column branch */
450
+ const columnType = column.generated ? `as ${column.generated}` : column.type;
451
+ const col = [this.quote(column.name), columnType];
452
+ Utils.runIfNotEmpty(() => col.push('generated by default as identity'), column.autoincrement);
453
+ /* v8 ignore next 3: default value branch */
454
+ const useDefault = changedProperties
455
+ ? false
456
+ : column.default != null && column.default !== 'null' && !column.autoincrement;
457
+ // const defaultName = this.platform.getConfig().getNamingStrategy().indexName(table.name, [column.name], 'default');
458
+ Utils.runIfNotEmpty(() => col.push(`default ${column.default}`), useDefault);
459
+ /* v8 ignore next 2: nullable/not-null branches */
460
+ Utils.runIfNotEmpty(() => col.push('null'), column.nullable);
461
+ Utils.runIfNotEmpty(() => col.push('not null'), !column.nullable && !column.generated);
462
+ /* v8 ignore next 6: autoincrement PK branch depends on column diff state */
463
+ if (column.autoincrement &&
464
+ !column.generated &&
465
+ !compositePK &&
466
+ (!changedProperties || changedProperties.has('autoincrement') || changedProperties.has('type'))) {
467
+ Utils.runIfNotEmpty(() => col.push('primary key'), primaryKey && column.primary);
468
+ }
469
+ return col.join(' ');
470
+ }
471
+ alterTableColumn(column, table, changedProperties) {
472
+ const parts = [];
473
+ const quotedTableName = table.getQuotedName();
474
+ // Oracle uses MODIFY for column changes, and always requires the column type
475
+ if (changedProperties.has('type') || changedProperties.has('nullable') || changedProperties.has('default')) {
476
+ const colParts = [this.quote(column.name), column.type];
477
+ if (changedProperties.has('default')) {
478
+ if (column.default != null && column.default !== 'null') {
479
+ colParts.push(`default ${column.default}`);
480
+ }
481
+ else {
482
+ colParts.push('default null');
483
+ }
484
+ }
485
+ if (changedProperties.has('nullable')) {
486
+ colParts.push(column.nullable ? 'null' : 'not null');
487
+ }
488
+ parts.push(`alter table ${quotedTableName} modify ${colParts.join(' ')}`);
489
+ }
490
+ return parts;
491
+ }
492
+ getCreateIndexSQL(tableName, index, partialExpression = false) {
493
+ if (index.expression && !partialExpression) {
494
+ return index.expression;
495
+ }
496
+ const keyName = this.quote(index.keyName);
497
+ /* v8 ignore next: deferred index branch */
498
+ const defer = index.deferMode ? ` deferrable initially ${index.deferMode}` : '';
499
+ const sql = `create ${index.unique ? 'unique ' : ''}index ${keyName} on ${this.quote(tableName)} `;
500
+ if (index.expression && partialExpression) {
501
+ return `${sql}(${index.expression})${defer}`;
502
+ }
503
+ return super.getCreateIndexSQL(tableName, index);
504
+ }
505
+ createIndex(index, table, createPrimary = false) {
506
+ if (index.primary) {
507
+ return '';
508
+ }
509
+ if (index.expression) {
510
+ return index.expression;
511
+ }
512
+ // oracle creates an implicit index for unique constraints, so skip creating
513
+ // a non-unique index when a unique index on the same columns already exists
514
+ if (!index.unique) {
515
+ const cols = index.columnNames.join(',');
516
+ const hasUniqueIndex = table
517
+ .getIndexes()
518
+ .some(i => i.unique && i.keyName !== index.keyName && i.columnNames.join(',') === cols);
519
+ if (hasUniqueIndex) {
520
+ return '';
521
+ }
522
+ }
523
+ const quotedTableName = table.getQuotedName();
524
+ if (index.unique) {
525
+ const nullableCols = index.columnNames.filter(column => table.getColumn(column)?.nullable);
526
+ return `create unique index ${this.quote(index.keyName)} on ${quotedTableName} (${index.columnNames
527
+ .map(c => {
528
+ if (table.getColumn(c)?.nullable) {
529
+ return `case when ${nullableCols.map(c => `${this.quote(c)} is not null`).join(' and ')} then ${this.quote(c)} end`;
530
+ }
531
+ return this.quote(c);
532
+ })
533
+ .join(', ')})`;
534
+ }
535
+ return super.createIndex(index, table);
536
+ }
537
+ dropForeignKey(tableName, constraintName) {
538
+ return `alter table ${this.quote(tableName)} drop constraint ${this.quote(constraintName)}`;
539
+ }
540
+ dropViewIfExists(name, schema) {
541
+ if (schema === this.platform.getDefaultSchemaName()) {
542
+ schema = undefined;
543
+ }
544
+ return `drop view if exists ${this.quote(schema, name)} cascade constraints`;
545
+ }
546
+ dropTableIfExists(name, schema) {
547
+ if (schema === this.platform.getDefaultSchemaName()) {
548
+ schema = undefined;
549
+ }
550
+ return `drop table if exists ${this.quote(schema, name)} cascade constraint`;
551
+ }
552
+ getAddColumnsSQL(table, columns) {
553
+ const adds = columns
554
+ .map(column => {
555
+ return this.createTableColumn(column, table);
556
+ })
557
+ .join(', ');
558
+ // Oracle requires parentheses when adding multiple columns
559
+ const wrap = columns.length > 1 ? `(${adds})` : adds;
560
+ return [`alter table ${table.getQuotedName()} add ${wrap}`];
561
+ }
562
+ appendComments(table) {
563
+ const sql = [];
564
+ if (table.comment) {
565
+ const comment = this.platform.quoteValue(this.processComment(table.comment));
566
+ sql.push(`comment on table ${table.getQuotedName()} is ${comment}`);
567
+ }
568
+ for (const column of table.getColumns()) {
569
+ if (column.comment) {
570
+ const comment = this.platform.quoteValue(this.processComment(column.comment));
571
+ sql.push(`comment on column ${table.getQuotedName()}.${this.quote(column.name)} is ${comment}`);
572
+ }
573
+ }
574
+ return sql;
575
+ }
576
+ inferLengthFromColumnType(type) {
577
+ const match = /^(\w+)\s*\(\s*(-?\d+|max)\s*\)/.exec(type);
578
+ if (!match) {
579
+ return;
580
+ }
581
+ if (match[2] === 'max') {
582
+ return -1;
583
+ }
584
+ return +match[2];
585
+ }
586
+ /* v8 ignore next 4: wrap is called by schema comparator internals */
587
+ wrap(val, type) {
588
+ const stringType = type instanceof StringType || type instanceof TextType || type instanceof EnumType;
589
+ return typeof val === 'string' && val.length > 0 && stringType ? this.platform.quoteValue(val) : val;
590
+ }
591
+ }