Skip to content

lsalenson/hinge

Hinge

CI Crates.io docs.rs Downloads License

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.

hinge plan demo


Quick start (CLI)

Install:

cargo install hinge_cli
# or download a prebuilt binary from the Releases page

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 hinge.yaml at the project root:

connection: postgresql://user:pass@localhost/mydb
models: models

Preview the execution plan without touching the database:

hinge plan
# 1.  raw.events
# 2.  staging.orders
# 3.  mart.revenue

Run everything:

hinge run

Run 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.orders

The connection string can also come from an environment variable:

HINGE_CONNECTION=postgresql://... hinge run

See examples/quickstart/ for a complete working example.


Quick start (library)

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(())
}

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 install conflicts 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
tar xzf hinge-v*.tar.gz
mv hinge /usr/local/bin/

Via cargo

cargo install hinge_cli

As a library

# Cargo.toml
[dependencies]
hinge = "0.1"

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.


Event-driven rebuilds

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 minute

Watch mode (hinge watch) — automatic re-run on source change — is on the roadmap.


Connectors

Database Status
PostgreSQL stable
ClickHouse stable
DuckDB stable
Snowflake stable

Roadmap

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.


Contributing

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.


Security

To report a vulnerability, please follow the process described in SECURITY.md.


License

Apache-2.0 — see LICENSE.

About

SQL-native ELT engine — dependency graph resolved automatically from FROM/JOIN clauses, single binary, no Python

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages