From 1499c2c96cad162014a45149175c2dfa7bfe5373 Mon Sep 17 00:00:00 2001 From: Tpt Date: Mon, 26 Nov 2018 18:28:18 +0100 Subject: [PATCH] Adds native support of xsd:date and xsd:time without duration --- lib/src/model/literal.rs | 64 +++++++++++++++++++++++++++++-- lib/src/sparql/eval.rs | 47 ++++++++++++++++++++++- lib/src/sparql/plan.rs | 10 ++++- lib/src/store/numeric_encoder.rs | 65 ++++++++++++++++++++++++++++++-- 4 files changed, 177 insertions(+), 9 deletions(-) diff --git a/lib/src/model/literal.rs b/lib/src/model/literal.rs index 351c04b2..6459f404 100644 --- a/lib/src/model/literal.rs +++ b/lib/src/model/literal.rs @@ -2,9 +2,7 @@ use crate::model::named_node::NamedNode; use crate::model::vocab::rdf; use crate::model::vocab::xsd; use crate::utils::Escaper; -use chrono::DateTime; -use chrono::FixedOffset; -use chrono::NaiveDateTime; +use chrono::prelude::*; use num_traits::identities::Zero; use num_traits::FromPrimitive; use num_traits::One; @@ -50,6 +48,8 @@ enum LiteralContent { Double(OrderedFloat), Integer(i128), Decimal(Decimal), + NaiveDate(NaiveDate), + NaiveTime(NaiveTime), DateTime(DateTime), NaiveDateTime(NaiveDateTime), TypedLiteral { value: String, datatype: NamedNode }, @@ -106,6 +106,16 @@ impl Literal { Ok(value) => LiteralContent::Decimal(value), Err(_) => LiteralContent::TypedLiteral { value, datatype }, } + } else if datatype == *xsd::DATE { + match NaiveDate::parse_from_str(&value, "%Y-%m-%d") { + Ok(value) => LiteralContent::NaiveDate(value), + Err(_) => LiteralContent::TypedLiteral { value, datatype }, + } + } else if datatype == *xsd::TIME { + match NaiveTime::parse_from_str(&value, "%H:%M:%S") { + Ok(value) => LiteralContent::NaiveTime(value), + Err(_) => LiteralContent::TypedLiteral { value, datatype }, + } } else if datatype == *xsd::DATE_TIME || datatype == *xsd::DATE_TIME_STAMP { match DateTime::parse_from_rfc3339(&value) { Ok(value) => LiteralContent::DateTime(value), @@ -142,6 +152,8 @@ impl Literal { 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::NaiveDate(value) => Cow::Owned(value.to_string()), + LiteralContent::NaiveTime(value) => Cow::Owned(value.to_string()), LiteralContent::DateTime(value) => Cow::Owned(value.to_string()), LiteralContent::NaiveDateTime(value) => Cow::Owned(value.to_string()), } @@ -169,6 +181,8 @@ impl Literal { LiteralContent::Double(_) => &xsd::DOUBLE, LiteralContent::Integer(_) => &xsd::INTEGER, LiteralContent::Decimal(_) => &xsd::DECIMAL, + LiteralContent::NaiveDate(_) => &xsd::DATE, + LiteralContent::NaiveTime(_) => &xsd::TIME, LiteralContent::DateTime(_) | LiteralContent::NaiveDateTime(_) => &xsd::DATE_TIME, LiteralContent::TypedLiteral { ref datatype, .. } => datatype, } @@ -233,6 +247,22 @@ impl Literal { } } + /// Checks if the literal has the datatype [xsd:date](http://www.w3.org/2001/XMLSchema#date) and is valid + pub fn is_date(&self) -> bool { + match self.0 { + LiteralContent::NaiveDate(_) => true, + _ => false, + } + } + + /// Checks if the literal has the datatype [xsd:date](http://www.w3.org/2001/XMLSchema#time) and is valid + pub fn is_time(&self) -> bool { + match self.0 { + LiteralContent::NaiveTime(_) => true, + _ => false, + } + } + /// Checks if the literal has the datatype [xsd:dateTime](http://www.w3.org/2001/XMLSchema#dateTime) or one of its sub datatype and is valid pub fn is_date_time(&self) -> bool { match self.0 { @@ -328,6 +358,22 @@ impl Literal { } } + /// Returns the value of this literal as NaiveDate if possible + pub(crate) fn to_date(&self) -> Option { + match self.0 { + LiteralContent::NaiveDate(value) => Some(value), + _ => None, + } + } + + /// Returns the value of this literal as NaiveTime if possible + pub(crate) fn to_time(&self) -> Option { + match self.0 { + LiteralContent::NaiveTime(value) => Some(value), + _ => None, + } + } + /// Returns the value of this literal as NaiveDateTime if possible pub(crate) fn to_date_time(&self) -> Option { match self.0 { @@ -437,6 +483,18 @@ impl From for Literal { } } +impl From for Literal { + fn from(value: NaiveDate) -> Self { + Literal(LiteralContent::NaiveDate(value)) + } +} + +impl From for Literal { + fn from(value: NaiveTime) -> Self { + Literal(LiteralContent::NaiveTime(value)) + } +} + impl From> for Literal { fn from(value: DateTime) -> Self { Literal(LiteralContent::DateTime(value)) diff --git a/lib/src/sparql/eval.rs b/lib/src/sparql/eval.rs index b82a56b3..cd6ce6a9 100644 --- a/lib/src/sparql/eval.rs +++ b/lib/src/sparql/eval.rs @@ -5,8 +5,7 @@ use crate::sparql::plan::*; use crate::store::encoded::EncodedQuadsStore; use crate::store::numeric_encoder::*; use crate::Result; -use chrono::DateTime; -use chrono::NaiveDateTime; +use chrono::prelude::*; use language_tags::LanguageTag; use num_traits::identities::Zero; use num_traits::FromPrimitive; @@ -658,6 +657,28 @@ impl SimpleEvaluator { )), _ => None, }, + PlanExpression::DateCast(e) => match self.eval_expression(e, tuple)? { + EncodedTerm::NaiveDate(value) => Some(value.into()), + EncodedTerm::DateTime(value) => Some(value.date().naive_utc().into()), //TODO: use date with timezone + EncodedTerm::NaiveDateTime(value) => Some(value.date().into()), + EncodedTerm::SimpleLiteral { value_id } + | EncodedTerm::StringLiteral { value_id } => { + let value = self.store.get_str(value_id).ok()?; + Some(NaiveDate::parse_from_str(&value, "%Y-%m-%d").ok()?.into()) + } + _ => None, + }, + PlanExpression::TimeCast(e) => match self.eval_expression(e, tuple)? { + EncodedTerm::NaiveTime(value) => Some(value.into()), + EncodedTerm::DateTime(value) => Some(value.time().into()), + EncodedTerm::NaiveDateTime(value) => Some(value.time().into()), + EncodedTerm::SimpleLiteral { value_id } + | EncodedTerm::StringLiteral { value_id } => { + let value = self.store.get_str(value_id).ok()?; + Some(NaiveTime::parse_from_str(&value, "%H:%M:%S").ok()?.into()) + } + _ => None, + }, PlanExpression::DateTimeCast(e) => match self.eval_expression(e, tuple)? { EncodedTerm::DateTime(value) => Some(value.into()), EncodedTerm::NaiveDateTime(value) => Some(value.into()), @@ -709,6 +730,8 @@ impl SimpleEvaluator { EncodedTerm::DoubleLiteral(value) => self.store.insert_str(&value.to_string()).ok(), EncodedTerm::IntegerLiteral(value) => self.store.insert_str(&value.to_string()).ok(), EncodedTerm::DecimalLiteral(value) => self.store.insert_str(&value.to_string()).ok(), + EncodedTerm::NaiveDate(value) => self.store.insert_str(&value.to_string()).ok(), + EncodedTerm::NaiveTime(value) => self.store.insert_str(&value.to_string()).ok(), EncodedTerm::DateTime(value) => self.store.insert_str(&value.to_string()).ok(), EncodedTerm::NaiveDateTime(value) => self.store.insert_str(&value.to_string()).ok(), } @@ -904,6 +927,26 @@ impl SimpleEvaluator { EncodedTerm::DecimalLiteral(b) => a.partial_cmp(&b), _ => None, }, + EncodedTerm::NaiveDate(a) => if let EncodedTerm::NaiveDate(ref b) = b { + a.partial_cmp(b) + } else { + None + }, + EncodedTerm::NaiveTime(a) => if let EncodedTerm::NaiveTime(ref b) = b { + a.partial_cmp(b) + } else { + None + }, + EncodedTerm::DateTime(a) => if let EncodedTerm::DateTime(ref b) = b { + a.partial_cmp(b) + } else { + None + }, + EncodedTerm::NaiveDateTime(a) => if let EncodedTerm::NaiveDateTime(ref b) = b { + a.partial_cmp(b) + } else { + None + }, _ => None, } } diff --git a/lib/src/sparql/plan.rs b/lib/src/sparql/plan.rs index 0747884a..13e476d3 100644 --- a/lib/src/sparql/plan.rs +++ b/lib/src/sparql/plan.rs @@ -245,6 +245,8 @@ pub enum PlanExpression { FloatCast(Box), DecimalCast(Box), IntegerCast(Box), + DateCast(Box), + TimeCast(Box), DateTimeCast(Box), StringCast(Box), } @@ -295,6 +297,8 @@ impl PlanExpression { | PlanExpression::FloatCast(e) | PlanExpression::IntegerCast(e) | PlanExpression::DecimalCast(e) + | PlanExpression::DateCast(e) + | PlanExpression::TimeCast(e) | PlanExpression::DateTimeCast(e) | PlanExpression::StringCast(e) => { e.add_variables(set); @@ -690,7 +694,11 @@ impl<'a, S: EncodedQuadsStore> PlanBuilder<'a, S> { variables, "integer", )? - } else if *name == *xsd::DATE_TIME { + } else if *name == *xsd::DATE { + self.build_cast(parameters, PlanExpression::DateCast, variables, "date")? + } else if *name == *xsd::TIME { + self.build_cast(parameters, PlanExpression::TimeCast, variables, "time")? + } else if *name == *xsd::DATE_TIME { self.build_cast( parameters, PlanExpression::DateTimeCast, diff --git a/lib/src/store/numeric_encoder.rs b/lib/src/store/numeric_encoder.rs index 720c7fb7..86203ace 100644 --- a/lib/src/store/numeric_encoder.rs +++ b/lib/src/store/numeric_encoder.rs @@ -4,9 +4,7 @@ use crate::model::*; use crate::utils::MutexPoisonError; use crate::Result; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; -use chrono::DateTime; -use chrono::FixedOffset; -use chrono::NaiveDateTime; +use chrono::prelude::*; use failure::format_err; use ordered_float::OrderedFloat; use rust_decimal::Decimal; @@ -28,6 +26,8 @@ const XSD_DOUBLE_ID: u64 = 5; const XSD_INTEGER_ID: u64 = 6; const XSD_DECIMAL_ID: u64 = 7; const XSD_DATE_TIME_ID: u64 = 8; +const XSD_DATE_ID: u64 = 9; +const XSD_TIME_ID: u64 = 10; pub trait StringStore { fn insert_str(&self, value: &str) -> Result; @@ -45,6 +45,8 @@ pub trait StringStore { && XSD_INTEGER_ID == self.insert_str(xsd::INTEGER.as_str())? && XSD_DECIMAL_ID == self.insert_str(xsd::DECIMAL.as_str())? && XSD_DATE_TIME_ID == self.insert_str(xsd::DATE_TIME.as_str())? + && XSD_DATE_ID == self.insert_str(xsd::DATE.as_str())? + && XSD_TIME_ID == self.insert_str(xsd::TIME.as_str())? { Ok(()) } else { @@ -131,6 +133,8 @@ const TYPE_INTEGER_LITERAL: u8 = 11; const TYPE_DECIMAL_LITERAL: u8 = 12; const TYPE_DATE_TIME_LITERAL: u8 = 13; const TYPE_NAIVE_DATE_TIME_LITERAL: u8 = 14; +const TYPE_NAIVE_DATE_LITERAL: u8 = 15; +const TYPE_NAIVE_TIME_LITERAL: u8 = 16; pub static ENCODED_DEFAULT_GRAPH: EncodedTerm = EncodedTerm::DefaultGraph {}; pub static ENCODED_EMPTY_SIMPLE_LITERAL: EncodedTerm = EncodedTerm::SimpleLiteral { @@ -160,6 +164,12 @@ pub static ENCODED_XSD_INTEGER_NAMED_NODE: EncodedTerm = EncodedTerm::NamedNode pub static ENCODED_XSD_DECIMAL_NAMED_NODE: EncodedTerm = EncodedTerm::NamedNode { iri_id: XSD_DECIMAL_ID, }; +pub static ENCODED_XSD_DATE_NAMED_NODE: EncodedTerm = EncodedTerm::NamedNode { + iri_id: XSD_DATE_ID, +}; +pub static ENCODED_XSD_TIME_NAMED_NODE: EncodedTerm = EncodedTerm::NamedNode { + iri_id: XSD_TIME_ID, +}; pub static ENCODED_XSD_DATE_TIME_NAMED_NODE: EncodedTerm = EncodedTerm::NamedNode { iri_id: XSD_DATE_TIME_ID, }; @@ -178,6 +188,8 @@ pub enum EncodedTerm { DoubleLiteral(OrderedFloat), IntegerLiteral(i128), DecimalLiteral(Decimal), + NaiveDate(NaiveDate), + NaiveTime(NaiveTime), DateTime(DateTime), NaiveDateTime(NaiveDateTime), } @@ -208,6 +220,8 @@ impl EncodedTerm { | EncodedTerm::DoubleLiteral(_) | EncodedTerm::IntegerLiteral(_) | EncodedTerm::DecimalLiteral(_) + | EncodedTerm::NaiveDate(_) + | EncodedTerm::NaiveTime(_) | EncodedTerm::DateTime(_) | EncodedTerm::NaiveDateTime(_) => true, _ => false, @@ -228,6 +242,8 @@ impl EncodedTerm { EncodedTerm::DoubleLiteral(..) => Some(ENCODED_XSD_DOUBLE_NAMED_NODE), EncodedTerm::IntegerLiteral(..) => Some(ENCODED_XSD_INTEGER_NAMED_NODE), EncodedTerm::DecimalLiteral(..) => Some(ENCODED_XSD_DECIMAL_NAMED_NODE), + EncodedTerm::NaiveDate(..) => Some(ENCODED_XSD_DATE_NAMED_NODE), + EncodedTerm::NaiveTime(..) => Some(ENCODED_XSD_TIME_NAMED_NODE), EncodedTerm::DateTime(..) | EncodedTerm::NaiveDateTime(..) => { Some(ENCODED_XSD_DATE_TIME_NAMED_NODE) } @@ -250,6 +266,8 @@ impl EncodedTerm { EncodedTerm::DoubleLiteral(_) => TYPE_DOUBLE_LITERAL, EncodedTerm::IntegerLiteral(_) => TYPE_INTEGER_LITERAL, EncodedTerm::DecimalLiteral(_) => TYPE_DECIMAL_LITERAL, + EncodedTerm::NaiveDate(_) => TYPE_NAIVE_DATE_LITERAL, + EncodedTerm::NaiveTime(_) => TYPE_NAIVE_TIME_LITERAL, EncodedTerm::DateTime(_) => TYPE_DATE_TIME_LITERAL, EncodedTerm::NaiveDateTime(_) => TYPE_NAIVE_DATE_TIME_LITERAL, } @@ -286,6 +304,18 @@ impl From for EncodedTerm { } } +impl From for EncodedTerm { + fn from(value: NaiveDate) -> Self { + EncodedTerm::NaiveDate(value) + } +} + +impl From for EncodedTerm { + fn from(value: NaiveTime) -> Self { + EncodedTerm::NaiveTime(value) + } +} + impl From> for EncodedTerm { fn from(value: DateTime) -> Self { EncodedTerm::DateTime(value) @@ -377,6 +407,16 @@ impl TermReader for R { self.read_exact(&mut buffer)?; Ok(EncodedTerm::DecimalLiteral(Decimal::deserialize(buffer))) } + TYPE_NAIVE_DATE_LITERAL => Ok(EncodedTerm::NaiveDate( + NaiveDate::from_num_days_from_ce_opt(self.read_i32::()?) + .ok_or_else(|| format_err!("Invalid date serialization"))?, + )), + TYPE_NAIVE_TIME_LITERAL => Ok(EncodedTerm::NaiveTime( + NaiveTime::from_num_seconds_from_midnight_opt( + self.read_u32::()?, + self.read_u32::()?, + ).ok_or_else(|| format_err!("Invalid time serialization"))?, + )), TYPE_DATE_TIME_LITERAL => Ok(EncodedTerm::DateTime(DateTime::from_utc( NaiveDateTime::from_timestamp_opt( self.read_i64::()?, @@ -473,6 +513,13 @@ impl TermWriter for R { EncodedTerm::DoubleLiteral(value) => self.write_f64::(*value)?, EncodedTerm::IntegerLiteral(value) => self.write_i128::(value)?, EncodedTerm::DecimalLiteral(value) => self.write_all(&value.serialize())?, + EncodedTerm::NaiveDate(value) => { + self.write_i32::(value.num_days_from_ce())?; + } + EncodedTerm::NaiveTime(value) => { + self.write_u32::(value.num_seconds_from_midnight())?; + self.write_u32::(value.nanosecond())?; + } EncodedTerm::DateTime(value) => { self.write_i64::(value.timestamp())?; self.write_u32::(value.timestamp_subsec_nanos())?; @@ -571,6 +618,16 @@ impl Encoder { .to_decimal() .ok_or_else(|| format_err!("decimal literal without decimal value"))? .into() + } else if literal.is_date() { + literal + .to_date() + .ok_or_else(|| format_err!("date literal without date value"))? + .into() + } else if literal.is_time() { + literal + .to_time() + .ok_or_else(|| format_err!("time literal without time value"))? + .into() } else if literal.is_date_time_stamp() { literal .to_date_time_stamp() @@ -665,6 +722,8 @@ impl Encoder { EncodedTerm::DoubleLiteral(value) => Ok(Literal::from(*value).into()), EncodedTerm::IntegerLiteral(value) => Ok(Literal::from(value).into()), EncodedTerm::DecimalLiteral(value) => Ok(Literal::from(value).into()), + EncodedTerm::NaiveDate(value) => Ok(Literal::from(value).into()), + EncodedTerm::NaiveTime(value) => Ok(Literal::from(value).into()), EncodedTerm::DateTime(value) => Ok(Literal::from(value).into()), EncodedTerm::NaiveDateTime(value) => Ok(Literal::from(value).into()), }