EvaluatedApplications.HoloDb 5.3.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package EvaluatedApplications.HoloDb --version 5.3.0
                    
NuGet\Install-Package EvaluatedApplications.HoloDb -Version 5.3.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="EvaluatedApplications.HoloDb" Version="5.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="EvaluatedApplications.HoloDb" Version="5.3.0" />
                    
Directory.Packages.props
<PackageReference Include="EvaluatedApplications.HoloDb" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add EvaluatedApplications.HoloDb --version 5.3.0
                    
#r "nuget: EvaluatedApplications.HoloDb, 5.3.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package EvaluatedApplications.HoloDb@5.3.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=EvaluatedApplications.HoloDb&version=5.3.0
                    
Install as a Cake Addin
#tool nuget:?package=EvaluatedApplications.HoloDb&version=5.3.0
                    
Install as a Cake Tool

HoloDb

An embeddable holographic SQL database for .NET. It speaks a real SQL dialect, is transactional (ACID with a write-ahead log), and stores data in a columnar engine with compiled predicates, holo-native constant-time aggregates, and hash joins — plus an optional durable larger-than-memory tier (paged buffer-pool + write-ahead log, no memory-mapped files). It runs inside an EvalApp harness, so parallel scans execute as compiled, resource-gated pipelines.

This file is the complete public surface: every public type, method and SQL feature this package ships, each with a one-line purpose and a working example.

Proprietary — this package ships the compiled library only; see LICENSE for the full terms. Installing and using unmodified copies for internal evaluation and non-commercial development or testing is licensed; commercial, production, or revenue-generating use requires a separate commercial licence from the owner. No feature on this page is gated behind a tier or a licence key — every capability described works identically with or without one. Copyright (c) 2026 Dongyang Stephen Chen, trading as Evaluated Applications. All rights reserved.

Table of contents

Quick start

dotnet add package EvaluatedApplications.HoloDb
using HoloDb;

var db = new HoloDbService(new HoloDbOptions());
db.Execute("CREATE TABLE users (id INT PRIMARY KEY, age INT, name TEXT)");
db.Execute("INSERT INTO users VALUES (1, 30, 'Ada')");

var result = await db.ExecuteAsync("SELECT name FROM users WHERE age >= 18");
foreach (var row in result.Rows) Console.WriteLine(row["name"]);
// Ada

Changes in 5.1.2

A statement is refused when any part of it would be ignored, instead of running as something else. LEFT, RIGHT, FULL, CROSS and NATURAL joins, JOIN ... USING, comma joins, OFFSET, UNION, INTERSECT, EXCEPT, a second statement after ;, ROLLBACK TO and trailing text after a statement each throw a HoloException that names what is not supported. A table alias throws and asks for the full table name. NEAREST combined with a join, GROUP BY, HAVING, ORDER BY, DISTINCT or an aggregate, HAVING without GROUP BY, DISTINCT with an aggregate, LIMIT 0 on an aggregate-only query and a non-integer LIMIT are refused the same way. Statements that ran correctly before are unchanged.

Changes in 5.1.1

A table with a Pleroma plane answers an equi-join COUNT or SUM faster. A join whose two tables have not been written since the same join last ran is served from a result memo kept with the plane: the first call, and the first call after any write to either table, costs the same as before, and the memo never changes a result. Dimension GROUP BY after a join on an INT key encodes through the column's zone map.

Changes in 5.1.0

New SQL (see the DDL table): CREATE INDEX [name] ON t (cols) [INCLUDE (cols)] [USING SORTED|SUMMARY] [WITH (...)], DROP INDEX, SHOW INDEXES and EXPLAIN.

New diagnostic hook: Database.RouteTrace is a thread-static List<string>?. Set it to a list and the engine appends the access paths and the route each statement on that thread takes; set it back to null to stop. EXPLAIN reports the same decision without running the query.

using HoloDb;

var db = new HoloDbService(new HoloDbOptions());
db.Execute("CREATE TABLE s (id INT PRIMARY KEY, n INT)");
db.Execute("INSERT INTO s VALUES (1, 1)");
db.Execute("INSERT INTO s VALUES (2, 2)");

Database.RouteTrace = new List<string>();
db.Execute("SELECT count(*) FROM s WHERE n > 1");
foreach (var step in Database.RouteTrace) Console.WriteLine(step);
Database.RouteTrace = null;
// access:block-wise mask scan
// route:predicate count (block-wise byte-mask, no position list)

foreach (var r in db.Execute("EXPLAIN SELECT count(*) FROM s WHERE n > 1").Rows)
    Console.WriteLine($"{r["kind"]}: {r["route"]}");
// access: block-wise mask scan
// route: predicate count (block-wise byte-mask, no position list)

Behaviour that differs from 5.0.0:

  • Older binaries refuse new files. A write-ahead log or snapshot that contains INCLUDE, USING, WITH, DROP INDEX, SHOW INDEXES or EXPLAIN text cannot be opened by an older binary (its parser throws). The snapshot is written in the new v4 form only when a table has an index name or a SQL-created plane; every other database stays readable by a 5.0.0 binary. Run Checkpoint() on the new binary before an older one opens the same file.
  • Rollback. BEGIN no longer copies the database; a rollback replays a journal. After a rolled-back DELETE the table holds the same rows, but their order may differ from before the transaction.
  • Text GROUP BY is ordinal (case-sensitive). 'Ada' and 'ada' are two groups, where 5.0.0 merged them. The coded group-by paths (single-table and after a join) emit groups in ascending code order; add ORDER BY when order matters.
  • Plane routing. In Auto mode a GROUP BY with a WHERE goes to the engine below 140,000 rows (a fixed engine constant, set from an early measurement and not re-derived on the current build); PleromaMode.Always still uses the plane.
  • UPDATE validates every SET value before changing any row, so a bad value leaves the table untouched.
var db = new HoloDbService(new HoloDbOptions());
db.Execute("CREATE TABLE s (id INT PRIMARY KEY, tag TEXT)");
db.Execute("INSERT INTO s VALUES (1, 'Ada')");
db.Execute("INSERT INTO s VALUES (2, 'ada')");
db.Execute("INSERT INTO s VALUES (3, 'Ada')");
foreach (var r in db.Execute("SELECT tag, count(*) FROM s GROUP BY tag ORDER BY tag").Rows)
    Console.WriteLine($"{r["tag"]} {r["count(*)"]}");
// Ada 2
// ada 1

Measured against 5.0.0 on an idle 16-core machine (release gate EXP-PB13, two runs of each build, rows identical to SQLite): no benchmark cell is slower; single-row autocommit UPDATE/DELETE at 1M rows went from about 69 ms/67 ms to about 12 us/9 us, BEGIN from 66 ms to 200 ns, and the 10M-row invoicing queries are faster on every join and group-by shape.

Core concepts

  • Columns are authoritative; holograms are derived. Every row lives in plain typed columnar storage (long[]/double[]/string[]), scanned via compiled predicates. A holographic vector (VSA/HRR bind+bundle, used for NEAREST similarity search) is computed FROM the columns, never the other way round — losing a hologram loses nothing durable. Exact answers (sums, counts) come from exact integer accumulators, not from holograms.
  • ACID. Atomicity + Isolation: one global lock serialises mutations, and a pre-statement undo journal (each changed row is journaled as it changes, BEGIN copies nothing) is replayed on error or ROLLBACK; DDL inside a transaction uses a snapshot. Durability: committed statements are write-ahead-logged and fsync'd. Consistency: schema/type, NOT NULL (the key, and every column not declared NULL), and PRIMARY KEY uniqueness are enforced on write.
  • Two storage shapes, one SQL surface. In-RAM tables (CREATE TABLE by default) and paged, durable, larger-than-memory tables (CreatePaged/OpenPaged, or whole-database HoloDbOptions.Paged) are driven through the same Execute(sql)/BulkLoad calls — see Durable tables for where their capabilities differ.
  • Constant-time exact aggregates. Whole-table and single-column-grouped COUNT/SUM/MIN/MAX on INT columns read a maintained accumulator instead of scanning — flat cost as the table grows. REAL aggregates and grouped REAL SUM are a fast SIMD scan, not maintained (see gotchas).
  • Not a toy dialect, but not all of SQL either. Real WHERE/GROUP BY/HAVING/ORDER BY/LIMIT/DISTINCT/a single INNER JOIN/subquery IN/transactions, but no multi-way joins, no UPSERT keyword (the ON CONFLICT form is there), one PRIMARY KEY per table. A column holds SQL NULL when it is declared NULL (NULL); every other column is NOT NULL. See the SQL reference for the exact grammar.

Opening a database

public sealed class HoloDbOptions
{
    public string? LicenseKey { get; set; }              // accepted, currently informational — nothing is gated by tier
    public string? WalPath { get; set; }                  // null = pure in-memory, no durability
    public long CheckpointThresholdBytes { get; set; }    // default 8 MB; 0 disables automatic checkpoints
    public bool Paged { get; set; }                       // opt in to whole-database paged-default mode (below)
}
var db = new HoloDbService(new HoloDbOptions { WalPath = "app.wal" });
  • HoloDbService is the recommended entry point: async-friendly, licensed (informational only), wraps the engine below.
    • Execute(sql) — run a statement synchronously, return a QueryResult.
    • ExecuteAsync(sql, ct = default) — the same, offloaded to the thread pool.
    • BulkLoad(table, cols, data, count) — see BulkLoad.
    • Checkpoint() — fold the write-ahead log into a fresh snapshot now (no-op in-memory).
    • CreatePaged/OpenPaged/PagedBulkLoad/ClosePaged — see Durable tables.
    • Close() — release every open paged table's file handles; call before re-opening the same WalPath in-process, or on shutdown.
    • .Database — the underlying Database for direct synchronous access (below).
    • .LicenseMode — Unlicensed/Licensed; informational, gates nothing today.
  • Database (HoloDb namespace) is the engine HoloDbService wraps; use it directly if you don't need the async wrapper or licensing shim.
    • Database.Open(walPath = null, checkpointThresholdBytes = 8 MB, paged = false) — static factory. walPath = null is pure in-memory. Recovery replays the write-ahead log over the last snapshot, so recovery time is bounded by live data, not by write history.
    • Execute(sql), BulkLoad(...), Checkpoint(), Close() — same contracts as HoloDbService.
    • WalPath — the configured log path, or null for in-memory (diagnostics).
    • IsPagedDefault — true iff this instance actually ended up in whole-database paged mode.
    • TableColumns(table) — the table's Column list in schema order, e.g. for building typed BulkLoad arrays without hardcoding types.
    • LastRowsScanned, LastTableRows, LastRowBytes, LastLogicalBytes — static, per-thread usage counters for the most recently executed query on the calling thread; see metering.
