From af1536077fe319462422da88ff70ad393fd959c0 Mon Sep 17 00:00:00 2001 From: Pieterjan Spoelders Date: Thu, 10 Oct 2024 20:43:52 +0200 Subject: [PATCH 1/8] patch vulnerabilities --- package-lock.json | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index beb89fe..1f7d049 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6122,12 +6122,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -7005,10 +7006,11 @@ } }, "node_modules/rollup": { - "version": "3.29.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", - "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "version": "3.29.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", + "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", "dev": true, + "license": "MIT", "bin": { "rollup": "dist/bin/rollup" }, From 70ac5079085485b0098a35277c1f7c2284ffe4e0 Mon Sep 17 00:00:00 2001 From: Pieterjan Spoelders Date: Thu, 10 Oct 2024 20:44:35 +0200 Subject: [PATCH 2/8] make pool max connections and compression configurable --- src/lib/sql-client.ts | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/lib/sql-client.ts b/src/lib/sql-client.ts index 83087cd..da0ada3 100644 --- a/src/lib/sql-client.ts +++ b/src/lib/sql-client.ts @@ -1,6 +1,5 @@ import * as forge from 'node-forge'; - import { getURIScheme } from './utils'; import { CreatePreparedStatementResponse, PublicKeyResponse, SQLQueriesResponse, SQLResponse } from './types'; import { Statement } from './statement'; @@ -44,6 +43,7 @@ export interface Config { interface InternalConfig { apiVersion: number; compression: boolean; + poolMaxConnections: number; } export const driverVersion = 'v1.0.0'; @@ -61,6 +61,7 @@ export class ExasolDriver implements IExasolDriver { encryption: true, compression: false, apiVersion: 3, + poolMaxConnections: 1, }; private readonly config: Config & InternalConfig & { websocketFactory: websocketFactory }; private readonly logger: ILogger; @@ -68,9 +69,13 @@ export class ExasolDriver implements IExasolDriver { private readonly pool: ConnectionPool; - constructor(websocketFactory: websocketFactory, config: Partial, logger: ILogger = new Logger(LogLevel.Debug)) { + constructor( + websocketFactory: websocketFactory, + config: Partial & Partial, + logger: ILogger = new Logger(LogLevel.Debug), + ) { // Used internally to avoid parallel execution - this.pool = new ConnectionPool(1, logger); + this.pool = new ConnectionPool(config.poolMaxConnections, logger); this.config = { ...this.defaultConfig, ...config, @@ -207,25 +212,25 @@ export class ExasolDriver implements IExasolDriver { async query( sqlStatement: string, attributes?: Partial | undefined, - getCancel?: CetCancelFunction | undefined + getCancel?: CetCancelFunction | undefined, ): Promise; async query( sqlStatement: string, attributes?: Partial | undefined, getCancel?: CetCancelFunction | undefined, - responseType?: 'default' | undefined + responseType?: 'default' | undefined, ): Promise; async query( sqlStatement: string, attributes?: Partial | undefined, getCancel?: CetCancelFunction | undefined, - responseType?: 'raw' | undefined + responseType?: 'raw' | undefined, ): Promise>; async query( sqlStatement: string, attributes?: Partial | undefined, getCancel?: CetCancelFunction | undefined, - responseType?: 'default' | 'raw' + responseType?: 'default' | 'raw', ): Promise> { const connection = await this.acquire(); return connection @@ -268,25 +273,25 @@ export class ExasolDriver implements IExasolDriver { async execute( sqlStatement: string, attributes?: Partial | undefined, - getCancel?: CetCancelFunction | undefined + getCancel?: CetCancelFunction | undefined, ): Promise; async execute( sqlStatement: string, attributes?: Partial | undefined, getCancel?: CetCancelFunction | undefined, - responseType?: 'default' | undefined + responseType?: 'default' | undefined, ): Promise; async execute( sqlStatement: string, attributes?: Partial | undefined, getCancel?: CetCancelFunction | undefined, - responseType?: 'raw' | undefined + responseType?: 'raw' | undefined, ): Promise>; async execute( sqlStatement: string, attributes?: Partial | undefined, getCancel?: CetCancelFunction | undefined, - responseType?: 'default' | 'raw' + responseType?: 'default' | 'raw', ): Promise | number> { const connection = await this.acquire(); return connection @@ -329,7 +334,7 @@ export class ExasolDriver implements IExasolDriver { public async executeBatch( sqlStatements: string[], attributes?: Partial, - getCancel?: CetCancelFunction + getCancel?: CetCancelFunction, ): Promise> { const connection = await this.acquire(); @@ -363,7 +368,7 @@ export class ExasolDriver implements IExasolDriver { command: 'createPreparedStatement', sqlText: sqlStatement, }, - getCancel + getCancel, ) .then((response) => { return new Statement(connection, this.pool, response.responseData.statementHandle, response.responseData.parameterData.columns); @@ -408,13 +413,12 @@ export class ExasolDriver implements IExasolDriver { } return connection; } - + private async loginBasicAuth() { return this.sendCommand({ command: 'login', protocolVersion: this.config.apiVersion, }).then((response) => { - const n = new forge.jsbn.BigInteger(response.responseData.publicKeyModulus, 16); const e = new forge.jsbn.BigInteger(response.responseData.publicKeyExponent, 16); From 1ead2c6bedde99247e8448cda7eda2768f205215 Mon Sep 17 00:00:00 2001 From: Pieterjan Spoelders Date: Fri, 18 Oct 2024 08:31:36 -0400 Subject: [PATCH 3/8] use acquire method instead of calling the acquire method directly on the pool (refactoring) --- src/lib/sql-client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/sql-client.ts b/src/lib/sql-client.ts index da0ada3..03abd5c 100644 --- a/src/lib/sql-client.ts +++ b/src/lib/sql-client.ts @@ -189,7 +189,7 @@ export class ExasolDriver implements IExasolDriver { if (this.closed) { return Promise.reject(ErrClosed); } - const connection = this.pool.acquire(); + const connection = await this.acquire(); if (connection) { return connection .sendCommandWithNoResult(cmd) From cff09b53b4e4569ebe65afea8acd15a9fac1e3ff Mon Sep 17 00:00:00 2001 From: Pieterjan Spoelders Date: Fri, 18 Oct 2024 08:36:43 -0400 Subject: [PATCH 4/8] - 'encapsulated' pool.release in new release() method. - added missing logic to acquire() method. --- src/lib/sql-client.ts | 52 +++++++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/src/lib/sql-client.ts b/src/lib/sql-client.ts index 03abd5c..b5ad4af 100644 --- a/src/lib/sql-client.ts +++ b/src/lib/sql-client.ts @@ -194,11 +194,11 @@ export class ExasolDriver implements IExasolDriver { return connection .sendCommandWithNoResult(cmd) .then(() => { - this.pool.release(connection); + this.release(connection); return; }) .catch((err) => { - this.pool.release(connection); + this.release(connection); throw err; }); } @@ -240,7 +240,7 @@ export class ExasolDriver implements IExasolDriver { }) .then((data) => { if (connection) { - this.pool.release(connection); + this.release(connection); } return data; }) @@ -261,7 +261,7 @@ export class ExasolDriver implements IExasolDriver { }) .catch((err) => { if (connection) { - this.pool.release(connection); + this.release(connection); } throw err; }); @@ -301,7 +301,7 @@ export class ExasolDriver implements IExasolDriver { }) .then((data) => { if (connection) { - this.pool.release(connection); + this.release(connection); } return data; }) @@ -322,7 +322,7 @@ export class ExasolDriver implements IExasolDriver { }) .catch((err) => { if (connection) { - this.pool.release(connection); + this.release(connection); } throw err; }); @@ -345,13 +345,13 @@ export class ExasolDriver implements IExasolDriver { }) .then((data) => { if (connection) { - this.pool.release(connection); + this.release(connection); } return data; }) .catch((err) => { if (connection) { - this.pool.release(connection); + this.release(connection); } throw err; }); @@ -385,35 +385,53 @@ export class ExasolDriver implements IExasolDriver { .sendCommand(cmd, getCancel) .then((data) => { if (connection) { - this.pool.release(connection); + this.release(connection); } return data; }) .catch((err) => { if (connection) { - this.pool.release(connection); + this.release(connection); } throw err; }); } + // Attempts to acquire a connection from the pool. + // If there is one available, acquire the connection. + // If there isn't one: + // If the pool is at max size: wait until a connection gets released and then acquire it. + // If the pool is not at max size: Create a new connection using the connect() method and acquire the new connection from the pool. private async acquire() { if (this.closed) { return Promise.reject(ErrClosed); } - + //acquire a connection. let connection = this.pool.acquire(); + //if acquiring a connection failed: if (!connection) { - this.logger.debug("[SQLClient] Found no free connection and pool did not reach it's limit, will create new connection"); - await this.connect(); - connection = this.pool.acquire(); - } - if (!connection) { - return Promise.reject(ErrInvalidConn); + //if the pool is at max size, wait until a connection opens up/gets released and acquire it. + if (this.pool.atMaxSize()) { + while (!connection) { + connection = this.pool.acquire(); + } + } else { + //create a new connection if the pool is not at max size, add it to the pool and then acquire it. + this.logger.debug('[SQLClient] Found no free connection and pool did not reach its limit, will create new connection'); + await this.connect(); + connection = this.pool.acquire(); + } + if (!connection) { + return Promise.reject(ErrInvalidConn); + } } return connection; } + private async release(connection: Connection) { + this.pool.release(connection); + } + private async loginBasicAuth() { return this.sendCommand({ command: 'login', From 6b06efcc5dd4f317356565fd87b12997dac7e935 Mon Sep 17 00:00:00 2001 From: Pieterjan Spoelders Date: Fri, 18 Oct 2024 08:42:10 -0400 Subject: [PATCH 5/8] add atMaxSize method to ConnectionPool class --- src/lib/pool/pool.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/pool/pool.ts b/src/lib/pool/pool.ts index 09dfa06..336d390 100644 --- a/src/lib/pool/pool.ts +++ b/src/lib/pool/pool.ts @@ -67,4 +67,7 @@ export class ConnectionPool { return undefined; } + atMaxSize() { + return this.pool.size == this.max; + } } From 8a7f6c7c84f958b3ff9d693988392476282de9a9 Mon Sep 17 00:00:00 2001 From: Pieterjan Spoelders Date: Fri, 18 Oct 2024 08:58:08 -0400 Subject: [PATCH 6/8] ran pk:fix --- doc/changes/changelog.md | 1 + doc/changes/changes_0.1.5.md | 8 ++++++++ package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 doc/changes/changes_0.1.5.md diff --git a/doc/changes/changelog.md b/doc/changes/changelog.md index 79c9305..36144f6 100644 --- a/doc/changes/changelog.md +++ b/doc/changes/changelog.md @@ -1,5 +1,6 @@ # Changes +* [0.1.5](changes_0.1.5.md) * [0.1.4](changes_0.1.4.md) * [0.1.3](changes_0.1.3.md) * [0.1.2](changes_0.1.2.md) diff --git a/doc/changes/changes_0.1.5.md b/doc/changes/changes_0.1.5.md new file mode 100644 index 0000000..94b8444 --- /dev/null +++ b/doc/changes/changes_0.1.5.md @@ -0,0 +1,8 @@ +# Exasol Driver ts 0.1.5, released 2024-??-?? + +Code name: WIP + +## Summary + +This release .... + diff --git a/package-lock.json b/package-lock.json index 1f7d049..14429b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@exasol/exasol-driver-ts", - "version": "0.1.4", + "version": "0.1.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@exasol/exasol-driver-ts", - "version": "0.1.4", + "version": "0.1.5", "license": "ISC", "dependencies": { "node-forge": "^1.3.1" diff --git a/package.json b/package.json index 572289d..a4b5d02 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@exasol/exasol-driver-ts", - "version": "0.1.4", + "version": "0.1.5", "description": "Exasol SQL Driver", "scripts": { "build": "rollup -c", From 56d71143505f1f490a420f7a0b03dafdc82738ff Mon Sep 17 00:00:00 2001 From: Pieterjan Spoelders Date: Fri, 18 Oct 2024 09:38:02 -0400 Subject: [PATCH 7/8] added extra comments and todo note --- src/lib/sql-client.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib/sql-client.ts b/src/lib/sql-client.ts index b5ad4af..440eb80 100644 --- a/src/lib/sql-client.ts +++ b/src/lib/sql-client.ts @@ -87,6 +87,10 @@ export class ExasolDriver implements IExasolDriver { /** * @inheritDoc */ + //TODO: add a check against 'overloading' the pool? + //problem is this function is both used publically (see examples) and internally in acquire() + //solution would be to hollow out this function, maybe have it call acquire() instead and have acquire call a new function containing this logic + //a la "create connection and add to pool public async connect(): Promise { let hasCredentials = false; let isBasicAuth = false; @@ -132,6 +136,7 @@ export class ExasolDriver implements IExasolDriver { }; webSocket.onopen = () => { this.logger.debug('[SQLClient] Login'); + //add connection to pool this.pool .add(connection) .then(() => { @@ -418,7 +423,7 @@ export class ExasolDriver implements IExasolDriver { } else { //create a new connection if the pool is not at max size, add it to the pool and then acquire it. this.logger.debug('[SQLClient] Found no free connection and pool did not reach its limit, will create new connection'); - await this.connect(); + await this.connect(); // connect will create a connection and add it to the pool connection = this.pool.acquire(); } if (!connection) { From bc9eefca6483bce5812ed50d019840b0afeff73f Mon Sep 17 00:00:00 2001 From: Pieterjan Spoelders Date: Wed, 23 Oct 2024 17:48:46 +0200 Subject: [PATCH 8/8] fixed connect logic for current version by calling acquire --- src/lib/sql-client.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib/sql-client.ts b/src/lib/sql-client.ts index b5ad4af..10419bb 100644 --- a/src/lib/sql-client.ts +++ b/src/lib/sql-client.ts @@ -88,6 +88,9 @@ export class ExasolDriver implements IExasolDriver { * @inheritDoc */ public async connect(): Promise { + await this.acquire(); + } + private async createConnectionAndAddToPool(): Promise { let hasCredentials = false; let isBasicAuth = false; if (this.config.user && this.config.password) { @@ -418,7 +421,7 @@ export class ExasolDriver implements IExasolDriver { } else { //create a new connection if the pool is not at max size, add it to the pool and then acquire it. this.logger.debug('[SQLClient] Found no free connection and pool did not reach its limit, will create new connection'); - await this.connect(); + await this.createConnectionAndAddToPool(); connection = this.pool.acquire(); } if (!connection) {