@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.
@@ -4,7 +4,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.fetchSchemaJson = void 0;
7
- /* eslint-disable camelcase */
7
+ /* TDD 72
8
+ * - In MSSQL, there's no notion of ENUM.
9
+ * - 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`.
10
+ * - 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.
11
+ * */
8
12
  const mssql_1 = __importDefault(require("mssql"));
9
13
  const parseSchema_1 = require("../utils/parseSchema");
10
14
  // https://learn.microsoft.com/en-us/sql/t-sql/data-types/date-and-time-types?view=sql-server-ver15
@@ -88,63 +92,6 @@ const getDbdefault = (data_type, column_default, default_type) => {
88
92
  value: default_type === 'string' ? value.slice(1, -1) : value, // Remove the quotes for string values
89
93
  };
90
94
  };
91
- const getEnumValues = (definition, constraint_name) => {
92
- // Use the example below to understand the regex:
93
- // ([quantity]>(0))
94
- // ([unit_price]>(0))
95
- // ([status]='cancelled' OR [status]='delivered' OR [status]='shipped' OR [status]='processing' OR [status]='pending')
96
- // ([total_amount]>(0))
97
- // ([price]>(0))
98
- // ([stock_quantity]>=(0))
99
- // ([age_start]<=[age_end])
100
- // ([age_start]<=[age_end])
101
- // ([gender]='Other' OR [gender]='Female' OR [gender]='Male')
102
- // ([date_of_birth]<=dateadd(year,(-13),getdate()))
103
- // ([email] like '%_@_%._%')
104
- if (!definition)
105
- return [];
106
- const values = definition.match(/\[([^\]]+)\]='([^']+)'/g); // Extracting the enum values when the definition contains ]='
107
- if (!values)
108
- return [];
109
- const colMap = {};
110
- values.forEach((value) => {
111
- const [columnName, enumValue] = value.split("]='");
112
- const cleanColumnName = columnName.slice(1); // Remove the leading '['
113
- const cleanEnumValue = enumValue.slice(0, -1); // Remove the trailing "'"
114
- if (!colMap[cleanColumnName]) {
115
- colMap[cleanColumnName] = [cleanEnumValue];
116
- }
117
- else {
118
- colMap[cleanColumnName].push(cleanEnumValue);
119
- }
120
- });
121
- const arraysEqual = (a, b) => {
122
- if (a.length !== b.length)
123
- return false;
124
- const sortedA = [...a].sort();
125
- const sortedB = [...b].sort();
126
- return sortedA.every((value, index) => value === sortedB[index]);
127
- };
128
- // Please check the comments of the EnumValuesDict type in types.ts to understand the structure
129
- const result = [];
130
- const processedKeys = new Set();
131
- Object.keys(colMap).forEach((key) => {
132
- if (processedKeys.has(key))
133
- return;
134
- const mergedColumns = [key];
135
- const colValues = colMap[key];
136
- Object.keys(colMap).forEach((innerKey) => {
137
- if (key !== innerKey && arraysEqual(colValues, colMap[innerKey])) {
138
- mergedColumns.push(innerKey);
139
- processedKeys.add(innerKey);
140
- }
141
- });
142
- const enumValues = colValues.map((value) => ({ name: value }));
143
- result.push({ columns: mergedColumns, enumValues, constraint_name: `${constraint_name}_${mergedColumns.join('_')}` });
144
- processedKeys.add(key);
145
- });
146
- return result;
147
- };
148
95
  const generateField = (row) => {
149
96
  const { column_name, data_type, character_maximum_length, numeric_precision, numeric_scale, identity_increment, is_nullable, column_default, default_type, column_comment, } = row;
150
97
  const dbdefault = column_default && default_type !== 'increment' ? getDbdefault(data_type, column_default, default_type) : null;
@@ -161,10 +108,8 @@ const generateField = (row) => {
161
108
  note: column_comment ? { value: column_comment } : { value: '' },
162
109
  };
163
110
  };
