@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.
Files changed (58) hide show
  1. package/__tests__/connector.test.ts +1 -1
  2. package/__tests__/connectors/mssql/expect-out-files/schema.json +98 -7
  3. package/__tests__/connectors/mssql/out-files/schema.json +2165 -0
  4. package/__tests__/connectors/mysql/expect-out-files/schema.json +99 -9
  5. package/__tests__/connectors/mysql/out-files/schema.json +1520 -0
  6. package/__tests__/connectors/oracle/connection.json +4 -0
  7. package/__tests__/connectors/oracle/expect-out-files/schema.json +3866 -0
  8. package/__tests__/connectors/oracle/out-files/schema.json +3866 -0
  9. package/__tests__/connectors/postgres/expect-out-files/schema.json +119 -8
  10. package/__tests__/connectors/postgres/out-files/schema.json +1830 -0
  11. package/dist/connectors/{bigqueryConnector.d.ts → bigquery/index.d.ts} +1 -1
  12. package/dist/connectors/{bigqueryConnector.js → bigquery/index.js} +3 -4
  13. package/dist/connectors/connector.js +13 -10
  14. package/dist/connectors/{snowflakeConnector.d.ts → mssql/index.d.ts} +1 -1
  15. package/dist/connectors/{mssqlConnector.js → mssql/index.js} +13 -10
  16. package/dist/connectors/{mysqlConnector.d.ts → mysql/index.d.ts} +1 -1
  17. package/dist/connectors/{mysqlConnector.js → mysql/index.js} +1 -1
  18. package/dist/connectors/oracle/constraints.d.ts +7 -0
  19. package/dist/connectors/oracle/constraints.js +112 -0
  20. package/dist/connectors/oracle/index.d.ts +3 -0
  21. package/dist/connectors/oracle/index.js +68 -0
  22. package/dist/connectors/oracle/indexes.d.ts +3 -0
  23. package/dist/connectors/oracle/indexes.js +65 -0
  24. package/dist/connectors/oracle/refs.d.ts +3 -0
  25. package/dist/connectors/oracle/refs.js +67 -0
  26. package/dist/connectors/oracle/tables.d.ts +7 -0
  27. package/dist/connectors/oracle/tables.js +280 -0
  28. package/dist/connectors/oracle/utils.d.ts +9 -0
  29. package/dist/connectors/oracle/utils.js +27 -0
  30. package/dist/connectors/{postgresConnector.d.ts → postgres/index.d.ts} +1 -1
  31. package/dist/connectors/{postgresConnector.js → postgres/index.js} +21 -16
  32. package/dist/connectors/{mssqlConnector.d.ts → snowflake/index.d.ts} +1 -1
  33. package/dist/connectors/{snowflakeConnector.js → snowflake/index.js} +1 -2
  34. package/dist/connectors/types.d.ts +1 -1
  35. package/dist/index.d.ts +1 -1
  36. package/dist/index.js +17 -7
  37. package/dist/utils/parseSchema.d.ts +1 -1
  38. package/dist/utils/parseSchema.js +13 -13
  39. package/eslint.config.ts +70 -0
  40. package/jest.config.ts +2 -2
  41. package/package.json +17 -6
  42. package/src/connectors/{bigqueryConnector.ts → bigquery/index.ts} +4 -5
  43. package/src/connectors/connector.ts +8 -5
  44. package/src/connectors/{mssqlConnector.ts → mssql/index.ts} +20 -15
  45. package/src/connectors/{mysqlConnector.ts → mysql/index.ts} +3 -3
  46. package/src/connectors/oracle/constraints.ts +113 -0
  47. package/src/connectors/oracle/index.ts +76 -0
  48. package/src/connectors/oracle/indexes.ts +71 -0
  49. package/src/connectors/oracle/refs.ts +73 -0
  50. package/src/connectors/oracle/tables.ts +305 -0
  51. package/src/connectors/oracle/utils.ts +23 -0
  52. package/src/connectors/{postgresConnector.ts → postgres/index.ts} +25 -18
  53. package/src/connectors/{snowflakeConnector.ts → snowflake/index.ts} +7 -6
  54. package/src/connectors/types.ts +14 -13
  55. package/src/index.ts +1 -1
  56. package/src/utils/parseSchema.ts +11 -11
  57. package/tsconfig.json +1 -1
  58. package/.eslintrc.json +0 -24
@@ -1,5 +1,5 @@
1
1
  import { BigQuery, BigQueryOptions } from '@google-cloud/bigquery';
2
- import { loadCredentialFromFile, parseBigQueryCredential } from '../utils/credential-loader';
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 '../utils/helpers';
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 './types';
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
- // eslint-disable-next-line no-useless-escape
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 './postgresConnector';
3
- import { fetchSchemaJson as fetchMssqlSchemaJson } from './mssqlConnector';
4
- import { fetchSchemaJson as fetchMysqlSchemaJson } from './mysqlConnector';
5
- import { fetchSchemaJson as fetchSnowflakeSchemaJson } from './snowflakeConnector';
6
- import { fetchSchemaJson as fetchBigQuerySchemaJson } from './bigqueryConnector';
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 '../utils/parseSchema';
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 './types';
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
- && endpoint.schemaName === ep2[index].schemaName
297
- && endpoint.fieldNames.length === ep2[index].fieldNames.length
298
- && endpoint.fieldNames.every((field, fieldIndex) => field === ep2[index].fieldNames[fieldIndex]),
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 ? expressions.split(',').map((expression: string) => {
451
- return {
452
- type: 'expression',
453
- value: expression,
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 './types';
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].primary = true;
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, tableConstraints: TableConstraintsDictionary }> {
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
+ }