use std::fmt::{Debug, Display};
use glaredb_error::{DbError, Result, ResultExt};
use glaredb_proto::ProtoConv;
use num_traits::{AsPrimitive, FromPrimitive, PrimInt, Signed, Zero};
use serde::{Deserialize, Serialize};
use crate::arrays::array::physical_type::{MutableScalarStorage, PhysicalI64, PhysicalI128};
use crate::arrays::datatype::{DataType, DataTypeMeta, DecimalTypeMeta};
pub trait DecimalPrimitive:
PrimInt + FromPrimitive + AsPrimitive<Self> + Signed + Default + Debug + Display + Sync + Send
{
const ZERO: Self;
const TEN: Self;
fn ilog10(self) -> u32;
}
impl DecimalPrimitive for i64 {
const ZERO: Self = 0;
const TEN: Self = 10;
fn ilog10(self) -> u32 {
i64::ilog10(self)
}
}
impl DecimalPrimitive for i128 {
const ZERO: Self = 0;
const TEN: Self = 10;
fn ilog10(self) -> u32 {
i128::ilog10(self)
}
}
pub trait DecimalType: Debug + Sync + Send + Copy + 'static {
type Primitive: DecimalPrimitive;
type Storage: MutableScalarStorage<StorageType = Self::Primitive>;
const MAX_PRECISION: u8;
const DEFAULT_SCALE: i8;
fn validate_precision(value: Self::Primitive, precision: u8) -> Result<()> {
if precision > Self::MAX_PRECISION {
return Err(DbError::new(format!(
"Precision {precision} is greater than max precision {}",
Self::MAX_PRECISION
)));
}
if value.is_zero() {
return Ok(());
}
let digits = value.abs().ilog10() + 1;
if digits > precision as u32 {
return Err(DbError::new(format!(
"{value} cannot be stored in decimal with a precision of {precision}"
)));
}
Ok(())
}
fn decimal_meta_opt(datatype: &DataType) -> Option<DecimalTypeMeta>;
fn decimal_meta(datatype: &DataType) -> Result<DecimalTypeMeta> {
Self::decimal_meta_opt(datatype).ok_or_else(|| {
DbError::new(format!(
"Failed to unwrap decimal type meta, got: {datatype}"
))
})
}
fn datatype_from_decimal_meta(meta: DecimalTypeMeta) -> DataType;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Decimal64Type;
impl DecimalType for Decimal64Type {
type Primitive = i64;
type Storage = PhysicalI64;
const MAX_PRECISION: u8 = 18;
const DEFAULT_SCALE: i8 = 3;
fn decimal_meta_opt(datatype: &DataType) -> Option<DecimalTypeMeta> {
match &datatype.metadata {
DataTypeMeta::Decimal(m) => Some(*m),
_ => None,
}
}
fn datatype_from_decimal_meta(meta: DecimalTypeMeta) -> DataType {
DataType::decimal64(meta)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Decimal128Type;
impl DecimalType for Decimal128Type {
type Primitive = i128;
type Storage = PhysicalI128;
const MAX_PRECISION: u8 = 38;
const DEFAULT_SCALE: i8 = 9;
fn decimal_meta_opt(datatype: &DataType) -> Option<DecimalTypeMeta> {
match &datatype.metadata {
DataTypeMeta::Decimal(m) => Some(*m),
_ => None,
}
}
fn datatype_from_decimal_meta(meta: DecimalTypeMeta) -> DataType {
DataType::decimal128(meta)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Hash)]
pub struct DecimalScalar<T: DecimalType> {
pub precision: u8,
pub scale: i8,
pub value: T::Primitive,
}
pub type Decimal64Scalar = DecimalScalar<Decimal64Type>;
pub type Decimal128Scalar = DecimalScalar<Decimal128Type>;
impl ProtoConv for Decimal64Scalar {
type ProtoType = glaredb_proto::generated::expr::Decimal64Scalar;
fn to_proto(&self) -> Result<Self::ProtoType> {
Ok(Self::ProtoType {
precision: self.precision as i32,
scale: self.scale as i32,
value: self.value,
})
}
fn from_proto(proto: Self::ProtoType) -> Result<Self> {
Ok(Self {
precision: proto
.precision
.try_into()
.context("precision doens't fit")?,
scale: proto.scale.try_into().context("scale doens't fit")?,
value: proto.value,
})
}
}
impl ProtoConv for Decimal128Scalar {
type ProtoType = glaredb_proto::generated::expr::Decimal128Scalar;
fn to_proto(&self) -> Result<Self::ProtoType> {
Ok(Self::ProtoType {
precision: self.precision as i32,
scale: self.scale as i32,
value: self.value.to_le_bytes().to_vec(),
})
}
fn from_proto(proto: Self::ProtoType) -> Result<Self> {
Ok(Self {
precision: proto
.precision
.try_into()
.context("precision doens't fit")?,
scale: proto.scale.try_into().context("scale doens't fit")?,
value: i128::from_le_bytes(
proto
.value
.try_into()
.map_err(|_| DbError::new("byte buffer not 16 bytes"))?,
),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validate_precision() {
Decimal64Type::validate_precision(222, 3).unwrap();
Decimal64Type::validate_precision(222, 4).unwrap();
Decimal64Type::validate_precision(222, 2).unwrap_err();
Decimal64Type::validate_precision(222, 19).unwrap_err();
}
}