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
| Capability | What it means here |
|---|---|
| Storage engine | Its 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. |
| Indexing | Native 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. |
| Concurrency | Cross-process cooperative record and table locks, pid-stamped, with stale-owner recovery. |
| Transactions | Buffered 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. |
| Catalogs | The engine's own metadata tables describe its commands, functions, messages and workspaces — the map drawn in the same ink as the territory. |
| Sessions and scope | Work 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, andORDER BYwith independent per-column directions INNER,LEFT,RIGHT,FULLandCROSSjoins; self-joins through aliases over a single physical table; composite booleanON; and three-tableINNER/LEFTchainsCOUNT,SUM,AVG,MIN,MAXwithGROUP BYandHAVINGUNION,UNION ALL,INTERSECT,EXCEPT- subqueries: scalar,
IN,NOT IN,EXISTSandNOT EXISTS, both uncorrelated and correlated — including double-NOT EXISTSrelational 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,DELETEwith 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
SQLSELreads committed table truth whileTUPLEpreviews 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
PKPOLICYasserts. A static gate counts 13 direct field-write call sites across 11 files that do not go through the funnel, andCALCWRITE, browse editing,COPY,SORTandIMPORTSQLare 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 whatCOMMITalready 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
NULLthrough DML.SQLSEL INSERT/UPDATErefuse a null rather than coercing a blank. Stored nulls themselves do exist — VFP-flavour tables carry a real_NullFlagsbit, 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.