@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
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
/* eslint-disable camelcase */
|
|
2
1
|
import { Client } from 'pg';
|
|
3
2
|
import { buildSchemaQuery, parseConnectionString } from '../utils/parseSchema';
|
|
4
3
|
import {
|
|
4
|
+
CheckConstraintDictionary,
|
|
5
5
|
DatabaseSchema,
|
|
6
6
|
DefaultInfo,
|
|
7
7
|
DefaultType,
|
|
@@ -96,13 +96,15 @@ const generateField = (row: Record<string, any>): Field => {
|
|
|
96
96
|
|
|
97
97
|
const dbdefault = column_default && default_type !== 'increment' ? getDbdefault(data_type, column_default, default_type) : null;
|
|
98
98
|
|
|
99
|
-
const fieldType = data_type === 'USER-DEFINED'
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
99
|
+
const fieldType = data_type === 'USER-DEFINED'
|
|
100
|
+
? {
|
|
101
|
+
type_name: udt_name,
|
|
102
|
+
schemaName: udt_schema,
|
|
103
|
+
}
|
|
104
|
+
: {
|
|
105
|
+
type_name: getFieldType(data_type, udt_name, character_maximum_length, numeric_precision, numeric_scale),
|
|
106
|
+
schemaName: null,
|
|
107
|
+
};
|
|
106
108
|
|
|
107
109
|
return {
|
|
108
110
|
name: column_name,
|
|
@@ -115,8 +117,8 @@ const generateField = (row: Record<string, any>): Field => {
|
|
|
115
117
|
};
|
|
116
118
|
|
|
117
119
|
const generateTablesAndFields = async (client: Client, schemas: string[]): Promise<{
|
|
118
|
-
tables: Table[]
|
|
119
|
-
fields: FieldsDictionary
|
|
120
|
+
tables: Table[];
|
|
121
|
+
fields: FieldsDictionary;
|
|
120
122
|
}> => {
|
|
121
123
|
const fields: FieldsDictionary = {};
|
|
122
124
|
const tablesAndFieldsSql = `
|
|
@@ -301,7 +303,7 @@ const generateRawRefs = async (client: Client, schemas: string[]): Promise<Ref[]
|
|
|
301
303
|
return refs;
|
|
302
304
|
};
|
|
303
305
|
|
|
304
|
-
const
|
|
306
|
+
const generateIndexesAndConstraints = async (client: Client, schemas: string[]) => {
|
|
305
307
|
const indexListSql = `
|
|
306
308
|
WITH user_tables AS (
|
|
307
309
|
SELECT
|
|
@@ -348,8 +350,8 @@ const generateIndexes = async (client: Client, schemas: string[]) => {
|
|
|
348
350
|
ii.is_unique,
|
|
349
351
|
ii.is_primary,
|
|
350
352
|
ii.index_type,
|
|
351
|
-
ii.columns,
|
|
352
|
-
ii.expressions,
|
|
353
|
+
ii.columns AS index_columns,
|
|
354
|
+
ii.expressions AS index_expressions,
|
|
353
355
|
ii.constraint_type -- Retained constraint type here
|
|
354
356
|
FROM
|
|
355
357
|
user_tables ut
|
|
@@ -364,43 +366,47 @@ const generateIndexes = async (client: Client, schemas: string[]) => {
|
|
|
364
366
|
;
|
|
365
367
|
`;
|
|
366
368
|
const indexListResult = await client.query(indexListSql);
|
|
367
|
-
const {
|
|
368
|
-
const { constraint_type,
|
|
369
|
+
const { outOfLineIndexes, inlineIndexes } = indexListResult.rows.reduce((acc, row) => {
|
|
370
|
+
const { constraint_type, index_columns } = row;
|
|
369
371
|
|
|
370
|
-
if (
|
|
372
|
+
if (index_columns === 'null' || index_columns.trim() === '') return acc;
|
|
371
373
|
|
|
372
|
-
const isSingleColumn =
|
|
373
|
-
const
|
|
374
|
-
if (
|
|
375
|
-
acc.
|
|
374
|
+
const isSingleColumn = index_columns.split(',').length === 1;
|
|
375
|
+
const isInlineIndex = isSingleColumn && (constraint_type === 'PRIMARY KEY' || constraint_type === 'UNIQUE');
|
|
376
|
+
if (isInlineIndex) {
|
|
377
|
+
acc.inlineIndexes.push(row);
|
|
376
378
|
} else {
|
|
377
|
-
acc.
|
|
379
|
+
acc.outOfLineIndexes.push(row);
|
|
378
380
|
}
|
|
379
381
|
return acc;
|
|
380
|
-
}, {
|
|
382
|
+
}, { outOfLineIndexes: [], inlineIndexes: [] });
|
|
381
383
|
|
|
382
|
-
const indexes =
|
|
384
|
+
const indexes = outOfLineIndexes.reduce((acc: IndexesDictionary, indexRow: Record<string, any>) => {
|
|
383
385
|
const {
|
|
384
386
|
table_schema,
|
|
385
387
|
table_name,
|
|
386
388
|
index_name,
|
|
387
389
|
index_type,
|
|
388
|
-
|
|
389
|
-
|
|
390
|
+
index_columns,
|
|
391
|
+
index_expressions,
|
|
392
|
+
is_primary,
|
|
393
|
+
is_unique,
|
|
390
394
|
} = indexRow;
|
|
391
|
-
const indexColumns =
|
|
395
|
+
const indexColumns = index_columns.split(',').map((column: string) => {
|
|
392
396
|
return {
|
|
393
397
|
type: 'column',
|
|
394
398
|
value: column.trim(),
|
|
395
399
|
};
|
|
396
400
|
});
|
|
397
401
|
|
|
398
|
-
const indexExpressions =
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
402
|
+
const indexExpressions = index_expressions
|
|
403
|
+
? index_expressions.split(',').map((expression: string) => {
|
|
404
|
+
return {
|
|
405
|
+
type: 'expression',
|
|
406
|
+
value: expression,
|
|
407
|
+
};
|
|
408
|
+
})
|
|
409
|
+
: [];
|
|
404
410
|
|
|
405
411
|
const index = {
|
|
406
412
|
name: index_name,
|
|
@@ -409,6 +415,8 @@ const generateIndexes = async (client: Client, schemas: string[]) => {
|
|
|
409
415
|
...indexColumns,
|
|
410
416
|
...indexExpressions,
|
|
411
417
|
],
|
|
418
|
+
pk: !!is_primary,
|
|
419
|
+
unique: !is_primary && !!is_unique,
|
|
412
420
|
};
|
|
413
421
|
|
|
414
422
|
const key = `${table_schema}.${table_name}`;
|
|
@@ -421,32 +429,105 @@ const generateIndexes = async (client: Client, schemas: string[]) => {
|
|
|
421
429
|
return acc;
|
|
422
430
|
}, {});
|
|
423
431
|
|
|
424
|
-
const tableConstraints
|
|
432
|
+
const tableConstraints: TableConstraintsDictionary = {};
|
|
433
|
+
|
|
434
|
+
inlineIndexes.forEach((row: Record<string, any>) => {
|
|
425
435
|
const {
|
|
426
436
|
table_schema,
|
|
427
437
|
table_name,
|
|
428
|
-
|
|
438
|
+
index_columns,
|
|
429
439
|
constraint_type,
|
|
430
440
|
} = row;
|
|
431
441
|
const key = `${table_schema}.${table_name}`;
|
|
432
|
-
if (!
|
|
442
|
+
if (!tableConstraints[key]) tableConstraints[key] = {};
|
|
433
443
|
|
|
434
|
-
const columnNames =
|
|
444
|
+
const columnNames = index_columns.split(',').map((column: string) => column.trim());
|
|
435
445
|
columnNames.forEach((columnName: string) => {
|
|
436
|
-
if (!
|
|
446
|
+
if (!tableConstraints[key][columnName]) tableConstraints[key][columnName] = { checks: [] };
|
|
437
447
|
if (constraint_type === 'PRIMARY KEY') {
|
|
438
|
-
|
|
448
|
+
tableConstraints[key][columnName].pk = true;
|
|
439
449
|
}
|
|
440
|
-
if (constraint_type === 'UNIQUE' && !
|
|
441
|
-
|
|
450
|
+
if (constraint_type === 'UNIQUE' && !tableConstraints[key][columnName].pk) {
|
|
451
|
+
tableConstraints[key][columnName].unique = true;
|
|
442
452
|
}
|
|
443
453
|
});
|
|
444
|
-
return acc;
|
|
445
454
|
}, {});
|
|
446
455
|
|
|
456
|
+
const checkListSql = `
|
|
457
|
+
SELECT
|
|
458
|
+
n.nspname AS table_schema,
|
|
459
|
+
c.relname AS table_name,
|
|
460
|
+
MAX(a.attname) AS column_name,
|
|
461
|
+
con.conname AS check_name,
|
|
462
|
+
pg_get_constraintdef(con.oid) AS check_definition,
|
|
463
|
+
CASE
|
|
464
|
+
WHEN COUNT(*) = 1 THEN TRUE
|
|
465
|
+
ELSE FALSE
|
|
466
|
+
END AS is_column_constraint
|
|
467
|
+
FROM
|
|
468
|
+
pg_catalog.pg_constraint AS con
|
|
469
|
+
JOIN
|
|
470
|
+
pg_catalog.pg_class AS c
|
|
471
|
+
ON con.conrelid = c.oid
|
|
472
|
+
LEFT JOIN
|
|
473
|
+
pg_catalog.pg_namespace AS n
|
|
474
|
+
ON c.relnamespace = n.oid
|
|
475
|
+
LEFT JOIN
|
|
476
|
+
pg_catalog.pg_attribute AS a
|
|
477
|
+
ON a.attrelid = c.oid AND a.attnum = ANY(con.conkey)
|
|
478
|
+
WHERE con.contype = 'c' -- 'c' for CHECK constraints
|
|
479
|
+
${buildSchemaQuery('n.nspname', schemas)}
|
|
480
|
+
GROUP BY
|
|
481
|
+
con.conname,
|
|
482
|
+
n.nspname,
|
|
483
|
+
c.relname,
|
|
484
|
+
con.oid
|
|
485
|
+
`;
|
|
486
|
+
const checkListResult = await client.query(checkListSql);
|
|
487
|
+
const checks: CheckConstraintDictionary = {};
|
|
488
|
+
checkListResult.rows.forEach((row) => {
|
|
489
|
+
const {
|
|
490
|
+
table_schema,
|
|
491
|
+
table_name,
|
|
492
|
+
check_name,
|
|
493
|
+
check_definition,
|
|
494
|
+
column_name,
|
|
495
|
+
is_column_constraint,
|
|
496
|
+
} = row;
|
|
497
|
+
if (typeof table_schema !== 'string' || typeof table_name !== 'string' || typeof check_definition !== 'string') {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
if (!check_definition.match(/CHECK \(\(.*\)\)/)) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
if (is_column_constraint && typeof column_name !== 'string') {
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
const key = `${table_schema}.${table_name}`;
|
|
507
|
+
if (is_column_constraint) {
|
|
508
|
+
if (!tableConstraints[key]) tableConstraints[key] = {};
|
|
509
|
+
if (!tableConstraints[key][column_name]) tableConstraints[key][column_name] = { checks: [] };
|
|
510
|
+
tableConstraints[key][column_name].checks.push({
|
|
511
|
+
name: check_name || undefined,
|
|
512
|
+
// The check definition has the form: `CHECK ((expr))`
|
|
513
|
+
// The expression starts at 8 and ends at -2
|
|
514
|
+
expression: check_definition.slice(8, -2),
|
|
515
|
+
});
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
if (!checks[key]) checks[key] = [];
|
|
519
|
+
checks[key].push({
|
|
520
|
+
name: check_name || undefined,
|
|
521
|
+
// The check definition has the form: `CHECK ((expr))`
|
|
522
|
+
// The expression starts at 8 and ends at -2
|
|
523
|
+
expression: check_definition.slice(8, -2),
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
|
|
447
527
|
return {
|
|
448
528
|
indexes,
|
|
449
529
|
tableConstraints,
|
|
530
|
+
checks,
|
|
450
531
|
};
|
|
451
532
|
};
|
|
452
533
|
|
|
@@ -496,7 +577,7 @@ const fetchSchemaJson = async (connection: string): Promise<DatabaseSchema> => {
|
|
|
496
577
|
const client = await getValidatedClient(connectionString);
|
|
497
578
|
|
|
498
579
|
const tablesAndFieldsRes = generateTablesAndFields(client, schemas);
|
|
499
|
-
const indexesRes =
|
|
580
|
+
const indexesRes = generateIndexesAndConstraints(client, schemas);
|
|
500
581
|
const refsRes = generateRawRefs(client, schemas);
|
|
501
582
|
const enumsRes = generateRawEnums(client, schemas);
|
|
502
583
|
|
|
@@ -509,7 +590,7 @@ const fetchSchemaJson = async (connection: string): Promise<DatabaseSchema> => {
|
|
|
509
590
|
client.end();
|
|
510
591
|
|
|
511
592
|
const { tables, fields } = res[0];
|
|
512
|
-
const { indexes, tableConstraints } = res[1];
|
|
593
|
+
const { indexes, tableConstraints, checks } = res[1];
|
|
513
594
|
const refs = res[2];
|
|
514
595
|
const enums = res[3];
|
|
515
596
|
|
|
@@ -520,9 +601,10 @@ const fetchSchemaJson = async (connection: string): Promise<DatabaseSchema> => {
|
|
|
520
601
|
enums,
|
|
521
602
|
indexes,
|
|
522
603
|
tableConstraints,
|
|
604
|
+
checks,
|
|
523
605
|
};
|
|
524
606
|
};
|
|
525
607
|
|
|
526
608
|
export {
|
|
527
|
-
fetchSchemaJson
|
|
609
|
+
fetchSchemaJson,
|
|
528
610
|
};
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable camelcase */
|
|
2
1
|
//
|
|
3
2
|
// Description:
|
|
4
3
|
// Snowflake info schema lacks reference direction for relationships.
|
|
@@ -43,12 +42,11 @@ const parseConnectionString = (connectionString: string): Record<string, string>
|
|
|
43
42
|
let match;
|
|
44
43
|
|
|
45
44
|
while ((match = regex.exec(connectionString)) !== null) {
|
|
46
|
-
|
|
45
|
+
params[match[1].trim()] = match[2].trim();
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
return params;
|
|
50
|
-
}
|
|
51
|
-
|
|
49
|
+
};
|
|
52
50
|
|
|
53
51
|
const connect = async (connection: Connection): Promise<void> => {
|
|
54
52
|
return new Promise((resolve, reject) => {
|
|
@@ -61,13 +59,13 @@ const connect = async (connection: Connection): Promise<void> => {
|
|
|
61
59
|
}
|
|
62
60
|
});
|
|
63
61
|
});
|
|
64
|
-
}
|
|
62
|
+
};
|
|
65
63
|
|
|
66
64
|
const executeQuery = (connection: Connection, sqlText: string): Promise<any[]> => {
|
|
67
65
|
return new Promise((resolve, reject) => {
|
|
68
66
|
console.log('Executing query...');
|
|
69
67
|
connection.execute({
|
|
70
|
-
sqlText
|
|
68
|
+
sqlText,
|
|
71
69
|
complete: (err, stmt, rows) => {
|
|
72
70
|
if (err) {
|
|
73
71
|
reject(err);
|
|
@@ -77,14 +75,14 @@ const executeQuery = (connection: Connection, sqlText: string): Promise<any[]> =
|
|
|
77
75
|
resolve(rows);
|
|
78
76
|
}
|
|
79
77
|
}
|
|
80
|
-
}
|
|
78
|
+
},
|
|
81
79
|
});
|
|
82
80
|
});
|
|
83
|
-
}
|
|
81
|
+
};
|
|
84
82
|
|
|
85
83
|
const isLogLevel = (value: string): value is LogLevel => {
|
|
86
|
-
return ['ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE'].includes(value as LogLevel)
|
|
87
|
-
}
|
|
84
|
+
return ['ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE'].includes(value as LogLevel);
|
|
85
|
+
};
|
|
88
86
|
|
|
89
87
|
const connectToSnowflake = async (config: Record<string, string>): Promise<Connection> => {
|
|
90
88
|
const logLevel = isLogLevel(config.LOG_LEVEL) ? config.LOG_LEVEL : 'INFO';
|
|
@@ -197,8 +195,8 @@ const generateField = (row: Record<string, any>): Field => {
|
|
|
197
195
|
};
|
|
198
196
|
|
|
199
197
|
const generateTablesAndFields = async (conn: Connection, schemas: string[]): Promise<{
|
|
200
|
-
tables: Table[]
|
|
201
|
-
fields: FieldsDictionary
|
|
198
|
+
tables: Table[];
|
|
199
|
+
fields: FieldsDictionary;
|
|
202
200
|
}> => {
|
|
203
201
|
const fields: FieldsDictionary = {};
|
|
204
202
|
const schemaSql = schemas.length > 0 ? `AND c.table_schema IN (${schemas.map((schema) => `'${schema}'`).join(',')})` : '';
|
|
@@ -266,14 +264,18 @@ const generateTablesAndFields = async (conn: Connection, schemas: string[]): Pro
|
|
|
266
264
|
|
|
267
265
|
const createConstraintKeysMap = (keys: Record<string, string>[], schemas: string[], constraintType: 'primary' | 'unique'): Record<string, ConstraintRow> => {
|
|
268
266
|
return keys.reduce((acc: Record<string, ConstraintRow>, row: Record<string, string>) => {
|
|
269
|
-
const {
|
|
267
|
+
const {
|
|
268
|
+
schema_name, table_name, column_name, constraint_name,
|
|
269
|
+
} = row;
|
|
270
270
|
const selectedSchema = schemas.length > 0 ? schemas.includes(schema_name) : true;
|
|
271
|
-
if (!selectedSchema) {
|
|
271
|
+
if (!selectedSchema) {
|
|
272
|
+
return acc;
|
|
273
|
+
}
|
|
272
274
|
|
|
273
275
|
const key = `${schema_name}.${table_name}.${constraint_name}`;
|
|
274
276
|
|
|
275
277
|
if (acc[key]) {
|
|
276
|
-
const columnNames = acc[key]
|
|
278
|
+
const { columnNames } = acc[key];
|
|
277
279
|
columnNames.push(column_name);
|
|
278
280
|
return acc;
|
|
279
281
|
}
|
|
@@ -291,8 +293,6 @@ const createConstraintKeysMap = (keys: Record<string, string>[], schemas: string
|
|
|
291
293
|
}, {});
|
|
292
294
|
};
|
|
293
295
|
const generateIndexes = async (conn: Connection, databaseName: string, schemas: string[]) => {
|
|
294
|
-
|
|
295
|
-
|
|
296
296
|
const getPrimaryKeysSql = `
|
|
297
297
|
SHOW PRIMARY KEYS IN DATABASE ${databaseName};
|
|
298
298
|
`;
|
|
@@ -309,7 +309,9 @@ const generateIndexes = async (conn: Connection, databaseName: string, schemas:
|
|
|
309
309
|
|
|
310
310
|
const allConstraints: ConstraintRow[] = [Object.values(primaryKeysByConstraint), Object.values(uniqueKeysByConstraint)].flat();
|
|
311
311
|
const { indexes, tableConstraints } = allConstraints.reduce((acc: GeneratedIndexes, row: ConstraintRow): GeneratedIndexes => {
|
|
312
|
-
const {
|
|
312
|
+
const {
|
|
313
|
+
schemaName, tableName, constraintName, columnNames, type, primary, unique,
|
|
314
|
+
} = row;
|
|
313
315
|
const key = `${schemaName}.${tableName}`;
|
|
314
316
|
|
|
315
317
|
if (columnNames.length < 2) {
|
|
@@ -317,6 +319,7 @@ const generateIndexes = async (conn: Connection, databaseName: string, schemas:
|
|
|
317
319
|
[columnNames[0]]: {
|
|
318
320
|
pk: primary,
|
|
319
321
|
unique,
|
|
322
|
+
checks: [],
|
|
320
323
|
},
|
|
321
324
|
};
|
|
322
325
|
return acc;
|
|
@@ -340,7 +343,7 @@ const generateIndexes = async (conn: Connection, databaseName: string, schemas:
|
|
|
340
343
|
}
|
|
341
344
|
|
|
342
345
|
return acc;
|
|
343
|
-
}, { indexes: {}, tableConstraints: {}});
|
|
346
|
+
}, { indexes: {}, tableConstraints: {} });
|
|
344
347
|
|
|
345
348
|
return {
|
|
346
349
|
indexes,
|
|
@@ -384,6 +387,8 @@ const fetchSchemaJson = async (connection: string): Promise<DatabaseSchema> => {
|
|
|
384
387
|
enums: [],
|
|
385
388
|
indexes,
|
|
386
389
|
tableConstraints,
|
|
390
|
+
// Checks are not supported in snowflake
|
|
391
|
+
checks: {},
|
|
387
392
|
};
|
|
388
393
|
};
|
|
389
394
|
|
package/src/connectors/types.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { columns } from "mssql";
|
|
2
|
-
|
|
3
1
|
interface NoteInfo {
|
|
4
2
|
value: string;
|
|
5
3
|
}
|
|
@@ -9,11 +7,10 @@ interface TypeInfo {
|
|
|
9
7
|
schemaName: string | null;
|
|
10
8
|
}
|
|
11
9
|
|
|
12
|
-
|
|
13
10
|
type DefaultType = 'string' | 'number' | 'boolean' | 'expression';
|
|
14
11
|
|
|
15
12
|
interface DefaultInfo {
|
|
16
|
-
type: DefaultType
|
|
13
|
+
type: DefaultType;
|
|
17
14
|
value: string;
|
|
18
15
|
}
|
|
19
16
|
|
|
@@ -38,10 +35,20 @@ interface FieldsDictionary {
|
|
|
38
35
|
[key: string]: Field[]; // Represents fields within tables, indexed by schemaName.tableName
|
|
39
36
|
}
|
|
40
37
|
|
|
38
|
+
interface CheckConstraint {
|
|
39
|
+
name?: string;
|
|
40
|
+
expression: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface CheckConstraintDictionary {
|
|
44
|
+
[tableName: string]: CheckConstraint[];
|
|
45
|
+
}
|
|
46
|
+
|
|
41
47
|
interface TableConstraint {
|
|
42
48
|
[fieldName: string]: {
|
|
43
49
|
pk?: boolean;
|
|
44
50
|
unique?: boolean;
|
|
51
|
+
checks: CheckConstraint[];
|
|
45
52
|
};
|
|
46
53
|
}
|
|
47
54
|
|
|
@@ -81,8 +88,8 @@ interface IndexColumn {
|
|
|
81
88
|
interface Index {
|
|
82
89
|
name: string;
|
|
83
90
|
type: string;
|
|
84
|
-
unique?: boolean
|
|
85
|
-
pk?: boolean
|
|
91
|
+
unique?: boolean;
|
|
92
|
+
pk?: boolean;
|
|
86
93
|
columns: IndexColumn[];
|
|
87
94
|
}
|
|
88
95
|
|
|
@@ -97,68 +104,16 @@ interface DatabaseSchema {
|
|
|
97
104
|
tableConstraints: TableConstraintsDictionary;
|
|
98
105
|
refs: Ref[];
|
|
99
106
|
indexes: IndexesDictionary; // Represents the indexes property
|
|
107
|
+
checks: CheckConstraintDictionary;
|
|
100
108
|
}
|
|
101
109
|
|
|
102
110
|
interface BigQueryCredentials {
|
|
103
|
-
projectId?: string
|
|
111
|
+
projectId?: string;
|
|
104
112
|
credentials?: {
|
|
105
|
-
clientEmail: string
|
|
106
|
-
privateKey: string
|
|
107
|
-
}
|
|
108
|
-
datasets: string[]
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Currently, we parse the check constraints as enum values in mssql.
|
|
112
|
-
// However, one check constraint can have multiple set of enum values.
|
|
113
|
-
// So, we need to store the enum values in a dictionary.
|
|
114
|
-
// Examples:
|
|
115
|
-
// CREATE TABLE AddressInfo (
|
|
116
|
-
// A1 UNIQUEIDENTIFIER NOT NULL,
|
|
117
|
-
// A2 UNIQUEIDENTIFIER NOT NULL,
|
|
118
|
-
// A3 UNIQUEIDENTIFIER NOT NULL,
|
|
119
|
-
// A4 UNIQUEIDENTIFIER NOT NULL,
|
|
120
|
-
// CONSTRAINT CK_Address_Valid CHECK (
|
|
121
|
-
// A1 IN ('1111', '2222', '333') AND
|
|
122
|
-
// A2 IN ('1111', '2222') AND
|
|
123
|
-
// A3 IN ('2222', '1111') AND
|
|
124
|
-
// A4 IN ('2222', '3333')
|
|
125
|
-
// )
|
|
126
|
-
// );
|
|
127
|
-
// GO
|
|
128
|
-
// In the above example, we have a check constraint CK_Address_Valid2 with 4 columns: A1, A2, A3, A4.
|
|
129
|
-
// Each column has a different set of enum values. So, we need to store the enum values in a dictionary.
|
|
130
|
-
// In the above example, the enum values dictionary will look like this:
|
|
131
|
-
// [
|
|
132
|
-
// {
|
|
133
|
-
// columns : ['A1'],
|
|
134
|
-
// enumValues: [
|
|
135
|
-
// { name: '1111' },
|
|
136
|
-
// { name: '2222' },
|
|
137
|
-
// { name: '333' }
|
|
138
|
-
// ],
|
|
139
|
-
// constraint_name: 'CK_Address_Valid_A1'
|
|
140
|
-
// },
|
|
141
|
-
// {
|
|
142
|
-
// columns : ['A2, A3'], => In this case, these two columns have the same set of enum values.
|
|
143
|
-
// enumValues: [
|
|
144
|
-
// { name: '1111' },
|
|
145
|
-
// { name: '2222' }
|
|
146
|
-
// ],
|
|
147
|
-
// constraint_name: 'CK_Address_Valid_A2_A3'
|
|
148
|
-
// },
|
|
149
|
-
// {
|
|
150
|
-
// columns : ['A4'],
|
|
151
|
-
// enumValues: [
|
|
152
|
-
// { name: '2222' },
|
|
153
|
-
// { name: '3333' }
|
|
154
|
-
// ],
|
|
155
|
-
// constraint_name: 'CK_Address_Valid_A4'
|
|
156
|
-
// }
|
|
157
|
-
// ]
|
|
158
|
-
interface EnumValuesDict {
|
|
159
|
-
columns: string[],
|
|
160
|
-
enumValues: EnumValue[],
|
|
161
|
-
constraint_name: string,
|
|
113
|
+
clientEmail: string;
|
|
114
|
+
privateKey: string;
|
|
115
|
+
};
|
|
116
|
+
datasets: string[];
|
|
162
117
|
}
|
|
163
118
|
|
|
164
119
|
export {
|
|
@@ -180,5 +135,5 @@ export {
|
|
|
180
135
|
IndexesDictionary,
|
|
181
136
|
DatabaseSchema,
|
|
182
137
|
BigQueryCredentials,
|
|
183
|
-
|
|
138
|
+
CheckConstraintDictionary,
|
|
184
139
|
};
|
package/src/index.ts
CHANGED
package/src/utils/helpers.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
|
+
CheckConstraintDictionary,
|
|
2
3
|
FieldsDictionary,
|
|
3
4
|
IndexesDictionary,
|
|
4
5
|
Table,
|
|
5
6
|
TableConstraintsDictionary,
|
|
6
7
|
} from '../connectors/types';
|
|
7
8
|
|
|
8
|
-
export function getIntersection<T>(firstList: T[], secondList: T[]): T[] {
|
|
9
|
+
export function getIntersection<T> (firstList: T[], secondList: T[]): T[] {
|
|
9
10
|
return firstList.filter((item) => secondList.includes(item));
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
export function getTableSchemaKey(schemaName: string, tableName: string) {
|
|
13
|
+
export function getTableSchemaKey (schemaName: string, tableName: string) {
|
|
13
14
|
if (!tableName || !tableName.trim()) {
|
|
14
15
|
throw new Error('Table name must be a non-empty string');
|
|
15
16
|
}
|
|
@@ -17,15 +18,15 @@ export function getTableSchemaKey(schemaName: string, tableName: string) {
|
|
|
17
18
|
return schemaName ? `${schemaName}.${tableName}` : `${tableName}`;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
export function mergeTables(firstTableList: Table[], secondTable: Table[]): Table[] {
|
|
21
|
+
export function mergeTables (firstTableList: Table[], secondTable: Table[]): Table[] {
|
|
21
22
|
return firstTableList.concat(secondTable);
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
export function mergeFieldDictionary(
|
|
25
|
+
export function mergeFieldDictionary (
|
|
25
26
|
firstDict: FieldsDictionary,
|
|
26
|
-
secondDict: FieldsDictionary
|
|
27
|
+
secondDict: FieldsDictionary,
|
|
27
28
|
): FieldsDictionary {
|
|
28
|
-
const result: FieldsDictionary =
|
|
29
|
+
const result: FieldsDictionary = { ...firstDict };
|
|
29
30
|
|
|
30
31
|
Object.keys(secondDict).forEach((key) => {
|
|
31
32
|
if (!result[key]) {
|
|
@@ -38,11 +39,11 @@ export function mergeFieldDictionary(
|
|
|
38
39
|
return result;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
export function mergeIndexDictionary(
|
|
42
|
+
export function mergeIndexDictionary (
|
|
42
43
|
firstDict: IndexesDictionary,
|
|
43
|
-
secondDict: IndexesDictionary
|
|
44
|
+
secondDict: IndexesDictionary,
|
|
44
45
|
): IndexesDictionary {
|
|
45
|
-
const result: IndexesDictionary =
|
|
46
|
+
const result: IndexesDictionary = { ...firstDict };
|
|
46
47
|
|
|
47
48
|
Object.keys(secondDict).forEach((key) => {
|
|
48
49
|
if (!result[key]) {
|
|
@@ -55,11 +56,11 @@ export function mergeIndexDictionary(
|
|
|
55
56
|
return result;
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
export function mergeTableConstraintDictionary(
|
|
59
|
+
export function mergeTableConstraintDictionary (
|
|
59
60
|
firstDict: TableConstraintsDictionary,
|
|
60
|
-
secondDict: TableConstraintsDictionary
|
|
61
|
+
secondDict: TableConstraintsDictionary,
|
|
61
62
|
): TableConstraintsDictionary {
|
|
62
|
-
const result: TableConstraintsDictionary =
|
|
63
|
+
const result: TableConstraintsDictionary = { ...firstDict };
|
|
63
64
|
|
|
64
65
|
Object.keys(secondDict).forEach((key) => {
|
|
65
66
|
if (!result[key]) {
|
|
@@ -71,3 +72,20 @@ export function mergeTableConstraintDictionary(
|
|
|
71
72
|
|
|
72
73
|
return result;
|
|
73
74
|
}
|
|
75
|
+
|
|
76
|
+
export function mergeCheckConstraintDictionary (
|
|
77
|
+
firstDict: CheckConstraintDictionary,
|
|
78
|
+
secondDict: CheckConstraintDictionary,
|
|
79
|
+
): CheckConstraintDictionary {
|
|
80
|
+
const result: CheckConstraintDictionary = { ...firstDict };
|
|
81
|
+
|
|
82
|
+
Object.keys(secondDict).forEach((key) => {
|
|
83
|
+
if (!result[key]) {
|
|
84
|
+
result[key] = [];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
result[key] = result[key].concat(secondDict[key]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return result;
|
|
91
|
+
}
|
package/src/utils/parseSchema.ts
CHANGED
|
@@ -14,6 +14,17 @@ const noSchemaResult = (connectionString: string) => ({
|
|
|
14
14
|
schemas: [],
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
+
// Inputs: schemas string -> Output: schema array string[]
|
|
18
|
+
// schema1, schema2 -> ['schema1', 'schema2']
|
|
19
|
+
// schema3,schema4 -> ['schema3", 'schema4']
|
|
20
|
+
// "schema5,gg", "schema6", schema7 -> ['schema5,gg', 'schema6', 'schema7']
|
|
21
|
+
// schema8 -> ['schema8']
|
|
22
|
+
export const parseSchema = (schemas: string | undefined): string[] => {
|
|
23
|
+
if (!schemas) return [];
|
|
24
|
+
const schemasWithDoubleQuotes = schemas.split(/,(?=(?:[^"]*(?:"[^"]*")*)*$)/).map((s) => s.trim());
|
|
25
|
+
return schemasWithDoubleQuotes.map((s) => s.replace(/"/g, ''));
|
|
26
|
+
};
|
|
27
|
+
|
|
17
28
|
const parseOdbcSchema = (connectionString: string) => {
|
|
18
29
|
const connectionParts = connectionString.split(';');
|
|
19
30
|
const schemasPart = connectionParts.find((part) => haveSchemas(part));
|
|
@@ -47,14 +58,3 @@ export const buildSchemaQuery = (columnName: string, schemas: string[], prefix =
|
|
|
47
58
|
if (schemas.length === 0) return '';
|
|
48
59
|
return `${prefix} ${columnName} IN (${schemas.map((schema) => `'${schema}'`).join(', ')})`;
|
|
49
60
|
};
|
|
50
|
-
|
|
51
|
-
// Inputs: schemas string -> Output: schema array string[]
|
|
52
|
-
// schema1, schema2 -> ['schema1', 'schema2']
|
|
53
|
-
// schema3,schema4 -> ['schema3", 'schema4']
|
|
54
|
-
// "schema5,gg", "schema6", schema7 -> ['schema5,gg', 'schema6', 'schema7']
|
|
55
|
-
// schema8 -> ['schema8']
|
|
56
|
-
export const parseSchema = (schemas: string | undefined): string[] => {
|
|
57
|
-
if (!schemas) return [];
|
|
58
|
-
const schemasWithDoubleQuotes = schemas.split(/,(?=(?:[^"]*(?:"[^"]*")*)*$)/).map((s) => s.trim());
|
|
59
|
-
return schemasWithDoubleQuotes.map((s) => s.replace(/"/g, ''));
|
|
60
|
-
};
|
package/test-output.log
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
> @dbml/connector@5.1.0 test
|
|
3
|
+
> jest
|
|
4
|
+
|
|
5
|
+
PASS __tests__/connector.test.ts
|
|
6
|
+
@dbml/connector
|
|
7
|
+
✓ connectors/mssql (264 ms)
|
|
8
|
+
✓ connectors/mysql (31 ms)
|
|
9
|
+
✓ connectors/postgres (55 ms)
|
|
10
|
+
|
|
11
|
+
Test Suites: 1 passed, 1 total
|
|
12
|
+
Tests: 3 passed, 3 total
|
|
13
|
+
Snapshots: 0 total
|
|
14
|
+
Time: 1.208 s, estimated 2 s
|
|
15
|
+
Ran all test suites.
|
|
16
|
+
Jest did not exit one second after the test run has completed.
|
|
17
|
+
|
|
18
|
+
'This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with `--detectOpenHandles` to troubleshoot this issue.
|