Case studies, applied AI, and engineering notes from Nairobi.

case study

Building a Relational Query Engine in TypeScript

The project explored how relational databases interpret, evaluate, and execute SQL internally without relying on existing database systems.

12 min read

Definition

Role

Systems and full-stack engineer

Timeline

Challenge build — 2026

Outcome

Delivered a working relational query engine with parsing, execution, joins, schema operations, automated tests, a terminal REPL, and a browser-based SQL playground.

Stack

TypeScriptBunNearleyMooLitElementVitestVite

System Snapshot

Input

SQL queries submitted through a terminal REPL or browser-based SQL interface.

Processing

Queries moved through tokenization, grammar parsing, AST generation, semantic evaluation, and relational execution against in-memory table structures.

Output

Executed query results, relational joins, schema mutations, and formatted table responses returned interactively through the REPL and playground

Overview

This project was built for the PesaPal Junior Developer Challenge 26 as a practical exercise in database internals, query execution, and systems design.

Rather than using an existing database engine, the project focused on implementing the mechanics of relational query processing directly in TypeScript. The system parses SQL text, transforms queries into executable structures, and evaluates them against an in-memory relational storage layer.

The final result is a lightweight relational query engine with two interfaces:

  • a terminal REPL for direct query execution
  • a browser-based SQL playground for interactive testing

Both interfaces operate on the same backend execution pipeline.

More than a CRUD application, the project became an exercise in compiler-style architecture, relational evaluation, and execution flow design.

Problem

Most developers interact with SQL databases through abstractions such as ORMs, managed services, or database drivers. While these tools simplify application development, they also hide the mechanics of how relational systems actually interpret and execute queries.

This project explored a more foundational question:

How does a relational database convert raw SQL text into executable operations against structured data?

The challenge was to build a small but functional relational engine capable of supporting realistic query workflows while remaining modular, understandable, and testable.

The focus was not full SQL compatibility. Instead, the goal was to understand the execution lifecycle behind relational systems:

  • parsing
  • AST generation
  • query evaluation
  • relational joins
  • schema management
  • state mutation

Approach

The system was designed around a staged query execution pipeline.

Rather than executing SQL directly from text input, queries move through several independent phases before evaluation:

raw SQL input

lexical tokenization

grammar parsing

AST generation

semantic validation

execution planning

relational evaluation

formatted output generation

This separation made the system easier to reason about and allowed each layer to evolve independently.

The project also intentionally separated interfaces from execution logic. Both the REPL and browser playground rely on the same backend engine, which ensured consistent behavior across environments and reduced duplication.

System Design

The architecture follows a three-layer structure:

Interface Layer

The system exposes two interfaces for interacting with the query engine.

Terminal REPL

The REPL provides direct SQL execution from the command line, making it useful for rapid testing and iterative query evaluation.

Browser SQL Playground

A lightweight browser interface built with LitElement allows interactive query execution in a more accessible environment.

Both interfaces share the same execution backend, ensuring identical parser and runtime behavior regardless of entry point.

Query Processing Layer

The query processing layer is the core of the system.

It is responsible for transforming raw SQL text into executable relational operations.

Lexical Analysis

The lexer, implemented with Moo, converts SQL text into structured tokens representing keywords, identifiers, operators, literals, and syntax boundaries.

Grammar Parsing

Nearley was used to define SQL grammar rules and generate parse trees for supported query structures.

This stage introduced some of the most complex engineering challenges in the project due to SQL ambiguity and nested syntax behavior.

AST Generation

Parsed queries are transformed into abstract syntax tree structures representing executable relational operations.

The AST layer acts as a boundary between syntax interpretation and execution behavior.

This made it possible to evolve execution logic independently from grammar handling.

Execution Engine

The executor evaluates AST nodes against relational table structures.

Responsibilities include:

  • filtering
  • ordering
  • row mutation
  • condition evaluation
  • join execution
  • schema inspection
  • relational traversal

The execution layer coordinates query behavior while preserving predictable evaluation flow across operations.

Storage Layer

The storage engine manages relational data structures entirely in memory.

It handles:

  • table definitions
  • schema metadata
  • row storage
  • inserts and updates
  • deletes
  • schema mutations

The project intentionally avoided persistence layers in order to keep the focus on query execution mechanics and relational evaluation behavior.

Using in-memory storage also simplified debugging and made execution flow easier to observe during development.

Query Lifecycle

A SQL query passes through several stages before execution.

1. Query Input

The system receives raw SQL text from either the REPL or browser interface.

2. Tokenization

