pax_global_header00006660000000000000000000000064147633670240014526gustar00rootroot0000000000000052 comment=68d45f71fe06a323db3605bb3a90cd59fc3beac5 oxigraph-oxigraph-68d45f7/000077500000000000000000000000001476336702400155375ustar00rootroot00000000000000oxigraph-oxigraph-68d45f7/.dockerignore000066400000000000000000000000051476336702400202060ustar00rootroot00000000000000benchoxigraph-oxigraph-68d45f7/.github/000077500000000000000000000000001476336702400170775ustar00rootroot00000000000000oxigraph-oxigraph-68d45f7/.github/FUNDING.yml000066400000000000000000000000201476336702400207040ustar00rootroot00000000000000github: [ Tpt ] oxigraph-oxigraph-68d45f7/.github/ISSUE_TEMPLATE/000077500000000000000000000000001476336702400212625ustar00rootroot00000000000000oxigraph-oxigraph-68d45f7/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000005371476336702400237610ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: bug assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Which version of Oxigraph are you using? On which platform? 2. A command-line or a code snippet that triggers the bug. oxigraph-oxigraph-68d45f7/.github/ISSUE_TEMPLATE/feature-request.md000066400000000000000000000012411476336702400247230ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: enhancement assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Please link to other systems implementing the feature, specification of it if it exists and/or existing documentation about this feature. oxigraph-oxigraph-68d45f7/.github/ISSUE_TEMPLATE/question.md000066400000000000000000000002211476336702400234460ustar00rootroot00000000000000--- name: Question about: Please don't use issues but the Q&A section of the "discussions" space title: '' labels: question assignees: '' --- oxigraph-oxigraph-68d45f7/.github/actions/000077500000000000000000000000001476336702400205375ustar00rootroot00000000000000oxigraph-oxigraph-68d45f7/.github/actions/setup-rust/000077500000000000000000000000001476336702400226725ustar00rootroot00000000000000oxigraph-oxigraph-68d45f7/.github/actions/setup-rust/action.yml000066400000000000000000000022311476336702400246700ustar00rootroot00000000000000name: 'Setup Rust' description: 'Setup Rust using Rustup' inputs: version: description: 'Rust version to use. By default latest stable version' required: false default: 'stable' component: description: 'Rust extra component to install like clippy' required: false target: description: 'Rust extra target to install like wasm32-unknown-unknown' required: false cache-key: description: 'Extra cache key to use' required: false default: '' runs: using: "composite" steps: - run: which rustup || sudo DEBIAN_FRONTEND=noninteractive apt-get -y install rustup shell: bash if: runner.os == 'Linux' - run: rustup default ${{ inputs.version }} shell: bash - run: rustup component add ${{ inputs.component }} shell: bash if: ${{ inputs.component }} - run: rustup target add ${{ inputs.target }} shell: bash if: ${{ inputs.target }} - uses: Swatinem/rust-cache@v2 continue-on-error: true with: cache-all-crates: true cache-on-failure: true key: ${{ inputs.cache-key }} workspaces: | . -> target fuzz -> target oxigraph-oxigraph-68d45f7/.github/codecov.yml000066400000000000000000000000731476336702400212440ustar00rootroot00000000000000ignore: - fuzz - js - lib/sparql-smith - testsuite oxigraph-oxigraph-68d45f7/.github/dependabot.yml000066400000000000000000000006061476336702400217310ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: weekly - package-ecosystem: "pip" directory: "/python/" versioning-strategy: increase-if-necessary schedule: interval: weekly - package-ecosystem: "npm" directory: "/js/" versioning-strategy: increase-if-necessary schedule: interval: weekly oxigraph-oxigraph-68d45f7/.github/workflows/000077500000000000000000000000001476336702400211345ustar00rootroot00000000000000oxigraph-oxigraph-68d45f7/.github/workflows/artifacts.yml000066400000000000000000000416241476336702400236460ustar00rootroot00000000000000name: Artifacts on: push: branches: - main - next release: types: - published concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: binary_linux: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust with: target: aarch64-unknown-linux-gnu - run: | sudo apt-get update && sudo apt-get install -y g++-aarch64-linux-gnu mkdir .cargo echo -e "[target.aarch64-unknown-linux-gnu]\nlinker = \"aarch64-linux-gnu-gcc\"" >> .cargo/config.toml - run: cargo build --release --no-default-features --features rustls-native,geosparql working-directory: ./cli - run: cargo build --release --target aarch64-unknown-linux-gnu --no-default-features --features rustls-native,geosparql working-directory: ./cli env: BINDGEN_EXTRA_CLANG_ARGS: --sysroot /usr/aarch64-linux-gnu - uses: actions/upload-artifact@v4 with: name: oxigraph_x86_64_linux_gnu path: target/release/oxigraph - uses: actions/upload-artifact@v4 with: name: oxigraph_aarch64_linux_gnu path: target/aarch64-unknown-linux-gnu/release/oxigraph - run: mv target/release/oxigraph oxigraph_${{ github.event.release.tag_name }}_x86_64_linux_gnu if: github.event_name == 'release' - run: mv target/aarch64-unknown-linux-gnu/release/oxigraph oxigraph_${{ github.event.release.tag_name }}_aarch64_linux_gnu if: github.event_name == 'release' - uses: softprops/action-gh-release@v2 with: files: | oxigraph_${{ github.event.release.tag_name }}_x86_64_linux_gnu oxigraph_${{ github.event.release.tag_name }}_aarch64_linux_gnu if: github.event_name == 'release' binary_mac: runs-on: macos-latest env: DEVELOPER_DIR: '/Applications/Xcode.app/Contents/Developer' SDKROOT: '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk' MACOSX_DEPLOYMENT_TARGET: '10.14' steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust with: target: x86_64-apple-darwin - run: cargo build --release --target x86_64-apple-darwin working-directory: ./cli - run: cargo build --release --target aarch64-apple-darwin working-directory: ./cli - uses: actions/upload-artifact@v4 with: name: oxigraph_x86_64_apple path: target/x86_64-apple-darwin/release/oxigraph - uses: actions/upload-artifact@v4 with: name: oxigraph_aarch64_apple path: target/aarch64-apple-darwin/release/oxigraph - run: mv target/x86_64-apple-darwin/release/oxigraph oxigraph_${{ github.event.release.tag_name }}_x86_64_apple if: github.event_name == 'release' - run: mv target/aarch64-apple-darwin/release/oxigraph oxigraph_${{ github.event.release.tag_name }}_aarch64_apple if: github.event_name == 'release' - uses: softprops/action-gh-release@v2 with: files: | oxigraph_${{ github.event.release.tag_name }}_x86_64_apple oxigraph_${{ github.event.release.tag_name }}_aarch64_apple if: github.event_name == 'release' binary_windows: runs-on: windows-latest steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust - run: cargo build --release working-directory: ./cli - uses: actions/upload-artifact@v4 with: name: oxigraph_x86_64_windows_msvc path: target/release/oxigraph.exe - run: mv target/release/oxigraph.exe oxigraph_${{ github.event.release.tag_name }}_x86_64_windows_msvc.exe if: github.event_name == 'release' - uses: softprops/action-gh-release@v2 with: files: oxigraph_${{ github.event.release.tag_name }}_x86_64_windows_msvc.exe if: github.event_name == 'release' python_sdist: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: true - run: bash trim_rocksdb.sh working-directory: ./oxrocksdb-sys - uses: ./.github/actions/setup-rust - uses: astral-sh/setup-uv@v5 with: python-version: "3.13" enable-cache: true cache-dependency-glob: "**/requirements**.txt" - run: uv pip install -r python/requirements.build.txt - run: maturin build -m python/Cargo.toml - run: uv pip install --no-index --find-links=target/wheels/ pyoxigraph - run: rm -r target/wheels - run: python generate_stubs.py pyoxigraph pyoxigraph.pyi --ruff working-directory: ./python - run: maturin sdist -m python/Cargo.toml - uses: actions/upload-artifact@v4 with: name: pyoxigraph_sdist path: target/wheels/*.tar.gz wheel_linux_x86_64: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust - run: sed 's/%arch%/x86_64/g' .github/workflows/manylinux_build.sh | sed 's/%for_each_version%/${{ github.event_name == 'release' || ''}}/g' > .github/workflows/manylinux_build_script.sh - run: docker run -v "$(pwd)":/workdir quay.io/pypa/manylinux2014_x86_64 /bin/bash /workdir/.github/workflows/manylinux_build_script.sh - uses: actions/upload-artifact@v4 with: name: pyoxigraph_x86_64_linux_gnu path: target/wheels/*.whl wheel_linux_aarch64: runs-on: ubuntu-24.04-arm steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust - run: sed 's/%arch%/aarch64/g' .github/workflows/manylinux_build.sh | sed 's/%for_each_version%/${{ github.event_name == 'release' || '' }}/g' > .github/workflows/manylinux_build_script.sh - run: docker run -v "$(pwd)":/workdir quay.io/pypa/manylinux2014_aarch64 /bin/bash /workdir/.github/workflows/manylinux_build_script.sh - uses: actions/upload-artifact@v4 with: name: pyoxigraph_aarch64_linux_gnu path: target/wheels/*.whl wheel_linux_musl_x86_64: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust - run: sed 's/%arch%/x86_64/g' .github/workflows/musllinux_build.sh | sed 's/%for_each_version%/${{ github.event_name == 'release' || '' }}/g' > .github/workflows/musllinux_build_script.sh - run: docker run -v "$(pwd)":/workdir --platform linux/x86_64 quay.io/pypa/musllinux_1_2_x86_64 /bin/bash /workdir/.github/workflows/musllinux_build_script.sh - uses: actions/upload-artifact@v4 with: name: pyoxigraph_x86_64_linux_musl path: target/wheels/*.whl wheel_linux_musl_aarch64: runs-on: ubuntu-24.04-arm steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust - run: sed 's/%arch%/aarch64/g' .github/workflows/musllinux_build.sh | sed 's/%for_each_version%/${{ github.event_name == 'release' || '' }}/g' > .github/workflows/musllinux_build_script.sh - run: docker run -v "$(pwd)":/workdir --platform linux/aarch64 quay.io/pypa/musllinux_1_2_aarch64 /bin/bash /workdir/.github/workflows/musllinux_build_script.sh - uses: actions/upload-artifact@v4 with: name: pyoxigraph_aarch64_linux_musl path: target/wheels/*.whl wheel_mac: runs-on: macos-latest env: DEVELOPER_DIR: '/Applications/Xcode.app/Contents/Developer' SDKROOT: '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk' MACOSX_DEPLOYMENT_TARGET: '10.14' steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust with: target: x86_64-apple-darwin - uses: astral-sh/setup-uv@v5 with: python-version: "3.13" enable-cache: true cache-dependency-glob: "**/requirements**.txt" - run: uv pip install -r python/requirements.build.txt - run: maturin build --release --features abi3 working-directory: ./python - run: uv pip install --no-index --find-links=target/wheels/ pyoxigraph - run: rm -r target/wheels - run: python generate_stubs.py pyoxigraph pyoxigraph.pyi --ruff working-directory: ./python - run: maturin build --release --target universal2-apple-darwin --features abi3 working-directory: ./python - run: maturin build --release --target x86_64-apple-darwin --features abi3 working-directory: ./python if: github.event_name == 'release' - run: maturin build --release --target aarch64-apple-darwin --features abi3 working-directory: ./python if: github.event_name == 'release' - uses: actions/upload-artifact@v4 with: name: pyoxigraph_macos path: target/wheels/*.whl wheel_windows: runs-on: windows-latest steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust - uses: actions/setup-python@v5 with: python-version: "3.13" cache: pip cache-dependency-path: '**/requirements.build.txt' - run: pip install -r python/requirements.build.txt - run: maturin build --release --features abi3 working-directory: ./python - run: pip install --no-index --find-links=target/wheels/ pyoxigraph - run: rm -r target/wheels - run: python generate_stubs.py pyoxigraph pyoxigraph.pyi --ruff working-directory: ./python - run: maturin build --release -m python/Cargo.toml --features abi3 - uses: actions/upload-artifact@v4 with: name: pyoxigraph_windows path: target/wheels/*.whl publish_pypi: if: github.event_name == 'release' runs-on: ubuntu-latest environment: name: pypi url: https://pypi.org/p/pyoxigraph permissions: id-token: write needs: - python_sdist - wheel_windows - wheel_mac - wheel_linux_x86_64 - wheel_linux_aarch64 - wheel_linux_musl_x86_64 - wheel_linux_musl_aarch64 steps: - uses: actions/download-artifact@v4 with: pattern: pyoxigraph_* path: dist merge-multiple: true - uses: pypa/gh-action-pypi-publish@release/v1 with: packages-dir: dist skip-existing: true npm_tarball: runs-on: ubuntu-latest permissions: contents: read id-token: write steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust - uses: actions/setup-node@v4 with: node-version: 22 cache: npm cache-dependency-path: "js/package.json" registry-url: https://registry.npmjs.org - run: npm install working-directory: ./js - run: npm run pack working-directory: ./js - uses: actions/upload-artifact@v4 with: name: oxigraph_wasm_npm path: js/*.tgz - run: npm run release working-directory: ./js env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} if: github.event_name == 'release' docker: runs-on: ubuntu-latest permissions: id-token: write packages: write contents: read attestations: write steps: - uses: actions/checkout@v4 with: submodules: true - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{github.actor}} password: ${{secrets.GITHUB_TOKEN}} - uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} if: github.event_name == 'release' - uses: docker/metadata-action@v5 id: docker_meta with: images: | ${{ github.repository }},enable=${{ github.event_name == 'release' }} ghcr.io/${{ github.repository }} tags: | type=ref,event=branch type=ref,event=pr type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} - uses: docker/build-push-action@v6 id: push with: context: . file: server/Dockerfile platforms: linux/amd64,linux/arm64 pull: true push: true tags: ${{ steps.docker_meta.outputs.tags }} labels: ${{ steps.docker_meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max - uses: actions/attest-build-provenance@v2 with: subject-name: ghcr.io/${{ github.repository }} subject-digest: ${{ steps.push.outputs.digest }} push-to-registry: true - uses: actions/attest-build-provenance@v2 with: subject-name: index.docker.io/${{ github.repository }} subject-digest: ${{ steps.push.outputs.digest }} push-to-registry: true if: github.event_name == 'release' publish_crates: if: github.event_name == 'release' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: true - run: bash trim_rocksdb.sh working-directory: ./oxrocksdb-sys - run: rustup update - run: cargo login $CRATES_IO_TOKEN env: CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} - run: cargo publish working-directory: ./oxrocksdb-sys continue-on-error: true - run: cargo publish working-directory: ./lib/oxsdatatypes continue-on-error: true - run: cargo publish working-directory: ./lib/oxrdf continue-on-error: true - run: cargo publish working-directory: ./lib/oxrdfxml continue-on-error: true - run: cargo publish working-directory: ./lib/oxttl continue-on-error: true - run: cargo publish working-directory: ./lib/oxrdfio continue-on-error: true - run: cargo publish working-directory: ./lib/sparesults continue-on-error: true - run: cargo publish working-directory: ./lib/spargebra continue-on-error: true - run: cargo publish working-directory: ./lib/sparopt continue-on-error: true - run: cargo publish working-directory: ./lib/spareval continue-on-error: true - run: cargo publish working-directory: ./lib/sparql-smith continue-on-error: true - run: cargo publish working-directory: ./lib/oxigraph continue-on-error: true - run: cargo publish working-directory: ./lib/spargeo continue-on-error: true - run: cargo publish working-directory: ./cli homebrew: if: "github.event_name == 'release' && !contains('-', github.event.release.tag_name)" runs-on: ubuntu-latest needs: full_archive steps: - uses: actions/checkout@v4 with: repository: oxigraph/homebrew-oxigraph token: ${{ secrets.FULL_ACCESS_TOKEN }} - run: | wget "https://github.com/oxigraph/oxigraph/releases/download/${{ github.event.release.tag_name }}/oxigraph_${{ github.event.release.tag_name }}.tar.gz" SHA=`shasum -a 256 "oxigraph_${{ github.event.release.tag_name }}.tar.gz" | awk '{ print $1 }'` rm "oxigraph_${{ github.event.release.tag_name }}.tar.gz" sed -i "s/download\/.*\.tar/download\/${{ github.event.release.tag_name }}\/oxigraph_${{ github.event.release.tag_name }}.tar/g" Formula/oxigraph.rb sed -i "s/sha256 \".*\"/sha256 \"$SHA\"/g" Formula/oxigraph.rb git config user.name github-actions git config user.email github-actions@github.com git add . git diff-index --quiet HEAD || git commit -m "Upgrades to ${{ github.event.release.tag_name }}" git push full_archive: if: github.event_name == 'release' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: true - run: bash trim_rocksdb.sh working-directory: ./oxrocksdb-sys - run: | rm -rf .git **/.git bench fuzz zip -r oxigraph_${{ github.event.release.tag_name }}.zip . tar -czf /tmp/oxigraph_${{ github.event.release.tag_name }}.tar.gz . mv /tmp/oxigraph_${{ github.event.release.tag_name }}.tar.gz . - uses: softprops/action-gh-release@v2 with: files: | oxigraph_${{ github.event.release.tag_name }}.zip oxigraph_${{ github.event.release.tag_name }}.tar.gz oxigraph-oxigraph-68d45f7/.github/workflows/install_rocksdb.sh000066400000000000000000000003351476336702400246460ustar00rootroot00000000000000if [ -f "rocksdb" ] then cd rocksdb || exit else git clone https://github.com/facebook/rocksdb.git cd rocksdb || exit git checkout v8.0.0 make shared_lib fi sudo make install-shared sudo ldconfig /usr/local/lib oxigraph-oxigraph-68d45f7/.github/workflows/manylinux_build.sh000066400000000000000000000015141476336702400246740ustar00rootroot00000000000000cd /workdir yum -y install centos-release-scl-rh yum -y install llvm-toolset-7.0 source scl_source enable llvm-toolset-7.0 curl https://static.rust-lang.org/rustup/dist/%arch%-unknown-linux-gnu/rustup-init --output rustup-init chmod +x rustup-init ./rustup-init -y --profile minimal source "$HOME/.cargo/env" cd python uv venv uv pip install -r requirements.build.txt source .venv/bin/activate maturin develop --release python generate_stubs.py pyoxigraph pyoxigraph.pyi --ruff maturin build --release --features abi3 --compatibility manylinux2014 if [ %for_each_version% ]; then for VERSION in 8 9 10 11 12 13 13t; do maturin build --release --interpreter "python3.$VERSION" --compatibility manylinux2014 done for VERSION in 9 10; do maturin build --release --interpreter "pypy3.$VERSION" --compatibility manylinux2014 done fi oxigraph-oxigraph-68d45f7/.github/workflows/musllinux_build.sh000066400000000000000000000011661476336702400247130ustar00rootroot00000000000000cd /workdir apk add clang-dev curl https://static.rust-lang.org/rustup/dist/%arch%-unknown-linux-musl/rustup-init --output rustup-init chmod +x rustup-init ./rustup-init -y --profile minimal source "$HOME/.cargo/env" cd python uv venv uv pip install -r requirements.build.txt source .venv/bin/activate maturin develop --release python generate_stubs.py pyoxigraph pyoxigraph.pyi --ruff maturin build --release --features abi3 --compatibility musllinux_1_2 if [ %for_each_version% ]; then for VERSION in 8 9 10 11 12 13 13t; do maturin build --release --interpreter "python3.$VERSION" --compatibility musllinux_1_2 done fi oxigraph-oxigraph-68d45f7/.github/workflows/nightly.yml000066400000000000000000000005311476336702400233340ustar00rootroot00000000000000name: Nightly tests on: schedule: - cron: "12 3 * * *" concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: deny: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: taiki-e/install-action@v2 with: { tool: cargo-deny } - run: cargo deny check oxigraph-oxigraph-68d45f7/.github/workflows/tests.yml000066400000000000000000000456601476336702400230340ustar00rootroot00000000000000name: Change tests on: pull_request: branches: - main - next push: branches: - main - next concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: fmt: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: ./.github/actions/setup-rust with: component: rustfmt - run: cargo fmt -- --check clippy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust with: version: 1.82.0 component: clippy - run: cargo clippy --all-targets -- -D warnings -D clippy::all working-directory: ./lib/oxsdatatypes - run: cargo clippy --all-targets -- -D warnings -D clippy::all working-directory: ./lib/oxrdf - run: cargo clippy --all-targets -- -D warnings -D clippy::all working-directory: ./lib/oxrdfxml - run: cargo clippy --all-targets -- -D warnings -D clippy::all working-directory: ./lib/oxttl - run: cargo clippy --all-targets -- -D warnings -D clippy::all working-directory: ./lib/oxrdfio - run: cargo clippy --all-targets --features async-tokio -- -D warnings -D clippy::all working-directory: ./lib/oxrdfio - run: cargo clippy --all-targets -- -D warnings -D clippy::all working-directory: ./lib/sparesults - run: cargo clippy --all-targets --features async-tokio -- -D warnings -D clippy::all working-directory: ./lib/sparesults - run: cargo clippy --all-targets -- -D warnings -D clippy::all working-directory: ./lib/spargebra - run: cargo clippy --all-targets -- -D warnings -D clippy::all working-directory: ./lib/sparopt - run: cargo clippy --all-targets -- -D warnings -D clippy::all working-directory: ./lib/spareval - run: cargo clippy --all-targets --features rdf-star -- -D warnings -D clippy::all working-directory: ./lib/spareval - run: cargo clippy --all-targets --features sep-0002 -- -D warnings -D clippy::all working-directory: ./lib/spareval - run: cargo clippy --all-targets --features sep-0006 -- -D warnings -D clippy::all working-directory: ./lib/spareval - run: cargo clippy --all-targets --no-default-features -- -D warnings -D clippy::all working-directory: ./lib/oxigraph - run: cargo clippy --all-targets -- -D warnings -D clippy::all working-directory: ./lib/oxigraph - run: cargo clippy --all-targets -- -D warnings -D clippy::all working-directory: ./python - run: cargo clippy --all-targets -- -D warnings -D clippy::all working-directory: ./cli - run: cargo clippy --all-targets -- -D warnings -D clippy::all working-directory: ./testsuite clippy_wasm_js: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust with: version: 1.82.0 target: wasm32-unknown-unknown component: clippy - run: cargo clippy --lib --tests --target wasm32-unknown-unknown -- -D warnings -D clippy::all working-directory: ./js clippy_wasip1: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust with: version: 1.82.0 target: wasm32-wasip1 component: clippy - run: cargo clippy --lib --tests --target wasm32-wasip1 -- -D warnings -D clippy::all working-directory: ./lib/oxigraph - run: cargo clippy --target wasm32-wasip1 --features abi3 --no-default-features -- -D warnings -D clippy::all working-directory: ./python clippy_wasm_emscripten: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust with: version: 1.82.0 target: wasm32-unknown-emscripten component: clippy - run: cargo clippy --lib --tests --target wasm32-unknown-emscripten -- -D warnings -D clippy::all working-directory: ./lib/oxigraph - run: cargo clippy --target wasm32-unknown-emscripten --features abi3 -- -D warnings -D clippy::all working-directory: ./python clippy_wasm_unknown: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust with: version: 1.82.0 target: wasm32-unknown-unknown component: clippy - run: cargo clippy --lib --tests --target wasm32-unknown-unknown --features getrandom/custom --features oxsdatatypes/custom-now -- -D warnings -D clippy::all working-directory: ./lib/oxigraph deny: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: taiki-e/install-action@v2 with: { tool: cargo-deny } - run: cargo deny check semver_checks: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust - uses: taiki-e/install-action@v2 with: { tool: cargo-semver-checks } - uses: actions/cache@v4 with: path: rocksdb key: ${{ runner.os }}-rocksdb-8.0.0 - run: bash .github/workflows/install_rocksdb.sh - run: cargo semver-checks check-release --exclude oxrocksdb-sys --exclude oxigraph-js --exclude pyoxigraph --exclude oxigraph-testsuite --exclude oxigraph-cli --exclude spareval test_os: strategy: matrix: os-version: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os-version }} name: Test on ${{ matrix.os-version }} steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust - run: cargo test test_linux_i686: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust with: target: i686-unknown-linux-gnu - run: sudo apt-get update && sudo apt-get install -y g++-multilib - run: cargo test --target i686-unknown-linux-gnu --all-features working-directory: ./lib/oxsdatatypes - run: cargo test --target i686-unknown-linux-gnu --all-features working-directory: ./lib/oxrdf - run: cargo test --target i686-unknown-linux-gnu --all-features working-directory: ./lib/oxrdfxml - run: cargo test --target i686-unknown-linux-gnu --all-features working-directory: ./lib/oxttl - run: cargo test --target i686-unknown-linux-gnu --all-features working-directory: ./lib/oxrdfio - run: cargo test --target i686-unknown-linux-gnu --all-features working-directory: ./lib/sparesults - run: cargo test --target i686-unknown-linux-gnu --all-features working-directory: ./lib/spargebra - run: cargo test --target i686-unknown-linux-gnu --all-features working-directory: ./lib/sparopt - run: cargo test --target i686-unknown-linux-gnu --all-features working-directory: ./lib/spareval - run: cargo test --target i686-unknown-linux-gnu --features http-client-rustls-native working-directory: ./lib/oxigraph - run: cargo test --target i686-unknown-linux-gnu working-directory: ./testsuite test_linux_msv: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: true - run: echo "rust-version=$(sed -ne 's/rust-version *= *\"\(.*\)\"/\1/p' Cargo.toml)" >> $GITHUB_OUTPUT id: metadata - run: mkdir .cargo && echo "resolver.incompatible-rust-versions = \"fallback\"" >> .cargo/config.toml - uses: ./.github/actions/setup-rust with: version: ${{ steps.metadata.outputs.rust-version }} - run: rustup toolchain install nightly - run: rm Cargo.lock && cargo +nightly update -Z direct-minimal-versions - run: cargo test --workspace --exclude sparql-smith test_linux_latest: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust - run: rm Cargo.lock && cargo update - run: cargo test test_linux_address_sanitizer: runs-on: ubuntu-latest continue-on-error: true steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust with: version: nightly - run: sudo apt-get update && sudo apt-get install -y llvm - run: cargo test --tests --target x86_64-unknown-linux-gnu --workspace --exclude pyoxigraph --exclude oxigraph-testsuite --exclude oxigraph-cli env: RUSTFLAGS: -Z sanitizer=address test_linux_dynamic_linking: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust - uses: actions/cache@v4 with: path: rocksdb key: ${{ runner.os }}-rocksdb-8.0.0 - run: bash .github/workflows/install_rocksdb.sh - run: cargo test --tests --features oxrocksdb-sys/pkg-config test_wasip1: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust with: target: wasm32-wasip1 - uses: taiki-e/install-action@v2 with: { tool: "wasmtime" } - run: cargo test --target wasm32-wasip1 --workspace --exclude oxigraph-js --exclude oxigraph-cli --exclude oxigraph-testsuite --exclude oxrocksdb-sys --exclude pyoxigraph env: CARGO_TARGET_WASM32_WASIP1_RUNNER: wasmtime run rustdoc: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust with: version: 1.82.0 - run: cargo doc --no-deps env: RUSTDOCFLAGS: -D warnings js: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: ./.github/actions/setup-rust - uses: actions/setup-node@v4 with: node-version: 18 cache: npm cache-dependency-path: "js/package.json" - run: npm install working-directory: ./js - run: npm test working-directory: ./js python: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust - uses: astral-sh/setup-uv@v5 with: python-version: "3.13" enable-cache: true cache-dependency-glob: "**/requirements**.txt" - run: uv pip install -r python/requirements.dev.txt - run: maturin build -m python/Cargo.toml - run: uv pip install --no-index --find-links=target/wheels/ pyoxigraph - run: rm -r target/wheels - run: python -m unittest working-directory: ./python/tests - run: sphinx-build -M html . build working-directory: ./python/docs - run: python generate_stubs.py pyoxigraph pyoxigraph.pyi --ruff working-directory: ./python - run: python -m mypy.stubtest pyoxigraph --allowlist=mypy_allowlist.txt working-directory: ./python - run: python -m mypy generate_stubs.py tests --strict working-directory: ./python - run: python -m ruff format --check . working-directory: ./python - run: python -m ruff check --output-format=github . working-directory: ./python - run: sphinx-lint docs working-directory: ./python python_msv: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: true - run: echo "rust-version=$(sed -ne 's/rust-version *= *\"\(.*\)\"/\1/p' Cargo.toml)" >> $GITHUB_OUTPUT id: metadata - run: mkdir .cargo && echo "resolver.incompatible-rust-versions = \"fallback\"" >> .cargo/config.toml - uses: ./.github/actions/setup-rust with: version: ${{ steps.metadata.outputs.rust-version }} - run: rustup toolchain install nightly - uses: astral-sh/setup-uv@v5 with: python-version: "3.8" enable-cache: true cache-dependency-glob: "**/requirements**.txt" - run: uv pip install -r python/requirements.dev.txt - run: rm Cargo.lock && cargo +nightly update -Z direct-minimal-versions - run: maturin build -m python/Cargo.toml - run: uv pip install --no-index --find-links=target/wheels/ pyoxigraph - run: rm -r target/wheels - run: python -m unittest working-directory: ./python/tests python_pypy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust - uses: astral-sh/setup-uv@v5 with: python-version: "pypy3.10" enable-cache: true cache-dependency-glob: "**/requirements**.txt" - run: uv pip install -r python/requirements.dev.txt - run: maturin build -m python/Cargo.toml - run: uv pip install --no-index --find-links=target/wheels/ pyoxigraph - run: rm -r target/wheels - run: python -m unittest working-directory: ./python/tests python_windows: runs-on: windows-latest steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust - uses: astral-sh/setup-uv@v5 with: python-version: "3.13" enable-cache: true cache-dependency-glob: "**/requirements**.txt" - run: uv run --with "maturin~=1.0" maturin build -m python/Cargo.toml - run: uv pip install --no-index --find-links=target/wheels/ pyoxigraph - run: rm -r target/wheels - run: python -m unittest working-directory: ./python/tests typos: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: taiki-e/install-action@v2 with: { tool: typos-cli } - run: typos clang_fmt: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: sudo apt-get update && sudo apt-get install -y clang-format - run: clang-format --Werror --dry-run oxrocksdb-sys/api/* fuzz_changes: strategy: matrix: include: - target: "sparql_query_eval" build_args: "--features sparql-smith/service" run_args: "-fork=1 -ignore_timeouts=1" - target: "sparql_update_eval" build_args: "--features rocksdb" run_args: "-fork=1 -ignore_timeouts=1" - target: "sparql_results_json" - target: "sparql_results_tsv" - target: "sparql_results_xml" - target: "n3" - target: "nquads" - target: "trig" - target: "rdf_xml" runs-on: ubuntu-latest continue-on-error: true steps: - uses: actions/checkout@v4 with: submodules: true - uses: actions/cache@v4 id: cache-corpus with: path: fuzz/corpus/${{ matrix.target }} key: fuzz-${{ matrix.target }}-${{ github.run_id }} restore-keys: | fuzz-${{ matrix.target }} - uses: ./.github/actions/setup-rust with: cache-key: ${{ matrix.target }} - run: cargo install cargo-fuzz || true - run: python3 build_corpus.py working-directory: ./fuzz if: steps.cache-corpus.outputs.cache-hit != 'true' - run: cargo fuzz run ${{ matrix.target }} --sanitizer none ${{ matrix.build_args }} -- -max_total_time=200 -timeout=5 ${{ matrix.run_args }} - run: cargo fuzz cmin ${{ matrix.target }} --sanitizer none ${{ matrix.build_args }} - uses: actions/upload-artifact@v4 if: failure() with: name: fuzz-artifacts-${{ matrix.target }} path: fuzz/artifacts/${{ matrix.target }} shellcheck: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: sudo apt-get update && sudo apt-get install -y shellcheck - run: git grep -l '^#\( *shellcheck \|!\(/bin/\|/usr/bin/env \)\(sh\|bash\|dash\|ksh\)\)' | xargs shellcheck spec_links: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: python lints/test_spec_links.py debian_compatibility: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: ./.github/actions/setup-rust - run: python lints/test_debian_compatibility.py codspeed: runs-on: ubuntu-latest strategy: matrix: build_args: - "-p oxigraph --features http-client-native-tls" - "-p oxigraph-testsuite" steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust - uses: taiki-e/install-action@v2 with: { tool: cargo-codspeed } - run: cargo codspeed build ${{ matrix.build_args }} - uses: CodSpeedHQ/action@v3 with: run: cargo codspeed run token: ${{ secrets.CODSPEED_TOKEN }} codspeed_js: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust - uses: actions/setup-node@v4 with: node-version: 22 cache: npm cache-dependency-path: "js/package.json" - run: npm install working-directory: ./js - run: npm run build-node working-directory: ./js - uses: CodSpeedHQ/action@v3 with: run: npx vitest bench token: ${{ secrets.CODSPEED_TOKEN }} working-directory: ./js codecov: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/setup-rust with: component: llvm-tools-preview - uses: astral-sh/setup-uv@v5 with: python-version: "3.13" enable-cache: true cache-dependency-glob: "**/requirements**.txt" - uses: taiki-e/install-action@v2 with: { tool: cargo-llvm-cov } - run: | source <(cargo llvm-cov show-env --export-prefix) export CARGO_TARGET_DIR=$CARGO_LLVM_COV_TARGET_DIR cargo llvm-cov clean --workspace cargo test --features oxrdfio/async-tokio,sparesults/async-tokio uv run --with "maturin~=1.0" maturin build -m python/Cargo.toml uv pip install --no-index --find-links=target/wheels/ pyoxigraph cd python/tests uv run -m unittest cd ../.. cargo llvm-cov report --codecov --output-path codecov.json - uses: codecov/codecov-action@v5 with: files: codecov.json flags: rust fail_ci_if_error: true token: ${{ secrets.CODECOV_TOKEN }} oxigraph-oxigraph-68d45f7/.gitignore000066400000000000000000000002361476336702400175300ustar00rootroot00000000000000**/target **/*.rs.bk Cargo.lock .idea *.iml js/node_modules lib/tests/rocksdb_bc_data venv .env data/ .htpasswd **/docs/_build **/docs/build *.so *.pyc **.pyioxigraph-oxigraph-68d45f7/.gitmodules000066400000000000000000000015151476336702400177160ustar00rootroot00000000000000[submodule "testsuite/rdf-tests"] path = testsuite/rdf-tests url = https://github.com/w3c/rdf-tests.git [submodule "testsuite/rdf-star"] path = testsuite/rdf-star url = https://github.com/w3c/rdf-star.git [submodule "bench/bsbm-tools"] path = bench/bsbm-tools url = https://github.com/Tpt/bsbm-tools.git [submodule "oxrocksdb-sys/rocksdb"] path = oxrocksdb-sys/rocksdb url = https://github.com/facebook/rocksdb.git [submodule "oxrocksdb-sys/lz4"] path = oxrocksdb-sys/lz4 url = https://github.com/lz4/lz4.git [submodule "testsuite/N3"] path = testsuite/N3 url = https://github.com/w3c/N3.git branch = master [submodule "testsuite/rdf-canon"] path = testsuite/rdf-canon url = https://github.com/w3c/rdf-canon.git [submodule "cli/templates/yasgui"] path = cli/templates/yasgui url = https://github.com/oxigraph/yasgui-release.git oxigraph-oxigraph-68d45f7/.mailmap000066400000000000000000000002731476336702400171620ustar00rootroot00000000000000Thomas Tanon Thomas Tanon Thomas Tanon oxigraph-oxigraph-68d45f7/.readthedocs.yaml000066400000000000000000000004661476336702400207740ustar00rootroot00000000000000version: 2 sphinx: builder: html configuration: python/docs/conf.py build: os: "ubuntu-lts-latest" tools: python: "3" rust: latest apt_packages: - clang python: install: - requirements: python/requirements.dev.txt - method: pip path: python submodules: include: all oxigraph-oxigraph-68d45f7/CHANGELOG.md000066400000000000000000001143621476336702400173570ustar00rootroot00000000000000## [0.4.9] - 2025-03-09 ### Changed - SPARQL: Fixes a bug that made `GROUP_CONCAT(..., SEPARATOR="...")` always assume `DISTINCT`. - `oxrdfxml`: Fixes `TokioAsyncWriterRdfXmlSerializer` name. ## [0.4.8] - 2025-02-14 ### Changed - SPARQL: Fixes a bug in `sameTerm` implementation on `xsd:decimal`. - Turtle: fixes a parsing ambiguity when a file ends with an integer then a concatenated dot eg. `11.`. ## [0.4.7] - 2025-01-19 ### Changed - SPARQL: `GRAPH ?g {}` now properly returns all named graph in the queried dataset. - SPARQL: `ASK { OPTIONAL { X }}` now properly returns true even if `X` raises an error. - `sparopt`: breaking 0.2 release because of the introduction of a `Graph` algebra node to support `GRAPH ?g {}` syntax. ## [0.4.6] - 2025-01-11 ### Added - `spareval` crate that provides a stand-alone SPARQL evaluator without dependency on Oxigraph storage systems. - Rust/Python: the API now allows to substitute variables with value before evaluating a query. Substitution follows [RDF-dev SEP-0007](https://github.com/w3c/sparql-dev/blob/main/SEP/SEP-0007/sep-0007.md). ### Changed - Bump RocksDB to 9.10.0. ## [0.4.5] - 2024-12-21 ### Added - SPARQL: support for the 'q' regular expression option. ### Changed - Turtle: do not buffer everything if there is no line jump in the file. - RDF/XML: fixes decoding of escaped whitespaces. - Bump RocksDB to 9.9.3. - Allow compilation on zkvm by removing the dependency on `std::time` for this platform. ## [0.4.4] - 2024-11-21 ### Added - Integrates geosparql in the CLI Docker image and the JS bindings. - Python: support for 3.13 free-threaded builds. ## [0.4.3] - 2024-11-07 ### Changed - Fixes segfaults on `Store` drop when using the memory backend on large datasets (drop was doing too many recursive call leading to a stack overflow). - Upgrades RocksDB to 9.7.4. - Allows `thiserror` 2.0 and `geo` 0.29. ## [0.4.2] - 2024-10-31 ### Added - `spargeo` crate providing [GeoSPARQL simple features query functions](https://docs.ogc.org/is/22-047r1/22-047r1.html#req_geometry-topology-extension_sf-query-functions). They are enabled by default in Oxigraph CLI and Oxigraph Python bindings. - Turtle, TriG and RDF/XML: allow to set a base IRI during serialization. - Python: exposes prefixes and base IRI after parsing. - Python: allows to give to `Literal` constructor a `int`, `float` or `bool`. ### Changed - SPARQL XML query results: escape whitespaces at the extremities of values to make sure they are not trimmed. - Turtle: fixes support of empty local name. - N3: properly inject the base IRI before parsing. - RDF/XML do not return invalid prefixes after parsing. - RDF/XML: properly deduplicate prefixes during serialization and fixes prefix conflicts. ## [0.4.1] - 2024-10-13 ### Added - JS: add `default_graph` and `named_graphs` query options to set the default and named graphs. - HTTP server: return SPARQL service description on GET requests. ### Changed - NTriples/NQuads error recovery: do not consider the predicate as a possible subject if the subject is invalid. - Turtle: prevent line jump in single quoted string (but not in unchecked/lenient mode). - Turtle: forbid invalid \u+123 escape sequences. - RDF/XML: only ignore possible literal values composed of whitespaces if an other object value is present. - SPARQL: fixes regex function evaluation when the regex is not set to a literal. - SPARQL: ignore errors in joins if the other side of the join returns no results (the output would be no results anyway if the errors are fixed). - SPARQL: make ASK return true if the inner query is returning a valid tuple even if the inner query is also returning errors (fixing these errors would not change the ASK output). - SPARQL optimizer: do not attempt to optimize SERVICE (keep them, do not push filter in them...). This was leading to a lot of subtle behavior changes. - oxRDF parser: allow spaces between literal value and langtag/datatype, unicode escapes in URIs. ## [0.4.0] - 2024-09-22 ### Changed - SPARQL: fixes evaluation of `GRAPH ?g { ?s / ?o }` property paths with `?g` and `?s` unbound and `?o` bound. ## [0.4.0-rc.2] - 2024-09-15 ### Added - Rust: `Triple::from_str` and `Quad::from_str`. ### Changed - SPARQL: fixes evaluation of `GRAPH ?g { ?s !(...) ?o }` property paths with `?g` and `?s` unbound and `?o` bound. - NTriples/NQuads: properly mandate that lines must contain at most a single triple/quad. - Turtle-like formats: do not allow the empty blank node identifier. - Rust: Big renaming in parsers and serializers to get an hopefully nicer API: - Use `Parser` suffix instead of `Reader` for parsers. - Use `Writer` suffix instead of `Serializer` for serializers. - Rename `FromRead` to `Reader`, `FromTokioAsyncRead` to `TokioAsyncReader`, `FromSlice` to `Slice`, `ToWrite` to `Writer` and `ToTokioAsyncWrite` to `TokioAsyncWriter`. - Rename `read_next` to `parse_next`. - Rename `from_read` to `for_reader`, `to_write` to `for_writer`, `from_tokio_async_read` to `for_tokio_async_reader`, `to_tokio_async_write` to `for_tokio_async_writer`, `from_slice` to `for_slice`. - CLI: Allows to set `default-graph-uri` query argument even if the `--union-default-graph` CLI option is set. ## [0.4.0-rc.1] - 2024-08-25 ### Added - CLI: `union-default-graph` option to the `serve` and `query` commands to use by default the union of all graphs as the default graph in SPARQL queries. ### Changed - RocksDB build: link with the atomic library on armv5te and riscv64gc - CLI build: do not generate man pages for help commands. ## [0.4.0-alpha.8] - 2024-08-22 ### Added - oxttl, oxrdfxml, sparesults and oxrdfio: new `parse_slice` method to parse a byte slice directly. - oxttl N-Triples, N-Quads and Turtle: new `split_slice_for_parallel_parsing` method that splits a slice into chunks that can be parsed in parallel. - Python: support for custom SPARQL functions. - CLI: man pages and shell autocompletion files are now automatically generated on build. ### Removed - The storage "secondary" mode. It was quite buggy, especially with RocksDB 9. If you need it, please reach out. ### Changed - Upgrades Rust MSV to 1.75. - Bumps RocksDB to 9.5.2. - IRI: ipvFuture are now allowed and some characters are now disallowed in some places to follow RFC 3987. - sparesults: `parse_` and `serialize_` functions now take the parser/serializer by value but the parsers and serializers are now `Clone`. - Release builds abort instead of unwind on panics. - Turtle-like languages: escaped UTF-16 surrogates are now supported in unchecked/lenient mode. - SPARQL: fixes parsing of `CONSTRUCT` queries when a dot directly follows a semicolon. - SPARQL: avoids to return duplicated triples in `CONSTRUCT` response (the deduplication is only partial, some duplicates may still be returned). - JS: better TypeScript types now written by hand. - JS: allow any `Iterator` implementation in `Store` constructor. - Python: `Store.bulk_load` now properly uses the bulk loader. - Python: improves the way the GIL is released. Might fix some weird bugs. - HTTP: increases max SPARQL body size to 128MB. ## [0.4.0-alpha.7] - 2024-05-18 ### Added - JS: `Store.query` has now a `results_format` option to select the format in which the results should be returned. When enabled the return type will be a string. ### Changed - Default in memory storage is now a brand new implementation based on hash sets and linked list. See [description in the wiki](https://github.com/oxigraph/oxigraph/wiki/Architecture#how-is-the-in-memory-storage-working). It provides proper transaction rollbacks and allows concurrent reads while a write transaction is happening. It is now used when a `Store` is created without an underlying storage directory. - SPARQL: `DESCRIBE` now implements [Concise Bounded Description](https://www.w3.org/submissions/CBD/). - SPARQL: avoids join reordering inside of `SERVICE` calls. - SPARQL: fixes parsing of `DESCRIBE` query when there is a space between the IRI after `FROM` and the `WHERE` keyword. - Uses the "fx hash" from Firefox and rustc in the hash maps to make hashing faster. - RDF/XML: if `unchecked` is enabled, `rdf:ID` distinctness won't be validated. - Python: uses Python 0.21 bindings. - JS: The `load` and `dump` method second argument is now an object with options. The old API is kept for backward compatibility and prints warnings. - CLI: removes dependencies on escargot. - CLI: uses `@zazuko/yasqui` instead of `@triply/yasqui` and vendors the JS and CSS in the built binary. ## [0.4.0-alpha.6] - 2024-03-25 ### Changed - Fixes compatibility with OxIRI 0.2.3. ## [0.4.0-alpha.5] - 2024-03-23 ### Added - Python: `Dataset` class ### Changed - Rust: `Dataset::canonicalize` and `Graph::canonicalize` takes for input a `CanonicalizationAlgorithm` to set which algorithm to use. - Upgrades RocksDB to 9.0.0 - JS: Drops NodeJS 12-16 and older web browsers (Edge before Chromium...) support. ## [0.4.0-alpha.4] - 2024-03-07 ### Added - Rust: `From` for `GraphName` (and similarly for `*Ref`). - Prebuilt Python wheels for Linux with [musl libc](https://www.musl-libc.org/). ### Changed - Fixes TriG serialization. - `QueryDataset` is now properly exposed in the public Rust API. ## [0.3.11] - 2023-01-18 ### Added - SPARQL: Implementation of the `LATERAL` operator following [SPARQL SEP-0006](https://github.com/w3c/sparql-12/blob/main/SEP/SEP-0006/sep-0006.md). Support is behind the `sep-0006` feature in `spargebra` and enabled by default in Oxigraph. - SPARQL: Implementation of the `ADJUST` function following [SPARQL SEP-0002](https://github.com/w3c/sparql-12/blob/main/SEP/SEP-0002/sep-0002.md). Support is behind the `sep-0002` feature in `spargebra` and enabled by default in Oxigraph. - Rust: There is a new stand-alone crate `oxsdatatypes` implementing Rust structs for the common XML schema datatypes. It was part of the Oxigraph crate and it might be useful for other SPARQL or XPath implementations. - Rust: The `oxigraph` crate can now be compiled for `wasm32-wasi` with the basic in-memory backend but without RocksDB. ### Changed - SPARQL: The property path evaluator was sometime emitting duplicates when evaluating disjunctive patterns (`a|b`). It is now fixed. - SPARQL: If written explicitly in the SPARQL query, the regular expressions are now compiled once and not for each row. - SPARQL: Property path evaluation with both start and end variables bound has been optimized. - SPARQL: Casts to `xsd:decimal` from `xsd:float` and `xsd:double` now properly fails on overflow instead of overflowing the internal 128-bits representation. This follows [XPath casting rules](https://www.w3.org/TR/xpath-functions-31/#casting-to-decimal). - Rust: The minimal supported Rust version is set at 1.60 and enforced using the CI. - Python: Local builds will now target the specific Python version instead of [abi3](https://docs.python.org/3/c-api/stable.html). abi3 wheels are still released on Pypi alongside new Python-version specific builds for Linux+GNU. - SPARQL: Fixes a panic when the estimated upper bound of the results size was overflowing a `usize`. - Python: Uses `typing.IO` in Python stubs instead of narrower interfaces. - Upgrades RocksDB to 7.9.2, `quick-xml` to 0.27 and `pyo3` to 0.18. ## [0.3.10] - 2022-12-21 ### Added - SPARQL: Property path with unbound graph are now evaluated. For example, `SELECT * WHERE { GRAPH ?g { ?s ex:p+ ?o } }` now works. - SPARQL: The query optimizer is now fuzzed for better testing. ### Changed - SPARQL: Evaluation of zero-length paths with both start and end unbounded now return only terms defined in the current graph but not terms only defined in the query. For example, in `SELECT * WHERE { VALUES ?s { 1 } { ?s ex:p? ?o } }` the literal `1` won't be returned anymore if not in the queried graph. - Python: type stubs are now compatible with Python 3.7 and Mypy strict mode. - RDF/XML: allows entities declared using other entities. - Upgrades `quick-xml` to 0.26. ## [0.3.9] - 2022-12-07 ### Added - Server: The `/store` endpoints now has a `no_transaction` HTTP option for `POST` and `PUT` request to get better performances at the cost of transactional guarantees. - Server: The `/store` endpoints now has a `lenient` HTTP option for `POST` and `PUT` request to ignore syntax errors (requires the `no_transaction` option). - Server: allows path that are not valid UTF-8 in file path CLI arguments. - Rust: `From` to `oxigraph::Query` (thanks to @hobofan). ### Changed - SPARQL: `NOW()` function properly returns the current time and not 1970-01-01 - SPARQL: fixes serialization of SPARQL queries (property path and STRSTARTS function). - SPARQL: slightly optimize aggregates by avoiding an unneeded projection. - SPARQL: the parser now cleanly fails if invalid `VALUES` clauses are provided. - SPARQL: In DELETE/INSERT UPDATE the currently written values can't be read anymore ("Halloween problem"). - `oxrdf`: makes Clippy run without warnings when `rdf-star` is disable. - Python: makes type annotations compatible with Python 3.7. - Python: makes sure the parameter default value is always included in the type annotation. ## [0.3.8] - 2022-10-22 ### Changed - Python: avoid uploading debug wheels to pypi. ## [0.3.7] - 2022-10-22 ### Added - Python type stubs are now generated and distributed alongside wheels. ### Changed - SPARQL: Fixes evaluation of sub queries in which some variables might not be always bound. - Python: Uses https://pyoxigraph.readthedocs.io instead of https://oxigraph.org/pyoxigraph/ to store pyoxigraph documentation. - Server: Migration to Clap v4. Slight changes to the console textual outputs. ## [0.3.6] - 2022-08-16 ### Changed - Server: Makes sure body is always sent for all HTTP response codes that are not explicitly to not do that according to the HTTP specification. - Python: Adds support of reading from `io.TextIOBase`. ## [0.3.5] - 2022-08-02 ### Changed - Fixes a bug in the SPARQL parser with nested anonymous blank nodes (`[]`). - Migrates the Python documentation skin to [Furo](https://pradyunsg.me/furo/). ## [0.3.4] - 2022-07-17 ### Changed - Allows multiple value to be given to `oxigraph_server` `--file` parameter. - Limits parallelism of `oxigraph_server` file loader to avoid stack overflows. ## [0.3.3] - 2022-06-12 ### Added - `QueryResults::with_http_redirection_limit` that allows to set a limit on the number of HTTP redirection followed (`0` by default). ### Changed - Fixes a bug in SPARQL parser that was always enabling the `SILENT` option of the `SERVICE` operator. - Allows unknown keys in the objects present in the SPARQL JSON query results. It allows to parse e.g. Virtuoso query results. - Allows `"typed-literal"` as an alias of `"literal"` in SPARQL JSON query results. - Improves the HTTP client error reporting: no silent failures on 4XX and 5XX responses. - Upgrades RocksDB to 7.3.1. - Upgrades quick-xml to 0.23. ## [0.3.2] - 2022-04-24 ### Changed - Fixes a bug in the server bulk loader that crashed on very small files. - Upgrades RocksDB to v7.1. - Removes some dead code in the bulk loader. ## [0.3.1] - 2022-04-02 ### Changed - The default git branch is now `main` and not `master` (thanks to @nyurik). - Upgrades RocksDB to v7.0.4. - Limits the number of bulk loader threads to at most 4 (allows bigger BTree chunks and a better data layout). - Limits the number of files opened by RocksDB to the soft file descriptor limit minus 48. ## [0.3.0] - 2022-03-19 ### Changed - Fixes compilation on ARM. - Moves some lints from errors to warnings in order to avoid compilation failures on different Rust versions. ## [0.3.0-rc.1] - 2022-03-14 ### Added - The bulk loader now checks available memory and tries to increase its batch size to make use of it. - The Bulk loader provides now a `--lenient` option to keep loading a file even if invalid data is found (works only with N-Triples and N-Quads). This behavior can be customised in the Rust API using the `BulkLoader::on_parse_error` method. ### Changed - Rocksdb has been upgrade to 7.0.2. It now requires a C++17 compatible compiler. This required dropping support of macOS 10.9 to 10.13. ## [0.3.0-beta.4] - 2022-02-27 ### Added - JS: Oxigraph NPM package is now also supporting web browsers and WebPack. - JS: RDF term related classes now overrides the `toString` method. - Python: It is now possible to directly give a file path to the `parse`, `serialize`, `Store.load`, `Store.bulk_load` and `Store.dump` functions. - Python: New `Store.clear_graph`, `Store.clear`, `Store.optimize` and `Store.flush` methods. ### Removed - `sophia_api` traits implementation following a request of Sophia maintainer. ### Changed - SPARQL: fixes evaluation of SPARQL queries with no results but an `ORDER BY` clause. There should be no group in the output instead of one empty group. This behavior has been changed following [this discussion](https://github.com/w3c/rdf-tests/pull/61). - SPARQL: fixes SPARQL-star evaluation of nested triples with both variables and constants. - SPARQL: if results are sorted, literals are now ordered by value, then datatype, then language tag. This ordering is considered as "implementation defined" by the SPARQL specification and is very likely to change in the future. - Python: all costly methods now release the python GIL allowing multithreaded usages of pyoxigraph. - Rust: SPARQL results writer now flushes the buffer at the end of the results writes. This makes their API less error-prone. - Rust: the bulk loader API has been rewritten to allow hooking a progress indicator and set parallelism limit. - Server: it is now possible to bulk load gzipped files. ## [0.3.0-beta.3] - 2022-02-02 ### Changed - Fixes a bug in the `bulk_load_dataset` method that was creating an invalid database. - Server: Takes into account also URL query parameters if the send SPARQL request body is using form-urlencoded. - Upgrades RocksDB to v0.28.2. - Generate clean python sdist files compiling Oxigraph from scratch with the proper `Cargo.lock`. - Do not push beta releases to homebrew and python stable documentation. - Moves RocksDB binding directory to `oxrocksdb-sys`. ## [0.3.0-beta.2] - 2022-01-29 ### Changed - Fixes release on crates.io of the RocksDB bindings. ## [0.3.0-beta.1] - 2022-01-29 ### Added - [RDF-star](https://w3c.github.io/rdf-star/cg-spec/2021-12-17.html) support. `Triple` is now a possible `Term`. Serialization formats and SPARQL support have been updated to match the [latest version of the specification draft](https://w3c.github.io/rdf-star/cg-spec/2021-12-17.html). - Fast data bulk load with the `Store` `bulk_load_dataset` and `bulk_load_graph` methods and a special command-line option of the server. - It is now possible to quickly backup the database using the `backup` method. - Rust: `*Syntax::from_extension` to easy guess a graph/dataset/sparql result format from a file extension. - Rust: Custom SPARQL functions are now supported using `QueryOptions::with_custom_function`. - Rust: Simple in-memory graph (`Graph`) and dataset (`Dataset`) data structures with canonicalization. - Nightly build of the server binary and docker image, and of pyoxigraph wheels. - `Store` 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). the `Store` `transaction` method now allows to do read/write transactions. -`RDF-star `_ is now supported (including serialization formats and SPARQL-star). :py:class:`.Triple` can now be used in :py:attr:`.Triple.object`, :py:attr:`.Triple.object`, :py:attr:`.Quad.subject` and :py:attr:`.Quad.object`. ### Changed - SPARQL: It is now possible to compare `rdf:langString` literals with the same language tag. - SPARQL: The parser now validates more carefully the inputs following the SPARQL specification and test suite. - SPARQL: Variable scoping was buggy with "FILTER EXISTS". It is now fixed. - Rust: RDF model, SPARQL parser and SPARQL result parsers have been moved to stand-alone reusable libraries. - Rust: HTTPS is not supported by default with the `http_client` option. You need to enable the `native-tls` or the `rustls` feature of the `oxhttp` crate to enable a TLS layer. - Rust: The error types have been cleaned. Most of the `Store` methods now return a `StorageError` that is more descriptive than the previous `std::io::Error`. The new error type all implements `Into` for easy conversion. - Rust: There is now a `Subject` struct that is the union of `NamedNode`, `BlankNode` and `Triple`. It is The used type of the `subject` field of the `Triple` and `Quad` structs. - Rust: The SPARQL algebra is not anymore publicly exposed in the `oxigraph` crate. The new `oxalgebra` crate exposes it. - Rust: `UpdateOptions` API have been rewritten. It can now be built using `From` or `Default`. - Server: The command-line API has been redesign. See the [server README](server/README.md) for more information. - Server: The HTTP implementation is now provided by [`oxhttp`](https://github.com/oxigraph/oxhttp). - Server: The HTTP response bodies are now generated on the fly instead of being buffered. - Python: The `SledStore` and `MemoryStore` classes have been removed in favor of the `Store` class. - JS: The `MemoryStore` class has been renamed to `Store`. - JS: The [RDF/JS `DataFactory` interface](http://rdf.js.org/data-model-spec/#datafactory-interface) is now implemented by the `oxigraph` module itself and the `MemoryStore.dataFactory` property has been removed. - The implementation of SPARQL evaluation has been improved for better performances (especially joins). - The TLS implementation used in SPARQL HTTP calls is now [rustls](https://github.com/rustls/rustls) and not [native-tls](https://github.com/sfackler/rust-native-tls). The host system certificate registry is still used. - Spargebra: The basic RDF terms are now the ones of the `oxrdf` crate. ### Removed - `SledStore` and `MemoryStore`. There is only the `RocksDbStore` anymore that is renamed to `Store`. - `oxigraph_wikibase` is now stored in [its own repository](https://github.com/oxigraph/oxigraph-wikibase). - Rust: `From` implementations between `oxigraph` terms and `rio_api` terms. Many thanks to [Thad Guidry](https://github.com/thadguidry), [James Overton](https://github.com/jamesaoverton) and [Jeremiah](https://github.com/jeremiahpslewis) who sponsored the project during the development of this version. ## [0.2.5] - 2021-07-11 ### Added - [SPARQL 1.1 Query Results JSON Format](https://www.w3.org/TR/sparql11-results-json/) parser. - Python wheels for macOS are now universal2 binaries. ### Changed - The `Cargo.lock` file is now provided with releases to avoid compilation failures because of changes in dependencies. - Uses clap instead of argh for the server arguments parsing. - Upgrades PyO3 to v0.14. ## [0.2.4] - 2021-04-28 ### Changed - The HTTP server allows to query the union of all graphs using the `union-default-graph` query parameter and to use the union graph for update `WHERE` clauses using the `using-union-graph` parameter. - Exposes Sled flush operation (useful for platforms without auto-flush like Windows or Android). - Fixes a possible out of bound panic in SPARQL query evaluation. - Upgrades RocksDB to 6.17.3. ## [0.2.3] - 2021-04-11 ### Changed - Server: Fixes HTTP content negotiation (charset constraints, failure to properly handle `*/*`...). - Makes Clippy 1.51 happy. ## [0.2.2] - 2021-03-18 ### Added - Support of XML entities to the RDF/XML parser ### Changed - Serve: Allows unsupported query parameters in HTTP SPARQL requests. - Fixes WASM compilation bug and optimises WASM release packages. - Fixes named graph creation inside of a SledStore transaction. ## [0.2.1] - 2021-01-16 ### Changed - Fixes `pyoxigraph` build by enforcing a given `maturin` version. - Adds code to build Python wheels for MacOS and Windows. ## [0.2.0] - 2021-01-07 ### Added - [SPARQL 1.1 Update](https://www.w3.org/TR/sparql11-update/) support for Rust, Python and JavaScript. All store-like classes now provide an `update` method. - [SPARQL 1.1 Query Results CSV and TSV Formats](https://www.w3.org/TR/sparql11-results-csv-tsv/) serializers and TSV format parser. - [SPARQL 1.1 Graph Store HTTP Protocol](https://www.w3.org/TR/sparql11-http-rdf-update/) partial support in `oxigraph_server`. This protocol is accessible under the `/store` path. - The SPARQL Query and Update algebra is now public. - The stores are now "graph aware" i.e. it is possible to create and keep empty named graphs. - A simple built-in HTTP client. In the Rust library, is disabled by default behind the `http_client` feature. It powers SPARQL federation and SPARQL UPDATE `LOAD` operations. - `std::str::FromStr` implementations to `NamedNode`, `BlankNode`, `Literal`, `Term` and `Variable` allowing to easily parse Turtle/SPARQL serialization of these terms. - Optional Sled storage for `oxigraph_server`. ### Removed - The `default_graph_uris` and `named_graph_uris` parameters from `pyoxigraph` `query` methods. - Python 3.5 support. - `(Memory|RocksDB|Sled)Store::prepare_query` methods. It is possible to cache SPARQL query parsing using the `Query::parse` function and give the parsed query to the `query` method. ### Changed - Loading data into `oxigraph_server` is now possible using `/store` and not anymore using `/`. For example, you should use now `curl -f -X POST -H 'Content-Type:application/n-quads' --data-binary "@MY_FILE.nq" http://localhost:7878/store` to add the N-Quads file MY_FILE.nt to the server dataset. - Fixes evaluation of `MONTH()` and `DAY()` functions on the `xsd:date` values. - `Variable::new` now validates the variable name. - `(Memory|RocksDB|Sled)Store::query` does not have an option parameter anymore. There is now a new `query_opt` method that allows giving options. - `xsd:boolean` SPARQL function now properly follows XPath specification. - Fixes SPARQL `DESCRIBE` evaluation. ### Disk data format The disk data format has been changed between Oxigraph 0.1 (version 0) and Oxigraph 0.2 (version 1). Data is automatically migrated from the version 0 format to the version 1 format when opened with Oxigraph 0.2. ## [0.1.1] - 2020-08-14 ### Added - The `"sophia"` feature implementing the [`sophia_api`](https://docs.rs/sophia_api/) traits on Oxigraph terms and stores. - Explicit types for quads iterators returned by stores. ### Changed - `QueryOptions::with_default_graph` now takes an `impl Into` instead of an `impl Into`. - `QueryOptions::with_named_graph` now takes an `impl Into` instead of an `impl Into`. - `pyoxigraph` `query` methods now takes two new parameters, `default_graph` and `named_graphs`. `default_graph_uris` and `named_graph_uris` parameters are deprecated. - Fixes a bug in `xsd:gYear` parsing. ## [0.1.0] - 2020-08-09 ### Added - `QueryOptions` now allows settings the query dataset graph URIs (the SPARQL protocol `default-graph-uri` and `named-graph-uri` parameters). - `pyoxigraph` store `query` methods allows to provide the dataset graph URIs. It also provides an option to use all graph names as the default graph. - "default graph as union option" now works with FROM NAMED. - `pyoxigraph` now exposes and documents `Variable`, `QuerySolution`, `QuerySolutions` and `QueryTriples` ## [0.1.0-rc.1] - 2020-08-08 ### Added - `oxigraph` Rust library with SPARQL 1.1 query support and memory, Sled and RocksDB stores. - `oxigraph_server` standalone SPARQL server. - `oxigraph_wikibase` standalone SPARQL server loading data from a Wikibase instance. - `pyoxigraph` Python library based on Oxigraph. - `oxigraph` NodeJS library based on Oxigraph. oxigraph-oxigraph-68d45f7/CITATION.cff000066400000000000000000000007501476336702400174330ustar00rootroot00000000000000cff-version: 1.2.0 abstract: "Oxigraph is a graph database implementing the SPARQL standard." authors: - given-names: Thomas family-names: Pellissier Tanon email: thomas@pellissier-tanon.fr orcid: "https://orcid.org/0000-0002-0620-6486" doi: 10.5281/zenodo.7408022 license: - Apache-2.0 - MIT message: "If you use this software, please cite it as below." repository-code: "https://github.com/oxigraph/oxigraph" title: Oxigraph type: software url: "https://oxigraph.org" oxigraph-oxigraph-68d45f7/Cargo.lock000066400000000000000000002130231476336702400174450ustar00rootroot00000000000000# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "addr2line" version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] name = "adler2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "allocator-api2" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", "once_cell", "windows-sys 0.59.0", ] [[package]] name = "anyhow" version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" [[package]] name = "approx" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" dependencies = [ "num-traits", ] [[package]] name = "arbitrary" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" dependencies = [ "derive_arbitrary", ] [[package]] name = "assert_cmd" version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d" dependencies = [ "anstyle", "bstr", "doc-comment", "libc", "predicates", "predicates-core", "predicates-tree", "wait-timeout", ] [[package]] name = "assert_fs" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7efdb1fdb47602827a342857666feb372712cbc64b414172bd6b167a02927674" dependencies = [ "anstyle", "doc-comment", "globwalk", "predicates", "predicates-core", "predicates-tree", "tempfile", ] [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", "windows-targets", ] [[package]] name = "bindgen" version = "0.71.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" dependencies = [ "bitflags", "cexpr", "clang-sys", "itertools 0.13.0", "log", "prettyplease", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", "syn", ] [[package]] name = "bitflags" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "bstr" version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" dependencies = [ "memchr", "regex-automata", "serde", ] [[package]] name = "bumpalo" version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "bzip2" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" dependencies = [ "bzip2-sys", ] [[package]] name = "bzip2-sys" version = "0.1.13+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" dependencies = [ "cc", "pkg-config", ] [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" version = "1.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" dependencies = [ "jobserver", "libc", "shlex", ] [[package]] name = "cexpr" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ "nom", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "ciborium" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", "serde", ] [[package]] name = "ciborium-io" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", ] [[package]] name = "clang-sys" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", "libloading", ] [[package]] name = "clap" version = "4.5.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.5.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_complete" version = "4.5.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5c5508ea23c5366f77e53f5a0070e5a84e51687ec3ef9e0464c86dc8d13ce98" dependencies = [ "clap", ] [[package]] name = "clap_derive" version = "4.5.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" dependencies = [ "heck", "proc-macro2", "quote", "syn", ] [[package]] name = "clap_lex" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "clap_mangen" version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "724842fa9b144f9b89b3f3d371a89f3455eea660361d13a554f68f8ae5d6c13a" dependencies = [ "clap", "roff", ] [[package]] name = "codspeed" version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de4b67ff8985f3993f06167d71cf4aec178b0a1580f91a987170c59d60021103" dependencies = [ "colored", "libc", "serde", "serde_json", "uuid", ] [[package]] name = "codspeed-criterion-compat" version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68403d768ed1def18a87e2306676781314448393ecf0d3057c4527cabf524a3d" dependencies = [ "codspeed", "colored", "criterion", ] [[package]] name = "colorchoice" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "colored" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", "windows-sys 0.59.0", ] [[package]] name = "console_error_panic_hook" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" dependencies = [ "cfg-if", "wasm-bindgen", ] [[package]] name = "core-foundation" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "core-foundation" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crc32fast" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "criterion" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" dependencies = [ "anes", "cast", "ciborium", "clap", "criterion-plot", "is-terminal", "itertools 0.10.5", "num-traits", "once_cell", "oorandom", "plotters", "rayon", "regex", "serde", "serde_derive", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "criterion-plot" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", "itertools 0.10.5", ] [[package]] name = "crossbeam-deque" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] [[package]] name = "csv" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" dependencies = [ "csv-core", "itoa", "ryu", "serde", ] [[package]] name = "csv-core" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" dependencies = [ "memchr", ] [[package]] name = "dashmap" version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ "cfg-if", "crossbeam-utils", "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", ] [[package]] name = "deranged" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", ] [[package]] name = "derive_arbitrary" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "difflib" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", ] [[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "doc-comment" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "earcutr" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79127ed59a85d7687c409e9978547cffb7dc79675355ed22da6b66fd5f6ead01" dependencies = [ "itertools 0.11.0", "num-traits", ] [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", "windows-sys 0.59.0", ] [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "flate2" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "float-cmp" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" dependencies = [ "num-traits", ] [[package]] name = "float_next_after" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" [[package]] name = "foldhash" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" [[package]] name = "foreign-types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ "foreign-types-shared", ] [[package]] name = "foreign-types-shared" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "geo" version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34f0e6e028c581e82e6822a68869514e94c25e7f8ea669a2d8595bdf7461ccc5" dependencies = [ "earcutr", "float_next_after", "geo-types", "geographiclib-rs", "i_overlay", "log", "num-traits", "robust", "rstar", "spade", ] [[package]] name = "geo-traits" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b018fc19fa58202b03f1c809aebe654f7d70fd3887dace34c3d05c11aeb474b5" dependencies = [ "geo-types", ] [[package]] name = "geo-types" version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3bd1157f0f936bf0cd68dec91e8f7c311afe60295574d62b70d4861a1bfdf2d9" dependencies = [ "approx", "num-traits", "rayon", "rstar", "serde", ] [[package]] name = "geographiclib-rs" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e5ed84f8089c70234b0a8e0aedb6dc733671612ddc0d37c6066052f9781960" dependencies = [ "libm", ] [[package]] name = "geojson" version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e26f3c45b36fccc9cf2805e61d4da6bc4bbd5a3a9589b01afa3a40eff703bd79" dependencies = [ "geo-types", "log", "serde", "serde_json", "thiserror 2.0.12", ] [[package]] name = "getopts" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" dependencies = [ "unicode-width", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] [[package]] name = "getrandom" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" dependencies = [ "cfg-if", "libc", "wasi 0.13.3+wasi-0.2.2", "windows-targets", ] [[package]] name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "globset" version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" dependencies = [ "aho-corasick", "bstr", "log", "regex-automata", "regex-syntax", ] [[package]] name = "globwalk" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ "bitflags", "ignore", "walkdir", ] [[package]] name = "half" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" dependencies = [ "cfg-if", "crunchy", ] [[package]] name = "hash32" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" dependencies = [ "byteorder", ] [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ "allocator-api2", "equivalent", "foldhash", ] [[package]] name = "heapless" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" dependencies = [ "hash32", "stable_deref_trait", ] [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "httparse" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "i_float" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "775f9961a8d2f879725da8aff789bb20a3ddf297473e0c90af75e69313919490" dependencies = [ "serde", ] [[package]] name = "i_key_sort" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "347c253b4748a1a28baf94c9ce133b6b166f08573157e05afe718812bc599fcd" [[package]] name = "i_overlay" version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01882ce5ed786bf6e8f5167f171a4026cd129ce17d9ff5cbf1e6749b98628ece" dependencies = [ "i_float", "i_key_sort", "i_shape", "i_tree", "rayon", ] [[package]] name = "i_shape" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27dbe9e5238d6b9c694c08415bf00fb370b089949bd818ab01f41f8927b8774c" dependencies = [ "i_float", "serde", ] [[package]] name = "i_tree" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "155181bc97d770181cf9477da51218a19ee92a8e5be642e796661aee2b601139" [[package]] name = "icu_collections" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ "displaydoc", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locid" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_locid_transform" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" dependencies = [ "displaydoc", "icu_locid", "icu_locid_transform_data", "icu_provider", "tinystr", "zerovec", ] [[package]] name = "icu_locid_transform_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" [[package]] name = "icu_normalizer" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" dependencies = [ "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", "utf16_iter", "utf8_iter", "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" [[package]] name = "icu_properties" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ "displaydoc", "icu_collections", "icu_locid_transform", "icu_properties_data", "icu_provider", "tinystr", "zerovec", ] [[package]] name = "icu_properties_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" [[package]] name = "icu_provider" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" dependencies = [ "displaydoc", "icu_locid", "icu_provider_macros", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_provider_macros" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "idna" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ "idna_adapter", "smallvec", "utf8_iter", ] [[package]] name = "idna_adapter" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" dependencies = [ "icu_normalizer", "icu_properties", ] [[package]] name = "ignore" version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" dependencies = [ "crossbeam-deque", "globset", "log", "memchr", "regex-automata", "same-file", "walkdir", "winapi-util", ] [[package]] name = "indoc" version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" [[package]] name = "is-terminal" version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi", "libc", "windows-sys 0.59.0", ] [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itertools" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ "either", ] [[package]] name = "itertools" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jobserver" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] [[package]] name = "js-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "json-event-parser" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73267b6bffa5356bd46cfa89386673e9a7f62f4eb3adcb45b1bd031892357853" dependencies = [ "tokio", ] [[package]] name = "kernel32-sys" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" dependencies = [ "winapi", "winapi-build", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" version = "0.2.170" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" [[package]] name = "libloading" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", "windows-targets", ] [[package]] name = "libm" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "linux-raw-sys" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" [[package]] name = "litemap" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "lock_api" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" [[package]] name = "md-5" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ "cfg-if", "digest", ] [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memoffset" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" dependencies = [ "adler2", ] [[package]] name = "native-tls" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" dependencies = [ "libc", "log", "openssl", "openssl-probe", "openssl-sys", "schannel", "security-framework 2.11.1", "security-framework-sys", "tempfile", ] [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "normalize-line-endings" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", ] [[package]] name = "object" version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "oorandom" version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "openssl" version = "0.10.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" dependencies = [ "bitflags", "cfg-if", "foreign-types", "libc", "once_cell", "openssl-macros", "openssl-sys", ] [[package]] name = "openssl-macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "openssl-probe" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" version = "0.9.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "oxhttp" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9855b85a55168c358914bb10df57e44a5fe833301262012cdb2a9dbd26295ac8" dependencies = [ "flate2", "httparse", "native-tls", "rustls", "rustls-native-certs", "rustls-pki-types", "url", "webpki-roots", ] [[package]] name = "oxigraph" version = "0.4.9" dependencies = [ "bzip2", "codspeed-criterion-compat", "csv", "dashmap", "getrandom 0.2.15", "js-sys", "libc", "oxhttp", "oxiri", "oxrdf", "oxrdfio", "oxrocksdb-sys", "oxsdatatypes", "rand", "rustc-hash", "siphasher", "sparesults", "spareval", "spargebra", "thiserror 2.0.12", ] [[package]] name = "oxigraph-cli" version = "0.4.9" dependencies = [ "anyhow", "assert_cmd", "assert_fs", "clap", "clap_complete", "clap_mangen", "flate2", "oxhttp", "oxigraph", "oxiri", "predicates", "rand", "rayon-core", "spargeo", "url", ] [[package]] name = "oxigraph-js" version = "0.4.9" dependencies = [ "console_error_panic_hook", "js-sys", "oxigraph", "spargeo", "wasm-bindgen", ] [[package]] name = "oxigraph-testsuite" version = "0.0.0" dependencies = [ "anyhow", "clap", "codspeed-criterion-compat", "oxigraph", "oxiri", "oxttl", "spareval", "spargebra", "spargeo", "sparopt", "text-diff", "time", ] [[package]] name = "oxilangtag" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23f3f87617a86af77fa3691e6350483e7154c2ead9f1261b75130e21ca0f8acb" dependencies = [ "serde", ] [[package]] name = "oxiri" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54b4ed3a7192fa19f5f48f99871f2755047fabefd7f222f12a1df1773796a102" [[package]] name = "oxrdf" version = "0.2.4" dependencies = [ "oxilangtag", "oxiri", "oxsdatatypes", "rand", "thiserror 2.0.12", ] [[package]] name = "oxrdfio" version = "0.1.7" dependencies = [ "oxrdf", "oxrdfxml", "oxttl", "thiserror 2.0.12", "tokio", ] [[package]] name = "oxrdfxml" version = "0.1.6" dependencies = [ "oxilangtag", "oxiri", "oxrdf", "quick-xml", "thiserror 2.0.12", "tokio", ] [[package]] name = "oxrocksdb-sys" version = "0.4.9" dependencies = [ "bindgen", "cc", "libc", "pkg-config", ] [[package]] name = "oxsdatatypes" version = "0.2.2" dependencies = [ "js-sys", "thiserror 2.0.12", ] [[package]] name = "oxttl" version = "0.1.7" dependencies = [ "memchr", "oxilangtag", "oxiri", "oxrdf", "rayon", "thiserror 2.0.12", "tokio", ] [[package]] name = "parking_lot_core" version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets", ] [[package]] name = "peg" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9928cfca101b36ec5163e70049ee5368a8a1c3c6efc9ca9c5f9cc2f816152477" dependencies = [ "peg-macros", "peg-runtime", ] [[package]] name = "peg-macros" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6298ab04c202fa5b5d52ba03269fb7b74550b150323038878fe6c372d8280f71" dependencies = [ "peg-runtime", "proc-macro2", "quote", ] [[package]] name = "peg-runtime" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "132dca9b868d927b35b5dd728167b2dee150eb1ad686008fc71ccb298b776fca" [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "plotters" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits", "plotters-backend", "plotters-svg", "wasm-bindgen", "web-sys", ] [[package]] name = "plotters-backend" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] [[package]] name = "portable-atomic" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ "zerocopy", ] [[package]] name = "predicates" version = "3.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" dependencies = [ "anstyle", "difflib", "float-cmp", "normalize-line-endings", "predicates-core", "regex", ] [[package]] name = "predicates-core" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" [[package]] name = "predicates-tree" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" dependencies = [ "predicates-core", "termtree", ] [[package]] name = "prettyplease" version = "0.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1ccf34da56fc294e7d4ccf69a85992b7dfb826b7cf57bac6a70bba3494cc08a" dependencies = [ "proc-macro2", "syn", ] [[package]] name = "proc-macro2" version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" version = "0.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7778bffd85cf38175ac1f545509665d0b9b92a198ca7941f131f85f7a4f9a872" dependencies = [ "cfg-if", "indoc", "libc", "memoffset", "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", "unindent", ] [[package]] name = "pyo3-build-config" version = "0.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94f6cbe86ef3bf18998d9df6e0f3fc1050a8c5efa409bf712e661a4366e010fb" dependencies = [ "once_cell", "target-lexicon", ] [[package]] name = "pyo3-ffi" version = "0.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9f1b4c431c0bb1c8fb0a338709859eed0d030ff6daa34368d3b152a63dfdd8d" dependencies = [ "libc", "pyo3-build-config", ] [[package]] name = "pyo3-macros" version = "0.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbc2201328f63c4710f68abdf653c89d8dbc2858b88c5d88b0ff38a75288a9da" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", "syn", ] [[package]] name = "pyo3-macros-backend" version = "0.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fca6726ad0f3da9c9de093d6f116a93c1a38e417ed73bf138472cf4064f72028" dependencies = [ "heck", "proc-macro2", "pyo3-build-config", "quote", "syn", ] [[package]] name = "pyoxigraph" version = "0.4.9" dependencies = [ "oxigraph", "pyo3", "spargeo", ] [[package]] name = "quick-xml" version = "0.37.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003" dependencies = [ "memchr", "tokio", ] [[package]] name = "quote" version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom 0.2.15", ] [[package]] name = "rayon" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] [[package]] name = "redox_syscall" version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" dependencies = [ "bitflags", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "ring" version = "0.17.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee" dependencies = [ "cc", "cfg-if", "getrandom 0.2.15", "libc", "untrusted", "windows-sys 0.52.0", ] [[package]] name = "robust" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbf4a6aa5f6d6888f39e980649f3ad6b666acdce1d78e95b8a2cb076e687ae30" [[package]] name = "roff" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3" [[package]] name = "rstar" version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "421400d13ccfd26dfa5858199c30a5d76f9c54e0dba7575273025b43c5175dbb" dependencies = [ "heapless", "num-traits", "smallvec", ] [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustix" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dade4812df5c384711475be5fcd8c162555352945401aed22a35bffeab61f657" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.59.0", ] [[package]] name = "rustls" version = "0.23.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" dependencies = [ "once_cell", "ring", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-native-certs" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", "security-framework 3.2.0", ] [[package]] name = "rustls-pki-types" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" [[package]] name = "rustls-webpki" version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", "untrusted", ] [[package]] name = "rustversion" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "schannel" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags", "core-foundation 0.9.4", "core-foundation-sys", "libc", "security-framework-sys", ] [[package]] name = "security-framework" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ "bitflags", "core-foundation 0.10.0", "core-foundation-sys", "libc", "security-framework-sys", ] [[package]] name = "security-framework-sys" version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "serde" version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "sha1" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "sha2" version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "siphasher" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "smallvec" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" [[package]] name = "spade" version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ece03ff43cd2a9b57ebf776ea5e78bd30b3b4185a619f041079f4109f385034" dependencies = [ "hashbrown 0.15.2", "num-traits", "robust", "smallvec", ] [[package]] name = "sparesults" version = "0.2.4" dependencies = [ "json-event-parser", "memchr", "oxrdf", "quick-xml", "thiserror 2.0.12", "tokio", ] [[package]] name = "spareval" version = "0.1.3" dependencies = [ "hex", "json-event-parser", "md-5", "oxiri", "oxrdf", "oxsdatatypes", "rand", "regex", "rustc-hash", "sha1", "sha2", "sparesults", "spargebra", "sparopt", "thiserror 2.0.12", ] [[package]] name = "spargebra" version = "0.3.5" dependencies = [ "oxilangtag", "oxiri", "oxrdf", "peg", "rand", "thiserror 2.0.12", ] [[package]] name = "spargeo" version = "0.1.6" dependencies = [ "geo", "geojson", "oxigraph", "spareval", "wkt", ] [[package]] name = "sparopt" version = "0.2.1" dependencies = [ "oxrdf", "rand", "spargebra", ] [[package]] name = "sparql-smith" version = "0.1.0-alpha.10" dependencies = [ "arbitrary", ] [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" version = "2.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "synstructure" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "target-lexicon" version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567" dependencies = [ "cfg-if", "fastrand", "getrandom 0.3.1", "once_cell", "rustix", "windows-sys 0.59.0", ] [[package]] name = "term" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2077e54d38055cf1ca0fd7933a2e00cd3ec8f6fed352b2a377f06dcdaaf3281" dependencies = [ "kernel32-sys", "winapi", ] [[package]] name = "termtree" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" [[package]] name = "text-diff" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "309238dd66f8bf11a20d015b727b926f294a13fcb8d56770bb984e7a22c43897" dependencies = [ "getopts", "term", ] [[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ "thiserror-impl 2.0.12", ] [[package]] name = "thiserror-impl" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "thiserror-impl" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "time" version = "0.3.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef" [[package]] name = "time-macros" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c" dependencies = [ "num-conv", "time-core", ] [[package]] name = "tinystr" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "tinytemplate" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ "serde", "serde_json", ] [[package]] name = "tokio" version = "1.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9975ea0f48b5aa3972bf2d888c238182458437cc2a19374b81b25cdf1023fb3a" dependencies = [ "backtrace", "bytes", "pin-project-lite", "tokio-macros", ] [[package]] name = "tokio-macros" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "typenum" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-width" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unindent" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] [[package]] name = "utf16_iter" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587" dependencies = [ "getrandom 0.3.1", ] [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wait-timeout" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" dependencies = [ "libc", ] [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" version = "0.13.3+wasi-0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" dependencies = [ "wit-bindgen-rt", ] [[package]] name = "wasm-bindgen" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "webpki-roots" version = "0.26.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" dependencies = [ "rustls-pki-types", ] [[package]] name = "winapi" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" [[package]] name = "winapi-build" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" [[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "wit-bindgen-rt" version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" dependencies = [ "bitflags", ] [[package]] name = "wkt" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1c591649bd1c9d4e28459758bbb5fb5c0edc7a67060b52422f4761c94ffe961" dependencies = [ "geo-traits", "geo-types", "log", "num-traits", "thiserror 1.0.69", ] [[package]] name = "write16" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" [[package]] name = "writeable" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "yoke" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "zerofrom" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[package]] name = "zerovec" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", "syn", ] oxigraph-oxigraph-68d45f7/Cargo.toml000066400000000000000000000176271476336702400175040ustar00rootroot00000000000000[workspace] members = [ "cli", "js", "lib/oxigraph", "lib/oxrdf", "lib/oxrdfio", "lib/oxrdfxml", "lib/oxsdatatypes", "lib/oxttl", "lib/sparesults", "lib/spareval", "lib/spargebra", "lib/spargeo", "lib/sparopt", "lib/sparql-smith", "oxrocksdb-sys", "python", "testsuite" ] resolver = "2" [workspace.package] version = "0.4.9" authors = ["Tpt "] license = "MIT OR Apache-2.0" edition = "2021" rust-version = "1.75" [workspace.dependencies] anyhow = "1.0.72" arbitrary = "1.3" assert_cmd = "2.0" assert_fs = "1.0" bindgen = ">=0.62, <0.72" bzip2 = ">=0.4, <0.6" cc = "1.0.73" clap = "4.0" clap_complete = "4.0" clap_mangen = "0.2.19" codspeed-criterion-compat = "2.3.3" console_error_panic_hook = "0.1.7" csv = "1.3" dashmap = ">=5.4, <7.0" flate2 = "1.0" geo = ">=0.28, <0.30" geojson = "0.24" getrandom = "0.2.8" hex = "0.4" js-sys = "0.3.60" json-event-parser = "0.2.1" libc = "0.2.147" md-5 = "0.10" memchr = "2.5" oxhttp = "0.2.0" oxilangtag = "0.1" oxiri = "0.2.8" peg = "0.8" pkg-config = "0.3.25" predicates = ">=2.0, <4.0" pyo3 = "0.23.3" quick-xml = "0.37" rand = "0.8" rayon = "1.8.1" rayon-core = "1.12.1" regex = "1.7" rustc-hash = "2" sha1 = "0.10" sha2 = "0.10" siphasher = ">=0.3.10, <2.0" text-diff = "0.4" thiserror = ">=1.0.50, <3.0" time = "0.3" tokio = "1.29" url = "2.4" wasm-bindgen = "0.2.83" wkt = "0.12" # Internal dependencies oxigraph = { version = "=0.4.9", path = "lib/oxigraph" } oxrdf = { version = "=0.2.4", path = "lib/oxrdf" } oxrdfio = { version = "=0.1.7", path = "lib/oxrdfio" } oxrdfxml = { version = "=0.1.6", path = "lib/oxrdfxml" } oxrocksdb-sys = { version = "=0.4.9", path = "./oxrocksdb-sys" } oxsdatatypes = { version = "=0.2.2", path = "lib/oxsdatatypes" } oxttl = { version = "=0.1.7", path = "lib/oxttl" } spargebra = { version = "=0.3.5", path = "lib/spargebra" } spargeo = { version = "=0.1.6", path = "lib/spargeo" } sparesults = { version = "=0.2.4", path = "lib/sparesults" } sparopt = { version = "=0.2.1", path = "lib/sparopt" } spareval = { version = "=0.1.3", path = "lib/spareval" } [workspace.lints.rust] absolute_paths_not_starting_with_crate = "warn" elided_lifetimes_in_paths = "warn" explicit_outlives_requirements = "warn" let_underscore_drop = "warn" macro_use_extern_crate = "warn" # TODO missing_docs = "warn" trivial_casts = "warn" trivial_numeric_casts = "warn" unsafe_code = "warn" unused_import_braces = "warn" unused_lifetimes = "warn" unused_macro_rules = "warn" unused_qualifications = "warn" [workspace.lints.clippy] allow_attributes = "warn" allow_attributes_without_reason = "warn" as_underscore = "warn" assertions_on_result_states = "warn" assigning_clones = "warn" bool_to_int_with_if = "warn" borrow_as_ptr = "warn" case_sensitive_file_extension_comparisons = "warn" cast_lossless = "warn" cast_possible_truncation = "warn" cast_possible_wrap = "warn" cast_precision_loss = "warn" cast_ptr_alignment = "warn" cast_sign_loss = "warn" cfg_not_test = "warn" checked_conversions = "warn" clone_on_ref_ptr = "warn" cloned_instead_of_copied = "warn" copy_iterator = "warn" create_dir = "warn" dbg_macro = "warn" decimal_literal_representation = "warn" default_trait_access = "warn" default_union_representation = "warn" deref_by_slicing = "warn" disallowed_script_idents = "warn" doc_link_with_quotes = "warn" empty_drop = "warn" empty_enum = "warn" empty_enum_variants_with_brackets = "warn" empty_structs_with_brackets = "warn" enum_glob_use = "warn" error_impl_error = "warn" exit = "warn" expect_used = "warn" expl_impl_clone_on_copy = "warn" explicit_deref_methods = "warn" explicit_into_iter_loop = "warn" explicit_iter_loop = "warn" field_scoped_visibility_modifiers = "warn" filetype_is_file = "warn" filter_map_next = "warn" flat_map_option = "warn" fn_params_excessive_bools = "warn" fn_to_numeric_cast_any = "warn" format_push_string = "warn" from_iter_instead_of_collect = "warn" get_unwrap = "warn" host_endian_bytes = "warn" if_not_else = "warn" if_then_some_else_none = "warn" ignored_unit_patterns = "warn" implicit_clone = "warn" implicit_hasher = "warn" inconsistent_struct_constructor = "warn" index_refutable_slice = "warn" inefficient_to_string = "warn" infinite_loop = "warn" inline_always = "warn" inline_asm_x86_att_syntax = "warn" inline_asm_x86_intel_syntax = "warn" into_iter_without_iter = "warn" invalid_upcast_comparisons = "warn" items_after_statements = "warn" iter_filter_is_ok = "warn" iter_filter_is_some = "warn" iter_not_returning_iterator = "warn" iter_without_into_iter = "warn" large_digit_groups = "warn" large_futures = "warn" large_include_file = "warn" large_stack_arrays = "warn" large_types_passed_by_value = "warn" let_underscore_must_use = "warn" let_underscore_untyped = "warn" linkedlist = "warn" lossy_float_literal = "warn" macro_use_imports = "warn" manual_assert = "warn" manual_instant_elapsed = "warn" manual_is_variant_and = "warn" manual_let_else = "warn" manual_ok_or = "warn" manual_string_new = "warn" many_single_char_names = "warn" map_unwrap_or = "warn" match_bool = "warn" match_on_vec_items = "warn" match_same_arms = "warn" match_wild_err_arm = "warn" match_wildcard_for_single_variants = "warn" maybe_infinite_iter = "warn" mem_forget = "warn" mismatching_type_param_order = "warn" missing_assert_message = "warn" missing_asserts_for_indexing = "warn" missing_fields_in_debug = "warn" multiple_inherent_impl = "warn" mut_mut = "warn" mutex_atomic = "warn" naive_bytecount = "warn" needless_bitwise_bool = "warn" needless_continue = "warn" needless_for_each = "warn" needless_pass_by_value = "warn" needless_raw_string_hashes = "warn" needless_raw_strings = "warn" negative_feature_names = "warn" no_effect_underscore_binding = "warn" no_mangle_with_rust_abi = "warn" non_ascii_literal = "warn" option_as_ref_cloned = "warn" panic = "warn" panic_in_result_fn = "warn" partial_pub_fields = "warn" pathbuf_init_then_push = "warn" print_stderr = "warn" print_stdout = "warn" ptr_as_ptr = "warn" ptr_cast_constness = "warn" pub_underscore_fields = "warn" pub_without_shorthand = "warn" range_minus_one = "warn" range_plus_one = "warn" rc_buffer = "warn" rc_mutex = "warn" redundant_closure_for_method_calls = "warn" redundant_else = "warn" redundant_feature_names = "warn" redundant_type_annotations = "warn" ref_as_ptr = "warn" ref_binding_to_reference = "warn" ref_option_ref = "warn" ref_patterns = "warn" renamed_function_params = "warn" rest_pat_in_fully_bound_structs = "warn" return_self_not_must_use = "warn" same_functions_in_if_condition = "warn" same_name_method = "warn" semicolon_inside_block = "warn" shadow_same = "warn" should_panic_without_expect = "warn" single_char_pattern = "warn" single_match_else = "warn" stable_sort_primitive = "warn" str_split_at_newline = "warn" str_to_string = "warn" string_add = "warn" string_add_assign = "warn" string_lit_chars_any = "warn" string_to_string = "warn" struct_excessive_bools = "warn" struct_field_names = "warn" suspicious_xor_used_as_pow = "warn" tests_outside_test_module = "warn" todo = "warn" transmute_ptr_to_ptr = "warn" trivially_copy_pass_by_ref = "warn" try_err = "warn" unchecked_duration_subtraction = "warn" undocumented_unsafe_blocks = "warn" unicode_not_nfc = "warn" unimplemented = "warn" uninlined_format_args = "warn" unnecessary_box_returns = "warn" unnecessary_join = "warn" unnecessary_safety_comment = "warn" unnecessary_safety_doc = "warn" unnecessary_self_imports = "warn" unnecessary_wraps = "warn" unneeded_field_pattern = "warn" unnested_or_patterns = "warn" unreadable_literal = "warn" unsafe_derive_deserialize = "warn" unseparated_literal_suffix = "warn" unused_async = "warn" unused_result_ok = "warn" unused_self = "warn" unwrap_in_result = "warn" use_debug = "warn" used_underscore_binding = "warn" verbose_bit_mask = "warn" verbose_file_reads = "warn" wildcard_dependencies = "warn" zero_sized_map_values = "warn" [profile.release] lto = true codegen-units = 1 strip = "debuginfo" panic = "abort" [profile.release.package.oxigraph-js] opt-level = "z" oxigraph-oxigraph-68d45f7/LICENSE-APACHE000066400000000000000000000251371476336702400174730ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. oxigraph-oxigraph-68d45f7/LICENSE-MIT000066400000000000000000000020471476336702400171760ustar00rootroot00000000000000Copyright (c) 2018 Oxigraph developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. oxigraph-oxigraph-68d45f7/README.md000066400000000000000000000163331476336702400170240ustar00rootroot00000000000000# Oxigraph [![Latest Version](https://img.shields.io/crates/v/oxigraph.svg)](https://crates.io/crates/oxigraph) [![Released API docs](https://docs.rs/oxigraph/badge.svg)](https://docs.rs/oxigraph) [![PyPI](https://img.shields.io/pypi/v/pyoxigraph)](https://pypi.org/project/pyoxigraph/) [![npm](https://img.shields.io/npm/v/oxigraph)](https://www.npmjs.com/package/oxigraph) [![tests status](https://github.com/oxigraph/oxigraph/actions/workflows/tests.yml/badge.svg)](https://github.com/oxigraph/oxigraph/actions) [![artifacts status](https://github.com/oxigraph/oxigraph/actions/workflows/artifacts.yml/badge.svg)](https://github.com/oxigraph/oxigraph/actions) [![dependency status](https://deps.rs/repo/github/oxigraph/oxigraph/status.svg)](https://deps.rs/repo/github/oxigraph/oxigraph) [![Gitter](https://badges.gitter.im/oxigraph/community.svg)](https://gitter.im/oxigraph/community) [![Twitter URL](https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Ftwitter.com%2Foxigraph)](https://twitter.com/oxigraph) Oxigraph is a graph database implementing the [SPARQL](https://www.w3.org/TR/sparql11-overview/) standard. Its goal is to provide a compliant, safe, and fast graph database based on the [RocksDB](https://rocksdb.org/) key-value store. It is written in Rust. It also provides a set of utility functions for reading, writing, and processing RDF files. Oxigraph is in heavy development and SPARQL query evaluation has not been optimized yet. The development roadmap is using [GitHub milestones](https://github.com/oxigraph/oxigraph/milestones?direction=desc&sort=completeness&state=open). Oxigraph internal design [is described on the wiki](https://github.com/oxigraph/oxigraph/wiki/Architecture). 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/). It is split into multiple parts: - [The database written as a Rust library](./lib/oxigraph). Its source code is in the `lib` directory. [![Latest Version](https://img.shields.io/crates/v/oxigraph.svg)](https://crates.io/crates/oxigraph) [![Released API docs](https://docs.rs/oxigraph/badge.svg)](https://docs.rs/oxigraph) - [`pyoxigraph` that exposes Oxigraph to the Python world](./python). Its source code is in the `python` directory. [![PyPI](https://img.shields.io/pypi/v/pyoxigraph)](https://pypi.org/project/pyoxigraph/) - [JavaScript bindings for Oxigraph](./js). WebAssembly is used to package Oxigraph into a NodeJS compatible NPM package. Its source code is in the `js` directory. [![npm](https://img.shields.io/npm/v/oxigraph)](https://www.npmjs.com/package/oxigraph) - [Oxigraph binary](./cli) that provides a standalone command-line tool allowing to manipulate RDF data and spawn a a web server implementing the [SPARQL 1.1 Protocol](https://www.w3.org/TR/sparql11-protocol/) and the [SPARQL 1.1 Graph Store Protocol](https://www.w3.org/TR/sparql11-http-rdf-update/). Its source code and instructions (including Docker) are in the `cli` directory. Note that it was previously named [Oxigraph server](https://crates.io/crates/oxigraph-server). [![Latest Version](https://img.shields.io/crates/v/oxigraph-cli.svg)](https://crates.io/crates/oxigraph-cli) Also, some parts of Oxigraph are available as standalone Rust crates: * [`oxrdf`](./lib/oxrdf), datastructures encoding RDF basic concepts (the [`oxigraph::model`](crate::model) module). * [`oxrdfio`](./lib/oxrdfio), a unified parser and serializer API for RDF formats (the [`oxigraph::io`](crate::io) module). It itself relies on: * [`oxttl`](./lib/oxttl), N-Triple, N-Quad, Turtle, TriG and N3 parsing and serialization. * [`oxrdfxml`](./lib/oxrdfxml), RDF/XML parsing and serialization. * [`spareval`](./lib/spareval), a SPARQL evaluator. * [`spargebra`](./lib/spargebra), a SPARQL parser. * [`sparesults`](./lib/sparesults), parsers and serializers for SPARQL result formats. * [`sparopt`](./lib/sparopt), a SPARQL optimizer. * [`oxsdatatypes`](./lib/oxsdatatypes), an implementation of some XML Schema datatypes. The library layers in Oxigraph. The elements above depend on the elements below: ![Oxigraph libraries architecture diagram](./docs/arch-diagram.svg) A preliminary benchmark [is provided](bench/README.md). There is also [a document describing Oxigraph technical architecture](https://github.com/oxigraph/oxigraph/wiki/Architecture). When cloning this codebase, don't forget to clone the submodules using `git clone --recursive https://github.com/oxigraph/oxigraph.git` to clone the repository including submodules or `git submodule update --init` to add the submodules to the already cloned repository. ## 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. ## Sponsors * [Zazuko](https://zazuko.com/), a knowledge graph consulting company. * [RelationLabs](https://relationlabs.ai/) that is building [Relation-Graph](https://github.com/relationlabs/Relation-Graph), a SPARQL database module for the [Substrate blockchain platform](https://substrate.io/) based on Oxigraph. * [Field 33](https://field33.com) that was building [an ontology management platform](https://plow.pm/). * [Magnus Bakken](https://github.com/magbak) who is building [Data Treehouse](https://www.data-treehouse.com/), a time-series + RDF datalake platform, and [chrontext](https://github.com/magbak/chrontext), a SPARQL query endpoint on top of joint RDF and time series databases. * [DeciSym.AI](https://www.decisym.ai/) a cyber security consulting company providing RDF-based software. * [ACE IoT Solutions](https://aceiotsolutions.com/), a building IOT platform. * [Albin Larsson](https://byabbe.se/) who is building [GovDirectory](https://www.govdirectory.org/), a directory of public agencies based on Wikidata. And [others](https://github.com/sponsors/Tpt). Many thanks to them! oxigraph-oxigraph-68d45f7/bench/000077500000000000000000000000001476336702400166165ustar00rootroot00000000000000oxigraph-oxigraph-68d45f7/bench/README.md000066400000000000000000000057731476336702400201110ustar00rootroot00000000000000BSBM ==== The [Berlin SPARQL Benchmark (BSBM)](http://wifo5-03.informatik.uni-mannheim.de/bizer/berlinsparqlbenchmark/) is a simple SPARQL benchmark. It provides a dataset generator and multiple sets of queries grouped by "use cases". ## Results We compare here Oxigraph 0.2 and 0.3 with some existing SPARQL implementations (Blazegraph, GraphDB, Jena, and Virtuoso). The dataset used in the following charts is generated with 100k "products" (see [its spec](http://wifo5-03.informatik.uni-mannheim.de/bizer/berlinsparqlbenchmark/spec/Dataset/index.html)). It leads to the creation of 35M triples. It has been executed on a PrevailPro P3000 with 32GB of RAM. The tests have been executed with a concurrency factor of 16 (i.e. at most 16 queries are sent at the same time to the server). Beware, the graph *y* axis is in log scale to properly display on the same graph systems with very different speed behaviors. ### Explore The [explore use case](http://wifo5-03.informatik.uni-mannheim.de/bizer/berlinsparqlbenchmark/spec/ExploreUseCase/index.html) is composed of 11 queries that do simple data retrieval. Query 6 existed in previous versions of the benchmark but is now removed. ![explore use case results](bsbm.explore.svg) ### Explore and update The [explore and update use case](http://wifo5-03.informatik.uni-mannheim.de/bizer/berlinsparqlbenchmark/spec/index.html#usecase_explore_and_update) is composed of the 2 operations of the [update use case](http://wifo5-03.informatik.uni-mannheim.de/bizer/berlinsparqlbenchmark/spec/UpdateUseCase/index.html) (`INSERT DATA` and `DELETE WHERE`) and the 11 queries of the [explore use case](http://wifo5-03.informatik.uni-mannheim.de/bizer/berlinsparqlbenchmark/spec/ExploreUseCase/index.html). The first two elements (1 and 2) are the 2 updates and the others (3 to 14) are the 11 queries. ![explore use case results](bsbm.exploreAndUpdate.svg) ## How to reproduce the benchmark The code of the benchmark is in the `bsbm-tools` submodule. You should pull it with a `git submodule update` before running the benchmark. To run the benchmark for Oxigraph run `bash bsbm_oxigraph.sh`. It will compile the current Oxigraph code and run the benchmark against it. You can tweak the number of products in the dataset and the number of concurrent requests using the environment variables at the beginning of `bsbm_oxigraph.sh`. To generate the plots run `python3 bsbsm-plot.py`. Scripts are also provided for the other systems (`bsbm_blazegraph.sh`, `bsbm_graphdb.sh`, `bsbm_jena.sh` and `bsbm_virtuoso.sh`). oxigraph-oxigraph-68d45f7/bench/bsbm-plot.py000066400000000000000000000037251476336702400210760ustar00rootroot00000000000000import matplotlib.pyplot as plt import xml.etree.ElementTree as ET from collections import defaultdict from glob import glob from numpy import array def plot_y_per_x_per_plot(data, xlabel, ylabel, file, log=False): plt.figure(file) bar_width = 1 / (len(data) + 1) for i, (label, xys) in enumerate(sorted(data.items())): plt.bar(array(list(xys.keys())) + bar_width * (i + 1 - len(data) / 2), array(list(xys.values())), bar_width, label=label) plt.legend() plt.xlabel(xlabel) plt.ylabel(ylabel) plt.yscale('log') if log: plt.yscale('log') plt.savefig(file) def plot_usecase(name: str): aqet = defaultdict(dict) avgresults_by_query = defaultdict(lambda: defaultdict(dict)) for file in glob('bsbm.{}.*.xml'.format(name)): parts = file.split('.') run = '.'.join(parts[2:-1]) for query in ET.parse(file).getroot().find('queries').findall('query'): query_id = int(query.attrib['nr']) for child in query.iter(): if child.tag == "aqet": val = float(query.find('aqet').text) if val > 0: aqet[run][query_id] = val elif child.tag == "avgresults": avgresults_by_query[query_id][int(parts[-3])][run] = float(query.find('avgresults').text) plot_y_per_x_per_plot(aqet, 'query id', 'execution time (s)', 'bsbm.{}.svg'.format(name)) # we check if avgresults seems consistent for query, t in avgresults_by_query.items(): for size, value_by_run in t.items(): avg = sum(value_by_run.values()) / len(value_by_run) if not all(abs(v - avg) < 1 for v in value_by_run.values()): print( f'Strange value for average results for usecase {name} of size {size} and query {query}: {value_by_run}') plot_usecase('explore') plot_usecase('exploreAndUpdate') plot_usecase('businessIntelligence') plt.show() oxigraph-oxigraph-68d45f7/bench/bsbm-tools/000077500000000000000000000000001476336702400206775ustar00rootroot00000000000000oxigraph-oxigraph-68d45f7/bench/bsbm.explore.svg000066400000000000000000001573451476336702400217560ustar00rootroot00000000000000 2022-01-29T14:06:50.368417 image/svg+xml Matplotlib v3.5.1, https://matplotlib.org/ oxigraph-oxigraph-68d45f7/bench/bsbm.exploreAndUpdate.svg000066400000000000000000001541131476336702400235320ustar00rootroot00000000000000 2022-01-29T14:06:51.041065 image/svg+xml Matplotlib v3.5.1, https://matplotlib.org/ oxigraph-oxigraph-68d45f7/bench/bsbm_blazegraph.sh000077500000000000000000000024141476336702400223000ustar00rootroot00000000000000#!/usr/bin/env bash DATASET_SIZE=100000 PARALLELISM=16 set -eu wget -nc https://github.com/blazegraph/database/releases/download/BLAZEGRAPH_RELEASE_2_1_5/blazegraph.jar cd bsbm-tools || exit ./generate -fc -pc ${DATASET_SIZE} -s nt -fn "explore-${DATASET_SIZE}" -ud -ufn "explore-update-${DATASET_SIZE}" /usr/lib/jvm/java-8-openjdk/bin/java -server -jar ../blazegraph.jar & sleep 10 curl -f -X POST -H 'Content-Type:text/turtle' -T "explore-${DATASET_SIZE}.nt" http://localhost:9999/blazegraph/sparql ./testdriver -mt ${PARALLELISM} -ucf usecases/explore/sparql.txt -o "../bsbm.explore.blazegraph.2.1.5.${DATASET_SIZE}.${PARALLELISM}.xml" http://localhost:9999/blazegraph/sparql ./testdriver -mt ${PARALLELISM} -ucf usecases/exploreAndUpdate/sparql.txt -o "../bsbm.exploreAndUpdate.blazegraph.2.1.5.${DATASET_SIZE}.${PARALLELISM}.xml" http://localhost:9999/blazegraph/sparql -u http://localhost:9999/blazegraph/sparql -udataset "explore-update-${DATASET_SIZE}.nt" #./testdriver -mt ${PARALLELISM} -ucf usecases/businessIntelligence/sparql.txt -o "../bsbm.businessIntelligence.blazegraph.2.1.5.${DATASET_SIZE}.${PARALLELISM}.xml" http://localhost:9999/blazegraph/sparql kill $! rm -f blazegraph.jnl rm -f "explore-${DATASET_SIZE}.nt" rm -f "explore-update-${DATASET_SIZE}.nt" rm -rf td_data oxigraph-oxigraph-68d45f7/bench/bsbm_graphdb.sh000077500000000000000000000040371476336702400215730ustar00rootroot00000000000000#!/usr/bin/env bash DATASET_SIZE=100000 PARALLELISM=16 VERSION="9.3.3" set -eu cd bsbm-tools ./generate -fc -pc ${DATASET_SIZE} -s nt -fn "explore-${DATASET_SIZE}" -ud -ufn "explore-update-${DATASET_SIZE}" ../graphdb-free-9.3.3/bin/graphdb -s -Dgraphdb.logger.root.level=WARN & sleep 10 curl -f -X POST http://localhost:7200/rest/repositories -H 'Content-Type:application/json' -d ' {"id":"bsbm","params":{"ruleset":{"label":"Ruleset","name":"ruleset","value":"empty"},"title":{"label":"Repository title","name":"title","value":"GraphDB Free repository"},"checkForInconsistencies":{"label":"Check for inconsistencies","name":"checkForInconsistencies","value":"false"},"disableSameAs":{"label":"Disable owl:sameAs","name":"disableSameAs","value":"true"},"baseURL":{"label":"Base URL","name":"baseURL","value":"http://example.org/owlim#"},"repositoryType":{"label":"Repository type","name":"repositoryType","value":"file-repository"},"id":{"label":"Repository ID","name":"id","value":"repo-bsbm"},"storageFolder":{"label":"Storage folder","name":"storageFolder","value":"storage"}},"title":"BSBM","type":"free"} ' curl -f -X PUT -H 'Content-Type:application/n-triples' -T "explore-${DATASET_SIZE}.nt" http://localhost:7200/repositories/bsbm/statements ./testdriver -mt ${PARALLELISM} -ucf usecases/explore/sparql.txt -o "../bsbm.explore.graphdb.${VERSION}.${DATASET_SIZE}.${PARALLELISM}.xml" http://localhost:7200/repositories/bsbm ./testdriver -mt ${PARALLELISM} -ucf usecases/exploreAndUpdate/sparql.txt -o "../bsbm.exploreAndUpdate.graphdb.${VERSION}.${DATASET_SIZE}.${PARALLELISM}.xml" http://localhost:7200/repositories/bsbm -u http://localhost:7200/repositories/bsbm/statements -udataset "explore-update-${DATASET_SIZE}.nt" #./testdriver -mt ${PARALLELISM} -ucf usecases/businessIntelligence/sparql.txt -o "../bsbm.businessIntelligence.graphdb.${VERSION}.${DATASET_SIZE}.${PARALLELISM}.xml" http://localhost:7200/repositories/bsbm kill $! sleep 5 rm -rf ../graphdb-free-9.3.3/data rm -f "explore-${DATASET_SIZE}.nt" rm -f "explore-update-${DATASET_SIZE}.nt" rm -rf td_data oxigraph-oxigraph-68d45f7/bench/bsbm_jena.sh000077500000000000000000000032151476336702400210760ustar00rootroot00000000000000#!/usr/bin/env bash DATASET_SIZE=100000 # number of products in the dataset. There is around 350 triples generated by product. PARALLELISM=16 VERSION="4.3.2" set -eu wget -nc https://downloads.apache.org/jena/binaries/apache-jena-${VERSION}.zip cd bsbm-tools || exit ./generate -fc -pc ${DATASET_SIZE} -s nt -fn "explore-${DATASET_SIZE}" -ud -ufn "explore-update-${DATASET_SIZE}" unzip ../"apache-jena-${VERSION}.zip" ./apache-jena-${VERSION}/bin/tdb2.tdbloader --loader=parallel --loc=td_data "explore-${DATASET_SIZE}.nt" wget https://downloads.apache.org/jena/binaries/apache-jena-fuseki-${VERSION}.zip unzip apache-jena-fuseki-${VERSION}.zip rm apache-jena-fuseki-${VERSION}.zip echo "rootLogger.level = ERROR" > ./apache-jena-fuseki-${VERSION}/log4j2.properties ./apache-jena-fuseki-${VERSION}/fuseki-server --tdb2 --loc=td_data --update /bsbm & sleep 10 ./testdriver -mt ${PARALLELISM} -ucf usecases/explore/sparql.txt -o "../bsbm.explore.jena.${VERSION}.${DATASET_SIZE}.${PARALLELISM}.xml" http://localhost:3030/bsbm/query ./testdriver -mt ${PARALLELISM} -ucf usecases/exploreAndUpdate/sparql.txt -o "../bsbm.exploreAndUpdate.jena.${VERSION}.${DATASET_SIZE}.${PARALLELISM}.xml" http://localhost:3030/bsbm/query -u http://localhost:3030/bsbm/update -udataset "explore-update-${DATASET_SIZE}.nt" #./testdriver -mt ${PARALLELISM} -ucf usecases/businessIntelligence/sparql.txt -o "../bsbm.businessIntelligence.jena.${VERSION}.${DATASET_SIZE}.${PARALLELISM}.xml" http://localhost:3030/bsbm/query kill $! rm -f "explore-${DATASET_SIZE}.nt" rm -f "explore-update-${DATASET_SIZE}.nt" rm -rf td_data rm -rf run rm -rf apache-jena-${VERSION} rm -rf apache-jena-fuseki-${VERSION} oxigraph-oxigraph-68d45f7/bench/bsbm_oxigraph.sh000077500000000000000000000025521476336702400220050ustar00rootroot00000000000000#!/usr/bin/env bash DATASET_SIZE=100000 # number of products in the dataset. There is around 350 triples generated by product. PARALLELISM=16 set -eu cd bsbm-tools ./generate -fc -pc ${DATASET_SIZE} -s nt -fn "explore-${DATASET_SIZE}" -ud -ufn "explore-update-${DATASET_SIZE}" cargo build --release --manifest-path="../../cli/Cargo.toml" VERSION=$(./../../target/release/oxigraph --version | sed 's/oxigraph //g') ./../../target/release/oxigraph serve --location oxigraph_data --bind 127.0.0.1:7878 & sleep 1 curl -X POST -T "explore-${DATASET_SIZE}.nt" -H "content-type: application/n-triples" "http://127.0.0.1:7878/store?no_transaction" ./testdriver -mt ${PARALLELISM} -ucf usecases/explore/sparql.txt -o "../bsbm.explore.oxigraph.${VERSION}.${DATASET_SIZE}.${PARALLELISM}.xml" http://127.0.0.1:7878/query ./testdriver -mt ${PARALLELISM} -ucf usecases/exploreAndUpdate/sparql.txt -o "../bsbm.exploreAndUpdate.oxigraph.${VERSION}.${DATASET_SIZE}.${PARALLELISM}.xml" http://127.0.0.1:7878/query -u http://127.0.0.1:7878/update -udataset "explore-update-${DATASET_SIZE}.nt" #./testdriver -mt ${PARALLELISM} -ucf usecases/businessIntelligence/sparql.txt -o "../bsbm.businessIntelligence.${VERSION}.${DATASET_SIZE}.${PARALLELISM}.xml" "http://127.0.0.1:7878/query" kill $! rm -rf oxigraph_data rm -f "explore-${DATASET_SIZE}.nt" rm -f "explore-update-${DATASET_SIZE}.nt" rm -rf td_data oxigraph-oxigraph-68d45f7/bench/bsbm_rdf4j.sh000077500000000000000000000052231476336702400211730ustar00rootroot00000000000000#!/usr/bin/env bash DATASET_SIZE=100000 PARALLELISM=16 VERSION="4.2.2" TOMCAT_VERSION="9.0.71" set -eu wget -nc -O "rdf4j-${VERSION}.zip" "https://www.eclipse.org/downloads/download.php?file=/rdf4j/eclipse-rdf4j-${VERSION}-sdk.zip&mirror_id=1" wget -nc -O "tomcat-${TOMCAT_VERSION}.zip" "https://dlcdn.apache.org/tomcat/tomcat-9/v${TOMCAT_VERSION}/bin/apache-tomcat-${TOMCAT_VERSION}.zip" cd bsbm-tools || exit ./generate -fc -pc ${DATASET_SIZE} -s nt -fn "explore-${DATASET_SIZE}" -ud -ufn "explore-update-${DATASET_SIZE}" wget -nc -O "rdf4j-${VERSION}.zip" "https://www.eclipse.org/downloads/download.php?file=/rdf4j/eclipse-rdf4j-${VERSION}-sdk.zip&mirror_id=1" unzip ../"rdf4j-${VERSION}.zip" unzip ../"tomcat-${TOMCAT_VERSION}.zip" CATALINA_HOME="$(pwd)/apache-tomcat-${TOMCAT_VERSION}" export CATALINA_HOME export JAVA_OPTS="-Dorg.eclipse.rdf4j.appdata.basedir=${CATALINA_HOME}/rdf4j" cp "eclipse-rdf4j-${VERSION}"/war/rdf4j-server.war "${CATALINA_HOME}"/webapps/ chmod +x "${CATALINA_HOME}"/bin/*.sh "${CATALINA_HOME}"/bin/startup.sh sleep 30 curl -f -X PUT http://localhost:8080/rdf4j-server/repositories/bsbm -H 'Content-Type:text/turtle' -d ' @prefix rdfs: . @prefix rep: . @prefix sr: . @prefix sail: . [] a rep:Repository ; rep:repositoryID "bsbm" ; rdfs:label "BSBM" ; rep:repositoryImpl [ rep:repositoryType "openrdf:SailRepository" ; sr:sailImpl [ sail:sailType "rdf4j:LmdbStore" ] ] . ' sleep 10 curl -f -X PUT -H 'Content-Type:application/n-triples' -T "explore-${DATASET_SIZE}.nt" http://localhost:8080/rdf4j-server/repositories/bsbm/statements ./testdriver -mt ${PARALLELISM} -ucf usecases/explore/sparql.txt -o "../bsbm.explore.rdf4j-lmdb.${VERSION}.${DATASET_SIZE}.${PARALLELISM}.xml" http://localhost:8080/rdf4j-server/repositories/bsbm ./testdriver -mt ${PARALLELISM} -ucf usecases/exploreAndUpdate/sparql.txt -o "../bsbm.exploreAndUpdate.rdf4j-lmdb.${VERSION}.${DATASET_SIZE}.${PARALLELISM}.xml" http://localhost:8080/rdf4j-server/repositories/bsbm -u http://localhost:8080/rdf4j-server/repositories/bsbm/statements -udataset "explore-update-${DATASET_SIZE}.nt" #./testdriver -mt ${PARALLELISM} -ucf usecases/businessIntelligence/sparql.txt -o "../bsbm.businessIntelligence.rdf4j-lmdb.${VERSION}.${DATASET_SIZE}.${PARALLELISM}.xml" http://localhost:8080/rdf4j-server/repositories/bsbm "${CATALINA_HOME}"/bin/shutdown.sh rm -f "explore-${DATASET_SIZE}.nt" rm -f "explore-update-${DATASET_SIZE}.nt" rm -rf td_data rm -rf "eclipse-rdf4j-${VERSION}" rm -rf "apache-tomcat-${TOMCAT_VERSION}" oxigraph-oxigraph-68d45f7/bench/bsbm_virtuoso.sh000077500000000000000000000027441476336702400220610ustar00rootroot00000000000000#!/usr/bin/env bash DATASET_SIZE=100000 # number of products in the dataset. There is around 350 triples generated by product. PARALLELISM=16 VERSION="7.2.5" set -eu cd bsbm-tools ./generate -fc -pc ${DATASET_SIZE} -s nt -fn "explore-${DATASET_SIZE}" -ud -ufn "explore-update-${DATASET_SIZE}" cp ../virtuoso-opensource/database/virtuoso.ini.sample virtuoso.ini mkdir ../database ../virtuoso-opensource/bin/virtuoso-t -f & sleep 10 ../virtuoso-opensource/bin/isql 1111 dba dba <; ld_dir('$(realpath .)', 'explore-${DATASET_SIZE}.nt', 'urn:graph:test'); rdf_loader_run(); EOF ./testdriver -mt ${PARALLELISM} -ucf usecases/explore/sparql.txt -o "../bsbm.explore.virtuoso.${VERSION}.${DATASET_SIZE}.${PARALLELISM}.xml" 'http://localhost:8890/sparql?graph-uri=urn:graph:test' # ./testdriver -mt ${PARALLELISM} -ucf usecases/exploreAndUpdate/sparql.txt -o "../bsbm.exploreAndUpdate.virtuoso.${DATASET_SIZE}.${PARALLELISM}.${PARALLELISM}.${VERSION}.xml" 'http://localhost:8890/sparql?graph-uri=urn:graph:test' -u 'http://dba:dba@localhost:8890/sparql-auth?graph-uri=urn:graph:test' -udataset "explore-update-${DATASET_SIZE}.nt" # ./testdriver -mt ${PARALLELISM} -ucf usecases/businessIntelligence/sparql.txt -o "../bsbm.businessIntelligence.virtuoso.${VERSION}.${DATASET_SIZE}.${PARALLELISM}.xml" 'http://localhost:8890/sparql?graph-uri=urn:graph:test' kill $! rm -rf ../database rm -f "explore-${DATASET_SIZE}.nt" rm -f "explore-update-${DATASET_SIZE}.nt" rm -rf td_data oxigraph-oxigraph-68d45f7/bench/explanation_to_flamegraph.py000066400000000000000000000045411476336702400244060ustar00rootroot00000000000000""" Converts a SPARQL query JSON explanation file to a flamegraph. Usage: python explanation_to_flamegraph.py explanation.json flamegraph.svg """ import json import subprocess from argparse import ArgumentParser from pathlib import Path from shutil import which from tempfile import NamedTemporaryFile parser = ArgumentParser( prog='OxigraphFlamegraph', description='Builds a flamegraph from the Oxigraph query explanation JSON format', epilog='Text at the bottom of help') parser.add_argument('json_explanation', type=Path) parser.add_argument('flamegraph_svg', type=Path) args = parser.parse_args() def trace_line(label: str, value: float): return f"{label} {int(value * 1_000_000)}" with args.json_explanation.open('rt') as fp: explanation = json.load(fp) trace = [] if "parsing duration in seconds" in explanation: trace.append(trace_line("parsing", explanation['parsing duration in seconds'])) if "planning duration in seconds" in explanation: trace.append(trace_line("planning", explanation['planning duration in seconds'])) already_used_names = {} def add_to_trace(node, path): path = f"{path};{node['name'].replace(' ', '`')}" if path in already_used_names: already_used_names[path] += 1 path = f"{path}`{already_used_names[path]}" else: already_used_names[path] = 0 samples = node['duration in seconds'] - sum(child['duration in seconds'] for child in node.get("children", ())) if int(samples * 1_000_000) > 0: trace.append(trace_line(path, samples)) for i, child in enumerate(node.get("children", ())): add_to_trace(child, path) add_to_trace(explanation["plan"], 'eval') inferno = which('inferno-flamegraph') flamegraph_pl = which('flamegraph.pl') if inferno: args.flamegraph_svg.write_text( subprocess.run([inferno], input='\n'.join(trace), stdout=subprocess.PIPE, text=True).stdout) elif flamegraph_pl: with NamedTemporaryFile('w+t') as fp: fp.write('\n'.join(trace)) fp.flush() args.flamegraph_svg.write_text( subprocess.run([flamegraph_pl, fp.name], stdout=subprocess.PIPE, text=True).stdout) else: raise Exception( 'This script requires either the inferno-flamegraph from https://github.com/jonhoo/inferno either the flamegraph.pl script from https://github.com/brendangregg/FlameGraph to be installed and be in $PATH.') oxigraph-oxigraph-68d45f7/bench/explanation_to_trace.py000066400000000000000000000033511476336702400233740ustar00rootroot00000000000000""" Converts a SPARQL query JSON explanation file to a tracing event file compatible with Chrome. Usage: python explanation_to_trace.py explanation.json trace.json """ import json from argparse import ArgumentParser from pathlib import Path parser = ArgumentParser( prog='OxigraphTracing', description='Builds a Trace Event Format file from the Oxigraph query explanation JSON format') parser.add_argument('json_explanation', type=Path) parser.add_argument('json_trace_event', type=Path) args = parser.parse_args() with args.json_explanation.open('rt') as fp: explanation = json.load(fp) trace = [] def trace_element(name: str, cat: str, start_s: float, duration_s: float): return { "name": name, "cat": cat, "ph": "X", "ts": int(start_s * 1_000_000), "dur": int(duration_s * 1_000_000), "pid": 1 } def add_to_trace(node, path, start_time: float): path = f"{path};{node['name'].replace(' ', '`')}" trace.append(trace_element(node["name"], node["name"].split("(")[0], start_time, node["duration in seconds"])) for child in node.get("children", ()): add_to_trace(child, path, start_time) start_time += child["duration in seconds"] current_time = 0 if "parsing duration in seconds" in explanation: d = explanation["parsing duration in seconds"] trace.append(trace_element(f"parsing", "parsing", current_time, d)) current_time += d if "planning duration in seconds" in explanation: d = explanation["planning duration in seconds"] trace.append(trace_element(f"planning", "planning", current_time, d)) current_time += d add_to_trace(explanation["plan"], 'eval', current_time) with args.json_trace_event.open("wt") as fp: json.dump(trace, fp) oxigraph-oxigraph-68d45f7/cli/000077500000000000000000000000001476336702400163065ustar00rootroot00000000000000oxigraph-oxigraph-68d45f7/cli/Cargo.toml000066400000000000000000000026021476336702400202360ustar00rootroot00000000000000[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", "geosparql"] native-tls = ["oxigraph/http-client-native-tls"] rocksdb-pkg-config = ["oxigraph/rocksdb-pkg-config"] rustls-native = ["oxigraph/http-client-rustls-native"] rustls-webpki = ["oxigraph/http-client-rustls-webpki"] geosparql = ["dep:spargeo"] [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 spargeo = { workspace = true, optional = true } url.workspace = true [dev-dependencies] assert_cmd.workspace = true assert_fs.workspace = true predicates.workspace = true [build-dependencies] clap = { workspace = true, features = ["derive"] } clap_complete.workspace = true clap_mangen.workspace = true [lints] workspace = true oxigraph-oxigraph-68d45f7/cli/Dockerfile000066400000000000000000000023011476336702400202740ustar00rootroot00000000000000FROM --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,geosparql && \ mv /oxigraph/target/aarch64-unknown-linux-gnu/release/oxigraph /oxigraph/target/release/oxigraph ; \ else \ cargo build --release --no-default-features --features rustls-webpki,geosparql ; \ fi FROM 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" ] oxigraph-oxigraph-68d45f7/cli/README.md000066400000000000000000000327771476336702400176050ustar00rootroot00000000000000Oxigraph 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 { ?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 { ?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://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 ``` ## Man pages and autocompletion Autocompletion for various shells are generated on build in the `target/{debug,release}/build/oxigraph-cli-/out/complete` directory. Similarly, man pages are generated in the `target/{debug,release}/build/oxigraph-cli-/out/man` directory. ## 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. oxigraph-oxigraph-68d45f7/cli/build.rs000066400000000000000000000015001476336702400177470ustar00rootroot00000000000000include!("src/cli.rs"); use clap::{CommandFactory, ValueEnum}; use clap_complete::Shell; use clap_mangen::Man; use std::env::var_os; use std::fs::create_dir_all; use std::io::Result; fn main() -> Result<()> { let out_dir = PathBuf::from(var_os("OUT_DIR").unwrap()); let mut app = Args::command(); let complete_dir = out_dir.join("complete"); create_dir_all(&complete_dir)?; for shell in Shell::value_variants() { clap_complete::generate_to(*shell, &mut app, "oxigraph", &complete_dir)?; } let man_dir = out_dir.join("man"); create_dir_all(&man_dir)?; Man::new(app.clone().disable_help_subcommand(true)).generate_to(&man_dir)?; for subcommand in app.get_subcommands() { Man::new(subcommand.clone().disable_help_subcommand(true)).generate_to(&man_dir)?; } Ok(()) } oxigraph-oxigraph-68d45f7/cli/logo.svg000066400000000000000000000110361476336702400177700ustar00rootroot00000000000000 oxigraph-oxigraph-68d45f7/cli/src/000077500000000000000000000000001476336702400170755ustar00rootroot00000000000000oxigraph-oxigraph-68d45f7/cli/src/cli.rs000066400000000000000000000260671476336702400202250ustar00rootroot00000000000000use clap::{Parser, Subcommand, ValueHint}; use std::path::PathBuf; #[derive(Parser)] #[command(about, version, name = "oxigraph")] /// Oxigraph command line toolkit and SPARQL HTTP server pub struct Args { #[command(subcommand)] pub command: Command, } #[derive(Subcommand)] pub enum Command { /// Start Oxigraph HTTP server in read-write mode Serve { /// Directory in which the data should be persisted /// /// If not present, an in-memory storage will be used. #[arg(short, long, value_hint = ValueHint::DirPath)] location: Option, /// Host and port to listen to #[arg(short, long, default_value = "localhost:7878", value_hint = ValueHint::Hostname)] bind: String, /// Allows cross-origin requests #[arg(long)] cors: bool, /// If the SPARQL queries should look for triples in all the dataset graphs by default (ie. without `GRAPH` operations) /// /// This is equivalent as setting the union-default-graph option in all SPARQL queries #[arg(long)] union_default_graph: bool, }, /// Start Oxigraph HTTP server in read-only mode /// /// It allows to read the database while other processes are also reading it. /// Opening as read-only while having an other process writing the database is undefined behavior. ServeReadOnly { /// Directory in which Oxigraph data are persisted #[arg(short, long, value_hint = ValueHint::DirPath)] location: PathBuf, /// Host and port to listen to #[arg(short, long, default_value = "localhost:7878")] bind: String, /// Allow cross-origin requests #[arg(long)] cors: bool, /// If the SPARQL queries should look for triples in all the dataset graphs by default (ie. without `GRAPH` operations) /// /// This is equivalent as setting the union-default-graph option in all SPARQL queries #[arg(long)] union_default_graph: bool, }, /// Create a database backup into a target directory /// /// After its creation, the backup is usable a separated Oxigraph database /// and operates independently from the original database. /// /// If the target directory is in the same file system as the current database, /// the database content will not be fully copied /// but hard links will be used to point to the original database immutable snapshots. /// This allows cheap regular backups. /// /// If you want to move your data to another RDF storage system, you should use the dump operation instead. Backup { /// Directory in which Oxigraph data are persisted #[arg(short, long, value_hint = ValueHint::DirPath)] location: PathBuf, /// Directory in which the backup will be written #[arg(short, long, value_hint = ValueHint::DirPath)] destination: PathBuf, }, /// Load file(s) into the store /// /// Feel free to enable the --lenient option if you know your input is valid to get better performances. Load { /// Directory in which Oxigraph data are persisted #[arg(short, long, value_hint = ValueHint::DirPath)] location: PathBuf, /// File(s) to load /// /// If multiple files are provided they are loaded in parallel. /// /// If no file is given, stdin is read. #[arg(short, long, num_args = 0.., value_hint = ValueHint::FilePath)] file: Vec, /// The format of the file(s) to load /// /// It can be an extension like "nt" or a MIME type like "application/n-triples". /// /// By default the format is guessed from the loaded file extension. #[arg(long, required_unless_present = "file")] format: Option, /// Base IRI of the file(s) to load #[arg(long, value_hint = ValueHint::Url)] base: Option, /// Attempt to keep loading even if the data file is invalid /// /// This disables most of validation on RDF content. #[arg(long)] lenient: bool, /// Name of the graph to load the data to /// /// By default the default graph is used. /// /// Only available when loading a graph file (N-Triples, Turtle...) and not a dataset file (N-Quads, TriG...). #[arg(long, value_hint = ValueHint::Url)] graph: Option, }, /// Dump the store content into a file Dump { /// Directory in which Oxigraph data are persisted #[arg(short, long, value_hint = ValueHint::DirPath)] location: PathBuf, /// File to dump to /// /// If no file is given, stdout is used. #[arg(short, long, value_hint = ValueHint::FilePath)] file: Option, /// The format of the file(s) to dump /// /// It can be an extension like "nt" or a MIME type like "application/n-triples". /// /// By default the format is guessed from the target file extension. #[arg(long, required_unless_present = "file")] format: Option, /// Name of the graph to dump /// /// Use "default" to dump the default graph. /// /// By default all graphs are dumped if the output format supports datasets. /// If the format does not support named graph, then this parameter must be set. #[arg(long, value_hint = ValueHint::Url)] graph: Option, }, /// Execute a SPARQL query against the store Query { /// Directory in which Oxigraph data are persisted #[arg(short, long, value_hint = ValueHint::DirPath)] location: PathBuf, /// The SPARQL query to execute /// /// If no query or query file are given, stdin is used. #[arg(short, long, conflicts_with = "query_file")] query: Option, /// File in which the query is stored /// /// If no query or query file are given, stdin is used. #[arg(long, conflicts_with = "query", value_hint = ValueHint::FilePath)] query_file: Option, /// Base IRI of the query #[arg(long, value_hint = ValueHint::Url)] query_base: Option, /// File in which the query results will be stored /// /// If no file is given, stdout is used. #[arg(short, long, value_hint = ValueHint::FilePath)] results_file: Option, /// The results format /// /// It can be an extension like "nt" or a MIME type like "application/n-triples". /// /// By default the format is guessed from the results file extension. #[arg(long, required_unless_present = "results_file")] results_format: Option, /// Print to stderr a human-readable explanation of the query evaluation /// /// Use the stats option to print also query evaluation statistics. #[arg(long, conflicts_with = "explain_file")] explain: bool, /// Write to the given file an explanation of the query evaluation /// /// If the file extension is .json the JSON format is used, if .txt a human readable format is used. /// /// Use the stats option to print also query evaluation statistics. #[arg(long, conflicts_with = "explain", value_hint = ValueHint::FilePath)] explain_file: Option, /// Compute some evaluation statistics to print as part of the query explanations /// /// Beware, computing the statistics adds some overhead to the evaluation runtime. #[arg(long)] stats: bool, /// If the SPARQL queries should look for triples in all the dataset graphs by default (ie. without `GRAPH` operations) #[arg(long)] union_default_graph: bool, }, /// Execute a SPARQL update against the store Update { /// Directory in which Oxigraph data are persisted #[arg(short, long, value_hint = ValueHint::DirPath)] location: PathBuf, /// The SPARQL update to execute /// /// If no query or query file are given, stdin is used. #[arg(short, long, conflicts_with = "update_file")] update: Option, /// File in which the update is stored /// /// If no update or update file are given, stdin is used. #[arg(long, conflicts_with = "update", value_hint = ValueHint::FilePath)] update_file: Option, /// Base IRI of the update #[arg(long, value_hint = ValueHint::Url)] update_base: Option, }, /// Optimize the database storage /// /// Done by default in the background when serving requests. /// It is likely to not be useful in most of cases except if you provide a read-only SPARQL endpoint under heavy load. Optimize { /// Directory in which Oxigraph data are persisted #[arg(short, long, value_hint = ValueHint::DirPath)] location: PathBuf, }, /// Convert a RDF serialization from one format to an other Convert { /// File to convert from /// /// If no file is given, stdin is read. #[arg(short, long, value_hint = ValueHint::FilePath)] from_file: Option, /// The format of the file(s) to convert from /// /// It can be an extension like "nt" or a MIME type like "application/n-triples". /// /// By default the format is guessed from the input file extension. #[arg(long, required_unless_present = "from_file")] from_format: Option, /// Base IRI of the file to read #[arg(long, value_hint = ValueHint::Url)] from_base: Option, /// File to convert to /// /// If no file is given, stdout is written. #[arg(short, long, value_hint = ValueHint::FilePath)] to_file: Option, /// The format of the file(s) to convert to /// /// It can be an extension like "nt" or a MIME type like "application/n-triples". /// /// By default the format is guessed from the target file extension. #[arg(long, required_unless_present = "to_file")] to_format: Option, /// Base IRI of the file to write #[arg(long, value_hint = ValueHint::Url)] to_base: Option, /// Attempt to keep converting even if the data file is invalid #[arg(long)] lenient: bool, /// Only load the given named graph from the input file /// /// By default all graphs are loaded. #[arg(long, conflicts_with = "from_default_graph", value_hint = ValueHint::Url)] from_graph: Option, /// Only load the default graph from the input file #[arg(long, conflicts_with = "from_graph")] from_default_graph: bool, /// Name of the graph to map the default graph to /// /// By default the default graph is used. #[arg(long, value_hint = ValueHint::Url)] to_graph: Option, }, } oxigraph-oxigraph-68d45f7/cli/src/main.rs000066400000000000000000003237411476336702400204010ustar00rootroot00000000000000#![allow(clippy::print_stderr, clippy::cast_precision_loss, clippy::use_debug)] use crate::cli::{Args, Command}; use crate::service_description::{generate_service_description, EndpointKind}; use anyhow::{bail, ensure, Context}; use clap::Parser; use flate2::read::MultiGzDecoder; use oxhttp::model::{Body, HeaderName, HeaderValue, Method, Request, Response, Status}; use oxhttp::Server; use oxigraph::io::{RdfFormat, RdfParser, RdfSerializer}; use oxigraph::model::{ GraphName, GraphNameRef, IriParseError, NamedNode, NamedNodeRef, NamedOrBlankNode, }; use oxigraph::sparql::results::{QueryResultsFormat, QueryResultsSerializer}; use oxigraph::sparql::{Query, QueryOptions, QueryResults, Update}; use oxigraph::store::{BulkLoader, LoaderError, Store}; use oxiri::Iri; use rand::random; use rayon_core::ThreadPoolBuilder; #[cfg(feature = "geosparql")] use spargeo::register_geosparql_functions; use std::borrow::Cow; use std::cell::RefCell; use std::cmp::{max, min}; #[cfg(target_os = "linux")] use std::env; use std::ffi::OsStr; use std::fs::File; use std::io::{self, stdin, stdout, BufWriter, Read, Write}; use std::net::ToSocketAddrs; #[cfg(target_os = "linux")] use std::os::unix::net::UnixDatagram; use std::path::Path; use std::rc::Rc; use std::str::FromStr; use std::thread::available_parallelism; use std::time::{Duration, Instant}; use std::{fmt, fs, str}; use url::form_urlencoded; mod cli; mod service_description; const MAX_SPARQL_BODY_SIZE: u64 = 1024 * 1024 * 128; // 128MB const HTTP_TIMEOUT: Duration = Duration::from_secs(60); const HTML_ROOT_PAGE: &str = include_str!("../templates/query.html"); #[allow(clippy::large_include_file)] const YASGUI_JS: &str = include_str!("../templates/yasgui/yasgui.min.js"); const YASGUI_CSS: &str = include_str!("../templates/yasgui/yasgui.min.css"); const LOGO: &str = include_str!("../logo.svg"); pub fn main() -> anyhow::Result<()> { let matches = Args::parse(); match matches.command { Command::Serve { location, bind, cors, union_default_graph, } => serve( if let Some(location) = location { Store::open(location) } else { Store::new() }?, &bind, false, cors, union_default_graph, ), Command::ServeReadOnly { location, bind, cors, union_default_graph, } => serve( Store::open_read_only(location)?, &bind, true, cors, union_default_graph, ), Command::Backup { location, destination, } => { let store = Store::open_read_only(location)?; store.backup(destination)?; Ok(()) } Command::Load { location, file, lenient, format, base, graph, } => { let store = Store::open(location)?; let format = if let Some(format) = format { Some(rdf_format_from_name(&format)?) } else { None }; let graph = if let Some(iri) = &graph { Some( NamedNode::new(iri) .with_context(|| format!("The target graph name {iri} is invalid"))?, ) } else { None }; #[allow(clippy::cast_precision_loss)] if file.is_empty() { // We read from stdin let start = Instant::now(); let mut loader = store.bulk_loader().on_progress(move |size| { let elapsed = start.elapsed(); eprintln!( "{size} triples loaded in {}s ({} t/s)", elapsed.as_secs(), ((size as f64) / elapsed.as_secs_f64()).round() ) }); if lenient { loader = loader.on_parse_error(move |e| { eprintln!("Parsing error: {e}"); Ok(()) }) } bulk_load( &loader, stdin().lock(), format.context("The --format option must be set when loading from stdin")?, base.as_deref(), graph, lenient, ) } else { ThreadPoolBuilder::new() .num_threads(max(1, available_parallelism()?.get() / 2)) .thread_name(|i| format!("Oxigraph bulk loader thread {i}")) .build()? .scope(|s| { for file in file { let store = store.clone(); let graph = graph.clone(); let base = base.clone(); s.spawn(move |_| { let f = file.clone(); let start = Instant::now(); let mut loader = store.bulk_loader().on_progress(move |size| { let elapsed = start.elapsed(); eprintln!( "{} triples loaded in {}s ({} t/s) from {}", size, elapsed.as_secs(), ((size as f64) / elapsed.as_secs_f64()).round(), f.display() ) }); if lenient { let f = file.clone(); loader = loader.on_parse_error(move |e| { eprintln!("Parsing error on file {}: {}", f.display(), e); Ok(()) }) } let fp = match File::open(&file) { Ok(fp) => fp, Err(error) => { eprintln!( "Error while opening file {}: {}", file.display(), error ); return; } }; if let Err(error) = { if file.extension().is_some_and(|e| e == OsStr::new("gz")) { bulk_load( &loader, MultiGzDecoder::new(fp), format.unwrap_or_else(|| { rdf_format_from_path(&file.with_extension("")) .unwrap() }), base.as_deref(), graph, lenient, ) } else { bulk_load( &loader, fp, format.unwrap_or_else(|| { rdf_format_from_path(&file).unwrap() }), base.as_deref(), graph, lenient, ) } } { eprintln!( "Error while loading file {}: {}", file.display(), error ) // TODO: hard fail } }) } }); store.flush()?; Ok(()) } } Command::Dump { location, file, format, graph, } => { let store = Store::open_read_only(location)?; let format = if let Some(format) = format { rdf_format_from_name(&format)? } else if let Some(file) = &file { rdf_format_from_path(file)? } else { bail!("The --format option must be set when writing to stdout") }; let graph = if let Some(graph) = &graph { Some(if graph.eq_ignore_ascii_case("default") { GraphNameRef::DefaultGraph } else { NamedNodeRef::new(graph) .with_context(|| format!("The target graph name {graph} is invalid"))? .into() }) } else { None }; if let Some(file) = file { close_file_writer(dump( &store, BufWriter::new(File::create(file)?), format, graph, )?)?; } else { dump(&store, stdout().lock(), format, graph)?.flush()?; } Ok(()) } Command::Query { location, query, query_file, query_base, results_file, results_format, explain, explain_file, stats, union_default_graph, } => { let query = if let Some(query) = query { query } else if let Some(query_file) = query_file { fs::read_to_string(&query_file).with_context(|| { format!("Not able to read query file {}", query_file.display()) })? } else { io::read_to_string(stdin().lock())? }; let mut query = Query::parse(&query, query_base.as_deref())?; if union_default_graph { query.dataset_mut().set_default_graph_as_union(); } let store = Store::open_read_only(location)?; let (results, explanation) = store.explain_query_opt(query, default_query_options(), stats)?; let print_result = (|| { match results? { QueryResults::Solutions(solutions) => { let format = if let Some(name) = results_format { if let Some(format) = QueryResultsFormat::from_extension(&name) { format } else if let Some(format) = QueryResultsFormat::from_media_type(&name) { format } else { bail!("The file format '{name}' is unknown") } } else if let Some(results_file) = &results_file { format_from_path(results_file, |ext| { QueryResultsFormat::from_extension(ext).with_context(|| { format!("The file extension '{ext}' is unknown") }) })? } else { bail!("The --results-format option must be set when writing to stdout") }; if let Some(results_file) = results_file { let mut serializer = QueryResultsSerializer::from_format(format) .serialize_solutions_to_writer( BufWriter::new(File::create(results_file)?), solutions.variables().to_vec(), )?; for solution in solutions { serializer.serialize(&solution?)?; } close_file_writer(serializer.finish()?)?; } else { let mut serializer = QueryResultsSerializer::from_format(format) .serialize_solutions_to_writer( stdout().lock(), solutions.variables().to_vec(), )?; for solution in solutions { serializer.serialize(&solution?)?; } serializer.finish()?.flush()?; } } QueryResults::Boolean(result) => { let format = if let Some(name) = results_format { if let Some(format) = QueryResultsFormat::from_extension(&name) { format } else if let Some(format) = QueryResultsFormat::from_media_type(&name) { format } else { bail!("The file format '{name}' is unknown") } } else if let Some(results_file) = &results_file { format_from_path(results_file, |ext| { QueryResultsFormat::from_extension(ext).with_context(|| { format!("The file extension '{ext}' is unknown") }) })? } else { bail!("The --results-format option must be set when writing to stdout") }; if let Some(results_file) = results_file { close_file_writer( QueryResultsSerializer::from_format(format) .serialize_boolean_to_writer( BufWriter::new(File::create(results_file)?), result, )?, )?; } else { QueryResultsSerializer::from_format(format) .serialize_boolean_to_writer(stdout().lock(), result)? .flush()?; } } QueryResults::Graph(triples) => { let format = if let Some(name) = &results_format { rdf_format_from_name(name) } else if let Some(results_file) = &results_file { rdf_format_from_path(results_file) } else { bail!("The --results-format option must be set when writing to stdout") }?; let serializer = RdfSerializer::from_format(format); if let Some(results_file) = results_file { let mut serializer = serializer.for_writer(BufWriter::new(File::create(results_file)?)); for triple in triples { serializer.serialize_triple(triple?.as_ref())?; } close_file_writer(serializer.finish()?)?; } else { let mut serializer = serializer.for_writer(stdout().lock()); for triple in triples { serializer.serialize_triple(triple?.as_ref())?; } serializer.finish()?.flush()?; } } } Ok(()) })(); if let Some(explain_file) = explain_file { let mut file = BufWriter::new(File::create(&explain_file)?); match explain_file .extension() .and_then(OsStr::to_str) { Some("json") => { explanation.write_in_json(&mut file)?; }, Some("txt") => { write!(file, "{explanation:?}")?; }, _ => bail!("The given explanation file {} must have an extension that is .json or .txt", explain_file.display()) } close_file_writer(file)?; } else if explain || stats { eprintln!("{explanation:#?}"); } print_result } Command::Update { location, update, update_file, update_base, } => { let update = if let Some(update) = update { update } else if let Some(update_file) = update_file { fs::read_to_string(&update_file).with_context(|| { format!("Not able to read update file {}", update_file.display()) })? } else { io::read_to_string(stdin().lock())? }; let update = Update::parse(&update, update_base.as_deref())?; let store = Store::open(location)?; store.update_opt(update, default_query_options())?; store.flush()?; Ok(()) } Command::Optimize { location } => { let store = Store::open(location)?; store.optimize()?; Ok(()) } Command::Convert { from_file, from_format, from_base, to_file, to_format, to_base, lenient, from_graph, from_default_graph, to_graph, } => { let from_format = if let Some(format) = from_format { rdf_format_from_name(&format)? } else if let Some(file) = &from_file { rdf_format_from_path(file)? } else { bail!("The --from-format option must be set when reading from stdin") }; let mut parser = RdfParser::from_format(from_format); if let Some(base) = from_base { parser = parser .with_base_iri(&base) .with_context(|| format!("Invalid base IRI {base}"))?; } let to_format = if let Some(format) = to_format { rdf_format_from_name(&format)? } else if let Some(file) = &to_file { rdf_format_from_path(file)? } else { bail!("The --to-format option must be set when writing to stdout") }; let serializer = RdfSerializer::from_format(to_format); let from_graph = if let Some(from_graph) = from_graph { Some( NamedNode::new(&from_graph) .with_context(|| format!("The source graph name {from_graph} is invalid"))? .into(), ) } else if from_default_graph { Some(GraphName::DefaultGraph) } else { None }; let to_graph = if let Some(to_graph) = to_graph { NamedNode::new(&to_graph) .with_context(|| format!("The target graph name {to_graph} is invalid"))? .into() } else { GraphName::DefaultGraph }; match (from_file, to_file) { (Some(from_file), Some(to_file)) => close_file_writer(do_convert( parser, File::open(from_file)?, serializer, BufWriter::new(File::create(to_file)?), lenient, &from_graph, &to_graph, to_base.as_deref(), )?), (Some(from_file), None) => do_convert( parser, File::open(from_file)?, serializer, stdout().lock(), lenient, &from_graph, &to_graph, to_base.as_deref(), )? .flush(), (None, Some(to_file)) => close_file_writer(do_convert( parser, stdin().lock(), serializer, BufWriter::new(File::create(to_file)?), lenient, &from_graph, &to_graph, to_base.as_deref(), )?), (None, None) => do_convert( parser, stdin().lock(), serializer, stdout().lock(), lenient, &from_graph, &to_graph, to_base.as_deref(), )? .flush(), }?; Ok(()) } } } fn bulk_load( loader: &BulkLoader, reader: impl Read, format: RdfFormat, base_iri: Option<&str>, to_graph_name: Option, lenient: bool, ) -> anyhow::Result<()> { let mut parser = RdfParser::from_format(format); if let Some(to_graph_name) = to_graph_name { parser = parser.with_default_graph(to_graph_name); } if let Some(base_iri) = base_iri { parser = parser .with_base_iri(base_iri) .with_context(|| format!("Invalid base IRI {base_iri}"))?; } if lenient { parser = parser.unchecked(); } loader.load_from_reader(parser, reader)?; Ok(()) } fn dump( store: &Store, writer: W, format: RdfFormat, from_graph_name: Option>, ) -> anyhow::Result { ensure!( format.supports_datasets() || from_graph_name.is_some(), "The --graph option is required when writing a format not supporting datasets like NTriples, Turtle or RDF/XML. Use --graph \"default\" to dump only the default graph." ); Ok(if let Some(from_graph_name) = from_graph_name { store.dump_graph_to_writer(from_graph_name, format, writer) } else { store.dump_to_writer(format, writer) }?) } fn do_convert( parser: RdfParser, reader: R, mut serializer: RdfSerializer, writer: W, lenient: bool, from_graph: &Option, default_graph: &GraphName, to_base: Option<&str>, ) -> anyhow::Result { let mut parser = parser.for_reader(reader); let first = parser.next(); // We read the first element to get prefixes and the base IRI if let Some(base_iri) = to_base.or_else(|| parser.base_iri()) { serializer = serializer .with_base_iri(base_iri) .with_context(|| format!("Invalid base IRI: {base_iri}"))?; } for (prefix_name, prefix_iri) in parser.prefixes() { serializer = serializer .with_prefix(prefix_name, prefix_iri) .with_context(|| format!("Invalid IRI for prefix {prefix_name}: {prefix_iri}"))?; } let mut serializer = serializer.for_writer(writer); for quad_result in first.into_iter().chain(parser) { match quad_result { Ok(mut quad) => { if let Some(from_graph) = from_graph { if quad.graph_name == *from_graph { quad.graph_name = GraphName::DefaultGraph; } else { continue; } } if quad.graph_name.is_default_graph() { quad.graph_name = default_graph.clone(); } serializer.serialize_quad(&quad)?; } Err(e) => { if lenient { eprintln!("Parsing error: {e}"); } else { return Err(e.into()); } } } } Ok(serializer.finish()?) } fn format_from_path( path: &Path, from_extension: impl FnOnce(&str) -> anyhow::Result, ) -> anyhow::Result { if let Some(ext) = path.extension().and_then(OsStr::to_str) { from_extension(ext).map_err(|e| { e.context(format!( "Not able to guess the file format from file name extension '{ext}'" )) }) } else { bail!( "The path {} has no extension to guess a file format from", path.display() ) } } fn rdf_format_from_path(path: &Path) -> anyhow::Result { format_from_path(path, |ext| { RdfFormat::from_extension(ext) .with_context(|| format!("The file extension '{ext}' is unknown")) }) } fn rdf_format_from_name(name: &str) -> anyhow::Result { if let Some(t) = RdfFormat::from_extension(name) { return Ok(t); } if let Some(t) = RdfFormat::from_media_type(name) { return Ok(t); } bail!("The file format '{name}' is unknown") } fn serve( store: Store, bind: &str, read_only: bool, cors: bool, union_default_graph: bool, ) -> anyhow::Result<()> { let mut server = if cors { Server::new(cors_middleware(move |request| { handle_request(request, store.clone(), read_only, union_default_graph) .unwrap_or_else(|(status, message)| error(status, message)) })) } else { Server::new(move |request| { handle_request(request, store.clone(), read_only, union_default_graph) .unwrap_or_else(|(status, message)| error(status, message)) }) } .with_global_timeout(HTTP_TIMEOUT) .with_server_name(concat!("Oxigraph/", env!("CARGO_PKG_VERSION")))? .with_max_concurrent_connections(available_parallelism()?.get() * 128); for socket in bind.to_socket_addrs()? { server = server.bind(socket); } let server = server.spawn()?; #[cfg(target_os = "linux")] systemd_notify_ready()?; eprintln!("Listening for requests at http://{bind}"); server.join()?; Ok(()) } fn cors_middleware( on_request: impl Fn(&mut Request) -> Response + Send + Sync + 'static, ) -> impl Fn(&mut Request) -> Response + Send + Sync + 'static { let origin = HeaderName::from_str("Origin").unwrap(); let access_control_allow_origin = HeaderName::from_str("Access-Control-Allow-Origin").unwrap(); let access_control_request_method = HeaderName::from_str("Access-Control-Request-Method").unwrap(); let access_control_allow_method = HeaderName::from_str("Access-Control-Allow-Methods").unwrap(); let access_control_request_headers = HeaderName::from_str("Access-Control-Request-Headers").unwrap(); let access_control_allow_headers = HeaderName::from_str("Access-Control-Allow-Headers").unwrap(); let star = HeaderValue::from_str("*").unwrap(); move |request| { if *request.method() == Method::OPTIONS { let mut response = Response::builder(Status::NO_CONTENT); if request.header(&origin).is_some() { response .headers_mut() .append(access_control_allow_origin.clone(), star.clone()); } if let Some(method) = request.header(&access_control_request_method) { response .headers_mut() .append(access_control_allow_method.clone(), method.clone()); } if let Some(headers) = request.header(&access_control_request_headers) { response .headers_mut() .append(access_control_allow_headers.clone(), headers.clone()); } response.build() } else { let mut response = on_request(request); if request.header(&origin).is_some() { response .headers_mut() .append(access_control_allow_origin.clone(), star.clone()); } response } } } type HttpError = (Status, String); fn handle_request( request: &mut Request, store: Store, read_only: bool, union_default_graph: bool, ) -> Result { match (request.url().path(), request.method().as_ref()) { ("/", "HEAD") => Ok(Response::builder(Status::OK) .with_header(HeaderName::CONTENT_TYPE, "text/html") .unwrap() .build()), ("/", "GET") => Ok(Response::builder(Status::OK) .with_header(HeaderName::CONTENT_TYPE, "text/html") .unwrap() .with_body(HTML_ROOT_PAGE)), ("/yasgui.min.css", "HEAD") => Ok(Response::builder(Status::OK) .with_header(HeaderName::CONTENT_TYPE, "text/css") .unwrap() .build()), ("/yasgui.min.css", "GET") => Ok(Response::builder(Status::OK) .with_header(HeaderName::CONTENT_TYPE, "text/css") .unwrap() .with_body(YASGUI_CSS)), ("/yasgui.min.js", "HEAD") => Ok(Response::builder(Status::OK) .with_header(HeaderName::CONTENT_TYPE, "application/javascript") .unwrap() .build()), ("/yasgui.min.js", "GET") => Ok(Response::builder(Status::OK) .with_header(HeaderName::CONTENT_TYPE, "application/javascript") .unwrap() .with_body(YASGUI_JS)), ("/logo.svg", "HEAD") => Ok(Response::builder(Status::OK) .with_header(HeaderName::CONTENT_TYPE, "image/svg+xml") .unwrap() .build()), ("/logo.svg", "GET") => Ok(Response::builder(Status::OK) .with_header(HeaderName::CONTENT_TYPE, "image/svg+xml") .unwrap() .with_body(LOGO)), ("/query", "GET") => { let query = url_query(request); if query.is_empty() { let format = rdf_content_negotiation(request)?; let description = generate_service_description(format, EndpointKind::Query, union_default_graph); Ok(Response::builder(Status::OK) .with_header(HeaderName::CONTENT_TYPE, format.media_type()) .map_err(internal_server_error)? .with_body(description)) } else { configure_and_evaluate_sparql_query( &store, &[url_query(request)], None, request, union_default_graph, ) } } ("/query", "POST") => { let content_type = content_type(request).ok_or_else(|| bad_request("No Content-Type given"))?; if content_type == "application/sparql-query" { let query = limited_string_body(request)?; configure_and_evaluate_sparql_query( &store, &[url_query(request)], Some(query), request, union_default_graph, ) } else if content_type == "application/x-www-form-urlencoded" { let buffer = limited_body(request)?; configure_and_evaluate_sparql_query( &store, &[url_query(request), &buffer], None, request, union_default_graph, ) } else { Err(unsupported_media_type(&content_type)) } } ("/update", "GET") => { if read_only { return Err(the_server_is_read_only()); } let format = rdf_content_negotiation(request)?; let description = generate_service_description(format, EndpointKind::Update, union_default_graph); Ok(Response::builder(Status::OK) .with_header(HeaderName::CONTENT_TYPE, format.media_type()) .map_err(internal_server_error)? .with_body(description)) } ("/update", "POST") => { if read_only { return Err(the_server_is_read_only()); } let content_type = content_type(request).ok_or_else(|| bad_request("No Content-Type given"))?; if content_type == "application/sparql-update" { let update = limited_string_body(request)?; configure_and_evaluate_sparql_update( &store, &[url_query(request)], Some(update), request, union_default_graph, ) } else if content_type == "application/x-www-form-urlencoded" { let buffer = limited_body(request)?; configure_and_evaluate_sparql_update( &store, &[url_query(request), &buffer], None, request, union_default_graph, ) } else { Err(unsupported_media_type(&content_type)) } } (path, "GET") if path.starts_with("/store") => { if let Some(target) = store_target(request)? { assert_that_graph_exists(&store, &target)?; let format = rdf_content_negotiation(request)?; let quads = store.quads_for_pattern( None, None, None, Some(GraphName::from(target).as_ref()), ); ReadForWrite::build_response( move |w| Ok((RdfSerializer::from_format(format).for_writer(w), quads)), |(mut serializer, mut quads)| { Ok(if let Some(q) = quads.next() { serializer.serialize_triple(&q?.into())?; Some((serializer, quads)) } else { serializer.finish()?; None }) }, format.media_type(), ) } else { let format = rdf_content_negotiation(request)?; if !format.supports_datasets() { return Err(bad_request(format!( "It is not possible to serialize the full RDF dataset using {format} that does not support named graphs" ))); } ReadForWrite::build_response( move |w| { Ok(( RdfSerializer::from_format(format).for_writer(w), store.iter(), )) }, |(mut serializer, mut quads)| { Ok(if let Some(q) = quads.next() { serializer.serialize_quad(&q?)?; Some((serializer, quads)) } else { serializer.finish()?; None }) }, format.media_type(), ) } } (path, "PUT") if path.starts_with("/store") => { if read_only { return Err(the_server_is_read_only()); } let content_type = content_type(request).ok_or_else(|| bad_request("No Content-Type given"))?; if let Some(target) = store_target(request)? { let format = RdfFormat::from_media_type(&content_type) .ok_or_else(|| unsupported_media_type(&content_type))?; let new = !match &target { NamedGraphName::NamedNode(target) => { if store .contains_named_graph(target) .map_err(internal_server_error)? { store.clear_graph(target).map_err(internal_server_error)?; true } else { store .insert_named_graph(target) .map_err(internal_server_error)?; false } } NamedGraphName::DefaultGraph => { store .clear_graph(GraphNameRef::DefaultGraph) .map_err(internal_server_error)?; true } }; web_load_graph(&store, request, format, &GraphName::from(target))?; Ok(Response::builder(if new { Status::CREATED } else { Status::NO_CONTENT }) .build()) } else { let format = RdfFormat::from_media_type(&content_type) .ok_or_else(|| unsupported_media_type(&content_type))?; store.clear().map_err(internal_server_error)?; web_load_dataset(&store, request, format)?; Ok(Response::builder(Status::NO_CONTENT).build()) } } (path, "DELETE") if path.starts_with("/store") => { if read_only { return Err(the_server_is_read_only()); } if let Some(target) = store_target(request)? { match target { NamedGraphName::DefaultGraph => store .clear_graph(GraphNameRef::DefaultGraph) .map_err(internal_server_error)?, NamedGraphName::NamedNode(target) => { if store .contains_named_graph(&target) .map_err(internal_server_error)? { store .remove_named_graph(&target) .map_err(internal_server_error)?; } else { return Err(( Status::NOT_FOUND, format!("The graph {target} does not exists"), )); } } } } else { store.clear().map_err(internal_server_error)?; } Ok(Response::builder(Status::NO_CONTENT).build()) } (path, "POST") if path.starts_with("/store") => { if read_only { return Err(the_server_is_read_only()); } let content_type = content_type(request).ok_or_else(|| bad_request("No Content-Type given"))?; if let Some(target) = store_target(request)? { let format = RdfFormat::from_media_type(&content_type) .ok_or_else(|| unsupported_media_type(&content_type))?; let new = assert_that_graph_exists(&store, &target).is_ok(); web_load_graph(&store, request, format, &GraphName::from(target))?; Ok(Response::builder(if new { Status::CREATED } else { Status::NO_CONTENT }) .build()) } else { let format = RdfFormat::from_media_type(&content_type) .ok_or_else(|| unsupported_media_type(&content_type))?; if format.supports_datasets() { web_load_dataset(&store, request, format)?; Ok(Response::builder(Status::NO_CONTENT).build()) } else { let graph = resolve_with_base(request, &format!("/store/{:x}", random::()))?; web_load_graph(&store, request, format, &graph.clone().into())?; Ok(Response::builder(Status::CREATED) .with_header(HeaderName::LOCATION, graph.into_string()) .unwrap() .build()) } } } (path, "HEAD") if path.starts_with("/store") => { if let Some(target) = store_target(request)? { assert_that_graph_exists(&store, &target)?; } Ok(Response::builder(Status::OK).build()) } _ => Err(( Status::NOT_FOUND, format!( "{} {} is not supported by this server", request.method(), request.url().path() ), )), } } fn base_url(request: &Request) -> String { let mut url = request.url().clone(); url.set_query(None); url.set_fragment(None); url.into() } fn resolve_with_base(request: &Request, url: &str) -> Result { Ok(Iri::parse(base_url(request)) .map_err(bad_request)? .resolve(url) .map_err(bad_request)? .into()) } fn url_query(request: &Request) -> &[u8] { request.url().query().unwrap_or("").as_bytes() } fn url_query_parameter<'a>(request: &'a Request, param: &str) -> Option> { request .url() .query_pairs() .find(|(k, _)| k == param) .map(|(_, v)| v) } fn limited_string_body(request: &mut Request) -> Result { String::from_utf8(limited_body(request)?) .map_err(|e| bad_request(format!("Invalid UTF-8 body: {e}"))) } fn limited_body(request: &mut Request) -> Result, HttpError> { let body = request.body_mut(); if let Some(body_len) = body.len() { if body_len > MAX_SPARQL_BODY_SIZE { // it's too big return Err(bad_request(format!( "SPARQL body payloads are limited to {MAX_SPARQL_BODY_SIZE} bytes, found {body_len} bytes" ))); } let mut payload = Vec::with_capacity( body_len .try_into() .map_err(|_| bad_request("Huge body size"))?, ); body.read_to_end(&mut payload) .map_err(internal_server_error)?; Ok(payload) } else { let mut payload = Vec::new(); body.take(MAX_SPARQL_BODY_SIZE + 1) .read_to_end(&mut payload) .map_err(internal_server_error)?; if payload.len() > MAX_SPARQL_BODY_SIZE.try_into().unwrap() { return Err(bad_request(format!( "SPARQL body payloads are limited to {MAX_SPARQL_BODY_SIZE} bytes" ))); } Ok(payload) } } fn configure_and_evaluate_sparql_query( store: &Store, encoded: &[&[u8]], mut query: Option, request: &Request, default_use_default_graph_as_union: bool, ) -> Result { let mut default_graph_uris = Vec::new(); let mut named_graph_uris = Vec::new(); let mut use_default_graph_as_union = false; for encoded in encoded { for (k, v) in form_urlencoded::parse(encoded) { match k.as_ref() { "query" => { if query.is_some() { return Err(bad_request("Multiple query parameters provided")); } query = Some(v.into_owned()) } "default-graph-uri" => default_graph_uris.push(v.into_owned()), "union-default-graph" => use_default_graph_as_union = true, "named-graph-uri" => named_graph_uris.push(v.into_owned()), _ => (), } } } if default_graph_uris.is_empty() && named_graph_uris.is_empty() { use_default_graph_as_union |= default_use_default_graph_as_union; } let query = query.ok_or_else(|| bad_request("You should set the 'query' parameter"))?; evaluate_sparql_query( store, &query, use_default_graph_as_union, default_graph_uris, named_graph_uris, request, ) } fn evaluate_sparql_query( store: &Store, query: &str, use_default_graph_as_union: bool, default_graph_uris: Vec, named_graph_uris: Vec, request: &Request, ) -> Result { let mut query = Query::parse(query, Some(&base_url(request))).map_err(bad_request)?; if use_default_graph_as_union { if !default_graph_uris.is_empty() || !named_graph_uris.is_empty() { return Err(bad_request( "default-graph-uri or named-graph-uri and union-default-graph should not be set at the same time" )); } query.dataset_mut().set_default_graph_as_union() } else if !default_graph_uris.is_empty() || !named_graph_uris.is_empty() { query.dataset_mut().set_default_graph( default_graph_uris .into_iter() .map(|e| Ok(NamedNode::new(e)?.into())) .collect::, IriParseError>>() .map_err(bad_request)?, ); query.dataset_mut().set_available_named_graphs( named_graph_uris .into_iter() .map(|e| Ok(NamedNode::new(e)?.into())) .collect::, IriParseError>>() .map_err(bad_request)?, ); } let results = store .query_opt(query, default_query_options()) .map_err(internal_server_error)?; match results { QueryResults::Solutions(solutions) => { let format = query_results_content_negotiation(request)?; ReadForWrite::build_response( move |w| { Ok(( QueryResultsSerializer::from_format(format) .serialize_solutions_to_writer(w, solutions.variables().to_vec())?, solutions, )) }, |(mut serializer, mut solutions)| { Ok(if let Some(solution) = solutions.next() { serializer.serialize(&solution?)?; Some((serializer, solutions)) } else { serializer.finish()?; None }) }, format.media_type(), ) } QueryResults::Boolean(result) => { let format = query_results_content_negotiation(request)?; let mut body = Vec::new(); QueryResultsSerializer::from_format(format) .serialize_boolean_to_writer(&mut body, result) .map_err(internal_server_error)?; Ok(Response::builder(Status::OK) .with_header(HeaderName::CONTENT_TYPE, format.media_type()) .unwrap() .with_body(body)) } QueryResults::Graph(triples) => { let format = rdf_content_negotiation(request)?; ReadForWrite::build_response( move |w| Ok((RdfSerializer::from_format(format).for_writer(w), triples)), |(mut serializer, mut triples)| { Ok(if let Some(t) = triples.next() { serializer.serialize_triple(&t?)?; Some((serializer, triples)) } else { serializer.finish()?; None }) }, format.media_type(), ) } } } fn default_query_options() -> QueryOptions { let mut options = QueryOptions::default(); #[cfg(feature = "geosparql")] { options = register_geosparql_functions(options); } options } fn configure_and_evaluate_sparql_update( store: &Store, encoded: &[&[u8]], mut update: Option, request: &Request, default_use_default_graph_as_union: bool, ) -> Result { let mut use_default_graph_as_union = false; let mut default_graph_uris = Vec::new(); let mut named_graph_uris = Vec::new(); for encoded in encoded { for (k, v) in form_urlencoded::parse(encoded) { match k.as_ref() { "update" => { if update.is_some() { return Err(bad_request("Multiple update parameters provided")); } update = Some(v.into_owned()) } "using-graph-uri" => default_graph_uris.push(v.into_owned()), "using-union-graph" => use_default_graph_as_union = true, "using-named-graph-uri" => named_graph_uris.push(v.into_owned()), _ => (), } } } if default_graph_uris.is_empty() && named_graph_uris.is_empty() { use_default_graph_as_union |= default_use_default_graph_as_union; } let update = update.ok_or_else(|| bad_request("You should set the 'update' parameter"))?; evaluate_sparql_update( store, &update, use_default_graph_as_union, default_graph_uris, named_graph_uris, request, ) } fn evaluate_sparql_update( store: &Store, update: &str, use_default_graph_as_union: bool, default_graph_uris: Vec, named_graph_uris: Vec, request: &Request, ) -> Result { let mut update = Update::parse(update, Some(base_url(request).as_str())).map_err(bad_request)?; if use_default_graph_as_union { if !default_graph_uris.is_empty() || !named_graph_uris.is_empty() { return Err(bad_request( "using-graph-uri or using-named-graph-uri and using-union-graph should not be set at the same time" )); } for using in update.using_datasets_mut() { if !using.is_default_dataset() { return Err(bad_request( "using-union-graph must not be used with a SPARQL UPDATE containing USING", )); } using.set_default_graph_as_union(); } } else if !default_graph_uris.is_empty() || !named_graph_uris.is_empty() { let default_graph_uris = default_graph_uris .into_iter() .map(|e| Ok(NamedNode::new(e)?.into())) .collect::, IriParseError>>() .map_err(bad_request)?; let named_graph_uris = named_graph_uris .into_iter() .map(|e| Ok(NamedNode::new(e)?.into())) .collect::, IriParseError>>() .map_err(bad_request)?; for using in update.using_datasets_mut() { if !using.is_default_dataset() { return Err(bad_request( "using-graph-uri and using-named-graph-uri must not be used with a SPARQL UPDATE containing USING", )); } using.set_default_graph(default_graph_uris.clone()); using.set_available_named_graphs(named_graph_uris.clone()); } } store .update_opt(update, default_query_options()) .map_err(internal_server_error)?; Ok(Response::builder(Status::NO_CONTENT).build()) } fn store_target(request: &Request) -> Result, HttpError> { if request.url().path() == "/store" { let mut graph = None; let mut default = false; for (k, v) in request.url().query_pairs() { match k.as_ref() { "graph" => graph = Some(v.into_owned()), "default" => default = true, _ => (), } } if let Some(graph) = graph { if default { Err(bad_request( "Both graph and default parameters should not be set at the same time", )) } else { Ok(Some(NamedGraphName::NamedNode(resolve_with_base( request, &graph, )?))) } } else if default { Ok(Some(NamedGraphName::DefaultGraph)) } else { Ok(None) } } else { Ok(Some(NamedGraphName::NamedNode(resolve_with_base( request, "", )?))) } } fn assert_that_graph_exists(store: &Store, target: &NamedGraphName) -> Result<(), HttpError> { if match target { NamedGraphName::DefaultGraph => true, NamedGraphName::NamedNode(target) => store .contains_named_graph(target) .map_err(internal_server_error)?, } { Ok(()) } else { Err(( Status::NOT_FOUND, format!( "The graph {} does not exists", GraphName::from(target.clone()) ), )) } } #[derive(Eq, PartialEq, Debug, Clone, Hash)] enum NamedGraphName { NamedNode(NamedNode), DefaultGraph, } impl From for GraphName { fn from(graph_name: NamedGraphName) -> Self { match graph_name { NamedGraphName::NamedNode(node) => node.into(), NamedGraphName::DefaultGraph => Self::DefaultGraph, } } } fn rdf_content_negotiation(request: &Request) -> Result { content_negotiation( request, RdfFormat::from_media_type, RdfFormat::NQuads, &[ ("application", RdfFormat::NQuads), ("text", RdfFormat::NQuads), ], "application/n-quads or text/turtle", ) } fn query_results_content_negotiation(request: &Request) -> Result { content_negotiation( request, QueryResultsFormat::from_media_type, QueryResultsFormat::Json, &[ ("application", QueryResultsFormat::Json), ("text", QueryResultsFormat::Json), ], "application/sparql-results+json or text/tsv", ) } fn content_negotiation( request: &Request, parse: impl Fn(&str) -> Option, default: F, default_by_base: &[(&str, F)], example: &str, ) -> Result { let default_value = HeaderValue::default(); let header = request .header(&HeaderName::ACCEPT) .unwrap_or(&default_value) .to_str() .map_err(|_| bad_request("The Accept header should be a valid ASCII string"))?; if header.is_empty() { return Ok(default); } let mut result = None; let mut result_score = 0_f32; for mut possible in header.split(',') { let mut score = 1.; if let Some((possible_type, last_parameter)) = possible.rsplit_once(';') { if let Some((name, value)) = last_parameter.split_once('=') { if name.trim().eq_ignore_ascii_case("q") { score = f32::from_str(value.trim()).map_err(|_| { bad_request(format!("Invalid Accept media type score: {value}")) })?; possible = possible_type; } } } if score <= result_score { continue; } let (possible_base, possible_sub) = possible .split_once(';') .unwrap_or((possible, "")) .0 .split_once('/') .ok_or_else(|| bad_request(format!("Invalid media type: '{possible}'")))?; let possible_base = possible_base.trim(); let possible_sub = possible_sub.trim(); let mut format = None; if possible_base == "*" && possible_sub == "*" { format = Some(default); } else if possible_sub == "*" { for (base, sub_format) in default_by_base { if *base == possible_base { format = Some(*sub_format); } } } else { format = parse(possible); } if let Some(format) = format { result = Some(format); result_score = score; } } result.ok_or_else(|| { ( Status::NOT_ACCEPTABLE, format!("The accept header does not provide any accepted format like {example}"), ) }) } fn content_type(request: &Request) -> Option { let value = request.header(&HeaderName::CONTENT_TYPE)?.to_str().ok()?; Some( value .split_once(';') .map_or(value, |(b, _)| b) .trim() .to_ascii_lowercase(), ) } fn web_load_graph( store: &Store, request: &mut Request, format: RdfFormat, to_graph_name: &GraphName, ) -> Result<(), HttpError> { let base_iri = if let GraphName::NamedNode(graph_name) = to_graph_name { Some(graph_name.as_str()) } else { None }; let mut parser = RdfParser::from_format(format) .without_named_graphs() .with_default_graph(to_graph_name.clone()); if url_query_parameter(request, "lenient").is_some() { parser = parser.unchecked(); } if let Some(base_iri) = base_iri { parser = parser.with_base_iri(base_iri).map_err(bad_request)?; } if url_query_parameter(request, "no_transaction").is_some() { web_bulk_loader(store, request).load_from_reader(parser, request.body_mut()) } else { store.load_from_reader(parser, request.body_mut()) } .map_err(loader_to_http_error) } fn web_load_dataset( store: &Store, request: &mut Request, format: RdfFormat, ) -> Result<(), HttpError> { let mut parser = RdfParser::from_format(format); if url_query_parameter(request, "lenient").is_some() { parser = parser.unchecked(); } if url_query_parameter(request, "no_transaction").is_some() { web_bulk_loader(store, request).load_from_reader(parser, request.body_mut()) } else { store.load_from_reader(parser, request.body_mut()) } .map_err(loader_to_http_error) } fn web_bulk_loader(store: &Store, request: &Request) -> BulkLoader { let start = Instant::now(); let mut loader = store.bulk_loader().on_progress(move |size| { let elapsed = start.elapsed(); eprintln!( "{} triples loaded in {}s ({} t/s)", size, elapsed.as_secs(), ((size as f64) / elapsed.as_secs_f64()).round() ) }); if url_query_parameter(request, "lenient").is_some() { loader = loader.on_parse_error(move |e| { eprintln!("Parsing error: {e}"); Ok(()) }) } loader } fn error(status: Status, message: impl fmt::Display) -> Response { Response::builder(status) .with_header(HeaderName::CONTENT_TYPE, "text/plain; charset=utf-8") .unwrap() .with_body(message.to_string()) } fn bad_request(message: impl fmt::Display) -> HttpError { (Status::BAD_REQUEST, message.to_string()) } fn the_server_is_read_only() -> HttpError { (Status::FORBIDDEN, "The server is read-only".into()) } fn unsupported_media_type(content_type: &str) -> HttpError { ( Status::UNSUPPORTED_MEDIA_TYPE, format!("No supported content Content-Type given: {content_type}"), ) } fn internal_server_error(message: impl fmt::Display) -> HttpError { eprintln!("Internal server error: {message}"); (Status::INTERNAL_SERVER_ERROR, message.to_string()) } fn loader_to_http_error(e: LoaderError) -> HttpError { match e { LoaderError::Parsing(e) => bad_request(e), LoaderError::Storage(e) => internal_server_error(e), LoaderError::InvalidBaseIri { .. } => bad_request(e), } } /// Hacky tool to allow implementing read on top of a write loop struct ReadForWrite io::Result>)> { buffer: Rc>>, position: usize, add_more_data: U, state: Option, } impl io::Result>) + 'static> ReadForWrite { fn build_response( initial_state_builder: impl FnOnce(ReadForWriteWriter) -> io::Result, add_more_data: U, content_type: &'static str, ) -> Result { let buffer = Rc::new(RefCell::new(Vec::new())); let state = initial_state_builder(ReadForWriteWriter { buffer: Rc::clone(&buffer), }) .map_err(internal_server_error)?; Ok(Response::builder(Status::OK) .with_header(HeaderName::CONTENT_TYPE, content_type) .map_err(internal_server_error)? .with_body(Body::from_read(Self { buffer, position: 0, add_more_data, state: Some(state), }))) } } impl io::Result>)> Read for ReadForWrite { fn read(&mut self, buf: &mut [u8]) -> io::Result { while self.position == self.buffer.borrow().len() { // We read more data if let Some(state) = self.state.take() { self.buffer.borrow_mut().clear(); self.position = 0; self.state = match (self.add_more_data)(state) { Ok(state) => state, Err(e) => { eprintln!("Internal server error while streaming results: {e}"); self.buffer .borrow_mut() .write_all(e.to_string().as_bytes())?; None } } } else { return Ok(0); // End } } let buffer = self.buffer.borrow(); let len = min(buffer.len() - self.position, buf.len()); buf[..len].copy_from_slice(&buffer[self.position..self.position + len]); self.position += len; Ok(len) } } struct ReadForWriteWriter { buffer: Rc>>, } impl Write for ReadForWriteWriter { fn write(&mut self, buf: &[u8]) -> io::Result { self.buffer.borrow_mut().write(buf) } fn flush(&mut self) -> io::Result<()> { Ok(()) } fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { self.buffer.borrow_mut().write_all(buf) } } fn close_file_writer(writer: BufWriter) -> io::Result<()> { let mut file = writer .into_inner() .map_err(io::IntoInnerError::into_error)?; file.flush()?; file.sync_all() } #[cfg(target_os = "linux")] fn systemd_notify_ready() -> io::Result<()> { if let Some(path) = env::var_os("NOTIFY_SOCKET") { UnixDatagram::unbound()?.send_to(b"READY=1", path)?; } Ok(()) } #[cfg(test)] #[allow(clippy::panic_in_result_fn)] mod tests { use super::*; use anyhow::Result; use assert_cmd::Command; use assert_fs::prelude::*; use assert_fs::{NamedTempFile, TempDir}; use flate2::write::GzEncoder; use flate2::Compression; use predicates::prelude::*; use std::fs::remove_dir_all; use std::io::read_to_string; fn cli_command() -> Command { let mut command = Command::new(env!("CARGO")); command .arg("run") .arg("--bin") .arg("oxigraph") .arg("--no-default-features"); #[cfg(feature = "rocksdb-pkg-config")] command.arg("--features").arg("rocksdb-pkg-config"); #[cfg(feature = "geosparql")] command.arg("--features").arg("geosparql"); command.arg("--"); command } fn initialized_cli_store(data: &'static str) -> Result { let store_dir = TempDir::new()?; cli_command() .arg("load") .arg("--location") .arg(store_dir.path()) .arg("--format") .arg("trig") .write_stdin(data) .assert() .success(); Ok(store_dir) } fn assert_cli_state(store_dir: &TempDir, data: &'static str) { cli_command() .arg("dump") .arg("--location") .arg(store_dir.path()) .arg("--format") .arg("nq") .assert() .stdout(data) .success(); } #[test] fn cli_help() { cli_command() .assert() .failure() .stdout("") .stderr(predicate::str::contains("Oxigraph")); } #[test] fn cli_load_optimize_and_dump_graph() -> Result<()> { let store_dir = TempDir::new()?; let input_file = NamedTempFile::new("input.ttl")?; input_file.write_str(" .")?; cli_command() .arg("load") .arg("--location") .arg(store_dir.path()) .arg("--file") .arg(input_file.path()) .arg("--base") .arg("http://example.com/") .assert() .success(); cli_command() .arg("optimize") .arg("--location") .arg(store_dir.path()) .assert() .success(); let output_file = NamedTempFile::new("output.nt")?; cli_command() .arg("dump") .arg("--location") .arg(store_dir.path()) .arg("--file") .arg(output_file.path()) .arg("--graph") .arg("default") .assert() .success(); output_file .assert(" .\n"); Ok(()) } #[test] fn cli_load_and_dump_dataset() -> Result<()> { let store_dir = TempDir::new()?; let input_file = NamedTempFile::new("input.nq")?; input_file .write_str(" .")?; cli_command() .arg("load") .arg("--location") .arg(store_dir.path()) .arg("--file") .arg(input_file.path()) .assert() .success(); let output_file = NamedTempFile::new("output.nq")?; cli_command() .arg("dump") .arg("--location") .arg(store_dir.path()) .arg("--file") .arg(output_file.path()) .assert() .success(); output_file .assert(" .\n"); Ok(()) } #[test] fn cli_load_gzip_dataset() -> Result<()> { let store_dir = TempDir::new()?; let file = NamedTempFile::new("sample.nq.gz")?; let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); encoder .write_all(b" .")?; file.write_binary(&encoder.finish()?)?; cli_command() .arg("load") .arg("-l") .arg(store_dir.path()) .arg("-f") .arg(file.path()) .assert() .success(); cli_command() .arg("dump") .arg("-l") .arg(store_dir.path()) .arg("--format") .arg("nq") .assert() .success() .stdout(" .\n"); Ok(()) } #[test] fn cli_load_and_dump_named_graph() -> Result<()> { let store_dir = TempDir::new()?; let input_file = NamedTempFile::new("input.nt")?; input_file.write_str( " .\n", )?; cli_command() .arg("load") .arg("-l") .arg(store_dir.path()) .arg("-f") .arg(input_file.path()) .arg("--graph") .arg("http://example.com/g") .assert() .success(); let output_file = NamedTempFile::new("output.nt")?; cli_command() .arg("dump") .arg("-l") .arg(store_dir.path()) .arg("-f") .arg(output_file.path()) .arg("--graph") .arg("http://example.com/g") .assert() .success(); output_file .assert(" .\n"); Ok(()) } #[test] fn cli_load_and_dump_with_format() -> Result<()> { let store_dir = TempDir::new()?; let input_file = NamedTempFile::new("input")?; input_file .write_str(" .")?; cli_command() .arg("load") .arg("-l") .arg(store_dir.path()) .arg("-f") .arg(input_file.path()) .arg("--format") .arg("nt") .assert() .success(); let output_file = NamedTempFile::new("output")?; cli_command() .arg("dump") .arg("--location") .arg(store_dir.path()) .arg("--file") .arg(output_file.path()) .arg("--graph") .arg("default") .arg("--format") .arg("nt") .assert() .success(); output_file .assert(" .\n"); Ok(()) } #[test] fn cli_load_from_stdin_and_dump_to_stdout() -> Result<()> { let store_dir = TempDir::new()?; cli_command() .arg("load") .arg("--location") .arg(store_dir.path()) .arg("--format") .arg("nq") .write_stdin(" .") .assert() .success(); cli_command() .arg("dump") .arg("--location") .arg(store_dir.path()) .arg("--format") .arg("nq") .assert() .success() .stdout(" .\n"); Ok(()) } #[test] fn cli_backup() -> Result<()> { let store_dir = initialized_cli_store( " .", )?; let backup_dir = TempDir::new()?; remove_dir_all(backup_dir.path())?; // The directory should not exist yet cli_command() .arg("backup") .arg("--location") .arg(store_dir.path()) .arg("--destination") .arg(backup_dir.path()) .assert() .success(); assert_cli_state( &store_dir, " .\n", ); Ok(()) } #[test] fn cli_ask_query_inline() -> Result<()> { let store_dir = initialized_cli_store( " .", )?; cli_command() .arg("query") .arg("--location") .arg(store_dir.path()) .arg("--query") .arg("ASK {

}") .arg("--query-base") .arg("http://example.com/") .arg("--results-format") .arg("csv") .assert() .stdout("true") .success(); Ok(()) } #[test] fn cli_construct_query_stdin() -> Result<()> { let store_dir = initialized_cli_store( " .", )?; cli_command() .arg("query") .arg("--location") .arg(store_dir.path()) .arg("--query-base") .arg("http://example.com/") .arg("--results-format") .arg("nt") .write_stdin("CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }") .assert() .stdout(" .\n") .success(); Ok(()) } #[test] fn cli_select_query_file() -> Result<()> { let store_dir = initialized_cli_store( " .", )?; let input_file = NamedTempFile::new("input.rq")?; input_file.write_str("SELECT ?s WHERE { ?s ?p ?o }")?; let output_file = NamedTempFile::new("output.tsv")?; cli_command() .arg("query") .arg("--location") .arg(store_dir.path()) .arg("--query-file") .arg(input_file.path()) .arg("--results-file") .arg(output_file.path()) .assert() .success(); output_file.assert("?s\n\n"); Ok(()) } #[test] fn cli_ask_union_default_graph() -> Result<()> { let store_dir = initialized_cli_store( "GRAPH { }", )?; cli_command() .arg("query") .arg("--location") .arg(store_dir.path()) .arg("--query") .arg("ASK { ?s ?p ?o }") .arg("--results-format") .arg("tsv") .arg("--union-default-graph") .assert() .stdout("true") .success(); Ok(()) } #[test] fn cli_update_inline() -> Result<()> { let store_dir = TempDir::new()?; cli_command() .arg("update") .arg("--location") .arg(store_dir.path()) .arg("--update") .arg("INSERT DATA {

}") .arg("--update-base") .arg("http://example.com/") .assert() .success(); assert_cli_state( &store_dir, " .\n", ); Ok(()) } #[test] fn cli_construct_update_stdin() -> Result<()> { let store_dir = TempDir::new()?; cli_command() .arg("update") .arg("--location") .arg(store_dir.path()) .arg("--update-base") .arg("http://example.com/") .write_stdin("INSERT DATA {

}") .assert() .success(); assert_cli_state( &store_dir, " .\n", ); Ok(()) } #[test] fn cli_update_file() -> Result<()> { let store_dir = TempDir::new()?; let input_file = NamedTempFile::new("input.rq")?; input_file.write_str( "INSERT DATA { }", )?; cli_command() .arg("update") .arg("--location") .arg(store_dir.path()) .arg("--update-file") .arg(input_file.path()) .assert() .success(); assert_cli_state( &store_dir, " .\n", ); Ok(()) } #[test] fn cli_convert_file() -> Result<()> { let input_file = NamedTempFile::new("input.ttl")?; input_file.write_str("@prefix schema: .\n<#me> a schema:Person ;\n\tschema:name \"Foo Bar\"@en .\n")?; let output_file = NamedTempFile::new("output.rdf")?; cli_command() .arg("convert") .arg("--from-file") .arg(input_file.path()) .arg("--from-base") .arg("http://example.com/") .arg("--to-file") .arg(output_file.path()) .assert() .success(); output_file .assert("\n\n\t\n\t\tFoo Bar\n\t\n"); Ok(()) } #[test] fn cli_convert_from_default_graph_to_named_graph() { cli_command() .arg("convert") .arg("--from-format") .arg("trig") .arg("--to-format") .arg("nq") .arg("--from-default-graph") .arg("--to-graph") .arg("http://example.com/t") .write_stdin("@base .

