pub struct Numeric { /* private fields */ }Expand description
A numeric (decimal) value with up to 38 digits of precision.
Hyper stores NUMERIC(precision, scale) as a fixed-point integer with
implicit scale: the actual value is unscaled_value × 10^(-scale).
The scale is determined by the column’s type modifier.
§Wire Format
Hyper uses two wire representations depending on declared precision:
| Declared Precision | Wire Size | Rust Storage |
|---|---|---|
| ≤ 18 digits | 8 bytes (i64) | Widened to i128 on read |
| 19–38 digits | 16 bytes (i128) | Stored directly |
This distinction is transparent to callers — Numeric always stores
an i128 internally. The 18-digit threshold corresponds to
SMALL_NUMERIC_MAX_PRECISION.
The maximum precision of 38 digits (MAX_PRECISION)
matches the limit of a signed 128-bit integer (which can represent up
to ±1.7 × 10^38).
§No Arithmetic
Numeric does not implement arithmetic operators (Add, Sub, etc.)
because scale handling during arithmetic is context-dependent and best
done via SQL. Use to_f64 for approximate arithmetic
in Rust, or perform calculations server-side.
Implementations§
Source§impl Numeric
impl Numeric
Sourcepub const MAX_PRECISION: u8 = 38
pub const MAX_PRECISION: u8 = 38
Maximum precision (38 digits).
This is the single source of truth for Hyper’s NUMERIC precision limit. All validation elsewhere in the codebase should reference this constant.
Sourcepub const SMALL_NUMERIC_MAX_PRECISION: u32 = 18
pub const SMALL_NUMERIC_MAX_PRECISION: u32 = 18
Maximum precision that fits in i64 storage (18 digits).
NUMERIC values with precision ≤ 18 are stored as 64-bit integers in HyperBinary format. Precision > 18 uses 128-bit storage.
Sourcepub const fn new(value: i128, scale: u8) -> Numeric
pub const fn new(value: i128, scale: u8) -> Numeric
Creates a Numeric from an unscaled value and scale.
Neither argument is validated. A scale above
MAX_PRECISION, or a value wider than the
engine’s 38-digit limit, is representable here and renders correctly
via Display, but the server rejects it when bound
as a parameter or inserted. Use
try_from_f64 for a checked construction from
a float.
Sourcepub const fn unscaled_value(&self) -> i128
pub const fn unscaled_value(&self) -> i128
Returns the unscaled value.
Sourcepub fn from_f64(value: f64, scale: u8) -> Numeric
pub fn from_f64(value: f64, scale: u8) -> Numeric
Creates from f64 with specified scale.
§Panics
Panics if value * 10^scale is not finite or exceeds the 38-digit
precision limit of Hyper’s Numeric type.
For a non-panicking variant, use try_from_f64.
Sourcepub fn try_from_f64(
value: f64,
scale: u8,
) -> Result<Numeric, Box<dyn Error + Send + Sync>>
pub fn try_from_f64( value: f64, scale: u8, ) -> Result<Numeric, Box<dyn Error + Send + Sync>>
Creates from f64 with specified scale, returning an error if the value exceeds Hyper’s 38-digit precision limit.
§Errors
Returns an error if the scaled value exceeds ±(10^38 - 1) or if the intermediate result is not finite.
Sourcepub fn encode(&self) -> [u8; 16]
pub fn encode(&self) -> [u8; 16]
Encodes the numeric as a 128-bit little-endian value, suitable for
the HyperBinary BigNumeric wire format (precision > 18).
Sourcepub fn encode_int64(&self) -> Result<[u8; 8], Box<dyn Error + Send + Sync>>
pub fn encode_int64(&self) -> Result<[u8; 8], Box<dyn Error + Send + Sync>>
Encodes the numeric as a 64-bit little-endian value, suitable for
the HyperBinary Numeric wire format (precision ≤ 18).
§Errors
Returns an Err if self.value is outside the i64 range.
This happens exactly when the unscaled value exceeds what a 19+
digit precision can hold, i.e. the value belongs in the
BigNumeric 16-byte wire form instead. The error names the
specific out-of-range value so callers have something
actionable in logs.
Returns Ok for every value that fits; callers who can
statically prove their values are in range (e.g. building from
a NUMERIC(p, s) schema with p ≤ 18) can .expect() the
result without losing any safety.
For values that are always representable in 128 bits — i.e.
anything Hyper can hold in any NUMERIC(p, s) with p ≤ 38 —
use encode instead, which is infallible.
Sourcepub fn decode(buf: &[u8; 16], scale: u8) -> Numeric
pub fn decode(buf: &[u8; 16], scale: u8) -> Numeric
Decodes a numeric from the 128-bit HyperBinary BigNumeric wire
form (precision > 18). See decode_int64 for
the 8-byte Numeric form used when precision ≤ 18.
The scale parameter is not carried in the wire bytes — it must
come from the column’s SqlType::Numeric { precision, scale }
descriptor. See hyperdb_api::result::Rowset::schema() for how this
flows end-to-end from a RowDescription message.
Sourcepub fn decode_int64(buf: &[u8; 8], scale: u8) -> Numeric
pub fn decode_int64(buf: &[u8; 8], scale: u8) -> Numeric
Decodes a numeric from the 64-bit HyperBinary Numeric wire form
(used by Hyper when precision ≤ 18 — see
Type::maxPrecisionNumeric = 18 in the Hyper DB source at
hyper/rts/type/Type.hpp). The 64-bit form is what
AVG(SmallInt | Integer) returns (as Numeric(11, 6) or
Numeric(16, 6) respectively — see AggregationLogic:: dividingAggregateType in
hyper/cts/algebra/operator/AggregationLogic.cpp).
Like decode, scale is not carried in the
bytes and must come from the column’s SqlType::Numeric
descriptor.
Source§impl Numeric
impl Numeric
Sourcepub fn from_binary_with_scale(
buf: &[u8],
scale: u8,
) -> Result<Numeric, Box<dyn Error + Send + Sync>>
pub fn from_binary_with_scale( buf: &[u8], scale: u8, ) -> Result<Numeric, Box<dyn Error + Send + Sync>>
Creates a Numeric from binary data with explicit scale.
This is the preferred method for decoding Numeric values from binary data, as the scale must be obtained from the column’s type metadata.
Dispatches on buffer size to handle both of Hyper’s NUMERIC wire
forms:
- 8 bytes (i64): used when the column’s precision is ≤ 18
(Hyper’s
Type::Numeric; seemaxPrecisionNumeric = 18inhyper/rts/type/Type.hpp). This is the form returned by aggregates likeAVG(INTEGER)which Hyper types asNumeric(16, 6)— seeAggregationLogic::dividingAggregateTypeinhyper/cts/algebra/operator/AggregationLogic.cpp. - 16 bytes (i128): used when the column’s precision is
18 (Hyper’s
Type::BigNumeric, up to 38 digits).
Any other buffer size is rejected with an error — callers that reach this function with, say, a 4-byte truncated read should surface the bug rather than silently decoding garbage.
§Arguments
buf- An 8-byte or 16-byte buffer containing the unscaled value in little-endianscale- The number of digits after the decimal point, from type metadata (SqlType::Numeric { scale, .. })
§Examples
use hyperdb_api_core::types::Numeric;
// 16-byte form: NUMERIC(25, 2) value = 100
let bytes16 = [100u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let numeric = Numeric::from_binary_with_scale(&bytes16, 2).unwrap();
assert_eq!(numeric.to_f64(), 1.00);
// 8-byte form: NUMERIC(16, 6) = 2.0 (as returned by AVG(INTEGER))
let bytes8: [u8; 8] = 2_000_000i64.to_le_bytes();
let numeric = Numeric::from_binary_with_scale(&bytes8, 6).unwrap();
assert_eq!(numeric.to_f64(), 2.0);§Errors
Returns an error if buf.len() is anything other than 8 or 16 bytes.
Hyper encodes NUMERIC(precision, scale) values in exactly one of
those two widths, so any other size indicates a truncated read or
protocol violation upstream.
§Panics
Does not panic in practice: the buf.len() == 8 and buf.len() == 16
branches each use try_into().expect(...) whose invariant is proven
by the enclosing match arm.
Trait Implementations§
impl Copy for Numeric
impl Eq for Numeric
Source§impl RowValue for Numeric
impl RowValue for Numeric
Source§fn from_row(row: &Row, idx: usize) -> Option<Self>
fn from_row(row: &Row, idx: usize) -> Option<Self>
Unlike every other RowValue impl, Numeric decode requires
per-column metadata (scale + wire-form width) that lives on the
row’s attached ResultSchema. Row::get_numeric does the
lookup; this impl delegates there so generic row.get::<Numeric>()
/ row.try_get::<Numeric>(idx, "name") call sites work the same
as every other type.
impl StructuralPartialEq for Numeric
Source§impl ToHyperBinary for Numeric
impl ToHyperBinary for Numeric
Source§fn to_hyper_binary(
&self,
buf: &mut BytesMut,
) -> Result<(), Box<dyn Error + Send + Sync>>
fn to_hyper_binary( &self, buf: &mut BytesMut, ) -> Result<(), Box<dyn Error + Send + Sync>>
Source§fn to_hyper_binary_not_null(
&self,
buf: &mut BytesMut,
) -> Result<(), Box<dyn Error + Send + Sync>>
fn to_hyper_binary_not_null( &self, buf: &mut BytesMut, ) -> Result<(), Box<dyn Error + Send + Sync>>
Source§fn hyper_binary_size(&self) -> usize
fn hyper_binary_size(&self) -> usize
Source§fn hyper_binary_size_not_null(&self) -> usize
fn hyper_binary_size_not_null(&self) -> usize
Source§impl ToSqlParam for Numeric
impl ToSqlParam for Numeric
Source§fn encode_param(&self) -> Option<Vec<u8>>
fn encode_param(&self) -> Option<Vec<u8>>
Binds whole numbers (scale() == 0) as PostgreSQL binary NUMERIC and
scaled decimals (scale() > 0) as text.
Hyper has no binary input path for a scaled NUMERIC: a binary NUMERIC
whose dscale exceeds the parameter’s resolved scale is rejected with
SQLSTATE 0A000 (“cannot handle truncation when reading numerics”),
and a bare numeric parameter OID resolves server-side to
NUMERIC(1,0) — scale 0 — so every scaled value hits that check,
whatever the SQL says. Correct nbase-10000 encoding does not help; the
blocker is type resolution, not the bytes.
Text sidesteps it, which is why Self::param_format returns
ParamFormat::Text and Self::sql_oid leaves the OID unspecified
for scaled values. See Self::sql_oid for what that costs.
Source§fn sql_oid(&self) -> Oid
fn sql_oid(&self) -> Oid
NUMERIC for whole numbers, unspecified (0) for scaled values.
A declared numeric OID carries no type modifier, and Hyper resolves
that to NUMERIC(1,0) — one digit, no fraction — so declaring it for
a scaled value fails with 22003 (numeric overflow) before the text
is even considered. Leaving the OID unspecified lets the server infer
the type from context, which is what makes INSERT INTO t VALUES ($1)
and WHERE col = $1 work against a real NUMERIC(p,s) column.
The trade-off: with no context to infer from, SELECT $1 returns the
parameter as TEXT rather than NUMERIC. That means
Row::get::<Numeric> returns None — the column
really is text — while get::<String> yields "1234.56". Wrap it —
SELECT CAST($1 AS NUMERIC(10,2)) — when the result type matters.
Two other bare-$1 contexts reject a scaled value outright with
42601 (“unable to deduce parameter type”): WHERE n IN ($1) and
COALESCE($1, 0). And in WHERE textcol = $1 the inference resolves
$1 to text, so the comparison is a string comparison: 1234.56
matches the literal '1234.56' but not '1234.560'. Whole numbers
keep the concrete OID and are unaffected by all of this.
§Prepared statements
sql_oid is consulted only on the one-shot
query_params /
command_params path, which
re-parses per call. A PreparedStatement
fixes its parameter OIDs at
prepare_typed time, before any
value exists, so one prepared statement cannot accept both whole and
scaled NUMERIC values:
prepare_typed(sql, &[oids::NUMERIC])— whole numbers work; a scaled value fails22003(numeric overflow).prepare_typed(sql, &[Oid::new(0)])— scaled values work; a whole number fails0A000(cannot handle truncation when reading numerics).
Use query_params when the scale varies across calls, or pick the OID
that matches the one class you bind. Geography has no such split —
it declares a concrete OID and always binds as text, so it works on
both paths.
Source§fn param_format(&self) -> ParamFormat
fn param_format(&self) -> ParamFormat
Self::encode_param produced. Read moreSource§fn to_sql_literal(&self) -> String
fn to_sql_literal(&self) -> String
Auto Trait Implementations§
impl Freeze for Numeric
impl RefUnwindSafe for Numeric
impl Send for Numeric
impl Sync for Numeric
impl Unpin for Numeric
impl UnsafeUnpin for Numeric
impl UnwindSafe for Numeric
Blanket Implementations§
impl<T> Allocation for T
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
Source§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request