Skip to content

Commit

Permalink
feat: add support for profiling queries
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Jan 12, 2020
1 parent 233a7c8 commit d31e8a1
Show file tree
Hide file tree
Showing 13 changed files with 345 additions and 154 deletions.
2 changes: 2 additions & 0 deletions adonis-typings/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,8 @@ declare module '@ioc:Adonis/Addons/Database' {
client?: knex,
readClient?: knex,

hasReadWriteReplicas: boolean,

/**
* Read/write connection pools
*/
Expand Down
12 changes: 12 additions & 0 deletions adonis-typings/querybuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
declare module '@ioc:Adonis/Addons/DatabaseQueryBuilder' {
import * as knex from 'knex'
import { Dictionary } from 'ts-essentials'
import { ProfilerRowContract } from '@poppinss/profiler'

/**
* The types for values for the aggregates. We need this coz of
Expand Down Expand Up @@ -923,6 +924,11 @@ declare module '@ioc:Adonis/Addons/DatabaseQueryBuilder' {
* of query builder
*/
interface QueryClientContract {
/**
* Custom profiler to time queries
*/
profiler?: ProfilerRowContract

/**
* Tells if client is a transaction client or not
*/
Expand All @@ -938,6 +944,12 @@ declare module '@ioc:Adonis/Addons/DatabaseQueryBuilder' {
*/
mode: 'dual' | 'write' | 'read'

/**
* The name of the connnection from which the client
* was originated
*/
connectionName: string

/**
* Returns the read and write clients
*/
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"homepage": "https://github.com/adonisjs/adonis-lucid#readme",
"dependencies": {
"@poppinss/logger": "^1.1.2",
"@poppinss/profiler": "^1.0.0",
"@poppinss/utils": "^1.0.4",
"knex": "^0.19.1",
"knex-dynamic-connection": "^1.0.0",
Expand Down
39 changes: 14 additions & 25 deletions src/Connection/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@ export class Connection extends EventEmitter implements ConnectionContract {
*/
public readClient?: knex

/**
* A boolean to know if connection operates on read/write
* replicas
*/
public hasReadWriteReplicas: boolean = !!(
this.config.replicas &&
this.config.replicas.read &&
this.config.replicas.write
)

/**
* Config for one or more read replicas. Only exists, when replicas are
* defined
Expand Down Expand Up @@ -97,6 +107,7 @@ export class Connection extends EventEmitter implements ConnectionContract {
this.client = undefined
this.readClient = undefined
this._readReplicas = []
this._roundRobinCounter = 0
}

/**
Expand Down Expand Up @@ -126,15 +137,6 @@ export class Connection extends EventEmitter implements ConnectionContract {
}
}

/**
* Returns a boolean telling if config has read/write
* connection settings vs a single connection
* object.
*/
private _hasReadWriteReplicas (): boolean {
return !!(this.config.replicas && this.config.replicas.read && this.config.replicas.write)
}

/**
* Returns normalized config object for write replica to be
* used by knex
Expand Down Expand Up @@ -241,7 +243,7 @@ export class Connection extends EventEmitter implements ConnectionContract {
* it will use reference the write client
*/
private _setupReadConnection () {
if (!this._hasReadWriteReplicas()) {
if (!this.hasReadWriteReplicas) {
this.readClient = this.client
return
}
Expand Down Expand Up @@ -322,20 +324,7 @@ 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!)
}

if (mode === 'read') {
return new QueryClient('read', undefined, this.readClient!)
}

return new QueryClient('write', this.client!)
this._logger.trace({ connection: this.name }, 'creating query client in %s mode', [mode || 'dual'])
return new QueryClient(mode || 'dual', this)
}
}
49 changes: 39 additions & 10 deletions src/QueryBuilder/Database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
} from '@ioc:Adonis/Addons/DatabaseQueryBuilder'

import { Chainable } from './Chainable'
import { executeQuery } from '../utils'
import { executeQuery, isInTransaction } from '../utils'

/**
* Wrapping the user function for a query callback and give them
Expand Down Expand Up @@ -52,36 +52,58 @@ export class DatabaseQueryBuilder extends Chainable implements DatabaseQueryBuil
}
}

/**
* Raises exception when client is not defined
*/
private _ensureClient () {
if (!this._client) {
throw new Exception('Cannot execute query without query client', 500, 'E_PROGRAMMING_EXCEPTION')
}
}

/**
* Returns the client to be used for the query. This method relies on the
* query method and will choose the read or write connection whenever
* required.
*/
private _getQueryClient () {
/**
* Do not use custom client when knex builder is using transaction
* client
* Return undefined when no parent client is defined or dialect
* is sqlite
*/
if (this.$knexBuilder['client']['transacting']) {
if (this._client!.dialect === 'sqlite3') {
return
}

/**
* Return undefined when no parent client is defined or dialect
* is sqlite
* Use transaction client directly, since it preloads the
* connection
*/
if (!this._client || this._client.dialect === 'sqlite3') {
if (isInTransaction(this.$knexBuilder, this._client!)) {
return
}

/**
* Use write client for updates and deletes
*/
if (['update', 'del'].includes(this.$knexBuilder['_method'])) {
return this._client.getWriteClient().client
return this._client!.getWriteClient().client
}

return this._client!.getReadClient().client
}

/**
* Returns the profiler data
*/
private _getProfilerData () {
if (!this._client!.profiler) {
return {}
}

return this._client.getReadClient().client
return {
connection: this._client!.connectionName,
}
}

/**
Expand Down Expand Up @@ -269,7 +291,14 @@ export class DatabaseQueryBuilder extends Chainable implements DatabaseQueryBuil
* Executes the query
*/
public async exec (): Promise<any> {
const result = await executeQuery(this.$knexBuilder, this._getQueryClient())
this._ensureClient()

const result = await executeQuery(
this.$knexBuilder,
this._getQueryClient(),
this._client!.profiler,
this._getProfilerData(),
)
return result
}

Expand Down
49 changes: 40 additions & 9 deletions src/QueryBuilder/Insert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
/// <reference path="../../adonis-typings/database.ts" />

import * as knex from 'knex'
import { Exception } from '@poppinss/utils'

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

import { executeQuery } from '../utils'
import { executeQuery, isInTransaction } from '../utils'

/**
* Exposes the API for performing SQL inserts
Expand All @@ -25,6 +27,15 @@ export class InsertQueryBuilder implements InsertQueryBuilderContract {
constructor (protected $knexBuilder: knex.QueryBuilder, private _client?: QueryClientContract) {
}

/**
* Raises exception when client is not defined
*/
private _ensureClient () {
if (!this._client) {
throw new Exception('Cannot execute query without query client', 500, 'E_PROGRAMMING_EXCEPTION')
}
}

/**
* Returns the client to be used for the query. Even though the insert query
* is always using the `write` client, we still go through the process of
Expand All @@ -33,25 +44,38 @@ export class InsertQueryBuilder implements InsertQueryBuilderContract {
*/
private _getQueryClient () {
/**
* Do not use custom client when knex builder is using transaction
* client
* Return undefined when no parent client is defined or dialect
* is sqlite
*/
if (this.$knexBuilder['client']['transacting']) {
if (this._client!.dialect === 'sqlite3') {
return
}

/**
* Return undefined when no parent client is defined or dialect
* is sqlite
* Use transaction client directly, since it preloads the
* connection
*/
if (!this._client || this._client.dialect === 'sqlite3') {
if (isInTransaction(this.$knexBuilder, this._client!)) {
return
}

/**
* Always use write client for write queries
*/
return this._client.getWriteClient().client
return this._client!.getWriteClient().client
}

/**
* Returns the profiler data
*/
private _getProfilerData () {
if (!this._client!.profiler) {
return {}
}

return {
connection: this._client!.connectionName,
}
}

/**
Expand Down Expand Up @@ -127,7 +151,14 @@ export class InsertQueryBuilder implements InsertQueryBuilderContract {
* Executes the query
*/
public async exec (): Promise<any> {
const result = await executeQuery(this.$knexBuilder, this._getQueryClient())
this._ensureClient()

const result = await executeQuery(
this.$knexBuilder,
this._getQueryClient(),
this._client!.profiler,
this._getProfilerData(),
)
return result
}

Expand Down
42 changes: 39 additions & 3 deletions src/QueryBuilder/Raw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,42 @@
/// <reference path="../../adonis-typings/database.ts" />

import * as knex from 'knex'
import { RawContract, TransactionClientContract } from '@ioc:Adonis/Addons/DatabaseQueryBuilder'
import { Exception } from '@poppinss/utils'

import {
RawContract,
TransactionClientContract,
QueryClientContract,
} from '@ioc:Adonis/Addons/DatabaseQueryBuilder'
import { executeQuery } from '../utils'

/**
* Exposes the API to execute raw queries
*/
export class RawQueryBuilder implements RawContract {
constructor (protected $knexBuilder: knex.Raw) {
constructor (protected $knexBuilder: knex.Raw, private _client?: QueryClientContract) {
}

/**
* Raises exception when client is not defined
*/
private _ensureClient () {
if (!this._client) {
throw new Exception('Cannot execute query without query client', 500, 'E_PROGRAMMING_EXCEPTION')
}
}

/**
* Returns the profiler data
*/
private _getProfilerData () {
if (!this._client!.profiler) {
return {}
}

return {
connection: this._client!.connectionName,
}
}

/**
Expand Down Expand Up @@ -69,7 +98,14 @@ export class RawQueryBuilder implements RawContract {
* Executes the query
*/
public async exec (): Promise<any> {
const result = await this.$knexBuilder
this._ensureClient()

const result = await executeQuery(
this.$knexBuilder,
undefined,
this._client!.profiler,
this._getProfilerData(),
)
return result
}

Expand Down
Loading

0 comments on commit d31e8a1

Please sign in to comment.