The following is an initial comparison made by Claude of the current "Firebird Internals" documentation (v1.5) against the actual Firebird source code (latest master, v6.0).
Document reviewed: "Firebird Internals: Inside a Firebird Database" v1.5 (9 February 2026) by Norman Dunbar and Mark Rotteveel.
Reference: Firebird source code, specifically src/jrd/ods.h and src/jrd/btr.h from the current Firebird 6.x codebase (which also contains the ODS 12/13 definitions used by Firebird 3.x/4.x/5.x).
Note: The document explicitly states it describes ODS 11.1 (Firebird 2.1) with a 4,096-byte page size and includes a caution that newer ODS versions may differ. However, many structural descriptions are presented as general truths without ODS-version qualification. The errors below are cases where the document's descriptions are factually incorrect even for ODS 11.1, or where the document makes forward-looking claims that are now verifiably wrong.
Error 1: Base Page Header — pag_checksum Description Is Stale
Section: 3. Standard Database Page Header
Document says:
pag_checksum — Two bytes, unsigned. Bytes 0x02 - 0x03. Checksum for the whole page. No longer used, always 12345, 0x3039.
"From Firebird 3.0, it is possible that this field in the page header will probably have a new name and function."
What actually happened (source: ods.h, line 249):
In ODS 12.0 (Firebird 3.0), pag_checksum was removed entirely. The field at offset 2 is now pag_reserved (USHORT) — it exists solely for alignment padding and carries no semantic meaning.
Additionally, the old pag_reserved at offset 12 was renamed to pag_pageno and repurposed as a page number self-check field for validation.
The speculative language ("it is possible that" / "will probably") should be updated to reflect the actual change that was made over 10 years ago.
Source reference: src/jrd/ods.h lines 246–256:
struct pag
{
UCHAR pag_type;
UCHAR pag_flags;
USHORT pag_reserved; // not used but anyway present because of alignment rules
ULONG pag_generation;
ULONG pag_scn;
ULONG pag_pageno; // for validation
};
Error 2: Base Page Header — Missing crypted_page Flag
Section: 3. Standard Database Page Header
Document says: The pag_flags byte "holds various flags for the page" (no specifics given for the base header level).
What's missing: Since ODS 12.0, bit 7 (0x80) of pag_flags is used as the crypted_page flag on all page types. When set, it indicates the page's data (everything after the 16-byte pag header) is encrypted on disk.
Source reference: src/jrd/ods.h line 242:
inline constexpr UCHAR crypted_page = 0x80;
Error 3: Page Type 0x0a — Described as "Write Ahead Log Page"
Section: 3. Standard Database Page Header (page type table) & Section 13
Document says:
0x0a — Page 2 of any database is a Write Ahead Log page. These pages are no longer used.
"From Firebird 3.0 it is likely there will not be a WAL page in any new databases."
Actual: In ODS 12.0 (Firebird 3.0), page type 10 (0x0a) was repurposed as pag_scns — the SCN Inventory Page. Page 2 (FIRST_SCN_PAGE) is now always a SCN page, not a WAL page. WAL pages no longer exist at all.
Source reference: src/jrd/ods.h lines 210, 216:
inline constexpr SCHAR pag_scns = 10; // SCN's inventory page
inline constexpr ULONG FIRST_SCN_PAGE = 2;
Error 4: Header Page — Struct Layout Does Not Match ODS 12.0+
Section: 4. Database Header Page — Type 0x01
Document shows the header page struct as:
struct header_page
{
pag hdr_header;
USHORT hdr_page_size;
USHORT hdr_ods_version;
SLONG hdr_PAGES;
ULONG hdr_next_page;
SLONG hdr_oldest_transaction;
SLONG hdr_oldest_active;
SLONG hdr_next_transaction;
USHORT hdr_sequence;
USHORT hdr_flags;
SLONG hdr_creation_date[2];
SLONG hdr_attachment_id;
...
UCHAR hdr_data[1];
};
Actual (ODS 12.0+, source: ods.h lines 512–543):
The struct was completely reorganized. Key differences:
| Issue |
Document |
Actual (ODS 12.0+) |
hdr_ods_minor position |
Offset 0x3e (near end) |
Offset 20 (right after hdr_ods_version) |
| Transaction fields type |
SLONG (4 bytes) |
FB_UINT64 (8 bytes) |
hdr_attachment_id type |
SLONG (4 bytes) |
FB_UINT64 (8 bytes) |
hdr_sequence |
Present |
Removed |
hdr_next_page |
Present |
Removed |
hdr_implementation |
SSHORT |
Replaced by hdr_db_impl struct (4 bytes: cpu, os, cc, compat) |
hdr_ods_minor_original |
Present |
Removed |
hdr_bumped_transaction |
Present |
Removed |
hdr_backup_pages |
Present |
Removed |
hdr_misc[3] |
Present (12 bytes) |
Removed |
hdr_backup_mode |
In hdr_flags bits |
Separate UCHAR field at offset 24 |
hdr_shutdown_mode |
In hdr_flags bits |
Separate UCHAR field at offset 25 |
hdr_replica_mode |
N/A |
New UCHAR field at offset 26 |
hdr_guid |
N/A |
New UCHAR[16] at offset 84 |
hdr_crypt_page |
N/A |
New ULONG at offset 112 |
hdr_crypt_plugin |
N/A |
New TEXT[32] at offset 116 |
| Total fixed size |
~96 bytes |
152 bytes |
hdr_data offset |
0x60 |
0x94 (148) |
While the document does say it describes ODS 11.1, it would benefit from at least noting that the header page layout changed fundamentally in ODS 12.0.
Error 5: Header Page Flags — Incomplete and Partially Wrong for Modern ODS
Section: 4. Database Header Page — hdr_flags
Document shows hdr_shutdown_mask as 0x1080 (bits 7 and 12) packed into hdr_flags.
Actual (ODS 12.0+): Shutdown mode and backup mode are no longer packed into hdr_flags. They have their own dedicated byte fields (hdr_shutdown_mode and hdr_backup_mode).
The current hdr_flags are (ods.h lines 592–598):
| Flag |
Value |
Description |
hdr_active_shadow |
0x01 |
Active shadow file |
hdr_force_write |
0x02 |
Forced writes |
hdr_crypt_process |
0x04 |
Encryption status changing (was unused bit 2) |
hdr_no_reserve |
0x08 |
Don't reserve space for versions (was 0x20) |
hdr_SQL_dialect_3 |
0x10 |
SQL dialect 3 (was 0x100) |
hdr_read_only |
0x20 |
Read-only (was 0x200) |
hdr_encrypted |
0x40 |
Database is encrypted (new) |
Note: The flag values have been reassigned. In ODS 11.x, hdr_no_reserve was 0x20 and hdr_SQL_dialect_3 was 0x100. In ODS 12.0+, they are 0x08 and 0x10 respectively.
Error 6: Header Page Clumplets — Values Are Renumbered in ODS 12.0+
Section: 4. Database Header Page — clumplet types
Document shows HDR_sweep_interval = 0x06 and HDR_difference_file = 0x0c.
Actual (ODS 12.0+, ods.h lines 575–588):
| Value |
Document (ODS 11.x) |
Actual (ODS 12.0+) |
| 0 |
HDR_end |
HDR_end ✅ |
| 1 |
HDR_root_file_name |
HDR_root_file_name ✅ |
| 2 |
HDR_journal_server |
(commented out as HDR_file) |
| 3 |
HDR_file |
(commented out as HDR_last_page) |
| 4 |
HDR_last_page |
HDR_sweep_interval ⚠️ |
| 5 |
HDR_unlicensed |
HDR_crypt_checksum ⚠️ |
| 6 |
HDR_sweep_interval |
HDR_difference_file ⚠️ |
| 7 |
HDR_log_name |
HDR_backup_guid ⚠️ |
| 8 |
HDR_journal_file |
HDR_crypt_key ⚠️ |
| 9 |
HDR_password_file_key |
HDR_crypt_hash ⚠️ |
| 10 |
HDR_backup_info |
(commented out: HDR_db_guid) |
| 11 |
HDR_cache_file |
HDR_repl_seq ⚠️ |
| 12 |
HDR_difference_file |
(no longer exists) |
| 13 |
HDR_backup_guid |
(no longer exists) |
The clumplet numbering was completely reshuffled. Several old types were removed and new encryption/replication types were added.
Error 7: PIP Page — Missing New Fields
Section: 5. Page Inventory Page — Type 0x02
Document shows the PIP struct as:
struct page_inv_page
{
pag pip_header;
SLONG pip_min;
UCHAR pip_bits[1];
};
Actual (ODS 12.0+, ods.h lines 619–625):
struct page_inv_page
{
pag pip_header;
ULONG pip_min; // was SLONG
ULONG pip_extent; // NEW - lowest free extent
ULONG pip_used; // NEW - number of pages allocated from this PIP
UCHAR pip_bits[1];
};
The total fixed size grew from 20 bytes to 32 bytes. The pip_bits array now starts at offset 28 instead of 20. pip_min changed from SLONG to ULONG.
Error 8: Pointer Page — ppg_max_space Field
Section: 7. Pointer Page — Type 0x04
Document says: ppg_max_space exists at offset 0x1e, "intended to indicate the last entry in the ppg_page array holding a page number which has free space in the page, but it has never been used."
Actual (ODS 12.0+, ods.h lines 690–699): The ppg_max_space field has been removed entirely. The pointer page struct no longer contains this field. ppg_page[] now starts at offset 32 (was 0x20).
Also, the per-data-page bitmaps at the end of the pointer page now use 8 bits (one full byte) per data page instead of the 2-bit encoding described in the document. The flags are (ods.h lines 713–717):
| Flag |
Value |
Description |
ppg_dp_full |
0x01 |
Data page is FULL |
ppg_dp_large |
0x02 |
Large object on data page |
ppg_dp_swept |
0x04 |
Sweep has nothing to do (new) |
ppg_dp_secondary |
0x08 |
Primary record versions not stored (new) |
ppg_dp_empty |
0x10 |
Data page is empty (new) |
Error 9: Data Page — Offsets Are Wrong for ODS 12.0+
Section: 8. Data Page — Type 0x05
Document says:
dpg_sequence — Offset 0x10 on the page
dpg_relation — Offset 0x14 on the page
dpg_count — Offset 0x16 on the page
dpg_rpt — begins at offset 0x18 on the page
Actual (ODS 12.0+, ods.h lines 341–352):
The offsets are still correct (base pag header is still 16 bytes, so data page fields start at offset 16), but:
dpg_sequence is now ULONG (was SLONG) — offset 0x10 ✅
dpg_relation — offset 0x14 ✅
dpg_count — offset 0x16 ✅
dpg_rpt — offset 0x18 ✅
The offsets happen to still be correct. However, the document says dpg_rpt is at offset 0x18, while in version 1.2 of the document there was an off-by-2 error that was already fixed.
The new flags dpg_swept (0x08) and dpg_secondary (0x10) are not mentioned.
Error 10: Record Header — Missing rhde Struct and New Flags
Section: 8.1. Record Header
Document shows only rhd (unfragmented) and rhdf (fragmented) record headers.
What's missing (ODS 12.0+, ods.h lines 794–804):
There is a third record header variant: rhde — the extended record header used when rhd_long_tranum (0x400) is set. This struct has a rhde_tra_high field (USHORT) that stores the upper 16 bits of a 48-bit transaction number.
Similarly, rhdf now also includes rhdf_tra_high between rhdf_format and rhdf_f_page.
New record flags not documented:
| Flag |
Value |
Description |
rhd_uk_modified |
0x200 |
Unique key field values changed |
rhd_long_tranum |
0x400 |
Transaction number is 64-bit (use rhde/rhdf) |
rhd_not_packed |
0x800 |
Record stored as-is (no RLE compression) |
Source reference: src/jrd/ods.h lines 886–897.
Error 11: Index Root Page — Struct Layout Significantly Changed
Section: 9. Index Root Page — Type 0x06
Document shows irt_repeat as 12 bytes (0x0b in the text — which is actually 11, likely a typo):
struct irt_repeat {
SLONG irt_root;
union {
float irt_selectivity;
SLONG irt_transaction;
} irt_stuff;
USHORT irt_desc;
UCHAR irt_keys;
UCHAR irt_flags;
};
Actual (ODS 12.0+, ods.h lines 393–421): irt_repeat is now 24 bytes:
| Offset |
Field |
Type |
Size |
| 0 |
irt_transaction |
FB_UINT64 |
8 |
| 8 |
irt_page_num |
ULONG |
4 |
| 12 |
irt_page_space_id |
ULONG |
4 |
| 16 |
irt_desc |
USHORT |
2 |
| 18 |
irt_flags |
USHORT |
2 (was UCHAR) |
| 20 |
irt_state |
UCHAR |
1 (new) |
| 21 |
irt_keys |
UCHAR |
1 |
| 22 |
irt_dummy |
USHORT |
2 (alignment) |
Key changes:
irt_root replaced by irt_page_num + irt_page_space_id
irt_selectivity removed from the repeat struct (lives only in key descriptors irtd)
irt_transaction is now FB_UINT64 (was SLONG in union)
irt_flags widened from UCHAR to USHORT
- New
irt_state field replaces the old in-progress behavior
- New flag:
irt_condition (0x20) for conditional indices
The document also says descriptors are "0x0b bytes long" — this should be 12 bytes for ODS 11.x (4+4+2+1+1 = 12), and is 24 bytes for ODS 12.0+.
Error 12: Index Root Page — Index Flag Values
Section: 9. Index Root Page — irt_flags
Document shows:
Bit 3: Index is a foreign key index
Bit 4: Index is a primary key index
Actual (ODS 12.0+, ods.h lines 442–447):
inline constexpr USHORT irt_unique = 1; // bit 0
inline constexpr USHORT irt_descending = 2; // bit 1
inline constexpr USHORT irt_foreign = 4; // bit 2 ← was bit 3 in document
inline constexpr USHORT irt_primary = 8; // bit 3 ← was bit 4 in document
inline constexpr USHORT irt_expression = 16; // bit 4 ← was bit 5 in document
inline constexpr USHORT irt_condition = 32; // bit 5 ← NEW
The document has the foreign key flag at bit 3 (value 8) and primary key at bit 4 (value 16). The actual source code has them at bit 2 (value 4) and bit 3 (value 8) respectively. The document's bit numbering appears to have been wrong even for ODS 11.x — these flag values have not changed between ODS versions.
Cross-checking with btr.h (lines 112–117) confirms:
inline constexpr int idx_unique = 1;
inline constexpr int idx_descending = 2;
inline constexpr int idx_foreign = 4;
inline constexpr int idx_primary = 8;
inline constexpr int idx_expression = 16;
inline constexpr int idx_condition = 32;
However, looking at the old fbdump source code, the old fbdump also used these same values (bit 2 = in_progress, bit 3 = foreign, bit 4 = primary, bit 5 = expression), and its output correctly shows "Foreign Key" for flag value 8 and "Primary Key" for flag value 17. So the document's description of the bit positions is wrong, but the old fbdump code was actually correct (it tests flags & 4 for "Creating", flags & 8 for "Foreign Key", flags & 16 for "Primary Key").
Wait — re-reading the document more carefully, it says:
- Bit 2: "Index [creation?] is in progress"
- Bit 3: "Index is a foreign key index"
- Bit 4: "Index is a primary key index"
And the source says:
- Bit 2 (
irt_foreign = 4): Foreign key
- Bit 3 (
irt_primary = 8): Primary key
- Bit 4 (
irt_expression = 16): Expression
So the document has irt_in_progress at bit 2 where the source has irt_foreign. In the current source, the "in progress" concept is handled by irt_state, not as a flag bit. In older ODS, bit 2 was irt_in_progress (value 4). The bit assignments for foreign (bit 3) and primary (bit 4) in the document match the old ODS 11.x code.
Corrected assessment: For ODS 11.x, the document's flag assignments may have been correct. But in ODS 12.0+, irt_in_progress was removed as a flag and the remaining flags were renumbered:
irt_foreign moved from bit 3 → bit 2
irt_primary moved from bit 4 → bit 3
irt_expression moved from bit 5 → bit 4
irt_condition added at bit 5
Error 13: B-tree Page — Jump Info Structure Differs
Section: 10.2. Index Jump Info
Document says the jump info is a separate struct following the btree header at byte 0x22:
struct IndexJumpInfo
{
USHORT firstNodeOffset;
USHORT jumpAreaSize;
UCHAR jumpers;
};
And says jumpers is at "Offset 0x05 in the structure" (this is wrong — it should be offset 0x04 within the 5-byte struct).
Actual (ODS 12.0+, ods.h lines 293–312):
The jump info fields are now integrated directly into the btree_page struct:
struct btree_page
{
pag btr_header; // offset 0, 16 bytes
ULONG btr_sibling; // offset 16
ULONG btr_left_sibling; // offset 20
SLONG btr_prefix_total; // offset 24
USHORT btr_relation; // offset 28
USHORT btr_length; // offset 30
UCHAR btr_id; // offset 32
UCHAR btr_level; // offset 33
USHORT btr_jump_interval; // offset 34 ← replaces firstNodeOffset
USHORT btr_jump_size; // offset 36 ← replaces jumpAreaSize
UCHAR btr_jump_count; // offset 38 ← replaces jumpers
UCHAR btr_nodes[1]; // offset 39 ← node data starts here
};
There is no separate IndexJumpInfo struct. The three jump fields are regular members of btree_page. Also:
- The field formerly called
firstNodeOffset is now btr_jump_interval (jump interval between nodes, different semantic)
jumpAreaSize → btr_jump_size
jumpers → btr_jump_count
- Node data starts at offset 39 (was 0x27 = 39 if you add the 5-byte jump info to 0x22, so the byte offset is coincidentally the same but the structural approach is different)
Error 14: B-tree Page — pag_flags Bit Numbering
Section: 10.1. B-Tree Header — pag_flags
Document says:
Bit 3: set means that this page/bucket is part of a descending index
Bit 4: set means that non-leaf nodes will contain record number information
Bit 5: set means that large keys are permitted/used
Bit 6: set means that the page contains index jump nodes
Actual (ODS 12.0+, ods.h lines 331–337):
The old B-tree page flags are all commented out in the current source:
//const UCHAR btr_dont_gc = 1; // Don't garbage-collect this page
//const UCHAR btr_descending = 2; // Page/bucket is part of a descending index
//const UCHAR btr_jump_info = 16; // AB: 2003-index-structure enhancement
inline constexpr UCHAR btr_released = 32; // Page was released from b-tree
The only remaining active flag is btr_released = 32 (bit 5). The descending flag was at bit 1 (value 2), not bit 3 as the document states. The document's bit 3 = "descending" is wrong — btr_descending = 2 = bit 1.
Note: the old fbdump source code at fbdump.c line ~822 also has the descending flag at bit 2 (value 4), which also doesn't match the Firebird source's value of 2.
Error 15: Generator Page — gpg_waste Fields Changed
Section: 12. Generator Page — Type 0x09
Document shows:
struct generator_page
{
pag gpg_header;
SLONG gpg_sequence;
SLONG gpg_waste1;
USHORT gpg_waste2;
USHORT gpg_waste3;
USHORT gpg_waste4;
USHORT gpg_waste5;
SINT64 gpg_values[1];
};
Actual (ODS 12.0+, ods.h lines 752–758):
struct generator_page
{
pag gpg_header;
ULONG gpg_sequence; // was SLONG
ULONG gpg_dummy1; // single dummy for alignment
SINT64 gpg_values[1]; // starts at offset 24
};
The five waste fields (gpg_waste1 through gpg_waste5, totaling 12 bytes) have been replaced by a single gpg_dummy1 (ULONG, 4 bytes). This means gpg_values[] now starts at offset 24 instead of offset 32. The capacity per page is therefore higher in ODS 12.0+: (pageSize - 24) / 8 generators per page (vs. the old (pageSize - 32) / 8).
For an 8192-byte page: 1021 generators per page (ODS 12.0+) vs. 1020 (ODS 11.x with 4-byte-aligned start at offset 32 — though the document says the formula is (page_size - 32) / 8 which gives 508 for 4K pages).
Error 16: Index Jump Node — Struct Contains Pointers (Not On-Disk)
Section: 10.3. Index Jump Nodes
Document shows:
struct IndexJumpNode
{
UCHAR* nodePointer;
USHORT prefix;
USHORT length;
USHORT offset;
UCHAR* data;
};
Issue: This struct contains pointers (UCHAR* nodePointer and UCHAR* data). These are in-memory runtime structures, not on-disk formats. Pointers are 4 or 8 bytes depending on platform and are meaningless on disk. The actual on-disk representation of jump nodes is a packed sequence of: prefix (variable-length encoded), length (variable-length encoded), offset (variable-length encoded), and data bytes — not a fixed struct with pointers.
The document presents this as if it were the on-disk layout, which is misleading.
Summary
| # |
Section |
Severity |
Category |
| 1 |
Base pag header — checksum |
Medium |
Stale/speculative text |
| 2 |
Base pag header — crypted_page flag |
Low |
Missing info |
| 3 |
Page type 0x0a — WAL vs SCN |
High |
Factually wrong for ODS 12+ |
| 4 |
Header page — struct layout |
High |
Completely different in ODS 12+ |
| 5 |
Header page — flag values |
High |
Values reassigned in ODS 12+ |
| 6 |
Header page — clumplet values |
High |
Renumbered in ODS 12+ |
| 7 |
PIP page — missing fields |
Medium |
Missing pip_extent, pip_used |
| 8 |
Pointer page — ppg_max_space |
Medium |
Removed; per-page bits changed from 2→8 |
| 9 |
Data page — new flags |
Low |
Missing dpg_swept, dpg_secondary |
| 10 |
Record header — missing rhde |
Medium |
Missing struct and new flags |
| 11 |
Index root — irt_repeat size |
High |
24 bytes, not 12 |
| 12 |
Index root — flag bit positions |
Medium |
Renumbered in ODS 12+ |
| 13 |
B-tree page — jump info |
Medium |
Integrated into struct |
| 14 |
B-tree page — pag_flags bits |
Medium |
Wrong bit for descending |
| 15 |
Generator page — waste fields |
Medium |
Reduced; gpg_values offset changed |
| 16 |
Jump node — pointer fields |
Low |
Runtime struct, not on-disk format |
The following is an initial comparison made by Claude of the current "Firebird Internals" documentation (v1.5) against the actual Firebird source code (latest
master, v6.0).Document reviewed: "Firebird Internals: Inside a Firebird Database" v1.5 (9 February 2026) by Norman Dunbar and Mark Rotteveel.
Reference: Firebird source code, specifically
src/jrd/ods.handsrc/jrd/btr.hfrom the current Firebird 6.x codebase (which also contains the ODS 12/13 definitions used by Firebird 3.x/4.x/5.x).Error 1: Base Page Header —
pag_checksumDescription Is StaleSection: 3. Standard Database Page Header
Document says:
What actually happened (source: ods.h, line 249):
In ODS 12.0 (Firebird 3.0),
pag_checksumwas removed entirely. The field at offset 2 is nowpag_reserved(USHORT) — it exists solely for alignment padding and carries no semantic meaning.Additionally, the old
pag_reservedat offset 12 was renamed topag_pagenoand repurposed as a page number self-check field for validation.The speculative language ("it is possible that" / "will probably") should be updated to reflect the actual change that was made over 10 years ago.
Source reference:
src/jrd/ods.hlines 246–256:Error 2: Base Page Header — Missing
crypted_pageFlagSection: 3. Standard Database Page Header
Document says: The
pag_flagsbyte "holds various flags for the page" (no specifics given for the base header level).What's missing: Since ODS 12.0, bit 7 (0x80) of
pag_flagsis used as thecrypted_pageflag on all page types. When set, it indicates the page's data (everything after the 16-byte pag header) is encrypted on disk.Source reference:
src/jrd/ods.hline 242:Error 3: Page Type 0x0a — Described as "Write Ahead Log Page"
Section: 3. Standard Database Page Header (page type table) & Section 13
Document says:
Actual: In ODS 12.0 (Firebird 3.0), page type 10 (0x0a) was repurposed as
pag_scns— the SCN Inventory Page. Page 2 (FIRST_SCN_PAGE) is now always a SCN page, not a WAL page. WAL pages no longer exist at all.Source reference:
src/jrd/ods.hlines 210, 216:Error 4: Header Page — Struct Layout Does Not Match ODS 12.0+
Section: 4. Database Header Page — Type 0x01
Document shows the header page struct as:
Actual (ODS 12.0+, source: ods.h lines 512–543):
The struct was completely reorganized. Key differences:
hdr_ods_minorpositionhdr_ods_version)hdr_attachment_idtypehdr_sequencehdr_next_pagehdr_implementationhdr_db_implstruct (4 bytes: cpu, os, cc, compat)hdr_ods_minor_originalhdr_bumped_transactionhdr_backup_pageshdr_misc[3]hdr_backup_modehdr_flagsbitshdr_shutdown_modehdr_flagsbitshdr_replica_modehdr_guidhdr_crypt_pagehdr_crypt_pluginhdr_dataoffsetWhile the document does say it describes ODS 11.1, it would benefit from at least noting that the header page layout changed fundamentally in ODS 12.0.
Error 5: Header Page Flags — Incomplete and Partially Wrong for Modern ODS
Section: 4. Database Header Page —
hdr_flagsDocument shows
hdr_shutdown_maskas0x1080 (bits 7 and 12)packed intohdr_flags.Actual (ODS 12.0+): Shutdown mode and backup mode are no longer packed into
hdr_flags. They have their own dedicated byte fields (hdr_shutdown_modeandhdr_backup_mode).The current
hdr_flagsare (ods.h lines 592–598):hdr_active_shadowhdr_force_writehdr_crypt_processhdr_no_reservehdr_SQL_dialect_3hdr_read_onlyhdr_encryptedNote: The flag values have been reassigned. In ODS 11.x,
hdr_no_reservewas 0x20 andhdr_SQL_dialect_3was 0x100. In ODS 12.0+, they are 0x08 and 0x10 respectively.Error 6: Header Page Clumplets — Values Are Renumbered in ODS 12.0+
Section: 4. Database Header Page — clumplet types
Document shows
HDR_sweep_interval= 0x06 andHDR_difference_file= 0x0c.Actual (ODS 12.0+, ods.h lines 575–588):
HDR_endHDR_end✅HDR_root_file_nameHDR_root_file_name✅HDR_journal_serverHDR_file)HDR_fileHDR_last_page)HDR_last_pageHDR_sweep_intervalHDR_unlicensedHDR_crypt_checksumHDR_sweep_intervalHDR_difference_fileHDR_log_nameHDR_backup_guidHDR_journal_fileHDR_crypt_keyHDR_password_file_keyHDR_crypt_hashHDR_backup_infoHDR_db_guid)HDR_cache_fileHDR_repl_seqHDR_difference_fileHDR_backup_guidThe clumplet numbering was completely reshuffled. Several old types were removed and new encryption/replication types were added.
Error 7: PIP Page — Missing New Fields
Section: 5. Page Inventory Page — Type 0x02
Document shows the PIP struct as:
Actual (ODS 12.0+, ods.h lines 619–625):
The total fixed size grew from 20 bytes to 32 bytes. The
pip_bitsarray now starts at offset 28 instead of 20.pip_minchanged from SLONG to ULONG.Error 8: Pointer Page —
ppg_max_spaceFieldSection: 7. Pointer Page — Type 0x04
Document says:
ppg_max_spaceexists at offset 0x1e, "intended to indicate the last entry in the ppg_page array holding a page number which has free space in the page, but it has never been used."Actual (ODS 12.0+, ods.h lines 690–699): The
ppg_max_spacefield has been removed entirely. The pointer page struct no longer contains this field.ppg_page[]now starts at offset 32 (was 0x20).Also, the per-data-page bitmaps at the end of the pointer page now use 8 bits (one full byte) per data page instead of the 2-bit encoding described in the document. The flags are (ods.h lines 713–717):
ppg_dp_fullppg_dp_largeppg_dp_sweptppg_dp_secondaryppg_dp_emptyError 9: Data Page — Offsets Are Wrong for ODS 12.0+
Section: 8. Data Page — Type 0x05
Document says:
Actual (ODS 12.0+, ods.h lines 341–352):
The offsets are still correct (base pag header is still 16 bytes, so data page fields start at offset 16), but:
dpg_sequenceis now ULONG (was SLONG) — offset 0x10 ✅dpg_relation— offset 0x14 ✅dpg_count— offset 0x16 ✅dpg_rpt— offset 0x18 ✅The offsets happen to still be correct. However, the document says
dpg_rptis at offset 0x18, while in version 1.2 of the document there was an off-by-2 error that was already fixed.The new flags
dpg_swept(0x08) anddpg_secondary(0x10) are not mentioned.Error 10: Record Header — Missing
rhdeStruct and New FlagsSection: 8.1. Record Header
Document shows only
rhd(unfragmented) andrhdf(fragmented) record headers.What's missing (ODS 12.0+, ods.h lines 794–804):
There is a third record header variant:
rhde— the extended record header used whenrhd_long_tranum(0x400) is set. This struct has arhde_tra_highfield (USHORT) that stores the upper 16 bits of a 48-bit transaction number.Similarly,
rhdfnow also includesrhdf_tra_highbetweenrhdf_formatandrhdf_f_page.New record flags not documented:
rhd_uk_modifiedrhd_long_tranumrhde/rhdf)rhd_not_packedSource reference:
src/jrd/ods.hlines 886–897.Error 11: Index Root Page — Struct Layout Significantly Changed
Section: 9. Index Root Page — Type 0x06
Document shows
irt_repeatas 12 bytes (0x0bin the text — which is actually 11, likely a typo):Actual (ODS 12.0+, ods.h lines 393–421):
irt_repeatis now 24 bytes:irt_transactionirt_page_numirt_page_space_idirt_descirt_flagsirt_stateirt_keysirt_dummyKey changes:
irt_rootreplaced byirt_page_num+irt_page_space_idirt_selectivityremoved from the repeat struct (lives only in key descriptorsirtd)irt_transactionis now FB_UINT64 (was SLONG in union)irt_flagswidened from UCHAR to USHORTirt_statefield replaces the old in-progress behaviorirt_condition(0x20) for conditional indicesThe document also says descriptors are "
0x0bbytes long" — this should be 12 bytes for ODS 11.x (4+4+2+1+1 = 12), and is 24 bytes for ODS 12.0+.Error 12: Index Root Page — Index Flag Values
Section: 9. Index Root Page —
irt_flagsDocument shows:
Actual (ODS 12.0+, ods.h lines 442–447):
The document has the foreign key flag at bit 3 (value 8) and primary key at bit 4 (value 16). The actual source code has them at bit 2 (value 4) and bit 3 (value 8) respectively. The document's bit numbering appears to have been wrong even for ODS 11.x — these flag values have not changed between ODS versions.
Cross-checking with
btr.h(lines 112–117) confirms:However, looking at the old fbdump source code, the old fbdump also used these same values (bit 2 = in_progress, bit 3 = foreign, bit 4 = primary, bit 5 = expression), and its output correctly shows "Foreign Key" for flag value 8 and "Primary Key" for flag value 17. So the document's description of the bit positions is wrong, but the old fbdump code was actually correct (it tests
flags & 4for "Creating",flags & 8for "Foreign Key",flags & 16for "Primary Key").Wait — re-reading the document more carefully, it says:
And the source says:
irt_foreign = 4): Foreign keyirt_primary = 8): Primary keyirt_expression = 16): ExpressionSo the document has
irt_in_progressat bit 2 where the source hasirt_foreign. In the current source, the "in progress" concept is handled byirt_state, not as a flag bit. In older ODS, bit 2 wasirt_in_progress(value 4). The bit assignments for foreign (bit 3) and primary (bit 4) in the document match the old ODS 11.x code.Corrected assessment: For ODS 11.x, the document's flag assignments may have been correct. But in ODS 12.0+,
irt_in_progresswas removed as a flag and the remaining flags were renumbered:irt_foreignmoved from bit 3 → bit 2irt_primarymoved from bit 4 → bit 3irt_expressionmoved from bit 5 → bit 4irt_conditionadded at bit 5Error 13: B-tree Page — Jump Info Structure Differs
Section: 10.2. Index Jump Info
Document says the jump info is a separate struct following the btree header at byte 0x22:
And says
jumpersis at "Offset 0x05 in the structure" (this is wrong — it should be offset 0x04 within the 5-byte struct).Actual (ODS 12.0+, ods.h lines 293–312):
The jump info fields are now integrated directly into the
btree_pagestruct:There is no separate
IndexJumpInfostruct. The three jump fields are regular members ofbtree_page. Also:firstNodeOffsetis nowbtr_jump_interval(jump interval between nodes, different semantic)jumpAreaSize→btr_jump_sizejumpers→btr_jump_countError 14: B-tree Page —
pag_flagsBit NumberingSection: 10.1. B-Tree Header —
pag_flagsDocument says:
Actual (ODS 12.0+, ods.h lines 331–337):
The old B-tree page flags are all commented out in the current source:
The only remaining active flag is
btr_released = 32(bit 5). The descending flag was at bit 1 (value 2), not bit 3 as the document states. The document's bit 3 = "descending" is wrong —btr_descending= 2 = bit 1.Note: the old fbdump source code at
fbdump.cline ~822 also has the descending flag at bit 2 (value 4), which also doesn't match the Firebird source's value of 2.Error 15: Generator Page —
gpg_wasteFields ChangedSection: 12. Generator Page — Type 0x09
Document shows:
Actual (ODS 12.0+, ods.h lines 752–758):
The five waste fields (
gpg_waste1throughgpg_waste5, totaling 12 bytes) have been replaced by a singlegpg_dummy1(ULONG, 4 bytes). This meansgpg_values[]now starts at offset 24 instead of offset 32. The capacity per page is therefore higher in ODS 12.0+:(pageSize - 24) / 8generators per page (vs. the old(pageSize - 32) / 8).For an 8192-byte page: 1021 generators per page (ODS 12.0+) vs. 1020 (ODS 11.x with 4-byte-aligned start at offset 32 — though the document says the formula is
(page_size - 32) / 8which gives 508 for 4K pages).Error 16: Index Jump Node — Struct Contains Pointers (Not On-Disk)
Section: 10.3. Index Jump Nodes
Document shows:
Issue: This struct contains pointers (
UCHAR* nodePointerandUCHAR* data). These are in-memory runtime structures, not on-disk formats. Pointers are 4 or 8 bytes depending on platform and are meaningless on disk. The actual on-disk representation of jump nodes is a packed sequence of: prefix (variable-length encoded), length (variable-length encoded), offset (variable-length encoded), and data bytes — not a fixed struct with pointers.The document presents this as if it were the on-disk layout, which is misleading.
Summary
pip_extent,pip_useddpg_swept,dpg_secondaryrhde