Stop assuming JS platform when compiling to wasm32-unknown-unknown

- Adds the "js" feature to enable JS support
- Adds the "custom-now" feature to oxsdatatypes to inject a custom "now" implementation
  It is already possible for random with the getrandom "custom" feature

Issue #471
pull/577/head
Tpt 1 year ago committed by Thomas Tanon
parent 8a398db20e
commit 1e1ed65d3b
  1. 15
      .github/workflows/tests.yml
  2. 2
      js/Cargo.toml
  3. 5
      lib/Cargo.toml
  4. 6
      lib/oxsdatatypes/Cargo.toml
  5. 16
      lib/oxsdatatypes/README.md
  6. 35
      lib/oxsdatatypes/src/date_time.rs
  7. 5
      lib/oxsdatatypes/src/duration.rs
  8. 6
      lib/src/sparql/algebra.rs
  9. 68
      lib/src/sparql/eval.rs
  10. 33
      lib/src/sparql/mod.rs

@ -75,6 +75,17 @@ jobs:
- run: cargo clippy --lib --tests --target wasm32-wasi
working-directory: ./lib
clippy_wasm_unknown:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
submodules: true
- run: rustup update && rustup target add wasm32-unknown-unknown && rustup component add clippy
- uses: Swatinem/rust-cache@v2
- run: cargo clippy --lib --tests --target wasm32-unknown-unknown --features getrandom/custom --features oxsdatatypes/custom-now
working-directory: ./lib
clippy_msv:
runs-on: ubuntu-latest
steps:
@ -158,7 +169,7 @@ jobs:
submodules: true
- run: rustup update
- uses: Swatinem/rust-cache@v2
- run: cargo test --all-features
- run: cargo test
env:
RUST_BACKTRACE: 1
@ -185,7 +196,7 @@ jobs:
- run: rustup update
- uses: Swatinem/rust-cache@v2
- run: Remove-Item -LiteralPath "C:\msys64\" -Force -Recurse
- run: cargo test --all-features
- run: cargo test
env:
RUST_BACKTRACE: 1

@ -14,7 +14,7 @@ crate-type = ["cdylib"]
name = "oxigraph"
[dependencies]
oxigraph = { version = "0.4.0-alpha.1-dev", path="../lib" }
oxigraph = { version = "0.4.0-alpha.1-dev", path="../lib", features = ["js"] }
wasm-bindgen = "0.2"
js-sys = "0.3"
console_error_panic_hook = "0.1"

@ -16,6 +16,7 @@ rust-version = "1.65"
[features]
default = []
js = ["getrandom/js", "oxsdatatypes/js", "js-sys"]
http_client = ["oxhttp", "oxhttp/rustls"]
rocksdb_debug = []
@ -46,8 +47,8 @@ oxrocksdb-sys = { version = "0.4.0-alpha.1-dev", path="../oxrocksdb-sys" }
oxhttp = { version = "0.1", optional = true }
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies]
getrandom = { version = "0.2", features = ["js"] }
js-sys = "0.3"
getrandom = "0.2"
js-sys = { version = "0.3", optional = true }
[target.'cfg(not(target_family = "wasm"))'.dev-dependencies]
criterion = "0.5"

@ -13,8 +13,12 @@ An implementation of some XSD datatypes for SPARQL implementations
edition = "2021"
rust-version = "1.65"
[features]
js = ["js-sys"]
custom-now = []
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies]
js-sys = "0.3"
js-sys = { version = "0.3", optional = true }
[package.metadata.docs.rs]
all-features = true

@ -32,6 +32,22 @@ Each datatype provides:
* `from_be_bytes` and `to_be_bytes` methods for serialization.
### `DateTime::now` behavior
The `DateTime::now()` function needs special OS support.
Currently:
- If the `custom-now` feature is enabled, a function computing `now` must be set:
```rust
use oxsdatatypes::{DateTimeError, Duration};
#[no_mangle]
fn custom_ox_now() -> Result<Duration, DateTimeError> {
unimplemented!("now implementation")
}
```
- For `wasm32-unknown-unknown` if the `js` feature is enabled the `Date.now()` ECMAScript API is used.
- For all other targets `SystemTime::now()` is used.
## License
This project is licensed under either of

@ -191,7 +191,7 @@ impl DateTime {
pub fn checked_sub_day_time_duration(self, rhs: impl Into<DayTimeDuration>) -> Option<Self> {
let rhs = rhs.into();
Some(Self {
timestamp: self.timestamp.checked_sub_seconds(rhs.all_seconds())?,
timestamp: self.timestamp.checked_sub_seconds(rhs.as_seconds())?,
})
}
@ -1757,7 +1757,22 @@ impl Timestamp {
}
}
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
#[cfg(feature = "custom-now")]
#[allow(unsafe_code)]
pub fn since_unix_epoch() -> Result<Duration, DateTimeError> {
extern "Rust" {
fn custom_ox_now() -> Result<Duration, DateTimeError>;
}
unsafe { custom_ox_now() }
}
#[cfg(all(
feature = "js",
not(feature = "custom-now"),
target_family = "wasm",
target_os = "unknown"
))]
fn since_unix_epoch() -> Result<Duration, DateTimeError> {
Ok(Duration::new(
0,
@ -1766,7 +1781,10 @@ fn since_unix_epoch() -> Result<Duration, DateTimeError> {
))
}
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
#[cfg(not(any(
feature = "custom-now",
all(feature = "js", target_family = "wasm", target_os = "unknown")
)))]
fn since_unix_epoch() -> Result<Duration, DateTimeError> {
use std::time::SystemTime;
@ -2605,6 +2623,17 @@ mod tests {
Ok(())
}
#[cfg(feature = "custom-now")]
#[test]
fn custom_now() {
#[no_mangle]
fn custom_ox_now() -> Result<Duration, DateTimeError> {
Ok(Duration::default())
}
assert!(DateTime::now().is_ok());
}
#[cfg(not(feature = "custom-now"))]
#[test]
fn now() -> Result<(), XsdParseError> {
let now = DateTime::now().unwrap();

@ -85,7 +85,7 @@ impl Duration {
#[inline]
#[must_use]
pub(super) const fn all_seconds(self) -> Decimal {
self.day_time.all_seconds()
self.day_time.as_seconds()
}
#[inline]
@ -443,8 +443,9 @@ impl DayTimeDuration {
self.seconds.checked_rem(60).unwrap()
}
/// The duration in seconds.
#[inline]
pub(super) const fn all_seconds(self) -> Decimal {
pub const fn as_seconds(self) -> Decimal {
self.seconds
}

@ -6,10 +6,10 @@
use crate::model::*;
use crate::sparql::eval::Timer;
use oxsdatatypes::DayTimeDuration;
use spargebra::GraphUpdateOperation;
use std::fmt;
use std::str::FromStr;
use std::time::Duration;
/// A parsed [SPARQL query](https://www.w3.org/TR/sparql11-query/).
///
@ -32,7 +32,7 @@ use std::time::Duration;
pub struct Query {
pub(super) inner: spargebra::Query,
pub(super) dataset: QueryDataset,
pub(super) parsing_duration: Option<Duration>,
pub(super) parsing_duration: Option<DayTimeDuration>,
}
impl Query {
@ -43,7 +43,7 @@ impl Query {
Ok(Self {
dataset: query.dataset,
inner: query.inner,
parsing_duration: Some(start.elapsed()),
parsing_duration: start.elapsed(),
})
}

@ -35,9 +35,6 @@ use std::hash::{Hash, Hasher};
use std::iter::Iterator;
use std::iter::{empty, once};
use std::rc::Rc;
use std::time::Duration as StdDuration;
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
use std::time::Instant;
use std::{fmt, io, str};
const REGEX_SIZE_LIMIT: usize = 1_000_000;
@ -258,16 +255,19 @@ impl SimpleEvaluator {
label: eval_node_label(pattern),
children: stat_children,
exec_count: Cell::new(0),
exec_duration: Cell::new(StdDuration::from_secs(0)),
exec_duration: Cell::new(self.run_stats.then(DayTimeDuration::default)),
});
if self.run_stats {
let stats = Rc::clone(&stats);
evaluator = Rc::new(move |tuple| {
let start = Timer::now();
let inner = evaluator(tuple);
stats
.exec_duration
.set(stats.exec_duration.get() + start.elapsed());
stats.exec_duration.set(
stats
.exec_duration
.get()
.and_then(|stat| stat.checked_add(start.elapsed()?)),
);
Box::new(StatsIterator {
inner,
stats: Rc::clone(&stats),
@ -5648,9 +5648,12 @@ impl Iterator for StatsIterator {
fn next(&mut self) -> Option<Result<EncodedTuple, EvaluationError>> {
let start = Timer::now();
let result = self.inner.next();
self.stats
.exec_duration
.set(self.stats.exec_duration.get() + start.elapsed());
self.stats.exec_duration.set(
self.stats
.exec_duration
.get()
.and_then(|stat| stat.checked_add(start.elapsed()?)),
);
if matches!(result, Some(Ok(_))) {
self.stats.exec_count.set(self.stats.exec_count.get() + 1);
}
@ -5662,7 +5665,7 @@ pub struct EvalNodeWithStats {
pub label: String,
pub children: Vec<Rc<EvalNodeWithStats>>,
pub exec_count: Cell<usize>,
pub exec_duration: Cell<StdDuration>,
pub exec_duration: Cell<Option<DayTimeDuration>>,
}
impl EvalNodeWithStats {
@ -5677,10 +5680,10 @@ impl EvalNodeWithStats {
if with_stats {
writer.write_event(JsonEvent::ObjectKey("number of results"))?;
writer.write_event(JsonEvent::Number(&self.exec_count.get().to_string()))?;
writer.write_event(JsonEvent::ObjectKey("duration in seconds"))?;
writer.write_event(JsonEvent::Number(
&self.exec_duration.get().as_secs_f32().to_string(),
))?;
if let Some(duration) = self.exec_duration.get() {
writer.write_event(JsonEvent::ObjectKey("duration in seconds"))?;
writer.write_event(JsonEvent::Number(&duration.as_seconds().to_string()))?;
}
}
writer.write_event(JsonEvent::ObjectKey("children"))?;
writer.write_event(JsonEvent::StartArray)?;
@ -5696,9 +5699,12 @@ impl fmt::Debug for EvalNodeWithStats {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut obj = f.debug_struct("Node");
obj.field("name", &self.label);
if self.exec_duration.get() > StdDuration::default() {
if let Some(exec_duration) = self.exec_duration.get() {
obj.field("number of results", &self.exec_count.get());
obj.field("duration in seconds", &self.exec_duration.get());
obj.field(
"duration in seconds",
&f32::from(Float::from(exec_duration.as_seconds())),
);
}
if !self.children.is_empty() {
obj.field("children", &self.children);
@ -5844,39 +5850,19 @@ fn format_list<T: ToString>(values: impl IntoIterator<Item = T>) -> String {
.join(", ")
}
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
pub struct Timer {
timestamp_ms: f64,
}
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
impl Timer {
pub fn now() -> Self {
Self {
timestamp_ms: js_sys::Date::now(),
}
}
pub fn elapsed(&self) -> StdDuration {
StdDuration::from_secs_f64((js_sys::Date::now() - self.timestamp_ms) / 1000.)
}
}
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
pub struct Timer {
instant: Instant,
start: Option<DateTime>,
}
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
impl Timer {
pub fn now() -> Self {
Self {
instant: Instant::now(),
start: DateTime::now().ok(),
}
}
pub fn elapsed(&self) -> StdDuration {
self.instant.elapsed()
pub fn elapsed(&self) -> Option<DayTimeDuration> {
DateTime::now().ok()?.checked_sub(self.start?)
}
}

@ -23,6 +23,7 @@ pub(crate) use crate::sparql::update::evaluate_update;
use crate::storage::StorageReader;
use json_event_parser::{JsonEvent, JsonWriter};
pub use oxrdf::{Variable, VariableNameParseError};
use oxsdatatypes::{DayTimeDuration, Float};
pub use sparesults::QueryResultsFormat;
pub use spargebra::ParseError;
use sparopt::algebra::GraphPattern;
@ -272,8 +273,8 @@ impl From<QueryOptions> for UpdateOptions {
pub struct QueryExplanation {
inner: Rc<EvalNodeWithStats>,
with_stats: bool,
parsing_duration: Option<Duration>,
planning_duration: Duration,
parsing_duration: Option<DayTimeDuration>,
planning_duration: Option<DayTimeDuration>,
}
impl QueryExplanation {
@ -284,13 +285,15 @@ impl QueryExplanation {
if let Some(parsing_duration) = self.parsing_duration {
writer.write_event(JsonEvent::ObjectKey("parsing duration in seconds"))?;
writer.write_event(JsonEvent::Number(
&parsing_duration.as_secs_f32().to_string(),
&parsing_duration.as_seconds().to_string(),
))?;
}
if let Some(planning_duration) = self.planning_duration {
writer.write_event(JsonEvent::ObjectKey("planning duration in seconds"))?;
writer.write_event(JsonEvent::Number(
&planning_duration.as_seconds().to_string(),
))?;
}
writer.write_event(JsonEvent::ObjectKey("planning duration in seconds"))?;
writer.write_event(JsonEvent::Number(
&self.planning_duration.as_secs_f32().to_string(),
))?;
writer.write_event(JsonEvent::ObjectKey("plan"))?;
self.inner.json_node(&mut writer, self.with_stats)?;
writer.write_event(JsonEvent::EndObject)
@ -299,6 +302,20 @@ impl QueryExplanation {
impl fmt::Debug for QueryExplanation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.inner)
let mut obj = f.debug_struct("QueryExplanation");
if let Some(parsing_duration) = self.parsing_duration {
obj.field(
"parsing duration in seconds",
&f32::from(Float::from(parsing_duration.as_seconds())),
);
}
if let Some(planning_duration) = self.planning_duration {
obj.field(
"planning duration in seconds",
&f32::from(Float::from(planning_duration.as_seconds())),
);
}
obj.field("tree", &self.inner);
obj.finish()
}
}

Loading…
Cancel
Save