@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.
- package/.babelrc +13 -0
- package/__tests__/connector.test.ts +28 -0
- package/__tests__/connectors/mssql/connection.json +4 -0
- package/__tests__/connectors/mssql/expect-out-files/schema.json +1714 -0
- package/__tests__/connectors/mysql/connection.json +4 -0
- package/__tests__/connectors/mysql/expect-out-files/schema.json +1390 -0
- package/__tests__/connectors/postgres/connection.json +4 -0
- package/__tests__/connectors/postgres/expect-out-files/schema.json +1411 -0
- package/dist/connectors/connector.d.ts +3 -0
- package/dist/connectors/connector.js +19 -0
- package/dist/connectors/mssqlConnector.d.ts +3 -0
- package/dist/connectors/mssqlConnector.js +435 -0
- package/dist/connectors/mysqlConnector.d.ts +3 -0
- package/dist/connectors/mysqlConnector.js +391 -0
- package/dist/connectors/postgresConnector.d.ts +3 -0
- package/dist/connectors/postgresConnector.js +414 -0
- package/dist/connectors/types.d.ts +80 -0
- package/dist/connectors/types.js +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +28 -0
- package/jest.config.ts +10 -0
- package/jestHelpers.ts +13 -0
- package/package.json +42 -0
- package/src/connectors/connector.ts +21 -0
- package/src/connectors/mssqlConnector.ts +523 -0
- package/src/connectors/mysqlConnector.ts +513 -0
- package/src/connectors/postgresConnector.ts +502 -0
- package/src/connectors/types.ts +116 -0
- package/src/index.ts +5 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
/* eslint-disable camelcase */
|
|
2
|
+
import { Client } from 'pg';
|
|
3
|
+
import {
|
|
4
|
+
DatabaseSchema,
|
|
5
|
+
DefaultInfo,
|
|
6
|
+
DefaultType,
|
|
7
|
+
Enum,
|
|
8
|
+
Field,
|
|
9
|
+
FieldsDictionary,
|
|
10
|
+
IndexesDictionary,
|
|
11
|
+
Ref,
|
|
12
|
+
RefEndpoint,
|
|
13
|
+
Table,
|
|
14
|
+
TableConstraintsDictionary,
|
|
15
|
+
} from './types';
|
|
16
|
+
|
|
17
|
+
const getValidatedClient = async (connection: string): Promise<Client> => {
|
|
18
|
+
const client = new Client(connection);
|
|
19
|
+
try {
|
|
20
|
+
// Connect to the PostgreSQL server
|
|
21
|
+
await client.connect();
|
|
22
|
+
|
|
23
|
+
// Validate if the connection is successful by making a simple query
|
|
24
|
+
await client.query('SELECT 1');
|
|
25
|
+
|
|
26
|
+
// If successful, return the client
|
|
27
|
+
return client;
|
|
28
|
+
} catch (err) {
|
|
29
|
+
// Ensure to close the client in case of failure
|
|
30
|
+
await client.end();
|
|
31
|
+
|
|
32
|
+
if (err instanceof Error) {
|
|
33
|
+
throw new Error(`PostgreSQL connection error: ${err}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
throw err; // Rethrow error if you want the calling code to handle it
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const convertQueryBoolean = (val: string | null) => val === 'YES';
|
|
41
|
+
|
|
42
|
+
const getFieldType = (data_type: string, udt_name: string, character_maximum_length: number, numeric_precision: number, numeric_scale: number): string => {
|
|
43
|
+
if (data_type === 'ARRAY') {
|
|
44
|
+
return `${udt_name.slice(1, udt_name.length)}[]`;
|
|
45
|
+
}
|
|
46
|
+
if (character_maximum_length) {
|
|
47
|
+
return `${udt_name}(${character_maximum_length})`;
|
|
48
|
+
}
|
|
49
|
+
if (numeric_precision && numeric_scale) {
|
|
50
|
+
return `${udt_name}(${numeric_precision},${numeric_scale})`;
|
|
51
|
+
}
|
|
52
|
+
return udt_name;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const getDbdefault = (data_type: string, column_default: string, default_type: DefaultType): DefaultInfo => {
|
|
56
|
+
if (data_type === 'ARRAY') {
|
|
57
|
+
const values = column_default.slice(6, -1).split(',').map((value) => {
|
|
58
|
+
return value.split('::')[0];
|
|
59
|
+
});
|
|
60
|
+
return {
|
|
61
|
+
type: default_type,
|
|
62
|
+
value: `ARRAY[${values.join(', ')}]`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (default_type === 'string') {
|
|
66
|
+
const defaultValues = column_default.split('::')[0];
|
|
67
|
+
const isJson = data_type === 'json' || data_type === 'jsonb';
|
|
68
|
+
const type = isJson ? 'expression' : 'string';
|
|
69
|
+
return {
|
|
70
|
+
type,
|
|
71
|
+
value: defaultValues.slice(1, -1),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
type: default_type,
|
|
76
|
+
value: column_default,
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const generateField = (row: Record<string, any>): Field => {
|
|
81
|
+
const {
|
|
82
|
+
column_name,
|
|
83
|
+
data_type,
|
|
84
|
+
character_maximum_length,
|
|
85
|
+
numeric_precision,
|
|
86
|
+
numeric_scale,
|
|
87
|
+
udt_schema,
|
|
88
|
+
udt_name,
|
|
89
|
+
identity_increment,
|
|
90
|
+
is_nullable,
|
|
91
|
+
column_default,
|
|
92
|
+
default_type,
|
|
93
|
+
column_comment,
|
|
94
|
+
} = row;
|
|
95
|
+
|
|
96
|
+
const dbdefault = column_default && default_type !== 'increment' ? getDbdefault(data_type, column_default, default_type) : null;
|
|
97
|
+
|
|
98
|
+
const fieldType = data_type === 'USER-DEFINED' ? {
|
|
99
|
+
type_name: udt_name,
|
|
100
|
+
schemaName: udt_schema,
|
|
101
|
+
} : {
|
|
102
|
+
type_name: getFieldType(data_type, udt_name, character_maximum_length, numeric_precision, numeric_scale),
|
|
103
|
+
schemaName: null,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
name: column_name,
|
|
108
|
+
type: fieldType,
|
|
109
|
+
dbdefault,
|
|
110
|
+
not_null: !convertQueryBoolean(is_nullable),
|
|
111
|
+
increment: !!identity_increment || default_type === 'increment',
|
|
112
|
+
note: column_comment ? { value: column_comment } : { value: '' },
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const generateTablesAndFields = async (client: Client): Promise<{
|
|
117
|
+
tables: Table[],
|
|
118
|
+
fields: FieldsDictionary,
|
|
119
|
+
}> => {
|
|
120
|
+
const fields: FieldsDictionary = {};
|
|
121
|
+
const tablesAndFieldsSql = `
|
|
122
|
+
WITH comments AS (
|
|
123
|
+
SELECT
|
|
124
|
+
pc.relname AS table_name,
|
|
125
|
+
pn.nspname AS table_schema,
|
|
126
|
+
pa.attname AS column_name,
|
|
127
|
+
pd.description
|
|
128
|
+
FROM
|
|
129
|
+
pg_description pd
|
|
130
|
+
JOIN
|
|
131
|
+
pg_class pc ON pd.objoid = pc.oid
|
|
132
|
+
JOIN
|
|
133
|
+
pg_namespace pn ON pc.relnamespace = pn.oid
|
|
134
|
+
LEFT JOIN
|
|
135
|
+
pg_attribute pa ON pd.objoid = pa.attrelid AND pd.objsubid = pa.attnum
|
|
136
|
+
WHERE
|
|
137
|
+
pc.relkind = 'r'
|
|
138
|
+
AND pn.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
139
|
+
)
|
|
140
|
+
SELECT
|
|
141
|
+
t.table_schema,
|
|
142
|
+
t.table_name,
|
|
143
|
+
c.column_name,
|
|
144
|
+
c.data_type,
|
|
145
|
+
c.character_maximum_length,
|
|
146
|
+
c.numeric_precision,
|
|
147
|
+
c.numeric_scale,
|
|
148
|
+
c.udt_schema,
|
|
149
|
+
c.udt_name,
|
|
150
|
+
c.identity_increment,
|
|
151
|
+
c.is_nullable,
|
|
152
|
+
c.column_default,
|
|
153
|
+
CASE
|
|
154
|
+
WHEN c.column_default IS NULL THEN NULL
|
|
155
|
+
WHEN c.column_default LIKE 'nextval(%' THEN 'increment'
|
|
156
|
+
WHEN c.column_default LIKE '''%' THEN 'string'
|
|
157
|
+
WHEN c.column_default = 'true' OR c.column_default = 'false' THEN 'boolean'
|
|
158
|
+
WHEN c.column_default ~ '^-?[0-9]+(.[0-9]+)?$' THEN 'number'
|
|
159
|
+
ELSE 'expression'
|
|
160
|
+
END AS default_type,
|
|
161
|
+
(SELECT description FROM comments WHERE table_name = t.table_name AND table_schema = t.table_schema AND column_name IS NULL) AS table_comment,
|
|
162
|
+
(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
|
|
163
|
+
FROM
|
|
164
|
+
information_schema.columns c
|
|
165
|
+
JOIN
|
|
166
|
+
information_schema.tables t ON c.table_name = t.table_name AND c.table_schema = t.table_schema
|
|
167
|
+
WHERE
|
|
168
|
+
t.table_type = 'BASE TABLE'
|
|
169
|
+
AND t.table_schema NOT IN ('pg_catalog', 'information_schema')
|
|
170
|
+
ORDER BY
|
|
171
|
+
t.table_schema,
|
|
172
|
+
t.table_name,
|
|
173
|
+
c.ordinal_position
|
|
174
|
+
;
|
|
175
|
+
`;
|
|
176
|
+
|
|
177
|
+
const tablesAndFieldsResult = await client.query(tablesAndFieldsSql);
|
|
178
|
+
const tables = tablesAndFieldsResult.rows.reduce((acc, row) => {
|
|
179
|
+
const { table_schema, table_name, table_comment } = row;
|
|
180
|
+
const key = `${table_schema}.${table_name}`;
|
|
181
|
+
|
|
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
|
+
|
|
190
|
+
if (!fields[key]) fields[key] = [];
|
|
191
|
+
const field = generateField(row);
|
|
192
|
+
fields[key].push(field);
|
|
193
|
+
|
|
194
|
+
return acc;
|
|
195
|
+
}, {});
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
tables: Object.values(tables),
|
|
199
|
+
fields,
|
|
200
|
+
};
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const generateRawRefs = async (client: Client): Promise<Ref[]> => {
|
|
204
|
+
const refs: Ref[] = [];
|
|
205
|
+
|
|
206
|
+
const refsListSql = `
|
|
207
|
+
SELECT
|
|
208
|
+
tc.table_schema,
|
|
209
|
+
tc.table_name,
|
|
210
|
+
tc.constraint_name as fk_constraint_name,
|
|
211
|
+
STRING_AGG(DISTINCT kcu.column_name, ',') AS column_names,
|
|
212
|
+
ccu.table_schema AS foreign_table_schema,
|
|
213
|
+
ccu.table_name AS foreign_table_name,
|
|
214
|
+
STRING_AGG(DISTINCT ccu.column_name, ',') AS foreign_column_names,
|
|
215
|
+
tc.constraint_type,
|
|
216
|
+
rc.delete_rule AS on_delete,
|
|
217
|
+
rc.update_rule AS on_update
|
|
218
|
+
FROM information_schema.table_constraints AS tc
|
|
219
|
+
JOIN information_schema.key_column_usage AS kcu
|
|
220
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
221
|
+
AND tc.table_schema = kcu.table_schema
|
|
222
|
+
JOIN information_schema.constraint_column_usage AS ccu
|
|
223
|
+
ON ccu.constraint_name = tc.constraint_name
|
|
224
|
+
JOIN information_schema.referential_constraints AS rc
|
|
225
|
+
ON tc.constraint_name = rc.constraint_name
|
|
226
|
+
AND tc.table_schema = rc.constraint_schema
|
|
227
|
+
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
228
|
+
AND tc.table_schema NOT IN ('pg_catalog', 'information_schema')
|
|
229
|
+
GROUP BY
|
|
230
|
+
tc.table_schema,
|
|
231
|
+
tc.table_name,
|
|
232
|
+
tc.constraint_name,
|
|
233
|
+
ccu.table_schema,
|
|
234
|
+
ccu.table_name,
|
|
235
|
+
tc.constraint_type,
|
|
236
|
+
rc.delete_rule,
|
|
237
|
+
rc.update_rule
|
|
238
|
+
ORDER BY
|
|
239
|
+
tc.table_schema,
|
|
240
|
+
tc.table_name;
|
|
241
|
+
`;
|
|
242
|
+
|
|
243
|
+
const refsQueryResult = await client.query(refsListSql);
|
|
244
|
+
refsQueryResult.rows.forEach((refRow) => {
|
|
245
|
+
const {
|
|
246
|
+
table_schema,
|
|
247
|
+
fk_constraint_name,
|
|
248
|
+
table_name,
|
|
249
|
+
column_names,
|
|
250
|
+
foreign_table_schema,
|
|
251
|
+
foreign_table_name,
|
|
252
|
+
foreign_column_names,
|
|
253
|
+
on_delete,
|
|
254
|
+
on_update,
|
|
255
|
+
} = refRow;
|
|
256
|
+
|
|
257
|
+
const ep1: RefEndpoint = {
|
|
258
|
+
tableName: table_name,
|
|
259
|
+
schemaName: table_schema,
|
|
260
|
+
fieldNames: column_names.split(','),
|
|
261
|
+
relation: '*',
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const ep2: RefEndpoint = {
|
|
265
|
+
tableName: foreign_table_name,
|
|
266
|
+
schemaName: foreign_table_schema,
|
|
267
|
+
fieldNames: foreign_column_names.split(','),
|
|
268
|
+
relation: '1',
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
refs.push({
|
|
272
|
+
name: fk_constraint_name,
|
|
273
|
+
endpoints: [ep1, ep2],
|
|
274
|
+
onDelete: on_delete === 'NO ACTION' ? null : on_delete,
|
|
275
|
+
onUpdate: on_update === 'NO ACTION' ? null : on_update,
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
return refs;
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const generateIndexes = async (client: Client) => {
|
|
283
|
+
// const tableConstraints = {};
|
|
284
|
+
const indexListSql = `
|
|
285
|
+
WITH user_tables AS (
|
|
286
|
+
SELECT
|
|
287
|
+
schemaname AS tableschema,
|
|
288
|
+
tablename
|
|
289
|
+
FROM pg_tables
|
|
290
|
+
WHERE schemaname NOT IN ('pg_catalog', 'information_schema') -- Exclude system schemas
|
|
291
|
+
AND tablename NOT LIKE 'pg_%' -- Exclude PostgreSQL system tables
|
|
292
|
+
AND tablename NOT LIKE 'sql_%' -- Exclude SQL standard tables
|
|
293
|
+
),
|
|
294
|
+
index_info AS (
|
|
295
|
+
SELECT
|
|
296
|
+
t.relname AS table_name,
|
|
297
|
+
i.relname AS index_name,
|
|
298
|
+
ix.indisunique AS is_unique,
|
|
299
|
+
ix.indisprimary AS is_primary,
|
|
300
|
+
am.amname AS index_type,
|
|
301
|
+
array_to_string(array_agg(a.attname ORDER BY x.n), ', ') AS columns,
|
|
302
|
+
pg_get_expr(ix.indexprs, ix.indrelid) AS expressions,
|
|
303
|
+
CASE
|
|
304
|
+
WHEN ix.indisprimary THEN 'PRIMARY KEY'
|
|
305
|
+
WHEN ix.indisunique THEN 'UNIQUE'
|
|
306
|
+
ELSE NULL
|
|
307
|
+
END AS constraint_type
|
|
308
|
+
FROM
|
|
309
|
+
pg_class t
|
|
310
|
+
JOIN pg_index ix ON t.oid = ix.indrelid
|
|
311
|
+
JOIN pg_class i ON i.oid = ix.indexrelid
|
|
312
|
+
LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(ix.indkey)
|
|
313
|
+
JOIN pg_am am ON i.relam = am.oid
|
|
314
|
+
LEFT JOIN generate_subscripts(ix.indkey, 1) AS x(n) ON a.attnum = ix.indkey[x.n]
|
|
315
|
+
WHERE
|
|
316
|
+
t.relkind = 'r'
|
|
317
|
+
AND t.relname NOT LIKE 'pg_%'
|
|
318
|
+
AND t.relname NOT LIKE 'sql_%'
|
|
319
|
+
GROUP BY
|
|
320
|
+
t.relname, i.relname, ix.indisunique, ix.indisprimary, am.amname, ix.indexprs, ix.indrelid
|
|
321
|
+
)
|
|
322
|
+
SELECT
|
|
323
|
+
ut.tableschema AS table_schema,
|
|
324
|
+
ut.tablename AS table_name,
|
|
325
|
+
ii.index_name,
|
|
326
|
+
ii.is_unique,
|
|
327
|
+
ii.is_primary,
|
|
328
|
+
ii.index_type,
|
|
329
|
+
ii.columns,
|
|
330
|
+
ii.expressions,
|
|
331
|
+
ii.constraint_type -- Added constraint type
|
|
332
|
+
FROM
|
|
333
|
+
user_tables ut
|
|
334
|
+
LEFT JOIN
|
|
335
|
+
index_info ii ON ut.tablename = ii.table_name
|
|
336
|
+
WHERE ii.columns IS NOT NULL
|
|
337
|
+
ORDER BY
|
|
338
|
+
ut.tablename,
|
|
339
|
+
ii.constraint_type,
|
|
340
|
+
ii.index_name
|
|
341
|
+
;
|
|
342
|
+
`;
|
|
343
|
+
const indexListResult = await client.query(indexListSql);
|
|
344
|
+
const { outOfLineConstraints, inlineConstraints } = indexListResult.rows.reduce((acc, row) => {
|
|
345
|
+
const { constraint_type, columns } = row;
|
|
346
|
+
|
|
347
|
+
if (columns === 'null' || columns.trim() === '') return acc;
|
|
348
|
+
|
|
349
|
+
const isSingleColumn = columns.split(',').length === 1;
|
|
350
|
+
const isInlineConstraint = isSingleColumn && (constraint_type === 'PRIMARY KEY' || constraint_type === 'UNIQUE');
|
|
351
|
+
if (isInlineConstraint) {
|
|
352
|
+
acc.inlineConstraints.push(row);
|
|
353
|
+
} else {
|
|
354
|
+
acc.outOfLineConstraints.push(row);
|
|
355
|
+
}
|
|
356
|
+
return acc;
|
|
357
|
+
}, { outOfLineConstraints: [], inlineConstraints: [] });
|
|
358
|
+
|
|
359
|
+
const indexes = outOfLineConstraints.reduce((acc: IndexesDictionary, indexRow: Record<string, any>) => {
|
|
360
|
+
const {
|
|
361
|
+
table_schema,
|
|
362
|
+
table_name,
|
|
363
|
+
index_name,
|
|
364
|
+
index_type,
|
|
365
|
+
columns,
|
|
366
|
+
expressions,
|
|
367
|
+
} = indexRow;
|
|
368
|
+
const indexColumns = columns.split(',').map((column: string) => {
|
|
369
|
+
return {
|
|
370
|
+
type: 'column',
|
|
371
|
+
value: column.trim(),
|
|
372
|
+
};
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
const indexExpressions = expressions ? expressions.split(',').map((expression: string) => {
|
|
376
|
+
return {
|
|
377
|
+
type: 'expression',
|
|
378
|
+
value: expression,
|
|
379
|
+
};
|
|
380
|
+
}) : [];
|
|
381
|
+
|
|
382
|
+
const index = {
|
|
383
|
+
name: index_name,
|
|
384
|
+
type: index_type,
|
|
385
|
+
columns: [
|
|
386
|
+
...indexColumns,
|
|
387
|
+
...indexExpressions,
|
|
388
|
+
],
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
const key = `${table_schema}.${table_name}`;
|
|
392
|
+
if (acc[key]) {
|
|
393
|
+
acc[key].push(index);
|
|
394
|
+
} else {
|
|
395
|
+
acc[key] = [index];
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return acc;
|
|
399
|
+
}, {});
|
|
400
|
+
|
|
401
|
+
const tableConstraints = inlineConstraints.reduce((acc: TableConstraintsDictionary, row: Record<string, any>) => {
|
|
402
|
+
const {
|
|
403
|
+
table_schema,
|
|
404
|
+
table_name,
|
|
405
|
+
columns,
|
|
406
|
+
constraint_type,
|
|
407
|
+
} = row;
|
|
408
|
+
const key = `${table_schema}.${table_name}`;
|
|
409
|
+
if (!acc[key]) acc[key] = {};
|
|
410
|
+
|
|
411
|
+
const columnNames = columns.split(',').map((column: string) => column.trim());
|
|
412
|
+
columnNames.forEach((columnName: string) => {
|
|
413
|
+
if (!acc[key][columnName]) acc[key][columnName] = {};
|
|
414
|
+
if (constraint_type === 'PRIMARY KEY') {
|
|
415
|
+
acc[key][columnName].pk = true;
|
|
416
|
+
}
|
|
417
|
+
if (constraint_type === 'UNIQUE' && !acc[key][columnName].pk) {
|
|
418
|
+
acc[key][columnName].unique = true;
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
return acc;
|
|
422
|
+
}, {});
|
|
423
|
+
|
|
424
|
+
return {
|
|
425
|
+
indexes,
|
|
426
|
+
tableConstraints,
|
|
427
|
+
};
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
const generateRawEnums = async (client: Client): Promise<Enum[]> => {
|
|
431
|
+
const enumListSql = `
|
|
432
|
+
SELECT
|
|
433
|
+
n.nspname AS schema_name,
|
|
434
|
+
t.typname AS enum_type,
|
|
435
|
+
e.enumlabel AS enum_value,
|
|
436
|
+
e.enumsortorder AS sort_order
|
|
437
|
+
FROM
|
|
438
|
+
pg_enum e
|
|
439
|
+
JOIN
|
|
440
|
+
pg_type t ON e.enumtypid = t.oid
|
|
441
|
+
JOIN
|
|
442
|
+
pg_namespace n ON t.typnamespace = n.oid
|
|
443
|
+
ORDER BY
|
|
444
|
+
schema_name,
|
|
445
|
+
enum_type,
|
|
446
|
+
sort_order;
|
|
447
|
+
;
|
|
448
|
+
`;
|
|
449
|
+
const enumListResult = await client.query(enumListSql);
|
|
450
|
+
const enums = enumListResult.rows.reduce((acc, row) => {
|
|
451
|
+
const { schema_name, enum_type, enum_value } = row;
|
|
452
|
+
|
|
453
|
+
if (!acc[enum_type]) {
|
|
454
|
+
acc[enum_type] = {
|
|
455
|
+
name: enum_type,
|
|
456
|
+
schemaName: schema_name,
|
|
457
|
+
values: [],
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
acc[enum_type].values.push({
|
|
461
|
+
name: enum_value,
|
|
462
|
+
});
|
|
463
|
+
return acc;
|
|
464
|
+
}, {});
|
|
465
|
+
|
|
466
|
+
return Object.values(enums);
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
const fetchSchemaJson = async (connection: string): Promise<DatabaseSchema> => {
|
|
470
|
+
const client = await getValidatedClient(connection);
|
|
471
|
+
|
|
472
|
+
const tablesAndFieldsRes = generateTablesAndFields(client);
|
|
473
|
+
const indexesRes = generateIndexes(client);
|
|
474
|
+
const refsRes = generateRawRefs(client);
|
|
475
|
+
const enumsRes = generateRawEnums(client);
|
|
476
|
+
|
|
477
|
+
const res = await Promise.all([
|
|
478
|
+
tablesAndFieldsRes,
|
|
479
|
+
indexesRes,
|
|
480
|
+
refsRes,
|
|
481
|
+
enumsRes,
|
|
482
|
+
]);
|
|
483
|
+
client.end();
|
|
484
|
+
|
|
485
|
+
const { tables, fields } = res[0];
|
|
486
|
+
const { indexes, tableConstraints } = res[1];
|
|
487
|
+
const refs = res[2];
|
|
488
|
+
const enums = res[3];
|
|
489
|
+
|
|
490
|
+
return {
|
|
491
|
+
tables,
|
|
492
|
+
fields,
|
|
493
|
+
refs,
|
|
494
|
+
enums,
|
|
495
|
+
indexes,
|
|
496
|
+
tableConstraints,
|
|
497
|
+
};
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
export {
|
|
501
|
+
fetchSchemaJson,
|
|
502
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
interface NoteInfo {
|
|
2
|
+
value: string;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
interface TypeInfo {
|
|
6
|
+
type_name: string;
|
|
7
|
+
schemaName: string | null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
type DefaultType = 'string' | 'number' | 'boolean' | 'expression';
|
|
12
|
+
|
|
13
|
+
interface DefaultInfo {
|
|
14
|
+
type: DefaultType,
|
|
15
|
+
value: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface Field {
|
|
19
|
+
name: string;
|
|
20
|
+
type: TypeInfo;
|
|
21
|
+
dbdefault: DefaultInfo | null;
|
|
22
|
+
not_null: boolean;
|
|
23
|
+
increment: boolean;
|
|
24
|
+
note: NoteInfo;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface Table {
|
|
28
|
+
name: string;
|
|
29
|
+
schemaName: string;
|
|
30
|
+
note: NoteInfo;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface FieldsDictionary {
|
|
34
|
+
[key: string]: Field[]; // Represents fields within tables, indexed by schemaName.tableName
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface TableConstraint {
|
|
38
|
+
[fieldName: string]: {
|
|
39
|
+
pk?: boolean;
|
|
40
|
+
unique?: boolean;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface TableConstraintsDictionary {
|
|
45
|
+
[tableIdentifier: string]: TableConstraint; // Represents constraints within tables, indexed by schemaName.tableName
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface RefEndpoint {
|
|
49
|
+
tableName: string; // Parent table or child table
|
|
50
|
+
schemaName: string;
|
|
51
|
+
fieldNames: string[]; // The parent fields or the child fields (foreign key fields)
|
|
52
|
+
relation: '*' | '1'; // The parent endpoint is '*' and the child endpoint is '1'
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface Ref {
|
|
56
|
+
name: string;
|
|
57
|
+
endpoints: RefEndpoint[]; // The first endpoint is the parent table and the second endpoint is the child table.
|
|
58
|
+
onDelete: string | null;
|
|
59
|
+
onUpdate: string | null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface EnumValue {
|
|
63
|
+
name: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface Enum {
|
|
67
|
+
name: string;
|
|
68
|
+
schemaName: string;
|
|
69
|
+
values: EnumValue[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface IndexColumn {
|
|
73
|
+
type: 'column' | 'expression';
|
|
74
|
+
value: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface Index {
|
|
78
|
+
name: string;
|
|
79
|
+
type: string;
|
|
80
|
+
unique?: boolean,
|
|
81
|
+
pk?: boolean,
|
|
82
|
+
columns: IndexColumn[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface IndexesDictionary {
|
|
86
|
+
[tableIdentifier: string]: Index[]; // Represents indexes within tables, indexed by schemaName.tableName
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface DatabaseSchema {
|
|
90
|
+
tables: Table[];
|
|
91
|
+
fields: FieldsDictionary;
|
|
92
|
+
enums: Enum[];
|
|
93
|
+
tableConstraints: TableConstraintsDictionary;
|
|
94
|
+
refs: Ref[];
|
|
95
|
+
indexes: IndexesDictionary; // Represents the indexes property
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export {
|
|
99
|
+
NoteInfo,
|
|
100
|
+
TypeInfo,
|
|
101
|
+
DefaultType,
|
|
102
|
+
DefaultInfo,
|
|
103
|
+
Field,
|
|
104
|
+
Table,
|
|
105
|
+
FieldsDictionary,
|
|
106
|
+
TableConstraint,
|
|
107
|
+
TableConstraintsDictionary,
|
|
108
|
+
RefEndpoint,
|
|
109
|
+
Ref,
|
|
110
|
+
EnumValue,
|
|
111
|
+
Enum,
|
|
112
|
+
IndexColumn,
|
|
113
|
+
Index,
|
|
114
|
+
IndexesDictionary,
|
|
115
|
+
DatabaseSchema,
|
|
116
|
+
};
|
package/src/index.ts
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2021",
|
|
4
|
+
"lib": ["ES2021"],
|
|
5
|
+
"module": "commonjs",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"declaration": true, // Generates .d.ts files
|
|
13
|
+
},
|
|
14
|
+
"include": ["./src"],
|
|
15
|
+
"exclude": ["node_modules", "**/*.test.ts"]
|
|
16
|
+
}
|