Skip to content

Commit

Permalink
feat: implement orm query builder
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Jan 12, 2020
1 parent f0ffd7a commit bc3f1ae
Show file tree
Hide file tree
Showing 15 changed files with 280 additions and 47 deletions.
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ MYSQL_PORT=3306
MYSQL_USER=virk
MYSQL_PASSWORD=password

PG_HOST=pg
PG_HOST=0.0.0.0
PG_PORT=5432
PG_USER=virk
PG_PASSWORD=password
5 changes: 5 additions & 0 deletions adonis-typings/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ declare module '@ioc:Adonis/Lucid/Database' {
getReadClient (): knex<any, any> | knex.Transaction<any, any>
getWriteClient (): knex<any, any> | knex.Transaction<any, any>

/**
* Returns the knex query builder instance
*/
knexQuery (): knex.QueryBuilder

/**
* Get new query builder instance for select, update and
* delete calls
Expand Down
51 changes: 33 additions & 18 deletions adonis-typings/orm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ declare module '@ioc:Adonis/Lucid/Orm' {
import { QueryClientContract, ExcutableQueryBuilderContract } from '@ioc:Adonis/Lucid/Database'

import {
ChainableContract,
InsertQueryBuilderContract,
DatabaseQueryBuilderContract,
} from '@ioc:Adonis/Lucid/DatabaseQueryBuilder'

import {
Expand All @@ -25,18 +25,27 @@ declare module '@ioc:Adonis/Lucid/Orm' {
} from '@poppinss/data-models'

/**
* Orm query builder will have extras methods on top of Database query builder
* Model query builder will have extras methods on top of Database query builder
*/
export interface OrmQueryBuilder<
Record extends any,
Result extends any,
> extends DatabaseQueryBuilderContract<Record, Result>, ExcutableQueryBuilderContract<Result> {
export interface ModelQueryBuilderContract<
Model extends ModelConstructorContract,
> extends ChainableContract<Model['refs']> {
model?: Model,

/**
* Execute and get first result
*/
first (): Promise<InstanceType<Model> | null>
}

/**
* The shape of query adapter
*/
export interface AdapterContract extends DataModelAdapterContract {
query<
T extends ModelConstructorContract
> (model: T): ModelQueryBuilderContract<T> & ExcutableQueryBuilderContract<InstanceType<T>>,

insert (instance: ModelContract, attributes: any): Promise<void>
update (instance: ModelContract, dirty: any): Promise<void>
delete (instance: ModelContract): Promise<void>
Expand Down Expand Up @@ -72,6 +81,8 @@ declare module '@ioc:Adonis/Lucid/Orm' {
* Shape of base model static properties
*/
export interface ModelConstructorContract extends DataModelConstructorContract {
$adapter: AdapterContract,

/**
* The database connection to use
*/
Expand Down Expand Up @@ -99,41 +110,45 @@ declare module '@ioc:Adonis/Lucid/Orm' {
query<
Model extends ModelConstructorContract,
Instance extends ModelContract,
> (this: new () => Instance): OrmQueryBuilder<Model, Instance>
> (
this: new () => Instance,
): ModelQueryBuilderContract<Model> & ExcutableQueryBuilderContract<Instance[]>

/**
* Creates model instance from the adapter result
*/
$createFromAdapterResult<T extends ModelContract> (
this: new () => T,
$createFromAdapterResult<Instance extends ModelContract> (
this: new () => Instance,
result?: any,
sideloadAttributes?: string[],
): null | T
): null | Instance

/**
* Creates multiple model instances from the adapter result
*/
$createMultipleFromAdapterResult<T extends ModelContract> (
this: new () => T,
$createMultipleFromAdapterResult<Instance extends ModelContract> (
this: new () => Instance,
results: any[],
sideloadAttributes?: string[],
): T[]
): Instance[]

/**
* Fetch row for a key/value pair
*/
findBy<T extends ModelContract> (
this: new () => T,
findBy<Instance extends ModelContract> (
this: new () => Instance,
key: string,
value: any,
): Promise<null | T>
): Promise<null | Instance>

/**
* Fetch all rows
*/
findAll<T extends ModelContract> (this: new () => T): Promise<T[]>
findAll<Instance extends ModelContract> (
this: new () => Instance,
): Promise<Instance[]>

new (): ModelContract,
new (): ModelContract
}

export const BaseModel: ModelConstructorContract
Expand Down
4 changes: 2 additions & 2 deletions src/Database/QueryBuilder/Database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
import { QueryClientContract } from '@ioc:Adonis/Lucid/Database'

import { Chainable } from './Chainable'
import { Executable, ExecutableConstrutor } from '../Traits/Executable'
import { Executable, ExecutableConstructor } from '../Traits/Executable'

/**
* Wrapping the user function for a query callback and give them
Expand All @@ -38,7 +38,7 @@ function queryCallback (userFn: QueryCallback<DatabaseQueryBuilderContract>) {
* Database query builder exposes the API to construct and run queries for selecting,
* updating and deleting records.
*/
@trait<ExecutableConstrutor>(Executable)
@trait<ExecutableConstructor>(Executable)
export class DatabaseQueryBuilder extends Chainable implements DatabaseQueryBuilderContract {
constructor (builder: knex.QueryBuilder, public client?: QueryClientContract) {
super(builder, queryCallback)
Expand Down
4 changes: 2 additions & 2 deletions src/Database/QueryBuilder/Insert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ import { trait } from '@poppinss/traits'
import { QueryClientContract } from '@ioc:Adonis/Lucid/Database'
import { InsertQueryBuilderContract } from '@ioc:Adonis/Lucid/DatabaseQueryBuilder'

import { Executable, ExecutableConstrutor } from '../Traits/Executable'
import { Executable, ExecutableConstructor } from '../Traits/Executable'

/**
* Exposes the API for performing SQL inserts
*/
@trait<ExecutableConstrutor>(Executable)
@trait<ExecutableConstructor>(Executable)
export class InsertQueryBuilder implements InsertQueryBuilderContract {
constructor (public $knexBuilder: knex.QueryBuilder, public client?: QueryClientContract) {
}
Expand Down
4 changes: 2 additions & 2 deletions src/Database/QueryBuilder/Raw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ import { trait } from '@poppinss/traits'
import { QueryClientContract } from '@ioc:Adonis/Lucid/Database'
import { RawContract } from '@ioc:Adonis/Lucid/DatabaseQueryBuilder'

import { Executable, ExecutableConstrutor } from '../Traits/Executable'
import { Executable, ExecutableConstructor } from '../Traits/Executable'

/**
* Exposes the API to execute raw queries
*/
@trait<ExecutableConstrutor>(Executable)
@trait<ExecutableConstructor>(Executable)
export class RawQueryBuilder implements RawContract {
constructor (public $knexBuilder: knex.Raw, public client?: QueryClientContract) {
}
Expand Down
9 changes: 8 additions & 1 deletion src/Database/QueryClient/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,19 @@ export class QueryClient implements QueryClientContract {
return transaction
}

/**
* Returns the knex query builder instance
*/
public knexQuery (): knex.QueryBuilder {
return this._connection.client!.queryBuilder()
}

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

/**
Expand Down
11 changes: 9 additions & 2 deletions src/Database/Traits/Executable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ import {
/**
* Enforcing constructor on the destination class
*/
export type ExecutableConstrutor<T = {
export type ExecutableConstructor<T = {
$knexBuilder: knex.Raw | knex.QueryBuilder,
getQueryClient: () => undefined | knex,
client?: QueryClientContract,
wrapQueryResults?: (results: any[]) => any[],
}> = { new (...args: any[]): T }

/**
Expand All @@ -36,6 +37,7 @@ export class Executable implements ExcutableQueryBuilderContract<any> {
protected $knexBuilder: knex.QueryBuilder | knex.Raw
protected client: QueryClientContract
protected getQueryClient: () => undefined | knex
protected wrapQueryResults?: (results: any[]) => any[]

/**
* Returns the profiler action
Expand Down Expand Up @@ -183,7 +185,12 @@ export class Executable implements ExcutableQueryBuilderContract<any> {
* Executing the query with a custom knex client when it exits
*/
const knexClient = this.getQueryClient()
return knexClient ? this._executeQueryWithCustomConnection(knexClient) : this._executeQuery()

const result = await (knexClient
? this._executeQueryWithCustomConnection(knexClient)
: this._executeQuery())

return typeof (this.wrapQueryResults) === 'function' ? this.wrapQueryResults(result) : result
}

/**
Expand Down
11 changes: 9 additions & 2 deletions src/Database/TransactionClient/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,18 +85,25 @@ export class TransactionClient implements TransactionClientContract {
return result
}

/**
* Get a new query builder instance
*/
public knexQuery (): knex.QueryBuilder {
return this.knexClient.queryBuilder()
}

/**
* Get a new query builder instance
*/
public query (): any {
return new DatabaseQueryBuilder(this.knexClient.queryBuilder(), this)
return new DatabaseQueryBuilder(this.knexQuery(), this)
}

/**
* Get a new insert query builder instance
*/
public insertQuery (): any {
return new InsertQueryBuilder(this.knexClient.queryBuilder(), this)
return new InsertQueryBuilder(this.knexQuery(), this)
}

/**
Expand Down
42 changes: 27 additions & 15 deletions src/Orm/Adapter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import { DatabaseContract } from '@ioc:Adonis/Lucid/Database'
import { AdapterContract, ModelConstructorContract, ModelContract } from '@ioc:Adonis/Lucid/Orm'

import { ModelQueryBuilder } from '../QueryBuilder'

/**
* Adapter exposes the API to make database queries and constructor
* model instances from it.
Expand All @@ -21,6 +23,24 @@ export class Adapter implements AdapterContract {
constructor (private _db: DatabaseContract) {
}

/**
* Returns the query client based upon the model instance
*/
private _getModelClient (modelConstructor: ModelConstructorContract) {
return this._db.connection(modelConstructor.$connection)
}

/**
* Returns the model query builder instance for a given model
*/
public query (modelConstructor: ModelConstructorContract): any {
const client = this._getModelClient(modelConstructor)
const query = client.knexQuery()
query.table(modelConstructor.$table)

return new ModelQueryBuilder(query, modelConstructor, client)
}

/**
* Find a given row and construct model instance from it
*/
Expand All @@ -29,34 +49,26 @@ export class Adapter implements AdapterContract {
key: string,
value: any,
): Promise<ModelContract | null> {
const client = this._db.connection(modelConstructor.$connection)

const result = await client
.query()
return this
.query(modelConstructor)
.select('*')
.from(modelConstructor.$table)
.where(key, value)
.limit(1)

return modelConstructor.$createFromAdapterResult(result[0])
.first()
}

/**
* Returns an array of models by making a select query
*/
public async findAll (modelConstructor: ModelConstructorContract): Promise<ModelContract[]> {
const client = this._db.connection(modelConstructor.$connection)

const results = await client.query().select('*').from(modelConstructor.$table)
return modelConstructor.$createMultipleFromAdapterResult(results)
return this.query(modelConstructor).select('*').exec()
}

/**
* Perform insert query on a given model instance
*/
public async insert (instance: ModelContract, attributes: any) {
const modelConstructor = instance.constructor as unknown as ModelConstructorContract
const client = this._db.connection(modelConstructor.$connection)
const client = this._getModelClient(modelConstructor)
const query = instance.$getQueryFor('insert', client)

const result = await query.insert(attributes)
Expand All @@ -70,7 +82,7 @@ export class Adapter implements AdapterContract {
*/
public async update (instance: ModelContract, dirty: any) {
const modelConstructor = instance.constructor as unknown as ModelConstructorContract
const client = this._db.connection(modelConstructor.$connection)
const client = this._getModelClient(modelConstructor)
const query = instance.$getQueryFor('update', client)

await query.update(dirty)
Expand All @@ -81,7 +93,7 @@ export class Adapter implements AdapterContract {
*/
public async delete (instance: ModelContract) {
const modelConstructor = instance.constructor as unknown as ModelConstructorContract
const client = this._db.connection(modelConstructor.$connection)
const client = this._getModelClient(modelConstructor)
const query = instance.$getQueryFor('delete', client)
await query.del()
}
Expand Down
8 changes: 7 additions & 1 deletion src/Orm/BaseModel/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import snakeCase from 'snake-case'
import { BaseModel as BaseDataModel, StaticImplements } from '@poppinss/data-models'

import { QueryClientContract } from '@ioc:Adonis/Lucid/Database'
import { ModelConstructorContract, ModelContract } from '@ioc:Adonis/Lucid/Orm'
import { ModelConstructorContract, ModelContract, AdapterContract } from '@ioc:Adonis/Lucid/Orm'

@StaticImplements<ModelConstructorContract>()
export class BaseModel extends BaseDataModel implements ModelContract {
Expand All @@ -41,7 +41,13 @@ export class BaseModel extends BaseDataModel implements ModelContract {
*/
public static $connection?: string

public static $adapter: AdapterContract

/**
* Returns the model query instance for the given model
*/
public static query (): any {
return this.$adapter.query(this)
}

/**
Expand Down
Loading

0 comments on commit bc3f1ae

Please sign in to comment.