Skip to content

Commit

Permalink
feat: add wipe.ignoreTables config (#881)
Browse files Browse the repository at this point in the history
  • Loading branch information
Julien-R44 committed Sep 18, 2022
1 parent 8c92165 commit af78689
Show file tree
Hide file tree
Showing 10 changed files with 113 additions and 29 deletions.
3 changes: 2 additions & 1 deletion adonis-typings/database.ts
Expand Up @@ -336,14 +336,15 @@ declare module '@ioc:Adonis/Lucid/Database' {
/**
* Shared config options for all clients
*/
type SharedConfigNode = {
export type SharedConfigNode = {
useNullAsDefault?: boolean
debug?: boolean
asyncStackTraces?: boolean
revision?: number
healthCheck?: boolean
migrations?: MigratorConfig
seeders?: SeedersConfig
wipe?: { ignoreTables?: string[] }
pool?: {
afterCreate?: (conn: any, done: any) => void
min?: number
Expand Down
2 changes: 1 addition & 1 deletion bin/test.ts
Expand Up @@ -21,7 +21,7 @@ import { join } from 'path'
configure({
...processCliArgs(process.argv.slice(2)),
...{
files: ['test/**/*.spec.ts', '!test/database/drop-table.spec.ts'],
files: ['test/**/*.spec.ts'],
plugins: [assert(), runFailedTests()],
reporters: [specReporter()],
importer: (filePath: string) => import(filePath),
Expand Down
23 changes: 15 additions & 8 deletions src/Dialects/Mssql.ts
Expand Up @@ -10,7 +10,7 @@
/// <reference path="../../adonis-typings/index.ts" />

import { RawBuilder } from '../Database/StaticBuilder/Raw'
import { DialectContract, QueryClientContract } from '@ioc:Adonis/Lucid/Database'
import { DialectContract, MssqlConfig, QueryClientContract } from '@ioc:Adonis/Lucid/Database'

export class MssqlDialect implements DialectContract {
public readonly name = 'mssql'
Expand All @@ -31,7 +31,7 @@ export class MssqlDialect implements DialectContract {
*/
public readonly dateTimeFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZ"

constructor(private client: QueryClientContract) {}
constructor(private client: QueryClientContract, private config: MssqlConfig) {}

/**
* Returns an array of table names
Expand Down Expand Up @@ -67,14 +67,21 @@ export class MssqlDialect implements DialectContract {
public async dropAllTables() {
await this.client.rawQuery(`
DECLARE @sql NVARCHAR(MAX) = N'';
SELECT @sql += 'ALTER TABLE '
+ QUOTENAME(OBJECT_SCHEMA_NAME(parent_object_id)) + '.' + + QUOTENAME(OBJECT_NAME(parent_object_id))
+ ' DROP CONSTRAINT ' + QUOTENAME(name) + ';'
FROM sys.foreign_keys;
EXEC sp_executesql @sql;
SELECT @sql += 'ALTER TABLE '
+ QUOTENAME(OBJECT_SCHEMA_NAME(parent_object_id)) + '.' + + QUOTENAME(OBJECT_NAME(parent_object_id))
+ ' DROP CONSTRAINT ' + QUOTENAME(name) + ';'
FROM sys.foreign_keys;
EXEC sp_executesql @sql;
`)

await this.client.rawQuery(`EXEC sp_MSforeachtable 'DROP TABLE \\?';`)
const ignoredTables = (this.config.wipe?.ignoreTables || [])
.map((table) => `"${table}"`)
.join(', ')

await this.client.rawQuery(`
EXEC sp_MSforeachtable 'DROP TABLE \\?',
@whereand='AND o.Name NOT IN (${ignoredTables || '""'})'
`)
}

public async getAllViews(): Promise<string[]> {
Expand Down
13 changes: 10 additions & 3 deletions src/Dialects/Mysql.ts
Expand Up @@ -10,7 +10,7 @@
/// <reference path="../../adonis-typings/index.ts" />

import { RawBuilder } from '../Database/StaticBuilder/Raw'
import { DialectContract, QueryClientContract } from '@ioc:Adonis/Lucid/Database'
import { DialectContract, MysqlConfig, QueryClientContract } from '@ioc:Adonis/Lucid/Database'

export class MysqlDialect implements DialectContract {
public readonly name = 'mysql'
Expand All @@ -31,7 +31,7 @@ export class MysqlDialect implements DialectContract {
*/
public readonly dateTimeFormat = 'yyyy-MM-dd HH:mm:ss'

constructor(private client: QueryClientContract) {}
constructor(private client: QueryClientContract, private config: MysqlConfig) {}

/**
* Truncate mysql table with option to cascade
Expand Down Expand Up @@ -99,14 +99,21 @@ export class MysqlDialect implements DialectContract {
public async dropAllTables() {
let tables = await this.getAllTables()

if (!tables.length) return
/**
* Filter out tables that are not allowed to be dropped
*/
tables = tables.filter((table) => !(this.config.wipe?.ignoreTables || []).includes(table))

/**
* Add backquote around table names to avoid syntax errors
* in case of a table name with a reserved keyword
*/
tables = tables.map((table) => '`' + table + '`')

if (!tables.length) {
return
}

/**
* Cascade and truncate
*/
Expand Down
18 changes: 14 additions & 4 deletions src/Dialects/Pg.ts
Expand Up @@ -9,7 +9,7 @@

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

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

export class PgDialect implements DialectContract {
public readonly name = 'postgres'
Expand All @@ -30,7 +30,7 @@ export class PgDialect implements DialectContract {
*/
public readonly dateTimeFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZ"

constructor(private client: QueryClientContract) {}
constructor(private client: QueryClientContract, private config: PostgreConfig) {}

/**
* Returns an array of table names for one or many schemas.
Expand Down Expand Up @@ -87,8 +87,18 @@ export class PgDialect implements DialectContract {
* Drop all tables inside the database
*/
public async dropAllTables(schemas: string[]) {
const tables = await this.getAllTables(schemas)
if (!tables.length) return
let tables = await this.getAllTables(schemas)

/**
* Filter out tables that are not allowed to be dropped
*/
tables = tables.filter(
(table) => !(this.config.wipe?.ignoreTables || ['spatial_ref_sys']).includes(table)
)

if (!tables.length) {
return
}

await this.client.rawQuery(`DROP TABLE "${tables.join('", "')}" CASCADE;`)
}
Expand Down
18 changes: 14 additions & 4 deletions src/Dialects/Redshift.ts
Expand Up @@ -9,7 +9,7 @@

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

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

export class RedshiftDialect implements DialectContract {
public readonly name = 'redshift'
Expand All @@ -30,7 +30,7 @@ export class RedshiftDialect implements DialectContract {
*/
public readonly dateTimeFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZ"

constructor(private client: QueryClientContract) {}
constructor(private client: QueryClientContract, private config: PostgreConfig) {}

/**
* Returns an array of table names for one or many schemas.
Expand Down Expand Up @@ -95,8 +95,18 @@ export class RedshiftDialect implements DialectContract {
* Drop all tables inside the database
*/
public async dropAllTables(schemas: string[]) {
const tables = await this.getAllTables(schemas)
if (!tables.length) return
let tables = await this.getAllTables(schemas)

/**
* Filter out tables that are not allowed to be dropped
*/
tables = tables.filter(
(table) => !(this.config.wipe?.ignoreTables || ['spatial_ref_sys']).includes(table)
)

if (!tables.length) {
return
}

await this.client.rawQuery(`DROP table ${tables.join(',')} CASCADE;`)
}
Expand Down
14 changes: 9 additions & 5 deletions src/Dialects/SqliteBase.ts
Expand Up @@ -9,7 +9,7 @@

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

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

export abstract class BaseSqliteDialect implements DialectContract {
public abstract readonly name: 'sqlite3' | 'better-sqlite3'
Expand All @@ -30,7 +30,7 @@ export abstract class BaseSqliteDialect implements DialectContract {
*/
public readonly dateTimeFormat = 'yyyy-MM-dd HH:mm:ss'

constructor(private client: QueryClientContract) {}
constructor(private client: QueryClientContract, private config: SqliteConfig) {}

/**
* Returns an array of table names
Expand Down Expand Up @@ -81,9 +81,13 @@ export abstract class BaseSqliteDialect implements DialectContract {
*/
public async dropAllTables() {
await this.client.rawQuery('PRAGMA writable_schema = 1;')
await this.client.rawQuery(
`delete from sqlite_master where type in ('table', 'index', 'trigger');`
)
await this.client
.knexQuery()
.delete()
.from('sqlite_master')
.whereIn('type', ['table', 'index', 'trigger'])
.whereNotIn('name', this.config.wipe?.ignoreTables || [])

await this.client.rawQuery('PRAGMA writable_schema = 0;')
await this.client.rawQuery('VACUUM;')
}
Expand Down
5 changes: 5 additions & 0 deletions src/Dialects/index.ts
Expand Up @@ -14,6 +14,7 @@ import { SqliteDialect } from './Sqlite'
import { OracleDialect } from './Oracle'
import { RedshiftDialect } from './Redshift'
import { BetterSqliteDialect } from './BetterSqlite'
import { DialectContract, QueryClientContract, SharedConfigNode } from '@ioc:Adonis/Lucid/Database'

export const dialects = {
'mssql': MssqlDialect,
Expand All @@ -24,4 +25,8 @@ export const dialects = {
'redshift': RedshiftDialect,
'sqlite3': SqliteDialect,
'better-sqlite3': BetterSqliteDialect,
} as {
[key: string]: {
new (client: QueryClientContract, config: SharedConfigNode): DialectContract
}
}
5 changes: 4 additions & 1 deletion src/QueryClient/index.ts
Expand Up @@ -44,7 +44,10 @@ export class QueryClient implements QueryClientContract {
/**
* The dialect in use
*/
public dialect: DialectContract = new dialects[this.connection.dialectName](this)
public dialect: DialectContract = new dialects[this.connection.dialectName](
this,
this.connection.config
)

/**
* The profiler to be used for profiling queries
Expand Down
41 changes: 39 additions & 2 deletions test/database/drop-tables.spec.ts
Expand Up @@ -26,7 +26,7 @@ test.group('Query client | drop tables', (group) => {
})

group.teardown(async () => {
await cleanup(['temp_posts', 'temp_users'])
await cleanup(['temp_posts', 'temp_users', 'table_that_should_not_be_dropped'])
await cleanup()
await fs.cleanup()
})
Expand Down Expand Up @@ -65,7 +65,7 @@ test.group('Query client | drop tables', (group) => {
await connection.disconnect()
})

test('dropAllTables should not throw when there are no tables', async ({ assert }) => {
test('drop all tables should not throw when there are no tables', async ({ assert }) => {
await fs.fsExtra.ensureDir(join(fs.basePath, 'temp'))
const connection = new Connection('primary', getConfig(), app.logger)
connection.connect()
Expand All @@ -81,4 +81,41 @@ test.group('Query client | drop tables', (group) => {

await connection.disconnect()
})

test('drop all tables except those defined in ignoreTables', async ({ assert }) => {
await fs.fsExtra.ensureDir(join(fs.basePath, 'temp'))
const config = getConfig()
config.wipe = {}
config.wipe.ignoreTables = ['table_that_should_not_be_dropped', 'ignore_me']

const connection = new Connection('primary', config, app.logger)
connection.connect()

await connection.client!.schema.createTableIfNotExists('temp_users', (table) => {
table.increments('id')
})

await connection.client!.schema.createTableIfNotExists('temp_posts', (table) => {
table.increments('id')
})

await connection.client!.schema.createTableIfNotExists(
'table_that_should_not_be_dropped',
(table) => table.increments('id')
)

await connection.client!.schema.createTableIfNotExists('ignore_me', (table) =>
table.increments('id')
)

const client = new QueryClient('dual', connection, app.container.use('Adonis/Core/Event'))
await client.dialect.dropAllTables(['public'])

assert.isFalse(await connection.client!.schema.hasTable('temp_users'))
assert.isFalse(await connection.client!.schema.hasTable('temp_posts'))
assert.isTrue(await connection.client!.schema.hasTable('table_that_should_not_be_dropped'))
assert.isTrue(await connection.client!.schema.hasTable('ignore_me'))

await connection.disconnect()
}).pin()
})

0 comments on commit af78689

Please sign in to comment.