@dbml/connector 3.9.7-alpha.0 → 3.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/.babelrc +0 -0
  2. package/LICENSE +0 -0
  3. package/README.md +0 -0
  4. package/__tests__/connector.test.ts +20 -1
  5. package/__tests__/connectors/bigquery/out-files/schema.json +565 -0
  6. package/__tests__/connectors/mssql/connection.json +0 -0
  7. package/__tests__/connectors/mssql/expect-out-files/schema.json +151 -4
  8. package/__tests__/connectors/mssql/out-files/schema.json +1858 -0
  9. package/__tests__/connectors/mysql/connection.json +0 -0
  10. package/__tests__/connectors/mysql/expect-out-files/schema.json +0 -0
  11. package/__tests__/connectors/mysql/out-files/schema.json +1390 -0
  12. package/__tests__/connectors/postgres/connection.json +0 -0
  13. package/__tests__/connectors/postgres/expect-out-files/schema.json +0 -0
  14. package/__tests__/connectors/postgres/out-files/schema.json +1774 -0
  15. package/dist/connectors/bigqueryConnector.d.ts +0 -0
  16. package/dist/connectors/bigqueryConnector.js +0 -0
  17. package/dist/connectors/connector.d.ts +0 -0
  18. package/dist/connectors/connector.js +0 -0
  19. package/dist/connectors/mssqlConnector.d.ts +0 -0
  20. package/dist/connectors/mssqlConnector.js +75 -22
  21. package/dist/connectors/mysqlConnector.d.ts +0 -0
  22. package/dist/connectors/mysqlConnector.js +0 -0
  23. package/dist/connectors/postgresConnector.d.ts +0 -0
  24. package/dist/connectors/postgresConnector.js +0 -0
  25. package/dist/connectors/snowflakeConnector.d.ts +0 -0
  26. package/dist/connectors/snowflakeConnector.js +0 -0
  27. package/dist/connectors/types.d.ts +6 -1
  28. package/dist/connectors/types.js +0 -0
  29. package/dist/index.d.ts +0 -0
  30. package/dist/index.js +0 -0
  31. package/dist/utils/credential-loader.d.ts +0 -0
  32. package/dist/utils/credential-loader.js +0 -0
  33. package/dist/utils/helpers.d.ts +0 -0
  34. package/dist/utils/helpers.js +0 -0
  35. package/dist/utils/parseSchema.d.ts +0 -0
  36. package/dist/utils/parseSchema.js +0 -0
  37. package/jest.config.ts +0 -0
  38. package/jestHelpers.ts +0 -0
  39. package/package.json +2 -2
  40. package/src/connectors/bigqueryConnector.ts +0 -0
  41. package/src/connectors/connector.ts +0 -0
  42. package/src/connectors/mssqlConnector.ts +87 -22
  43. package/src/connectors/mysqlConnector.ts +0 -0
  44. package/src/connectors/postgresConnector.ts +0 -0
  45. package/src/connectors/snowflakeConnector.ts +0 -0
  46. package/src/connectors/types.ts +56 -0
  47. package/src/ddl_samples/bigquery.sql +0 -0
  48. package/src/ddl_samples/snowflake.sql +0 -0
  49. package/src/index.ts +0 -0
  50. package/src/utils/credential-loader.ts +0 -0
  51. package/src/utils/helpers.ts +0 -0
  52. package/src/utils/parseSchema.ts +0 -0
  53. package/tsconfig.json +0 -0
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -88,7 +88,7 @@ const getDbdefault = (data_type, column_default, default_type) => {
88
88
  value: default_type === 'string' ? value.slice(1, -1) : value, // Remove the quotes for string values
89
89
  };
90
90
  };
