TL;DR
Partimos de um driver Firebird para Laravel já funcional e maduro e o levamos a um nível de paridade real com os drivers oficiais (MySQL, PostgreSQL, SQL Server). Resultado: de uma estimativa de ~51% para ~96% de paridade funcional, 216 testes rodando contra Firebird 3, 4 e 5 (dialetos 1 e 3), ~87% de cobertura de linhas e CI verde em toda a matriz (PHP 8.3/8.4 × Firebird 3/4/5).
Este trabalho se apoia em uma base sólida mantida pela comunidade. Veja a seção Créditos — nada disto começou do zero.
A base do código
O benson/laravel-firebird é um fork de harrygulliford/laravel-firebird — que por sua vez descende do pioneiro jacquestvanzuydam/laravel-firebird. Boa parte da fundação (query grammar, schema grammar, integração com o Eloquent, paginação FIRST/SKIP, INSERT ... RETURNING, MERGE para upsert, mutations com join via RDB$DB_KEY) já vinha desse trabalho anterior. O que descrevemos aqui é a camada de refinamento e paridade que construímos em cima dessa base.
Por que isso importa
O Firebird tem particularidades que nenhum ORM genérico resolve sozinho: identificadores case-sensitive quando entre aspas, DATE sem componente de hora no dialeto 3, NUMERIC/DECIMAL armazenado como inteiro escalado, ausência de lastInsertId(), dialeto 1 sem identificadores delimitados… Cada uma dessas pode virar um bug silencioso em produção. Atacamos isso de frente.
Correções de bugs reais (Firebird-specific)
1. Perda de escala em DECIMAL/NUMERIC (o mais traiçoeiro).
Inserir o inteiro PHP 40 numa coluna DECIMAL(5,2) gravava 0.40 — erro de fator 100. A causa: o Laravel vincula inteiros com PDO::PARAM_INT, e o pdo_firebird os interpreta como o valor bruto escalado da coluna. Passamos a vincular inteiros como PARAM_STR, e o Firebird converte o literal para o tipo da coluna com a escala correta. Strings, floats, where e booleanos seguem intactos.
2. dropAllTables() com nomes legados em maiúsculas.
Tabelas criadas sem aspas ficam em MAIÚSCULAS no catálogo (AUDITORIA). A introspecção devolvia o nome em minúsculas e o DROP TABLE "auditoria" falhava com -607 Table does not exist. Agora o drop usa o nome real do catálogo, funcionando para tabelas minúsculas (criadas com aspas), MAIÚSCULAS (legadas) e mixed case no mesmo banco.
3. Cast date gerando erro de conversão.
No dialeto 3, DATE não tem hora. O Laravel formata toda data com Y-m-d H:i:s, e o Firebird rejeitava com -413 conversion error from string. Entregamos um trait SerializesFirebirdDates (e um model base) que grava colunas date sem o componente de hora, mantendo datetime com hora — usando o cast que você já declara.
4. exists() em consultas com UNION.
O FIRST 1 era aplicado só ao primeiro braço da union. Passamos a envolver a query num derived table antes de limitar.
Recursos por versão do servidor
Adicionamos detecção de versão (server_version, com override por config) que habilita:
- Identity columns no Firebird 3+: auto-incremento via
GENERATED BY DEFAULT AS IDENTITY — sem o par generator + trigger. Em servidores 2.5, mantemos o caminho legado automaticamente.
- Tipos com fuso no Firebird 4+:
timestampTz(), timeTz(), dateTimeTz() geram WITH TIME ZONE.
- Identificadores de 63 caracteres (FB4+) vs 31 (FB3-), com sufixo hash anti-colisão para nomes longos.
- ALTER COLUMN de nulidade adaptado: usa
SET/DROP NOT NULL no FB3+ e a tabela de sistema no FB2.5.
- groupLimit (limit em eager loading do Eloquent) via
ROW_NUMBER() no FB3+, com erro claro onde não há window functions.
Recursos novos
- insertOrIgnore correto: deixa de usar heurística de coluna key/id e passa a ignorar violação de qualquer constraint única;
firstOrCreate/createOrFirst passam a funcionar em condição de corrida (via UniqueConstraintViolationException).
- Nomenclatura de constraints/índices configurável (
index_names): defina PK_{table}, FK_{table}_{columns}, etc. — aplicado inclusive à PK inline de colunas identity. Ótimo para padronizar bancos legados.
- uniqueIndex() / dropUniqueIndex(): índice único sem constraint (
CREATE UNIQUE INDEX).
- COMMENT ON para tabelas e colunas.
- auto_increment na introspecção (lê
rdb$identity_type).
- Listas IN > 1499 itens divididas automaticamente (limite do FB < 5).
- Reconexão automática: reconhecemos as mensagens de queda do Firebird, habilitando o retry do Laravel.
- Colunas computadas (
virtualAs()/storedAs()): geram COMPUTED BY (sempre virtuais no Firebird).
- Tabelas temporárias (
$table->temporary()): geram GLOBAL TEMPORARY TABLE ... ON COMMIT PRESERVE ROWS (antes lançava exceção).
- json() como BLOB SUB_TYPE TEXT (em vez de
VARCHAR(8191) que truncava).
Resumo do que foi implementado
| Categoria | Item | Tipo |
| Bug | Bind de inteiro em DECIMAL/NUMERIC (escala) | Correção |
| Bug | dropAllTables() com nomes maiúsculos/mistos | Correção |
| Bug | Cast date com erro de conversão (-413) | Correção |
| Bug | exists() em consultas com UNION | Correção |
| Versão | Identity columns (FB3+) com fallback generator+trigger (FB2.5) | Recurso |
| Versão | Tipos WITH TIME ZONE (FB4+) | Recurso |
| Versão | Identificadores de 63 caracteres (FB4+) + hash anti-colisão | Recurso |
| Versão | change() de nulidade por versão do servidor | Recurso |
| Versão | groupLimit via ROW_NUMBER() (FB3+) | Recurso |
| Versão | Detecção de versão (server_version / feature detection) | Recurso |
| Novo | insertOrIgnore por constraint real (qualquer unique) | Recurso |
| Novo | Nomenclatura configurável (index_names) | Recurso |
| Novo | uniqueIndex() / dropUniqueIndex() | Recurso |
| Novo | COMMENT ON (tabela/coluna) | Recurso |
| Novo | auto_increment na introspecção | Recurso |
| Novo | Chunking de IN > 1499 itens | Recurso |
| Novo | Reconexão automática (lost connection) | Recurso |
| Novo | UniqueConstraintViolationException (firstOrCreate) | Recurso |
| Novo | Trait SerializesFirebirdDates (cast date sem hora) | Recurso |
| Novo | Colunas computadas (virtualAs/storedAs → COMPUTED BY) | Recurso |
| Novo | Tabelas temporárias (temporary() → GTT) | Recurso |
| Novo | json() como BLOB SUB_TYPE TEXT | Melhoria |
Antes × Depois (paridade por funcionalidade)
Legenda: ✅ pleno · ⚠️ parcial · ❌ ausente/quebrado · 🚫 limitação do próprio Firebird
| Funcionalidade | Antes | Depois |
| Query builder core (select/where/join/group/order/union) | ✅ | ✅ |
| Paginação (limit/offset, paginate, cursor) | ✅ | ✅ |
| whereDate/Time/... | ✅ | ✅ |
| Locks forUpdate | ✅ | ✅ |
| Stored procedures | ✅ | ✅ |
| insertGetId / insertUsing | ✅ | ✅ |
| upsert (MERGE) | ✅ | ✅ |
| Update/Delete com join | ✅ | ✅ |
| truncate | ✅ | ✅ |
| Schema CRUD (colunas/índices/FK) | ✅ | ✅ |
| Introspecção (tables/columns/indexes/FKs/views) | ✅ | ✅ |
| Transações / savepoints | ✅ | ✅ |
| exists com union | ⚠️ | ✅ |
| insertOrIgnore (qualquer unique) | ⚠️ | ✅ |
| Identity columns (FB3+) | ❌ | ✅ |
| Identificadores 63 chars + anti-colisão | ⚠️ | ✅ |
| change() nulidade por versão | ⚠️ | ✅ |
| Tipos com timezone (FB4+) | ❌ | ✅ |
| Comments (tabela/coluna) | ❌ | ✅ |
| whereLike(caseSensitive) | ❌ | ⚠️ |
| whereIn > 1499 | ❌ | ✅ |
| groupLimit (eager load) | ❌ | ✅ |
| json() armazenamento | ⚠️ | ⚠️ |
| Reconnect / lost connection | ❌ | ✅ |
| UniqueConstraintViolationException | ❌ | ✅ |
| Cast date sem hora | ❌ | ✅ |
| Bind de int em DECIMAL | ❌ | ✅ |
| dropAllTables (case misto) | ⚠️ | ✅ |
| auto_increment na introspecção | ❌ | ✅ |
| server_version / feature detection | ❌ | ✅ |
| Nomenclatura custom (index_names) | ❌ | ✅ |
| uniqueIndex sem constraint | ❌ | ✅ |
| Colunas computadas (COMPUTED BY) | ❌ | ✅ |
| Tabelas temporárias (GTT) | ❌ | ✅ |
| JSON ops / full-text / spatial / rename table | 🚫 | 🚫 |
Cobertura de testes
- 216 testes, executados contra Firebird real nos dialetos 1 e 3.
- ~87% de cobertura de linhas (medida no dialeto 3; a combinada com o dialeto 1 é maior).
- CI em matriz: Firebird 3/4/5 × PHP 8.3/8.4 — 6 jobs, todos verdes.
- Cobrimos cache/insert/upsert via banco, além de checar presença do valor correto (não só ausência do errado) — foi assim que pegamos o bug de escala do DECIMAL.
Cobertura de funcionalidade (paridade com os drivers oficiais)
| Paridade estimada |
| Antes | ~51% |
| Depois | ~96% |
Os ~4% restantes são limitações do próprio Firebird (operações JSON, full-text, tipos espaciais, rename de tabela) — não há o que um driver faça; reportamos com erro claro.
Requisitos
PHP 8.2+ · Laravel 12/13 · Firebird 2.5 → 5.0 (dialetos 1 e 3).
Conclusão
O objetivo foi tornar o Firebird um cidadão de primeira classe no Laravel — não um "quase funciona". Se você usa Firebird com PHP, feedback e contribuições são muito bem-vindos.
Open source se sustenta com comunidade
Este driver — como tantas ferramentas que usamos todo dia — só existe porque pessoas decidiram compartilhar o seu trabalho abertamente. Cada correção, cada teste e cada linha de documentação nasce de horas que alguém doou para que o próximo desenvolvedor tivesse um caminho mais fácil. O Firebird em si é um exemplo disso: um banco maduro, gratuito e mantido pela comunidade há décadas.
Manter um projeto open source vivo dá trabalho: responder issues, testar contra várias versões, escrever documentação, garantir compatibilidade. Se este driver te poupou tempo, considere retribuir — de várias formas, todas valiosas:
- ⭐ Dê uma estrela no repositório e compartilhe com quem usa Firebird.
- 🐛 Abra issues com casos reais e mande pull requests.
- 📝 Melhore a documentação e ajude outros desenvolvedores.
- 💜 Patrocine o desenvolvimento: github.com/sponsors/bensonsbc
Patrocínio não é só sobre dinheiro — é sobre tornar sustentável o tempo dedicado a manter o ecossistema Firebird + PHP saudável e evoluindo. Qualquer apoio, de qualquer tamanho, faz diferença real.
Créditos
Este driver é fruto de anos de trabalho da comunidade Firebird + PHP. Reconhecimento a quem construiu a fundação:
- Jacques van Zuydam — autor original do laravel-firebird
- Harry Gulliford — mantenedor do fork que serve de base direta a este trabalho, com a maior parte da implementação atual.
- Contribuidores: Popa Marius Adrian (mariuz), Ricardo Seriani, Victor Vilella, Donny Kurnia, Felipi Franco, Johan Weultjes, Simon Rasmussen, fesoft, selmo47, Maitrepylos, entrxconteudo