Skip to content

Commit

Permalink
feat: add support for getting advisory locks with pg, mysql and mariadb
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Jan 12, 2020
1 parent b957c6e commit e424d1f
Show file tree
Hide file tree
Showing 12 changed files with 126 additions and 11 deletions.
28 changes: 27 additions & 1 deletion adonis-typings/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ declare module '@ioc:Adonis/Lucid/Database' {
toSQL (): knex.Sql
}

/**
* Dialect specfic methods
*/
export interface DialectContract {
readonly name: string
supportsAdvisoryLocks: boolean
getAdvisoryLock (key: string | number, timeout?: number): Promise<boolean>
releaseAdvisoryLock (key: string | number): Promise<boolean>
}

/**
* Shape of the query client, that is used to retrive instances
* of query builder
Expand All @@ -60,7 +70,7 @@ declare module '@ioc:Adonis/Lucid/Database' {
/**
* The database dialect in use
*/
readonly dialect: string
dialect: DialectContract

/**
* The client mode in which it is execute queries
Expand All @@ -73,6 +83,11 @@ declare module '@ioc:Adonis/Lucid/Database' {
*/
readonly connectionName: string

/**
* Returns schema instance for the write client
*/
schema: knex.SchemaBuilder

/**
* Returns the read and write clients
*/
Expand Down Expand Up @@ -191,6 +206,16 @@ declare module '@ioc:Adonis/Lucid/Database' {
error: any,
}

/**
* Migrations config
*/
export type MigratorConfigContract = {
disableTransactions?: boolean,
paths?: string[],
schemaName?: string,
tableName?: string,
}

