Context
Every typed xsd:decimal routes to the per-(graph, predicate) NumBig arena — even \"1\"^^xsd:decimal (fluree-db-indexer/src/run_index/resolve/resolver.rs, DecimalStr arm). That makes decimals handle-keyed and equality-only, costs an arena round-trip on decode, and is the root reason novelty decimals are hard to translate into V3 overlay ops (see #1324).
Integers already have the right shape: i64-fitting values encode inline (NUM_INT), only overflow goes to the arena. Decimals deserve the same split.
Proposal
Canonical xsd:decimal (trailing zeros stripped) gives every value a unique (unscaled mantissa, scale) pair, so a packed inline o_key is well-defined, e.g.:
1 bit sign | 6 bits scale (0–63) | 57 bits |mantissa| fits-check: |m| < 2^56, scale <= 63
17 significant digits with up to 63 decimal places — fixed-point money (scale 2) inlines to ±$7e14. Decode rebuilds the exact BigDecimal, so precision math is unaffected. Values that don't fit keep the arena path, exactly like overflow integers.
Two design decisions to settle in spec:
- Equality-keyed vs order-preserving. Equality-keyed (packed canonical form) matches current decimal semantics — decimals already compare by decode, never by o_key order. An order-preserving variant (normalized scientific form: sign, biased exponent, mantissa) would additionally enable range/FILTER pushdown on decimals, at meaningfully higher design risk.
- Migration.
(o_type, o_key) is persisted fact identity: the same value must never exist under two encodings within one index root, or retracts re-encoding inline would fail to match arena-stored base rows. This requires an index-format version gate with reindex as the migration path, and a sweep through resolver spool kinds, the o_type registry, decode paths, stats hooks, overlay translation, and filter-pushdown gating (which must not assume numeric order unless the order-preserving variant is chosen).
Out of scope
This is independent of #1324 (which needs no format change and covers existing ledgers); inline decimals are the long-term fix that removes the arena dependency for the common case.
Context
Every typed
xsd:decimalroutes to the per-(graph, predicate) NumBig arena — even\"1\"^^xsd:decimal(fluree-db-indexer/src/run_index/resolve/resolver.rs,DecimalStrarm). That makes decimals handle-keyed and equality-only, costs an arena round-trip on decode, and is the root reason novelty decimals are hard to translate into V3 overlay ops (see #1324).Integers already have the right shape: i64-fitting values encode inline (
NUM_INT), only overflow goes to the arena. Decimals deserve the same split.Proposal
Canonical
xsd:decimal(trailing zeros stripped) gives every value a unique(unscaled mantissa, scale)pair, so a packed inline o_key is well-defined, e.g.:17 significant digits with up to 63 decimal places — fixed-point money (scale 2) inlines to ±$7e14. Decode rebuilds the exactBigDecimal, so precision math is unaffected. Values that don't fit keep the arena path, exactly like overflow integers.Two design decisions to settle in spec:
(o_type, o_key)is persisted fact identity: the same value must never exist under two encodings within one index root, or retracts re-encoding inline would fail to match arena-stored base rows. This requires an index-format version gate with reindex as the migration path, and a sweep through resolver spool kinds, the o_type registry, decode paths, stats hooks, overlay translation, and filter-pushdown gating (which must not assume numeric order unless the order-preserving variant is chosen).Out of scope
This is independent of #1324 (which needs no format change and covers existing ledgers); inline decimals are the long-term fix that removes the arena dependency for the common case.