DBF_64 is x64base's 64-bit-capacity evolution of the xBase DBF table. It keeps
the familiar self-describing header + fixed-length records, but lifts record
counts and file offsets past the classic 32-bit ceiling while staying
backward-compatible with tools that read the legacy header. This page is a
source-derived summary of the engine's own reader/writer and validator
(src/xbase/dbf_file.cpp, include/xbase/dbf_create.hpp,
src/cli/dbf64_header_validate.cpp).
Current explanatory diagrams
Diagram attachments: DIAG-X64GEOM-005, DIAG-X64SELFDESC-006
Current sources:
docs/manuals/assets/diagrams/trinity_headers_v1.svgdocs/manuals/assets/diagrams/x64_self_describing_dbf_v1.svg
Version byte
The header's version byte identifies the flavor. The engine recognizes the
classic families (0x03, 0x83, 0xF5) and Visual FoxPro (0x30, 0x31,
0x32), and adds its own DBF_VERSION_64 marker for 64-bit tables. When the
version byte is DBF_VERSION_64, the authoritative record count is the 64-bit
field (below) rather than the legacy 32-bit one.
Table header
The base header follows the VFP-style layout (version byte, last-update date,
record count, header length, and record length), so legacy readers still see a
valid DBF. DBF_64 then carries a large-header extension whose record_count
is a 64-bit value.
- The classic 32-bit record count at offset 4 is preserved and patched for compatibility (clamped when the true count exceeds a 32-bit maximum).
- The 64-bit record count in the extension is the authoritative row count for
a
DBF_VERSION_64table. - Header length and record length locate the first data byte and the fixed record stride.
Field descriptors
Each field descriptor defines a name, a type, a length, and decimal places (for
numeric types). Which type codes CREATE accepts depends on the DBF flavor.
The classic types are shared by every flavor; the binary Visual FoxPro types
(I/B/Y/T) are stored as real binary — not the fixed-width ASCII
placeholders older builds used — and are accepted by the VFP and x64 flavors.
| Type | Code | On-disk storage | dBASE / MSDOS | FoxPro 2.6 | VFP | x64 |
|---|---|---|---|---|---|---|
| Character | C | fixed-width text | ✓ | ✓ | ✓ | ✓ |
| Numeric | N | ASCII numeric | ✓ | ✓ | ✓ | ✓ |
| Float | F | ASCII numeric | ✓ | ✓ | ✓ | ✓ |
| Date | D | YYYYMMDD text | ✓ | ✓ | ✓ | ✓ |
| Logical | L | T / F / ? | ✓ | ✓ | ✓ | ✓ |
| Memo | M | sidecar reference | ✓ | ✓ | ✓ | ✓ |
| Integer | I | 4-byte little-endian int32 | — | — | ✓ | ✓ |
| Double | B | 8-byte IEEE-754 | — | — | ✓ | ✓ |
| Currency | Y | 8-byte int64, scaled 10⁴ | — | — | ✓ | ✓ |
| DateTime | T | 4-byte Julian day + 4-byte ms | — | — | ✓ | ✓ |
| Custom | (registered) | codec-defined | — | — | ✓ | ✓ |
The four binary types round-trip as their true VFP encodings — verified against an
independent, spec-based VFP reader and writer in both directions — rather than the
ASCII placeholders earlier builds stored. Custom field types register a type
code once (codec + on-disk width + flavor eligibility + display name) and then flow
through CREATE, validation, and storage with no further engine change; the
built-in binary types use that same registry.
Memo (M) fields store a reference, not the payload — see
FPT64 Memo Format.
The catalog also reserves V (Varchar), Q (Varbinary), W (Blob), G
(General), and the SQL-native extensions (BigInt, Timestamp, Uuid, …) as
planned types; these are not yet accepted by CREATE.
Records
- Fixed-length records; the stride is the header's record length.
- A leading deleted flag byte (space = live,
*= deleted) precedes the field-aligned payload. - Record positions are computed as
data_start + recno * record_lengthusing 64-bit arithmetic with overflow checks, so the positioning math itself is not the 32-bit ceiling (subject to the path-by-path status in 64-Bit Capacity Math). - A trailing
0x1Abyte marks end-of-file.
Runtime widening status (public build, 2026-07)
The on-disk layout above is 64-bit, and the runtime that operates on it has been
widened to match. In the current public build, 64-bit record positioning
(gotoRec64), the navigation commands, the record-lock API, and the active
CDX/LMDB index record numbers are widened and regression-checked (CURSOR,
INDEX_X64); the classic recno()/recLength()/recCount()/cpr() accessors
now return -1 past their 32-bit range instead of clamping to INT_MAX. A synthetic boundary
unit test and a real sparse-file end-to-end test resolve distinct record numbers
past 2^31 — the engine reads records at recno 2^31+1/+2 off a sparse x64 table
whose record_count is 2^31+2 — so 64-bit record addressing is proven past
2^31. Materializing a fully populated multi-billion-row table is a separate
data-volume question. The path-by-path status lives in
64-Bit Capacity Math.