WEBSITE ALPHA · AI-assisted, source-reviewed documentation · Full-stack docs reconciled 2026-09-14
64x64base

DotTalk++ as a DBMS and an RDBMS

What makes x64base a database management system, what makes it relational -- including enforced primary keys -- and exactly where a purist would say it stops.

DotTalk++ / x64base is a DBMS — it owns its storage, its indexes, its locking and its recovery — and it is also an RDBMS: it stores relations, declares relationships between them, enforces a declared primary key on write, and answers questions with relational algebra rather than only with cursor navigation.

Keys came last and are stated first here because they are the relational primitive. A column designated PRIMARY is generated on APPEND, held against reuse until PACK, refused on rewrite, and stamped into the table's own metadata so a fresh process still refuses. That is a real key, not a convention — and it landed on 2026-09-07, which is recent enough that a reader who last looked earlier would reasonably still believe otherwise.

Both halves are stated here with their evidence, and so is the boundary. A system that claims the word "relational" without saying where it stops is selling, not documenting.

The DBMS half

CapabilityWhat it means here
Storage engineIts own DBF-family formats including the 64-bit x64 dialect — real binary field types, 64-bit record addressing, and a memo store with its own object model. See DBF_64 Specification.
IndexingNative CDX and CNX containers, single-tag INX, and an LMDB-backed ordered key store, with tags resolved to fields at build time. See CDX and LMDB Indexing.
ConcurrencyCross-process cooperative record and table locks, pid-stamped, with stale-owner recovery.
TransactionsBuffered writes with COMMIT / ROLLBACK and a durable redo log under TABLE BUFFER PERSISTENT. Scoped, not a full-ACID claim — see ACID and the Glass-Box Engine.
CatalogsThe engine's own metadata tables describe its commands, functions, messages and workspaces — the map drawn in the same ink as the territory.
Sessions and scopeWork areas, workspaces that own their own path environment, and a durable workspace identity. See Workspaces.

The relational half

A relation here is an ordinary table, and relationships between them are expressed two different ways on purpose.

Declared, navigational — RelTalk. REL ADD parent child ON key establishes an edge in advance; REL JOIN walks it and emits one tuple per leaf combination. The question it answers is "where do I go from here?". See RelTalk.

Undeclared, set-oriented — SQLsel. A SQLSEL statement names its own tables and ON columns, reads no REL or SET RELATION state at all, and answers "which rows satisfy this?". It carries the operators a relational engine is expected to have:

  • selection and projection, DISTINCT, LIMIT, and ORDER BY with independent per-column directions
  • INNER, LEFT, RIGHT, FULL and CROSS joins; self-joins through aliases over a single physical table; composite boolean ON; and three-table INNER/LEFT chains
  • COUNT, SUM, AVG, MIN, MAX with GROUP BY and HAVING
  • UNION, UNION ALL, INTERSECT, EXCEPT
  • subqueries: scalar, IN, NOT IN, EXISTS and NOT EXISTS, both uncorrelated and correlated — including double-NOT EXISTS relational division, which is the "for all" question and the operator most often missing from things that call themselves relational
  • expression projection, so TRIM(LNAME) is a column, over an AST shared between projection and predicate
  • INSERT, UPDATE, DELETE with transactions that span every table they touch: each member prepares durably, one group-log row decides all of them, and a crash therefore leaves every member applied or none
  • workspace-scoped name resolution: two open workspaces may hold the same table name with different rows, and a statement resolves within the current workspace rather than to the engine's lowest matching slot
  • committed-read isolation: inside an explicit transaction SQLSEL reads committed table truth while TUPLE previews the dirty row buffer, so the two surfaces disagree on purpose and each says which it is showing
  • typed empty results: a join whose right side matches nothing preserves the column schema rather than collapsing to no shape at all

The verb is SQLSEL because xBase already spends SELECT on SELECT <area>, which switches the active work area rather than querying anything. The bare SELECT ... FROM ... form does ship, behind the session switch SET MODE SQL, which routes seven verbs to SQLSEL and blocks the cursor-oriented surfaces that would otherwise be reachable inside a statement. See SQLsel and SQL Conformance.

Neither of those two query surfaces replaces the other. One follows a structure you asserted about your data; the other asks a question that stands on its own.

