From 781add75429244688214bf0fcc5df1276daa3187 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 8 Nov 2021 11:48:36 +0530 Subject: [PATCH] feat: allow column name in where and having clauses to be a raw query --- adonis-typings/querybuilder.ts | 22 +++--- src/Database/QueryBuilder/Chainable.ts | 101 ++++++++++++++----------- test/database/query-builder.spec.ts | 22 ++++++ 3 files changed, 91 insertions(+), 54 deletions(-) diff --git a/adonis-typings/querybuilder.ts b/adonis-typings/querybuilder.ts index 0c15d930..a64f5881 100644 --- a/adonis-typings/querybuilder.ts +++ b/adonis-typings/querybuilder.ts @@ -141,8 +141,8 @@ declare module '@ioc:Adonis/Lucid/Database' { /** * Key-value pair. The value can also be a subquery */ - (key: string, value: StrictValues | ChainableContract): Builder - (key: string, operator: string, value: StrictValues | ChainableContract): Builder + (key: string | RawQuery, value: StrictValues | ChainableContract): Builder + (key: string | RawQuery, operator: string, value: StrictValues | ChainableContract): Builder } /** @@ -152,8 +152,8 @@ declare module '@ioc:Adonis/Lucid/Database' { /** * Key-value pair. */ - (column: string, comparisonColumn: string): Builder - (column: string, operator: string, comparisonColumn: string): Builder + (column: string | RawQuery, comparisonColumn: string): Builder + (column: string | RawQuery, operator: string, comparisonColumn: string): Builder } /** @@ -163,7 +163,7 @@ declare module '@ioc:Adonis/Lucid/Database' { /** * Column name and array of values */ - (K: string, value: StrictValues[]): Builder + (K: string | RawQuery, value: StrictValues[]): Builder /** * Column names and array of values as an 2d array @@ -175,7 +175,7 @@ declare module '@ioc:Adonis/Lucid/Database' { * results */ ( - k: string, + k: string | RawQuery, subquery: ChainableContract | QueryCallback | RawBuilderContract | RawQuery ): Builder @@ -189,7 +189,7 @@ declare module '@ioc:Adonis/Lucid/Database' { * Possible signatures for adding whereNull clause. */ interface WhereNull { - (key: string): Builder + (key: string | RawQuery): Builder } /** @@ -207,7 +207,7 @@ declare module '@ioc:Adonis/Lucid/Database' { * Accept any string as a key for supporting prefix columns */ ( - key: string, + key: string | RawQuery, value: [StrictValues | ChainableContract, StrictValues | ChainableContract] ): Builder } @@ -331,7 +331,7 @@ declare module '@ioc:Adonis/Lucid/Database' { * Key operator and value. Value can be a subquery as well */ ( - key: string, + key: string | RawQuery, operator: string, value: StrictValues | ChainableContract | RawBuilderContract | RawQuery ): Builder @@ -346,14 +346,14 @@ declare module '@ioc:Adonis/Lucid/Database' { * subqueries. */ ( - key: string, + key: string | RawQuery, value: (StrictValues | ChainableContract | RawBuilderContract | RawQuery)[] ): Builder /** * Key, along with a query callback */ - (key: string, callback: QueryCallback): Builder + (key: string | RawQuery, callback: QueryCallback): Builder } /** diff --git a/src/Database/QueryBuilder/Chainable.ts b/src/Database/QueryBuilder/Chainable.ts index 24faf46f..088629d0 100644 --- a/src/Database/QueryBuilder/Chainable.ts +++ b/src/Database/QueryBuilder/Chainable.ts @@ -165,6 +165,21 @@ export abstract class Chainable extends Macroable implements ChainableContract { } } + /** + * Resolves the column name considering raw queries as well. + */ + private resolveColumn(columns: any, checkForObject: boolean = false, returnValue?: any) { + if (columns instanceof RawQueryBuilder) { + return columns['knexQuery'] + } + + if (columns instanceof RawBuilder) { + return columns.toKnex(this.knexQuery.client) + } + + return this.resolveKey(columns, checkForObject, returnValue) + } + /** * Resolves column names */ @@ -316,12 +331,12 @@ export abstract class Chainable extends Macroable implements ChainableContract { if (value !== undefined) { whereClauses.push({ method: 'where', - args: [this.resolveKey(key), operator, this.transformValue(value)], + args: [this.resolveColumn(key), operator, this.transformValue(value)], }) } else if (operator !== undefined) { whereClauses.push({ method: 'where', - args: [this.resolveKey(key), this.transformValue(operator)], + args: [this.resolveColumn(key), this.transformValue(operator)], }) } else { /** @@ -331,7 +346,7 @@ export abstract class Chainable extends Macroable implements ChainableContract { this.validateWhereSingleArgument(key, 'where') whereClauses.push({ method: 'where', - args: [this.resolveKey(key, true, this.transformCallback(key))], + args: [this.resolveColumn(key, true, this.transformCallback(key))], }) } @@ -347,18 +362,18 @@ export abstract class Chainable extends Macroable implements ChainableContract { if (value !== undefined) { whereClauses.push({ method: 'orWhere', - args: [this.resolveKey(key), operator, this.transformValue(value)], + args: [this.resolveColumn(key), operator, this.transformValue(value)], }) } else if (operator !== undefined) { whereClauses.push({ method: 'orWhere', - args: [this.resolveKey(key), this.transformValue(operator)], + args: [this.resolveColumn(key), this.transformValue(operator)], }) } else { this.validateWhereSingleArgument(key, 'orWhere') whereClauses.push({ method: 'orWhere', - args: [this.resolveKey(key, true, this.transformCallback(key))], + args: [this.resolveColumn(key, true, this.transformCallback(key))], }) } @@ -381,18 +396,18 @@ export abstract class Chainable extends Macroable implements ChainableContract { if (value !== undefined) { whereClauses.push({ method: 'whereNot', - args: [this.resolveKey(key), operator, this.transformValue(value)], + args: [this.resolveColumn(key), operator, this.transformValue(value)], }) } else if (operator !== undefined) { whereClauses.push({ method: 'whereNot', - args: [this.resolveKey(key), this.transformValue(operator)], + args: [this.resolveColumn(key), this.transformValue(operator)], }) } else { this.validateWhereSingleArgument(key, 'whereNot') whereClauses.push({ method: 'whereNot', - args: [this.resolveKey(key, true, this.transformCallback(key))], + args: [this.resolveColumn(key, true, this.transformCallback(key))], }) } @@ -408,18 +423,18 @@ export abstract class Chainable extends Macroable implements ChainableContract { if (value !== undefined) { whereClauses.push({ method: 'orWhereNot', - args: [this.resolveKey(key), operator, this.transformValue(value)], + args: [this.resolveColumn(key), operator, this.transformValue(value)], }) } else if (operator !== undefined) { whereClauses.push({ method: 'orWhereNot', - args: [this.resolveKey(key), this.transformValue(operator)], + args: [this.resolveColumn(key), this.transformValue(operator)], }) } else { this.validateWhereSingleArgument(key, 'orWhereNot') whereClauses.push({ method: 'orWhereNot', - args: [this.resolveKey(key, true, this.transformCallback(key))], + args: [this.resolveColumn(key, true, this.transformCallback(key))], }) } @@ -508,8 +523,8 @@ export abstract class Chainable extends Macroable implements ChainableContract { : this.transformValue(value) columns = Array.isArray(columns) - ? columns.map((column) => this.resolveKey(column)) - : this.resolveKey(columns) + ? columns.map((column) => this.resolveColumn(column)) + : this.resolveColumn(columns) const whereClauses = this.getRecentStackItem() whereClauses.push({ @@ -528,8 +543,8 @@ export abstract class Chainable extends Macroable implements ChainableContract { : this.transformValue(value) columns = Array.isArray(columns) - ? columns.map((column) => this.resolveKey(column)) - : this.resolveKey(columns) + ? columns.map((column) => this.resolveColumn(column)) + : this.resolveColumn(columns) const whereClauses = this.getRecentStackItem() whereClauses.push({ @@ -555,8 +570,8 @@ export abstract class Chainable extends Macroable implements ChainableContract { : this.transformValue(value) columns = Array.isArray(columns) - ? columns.map((column) => this.resolveKey(column)) - : this.resolveKey(columns) + ? columns.map((column) => this.resolveColumn(column)) + : this.resolveColumn(columns) const whereClauses = this.getRecentStackItem() whereClauses.push({ @@ -575,8 +590,8 @@ export abstract class Chainable extends Macroable implements ChainableContract { : this.transformValue(value) columns = Array.isArray(columns) - ? columns.map((column) => this.resolveKey(column)) - : this.resolveKey(columns) + ? columns.map((column) => this.resolveColumn(column)) + : this.resolveColumn(columns) const whereClauses = this.getRecentStackItem() whereClauses.push({ @@ -600,7 +615,7 @@ export abstract class Chainable extends Macroable implements ChainableContract { const whereClauses = this.getRecentStackItem() whereClauses.push({ method: 'whereNull', - args: [this.resolveKey(key)], + args: [this.resolveColumn(key)], }) return this } @@ -612,7 +627,7 @@ export abstract class Chainable extends Macroable implements ChainableContract { const whereClauses = this.getRecentStackItem() whereClauses.push({ method: 'orWhereNull', - args: [this.resolveKey(key)], + args: [this.resolveColumn(key)], }) return this } @@ -631,7 +646,7 @@ export abstract class Chainable extends Macroable implements ChainableContract { const whereClauses = this.getRecentStackItem() whereClauses.push({ method: 'whereNotNull', - args: [this.resolveKey(key)], + args: [this.resolveColumn(key)], }) return this } @@ -643,7 +658,7 @@ export abstract class Chainable extends Macroable implements ChainableContract { const whereClauses = this.getRecentStackItem() whereClauses.push({ method: 'orWhereNotNull', - args: [this.resolveKey(key)], + args: [this.resolveColumn(key)], }) return this } @@ -724,7 +739,7 @@ export abstract class Chainable extends Macroable implements ChainableContract { const whereClauses = this.getRecentStackItem() whereClauses.push({ method: 'whereBetween', - args: [this.resolveKey(key), this.getBetweenPair(value)], + args: [this.resolveColumn(key), this.getBetweenPair(value)], }) return this } @@ -736,7 +751,7 @@ export abstract class Chainable extends Macroable implements ChainableContract { const whereClauses = this.getRecentStackItem() whereClauses.push({ method: 'orWhereBetween', - args: [this.resolveKey(key), this.getBetweenPair(value)], + args: [this.resolveColumn(key), this.getBetweenPair(value)], }) return this } @@ -755,7 +770,7 @@ export abstract class Chainable extends Macroable implements ChainableContract { const whereClauses = this.getRecentStackItem() whereClauses.push({ method: 'whereNotBetween', - args: [this.resolveKey(key), this.getBetweenPair(value)], + args: [this.resolveColumn(key), this.getBetweenPair(value)], }) return this } @@ -767,7 +782,7 @@ export abstract class Chainable extends Macroable implements ChainableContract { const whereClauses = this.getRecentStackItem() whereClauses.push({ method: 'orWhereNotBetween', - args: [this.resolveKey(key), this.getBetweenPair(value)], + args: [this.resolveColumn(key), this.getBetweenPair(value)], }) return this } @@ -976,7 +991,7 @@ export abstract class Chainable extends Macroable implements ChainableContract { */ public having(key: any, operator?: any, value?: any): this { if (value !== undefined) { - this.knexQuery.having(this.resolveKey(key), operator, this.transformValue(value)) + this.knexQuery.having(this.resolveColumn(key), operator, this.transformValue(value)) return this } @@ -998,7 +1013,7 @@ export abstract class Chainable extends Macroable implements ChainableContract { */ public orHaving(key: any, operator?: any, value?: any): this { if (value !== undefined) { - this.knexQuery.orHaving(this.resolveKey(key), operator, this.transformValue(value)) + this.knexQuery.orHaving(this.resolveColumn(key), operator, this.transformValue(value)) return this } @@ -1027,7 +1042,7 @@ export abstract class Chainable extends Macroable implements ChainableContract { ? value.map((one) => this.transformValue(one)) : this.transformValue(value) - this.knexQuery.havingIn(this.resolveKey(key), value) + this.knexQuery.havingIn(this.resolveColumn(key), value) return this } @@ -1039,7 +1054,7 @@ export abstract class Chainable extends Macroable implements ChainableContract { ? value.map((one) => this.transformValue(one)) : this.transformValue(value) - this.knexQuery['orHavingIn'](this.resolveKey(key), value) + this.knexQuery['orHavingIn'](this.resolveColumn(key), value) return this } @@ -1058,7 +1073,7 @@ export abstract class Chainable extends Macroable implements ChainableContract { ? value.map((one) => this.transformValue(one)) : this.transformValue(value) - this.knexQuery['havingNotIn'](this.resolveKey(key), value) + this.knexQuery['havingNotIn'](this.resolveColumn(key), value) return this } @@ -1070,7 +1085,7 @@ export abstract class Chainable extends Macroable implements ChainableContract { ? value.map((one) => this.transformValue(one)) : this.transformValue(value) - this.knexQuery['orHavingNotIn'](this.resolveKey(key), value) + this.knexQuery['orHavingNotIn'](this.resolveColumn(key), value) return this } @@ -1085,7 +1100,7 @@ export abstract class Chainable extends Macroable implements ChainableContract { * Adding having null clause */ public havingNull(key: any): this { - this.knexQuery['havingNull'](this.resolveKey(key)) + this.knexQuery['havingNull'](this.resolveColumn(key)) return this } @@ -1093,7 +1108,7 @@ export abstract class Chainable extends Macroable implements ChainableContract { * Adding or having null clause */ public orHavingNull(key: any): this { - this.knexQuery['orHavingNull'](this.resolveKey(key)) + this.knexQuery['orHavingNull'](this.resolveColumn(key)) return this } @@ -1108,7 +1123,7 @@ export abstract class Chainable extends Macroable implements ChainableContract { * Adding having not null clause */ public havingNotNull(key: any): this { - this.knexQuery['havingNotNull'](this.resolveKey(key)) + this.knexQuery['havingNotNull'](this.resolveColumn(key)) return this } @@ -1116,7 +1131,7 @@ export abstract class Chainable extends Macroable implements ChainableContract { * Adding or having not null clause */ public orHavingNotNull(key: any): this { - this.knexQuery['orHavingNotNull'](this.resolveKey(key)) + this.knexQuery['orHavingNotNull'](this.resolveColumn(key)) return this } @@ -1177,7 +1192,7 @@ export abstract class Chainable extends Macroable implements ChainableContract { * Adding `having between` clause */ public havingBetween(key: any, value: any): this { - this.knexQuery.havingBetween(this.resolveKey(key), this.getBetweenPair(value)) + this.knexQuery.havingBetween(this.resolveColumn(key), this.getBetweenPair(value)) return this } @@ -1185,7 +1200,7 @@ export abstract class Chainable extends Macroable implements ChainableContract { * Adding `or having between` clause */ public orHavingBetween(key: any, value: any): this { - this.knexQuery.orHavingBetween(this.resolveKey(key), this.getBetweenPair(value)) + this.knexQuery.orHavingBetween(this.resolveColumn(key), this.getBetweenPair(value)) return this } @@ -1193,14 +1208,14 @@ export abstract class Chainable extends Macroable implements ChainableContract { * Alias for [[havingBetween]] */ public andHavingBetween(key: any, value: any): this { - return this.havingBetween(this.resolveKey(key), value) + return this.havingBetween(this.resolveColumn(key), value) } /** * Adding `having not between` clause */ public havingNotBetween(key: any, value: any): this { - this.knexQuery.havingNotBetween(this.resolveKey(key), this.getBetweenPair(value)) + this.knexQuery.havingNotBetween(this.resolveColumn(key), this.getBetweenPair(value)) return this } @@ -1208,7 +1223,7 @@ export abstract class Chainable extends Macroable implements ChainableContract { * Adding `or having not between` clause */ public orHavingNotBetween(key: any, value: any): this { - this.knexQuery.orHavingNotBetween(this.resolveKey(key), this.getBetweenPair(value)) + this.knexQuery.orHavingNotBetween(this.resolveColumn(key), this.getBetweenPair(value)) return this } diff --git a/test/database/query-builder.spec.ts b/test/database/query-builder.spec.ts index 7fca4476..71b350de 100644 --- a/test/database/query-builder.spec.ts +++ b/test/database/query-builder.spec.ts @@ -1050,6 +1050,28 @@ test.group('Query Builder | where', (group) => { assert.deepEqual(resolverBindings, knexResolverBindings) await connection.disconnect() }) + + test('allow raw query for the column name', async (assert) => { + const connection = new Connection('primary', getConfig(), app.logger) + connection.connect() + + const client = getQueryClient(connection, app) + let db = getQueryBuilder(client) + const { sql, bindings } = db + .from('users') + .where(getRawQueryBuilder(client, 'age', []), '>', 22) + .toSQL() + + const { sql: knexSql, bindings: knexBindings } = connection + .client!.from('users') + .where(connection.client!.raw('age'), '>', 22) + .toSQL() + + assert.equal(sql, knexSql) + assert.deepEqual(bindings, knexBindings) + + await connection.disconnect() + }) }) test.group('Query Builder | whereNot', (group) => {