91
- const getEnumValues = (definition) => {
91
+ const getEnumValues = (definition, constraint_name) => {
92
92
  // Use the example below to understand the regex:
93
93
  // ([quantity]>(0))
94
94
  // ([unit_price]>(0))
@@ -102,15 +102,48 @@ const getEnumValues = (definition) => {
102
102
  // ([date_of_birth]<=dateadd(year,(-13),getdate()))
103
103
  // ([email] like '%_@_%._%')
104
104
  if (!definition)
105
- return null;
105
+ return [];
106
106
  const values = definition.match(/\[([^\]]+)\]='([^']+)'/g); // Extracting the enum values when the definition contains ]='
107
107
  if (!values)
108
- return null;
109
- const enumValues = values.map((value) => {
110
- const enumValue = value.split("]='")[1];
111
- return { name: enumValue.slice(0, -1) };
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
+ let mergedColumns = [key];
135
+ const values = colMap[key];
136
+ Object.keys(colMap).forEach((innerKey) => {
137
+ if (key !== innerKey && arraysEqual(values, colMap[innerKey])) {
138
+ mergedColumns.push(innerKey);
139
+ processedKeys.add(innerKey);
140
+ }
141
+ });
142
+ const enumValues = values.map((value) => ({ name: value }));
143
+ result.push({ columns: mergedColumns, enumValues, constraint_name: `${constraint_name}_${mergedColumns.join('_')}` });
144
+ processedKeys.add(key);
112
145
  });
113
- return enumValues;
146
+ return result;
114
147
  };
115
148
  const generateField = (row) => {
116
149
  const { column_name, data_type, character_maximum_length, numeric_precision, numeric_scale, identity_increment, is_nullable, column_default, default_type, column_comment, } = row;
@@ -129,6 +162,7 @@ const generateField = (row) => {
129
162
  };
130
163
  };
131
164
  const generateTablesFieldsAndEnums = async (client, schemas) => {
165
+ const checkConstraintDefinitionDedup = {};
132
166
  const fields = {};
133
167
  const enums = [];
134
168
  const tablesAndFieldsSql = `
@@ -239,22 +273,29 @@ const generateTablesFieldsAndEnums = async (client, schemas) => {
239
273
  note: table_comment ? { value: table_comment } : { value: '' },
240
274
  };
241
275
  }
242
- const enumValues = getEnumValues(check_constraint_definition);
243
- if (enumValues) {
244
- enums.push({
245
- name: check_constraint_name,
246
- schemaName: table_schema,
247
- values: enumValues,
248
- });
249
- }
250
276
  if (!fields[key])
251
277
  fields[key] = [];
252
278
  const field = generateField(row);
253
- if (enumValues) {
254
- field.type = {
255
- type_name: check_constraint_name,
256
- schemaName: table_schema,
257
- };
279
+ if (check_constraint_definition && !checkConstraintDefinitionDedup[check_constraint_name]) {
280
+ const enumValuesByColumns = getEnumValues(check_constraint_definition, check_constraint_name);
281
+ enumValuesByColumns.forEach((item) => {
282
+ enums.push({
283
+ name: item.constraint_name,
284
+ schemaName: table_schema,
285
+ values: item.enumValues,
286
+ });
287
+ });
288
+ checkConstraintDefinitionDedup[check_constraint_name] = enumValuesByColumns;
289
+ }
290
+ if (checkConstraintDefinitionDedup[check_constraint_name]) {
291
+ const enumValuesByColumns = checkConstraintDefinitionDedup[check_constraint_name];
292
+ const columnEnumValues = enumValuesByColumns.find((item) => item.columns.includes(field.name));
293
+ if (columnEnumValues) {
294
+ field.type = {
295
+ type_name: columnEnumValues.constraint_name,
296
+ schemaName: table_schema,
297
+ };
298
+ }
258
299
  }
259
300
  fields[key].push(field);
260
301
  return acc;
@@ -301,6 +342,14 @@ const generateRefs = async (client, schemas) => {
301
342
  t.name;
302
343
  `;
303
344
  const refsQueryResult = await client.query(refsListSql);
345
+ const endpointsEqual = (ep1, ep2) => {
346
+ if (ep1.length !== ep2.length)
347
+ return false;
348
+ return ep1.every((endpoint, index) => endpoint.tableName === ep2[index].tableName &&
349
+ endpoint.schemaName === ep2[index].schemaName &&
350
+ endpoint.fieldNames.length === ep2[index].fieldNames.length &&
351
+ endpoint.fieldNames.every((field, fieldIndex) => field === ep2[index].fieldNames[fieldIndex]));
352
+ };
304
353
  refsQueryResult.recordset.forEach((refRow) => {
305
354
  const { table_schema, fk_constraint_name, table_name, column_names, foreign_table_schema, foreign_table_name, foreign_column_names, on_delete, on_update, } = refRow;
306
355
  const ep1 = {
@@ -315,12 +364,16 @@ const generateRefs = async (client, schemas) => {
315
364
  fieldNames: foreign_column_names.split(','),
316
365
  relation: '1',
317
366
  };
318
- refs.push({
367
+ const newRef = {
319
368
  name: fk_constraint_name,
320
369
  endpoints: [ep1, ep2],
321
370
  onDelete: on_delete === 'NO_ACTION' ? null : on_delete,
322
371
  onUpdate: on_update === 'NO_ACTION' ? null : on_update,
323
- });
372
+ };
373
+ const isDuplicate = refs.some(ref => endpointsEqual(ref.endpoints, newRef.endpoints));
374
+ if (!isDuplicate) {
375
+ refs.push(newRef);
376
+ }
324
377
  });
325
378
  return refs;
326
379
  };
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -87,4 +87,9 @@ interface BigQueryCredentials {
87
87
  };
88
88
  datasets: string[];
89
89
  }
