Architecture

Four-stage pipeline from wire to storage

~24K lines of C. Zero dependencies. Four stages: wire protocol, parser, executor, storage. Every query flows through the same pipeline. The block-oriented plan executor processes 1,024-row column blocks for cache-friendly, vectorized execution.

The pipeline

Every query passes through four stages:

StageSourceDescription
Wire protocol pgwire.c Accepts TCP connections on port 5433, handles SSL negotiation, implements the Simple Query, Extended Query (prepared statements, portals, $1/$2 parameter substitution), and COPY protocols (tab-delimited and CSV), intercepts information_schema and pg_catalog queries for tool compatibility, and serializes result rows
SQL parser parser.c Hand-written recursive-descent parser producing a typed struct query AST—no generators. ~4,500 lines with proper operator precedence, CTEs, window functions, set operations, and generate_series() table functions
Query executor query.c, database.c Block-oriented plan executor (plan.c, ~4,500 lines) handles most queries with vectorized operators: scan, filter, project, sort, hash join, hash aggregation, index scan, window functions, set operations, CTEs, and generate_series(). A scan cache with generation tracking skips redundant scans. Direct columnar-to-wire serialization bypasses row materialization. Complex queries (correlated subqueries, ROLLUP/CUBE) fall back to the legacy row-at-a-time evaluator (query.c, database.c)
Storage table.c, row.c, index.c Tables are arrays of rows; rows are arrays of typed cells. B-tree indexes for equality lookups. Snapshot-and-restore for transaction rollback

Memory management

Arena allocator

All memory is managed through a pool-based arena (arena.h). Parsed structures use uint32_t indices instead of pointers. Freeing an entire query is one query_arena_destroy() call—no recursive walks, no per-node free().

Bump allocators

A slab-chain bump allocator (arena.scratch) handles temporary executor state: aggregate accumulators, sort buffers, window-function arrays. All freed in bulk per query—zero malloc/free calls at steady state. A separate result-text bump allocator (arena.result_text) eliminates per-cell strdup for text results.

Expression evaluator

Expressions are a tagged-union AST (struct expr) evaluated by eval_expr(). Supports CAST/::, date/time arithmetic, 30+ built-in scalar functions (math, string, temporal), CASE WHEN, and subqueries.

Internal abstractions

Dynamic-array macro (dynamic_array.h), zero-copy string views (stringview.h), and JPL-style ownership: the allocating module deallocates.

Why this design

Fast. The block-oriented executor with columnar scan caching beats PostgreSQL on 16 of 19 batch benchmarks. Aggregate queries run 8× faster. Direct columnar-to-wire serialization avoids row materialization entirely.

Simple. Zero external dependencies. One .c file per stage. The entire codebase fits in a single developer’s head.

Safe. Every test runs under AddressSanitizer. Arena allocation eliminates use-after-free and leak classes by construction.

Explore further

Benchmarks vs PostgreSQL  ·  SQL grammar  ·  Source on GitHub  ·  Try it in the browser