Skip to content

Commit

Permalink
feat: implement connection class to manage and monitor pool resources
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Aug 3, 2019
1 parent c8713e9 commit 69b366a
Show file tree
Hide file tree
Showing 6 changed files with 421 additions and 14 deletions.
6 changes: 6 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
DB=mysql
MYSQL_HOST=0.0.0.0
MYSQL_PORT=3306
DB_NAME=lucid
MYSQL_USER=virk
MYSQL_PASSWORD=password
24 changes: 10 additions & 14 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
version: '2'
version: '3.3'
services:
mysql:
db:
image: mysql:5.7
expose:
- '3306'
restart: always
environment:
MYSQL_DATABASE: 'lucid'
MYSQL_USER: 'virk'
MYSQL_PASSWORD: 'password'
MYSQL_ROOT_PASSWORD: 'password'
ports:
- '3306:3306'
restart: 'always'
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: ''
MYSQL_ROOT_PASSWORD: 'root'
MYSQL_USER: 'travis'
MYSQL_PASSWORD: ''
MYSQL_ROOT_HOST: '%'
MYSQL_DATABASE: 'testing_lucid'
volumes:
- ./mysqldata:/var/lib/mysql
expose:
- '3306'
144 changes: 144 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// import * as Knex from 'knex'
// // import { join } from 'path'
// // import { DatabaseQueryBuilderContract } from '@ioc:Adonis/Addons/Database'

// const knex = Knex({
// client: 'pg',
// connection: {
// host: '0.0.0.0',
// user: 'virk',
// password: '',
// database: 'directory-service',
// },
// pool: {
// min: 0,
// max: 5,
// idleTimeoutMillis: 30000,
// },
// useNullAsDefault: true,
// })

// // let i = 0;
// knex['_context'].client.pool.on('destroySuccess', _eventId => {
// // i++
// console.log(
// knex['_context'].client.pool.numUsed(),
// knex['_context'].client.pool.numFree(),
// knex['_context'].client.pool.numPendingAcquires(),
// )

// // if (i === 3) {
// // knex['_context'].client.pool.destroy()
// // }
// });

// knex['_context'].client.pool.on('poolDestroySuccess', _resource => {
// console.log('poolDestroySuccess>>>')
// });

// // setInterval(() => {
// // console.log('ping')
// // }, 1000)

// // type User = {
// // id: number,
// // }

// // console.log(knex.raw(['10']).toQuery())

// // knex.schema.createTable('users', (table) => {
// // table.increments('id')
// // table.string('username')
// // table.integer('age')
// // table.timestamps()
// // }).then(() => console.log('created'))

// // knex.table('users').insert([
// // { username: 'virk', age: 29 }, { username: 'nikk', age: 28 }, { username: 'prasan', age: 29 },
// // ]).then(console.log)

// Promise.all([
// knex
// .select('*')
// .from('users')
// .debug(true)
// .then((result) => {
// console.log(result)
// }),
// knex
// .select('*')
// .from('users')
// .debug(true)
// .then((result) => {
// console.log(result)
// }),
// knex
// .select('*')
// .from('users')
// .debug(true)
// .then((result) => {
// console.log(result)
// }),
// knex
// .select('*')
// .from('users')
// .debug(true)
// .then((result) => {
// console.log(result)
// }),
// knex
// .select('*')
// .from('users')
// .debug(true)
// .then((result) => {
// console.log(result)
// }),
// ]).then(() => {
// })

// // knex.transaction().then((trx) => {
// // })

// // console.log(query.toSQL())

// // type FilteredKeys<T> = { [P in keyof T]: T[P] extends Function ? never : P }[keyof T]

// // type GetRefs<T extends typeof BaseModel> = T['refs'] extends object ? {
// // [P in keyof T['refs']]: InstanceType<T>[P]
// // } : {
// // [P in FilteredKeys<InstanceType<T>>]: InstanceType<T>[P]
// // }

// // class BaseModel {
// // public static refs: unknown

// // public static query<T extends typeof BaseModel> (
// // this: T,
// // ): DatabaseQueryBuilderContract<GetRefs<T>, InstanceType<T>> {
// // return {} as DatabaseQueryBuilderContract<GetRefs<T>, InstanceType<T>>
// // }
// // }

