Skip to content

REPL: dbfy repl — interactive psql-style shell #17

@frhack

Description

@frhack

Why

dbfy today is "write YAML, run one-shot queries from the CLI". A
REPL closes the demo experience: download binary, dbfy repl,
exploratory queries land in 30 seconds. The pitch becomes "psql, but
for REST + logs + Excel + LDAP + Postgres + …".

Every comparable project has one — psql, duckdb, sqlite3,
clickhouse-client. Without it, dbfy feels less mature than it
actually is.

Sketch

$ dbfy repl --config examples/showcase/configs/all.yaml
dbfy v0.5.0 — connected to 3 sources, 5 tables.
Type \? for help, \q to quit.

dbfy> \d
 source         table          rows (est)
 ─────────────  ─────────────  ──────────
 github         repos          ~10
 fleet_logs     jsonl          ~50000
 syslog         events         ~5000

dbfy> SELECT count(*) FROM github.repos
     ;
 count(*)
 ────────
       10

(1 row, 312 ms)

dbfy> \timing on
dbfy> \explain SELECT count(*) FROM fleet_logs.jsonl WHERE level = 'ERROR';
[plan + per-source pushdown summary]

dbfy> \q
$

Meta-commands (psql-style \ prefix)

Command Behaviour
\d List tables (qualified name + estimated row count)
\d <table> Schema for that table — typed columns, pushdown capabilities
\explain <sql> Logical plan + per-source pushdown summary (already implemented in Engine::explain)
\timing on|off Show ms per query
\format table|csv|json|tsv Output formatter
\source <file.sql> Run a SQL script
\reload Re-read the YAML config from disk (live config reload, picks up new sources without restart)
\? Help
\q Quit (also Ctrl-D)

Implementation

  • rustyline crate — canonical Rust readline replacement. Handles
    line editing, history, multi-line, terminal capabilities.
  • History file at ${XDG_STATE_HOME:-~/.local/state}/dbfy/history with
    the standard 1000-line cap.
  • Multi-line input: prompt becomes dbfy*> (asterisk) when the
    current input doesn't end in ; — matches psql convention.
  • Tab completion wired to:
    • keywords (SELECT, FROM, WHERE, …)
    • registered table names from engine.registered_tables()
    • column names from arrow_schema per table after the user types
      <table>. or after SELECT when DataFusion can resolve the FROM
      clause
  • Pretty-print: reuse the --format=table printer from
    dbfy query so REPL and one-shot output match byte-for-byte.

New file: crates/dbfy-cli/src/repl.rs. New CLI subcommand:
dbfy repl --config <path>.

Effort

Step Effort
Loop + rustyline + history + multi-line 0.5 d
Meta-commands \d \q \? \timing \format \source 0.3 d
Tab completion from registered schemas 0.5 d
\explain + \reload (live YAML reload) 0.4 d
4 integration tests with expectrl (script-driven) 0.3 d
Total ~2 d

Bonus per language binding

  • Python: dbfy.repl(engine) launches the same loop inside an
    IPython kernel — useful in Jupyter. ~0.3 d on top.
  • Node: a wrapper that spawns the dbfy binary in REPL mode. ~0.2 d.

These can ship as v0.5.1 patches; not gating the v0.5 milestone.

Acceptance for v1

  • dbfy repl --config x.yaml opens an interactive prompt
  • Multi-line SQL works (input not terminated until ;)
  • All meta-commands above work and are documented in \?
  • Tab completion produces ≥ table names; column names is a
    stretch goal
  • History persists across sessions
  • Ctrl-C cancels the current query (not the REPL); Ctrl-D quits
  • Showcase demo: an asciicast linked from the README that walks
    through 5 queries against the examples/showcase/ config

Targeted milestone: v0.5.

Metadata

Metadata

Assignees

No one assigned

    Labels

    tier-1Wow factor — opens a new mark

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions