@dbml/connector 3.7.4 → 3.8.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/dist/connectors/bigqueryConnector.d.ts +3 -0
- package/dist/connectors/bigqueryConnector.js +276 -0
- package/dist/connectors/connector.d.ts +1 -1
- package/dist/connectors/connector.js +9 -3
- package/dist/connectors/mssqlConnector.js +76 -74
- package/dist/connectors/postgresConnector.js +19 -13
- package/dist/connectors/snowflakeConnector.d.ts +3 -0
- package/dist/connectors/snowflakeConnector.js +292 -0
- package/dist/connectors/types.d.ts +11 -1
- package/dist/utils/credential-loader.d.ts +3 -0
- package/dist/utils/credential-loader.js +53 -0
- package/dist/utils/helpers.d.ts +7 -0
- package/dist/utils/helpers.js +50 -0
- package/dist/utils/parseSchema.d.ts +10 -0
- package/dist/utils/parseSchema.js +59 -0
- package/package.json +5 -3
- package/src/connectors/bigqueryConnector.ts +389 -0
- package/src/connectors/connector.ts +9 -3
- package/src/connectors/mssqlConnector.ts +76 -74
- package/src/connectors/postgresConnector.ts +19 -13
- package/src/connectors/snowflakeConnector.ts +370 -0
- package/src/connectors/types.ts +12 -0
- package/src/ddl_samples/bigquery.sql +71 -0
- package/src/ddl_samples/snowflake.sql +131 -0
- package/src/utils/credential-loader.ts +64 -0
- package/src/utils/helpers.ts +73 -0
- package/src/utils/parseSchema.ts +60 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import { BigQuery } from '@google-cloud/bigquery';
|
|
2
|
+
import { loadCredentialFromFile, parseBigQueryCredential } from '../utils/credential-loader';
|
|
3
|
+
import {
|
|
4
|
+
getIntersection,
|
|
5
|
+
getTableSchemaKey,
|
|
6
|
+
mergeFieldDictionary,
|
|
7
|
+
mergeIndexDictionary,
|
|
8
|
+
mergeTableConstraintDictionary,
|
|
9
|
+
mergeTables,
|
|
10
|
+
} from '../utils/helpers';
|
|
11
|
+
import {
|
|
12
|
+
BigQueryCredentials,
|
|
13
|
+
DatabaseSchema,
|
|
14
|
+
DefaultInfo,
|
|
15
|
+
DefaultType,
|
|
16
|
+
Field,
|
|
17
|
+
FieldsDictionary,
|
|
18
|
+
Index,
|
|
19
|
+
IndexesDictionary,
|
|
20
|
+
Table,
|
|
21
|
+
TableConstraintsDictionary,
|
|
22
|
+
} from './types';
|
|
23
|
+
|
|
24
|
+
async function connectBigQuery(credential: BigQueryCredentials): Promise<BigQuery> {
|
|
25
|
+
const client = new BigQuery({
|
|
26
|
+
projectId: credential.projectId,
|
|
27
|
+
credentials: {
|
|
28
|
+
private_key: credential.credentials.privateKey,
|
|
29
|
+
client_email: credential.credentials.clientEmail,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const query = `SELECT CURRENT_DATE() as today`;
|
|
35
|
+
await client.query(query);
|
|
36
|
+
return client;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (error instanceof Error) {
|
|
39
|
+
throw new Error(`BigQuery connection error: ${error.message}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getDbDefault(
|
|
47
|
+
columnDefault: string,
|
|
48
|
+
defaultValueType: DefaultType | null
|
|
49
|
+
): DefaultInfo | null {
|
|
50
|
+
if (defaultValueType === null) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (defaultValueType === 'string') {
|
|
55
|
+
// bigquery store the double qoutes and quotes at the end and the beginning of the string: "string" or 'string'
|
|
56
|
+
return { value: columnDefault.slice(1, -1).replaceAll("'", "\'"), type: defaultValueType }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return { value: columnDefault, type: defaultValueType };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function generateField(row: any): Field {
|
|
63
|
+
const {
|
|
64
|
+
columnName,
|
|
65
|
+
isNullable,
|
|
66
|
+
dataType,
|
|
67
|
+
columnDefault,
|
|
68
|
+
columnDefaultType,
|
|
69
|
+
columnDescription,
|
|
70
|
+
} = row;
|
|
71
|
+
|
|
72
|
+
const fieldDefaultValue = getDbDefault(columnDefault, columnDefaultType);
|
|
73
|
+
|
|
74
|
+
const field: Field = {
|
|
75
|
+
name: columnName,
|
|
76
|
+
not_null: isNullable === 'NO',
|
|
77
|
+
type: {
|
|
78
|
+
type_name: dataType,
|
|
79
|
+
schemaName: null,
|
|
80
|
+
},
|
|
81
|
+
note: {
|
|
82
|
+
value: columnDescription || '',
|
|
83
|
+
},
|
|
84
|
+
dbdefault: fieldDefaultValue,
|
|
85
|
+
increment: false, // bigquery does not support native auto-increment
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
return field;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function generateTablesAndFields(
|
|
92
|
+
client: BigQuery,
|
|
93
|
+
projectId: string,
|
|
94
|
+
datasetId: string
|
|
95
|
+
): Promise<{
|
|
96
|
+
tables: Table[];
|
|
97
|
+
fieldMap: FieldsDictionary;
|
|
98
|
+
}> {
|
|
99
|
+
const tableName = `${projectId}.${datasetId}`;
|
|
100
|
+
|
|
101
|
+
const query = `
|
|
102
|
+
with
|
|
103
|
+
table_comments as (
|
|
104
|
+
select table_schema, table_name, option_value as description
|
|
105
|
+
from ${tableName}.INFORMATION_SCHEMA.TABLE_OPTIONS
|
|
106
|
+
where option_name = 'description'
|
|
107
|
+
),
|
|
108
|
+
column_comments as (
|
|
109
|
+
select column_name, table_schema, table_name, description
|
|
110
|
+
from ${tableName}.INFORMATION_SCHEMA.COLUMN_FIELD_PATHS
|
|
111
|
+
where column_name = field_path
|
|
112
|
+
)
|
|
113
|
+
select
|
|
114
|
+
t.table_schema as tableSchema,
|
|
115
|
+
t.table_name as tableName,
|
|
116
|
+
(select description from table_comments tc where tc.table_name = t.table_name and tc.table_schema = t.table_schema) as tableDescription,
|
|
117
|
+
(
|
|
118
|
+
select description
|
|
119
|
+
from column_comments cc
|
|
120
|
+
where cc.table_name = c.table_name and cc.table_schema = c.table_schema and cc.column_name = c.column_name
|
|
121
|
+
) as columnDescription,
|
|
122
|
+
c.column_name as columnName,
|
|
123
|
+
c.is_nullable as isNullable,
|
|
124
|
+
c.data_type as dataType,
|
|
125
|
+
c.column_default as columnDefault,
|
|
126
|
+
CASE
|
|
127
|
+
WHEN SAFE_CAST(c.column_default AS FLOAT64) IS NOT NULL THEN 'number'
|
|
128
|
+
WHEN SAFE_CAST(c.column_default AS BOOLEAN) IS NOT NULL THEN 'boolean'
|
|
129
|
+
-- string value is enclosed in single quotes, double quotes
|
|
130
|
+
WHEN REGEXP_CONTAINS(c.column_default, r"^['\\"].*['\\"]$") THEN 'string'
|
|
131
|
+
WHEN c.column_default = "NULL" THEN NULL
|
|
132
|
+
ELSE 'expression'
|
|
133
|
+
END AS columnDefaultType
|
|
134
|
+
from ${tableName}.INFORMATION_SCHEMA.TABLES t
|
|
135
|
+
join ${tableName}.INFORMATION_SCHEMA.COLUMNS c
|
|
136
|
+
on
|
|
137
|
+
t.table_schema = c.table_schema
|
|
138
|
+
and t.table_name = c.table_name
|
|
139
|
+
where t.table_type = 'BASE TABLE'
|
|
140
|
+
order by t.table_name, c.ordinal_position;
|
|
141
|
+
`;
|
|
142
|
+
|
|
143
|
+
const [queryResult] = await client.query({
|
|
144
|
+
query,
|
|
145
|
+
params: {
|
|
146
|
+
// stringRegex: STRING_REGEX,
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const tableMap: Record<string, Table> = {};
|
|
151
|
+
const fieldMap: FieldsDictionary = {};
|
|
152
|
+
|
|
153
|
+
queryResult.forEach((row) => {
|
|
154
|
+
const { tableSchema, tableName, tableComment } = row;
|
|
155
|
+
|
|
156
|
+
const key = getTableSchemaKey(tableSchema, tableName);
|
|
157
|
+
|
|
158
|
+
if (!tableMap[key]) {
|
|
159
|
+
tableMap[key] = {
|
|
160
|
+
name: tableName,
|
|
161
|
+
note: { value: tableComment || '' },
|
|
162
|
+
schemaName: tableSchema,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (!fieldMap[key]) {
|
|
167
|
+
fieldMap[key] = [];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const field = generateField(row);
|
|
171
|
+
fieldMap[key].push(field);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
tables: Object.values(tableMap),
|
|
176
|
+
fieldMap,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function generateIndexes(
|
|
181
|
+
client: BigQuery,
|
|
182
|
+
projectId: string,
|
|
183
|
+
datasetId: string
|
|
184
|
+
): Promise<IndexesDictionary> {
|
|
185
|
+
const tableName = `${projectId}.${datasetId}`;
|
|
186
|
+
|
|
187
|
+
const query = `
|
|
188
|
+
select
|
|
189
|
+
s.index_schema as indexSchema,
|
|
190
|
+
s.table_name as tableName,
|
|
191
|
+
s.index_name as indexName,
|
|
192
|
+
string_agg(sc.index_column_name, ', ') as indexColumnNames
|
|
193
|
+
from ${tableName}.INFORMATION_SCHEMA.SEARCH_INDEXES s
|
|
194
|
+
join ${tableName}.INFORMATION_SCHEMA.SEARCH_INDEX_COLUMNS sc
|
|
195
|
+
on
|
|
196
|
+
s.index_schema = sc.index_schema
|
|
197
|
+
and s.table_name = sc.table_name
|
|
198
|
+
and s.index_name = sc.index_name
|
|
199
|
+
where sc.index_column_name = sc.index_field_path
|
|
200
|
+
group by s.index_schema, s.table_name, s.index_name
|
|
201
|
+
order by s.table_name, s.index_name;
|
|
202
|
+
`;
|
|
203
|
+
|
|
204
|
+
const [queryResult] = await client.query(query);
|
|
205
|
+
|
|
206
|
+
const indexMap: IndexesDictionary = {};
|
|
207
|
+
|
|
208
|
+
queryResult.forEach((row) => {
|
|
209
|
+
const { indexSchema, tableName, indexName, indexColumnNames } = row;
|
|
210
|
+
|
|
211
|
+
const indexColumns = indexColumnNames.split(', ').map((column: string) => {
|
|
212
|
+
return {
|
|
213
|
+
type: 'column',
|
|
214
|
+
value: column,
|
|
215
|
+
};
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const index: Index = {
|
|
219
|
+
name: indexName,
|
|
220
|
+
type: '',
|
|
221
|
+
columns: [...indexColumns],
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const key = getTableSchemaKey(indexSchema, tableName);
|
|
225
|
+
|
|
226
|
+
if (indexMap[key]) {
|
|
227
|
+
indexMap[key].push(index);
|
|
228
|
+
} else {
|
|
229
|
+
indexMap[key] = [index];
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
return indexMap;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function generateTableConstraints(
|
|
236
|
+
client: BigQuery,
|
|
237
|
+
projectId: string,
|
|
238
|
+
datasetId: string
|
|
239
|
+
): Promise<{
|
|
240
|
+
inlinePrimaryKeyMap: TableConstraintsDictionary;
|
|
241
|
+
compositePrimaryKeyMap: IndexesDictionary;
|
|
242
|
+
}> {
|
|
243
|
+
const tableName = `${projectId}.${datasetId}`;
|
|
244
|
+
|
|
245
|
+
const query = `
|
|
246
|
+
select
|
|
247
|
+
tc.table_schema as tableSchema,
|
|
248
|
+
tc.table_name as tableName,
|
|
249
|
+
tc.constraint_name as constraintName,
|
|
250
|
+
string_agg(kcu.column_name, ', ') as columnNames,
|
|
251
|
+
count(kcu.column_name) as columnCount,
|
|
252
|
+
from ${tableName}.INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
|
|
253
|
+
join ${tableName}.INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
|
|
254
|
+
on
|
|
255
|
+
tc.table_schema = kcu.table_schema
|
|
256
|
+
and tc.table_name = kcu.table_name
|
|
257
|
+
and tc.constraint_name = kcu.constraint_name
|
|
258
|
+
where tc.constraint_type = 'PRIMARY KEY'
|
|
259
|
+
group by tc.table_schema, tc.table_name, tc.constraint_name
|
|
260
|
+
order by tc.table_name, tc.constraint_name;
|
|
261
|
+
`;
|
|
262
|
+
|
|
263
|
+
const [queryResult] = await client.query(query);
|
|
264
|
+
|
|
265
|
+
const inlinePrimaryKeyList = queryResult.filter(
|
|
266
|
+
(row) => row.columnCount && row.columnCount === 1
|
|
267
|
+
);
|
|
268
|
+
const compositePrimaryKeyList = queryResult.filter(
|
|
269
|
+
(row) => row.columnCount && row.columnCount > 1
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
const inlinePrimaryKeyMap: TableConstraintsDictionary = {};
|
|
273
|
+
inlinePrimaryKeyList.forEach((inlineKey) => {
|
|
274
|
+
const { tableSchema, tableName, columnNames } = inlineKey;
|
|
275
|
+
|
|
276
|
+
const key = getTableSchemaKey(tableSchema, tableName);
|
|
277
|
+
|
|
278
|
+
if (!inlinePrimaryKeyMap[key]) {
|
|
279
|
+
inlinePrimaryKeyMap[key] = {};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (!inlinePrimaryKeyMap[key][columnNames]) {
|
|
283
|
+
inlinePrimaryKeyMap[key][columnNames] = {};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
inlinePrimaryKeyMap[key][columnNames].pk = true;
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const compositePrimaryKeyMap: IndexesDictionary = {};
|
|
290
|
+
compositePrimaryKeyList.forEach((compositeKey) => {
|
|
291
|
+
const { tableSchema, tableName, constraintName, columnNames } = compositeKey;
|
|
292
|
+
|
|
293
|
+
const indexColumns = columnNames.split(', ').map((column: string) => {
|
|
294
|
+
return {
|
|
295
|
+
type: 'column',
|
|
296
|
+
value: column,
|
|
297
|
+
};
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const index: Index = {
|
|
301
|
+
name: constraintName,
|
|
302
|
+
type: '',
|
|
303
|
+
columns: [...indexColumns],
|
|
304
|
+
pk: true,
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
const key = getTableSchemaKey(tableSchema, tableName);
|
|
308
|
+
|
|
309
|
+
if (compositePrimaryKeyMap[key]) {
|
|
310
|
+
compositePrimaryKeyMap[key].push(index);
|
|
311
|
+
} else {
|
|
312
|
+
compositePrimaryKeyMap[key] = [index];
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
inlinePrimaryKeyMap,
|
|
318
|
+
compositePrimaryKeyMap,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async function validateDatasetIdList(client: BigQuery, datasetIdList: string[]): Promise<string[]> {
|
|
323
|
+
const [datasets] = await client.getDatasets();
|
|
324
|
+
|
|
325
|
+
const rawDatasetIdList = datasets.map((dataset) => dataset.id).filter((id) => id) as string[];
|
|
326
|
+
|
|
327
|
+
// If user does not specify which datasets to be fetched, we will fetch all datasets
|
|
328
|
+
if (datasetIdList.length === 0) {
|
|
329
|
+
return rawDatasetIdList;
|
|
330
|
+
}
|
|
331
|
+
return getIntersection(datasetIdList, rawDatasetIdList);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async function fetchSchemaJsonByDataset(
|
|
335
|
+
client: BigQuery,
|
|
336
|
+
projectId: string,
|
|
337
|
+
datasetId: string
|
|
338
|
+
): Promise<DatabaseSchema> {
|
|
339
|
+
const res = await Promise.all([
|
|
340
|
+
generateTablesAndFields(client, projectId, datasetId),
|
|
341
|
+
generateIndexes(client, projectId, datasetId),
|
|
342
|
+
generateTableConstraints(client, projectId, datasetId),
|
|
343
|
+
]);
|
|
344
|
+
|
|
345
|
+
const { tables, fieldMap } = res[0];
|
|
346
|
+
const indexMap = res[1];
|
|
347
|
+
const { inlinePrimaryKeyMap, compositePrimaryKeyMap } = res[2];
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
tables,
|
|
351
|
+
fields: fieldMap,
|
|
352
|
+
refs: [],
|
|
353
|
+
enums: [],
|
|
354
|
+
indexes: mergeIndexDictionary(indexMap, compositePrimaryKeyMap),
|
|
355
|
+
tableConstraints: inlinePrimaryKeyMap,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async function fetchSchemaJson(keyFilename: string): Promise<DatabaseSchema> {
|
|
360
|
+
const rawCredential = await loadCredentialFromFile(keyFilename);
|
|
361
|
+
const credentialJson = parseBigQueryCredential(rawCredential);
|
|
362
|
+
const client = await connectBigQuery(credentialJson);
|
|
363
|
+
|
|
364
|
+
const projectId = await client.getProjectId();
|
|
365
|
+
const datasetIdList = await validateDatasetIdList(client, credentialJson.datasets);
|
|
366
|
+
|
|
367
|
+
const querySchemaPromises = datasetIdList.map((dataset) =>
|
|
368
|
+
fetchSchemaJsonByDataset(client, projectId, dataset)
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
const databaseSchemaRes = await Promise.all(querySchemaPromises);
|
|
372
|
+
|
|
373
|
+
const res = databaseSchemaRes.reduce((acc, dbSchema) => {
|
|
374
|
+
const { tables, fields, indexes, tableConstraints } = dbSchema;
|
|
375
|
+
|
|
376
|
+
acc.tables = mergeTables(acc.tables, tables);
|
|
377
|
+
acc.fields = mergeFieldDictionary(acc.fields, fields);
|
|
378
|
+
acc.indexes = mergeIndexDictionary(acc.indexes, indexes);
|
|
379
|
+
acc.tableConstraints = mergeTableConstraintDictionary(acc.tableConstraints, tableConstraints);
|
|
380
|
+
|
|
381
|
+
return acc;
|
|
382
|
+
},
|
|
383
|
+
{ tables: [], fields: {}, tableConstraints: {}, indexes: {}, refs: [], enums: [] }
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
return res;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export { fetchSchemaJson };
|
|
@@ -2,17 +2,23 @@ import { DatabaseSchema } from './types';
|
|
|
2
2
|
import { fetchSchemaJson as fetchPostgresSchemaJson } from './postgresConnector';
|
|
3
3
|
import { fetchSchemaJson as fetchMssqlSchemaJson } from './mssqlConnector';
|
|
4
4
|
import { fetchSchemaJson as fetchMysqlSchemaJson } from './mysqlConnector';
|
|
5
|
+
import { fetchSchemaJson as fetchSnowflakeSchemaJson } from './snowflakeConnector';
|
|
6
|
+
import { fetchSchemaJson as fetchBigQuerySchemaJson } from './bigqueryConnector';
|
|
5
7
|
|
|
6
|
-
const fetchSchemaJson = async (connection: string,
|
|
7
|
-
switch (
|
|
8
|
+
const fetchSchemaJson = async (connection: string, databaseType: string): Promise<DatabaseSchema> => {
|
|
9
|
+
switch (databaseType) {
|
|
8
10
|
case 'postgres':
|
|
9
11
|
return fetchPostgresSchemaJson(connection);
|
|
10
12
|
case 'mssql':
|
|
11
13
|
return fetchMssqlSchemaJson(connection);
|
|
12
14
|
case 'mysql':
|
|
13
15
|
return fetchMysqlSchemaJson(connection);
|
|
16
|
+
case 'snowflake':
|
|
17
|
+
return fetchSnowflakeSchemaJson(connection);
|
|
18
|
+
case 'bigquery':
|
|
19
|
+
return fetchBigQuerySchemaJson(connection);
|
|
14
20
|
default:
|
|
15
|
-
throw new Error(`Unsupported
|
|
21
|
+
throw new Error(`Unsupported database type: ${databaseType}`);
|
|
16
22
|
}
|
|
17
23
|
};
|
|
18
24
|
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
DatabaseSchema,
|
|
15
15
|
DefaultInfo,
|
|
16
16
|
} from './types';
|
|
17
|
+
import { buildSchemaQuery, parseConnectionString } from '../utils/parseSchema';
|
|
17
18
|
|
|
18
19
|
const MSSQL_DATE_TYPES = [
|
|
19
20
|
'date',
|
|
@@ -132,7 +133,7 @@ const generateField = (row: Record<string, any>): Field => {
|
|
|
132
133
|
};
|
|
133
134
|
};
|
|
134
135
|
|
|
135
|
-
const generateTablesFieldsAndEnums = async (client: sql.ConnectionPool): Promise<{
|
|
136
|
+
const generateTablesFieldsAndEnums = async (client: sql.ConnectionPool, schemas: string[]): Promise<{
|
|
136
137
|
tables: Table[],
|
|
137
138
|
fields: FieldsDictionary,
|
|
138
139
|
enums: Enum[],
|
|
@@ -140,75 +141,71 @@ const generateTablesFieldsAndEnums = async (client: sql.ConnectionPool): Promise
|
|
|
140
141
|
const fields: FieldsDictionary = {};
|
|
141
142
|
const enums: Enum[] = [];
|
|
142
143
|
const tablesAndFieldsSql = `
|
|
143
|
-
|
|
144
|
-
SELECT
|
|
145
|
-
s.name AS table_schema,
|
|
146
|
-
t.name AS table_name,
|
|
147
|
-
c.name AS column_name,
|
|
148
|
-
ty.name AS data_type,
|
|
149
|
-
c.max_length AS character_maximum_length,
|
|
150
|
-
c.precision AS numeric_precision,
|
|
151
|
-
c.scale AS numeric_scale,
|
|
152
|
-
c.is_identity AS identity_increment,
|
|
153
|
-
CASE
|
|
154
|
-
WHEN c.is_nullable = 1 THEN 'YES'
|
|
155
|
-
ELSE 'NO'
|
|
156
|
-
END AS is_nullable,
|
|
157
|
-
CASE
|
|
158
|
-
WHEN c.default_object_id = 0 THEN NULL
|
|
159
|
-
ELSE OBJECT_DEFINITION(c.default_object_id)
|
|
160
|
-
END AS column_default,
|
|
161
|
-
-- Fetching table comments
|
|
162
|
-
p.value AS table_comment,
|
|
163
|
-
ep.value AS column_comment
|
|
164
|
-
FROM
|
|
165
|
-
sys.tables t
|
|
166
|
-
JOIN
|
|
167
|
-
sys.schemas s ON t.schema_id = s.schema_id
|
|
168
|
-
JOIN
|
|
169
|
-
sys.columns c ON t.object_id = c.object_id
|
|
170
|
-
JOIN
|
|
171
|
-
sys.types ty ON c.user_type_id = ty.user_type_id
|
|
172
|
-
LEFT JOIN
|
|
173
|
-
sys.extended_properties p ON p.major_id = t.object_id
|
|
174
|
-
AND p.name = 'MS_Description'
|
|
175
|
-
AND p.minor_id = 0 -- Ensure minor_id is 0 for table comments
|
|
176
|
-
LEFT JOIN
|
|
177
|
-
sys.extended_properties ep ON ep.major_id = c.object_id
|
|
178
|
-
AND ep.minor_id = c.column_id
|
|
179
|
-
AND ep.name = 'MS_Description'
|
|
180
|
-
WHERE
|
|
181
|
-
t.type = 'U' -- User-defined tables
|
|
182
|
-
)
|
|
144
|
+
WITH tables_and_fields AS (
|
|
183
145
|
SELECT
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
146
|
+
s.name AS table_schema,
|
|
147
|
+
t.name AS table_name,
|
|
148
|
+
c.name AS column_name,
|
|
149
|
+
ty.name AS data_type,
|
|
150
|
+
c.max_length AS character_maximum_length,
|
|
151
|
+
c.precision AS numeric_precision,
|
|
152
|
+
c.scale AS numeric_scale,
|
|
153
|
+
c.is_identity AS identity_increment,
|
|
154
|
+
CASE
|
|
155
|
+
WHEN c.is_nullable = 1 THEN 'YES'
|
|
156
|
+
ELSE 'NO'
|
|
157
|
+
END AS is_nullable,
|
|
158
|
+
CASE
|
|
159
|
+
WHEN c.default_object_id = 0 THEN NULL
|
|
160
|
+
ELSE OBJECT_DEFINITION(c.default_object_id)
|
|
161
|
+
END AS column_default,
|
|
162
|
+
-- Fetching table comments
|
|
163
|
+
p.value AS table_comment,
|
|
164
|
+
ep.value AS column_comment
|
|
203
165
|
FROM
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
166
|
+
sys.tables t
|
|
167
|
+
JOIN sys.schemas s ON t.schema_id = s.schema_id
|
|
168
|
+
JOIN sys.columns c ON t.object_id = c.object_id
|
|
169
|
+
JOIN sys.types ty ON c.user_type_id = ty.user_type_id
|
|
170
|
+
LEFT JOIN sys.extended_properties p ON p.major_id = t.object_id
|
|
171
|
+
AND p.name = 'MS_Description'
|
|
172
|
+
AND p.minor_id = 0 -- Ensure minor_id is 0 for table comments
|
|
173
|
+
LEFT JOIN sys.extended_properties ep ON ep.major_id = c.object_id
|
|
174
|
+
AND ep.minor_id = c.column_id
|
|
175
|
+
AND ep.name = 'MS_Description'
|
|
176
|
+
WHERE
|
|
177
|
+
t.type = 'U' -- User-defined tables
|
|
178
|
+
)
|
|
179
|
+
SELECT
|
|
180
|
+
tf.table_schema,
|
|
181
|
+
tf.table_name,
|
|
182
|
+
tf.column_name,
|
|
183
|
+
tf.data_type,
|
|
184
|
+
tf.character_maximum_length,
|
|
185
|
+
tf.numeric_precision,
|
|
186
|
+
tf.numeric_scale,
|
|
187
|
+
tf.identity_increment,
|
|
188
|
+
tf.is_nullable,
|
|
189
|
+
tf.column_default,
|
|
190
|
+
tf.table_comment,
|
|
191
|
+
tf.column_comment,
|
|
192
|
+
cc.name AS check_constraint_name, -- Adding CHECK constraint name
|
|
193
|
+
cc.definition AS check_constraint_definition, -- Adding CHECK constraint definition
|
|
194
|
+
CASE
|
|
195
|
+
WHEN tf.column_default LIKE '((%))' THEN 'number'
|
|
196
|
+
WHEN tf.column_default LIKE '(''%'')' THEN 'string'
|
|
197
|
+
ELSE 'expression'
|
|
198
|
+
END AS default_type
|
|
199
|
+
FROM
|
|
200
|
+
tables_and_fields AS tf
|
|
201
|
+
LEFT JOIN sys.check_constraints cc
|
|
202
|
+
ON cc.parent_object_id = OBJECT_ID(tf.table_schema + '.' + tf.table_name)
|
|
203
|
+
AND cc.definition LIKE '%' + tf.column_name + '%' -- Ensure the constraint references the column
|
|
204
|
+
${buildSchemaQuery('tf.table_schema', schemas, 'WHERE')}
|
|
205
|
+
ORDER BY
|
|
206
|
+
tf.table_schema,
|
|
207
|
+
tf.table_name,
|
|
208
|
+
tf.column_name;
|
|
212
209
|
`;
|
|
213
210
|
|
|
214
211
|
const tablesAndFieldsResult = await client.query(tablesAndFieldsSql);
|
|
@@ -259,7 +256,7 @@ const generateTablesFieldsAndEnums = async (client: sql.ConnectionPool): Promise
|
|
|
259
256
|
};
|
|
260
257
|
};
|
|
261
258
|
|
|
262
|
-
const generateRefs = async (client: sql.ConnectionPool): Promise<Ref[]> => {
|
|
259
|
+
const generateRefs = async (client: sql.ConnectionPool, schemas: string[]): Promise<Ref[]> => {
|
|
263
260
|
const refs: Ref[] = [];
|
|
264
261
|
|
|
265
262
|
const refsListSql = `
|
|
@@ -290,6 +287,7 @@ const generateRefs = async (client: sql.ConnectionPool): Promise<Ref[]> => {
|
|
|
290
287
|
JOIN sys.tables AS t2 ON fk.referenced_object_id = t2.object_id
|
|
291
288
|
JOIN sys.schemas AS s2 ON t2.schema_id = s2.schema_id
|
|
292
289
|
WHERE s.name NOT IN ('sys', 'information_schema')
|
|
290
|
+
${buildSchemaQuery('s.name', schemas)}
|
|
293
291
|
ORDER BY
|
|
294
292
|
s.name,
|
|
295
293
|
t.name;
|
|
@@ -334,7 +332,7 @@ const generateRefs = async (client: sql.ConnectionPool): Promise<Ref[]> => {
|
|
|
334
332
|
return refs;
|
|
335
333
|
};
|
|
336
334
|
|
|
337
|
-
const generateIndexes = async (client: sql.ConnectionPool) => {
|
|
335
|
+
const generateIndexes = async (client: sql.ConnectionPool, schemas: string[]) => {
|
|
338
336
|
const indexListSql = `
|
|
339
337
|
WITH user_tables AS (
|
|
340
338
|
SELECT
|
|
@@ -352,6 +350,7 @@ const generateIndexes = async (client: sql.ConnectionPool) => {
|
|
|
352
350
|
),
|
|
353
351
|
index_info AS (
|
|
354
352
|
SELECT
|
|
353
|
+
SCHEMA_NAME(t.schema_id) AS table_schema, -- Add schema information
|
|
355
354
|
OBJECT_NAME(i.object_id) AS table_name,
|
|
356
355
|
i.name AS index_name,
|
|
357
356
|
i.is_unique,
|
|
@@ -399,8 +398,10 @@ const generateIndexes = async (client: sql.ConnectionPool) => {
|
|
|
399
398
|
user_tables ut
|
|
400
399
|
LEFT JOIN
|
|
401
400
|
index_info ii ON ut.TABLE_NAME = ii.table_name
|
|
401
|
+
AND ut.TABLE_SCHEMA = ii.table_schema
|
|
402
402
|
WHERE
|
|
403
403
|
ii.columns IS NOT NULL
|
|
404
|
+
${buildSchemaQuery('ut.TABLE_SCHEMA', schemas)}
|
|
404
405
|
ORDER BY
|
|
405
406
|
ut.TABLE_NAME,
|
|
406
407
|
ii.constraint_type,
|
|
@@ -491,11 +492,12 @@ const generateIndexes = async (client: sql.ConnectionPool) => {
|
|
|
491
492
|
};
|
|
492
493
|
|
|
493
494
|
const fetchSchemaJson = async (connection: string): Promise<DatabaseSchema> => {
|
|
494
|
-
const
|
|
495
|
+
const { connectionString, schemas } = parseConnectionString(connection, 'odbc');
|
|
496
|
+
const client = await getValidatedClient(connectionString);
|
|
495
497
|
|
|
496
|
-
const tablesFieldsAndEnumsRes = generateTablesFieldsAndEnums(client);
|
|
497
|
-
const indexesRes = generateIndexes(client);
|
|
498
|
-
const refsRes = generateRefs(client);
|
|
498
|
+
const tablesFieldsAndEnumsRes = generateTablesFieldsAndEnums(client, schemas);
|
|
499
|
+
const indexesRes = generateIndexes(client, schemas);
|
|
500
|
+
const refsRes = generateRefs(client, schemas);
|
|
499
501
|
|
|
500
502
|
const res = await Promise.all([
|
|
501
503
|
tablesFieldsAndEnumsRes,
|