Skip to content

Commit

Permalink
feat(drivers): add support for PostgreSQL
Browse files Browse the repository at this point in the history
  • Loading branch information
B4nan committed Mar 15, 2019
1 parent 583edef commit 4e6486e
Show file tree
Hide file tree
Showing 13 changed files with 1,095 additions and 3 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ node_js:
services:
- mongodb
- mysql
- postgresql

cache:
directories:
Expand All @@ -20,6 +21,7 @@ script:

before_install:
- mysql -u root -e 'CREATE DATABASE mikro_orm_test;'
- psql -c 'create database mikro_orm_test;' -U postgres
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
- chmod +x ./cc-test-reporter
- ./cc-test-reporter before-build
Expand Down
1 change: 0 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# TODO list

- postgres driver
- schema generator for SQL drivers
- single table inheritance
- implement transactions in mongo driver
Expand Down
95 changes: 95 additions & 0 deletions lib/connections/PostgreSqlConnection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { Client, ClientConfig } from 'pg';
import { readFileSync } from 'fs';
import { URL } from 'url';
import { Connection, QueryResult } from './Connection';
import { EntityData, IEntity } from '../decorators';

export class PostgreSqlConnection extends Connection {

private connection: Client;

async connect(): Promise<void> {
this.connection = new Client(this.getConnectionOptions());
await this.connection.connect();
}

async close(force?: boolean): Promise<void> {
await this.connection.end();
}

async isConnected(): Promise<boolean> {
try {
await this.connection.query('SELECT 1');
return true;
} catch {
return false;
}
}

async beginTransaction(savepoint?: string): Promise<void> {
await this.execute(savepoint ? `SAVEPOINT ${savepoint}` : 'START TRANSACTION', [], 'run');
}

async commit(savepoint?: string): Promise<void> {
await this.execute(savepoint ? `RELEASE SAVEPOINT ${savepoint}` : 'COMMIT', [], 'run');
}

async rollback(savepoint?: string): Promise<void> {
await this.execute(savepoint ? `ROLLBACK TO SAVEPOINT ${savepoint}` : 'ROLLBACK', [], 'run');
}

getDefaultClientUrl(): string {
return 'postgre://postgres@127.0.0.1:5432';
}

async execute(query: string, params: any[] = [], method: 'all' | 'get' | 'run' = 'all'): Promise<QueryResult | any | any[]> {
try {
const now = Date.now();
const res = await this.connection.query(query, params);
this.logQuery(query + ` [took ${Date.now() - now} ms]`);

return this.transformResult(res, method);
} catch (e) {
e.message += `\n in query: ${query}`;

if (params && params.length) {
e.message += `\n with params: ${JSON.stringify(params)}`;
}

throw e;
}
}

getConnectionOptions(): ClientConfig {
const ret = {} as ClientConfig;
const url = new URL(this.config.getClientUrl());
ret.host = this.config.get('host', url.hostname);
ret.port = this.config.get('port', +url.port);
ret.user = this.config.get('user', url.username);
ret.password = this.config.get('password', url.password);
ret.database = this.config.get('dbName', url.pathname.replace(/^\//, ''));

return ret;
}

async loadFile(path: string): Promise<void> {
const file = readFileSync(path);
await this.execute(file.toString());
}

private transformResult(res: any, method: 'all' | 'get' | 'run'): QueryResult | EntityData<IEntity> | EntityData<IEntity>[] {
if (method === 'get') {
return res.rows[0];
}

if (method === 'run') {
return {
affectedRows: res.rowCount,
insertId: res.rows[0] ? res.rows[0].id : 0,
};
}

return res.rows;
}

}
1 change: 1 addition & 0 deletions lib/connections/SqliteConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export class SqliteConnection extends Connection {

return res;
}

}

export type SqliteDatabase = Database & { driver: { open: boolean } };
28 changes: 28 additions & 0 deletions lib/drivers/PostgreSqlDriver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { PostgreSqlConnection } from '../connections/PostgreSqlConnection';
import { AbstractSqlDriver } from './AbstractSqlDriver';
import { EntityData, IEntityType } from '../decorators';
import { QueryType } from '../query';
import { PostgreSqPlatform } from '../platforms/PostgreSqPlatform';

export class PostgreSqlDriver extends AbstractSqlDriver<PostgreSqlConnection> {

protected readonly connection = new PostgreSqlConnection(this.config);
protected readonly platform = new PostgreSqPlatform();

async nativeInsert<T extends IEntityType<T>>(entityName: string, data: EntityData<T>): Promise<number> {
const collections = this.extractManyToMany(entityName, data);
const qb = this.createQueryBuilder(entityName).insert(data);
const params = qb.getParams();
let sql = qb.getQuery();

if (qb.type === QueryType.INSERT && Object.keys(params).length === 0) {
sql = sql.replace('() VALUES ()', '("id") VALUES (DEFAULT)');
}

const res = await this.connection.execute(sql, params, 'run');
await this.processManyToMany(entityName, res.insertId, collections);

return res.insertId;
}

}
8 changes: 8 additions & 0 deletions lib/platforms/Platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,12 @@ export abstract class Platform {
return '"';
}

getParameterPlaceholder(index?: number): string {
return '?';
}

usesReturningStatement(): boolean {
return false;
}

}
26 changes: 26 additions & 0 deletions lib/platforms/PostgreSqPlatform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { NamingStrategy, UnderscoreNamingStrategy } from '../naming-strategy';
import { Platform } from './Platform';