. { . }") .assert() .stdout(" .\n") .success(); } #[test] fn cli_convert_from_named_graph() { cli_command() .arg("convert") .arg("--from-format") .arg("trig") .arg("--to-format") .arg("nq") .arg("--from-graph") .arg("http://example.com/g") .write_stdin("@base .

. { . }") .assert() .stdout(" .\n"); } #[test] fn cli_convert_to_base() { cli_command() .arg("convert") .arg("--from-format") .arg("ttl") .arg("--to-format") .arg("ttl") .arg("--to-base") .arg("http://example.com") .write_stdin("@base .

.") .assert() .stdout("@base .\n

.\n"); } #[test] fn get_ui() -> Result<()> { ServerTest::new()?.test_status( Request::builder(Method::GET, "http://localhost/".parse()?).build(), Status::OK, ) } #[test] fn post_dataset_file() -> Result<()> { let request = Request::builder(Method::POST, "http://localhost/store".parse()?) .with_header(HeaderName::CONTENT_TYPE, "application/trig")? .with_body(" ."); ServerTest::new()?.test_status(request, Status::NO_CONTENT) } #[test] fn post_wrong_file() -> Result<()> { let request = Request::builder(Method::POST, "http://localhost/store".parse()?) .with_header(HeaderName::CONTENT_TYPE, "application/trig")? .with_body(""); ServerTest::new()?.test_status(request, Status::BAD_REQUEST) } #[test] fn post_unsupported_file() -> Result<()> { let request = Request::builder(Method::POST, "http://localhost/store".parse()?) .with_header(HeaderName::CONTENT_TYPE, "text/foo")? .build(); ServerTest::new()?.test_status(request, Status::UNSUPPORTED_MEDIA_TYPE) } #[test] fn get_query() -> Result<()> { let server = ServerTest::new()?; let request = Request::builder(Method::POST, "http://localhost/store".parse()?) .with_header(HeaderName::CONTENT_TYPE, "application/trig")? .with_body(" ."); server.test_status(request, Status::NO_CONTENT)?; let request = Request::builder( Method::GET, "http://localhost/query?query=SELECT%20?s%20?p%20?o%20WHERE%20{%20?s%20?p%20?o%20}" .parse()?, ) .with_header(HeaderName::ACCEPT, "text/csv")? .build(); server.test_body( request, "s,p,o\r\nhttp://example.com,http://example.com,http://example.com\r\n", ) } #[test] fn get_query_accept_star() -> Result<()> { let request = Request::builder( Method::GET, "http://localhost/query?query=SELECT%20?s%20?p%20?o%20WHERE%20{%20?s%20?p%20?o%20}" .parse()?, ) .with_header(HeaderName::ACCEPT, "*/*")? .build(); ServerTest::new()?.test_body( request, r#"{"head":{"vars":["s","p","o"]},"results":{"bindings":[]}}"#, ) } #[test] fn get_query_accept_substar() -> Result<()> { let request = Request::builder( Method::GET, "http://localhost/query?query=SELECT%20?s%20?p%20?o%20WHERE%20{%20?s%20?p%20?o%20}" .parse()?, ) .with_header(HeaderName::ACCEPT, "text/*")? .build(); ServerTest::new()?.test_body( request, r#"{"head":{"vars":["s","p","o"]},"results":{"bindings":[]}}"#, ) } #[test] fn get_query_accept_good() -> Result<()> { let request = Request::builder( Method::GET, "http://localhost/query?query=SELECT%20?s%20?p%20?o%20WHERE%20{%20?s%20?p%20?o%20}" .parse()?, ) .with_header( HeaderName::ACCEPT, "application/sparql-results+json;charset=utf-8", )? .build(); ServerTest::new()?.test_body( request, r#"{"head":{"vars":["s","p","o"]},"results":{"bindings":[]}}"#, ) } #[test] fn get_query_accept_bad() -> Result<()> { let request = Request::builder( Method::GET, "http://localhost/query?query=SELECT%20?s%20?p%20?o%20WHERE%20{%20?s%20?p%20?o%20}" .parse()?, ) .with_header(HeaderName::ACCEPT, "application/foo")? .build(); ServerTest::new()?.test_status(request, Status::NOT_ACCEPTABLE) } #[test] fn get_query_accept_explicit_priority() -> Result<()> { let request = Request::builder( Method::GET, "http://localhost/query?query=SELECT%20?s%20?p%20?o%20WHERE%20{%20?s%20?p%20?o%20}" .parse()?, ) .with_header(HeaderName::ACCEPT, "text/foo;q=0.5 , text/json ; q = 0.7")? .build(); ServerTest::new()?.test_body( request, r#"{"head":{"vars":["s","p","o"]},"results":{"bindings":[]}}"#, ) } #[test] fn get_query_accept_implicit_priority() -> Result<()> { let request = Request::builder( Method::GET, "http://localhost/query?query=SELECT%20?s%20?p%20?o%20WHERE%20{%20?s%20?p%20?o%20}" .parse()?, ) .with_header(HeaderName::ACCEPT, "text/json,text/foo")? .build(); ServerTest::new()?.test_body( request, r#"{"head":{"vars":["s","p","o"]},"results":{"bindings":[]}}"#, ) } #[test] fn get_query_accept_implicit_and_explicit_priority() -> Result<()> { let request = Request::builder( Method::GET, "http://localhost/query?query=SELECT%20?s%20?p%20?o%20WHERE%20{%20?s%20?p%20?o%20}" .parse()?, ) .with_header(HeaderName::ACCEPT, "text/foo;q=0.9,text/csv")? .build(); ServerTest::new()?.test_body(request, "s,p,o\r\n") } #[test] fn get_bad_query() -> Result<()> { ServerTest::new()?.test_status( Request::builder(Method::GET, "http://localhost/query?query=SELECT".parse()?).build(), Status::BAD_REQUEST, ) } #[test] fn get_query_union_graph() -> Result<()> { let server = ServerTest::new()?; let request = Request::builder(Method::PUT, "http://localhost/store/1".parse()?) .with_header(HeaderName::CONTENT_TYPE, "text/turtle")? .with_body(" ."); server.test_status(request, Status::CREATED)?; let request = Request::builder( Method::GET, "http://localhost/query?query=SELECT%20?s%20?p%20?o%20WHERE%20{%20?s%20?p%20?o%20}&union-default-graph" .parse() ?, ).with_header(HeaderName::ACCEPT, "text/csv") ? .build(); server.test_body( request, "s,p,o\r\nhttp://example.com,http://example.com,http://example.com\r\n", ) } #[test] fn get_query_union_graph_in_url_and_urlencoded() -> Result<()> { let server = ServerTest::new()?; let request = Request::builder(Method::PUT, "http://localhost/store/1".parse()?) .with_header(HeaderName::CONTENT_TYPE, "text/turtle")? .with_body(" ."); server.test_status(request, Status::CREATED)?; let request = Request::builder( Method::POST, "http://localhost/query?union-default-graph".parse()?, ) .with_header( HeaderName::CONTENT_TYPE, "application/x-www-form-urlencoded", )? .with_header(HeaderName::ACCEPT, "text/csv")? .with_body("query=SELECT%20?s%20?p%20?o%20WHERE%20{%20?s%20?p%20?o%20}"); server.test_body( request, "s,p,o\r\nhttp://example.com,http://example.com,http://example.com\r\n", ) } #[test] fn get_query_union_graph_and_default_graph() -> Result<()> { ServerTest::new()?.test_status(Request::builder( Method::GET, "http://localhost/query?query=SELECT%20*%20WHERE%20{%20?s%20?p%20?o%20}&union-default-graph&default-graph-uri=http://example.com".parse() ?, ).build(), Status::BAD_REQUEST) } #[test] fn get_query_description() -> Result<()> { ServerTest::new()?.test_status( Request::builder(Method::GET, "http://localhost/query".parse()?).build(), Status::OK, ) } #[test] fn post_query() -> Result<()> { let request = Request::builder(Method::POST, "http://localhost/query".parse()?) .with_header(HeaderName::CONTENT_TYPE, "application/sparql-query")? .with_body("SELECT * WHERE { ?s ?p ?o }"); ServerTest::new()?.test_status(request, Status::OK) } #[test] fn post_bad_query() -> Result<()> { let request = Request::builder(Method::POST, "http://localhost/query".parse()?) .with_header(HeaderName::CONTENT_TYPE, "application/sparql-query")? .with_body("SELECT"); ServerTest::new()?.test_status(request, Status::BAD_REQUEST) } #[test] fn post_unknown_query() -> Result<()> { let request = Request::builder(Method::POST, "http://localhost/query".parse()?) .with_header(HeaderName::CONTENT_TYPE, "application/sparql-todo")? .with_body("SELECT"); ServerTest::new()?.test_status(request, Status::UNSUPPORTED_MEDIA_TYPE) } #[test] fn post_federated_query_wikidata() -> Result<()> { let request = Request::builder(Method::POST, "http://localhost/query".parse()?) .with_header(HeaderName::CONTENT_TYPE, "application/sparql-query") ?.with_body("SELECT * WHERE { SERVICE { ?p ?o } }"); ServerTest::new()?.test_status(request, Status::OK) } #[test] fn post_federated_query_dbpedia() -> Result<()> { let request = Request::builder(Method::POST, "http://localhost/query".parse()?) .with_header(HeaderName::CONTENT_TYPE, "application/sparql-query") ?.with_body("SELECT * WHERE { SERVICE { ?p ?o } }"); ServerTest::new()?.test_status(request, Status::OK) } #[test] fn get_update_description() -> Result<()> { ServerTest::new()?.test_status( Request::builder(Method::GET, "http://localhost/update".parse()?).build(), Status::OK, ) } #[test] fn post_update() -> Result<()> { let request = Request::builder(Method::POST, "http://localhost/update".parse()?) .with_header(HeaderName::CONTENT_TYPE, "application/sparql-update")? .with_body( "INSERT DATA { }", ); ServerTest::new()?.test_status(request, Status::NO_CONTENT) } #[test] fn post_bad_update() -> Result<()> { let request = Request::builder(Method::POST, "http://localhost/update".parse()?) .with_header(HeaderName::CONTENT_TYPE, "application/sparql-update")? .with_body("INSERT"); ServerTest::new()?.test_status(request, Status::BAD_REQUEST) } #[test] fn post_update_read_only() -> Result<()> { let request = Request::builder(Method::POST, "http://localhost/update".parse()?) .with_header(HeaderName::CONTENT_TYPE, "application/sparql-update")? .with_body( "INSERT DATA { }", ); ServerTest::check_status( ServerTest::new()?.exec_read_only(request), Status::FORBIDDEN, ) } #[test] fn graph_store_url_normalization() -> Result<()> { let server = ServerTest::new()?; // PUT let request = Request::builder( Method::PUT, "http://localhost/store?graph=http://example.com".parse()?, ) .with_header(HeaderName::CONTENT_TYPE, "text/turtle")? .with_body(" ."); server.test_status(request, Status::CREATED)?; // GET good URI server.test_status( Request::builder( Method::GET, "http://localhost/store?graph=http://example.com".parse()?, ) .build(), Status::OK, )?; // GET bad URI server.test_status( Request::builder( Method::GET, "http://localhost/store?graph=http://example.com/".parse()?, ) .build(), Status::NOT_FOUND, ) } #[test] fn graph_store_base_url() -> Result<()> { let server = ServerTest::new()?; // POST let request = Request::builder( Method::POST, "http://localhost/store?graph=http://example.com".parse()?, ) .with_header(HeaderName::CONTENT_TYPE, "text/turtle")? .with_body("<> ."); server.test_status(request, Status::NO_CONTENT)?; // GET let request = Request::builder( Method::GET, "http://localhost/store?graph=http://example.com".parse()?, ) .with_header(HeaderName::ACCEPT, "application/n-triples")? .build(); server.test_body( request, " .\n", )?; // PUT let request = Request::builder( Method::PUT, "http://localhost/store?graph=http://example.com".parse()?, ) .with_header(HeaderName::CONTENT_TYPE, "text/turtle")? .with_body("<> ."); server.test_status(request, Status::NO_CONTENT)?; // GET let request = Request::builder( Method::GET, "http://localhost/store?graph=http://example.com".parse()?, ) .with_header(HeaderName::ACCEPT, "application/n-triples")? .build(); server.test_body( request, " .\n", ) } #[test] fn graph_store_protocol() -> Result<()> { // Tests from https://www.w3.org/2009/sparql/docs/tests/data-sparql11/http-rdf-update/ let server = ServerTest::new()?; // PUT - Initial state let request = Request::builder(Method::PUT, "http://localhost/store/person/1.ttl".parse()?) .with_header(HeaderName::CONTENT_TYPE, "text/turtle; charset=utf-8")? .with_body( r#" @prefix foaf: . @prefix v: . a foaf:Person; foaf:businessCard [ a v:VCard; v:fn "John Doe" ]. "#, ); server.test_status(request, Status::CREATED)?; // GET of PUT - Initial state let request = Request::builder( Method::GET, "http://localhost/store?graph=/store/person/1.ttl".parse()?, ) .with_header(HeaderName::ACCEPT, "text/turtle")? .build(); server.test_status(request, Status::OK)?; // HEAD on an existing graph server.test_status( Request::builder(Method::HEAD, "http://localhost/store/person/1.ttl".parse()?).build(), Status::OK, )?; // HEAD on a non-existing graph server.test_status( Request::builder(Method::HEAD, "http://localhost/store/person/4.ttl".parse()?).build(), Status::NOT_FOUND, )?; // PUT - graph already in store let request = Request::builder(Method::PUT, "http://localhost/store/person/1.ttl".parse()?) .with_header(HeaderName::CONTENT_TYPE, "text/turtle; charset=utf-8")? .with_body( r#" @prefix foaf: . @prefix v: . a foaf:Person; foaf:businessCard [ a v:VCard; v:fn "Jane Doe" ]. "#, ); server.test_status(request, Status::NO_CONTENT)?; // GET of PUT - graph already in store let request = Request::builder(Method::GET, "http://localhost/store/person/1.ttl".parse()?) .with_header(HeaderName::ACCEPT, "text/turtle")? .build(); server.test_status(request, Status::OK)?; // PUT - default graph let request = Request::builder(Method::PUT, "http://localhost/store?default".parse()?) .with_header(HeaderName::CONTENT_TYPE, "text/turtle; charset=utf-8")? .with_body( r#" @prefix foaf: . @prefix v: . [] a foaf:Person; foaf:businessCard [ a v:VCard; v:given-name "Alice" ] . "#, ); server.test_status(request, Status::NO_CONTENT)?; // The default graph always exists in Oxigraph // GET of PUT - default graph let request = Request::builder(Method::GET, "http://localhost/store?default".parse()?) .with_header(HeaderName::ACCEPT, "text/turtle")? .build(); server.test_status(request, Status::OK)?; // PUT - mismatched payload let request = Request::builder(Method::PUT, "http://localhost/store/person/1.ttl".parse()?) .with_header(HeaderName::CONTENT_TYPE, "text/turtle; charset=utf-8")? .with_body("@prefix foo"); server.test_status(request, Status::BAD_REQUEST)?; // PUT - empty graph let request = Request::builder(Method::PUT, "http://localhost/store/person/2.ttl".parse()?) .with_header(HeaderName::CONTENT_TYPE, "text/turtle; charset=utf-8")? .build(); server.test_status(request, Status::CREATED)?; // GET of PUT - empty graph let request = Request::builder(Method::GET, "http://localhost/store/person/2.ttl".parse()?) .with_header(HeaderName::ACCEPT, "text/turtle")? .build(); server.test_status(request, Status::OK)?; // PUT - replace empty graph let request = Request::builder(Method::PUT, "http://localhost/store/person/2.ttl".parse()?) .with_header(HeaderName::CONTENT_TYPE, "text/turtle; charset=utf-8")? .with_body( r#" @prefix foaf: . @prefix v: . [] a foaf:Person; foaf:businessCard [ a v:VCard; v:given-name "Alice" ] . "#, ); server.test_status(request, Status::NO_CONTENT)?; // GET of replacement for empty graph let request = Request::builder(Method::GET, "http://localhost/store/person/2.ttl".parse()?) .with_header(HeaderName::ACCEPT, "text/turtle")? .build(); server.test_status(request, Status::OK)?; // DELETE - existing graph server.test_status( Request::builder( Method::DELETE, "http://localhost/store/person/2.ttl".parse()?, ) .build(), Status::NO_CONTENT, )?; // GET of DELETE - existing graph server.test_status( Request::builder(Method::GET, "http://localhost/store/person/2.ttl".parse()?).build(), Status::NOT_FOUND, )?; // DELETE - non-existent graph server.test_status( Request::builder( Method::DELETE, "http://localhost/store/person/2.ttl".parse()?, ) .build(), Status::NOT_FOUND, )?; // POST - existing graph let request = Request::builder(Method::PUT, "http://localhost/store/person/1.ttl".parse()?) .with_header(HeaderName::CONTENT_TYPE, "text/turtle; charset=utf-8")? .build(); server.test_status(request, Status::NO_CONTENT)?; // TODO: POST - multipart/form-data // TODO: GET of POST - multipart/form-data // POST - create new graph let request = Request::builder(Method::POST, "http://localhost/store".parse()?) .with_header(HeaderName::CONTENT_TYPE, "text/turtle; charset=utf-8")? .with_body( r#" @prefix foaf: . @prefix v: . [] a foaf:Person; foaf:businessCard [ a v:VCard; v:given-name "Alice" ] . "#, ); let response = server.exec(request); assert_eq!(response.status(), Status::CREATED); let location = response.header(&HeaderName::LOCATION).unwrap().to_str()?; // GET of POST - create new graph let request = Request::builder(Method::GET, location.parse()?) .with_header(HeaderName::ACCEPT, "text/turtle")? .build(); server.test_status(request, Status::OK)?; // POST - empty graph to existing graph let request = Request::builder(Method::PUT, location.parse()?) .with_header(HeaderName::CONTENT_TYPE, "text/turtle; charset=utf-8")? .build(); server.test_status(request, Status::NO_CONTENT)?; // GET of POST - after noop let request = Request::builder(Method::GET, location.parse()?) .with_header(HeaderName::ACCEPT, "text/turtle")? .build(); server.test_status(request, Status::OK) } #[test] fn graph_store_lenient_bulk() -> Result<()> { let server = ServerTest::new()?; let invalid_data = " @prefix foaf: . @prefix v: . a foaf:Person . foo"; // POST let request = Request::builder( Method::POST, "http://localhost/store/person/1.ttl?no_transaction&lenient".parse()?, ) .with_header(HeaderName::CONTENT_TYPE, "text/turtle; charset=utf-8")? .with_body(invalid_data); server.test_status(request, Status::NO_CONTENT)?; // GET of POST let request = Request::builder( Method::GET, "http://localhost/store?graph=/store/person/1.ttl".parse()?, ) .with_header(HeaderName::ACCEPT, "text/turtle")? .build(); server.test_status(request, Status::OK)?; // POST dataset let request = Request::builder( Method::POST, "http://localhost/store?lenient&no_transaction".parse()?, ) .with_header(HeaderName::CONTENT_TYPE, "application/trig; charset=utf-8")? .with_body(invalid_data); server.test_status(request, Status::NO_CONTENT)?; // GET of POST dataset let request = Request::builder(Method::GET, "http://localhost/store?default".parse()?) .with_header(HeaderName::ACCEPT, "text/turtle")? .build(); server.test_status(request, Status::OK)?; // PUT let request = Request::builder( Method::PUT, "http://localhost/store/person/1.ttl?lenient&no_transaction".parse()?, ) .with_header(HeaderName::CONTENT_TYPE, "text/turtle; charset=utf-8")? .with_body(invalid_data); server.test_status(request, Status::NO_CONTENT)?; // GET of PUT - Initial state let request = Request::builder( Method::GET, "http://localhost/store?graph=/store/person/1.ttl".parse()?, ) .with_header(HeaderName::ACCEPT, "text/turtle")? .build(); server.test_status(request, Status::OK)?; // PUT dataset let request = Request::builder( Method::PUT, "http://localhost/store?lenient&no_transaction".parse()?, ) .with_header(HeaderName::CONTENT_TYPE, "application/trig; charset=utf-8")? .with_body(invalid_data); server.test_status(request, Status::NO_CONTENT)?; // GET of PUT dataset let request = Request::builder(Method::GET, "http://localhost/store?default".parse()?) .with_header(HeaderName::ACCEPT, "text/turtle")? .build(); server.test_status(request, Status::OK)?; // GET of PUT dataset - replacement let request = Request::builder( Method::GET, "http://localhost/store?graph=/store/person/1.ttl".parse()?, ) .with_header(HeaderName::ACCEPT, "text/turtle")? .build(); server.test_status(request, Status::NOT_FOUND) } #[test] fn lenient_load() -> Result<()> { let server = ServerTest::new()?; // POST let request = Request::builder( Method::POST, "http://localhost/store?lenient&graph=http://example.com".parse()?, ) .with_header(HeaderName::CONTENT_TYPE, "text/turtle")? .with_body("< s> < p> \"\\uD83D\\uDC68\" ."); server.test_status(request, Status::NO_CONTENT)?; // GET let request = Request::builder( Method::GET, "http://localhost/store?graph=http://example.com".parse()?, ) .with_header(HeaderName::ACCEPT, "application/n-triples")? .build(); server.test_body( request, " \"\u{1f468}\" .\n", )?; // PUT let request = Request::builder( Method::PUT, "http://localhost/store?lenient&graph=http://example.com".parse()?, ) .with_header(HeaderName::CONTENT_TYPE, "text/turtle")? .with_body("< s> < p> \"\\uD83D\\uDC68\\u200D\\uD83D\\uDC69\\u200D\\uD83D\\uDC67\\u200D\\uD83D\\uDC67\" ."); server.test_status(request, Status::NO_CONTENT)?; // GET let request = Request::builder( Method::GET, "http://localhost/store?graph=http://example.com".parse()?, ) .with_header(HeaderName::ACCEPT, "application/n-triples")? .build(); server.test_body( request, " \"\u{1f468}\u{200d}\u{1f469}\u{200d}\u{1f467}\u{200d}\u{1f467}\" .\n", )?; // POST dataset let request = Request::builder(Method::POST, "http://localhost/store?lenient".parse()?) .with_header(HeaderName::CONTENT_TYPE, "application/trig")? .with_body("

