Migrações
Com histórico no banco, não num arquivo — e nunca apagando dado sozinho.
A forma simples: a diferença#
O modelo descreve como a tabela deveria ser. diferenca() compara com como ela é:
adopt Forge
db := Forge.conectar(":memory:")
U := Forge.modelo("Usuario", {
"id": {"tipo": "Serial"},
"nome": {"tipo": "Texto", "obrigatorio": yes}
}, {"conexao": db})
Forge.migrar_tudo(db)
// agora o modelo cresce
U2 := Forge.modelo("Usuario", {
"id": {"tipo": "Serial"},
"nome": {"tipo": "Texto", "obrigatorio": yes},
"telefone": {"tipo": "Texto"}
}, {"conexao": db})
d := U2.diferenca()
assert d["colunas_faltando"] is ["telefone"]
U2.aplicar_diferenca()
assert U2.diferenca()["colunas_faltando"] is []Comparar o modelo com a tabela de verdade é melhor que confiar num histórico de arquivos, que diverge assim que alguém mexe no banco à mão.
A forma completa: passos numerados#
Para o que a diferença não cobre — renomear uma coluna, migrar dados, criar um índice composto:
adopt Forge
db := Forge.conectar(":memory:")
m := Forge.migracoes(db)
m.passo("001_cria_usuarios",
lambda c => c.executar(
"create table usuarios (id integer primary key, nome text)"),
lambda c => c.executar("drop table usuarios"))
m.passo("002_acrescenta_email",
lambda c => c.executar("alter table usuarios add column email text"),
lambda c => c.executar("alter table usuarios drop column email"))
assert m.subir() is ["001_cria_usuarios", "002_acrescenta_email"]
assert m.pendentes() is []
assert m.subir() is [] // rodar de novo não repeteO histórico mora no banco#
Numa tabela _forge_migracoes. Guardá-lo ali, e não num arquivo, é o que faz duas máquinas concordarem sobre o estado: um arquivo versionado diz o que deveria ter rodado; a tabela diz o que rodou.
adopt Forge
db := Forge.conectar(":memory:")
m := Forge.migracoes(db)
m.passo("001", lambda c => c.executar("create table a (id integer)"),
lambda c => c.executar("drop table a"))
m.subir()
e := m.estado()
assert e["aplicadas"] is 1
assert e["pendentes"] is 0
assert e["passos"][0]["reversivel"] is yesFalha para tudo#
Continuar depois de uma migração que falhou deixa o banco num estado que nenhuma migração previu:
adopt Forge
db := Forge.conectar(":memory:")
m := Forge.migracoes(db)
m.passo("001", lambda c => c.executar("create table a (id integer)"))
m.passo("002", lambda c => c.executar("isto não é sql"))
m.passo("003", lambda c => c.executar("create table c (id integer)"))
monitor:
m.subir()
handle MigrationError as e:
assert "002" in e.message
// a 003 não rodou
assert not ("c" in Forge.tabelas(db))Desfazer#
adopt Forge
db := Forge.conectar(":memory:")
m := Forge.migracoes(db)
m.passo("001", lambda c => c.executar("create table a (id integer)"),
lambda c => c.executar("drop table a"))
m.subir()
assert m.descer(1) is ["001"]
assert not ("a" in Forge.tabelas(db))Só desce o que declarou como desfazer. Uma migração sem caminho de volta avisa em vez de tentar adivinhar.