@dbml/connector 6.2.0-alpha.0 → 6.2.1

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.
@@ -1,486 +0,0 @@
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
- /* 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
- * */
12
- const mssql_1 = __importDefault(require("mssql"));
13
- const parseSchema_1 = require("../../utils/parseSchema");
14
- // https://learn.microsoft.com/en-us/sql/t-sql/data-types/date-and-time-types?view=sql-server-ver15
15
- const MSSQL_DATE_TYPES = [
16
- 'date',
17
- 'datetime',
18
- 'datetime2',
19
- 'smalldatetime',
20
- 'datetimeoffset',
21
- 'time',
22
- ];
23
- const getValidatedClient = async (connection) => {
24
- const pool = await mssql_1.default.connect(connection);
25
- try {
26
- // Establish a connection pool
27
- // Validate if the connection is successful by making a simple query
28
- await pool.request().query('SELECT 1');
29
- // If successful, return the pool
30
- return pool;
31
- }
32
- catch (err) {
33
- // Ensure to close any open pool in case of failure
34
- if (pool.connected) {
35
- await pool.close();
36
- }
37
- if (err instanceof Error) {
38
- throw new Error(`SQL connection error: ${err.message}`);
39
- }
40
- throw err; // Rethrow error if you want the calling code to handle it
41
- }
42
- };
43
- const convertQueryBoolean = (val) => val === 'YES';
44
- const getFieldType = (data_type, character_maximum_length, numeric_precision, numeric_scale) => {
45
- if (MSSQL_DATE_TYPES.includes(data_type)) {
46
- return data_type;
47
- }
48
- // timestamp is a synonym of rowversion and we cannot specify the precision for it
49
- // https://learn.microsoft.com/en-us/sql/t-sql/data-types/rowversion-transact-sql?view=sql-server-ver15
50
- if (data_type === 'timestamp') {
51
- return data_type;
52
- }
53
- // process numeric-based type
54
- if (data_type === 'bit') {
55
- return data_type;
56
- }
57
- // if precision != 0 => numeric-based column
58
- if (numeric_precision) {
59
- return numeric_scale
60
- ? `${data_type}(${numeric_precision},${numeric_scale})`
61
- : `${data_type}(${numeric_precision})`;
62
- }
63
- // process string-based type
64
- // ntext, text & image
65
- if (['ntext', 'text', 'image'].includes(data_type)) {
66
- return data_type;
67
- }
68
- // Column data type is varchar(max), nvarchar(max), varbinary(max), or xml
69
- if (character_maximum_length < 0) {
70
- return data_type === 'xml' ? data_type : `${data_type}(MAX)`;
71
- }
72
- // character_maximum_length is the lenght in bytes
73
- // nchar and nvarchar store Unicode characters, each character needs 2 bytes
74
- // so we have to divide it by 2 to get the correct maximum lenght in character.
75
- // ref: https://learn.microsoft.com/en-us/sql/t-sql/data-types/nchar-and-nvarchar-transact-sql?view=sql-server-ver15
76
- if (character_maximum_length > 0) {
77
- const maximum_length_in_character = (data_type === 'nchar' || data_type === 'nvarchar')
78
- ? character_maximum_length / 2
79
- : character_maximum_length;
80
- return `${data_type}(${maximum_length_in_character})`;
81
- }
82
- return data_type;
83
- };
84
- const getDbdefault = (data_type, column_default, default_type) => {
85
- // The regex below is used to extract the value from the default value
86
- // \( and \) are used to escape parentheses
87
- // [^()]+ is used to match any character except parentheses
88
- // Example: (1) => 1, ('hello') => hello, getdate()-(1) => getdate()-1
89
- const value = column_default.slice(1, -1).replace(/\(([^()]+)\)/g, '$1');
90
- return {
91
- type: default_type,
92
- value: default_type === 'string' ? value.slice(1, -1) : value, // Remove the quotes for string values
93
- };
94
- };
95
- const generateField = (row) => {
96
- const { column_name, data_type, character_maximum_length, numeric_precision, numeric_scale, identity_increment, is_nullable, column_default, default_type, column_comment, } = row;
97
- const dbdefault = column_default && default_type !== 'increment' ? getDbdefault(data_type, column_default, default_type) : null;
98
- const fieldType = {
99
- type_name: getFieldType(data_type, character_maximum_length, numeric_precision, numeric_scale),
100
- schemaName: null,
101
- };
102
- return {
103
- name: column_name,
104
- type: fieldType,
105
- dbdefault,
106
- not_null: !convertQueryBoolean(is_nullable),
107
- increment: !!identity_increment,
108
- note: column_comment ? { value: column_comment } : { value: '' },
109
- };
110
- };
111
- const generateTablesFields = async (client, schemas) => {
112
- const fields = {};
113
- const tablesAndFieldsSql = `
114
- WITH tables_and_fields AS (
115
- SELECT
116
- t.object_id AS table_id,
117
- s.name AS table_schema,
118
- t.name AS table_name,
119
- t.create_date as table_create_date,
120
- c.name AS column_name,
121
- c.column_id as column_id,
122
- ty.name AS data_type,
123
- c.max_length AS character_maximum_length,
124
- c.precision AS numeric_precision,
125
- c.scale AS numeric_scale,
126
- c.is_identity AS identity_increment,
127
- CASE
128
- WHEN c.is_nullable = 1 THEN 'YES'
129
- ELSE 'NO'
130
- END AS is_nullable,
131
- CASE
132
- WHEN c.default_object_id = 0 THEN NULL
133
- ELSE OBJECT_DEFINITION(c.default_object_id)
134
- END AS column_default,
135
- -- Fetching table comments
136
- p.value AS table_comment,
137
- ep.value AS column_comment
138
- FROM
139
- sys.tables t
140
- JOIN sys.schemas s ON t.schema_id = s.schema_id
141
- JOIN sys.columns c ON t.object_id = c.object_id
142
- JOIN sys.types ty ON c.user_type_id = ty.user_type_id
143
- LEFT JOIN sys.extended_properties p ON p.major_id = t.object_id
144
- AND p.name like '%description%'
145
- AND p.minor_id = 0 -- Ensure minor_id is 0 for table comments
146
- LEFT JOIN sys.extended_properties ep ON ep.major_id = c.object_id
147
- AND ep.minor_id = c.column_id
148
- AND ep.name like '%description%'
149
- WHERE
150
- t.type = 'U' -- User-defined tables
151
- )
152
- SELECT
153
- table_schema,
154
- table_name,
155
- column_name,
156
- data_type,
157
- character_maximum_length,
158
- numeric_precision,
159
- numeric_scale,
160
- identity_increment,
161
- is_nullable,
162
- column_default,
163
- table_comment,
164
- column_comment,
165
- CASE
166
- WHEN column_default LIKE '((%))' THEN 'number'
167
- WHEN column_default LIKE '(''%'')' THEN 'string'
168
- ELSE 'expression'
169
- END AS default_type
170
- FROM
171
- tables_and_fields
172
- WHERE
173
- 1 = 1
174
- ${(0, parseSchema_1.buildSchemaQuery)('table_schema', schemas)}
175
- ORDER BY
176
- table_schema,
177
- table_create_date,
178
- table_id,
179
- column_id;
180
- `;
181
- const tablesAndFieldsResult = await client.query(tablesAndFieldsSql);
182
- const tables = tablesAndFieldsResult.recordset.reduce((acc, row) => {
183
- const { table_schema, table_name, table_comment, } = row;
184
- const key = `${table_schema}.${table_name}`;
185
- if (!acc[key]) {
186
- acc[key] = {
187
- name: table_name,
188
- schemaName: table_schema,
189
- note: table_comment ? { value: table_comment } : { value: '' },
190
- };
191
- }
192
- if (!fields[key])
193
- fields[key] = [];
194
- const field = generateField(row);
195
- fields[key].push(field);
196
- return acc;
197
- }, {});
198
- return {
199
- tables: Object.values(tables),
200
- fields,
201
- };
202
- };
203
- const generateRefs = async (client, schemas) => {
204
- const refs = [];
205
- const refsListSql = `
206
- SELECT
207
- s.name AS table_schema,
208
- t.name AS table_name,
209
- fk.name AS fk_constraint_name,
210
- STUFF((
211
- SELECT ',' + c1.name
212
- FROM sys.foreign_key_columns AS fkc
213
- JOIN sys.columns AS c1 ON fkc.parent_object_id = c1.object_id AND fkc.parent_column_id = c1.column_id
214
- WHERE fkc.constraint_object_id = fk.object_id
215
- FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '') AS column_names,
216
- s2.name AS foreign_table_schema,
217
- t2.name AS foreign_table_name,
218
- STUFF((
219
- SELECT ',' + c2.name
220
- FROM sys.foreign_key_columns AS fkc
221
- JOIN sys.columns AS c2 ON fkc.referenced_object_id = c2.object_id AND fkc.referenced_column_id = c2.column_id
222
- WHERE fkc.constraint_object_id = fk.object_id
223
- FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '') AS foreign_column_names,
224
- fk.type_desc AS constraint_type,
225
- fk.delete_referential_action_desc AS on_delete,
226
- fk.update_referential_action_desc AS on_update
227
- FROM sys.foreign_keys AS fk
228
- JOIN sys.tables AS t ON fk.parent_object_id = t.object_id
229
- JOIN sys.schemas AS s ON t.schema_id = s.schema_id
230
- JOIN sys.tables AS t2 ON fk.referenced_object_id = t2.object_id
231
- JOIN sys.schemas AS s2 ON t2.schema_id = s2.schema_id
232
- WHERE s.name NOT IN ('sys', 'information_schema')
233
- ${(0, parseSchema_1.buildSchemaQuery)('s.name', schemas)}
234
- ORDER BY
235
- s.name,
236
- t.name;
237
- `;
238
- const refsQueryResult = await client.query(refsListSql);
239
- const endpointsEqual = (ep1, ep2) => {
240
- if (ep1.length !== ep2.length)
241
- return false;
242
- return ep1.every((endpoint, index) => endpoint.tableName === ep2[index].tableName
243
- && endpoint.schemaName === ep2[index].schemaName
244
- && endpoint.fieldNames.length === ep2[index].fieldNames.length
245
- && endpoint.fieldNames.every((field, fieldIndex) => field === ep2[index].fieldNames[fieldIndex]));
246
- };
247
- refsQueryResult.recordset.forEach((refRow) => {
248
- const { table_schema, fk_constraint_name, table_name, column_names, foreign_table_schema, foreign_table_name, foreign_column_names, on_delete, on_update, } = refRow;
249
- const ep1 = {
250
- tableName: table_name,
251
- schemaName: table_schema,
252
- fieldNames: column_names.split(','),
253
- relation: '*',
254
- };
255
- const ep2 = {
256
- tableName: foreign_table_name,
257
- schemaName: foreign_table_schema,
258
- fieldNames: foreign_column_names.split(','),
259
- relation: '1',
260
- };
261
- const newRef = {
262
- name: fk_constraint_name,
263
- endpoints: [ep1, ep2],
264
- onDelete: on_delete === 'NO_ACTION' ? null : on_delete,
265
- onUpdate: on_update === 'NO_ACTION' ? null : on_update,
266
- };
267
- const isDuplicate = refs.some((ref) => endpointsEqual(ref.endpoints, newRef.endpoints));
268
- if (!isDuplicate) {
269
- refs.push(newRef);
270
- }
271
- });
272
- return refs;
273
- };
274
- const generateIndexesAndConstraints = async (client, schemas) => {
275
- const indexListSql = `
276
- WITH user_tables AS (
277
- SELECT
278
- TABLE_SCHEMA,
279
- TABLE_NAME
280
- FROM
281
- INFORMATION_SCHEMA.TABLES
282
- WHERE
283
- TABLE_TYPE = 'BASE TABLE' -- Ensure we are only getting base tables
284
- AND TABLE_NAME NOT LIKE 'dt%'
285
- AND TABLE_NAME NOT LIKE 'syscs%'
286
- AND TABLE_NAME NOT LIKE 'sysss%'
287
- AND TABLE_NAME NOT LIKE 'sysrs%'
288
- AND TABLE_NAME NOT LIKE 'sysxlgc%'
289
- ),
290
- index_info AS (
291
- SELECT
292
- SCHEMA_NAME(t.schema_id) AS table_schema, -- Add schema information
293
- OBJECT_NAME(i.object_id) AS table_name,
294
- i.name AS index_name,
295
- i.is_unique,
296
- CASE
297
- WHEN i.type = 1 THEN 1
298
- ELSE 0
299
- END AS is_primary,
300
- i.type_desc AS index_type,
301
- STUFF((
302
- SELECT
303
- ', ' + c.name
304
- FROM
305
- sys.index_columns ic
306
- JOIN sys.columns c ON ic.column_id = c.column_id AND ic.object_id = c.object_id
307
- WHERE
308
- ic.index_id = i.index_id
309
- AND ic.object_id = i.object_id
310
- AND OBJECT_NAME(ic.object_id) IN (SELECT TABLE_NAME FROM user_tables) -- Filter for user tables
311
- ORDER BY
312
- ic.key_ordinal
313
- FOR XML PATH('')
314
- ), 1, 2, '') AS columns,
315
- CASE
316
- WHEN i.type = 1 THEN 'PRIMARY KEY'
317
- WHEN i.is_unique = 1 THEN 'UNIQUE'
318
- ELSE NULL
319
- END AS constraint_type
320
- FROM
321
- sys.indexes i
322
- JOIN sys.tables t ON i.object_id = t.object_id
323
- WHERE
324
- t.is_ms_shipped = 0
325
- AND i.type <> 0
326
- )
327
- SELECT
328
- ut.TABLE_SCHEMA AS table_schema,
329
- ut.TABLE_NAME AS table_name,
330
- ii.index_name,
331
- ii.is_unique,
332
- ii.is_primary,
333
- ii.index_type,
334
- ii.columns,
335
- ii.constraint_type
336
- FROM
337
- user_tables ut
338
- LEFT JOIN
339
- index_info ii ON ut.TABLE_NAME = ii.table_name
340
- AND ut.TABLE_SCHEMA = ii.table_schema
341
- WHERE
342
- ii.columns IS NOT NULL
343
- ${(0, parseSchema_1.buildSchemaQuery)('ut.TABLE_SCHEMA', schemas)}
344
- ORDER BY
345
- ut.TABLE_NAME,
346
- ii.constraint_type,
347
- ii.index_name;
348
- `;
349
- const indexListResult = await client.query(indexListSql);
350
- const { outOfLineIndexes, inlineIndexes } = indexListResult.recordset.reduce((acc, row) => {
351
- const { constraint_type, columns } = row;
352
- if (columns === 'null' || columns.trim() === '')
353
- return acc;
354
- if (constraint_type === 'PRIMARY KEY' || constraint_type === 'UNIQUE') {
355
- acc.inlineIndexes.push(row);
356
- }
357
- else {
358
- acc.outOfLineIndexes.push(row);
359
- }
360
- return acc;
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;
364
- const indexColumns = columns.split(',').map((column) => {
365
- return {
366
- type: 'column',
367
- value: column.trim(),
368
- };
369
- });
370
- const indexExpressions = expressions
371
- ? expressions.split(',').map((expression) => {
372
- return {
373
- type: 'expression',
374
- value: expression,
375
- };
376
- })
377
- : [];
378
- const index = {
379
- name: index_name,
380
- type: index_type,
381
- columns: [
382
- ...indexColumns,
383
- ...indexExpressions,
384
- ],
385
- pk: !!is_primary,
386
- unique: !is_primary && !!is_unique,
387
- };
388
- const key = `${table_schema}.${table_name}`;
389
- if (acc[key]) {
390
- acc[key].push(index);
391
- }
392
- else {
393
- acc[key] = [index];
394
- }
395
- return acc;
396
- }, {});
397
- const tableConstraints = {};
398
- inlineIndexes.forEach((row) => {
399
- const { table_schema, table_name, columns, constraint_type, } = row;
400
- const key = `${table_schema}.${table_name}`;
401
- if (!tableConstraints[key])
402
- tableConstraints[key] = {};
403
- const columnNames = columns.split(',').map((column) => column.trim());
404
- columnNames.forEach((columnName) => {
405
- if (!tableConstraints[key][columnName])
406
- tableConstraints[key][columnName] = { checks: [] };
407
- if (constraint_type === 'PRIMARY KEY') {
408
- tableConstraints[key][columnName].pk = true;
409
- }
410
- if (constraint_type === 'UNIQUE' && !tableConstraints[key][columnName].pk) {
411
- tableConstraints[key][columnName].unique = true;
412
- }
413
- });
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
- });
450
- return {
451
- indexes,
452
- tableConstraints,
453
- checks,
454
- };
455
- };
456
- const fetchSchemaJson = async (connection) => {
457
- const { connectionString, schemas } = (0, parseSchema_1.parseConnectionString)(connection, 'odbc');
458
- const client = await getValidatedClient(connectionString);
459
- try {
460
- const tablesRes = generateTablesFields(client, schemas);
461
- const indexesAndConstraintsRes = generateIndexesAndConstraints(client, schemas);
462
- const refsRes = generateRefs(client, schemas);
463
- const res = await Promise.all([
464
- tablesRes,
465
- indexesAndConstraintsRes,
466
- refsRes,
467
- ]);
468
- const { tables, fields } = res[0];
469
- const { indexes, tableConstraints, checks } = res[1];
470
- const refs = res[2];
471
- return {
472
- tables,
473
- fields,
474
- // MSSQL doesn't support the notion of enums
475
- enums: [],
476
- refs,
477
- indexes,
478
- tableConstraints,
479
- checks,
480
- };
481
- }
482
- finally {
483
- await client.close();
484
- }
485
- };
486
- exports.fetchSchemaJson = fetchSchemaJson;