A primeira extensao Firebird publicada no registro DuckDB Community Extensions.
Este guia mostra como usar a extensao firebird para consultar bases
Firebird a partir do DuckDB, criar uma camada local de analytics em
arquivo .duckdb, materializar dados em tabelas rapidas e exportar
resultados para Parquet/CSV.
O publico-alvo e o analista de dados que conhece SQL, mas ainda nao usa DuckDB no dia a dia. A ideia principal e simples:
- O Firebird continua sendo o sistema transacional.
- O DuckDB consulta o Firebird quando voce precisa de dados vivos.
- Para analises pesadas e repetidas, voce cria tabelas locais no DuckDB.
- Dashboards, notebooks e queries exploratorias passam a bater no
arquivo
.duckdb, que e muito mais rapido para analytics.
Este guia segue os conceitos documentados pelo DuckDB:
CREATE VIEW: views sao consultas salvas; elas nao sao materializadas fisicamente.CREATE TABLE:CREATE TABLE ... AS SELECTcria uma tabela fisica a partir de uma consulta.INSERT:INSERT INTO ... SELECTadiciona o resultado de uma consulta em uma tabela existente.COPY: importa/exporta dados entre DuckDB e arquivos como CSV, Parquet e JSON.ATTACH: anexa outro catalogo ao DuckDB. Esta extensao usa o mesmo modelo para expor Firebird como catalogo read-only.Indexese performance de indexes: DuckDB cria zonemaps automaticamente; indices ART ajudam em filtros muito seletivos.EXPLAIN/EXPLAIN ANALYZE: inspeciona plano e tempo real de uma consulta.- DuckDB CLI
e dot commands:
comandos como
.open,.read,.tables,.schema,.timer.
Quando voce abre apenas duckdb, sem caminho de arquivo, pode trabalhar
em memoria. Isso e bom para testes rapidos, mas os objetos somem ao
fechar a sessao.
Para criar um pequeno data mart local, abra um arquivo persistente:
duckdb analytics.duckdbou, no Windows:
duckdb.exe analytics.duckdbTudo que voce criar sem qualificar outro catalogo sera salvo nesse arquivo: tabelas, views, schemas, indices e macros.
No DuckDB, CREATE VIEW salva a query. A view nao guarda os dados. Cada
vez que voce consulta a view, a query roda novamente.
CREATE OR REPLACE VIEW vw_clientes_firebird AS
SELECT *
FROM firebird_scan('C:/dados/empresa.fdb', 'CLIENTES');Isso e excelente para uma camada semantica fina, mas nao acelera uma query pesada se ela continuar lendo o Firebird toda vez.
Para materializar, crie uma tabela:
CREATE TABLE clientes AS
SELECT *
FROM firebird_scan('C:/dados/empresa.fdb', 'CLIENTES');Essa tabela vive dentro do analytics.duckdb. Depois disso, suas
analises leem o DuckDB local, nao o Firebird.
A extensao oferece dois jeitos principais de uso.
Use firebird_scan(...) quando quiser consultar uma tabela especifica:
SELECT *
FROM firebird_scan('C:/dados/empresa.fdb', 'TABPESSOAS')
LIMIT 10;Use ATTACH ... (TYPE firebird) quando quiser navegar a base Firebird
como se fosse um catalogo:
ATTACH 'C:/dados/empresa.fdb' AS fb
(TYPE firebird, user 'APP_READONLY', password 'secret');
SELECT *
FROM fb.main.TABPESSOAS
LIMIT 10;O catalogo Firebird e read-only nesta extensao. Crie tabelas analiticas no catalogo DuckDB local.
A versao publica atual do projeto e v0.6.0. O pedido de publicacao no
catalogo DuckDB Community foi mergeado em
duckdb/community-extensions#1980
e agora aponta para essa tag.
Use o catalogo community oficial do DuckDB em ambientes com internet:
INSTALL firebird FROM community;
LOAD firebird;Use isso em cada ambiente novo. O INSTALL baixa a extensao; o LOAD
carrega a extensao na sessao atual.
Use um arquivo .duckdb_extension gerado localmente, ou baixado dos releases
deste repositorio, apenas para desenvolvimento, testes de mudancas ainda nao
publicadas, ou maquinas offline sem acesso ao catalogo community:
LOAD 'D:/01_Projetos/duckdb-firebird/build/release/extension/firebird/firebird.duckdb_extension';Em algumas builds locais, voce pode precisar iniciar o DuckDB com
-unsigned:
duckdb analytics.duckdb -unsignedSELECT *
FROM duckdb_extensions()
WHERE extension_name = 'firebird';Depois teste a listagem de tabelas:
SELECT *
FROM firebird_tables('C:/dados/empresa.fdb');SELECT *
FROM firebird_scan('C:/legacy/erp.fdb', 'TABPESSOAS')
LIMIT 10;Para bases remotas, prefira o formato URL:
firebird://USUARIO:SENHA@HOST:PORTA/caminho/no/servidor/database.fdb?charset=UTF8
O caminho do banco e o caminho visto pelo servidor Firebird, nao um
caminho local da maquina que executa o DuckDB. Nao use
HOST:PORTA://caminho/no/servidor/database.fdb; esse nao e um formato
valido de conexao Firebird para esta extensao.
SELECT *
FROM firebird_scan(
'firebird://APP_READONLY:secret@db.example.com:3050/path/to/database.fdb?charset=UTF8',
'CUSTOMER'
)
LIMIT 10;O formato equivalente do libfbclient coloca a porta depois do host com
/PORTA:
SELECT *
FROM firebird_tables(
'database=db.example.com/3050:/path/to/database.fdb;user=APP_READONLY;password=secret;charset=UTF8'
);ATTACH aceita as mesmas connection strings:
ATTACH 'firebird://APP_READONLY:secret@db.example.com:3050/path/to/database.fdb?charset=UTF8'
AS fb (TYPE firebird);Prefira parametros nomeados quando quiser separar caminho, usuario e senha:
SELECT *
FROM firebird_scan(
'C:/legacy/erp.fdb',
'TABPESSOAS',
user='APP_READONLY',
password='secret',
charset='UTF8'
)
LIMIT 10;Nota de seguranca: connection strings e SQL podem ir para historico do terminal, notebooks e logs. Em producao, use usuario Firebird somente-leitura e nao commite senhas reais em scripts.
Muitas bases Firebird antigas foram criadas com CHARACTER SET NONE.
Nessas bases, os bytes de texto podem ter sido gravados como Windows-1252
mesmo sem o banco declarar isso formalmente.
A extensao usa none_encoding='win1252' por padrao porque esse e o caso
mais comum em ERPs legados brasileiros e ocidentais.
Na maioria dos casos, nao passe nada:
SELECT *
FROM firebird_scan('C:/legacy/erp.fdb', 'TABPESSOAS')
LIMIT 10;Se precisar ser explicito:
SELECT *
FROM firebird_scan(
'C:/legacy/erp.fdb',
'TABPESSOAS',
none_encoding='win1252'
);Alternativas:
-- Falha se encontrar bytes que nao sejam UTF-8 valido.
SELECT *
FROM firebird_scan('C:/dados/empresa.fdb', 'TABELA',
none_encoding='strict');
-- Latin-1 puro.
SELECT *
FROM firebird_scan('C:/dados/empresa.fdb', 'TABELA',
none_encoding='iso8859_1');
-- Preserva bytes brutos como BLOB.
SELECT *
FROM firebird_scan('C:/dados/empresa.fdb', 'TABELA',
none_encoding='blob');Importante: charset= e none_encoding= nao sao a mesma coisa.
charset= controla o charset do cliente Firebird na conexao.
none_encoding= controla como a extensao interpreta bytes de colunas
Firebird declaradas como CHARACTER SET NONE.
Para fatias grandes onde voce nao quer trazer a tabela inteira,
combine row_limit e row_offset:
-- Primeira pagina: 1000 linhas.
SELECT *
FROM firebird_scan('C:/legacy/erp.fdb', 'TABENTRADASAIDA',
row_limit=1000);
-- Pagina seguinte: pula 1000, traz proximas 1000.
SELECT *
FROM firebird_scan('C:/legacy/erp.fdb', 'TABENTRADASAIDA',
row_limit=1000, row_offset=1000);Internamente a extensao emite ROWS M+1 TO M+N no Firebird (1-based,
inclusivo). O scan vira serial automaticamente quando ha paginacao,
porque a ordem so faz sentido com um unico produtor. Pedir
partitions > 1 junto com paging explicito e rejeitado no bind.
Caveat importante: isto e paginacao fisica (offset/limit), nao
keyset pagination. Sem ORDER BY server-side, paginas sucessivas
podem repetir ou pular linhas se o Firebird reordenar leituras entre
chamadas. Para resultados estaveis em uma serie de paginas, prefira
materializar uma tabela local e paginar la, ou aplique um
ORDER BY <chave estavel> na consulta.
A extensao empurra para o Firebird os predicados que ela sabe traduzir com seguranca, incluindo (v0.5):
col NOT IN (...)— vira uma clausulaNOT INserver-side.NOT bool_col/bool_col = FALSE— empurrado comoNOT col.col LIKE 'prefixo%'com texto estatico — empurrado.BETWEEN a AND b— DuckDB decompoe em>= AND <=, ambos empurrados.
Exemplos:
-- NOT IN
SELECT COUNT(*)
FROM firebird_scan('C:/legacy/erp.fdb', 'TABENTRADASAIDA')
WHERE CODIGOEMPRESA NOT IN (99, 100, 101);
-- LIKE com prefixo (apostrofo no literal e seguro):
SELECT IDMASTER, OBSERVACOES
FROM firebird_scan('C:/legacy/erp.fdb', 'TABENTRADASAIDA')
WHERE OBSERVACOES LIKE 'CONTA PARA%'
LIMIT 5;Detalhe interno (v0.5): para tipos seguros e valores nao-literais
(parametros, datas, strings com acento ou apostrofo), o filtro vai
para o Firebird como bind variable via XSQLDA de entrada, em vez
de inline no SQL. Isto evita escape errado e permite que o Firebird
reutilize plano. Voce nao precisa fazer nada — a extensao cuida. Em
colunas CHARACTER SET NONE com none_encoding != 'strict', filtros
de texto sao mantidos como residual em DuckDB (a comparacao precisa
do decode da extensao para ser correta).
Comece descobrindo as tabelas:
SELECT *
FROM firebird_tables('C:/legacy/erp.fdb')
ORDER BY table_name;Veja algumas linhas:
SELECT *
FROM firebird_scan('C:/legacy/erp.fdb', 'TABPESSOAS')
LIMIT 20;Conte linhas antes de materializar:
SELECT count(*) AS linhas
FROM firebird_scan('C:/legacy/erp.fdb', 'TABPESSOAS');Olhe tipos inferidos pelo DuckDB:
DESCRIBE
SELECT *
FROM firebird_scan('C:/legacy/erp.fdb', 'TABPESSOAS');Quando usar ATTACH, navegue com nomes qualificados:
ATTACH 'C:/legacy/erp.fdb' AS fb
(TYPE firebird, user 'APP_READONLY', password 'secret');
SHOW DATABASES;
SELECT *
FROM fb.main.TABPESSOAS
LIMIT 20;Quando voce anexa a base como catalogo, as ferramentas padrao do DuckDB enxergam tudo:
ATTACH 'C:/legacy/erp.fdb' AS fb
(TYPE firebird, user 'APP_READONLY', password 'secret');
-- Listagem de tabelas (e views) do catalogo Firebird:
SHOW TABLES FROM fb;
-- Forma da tabela:
DESCRIBE fb.main.TABPESSOAS;
-- Catalogo padrao information_schema, util para BI / dbt / clients ADBC:
SELECT table_catalog, table_schema, table_name, table_type
FROM information_schema.tables
WHERE table_catalog = 'fb'
ORDER BY table_name;
SELECT column_name, data_type, ordinal_position, is_nullable
FROM information_schema.columns
WHERE table_catalog = 'fb' AND table_name = 'TABPESSOAS'
ORDER BY ordinal_position;Pontos a saber:
- Apenas um schema,
main, e exposto, independente deRDB$OWNER_NAMEna origem. Consultarfb.public.Xou outro schema retorna erro. - A extensao normaliza identificadores para upper-case interno, igual
ao Firebird;
fb.main.tabpessoas,fb.main.Tabpessoasefb.main."TABPESSOAS"resolvem para a mesma entrada. is_nullableeminformation_schema.columnsreflete a definicao Firebird (RDB$NULL_FLAGno campo ou no dominio). Colunas declaradasNOT NULLaparecem comoNO.- Views Firebird (
RDB$RELATION_TYPE = 1) aparecem junto com tabelas e podem ser consultadas igual. Noinformation_schema.tables, todas vem hoje comoBASE TABLE— esta simplificacao do catalog layer esta registrada nos testes para ser revisitada.
Views sao boas para padronizar nomes, esconder colunas tecnicas e documentar regras de negocio. Elas continuam lendo o Firebird ao serem consultadas.
CREATE SCHEMA IF NOT EXISTS bronze;
CREATE OR REPLACE VIEW bronze.pessoas AS
SELECT
CODIGO,
NOME,
APELIDO,
CNPJCPF,
CIDADE,
UF
FROM firebird_scan('C:/legacy/erp.fdb', 'TABPESSOAS');Crie views para as tabelas principais:
CREATE OR REPLACE VIEW bronze.entrada_saida AS
SELECT
CODIGOEMPRESA,
CODIGOFILIAL,
CODIGO,
CODIGOPESSOA,
DATAMOVIMENTO,
VALORTOTAL,
TIPO
FROM firebird_scan('C:/legacy/erp.fdb', 'TABENTRADASAIDA');Agora o analista consulta objetos com nomes mais amigaveis:
SELECT *
FROM bronze.pessoas
WHERE UF = 'SP'
LIMIT 100;Como a view nao e materializada, use filtros e projecoes sempre que possivel. A extensao tenta empurrar filtros e colunas para o Firebird quando isso e seguro.
Para performance, crie uma tabela local. Esse e o substituto pratico de "view materializada" no DuckDB.
CREATE SCHEMA IF NOT EXISTS silver;
CREATE OR REPLACE TABLE silver.pessoas AS
SELECT *
FROM bronze.pessoas;Se sua versao/ambiente nao aceitar CREATE OR REPLACE TABLE, use:
DROP TABLE IF EXISTS silver.pessoas;
CREATE TABLE silver.pessoas AS
SELECT *
FROM bronze.pessoas;Crie uma tabela de fatos local:
CREATE OR REPLACE TABLE silver.entrada_saida AS
SELECT *
FROM bronze.entrada_saida
WHERE DATAMOVIMENTO >= DATE '2023-01-01';Depois disso:
SELECT count(*)
FROM silver.entrada_saida;Essa query le dados do arquivo analytics.duckdb, nao do Firebird.
Um desenho simples para analises:
bronze: views ou tabelas quase iguais ao Firebird.silver: tabelas locais limpas, tipadas e filtradas.gold: tabelas agregadas para dashboard e indicadores.
Exemplo:
CREATE SCHEMA IF NOT EXISTS gold;
CREATE OR REPLACE TABLE gold.vendas_mes AS
SELECT
date_trunc('month', DATAMOVIMENTO)::DATE AS mes,
CODIGOEMPRESA,
CODIGOFILIAL,
count(*) AS qtd_movimentos,
sum(VALORTOTAL) AS valor_total
FROM silver.entrada_saida
WHERE TIPO = 'S'
GROUP BY 1, 2, 3
ORDER BY 1, 2, 3;Agora o dashboard consulta gold.vendas_mes, que costuma ser muito menor
e muito mais rapido que a tabela transacional original.
Para bases pequenas ou rotinas noturnas, o refresh completo e o mais simples e confiavel.
Crie um arquivo refresh.sql:
LOAD firebird;
CREATE SCHEMA IF NOT EXISTS bronze;
CREATE SCHEMA IF NOT EXISTS silver;
CREATE SCHEMA IF NOT EXISTS gold;
CREATE OR REPLACE VIEW bronze.pessoas AS
SELECT *
FROM firebird_scan('C:/legacy/erp.fdb', 'TABPESSOAS');
CREATE OR REPLACE VIEW bronze.entrada_saida AS
SELECT *
FROM firebird_scan('C:/legacy/erp.fdb', 'TABENTRADASAIDA');
CREATE OR REPLACE TABLE silver.pessoas AS
SELECT *
FROM bronze.pessoas;
CREATE OR REPLACE TABLE silver.entrada_saida AS
SELECT *
FROM bronze.entrada_saida
WHERE DATAMOVIMENTO >= DATE '2023-01-01';
CREATE OR REPLACE TABLE gold.vendas_mes AS
SELECT
date_trunc('month', DATAMOVIMENTO)::DATE AS mes,
CODIGOEMPRESA,
CODIGOFILIAL,
TIPO,
count(*) AS qtd_movimentos,
sum(VALORTOTAL) AS valor_total
FROM silver.entrada_saida
GROUP BY 1, 2, 3, 4;Execute:
duckdb analytics.duckdb -unsigned -init refresh.sqlOu dentro do CLI:
.read refresh.sqlQuando a tabela Firebird e grande, evite recarregar tudo. Um padrao simples e carregar por janela de data.
Primeiro crie a tabela local uma vez:
CREATE TABLE IF NOT EXISTS silver.entrada_saida (
CODIGOEMPRESA INTEGER,
CODIGOFILIAL INTEGER,
CODIGO INTEGER,
CODIGOPESSOA INTEGER,
DATAMOVIMENTO DATE,
VALORTOTAL DECIMAL(18, 2),
TIPO VARCHAR
);Depois, em cada refresh, remova e recarregue uma janela recente:
BEGIN TRANSACTION;
DELETE FROM silver.entrada_saida
WHERE DATAMOVIMENTO >= current_date - INTERVAL 30 DAY;
INSERT INTO silver.entrada_saida
SELECT
CODIGOEMPRESA,
CODIGOFILIAL,
CODIGO,
CODIGOPESSOA,
DATAMOVIMENTO,
VALORTOTAL,
TIPO
FROM firebird_scan('C:/legacy/erp.fdb', 'TABENTRADASAIDA')
WHERE DATAMOVIMENTO >= current_date - INTERVAL 30 DAY;
COMMIT;Esse modelo funciona bem quando movimentos antigos nao mudam. Se o ERP altera registros antigos, use uma janela maior ou faca refresh completo periodico.
DuckDB cria zonemaps automaticamente para tipos comuns. Eles funcionam melhor quando os dados estao mais ordenados pelas colunas usadas em filtros.
Ao materializar uma tabela de fatos, ordene por data e chaves comuns:
CREATE OR REPLACE TABLE silver.entrada_saida AS
SELECT *
FROM bronze.entrada_saida
WHERE DATAMOVIMENTO >= DATE '2023-01-01'
ORDER BY DATAMOVIMENTO, CODIGOEMPRESA, CODIGOFILIAL;Para filtros muito seletivos por uma coluna, um indice ART pode ajudar:
CREATE INDEX idx_pessoas_codigo
ON silver.pessoas (CODIGO);
CREATE INDEX idx_entrada_saida_pessoa
ON silver.entrada_saida (CODIGOPESSOA);Nao crie indices por reflexo. Eles aceleram alguns filtros pontuais, mas tambem consomem memoria e deixam carga/atualizacao mais lenta. Para analises agregadas grandes, ordenacao e colunas bem escolhidas costumam dar mais retorno.
Um ponto forte do DuckDB e consultar varias fontes no mesmo SQL.
Exemplo: tabela de metas em Parquet + vendas vindas do Firebird:
CREATE OR REPLACE TABLE silver.metas AS
SELECT *
FROM read_parquet('C:/dados/metas/*.parquet');
CREATE OR REPLACE TABLE gold.realizado_vs_meta AS
SELECT
v.mes,
v.CODIGOEMPRESA,
v.CODIGOFILIAL,
v.valor_total AS realizado,
m.meta_valor AS meta,
v.valor_total / NULLIF(m.meta_valor, 0) AS pct_meta
FROM gold.vendas_mes v
LEFT JOIN silver.metas m
ON m.mes = v.mes
AND m.codigoempresa = v.CODIGOEMPRESA
AND m.codigofilial = v.CODIGOFILIAL;Para entregar um dataset para BI, Python, R ou outro time, use COPY.
Parquet e a melhor opcao para analytics:
COPY gold.vendas_mes
TO 'C:/exports/vendas_mes.parquet'
(FORMAT parquet, COMPRESSION zstd);CSV para usuarios finais:
COPY (
SELECT *
FROM gold.vendas_mes
WHERE mes >= DATE '2024-01-01'
)
TO 'C:/exports/vendas_mes_2024.csv'
(HEADER, DELIMITER ';');Tambem e possivel criar um arquivo por particao:
COPY gold.vendas_mes
TO 'C:/exports/vendas_mes_partitioned'
(FORMAT parquet, PARTITION_BY (CODIGOEMPRESA));Exemplo minimo para notebooks:
import duckdb
con = duckdb.connect("analytics.duckdb")
con.execute("LOAD firebird")
df = con.execute("""
SELECT *
FROM firebird_scan('C:/legacy/erp.fdb', 'TABPESSOAS')
LIMIT 1000
""").df()
print(df.head())Depois que voce materializa tabelas no .duckdb, os notebooks podem
consultar somente o arquivo local:
import duckdb
con = duckdb.connect("analytics.duckdb", read_only=True)
df = con.execute("""
SELECT mes, sum(valor_total) AS total
FROM gold.vendas_mes
GROUP BY 1
ORDER BY 1
""").df()Use EXPLAIN para ver o plano sem executar:
EXPLAIN
SELECT
CODIGOEMPRESA,
sum(VALORTOTAL)
FROM silver.entrada_saida
WHERE DATAMOVIMENTO >= DATE '2024-01-01'
GROUP BY 1;Use EXPLAIN ANALYZE para executar e medir:
EXPLAIN ANALYZE
SELECT
CODIGOEMPRESA,
sum(VALORTOTAL)
FROM silver.entrada_saida
WHERE DATAMOVIMENTO >= DATE '2024-01-01'
GROUP BY 1;No CLI, ative timer:
.timer onCompare a mesma consulta lendo Firebird ao vivo e lendo a tabela materializada:
-- Ao vivo: bom para dados recentes e exploracao inicial.
SELECT count(*)
FROM firebird_scan('C:/legacy/erp.fdb', 'TABENTRADASAIDA')
WHERE DATAMOVIMENTO >= DATE '2024-01-01';
-- Local: melhor para analise repetida.
SELECT count(*)
FROM silver.entrada_saida
WHERE DATAMOVIMENTO >= DATE '2024-01-01';- Use
firebird_scanpara exploracao e queries pontuais. - Use
ATTACHquando quiser navegar varias tabelas Firebird como catalogo. - Materialize no DuckDB as tabelas usadas repetidamente.
- Crie views para semantica, nao para performance.
- Prefira
CREATE TABLE AS SELECTpara snapshots completos. - Prefira
INSERT INTO ... SELECTpara cargas incrementais. - Filtre e selecione colunas antes de materializar tabelas grandes.
- Ordene tabelas de fatos por data e chaves de filtro.
- Crie indices apenas para buscas pontuais muito seletivas.
- Exporte datasets analiticos em Parquet quando possivel.
- Use usuario Firebird somente-leitura para analytics.
- Nao rode cargas pesadas contra o Firebird em horario critico sem validar impacto.
-- 1. Abrir um arquivo DuckDB persistente:
-- duckdb analytics.duckdb
-- use -unsigned somente para build local nao assinada
INSTALL firebird FROM community;
LOAD firebird;
-- 2. Explorar origem.
SELECT *
FROM firebird_tables('C:/legacy/erp.fdb')
ORDER BY table_name;
-- 3. Criar schemas.
CREATE SCHEMA IF NOT EXISTS bronze;
CREATE SCHEMA IF NOT EXISTS silver;
CREATE SCHEMA IF NOT EXISTS gold;
-- 4. Criar view da origem.
CREATE OR REPLACE VIEW bronze.pessoas AS
SELECT *
FROM firebird_scan('C:/legacy/erp.fdb', 'TABPESSOAS');
-- 5. Materializar local.
CREATE OR REPLACE TABLE silver.pessoas AS
SELECT *
FROM bronze.pessoas;
-- 6. Criar agregado de negocio.
CREATE OR REPLACE TABLE gold.pessoas_por_uf AS
SELECT
UF,
count(*) AS qtd_pessoas
FROM silver.pessoas
GROUP BY 1
ORDER BY 2 DESC;
-- 7. Exportar.
COPY gold.pessoas_por_uf
TO 'C:/exports/pessoas_por_uf.parquet'
(FORMAT parquet, COMPRESSION zstd);Colunas de texto NONE nunca empurravam filtros - um IN grande
significava full scan no servidor. Ative e literais constantes de
=/IN viajam re-encodeados (casamento byte a byte lossless; literais
nao codificaveis caem de volta automaticamente):
ATTACH 'C:/legacy/erp.fdb' AS erp
(TYPE firebird, none_encoding 'win1252', none_pushdown true);
SELECT count(*)
FROM erp.main.APURACAOSIMPLESDASWEBTEMP
WHERE ANOMES IN ('202501', '202502', '202503');
-- remote_sql agora carrega: "ANOMES" IN (_WIN1252 '202501', ...)
SELECT remote_sql, pushed_filters FROM firebird_last_query();Escopo: = e IN em CHAR/VARCHAR. Range, LIKE e NOT IN ficam no
cliente (o DuckDB os aplica, corretamente).
Arme antes de cargas longas; um scan que mantem filtros no DuckDB falha com a razao e o remedio em vez de rodar por horas:
SET firebird_unpushed_mode = 'error'; -- ou 'warn' (default 'silent')
SELECT count(*) FROM erp.main.T WHERE DESCRICAORECEITA NOT IN ('VENDA');
-- Invalid Input Error: ... NONE_CHARSET (1): ... use none_pushdown=true ...SELECT typeof(QTDPMR) FROM firebird_scan('C:/legacy/erp.fdb', 'SFCLIENTE');
-- DECIMAL(18,2)
SELECT typeof(QTDPMR) FROM firebird_scan('C:/legacy/erp.fdb', 'SFCLIENTE',
numeric_widen_int64=true);
-- DECIMAL(38,2) - a faixa completa do int64 escalado sobrevive a aritmeticaO firebird_type_audit('erp') lista cada coluna afetada como
int64_numeric_widenable.
SET firebird_dummy_packet_interval = 60; -- DPB de keepalive, segundos
SELECT rows_read, bytes_read_estimate FROM firebird_last_query();
SELECT bytes_read_estimate FROM firebird_pool_stats('erp'); -- acumuladoVerifique se a extensao foi instalada ou se o caminho do LOAD aponta
para o arquivo correto. No fluxo oficial, rode INSTALL firebird FROM community;
antes do LOAD. Em build local nao assinada, rode o CLI com -unsigned.
Garanta que o fbclient.dll correto esta no PATH ou ao lado do
duckdb.exe. Misturar cliente Firebird antigo com banco novo pode causar
erro de tipo desconhecido.
Se a base usa CHARACTER SET NONE, comece sem parametros: o default e
none_encoding='win1252'. Se ainda houver problema, teste:
SELECT *
FROM firebird_scan('C:/dados/empresa.fdb', 'TABELA',
none_encoding='iso8859_1')
LIMIT 20;Se voce precisa investigar bytes brutos:
SELECT *
FROM firebird_scan('C:/dados/empresa.fdb', 'TABELA',
none_encoding='blob')
LIMIT 20;Leia menos colunas, aplique filtros seletivos e materialize as tabelas usadas repetidamente.
CREATE OR REPLACE TABLE silver.movimentos_2024 AS
SELECT
CODIGOEMPRESA,
CODIGOFILIAL,
CODIGOPESSOA,
DATAMOVIMENTO,
VALORTOTAL
FROM firebird_scan('C:/legacy/erp.fdb', 'TABENTRADASAIDA')
WHERE DATAMOVIMENTO >= DATE '2024-01-01';Compartilhe o arquivo .duckdb quando o consumidor tambem usa DuckDB, ou
exporte tabelas gold para Parquet/CSV com COPY.