Skip to content

Commit

Permalink
Postgress pooling (#2)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
3 people authored Oct 23, 2022
1 parent 324c26d commit 62ae2f7
Show file tree
Hide file tree
Showing 9 changed files with 175 additions and 44 deletions.
9 changes: 9 additions & 0 deletions .github/workflows/test-mysql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ jobs:
ports:
- 3306
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=5s --health-retries=3
postgres:
image: postgres:11
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test
ports:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
steps:
- uses: actions/checkout@v2

Expand Down
3 changes: 3 additions & 0 deletions deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"importMap": "./import_map.json"
}
3 changes: 2 additions & 1 deletion deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ export {
} from "https://deno.land/x/mysql@v2.10.2/mod.ts";
export type { LoggerConfig } from "https://deno.land/x/mysql@v2.10.2/mod.ts";

export { Client as PostgresClient } from "https://deno.land/x/postgres@v0.16.1/mod.ts";
export { Client as PostgresClient,Pool as PostgresPool } from "https://deno.land/x/postgres@v0.16.1/mod.ts";
export type { ClientOptions as PostgresClientOptions, ConnectionString as PostgresConnectionString } from "https://deno.land/x/postgres@v0.16.1/mod.ts";

export { DB as SQLiteClient } from "https://deno.land/x/sqlite@v3.4.0/mod.ts";

Expand Down
7 changes: 7 additions & 0 deletions import_map.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"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"
}
}
32 changes: 25 additions & 7 deletions lib/connectors/connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@ import type { QueryDescription } from "../query-builder.ts";
import { Translator } from "../translators/translator.ts";

/** Default connector options. */
export interface ConnectorOptions {}
export interface ConnectorOptions { }

/** Default pool options. */
export interface ConnectorPoolOptions { }

/** Default connector client. */
export interface ConnectorClient {}
export interface ConnectorClient { }

/** Default connector pool. */
export interface ConnectorPool { }

