use crate::errors::{YdbError, YdbResult};
use std::collections::HashMap;
use crate::grpc_wrapper::raw_table_service::value::r#type::RawType;
use crate::grpc_wrapper::raw_table_service::value::RawColumn;
use std::convert::TryInto;
use std::fmt::Debug;
use std::num::TryFromIntError;
use std::time::{Duration, SystemTime};
use strum::{EnumCount, EnumDiscriminants, EnumIter, IntoStaticStr};
pub(crate) const SECONDS_PER_DAY: u64 = 60 * 60 * 24;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct YdbDecimal {
inner: decimal_rs::Decimal,
precision: u32,
scale: u32,
}
impl YdbDecimal {
pub fn try_new(value: decimal_rs::Decimal, precision: u32, scale: u32) -> YdbResult<Self> {
let scale_i16: i16 = scale
.try_into()
.map_err(|_| YdbError::Convert(format!("scale {} does not fit into i16", scale)))?;
let current_scale = value.scale();
let adjusted = if current_scale != scale_i16 {
if scale_i16 < current_scale {
return Err(YdbError::Convert(format!(
"cannot decrease decimal scale from {} to {}",
current_scale, scale
)));
}
value.normalize_to_scale(scale_i16)
} else {
value
};
let digit_count = adjusted.precision() as u32;
if digit_count > precision {
return Err(YdbError::Convert(format!(
"decimal value has {} digits, which exceeds precision {}",
digit_count, precision
)));
}
Ok(Self {
inner: adjusted,
precision,
scale,
})
}
pub(crate) fn new_unchecked(value: decimal_rs::Decimal, precision: u32, scale: u32) -> Self {
Self {
inner: value,
precision,
scale,
}
}
pub fn precision(&self) -> u32 {
self.precision
}
pub fn scale(&self) -> u32 {
self.scale
}
pub fn decimal(&self) -> &decimal_rs::Decimal {
&self.inner
}
}
impl std::fmt::Display for YdbDecimal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.inner, f)
}
}
impl From<YdbDecimal> for decimal_rs::Decimal {
fn from(d: YdbDecimal) -> Self {
d.inner
}
}
impl From<YdbDecimal> for Value {
fn from(d: YdbDecimal) -> Self {
Value::Decimal(d)
}
}
#[derive(Clone, Debug, EnumCount, EnumDiscriminants, PartialEq)]
#[strum_discriminants(vis(pub(crate)))] #[strum_discriminants(derive(IntoStaticStr, EnumIter, Hash))]
#[strum_discriminants(name(ValueDiscriminants))]
#[allow(dead_code)]
#[cfg_attr(not(feature = "force-exhaustive-all"), non_exhaustive)]
pub enum Value {
Void,
Null,
Bool(bool),
Int8(i8),
Uint8(u8),
Int16(i16),
Uint16(u16),
Int32(i32),
Uint32(u32),
Int64(i64),
Uint64(u64),
Float(f32),
Double(f64),
Date(SystemTime),
DateTime(SystemTime),
Timestamp(SystemTime),
IntervalMicros(SignedInterval),
Date32(SystemTime),
Datetime64(SystemTime),
Timestamp64(SystemTime),
Interval64(SignedInterval),
Bytes(Bytes),
Text(String),
Yson(Bytes),
Json(String),
JsonDocument(String),
Optional(Box<ValueOptional>),
List(Box<ValueList>),
Struct(ValueStruct),
Decimal(YdbDecimal),
Uuid(uuid::Uuid),
}
impl Value {
pub(crate) fn kind_static(&self) -> &'static str {
let discriminant: ValueDiscriminants = self.into();
discriminant.into()
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ValueStruct {
pub(crate) fields_name: Vec<String>,
pub(crate) values: Vec<Value>,
}
impl ValueStruct {
pub(crate) fn insert(&mut self, name: String, v: Value) {
self.fields_name.push(name);
self.values.push(v);
}
pub(crate) fn from_fields(fields: Vec<(String, Value)>) -> ValueStruct {
let fields_len = fields.len();
let (names, values) = fields.into_iter().fold(
(
Vec::with_capacity(fields_len),
Vec::with_capacity(fields_len),
),
|(mut names, mut values), (name, value)| {
names.push(name);
values.push(value);
(names, values)
},
);
ValueStruct {
fields_name: names,
values,
}
}
#[allow(dead_code)]
pub(crate) fn from_names_and_values(
fields_name: Vec<String>,
values: Vec<Value>,
) -> YdbResult<Self> {
if fields_name.len() != values.len() {
return Err(YdbError::Custom(format!("different len fields_name and values. fields_name len: {}, values len: {}. fields_name: {:?}, values: {:?}", fields_name.len(), values.len(), fields_name, values)));
};
Ok(ValueStruct {
fields_name,
values,
})
}
pub(crate) fn new() -> Self {
Self::with_capacity(0)
}
pub(crate) fn with_capacity(capacity: usize) -> Self {
ValueStruct {
fields_name: Vec::with_capacity(capacity),
values: Vec::with_capacity(capacity),
}
}
}
impl Default for ValueStruct {
fn default() -> Self {
Self::new()
}
}
impl From<ValueStruct> for HashMap<String, Value> {
fn from(mut from_value: ValueStruct) -> Self {
let mut map = HashMap::with_capacity(from_value.fields_name.len());
from_value.values.into_iter().rev().for_each(|val| {
let key = from_value.fields_name.pop().unwrap();
map.insert(key, val);
});
map
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ValueList {
pub(crate) t: Value,
pub(crate) values: Vec<Value>,
}
impl Default for Box<ValueList> {
fn default() -> Self {
Box::new(ValueList {
t: Value::Bool(false),
values: Vec::default(),
})
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ValueOptional {
pub(crate) t: Value,
pub(crate) value: Option<Value>,
}
impl Default for Box<ValueOptional> {
fn default() -> Self {
Box::new(ValueOptional {
t: Value::Bool(false),
value: None,
})
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum Sign {
#[default]
Plus,
Minus,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct SignedInterval {
pub sign: Sign,
pub duration: Duration,
}
impl SignedInterval {
pub(crate) fn as_micros(self) -> std::result::Result<i64, TryFromIntError> {
let micros: i64 = self.duration.as_micros().try_into()?;
let res = match self.sign {
Sign::Plus => micros,
Sign::Minus => -micros,
};
Ok(res)
}
pub(crate) fn from_micros(micros: i64) -> Self {
let (sign, micros) = if micros >= 0 {
(Sign::Plus, micros as u64)
} else {
(Sign::Minus, micros.unsigned_abs())
};
Self {
sign,
duration: Duration::from_micros(micros),
}
}
}
pub(crate) fn system_time_to_signed_secs(
t: SystemTime,
) -> std::result::Result<i64, TryFromIntError> {
match t.duration_since(SystemTime::UNIX_EPOCH) {
Ok(d) => d.as_secs().try_into(),
Err(err) => {
let back = err.duration();
let mut secs: i64 = back.as_secs().try_into()?;
if back.subsec_nanos() > 0 {
secs += 1;
}
Ok(-secs)
}
}
}
pub(crate) fn signed_secs_to_system_time(secs: i64) -> SystemTime {
if secs >= 0 {
SystemTime::UNIX_EPOCH + Duration::from_secs(secs as u64)
} else {
SystemTime::UNIX_EPOCH - Duration::from_secs(secs.unsigned_abs())
}
}
pub(crate) fn system_time_to_signed_micros(
t: SystemTime,
) -> std::result::Result<i64, TryFromIntError> {
match t.duration_since(SystemTime::UNIX_EPOCH) {
Ok(d) => d.as_micros().try_into(),
Err(err) => {
let back = err.duration();
let mut micros: i64 = back.as_micros().try_into()?;
if (back.subsec_nanos() % 1000) > 0 {
micros += 1;
}
Ok(-micros)
}
}
}
pub(crate) fn signed_micros_to_system_time(micros: i64) -> SystemTime {
if micros >= 0 {
SystemTime::UNIX_EPOCH + Duration::from_micros(micros as u64)
} else {
SystemTime::UNIX_EPOCH - Duration::from_micros(micros.unsigned_abs())
}
}
pub(crate) fn system_time_to_signed_days(
t: SystemTime,
) -> std::result::Result<i32, TryFromIntError> {
let secs = system_time_to_signed_secs(t)?;
let days = secs.div_euclid(SECONDS_PER_DAY as i64);
days.try_into()
}
pub(crate) fn signed_days_to_system_time(days: i32) -> SystemTime {
let secs = (days as i64).saturating_mul(SECONDS_PER_DAY as i64);
signed_secs_to_system_time(secs)
}
impl Value {
pub fn list_from(example_value: Value, values: Vec<Value>) -> YdbResult<Self> {
for (index, value) in values.iter().enumerate() {
if std::mem::discriminant(&example_value) != std::mem::discriminant(value) {
return Err(YdbError::Custom(format!("failed list_from: type and value has different enum-types. index: {index}, type: '{example_value:?}', value: '{value:?}'")));
}
}
if let Value::Struct(example_value_struct) = &example_value {
for (i, value) in values.iter().enumerate() {
if let Value::Struct(value_struct) = &value {
if value_struct.fields_name != example_value_struct.fields_name {
return Err(YdbError::Custom(format!(
"failed list_from: fields of value struct with index `{i}`: '{:?}' is not equal to fields of example value struct: '{:?}'",
value_struct.fields_name, example_value_struct.fields_name
)));
}
}
}
}
Ok(Value::List(Box::new(ValueList {
t: example_value,
values,
})))
}
pub(crate) fn optional_from(t: Value, value: Option<Value>) -> YdbResult<Self> {
if let Some(value) = &value {
if std::mem::discriminant(&t) != std::mem::discriminant(value) {
return Err(YdbError::Custom(format!("failed optional_from: type and value has different enum-types. type: '{t:?}', value: '{value:?}'")));
}
}
Ok(Value::Optional(Box::new(ValueOptional { t, value })))
}
pub fn struct_from_fields(fields: Vec<(String, Value)>) -> Value {
Value::Struct(ValueStruct::from_fields(fields))
}
pub fn is_optional(&self) -> bool {
matches!(self, Self::Optional(_))
}
pub fn to_option(self) -> Option<Value> {
match self {
Value::Optional(inner_box) => inner_box.value,
other => Some(other),
}
}
#[cfg(test)]
pub(crate) fn examples_for_test() -> Vec<Value> {
use std::{collections::HashSet, ops::Add};
macro_rules! num_tests {
($values:ident, $en_name:path, $type_name:ty) => {
$values.push($en_name(0 as $type_name));
$values.push($en_name(1 as $type_name));
$values.push($en_name(<$type_name>::MIN));
$values.push($en_name(<$type_name>::MAX));
};
}
let mut values = vec![
Value::Null,
Value::Bool(false),
Value::Bool(true),
Value::Bytes(Bytes::from("asd".to_string())),
Value::Text("asd".into()),
Value::Text("фыв".into()),
Value::Json("{}".into()),
Value::JsonDocument("{}".into()),
Value::Yson("1;2;3;".into()),
Value::Decimal(
YdbDecimal::try_new(
"123456789.987654321"
.parse::<decimal_rs::Decimal>()
.unwrap(),
22,
9,
)
.unwrap(),
),
Value::Uuid(uuid::Uuid::now_v7()),
Value::Uuid(uuid::Uuid::new_v4()),
];
num_tests!(values, Value::Int8, i8);
num_tests!(values, Value::Uint8, u8);
num_tests!(values, Value::Int16, i16);
num_tests!(values, Value::Uint16, u16);
num_tests!(values, Value::Int32, i32);
num_tests!(values, Value::Uint32, u32);
num_tests!(values, Value::Int64, i64);
num_tests!(values, Value::Uint64, u64);
num_tests!(values, Value::Float, f32);
num_tests!(values, Value::Double, f64);
values.push(Value::Void);
values.push(Value::Date(
SystemTime::UNIX_EPOCH.add(std::time::Duration::from_secs(1633996800)),
)); values.push(Value::DateTime(
SystemTime::UNIX_EPOCH.add(std::time::Duration::from_secs(1634000523)),
));
values.push(Value::Timestamp(
SystemTime::UNIX_EPOCH.add(std::time::Duration::from_micros(16340005230000123)),
));
values.push(Value::IntervalMicros(SignedInterval {
sign: Sign::Plus,
duration: Duration::from_secs(1),
}));
values.push(Value::IntervalMicros(SignedInterval {
sign: Sign::Minus,
duration: Duration::from_secs(1),
}));
values.push(Value::Date32(SystemTime::UNIX_EPOCH));
values.push(Value::Date32(
SystemTime::UNIX_EPOCH - Duration::from_secs(SECONDS_PER_DAY),
));
values.push(Value::Datetime64(SystemTime::UNIX_EPOCH));
values.push(Value::Datetime64(
SystemTime::UNIX_EPOCH - Duration::from_secs(1),
));
values.push(Value::Timestamp64(SystemTime::UNIX_EPOCH));
values.push(Value::Timestamp64(
SystemTime::UNIX_EPOCH - Duration::from_micros(1),
));
values.push(Value::Interval64(SignedInterval {
sign: Sign::Plus,
duration: Duration::from_micros(1),
}));
values.push(Value::Interval64(SignedInterval {
sign: Sign::Minus,
duration: Duration::from_micros(1),
}));
values.push(Value::optional_from(Value::Int8(0), None).unwrap());
values.push(Value::optional_from(Value::Int8(0), Some(Value::Int8(1))).unwrap());
values.push(
Value::list_from(
Value::Int8(0),
vec![Value::Int8(1), Value::Int8(2), Value::Int8(3)],
)
.unwrap(),
);
values.push(Value::Struct(ValueStruct {
fields_name: vec!["a".into(), "b".into()],
values: vec![
Value::Int32(1),
Value::list_from(
Value::Int32(0),
vec![Value::Int32(1), Value::Int32(2), Value::Int32(3)],
)
.unwrap(),
],
}));
let mut discriminants = HashSet::new();
for item in values.iter() {
discriminants.insert(std::mem::discriminant(item));
}
assert_eq!(discriminants.len(), Value::COUNT);
values
}
}
#[derive(Debug)]
pub(crate) struct Column {
#[allow(dead_code)]
pub(crate) name: String,
pub(crate) v_type: RawType,
}
impl TryFrom<RawColumn> for Column {
type Error = YdbError;
fn try_from(value: RawColumn) -> Result<Self, Self::Error> {
Ok(Self {
name: value.name,
v_type: value.column_type,
})
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Bytes {
vec: Vec<u8>,
}
impl From<Vec<u8>> for Bytes {
fn from(vec: Vec<u8>) -> Self {
Bytes { vec }
}
}
impl From<Bytes> for Vec<u8> {
fn from(val: Bytes) -> Self {
val.vec
}
}
impl From<String> for Bytes {
fn from(val: String) -> Self {
Self { vec: val.into() }
}
}
impl From<&str> for Bytes {
fn from(val: &str) -> Self {
Self { vec: val.into() }
}
}