@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.
- package/LICENSE +21 -0
- package/OracleConnection.d.ts +11 -0
- package/OracleConnection.js +176 -0
- package/OracleDriver.d.ts +14 -0
- package/OracleDriver.js +80 -0
- package/OracleExceptionConverter.d.ts +7 -0
- package/OracleExceptionConverter.js +145 -0
- package/OracleMikroORM.d.ts +18 -0
- package/OracleMikroORM.js +22 -0
- package/OraclePlatform.d.ts +120 -0
- package/OraclePlatform.js +283 -0
- package/OracleQueryBuilder.d.ts +9 -0
- package/OracleQueryBuilder.js +85 -0
- package/OracleSchemaGenerator.d.ts +41 -0
- package/OracleSchemaGenerator.js +395 -0
- package/OracleSchemaHelper.d.ts +45 -0
- package/OracleSchemaHelper.js +591 -0
- package/README.md +391 -0
- package/index.d.ts +10 -0
- package/index.js +9 -0
- package/package.json +58 -0
- package/tsconfig.build.tsbuildinfo +1 -0
|
@@ -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
|
+
}
|