@dbml/connector 5.0.0 → 5.2.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 +238 -113
- package/__tests__/connectors/mysql/expect-out-files/schema.json +132 -2
- package/__tests__/connectors/postgres/expect-out-files/schema.json +219 -8
- package/dist/connectors/bigqueryConnector.js +5 -4
- package/dist/connectors/mssqlConnector.js +82 -149
- package/dist/connectors/mysqlConnector.js +67 -8
- package/dist/connectors/postgresConnector.d.ts +1 -1
- package/dist/connectors/postgresConnector.js +115 -42
- package/dist/connectors/snowflakeConnector.js +8 -6
- package/dist/connectors/types.d.ts +10 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +17 -7
- package/dist/utils/helpers.d.ts +2 -1
- package/dist/utils/helpers.js +14 -3
- 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 +15 -6
- package/src/connectors/bigqueryConnector.ts +5 -4
- package/src/connectors/mssqlConnector.ts +92 -162
- package/src/connectors/mysqlConnector.ts +77 -12
- package/src/connectors/postgresConnector.ts +126 -44
- package/src/connectors/snowflakeConnector.ts +24 -19
- package/src/connectors/types.ts +20 -65
- package/src/index.ts +1 -1
- package/src/utils/helpers.ts +30 -12
- package/src/utils/parseSchema.ts +11 -11
- package/test-output.log +18 -0
- package/tsconfig.json +1 -1
- package/.eslintrc.json +0 -24
|
@@ -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 };
|
|
@@ -297,7 +296,7 @@ async function generateTableConstraints (
|
|
|
297
296
|
}
|
|
298
297
|
|
|
299
298
|
if (!inlinePrimaryKeyMap[key][columnNames]) {
|
|
300
|
-
inlinePrimaryKeyMap[key][columnNames] = {};
|
|
299
|
+
inlinePrimaryKeyMap[key][columnNames] = { checks: [] };
|
|
301
300
|
}
|
|
302
301
|
|
|
303
302
|
inlinePrimaryKeyMap[key][columnNames].pk = true;
|
|
@@ -372,6 +371,8 @@ async function fetchSchemaJsonByDataset (
|
|
|
372
371
|
enums: [],
|
|
373
372
|
indexes: mergeIndexDictionary(indexMap, compositePrimaryKeyMap),
|
|
374
373
|
tableConstraints: inlinePrimaryKeyMap,
|
|
374
|
+
// Checks are not supported in BigQuery
|
|
375
|
+
checks: {},
|
|
375
376
|
};
|
|
376
377
|
}
|
|
377
378
|
|
|
@@ -401,7 +402,7 @@ async function fetchSchemaJson (keyFilename: string): Promise<DatabaseSchema> {
|
|
|
401
402
|
return acc;
|
|
402
403
|
},
|
|
403
404
|
{
|
|
404
|
-
tables: [], fields: {}, tableConstraints: {}, indexes: {}, refs: [], enums: [],
|
|
405
|
+
tables: [], fields: {}, tableConstraints: {}, indexes: {}, refs: [], enums: [], checks: {},
|
|
405
406
|
},
|
|
406
407
|
);
|
|
407
408
|
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
/*
|
|
1
|
+
/* TDD 72
|
|
2
|
+
* - In MSSQL, there's no notion of ENUM.
|
|
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`.
|
|
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.
|
|
5
|
+
* */
|
|
2
6
|
import sql from 'mssql';
|
|
3
7
|
import { buildSchemaQuery, parseConnectionString } from '../utils/parseSchema';
|
|
4
8
|
import {
|
|
9
|
+
CheckConstraintDictionary,
|
|
5
10
|
DatabaseSchema,
|
|
6
11
|
DefaultInfo,
|
|
7
12
|
DefaultType,
|
|
8
|
-
Enum,
|
|
9
13
|
Field,
|
|
10
14
|
FieldsDictionary,
|
|
11
15
|
IndexesDictionary,
|
|
@@ -13,7 +17,6 @@ import {
|
|
|
13
17
|
RefEndpoint,
|
|
14
18
|
Table,
|
|
15
19
|
TableConstraintsDictionary,
|
|
16
|
-
EnumValuesDict,
|
|
17
20
|
} from './types';
|
|
18
21
|
|
|
19
22
|
// https://learn.microsoft.com/en-us/sql/t-sql/data-types/date-and-time-types?view=sql-server-ver15
|
|
@@ -111,66 +114,6 @@ const getDbdefault = (data_type: string, column_default: string, default_type: D
|
|
|
111
114
|
};
|
|
112
115
|
};
|
|
113
116
|
|
|
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
117
|
const generateField = (row: Record<string, any>): Field => {
|
|
175
118
|
const {
|
|
176
119
|
column_name,
|
|
@@ -202,15 +145,11 @@ const generateField = (row: Record<string, any>): Field => {
|
|
|
202
145
|
};
|
|
203
146
|
};
|
|
204
147
|
|
|
205
|
-
const
|
|
206
|
-
tables: Table[]
|
|
207
|
-
fields: FieldsDictionary
|
|
208
|
-
enums: Enum[],
|
|
148
|
+
const generateTablesFields = async (client: sql.ConnectionPool, schemas: string[]): Promise<{
|
|
149
|
+
tables: Table[];
|
|
150
|
+
fields: FieldsDictionary;
|
|
209
151
|
}> => {
|
|
210
|
-
const checkConstraintDefinitionDedup: { [key: string]: EnumValuesDict[] } = {};
|
|
211
|
-
|
|
212
152
|
const fields: FieldsDictionary = {};
|
|
213
|
-
const enums: Enum[] = [];
|
|
214
153
|
const tablesAndFieldsSql = `
|
|
215
154
|
WITH tables_and_fields AS (
|
|
216
155
|
SELECT
|
|
@@ -249,35 +188,6 @@ const generateTablesFieldsAndEnums = async (client: sql.ConnectionPool, schemas:
|
|
|
249
188
|
AND ep.name like '%description%'
|
|
250
189
|
WHERE
|
|
251
190
|
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
191
|
)
|
|
282
192
|
SELECT
|
|
283
193
|
table_schema,
|
|
@@ -292,17 +202,15 @@ const generateTablesFieldsAndEnums = async (client: sql.ConnectionPool, schemas:
|
|
|
292
202
|
column_default,
|
|
293
203
|
table_comment,
|
|
294
204
|
column_comment,
|
|
295
|
-
check_constraint_name,
|
|
296
|
-
check_constraint_definition,
|
|
297
205
|
CASE
|
|
298
206
|
WHEN column_default LIKE '((%))' THEN 'number'
|
|
299
207
|
WHEN column_default LIKE '(''%'')' THEN 'string'
|
|
300
208
|
ELSE 'expression'
|
|
301
209
|
END AS default_type
|
|
302
210
|
FROM
|
|
303
|
-
|
|
211
|
+
tables_and_fields
|
|
304
212
|
WHERE
|
|
305
|
-
|
|
213
|
+
1 = 1
|
|
306
214
|
${buildSchemaQuery('table_schema', schemas)}
|
|
307
215
|
ORDER BY
|
|
308
216
|
table_schema,
|
|
@@ -317,8 +225,6 @@ const generateTablesFieldsAndEnums = async (client: sql.ConnectionPool, schemas:
|
|
|
317
225
|
table_schema,
|
|
318
226
|
table_name,
|
|
319
227
|
table_comment,
|
|
320
|
-
check_constraint_name,
|
|
321
|
-
check_constraint_definition,
|
|
322
228
|
} = row;
|
|
323
229
|
const key = `${table_schema}.${table_name}`;
|
|
324
230
|
|
|
@@ -332,30 +238,6 @@ const generateTablesFieldsAndEnums = async (client: sql.ConnectionPool, schemas:
|
|
|
332
238
|
|
|
333
239
|
if (!fields[key]) fields[key] = [];
|
|
334
240
|
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
241
|
fields[key].push(field);
|
|
360
242
|
|
|
361
243
|
return acc;
|
|
@@ -364,7 +246,6 @@ const generateTablesFieldsAndEnums = async (client: sql.ConnectionPool, schemas:
|
|
|
364
246
|
return {
|
|
365
247
|
tables: Object.values(tables),
|
|
366
248
|
fields,
|
|
367
|
-
enums,
|
|
368
249
|
};
|
|
369
250
|
};
|
|
370
251
|
|
|
@@ -411,9 +292,9 @@ const generateRefs = async (client: sql.ConnectionPool, schemas: string[]): Prom
|
|
|
411
292
|
if (ep1.length !== ep2.length) return false;
|
|
412
293
|
return ep1.every(
|
|
413
294
|
(endpoint, index) => endpoint.tableName === ep2[index].tableName
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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]),
|
|
417
298
|
);
|
|
418
299
|
};
|
|
419
300
|
|
|
@@ -451,7 +332,7 @@ const generateRefs = async (client: sql.ConnectionPool, schemas: string[]): Prom
|
|
|
451
332
|
onUpdate: on_update === 'NO_ACTION' ? null : on_update,
|
|
452
333
|
};
|
|
453
334
|
|
|
454
|
-
const isDuplicate = refs.some(ref => endpointsEqual(ref.endpoints, newRef.endpoints));
|
|
335
|
+
const isDuplicate = refs.some((ref) => endpointsEqual(ref.endpoints, newRef.endpoints));
|
|
455
336
|
|
|
456
337
|
if (!isDuplicate) {
|
|
457
338
|
refs.push(newRef);
|
|
@@ -461,7 +342,7 @@ const generateRefs = async (client: sql.ConnectionPool, schemas: string[]): Prom
|
|
|
461
342
|
return refs;
|
|
462
343
|
};
|
|
463
344
|
|
|
464
|
-
const
|
|
345
|
+
const generateIndexesAndConstraints = async (client: sql.ConnectionPool, schemas: string[]) => {
|
|
465
346
|
const indexListSql = `
|
|
466
347
|
WITH user_tables AS (
|
|
467
348
|
SELECT
|
|
@@ -537,19 +418,19 @@ const generateIndexes = async (client: sql.ConnectionPool, schemas: string[]) =>
|
|
|
537
418
|
ii.index_name;
|
|
538
419
|
`;
|
|
539
420
|
const indexListResult = await client.query(indexListSql);
|
|
540
|
-
const {
|
|
421
|
+
const { outOfLineIndexes, inlineIndexes } = indexListResult.recordset.reduce((acc, row) => {
|
|
541
422
|
const { constraint_type, columns } = row;
|
|
542
423
|
|
|
543
424
|
if (columns === 'null' || columns.trim() === '') return acc;
|
|
544
425
|
if (constraint_type === 'PRIMARY KEY' || constraint_type === 'UNIQUE') {
|
|
545
|
-
acc.
|
|
426
|
+
acc.inlineIndexes.push(row);
|
|
546
427
|
} else {
|
|
547
|
-
acc.
|
|
428
|
+
acc.outOfLineIndexes.push(row);
|
|
548
429
|
}
|
|
549
430
|
return acc;
|
|
550
|
-
}, {
|
|
431
|
+
}, { inlineIndexes: [], outOfLineIndexes: [] });
|
|
551
432
|
|
|
552
|
-
const indexes =
|
|
433
|
+
const indexes = outOfLineIndexes.reduce((acc: IndexesDictionary, indexRow: Record<string, any>) => {
|
|
553
434
|
const {
|
|
554
435
|
table_schema,
|
|
555
436
|
table_name,
|
|
@@ -557,6 +438,8 @@ const generateIndexes = async (client: sql.ConnectionPool, schemas: string[]) =>
|
|
|
557
438
|
index_type,
|
|
558
439
|
columns,
|
|
559
440
|
expressions,
|
|
441
|
+
is_unique,
|
|
442
|
+
is_primary,
|
|
560
443
|
} = indexRow;
|
|
561
444
|
const indexColumns = columns.split(',').map((column: string) => {
|
|
562
445
|
return {
|
|
@@ -565,12 +448,14 @@ const generateIndexes = async (client: sql.ConnectionPool, schemas: string[]) =>
|
|
|
565
448
|
};
|
|
566
449
|
});
|
|
567
450
|
|
|
568
|
-
const indexExpressions = expressions
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
451
|
+
const indexExpressions = expressions
|
|
452
|
+
? expressions.split(',').map((expression: string) => {
|
|
453
|
+
return {
|
|
454
|
+
type: 'expression',
|
|
455
|
+
value: expression,
|
|
456
|
+
};
|
|
457
|
+
})
|
|
458
|
+
: [];
|
|
574
459
|
|
|
575
460
|
const index = {
|
|
576
461
|
name: index_name,
|
|
@@ -579,6 +464,8 @@ const generateIndexes = async (client: sql.ConnectionPool, schemas: string[]) =>
|
|
|
579
464
|
...indexColumns,
|
|
580
465
|
...indexExpressions,
|
|
581
466
|
],
|
|
467
|
+
pk: !!is_primary,
|
|
468
|
+
unique: !is_primary && !!is_unique,
|
|
582
469
|
};
|
|
583
470
|
|
|
584
471
|
const key = `${table_schema}.${table_name}`;
|
|
@@ -591,7 +478,9 @@ const generateIndexes = async (client: sql.ConnectionPool, schemas: string[]) =>
|
|
|
591
478
|
return acc;
|
|
592
479
|
}, {});
|
|
593
480
|
|
|
594
|
-
const tableConstraints
|
|
481
|
+
const tableConstraints: TableConstraintsDictionary = {};
|
|
482
|
+
|
|
483
|
+
inlineIndexes.forEach((row: Record<string, any>) => {
|
|
595
484
|
const {
|
|
596
485
|
table_schema,
|
|
597
486
|
table_name,
|
|
@@ -599,24 +488,64 @@ const generateIndexes = async (client: sql.ConnectionPool, schemas: string[]) =>
|
|
|
599
488
|
constraint_type,
|
|
600
489
|
} = row;
|
|
601
490
|
const key = `${table_schema}.${table_name}`;
|
|
602
|
-
if (!
|
|
491
|
+
if (!tableConstraints[key]) tableConstraints[key] = {};
|
|
603
492
|
|
|
604
493
|
const columnNames = columns.split(',').map((column: string) => column.trim());
|
|
605
494
|
columnNames.forEach((columnName: string) => {
|
|
606
|
-
if (!
|
|
495
|
+
if (!tableConstraints[key][columnName]) tableConstraints[key][columnName] = { checks: [] };
|
|
607
496
|
if (constraint_type === 'PRIMARY KEY') {
|
|
608
|
-
|
|
497
|
+
tableConstraints[key][columnName].pk = true;
|
|
609
498
|
}
|
|
610
|
-
if (constraint_type === 'UNIQUE' && !
|
|
611
|
-
|
|
499
|
+
if (constraint_type === 'UNIQUE' && !tableConstraints[key][columnName].pk) {
|
|
500
|
+
tableConstraints[key][columnName].unique = true;
|
|
612
501
|
}
|
|
613
502
|
});
|
|
614
|
-
|
|
615
|
-
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
const checkListSql = `
|
|
506
|
+
SELECT
|
|
507
|
+
OBJECT_SCHEMA_NAME(cc.parent_object_id) AS table_schema,
|
|
508
|
+
OBJECT_NAME(cc.parent_object_id) AS table_name,
|
|
509
|
+
c.name AS column_name,
|
|
510
|
+
cc.name AS check_name,
|
|
511
|
+
cc.definition AS check_expression
|
|
512
|
+
FROM
|
|
513
|
+
sys.check_constraints cc
|
|
514
|
+
LEFT JOIN
|
|
515
|
+
sys.columns c ON cc.parent_object_id = c.object_id AND cc.parent_column_id = c.column_id
|
|
516
|
+
WHERE 1 = 1
|
|
517
|
+
${buildSchemaQuery('OBJECT_SCHEMA_NAME(cc.parent_object_id)', schemas)}
|
|
518
|
+
`;
|
|
519
|
+
|
|
520
|
+
const checkListResult = await client.query(checkListSql);
|
|
521
|
+
const checks: CheckConstraintDictionary = {};
|
|
522
|
+
checkListResult.recordset.forEach((row) => {
|
|
523
|
+
const {
|
|
524
|
+
table_schema,
|
|
525
|
+
table_name,
|
|
526
|
+
column_name,
|
|
527
|
+
check_name,
|
|
528
|
+
check_expression,
|
|
529
|
+
} = row;
|
|
530
|
+
const key = `${table_schema}.${table_name}`;
|
|
531
|
+
if (column_name) {
|
|
532
|
+
if (!tableConstraints[key]) tableConstraints[key] = {};
|
|
533
|
+
if (!tableConstraints[key][column_name]) tableConstraints[key][column_name] = { checks: [] };
|
|
534
|
+
// The check definition has the form: `(expr)`
|
|
535
|
+
// The expression starts at 1 and ends at -1
|
|
536
|
+
tableConstraints[key][column_name].checks.push({ name: check_name, expression: check_expression.slice(1, -1) });
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
if (!checks[key]) checks[key] = [];
|
|
540
|
+
// The check definition has the form: `(expr)`
|
|
541
|
+
// The expression starts at 1 and ends at -1
|
|
542
|
+
checks[key].push({ name: check_name, expression: check_expression.slice(1, -1) });
|
|
543
|
+
});
|
|
616
544
|
|
|
617
545
|
return {
|
|
618
546
|
indexes,
|
|
619
547
|
tableConstraints,
|
|
548
|
+
checks,
|
|
620
549
|
};
|
|
621
550
|
};
|
|
622
551
|
|
|
@@ -624,28 +553,29 @@ const fetchSchemaJson = async (connection: string): Promise<DatabaseSchema> => {
|
|
|
624
553
|
const { connectionString, schemas } = parseConnectionString(connection, 'odbc');
|
|
625
554
|
const client = await getValidatedClient(connectionString);
|
|
626
555
|
|
|
627
|
-
const
|
|
628
|
-
const
|
|
556
|
+
const tablesRes = generateTablesFields(client, schemas);
|
|
557
|
+
const indexesAndConstraintsRes = generateIndexesAndConstraints(client, schemas);
|
|
629
558
|
const refsRes = generateRefs(client, schemas);
|
|
630
559
|
|
|
631
560
|
const res = await Promise.all([
|
|
632
|
-
|
|
633
|
-
|
|
561
|
+
tablesRes,
|
|
562
|
+
indexesAndConstraintsRes,
|
|
634
563
|
refsRes,
|
|
635
564
|
]);
|
|
636
|
-
client.close();
|
|
637
565
|
|
|
638
|
-
const { tables, fields
|
|
639
|
-
const { indexes, tableConstraints } = res[1];
|
|
566
|
+
const { tables, fields } = res[0];
|
|
567
|
+
const { indexes, tableConstraints, checks } = res[1];
|
|
640
568
|
const refs = res[2];
|
|
641
569
|
|
|
642
570
|
return {
|
|
643
571
|
tables,
|
|
644
572
|
fields,
|
|
645
|
-
enums
|
|
573
|
+
// MSSQL doesn't support the notion of enums
|
|
574
|
+
enums: [],
|
|
646
575
|
refs,
|
|
647
576
|
indexes,
|
|
648
577
|
tableConstraints,
|
|
578
|
+
checks,
|
|
649
579
|
};
|
|
650
580
|
};
|
|
651
581
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createConnection, Connection } from 'mysql2/promise';
|
|
2
2
|
import { flatten } from 'lodash';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
CheckConstraintDictionary, DefaultInfo, DatabaseSchema, DefaultType, Field, IndexColumn, FieldsDictionary, Table, Enum, IndexesDictionary, TableConstraintsDictionary, Index, Ref,
|
|
5
|
+
} from './types';
|
|
4
6
|
|
|
5
7
|
const NUMBER_REGEX = '^-?[0-9]+(.[0-9]+)?$';
|
|
6
8
|
|
|
@@ -221,7 +223,7 @@ async function generateEnums (client: Connection, schemaName: string): Promise<E
|
|
|
221
223
|
}
|
|
222
224
|
|
|
223
225
|
/**
|
|
224
|
-
* Mysql
|
|
226
|
+
* Mysql automatically creates index for primary keys, foreign keys, unique constraint. -> Ignore
|
|
225
227
|
*/
|
|
226
228
|
async function generateIndexes (client: Connection, schemaName: string): Promise<IndexesDictionary> {
|
|
227
229
|
const query = `
|
|
@@ -321,9 +323,9 @@ async function generatePrimaryAndUniqueConstraint (client: Connection, schemaNam
|
|
|
321
323
|
const rows: any = queryResponse[0];
|
|
322
324
|
|
|
323
325
|
const inlineConstraintList = rows.filter((constraint: Record<string, any>) => constraint.columnCount === 1);
|
|
324
|
-
const
|
|
326
|
+
const outOfLineConstraintList = rows.filter((constraint: Record<string, any>) => constraint.columnCount > 1);
|
|
325
327
|
|
|
326
|
-
const compositeTableConstraintMap =
|
|
328
|
+
const compositeTableConstraintMap = outOfLineConstraintList.reduce((acc: any, row: Record<string, any>) => {
|
|
327
329
|
const {
|
|
328
330
|
tableName, constraintName, columnNames, constraintType,
|
|
329
331
|
} = row;
|
|
@@ -340,7 +342,7 @@ async function generatePrimaryAndUniqueConstraint (client: Connection, schemaNam
|
|
|
340
342
|
}
|
|
341
343
|
|
|
342
344
|
if (constraintType === 'PRIMARY KEY') {
|
|
343
|
-
acc[key][constraintName].
|
|
345
|
+
acc[key][constraintName].pk = true;
|
|
344
346
|
}
|
|
345
347
|
|
|
346
348
|
if (constraintType === 'UNIQUE') {
|
|
@@ -355,12 +357,12 @@ async function generatePrimaryAndUniqueConstraint (client: Connection, schemaNam
|
|
|
355
357
|
return acc;
|
|
356
358
|
}, {});
|
|
357
359
|
|
|
358
|
-
const compositeConstraintMap:
|
|
360
|
+
const compositeConstraintMap: IndexesDictionary = {};
|
|
359
361
|
Object.keys(compositeTableConstraintMap).forEach((tableName) => {
|
|
360
362
|
compositeConstraintMap[tableName] = flatten(Object.values(compositeTableConstraintMap[tableName]));
|
|
361
363
|
});
|
|
362
364
|
|
|
363
|
-
const constraintMap = inlineConstraintList.reduce((acc:
|
|
365
|
+
const constraintMap = inlineConstraintList.reduce((acc: TableConstraintsDictionary, row: Record<string, any>) => {
|
|
364
366
|
const { tableName, columnNames, constraintType } = row;
|
|
365
367
|
|
|
366
368
|
const key = tableName;
|
|
@@ -372,7 +374,7 @@ async function generatePrimaryAndUniqueConstraint (client: Connection, schemaNam
|
|
|
372
374
|
|
|
373
375
|
columnList.forEach((columnName: string) => {
|
|
374
376
|
if (!acc[key][columnName]) {
|
|
375
|
-
acc[key][columnName] = {};
|
|
377
|
+
acc[key][columnName] = { checks: [] };
|
|
376
378
|
}
|
|
377
379
|
|
|
378
380
|
if (constraintType === 'PRIMARY KEY') {
|
|
@@ -386,7 +388,58 @@ async function generatePrimaryAndUniqueConstraint (client: Connection, schemaNam
|
|
|
386
388
|
return acc;
|
|
387
389
|
}, {});
|
|
388
390
|
|
|
389
|
-
return { compositeConstraintMap, constraintMap };
|
|
391
|
+
return { compositeConstraintMap, primaryAndUniqueConstraintMap: constraintMap };
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function extractColumnsFromCheckConstraint (checkClause: string): string[] {
|
|
395
|
+
return [...checkClause.matchAll(/`(?:``|[^`])*`/g)].map((col) => col.toString().slice(1, -1));
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
async function generateCheckConstraints (client: Connection, schemaName: string): Promise<{ checks: CheckConstraintDictionary; tableConstraints: TableConstraintsDictionary }> {
|
|
399
|
+
const query = `
|
|
400
|
+
select
|
|
401
|
+
cc.constraint_name as constraintName,
|
|
402
|
+
tc.table_name as tableName,
|
|
403
|
+
cc.check_clause as checkClause
|
|
404
|
+
from information_schema.check_constraints cc
|
|
405
|
+
join information_schema.table_constraints tc
|
|
406
|
+
on cc.constraint_name = tc.constraint_name
|
|
407
|
+
where cc.constraint_schema = ?
|
|
408
|
+
`;
|
|
409
|
+
|
|
410
|
+
const queryResponse = await client.query(query, [schemaName]);
|
|
411
|
+
const rows: any = queryResponse[0];
|
|
412
|
+
const checks: CheckConstraintDictionary = {};
|
|
413
|
+
const tableConstraints: TableConstraintsDictionary = {};
|
|
414
|
+
rows.forEach((row: any) => {
|
|
415
|
+
const {
|
|
416
|
+
constraintName,
|
|
417
|
+
tableName,
|
|
418
|
+
checkClause,
|
|
419
|
+
} = row;
|
|
420
|
+
const key = tableName;
|
|
421
|
+
const cols = extractColumnsFromCheckConstraint(checkClause);
|
|
422
|
+
if (cols.length === 1) {
|
|
423
|
+
const col = cols[0];
|
|
424
|
+
if (!tableConstraints[key]) tableConstraints[key] = {};
|
|
425
|
+
if (!tableConstraints[key][col]) tableConstraints[key][col] = { checks: [] };
|
|
426
|
+
tableConstraints[key][col].checks.push({
|
|
427
|
+
name: constraintName,
|
|
428
|
+
// The check definition has the form: `(expr)`
|
|
429
|
+
// The expression starts at 1 and ends at -1
|
|
430
|
+
expression: checkClause.slice(1, -1),
|
|
431
|
+
});
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
if (!checks[key]) checks[key] = [];
|
|
435
|
+
checks[key].push({
|
|
436
|
+
name: constraintName,
|
|
437
|
+
// The check definition has the form: `(expr)`
|
|
438
|
+
// The expression starts at 1 and ends at -1
|
|
439
|
+
expression: checkClause.slice(1, -1),
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
return { checks, tableConstraints };
|
|
390
443
|
}
|
|
391
444
|
|
|
392
445
|
async function generateForeignKeys (client: Connection, schemaName: string): Promise<Ref[]> {
|
|
@@ -453,7 +506,7 @@ async function generateForeignKeys (client: Connection, schemaName: string): Pro
|
|
|
453
506
|
return foreignKeyList;
|
|
454
507
|
}
|
|
455
508
|
|
|
456
|
-
function combineIndexAndCompositeConstraint (userDefinedIndexMap:
|
|
509
|
+
function combineIndexAndCompositeConstraint (userDefinedIndexMap: IndexesDictionary, compositeConstraintMap: IndexesDictionary): IndexesDictionary {
|
|
457
510
|
const indexMap = Object.assign(userDefinedIndexMap, {});
|
|
458
511
|
|
|
459
512
|
Object.keys(compositeConstraintMap).forEach((tableName) => {
|
|
@@ -484,6 +537,7 @@ async function fetchSchemaJson (connection: string): Promise<DatabaseSchema> {
|
|
|
484
537
|
generateIndexes(client, schemaName),
|
|
485
538
|
generatePrimaryAndUniqueConstraint(client, schemaName),
|
|
486
539
|
generateForeignKeys(client, schemaName),
|
|
540
|
+
generateCheckConstraints(client, schemaName),
|
|
487
541
|
]);
|
|
488
542
|
client.end();
|
|
489
543
|
|
|
@@ -491,20 +545,31 @@ async function fetchSchemaJson (connection: string): Promise<DatabaseSchema> {
|
|
|
491
545
|
{ tableList, fieldMap },
|
|
492
546
|
enumList,
|
|
493
547
|
rawIndexMap,
|
|
494
|
-
{
|
|
548
|
+
{ primaryAndUniqueConstraintMap, compositeConstraintMap },
|
|
495
549
|
foreignKeyList,
|
|
550
|
+
{ checks: tableCheckConstraintMap, tableConstraints: columnCheckConstraintMap },
|
|
496
551
|
] = result;
|
|
497
552
|
|
|
498
553
|
// combine normal index and composite key
|
|
499
554
|
const indexMap = combineIndexAndCompositeConstraint(rawIndexMap, compositeConstraintMap);
|
|
500
555
|
|
|
556
|
+
const tableConstraints: TableConstraintsDictionary = { ...primaryAndUniqueConstraintMap };
|
|
557
|
+
Object.keys(columnCheckConstraintMap).forEach((tableName) => {
|
|
558
|
+
if (!tableConstraints[tableName]) tableConstraints[tableName] = {};
|
|
559
|
+
Object.keys(columnCheckConstraintMap[tableName]).forEach((colName) => {
|
|
560
|
+
if (!tableConstraints[tableName][colName]) tableConstraints[tableName][colName] = { checks: [] };
|
|
561
|
+
tableConstraints[tableName][colName].checks.push(...columnCheckConstraintMap[tableName][colName].checks);
|
|
562
|
+
});
|
|
563
|
+
});
|
|
564
|
+
|
|
501
565
|
return {
|
|
502
566
|
tables: tableList,
|
|
503
567
|
fields: fieldMap,
|
|
504
568
|
refs: foreignKeyList,
|
|
505
569
|
enums: enumList,
|
|
506
570
|
indexes: indexMap,
|
|
507
|
-
tableConstraints
|
|
571
|
+
tableConstraints,
|
|
572
|
+
checks: tableCheckConstraintMap,
|
|
508
573
|
};
|
|
509
574
|
}
|
|
510
575
|
|