Skip to content
1 change: 1 addition & 0 deletions client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export class Client {
_aexit = this.end;
}

// TODO(bartlomieju) to be refactored
export class PooledClient extends Client {
constructor(connection: Connection, release: () => void) {
super();
Expand Down
98 changes: 73 additions & 25 deletions connection_params.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,40 @@
import { parseDsn } from "./utils.ts";

function getPgEnv(): IConnectionParams {
// this is dummy env object, if program
// was run with --allow-env permission then
// it's filled with actual values
let pgEnv: IConnectionParams = {};

if (Deno.permissions().env) {
const env = Deno.env();

pgEnv = {
database: env.PGDATABASE,
host: env.PGHOST,
port: env.PGPORT,
user: env.PGUSER,
password: env.PGPASSWORD,
application_name: env.PGAPPNAME
};
}

return pgEnv;
}

function selectFrom(sources: Object[], key: string): string | undefined {
for (const source of sources) {
if (source[key]) {
return source[key];
}
}

return undefined;
}

const DEFAULT_CONNECTION_PARAMS = {
host: "127.0.0.1",
port: "5432",
user: "postgres",
database: "postgres",
password: "",
application_name: "deno_postgres"
};

Expand All @@ -18,43 +47,62 @@ export interface IConnectionParams {
application_name?: string;
}

class ConnectionParamsError extends Error {
constructor(message: string) {
super(message);
this.name = "ConnectionParamsError";
}
}

export class ConnectionParams {
database?: string;
host?: string;
port?: string;
user?: string;
database: string;
host: string;
port: string;
user: string;
password?: string;
application_name?: string;
application_name: string;
// TODO: support other params

constructor(config?: string | IConnectionParams) {
// TODO: I don't really like that we require access to environment
// by default, maybe it should be flag-controlled?
const envVars = Deno.env();

if (!config) {
config = {};
}

const pgEnv = getPgEnv();

if (typeof config === "string") {
const dsn = parseDsn(config);
if (dsn.driver !== "postgres") {
throw new Error(`Supplied DSN has invalid driver: ${dsn.driver}.`);
}
config = dsn;
}

this.database = selectFrom([config, pgEnv], "database");
this.host = selectFrom([config, pgEnv, DEFAULT_CONNECTION_PARAMS], "host");
this.port = selectFrom([config, pgEnv, DEFAULT_CONNECTION_PARAMS], "port");
this.user = selectFrom([config, pgEnv], "user");
this.password = selectFrom([config, pgEnv], "password");
this.application_name = selectFrom(
[config, pgEnv, DEFAULT_CONNECTION_PARAMS],
"application_name"
);

const missingParams: string[] = [];

["database", "user"].forEach(param => {
if (!this[param]) {
missingParams.push(param);
}
});

this.database = dsn.database || envVars.PGDATABASE;
this.host = dsn.host || envVars.PGHOST;
this.port = dsn.port || envVars.PGPORT;
this.user = dsn.user || envVars.PGUSER;
this.password = dsn.password || envVars.PGPASSWORD;
this.application_name = dsn.params.application_name || envVars.PGAPPNAME;
} else {
this.database = config.database || envVars.PGDATABASE;
this.host = config.host || envVars.PGHOST;
this.port = config.port || envVars.PGPORT;
this.user = config.user || envVars.PGUSER;
this.password = config.password || envVars.PGPASSWORD;
this.application_name = config.application_name || envVars.PGAPPNAME;
if (missingParams.length) {
throw new ConnectionParamsError(
`Missing connection parameters: ${missingParams.join(
", "
)}. Connection parameters can be read
from environment only if Deno is run with env permission (deno run --allow-env)`
);
}
}
}
49 changes: 36 additions & 13 deletions deferred.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
// taken from: https://deno.land/x/std@v0.2.11/util/deferred.ts
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.

export type Deferred<T = any, R = Error> = {
promise: Promise<T>;
resolve: (t?: T) => void;
Expand All @@ -10,9 +7,10 @@ export type Deferred<T = any, R = Error> = {

/** Create deferred promise that can be resolved and rejected by outside */
export function defer<T>(): Deferred<T> {
let handled = false;
let resolve;
let reject;
let handled = false,
resolve,
reject;

const promise = new Promise<T>((res, rej) => {
resolve = r => {
handled = true;
Expand All @@ -23,21 +21,46 @@ export function defer<T>(): Deferred<T> {
rej(r);
};
});

return {
promise,
resolve,
reject,

get handled() {
return handled;
}
};
}

export function isDeferred(x): x is Deferred {
return (
typeof x === "object" &&
x.promise instanceof Promise &&
typeof x["resolve"] === "function" &&
typeof x["reject"] === "function"
);
export class DeferredStack<T> {
private _array: Array<T>;
private _queue: Array<Deferred>;

constructor(ls?: Iterable<T>) {
this._array = ls ? [...ls] : [];
this._queue = [];
}

async pop(): Promise<T> {
if (this._array.length > 0) {
return this._array.pop();
}
const d = defer();
this._queue.push(d);
await d.promise;
return this._array.pop();
}

push(value: T): void {
this._array.push(value);
if (this._queue.length > 0) {
const d = this._queue.shift();
d.resolve();
}
}

get size(): number {
return this._array.length;
}
}
5 changes: 4 additions & 1 deletion deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@ export {
TestFunction
} from "https://deno.land/x/std@v0.4.0/testing/mod.ts";

export { assertEquals } from "https://deno.land/std@v0.4.0/testing/asserts.ts";
export {
assertEquals,
assertStrContains
} from "https://deno.land/std@v0.4.0/testing/asserts.ts";
35 changes: 3 additions & 32 deletions pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ import { Client, PooledClient } from "./client.ts";
import { Connection } from "./connection.ts";
import { ConnectionParams, IConnectionParams } from "./connection_params.ts";
import { Query, QueryConfig, QueryResult } from "./query.ts";
import { defer, Deferred } from "./deferred.ts";
import { DeferredStack } from "./deferred.ts";

export class Pool {
private _connectionParams: IConnectionParams;
private _connectionParams: ConnectionParams;
private _connections: Array<Connection>;
private _availableConnections: DeferredStack<Connection>;
private _size: number;
private _ready: Promise<void>;

constructor(connectionParams: IConnectionParams, size: number) {
this._connectionParams = connectionParams;
this._connectionParams = new ConnectionParams(connectionParams);
this._size = size;
this._ready = this._startup();
}
Expand Down Expand Up @@ -74,32 +74,3 @@ export class Pool {
_aenter = () => {};
_aexit = this.end;
}

// perhaps this should be exported somewhere?
class DeferredStack<T> {
private _array: Array<T>;
private _queue: Array<Deferred>;
constructor(ls?: Iterable<T>) {
this._array = ls ? [...ls] : [];
this._queue = [];
}
async pop(): Promise<T> {
if (this._array.length > 0) {
return this._array.pop();
}
const d = defer();
this._queue.push(d);
await d.promise;
return this._array.pop();
}
push(value: T): void {
this._array.push(value);
if (this._queue.length > 0) {
const d = this._queue.shift();
d.resolve();
}
}
get size(): number {
return this._array.length;
}
}
1 change: 1 addition & 0 deletions test.ts
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#! /usr/bin/env deno run --allow-net --allow-env test.ts
import { runTests } from "./deps.ts";

import "./tests/queries.ts";
Expand Down
43 changes: 39 additions & 4 deletions tests/connection_params.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { test, assertEquals } from "../deps.ts";
import { test, assertEquals, assertStrContains } from "../deps.ts";
import { ConnectionParams } from "../connection_params.ts";

test(async function testDsnStyleParameters() {
test(async function dsnStyleParameters() {
const p = new ConnectionParams(
"postgres://some_user@some_host:10101/deno_postgres"
);
Expand All @@ -12,7 +12,7 @@ test(async function testDsnStyleParameters() {
assertEquals(p.port, "10101");
});

test(async function testObjectStyleParameters() {
test(async function objectStyleParameters() {
const p = new ConnectionParams({
user: "some_user",
host: "some_host",
Expand All @@ -26,7 +26,8 @@ test(async function testObjectStyleParameters() {
assertEquals(p.port, "10101");
});

test(async function testEnvParameters() {
// TODO: add test when env is not allowed
test(async function envParameters() {
const currentEnv = Deno.env();

currentEnv.PGUSER = "some_user";
Expand All @@ -39,4 +40,38 @@ test(async function testEnvParameters() {
assertEquals(p.user, "some_user");
assertEquals(p.host, "some_host");
assertEquals(p.port, "10101");

// clear out env
currentEnv.PGUSER = "";
currentEnv.PGHOST = "";
currentEnv.PGPORT = "";
currentEnv.PGDATABASE = "";
});

test(async function defaultParameters() {
const p = new ConnectionParams({
database: "deno_postgres",
user: "deno_postgres"
});
assertEquals(p.database, "deno_postgres");
assertEquals(p.user, "deno_postgres");
assertEquals(p.host, "127.0.0.1");
assertEquals(p.port, "5432");
assertEquals(p.password, undefined);
});

test(async function requiredParameters() {
let thrown = false;

try {
new ConnectionParams();
} catch (e) {
thrown = true;
assertEquals(e.name, "ConnectionParamsError");
assertStrContains(
e.message,
"Missing connection parameters: database, user"
);
}
assertEquals(thrown, true);
});
4 changes: 3 additions & 1 deletion tests/pool.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { test, assertEquals, TestFunction } from "../deps.ts";
import { Client } from "../mod.ts";
import { Pool } from "../pool.ts";
import { delay } from "../utils.ts";
import { DEFAULT_PARAMS, DEFAULT_SETUP } from "./queries.ts";

let POOL: Pool;

async function testPool(t: TestFunction, setupQueries?: Array<string>) {
// TODO(bartlomieju) reenable these tests
return;

// constructing Pool instantiates the connections,
// so this has to be constructed for each test.
const fn = async () => {
Expand Down