Skip to main content

Numeric

Struct Numeric 

Source
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 PrecisionWire SizeRust Storage
≤ 18 digits8 bytes (i64)Widened to i128 on read
19–38 digits16 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

Source

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.

Source

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.

Source

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.

Source

pub const fn from_i128(value: i128) -> Numeric

Creates a Numeric from an integer.

Source

pub const fn from_i64(value: i64) -> Numeric

Creates a Numeric from an i64.

Source

pub const fn from_i32(value: i32) -> Numeric

Creates a Numeric from an i32.

Source

pub const fn unscaled_value(&self) -> i128

Returns the unscaled value.

Source

pub const fn scale(&self) -> u8

Returns the scale.

Source

pub fn to_f64(&self) -> f64

Converts to f64 (may lose precision).

Source

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.

Source

pub fn try_from_f64( value: f64, scale: u8, ) -> Result<Numeric, Box<dyn Error + Sync + Send>>

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.

Source

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).

Source

pub fn encode_int64(&self) -> Result<[u8; 8], Box<dyn Error + Sync + Send>>

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.

Source

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.

Source

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

pub fn to_packed(&self) -> [u8; 16]

Returns the packed 128-bit representation for insertion.

This is an alias for encode() for API consistency.

Source§

impl Numeric

Source

pub fn from_binary_with_scale( buf: &[u8], scale: u8, ) -> Result<Numeric, Box<dyn Error + Sync + Send>>

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; see maxPrecisionNumeric = 18 in hyper/rts/type/Type.hpp). This is the form returned by aggregates like AVG(INTEGER) which Hyper types as Numeric(16, 6) — see AggregationLogic::dividingAggregateType in hyper/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-endian
  • scale - 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§

Source§

impl Clone for Numeric

Source§

fn clone(&self) -> Numeric

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for Numeric

Source§

impl Debug for Numeric

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Display for Numeric

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Eq for Numeric

Source§

impl Hash for Numeric

Source§

fn hash<__H>(&self, state: &mut __H)
where __H: Hasher,

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl IntoValue for Numeric

Source§

fn add_to_inserter(&self, inserter: &mut Inserter<'_>) -> Result<()>

Adds this value to the inserter. Read more
Source§

impl IntoValue for &Numeric

Source§

fn add_to_inserter(&self, inserter: &mut Inserter<'_>) -> Result<()>

Adds this value to the inserter. Read more
Source§

impl PartialEq for Numeric

Source§

fn eq(&self, other: &Numeric) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl RowValue for Numeric

Source§

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.

Source§

impl StructuralPartialEq for Numeric

Source§

impl ToHyperBinary for Numeric

Source§

fn to_hyper_binary( &self, buf: &mut BytesMut, ) -> Result<(), Box<dyn Error + Sync + Send>>

Serializes the value to HyperBinary format for a nullable column. Read more
Source§

fn to_hyper_binary_not_null( &self, buf: &mut BytesMut, ) -> Result<(), Box<dyn Error + Sync + Send>>

Serializes the value to HyperBinary format for a NOT NULL column. Read more
Source§

fn hyper_binary_size(&self) -> usize

Returns the size in bytes this value will occupy when serialized (nullable).
Source§

fn hyper_binary_size_not_null(&self) -> usize

Returns the size in bytes this value will occupy when serialized (not nullable).
Source§

impl ToSqlParam for Numeric

Source§

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

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 fails 22003 (numeric overflow).
  • prepare_typed(sql, &[Oid::new(0)]) — scaled values work; a whole number fails 0A000 (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

Returns the wire format Self::encode_param produced. Read more
Source§

fn to_sql_literal(&self) -> String

Returns the SQL literal representation of this value. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Allocation for T
where T: RefUnwindSafe + Send + Sync,

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoRequest<T> for T

Source§

fn into_request(self) -> Request<T>

Wrap the input message T in a tonic::Request
Source§

impl<L> LayerExt<L> for L

Source§

fn named_layer<S>(&self, service: S) -> Layered<<L as Layer<S>>::Service, S>
where L: Layer<S>,

Applies the layer to a service and wraps it in Layered.
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more