Your Markdown
is already a database.
mq-db parses Markdown into a flat block list with an interval index (Nested Set / Pre-Post Order), turning heading hierarchy into O(1) integer comparisons. Query it with SQL or mq, persist it to a compact custom page-file format — no SQLite dependency.
the source, annotated at index time
1 every block gets a pre/post pair at index time — "is X inside Y?" becomes one integer range comparison, not a tree walk.
2 the query planner picks a BitmapIndex, BTreeIndex, or HashIndex automatically, based on the predicate.
Hierarchy checks
without a tree walk.
Most Markdown tooling answers "what's inside this section?" by
walking a tree. mq-db assigns every block a
pre/post
pair (Nested Set encoding) at index time, so an ancestor
check becomes a single interval comparison —
under(pre, post, anc_pre, anc_post).
Three secondary index layers push SQL predicates down before a
single block is materialized: a BitmapIndex
on block type, a BTreeIndex on pre/post, and a
HashIndex on content/lang/depth.
-- all text/code nested under the "Architecture" heading SELECT b.block_type, b.content FROM blocks b WHERE under(b.pre, b.post, (SELECT pre FROM blocks WHERE block_type = 'heading' AND content = 'Architecture'), (SELECT post FROM blocks WHERE block_type = 'heading' AND content = 'Architecture')) AND b.block_type IN ('paragraph', 'code') ORDER BY b.pre;
fig. 1 — scoping a query to everything nested under a heading, via a single interval comparison.
O(1) ancestry
Interval index turns "is X inside Y?" into an integer range check.
Dual engines
A custom sqlparser-based SQL evaluator, and mq via mq-lang — no SQLite.
Zone pruning
Per-document stats skip irrelevant files before any block is scanned.
SQL, joins included
documents and blocks are
plain virtual tables — join them like any relational schema, plus
self-joins over the interval index for structural checks.
-- documents that contain Python code SELECT DISTINCT d.path FROM documents d JOIN blocks b ON b.document_id = d.id WHERE b.block_type = 'code' AND b.lang = 'python';
fig. 2 — a plain relational join across documents and blocks.
-- H2 headings immediately followed by a list SELECT d.path, h.content AS heading FROM blocks h JOIN blocks nxt ON nxt.document_id = h.document_id AND nxt.pre = h.pre + 1 JOIN documents d ON d.id = h.document_id WHERE h.block_type = 'heading' AND h.depth = 2 AND nxt.block_type = 'list';
fig. 3 — a structural self-join, one row apart in pre-order.
-- run an mq program against Markdown content, in SQL SELECT mq('.h1 | to_text', content) AS title FROM blocks WHERE block_type = 'code' AND lang = 'markdown';
fig. 4 — mq embedded as a scalar function inside a SQL projection.
-- bucket headings by depth, summarize per bucket SELECT CASE WHEN depth <= 1 THEN 'top-level' ELSE 'nested' END AS bucket, count(*), group_concat(initcap(trim(content)), ', ') AS headings FROM blocks WHERE block_type = 'heading' GROUP BY CASE WHEN depth <= 1 THEN 'top-level' ELSE 'nested' END;
fig. 5 — CASE expressions and aggregates over heading depth.
Index
9 entriesFlat block storage
Every Markdown element becomes a typed Block with row-polymorphic properties.
O(1) hierarchy queries
Interval index (pre/post) makes ancestor/descendant checks a single comparison.
Three-layer indexes
Bitmap, BTree, and Hash indexes for fast SQL predicate pushdown.
Zone maps
Per-document statistics skip irrelevant files before scanning any blocks.
Dual query engines
SQL via a custom sqlparser-based evaluator, and mq via mq-lang.
DDL support
CREATE TABLE, INSERT INTO, DROP TABLE for in-memory custom tables.
Function library
String, numeric, null-handling, CASE, and aggregate functions comparable to a general-purpose RDBMS.
Custom page storage
8 KB fixed pages, checksums, atomic writes to a single-file format.
CLI + REPL + TUI
Full terminal experience, plus an HTTP server for SQL and mq over JSON.
Install
# downloads, verifies (SHA256), and installs to ~/.local/bin
curl -fsSL https://raw.githubusercontent.com/harehare/mq-db/main/bin/install.sh | bash
mq-db index docs/ --recursive && mq-db sql "SELECT block_type, count(*) FROM blocks GROUP BY block_type"