@dbml/connector 3.7.4 → 3.8.1
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__/connector.test.ts +2 -2
- package/__tests__/connectors/postgres/expect-out-files/schema.json +208 -0
- 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.d.ts +1 -1
- package/dist/connectors/mssqlConnector.js +100 -70
- package/dist/connectors/postgresConnector.d.ts +1 -1
- package/dist/connectors/postgresConnector.js +28 -20
- 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 +107 -77
- package/src/connectors/postgresConnector.ts +29 -21
- 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
|
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
/* eslint-disable camelcase */
|
|
2
2
|
import sql from 'mssql';
|
|
3
|
+
import { buildSchemaQuery, parseConnectionString } from '../utils/parseSchema';
|
|
3
4
|
import {
|
|
4
|
-
|
|
5
|
+
DatabaseSchema,
|
|
6
|
+
DefaultInfo,
|
|
5
7
|
DefaultType,
|
|
6
|
-
FieldsDictionary,
|
|
7
8
|
Enum,
|
|
9
|
+
EnumValue,
|
|
10
|
+
Field,
|
|
11
|
+
FieldsDictionary,
|
|
12
|
+
IndexesDictionary,
|
|
8
13
|
Ref,
|
|
9
14
|
RefEndpoint,
|
|
10
15
|
Table,
|
|
11
|
-
IndexesDictionary,
|
|
12
|
-
EnumValue,
|
|
13
16
|
TableConstraintsDictionary,
|
|
14
|
-
DatabaseSchema,
|
|
15
|
-
DefaultInfo,
|
|
16
17
|
} from './types';
|
|
17
18
|
|
|
18
19
|
const MSSQL_DATE_TYPES = [
|
|
@@ -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[],
|
|
@@ -141,74 +142,98 @@ const generateTablesFieldsAndEnums = async (client: sql.ConnectionPool): Promise
|
|
|
141
142
|
const enums: Enum[] = [];
|
|
142
143
|
const tablesAndFieldsSql = `
|
|
143
144
|
WITH tables_and_fields AS (
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
145
|
+
SELECT
|
|
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
|
|
165
|
+
FROM
|
|
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
|
+
constraints_with_nulls AS (
|
|
180
|
+
SELECT
|
|
181
|
+
tf.*,
|
|
182
|
+
cc.name AS check_constraint_name,
|
|
183
|
+
CASE
|
|
184
|
+
WHEN cc.definition LIKE '%[' + tf.column_name + ']%=''%' THEN cc.definition
|
|
185
|
+
ELSE NULL
|
|
186
|
+
END AS check_constraint_definition
|
|
187
|
+
FROM
|
|
188
|
+
tables_and_fields AS tf
|
|
189
|
+
LEFT JOIN sys.check_constraints cc
|
|
190
|
+
ON cc.parent_object_id = OBJECT_ID(tf.table_schema + '.' + tf.table_name)
|
|
191
|
+
AND cc.definition LIKE '%' + tf.column_name + '%' -- Ensure the constraint references the column
|
|
192
|
+
),
|
|
193
|
+
constraints_with_row_number AS (
|
|
194
|
+
SELECT
|
|
195
|
+
*,
|
|
196
|
+
ROW_NUMBER() OVER (
|
|
197
|
+
PARTITION BY table_schema, table_name, column_name
|
|
198
|
+
ORDER BY
|
|
157
199
|
CASE
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
END
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
|
200
|
+
WHEN check_constraint_definition IS NOT NULL THEN 1
|
|
201
|
+
ELSE 2
|
|
202
|
+
END,
|
|
203
|
+
check_constraint_name
|
|
204
|
+
) AS rn
|
|
205
|
+
FROM
|
|
206
|
+
constraints_with_nulls
|
|
182
207
|
)
|
|
183
208
|
SELECT
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
209
|
+
table_schema,
|
|
210
|
+
table_name,
|
|
211
|
+
column_name,
|
|
212
|
+
data_type,
|
|
213
|
+
character_maximum_length,
|
|
214
|
+
numeric_precision,
|
|
215
|
+
numeric_scale,
|
|
216
|
+
identity_increment,
|
|
217
|
+
is_nullable,
|
|
218
|
+
column_default,
|
|
219
|
+
table_comment,
|
|
220
|
+
column_comment,
|
|
221
|
+
check_constraint_name,
|
|
222
|
+
check_constraint_definition,
|
|
223
|
+
CASE
|
|
224
|
+
WHEN column_default LIKE '((%))' THEN 'number'
|
|
225
|
+
WHEN column_default LIKE '(''%'')' THEN 'string'
|
|
226
|
+
ELSE 'expression'
|
|
227
|
+
END AS default_type
|
|
203
228
|
FROM
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
229
|
+
constraints_with_row_number
|
|
230
|
+
WHERE
|
|
231
|
+
rn = 1
|
|
232
|
+
${buildSchemaQuery('table_schema', schemas, 'WHERE')}
|
|
208
233
|
ORDER BY
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
234
|
+
table_schema,
|
|
235
|
+
table_name,
|
|
236
|
+
column_name;
|
|
212
237
|
`;
|
|
213
238
|
|
|
214
239
|
const tablesAndFieldsResult = await client.query(tablesAndFieldsSql);
|
|
@@ -259,7 +284,7 @@ const generateTablesFieldsAndEnums = async (client: sql.ConnectionPool): Promise
|
|
|
259
284
|
};
|
|
260
285
|
};
|
|
261
286
|
|
|
262
|
-
const generateRefs = async (client: sql.ConnectionPool): Promise<Ref[]> => {
|
|
287
|
+
const generateRefs = async (client: sql.ConnectionPool, schemas: string[]): Promise<Ref[]> => {
|
|
263
288
|
const refs: Ref[] = [];
|
|
264
289
|
|
|
265
290
|
const refsListSql = `
|
|
@@ -290,6 +315,7 @@ const generateRefs = async (client: sql.ConnectionPool): Promise<Ref[]> => {
|
|
|
290
315
|
JOIN sys.tables AS t2 ON fk.referenced_object_id = t2.object_id
|
|
291
316
|
JOIN sys.schemas AS s2 ON t2.schema_id = s2.schema_id
|
|
292
317
|
WHERE s.name NOT IN ('sys', 'information_schema')
|
|
318
|
+
${buildSchemaQuery('s.name', schemas)}
|
|
293
319
|
ORDER BY
|
|
294
320
|
s.name,
|
|
295
321
|
t.name;
|
|
@@ -334,7 +360,7 @@ const generateRefs = async (client: sql.ConnectionPool): Promise<Ref[]> => {
|
|
|
334
360
|
return refs;
|
|
335
361
|
};
|
|
336
362
|
|
|
337
|
-
const generateIndexes = async (client: sql.ConnectionPool) => {
|
|
363
|
+
const generateIndexes = async (client: sql.ConnectionPool, schemas: string[]) => {
|
|
338
364
|
const indexListSql = `
|
|
339
365
|
WITH user_tables AS (
|
|
340
366
|
SELECT
|
|
@@ -352,6 +378,7 @@ const generateIndexes = async (client: sql.ConnectionPool) => {
|
|
|
352
378
|
),
|
|
353
379
|
index_info AS (
|
|
354
380
|
SELECT
|
|
381
|
+
SCHEMA_NAME(t.schema_id) AS table_schema, -- Add schema information
|
|
355
382
|
OBJECT_NAME(i.object_id) AS table_name,
|
|
356
383
|
i.name AS index_name,
|
|
357
384
|
i.is_unique,
|
|
@@ -399,8 +426,10 @@ const generateIndexes = async (client: sql.ConnectionPool) => {
|
|
|
399
426
|
user_tables ut
|
|
400
427
|
LEFT JOIN
|
|
401
428
|
index_info ii ON ut.TABLE_NAME = ii.table_name
|
|
429
|
+
AND ut.TABLE_SCHEMA = ii.table_schema
|
|
402
430
|
WHERE
|
|
403
431
|
ii.columns IS NOT NULL
|
|
432
|
+
${buildSchemaQuery('ut.TABLE_SCHEMA', schemas)}
|
|
404
433
|
ORDER BY
|
|
405
434
|
ut.TABLE_NAME,
|
|
406
435
|
ii.constraint_type,
|
|
@@ -491,11 +520,12 @@ const generateIndexes = async (client: sql.ConnectionPool) => {
|
|
|
491
520
|
};
|
|
492
521
|
|
|
493
522
|
const fetchSchemaJson = async (connection: string): Promise<DatabaseSchema> => {
|
|
494
|
-
const
|
|
523
|
+
const { connectionString, schemas } = parseConnectionString(connection, 'odbc');
|
|
524
|
+
const client = await getValidatedClient(connectionString);
|
|
495
525
|
|
|
496
|
-
const tablesFieldsAndEnumsRes = generateTablesFieldsAndEnums(client);
|
|
497
|
-
const indexesRes = generateIndexes(client);
|
|
498
|
-
const refsRes = generateRefs(client);
|
|
526
|
+
const tablesFieldsAndEnumsRes = generateTablesFieldsAndEnums(client, schemas);
|
|
527
|
+
const indexesRes = generateIndexes(client, schemas);
|
|
528
|
+
const refsRes = generateRefs(client, schemas);
|
|
499
529
|
|
|
500
530
|
const res = await Promise.all([
|
|
501
531
|
tablesFieldsAndEnumsRes,
|
|
@@ -519,5 +549,5 @@ const fetchSchemaJson = async (connection: string): Promise<DatabaseSchema> => {
|
|
|
519
549
|
};
|
|
520
550
|
|
|
521
551
|
export {
|
|
522
|
-
fetchSchemaJson
|
|
552
|
+
fetchSchemaJson
|
|
523
553
|
};
|