The lexer scans the query and converts it into structured tokens.

3. Parsing

Grammar rules validate syntax and construct parse structures representing the query.

4. AST Construction

The parser output is transformed into structured AST nodes representing executable operations.

5. Semantic Evaluation

The executor validates table references, conditions, operators, and schema compatibility before execution.

6. Relational Execution

The engine evaluates filters, joins, ordering, limits, and mutations against relational table data.

7. Output Formatting

The final result set is formatted and returned through the active interface.

Supported SQL Features

The engine supports a practical subset of relational operations, including:

  • CREATE TABLE
  • INSERT
  • SELECT
  • WHERE
  • ORDER BY
  • LIMIT
  • UPDATE
  • DELETE
  • INNER JOIN
  • LEFT JOIN
  • ALTER TABLE
  • DROP TABLE
  • DESCRIBE
  • SHOW TABLES

The goal was to support enough relational functionality to exercise realistic query execution behavior without attempting full SQL specification coverage.

Key Decisions

AST-Driven Execution

One of the most important architectural decisions was separating parsing from execution through AST structures.

Rather than coupling runtime logic directly to parser output, queries are first transformed into normalized execution nodes.

This created cleaner execution boundaries and made the system easier to extend as new operations were introduced.

Shared Execution Backend

The REPL and browser playground both rely on the same execution engine.

This avoided duplicated query logic and ensured behavioral consistency across interfaces.

Modular Pipeline Design

Lexing, parsing, execution, and storage were intentionally separated into independent layers.

This reduced coupling and made debugging significantly easier as parser complexity increased.

Limiting Scope to Core Relational Behavior

The project intentionally focused on core relational execution mechanics instead of broad SQL compatibility.

Features such as persistence, indexing, transactions, and optimization were deliberately excluded to keep the architecture manageable within the challenge scope.

Challenges and Tradeoffs

SQL Grammar Ambiguity

Handling SQL grammar proved significantly more difficult than expected.

Challenges included:

  • nested query structures
  • optional clause ordering
  • operator precedence
  • join condition parsing
  • ambiguous grammar paths

Several parser iterations were required before AST generation became stable across supported query types.

Join Execution Complexity

Implementing joins manually introduced additional relational complexity.

The engine needed to:

  • traverse relational row sets
  • evaluate matching conditions
  • preserve unmatched rows for left joins
  • maintain predictable evaluation behavior

Even within a simplified relational engine, join execution quickly became stateful and difficult to reason about without clear execution boundaries.

Maintaining Execution Correctness

Query execution introduced edge cases around:

  • filtering order
  • update targeting
  • mutation safety
  • schema enforcement
  • conditional evaluation

Automated testing became essential as parser and executor behavior evolved together.

Testing and Reliability

The project includes automated tests using Vitest to validate both parsing behavior and relational execution correctness.

Coverage includes:

  • parser validation
  • CRUD operations
  • join execution
  • schema mutations
  • query consistency
  • execution edge cases

Testing became especially important because parser and executor changes frequently affected each other indirectly.

The test suite helped prevent regressions while making architectural refactoring safer.

AI-Assisted Development Workflow

The project combined AI-assisted iteration with manual systems engineering work.

AI accelerated:

  • boilerplate generation
  • early grammar drafting
  • repetitive scaffolding
  • interface setup

Most of the critical engineering work still depended heavily on manual debugging and architectural control, particularly around:

  • parser stability
  • execution correctness
  • relational edge cases
  • join behavior
  • system integration
  • execution flow design

The final system reflects a hybrid workflow where AI improved iteration speed, but correctness depended on deliberate engineering decisions and repeated debugging cycles.

Outcome

The final result was a functioning relational query engine capable of parsing and executing meaningful SQL workflows through both terminal and browser interfaces.

More importantly, the project became a practical exercise in:

  • compiler-style architecture
  • AST-based execution systems
  • relational query evaluation
  • modular systems design
  • stateful execution behavior
  • interface abstraction

It also exposed how quickly seemingly simple SQL operations become complex once parsing, execution, joins, and schema management are implemented directly.

What I Learned

This project made database internals substantially more concrete.

Implementing a relational execution engine from scratch exposed how tightly connected parsing, execution, and state management become once higher-level abstractions disappear.

It also reinforced the importance of architecture boundaries in systems work. Separating lexing, parsing, AST generation, execution, and storage made the project significantly easier to reason about as complexity increased.

Most importantly, the project demonstrated that many systems problems are less about individual algorithms and more about designing clean execution boundaries between interconnected moving parts.