Declared, enforced — a primary key. As of 2026-09-07 a column can be designated with SET UNIQUE FIELD <field> PRIMARY, and the designation does three separate things. It generates: an APPEND into a blank key mints max+1 inside the writer's table lock, so two writers cannot mint the same value. It reserves: a deleted row keeps its key until PACK, because RECALL can bring that row back and a reissued key would collide with it. And it refuses: a write that would change a minted key is rejected rather than quietly accepted.

It is real schema, not session state. The designation is stamped into the x64 field metadata as X64_FIELD_FLAG_PRIMARY, so a fresh process reopens the table and still refuses — which was not true until 2026-09-07, when the designation lived only in a process-local map that a restart discarded. Two consequences follow and both matter to a reader: it is x64-flavour-scoped, because the flag has nowhere to live in a classic or VFP descriptor, and a reader that does not know the flag reads zero and enforces nothing, in silence.

The policy in full — what each behaviour is, which spec proves it, why two of that spec's arms are expected red, and the write paths enforcement does not yet reach — is on Primary Keys.

How the relational claim is checked

Every SQLsel regression compares its result sets against an in-process SQLite oracle over identical data in the same run — not against a recorded expectation. That distinction is the point: a stored expectation only proves our reader agrees with our writer, and a shared misunderstanding passes. An independent engine answering the same question over the same rows does not.

Thirteen specs are graded that way. Six run on every REGRESSION ALL and seven are explicit-run; twelve of the thirteen are named SQLSEL_* and the thirteenth, SQLMODE_SMOKE, grades the mode router the same way. See Regression and Proof Testing and SQLsel and SQL Conformance.

The key claims are checked differently, and the difference is worth stating. There is no oracle for "was this write refused" — SQLite would simply have refused it too. So PKPOLICY asks the answerable question instead: every arm reads a FIELD VALUE back after the attempt. If the cell holds its original value the write was refused; if it holds the new one it was not. Console text is never the evidence. Five arms cover five different doors into a field write — native REPLACE, SQLSEL INSERT, SQLSEL UPDATE, and the two legacy bare INSERT/UPDATE verbs, which are different files reached by a different registration — because a fix wired into one is not evidence about another.

Persistence needs a second process, which no single script can provide, so PKDURABLE is a two-run harness: one process declares and writes, another starts cold and must still refuse. It is the only spec in the tree that can say a designation survived a restart.

Where it stops, stated plainly

Referential integrity is not enforced on write. Relations are declared for traversal, not as constraints. Deleting a parent does not refuse, cascade, or null out its children. This is the sharpest gap between x64base and a constraint-enforcing RDBMS, and it is a design position rather than an oversight — but a reader should not infer enforcement from the word "relational".

This gap got sharper in 2026-09, not softer. A declared primary key is now refused on write, so the engine demonstrably can decline a write on integrity grounds — it simply does not decline this one. "Not built yet" and "built elsewhere and deliberately not here" are different statements, and this is now the second.

Also absent, and each refused rather than silently approximated:

  • Universal primary-key enforcement. What is proven is five write paths — the ones PKPOLICY asserts. A static gate counts 13 direct field-write call sites across 11 files that do not go through the funnel, and CALCWRITE, browse editing, COPY, SORT and IMPORTSQL are unasserted. That count was 22 across 15 on 2026-09-08 and has come down three times: six were never writes at all — record-view turns out to be a viewer — two were real and are now gated, and one is exempt because gating it would be wrong: log replay puts back what COMMIT already accepted. Five of five is the arms that exist, not a key that is enforced everywhere; closing that is a static-analysis job, because no runtime marker can enumerate a call site. See Primary Keys.

  • Stored NULL through DML. SQLSEL INSERT/UPDATE refuse a null rather than coercing a blank. Stored nulls themselves do exist — VFP-flavour tables carry a real _NullFlags bit, byte-verified against files Visual FoxPro wrote — but the DML path does not write one.

  • Subqueries over a joined outer scope.

  • Memo-field DML.

Produced absence and stored absence are also kept apart deliberately: an unmatched outer-join row renders <UNMATCHED>, never a blank that could be mistaken for data and never a null that would claim more than the join knows.