Skip to content

Commit

Permalink
feat: add schema class to create/modify tables under migrations
Browse files Browse the repository at this point in the history
Schema class itself can execute it's own instructions when provided
a query or transaction client. The migrator will collect these
classes and will execute them in sequence.

In short the job of migrator will be to acquire the locks and then execute
schemas in order
  • Loading branch information
thetutlage committed Oct 11, 2019
1 parent 3c3b8ca commit ba751e6
Show file tree
Hide file tree
Showing 15 changed files with 803 additions and 1 deletion.
1 change: 0 additions & 1 deletion adonis-typings/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,6 @@ declare module '@ioc:Adonis/Lucid/Database' {
export type MigratorConfigContract = {
disableTransactions?: boolean,
paths?: string[],
schemaName?: string,
tableName?: string,
}

Expand Down
22 changes: 22 additions & 0 deletions adonis-typings/migrator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

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

export type MigrationNode = {
absPath: string,
name: string,
source: SchemaConstructorContract,
}

export interface MigratorContract {
migrate (): Promise<void>
}
}
35 changes: 35 additions & 0 deletions adonis-typings/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare module '@ioc:Adonis/Lucid/Schema' {
import { QueryClientContract, ExcutableQueryBuilderContract } from '@ioc:Adonis/Lucid/Database'
import { RawContract } from '@ioc:Adonis/Lucid/DatabaseQueryBuilder'
import { SchemaBuilder } from 'knex'

export type DeferCallback = (client: QueryClientContract) => void | Promise<void>

export interface SchemaConstructorContract {
new (db: QueryClientContract, file: string, dryRun: boolean): SchemaContract
}

export interface SchemaContract {
dryRun: boolean
db: QueryClientContract
schema: SchemaBuilder
file: string
disableTransactions: boolean

now (precision?: number): RawContract & ExcutableQueryBuilderContract<any>
defer: (cb: DeferCallback) => void
up (): Promise<void> | void
down (): Promise<void> | void
execUp (): Promise<string [] | boolean>
execDown (): Promise<string [] | boolean>
}
}
25 changes: 25 additions & 0 deletions src/Dialects/Mssql.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

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

import { DialectContract } from '@ioc:Adonis/Lucid/Database'

export class MssqlDialect implements DialectContract {
public readonly name = 'mssql'
public supportsAdvisoryLocks = false

public async getAdvisoryLock (): Promise<boolean> {
throw new Error(`Support for advisory locks is not implemented for mssql. Create a PR to add the feature`)
}

public async releaseAdvisoryLock (): Promise<boolean> {
throw new Error(`Support for advisory locks is not implemented for mssql. Create a PR to add the feature`)
}
}
37 changes: 37 additions & 0 deletions src/Dialects/Mysql.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

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

import { DialectContract, QueryClientContract } from '@ioc:Adonis/Lucid/Database'

export class MysqlDialect implements DialectContract {
public readonly name = 'mysql'
public supportsAdvisoryLocks = true

constructor (private _client: QueryClientContract) {
}

/**
* Attempts to add advisory lock to the database and
* returns it's status.
*/
public async getAdvisoryLock (key: string, timeout: number = 0): Promise<boolean> {
const response = await this._client.raw(`SELECT GET_LOCK('${key}', ${timeout}) as lock_status;`)
return response[0] && response[0][0] && response[0][0].lock_status === 1
}

/**
* Releases the advisory lock
*/
public async releaseAdvisoryLock (key: string): Promise<boolean> {
const response = await this._client.raw(`SELECT RELEASE_LOCK('${key}') as lock_status;`)
return response[0] && response[0][0] && response[0][0].lock_status === 1
}
}
25 changes: 25 additions & 0 deletions src/Dialects/Oracle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

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

import { DialectContract } from '@ioc:Adonis/Lucid/Database'

export class OracleDialect implements DialectContract {
public readonly name = 'oracledb'
public supportsAdvisoryLocks = false

public async getAdvisoryLock (): Promise<boolean> {
throw new Error(`Support for advisory locks is not implemented for oracledb. Create a PR to add the feature`)
}

public async releaseAdvisoryLock (): Promise<boolean> {
throw new Error(`Support for advisory locks is not implemented for oracledb. Create a PR to add the feature`)
}
}
37 changes: 37 additions & 0 deletions src/Dialects/Pg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

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

import { DialectContract, QueryClientContract } from '@ioc:Adonis/Lucid/Database'

