Skip to content

Commit

Permalink
feat: implement insert query builder
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Aug 7, 2019
1 parent e99b202 commit 447fda7
Show file tree
Hide file tree
Showing 8 changed files with 246 additions and 21 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
- setup_remote_docker
- run:
name: Run tests
command: docker-compose run -e "ENV_PATH=.env.testing" --rm test
command: docker-compose -f docker-compose.yml -f docker-compose-test.yml run -e "ENV_PATH=.env.testing" --rm test
workflows:
version: 2.1
workflow:
Expand Down
61 changes: 50 additions & 11 deletions adonis-typings/querybuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,14 @@ declare module '@ioc:Adonis/Addons/DatabaseQueryBuilder' {
interface HavingRaw<Builder extends ChainableContract, Record> extends RawQueryBuilderContract<Builder> {
}

/**
* Possible signatures for defining table
*/
interface Table<Builder> {
(table: string): Builder
(table: Dictionary<string, string>): Builder
}

/**
* Possible signatures for performing an update
*/
Expand All @@ -578,8 +586,7 @@ declare module '@ioc:Adonis/Addons/DatabaseQueryBuilder' {
export interface ChainableContract <
Record extends Dictionary<StrictValues, string> = Dictionary<StrictValues, string>,
> {
from (table: string): this
from (table: Dictionary<string, string>): this
from: Table<this>

where: Where<this, Record>
orWhere: Where<this, Record>
Expand Down Expand Up @@ -703,6 +710,7 @@ declare module '@ioc:Adonis/Addons/DatabaseQueryBuilder' {
noWait (): this

toSQL (): Sql
toString (): string
}

/**
Expand Down Expand Up @@ -745,23 +753,44 @@ declare module '@ioc:Adonis/Addons/DatabaseQueryBuilder' {
/**
* Possible signatures for an insert query
*/
interface Insert<Record, ReturnColumns> {
interface Insert<
Record extends Dictionary<StrictValues, string>,
ReturnColumns extends any[]
> {
/**
* Accepts an object of named key/value pair and returns an array
* of Generic return columns.
* Defers the `insert` to `exec` method
*/
<K extends keyof Record> (
columns: { [P in K]: Record[P] },
defer: true,
): InsertQueryBuilderContract<Record, ReturnColumns>

/**
* Performs the insert right away when defer is not defined or is set to false
*/
<K extends keyof Record> (columns: { [P in K]: Record[P] }): Promise<ReturnColumns>
<K extends keyof Record> (columns: { [P in K]: Record[P] }, defer?: boolean): Promise<ReturnColumns>
}

/**
* Possible signatures for doing Bulk insert
* Possible signatures for doing multiple inserts in a single query
*/
interface BatchInsert<Record, ReturnColumns> {
interface MultiInsert<
Record extends Dictionary<StrictValues, string>,
ReturnColumns extends any[]
> {
/**
* Defers the `insert` to `exec` method
*/
<K extends keyof Record> (
values: { [P in K]: Record[P] }[],
defer: true,
): InsertQueryBuilderContract<Record, ReturnColumns>

/**
* Accepts an array of object of named key/value pair and returns an array
* of Generic return columns.
*/
<K extends keyof Record> (values: { [P in K]: Record[P] }[]): Promise<ReturnColumns>
<K extends keyof Record> (values: { [P in K]: Record[P] }[], defer?: boolean): Promise<ReturnColumns>
}

/**
Expand All @@ -772,6 +801,8 @@ declare module '@ioc:Adonis/Addons/DatabaseQueryBuilder' {
Record extends Dictionary<StrictValues, string> = Dictionary<StrictValues, string>,
ReturnColumns extends any[] = number[]
> {
table: Table<this>

/**
* Define returning columns
*/
Expand All @@ -784,10 +815,18 @@ declare module '@ioc:Adonis/Addons/DatabaseQueryBuilder' {

/**
* In single insert, we always know that one item inside an array is returned, so
* instead of using `val[]`, we make use of `[val]`. However, in `batchInsert`,
* instead of using `val[]`, we make use of `[val]`. However, in `multiInsert`,
* we need the `val[]`. So that's why, we pick the insert item of the
* `ReturnColumns` and return an array of it. COMPLEX 🤷
*/
batchInsert: BatchInsert<Record, ReturnColumns[0][]>
multiInsert: MultiInsert<Record, ReturnColumns[0][]>

/**
* Execute the insert
*/
exec (): Promise<ReturnColumns>

toSQL (): Sql
toString (): string
}
}
10 changes: 10 additions & 0 deletions docker-compose-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: '3.4'
services:
test:
build:
context: .
target: build-deps
links:
- mysql
- pg
command: ["npm", "run", "test:docker"]
8 changes: 0 additions & 8 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
version: '3.4'
services:
test:
build:
context: .
target: build-deps
links:
- mysql
- pg
command: ["npm", "run", "test:docker"]
mysql:
image: mysql:5.7
restart: always
Expand Down
10 changes: 10 additions & 0 deletions src/BaseQueryBuilder/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import { RawQueryBuilder } from '../RawQueryBuilder'
*/
type DBQueryCallback = (userFn: QueryCallback<ChainableContract>) => ((builder: knex.QueryBuilder) => void)

/**
* Base query builder exposes the API for constructing SQL queries.
*/
export class BaseQueryBuilder implements ChainableContract {
constructor (
protected $knexBuilder: knex.QueryBuilder,
Expand Down Expand Up @@ -960,4 +963,11 @@ export class BaseQueryBuilder implements ChainableContract {
public toSQL () {
return this.$knexBuilder.toSQL()
}

/**
* Returns string representation of the query
*/
public toString () {
return this.$knexBuilder.toString()
}
}
79 changes: 79 additions & 0 deletions src/InsertQueryBuilder/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

/// <reference path="../../adonis-typings/database.ts" />

import * as knex from 'knex'
import { InsertQueryBuilderContract } from '@ioc:Adonis/Addons/DatabaseQueryBuilder'

/**
* Exposes the API for performing SQL inserts
*/
export class InsertQueryBuilder implements InsertQueryBuilderContract {
constructor (protected $knexBuilder: knex.QueryBuilder) {
}

/**
* Define table for performing the insert query
*/
public table (table: any): this {
this.$knexBuilder.table(table)
return this
}

/**
* Define returning columns for the insert query
*/
public returning (column: any): any {
this.$knexBuilder.returning(column)
return this
}

/**
* Perform insert query
*/
public insert (columns: any, defer = false): Promise<any> | any {
this.$knexBuilder.insert(columns)

if (!defer) {
return this.exec()
}

return this
}

/**
* Insert multiple rows in a single query
*/
public multiInsert (columns: any, defer = false): Promise<any> | any {
return this.insert(columns, defer)
}

/**
* Executes the insert query
*/
public async exec (): Promise<any> {
const returnValue = await this.$knexBuilder
return returnValue
}

/**
* Get SQL representation of the constructed query
*/
public toSQL () {
return this.$knexBuilder.toSQL()
}

/**
* Returns string representation of the query
*/
public toString () {
return this.$knexBuilder.toString()
}
}
16 changes: 15 additions & 1 deletion test-helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ import { join } from 'path'
import * as dotenv from 'dotenv'
import { Filesystem } from '@poppinss/dev-utils'
import { ConnectionConfigContract, ConnectionContract } from '@ioc:Adonis/Addons/Database'
import { DatabaseQueryBuilderContract, RawContract } from '@ioc:Adonis/Addons/DatabaseQueryBuilder'
import {
DatabaseQueryBuilderContract,
RawContract,
InsertQueryBuilderContract,
} from '@ioc:Adonis/Addons/DatabaseQueryBuilder'

import { DatabaseQueryBuilder } from '../src/Database'
import { InsertQueryBuilder } from '../src/InsertQueryBuilder'
import { RawQueryBuilder } from '../src/RawQueryBuilder'

export const fs = new Filesystem(join(__dirname, 'tmp'))
Expand Down Expand Up @@ -98,3 +103,12 @@ export function getRawQueryBuilder (connection: ConnectionContract, sql: string,
bindings ? connection.client!.raw(sql, bindings) : connection.client!.raw(sql),
) as unknown as RawContract
}

/**
* Returns query builder instance for a given connection
*/
export function getInsertBuilder (connection: ConnectionContract) {
return new InsertQueryBuilder(
connection.client!.queryBuilder(),
) as unknown as InsertQueryBuilderContract
}
81 changes: 81 additions & 0 deletions test/insert-query-builder.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

/// <reference path="../adonis-typings/database.ts" />

import * as test from 'japa'

import { Connection } from '../src/Connection'
import { getConfig, setup, cleanup, getInsertBuilder } from '../test-helpers'

test.group('Query Builder | from', (group) => {
group.before(async () => {
await setup()
})

group.after(async () => {
await cleanup()
})

test('perform insert', (assert) => {
const connection = new Connection('primary', getConfig())
connection.connect()

const db = getInsertBuilder(connection)
const { sql, bindings } = db.table('users').insert({ username: 'virk' }, true).toSQL()

const { sql: knexSql, bindings: knexBindings } = connection.client!
.from('users')
.insert({ username: 'virk' })
.toSQL()

assert.equal(sql, knexSql)
assert.deepEqual(bindings, knexBindings)
})

test('perform multi insert', (assert) => {
const connection = new Connection('primary', getConfig())
connection.connect()

const db = getInsertBuilder(connection)
const { sql, bindings } = db
.table('users')
.multiInsert([{ username: 'virk' }, { username: 'nikk' }], true)
.toSQL()

const { sql: knexSql, bindings: knexBindings } = connection.client!
.from('users')
.insert([{ username: 'virk' }, { username: 'nikk' }])
.toSQL()

assert.equal(sql, knexSql)
assert.deepEqual(bindings, knexBindings)
})

test('define returning columns', (assert) => {
const connection = new Connection('primary', getConfig())
connection.connect()

const db = getInsertBuilder(connection)
const { sql, bindings } = db
.table('users')
.returning(['id', 'username'])
.multiInsert([{ username: 'virk' }, { username: 'nikk' }], true)
.toSQL()

const { sql: knexSql, bindings: knexBindings } = connection.client!
.from('users')
.returning(['id', 'username'])
.insert([{ username: 'virk' }, { username: 'nikk' }])
.toSQL()

assert.equal(sql, knexSql)
assert.deepEqual(bindings, knexBindings)
})
})

0 comments on commit 447fda7

Please sign in to comment.