@dbml/connector 5.5.1-alpha.0 → 5.6.0-alpha.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.
@@ -1,517 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.fetchSchemaJson = void 0;
4
- const pg_1 = require("pg");
5
- const parseSchema_1 = require("../../utils/parseSchema");
6
- const getValidatedClient = async (connection) => {
7
- const client = new pg_1.Client(connection);
8
- try {
9
- // Connect to the PostgreSQL server
10
- await client.connect();
11
- // Validate if the connection is successful by making a simple query
12
- await client.query('SELECT 1');
13
- // If successful, return the client
14
- return client;
15
- }
16
- catch (err) {
17
- // Ensure to close the client in case of failure
18
- await client.end();
19
- if (err instanceof Error) {
20
- throw new Error(`PostgreSQL connection error: ${err}`);
21
- }
22
- throw err; // Rethrow error if you want the calling code to handle it
23
- }
24
- };
25
- const convertQueryBoolean = (val) => val === 'YES';
26
- const getFieldType = (data_type, udt_name, character_maximum_length, numeric_precision, numeric_scale) => {
27
- if (data_type === 'ARRAY') {
28
- return `${udt_name.slice(1, udt_name.length)}[]`;
29
- }
30
- if (character_maximum_length) {
31
- return `${udt_name}(${character_maximum_length})`;
32
- }
33
- if (numeric_precision && numeric_scale) {
34
- return `${udt_name}(${numeric_precision},${numeric_scale})`;
35
- }
36
- return udt_name;
37
- };
38
- const getDbdefault = (data_type, column_default, default_type) => {
39
- if (data_type === 'ARRAY') {
40
- const values = column_default.slice(6, -1).split(',').map((value) => {
41
- return value.split('::')[0];
42
- });
43
- return {
44
- type: default_type,
45
- value: `ARRAY[${values.join(', ')}]`,
46
- };
47
- }
48
- if (default_type === 'string') {
49
- const defaultValues = column_default.split('::')[0];
50
- const isJson = data_type === 'json' || data_type === 'jsonb';
51
- const type = isJson ? 'expression' : 'string';
52
- return {
53
- type,
54
- value: defaultValues.slice(1, -1),
55
- };
56
- }
57
- return {
58
- type: default_type,
59
- value: column_default,
60
- };
61
- };
62
- const generateField = (row) => {
63
- const { column_name, data_type, character_maximum_length, numeric_precision, numeric_scale, udt_schema, udt_name, identity_increment, is_nullable, column_default, default_type, column_comment, } = row;
64
- const dbdefault = column_default && default_type !== 'increment' ? getDbdefault(data_type, column_default, default_type) : null;
65
- const fieldType = data_type === 'USER-DEFINED'
66
- ? {
67
- type_name: udt_name,
68
- schemaName: udt_schema,
69
- }
70
- : {
71
- type_name: getFieldType(data_type, udt_name, character_maximum_length, numeric_precision, numeric_scale),
72
- schemaName: null,
73
- };
74
- return {
75
- name: column_name,
76
- type: fieldType,
77
- dbdefault,
78
- not_null: !convertQueryBoolean(is_nullable),
79
- increment: !!identity_increment || default_type === 'increment',
80
- note: column_comment ? { value: column_comment } : { value: '' },
81
- };
82
- };
83
- const generateTablesAndFields = async (client, schemas) => {
84
- const fields = {};
85
- const tablesAndFieldsSql = `
86
- WITH
87
- comments AS (
88
- SELECT DISTINCT ON (pc.relname, pn.nspname, pa.attname)
89
- pc.relname AS table_name,
90
- pn.nspname AS table_schema,
91
- pa.attname AS column_name,
92
- pd.description
93
- FROM
94
- pg_description pd
95
- JOIN
96
- pg_class pc ON pd.objoid = pc.oid
97
- JOIN
98
- pg_namespace pn ON pc.relnamespace = pn.oid
99
- LEFT JOIN
100
- pg_attribute pa ON pd.objoid = pa.attrelid AND pd.objsubid = pa.attnum
101
- WHERE
102
- pc.relkind = 'r'
103
- AND pn.nspname NOT IN ('pg_catalog', 'information_schema')
104
- ),
105
- table_positions AS (
106
- SELECT
107
- pc.oid as table_ordinal_position,
108
- pc.relname as table_name,
109
- pn.nspname as table_schema
110
- FROM pg_class pc
111
- JOIN pg_namespace pn
112
- ON pc.relnamespace = pn.oid
113
- WHERE
114
- -- r is ordinary table
115
- -- see https://www.postgresql.org/docs/14/catalog-pg-class.html#:~:text=%3D%20temporary%20table-,relkind,-char
116
- pc.relkind = 'r'
117
- AND pn.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
118
- ${(0, parseSchema_1.buildSchemaQuery)('pn.nspname', schemas)}
119
- )
120
- SELECT
121
- t.table_schema,
122
- t.table_name,
123
- c.column_name,
124
- c.data_type,
125
- c.character_maximum_length,
126
- c.numeric_precision,
127
- c.numeric_scale,
128
- c.udt_schema,
129
- c.udt_name,
130
- c.identity_increment,
131
- c.is_nullable,
132
- c.column_default,
133
- c.ordinal_position,
134
- CASE
135
- WHEN c.column_default IS NULL THEN NULL
136
- WHEN c.column_default LIKE 'nextval(%' THEN 'increment'
137
- WHEN c.column_default LIKE '''%' THEN 'string'
138
- WHEN c.column_default = 'true' OR c.column_default = 'false' THEN 'boolean'
139
- WHEN c.column_default ~ '^-?[0-9]+(.[0-9]+)?$' THEN 'number'
140
- ELSE 'expression'
141
- END AS default_type,
142
- (SELECT description FROM comments WHERE table_name = t.table_name AND table_schema = t.table_schema AND column_name IS NULL LIMIT 1) AS table_comment,
143
- (SELECT description FROM comments WHERE table_name = t.table_name AND table_schema = t.table_schema AND column_name = c.column_name LIMIT 1) AS column_comment
144
- FROM
145
- information_schema.columns c
146
- JOIN
147
- information_schema.tables t ON c.table_name = t.table_name AND c.table_schema = t.table_schema
148
- JOIN
149
- table_positions tp ON tp.table_name = t.table_name AND tp.table_schema = t.table_schema
150
- WHERE
151
- t.table_type = 'BASE TABLE'
152
- AND t.table_schema NOT IN ('pg_catalog', 'information_schema')
153
- ${(0, parseSchema_1.buildSchemaQuery)('t.table_schema', schemas)}
154
- ORDER BY
155
- t.table_schema,
156
- tp.table_ordinal_position,
157
- c.ordinal_position
158
- ;
159
- `;
160
- const tablesAndFieldsResult = await client.query(tablesAndFieldsSql);
161
- const tables = tablesAndFieldsResult.rows.reduce((acc, row) => {
162
- const { table_schema, table_name, table_comment } = row;
163
- const key = `${table_schema}.${table_name}`;
164
- if (!acc[key]) {
165
- acc[key] = {
166
- name: table_name,
167
- schemaName: table_schema,
168
- note: table_comment ? { value: table_comment } : { value: '' },
169
- };
170
- }
171
- if (!fields[key])
172
- fields[key] = [];
173
- const field = generateField(row);
174
- fields[key].push(field);
175
- return acc;
176
- }, {});
177
- return {
178
- tables: Object.values(tables),
179
- fields,
180
- };
181
- };
182
- const generateRawRefs = async (client, schemas) => {
183
- const refs = [];
184
- const refsListSql = `
185
- SELECT
186
- tc.table_schema,
187
- tc.table_name,
188
- tc.constraint_name as fk_constraint_name,
189
- STRING_AGG(DISTINCT kcu.column_name, ',') AS column_names,
190
- ccu.table_schema AS foreign_table_schema,
191
- ccu.table_name AS foreign_table_name,
192
- STRING_AGG(DISTINCT ccu.column_name, ',') AS foreign_column_names,
193
- tc.constraint_type,
194
- rc.delete_rule AS on_delete,
195
- rc.update_rule AS on_update
196
- FROM information_schema.table_constraints AS tc
197
- JOIN information_schema.key_column_usage AS kcu
198
- ON tc.constraint_name = kcu.constraint_name
199
- AND tc.table_schema = kcu.table_schema
200
- JOIN information_schema.constraint_column_usage AS ccu
201
- ON ccu.constraint_name = tc.constraint_name
202
- JOIN information_schema.referential_constraints AS rc
203
- ON tc.constraint_name = rc.constraint_name
204
- AND tc.table_schema = rc.constraint_schema
205
- WHERE tc.constraint_type = 'FOREIGN KEY'
206
- AND tc.table_schema NOT IN ('pg_catalog', 'information_schema')
207
- ${(0, parseSchema_1.buildSchemaQuery)('tc.table_schema', schemas)}
208
- GROUP BY
209
- tc.table_schema,
210
- tc.table_name,
211
- tc.constraint_name,
212
- ccu.table_schema,
213
- ccu.table_name,
214
- tc.constraint_type,
215
- rc.delete_rule,
216
- rc.update_rule
217
- ORDER BY
218
- tc.table_schema,
219
- tc.table_name;
220
- `;
221
- const refsQueryResult = await client.query(refsListSql);
222
- refsQueryResult.rows.forEach((refRow) => {
223
- const { table_schema, fk_constraint_name, table_name, column_names, foreign_table_schema, foreign_table_name, foreign_column_names, on_delete, on_update, } = refRow;
224
- const ep1 = {
225
- tableName: table_name,
226
- schemaName: table_schema,
227
- fieldNames: column_names.split(','),
228
- relation: '*',
229
- };
230
- const ep2 = {
231
- tableName: foreign_table_name,
232
- schemaName: foreign_table_schema,
233
- fieldNames: foreign_column_names.split(','),
234
- relation: '1',
235
- };
236
- refs.push({
237
- name: fk_constraint_name,
238
- endpoints: [ep1, ep2],
239
- onDelete: on_delete === 'NO ACTION' ? null : on_delete,
240
- onUpdate: on_update === 'NO ACTION' ? null : on_update,
241
- });
242
- });
243
- return refs;
244
- };
245
- const generateIndexesAndConstraints = async (client, schemas) => {
246
- const indexListSql = `
247
- WITH user_tables AS (
248
- SELECT
249
- schemaname AS tableschema,
250
- tablename
251
- FROM pg_tables
252
- WHERE schemaname NOT IN ('pg_catalog', 'information_schema') -- Exclude system schemas
253
- AND tablename NOT LIKE 'pg_%' -- Exclude PostgreSQL system tables
254
- AND tablename NOT LIKE 'sql_%' -- Exclude SQL standard tables
255
- ),
256
- index_info AS (
257
- SELECT
258
- t.relnamespace::regnamespace::text AS table_schema,
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.relnamespace, t.relname, i.relname, ix.indisunique, ix.indisprimary, am.amname, ix.indexprs, ix.indrelid
284
- )
285
- SELECT
286
- ut.tableschema AS table_schema,
287
- ut.tablename AS table_name,
288
- ii.index_name,
289
- ii.is_unique,
290
- ii.is_primary,
291
- ii.index_type,
292
- ii.columns AS index_columns,
293
- ii.expressions AS index_expressions,
294
- ii.constraint_type -- Retained constraint type here
295
- FROM
296
- user_tables ut
297
- LEFT JOIN
298
- index_info ii ON ut.tableschema = ii.table_schema AND ut.tablename = ii.table_name
299
- WHERE ii.columns IS NOT NULL
300
- ${(0, parseSchema_1.buildSchemaQuery)('ut.tableschema', schemas)}
301
- ORDER BY
302
- ut.tablename,
303
- ii.constraint_type,
304
- ii.index_name
305
- ;
306
- `;
307
- const indexListResult = await client.query(indexListSql);
308
- const { outOfLineIndexes, inlineIndexes } = indexListResult.rows.reduce((acc, row) => {
309
- const { constraint_type, index_columns } = row;
310
- if (index_columns === 'null' || index_columns.trim() === '')
311
- return acc;
312
- const isSingleColumn = index_columns.split(',').length === 1;
313
- const isInlineIndex = isSingleColumn && (constraint_type === 'PRIMARY KEY' || constraint_type === 'UNIQUE');
314
- if (isInlineIndex) {
315
- acc.inlineIndexes.push(row);
316
- }
317
- else {
318
- acc.outOfLineIndexes.push(row);
319
- }
320
- return acc;
321
- }, { outOfLineIndexes: [], inlineIndexes: [] });
322
- const indexes = outOfLineIndexes.reduce((acc, indexRow) => {
323
- const { table_schema, table_name, index_name, index_type, index_columns, index_expressions, is_primary, is_unique, } = indexRow;
324
- const indexColumns = index_columns.split(',').map((column) => {
325
- return {
326
- type: 'column',
327
- value: column.trim(),
328
- };
329
- });
330
- const indexExpressions = index_expressions
331
- ? index_expressions.split(',').map((expression) => {
332
- return {
333
- type: 'expression',
334
- value: expression,
335
- };
336
- })
337
- : [];
338
- const index = {
339
- name: index_name,
340
- type: index_type,
341
- columns: [
342
- ...indexColumns,
343
- ...indexExpressions,
344
- ],
345
- pk: !!is_primary,
346
- unique: !is_primary && !!is_unique,
347
- };
348
- const key = `${table_schema}.${table_name}`;
349
- if (acc[key]) {
350
- acc[key].push(index);
351
- }
352
- else {
353
- acc[key] = [index];
354
- }
355
- return acc;
356
- }, {});
357
- const tableConstraints = {};
358
- inlineIndexes.forEach((row) => {
359
- const { table_schema, table_name, index_columns, constraint_type, } = row;
360
- const key = `${table_schema}.${table_name}`;
361
- if (!tableConstraints[key])
362
- tableConstraints[key] = {};
363
- const columnNames = index_columns.split(',').map((column) => column.trim());
364
- columnNames.forEach((columnName) => {
365
- if (!tableConstraints[key][columnName])
366
- tableConstraints[key][columnName] = { checks: [] };
367
- if (constraint_type === 'PRIMARY KEY') {
368
- tableConstraints[key][columnName].pk = true;
369
- }
370
- if (constraint_type === 'UNIQUE' && !tableConstraints[key][columnName].pk) {
371
- tableConstraints[key][columnName].unique = true;
372
- }
373
- });
374
- }, {});
375
- const checkListSql = `
376
- SELECT
377
- n.nspname AS table_schema,
378
- c.relname AS table_name,
379
- MAX(a.attname) AS column_name,
380
- con.conname AS check_name,
381
- pg_get_constraintdef(con.oid) AS check_definition,
382
- CASE
383
- WHEN COUNT(*) = 1 THEN TRUE
384
- ELSE FALSE
385
- END AS is_column_constraint
386
- FROM
387
- pg_catalog.pg_constraint AS con
388
- JOIN
389
- pg_catalog.pg_class AS c
390
- ON con.conrelid = c.oid
391
- LEFT JOIN
392
- pg_catalog.pg_namespace AS n
393
- ON c.relnamespace = n.oid
394
- LEFT JOIN
395
- pg_catalog.pg_attribute AS a
396
- ON a.attrelid = c.oid AND a.attnum = ANY(con.conkey)
397
- WHERE con.contype = 'c' -- 'c' for CHECK constraints
398
- ${(0, parseSchema_1.buildSchemaQuery)('n.nspname', schemas)}
399
- GROUP BY
400
- con.conname,
401
- n.nspname,
402
- c.relname,
403
- con.oid
404
- `;
405
- const checkListResult = await client.query(checkListSql);
406
- const checks = {};
407
- checkListResult.rows.forEach((row) => {
408
- const { table_schema, table_name, check_name, check_definition, column_name, is_column_constraint, } = row;
409
- if (typeof table_schema !== 'string' || typeof table_name !== 'string' || typeof check_definition !== 'string') {
410
- return;
411
- }
412
- if (!check_definition.match(/CHECK \(\(.*\)\)/)) {
413
- return;
414
- }
415
- if (is_column_constraint && typeof column_name !== 'string') {
416
- return;
417
- }
418
- const key = `${table_schema}.${table_name}`;
419
- if (is_column_constraint) {
420
- if (!tableConstraints[key])
421
- tableConstraints[key] = {};
422
- if (!tableConstraints[key][column_name])
423
- tableConstraints[key][column_name] = { checks: [] };
424
- tableConstraints[key][column_name].checks.push({
425
- name: check_name || undefined,
426
- // The check definition has the form: `CHECK ((expr))`
427
- // The expression starts at 8 and ends at -2
428
- expression: check_definition.slice(8, -2),
429
- });
430
- return;
431
- }
432
- if (!checks[key])
433
- checks[key] = [];
434
- checks[key].push({
435
- name: check_name || undefined,
436
- // The check definition has the form: `CHECK ((expr))`
437
- // The expression starts at 8 and ends at -2
438
- expression: check_definition.slice(8, -2),
439
- });
440
- });
441
- return {
442
- indexes,
443
- tableConstraints,
444
- checks,
445
- };
446
- };
447
- const generateRawEnums = async (client, schemas) => {
448
- const enumListSql = `
449
- SELECT DISTINCT
450
- n.nspname AS schema_name,
451
- t.typname AS enum_type,
452
- e.enumlabel AS enum_value,
453
- e.enumsortorder AS sort_order
454
- FROM
455
- pg_enum e
456
- JOIN
457
- pg_type t ON e.enumtypid = t.oid
458
- JOIN
459
- pg_namespace n ON t.typnamespace = n.oid
460
- ${(0, parseSchema_1.buildSchemaQuery)('n.nspname', schemas, 'WHERE')}
461
- ORDER BY
462
- schema_name,
463
- enum_type,
464
- sort_order;
465
- ;
466
- `;
467
- const enumListResult = await client.query(enumListSql);
468
- const enums = enumListResult.rows.reduce((acc, row) => {
469
- const { schema_name, enum_type, enum_value } = row;
470
- const key = `${schema_name}.${enum_type}`;
471
- if (!acc[key]) {
472
- acc[key] = {
473
- name: enum_type,
474
- schemaName: schema_name,
475
- values: [],
476
- };
477
- }
478
- acc[key].values.push({
479
- name: enum_value,
480
- });
481
- return acc;
482
- }, {});
483
- return Object.values(enums);
484
- };
485
- const fetchSchemaJson = async (connection) => {
486
- const { connectionString, schemas } = (0, parseSchema_1.parseConnectionString)(connection, 'jdbc');
487
- const client = await getValidatedClient(connectionString);
488
- try {
489
- const tablesAndFieldsRes = generateTablesAndFields(client, schemas);
490
- const indexesRes = generateIndexesAndConstraints(client, schemas);
491
- const refsRes = generateRawRefs(client, schemas);
492
- const enumsRes = generateRawEnums(client, schemas);
493
- const res = await Promise.all([
494
- tablesAndFieldsRes,
495
- indexesRes,
496
- refsRes,
497
- enumsRes,
498
- ]);
499
- const { tables, fields } = res[0];
500
- const { indexes, tableConstraints, checks } = res[1];
501
- const refs = res[2];
502
- const enums = res[3];
503
- return {
504
- tables,
505
- fields,
506
- refs,
507
- enums,
508
- indexes,
509
- tableConstraints,
510
- checks,
511
- };
512
- }
513
- finally {
514
- await client.end();
515
- }
516
- };
517
- exports.fetchSchemaJson = fetchSchemaJson;