// // class User extends BaseModel {
// // public username: string
// // public age: number

// // public castToInt (): number {
// // return 22
// // }
// // }

// // class Post extends BaseModel {
// // }

// // const foo: DatabaseQueryBuilderContract<{ username: string, age: number }>
// // const b = foo
// // .select('*')
// // .select({
// // 'a': 'username',
// // 'age': 'age',
// // }).first()

// // async function foo () {
// // const a = User.query().select(['username', 'age']).first()
// // }
101 changes: 101 additions & 0 deletions src/Connection/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

/// <reference path="../../adonis-typings/database.ts" />

import { Pool } from 'tarn'
import * as knex from 'knex'
import { EventEmitter } from 'events'
import { ConnectionConfigContract, ConnectionContract } from '@ioc:Adonis/Addons/Database'

/**
* Connection class manages a given database connection. Internally it uses
* knex to build the database connection with appropriate database
* driver.
*/
export class Connection extends EventEmitter implements ConnectionContract {
/**
* Reference to knex. The instance is created once the `open`
* method is invoked
*/
public client?: knex

/**
* List of events emitted by this class
*/
public readonly EVENTS: ['open', 'close', 'close:error']

constructor (public name: string, public config: ConnectionConfigContract) {
super()
}

/**
* Does cleanup by removing knex reference and removing
* all listeners
*/
private _monitorPoolResources () {
this.pool!.on('destroySuccess', () => {
/**
* Force close when `numUsed` and `numFree` both are zero. This happens
* when `min` resources inside the pool are set to `0`.
*/
if (this.pool!.numFree() === 0 && this.pool!.numUsed() === 0) {
this.close()
}
})

/**
* Pool has destroyed and hence we must cleanup resources
* as well.
*/
this.pool!.on('poolDestroySuccess', () => {
this.client = undefined
this.emit('close')
this.removeAllListeners()
})
}

/**
* Returns the pool instance for the given connection
*/
public get pool (): null | Pool<any> {
return this.client ? this.client['_context'].client.pool : null
}

/**
* Opens the connection by creating knex instance
*/
public open () {
try {
this.client = knex(this.config)
this._monitorPoolResources()
this.emit('open')
} catch (error) {
this.emit('error', error)
throw error
}
}

/**
* Closes DB connection by destroying knex instance. The `connection`
* object must be free for garbage collection.
*
* In case of error this method will emit `close:error` event followed
* by the `close` event.
*/
public async close (): Promise<void> {
if (this.client) {
try {
await this.client!.destroy()
} catch (error) {
this.emit('close:error', error)
}
}
}
}
66 changes: 66 additions & 0 deletions test-helpers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

/// <reference path="../adonis-typings/database.ts" />

import { join } from 'path'
import * as dotenv from 'dotenv'
import { Filesystem } from '@poppinss/dev-utils'
import { ConnectionConfigContract } from '@ioc:Adonis/Addons/Database'

export const fs = new Filesystem(join(__dirname, 'tmp'))
dotenv.config()

/**
* Returns config based upon DB set in environment variables
*/
export function getConfig (): ConnectionConfigContract {
switch (process.env.DB) {
case 'sqlite':
return {
client: 'sqlite',
connection: {
filename: join(fs.basePath, 'db.sqlite'),
},
useNullAsDefault: true,
}
case 'mysql':
return {
client: 'mysql',
connection: {
host: process.env.MYSQL_HOST as string,
port: Number(process.env.MYSQL_PORT),
database: process.env.DB_NAME as string,
user: process.env.MYSQL_USER as string,
password: process.env.MYSQL_PASSWORD as string,
},
useNullAsDefault: true,
}
default:
throw new Error(`Missing test config for ${process.env.DB} connection`)
}
}

/**
* Does base setup by creating databases
*/
export async function setup () {
if (process.env.DB === 'sqlite') {
await fs.ensureRoot()
}
}

/**
* Does cleanup removes database
*/
export async function cleanup () {
if (process.env.DB === 'sqlite') {
await fs.cleanup()
}
}

0 comments on commit 69b366a

Please sign in to comment.