Migrações de esquema
Migrações de esquema são o controle de versão para seu banco de dados. Pense nelas como scripts independentes escritos em TypeScript para alterar seu esquema de banco de dados ao longo do tempo.
Veja como as migrações funcionam em poucas palavras.
Você cria um novo arquivo de migração para cada alteração de esquema de banco de dados (por exemplo, criar ou alterar tabela).
- Dentro do arquivo de migração, você escreverá as instruções para executar as ações necessárias.
- Execute migrações usando a ferramenta de linha de comando AdonisJS.
- O AdonisJS manterá o controle das migrações executadas. Isso garante que cada migração seja executada apenas uma vez.
- Durante o desenvolvimento, você também pode reverter migrações para editá-las.
Criando sua primeira migração
Você pode criar uma nova migração executando o seguinte comando Ace. Os arquivos de migração são armazenados dentro do diretório database/migrations.
NOTA
Você também pode criar um modelo Lucid e a migração juntos executando o sinalizador node ace make:model -m.
node ace make:migration users
# CREATE: database/migrations/1630981615472_create_users_table.tsSe você notar, o nome do arquivo de migração é prefixado com algum valor numérico. Adicionamos o registro de data e hora atual ao nome do arquivo para que os arquivos de migração sejam classificados na ordem de criação.
Estrutura da classe de migração
Uma classe de migração sempre estende a classe BaseSchema e deve implementar os métodos up e down.
- O método
upé usado para evoluir ainda mais o esquema do banco de dados. Normalmente, você criará novas tabelas/índices ou alterará tabelas existentes dentro deste método. - O método
downé usado para reverter as ações executadas pelo métodoup. Por exemplo, se o método up cria uma tabela, o método down deve remover a mesma tabela.
Ambos os métodos têm acesso ao Schema builder que você pode usar para construir consultas SQL DDL.
import { BaseSchema } from '@adonisjs/lucid/schema'
export default class extends BaseSchema {
protected tableName = 'users'
async up() {
this.schema.createTable(this.tableName, (table) => {
table.increments('id')
table.timestamp('created_at', { useTz: true })
table.timestamp('updated_at', { useTz: true })
})
}
async down() {
this.schema.dropTable(this.tableName)
}
}Executar e reverter migrações
Depois de criar os arquivos de migração necessários, você pode executar o seguinte comando Ace para processar migrações. Por exemplo, o comando migration:run executa o método up em todos os arquivos de migração.
node ace migration:runAs instruções SQL para cada arquivo de migração são encapsuladas dentro de uma transação. Portanto, se uma instrução falhar, todas as outras instruções dentro do mesmo arquivo serão revertidas.
Além disso, em caso de falha, as migrações subsequentes serão abortadas. No entanto, as migrações antes da migração com falha permanecem no estado concluído.
Rastreando migrações concluídas
O AdonisJS rastreia o caminho do arquivo de migrações executadas dentro da tabela de banco de dados adonis_schema. Isso é feito para evitar a reexecução dos mesmos arquivos de migração.
A seguir estão as colunas dentro da tabela adonis_schema.
+----+----------------------------------------------+-------+----------------------------------+
| id | name | batch | migration_time |
+----+----------------------------------------------+-------+----------------------------------+
| 1 | database/migrations/1587988332388_users | 1 | 2021-08-26 10:41:31.176333+05:30 |
| 2 | database/migrations/1592489784670_api_tokens | 1 | 2021-08-26 10:41:31.2074+05:30 |
+----+----------------------------------------------+-------+----------------------------------+- name: Caminho para o arquivo de migração. É sempre relativo à raiz do projeto.
- batch: O lote sob o qual a migração foi executada. O número do lote é incrementado toda vez que você executa o comando
migration:run. - migration_time: Carimbo de data/hora da execução da migração.
Migrações de reversão
Você pode reverter migrações executando o comando migration:rollback. A ação de reversão é realizada nas migrações do lote mais recente. No entanto, você também pode especificar um número de lote personalizado até o qual deseja reverter.
# Reverter o lote mais recente
node ace migration:rollback
# Reverter até o início da migração
node ace migration:rollback --batch=0
# Reverter até o lote 1
node ace migration:rollback --batch=1O comando migration:reset é basicamente um alias para migration:rollback --batch=0. Isso reverterá todas as migrações do seu aplicativo:
node ace migration:resetPara reverter um número específico de migrações, você pode usar o sinalizador --step.
# Reverter as últimas 3 migrações
node ace migration:rollback --step=3O comando rollback executa o método down da classe de migração. Assim como o método up, as instruções SQL do método down também são encapsuladas dentro de uma transação de banco de dados.
Reverter e migrar usando um único comando
O comando migration:refresh reverterá todas as suas migrações e, em seguida, executará o comando migration:run. Este comando efetivamente recria todo o seu banco de dados:
node ace migration:refresh
# Atualizar o banco de dados e executar todos os seeders
node ace migration:refresh --seedExclua tabelas e migre
Ao contrário do comando migration:refresh, o comando migration:fresh não executará o método down dos arquivos de migração. Em vez disso, ele excluirá todas as tabelas usando o comando db:wipe e, em seguida, executará o comando migration:run.
node ace migration:fresh
# Remover todas as tabelas, migrar e executar os seeders
node ace migration:fresh --seedATENÇÃO
Os comandos migration:fresh e db:wipe excluirão todas as tabelas do banco de dados. Esses comandos devem ser usados com cautela ao desenvolver em um banco de dados compartilhado com outros aplicativos.
Evite rollback na produção
Executar um rollback durante o desenvolvimento é perfeitamente aceitável, pois não há medo de perda de dados. No entanto, executar um rollback na produção não é uma opção na maioria dos casos. Considere o seguinte exemplo:
- Você cria e executa uma migração para configurar a tabela
users. - Com o tempo, esta tabela recebeu dados desde que o aplicativo está em execução na produção.
- Seu produto evoluiu e agora você deseja adicionar uma nova coluna à tabela
users.
Você não pode simplesmente reverter, editar a migração existente e executá-la novamente porque o rollback removerá a tabela users.
Em vez disso, você deve criar um novo arquivo de migração para alterar a tabela users existente adicionando a coluna necessária. Em outras palavras, as migrações devem sempre avançar.
Crie uma tabela
Você pode usar o método schema.createTable para criar uma nova tabela de banco de dados. O método aceita o nome da tabela como o primeiro argumento e uma função de retorno de chamada para definir as colunas da tabela.
import { BaseSchema } from '@adonisjs/lucid/schema'
export default class extends BaseSchema {
protected tableName = 'users'
async up() {
this.schema.createTable(this.tableName, (table) => {
table.increments('id')
table.timestamp('created_at', { useTz: true })
table.timestamp('updated_at', { useTz: true })
})
}
async down() {
this.schema.dropTable(this.tableName)
}
}Guia de referência do construtor de esquemas →
Alterar tabela
Você pode alterar uma tabela de banco de dados existente usando o método schema.alterTable. O método aceita o nome da tabela como o primeiro argumento e uma função de retorno de chamada para alterar/adicionar as colunas da tabela.
export default class extends BaseSchema {
async up() {
this.schema.alterTable('user', (table) => {
table.dropColumn('name')
table.string('first_name')
table.string('last_name')
})
}
}Renomear/remover tabela
Você pode renomear a tabela usando schema.renameTable. O método aceita o nome da tabela existente como o primeiro argumento e o novo nome como o segundo argumento.
export default class extends BaseSchema {
async up() {
this.schema.renameTable('user', 'app_users')
}
}Você pode remover a tabela usando schema.dropTable. O método aceita o nome da tabela como o único argumento.
export default class extends BaseSchema {
async down() {
this.schema.dropTable('users')
}
}Execução a seco
O modo de execução a seco das migrações permite que você visualize as consultas SQL no console em vez de executá-las. Basta passar o sinalizador --dry-run para os comandos de migração para ativar o modo de execução a seco.
# Executar
node ace migration:run --dry-run
# Reverter
node ace migration:rollback --dry-runExecutando outras operações de banco de dados
Muitas vezes, você terá requisitos para executar consultas SQL além de apenas criar/alterar tabelas. Por exemplo: migrar dados para uma tabela recém-criada antes de excluir a tabela antiga.
Você deve definir essas operações usando o método this.defer, conforme mostrado abaixo.
NOTA
Migramos os e-mails da tabela users para a tabela user_emails no exemplo a seguir.
export default class extends BaseSchema {
async up() {
this.schema.createTable('user_emails', (table) => {
// colunas da tabela
})
this.defer(async (db) => {
const users = await db.from('users').select('*')
await Promise.all(
users.map((user) => {
return db.table('user_emails').insert({ user_id: user.id, email: user.email })
})
)
})
this.schema.alterTable('users', (table) => {
table.dropColumn('email')
})
}
}Envolver suas consultas de banco de dados dentro do método this.defer garante que elas não sejam executadas ao executar migrações no modo dry run.
Alterando a conexão do banco de dados de migrações
Você pode gerenciar a conexão do banco de dados para migrações de algumas maneiras diferentes.
Origem de migração separada
A primeira opção é manter as migrações separadas para cada conexão de banco de dados. Isso geralmente é útil quando cada conexão de banco de dados consulta tabelas diferentes. Por exemplo, você está usando um banco de dados diferente para dados de usuários e um banco de dados diferente para dados de produtos.
Defina o caminho das migrações ao lado da configuração da conexão do banco de dados.
{
users: {
client: 'mysql2',
migrations: {
paths: ['./database/users/migrations']
}
},
products: {
client: 'mysql2',
migrations: {
paths: ['./database/products/migrations']
}
}
}Ao criar uma nova migração, defina o sinalizador --connection, e o comando criará o arquivo no diretório correto.
node ace make:migration --connection=productsAo executar as migrações, o sinalizador --connection executará migrações apenas do diretório de conexão selecionado.
node ace migration:run --connection=productsMigrações compartilhadas
Se você quiser executar as mesmas migrações em uma conexão de banco de dados diferente, você pode usar o sinalizador --connection. As migrações usarão a configuração da conexão selecionada para executar as migrações.
Esta opção é útil para aplicativos multilocatários, onde você deseja alternar as conexões toda vez que executar a migração.
node ace migration:run --connection=tenantAUma observação sobre bloqueios consultivos
Obtemos um bloqueio consultivo com o servidor de banco de dados para garantir que apenas um processo esteja executando migrações por vez. Os bloqueios consultivos são suportados apenas pelos drivers pg e mysql e você pode desabilitar o sistema de bloqueio usando o sinalizador de linha de comando --disable-locks.
node ace migration:run --disable-locks
node ace migration:refresh --disable-locks
node ace migration:rollback --disable-locksExecutando migrações programaticamente
Usando o módulo MigrationRunner, você pode executar migrações programaticamente. Isso geralmente é útil ao executar migrações de uma interface da web e não da linha de comando.
A seguir, um exemplo de execução de migrações de uma rota e retorno de uma lista de arquivos migrados na resposta.
import db from '@adonisjs/lucid/services/db'
import app from '@adonisjs/core/services/app'
import router from '@adonisjs/core/services/router'
import { MigrationRunner } from '@adonisjs/lucid/migration'
router.get('/', async () => {
const migrator = new MigrationRunner(db, app, {
direction: 'up',
dryRun: false,
// connectionName: 'pg',
})
await migrator.run()
return migrator.migratedFiles
})direction = upsignifica executar o métodoupdentro dos arquivos de migração. Você pode definirdirection = downpara reverter as migrações.Habilitar
dryRunnão executará as consultas, mas as coletará dentro do arrayqueries.Você também pode definir opcionalmente a propriedade
connectionNamepara executar as migrações em uma conexão de banco de dados específica.
migratedFiles
O migrator.migratedFiles é um objeto. A chave é o nome exclusivo (derivado do caminho do arquivo) e o valor é outro objeto das propriedades do arquivo de migração.
{
"database/migrations/1623289360244_users": {
"status": "completed",
"queries": [],
"file": {
"filename": "1623289360244_users.ts",
"absPath": "/path/to/project/database/migrations/1623289360244_users.ts",
"name": "database/migrations/1623289360244_users"
},
"batch": 1
}
}- O
statusserá um dos "pending", "completed" ou "error". - A matriz
queriescontém uma matriz de consultas executadas. Somente quandodryRunestiver habilitado. - A propriedade
filecontém as informações do arquivo de migração. - A propriedade
batchinforma o lote no qual a migração foi executada.
getList
O método migrator.getList retorna uma lista de todas as migrações, incluindo as concluídas e as pendentes. Esta é a mesma lista que você vê ao executar o comando node ace migration:status.
await migrator.getList()[
{
"name": "database/migrations/1623289360244_users",
"status": "pending"
}
]status
Retorna o status atual do migrador. Ele sempre será um dos seguintes.
- O status
pendingsignifica que o métodomigrator.runainda não foi chamado. - O status
completedsignifica que as migrações foram executadas com sucesso. - O status
errorsignifica que houve um erro no processo de migração. Você pode ler o erro real na propriedademigrator.errorem caso de status de erro. - O status
skipedsignifica que não houve migrações para executar ou reverter.
Configuração de migrações
O objeto migration definido em cada conexão de banco de dados é usado para configurar o sistema de migração do Lucid. A seguir está a lista de opções disponíveis.
{
migrations: {
naturalSort: true,
paths: ['database/migrations'],
disableRollbacksInProduction: true,
disableTransactions: false,
tableName: 'adonis_schema',
}
}naturalSort
Use a classificação natural para classificar os arquivos dentro do diretório de migrações.
paths
Uma matriz de diretórios para escanear e carregar arquivos de migração. Todos os arquivos que terminam com .ts ou .js são importados e executados como arquivos de migração.
disableRollbacksInProduction
Um sinalizador de segurança para desabilitar reversões acidentais no ambiente de produção. As ações de reversão definidas dentro de um arquivo de migração são geralmente destrutivas, como remover uma tabela, remover uma coluna e assim por diante. Portanto, recomendamos desabilitar reversões na produção.
disableTransactions
Desabilite o uso de transações ao executar arquivos de migração. Por padrão, iniciamos uma transação para cada arquivo de migração.
tableName
O nome da tabela para rastrear o estado dos arquivos de migração executados. O padrão é adonis_schema.