diff --git a/lib/Cargo.toml b/lib/Cargo.toml index e7113ea7..1be5637c 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -25,6 +25,7 @@ byteorder = {version="1", features = ["i128"] } quick-xml = "0.13" ordered-float = "1" num-traits = "0.2" +rust_decimal = "0.10" [build-dependencies] peg = "0.5" diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 28b94f32..3aa9baed 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -7,6 +7,7 @@ extern crate num_traits; extern crate ordered_float; extern crate quick_xml; extern crate rocksdb; +extern crate rust_decimal; extern crate url; extern crate uuid; diff --git a/lib/src/model/literal.rs b/lib/src/model/literal.rs index 8e6dbfee..71d5f2ac 100644 --- a/lib/src/model/literal.rs +++ b/lib/src/model/literal.rs @@ -2,7 +2,11 @@ use model::named_node::NamedNode; use model::vocab::rdf; use model::vocab::xsd; use num_traits::identities::Zero; +use num_traits::FromPrimitive; +use num_traits::One; +use num_traits::ToPrimitive; use ordered_float::OrderedFloat; +use rust_decimal::Decimal; use std::borrow::Cow; use std::fmt; use std::option::Option; @@ -42,6 +46,7 @@ enum LiteralContent { Float(OrderedFloat), Double(OrderedFloat), Integer(i128), + Decimal(Decimal), TypedLiteral { value: String, datatype: NamedNode }, } @@ -78,6 +83,11 @@ impl Literal { Ok(value) => LiteralContent::Integer(value), Err(_) => LiteralContent::TypedLiteral { value, datatype }, } + } else if datatype == *xsd::DECIMAL { + match value.parse() { + Ok(value) => LiteralContent::Decimal(value), + Err(_) => LiteralContent::TypedLiteral { value, datatype }, + } } else { LiteralContent::TypedLiteral { value, datatype } }) @@ -105,6 +115,7 @@ impl Literal { LiteralContent::Float(value) => Cow::Owned(value.to_string()), LiteralContent::Double(value) => Cow::Owned(value.to_string()), LiteralContent::Integer(value) => Cow::Owned(value.to_string()), + LiteralContent::Decimal(value) => Cow::Owned(value.to_string()), LiteralContent::TypedLiteral { ref value, .. } => Cow::Borrowed(value), } } @@ -130,6 +141,7 @@ impl Literal { LiteralContent::Float(_) => &xsd::FLOAT, LiteralContent::Double(_) => &xsd::DOUBLE, LiteralContent::Integer(_) => &xsd::INTEGER, + LiteralContent::Decimal(_) => &xsd::DECIMAL, LiteralContent::TypedLiteral { ref datatype, .. } => datatype, } } @@ -186,6 +198,15 @@ impl Literal { } } + /// Checks if the literal has the datatype [xsd:decimal](http://www.w3.org/2001/XMLSchema#decimal) or one of its sub datatype and is valid + pub fn is_decimal(&self) -> bool { + match self.0 { + LiteralContent::Integer(_) => true, + LiteralContent::Decimal(_) => true, + _ => false, + } + } + /// Returns the [effective boolean value](https://www.w3.org/TR/sparql11-query/#ebv) of the literal if it exists pub fn to_bool(&self) -> Option { match self.0 { @@ -197,6 +218,7 @@ impl Literal { LiteralContent::Float(value) => Some(!value.is_zero()), LiteralContent::Double(value) => Some(!value.is_zero()), LiteralContent::Integer(value) => Some(!value.is_zero()), + LiteralContent::Decimal(value) => Some(!value.is_zero()), LiteralContent::TypedLiteral { .. } => None, } } @@ -204,10 +226,11 @@ impl Literal { /// Returns the value of this literal as an f32 if it exists following the rules of [XPath xsd:float casting](https://www.w3.org/TR/xpath-functions/#casting-to-float) pub fn to_float(&self) -> Option { match self.0 { - LiteralContent::Float(value) => Some(*value), + LiteralContent::Float(value) => value.to_f32(), + LiteralContent::Double(value) => value.to_f32(), + LiteralContent::Integer(value) => value.to_f32(), + LiteralContent::Decimal(value) => value.to_f32(), LiteralContent::Boolean(value) => Some(if value { 1. } else { 0. }), - LiteralContent::Double(value) => Some(*value as f32), - LiteralContent::Integer(value) => Some(value as f32), LiteralContent::SimpleLiteral(ref value) | LiteralContent::String(ref value) => { value.parse().ok() } @@ -218,9 +241,10 @@ impl Literal { /// Returns the value of this literal as an f64 if it exists following the rules of [XPath xsd:double casting](https://www.w3.org/TR/xpath-functions/#casting-to-double) pub fn to_double(&self) -> Option { match self.0 { - LiteralContent::Double(value) => Some(*value), - LiteralContent::Float(value) => Some(*value as f64), - LiteralContent::Integer(value) => Some(value as f64), + LiteralContent::Float(value) => value.to_f64(), + LiteralContent::Double(value) => value.to_f64(), + LiteralContent::Integer(value) => value.to_f64(), + LiteralContent::Decimal(value) => value.to_f64(), LiteralContent::Boolean(value) => Some(if value { 1. } else { 0. }), LiteralContent::SimpleLiteral(ref value) | LiteralContent::String(ref value) => { value.parse().ok() @@ -232,9 +256,10 @@ impl Literal { /// Returns the value of this literal as an i128 if it exists following the rules of [XPath xsd:integer casting](https://www.w3.org/TR/xpath-functions/#casting-to-integer) pub fn to_integer(&self) -> Option { match self.0 { - LiteralContent::Integer(value) => Some(value), - LiteralContent::Float(value) => Some(*value as i128), - LiteralContent::Double(value) => Some(*value as i128), + LiteralContent::Float(value) => value.to_i128(), + LiteralContent::Double(value) => value.to_i128(), + LiteralContent::Integer(value) => value.to_i128(), + LiteralContent::Decimal(value) => value.to_i128(), LiteralContent::Boolean(value) => Some(if value { 1 } else { 0 }), LiteralContent::SimpleLiteral(ref value) | LiteralContent::String(ref value) => { value.parse().ok() @@ -242,6 +267,25 @@ impl Literal { _ => None, } } + + /// Returns the value of this literal as Decimal if it exists following the rules of [XPath xsd:decimal casting](https://www.w3.org/TR/xpath-functions/#casting-to-decimal) + pub(crate) fn to_decimal(&self) -> Option { + match self.0 { + LiteralContent::Float(value) => Decimal::from_f32(*value), + LiteralContent::Double(value) => Decimal::from_f64(*value), + LiteralContent::Integer(value) => Decimal::from_i128(value), + LiteralContent::Decimal(value) => Some(value), + LiteralContent::Boolean(value) => Some(if value { + Decimal::one() + } else { + Decimal::zero() + }), + LiteralContent::SimpleLiteral(ref value) | LiteralContent::String(ref value) => { + value.parse().ok() + } + _ => None, + } + } } impl fmt::Display for Literal { @@ -327,3 +371,9 @@ impl From for Literal { Literal(LiteralContent::Double(value.into())) } } + +impl From for Literal { + fn from(value: Decimal) -> Self { + Literal(LiteralContent::Decimal(value)) + } +} diff --git a/lib/src/sparql/eval.rs b/lib/src/sparql/eval.rs index 63aefb4a..878054f1 100644 --- a/lib/src/sparql/eval.rs +++ b/lib/src/sparql/eval.rs @@ -1,4 +1,7 @@ use num_traits::identities::Zero; +use num_traits::FromPrimitive; +use num_traits::ToPrimitive; +use rust_decimal::Decimal; use sparql::algebra::*; use sparql::plan::*; use std::collections::HashSet; @@ -245,32 +248,38 @@ impl SimpleEvaluator { NumericBinaryOperands::Float(v1, v2) => (v1 + v2).into(), NumericBinaryOperands::Double(v1, v2) => (v1 + v2).into(), NumericBinaryOperands::Integer(v1, v2) => (v1 + v2).into(), + NumericBinaryOperands::Decimal(v1, v2) => (v1 + v2).into(), }), PlanExpression::Sub(a, b) => Some(match self.parse_numeric_operands(a, b, tuple)? { NumericBinaryOperands::Float(v1, v2) => (v1 - v2).into(), NumericBinaryOperands::Double(v1, v2) => (v1 - v2).into(), NumericBinaryOperands::Integer(v1, v2) => (v1 - v2).into(), + NumericBinaryOperands::Decimal(v1, v2) => (v1 - v2).into(), }), PlanExpression::Mul(a, b) => Some(match self.parse_numeric_operands(a, b, tuple)? { NumericBinaryOperands::Float(v1, v2) => (v1 * v2).into(), NumericBinaryOperands::Double(v1, v2) => (v1 * v2).into(), NumericBinaryOperands::Integer(v1, v2) => (v1 * v2).into(), + NumericBinaryOperands::Decimal(v1, v2) => (v1 * v2).into(), }), PlanExpression::Div(a, b) => Some(match self.parse_numeric_operands(a, b, tuple)? { NumericBinaryOperands::Float(v1, v2) => (v1 / v2).into(), NumericBinaryOperands::Double(v1, v2) => (v1 / v2).into(), NumericBinaryOperands::Integer(v1, v2) => (v1 / v2).into(), + NumericBinaryOperands::Decimal(v1, v2) => (v1 / v2).into(), }), PlanExpression::UnaryPlus(e) => match self.eval_expression(e, tuple)? { EncodedTerm::FloatLiteral(value) => Some((*value).into()), EncodedTerm::DoubleLiteral(value) => Some((*value).into()), EncodedTerm::IntegerLiteral(value) => Some((value).into()), + EncodedTerm::DecimalLiteral(value) => Some((value).into()), _ => None, }, PlanExpression::UnaryMinus(e) => match self.eval_expression(e, tuple)? { EncodedTerm::FloatLiteral(value) => Some((-*value).into()), EncodedTerm::DoubleLiteral(value) => Some((-*value).into()), EncodedTerm::IntegerLiteral(value) => Some((-value).into()), + EncodedTerm::DecimalLiteral(value) => Some((-value).into()), _ => None, }, PlanExpression::UnaryNot(e) => self @@ -329,6 +338,7 @@ impl SimpleEvaluator { EncodedTerm::FloatLiteral(value) => Some(!value.is_zero()), EncodedTerm::DoubleLiteral(value) => Some(!value.is_zero()), EncodedTerm::IntegerLiteral(value) => Some(!value.is_zero()), + EncodedTerm::DecimalLiteral(value) => Some(!value.is_zero()), _ => None, } } @@ -354,6 +364,9 @@ impl SimpleEvaluator { EncodedTerm::IntegerLiteral(value) => { self.store.insert_bytes(value.to_string().as_bytes()).ok() } + EncodedTerm::DecimalLiteral(value) => { + self.store.insert_bytes(value.to_string().as_bytes()).ok() + } _ => None, } } @@ -369,20 +382,53 @@ impl SimpleEvaluator { self.eval_expression(&e2, tuple)?, ) { (EncodedTerm::FloatLiteral(v1), EncodedTerm::FloatLiteral(v2)) => { - Some(NumericBinaryOperands::Float(*v1, *v2)) + Some(NumericBinaryOperands::Float(*v1, v2.to_f32()?)) } (EncodedTerm::FloatLiteral(v1), EncodedTerm::DoubleLiteral(v2)) => { - Some(NumericBinaryOperands::Double(*v1 as f64, *v2)) + Some(NumericBinaryOperands::Double(v1.to_f64()?, *v2)) + } + (EncodedTerm::FloatLiteral(v1), EncodedTerm::IntegerLiteral(v2)) => { + Some(NumericBinaryOperands::Float(*v1, v2.to_f32()?)) + } + (EncodedTerm::FloatLiteral(v1), EncodedTerm::DecimalLiteral(v2)) => { + Some(NumericBinaryOperands::Float(*v1, v2.to_f32()?)) } (EncodedTerm::DoubleLiteral(v1), EncodedTerm::FloatLiteral(v2)) => { - Some(NumericBinaryOperands::Double(*v1, *v2 as f64)) + Some(NumericBinaryOperands::Double(*v1, v2.to_f64()?)) } (EncodedTerm::DoubleLiteral(v1), EncodedTerm::DoubleLiteral(v2)) => { Some(NumericBinaryOperands::Double(*v1, *v2)) } + (EncodedTerm::DoubleLiteral(v1), EncodedTerm::IntegerLiteral(v2)) => { + Some(NumericBinaryOperands::Double(*v1, v2.to_f64()?)) + } + (EncodedTerm::DoubleLiteral(v1), EncodedTerm::DecimalLiteral(v2)) => { + Some(NumericBinaryOperands::Double(*v1, v2.to_f64()?)) + } + (EncodedTerm::IntegerLiteral(v1), EncodedTerm::FloatLiteral(v2)) => { + Some(NumericBinaryOperands::Float(v1.to_f32()?, *v2)) + } + (EncodedTerm::IntegerLiteral(v1), EncodedTerm::DoubleLiteral(v2)) => { + Some(NumericBinaryOperands::Double(v1.to_f64()?, *v2)) + } (EncodedTerm::IntegerLiteral(v1), EncodedTerm::IntegerLiteral(v2)) => { Some(NumericBinaryOperands::Integer(v1, v2)) } + (EncodedTerm::IntegerLiteral(v1), EncodedTerm::DecimalLiteral(v2)) => { + Some(NumericBinaryOperands::Decimal(Decimal::from_i128(v1)?, v2)) + } + (EncodedTerm::DecimalLiteral(v1), EncodedTerm::FloatLiteral(v2)) => { + Some(NumericBinaryOperands::Float(v1.to_f32()?, *v2)) + } + (EncodedTerm::DecimalLiteral(v1), EncodedTerm::DoubleLiteral(v2)) => { + Some(NumericBinaryOperands::Double(v1.to_f64()?, *v2)) + } + (EncodedTerm::DecimalLiteral(v1), EncodedTerm::IntegerLiteral(v2)) => { + Some(NumericBinaryOperands::Decimal(v1, Decimal::from_i128(v2)?)) + } + (EncodedTerm::DecimalLiteral(v1), EncodedTerm::DecimalLiteral(v2)) => { + Some(NumericBinaryOperands::Decimal(v1, v2)) + } _ => None, } } @@ -414,6 +460,7 @@ enum NumericBinaryOperands { Float(f32, f32), Double(f64, f64), Integer(i128, i128), + Decimal(Decimal, Decimal), } fn get_pattern_value( diff --git a/lib/src/store/numeric_encoder.rs b/lib/src/store/numeric_encoder.rs index 5f74a9d2..3d8a8bc0 100644 --- a/lib/src/store/numeric_encoder.rs +++ b/lib/src/store/numeric_encoder.rs @@ -3,6 +3,7 @@ use model::vocab::rdf; use model::vocab::xsd; use model::*; use ordered_float::OrderedFloat; +use rust_decimal::Decimal; use std::io::Read; use std::io::Write; use std::ops::Deref; @@ -60,6 +61,7 @@ const TYPE_BOOLEAN_LITERAL_FALSE: u8 = 8; const TYPE_FLOAT_LITERAL: u8 = 9; const TYPE_DOUBLE_LITERAL: u8 = 10; const TYPE_INTEGER_LITERAL: u8 = 11; +const TYPE_DECIMAL_LITERAL: u8 = 12; pub static ENCODED_DEFAULT_GRAPH: EncodedTerm = EncodedTerm::DefaultGraph {}; pub static ENCODED_EMPTY_SIMPLE_LITERAL: EncodedTerm = EncodedTerm::SimpleLiteral { @@ -106,6 +108,7 @@ pub enum EncodedTerm { FloatLiteral(OrderedFloat), DoubleLiteral(OrderedFloat), IntegerLiteral(i128), + DecimalLiteral(Decimal), } impl EncodedTerm { @@ -133,6 +136,7 @@ impl EncodedTerm { EncodedTerm::FloatLiteral(_) => true, EncodedTerm::DoubleLiteral(_) => true, EncodedTerm::IntegerLiteral(_) => true, + EncodedTerm::DecimalLiteral(_) => true, _ => false, } } @@ -150,6 +154,7 @@ impl EncodedTerm { EncodedTerm::FloatLiteral(..) => Some(ENCODED_XSD_FLOAT_NAMED_NODE), EncodedTerm::DoubleLiteral(..) => Some(ENCODED_XSD_DOUBLE_NAMED_NODE), EncodedTerm::IntegerLiteral(..) => Some(ENCODED_XSD_INTEGER_NAMED_NODE), + EncodedTerm::DecimalLiteral(..) => Some(ENCODED_XSD_DECIMAL_NAMED_NODE), _ => None, } } @@ -168,6 +173,7 @@ impl EncodedTerm { EncodedTerm::FloatLiteral(_) => TYPE_FLOAT_LITERAL, EncodedTerm::DoubleLiteral(_) => TYPE_DOUBLE_LITERAL, EncodedTerm::IntegerLiteral(_) => TYPE_INTEGER_LITERAL, + EncodedTerm::DecimalLiteral(_) => TYPE_DECIMAL_LITERAL, } } } @@ -196,6 +202,12 @@ impl From for EncodedTerm { } } +impl From for EncodedTerm { + fn from(value: Decimal) -> Self { + EncodedTerm::DecimalLiteral(value) + } +} + #[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash)] pub struct EncodedQuad { pub subject: EncodedTerm, @@ -264,6 +276,11 @@ impl TermReader for R { TYPE_INTEGER_LITERAL => Ok(EncodedTerm::IntegerLiteral( self.read_i128::()?, )), + TYPE_DECIMAL_LITERAL => { + let mut buffer = [0 as u8; 16]; + self.read_exact(&mut buffer)?; + Ok(EncodedTerm::DecimalLiteral(Decimal::deserialize(buffer))) + } _ => Err("the term buffer has an invalid type id".into()), } } @@ -347,6 +364,7 @@ impl TermWriter for R { EncodedTerm::FloatLiteral(value) => self.write_f32::(*value)?, EncodedTerm::DoubleLiteral(value) => self.write_f64::(*value)?, EncodedTerm::IntegerLiteral(value) => self.write_i128::(value)?, + EncodedTerm::DecimalLiteral(value) => self.write_all(&value.serialize())?, } Ok(()) } @@ -431,6 +449,11 @@ impl Encoder { .to_integer() .ok_or_else(|| Error::from("integer literal without integer value"))? .into() + } else if literal.is_decimal() { + literal + .to_decimal() + .ok_or_else(|| Error::from("decimal literal without decimal value"))? + .into() } else { EncodedTerm::TypedLiteral { value_id: self.encode_str_value(&literal.value())?, @@ -510,6 +533,7 @@ impl Encoder { EncodedTerm::FloatLiteral(value) => Ok(Literal::from(*value).into()), EncodedTerm::DoubleLiteral(value) => Ok(Literal::from(*value).into()), EncodedTerm::IntegerLiteral(value) => Ok(Literal::from(value).into()), + EncodedTerm::DecimalLiteral(value) => Ok(Literal::from(value).into()), } }