@lightdash/warehouses 0.2100.0 → 0.2101.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.
|
@@ -43,11 +43,19 @@ export declare class SnowflakeSqlBuilder extends WarehouseBaseSqlBuilder {
|
|
|
43
43
|
export declare class SnowflakeWarehouseClient extends WarehouseBaseClient<CreateSnowflakeCredentials> {
|
|
44
44
|
connectionOptions: ConnectionOptions;
|
|
45
45
|
quotedIdentifiersIgnoreCase?: boolean;
|
|
46
|
+
private externalBrowserConnectionPromise?;
|
|
46
47
|
constructor(credentials: CreateSnowflakeCredentials);
|
|
47
48
|
private getConnection;
|
|
49
|
+
/**
|
|
50
|
+
* Creates and caches a connection for external browser authentication.
|
|
51
|
+
* This prevents opening multiple browser tabs when parallel queries are executed.
|
|
52
|
+
*/
|
|
53
|
+
private createExternalBrowserConnection;
|
|
54
|
+
private createConnection;
|
|
48
55
|
private prepareWarehouse;
|
|
49
56
|
private getFieldsFromStatement;
|
|
50
57
|
private getAsyncStatementResults;
|
|
58
|
+
private destroyConnection;
|
|
51
59
|
getAsyncQueryResults<TFormattedRow extends Record<string, unknown>>({ sql, page, pageSize, queryId }: WarehouseGetAsyncQueryResultsArgs, rowFormatter?: (row: Record<string, unknown>) => TFormattedRow): Promise<WarehouseGetAsyncQueryResults<TFormattedRow>>;
|
|
52
60
|
executeAsyncQuery({ sql, values, tags, timezone }: WarehouseExecuteAsyncQueryArgs, resultsStreamCallback: (rows: WarehouseResults['rows'], fields: WarehouseResults['fields']) => void): Promise<WarehouseExecuteAsyncQuery>;
|
|
53
61
|
private executeAsyncStatement;
|
|
@@ -82,6 +82,7 @@ const normaliseSnowflakeType = (type) => {
|
|
|
82
82
|
}
|
|
83
83
|
return match[0];
|
|
84
84
|
};
|
|
85
|
+
const EXTERNAL_BROWSER_AUTHENTICATOR = 'EXTERNALBROWSER';
|
|
85
86
|
const mapFieldType = (type) => {
|
|
86
87
|
switch (normaliseSnowflakeType(type)) {
|
|
87
88
|
case SnowflakeTypes.NUMBER:
|
|
@@ -167,7 +168,7 @@ class SnowflakeWarehouseClient extends WarehouseBaseClient_1.default {
|
|
|
167
168
|
}
|
|
168
169
|
let authenticationOptions = {};
|
|
169
170
|
// if authenticationType is undefined, we assume it is a password authentication, for backwards compatibility
|
|
170
|
-
if (credentials.authenticationType ===
|
|
171
|
+
if (credentials.authenticationType === common_1.SnowflakeAuthenticationType.SSO) {
|
|
171
172
|
if (!credentials.token) {
|
|
172
173
|
// Perhaps we forgot to refresh the token before building the client, check buildAdapter for more details
|
|
173
174
|
throw new common_1.UnexpectedServerError('Snowflake token is required for SSO authentication');
|
|
@@ -178,9 +179,16 @@ class SnowflakeWarehouseClient extends WarehouseBaseClient_1.default {
|
|
|
178
179
|
authenticator: 'OAUTH',
|
|
179
180
|
};
|
|
180
181
|
}
|
|
182
|
+
else if (credentials.authenticationType ===
|
|
183
|
+
common_1.SnowflakeAuthenticationType.EXTERNAL_BROWSER) {
|
|
184
|
+
authenticationOptions = {
|
|
185
|
+
authenticator: EXTERNAL_BROWSER_AUTHENTICATOR,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
181
188
|
else if (credentials.privateKey &&
|
|
182
189
|
(!credentials.password ||
|
|
183
|
-
credentials.authenticationType ===
|
|
190
|
+
credentials.authenticationType ===
|
|
191
|
+
common_1.SnowflakeAuthenticationType.PRIVATE_KEY)) {
|
|
184
192
|
if (!credentials.privateKeyPass) {
|
|
185
193
|
authenticationOptions = {
|
|
186
194
|
username: credentials.user,
|
|
@@ -242,9 +250,53 @@ class SnowflakeWarehouseClient extends WarehouseBaseClient_1.default {
|
|
|
242
250
|
warehouse: this.connectionOptions.warehouse,
|
|
243
251
|
accessUrl: this.connectionOptions.accessUrl,
|
|
244
252
|
};
|
|
245
|
-
|
|
253
|
+
if (credentials.requireUserCredentials)
|
|
254
|
+
console.info(`Initialized snowflake warehouse client with "requireUserCredentials" authentication type "${credentials.authenticationType}" and connection options: ${JSON.stringify(logConnectionOptions)}`);
|
|
246
255
|
}
|
|
247
256
|
async getConnection(connectionOptionsOverrides) {
|
|
257
|
+
// External browser authentication uses a cached connection to avoid opening multiple browser tabs
|
|
258
|
+
if (this.connectionOptions.authenticator ===
|
|
259
|
+
EXTERNAL_BROWSER_AUTHENTICATOR) {
|
|
260
|
+
return this.createExternalBrowserConnection(connectionOptionsOverrides);
|
|
261
|
+
}
|
|
262
|
+
// For other authentication types, create a new connection with optional overrides
|
|
263
|
+
return this.createConnection(connectionOptionsOverrides);
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Creates and caches a connection for external browser authentication.
|
|
267
|
+
* This prevents opening multiple browser tabs when parallel queries are executed.
|
|
268
|
+
*/
|
|
269
|
+
async createExternalBrowserConnection(connectionOptionsOverrides) {
|
|
270
|
+
// Return cached promise if one exists (handles both in-flight and completed connections)
|
|
271
|
+
if (this.externalBrowserConnectionPromise) {
|
|
272
|
+
return this.externalBrowserConnectionPromise;
|
|
273
|
+
}
|
|
274
|
+
// Create and cache the connection promise
|
|
275
|
+
this.externalBrowserConnectionPromise = (async () => {
|
|
276
|
+
let connection;
|
|
277
|
+
try {
|
|
278
|
+
connection = (0, snowflake_sdk_1.createConnection)({
|
|
279
|
+
...this.connectionOptions,
|
|
280
|
+
...connectionOptionsOverrides,
|
|
281
|
+
});
|
|
282
|
+
console.info(`Connecting to snowflake warehouse with "external_browser" authentication type`);
|
|
283
|
+
await Util.promisify(connection.connectAsync.bind(connection))();
|
|
284
|
+
}
|
|
285
|
+
catch (e) {
|
|
286
|
+
throw new common_1.WarehouseConnectionError(`Snowflake error: ${(0, common_1.getErrorMessage)(e)}`);
|
|
287
|
+
}
|
|
288
|
+
return connection;
|
|
289
|
+
})();
|
|
290
|
+
try {
|
|
291
|
+
return await this.externalBrowserConnectionPromise;
|
|
292
|
+
}
|
|
293
|
+
catch (e) {
|
|
294
|
+
// Clear cache on error to allow retry
|
|
295
|
+
this.externalBrowserConnectionPromise = undefined;
|
|
296
|
+
throw e;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
async createConnection(connectionOptionsOverrides) {
|
|
248
300
|
let connection;
|
|
249
301
|
try {
|
|
250
302
|
connection = (0, snowflake_sdk_1.createConnection)({
|
|
@@ -328,6 +380,22 @@ class SnowflakeWarehouseClient extends WarehouseBaseClient_1.default {
|
|
|
328
380
|
numRows: statement.getNumRows(),
|
|
329
381
|
};
|
|
330
382
|
}
|
|
383
|
+
async destroyConnection(connection, authenticator) {
|
|
384
|
+
if (authenticator === EXTERNAL_BROWSER_AUTHENTICATOR) {
|
|
385
|
+
// EXTERNALBROWSER connections are never destroyed - they live for the lifetime of the client✅
|
|
386
|
+
// Other auth types (password, SSO, private key) still destroy connections after use
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
console.info(`Destroying snowflake connection for authenticator ${authenticator}`);
|
|
390
|
+
await new Promise((resolve, reject) => {
|
|
391
|
+
connection.destroy((err, conn) => {
|
|
392
|
+
if (err) {
|
|
393
|
+
reject(new common_1.WarehouseConnectionError(err.message));
|
|
394
|
+
}
|
|
395
|
+
resolve(conn);
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
}
|
|
331
399
|
async getAsyncQueryResults({ sql, page, pageSize, queryId }, rowFormatter) {
|
|
332
400
|
if (queryId === null) {
|
|
333
401
|
throw new common_1.WarehouseQueryError('Query ID is required');
|
|
@@ -350,14 +418,7 @@ class SnowflakeWarehouseClient extends WarehouseBaseClient_1.default {
|
|
|
350
418
|
throw this.parseError(error, sql);
|
|
351
419
|
}
|
|
352
420
|
finally {
|
|
353
|
-
await
|
|
354
|
-
connection.destroy((err, conn) => {
|
|
355
|
-
if (err) {
|
|
356
|
-
reject(new common_1.WarehouseConnectionError(err.message));
|
|
357
|
-
}
|
|
358
|
-
resolve(conn);
|
|
359
|
-
});
|
|
360
|
-
});
|
|
421
|
+
await this.destroyConnection(connection, this.connectionOptions.authenticator);
|
|
361
422
|
}
|
|
362
423
|
}
|
|
363
424
|
async executeAsyncQuery({ sql, values, tags, timezone }, resultsStreamCallback) {
|
|
@@ -451,14 +512,7 @@ class SnowflakeWarehouseClient extends WarehouseBaseClient_1.default {
|
|
|
451
512
|
throw this.parseError(error, sql);
|
|
452
513
|
}
|
|
453
514
|
finally {
|
|
454
|
-
await
|
|
455
|
-
connection.destroy((err, conn) => {
|
|
456
|
-
if (err) {
|
|
457
|
-
reject(new common_1.WarehouseConnectionError(err.message));
|
|
458
|
-
}
|
|
459
|
-
resolve(conn);
|
|
460
|
-
});
|
|
461
|
-
});
|
|
515
|
+
await this.destroyConnection(connection, this.connectionOptions.authenticator);
|
|
462
516
|
}
|
|
463
517
|
}
|
|
464
518
|
async executeStreamStatement(connection, sqlText, streamCallback, options) {
|
|
@@ -522,10 +576,7 @@ class SnowflakeWarehouseClient extends WarehouseBaseClient_1.default {
|
|
|
522
576
|
}
|
|
523
577
|
async runTableCatalogQuery(database, schema, table) {
|
|
524
578
|
const sqlText = `SHOW COLUMNS IN TABLE ${table}`;
|
|
525
|
-
const connection = await this.getConnection({
|
|
526
|
-
schema,
|
|
527
|
-
database,
|
|
528
|
-
});
|
|
579
|
+
const connection = await this.getConnection({ schema, database });
|
|
529
580
|
try {
|
|
530
581
|
return await this.executeStatements(connection, sqlText);
|
|
531
582
|
}
|
|
@@ -536,14 +587,7 @@ class SnowflakeWarehouseClient extends WarehouseBaseClient_1.default {
|
|
|
536
587
|
return undefined;
|
|
537
588
|
}
|
|
538
589
|
finally {
|
|
539
|
-
await
|
|
540
|
-
connection.destroy((err, conn) => {
|
|
541
|
-
if (err) {
|
|
542
|
-
reject(new common_1.WarehouseConnectionError(err.message));
|
|
543
|
-
}
|
|
544
|
-
resolve(conn);
|
|
545
|
-
});
|
|
546
|
-
});
|
|
590
|
+
await this.destroyConnection(connection, this.connectionOptions.authenticator);
|
|
547
591
|
}
|
|
548
592
|
}
|
|
549
593
|
async getCatalog(config) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightdash/warehouses",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2101.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
"lodash": "^4.17.21",
|
|
15
15
|
"pg": "^8.13.1",
|
|
16
16
|
"pg-cursor": "^2.10.0",
|
|
17
|
-
"snowflake-sdk": "~2.1
|
|
17
|
+
"snowflake-sdk": "~2.3.1",
|
|
18
18
|
"ssh2": "^1.14.0",
|
|
19
19
|
"trino-client": "0.2.6",
|
|
20
|
-
"@lightdash/common": "0.
|
|
20
|
+
"@lightdash/common": "0.2101.1"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@types/pg": "^8.11.10",
|