var path = "app.wal";
var db = new HoloDbService(new HoloDbOptions { WalPath = path });
db.Execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)");
db.Execute("INSERT INTO t VALUES (1, 100)");
db.Checkpoint();
db.Close();

var reopened = new HoloDbService(new HoloDbOptions { WalPath = path });
var r = reopened.Execute("SELECT v FROM t WHERE id = 1");
Console.WriteLine(r.GetLong(0, 0));
// 100
reopened.Close();

SQL reference

DDL: CREATE TABLE, ALTER TABLE, CREATE INDEX

Statement Purpose
CREATE TABLE [IF NOT EXISTS] t (col TYPE [NULL \| NOT NULL] [PRIMARY KEY], ...) Define a table. Exactly one PRIMARY KEY, type INT or TEXT (a GUID is just a TEXT value — there is no dedicated GUID type), never NULL. A column is NOT NULL unless it says NULL: see NULL. IF NOT EXISTS makes a redundant create a no-op instead of throwing.
ALTER TABLE t ADD [COLUMN] col TYPE [NULL] [DEFAULT <literal> \| NULL] Add one column to an existing table. ADD-only — no DROP, rename, or type change. A NOT NULL column needs a literal DEFAULT; a column declared NULL may leave DEFAULT out (existing rows are NULL). The default backfills rows that existed at ALTER time only.
CREATE INDEX [name] ON t(col) A single-column index, INT or TEXT only (never REAL) and NOT NULL columns only (a column declared NULL is refused; the summary index below accepts one). The optional name is kept (unique per table); an unnamed index is called ix_<table>_<col>. USING SORTED is the same thing spelled out.
CREATE INDEX [name] ON t(catCol, orderCol) A composite index: per-catCol-value buckets of primary keys pre-sorted by orderCol (orderCol must be INT). Speeds up WHERE catCol = 'v' ORDER BY orderCol LIMIT k.
CREATE INDEX [name] ON t(dims...) [INCLUDE (measures...)] USING SUMMARY [WITH (max_dim_bits = n, max_joint_bits = n)] Build the Pleroma aggregate plane with SQL: key columns are its dimensions (INT/TEXT), INCLUDE columns its measures (INT). Caps default to 10 and 12 bits. One summary index per table; in-RAM tables only (a paged table refuses). A dimension past its cap does not fail the statement: the result row lists it and queries that need it go to the engine. USING NEAREST is parsed and refused (the similarity index is built automatically).
DROP INDEX name [ON t] Remove an index by its given or generated name (ON t is only needed if two tables share the name).
SHOW INDEXES [ON t] One row per index: table, name, kind (sorted/text/composite/summary), columns, include, rows, bytes, served, declined, over_cap.
EXPLAIN [PLAN [FOR]] SELECT ... Report how a SELECT would be answered, without running it or changing anything. One row per step: step, kind, route, detail, est_rows. access rows name the row-access path (primary-key lookup, index seek, zone-map block skip with the column, block-wise mask scan), decline rows give the reason a faster route was not taken (for example a plane that does not hold the column, a dimension over its cap, a join with more than 4,095 keys), and the last route row is the one that serves the query (maintained accumulator, fused filter and aggregate, coded GROUP BY, columnar join, Pleroma plane, row-decode fallback). A subquery in IN (SELECT ...) is shown unresolved.

Column types accept SQL-ish synonyms: INT/INTEGER, TEXT/VARCHAR/STRING, REAL/FLOAT/DOUBLE/DECIMAL/NUMERIC — all three map onto exactly three physical storage kinds (long, string, double).

var db = new HoloDbService(new HoloDbOptions());
db.Execute("CREATE TABLE users (id INT PRIMARY KEY, age INT, name TEXT)");
db.Execute("CREATE TABLE IF NOT EXISTS users (id INT PRIMARY KEY, age INT, name TEXT)"); // no-op
db.Execute("INSERT INTO users VALUES (1, 30, 'Ada')");
db.Execute("ALTER TABLE users ADD COLUMN vip INT DEFAULT 0");
db.Execute("UPDATE users SET vip = 1 WHERE id = 1");
Console.WriteLine(db.Execute("SELECT vip FROM users WHERE id = 1").GetLong(0, 0));
// 1
var db = new HoloDbService(new HoloDbOptions());
db.Execute("CREATE TABLE events (id INT PRIMARY KEY, category TEXT, ts INT)");
db.Execute("CREATE INDEX ON events(category)");
db.Execute("CREATE INDEX ON events(category, ts)");   // composite
db.Execute("INSERT INTO events VALUES (1, 'login', 100), (2, 'login', 200), (3, 'logout', 50)");
Console.WriteLine(db.Execute("SELECT count(*) FROM events WHERE category = 'login'").Rows[0]["count(*)"]);
// 2

DML: INSERT, UPDATE, DELETE

Statement Purpose
INSERT INTO t [(col, ...)] VALUES (v, ...)[, (v, ...)]... Insert one or more rows (batch form supported). An omitted column (a partial column list) is NULL when it is declared NULL, else its type's zero value (0/0.0/""). NULL is a value here (VALUES (1, NULL)); into a NOT NULL column it throws. Throws on a duplicate primary key.
INSERT INTO t [(col, ...)] VALUES (...)[, ...] ON CONFLICT (pk) DO UPDATE SET col = EXCLUDED.col[, ...] Upsert. A row whose primary key exists changes only the SET columns (every other column keeps its value); a new key is inserted. EXCLUDED.col must name the same column, and every SET column must be in the INSERT column list. DO NOTHING, expressions, WHERE, INSERT OR REPLACE and REPLACE INTO throw. In-RAM tables only. See Atomic change batches.
UPDATE t SET col = <literal>[, ...] [WHERE ...] Update matching rows. The right-hand side of SET must be a literal (NULL for a column declared NULL) — SET v = v + 1 is not supported (throws). Updating the primary key column itself throws.
DELETE FROM t [WHERE ...] Delete matching rows.
var db = new HoloDbService(new HoloDbOptions());
db.Execute("CREATE TABLE p (id INT PRIMARY KEY, name TEXT)");
db.Execute("INSERT INTO p VALUES (1, 'Ada')");
try { db.Execute("INSERT INTO p VALUES (1, 'Dup')"); }
catch (HoloException ex) { Console.WriteLine(ex.Message); }
// duplicate primary key 1
try { db.Execute("UPDATE p SET id = 2 WHERE id = 1"); }
catch (HoloException ex) { Console.WriteLine(ex.Message); }
// cannot UPDATE the primary key

Parameterized queries

Every SQL method (Database.Execute, HoloDbService.Execute/ExecuteAsync) has a parameterized overload: ? placeholders, substituted in order by the trailing arguments as correctly quoted/ escaped SQL literals (ADO.NET-style positional binding) — added 2026-10, customer finding F-05.

var db = new HoloDbService(new HoloDbOptions());
db.Execute("CREATE TABLE p (id INT PRIMARY KEY, name TEXT)");
db.Execute("INSERT INTO p VALUES (?, ?)", 1L, "O'Brien");
var r = db.Execute("SELECT name FROM p WHERE id = ?", 1L);
Console.WriteLine(r.Rows[0]["name"]);
// O'Brien

A string argument has its embedded ' doubled automatically — the same rule raw SQL text already requires ('O''Brien' when you write the literal yourself: a single quote inside a string literal is escaped by doubling it, per Sql.cs's lexer). Parameters don't replace that rule, they apply it for you. ? only has placeholder meaning OUTSIDE a string literal — WHERE name = 'what?' is untouched. Supported argument types: string, bool, any integer type (long/int/ short/.../ulong), any floating-point type (double/float/decimal). A null (or DBNull) argument is the SQL literal NULL (a value in INSERT/UPDATE SET; in a comparison it throws, write IS NULL). An argument-count mismatch or an unsupported CLR type throws HoloException (e.g. DateTime — there is no date column type; encode a date as an INT day/epoch number instead). Database.Bind(sql, args) is also exposed directly if you want the literal SQL text without executing it (e.g. to log it). A bare Bind(sql, null) is one NULL argument. A name in the SELECT list that is not a column of the table throws HoloException: no such column 'name'.

var db2 = new HoloDbService(new HoloDbOptions());
string text = Database.Bind("SELECT * FROM t WHERE id = ? AND name = ?", 1L, "Widget");
Console.WriteLine(text);
// SELECT * FROM t WHERE id = 1 AND name = 'Widget'

NULL