90
- export { NoteInfo, TypeInfo, DefaultType, DefaultInfo, Field, Table, FieldsDictionary, TableConstraint, TableConstraintsDictionary, RefEndpoint, Ref, EnumValue, Enum, IndexColumn, Index, IndexesDictionary, DatabaseSchema, BigQueryCredentials, };
90
+ interface EnumValuesDict {
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, };
File without changes
package/dist/index.d.ts CHANGED
File without changes
package/dist/index.js CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/jest.config.ts CHANGED
File without changes
package/jestHelpers.ts CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dbml/connector",
3
- "version": "3.9.7-alpha.0",
3
+ "version": "3.10.2",
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",
@@ -40,5 +40,5 @@
40
40
  "engines": {
41
41
  "node": ">=18"
42
42
  },
43
- "gitHead": "459732f65cd87e233862b7568971997436885dad"
43
+ "gitHead": "59a06de19f675bf9af31bdb9801d8e022ef87529"
44
44
  }
File without changes
File without changes
@@ -14,6 +14,7 @@ import {
14
14
  RefEndpoint,
15
15
  Table,
16
16
  TableConstraintsDictionary,
17
+ EnumValuesDict,
17
18
  } from './types';
18
19
 
19
20
  // https://learn.microsoft.com/en-us/sql/t-sql/data-types/date-and-time-types?view=sql-server-ver15
@@ -111,7 +112,7 @@ const getDbdefault = (data_type: string, column_default: string, default_type: D
111
112
  };
112
113
  };
113
114
 