164
- const generateTablesFieldsAndEnums = async (client, schemas) => {
165
- const checkConstraintDefinitionDedup = {};
111
+ const generateTablesFields = async (client, schemas) => {
166
112
  const fields = {};
167
- const enums = [];
168
113
  const tablesAndFieldsSql = `
169
114
  WITH tables_and_fields AS (
170
115
  SELECT
@@ -203,35 +148,6 @@ const generateTablesFieldsAndEnums = async (client, schemas) => {
203
148
  AND ep.name like '%description%'
204
149
  WHERE
205
150
  t.type = 'U' -- User-defined tables
206
- ),
207
- constraints_with_nulls AS (
208
- SELECT
209
- tf.*,
210
- cc.name AS check_constraint_name,
211
- CASE
212
- WHEN cc.definition LIKE '%[' + tf.column_name + ']%=''%' THEN cc.definition
213
- ELSE NULL
214
- END AS check_constraint_definition
215
- FROM
216
- tables_and_fields AS tf
217
- LEFT JOIN sys.check_constraints cc
218
- ON cc.parent_object_id = OBJECT_ID(tf.table_schema + '.' + tf.table_name)
219
- AND cc.definition LIKE '%' + tf.column_name + '%' -- Ensure the constraint references the column
220
- ),
221
- constraints_with_row_number AS (
222
- SELECT
223
- *,
224
- ROW_NUMBER() OVER (
225
- PARTITION BY table_schema, table_name, column_name
226
- ORDER BY
227
- CASE
228
- WHEN check_constraint_definition IS NOT NULL THEN 1
229
- ELSE 2
230
- END,
231
- check_constraint_name
232
- ) AS rn
233
- FROM
234
- constraints_with_nulls
235
151
  )
236
152
  SELECT
237
153
  table_schema,
@@ -246,17 +162,15 @@ const generateTablesFieldsAndEnums = async (client, schemas) => {
246
162
  column_default,
247
163
  table_comment,
248
164
  column_comment,
249
- check_constraint_name,
250
- check_constraint_definition,
251
165
  CASE
252
166
  WHEN column_default LIKE '((%))' THEN 'number'
253
167
  WHEN column_default LIKE '(''%'')' THEN 'string'
254
168
  ELSE 'expression'
255
169
  END AS default_type
256
170
  FROM
257
- constraints_with_row_number
171
+ tables_and_fields
258
172
  WHERE
259
- rn = 1
173
+ 1 = 1
260
174
  ${(0, parseSchema_1.buildSchemaQuery)('table_schema', schemas)}
261
175
  ORDER BY
262
176
  table_schema,
@@ -266,7 +180,7 @@ const generateTablesFieldsAndEnums = async (client, schemas) => {
266
180
  `;
267
181
  const tablesAndFieldsResult = await client.query(tablesAndFieldsSql);
268
182
  const tables = tablesAndFieldsResult.recordset.reduce((acc, row) => {
269
- const { table_schema, table_name, table_comment, check_constraint_name, check_constraint_definition, } = row;
183
+ const { table_schema, table_name, table_comment, } = row;
270
184
  const key = `${table_schema}.${table_name}`;
271
185
  if (!acc[key]) {
272
186
  acc[key] = {
@@ -278,34 +192,12 @@ const generateTablesFieldsAndEnums = async (client, schemas) => {
278
192
  if (!fields[key])
279
193
  fields[key] = [];
280
194
  const field = generateField(row);
281
- if (check_constraint_definition && !checkConstraintDefinitionDedup[check_constraint_name]) {
282
- const enumValuesByColumns = getEnumValues(check_constraint_definition, check_constraint_name);
283
- enumValuesByColumns.forEach((item) => {
284
- enums.push({
285
- name: item.constraint_name,
286
- schemaName: table_schema,
287
- values: item.enumValues,
288
- });
289
- });
290
- checkConstraintDefinitionDedup[check_constraint_name] = enumValuesByColumns;
291
- }
292
- if (checkConstraintDefinitionDedup[check_constraint_name]) {
293
- const enumValuesByColumns = checkConstraintDefinitionDedup[check_constraint_name];
294
- const columnEnumValues = enumValuesByColumns.find((item) => item.columns.includes(field.name));
295
- if (columnEnumValues) {
296
- field.type = {
297
- type_name: columnEnumValues.constraint_name,
298
- schemaName: table_schema,
299
- };
300
- }
301
- }
302
195
  fields[key].push(field);
303
196
  return acc;
304
197
  }, {});
305
198
  return {
306
199
  tables: Object.values(tables),
307
200
  fields,
308
- enums,
309
201
  };
310
202
  };
311
203
  const generateRefs = async (client, schemas) => {
@@ -372,14 +264,14 @@ const generateRefs = async (client, schemas) => {
372
264
  onDelete: on_delete === 'NO_ACTION' ? null : on_delete,
373
265
  onUpdate: on_update === 'NO_ACTION' ? null : on_update,
374
266
  };
375
- const isDuplicate = refs.some(ref => endpointsEqual(ref.endpoints, newRef.endpoints));
267
+ const isDuplicate = refs.some((ref) => endpointsEqual(ref.endpoints, newRef.endpoints));
376
268
  if (!isDuplicate) {
377
269
  refs.push(newRef);
378
270
  }
379
271
  });
380
272
  return refs;
381
273
  };
382
- const generateIndexes = async (client, schemas) => {
274
+ const generateIndexesAndConstraints = async (client, schemas) => {
383
275
  const indexListSql = `
384
276
  WITH user_tables AS (
385
277
  SELECT
@@ -455,32 +347,34 @@ const generateIndexes = async (client, schemas) => {
455
347
  ii.index_name;
456
348
  `;
457
349
  const indexListResult = await client.query(indexListSql);
458
- const { outOfLineConstraints, inlineConstraints } = indexListResult.recordset.reduce((acc, row) => {
350
+ const { outOfLineIndexes, inlineIndexes } = indexListResult.recordset.reduce((acc, row) => {
459
351
  const { constraint_type, columns } = row;
460
352
  if (columns === 'null' || columns.trim() === '')
461
353
  return acc;
462
354
  if (constraint_type === 'PRIMARY KEY' || constraint_type === 'UNIQUE') {
463
- acc.inlineConstraints.push(row);
355
+ acc.inlineIndexes.push(row);
464
356
  }
465
357
  else {
466
- acc.outOfLineConstraints.push(row);
358
+ acc.outOfLineIndexes.push(row);
467
359
  }
468
360
  return acc;
469
- }, { outOfLineConstraints: [], inlineConstraints: [] });
470
- const indexes = outOfLineConstraints.reduce((acc, indexRow) => {
471
- const { table_schema, table_name, index_name, index_type, columns, expressions, } = indexRow;
361
+ }, { inlineIndexes: [], outOfLineIndexes: [] });
362
+ const indexes = outOfLineIndexes.reduce((acc, indexRow) => {
363
+ const { table_schema, table_name, index_name, index_type, columns, expressions, is_unique, is_primary, } = indexRow;
472
364
  const indexColumns = columns.split(',').map((column) => {
473
365
  return {
474
366
  type: 'column',
475
367
  value: column.trim(),
476
368
  };
477
369
  });
478
- const indexExpressions = expressions ? expressions.split(',').map((expression) => {
479
- return {
480
- type: 'expression',
481
- value: expression,
482
- };
483
- }) : [];
370
+ const indexExpressions = expressions
371
+ ? expressions.split(',').map((expression) => {
372
+ return {
373
+ type: 'expression',
374
+ value: expression,
375
+ };
376
+ })
377
+ : [];
484
378
  const index = {
485
379
  name: index_name,
486
380
  type: index_type,
@@ -488,6 +382,8 @@ const generateIndexes = async (client, schemas) => {
488
382
  ...indexColumns,
489
383
  ...indexExpressions,
490
384
  ],
385
+ pk: !!is_primary,
386
+ unique: !is_primary && !!is_unique,
491
387
  };
492
388
  const key = `${table_schema}.${table_name}`;
493
389
  if (acc[key]) {
@@ -498,51 +394,88 @@ const generateIndexes = async (client, schemas) => {
498
394
  }
499
395
  return acc;
500
396
  }, {});
501
- const tableConstraints = inlineConstraints.reduce((acc, row) => {
397
+ const tableConstraints = {};
398
+ inlineIndexes.forEach((row) => {
502
399
  const { table_schema, table_name, columns, constraint_type, } = row;
503
400
  const key = `${table_schema}.${table_name}`;
504
- if (!acc[key])
505
- acc[key] = {};
401
+ if (!tableConstraints[key])
402
+ tableConstraints[key] = {};
506
403
  const columnNames = columns.split(',').map((column) => column.trim());
507
404
  columnNames.forEach((columnName) => {
508
- if (!acc[key][columnName])
509
- acc[key][columnName] = {};
405
+ if (!tableConstraints[key][columnName])
406
+ tableConstraints[key][columnName] = { checks: [] };
510
407
  if (constraint_type === 'PRIMARY KEY') {
511
- acc[key][columnName].pk = true;
408
+ tableConstraints[key][columnName].pk = true;
512
409
  }
513
- if (constraint_type === 'UNIQUE' && !acc[key][columnName].pk) {
514
- acc[key][columnName].unique = true;
410
+ if (constraint_type === 'UNIQUE' && !tableConstraints[key][columnName].pk) {
411
+ tableConstraints[key][columnName].unique = true;
515
412
  }
516
413
  });
517
- return acc;
518
- }, {});
414
+ });
415
+ const checkListSql = `
416
+ SELECT
417
+ OBJECT_SCHEMA_NAME(cc.parent_object_id) AS table_schema,
418
+ OBJECT_NAME(cc.parent_object_id) AS table_name,
419
+ c.name AS column_name,
420
+ cc.name AS check_name,
421
+ cc.definition AS check_expression
422
+ FROM
423
+ sys.check_constraints cc
424
+ LEFT JOIN
425
+ sys.columns c ON cc.parent_object_id = c.object_id AND cc.parent_column_id = c.column_id
426
+ WHERE 1 = 1
427
+ ${(0, parseSchema_1.buildSchemaQuery)('OBJECT_SCHEMA_NAME(cc.parent_object_id)', schemas)}
428
+ `;
429
+ const checkListResult = await client.query(checkListSql);
430
+ const checks = {};
431
+ checkListResult.recordset.forEach((row) => {
432
+ const { table_schema, table_name, column_name, check_name, check_expression, } = row;
433
+ const key = `${table_schema}.${table_name}`;
434
+ if (column_name) {
435
+ if (!tableConstraints[key])
436
+ tableConstraints[key] = {};
437
+ if (!tableConstraints[key][column_name])
438
+ tableConstraints[key][column_name] = { checks: [] };
439
+ // The check definition has the form: `(expr)`
440
+ // The expression starts at 1 and ends at -1
441
+ tableConstraints[key][column_name].checks.push({ name: check_name, expression: check_expression.slice(1, -1) });
442
+ return;
443
+ }
444
+ if (!checks[key])
445
+ checks[key] = [];
446
+ // The check definition has the form: `(expr)`
447
+ // The expression starts at 1 and ends at -1
448
+ checks[key].push({ name: check_name, expression: check_expression.slice(1, -1) });
449
+ });
519
450
  return {
520
451
  indexes,
521
452
  tableConstraints,
453
+ checks,
522
454
  };
523
455
  };
524
456
  const fetchSchemaJson = async (connection) => {
525
457
  const { connectionString, schemas } = (0, parseSchema_1.parseConnectionString)(connection, 'odbc');
526
458
  const client = await getValidatedClient(connectionString);
527
- const tablesFieldsAndEnumsRes = generateTablesFieldsAndEnums(client, schemas);
528
- const indexesRes = generateIndexes(client, schemas);
459
+ const tablesRes = generateTablesFields(client, schemas);
460
+ const indexesAndConstraintsRes = generateIndexesAndConstraints(client, schemas);
529
461
  const refsRes = generateRefs(client, schemas);
530
462
  const res = await Promise.all([
531
- tablesFieldsAndEnumsRes,
532
- indexesRes,
463
+ tablesRes,
464
+ indexesAndConstraintsRes,
533
465
  refsRes,
534
466
  ]);
535
- client.close();
536
- const { tables, fields, enums } = res[0];
537
- const { indexes, tableConstraints } = res[1];
467
+ const { tables, fields } = res[0];
468
+ const { indexes, tableConstraints, checks } = res[1];
538
469
  const refs = res[2];
539
470
  return {
540
471
  tables,
541
472
  fields,
542
- enums,
473
+ // MSSQL doesn't support the notion of enums
474
+ enums: [],
543
475
  refs,
544
476
  indexes,
545
477
  tableConstraints,
478
+ checks,
546
479
  };
547
480
  };
548
481
  exports.fetchSchemaJson = fetchSchemaJson;
@@ -164,7 +164,7 @@ async function generateEnums(client, schemaName) {
164
164
  return enumList;
165
165
  }
166
166
  /**
167
- * Mysql is automatically create index for primary keys, foreign keys, unique constraint. -> Ignore
167
+ * Mysql automatically creates index for primary keys, foreign keys, unique constraint. -> Ignore
168
168
  */
169
169
  async function generateIndexes(client, schemaName) {
170
170
  const query = `
@@ -251,8 +251,8 @@ async function generatePrimaryAndUniqueConstraint(client, schemaName) {
251
251
  const queryResponse = await client.query(query, [schemaName]);
252
252
  const rows = queryResponse[0];
253
253
  const inlineConstraintList = rows.filter((constraint) => constraint.columnCount === 1);
254
- const outOfConstraintList = rows.filter((constraint) => constraint.columnCount > 1);
255
- const compositeTableConstraintMap = outOfConstraintList.reduce((acc, row) => {
254
+ const outOfLineConstraintList = rows.filter((constraint) => constraint.columnCount > 1);
255
+ const compositeTableConstraintMap = outOfLineConstraintList.reduce((acc, row) => {
256
256
  const { tableName, constraintName, columnNames, constraintType, } = row;
257
257
  const key = tableName;
258
258
  if (!acc[key]) {
@@ -264,7 +264,7 @@ async function generatePrimaryAndUniqueConstraint(client, schemaName) {
264
264
  };
265
265
  }
266
266
  if (constraintType === 'PRIMARY KEY') {
267
- acc[key][constraintName].primary = true;
267
+ acc[key][constraintName].pk = true;
268
268
  }
269
269
  if (constraintType === 'UNIQUE') {
270
270
  acc[key][constraintName].unique = true;
@@ -288,7 +288,7 @@ async function generatePrimaryAndUniqueConstraint(client, schemaName) {
288
288
  const columnList = columnNames.split(',');
289
289
  columnList.forEach((columnName) => {
290
290
  if (!acc[key][columnName]) {
291
- acc[key][columnName] = {};
291
+ acc[key][columnName] = { checks: [] };
292
292
  }
293
293
  if (constraintType === 'PRIMARY KEY') {
294
294
  acc[key][columnName].pk = true;
@@ -299,7 +299,54 @@ async function generatePrimaryAndUniqueConstraint(client, schemaName) {
299
299
  });
300
300
  return acc;
301
301
  }, {});
302
- return { compositeConstraintMap, constraintMap };
302
+ return { compositeConstraintMap, primaryAndUniqueConstraintMap: constraintMap };
303
+ }
304
+ function extractColumnsFromCheckConstraint(checkClause) {
305
+ return [...checkClause.matchAll(/`(?:``|[^`])*`/g)].map((col) => col.toString().slice(1, -1));
306
+ }
307
+ async function generateCheckConstraints(client, schemaName) {
308
+ const query = `
309
+ select
310
+ cc.constraint_name as constraintName,
311
+ tc.table_name as tableName,
312
+ cc.check_clause as checkClause
313
+ from information_schema.check_constraints cc
314
+ join information_schema.table_constraints tc
315
+ on cc.constraint_name = tc.constraint_name
316
+ where cc.constraint_schema = ?
317
+ `;
318
+ const queryResponse = await client.query(query, [schemaName]);
319
+ const rows = queryResponse[0];
320
+ const checks = {};
321
+ const tableConstraints = {};
322
+ rows.forEach((row) => {
323
+ const { constraintName, tableName, checkClause, } = row;
324
+ const key = tableName;
325
+ const cols = extractColumnsFromCheckConstraint(checkClause);
326
+ if (cols.length === 1) {
327
+ const col = cols[0];
328
+ if (!tableConstraints[key])
329
+ tableConstraints[key] = {};
330
+ if (!tableConstraints[key][col])
331
+ tableConstraints[key][col] = { checks: [] };
332
+ tableConstraints[key][col].checks.push({
333
+ name: constraintName,
334
+ // The check definition has the form: `(expr)`
335
+ // The expression starts at 1 and ends at -1
336
+ expression: checkClause.slice(1, -1),
337
+ });
338
+ return;
339
+ }
340
+ if (!checks[key])
341
+ checks[key] = [];
342
+ checks[key].push({
343
+ name: constraintName,
344
+ // The check definition has the form: `(expr)`
345
+ // The expression starts at 1 and ends at -1
346
+ expression: checkClause.slice(1, -1),
347
+ });
348
+ });
349
+ return { checks, tableConstraints };
303
350
  }
304
351
  async function generateForeignKeys(client, schemaName) {
305
352
  const query = `
@@ -375,17 +422,29 @@ async function fetchSchemaJson(connection) {
375
422
  generateIndexes(client, schemaName),
376
423
  generatePrimaryAndUniqueConstraint(client, schemaName),
377
424
  generateForeignKeys(client, schemaName),
425
+ generateCheckConstraints(client, schemaName),
378
426
  ]);
379
427
  client.end();
380
- const [{ tableList, fieldMap }, enumList, rawIndexMap, { constraintMap, compositeConstraintMap }, foreignKeyList,] = result;
428
+ const [{ tableList, fieldMap }, enumList, rawIndexMap, { primaryAndUniqueConstraintMap, compositeConstraintMap }, foreignKeyList, { checks: tableCheckConstraintMap, tableConstraints: columnCheckConstraintMap },] = result;
381
429
  // combine normal index and composite key
382
430
  const indexMap = combineIndexAndCompositeConstraint(rawIndexMap, compositeConstraintMap);
431
+ const tableConstraints = { ...primaryAndUniqueConstraintMap };
432
+ Object.keys(columnCheckConstraintMap).forEach((tableName) => {
433
+ if (!tableConstraints[tableName])
434
+ tableConstraints[tableName] = {};
435
+ Object.keys(columnCheckConstraintMap[tableName]).forEach((colName) => {
436
+ if (!tableConstraints[tableName][colName])
437
+ tableConstraints[tableName][colName] = { checks: [] };
438
+ tableConstraints[tableName][colName].checks.push(...columnCheckConstraintMap[tableName][colName].checks);
439
+ });
440
+ });
383
441
  return {
384
442
  tables: tableList,
385
443
  fields: fieldMap,
386
444
  refs: foreignKeyList,
387
445
  enums: enumList,
388
446
  indexes: indexMap,
389
- tableConstraints: constraintMap,
447
+ tableConstraints,
448
+ checks: tableCheckConstraintMap,
390
449
  };
391
450
  }
@@ -1,3 +1,3 @@
1
1
  import { DatabaseSchema } from './types';
2
2
  declare const fetchSchemaJson: (connection: string) => Promise<DatabaseSchema>;
3
- export { fetchSchemaJson };
3
+ export { fetchSchemaJson, };