dbt-firebird
Execute dbt-core diretamente em bancos Firebird.
O primeiro adaptador dbt para o Firebird — construído sobre o driver oficial firebird-driver.
Modelos SQL versionados, testes de qualidade de dados, documentação gerada, snapshots (SCD2) e materializações incrementais para Firebird 3.0, 4.0 e 5.0 — transações corretas, swap atômico de tabelas e tolerância a concorrência.
Status: versão 0.1.1, validada contra Firebird 5.0.3 (incluindo uma base real de ~66 GB com 3.144 tabelas — ver
RELATORIO_TESTES_BANCO_REAL.md) e pela matriz de CI Firebird 3/4/5 × Python 3.9/3.12.
Sumário
- Requisitos
- Instalação
- Preparando um banco de teste
- Configurando a conexão (profiles.yml)
- Seu primeiro projeto dbt do zero
- O que cada materialização faz
- Comandos do dbt e o que esperar de cada um
- Testes de qualidade de dados
- Snapshots: histórico de mudanças (SCD2)
- Solução de problemas
- Como o adaptador funciona por dentro
- Limitações conhecidas
- Desenvolvimento e contribuição
- Licença
Requisitos
| Item | Versão | Observação |
|---|---|---|
| Firebird | 3.0, 4.0 ou 5.0 | Servidor acessível por TCP (padrão porta 3050) ou acesso direto ao arquivo |
| Python | 3.9 a 3.12 | No Windows, marque "Add Python to PATH" na instalação |
| dbt-core | 1.8+ | Instalado junto do adaptador no passo seguinte |
| fbclient | opcional | A biblioteca cliente do Firebird (fbclient.dll / libfbclient.so) já vem com a instalação do servidor; em máquinas sem servidor instale o pacote "Firebird Client" |
Instalação
pip install dbt-firebird
Isso instala o adaptador e as dependências (dbt-adapters,
firebird-driver). Instale também o dbt-core:
pip install dbt-core
dbt --version
A saída deve listar o adaptador:
Plugins:
- firebird: 0.1.1
Alternativa, instalando direto do código-fonte (para testar mudanças não publicadas):
pip install git+https://github.com/flozer/dbt-firebird.git
Preparando um banco de teste
Se você já tem um banco Firebird, pule para a próxima seção. Para criar um
banco vazio de testes, use o isql (vem com o Firebird):
C:\> isql -user SYSDBA -password masterkey
SQL> CREATE DATABASE 'C:\dados\meu_teste.fdb';
SQL> EXIT;
Ou por qualquer ferramenta gráfica (IBExpert, FlameRobin, DBeaver).
O caminho do arquivo
.fdbé do ponto de vista do servidor. Se o dbt rodar na mesma máquina do Firebird, é o caminho normal do Windows (C:\dados\meu_teste.fdb). Se o servidor for remoto, é o caminho no servidor (/dados/meu_teste.fdbem Linux, por exemplo).
Configurando a conexão (profiles.yml)
Atalho: se você está criando um projeto novo, o
dbt initfaz isso para você — ele listafirebirdcomo opção, pergunta host, porta, caminho, usuário, senha, charset e threads, grava oprofiles.ymle já roda odbt debugno final. A referência completa dos campos está abaixo, útil para conferir, ajustar ou configurar projetos existentes manualmente.
O dbt guarda as conexões em ~/.dbt/profiles.yml (Windows:
C:\Users\SEU_USUARIO\.dbt\profiles.yml). Exemplo mínimo para uma base local:
meu_projeto:
target: dev
outputs:
dev:
type: firebird
path: C:/dados/meu_teste.fdb
user: SYSDBA
password: masterkey
threads: 1
Atenção às barras: use
/(barra normal) no caminho, não\.
Todas as opções
| Opção | Obrigatória | Padrão | O que significa |
|---|---|---|---|
type |
sim | — | Sempre firebird (nome do adaptador) |
path |
sim | — | Caminho do arquivo .fdb visto pelo servidor |
host |
não | (local) | Host/IP do servidor. Omitir = conexão direta ao arquivo na mesma máquina |
port |
não | 3050 |
Porta TCP do servidor |
user |
sim | — | Usuário do banco (padrão de instalação: SYSDBA) |
password |
sim | — | Senha (padrão de instalação: masterkey) |
charset |
não | UTF8 |
Charset da conexão — ver tabela abaixo |
role |
não | — | Role (papel) do Firebird, se você usa |
lock_timeout |
não | 10 |
Segundos que um statement espera por lock de outra transação antes de falhar (0 = não espera; -1 = espera indefinidamente) |
threads |
não | 1 |
Execuções paralelas. 1 é recomendado (o Firebird não tem schemas; mais threads compartilham o mesmo namespace) |
database |
não | nome do arquivo | Rótulo lógico interno do dbt — não afeta o SQL gerado |
schema |
não | main |
Rótulo lógico interno do dbt — o Firebird não tem schemas; nunca é usado no SQL |
Exemplo com servidor remoto:
type: firebird
host: 192.168.0.10
port: 3050
path: /dados/producao.fdb
user: SYSDBA
password: masterkey
charset: WIN1252
threads: 1
Qual charset usar?
| Situação | charset |
|---|---|
| Base criada em Firebird 3+ com UTF8, ou dados só com ASCII (sem acento) | UTF8 (padrão) |
Base antiga/restaurada com charset NONE e acentuação portuguesa |
WIN1252 |
| Base em ISO8859-1 | ISO8859_1 |
Se vir erros UnicodeDecodeError: 'utf-8' codec can't decode... ao rodar,
troque o charset para WIN1252 — é o caso mais comum em bases legadas
brasileiras.
Valide a conexão
dbt debug
O que esperar: Connection test: OK connection ok e All checks passed!.
Se falhar aqui, é conexão (host/path/senha) — não adianta seguir.
Seu primeiro projeto dbt do zero
mkdir meu_projeto && cd meu_projeto
Crie o dbt_project.yml:
name: meu_projeto
version: "1.0"
config-version: 2
profile: meu_projeto
model-paths: ["models"]
seed-paths: ["seeds"]
Crie um primeiro modelo em models/clientes.sql:
select
codigo,
nome,
cidade
from CLIENTES
CLIENTESé uma tabela que já existe no seu banco. Modelos podem ler tabelas reais diretamente ou referenciar outros modelos com{{ ref('outro_modelo') }}.
Rode:
dbt run
O que esperar:
1 of 1 START sql table model main.clientes ................ [RUN]
1 of 1 OK created sql table model main.clientes ........... [OK in 0.15s]
Done. PASS=1 WARN=0 ERROR=0 SKIP=0 NO-OP=0 TOTAL=1
Isso criou a tabela clientes no seu banco (o nome do modelo vira o nome
da tabela). Mude o modelo para select 1 as id e rode de novo — o dbt
substitui a tabela pelos dados novos.
Onde estão as tabelas criadas pelo dbt?
No Firebird, identificadores escritos sem aspas no SQL são convertidos
para MAIÚSCULAS. O dbt (convenção de todos os adaptadores, igual faz no
Postgres) cria seus objetos com o nome entre aspas e em minúsculas —
o modelo clientes vira a tabela "clientes". Consequências práticas:
select * from "clientes"; -- ✅ correto (com aspas)
select * from clientes; -- ❌ procura CLIENTES, que não existe
Ferramentas gráficas (FlameRobin, IBExpert, DBeaver) mostram os nomes minúsculos na lista de tabelas — costumam aparecer agrupados após os nomes maiúsculos legados. Consultas que juntam tabelas dbt com tabelas reais funcionam normalmente misturando os dois estilos:
select c.customer_name, g.descricao
from "marts_customers" c
join ENG_GRUMAT g on g.tag = c.grupo_tag
Nota sobre IBExpert e afins: os objetos minúsculos estão registrados no catálogo (mesmos metadados das tabelas maiúsculas:
relation_type,system_flag, owner idênticos) e funcionam por SQL — só são fáceis de perder de vista na árvore do explorador, porque na ordenação por bytes aparecem depois de todos os nomes maiúsculos. Se não aparecer nem no fim da lista, atualize a árvore (refresh) e procure no grupo de nomes citados/entre aspas.
Prefere nomes no estilo Firebird (sem aspas/maiúsculos)? Configure o
alias do modelo/seed em maiúsculas — o dbt criará "TABELA", que é
exatamente o nome que uma consulta sem aspas encontra:
-- dbt_project.yml
seeds:
meu_projeto:
store:
+alias: STORE
Se já existir a versão minúscula (
"store"), derrube-a antes de mudar o alias — o dbt se recusa a adivinhar entre duas tabelas que só diferem no caso do nome.
O que cada materialização faz
A materialização é como o dbt materializa o modelo no banco. Configure por arquivo, por pasta ou no próprio modelo:
{{ config(materialized='table') }}
select ...
| Materialização | O que faz no Firebird | Quando usar |
|---|---|---|
view (padrão de projeto novo) |
CREATE OR ALTER VIEW — consulta guardada, sem dados duplicados |
Camadas de preparação (staging); o Firebird reescreve a query a cada leitura |
table |
Cria a tabela e insere os dados; ao re-rodar, substitui atomicamente | Resultados finais (marts) consumidos por dashboards |
incremental |
Na 1ª vez cria a tabela; nas seguintes processa só o novo | Tabelas grandes que crescem (events, vendas) |
seed |
Carrega um CSV para uma tabela | Listas pequenas de referência, parâmetros de teste |
snapshot |
Mantém o histórico de mudanças de uma tabela (SCD2) | Auditoria: como o registro estava em cada época |
ephemeral |
Não cria nada; vira uma CTE dentro dos modelos que a usam | Intermediários que ninguém consulta direto |
table — substituição atômica
Ao re-rodar um modelo table, o adaptador cria os dados numa tabela de apoio
(__dbt_tmp), depois faz DELETE + INSERT + DROP numa única transação:
quem estiver lendo a tabela nunca vê estado vazio (MVCC do Firebird). Se a
estrutura de colunas mudou, ele recria a tabela do zero (aí há uma janela
curta — e views dependentes precisam ser recriadas; veja
Solução de problemas).
incremental — só o que mudou
{{
config(
materialized='incremental',
unique_key='id'
)
}}
select id, nome, valor, data_venda
from VENDAS
{% if is_incremental() %}
where data_venda > (select max(data_venda) from {{ this }})
{% endif %}
Na primeira execução cria a tabela completa. Nas seguintes, roda a query
(com o filtro is_incremental ativo) e aplica a estratégia:
incremental_strategy |
O que faz | Requer |
|---|---|---|
append (padrão) |
Insere as novas linhas no final | nada |
delete+insert |
Apaga as linhas com as mesmas chaves e reinsere | unique_key |
merge |
Atualiza as linhas existentes e insere as novas (MERGE nativo) | unique_key |
Também suporta on_schema_change (ignore/fail/append/sync_all) para
quando as colunas do modelo mudam, e full_refresh=True/dbt run --full-refresh para reconstruir do zero.
seed — CSV para tabela
Coloque produtos.csv em seeds/ e rode dbt seed. O adaptador detecta os
tipos das colunas (inteiro, decimal, data, texto...) e dimensiona os
VARCHAR pelo conteúdo real do arquivo. Re-rodar um seed substitui os
dados atomicamente (delete + insert na mesma transação).
Configurações globais por pasta
# dbt_project.yml
models:
meu_projeto:
staging:
+materialized: view
marts:
+materialized: table
Comandos do dbt e o que esperar de cada um
Todos os comandos abaixo, executados dentro da pasta do projeto:
| Comando | O que faz | O que esperar |
|---|---|---|
dbt debug |
Testa a conexão e o ambiente | All checks passed! |
dbt parse |
Só valida o projeto (sem tocar no banco) | Performance info: ... |
dbt seed |
Carrega os CSVs de seeds/ |
Done. PASS=n |
dbt run |
Compila e materializa os modelos | Done. PASS=n ERROR=0 |
dbt run -s nome_modelo |
Roda só um modelo | PASS=1 |
dbt run --full-refresh |
Reconstrói tudo do zero | PASS=n (tabelas recriadas) |
dbt test |
Executa os testes de dados (próxima seção) | PASS=x ERROR=0 — falhas indicam dados que violam as regras |
dbt snapshot |
Atualiza os snapshots | Done. PASS=n |
dbt docs generate |
Gera a documentação lendo o catálogo do banco | Catalog written to .../catalog.json |
dbt docs serve |
Abre a documentação no navegador | Site em http://localhost:8080 |
Cada modelo tem o SQL compilado em target/compiled/ — útil para depurar
exatamente o que foi executado no Firebird.
Testes de qualidade de dados
Declare regras num schema.yml junto dos modelos:
version: 2
models:
- name: clientes
description: "Clientes ativos"
columns:
- name: codigo
tests:
- unique # não pode repetir
- not_null # não pode ser nulo
- name: cidade
tests:
- not_null
dbt test roda cada regra como um SELECT de contagem. Teste reprovado
não é erro do adaptador — é o dbt avisando que encontrou dados que violam a
regra (por exemplo, um unique que falhou porque a tabela real tem chaves
duplicadas). Nos nossos testes contra uma base real, isso detectou uma chave
duplicada genuína — exatamente o propósito.
Snapshots: histórico de mudanças (SCD2)
Um snapshot guarda como o registro estava a cada execução. Exemplo sobre
a tabela real CLIENTES:
Crie snapshots/clientes_snapshot.sql:
{% snapshot clientes_snapshot %}
{{
config(
unique_key='codigo',
strategy='check',
check_cols=['nome', 'cidade'],
invalidate_hard_deletes=True
)
}}
select codigo, nome, cidade from CLIENTES
{% endsnapshot %}
| Opção | Significado |
|---|---|
strategy='check' |
Compara as colunas listadas em check_cols para detectar mudança |
strategy='timestamp' |
Alternativa: usa uma coluna de data de atualização (updated_at) |
check_cols='all' |
Compara todas as colunas |
unique_key |
Chave que identifica o registro |
invalidate_hard_deletes=True |
Registro que sumiu da origem é marcado como apagado (em vez de ignorado) |
Cada dbt snapshot compara a origem com a tabela clientes_snapshot:
linha nova → inserida; linha alterada → a antiga recebe dbt_valid_to (fechada)
e a nova é inserida; linha que sumiu → marcada como deletada. Execuções
repetidas sem mudanças não alteram nada.
Solução de problemas
| Sintoma | Causa provável | Solução |
|---|---|---|
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe1 |
Base legada (charset NONE) com acentuação WIN1252 |
charset: WIN1252 no profile |
string truncation / expected length N ao rodar snapshot ou modelo |
Overfill: a tabela real tem dados maiores que o declarado (ex.: coluna VARCHAR(3) com 6 chars — comum em bases restauradas) | Problema da base: corrija a coluna (ALTER TABLE ... ALTER COLUMN ... TYPE) ou filtre no modelo |
Table X already exists em snapshot |
Execução anterior falhou no meio | Reexecute — o adaptador limpa a tabela de apoio sozinho |
cannot delete COLUMN ... there are dependencies |
Tentando recriar uma tabela que tem views dependentes | Re-rodar sem mudar colunas usa o swap atômico (sem drop). Mudando colunas, derrube as views dependentes antes |
Token unknown - line 1, column 13 - SELECT (ou similar) |
Modelo com SQL sem FROM (o Firebird exige) |
Adicione from RDB$DATABASE à query |
field ... exceeds maximum record size |
Modelo com muitas colunas de texto largas somando > 64 KB por linha | Reduza os VARCHARs com cast(coluna as varchar(200)) no modelo |
deadlock / update conflicts with concurrent update |
Outra transação (aplicação, outro modelo em paralelo, trigger de auditoria) alterou a mesma linha/página enquanto o dbt escrevia | O adaptador já contorna automaticamente: transações em READ COMMITTED + lock_timeout de espera + até 3 tentativas com backoff. Se ainda ocorrer, reduza a concorrência (threads: 1) ou aumente lock_timeout no profile |
| Nomes de modelo com +31 caracteres no Firebird 3 | Limite de identificador de 31 bytes do FB3 | Encurte o nome (FB4/5 aceitam 63) |
Table unknown logo após CREATE no mesmo script |
DDL não é visível ao DML na mesma transação | É o dbt que orquestra — se acontecer em hooks, comite entre eles |
| "A tabela do meu modelo/seed não existe no banco" | Ela existe, em minúsculas e com aspas ("store"); consulta sem aspas procura STORE |
Consulte com aspas (select * from "store") — ver seção Onde estão as tabelas criadas pelo dbt? |
Como o adaptador funciona por dentro
O Firebird tem características que exigiram adaptações específicas:
-
Snapshot de metadados: statements compilam contra o estado de metadados commitado antes da transação — DDL executado na mesma transação não é visível para o DML seguinte. Por isso o adaptador commita entre CREATE e INSERT (duas transações por materialização). Os dados, por outro lado, são lidos em READ COMMITTED (com espera limitada por
lock_timeout) — isolamento adequado a ETL concorrente, que evita o clássico "update conflicts with concurrent update" do SNAPSHOT. -
Sem
CREATE TABLE AS SELECT: o adaptador prepara (PREPARE) a query do modelo — sem executá-la — para extrair nomes e tipos de colunas, gera oCREATE TABLEe depois carrega comINSERT INTO ... SELECT. -
Substituição atômica de tabelas: como não há
RENAME TABLE, a re-materialização cria uma tabela staging__dbt_tmp, carrega os dados e, se as colunas forem compatíveis, fazDELETE + INSERT + DROPna mesma transação — leitores (MVCC) nunca veem a tabela vazia. -
Sem schemas: o namespace do Firebird é único.
database/schemado perfil são rótulos lógicos; identificadores são sempre renderizados entre aspas duplas. -
Colunas de seed não são quotadas: identificadores não-quotados são armazenados em maiúsculas — colunas de seed ficam alcançáveis por SQL sem aspas (padrão do dialeto).
-
Tipos de texto dimensionados com segurança:
VARCHARUTF8 consome 4 bytes/char e o limite de registro é ~64 KB; seeds e CTAS dimensionam osVARCHARpelo tamanho real (via tabelas de sistema ePREPARE), com piso de segurança para expressões cuja largura o engine descreve de forma imprecisa.
Limitações conhecidas
- Sem
MD5(): snapshots usamHASH()(FNV1a-64,BIGINT) para odbt_scd_id. renamede tabelas/views não existe no Firebird; macros que dependem disso (rename_relation) retornam erro explícito.- DROP TABLE bloqueado por dependências: recriar uma tabela cujo schema mudou exige que views dependentes sejam derrubadas antes (o swap atômico evita o drop no caso comum).
- Contratos (
contract: enforced): melhor esforço; a comparação de tipos depende dos nomes exatos de tipos do Firebird no YAML. - CANCEL de queries não é suportado pelo firebird-driver (
is_cancelable = False). - Identificadores com mais de 31 caracteres: OK no Firebird 4/5; modelos dbt
com sufixos internos (
__dbt_tmp) devem respeitar o limite da sua versão (31 bytes no Firebird 3).
Desenvolvimento e contribuição
Veja CONTRIBUTING.md para ambiente de desenvolvimento,
como rodar as suítes oficiais do dbt-tests-adapter (.github/workflows/ci.yml
roda a matriz Firebird 3/4/5 × Python 3.9/3.12) e as regras do dialeto.
Projeto de exemplo completo em example/ e relatório de testes
contra base real de produção em
RELATORIO_TESTES_BANCO_REAL.md.
Autor
Fernando Lozer — GitHub @flozer · LinkedIn /fernandolozer
Apoie o projeto
Se o dbt-firebird ajuda o seu trabalho, considere apoiar o desenvolvimento contínuo.
Licença
Release files for dbt-firebird 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| dbt_firebird-0.1.1.tar.gz | 44.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| dbt_firebird-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 87.4 kB
Release files / dbt_firebird-0.1.1.tar.gz
| Download URL | dbt_firebird-0.1.1.tar.gz |
|---|---|
| Size | 44.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
087999eb75ac6200565e459641215a90e211a36d41c712c9f0f3f3d4ed1be017
|
|
BLAKE2b-256 checksum How to use checksums |
d60a36beeb8cd55512a45f0f38453fd2863aa18d17a1c97e66c635c621af1da5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.
Transparency logRelease files / dbt_firebird-0.1.1-py3-none-any.whl
| Download URL | dbt_firebird-0.1.1-py3-none-any.whl |
|---|---|
| Size | 43.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7b4a80699eac3d0cb6a3154950bb987961d4bd6d81f7fa841b964d473852d092
|
|
BLAKE2b-256 checksum How to use checksums |
2b0938407ad94609c9a4174a5f5060e622eacdb83e59a7270c2c051f211ea3e2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.
Transparency log