Every database in one native app. Free and open source.

Postgres, MySQL, SQLite, Redis and MongoDB — SQL and documents as equals, not one bolted onto the other. A native macOS app and a CLI, both over one Rust engine. No Electron, no JVM.

build from source
git clone https://github.com/chud-lori/datagrep.git
cd datagrep
cargo build --release            # engine + CLI
cd ui/macos && ./build-app.sh    # macOS app — no Xcode needed

Rust toolchain + macOS Command Line Tools are the only prerequisites.

128 ms
to the first row of a 1,000,000-row query that takes 12.4 s to finish. You read while it streams.
174 MB
peak memory streaming that same 1M-row result — flat, it does not grow with the dataset.
3–8 ms
to cancel a query, with zero orphaned queries left running on the server.
7–29 ms
to connect to a schema with 100,000 relations — the catalog loads lazily, never crawled up front.

Every number above was measured on 2026-08-08 with the release CLI against a live postgres:16 (1M-row × 24-column table). Method and full results: notes/test-report.md. No other benchmark claims are made on this page.

Why datagrep

Like DataGrip, but free and light

Most database clients make you choose: a heavyweight IDE, an Electron app, or one tool per engine. datagrep’s bet is different — one small native app where relational rows and documents are equals.

SQL and NoSQL as equals

PostgreSQL and MongoDB sit in the same sidebar with the same interaction model. Document databases aren’t bolted onto a SQL tool, and SQL isn’t bolted onto a document viewer.

Results stream, memory stays flat

The driver, a bounded channel, and the result store form a pipeline where nothing runs more than two chunks ahead. A million-row result never becomes a million resident rows.

Native macOS

SwiftUI + AppKit over a Rust engine. No Electron, no JVM. The CLI binary is 7.7 MB.

Cancel actually cancels

Measured at 3–8 ms from cancel to a usable connection, with no orphaned server-side queries — verified in pg_stat_activity.

Three kinds of blank

NULL, the empty string, and a field that is absent from a document are three different facts — and render as three visibly different things. See below.

Free and open source

No tiers, no licence server, no account. It works offline, and the source is on GitHub.

Schema, not just names

Click a table and the detail pane shows its columns with types, nullability and primary keys, its indexes, and row / size estimates. For MongoDB the fields are inferred from a sample — and the pane says so, because a field missing from the sample may still exist in the collection.

Read-only that admits its limits

Mark a connection read-only and the badge says exactly how real that promise is: enforced by the server only when the engine itself refuses writes, blocked by datagrep only when the guard is our client-side classifier. It never claims more protection than exists.

Query history, in both frontends

⌘Y opens every query you’ve run, searchable, in the app — the same history the CLI’s history command reads.

A trust surface, not cosmetics

NULL is not "" is not absent

In a document store a field can be missing entirely — a third fact, distinct from NULL and from an empty string. Most clients collapse all three into one blank cell; our competitor study found none that cleanly distinguishes all three, and several with shipped bugs here — an invisible NULL label in dark mode, NULL exported as the literal string "null". datagrep renders each one distinctly, in the grid and in the CLI.

idnamenotewhat the cell means
1alice NULL The value is NULL — explicitly unknown.
2bob "" The value is an empty string — known, and empty.
3carol likes SQL An ordinary value.
4dave absent The document has no note field at all.

The honest comparison

How it stacks up

Every claim about a competitor below traces to a cited source in our UX reference study — GitHub issues, official docs, or the vendor’s own pages. Where the study found nothing, the cell says so. And where a competitor is ahead of us, the table says that too.

