@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.
@@ -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, format: string): Promise<DatabaseSchema> => {
7
- switch (format) {
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 connection format: ${format}`);
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
- Field,
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
- 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,
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
- 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
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
- tf.table_schema,
185
- tf.table_name,
186
- tf.column_name,
187
- tf.data_type,
188
- tf.character_maximum_length,
189
- tf.numeric_precision,
190
- tf.numeric_scale,
191
- tf.identity_increment,
192
- tf.is_nullable,
193
- tf.column_default,
194
- tf.table_comment,
195
- tf.column_comment,
196
- cc.name AS check_constraint_name, -- Adding CHECK constraint name
197
- cc.definition AS check_constraint_definition, -- Adding CHECK constraint definition
198
- CASE
199
- WHEN tf.column_default LIKE '((%))' THEN 'number'
200
- WHEN tf.column_default LIKE '(''%'')' THEN 'string'
201
- ELSE 'expression'
202
- END AS default_type
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
- tables_and_fields AS tf
205
- LEFT JOIN
206
- sys.check_constraints cc ON cc.parent_object_id = OBJECT_ID(tf.table_schema + '.' + tf.table_name)
207
- AND cc.definition LIKE '%' + tf.column_name + '%' -- Ensure the constraint references the column
229
+ constraints_with_row_number
230
+ WHERE
231
+ rn = 1
232
+ ${buildSchemaQuery('table_schema', schemas, 'WHERE')}
208
233
  ORDER BY
209
- tf.table_schema,
210
- tf.table_name,
211
- tf.column_name;
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 client = await getValidatedClient(connection);
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
  };