@dbml/connector 3.7.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.
@@ -0,0 +1,3 @@
1
+ import { DatabaseSchema } from './types';
2
+ declare const fetchSchemaJson: (connection: string, format: string) => Promise<DatabaseSchema>;
3
+ export { fetchSchemaJson, };
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fetchSchemaJson = void 0;
4
+ const postgresConnector_1 = require("./postgresConnector");
5
+ const mssqlConnector_1 = require("./mssqlConnector");
6
+ const mysqlConnector_1 = require("./mysqlConnector");
7
+ const fetchSchemaJson = async (connection, format) => {
8
+ switch (format) {
9
+ case 'postgres':
10
+ return (0, postgresConnector_1.fetchSchemaJson)(connection);
11
+ case 'mssql':
12
+ return (0, mssqlConnector_1.fetchSchemaJson)(connection);
13
+ case 'mysql':
14
+ return (0, mysqlConnector_1.fetchSchemaJson)(connection);
15
+ default:
16
+ throw new Error(`Unsupported connection format: ${format}`);
17
+ }
18
+ };
19
+ exports.fetchSchemaJson = fetchSchemaJson;
@@ -0,0 +1,3 @@
1
+ import { DatabaseSchema } from './types';
2
+ declare const fetchSchemaJson: (connection: string) => Promise<DatabaseSchema>;
3
+ export { fetchSchemaJson, };
@@ -0,0 +1,435 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.fetchSchemaJson = void 0;
7
+ /* eslint-disable camelcase */
8
+ const mssql_1 = __importDefault(require("mssql"));
9
+ const MSSQL_DATE_TYPES = [
10
+ 'date',
11
+ 'datetime',
12
+ 'datetime2',
13
+ 'smalldatetime',
14
+ 'datetimeoffset',
15
+ 'time',
16
+ ];
17
+ const getValidatedClient = async (connection) => {
18
+ const pool = await mssql_1.default.connect(connection);
19
+ try {
20
+ // Establish a connection pool
21
+ // Validate if the connection is successful by making a simple query
22
+ await pool.request().query('SELECT 1');
23
+ // If successful, return the pool
24
+ return pool;
25
+ }
26
+ catch (err) {
27
+ // Ensure to close any open pool in case of failure
28
+ if (pool.connected) {
29
+ await pool.close();
30
+ }
31
+ if (err instanceof Error) {
32
+ throw new Error(`SQL connection error: ${err.message}`);
33
+ }
34
+ throw err; // Rethrow error if you want the calling code to handle it
35
+ }
36
+ };
37
+ const convertQueryBoolean = (val) => val === 'YES';
38
+ const getFieldType = (data_type, default_type, character_maximum_length, numeric_precision, numeric_scale) => {
39
+ if (MSSQL_DATE_TYPES.includes(data_type)) {
40
+ return data_type;
41
+ }
42
+ if (data_type === 'bit') {
43
+ return data_type;
44
+ }
45
+ if (numeric_precision && numeric_scale && default_type === 'number') {
46
+ return `${data_type}(${numeric_precision},${numeric_scale})`;
47
+ }
48
+ if (character_maximum_length && character_maximum_length > 0 && default_type === 'string') {
49
+ return `${data_type}(${character_maximum_length})`;
50
+ }
51
+ return data_type;
52
+ };
53
+ const getDbdefault = (data_type, column_default, default_type) => {
54
+ // The regex below is used to extract the value from the default value
55
+ // \( and \) are used to escape parentheses
56
+ // [^()]+ is used to match any character except parentheses
57
+ // Example: (1) => 1, ('hello') => hello, getdate()-(1) => getdate()-1
58
+ const value = column_default.slice(1, -1).replace(/\(([^()]+)\)/g, '$1');
59
+ return {
60
+ type: default_type,
61
+ value: default_type === 'string' ? value.slice(1, -1) : value, // Remove the quotes for string values
62
+ };
63
+ };
64
+ const getEnumValues = (definition) => {
65
+ // Use the example below to understand the regex:
66
+ // ([quantity]>(0))
67
+ // ([unit_price]>(0))
68
+ // ([status]='cancelled' OR [status]='delivered' OR [status]='shipped' OR [status]='processing' OR [status]='pending')
69
+ // ([total_amount]>(0))
70
+ // ([price]>(0))
71
+ // ([stock_quantity]>=(0))
72
+ // ([age_start]<=[age_end])
73
+ // ([age_start]<=[age_end])
74
+ // ([gender]='Other' OR [gender]='Female' OR [gender]='Male')
75
+ // ([date_of_birth]<=dateadd(year,(-13),getdate()))
76
+ // ([email] like '%_@_%._%')
77
+ if (!definition)
78
+ return null;
79
+ const values = definition.match(/\[([^\]]+)\]='([^']+)'/g); // Extracting the enum values when the definition contains ]='
80
+ if (!values)
81
+ return null;
82
+ const enumValues = values.map((value) => {
83
+ const enumValue = value.split("]='")[1];
84
+ return { name: enumValue.slice(0, -1) };
85
+ });
86
+ return enumValues;
87
+ };
88
+ const generateField = (row) => {
89
+ const { column_name, data_type, character_maximum_length, numeric_precision, numeric_scale, identity_increment, is_nullable, column_default, default_type, column_comment, } = row;
90
+ const dbdefault = column_default && default_type !== 'increment' ? getDbdefault(data_type, column_default, default_type) : null;
91
+ const fieldType = {
92
+ type_name: getFieldType(data_type, default_type, character_maximum_length, numeric_precision, numeric_scale),
93
+ schemaName: null,
94
+ };
95
+ return {
96
+ name: column_name,
97
+ type: fieldType,
98
+ dbdefault,
99
+ not_null: !convertQueryBoolean(is_nullable),
100
+ increment: !!identity_increment,
101
+ note: column_comment ? { value: column_comment } : { value: '' },
102
+ };
103
+ };
104
+ const generateTablesFieldsAndEnums = async (client) => {
105
+ const fields = {};
106
+ const enums = [];
107
+ const tablesAndFieldsSql = `
108
+ WITH tables_and_fields AS (
109
+ SELECT
110
+ s.name AS table_schema,
111
+ t.name AS table_name,
112
+ c.name AS column_name,
113
+ ty.name AS data_type,
114
+ c.max_length AS character_maximum_length,
115
+ c.precision AS numeric_precision,
116
+ c.scale AS numeric_scale,
117
+ c.is_identity AS identity_increment,
118
+ CASE
119
+ WHEN c.is_nullable = 1 THEN 'YES'
120
+ ELSE 'NO'
121
+ END AS is_nullable,
122
+ CASE
123
+ WHEN c.default_object_id = 0 THEN NULL
124
+ ELSE OBJECT_DEFINITION(c.default_object_id)
125
+ END AS column_default,
126
+ -- Fetching table comments
127
+ p.value AS table_comment,
128
+ ep.value AS column_comment
129
+ FROM
130
+ sys.tables t
131
+ JOIN
132
+ sys.schemas s ON t.schema_id = s.schema_id
133
+ JOIN
134
+ sys.columns c ON t.object_id = c.object_id
135
+ JOIN
136
+ sys.types ty ON c.user_type_id = ty.user_type_id
137
+ LEFT JOIN
138
+ sys.extended_properties p ON p.major_id = t.object_id
139
+ AND p.name = 'MS_Description'
140
+ AND p.minor_id = 0 -- Ensure minor_id is 0 for table comments
141
+ LEFT JOIN
142
+ sys.extended_properties ep ON ep.major_id = c.object_id
143
+ AND ep.minor_id = c.column_id
144
+ AND ep.name = 'MS_Description'
145
+ WHERE
146
+ t.type = 'U' -- User-defined tables
147
+ )
148
+ SELECT
149
+ tf.table_schema,
150
+ tf.table_name,
151
+ tf.column_name,
152
+ tf.data_type,
153
+ tf.character_maximum_length,
154
+ tf.numeric_precision,
155
+ tf.numeric_scale,
156
+ tf.identity_increment,
157
+ tf.is_nullable,
158
+ tf.column_default,
159
+ tf.table_comment,
160
+ tf.column_comment,
161
+ cc.name AS check_constraint_name, -- Adding CHECK constraint name
162
+ cc.definition AS check_constraint_definition, -- Adding CHECK constraint definition
163
+ CASE
164
+ WHEN tf.column_default LIKE '((%))' THEN 'number'
165
+ WHEN tf.column_default LIKE '(''%'')' THEN 'string'
166
+ ELSE 'expression'
167
+ END AS default_type
168
+ FROM
169
+ tables_and_fields AS tf
170
+ LEFT JOIN
171
+ sys.check_constraints cc ON cc.parent_object_id = OBJECT_ID(tf.table_schema + '.' + tf.table_name)
172
+ AND cc.definition LIKE '%' + tf.column_name + '%' -- Ensure the constraint references the column
173
+ ORDER BY
174
+ tf.table_schema,
175
+ tf.table_name,
176
+ tf.column_name;
177
+ `;
178
+ const tablesAndFieldsResult = await client.query(tablesAndFieldsSql);
179
+ const tables = tablesAndFieldsResult.recordset.reduce((acc, row) => {
180
+ const { table_schema, table_name, table_comment, check_constraint_name, check_constraint_definition, } = row;
181
+ const key = `${table_schema}.${table_name}`;
182
+ if (!acc[key]) {
183
+ acc[key] = {
184
+ name: table_name,
185
+ schemaName: table_schema,
186
+ note: table_comment ? { value: table_comment } : { value: '' },
187
+ };
188
+ }
189
+ const enumValues = getEnumValues(check_constraint_definition);
190
+ if (enumValues) {
191
+ enums.push({
192
+ name: check_constraint_name,
193
+ schemaName: table_schema,
194
+ values: enumValues,
195
+ });
196
+ }
197
+ if (!fields[key])
198
+ fields[key] = [];
199
+ const field = generateField(row);
200
+ if (enumValues) {
201
+ field.type = {
202
+ type_name: check_constraint_name,
203
+ schemaName: table_schema,
204
+ };
205
+ }
206
+ fields[key].push(field);
207
+ return acc;
208
+ }, {});
209
+ return {
210
+ tables: Object.values(tables),
211
+ fields,
212
+ enums,
213
+ };
214
+ };
215
+ const generateRefs = async (client) => {
216
+ const refs = [];
217
+ const refsListSql = `
218
+ SELECT
219
+ s.name AS table_schema,
220
+ t.name AS table_name,
221
+ fk.name AS fk_constraint_name,
222
+ STUFF((
223
+ SELECT ',' + c1.name
224
+ FROM sys.foreign_key_columns AS fkc
225
+ JOIN sys.columns AS c1 ON fkc.parent_object_id = c1.object_id AND fkc.parent_column_id = c1.column_id
226
+ WHERE fkc.constraint_object_id = fk.object_id
227
+ FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '') AS column_names,
228
+ s2.name AS foreign_table_schema,
229
+ t2.name AS foreign_table_name,
230
+ STUFF((
231
+ SELECT ',' + c2.name
232
+ FROM sys.foreign_key_columns AS fkc
233
+ JOIN sys.columns AS c2 ON fkc.referenced_object_id = c2.object_id AND fkc.referenced_column_id = c2.column_id
234
+ WHERE fkc.constraint_object_id = fk.object_id
235
+ FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '') AS foreign_column_names,
236
+ fk.type_desc AS constraint_type,
237
+ fk.delete_referential_action_desc AS on_delete,
238
+ fk.update_referential_action_desc AS on_update
239
+ FROM sys.foreign_keys AS fk
240
+ JOIN sys.tables AS t ON fk.parent_object_id = t.object_id
241
+ JOIN sys.schemas AS s ON t.schema_id = s.schema_id
242
+ JOIN sys.tables AS t2 ON fk.referenced_object_id = t2.object_id
243
+ JOIN sys.schemas AS s2 ON t2.schema_id = s2.schema_id
244
+ WHERE s.name NOT IN ('sys', 'information_schema')
245
+ ORDER BY
246
+ s.name,
247
+ t.name;
248
+ `;
249
+ const refsQueryResult = await client.query(refsListSql);
250
+ refsQueryResult.recordset.forEach((refRow) => {
251
+ const { table_schema, fk_constraint_name, table_name, column_names, foreign_table_schema, foreign_table_name, foreign_column_names, on_delete, on_update, } = refRow;
252
+ const ep1 = {
253
+ tableName: table_name,
254
+ schemaName: table_schema,
255
+ fieldNames: column_names.split(','),
256
+ relation: '*',
257
+ };
258
+ const ep2 = {
259
+ tableName: foreign_table_name,
260
+ schemaName: foreign_table_schema,
261
+ fieldNames: foreign_column_names.split(','),
262
+ relation: '1',
263
+ };
264
+ refs.push({
265
+ name: fk_constraint_name,
266
+ endpoints: [ep1, ep2],
267
+ onDelete: on_delete === 'NO_ACTION' ? null : on_delete,
268
+ onUpdate: on_update === 'NO_ACTION' ? null : on_update,
269
+ });
270
+ });
271
+ return refs;
272
+ };
273
+ const generateIndexes = async (client) => {
274
+ const indexListSql = `
275
+ WITH user_tables AS (
276
+ SELECT
277
+ TABLE_SCHEMA,
278
+ TABLE_NAME
279
+ FROM
280
+ INFORMATION_SCHEMA.TABLES
281
+ WHERE
282
+ TABLE_TYPE = 'BASE TABLE' -- Ensure we are only getting base tables
283
+ AND TABLE_NAME NOT LIKE 'dt%'
284
+ AND TABLE_NAME NOT LIKE 'syscs%'
285
+ AND TABLE_NAME NOT LIKE 'sysss%'
286
+ AND TABLE_NAME NOT LIKE 'sysrs%'
287
+ AND TABLE_NAME NOT LIKE 'sysxlgc%'
288
+ ),
289
+ index_info AS (
290
+ SELECT
291
+ OBJECT_NAME(i.object_id) AS table_name,
292
+ i.name AS index_name,
293
+ i.is_unique,
294
+ CASE
295
+ WHEN i.type = 1 THEN 1
296
+ ELSE 0
297
+ END AS is_primary,
298
+ i.type_desc AS index_type,
299
+ STUFF((
300
+ SELECT
301
+ ', ' + c.name
302
+ FROM
303
+ sys.index_columns ic
304
+ JOIN sys.columns c ON ic.column_id = c.column_id AND ic.object_id = c.object_id
305
+ WHERE
306
+ ic.index_id = i.index_id
307
+ AND ic.object_id = i.object_id
308
+ AND OBJECT_NAME(ic.object_id) IN (SELECT TABLE_NAME FROM user_tables) -- Filter for user tables
309
+ ORDER BY
310
+ ic.key_ordinal
311
+ FOR XML PATH('')
312
+ ), 1, 2, '') AS columns,
313
+ CASE
314
+ WHEN i.type = 1 THEN 'PRIMARY KEY'
315
+ WHEN i.is_unique = 1 THEN 'UNIQUE'
316
+ ELSE NULL
317
+ END AS constraint_type
318
+ FROM
319
+ sys.indexes i
320
+ JOIN sys.tables t ON i.object_id = t.object_id
321
+ WHERE
322
+ t.is_ms_shipped = 0
323
+ AND i.type <> 0
324
+ )
325
+ SELECT
326
+ ut.TABLE_SCHEMA AS table_schema,
327
+ ut.TABLE_NAME AS table_name,
328
+ ii.index_name,
329
+ ii.is_unique,
330
+ ii.is_primary,
331
+ ii.index_type,
332
+ ii.columns,
333
+ ii.constraint_type
334
+ FROM
335
+ user_tables ut
336
+ LEFT JOIN
337
+ index_info ii ON ut.TABLE_NAME = ii.table_name
338
+ WHERE
339
+ ii.columns IS NOT NULL
340
+ ORDER BY
341
+ ut.TABLE_NAME,
342
+ ii.constraint_type,
343
+ ii.index_name;
344
+ `;
345
+ const indexListResult = await client.query(indexListSql);
346
+ const { outOfLineConstraints, inlineConstraints } = indexListResult.recordset.reduce((acc, row) => {
347
+ const { constraint_type, columns } = row;
348
+ if (columns === 'null' || columns.trim() === '')
349
+ return acc;
350
+ if (constraint_type === 'PRIMARY KEY' || constraint_type === 'UNIQUE') {
351
+ acc.inlineConstraints.push(row);
352
+ }
353
+ else {
354
+ acc.outOfLineConstraints.push(row);
355
+ }
356
+ return acc;
357
+ }, { outOfLineConstraints: [], inlineConstraints: [] });
358
+ const indexes = outOfLineConstraints.reduce((acc, indexRow) => {
359
+ const { table_schema, table_name, index_name, index_type, columns, expressions, } = indexRow;
360
+ const indexColumns = columns.split(',').map((column) => {
361
+ return {
362
+ type: 'column',
363
+ value: column.trim(),
364
+ };
365
+ });
366
+ const indexExpressions = expressions ? expressions.split(',').map((expression) => {
367
+ return {
368
+ type: 'expression',
369
+ value: expression,
370
+ };
371
+ }) : [];
372
+ const index = {
373
+ name: index_name,
374
+ type: index_type,
375
+ columns: [
376
+ ...indexColumns,
377
+ ...indexExpressions,
378
+ ],
379
+ };
380
+ const key = `${table_schema}.${table_name}`;
381
+ if (acc[key]) {
382
+ acc[key].push(index);
383
+ }
384
+ else {
385
+ acc[key] = [index];
386
+ }
387
+ return acc;
388
+ }, {});
389
+ const tableConstraints = inlineConstraints.reduce((acc, row) => {
390
+ const { table_schema, table_name, columns, constraint_type, } = row;
391
+ const key = `${table_schema}.${table_name}`;
392
+ if (!acc[key])
393
+ acc[key] = {};
394
+ const columnNames = columns.split(',').map((column) => column.trim());
395
+ columnNames.forEach((columnName) => {
396
+ if (!acc[key][columnName])
397
+ acc[key][columnName] = {};
398
+ if (constraint_type === 'PRIMARY KEY') {
399
+ acc[key][columnName].pk = true;
400
+ }
401
+ if (constraint_type === 'UNIQUE' && !acc[key][columnName].pk) {
402
+ acc[key][columnName].unique = true;
403
+ }
404
+ });
405
+ return acc;
406
+ }, {});
407
+ return {
408
+ indexes,
409
+ tableConstraints,
410
+ };
411
+ };
412
+ const fetchSchemaJson = async (connection) => {
413
+ const client = await getValidatedClient(connection);
414
+ const tablesFieldsAndEnumsRes = generateTablesFieldsAndEnums(client);
415
+ const indexesRes = generateIndexes(client);
416
+ const refsRes = generateRefs(client);
417
+ const res = await Promise.all([
418
+ tablesFieldsAndEnumsRes,
419
+ indexesRes,
420
+ refsRes,
421
+ ]);
422
+ client.close();
423
+ const { tables, fields, enums } = res[0];
424
+ const { indexes, tableConstraints } = res[1];
425
+ const refs = res[2];
426
+ return {
427
+ tables,
428
+ fields,
429
+ enums,
430
+ refs,
431
+ indexes,
432
+ tableConstraints,
433
+ };
434
+ };
435
+ exports.fetchSchemaJson = fetchSchemaJson;
@@ -0,0 +1,3 @@
1
+ import { DatabaseSchema } from './types';
2
+ declare function fetchSchemaJson(connection: string): Promise<DatabaseSchema>;
3
+ export { fetchSchemaJson, };