export class PostgreSqPlatform extends Platform {

supportsSavePoints(): boolean {
return true;
}

getNamingStrategy(): { new(): NamingStrategy} {
return UnderscoreNamingStrategy;
}

getIdentifierQuoteCharacter(): string {
return '"';
}

getParameterPlaceholder(index?: number): string {
return '$' + index;
}

usesReturningStatement(): boolean {
return true;
}

}
2 changes: 1 addition & 1 deletion lib/query/QueryBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export class QueryBuilder {

sql += this.helper.getQueryPagination(this._limit, this._offset);

return sql;
return this.helper.finalize(this.type, sql, this.metadata[this.entityName]);
}

getParams(): any[] {
Expand Down
21 changes: 20 additions & 1 deletion lib/query/QueryBuilderHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export class QueryBuilderHelper {
}

processJoins(leftJoins: Record<string, [string, string, string, string, string]>): string {
return Object.values(leftJoins).map(([table, alias, column, joinColumn, pk]) => {
return Object.values(leftJoins).map(([table, alias, column, , pk]) => {
return ` LEFT JOIN ${this.wrap(table)} AS ${this.wrap(alias)} ON ${this.wrap(this.alias)}.${this.wrap(pk)} = ${this.wrap(alias)}.${this.wrap(column)}`;
}).join('');
}
Expand Down Expand Up @@ -206,6 +206,25 @@ export class QueryBuilderHelper {
return pagination;
}

finalize(type: QueryType, sql: string, meta?: EntityMetadata): string {
let append = '';
const useReturningStatement = type === QueryType.INSERT && this.platform.usesReturningStatement();

if (useReturningStatement && meta && meta.properties[meta.primaryKey]) {
const prop = meta.properties[meta.primaryKey];
append = ` RETURNING ${this.wrap(prop.fieldName)}`;
}

if (this.platform.getParameterPlaceholder() === '?') {
return sql + append;
}

let index = 1;
return sql.replace(/(\?)/g, () => {
return this.platform.getParameterPlaceholder(index++);
}) + append;
}

private processValue(value: any): string | undefined {
if (value instanceof RegExp) {
return ' LIKE ?';
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
"peerDependencies": {
"mongodb": "^3.1.13",
"mysql2": "^1.6.5",
"pg": "^7.8.2",
"sqlite": "^3.0.2"
},
"devDependencies": {
Expand All @@ -98,13 +99,15 @@
"@types/mongodb": "^3.1.21",
"@types/mysql2": "types/mysql2",
"@types/node": "^11.10.5",
"@types/pg": "^7.4.13",
"@types/uuid": "^3.4.4",
"coveralls": "^3.0.3",
"husky": "^1.3.1",
"jest": "^24.5.0",
"lint-staged": "^8.1.5",
"mongodb": "^3.1.13",
"mysql2": "^1.6.5",
"pg": "^7.8.2",
"rimraf": "^2.6.3",
"semantic-release": "^15.13.3",
"sqlite": "^3.0.2",
Expand Down

0 comments on commit 4e6486e

Please sign in to comment.