Skip to content
zsltgPublic

About

Run jq filters across NoSQL databases, the filter selects the keys, so it never scans more than you ask for.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

604 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

iq

CI Coverage
OpenSSF Scorecard OpenSSF Best Practices CodeScene Code Health
Release Go Reference Go version License: MIT

iq

iq runs jq filters to query, dump, copy, diff and write data across NoSQL databases, and their dump files, from a single static binary. See Drivers for supported databases.

iq registers a MongoDB source, reads one document by key, filters a scan with a pushed-down predicate, explains the plan, and prints the result as gron

iq normalizes fetched values to JSON. The filter runs entirely client-side, so one filter means the same thing everywhere.

The filter is also the key selector. Its top-level paths name the keys to fetch. As a result, a query reads only what it asks for (see Architecture).

Typed dumps carry native types across stores. As a result, a copy, a restore or a migration is one command instead of an export plus a conversion script.

iq is inspired by sq, whose command set it deliberately follows to make the tool feel familiar.

Note

iq is built with AI assistance, every change passes the full test suite, container-backed integration tests for every backend and a mutation gate before it lands (see CONTRIBUTING.md).

Queries are read-only. --insert, --replace, iq data clear, iq data drop and iq data delete write to the target. iq exec forwards a native command to the database, so it can write too. Use --explain to see the query plan or --dry-run to report the effect of a write, without changing anything. iq exec has no dry run.

Feedback and bug reports are very welcome. Report security problems privately, see the security policy.

Install

iq ships as a single static binary (no runtime dependencies, no CGO), prebuilt for Linux, macOS, and Windows on amd64 and arm64.

Linux

curl -fsSL https://raw.githubusercontent.com/zsltg/iq/main/install.sh | sh

The script downloads the release for your OS/arch, verifies its SHA-256 against the release checksums, and installs the binary. IQ_VERSION pins a version and IQ_INSTALL_DIR picks the target directory. Or grab a .deb, .rpm, .apk, or Arch .pkg.tar.zst from the releases. Each release carries a cosign signature and SLSA build provenance, see Verify a release.

macOS

brew install zsltg/tap/iq

The Linux curl … | sh one-liner works on macOS too.

Windows

scoop bucket add zsltg https://github.com/zsltg/scoop-bucket
scoop install iq

Go

go install github.com/zsltg/iq@latest

From source

git clone https://github.com/zsltg/iq
cd iq && make build

Agent Skill

iq ships an Agent Skill, a single Markdown file (skills/iq/SKILL.md) that any agent reading the Agent Skills format can load. It covers these topics:

  • Finding a source
  • --explain before every scan
  • --dry-run before every write
  • The machine-readable output flags and the JSON error shape
  • The rules around destructive commands.
npx skills add zsltg/iq

Agent MCP

For an agent that speaks MCP, iq mcp serves the same core over stdio.

It is read-only by default. --allow writes|exec|destructive opens the rest. A tool that is not allowed is never registered. Every result is capped, every error is redacted, and every destructive call is confirmed.

Claude Code:

claude mcp add iq -- iq mcp --timeout 30s

Codex CLI:

codex mcp add iq -- iq mcp --timeout 30s

Gemini CLI:

gemini mcp add iq iq mcp -- --timeout 30s

Cursor, Cline and Antigravity:

{
  "mcpServers": {
    "iq": {
      "command": "iq",
      "args": ["mcp", "--timeout", "30s"]
    }
  }
}

Copilot in VS Code, which names the map servers and wants the transport spelled out:

{
  "servers": {
    "iq": {
      "type": "stdio",
      "command": "iq",
      "args": ["mcp", "--timeout", "30s"]
    }
  }
}

OpenCode, which names it mcp and takes the command as one array:

{
  "mcp": {
    "iq": {
      "type": "local",
      "command": ["iq", "mcp", "--timeout", "30s"],
      "enabled": true
    }
  }
}

Check AI agents for each client's config file and the key it wants around the block.

Shell completions

The .deb, .rpm, .apk and .pkg.tar.zst packages install bash, zsh and fish completions for you. For a brew, scoop, go-install or source build, iq completion <shell> prints a script to install by hand:

# bash: load in the current session, or drop it on the completion path
eval "$(iq completion bash)"
iq completion bash | sudo tee /usr/share/bash-completion/completions/iq >/dev/null

