@dbml/connector 5.6.0-alpha.1 → 6.0.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/.babelrc +13 -0
- package/dist/connectors/bigquery/index.js +291 -0
- package/dist/connectors/connector.js +28 -0
- package/dist/connectors/mssql/index.js +486 -0
- package/dist/connectors/mysql/index.js +454 -0
- package/dist/connectors/oracle/constraints.js +112 -0
- package/dist/connectors/oracle/index.js +68 -0
- package/dist/connectors/oracle/indexes.js +65 -0
- package/dist/connectors/oracle/refs.js +67 -0
- package/dist/connectors/oracle/tables.js +280 -0
- package/dist/connectors/oracle/utils.js +27 -0
- package/dist/connectors/postgres/index.js +517 -0
- package/dist/connectors/snowflake/index.js +311 -0
- package/dist/connectors/types.js +2 -0
- package/dist/index.js +38 -3739
- package/dist/utils/credential-loader.js +58 -0
- package/dist/utils/helpers.js +61 -0
- package/dist/utils/parseSchema.js +59 -0
- package/eslint.config.ts +1 -2
- package/jest.config.ts +18 -0
- package/package.json +25 -8
- package/src/connectors/mysql/index.ts +1 -1
- package/dist/index.mjs +0 -10359
- package/vite.config.ts +0 -53
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fetchSchemaJson = fetchSchemaJson;
|
|
4
|
+
const promise_1 = require("mysql2/promise");
|
|
5
|
+
const lodash_1 = require("lodash");
|
|
6
|
+
const NUMBER_REGEX = '^-?[0-9]+(.[0-9]+)?$';
|
|
7
|
+
async function connectMySQL(connection) {
|
|
8
|
+
const client = await (0, promise_1.createConnection)(connection);
|
|
9
|
+
try {
|
|
10
|
+
await client.connect();
|
|
11
|
+
await client.query('SELECT 1');
|
|
12
|
+
return client;
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
await client.end();
|
|
16
|
+
if (error instanceof Error) {
|
|
17
|
+
throw new Error(`MySQL connection error: ${error.message}`);
|
|
18
|
+
}
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function getEnumName(tableName, columnName) {
|
|
23
|
+
return `${tableName}_${columnName}_enum`;
|
|
24
|
+
}
|
|
25
|
+
function getGenerationExpression(extraType, generationExpression) {
|
|
26
|
+
if (extraType === 'VIRTUAL GENERATED') {
|
|
27
|
+
return `GENERATED ALWAYS AS (${generationExpression}) VIRTUAL`;
|
|
28
|
+
}
|
|
29
|
+
if (extraType === 'STORED GENERATED') {
|
|
30
|
+
return `GENERATED ALWAYS AS (${generationExpression}) STORED`;
|
|
31
|
+
}
|
|
32
|
+
// for timestamp data type
|
|
33
|
+
if (extraType.includes('on update CURRENT_TIMESTAMP')) {
|
|
34
|
+
return 'on update CURRENT_TIMESTAMP';
|
|
35
|
+
}
|
|
36
|
+
// others
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
function getDbDefault(columnDefault, defaultValueType) {
|
|
40
|
+
if (columnDefault === null) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return { value: columnDefault, type: defaultValueType };
|
|
44
|
+
}
|
|
45
|
+
function getFieldType(tableName, columnName, columnType, columnDataType, columnExtra, generationExpression) {
|
|
46
|
+
if (columnDataType === 'enum') {
|
|
47
|
+
// enum must have static value -> no need to check the generation expression
|
|
48
|
+
return getEnumName(tableName, columnName);
|
|
49
|
+
}
|
|
50
|
+
const fieldGenerationExpression = getGenerationExpression(columnExtra, generationExpression);
|
|
51
|
+
if (fieldGenerationExpression) {
|
|
52
|
+
return `${columnType} ${fieldGenerationExpression}`;
|
|
53
|
+
}
|
|
54
|
+
return columnType;
|
|
55
|
+
}
|
|
56
|
+
function generateField(row) {
|
|
57
|
+
const { tableName, columnName, columnDefault, defaultValueType, columnIsNullable, columnType, columnDataType, columnComment, columnExtra, generationExpression, } = row;
|
|
58
|
+
const fieldType = getFieldType(tableName, columnName, columnType, columnDataType, columnExtra, generationExpression);
|
|
59
|
+
const fieldDefaultValue = getDbDefault(columnDefault, defaultValueType);
|
|
60
|
+
const isNullable = columnIsNullable === 'YES';
|
|
61
|
+
return {
|
|
62
|
+
name: columnName,
|
|
63
|
+
type: { type_name: fieldType, schemaName: null },
|
|
64
|
+
dbdefault: fieldDefaultValue,
|
|
65
|
+
not_null: !isNullable,
|
|
66
|
+
increment: columnExtra === 'auto_increment',
|
|
67
|
+
note: { value: columnComment || '' },
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// Do not get the index sub part since in DBML, it is impossible to create index on part of column.
|
|
71
|
+
function getIndexColumn(columnName, idxExpression, idxSubPart) {
|
|
72
|
+
if (idxExpression) {
|
|
73
|
+
return { value: idxExpression, type: 'expression' };
|
|
74
|
+
}
|
|
75
|
+
if (idxSubPart) {
|
|
76
|
+
return { value: `${columnName}(${idxSubPart})`, type: 'expression' };
|
|
77
|
+
}
|
|
78
|
+
if (columnName) {
|
|
79
|
+
return { value: columnName, type: 'column' };
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
async function generateTablesAndFields(client, schemaName) {
|
|
84
|
+
const query = `
|
|
85
|
+
select
|
|
86
|
+
t.table_name as tableName,
|
|
87
|
+
t.table_comment as tableComment,
|
|
88
|
+
c.column_name as columnName,
|
|
89
|
+
c.column_default as columnDefault,
|
|
90
|
+
case
|
|
91
|
+
when c.column_default is null then 'boolean'
|
|
92
|
+
when c.data_type = 'enum' then 'string'
|
|
93
|
+
when c.column_default regexp ? then 'number'
|
|
94
|
+
when c.extra like '%DEFAULT_GENERATED%' then 'expression'
|
|
95
|
+
else 'string'
|
|
96
|
+
end as defaultValueType,
|
|
97
|
+
c.is_nullable as columnIsNullable,
|
|
98
|
+
c.data_type as columnDataType,
|
|
99
|
+
c.column_type as columnType,
|
|
100
|
+
c.extra as columnExtra,
|
|
101
|
+
c.column_comment as columnComment,
|
|
102
|
+
c.generation_expression as generationExpression
|
|
103
|
+
from information_schema.tables t
|
|
104
|
+
join information_schema.columns c
|
|
105
|
+
on t.table_schema = c.table_schema and t.table_name = c.table_name
|
|
106
|
+
where
|
|
107
|
+
t.table_schema = ? and t.table_type = 'BASE TABLE'
|
|
108
|
+
order by
|
|
109
|
+
t.table_name, c.ordinal_position;
|
|
110
|
+
`;
|
|
111
|
+
const queryResponse = await client.query(query, [NUMBER_REGEX, schemaName]);
|
|
112
|
+
const rows = queryResponse[0];
|
|
113
|
+
const tableMap = {};
|
|
114
|
+
const fieldMap = {};
|
|
115
|
+
rows.forEach((row) => {
|
|
116
|
+
const { tableName, tableComment } = row;
|
|
117
|
+
const key = tableName;
|
|
118
|
+
if (!tableMap[key]) {
|
|
119
|
+
tableMap[key] = {
|
|
120
|
+
name: tableName,
|
|
121
|
+
note: { value: tableComment || '' },
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (!fieldMap[key]) {
|
|
125
|
+
fieldMap[key] = [];
|
|
126
|
+
}
|
|
127
|
+
const field = generateField(row);
|
|
128
|
+
fieldMap[key].push(field);
|
|
129
|
+
});
|
|
130
|
+
return {
|
|
131
|
+
tableList: Object.values(tableMap),
|
|
132
|
+
fieldMap,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
async function generateEnums(client, schemaName) {
|
|
136
|
+
const query = `
|
|
137
|
+
select
|
|
138
|
+
t.table_name as tableName,
|
|
139
|
+
c.column_name as columnName,
|
|
140
|
+
TRIM(LEADING 'enum' FROM c.column_type) AS rawValues
|
|
141
|
+
from information_schema.tables t
|
|
142
|
+
join information_schema.columns c
|
|
143
|
+
on t.table_schema = c.table_schema and t.table_name = c.table_name
|
|
144
|
+
where
|
|
145
|
+
t.table_schema = ? and t.table_type = 'BASE TABLE' and c.data_type = 'enum'
|
|
146
|
+
order by
|
|
147
|
+
t.table_name, c.ordinal_position;
|
|
148
|
+
`;
|
|
149
|
+
const queryResponse = await client.query(query, [schemaName]);
|
|
150
|
+
const rows = queryResponse[0];
|
|
151
|
+
const enumList = rows.map((row) => {
|
|
152
|
+
const { tableName, columnName, rawValues } = row;
|
|
153
|
+
// i.e. ('value1','value2')
|
|
154
|
+
const valueList = rawValues
|
|
155
|
+
.slice(1, -1)
|
|
156
|
+
.split(',')
|
|
157
|
+
.map((value) => ({ name: value.slice(1, -1) }));
|
|
158
|
+
const enumName = getEnumName(tableName, columnName);
|
|
159
|
+
return {
|
|
160
|
+
name: enumName,
|
|
161
|
+
values: valueList,
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
return enumList;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Mysql automatically creates index for primary keys, foreign keys, unique constraint. -> Ignore
|
|
168
|
+
*/
|
|
169
|
+
async function generateIndexes(client, schemaName) {
|
|
170
|
+
const query = `
|
|
171
|
+
with
|
|
172
|
+
pk_fk_uniques as (
|
|
173
|
+
select constraint_name, table_name
|
|
174
|
+
from information_schema.table_constraints
|
|
175
|
+
where table_schema = ?
|
|
176
|
+
)
|
|
177
|
+
select
|
|
178
|
+
st.table_name as tableName,
|
|
179
|
+
case
|
|
180
|
+
when st.non_unique = 0 then true
|
|
181
|
+
else false
|
|
182
|
+
end as isIdxUnique,
|
|
183
|
+
st.index_name as idxName,
|
|
184
|
+
st.column_name as columnName,
|
|
185
|
+
st.sub_part as idxSubPart,
|
|
186
|
+
st.index_type as idxType,
|
|
187
|
+
replace(st.expression, '\`', '') as idxExpression
|
|
188
|
+
from information_schema.statistics st
|
|
189
|
+
where
|
|
190
|
+
st.table_schema = ?
|
|
191
|
+
and st.index_name not in (
|
|
192
|
+
select constraint_name
|
|
193
|
+
from pk_fk_uniques pfu
|
|
194
|
+
where pfu.table_name = st.table_name
|
|
195
|
+
)
|
|
196
|
+
and st.index_type in ('BTREE', 'HASH')
|
|
197
|
+
group by
|
|
198
|
+
st.table_name, st.non_unique, st.index_name, st.column_name, st.sub_part, st.index_type, st.expression
|
|
199
|
+
order by st.index_name;
|
|
200
|
+
`;
|
|
201
|
+
const queryResponse = await client.query(query, [schemaName, schemaName]);
|
|
202
|
+
const rows = queryResponse[0];
|
|
203
|
+
const tableIndexMap = rows.reduce((acc, row) => {
|
|
204
|
+
const { tableName, idxName, idxType, isIdxUnique, columnName, idxExpression, idxSubPart, } = row;
|
|
205
|
+
const key = tableName;
|
|
206
|
+
if (!acc[key]) {
|
|
207
|
+
acc[key] = {};
|
|
208
|
+
}
|
|
209
|
+
if (!acc[key][idxName]) {
|
|
210
|
+
// init first index
|
|
211
|
+
acc[key][idxName] = {
|
|
212
|
+
name: idxName,
|
|
213
|
+
type: idxType,
|
|
214
|
+
columns: [],
|
|
215
|
+
unique: !!isIdxUnique,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
const currentIndex = acc[key][idxName];
|
|
219
|
+
const column = getIndexColumn(columnName, idxExpression, idxSubPart);
|
|
220
|
+
if (column) {
|
|
221
|
+
currentIndex.columns.push(column);
|
|
222
|
+
}
|
|
223
|
+
return acc;
|
|
224
|
+
}, {});
|
|
225
|
+
const indexMap = {};
|
|
226
|
+
Object.keys(tableIndexMap).forEach((tableName) => {
|
|
227
|
+
indexMap[tableName] = (0, lodash_1.flatten)(Object.values(tableIndexMap[tableName]));
|
|
228
|
+
});
|
|
229
|
+
return indexMap;
|
|
230
|
+
}
|
|
231
|
+
async function generatePrimaryAndUniqueConstraint(client, schemaName) {
|
|
232
|
+
const query = `
|
|
233
|
+
select
|
|
234
|
+
tc.table_name as tableName,
|
|
235
|
+
tc.constraint_name as constraintName,
|
|
236
|
+
group_concat(kcu.column_name order by kcu.ordinal_position separator ',') as columnNames,
|
|
237
|
+
count(kcu.column_name) as columnCount,
|
|
238
|
+
tc.constraint_type as constraintType
|
|
239
|
+
from information_schema.table_constraints tc
|
|
240
|
+
join information_schema.key_column_usage kcu
|
|
241
|
+
on
|
|
242
|
+
tc.constraint_schema = kcu.constraint_schema
|
|
243
|
+
and tc.constraint_name = kcu.constraint_name
|
|
244
|
+
and tc.table_name = kcu.table_name
|
|
245
|
+
where
|
|
246
|
+
(tc.constraint_type = 'PRIMARY KEY' or tc.constraint_type = 'UNIQUE')
|
|
247
|
+
and tc.table_schema = ?
|
|
248
|
+
group by tc.table_name, tc.constraint_name, tc.constraint_type
|
|
249
|
+
order by tc.table_name, tc.constraint_name;
|
|
250
|
+
`;
|
|
251
|
+
const queryResponse = await client.query(query, [schemaName]);
|
|
252
|
+
const rows = queryResponse[0];
|
|
253
|
+
const inlineConstraintList = rows.filter((constraint) => constraint.columnCount === 1);
|
|
254
|
+
const outOfLineConstraintList = rows.filter((constraint) => constraint.columnCount > 1);
|
|
255
|
+
const compositeTableConstraintMap = outOfLineConstraintList.reduce((acc, row) => {
|
|
256
|
+
const { tableName, constraintName, columnNames, constraintType, } = row;
|
|
257
|
+
const key = tableName;
|
|
258
|
+
if (!acc[key]) {
|
|
259
|
+
acc[key] = {};
|
|
260
|
+
}
|
|
261
|
+
if (!acc[key][constraintName]) {
|
|
262
|
+
acc[key][constraintName] = {
|
|
263
|
+
name: constraintName,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
if (constraintType === 'PRIMARY KEY') {
|
|
267
|
+
acc[key][constraintName].pk = true;
|
|
268
|
+
}
|
|
269
|
+
if (constraintType === 'UNIQUE') {
|
|
270
|
+
acc[key][constraintName].unique = true;
|
|
271
|
+
}
|
|
272
|
+
const columnList = columnNames
|
|
273
|
+
.split(',')
|
|
274
|
+
.map((col) => ({ type: 'column', value: col }));
|
|
275
|
+
acc[key][constraintName].columns = columnList;
|
|
276
|
+
return acc;
|
|
277
|
+
}, {});
|
|
278
|
+
const compositeConstraintMap = {};
|
|
279
|
+
Object.keys(compositeTableConstraintMap).forEach((tableName) => {
|
|
280
|
+
compositeConstraintMap[tableName] = (0, lodash_1.flatten)(Object.values(compositeTableConstraintMap[tableName]));
|
|
281
|
+
});
|
|
282
|
+
const constraintMap = inlineConstraintList.reduce((acc, row) => {
|
|
283
|
+
const { tableName, columnNames, constraintType } = row;
|
|
284
|
+
const key = tableName;
|
|
285
|
+
if (!acc[key]) {
|
|
286
|
+
acc[key] = {};
|
|
287
|
+
}
|
|
288
|
+
const columnList = columnNames.split(',');
|
|
289
|
+
columnList.forEach((columnName) => {
|
|
290
|
+
if (!acc[key][columnName]) {
|
|
291
|
+
acc[key][columnName] = { checks: [] };
|
|
292
|
+
}
|
|
293
|
+
if (constraintType === 'PRIMARY KEY') {
|
|
294
|
+
acc[key][columnName].pk = true;
|
|
295
|
+
}
|
|
296
|
+
if (constraintType === 'UNIQUE' && !acc[key][columnName].pk) {
|
|
297
|
+
acc[key][columnName].unique = true;
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
return acc;
|
|
301
|
+
}, {});
|
|
302
|
+
return { compositeConstraintMap, primaryAndUniqueConstraintMap: constraintMap };
|
|
303
|
+
}
|
|
304
|
+
function extractColumnsFromCheckConstraint(checkClause) {
|
|
305
|
+
return [...checkClause.matchAll(/`(?:``|[^`])*`/g)].map((col) => col.toString().slice(1, -1));
|
|
306
|
+
}
|
|
307
|
+
async function generateCheckConstraints(client, schemaName) {
|
|
308
|
+
const query = `
|
|
309
|
+
select
|
|
310
|
+
cc.constraint_name as constraintName,
|
|
311
|
+
tc.table_name as tableName,
|
|
312
|
+
cc.check_clause as checkClause
|
|
313
|
+
from information_schema.check_constraints cc
|
|
314
|
+
join information_schema.table_constraints tc
|
|
315
|
+
on cc.constraint_name = tc.constraint_name
|
|
316
|
+
where cc.constraint_schema = ?
|
|
317
|
+
`;
|
|
318
|
+
const queryResponse = await client.query(query, [schemaName]);
|
|
319
|
+
const rows = queryResponse[0];
|
|
320
|
+
const checks = {};
|
|
321
|
+
const tableConstraints = {};
|
|
322
|
+
rows.forEach((row) => {
|
|
323
|
+
const { constraintName, tableName, checkClause, } = row;
|
|
324
|
+
const key = tableName;
|
|
325
|
+
const cols = extractColumnsFromCheckConstraint(checkClause);
|
|
326
|
+
if (cols.length === 1) {
|
|
327
|
+
const col = cols[0];
|
|
328
|
+
if (!tableConstraints[key])
|
|
329
|
+
tableConstraints[key] = {};
|
|
330
|
+
if (!tableConstraints[key][col])
|
|
331
|
+
tableConstraints[key][col] = { checks: [] };
|
|
332
|
+
tableConstraints[key][col].checks.push({
|
|
333
|
+
name: constraintName,
|
|
334
|
+
// The check definition has the form: `(expr)`
|
|
335
|
+
// The expression starts at 1 and ends at -1
|
|
336
|
+
expression: checkClause.slice(1, -1),
|
|
337
|
+
});
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (!checks[key])
|
|
341
|
+
checks[key] = [];
|
|
342
|
+
checks[key].push({
|
|
343
|
+
name: constraintName,
|
|
344
|
+
// The check definition has the form: `(expr)`
|
|
345
|
+
// The expression starts at 1 and ends at -1
|
|
346
|
+
expression: checkClause.slice(1, -1),
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
return { checks, tableConstraints };
|
|
350
|
+
}
|
|
351
|
+
async function generateForeignKeys(client, schemaName) {
|
|
352
|
+
const query = `
|
|
353
|
+
select
|
|
354
|
+
rc.constraint_name as constraintName,
|
|
355
|
+
rc.table_name as foreignTableName,
|
|
356
|
+
group_concat(kcu.column_name order by kcu.ordinal_position separator ',') as foreignColumnNames,
|
|
357
|
+
kcu.referenced_table_name as refTableName,
|
|
358
|
+
group_concat(kcu.referenced_column_name order by kcu.ordinal_position separator ',') as refColumnNames,
|
|
359
|
+
rc.update_rule as onUpdate,
|
|
360
|
+
rc.delete_rule as onDelete
|
|
361
|
+
from information_schema.referential_constraints rc
|
|
362
|
+
join information_schema.key_column_usage kcu
|
|
363
|
+
on
|
|
364
|
+
rc.constraint_name = kcu.constraint_name
|
|
365
|
+
and rc.table_name = kcu.table_name
|
|
366
|
+
and rc.constraint_schema = kcu.table_schema
|
|
367
|
+
where rc.constraint_schema = ?
|
|
368
|
+
group by
|
|
369
|
+
rc.constraint_name,
|
|
370
|
+
rc.table_name,
|
|
371
|
+
kcu.referenced_table_name,
|
|
372
|
+
rc.update_rule,
|
|
373
|
+
rc.delete_rule
|
|
374
|
+
order by
|
|
375
|
+
rc.table_name;
|
|
376
|
+
`;
|
|
377
|
+
const queryResponse = await client.query(query, [schemaName]);
|
|
378
|
+
const rows = queryResponse[0];
|
|
379
|
+
const foreignKeyList = rows.map((row) => {
|
|
380
|
+
const { constraintName, onDelete, onUpdate, foreignTableName, foreignColumnNames, refTableName, refColumnNames, } = row;
|
|
381
|
+
const endpoint1 = {
|
|
382
|
+
tableName: foreignTableName,
|
|
383
|
+
fieldNames: foreignColumnNames.split(','),
|
|
384
|
+
relation: '*',
|
|
385
|
+
};
|
|
386
|
+
const endpoint2 = {
|
|
387
|
+
tableName: refTableName,
|
|
388
|
+
fieldNames: refColumnNames.split(','),
|
|
389
|
+
relation: '1',
|
|
390
|
+
};
|
|
391
|
+
return {
|
|
392
|
+
name: constraintName,
|
|
393
|
+
endpoints: [endpoint1, endpoint2],
|
|
394
|
+
onDelete: onDelete === 'NO ACTION' ? null : onDelete,
|
|
395
|
+
onUpdate: onUpdate === 'NO ACTION' ? null : onUpdate,
|
|
396
|
+
};
|
|
397
|
+
});
|
|
398
|
+
return foreignKeyList;
|
|
399
|
+
}
|
|
400
|
+
function combineIndexAndCompositeConstraint(userDefinedIndexMap, compositeConstraintMap) {
|
|
401
|
+
const indexMap = Object.assign(userDefinedIndexMap, {});
|
|
402
|
+
Object.keys(compositeConstraintMap).forEach((tableName) => {
|
|
403
|
+
const compositeConstraint = compositeConstraintMap[tableName];
|
|
404
|
+
if (!indexMap[tableName]) {
|
|
405
|
+
indexMap[tableName] = compositeConstraint;
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
indexMap[tableName].push(...compositeConstraint);
|
|
409
|
+
});
|
|
410
|
+
return indexMap;
|
|
411
|
+
}
|
|
412
|
+
async function fetchSchemaJson(connection) {
|
|
413
|
+
const client = await connectMySQL(connection);
|
|
414
|
+
// In MySQL, a schema is equal database
|
|
415
|
+
const { database: schemaName } = client.config;
|
|
416
|
+
if (!schemaName) {
|
|
417
|
+
throw new Error('Cannot get schema name from the connection');
|
|
418
|
+
}
|
|
419
|
+
try {
|
|
420
|
+
const result = await Promise.all([
|
|
421
|
+
generateTablesAndFields(client, schemaName),
|
|
422
|
+
generateEnums(client, schemaName),
|
|
423
|
+
generateIndexes(client, schemaName),
|
|
424
|
+
generatePrimaryAndUniqueConstraint(client, schemaName),
|
|
425
|
+
generateForeignKeys(client, schemaName),
|
|
426
|
+
generateCheckConstraints(client, schemaName),
|
|
427
|
+
]);
|
|
428
|
+
const [{ tableList, fieldMap }, enumList, rawIndexMap, { primaryAndUniqueConstraintMap, compositeConstraintMap }, foreignKeyList, { checks: tableCheckConstraintMap, tableConstraints: columnCheckConstraintMap },] = result;
|
|
429
|
+
// combine normal index and composite key
|
|
430
|
+
const indexMap = combineIndexAndCompositeConstraint(rawIndexMap, compositeConstraintMap);
|
|
431
|
+
const tableConstraints = { ...primaryAndUniqueConstraintMap };
|
|
432
|
+
Object.keys(columnCheckConstraintMap).forEach((tableName) => {
|
|
433
|
+
if (!tableConstraints[tableName])
|
|
434
|
+
tableConstraints[tableName] = {};
|
|
435
|
+
Object.keys(columnCheckConstraintMap[tableName]).forEach((colName) => {
|
|
436
|
+
if (!tableConstraints[tableName][colName])
|
|
437
|
+
tableConstraints[tableName][colName] = { checks: [] };
|
|
438
|
+
tableConstraints[tableName][colName].checks.push(...columnCheckConstraintMap[tableName][colName].checks);
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
return {
|
|
442
|
+
tables: tableList,
|
|
443
|
+
fields: fieldMap,
|
|
444
|
+
refs: foreignKeyList,
|
|
445
|
+
enums: enumList,
|
|
446
|
+
indexes: indexMap,
|
|
447
|
+
tableConstraints,
|
|
448
|
+
checks: tableCheckConstraintMap,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
finally {
|
|
452
|
+
await client.end();
|
|
453
|
+
}
|
|
454
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateConstraints = generateConstraints;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
async function generateConstraints(client) {
|
|
6
|
+
const query = `
|
|
7
|
+
WITH all_constraints AS (
|
|
8
|
+
SELECT
|
|
9
|
+
c.CONSTRAINT_NAME,
|
|
10
|
+
c.TABLE_NAME,
|
|
11
|
+
c.CONSTRAINT_TYPE,
|
|
12
|
+
c.SEARCH_CONDITION_VC AS CHECK_EXPRESSION
|
|
13
|
+
FROM USER_CONSTRAINTS c
|
|
14
|
+
WHERE c.CONSTRAINT_TYPE IN ('P', 'U', 'C') -- P stands for primary key, U stands for unique and C stands for check
|
|
15
|
+
AND (c.CONSTRAINT_TYPE != 'C' OR UPPER(c.SEARCH_CONDITION_VC) NOT LIKE '%IS NOT NULL')
|
|
16
|
+
),
|
|
17
|
+
constraint_columns AS (
|
|
18
|
+
SELECT
|
|
19
|
+
cc.CONSTRAINT_NAME,
|
|
20
|
+
LISTAGG(cc.COLUMN_NAME, '${utils_1.LIST_SEPARATOR}') WITHIN GROUP (ORDER BY cc.POSITION) AS COLUMNS,
|
|
21
|
+
COUNT(cc.COLUMN_NAME) AS COLUMN_COUNT
|
|
22
|
+
FROM USER_CONS_COLUMNS cc
|
|
23
|
+
GROUP BY cc.CONSTRAINT_NAME
|
|
24
|
+
)
|
|
25
|
+
SELECT
|
|
26
|
+
ac.CONSTRAINT_NAME AS "constraint_name",
|
|
27
|
+
ac.TABLE_NAME AS "table_name",
|
|
28
|
+
cc.COLUMNS AS "columns",
|
|
29
|
+
cc.COLUMN_COUNT AS "column_count",
|
|
30
|
+
ac.CHECK_EXPRESSION AS "check_expression",
|
|
31
|
+
ac.CONSTRAINT_TYPE AS "constraint_type"
|
|
32
|
+
FROM all_constraints ac
|
|
33
|
+
LEFT JOIN constraint_columns cc
|
|
34
|
+
ON ac.CONSTRAINT_NAME = cc.CONSTRAINT_NAME
|
|
35
|
+
LEFT JOIN USER_OBJECTS obj
|
|
36
|
+
ON obj.OBJECT_NAME = ac.TABLE_NAME
|
|
37
|
+
AND obj.OBJECT_TYPE = 'TABLE'
|
|
38
|
+
ORDER BY
|
|
39
|
+
obj.CREATED,
|
|
40
|
+
obj.OBJECT_ID,
|
|
41
|
+
ac.CONSTRAINT_NAME
|
|
42
|
+
`;
|
|
43
|
+
const indexes = {};
|
|
44
|
+
const tableConstraints = {};
|
|
45
|
+
const checks = {};
|
|
46
|
+
const res = await client.execute(query, [], utils_1.EXECUTE_OPTIONS);
|
|
47
|
+
res.rows?.forEach((row) => {
|
|
48
|
+
const { constraint_name, table_name, columns: columnsStr, column_count, check_expression, constraint_type } = row;
|
|
49
|
+
const tableName = table_name;
|
|
50
|
+
const constraintName = constraint_name || '';
|
|
51
|
+
const columns = (columnsStr || '').split(utils_1.LIST_SEPARATOR);
|
|
52
|
+
const columnCount = column_count || 1;
|
|
53
|
+
const constraintType = constraint_type;
|
|
54
|
+
const checkExpression = (check_expression || null);
|
|
55
|
+
// Table-level check constraints
|
|
56
|
+
if (constraintType === 'C' && checkExpression && columnCount > 1) {
|
|
57
|
+
if (!checks[tableName])
|
|
58
|
+
checks[tableName] = [];
|
|
59
|
+
const check = {
|
|
60
|
+
name: constraintName,
|
|
61
|
+
expression: checkExpression,
|
|
62
|
+
};
|
|
63
|
+
checks[tableName].push(check);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
// Column-level check constraints
|
|
67
|
+
if (constraintType === 'C' && checkExpression && columnCount === 1 && columns.length === 1) {
|
|
68
|
+
if (!tableConstraints[tableName])
|
|
69
|
+
tableConstraints[tableName] = {};
|
|
70
|
+
const column = columns[0];
|
|
71
|
+
if (!tableConstraints[tableName][column])
|
|
72
|
+
tableConstraints[tableName][column] = { checks: [] };
|
|
73
|
+
tableConstraints[tableName][column].checks.push({
|
|
74
|
+
name: constraintName,
|
|
75
|
+
expression: checkExpression,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
// Column-level unique or primary indexes
|
|
79
|
+
if (['P', 'U'].includes(constraintType) && columnCount === 1 && columns.length === 1) {
|
|
80
|
+
if (!tableConstraints[tableName])
|
|
81
|
+
tableConstraints[tableName] = {};
|
|
82
|
+
const column = columns[0];
|
|
83
|
+
if (!tableConstraints[tableName][column])
|
|
84
|
+
tableConstraints[tableName][column] = { checks: [] };
|
|
85
|
+
tableConstraints[tableName][column].pk ||= constraintType === 'P';
|
|
86
|
+
tableConstraints[tableName][column].unique ||= constraintType === 'U';
|
|
87
|
+
}
|
|
88
|
+
// Table-level unique or primary indexes
|
|
89
|
+
if (['P', 'U'].includes(constraintType) && columnCount > 1) {
|
|
90
|
+
if (!tableConstraints[tableName])
|
|
91
|
+
tableConstraints[tableName] = {};
|
|
92
|
+
if (!indexes[tableName])
|
|
93
|
+
indexes[tableName] = [];
|
|
94
|
+
const index = {
|
|
95
|
+
name: constraintName,
|
|
96
|
+
type: '',
|
|
97
|
+
unique: true,
|
|
98
|
+
pk: constraintType === 'P',
|
|
99
|
+
columns: columns.map((c) => ({
|
|
100
|
+
type: 'column',
|
|
101
|
+
value: c,
|
|
102
|
+
})),
|
|
103
|
+
};
|
|
104
|
+
indexes[tableName].push(index);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
return {
|
|
108
|
+
indexes,
|
|
109
|
+
tableConstraints,
|
|
110
|
+
checks,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fetchSchemaJson = fetchSchemaJson;
|
|
4
|
+
const oracledb_1 = require("oracledb");
|
|
5
|
+
const tables_1 = require("./tables");
|
|
6
|
+
const refs_1 = require("./refs");
|
|
7
|
+
const constraints_1 = require("./constraints");
|
|
8
|
+
const indexes_1 = require("./indexes");
|
|
9
|
+
const utils_1 = require("./utils");
|
|
10
|
+
const getValidatedClient = async (username, password, dbidentifier) => {
|
|
11
|
+
const client = await (0, oracledb_1.getConnection)({
|
|
12
|
+
username,
|
|
13
|
+
password,
|
|
14
|
+
connectionString: dbidentifier,
|
|
15
|
+
});
|
|
16
|
+
try {
|
|
17
|
+
await client.execute('SELECT 1 FROM DUAL');
|
|
18
|
+
return client;
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
await client.close();
|
|
22
|
+
if (error instanceof Error) {
|
|
23
|
+
throw new Error(`OracleSQL connection error: ${error.message}`);
|
|
24
|
+
}
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
// Expect an Easy Connect string format: username/password@[//]host[:port][/database]
|
|
29
|
+
// `username` and `password` are mandatory
|
|
30
|
+
// `port` and `database` (and the parts enclosed in `[]`) are optional
|
|
31
|
+
// Explanation of the format: https://www.orafaq.com/wiki/EZCONNECT
|
|
32
|
+
async function fetchSchemaJson(connection) {
|
|
33
|
+
const { username, password, dbidentifier } = (0, utils_1.processEasyConnectString)(connection);
|
|
34
|
+
const client = await getValidatedClient(username, password, dbidentifier);
|
|
35
|
+
const tablesAndFieldsRes = (0, tables_1.generateTablesAndFields)(client);
|
|
36
|
+
const constraintsRes = (0, constraints_1.generateConstraints)(client);
|
|
37
|
+
const refsRes = (0, refs_1.generateRawRefs)(client);
|
|
38
|
+
const indexesRes = (0, indexes_1.generateIndexes)(client);
|
|
39
|
+
const res = await Promise.all([
|
|
40
|
+
tablesAndFieldsRes,
|
|
41
|
+
constraintsRes,
|
|
42
|
+
refsRes,
|
|
43
|
+
indexesRes,
|
|
44
|
+
]);
|
|
45
|
+
const { tables, fields } = res[0];
|
|
46
|
+
const { indexes: uniqueIndexes, tableConstraints, checks } = res[1];
|
|
47
|
+
const refs = res[2];
|
|
48
|
+
const indexes = res[3];
|
|
49
|
+
Object.keys(uniqueIndexes).forEach((key) => {
|
|
50
|
+
if (!indexes[key])
|
|
51
|
+
indexes[key] = uniqueIndexes[key];
|
|
52
|
+
else
|
|
53
|
+
indexes[key].push(...uniqueIndexes[key]);
|
|
54
|
+
});
|
|
55
|
+
await client.close();
|
|
56
|
+
return {
|
|
57
|
+
tables,
|
|
58
|
+
fields,
|
|
59
|
+
refs,
|
|
60
|
+
// As of oracle 19c, there's no native ENUM data type
|
|
61
|
+
// ENUM is introduced in oracle 23ai
|
|
62
|
+
enums: [],
|
|
63
|
+
indexes,
|
|
64
|
+
tableConstraints,
|
|
65
|
+
checks,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateIndexes = generateIndexes;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
async function generateIndexes(client) {
|
|
6
|
+
const query = `
|
|
7
|
+
SELECT
|
|
8
|
+
i.INDEX_NAME AS "index_name",
|
|
9
|
+
i.TABLE_NAME AS "table_name",
|
|
10
|
+
CASE
|
|
11
|
+
WHEN i.INDEX_TYPE LIKE '%NORMAL' THEN 'btree'
|
|
12
|
+
ELSE LOWER(REGEXP_REPLACE(i.INDEX_TYPE, '^FUNCTION-BASED ', ''))
|
|
13
|
+
END AS "index_type",
|
|
14
|
+
CASE WHEN i.UNIQUENESS = 'UNIQUE' THEN 1 ELSE 0 END AS "is_unique",
|
|
15
|
+
ic.COLUMN_NAME AS "column_name",
|
|
16
|
+
ic.COLUMN_POSITION AS "column_position",
|
|
17
|
+
ie.COLUMN_EXPRESSION AS "column_expression"
|
|
18
|
+
FROM USER_INDEXES i
|
|
19
|
+
LEFT JOIN USER_IND_COLUMNS ic
|
|
20
|
+
ON i.INDEX_NAME = ic.INDEX_NAME
|
|
21
|
+
LEFT JOIN USER_IND_EXPRESSIONS ie
|
|
22
|
+
ON i.INDEX_NAME = ie.INDEX_NAME
|
|
23
|
+
AND ic.COLUMN_POSITION = ie.COLUMN_POSITION
|
|
24
|
+
LEFT JOIN USER_OBJECTS obj
|
|
25
|
+
ON obj.OBJECT_NAME = i.TABLE_NAME
|
|
26
|
+
AND obj.OBJECT_TYPE = 'TABLE'
|
|
27
|
+
WHERE NOT EXISTS (
|
|
28
|
+
SELECT 1
|
|
29
|
+
FROM USER_CONSTRAINTS c
|
|
30
|
+
WHERE c.INDEX_NAME = i.INDEX_NAME
|
|
31
|
+
)
|
|
32
|
+
ORDER BY obj.CREATED, obj.OBJECT_ID, i.INDEX_NAME, ic.COLUMN_POSITION
|
|
33
|
+
`;
|
|
34
|
+
const res = await client.execute(query, [], utils_1.EXECUTE_OPTIONS);
|
|
35
|
+
const indexes = {};
|
|
36
|
+
const indexMap = {};
|
|
37
|
+
res.rows?.forEach((row) => {
|
|
38
|
+
const { index_name, table_name, index_type, is_unique, column_name, column_expression } = row;
|
|
39
|
+
const name = index_name;
|
|
40
|
+
const table = table_name;
|
|
41
|
+
const type = index_type;
|
|
42
|
+
const unique = !!is_unique;
|
|
43
|
+
if (column_expression) {
|
|
44
|
+
if (!indexMap[name]) {
|
|
45
|
+
indexMap[name] = { table, type, unique, columns: [] };
|
|
46
|
+
}
|
|
47
|
+
indexMap[name].columns.push({ type: 'expression', value: column_expression });
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (column_name) {
|
|
51
|
+
if (!indexMap[name]) {
|
|
52
|
+
indexMap[name] = { table, type, unique, columns: [] };
|
|
53
|
+
}
|
|
54
|
+
indexMap[name].columns.push({ type: 'column', value: column_name });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
Object.entries(indexMap).forEach(([name, { table, type, unique, columns }]) => {
|
|
59
|
+
const index = { name, type, unique, columns };
|
|
60
|
+
if (!indexes[table])
|
|
61
|
+
indexes[table] = [];
|
|
62
|
+
indexes[table].push(index);
|
|
63
|
+
});
|
|
64
|
+
return indexes;
|
|
65
|
+
}
|