Architecture

CSharpDB is built in clean layers, each with a single responsibility. From client access down to file I/O.

Need the full source guide? The original long-form markdown version is preserved as Architecture Source Reference.

Layered Architecture

Client & Access Layer
ADO.NET, Client SDK (HTTP/gRPC/Direct), REST API, CLI, MCP Server, VS Code Extension
Application Layer
Admin UI (Blazor), Database class, Collection<T> API
Query Execution
SQL Tokenizer, Parser, Query Planner, Operators, Expression Evaluator
Pipeline & Procedures
PipelineOrchestrator, Sources, Transforms, Destinations, Stored Procedures
Catalog & Indexing
SchemaCatalog, BTreeIndexStore, CachingIndexStore, collection path indexes
Storage Engine
BTree, Pager, RecordEncoder, TransactionCoordinator
Durability & Recovery
WriteAheadLog, WalIndex, CheckpointCoordinator, Snapshot Readers
Device / File I/O
FileStream, LRU PageCache, DictionaryPageCache (in-memory mode)

Embedded-to-gRPC Runtime Boundary

CSharpDB has one database engine and one public client contract across local and remote shapes. Embedded mode keeps the engine in the application process. gRPC mode moves file ownership into CSharpDB.Daemon and lets applications call the same ICSharpDbClient contract through Transport = Grpc.

flowchart TB
    App["Application Code"]

    subgraph Embedded["Embedded Mode"]
        DirectClient["CSharpDbClient<br/>Transport = Direct"]
        Engine["CSharpDB.Engine<br/>Database"]
        Storage["Storage Stack<br/>SchemaCatalog, B+Tree, Pager, WAL"]
        File[".db + .wal files"]
    end

    subgraph Remote["gRPC Mode"]
        GrpcClient["CSharpDbClient<br/>Transport = Grpc"]
        Daemon["CSharpDB.Daemon<br/>ASP.NET Core + gRPC"]
        RpcService["CSharpDbRpcService"]
        HostClient["ICSharpDbClient<br/>Direct inside daemon"]
    end

    App --> DirectClient --> Engine --> Storage --> File
    App --> GrpcClient --> Daemon --> RpcService --> HostClient --> Engine
Boundary rule: gRPC is transport and process isolation, not a second database engine. The daemon opens one configured database file through a direct ICSharpDbClient, keeps that runtime warm, and exposes generated RPC methods over the same client-facing operation set.
sequenceDiagram
    participant App
    participant Client as GrpcTransportClient
    participant RPC as CSharpDbRpcService
    participant Host as Daemon ICSharpDbClient
    participant DB as Database Engine
    participant WAL as Pager/WAL/File

    App->>Client: ExecuteSqlAsync(sql)
    Client->>RPC: ExecuteSql(SqlRequest)
    RPC->>Host: ExecuteSqlAsync(sql)
    Host->>DB: Parse/plan/execute
    DB->>WAL: Read/write pages, commit via WAL
    WAL-->>DB: Durable result
    DB-->>Host: SqlExecutionResult
    Host-->>RPC: Model result
    RPC-->>Client: SqlExecutionResultMessage
    Client-->>App: SqlExecutionResult

Scenarios

ScenarioUseRecommended entry point
Single embedded appOne process safely owns the database file.Database.OpenAsync(...) or direct CSharpDbClient
Embedded with client abstractionApp code should stay transport-neutral.CSharpDbClientOptions { DataSource = "app.db" }
Local daemon / sidecarMultiple local processes or tools need one warm owner.Transport = CSharpDbTransport.Grpc, localhost daemon endpoint
Internal service hostTrusted backend services share one database owner.gRPC daemon with API key, TLS termination, and long-lived clients
Admin and tooling accessUI, CLI, VS Code, REST, and gRPC clients share the same runtime.Point tools at the daemon instead of opening the file directly
Not a fitPublic internet, multi-tenant, multi-database, or distributed write coordination.Build an outer service layer before exposing it broadly

Page Format (4096 bytes)

Every page uses a slotted format with a fixed header, cell pointer array growing forward, and cell data growing backward from the end of the page.

Page Header
Type, Cell Count, Free Offset
Cell Pointer Array
Free Space
Cell Data
Header Pointers (grow →) Free space Cells (grow ←)

WAL File Format

The Write-Ahead Log consists of a 32-byte header followed by a sequence of frames. Each frame contains a 24-byte header and a full 4096-byte page image.

WAL Header (32 bytes)
Magic "CWAL", Version, Page Size, Salts, Checksum Seed
Frame Header (24 bytes)
Page Number, DB Page Count, Salt1, Salt2, Checksum
Page Image (4096 bytes)
Frame Header (24 bytes)
Page Image (4096 bytes)
...
...

Transaction Flow

Writer Path

Acquire exclusive lock
Modify pages in memory
Write dirty pages to WAL
Mark commit frame
Update WAL index
Release lock
Checkpoint (if policy triggers)

Reader Path

Capture WAL snapshot
Create read-only pager
Read pages (WAL first, then DB file)
Return consistent results
Release snapshot on dispose

Database Modes

ModeEntry PointCharacteristics
DefaultDatabase.OpenAsync(...)Standard file-backed open with full durability.
In-MemoryDatabase.OpenInMemoryAsync(...)Dictionary-based page cache. No file I/O. Fast but non-persistent.
HybridDatabase.OpenHybridAsync(...)Lazy-resident pages with durable backing file. On-demand loading.

Project Structure

AssemblyPurpose
CSharpDB.PrimitivesShared types: DbValue, DbType, Schema, ErrorCodes
CSharpDB.StorageB+tree, Pager, WAL, page cache, serialization
CSharpDB.Storage.DiagnosticsDatabase/WAL/Index inspection
CSharpDB.SqlSQL tokenizer, parser, AST
CSharpDB.ExecutionQuery planner, operators, expression evaluator
CSharpDB.EngineDatabase class, Collection<T>, top-level API
CSharpDB.PipelinesETL orchestrator, sources, transforms, destinations
CSharpDB.DataADO.NET provider
CSharpDB.ClientUnified client SDK with pluggable transports
CSharpDB.ApiASP.NET Core REST API
CSharpDB.DaemongRPC service host
CSharpDB.CliInteractive REPL shell
CSharpDB.AdminBlazor Server dashboard
CSharpDB.McpModel Context Protocol server for AI
CSharpDB.GeneratorsSource generators for trim-safe typed collections
CSharpDB.NativeNativeAOT C library for FFI