@dbml/cli 3.7.0 → 3.7.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dbml/cli",
3
- "version": "3.7.0",
3
+ "version": "3.7.1",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "license": "Apache-2.0",
@@ -26,7 +26,7 @@
26
26
  ],
27
27
  "dependencies": {
28
28
  "@babel/cli": "^7.21.0",
29
- "@dbml/core": "^3.7.0",
29
+ "@dbml/core": "^3.7.1",
30
30
  "bluebird": "^3.5.5",
31
31
  "chalk": "^2.4.2",
32
32
  "commander": "^2.20.0",
@@ -54,5 +54,8 @@
54
54
  "^.+\\.js$": "babel-jest"
55
55
  }
56
56
  },
57
- "gitHead": "3222baa4bf45b9ff74711a63d376505cf4bd9ac0"
57
+ "gitHead": "71d7e2cd00ba5d60d2e36c465d43ce1d91189274",
58
+ "engines": {
59
+ "node": ">=18"
60
+ }
58
61
  }
@@ -1,19 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.fetchSchemaJson = void 0;
7
- var _PostgresConnector = require("./PostgresConnector");
8
- var _MssqlConnector = require("./MssqlConnector");
9
- const fetchSchemaJson = async (connection, format) => {
10
- switch (format) {
11
- case 'postgres':
12
- return (0, _PostgresConnector.fetchSchemaJson)(connection);
13
- case 'mssql':
14
- return (0, _MssqlConnector.fetchSchemaJson)(connection);
15
- default:
16
- throw new Error(`Unsupported connection format: ${format}`);
17
- }
18
- };
19
- exports.fetchSchemaJson = fetchSchemaJson;
@@ -1,483 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.fetchSchemaJson = void 0;
7
- var _mssql = _interopRequireDefault(require("mssql"));
8
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
9
- /* eslint-disable camelcase */
10
-
11
- const MSSQL_DATE_TYPES = ['date', 'datetime', 'datetime2', 'smalldatetime', 'datetimeoffset', 'time'];
12
- const connect = async connection => {
13
- const options = connection.split(';').reduce((acc, option) => {
14
- const [key, value] = option.split('=');
15
- acc[key] = value;
16
- return acc;
17
- }, {});
18
- const [host, port] = options['Data Source'].split(',');
19
- const config = {
20
- user: options['User ID'],
21
- password: options.Password,
22
- server: host,
23
- database: options['Initial Catalog'],
24
- options: {
25
- encrypt: options.Encrypt === 'True',
26
- trustServerCertificate: options['Trust Server Certificate'] === 'True',
27
- port: port || 1433
28
- }
29
- };
30
- try {
31
- // Connect to the database using the connection string
32
- const client = await _mssql.default.connect(config);
33
- return client;
34
- } catch (err) {
35
- console.log('MSSQL connection error:', err);
36
- return null;
37
- }
38
- };
39
- const convertQueryBoolean = val => val === 'YES';
40
- const getFieldType = (data_type, default_type, character_maximum_length, numeric_precision, numeric_scale) => {
41
- if (MSSQL_DATE_TYPES.includes(data_type)) {
42
- return data_type;
43
- }
44
- if (data_type === 'bit') {
45
- return data_type;
46
- }
47
- if (numeric_precision && numeric_scale && default_type === 'number') {
48
- return `${data_type}(${numeric_precision},${numeric_scale})`;
49
- }
50
- if (character_maximum_length && character_maximum_length > 0 && default_type === 'string') {
51
- return `${data_type}(${character_maximum_length})`;
52
- }
53
- return data_type;
54
- };
55
- const getDbdefault = (data_type, column_default, default_type) => {
56
- // The regex below is used to extract the value from the default value
57
- // \( and \) are used to escape parentheses
58
- // [^()]+ is used to match any character except parentheses
59
- // Example: (1) => 1, ('hello') => hello, getdate()-(1) => getdate()-1
60
- const value = column_default.slice(1, -1).replace(/\(([^()]+)\)/g, '$1');
61
- return {
62
- type: default_type,
63
- value: default_type === 'string' ? value.slice(1, -1) : value // Remove the quotes for string values
64
- };
65
- };
66
-
67
- const getEnumValues = definition => {
68
- // Use the example below to understand the regex:
69
- // ([quantity]>(0))
70
- // ([unit_price]>(0))
71
- // ([status]='cancelled' OR [status]='delivered' OR [status]='shipped' OR [status]='processing' OR [status]='pending')
72
- // ([total_amount]>(0))
73
- // ([price]>(0))
74
- // ([stock_quantity]>=(0))
75
- // ([age_start]<=[age_end])
76
- // ([age_start]<=[age_end])
77
- // ([gender]='Other' OR [gender]='Female' OR [gender]='Male')
78
- // ([date_of_birth]<=dateadd(year,(-13),getdate()))
79
- // ([email] like '%_@_%._%')
80
- if (!definition) return null;
81
- const values = definition.match(/\[([^\]]+)\]='([^']+)'/g); // Extracting the enum values when the definition contains ]='
82
- if (!values) return null;
83
- const enumValues = values.map(value => {
84
- const enumValue = value.split("]='")[1];
85
- return {
86
- name: enumValue.slice(0, -1)
87
- };
88
- });
89
- return enumValues;
90
- };
91
- const generateField = row => {
92
- const {
93
- column_name,
94
- data_type,
95
- character_maximum_length,
96
- numeric_precision,
97
- numeric_scale,
98
- identity_increment,
99
- is_nullable,
100
- column_default,
101
- default_type,
102
- column_comment
103
- } = row;
104
- const dbdefault = column_default && default_type !== 'increment' ? getDbdefault(data_type, column_default, default_type) : null;
105
- const fieldType = {
106
- type_name: getFieldType(data_type, default_type, character_maximum_length, numeric_precision, numeric_scale),
107
- schemaname: null
108
- };
109
- return {
110
- name: column_name,
111
- type: fieldType,
112
- dbdefault,
113
- not_null: !convertQueryBoolean(is_nullable),
114
- increment: !!identity_increment,
115
- note: column_comment ? {
116
- value: column_comment
117
- } : {
118
- value: ''
119
- }
120
- };
121
- };
122
- const generateTablesFieldsAndEnums = async client => {
123
- const fields = {};
124
- const enums = [];
125
- const tablesAndFieldsSql = `
126
- WITH tables_and_fields AS (
127
- SELECT
128
- s.name AS table_schema,
129
- t.name AS table_name,
130
- c.name AS column_name,
131
- ty.name AS data_type,
132
- c.max_length AS character_maximum_length,
133
- c.precision AS numeric_precision,
134
- c.scale AS numeric_scale,
135
- c.is_identity AS identity_increment,
136
- CASE
137
- WHEN c.is_nullable = 1 THEN 'YES'
138
- ELSE 'NO'
139
- END AS is_nullable,
140
- CASE
141
- WHEN c.default_object_id = 0 THEN NULL
142
- ELSE OBJECT_DEFINITION(c.default_object_id)
143
- END AS column_default,
144
- -- Fetching table comments
145
- p.value AS table_comment,
146
- ep.value AS column_comment
147
- FROM
148
- sys.tables t
149
- JOIN
150
- sys.schemas s ON t.schema_id = s.schema_id
151
- JOIN
152
- sys.columns c ON t.object_id = c.object_id
153
- JOIN
154
- sys.types ty ON c.user_type_id = ty.user_type_id
155
- LEFT JOIN
156
- sys.extended_properties p ON p.major_id = t.object_id
157
- AND p.name = 'MS_Description'
158
- AND p.minor_id = 0 -- Ensure minor_id is 0 for table comments
159
- LEFT JOIN
160
- sys.extended_properties ep ON ep.major_id = c.object_id
161
- AND ep.minor_id = c.column_id
162
- AND ep.name = 'MS_Description'
163
- WHERE
164
- t.type = 'U' -- User-defined tables
165
- )
166
- SELECT
167
- tf.table_schema,
168
- tf.table_name,
169
- tf.column_name,
170
- tf.data_type,
171
- tf.character_maximum_length,
172
- tf.numeric_precision,
173
- tf.numeric_scale,
174
- tf.identity_increment,
175
- tf.is_nullable,
176
- tf.column_default,
177
- tf.table_comment,
178
- tf.column_comment,
179
- cc.name AS check_constraint_name, -- Adding CHECK constraint name
180
- cc.definition AS check_constraint_definition, -- Adding CHECK constraint definition
181
- CASE
182
- WHEN tf.column_default LIKE '((%))' THEN 'number'
183
- WHEN tf.column_default LIKE '(''%'')' THEN 'string'
184
- ELSE 'expression'
185
- END AS default_type
186
- FROM
187
- tables_and_fields AS tf
188
- LEFT JOIN
189
- sys.check_constraints cc ON cc.parent_object_id = OBJECT_ID(tf.table_schema + '.' + tf.table_name)
190
- AND cc.definition LIKE '%' + tf.column_name + '%' -- Ensure the constraint references the column
191
- ORDER BY
192
- tf.table_schema,
193
- tf.table_name,
194
- tf.column_name;
195
- `;
196
- const tablesAndFieldsResult = await client.query(tablesAndFieldsSql);
197
- const tables = tablesAndFieldsResult.recordset.reduce((acc, row) => {
198
- const {
199
- table_schema,
200
- table_name,
201
- table_comment,
202
- check_constraint_name,
203
- check_constraint_definition
204
- } = row;
205
- if (!acc[table_name]) {
206
- acc[table_name] = {
207
- name: table_name,
208
- schemaName: table_schema,
209
- note: table_comment ? {
210
- value: table_comment
211
- } : {
212
- value: ''
213
- }
214
- };
215
- }
216
- const enumValues = getEnumValues(check_constraint_definition);
217
- if (enumValues) {
218
- enums.push({
219
- name: check_constraint_name,
220
- schemaName: table_schema,
221
- values: enumValues
222
- });
223
- }
224
- if (!fields[table_name]) fields[table_name] = [];
225
- const field = generateField(row);
226
- if (enumValues) {
227
- field.type = {
228
- type_name: check_constraint_name,
229
- schemaName: table_schema
230
- };
231
- }
232
- fields[table_name].push(field);
233
- return acc;
234
- }, {});
235
- return {
236
- tables: Object.values(tables),
237
- fields,
238
- enums
239
- };
240
- };
241
- const generateRefs = async client => {
242
- const refs = [];
243
- const refsListSql = `
244
- SELECT
245
- s.name AS table_schema,
246
- t.name AS table_name,
247
- fk.name AS fk_constraint_name,
248
- STUFF((
249
- SELECT ',' + c1.name
250
- FROM sys.foreign_key_columns AS fkc
251
- JOIN sys.columns AS c1 ON fkc.parent_object_id = c1.object_id AND fkc.parent_column_id = c1.column_id
252
- WHERE fkc.constraint_object_id = fk.object_id
253
- FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '') AS column_names,
254
- s2.name AS foreign_table_schema,
255
- t2.name AS foreign_table_name,
256
- STUFF((
257
- SELECT ',' + c2.name
258
- FROM sys.foreign_key_columns AS fkc
259
- JOIN sys.columns AS c2 ON fkc.referenced_object_id = c2.object_id AND fkc.referenced_column_id = c2.column_id
260
- WHERE fkc.constraint_object_id = fk.object_id
261
- FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '') AS foreign_column_names,
262
- fk.type_desc AS constraint_type,
263
- fk.delete_referential_action_desc AS on_delete,
264
- fk.update_referential_action_desc AS on_update
265
- FROM sys.foreign_keys AS fk
266
- JOIN sys.tables AS t ON fk.parent_object_id = t.object_id
267
- JOIN sys.schemas AS s ON t.schema_id = s.schema_id
268
- JOIN sys.tables AS t2 ON fk.referenced_object_id = t2.object_id
269
- JOIN sys.schemas AS s2 ON t2.schema_id = s2.schema_id
270
- WHERE s.name NOT IN ('sys', 'information_schema')
271
- ORDER BY
272
- s.name,
273
- t.name;
274
- `;
275
- const refsQueryResult = await client.query(refsListSql);
276
- refsQueryResult.recordset.forEach(refRow => {
277
- const {
278
- table_schema,
279
- fk_constraint_name,
280
- table_name,
281
- column_names,
282
- foreign_table_schema,
283
- foreign_table_name,
284
- foreign_column_names,
285
- on_delete,
286
- on_update
287
- } = refRow;
288
- const ep1 = {
289
- tableName: table_name,
290
- schemaName: table_schema,
291
- fieldNames: column_names.split(','),
292
- relation: '*'
293
- };
294
- const ep2 = {
295
- tableName: foreign_table_name,
296
- schemaName: foreign_table_schema,
297
- fieldNames: foreign_column_names.split(','),
298
- relation: '1'
299
- };
300
- refs.push({
301
- name: fk_constraint_name,
302
- endpoints: [ep1, ep2],
303
- onDelete: on_delete === 'NO_ACTION' ? null : on_delete,
304
- onUpdate: on_update === 'NO_ACTION' ? null : on_update
305
- });
306
- });
307
- return refs;
308
- };
309
- const generateIndexes = async client => {
310
- const indexListSql = `
311
- WITH user_tables AS (
312
- SELECT
313
- TABLE_NAME
314
- FROM
315
- INFORMATION_SCHEMA.TABLES
316
- WHERE
317
- TABLE_SCHEMA = 'dbo'
318
- AND TABLE_TYPE = 'BASE TABLE' -- Ensure we are only getting base tables
319
- AND TABLE_NAME NOT LIKE 'dt%'
320
- AND TABLE_NAME NOT LIKE 'syscs%'
321
- AND TABLE_NAME NOT LIKE 'sysss%'
322
- AND TABLE_NAME NOT LIKE 'sysrs%'
323
- AND TABLE_NAME NOT LIKE 'sysxlgc%'
324
- ),
325
- index_info AS (
326
- SELECT
327
- OBJECT_NAME(i.object_id) AS table_name,
328
- i.name AS index_name,
329
- i.is_unique,
330
- CASE
331
- WHEN i.type = 1 THEN 1
332
- ELSE 0
333
- END AS is_primary,
334
- i.type_desc AS index_type,
335
- STUFF((
336
- SELECT
337
- ', ' + c.name
338
- FROM
339
- sys.index_columns ic
340
- JOIN sys.columns c ON ic.column_id = c.column_id AND ic.object_id = c.object_id
341
- WHERE
342
- ic.index_id = i.index_id
343
- AND ic.object_id = i.object_id
344
- AND OBJECT_NAME(ic.object_id) IN (SELECT TABLE_NAME FROM user_tables) -- Filter for user tables
345
- ORDER BY
346
- ic.key_ordinal
347
- FOR XML PATH('')
348
- ), 1, 2, '') AS columns,
349
- CASE
350
- WHEN i.type = 1 THEN 'PRIMARY KEY'
351
- WHEN i.is_unique = 1 THEN 'UNIQUE'
352
- ELSE NULL
353
- END AS constraint_type
354
- FROM
355
- sys.indexes i
356
- JOIN sys.tables t ON i.object_id = t.object_id
357
- WHERE
358
- t.is_ms_shipped = 0
359
- AND i.type <> 0
360
- )
361
- SELECT
362
- ut.TABLE_NAME AS table_name,
363
- ii.index_name,
364
- ii.is_unique,
365
- ii.is_primary,
366
- ii.index_type,
367
- ii.columns,
368
- ii.constraint_type
369
- FROM
370
- user_tables ut
371
- LEFT JOIN
372
- index_info ii ON ut.TABLE_NAME = ii.table_name
373
- WHERE
374
- ii.columns IS NOT NULL
375
- ORDER BY
376
- ut.TABLE_NAME,
377
- ii.constraint_type,
378
- ii.index_name;
379
- `;
380
- const indexListResult = await client.query(indexListSql);
381
- const {
382
- outOfLineConstraints,
383
- inlineConstraints
384
- } = indexListResult.recordset.reduce((acc, row) => {
385
- const {
386
- constraint_type,
387
- columns
388
- } = row;
389
- if (columns === 'null' || columns.trim() === '') return acc;
390
- if (constraint_type === 'PRIMARY KEY' || constraint_type === 'UNIQUE') {
391
- acc.inlineConstraints.push(row);
392
- } else {
393
- acc.outOfLineConstraints.push(row);
394
- }
395
- return acc;
396
- }, {
397
- outOfLineConstraints: [],
398
- inlineConstraints: []
399
- });
400
- const indexes = outOfLineConstraints.reduce((acc, indexRow) => {
401
- const {
402
- table_name,
403
- index_name,
404
- index_type,
405
- columns,
406
- expressions
407
- } = indexRow;
408
- const indexColumns = columns.split(',').map(column => {
409
- return {
410
- type: 'column',
411
- value: column.trim()
412
- };
413
- });
414
- const indexExpressions = expressions ? expressions.split(',').map(expression => {
415
- return {
416
- type: 'expression',
417
- value: expression
418
- };
419
- }) : [];
420
- const index = {
421
- name: index_name,
422
- type: index_type,
423
- columns: [...indexColumns, ...indexExpressions]
424
- };
425
- if (acc[table_name]) {
426
- acc[table_name].push(index);
427
- } else {
428
- acc[table_name] = [index];
429
- }
430
- return acc;
431
- }, {});
432
- const tableConstraints = inlineConstraints.reduce((acc, row) => {
433
- const {
434
- table_name,
435
- columns,
436
- constraint_type
437
- } = row;
438
- if (!acc[table_name]) acc[table_name] = {};
439
- const columnNames = columns.split(',').map(column => column.trim());
440
- columnNames.forEach(columnName => {
441
- if (!acc[table_name][columnName]) acc[table_name][columnName] = {};
442
- if (constraint_type === 'PRIMARY KEY') {
443
- acc[table_name][columnName].pk = true;
444
- }
445
- if (constraint_type === 'UNIQUE' && !acc[table_name][columnName].pk) {
446
- acc[table_name][columnName].unique = true;
447
- }
448
- });
449
- return acc;
450
- }, {});
451
- return {
452
- indexes,
453
- tableConstraints
454
- };
455
- };
456
- const fetchSchemaJson = async connection => {
457
- const client = await connect(connection);
458
- if (!client) throw new Error('Failed to connect to the database');
459
- const tablesFieldsAndEnumsRes = generateTablesFieldsAndEnums(client);
460
- const indexesRes = generateIndexes(client);
461
- const refsRes = generateRefs(client);
462
- const res = await Promise.all([tablesFieldsAndEnumsRes, indexesRes, refsRes]);
463
- client.close();
464
- const {
465
- tables,
466
- fields,
467
- enums
468
- } = res[0];
469
- const {
470
- indexes,
471
- tableConstraints
472
- } = res[1];
473
- const refs = res[2];
474
- return {
475
- tables,
476
- fields,
477
- enums,
478
- refs,
479
- indexes,
480
- tableConstraints
481
- };
482
- };
483
- exports.fetchSchemaJson = fetchSchemaJson;
@@ -1,450 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.fetchSchemaJson = void 0;
7
- var _pg = require("pg");
8
- /* eslint-disable camelcase */
9
-
10
- const connectPg = async connection => {
11
- const client = new _pg.Client(connection);
12
- // bearer:disable javascript_lang_logger
13
- client.on('error', err => console.log('PG connection error:', err));
14
- await client.connect();
15
- return client;
16
- };
17
- const convertQueryBoolean = val => val === 'YES';
18
- const getFieldType = (data_type, udt_name, character_maximum_length, numeric_precision, numeric_scale) => {
19
- if (data_type === 'ARRAY') {
20
- return `${udt_name.slice(1, udt_name.length)}[]`;
21
- }
22
- if (character_maximum_length) {
23
- return `${udt_name}(${character_maximum_length})`;
24
- }
25
- if (numeric_precision && numeric_scale) {
26
- return `${udt_name}(${numeric_precision},${numeric_scale})`;
27
- }
28
- return udt_name;
29
- };
30
- const getDbdefault = (data_type, column_default, default_type) => {
31
- if (data_type === 'ARRAY') {
32
- const values = column_default.slice(6, -1).split(',').map(value => {
33
- return value.split('::')[0];
34
- });
35
- return {
36
- type: default_type,
37
- value: `ARRAY[${values.join(', ')}]`
38
- };
39
- }
40
- if (default_type === 'string') {
41
- const defaultValues = column_default.split('::')[0];
42
- const isJson = data_type === 'json' || data_type === 'jsonb';
43
- const type = isJson ? 'expression' : 'string';
44
- return {
45
- type,
46
- value: defaultValues.slice(1, -1)
47
- };
48
- }
49
- return {
50
- type: default_type,
51
- value: column_default
52
- };
53
- };
54
- const generateField = row => {
55
- const {
56
- column_name,
57
- data_type,
58
- character_maximum_length,
59
- numeric_precision,
60
- numeric_scale,
61
- udt_schema,
62
- udt_name,
63
- identity_increment,
64
- is_nullable,
65
- column_default,
66
- default_type,
67
- column_comment
68
- } = row;
69
- const dbdefault = column_default && default_type !== 'increment' ? getDbdefault(data_type, column_default, default_type) : null;
70
- const fieldType = data_type === 'USER-DEFINED' ? {
71
- type_name: udt_name,
72
- schemaName: udt_schema
73
- } : {
74
- type_name: getFieldType(data_type, udt_name, character_maximum_length, numeric_precision, numeric_scale),
75
- schemaname: null
76
- };
77
- return {
78
- name: column_name,
79
- type: fieldType,
80
- dbdefault,
81
- not_null: !convertQueryBoolean(is_nullable),
82
- increment: !!identity_increment || default_type === 'increment',
83
- note: column_comment ? {
84
- value: column_comment
85
- } : {
86
- value: ''
87
- }
88
- };
89
- };
90
- const generateTablesAndFields = async client => {
91
- const fields = {};
92
- const tablesAndFieldsSql = `
93
- WITH comments AS (
94
- SELECT
95
- pc.relname AS table_name,
96
- pn.nspname AS table_schema,
97
- pa.attname AS column_name,
98
- pd.description
99
- FROM
100
- pg_description pd
101
- JOIN
102
- pg_class pc ON pd.objoid = pc.oid
103
- JOIN
104
- pg_namespace pn ON pc.relnamespace = pn.oid
105
- LEFT JOIN
106
- pg_attribute pa ON pd.objoid = pa.attrelid AND pd.objsubid = pa.attnum
107
- WHERE
108
- pc.relkind = 'r'
109
- AND pn.nspname NOT IN ('pg_catalog', 'information_schema')
110
- )
111
- SELECT
112
- t.table_schema,
113
- t.table_name,
114
- c.column_name,
115
- c.data_type,
116
- c.character_maximum_length,
117
- c.numeric_precision,
118
- c.numeric_scale,
119
- c.udt_schema,
120
- c.udt_name,
121
- c.identity_increment,
122
- c.is_nullable,
123
- c.column_default,
124
- CASE
125
- WHEN c.column_default IS NULL THEN NULL
126
- WHEN c.column_default LIKE 'nextval(%' THEN 'increment'
127
- WHEN c.column_default LIKE '''%' THEN 'string'
128
- WHEN c.column_default = 'true' OR c.column_default = 'false' THEN 'boolean'
129
- WHEN c.column_default ~ '^-?[0-9]+(.[0-9]+)?$' THEN 'number'
130
- ELSE 'expression'
131
- END AS default_type,
132
- (SELECT description FROM comments WHERE table_name = t.table_name AND table_schema = t.table_schema AND column_name IS NULL) AS table_comment,
133
- (SELECT description FROM comments WHERE table_name = t.table_name AND table_schema = t.table_schema AND column_name = c.column_name) AS column_comment
134
- FROM
135
- information_schema.columns c
136
- JOIN
137
- information_schema.tables t ON c.table_name = t.table_name AND c.table_schema = t.table_schema
138
- WHERE
139
- t.table_type = 'BASE TABLE'
140
- AND t.table_schema NOT IN ('pg_catalog', 'information_schema')
141
- ORDER BY
142
- t.table_schema,
143
- t.table_name,
144
- c.ordinal_position
145
- ;
146
- `;
147
- const tablesAndFieldsResult = await client.query(tablesAndFieldsSql);
148
- const tables = tablesAndFieldsResult.rows.reduce((acc, row) => {
149
- const {
150
- table_schema,
151
- table_name,
152
- table_comment
153
- } = row;
154
- if (!acc[table_name]) {
155
- acc[table_name] = {
156
- name: table_name,
157
- schemaName: table_schema,
158
- note: table_comment ? {
159
- value: table_comment
160
- } : {
161
- value: ''
162
- }
163
- };
164
- }
165
- if (!fields[table_name]) fields[table_name] = [];
166
- const field = generateField(row);
167
- fields[table_name].push(field);
168
- return acc;
169
- }, {});
170
- return {
171
- tables: Object.values(tables),
172
- fields
173
- };
174
- };
175
- const generateRawRefs = async client => {
176
- const refs = [];
177
- const refsListSql = `
178
- SELECT
179
- tc.table_schema,
180
- tc.table_name,
181
- tc.constraint_name as fk_constraint_name,
182
- STRING_AGG(DISTINCT kcu.column_name, ',') AS column_names,
183
- ccu.table_schema AS foreign_table_schema,
184
- ccu.table_name AS foreign_table_name,
185
- STRING_AGG(DISTINCT ccu.column_name, ',') AS foreign_column_names,
186
- tc.constraint_type,
187
- rc.delete_rule AS on_delete,
188
- rc.update_rule AS on_update
189
- FROM information_schema.table_constraints AS tc
190
- JOIN information_schema.key_column_usage AS kcu
191
- ON tc.constraint_name = kcu.constraint_name
192
- AND tc.table_schema = kcu.table_schema
193
- JOIN information_schema.constraint_column_usage AS ccu
194
- ON ccu.constraint_name = tc.constraint_name
195
- JOIN information_schema.referential_constraints AS rc
196
- ON tc.constraint_name = rc.constraint_name
197
- AND tc.table_schema = rc.constraint_schema
198
- WHERE tc.constraint_type = 'FOREIGN KEY'
199
- AND tc.table_schema NOT IN ('pg_catalog', 'information_schema')
200
- GROUP BY
201
- tc.table_schema,
202
- tc.table_name,
203
- tc.constraint_name,
204
- ccu.table_schema,
205
- ccu.table_name,
206
- tc.constraint_type,
207
- rc.delete_rule,
208
- rc.update_rule
209
- ORDER BY
210
- tc.table_schema,
211
- tc.table_name;
212
- `;
213
- const refsQueryResult = await client.query(refsListSql);
214
- refsQueryResult.rows.forEach(refRow => {
215
- const {
216
- table_schema,
217
- fk_constraint_name,
218
- table_name,
219
- column_names,
220
- foreign_table_schema,
221
- foreign_table_name,
222
- foreign_column_names,
223
- on_delete,
224
- on_update
225
- } = refRow;
226
- const ep1 = {
227
- tableName: table_name,
228
- schemaName: table_schema,
229
- fieldNames: column_names.split(','),
230
- relation: '*'
231
- };
232
- const ep2 = {
233
- tableName: foreign_table_name,
234
- schemaName: foreign_table_schema,
235
- fieldNames: foreign_column_names.split(','),
236
- relation: '1'
237
- };
238
- refs.push({
239
- name: fk_constraint_name,
240
- endpoints: [ep1, ep2],
241
- onDelete: on_delete === 'NO ACTION' ? null : on_delete,
242
- onUpdate: on_update === 'NO ACTION' ? null : on_update
243
- });
244
- });
245
- return refs;
246
- };
247
- const generateIndexes = async client => {
248
- // const tableConstraints = {};
249
- const indexListSql = `
250
- WITH user_tables AS (
251
- SELECT tablename
252
- FROM pg_tables
253
- WHERE schemaname NOT IN ('pg_catalog', 'information_schema') -- Exclude system schemas
254
- AND tablename NOT LIKE 'pg_%' -- Exclude PostgreSQL system tables
255
- AND tablename NOT LIKE 'sql_%' -- Exclude SQL standard tables
256
- ),
257
- index_info AS (
258
- SELECT
259
- t.relname AS table_name,
260
- i.relname AS index_name,
261
- ix.indisunique AS is_unique,
262
- ix.indisprimary AS is_primary,
263
- am.amname AS index_type,
264
- array_to_string(array_agg(a.attname ORDER BY x.n), ', ') AS columns,
265
- pg_get_expr(ix.indexprs, ix.indrelid) AS expressions,
266
- CASE
267
- WHEN ix.indisprimary THEN 'PRIMARY KEY'
268
- WHEN ix.indisunique THEN 'UNIQUE'
269
- ELSE NULL
270
- END AS constraint_type
271
- FROM
272
- pg_class t
273
- JOIN pg_index ix ON t.oid = ix.indrelid
274
- JOIN pg_class i ON i.oid = ix.indexrelid
275
- LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(ix.indkey)
276
- JOIN pg_am am ON i.relam = am.oid
277
- LEFT JOIN generate_subscripts(ix.indkey, 1) AS x(n) ON a.attnum = ix.indkey[x.n]
278
- WHERE
279
- t.relkind = 'r'
280
- AND t.relname NOT LIKE 'pg_%'
281
- AND t.relname NOT LIKE 'sql_%'
282
- GROUP BY
283
- t.relname, i.relname, ix.indisunique, ix.indisprimary, am.amname, ix.indexprs, ix.indrelid
284
- )
285
- SELECT
286
- ut.tablename AS table_name,
287
- ii.index_name,
288
- ii.is_unique,
289
- ii.is_primary,
290
- ii.index_type,
291
- ii.columns,
292
- ii.expressions,
293
- ii.constraint_type -- Added constraint type
294
- FROM
295
- user_tables ut
296
- LEFT JOIN
297
- index_info ii ON ut.tablename = ii.table_name
298
- WHERE ii.columns IS NOT NULL
299
- ORDER BY
300
- ut.tablename,
301
- ii.constraint_type,
302
- ii.index_name
303
- ;
304
- `;
305
- const indexListResult = await client.query(indexListSql);
306
- const {
307
- outOfLineConstraints,
308
- inlineConstraints
309
- } = indexListResult.rows.reduce((acc, row) => {
310
- const {
311
- constraint_type,
312
- columns
313
- } = row;
314
- if (columns === 'null' || columns.trim() === '') return acc;
315
- const isSingleColumn = columns.split(',').length === 1;
316
- const isInlineConstraint = isSingleColumn && (constraint_type === 'PRIMARY KEY' || constraint_type === 'UNIQUE');
317
- if (isInlineConstraint) {
318
- acc.inlineConstraints.push(row);
319
- } else {
320
- acc.outOfLineConstraints.push(row);
321
- }
322
- return acc;
323
- }, {
324
- outOfLineConstraints: [],
325
- inlineConstraints: []
326
- });
327
- const indexes = outOfLineConstraints.reduce((acc, indexRow) => {
328
- const {
329
- table_name,
330
- index_name,
331
- index_type,
332
- columns,
333
- expressions
334
- } = indexRow;
335
- const indexColumns = columns.split(',').map(column => {
336
- return {
337
- type: 'column',
338
- value: column.trim()
339
- };
340
- });
341
- const indexExpressions = expressions ? expressions.split(',').map(expression => {
342
- return {
343
- type: 'expression',
344
- value: expression
345
- };
346
- }) : [];
347
- const index = {
348
- name: index_name,
349
- type: index_type,
350
- columns: [...indexColumns, ...indexExpressions]
351
- };
352
- if (acc[table_name]) {
353
- acc[table_name].push(index);
354
- } else {
355
- acc[table_name] = [index];
356
- }
357
- return acc;
358
- }, {});
359
- const tableConstraints = inlineConstraints.reduce((acc, row) => {
360
- const {
361
- table_name,
362
- columns,
363
- constraint_type
364
- } = row;
365
- if (!acc[table_name]) acc[table_name] = {};
366
- const columnNames = columns.split(',').map(column => column.trim());
367
- columnNames.forEach(columnName => {
368
- if (!acc[table_name][columnName]) acc[table_name][columnName] = {};
369
- if (constraint_type === 'PRIMARY KEY') {
370
- acc[table_name][columnName].pk = true;
371
- }
372
- if (constraint_type === 'UNIQUE' && !acc[table_name][columnName].pk) {
373
- acc[table_name][columnName].unique = true;
374
- }
375
- });
376
- return acc;
377
- }, {});
378
- return {
379
- indexes,
380
- tableConstraints
381
- };
382
- };
383
- const generateRawEnums = async client => {
384
- const enumListSql = `
385
- SELECT
386
- n.nspname AS schema_name,
387
- t.typname AS enum_type,
388
- e.enumlabel AS enum_value,
389
- e.enumsortorder AS sort_order
390
- FROM
391
- pg_enum e
392
- JOIN
393
- pg_type t ON e.enumtypid = t.oid
394
- JOIN
395
- pg_namespace n ON t.typnamespace = n.oid
396
- ORDER BY
397
- schema_name,
398
- enum_type,
399
- sort_order;
400
- ;
401
- `;
402
- const enumListResult = await client.query(enumListSql);
403
- const enums = enumListResult.rows.reduce((acc, row) => {
404
- const {
405
- schema_name,
406
- enum_type,
407
- enum_value
408
- } = row;
409
- if (!acc[enum_type]) {
410
- acc[enum_type] = {
411
- name: enum_type,
412
- schemaName: schema_name,
413
- values: []
414
- };
415
- }
416
- acc[enum_type].values.push({
417
- name: enum_value
418
- });
419
- return acc;
420
- }, {});
421
- return Object.values(enums);
422
- };
423
- const fetchSchemaJson = async connection => {
424
- const client = await connectPg(connection);
425
- const tablesAndFieldsRes = generateTablesAndFields(client);
426
- const indexesRes = generateIndexes(client);
427
- const refsRes = generateRawRefs(client);
428
- const enumsRes = generateRawEnums(client);
429
- const res = await Promise.all([tablesAndFieldsRes, indexesRes, refsRes, enumsRes]);
430
- client.end();
431
- const {
432
- tables,
433
- fields
434
- } = res[0];
435
- const {
436
- indexes,
437
- tableConstraints
438
- } = res[1];
439
- const refs = res[2];
440
- const enums = res[3];
441
- return {
442
- tables,
443
- fields,
444
- refs,
445
- enums,
446
- indexes,
447
- tableConstraints
448
- };
449
- };
450
- exports.fetchSchemaJson = fetchSchemaJson;