# zsh: write to a directory on your $fpath, then restart the shell
iq completion zsh > ~/.zsh/completions/_iq

# fish
iq completion fish > ~/.config/fish/completions/iq.fish

# powershell: append to your profile
iq completion powershell >> $PROFILE

Completions cover the commands, their sub-subcommands and flags. They also cover your saved source handles, groups, and config-option keys, which they read live from your config. As a result, iq --src <TAB> offers the sources iq ls lists.

In zsh, fish, and PowerShell, a candidate shows a short description next to it. Bash shows the descriptions when it lists more than one candidate. A source handle shows its driver, its keyspace, and active for the active source, for example prod/books mongo, orders, active. It never shows the host, the user, or the URI. A group shows its number of sources, and a config key shows the help text of its flag. To turn the descriptions off, generate the script with iq completion <shell> --no-descriptions.

A flag that takes a closed set completes its values (--format, --from-format, --format.decimal, --log.level, --log.format, --error.format, --debug.pprof, iq add --driver/--store, iq schema --format). iq config set <option> <TAB> offers that option's own values. iq inspect --only <TAB> and iq diff --section <TAB> offer the introspection subcommands of the selected source's backend, worked out from its saved URI.

iq add <TAB> completes the connection URI part by part. It offers the schemes, then the names of the URI options that iq itself reads, then the values of an option that has a closed set of values. A shell gives ? and & a special meaning, so type them with a backslash, for example iq add cassandra://host/ks\?consistency=lo<TAB>, which offers local_quorum and local_one. Completion does not work inside quotes, because the bash and zsh completion scripts pass the quote character to iq. A quoted URI still runs correctly. Completion does not offer the host, the path, or the options that a backend SDK reads, such as authSource for MongoDB. It also offers nothing when the typed text holds a password, so a password never appears in a candidate.

The jq filter itself is a program, not a completable value. As a result, iq offers no candidates there (and never falls back to filenames). iq also offers no candidates for the backend verb of iq exec and its operands.

Every completion is offline. It reads your config file and nothing else. As a result, a <TAB> never opens a connection, never reads the OS keyring, and cannot hang. That is why a collection suffix does not complete. iq --src shop.<TAB> offers nothing, because listing collections needs a connection.

Man page

The packages also install an iq(1) manual page, so man iq works after a package install. For a non-package install, pipe it into your man path:

iq man | sudo tee /usr/share/man/man1/iq.1 >/dev/null

Get started

Sources

Driver list:

iq driver ls

Add a collection named "books" from a MongoDB source and make it active:

iq add -a 'mongodb://localhost:27017/iq?collection=books'

Inspect a source:

iq inspect books

List sources:

iq ls

Check Sources for more details.

Query data

Fetch an item with the ID "2":

iq '.["2"]'

Filter a scan and reshape each item:

iq '.[] | select(.year > 2015) | {title, price}'

Print a formatted query plan:

iq '.[] | select(.year > 2015) | {title, price}' --explain -v

Check Query data for more details.

Diff

Diff schema of two sources:

iq diff --schema dev qa

Diff the items with the same ID from two sources:

iq diff 'dev=.["1"]' 'qa=.["1"]'

Diff items key by key:

iq diff dev qa

Check Diff for more details.

Write data

Copy one source into another, keys preserved (a cross-driver copy carries values only):

iq --src books --insert books2

Create a lossless (typed) dump:

iq --src cache --typed -o dump.jsonl

Add a dump as a source and restore it to a live source:

iq add file:///dump.jsonl -n snap
iq --src snap --insert cache

Copy only the items a filter selects:

iq '.[] | select(.year > 2015)' --src books --insert recent

Check Write data for more details.

Cross-source query

Compose across sources:

iq 'INDEX(source("users"; ".[]"); .id) as $u
    | source("orders"; ".[] | select(.total > 99)")
    | {name: $u[.userId].name, total}'

Combine across sources:

iq combine 'users=.[] | {id, name}' \
           'orders=.[] | select(.total > 99)' \
   --with '($users | INDEX(.id)) as $u | $orders[] | . + {name: $u[.userId].name}'

Check Cross-source queries for more details.

Keyspace commands

Delete two items:

iq data delete cache book:1 book:2

