@dbml/connector 5.0.0-alpha.0 → 5.1.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__/connectors/mssql/expect-out-files/schema.json +140 -106
- package/__tests__/connectors/mysql/expect-out-files/schema.json +40 -0
- package/__tests__/connectors/postgres/expect-out-files/schema.json +100 -0
- package/dist/connectors/bigqueryConnector.js +4 -2
- package/dist/connectors/mssqlConnector.js +70 -140
- package/dist/connectors/mysqlConnector.js +66 -7
- package/dist/connectors/postgresConnector.d.ts +1 -1
- package/dist/connectors/postgresConnector.js +97 -29
- package/dist/connectors/snowflakeConnector.js +8 -5
- package/dist/connectors/types.d.ts +10 -6
- package/dist/utils/helpers.d.ts +2 -1
- package/dist/utils/helpers.js +14 -3
- package/package.json +2 -2
- package/src/connectors/bigqueryConnector.ts +4 -2
- package/src/connectors/mssqlConnector.ts +74 -149
- package/src/connectors/mysqlConnector.ts +76 -11
- package/src/connectors/postgresConnector.ts +104 -29
- package/src/connectors/snowflakeConnector.ts +19 -15
- package/src/connectors/types.ts +16 -61
- package/src/utils/helpers.ts +30 -12
- package/__tests__/connectors/mssql/out-files/schema.json +0 -2074
- package/__tests__/connectors/mysql/out-files/schema.json +0 -1430
- package/__tests__/connectors/postgres/out-files/schema.json +0 -1719
|
@@ -40,7 +40,7 @@ const executeQuery = (connection, sqlText) => {
|
|
|
40
40
|
return new Promise((resolve, reject) => {
|
|
41
41
|
console.log('Executing query...');
|
|
42
42
|
connection.execute({
|
|
43
|
-
sqlText
|
|
43
|
+
sqlText,
|
|
44
44
|
complete: (err, stmt, rows) => {
|
|
45
45
|
if (err) {
|
|
46
46
|
reject(err);
|
|
@@ -51,7 +51,7 @@ const executeQuery = (connection, sqlText) => {
|
|
|
51
51
|
resolve(rows);
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
-
}
|
|
54
|
+
},
|
|
55
55
|
});
|
|
56
56
|
});
|
|
57
57
|
};
|
|
@@ -203,14 +203,14 @@ const generateTablesAndFields = async (conn, schemas) => {
|
|
|
203
203
|
};
|
|
204
204
|
const createConstraintKeysMap = (keys, schemas, constraintType) => {
|
|
205
205
|
return keys.reduce((acc, row) => {
|
|
206
|
-
const { schema_name, table_name, column_name, constraint_name } = row;
|
|
206
|
+
const { schema_name, table_name, column_name, constraint_name, } = row;
|
|
207
207
|
const selectedSchema = schemas.length > 0 ? schemas.includes(schema_name) : true;
|
|
208
208
|
if (!selectedSchema) {
|
|
209
209
|
return acc;
|
|
210
210
|
}
|
|
211
211
|
const key = `${schema_name}.${table_name}.${constraint_name}`;
|
|
212
212
|
if (acc[key]) {
|
|
213
|
-
const columnNames = acc[key]
|
|
213
|
+
const { columnNames } = acc[key];
|
|
214
214
|
columnNames.push(column_name);
|
|
215
215
|
return acc;
|
|
216
216
|
}
|
|
@@ -238,13 +238,14 @@ const generateIndexes = async (conn, databaseName, schemas) => {
|
|
|
238
238
|
const uniqueKeysByConstraint = createConstraintKeysMap(uniqueKeys, schemas, 'unique');
|
|
239
239
|
const allConstraints = [Object.values(primaryKeysByConstraint), Object.values(uniqueKeysByConstraint)].flat();
|
|
240
240
|
const { indexes, tableConstraints } = allConstraints.reduce((acc, row) => {
|
|
241
|
-
const { schemaName, tableName, constraintName, columnNames, type, primary, unique } = row;
|
|
241
|
+
const { schemaName, tableName, constraintName, columnNames, type, primary, unique, } = row;
|
|
242
242
|
const key = `${schemaName}.${tableName}`;
|
|
243
243
|
if (columnNames.length < 2) {
|
|
244
244
|
acc.tableConstraints[key] = {
|
|
245
245
|
[columnNames[0]]: {
|
|
246
246
|
pk: primary,
|
|
247
247
|
unique,
|
|
248
|
+
checks: [],
|
|
248
249
|
},
|
|
249
250
|
};
|
|
250
251
|
return acc;
|
|
@@ -304,6 +305,8 @@ const fetchSchemaJson = async (connection) => {
|
|
|
304
305
|
enums: [],
|
|
305
306
|
indexes,
|
|
306
307
|
tableConstraints,
|
|
308
|
+
// Checks are not supported in snowflake
|
|
309
|
+
checks: {},
|
|
307
310
|
};
|
|
308
311
|
};
|
|
309
312
|
exports.fetchSchemaJson = fetchSchemaJson;
|
|
@@ -28,10 +28,18 @@ interface Table {
|
|
|
28
28
|
interface FieldsDictionary {
|
|
29
29
|
[key: string]: Field[];
|
|
30
30
|
}
|
|
31
|
+
interface CheckConstraint {
|
|
32
|
+
name?: string;
|
|
33
|
+
expression: string;
|
|
34
|
+
}
|
|
35
|
+
interface CheckConstraintDictionary {
|
|
36
|
+
[tableName: string]: CheckConstraint[];
|
|
37
|
+
}
|
|
31
38
|
interface TableConstraint {
|
|
32
39
|
[fieldName: string]: {
|
|
33
40
|
pk?: boolean;
|
|
34
41
|
unique?: boolean;
|
|
42
|
+
checks: CheckConstraint[];
|
|
35
43
|
};
|
|
36
44
|
}
|
|
37
45
|
interface TableConstraintsDictionary {
|
|
@@ -78,6 +86,7 @@ interface DatabaseSchema {
|
|
|
78
86
|
tableConstraints: TableConstraintsDictionary;
|
|
79
87
|
refs: Ref[];
|
|
80
88
|
indexes: IndexesDictionary;
|
|
89
|
+
checks: CheckConstraintDictionary;
|
|
81
90
|
}
|
|
82
91
|
interface BigQueryCredentials {
|
|
83
92
|
projectId?: string;
|
|
@@ -87,9 +96,4 @@ interface BigQueryCredentials {
|
|
|
87
96
|
};
|
|
88
97
|
datasets: string[];
|
|
89
98
|
}
|
|
90
|
-
|
|
91
|
-
columns: string[];
|
|
92
|
-
enumValues: EnumValue[];
|
|
93
|
-
constraint_name: string;
|
|
94
|
-
}
|
|
95
|
-
export { NoteInfo, TypeInfo, DefaultType, DefaultInfo, Field, Table, FieldsDictionary, TableConstraint, TableConstraintsDictionary, RefEndpoint, Ref, EnumValue, Enum, IndexColumn, Index, IndexesDictionary, DatabaseSchema, BigQueryCredentials, EnumValuesDict, };
|
|
99
|
+
export { NoteInfo, TypeInfo, DefaultType, DefaultInfo, Field, Table, FieldsDictionary, TableConstraint, TableConstraintsDictionary, RefEndpoint, Ref, EnumValue, Enum, IndexColumn, Index, IndexesDictionary, DatabaseSchema, BigQueryCredentials, CheckConstraintDictionary, };
|
package/dist/utils/helpers.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { FieldsDictionary, IndexesDictionary, Table, TableConstraintsDictionary } from '../connectors/types';
|
|
1
|
+
import { CheckConstraintDictionary, FieldsDictionary, IndexesDictionary, Table, TableConstraintsDictionary } from '../connectors/types';
|
|
2
2
|
export declare function getIntersection<T>(firstList: T[], secondList: T[]): T[];
|
|
3
3
|
export declare function getTableSchemaKey(schemaName: string, tableName: string): string;
|
|
4
4
|
export declare function mergeTables(firstTableList: Table[], secondTable: Table[]): Table[];
|
|
5
5
|
export declare function mergeFieldDictionary(firstDict: FieldsDictionary, secondDict: FieldsDictionary): FieldsDictionary;
|
|
6
6
|
export declare function mergeIndexDictionary(firstDict: IndexesDictionary, secondDict: IndexesDictionary): IndexesDictionary;
|
|
7
7
|
export declare function mergeTableConstraintDictionary(firstDict: TableConstraintsDictionary, secondDict: TableConstraintsDictionary): TableConstraintsDictionary;
|
|
8
|
+
export declare function mergeCheckConstraintDictionary(firstDict: CheckConstraintDictionary, secondDict: CheckConstraintDictionary): CheckConstraintDictionary;
|
package/dist/utils/helpers.js
CHANGED
|
@@ -6,6 +6,7 @@ exports.mergeTables = mergeTables;
|
|
|
6
6
|
exports.mergeFieldDictionary = mergeFieldDictionary;
|
|
7
7
|
exports.mergeIndexDictionary = mergeIndexDictionary;
|
|
8
8
|
exports.mergeTableConstraintDictionary = mergeTableConstraintDictionary;
|
|
9
|
+
exports.mergeCheckConstraintDictionary = mergeCheckConstraintDictionary;
|
|
9
10
|
function getIntersection(firstList, secondList) {
|
|
10
11
|
return firstList.filter((item) => secondList.includes(item));
|
|
11
12
|
}
|
|
@@ -19,7 +20,7 @@ function mergeTables(firstTableList, secondTable) {
|
|
|
19
20
|
return firstTableList.concat(secondTable);
|
|
20
21
|
}
|
|
21
22
|
function mergeFieldDictionary(firstDict, secondDict) {
|
|
22
|
-
const result =
|
|
23
|
+
const result = { ...firstDict };
|
|
23
24
|
Object.keys(secondDict).forEach((key) => {
|
|
24
25
|
if (!result[key]) {
|
|
25
26
|
result[key] = [];
|
|
@@ -29,7 +30,7 @@ function mergeFieldDictionary(firstDict, secondDict) {
|
|
|
29
30
|
return result;
|
|
30
31
|
}
|
|
31
32
|
function mergeIndexDictionary(firstDict, secondDict) {
|
|
32
|
-
const result =
|
|
33
|
+
const result = { ...firstDict };
|
|
33
34
|
Object.keys(secondDict).forEach((key) => {
|
|
34
35
|
if (!result[key]) {
|
|
35
36
|
result[key] = [];
|
|
@@ -39,7 +40,7 @@ function mergeIndexDictionary(firstDict, secondDict) {
|
|
|
39
40
|
return result;
|
|
40
41
|
}
|
|
41
42
|
function mergeTableConstraintDictionary(firstDict, secondDict) {
|
|
42
|
-
const result =
|
|
43
|
+
const result = { ...firstDict };
|
|
43
44
|
Object.keys(secondDict).forEach((key) => {
|
|
44
45
|
if (!result[key]) {
|
|
45
46
|
result[key] = {};
|
|
@@ -48,3 +49,13 @@ function mergeTableConstraintDictionary(firstDict, secondDict) {
|
|
|
48
49
|
});
|
|
49
50
|
return result;
|
|
50
51
|
}
|
|
52
|
+
function mergeCheckConstraintDictionary(firstDict, secondDict) {
|
|
53
|
+
const result = { ...firstDict };
|
|
54
|
+
Object.keys(secondDict).forEach((key) => {
|
|
55
|
+
if (!result[key]) {
|
|
56
|
+
result[key] = [];
|
|
57
|
+
}
|
|
58
|
+
result[key] = result[key].concat(secondDict[key]);
|
|
59
|
+
});
|
|
60
|
+
return result;
|
|
61
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dbml/connector",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"description": "This package was created to fetch the schema JSON from many kind of databases.",
|
|
5
5
|
"author": "huy.phung.sw@gmail.com",
|
|
6
6
|
"license": "MIT",
|
|
@@ -44,5 +44,5 @@
|
|
|
44
44
|
"engines": {
|
|
45
45
|
"node": ">=18"
|
|
46
46
|
},
|
|
47
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "376dca507512a3fe549f630e6f22d7d339939183"
|
|
48
48
|
}
|
|
@@ -297,7 +297,7 @@ async function generateTableConstraints (
|
|
|
297
297
|
}
|
|
298
298
|
|
|
299
299
|
if (!inlinePrimaryKeyMap[key][columnNames]) {
|
|
300
|
-
inlinePrimaryKeyMap[key][columnNames] = {};
|
|
300
|
+
inlinePrimaryKeyMap[key][columnNames] = { checks: [] };
|
|
301
301
|
}
|
|
302
302
|
|
|
303
303
|
inlinePrimaryKeyMap[key][columnNames].pk = true;
|
|
@@ -372,6 +372,8 @@ async function fetchSchemaJsonByDataset (
|
|
|
372
372
|
enums: [],
|
|
373
373
|
indexes: mergeIndexDictionary(indexMap, compositePrimaryKeyMap),
|
|
374
374
|
tableConstraints: inlinePrimaryKeyMap,
|
|
375
|
+
// Checks are not supported in BigQuery
|
|
376
|
+
checks: {},
|
|
375
377
|
};
|
|
376
378
|
}
|
|
377
379
|
|
|
@@ -401,7 +403,7 @@ async function fetchSchemaJson (keyFilename: string): Promise<DatabaseSchema> {
|
|
|
401
403
|
return acc;
|
|
402
404
|
},
|
|
403
405
|
{
|
|
404
|
-
tables: [], fields: {}, tableConstraints: {}, indexes: {}, refs: [], enums: [],
|
|
406
|
+
tables: [], fields: {}, tableConstraints: {}, indexes: {}, refs: [], enums: [], checks: {},
|
|
405
407
|
},
|
|
406
408
|
);
|
|
407
409
|
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
/* eslint-disable camelcase */
|
|
2
|
+
/* TDD 72
|
|
3
|
+
* - In MSSQL, there's no notion of ENUM.
|
|
4
|
+
* - 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
|
+
* - 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
|
+
* */
|
|
2
7
|
import sql from 'mssql';
|
|
3
8
|
import { buildSchemaQuery, parseConnectionString } from '../utils/parseSchema';
|
|
4
9
|
import {
|
|
10
|
+
CheckConstraintDictionary,
|
|
5
11
|
DatabaseSchema,
|
|
6
12
|
DefaultInfo,
|
|
7
13
|
DefaultType,
|
|
8
|
-
Enum,
|
|
9
14
|
Field,
|
|
10
15
|
FieldsDictionary,
|
|
11
16
|
IndexesDictionary,
|
|
@@ -13,7 +18,6 @@ import {
|
|
|
13
18
|
RefEndpoint,
|
|
14
19
|
Table,
|
|
15
20
|
TableConstraintsDictionary,
|
|
16
|
-
EnumValuesDict,
|
|
17
21
|
} from './types';
|
|
18
22
|
|
|
19
23
|
// https://learn.microsoft.com/en-us/sql/t-sql/data-types/date-and-time-types?view=sql-server-ver15
|
|
@@ -111,66 +115,6 @@ const getDbdefault = (data_type: string, column_default: string, default_type: D
|
|
|
111
115
|
};
|
|
112
116
|
};
|
|
113
117
|
|
|
114
|
-
const getEnumValues = (definition: string, constraint_name: string): EnumValuesDict[] => {
|
|
115
|
-
// Use the example below to understand the regex:
|
|
116
|
-
// ([quantity]>(0))
|
|
117
|
-
// ([unit_price]>(0))
|
|
118
|
-
// ([status]='cancelled' OR [status]='delivered' OR [status]='shipped' OR [status]='processing' OR [status]='pending')
|
|
119
|
-
// ([total_amount]>(0))
|
|
120
|
-
// ([price]>(0))
|
|
121
|
-
// ([stock_quantity]>=(0))
|
|
122
|
-
// ([age_start]<=[age_end])
|
|
123
|
-
// ([age_start]<=[age_end])
|
|
124
|
-
// ([gender]='Other' OR [gender]='Female' OR [gender]='Male')
|
|
125
|
-
// ([date_of_birth]<=dateadd(year,(-13),getdate()))
|
|
126
|
-
// ([email] like '%_@_%._%')
|
|
127
|
-
if (!definition) return [];
|
|
128
|
-
const values = definition.match(/\[([^\]]+)\]='([^']+)'/g); // Extracting the enum values when the definition contains ]='
|
|
129
|
-
if (!values) return [];
|
|
130
|
-
|
|
131
|
-
const colMap: { [key: string]: string[] } = {};
|
|
132
|
-
values.forEach((value) => {
|
|
133
|
-
const [columnName, enumValue] = value.split("]='");
|
|
134
|
-
const cleanColumnName = columnName.slice(1); // Remove the leading '['
|
|
135
|
-
const cleanEnumValue = enumValue.slice(0, -1); // Remove the trailing "'"
|
|
136
|
-
if (!colMap[cleanColumnName]) {
|
|
137
|
-
colMap[cleanColumnName] = [cleanEnumValue];
|
|
138
|
-
} else {
|
|
139
|
-
colMap[cleanColumnName].push(cleanEnumValue);
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
const arraysEqual = (a: string[], b: string[]): boolean => {
|
|
144
|
-
if (a.length !== b.length) return false;
|
|
145
|
-
const sortedA = [...a].sort();
|
|
146
|
-
const sortedB = [...b].sort();
|
|
147
|
-
return sortedA.every((value, index) => value === sortedB[index]);
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
// Please check the comments of the EnumValuesDict type in types.ts to understand the structure
|
|
151
|
-
const result: EnumValuesDict[] = [];
|
|
152
|
-
const processedKeys = new Set<string>();
|
|
153
|
-
|
|
154
|
-
Object.keys(colMap).forEach((key) => {
|
|
155
|
-
if (processedKeys.has(key)) return;
|
|
156
|
-
|
|
157
|
-
const mergedColumns = [key];
|
|
158
|
-
const colValues = colMap[key];
|
|
159
|
-
|
|
160
|
-
Object.keys(colMap).forEach((innerKey) => {
|
|
161
|
-
if (key !== innerKey && arraysEqual(colValues, colMap[innerKey])) {
|
|
162
|
-
mergedColumns.push(innerKey);
|
|
163
|
-
processedKeys.add(innerKey);
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
const enumValues = colValues.map((value) => ({ name: value }));
|
|
167
|
-
result.push({ columns: mergedColumns, enumValues, constraint_name: `${constraint_name}_${mergedColumns.join('_')}` });
|
|
168
|
-
processedKeys.add(key);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
return result;
|
|
172
|
-
};
|
|
173
|
-
|
|
174
118
|
const generateField = (row: Record<string, any>): Field => {
|
|
175
119
|
const {
|
|
176
120
|
column_name,
|
|
@@ -202,15 +146,11 @@ const generateField = (row: Record<string, any>): Field => {
|
|
|
202
146
|
};
|
|
203
147
|
};
|
|
204
148
|
|
|
205
|
-
const
|
|
149
|
+
const generateTablesFields = async (client: sql.ConnectionPool, schemas: string[]): Promise<{
|
|
206
150
|
tables: Table[],
|
|
207
151
|
fields: FieldsDictionary,
|
|
208
|
-
enums: Enum[],
|
|
209
152
|
}> => {
|
|
210
|
-
const checkConstraintDefinitionDedup: { [key: string]: EnumValuesDict[] } = {};
|
|
211
|
-
|
|
212
153
|
const fields: FieldsDictionary = {};
|
|
213
|
-
const enums: Enum[] = [];
|
|
214
154
|
const tablesAndFieldsSql = `
|
|
215
155
|
WITH tables_and_fields AS (
|
|
216
156
|
SELECT
|
|
@@ -249,35 +189,6 @@ const generateTablesFieldsAndEnums = async (client: sql.ConnectionPool, schemas:
|
|
|
249
189
|
AND ep.name like '%description%'
|
|
250
190
|
WHERE
|
|
251
191
|
t.type = 'U' -- User-defined tables
|
|
252
|
-
),
|
|
253
|
-
constraints_with_nulls AS (
|
|
254
|
-
SELECT
|
|
255
|
-
tf.*,
|
|
256
|
-
cc.name AS check_constraint_name,
|
|
257
|
-
CASE
|
|
258
|
-
WHEN cc.definition LIKE '%[' + tf.column_name + ']%=''%' THEN cc.definition
|
|
259
|
-
ELSE NULL
|
|
260
|
-
END AS check_constraint_definition
|
|
261
|
-
FROM
|
|
262
|
-
tables_and_fields AS tf
|
|
263
|
-
LEFT JOIN sys.check_constraints cc
|
|
264
|
-
ON cc.parent_object_id = OBJECT_ID(tf.table_schema + '.' + tf.table_name)
|
|
265
|
-
AND cc.definition LIKE '%' + tf.column_name + '%' -- Ensure the constraint references the column
|
|
266
|
-
),
|
|
267
|
-
constraints_with_row_number AS (
|
|
268
|
-
SELECT
|
|
269
|
-
*,
|
|
270
|
-
ROW_NUMBER() OVER (
|
|
271
|
-
PARTITION BY table_schema, table_name, column_name
|
|
272
|
-
ORDER BY
|
|
273
|
-
CASE
|
|
274
|
-
WHEN check_constraint_definition IS NOT NULL THEN 1
|
|
275
|
-
ELSE 2
|
|
276
|
-
END,
|
|
277
|
-
check_constraint_name
|
|
278
|
-
) AS rn
|
|
279
|
-
FROM
|
|
280
|
-
constraints_with_nulls
|
|
281
192
|
)
|
|
282
193
|
SELECT
|
|
283
194
|
table_schema,
|
|
@@ -292,17 +203,15 @@ const generateTablesFieldsAndEnums = async (client: sql.ConnectionPool, schemas:
|
|
|
292
203
|
column_default,
|
|
293
204
|
table_comment,
|
|
294
205
|
column_comment,
|
|
295
|
-
check_constraint_name,
|
|
296
|
-
check_constraint_definition,
|
|
297
206
|
CASE
|
|
298
207
|
WHEN column_default LIKE '((%))' THEN 'number'
|
|
299
208
|
WHEN column_default LIKE '(''%'')' THEN 'string'
|
|
300
209
|
ELSE 'expression'
|
|
301
210
|
END AS default_type
|
|
302
211
|
FROM
|
|
303
|
-
|
|
212
|
+
tables_and_fields
|
|
304
213
|
WHERE
|
|
305
|
-
|
|
214
|
+
1 = 1
|
|
306
215
|
${buildSchemaQuery('table_schema', schemas)}
|
|
307
216
|
ORDER BY
|
|
308
217
|
table_schema,
|
|
@@ -317,8 +226,6 @@ const generateTablesFieldsAndEnums = async (client: sql.ConnectionPool, schemas:
|
|
|
317
226
|
table_schema,
|
|
318
227
|
table_name,
|
|
319
228
|
table_comment,
|
|
320
|
-
check_constraint_name,
|
|
321
|
-
check_constraint_definition,
|
|
322
229
|
} = row;
|
|
323
230
|
const key = `${table_schema}.${table_name}`;
|
|
324
231
|
|
|
@@ -332,30 +239,6 @@ const generateTablesFieldsAndEnums = async (client: sql.ConnectionPool, schemas:
|
|
|
332
239
|
|
|
333
240
|
if (!fields[key]) fields[key] = [];
|
|
334
241
|
const field = generateField(row);
|
|
335
|
-
|
|
336
|
-
if (check_constraint_definition && !checkConstraintDefinitionDedup[check_constraint_name]) {
|
|
337
|
-
const enumValuesByColumns = getEnumValues(check_constraint_definition, check_constraint_name);
|
|
338
|
-
enumValuesByColumns.forEach((item) => {
|
|
339
|
-
enums.push({
|
|
340
|
-
name: item.constraint_name,
|
|
341
|
-
schemaName: table_schema,
|
|
342
|
-
values: item.enumValues,
|
|
343
|
-
});
|
|
344
|
-
});
|
|
345
|
-
checkConstraintDefinitionDedup[check_constraint_name] = enumValuesByColumns;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
if (checkConstraintDefinitionDedup[check_constraint_name]) {
|
|
349
|
-
const enumValuesByColumns = checkConstraintDefinitionDedup[check_constraint_name];
|
|
350
|
-
const columnEnumValues = enumValuesByColumns.find((item) => item.columns.includes(field.name));
|
|
351
|
-
if (columnEnumValues) {
|
|
352
|
-
field.type = {
|
|
353
|
-
type_name: columnEnumValues.constraint_name,
|
|
354
|
-
schemaName: table_schema,
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
242
|
fields[key].push(field);
|
|
360
243
|
|
|
361
244
|
return acc;
|
|
@@ -364,7 +247,6 @@ const generateTablesFieldsAndEnums = async (client: sql.ConnectionPool, schemas:
|
|
|
364
247
|
return {
|
|
365
248
|
tables: Object.values(tables),
|
|
366
249
|
fields,
|
|
367
|
-
enums,
|
|
368
250
|
};
|
|
369
251
|
};
|
|
370
252
|
|
|
@@ -461,7 +343,7 @@ const generateRefs = async (client: sql.ConnectionPool, schemas: string[]): Prom
|
|
|
461
343
|
return refs;
|
|
462
344
|
};
|
|
463
345
|
|
|
464
|
-
const
|
|
346
|
+
const generateIndexesAndConstraints = async (client: sql.ConnectionPool, schemas: string[]) => {
|
|
465
347
|
const indexListSql = `
|
|
466
348
|
WITH user_tables AS (
|
|
467
349
|
SELECT
|
|
@@ -537,19 +419,19 @@ const generateIndexes = async (client: sql.ConnectionPool, schemas: string[]) =>
|
|
|
537
419
|
ii.index_name;
|
|
538
420
|
`;
|
|
539
421
|
const indexListResult = await client.query(indexListSql);
|
|
540
|
-
const {
|
|
422
|
+
const { outOfLineIndexes, inlineIndexes } = indexListResult.recordset.reduce((acc, row) => {
|
|
541
423
|
const { constraint_type, columns } = row;
|
|
542
424
|
|
|
543
425
|
if (columns === 'null' || columns.trim() === '') return acc;
|
|
544
426
|
if (constraint_type === 'PRIMARY KEY' || constraint_type === 'UNIQUE') {
|
|
545
|
-
acc.
|
|
427
|
+
acc.inlineIndexes.push(row);
|
|
546
428
|
} else {
|
|
547
|
-
acc.
|
|
429
|
+
acc.outOfLineIndexes.push(row);
|
|
548
430
|
}
|
|
549
431
|
return acc;
|
|
550
|
-
}, {
|
|
432
|
+
}, { inlineIndexes: [], outOfLineIndexes: [] });
|
|
551
433
|
|
|
552
|
-
const indexes =
|
|
434
|
+
const indexes = outOfLineIndexes.reduce((acc: IndexesDictionary, indexRow: Record<string, any>) => {
|
|
553
435
|
const {
|
|
554
436
|
table_schema,
|
|
555
437
|
table_name,
|
|
@@ -591,7 +473,9 @@ const generateIndexes = async (client: sql.ConnectionPool, schemas: string[]) =>
|
|
|
591
473
|
return acc;
|
|
592
474
|
}, {});
|
|
593
475
|
|
|
594
|
-
const tableConstraints
|
|
476
|
+
const tableConstraints: TableConstraintsDictionary = {};
|
|
477
|
+
|
|
478
|
+
inlineIndexes.forEach((row: Record<string, any>) => {
|
|
595
479
|
const {
|
|
596
480
|
table_schema,
|
|
597
481
|
table_name,
|
|
@@ -599,24 +483,64 @@ const generateIndexes = async (client: sql.ConnectionPool, schemas: string[]) =>
|
|
|
599
483
|
constraint_type,
|
|
600
484
|
} = row;
|
|
601
485
|
const key = `${table_schema}.${table_name}`;
|
|
602
|
-
if (!
|
|
486
|
+
if (!tableConstraints[key]) tableConstraints[key] = {};
|
|
603
487
|
|
|
604
488
|
const columnNames = columns.split(',').map((column: string) => column.trim());
|
|
605
489
|
columnNames.forEach((columnName: string) => {
|
|
606
|
-
if (!
|
|
490
|
+
if (!tableConstraints[key][columnName]) tableConstraints[key][columnName] = { checks: [] };
|
|
607
491
|
if (constraint_type === 'PRIMARY KEY') {
|
|
608
|
-
|
|
492
|
+
tableConstraints[key][columnName].pk = true;
|
|
609
493
|
}
|
|
610
|
-
if (constraint_type === 'UNIQUE' && !
|
|
611
|
-
|
|
494
|
+
if (constraint_type === 'UNIQUE' && !tableConstraints[key][columnName].pk) {
|
|
495
|
+
tableConstraints[key][columnName].unique = true;
|
|
612
496
|
}
|
|
613
497
|
});
|
|
614
|
-
|
|
615
|
-
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
const checkListSql = `
|
|
501
|
+
SELECT
|
|
502
|
+
OBJECT_SCHEMA_NAME(cc.parent_object_id) AS table_schema,
|
|
503
|
+
OBJECT_NAME(cc.parent_object_id) AS table_name,
|
|
504
|
+
c.name AS column_name,
|
|
505
|
+
cc.name AS check_name,
|
|
506
|
+
cc.definition AS check_expression
|
|
507
|
+
FROM
|
|
508
|
+
sys.check_constraints cc
|
|
509
|
+
LEFT JOIN
|
|
510
|
+
sys.columns c ON cc.parent_object_id = c.object_id AND cc.parent_column_id = c.column_id
|
|
511
|
+
WHERE 1 = 1
|
|
512
|
+
${buildSchemaQuery('OBJECT_SCHEMA_NAME(cc.parent_object_id)', schemas)}
|
|
513
|
+
`;
|
|
514
|
+
|
|
515
|
+
const checkListResult = await client.query(checkListSql);
|
|
516
|
+
const checks: CheckConstraintDictionary = {};
|
|
517
|
+
checkListResult.recordset.forEach((row) => {
|
|
518
|
+
const {
|
|
519
|
+
table_schema,
|
|
520
|
+
table_name,
|
|
521
|
+
column_name,
|
|
522
|
+
check_name,
|
|
523
|
+
check_expression,
|
|
524
|
+
} = row;
|
|
525
|
+
const key = `${table_schema}.${table_name}`;
|
|
526
|
+
if (column_name) {
|
|
527
|
+
if (!tableConstraints[key]) tableConstraints[key] = {};
|
|
528
|
+
if (!tableConstraints[key][column_name]) tableConstraints[key][column_name] = { checks: [] };
|
|
529
|
+
// The check definition has the form: `(expr)`
|
|
530
|
+
// The expression starts at 1 and ends at -1
|
|
531
|
+
tableConstraints[key][column_name].checks.push({ name: check_name, expression: check_expression.slice(1, -1) });
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
if (!checks[key]) checks[key] = [];
|
|
535
|
+
// The check definition has the form: `(expr)`
|
|
536
|
+
// The expression starts at 1 and ends at -1
|
|
537
|
+
checks[key].push({ name: check_name, expression: check_expression.slice(1, -1) });
|
|
538
|
+
});
|
|
616
539
|
|
|
617
540
|
return {
|
|
618
541
|
indexes,
|
|
619
542
|
tableConstraints,
|
|
543
|
+
checks,
|
|
620
544
|
};
|
|
621
545
|
};
|
|
622
546
|
|
|
@@ -624,28 +548,29 @@ const fetchSchemaJson = async (connection: string): Promise<DatabaseSchema> => {
|
|
|
624
548
|
const { connectionString, schemas } = parseConnectionString(connection, 'odbc');
|
|
625
549
|
const client = await getValidatedClient(connectionString);
|
|
626
550
|
|
|
627
|
-
const
|
|
628
|
-
const
|
|
551
|
+
const tablesRes = generateTablesFields(client, schemas);
|
|
552
|
+
const indexesAndConstraintsRes = generateIndexesAndConstraints(client, schemas);
|
|
629
553
|
const refsRes = generateRefs(client, schemas);
|
|
630
554
|
|
|
631
555
|
const res = await Promise.all([
|
|
632
|
-
|
|
633
|
-
|
|
556
|
+
tablesRes,
|
|
557
|
+
indexesAndConstraintsRes,
|
|
634
558
|
refsRes,
|
|
635
559
|
]);
|
|
636
|
-
client.close();
|
|
637
560
|
|
|
638
|
-
const { tables, fields
|
|
639
|
-
const { indexes, tableConstraints } = res[1];
|
|
561
|
+
const { tables, fields } = res[0];
|
|
562
|
+
const { indexes, tableConstraints, checks } = res[1];
|
|
640
563
|
const refs = res[2];
|
|
641
564
|
|
|
642
565
|
return {
|
|
643
566
|
tables,
|
|
644
567
|
fields,
|
|
645
|
-
enums
|
|
568
|
+
// MSSQL doesn't support the notion of enums
|
|
569
|
+
enums: [],
|
|
646
570
|
refs,
|
|
647
571
|
indexes,
|
|
648
572
|
tableConstraints,
|
|
573
|
+
checks,
|
|
649
574
|
};
|
|
650
575
|
};
|
|
651
576
|
|