An ELT engine written in Rust. You write SQL, Hinge resolves the dependency graph and runs everything in order — fast, with no runtime other than the binary itself.
Install:
cargo install hinge_cli
# or download a prebuilt binary from the Releases pageOrganize your SQL files by schema:
models/
raw/
events.sql
staging/
orders.sql ← SELECT id FROM raw.events
mart/
revenue.sql ← SELECT sum FROM staging.orders
Create hinge.yaml at the project root:
connection: postgresql://user:pass@localhost/mydb
models: modelsPreview the execution plan without touching the database:
hinge plan
# 1. raw.events
# 2. staging.orders
# 3. mart.revenueRun everything:
hinge runRun a subset:
# Everything staging.orders depends on, then staging.orders itself
hinge run --upstream staging.orders
# staging.orders and everything that depends on it
hinge run --downstream staging.orders
# Both directions from a node
hinge run --bidirectional staging.ordersThe connection string can also come from an environment variable:
HINGE_CONNECTION=postgresql://... hinge runSee examples/quickstart/ for a complete working example.
use hinge::{BuildGraph, RunAll, PostgresExecutor};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let graph = BuildGraph::from_dir("models")?;
let executor = PostgresExecutor::new("postgresql://user:pass@localhost/mydb").await?;
RunAll::new(graph, executor).execute().await?;
// Runs: raw.events → staging.orders → mart.revenue
Ok(())
}Most ELT frameworks run on Python. For pure SQL transformation work, that means:
- A virtual environment to maintain per project
pip installconflicts that silently break pipelines- Slow interpreter startup on every run
- A Python process mediating every query to your database
- Dependency hell when two tools need incompatible package versions
Hinge removes that layer entirely. It's a single binary. No interpreter, no virtual environment, no package manager involved at runtime.
| hinge | dbt Core | |
|---|---|---|
| Runtime | Single binary (~5 MB) | Python + venv (~500 MB) |
| Setup | cargo install hinge_cli |
pip install dbt-core dbt-postgres + adapter |
| Cold start | < 10 ms | ~1–3 s |
| SQL syntax | Plain SQL | Jinja-templated SQL |
| Dependency declaration | Automatic (parsed from FROM/JOIN) |
Manual {{ ref() }} calls |
| Parallel execution | Yes (wave-based) | Yes (dbt 1.x+) |
| Dry run | hinge plan |
dbt compile + dbt ls |
| ClickHouse atomic swap | Yes (EXCHANGE TABLES) |
Third-party adapter |
| DuckDB support | Yes (embedded, no server) | No |
Measured on macOS Apple M-series with 50 SQL models (3-layer graph):
| Operation | hinge | Notes |
|---|---|---|
hinge plan (50 models) |
~11 ms | Graph resolution + sorted output, no DB connection |
| Python 3 cold start | ~15 ms | Just python3 -c "pass" — before importing anything |
| dbt-core cold start | ~1–3 s | Community benchmarks; imports Click, networkx, Jinja2 |
hinge plan resolves a 50-model dependency graph, computes the topological sort, and prints the full execution order in about the same time it takes Python to start its interpreter — before loading a single package.
Zero runtime dependencies The compiled binary is the runtime. Ship it, run it anywhere.
Dependency resolution from plain SQL
Hinge parses FROM and JOIN clauses in your .sql files to build the dependency graph automatically. No ref() calls, no manifest files to maintain — your SQL is the source of truth.
Parallel execution Assets with no dependency between them run concurrently in the same wave. Sequential only where it's actually required.
Precise rebuild modes Run only what changed — upstream, downstream, or both — instead of rebuilding the full graph every time.
Atomic table replacement
On ClickHouse, Hinge uses EXCHANGE TABLES to swap a rebuilt table with the live one atomically. No query downtime during a refresh.
Structured observability
Every asset run emits a structured tracing event with kind, rows affected, and duration. Slow queries are surfaced at WARN automatically.
Download the latest release for your platform from the Releases page.
# macOS / Linux
tar xzf hinge-v*.tar.gz
mv hinge /usr/local/bin/cargo install hinge_cli# Cargo.toml
[dependencies]
hinge = "0.1"The default kind is VIEW. Override with a header comment at the top of the file:
-- @kind: table
SELECT ...-- @kind: materialized_view
SELECT ...ClickHouse table options are passed through as headers:
-- @kind: table
-- @clickhouse.order_by: (event_date, user_id)
-- @clickhouse.partition_by: toYYYYMM(event_date)
SELECT ...| API | CLI flag | Behaviour |
|---|---|---|
RunAll |
(default) | Full graph in dependency order |
RunUpstream |
--upstream schema.model |
All ancestors of a node, then the node itself |
RunDownstream |
--downstream schema.model |
The node, then all its descendants |
RunBidirectional |
--bidirectional schema.model |
Both directions from a node |
Each use case also exposes .plan() to preview the execution order without running anything.
Hinge is a batch engine, but --downstream makes it a natural fit for event-driven pipelines. Instead of rebuilding the full graph on a schedule, trigger only the affected subgraph when new data arrives:
new rows land in raw.events
↓
webhook / pg_notify / cron every 30 s
↓
hinge run --downstream raw.events
↓
only staging.orders → mart.revenue rebuild — nothing else touched
Because hinge starts in < 10 ms and runs only what actually depends on the changed model, this pattern gives you near-real-time tables without a streaming infrastructure.
A typical shell hook with pg_notify:
# called by your ingest process after each batch load
hinge run --downstream "$CHANGED_MODEL"Or via a simple cron for high-frequency sources:
* * * * * hinge run --downstream raw.events # every minuteWatch mode (hinge watch) — automatic re-run on source change — is on the roadmap.
| Database | Status |
|---|---|
| PostgreSQL | stable |
| ClickHouse | stable |
| DuckDB | stable |
| Snowflake | stable |
| Item | Status |
|---|---|
| Delta / incremental runs | planned |
Watch mode (hinge watch) |
planned |
| Homebrew tap | planned |
| BigQuery connector | planned |
| Redshift connector | planned |
| OpenTelemetry export | planned |
| Web UI for graph visualization | exploring |
Have a use case not covered? Open a discussion or file an issue.
Contributions are welcome. See CONTRIBUTING.md for setup, code style, and how to submit changes.
Please note that this project is released with a Contributor Code of Conduct. By participating, you agree to abide by its terms.
To report a vulnerability, please follow the process described in SECURITY.md.
Apache-2.0 — see LICENSE.