diff --git a/.github/workflows/test-mysql.yml b/.github/workflows/test-mysql.yml index 25abac4..c70eb85 100644 --- a/.github/workflows/test-mysql.yml +++ b/.github/workflows/test-mysql.yml @@ -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 diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..67ba286 --- /dev/null +++ b/deno.json @@ -0,0 +1,3 @@ +{ + "importMap": "./import_map.json" +} \ No newline at end of file diff --git a/deps.ts b/deps.ts index 5ee2278..5a5cbba 100644 --- a/deps.ts +++ b/deps.ts @@ -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"; diff --git a/import_map.json b/import_map.json new file mode 100644 index 0000000..c119769 --- /dev/null +++ b/import_map.json @@ -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" + } +} diff --git a/lib/connectors/connector.ts b/lib/connectors/connector.ts index 59cb9e2..289a391 100644 --- a/lib/connectors/connector.ts +++ b/lib/connectors/connector.ts @@ -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 { @@ -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; + /** 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 + + /** Gets the client connected to the database */ + getClient?(): any + + /** Gets the pool connected to the database */ + getPool?(): any + + /** Test connection. */ + ping(): Promise; /** Execute a query on the external database instance. */ query(queryDescription: QueryDescription): Promise; diff --git a/lib/connectors/postgres-connector.ts b/lib/connectors/postgres-connector.ts index c1c838f..0709310 100644 --- a/lib/connectors/postgres-connector.ts +++ b/lib/connectors/postgres-connector.ts @@ -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"; @@ -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; @@ -70,32 +105,33 @@ export class PostgresConnector implements Connector { // deno-lint-ignore no-explicit-any async query(queryDescription: QueryDescription): Promise { - 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) { - 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; } + } diff --git a/lib/database.ts b/lib/database.ts index 48e884e..cf2f772 100644 --- a/lib/database.ts +++ b/lib/database.ts @@ -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 }); diff --git a/tests/connection.ts b/tests/connection.ts index b649d10..2f45e27 100644 --- a/tests/connection.ts +++ b/tests/connection.ts @@ -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 }, @@ -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 }; diff --git a/tests/units/connectors/postgres/connection.test.ts b/tests/units/connectors/postgres/connection.test.ts new file mode 100644 index 0000000..bc21c7a --- /dev/null +++ b/tests/units/connectors/postgres/connection.test.ts @@ -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); +});