Query Firebird directly from DuckDB.
The first Firebird extension published in the DuckDB Community Extensions registry.
Read-only scan, native ATTACH, metadata/diagnostics, and pushdown explain for Firebird 3/4/5 -- DuckDB's own SQL (CREATE TABLE AS, COPY ... TO PARQUET) handles materialization and export; this extension is the read path, not an ETL/CDC/scheduler layer.
Usage guide | Function manual | Roadmap (PT) | Contributing | Code of conduct | Security
duckdb-firebird is a DuckDB extension for read-only analytics on Firebird
databases. It lets analysts keep Firebird as the transactional source of truth
and use DuckDB as the local OLAP engine for ad hoc analysis, materialized
tables, Parquet exports, lakehouse-style caches, and BI serving through tools
such as GizmoSQL.
- Read-only by design - optimized for analytics without mutating the OLTP source.
- Push work to Firebird when safe - projection, filters, paging, and PK-range partitioning reduce wire traffic.
- Keep DuckDB in charge of analytics - joins, aggregations, Parquet/S3, and local materializations stay in DuckDB's vectorized engine.
- Legacy friendly -
CHARACTER SET NONEdatabases default tonone_encoding='win1252', withstrict,iso8859_1, andblobavailable. - Public-repo ready - security policy, contribution docs, CI, Dependabot, and branch protection are in place.
firebird_scan(conn, table)- scan a Firebird table from DuckDB SQL.- Native
ATTACH- query Firebird tables asfb.main.TABLE. - Metadata discovery -
SHOW TABLES,DESCRIBE, andinformation_schema.tables/information_schema.columns. - Projection pushdown - only requested columns are fetched.
- Predicate pushdown - comparisons,
IS NULL,BETWEEN,IN,NOT IN,NOT bool,AND/OR, and safeLIKE 'prefix%'cases.=/INpush down except onCHARACTER SET NONEtext columns — those stay in DuckDB (seenone_encoding), optionally guarded viaSET firebird_unpushed_mode = 'warn' | 'error'; thenone_pushdown=trueopt-in pushes=/INover NONE CHAR/VARCHAR columns by re-encoding each literal to the storage bytes (lossless byte-wise comparison). - Streaming batches - rows flow into DuckDB
DataChunks instead of a whole-table in-memory copy. - Prepared statements + bind variables - string/date/numeric filters avoid unsafe SQL interpolation.
- Paging -
row_limit=Nandrow_offset=Mmap to FirebirdROWS. - Parallel PK-range scans - opt in with
partitions=N. - Firebird 3/4/5 type mapping - including
INT128,DECIMAL(38),TIMESTAMP WITH TIME ZONE, text BLOBs, binary BLOBs, booleans, dates, and timestamps. Int64-backedNUMERIC(18,s)defaults toDECIMAL(18,s); thenumeric_widen_int64=trueopt-in (firebird_scanparameter or ATTACH option) projects those columns asDECIMAL(38,s)so values beyond DuckDB's 18-digit domain (e.g. the scaled-INT64_MINsentinel) survive losslessly.firebird_type_auditflags every affected column with theint64_numeric_widenablefinding. - Diagnostics and metadata bridge - inspect constraints, indexes,
generators, domains, dependencies, comments, health, type-fidelity findings
(
firebird_type_audit), index profile, table profile alerts, andfirebird_explain_pushdownplanning reports. - Plays well with plain DuckDB SQL - once scanned/attached, ordinary
CREATE TABLE AS,COPY ... TO PARQUET, S3/MinIO writes, and Arrow Flight SQL via GizmoSQL are DuckDB's own features, not something this extension implements or manages.
INSTALL firebird FROM community;
LOAD firebird;
-- Live scan
SELECT *
FROM firebird_scan(
'firebird://SYSDBA:masterkey@db.host:3050/srv:/data/prod.fdb?charset=UTF8',
'EMPLOYEE'
)
LIMIT 10;
-- Projection + predicate pushdown
SELECT EMP_NO, FIRST_NAME
FROM firebird_scan('firebird://SYSDBA:masterkey@db.host:3050/srv:/data/prod.fdb', 'EMPLOYEE')
WHERE DEPT_NO = '600'
AND HIRE_DATE >= DATE '2020-01-01';
-- Native catalog
ATTACH 'firebird://SYSDBA:masterkey@db.host:3050/srv:/data/prod.fdb' AS fb
(TYPE firebird);
SELECT *
FROM fb.main.EMPLOYEE
WHERE DEPT_NO = '600';Use live scans when you need fresh operational data:
SELECT o.CUSTOMER_ID, SUM(o.TOTAL_VALUE) AS revenue
FROM fb.main.ORDERS o
WHERE o.ORDER_DATE >= DATE '2026-01-01'
GROUP BY 1;Materialize when the query is reused or joins large historical tables:
CREATE OR REPLACE TABLE mart_orders_2026 AS
SELECT *
FROM fb.main.ORDERS
WHERE ORDER_DATE >= DATE '2026-01-01';Export to Parquet for a local lake cache:
COPY (
SELECT *
FROM fb.main.ORDERS
WHERE ORDER_DATE >= DATE '2026-01-01'
)
TO 'lake/erp/orders_2026.parquet'
(FORMAT PARQUET);For a longer, analyst-oriented walkthrough, including materialized tables, incremental refresh, Parquet partitioning, MinIO, and Power BI/GizmoSQL usage, see docs/en/usage_guide.md. For a public how-to of each SQL function and option, see docs/en/function_manual.md (PT: docs/pt/function_manual.md).
firebird://USER:PASS@HOST:PORT/DB_PATH?charset=UTF8&dialect=3&role=ROLE
user=SYSDBA;password=masterkey;database=server:/data/db.fdb;charset=UTF8
C:/data/local.fdb
/var/lib/firebird/data/local.fdb
Security note: connection strings may end up in shell history, notebooks, DuckDB logs, or BI configuration. Use least-privilege Firebird users and avoid committing real credentials.
Long fetches getting killed by NAT/firewall idle timeouts (-504 cursor
lost)? SET firebird_dummy_packet_interval = 60; arms a wire-level
keepalive (seconds) on every connection; see the function manual's
session options.
SELECT *
FROM firebird_scan(
'/data/prod.fdb',
'EMPLOYEE',
user='analytics',
password='secret',
charset='UTF8',
role='READER',
dialect=3,
partitions=4,
row_limit=100,
row_offset=200
);row_offset requires row_limit. Paging is a global Firebird slice and runs
serially; explicit partitions > 1 with paging is rejected to avoid returning
incorrect slices.
DuckDB strings are UTF-8. Firebird databases declared with a real character set
can usually use the default charset=UTF8; Firebird transliterates values on
the wire.
CHARACTER SET NONE is different: Firebird returns raw bytes. The extension
defaults to none_encoding='win1252', matching many legacy Brazilian and
Western-European ERPs. Override it when needed:
none_encoding |
Behavior |
|---|---|
win1252 |
Default. Decode NONE text bytes as Windows-1252 to UTF-8. |
iso8859_1 / latin1 |
Decode NONE text bytes as ISO-8859-1 to UTF-8. |
strict |
Accept only already-valid UTF-8; error on invalid bytes. |
blob |
Surface NONE text columns as DuckDB BLOB raw bytes. |
SELECT *
FROM firebird_scan('C:/legacy/erp.fdb', 'TABENTRADASAIDA',
none_encoding='win1252');
ATTACH 'C:/legacy/erp.fdb' AS erp
(TYPE firebird, user 'SYSDBA', password 'masterkey',
none_encoding 'win1252');When none_encoding is not strict, text filter pushdown on NONE columns
is disabled deliberately: DuckDB applies those filters after transcoding so
query results remain correct. To push = / IN over NONE CHAR/VARCHAR
columns anyway — re-encoding each UTF-8 literal to the stored bytes behind a
charset introducer, lossless under Firebird's byte-wise NONE comparison —
pass none_pushdown=true to firebird_scan or the ATTACH options.
SHOW TABLES FROM fb;
DESCRIBE fb.main.EMPLOYEE;
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'main';
SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_name = 'EMPLOYEE'
ORDER BY ordinal_position;Firebird has a flat namespace, so the extension exposes a single DuckDB schema:
main. Name lookup is case-insensitive unless quoted identifiers require exact
DuckDB behavior.
Published community release: v1.2.1 (INSTALL firebird FROM community; descriptor merged as
duckdb/community-extensions#3022
on 2026-10-12). Newer repository releases are distributed through this
project's GitHub Releases; a community-catalog update is a separate,
explicitly authorized publication step. The copy in
community-extensions/description.yml
mirrors the latest prepared descriptor. Descriptors pin an immutable
commit SHA in repo.ref (catalog convention).
main is at v1.3.1 (see
docs/en/release_notes_v1.3.1.md and
CHANGELOG.md). This patch completes DuckDB v2.0 NOT IN
pushdown and its residual-filter observability. Supported DuckDB versions
are v1.5.6+ and v2.0+, with release assets built per DuckDB line
(-duckdb2 suffix for the v2.0-built binaries). v1.3.0 introduced the
dual-path v1.5.x/v2.x C++ API build. v1.2.1 added connection-string
support for none_encoding / none_pushdown; v1.2.0 delivered the
G-queue — none_pushdown (=/IN over CHARACTER SET NONE columns), the
firebird_unpushed_mode guard, numeric_widen_int64 (NUMERIC(18,s) →
DECIMAL(38,s)), firebird_dummy_packet_interval keepalive with
fetch-failure context, and bytes_read_estimate telemetry.
v1.0.1 (see docs/en/release_notes_v1.0.1.md) delivered Metadata Bridge 2.0, pushdown explain planning, type audit, database/ table/index diagnostics, the Smart Scan Planning report, deterministic paging safeguards, lossless type/BLOB hardening, and a closed Production Stability + Runtime/ABI Compatibility gate (fresh DuckDB v1.5.2/v1.5.3/v1.5.4/v1.5.5 and Firebird 3/4/5 matrices, a read-only maturity battery run against a real ~90GB database, zero product errors). DuckDB v1.5.5 — the current DuckDB Community Extensions compatibility target — and v1.5.6 (released 2026-09-28) are validated as additional matrix versions (v1.5.6: full suite, 21/21 test files, 2026-09-29); the repository's own default build pin stays v1.5.3 for now. See docs/en/release_notes_v1.0.0.md and docs/pt/duckdb_1_5_compatibility_plan.md for full detail.
| Area | Status |
|---|---|
firebird_scan table function |
Done |
Native ATTACH ... (TYPE firebird) |
Done |
firebird_tables discovery |
Done |
firebird_attach_sql generated-view helper |
Done |
| Projection pushdown | Done |
| Predicate pushdown | Done |
Manual paging with row_limit / row_offset |
Done |
| Prepared statement bind variables | Done |
PK-range parallel scan via partitions=N |
Done |
CHARACTER SET NONE decoding |
Done |
Metadata via SHOW, DESCRIBE, information_schema |
Done |
Automatic DuckDB LIMIT pushdown |
Deferred upstream hook |
| LRU prepared statement cache | Deferred benchmark item |
Scanner-native Arrow RecordBatch output |
v1.x candidate |
| Area | Status |
|---|---|
firebird_profile_table('fb.main.T') factual table/view diagnostics |
Done |
Structured profile alerts (alerts LIST(STRUCT(code,severity,message))) |
Done |
| Heavy-view shape detection (JOIN / aggregation / no-filter warnings) | Done |
Row estimate (estimated_rows / row_estimate_method, opt-in exact COUNT(*) via exact_row_count=true) |
Done (v1.1.0) |
Structured required-filter alerts (filter_before_scan, materialize_before_scan) |
Done (v1.1.0) |
firebird_explain_pushdown(sql) planning report |
Done |
Pushdown explainability in firebird_last_query / firebird_query_log (limit_pushed, offset_pushed, not_pushed_reasons) |
Done |
firebird_pool_stats('fb') connection-pool introspection |
Done |
Pool active-lease count + sanitized last_error (active_connections, last_error) |
Done (v1.1.0) |
firebird_health('fb') database/server health facts |
Done |
firebird_index_profile('fb.main.T') index diagnostics |
Done |
firebird_type_audit('fb') type/charset fidelity findings |
Done |
DECFLOAT(16/34) lossless fallback (VARCHAR via server-side CAST) |
Done |
| Adaptive parallel scan recommendations (shared with scanner planning) | Done |
All 19 functions documented in-band in duckdb_functions() |
Done (v1.0.2) |
none_pushdown: =/IN pushdown over CHARACTER SET NONE text |
Done (v1.2.0) |
firebird_unpushed_mode residual-filter guard (silent/warn/error) |
Done (v1.2.0) |
numeric_widen_int64: NUMERIC(18,s) as DECIMAL(38,s), opt-in |
Done (v1.2.0) |
Keepalive DPB (firebird_dummy_packet_interval) + fetch-failure context |
Done (v1.2.0) |
bytes_read_estimate telemetry + per-catalog lifetime total |
Done (v1.2.0) |
firebird_pool_stats('fb') reports one attached catalog's pool state
(config + idle queue + active leases + lifetime counters + last error) by
explicit alias. It does not enumerate catalogs, reads only counters the pool
already tracks, and never leases a connection.
DECFLOAT(16) / DECFLOAT(34) (Firebird 4+ IEEE Decimal64/Decimal128) now
surface as lossless VARCHAR via a server-side CAST(... AS VARCHAR(64)),
replacing the old behavior where the column was typed DOUBLE but always
fetched NULL. No local decimal-float decoder, no DOUBLE default. The
DECFLOAT test fixture is dedicated and not yet in the main CI fixture.
Adaptive parallel scan recommendations refine firebird_profile_table's
recommended_partitions and warnings only — firebird_scan is unchanged,
nothing is parallelized automatically, and no performance gain is promised.
A numeric PK with a small range still recommends partitions=1, and a
server-side parallelism caveat (Firebird 5 ParallelWorkers) is surfaced
when partitions > 1 is suggested.
Arrow note: the scanner produces DuckDB Vector / DataChunk columns. When a
Flight SQL client consumes query results through DuckDB or GizmoSQL, DuckDB
converts the result stream to Arrow at the boundary. Direct scanner-native Arrow
is intentionally deferred.
Starting with v0.5.5, the extension loads the Firebird client
library at runtime via dlopen / LoadLibrary. It is not
embedded in the published binary, and the build no longer links
libfbclient — including the community-extensions build that
publishes INSTALL firebird FROM community;.
You need a Firebird client reachable on the platform's default dynamic-loader search path on the machine that runs the queries:
| Platform | Provide |
|---|---|
| Linux | libfbclient.so.2 (or libfbclient.so) - e.g. apt-get install libfbclient2, dnf install libfbclient, or a Firebird server install |
| macOS | libfbclient.dylib from a Firebird install |
| Windows | fbclient.dll (or fbclient_ms.dll) from a Firebird install or the Firebird ODBC client distribution |
If the library lives somewhere non-standard, set
DUCKDB_FIREBIRD_CLIENT_LIBRARY to the absolute path before the first
firebird_* call. The override is authoritative: when set non-empty
and the path cannot be loaded, the extension raises a clear
IO Error: DUCKDB_FIREBIRD_CLIENT_LIBRARY is set to '...' but that file could not be loaded. instead of silently falling back. On Windows the
DLLs that fbclient.dll itself needs (for Firebird 3: msvcr100.dll,
zlib1.dll, ICU) are looked up in that library's own folder first, so
pointing the variable at a Firebird install or portable kit is enough.
When no client is reachable, the first firebird_* call fails with:
IO Error: Firebird client library not found. Install the Firebird
client (libfbclient on Linux/macOS, fbclient.dll on Windows) or set
DUCKDB_FIREBIRD_CLIENT_LIBRARY=/path/to/library. Tried: ...
# Linux dependencies (firebird-dev is OPTIONAL - only needed if you
# want to build against a system-installed ibase.h; the repo ships
# vendored headers under third_party/firebird/include for the
# community CI path).
sudo apt-get install -y cmake ninja-build g++ pkg-config python3 ccache
# Build and package
scripts/build_linux_local.sh
scripts/package_dist_linux.sh
# Tests require a reachable Firebird test database AND a Firebird
# client library on the loader path.
./build/release/test/unittest test/sql/firebird_scan.testWindows builds use the companion batch scripts:
scripts\build_windows_local.bat
scripts\package_dist.batRelease assets are automated by .github/workflows/release-assets.yml.
Pushing a v* tag builds and uploads Linux .tar.gz and Windows .zip
archives to the GitHub Release. The workflow can also be run manually with a
tag input.
See docs/en/guide_linux.md and docs/en/guide_windows.md.
Docs are split by language under docs/en/ (English, primary) and
docs/pt/ (Portuguese translations).
- docs/DOCS_PARITY.md - PT/EN documentation parity map.
- docs/en/usage_guide.md - analyst guide: live scans,
ATTACH, views, materialization, incremental refresh, Parquet, MinIO, dbt/GizmoSQL/Power BI patterns, and troubleshooting. (PT: docs/pt/usage_guide.md) - docs/en/function_manual.md - public function and setting reference. (PT: docs/pt/function_manual.md)
- docs/en/guide_windows.md - Windows build and smoke tests.
- docs/en/guide_linux.md - Linux build and smoke tests.
- docs/en/roadmap.md - performance roadmap and deferred work. (PT: docs/pt/roadmap.md)
- docs/en/test_report.md - release verification notes.
- docs/en/architecture.md - implementation architecture.
src/
firebird_client.cpp libfbclient wrapper
firebird_types.cpp Firebird to DuckDB type mapping
firebird_query.cpp SQL builder and pushdown
firebird_scanner.cpp table functions
firebird_storage.cpp native ATTACH catalog
include/
test/sql/ sqllogictest coverage
scripts/ fixtures and smoke scripts
docs/en/ English guides, roadmap, reports (primary)
docs/pt/ Portuguese translations
community-extensions/ DuckDB community descriptor copy
DuckDB Firebird is released under the MIT License.
The DuckDB community-extension submission was merged via
duckdb/community-extensions#1980.
The catalog currently serves v1.0.1; the v1.0.2 descriptor update is
fully green and awaiting maintainer merge
(duckdb/community-extensions#2849),
and the v1.1.0 bump is prepared. Descriptors pin an immutable commit SHA in
repo.ref (catalog convention), and the build keeps the Firebird client
runtime-loaded — no libfbclient build dependency. Normal users should
install from the official DuckDB community repository:
INSTALL firebird FROM community;
LOAD firebird;Use the GitHub release artifact or a locally built .duckdb_extension only
for development work on this repository, testing unreleased changes, or
offline machines without access to the DuckDB community repository:
LOAD '/absolute/path/to/firebird.duckdb_extension';Local unsigned builds may require starting DuckDB with -unsigned.
Fernando Lozer — GitHub @flozer · LinkedIn /fernandolozer
Design and type-mapping decisions build on
firebird_peregrine_falcon
and the encoding work from fb-cdc-rust.