This page is a lesson first and a specification matrix second. The important question is not only "what is the maximum?" The real question is:
Which part of the system still decides the maximum?
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
A table format can put a 64-bit record count in the header, but the system is not truly x64 all the way if navigation, locks, memo pointers, index payloads, or GUI APIs still pass record numbers through 16-bit or 32-bit slots.
Why x64 all the way?
Classic DBF-family files are beautifully simple: a header, field descriptors,
fixed-length records, optional memo sidecars, and indexes. That simplicity also
means the math is visible. If a header field is 16 bits wide, its maximum is
bounded by 2^16 - 1. If a record counter is 32 bits wide, its natural ceiling
is 2^32 - 1, and many products impose lower practical limits for file size,
locking, compatibility, or runtime memory.
Going x64 changes the shape of the possible database:
| Width | Unsigned maximum | What it means in a DBF-style engine |
|---|---|---|
| 8-bit | 255 | Classic one-byte field lengths and flags. |
| 16-bit | 65,535 | Header length, record length, field offsets, and compact index limits in many classic paths. |
| 32-bit | 4,294,967,295 | Record numbers, file offsets, memo blocks, lock offsets, and index payloads in many older engines. |
| 64-bit | 18,446,744,073,709,551,615 | Large record counts, file offsets, memo object ids, index page addresses, and future table metadata. |
The lesson is not that every table should contain trillions of records. The lesson is that each old counter becomes a choke point unless it is widened through the whole stack.
For x64base, it helps to distinguish three different ceilings:
| Ceiling type | Current meaning in x64base |
|---|---|
| 64-bit design field | The x64 extension can store wide counts, offsets, and row geometry. |
| 16-bit compatible mirror | The VFP-style compatibility header still mirrors header size and record size through 16-bit fields, and those mirrors may saturate at 65535. |
| Parser / construction limit | Some creation surfaces still impose narrower convenience limits, such as the current CREATE X64 single-character-field cap of 4096, even though the runtime can now honor wider total row geometry. |
These are not the same thing, and the matrix below should not pretend they are.
The choke-point chain
For a DBF-style runtime to be truly 64-bit, the following layers need to agree:
- Table header - record count, record length, header length, and data-start offset.
- Record navigation -
GOTO,SKIP,RECNO, EOF/BOF, append, delete, and scan loops. - Record locking - lock-file names, lock offsets, owner records, and stale-lock cleanup.
- Memo references - in-row memo reference width and sidecar object/block addressing.
- Index payloads - key entries must carry wide record numbers and wide page or object offsets.
- Expression/runtime APIs - scalar functions, commands, GUI bindings, and scripting need wide-safe values.
- Diagnostics and manuals - reports must say whether a value is a design target, a header capability, or a runtime-proven limit.
If any one of those layers still clamps a value to 16 or 32 bits, the system may have an x64 file header but not an x64 operating envelope.
Record-number widening status (public build, 2026-07)
The record-number widening lane (internal name RECNO64) removed the 32-bit choke points listed above. The table records the current state in the public build; prior states are preserved in this site's version history.
| Choke-point layer | Current state |
|---|---|
| Table header | 64-bit record count, length, and data-start via the *64 accessors. |
| Record navigation | 64-bit positioning (gotoRec64); GO/GOTO/SKIP/RECNO/TOP/BOTTOM/FIRST/LAST/NEXT/PRIOR regression-checked (CURSOR, INDEX_X64). |
| Record locking | 64-bit — lock API and lock-file record keys widened from 32-bit. |
| Memo references | 64-bit DTX object ids, offsets, and append offsets. |
| Index payloads | Active CDX/LMDB path is 64-bit (index hooks + keyed-seek decoders); classic CNX and legacy .inx kept 32-bit by design with a capability report. |
| Expression / runtime APIs | 64-bit through the *64 accessors; the classic recno()/recLength()/cpr() accessors now return -1 past their 32-bit range (2026-07) instead of clamping to INT_MAX, so a legacy consumer sees "out of range" rather than a wrong record. |
| Diagnostics and manuals | This page and the DBF-64 specification separate design target from runtime-proven limit. |
The decisive measurement — resolving a distinct record beyond 2^31 — has been
performed two ways: a synthetic boundary unit test
(dottalkpp_recno64_boundary_test, where recno64/recCount64/recLength64
resolve distinct values at INT32_MAX, +1, +2, UINT32_MAX, UINT32_MAX+1
while the legacy accessors return -1), and a real sparse-file end-to-end test
(dottalkpp_recno64_sparse_e2e_test) that sets an x64 table's record_count to
2^31+2, writes real records at recno 1 / 2^31+1 / 2^31+2 into a sparse file — the
~18 GiB gap is a filesystem hole, a few KB physical — reopens through the engine,
and reads the two records past 2^31 distinctly off disk (in ~0.5 s). What has
not been done is materializing a fully populated multi-billion-row table, which
is a data-volume / performance question (see the Pinocchio benchmarks), not an
addressing-correctness one. The record-number path itself is proven past 2^31.
x64base current truth
x64base already has real x64 structure in the local source tree, but the honest documentation must separate format direction from current runtime limit.
Where these limits live (and why they are adjustable)
MAX_AREA, MAX_FIELDS, the record-size guardrails, and the name-length ceilings
are engineering choices, not format-forced values — the DBF_64 format could
represent larger, and these numbers are deliberately round. They are currently
centralized as compile-time constants in include/xbase.hpp (with the
table-buffer bitset width in include/cli/table_state.hpp), so raising one is a
single-point edit plus a rebuild. A natural future direction is a proper
configuration layer — runtime-tunable profile parameters in the style of
enterprise systems, or an xbase.meta descriptor — so a deployment could set its
own ceilings for MAX_RECORDS, MAX_RECORD_SIZE, MAX_FIELDS, and MAX_AREAS
without a recompile. The values below are the current constants in the public
build (2026-07); earlier builds carried the prior values.
| Capacity item | Current x64base evidence | Documentation status |
|---|---|---|
| Work areas / open table slots | include/xbase.hpp defines MAX_AREA = 512 (raised from 256 in 2026-07). | Public build; raised from 256 (2026-07). |
| Field count | include/xbase.hpp defines MAX_FIELDS = 256 (raised from 128 in 2026-07; the table-buffer kMaxFields bitsets were already 256, so this only relaxes two guard checks). | Public build; raised from 128 (2026-07). |
| Classic/VFP record count | Classic header uses signed 32-bit mirror; VFP header has a uint32_t num_recs, but some current paths mirror through signed 32-bit APIs. | Classic-compatible, with current runtime chokepoints. |
| x64 record count | include/xbase_64.hpp stores LargeHeaderExtension::record_count as uint64_t; DbArea keeps _rec_count64 and exposes recCount64(). In the public build (2026-07): navigation and command paths — GOTO/GO/SKIP/RECNO/TOP/BOTTOM/FIRST/LAST/NEXT/PRIOR — position through 64-bit gotoRec64/recno64. | 64-bit header state exists; navigation/command paths are widened and pass the CURSOR (x32/CNX) and INDEX_X64 (v64/CDX/LMDB) regression suites. The classic recno()/recLength()/recCount()/cpr() accessors now return -1 past their 32-bit range (2026-07; no longer clamp to INT_MAX), and both a synthetic unit test and a sparse-file end-to-end test read distinct records at recno 2^31+1/+2 off disk. A fully populated multi-billion-row table is a volume question, not proven here. |
| Header/data-start size | x64 extension stores data_start_64 as uint64_t, validator/open now accept a saturated 16-bit compatibility mirror through x64_compatible_u16_matches(), and runtime read/write paths consume dataStart64() directly. | Wide geometry is active; compatibility mirror still exists but no longer defines live x64 geometry. |
| Record size | x64 extension stores record_size_64 as uint64_t, validator/open now accept a saturated 16-bit compatibility mirror through x64_compatible_u16_matches(), and runtime read/write paths consume recLength64() directly. | Wide geometry is active; compatibility mirror still exists but no longer defines live x64 geometry. |
| Table names | x64 naming policy allows up to 256-byte table names (ceiling raised from 128 in 2026-07; default stays 128). | Public build ceiling 256; default stays 128. |
| Field names | x64 naming policy allows up to 256-byte field names (ceiling raised from 128 in 2026-07; default stays 128) with 10-byte descriptor fallback tokens. | Public build ceiling 256; default stays 128; fallback remains classic-compatible. |
| Field length metadata | X64FieldMetaEntry::field_length is uint32_t; FieldDef.length is also uint32_t; current CREATE X64 allows x64 character fields above 255 bytes, but one C field is still capped at 4096 in the parser. | Wider field-length metadata exists; current parser/construction limits are narrower than the x64 design lane. |
| x64 memo references | x64 memo fields use an 8-byte object-id slot; DTX memo object ids, offsets, payload bytes, and append offsets are uint64_t. | Source-evidenced DTX/x64 memo architecture. |
| Index record numbers | include/xindex/key_common.hpp defines RecNo = std::uint64_t; the active LMDB/CDX backend stores the record number as 8 bytes little-endian. In the public build (2026-07): the index-hook apply_replace chain and the CDX keyed-seek decoders (decode_recno_from_kv_, seekRecnoUserKey) were widened to 64-bit; classic CNX and legacy .inx remain 32-bit by design and report their ceiling through IIndexBackend::maxRecordNumber(). | Active path (CDX/LMDB) carries 64-bit record numbers; legacy 32-bit formats are retained by design with an explicit capability report rather than silent truncation. |
This is the exact reason the project says x64base implements an x64 DBF-style architecture with 64-bit record addressing proven end to end, while staying candid that a fully populated multi-billion-row table is a volume question — not something these boundary proofs establish.
Current boundary proof
The current canonical proof is the x64 matrix/metrics boundary canary:
dottalkpp/data/scripts/canaries/x64_matrix_metrics_boundary_canary.dts
It proves two separate x64 row-geometry cases:
- a disposable x64 table with a record length of
36,865bytes - a disposable x64 table with a record length of
69,633bytes
That second case matters because it crosses the old 65,535 compatibility-mirror
ceiling. The mirror may saturate, but the runtime now honors the wide x64
metrics from the extension instead of shrinking live geometry back to a 16-bit
active path.
Ecosystem capacity matrix
The values below are deliberately conservative. They compare published or source-evidenced limits, not sales claims.
| Capacity / specification | x64base / DotTalk++ Workbench | Visual FoxPro 9 | dBASE / BDE dBASE | Harbour / xHarbour DBF-family | Alaska Xbase++ DBFDBE | Python DBF libraries |
|---|---|---|---|---|---|---|
| Primary intent | Educational-first open architecture and x64 DBF-style research system | Mature desktop database/runtime | Commercial DBF database/tooling lineage | Open Clipper/xBase compiler/runtime ecosystem | Commercial modernization/runtime platform | Utility libraries for reading/writing DBF files |
| Open table/work areas | 512 work areas (MAX_AREA, raised from 256 in 2026-07; public build 512) | 65,535 tables open at one time, memory/file-handle limited | 256-512 open dBASE tables per system depending BDE generation | Implementation/runtime dependent | Runtime/DBE dependent | Not a work-area engine |
| Records per table | x64 header stores a 64-bit record count; navigation and command paths were widened to 64-bit (2026-07) and pass the CURSOR and INDEX_X64 regression suites. The classic recno() accessor now returns -1 past 2^31 - 1 (no longer clamps); proven by a unit test and a sparse-file end-to-end test that reads distinct records at recno 2^31+1/+2 off disk. A fully populated multi-billion-row table is a volume question, not proven here. | 1 billion | 1 billion | Often described by DBF structure as up to 2^32 - 1 records, implementation dependent | (lock offset - header - 1) / record size | Follows target DBF dialect and library implementation |
| DBF/table file size | Wide-offset design target exists, and open/read/write now honor wide x64 geometry; broader whole-file-scale proof is still pending across the full stack | 2 GB table file | 2 GB table file | Format/backend discussions cite much larger theoretical DBF ceilings, but product/backend dependent | Default lock-offset model around 1 GB unless configured | Usually inherits classic/VFP file limits and Python/runtime memory constraints |
| Record size | x64 extension stores a 64-bit row size; a 16 MiB hard ceiling and 64 KiB soft advisory were added in 2026-07. The compatibility mirror may saturate at 65,535, and the current canary proves live rows at 36,865 and 69,633 bytes. CREATE X64 still caps one C field at 4096. | 65,500 characters per record | 4,000 bytes dBASE 4; 32,767 bytes dBASE for Windows | Often tied to 16-bit record-size structure, around 65,535 bytes | Fixed-length DBF rows; character fields up to 64 KB | Dialect dependent |
| Fields per table | 256 (MAX_FIELDS, raised from 128 in 2026-07; public build 256) | 255, or 254 when nullable-field overhead applies | 255 dBASE 4; 1024 dBASE for Windows | Backend/dialect dependent | Listed as not limited by DBFDBE table, but practical DBF/runtime limits apply | Dialect/library dependent |
| Character field width | Wider x64 metadata exists and current CREATE allows x64 character fields beyond 255 bytes, but practical width is still bounded by the same row-size mirrors | 254 | 254 | Dialect/backend dependent; some readers note extended 16-bit character widths | Character values max 64 KB | Library/dialect dependent; dbfread documents up to 65,535 for reused length bytes |
| Free-table field names | 10-byte fallback descriptor token; x64 metadata supports up to 256-byte names (public build 256; default 128) | 10 chars free table; 128 chars in database-contained table | dBASE/BDE table and field name size listed as 31 characters in general limits | Classic DBF-compatible fallback usually short names | DBF/Clipper-compatible rules | Dialect/library dependent |
| Memo file / object scale | DTX uses 64-bit object ids, offsets, payload sizes, and append offsets; proof lane active | 2 GB FPT file | Memo/Blob limits product dependent; BDE Paradox lists 256 MB BLOB, dBASE memo limits vary by format | Backend dependent; public discussions cite large DBT/FPT/SMT ceilings | Memo text length and memo file size limited by system resources in DBFDBE docs | Dialect/library dependent |
| Index key / index file scale | CDX/LMDB active with 64-bit record numbers (index hooks and keyed-seek decoders widened, 2026-07); classic CNX and legacy .inx kept 32-bit by design with a capability report | Compact index key 240 bytes; non-compact 100 bytes | 47 MDX tags; 220-character key expression | Backend dependent: NTX/CDX/other RDDs | Requires ORDER component such as NTXDBE/CDXDBE | Usually no native persistent index engine |
| Locking model | Record locks exist; the record-lock API and lock-file record keys were widened from 32-bit to 64-bit (2026-07) | Product/runtime managed | 100 record locks per dBASE table in listed BDE limits | Runtime/RDD dependent | DBF file size tied to record-lock offset model | Not a multi-user DBF engine |
| Internationalization | Message catalog and locale lanes in progress | Code pages/collation settings | BDE/codepage dependent | Ecosystem/runtime dependent | OEM/ANSI conversion through SET CHARSET | Python ecosystem dependent |
| SQL / relational support | SQL bridge, relation graph, workspaces, and DDL lanes active with caveats | SQL SELECT/update support in VFP runtime | SQL through BDE/tooling | Libraries and drivers | Database engines and migration tooling | Python DB/CSV/DBF tooling rather than native xBase work areas |
Student exercise: find the hidden 32-bit ceiling
Take any proposed x64 feature and ask these questions:
| Question | Why it matters |
|---|---|
| Is the value stored on disk as 64-bit? | If not, the file format itself is the ceiling. |
| Is the value kept in memory as 64-bit? | If not, large values are lost after open. |
| Do commands accept and print the value as 64-bit? | If not, scripts and diagnostics lie. |
| Do locks, indexes, and memos carry the same width? | If not, mutation and navigation break before storage does. |
| Do GUI/TUI/API bindings expose the wide value? | If not, the engine may be wider than the application surface. |
For x64base, that exercise produces real engineering lanes:
- keep wide x64 geometry authoritative even when compatibility mirrors saturate.
- audit remaining create/export/utility surfaces that still emit or assume classic-sized geometry.
- audit every
int32_tanduint32_trecord-number path. - prove x64 record navigation, append, delete, replace, lock, and scan behavior.
- prove index backends with 64-bit record payloads.
- keep fallback descriptor tokens for DBF interoperability while preserving x64 vector names.
- make the manuals report current, proven, planned, and design-target limits separately.
Source notes
- x64base local source evidence:
include/xbase.hpp,include/xbase_vfp.hpp,include/xbase_64.hpp,include/memo/dtx_format.hpp, andinclude/memo/memostore.hpp. - dBASE/BDE limits: dBASE BDE Limits.
- Visual FoxPro limits: Visual FoxPro System Capacities.
- Alaska Xbase++ DBFDBE limits: DBFDBE data component.
- Python DBF field-type note: dbfread field types.