A column holds SQL NULL when it is declared NULL: CREATE TABLE orders (id INT PRIMARY KEY, region TEXT NULL, amount INT NULL, qty INT NOT NULL). Every other column is NOT NULL (NOT NULL is accepted and means the default), and a primary key is never NULL. A NOT NULL column that is left out of an INSERT still gets its type zero (0/0.0/""); a NULL column that is left out is NULL. NULL is a value in INSERT, UPDATE .. SET, ON CONFLICT .. SET, ALTER .. DEFAULT, Database.Bind(.., null), RowChange.Upsert (a C# null) and Database.BulkLoad(.., nulls); it is refused (with a message that says to write IS NULL) in = != < > <= >=, IN, BETWEEN, LIKE, NEAREST (..) and a join ON, because a comparison with NULL is never true.

  • Three-valued logic. WHERE and HAVING keep a row only when the predicate is true. A comparison, LIKE, IN or BETWEEN on a NULL is unknown; false AND unknown is false, true OR unknown is true, NOT unknown is unknown. x NOT IN (SELECT ..) is never true when the subquery returns a NULL. IS NULL and IS NOT NULL are always true or false.
  • Aggregates skip NULL. COUNT(*) counts rows; COUNT(col), COUNT(DISTINCT col), SUM, AVG, MIN and MAX count and combine the non-NULL values. SUM, AVG, MIN and MAX of a group whose values are all NULL are NULL. Whole-table COUNT, SUM/AVG/MIN/MAX of an INT column and a one-column GROUP BY with COUNT, SUM and AVG stay maintained answers with NULLs present (no row is read). On an empty selection SUM is 0 and AVG is 0.0, as for a NOT NULL column.
  • GROUP BY and DISTINCT make one group (one value) of all the NULLs, apart from 0 and ''. ORDER BY puts NULL first ascending and last descending. An INNER JOIN never matches a NULL key, not even another NULL. NEAREST: a NULL cell adds nothing to the row's similarity.
  • Results. A result that holds a NULL is row-form: read it with Rows or GetValue, where the cell is DBNull.Value; QueryResult.IsNull(row, col) says so. GetLong, GetDouble and GetText serve a non-NULL cell of the matching type and throw a HoloException on a NULL.
  • Where it stops. CREATE INDEX (sorted, text, composite) takes NOT NULL columns only. A Pleroma plane stores the type zero for a NULL and declines (the engine answers) any statement that touches a plane column holding one, so it never answers wrongly. A plane-only (summary) table has no rows to fall back on and refuses a NULL (map it to a label, or add an indicator measure). Paged tables do not take NULL columns.
  • Speed. A column that has never received a NULL has no null bitmap, and a statement that touches no column holding a NULL and has no IS [NOT] NULL takes the routes it takes for a NOT NULL column. The maintained answers listed above stay flat with NULLs present. Any other statement on a column that holds NULLs filters with a null-aware compiled predicate and then builds boxed rows (a SELECT * or a filtered aggregate, for example), slower than the typed columnar result a NOT NULL column returns. A table that declares a NULL column is not read lock-free, and the live snapshot does not serve a statement that references a declared NULL column, even before it holds a NULL: declare a column NULL only where you need it.
  • Older binaries. A log with NULL in it, or a snapshot of a table with a NULL column (format 6), is refused by an older HoloDb binary with a clear error until a newer one has opened it and called Checkpoint(); a database with no NULL column writes the same files as before.
using HoloDb;

var db = new HoloDbService(new HoloDbOptions());
db.Execute("CREATE TABLE orders (id INT PRIMARY KEY, region TEXT NULL, amount INT NULL, qty INT NOT NULL)");
db.Execute("INSERT INTO orders VALUES (1, 'north', 10, 1), (2, NULL, 20, 1), (3, 'north', NULL, 2)");
var r = db.Execute("SELECT COUNT(*), COUNT(amount), SUM(amount), AVG(amount) FROM orders").Rows[0];
Console.WriteLine($"{r["count(*)"]} rows, {r["count(amount)"]} amounts, sum {r["sum(amount)"]}, avg {r["avg(amount)"]}");
// 3 rows, 2 amounts, sum 30, avg 15
Console.WriteLine(db.Execute("SELECT COUNT(*) FROM orders WHERE region IS NULL").Rows[0]["count(*)"]);
// 1
Console.WriteLine(db.Execute("SELECT COUNT(*) FROM orders WHERE region != 'north'").Rows[0]["count(*)"]);
// 0   (the NULL region is neither equal nor different)
foreach (var g in db.Execute("SELECT region, COUNT(*) FROM orders GROUP BY region ORDER BY region").Rows)
    Console.WriteLine($"{(g["region"] is DBNull ? "(null)" : g["region"])}: {g["count(*)"]}");
// (null): 1
// north: 2

// A change feed carries NULLs as C# nulls; BulkLoad takes a mask per column (true = NULL) and a left-out nullable column is NULL.
db.ApplyChanges("orders", new[] { RowChange.Upsert(new Dictionary<string, object?> { ["id"] = 4L, ["region"] = null, ["amount"] = 5L, ["qty"] = 1L }) });
db.BulkLoad("orders", new[] { "id", "amount", "qty" }, new Array[] { new long[] { 5, 6 }, new long[] { 0, 7 }, new long[] { 1, 1 } }, 2,
    new bool[]?[] { null, new[] { true, false }, null });
Console.WriteLine(db.Execute("SELECT COUNT(*) FROM orders WHERE amount IS NULL").Rows[0]["count(*)"]);
// 2   (id 3 and id 5)

SELECT: projection, DISTINCT, aggregates

SELECT [DISTINCT] col [, col ...] | agg(col) | *
FROM t
[INNER JOIN t2 ON ...]
[NEAREST (col = val, ...)]
[WHERE ...]
[GROUP BY col [, col ...]]
[HAVING ...]
[ORDER BY col [ASC|DESC] [, ...]]
[LIMIT n]

Aggregate functions: COUNT, SUM, MIN, MAX, AVG (COUNT(*) and COUNT(col) both supported). Result columns for aggregates are named in lower-case agg(col) form, e.g. count(*), sum(amount) — not the SQL text as typed, never aliased. AVG over an INT column returns a double, never integer-truncated.

var db = new HoloDbService(new HoloDbOptions());
db.Execute("CREATE TABLE sales (id INT PRIMARY KEY, region TEXT, amount INT)");
db.Execute("INSERT INTO sales VALUES (1,'EU',100),(2,'EU',50),(3,'US',10)");

var agg = db.Execute("SELECT region, sum(amount) FROM sales GROUP BY region HAVING sum(amount) > 20 ORDER BY region LIMIT 10");
foreach (var row in agg.Rows) Console.WriteLine($"{row["region"]}={row["sum(amount)"]}");
// EU=150

var distinctRegions = db.Execute("SELECT DISTINCT region FROM sales ORDER BY region");
foreach (var row in distinctRegions.Rows) Console.WriteLine(row["region"]);
// EU
// US

JOIN

One INNER JOIN ... ON a = b per query (JOIN/INNER JOIN are equivalent). Qualify a projected or join-condition column with table.column when both sides share a name.

Anything else is refused with a HoloException naming it, never answered as if it were an inner join: LEFT/RIGHT/FULL [OUTER] JOIN, CROSS JOIN, NATURAL JOIN, JOIN ... USING (...), and comma joins (FROM a, b). Table aliases (FROM orders o, FROM orders AS o) are refused too: write the table name (orders.id). The same refusal covers any text left over after a complete statement (OFFSET, UNION, ROLLBACK TO ..., a second statement after ;), so a clause the dialect does not have can never be silently dropped.

var db = new HoloDbService(new HoloDbOptions());
db.Execute("CREATE TABLE users (id INT PRIMARY KEY, name TEXT)");
db.Execute("INSERT INTO users VALUES (1, 'Ada')");
db.Execute("CREATE TABLE orders (id INT PRIMARY KEY, userId INT)");
db.Execute("INSERT INTO orders VALUES (1, 1)");
var j = db.Execute("SELECT users.name FROM orders INNER JOIN users ON orders.userId = users.id");
foreach (var row in j.Rows) Console.WriteLine(row["users.name"]);
// Ada

WHERE operators

AND / OR / NOT / parentheses, comparisons (= != < > <= >=, <> is accepted as a synonym for !=), LIKE (% = any run of characters, _ = any one character, case-insensitive), IN (v, ...) / IN (SELECT ...) (a scalar subquery), BETWEEN lo AND hi, each with an optional leading NOT.

var db = new HoloDbService(new HoloDbOptions());
db.Execute("CREATE TABLE p (id INT PRIMARY KEY, name TEXT, price INT)");
db.Execute("INSERT INTO p VALUES (1,'Widget',25),(2,'Gadget',75),(3,'Widgetpro',150)");

db.Execute("SELECT name FROM p WHERE name LIKE 'Widget%'");       // Widget, Widgetpro
db.Execute("SELECT name FROM p WHERE id IN (1,2)");                // Widget, Gadget
db.Execute("SELECT name FROM p WHERE price BETWEEN 50 AND 100");   // Gadget
db.Execute("SELECT name FROM p WHERE id IN (SELECT id FROM p WHERE price > 50)");   // Gadget, Widgetpro

GROUP BY / HAVING, ORDER BY, LIMIT

GROUP BY accepts one or more columns; HAVING may reference an aggregate (count(*), sum(col), …) or a grouped column. ORDER BY accepts multiple keys with independent ASC/DESC. LIMIT n caps the row count (used alone or with NEAREST's top-k). See the aggregates example above — the same clauses apply there.

These combinations are refused with a HoloException because the engine would otherwise ignore one of the clauses: HAVING without GROUP BY (filter rows with WHERE), DISTINCT together with an aggregate (use GROUP BY, or COUNT(DISTINCT col)), and LIMIT 0 on an aggregate-only query. NEAREST accepts only a projection, WHERE and LIMIT: with JOIN, GROUP BY, HAVING, ORDER BY (the rows already come back ranked; select score to read the ranking), DISTINCT or an aggregate it is refused.

SELECT ... FROM t NEAREST (col = val [, col = val ...]) [WHERE ...] [LIMIT k]

Ranks rows by cosine similarity of their holographic encoding to a partial key (any subset of columns, any mix of INT/TEXT/REAL) and returns the top k (LIMIT; omitted = all rows, ranked). No separate vector index to build or keep in sync — it is derived from the same columns the table already holds, incrementally updated on INSERT (O(1) per new row). Below ~8,000 rows ranking is exact (brute-force cosine over every row); at/above that it switches to an approximate, sub-linear LSH candidate search — both return the same shape of result, just traded for speed at scale. An UPDATE/DELETE/ALTER TABLE on the table forces the next NEAREST to rebuild its index (bucket removal isn't tracked incrementally).

Similarity is graded, not exact-match-only (fixed 2026-10, customer finding F-04): an INT/ REAL column scores a close value higher than a far one (e.g. querying price = 1000, 900 and 1100 rank right behind the exact match, well ahead of 1800), and a TEXT column scores a near-identical string higher than an unrelated one via character-trigram overlap (e.g. querying name = 'ACME LTD', 'ACME LTD.' ranks well ahead of 'BOLT SUPPLIES'). This grading is a well-behaved LOCAL/mid-range decay, not a global guarantee that EVERY pair orders strictly by distance — two values several orders of magnitude apart can occasionally tie or swap rank with each other (both score low either way); it reliably separates "close" from "far", not every far value from every farther one. Add score to the projection to see the ranking metric itself (cosine similarity, descending, 1.0 for an exact match):

var db = new HoloDbService(new HoloDbOptions());
db.Execute("CREATE TABLE products (id INT PRIMARY KEY, category TEXT, price INT)");
db.Execute("INSERT INTO products VALUES (1,'electronics',100),(2,'electronics',120),(3,'furniture',300)");

var near = db.Execute("SELECT id, category, price, score FROM products NEAREST (category = 'electronics', price = 110) LIMIT 2");
foreach (var row in near.Rows) Console.WriteLine($"id={row["id"]} category={row["category"]} price={row["price"]} score={row["score"]:F3}");
// id=2 category=electronics price=120 score=0.989
// id=1 category=electronics price=100 score=0.989

score is a pseudo-column, like an aggregate's agg(col) name — it only appears when you ask for it by name; SELECT * keeps returning just the table's own columns (no shape change for existing callers). It is not a real schema column, so it can't appear in WHERE/GROUP BY/etc.

Transactions: BEGIN / COMMIT / ROLLBACK

var db = new HoloDbService(new HoloDbOptions());
db.Execute("CREATE TABLE p (id INT PRIMARY KEY, name TEXT)");

db.Execute("BEGIN");
db.Execute("INSERT INTO p VALUES (1, 'Temp')");
db.Execute("ROLLBACK");
Console.WriteLine(db.Execute("SELECT count(*) FROM p").Rows[0]["count(*)"]);
// 0

db.Execute("BEGIN");
db.Execute("INSERT INTO p VALUES (1, 'Kept')");
db.Execute("COMMIT");
Console.WriteLine(db.Execute("SELECT count(*) FROM p").Rows[0]["count(*)"]);
// 1

A multi-statement transaction is a series of separate Execute calls on the same Database instance — the engine's internal lock releases between them, so if multiple callers share one Database concurrently, holding a transaction open across statements from different callers is the caller's own responsibility (serialise/gate it yourself). BulkLoad cannot run inside an explicit transaction (throws).

BulkLoad — fast typed-array ingest

int BulkLoad(string table, IReadOnlyList<string> cols, IReadOnlyList<Array> data, int count)

Appends count rows from typed column arrays (long[] for INT, double[] for REAL, string[] for TEXT) — one array per name in cols — bypassing the SQL parser entirely. Atomic (validates every key up front); throws on a duplicate primary key, same as INSERT. A column omitted from cols is filled with its type's zero value, exactly like a partial INSERT.

var db = new HoloDbService(new HoloDbOptions());
db.Execute("CREATE TABLE users (id INT PRIMARY KEY, age INT, name TEXT)");
var cols = new[] { "id", "age", "name" };
var data = new Array[] { new long[] { 10, 11 }, new long[] { 40, 22 }, new[] { "Bo", "Cy" } };
int n = db.BulkLoad("users", cols, data, 2);
Console.WriteLine(n);
// 2

Atomic change batches and upsert

A live replica turns an ordered change feed from another system into one call per batch. ApplyChanges(table, changes) on Database (and ApplyChanges / ApplyChangesAsync on HoloDbService) applies upserts and deletes atomically: one write lock, so a reader sees the table before the batch or after it, never part of it, on every read path including the lock-free live metrics. The batch is one WAL commit group (one fsync, all or nothing on replay). A table with a Pleroma plane takes the whole batch in one pass.

  • RowChange.Upsert(row) carries a full row (every column, case-insensitive names; INT takes any integral type, REAL a float or integral, TEXT a string; no NaN; a C# null is SQL NULL and is accepted only for a column declared NULL). It inserts a new key or replaces the whole row of an existing one. A missing column throws: there is no default-on-omit, so an omitted column would silently become 0, "" or NULL. For a partial update use the SQL form, where the SET list says which columns change.
  • RowChange.Delete(key) deletes by primary key; a key that is not there is a counted no-op.
  • Changes count as if applied in order (the last change of a key wins); only the net effect runs. ChangeResult has Inserted, Updated, Deleted, MissingDeletes (and Applied).
  • Everything is validated before anything changes. A failure leaves the table, its accumulators and its plane as they were (up to physical row order after a rolled-back delete, as for ROLLBACK).
  • Allowed inside BEGIN .. COMMIT (the batch then commits or rolls back with it). A paged table throws (HoloException naming the table).
  • The log holds the net effect as plain DELETE, UPDATE and INSERT lines, so an older binary can replay it: the upsert keywords never reach the log. Text keeps every character it was given, line breaks and backslashes included: a statement with a line break is written to the log escaped (S \e ...) and read back exactly. An older HoloDb binary refuses a log that holds such a line, with a clear error, until a newer binary has opened it and called Checkpoint(); a log with no line break in any text is unchanged.
using HoloDb;

var db = new HoloDbService(new HoloDbOptions());
db.Execute("CREATE TABLE orders (id INT PRIMARY KEY, region TEXT, total INT)");

var r = db.ApplyChanges("orders", new[]
{
    RowChange.Upsert(new Dictionary<string, object?> { ["id"] = 1L, ["region"] = "north", ["total"] = 40L }),
    RowChange.Upsert(new Dictionary<string, object?> { ["id"] = 2L, ["region"] = "south", ["total"] = 15L }),
    RowChange.Upsert(new Dictionary<string, object?> { ["id"] = 1L, ["region"] = "north", ["total"] = 45L }),
    RowChange.Delete(2L),
    RowChange.Delete(99L),
});
Console.WriteLine($"{r.Inserted} inserted, {r.Updated} updated, {r.Deleted} deleted, {r.MissingDeletes} missing");
// 2 inserted, 1 updated, 1 deleted, 1 missing

db.Execute("INSERT INTO orders VALUES (1, 'west', 50), (3, 'east', 7) ON CONFLICT (id) DO UPDATE SET total = EXCLUDED.total");
foreach (var row in db.Execute("SELECT id, region, total FROM orders ORDER BY id").Rows)
    Console.WriteLine($"{row["id"]} {row["region"]} {row["total"]}");
// 1 north 50
// 3 east 7

The SQL form logs the same net-effect lines. It reports Affected = rows inserted plus rows updated. It is the only way to change some columns of a row and keep the rest.

Several tables in one step

A source transaction that touches a fact row and its dimension row, or a batch plus the position it came from, must not be seen half applied. ApplyChanges(IReadOnlyList<TableChanges>) (on Database and HoloDbService, with ApplyChangesAsync) applies the changes of several tables as one atomic step: one write lock, one undo journal, one WAL commit group, and each table's plane and live snapshot maintained once. A reader (a single statement such as a JOIN, or any later statement) sees every table before the batch or every table after it.

  • Every table, key and value is validated before the first change; a failure leaves all tables and planes exactly as they were. Unknown table, a table named twice (put its changes in one entry) and a paged table are refused by name.
  • Tables apply and log in list order; each table's changes behave as in the single-table call. You get one ChangeResult per entry, in list order. Allowed inside BEGIN .. COMMIT (the whole batch commits or rolls back with it).
  • The lock is released between statements, so a cross-table invariant can only be read in ONE statement (a join); two separate Execute calls can still straddle a batch.
using HoloDb;

var db = new HoloDbService(new HoloDbOptions());
db.Execute("CREATE TABLE accounts (id INT PRIMARY KEY, balance INT)");
db.Execute("CREATE TABLE ledger (id INT PRIMARY KEY, account INT, amount INT)");
db.Execute("INSERT INTO accounts VALUES (1, 100), (2, 100)");

var results = db.ApplyChanges(new[]
{
    new TableChanges("accounts", new[]
    {
        RowChange.Upsert(new Dictionary<string, object?> { ["id"] = 1L, ["balance"] = 70L }),
        RowChange.Upsert(new Dictionary<string, object?> { ["id"] = 2L, ["balance"] = 130L }),
    }),
    new TableChanges("ledger", new[]
    {
        RowChange.Upsert(new Dictionary<string, object?> { ["id"] = 1L, ["account"] = 1L, ["amount"] = -30L }),
        RowChange.Upsert(new Dictionary<string, object?> { ["id"] = 2L, ["account"] = 2L, ["amount"] = 30L }),
    }),
});
Console.WriteLine($"{results[0].Updated} updated, {results[1].Inserted} inserted");
// 2 updated, 2 inserted

var both = db.Execute("SELECT SUM(accounts.balance), SUM(ledger.amount) FROM accounts JOIN ledger ON accounts.id = ledger.account");
Console.WriteLine($"{both.GetValue(0, 0)} {both.GetValue(0, 1)}");
// 200 0

Comparing key sets: KeyDigest

KeyDigest(table, modulus) returns, for an INT primary key, one KeyBucket(Count, Sum) per bucket (Sum is an exact Int128; bucket b holds the keys with floor(key mod modulus) = b, so negative keys land in [0, modulus) too) in one pass over the key column: a fingerprint of modulus numbers however many rows the table has. A source computes the same numbers with a GROUP BY on the same bucket (for a power-of-two modulus, pk & (m - 1)), so two systems exchange modulus small rows instead of every key and list keys only for the buckets that differ. Equal buckets do not prove equal keys (two keys that left and two that arrived with the same total cancel), so keep an occasional full comparison as the backstop. A TEXT key or a paged table is refused.

using HoloDb;

var db = new HoloDbService(new HoloDbOptions());
db.Execute("CREATE TABLE accounts (id INT PRIMARY KEY, balance INT)");
db.Execute("INSERT INTO accounts VALUES (1, 100), (2, 100), (6, 5)");

KeyBucket[] d = db.KeyDigest("accounts", 4);
Console.WriteLine($"bucket 1: {d[1].Count} key, sum {d[1].Sum}; bucket 2: {d[2].Count} keys, sum {d[2].Sum}");
// bucket 1: 1 key, sum 1; bucket 2: 2 keys, sum 8

Summary tables: the plane without the rows

A summary table keeps no rows. It stores only the Pleroma plane (the exact summaries behind COUNT, SUM, GROUP BY and WHERE), the value dictionaries and the position of each feed, so its memory does not depend on how many rows went through it. The plane-servable reads still answer; everything else is refused by name, because there is no row to scan.

CREATE SUMMARY TABLE sales (region TEXT DIMENSION, channel TEXT DIMENSION, units INT MEASURE, revenue INT MEASURE)
    [WITH (max_dim_bits = 10, max_joint_bits = 12)]

or Database.CreatePlaneOnly(table, dimensions, measures, maxDimBits, maxJointBits). A dimension is an INT or TEXT column you filter, group or count distinct on; a measure is an INT column you SUM or AVG (exact, Int128 inside). The columns are fixed when the table is created: a new dimension means a new table filled again from the source.

  • Writing. ApplyPlaneChanges(table, source, position, changes) (also on HoloDbService, with an async form) takes PlaneChange.Add(row), PlaneChange.Remove(row) and PlaneChange.Replace(oldRow, newRow). A row is an array in the table's declared column order or a name-to-value dictionary with every column present. A Remove carries the row's values: the table cannot look them up. The whole batch is one atomic plane pass and one log commit group.
  • Exactly once. There is no key to deduplicate on, so a replayed batch would count twice. Every batch carries the position its source has reached; a batch at or below the position already applied for that source is a no-op and comes back with Skipped = true. The position is a number (an offset, a sequence number, a version) or opaque bytes compared lexicographically as unsigned numbers (a SQL Server LSN is 10 big-endian bytes; use one fixed width per source). It is saved in the same log statement and snapshot as the counts. PlanePosition(table, source) (numbers) and PlanePositionBytes(table, source) read it back (null before the first batch).
  • No negative counts. The changes are walked in order before anything is applied. If a Remove would take the count of a dimension-value combination below zero (the row was never added, or was already removed) the whole batch is refused: no count, dictionary entry or position changes. A Remove with the right dimensions and a wrong measure value cannot be detected here; compare the totals with the source (PlaneTotals(table) returns the row count and the exact sum of every measure).
  • Caps. A dimension holds at most 2^max_dim_bits distinct values (at most 4,096) for the life of the table (a value never leaves its dictionary), and all dimensions together at most max_joint_bits flag bits (at most 16). A batch that would pass a cap is refused whole, with the cap named; the HoloException carries Code (HoloErrorCodes.PlaneDimensionCap or PlaneJointCap), Dimension, Cap and Needed, so a caller can tell a cap from any other failure without reading the message. Each distinct value costs about 250 bytes (the dictionary entry plus the extra plane cells its bit width forces).
  • Export and import. ExportPlane(table) returns the whole state of a plane-only table as one blob (columns and caps, every source's position, the value dictionaries and the exact cells), behind a magic, a format version and a SHA-256 checksum. Its size follows the plane's bit widths, not the number of rows counted. ImportPlane(table, blob, replace: false) makes a plane-only table from a blob (under any name; replace: true overwrites a table of the same columns and caps in one step). The blob is checked before anything changes: a damaged, truncated or altered blob is refused with HoloErrorCodes.PlaneBlobCorrupt, one from a newer format with PlaneBlobVersion, and nothing is imported. On a database with a log the import is made durable by an immediate checkpoint. The copy is a live table: it keeps its positions, so a replayed batch is still skipped.
  • Reading. COUNT, SUM, AVG of the measures; MIN, MAX and COUNT(DISTINCT ...) of a dimension; WHERE over dimensions (=, !=, <, IN, BETWEEN, LIKE, AND/OR/NOT); GROUP BY, HAVING over COUNT/SUM, ORDER BY, LIMIT. Not served, and refused with a message that names the reason: SELECT * or any row list, a WHERE on a measure, MIN/MAX of a measure or of an empty selection, joins, DISTINCT, NEAREST, and every row statement (INSERT, UPDATE, DELETE, ALTER, CREATE INDEX, ApplyChanges, BulkLoad). EXPLAIN and RouteTrace report the plane route; PleromaMode does not apply (the plane is the only route).
  • Durable. With a write-ahead log the table is saved in the snapshot (format v5, written only when a summary table exists) as its dictionaries, positions and the exact cells of the plane, and every batch is a log statement. A reopen restores the same counts and positions; re-sending the whole feed after a crash lands on the same state, whatever the crash point. An older binary refuses a database that holds a summary table. PleromaVerify(table) checks the plane against itself (totals = every dimension = the joint, no negative count), since no row exists to rebuild it from. Not allowed inside BEGIN; in-RAM databases only.
using HoloDb;

var db = new HoloDbService(new HoloDbOptions());
db.Execute("CREATE SUMMARY TABLE sales (region TEXT DIMENSION, channel TEXT DIMENSION, units INT MEASURE, revenue INT MEASURE)");

// A feed delivers rows with the position it has reached; the table keeps no rows, only the summary.
var batch = new[]
{
    PlaneChange.Add(new object?[] { "north", "web", 3L, 30L }),
    PlaneChange.Add(new object?[] { "south", "shop", 5L, 50L }),
    PlaneChange.Add(new object?[] { "south", "web", 1L, 10L }),
};
var r = db.ApplyPlaneChanges("sales", "orders-feed", 100, batch);
Console.WriteLine($"{r.Added} added, skipped: {r.Skipped}");
// 3 added, skipped: False

// The same batch again (a replayed feed) is a no-op.
Console.WriteLine($"replay skipped: {db.ApplyPlaneChanges("sales", "orders-feed", 100, batch).Skipped}");
// replay skipped: True

// A row changed at the source: send the old image and the new one.
db.ApplyPlaneChanges("sales", "orders-feed", 101, new[]
{
    PlaneChange.Replace(new object?[] { "south", "shop", 5L, 50L }, new object?[] { "south", "shop", 6L, 60L }),
});

foreach (var row in db.Execute("SELECT region, SUM(units), SUM(revenue) FROM sales GROUP BY region ORDER BY region").Rows)
    Console.WriteLine($"{row["region"]} {row["sum(units)"]} {row["sum(revenue)"]}");
// north 3 30
// south 7 70

var t = db.PlaneTotals("sales");
Console.WriteLine($"{t.Rows} rows, {t.Sums["units"]} units, position {db.PlanePosition("sales", "orders-feed")}");
// 3 rows, 10 units, position 101

// A remove must name a row that was added; otherwise the whole batch is refused and nothing changes.
try { db.ApplyPlaneChanges("sales", "orders-feed", 102, new[] { PlaneChange.Remove(new object?[] { "east", "web", 1L, 1L }) }); }
catch (HoloException e) { Console.WriteLine(e.Message.Split(':')[0]); }
// plane-only table 'sales'

// There are no rows to list.
try { db.Execute("SELECT * FROM sales"); }
catch (HoloException e) { Console.WriteLine(e.Message.Split(". ")[0]); }
// plane-only table 'sales' cannot answer this statement: SELECT * lists rows

Measured (verb planeonly scale, each point in its own process): with 5 columns and 3 dimensions of 4, 3 and 7 bits, the table holds 1.82 MB at 100,000 rows and at 10,000,000 rows (log-log slope 0.00 in rows); the same data kept as rows takes 441 MB at 2,000,000 rows (a narrow copy with only these columns: 204 MB). A filtered or two-column read costs 0.2 to 0.4 ms at any size, where the engine's own scan of the same two statements takes 7.6 and 16 ms at 2,000,000 rows. A snapshot is 0.9 MB and reopens in about 2 ms whatever the row count; the log before a checkpoint costs about 35 bytes per row. Applying costs about 0.6 to 0.9 us per row once a batch holds a few hundred rows (2,048 rows as single-row calls cost 30 to 40 times as much); a batch that contains removes pays one extra read of the count plane (about 1.1 ms at 14 joint bits).

QueryResult — reading results

Shape reference (abbreviated — bodies omitted, not literal source):

public sealed class QueryResult
{
    public int Affected { get; }                                       // rows affected (mutations) / returned (SELECT)
    public List<Dictionary<string,object>> Rows { get; }                // lazily materialised row-dict view; always safe
    public bool IsColumnar { get; }                                     // true for a plain, non-aggregated projection scan
    public int RowCount { get; }
    public IReadOnlyList<string> Columns { get; }
    public bool IsIntColumn(int col); public bool IsRealColumn(int col);
    public long GetLong(int row, int col); public double GetDouble(int row, int col); public string GetText(int row, int col);
    public object GetValue(int row, int col);                           // works for both columnar and row-dict results
    public static QueryResult FromRows(List<Dictionary<string,object>> rows, string[] columns);  // rebuild from wire data (HoloDb.Protocol)
}

.Rows (a case-insensitive Dictionary<string,object> per row) always works, for any query shape, and is the simplest way to read a result. IsColumnar is true only for a plain projection scan — SELECT col[, col...] FROM t [WHERE ...] [ORDER BY ...] [LIMIT ...] with no aggregate, GROUP BY, JOIN, DISTINCT, or NEAREST — in which case GetLong/GetDouble/ GetText read the underlying typed arrays with zero boxing, the fast path for large result sets. For every other query shape (aggregates, GROUP BY, JOIN, DISTINCT, NEAREST), use .Rows or GetValue. GetLong/GetDouble/GetText also work on a row-form result when the cell holds that type (GetLong on count(*)); a NULL cell, a cell of another type or an out-of-range index throws a HoloException naming the column (test IsNull first).

var db = new HoloDbService(new HoloDbOptions());
db.Execute("CREATE TABLE t (id INT PRIMARY KEY, name TEXT)");
db.Execute("INSERT INTO t VALUES (1, 'Ada'), (2, 'Bo')");
var r = db.Execute("SELECT id, name FROM t WHERE id >= 1 ORDER BY id");   // plain projection -> columnar
Console.WriteLine(r.IsColumnar);
// True
for (var i = 0; i < r.RowCount; i++) Console.WriteLine($"{r.GetLong(i, 0)}:{r.GetText(i, 1)}");
// 1:Ada
// 2:Bo

Durable, larger-than-memory (paged) tables

For data that outgrows RAM, create a paged table: stored on a buffer-pool + write-ahead-log substrate (plain FileStream-based, not memory-mapped), so it is durable and its data can exceed memory — cold pages demand-page back from disk. Whole-table aggregates and single-column GROUP BY count/sum stay O(1) regardless of table size. INSERT/UPDATE/DELETE/SELECT work through the normal Execute(sql) path once the table exists; transactions, primary-key uniqueness (with O(1) point lookup), a single INNER JOIN (COUNT/SUM only — no GROUP BY over a join, and no multi-way join), GROUP BY/HAVING, ORDER BY, LIMIT, DISTINCT, and NEAREST are all supported on a paged table. The primary-key index is kept in RAM (about 35 bytes/row, measured 34 to 39) — for a table far larger than RAM, that index, not the buffer pool, is the limiting resource.

  • CreatePaged(createTableSql, dir, bufferFrames = 8192) — create a table backed by the paged store in dir (RAM budget = bufferFrames × 8 KB). Returns the table name.
  • OpenPaged(dir, ifNotExists = false) — re-open a paged table previously created in dir, recovering committed state from its log. ifNotExists: if a table of that name is already registered on this Database, return its name instead of throwing.
  • PagedBulkLoad(table, rows, commitEvery = 4096) — stream object?[] rows (schema column order) into a paged table, committing every commitEvery rows. Returns the row count.
  • ClosePaged(table) — checkpoint and close one paged table's store.
var db = new HoloDbService(new HoloDbOptions());
var dir = Path.Combine(Path.GetTempPath(), "orders-paged");
db.CreatePaged("CREATE TABLE big (id INT PRIMARY KEY, v INT)", dir);
db.PagedBulkLoad("big", new object?[][] { new object?[] { 1L, 100L }, new object?[] { 2L, 200L } });
Console.WriteLine(db.Execute("SELECT sum(v) FROM big").Rows[0]["sum(v)"]);
// 300
db.ClosePaged("big");

var reopened = db.OpenPaged(dir);               // recovers from the log
Console.WriteLine(db.Execute($"SELECT count(*) FROM {reopened}").Rows[0]["count(*)"]);
// 2
db.ClosePaged(reopened);

Whole-database paged-default mode (HoloDbOptions.Paged = true, or Database.Open(walPath, ckpt, paged: true)) lets a plain CREATE TABLE/BulkLoad target the paged substrate automatically — no CreatePaged/PagedBulkLoad call needed. It only takes effect for a brand-new database (no existing WalPath/WalPath + ".snap" on disk); opening an existing legacy database with Paged = true silently falls back to the in-RAM engine — check Database.IsPagedDefault to see which mode you actually got.

var dir = Path.Combine(Path.GetTempPath(), "pageddefault-demo");
Directory.CreateDirectory(dir);
var walPath = Path.Combine(dir, "db.wal");
var pd = new HoloDbService(new HoloDbOptions { WalPath = walPath, Paged = true });
pd.Execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)");
pd.Execute("INSERT INTO t VALUES (1, 42)");
Console.WriteLine(pd.Database.IsPagedDefault);
// True
Console.WriteLine(pd.Execute("SELECT v FROM t WHERE id = 1").Rows[0]["v"]);
// 42
pd.Close();

Pleroma aggregate plane and COUNT(DISTINCT)

An optional, derived summary of a table's small-domain columns, held in Phasor Pleroma stores. The columns stay the source of truth; the plane is rebuilt from them at any time and never answers approximately. Pick dimensions (INT or TEXT columns, used by WHERE, GROUP BY, COUNT(DISTINCT), MIN/MAX and join keys) and measures (INT columns whose SUM is served; exact past Int64 internally).

using HoloDb;

var db = Database.Open();
db.Execute("CREATE TABLE sales (id INT PRIMARY KEY, region TEXT, tier INT, qty INT)");
db.Execute("INSERT INTO sales (id, region, tier, qty) VALUES (1,'north',1,10),(2,'north',2,-4),(3,'south',1,7),(4,'east',3,5),(5,'south',2,9)");

// attach the plane: dimensions answer WHERE / GROUP BY / COUNT DISTINCT, measures answer SUM
db.EnablePleroma("sales", new PleromaPlaneOptions(new[] { "region", "tier" }, new[] { "qty" }));
db.PleromaMode = PleromaMode.Always;   // serve every shape the plane covers (the default, Auto, leaves O(1) accumulator shapes to the engine)

var r = db.Execute("SELECT COUNT(*), SUM(qty) FROM sales WHERE region = 'north' OR tier = 3");
Console.WriteLine($"{r.Rows[0]["count(*)"]} {r.Rows[0]["sum(qty)"]}");                 // 3 11

r = db.Execute("SELECT COUNT(DISTINCT region) FROM sales");
Console.WriteLine(r.Rows[0]["count(distinct region)"]);                                 // 3

db.Execute("DELETE FROM sales WHERE id = 4");                                           // writes keep the plane current
Console.WriteLine(db.Execute("SELECT COUNT(DISTINCT region) FROM sales").Rows[0]["count(distinct region)"]);   // 2

Console.WriteLine(db.PleromaVerify("sales") ?? "plane equals a rebuild from the columns");   // plane equals a rebuild from the columns
Console.WriteLine(db.PleromaSumExact("SELECT SUM(qty) FROM sales"));                    // 22

The same plane from plain SQL (so a remote client can ask for it too):

using HoloDb;

var db = Database.Open();
db.Execute("CREATE TABLE sales (id INT PRIMARY KEY, region TEXT, tier INT, qty INT)");
db.Execute("INSERT INTO sales (id, region, tier, qty) VALUES (1,'north',1,10),(2,'north',2,-4),(3,'south',1,7)");
db.Execute("CREATE INDEX by_region ON sales (region, tier) INCLUDE (qty) USING SUMMARY");

var show = db.Execute("SHOW INDEXES ON sales");
Console.WriteLine($"{show.GetValue(0, 1)} {show.GetValue(0, 2)} {show.GetValue(0, 3)}");   // by_region summary region,tier
db.PleromaMode = PleromaMode.Always;
Console.WriteLine(db.Execute("SELECT SUM(qty) FROM sales WHERE region = 'north'").Rows[0]["sum(qty)"]);   // 6
db.Execute("DROP INDEX by_region");
  • Served by the plane: COUNT and SUM (with any AND/OR/NOT/LIKE/IN/BETWEEN over dimensions), GROUP BY on one or several dimensions with HAVING on count/sum, COUNT(DISTINCT dim), MIN/MAX of a dimension, and a single equi-join COUNT / SUM between two tables that both have planes.
  • Declined (the engine answers): AVG, columns that are not plane dimensions or measures, a dimension past MaxDimBits distinct values, multi-dimension queries when the joint width exceeds MaxJointBits, joins with a WHERE or more than 4095 distinct keys. A declined query is never wrong, only unaccelerated.
  • PleromaMode: Auto (default) leaves whole-table and single-column GROUP BY to the engine's own O(1) accumulators; Always serves every covered shape; Off bypasses the plane while it is still maintained.
  • SQL form: CREATE INDEX sm ON sales (region, tier) INCLUDE (qty) USING SUMMARY builds the same plane, works through HoloDb.Server like any statement, is replayed from the write-ahead log on Open, survives Checkpoint() (the snapshot stores the definition and rebuilds the plane at load), and is undone by ROLLBACK. DROP INDEX sm removes it; SHOW INDEXES reports its size, served/declined counts and over-cap dimensions.
  • Lifecycle: in-RAM tables only. A plane attached with EnablePleroma is not persisted: call it again after Database.Open (a SQL-created one is persisted). ROLLBACK restores it with the table. PleromaStats reports sizes and served/declined counts; PleromaVerify compares it with a fresh rebuild.
  • Sums past Int64: the plane raises an error instead of wrapping; PleromaSumExact returns the exact Int128.
  • COUNT(DISTINCT col) is a general SQL addition: it must be the only kind of select item (several COUNT(DISTINCT ...) items are allowed), with no GROUP BY or JOIN, and is not available on paged tables.
  • Memory: the plane's cells are about 0.92 MB for a 14-bit joint whatever the row count (plus the same again in pending deltas before a flush), and attaching a plane added 1.9 to 4.6 MB of heap in the benchmark (measured at 100,000 to 1,000,000 rows).

Pleroma page store (the Pleroma as the data)

PleromaPageStore keeps a family of Phasor Pleroma pages of one shape (for example dense CompleteKeys pages, one per context) durably and larger than memory, with no columns behind them. Each page is saved through RawCells and RawCheckCells into HoloDb's paged buffer pool and write-ahead log; hot pages are live Pleroma objects in a byte-budgeted cache, cold pages load on demand.

using HoloDb;
using Phasor;

var shape = new HologramShape { KeyCapacity = 2, KeyCycle = 110, CompleteKeys = true };   // every page: 110 exact key counts
var dir = Path.Combine(Path.GetTempPath(), "plrstore-demo");
if (Directory.Exists(dir)) Directory.Delete(dir, true);

using (var store = PleromaPageStore.Open(dir, shape))
{
    // adds are journaled before they are applied; page 42 and page 7 are created on first use
    store.AddBatch(new[] { new PageAdd(42, 5, 3), new PageAdd(42, 77, -2), new PageAdd(7, 5, 1) });
    store.AppendEvent("context 42 seen"u8);                       // an opaque owner event, ordered with the batches
    Console.WriteLine($"{store.Counts(42)[5]} {store.Counts(42)[77]}");                 // 3 -2
}

using (var store = PleromaPageStore.Open(dir, shape))             // reopened: the pages come back exactly
{
    Console.WriteLine($"{store.Counts(42)[5]} {store.Counts(7)[5]}");                   // 3 1
    store.ReplayEvents((seq, bytes) => Console.WriteLine($"{seq} {System.Text.Encoding.UTF8.GetString(bytes)}"));   // 2 context 42 seen
    Console.WriteLine(store.Verify().Clean);                                            // True  (every record's check and parity group)
    Console.WriteLine(store.Stats().Pages);                                             // 2
}
Directory.Delete(dir, true);
  • API: Open(path, shape, options), AddBatch(IReadOnlyList<PageAdd>) (returns the batch's sequence number), Get(pageKey) (a copy; null if absent), Read(pageKey, fn) (zero-copy), Counts(pageKey), Contains, PageKeys, KeysInRange(lo, hi) (children or prefix scan when the owner packs a parent into the high bits of the key), AppendEvent / ReplayEvents / TrimEvents, Checkpoint, Checkpoints, RestoreTo, Verify, Repair, Stats, Dispose. Page keys are non-negative longs.
  • Concurrency: readers never take a store-wide lock. A read holds a shared lock on its page's stripe (1,024 stripes), so it waits only for a write to a page on the same stripe, or for Verify / Repair / RestoreTo. One writer at a time. A batch is applied page by page: a reader sees every page whole, but may see a batch applied to some pages and not yet to others. A stress test compares concurrent readers with a single-threaded oracle.
  • Writes: every AddBatch is appended to the journal (fsynced unless SyncEveryBatch is false) before it is applied. Each page remembers the sequence number of the last batch applied to it, so crash recovery replays a logged add exactly once, even over pages the cache had already flushed. A torn journal tail is discarded, so a batch survives whole or not at all.
  • Restore points: Checkpoint() returns a PleromaCheckpoint (id and journal sequence number). RestoreTo(id) returns the store to exactly that state, with no recount: pages written since go back, pages created since vanish, later points are dropped. It is copy-on-write: before a record is first overwritten after a restore point, its old bytes are appended to that point's undo file, so a restore point costs one before-image per page changed since, not a copy of the data file. MaxCheckpoints (default 8) bounds how many are kept. Events stay in the event journal (read them with ReplayEvents(afterSeq)).
  • Event journal: AppendEvent stores an opaque record in the same sequence space as the batches; ReplayEvents returns them in order; TrimEvents(upToSeq) drops the absorbed prefix and sequence numbers never repeat.
  • Redundancy: the store's shape carries a Phasor CheckPrime: every page keeps a mirror of its cells in a second prime, both copies are saved, and Pleroma.VerifyCheck proves a record intact (Verify runs it on every page; VerifyOnLoad runs it on every load, about 100 microseconds for a 110-key page). Records are grouped (ParityGroup, default 8) with one PleromaParity exact-sum record per group (one over the main cells, one over the check cells), and the members of a group sit on different 8 KB pager pages, so any one lost pager page, or any one damaged record per group, is rebuilt by Repair. A rebuilt page must pass its own check before it is written; a member and its group's parity both damaged are reported as unrecoverable, never rebuilt wrong.
  • Cache: CacheBytes (default 64 MB) bounds the live pages (clock eviction); Stats reports pages, resident pages and bytes, hits, misses and hit rate, page flushes, the last sequence number and the restore points kept.
  • Limits: one shape per store (reopening with another shape is refused); the shape must be CompleteKeys and admissible for a check prime; shapes with a continuous component, a relation face or a non-unit TimeScale cannot be saved; one process opens a store at a time.

Sparse pages for a large vocabulary

PleromaSparsePageStore stores Phasor PleromaSparsePage pages (a histogram over the whole 2^61 key cycle whose size follows the keys present) with the same journal, exactly-once replay, striped concurrent reads and cache. Pages have different sizes, so records come in capacity classes: class c holds pages of capacity up to 2^c in a record of one fixed size, laid out as header, capacity, cells, support count and RawKeys (several records to an 8 KB pager page for small classes, a chain of pages for large ones). When a page outgrows its class the next flush writes it into the larger class and appends a new directory entry (the directory is last-entry-wins). AddBatch(IReadOnlyList<SparseAdd>), Get, Read, Counts, KeysInRange, Verify (header hash, then support against cells), Checkpoint, Stats (pages per class, class changes). Parity groups, restore points and the event journal are in the dense store only.

Dependency injection

public static IServiceCollection AddHoloDb(this IServiceCollection services, Action<HoloDbOptions>? configure = null)

Registers a singleton HoloDbService. Needs only IServiceCollection (Microsoft.Extensions.DependencyInjection.Abstractions) — add the full Microsoft.Extensions.DependencyInjection package (or use ASP.NET Core / Generic Host, which already provides it) to get BuildServiceProvider().

var services = new ServiceCollection();
services.AddHoloDb(o => o.WalPath = null);
var provider = services.BuildServiceProvider();
var db = provider.GetRequiredService<HoloDbService>();
db.Execute("CREATE TABLE ping (id INT PRIMARY KEY)");
db.Execute("INSERT INTO ping VALUES (1)");
Console.WriteLine(db.Execute("SELECT count(*) FROM ping").Rows[0]["count(*)"]);
// 1

EvalApp pipeline integration

public static IAppBuilder WithHoloDb(this IAppBuilder builder, HoloDbService service)

Attaches a HoloDbContext (an EvalApp GlobalContext wrapping .Service) to a pipeline app, so a context-aware step (ContextPureStep<HoloDbContext, TDomain, T>) can reach global.Service inside TransformAsync without threading a connection through every record. An app has one global context (WithContext sets it; a second call replaces the first), and WithHoloDb is the shorthand that makes that context a HoloDbContext. When the app needs the database and something else (another service, a second database, other pipelines), the context is your own object that holds a HoloDbContext for each database, in one app, as below; steps reach it as global.Db.Service.

using EvalApp;
using HoloDb;

var service = new HoloDbService(new HoloDbOptions());
service.Execute("CREATE TABLE users (id INT PRIMARY KEY, name TEXT)");
service.Execute("INSERT INTO users VALUES (1, 'Ada')");

// One app, one context object of your own: it holds the database (and anything else a step needs).
var services = new Services(new HoloDbContext(service));

Eval.App("Catalog")
    .WithContext(services)
    .DefineDomain("Catalog")
        .DefineTask<LookupQuery>("LookupUser")
            .AddStep<LookupUserStep>("Lookup")
        .Run(out ICompiledPipeline<LookupQuery> pipeline)
    .Build();

var outcome = await pipeline.RunAsync(new LookupQuery(1));
if (outcome is PipelineResult<LookupQuery>.Success ok) Console.WriteLine(ok.Data.Name);
// Ada

// Top-level statements must precede type declarations in a Program.cs:
public sealed record LookupQuery(long Id, string Name = "");

public sealed class Services(HoloDbContext db) : GlobalContext
{
    public HoloDbContext Db { get; } = db;
}

public sealed class LookupUserStep : ContextPureStep<Services, LookupUserStep.NoDomain, LookupQuery>
{
    public sealed class NoDomain : DomainContext { }
    protected override ValueTask<LookupQuery> TransformAsync(LookupQuery data, Services global, NoDomain domain, CancellationToken ct)
    {
        var r = global.Db.Service.Database.Execute($"SELECT name FROM users WHERE id = {data.Id}");
        return ValueTask.FromResult(data with { Name = (string)r.Rows[0]["name"] });
    }
}

HoloDbService.ExecuteAsync and ApplyChangesAsync run the synchronous engine on a thread-pool thread (Task.Run) and are not gated by the pipeline; inside a step, call the synchronous Database as above.

Usage metering and the billing model

HoloDb.Analytics wraps a Database to report, per query, the WORK actually done versus what a per-scan warehouse (BigQuery, Athena) would bill for the same query on the same metric (bytes processed) — the spine of the metered analytics API this engine also runs behind.

Shape reference (abbreviated — bodies omitted, not literal source):

public readonly record struct QueryUsage(long RowsScanned, long TableRows, long RowBytes, long BilledBytes, double ElapsedMs)
{
    public long BytesActuallyRead { get; }   // RowsScanned * RowBytes — 0 for a maintained-accumulator aggregate
    public bool ConstantTime { get; }         // true iff RowsScanned == 0
    public double MarginHeadroom { get; }     // BilledBytes / BytesActuallyRead
}
public sealed record ScanPricing(string Name, double UsdPerTiB, long MinBytesPerQuery) { /* .BigQuery, .Athena, .All */ }
public sealed record HoloPricing(double UsdPerTiB = 0.625, long MinBytesPerQuery = 10L * 1024 * 1024) { public double CostUsd(QueryUsage u); public double DiscountVs(ScanPricing warehouse); }
public sealed class MeteredDatabase
{
    public MeteredDatabase(Database db, HoloPricing? pricing = null);
    public Database Inner { get; }
    public (QueryResult Result, QueryUsage Usage) Run(string sql);
    public double OurCostUsd(QueryUsage u);
    public double ScanCostUsd(QueryUsage u, ScanPricing engine);
}
var db = new HoloDbService(new HoloDbOptions());
db.Execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)");
db.Execute("INSERT INTO t VALUES (1, 100), (2, 200)");
var metered = new MeteredDatabase(db.Database);
var (res, usage) = metered.Run("SELECT sum(v) FROM t");
Console.WriteLine(usage.ConstantTime);
// True  (served from the maintained accumulator, no scan)
Console.WriteLine(metered.OurCostUsd(usage) <= metered.ScanCostUsd(usage, ScanPricing.BigQuery));
// True

Database.LastRowsScanned/LastTableRows/LastRowBytes/LastLogicalBytes (static, per-thread) are the lower-level counters MeteredDatabase.Run reads; use them directly if you call Database.Execute yourself and just want the numbers without the pricing wrapper.

Parallel fan-out primitive (HoloPar)

public interface IParallelMap { void Map(int chunks, Action<int> body /* + compiler-filled caller info */); }
public static class HoloPar { public static IParallelMap Cpu { get; } public static bool Licensed { get; } }

The single parallel primitive the columnar query engine fans out through internally (never a raw Parallel.For/new Thread) — exposed publicly, but you don't need to call it: every query path that benefits from it already does. It runs on EvalApp's adaptive dispatch: each call site learns its own cost and runs inline when a fan-out would not pay. Licensed is always true and is not a license/tier check — nothing here is license-gated.

var sum = 0; var gate = new object();
HoloPar.Cpu.Map(4, i => { lock (gate) sum += i; });
Console.WriteLine(sum);
// 6

Advanced / low-level engine types

These are public types (the assembly ships them unmarked internal), but none of them is returned by, or needed to call, Database/HoloDbService/SQL — they are the engine's own building blocks. Documented here for completeness; prefer the SQL surface above for anything application-level.

Type What it is
Holo (static) The row-hologram codec: EncodeRow(Schema, row) binds each column into a block-partitioned holographic vector; Similarity(a, b) is cosine similarity. This is what NEAREST runs on. NumberFace(v)/TextFace(s) are the graded per-value encodings behind it (geometric-period number ladder; character-trigram text bundle) — see the NEAREST section above.
HoloLsh A locality-sensitive-hash index over row holograms (SimHash over random hyperplanes): Add, Approx (sub-linear top-k), Exact (brute-force top-k). What NEAREST builds over a table once it crosses ~8,000 rows.
GrowColumn<T> An append-only, copy-on-grow T[] with a published count — the lock-free-readable column primitive under every in-RAM table.
Table / Schema / Column / ColType / LiveMetrics / GroupRow The in-RAM table/schema primitives Database builds on (Schema requires exactly one PRIMARY KEY, INT or TEXT). Public by assembly visibility, not a supported construction path — build tables through Database/SQL, not new Table(schema).
var schema = new Schema(new[] { new Column("id", ColType.Int, true), new Column("tag", ColType.Text, false) });
var v1 = Holo.EncodeRow(schema, new Dictionary<string, object?> { ["tag"] = "red" });
var v2 = Holo.EncodeRow(schema, new Dictionary<string, object?> { ["tag"] = "red" });
Console.WriteLine(Holo.Similarity(v1, v2) > 0.99);
// True

Invariants and gotchas

  • Upsert. A plain INSERT on an existing primary key throws. Use INSERT .. ON CONFLICT (pk) DO UPDATE SET col = EXCLUDED.col or ApplyChanges (see Atomic change batches).
  • One global reader-writer lock. SELECT takes the shared read lock (many run concurrently); every mutation/DDL/transaction statement takes the exclusive write lock (serialised). A lock-free "live metrics" fast path serves pure-accumulator reads (whole-table COUNT/SUM, a single-column GROUP BY) off a snapshot published after each write, so a streaming ingest never stalls a dashboard read. A statement that references a column declared NULL is not served by it (NULL).
  • A multi-statement transaction is a series of separate Execute calls — the lock releases between them, so cross-statement isolation between different callers sharing one Database is the caller's own responsibility.
  • NEAREST similarity is graded but not a global total order (2026-10 fix, F-04): close values reliably rank ahead of far ones at local/mid range; it is not guaranteed that EVERY pair of far values orders strictly by distance from each other (both score low either way). See the NEAREST section above.
  • No AS aliasing (columns or tables). It does not exist anywhere in the grammar today; a table alias in a FROM/JOIN throws. Projected columns always keep their raw/agg(col) name (read QueryResult by position if you want a different label). A column not declared NULL has a type-zero default (0/0.0/""), never a null state; see NULL for the columns that do.
  • Indexes are INT or TEXT only, never REAL.
  • REAL columns never get a maintained accumulator (INT sums are exact Int128 running totals). Whole-table REAL SUM/MIN/MAX/AVG is a SIMD scan (still sub-millisecond at typical sizes); grouped REAL SUM scans too — the one place this engine doesn't maintain a running accumulator.
  • Aggregate result columns are lower-case agg(col), e.g. count(*), sum(x) — never the SQL text as typed, never an alias.
  • Paged tables have a narrower JOIN ceiling than in-RAM tables: one INNER JOIN, COUNT/SUM only, no GROUP BY over a join. This is exactly why HoloDbOptions.Paged/Database.Open(..., paged: true) is an opt-in, not the library default.
  • BulkLoad cannot run inside an explicit transaction (throws).
  • A legacy (non-paged) database's write-ahead log is literally SQL text, replayed by re-parsing it on the next Open. A build that understands a newer keyword (e.g. ALTER) writes that text into the log; an older binary's parser then throws on it and refuses to open the database until a newer binary checkpoints it first (Checkpoint() writes a fresh schema-only snapshot + empty log). Mixed binary versions sharing one on-disk legacy database must checkpoint on the newer binary before an older one opens that file. Paged tables have no such risk — their log is a generic, statement-agnostic physical redo log of raw pages.
  • DebugType=none, no XML doc file, no PDB ship in the package — no IntelliSense doc text or symbol debugging from the NuGet package itself; this file is the documentation.
  • HoloDbOptions.LicenseKey/Database's license plumbing is accepted but has no effect on which capabilities are available — nothing in this package is gated behind a tier or a license key.

Exceptions

Every error this engine raises deliberately (bad SQL, a schema violation, a duplicate key, an unsupported shape) is a HoloException : Exception with a plain message — catch that one type:

public sealed class HoloException : Exception { public HoloException(string message); public string? Code { get; } public string? Dimension { get; } public int? Cap { get; } public int? Needed { get; } }   // Code is set only for a plane-only cap refusal (HoloErrorCodes.PlaneDimensionCap / PlaneJointCap) or a refused plane blob (PlaneBlobCorrupt / PlaneBlobVersion)

File layout

  • In-memory (WalPath = null): nothing touches disk; state does not survive the process.
  • Legacy durable (WalPath set, Paged not set / existing legacy database): WalPath is an append-only text log of committed SQL; WalPath + ".snap" is the latest binary snapshot of all table data. Checkpoint() folds the log into a fresh snapshot and truncates the log.
  • Paged / durable larger-than-memory (CreatePaged(sql, dir, ...), or whole-database Paged = true): one self-contained paged store per table — a buffer pool + a physical page-level write-ahead log — under its own directory (dir, or one subdirectory per table under WalPath + ".tables" in whole-database paged mode), auto-discovered on Open. No memory-mapped files anywhere in either storage shape.

License

Proprietary — this package ships the compiled library only; see LICENSE for the full terms. Installing and using unmodified copies for internal evaluation and non-commercial development or testing is licensed; commercial, production, or revenue-generating use requires a separate commercial licence from the owner. No feature on this page is gated behind a tier or a licence key — every capability described works identically with or without one. Copyright (c) 2026 Dongyang Stephen Chen, trading as Evaluated Applications. All rights reserved.

HoloDbOptions.LicenseKey is accepted but has no effect on which capabilities are available.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (4)

Showing the top 4 NuGet packages that depend on EvaluatedApplications.HoloDb:

Package Downloads
EvaluatedApplications.Prose

Grammar-driven synthetic corpus generator: mines nouns, verbs and adjectives and the syntax that joins them from real text with the rules of grammar, stores them in HoloDb, and recombines them into new grammatical sentences built only from words and pairings the text used, plus reading-comprehension Q&A pairs (single-sentence, multi-sentence passage, and two-turn). Rules-first (deterministic, zero-training) part-of-speech tagging, chunking and role assignment; mining fans out across cores through EvalApp (generation does too, opt-in); an optional mini-HoloFormer scorer re-ranks generated candidates by next-lemma likelihood once trained (POS tagging stays 100% rules-based).

EvaluatedApplications.HoloDb.Protocol

The wire contract for HoloDb clients and servers: length-prefixed TCP framing plus a columnar-preserving binary codec that encodes requests and decodes responses as typed column arrays (long[], double[], string[]) instead of JSON. Includes protocol version negotiation and three exception types for version skew, malformed frames, and server errors. Use this if you are implementing a HoloDb server or building a custom client; most applications should use EvaluatedApplications.HoloDb.Client instead.

EvaluatedApplications.HoloGenome

Encode DNA as phasor holograms whose geometry comes from measured DNA physics. Each base is a Phasor symbol face carried on a helical phase that advances by published, sequence-dependent dinucleotide twist (Olson et al. 1998, built in), optionally with a local DNA-shape layer (minor groove width, propeller twist, roll, helix twist) read from a pentamer table you supply. Windows of sequence become fixed-width vectors you can store in HoloDb, compare, or feed to any regression. Also grades sequences against your own measurements, with no files or console so it runs in a browser: a zero-parameter helical score, a cross-validated ridge readout on the holograms, and a fitted phasor model that reports the helical period (bases per turn), each with a correlation and a block-resampled 95% interval. Research and education only: it makes no health, disease-risk or drug-response claims. Experimental (0.x).

EvaluatedApplications.PleromaMind

A persistent conversational unit built on exact holograms, created empty from a design you choose and taught from your own text; the package ships no voice and no text. Its alphabet is Prism's 320 tokens (characters, common words and chat tags) plus subwords you can mint from your own text. It learns every exchange it hears and says, plus anything taught or ingested by reference, into exact per-conversation counts for the context lengths you pick. Memory grows with the text learned (ModelDesign.Estimate gives the cost by formula). It has a conversation memory for long matches (its trust can be calibrated on your own text and is journaled) and an optional learned sharing layer (token classes and solved trust) that learns only when you call LearnStep, with Compare scoring held-out text with and without it; it offers N sampled replies to pick from without learning anything; takes bounded and fading approve or disapprove feedback without touching memory; forgets a conversation exactly; and rebuilds its whole state bit for bit from its journal on a HoloDb page store, including after a crash. Every choice can be inspected per context length.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
10.4.0 0 11/12/2026
10.3.0 45 11/11/2026
10.2.0 59 11/10/2026
10.1.0 65 11/10/2026
10.0.1 90 11/10/2026
10.0.0 86 11/9/2026
9.0.0 111 11/3/2026
7.0.0 126 10/24/2026
6.0.1 107 10/23/2026
6.0.0 101 10/23/2026
5.3.1 110 10/17/2026
5.3.0 151 10/17/2026
5.2.0 114 10/16/2026
5.1.2 129 10/14/2026
5.1.1 114 10/13/2026
5.1.0 107 10/12/2026
5.0.0 104 10/11/2026
4.2.0 123 10/11/2026
4.1.0 105 10/11/2026
4.0.0 110 10/10/2026
Loading failed