laravel-procedure maintained by alncris2
laravel-procedure
Versionamento e aplicação de stored procedures para projetos Laravel, com snapshots automáticos em disco e histórico em banco.
- Laravel 5.8+ / PHP 7.1.3+
- Usa sempre a connection default do Laravel (
config('database.default')) - Estrutura por grupo:
database/procedures/{grupo}/{PROCEDURE}/current.sql+versions/YYYYMMdd_HHmmss_label.sql current.sqlé a fonte viva editada pelo dev; os snapshots emversions/são gerados automaticamente- Snapshots com nome baseado em timestamp — sem conflito quando dois devs trabalham na mesma procedure
Instalação
composer require alncris2/laravel-procedure
Publicar config e migration (tudo de uma vez):
php artisan vendor:publish --tag=procedure
php artisan migrate
Ou separadamente, se preferir:
php artisan vendor:publish --tag=procedure-config
php artisan vendor:publish --tag=procedure-migrations
Uso
Criar uma procedure
php artisan procedure:make atendimento PRC_BUSCAR_ATENDIMENTOS
Gera:
database/procedures/atendimento/PRC_BUSCAR_ATENDIMENTOS/
current.sql
versions/
O current.sql vem com um template compatível com o driver detectado (oracle, mysql, pgsql, sqlsrv).
Editar
Abra e edite current.sql. Nunca edite os arquivos em versions/ — eles são snapshots congelados.
Ver status
php artisan procedure:status
php artisan procedure:status --group=atendimento
php artisan procedure:status --changed
Estados possíveis: SYNCED, CHANGED, PENDING, FAILED, UNTRACKED.
Aplicar
php artisan procedure:apply
php artisan procedure:apply --only=PRC_BUSCAR_ATENDIMENTOS --message="corrige filtro de status"
php artisan procedure:apply --group=atendimento
O apply:
- Detecta o que mudou comparando o checksum do
current.sqlcom a versão marcada comois_currentno banco. - Cria automaticamente um snapshot em
versions/YYYYMMdd_HHmmss_slug.sql(sesnapshot_on_applyestiver habilitado). - Executa o SQL com
DB::connection()->unprepared(...). - Registra o histórico em
procedure_versions. - Aplica o rolling window: se o número de snapshots em disco exceder
max_snapshots, o mais antigo é removido automaticamente.
Dump (importar procedures do banco)
Para trazer procedures já existentes no banco para dentro do projeto:
# Sem --group: infere grupos automaticamente em modo dry-run
php artisan procedure:dump
# Efetiva a proposta do auto-group
php artisan procedure:dump --apply
# Grupo explícito (comportamento clássico)
php artisan procedure:dump --group=atendimento
php artisan procedure:dump --group=atendimento --only=PRC_BUSCAR_ATENDIMENTOS
php artisan procedure:dump --group=atendimento --owner=MYSCHEMA # Oracle
php artisan procedure:dump --group=imported --no-register # não grava linha em procedure_versions
# Forçar uma estratégia específica de auto-group
php artisan procedure:dump --apply --strategy=prefix # cascade|prefix|tables|schema
Auto-group (quando --group é omitido)
O grupo de destino de cada procedure é inferido por uma cascata determinística (sem APIs externas, sem dependências de ML):
- Prefixo do nome —
SP_INV_*,PRC_FIN_*,UpdateCustomer→ agrupa pelo token de domínio, pulando prefixos de tipo (sp_,prc_, etc.). - Tabelas referenciadas — parse leve de
FROM/JOIN/UPDATE/INSERT INTO/DELETE FROM/MERGE INTO; procedures que compartilham tabelas caem no mesmo grupo, nomeado pela tabela mais frequente. - Owner/schema do banco como fallback.
ungroupedcomo último recurso.
Sem --apply, o comando apenas imprime a proposta (tabela procedure | grupo proposto | estratégia | tabelas-chave) sem gravar nada. Com --apply, efetiva.
Configurável em config/procedure.php no bloco auto_group (veja abaixo).
Comportamento por procedure
- Procedure nova no projeto (primeira importação) → grava
current.sqle uma baseline silenciosa emprocedure_versionscom labeldump_import. Nenhum arquivo é criado emversions/—statusjá ficaSYNCED. - Existe e banco == disco → resultado
synced, nenhuma escrita. - Existe e diverge → sobrescreve
current.sqle cria snapshot físicoversions/YYYYMMdd_HHmmss_dump_sync.sqlcomo uma versão real do histórico.
Dessa forma versions/ só registra mudanças reais vindas do banco; a primeira importação não polui o diretório. Rollback sobre uma baseline sem snapshot físico retorna mensagem explicativa.
Suporta Oracle (USER_SOURCE / ALL_SOURCE) e MySQL (SHOW CREATE PROCEDURE).
Rollback
php artisan procedure:rollback --only=PRC_BUSCAR_ATENDIMENTOS
php artisan procedure:rollback --only=PRC_BUSCAR_ATENDIMENTOS --to-version=1
php artisan procedure:rollback --group=atendimento
Reaplica o snapshot alvo completo (full-state) e marca o is_current no registro correspondente.
--to-version=N refere-se à posição do snapshot na lista ordenada por nome (do mais antigo para o mais novo).
Fluxo com dois devs na mesma procedure
Este é o cenário mais comum em times que trabalham em paralelo.
Situação inicial
O time fez o dump do banco e commitou tudo na branch prd. Cada dev cria sua branch a partir dali:
# Dev A
git checkout -b feature/filtro-ativo prd
# Dev B (em paralelo)
git checkout -b fix/ordenacao prd
Ambos partem do mesmo current.sql:
-- SP_USUARIOS/current.sql
CREATE OR REPLACE PROCEDURE SP_USUARIOS AS
BEGIN
SELECT * FROM usuarios;
END;
Dev A faz sua alteração
-- Adiciona filtro de ativos
SELECT * FROM usuarios WHERE ativo = 1;
php artisan procedure:apply --message="filtro ativo"
# Gera: versions/20260601_090000_filtro_ativo.sql
git add .
git commit -m "feat: SP_USUARIOS filtra apenas usuários ativos"
Dev B faz sua alteração (em paralelo)
-- Adiciona ordenação
SELECT * FROM usuarios ORDER BY nome;
php artisan procedure:apply --message="ordenacao por nome"
# Gera: versions/20260601_103000_ordenacao_por_nome.sql
git add .
git commit -m "fix: SP_USUARIOS retorna usuários ordenados por nome"
Merge na branch de desenvolvimento
O PR do Dev A entra primeiro — sem conflito, a branch dev ainda tem a base de prd.
Quando o PR do Dev B vai ser mergeado:
| Arquivo | Resultado no git |
|---|---|
versions/20260601_103000_ordenacao_por_nome.sql |
Sem conflito — arquivo com nome único (timestamp diferente do de A) |
current.sql |
Conflito — A tem WHERE ativo = 1, B tem ORDER BY nome |
O git marca o conflito em current.sql:
<<<<<<< feature/filtro-ativo
SELECT * FROM usuarios WHERE ativo = 1;
=======
SELECT * FROM usuarios ORDER BY nome;
>>>>>>> fix/ordenacao
Quem fizer o merge resolve mantendo as duas alterações:
SELECT * FROM usuarios WHERE ativo = 1 ORDER BY nome;
Deploy (CI/CD)
php artisan procedure:apply
O procedure:status detecta CHANGED — o current.sql mergeado tem checksum diferente de qualquer snapshot existente. O apply:
- Cria
versions/20260601_140000_auto_snapshot.sqlcom o SQL das duas alterações juntas. - Executa no banco.
- Remove o snapshot mais antigo se o limite de
max_snapshotsfor atingido.
O banco fica com as alterações dos dois devs. Nenhuma sobrescreve a outra.
Por que não há conflito nos snapshots?
O nome do snapshot inclui o timestamp do momento em que foi gerado. Dev A gerou às 09:00, Dev B às 10:30 — nomes diferentes, o git os trata como dois arquivos novos independentes e não gera conflito entre eles.
Sem isso, ambos gerariam versions/002_auto_snapshot.sql — mesmo nome, conflito garantido no merge.
Configuração
config/procedure.php:
return [
'base_path' => database_path('procedures'),
'history_table' => 'procedure_versions',
'snapshot_on_apply' => true,
'default_snapshot_message'=> 'auto_snapshot',
'max_snapshots' => 5,
'sql' => [
'strip_trailing_oracle_slash' => true,
'remove_mysql_delimiter' => true,
],
'auto_group' => [
'min_cluster_size' => 2,
'prefix_separator' => '_',
'noise_prefixes' => ['sp', 'usp', 'prc', 'proc', 'fn', 'fnc', 'p'],
'noise_tables' => ['dual'],
'fallback' => 'ungrouped',
],
];
Opções principais
| Opção | Padrão | Descrição |
|---|---|---|
base_path |
database/procedures |
Raiz da estrutura de procedures |
history_table |
procedure_versions |
Tabela de histórico no banco |
snapshot_on_apply |
true |
Grava snapshot em versions/ antes de executar |
default_snapshot_message |
auto_snapshot |
Label do snapshot quando --message não é passado |
max_snapshots |
5 |
Máximo de snapshots por procedure em disco. O mais antigo é removido quando excedido. Use 0 ou false para desativar. |
Tabela de histórico
procedure_versions:
| Coluna | Descrição |
|---|---|
group_name, procedure_name |
Localização lógica |
version_number, version_label, file_name, file_path |
Rastreabilidade do snapshot |
checksum |
sha256 do conteúdo aplicado |
execution_status |
success ou failed |
execution_time_ms, error_message |
Métricas/erro da execução |
applied_at, rolled_back_at |
Auditoria |
is_current |
Flag da versão atualmente ativa |
Filosofia
- Full-state, não patch: cada snapshot contém o SQL completo da procedure. Simples de rollback, simples de auditar.
- Git como fonte da verdade:
current.sqlé o arquivo que o time edita e mergeia. O banco segue o git, não o contrário. - Sem conflito de snapshots em paralelo: nomes baseados em timestamp eliminam colisões quando múltiplos devs trabalham na mesma procedure.
- Idempotência no DDL: escreva
CREATE OR REPLACE/DROP ... IF EXISTSconforme o driver. - Uma fonte viva:
current.sqlé o único arquivo editável; o resto é histórico.
Licença
MIT