parent
77edc05ced
commit
fdcaf65a8d
@ -1,45 +0,0 @@ |
||||
[package] |
||||
name = "oxigraph-cli" |
||||
version.workspace = true |
||||
authors.workspace = true |
||||
license.workspace = true |
||||
readme = "README.md" |
||||
keywords = ["RDF", "SPARQL", "graph-database", "database"] |
||||
categories = ["command-line-utilities", "database"] |
||||
repository = "https://github.com/oxigraph/oxigraph/tree/main/cli" |
||||
homepage = "https://oxigraph.org/cli/" |
||||
description = """ |
||||
Oxigraph CLI tool and SPARQL HTTP server |
||||
""" |
||||
edition.workspace = true |
||||
rust-version.workspace = true |
||||
|
||||
[[bin]] |
||||
name = "oxigraph" |
||||
path = "src/main.rs" |
||||
doc = false |
||||
|
||||
[features] |
||||
default = [] |
||||
native-tls = ["oxigraph/http-client-native-tls"] |
||||
rustls-native = ["oxigraph/http-client-rustls-native"] |
||||
rustls-webpki = ["oxigraph/http-client-rustls-webpki"] |
||||
|
||||
[dependencies] |
||||
anyhow.workspace = true |
||||
clap = { workspace = true, features = ["derive"] } |
||||
flate2.workspace = true |
||||
oxhttp = { workspace = true, features = ["flate2"] } |
||||
oxigraph.workspace = true |
||||
oxiri.workspace = true |
||||
rand.workspace = true |
||||
rayon-core.workspace = true |
||||
url.workspace = true |
||||
|
||||
[dev-dependencies] |
||||
assert_cmd.workspace = true |
||||
assert_fs.workspace = true |
||||
predicates.workspace = true |
||||
|
||||
[lints] |
||||
workspace = true |
@ -1,25 +0,0 @@ |
||||
FROM --platform=$BUILDPLATFORM rust:1-bookworm as builder |
||||
ARG BUILDARCH TARGETARCH |
||||
RUN apt-get update && \ |
||||
apt-get install -y libclang-dev clang && \ |
||||
if [ "$BUILDARCH" != "$TARGETARCH" ] && [ "$TARGETARCH" = "arm64" ] ; \ |
||||
then \ |
||||
apt-get install -y g++-aarch64-linux-gnu && \ |
||||
rustup target add aarch64-unknown-linux-gnu ; \ |
||||
fi |
||||
COPY . /oxigraph |
||||
WORKDIR /oxigraph/cli |
||||
RUN if [ "$BUILDARCH" != "$TARGETARCH" ] && [ "$TARGETARCH" = "arm64" ] ; \ |
||||
then \ |
||||
export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc && \ |
||||
export BINDGEN_EXTRA_CLANG_ARGS="--sysroot /usr/aarch64-linux-gnu" && \ |
||||
cargo build --release --target aarch64-unknown-linux-gnu --no-default-features --features rustls-webpki && \ |
||||
mv /oxigraph/target/aarch64-unknown-linux-gnu/release/oxigraph /oxigraph/target/release/oxigraph ; \ |
||||
else \ |
||||
cargo build --release --no-default-features --features rustls-webpki ; \ |
||||
fi |
||||
|
||||
FROM --platform=$TARGETPLATFORM gcr.io/distroless/cc-debian12 |
||||
COPY --from=builder /oxigraph/target/release/oxigraph /usr/local/bin/oxigraph |
||||
ENTRYPOINT [ "/usr/local/bin/oxigraph" ] |
||||
CMD [ "serve", "--location", "/data", "--bind", "0.0.0.0:7878" ] |
@ -1,284 +0,0 @@ |
||||
Oxigraph CLI |
||||
============ |
||||
|
||||
[![Latest Version](https://img.shields.io/crates/v/oxigraph-cli.svg)](https://crates.io/crates/oxigraph-cli) |
||||
[![Crates.io downloads](https://img.shields.io/crates/d/oxigraph-cli)](https://crates.io/crates/oxigraph-cli) |
||||
[![Conda](https://img.shields.io/conda/vn/conda-forge/oxigraph-server)](https://anaconda.org/conda-forge/oxigraph-server) |
||||
[![actions status](https://github.com/oxigraph/oxigraph/workflows/build/badge.svg)](https://github.com/oxigraph/oxigraph/actions) |
||||
[![Gitter](https://badges.gitter.im/oxigraph/community.svg)](https://gitter.im/oxigraph/community) |
||||
|
||||
Oxigraph CLI is a graph database implementing the [SPARQL](https://www.w3.org/TR/sparql11-overview/) standard. |
||||
It is packaged as a command-line tool allowing to manipulate RDF files, query them using SPARQL... |
||||
It also allows to spawn a HTTP server on top of the database. |
||||
|
||||
Oxigraph is in heavy development and SPARQL query evaluation has not been optimized yet. |
||||
|
||||
Oxigraph provides different installation methods for Oxigraph CLI: |
||||
* [`cargo install`](#installation) (multiplatform) |
||||
* [A Docker image](#using-a-docker-image) |
||||
* [A Homebrew formula](#homebrew) |
||||
* [A conda-forge package](https://anaconda.org/conda-forge/oxigraph-server) |
||||
* [Pre-built binaries](https://github.com/oxigraph/oxigraph/releases/latest) |
||||
|
||||
It is also usable as [a Rust library](https://crates.io/crates/oxigraph) and as [a Python library](https://pyoxigraph.readthedocs.io/). |
||||
|
||||
Oxigraph implements the following specifications: |
||||
* [SPARQL 1.1 Query](https://www.w3.org/TR/sparql11-query/), [SPARQL 1.1 Update](https://www.w3.org/TR/sparql11-update/), and [SPARQL 1.1 Federated Query](https://www.w3.org/TR/sparql11-federated-query/). |
||||
* [Turtle](https://www.w3.org/TR/turtle/), [TriG](https://www.w3.org/TR/trig/), [N-Triples](https://www.w3.org/TR/n-triples/), [N-Quads](https://www.w3.org/TR/n-quads/), and [RDF/XML](https://www.w3.org/TR/rdf-syntax-grammar/) RDF serialization formats for both data ingestion and retrieval. |
||||
* [SPARQL Query Results XML Format](https://www.w3.org/TR/rdf-sparql-XMLres/), [SPARQL 1.1 Query Results JSON Format](https://www.w3.org/TR/sparql11-results-json/) and [SPARQL 1.1 Query Results CSV and TSV Formats](https://www.w3.org/TR/sparql11-results-csv-tsv/). |
||||
* [SPARQL 1.1 Protocol](https://www.w3.org/TR/sparql11-protocol/#query-operation) and [SPARQL 1.1 Graph Store HTTP Protocol](https://www.w3.org/TR/sparql11-http-rdf-update/). |
||||
|
||||
A preliminary benchmark [is provided](../bench/README.md). |
||||
|
||||
Note that Oxigraph CLI was previously named Oxigraph Server before version 0.4. Older versions are available under [this name](https://crates.io/crates/oxigraph_server). |
||||
|
||||
[![Packaging status](https://repology.org/badge/vertical-allrepos/oxigraph.svg)](https://repology.org/project/oxigraph/versions) |
||||
|
||||
## Installation |
||||
|
||||
You need to have [a recent stable version of Rust and Cargo installed](https://www.rust-lang.org/tools/install). |
||||
|
||||
To download, build and install the latest released version run `cargo install oxigraph-cli`. |
||||
There is no need to clone the git repository. |
||||
|
||||
To compile the command-line tool from source, clone this git repository including its submodules (`git clone --recursive https://github.com/oxigraph/oxigraph.git`), and execute `cargo build --release` in the `cli` directory to compile the full binary after having downloaded its dependencies. |
||||
It will create a fat binary in `target/release/oxigraph`. |
||||
|
||||
Some build options (cargo features) are available: |
||||
- `rocksdb-pkg-config`: links against an already compiled rocksdb shared library found using [pkg-config](https://crates.io/crates/pkg-config). |
||||
- `native-tls`: Enables Oxigraph HTTP client for query federation using the host OS TLS stack (enabled by default). |
||||
- `rustls-native` Enables Oxigraph HTTP client for query federation using [Rustls](https://crates.io/crates/rustls) and the native certificates. |
||||
- `rustls-webpki` Enables Oxigraph HTTP client for query federation using [Rustls](https://crates.io/crates/rustls) and the [Common CA Database](https://www.ccadb.org/) certificates. |
||||
|
||||
|
||||
## Usage |
||||
|
||||
Run `oxigraph serve --location my_data_storage_directory` to start the server where `my_data_storage_directory` is the directory where you want Oxigraph data to be stored. It listens by default on `localhost:7878`. |
||||
|
||||
The server provides an HTML UI, based on [YASGUI](https://yasgui.triply.cc), with a form to execute SPARQL requests. |
||||
|
||||
It provides the following REST actions: |
||||
* `/query` allows evaluating SPARQL queries against the server repository following the [SPARQL 1.1 Protocol](https://www.w3.org/TR/sparql11-protocol/#query-operation). |
||||
For example: |
||||
```bash |
||||
curl -X POST -H 'Content-Type:application/sparql-query' \ |
||||
--data 'SELECT * WHERE { ?s ?p ?o } LIMIT 10' http://localhost:7878/query |
||||
``` |
||||
This action supports content negotiation and could return [Turtle](https://www.w3.org/TR/turtle/), [N-Triples](https://www.w3.org/TR/n-triples/), [RDF/XML](https://www.w3.org/TR/rdf-syntax-grammar/), [SPARQL Query Results XML Format](https://www.w3.org/TR/rdf-sparql-XMLres/) and [SPARQL Query Results JSON Format](https://www.w3.org/TR/sparql11-results-json/). |
||||
* `/update` allows to execute SPARQL updates against the server repository following the [SPARQL 1.1 Protocol](https://www.w3.org/TR/sparql11-protocol/#update-operation). |
||||
For example: |
||||
```sh |
||||
curl -X POST -H 'Content-Type: application/sparql-update' \ |
||||
--data 'DELETE WHERE { <http://example.com/s> ?p ?o }' http://localhost:7878/update |
||||
``` |
||||
* `/store` allows to retrieve and change the server content using the [SPARQL 1.1 Graph Store HTTP Protocol](https://www.w3.org/TR/sparql11-http-rdf-update/). |
||||
For example: |
||||
```sh |
||||
curl -f -X POST -H 'Content-Type:application/n-triples' \ |
||||
-T MY_FILE.nt "http://localhost:7878/store?graph=http://example.com/g" |
||||
``` |
||||
will add the N-Triples file `MY_FILE.nt` to the server dataset inside of the `http://example.com/g` named graph. |
||||
[Turtle](https://www.w3.org/TR/turtle/), [N-Triples](https://www.w3.org/TR/n-triples/) and [RDF/XML](https://www.w3.org/TR/rdf-syntax-grammar/) are supported. |
||||
It is also possible to `POST`, `PUT` and `GET` the complete RDF dataset on the server using RDF dataset formats ([TriG](https://www.w3.org/TR/trig/) and [N-Quads](https://www.w3.org/TR/n-quads/)) against the `/store` endpoint. |
||||
For example: |
||||
```sh |
||||
curl -f -X POST -H 'Content-Type:application/n-quads' \ |
||||
-T MY_FILE.nq http://localhost:7878/store |
||||
``` |
||||
will add the N-Quads file `MY_FILE.nq` to the server dataset. |
||||
|
||||
Use `oxigraph --help` to see the possible options when starting the server. |
||||
|
||||
It is also possible to load RDF data offline using bulk loading: |
||||
`oxigraph load --location my_data_storage_directory --file my_file.nq` |
||||
|
||||
## Using a Docker image |
||||
|
||||
### Display the help menu |
||||
```sh |
||||
docker run --rm ghcr.io/oxigraph/oxigraph --help |
||||
``` |
||||
|
||||
### Run the Webserver |
||||
Expose the server on port `7878` of the host machine, and save data on the local `./data` folder |
||||
```sh |
||||
docker run --rm -v $PWD/data:/data -p 7878:7878 ghcr.io/oxigraph/oxigraph serve --location /data --bind 0.0.0.0:7878 |
||||
``` |
||||
|
||||
You can then access it from your machine on port `7878`: |
||||
|
||||
```sh |
||||
# Open the GUI in a browser |
||||
firefox http://localhost:7878 |
||||
|
||||
# Post some data |
||||
curl http://localhost:7878/store?default -H 'Content-Type: text/turtle' -T ./data.ttl |
||||
|
||||
# Make a query |
||||
curl -X POST -H 'Accept: application/sparql-results+json' -H 'Content-Type: application/sparql-query' --data 'SELECT * WHERE { ?s ?p ?o } LIMIT 10' http://localhost:7878/query |
||||
|
||||
# Make an UPDATE |
||||
curl -X POST -H 'Content-Type: application/sparql-update' --data 'DELETE WHERE { <http://example.com/s> ?p ?o }' http://localhost:7878/update |
||||
``` |
||||
|
||||
### Run the Web server with basic authentication |
||||
|
||||
It can be useful to make Oxigraph SPARQL endpoint available publicly, with a layer of authentication on `/update` to be able to add data. |
||||
|
||||
You can do so by using a nginx basic authentication in an additional docker container with `docker-compose`. First create a `nginx.conf` file: |
||||
|
||||
```nginx |
||||
daemon off; |
||||
events { |
||||
worker_connections 1024; |
||||
} |
||||
http { |
||||
server { |
||||
server_name localhost; |
||||
listen 7878; |
||||
rewrite ^/(.*) /$1 break; |
||||
proxy_ignore_client_abort on; |
||||
proxy_set_header X-Real-IP $remote_addr; |
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
||||
proxy_set_header Host $http_host; |
||||
proxy_set_header Access-Control-Allow-Origin "*"; |
||||
location ~ ^(/|/query)$ { |
||||
proxy_pass http://oxigraph:7878; |
||||
proxy_pass_request_headers on; |
||||
} |
||||
location ~ ^(/update|/store)$ { |
||||
auth_basic "Oxigraph Administrator's Area"; |
||||
auth_basic_user_file /etc/nginx/.htpasswd; |
||||
proxy_pass http://oxigraph:7878; |
||||
proxy_pass_request_headers on; |
||||
} |
||||
} |
||||
} |
||||
``` |
||||
|
||||
Then a `docker-compose.yml` in the same folder, you can change the default user and password in the `environment` section: |
||||
|
||||
```yaml |
||||
version: "3" |
||||
services: |
||||
oxigraph: |
||||
image: ghcr.io/oxigraph/oxigraph:latest |
||||
## To build from local source code: |
||||
# build: |
||||
# context: . |
||||
# dockerfile: server/Dockerfile |
||||
volumes: |
||||
- ./data:/data |
||||
|
||||
nginx-auth: |
||||
image: nginx:1.21.4 |
||||
environment: |
||||
- OXIGRAPH_USER=oxigraph |
||||
- OXIGRAPH_PASSWORD=oxigraphy |
||||
volumes: |
||||
- ./nginx.conf:/etc/nginx/nginx.conf |
||||
## For multiple users: uncomment this line to mount a pre-generated .htpasswd |
||||
# - ./.htpasswd:/etc/nginx/.htpasswd |
||||
ports: |
||||
- "7878:7878" |
||||
entrypoint: "bash -c 'echo -n $OXIGRAPH_USER: >> /etc/nginx/.htpasswd && echo $OXIGRAPH_PASSWORD | openssl passwd -stdin -apr1 >> /etc/nginx/.htpasswd && /docker-entrypoint.sh nginx'" |
||||
``` |
||||
|
||||
Once the `docker-compose.yaml` and `nginx.conf` are ready, start the Oxigraph server and nginx proxy for authentication on http://localhost:7878: |
||||
|
||||
```sh |
||||
docker-compose up |
||||
``` |
||||
|
||||
Then it is possible to update the graph using basic authentication mechanisms. For example with `curl`: change `$OXIGRAPH_USER` and `$OXIGRAPH_PASSWORD`, or set them as environment variables, then run this command to insert a simple triple: |
||||
|
||||
```sh |
||||
curl -X POST -u $OXIGRAPH_USER:$OXIGRAPH_PASSWORD -H 'Content-Type: application/sparql-update' --data 'INSERT DATA { <http://example.com/s> <http://example.com/p> <http://example.com/o> }' http://localhost:7878/update |
||||
``` |
||||
|
||||
In case you want to have multiple users, you can comment the `entrypoint:` line in the `docker-compose.yml` file, uncomment the `.htpasswd` volume, then generate each user in the `.htpasswd` file with this command: |
||||
|
||||
```sh |
||||
htpasswd -Bbn $OXIGRAPH_USER $OXIGRAPH_PASSWORD >> .htpasswd |
||||
``` |
||||
|
||||
### Build the image |
||||
|
||||
You could easily build your own Docker image by cloning this repository with its submodules, and going to the root folder: |
||||
|
||||
```sh |
||||
git clone --recursive https://github.com/oxigraph/oxigraph.git |
||||
cd oxigraph |
||||
``` |
||||
|
||||
Then run this command to build the image locally: |
||||
|
||||
````sh |
||||
docker build -t ghcr.io/oxigraph/oxigraph -f server/Dockerfile . |
||||
```` |
||||
|
||||
## Homebrew |
||||
|
||||
Oxigraph maintains a [Homebrew](https://brew.sh) formula in [a custom tap](https://github.com/oxigraph/homebrew-oxigraph). |
||||
|
||||
To install Oxigraph server using Homebrew do: |
||||
```sh |
||||
brew tap oxigraph/oxigraph |
||||
brew install oxigraph |
||||
``` |
||||
It installs the `oxigraph` binary. [See the usage documentation to know how to use it](#usage). |
||||
|
||||
|
||||
## Systemd |
||||
|
||||
It is possible to run Oxigraph in the background using systemd. |
||||
|
||||
For that, you can use the following `oxigraph.service` file (it might be inserted into `/etc/systemd/system/` or `$HOME/.config/systemd/user`): |
||||
```ini |
||||
[Unit] |
||||
Description=Oxigraph database server |
||||
After=network-online.target |
||||
Wants=network-online.target |
||||
|
||||
[Service] |
||||
Type=notify |
||||
ExecStart=/PATH/TO/oxigraph serve --location /PATH/TO/OXIGRAPH/DATA |
||||
|
||||
[Install] |
||||
WantedBy=multi-user.target |
||||
``` |
||||
|
||||
## Migration guide |
||||
|
||||
### From 0.2 to 0.3 |
||||
* The cli API has been completely rewritten. To start the server run `oxigraph serve --location MY_STORAGE` instead of `oxigraph --file MY_STORAGE`. |
||||
* Fast data bulk loading is not supported using `oxigraph load --location MY_STORAGE --file MY_FILE`. The file format is guessed from the extension (`.nt`, `.ttl`, `.nq`...). |
||||
* [RDF-star](https://w3c.github.io/rdf-star/cg-spec/2021-12-17.html) is now implemented. |
||||
* All operations are now transactional using the "repeatable read" isolation level: |
||||
the store only exposes changes that have been "committed" (i.e. no partial writes) |
||||
and the exposed state does not change for the complete duration of a read operation (e.g. a SPARQL query) or a read/write operation (e.g. a SPARQL update). |
||||
|
||||
|
||||
## Help |
||||
|
||||
Feel free to use [GitHub discussions](https://github.com/oxigraph/oxigraph/discussions) or [the Gitter chat](https://gitter.im/oxigraph/community) to ask questions or talk about Oxigraph. |
||||
[Bug reports](https://github.com/oxigraph/oxigraph/issues) are also very welcome. |
||||
|
||||
If you need advanced support or are willing to pay to get some extra features, feel free to reach out to [Tpt](https://github.com/Tpt). |
||||
|
||||
|
||||
## License |
||||
|
||||
This project is licensed under either of |
||||
|
||||
* Apache License, Version 2.0, ([LICENSE-APACHE](../LICENSE-APACHE) or |
||||
http://www.apache.org/licenses/LICENSE-2.0) |
||||
* MIT license ([LICENSE-MIT](../LICENSE-MIT) or |
||||
http://opensource.org/licenses/MIT) |
||||
|
||||
at your option. |
||||
|
||||
|
||||
### Contribution |
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in Oxigraph by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. |
Before Width: | Height: | Size: 4.5 KiB |
File diff suppressed because it is too large
Load Diff
@ -1,27 +0,0 @@ |
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<title>Oxigraph server</title> |
||||
<link href="https://unpkg.com/@triply/yasgui@4/build/yasgui.min.css" rel="stylesheet" type="text/css" /> |
||||
<script src="https://unpkg.com/@triply/yasgui@4/build/yasgui.min.js"></script> |
||||
<link rel="icon" type="image/svg+xml" href="/logo.svg"> |
||||
</head> |
||||
<body> |
||||
<div id="yasgui"></div> |
||||
<script> |
||||
const url = window.location.href.endsWith('/') ? window.location.href.slice(0, -1) : window.location.href; |
||||
new Yasgui(document.getElementById("yasgui"), { |
||||
requestConfig: { endpoint: url + "/query" }, |
||||
endpointCatalogueOptions: { |
||||
getData: function () { |
||||
return [ |
||||
{ endpoint: url + "/query" }, |
||||
{ endpoint: url + "/update" }, |
||||
]; |
||||
}, |
||||
keys: [], |
||||
} |
||||
}); |
||||
</script> |
||||
</body> |
@ -1,249 +0,0 @@ |
||||
#![allow(clippy::panic)] |
||||
|
||||
use codspeed_criterion_compat::{criterion_group, criterion_main, Criterion, Throughput}; |
||||
use oxhttp::model::{Method, Request, Status}; |
||||
use oxigraph::io::{RdfFormat, RdfParser}; |
||||
use oxigraph::sparql::{Query, QueryResults, Update}; |
||||
use oxigraph::store::Store; |
||||
use rand::random; |
||||
use std::env::temp_dir; |
||||
use std::fs::{remove_dir_all, File}; |
||||
use std::io::Read; |
||||
use std::path::{Path, PathBuf}; |
||||
use std::str; |
||||
|
||||
fn parse_nt(c: &mut Criterion) { |
||||
let data = read_data("explore-1000.nt.zst"); |
||||
let mut group = c.benchmark_group("parse"); |
||||
group.throughput(Throughput::Bytes(data.len() as u64)); |
||||
group.sample_size(50); |
||||
group.bench_function("parse BSBM explore 1000", |b| { |
||||
b.iter(|| { |
||||
for r in RdfParser::from_format(RdfFormat::NTriples).parse_read(data.as_slice()) { |
||||
r.unwrap(); |
||||
} |
||||
}) |
||||
}); |
||||
group.bench_function("parse BSBM explore 1000 unchecked", |b| { |
||||
b.iter(|| { |
||||
for r in RdfParser::from_format(RdfFormat::NTriples) |
||||
.unchecked() |
||||
.parse_read(data.as_slice()) |
||||
{ |
||||
r.unwrap(); |
||||
} |
||||
}) |
||||
}); |
||||
} |
||||
|
||||
fn store_load(c: &mut Criterion) { |
||||
let data = read_data("explore-1000.nt.zst"); |
||||
let mut group = c.benchmark_group("store load"); |
||||
group.throughput(Throughput::Bytes(data.len() as u64)); |
||||
group.sample_size(10); |
||||
group.bench_function("load BSBM explore 1000 in memory", |b| { |
||||
b.iter(|| { |
||||
let store = Store::new().unwrap(); |
||||
do_load(&store, &data); |
||||
}) |
||||
}); |
||||
group.bench_function("load BSBM explore 1000 in on disk", |b| { |
||||
b.iter(|| { |
||||
let path = TempDir::default(); |
||||
let store = Store::open(&path).unwrap(); |
||||
do_load(&store, &data); |
||||
}) |
||||
}); |
||||
group.bench_function("load BSBM explore 1000 in on disk with bulk load", |b| { |
||||
b.iter(|| { |
||||
let path = TempDir::default(); |
||||
let store = Store::open(&path).unwrap(); |
||||
do_bulk_load(&store, &data); |
||||
}) |
||||
}); |
||||
} |
||||
|
||||
fn do_load(store: &Store, data: &[u8]) { |
||||
store.load_from_read(RdfFormat::NTriples, data).unwrap(); |
||||
store.optimize().unwrap(); |
||||
} |
||||
|
||||
fn do_bulk_load(store: &Store, data: &[u8]) { |
||||
store |
||||
.bulk_loader() |
||||
.load_from_read(RdfFormat::NTriples, data) |
||||
.unwrap(); |
||||
store.optimize().unwrap(); |
||||
} |
||||
|
||||
fn store_query_and_update(c: &mut Criterion) { |
||||
let data = read_data("explore-1000.nt.zst"); |
||||
let operations = bsbm_sparql_operation() |
||||
.into_iter() |
||||
.map(|op| match op { |
||||
RawOperation::Query(q) => Operation::Query(Query::parse(&q, None).unwrap()), |
||||
RawOperation::Update(q) => Operation::Update(Update::parse(&q, None).unwrap()), |
||||
}) |
||||
.collect::<Vec<_>>(); |
||||
let query_operations = operations |
||||
.iter() |
||||
.filter(|o| matches!(o, Operation::Query(_))) |
||||
.cloned() |
||||
.collect::<Vec<_>>(); |
||||
|
||||
let mut group = c.benchmark_group("store operations"); |
||||
group.throughput(Throughput::Elements(operations.len() as u64)); |
||||
group.sample_size(10); |
||||
|
||||
{ |
||||
let memory_store = Store::new().unwrap(); |
||||
do_bulk_load(&memory_store, &data); |
||||
group.bench_function("BSBM explore 1000 query in memory", |b| { |
||||
b.iter(|| run_operation(&memory_store, &query_operations)) |
||||
}); |
||||
group.bench_function("BSBM explore 1000 queryAndUpdate in memory", |b| { |
||||
b.iter(|| run_operation(&memory_store, &operations)) |
||||
}); |
||||
} |
||||
|
||||
{ |
||||
let path = TempDir::default(); |
||||
let disk_store = Store::open(&path).unwrap(); |
||||
do_bulk_load(&disk_store, &data); |
||||
group.bench_function("BSBM explore 1000 query on disk", |b| { |
||||
b.iter(|| run_operation(&disk_store, &query_operations)) |
||||
}); |
||||
group.bench_function("BSBM explore 1000 queryAndUpdate on disk", |b| { |
||||
b.iter(|| run_operation(&disk_store, &operations)) |
||||
}); |
||||
} |
||||
} |
||||
|
||||
fn run_operation(store: &Store, operations: &[Operation]) { |
||||
for operation in operations { |
||||
match operation { |
||||
Operation::Query(q) => match store.query(q.clone()).unwrap() { |
||||
QueryResults::Boolean(_) => (), |
||||
QueryResults::Solutions(s) => { |
||||
for s in s { |
||||
s.unwrap(); |
||||
} |
||||
} |
||||
QueryResults::Graph(g) => { |
||||
for t in g { |
||||
t.unwrap(); |
||||
} |
||||
} |
||||
}, |
||||
Operation::Update(u) => store.update(u.clone()).unwrap(), |
||||
} |
||||
} |
||||
} |
||||
|
||||
fn sparql_parsing(c: &mut Criterion) { |
||||
let operations = bsbm_sparql_operation(); |
||||
let mut group = c.benchmark_group("sparql parsing"); |
||||
group.sample_size(10); |
||||
group.throughput(Throughput::Bytes( |
||||
operations |
||||
.iter() |
||||
.map(|o| match o { |
||||
RawOperation::Query(q) => q.len(), |
||||
RawOperation::Update(u) => u.len(), |
||||
}) |
||||
.sum::<usize>() as u64, |
||||
)); |
||||
group.bench_function("BSBM query and update set", |b| { |
||||
b.iter(|| { |
||||
for operation in &operations { |
||||
match operation { |
||||
RawOperation::Query(q) => { |
||||
Query::parse(q, None).unwrap(); |
||||
} |
||||
RawOperation::Update(u) => { |
||||
Update::parse(u, None).unwrap(); |
||||
} |
||||
} |
||||
} |
||||
}) |
||||
}); |
||||
} |
||||
|
||||
criterion_group!(parse, parse_nt); |
||||
criterion_group!(store, sparql_parsing, store_query_and_update, store_load); |
||||
|
||||
criterion_main!(parse, store); |
||||
|
||||
fn read_data(file: &str) -> Vec<u8> { |
||||
if !Path::new(file).exists() { |
||||
let client = oxhttp::Client::new().with_redirection_limit(5); |
||||
let url = format!("https://github.com/Tpt/bsbm-tools/releases/download/v0.2/{file}"); |
||||
let request = Request::builder(Method::GET, url.parse().unwrap()).build(); |
||||
let response = client.request(request).unwrap(); |
||||
assert_eq!( |
||||
response.status(), |
||||
Status::OK, |
||||
"{}", |
||||
response.into_body().to_string().unwrap() |
||||
); |
||||
std::io::copy(&mut response.into_body(), &mut File::create(file).unwrap()).unwrap(); |
||||
} |
||||
let mut buf = Vec::new(); |
||||
zstd::Decoder::new(File::open(file).unwrap()) |
||||
.unwrap() |
||||
.read_to_end(&mut buf) |
||||
.unwrap(); |
||||
buf |
||||
} |
||||
|
||||
fn bsbm_sparql_operation() -> Vec<RawOperation> { |
||||
String::from_utf8(read_data("mix-exploreAndUpdate-1000.tsv.zst")) |
||||
.unwrap() |
||||
.lines() |
||||
.rev() |
||||
.take(300) // We take only 10 groups
|
||||
.map(|l| { |
||||
let mut parts = l.trim().split('\t'); |
||||
let kind = parts.next().unwrap(); |
||||
let operation = parts.next().unwrap(); |
||||
match kind { |
||||
"query" => RawOperation::Query(operation.into()), |
||||
"update" => RawOperation::Update(operation.into()), |
||||
_ => panic!("Unexpected operation kind {kind}"), |
||||
} |
||||
}) |
||||
.collect() |
||||
} |
||||
|
||||
#[derive(Clone)] |
||||
enum RawOperation { |
||||
Query(String), |
||||
Update(String), |
||||
} |
||||
|
||||
#[allow(clippy::large_enum_variant)] |
||||
#[derive(Clone)] |
||||
enum Operation { |
||||
Query(Query), |
||||
Update(Update), |
||||
} |
||||
|
||||
struct TempDir(PathBuf); |
||||
|
||||
impl Default for TempDir { |
||||
fn default() -> Self { |
||||
Self(temp_dir().join(format!("oxigraph-bench-{}", random::<u128>()))) |
||||
} |
||||
} |
||||
|
||||
impl AsRef<Path> for TempDir { |
||||
fn as_ref(&self) -> &Path { |
||||
&self.0 |
||||
} |
||||
} |
||||
|
||||
impl Drop for TempDir { |
||||
fn drop(&mut self) { |
||||
remove_dir_all(&self.0).unwrap() |
||||
} |
||||
} |
Loading…
Reference in new issue