The Problem

Developers and DBAs often need a single terminal client that works across dozens of SQL and NoSQL engines. Maintaining a separate native CLI for each system adds friction, especially when scripts must run on multiple back‑ends.

What This Does

usql provides a PostgreSQL‑style interactive shell that drives any of the supported drivers. The source is organized as a collection of self‑contained driver packages (drivers/ ≈ 104 files) plus a thin command dispatcher in handler/handler.go and the entry point main.go. Driver‑specific metadata lives under drivers/metadata/, while helper utilities (environment handling, command‑line completion, meta‑commands) are in env/, metacmd/, and stmt/. The repository ships a Dockerfile (drivers/testdata/docker/Dockerfile) for containerised builds and a full CI pipeline (.github/workflows/*.yml).

How It Is Wired

  • Program start – main in main.go (line 23) creates a handler and calls Run. This reaches 78 functions and is the only top‑level caller.
  • Command handling – handler/handler.go defines Execute, Run, Bind, and doExecChart. Run (line 145) is the busiest entry point, reaching 134 functions and invoking the core driver logic.
  • Driver selection – drivers/drivers.go registers each driver in init (e.g., adodb, clickhouse). The global Next function (called from 68 places) drives the REPL loop, pulling commands from handler.
  • Database I/O – The shortest external‑effect path is: main → Run → Execute → Rollback → tx.Rollback (DB) Run → Execute → Columns → Query → r.db.Query (DB) main → run → writeReadme → os.ReadFile (filesystem)
  • High‑impact functions – Next, Close, Vars, Run, New, Scan, Print, and WrapErr are called from 15‑68 locations each, so changes ripple widely.
  • File responsibilities –
  • drivers/metadata/metadata.go – central catalog API (65 functions, 55 types).
  • handler/handler.go – orchestrates command execution, I/O, and timing (62 functions).
  • drivers/drivers.go – driver registry and capability queries (37 functions).
  • env/env.go – environment variable handling and external command launching (20 functions).

No circular import cycles were detected; the internal call graph contains 995 self‑calls, confirming a flat package hierarchy.

How To Use It

# Clone the repo
git clone https://github.com/moses-y/usql
cd usql

# Build the binary (requires Go 1.22+)
go build -o usql ./cmd/usql   # main.go is the entry point

# Run the CLI against a database
./usql postgres://user:pass@localhost/dbname

The repository also supplies a Docker build (docker build -f drivers/testdata/docker/Dockerfile .) and a helper script build.sh for reproducible container images.

Configuration files for individual drivers live under contrib/ (e.g., contrib/mysql/usql-config). Environment variables are read via env/vars.go (Vars, Getenv). No additional runtime flags are required beyond standard DSN syntax.

Real‑World Use

A CI pipeline can invoke usql to run migration scripts against multiple back‑ends without changing the command line. For example:

./usql mysql://root@localhost/db -c "\i migrations/2024_add_user.sql"
./usql postgres://admin@dbserver/db -c "\i migrations/2024_add_user.sql"

The same script works for Oracle, SQLite, or any driver present in drivers/.

Code Health & Issues

  • High – GitHub Actions are pinned to tags only (shimataro/ssh-key-action@v2, etc.). Tags can move, potentially exposing secrets. Fix: Replace each tag with a 40‑character commit SHA and keep the version comment.
  • Medium – Workflows lack explicit permissions for GITHUB_TOKEN. Without restriction the token inherits full repo rights. Fix: Add permissions: { contents: read } at the top of each workflow, expanding only where needed.
  • Medium – No dependency‑vulnerability scan is gated in CI. Fix: Add dependency-review-action on pull_request or an osv-scanner step.
  • Low – Jobs omit timeout-minutes, so they may run up to the platform default (6 h). Fix: Set a reasonable timeout-minutes per job.

Additional static findings include duplicated 6‑line shell blocks across 39 files, nine oversized source files (e.g., drivers/completer/completer.go ≈ 1.6 k lines), deep nesting (max depth 6) in several driver implementations, and lingering TODO/FIXME markers (12 total). Refactoring the large files into smaller, responsibility‑focused packages would reduce cognitive load and blast radius.

The Bottom Line

usql delivers a functional, Go‑based multi‑driver SQL shell with a clear entry point and well‑isolated driver packages, making it a practical replacement for per‑engine CLIs. The codebase is healthy overall but would benefit from refactoring large files, deduping helper scripts, and tightening CI security settings before adopting in a high‑risk production environment. Engineers comfortable with Go and SQL driver development can extend or harden the tool without major architectural changes.