Skip to content

Commit

Permalink
refactor: abstract queryclient to it's own class from connection
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Aug 16, 2019
1 parent 80cb80b commit d5a52c0
Show file tree
Hide file tree
Showing 10 changed files with 718 additions and 681 deletions.
11 changes: 10 additions & 1 deletion adonis-typings/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,10 @@ declare module '@ioc:Adonis/Addons/Database' {
* Connection represents a single knex instance with inbuilt
* pooling capabilities.
*/
export interface ConnectionContract extends EventEmitter, QueryClientContract {
export interface ConnectionContract extends EventEmitter {
client?: knex,
readClient?: knex,

/**
* Read/write connection pools
*/
Expand Down Expand Up @@ -357,5 +360,11 @@ declare module '@ioc:Adonis/Addons/Database' {
* Disconnect knex
*/
disconnect (): Promise<void>,

/**
* Returns an instance of a given client. A sticky client
* always uses the write connection for all queries
*/
getClient (sticky?: boolean): QueryClientContract,
}
}
Empty file removed index.ts
Empty file.
142 changes: 22 additions & 120 deletions src/Connection/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,9 @@ import { EventEmitter } from 'events'
import { Exception } from '@poppinss/utils'
import { LoggerContract } from '@poppinss/logger'
import { patchKnex } from 'knex-dynamic-connection'
import { resolveClientNameWithAliases } from 'knex/lib/helpers'

import { ConnectionConfigContract, ConnectionContract } from '@ioc:Adonis/Addons/Database'

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

import { Transaction } from '../Transaction'
import { RawQueryBuilder } from '../QueryBuilder/Raw'
import { InsertQueryBuilder } from '../QueryBuilder/Insert'
import { DatabaseQueryBuilder } from '../QueryBuilder/Database'
import { QueryClient } from '../QueryClient'

/**
* Connection class manages a given database connection. Internally it uses
Expand All @@ -41,23 +29,13 @@ export class Connection extends EventEmitter implements ConnectionContract {
* Reference to knex. The instance is created once the `open`
* method is invoked
*/
private _client?: knex
public client?: knex

/**
* Read client when read/write replicas are defined in the config, otherwise
* it is a reference to the `client`.
*/
private _readClient?: knex

/**
* Not a transaction client
*/
public isTransaction = false

/**
* The name of the dialect in use
*/
public dialect = resolveClientNameWithAliases(this.config.client)
public readClient?: knex

/**
* Config for one or more read replicas. Only exists, when replicas are
Expand All @@ -83,7 +61,7 @@ export class Connection extends EventEmitter implements ConnectionContract {
* Raises exception when client or readClient are not defined
*/
private _ensureClients () {
if (!this._client || !this._readClient) {
if (!this.client || !this.readClient) {
throw new Exception('Connection is not in ready state. Make sure to call .connect first')
}
}
Expand Down Expand Up @@ -116,8 +94,8 @@ export class Connection extends EventEmitter implements ConnectionContract {
* Cleanup references
*/
private _cleanup () {
this._client = undefined
this._readClient = undefined
this.client = undefined
this.readClient = undefined
this._readReplicas = []
}

Expand Down Expand Up @@ -254,8 +232,8 @@ export class Connection extends EventEmitter implements ConnectionContract {
* Creates the write connection
*/
private _setupWriteConnection () {
this._client = knex(this._getWriteConfig())
patchKnex(this._client, this._writeConfigResolver.bind(this))
this.client = knex(this._getWriteConfig())
patchKnex(this.client, this._writeConfigResolver.bind(this))
}

/**
Expand All @@ -264,28 +242,29 @@ export class Connection extends EventEmitter implements ConnectionContract {
*/
private _setupReadConnection () {
if (!this._hasReadWriteReplicas()) {
this._readClient = this._client
this.readClient = this.client
return
}

this._logger.trace({ connection: this.name }, 'setting up read/write replicas')
this._readClient = knex(this._getReadConfig())
patchKnex(this._readClient, this._readConfigResolver.bind(this))

this.readClient = knex(this._getReadConfig())
patchKnex(this.readClient, this._readConfigResolver.bind(this))
}

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

/**
* Returns the pool instance for the read connection. When replicas are
* not in use, then read/write pools are same.
*/
public get readPool (): null | Pool<any> {
return this._readClient ? this._readClient.client.pool : null
return this.readClient ? this.readClient.client.pool : null
}

/**
Expand Down Expand Up @@ -316,9 +295,9 @@ export class Connection extends EventEmitter implements ConnectionContract {
/**
* Disconnect write client
*/
if (this._client) {
if (this.client) {
try {
await this._client.destroy()
await this.client.destroy()
} catch (error) {
this.emit('disconnect:error', error, this)
}
Expand All @@ -328,98 +307,21 @@ export class Connection extends EventEmitter implements ConnectionContract {
* Disconnect read client when it exists and both clients
* aren't same
*/
if (this._readClient && this._readClient !== this._client) {
if (this.readClient && this.readClient !== this.client) {
try {
await this._readClient.destroy()
await this.readClient.destroy()
} catch (error) {
this.emit('disconnect:error', error, this)
}
}
}

/**
* Returns the read client
*/
public getReadClient () {
this._ensureClients()
return this._readClient!
}

/**
* Returns the write client
*/
public getWriteClient () {
this._ensureClients()
return this._client!
}

/**
* Truncate table
*/
public async truncate (table: string): Promise<void> {
this._ensureClients()
await this._client!.select(table).truncate()
}

/**
* Get information for a table columns
*/
public async columnsInfo (table: string, column?: string): Promise<any> {
this._ensureClients()
const query = this._client!.select(table)
const result = await (column ? query.columnInfo(column) : query.columnInfo())
return result
}

/**
* Returns an instance of a transaction. Each transaction will
* query and hold a single connection for all queries.
*/
public async transaction (): Promise<TransactionClientContract> {
this._ensureClients()
const trx = await this._client!.transaction()
return new Transaction(trx, this.dialect)
}

/**
* Returns instance of a query builder for selecting, updating
* or deleting rows
*/
public query (): DatabaseQueryBuilderContract {
this._ensureClients()
return new DatabaseQueryBuilder(this._client!.queryBuilder(), this)
}

/**
* Returns instance of a query builder for inserting rows
*/
public insertQuery (): InsertQueryBuilderContract {
this._ensureClients()
return new InsertQueryBuilder(this._client!.queryBuilder(), this)
}

/**
* Returns instance of raw query builder
*/
public raw (sql: any, bindings?: any): RawContract {
this._ensureClients()
return new RawQueryBuilder(this._client!.raw(sql, bindings))
}

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

/**
* Returns instance of a query builder and selects the table
* for an insert query
* Returns an instance for a query client that using this connection. Setting
* `sticky=true` will use the write connection for reads
*/
public table (table: any): InsertQueryBuilderContract {
public getClient (sticky = false) {
this._ensureClients()
return this.insertQuery().table(table)
return sticky ? new QueryClient(this.client!) : new QueryClient(this.client!, this.readClient!)
}
}
121 changes: 121 additions & 0 deletions src/QueryClient/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* @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 knex from 'knex'
import {
RawContract,
QueryClientContract,
TransactionClientContract,
InsertQueryBuilderContract,
DatabaseQueryBuilderContract,
} from '@ioc:Adonis/Addons/DatabaseQueryBuilder'
import { resolveClientNameWithAliases } from 'knex/lib/helpers'

import { TransactionClient } from '../TransactionClient'
import { RawQueryBuilder } from '../QueryBuilder/Raw'
import { InsertQueryBuilder } from '../QueryBuilder/Insert'
import { DatabaseQueryBuilder } from '../QueryBuilder/Database'

/**
* Query client exposes the API to fetch instance of different query builders
* to perform queries on a selection connection.
*/
export class QueryClient implements QueryClientContract {
/**
* Not a transaction client
*/
public isTransaction = false

/**
* The name of the dialect in use
*/
public dialect = resolveClientNameWithAliases(this._client.client.config)

constructor (private _client: knex, private _readClient?: knex) {
}

/**
* Returns the read client. The readClient is optional, since we can get
* an instance of [[QueryClient]] with a sticky write client.
*/
public getReadClient () {
return this._readClient || this._client
}

/**
* Returns the write client
*/
public getWriteClient () {
return this._client
}

/**
* Truncate table
*/
public async truncate (table: string): Promise<void> {
await this._client.select(table).truncate()
}

/**
* Get information for a table columns
*/
public async columnsInfo (table: string, column?: string): Promise<any> {
const query = this._client.select(table)
const result = await (column ? query.columnInfo(column) : query.columnInfo())
return result
}

/**
* Returns an instance of a transaction. Each transaction will
* query and hold a single connection for all queries.
*/
public async transaction (): Promise<TransactionClientContract> {
const trx = await this._client.transaction()
return new TransactionClient(trx, this.dialect)
}

/**
* Returns instance of a query builder for selecting, updating
* or deleting rows
*/
public query (): DatabaseQueryBuilderContract {
return new DatabaseQueryBuilder(this._client.queryBuilder(), this)
}

/**
* Returns instance of a query builder for inserting rows
*/
public insertQuery (): InsertQueryBuilderContract {
return new InsertQueryBuilder(this._client.queryBuilder(), this)
}

/**
* Returns instance of raw query builder
*/
public raw (sql: any, bindings?: any): RawContract {
return new RawQueryBuilder(this._client.raw(sql, bindings))
}

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

/**
* Returns instance of a query builder and selects the table
* for an insert query
*/
public table (table: any): InsertQueryBuilderContract {
return this.insertQuery().table(table)
}
}
7 changes: 5 additions & 2 deletions src/Transaction/index.ts → src/TransactionClient/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ import { DatabaseQueryBuilder } from '../QueryBuilder/Database'
* Transaction uses a dedicated connection from the connection pool
* and executes queries inside a given transaction.
*/
export class Transaction implements TransactionClientContract {
export class TransactionClient implements TransactionClientContract {
/**
* Always true
*/
public isTransaction: true = true

constructor (public knexClient: knex.Transaction, public dialect: string) {
Expand Down Expand Up @@ -91,7 +94,7 @@ export class Transaction implements TransactionClientContract {
*/
public async transaction (): Promise<TransactionClientContract> {
const trx = await this.knexClient.transaction()
return new Transaction(trx, this.dialect)
return new TransactionClient(trx, this.dialect)
}

/**
Expand Down

0 comments on commit d5a52c0

Please sign in to comment.