@dbml/connector 5.0.0 → 5.2.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 +238 -113
- package/__tests__/connectors/mysql/expect-out-files/schema.json +132 -2
- package/__tests__/connectors/postgres/expect-out-files/schema.json +219 -8
- package/dist/connectors/bigqueryConnector.js +5 -4
- package/dist/connectors/mssqlConnector.js +82 -149
- package/dist/connectors/mysqlConnector.js +67 -8
- package/dist/connectors/postgresConnector.d.ts +1 -1
- package/dist/connectors/postgresConnector.js +115 -42
- package/dist/connectors/snowflakeConnector.js +8 -6
- package/dist/connectors/types.d.ts +10 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +17 -7
- package/dist/utils/helpers.d.ts +2 -1
- package/dist/utils/helpers.js +14 -3
- package/dist/utils/parseSchema.d.ts +1 -1
- package/dist/utils/parseSchema.js +13 -13
- package/eslint.config.ts +70 -0
- package/jest.config.ts +2 -2
- package/package.json +15 -6
- package/src/connectors/bigqueryConnector.ts +5 -4
- package/src/connectors/mssqlConnector.ts +92 -162
- package/src/connectors/mysqlConnector.ts +77 -12
- package/src/connectors/postgresConnector.ts +126 -44
- package/src/connectors/snowflakeConnector.ts +24 -19
- package/src/connectors/types.ts +20 -65
- package/src/index.ts +1 -1
- package/src/utils/helpers.ts +30 -12
- package/src/utils/parseSchema.ts +11 -11
- package/test-output.log +18 -0
- package/tsconfig.json +1 -1
- package/.eslintrc.json +0 -24
|
@@ -4,7 +4,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.fetchSchemaJson = void 0;
|
|
7
|
-
/*
|
|
7
|
+
/* TDD 72
|
|
8
|
+
* - In MSSQL, there's no notion of ENUM.
|
|
9
|
+
* - 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`.
|
|
10
|
+
* - 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.
|
|
11
|
+
* */
|
|
8
12
|
const mssql_1 = __importDefault(require("mssql"));
|
|
9
13
|
const parseSchema_1 = require("../utils/parseSchema");
|
|
10
14
|
// https://learn.microsoft.com/en-us/sql/t-sql/data-types/date-and-time-types?view=sql-server-ver15
|
|
@@ -88,63 +92,6 @@ const getDbdefault = (data_type, column_default, default_type) => {
|
|
|
88
92
|
value: default_type === 'string' ? value.slice(1, -1) : value, // Remove the quotes for string values
|
|
89
93
|
};
|
|
90
94
|
};
|
|
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
95
|
const generateField = (row) => {
|
|
149
96
|
const { column_name, data_type, character_maximum_length, numeric_precision, numeric_scale, identity_increment, is_nullable, column_default, default_type, column_comment, } = row;
|
|
150
97
|
const dbdefault = column_default && default_type !== 'increment' ? getDbdefault(data_type, column_default, default_type) : null;
|
|
@@ -161,10 +108,8 @@ const generateField = (row) => {
|
|
|
161
108
|
note: column_comment ? { value: column_comment } : { value: '' },
|
|
162
109
|
};
|
|
163
110
|
};
|
|
164
|
-
const
|
|
165
|
-
const checkConstraintDefinitionDedup = {};
|
|
111
|
+
const generateTablesFields = async (client, schemas) => {
|
|
166
112
|
const fields = {};
|
|
167
|
-
const enums = [];
|
|
168
113
|
const tablesAndFieldsSql = `
|
|
169
114
|
WITH tables_and_fields AS (
|
|
170
115
|
SELECT
|
|
@@ -203,35 +148,6 @@ const generateTablesFieldsAndEnums = async (client, schemas) => {
|
|
|
203
148
|
AND ep.name like '%description%'
|
|
204
149
|
WHERE
|
|
205
150
|
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
151
|
)
|
|
236
152
|
SELECT
|
|
237
153
|
table_schema,
|
|
@@ -246,17 +162,15 @@ const generateTablesFieldsAndEnums = async (client, schemas) => {
|
|
|
246
162
|
column_default,
|
|
247
163
|
table_comment,
|
|
248
164
|
column_comment,
|
|
249
|
-
check_constraint_name,
|
|
250
|
-
check_constraint_definition,
|
|
251
165
|
CASE
|
|
252
166
|
WHEN column_default LIKE '((%))' THEN 'number'
|
|
253
167
|
WHEN column_default LIKE '(''%'')' THEN 'string'
|
|
254
168
|
ELSE 'expression'
|
|
255
169
|
END AS default_type
|
|
256
170
|
FROM
|
|
257
|
-
|
|
171
|
+
tables_and_fields
|
|
258
172
|
WHERE
|
|
259
|
-
|
|
173
|
+
1 = 1
|
|
260
174
|
${(0, parseSchema_1.buildSchemaQuery)('table_schema', schemas)}
|
|
261
175
|
ORDER BY
|
|
262
176
|
table_schema,
|
|
@@ -266,7 +180,7 @@ const generateTablesFieldsAndEnums = async (client, schemas) => {
|
|
|
266
180
|
`;
|
|
267
181
|
const tablesAndFieldsResult = await client.query(tablesAndFieldsSql);
|
|
268
182
|
const tables = tablesAndFieldsResult.recordset.reduce((acc, row) => {
|
|
269
|
-
const { table_schema, table_name, table_comment,
|
|
183
|
+
const { table_schema, table_name, table_comment, } = row;
|
|
270
184
|
const key = `${table_schema}.${table_name}`;
|
|
271
185
|
if (!acc[key]) {
|
|
272
186
|
acc[key] = {
|
|
@@ -278,34 +192,12 @@ const generateTablesFieldsAndEnums = async (client, schemas) => {
|
|
|
278
192
|
if (!fields[key])
|
|
279
193
|
fields[key] = [];
|
|
280
194
|
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
195
|
fields[key].push(field);
|
|
303
196
|
return acc;
|
|
304
197
|
}, {});
|
|
305
198
|
return {
|
|
306
199
|
tables: Object.values(tables),
|
|
307
200
|
fields,
|
|
308
|
-
enums,
|
|
309
201
|
};
|
|
310
202
|
};
|
|
311
203
|
const generateRefs = async (client, schemas) => {
|
|
@@ -372,14 +264,14 @@ const generateRefs = async (client, schemas) => {
|
|
|
372
264
|
onDelete: on_delete === 'NO_ACTION' ? null : on_delete,
|
|
373
265
|
onUpdate: on_update === 'NO_ACTION' ? null : on_update,
|
|
374
266
|
};
|
|
375
|
-
const isDuplicate = refs.some(ref => endpointsEqual(ref.endpoints, newRef.endpoints));
|
|
267
|
+
const isDuplicate = refs.some((ref) => endpointsEqual(ref.endpoints, newRef.endpoints));
|
|
376
268
|
if (!isDuplicate) {
|
|
377
269
|
refs.push(newRef);
|
|
378
270
|
}
|
|
379
271
|
});
|
|
380
272
|
return refs;
|
|
381
273
|
};
|
|
382
|
-
const
|
|
274
|
+
const generateIndexesAndConstraints = async (client, schemas) => {
|
|
383
275
|
const indexListSql = `
|
|
384
276
|
WITH user_tables AS (
|
|
385
277
|
SELECT
|
|
@@ -455,32 +347,34 @@ const generateIndexes = async (client, schemas) => {
|
|
|
455
347
|
ii.index_name;
|
|
456
348
|
`;
|
|
457
349
|
const indexListResult = await client.query(indexListSql);
|
|
458
|
-
const {
|
|
350
|
+
const { outOfLineIndexes, inlineIndexes } = indexListResult.recordset.reduce((acc, row) => {
|
|
459
351
|
const { constraint_type, columns } = row;
|
|
460
352
|
if (columns === 'null' || columns.trim() === '')
|
|
461
353
|
return acc;
|
|
462
354
|
if (constraint_type === 'PRIMARY KEY' || constraint_type === 'UNIQUE') {
|
|
463
|
-
acc.
|
|
355
|
+
acc.inlineIndexes.push(row);
|
|
464
356
|
}
|
|
465
357
|
else {
|
|
466
|
-
acc.
|
|
358
|
+
acc.outOfLineIndexes.push(row);
|
|
467
359
|
}
|
|
468
360
|
return acc;
|
|
469
|
-
}, {
|
|
470
|
-
const indexes =
|
|
471
|
-
const { table_schema, table_name, index_name, index_type, columns, expressions, } = indexRow;
|
|
361
|
+
}, { inlineIndexes: [], outOfLineIndexes: [] });
|
|
362
|
+
const indexes = outOfLineIndexes.reduce((acc, indexRow) => {
|
|
363
|
+
const { table_schema, table_name, index_name, index_type, columns, expressions, is_unique, is_primary, } = indexRow;
|
|
472
364
|
const indexColumns = columns.split(',').map((column) => {
|
|
473
365
|
return {
|
|
474
366
|
type: 'column',
|
|
475
367
|
value: column.trim(),
|
|
476
368
|
};
|
|
477
369
|
});
|
|
478
|
-
const indexExpressions = expressions
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
370
|
+
const indexExpressions = expressions
|
|
371
|
+
? expressions.split(',').map((expression) => {
|
|
372
|
+
return {
|
|
373
|
+
type: 'expression',
|
|
374
|
+
value: expression,
|
|
375
|
+
};
|
|
376
|
+
})
|
|
377
|
+
: [];
|
|
484
378
|
const index = {
|
|
485
379
|
name: index_name,
|
|
486
380
|
type: index_type,
|
|
@@ -488,6 +382,8 @@ const generateIndexes = async (client, schemas) => {
|
|
|
488
382
|
...indexColumns,
|
|
489
383
|
...indexExpressions,
|
|
490
384
|
],
|
|
385
|
+
pk: !!is_primary,
|
|
386
|
+
unique: !is_primary && !!is_unique,
|
|
491
387
|
};
|
|
492
388
|
const key = `${table_schema}.${table_name}`;
|
|
493
389
|
if (acc[key]) {
|
|
@@ -498,51 +394,88 @@ const generateIndexes = async (client, schemas) => {
|
|
|
498
394
|
}
|
|
499
395
|
return acc;
|
|
500
396
|
}, {});
|
|
501
|
-
const tableConstraints =
|
|
397
|
+
const tableConstraints = {};
|
|
398
|
+
inlineIndexes.forEach((row) => {
|
|
502
399
|
const { table_schema, table_name, columns, constraint_type, } = row;
|
|
503
400
|
const key = `${table_schema}.${table_name}`;
|
|
504
|
-
if (!
|
|
505
|
-
|
|
401
|
+
if (!tableConstraints[key])
|
|
402
|
+
tableConstraints[key] = {};
|
|
506
403
|
const columnNames = columns.split(',').map((column) => column.trim());
|
|
507
404
|
columnNames.forEach((columnName) => {
|
|
508
|
-
if (!
|
|
509
|
-
|
|
405
|
+
if (!tableConstraints[key][columnName])
|
|
406
|
+
tableConstraints[key][columnName] = { checks: [] };
|
|
510
407
|
if (constraint_type === 'PRIMARY KEY') {
|
|
511
|
-
|
|
408
|
+
tableConstraints[key][columnName].pk = true;
|
|
512
409
|
}
|
|
513
|
-
if (constraint_type === 'UNIQUE' && !
|
|
514
|
-
|
|
410
|
+
if (constraint_type === 'UNIQUE' && !tableConstraints[key][columnName].pk) {
|
|
411
|
+
tableConstraints[key][columnName].unique = true;
|
|
515
412
|
}
|
|
516
413
|
});
|
|
517
|
-
|
|
518
|
-
|
|
414
|
+
});
|
|
415
|
+
const checkListSql = `
|
|
416
|
+
SELECT
|
|
417
|
+
OBJECT_SCHEMA_NAME(cc.parent_object_id) AS table_schema,
|
|
418
|
+
OBJECT_NAME(cc.parent_object_id) AS table_name,
|
|
419
|
+
c.name AS column_name,
|
|
420
|
+
cc.name AS check_name,
|
|
421
|
+
cc.definition AS check_expression
|
|
422
|
+
FROM
|
|
423
|
+
sys.check_constraints cc
|
|
424
|
+
LEFT JOIN
|
|
425
|
+
sys.columns c ON cc.parent_object_id = c.object_id AND cc.parent_column_id = c.column_id
|
|
426
|
+
WHERE 1 = 1
|
|
427
|
+
${(0, parseSchema_1.buildSchemaQuery)('OBJECT_SCHEMA_NAME(cc.parent_object_id)', schemas)}
|
|
428
|
+
`;
|
|
429
|
+
const checkListResult = await client.query(checkListSql);
|
|
430
|
+
const checks = {};
|
|
431
|
+
checkListResult.recordset.forEach((row) => {
|
|
432
|
+
const { table_schema, table_name, column_name, check_name, check_expression, } = row;
|
|
433
|
+
const key = `${table_schema}.${table_name}`;
|
|
434
|
+
if (column_name) {
|
|
435
|
+
if (!tableConstraints[key])
|
|
436
|
+
tableConstraints[key] = {};
|
|
437
|
+
if (!tableConstraints[key][column_name])
|
|
438
|
+
tableConstraints[key][column_name] = { checks: [] };
|
|
439
|
+
// The check definition has the form: `(expr)`
|
|
440
|
+
// The expression starts at 1 and ends at -1
|
|
441
|
+
tableConstraints[key][column_name].checks.push({ name: check_name, expression: check_expression.slice(1, -1) });
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
if (!checks[key])
|
|
445
|
+
checks[key] = [];
|
|
446
|
+
// The check definition has the form: `(expr)`
|
|
447
|
+
// The expression starts at 1 and ends at -1
|
|
448
|
+
checks[key].push({ name: check_name, expression: check_expression.slice(1, -1) });
|
|
449
|
+
});
|
|
519
450
|
return {
|
|
520
451
|
indexes,
|
|
521
452
|
tableConstraints,
|
|
453
|
+
checks,
|
|
522
454
|
};
|
|
523
455
|
};
|
|
524
456
|
const fetchSchemaJson = async (connection) => {
|
|
525
457
|
const { connectionString, schemas } = (0, parseSchema_1.parseConnectionString)(connection, 'odbc');
|
|
526
458
|
const client = await getValidatedClient(connectionString);
|
|
527
|
-
const
|
|
528
|
-
const
|
|
459
|
+
const tablesRes = generateTablesFields(client, schemas);
|
|
460
|
+
const indexesAndConstraintsRes = generateIndexesAndConstraints(client, schemas);
|
|
529
461
|
const refsRes = generateRefs(client, schemas);
|
|
530
462
|
const res = await Promise.all([
|
|
531
|
-
|
|
532
|
-
|
|
463
|
+
tablesRes,
|
|
464
|
+
indexesAndConstraintsRes,
|
|
533
465
|
refsRes,
|
|
534
466
|
]);
|
|
535
|
-
|
|
536
|
-
const {
|
|
537
|
-
const { indexes, tableConstraints } = res[1];
|
|
467
|
+
const { tables, fields } = res[0];
|
|
468
|
+
const { indexes, tableConstraints, checks } = res[1];
|
|
538
469
|
const refs = res[2];
|
|
539
470
|
return {
|
|
540
471
|
tables,
|
|
541
472
|
fields,
|
|
542
|
-
enums
|
|
473
|
+
// MSSQL doesn't support the notion of enums
|
|
474
|
+
enums: [],
|
|
543
475
|
refs,
|
|
544
476
|
indexes,
|
|
545
477
|
tableConstraints,
|
|
478
|
+
checks,
|
|
546
479
|
};
|
|
547
480
|
};
|
|
548
481
|
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]) {
|
|
@@ -264,7 +264,7 @@ async function generatePrimaryAndUniqueConstraint(client, schemaName) {
|
|
|
264
264
|
};
|
|
265
265
|
}
|
|
266
266
|
if (constraintType === 'PRIMARY KEY') {
|
|
267
|
-
acc[key][constraintName].
|
|
267
|
+
acc[key][constraintName].pk = true;
|
|
268
268
|
}
|
|
269
269
|
if (constraintType === 'UNIQUE') {
|
|
270
270
|
acc[key][constraintName].unique = true;
|
|
@@ -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
|
}
|