Hinge
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.
The problem with Python-based data tooling
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 vs dbt-core
| 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 |
Benchmarks
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.
Benefits
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.
Installation
Prebuilt binary (recommended)
Download the latest release for your platform from the Releases page.
# macOS / Linux
Via cargo
As a library
# Cargo.toml
[]
= "0.1"
Quick start (CLI)
Organize 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 a hinge.yaml at the project root:
connection: postgresql://user:pass@localhost/mydb
models: models
Preview the execution plan without touching the database:
# 1. raw.events
# 2. staging.orders
# 3. mart.revenue
Run everything:
Run a subset:
# Everything staging.orders depends on, then staging.orders itself
# staging.orders and everything that depends on it
# Both directions from a node
The connection string can also come from an environment variable:
HINGE_CONNECTION=postgresql://...
See examples/quickstart/ for a complete working example.
Quick start (library)
use ;
async
Asset types
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 ...
Run modes
| 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.
Connectors
| Database | Status |
|---|---|
| PostgreSQL | stable |
| ClickHouse | stable |
| DuckDB | stable |
| Snowflake | stable |
Roadmap
- Config file for connection strings (in progress)
- Delta / incremental runs
- Homebrew tap
- Watch mode (
hinge watch)
Contributing
See CONTRIBUTING.md.
License
Apache-2.0 — see LICENSE.