Empty a source called "cache":

iq data clear cache

Drop a collection called "orders":

iq data drop shop.orders

Check Delete, Clear and Drop for more details.

UNIX pipes

Implicit stdin source:

cat dump.jsonl | iq '.[]'

Check Insert for more details on piped stdin.

Drivers

iq picks the backend from a source's URI scheme. The query core is driver-agnostic, so further backends slot in behind the same port. The Drivers page documents these topics for each driver:

  • Keyspace mapping
  • Value encoding
  • Predicate pushdown
  • Raw commands.
Name Database Versions
cassandra Apache Cassandra 3.11+
couchbase Couchbase 7.x, 8.x (Community or Enterprise)
couchdb Apache CouchDB 2.x, 3.x
dynamodb Amazon DynamoDB AWS (managed)
elasticsearch Elasticsearch 8.x
file Local dump file, read-only (see Backups and dumps) —
hbase Apache HBase 1.0+
mongo MongoDB 4.2+
neo4j Neo4j 5.x
opensearch OpenSearch 2.x, 3.x
redis Redis 7.0+

The Versions column lists the range of backend server versions the bundled client library supports.

Guarantees

Drivers differ in encoding and pushdown detail, but every backend honors the same contract:

  • One URI, native nouns. The URI scheme picks the driver. The keyspace rides in the URI as the backend's own noun (?collection=, ?table=, ?database=, ?label=/?rel=, ?index=). A query overrides it per run with the dotted handle.<keyspace> suffix.
  • One jq interface. A bounded filter fetches exactly the named keys. A missing key reads as null, never an error. A .[]-rooted filter streams the keyspace in bounded pages. A filter that collapses the keyspace into one value materializes only behind --unbounded.
  • Pushdown never changes results. A pushed predicate is only ever a conservative pre-filter. Where the backend can filter, the pre-filter is server-side. Where the backend cannot, the pre-filter is a client-side raw-byte prefilter that drops a provable non-match before decode (Redis, on RedisJSON values, Elasticsearch/OpenSearch and Couchbase, over the residual their server-side query cannot narrow). The full jq always re-runs client-side. As a result, output is identical with or without the pushed predicate, and --explain shows exactly what was pushed.
  • Capabilities are explicit. Filtered scans, count estimates, writes, clear, drop and per-key delete are opt-in ports. A backend implements what its model supports. A command against a missing capability fails with a clear message instead of emulating it. For example, Redis, whose DB index cannot be removed, has no drop. The read-only file dump has no per-key delete.
  • Values round-trip. Every value normalizes to JSON under a frozen per-backend encoding contract. A --typed dump restores through --insert losslessly.
  • Bounded and redacted. --timeout bounds every backend call. iq redacts a URI's password from every listing, log line and error.
  • Native commands. iq exec speaks the backend's own language, verbatim where one exists (Redis commands, Mongo command documents, CQL, PartiQL, Cypher, Mango, the Elasticsearch DSL). Where none exists, iq exec speaks a small fixed verb set (HBase). See each driver's Raw commands section on the Drivers page. Every iq flag must come before exec. iq forwards everything after exec to the backend untouched.

Backups and dumps

A file:// source reads a database dump straight from disk. As a result, you can do these operations on a snapshot through the same jq interface, with no running server:

  • Query it
  • Inspect it for shape
  • Diff it against a live source
  • Restore it.

A file:// source is read-only. A file:// endpoint is never a copy destination. iq exec and iq inspect, which need a live server, do not apply.

Register a dump like any other source:

iq add -n snap file:///backups/prod.rdb

Query, diff and restore it with no server running:

iq --src snap '.["session:42"]'
iq diff snap cache --data
iq --src snap --insert cache
Type Description
jsonl iq typed JSON Lines / array
yaml iq typed YAML
mongoexport mongoexport Extended JSON
bson mongodump BSON
rdb Redis RDB snapshot
?format=dynamodb-json DynamoDB S3 export / scan JSON
?format=cassandra-csv cqlsh COPY TO CSV
?format=neo4j-json Neo4j APOC JSON export

