use crate::errors::{YdbError, YdbResult};
use std::collections::{HashMap};
use std::convert::TryInto;
use std::fmt::Debug;
use std::num::TryFromIntError;
use std::time::Duration;
use strum::{EnumCount, EnumDiscriminants, EnumIter, IntoStaticStr};
use ydb_grpc::ydb_proto;
use crate::grpc_wrapper::raw_table_service::value::r#type::RawType;
use crate::grpc_wrapper::raw_table_service::value::RawColumn;
pub(crate) const SECONDS_PER_DAY: u64 = 60 * 60 * 24;
#[derive(Clone, Debug, EnumCount, EnumDiscriminants, EnumIter, PartialEq)]
#[strum_discriminants(vis(pub(crate)))] #[strum_discriminants(derive(IntoStaticStr,EnumIter,Hash))]
#[strum_discriminants(name(ValueDiscriminants))]
#[allow(dead_code)]
#[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(std::time::Duration), DateTime(std::time::Duration), Timestamp(std::time::Duration), Interval(SignedInterval),
String(Bytes),
Text(String),
Yson(String),
Json(String),
JsonDocument(String),
Optional(Box<ValueOptional>),
List(Box<ValueList>),
Struct(ValueStruct),
}
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, PartialEq, Eq)]
pub enum Sign {
Plus,
Minus,
}
impl Default for Sign {
fn default() -> Self {
Sign::Plus
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct SignedInterval {
pub sign: Sign,
pub duration: Duration,
}
impl SignedInterval {
pub(crate) fn as_nanos(self) -> std::result::Result<i64, TryFromIntError> {
let nanos: i64 = self.duration.as_nanos().try_into()?;
let res = match self.sign {
Sign::Plus => nanos,
Sign::Minus => -nanos,
};
Ok(res)
}
pub(crate) fn from_nanos(nanos: i64) -> Self {
let (sign, nanos) = if nanos >= 0 {
(Sign::Plus, nanos as u64)
} else {
(Sign::Minus, (-nanos) as u64)
};
Self {
sign,
duration: Duration::from_nanos(nanos),
}
}
}
impl Value {
#[allow(dead_code)]
pub(crate) fn list_from(t: Value, values: Vec<Value>) -> YdbResult<Self> {
for (index, value) in values.iter().enumerate() {
if std::mem::discriminant(&t) != std::mem::discriminant(value) {
return Err(YdbError::Custom(format!("failed list_from: type and value has different enum-types. index: {}, type: '{:?}', value: '{:?}'", index, t, value)));
}
}
Ok(Value::List(Box::new(ValueList { t, 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: '{:?}', value: '{:?}'", t, 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))
}
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_typed_value(self) -> YdbResult<ydb_proto::TypedValue> {
use ydb_proto::r#type::PrimitiveTypeId as pt;
use ydb_proto::value::Value as pv;
fn proto_typed_value(t: pt, v: pv) -> ydb_proto::TypedValue {
ydb_proto::TypedValue {
r#type: Some(ydb_proto::Type {
r#type: Some(ydb_proto::r#type::Type::TypeId(t.into())),
}),
value: Some(ydb_proto::Value {
value: Some(v),
..ydb_proto::Value::default()
}),
}
}
#[allow(unreachable_patterns)]
let res = match self {
Self::Void => ydb_proto::TypedValue {
r#type: Some(ydb_proto::Type {
r#type: Some(ydb_proto::r#type::Type::VoidType(
prost_types::NullValue::NullValue.into(),
)),
}),
value: Some(ydb_proto::Value {
value: Some(ydb_proto::value::Value::NullFlagValue(
prost_types::NullValue::NullValue.into(),
)),
..ydb_proto::Value::default()
}),
},
Self::Null => ydb_proto::TypedValue {
r#type: Some(ydb_proto::Type {
r#type: Some(ydb_proto::r#type::Type::NullType(0)),
}),
value: Some(ydb_proto::Value {
value: Some(ydb_proto::value::Value::NullFlagValue(
prost_types::NullValue::NullValue.into(),
)),
..ydb_proto::Value::default()
}),
},
Self::Bool(val) => proto_typed_value(pt::Bool, pv::BoolValue(val)),
Self::Int8(val) => proto_typed_value(pt::Int8, pv::Int32Value(val.into())),
Self::Uint8(val) => proto_typed_value(pt::Uint8, pv::Uint32Value(val.into())),
Self::Int16(val) => proto_typed_value(pt::Int16, pv::Int32Value(val.into())),
Self::Uint16(val) => proto_typed_value(pt::Uint16, pv::Uint32Value(val.into())),
Self::Int32(val) => proto_typed_value(pt::Int32, pv::Int32Value(val)),
Self::Uint32(val) => proto_typed_value(pt::Uint32, pv::Uint32Value(val)),
Self::Int64(val) => proto_typed_value(pt::Int64, pv::Int64Value(val)),
Self::Uint64(val) => proto_typed_value(pt::Uint64, pv::Uint64Value(val)),
Self::Float(val) => proto_typed_value(pt::Float, pv::FloatValue(val)),
Self::Double(val) => proto_typed_value(pt::Double, pv::DoubleValue(val)),
Self::Date(val) => proto_typed_value(
pt::Date,
pv::Uint32Value((val.as_secs() / SECONDS_PER_DAY).try_into()?),
),
Self::DateTime(val) => {
proto_typed_value(pt::Datetime, pv::Uint32Value(val.as_secs().try_into()?))
}
Self::Timestamp(val) => {
proto_typed_value(pt::Timestamp, pv::Uint64Value(val.as_micros().try_into()?))
}
Self::Interval(val) => proto_typed_value(pt::Interval, pv::Int64Value(val.as_nanos()?)),
Self::String(val) => proto_typed_value(pt::String, pv::BytesValue(val.into())),
Self::Text(val) => proto_typed_value(pt::Utf8, pv::TextValue(val)),
Self::Yson(val) => proto_typed_value(pt::Yson, pv::TextValue(val)),
Self::Json(val) => proto_typed_value(pt::Json, pv::TextValue(val)),
Self::JsonDocument(val) => proto_typed_value(pt::JsonDocument, pv::TextValue(val)),
Self::Optional(val) => Self::to_typed_optional(*val)?,
Self::List(items) => Self::to_typed_value_list(*items)?,
Value::Struct(s) => { Self::to_typed_struct(s) }?,
};
Ok(res)
}
fn to_typed_optional(optional: ValueOptional) -> YdbResult<ydb_proto::TypedValue> {
if let Value::Optional(_opt) = optional.t {
unimplemented!("nested optional")
}
let val = match optional.value {
Some(val) => val.to_typed_value()?.value.unwrap(),
None => ydb_proto::Value {
value: Some(ydb_proto::value::Value::NullFlagValue(0)),
..ydb_proto::Value::default()
},
};
Ok(ydb_proto::TypedValue {
r#type: Some(ydb_proto::Type {
r#type: Some(ydb_proto::r#type::Type::OptionalType(Box::new(
ydb_proto::OptionalType {
item: Some(Box::new(optional.t.to_typed_value()?.r#type.unwrap())),
},
))),
}),
value: Some(val),
})
}
fn to_typed_struct(s: ValueStruct) -> YdbResult<ydb_proto::TypedValue> {
let mut members: Vec<ydb_proto::StructMember> = Vec::with_capacity(s.fields_name.len());
let mut items: Vec<ydb_proto::Value> = Vec::with_capacity(s.fields_name.len());
for (index, v) in s.values.into_iter().enumerate() {
let typed_val = v.to_typed_value()?;
members.push(ydb_proto::StructMember {
name: s.fields_name[index].clone(),
r#type: typed_val.r#type,
});
items.push(typed_val.value.unwrap());
}
Ok(ydb_proto::TypedValue {
r#type: Some(ydb_proto::Type {
r#type: Some(ydb_proto::r#type::Type::StructType(ydb_proto::StructType {
members,
})),
}),
value: Some(ydb_proto::Value {
items,
..ydb_proto::Value::default()
}),
})
}
#[allow(clippy::boxed_local)]
fn to_typed_value_list(ydb_list: ValueList) -> YdbResult<ydb_proto::TypedValue> {
let ydb_list_type = ydb_list.t;
let proto_items_result: Vec<YdbResult<ydb_proto::TypedValue>> = ydb_list
.values
.into_iter()
.map(|item| item.to_typed_value())
.collect();
let mut proto_items = Vec::with_capacity(proto_items_result.len());
for item in proto_items_result.into_iter() {
proto_items.push(item?);
}
Ok(ydb_proto::TypedValue {
r#type: Some(ydb_proto::Type {
r#type: Some(ydb_proto::r#type::Type::ListType(Box::new(
ydb_proto::ListType {
item: Some(Box::new(ydb_list_type.to_typed_value()?.r#type.unwrap())),
},
))),
}),
value: Some(ydb_proto::Value {
items: proto_items
.into_iter()
.map(|item| item.value.unwrap())
.collect(),
..ydb_proto::Value::default()
}),
})
}
#[cfg(test)]
pub(crate) fn examples_for_test() ->Vec<Value>{
use std::collections::HashSet;
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::String(Bytes::from("asd".to_string())),
Value::Text("asd".into()),
Value::Text("фыв".into()),
Value::Json("{}".into()),
Value::JsonDocument("{}".into()),
Value::Yson("1;2;3;".into()),
];
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(std::time::Duration::from_secs(1633996800))); values.push(Value::DateTime(std::time::Duration::from_secs(1634000523)));
values.push(Value::Timestamp(std::time::Duration::from_micros(
16340005230000123,
)));
values.push(Value::Interval(SignedInterval {
sign: Sign::Plus,
duration: Duration::from_secs(1),
}));
values.push(Value::Interval(SignedInterval {
sign: Sign::Minus,
duration: Duration::from_secs(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() }
}
}