Skip to content

Commit

Permalink
feat(core): add orm.checkConnection() helper (#4961)
Browse files Browse the repository at this point in the history
```ts
// boolean
const isConnected = await orm.isConnected();
// object with `ok`, `reason` and `error` keys
const check = await orm.checkConnection();

console.log(check.ok, check.reason);
```

Closes #4959
  • Loading branch information
B4nan committed Nov 26, 2023
1 parent f3a22bd commit b868f02
Show file tree
Hide file tree
Showing 12 changed files with 135 additions and 6 deletions.
13 changes: 13 additions & 0 deletions docs/docs/faq.md
Expand Up @@ -134,3 +134,16 @@ To fix this, disable the [`useDefineForClassFields`](https://www.typescriptlang.
}
}
```

### How can I check if database connection works?

There are two methods you can use to check the database status, they live on the `Connection` class and both have a shortcut on the `MikroORM` class too:

```ts
// boolean
const isConnected = await orm.isConnected();
// object with `ok`, `reason` and `error` keys
const check = await orm.checkConnection();

console.log(check.ok, check.reason);
```
7 changes: 7 additions & 0 deletions packages/core/src/MikroORM.ts
Expand Up @@ -157,6 +157,13 @@ export class MikroORM<D extends IDatabaseDriver = IDatabaseDriver> {
return this.driver.getConnection().isConnected();
}

/**
* Checks whether the database connection is active, returns .
*/
async checkConnection(): Promise<{ ok: boolean; reason?: string; error?: Error }> {
return this.driver.getConnection().checkConnection();
}

/**
* Closes the database connection.
*/
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/connections/Connection.ts
Expand Up @@ -41,6 +41,11 @@ export abstract class Connection {
*/
abstract isConnected(): Promise<boolean>;

/**
* Are we connected to the database
*/
abstract checkConnection(): Promise<{ ok: boolean; reason?: string; error?: Error }>;

/**
* Closes the database connection (aka disconnect)
*/
Expand Down
18 changes: 18 additions & 0 deletions packages/knex/src/AbstractSqlConnection.ts
Expand Up @@ -50,11 +50,17 @@ export abstract class AbstractSqlConnection extends Connection {
return this.client;
}

/**
* @inheritDoc
*/
override async close(force?: boolean): Promise<void> {
await super.close(force);
await this.getKnex().destroy();
}

/**
* @inheritDoc
*/
async isConnected(): Promise<boolean> {
try {
await this.getKnex().raw('select 1');
Expand All @@ -64,6 +70,18 @@ export abstract class AbstractSqlConnection extends Connection {
}
}

/**
* @inheritDoc
*/
async checkConnection(): Promise<{ ok: boolean; reason?: string; error?: Error }> {
try {
await this.getKnex().raw('select 1');
return { ok: true };
} catch (error: any) {
return { ok: false, reason: error.message, error };
}
}

override async transactional<T>(cb: (trx: Transaction<Knex.Transaction>) => Promise<T>, options: { isolationLevel?: IsolationLevel; readOnly?: boolean; ctx?: Knex.Transaction; eventBroadcaster?: TransactionEventBroadcaster } = {}): Promise<T> {
const trx = await this.begin(options);

Expand Down
16 changes: 15 additions & 1 deletion packages/mongodb/src/MongoConnection.ts
Expand Up @@ -82,7 +82,21 @@ export class MongoConnection extends Connection {
}

async isConnected(): Promise<boolean> {
return this.connected;
try {
const res = await this.db?.command({ ping: 1 });
return this.connected = !!res.ok;
} catch (error) {
return this.connected = false;
}
}

async checkConnection(): Promise<{ ok: boolean; reason?: string; error?: Error }> {
try {
const res = await this.db?.command({ ping: 1 });
return { ok: !!res.ok };
} catch (error: any) {
return { ok: false, reason: error.message, error };
}
}

getClient(): MongoClient {
Expand Down
4 changes: 4 additions & 0 deletions tests/Connection.test.ts
Expand Up @@ -27,6 +27,10 @@ class CustomConnection extends Connection {
return false;
}

async checkConnection(): Promise<{ ok: boolean; reason?: string; error?: Error }> {
return { ok: false, reason: 'foo' };
}

}

describe('Connection', () => {
Expand Down
12 changes: 12 additions & 0 deletions tests/EntityManager.mariadb.test.ts
Expand Up @@ -18,10 +18,22 @@ describe('EntityManagerMariaDb', () => {

test('isConnected()', async () => {
expect(await orm.isConnected()).toBe(true);
expect(await orm.checkConnection()).toEqual({
ok: true,
});
await orm.close(true);
expect(await orm.isConnected()).toBe(false);
const check = await orm.checkConnection();
expect(check).toMatchObject({
ok: false,
error: expect.any(Error),
reason: 'Unable to acquire a connection',
});
await orm.connect();
expect(await orm.isConnected()).toBe(true);
expect(await orm.checkConnection()).toEqual({
ok: true,
});
});

test('getConnectionOptions()', async () => {
Expand Down
19 changes: 19 additions & 0 deletions tests/EntityManager.mongo2.test.ts
Expand Up @@ -12,6 +12,25 @@ describe('EntityManagerMongo2', () => {
beforeAll(async () => orm = await initORMMongo());
beforeEach(async () => orm.schema.clearDatabase());

test('isConnected()', async () => {
expect(await orm.isConnected()).toBe(true);
expect(await orm.checkConnection()).toEqual({
ok: true,
});
await orm.close(true);
expect(await orm.isConnected()).toBe(false);
expect(await orm.checkConnection()).toMatchObject({
ok: false,
error: expect.any(Error),
reason: 'Client must be connected before running operations',
});
await orm.connect();
expect(await orm.isConnected()).toBe(true);
expect(await orm.checkConnection()).toEqual({
ok: true,
});
});

test('loaded references and collections', async () => {
const pub = new Publisher('Publisher 123');
const god = new Author('God', 'hello@heaven.god');
Expand Down
12 changes: 12 additions & 0 deletions tests/EntityManager.mysql.test.ts
Expand Up @@ -52,10 +52,22 @@ describe('EntityManagerMySql', () => {

test('isConnected()', async () => {
expect(await orm.isConnected()).toBe(true);
expect(await orm.checkConnection()).toEqual({
ok: true,
});
await orm.close(true);
expect(await orm.isConnected()).toBe(false);
const check = await orm.checkConnection();
expect(check).toMatchObject({
ok: false,
error: expect.any(Error),
reason: 'Unable to acquire a connection',
});
await orm.connect();
expect(await orm.isConnected()).toBe(true);
expect(await orm.checkConnection()).toEqual({
ok: true,
});
expect(inspect(orm.em)).toBe(`[EntityManager<${orm.em.id}>]`);
});

Expand Down
18 changes: 15 additions & 3 deletions tests/EntityManager.postgre.test.ts
Expand Up @@ -77,11 +77,23 @@ describe('EntityManagerPostgre', () => {
});

test('isConnected()', async () => {
await expect(orm.isConnected()).resolves.toBe(true);
expect(await orm.isConnected()).toBe(true);
expect(await orm.checkConnection()).toEqual({
ok: true,
});
await orm.close(true);
await expect(orm.isConnected()).resolves.toBe(false);
expect(await orm.isConnected()).toBe(false);
const check = await orm.checkConnection();
expect(check).toMatchObject({
ok: false,
error: expect.any(Error),
reason: 'Unable to acquire a connection',
});
await orm.connect();
await expect(orm.isConnected()).resolves.toBe(true);
expect(await orm.isConnected()).toBe(true);
expect(await orm.checkConnection()).toEqual({
ok: true,
});
});

test('getConnectionOptions()', async () => {
Expand Down
12 changes: 12 additions & 0 deletions tests/EntityManager.sqlite2.test.ts
Expand Up @@ -23,10 +23,22 @@ describe.each(['sqlite', 'better-sqlite'] as const)('EntityManager (%s)', driver

test('isConnected()', async () => {
expect(await orm.isConnected()).toBe(true);
expect(await orm.checkConnection()).toEqual({
ok: true,
});
await orm.close(true);
expect(await orm.isConnected()).toBe(false);
const check = await orm.checkConnection();
expect(check).toMatchObject({
ok: false,
error: expect.any(Error),
reason: 'Unable to acquire a connection',
});
await orm.connect();
expect(await orm.isConnected()).toBe(true);
expect(await orm.checkConnection()).toEqual({
ok: true,
});

// as the db lives only in memory, we need to re-create the schema after reconnection
await orm.schema.createSchema();
Expand Down
5 changes: 3 additions & 2 deletions tests/features/reusing-mongo-client.test.ts
Expand Up @@ -19,6 +19,7 @@ test('should allow reusing mongo connection', async () => {

await expect(orm.em.find(Author, {})).resolves.toHaveLength(0);
await orm2.close(); // closing orm2 will make orm1 disconnect too as they share mongo client
await expect(orm.isConnected()).resolves.toBe(true);
await expect(orm.em.find(Author, {})).rejects.toThrow('Client must be connected before running operations');
await expect(orm.isConnected()).resolves.toBe(false);
// this works now as we correctly detect the broken connection (as asserted above) and reconnect automatically
await expect(orm.em.find(Author, {})).resolves.toEqual([]);
});

0 comments on commit b868f02

Please sign in to comment.