114
- const getEnumValues = (definition: string): EnumValue[] | null => {
115
+ const getEnumValues = (definition: string, constraint_name: string): EnumValuesDict[] => {
115
116
  // Use the example below to understand the regex:
116
117
  // ([quantity]>(0))
117
118
  // ([unit_price]>(0))
@@ -124,15 +125,51 @@ const getEnumValues = (definition: string): EnumValue[] | null => {
124
125
  // ([gender]='Other' OR [gender]='Female' OR [gender]='Male')
125
126
  // ([date_of_birth]<=dateadd(year,(-13),getdate()))
126
127
  // ([email] like '%_@_%._%')
127
- if (!definition) return null;
128
+ if (!definition) return [];
128
129
  const values = definition.match(/\[([^\]]+)\]='([^']+)'/g); // Extracting the enum values when the definition contains ]='
129
- if (!values) return null;
130
+ if (!values) return [];
131
+
132
+ const colMap: { [key: string]: string[] } = {};
133
+ values.forEach((value) => {
134
+ const [columnName, enumValue] = value.split("]='");
135
+ const cleanColumnName = columnName.slice(1); // Remove the leading '['
136
+ const cleanEnumValue = enumValue.slice(0, -1); // Remove the trailing "'"
137
+ if (!colMap[cleanColumnName]) {
138
+ colMap[cleanColumnName] = [cleanEnumValue];
139
+ } else {
140
+ colMap[cleanColumnName].push(cleanEnumValue);
141
+ }
142
+ });
143
+
144
+ const arraysEqual = (a: string[], b: string[]): boolean => {
145
+ if (a.length !== b.length) return false;
146
+ const sortedA = [...a].sort();
147
+ const sortedB = [...b].sort();
148
+ return sortedA.every((value, index) => value === sortedB[index]);
149
+ };
150
+
151
+ // Please check the comments of the EnumValuesDict type in types.ts to understand the structure
152
+ const result: EnumValuesDict[] = [];
153
+ const processedKeys = new Set<string>();
154
+
155
+ Object.keys(colMap).forEach((key) => {
156
+ if (processedKeys.has(key)) return;
157
+
158
+ let mergedColumns = [key];
159
+ const values = colMap[key];
130
160
 
131
- const enumValues = values.map((value) => {
132
- const enumValue = value.split("]='")[1];
133
- return { name: enumValue.slice(0, -1) };
161
+ Object.keys(colMap).forEach((innerKey) => {
162
+ if (key !== innerKey && arraysEqual(values, colMap[innerKey])) {
163
+ mergedColumns.push(innerKey);
164
+ processedKeys.add(innerKey);
165
+ }
166
+ });
167
+ const enumValues = values.map((value) => ({ name: value }));
168
+ result.push({ columns: mergedColumns, enumValues, constraint_name: `${constraint_name}_${mergedColumns.join('_')}` });
169
+ processedKeys.add(key);
134
170
  });
135
- return enumValues;
171
+
172
+ return result;
136
173
  };
137
174
 
138
175
  const generateField = (row: Record<string, any>): Field => {
@@ -171,6 +208,8 @@ const generateTablesFieldsAndEnums = async (client: sql.ConnectionPool, schemas:
171
208
  fields: FieldsDictionary,
172
209
  enums: Enum[],
173
210
  }> => {
211
+ const checkConstraintDefinitionDedup: { [key: string]: EnumValuesDict[] } = {};
212
+
174
213
  const fields: FieldsDictionary = {};
175
214
  const enums: Enum[] = [];
176
215
  const tablesAndFieldsSql = `
@@ -290,23 +329,32 @@ const generateTablesFieldsAndEnums = async (client: sql.ConnectionPool, schemas:
290
329
  };
291
330
  }
292
331
 
293
- const enumValues = getEnumValues(check_constraint_definition);
294
- if (enumValues) {
295
- enums.push({
296
- name: check_constraint_name,
297
- schemaName: table_schema,
298
- values: enumValues,
332
+ if (!fields[key]) fields[key] = [];
333
+ const field = generateField(row);
334
+
335
+ if (check_constraint_definition && !checkConstraintDefinitionDedup[check_constraint_name]) {
336
+ const enumValuesByColumns = getEnumValues(check_constraint_definition, check_constraint_name);
337
+ enumValuesByColumns.forEach((item) => {
338
+ enums.push({
339
+ name: item.constraint_name,
340
+ schemaName: table_schema,
341
+ values: item.enumValues,
342
+ });
299
343
  });
344
+ checkConstraintDefinitionDedup[check_constraint_name] = enumValuesByColumns;
300
345
  }
301
346
 
302
- if (!fields[key]) fields[key] = [];
303
- const field = generateField(row);
304
- if (enumValues) {
305
- field.type = {
306
- type_name: check_constraint_name,
307
- schemaName: table_schema,
308
- };
347
+ if (checkConstraintDefinitionDedup[check_constraint_name]) {
348
+ const enumValuesByColumns = checkConstraintDefinitionDedup[check_constraint_name];
349
+ const columnEnumValues = enumValuesByColumns.find((item) => item.columns.includes(field.name));
350
+ if (columnEnumValues) {
351
+ field.type = {
352
+ type_name: columnEnumValues.constraint_name,
353
+ schemaName: table_schema,
354
+ };
355
+ }
309
356
  }
357
+
310
358
  fields[key].push(field);
311
359
 
312
360
  return acc;
@@ -357,6 +405,17 @@ const generateRefs = async (client: sql.ConnectionPool, schemas: string[]): Prom
357
405
  `;
358
406
 
359
407
  const refsQueryResult = await client.query(refsListSql);
408
+
409
+ const endpointsEqual = (ep1: RefEndpoint[], ep2: RefEndpoint[]): boolean => {
410
+ if (ep1.length !== ep2.length) return false;
411
+ return ep1.every((endpoint, index) =>
412
+ endpoint.tableName === ep2[index].tableName &&
413
+ endpoint.schemaName === ep2[index].schemaName &&
414
+ endpoint.fieldNames.length === ep2[index].fieldNames.length &&
415
+ endpoint.fieldNames.every((field, fieldIndex) => field === ep2[index].fieldNames[fieldIndex])
416
+ );
417
+ };
418
+
360
419
  refsQueryResult.recordset.forEach((refRow) => {
361
420
  const {
362
421
  table_schema,
@@ -384,12 +443,18 @@ const generateRefs = async (client: sql.ConnectionPool, schemas: string[]): Prom
384
443
  relation: '1',
385
444
  };
386
445
 
387
- refs.push({
446
+ const newRef = {
388
447
  name: fk_constraint_name,
389
448
  endpoints: [ep1, ep2],
390
449
  onDelete: on_delete === 'NO_ACTION' ? null : on_delete,
391
450
  onUpdate: on_update === 'NO_ACTION' ? null : on_update,
392
- });
451
+ };
452
+
453
+ const isDuplicate = refs.some(ref => endpointsEqual(ref.endpoints, newRef.endpoints));
454
+
455
+ if (!isDuplicate) {
456
+ refs.push(newRef);
457
+ }
393
458
  });
394
459
 
395
460
  return refs;
File without changes
File without changes
File without changes
@@ -1,3 +1,5 @@
1
+ import { columns } from "mssql";
2
+
1
3
  interface NoteInfo {
2
4
  value: string;
3
5
  }
@@ -106,6 +108,59 @@ interface BigQueryCredentials {
106
108
  datasets: string[],
107
109
  }
108
110
 
111
+ // Currently, we parse the check constraints as enum values in mssql.
112
+ // However, one check constraint can have multiple set of enum values.
113
+ // So, we need to store the enum values in a dictionary.
114
+ // Examples:
115
+ // CREATE TABLE AddressInfo (
116
+ // A1 UNIQUEIDENTIFIER NOT NULL,
117
+ // A2 UNIQUEIDENTIFIER NOT NULL,
118
+ // A3 UNIQUEIDENTIFIER NOT NULL,
119
+ // A4 UNIQUEIDENTIFIER NOT NULL,
120
+ // CONSTRAINT CK_Address_Valid CHECK (
121
+ // A1 IN ('1111', '2222', '333') AND
122
+ // A2 IN ('1111', '2222') AND
123
+ // A3 IN ('2222', '1111') AND
124
+ // A4 IN ('2222', '3333')
125
+ // )
126
+ // );
127
+ // GO
128
+ // In the above example, we have a check constraint CK_Address_Valid2 with 4 columns: A1, A2, A3, A4.
129
+ // Each column has a different set of enum values. So, we need to store the enum values in a dictionary.
130
+ // In the above example, the enum values dictionary will look like this:
131
+ // [
132
+ // {
133
+ // columns : ['A1'],
134
+ // enumValues: [
135
+ // { name: '1111' },
136
+ // { name: '2222' },
137
+ // { name: '333' }
138
+ // ],
139
+ // constraint_name: 'CK_Address_Valid_A1'
140
+ // },
141
+ // {
142
+ // columns : ['A2, A3'], => In this case, these two columns have the same set of enum values.
143
+ // enumValues: [
144
+ // { name: '1111' },
145
+ // { name: '2222' }
146
+ // ],
147
+ // constraint_name: 'CK_Address_Valid_A2_A3'
148
+ // },
149
+ // {
150
+ // columns : ['A4'],
151
+ // enumValues: [
152
+ // { name: '2222' },
153
+ // { name: '3333' }
154
+ // ],
155
+ // constraint_name: 'CK_Address_Valid_A4'
156
+ // }
157
+ // ]
158
+ interface EnumValuesDict {
159
+ columns: string[],
160
+ enumValues: EnumValue[],
161
+ constraint_name: string,
162
+ }
163
+
109
164
  export {
110
165
  NoteInfo,
111
166
  TypeInfo,
@@ -125,4 +180,5 @@ export {
125
180
  IndexesDictionary,
126
181
  DatabaseSchema,
127
182
  BigQueryCredentials,
183
+ EnumValuesDict,
128
184
  };
File without changes
File without changes
package/src/index.ts CHANGED
File without changes
File without changes
File without changes
File without changes
package/tsconfig.json CHANGED
File without changes