@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
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
Table,
|
|
14
14
|
TableConstraintsDictionary,
|
|
15
15
|
} from './types';
|
|
16
|
+
import { parseConnectionString, buildSchemaQuery } from '../utils/parseSchema';
|
|
16
17
|
|
|
17
18
|
const getValidatedClient = async (connection: string): Promise<Client> => {
|
|
18
19
|
const client = new Client(connection);
|
|
@@ -113,7 +114,7 @@ const generateField = (row: Record<string, any>): Field => {
|
|
|
113
114
|
};
|
|
114
115
|
};
|
|
115
116
|
|
|
116
|
-
const generateTablesAndFields = async (client: Client): Promise<{
|
|
117
|
+
const generateTablesAndFields = async (client: Client, schemas: string[]): Promise<{
|
|
117
118
|
tables: Table[],
|
|
118
119
|
fields: FieldsDictionary,
|
|
119
120
|
}> => {
|
|
@@ -167,6 +168,7 @@ const generateTablesAndFields = async (client: Client): Promise<{
|
|
|
167
168
|
WHERE
|
|
168
169
|
t.table_type = 'BASE TABLE'
|
|
169
170
|
AND t.table_schema NOT IN ('pg_catalog', 'information_schema')
|
|
171
|
+
${buildSchemaQuery('t.table_schema', schemas)}
|
|
170
172
|
ORDER BY
|
|
171
173
|
t.table_schema,
|
|
172
174
|
t.table_name,
|
|
@@ -200,7 +202,7 @@ const generateTablesAndFields = async (client: Client): Promise<{
|
|
|
200
202
|
};
|
|
201
203
|
};
|
|
202
204
|
|
|
203
|
-
const generateRawRefs = async (client: Client): Promise<Ref[]> => {
|
|
205
|
+
const generateRawRefs = async (client: Client, schemas: string[]): Promise<Ref[]> => {
|
|
204
206
|
const refs: Ref[] = [];
|
|
205
207
|
|
|
206
208
|
const refsListSql = `
|
|
@@ -226,6 +228,7 @@ const generateRawRefs = async (client: Client): Promise<Ref[]> => {
|
|
|
226
228
|
AND tc.table_schema = rc.constraint_schema
|
|
227
229
|
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
228
230
|
AND tc.table_schema NOT IN ('pg_catalog', 'information_schema')
|
|
231
|
+
${buildSchemaQuery('tc.table_schema', schemas)}
|
|
229
232
|
GROUP BY
|
|
230
233
|
tc.table_schema,
|
|
231
234
|
tc.table_name,
|
|
@@ -279,8 +282,7 @@ const generateRawRefs = async (client: Client): Promise<Ref[]> => {
|
|
|
279
282
|
return refs;
|
|
280
283
|
};
|
|
281
284
|
|
|
282
|
-
const generateIndexes = async (client: Client) => {
|
|
283
|
-
// const tableConstraints = {};
|
|
285
|
+
const generateIndexes = async (client: Client, schemas: string[]) => {
|
|
284
286
|
const indexListSql = `
|
|
285
287
|
WITH user_tables AS (
|
|
286
288
|
SELECT
|
|
@@ -293,6 +295,7 @@ const generateIndexes = async (client: Client) => {
|
|
|
293
295
|
),
|
|
294
296
|
index_info AS (
|
|
295
297
|
SELECT
|
|
298
|
+
t.relnamespace::regnamespace::text AS table_schema,
|
|
296
299
|
t.relname AS table_name,
|
|
297
300
|
i.relname AS index_name,
|
|
298
301
|
ix.indisunique AS is_unique,
|
|
@@ -317,7 +320,7 @@ const generateIndexes = async (client: Client) => {
|
|
|
317
320
|
AND t.relname NOT LIKE 'pg_%'
|
|
318
321
|
AND t.relname NOT LIKE 'sql_%'
|
|
319
322
|
GROUP BY
|
|
320
|
-
t.relname, i.relname, ix.indisunique, ix.indisprimary, am.amname, ix.indexprs, ix.indrelid
|
|
323
|
+
t.relnamespace, t.relname, i.relname, ix.indisunique, ix.indisprimary, am.amname, ix.indexprs, ix.indrelid
|
|
321
324
|
)
|
|
322
325
|
SELECT
|
|
323
326
|
ut.tableschema AS table_schema,
|
|
@@ -328,12 +331,13 @@ const generateIndexes = async (client: Client) => {
|
|
|
328
331
|
ii.index_type,
|
|
329
332
|
ii.columns,
|
|
330
333
|
ii.expressions,
|
|
331
|
-
ii.constraint_type --
|
|
334
|
+
ii.constraint_type -- Retained constraint type here
|
|
332
335
|
FROM
|
|
333
336
|
user_tables ut
|
|
334
337
|
LEFT JOIN
|
|
335
|
-
index_info ii ON ut.tablename = ii.table_name
|
|
338
|
+
index_info ii ON ut.tableschema = ii.table_schema AND ut.tablename = ii.table_name
|
|
336
339
|
WHERE ii.columns IS NOT NULL
|
|
340
|
+
${buildSchemaQuery('ut.tableschema', schemas)}
|
|
337
341
|
ORDER BY
|
|
338
342
|
ut.tablename,
|
|
339
343
|
ii.constraint_type,
|
|
@@ -427,7 +431,7 @@ const generateIndexes = async (client: Client) => {
|
|
|
427
431
|
};
|
|
428
432
|
};
|
|
429
433
|
|
|
430
|
-
const generateRawEnums = async (client: Client): Promise<Enum[]> => {
|
|
434
|
+
const generateRawEnums = async (client: Client, schemas: string[]): Promise<Enum[]> => {
|
|
431
435
|
const enumListSql = `
|
|
432
436
|
SELECT
|
|
433
437
|
n.nspname AS schema_name,
|
|
@@ -440,6 +444,7 @@ const generateRawEnums = async (client: Client): Promise<Enum[]> => {
|
|
|
440
444
|
pg_type t ON e.enumtypid = t.oid
|
|
441
445
|
JOIN
|
|
442
446
|
pg_namespace n ON t.typnamespace = n.oid
|
|
447
|
+
${buildSchemaQuery('n.nspname', schemas, 'WHERE')}
|
|
443
448
|
ORDER BY
|
|
444
449
|
schema_name,
|
|
445
450
|
enum_type,
|
|
@@ -467,12 +472,13 @@ const generateRawEnums = async (client: Client): Promise<Enum[]> => {
|
|
|
467
472
|
};
|
|
468
473
|
|
|
469
474
|
const fetchSchemaJson = async (connection: string): Promise<DatabaseSchema> => {
|
|
470
|
-
const
|
|
475
|
+
const { connectionString, schemas } = parseConnectionString(connection, 'jdbc');
|
|
476
|
+
const client = await getValidatedClient(connectionString);
|
|
471
477
|
|
|
472
|
-
const tablesAndFieldsRes = generateTablesAndFields(client);
|
|
473
|
-
const indexesRes = generateIndexes(client);
|
|
474
|
-
const refsRes = generateRawRefs(client);
|
|
475
|
-
const enumsRes = generateRawEnums(client);
|
|
478
|
+
const tablesAndFieldsRes = generateTablesAndFields(client, schemas);
|
|
479
|
+
const indexesRes = generateIndexes(client, schemas);
|
|
480
|
+
const refsRes = generateRawRefs(client, schemas);
|
|
481
|
+
const enumsRes = generateRawEnums(client, schemas);
|
|
476
482
|
|
|
477
483
|
const res = await Promise.all([
|
|
478
484
|
tablesAndFieldsRes,
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
/* eslint-disable camelcase */
|
|
2
|
+
//
|
|
3
|
+
// Description:
|
|
4
|
+
// Snowflake info schema lacks reference direction for relationships.
|
|
5
|
+
// Constraints and uniqueness cannot be auto-generated from Snowflake info schema.
|
|
6
|
+
// Snowflake does not support ENUMs.
|
|
7
|
+
// Snowflake does not support indexes for standard tables.
|
|
8
|
+
//
|
|
9
|
+
|
|
10
|
+
import snowflake, { Connection, LogLevel } from 'snowflake-sdk';
|
|
11
|
+
import { parseSchema } from '../utils/parseSchema';
|
|
12
|
+
import {
|
|
13
|
+
DatabaseSchema,
|
|
14
|
+
DefaultInfo,
|
|
15
|
+
DefaultType,
|
|
16
|
+
Field,
|
|
17
|
+
FieldsDictionary,
|
|
18
|
+
Index,
|
|
19
|
+
IndexesDictionary,
|
|
20
|
+
Table,
|
|
21
|
+
TableConstraintsDictionary,
|
|
22
|
+
} from './types';
|
|
23
|
+
|
|
24
|
+
type ConstraintRow = {
|
|
25
|
+
schemaName: string;
|
|
26
|
+
tableName: string;
|
|
27
|
+
constraintName: string;
|
|
28
|
+
columnNames: string[];
|
|
29
|
+
type: string;
|
|
30
|
+
primary?: boolean;
|
|
31
|
+
unique?: boolean;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type GeneratedIndexes = {
|
|
35
|
+
indexes: IndexesDictionary;
|
|
36
|
+
tableConstraints: TableConstraintsDictionary;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const parseConnectionString = (connectionString: string): Record<string, string> => {
|
|
40
|
+
const params: Record<string, string> = {};
|
|
41
|
+
const regex = /([^;=]+)=([^;]*)/g;
|
|
42
|
+
let match;
|
|
43
|
+
|
|
44
|
+
while ((match = regex.exec(connectionString)) !== null) {
|
|
45
|
+
params[match[1].trim()] = match[2].trim();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return params;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
const connect = async (connection: Connection): Promise<void> => {
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
console.log('Attempting to connect...');
|
|
55
|
+
connection.connect((err) => {
|
|
56
|
+
if (err) {
|
|
57
|
+
reject(err);
|
|
58
|
+
} else {
|
|
59
|
+
resolve();
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const executeQuery = (connection: Connection, sqlText: string): Promise<any[]> => {
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
console.log('Executing query...');
|
|
68
|
+
connection.execute({
|
|
69
|
+
sqlText: sqlText,
|
|
70
|
+
complete: (err, stmt, rows) => {
|
|
71
|
+
if (err) {
|
|
72
|
+
reject(err);
|
|
73
|
+
} else {
|
|
74
|
+
console.log('Query executed successfully.');
|
|
75
|
+
if (rows) {
|
|
76
|
+
resolve(rows);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const isLogLevel = (value: string): value is LogLevel => {
|
|
85
|
+
return ['ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE'].includes(value as LogLevel)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const connectToSnowflake = async (config: Record<string, string>): Promise<Connection> => {
|
|
89
|
+
const logLevel = isLogLevel(config.LOG_LEVEL) ? config.LOG_LEVEL : 'INFO';
|
|
90
|
+
const isDebugMode = config.IS_DEBUG_MODE === 'true';
|
|
91
|
+
snowflake.configure({
|
|
92
|
+
logLevel,
|
|
93
|
+
additionalLogToConsole: isDebugMode,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// "SERVER=myaccount.snowflakecomputing.com;UID=myusername;PWD=mypassword;DATABASE=mydatabase;WAREHOUSE=mywarehouse;ROLE=myrole";
|
|
97
|
+
const connection = snowflake.createConnection({
|
|
98
|
+
account: config.SERVER,
|
|
99
|
+
username: config.UID,
|
|
100
|
+
password: config.PWD,
|
|
101
|
+
database: config.DATABASE,
|
|
102
|
+
warehouse: config.WAREHOUSE,
|
|
103
|
+
sfRetryMaxLoginRetries: 3,
|
|
104
|
+
timeout: 10000,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
// Connect to Snowflake
|
|
109
|
+
await connect(connection);
|
|
110
|
+
|
|
111
|
+
// Execute the query
|
|
112
|
+
await executeQuery(connection, 'SELECT CURRENT_VERSION();');
|
|
113
|
+
|
|
114
|
+
return connection;
|
|
115
|
+
} catch (err) {
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
const convertQueryBoolean = (val: string | null) => val === 'YES';
|
|
122
|
+
|
|
123
|
+
const getFieldType = (data_type: string, character_maximum_length: number, numeric_precision: number, numeric_scale: number): string => {
|
|
124
|
+
if (character_maximum_length) {
|
|
125
|
+
return `${data_type}(${character_maximum_length})`;
|
|
126
|
+
}
|
|
127
|
+
if (numeric_precision && numeric_scale) {
|
|
128
|
+
return `${data_type}(${numeric_precision},${numeric_scale})`;
|
|
129
|
+
}
|
|
130
|
+
return data_type;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const getDbdefault = (column_default: string, default_type: DefaultType): DefaultInfo => {
|
|
134
|
+
if (default_type === 'string') {
|
|
135
|
+
return {
|
|
136
|
+
type: default_type,
|
|
137
|
+
value: column_default.slice(1, -1),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
type: default_type,
|
|
142
|
+
value: column_default,
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const generateField = (row: Record<string, any>): Field => {
|
|
147
|
+
const {
|
|
148
|
+
COLUMN_NAME: column_name,
|
|
149
|
+
DATA_TYPE: data_type,
|
|
150
|
+
CHARACTER_MAXIMUM_LENGTH: character_maximum_length,
|
|
151
|
+
NUMERIC_PRECISION: numeric_precision,
|
|
152
|
+
NUMERIC_SCALE: numeric_scale,
|
|
153
|
+
IDENTITY_INCREMENT: identity_increment,
|
|
154
|
+
IS_NULLABLE: is_nullable,
|
|
155
|
+
COLUMN_DEFAULT: column_default,
|
|
156
|
+
DEFAULT_TYPE: default_type,
|
|
157
|
+
COLUMN_COMMENT: column_comment,
|
|
158
|
+
} = row;
|
|
159
|
+
|
|
160
|
+
const dbdefault = column_default ? getDbdefault(column_default, default_type) : null;
|
|
161
|
+
|
|
162
|
+
const fieldType = {
|
|
163
|
+
type_name: getFieldType(data_type, character_maximum_length, numeric_precision, numeric_scale),
|
|
164
|
+
schemaName: null,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
name: column_name,
|
|
169
|
+
type: fieldType,
|
|
170
|
+
dbdefault,
|
|
171
|
+
not_null: !convertQueryBoolean(is_nullable),
|
|
172
|
+
increment: !!identity_increment,
|
|
173
|
+
note: column_comment ? { value: column_comment } : { value: '' },
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const generateTablesAndFields = async (conn: Connection, schemas: string[]): Promise<{
|
|
178
|
+
tables: Table[],
|
|
179
|
+
fields: FieldsDictionary,
|
|
180
|
+
}> => {
|
|
181
|
+
const fields: FieldsDictionary = {};
|
|
182
|
+
const schemaSql = schemas.length > 0 ? `AND c.table_schema IN (${schemas.map((schema) => `'${schema}'`).join(',')})` : '';
|
|
183
|
+
const tablesAndFieldsSql = `
|
|
184
|
+
SELECT
|
|
185
|
+
c.table_schema,
|
|
186
|
+
c.table_name,
|
|
187
|
+
c.column_name,
|
|
188
|
+
c.data_type,
|
|
189
|
+
c.character_maximum_length,
|
|
190
|
+
c.numeric_precision,
|
|
191
|
+
c.numeric_scale,
|
|
192
|
+
c.is_nullable,
|
|
193
|
+
c.column_default,
|
|
194
|
+
CASE
|
|
195
|
+
WHEN c.column_default IS NULL THEN NULL
|
|
196
|
+
WHEN c.column_default LIKE '''%' THEN 'string'
|
|
197
|
+
WHEN LOWER(c.column_default) = 'true' OR LOWER(c.column_default) = 'false' THEN 'boolean'
|
|
198
|
+
WHEN c.column_default REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN 'number'
|
|
199
|
+
ELSE 'expression'
|
|
200
|
+
END AS default_type,
|
|
201
|
+
t.comment AS table_comment,
|
|
202
|
+
c.comment AS column_comment,
|
|
203
|
+
c.identity_increment
|
|
204
|
+
FROM
|
|
205
|
+
information_schema.columns c
|
|
206
|
+
JOIN information_schema.tables t
|
|
207
|
+
ON c.table_name = t.table_name
|
|
208
|
+
AND c.table_schema = t.table_schema
|
|
209
|
+
WHERE
|
|
210
|
+
t.table_type = 'BASE TABLE'
|
|
211
|
+
AND t.table_schema NOT IN ('INFORMATION_SCHEMA')
|
|
212
|
+
${schemaSql}
|
|
213
|
+
ORDER BY
|
|
214
|
+
c.table_schema,
|
|
215
|
+
c.table_name,
|
|
216
|
+
c.ordinal_position;
|
|
217
|
+
`;
|
|
218
|
+
|
|
219
|
+
const tablesAndFieldsResult = await executeQuery(conn, tablesAndFieldsSql);
|
|
220
|
+
const tables = tablesAndFieldsResult.reduce((acc: Record<string, Table>, row: Record<string, string>) => {
|
|
221
|
+
const { TABLE_SCHEMA: table_schema, TABLE_NAME: table_name, TABLE_COMMENT: table_comment } = row;
|
|
222
|
+
const key = `${table_schema}.${table_name}`;
|
|
223
|
+
|
|
224
|
+
if (!acc[key]) {
|
|
225
|
+
acc[key] = {
|
|
226
|
+
name: table_name,
|
|
227
|
+
schemaName: table_schema,
|
|
228
|
+
note: table_comment ? { value: table_comment } : { value: '' },
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!fields[key]) fields[key] = [];
|
|
233
|
+
const field = generateField(row);
|
|
234
|
+
fields[key].push(field);
|
|
235
|
+
|
|
236
|
+
return acc;
|
|
237
|
+
}, {});
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
tables: Object.values(tables),
|
|
241
|
+
fields,
|
|
242
|
+
};
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const createConstraintKeysMap = (keys: Record<string, string>[], schemas: string[], constraintType: 'primary' | 'unique'): Record<string, ConstraintRow> => {
|
|
246
|
+
return keys.reduce((acc: Record<string, ConstraintRow>, row: Record<string, string>) => {
|
|
247
|
+
const { schema_name, table_name, column_name, constraint_name } = row;
|
|
248
|
+
const selectedSchema = schemas.length > 0 ? schemas.includes(schema_name) : true;
|
|
249
|
+
if (!selectedSchema) { return acc; }
|
|
250
|
+
|
|
251
|
+
const key = `${schema_name}.${table_name}.${constraint_name}`;
|
|
252
|
+
|
|
253
|
+
if (acc[key]) {
|
|
254
|
+
const columnNames = acc[key].columnNames;
|
|
255
|
+
columnNames.push(column_name);
|
|
256
|
+
return acc;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
acc[key] = {
|
|
260
|
+
schemaName: schema_name,
|
|
261
|
+
tableName: table_name,
|
|
262
|
+
constraintName: constraint_name,
|
|
263
|
+
columnNames: [column_name],
|
|
264
|
+
type: '',
|
|
265
|
+
};
|
|
266
|
+
acc[key][constraintType] = true;
|
|
267
|
+
|
|
268
|
+
return acc;
|
|
269
|
+
}, {});
|
|
270
|
+
};
|
|
271
|
+
const generateIndexes = async (conn: Connection, databaseName: string, schemas: string[]) => {
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
const getPrimaryKeysSql = `
|
|
275
|
+
SHOW PRIMARY KEYS IN DATABASE ${databaseName};
|
|
276
|
+
`;
|
|
277
|
+
|
|
278
|
+
const getUniqueKeysSql = `
|
|
279
|
+
SHOW UNIQUE KEYS IN DATABASE ${databaseName};
|
|
280
|
+
`;
|
|
281
|
+
|
|
282
|
+
const primaryKeys = await executeQuery(conn, getPrimaryKeysSql);
|
|
283
|
+
const uniqueKeys = await executeQuery(conn, getUniqueKeysSql);
|
|
284
|
+
|
|
285
|
+
const primaryKeysByConstraint: Record<string, ConstraintRow> = createConstraintKeysMap(primaryKeys, schemas, 'primary');
|
|
286
|
+
const uniqueKeysByConstraint: Record<string, ConstraintRow> = createConstraintKeysMap(uniqueKeys, schemas, 'unique');
|
|
287
|
+
|
|
288
|
+
const allConstraints: ConstraintRow[] = [Object.values(primaryKeysByConstraint), Object.values(uniqueKeysByConstraint)].flat();
|
|
289
|
+
const { indexes, tableConstraints } = allConstraints.reduce((acc: GeneratedIndexes, row: ConstraintRow): GeneratedIndexes => {
|
|
290
|
+
const { schemaName, tableName, constraintName, columnNames, type, primary, unique } = row;
|
|
291
|
+
const key = `${schemaName}.${tableName}`;
|
|
292
|
+
|
|
293
|
+
if (columnNames.length < 2) {
|
|
294
|
+
acc.tableConstraints[key] = {
|
|
295
|
+
[columnNames[0]]: {
|
|
296
|
+
pk: primary,
|
|
297
|
+
unique,
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
return acc;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const index: Index = {
|
|
304
|
+
name: constraintName,
|
|
305
|
+
type,
|
|
306
|
+
unique,
|
|
307
|
+
pk: primary,
|
|
308
|
+
columns: columnNames.map((columnName) => ({
|
|
309
|
+
type: 'column',
|
|
310
|
+
value: columnName,
|
|
311
|
+
})),
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
if (acc.indexes[key]) {
|
|
315
|
+
acc.indexes[key].push(index);
|
|
316
|
+
} else {
|
|
317
|
+
acc.indexes[key] = [index];
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return acc;
|
|
321
|
+
}, { indexes: {}, tableConstraints: {}});
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
indexes,
|
|
325
|
+
tableConstraints,
|
|
326
|
+
};
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
const fetchSchemaJson = async (connection: string): Promise<DatabaseSchema> => {
|
|
330
|
+
const config = parseConnectionString(connection);
|
|
331
|
+
const conn = await connectToSnowflake(config);
|
|
332
|
+
if (conn instanceof Error) {
|
|
333
|
+
throw conn;
|
|
334
|
+
}
|
|
335
|
+
// Schemas: schema1,schema2,schema3
|
|
336
|
+
const schemas = config.SCHEMAS ? parseSchema(config.SCHEMAS) : [];
|
|
337
|
+
const databaseName = config.DATABASE;
|
|
338
|
+
|
|
339
|
+
const tablesAndFieldsRes = generateTablesAndFields(conn, schemas);
|
|
340
|
+
const indexesRes = generateIndexes(conn, databaseName, schemas);
|
|
341
|
+
|
|
342
|
+
const res = await Promise.all([
|
|
343
|
+
tablesAndFieldsRes,
|
|
344
|
+
indexesRes,
|
|
345
|
+
]);
|
|
346
|
+
|
|
347
|
+
conn.destroy((err) => {
|
|
348
|
+
if (err) {
|
|
349
|
+
throw err;
|
|
350
|
+
} else {
|
|
351
|
+
console.log('Connection destroyed successfully.');
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
const { tables, fields } = res[0];
|
|
356
|
+
const { indexes, tableConstraints } = res[1];
|
|
357
|
+
|
|
358
|
+
return {
|
|
359
|
+
tables,
|
|
360
|
+
fields,
|
|
361
|
+
refs: [],
|
|
362
|
+
enums: [],
|
|
363
|
+
indexes,
|
|
364
|
+
tableConstraints,
|
|
365
|
+
};
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
export {
|
|
369
|
+
fetchSchemaJson,
|
|
370
|
+
};
|
package/src/connectors/types.ts
CHANGED
|
@@ -21,6 +21,8 @@ interface Field {
|
|
|
21
21
|
dbdefault: DefaultInfo | null;
|
|
22
22
|
not_null: boolean;
|
|
23
23
|
increment: boolean;
|
|
24
|
+
pk?: boolean;
|
|
25
|
+
unique?: boolean;
|
|
24
26
|
note: NoteInfo;
|
|
25
27
|
}
|
|
26
28
|
|
|
@@ -95,6 +97,15 @@ interface DatabaseSchema {
|
|
|
95
97
|
indexes: IndexesDictionary; // Represents the indexes property
|
|
96
98
|
}
|
|
97
99
|
|
|
100
|
+
interface BigQueryCredentials {
|
|
101
|
+
projectId: string,
|
|
102
|
+
credentials: {
|
|
103
|
+
clientEmail: string,
|
|
104
|
+
privateKey: string,
|
|
105
|
+
},
|
|
106
|
+
datasets: string[],
|
|
107
|
+
}
|
|
108
|
+
|
|
98
109
|
export {
|
|
99
110
|
NoteInfo,
|
|
100
111
|
TypeInfo,
|
|
@@ -113,4 +124,5 @@ export {
|
|
|
113
124
|
Index,
|
|
114
125
|
IndexesDictionary,
|
|
115
126
|
DatabaseSchema,
|
|
127
|
+
BigQueryCredentials,
|
|
116
128
|
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
create schema if not exists dbml_test;
|
|
2
|
+
|
|
3
|
+
create table if not exists dbml_test.test_data_type_default (
|
|
4
|
+
t_int INT64 default 1 OPTIONS(description="Int64 column"),
|
|
5
|
+
t_bool_1 BOOLEAN default true,
|
|
6
|
+
t_bool_2 BOOLEAN default True,
|
|
7
|
+
t_string_1 STRING default 'string column description 1',
|
|
8
|
+
t_string_2 STRING default "string column description 2",
|
|
9
|
+
t_string_3 STRING default "NULL",
|
|
10
|
+
t_float FLOAT64 default 123.67,
|
|
11
|
+
t_decimal DECIMAL default 10,
|
|
12
|
+
t_date DATE default CURRENT_DATE,
|
|
13
|
+
t_datetime DATETIME default CURRENT_DATETIME,
|
|
14
|
+
t_timestamp TIMESTAMP default CURRENT_TIMESTAMP,
|
|
15
|
+
t_array ARRAY<INT64> default ARRAY<INT64>[1, 2, 3],
|
|
16
|
+
t_struct STRUCT<
|
|
17
|
+
street STRING,
|
|
18
|
+
city STRING,
|
|
19
|
+
zip_code INT64
|
|
20
|
+
> default STRUCT('123 Example St', 'Sample City', 12345),
|
|
21
|
+
t_interval INTERVAL,
|
|
22
|
+
t_geo GEOGRAPHY
|
|
23
|
+
) OPTIONS(description="Table description");
|
|
24
|
+
|
|
25
|
+
create table if not exists dbml_test.test_constraint_1 (
|
|
26
|
+
id int64,
|
|
27
|
+
primary key (id) not enforced
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
create table if not exists dbml_test.test_constraint_2 (
|
|
31
|
+
id int64,
|
|
32
|
+
name string,
|
|
33
|
+
|
|
34
|
+
primary key (id, name) not enforced
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
create table if not exists dbml_test.test_constraint_3 (
|
|
38
|
+
id int64,
|
|
39
|
+
fk_id int64
|
|
40
|
+
);
|
|
41
|
+
alter table dbml_test.test_constraint_3
|
|
42
|
+
add foreign key (fk_id) references dbml_test.test_constraint_1 (id) not enforced;
|
|
43
|
+
|
|
44
|
+
create table if not exists dbml_test.test_constraint_4 (
|
|
45
|
+
fk_id int64,
|
|
46
|
+
fk_name string,
|
|
47
|
+
primary key (fk_id, fk_name) not enforced
|
|
48
|
+
);
|
|
49
|
+
alter table dbml_test.test_constraint_4
|
|
50
|
+
add foreign key (fk_id, fk_name) references dbml_test.test_constraint_2 (id, name) not enforced;
|
|
51
|
+
|
|
52
|
+
create table dbml_test.test_all_index (
|
|
53
|
+
a string,
|
|
54
|
+
b int64,
|
|
55
|
+
c struct <d int64, e array<string>, f struct<g string, h int64>>
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
create search index all_col_index
|
|
59
|
+
on dbml_test.test_all_index(ALL COLUMNS)
|
|
60
|
+
options (analyzer = 'NO_OP_ANALYZER');
|
|
61
|
+
|
|
62
|
+
create table dbml_test.test_multi_index (a string, b int64, c json, d timestamp);
|
|
63
|
+
|
|
64
|
+
create search index multi_index
|
|
65
|
+
on dbml_test.test_multi_index (a, b, c)
|
|
66
|
+
options ( data_types = ['string', 'int64', 'timestamp']);
|
|
67
|
+
|
|
68
|
+
create table dbml_test.test_index (a string, b int64);
|
|
69
|
+
|
|
70
|
+
create search index simple_index
|
|
71
|
+
on dbml_test.test_index (a);
|