\"\"@abcdefghijklmn ."); server.test_status(request, Status::NO_CONTENT)?; // GET let request = Request::builder(Method::GET, "http://localhost/store".parse()?) .with_header(HeaderName::ACCEPT, "application/n-quads")? .build(); server.test_body(request, "

\"\"@abcdefghijklmn .\n \"\u{1f468}\u{200d}\u{1f469}\u{200d}\u{1f467}\u{200d}\u{1f467}\" .\n") } struct ServerTest { store: Store, } impl ServerTest { fn new() -> Result { Ok(Self { store: Store::new()?, }) } fn exec(&self, mut request: Request) -> Response { handle_request(&mut request, self.store.clone(), false, false) .unwrap_or_else(|(status, message)| error(status, message)) } fn exec_read_only(&self, mut request: Request) -> Response { handle_request(&mut request, self.store.clone(), true, false) .unwrap_or_else(|(status, message)| error(status, message)) } fn test_status(&self, request: Request, expected_status: Status) -> Result<()> { Self::check_status(self.exec(request), expected_status) } fn check_status(mut response: Response, expected_status: Status) -> Result<()> { let body = read_to_string(response.body_mut())?; assert_eq!(response.status(), expected_status, "Error message: {body}"); Ok(()) } fn test_body(&self, request: Request, expected_body: &str) -> Result<()> { let mut response = self.exec(request); let body = read_to_string(response.body_mut())?; assert_eq!(response.status(), Status::OK, "Error message: {body}"); assert_eq!(&body, expected_body); Ok(()) } } #[test] fn clap_debug() { use clap::CommandFactory; Args::command().debug_assert() } } oxigraph-oxigraph-68d45f7/cli/src/service_description.rs000066400000000000000000000122221476336702400235050ustar00rootroot00000000000000use oxigraph::io::{RdfFormat, RdfSerializer}; use oxigraph::model::vocab::rdf; use oxigraph::model::{BlankNode, NamedNodeRef, TripleRef}; use oxigraph::sparql::results::QueryResultsFormat; mod sd { use oxigraph::model::NamedNodeRef; pub const SERVICE: NamedNodeRef<'_> = NamedNodeRef::new_unchecked("http://www.w3.org/ns/sparql-service-description#Service"); pub const DEFAULT_ENTAILMENT_REGIME: NamedNodeRef<'_> = NamedNodeRef::new_unchecked( "http://www.w3.org/ns/sparql-service-description#defaultEntailmentRegime", ); pub const ENDPOINT: NamedNodeRef<'_> = NamedNodeRef::new_unchecked("http://www.w3.org/ns/sparql-service-description#endpoint"); pub const EXTENSION_FUNCTION: NamedNodeRef<'_> = NamedNodeRef::new_unchecked( "http://www.w3.org/ns/sparql-service-description#extensionFunction", ); pub const FEATURE: NamedNodeRef<'_> = NamedNodeRef::new_unchecked("http://www.w3.org/ns/sparql-service-description#feature"); pub const RESULT_FORMAT: NamedNodeRef<'_> = NamedNodeRef::new_unchecked("http://www.w3.org/ns/sparql-service-description#resultFormat"); pub const SUPPORTED_LANGUAGE: NamedNodeRef<'_> = NamedNodeRef::new_unchecked( "http://www.w3.org/ns/sparql-service-description#supportedLanguage", ); pub const EMPTY_GRAPHS: NamedNodeRef<'_> = NamedNodeRef::new_unchecked("http://www.w3.org/ns/sparql-service-description#EmptyGraphs"); pub const BASIC_FEDERATED_QUERY: NamedNodeRef<'_> = NamedNodeRef::new_unchecked( "http://www.w3.org/ns/sparql-service-description#BasicFederatedQuery", ); pub const SPARQL_10_QUERY: NamedNodeRef<'_> = NamedNodeRef::new_unchecked( "http://www.w3.org/ns/sparql-service-description#SPARQL10Query", ); pub const SPARQL_11_QUERY: NamedNodeRef<'_> = NamedNodeRef::new_unchecked( "http://www.w3.org/ns/sparql-service-description#SPARQL11Query", ); pub const SPARQL_11_UPDATE: NamedNodeRef<'_> = NamedNodeRef::new_unchecked( "http://www.w3.org/ns/sparql-service-description#SPARQL11Update", ); pub const UNION_DEFAULT_GRAPH: NamedNodeRef<'_> = NamedNodeRef::new_unchecked( "http://www.w3.org/ns/sparql-service-description#UnionDefaultGraph", ); } #[derive(Eq, PartialEq, Clone, Copy)] pub enum EndpointKind { Query, Update, } pub fn generate_service_description( format: RdfFormat, kind: EndpointKind, union_default_graph: bool, ) -> Vec { let mut graph = Vec::new(); let root = BlankNode::default(); graph.push(TripleRef::new(&root, rdf::TYPE, sd::SERVICE)); if matches!( format, RdfFormat::Turtle | RdfFormat::TriG | RdfFormat::N3 | RdfFormat::RdfXml ) { // Hack: we use the default base IRI ie. the IRI from which the file is served graph.push(TripleRef::new( &root, sd::ENDPOINT, NamedNodeRef::new_unchecked(""), )); } for language in match kind { EndpointKind::Query => [sd::SPARQL_10_QUERY, sd::SPARQL_11_QUERY].as_slice(), EndpointKind::Update => [sd::SPARQL_11_UPDATE].as_slice(), } { graph.push(TripleRef::new(&root, sd::SUPPORTED_LANGUAGE, *language)); } if kind == EndpointKind::Query { for format in [ QueryResultsFormat::Json, QueryResultsFormat::Xml, QueryResultsFormat::Csv, QueryResultsFormat::Tsv, ] { graph.push(TripleRef::new( &root, sd::RESULT_FORMAT, NamedNodeRef::new_unchecked(format.iri()), )); } for format in [ RdfFormat::NTriples, RdfFormat::NQuads, RdfFormat::Turtle, RdfFormat::TriG, RdfFormat::N3, RdfFormat::RdfXml, ] { graph.push(TripleRef::new( &root, sd::RESULT_FORMAT, NamedNodeRef::new_unchecked(format.iri()), )); } } #[cfg(any( feature = "native-tls", feature = "rustls-native", feature = "rustls-webpki" ))] if kind == EndpointKind::Query { graph.push(TripleRef::new( &root, sd::FEATURE, sd::BASIC_FEDERATED_QUERY, )); } if kind == EndpointKind::Update { graph.push(TripleRef::new(&root, sd::FEATURE, sd::EMPTY_GRAPHS)); } if union_default_graph { graph.push(TripleRef::new(&root, sd::FEATURE, sd::UNION_DEFAULT_GRAPH)); } graph.push(TripleRef::new( &root, sd::DEFAULT_ENTAILMENT_REGIME, NamedNodeRef::new_unchecked("http://www.w3.org/ns/entailment/Simple"), )); #[cfg(feature = "geosparql")] for function_name in spargeo::GEOSPARQL_EXTENSION_FUNCTIONS { graph.push(TripleRef::new(&root, sd::EXTENSION_FUNCTION, function_name)); } let mut serializer = RdfSerializer::from_format(format) .with_prefix("sd", "http://www.w3.org/ns/sparql-service-description#") .unwrap() .for_writer(Vec::new()); for t in graph { serializer.serialize_triple(t).unwrap(); } serializer.finish().unwrap() } oxigraph-oxigraph-68d45f7/cli/templates/000077500000000000000000000000001476336702400203045ustar00rootroot00000000000000oxigraph-oxigraph-68d45f7/cli/templates/query.html000066400000000000000000000014751476336702400223460ustar00rootroot00000000000000 Oxigraph server

