Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Postgress pooling #2

Merged
merged 8 commits into from
Oct 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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/[email protected]/mod.ts";
export type { LoggerConfig } from "https://deno.land/x/[email protected]/mod.ts";

export { Client as PostgresClient } from "https://deno.land/x/[email protected]/mod.ts";
export { Client as PostgresClient,Pool as PostgresPool } from "https://deno.land/x/[email protected]/mod.ts";
export type { ClientOptions as PostgresClientOptions, ConnectionString as PostgresConnectionString } from "https://deno.land/x/[email protected]/mod.ts";

export { DB as SQLiteClient } from "https://deno.land/x/[email protected]/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/[email protected]/node/timers.ts",
"https://dev.jspm.io/npm:@jspm/core@1/nodelibs/url.js": "https://deno.land/[email protected]/node/url.ts",
"https://dev.jspm.io/npm:@jspm/core@1/nodelibs/events.js": "https://deno.land/[email protected]/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);
});