Skip to content

Commit

Permalink
feat: add pivot where clauses to many to many relationship query builder
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Oct 3, 2019
1 parent 4cc757e commit dfe1b28
Show file tree
Hide file tree
Showing 7 changed files with 1,184 additions and 18 deletions.
38 changes: 37 additions & 1 deletion adonis-typings/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
*/

declare module '@ioc:Adonis/Lucid/Model' {
import { ChainableContract } from '@ioc:Adonis/Lucid/DatabaseQueryBuilder'
import { ProfilerContract, ProfilerRowContract } from '@ioc:Adonis/Core/Profiler'
import { ChainableContract, StrictValues, QueryCallback } from '@ioc:Adonis/Lucid/DatabaseQueryBuilder'
import {
QueryClientContract,
TransactionClientContract,
Expand Down Expand Up @@ -181,12 +181,48 @@ declare module '@ioc:Adonis/Lucid/Model' {
*/
export type RelationContract = BaseRelationContract | ManyToManyRelationContract

/**
* Possible signatures for adding a where clause
*/
interface WherePivot<Builder extends ChainableContract> {
(key: string, value: StrictValues | ChainableContract<any>): Builder
(key: string, operator: string, value: StrictValues | ChainableContract<any>): Builder
}

/**
* Possible signatures for adding where in clause.
*/
interface WhereInPivot<
Builder extends ChainableContract,
> {
(K: string, value: (StrictValues | ChainableContract<any>)[]): Builder
(K: string[], value: (StrictValues | ChainableContract<any>)[][]): Builder
(k: string, subquery: ChainableContract<any> | QueryCallback<Builder>): Builder
(k: string[], subquery: ChainableContract<any>): Builder
}

/**
* Shape of many to many query builder. It has few methods over the standard
* model query builder
*/
export interface ManyToManyQueryBuilderContract extends ModelQueryBuilderContract<any> {
pivotColumns (columns: string[]): this

wherePivot: WherePivot<this>
orWherePivot: WherePivot<this>
andWherePivot: WherePivot<this>

whereNotPivot: WherePivot<this>
orWhereNotPivot: WherePivot<this>
andWhereNotPivot: WherePivot<this>

whereInPivot: WhereInPivot<this>
orWhereInPivot: WhereInPivot<this>
andWhereInPivot: WhereInPivot<this>

whereNotInPivot: WhereInPivot<this>
orWhereNotInPivot: WhereInPivot<this>
andWhereNotInPivot: WhereInPivot<this>
}

/**
Expand Down
6 changes: 6 additions & 0 deletions adonis-typings/querybuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ declare module '@ioc:Adonis/Lucid/DatabaseQueryBuilder' {
(builder: Builder) => void
)

/**
* Function to transform the query callbacks and passing them the right
* instance
*/
type DBQueryCallback = (userFn: QueryCallback<ChainableContract>) => ((builder: knex.QueryBuilder) => void)

/**
* Possible signatures for a select method on database query builder. The select narrows the result
* based upon many factors.
Expand Down
9 changes: 1 addition & 8 deletions src/Database/QueryBuilder/Chainable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,9 @@
/// <reference path="../../../adonis-typings/index.ts" />

import knex from 'knex'
import { ChainableContract, QueryCallback } from '@ioc:Adonis/Lucid/DatabaseQueryBuilder'

import { ChainableContract, DBQueryCallback } from '@ioc:Adonis/Lucid/DatabaseQueryBuilder'
import { RawQueryBuilder } from './Raw'

/**
* Function to transform the query callbacks and passing them the right
* instance
*/
type DBQueryCallback = (userFn: QueryCallback<ChainableContract>) => ((builder: knex.QueryBuilder) => void)

/**
* The chainable query builder to consturct SQL queries for selecting, updating and
* deleting records.
Expand Down
11 changes: 6 additions & 5 deletions src/Orm/QueryBuilder/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ import {
} from '@ioc:Adonis/Lucid/Model'

import { QueryClientContract } from '@ioc:Adonis/Lucid/Database'
import { Chainable } from '../../Database/QueryBuilder/Chainable'
import { DBQueryCallback } from '@ioc:Adonis/Lucid/DatabaseQueryBuilder'

import { Preloader } from '../Preloader'
import { Chainable } from '../../Database/QueryBuilder/Chainable'
import { Executable, ExecutableConstructor } from '../../Traits/Executable'

/**
Expand Down Expand Up @@ -55,13 +56,13 @@ export class ModelQueryBuilder extends Chainable implements ModelQueryBuilderCon
builder: knex.QueryBuilder,
public model: ModelConstructorContract,
public client: QueryClientContract,
) {
super(builder, (userFn) => {
customFn: DBQueryCallback = (userFn) => {
return (builder) => {
userFn(new ModelQueryBuilder(builder, this.model, this.client))
}
})

},
) {
super(builder, customFn)
builder.table(model.$table)
}

Expand Down
173 changes: 171 additions & 2 deletions src/Orm/Relations/ManyToMany/QueryBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,187 @@ import { ManyToManyRelationContract, ManyToManyQueryBuilderContract } from '@ioc

import { ModelQueryBuilder } from '../../QueryBuilder'

/**
* Query builder with many to many relationships
*/
export class ManyToManyQueryBuilder extends ModelQueryBuilder implements ManyToManyQueryBuilderContract {
constructor (
builder: knex.QueryBuilder,
private _relation: ManyToManyRelationContract,
client: QueryClientContract,
) {
super(builder, _relation.relatedModel(), client)
super(builder, _relation.relatedModel(), client, (userFn) => {
return (builder) => {
userFn(new ManyToManyQueryBuilder(builder, this._relation, this.client))
}
})
}

/**
* Prefixes the pivot table name to the key
*/
private _prefixPivotTable (key: string) {
return `${this._relation.pivotTable}.${key}`
}

/**
* Add where clause with pivot table prefix
*/
public wherePivot (key: any, operator?: any, value?: any): this {
if (value) {
this.$knexBuilder.where(this._prefixPivotTable(key), operator, this.$transformValue(value))
} else if (operator) {
this.$knexBuilder.where(this._prefixPivotTable(key), this.$transformValue(operator))
} else {
this.$knexBuilder.where(this.$transformCallback(key))
}

return this
}

/**
* Add or where clause with pivot table prefix
*/
public orWherePivot (key: any, operator?: any, value?: any): this {
if (value) {
this.$knexBuilder.orWhere(this._prefixPivotTable(key), operator, this.$transformValue(value))
} else if (operator) {
this.$knexBuilder.orWhere(this._prefixPivotTable(key), this.$transformValue(operator))
} else {
this.$knexBuilder.orWhere(this.$transformCallback(key))
}

return this
}

/**
* Alias for wherePivot
*/
public andWherePivot (key: any, operator?: any, value?: any): this {
return this.wherePivot(key, operator, value)
}

/**
* Add where not pivot
*/
public whereNotPivot (key: any, operator?: any, value?: any): this {
if (value) {
this.$knexBuilder.whereNot(this._prefixPivotTable(key), operator, this.$transformValue(value))
} else if (operator) {
this.$knexBuilder.whereNot(this._prefixPivotTable(key), this.$transformValue(operator))
} else {
this.$knexBuilder.whereNot(this.$transformCallback(key))
}

return this
}

/**
* Add or where not pivot
*/
public orWhereNotPivot (key: any, operator?: any, value?: any): this {
if (value) {
this.$knexBuilder.orWhereNot(this._prefixPivotTable(key), operator, this.$transformValue(value))
} else if (operator) {
this.$knexBuilder.orWhereNot(this._prefixPivotTable(key), this.$transformValue(operator))
} else {
this.$knexBuilder.orWhereNot(this.$transformCallback(key))
}

return this
}

/**
* Alias for `whereNotPivot`
*/
public andWhereNotPivot (key: any, operator?: any, value?: any): this {
return this.whereNotPivot(key, operator, value)
}

/**
* Adds where in clause
*/
public whereInPivot (key: any, value: any) {
value = Array.isArray(value)
? value.map((one) => this.$transformValue(one))
: this.$transformValue(value)

key = Array.isArray(key)
? key.map((one) => this._prefixPivotTable(one))
: this._prefixPivotTable(key)

this.$knexBuilder.whereIn(key, value)
return this
}

/**
* Adds or where in clause
*/
public orWhereInPivot (key: any, value: any) {
value = Array.isArray(value)
? value.map((one) => this.$transformValue(one))
: this.$transformValue(value)

key = Array.isArray(key)
? key.map((one) => this._prefixPivotTable(one))
: this._prefixPivotTable(key)

this.$knexBuilder.orWhereIn(key, value)
return this
}

/**
* Alias from `whereInPivot`
*/
public andWhereInPivot (key: any, value: any): this {
return this.whereInPivot(key, value)
}

/**
* Adds where not in clause
*/
public whereNotInPivot (key: any, value: any) {
value = Array.isArray(value)
? value.map((one) => this.$transformValue(one))
: this.$transformValue(value)

key = Array.isArray(key)
? key.map((one) => this._prefixPivotTable(one))
: this._prefixPivotTable(key)

this.$knexBuilder.whereNotIn(key, value)
return this
}

/**
* Adds or where not in clause
*/
public orWhereNotInPivot (key: any, value: any) {
value = Array.isArray(value)
? value.map((one) => this.$transformValue(one))
: this.$transformValue(value)

key = Array.isArray(key)
? key.map((one) => this._prefixPivotTable(one))
: this._prefixPivotTable(key)

this.$knexBuilder.orWhereNotIn(key, value)
return this
}

/**
* Alias from `whereNotInPivot`
*/
public andWhereNotInPivot (key: any, value: any): this {
return this.whereNotInPivot(key, value)
}

/**
* Select pivot columns
*/
public pivotColumns (columns: string[]): this {
this.$knexBuilder.select(columns.map((column) => {
return `${this._relation.pivotTable}.${column} as pivot_${column}`
return `${this._prefixPivotTable(column)} as pivot_${column}`
}))
return this
}
Expand Down
21 changes: 20 additions & 1 deletion test-helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,14 @@ import {
DatabaseQueryBuilderContract,
} from '@ioc:Adonis/Lucid/DatabaseQueryBuilder'

import { ModelConstructorContract, ModelContract, AdapterContract } from '@ioc:Adonis/Lucid/Model'
import {
ModelContract,
AdapterContract,
RelationContract,
ModelConstructorContract,
ManyToManyRelationContract,
ManyToManyExecutableQueryBuilder,
} from '@ioc:Adonis/Lucid/Model'

import { Adapter } from '../src/Orm/Adapter'
import { BaseModel } from '../src/Orm/BaseModel'
Expand All @@ -39,6 +46,7 @@ import { Database } from '../src/Database/index'
import { RawQueryBuilder } from '../src/Database/QueryBuilder/Raw'
import { InsertQueryBuilder } from '../src/Database/QueryBuilder/Insert'
import { DatabaseQueryBuilder } from '../src/Database/QueryBuilder/Database'
import { ManyToManyQueryBuilder } from '../src/Orm/Relations/ManyToMany/QueryBuilder'

export const fs = new Filesystem(join(__dirname, 'tmp'))
dotenv.config()
Expand Down Expand Up @@ -230,6 +238,17 @@ export function getQueryBuilder (client: QueryClientContract) {
) as unknown as DatabaseQueryBuilderContract & ExcutableQueryBuilderContract<any>
}

/**
* Returns many to many query builder
*/
export function getManyToManyQueryBuilder (relation: RelationContract, client: QueryClientContract) {
return new ManyToManyQueryBuilder(
client.getWriteClient().queryBuilder(),
relation as ManyToManyRelationContract,
client,
) as unknown as ManyToManyExecutableQueryBuilder
}

/**
* Returns raw query builder instance for a given connection
*/
Expand Down

0 comments on commit dfe1b28

Please sign in to comment.