@dbml/connector 10.4.0-alpha.1 → 10.4.0-metadata-ref-type.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,5 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { Client } from 'pg';
|
|
3
4
|
import { scanDirNames } from './testHelpers';
|
|
4
5
|
import { connector } from '../src/index';
|
|
5
6
|
|
|
@@ -44,4 +45,76 @@ describe('@dbml/connector', () => {
|
|
|
44
45
|
test.each(scanDirNames(__dirname, 'connectors'))('connectors/%s', async (dirName) => {
|
|
45
46
|
await runTest(path.join(__dirname, 'connectors', dirName));
|
|
46
47
|
}, 10000);
|
|
48
|
+
|
|
49
|
+
test('postgres preserves composite foreign key column order', async () => {
|
|
50
|
+
const testDir = path.join(__dirname, 'connectors', 'postgres');
|
|
51
|
+
const { connection } = JSON.parse(fs.readFileSync(path.join(testDir, 'connection.json'), 'utf-8'));
|
|
52
|
+
const schema = `dbml_fk_order_${Math.random().toString(16).slice(2)}`;
|
|
53
|
+
const client = new Client({ connectionString: connection });
|
|
54
|
+
|
|
55
|
+
await client.connect();
|
|
56
|
+
try {
|
|
57
|
+
await client.query(`CREATE SCHEMA "${schema}"`);
|
|
58
|
+
await client.query(`
|
|
59
|
+
CREATE TABLE "${schema}".parent (
|
|
60
|
+
zeta integer NOT NULL,
|
|
61
|
+
alpha integer NOT NULL,
|
|
62
|
+
PRIMARY KEY (zeta, alpha)
|
|
63
|
+
);
|
|
64
|
+
CREATE TABLE "${schema}".child (
|
|
65
|
+
id integer PRIMARY KEY,
|
|
66
|
+
a_zeta integer,
|
|
67
|
+
z_alpha integer,
|
|
68
|
+
CONSTRAINT child_parent_fk FOREIGN KEY (a_zeta, z_alpha)
|
|
69
|
+
REFERENCES "${schema}".parent (zeta, alpha)
|
|
70
|
+
);
|
|
71
|
+
`);
|
|
72
|
+
|
|
73
|
+
const schemaJson = await connector.fetchSchemaJson(`${connection}?schemas=${schema}`, 'postgres');
|
|
74
|
+
const ref = schemaJson.refs.find(({ name }) => name === 'child_parent_fk');
|
|
75
|
+
|
|
76
|
+
expect(ref?.endpoints[0].fieldNames).toEqual(['a_zeta', 'z_alpha']);
|
|
77
|
+
expect(ref?.endpoints[1].fieldNames).toEqual(['zeta', 'alpha']);
|
|
78
|
+
} finally {
|
|
79
|
+
await client.query(`DROP SCHEMA IF EXISTS "${schema}" CASCADE`);
|
|
80
|
+
await client.end();
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('postgres keeps array column defaults that are not an ARRAY[...] list', async () => {
|
|
85
|
+
const testDir = path.join(__dirname, 'connectors', 'postgres');
|
|
86
|
+
const { connection } = JSON.parse(fs.readFileSync(path.join(testDir, 'connection.json'), 'utf-8'));
|
|
87
|
+
const schema = `dbml_array_default_${Math.random().toString(16).slice(2)}`;
|
|
88
|
+
const client = new Client({ connectionString: connection });
|
|
89
|
+
|
|
90
|
+
await client.connect();
|
|
91
|
+
try {
|
|
92
|
+
await client.query(`CREATE SCHEMA "${schema}"`);
|
|
93
|
+
await client.query(`
|
|
94
|
+
CREATE TABLE "${schema}".t (
|
|
95
|
+
literal_col varchar[] DEFAULT '{full_name,date_of_birth}',
|
|
96
|
+
empty_literal_col text[] DEFAULT '{}',
|
|
97
|
+
int_literal_col integer[] DEFAULT '{1,2}',
|
|
98
|
+
empty_array_col text[] DEFAULT ARRAY[]::text[],
|
|
99
|
+
function_col integer[] DEFAULT array_fill(0, ARRAY[3]),
|
|
100
|
+
array_col text[] DEFAULT ARRAY['a', 'b']
|
|
101
|
+
);
|
|
102
|
+
`);
|
|
103
|
+
|
|
104
|
+
const schemaJson = await connector.fetchSchemaJson(`${connection}?schemas=${schema}`, 'postgres');
|
|
105
|
+
const dbdefaults = Object.fromEntries(schemaJson.fields[`${schema}.t`].map(({ name, dbdefault }) => [name, dbdefault]));
|
|
106
|
+
|
|
107
|
+
expect(dbdefaults).toEqual({
|
|
108
|
+
literal_col: { type: 'string', value: '{full_name,date_of_birth}' },
|
|
109
|
+
empty_literal_col: { type: 'string', value: '{}' },
|
|
110
|
+
int_literal_col: { type: 'string', value: '{1,2}' },
|
|
111
|
+
empty_array_col: { type: 'expression', value: 'ARRAY[]::text[]' },
|
|
112
|
+
function_col: { type: 'expression', value: 'array_fill(0, ARRAY[3])' },
|
|
113
|
+
array_col: { type: 'expression', value: 'ARRAY[\'a\', \'b\']' },
|
|
114
|
+
});
|
|
115
|
+
} finally {
|
|
116
|
+
await client.query(`DROP SCHEMA IF EXISTS "${schema}" CASCADE`);
|
|
117
|
+
await client.end();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
47
120
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@dbml/connector",
|
|
4
|
-
"version": "10.4.0-
|
|
4
|
+
"version": "10.4.0-metadata-ref-type.0",
|
|
5
5
|
"description": "This package was created to fetch the schema JSON from many kind of databases.",
|
|
6
6
|
"author": "huy.phung.sw@gmail.com",
|
|
7
7
|
"license": "MIT",
|
|
@@ -39,5 +39,5 @@
|
|
|
39
39
|
"engines": {
|
|
40
40
|
"node": ">=18"
|
|
41
41
|
},
|
|
42
|
-
"gitHead": "
|
|
42
|
+
"gitHead": "f2806cfb089ae685523e444dd409fd1e4514962b"
|
|
43
43
|
}
|
|
@@ -54,7 +54,9 @@ const getFieldType = (data_type: string, udt_name: string, character_maximum_len
|
|
|
54
54
|
};
|
|
55
55
|
|
|
56
56
|
const getDbdefault = (data_type: string, column_default: string, default_type: DefaultType): DefaultInfo => {
|
|
57
|
-
|
|
57
|
+
// Only a non-empty ARRAY[...] constructor is rewritten here. Other array defaults, such as
|
|
58
|
+
// '{a,b}'::text[], ARRAY[]::text[] or a function call, are handled below like any other default
|
|
59
|
+
if (data_type === 'ARRAY' && column_default.startsWith('ARRAY[') && !column_default.startsWith('ARRAY[]')) {
|
|
58
60
|
const values = column_default.slice(6, -1).split(',').map((value) => {
|
|
59
61
|
return value.split('::')[0];
|
|
60
62
|
});
|
|
@@ -228,40 +230,59 @@ const generateRawRefs = async (client: Client, schemas: string[]): Promise<Ref[]
|
|
|
228
230
|
|
|
229
231
|
const refsListSql = `
|
|
230
232
|
SELECT
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
STRING_AGG(
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
STRING_AGG(
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
233
|
+
source_ns.nspname AS table_schema,
|
|
234
|
+
source_table.relname AS table_name,
|
|
235
|
+
constraint_row.conname AS fk_constraint_name,
|
|
236
|
+
STRING_AGG(source_column.attname, ',' ORDER BY key_column.ordinality) AS column_names,
|
|
237
|
+
target_ns.nspname AS foreign_table_schema,
|
|
238
|
+
target_table.relname AS foreign_table_name,
|
|
239
|
+
STRING_AGG(target_column.attname, ',' ORDER BY key_column.ordinality) AS foreign_column_names,
|
|
240
|
+
CASE constraint_row.confdeltype
|
|
241
|
+
WHEN 'a' THEN 'NO ACTION'
|
|
242
|
+
WHEN 'r' THEN 'RESTRICT'
|
|
243
|
+
WHEN 'c' THEN 'CASCADE'
|
|
244
|
+
WHEN 'n' THEN 'SET NULL'
|
|
245
|
+
WHEN 'd' THEN 'SET DEFAULT'
|
|
246
|
+
END AS on_delete,
|
|
247
|
+
CASE constraint_row.confupdtype
|
|
248
|
+
WHEN 'a' THEN 'NO ACTION'
|
|
249
|
+
WHEN 'r' THEN 'RESTRICT'
|
|
250
|
+
WHEN 'c' THEN 'CASCADE'
|
|
251
|
+
WHEN 'n' THEN 'SET NULL'
|
|
252
|
+
WHEN 'd' THEN 'SET DEFAULT'
|
|
253
|
+
END AS on_update
|
|
254
|
+
FROM pg_constraint AS constraint_row
|
|
255
|
+
JOIN pg_class AS source_table
|
|
256
|
+
ON constraint_row.conrelid = source_table.oid
|
|
257
|
+
JOIN pg_namespace AS source_ns
|
|
258
|
+
ON source_table.relnamespace = source_ns.oid
|
|
259
|
+
JOIN pg_class AS target_table
|
|
260
|
+
ON constraint_row.confrelid = target_table.oid
|
|
261
|
+
JOIN pg_namespace AS target_ns
|
|
262
|
+
ON target_table.relnamespace = target_ns.oid
|
|
263
|
+
JOIN LATERAL unnest(constraint_row.conkey, constraint_row.confkey)
|
|
264
|
+
WITH ORDINALITY AS key_column(source_attnum, target_attnum, ordinality)
|
|
265
|
+
ON TRUE
|
|
266
|
+
JOIN pg_attribute AS source_column
|
|
267
|
+
ON source_column.attrelid = source_table.oid
|
|
268
|
+
AND source_column.attnum = key_column.source_attnum
|
|
269
|
+
JOIN pg_attribute AS target_column
|
|
270
|
+
ON target_column.attrelid = target_table.oid
|
|
271
|
+
AND target_column.attnum = key_column.target_attnum
|
|
272
|
+
WHERE constraint_row.contype = 'f'
|
|
273
|
+
AND source_ns.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
274
|
+
${buildSchemaQuery('source_ns.nspname', schemas)}
|
|
253
275
|
GROUP BY
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
rc.update_rule
|
|
276
|
+
source_ns.nspname,
|
|
277
|
+
source_table.relname,
|
|
278
|
+
constraint_row.conname,
|
|
279
|
+
target_ns.nspname,
|
|
280
|
+
target_table.relname,
|
|
281
|
+
constraint_row.confdeltype,
|
|
282
|
+
constraint_row.confupdtype
|
|
262
283
|
ORDER BY
|
|
263
|
-
|
|
264
|
-
|
|
284
|
+
source_ns.nspname,
|
|
285
|
+
source_table.relname;
|
|
265
286
|
`;
|
|
266
287
|
|
|
267
288
|
const refsQueryResult = await client.query(refsListSql);
|