@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.
@@ -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 };
@@ -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
- /* eslint-disable camelcase */
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 generateTablesFieldsAndEnums = async (client: sql.ConnectionPool, schemas: string[]): Promise<{
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
- constraints_with_row_number
211
+ tables_and_fields
304
212
  WHERE
305
- rn = 1
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
- && endpoint.schemaName === ep2[index].schemaName
415
- && endpoint.fieldNames.length === ep2[index].fieldNames.length
416
- && 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]),
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 generateIndexes = async (client: sql.ConnectionPool, schemas: string[]) => {
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 { outOfLineConstraints, inlineConstraints } = indexListResult.recordset.reduce((acc, row) => {
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.inlineConstraints.push(row);
426
+ acc.inlineIndexes.push(row);
546
427
  } else {
547
- acc.outOfLineConstraints.push(row);
428
+ acc.outOfLineIndexes.push(row);
548
429
  }
549
430
  return acc;
550
- }, { outOfLineConstraints: [], inlineConstraints: [] });
431
+ }, { inlineIndexes: [], outOfLineIndexes: [] });
551
432
 
552
- const indexes = outOfLineConstraints.reduce((acc: IndexesDictionary, indexRow: Record<string, any>) => {
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 ? expressions.split(',').map((expression: string) => {
569
- return {
570
- type: 'expression',
571
- value: expression,
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 = inlineConstraints.reduce((acc: TableConstraintsDictionary, row: Record<string, any>) => {
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 (!acc[key]) acc[key] = {};
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 (!acc[key][columnName]) acc[key][columnName] = {};
495
+ if (!tableConstraints[key][columnName]) tableConstraints[key][columnName] = { checks: [] };
607
496
  if (constraint_type === 'PRIMARY KEY') {
608
- acc[key][columnName].pk = true;
497
+ tableConstraints[key][columnName].pk = true;
609
498
  }
610
- if (constraint_type === 'UNIQUE' && !acc[key][columnName].pk) {
611
- acc[key][columnName].unique = true;
499
+ if (constraint_type === 'UNIQUE' && !tableConstraints[key][columnName].pk) {
500
+ tableConstraints[key][columnName].unique = true;
612
501
  }
613
502
  });
614
- return acc;
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 tablesFieldsAndEnumsRes = generateTablesFieldsAndEnums(client, schemas);
628
- const indexesRes = generateIndexes(client, schemas);
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
- tablesFieldsAndEnumsRes,
633
- indexesRes,
561
+ tablesRes,
562
+ indexesAndConstraintsRes,
634
563
  refsRes,
635
564
  ]);
636
- client.close();
637
565
 
638
- const { tables, fields, enums } = res[0];
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 { DefaultInfo, DatabaseSchema, DefaultType, Field, IndexColumn, FieldsDictionary, Table, Enum, IndexesDictionary, TableConstraintsDictionary, Index, TableConstraint, Ref } from './types';
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 is automatically create index for primary keys, foreign keys, unique constraint. -> Ignore
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 outOfConstraintList = rows.filter((constraint: Record<string, any>) => constraint.columnCount > 1);
326
+ const outOfLineConstraintList = rows.filter((constraint: Record<string, any>) => constraint.columnCount > 1);
325
327
 
326
- const compositeTableConstraintMap = outOfConstraintList.reduce((acc: any, row: Record<string, any>) => {
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].primary = true;
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: any = {};
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: any, row: Record<string, any>) => {
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: any, compositeConstraintMap: any) {
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
- { constraintMap, compositeConstraintMap },
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: constraintMap,
571
+ tableConstraints,
572
+ checks: tableCheckConstraintMap,
508
573
  };
509
574
  }
510
575