A type with a bare name auto-detects (file:///<file_path>). For a type in the ?format= rows, you must pass the ?format= form (file:///<file_path>?format=<source_format>). The Drivers page documents what produces each format, the options some of them need, and the round-trip caveats.

Architecture

The URI scheme chooses the backend. The query core is driver-agnostic, so further backends slot in behind the same port.

The filter is both the transform and the key selector. Its top-level paths name the keys to fetch, so a normal query reads a bounded set of keys. A .[]-rooted filter streams the keyspace in pages. A filter that collapses the keyspace into one value materializes only behind --unbounded. iq normalizes fetched values to JSON. The filter then runs entirely client-side, so its semantics are identical for every backend.

The CLI and iq mcp are two thin delivery mechanisms over that one core. The MCP server exposes the CLI's own operations as tools, resolves the same saved sources, and runs the same engine. As a result, it adds no port and changes no classification. It adds only its own bounds, a tool set fixed at startup by --allow, per-result item and byte caps, and the CLI's redacted error shape.

Query routes

The selector classifies a jq filter. iq optionally decomposes a scan into a native predicate. Each backend then maps that predicate its own way.

Regardless of pushdown, the full jq re-runs client-side. As a result, the pushed predicate is only ever a conservative pre-filter, and results are identical with or without it.

Pushdown reads only the select() stages that test each item itself: the stages after .[] up to the first stage that changes the item, such as .b, map(...) or an object construction. A select() after such a stage tests a derived value, so pushdown leaves it to the client-side filter.

graph TD
  F["jq filter (CLI)"] --> SEL["selector.Keys<br/>(AST analysis)"]
  SEL -->|bounded| GET["KVStore.Get(keys)"]
  SEL -->|"streamable scan"| CMP{"FilteredScanner?<br/>(pushdown on)"}
  SEL -->|"holistic scan"| MAT["materialize<br/>(--unbounded)"]

  CMP -->|yes| PD["pushdown.Compile → predicate.Node<br/>(adapter pre-filters server-side)"]
  CMP -->|no| RS["KVStore.ScanBatches<br/>(full scan, no pushdown)"]

  GET --> JQ["run full jq<br/>client-side, per batch"]
  MAT --> JQ
  PD --> JQ
  RS --> JQ
  JQ --> OUT["format renderer<br/>→ output"]
Loading

A scan emits per-page progress to a stderr spinner (CLI only, off unless attached to a terminal). An unfiltered scan can also fetch a cheap up-front total estimate (where the backend metadata makes it possible).

A bounded filter runs client-side over only the named keys, so its cost is O(keys requested). A streamable scan runs in O(page) memory.

gojq (pure Go, no cgo) provides jq and exposes the AST the key selector walks.

Write routes

Writes ride the query command. There is no separate copy tool. Items arrive from --src (a live source or a file:// dump) or piped stdin. iq reads each item as a typed record, so the native type survives the trip.

The jq filter transforms each item with its key preserved. This transform is the one place where a filter runs per item rather than over the whole keyspace. Iteration is implicit, and you do not write .[].

graph TD
  MV["iq --insert / --typed (CLI)"] --> MSRC["source: --src (live or file:// dump) / stdin → TypedScan"]
  MSRC --> TX["per-item jq transform + re-key"]
  TX --> DST{"--insert or --typed?"}
  DST -->|--insert| PUT["Putter.Put (upsert / insert-only)"]
  DST -->|--typed| ENC["emit {key,type,value} → jsonl / json / jsona / yaml"]
  PUT --> BW["backend adapter:<br/>type-aware native writes"]
  LF["iq data clear / drop / delete (CLI)"] --> CAP["Clearer.Clear / Dropper.Drop / Deleter.Delete (capability-gated)"]
Loading

See also

Where iq sits among other tools, and the jq ecosystem its filter language carries over.

  • jq manual: the language reference for the filters iq runs.
  • awesome-jq: a curated list of jq tools, guides, and resources.
  • sq: jq-style queries over SQL databases and document files.
  • gojq: the pure-Go jq implementation iq embeds.
  • jaq: a Rust jq clone focused on speed and stricter semantics.
  • yq: jq-style filters for YAML, TOML, and XML.
  • fq: jq for binary formats.
  • jc: converts classic CLI output to JSON.
  • jqp: a TUI playground that live-previews a filter as you type.
  • ijq: interactive jq with a side-by-side input and output view.

About

Run jq filters across NoSQL databases, the filter selects the keys, so it never scans more than you ask for.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages