Implements SEP-0002 ADJUST function

ADJUST is now only implemented when a new timezone is given. I am not sure "" for no timezone is the best way to go.

It is behind a sep-0002 feature in spargebra and sparql-smith and enabled by default in oxigraph.
pull/348/head
Tpt 2 years ago committed by Thomas Tanon
parent 3485833875
commit 719cde2eac
  1. 2
      lib/Cargo.toml
  2. 240
      lib/oxsdatatypes/src/date_time.rs
  3. 1
      lib/spargebra/Cargo.toml
  4. 6
      lib/spargebra/src/algebra.rs
  5. 4
      lib/spargebra/src/parser.rs
  6. 32
      lib/src/sparql/eval.rs
  7. 4
      lib/src/sparql/plan.rs
  8. 4
      lib/src/sparql/plan_builder.rs

@ -39,7 +39,7 @@ lazy_static = "1"
sysinfo = "0.27"
oxrdf = { version = "0.1.1", path="oxrdf", features = ["rdf-star", "oxsdatatypes"] }
oxsdatatypes = { version = "0.1.0", path="oxsdatatypes" }
spargebra = { version = "0.2.3", path="spargebra", features = ["rdf-star", "sep-0006"] }
spargebra = { version = "0.2.3", path="spargebra", features = ["rdf-star", "sep-0002", "sep-0006"] }
sparesults = { version = "0.1.3", path="sparesults", features = ["rdf-star"] }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]

