Os sistemas de controle de versão, e o Git em particular, são ferramentas essenciais para rastrear alterações de código, colaborar com sua equipe e garantir a estabilidade de sua base de código. Embora o Git seja projetado principalmente para código-fonte, você também pode usá-lo em combinação com bancos de dados MySQL para controle de versão e gerenciamento de alterações de esquema.
Neste artigo, exploraremos como integrar Git com MySQL para controle de versão usando ganchos Git, com exemplos específicos em formato de guia. Todos os scripts fornecidos nas listagens são totalmente funcionais e completos. Você pode reproduzi-los sequencialmente em seu ambiente de teste.
Primeiro de tudo, vamos criar um banco de dados de teste e um usuário:
create database testdb_remote; create user 'user_remote'@'localhost' identified WITH mysql_native_password by 'remote123'; grant all on testdb_remote.* to 'user_remote'@'localhost';
A seguir, criaremos um repositório remoto. Este pode ser um repositório em qualquer servidor remoto, mas para simplificar, iremos criá-lo localmente. Para facilitar a execução de comandos, eu uso o git bash. Minha máquina local já possui uma pasta git, então eu a uso:
cd /c/git mkdir testdb.remote cd testdb.remote git init --bare
E crie um repositório local como um clone do remoto:
cd /c/git git clone /c/git/testdb.remote testdb.local cd testdb.local git ls-files
Não há arquivos no repositório; vamos criar um e enviar nossas alterações para o repositório remoto:
echo "Test DB repo" > readme.md git status git add . git commit -m "1st commit" git push
Vamos verificar o conteúdo do repositório remoto:
cd /c/git/testdb.remote git ls-tree --full-tree -r HEAD
Existe uma pasta hooks no repositório remoto, que contém vários arquivos com exemplos:
ls -1 /c/git/testdb.remote/hooks
Hooks são scripts executados quando ocorrem eventos específicos. O Git possui ganchos do lado do cliente e do lado do servidor. Os ganchos do lado do cliente são acionados por operações como confirmação e mesclagem. Os ganchos do lado do servidor são executados em operações de rede, como o recebimento de confirmações enviadas. Os ganchos são descritos em detalhes aqui . Existem diferentes opções para implementar a lógica; Darei um exemplo de uso do gancho pós-recebimento do lado do servidor.
Na pasta hooks, precisamos criar um arquivo chamado “post-receive”, este é um script bash normal:
#!/bin/sh while read oval nval ref do echo List of files changed in the commit: git diff --name-only $oval $nval done
O script acima será executado no servidor sempre que um push for concluído com sucesso e gerará uma lista de arquivos modificados. Vamos verificar como funciona adicionando uma linha ao readme.md e enviando as alterações para o repositório remoto:
cd /c/git/testdb.local echo "New line" >> readme.md git add . git commit -m "Line added" git push
Você pode ver que ao executar o comando git push, a saída agora contém linhas começando com remote:
- esta é a saída do script pós-recebimento que foi executado no servidor.
Falando em alterações básicas, os arquivos podem ser adicionados, modificados e excluídos. Você pode adotar diferentes abordagens sobre como aplicar essas alterações ao banco de dados:
Suponha que você escolheu a segunda opção e precisa executar os arquivos que foram adicionados ou alterados. Podemos filtrar esses arquivos conforme descrito aqui adicionando o parâmetro --diff-filter=AM
:
#!/bin/sh while read oval nval ref do echo List of files added or changed in the commit: git diff --name-only --diff-filter=AM $oval $nval done
Adicione alguns arquivos e altere também o readme.md novamente:
echo "SELECT 1;" > test1.sql echo "SELECT 2;" > test2.sql echo "SELECT 3;" > test3.sql echo "New line 2" >> readme.md git add . git commit -m "New files" git push
A saída do script de gancho contém 4 arquivos:
Editamos os arquivos test1.sql e readme.md, excluímos test2.sql e adicionamos outro arquivo:
echo "SELECT 11;" > test1.sql echo "New line 2" >> readme.md rm test2.sql echo "SELECT 4;" > test4.sql git add . git commit -m "Modify, remove and add" git push
Somente arquivos modificados e adicionados são exibidos:
Nosso objetivo é executar scripts SQL após cada push bem-sucedido, portanto, precisamos filtrar apenas arquivos desse tipo; no nosso caso, definiremos o requisito de que todos eles tenham a extensão “.sql”. Para filtrar, adicione o parâmetro -- "*.sql"
ao comando git diff
:
#!/bin/sh while read oval nval ref do echo List of files added or changed in the commit: git diff --name-only --diff-filter=AM $oval $nval -- "*.sql" done
Para executar scripts, também devemos ser capazes de nos conectar ao banco de dados. Para isso, criaremos credenciais e testaremos a conexão:
mysql_config_editor set --login-path=testdb_remote --host=localhost --port=3306 --user=user_remote --password mysql --login-path=testdb_remote --database=testdb_remote
Modifique nosso script para iterar pelos arquivos “.sql”, execute cada arquivo e verifique o resultado. Também precisamos classificar a saída da lista para executar os arquivos na ordem necessária. Com o comando git show, exibimos o conteúdo do script SQL e o passamos pelo pipe para execução pelo MySQL .
A variável “$?” conterá 0 se o script SQL foi executado com sucesso e outro valor se houve um erro:
#!/bin/sh while read oval nval ref do echo List of files added or changed in the commit: for file in $(git diff --name-only --diff-filter=AM $oval $nval -- "*.sql" | sort); do git show master:${file} | mysql --login-path=testdb_remote --database=testdb_remote echo "FILE: ${file} - result $?" done done
Faça mais um teste - exclua todos os arquivos “.sql” criados anteriormente e crie scripts para:
Também precisamos adicionar um prefixo (1_, 2_, etc.) a cada nome de arquivo para garantir a ordem de execução desejada dos arquivos:
rm *.sql echo "DB Initialization" >> readme.md echo " DROP TABLE IF EXISTS customers; CREATE TABLE customers ( id int UNSIGNED NOT NULL AUTO_INCREMENT, name varchar(255) DEFAULT NULL, PRIMARY KEY (id) ); " > 1_customers.sql echo " INSERT INTO customers (id, name) VALUES (1, 'John Doe'), (2, 'Jane Smith') AS new ON DUPLICATE KEY UPDATE customers.name = new.name; " > 2_customers_init.sql echo " DROP PROCEDURE IF EXISTS get_customer; DELIMITER $$ CREATE PROCEDURE get_customer(IN customer_id int UNSIGNED) BEGIN SELECT c.id, c.name FROM customers c WHERE c.id = customer_id; END $$ " > 3_get_customer.sql echo "SELECT FROM customers;" > 4_error_select.sql ls -1
Portanto temos quatro arquivos “.sql” que precisam ser executados:
Fazemos alterações no repositório:
E vemos que quando git push
é executado, os arquivos são executados sequencialmente e o resultado da execução (código de saída do comando MySQL) de cada arquivo é exibido. O arquivo “4_error_select.sql” contém um erro de sintaxe, portanto o resultado de sua execução é 1.
E por fim, vamos verificar o que temos no banco de dados:
mysql --login-path=testdb_remote --database=testdb_remote show tables; call get_customer(1); call get_customer(2);
Como você pode ver, a tabela e o procedimento foram criados no banco de dados remoto. O procedimento é executado com sucesso e retorna dados.
Para melhorar a legibilidade da saída do script de gancho, você pode suprimir a saída da CLI do MySQL ou redirecioná-la para um arquivo de log. Você também pode analisar o resultado da execução do comando MySQL e adicionar mais lógica ao script de gancho.
Por exemplo, você pode executar scripts SQL em um banco de dados de teste e depois executar alguns testes nele (como descrevi aqui ). Se os testes forem concluídos com êxito no banco de dados de teste, execute scripts SQL no banco de dados de produção e provavelmente execute alguns testes nele também.
Ao analisar os resultados de cada etapa, você pode criar pipelines de qualquer configuração.
É claro que cada abordagem tem uma série de vantagens e limitações. Com a segunda abordagem, é necessário garantir a ordem em que os scripts são executados, pois não podemos, por exemplo, inserir dados em uma tabela até que ela seja criada. Também é necessário garantir que você possa executar novamente os scripts, ou seja, lidar corretamente com situações em que o objeto que está sendo criado já está no banco de dados, ou o banco de dados já contém os dados a serem adicionados.
Ao utilizar um sistema de versionamento, o processo de desenvolvimento torna-se um pouco mais complicado, pois é necessário formalizar adicionalmente alterações em scripts de formato pré-determinado. No entanto, você pode obter alguma flexibilidade usando um arquivo de instalação.
A principal vantagem da técnica descrita é a implementação de versionamento no banco de dados com pouquíssimo esforço, bem como a capacidade de implementar pipelines de CI/CD .
Para avaliar se isso pode ser útil para você, comece perguntando-se: com que frequência você escreve código diretamente na produção?