@dbml/connector 5.6.0-alpha.1 → 6.0.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,311 @@
1
+ "use strict";
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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
10
+ return (mod && mod.__esModule) ? mod : { "default": mod };
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.fetchSchemaJson = void 0;
14
+ const snowflake_sdk_1 = __importDefault(require("snowflake-sdk"));
15
+ const parseSchema_1 = require("../../utils/parseSchema");
16
+ const parseConnectionString = (connectionString) => {
17
+ const params = {};
18
+ const regex = /([^;=]+)=([^;]*)/g;
19
+ let match;
20
+ while ((match = regex.exec(connectionString)) !== null) {
21
+ params[match[1].trim()] = match[2].trim();
22
+ }
23
+ return params;
24
+ };
25
+ const connect = async (connection) => {
26
+ return new Promise((resolve, reject) => {
27
+ console.log('Attempting to connect...');
28
+ connection.connect((err) => {
29
+ if (err) {
30
+ reject(err);
31
+ }
32
+ else {
33
+ resolve();
34
+ }
35
+ });
36
+ });
37
+ };
38
+ const executeQuery = (connection, sqlText) => {
39
+ return new Promise((resolve, reject) => {
40
+ console.log('Executing query...');
41
+ connection.execute({
42
+ sqlText,
43
+ complete: (err, stmt, rows) => {
44
+ if (err) {
45
+ reject(err);
46
+ }
47
+ else {
48
+ console.log('Query executed successfully.');
49
+ if (rows) {
50
+ resolve(rows);
51
+ }
52
+ }
53
+ },
54
+ });
55
+ });
56
+ };
57
+ const isLogLevel = (value) => {
58
+ return ['ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE'].includes(value);
59
+ };
60
+ const connectToSnowflake = async (config) => {
61
+ const logLevel = isLogLevel(config.LOG_LEVEL) ? config.LOG_LEVEL : 'INFO';
62
+ const isDebugMode = config.IS_DEBUG_MODE === 'true';
63
+ snowflake_sdk_1.default.configure({
64
+ logLevel,
65
+ additionalLogToConsole: isDebugMode,
66
+ });
67
+ // https://docs.snowflake.com/en/developer-guide/node-js/nodejs-driver-options#authentication-options
68
+ let authConfig = {};
69
+ switch (config.AUTHENTICATOR) {
70
+ case 'SNOWFLAKE_JWT':
71
+ // https://docs.snowflake.com/en/developer-guide/node-js/nodejs-driver-authenticate#label-nodejs-key-pair-authentication
72
+ // Support private key file
73
+ authConfig = {
74
+ privateKeyPath: config.PRIVATE_KEY_PATH,
75
+ privateKeyPass: config.PASSPHRASE,
76
+ };
77
+ break;
78
+ case 'SNOWFLAKE':
79
+ case undefined:
80
+ authConfig = {
81
+ password: config.PWD,
82
+ };
83
+ break;
84
+ default:
85
+ throw new Error('Unsupported authenticator');
86
+ }
87
+ // "SERVER=myaccount.snowflakecomputing.com;UID=myusername;PWD=mypassword;DATABASE=mydatabase;WAREHOUSE=mywarehouse;ROLE=myrole";
88
+ const connectionOptions = {
89
+ authenticator: config.AUTHENTICATOR,
90
+ account: config.SERVER,
91
+ username: config.UID,
92
+ database: config.DATABASE,
93
+ warehouse: config.WAREHOUSE,
94
+ sfRetryMaxLoginRetries: 3,
95
+ timeout: 10000,
96
+ ...authConfig,
97
+ };
98
+ const connection = snowflake_sdk_1.default.createConnection(connectionOptions);
99
+ // Connect to Snowflake
100
+ await connect(connection);
101
+ // Execute the query
102
+ await executeQuery(connection, 'SELECT CURRENT_VERSION();');
103
+ return connection;
104
+ };
105
+ const convertQueryBoolean = (val) => val === 'YES';
106
+ const getFieldType = (data_type, character_maximum_length, numeric_precision, numeric_scale) => {
107
+ if (character_maximum_length) {
108
+ return `${data_type}(${character_maximum_length})`;
109
+ }
110
+ if (numeric_precision && numeric_scale) {
111
+ return `${data_type}(${numeric_precision},${numeric_scale})`;
112
+ }
113
+ return data_type;
114
+ };
115
+ const getDbdefault = (column_default, default_type) => {
116
+ if (default_type === 'string') {
117
+ return {
118
+ type: default_type,
119
+ value: column_default.slice(1, -1),
120
+ };
121
+ }
122
+ return {
123
+ type: default_type,
124
+ value: column_default,
125
+ };
126
+ };
127
+ const generateField = (row) => {
128
+ const { COLUMN_NAME: column_name, DATA_TYPE: data_type, CHARACTER_MAXIMUM_LENGTH: character_maximum_length, NUMERIC_PRECISION: numeric_precision, NUMERIC_SCALE: numeric_scale, IDENTITY_INCREMENT: identity_increment, IS_NULLABLE: is_nullable, COLUMN_DEFAULT: column_default, DEFAULT_TYPE: default_type, COLUMN_COMMENT: column_comment, } = row;
129
+ const dbdefault = column_default ? getDbdefault(column_default, default_type) : null;
130
+ const fieldType = {
131
+ type_name: getFieldType(data_type, character_maximum_length, numeric_precision, numeric_scale),
132
+ schemaName: null,
133
+ };
134
+ return {
135
+ name: column_name,
136
+ type: fieldType,
137
+ dbdefault,
138
+ not_null: !convertQueryBoolean(is_nullable),
139
+ increment: !!identity_increment,
140
+ note: column_comment ? { value: column_comment } : { value: '' },
141
+ };
142
+ };
143
+ const generateTablesAndFields = async (conn, schemas) => {
144
+ const fields = {};
145
+ const schemaSql = schemas.length > 0 ? `AND c.table_schema IN (${schemas.map((schema) => `'${schema}'`).join(',')})` : '';
146
+ const tablesAndFieldsSql = `
147
+ SELECT
148
+ c.table_schema,
149
+ c.table_name,
150
+ c.column_name,
151
+ c.data_type,
152
+ c.character_maximum_length,
153
+ c.numeric_precision,
154
+ c.numeric_scale,
155
+ c.is_nullable,
156
+ c.column_default,
157
+ CASE
158
+ WHEN c.column_default IS NULL THEN NULL
159
+ WHEN c.column_default LIKE '''%' THEN 'string'
160
+ WHEN LOWER(c.column_default) = 'true' OR LOWER(c.column_default) = 'false' THEN 'boolean'
161
+ WHEN c.column_default REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN 'number'
162
+ ELSE 'expression'
163
+ END AS default_type,
164
+ t.comment AS table_comment,
165
+ c.comment AS column_comment,
166
+ c.identity_increment
167
+ FROM
168
+ information_schema.columns c
169
+ JOIN information_schema.tables t
170
+ ON c.table_name = t.table_name
171
+ AND c.table_schema = t.table_schema
172
+ WHERE
173
+ t.table_type = 'BASE TABLE'
174
+ AND t.table_schema NOT IN ('INFORMATION_SCHEMA')
175
+ ${schemaSql}
176
+ ORDER BY
177
+ c.table_schema,
178
+ t.created,
179
+ c.ordinal_position;
180
+ `;
181
+ const tablesAndFieldsResult = await executeQuery(conn, tablesAndFieldsSql);
182
+ const tables = tablesAndFieldsResult.reduce((acc, row) => {
183
+ const { TABLE_SCHEMA: table_schema, TABLE_NAME: table_name, TABLE_COMMENT: table_comment } = row;
184
+ const key = `${table_schema}.${table_name}`;
185
+ if (!acc[key]) {
186
+ acc[key] = {
187
+ name: table_name,
188
+ schemaName: table_schema,
189
+ note: table_comment ? { value: table_comment } : { value: '' },
190
+ };
191
+ }
192
+ if (!fields[key])
193
+ fields[key] = [];
194
+ const field = generateField(row);
195
+ fields[key].push(field);
196
+ return acc;
197
+ }, {});
198
+ return {
199
+ tables: Object.values(tables),
200
+ fields,
201
+ };
202
+ };
203
+ const createConstraintKeysMap = (keys, schemas, constraintType) => {
204
+ return keys.reduce((acc, row) => {
205
+ const { schema_name, table_name, column_name, constraint_name, } = row;
206
+ const selectedSchema = schemas.length > 0 ? schemas.includes(schema_name) : true;
207
+ if (!selectedSchema) {
208
+ return acc;
209
+ }
210
+ const key = `${schema_name}.${table_name}.${constraint_name}`;
211
+ if (acc[key]) {
212
+ const { columnNames } = acc[key];
213
+ columnNames.push(column_name);
214
+ return acc;
215
+ }
216
+ acc[key] = {
217
+ schemaName: schema_name,
218
+ tableName: table_name,
219
+ constraintName: constraint_name,
220
+ columnNames: [column_name],
221
+ type: '',
222
+ };
223
+ acc[key][constraintType] = true;
224
+ return acc;
225
+ }, {});
226
+ };
227
+ const generateIndexes = async (conn, databaseName, schemas) => {
228
+ const getPrimaryKeysSql = `
229
+ SHOW PRIMARY KEYS IN DATABASE ${databaseName};
230
+ `;
231
+ const getUniqueKeysSql = `
232
+ SHOW UNIQUE KEYS IN DATABASE ${databaseName};
233
+ `;
234
+ const primaryKeys = await executeQuery(conn, getPrimaryKeysSql);
235
+ const uniqueKeys = await executeQuery(conn, getUniqueKeysSql);
236
+ const primaryKeysByConstraint = createConstraintKeysMap(primaryKeys, schemas, 'primary');
237
+ const uniqueKeysByConstraint = createConstraintKeysMap(uniqueKeys, schemas, 'unique');
238
+ const allConstraints = [Object.values(primaryKeysByConstraint), Object.values(uniqueKeysByConstraint)].flat();
239
+ const { indexes, tableConstraints } = allConstraints.reduce((acc, row) => {
240
+ const { schemaName, tableName, constraintName, columnNames, type, primary, unique, } = row;
241
+ const key = `${schemaName}.${tableName}`;
242
+ if (columnNames.length < 2) {
243
+ acc.tableConstraints[key] = {
244
+ [columnNames[0]]: {
245
+ pk: primary,
246
+ unique,
247
+ checks: [],
248
+ },
249
+ };
250
+ return acc;
251
+ }
252
+ const index = {
253
+ name: constraintName,
254
+ type,
255
+ unique,
256
+ pk: primary,
257
+ columns: columnNames.map((columnName) => ({
258
+ type: 'column',
259
+ value: columnName,
260
+ })),
261
+ };
262
+ if (acc.indexes[key]) {
263
+ acc.indexes[key].push(index);
264
+ }
265
+ else {
266
+ acc.indexes[key] = [index];
267
+ }
268
+ return acc;
269
+ }, { indexes: {}, tableConstraints: {} });
270
+ return {
271
+ indexes,
272
+ tableConstraints,
273
+ };
274
+ };
275
+ const fetchSchemaJson = async (connection) => {
276
+ const config = parseConnectionString(connection);
277
+ const conn = await connectToSnowflake(config);
278
+ if (conn instanceof Error) {
279
+ throw conn;
280
+ }
281
+ // Schemas: schema1,schema2,schema3
282
+ const schemas = config.SCHEMAS ? (0, parseSchema_1.parseSchema)(config.SCHEMAS) : [];
283
+ const databaseName = config.DATABASE;
284
+ const tablesAndFieldsRes = generateTablesAndFields(conn, schemas);
285
+ const indexesRes = generateIndexes(conn, databaseName, schemas);
286
+ const res = await Promise.all([
287
+ tablesAndFieldsRes,
288
+ indexesRes,
289
+ ]);
290
+ conn.destroy((err) => {
291
+ if (err) {
292
+ throw err;
293
+ }
294
+ else {
295
+ console.log('Connection destroyed successfully.');
296
+ }
297
+ });
298
+ const { tables, fields } = res[0];
299
+ const { indexes, tableConstraints } = res[1];
300
+ return {
301
+ tables,
302
+ fields,
303
+ refs: [],
304
+ enums: [],
305
+ indexes,
306
+ tableConstraints,
307
+ // Checks are not supported in snowflake
308
+ checks: {},
309
+ };
310
+ };
311
+ exports.fetchSchemaJson = fetchSchemaJson;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });