@dbml/connector 5.0.0 → 5.1.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.
- package/__tests__/connectors/mssql/expect-out-files/schema.json +140 -106
- package/__tests__/connectors/mysql/expect-out-files/schema.json +40 -0
- package/__tests__/connectors/postgres/expect-out-files/schema.json +100 -0
- package/dist/connectors/bigqueryConnector.js +4 -2
- package/dist/connectors/mssqlConnector.js +70 -140
- package/dist/connectors/mysqlConnector.js +66 -7
- package/dist/connectors/postgresConnector.d.ts +1 -1
- package/dist/connectors/postgresConnector.js +97 -29
- package/dist/connectors/snowflakeConnector.js +8 -5
- package/dist/connectors/types.d.ts +10 -6
- package/dist/utils/helpers.d.ts +2 -1
- package/dist/utils/helpers.js +14 -3
- package/package.json +2 -2
- package/src/connectors/bigqueryConnector.ts +4 -2
- package/src/connectors/mssqlConnector.ts +74 -149
- package/src/connectors/mysqlConnector.ts +76 -11
- package/src/connectors/postgresConnector.ts +104 -29
- package/src/connectors/snowflakeConnector.ts +19 -15
- package/src/connectors/types.ts +16 -61
- package/src/utils/helpers.ts +30 -12
- package/__tests__/connectors/mssql/out-files/schema.json +0 -2074
- package/__tests__/connectors/mysql/out-files/schema.json +0 -1430
- package/__tests__/connectors/postgres/out-files/schema.json +0 -1719
|
@@ -5,6 +5,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.fetchSchemaJson = void 0;
|
|
7
7
|
/* eslint-disable camelcase */
|
|
8
|
+
/* TDD 72
|
|
9
|
+
* - In MSSQL, there's no notion of ENUM.
|
|
10
|
+
* - 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`.
|
|
11
|
+
* - 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.
|
|
12
|
+
* */
|
|
8
13
|
const mssql_1 = __importDefault(require("mssql"));
|
|
9
14
|
const parseSchema_1 = require("../utils/parseSchema");
|
|
10
15
|
// https://learn.microsoft.com/en-us/sql/t-sql/data-types/date-and-time-types?view=sql-server-ver15
|
|
@@ -88,63 +93,6 @@ const getDbdefault = (data_type, column_default, default_type) => {
|
|
|
88
93
|
value: default_type === 'string' ? value.slice(1, -1) : value, // Remove the quotes for string values
|
|
89
94
|
};
|
|
90
95
|
};
|
|
91
|
-
const getEnumValues = (definition, constraint_name) => {
|
|
92
|
-
// Use the example below to understand the regex:
|
|
93
|
-
// ([quantity]>(0))
|
|
94
|
-
// ([unit_price]>(0))
|
|
95
|
-
// ([status]='cancelled' OR [status]='delivered' OR [status]='shipped' OR [status]='processing' OR [status]='pending')
|
|
96
|
-
// ([total_amount]>(0))
|
|
97
|
-
// ([price]>(0))
|
|
98
|
-
// ([stock_quantity]>=(0))
|
|
99
|
-
// ([age_start]<=[age_end])
|
|
100
|
-
// ([age_start]<=[age_end])
|
|
101
|
-
// ([gender]='Other' OR [gender]='Female' OR [gender]='Male')
|
|
102
|
-
// ([date_of_birth]<=dateadd(year,(-13),getdate()))
|
|
103
|
-
// ([email] like '%_@_%._%')
|
|
104
|
-
if (!definition)
|
|
105
|
-
return [];
|
|
106
|
-
const values = definition.match(/\[([^\]]+)\]='([^']+)'/g); // Extracting the enum values when the definition contains ]='
|
|
107
|
-
if (!values)
|
|
108
|
-
return [];
|
|
109
|
-
const colMap = {};
|
|
110
|
-
values.forEach((value) => {
|
|
111
|
-
const [columnName, enumValue] = value.split("]='");
|
|
112
|
-
const cleanColumnName = columnName.slice(1); // Remove the leading '['
|
|
113
|
-
const cleanEnumValue = enumValue.slice(0, -1); // Remove the trailing "'"
|
|
114
|
-
if (!colMap[cleanColumnName]) {
|
|
115
|
-
colMap[cleanColumnName] = [cleanEnumValue];
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
colMap[cleanColumnName].push(cleanEnumValue);
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
const arraysEqual = (a, b) => {
|
|
122
|
-
if (a.length !== b.length)
|
|
123
|
-
return false;
|
|
124
|
-
const sortedA = [...a].sort();
|
|
125
|
-
const sortedB = [...b].sort();
|
|
126
|
-
return sortedA.every((value, index) => value === sortedB[index]);
|
|
127
|
-
};
|
|
128
|
-
// Please check the comments of the EnumValuesDict type in types.ts to understand the structure
|
|
129
|
-
const result = [];
|
|
130
|
-
const processedKeys = new Set();
|
|
131
|
-
Object.keys(colMap).forEach((key) => {
|
|
132
|
-
if (processedKeys.has(key))
|
|
133
|
-
return;
|
|
134
|
-
const mergedColumns = [key];
|
|
135
|
-
const colValues = colMap[key];
|
|
136
|
-
Object.keys(colMap).forEach((innerKey) => {
|
|
137
|
-
if (key !== innerKey && arraysEqual(colValues, colMap[innerKey])) {
|
|
138
|
-
mergedColumns.push(innerKey);
|
|
139
|
-
processedKeys.add(innerKey);
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
const enumValues = colValues.map((value) => ({ name: value }));
|
|
143
|
-
result.push({ columns: mergedColumns, enumValues, constraint_name: `${constraint_name}_${mergedColumns.join('_')}` });
|
|
144
|
-
processedKeys.add(key);
|
|
145
|
-
});
|
|
146
|
-
return result;
|
|
147
|
-
};
|
|
148
96
|
const generateField = (row) => {
|
|
149
97
|
const { column_name, data_type, character_maximum_length, numeric_precision, numeric_scale, identity_increment, is_nullable, column_default, default_type, column_comment, } = row;
|
|
150
98
|
const dbdefault = column_default && default_type !== 'increment' ? getDbdefault(data_type, column_default, default_type) : null;
|
|
@@ -161,10 +109,8 @@ const generateField = (row) => {
|
|
|
161
109
|
note: column_comment ? { value: column_comment } : { value: '' },
|
|
162
110
|
};
|
|
163
111
|
};
|
|
164
|
-
const
|
|
165
|
-
const checkConstraintDefinitionDedup = {};
|
|
112
|
+
const generateTablesFields = async (client, schemas) => {
|
|
166
113
|
const fields = {};
|
|
167
|
-
const enums = [];
|
|
168
114
|
const tablesAndFieldsSql = `
|
|
169
115
|
WITH tables_and_fields AS (
|
|
170
116
|
SELECT
|
|
@@ -203,35 +149,6 @@ const generateTablesFieldsAndEnums = async (client, schemas) => {
|
|
|
203
149
|
AND ep.name like '%description%'
|
|
204
150
|
WHERE
|
|
205
151
|
t.type = 'U' -- User-defined tables
|
|
206
|
-
),
|
|
207
|
-
constraints_with_nulls AS (
|
|
208
|
-
SELECT
|
|
209
|
-
tf.*,
|
|
210
|
-
cc.name AS check_constraint_name,
|
|
211
|
-
CASE
|
|
212
|
-
WHEN cc.definition LIKE '%[' + tf.column_name + ']%=''%' THEN cc.definition
|
|
213
|
-
ELSE NULL
|
|
214
|
-
END AS check_constraint_definition
|
|
215
|
-
FROM
|
|
216
|
-
tables_and_fields AS tf
|
|
217
|
-
LEFT JOIN sys.check_constraints cc
|
|
218
|
-
ON cc.parent_object_id = OBJECT_ID(tf.table_schema + '.' + tf.table_name)
|
|
219
|
-
AND cc.definition LIKE '%' + tf.column_name + '%' -- Ensure the constraint references the column
|
|
220
|
-
),
|
|
221
|
-
constraints_with_row_number AS (
|
|
222
|
-
SELECT
|
|
223
|
-
*,
|
|
224
|
-
ROW_NUMBER() OVER (
|
|
225
|
-
PARTITION BY table_schema, table_name, column_name
|
|
226
|
-
ORDER BY
|
|
227
|
-
CASE
|
|
228
|
-
WHEN check_constraint_definition IS NOT NULL THEN 1
|
|
229
|
-
ELSE 2
|
|
230
|
-
END,
|
|
231
|
-
check_constraint_name
|
|
232
|
-
) AS rn
|
|
233
|
-
FROM
|
|
234
|
-
constraints_with_nulls
|
|
235
152
|
)
|
|
236
153
|
SELECT
|
|
237
154
|
table_schema,
|
|
@@ -246,17 +163,15 @@ const generateTablesFieldsAndEnums = async (client, schemas) => {
|
|
|
246
163
|
column_default,
|
|
247
164
|
table_comment,
|
|
248
165
|
column_comment,
|
|
249
|
-
check_constraint_name,
|
|
250
|
-
check_constraint_definition,
|
|
251
166
|
CASE
|
|
252
167
|
WHEN column_default LIKE '((%))' THEN 'number'
|
|
253
168
|
WHEN column_default LIKE '(''%'')' THEN 'string'
|
|
254
169
|
ELSE 'expression'
|
|
255
170
|
END AS default_type
|
|
256
171
|
FROM
|
|
257
|
-
|
|
172
|
+
tables_and_fields
|
|
258
173
|
WHERE
|
|
259
|
-
|
|
174
|
+
1 = 1
|
|
260
175
|
${(0, parseSchema_1.buildSchemaQuery)('table_schema', schemas)}
|
|
261
176
|
ORDER BY
|
|
262
177
|
table_schema,
|
|
@@ -266,7 +181,7 @@ const generateTablesFieldsAndEnums = async (client, schemas) => {
|
|
|
266
181
|
`;
|
|
267
182
|
const tablesAndFieldsResult = await client.query(tablesAndFieldsSql);
|
|
268
183
|
const tables = tablesAndFieldsResult.recordset.reduce((acc, row) => {
|
|
269
|
-
const { table_schema, table_name, table_comment,
|
|
184
|
+
const { table_schema, table_name, table_comment, } = row;
|
|
270
185
|
const key = `${table_schema}.${table_name}`;
|
|
271
186
|
if (!acc[key]) {
|
|
272
187
|
acc[key] = {
|
|
@@ -278,34 +193,12 @@ const generateTablesFieldsAndEnums = async (client, schemas) => {
|
|
|
278
193
|
if (!fields[key])
|
|
279
194
|
fields[key] = [];
|
|
280
195
|
const field = generateField(row);
|
|
281
|
-
if (check_constraint_definition && !checkConstraintDefinitionDedup[check_constraint_name]) {
|
|
282
|
-
const enumValuesByColumns = getEnumValues(check_constraint_definition, check_constraint_name);
|
|
283
|
-
enumValuesByColumns.forEach((item) => {
|
|
284
|
-
enums.push({
|
|
285
|
-
name: item.constraint_name,
|
|
286
|
-
schemaName: table_schema,
|
|
287
|
-
values: item.enumValues,
|
|
288
|
-
});
|
|
289
|
-
});
|
|
290
|
-
checkConstraintDefinitionDedup[check_constraint_name] = enumValuesByColumns;
|
|
291
|
-
}
|
|
292
|
-
if (checkConstraintDefinitionDedup[check_constraint_name]) {
|
|
293
|
-
const enumValuesByColumns = checkConstraintDefinitionDedup[check_constraint_name];
|
|
294
|
-
const columnEnumValues = enumValuesByColumns.find((item) => item.columns.includes(field.name));
|
|
295
|
-
if (columnEnumValues) {
|
|
296
|
-
field.type = {
|
|
297
|
-
type_name: columnEnumValues.constraint_name,
|
|
298
|
-
schemaName: table_schema,
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
196
|
fields[key].push(field);
|
|
303
197
|
return acc;
|
|
304
198
|
}, {});
|
|
305
199
|
return {
|
|
306
200
|
tables: Object.values(tables),
|
|
307
201
|
fields,
|
|
308
|
-
enums,
|
|
309
202
|
};
|
|
310
203
|
};
|
|
311
204
|
const generateRefs = async (client, schemas) => {
|
|
@@ -379,7 +272,7 @@ const generateRefs = async (client, schemas) => {
|
|
|
379
272
|
});
|
|
380
273
|
return refs;
|
|
381
274
|
};
|
|
382
|
-
const
|
|
275
|
+
const generateIndexesAndConstraints = async (client, schemas) => {
|
|
383
276
|
const indexListSql = `
|
|
384
277
|
WITH user_tables AS (
|
|
385
278
|
SELECT
|
|
@@ -455,19 +348,19 @@ const generateIndexes = async (client, schemas) => {
|
|
|
455
348
|
ii.index_name;
|
|
456
349
|
`;
|
|
457
350
|
const indexListResult = await client.query(indexListSql);
|
|
458
|
-
const {
|
|
351
|
+
const { outOfLineIndexes, inlineIndexes } = indexListResult.recordset.reduce((acc, row) => {
|
|
459
352
|
const { constraint_type, columns } = row;
|
|
460
353
|
if (columns === 'null' || columns.trim() === '')
|
|
461
354
|
return acc;
|
|
462
355
|
if (constraint_type === 'PRIMARY KEY' || constraint_type === 'UNIQUE') {
|
|
463
|
-
acc.
|
|
356
|
+
acc.inlineIndexes.push(row);
|
|
464
357
|
}
|
|
465
358
|
else {
|
|
466
|
-
acc.
|
|
359
|
+
acc.outOfLineIndexes.push(row);
|
|
467
360
|
}
|
|
468
361
|
return acc;
|
|
469
|
-
}, {
|
|
470
|
-
const indexes =
|
|
362
|
+
}, { inlineIndexes: [], outOfLineIndexes: [] });
|
|
363
|
+
const indexes = outOfLineIndexes.reduce((acc, indexRow) => {
|
|
471
364
|
const { table_schema, table_name, index_name, index_type, columns, expressions, } = indexRow;
|
|
472
365
|
const indexColumns = columns.split(',').map((column) => {
|
|
473
366
|
return {
|
|
@@ -498,51 +391,88 @@ const generateIndexes = async (client, schemas) => {
|
|
|
498
391
|
}
|
|
499
392
|
return acc;
|
|
500
393
|
}, {});
|
|
501
|
-
const tableConstraints =
|
|
394
|
+
const tableConstraints = {};
|
|
395
|
+
inlineIndexes.forEach((row) => {
|
|
502
396
|
const { table_schema, table_name, columns, constraint_type, } = row;
|
|
503
397
|
const key = `${table_schema}.${table_name}`;
|
|
504
|
-
if (!
|
|
505
|
-
|
|
398
|
+
if (!tableConstraints[key])
|
|
399
|
+
tableConstraints[key] = {};
|
|
506
400
|
const columnNames = columns.split(',').map((column) => column.trim());
|
|
507
401
|
columnNames.forEach((columnName) => {
|
|
508
|
-
if (!
|
|
509
|
-
|
|
402
|
+
if (!tableConstraints[key][columnName])
|
|
403
|
+
tableConstraints[key][columnName] = { checks: [] };
|
|
510
404
|
if (constraint_type === 'PRIMARY KEY') {
|
|
511
|
-
|
|
405
|
+
tableConstraints[key][columnName].pk = true;
|
|
512
406
|
}
|
|
513
|
-
if (constraint_type === 'UNIQUE' && !
|
|
514
|
-
|
|
407
|
+
if (constraint_type === 'UNIQUE' && !tableConstraints[key][columnName].pk) {
|
|
408
|
+
tableConstraints[key][columnName].unique = true;
|
|
515
409
|
}
|
|
516
410
|
});
|
|
517
|
-
|
|
518
|
-
|
|
411
|
+
});
|
|
412
|
+
const checkListSql = `
|
|
413
|
+
SELECT
|
|
414
|
+
OBJECT_SCHEMA_NAME(cc.parent_object_id) AS table_schema,
|
|
415
|
+
OBJECT_NAME(cc.parent_object_id) AS table_name,
|
|
416
|
+
c.name AS column_name,
|
|
417
|
+
cc.name AS check_name,
|
|
418
|
+
cc.definition AS check_expression
|
|
419
|
+
FROM
|
|
420
|
+
sys.check_constraints cc
|
|
421
|
+
LEFT JOIN
|
|
422
|
+
sys.columns c ON cc.parent_object_id = c.object_id AND cc.parent_column_id = c.column_id
|
|
423
|
+
WHERE 1 = 1
|
|
424
|
+
${(0, parseSchema_1.buildSchemaQuery)('OBJECT_SCHEMA_NAME(cc.parent_object_id)', schemas)}
|
|
425
|
+
`;
|
|
426
|
+
const checkListResult = await client.query(checkListSql);
|
|
427
|
+
const checks = {};
|
|
428
|
+
checkListResult.recordset.forEach((row) => {
|
|
429
|
+
const { table_schema, table_name, column_name, check_name, check_expression, } = row;
|
|
430
|
+
const key = `${table_schema}.${table_name}`;
|
|
431
|
+
if (column_name) {
|
|
432
|
+
if (!tableConstraints[key])
|
|
433
|
+
tableConstraints[key] = {};
|
|
434
|
+
if (!tableConstraints[key][column_name])
|
|
435
|
+
tableConstraints[key][column_name] = { checks: [] };
|
|
436
|
+
// The check definition has the form: `(expr)`
|
|
437
|
+
// The expression starts at 1 and ends at -1
|
|
438
|
+
tableConstraints[key][column_name].checks.push({ name: check_name, expression: check_expression.slice(1, -1) });
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
if (!checks[key])
|
|
442
|
+
checks[key] = [];
|
|
443
|
+
// The check definition has the form: `(expr)`
|
|
444
|
+
// The expression starts at 1 and ends at -1
|
|
445
|
+
checks[key].push({ name: check_name, expression: check_expression.slice(1, -1) });
|
|
446
|
+
});
|
|
519
447
|
return {
|
|
520
448
|
indexes,
|
|
521
449
|
tableConstraints,
|
|
450
|
+
checks,
|
|
522
451
|
};
|
|
523
452
|
};
|
|
524
453
|
const fetchSchemaJson = async (connection) => {
|
|
525
454
|
const { connectionString, schemas } = (0, parseSchema_1.parseConnectionString)(connection, 'odbc');
|
|
526
455
|
const client = await getValidatedClient(connectionString);
|
|
527
|
-
const
|
|
528
|
-
const
|
|
456
|
+
const tablesRes = generateTablesFields(client, schemas);
|
|
457
|
+
const indexesAndConstraintsRes = generateIndexesAndConstraints(client, schemas);
|
|
529
458
|
const refsRes = generateRefs(client, schemas);
|
|
530
459
|
const res = await Promise.all([
|
|
531
|
-
|
|
532
|
-
|
|
460
|
+
tablesRes,
|
|
461
|
+
indexesAndConstraintsRes,
|
|
533
462
|
refsRes,
|
|
534
463
|
]);
|
|
535
|
-
|
|
536
|
-
const {
|
|
537
|
-
const { indexes, tableConstraints } = res[1];
|
|
464
|
+
const { tables, fields } = res[0];
|
|
465
|
+
const { indexes, tableConstraints, checks } = res[1];
|
|
538
466
|
const refs = res[2];
|
|
539
467
|
return {
|
|
540
468
|
tables,
|
|
541
469
|
fields,
|
|
542
|
-
enums
|
|
470
|
+
// MSSQL doesn't support the notion of enums
|
|
471
|
+
enums: [],
|
|
543
472
|
refs,
|
|
544
473
|
indexes,
|
|
545
474
|
tableConstraints,
|
|
475
|
+
checks,
|
|
546
476
|
};
|
|
547
477
|
};
|
|
548
478
|
exports.fetchSchemaJson = fetchSchemaJson;
|
|
@@ -164,7 +164,7 @@ async function generateEnums(client, schemaName) {
|
|
|
164
164
|
return enumList;
|
|
165
165
|
}
|
|
166
166
|
/**
|
|
167
|
-
* Mysql
|
|
167
|
+
* Mysql automatically creates index for primary keys, foreign keys, unique constraint. -> Ignore
|
|
168
168
|
*/
|
|
169
169
|
async function generateIndexes(client, schemaName) {
|
|
170
170
|
const query = `
|
|
@@ -251,8 +251,8 @@ async function generatePrimaryAndUniqueConstraint(client, schemaName) {
|
|
|
251
251
|
const queryResponse = await client.query(query, [schemaName]);
|
|
252
252
|
const rows = queryResponse[0];
|
|
253
253
|
const inlineConstraintList = rows.filter((constraint) => constraint.columnCount === 1);
|
|
254
|
-
const
|
|
255
|
-
const compositeTableConstraintMap =
|
|
254
|
+
const outOfLineConstraintList = rows.filter((constraint) => constraint.columnCount > 1);
|
|
255
|
+
const compositeTableConstraintMap = outOfLineConstraintList.reduce((acc, row) => {
|
|
256
256
|
const { tableName, constraintName, columnNames, constraintType, } = row;
|
|
257
257
|
const key = tableName;
|
|
258
258
|
if (!acc[key]) {
|
|
@@ -288,7 +288,7 @@ async function generatePrimaryAndUniqueConstraint(client, schemaName) {
|
|
|
288
288
|
const columnList = columnNames.split(',');
|
|
289
289
|
columnList.forEach((columnName) => {
|
|
290
290
|
if (!acc[key][columnName]) {
|
|
291
|
-
acc[key][columnName] = {};
|
|
291
|
+
acc[key][columnName] = { checks: [] };
|
|
292
292
|
}
|
|
293
293
|
if (constraintType === 'PRIMARY KEY') {
|
|
294
294
|
acc[key][columnName].pk = true;
|
|
@@ -299,7 +299,54 @@ async function generatePrimaryAndUniqueConstraint(client, schemaName) {
|
|
|
299
299
|
});
|
|
300
300
|
return acc;
|
|
301
301
|
}, {});
|
|
302
|
-
return { compositeConstraintMap, constraintMap };
|
|
302
|
+
return { compositeConstraintMap, primaryAndUniqueConstraintMap: constraintMap };
|
|
303
|
+
}
|
|
304
|
+
function extractColumnsFromCheckConstraint(checkClause) {
|
|
305
|
+
return [...checkClause.matchAll(/`(?:``|[^`])*`/g)].map((col) => col.toString().slice(1, -1));
|
|
306
|
+
}
|
|
307
|
+
async function generateCheckConstraints(client, schemaName) {
|
|
308
|
+
const query = `
|
|
309
|
+
select
|
|
310
|
+
cc.constraint_name as constraintName,
|
|
311
|
+
tc.table_name as tableName,
|
|
312
|
+
cc.check_clause as checkClause
|
|
313
|
+
from information_schema.check_constraints cc
|
|
314
|
+
join information_schema.table_constraints tc
|
|
315
|
+
on cc.constraint_name = tc.constraint_name
|
|
316
|
+
where cc.constraint_schema = ?
|
|
317
|
+
`;
|
|
318
|
+
const queryResponse = await client.query(query, [schemaName]);
|
|
319
|
+
const rows = queryResponse[0];
|
|
320
|
+
const checks = {};
|
|
321
|
+
const tableConstraints = {};
|
|
322
|
+
rows.forEach((row) => {
|
|
323
|
+
const { constraintName, tableName, checkClause, } = row;
|
|
324
|
+
const key = tableName;
|
|
325
|
+
const cols = extractColumnsFromCheckConstraint(checkClause);
|
|
326
|
+
if (cols.length === 1) {
|
|
327
|
+
const col = cols[0];
|
|
328
|
+
if (!tableConstraints[key])
|
|
329
|
+
tableConstraints[key] = {};
|
|
330
|
+
if (!tableConstraints[key][col])
|
|
331
|
+
tableConstraints[key][col] = { checks: [] };
|
|
332
|
+
tableConstraints[key][col].checks.push({
|
|
333
|
+
name: constraintName,
|
|
334
|
+
// The check definition has the form: `(expr)`
|
|
335
|
+
// The expression starts at 1 and ends at -1
|
|
336
|
+
expression: checkClause.slice(1, -1),
|
|
337
|
+
});
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (!checks[key])
|
|
341
|
+
checks[key] = [];
|
|
342
|
+
checks[key].push({
|
|
343
|
+
name: constraintName,
|
|
344
|
+
// The check definition has the form: `(expr)`
|
|
345
|
+
// The expression starts at 1 and ends at -1
|
|
346
|
+
expression: checkClause.slice(1, -1),
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
return { checks, tableConstraints };
|
|
303
350
|
}
|
|
304
351
|
async function generateForeignKeys(client, schemaName) {
|
|
305
352
|
const query = `
|
|
@@ -375,17 +422,29 @@ async function fetchSchemaJson(connection) {
|
|
|
375
422
|
generateIndexes(client, schemaName),
|
|
376
423
|
generatePrimaryAndUniqueConstraint(client, schemaName),
|
|
377
424
|
generateForeignKeys(client, schemaName),
|
|
425
|
+
generateCheckConstraints(client, schemaName),
|
|
378
426
|
]);
|
|
379
427
|
client.end();
|
|
380
|
-
const [{ tableList, fieldMap }, enumList, rawIndexMap, {
|
|
428
|
+
const [{ tableList, fieldMap }, enumList, rawIndexMap, { primaryAndUniqueConstraintMap, compositeConstraintMap }, foreignKeyList, { checks: tableCheckConstraintMap, tableConstraints: columnCheckConstraintMap },] = result;
|
|
381
429
|
// combine normal index and composite key
|
|
382
430
|
const indexMap = combineIndexAndCompositeConstraint(rawIndexMap, compositeConstraintMap);
|
|
431
|
+
const tableConstraints = { ...primaryAndUniqueConstraintMap };
|
|
432
|
+
Object.keys(columnCheckConstraintMap).forEach((tableName) => {
|
|
433
|
+
if (!tableConstraints[tableName])
|
|
434
|
+
tableConstraints[tableName] = {};
|
|
435
|
+
Object.keys(columnCheckConstraintMap[tableName]).forEach((colName) => {
|
|
436
|
+
if (!tableConstraints[tableName][colName])
|
|
437
|
+
tableConstraints[tableName][colName] = { checks: [] };
|
|
438
|
+
tableConstraints[tableName][colName].checks.push(...columnCheckConstraintMap[tableName][colName].checks);
|
|
439
|
+
});
|
|
440
|
+
});
|
|
383
441
|
return {
|
|
384
442
|
tables: tableList,
|
|
385
443
|
fields: fieldMap,
|
|
386
444
|
refs: foreignKeyList,
|
|
387
445
|
enums: enumList,
|
|
388
446
|
indexes: indexMap,
|
|
389
|
-
tableConstraints
|
|
447
|
+
tableConstraints,
|
|
448
|
+
checks: tableCheckConstraintMap,
|
|
390
449
|
};
|
|
391
450
|
}
|
|
@@ -241,7 +241,7 @@ const generateRawRefs = async (client, schemas) => {
|
|
|
241
241
|
});
|
|
242
242
|
return refs;
|
|
243
243
|
};
|
|
244
|
-
const
|
|
244
|
+
const generateIndexesAndConstraints = async (client, schemas) => {
|
|
245
245
|
const indexListSql = `
|
|
246
246
|
WITH user_tables AS (
|
|
247
247
|
SELECT
|
|
@@ -288,8 +288,8 @@ const generateIndexes = async (client, schemas) => {
|
|
|
288
288
|
ii.is_unique,
|
|
289
289
|
ii.is_primary,
|
|
290
290
|
ii.index_type,
|
|
291
|
-
ii.columns,
|
|
292
|
-
ii.expressions,
|
|
291
|
+
ii.columns AS index_columns,
|
|
292
|
+
ii.expressions AS index_expressions,
|
|
293
293
|
ii.constraint_type -- Retained constraint type here
|
|
294
294
|
FROM
|
|
295
295
|
user_tables ut
|
|
@@ -304,29 +304,29 @@ const generateIndexes = async (client, schemas) => {
|
|
|
304
304
|
;
|
|
305
305
|
`;
|
|
306
306
|
const indexListResult = await client.query(indexListSql);
|
|
307
|
-
const {
|
|
308
|
-
const { constraint_type,
|
|
309
|
-
if (
|
|
307
|
+
const { outOfLineIndexes, inlineIndexes } = indexListResult.rows.reduce((acc, row) => {
|
|
308
|
+
const { constraint_type, index_columns } = row;
|
|
309
|
+
if (index_columns === 'null' || index_columns.trim() === '')
|
|
310
310
|
return acc;
|
|
311
|
-
const isSingleColumn =
|
|
312
|
-
const
|
|
313
|
-
if (
|
|
314
|
-
acc.
|
|
311
|
+
const isSingleColumn = index_columns.split(',').length === 1;
|
|
312
|
+
const isInlineIndex = isSingleColumn && (constraint_type === 'PRIMARY KEY' || constraint_type === 'UNIQUE');
|
|
313
|
+
if (isInlineIndex) {
|
|
314
|
+
acc.inlineIndexes.push(row);
|
|
315
315
|
}
|
|
316
316
|
else {
|
|
317
|
-
acc.
|
|
317
|
+
acc.outOfLineIndexes.push(row);
|
|
318
318
|
}
|
|
319
319
|
return acc;
|
|
320
|
-
}, {
|
|
321
|
-
const indexes =
|
|
322
|
-
const { table_schema, table_name, index_name, index_type,
|
|
323
|
-
const indexColumns =
|
|
320
|
+
}, { outOfLineIndexes: [], inlineIndexes: [] });
|
|
321
|
+
const indexes = outOfLineIndexes.reduce((acc, indexRow) => {
|
|
322
|
+
const { table_schema, table_name, index_name, index_type, index_columns, index_expressions, } = indexRow;
|
|
323
|
+
const indexColumns = index_columns.split(',').map((column) => {
|
|
324
324
|
return {
|
|
325
325
|
type: 'column',
|
|
326
326
|
value: column.trim(),
|
|
327
327
|
};
|
|
328
328
|
});
|
|
329
|
-
const indexExpressions =
|
|
329
|
+
const indexExpressions = index_expressions ? index_expressions.split(',').map((expression) => {
|
|
330
330
|
return {
|
|
331
331
|
type: 'expression',
|
|
332
332
|
value: expression,
|
|
@@ -349,27 +349,94 @@ const generateIndexes = async (client, schemas) => {
|
|
|
349
349
|
}
|
|
350
350
|
return acc;
|
|
351
351
|
}, {});
|
|
352
|
-
const tableConstraints =
|
|
353
|
-
|
|
352
|
+
const tableConstraints = {};
|
|
353
|
+
inlineIndexes.forEach((row) => {
|
|
354
|
+
const { table_schema, table_name, index_columns, constraint_type, } = row;
|
|
354
355
|
const key = `${table_schema}.${table_name}`;
|
|
355
|
-
if (!
|
|
356
|
-
|
|
357
|
-
const columnNames =
|
|
356
|
+
if (!tableConstraints[key])
|
|
357
|
+
tableConstraints[key] = {};
|
|
358
|
+
const columnNames = index_columns.split(',').map((column) => column.trim());
|
|
358
359
|
columnNames.forEach((columnName) => {
|
|
359
|
-
if (!
|
|
360
|
-
|
|
360
|
+
if (!tableConstraints[key][columnName])
|
|
361
|
+
tableConstraints[key][columnName] = { checks: [] };
|
|
361
362
|
if (constraint_type === 'PRIMARY KEY') {
|
|
362
|
-
|
|
363
|
+
tableConstraints[key][columnName].pk = true;
|
|
363
364
|
}
|
|
364
|
-
if (constraint_type === 'UNIQUE' && !
|
|
365
|
-
|
|
365
|
+
if (constraint_type === 'UNIQUE' && !tableConstraints[key][columnName].pk) {
|
|
366
|
+
tableConstraints[key][columnName].unique = true;
|
|
366
367
|
}
|
|
367
368
|
});
|
|
368
|
-
return acc;
|
|
369
369
|
}, {});
|
|
370
|
+
const checkListSql = `
|
|
371
|
+
SELECT
|
|
372
|
+
n.nspname AS table_schema,
|
|
373
|
+
c.relname AS table_name,
|
|
374
|
+
MAX(a.attname) AS column_name,
|
|
375
|
+
con.conname AS check_name,
|
|
376
|
+
pg_get_constraintdef(con.oid) AS check_definition,
|
|
377
|
+
CASE
|
|
378
|
+
WHEN COUNT(*) = 1 THEN TRUE
|
|
379
|
+
ELSE FALSE
|
|
380
|
+
END AS is_column_constraint
|
|
381
|
+
FROM
|
|
382
|
+
pg_catalog.pg_constraint AS con
|
|
383
|
+
JOIN
|
|
384
|
+
pg_catalog.pg_class AS c
|
|
385
|
+
ON con.conrelid = c.oid
|
|
386
|
+
LEFT JOIN
|
|
387
|
+
pg_catalog.pg_namespace AS n
|
|
388
|
+
ON c.relnamespace = n.oid
|
|
389
|
+
LEFT JOIN
|
|
390
|
+
pg_catalog.pg_attribute AS a
|
|
391
|
+
ON a.attrelid = c.oid AND a.attnum = ANY(con.conkey)
|
|
392
|
+
WHERE con.contype = 'c' -- 'c' for CHECK constraints
|
|
393
|
+
${(0, parseSchema_1.buildSchemaQuery)('n.nspname', schemas)}
|
|
394
|
+
GROUP BY
|
|
395
|
+
con.conname,
|
|
396
|
+
n.nspname,
|
|
397
|
+
c.relname,
|
|
398
|
+
con.oid
|
|
399
|
+
`;
|
|
400
|
+
const checkListResult = await client.query(checkListSql);
|
|
401
|
+
const checks = {};
|
|
402
|
+
checkListResult.rows.forEach((row) => {
|
|
403
|
+
const { table_schema, table_name, check_name, check_definition, column_name, is_column_constraint, } = row;
|
|
404
|
+
if (typeof table_schema !== 'string' || typeof table_name !== 'string' || typeof check_definition !== 'string') {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (!check_definition.match(/CHECK \(\(.*\)\)/)) {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
if (is_column_constraint && typeof column_name !== 'string') {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
const key = `${table_schema}.${table_name}`;
|
|
414
|
+
if (is_column_constraint) {
|
|
415
|
+
if (!tableConstraints[key])
|
|
416
|
+
tableConstraints[key] = {};
|
|
417
|
+
if (!tableConstraints[key][column_name])
|
|
418
|
+
tableConstraints[key][column_name] = { checks: [] };
|
|
419
|
+
tableConstraints[key][column_name].checks.push({
|
|
420
|
+
name: check_name || undefined,
|
|
421
|
+
// The check definition has the form: `CHECK ((expr))`
|
|
422
|
+
// The expression starts at 8 and ends at -2
|
|
423
|
+
expression: check_definition.slice(8, -2),
|
|
424
|
+
});
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
if (!checks[key])
|
|
428
|
+
checks[key] = [];
|
|
429
|
+
checks[key].push({
|
|
430
|
+
name: check_name || undefined,
|
|
431
|
+
// The check definition has the form: `CHECK ((expr))`
|
|
432
|
+
// The expression starts at 8 and ends at -2
|
|
433
|
+
expression: check_definition.slice(8, -2),
|
|
434
|
+
});
|
|
435
|
+
});
|
|
370
436
|
return {
|
|
371
437
|
indexes,
|
|
372
438
|
tableConstraints,
|
|
439
|
+
checks,
|
|
373
440
|
};
|
|
374
441
|
};
|
|
375
442
|
const generateRawEnums = async (client, schemas) => {
|
|
@@ -414,7 +481,7 @@ const fetchSchemaJson = async (connection) => {
|
|
|
414
481
|
const { connectionString, schemas } = (0, parseSchema_1.parseConnectionString)(connection, 'jdbc');
|
|
415
482
|
const client = await getValidatedClient(connectionString);
|
|
416
483
|
const tablesAndFieldsRes = generateTablesAndFields(client, schemas);
|
|
417
|
-
const indexesRes =
|
|
484
|
+
const indexesRes = generateIndexesAndConstraints(client, schemas);
|
|
418
485
|
const refsRes = generateRawRefs(client, schemas);
|
|
419
486
|
const enumsRes = generateRawEnums(client, schemas);
|
|
420
487
|
const res = await Promise.all([
|
|
@@ -425,7 +492,7 @@ const fetchSchemaJson = async (connection) => {
|
|
|
425
492
|
]);
|
|
426
493
|
client.end();
|
|
427
494
|
const { tables, fields } = res[0];
|
|
428
|
-
const { indexes, tableConstraints } = res[1];
|
|
495
|
+
const { indexes, tableConstraints, checks } = res[1];
|
|
429
496
|
const refs = res[2];
|
|
430
497
|
const enums = res[3];
|
|
431
498
|
return {
|
|
@@ -435,6 +502,7 @@ const fetchSchemaJson = async (connection) => {
|
|
|
435
502
|
enums,
|
|
436
503
|
indexes,
|
|
437
504
|
tableConstraints,
|
|
505
|
+
checks,
|
|
438
506
|
};
|
|
439
507
|
};
|
|
440
508
|
exports.fetchSchemaJson = fetchSchemaJson;
|