@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.
@@ -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
 
@@ -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
- 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,
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
- 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
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
- 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
208
- ORDER BY
209
- tf.table_schema,
210
- tf.table_name,
211
- tf.column_name;
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 client = await getValidatedClient(connection);
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,