Support for Postgres Pooling - #363
Conversation
eveningkid
left a comment
There was a problem hiding this comment.
Good evening, Matteo!
First off, thank you so much for contributing to this project–and for emailing me :)
I am usually swamped with work from all sides, it's hard for me to take time on open-source
Regarding this PR, looks solid. My comments are all to keep harmony in the codebase and should be easy to address
If not, feel free to raise any question/seek help in the comments and I'll be happy to assist :)
Have a great rest of your day!
| { | ||
| "imports": { | ||
| "https://dev.jspm.io/npm:@jspm/core@1/nodelibs/timers.js": "https://deno.land/std@0.159.0/node/timers.ts", | ||
| "https://dev.jspm.io/npm:@jspm/core@1/nodelibs/url.js": "https://deno.land/std@0.159.0/node/url.ts", | ||
| "https://dev.jspm.io/npm:@jspm/core@1/nodelibs/events.js": "https://deno.land/std@0.159.0/node/events.ts" | ||
| } | ||
| } |
There was a problem hiding this comment.
I am sure this is a larger problem, but do you mind sharing what was causing this issue?
What parent lib?
There was a problem hiding this comment.
Yes, i saw that some of these libraries relies on "dev.jspm.io" and are no longer available. But anyway, the parent lib is the main postgres lib. The error that gives is the following:
Module not found :
"https://dev.jspm.io/npm:@jspm/core@1/nodelibs/url.js" at https://dev.jspm.io/pg-connection-string@2.5.0:3:8
There was a problem hiding this comment.
one of the borken import for me is
Line 5 in 09eb699
| _connected?: boolean | undefined; | ||
|
|
||
| /** Is the optional pool for making connections to an external instance. */ | ||
| _pool?: ConnectionPool | undefined; |
There was a problem hiding this comment.
Same thing here, | undefined should be omitted :)
|
|
||
| /** Connect to an external database instance. */ | ||
| _makeConnection(): void; | ||
| _makeConnection(): void | ConnectorClient | Promise<void> | Promise<ConnectorClient>; |
There was a problem hiding this comment.
We should keep this a void method
It makes a connection and shouldn't return anything
There was a problem hiding this comment.
For the pool, we need it because of the underlying implementation. Should we keep it so?
| if (this._client) { | ||
| if (this._connected) { | ||
| return; | ||
| } | ||
|
|
||
| await this._client.connect(); | ||
| this._connected = true; | ||
| return this._client; | ||
| } else { | ||
| return await this._pool!.connect(); |
There was a problem hiding this comment.
Let's create two new methods:
_isPoolConnector() {
return "size" in this._options;
}
_getClientOrPool() {
return this._isPoolConnector() ? this.getPool() : this.getClient();
}
_makeConnection() {
if (this._connected) {
return;
}
await this._getClientOrPool().connect();
this._connected = true;
}| async ping() { | ||
| await this._makeConnection(); | ||
| return await this.tryConnection(await this._makeConnection()); | ||
| } | ||
|
|
||
| async tryConnection(client?: PostgresClient) { | ||
| try { | ||
| const [result] = ( | ||
| await this._client.queryObject("SELECT 1 + 1 as result") | ||
| ).rows; | ||
| await client!.queryArray("SELECT 1 + 1 as result") | ||
| ).rows[0]; | ||
| return result === 2; | ||
| } catch { | ||
| return false; |
There was a problem hiding this comment.
Could we keep all the code inside ping()?
Now we'll need to call this._getClientOrPool() every time we need this._client or ._pool
There was a problem hiding this comment.
Yes, as we stick to the this._getClientOrPool() we can keep everything inside ping()
| if (this._client) { | ||
| if (!this._connected) { | ||
| return; | ||
| } | ||
| await this._client.end(); | ||
| } else { | ||
| await this._pool?.end()! | ||
| } |
There was a problem hiding this comment.
Same thing here, using this._getClientOrPool().end() would be great :)
| } | ||
|
|
||
| /* Get the database pool if existent. */ | ||
| getPool?() { |
There was a problem hiding this comment.
Can we drop the ? here?
| getPool?() { | |
| getPool() { |
There was a problem hiding this comment.
For this, actually is up to the implementation to give a "pooling" experience on the connector. Afaik, not every connectors have a pool implementation out of the box, but prove me wrong
| * @example | ||
| * //Direct connection usage: | ||
| * const connection = new PostgresConnector({ | ||
| * host: '...', | ||
| * username: 'user', | ||
| * password: 'password', | ||
| * database: 'airlines', | ||
| * }); | ||
| * //Pool connection usage: | ||
| * const connection = new PostgresConnector({ | ||
| * connection_params: { | ||
| * host: '...', | ||
| * username: 'user', | ||
| * password: 'password', | ||
| * database: 'airlines', | ||
| * }, | ||
| * size: 5, | ||
| * lazy: false | ||
| * }); |
There was a problem hiding this comment.
I know this comes from a good intention but I believe types are good hints to use the connector
Types are always be up-to-date, we don't need to manually edit them each time
So let's keep your life easy here haha :D
removed PostgresPool class as Connector is a base class Co-authored-by: Arnaud <eveningkid@users.noreply.github.com>
Co-authored-by: Arnaud <eveningkid@users.noreply.github.com>
|
new commit: 3aeaec8 I fixed some stuff that I made and added yours. I did not test the other components involved with the If something seems strange, I forgot something or everything else, let's chat about it :) |
* Added pool feature for the postgres connector * added stable support for postgres pool, added tests * Update lib/connectors/connector.ts * fixed stuff as previously mentioned in eveningkid#363 * making getClient & _getClientOrPool optional * update test Co-authored-by: Matteo Stellato <matteo.stellato.external@atos.net> Co-authored-by: Arnaud <eveningkid@users.noreply.github.com>
eveningkid
left a comment
There was a problem hiding this comment.
Another series of comments, thanks a lot for keeping up with them!
Also, please always run deno fmt at the root of your project before committing your files
It will keep all the syntax bits coherent with the rest of the codebase :)
| async _makeConnection() { | ||
| if (this._connected) { | ||
| return; | ||
| if (!this._isPoolConnector()) { | ||
| if (this._connected) { | ||
| return this._client!; | ||
| } | ||
| await this._client!.connect(); | ||
| return this._client!; | ||
| } else if (this._pool?.available || !this._pool?.available) { | ||
| return await this.getPool()?.connect() | ||
| } else { | ||
| throw new Error("no connections available") | ||
| } | ||
| } |
There was a problem hiding this comment.
Could makeConnection not return anything?
It was created to work as:
querySomething() {
await makeConnection() // << make sure that this.client next line won't be undefined
this.client.query(something)
}Only connect, don't return a client :)
| } | ||
| await this._client!.connect(); | ||
| return this._client!; | ||
| } else if (this._pool?.available || !this._pool?.available) { |
There was a problem hiding this comment.
Not sure if you intended to write this?
if (a || !a)I think this whole method could be rewritten as:
async _makeConnection() {
if (this._connected) {
return;
}
if (this._isPoolConnector()) {
await this._getPool().connect();
} else {
await this._getClient().connect();
}
}There was a problem hiding this comment.
The problem here, like the previous comment is that if you do this:
await this._getClient().connect() // -> connects the clientthis connects the actual client already instanciated before.
While doing this:
await this._getPool().connect() //-> gives a client back on which connection should be establishedSo that's why I make it return a client.
If we write the code like so:
async _makeConnection() {
if (this._connected) {
return;
}
if (this._isPoolConnector()) {
await this._getPool().connect();
} else {
await this._getClient().connect();
}
}we're going to get nothing.
We need something that makes us doing async client operations, and maybe could be like having an array of clients:
_clients?: Array<PostgresClient>;where the first element is always instanciated if the connector is in "client" mode, while if in "pool" mode, the clients field grows until the max pool size choosen in the options.
Of course this has a bit of impact on the general design of the connector itself.
Another way could be to build a completely custom Pool class that every connector implement on their own. So the idea already shown with the clients could be possible without completely twsting the main design.
In a last option we can stick with _makeConnection() returning the client.
I do not see any better solutions at the moment, but something possibly will get in my mind soon.
@eveningkid what do you think?
| const [result] = ( | ||
| await this._client.queryObject("SELECT 1 + 1 as result") | ||
| ).rows; | ||
| const connection = await this._makeConnection(); |
There was a problem hiding this comment.
So now we can keep this outside of the try, as it was before:
async ping() {
await this._makeConnection()
try {
const [result] = await this._getClientOrPool().queryArray(...)And also, keep no console.log in your changes, thanks! :)
There was a problem hiding this comment.
Forgot those while testing out😄
| const client = await this._makeConnection() | ||
| const query = this._translator.translateToQuery(queryDescription); | ||
| const response = await this._client.queryObject(query); | ||
| const response = await client!.queryObject(query); |
There was a problem hiding this comment.
I assume that every call to client should now call this._getClientOrPool()
| if (this._client) { | ||
| if (!this._connected) { | ||
| return; | ||
| } | ||
| await this._getClientOrPool().end(); | ||
| } |
There was a problem hiding this comment.
Let's keep things simple here:
async close() {
if (!this.connected) {
return;
}
await this._getClientOrPool().end();
this._connected = false;
}| } | ||
|
|
||
| /* Get the database pool if existent. */ | ||
| getPool?() { |
| /** Gets the client connected to the database */ | ||
| getClient(): any | ||
|
|
||
| /** Gets the pool connected to the database */ | ||
| getPool?(): any |
There was a problem hiding this comment.
| /** Gets the client connected to the database */ | |
| getClient(): any | |
| /** Gets the pool connected to the database */ | |
| getPool?(): any | |
| /** Gets the client connected to the database */ | |
| getClient?(): ConnectorClient; | |
| /** Gets the pool connected to the database */ | |
| getPool?(): ConnectorPool; |
|
|
||
| /** Is the client connected to an external instance. */ | ||
| _connected: boolean; | ||
| _connected?: boolean |
There was a problem hiding this comment.
This is always defined:
| _connected?: boolean | |
| _connected: boolean |
|
|
||
| /** Client that maintains an external database connection. */ | ||
| _client: ConnectorClient; | ||
| _client?: ConnectorClient; |
There was a problem hiding this comment.
Won't this cause type errors in other connectors?
Since it wasn't optional before, I assume that any code like:
this.client.doSomething()Would not work anymore, complaining that this.client could be not defined
We could keep _client as always defined in this file and override this interface just for Postgres
There was a problem hiding this comment.
Yes, this will have an impact in other files, so we can override it only in Postgres.
Co-authored-by: Arnaud <eveningkid@users.noreply.github.com>
Added stable support for Pooling in Postgres.
A few changes have been made in order to achieve a consistent code.
deno.jsonandimport_map.jsonhave been added for support to some node libraries;in the
Connectorinterface, for code design issues I had to put_clientand_connectedas optional due to inchoerence with the pool existence;PostgresPoolOptionshas been added and relies on the original options of PostgresPoolclass;PostgresConnectorhas been modified with substantial edits in the various exposed methods;Databasehas been modified with the support of the underlying connector implementation of_poolwith the methodgetPool?