@ -190,6 +190,14 @@ impl DateTime {
}
}
// [fn:adjust-dateTime-to-timezone](https://www.w3.org/TR/xpath-functions/#func-adjust-dateTime-to-timezone)
#[inline]
pub fn adjust(&self, timezone_offset: Option<TimezoneOffset>) -> Option<Self> {
Some(Self {
timestamp: self.timestamp.adjust(timezone_offset)?,
})
}
/// Checks if the two values are [identical](https://www.w3.org/TR/xmlschema11-2/#identity).
#[inline]
pub fn is_identical_with(&self, other: &Self) -> bool {
@ -372,6 +380,24 @@ impl Time {
.ok()
}
// [fn:adjust-time-to-timezone](https://www.w3.org/TR/xpath-functions/#func-adjust-time-to-timezone)
#[inline]
pub fn adjust(&self, timezone_offset: Option<TimezoneOffset>) -> Option<Self> {
DateTime::new(
1972,
12,
31,
self.hour(),
self.minute(),
self.second(),
self.timezone_offset(),
)
.ok()?
.adjust(timezone_offset)?
.try_into()
.ok()
}
/// Checks if the two values are [identical](https://www.w3.org/TR/xmlschema11-2/#identity).
#[inline]
pub fn is_identical_with(&self, other: &Self) -> bool {
@ -543,6 +569,24 @@ impl Date {
.ok()
}
// [fn:adjust-date-to-timezone](https://www.w3.org/TR/xpath-functions/#func-adjust-date-to-timezone)
#[inline]
pub fn adjust(&self, timezone_offset: Option<TimezoneOffset>) -> Option<Self> {
DateTime::new(
self.year(),
self.month(),
self.day(),
0,
0,
Decimal::default(),
self.timezone_offset(),
)
.ok()?
.adjust(timezone_offset)?
.try_into()
.ok()
}
/// Checks if the two values are [identical](https://www.w3.org/TR/xmlschema11-2/#identity).
#[inline]
pub fn is_identical_with(&self, other: &Self) -> bool {
@ -641,6 +685,13 @@ impl GYearMonth {
self.timestamp.timezone_offset()
}
#[inline]
pub fn adjust(&self, timezone_offset: Option<TimezoneOffset>) -> Option<Self> {
Some(Self {
timestamp: self.timestamp.adjust(timezone_offset)?,
})
}
#[inline]
pub fn to_be_bytes(self) -> [u8; 18] {
self.timestamp.to_be_bytes()
@ -747,6 +798,13 @@ impl GYear {
self.timestamp.timezone_offset()
}
#[inline]
pub fn adjust(&self, timezone_offset: Option<TimezoneOffset>) -> Option<Self> {
Some(Self {
timestamp: self.timestamp.adjust(timezone_offset)?,
})
}
#[inline]
pub fn to_be_bytes(self) -> [u8; 18] {
self.timestamp.to_be_bytes()
@ -864,6 +922,13 @@ impl GMonthDay {
self.timestamp.timezone_offset()
}
#[inline]
pub fn adjust(&self, timezone_offset: Option<TimezoneOffset>) -> Option<Self> {
Some(Self {
timestamp: self.timestamp.adjust(timezone_offset)?,
})
}
#[inline]
pub fn to_be_bytes(self) -> [u8; 18] {
self.timestamp.to_be_bytes()
@ -966,6 +1031,13 @@ impl GMonth {
self.timestamp.timezone_offset()
}
#[inline]
pub fn adjust(&self, timezone_offset: Option<TimezoneOffset>) -> Option<Self> {
Some(Self {
timestamp: self.timestamp.adjust(timezone_offset)?,
})
}
#[inline]
pub fn to_be_bytes(self) -> [u8; 18] {
self.timestamp.to_be_bytes()
@ -1082,6 +1154,13 @@ impl GDay {
self.timestamp.timezone_offset()
}
#[inline]
pub fn adjust(&self, timezone_offset: Option<TimezoneOffset>) -> Option<Self> {
Some(Self {
timestamp: self.timestamp.adjust(timezone_offset)?,
})
}
#[inline]
pub fn to_be_bytes(self) -> [u8; 18] {
self.timestamp.to_be_bytes()
@ -1210,7 +1289,7 @@ impl TryFrom<Duration> for TimezoneOffset {
impl From<TimezoneOffset> for DayTimeDuration {
#[inline]
fn from(value: TimezoneOffset) -> Self {
Self::new(i32::from(value.offset) * 60)
Self::new(i64::from(value.offset) * 60)
}
}
@ -1451,9 +1530,9 @@ impl Timestamp {
}
#[inline]
fn checked_add_seconds(&self, seconds: Decimal) -> Option<Self> {
fn checked_add_seconds(&self, seconds: impl Into<Decimal>) -> Option<Self> {
Some(Self {
value: self.value.checked_add(seconds)?,
value: self.value.checked_add(seconds.into())?,
timezone_offset: self.timezone_offset,
})
}
@ -1476,6 +1555,35 @@ impl Timestamp {
})
}
#[inline]
fn adjust(&self, timezone_offset: Option<TimezoneOffset>) -> Option<Self> {
Some(if let Some(from_timezone) = self.timezone_offset {
if let Some(to_timezone) = timezone_offset {
Self {
value: self.value, // We keep the timestamp
timezone_offset: Some(to_timezone),
}
} else {
Self {
value: self
.value
.checked_add(i64::from(from_timezone.offset) * 60)?, // We keep the literal value
timezone_offset: None,
}
}
} else if let Some(to_timezone) = timezone_offset {
Self {
value: self.value.checked_sub(i64::from(to_timezone.offset) * 60)?, // We keep the literal value
timezone_offset: Some(to_timezone),
}
} else {
Self {
value: self.value,
timezone_offset: None,
}
})
}
#[inline]
fn to_be_bytes(self) -> [u8; 18] {
let mut bytes = [0; 18];
@ -2448,6 +2556,132 @@ mod tests {
);
}
#[test]
fn adjust() {
assert_eq!(
DateTime::from_str("2002-03-07T10:00:00-07:00")
.unwrap()
.adjust(Some(
DayTimeDuration::from_str("PT10H")
.unwrap()
.try_into()
.unwrap()
))
.unwrap(),
DateTime::from_str("2002-03-08T03:00:00+10:00").unwrap()
);
assert_eq!(
DateTime::from_str("2002-03-07T00:00:00+01:00")
.unwrap()
.adjust(Some(
DayTimeDuration::from_str("-PT8H")
.unwrap()
.try_into()
.unwrap()
))
.unwrap(),
DateTime::from_str("2002-03-06T15:00:00-08:00").unwrap()
);
assert_eq!(
DateTime::from_str("2002-03-07T10:00:00")
.unwrap()
.adjust(None)
.unwrap(),
DateTime::from_str("2002-03-07T10:00:00").unwrap()
);
assert_eq!(
DateTime::from_str("2002-03-07T10:00:00-07:00")
.unwrap()
.adjust(None)
.unwrap(),
DateTime::from_str("2002-03-07T10:00:00").unwrap()
);
assert_eq!(
Date::from_str("2002-03-07")
.unwrap()
.adjust(Some(
DayTimeDuration::from_str("-PT10H")
.unwrap()
.try_into()
.unwrap()
))
.unwrap(),
Date::from_str("2002-03-07-10:00").unwrap()
);
assert_eq!(
Date::from_str("2002-03-07-07:00")
.unwrap()
.adjust(Some(
DayTimeDuration::from_str("-PT10H")
.unwrap()
.try_into()
.unwrap()
))
.unwrap(),
Date::from_str("2002-03-06-10:00").unwrap()
);
assert_eq!(
Date::from_str("2002-03-07").unwrap().adjust(None).unwrap(),
Date::from_str("2002-03-07").unwrap()
);
assert_eq!(
Date::from_str("2002-03-07-07:00")
.unwrap()
.adjust(None)
.unwrap(),
Date::from_str("2002-03-07").unwrap()
);
assert_eq!(
Time::from_str("10:00:00")
.unwrap()
.adjust(Some(
DayTimeDuration::from_str("-PT10H")
.unwrap()
.try_into()
.unwrap()
))
.unwrap(),
Time::from_str("10:00:00-10:00").unwrap()
);
assert_eq!(
Time::from_str("10:00:00-07:00")
.unwrap()
.adjust(Some(
DayTimeDuration::from_str("-PT10H")
.unwrap()
.try_into()
.unwrap()
))
.unwrap(),
Time::from_str("07:00:00-10:00").unwrap()
);
assert_eq!(
Time::from_str("10:00:00").unwrap().adjust(None).unwrap(),
Time::from_str("10:00:00").unwrap()
);
assert_eq!(
Time::from_str("10:00:00-07:00")
.unwrap()
.adjust(None)
.unwrap(),
Time::from_str("10:00:00").unwrap()
);
assert_eq!(
Time::from_str("10:00:00-07:00")
.unwrap()
.adjust(Some(
DayTimeDuration::from_str("PT10H")
.unwrap()
.try_into()
.unwrap()
))
.unwrap(),
Time::from_str("03:00:00+10:00").unwrap()
);
}
#[test]
fn now() {
let now = DateTime::now().unwrap();

@ -16,6 +16,7 @@ rust-version = "1.60"
[features]
default = []
rdf-star = ["oxrdf/rdf-star"]
sep-0002 = []
sep-0006 = []
[dependencies]

@ -376,6 +376,8 @@ pub enum Function {
Object,
#[cfg(feature = "rdf-star")]
IsTriple,
#[cfg(feature = "sep-0002")]
Adjust,
Custom(NamedNode),
}
@ -439,6 +441,8 @@ impl Function {
Self::Object => write!(f, "object"),
#[cfg(feature = "rdf-star")]
Self::IsTriple => write!(f, "istriple"),
#[cfg(feature = "sep-0002")]
Self::Adjust => write!(f, "adjust"),
Self::Custom(iri) => write!(f, "{iri}"),
}
}
@ -503,6 +507,8 @@ impl fmt::Display for Function {
Self::Object => write!(f, "OBJECT"),
#[cfg(feature = "rdf-star")]
Self::IsTriple => write!(f, "isTRIPLE"),
#[cfg(feature = "sep-0002")]
Self::Adjust => write!(f, "ADJUST"),
Self::Custom(iri) => iri.fmt(f),
}
}

@ -2109,6 +2109,10 @@ parser! {
i("isTriple") "(" _ e:Expression() _ ")" {?
#[cfg(feature = "rdf-star")]{Ok(Expression::FunctionCall(Function::IsTriple, vec![e]))}
#[cfg(not(feature = "rdf-star"))]{Err("The isTriple function is only available in SPARQL-star")}
} /
i("ADJUST") "(" _ a:Expression() _ "," _ b:Expression() _ ")" {?
#[cfg(feature = "sep-0002")]{Ok(Expression::FunctionCall(Function::Adjust, vec![a, b]))}
#[cfg(not(feature = "sep-0002"))]{Err("The ADJUST function is only available in SPARQL 1.2 SEP 0002")}
}
//[122]

@ -1563,6 +1563,38 @@ impl SimpleEvaluator {
})
})
}
PlanExpression::Adjust(dt, tz) => {
let dt = self.expression_evaluator(dt);
let tz = self.expression_evaluator(tz);
Rc::new(move |tuple| {
let timezone_offset = Some(
match tz(tuple)? {
EncodedTerm::DayTimeDurationLiteral(tz) => TimezoneOffset::try_from(tz),
EncodedTerm::DurationLiteral(tz) => TimezoneOffset::try_from(tz),
_ => return None,
}
.ok()?,
);
Some(match dt(tuple)? {
EncodedTerm::DateTimeLiteral(date_time) => {
date_time.adjust(timezone_offset)?.into()
}
EncodedTerm::TimeLiteral(time) => time.adjust(timezone_offset)?.into(),
EncodedTerm::DateLiteral(date) => date.adjust(timezone_offset)?.into(),
EncodedTerm::GYearMonthLiteral(year_month) => {
year_month.adjust(timezone_offset)?.into()
}
EncodedTerm::GYearLiteral(year) => year.adjust(timezone_offset)?.into(),
EncodedTerm::GMonthDayLiteral(month_day) => {
month_day.adjust(timezone_offset)?.into()
}
EncodedTerm::GDayLiteral(day) => day.adjust(timezone_offset)?.into(),
EncodedTerm::GMonthLiteral(month) => month.adjust(timezone_offset)?.into(),
_ => return None,
})
})
}
PlanExpression::Now => {
let now = self.now;
Rc::new(move |_| Some(now.into()))

@ -460,6 +460,7 @@ pub enum PlanExpression {
Predicate(Box<Self>),
Object(Box<Self>),
IsTriple(Box<Self>),
Adjust(Box<Self>, Box<Self>),
BooleanCast(Box<Self>),
DoubleCast(Box<Self>),
FloatCast(Box<Self>),
@ -557,7 +558,8 @@ impl PlanExpression {
| Self::StrDt(a, b)
| Self::SameTerm(a, b)
| Self::SubStr(a, b, None)
| Self::Regex(a, b, None) => {
| Self::Regex(a, b, None)
| Self::Adjust(a, b) => {
a.lookup_used_variables(callback);
b.lookup_used_variables(callback);
}

@ -651,6 +651,10 @@ impl<'a> PlanBuilder<'a> {
Function::IsTriple => PlanExpression::IsTriple(Box::new(
self.build_for_expression(&parameters[0], variables, graph_name)?,
)),
Function::Adjust => PlanExpression::Adjust(
Box::new(self.build_for_expression(&parameters[0], variables, graph_name)?),
Box::new(self.build_for_expression(&parameters[1], variables, graph_name)?),
),
Function::Custom(name) => {
if self.custom_functions.contains_key(name) {
PlanExpression::CustomFunction(

Loading…
Cancel
Save