Skip to content

Commit

Permalink
Merge 6b435d1 into 40bcdc0
Browse files Browse the repository at this point in the history
  • Loading branch information
B4nan committed Mar 20, 2019
2 parents 40bcdc0 + 6b435d1 commit 3a60c5a
Show file tree
Hide file tree
Showing 32 changed files with 1,263 additions and 126 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
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# MikroORM

Simple typescript ORM for node.js based on data-mapper, unit-of-work and identity-map patterns. Supports MongoDB,
MySQL and SQLite databases.
MySQL, PostgreSQL and SQLite databases.

Heavily inspired by [Doctrine](https://www.doctrine-project.org/) and [Nextras Orm](https://nextras.org/orm/).

Expand Down Expand Up @@ -37,16 +37,18 @@ First install the module via `yarn` or `npm` and do not forget to install the da

```
$ yarn add mikro-orm mongodb # for mongo
$ yarn add mikro-orm mysql2 # for mysql
$ yarn add mikro-orm sqlite # for sqlite
$ yarn add mikro-orm mysql2 # for mysql
$ yarn add mikro-orm pg # for postgresql
$ yarn add mikro-orm sqlite # for sqlite
```

or

```
$ npm i -s mikro-orm mongodb # for mongo
$ npm i -s mikro-orm mysql2 # for mysql
$ npm i -s mikro-orm sqlite # for sqlite
$ npm i -s mikro-orm mysql2 # for mysql
$ npm i -s mikro-orm pg # for postgresql
$ npm i -s mikro-orm sqlite # for sqlite
```

Next you will need to enable support for [decorators](https://www.typescriptlang.org/docs/handbook/decorators.html)
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
11 changes: 10 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,21 @@ services:
image: mysql:5.7
restart: unless-stopped
ports:
- "127.0.0.1:3307:3306"
- 3307:3306
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: 1
volumes:
- mysql:/var/lib/mysql

postgre:
container_name: postgre
image: postgres:11.2
ports:
- 5432:5432
volumes:
- postgre:/var/lib/postgresql/data

volumes:
mongo:
mysql:
postgre:
12 changes: 8 additions & 4 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,18 @@ First install the module via `yarn` or `npm` and do not forget to install the da

```
$ yarn add mikro-orm mongodb # for mongo
$ yarn add mikro-orm mysql2 # for mysql
$ yarn add mikro-orm sqlite # for sqlite
$ yarn add mikro-orm mysql2 # for mysql
$ yarn add mikro-orm pg # for postgresql
$ yarn add mikro-orm sqlite # for sqlite
```

or

```
$ npm i -s mikro-orm mongodb # for mongo
$ npm i -s mikro-orm mysql2 # for mysql
$ npm i -s mikro-orm sqlite # for sqlite
$ npm i -s mikro-orm mysql2 # for mysql
$ npm i -s mikro-orm pg # for postgresql
$ npm i -s mikro-orm sqlite # for sqlite
```

Next you will need to enable support for [decorators](https://www.typescriptlang.org/docs/handbook/decorators.html)
Expand Down Expand Up @@ -76,6 +78,8 @@ const orm = await MikroORM.init({
});
```

## Request context

Then you will need to fork entity manager for each request so their identity maps will not
collide. To do so, use the `RequestContext` helper:

Expand Down
3 changes: 2 additions & 1 deletion docs/usage-with-sql.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
To use `mikro-orm` with MySQL database, do not forget to install `mysql2` dependency and provide
`MySqlDriver` class when initializing ORM.

Similarly for SQLite install `sqlite` dependency and provide `SqliteDriver`.
Similarly for SQLite install `sqlite` dependency and provide `SqliteDriver`. For PostgreSQL
install `pg` and provide `PostgreSqlDriver`.

Then call `MikroORM.init` as part of bootstrapping your app:

Expand Down
17 changes: 11 additions & 6 deletions lib/EntityManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,22 +126,28 @@ export class EntityManager {
async nativeInsert<T extends IEntityType<T>>(entityName: EntityName<T>, data: EntityData<T>): Promise<IPrimaryKey> {
entityName = Utils.className(entityName);
this.validator.validateParams(data, 'insert data');
return this.driver.nativeInsert(entityName, data);
const res = await this.driver.nativeInsert(entityName, data);

return res.insertId;
}

async nativeUpdate<T extends IEntityType<T>>(entityName: EntityName<T>, where: FilterQuery<T>, data: EntityData<T>): Promise<number> {
entityName = Utils.className(entityName);
where = SmartQueryHelper.processWhere(where as FilterQuery<T>);
this.validator.validateParams(data, 'update data');
this.validator.validateParams(where, 'update condition');
return this.driver.nativeUpdate(entityName, where, data);
const res = await this.driver.nativeUpdate(entityName, where, data);

return res.affectedRows;
}

async nativeDelete<T extends IEntityType<T>>(entityName: EntityName<T>, where: FilterQuery<T> | string | any): Promise<number> {
entityName = Utils.className(entityName);
where = SmartQueryHelper.processWhere(where as FilterQuery<T>);
this.validator.validateParams(where, 'delete condition');
return this.driver.nativeDelete(entityName, where);
const res = await this.driver.nativeDelete(entityName, where);

return res.affectedRows;
}

/**
Expand All @@ -160,7 +166,7 @@ export class EntityManager {
throw new Error('You cannot merge entity without identifier!');
}

const entity = Utils.isEntity<T>(data) ? data : this.getEntityFactory().create<T>(entityName, data, true);
const entity = Utils.isEntity<T>(data) ? data : this.getEntityFactory().create<T>(entityName, data);
EntityAssigner.assign(entity, data, true);
this.getUnitOfWork().addToIdentityMap(entity);

Expand All @@ -171,8 +177,7 @@ export class EntityManager {
* Creates new instance of given entity and populates it with given data
*/
create<T extends IEntityType<T>>(entityName: EntityName<T>, data: EntityData<T>): T {
entityName = Utils.className(entityName);
return this.getEntityFactory().create<T>(entityName, data, false);
return this.getEntityFactory().create<T>(entityName, data, false, false);
}

/**
Expand Down
43 changes: 42 additions & 1 deletion lib/connections/Connection.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { URL } from 'url';
import { Configuration } from '../utils';

export abstract class Connection {

protected readonly logger = this.config.getLogger();
protected abstract client: any;

constructor(protected readonly config: Configuration) { }

Expand Down Expand Up @@ -47,7 +49,37 @@ export abstract class Connection {
throw new Error(`Transactions are not supported by current driver`);
}

abstract async execute(query: string, params?: any[], method?: string): Promise<QueryResult | any | any[]>;
abstract async execute(query: string, params: any[], method?: 'all' | 'get' | 'run'): Promise<QueryResult | any | any[]>;

getConnectionOptions(): ConnectionConfig {
const ret: ConnectionConfig = {};
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;
}

protected async executeQuery<T>(query: string, params: any[], cb: () => Promise<T>): Promise<T> {
try {
const now = Date.now();
const res = await cb();
this.logQuery(query + ` [took ${Date.now() - now} ms]`);

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

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

throw e;
}
}

protected logQuery(query: string): void {
this.logger.debug(`[query-logger] ${query}`);
Expand All @@ -58,4 +90,13 @@ export abstract class Connection {
export interface QueryResult {
affectedRows: number;
insertId: number;
row?: Record<string, any>,
}

export interface ConnectionConfig {
host?: string;
port?: number;
user?: string;
password?: string;
database?: string;
}
23 changes: 15 additions & 8 deletions lib/connections/MongoConnection.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Collection, Db, DeleteWriteOpResultObject, InsertOneWriteOpResult, MongoClient, ObjectID, UpdateWriteOpResult } from 'mongodb';
import { Connection } from './Connection';
import { Collection, Db, MongoClient, ObjectID } from 'mongodb';
import { Connection, QueryResult } from './Connection';
import { Utils } from '../utils';
import { QueryOrder } from '../query';
import { FilterQuery } from '..';
Expand Down Expand Up @@ -63,35 +63,35 @@ export class MongoConnection extends Connection {
return res;
}

async insertOne<T>(collection: string, data: Partial<T>): Promise<InsertOneWriteOpResult> {
async insertOne<T>(collection: string, data: Partial<T>): Promise<QueryResult> {
data = this.convertObjectIds(data);
const now = Date.now();
const res = await this.getCollection(collection).insertOne(data);
const query = `db.getCollection("${collection}").insertOne(${JSON.stringify(data)});`;
this.logQuery(`${query} [took ${Date.now() - now} ms]`);

return res;
return this.transformResult(res);
}

async updateMany<T>(collection: string, where: FilterQuery<T>, data: Partial<T>): Promise<UpdateWriteOpResult> {
async updateMany<T>(collection: string, where: FilterQuery<T>, data: Partial<T>): Promise<QueryResult> {
where = this.convertObjectIds(where);
data = this.convertObjectIds(data);
const now = Date.now();
const res = await this.getCollection(collection).updateMany(where, { $set: data });
const query = `db.getCollection("${collection}").updateMany(${JSON.stringify(where)}, { $set: ${JSON.stringify(data)} });`;
this.logQuery(`${query} [took ${Date.now() - now} ms]`);

return res;
return this.transformResult(res);
}

async deleteMany<T>(collection: string, where: FilterQuery<T>): Promise<DeleteWriteOpResultObject> {
async deleteMany<T>(collection: string, where: FilterQuery<T>): Promise<QueryResult> {
where = this.convertObjectIds(where);
const res = await this.getCollection(collection).deleteMany(where);
const query = `db.getCollection("${collection}").deleteMany()`;
const now = Date.now();
this.logQuery(`${query} [took ${Date.now() - now} ms]`);

return res;
return this.transformResult(res);
}

async aggregate(collection: string, pipeline: any[]): Promise<any[]> {
Expand Down Expand Up @@ -135,4 +135,11 @@ export class MongoConnection extends Connection {
return payload;
}

private transformResult(res: any): QueryResult {
return {
affectedRows: res.modifiedCount || res.deletedCount || 0,
insertId: res.insertedId,
};
}

}
43 changes: 12 additions & 31 deletions lib/connections/MySqlConnection.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
import { Connection as MySql2Connection, ConnectionOptions, createConnection } from 'mysql2/promise';
import { readFileSync } from 'fs';
import { URL } from 'url';
import { Connection, QueryResult } from './Connection';

export class MySqlConnection extends Connection {

private connection: MySql2Connection;
protected client: MySql2Connection;

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

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

async isConnected(): Promise<boolean> {
try {
await this.connection.query('SELECT 1');
await this.client.query('SELECT 1');
return true;
} catch {
return false;
Expand All @@ -41,35 +40,17 @@ export class MySqlConnection extends Connection {
}

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.execute(query, params);
this.logQuery(query + ` [took ${Date.now() - now} ms]`);

if (method === 'get') {
return (res as QueryResult[][])[0][0];
}

return res[0];
} catch (e) {
e.message += `\n in query: ${query}`;
const res = await this.executeQuery(query, params, () => this.client.execute(query, params));

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

throw e;
if (method === 'get') {
return (res as QueryResult[][])[0][0];
}

return res[0];
}

getConnectionOptions(): ConnectionOptions {
const ret = {} as ConnectionOptions;
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(/^\//, ''));
const ret: ConnectionOptions = super.getConnectionOptions();

if (this.config.get('multipleStatements')) {
ret.multipleStatements = this.config.get('multipleStatements');
Expand All @@ -79,12 +60,12 @@ export class MySqlConnection extends Connection {
}

async loadFile(path: string): Promise<void> {
await this.connection.query(readFileSync(path).toString());
await this.client.query(readFileSync(path).toString());
}

private async query(sql: string): Promise<void> {
const now = Date.now();
await this.connection.query(sql);
await this.client.query(sql);
this.logQuery(`${sql} [took ${Date.now() - now} ms]`);
}

Expand Down

0 comments on commit 3a60c5a

Please sign in to comment.