oxigraph-oxigraph-68d45f7/cli/templates/yasgui/000077500000000000000000000000001476336702400216055ustar00rootroot00000000000000oxigraph-oxigraph-68d45f7/clippy.toml000066400000000000000000000002111476336702400177260ustar00rootroot00000000000000avoid-breaking-exported-api = false cognitive-complexity-threshold = 50 too-many-arguments-threshold = 10 type-complexity-threshold = 500oxigraph-oxigraph-68d45f7/deny.toml000066400000000000000000000006221476336702400173730ustar00rootroot00000000000000[advisories] ignore = ["RUSTSEC-2018-0015"] [licenses] allow = [ "Apache-2.0", "Apache-2.0 WITH LLVM-exception", "BSD-3-Clause", "ISC", "MIT", "OpenSSL", "Unicode-3.0", "Zlib" ] [[licenses.clarify]] name = "ring" version = "*" expression = "MIT AND ISC AND OpenSSL" license-files = [ { path = "LICENSE", hash = 0xbd0eed23 } ] [sources] unknown-registry = "deny" oxigraph-oxigraph-68d45f7/docs/000077500000000000000000000000001476336702400164675ustar00rootroot00000000000000oxigraph-oxigraph-68d45f7/docs/arch-diagram.svg000066400000000000000000000121001476336702400215210ustar00rootroot00000000000000 oxigraph CLI pyoxigraph oxigraph JS oxigraph Rust oxrdfio spareval oxrdfio sparopt oxttl oxrdfxml spargebra sparesults oxrdf oxsdatatypes oxigraph-oxigraph-68d45f7/docs/arch-diagram.txt000066400000000000000000000027271476336702400215570ustar00rootroot00000000000000+------------------+ +----------------+ +-----------------+ + oxigraph CLI {r} + + pyoxigraph {p} + + oxigraph JS {j} + +------------------+ +----------------+ +-----------------+ +---------------------------------------------------------------------------+ + oxigraph (Rust) {r} + +---------------------------------------------------------------------------+ +----------------------------+ +----------------------------+ + oxrdfio {r} + + spareval {r} + +----------------------------+ +----------------------------+ +----------------------------+ +-------------+ + oxrdfio {r} + + sparopt {r} + +----------------------------+ +-------------+ +-----------+ +--------------+ +-----------------+ +----------------+ + oxttl {r} + + oxrdfxml {r} + + spargebra {r} + + sparesults {r} + +-----------+ +--------------+ +-----------------+ +----------------+ +-----------------------------------------------------------------------+ + oxrdf {r} + +-----------------------------------------------------------------------+ +------------------+ + oxsdatatypes {r} + +------------------+ # Legend: r = { fill: papayawhip; } p = { fill: lightyellow; } j = { fill: lightgreen; } oxigraph-oxigraph-68d45f7/fuzz/000077500000000000000000000000001476336702400165355ustar00rootroot00000000000000oxigraph-oxigraph-68d45f7/fuzz/.gitignore000066400000000000000000000000271476336702400205240ustar00rootroot00000000000000target corpus artifactsoxigraph-oxigraph-68d45f7/fuzz/Cargo.toml000066400000000000000000000030511476336702400204640ustar00rootroot00000000000000[package] name = "oxigraph-fuzz" version = "0.0.0" publish = false edition = "2021" [package.metadata] cargo-fuzz = true [features] rocksdb = ["oxigraph/rocksdb"] [dependencies] anyhow = "1.0.72" libfuzzer-sys = "0.4" oxiri = "0.2" oxigraph = { path = "../lib/oxigraph", default-features = false } oxrdf = { path = "../lib/oxrdf", features = ["rdf-star"] } oxrdfxml.path = "../lib/oxrdfxml" oxttl = { path = "../lib/oxttl", features = ["rdf-star"] } sparesults = { path = "../lib/sparesults", features = ["rdf-star"] } spargebra = { path = "../lib/spargebra", features = ["rdf-star", "sep-0006"] } spareval = { path = "../lib/spareval", features = ["rdf-star", "sep-0006"] } sparql-smith = { path = "../lib/sparql-smith", features = ["sep-0006"] } [profile.release] codegen-units = 1 debug = true [workspace] [[bin]] name = "nquads" path = "fuzz_targets/nquads.rs" [[bin]] name = "n3" path = "fuzz_targets/n3.rs" [[bin]] name = "rdf_xml" path = "fuzz_targets/rdf_xml.rs" [[bin]] name = "sparql_query" path = "fuzz_targets/sparql_query.rs" [[bin]] name = "sparql_query_eval" path = "fuzz_targets/sparql_query_eval.rs" [[bin]] name = "sparql_update" path = "fuzz_targets/sparql_update.rs" [[bin]] name = "sparql_update_eval" path = "fuzz_targets/sparql_update_eval.rs" [[bin]] name = "sparql_results_json" path = "fuzz_targets/sparql_results_json.rs" [[bin]] name = "sparql_results_xml" path = "fuzz_targets/sparql_results_xml.rs" [[bin]] name = "sparql_results_tsv" path = "fuzz_targets/sparql_results_tsv.rs" [[bin]] name = "trig" path = "fuzz_targets/trig.rs" oxigraph-oxigraph-68d45f7/fuzz/build_corpus.py000066400000000000000000000015741476336702400216100ustar00rootroot00000000000000import hashlib import random from pathlib import Path base = Path(__file__).parent.parent for target, ext in [ ("sparql_query", "rq"), ("sparql_update", "ru"), ("sparql_results_xml", "srx"), ("sparql_results_json", "srj"), ("sparql_results_tsv", "tsv"), ("n3", "n3"), ("nquads", "nq"), ("trig", "trig"), ("rdf_xml", "rdf"), ]: target_dir = base / "fuzz" / "corpus" / target for f in base.rglob(f"*.{ext}"): if "manifest" in str(f): continue # we skip the manifests with f.open("rb") as fp: data = fp.read() pos = random.randint(0, len(data)) data = data[:pos] + b"\xff" + data[pos:] hash = hashlib.sha256() hash.update(data) target_dir.mkdir(parents=True, exist_ok=True) with (target_dir / f"{hash.hexdigest()}").open("wb") as fp: fp.write(data) oxigraph-oxigraph-68d45f7/fuzz/fuzz_targets/000077500000000000000000000000001476336702400212645ustar00rootroot00000000000000oxigraph-oxigraph-68d45f7/fuzz/fuzz_targets/n3.rs000066400000000000000000000012771476336702400221610ustar00rootroot00000000000000#![no_main] use libfuzzer_sys::fuzz_target; use oxttl::N3Parser; fuzz_target!(|data: &[u8]| { let mut quads = Vec::new(); let mut parser = N3Parser::new() .with_base_iri("http://example.com/") .unwrap() .low_level(); for chunk in data.split(|c| *c == 0xFF) { parser.extend_from_slice(chunk); while let Some(result) = parser.parse_next() { if let Ok(quad) = result { quads.push(quad); } } } parser.end(); while let Some(result) = parser.parse_next() { if let Ok(quad) = result { quads.push(quad); } } assert!(parser.is_end()); // TODO: serialize }); oxigraph-oxigraph-68d45f7/fuzz/fuzz_targets/nquads.rs000066400000000000000000000066611476336702400231360ustar00rootroot00000000000000#![no_main] use libfuzzer_sys::fuzz_target; use oxrdf::Quad; use oxttl::{NQuadsParser, NQuadsSerializer}; use std::str; use std::str::FromStr; fn parse<'a>( chunks: impl IntoIterator, unchecked: bool, ) -> (Vec, Vec) { let mut quads = Vec::new(); let mut errors = Vec::new(); let mut parser = NQuadsParser::new().with_quoted_triples(); if unchecked { parser = parser.unchecked(); } let mut parser = parser.low_level(); for chunk in chunks { parser.extend_from_slice(chunk); while let Some(result) = parser.parse_next() { match result { Ok(quad) => quads.push(quad), Err(error) => errors.push(error.to_string()), } } } parser.end(); while let Some(result) = parser.parse_next() { match result { Ok(quad) => quads.push(quad), Err(error) => errors.push(error.to_string()), } } assert!(parser.is_end()); (quads, errors) } fuzz_target!(|data: &[u8]| { // We parse with splitting let (quads, errors) = parse(data.split(|c| *c == 0xFF), false); // We parse without splitting let data_without_breaks = data .iter() .copied() .filter(|c| *c != 0xFF) .collect::>(); let (quads_without_split, errors_without_split) = parse([data_without_breaks.as_slice()], false); assert_eq!(quads, quads_without_split); assert_eq!(errors, errors_without_split); // We test also unchecked if valid if errors.is_empty() { let (quads_unchecked, errors_unchecked) = parse(data.split(|c| *c == 0xFF), true); assert!(errors_unchecked.is_empty()); assert_eq!(quads, quads_unchecked); } // We serialize let mut serializer = NQuadsSerializer::new().for_writer(Vec::new()); for quad in &quads { serializer.serialize_quad(quad).unwrap(); } let new_serialization = serializer.finish(); // We parse the serialization let new_quads = NQuadsParser::new() .with_quoted_triples() .for_slice(&new_serialization) .collect::, _>>() .map_err(|e| { format!( "Error on {:?} from {quads:?} based on {:?}: {e}", String::from_utf8_lossy(&new_serialization), String::from_utf8_lossy(data) ) }) .unwrap(); // We check the roundtrip has not changed anything assert_eq!(new_quads, quads); // We parse with Quad::from_str if there is no comments if !data_without_breaks.contains(&b'#') { match str::from_utf8(&data_without_breaks) .map_err(|e| e.to_string()) .and_then(|d| { d.split(['\r', '\n']) .filter(|l| !l.trim().is_empty()) .map(Quad::from_str) .collect::, _>>() .map_err(|e| e.to_string()) }) { Ok(term_quads) => { for quad in quads { assert!( term_quads.contains(&quad), "Quad::from_str has not managed to parse {quad}" ) } } Err(e) => { assert!( !errors.is_empty(), "Unexpected error from Quad::from_str: {e}" ) } } } }); oxigraph-oxigraph-68d45f7/fuzz/fuzz_targets/rdf_xml.rs000066400000000000000000000062301476336702400232660ustar00rootroot00000000000000#![no_main] use libfuzzer_sys::fuzz_target; use oxrdf::graph::CanonicalizationAlgorithm; use oxrdf::{Graph, Subject, Term, Triple}; use oxrdfxml::{RdfXmlParser, RdfXmlSerializer}; fn parse( data: &[u8], unchecked: bool, ) -> ( Vec, Vec, Vec<(String, String)>, Option, ) { let mut triples = Vec::new(); let mut errors = Vec::new(); let mut parser = RdfXmlParser::new(); if unchecked { parser = parser.unchecked(); } let mut parser = parser.for_slice(data); for result in &mut parser { match result { Ok(triple) => triples.push(triple), Err(error) => errors.push(error.to_string()), } } ( triples, errors, parser .prefixes() .map(|(k, v)| (k.to_string(), v.to_string())) .collect(), parser.base_iri().map(ToString::to_string), ) } fn count_triple_blank_nodes(triple: &Triple) -> usize { (match &triple.subject { Subject::BlankNode(_) => 1, Subject::Triple(t) => count_triple_blank_nodes(t), _ => 0, }) + (match &triple.object { Term::BlankNode(_) => 1, Term::Triple(t) => count_triple_blank_nodes(t), _ => 0, }) } fuzz_target!(|data: &[u8]| { // We parse let (triples, errors, prefixes, base_iri) = parse(data, false); // We test also unchecked if valid if errors.is_empty() { let (triples_unchecked, errors_unchecked, _, _) = parse(data, true); assert!(errors_unchecked.is_empty()); let bnodes_count = triples.iter().map(count_triple_blank_nodes).sum::(); if bnodes_count == 0 { assert_eq!(triples, triples_unchecked); } else if bnodes_count <= 4 { let mut graph_with_split = triples.iter().collect::(); let mut graph_unchecked = triples_unchecked.iter().collect::(); graph_with_split.canonicalize(CanonicalizationAlgorithm::Unstable); graph_unchecked.canonicalize(CanonicalizationAlgorithm::Unstable); assert_eq!(graph_with_split, graph_unchecked); } } // We serialize let mut serializer = RdfXmlSerializer::new(); for (prefix_name, prefix_iri) in prefixes { serializer = serializer.with_prefix(prefix_name, prefix_iri).unwrap(); } if let Some(base_iri) = base_iri { serializer = serializer.with_base_iri(base_iri).unwrap(); } let mut serializer = serializer.for_writer(Vec::new()); for triple in &triples { serializer.serialize_triple(triple).unwrap(); } let new_serialization = serializer.finish().unwrap(); // We parse the serialization let new_triples = RdfXmlParser::new() .for_slice(&new_serialization) .collect::, _>>() .map_err(|e| { format!( "Error on '{}' from {triples:?} based on '{}': {e}", String::from_utf8_lossy(&new_serialization), String::from_utf8_lossy(data) ) }) .unwrap(); // We check the roundtrip has not changed anything assert_eq!(new_triples, triples); }); oxigraph-oxigraph-68d45f7/fuzz/fuzz_targets/sparql_query.rs000066400000000000000000000002111476336702400243530ustar00rootroot00000000000000#![no_main] use libfuzzer_sys::fuzz_target; use spargebra::Query; fuzz_target!(|data: &str| { let _ = Query::parse(data, None); }); oxigraph-oxigraph-68d45f7/fuzz/fuzz_targets/sparql_query_eval.rs000066400000000000000000000131571476336702400253770ustar00rootroot00000000000000#![no_main] use libfuzzer_sys::fuzz_target; use oxigraph::io::{RdfFormat, RdfParser}; use oxigraph::model::graph::CanonicalizationAlgorithm; use oxigraph::model::{Dataset, Graph, NamedNode}; use oxigraph::sparql::{EvaluationError, Query, QueryOptions, QueryResults, ServiceHandler}; use oxigraph::store::Store; use oxigraph_fuzz::count_triple_blank_nodes; use oxiri::Iri; use oxrdf::{GraphNameRef, QuadRef}; use spareval::{DefaultServiceHandler, QueryEvaluationError, QueryEvaluator}; use spargebra::algebra::GraphPattern; use std::sync::OnceLock; fuzz_target!(|data: sparql_smith::Query| { static STORE: OnceLock = OnceLock::new(); let store = STORE.get_or_init(|| { let store = Store::new().unwrap(); store .load_from_reader(RdfFormat::TriG, sparql_smith::DATA_TRIG.as_bytes()) .unwrap(); store }); static DATASET: OnceLock = OnceLock::new(); let dataset = DATASET.get_or_init(|| { RdfParser::from(RdfFormat::TriG) .for_slice(sparql_smith::DATA_TRIG.as_bytes()) .collect::>() .unwrap() }); let query_str = data.to_string(); if let Ok(query) = spargebra::Query::parse(&query_str, None) { let options = QueryOptions::default().with_service_handler(StoreServiceHandler { store: store.clone(), }); let with_opt = store.query_opt(Query::from(query.clone()), options.clone()); let without_opt = QueryEvaluator::new() .without_optimizations() .with_default_service_handler(DatasetServiceHandler { dataset: dataset.clone(), }) .execute(dataset.clone(), &query); assert_eq!( query_results_key(with_opt, query_str.contains(" REDUCED ")), query_results_key( without_opt.map(Into::into).map_err(Into::into), query_str.contains(" REDUCED ") ) ) } }); fn query_results_key(results: Result, is_reduced: bool) -> String { match results { Ok(QueryResults::Solutions(iter)) => { // TODO: ordering let mut b = iter .into_iter() .filter_map(Result::ok) .map(|t| { let mut b = t .iter() .map(|(var, val)| format!("{var}: {val}")) .collect::>(); b.sort_unstable(); b.join(" ") }) .collect::>(); b.sort_unstable(); if is_reduced { b.dedup(); } b.join("\n") } Ok(QueryResults::Graph(iter)) => { let mut graph = iter.filter_map(Result::ok).collect::(); if graph.iter().map(count_triple_blank_nodes).sum::() > 4 { return String::new(); // canonicalization might be too slow }; graph.canonicalize(CanonicalizationAlgorithm::Unstable); let mut triples = graph.into_iter().map(|t| t.to_string()).collect::>(); triples.sort_unstable(); triples.join("\n") } Ok(QueryResults::Boolean(bool)) => if bool { "true" } else { "" }.into(), Err(_) => String::new(), } } #[derive(Clone)] struct StoreServiceHandler { store: Store, } impl ServiceHandler for StoreServiceHandler { type Error = EvaluationError; fn handle( &self, service_name: NamedNode, mut query: Query, ) -> Result { if !self.store.contains_named_graph(&service_name)? { return Err(EvaluationError::Service("Graph does not exist".into())); } query .dataset_mut() .set_default_graph(vec![service_name.into()]); self.store.query_opt( query, QueryOptions::default().with_service_handler(self.clone()), ) } } #[derive(Clone)] struct DatasetServiceHandler { dataset: Dataset, } impl DefaultServiceHandler for DatasetServiceHandler { type Error = QueryEvaluationError; fn handle( &self, service_name: NamedNode, pattern: GraphPattern, base_iri: Option, ) -> Result { if self .dataset .quads_for_graph_name(&service_name) .next() .is_none() { return Err(QueryEvaluationError::Service("Graph does not exist".into())); } let dataset = self .dataset .iter() .flat_map(|q| { if q.graph_name.is_default_graph() { vec![] } else if q.graph_name == service_name.as_ref().into() { vec![ QuadRef::new(q.subject, q.predicate, q.object, GraphNameRef::DefaultGraph), q, ] } else { vec![q] } }) .collect::(); let evaluator = QueryEvaluator::new().with_default_service_handler(DatasetServiceHandler { dataset: dataset.clone(), }); let spareval::QueryResults::Solutions(iter) = evaluator.execute( dataset, &spargebra::Query::Select { dataset: None, pattern, base_iri: base_iri.map(|iri| Iri::parse(iri).unwrap()), }, )? else { panic!() }; Ok(iter) } } oxigraph-oxigraph-68d45f7/fuzz/fuzz_targets/sparql_results_json.rs000066400000000000000000000003271476336702400257500ustar00rootroot00000000000000#![no_main] use libfuzzer_sys::fuzz_target; use oxigraph_fuzz::result_format::fuzz_result_format; use sparesults::QueryResultsFormat; fuzz_target!(|data: &[u8]| fuzz_result_format(QueryResultsFormat::Json, data)); oxigraph-oxigraph-68d45f7/fuzz/fuzz_targets/sparql_results_tsv.rs000066400000000000000000000003261476336702400256120ustar00rootroot00000000000000#![no_main] use libfuzzer_sys::fuzz_target; use oxigraph_fuzz::result_format::fuzz_result_format; use sparesults::QueryResultsFormat; fuzz_target!(|data: &[u8]| fuzz_result_format(QueryResultsFormat::Tsv, data)); oxigraph-oxigraph-68d45f7/fuzz/fuzz_targets/sparql_results_xml.rs000066400000000000000000000003261476336702400255760ustar00rootroot00000000000000#![no_main] use libfuzzer_sys::fuzz_target; use oxigraph_fuzz::result_format::fuzz_result_format; use sparesults::QueryResultsFormat; fuzz_target!(|data: &[u8]| fuzz_result_format(QueryResultsFormat::Xml, data)); oxigraph-oxigraph-68d45f7/fuzz/fuzz_targets/sparql_update.rs000066400000000000000000000002311476336702400244720ustar00rootroot00000000000000#![no_main] use libfuzzer_sys::fuzz_target; use spargebra::Update; use std::str; fuzz_target!(|data: &str| { let _ = Update::parse(data, None); }); oxigraph-oxigraph-68d45f7/fuzz/fuzz_targets/sparql_update_eval.rs000066400000000000000000000037001476336702400255050ustar00rootroot00000000000000#![no_main] use libfuzzer_sys::fuzz_target; use oxigraph::model::dataset::{CanonicalizationAlgorithm, Dataset}; use oxigraph::sparql::{QueryOptions, Update}; use oxigraph::store::Store; #[cfg(feature = "rocksdb")] use std::env::temp_dir; use std::sync::OnceLock; fuzz_target!(|data: sparql_smith::Update| { static DISK_STORE: OnceLock = OnceLock::new(); let disk_store = DISK_STORE.get_or_init(|| { #[cfg(feature = "rocksdb")] { Store::open(temp_dir().join("oxigraph-fuzz-update")).unwrap() } #[cfg(not(feature = "rocksdb"))] { Store::new().unwrap() } }); let update_str = data.to_string(); if let Ok(update) = Update::parse(&update_str, None) { let options = QueryOptions::default(); disk_store.clear().unwrap(); let disk_with_opt = disk_store.update_opt(update.clone(), options.clone()); disk_store.validate().unwrap(); let mut dataset_disk_with_opt = disk_store.iter().collect::>().unwrap(); dataset_disk_with_opt.canonicalize(CanonicalizationAlgorithm::Unstable); let memory_store = Store::new().unwrap(); let memory_without_opt = memory_store.update_opt(update, options.without_optimizations()); memory_store.validate().unwrap(); let mut dataset_memory_without_opt = memory_store.iter().collect::>().unwrap(); dataset_memory_without_opt.canonicalize(CanonicalizationAlgorithm::Unstable); assert_eq!( disk_with_opt.is_ok(), memory_without_opt.is_ok(), "Worked and failed depending on using optimizations: {disk_with_opt:?} {memory_without_opt:?}" ); assert_eq!( dataset_disk_with_opt, dataset_memory_without_opt, "With optimizations on disk:\n{dataset_disk_with_opt}\nWithout optimizations in memory:\n{dataset_memory_without_opt}" ); } }); oxigraph-oxigraph-68d45f7/fuzz/fuzz_targets/trig.rs000066400000000000000000000123351476336702400226030ustar00rootroot00000000000000#![no_main] use libfuzzer_sys::fuzz_target; use oxigraph_fuzz::count_quad_blank_nodes; use oxrdf::graph::CanonicalizationAlgorithm; use oxrdf::{Dataset, Quad}; use oxttl::{TriGParser, TriGSerializer}; fn parse<'a>( chunks: impl IntoIterator, unchecked: bool, ) -> ( Vec, Vec, Vec<(String, String)>, Option, ) { let mut quads = Vec::new(); let mut errors = Vec::new(); let mut parser = TriGParser::new() .with_quoted_triples() .with_base_iri("http://example.com/") .unwrap(); if unchecked { parser = parser.unchecked(); } let mut parser = parser.low_level(); for chunk in chunks { parser.extend_from_slice(chunk); while let Some(result) = parser.parse_next() { match result { Ok(quad) => quads.push(quad), Err(error) => errors.push(error.to_string()), } } } parser.end(); while let Some(result) = parser.parse_next() { match result { Ok(quad) => quads.push(quad), Err(error) => errors.push(error.to_string()), } } assert!(parser.is_end()); ( quads, errors, parser .prefixes() .map(|(k, v)| (k.to_owned(), v.to_owned())) .collect(), parser.base_iri().map(ToString::to_string), ) } fn serialize_quads( quads: &[Quad], prefixes: Vec<(String, String)>, base_iri: Option, ) -> Vec { let mut serializer = TriGSerializer::new(); for (prefix_name, prefix_iri) in prefixes { serializer = serializer.with_prefix(prefix_name, prefix_iri).unwrap(); } if let Some(base_iri) = base_iri { serializer = serializer.with_base_iri(base_iri).unwrap(); } let mut serializer = serializer.for_writer(Vec::new()); for quad in quads { serializer.serialize_quad(quad).unwrap(); } serializer.finish().unwrap() } fuzz_target!(|data: &[u8]| { // We parse with splitting let (quads, errors, prefixes, base_iri) = parse(data.split(|c| *c == 0xFF), false); // We parse without splitting let (quads_without_split, errors_without_split, _, _) = parse( [data .iter() .copied() .filter(|c| *c != 0xFF) .collect::>() .as_slice()], false, ); let (quads_unchecked, errors_unchecked, _, _) = parse(data.split(|c| *c == 0xFF), true); if errors.is_empty() { assert!(errors_unchecked.is_empty()); } let bnodes_count = quads .iter() .map(|q| count_quad_blank_nodes(q.as_ref())) .sum::(); if bnodes_count == 0 { assert_eq!( quads, quads_without_split, "With split:\n{}\nWithout split:\n{}", String::from_utf8_lossy(&serialize_quads(&quads, Vec::new(), None)), String::from_utf8_lossy(&serialize_quads(&quads_without_split, Vec::new(), None)) ); if errors.is_empty() { assert_eq!( quads, quads_unchecked, "Validating:\n{}\nUnchecked:\n{}", String::from_utf8_lossy(&serialize_quads(&quads, Vec::new(), None)), String::from_utf8_lossy(&serialize_quads(&quads_unchecked, Vec::new(), None)) ); } } else if bnodes_count <= 4 { let mut dataset_with_split = quads.iter().collect::(); let mut dataset_without_split = quads_without_split.iter().collect::(); dataset_with_split.canonicalize(CanonicalizationAlgorithm::Unstable); dataset_without_split.canonicalize(CanonicalizationAlgorithm::Unstable); assert_eq!( dataset_with_split, dataset_without_split, "With split:\n{}\nWithout split:\n{}", String::from_utf8_lossy(&serialize_quads(&quads, Vec::new(), None)), String::from_utf8_lossy(&serialize_quads(&quads_without_split, Vec::new(), None)) ); if errors.is_empty() { let mut dataset_unchecked = quads_unchecked.iter().collect::(); dataset_unchecked.canonicalize(CanonicalizationAlgorithm::Unstable); assert_eq!( dataset_with_split, dataset_unchecked, "Validating:\n{}\nUnchecked:\n{}", String::from_utf8_lossy(&serialize_quads(&quads, Vec::new(), None)), String::from_utf8_lossy(&serialize_quads(&quads_unchecked, Vec::new(), None)) ); } } assert_eq!(errors, errors_without_split); // We serialize let new_serialization = serialize_quads(&quads, prefixes, base_iri); // We parse the serialization let new_quads = TriGParser::new() .with_quoted_triples() .for_slice(&new_serialization) .collect::, _>>() .map_err(|e| { format!( "Error on {:?} from {quads:?} based on {:?}: {e}", String::from_utf8_lossy(&new_serialization), String::from_utf8_lossy(data) ) }) .unwrap(); // We check the roundtrip has not changed anything assert_eq!(new_quads, quads); }); oxigraph-oxigraph-68d45f7/fuzz/src/000077500000000000000000000000001476336702400173245ustar00rootroot00000000000000oxigraph-oxigraph-68d45f7/fuzz/src/lib.rs000066400000000000000000000016271476336702400204460ustar00rootroot00000000000000use oxrdf::{GraphNameRef, QuadRef, SubjectRef, TermRef, TripleRef}; pub mod result_format; pub fn count_triple_blank_nodes(triple: TripleRef<'_>) -> usize { (match &triple.subject { SubjectRef::BlankNode(_) => 1, SubjectRef::Triple(t) => count_triple_blank_nodes(t.as_ref()), _ => 0, }) + (match &triple.object { TermRef::BlankNode(_) => 1, TermRef::Triple(t) => count_triple_blank_nodes(t.as_ref()), _ => 0, }) } pub fn count_quad_blank_nodes(quad: QuadRef<'_>) -> usize { (match &quad.subject { SubjectRef::BlankNode(_) => 1, SubjectRef::Triple(t) => count_triple_blank_nodes(t.as_ref()), _ => 0, }) + (match &quad.object { TermRef::BlankNode(_) => 1, TermRef::Triple(t) => count_triple_blank_nodes(t.as_ref()), _ => 0, }) + usize::from(matches!(quad.graph_name, GraphNameRef::BlankNode(_))) } oxigraph-oxigraph-68d45f7/fuzz/src/result_format.rs000066400000000000000000000045041476336702400225630ustar00rootroot00000000000000use anyhow::Context; use sparesults::{ QueryResultsFormat, QueryResultsParser, QueryResultsSerializer, SliceQueryResultsParserOutput, }; pub fn fuzz_result_format(format: QueryResultsFormat, data: &[u8]) { let Ok(reader) = QueryResultsParser::from_format(format).for_slice(data) else { return; }; match reader { SliceQueryResultsParserOutput::Solutions(solutions) => { let Ok(solutions) = solutions.collect::, _>>() else { return; }; // We try to write again let mut serializer = QueryResultsSerializer::from_format(format) .serialize_solutions_to_writer( Vec::new(), solutions .first() .map_or_else(Vec::new, |s| s.variables().to_vec()), ) .unwrap(); for solution in &solutions { serializer.serialize(solution).unwrap(); } let serialized = serializer.finish().unwrap(); // And to parse again if let SliceQueryResultsParserOutput::Solutions(roundtrip_solutions) = QueryResultsParser::from_format(format) .for_slice(&serialized) .with_context(|| format!("Parsing {:?}", String::from_utf8_lossy(&serialized))) .unwrap() { assert_eq!( roundtrip_solutions .collect::, _>>() .with_context(|| format!("Parsing {serialized:?}")) .unwrap(), solutions ) } } SliceQueryResultsParserOutput::Boolean(value) => { // We try to write again let mut serialized = Vec::new(); QueryResultsSerializer::from_format(format) .serialize_boolean_to_writer(&mut serialized, value) .unwrap(); // And to parse again if let SliceQueryResultsParserOutput::Boolean(roundtrip_value) = QueryResultsParser::from_format(format) .for_slice(&serialized) .unwrap() { assert_eq!(roundtrip_value, value) } } } } oxigraph-oxigraph-68d45f7/js/000077500000000000000000000000001476336702400161535ustar00rootroot00000000000000oxigraph-oxigraph-68d45f7/js/Cargo.toml000066400000000000000000000013341476336702400201040ustar00rootroot00000000000000[package] name = "oxigraph-js" version.workspace = true authors.workspace = true license.workspace = true readme = "README.md" keywords = ["RDF", "N-Triples", "Turtle", "XML", "SPARQL"] repository = "https://github.com/oxigraph/oxigraph/tree/main/js" description = "JavaScript bindings of Oxigraph" edition.workspace = true rust-version.workspace = true publish = false [lib] crate-type = ["cdylib"] name = "oxigraph" doc = false [features] default = ["geosparql"] geosparql = ["dep:spargeo"] [dependencies] console_error_panic_hook.workspace = true js-sys.workspace = true oxigraph = { workspace = true, features = ["js"] } spargeo = { workspace = true, optional = true } wasm-bindgen.workspace = true [lints] workspace = true oxigraph-oxigraph-68d45f7/js/README.md000066400000000000000000000304211476336702400174320ustar00rootroot00000000000000Oxigraph for JavaScript ======================= [![npm](https://img.shields.io/npm/v/oxigraph)](https://www.npmjs.com/package/oxigraph) [![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) This package provides a JavaScript API on top of [Oxigraph](https://crates.io/crates/oxigraph), compiled with WebAssembly. Oxigraph is a graph database written in Rust implementing the [SPARQL](https://www.w3.org/TR/sparql11-overview/) standard. Oxigraph for JavaScript is a work in progress and currently offers a simple in-memory store with [SPARQL 1.1 Query](https://www.w3.org/TR/sparql11-query/) and [SPARQL 1.1 Update](https://www.w3.org/TR/sparql11-update/) capabilities. The store is also able to load RDF serialized in [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/). It is distributed using a [a NPM package](https://www.npmjs.com/package/oxigraph) that should work with Node.JS 18+ and [modern web browsers compatible with WebAssembly reference types and JavaScript `WeakRef`](https://caniuse.com/wasm-reference-types,mdn-javascript_builtins_weakref). To install: ```bash npm install oxigraph ``` To load with Node.JS: ```js const oxigraph = require('oxigraph'); ``` or with ES modules: ```js import oxigraph from './node_modules/oxigraph/node.js'; ``` To load on an HTML web page (for [WebPack 5](https://webpack.js.org/) remove the ` ``` ## Node.JS Example Insert the triple ` "example"` and log the name of `` in SPARQL: ```js const oxigraph = require('oxigraph'); const store = new oxigraph.Store(); const ex = oxigraph.namedNode("http://example/"); const schemaName = oxigraph.namedNode("http://schema.org/name"); store.add(oxigraph.triple(ex, schemaName, oxigraph.literal("example"))); for (const binding of store.query("SELECT ?name WHERE { ?name }")) { console.log(binding.get("name").value); } ``` ## Web Example Insert the triple ` "example"` and log the name of `` in SPARQL: ```html ``` This example works with WebPack too if you remove the `