@dbml/connector 5.1.0 → 5.3.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/__tests__/connector.test.ts +1 -1
- package/__tests__/connectors/mssql/expect-out-files/schema.json +98 -7
- package/__tests__/connectors/mssql/out-files/schema.json +2165 -0
- package/__tests__/connectors/mysql/expect-out-files/schema.json +99 -9
- package/__tests__/connectors/mysql/out-files/schema.json +1520 -0
- package/__tests__/connectors/oracle/connection.json +4 -0
- package/__tests__/connectors/oracle/expect-out-files/schema.json +3866 -0
- package/__tests__/connectors/oracle/out-files/schema.json +3866 -0
- package/__tests__/connectors/postgres/expect-out-files/schema.json +119 -8
- package/__tests__/connectors/postgres/out-files/schema.json +1830 -0
- package/dist/connectors/{bigqueryConnector.d.ts → bigquery/index.d.ts} +1 -1
- package/dist/connectors/{bigqueryConnector.js → bigquery/index.js} +3 -4
- package/dist/connectors/connector.js +13 -10
- package/dist/connectors/{snowflakeConnector.d.ts → mssql/index.d.ts} +1 -1
- package/dist/connectors/{mssqlConnector.js → mssql/index.js} +13 -10
- package/dist/connectors/{mysqlConnector.d.ts → mysql/index.d.ts} +1 -1
- package/dist/connectors/{mysqlConnector.js → mysql/index.js} +1 -1
- package/dist/connectors/oracle/constraints.d.ts +7 -0
- package/dist/connectors/oracle/constraints.js +112 -0
- package/dist/connectors/oracle/index.d.ts +3 -0
- package/dist/connectors/oracle/index.js +68 -0
- package/dist/connectors/oracle/indexes.d.ts +3 -0
- package/dist/connectors/oracle/indexes.js +65 -0
- package/dist/connectors/oracle/refs.d.ts +3 -0
- package/dist/connectors/oracle/refs.js +67 -0
- package/dist/connectors/oracle/tables.d.ts +7 -0
- package/dist/connectors/oracle/tables.js +280 -0
- package/dist/connectors/oracle/utils.d.ts +9 -0
- package/dist/connectors/oracle/utils.js +27 -0
- package/dist/connectors/{postgresConnector.d.ts → postgres/index.d.ts} +1 -1
- package/dist/connectors/{postgresConnector.js → postgres/index.js} +21 -16
- package/dist/connectors/{mssqlConnector.d.ts → snowflake/index.d.ts} +1 -1
- package/dist/connectors/{snowflakeConnector.js → snowflake/index.js} +1 -2
- package/dist/connectors/types.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +17 -7
- package/dist/utils/parseSchema.d.ts +1 -1
- package/dist/utils/parseSchema.js +13 -13
- package/eslint.config.ts +70 -0
- package/jest.config.ts +2 -2
- package/package.json +17 -6
- package/src/connectors/{bigqueryConnector.ts → bigquery/index.ts} +4 -5
- package/src/connectors/connector.ts +8 -5
- package/src/connectors/{mssqlConnector.ts → mssql/index.ts} +20 -15
- package/src/connectors/{mysqlConnector.ts → mysql/index.ts} +3 -3
- package/src/connectors/oracle/constraints.ts +113 -0
- package/src/connectors/oracle/index.ts +76 -0
- package/src/connectors/oracle/indexes.ts +71 -0
- package/src/connectors/oracle/refs.ts +73 -0
- package/src/connectors/oracle/tables.ts +305 -0
- package/src/connectors/oracle/utils.ts +23 -0
- package/src/connectors/{postgresConnector.ts → postgres/index.ts} +25 -18
- package/src/connectors/{snowflakeConnector.ts → snowflake/index.ts} +7 -6
- package/src/connectors/types.ts +14 -13
- package/src/index.ts +1 -1
- package/src/utils/parseSchema.ts +11 -11
- package/tsconfig.json +1 -1
- package/.eslintrc.json +0 -24
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BigQuery, BigQueryOptions } from '@google-cloud/bigquery';
|
|
2
|
-
import { loadCredentialFromFile, parseBigQueryCredential } from '
|
|
2
|
+
import { loadCredentialFromFile, parseBigQueryCredential } from '../../utils/credential-loader';
|
|
3
3
|
import {
|
|
4
4
|
getIntersection,
|
|
5
5
|
getTableSchemaKey,
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
mergeIndexDictionary,
|
|
8
8
|
mergeTableConstraintDictionary,
|
|
9
9
|
mergeTables,
|
|
10
|
-
} from '
|
|
10
|
+
} from '../../utils/helpers';
|
|
11
11
|
import {
|
|
12
12
|
BigQueryCredentials,
|
|
13
13
|
DatabaseSchema,
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
IndexesDictionary,
|
|
20
20
|
Table,
|
|
21
21
|
TableConstraintsDictionary,
|
|
22
|
-
} from '
|
|
22
|
+
} from '../types';
|
|
23
23
|
|
|
24
24
|
// BigQuery client authentication strategy:
|
|
25
25
|
// 1. Start with environment/default authentication:
|
|
@@ -72,8 +72,7 @@ function getDbDefault (
|
|
|
72
72
|
|
|
73
73
|
if (defaultValueType === 'string') {
|
|
74
74
|
// bigquery store the double qoutes and quotes at the end and the beginning of the string: "string" or 'string'
|
|
75
|
-
|
|
76
|
-
return { value: columnDefault.slice(1, -1).replaceAll("'", "\'"), type: defaultValueType };
|
|
75
|
+
return { value: columnDefault.slice(1, -1).replaceAll('\'', '\''), type: defaultValueType };
|
|
77
76
|
}
|
|
78
77
|
|
|
79
78
|
return { value: columnDefault, type: defaultValueType };
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { DatabaseSchema } from './types';
|
|
2
|
-
import { fetchSchemaJson as fetchPostgresSchemaJson } from './
|
|
3
|
-
import { fetchSchemaJson as fetchMssqlSchemaJson } from './
|
|
4
|
-
import { fetchSchemaJson as fetchMysqlSchemaJson } from './
|
|
5
|
-
import { fetchSchemaJson as fetchSnowflakeSchemaJson } from './
|
|
6
|
-
import { fetchSchemaJson as fetchBigQuerySchemaJson } from './
|
|
2
|
+
import { fetchSchemaJson as fetchPostgresSchemaJson } from './postgres';
|
|
3
|
+
import { fetchSchemaJson as fetchMssqlSchemaJson } from './mssql';
|
|
4
|
+
import { fetchSchemaJson as fetchMysqlSchemaJson } from './mysql';
|
|
5
|
+
import { fetchSchemaJson as fetchSnowflakeSchemaJson } from './snowflake';
|
|
6
|
+
import { fetchSchemaJson as fetchBigQuerySchemaJson } from './bigquery';
|
|
7
|
+
import { fetchSchemaJson as fetchOracleSchemaJson } from './oracle';
|
|
7
8
|
|
|
8
9
|
const fetchSchemaJson = async (connection: string, databaseType: string): Promise<DatabaseSchema> => {
|
|
9
10
|
switch (databaseType) {
|
|
@@ -17,6 +18,8 @@ const fetchSchemaJson = async (connection: string, databaseType: string): Promis
|
|
|
17
18
|
return fetchSnowflakeSchemaJson(connection);
|
|
18
19
|
case 'bigquery':
|
|
19
20
|
return fetchBigQuerySchemaJson(connection);
|
|
21
|
+
case 'oracle':
|
|
22
|
+
return fetchOracleSchemaJson(connection);
|
|
20
23
|
default:
|
|
21
24
|
throw new Error(`Unsupported database type: ${databaseType}`);
|
|
22
25
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
/* eslint-disable camelcase */
|
|
2
1
|
/* TDD 72
|
|
3
2
|
* - In MSSQL, there's no notion of ENUM.
|
|
4
3
|
* - MSSQL connector used to extract ENUMS from CHECK constraints, for example: `CHECK ([col]=value1 OR [col]=value2)` would be translated to an enum with 2 values, `value1` and `value2`.
|
|
5
4
|
* - Since DBML 4.0.0, we have supported CHECK constraints, so we no longer extract ENUMS out from CHECK constraints but rather keep them as CHECK constraints.
|
|
6
5
|
* */
|
|
7
6
|
import sql from 'mssql';
|
|
8
|
-
import { buildSchemaQuery, parseConnectionString } from '
|
|
7
|
+
import { buildSchemaQuery, parseConnectionString } from '../../utils/parseSchema';
|
|
9
8
|
import {
|
|
10
9
|
CheckConstraintDictionary,
|
|
11
10
|
DatabaseSchema,
|
|
@@ -18,7 +17,7 @@ import {
|
|
|
18
17
|
RefEndpoint,
|
|
19
18
|
Table,
|
|
20
19
|
TableConstraintsDictionary,
|
|
21
|
-
} from '
|
|
20
|
+
} from '../types';
|
|
22
21
|
|
|
23
22
|
// https://learn.microsoft.com/en-us/sql/t-sql/data-types/date-and-time-types?view=sql-server-ver15
|
|
24
23
|
const MSSQL_DATE_TYPES = [
|
|
@@ -147,8 +146,8 @@ const generateField = (row: Record<string, any>): Field => {
|
|
|
147
146
|
};
|
|
148
147
|
|
|
149
148
|
const generateTablesFields = async (client: sql.ConnectionPool, schemas: string[]): Promise<{
|
|
150
|
-
tables: Table[]
|
|
151
|
-
fields: FieldsDictionary
|
|
149
|
+
tables: Table[];
|
|
150
|
+
fields: FieldsDictionary;
|
|
152
151
|
}> => {
|
|
153
152
|
const fields: FieldsDictionary = {};
|
|
154
153
|
const tablesAndFieldsSql = `
|
|
@@ -293,9 +292,9 @@ const generateRefs = async (client: sql.ConnectionPool, schemas: string[]): Prom
|
|
|
293
292
|
if (ep1.length !== ep2.length) return false;
|
|
294
293
|
return ep1.every(
|
|
295
294
|
(endpoint, index) => endpoint.tableName === ep2[index].tableName
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
295
|
+
&& endpoint.schemaName === ep2[index].schemaName
|
|
296
|
+
&& endpoint.fieldNames.length === ep2[index].fieldNames.length
|
|
297
|
+
&& endpoint.fieldNames.every((field, fieldIndex) => field === ep2[index].fieldNames[fieldIndex]),
|
|
299
298
|
);
|
|
300
299
|
};
|
|
301
300
|
|
|
@@ -333,7 +332,7 @@ const generateRefs = async (client: sql.ConnectionPool, schemas: string[]): Prom
|
|
|
333
332
|
onUpdate: on_update === 'NO_ACTION' ? null : on_update,
|
|
334
333
|
};
|
|
335
334
|
|
|
336
|
-
const isDuplicate = refs.some(ref => endpointsEqual(ref.endpoints, newRef.endpoints));
|
|
335
|
+
const isDuplicate = refs.some((ref) => endpointsEqual(ref.endpoints, newRef.endpoints));
|
|
337
336
|
|
|
338
337
|
if (!isDuplicate) {
|
|
339
338
|
refs.push(newRef);
|
|
@@ -439,6 +438,8 @@ const generateIndexesAndConstraints = async (client: sql.ConnectionPool, schemas
|
|
|
439
438
|
index_type,
|
|
440
439
|
columns,
|
|
441
440
|
expressions,
|
|
441
|
+
is_unique,
|
|
442
|
+
is_primary,
|
|
442
443
|
} = indexRow;
|
|
443
444
|
const indexColumns = columns.split(',').map((column: string) => {
|
|
444
445
|
return {
|
|
@@ -447,12 +448,14 @@ const generateIndexesAndConstraints = async (client: sql.ConnectionPool, schemas
|
|
|
447
448
|
};
|
|
448
449
|
});
|
|
449
450
|
|
|
450
|
-
const indexExpressions = expressions
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
451
|
+
const indexExpressions = expressions
|
|
452
|
+
? expressions.split(',').map((expression: string) => {
|
|
453
|
+
return {
|
|
454
|
+
type: 'expression',
|
|
455
|
+
value: expression,
|
|
456
|
+
};
|
|
457
|
+
})
|
|
458
|
+
: [];
|
|
456
459
|
|
|
457
460
|
const index = {
|
|
458
461
|
name: index_name,
|
|
@@ -461,6 +464,8 @@ const generateIndexesAndConstraints = async (client: sql.ConnectionPool, schemas
|
|
|
461
464
|
...indexColumns,
|
|
462
465
|
...indexExpressions,
|
|
463
466
|
],
|
|
467
|
+
pk: !!is_primary,
|
|
468
|
+
unique: !is_primary && !!is_unique,
|
|
464
469
|
};
|
|
465
470
|
|
|
466
471
|
const key = `${table_schema}.${table_name}`;
|
|
@@ -2,7 +2,7 @@ import { createConnection, Connection } from 'mysql2/promise';
|
|
|
2
2
|
import { flatten } from 'lodash';
|
|
3
3
|
import {
|
|
4
4
|
CheckConstraintDictionary, DefaultInfo, DatabaseSchema, DefaultType, Field, IndexColumn, FieldsDictionary, Table, Enum, IndexesDictionary, TableConstraintsDictionary, Index, Ref,
|
|
5
|
-
} from '
|
|
5
|
+
} from '../types';
|
|
6
6
|
|
|
7
7
|
const NUMBER_REGEX = '^-?[0-9]+(.[0-9]+)?$';
|
|
8
8
|
|
|
@@ -342,7 +342,7 @@ async function generatePrimaryAndUniqueConstraint (client: Connection, schemaNam
|
|
|
342
342
|
}
|
|
343
343
|
|
|
344
344
|
if (constraintType === 'PRIMARY KEY') {
|
|
345
|
-
acc[key][constraintName].
|
|
345
|
+
acc[key][constraintName].pk = true;
|
|
346
346
|
}
|
|
347
347
|
|
|
348
348
|
if (constraintType === 'UNIQUE') {
|
|
@@ -395,7 +395,7 @@ function extractColumnsFromCheckConstraint (checkClause: string): string[] {
|
|
|
395
395
|
return [...checkClause.matchAll(/`(?:``|[^`])*`/g)].map((col) => col.toString().slice(1, -1));
|
|
396
396
|
}
|
|
397
397
|
|
|
398
|
-
async function generateCheckConstraints (client: Connection, schemaName: string): Promise<{ checks: CheckConstraintDictionary
|
|
398
|
+
async function generateCheckConstraints (client: Connection, schemaName: string): Promise<{ checks: CheckConstraintDictionary; tableConstraints: TableConstraintsDictionary }> {
|
|
399
399
|
const query = `
|
|
400
400
|
select
|
|
401
401
|
cc.constraint_name as constraintName,
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { Connection } from 'oracledb';
|
|
2
|
+
import { CheckConstraint, CheckConstraintDictionary, Index, IndexesDictionary, TableConstraintsDictionary } from '../types';
|
|
3
|
+
import { EXECUTE_OPTIONS, LIST_SEPARATOR } from './utils';
|
|
4
|
+
|
|
5
|
+
export async function generateConstraints (client: Connection): Promise<{
|
|
6
|
+
indexes: IndexesDictionary;
|
|
7
|
+
tableConstraints: TableConstraintsDictionary;
|
|
8
|
+
checks: CheckConstraintDictionary;
|
|
9
|
+
}> {
|
|
10
|
+
const query = `
|
|
11
|
+
WITH all_constraints AS (
|
|
12
|
+
SELECT
|
|
13
|
+
c.CONSTRAINT_NAME,
|
|
14
|
+
c.TABLE_NAME,
|
|
15
|
+
c.CONSTRAINT_TYPE,
|
|
16
|
+
c.SEARCH_CONDITION_VC AS CHECK_EXPRESSION
|
|
17
|
+
FROM USER_CONSTRAINTS c
|
|
18
|
+
WHERE c.CONSTRAINT_TYPE IN ('P', 'U', 'C') -- P stands for primary key, U stands for unique and C stands for check
|
|
19
|
+
AND (c.CONSTRAINT_TYPE != 'C' OR UPPER(c.SEARCH_CONDITION_VC) NOT LIKE '%IS NOT NULL')
|
|
20
|
+
),
|
|
21
|
+
constraint_columns AS (
|
|
22
|
+
SELECT
|
|
23
|
+
cc.CONSTRAINT_NAME,
|
|
24
|
+
LISTAGG(cc.COLUMN_NAME, '${LIST_SEPARATOR}') WITHIN GROUP (ORDER BY cc.POSITION) AS COLUMNS,
|
|
25
|
+
COUNT(cc.COLUMN_NAME) AS COLUMN_COUNT
|
|
26
|
+
FROM USER_CONS_COLUMNS cc
|
|
27
|
+
GROUP BY cc.CONSTRAINT_NAME
|
|
28
|
+
)
|
|
29
|
+
SELECT
|
|
30
|
+
ac.CONSTRAINT_NAME AS "constraint_name",
|
|
31
|
+
ac.TABLE_NAME AS "table_name",
|
|
32
|
+
cc.COLUMNS AS "columns",
|
|
33
|
+
cc.COLUMN_COUNT AS "column_count",
|
|
34
|
+
ac.CHECK_EXPRESSION AS "check_expression",
|
|
35
|
+
ac.CONSTRAINT_TYPE AS "constraint_type"
|
|
36
|
+
FROM all_constraints ac
|
|
37
|
+
LEFT JOIN constraint_columns cc
|
|
38
|
+
ON ac.CONSTRAINT_NAME = cc.CONSTRAINT_NAME
|
|
39
|
+
LEFT JOIN USER_OBJECTS obj
|
|
40
|
+
ON obj.OBJECT_NAME = ac.TABLE_NAME
|
|
41
|
+
AND obj.OBJECT_TYPE = 'TABLE'
|
|
42
|
+
ORDER BY
|
|
43
|
+
obj.CREATED,
|
|
44
|
+
obj.OBJECT_ID,
|
|
45
|
+
ac.CONSTRAINT_NAME
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
const indexes: IndexesDictionary = {};
|
|
49
|
+
const tableConstraints: TableConstraintsDictionary = {};
|
|
50
|
+
const checks: CheckConstraintDictionary = {};
|
|
51
|
+
|
|
52
|
+
const res = await client.execute(query, [], EXECUTE_OPTIONS);
|
|
53
|
+
res.rows?.forEach((row) => {
|
|
54
|
+
const { constraint_name, table_name, columns: columnsStr, column_count, check_expression, constraint_type } = row as Record<string, unknown>;
|
|
55
|
+
const tableName = table_name as string;
|
|
56
|
+
const constraintName = (constraint_name as string) || '';
|
|
57
|
+
const columns = ((columnsStr as string) || '').split(LIST_SEPARATOR);
|
|
58
|
+
const columnCount = (column_count as number) || 1;
|
|
59
|
+
const constraintType = constraint_type as 'P' | 'U' | 'C';
|
|
60
|
+
const checkExpression = (check_expression || null) as string | null;
|
|
61
|
+
|
|
62
|
+
// Table-level check constraints
|
|
63
|
+
if (constraintType === 'C' && checkExpression && columnCount > 1) {
|
|
64
|
+
if (!checks[tableName]) checks[tableName] = [];
|
|
65
|
+
const check: CheckConstraint = {
|
|
66
|
+
name: constraintName,
|
|
67
|
+
expression: checkExpression,
|
|
68
|
+
};
|
|
69
|
+
checks[tableName].push(check);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Column-level check constraints
|
|
73
|
+
if (constraintType === 'C' && checkExpression && columnCount === 1 && columns.length === 1) {
|
|
74
|
+
if (!tableConstraints[tableName]) tableConstraints[tableName] = {};
|
|
75
|
+
const column = columns[0];
|
|
76
|
+
if (!tableConstraints[tableName][column]) tableConstraints[tableName][column] = { checks: [] };
|
|
77
|
+
tableConstraints[tableName][column].checks.push({
|
|
78
|
+
name: constraintName,
|
|
79
|
+
expression: checkExpression,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// Column-level unique or primary indexes
|
|
83
|
+
if (['P', 'U'].includes(constraintType) && columnCount === 1 && columns.length === 1) {
|
|
84
|
+
if (!tableConstraints[tableName]) tableConstraints[tableName] = {};
|
|
85
|
+
const column = columns[0];
|
|
86
|
+
if (!tableConstraints[tableName][column]) tableConstraints[tableName][column] = { checks: [] };
|
|
87
|
+
tableConstraints[tableName][column].pk ||= constraintType === 'P';
|
|
88
|
+
tableConstraints[tableName][column].unique ||= constraintType === 'U';
|
|
89
|
+
}
|
|
90
|
+
// Table-level unique or primary indexes
|
|
91
|
+
if (['P', 'U'].includes(constraintType) && columnCount > 1) {
|
|
92
|
+
if (!tableConstraints[tableName]) tableConstraints[tableName] = {};
|
|
93
|
+
if (!indexes[tableName]) indexes[tableName] = [];
|
|
94
|
+
const index: 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
|
+
|
|
108
|
+
return {
|
|
109
|
+
indexes,
|
|
110
|
+
tableConstraints,
|
|
111
|
+
checks,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { getConnection, Connection } from 'oracledb';
|
|
2
|
+
import { DatabaseSchema } from '../types';
|
|
3
|
+
import { generateTablesAndFields } from './tables';
|
|
4
|
+
import { generateRawRefs } from './refs';
|
|
5
|
+
import { generateConstraints } from './constraints';
|
|
6
|
+
import { generateIndexes } from './indexes';
|
|
7
|
+
import { processEasyConnectString } from './utils';
|
|
8
|
+
|
|
9
|
+
const getValidatedClient = async (username: string, password: string, dbidentifier: string): Promise<Connection> => {
|
|
10
|
+
const client = await getConnection({
|
|
11
|
+
username,
|
|
12
|
+
password,
|
|
13
|
+
connectionString: dbidentifier,
|
|
14
|
+
});
|
|
15
|
+
try {
|
|
16
|
+
await client.execute('SELECT 1 FROM DUAL');
|
|
17
|
+
|
|
18
|
+
return client;
|
|
19
|
+
} catch (error) {
|
|
20
|
+
await client.close();
|
|
21
|
+
if (error instanceof Error) {
|
|
22
|
+
throw new Error(`OracleSQL connection error: ${error.message}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Expect an Easy Connect string format: username/password@[//]host[:port][/database]
|
|
30
|
+
// `username` and `password` are mandatory
|
|
31
|
+
// `port` and `database` (and the parts enclosed in `[]`) are optional
|
|
32
|
+
// Explanation of the format: https://www.orafaq.com/wiki/EZCONNECT
|
|
33
|
+
async function fetchSchemaJson (connection: string): Promise<DatabaseSchema> {
|
|
34
|
+
const { username, password, dbidentifier } = processEasyConnectString(connection);
|
|
35
|
+
|
|
36
|
+
const client = await getValidatedClient(username, password, dbidentifier);
|
|
37
|
+
|
|
38
|
+
const tablesAndFieldsRes = generateTablesAndFields(client);
|
|
39
|
+
const constraintsRes = generateConstraints(client);
|
|
40
|
+
const refsRes = generateRawRefs(client);
|
|
41
|
+
const indexesRes = generateIndexes(client);
|
|
42
|
+
|
|
43
|
+
const res = await Promise.all([
|
|
44
|
+
tablesAndFieldsRes,
|
|
45
|
+
constraintsRes,
|
|
46
|
+
refsRes,
|
|
47
|
+
indexesRes,
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
const { tables, fields } = res[0];
|
|
51
|
+
const { indexes: uniqueIndexes, tableConstraints, checks } = res[1];
|
|
52
|
+
const refs = res[2];
|
|
53
|
+
const indexes = res[3];
|
|
54
|
+
Object.keys(uniqueIndexes).forEach((key) => {
|
|
55
|
+
if (!indexes[key]) indexes[key] = uniqueIndexes[key];
|
|
56
|
+
else indexes[key].push(...uniqueIndexes[key]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
await client.close();
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
tables,
|
|
63
|
+
fields,
|
|
64
|
+
refs,
|
|
65
|
+
// As of oracle 19c, there's no native ENUM data type
|
|
66
|
+
// ENUM is introduced in oracle 23ai
|
|
67
|
+
enums: [],
|
|
68
|
+
indexes,
|
|
69
|
+
tableConstraints,
|
|
70
|
+
checks,
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export {
|
|
75
|
+
fetchSchemaJson,
|
|
76
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Connection } from 'oracledb';
|
|
2
|
+
import { Index, IndexesDictionary } from '../types';
|
|
3
|
+
import { EXECUTE_OPTIONS } from './utils';
|
|
4
|
+
|
|
5
|
+
export async function generateIndexes (client: Connection): Promise<IndexesDictionary> {
|
|
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
|
+
|
|
35
|
+
const res = await client.execute(query, [], EXECUTE_OPTIONS);
|
|
36
|
+
|
|
37
|
+
const indexes: IndexesDictionary = {};
|
|
38
|
+
const indexMap: Record<string, { table: string; type: string; unique: boolean; columns: Array<{ type: 'column' | 'expression'; value: string }> }> = {};
|
|
39
|
+
|
|
40
|
+
res.rows?.forEach((row) => {
|
|
41
|
+
const { index_name, table_name, index_type, is_unique, column_name, column_expression } = row as Record<string, unknown>;
|
|
42
|
+
|
|
43
|
+
const name = index_name as string;
|
|
44
|
+
const table = table_name as string;
|
|
45
|
+
const type = index_type as string;
|
|
46
|
+
const unique = !!(is_unique as number);
|
|
47
|
+
|
|
48
|
+
if (column_expression) {
|
|
49
|
+
if (!indexMap[name]) {
|
|
50
|
+
indexMap[name] = { table, type, unique, columns: [] };
|
|
51
|
+
}
|
|
52
|
+
indexMap[name].columns.push({ type: 'expression', value: column_expression as string });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (column_name) {
|
|
56
|
+
if (!indexMap[name]) {
|
|
57
|
+
indexMap[name] = { table, type, unique, columns: [] };
|
|
58
|
+
}
|
|
59
|
+
indexMap[name].columns.push({ type: 'column', value: column_name as string });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
Object.entries(indexMap).forEach(([name, { table, type, unique, columns }]) => {
|
|
65
|
+
const index: Index = { name, type, unique, columns };
|
|
66
|
+
if (!indexes[table]) indexes[table] = [];
|
|
67
|
+
indexes[table].push(index);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return indexes;
|
|
71
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Connection } from 'oracledb';
|
|
2
|
+
import { Ref, RefEndpoint } from '../types';
|
|
3
|
+
import { EXECUTE_OPTIONS, LIST_SEPARATOR } from './utils';
|
|
4
|
+
|
|
5
|
+
export async function generateRawRefs (client: Connection): Promise<Ref[]> {
|
|
6
|
+
const refs: Ref[] = [];
|
|
7
|
+
|
|
8
|
+
const refsListSql = `
|
|
9
|
+
SELECT
|
|
10
|
+
c.CONSTRAINT_NAME AS "fk_name",
|
|
11
|
+
c.TABLE_NAME AS "child_table",
|
|
12
|
+
LISTAGG(cc.COLUMN_NAME, '${LIST_SEPARATOR}') WITHIN GROUP (ORDER BY cc.POSITION) AS "child_columns",
|
|
13
|
+
p.TABLE_NAME AS "parent_table",
|
|
14
|
+
LISTAGG(pc.COLUMN_NAME, '${LIST_SEPARATOR}') WITHIN GROUP (ORDER BY pc.POSITION) AS "parent_columns",
|
|
15
|
+
c.DELETE_RULE AS "delete_rule"
|
|
16
|
+
FROM USER_CONSTRAINTS c
|
|
17
|
+
JOIN USER_CONSTRAINTS p
|
|
18
|
+
ON c.R_CONSTRAINT_NAME = p.CONSTRAINT_NAME
|
|
19
|
+
JOIN USER_CONS_COLUMNS cc
|
|
20
|
+
ON c.CONSTRAINT_NAME = cc.CONSTRAINT_NAME
|
|
21
|
+
JOIN USER_CONS_COLUMNS pc
|
|
22
|
+
ON p.CONSTRAINT_NAME = pc.CONSTRAINT_NAME
|
|
23
|
+
AND cc.POSITION = pc.POSITION
|
|
24
|
+
LEFT JOIN USER_OBJECTS obj_child
|
|
25
|
+
ON obj_child.OBJECT_NAME = c.TABLE_NAME
|
|
26
|
+
AND obj_child.OBJECT_TYPE = 'TABLE'
|
|
27
|
+
LEFT JOIN USER_OBJECTS obj_parent
|
|
28
|
+
ON obj_parent.OBJECT_NAME = p.TABLE_NAME
|
|
29
|
+
AND obj_parent.OBJECT_TYPE = 'TABLE'
|
|
30
|
+
WHERE c.CONSTRAINT_TYPE = 'R'
|
|
31
|
+
GROUP BY
|
|
32
|
+
c.CONSTRAINT_NAME,
|
|
33
|
+
c.TABLE_NAME,
|
|
34
|
+
p.TABLE_NAME,
|
|
35
|
+
c.DELETE_RULE,
|
|
36
|
+
obj_child.CREATED,
|
|
37
|
+
obj_child.OBJECT_ID,
|
|
38
|
+
obj_parent.CREATED,
|
|
39
|
+
obj_parent.OBJECT_ID
|
|
40
|
+
ORDER BY
|
|
41
|
+
obj_child.CREATED,
|
|
42
|
+
obj_child.OBJECT_ID,
|
|
43
|
+
c.CONSTRAINT_NAME
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
const refsQueryResult = await client.execute(refsListSql, [], EXECUTE_OPTIONS);
|
|
47
|
+
refsQueryResult.rows?.forEach((refRow) => {
|
|
48
|
+
const { fk_name, child_table, child_columns, parent_table, parent_columns, delete_rule } = refRow as Record<string, string>;
|
|
49
|
+
|
|
50
|
+
const ep1: RefEndpoint = {
|
|
51
|
+
tableName: child_table,
|
|
52
|
+
schemaName: '',
|
|
53
|
+
fieldNames: child_columns.split(LIST_SEPARATOR),
|
|
54
|
+
relation: '*',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const ep2: RefEndpoint = {
|
|
58
|
+
tableName: parent_table,
|
|
59
|
+
schemaName: '',
|
|
60
|
+
fieldNames: parent_columns.split(LIST_SEPARATOR),
|
|
61
|
+
relation: '1',
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
refs.push({
|
|
65
|
+
name: fk_name,
|
|
66
|
+
endpoints: [ep1, ep2],
|
|
67
|
+
onDelete: delete_rule === 'NO ACTION' ? null : delete_rule,
|
|
68
|
+
onUpdate: null,
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return refs;
|
|
73
|
+
}
|