export class PgDialect implements DialectContract {
public readonly name = 'postgres'
public supportsAdvisoryLocks = true

constructor (private _client: QueryClientContract) {
}

/**
* Attempts to add advisory lock to the database and
* returns it's status.
*/
public async getAdvisoryLock (key: string): Promise<boolean> {
const response = await this._client.raw(`SELECT PG_TRY_ADVISORY_LOCK('${key}') as lock_status;`)
return response.rows[0] && response.rows[0].lock_status === true
}

/**
* Releases the advisory lock
*/
public async releaseAdvisoryLock (key: string): Promise<boolean> {
const response = await this._client.raw(`SELECT PG_ADVISORY_UNLOCK('${key}') as lock_status;`)
return response.rows[0] && response.rows[0].lock_status === true
}
}
33 changes: 33 additions & 0 deletions src/Dialects/Redshift.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

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

import { DialectContract } from '@ioc:Adonis/Lucid/Database'

export class RedshiftDialect implements DialectContract {
public readonly name = 'redshift'
public supportsAdvisoryLocks = false

/**
* Redshift doesn't support advisory locks. Learn more:
* https://tableplus.com/blog/2018/10/redshift-vs-postgres-database-comparison.html
*/
public async getAdvisoryLock (): Promise<boolean> {
throw new Error(`Redshift doesn't support advisory locks`)
}

/**
* Redshift doesn't support advisory locks. Learn more:
* https://tableplus.com/blog/2018/10/redshift-vs-postgres-database-comparison.html
*/
public async releaseAdvisoryLock (): Promise<boolean> {
throw new Error(`Redshift doesn't support advisory locks`)
}
}
32 changes: 32 additions & 0 deletions src/Dialects/Sqlite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

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

import { DialectContract } from '@ioc:Adonis/Lucid/Database'

export class SqliteDialect implements DialectContract {
public readonly name = 'sqlite3'
public supportsAdvisoryLocks = false

/**
* Attempts to add advisory lock to the database and
* returns it's status.
*/
public async getAdvisoryLock (): Promise<boolean> {
throw new Error(`Sqlite doesn't support advisory locks`)
}

/**
* Releases the advisory lock
*/
public async releaseAdvisoryLock (): Promise<boolean> {
throw new Error(`Sqlite doesn't support advisory locks`)
}
}
25 changes: 25 additions & 0 deletions src/Dialects/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { PgDialect } from './Pg'
import { MysqlDialect } from './Mysql'
import { MssqlDialect } from './Mssql'
import { SqliteDialect } from './Sqlite'
import { OracleDialect } from './Oracle'
import { RedshiftDialect } from './Redshift'

export const dialects = {
'mssql': MssqlDialect,
'mysql': MysqlDialect,
'mysql2': MysqlDialect,
'oracledb': OracleDialect,
'postgres': PgDialect,
'redshift': RedshiftDialect,
'sqlite3': SqliteDialect,
}
74 changes: 74 additions & 0 deletions src/Migrator/MigrationSource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

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

import { readdir } from 'fs'
import { join, isAbsolute, extname } from 'path'
import { MigrationNode } from '@ioc:Adonis/Lucid/Migrator'
import { ApplicationContract } from '@ioc:Adonis/Core/Application'
import { ConnectionConfigContract } from '@ioc:Adonis/Lucid/Database'

/**
* Migration source exposes the API to read the migration files
* from disk for a given connection.
*/
export class MigrationSource {
constructor (
private _config: ConnectionConfigContract,
private _app: ApplicationContract,
) {}

/**
* Returns an array of files inside a given directory. Relative
* paths are resolved from the project root
*/
private _getDirectoryFiles (directoryPath: string): Promise<MigrationNode[]> {
return new Promise((resolve, reject) => {
const path = isAbsolute(directoryPath) ? directoryPath : join(this._app.appRoot, directoryPath)
readdir(path, (error, files) => {
if (error) {
reject(error)
return
}

return resolve(files.sort().map((file) => {
return {
absPath: join(path, file),
name: file.replace(RegExp(`${extname(file)}$`), ''),
source: require(join(path, file)),
}
}))
})
})
}

/**
* Returns an array of migrations paths for a given connection. If paths
* are not defined, then `database/migrations` fallback is used
*/
private _getMigrationsPath (): string[] {
return (this._config.migrations && this._config.migrations.paths) || ['database/migrations']
}

/**
* Returns an array of files for all defined directories
*/
public async getMigrations () {
const migrationPaths = this._getMigrationsPath().sort()
const directories = await Promise.all(migrationPaths.map((directoryPath) => {
return this._getDirectoryFiles(directoryPath)
}))

return directories.reduce((result, directory) => {
result = result.concat(directory)
return result
}, [])
}
}
11 changes: 11 additions & 0 deletions src/Migrator/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

export class Migrator {
}
Loading

0 comments on commit ba751e6

Please sign in to comment.