Tool Price & licence Runtime SQL + NoSQL Large results NULL vs "" vs absent Inline editing / autocomplete
datagrep Free, open source. No tiers, no account. Native macOS + Rust engine Both, as equals — Postgres, MySQL/MariaDB, SQLite, Redis, MongoDB, Elasticsearch Streams end-to-end; 174 MB peak through a 1M-row result (measured) Three visually distinct states Neither yet — see Not yet
DataGrip Free for non-commercial use (since Oct 2025); paid subscription (≈$99/yr) for commercial work JVM (IntelliJ platform) SQL-first, very broad engine list 500-row pages; JetBrains publishes its own “high memory consumption” troubleshooting guide Not verified either way in the study Both — its autocomplete is best-in-class (completes entire JOINs)
DBeaver Free Community edition; MongoDB/Redis and other NoSQL require paid editions JVM (Eclipse platform) SQL broad; NoSQL behind paid tiers Paged; long-standing documented startup-time and memory-growth complaints Shipped a bug rendering [NULL] for all string values (#8803) Both
TablePlus Proprietary; free tier capped at 2 tabs, 2 windows, 2 filters Native SQL + some NoSQL 300-row pages; documented lock-ups while autocomplete indexes 100+ schemas (#3649) Not verified in the study Both
Beekeeper Studio Open-source core; paid edition for the full feature set Electron SQL + MongoDB 20k-row on-screen cap; documented crash loading an 18k-table schema (#479) Documented false-NULL display (#2652); exported NULL as the string "null" (#2863) Both
MongoDB Compass Free; MongoDB only Electron MongoDB only — one more app to keep open 25 documents per page; documented freezes on 30 MB+ collections Credit where due: its Schema tab shows an explicit “% undefined” for sparse fields Document editing
RedisInsight Free; Redis only Electron Redis only — one more app to keep open SCAN batches by design; maintainers decline “scan all” to avoid OOM (#526) — which validates datagrep’s prefix-scoped Redis browsing — Key editing

To be plain about where datagrep is behind: every tool in this table has inline cell editing today, and most have autocomplete — datagrep has neither yet. If you edit rows in a grid all day, those tools currently serve you better. What datagrep already does better is the part they routinely get wrong at scale: streaming large results with flat memory, staying responsive against huge schemas, and telling you the truth about blank cells.

Engines

What connects today

One driver crate per engine, behind a capability-based API — no engine ever gets special-cased above the driver seam.

EngineStatus
PostgreSQLworking
SQLiteworking
Redisworking
MongoDBworking
MySQL / MariaDBworking
Elasticsearchworking read-only — no writes yet

The app is macOS today, and it reaches all six engines; a native Linux app (Qt6/C++) over the same Rust engine is in progress. The CLI currently registers PostgreSQL and SQLite only — Redis, MongoDB, MySQL / MariaDB and Elasticsearch surface through the app.

Install

Download, or build from source

Each release ships datagrep-macos.dmg — open it and drag datagrep.app onto the Applications shortcut. The installer script does the same thing, and also installs the CLI:

terminal
curl -fsSL https://raw.githubusercontent.com/chud-lori/datagrep/main/install.sh | bash
# → the CLI; add `-s -- --app` to install the app too

The app checks for a newer release once per launch and only tells you about it — it never downloads or installs anything behind your back, and you can turn the check off.

First launch from the DMG shows a warning. datagrep is not notarized — there is no Apple Developer account behind it — so macOS says it “could not verify datagrep is free of malware”, and on recent versions the only obvious button is Move to Bin. The app is fine; macOS simply cannot attribute it to a registered developer. Open it once via System Settings → Privacy & Security → Open Anyway, or clear the download flag yourself:

terminal
xattr -dr com.apple.quarantine /Applications/datagrep.app

Ctrl-click → Open also worked on older macOS, but no longer does on macOS 15. Installing with the script avoids all of this, because a file that was never downloaded by a browser is never quarantined.

Uninstall

The same script removes what it installed — the CLI on its own, or the app as well with --app.

terminal
curl -fsSL https://raw.githubusercontent.com/chud-lori/datagrep/main/install.sh | bash -s -- --uninstall
# add --app to remove datagrep.app too

Uninstalling deliberately leaves your data alone, so a reinstall finds your connections, query history and saved editors where you left them. To remove those as well, delete ~/Library/Application Support/datagrep and ~/.config/datagrep. Saved passwords live in the login keychain under the service datagrep and go with the connection that owns them.

Build from source

Two commands, two artifacts. The macOS app needs no Xcode — the Command Line Tools are enough, because it builds with Swift Package Manager rather than xcodebuild.

1. Engine + CLI

terminal
cargo build --release
# → target/release/datagrep

2. The macOS app

terminal
cd ui/macos && ./build-app.sh
# → ui/macos/datagrep.app

Prerequisites: a Rust toolchain (see rust-toolchain.toml) and the macOS Command Line Tools (xcode-select --install). cargo test --workspace runs everything that needs no live server.

The CLI

The same engine, in your terminal

Connections are named profiles. A password in a connection URL is moved to the OS keychain on import — profiles store only a reference to it, never the secret.

quickstart
$ datagrep profiles add local "sqlite:///tmp/app.db"
created profile `local` (sqlite)

$ datagrep query --profile local -c "SELECT id, name, note FROM users ORDER BY id"
id  | name  | note
----+-------+----------
1   | alice | NULL
2   | bob   |
3   | carol | likes SQL
(3 rows)

$ datagrep export --profile local -c "SELECT * FROM events" --format csv -o events.csv
$ datagrep query --profile prod -f report.sql --format json | jq '.[].email'

NULL (row 1) renders differently from an empty string (row 2) — the same three-way distinction the app’s grid makes.

query
run SQL (or a Mongo/Redis command) and stream results in table, csv, json, ndjson formats
export
write a full result set to a file
profiles
add, list, show, import/export named connections
catalog
browse databases, schemas, and tables
history
list past queries, with full-text search
doctor
check connectivity and configuration

Block directives

Any statement can carry per-block settings as plain comments — they travel with the query text, so they survive copy-paste and version control. The status bar shows what’s in effect.

editor
-- @limit 200          cap the rows this block returns
-- @timeout 30s        give up after 30 seconds
-- @connection staging run against another profile
-- @readonly           refuse writes, checked client-side
SELECT * FROM events;

In the app, ⌘↵ runs the statement under the caret — never the whole buffer by accident.

Under the hood

How it stays small

The goal, in one line: the client you don’t have to close to get your laptop back. Three design decisions get it there.

Results stream. The driver, a bounded channel, and the result store form a pipeline in which nothing runs more than two chunks ahead. When the UI stops consuming — you stop scrolling — the driver stops reading its socket, the TCP window closes, and the server stops producing. Backpressure reaches all the way to the database, end to end. That is why memory stays flat at 174 MB through a million-row result: the rows you aren’t looking at simply never exist on your machine. A row-number gutter keeps your place in the stream, and when a result stops at the 500,000-row cap the grid says so in words — “stopped at the 500,000-row limit — result incomplete” — rather than showing a count that looks final.

Schema browsing is lazy. Expanding a tree level costs one cheap query for that level — connecting never crawls the catalog. That is the whole trick behind connecting to a 100,000-relation schema in 7–29 ms, and it deliberately avoids the failure mode our competitor study documents in other tools: eager, blocking, whole-catalog work that locks up the app at real-world scale.

The budget is enforced, not aspirational. Startup time, idle memory, first-row latency, and binary size have machine-readable targets in budget.toml, checked by ci/gates.sh. If a change makes the client heavy, CI goes red.

Security

What’s defended — and what isn’t

A database client holds live credentials, opens connections to production systems, and renders whatever those systems send back. So datagrep has a written threat model that treats three inputs as attacker-controlled — the server, the connection URL, and an imported profile bundle. The claims below are the checkable kind: each one points at the code or policy file that makes it true.

Secrets live in the OS keychain

A password never lands in the profile store — the profile keeps only a keychain: reference, and the exported profile format has no field capable of holding a secret, so exports are safe to commit structurally, not by a filter someone has to remember to run. DataGrip gets this right too; DBeaver’s default credentials file misses the bar — it is encrypted with a static, publicly known key (dbeaver#6991).

The supply chain is gated, not trusted

cargo audit and cargo deny run on every pull request, and weekly against the unchanged tree — so an advisory filed against a lockfile nobody touched still goes red on Monday morning. Advisories fail the build whether or not a fix exists, licences are policed, and native-tls / openssl are banned by name so a second TLS stack cannot arrive as someone’s transitive default. The policy is a readable file: deny.toml.

Hostile input is assumed, and tested

Every FFI entry point wraps its body in catch_unwind — a Rust panic unwinding across the C ABI is undefined behaviour, so that wrapper is load-bearing, not defensive style — and every entry point has hostile-input tests. The driver panics found reachable from a malicious connection URL or server response were fixed, with tests pinning each one. The protocol decoders are not fuzzed yet, and SECURITY.md says so rather than rounding up.

Tunnels that fail closed

SSH tunnels run in-process, with no listening port on your machine. Host keys are pinned on first use, and the trust prompt is built so a dropped or unanswered prompt counts as reject — never silent acceptance. And the read-only badge above already told you the same kind of truth: it reports whether the server enforces the promise, or only datagrep does.

The gaps, stated plainly

  • PostgreSQL and MySQL connections are not encrypted. The Postgres driver implements no TLS, and the MySQL driver compiles it out. Both fail loudly — an sslmode=require URL is rejected, never silently downgraded to plaintext — but the consequence stands: across an untrusted network, use the built-in SSH tunnel. That is the supported answer until the drivers grow TLS.
  • A profile bundle is executable. A profile’s secret can be an exec: command, and import does not gate that yet. Treat a bundle you did not write exactly as you would treat a shell script from the same source: read it before importing it.
  • No external audit, one maintainer, ad-hoc code signing. datagrep is pre-1.0 and not notarized — this page says so rather than implying otherwise.

The full threat model, what “out of scope” means here, and the coordinated-disclosure process are in SECURITY.md. Found something? Use private vulnerability reporting — a human reads it within three business days.

The honest part

What’s not built yet

You’ll find these gaps eventually; better you hear them here first. If one of them is your daily driver requirement, datagrep isn’t your tool yet.

  • No inline cell editing. Every competitor in the table above has it. The plan is a staged-edit, review-before-commit model — because a naive implementation can update the wrong row, and that’s worse than not shipping it.
  • No autocomplete. Planned as a restrained, lazily indexed version — our study found a bad autocomplete generates more anger than none.
  • Elasticsearch is read-only — browsing and querying work, writes are not implemented.
  • App-side gaps: no export UI (the CLI has export), no foreign-key click-through, no ER diagrams.
  • macOS only. No Linux or Windows app today.

Something else missing, or broken? File an issue — engineering notes, test reports, and the full competitor study live in notes/.