/**
* Shared config options for all clients
*/
Expand All @@ -200,6 +225,7 @@ declare module '@ioc:Adonis/Lucid/Database' {
asyncStackTraces?: boolean,
revision?: number,
healthCheck?: boolean,
migrations?: MigratorConfigContract,
pool?: {
afterCreate?: (conn: any, done: any) => void,
min?: number,
Expand Down
2 changes: 2 additions & 0 deletions adonis-typings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
/// <reference path="./querybuilder.ts" />
/// <reference path="./model.ts" />
/// <reference path="./orm.ts" />
/// <reference path="./schema.ts" />
/// <reference path="./migrator.ts" />
2 changes: 1 addition & 1 deletion src/Database/QueryBuilder/Database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export class DatabaseQueryBuilder extends Chainable implements DatabaseQueryBuil
/**
* Do not chain `returning` in sqlite3 to avoid knex warnings
*/
if (this.client && ['sqlite3', 'mysql'].includes(this.client.dialect)) {
if (this.client && ['sqlite3', 'mysql'].includes(this.client.dialect.name)) {
return this
}

Expand Down
2 changes: 1 addition & 1 deletion src/Database/QueryBuilder/Insert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export class InsertQueryBuilder implements InsertQueryBuilderContract {
/**
* Do not chain `returning` in sqlite3 to avoid knex warnings
*/
if (this.client && ['sqlite3', 'mysql'].includes(this.client.dialect)) {
if (this.client && ['sqlite3', 'mysql'].includes(this.client.dialect.name)) {
return this
}

Expand Down
21 changes: 20 additions & 1 deletion src/QueryClient/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { resolveClientNameWithAliases } from 'knex/lib/helpers'
import { ProfilerRowContract, ProfilerContract } from '@ioc:Adonis/Core/Profiler'

import {
DialectContract,
ConnectionContract,
QueryClientContract,
TransactionClientContract,
Expand All @@ -25,6 +26,7 @@ import { TransactionClient } from '../TransactionClient'
import { RawQueryBuilder } from '../Database/QueryBuilder/Raw'
import { InsertQueryBuilder } from '../Database/QueryBuilder/Insert'
import { DatabaseQueryBuilder } from '../Database/QueryBuilder/Database'
import { dialects } from '../Dialects'

/**
* Query client exposes the API to fetch instance of different query builders
Expand All @@ -34,6 +36,8 @@ import { DatabaseQueryBuilder } from '../Database/QueryBuilder/Database'
* it doesn't real matter what are the return types from this class
*/
export class QueryClient implements QueryClientContract {
private _dialect: DialectContract

/**
* Not a transaction client
*/
Expand All @@ -42,7 +46,7 @@ export class QueryClient implements QueryClientContract {
/**
* The name of the dialect in use
*/
public readonly dialect: string = resolveClientNameWithAliases(this._connection.config.client)
public dialect = new (dialects[resolveClientNameWithAliases(this._connection.config.client)])(this)

/**
* The profiler to be used for profiling queries
Expand All @@ -60,6 +64,13 @@ export class QueryClient implements QueryClientContract {
) {
}

/**
* Returns schema instance for the write client
*/
public get schema () {
return this.getWriteClient().schema
}

/**
* Returns the read client. The readClient is optional, since we can get
* an instance of [[QueryClient]] with a sticky write client.
Expand Down Expand Up @@ -171,4 +182,12 @@ export class QueryClient implements QueryClientContract {
public table (table: any): any {
return this.insertQuery().table(table)
}

public getAdvisoryLock (key: string, timeout?: number): any {
return this._dialect.getAdvisoryLock(key, timeout)
}

public releaseAdvisoryLock (key: string): any {
return this._dialect.releaseAdvisoryLock(key)
}
}
2 changes: 1 addition & 1 deletion src/Traits/Executable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ export class Executable implements ExcutableQueryBuilderContract<any> {
* transaction
*/
if (
this.client.dialect === 'sqlite3'
this.client.dialect.name === 'sqlite3'
|| this.client.isTransaction
|| this.$knexBuilder['client'].transacting
) {
Expand Down
11 changes: 9 additions & 2 deletions src/TransactionClient/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import knex from 'knex'
import { EventEmitter } from 'events'
import { TransactionClientContract } from '@ioc:Adonis/Lucid/Database'
import { TransactionClientContract, DialectContract } from '@ioc:Adonis/Lucid/Database'
import { ProfilerRowContract, ProfilerContract } from '@ioc:Adonis/Core/Profiler'

import { ModelQueryBuilder } from '../Orm/QueryBuilder'
Expand Down Expand Up @@ -42,7 +42,7 @@ export class TransactionClient extends EventEmitter implements TransactionClient

constructor (
public knexClient: knex.Transaction,
public dialect: string,
public dialect: DialectContract,
public connectionName: string,
) {
super()
Expand All @@ -55,6 +55,13 @@ export class TransactionClient extends EventEmitter implements TransactionClient
return this.knexClient.isCompleted()
}

/**
* Returns schema instance for the write client
*/
public get schema () {
return this.getWriteClient().schema
}

/**
* Returns the read client. Which is just a single client in case
* of transactions
Expand Down
13 changes: 12 additions & 1 deletion test-helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* file that was distributed with this source code.
*/

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

import knex from 'knex'
import dotenv from 'dotenv'
Expand Down Expand Up @@ -38,6 +38,9 @@ import {
ManyToManyQueryBuilderContract,
} from '@ioc:Adonis/Lucid/Model'

import { SchemaConstructorContract } from '@ioc:Adonis/Lucid/Schema'

import { Schema } from '../src/Schema'
import { Adapter } from '../src/Orm/Adapter'
import { BaseModel } from '../src/Orm/BaseModel'
import { QueryClient } from '../src/QueryClient'
Expand Down Expand Up @@ -200,6 +203,7 @@ export async function cleanup () {
await db.schema.dropTableIfExists('posts')
await db.schema.dropTableIfExists('comments')
await db.schema.dropTableIfExists('identities')
await db.schema.dropTableIfExists('knex_migrations')
await db.destroy()
}

Expand Down Expand Up @@ -413,3 +417,10 @@ export function mapToObj<T extends any> (value: Map<any, any>): T {
})
return obj
}

/**
* Returns the base schema class typed to it's interface
*/
export function getBaseSchema () {
return Schema as unknown as SchemaConstructorContract
}
Binary file modified test-helpers/tmp/db.sqlite
Binary file not shown.
4 changes: 2 additions & 2 deletions test/connection/connection.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ test.group('Health Checks', (group) => {

const report = await connection.getReport()
assert.equal(report.message, 'Unable to reach the database server')
assert.equal(report.error!.errno, 'ENOTFOUND')
assert.exists(report.error)

await connection.disconnect()
}).timeout(0)
Expand All @@ -220,7 +220,7 @@ test.group('Health Checks', (group) => {

const report = await connection.getReport()
assert.equal(report.message, 'Unable to reach one of the read hosts')
assert.equal(report.error!.errno, 'ENOTFOUND')
assert.exists(report.error)

await connection.disconnect()
}).timeout(0)
Expand Down
49 changes: 49 additions & 0 deletions test/database/query-client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
/// <reference path="../../adonis-typings/index.ts" />

import test from 'japa'
import { resolveClientNameWithAliases } from 'knex/lib/helpers'

import { Connection } from '../../src/Connection'
import { QueryClient } from '../../src/QueryClient'
import { getConfig, setup, cleanup, getLogger, resetTables } from '../../test-helpers'
Expand Down Expand Up @@ -271,3 +273,50 @@ test.group('Query client | write mode', (group) => {
await connection.disconnect()
})
})

if (process.env.DB !== 'sqlite') {
test.group('Query client | advisory locks', (group) => {
group.before(async () => {
await setup()
})

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

group.afterEach(async () => {
await resetTables()
})

test('get advisory lock', async (assert) => {
const connection = new Connection('primary', getConfig(), getLogger())
connection.connect()

const client = new QueryClient('dual', connection)
const lock = await client.dialect.getAdvisoryLock(1)

assert.isTrue(lock)
assert.equal(client.dialect.name, resolveClientNameWithAliases(connection.config.client))

await client.dialect.releaseAdvisoryLock(1)
await connection.disconnect()
})

test('release advisory lock', async (assert) => {
const connection = new Connection('primary', getConfig(), getLogger())
connection.connect()

const client = new QueryClient('dual', connection)
if (client.dialect.name === 'sqlite3') {
await connection.disconnect()
return
}

await client.dialect.getAdvisoryLock(1)
const released = await client.dialect.releaseAdvisoryLock(1)
assert.isTrue(released)

await connection.disconnect()
})
})
}
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
},
"files": [
"./node_modules/@adonisjs/profiler/build/adonis-typings/profiler.d.ts",
"./node_modules/@adonisjs/logger/build/adonis-typings/logger.d.ts"
"./node_modules/@adonisjs/logger/build/adonis-typings/logger.d.ts",
"./node_modules/@adonisjs/application/build/adonis-typings/application.d.ts"
]
}

0 comments on commit e424d1f

Please sign in to comment.