OpenADS native Data Dictionary format (.add, OADD)
Status: v1 (2026-07-15). This is a first-class, OpenADS-owned format. Unlike
the SAP ADS .add binary (which OpenADS could only partially reverse-engineer),
everything here is specified, so a reader never guesses — it reads exactly
what a writer wrote.
Why it exists
The migration tool import_dd reads a SAP dictionary through the SAP ACE runtime
(which decodes SAP’s format completely and correctly) and writes an OpenADS
dictionary in this format. OpenADS then reads and writes only this format. The
SAP-binary reader is therefore an import-time concern, never a runtime one.
Design goals
- Self-describing. Every section carries its column names inline; every value carries its length. No fixed offsets, no property blobs, no encryption.
- Forward compatible. Each section and each value is length-prefixed, so a reader can skip an unknown section or ignore an extra column without failing.
- Catalog-shaped. The dictionary is stored as a set of tables — one section
per
system.*catalog — soSELECT * FROM system.Xprojects a section directly, and a native DD is byte-inspectable against SAP’s catalog output. - String values. Every value is stored as UTF-8 text, matching the CICHAR
columns the ADS
system.*catalogs expose (Table_Type="3",Index_Options="2051", etc.). Consumers that need a number parse it.
Encoding primitives
All integers are little-endian. All text is UTF-8.
| Primitive | Layout |
|---|---|
u16 |
2 bytes LE |
u32 |
4 bytes LE |
str |
u32 length followed by length UTF-8 bytes (no terminator) |
sstr |
u16 length followed by length UTF-8 bytes (for names) |
File layout
Header (12 bytes):
0..3 magic = "OADD" (0x4F 0x41 0x44 0x44)
4..5 u16 version = 1
6..7 u16 flags = 0 (reserved; readers must ignore unknown bits)
8..11 u32 nsections
Then `nsections` sections, in order:
Section:
sstr name section name, lower-case (e.g. "indexes")
u32 body_len number of bytes in `body` (enables skip)
body (exactly body_len bytes):
u32 ncols
ncols x sstr column_name the section's column schema, in order
u32 nrows
nrows x ( ncols x str value ) row-major values, one str per column
A reader that does not recognise a section name reads body_len and skips the
body. A reader that finds extra columns beyond the ones it maps ignores them; a
reader that finds a column missing treats it as empty. This is how the format
evolves without a hard version break.
Sections (v1)
One section per catalog. Column names are exactly the ADS system.* column
names (SAP casing), so the system.* projection is a pass-through.
| Section | Mirrors | Key columns (not exhaustive) |
|---|---|---|
dbprops |
database properties | Key, Value |
tables |
system.tables |
Name, Table_Relative_Path, Table_Type, Table_Primary_Key, Table_Default_Index, Table_Validation_Expr, … |
columns |
system.columns |
Parent, Field_Name, Field_Type, Field_Length, Field_Decimal, … |
indexes |
system.indexes |
Name, Parent, Index_File_Name, Index_Expression, Index_Condition, Index_Options, Index_Key_Length, Index_Collation, Comment |
relations |
system.relations |
Name, Parent, Child, Parent_Tag, Child_Tag, Update_Option, Delete_Option, … |
triggers |
system.triggers |
Name, Parent, Trigger_Type, Event, Container, Procedure, … |
procedures |
system.storedprocedures |
Name, Procedure_Input, Procedure_Output, Procedure_Body, … |
functions |
system.functions |
Name, Return_Type, Function_Input, Function_Body |
views |
system.views |
Name, View_Definition |
users |
system.users |
Name, … |
usergroups |
system.usergroups |
Name, … |
usergroupmembers |
system.usergroupmembers |
Group_Name, User_Name |
permissions |
system.permissions |
Grantee_Name, Object_Name, Object_Type, Permissions (bitmask, text) |
The runtime DataDict maps a subset of these columns into its typed model for
navigation and enforcement (table paths, index files/expressions, RI rules,
permission bitmasks, trigger bindings); the full column set is preserved for the
catalog projection. Nothing is lost on round-trip: save() writes back every
section it read plus any the runtime modified.
Integrity
The header magic + version identify the format. DataDict::open() sniffs the
first 4 bytes:
OADD→ native reader.- anything else → OpenADS does NOT attempt to read it. It returns a clear
“this dictionary must be imported” error (
AE_SAP_PERMS_NEED_IMPORT/ 5174) with instructions to runimport_dd. OpenADS never parses a SAP.addat runtime; SAP-format reading lives only insideimport_dd, which uses the SAP ACE runtime to do it.
There is no checksum in v1; the file is produced by trusted tooling and validated structurally on load (every length must stay within the enclosing bound, or the load fails cleanly).
Maintenance
This document is the source of truth for the format and is kept in lock-step
with src/engine/dd_native.{h,cpp}. Any change to the on-disk layout bumps the
u16 version, is described here, and — because sections and values are
length-prefixed — old readers keep working by skipping what they do not know.