// Copyright 2026 Noverberrain.
// SPDX-License-Identifier: Apache-2.0
///|
/// An exact decimal string retaining schema scale, without floating conversion.
pub struct DecodedDecimal {
value : String
precision : Int32
scale : Int32
bytes_read : Int32
} derive(Debug, Eq)
///|
pub extend DecodedDecimal with Eq::{equal, not_equal}
///|
pub extend DecodedDecimal with @debug.Debug::{to_repr}
///|
/// Decode a present, non-NULL NEWDECIMAL (246) for MySQL 8.0.
/// Metadata must contain precision (1..65) and scale (0..30, <= precision).
/// Return normalized integer digits and exactly `scale` fractional digits.
/// Negative zero is normalized to zero; no unsigned schema rule is inferred.
/// `file_offset` is this field's first byte; `column_index` is zero-based.
/// Trailing fields are not consumed. The caller handles presence/NULL bitmaps.
pub fn TableColumn::decode_decimal(
self : TableColumn,
data : BytesView,
file_offset~ : UInt64,
column_index~ : Int32,
) -> DecodedDecimal raise DecodeError {
guard self.wire_type == 246 else {
raise UnsupportedDecimalColumn(
file_offset~,
column_index~,
wire_type=self.wire_type,
)
}
guard self.metadata.length() == 2 else {
raise InvalidDecimalMetadata(
file_offset~,
column_index~,
reason="expected precision and scale bytes",
)
}
let precision = self.metadata[0].to_int()
let scale = self.metadata[1].to_int()
guard precision >= 1 && precision <= 65 && scale <= 30 && scale <= precision else {
raise InvalidDecimalMetadata(
file_offset~,
column_index~,
reason="invalid MySQL 8.0 precision/scale",
)
}
let integer_digits = precision - scale
let bytes_read = (integer_digits / 9 + scale / 9) * 4 +
decimal_group_width(integer_digits % 9) +
decimal_group_width(scale % 9)
guard data.length() >= bytes_read else {
raise TruncatedColumnValue(
file_offset~,
column_index~,
expected=bytes_read,
actual=data.length(),
)
}
let negative = (data[0].to_int() & 0x80) == 0
let integer = StringBuilder()
let fraction = StringBuilder()
let mut position = 0
let mut started = false
let mut nonzero = false
for part in 0..<2 {
let mut remaining = if part == 0 { integer_digits } else { scale }
while remaining > 0 {
let digits = if part == 0 && remaining % 9 != 0 {
remaining % 9
} else if remaining >= 9 {
9
} else {
remaining
}
let width = decimal_group_width(digits)
let mut group = 0UL
let mut bound = 1UL
for _ in 0.. Int32 {
match digits {
0 => 0
1..<=2 => 1
3..<=4 => 2
5..<=6 => 3
_ => 4
}
}