Skip to content

Commit

Permalink
feat: add database to be the primary API for making queries
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Aug 16, 2019
1 parent 069c5aa commit 2443ce9
Show file tree
Hide file tree
Showing 5 changed files with 333 additions and 7 deletions.
22 changes: 15 additions & 7 deletions adonis-typings/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ declare module '@ioc:Adonis/Addons/Database' {
import { EventEmitter } from 'events'

import {
DatabaseQueryBuilderContract,
InsertQueryBuilderContract,
RawContract,
RawBuilderContract,
QueryClientContract,
TransactionClientContract,
InsertQueryBuilderContract,
DatabaseQueryBuilderContract,
} from '@ioc:Adonis/Addons/DatabaseQueryBuilder'

/**
Expand Down Expand Up @@ -241,7 +243,10 @@ declare module '@ioc:Adonis/Addons/Database' {
/**
* Shape of config inside the database config file
*/
export type DatabaseConfigContract = { connection: string } & { [key: string]: ConnectionConfigContract }
export type DatabaseConfigContract = {
connection: string,
connections: { [key: string]: ConnectionConfigContract },
}

/**
* The shape of a connection within the connection manager
Expand Down Expand Up @@ -380,11 +385,14 @@ declare module '@ioc:Adonis/Addons/Database' {
export interface DatabaseContract {
primaryConnectionName: string,
getRawConnection: ConnectionManagerContract['get']
manager: ConnectionManagerContract,

connection (connectionName: string): QueryClientContract
query: QueryClientContract['query']
insertQuery: QueryClientContract['insertQuery']
from: QueryClientContract['from']
table: QueryClientContract['table']
query (mode?: 'read' | 'write'): DatabaseQueryBuilderContract
insertQuery (): InsertQueryBuilderContract
from (table: string): DatabaseQueryBuilderContract
table (table: string): InsertQueryBuilderContract
transaction (): Promise<TransactionClientContract>
raw (sql: string, bindings?: any, mode?: 'read' | 'write'): RawContract
}
}
26 changes: 26 additions & 0 deletions providers/DatabaseProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* @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.
*/

import { Database } from '../src/Database'

export class DatabaseServiceProvider {
constructor (protected $container: any) {
}

/**
* Register database binding
*/
public register () {
this.$container.singleton('Adonis/Addons/Database', () => {
const config = this.$container.use('Adonis/Core/Config').get('database', {})
const Logger = this.$container.use('Adonis/Core/Logger')
return new Database(config, Logger)
})
}
}
6 changes: 6 additions & 0 deletions src/Connection/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,12 @@ export class Connection extends EventEmitter implements ConnectionContract {
*/
public getClient (mode?: 'read' | 'write') {
this._ensureClients()
this._logger.trace(
{ connection: this.name },
`creating query client in %s mode`,
[mode || 'dual'],
)

if (!mode) {
return new QueryClient('dual', this.client!, this.readClient!)
}
Expand Down
132 changes: 132 additions & 0 deletions src/Database/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* @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 { Exception } from '@poppinss/utils'
import { LoggerContract } from '@poppinss/logger'

import {
ConnectionManagerContract,
DatabaseConfigContract,
DatabaseContract,
} from '@ioc:Adonis/Addons/Database'

import { ConnectionManager } from '../Connection/Manager'

/**
* Database class exposes the API to manage multiple connections and obtain an instance
* of query/transaction clients.
*/
export class Database implements DatabaseContract {
/**
* Reference to connections manager
*/
public manager: ConnectionManagerContract

/**
* Primary connection name
*/
public primaryConnectionName = this._config.connection

constructor (private _config: DatabaseConfigContract, private _logger: LoggerContract) {
this.manager = new ConnectionManager(this._logger)
this._registerConnections()
}

/**
* Registering all connections with the manager, so that we can fetch
* and connect with them whenver required.
*/
private _registerConnections () {
Object.keys(this._config.connections).forEach((name) => {
this.manager.add(name, this._config.connections[name])
})
}

/**
* Returns the connection node from the connection manager
*/
public getRawConnection (name) {
return this.manager.get(name)
}

/**
* Returns the query client for a given connection
*/
public connection (connection: string = this.primaryConnectionName) {
const [name, mode] = connection.split('::')

/**
* Connect is noop when already connected
*/
this.manager.connect(name)

/**
* Disallow modes other than `read` or `write`
*/
if (mode && !['read', 'write'].includes(mode)) {
throw new Exception(`Invalid mode ${mode}. Must be read or write`)
}

const rawConnection = this.manager.get(name)!.connection!
return mode ? rawConnection.getClient(mode as ('read' | 'write')) : rawConnection.getClient()
}

/**
* Returns query builder. Optionally one can define the mode as well
*/
public query (mode?: 'read' | 'write') {
return mode
? this.connection(`${this.primaryConnectionName}::${mode}`).query()
: this.connection().query()
}

/**
* Returns insert query builder. Always has to be dual or write mode and
* hence it doesn't matter, since in both `dual` and `write` mode,
* the `write` connection is always used.
*/
public insertQuery () {
return this.connection().insertQuery()
}

/**
* Returns instance of a query builder and selects the table
*/
public from (table: any) {
return this.connection().from(table)
}

/**
* Returns insert query builder and selects the table
*/
public table (table: any) {
return this.connection().table(table)
}

/**
* Returns a transaction instance on the default
* connection
*/
public transaction () {
return this.connection().transaction()
}

/**
* Returns an instance of raw query builder. Optionally one can
* defined the `read/write` mode in which to execute the
* query
*/
public raw (sql: string, bindings?: any, mode?: 'read' | 'write') {
return mode
? this.connection(`${this.primaryConnectionName}::${mode}`).raw(sql, bindings)
: this.connection().raw(sql, bindings)
}
}
154 changes: 154 additions & 0 deletions test/database.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* @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 * as test from 'japa'

import { Database } from '../src/Database'
import { getConfig, setup, cleanup, getLogger } from '../test-helpers'

test.group('Database', (group) => {
group.before(async () => {
await setup()
})

group.after(async () => {
await cleanup()
})

test('register all connections with the manager', (assert) => {
const config = {
connection: 'primary',
connections: { primary: getConfig() },
}

const db = new Database(config, getLogger())

assert.isDefined(db.manager.connections.get('primary'))
assert.equal(db.manager.connections.get('primary')!.state, 'idle')
assert.isUndefined(db.manager.connections.get('primary')!.connection)
})

test('make connection when db.connection is called', async (assert) => {
assert.plan(1)

const config = {
connection: 'primary',
connections: { primary: getConfig() },
}

const db = new Database(config, getLogger())
db.manager.on('connect', (connection) => {
assert.equal(connection.name, 'primary')
})

db.connection()
await db.manager.closeAll()
})

test('make connection to a named connection', async (assert) => {
assert.plan(1)

const config = {
connection: 'primary',
connections: { primary: getConfig() },
}

const db = new Database(config, getLogger())
db.manager.on('connect', (connection) => {
assert.equal(connection.name, 'primary')
})

db.connection('primary')
await db.manager.closeAll()
})

test('make connection to a named connection in write mode', async (assert) => {
assert.plan(1)

const config = {
connection: 'primary',
connections: { primary: getConfig() },
}

const db = new Database(config, getLogger())
const client = db.connection('primary::write')

assert.equal(client.mode, 'write')
await db.manager.closeAll()
})

test('make connection to a named connection in read mode', async (assert) => {
assert.plan(1)

const config = {
connection: 'primary',
connections: { primary: getConfig() },
}

const db = new Database(config, getLogger())
const client = db.connection('primary::read')

assert.equal(client.mode, 'read')
await db.manager.closeAll()
})

test('get transaction instance', async (assert) => {
const config = {
connection: 'primary',
connections: { primary: getConfig() },
}

const db = new Database(config, getLogger())
const trx = await db.transaction()

assert.equal(trx.mode, 'dual')
assert.isTrue(trx.isTransaction)

await trx.rollback()
await db.manager.closeAll()
})

test('get raw query builder instance', async (assert) => {
const config = {
connection: 'primary',
connections: { primary: getConfig() },
}

const db = new Database(config, getLogger())
const result = await db.raw('select 1 + 1')
assert.isDefined(result)
await db.manager.closeAll()
})

test('get raw query builder instance in read mode', async (assert) => {
const config = {
connection: 'primary',
connections: { primary: getConfig() },
}

const db = new Database(config, getLogger())
const result = await db.raw('select 1 + 1', [], 'read')
assert.isDefined(result)
await db.manager.closeAll()
})

test('get raw query builder instance in write mode', async (assert) => {
const config = {
connection: 'primary',
connections: { primary: getConfig() },
}

const db = new Database(config, getLogger())
const result = await db.raw('select 1 + 1', [], 'write')
assert.isDefined(result)
await db.manager.closeAll()
})
})

0 comments on commit 2443ce9

Please sign in to comment.