/** Connector interface for a database provider connection. */
export interface Connector {
Expand All @@ -16,19 +22,31 @@ export interface Connector {
_translator: Translator;

/** Client that maintains an external database connection. */
_client: ConnectorClient;
_client?: ConnectorClient;

/** Options to connect to an external instance. */
_options: ConnectorOptions;

/** Is the client connected to an external instance. */
_connected: boolean;
_connected?: boolean

/** Test connection. */
ping(): Promise<boolean>;
/** Is the optional pool for making connections to an external instance. */
_pool?: ConnectorPool

/** Gets the client or the pool connected to the database*/
_getClientOrPool?(): ConnectorPool | ConnectorClient

/** Connect to an external database instance. */
_makeConnection(): void;
_makeConnection(): void | Promise<any>

/** Gets the client connected to the database */
getClient?(): any

/** Gets the pool connected to the database */
getPool?(): any

/** Test connection. */
ping(): Promise<boolean>;

/** Execute a query on the external database instance. */
query(queryDescription: QueryDescription): Promise<any | any[]>;
Expand Down
106 changes: 71 additions & 35 deletions lib/connectors/postgres-connector.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { PostgresClient } from "../../deps.ts";
import type { Connector, ConnectorOptions } from "./connector.ts";
import { PostgresClient, PostgresPool } from "../../deps.ts";
import type { Connector, ConnectorOptions, ConnectorPoolOptions } from "./connector.ts";
import { SQLTranslator } from "../translators/sql-translator.ts";
import type { SupportedSQLDatabaseDialect } from "../translators/sql-translator.ts";
import type { QueryDescription } from "../query-builder.ts";
Expand All @@ -17,51 +17,86 @@ interface PostgresOptionsWithURI extends ConnectorOptions {
uri: string;
}

export type PostgresOptions =
| PostgresOptionsWithConfig
| PostgresOptionsWithURI;
interface PostgresPoolOptions extends ConnectorPoolOptions, PostgresOptionsWithConfig, PostgresOptionsWithURI {
size: number;
lazy: boolean;
}

export type PostgresOptions = PostgresPoolOptions;

export class PostgresConnector implements Connector {
_dialect: SupportedSQLDatabaseDialect = "postgres";

_client: PostgresClient;
_pool?: PostgresPool;
_client?: PostgresClient;
_options: PostgresOptions;
_translator: SQLTranslator;
_connected = false;

/** Create a PostgreSQL connection. */
constructor(options: PostgresOptions) {
this._options = options;
if ("uri" in options) {
this._client = new PostgresClient(options.uri);
} else {
this._client = new PostgresClient({
if (this._isPoolConnector()) {
this._pool = new PostgresPool("uri" in options ? options.uri : {
hostname: options.host,
user: options.username,
password: options.password,
database: options.database,
port: options.port ?? 5432,
});
}
...options,
},
options.size,
options.lazy
);
} else
if ("uri" in options) {
this._client = new PostgresClient(options.uri);
} else {
this._client = new PostgresClient({
hostname: options.host,
user: options.username,
password: options.password,
database: options.database,
port: options.port ?? 5432,
});
}
this._translator = new SQLTranslator(this._dialect);
}

_isPoolConnector() {
return "size" in this._options;
}

_getClientOrPool() {
return this._isPoolConnector() ? this.getPool()! : this.getClient()!;
}

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")
}
}

await this._client.connect();
this._connected = true;
getClient() {
return this._client;
}

async ping() {
await this._makeConnection();
getPool() {
return this._pool;
}

async ping() {
try {
const [result] = (
await this._client.queryObject("SELECT 1 + 1 as result")
).rows;
const connection = await this._makeConnection();
console.log(connection)
const [result] =
(await connection!.queryArray("SELECT 1 + 1 as result")
).rows[0];

console.log(result)
return result === 2;
} catch {
return false;
Expand All @@ -70,32 +105,33 @@ export class PostgresConnector implements Connector {

// deno-lint-ignore no-explicit-any
async query(queryDescription: QueryDescription): Promise<any | any[]> {
await this._makeConnection();

const client = await this._makeConnection()
const query = this._translator.translateToQuery(queryDescription);
const response = await this._client.queryObject(query);
const response = await client!.queryObject(query);
const results = response.rows as Values[];

if (queryDescription.type === "insert") {
return results.length === 1 ? results[0] : results;
}

return results;
}

async transaction(queries: () => Promise<void>) {
const transaction = this._client.createTransaction("transaction");
const client = await this._makeConnection()
const transaction = client!.createTransaction("transaction");
await transaction.begin();
await queries();
return transaction.commit();
}

async close() {
if (!this._connected) {
return;
if (this._client) {
if (!this._connected) {
return;
}
await this._getClientOrPool().end();
}

await this._client.end();
this._connected = false;
}

}
5 changes: 5 additions & 0 deletions lib/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,11 @@ export class Database {
return this.getConnector()._client;
}

/* Get the database pool if existent. */
getPool?() {
return this.getConnector()._pool;
}

/** Create the given models in the current database.
*
* await db.sync({ drop: true });
Expand Down
36 changes: 35 additions & 1 deletion tests/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ const defaultSQLiteOptions = {
filepath: "test.sqlite",
};

const defaultPostgreSQLPoolOptions = {
uri: "postgres://postgres:postgres@localhost:5432/test",
size: 2,
lazy: true,
};

const defaultPostgreSQLOptions = {
uri: "postgres://postgres:postgres@localhost:5432/test",
};

const getMySQLConnection = (options = {}, debug = true): Database => {
const connection: Database = new Database(
{ dialect: "mysql", debug },
Expand All @@ -35,4 +45,28 @@ const getSQLiteConnection = (options = {}, debug = true): Database => {
return database;
};

export { getMySQLConnection, getSQLiteConnection };
const getPostgreSQLPoolConnection = (options = {}, debug = true): Database => {
const connection: Database = new Database(
{ dialect: "postgres", debug },
{
...defaultPostgreSQLPoolOptions,
...options,
},
);

return connection;
};

const getPostgreSQLConnection = (options = {}, debug = true): Database => {
const connection: Database = new Database(
{ dialect: "postgres", debug },
{
...defaultPostgreSQLOptions,
...options,
},
);

return connection;
};

export { getMySQLConnection, getSQLiteConnection, getPostgreSQLPoolConnection, getPostgreSQLConnection };
18 changes: 18 additions & 0 deletions tests/units/connectors/postgres/connection.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { getPostgreSQLPoolConnection, getPostgreSQLConnection } from "../../../connection.ts";
import { assertEquals } from "../../../deps.ts";

Deno.test({ name: "PostgreSQL: Connection", sanitizeResources: false}, async function () {
const connection = getPostgreSQLConnection();
const ping = await connection.ping();
await connection.close();

assertEquals(ping, true);
});

Deno.test({ name: "PostgreSQL: Pool Connection", sanitizeResources: false}, async function () {
const connection = getPostgreSQLPoolConnection();
const ping = await connection.ping();
await connection.close();

assertEquals(ping, true);
});

0 comments on commit 62ae2f7

Please sign in to comment.