dbfy

May 2, 2026 · View on GitHub

Five-minute path from zero to running federated SQL. Pick whichever section matches your starting point — a local log file, a REST endpoint, or DuckDB. The three paths share the same YAML config schema, so a config you generate for one frontend works in the other.

1. Build

git clone https://github.com/typeeffect/dbfy && cd dbfy

# Standalone CLI (under target/release/dbfy-cli):
cargo build --release -p dbfy-cli

The DuckDB extension build is opt-in (it pulls bundled DuckDB the first time, ~30 minutes; subsequent builds are incremental):

cargo build -p dbfy-frontend-duckdb --features loadable_extension --release --jobs 1
strip target/release/libdbfy_duckdb.so
python3 crates/dbfy-frontend-duckdb/scripts/append_metadata.py \
    target/release/libdbfy_duckdb.so \
    --output target/release/dbfy.duckdb_extension --duckdb-capi-version v1.2.0

2. Path A — query a JSONL log file

You have app.jsonl with one JSON object per line and want to run SQL on it.

# 1. Auto-detect schema and emit a starter YAML config:
./target/release/dbfy-cli detect /var/log/app.jsonl > app.yaml

# 2. Verify the config is valid:
./target/release/dbfy-cli validate app.yaml

# 3. Run SQL:
./target/release/dbfy-cli query --config app.yaml \
    "SELECT level, count(*) FROM files.events GROUP BY level ORDER BY 2 DESC"

The first query builds a .dbfy_idx sidecar next to the file (zone maps + bloom filters on whatever columns detect heuristically picked as indexable). Subsequent queries reuse it; if the file grows on the end, the next query incrementally indexes only the new tail.

Pre-warm the sidecar explicitly (e.g. from cron or a build pipeline):

./target/release/dbfy-cli index --config app.yaml --table files.events

3. Path B — query a REST endpoint

You have an HTTP/JSON endpoint and want to query it as a SQL table.

# 1. Probe the endpoint, infer schema:
./target/release/dbfy-cli probe https://api.example.com/users > api.yaml

# 2. Run SQL:
./target/release/dbfy-cli query --config api.yaml \
    "SELECT id, name FROM api.items LIMIT 10"

probe performs one GET, finds the row array via a candidate ladder ($.data[*], $.results[*], $.items[*], …), infers per-field types (int / float / timestamp / string / boolean), and emits a YAML config ready to consume.

For auth, pass the env var holding your bearer token:

./target/release/dbfy-cli probe https://api.github.com/user \
    --auth-bearer-env GITHUB_TOKEN > github.yaml

4. Path C — interactive wizard

If you don't yet know whether your source is a file or a REST endpoint:

./target/release/dbfy-cli init > config.yaml

The wizard prompts for source kind, name, and target, then delegates to detect (file path) or probe (URL) under the hood.

5. Path D — DuckDB

Use the extension from any DuckDB host. The example below runs from the DuckDB Python package, but the same SQL works in duckdb CLI, R, JVM, JS, etc.

pip install duckdb
import duckdb
con = duckdb.connect(":memory:", config={"allow_unsigned_extensions": "true"})
con.execute("LOAD 'target/release/dbfy.duckdb_extension'")

# Untyped exploration: every JSON object at $.data[*] returned as one VARCHAR row.
print(con.execute("SELECT count(*) FROM dbfy_rest('https://api.example.com/users')").fetchone())

# Typed columns + projection pushdown via inline YAML config.
config = """
root: $.data[*]
columns:
  id:   { path: "$.id",   type: int64  }
  name: { path: "$.name", type: string }
"""
print(con.execute(
    "SELECT id, name FROM dbfy_rest(?, config := ?) LIMIT 5",
    [f"https://api.example.com/users", config],
).fetchall())

# Same story for files:
file_config = """
parser:
  format: jsonl
  columns:
    - { name: id,    path: "$.id",    type: int64 }
    - { name: level, path: "$.level", type: string }
indexed_columns:
  - { name: id,    kind: zone_map }
  - { name: level, kind: bloom    }
"""
print(con.execute(
    "SELECT level, count(*) FROM dbfy_rows_file(?, config := ?) GROUP BY level",
    ["/var/log/app.jsonl", file_config],
).fetchall())

6. Path E — DuckDB ATTACH-style: every config table as a SQL view

If you've already produced a config.yaml (via detect, probe, or init), the CLI can emit a SQL script that registers every configured table as a CREATE OR REPLACE VIEW <schema>.<table> over the right table function. Subsequent queries are pure DuckDB — no need to repeat URL/config in every SELECT.

./target/release/dbfy-cli duckdb-attach \
    --config config.yaml \
    --schema api \
    --extension target/release/dbfy.duckdb_extension > attach.sql

duckdb mydb.duckdb < attach.sql
-- After the script runs, every configured table is a normal DuckDB view:
SELECT * FROM api.users;
SELECT u.name, count(o.id)
  FROM api.users u
  JOIN api.orders o USING (user_id)
  GROUP BY u.name;

The full ATTACH 'config.yaml' (TYPE dbfy) syntax is a planned upgrade once duckdb-rs exposes the StorageExtension C API; today's emitter is the SQL-equivalent shortcut and the <schema>.<table> contract is forward-compatible.

7. Federated query — REST × REST × file

The point of dbfy is putting heterogeneous sources in one SELECT. After the above paths, you can do this in a single statement:

SELECT s.zone, count(*) AS hot_acked, round(avg(r.temperature), 2) AS avg_temp
  FROM api.readings r
  JOIN api.sensors  s USING (sensor_id)
  JOIN files.events e
    ON e.sensor_id = r.sensor_id
   AND e.kind = 'alarm_acked'
   AND abs(epoch(e.ts) - epoch(r.timestamp)) <= 90
 WHERE r.temperature > 22
 GROUP BY s.zone
 ORDER BY hot_acked DESC;

Pre-dbfy version of this query: three Python scripts, two CSV intermediates, one pd.merge chain, and a regex over the log file. Now: one SELECT.

Where to next

  • crates/dbfy-frontend-duckdb/examples/sensor_analytics.py — annotated end-to-end demo of all six patterns above (run it after building the loadable extension).
  • crates/dbfy-provider-rows-file/examples/log_analytics.rs — Rust example showing the L3 index doing 98–100 % chunk-skip on selective filters across 50k synthetic log rows.
  • DuckDB extension reference — full table-function surface, capabilities matrix.
  • Python bindingsmaturin develop wheel + API + asyncio.
  • Implementation plan — what was built, why, and what's next.