From e782f28881f45bf9f56bbeafdd2f1a0b0d5a76bf Mon Sep 17 00:00:00 2001 From: Harminder virk Date: Sun, 1 Sep 2019 21:14:53 +0530 Subject: [PATCH] refactor: cleaning up and using traits to keep the code DRY --- adonis-typings/database.ts | 234 ++++++++++++++++++++--- adonis-typings/querybuilder.ts | 227 ++++++---------------- package.json | 1 + src/Connection/Manager.ts | 4 +- src/Connection/index.ts | 2 +- src/Database/QueryBuilder/Chainable.ts | 12 +- src/Database/QueryBuilder/Database.ts | 173 +++-------------- src/Database/QueryBuilder/Insert.ts | 139 +------------- src/Database/QueryBuilder/Raw.ts | 118 ++---------- src/Database/QueryClient/index.ts | 42 ++-- src/Database/Traits/Executable.ts | 223 ++++++++++++++++++++++ src/Database/TransactionClient/index.ts | 18 +- src/Database/index.ts | 20 +- src/utils/index.ts | 242 ++++++++++++------------ test-helpers/index.ts | 15 +- test/connection.spec.ts | 2 +- test/query-builder.spec.ts | 40 ++-- tsconfig.json | 5 +- 18 files changed, 724 insertions(+), 793 deletions(-) create mode 100644 src/Database/Traits/Executable.ts diff --git a/adonis-typings/database.ts b/adonis-typings/database.ts index f503a14b..dd8a768f 100644 --- a/adonis-typings/database.ts +++ b/adonis-typings/database.ts @@ -9,20 +9,151 @@ /// -declare module '@ioc:Adonis/Addons/Database' { +declare module '@ioc:Adonis/Lucid/Database' { import { Pool } from 'tarn' import * as knex from 'knex' import { EventEmitter } from 'events' - import { ProfilerRowContract } from '@poppinss/profiler' + import { Dictionary } from 'ts-essentials' + import { ProfilerRowContract, ProfilerContract } from '@poppinss/profiler' import { RawContract, - RawBuilderContract, - QueryClientContract, - TransactionClientContract, - InsertQueryBuilderContract, DatabaseQueryBuilderContract, - } from '@ioc:Adonis/Addons/DatabaseQueryBuilder' + InsertQueryBuilderContract, + StrictValuesWithoutRaw, + SelectTable, + Table, + } from '@ioc:Adonis/Lucid/DatabaseQueryBuilder' + + /** + * A executable query builder will always have these methods on it. + */ + interface ExcutableQueryBuilderContract extends Promise { + debug (debug: boolean): this + timeout (time: number, options?: { cancel: boolean }): this + useTransaction (trx: TransactionClientContract): this + toQuery (): string + exec (): Promise + toSQL (): knex.Sql + } + + /** + * Shape of the query client, that is used to retrive instances + * of query builder + */ + interface QueryClientContract { + /** + * Custom profiler to time queries + */ + profiler?: ProfilerRowContract | ProfilerContract + + /** + * Tells if client is a transaction client or not + */ + isTransaction: boolean + + /** + * The database dialect in use + */ + dialect: string + + /** + * The client mode in which it is execute queries + */ + mode: 'dual' | 'write' | 'read' + + /** + * The name of the connnection from which the client + * was originated + */ + connectionName: string + + /** + * Returns the read and write clients + */ + getReadClient (): knex | knex.Transaction + getWriteClient (): knex | knex.Transaction + + /** + * Get new query builder instance for select, update and + * delete calls + */ + query< + Record extends any = any, + Result extends any = any, + > (): DatabaseQueryBuilderContract & ExcutableQueryBuilderContract, + + /** + * Get new query builder instance inserts + */ + insertQuery< + Record extends any = any, + ReturnColumns extends any = any[] + > (): InsertQueryBuilderContract & ExcutableQueryBuilderContract, + + /** + * Get raw query builder instance + */ + raw< + Result extends any = any + > ( + sql: string, + bindings?: { [key: string]: StrictValuesWithoutRaw } | StrictValuesWithoutRaw, + ): RawContract & ExcutableQueryBuilderContract + + /** + * Truncate a given table + */ + truncate (table: string): Promise, + + /** + * Returns columns info for a given table + */ + columnsInfo (table: string): Promise<{ [column: string]: knex.ColumnInfo }>, + columnsInfo (table: string, column: string): Promise, + + /** + * Same as `query()`, but also selects the table for the query. The `from` method + * doesn't allow defining the return type and one must use `query` to define + * that. + */ + from: SelectTable>, + + /** + * Same as `insertQuery()`, but also selects the table for the query. The `table` + * method doesn't allow defining the return type and one must use `insertQuery` + * to define that. + */ + table: Table>, + + /** + * Get instance of transaction client + */ + transaction (): Promise, + } + + /** + * The shape of transaction client to run queries under a given + * transaction on a single connection + */ + interface TransactionClientContract extends QueryClientContract { + knexClient: knex.Transaction, + + /** + * Is transaction completed or not + */ + isCompleted: boolean, + + /** + * Commit transaction + */ + commit (): Promise, + + /** + * Rollback transaction + */ + rollback (): Promise + } /** * Connection node used by majority of database @@ -338,6 +469,9 @@ declare module '@ioc:Adonis/Addons/Database' { client?: knex, readClient?: knex, + /** + * Property to find if explicit read/write is enabled + */ hasReadWriteReplicas: boolean, /** @@ -381,36 +515,86 @@ declare module '@ioc:Adonis/Addons/Database' { getClient (mode?: 'write' | 'read'): QueryClientContract, } + type DatabaseClientOptions = Partial<{ + mode: 'read' | 'write', + profiler: ProfilerRowContract | ProfilerContract, + }> + /** * Database contract serves as the main API to interact with multiple * database connections */ export interface DatabaseContract { + /** + * Name of the primary connection defined inside `config/database.ts` + * file + */ primaryConnectionName: string, - getRawConnection: ConnectionManagerContract['get'] + + /** + * Reference to the connection manager + */ manager: ConnectionManagerContract, - connection ( - connectionName: string, - options?: Partial<{ mode: 'read' | 'write', profiler: ProfilerRowContract }>, - ): QueryClientContract + /** + * Returns the raw connection instance + */ + getRawConnection: ConnectionManagerContract['get'], - query ( - options?: Partial<{ mode: 'read' | 'write', profiler: ProfilerRowContract }>, - ): DatabaseQueryBuilderContract + /** + * Get query client for a given connection. Optionally one can also define + * the mode of the connection and profiler row + */ + connection (connectionName: string, options?: DatabaseClientOptions): QueryClientContract - insertQuery ( - options?: Partial<{ profiler: ProfilerRowContract }>, - ): InsertQueryBuilderContract + /** + * Get query builder instance for a given connection. + */ + query< + Record extends any = any, + Result extends any = any, + > ( + options?: DatabaseClientOptions, + ): DatabaseQueryBuilderContract & ExcutableQueryBuilderContract, - from (table: string): DatabaseQueryBuilderContract - table (table: string): InsertQueryBuilderContract - transaction (): Promise + /** + * Get insert query builder instance for a given connection. + */ + insertQuery< + Record extends any = any, + ReturnColumns extends any = any + > ( + options?: DatabaseClientOptions, + ): InsertQueryBuilderContract & ExcutableQueryBuilderContract, - raw ( + /** + * Get raw query builder instance + */ + raw< + Result extends any = any + > ( sql: string, - bindings?: any, - options?: Partial<{ mode: 'read' | 'write', profiler: ProfilerRowContract }>, - ): RawContract + bindings?: { [key: string]: StrictValuesWithoutRaw } | StrictValuesWithoutRaw[], + options?: DatabaseClientOptions, + ): RawContract & ExcutableQueryBuilderContract + + /** + * Selects a table on the default connection by instantiating a new query + * builder instance. This method provides no control over the client + * mode and one must use `query` for that + */ + from: QueryClientContract['from'] + + /** + * Selects a table on the default connection by instantiating a new query + * builder instance. This method provides no control over the client + * mode and one must use `insertQuery` for that + */ + table: QueryClientContract['table'] + + /** + * Start a new transaction + */ + transaction (): Promise } } diff --git a/adonis-typings/querybuilder.ts b/adonis-typings/querybuilder.ts index c7705984..b4353624 100644 --- a/adonis-typings/querybuilder.ts +++ b/adonis-typings/querybuilder.ts @@ -7,7 +7,7 @@ * file that was distributed with this source code. */ -declare module '@ioc:Adonis/Addons/DatabaseQueryBuilder' { +declare module '@ioc:Adonis/Lucid/DatabaseQueryBuilder' { import * as knex from 'knex' import { Dictionary } from 'ts-essentials' import { ProfilerRowContract, ProfilerContract } from '@poppinss/profiler' @@ -35,7 +35,7 @@ declare module '@ioc:Adonis/Addons/DatabaseQueryBuilder' { * A known set of values allowed when defining values for different * clauses */ - type StrictValues = + export type StrictValues = | string | number | boolean @@ -47,24 +47,16 @@ declare module '@ioc:Adonis/Addons/DatabaseQueryBuilder' { | Buffer | RawContract - /** - * Shape of raw query builder. The builder is a method used to build - * raw queries. - */ - interface RawBuilderContract { - (sql: string): RawContract - (sql: string, bindings: { [key: string]: Exclude }): RawContract - (sql: string, bindings: Exclude[]): RawContract - } + export type StrictValuesWithoutRaw = Exclude /** * A builder method to allow raw queries. However, the return type is the * instance of current query builder. This is used for `.{verb}Raw` methods. */ - interface RawQueryBuilderContract { + interface RawQueryFn { (sql: string): Builder - (sql: string, bindings: { [key: string]: Exclude }): Builder - (sql: string, bindings: Exclude[]): Builder + (sql: string, bindings: { [key: string]: StrictValuesWithoutRaw }): Builder + (sql: string, bindings: StrictValuesWithoutRaw[]): Builder (sql: RawContract): Builder } @@ -375,8 +367,8 @@ declare module '@ioc:Adonis/Addons/DatabaseQueryBuilder' { * result set. Unlike knex, we force defining aliases for each aggregate. */ interface Aggregate < + Builder extends ChainableContract, Record extends Dictionary, - Result extends any, > { /** * Accepting a typed column with the alias for the count. Unlike knex @@ -386,7 +378,7 @@ declare module '@ioc:Adonis/Addons/DatabaseQueryBuilder' { ( column: OneOrMany, alias: Alias, - ): DatabaseQueryBuilderContract> + ): Builder /** * Accepting an object for multiple counts in a single query. Again @@ -398,7 +390,7 @@ declare module '@ioc:Adonis/Addons/DatabaseQueryBuilder' { Columns extends Dictionary, Alias>, >( columns: Columns, - ): DatabaseQueryBuilderContract + ): Builder /** * Accepting an un typed column with the alias for the count. @@ -406,7 +398,7 @@ declare module '@ioc:Adonis/Addons/DatabaseQueryBuilder' { ( column: OneOrMany>, alias: Alias, - ): DatabaseQueryBuilderContract> + ): Builder /** * Accepting an object for multiple counts in a single query. Again @@ -417,7 +409,7 @@ declare module '@ioc:Adonis/Addons/DatabaseQueryBuilder' { Columns extends Dictionary>, Alias>, >( columns: Columns, - ): DatabaseQueryBuilderContract + ): Builder } /** @@ -673,18 +665,6 @@ declare module '@ioc:Adonis/Addons/DatabaseQueryBuilder' { (values: { [P in K]: number }): Builder } - /** - * A executable query builder will always have these methods on it. - */ - interface ExcutableQueryBuilderContract extends Promise { - debug (debug: boolean): this - timeout (time: number, options?: { cancel: boolean }): this - useTransaction (trx: TransactionClientContract): this - toQuery (): string - exec (): Promise - toSQL (): knex.Sql - } - /** * Possible signatures for an insert query */ @@ -714,6 +694,7 @@ declare module '@ioc:Adonis/Addons/DatabaseQueryBuilder' { Record extends Dictionary = Dictionary, > { from: SelectTable + select: DatabaseQueryBuilderSelect where: Where orWhere: Where @@ -755,9 +736,9 @@ declare module '@ioc:Adonis/Addons/DatabaseQueryBuilder' { orWhereNotBetween: WhereBetween andWhereNotBetween: WhereBetween - whereRaw: RawQueryBuilderContract - orWhereRaw: RawQueryBuilderContract - andWhereRaw: RawQueryBuilderContract + whereRaw: RawQueryFn + orWhereRaw: RawQueryFn + andWhereRaw: RawQueryFn join: Join innerJoin: Join @@ -767,7 +748,7 @@ declare module '@ioc:Adonis/Addons/DatabaseQueryBuilder' { rightOuterJoin: Join fullOuterJoin: Join crossJoin: Join - joinRaw: RawQueryBuilderContract + joinRaw: RawQueryFn having: Having orHaving: Having @@ -805,17 +786,17 @@ declare module '@ioc:Adonis/Addons/DatabaseQueryBuilder' { orHavingNotBetween: HavingBetween andHavingNotBetween: HavingBetween - havingRaw: RawQueryBuilderContract - orHavingRaw: RawQueryBuilderContract - andHavingRaw: RawQueryBuilderContract + havingRaw: RawQueryFn + orHavingRaw: RawQueryFn + andHavingRaw: RawQueryFn distinct: Distinct groupBy: GroupBy - groupByRaw: RawQueryBuilderContract + groupByRaw: RawQueryFn orderBy: OrderBy - orderByRaw: RawQueryBuilderContract + orderByRaw: RawQueryFn union: Union unionAll: UnionAll @@ -844,30 +825,21 @@ declare module '@ioc:Adonis/Addons/DatabaseQueryBuilder' { } /** - * Database query builder interface. Some of the methods on the Database - * query builder and Model query builder will behave differently. + * Shape of the raw query that can also be passed as a value to + * other queries */ + interface RawContract { + wrap (before: string, after: string): this + } + + /** + * Database query builder interface. It will use the `Executable` trait + * and hence must be typed properly for that. + */ export interface DatabaseQueryBuilderContract < Record extends Dictionary = Dictionary, Result extends any = Record, - > extends ChainableContract, ExcutableQueryBuilderContract { - select: DatabaseQueryBuilderSelect - - /** - * Aggregates - */ - count: Aggregate - countDistinct: Aggregate - min: Aggregate - max: Aggregate - sum: Aggregate - avg: Aggregate - avgDistinct: Aggregate - - returning: Returning - update: Update - increment: Counter - decrement: Counter + > extends ChainableContract { del (): this /** @@ -882,15 +854,34 @@ declare module '@ioc:Adonis/Addons/DatabaseQueryBuilder' { * Execute and get first result */ first (): Promise + + /** + * Aggregates + */ + count: Aggregate + countDistinct: Aggregate + min: Aggregate + max: Aggregate + sum: Aggregate + avg: Aggregate + avgDistinct: Aggregate + + /** + * Mutations + */ + update: Update + increment: Counter + decrement: Counter } /** - * Insert query builder to perform database inserts + * Insert query builder to perform database inserts. It will use the + * `Executable` trait and hence must be typed property for that. */ export interface InsertQueryBuilderContract< Record extends Dictionary = Dictionary, - ReturnColumns extends any[] = any[] - > extends ExcutableQueryBuilderContract { + ReturnColumns extends any = any[] + > { /** * Table for the insert query */ @@ -911,114 +902,4 @@ declare module '@ioc:Adonis/Addons/DatabaseQueryBuilder' { */ multiInsert: MultiInsert } - - /** - * Shape of raw query instance - */ - interface RawContract extends ExcutableQueryBuilderContract { - wrap (before: string, after: string): this - } - - /** - * Shape of the query client, that is used to retrive instances - * of query builder - */ - interface QueryClientContract { - /** - * Custom profiler to time queries - */ - profiler?: ProfilerRowContract | ProfilerContract - - /** - * Tells if client is a transaction client or not - */ - isTransaction: boolean - - /** - * The database dialect in use - */ - dialect: string - - /** - * The client mode in which it is execute queries - */ - mode: 'dual' | 'write' | 'read' - - /** - * The name of the connnection from which the client - * was originated - */ - connectionName: string - - /** - * Returns the read and write clients - */ - getReadClient (): knex | knex.Transaction - getWriteClient (): knex | knex.Transaction - - /** - * Get new query builder instance for select, update and - * delete calls - */ - query (): DatabaseQueryBuilderContract, - - /** - * Get new query builder instance inserts - */ - insertQuery (): InsertQueryBuilderContract, - - /** - * Get raw query builder instance - */ - raw: RawBuilderContract, - - /** - * Truncate a given table - */ - truncate (table: string): Promise, - - /** - * Returns columns info for a given table - */ - columnsInfo (table: string): Promise<{ [column: string]: knex.ColumnInfo }>, - columnsInfo (table: string, column: string): Promise, - - /** - * Same as `query()`, but also selects the table for the query - */ - from: DatabaseQueryBuilderContract['from'], - - /** - * Same as `insertQuery()`, but also selects the table for the query - */ - table: InsertQueryBuilderContract['table'], - - /** - * Get instance of transaction client - */ - transaction (): Promise, - } - - /** - * The shape of transaction client to run queries under a given - * transaction on a single connection - */ - interface TransactionClientContract extends QueryClientContract { - knexClient: knex.Transaction, - - /** - * Is transaction completed or not - */ - isCompleted: boolean, - - /** - * Commit transaction - */ - commit (): Promise, - - /** - * Rollback transaction - */ - rollback (): Promise - } } diff --git a/package.json b/package.json index 5f4cd0f5..e0952e0b 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "dependencies": { "@poppinss/logger": "^1.1.2", "@poppinss/profiler": "^1.1.0", + "@poppinss/traits": "^1.0.0", "@poppinss/utils": "^1.0.4", "knex": "^0.19.2", "knex-dynamic-connection": "^1.0.0", diff --git a/src/Connection/Manager.ts b/src/Connection/Manager.ts index 3edb6ef8..dcb9bec3 100644 --- a/src/Connection/Manager.ts +++ b/src/Connection/Manager.ts @@ -14,10 +14,10 @@ import { Exception } from '@poppinss/utils' import { LoggerContract } from '@poppinss/logger' import { - ConnectionConfigContract, ConnectionContract, + ConnectionConfigContract, ConnectionManagerContract, -} from '@ioc:Adonis/Addons/Database' +} from '@ioc:Adonis/Lucid/Database' import { Connection } from './index' diff --git a/src/Connection/index.ts b/src/Connection/index.ts index f122a8eb..a3dc19af 100644 --- a/src/Connection/index.ts +++ b/src/Connection/index.ts @@ -15,8 +15,8 @@ import { EventEmitter } from 'events' import { Exception } from '@poppinss/utils' import { LoggerContract } from '@poppinss/logger' import { patchKnex } from 'knex-dynamic-connection' +import { ConnectionConfigContract, ConnectionContract } from '@ioc:Adonis/Lucid/Database' -import { ConnectionConfigContract, ConnectionContract } from '@ioc:Adonis/Addons/Database' import { QueryClient } from '../Database/QueryClient' /** diff --git a/src/Database/QueryBuilder/Chainable.ts b/src/Database/QueryBuilder/Chainable.ts index 1dd6c3d5..045a205e 100644 --- a/src/Database/QueryBuilder/Chainable.ts +++ b/src/Database/QueryBuilder/Chainable.ts @@ -10,7 +10,7 @@ /// import * as knex from 'knex' -import { ChainableContract, QueryCallback } from '@ioc:Adonis/Addons/DatabaseQueryBuilder' +import { ChainableContract, QueryCallback } from '@ioc:Adonis/Lucid/DatabaseQueryBuilder' import { RawQueryBuilder } from './Raw' @@ -29,7 +29,7 @@ type DBQueryCallback = (userFn: QueryCallback) => ((builder: */ export abstract class Chainable implements ChainableContract { constructor ( - protected $knexBuilder: knex.QueryBuilder, + public $knexBuilder: knex.QueryBuilder, // Needs to be public for Executable trait private _queryCallback: DBQueryCallback, ) {} @@ -90,6 +90,14 @@ export abstract class Chainable implements ChainableContract { return value } + /** + * Define columns for selection + */ + public select (): this { + this.$knexBuilder.select(...arguments) + return this + } + /** * Select table for the query. Re-calling this method multiple times will * use the last selected table diff --git a/src/Database/QueryBuilder/Database.ts b/src/Database/QueryBuilder/Database.ts index 797dd6db..abfab2b2 100644 --- a/src/Database/QueryBuilder/Database.ts +++ b/src/Database/QueryBuilder/Database.ts @@ -11,16 +11,17 @@ import * as knex from 'knex' import { Exception } from '@poppinss/utils' +import { trait } from '@poppinss/traits' import { DatabaseQueryBuilderContract, QueryCallback, - TransactionClientContract, - QueryClientContract, - } from '@ioc:Adonis/Addons/DatabaseQueryBuilder' +} from '@ioc:Adonis/Lucid/DatabaseQueryBuilder' + +import { QueryClientContract } from '@ioc:Adonis/Lucid/Database' import { Chainable } from './Chainable' -import { executeQuery, isInTransaction } from '../../utils' +import { Executable, ExecutableConstrutor } from '../Traits/Executable' /** * Wrapping the user function for a query callback and give them @@ -37,8 +38,9 @@ function queryCallback (userFn: QueryCallback) { * Database query builder exposes the API to construct and run queries for selecting, * updating and deleting records. */ +@trait(Executable) export class DatabaseQueryBuilder extends Chainable implements DatabaseQueryBuilderContract { - constructor (builder: knex.QueryBuilder, private _client?: QueryClientContract) { + constructor (builder: knex.QueryBuilder, public client?: QueryClientContract) { super(builder, queryCallback) } @@ -47,65 +49,11 @@ export class DatabaseQueryBuilder extends Chainable implements DatabaseQueryBuil * client */ private _ensureCanPerformWrites () { - if (this._client && this._client.mode === 'read') { + if (this.client && this.client.mode === 'read') { throw new Exception('Updates and deletes cannot be performed in read mode') } } - /** - * 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 () { - /** - * Return undefined when no parent client is defined or dialect - * is sqlite - */ - if (this._client!.dialect === 'sqlite3') { - return - } - - /** - * Use transaction client directly, since it preloads the - * connection - */ - 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!.getReadClient().client - } - - /** - * Returns the profiler data - */ - private _getProfilerData () { - if (!this._client!.profiler) { - return {} - } - - return { - connection: this._client!.connectionName, - } - } - /** * Normalizes the columns aggregates functions to something * knex can process. @@ -125,14 +73,6 @@ export class DatabaseQueryBuilder extends Chainable implements DatabaseQueryBuil return { [alias]: this.$transformValue(columns) } } - /** - * Define columns for selection - */ - public select (): this { - this.$knexBuilder.select(...arguments) - return this - } - /** * Count rows for the current query */ @@ -220,7 +160,7 @@ export class DatabaseQueryBuilder extends Chainable implements DatabaseQueryBuil * Clone the current query builder */ public clone (): DatabaseQueryBuilder { - return new DatabaseQueryBuilder(this.$knexBuilder.clone(), this._client) + return new DatabaseQueryBuilder(this.$knexBuilder.clone(), this.client) } /** @@ -230,7 +170,7 @@ export class DatabaseQueryBuilder extends Chainable implements DatabaseQueryBuil /** * Do not chain `returning` in sqlite3 to avoid knex warnings */ - if (this._client && this._client.dialect === 'sqlite3') { + if (this.client && this.client.dialect === 'sqlite3') { return this } @@ -252,88 +192,27 @@ export class DatabaseQueryBuilder extends Chainable implements DatabaseQueryBuil * will implicitly set a `limit` on the query */ public async first (): Promise { - const result = await this.limit(1).exec() + const result = await this.limit(1)['exec']() return result[0] || null } /** - * Required when Promises are extended - */ - public get [Symbol.toStringTag] () { - return this.constructor.name - } - - /** - * Turn on/off debugging for this query - */ - public debug (debug: boolean): this { - this.$knexBuilder.debug(debug) - return this - } - - /** - * Define query timeout - */ - public timeout (time: number, options?: { cancel: boolean }): this { - this.$knexBuilder['timeout'](time, options) - return this - } - - /** - * Use transaction connection - */ - public useTransaction (trx: TransactionClientContract): this { - this.$knexBuilder.transacting(trx.knexClient) - return this - } - - /** - * Returns SQL query as a string - */ - public toQuery (): string { - return this.$knexBuilder.toQuery() - } - - /** - * Executes the query - */ - public async exec (): Promise { - this._ensureClient() - - const result = await executeQuery( - this.$knexBuilder, - this._getQueryClient(), - this._client!.profiler, - this._getProfilerData(), - ) - return result - } - - /** - * Get sql representation of the query - */ - public toSQL (): knex.Sql { - return this.$knexBuilder.toSQL() - } - - /** - * Implementation of `then` for the promise API - */ - public then (resolve: any, reject?: any): any { - return this.exec().then(resolve, reject) - } - - /** - * Implementation of `catch` for the promise API + * 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. + * + * This method is invoked by the `Executable` Trait, only when actually + * query isn't using the transaction */ - public catch (reject: any): any { - return this.exec().catch(reject) - } + public getQueryClient () { + /** + * Use write client for updates and deletes + */ + if (['update', 'del'].includes(this.$knexBuilder['_method'])) { + this._ensureCanPerformWrites() + return this.client!.getWriteClient().client + } - /** - * Implementation of `finally` for the promise API - */ - public finally (fullfilled: any) { - return this.exec().finally(fullfilled) + return this.client!.getReadClient().client } } diff --git a/src/Database/QueryBuilder/Insert.ts b/src/Database/QueryBuilder/Insert.ts index 2be5b129..70ed24cf 100644 --- a/src/Database/QueryBuilder/Insert.ts +++ b/src/Database/QueryBuilder/Insert.ts @@ -10,30 +10,19 @@ /// import * as knex from 'knex' -import { Exception } from '@poppinss/utils' +import { trait } from '@poppinss/traits' -import { - InsertQueryBuilderContract, - TransactionClientContract, - QueryClientContract, -} from '@ioc:Adonis/Addons/DatabaseQueryBuilder' +import { QueryClientContract } from '@ioc:Adonis/Lucid/Database' +import { InsertQueryBuilderContract } from '@ioc:Adonis/Lucid/DatabaseQueryBuilder' -import { executeQuery, isInTransaction } from '../../utils' +import { Executable, ExecutableConstrutor } from '../Traits/Executable' /** * Exposes the API for performing SQL inserts */ +@trait(Executable) 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') - } + constructor (public $knexBuilder: knex.QueryBuilder, public client?: QueryClientContract) { } /** @@ -42,40 +31,11 @@ export class InsertQueryBuilder implements InsertQueryBuilderContract { * self defining the connection, so that we can discover any bugs during * this process. */ - private _getQueryClient () { - /** - * Return undefined when no parent client is defined or dialect - * is sqlite - */ - if (this._client!.dialect === 'sqlite3') { - return - } - - /** - * Use transaction client directly, since it preloads the - * connection - */ - if (isInTransaction(this.$knexBuilder, this._client!)) { - return - } - + public getQueryClient () { /** * Always use write client for write queries */ - return this._client!.getWriteClient().client - } - - /** - * Returns the profiler data - */ - private _getProfilerData () { - if (!this._client!.profiler) { - return {} - } - - return { - connection: this._client!.connectionName, - } + return this.client!.getWriteClient().client } /** @@ -93,7 +53,7 @@ export class InsertQueryBuilder implements InsertQueryBuilderContract { /** * Do not chain `returning` in sqlite3 to avoid knex warnings */ - if (this._client && this._client.dialect === 'sqlite3') { + if (this.client && this.client.dialect === 'sqlite3') { return this } @@ -115,85 +75,4 @@ export class InsertQueryBuilder implements InsertQueryBuilderContract { public multiInsert (columns: any): this { return this.insert(columns) } - - /** - * Required when Promises are extended - */ - public get [Symbol.toStringTag] () { - return this.constructor.name - } - - /** - * Turn on/off debugging for this query - */ - public debug (debug: boolean): this { - this.$knexBuilder.debug(debug) - return this - } - - /** - * Define query timeout - */ - public timeout (time: number, options?: { cancel: boolean }): this { - this.$knexBuilder['timeout'](time, options) - return this - } - - /** - * Use transaction connection - */ - public useTransaction (trx: TransactionClientContract): this { - this.$knexBuilder.transacting(trx.knexClient) - return this - } - - /** - * Returns SQL query as a string - */ - public toQuery (): string { - return this.$knexBuilder.toQuery() - } - - /** - * Executes the query - */ - public async exec (): Promise { - this._ensureClient() - - const result = await executeQuery( - this.$knexBuilder, - this._getQueryClient(), - this._client!.profiler, - this._getProfilerData(), - ) - return result - } - - /** - * Get sql representation of the query - */ - public toSQL (): knex.Sql { - return this.$knexBuilder.toSQL() - } - - /** - * Implementation of `then` for the promise API - */ - public then (resolve: any, reject?: any): any { - return this.exec().then(resolve, reject) - } - - /** - * Implementation of `catch` for the promise API - */ - public catch (reject: any): any { - return this.exec().catch(reject) - } - - /** - * Implementation of `finally` for the promise API - */ - public finally (fullfilled: any) { - return this.exec().finally(fullfilled) - } } diff --git a/src/Database/QueryBuilder/Raw.ts b/src/Database/QueryBuilder/Raw.ts index 69e54a40..d779b4be 100644 --- a/src/Database/QueryBuilder/Raw.ts +++ b/src/Database/QueryBuilder/Raw.ts @@ -10,42 +10,27 @@ /// import * as knex from 'knex' -import { Exception } from '@poppinss/utils' +import { trait } from '@poppinss/traits' -import { - RawContract, - TransactionClientContract, - QueryClientContract, -} from '@ioc:Adonis/Addons/DatabaseQueryBuilder' -import { executeQuery } from '../../utils' +import { QueryClientContract } from '@ioc:Adonis/Lucid/Database' +import { RawContract } from '@ioc:Adonis/Lucid/DatabaseQueryBuilder' + +import { Executable, ExecutableConstrutor } from '../Traits/Executable' /** * Exposes the API to execute raw queries */ +@trait(Executable) export class RawQueryBuilder implements RawContract { - constructor (protected $knexBuilder: knex.Raw, private _client?: QueryClientContract) { + constructor (public $knexBuilder: knex.Raw, public client?: QueryClientContract) { } /** - * Raises exception when client is not defined + * It's impossible to judge the client for the raw query and + * hence we always use the default client */ - 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, - } + public getQueryClient () { + return undefined } /** @@ -55,85 +40,4 @@ export class RawQueryBuilder implements RawContract { this.$knexBuilder.wrap(before, after) return this } - - /** - * Required when Promises are extended - */ - public get [Symbol.toStringTag] () { - return this.constructor.name - } - - /** - * Turn on/off debugging for this query - */ - public debug (debug: boolean): this { - this.$knexBuilder.debug(debug) - return this - } - - /** - * Define query timeout - */ - public timeout (time: number, options?: { cancel: boolean }): this { - this.$knexBuilder['timeout'](time, options) - return this - } - - /** - * Use transaction connection - */ - public useTransaction (trx: TransactionClientContract): this { - this.$knexBuilder.transacting(trx.knexClient) - return this - } - - /** - * Returns SQL query as a string - */ - public toQuery (): string { - return this.$knexBuilder.toQuery() - } - - /** - * Executes the query - */ - public async exec (): Promise { - this._ensureClient() - - const result = await executeQuery( - this.$knexBuilder, - undefined, - this._client!.profiler, - this._getProfilerData(), - ) - return result - } - - /** - * Get sql representation of the query - */ - public toSQL (): knex.Sql { - return this.$knexBuilder.toSQL() - } - - /** - * Implementation of `then` for the promise API - */ - public then (resolve: any, reject?: any): any { - return this.exec().then(resolve, reject) - } - - /** - * Implementation of `catch` for the promise API - */ - public catch (reject: any): any { - return this.exec().catch(reject) - } - - /** - * Implementation of `finally` for the promise API - */ - public finally (fullfilled: any) { - return this.exec().finally(fullfilled) - } } diff --git a/src/Database/QueryClient/index.ts b/src/Database/QueryClient/index.ts index 35c42fcd..ffa27d52 100644 --- a/src/Database/QueryClient/index.ts +++ b/src/Database/QueryClient/index.ts @@ -11,27 +11,26 @@ import * as knex from 'knex' import { Exception } from '@poppinss/utils' -import { ProfilerRowContract, ProfilerContract } from '@poppinss/profiler' import { resolveClientNameWithAliases } from 'knex/lib/helpers' +import { ProfilerRowContract, ProfilerContract } from '@poppinss/profiler' import { - RawContract, + ConnectionContract, QueryClientContract, TransactionClientContract, - InsertQueryBuilderContract, - DatabaseQueryBuilderContract, -} from '@ioc:Adonis/Addons/DatabaseQueryBuilder' - -import { ConnectionContract } from '@ioc:Adonis/Addons/Database' +} from '@ioc:Adonis/Lucid/Database' -import { TransactionClient } from '../TransactionClient' import { RawQueryBuilder } from '../QueryBuilder/Raw' +import { TransactionClient } from '../TransactionClient' 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. + * + * Many of the methods returns `any`, since this class is type casted to an interface, + * it doesn't real matter what are the return types from this class */ export class QueryClient implements QueryClientContract { /** @@ -58,18 +57,7 @@ export class QueryClient implements QueryClientContract { public mode: 'dual' | 'write' | 'read', private _connection: ConnectionContract, ) { - this.dialect = resolveClientNameWithAliases(this._getAvailableClient().client.config.client) - } - - /** - * Returns any of the available clients, giving preference to the - * write client. - * - * One client will always exists, otherwise instantiation of this - * class will fail. - */ - private _getAvailableClient () { - return this._connection.client! + this.dialect = resolveClientNameWithAliases(this._connection.config.client) } /** @@ -131,28 +119,28 @@ export class QueryClient implements QueryClientContract { * Returns instance of a query builder for selecting, updating * or deleting rows */ - public query (): DatabaseQueryBuilderContract { - return new DatabaseQueryBuilder(this._getAvailableClient().queryBuilder(), this) + public query (): any { + return new DatabaseQueryBuilder(this._connection.client!.queryBuilder(), this) } /** * Returns instance of a query builder for inserting rows */ - public insertQuery (): InsertQueryBuilderContract { + public insertQuery (): any { return new InsertQueryBuilder(this.getWriteClient().queryBuilder(), this) } /** * Returns instance of raw query builder */ - public raw (sql: any, bindings?: any): RawContract { - return new RawQueryBuilder(this._getAvailableClient().raw(sql, bindings), this) + public raw (sql: any, bindings?: any): any { + return new RawQueryBuilder(this._connection.client!.raw(sql, bindings), this) } /** * Returns instance of a query builder and selects the table */ - public from (table: any): DatabaseQueryBuilderContract { + public from (table: any): any { return this.query().from(table) } @@ -160,7 +148,7 @@ export class QueryClient implements QueryClientContract { * Returns instance of a query builder and selects the table * for an insert query */ - public table (table: any): InsertQueryBuilderContract { + public table (table: any): any { return this.insertQuery().table(table) } } diff --git a/src/Database/Traits/Executable.ts b/src/Database/Traits/Executable.ts new file mode 100644 index 00000000..1b40dded --- /dev/null +++ b/src/Database/Traits/Executable.ts @@ -0,0 +1,223 @@ +/* + * @adonisjs/lucid + * + * (c) Harminder Virk + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. +*/ + +/// + +import knex from 'knex' +import { Exception } from '@poppinss/utils' +import { ProfilerActionContract } from '@poppinss/profiler/build/src/Contracts' + +import { + QueryClientContract, + TransactionClientContract, + ExcutableQueryBuilderContract, +} from '@ioc:Adonis/Lucid/Database' + +/** + * Enforcing constructor on the destination class + */ +export type ExecutableConstrutor undefined | knex, + client?: QueryClientContract, +}> = { new (...args: any[]): T } + +/** + * To be used as a trait for executing a query that has a public + * `$knexBuilder` + */ +export class Executable implements ExcutableQueryBuilderContract { + protected $knexBuilder: knex.QueryBuilder | knex.Raw + protected client: QueryClientContract + protected getQueryClient: () => undefined | knex + + /** + * Returns the profiler action + */ + private _getProfilerAction () { + if (!this.client.profiler) { + return null + } + + return this.client.profiler.profile('sql:query', Object.assign(this.toSQL(), { + connection: this.client.connectionName, + })) + } + + /** + * Ends the profile action + */ + private _endProfilerAction (action: null | ProfilerActionContract, error?: any) { + if (!action) { + return + } + + error ? action.end({ error }) : action.end() + } + + /** + * Executes the knex query builder + */ + private async _executeQuery () { + const action = this._getProfilerAction() + try { + const result = await this.$knexBuilder + this._endProfilerAction(action) + return result + } catch (error) { + this._endProfilerAction(action, error) + throw error + } + } + + /** + * Executes the query by acquiring a connection from a custom + * knex client + */ + private async _executeQueryWithCustomConnection (knexClient: knex) { + const action = this._getProfilerAction() + + /** + * Acquire connection from the client and set it as the + * connection to be used for executing the query + */ + const connection = await knexClient['acquireConnection']() + this.$knexBuilder.connection(connection) + + let queryError: any = null + let queryResult: any = null + + /** + * Executing the query and catching exceptions so that we can + * dispose the connection before raising exception from this + * method + */ + try { + queryResult = await this.$knexBuilder + this._endProfilerAction(action) + } catch (error) { + queryError = error + this._endProfilerAction(action, error) + } + + /** + * Releasing the connection back to pool + */ + knexClient['releaseConnection'](connection) + + /** + * Re-throw if there was an exception + */ + if (queryError) { + throw queryError + } + + /** + * Return result + */ + return queryResult + } + + /** + * Turn on/off debugging for this query + */ + public debug (debug: boolean): this { + this.$knexBuilder.debug(debug) + return this + } + + /** + * Define query timeout + */ + public timeout (time: number, options?: { cancel: boolean }): this { + this.$knexBuilder['timeout'](time, options) + return this + } + + /** + * Returns SQL query as a string + */ + public toQuery (): string { + return this.$knexBuilder.toQuery() + } + + /** + * Run query inside the given transaction + */ + public useTransaction (transaction: TransactionClientContract) { + this.$knexBuilder.transacting(transaction.knexClient) + return this + } + + /** + * Executes the query + */ + public async exec (): Promise { + /** + * Raise exception when client is missing, since we need one to execute + * the query + */ + if (!this.client) { + throw new Exception('Cannot execute query without query client', 500, 'E_RUNTIME_EXCEPTION') + } + + /** + * Execute the query as it is when using `sqlite3` or query builder is part of a + * transaction + */ + if ( + this.client.dialect === 'sqlite3' + || this.client.isTransaction + || this.$knexBuilder['client'].transacting + ) { + return this._executeQuery() + } + + /** + * Executing the query with a custom knex client when it exits + */ + const knexClient = this.getQueryClient() + return knexClient ? this._executeQueryWithCustomConnection(knexClient) : this._executeQuery() + } + + /** + * Get sql representation of the query + */ + public toSQL (): knex.Sql { + return this.$knexBuilder.toSQL() + } + + /** + * Implementation of `then` for the promise API + */ + public then (resolve: any, reject?: any): any { + return this.exec().then(resolve, reject) + } + + /** + * Implementation of `catch` for the promise API + */ + public catch (reject: any): any { + return this.exec().catch(reject) + } + + /** + * Implementation of `finally` for the promise API + */ + public finally (fullfilled: any) { + return this.exec().finally(fullfilled) + } + + /** + * Required when Promises are extended + */ + public get [Symbol.toStringTag] () { + return this.constructor.name + } +} diff --git a/src/Database/TransactionClient/index.ts b/src/Database/TransactionClient/index.ts index 0abaa52a..d200bf3d 100644 --- a/src/Database/TransactionClient/index.ts +++ b/src/Database/TransactionClient/index.ts @@ -10,14 +10,8 @@ /// import * as knex from 'knex' +import { TransactionClientContract } from '@ioc:Adonis/Lucid/Database' import { ProfilerRowContract, ProfilerContract } from '@poppinss/profiler' -import { TransactionClientContract } from '@ioc:Adonis/Addons/DatabaseQueryBuilder' - -import { - RawContract, - InsertQueryBuilderContract, - DatabaseQueryBuilderContract, -} from '@ioc:Adonis/Addons/DatabaseQueryBuilder' import { RawQueryBuilder } from '../QueryBuilder/Raw' import { InsertQueryBuilder } from '../QueryBuilder/Insert' @@ -94,14 +88,14 @@ export class TransactionClient implements TransactionClientContract { /** * Get a new query builder instance */ - public query (): DatabaseQueryBuilderContract { + public query (): any { return new DatabaseQueryBuilder(this.knexClient.queryBuilder(), this) } /** * Get a new insert query builder instance */ - public insertQuery (): InsertQueryBuilderContract { + public insertQuery (): any { return new InsertQueryBuilder(this.knexClient.queryBuilder(), this) } @@ -118,21 +112,21 @@ export class TransactionClient implements TransactionClientContract { /** * Execute raw query on transaction */ - public raw (sql: any, bindings?: any): RawContract { + public raw (sql: any, bindings?: any): any { return new RawQueryBuilder(this.knexClient.raw(sql, bindings), this) } /** * Same as [[Transaction.query]] but also selects the table */ - public from (table: any): DatabaseQueryBuilderContract { + public from (table: any): any { return this.query().from(table) } /** * Same as [[Transaction.insertTable]] but also selects the table */ - public table (table: any): InsertQueryBuilderContract { + public table (table: any): any { return this.insertQuery().table(table) } diff --git a/src/Database/index.ts b/src/Database/index.ts index d5010419..dc84c94d 100644 --- a/src/Database/index.ts +++ b/src/Database/index.ts @@ -11,13 +11,14 @@ import { Exception } from '@poppinss/utils' import { LoggerContract } from '@poppinss/logger' -import { ProfilerContract, ProfilerRowContract } from '@poppinss/profiler' +import { ProfilerContract } from '@poppinss/profiler' import { ConnectionManagerContract, DatabaseConfigContract, + DatabaseClientOptions, DatabaseContract, -} from '@ioc:Adonis/Addons/Database' +} from '@ioc:Adonis/Lucid/Database' import { ConnectionManager } from '../Connection/Manager' @@ -65,10 +66,7 @@ export class Database implements DatabaseContract { /** * Returns the query client for a given connection */ - public connection ( - connection: string = this.primaryConnectionName, - options?: Partial<{ mode: 'read' | 'write', profiler: ProfilerRowContract | ProfilerContract }>, - ) { + public connection (connection: string = this.primaryConnectionName, options?: DatabaseClientOptions) { options = options || {} if (!options.profiler) { @@ -113,7 +111,7 @@ export class Database implements DatabaseContract { /** * Returns query builder. Optionally one can define the mode as well */ - public query (options?: Partial<{ mode: 'read' | 'write', profiler: ProfilerRowContract }>) { + public query (options?: DatabaseClientOptions) { return this.connection(this.primaryConnectionName, options).query() } @@ -122,7 +120,7 @@ export class Database implements DatabaseContract { * hence it doesn't matter, since in both `dual` and `write` mode, * the `write` connection is always used. */ - public insertQuery (options?: Partial<{ mode: 'read' | 'write', profiler: ProfilerRowContract }>) { + public insertQuery (options?: DatabaseClientOptions) { return this.connection(this.primaryConnectionName, options).insertQuery() } @@ -153,11 +151,7 @@ export class Database implements DatabaseContract { * defined the `read/write` mode in which to execute the * query */ - public raw ( - sql: string, - bindings?: any, - options?: Partial<{ mode: 'read' | 'write', profiler: ProfilerRowContract }>, - ) { + public raw (sql: string, bindings?: any, options?: DatabaseClientOptions) { return this.connection(this.primaryConnectionName, options).raw(sql, bindings) } } diff --git a/src/utils/index.ts b/src/utils/index.ts index b4413569..d2994cac 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,121 +1,121 @@ -/* -* @adonisjs/lucid -* -* (c) Harminder Virk -* -* For the full copyright and license information, please view the LICENSE -* file that was distributed with this source code. -*/ - -/// - -import * as knex from 'knex' -import { ProfilerRowContract, ProfilerContract } from '@poppinss/profiler' -import { ProfilerActionContract } from '@poppinss/profiler/build/src/Contracts' -import { QueryClientContract } from '@ioc:Adonis/Addons/DatabaseQueryBuilder' - -/** - * Returns the profiler action for the SQL query. `null` is - * returned when profiler doesn't exists. - */ -function getProfilerAction ( - builder: knex.QueryBuilder | knex.Raw, - profiler?: ProfilerRowContract | ProfilerContract, - profilerData?: any, -) { - if (!profiler) { - return null - } - - return profiler.profile('sql:query', Object.assign(builder.toSQL(), profilerData)) -} - -/** - * Ends the profiler action - */ -function endProfilerAction (action: null | ProfilerActionContract, error?: any) { - if (!action) { - return - } - - error ? action.end({ error }) : action.end() -} - -/** - * Returns a boolean telling if query builder or query client - * is in transaction mode - */ -export function isInTransaction ( - builder: knex.QueryBuilder | knex.Raw, - client: QueryClientContract, -) { - if (client.isTransaction) { - return true - } - - return builder['client'].transacting -} - -/** - * Executes the knex query with an option to manually pull connection from - * a dedicated knex client. - */ -export async function executeQuery ( - builder: knex.QueryBuilder | knex.Raw, - knexClient?: knex, - profiler?: ProfilerRowContract | ProfilerContract, - profilerData?: any, -) { - let action = getProfilerAction(builder, profiler, profilerData) - - if (!knexClient) { - try { - const result = await builder - endProfilerAction(action) - return result - } catch (error) { - endProfilerAction(action, error) - throw error - } - } - - /** - * Acquire connection from the client and set it as the - * connection to be used for executing the query - */ - const connection = await knexClient['acquireConnection']() - builder.connection(connection) - - let queryError: any = null - let queryResult: any = null - - /** - * Executing the query and catching exceptions so that we can - * dispose the connection before raising exception from this - * method - */ - try { - queryResult = await builder - endProfilerAction(action) - } catch (error) { - queryError = error - endProfilerAction(action, error) - } - - /** - * Releasing the connection back to pool - */ - knexClient['releaseConnection'](connection) - - /** - * Re-throw if there was an exception - */ - if (queryError) { - throw queryError - } - - /** - * Return result - */ - return queryResult -} +// /* +// * @adonisjs/lucid +// * +// * (c) Harminder Virk +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. +// */ + +// /// + +// import * as knex from 'knex' +// import { ProfilerRowContract, ProfilerContract } from '@poppinss/profiler' +// import { ProfilerActionContract } from '@poppinss/profiler/build/src/Contracts' +// import { QueryClientContract } from '@ioc:Adonis/Addons/DatabaseQueryBuilder' + +// /** +// * Returns the profiler action for the SQL query. `null` is +// * returned when profiler doesn't exists. +// */ +// function getProfilerAction ( +// builder: knex.QueryBuilder | knex.Raw, +// profiler?: ProfilerRowContract | ProfilerContract, +// profilerData?: any, +// ) { +// if (!profiler) { +// return null +// } + +// return profiler.profile('sql:query', Object.assign(builder.toSQL(), profilerData)) +// } + +// /** +// * Ends the profiler action +// */ +// function endProfilerAction (action: null | ProfilerActionContract, error?: any) { +// if (!action) { +// return +// } + +// error ? action.end({ error }) : action.end() +// } + +// /** +// * Returns a boolean telling if query builder or query client +// * is in transaction mode +// */ +// export function isInTransaction ( +// builder: knex.QueryBuilder | knex.Raw, +// client: QueryClientContract, +// ) { +// if (client.isTransaction) { +// return true +// } + +// return builder['client'].transacting +// } + +// /** +// * Executes the knex query with an option to manually pull connection from +// * a dedicated knex client. +// */ +// export async function executeQuery ( +// builder: knex.QueryBuilder | knex.Raw, +// knexClient?: knex, +// profiler?: ProfilerRowContract | ProfilerContract, +// profilerData?: any, +// ) { +// let action = getProfilerAction(builder, profiler, profilerData) + +// if (!knexClient) { +// try { +// const result = await builder +// endProfilerAction(action) +// return result +// } catch (error) { +// endProfilerAction(action, error) +// throw error +// } +// } + +// /** +// * Acquire connection from the client and set it as the +// * connection to be used for executing the query +// */ +// const connection = await knexClient['acquireConnection']() +// builder.connection(connection) + +// let queryError: any = null +// let queryResult: any = null + +// /** +// * Executing the query and catching exceptions so that we can +// * dispose the connection before raising exception from this +// * method +// */ +// try { +// queryResult = await builder +// endProfilerAction(action) +// } catch (error) { +// queryError = error +// endProfilerAction(action, error) +// } + +// /** +// * Releasing the connection back to pool +// */ +// knexClient['releaseConnection'](connection) + +// /** +// * Re-throw if there was an exception +// */ +// if (queryError) { +// throw queryError +// } + +// /** +// * Return result +// */ +// return queryResult +// } diff --git a/test-helpers/index.ts b/test-helpers/index.ts index 58012830..03e27dfd 100644 --- a/test-helpers/index.ts +++ b/test-helpers/index.ts @@ -16,13 +16,16 @@ import { FakeLogger } from '@poppinss/logger' import { Profiler } from '@poppinss/profiler' import { Filesystem } from '@poppinss/dev-utils' -import { ConnectionConfigContract } from '@ioc:Adonis/Addons/Database' +import { + ConnectionConfigContract, + QueryClientContract, + ExcutableQueryBuilderContract, +} from '@ioc:Adonis/Lucid/Database' import { RawContract, InsertQueryBuilderContract, DatabaseQueryBuilderContract, - QueryClientContract, -} from '@ioc:Adonis/Addons/DatabaseQueryBuilder' +} from '@ioc:Adonis/Lucid/DatabaseQueryBuilder' import { RawQueryBuilder } from '../src/Database/QueryBuilder/Raw' import { InsertQueryBuilder } from '../src/Database/QueryBuilder/Insert' @@ -124,7 +127,7 @@ export function getQueryBuilder (client: QueryClientContract) { return new DatabaseQueryBuilder( client.getWriteClient().queryBuilder(), client, - ) as unknown as DatabaseQueryBuilderContract + ) as unknown as DatabaseQueryBuilderContract & ExcutableQueryBuilderContract } /** @@ -135,7 +138,7 @@ export function getRawQueryBuilder (client: QueryClientContract, sql: string, bi return new RawQueryBuilder( bindings ? writeClient.raw(sql, bindings) : writeClient.raw(sql), client, - ) as unknown as RawContract + ) as unknown as RawContract & ExcutableQueryBuilderContract } /** @@ -145,7 +148,7 @@ export function getInsertBuilder (client: QueryClientContract) { return new InsertQueryBuilder( client.getWriteClient().queryBuilder(), client, - ) as unknown as InsertQueryBuilderContract + ) as unknown as InsertQueryBuilderContract & ExcutableQueryBuilderContract } /** diff --git a/test/connection.spec.ts b/test/connection.spec.ts index d4f51538..21df5f11 100644 --- a/test/connection.spec.ts +++ b/test/connection.spec.ts @@ -10,7 +10,7 @@ /// import * as test from 'japa' -import { MysqlConfigContract } from '@ioc:Adonis/Addons/Database' +import { MysqlConfigContract } from '@ioc:Adonis/Lucid/Database' import { Connection } from '../src/Connection' import { getConfig, setup, cleanup, resetTables, getLogger } from '../test-helpers' diff --git a/test/query-builder.spec.ts b/test/query-builder.spec.ts index 69b640ed..ec3aa751 100644 --- a/test/query-builder.spec.ts +++ b/test/query-builder.spec.ts @@ -45,7 +45,7 @@ if (process.env.DB !== 'sqlite') { } db.select('*').from('users') - db['_getQueryClient']() + db['getQueryClient']() }) test('use write client for update', (assert) => { @@ -61,7 +61,7 @@ if (process.env.DB !== 'sqlite') { } db.from('users').update('username', 'virk') - db['_getQueryClient']() + db['getQueryClient']() }) test('use write client for delete', (assert) => { @@ -77,11 +77,10 @@ if (process.env.DB !== 'sqlite') { } db.from('users').del() - db['_getQueryClient']() + db['getQueryClient']() }) - test('use transaction client when query is used inside a transaction', async (assert) => { - assert.plan(1) + test('use transaction client when query is used inside a transaction', async () => { const connection = new Connection('primary', getConfig(), getLogger()) connection.connect() const client = connection.getClient() @@ -92,13 +91,11 @@ if (process.env.DB !== 'sqlite') { } const trx = await client.transaction() - db.select('*').from('users').useTransaction(trx) - - assert.isUndefined(db['_getQueryClient']()) + await db.select('*').from('users').useTransaction(trx).exec() + await trx.commit() }) - test('use transaction client when insert query is used inside a transaction', async (assert) => { - assert.plan(1) + test('use transaction client when insert query is used inside a transaction', async () => { const connection = new Connection('primary', getConfig(), getLogger()) connection.connect() const client = connection.getClient() @@ -109,13 +106,11 @@ if (process.env.DB !== 'sqlite') { } const trx = await client.transaction() - db.table('users').useTransaction(trx) - - assert.isUndefined(db['_getQueryClient']()) + await db.table('users').useTransaction(trx).insert({ username: 'virk' }).exec() + await trx.rollback() }) - test('use transaction client when query is issued from transaction client', async (assert) => { - assert.plan(1) + test('use transaction client when query is issued from transaction client', async () => { const connection = new Connection('primary', getConfig(), getLogger()) connection.connect() const client = connection.getClient() @@ -125,14 +120,11 @@ if (process.env.DB !== 'sqlite') { } const trx = await client.transaction() - const db = trx.query() - - db.select('*').from('users') - assert.isUndefined(db['_getQueryClient']()) + await trx.query().select('*').from('users').exec() + await trx.commit() }) - test('use transaction client when insert query is issued from transaction client', async (assert) => { - assert.plan(1) + test('use transaction client when insert query is issued from transaction client', async () => { const connection = new Connection('primary', getConfig(), getLogger()) connection.connect() const client = connection.getClient() @@ -142,10 +134,8 @@ if (process.env.DB !== 'sqlite') { throw new Error('Never expected to reach here') } - const db = trx.insertQuery() - db.table('users').useTransaction(trx) - - assert.isUndefined(db['_getQueryClient']()) + await trx.insertQuery().table('users').insert({ username: 'virk' }).exec() + await trx.commit() }) }) } diff --git a/tsconfig.json b/tsconfig.json index ff4e2734..77ab327f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,3 +1,6 @@ { - "extends": "./node_modules/@adonisjs/mrm-preset/_tsconfig" + "extends": "./node_modules/@adonisjs/mrm-preset/_tsconfig", + "compilerOptions": { + "experimentalDecorators": true + } }