├── .github ├── dependabot.yml └── workflows │ ├── check.yml │ ├── docs.yml │ ├── pull-request.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .gitmodules ├── .prettierignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── commitlint.config.js ├── release-plz.toml └── src ├── extensions.rs ├── lib.rs ├── parse ├── context.rs ├── mod.rs ├── proto │ ├── extensions │ │ ├── mod.rs │ │ └── simple_extension_uri.rs │ ├── mod.rs │ ├── plan_version.rs │ └── version.rs ├── text │ ├── mod.rs │ └── simple_extensions │ │ ├── argument.rs │ │ └── mod.rs └── typed.rs ├── proto.rs ├── text.rs └── version.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: "cargo" 6 | directory: "/" 7 | schedule: 8 | interval: "daily" 9 | rebase-strategy: "disabled" 10 | commit-message: 11 | prefix: "chore(deps,cargo)" 12 | groups: 13 | proto: 14 | applies-to: version-updates 15 | patterns: 16 | - "prost*" 17 | - "pbjson*" 18 | cargo: 19 | applies-to: version-updates 20 | patterns: 21 | - "*" 22 | 23 | - package-ecosystem: "github-actions" 24 | directory: "/" 25 | schedule: 26 | interval: "daily" 27 | commit-message: 28 | prefix: "chore(deps,github-actions)" 29 | ignore: 30 | - dependency-name: "dtolnay/rust-toolchain" 31 | 32 | - package-ecosystem: "gitsubmodule" 33 | directory: "/" 34 | schedule: 35 | # 4 hours after the weekly Substrait release cron job 36 | # https://github.com/substrait-io/substrait/blob/90fce6a582c1009435d26b63083486ac1eed5906/.github/workflows/release.yml#L1-L6 37 | interval: "weekly" 38 | day: "sunday" 39 | time: "06:00" 40 | commit-message: 41 | prefix: "feat(deps,substrait)!:" 42 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | name: Check 4 | 5 | on: [push, pull_request] 6 | 7 | jobs: 8 | license: 9 | name: SPDX License Header 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: enarx/spdx@master 14 | with: 15 | licenses: Apache-2.0 16 | 17 | formatting: 18 | name: Formatting 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: actions/setup-node@v4 23 | - run: npm install prettier prettier-plugin-toml 24 | - run: npx prettier --check --no-config . 25 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | name: Docs 4 | 5 | on: [push, pull_request] 6 | 7 | permissions: 8 | contents: read 9 | pages: write 10 | id-token: write 11 | 12 | concurrency: 13 | group: ${{ github.ref }} 14 | 15 | jobs: 16 | rustdoc: 17 | name: Rustdoc 18 | runs-on: ubuntu-latest 19 | env: 20 | RUSTDOCFLAGS: -Dwarnings 21 | steps: 22 | - uses: actions/checkout@v4 23 | with: 24 | fetch-depth: 0 25 | submodules: recursive 26 | - uses: dtolnay/rust-toolchain@nightly 27 | id: rust-toolchain 28 | - uses: dtolnay/install@cargo-docs-rs 29 | - run: cargo docs-rs 30 | - run: mv "target/$(rustc -vV | awk '/^host/ { print $2 }')/doc" "target/doc" 31 | - run: chmod -c -R +rX "target/doc" 32 | - run: echo "" > target/doc/index.html 33 | - if: github.event_name == 'push' && github.ref == 'refs/heads/main' 34 | uses: actions/upload-pages-artifact@v3 35 | with: 36 | path: target/doc/ 37 | 38 | deploy: 39 | name: Deploy 40 | if: github.event_name == 'push' && github.ref == 'refs/heads/main' 41 | runs-on: ubuntu-latest 42 | needs: rustdoc 43 | environment: 44 | name: github-pages 45 | url: ${{ steps.deployment.outputs.page_url }} 46 | steps: 47 | - uses: actions/configure-pages@v5 48 | - uses: actions/deploy-pages@v4 49 | id: deployment 50 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | name: Pull Request 4 | 5 | on: 6 | pull_request_target: 7 | types: 8 | - opened 9 | - reopened 10 | - edited 11 | - synchronize 12 | 13 | jobs: 14 | conventional-commits: 15 | name: Conventional Commits 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: actions/setup-node@v4 20 | - run: npm install @commitlint/config-conventional 21 | - run: npx commitlint <<< $CONVENTIONAL_COMMIT 22 | env: 23 | CONVENTIONAL_COMMIT: | 24 | ${{ github.event.pull_request.title }} 25 | 26 | ${{ github.event.pull_request.body }} 27 | - if: failure() 28 | run: 29 | echo "Substrait follows the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) for release automation. 30 | The PR title and body are used as the merge commit message. 31 | 32 | Please update your PR title to match the specification." >> $GITHUB_STEP_SUMMARY 33 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | name: Release 4 | 5 | permissions: 6 | pull-requests: write 7 | contents: write 8 | 9 | on: 10 | push: 11 | branches: 12 | - main 13 | 14 | jobs: 15 | release: 16 | name: Release 17 | runs-on: ubuntu-latest 18 | environment: 19 | name: crates-io 20 | permissions: 21 | contents: write 22 | steps: 23 | - uses: actions/checkout@v4 24 | with: 25 | fetch-depth: 0 26 | submodules: recursive 27 | - uses: arduino/setup-protoc@v3 28 | with: 29 | repo-token: ${{ secrets.GITHUB_TOKEN }} 30 | - uses: dtolnay/rust-toolchain@stable 31 | # Trigger build script to generate version info 32 | - run: cargo build 33 | - uses: MarcoIeni/release-plz-action@v0.5.107 34 | with: 35 | command: release 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} 39 | 40 | pr: 41 | name: PR 42 | runs-on: ubuntu-latest 43 | concurrency: 44 | group: release-${{ github.ref }} 45 | cancel-in-progress: false 46 | steps: 47 | - uses: actions/checkout@v4 48 | with: 49 | fetch-depth: 0 50 | submodules: recursive 51 | - uses: dtolnay/rust-toolchain@stable 52 | - uses: arduino/setup-protoc@v3 53 | with: 54 | repo-token: ${{ secrets.GITHUB_TOKEN }} 55 | - run: cargo build 56 | - uses: MarcoIeni/release-plz-action@v0.5.107 57 | with: 58 | command: release-pr 59 | env: 60 | RUST_LOG: debug 61 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 62 | CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} 63 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | name: Test 4 | 5 | on: [push, pull_request] 6 | 7 | jobs: 8 | msrv: 9 | name: Minimum Supported Rust Version 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | fetch-depth: 0 15 | submodules: recursive 16 | - uses: dtolnay/rust-toolchain@1.80.1 17 | id: rust-toolchain 18 | - uses: actions/cache@v4 19 | with: 20 | path: | 21 | ~/.cargo/bin/ 22 | ~/.cargo/registry/index/ 23 | ~/.cargo/registry/cache/ 24 | ~/.cargo/git/db/ 25 | target/ 26 | key: ${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}-check-${{ hashFiles('**/Cargo.toml') }} 27 | restore-keys: | 28 | ${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}-check- 29 | ${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}- 30 | ${{ runner.os }}-cargo- 31 | - run: cargo check --all-targets --no-default-features --features protoc 32 | - run: cargo check --all-targets --all-features 33 | 34 | check: 35 | name: Check 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@v4 39 | with: 40 | fetch-depth: 0 41 | submodules: recursive 42 | - uses: dtolnay/rust-toolchain@stable 43 | id: rust-toolchain 44 | - uses: actions/cache@v4 45 | with: 46 | path: | 47 | ~/.cargo/bin/ 48 | ~/.cargo/registry/index/ 49 | ~/.cargo/registry/cache/ 50 | ~/.cargo/git/db/ 51 | target/ 52 | key: ${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}-check-${{ hashFiles('**/Cargo.toml') }} 53 | restore-keys: | 54 | ${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}-check- 55 | ${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}- 56 | ${{ runner.os }}-cargo- 57 | - run: cargo check --all-targets --no-default-features --features protoc 58 | - run: cargo check --all-targets --all-features 59 | 60 | test: 61 | name: Test 62 | runs-on: ubuntu-latest 63 | steps: 64 | - uses: actions/checkout@v4 65 | with: 66 | fetch-depth: 0 67 | submodules: recursive 68 | - uses: dtolnay/rust-toolchain@stable 69 | id: rust-toolchain 70 | - uses: actions/cache@v4 71 | with: 72 | path: | 73 | ~/.cargo/bin/ 74 | ~/.cargo/registry/index/ 75 | ~/.cargo/registry/cache/ 76 | ~/.cargo/git/db/ 77 | target/ 78 | key: ${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}-test-${{ hashFiles('**/Cargo.toml') }} 79 | restore-keys: | 80 | ${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}-test- 81 | ${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}- 82 | ${{ runner.os }}-cargo- 83 | - run: cargo test --all-targets --no-default-features --features protoc 84 | - run: cargo test --all-targets --all-features 85 | - run: cargo test --doc --all-features 86 | - run: cargo test --doc --no-default-features --features protoc 87 | 88 | rustfmt: 89 | name: Rustfmt 90 | runs-on: ubuntu-latest 91 | steps: 92 | - uses: actions/checkout@v4 93 | - uses: dtolnay/rust-toolchain@stable 94 | with: 95 | components: rustfmt 96 | - run: cargo fmt -- --check 97 | 98 | clippy: 99 | name: Clippy 100 | runs-on: ubuntu-latest 101 | steps: 102 | - uses: actions/checkout@v4 103 | with: 104 | fetch-depth: 0 105 | submodules: recursive 106 | - uses: dtolnay/rust-toolchain@stable 107 | id: rust-toolchain 108 | with: 109 | components: clippy 110 | - uses: actions/cache@v4 111 | with: 112 | path: | 113 | ~/.cargo/bin/ 114 | ~/.cargo/registry/index/ 115 | ~/.cargo/registry/cache/ 116 | ~/.cargo/git/db/ 117 | target/ 118 | key: ${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}-clippy-${{ hashFiles('**/Cargo.toml') }} 119 | restore-keys: | 120 | ${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}-clippy- 121 | ${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}- 122 | ${{ runner.os }}-cargo- 123 | - run: cargo clippy --all-targets --no-default-features --features protoc -- -Dwarnings 124 | - run: cargo clippy --all-targets --all-features -- -Dwarnings 125 | 126 | package: 127 | name: Package 128 | runs-on: ubuntu-latest 129 | steps: 130 | - uses: actions/checkout@v4 131 | with: 132 | fetch-depth: 0 133 | submodules: recursive 134 | - uses: dtolnay/rust-toolchain@stable 135 | id: rust-toolchain 136 | - uses: actions/cache@v4 137 | with: 138 | path: | 139 | ~/.cargo/bin/ 140 | ~/.cargo/registry/index/ 141 | ~/.cargo/registry/cache/ 142 | ~/.cargo/git/db/ 143 | target/ 144 | key: ${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}-package-${{ hashFiles('**/Cargo.toml') }} 145 | restore-keys: | 146 | ${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}-package- 147 | ${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}- 148 | ${{ runner.os }}-cargo- 149 | - run: cargo build --all-features 150 | - run: cargo package --all-features --allow-dirty 151 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | /target 4 | 5 | # Generated by build.rs (substrait submodule version information) 6 | gen/ 7 | 8 | .idea 9 | 10 | package.json 11 | package-lock.json 12 | node_modules/ 13 | .vscode/ 14 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | [submodule "substrait"] 4 | path = substrait 5 | url = https://github.com/substrait-io/substrait 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | /target 4 | CHANGELOG.md 5 | LICENSE 6 | substrait/ 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | All contributors and contributions are welcome! Please open an issue on GitHub if you have issues, questions or ideas. 4 | 5 | ## GitHub 6 | 7 | ### Pull requests 8 | 9 | Substrait follows the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) for commit messages. This allows for automation of releases based on commit messages that are merged to the default branch. 10 | 11 | The `Conventional Commits` job of the [Pull Request](.github/workflows/pull-request.yml) workflow check the Pull Request title and body and the resulting merge commit message. 12 | 13 | ### Releases 14 | 15 | Releases are published automatically with the [Release](./github/workflows/release.yml) workflow. The workflow is triggered for every commit to the `main` branch. [`cargo-smart-release`](https://github.com/Byron/gitoxide/tree/main/cargo-smart-release) is used to bump the version, create and publish the new release. 16 | 17 | ### Dependabot 18 | 19 | substrait-rs uses [Depedendabot](https://docs.github.com/en/code-security/dependabot) to update dependencies using the [dependabot.yml](.github/dependabot.yml) configuration file. 20 | 21 | ### Prettier 22 | 23 | substrait-rs uses [Prettier](https://prettier.io/) to format non-Rust source files. The `Formatting` job in the [Check](.github/workflows/check.yml) workflow checks this. To format your files locally (requires [Node.js](https://nodejs.org/en/)): 24 | 25 | ```shell 26 | npm install prettier prettier-plugin-toml --save-dev --save-exact 27 | npx prettier --write --no-config . 28 | ``` 29 | 30 | ## Governance 31 | 32 | Please refer to the [Substrait Governance](https://substrait.io/governance/) page. 33 | 34 | ## Community 35 | 36 | Please refer to the [Substrait Community](https://substrait.io/community/) page. 37 | 38 | ## License 39 | 40 | All contributions should be licensed under [Apache License, Version 2.0](LICENSE). 41 | All source files must have a valid SPDX license header. The `SPDX License Header` job in the [Check](.github/workflow/check.yml) workflow checks this. 42 | 43 | Substrait requires all contributors to sign the [Contributor License Agreement (CLA)](https://cla-assistant.io/substrait-io/substrait). There is a GitHub app installed to help new contributors sign it. 44 | 45 | ## Development 46 | 47 | ### Requirements 48 | 49 | - [Rust](https://rustup.rs) 50 | - [protoc (>=3.15)](https://github.com/protocolbuffers/protobuf/releases) 51 | 52 | In environments where no `protoc` is available the `protoc` feature can be enabled to build `protoc` from source: 53 | 54 | ```shell 55 | cargo build --features protoc 56 | ``` 57 | 58 | ### Substrait submodule 59 | 60 | There is a git submodule for [Substrait](https://github.com/substrait-io/substrait) that must be cloned when building from source. 61 | 62 | ```shell 63 | git clone --recurse-submodules git@github.com:substrait-io/substrait-rs.git 64 | ``` 65 | 66 | When the Substrait version is bumped make sure to update your local submodule. 67 | 68 | ```shell 69 | git submodule update 70 | ``` 71 | 72 | Formatting, lints and tests are checked in the [Test](.github/workflows/test.yml) workflow. 73 | 74 | ### Docs 75 | 76 | #### Rustdoc 77 | 78 | The crate documentation is built with [rustdoc](https://doc.rust-lang.org/rustdoc/what-is-rustdoc.html): 79 | 80 | ```shell 81 | cargo doc 82 | ``` 83 | 84 | Or to enable automatic feature information (requires a nightly toolchain): 85 | 86 | ```shell 87 | cargo +nightly rustdoc -- --cfg docsrs 88 | ``` 89 | 90 | Or use [cargo-doc-rs](https://crates.io/crates/cargo-docs-rs): 91 | 92 | ``` 93 | cargo +nightly docs-rs 94 | ``` 95 | 96 | ### Formatting 97 | 98 | All Rust code is formatted using [rustfmt](https://github.com/rust-lang/rustfmt): 99 | 100 | ```shell 101 | cargo fmt 102 | ``` 103 | 104 | ### Linting 105 | 106 | All Rust code passes [Clippy](https://github.com/rust-lang/rust-clippy) lints without warnings: 107 | 108 | ```shell 109 | cargo clippy -- -Dwarnings 110 | ``` 111 | 112 | ### Tests 113 | 114 | To run tests and [documentation tests](https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html): 115 | 116 | ```shell 117 | cargo test 118 | ``` 119 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "allocator-api2" 7 | version = "0.2.16" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" 10 | 11 | [[package]] 12 | name = "anyhow" 13 | version = "1.0.71" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" 16 | 17 | [[package]] 18 | name = "autocfg" 19 | version = "1.1.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 22 | 23 | [[package]] 24 | name = "base64" 25 | version = "0.21.4" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" 28 | 29 | [[package]] 30 | name = "bitflags" 31 | version = "1.3.2" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 34 | 35 | [[package]] 36 | name = "bytes" 37 | version = "1.4.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" 40 | 41 | [[package]] 42 | name = "cc" 43 | version = "1.0.83" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 46 | dependencies = [ 47 | "libc", 48 | ] 49 | 50 | [[package]] 51 | name = "cfg-if" 52 | version = "1.0.0" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 55 | 56 | [[package]] 57 | name = "chrono" 58 | version = "0.4.31" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" 61 | dependencies = [ 62 | "num-traits", 63 | ] 64 | 65 | [[package]] 66 | name = "cmake" 67 | version = "0.1.53" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "e24a03c8b52922d68a1589ad61032f2c1aa5a8158d2aa0d93c6e9534944bbad6" 70 | dependencies = [ 71 | "cc", 72 | ] 73 | 74 | [[package]] 75 | name = "displaydoc" 76 | version = "0.2.5" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 79 | dependencies = [ 80 | "proc-macro2", 81 | "quote", 82 | "syn", 83 | ] 84 | 85 | [[package]] 86 | name = "dyn-clone" 87 | version = "1.0.11" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" 90 | 91 | [[package]] 92 | name = "either" 93 | version = "1.8.1" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" 96 | 97 | [[package]] 98 | name = "equivalent" 99 | version = "1.0.0" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" 102 | 103 | [[package]] 104 | name = "errno" 105 | version = "0.3.1" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" 108 | dependencies = [ 109 | "errno-dragonfly", 110 | "libc", 111 | "windows-sys", 112 | ] 113 | 114 | [[package]] 115 | name = "errno-dragonfly" 116 | version = "0.1.2" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 119 | dependencies = [ 120 | "cc", 121 | "libc", 122 | ] 123 | 124 | [[package]] 125 | name = "fastrand" 126 | version = "1.9.0" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" 129 | dependencies = [ 130 | "instant", 131 | ] 132 | 133 | [[package]] 134 | name = "fixedbitset" 135 | version = "0.4.2" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" 138 | 139 | [[package]] 140 | name = "foldhash" 141 | version = "0.1.4" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" 144 | 145 | [[package]] 146 | name = "form_urlencoded" 147 | version = "1.2.1" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 150 | dependencies = [ 151 | "percent-encoding", 152 | ] 153 | 154 | [[package]] 155 | name = "hashbrown" 156 | version = "0.12.3" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 159 | 160 | [[package]] 161 | name = "hashbrown" 162 | version = "0.14.3" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" 165 | 166 | [[package]] 167 | name = "hashbrown" 168 | version = "0.15.2" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 171 | dependencies = [ 172 | "allocator-api2", 173 | "equivalent", 174 | "foldhash", 175 | ] 176 | 177 | [[package]] 178 | name = "heck" 179 | version = "0.5.0" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 182 | 183 | [[package]] 184 | name = "hermit-abi" 185 | version = "0.3.2" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" 188 | 189 | [[package]] 190 | name = "hex" 191 | version = "0.4.3" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 194 | 195 | [[package]] 196 | name = "icu_collections" 197 | version = "1.5.0" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 200 | dependencies = [ 201 | "displaydoc", 202 | "yoke", 203 | "zerofrom", 204 | "zerovec", 205 | ] 206 | 207 | [[package]] 208 | name = "icu_locid" 209 | version = "1.5.0" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 212 | dependencies = [ 213 | "displaydoc", 214 | "litemap", 215 | "tinystr", 216 | "writeable", 217 | "zerovec", 218 | ] 219 | 220 | [[package]] 221 | name = "icu_locid_transform" 222 | version = "1.5.0" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 225 | dependencies = [ 226 | "displaydoc", 227 | "icu_locid", 228 | "icu_locid_transform_data", 229 | "icu_provider", 230 | "tinystr", 231 | "zerovec", 232 | ] 233 | 234 | [[package]] 235 | name = "icu_locid_transform_data" 236 | version = "1.5.0" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" 239 | 240 | [[package]] 241 | name = "icu_normalizer" 242 | version = "1.5.0" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 245 | dependencies = [ 246 | "displaydoc", 247 | "icu_collections", 248 | "icu_normalizer_data", 249 | "icu_properties", 250 | "icu_provider", 251 | "smallvec", 252 | "utf16_iter", 253 | "utf8_iter", 254 | "write16", 255 | "zerovec", 256 | ] 257 | 258 | [[package]] 259 | name = "icu_normalizer_data" 260 | version = "1.5.0" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" 263 | 264 | [[package]] 265 | name = "icu_properties" 266 | version = "1.5.1" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" 269 | dependencies = [ 270 | "displaydoc", 271 | "icu_collections", 272 | "icu_locid_transform", 273 | "icu_properties_data", 274 | "icu_provider", 275 | "tinystr", 276 | "zerovec", 277 | ] 278 | 279 | [[package]] 280 | name = "icu_properties_data" 281 | version = "1.5.0" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" 284 | 285 | [[package]] 286 | name = "icu_provider" 287 | version = "1.5.0" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 290 | dependencies = [ 291 | "displaydoc", 292 | "icu_locid", 293 | "icu_provider_macros", 294 | "stable_deref_trait", 295 | "tinystr", 296 | "writeable", 297 | "yoke", 298 | "zerofrom", 299 | "zerovec", 300 | ] 301 | 302 | [[package]] 303 | name = "icu_provider_macros" 304 | version = "1.5.0" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 307 | dependencies = [ 308 | "proc-macro2", 309 | "quote", 310 | "syn", 311 | ] 312 | 313 | [[package]] 314 | name = "idna" 315 | version = "1.0.3" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 318 | dependencies = [ 319 | "idna_adapter", 320 | "smallvec", 321 | "utf8_iter", 322 | ] 323 | 324 | [[package]] 325 | name = "idna_adapter" 326 | version = "1.2.0" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" 329 | dependencies = [ 330 | "icu_normalizer", 331 | "icu_properties", 332 | ] 333 | 334 | [[package]] 335 | name = "indexmap" 336 | version = "1.9.3" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 339 | dependencies = [ 340 | "autocfg", 341 | "hashbrown 0.12.3", 342 | ] 343 | 344 | [[package]] 345 | name = "indexmap" 346 | version = "2.2.5" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" 349 | dependencies = [ 350 | "equivalent", 351 | "hashbrown 0.14.3", 352 | ] 353 | 354 | [[package]] 355 | name = "instant" 356 | version = "0.1.12" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 359 | dependencies = [ 360 | "cfg-if", 361 | ] 362 | 363 | [[package]] 364 | name = "io-lifetimes" 365 | version = "1.0.11" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" 368 | dependencies = [ 369 | "hermit-abi", 370 | "libc", 371 | "windows-sys", 372 | ] 373 | 374 | [[package]] 375 | name = "itertools" 376 | version = "0.13.0" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 379 | dependencies = [ 380 | "either", 381 | ] 382 | 383 | [[package]] 384 | name = "itoa" 385 | version = "1.0.8" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" 388 | 389 | [[package]] 390 | name = "libc" 391 | version = "0.2.153" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" 394 | 395 | [[package]] 396 | name = "linux-raw-sys" 397 | version = "0.3.8" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" 400 | 401 | [[package]] 402 | name = "litemap" 403 | version = "0.7.3" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" 406 | 407 | [[package]] 408 | name = "log" 409 | version = "0.4.27" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 412 | 413 | [[package]] 414 | name = "memchr" 415 | version = "2.5.0" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 418 | 419 | [[package]] 420 | name = "multimap" 421 | version = "0.8.3" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" 424 | 425 | [[package]] 426 | name = "num-traits" 427 | version = "0.2.15" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 430 | dependencies = [ 431 | "autocfg", 432 | ] 433 | 434 | [[package]] 435 | name = "once_cell" 436 | version = "1.20.2" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 439 | 440 | [[package]] 441 | name = "pbjson" 442 | version = "0.7.0" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "c7e6349fa080353f4a597daffd05cb81572a9c031a6d4fff7e504947496fcc68" 445 | dependencies = [ 446 | "base64", 447 | "serde", 448 | ] 449 | 450 | [[package]] 451 | name = "pbjson-build" 452 | version = "0.7.0" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "6eea3058763d6e656105d1403cb04e0a41b7bbac6362d413e7c33be0c32279c9" 455 | dependencies = [ 456 | "heck", 457 | "itertools", 458 | "prost", 459 | "prost-types", 460 | ] 461 | 462 | [[package]] 463 | name = "pbjson-types" 464 | version = "0.7.0" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "e54e5e7bfb1652f95bc361d76f3c780d8e526b134b85417e774166ee941f0887" 467 | dependencies = [ 468 | "bytes", 469 | "chrono", 470 | "pbjson", 471 | "pbjson-build", 472 | "prost", 473 | "prost-build", 474 | "serde", 475 | ] 476 | 477 | [[package]] 478 | name = "percent-encoding" 479 | version = "2.3.1" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 482 | 483 | [[package]] 484 | name = "petgraph" 485 | version = "0.6.3" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" 488 | dependencies = [ 489 | "fixedbitset", 490 | "indexmap 1.9.3", 491 | ] 492 | 493 | [[package]] 494 | name = "prettyplease" 495 | version = "0.2.32" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" 498 | dependencies = [ 499 | "proc-macro2", 500 | "syn", 501 | ] 502 | 503 | [[package]] 504 | name = "proc-macro2" 505 | version = "1.0.95" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 508 | dependencies = [ 509 | "unicode-ident", 510 | ] 511 | 512 | [[package]] 513 | name = "prost" 514 | version = "0.13.5" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" 517 | dependencies = [ 518 | "bytes", 519 | "prost-derive", 520 | ] 521 | 522 | [[package]] 523 | name = "prost-build" 524 | version = "0.13.5" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" 527 | dependencies = [ 528 | "heck", 529 | "itertools", 530 | "log", 531 | "multimap", 532 | "once_cell", 533 | "petgraph", 534 | "prettyplease", 535 | "prost", 536 | "prost-types", 537 | "regex", 538 | "syn", 539 | "tempfile", 540 | ] 541 | 542 | [[package]] 543 | name = "prost-derive" 544 | version = "0.13.5" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" 547 | dependencies = [ 548 | "anyhow", 549 | "itertools", 550 | "proc-macro2", 551 | "quote", 552 | "syn", 553 | ] 554 | 555 | [[package]] 556 | name = "prost-types" 557 | version = "0.13.5" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" 560 | dependencies = [ 561 | "prost", 562 | ] 563 | 564 | [[package]] 565 | name = "protobuf-src" 566 | version = "2.1.1+27.1" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "6217c3504da19b85a3a4b2e9a5183d635822d83507ba0986624b5c05b83bfc40" 569 | dependencies = [ 570 | "cmake", 571 | ] 572 | 573 | [[package]] 574 | name = "quote" 575 | version = "1.0.40" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 578 | dependencies = [ 579 | "proc-macro2", 580 | ] 581 | 582 | [[package]] 583 | name = "redox_syscall" 584 | version = "0.3.5" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" 587 | dependencies = [ 588 | "bitflags", 589 | ] 590 | 591 | [[package]] 592 | name = "regex" 593 | version = "1.8.4" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" 596 | dependencies = [ 597 | "regex-syntax", 598 | ] 599 | 600 | [[package]] 601 | name = "regex-syntax" 602 | version = "0.7.2" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" 605 | 606 | [[package]] 607 | name = "regress" 608 | version = "0.10.3" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "78ef7fa9ed0256d64a688a3747d0fef7a88851c18a5e1d57f115f38ec2e09366" 611 | dependencies = [ 612 | "hashbrown 0.15.2", 613 | "memchr", 614 | ] 615 | 616 | [[package]] 617 | name = "rustix" 618 | version = "0.37.27" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" 621 | dependencies = [ 622 | "bitflags", 623 | "errno", 624 | "io-lifetimes", 625 | "libc", 626 | "linux-raw-sys", 627 | "windows-sys", 628 | ] 629 | 630 | [[package]] 631 | name = "ryu" 632 | version = "1.0.14" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" 635 | 636 | [[package]] 637 | name = "same-file" 638 | version = "1.0.6" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 641 | dependencies = [ 642 | "winapi-util", 643 | ] 644 | 645 | [[package]] 646 | name = "schemars" 647 | version = "0.8.22" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" 650 | dependencies = [ 651 | "dyn-clone", 652 | "schemars_derive", 653 | "serde", 654 | "serde_json", 655 | ] 656 | 657 | [[package]] 658 | name = "schemars_derive" 659 | version = "0.8.22" 660 | source = "registry+https://github.com/rust-lang/crates.io-index" 661 | checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" 662 | dependencies = [ 663 | "proc-macro2", 664 | "quote", 665 | "serde_derive_internals", 666 | "syn", 667 | ] 668 | 669 | [[package]] 670 | name = "semver" 671 | version = "1.0.26" 672 | source = "registry+https://github.com/rust-lang/crates.io-index" 673 | checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" 674 | dependencies = [ 675 | "serde", 676 | ] 677 | 678 | [[package]] 679 | name = "serde" 680 | version = "1.0.219" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 683 | dependencies = [ 684 | "serde_derive", 685 | ] 686 | 687 | [[package]] 688 | name = "serde_derive" 689 | version = "1.0.219" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 692 | dependencies = [ 693 | "proc-macro2", 694 | "quote", 695 | "syn", 696 | ] 697 | 698 | [[package]] 699 | name = "serde_derive_internals" 700 | version = "0.29.1" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" 703 | dependencies = [ 704 | "proc-macro2", 705 | "quote", 706 | "syn", 707 | ] 708 | 709 | [[package]] 710 | name = "serde_json" 711 | version = "1.0.140" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 714 | dependencies = [ 715 | "itoa", 716 | "memchr", 717 | "ryu", 718 | "serde", 719 | ] 720 | 721 | [[package]] 722 | name = "serde_tokenstream" 723 | version = "0.2.2" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "64060d864397305347a78851c51588fd283767e7e7589829e8121d65512340f1" 726 | dependencies = [ 727 | "proc-macro2", 728 | "quote", 729 | "serde", 730 | "syn", 731 | ] 732 | 733 | [[package]] 734 | name = "serde_yaml" 735 | version = "0.9.34+deprecated" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" 738 | dependencies = [ 739 | "indexmap 2.2.5", 740 | "itoa", 741 | "ryu", 742 | "serde", 743 | "unsafe-libyaml", 744 | ] 745 | 746 | [[package]] 747 | name = "smallvec" 748 | version = "1.13.2" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 751 | 752 | [[package]] 753 | name = "stable_deref_trait" 754 | version = "1.2.0" 755 | source = "registry+https://github.com/rust-lang/crates.io-index" 756 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 757 | 758 | [[package]] 759 | name = "substrait" 760 | version = "0.57.0" 761 | dependencies = [ 762 | "heck", 763 | "hex", 764 | "pbjson", 765 | "pbjson-build", 766 | "pbjson-types", 767 | "prettyplease", 768 | "prost", 769 | "prost-build", 770 | "prost-types", 771 | "protobuf-src", 772 | "regress", 773 | "schemars", 774 | "semver", 775 | "serde", 776 | "serde_json", 777 | "serde_yaml", 778 | "syn", 779 | "thiserror", 780 | "typify", 781 | "url", 782 | "walkdir", 783 | ] 784 | 785 | [[package]] 786 | name = "syn" 787 | version = "2.0.101" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" 790 | dependencies = [ 791 | "proc-macro2", 792 | "quote", 793 | "unicode-ident", 794 | ] 795 | 796 | [[package]] 797 | name = "synstructure" 798 | version = "0.13.1" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" 801 | dependencies = [ 802 | "proc-macro2", 803 | "quote", 804 | "syn", 805 | ] 806 | 807 | [[package]] 808 | name = "tempfile" 809 | version = "3.6.0" 810 | source = "registry+https://github.com/rust-lang/crates.io-index" 811 | checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" 812 | dependencies = [ 813 | "autocfg", 814 | "cfg-if", 815 | "fastrand", 816 | "redox_syscall", 817 | "rustix", 818 | "windows-sys", 819 | ] 820 | 821 | [[package]] 822 | name = "thiserror" 823 | version = "2.0.12" 824 | source = "registry+https://github.com/rust-lang/crates.io-index" 825 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 826 | dependencies = [ 827 | "thiserror-impl", 828 | ] 829 | 830 | [[package]] 831 | name = "thiserror-impl" 832 | version = "2.0.12" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 835 | dependencies = [ 836 | "proc-macro2", 837 | "quote", 838 | "syn", 839 | ] 840 | 841 | [[package]] 842 | name = "tinystr" 843 | version = "0.7.6" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 846 | dependencies = [ 847 | "displaydoc", 848 | "zerovec", 849 | ] 850 | 851 | [[package]] 852 | name = "typify" 853 | version = "0.4.1" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "fcc5bec3cdff70fd542e579aa2e52967833e543a25fae0d14579043d2e868a50" 856 | dependencies = [ 857 | "typify-impl", 858 | "typify-macro", 859 | ] 860 | 861 | [[package]] 862 | name = "typify-impl" 863 | version = "0.4.1" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "b52a67305054e1da6f3d99ad94875dcd0c7c49adbd17b4b64f0eefb7ae5bf8ab" 866 | dependencies = [ 867 | "heck", 868 | "log", 869 | "proc-macro2", 870 | "quote", 871 | "regress", 872 | "schemars", 873 | "semver", 874 | "serde", 875 | "serde_json", 876 | "syn", 877 | "thiserror", 878 | "unicode-ident", 879 | ] 880 | 881 | [[package]] 882 | name = "typify-macro" 883 | version = "0.4.1" 884 | source = "registry+https://github.com/rust-lang/crates.io-index" 885 | checksum = "0ff5799be156e4f635c348c6051d165e1c59997827155133351a8c4d333d9841" 886 | dependencies = [ 887 | "proc-macro2", 888 | "quote", 889 | "schemars", 890 | "semver", 891 | "serde", 892 | "serde_json", 893 | "serde_tokenstream", 894 | "syn", 895 | "typify-impl", 896 | ] 897 | 898 | [[package]] 899 | name = "unicode-ident" 900 | version = "1.0.18" 901 | source = "registry+https://github.com/rust-lang/crates.io-index" 902 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 903 | 904 | [[package]] 905 | name = "unsafe-libyaml" 906 | version = "0.2.11" 907 | source = "registry+https://github.com/rust-lang/crates.io-index" 908 | checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" 909 | 910 | [[package]] 911 | name = "url" 912 | version = "2.5.4" 913 | source = "registry+https://github.com/rust-lang/crates.io-index" 914 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 915 | dependencies = [ 916 | "form_urlencoded", 917 | "idna", 918 | "percent-encoding", 919 | ] 920 | 921 | [[package]] 922 | name = "utf16_iter" 923 | version = "1.0.5" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 926 | 927 | [[package]] 928 | name = "utf8_iter" 929 | version = "1.0.4" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 932 | 933 | [[package]] 934 | name = "walkdir" 935 | version = "2.5.0" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 938 | dependencies = [ 939 | "same-file", 940 | "winapi-util", 941 | ] 942 | 943 | [[package]] 944 | name = "winapi" 945 | version = "0.3.9" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 948 | dependencies = [ 949 | "winapi-i686-pc-windows-gnu", 950 | "winapi-x86_64-pc-windows-gnu", 951 | ] 952 | 953 | [[package]] 954 | name = "winapi-i686-pc-windows-gnu" 955 | version = "0.4.0" 956 | source = "registry+https://github.com/rust-lang/crates.io-index" 957 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 958 | 959 | [[package]] 960 | name = "winapi-util" 961 | version = "0.1.5" 962 | source = "registry+https://github.com/rust-lang/crates.io-index" 963 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 964 | dependencies = [ 965 | "winapi", 966 | ] 967 | 968 | [[package]] 969 | name = "winapi-x86_64-pc-windows-gnu" 970 | version = "0.4.0" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 973 | 974 | [[package]] 975 | name = "windows-sys" 976 | version = "0.48.0" 977 | source = "registry+https://github.com/rust-lang/crates.io-index" 978 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 979 | dependencies = [ 980 | "windows-targets", 981 | ] 982 | 983 | [[package]] 984 | name = "windows-targets" 985 | version = "0.48.1" 986 | source = "registry+https://github.com/rust-lang/crates.io-index" 987 | checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" 988 | dependencies = [ 989 | "windows_aarch64_gnullvm", 990 | "windows_aarch64_msvc", 991 | "windows_i686_gnu", 992 | "windows_i686_msvc", 993 | "windows_x86_64_gnu", 994 | "windows_x86_64_gnullvm", 995 | "windows_x86_64_msvc", 996 | ] 997 | 998 | [[package]] 999 | name = "windows_aarch64_gnullvm" 1000 | version = "0.48.0" 1001 | source = "registry+https://github.com/rust-lang/crates.io-index" 1002 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" 1003 | 1004 | [[package]] 1005 | name = "windows_aarch64_msvc" 1006 | version = "0.48.0" 1007 | source = "registry+https://github.com/rust-lang/crates.io-index" 1008 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" 1009 | 1010 | [[package]] 1011 | name = "windows_i686_gnu" 1012 | version = "0.48.0" 1013 | source = "registry+https://github.com/rust-lang/crates.io-index" 1014 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" 1015 | 1016 | [[package]] 1017 | name = "windows_i686_msvc" 1018 | version = "0.48.0" 1019 | source = "registry+https://github.com/rust-lang/crates.io-index" 1020 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" 1021 | 1022 | [[package]] 1023 | name = "windows_x86_64_gnu" 1024 | version = "0.48.0" 1025 | source = "registry+https://github.com/rust-lang/crates.io-index" 1026 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" 1027 | 1028 | [[package]] 1029 | name = "windows_x86_64_gnullvm" 1030 | version = "0.48.0" 1031 | source = "registry+https://github.com/rust-lang/crates.io-index" 1032 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" 1033 | 1034 | [[package]] 1035 | name = "windows_x86_64_msvc" 1036 | version = "0.48.0" 1037 | source = "registry+https://github.com/rust-lang/crates.io-index" 1038 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" 1039 | 1040 | [[package]] 1041 | name = "write16" 1042 | version = "1.0.0" 1043 | source = "registry+https://github.com/rust-lang/crates.io-index" 1044 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 1045 | 1046 | [[package]] 1047 | name = "writeable" 1048 | version = "0.5.5" 1049 | source = "registry+https://github.com/rust-lang/crates.io-index" 1050 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 1051 | 1052 | [[package]] 1053 | name = "yoke" 1054 | version = "0.7.4" 1055 | source = "registry+https://github.com/rust-lang/crates.io-index" 1056 | checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" 1057 | dependencies = [ 1058 | "serde", 1059 | "stable_deref_trait", 1060 | "yoke-derive", 1061 | "zerofrom", 1062 | ] 1063 | 1064 | [[package]] 1065 | name = "yoke-derive" 1066 | version = "0.7.4" 1067 | source = "registry+https://github.com/rust-lang/crates.io-index" 1068 | checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" 1069 | dependencies = [ 1070 | "proc-macro2", 1071 | "quote", 1072 | "syn", 1073 | "synstructure", 1074 | ] 1075 | 1076 | [[package]] 1077 | name = "zerofrom" 1078 | version = "0.1.4" 1079 | source = "registry+https://github.com/rust-lang/crates.io-index" 1080 | checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" 1081 | dependencies = [ 1082 | "zerofrom-derive", 1083 | ] 1084 | 1085 | [[package]] 1086 | name = "zerofrom-derive" 1087 | version = "0.1.4" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" 1090 | dependencies = [ 1091 | "proc-macro2", 1092 | "quote", 1093 | "syn", 1094 | "synstructure", 1095 | ] 1096 | 1097 | [[package]] 1098 | name = "zerovec" 1099 | version = "0.10.4" 1100 | source = "registry+https://github.com/rust-lang/crates.io-index" 1101 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" 1102 | dependencies = [ 1103 | "yoke", 1104 | "zerofrom", 1105 | "zerovec-derive", 1106 | ] 1107 | 1108 | [[package]] 1109 | name = "zerovec-derive" 1110 | version = "0.10.3" 1111 | source = "registry+https://github.com/rust-lang/crates.io-index" 1112 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" 1113 | dependencies = [ 1114 | "proc-macro2", 1115 | "quote", 1116 | "syn", 1117 | ] 1118 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | [package] 3 | name = "substrait" 4 | version = "0.57.0" 5 | edition = "2021" 6 | rust-version = "1.80.1" 7 | description = "Cross-Language Serialization for Relational Algebra" 8 | documentation = "https://docs.rs/substrait" 9 | readme = "README.md" 10 | homepage = "https://substrait.io" 11 | repository = "https://github.com/substrait-io/substrait-rs" 12 | license = "Apache-2.0" 13 | keywords = ["substrait"] 14 | build = "build.rs" 15 | include = [ 16 | "LICENSE", 17 | "build.rs", 18 | "gen/", 19 | "src/**/*.rs", 20 | "substrait/LICENSE", 21 | "substrait/README.md", 22 | "substrait/extensions/**/*.yaml", 23 | "substrait/proto/**/*.proto", 24 | "substrait/text/**/*.yaml", 25 | ] 26 | 27 | [features] 28 | default = [] 29 | extensions = ["dep:serde_yaml"] 30 | parse = ["dep:hex", "dep:thiserror", "dep:url", "semver"] 31 | protoc = ["dep:protobuf-src"] 32 | semver = ["dep:semver"] 33 | serde = ["dep:pbjson", "dep:pbjson-build", "dep:pbjson-types"] 34 | 35 | [dependencies] 36 | hex = { version = "0.4.3", optional = true } 37 | pbjson = { version = "0.7.0", optional = true } 38 | pbjson-types = { version = "0.7.0", optional = true } 39 | prost = "0.13.5" 40 | prost-types = "0.13.5" 41 | url = { version = "2.5.4", optional = true } 42 | regress = "0.10.3" 43 | semver = { version = "1.0.26", optional = true } 44 | serde = { version = "1.0.219", features = ["derive"] } 45 | serde_json = "1.0.140" 46 | serde_yaml = { version = "0.9.34", optional = true } 47 | thiserror = { version = "2.0.12", optional = true } 48 | 49 | [build-dependencies] 50 | heck = "0.5.0" 51 | pbjson-build = { version = "0.7.0", optional = true } 52 | prettyplease = "0.2.32" 53 | prost-build = { version = "0.13.5", default-features = false } 54 | protobuf-src = { version = "2.1.1", optional = true } 55 | schemars = "0.8.22" 56 | semver = "1.0.26" 57 | serde_yaml = "0.9.34" 58 | syn = "2.0.101" 59 | typify = "0.4.1" 60 | walkdir = "2.5.0" 61 | 62 | [package.metadata.docs.rs] 63 | all-features = true 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the reference the license in your work: 179 | 180 | Add the following as a comment header in your files: 181 | 182 | SPDX-License-Identifier: Apache-2.0 183 | 184 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | # substrait-rs 6 | 7 | [![substrait](https://raw.githubusercontent.com/substrait-io/substrait/main/site/docs/img/logo.svg)](https://substrait.io) 8 | 9 | [![crates.io](https://img.shields.io/crates/v/substrait.svg)](https://crates.io/crates/substrait) 10 | [![docs.rs](https://docs.rs/substrait/badge.svg)](https://docs.rs/substrait) 11 | 12 | Rust crate for [Substrait](https://substrait.io/): Cross-Language Serialization for Relational Algebra. 13 | 14 | ## Documentation 15 | 16 | - [Docs (release)](https://docs.rs/substrait) 17 | - [Docs (main)](https://substrait-io.github.io/substrait-rs/) 18 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | use prost_build::Config; 4 | use std::{ 5 | env, 6 | error::Error, 7 | fs::{self, File}, 8 | io::Write, 9 | path::{Path, PathBuf}, 10 | process::Command, 11 | }; 12 | use walkdir::{DirEntry, WalkDir}; 13 | 14 | const SUBMODULE_ROOT: &str = "substrait"; 15 | #[cfg(feature = "extensions")] 16 | const EXTENSIONS_ROOT: &str = "substrait/extensions"; 17 | const PROTO_ROOT: &str = "substrait/proto"; 18 | const TEXT_ROOT: &str = "substrait/text"; 19 | const GEN_ROOT: &str = "gen"; 20 | 21 | /// Add Substrait version information to the build 22 | fn substrait_version() -> Result> { 23 | let gen_dir = Path::new(GEN_ROOT); 24 | fs::create_dir_all(gen_dir)?; 25 | 26 | let version_in_file = gen_dir.join("version.in"); 27 | let substrait_version_file = gen_dir.join("version"); 28 | 29 | // Rerun if the Substrait submodule changed (to allow setting `dirty`) 30 | println!( 31 | "cargo:rerun-if-changed={}", 32 | Path::new("substrait").display() 33 | ); 34 | 35 | // Check if there is a submodule. This file is not included in the packaged crate. 36 | if Path::new(SUBMODULE_ROOT).join(".git").exists() { 37 | // Rerun if the Substrait submodule HEAD changed (when there is a submodule) 38 | println!( 39 | "cargo:rerun-if-changed={}", 40 | Path::new(".git/modules/substrait/HEAD").display() 41 | ); 42 | 43 | // Get the version of the submodule by directly calling `git describe`. 44 | let git_describe = String::from_utf8( 45 | Command::new("git") 46 | .current_dir(SUBMODULE_ROOT) 47 | .arg("describe") 48 | .arg("--tags") 49 | .arg("--long") 50 | .arg("--dirty=-dirty") 51 | .arg("--abbrev=40") 52 | .output()? 53 | .stdout, 54 | )?; 55 | 56 | // Extract the parts. 57 | let mut split = git_describe.split('-'); 58 | let git_version = split.next().unwrap_or_default(); 59 | let git_depth = split.next().unwrap_or_default(); 60 | let git_hash = split.next().unwrap_or_default().trim_end(); 61 | let git_dirty = git_describe.ends_with("dirty"); 62 | let version = semver::Version::parse(git_version.trim_start_matches('v'))?; 63 | 64 | let &semver::Version { 65 | major, 66 | minor, 67 | patch, 68 | .. 69 | } = &version; 70 | 71 | fs::write( 72 | version_in_file, 73 | format!( 74 | r#"// SPDX-License-Identifier: Apache-2.0 75 | 76 | // Note that this file is auto-generated and auto-synced using `build.rs`. It is 77 | // included in `version.rs`. 78 | 79 | /// The major version of Substrait used to build this crate 80 | pub const SUBSTRAIT_MAJOR_VERSION: u32 = {major}; 81 | 82 | /// The minor version of Substrait used to build this crate 83 | pub const SUBSTRAIT_MINOR_VERSION: u32 = {minor}; 84 | 85 | /// The patch version of Substrait used to build this crate 86 | pub const SUBSTRAIT_PATCH_VERSION: u32 = {patch}; 87 | 88 | /// The Git SHA (lower hex) of Substrait used to build this crate 89 | pub const SUBSTRAIT_GIT_SHA: &str = "{git_hash}"; 90 | 91 | /// The `git describe` output of the Substrait submodule used to build this 92 | /// crate 93 | pub const SUBSTRAIT_GIT_DESCRIBE: &str = "{git_describe}"; 94 | 95 | /// The amount of commits between the latest tag and the version of the 96 | /// Substrait submodule used to build this crate 97 | pub const SUBSTRAIT_GIT_DEPTH: u32 = {git_depth}; 98 | 99 | /// The dirty state of the Substrait submodule used to build this crate 100 | pub const SUBSTRAIT_GIT_DIRTY: bool = {git_dirty}; 101 | 102 | /// A constant with the Substrait version as name, to trigger semver bumps when 103 | /// the Substrait version changes. 104 | #[doc(hidden)] 105 | pub const SUBSTRAIT_{major}_{minor}_{patch}: () = (); 106 | "# 107 | ), 108 | )?; 109 | 110 | // Also write the version to a file 111 | fs::write(substrait_version_file, version.to_string())?; 112 | 113 | Ok(version) 114 | } else { 115 | // If we don't have a version file yet we fail the build. 116 | if !version_in_file.exists() { 117 | panic!("Couldn't find the substrait submodule. Please clone the submodule: `git submodule update --init`.") 118 | } 119 | 120 | // File exists we should get the version and return it. 121 | Ok(semver::Version::parse(&fs::read_to_string( 122 | substrait_version_file, 123 | )?)?) 124 | } 125 | } 126 | 127 | /// `text` type generation 128 | fn text(out_dir: &Path) -> Result<(), Box> { 129 | use heck::ToSnakeCase; 130 | use schemars::schema::{RootSchema, Schema}; 131 | use typify::{TypeSpace, TypeSpaceSettings}; 132 | 133 | let mut out_file = File::create(out_dir.join("substrait_text").with_extension("rs"))?; 134 | 135 | for schema_path in WalkDir::new(TEXT_ROOT) 136 | .into_iter() 137 | .filter_map(Result::ok) 138 | .filter(|entry| entry.file_type().is_file() || entry.file_type().is_symlink()) 139 | .filter(|entry| { 140 | entry 141 | .path() 142 | .extension() 143 | .filter(|&extension| extension == "yaml") // Option::contains 144 | .is_some() 145 | }) 146 | .map(DirEntry::into_path) 147 | .inspect(|entry| { 148 | println!("cargo:rerun-if-changed={}", entry.display()); 149 | }) 150 | { 151 | let schema = serde_yaml::from_reader::<_, RootSchema>(File::open(&schema_path)?)?; 152 | let metadata = schema.schema.metadata.as_ref(); 153 | let id = metadata 154 | .and_then(|metadata| metadata.id.as_ref()) 155 | .map(ToString::to_string) 156 | .unwrap_or_else(|| { 157 | panic!( 158 | "$id missing in schema metadata (`{}`)", 159 | schema_path.display() 160 | ) 161 | }); 162 | let title = metadata 163 | .and_then(|metadata| metadata.title.as_ref()) 164 | .map(|title| title.to_snake_case()) 165 | .unwrap_or_else(|| { 166 | panic!( 167 | "title missing in schema metadata (`{}`)", 168 | schema_path.display() 169 | ) 170 | }); 171 | let mut type_space = TypeSpace::new(TypeSpaceSettings::default().with_struct_builder(true)); 172 | type_space.add_ref_types(schema.definitions)?; 173 | type_space.add_type(&Schema::Object(schema.schema))?; 174 | out_file.write_fmt(format_args!( 175 | r#" 176 | #[doc = "Generated types for `{id}`"] 177 | pub mod {title} {{ 178 | {} 179 | }}"#, 180 | prettyplease::unparse(&syn::parse2::(type_space.to_stream())?), 181 | ))?; 182 | } 183 | Ok(()) 184 | } 185 | 186 | #[cfg(feature = "extensions")] 187 | /// Add Substrait core extensions 188 | fn extensions(version: semver::Version, out_dir: &Path) -> Result<(), Box> { 189 | use std::collections::HashMap; 190 | 191 | let substrait_extensions_file = out_dir.join("extensions.in"); 192 | 193 | let mut output = String::from( 194 | r#"// SPDX-License-Identifier: Apache-2.0 195 | // Note that this file is auto-generated and auto-synced using `build.rs`. It is 196 | // included in `extensions.rs`. 197 | "#, 198 | ); 199 | let mut map = HashMap::::default(); 200 | for extension in WalkDir::new(EXTENSIONS_ROOT) 201 | .into_iter() 202 | .filter_map(Result::ok) 203 | .filter(|entry| entry.file_type().is_file()) 204 | .filter(|entry| { 205 | entry 206 | .path() 207 | .extension() 208 | .filter(|&extension| extension == "yaml") 209 | .is_some() 210 | }) 211 | .map(DirEntry::into_path) 212 | .inspect(|entry| { 213 | println!("cargo:rerun-if-changed={}", entry.display()); 214 | }) 215 | { 216 | let name = extension.file_stem().unwrap_or_default().to_string_lossy(); 217 | let url = format!( 218 | "https://github.com/substrait-io/substrait/raw/v{}/extensions/{}", 219 | version, 220 | extension.file_name().unwrap_or_default().to_string_lossy() 221 | ); 222 | let var_name = name.to_uppercase(); 223 | output.push_str(&format!( 224 | r#" 225 | /// Included source of [`{name}`]({url}). 226 | const {var_name}: &str = include_str!("{}/{}"); 227 | "#, 228 | PathBuf::from(dbg!(env::var("CARGO_MANIFEST_DIR").unwrap())).display(), 229 | extension.display() 230 | )); 231 | map.insert(url, var_name); 232 | } 233 | // Add static lookup map. 234 | output.push_str( 235 | r#" 236 | use std::collections::HashMap; 237 | use std::str::FromStr; 238 | use std::sync::LazyLock; 239 | use crate::text::simple_extensions::SimpleExtensions; 240 | use url::Url; 241 | 242 | /// Map with Substrait core extensions. Maps URIs to included extensions. 243 | pub static EXTENSIONS: LazyLock> = LazyLock::new(|| { 244 | let mut map = HashMap::new();"#, 245 | ); 246 | 247 | for (url, var_name) in map { 248 | output.push_str(&format!(r#" 249 | map.insert(Url::from_str("{url}").expect("a valid url"), serde_yaml::from_str({var_name}).expect("a valid core extension"));"#)); 250 | } 251 | 252 | output.push_str( 253 | r#" 254 | map 255 | });"#, 256 | ); 257 | 258 | // Write the file. 259 | fs::write(substrait_extensions_file, output)?; 260 | 261 | Ok(()) 262 | } 263 | 264 | #[cfg(feature = "serde")] 265 | /// Serialize and deserialize implementations for proto types using `pbjson` 266 | fn serde(protos: &[impl AsRef], out_dir: PathBuf) -> Result<(), Box> { 267 | use pbjson_build::Builder; 268 | 269 | let descriptor_path = out_dir.join("proto_descriptor.bin"); 270 | let mut cfg = Config::new(); 271 | cfg.file_descriptor_set_path(&descriptor_path); 272 | cfg.compile_well_known_types() 273 | .extern_path(".google.protobuf", "::pbjson_types") 274 | .compile_protos(protos, &[PROTO_ROOT])?; 275 | 276 | Builder::new() 277 | .register_descriptors(&fs::read(descriptor_path)?)? 278 | .build(&[".substrait"])?; 279 | 280 | Ok(()) 281 | } 282 | 283 | fn main() -> Result<(), Box> { 284 | // for use in docker build where file changes can be wonky 285 | println!("cargo:rerun-if-env-changed=FORCE_REBUILD"); 286 | 287 | let _version = substrait_version()?; 288 | 289 | #[cfg(feature = "protoc")] 290 | std::env::set_var("PROTOC", protobuf_src::protoc()); 291 | 292 | let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); 293 | 294 | text(out_dir.as_path())?; 295 | 296 | #[cfg(feature = "extensions")] 297 | extensions(_version, out_dir.as_path())?; 298 | 299 | let protos = WalkDir::new(PROTO_ROOT) 300 | .into_iter() 301 | .filter_map(Result::ok) 302 | .filter(|entry| entry.file_type().is_file() || entry.file_type().is_symlink()) 303 | .filter(|entry| { 304 | entry 305 | .path() 306 | .extension() 307 | .filter(|&extension| extension == "proto") 308 | .is_some() 309 | }) 310 | .map(DirEntry::into_path) 311 | .inspect(|entry| { 312 | println!("cargo:rerun-if-changed={}", entry.display()); 313 | }) 314 | .collect::>(); 315 | 316 | #[cfg(feature = "serde")] 317 | serde(&protos, out_dir)?; 318 | 319 | #[cfg(not(feature = "serde"))] 320 | Config::new().compile_protos(&protos, &[PROTO_ROOT])?; 321 | 322 | Ok(()) 323 | } 324 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["@commitlint/config-conventional"], 3 | rules: { 4 | "body-max-line-length": [0, "always", Infinity], 5 | "footer-max-line-length": [0, "always", Infinity], 6 | "header-max-length": [0, "always", Infinity], 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /release-plz.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | release_always = false 3 | 4 | [[package]] 5 | name = "substrait" 6 | publish_allow_dirty = true 7 | -------------------------------------------------------------------------------- /src/extensions.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | //! Substrait core extensions 4 | //! 5 | //! The contents of this module are auto-generated using `build.rs`. It is 6 | //! included in the packaged crate, ignored by git, and automatically kept 7 | //! in-sync. 8 | 9 | include!(concat!(env!("OUT_DIR"), "/extensions.in")); 10 | 11 | #[cfg(test)] 12 | mod tests { 13 | use super::*; 14 | 15 | use std::sync::LazyLock; 16 | 17 | #[test] 18 | fn core_extensions() { 19 | // Force evaluation of core extensions. 20 | LazyLock::force(&EXTENSIONS); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | //! [Substrait]: Cross-Language Serialization for Relational Algebra 4 | //! 5 | //! # Serialization and deserialization 6 | //! 7 | //! This crate provides generated types to serialize and deserialize Substrait 8 | //! data. 9 | //! 10 | //! ## Protobuf 11 | //! 12 | //! Protobuf serialization and deserialization are provided via [prost] in the 13 | //! [proto] module. 14 | //! 15 | //! ### Example 16 | //! 17 | //! #### Serialize and deserialize a plan 18 | //! ```rust 19 | //! # fn main() -> Result<(), prost::DecodeError> { 20 | //! use prost::Message; 21 | //! use substrait::proto::Plan; 22 | //! 23 | //! let plan = Plan::default(); 24 | //! 25 | //! // Serialize the plan 26 | //! let encoded = plan.encode_to_vec(); 27 | //! 28 | //! // Deserialize the buffer to a Plan 29 | //! let decoded = Plan::decode(encoded.as_slice())?; 30 | //! 31 | //! assert_eq!(plan, decoded); 32 | //! # Ok(()) } 33 | //! ``` 34 | //! 35 | //! ### Serde support 36 | //! 37 | //! The `serde` feature generates serde implementations that match the [Protobuf JSON Mapping] 38 | //! via [pbjson]. 39 | //! 40 | //! ##### Example 41 | //! ###### Deserialize a plan version using the `serde` feature 42 | //! ```rust 43 | //! # fn main() -> Result<(), serde_json::Error> { 44 | //! # #[cfg(feature="serde")] { 45 | //! use substrait::proto::Version; 46 | //! 47 | //! let version_json = r#"{ 48 | //! "minorNumber": 21 49 | //! }"#; 50 | //! 51 | //! let version = serde_json::from_str::(version_json)?; 52 | //! assert_eq!( 53 | //! version, 54 | //! Version { 55 | //! minor_number: 21, 56 | //! ..Default::default() 57 | //! } 58 | //! ); 59 | //! # } Ok(()) } 60 | //! ``` 61 | //! 62 | //! ## Text 63 | //! 64 | //! Substrait defines a YAML schema for extensions. Types with serialization and 65 | //! deserialization support for these are provided via [typify] in the [text] 66 | //! module. 67 | //! 68 | //! ### Example 69 | //! 70 | //! #### Read a simple extension 71 | //! ```rust 72 | //! # #[cfg(feature="extensions")] 73 | //! # fn main() -> Result<(), serde_yaml::Error> { 74 | //! use substrait::text::simple_extensions::SimpleExtensions; 75 | //! 76 | //! let simple_extension_yaml = r#" 77 | //! %YAML 1.2 78 | //! --- 79 | //! scalar_functions: 80 | //! - 81 | //! name: "add" 82 | //! description: "Add two values." 83 | //! impls: 84 | //! - args: 85 | //! - name: x 86 | //! value: i8 87 | //! - name: y 88 | //! value: i8 89 | //! options: 90 | //! overflow: 91 | //! values: [ SILENT, SATURATE, ERROR ] 92 | //! return: i8 93 | //! "#; 94 | //! 95 | //! let simple_extension = serde_yaml::from_str::(simple_extension_yaml)?; 96 | //! 97 | //! assert_eq!(simple_extension.scalar_functions.len(), 1); 98 | //! assert_eq!(simple_extension.scalar_functions[0].name, "add"); 99 | //! # Ok(()) } 100 | //! # #[cfg(not(feature="extensions"))] 101 | //! # fn main() {} 102 | //! ``` 103 | //! 104 | //! [pbjson]: https://docs.rs/pbjson 105 | //! [Protobuf JSON Mapping]: 106 | //! https://developers.google.com/protocol-buffers/docs/proto3#json 107 | //! [Substrait]: https://substrait.io 108 | //! [typify]: https://docs.rs/typify 109 | 110 | #![doc( 111 | html_logo_url = "https://raw.githubusercontent.com/substrait-io/substrait/main/site/docs/img/logo.svg", 112 | html_favicon_url = "https://raw.githubusercontent.com/substrait-io/substrait/main/site/docs/img/logo.svg" 113 | )] 114 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 115 | #![deny(missing_docs)] 116 | 117 | #[cfg(feature = "extensions")] 118 | pub mod extensions; 119 | #[allow(missing_docs)] 120 | pub mod proto; 121 | #[allow(missing_docs)] 122 | pub mod text; 123 | pub mod version; 124 | 125 | #[cfg(feature = "parse")] 126 | pub mod parse; 127 | -------------------------------------------------------------------------------- /src/parse/context.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | //! A parse context. 4 | 5 | use thiserror::Error; 6 | 7 | use crate::parse::{ 8 | proto::extensions::SimpleExtensionUri, text::simple_extensions::SimpleExtensions, Anchor, Parse, 9 | }; 10 | 11 | /// A parse context. 12 | /// 13 | /// Parsing Substrait data is context-sensitive. This trait provides methods 14 | /// that can be used by parser implementations to parse Substrait data. 15 | pub trait Context { 16 | /// Parse an item with this context. 17 | /// 18 | /// See [Parse::parse]. 19 | fn parse>(&mut self, item: T) -> Result 20 | where 21 | Self: Sized, 22 | { 23 | item.parse(self) 24 | } 25 | 26 | /// Add a [SimpleExtensionUri] to this context. Must return an error for duplicate 27 | /// anchors or when the URI is not supported. 28 | /// 29 | /// This function must eagerly resolve and parse the simple extension, returning an 30 | /// error if either fails. 31 | fn add_simple_extension_uri( 32 | &mut self, 33 | simple_extension_uri: &SimpleExtensionUri, 34 | ) -> Result<&SimpleExtensions, ContextError>; 35 | 36 | /// Returns the simple extensions for the given simple extension anchor. 37 | fn simple_extensions( 38 | &self, 39 | anchor: &Anchor, 40 | ) -> Result<&SimpleExtensions, ContextError>; 41 | } 42 | 43 | /// Parse context errors. 44 | #[derive(Debug, Error, PartialEq)] 45 | pub enum ContextError { 46 | /// Undefined reference to simple extension. 47 | #[error("undefined reference to simple extension with anchor `{0}`")] 48 | UndefinedSimpleExtension(Anchor), 49 | 50 | /// Duplicate anchor for simple extension. 51 | #[error("duplicate anchor `{0}` for simple extension")] 52 | DuplicateSimpleExtension(Anchor), 53 | 54 | /// Unsupported simple extension URI. 55 | #[error("unsupported simple extension URI: {0}")] 56 | UnsupportedURI(String), 57 | } 58 | 59 | #[cfg(test)] 60 | pub(crate) mod tests { 61 | use std::collections::{hash_map::Entry, HashMap}; 62 | 63 | use crate::parse::{ 64 | context::ContextError, proto::extensions::SimpleExtensionUri, 65 | text::simple_extensions::SimpleExtensions, Anchor, 66 | }; 67 | 68 | /// A test context. 69 | /// 70 | /// This currently mocks support for simple extensions (does not resolve or 71 | /// parse). 72 | pub struct Context { 73 | empty_simple_extensions: SimpleExtensions, 74 | simple_extensions: HashMap, SimpleExtensionUri>, 75 | } 76 | 77 | impl Default for Context { 78 | fn default() -> Self { 79 | Self { 80 | empty_simple_extensions: SimpleExtensions {}, 81 | simple_extensions: Default::default(), 82 | } 83 | } 84 | } 85 | 86 | impl super::Context for Context { 87 | fn add_simple_extension_uri( 88 | &mut self, 89 | simple_extension_uri: &crate::parse::proto::extensions::SimpleExtensionUri, 90 | ) -> Result<&SimpleExtensions, ContextError> { 91 | match self.simple_extensions.entry(simple_extension_uri.anchor()) { 92 | Entry::Occupied(_) => Err(ContextError::DuplicateSimpleExtension( 93 | simple_extension_uri.anchor(), 94 | )), 95 | Entry::Vacant(entry) => { 96 | // This is where we would resolve and then parse. 97 | // This check shows the use of the unsupported uri error. 98 | if let "http" | "https" | "file" = simple_extension_uri.uri().scheme() { 99 | entry.insert(simple_extension_uri.clone()); 100 | // Here we just return an empty simple extensions. 101 | Ok(&self.empty_simple_extensions) 102 | } else { 103 | Err(ContextError::UnsupportedURI(format!( 104 | "`{}` scheme not supported", 105 | simple_extension_uri.uri().scheme() 106 | ))) 107 | } 108 | } 109 | } 110 | } 111 | 112 | fn simple_extensions( 113 | &self, 114 | anchor: &Anchor, 115 | ) -> Result<&SimpleExtensions, ContextError> { 116 | self.simple_extensions 117 | .contains_key(anchor) 118 | .then_some(&self.empty_simple_extensions) 119 | .ok_or(ContextError::UndefinedSimpleExtension(*anchor)) 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/parse/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | //! Parsing of Substrait data. 4 | //! 5 | //! Some requirements of Substrait data can not be expressed via Protobuf 6 | //! definition or schema files. This module provides new types for the generated 7 | //! types, that when constructed are known to be checked. This enables producers 8 | //! and consumers to skip redundant checking of invariants described by the 9 | //! specification. 10 | //! 11 | //! This is based on the idea described in the [Parse don't 12 | //! validate](https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/) 13 | //! blog post. 14 | 15 | use std::{error::Error, fmt::Debug}; 16 | 17 | mod context; 18 | pub use context::Context; 19 | 20 | pub mod proto; 21 | pub mod text; 22 | 23 | mod typed; 24 | pub use typed::Anchor; 25 | 26 | /// A parse trait. 27 | pub trait Parse: Debug + Sized { 28 | /// The parsed type. 29 | /// 30 | /// After parsing this type must be able to convert back. Note that it is 31 | /// not required for the conversion to be lossless as long as the semantics 32 | /// don't change. 33 | /// 34 | // This bound also helps with tracking breaking Protobuf definition changes 35 | // via compilation errors. 36 | type Parsed: Into; 37 | 38 | /// The error type for this parser. 39 | type Error: Error; 40 | 41 | /// Parse and return a parsed type or error. 42 | fn parse(self, ctx: &mut C) -> Result; 43 | } 44 | -------------------------------------------------------------------------------- /src/parse/proto/extensions/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | //! Parsing of [crate::proto::extensions] types. 4 | 5 | mod simple_extension_uri; 6 | pub use simple_extension_uri::SimpleExtensionUri; 7 | -------------------------------------------------------------------------------- /src/parse/proto/extensions/simple_extension_uri.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | //! Parsing of [proto::extensions::SimpleExtensionUri]. 4 | 5 | use thiserror::Error; 6 | use url::Url; 7 | 8 | use crate::{ 9 | parse::{context::ContextError, Anchor, Context, Parse}, 10 | proto, 11 | }; 12 | 13 | /// A parsed [proto::extensions::SimpleExtensionUri]. 14 | #[derive(Clone, Debug, PartialEq)] 15 | pub struct SimpleExtensionUri { 16 | /// The URI of this simple extension. 17 | uri: Url, 18 | 19 | /// The anchor value of this simple extension. 20 | anchor: Anchor, 21 | } 22 | 23 | impl SimpleExtensionUri { 24 | /// Returns the uri of this simple extension. 25 | /// 26 | /// See [proto::extensions::SimpleExtensionUri::uri]. 27 | pub fn uri(&self) -> &Url { 28 | &self.uri 29 | } 30 | 31 | /// Returns the anchor value of this simple extension. 32 | /// 33 | /// See [proto::extensions::SimpleExtensionUri::extension_uri_anchor]. 34 | pub fn anchor(&self) -> Anchor { 35 | self.anchor 36 | } 37 | } 38 | 39 | /// Parse errors for [proto::extensions::SimpleExtensionUri]. 40 | #[derive(Debug, Error, PartialEq)] 41 | pub enum SimpleExtensionUriError { 42 | /// Invalid URI 43 | #[error("invalid URI: {0}")] 44 | InvalidURI(#[from] url::ParseError), 45 | 46 | /// Context error 47 | #[error(transparent)] 48 | Context(#[from] ContextError), 49 | } 50 | 51 | impl Parse for proto::extensions::SimpleExtensionUri { 52 | type Parsed = SimpleExtensionUri; 53 | type Error = SimpleExtensionUriError; 54 | 55 | fn parse(self, ctx: &mut C) -> Result { 56 | let proto::extensions::SimpleExtensionUri { 57 | extension_uri_anchor: anchor, 58 | uri, 59 | } = self; 60 | 61 | // The uri is is required and must be valid. 62 | let uri = Url::parse(&uri)?; 63 | 64 | // Construct the parsed simple extension URI. 65 | let simple_extension_uri = SimpleExtensionUri { 66 | uri, 67 | anchor: Anchor::new(anchor), 68 | }; 69 | 70 | // Make sure the URI is supported by this parse context, resolves and 71 | // parses, and the anchor is unique. 72 | ctx.add_simple_extension_uri(&simple_extension_uri)?; 73 | 74 | Ok(simple_extension_uri) 75 | } 76 | } 77 | 78 | impl From for proto::extensions::SimpleExtensionUri { 79 | fn from(simple_extension_uri: SimpleExtensionUri) -> Self { 80 | let SimpleExtensionUri { uri, anchor } = simple_extension_uri; 81 | proto::extensions::SimpleExtensionUri { 82 | uri: uri.to_string(), 83 | extension_uri_anchor: anchor.into_inner(), 84 | } 85 | } 86 | } 87 | 88 | #[cfg(test)] 89 | mod tests { 90 | use super::*; 91 | use crate::parse::{context::tests::Context, Context as _}; 92 | 93 | #[test] 94 | fn parse() -> Result<(), SimpleExtensionUriError> { 95 | let simple_extension_uri = proto::extensions::SimpleExtensionUri { 96 | extension_uri_anchor: 1, 97 | uri: "https://substrait.io".to_string(), 98 | }; 99 | let simple_extension_uri = simple_extension_uri.parse(&mut Context::default())?; 100 | assert_eq!(simple_extension_uri.anchor(), Anchor::new(1)); 101 | assert_eq!(simple_extension_uri.uri().as_str(), "https://substrait.io/"); 102 | Ok(()) 103 | } 104 | 105 | #[test] 106 | fn invalid_uri() { 107 | let simple_extension_uri = proto::extensions::SimpleExtensionUri::default(); 108 | assert_eq!( 109 | simple_extension_uri.parse(&mut Context::default()), 110 | Err(SimpleExtensionUriError::InvalidURI( 111 | url::ParseError::RelativeUrlWithoutBase 112 | )) 113 | ); 114 | let simple_extension_uri = proto::extensions::SimpleExtensionUri { 115 | extension_uri_anchor: 1, 116 | uri: "http://".to_string(), 117 | }; 118 | assert_eq!( 119 | simple_extension_uri.parse(&mut Context::default()), 120 | Err(SimpleExtensionUriError::InvalidURI( 121 | url::ParseError::EmptyHost 122 | )) 123 | ); 124 | } 125 | 126 | #[test] 127 | fn duplicate_simple_extension() { 128 | let mut ctx = Context::default(); 129 | let simple_extension_uri = proto::extensions::SimpleExtensionUri { 130 | extension_uri_anchor: 1, 131 | uri: "https://substrait.io".to_string(), 132 | }; 133 | assert!(ctx.parse(simple_extension_uri.clone()).is_ok()); 134 | assert_eq!( 135 | ctx.parse(simple_extension_uri), 136 | Err(SimpleExtensionUriError::Context( 137 | ContextError::DuplicateSimpleExtension(Anchor::new(1)) 138 | )) 139 | ); 140 | } 141 | 142 | #[test] 143 | fn unsupported_uri() { 144 | let simple_extension_uri = proto::extensions::SimpleExtensionUri { 145 | extension_uri_anchor: 1, 146 | uri: "ftp://substrait.io".to_string(), 147 | }; 148 | assert_eq!( 149 | simple_extension_uri.parse(&mut Context::default()), 150 | Err(SimpleExtensionUriError::Context( 151 | ContextError::UnsupportedURI("`ftp` scheme not supported".to_string()) 152 | )) 153 | ); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/parse/proto/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | //! Parsing of [proto](crate::proto) types. 4 | 5 | mod version; 6 | pub use version::{Version, VersionError}; 7 | 8 | mod plan_version; 9 | pub use plan_version::{PlanVersion, PlanVersionError}; 10 | 11 | pub mod extensions; 12 | -------------------------------------------------------------------------------- /src/parse/proto/plan_version.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | //! Parsing of [proto::PlanVersion]. 4 | 5 | use crate::{ 6 | parse::{context::Context, proto::Version, Parse}, 7 | proto, 8 | }; 9 | use thiserror::Error; 10 | 11 | use super::VersionError; 12 | 13 | /// A parsed [proto::PlanVersion]. 14 | #[derive(Clone, Debug, PartialEq)] 15 | pub struct PlanVersion { 16 | /// The version of the plan. 17 | version: Version, 18 | } 19 | 20 | impl PlanVersion { 21 | /// Returns the version of this plan version. 22 | /// 23 | /// See [proto::PlanVersion::version]. 24 | pub fn version(&self) -> &Version { 25 | &self.version 26 | } 27 | } 28 | 29 | /// Parse errors for [proto::PlanVersion]. 30 | #[derive(Debug, Error, PartialEq)] 31 | pub enum PlanVersionError { 32 | /// Version is missing. 33 | #[error("version must be specified")] 34 | Missing, 35 | 36 | /// Version error. 37 | #[error("version must be valid")] 38 | Version(#[from] VersionError), 39 | } 40 | 41 | impl Parse for proto::PlanVersion { 42 | type Parsed = PlanVersion; 43 | type Error = PlanVersionError; 44 | 45 | fn parse(self, ctx: &mut C) -> Result { 46 | let proto::PlanVersion { version } = self; 47 | 48 | // The version is required, and must be valid. 49 | let version = version 50 | .map(|version| ctx.parse(version)) 51 | .transpose()? 52 | .ok_or(PlanVersionError::Missing)?; 53 | 54 | let plan_version = PlanVersion { version }; 55 | 56 | Ok(plan_version) 57 | } 58 | } 59 | 60 | impl From for proto::PlanVersion { 61 | fn from(plan_version: PlanVersion) -> Self { 62 | let PlanVersion { version } = plan_version; 63 | 64 | proto::PlanVersion { 65 | version: Some(version.into()), 66 | } 67 | } 68 | } 69 | 70 | #[cfg(test)] 71 | mod tests { 72 | use super::*; 73 | use crate::{ 74 | parse::{context::tests::Context, proto::VersionError}, 75 | version, 76 | }; 77 | 78 | #[test] 79 | fn parse() -> Result<(), PlanVersionError> { 80 | let plan_version = proto::PlanVersion { 81 | version: Some(version::version()), 82 | }; 83 | plan_version.parse(&mut Context::default())?; 84 | Ok(()) 85 | } 86 | 87 | #[test] 88 | fn missing() { 89 | let plan_version = proto::PlanVersion::default(); 90 | assert_eq!( 91 | plan_version.parse(&mut Context::default()), 92 | Err(PlanVersionError::Missing) 93 | ); 94 | } 95 | 96 | #[test] 97 | fn version_error() { 98 | let plan_version = proto::PlanVersion { 99 | version: Some(proto::Version::default()), 100 | }; 101 | assert_eq!( 102 | plan_version.parse(&mut Context::default()), 103 | Err(PlanVersionError::Version(VersionError::Missing)) 104 | ); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/parse/proto/version.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | //! Parsing of [proto::Version]. 4 | 5 | use crate::{ 6 | parse::{context::Context, Parse}, 7 | proto, version, 8 | }; 9 | use hex::FromHex; 10 | use thiserror::Error; 11 | 12 | /// A parsed [proto::Version]. 13 | /// 14 | /// This parses only for compatible versions. See [`version::semver_req`]. 15 | #[derive(Clone, Debug, PartialEq)] 16 | pub struct Version { 17 | /// The semantic version. 18 | version: semver::Version, 19 | /// The git hash if set as bytes. 20 | git_hash: Option<[u8; 20]>, 21 | /// The producer string if set. 22 | producer: Option, 23 | } 24 | 25 | impl Version { 26 | /// Returns the semantic version of this version. 27 | /// 28 | /// See [proto::Version::major_number], [proto::Version::minor_number] and 29 | /// [proto::Version::patch_number]. 30 | pub fn version(&self) -> &semver::Version { 31 | &self.version 32 | } 33 | 34 | /// Returns the git hash of this version. 35 | /// 36 | /// See [proto::Version::git_hash]. 37 | pub fn git_hash(&self) -> Option<&[u8; 20]> { 38 | self.git_hash.as_ref() 39 | } 40 | 41 | /// Returns the producer of this version. 42 | /// 43 | /// See [proto::Version::producer]. 44 | pub fn producer(&self) -> Option<&str> { 45 | self.producer.as_deref() 46 | } 47 | 48 | /// Returns [VersionError::Substrait] if this version is incompatible with 49 | /// the Substrait [version::version] of this crate. 50 | pub(crate) fn compatible(&self) -> Result<(), VersionError> { 51 | let version = self.version(); 52 | let version_req = version::semver_req(); 53 | version_req 54 | .matches(version) 55 | .then_some(()) 56 | .ok_or_else(|| VersionError::Substrait(version.clone(), version_req)) 57 | } 58 | } 59 | 60 | /// Parse errors for [proto::Version]. 61 | #[derive(Debug, Error, PartialEq)] 62 | pub enum VersionError { 63 | /// Git hash is incorrect. 64 | #[error( 65 | "git hash must be a lowercase hex ASCII string, 40 characters in length: (git hash: {0})" 66 | )] 67 | GitHash(String), 68 | 69 | /// Version is missing. 70 | #[error("version must be specified")] 71 | Missing, 72 | 73 | /// Version is incompatible. 74 | #[error("substrait version incompatible (version: `{0}`, supported: `{1}`)")] 75 | Substrait(semver::Version, semver::VersionReq), 76 | } 77 | 78 | impl Parse for proto::Version { 79 | type Parsed = Version; 80 | type Error = VersionError; 81 | 82 | fn parse(self, _ctx: &mut C) -> Result { 83 | let proto::Version { 84 | major_number, 85 | minor_number, 86 | patch_number, 87 | git_hash, 88 | producer, 89 | } = self; 90 | 91 | // All version numbers unset (u32::default()) is an error, because 92 | // version is required. 93 | if major_number == u32::default() 94 | && minor_number == u32::default() 95 | && patch_number == u32::default() 96 | { 97 | return Err(VersionError::Missing); 98 | } 99 | 100 | // The git hash, when set, must be a lowercase hex ASCII string, 40 101 | // characters in length. 102 | if !git_hash.is_empty() 103 | && (git_hash.len() != 40 104 | || !git_hash.chars().all(|x| matches!(x, '0'..='9' | 'a'..='f'))) 105 | { 106 | return Err(VersionError::GitHash(git_hash)); 107 | } 108 | 109 | let version = Version { 110 | version: semver::Version::new(major_number as _, minor_number as _, patch_number as _), 111 | git_hash: (!git_hash.is_empty()).then(|| <[u8; 20]>::from_hex(git_hash).unwrap()), 112 | producer: (!producer.is_empty()).then_some(producer), 113 | }; 114 | 115 | // The version must be compatible with the substrait version of this crate. 116 | version.compatible()?; 117 | 118 | Ok(version) 119 | } 120 | } 121 | 122 | impl From for proto::Version { 123 | fn from(version: Version) -> Self { 124 | let Version { 125 | version, 126 | git_hash, 127 | producer, 128 | } = version; 129 | 130 | proto::Version { 131 | // Note: we can use `as _` here because this Version is always 132 | // constructed from `u32` values. 133 | major_number: version.major as _, 134 | minor_number: version.minor as _, 135 | patch_number: version.patch as _, 136 | git_hash: git_hash.map(hex::encode).unwrap_or_default(), 137 | producer: producer.unwrap_or_default(), 138 | } 139 | } 140 | } 141 | 142 | #[cfg(test)] 143 | mod tests { 144 | use super::*; 145 | use crate::parse::context::tests::Context; 146 | 147 | #[test] 148 | fn version() -> Result<(), VersionError> { 149 | let version = proto::Version::default(); 150 | assert_eq!( 151 | version.parse(&mut Context::default()), 152 | Err(VersionError::Missing) 153 | ); 154 | 155 | let version = version::version(); 156 | version.parse(&mut Context::default())?; 157 | Ok(()) 158 | } 159 | 160 | #[test] 161 | fn git_hash() { 162 | let base = version::version(); 163 | 164 | // Bad length. 165 | let git_hash = String::from("short"); 166 | let version = proto::Version { 167 | git_hash: git_hash.clone(), 168 | ..base.clone() 169 | }; 170 | assert_eq!( 171 | version.parse(&mut Context::default()), 172 | Err(VersionError::GitHash(git_hash)) 173 | ); 174 | 175 | // Not lowercase. 176 | let git_hash = String::from("2FD4E1C67A2D28FCED849EE1BB76E7391B93EB12"); 177 | let version = proto::Version { 178 | git_hash: git_hash.clone(), 179 | ..base.clone() 180 | }; 181 | assert_eq!( 182 | version.parse(&mut Context::default()), 183 | Err(VersionError::GitHash(git_hash)) 184 | ); 185 | 186 | // Not all hex digits. 187 | let git_hash = String::from("2fd4e1c67a2d28fced849ee1bb76e7391b93eb1g"); 188 | let version = proto::Version { 189 | git_hash: git_hash.clone(), 190 | ..base.clone() 191 | }; 192 | assert_eq!( 193 | version.parse(&mut Context::default()), 194 | Err(VersionError::GitHash(git_hash)) 195 | ); 196 | 197 | // Not all ascii. 198 | let git_hash = String::from("2fd4e1c67a2d28fced849ee1bb76e7391b93eb1å"); 199 | let version = proto::Version { 200 | git_hash: git_hash.clone(), 201 | ..base.clone() 202 | }; 203 | assert_eq!( 204 | version.parse(&mut Context::default()), 205 | Err(VersionError::GitHash(git_hash)) 206 | ); 207 | 208 | // Valid. 209 | let git_hash = String::from("2fd4e1c67a2d28fced849ee1bb76e7391b93eb12"); 210 | let version = proto::Version { git_hash, ..base }; 211 | assert!(version.parse(&mut Context::default()).is_ok()); 212 | } 213 | 214 | #[test] 215 | fn producer() -> Result<(), VersionError> { 216 | // Empty producer maps to `None` 217 | let version = proto::Version { 218 | producer: String::from(""), 219 | ..version::version() 220 | }; 221 | let version = version.parse(&mut Context::default())?; 222 | assert!(version.producer.is_none()); 223 | Ok(()) 224 | } 225 | 226 | #[test] 227 | fn convert() -> Result<(), VersionError> { 228 | let version = version::version(); 229 | assert_eq!( 230 | proto::Version::from(version.clone().parse(&mut Context::default())?), 231 | version 232 | ); 233 | Ok(()) 234 | } 235 | 236 | #[test] 237 | fn compatible() -> Result<(), VersionError> { 238 | let _version = version::version().parse(&mut Context::default())?; 239 | 240 | let mut version = version::version(); 241 | version.major_number += 1; 242 | let version = version.parse(&mut Context::default()); 243 | matches!(version, Err(VersionError::Substrait(_, _))); 244 | 245 | let mut version = version::version(); 246 | version.minor_number += 1; 247 | let version = version.parse(&mut Context::default()); 248 | matches!(version, Err(VersionError::Substrait(_, _))); 249 | 250 | let mut version = version::version(); 251 | version.patch_number += 1; 252 | let _version = version.parse(&mut Context::default())?; 253 | 254 | Ok(()) 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /src/parse/text/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | //! Parsing of [text](crate::text) types. 4 | 5 | pub mod simple_extensions; 6 | -------------------------------------------------------------------------------- /src/parse/text/simple_extensions/argument.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | //! Parsing of [simple_extensions::ArgumentsItem]. 4 | 5 | use std::{collections::HashSet, ops::Deref}; 6 | 7 | use thiserror::Error; 8 | 9 | use crate::{ 10 | parse::{Context, Parse}, 11 | text::simple_extensions, 12 | }; 13 | 14 | /// A parsed [simple_extensions::ArgumentsItem]. 15 | #[derive(Clone, Debug)] 16 | pub enum ArgumentsItem { 17 | /// Arguments that support a fixed set of declared values as constant arguments. 18 | EnumArgument(EnumerationArg), 19 | 20 | /// Arguments that refer to a data value. 21 | ValueArgument(ValueArg), 22 | 23 | /// Arguments that are used only to inform the evaluation and/or type derivation of the function. 24 | TypeArgument(TypeArg), 25 | } 26 | 27 | impl ArgumentsItem { 28 | /// Parses an `Option` field, rejecting it if an empty string is provided. 29 | #[inline] 30 | fn parse_optional_string( 31 | name: &str, 32 | value: Option, 33 | ) -> Result, ArgumentsItemError> { 34 | match value { 35 | Some(s) if s.is_empty() => { 36 | Err(ArgumentsItemError::EmptyOptionalField(name.to_string())) 37 | } 38 | _ => Ok(value), 39 | } 40 | } 41 | 42 | #[inline] 43 | fn parse_name(name: Option) -> Result, ArgumentsItemError> { 44 | ArgumentsItem::parse_optional_string("name", name) 45 | } 46 | 47 | #[inline] 48 | fn parse_description( 49 | description: Option, 50 | ) -> Result, ArgumentsItemError> { 51 | ArgumentsItem::parse_optional_string("description", description) 52 | } 53 | } 54 | 55 | impl Parse for simple_extensions::ArgumentsItem { 56 | type Parsed = ArgumentsItem; 57 | type Error = ArgumentsItemError; 58 | 59 | fn parse(self, ctx: &mut C) -> Result { 60 | match self { 61 | simple_extensions::ArgumentsItem::EnumerationArg(arg) => Ok(ctx.parse(arg)?.into()), 62 | simple_extensions::ArgumentsItem::ValueArg(arg) => Ok(ctx.parse(arg)?.into()), 63 | simple_extensions::ArgumentsItem::TypeArg(arg) => Ok(ctx.parse(arg)?.into()), 64 | } 65 | } 66 | } 67 | 68 | impl From for simple_extensions::ArgumentsItem { 69 | fn from(value: ArgumentsItem) -> Self { 70 | match value { 71 | ArgumentsItem::EnumArgument(arg) => arg.into(), 72 | ArgumentsItem::ValueArgument(arg) => arg.into(), 73 | ArgumentsItem::TypeArgument(arg) => arg.into(), 74 | } 75 | } 76 | } 77 | 78 | /// Parse errors for [simple_extensions::ArgumentsItem]. 79 | #[derive(Debug, Error, PartialEq)] 80 | pub enum ArgumentsItemError { 81 | /// Invalid enumeration options. 82 | #[error("invalid enumeration options: {0}")] 83 | InvalidEnumOptions(#[from] EnumOptionsError), 84 | 85 | /// Empty optional field. 86 | #[error("the optional field `{0}` is empty and should be removed")] 87 | EmptyOptionalField(String), 88 | } 89 | 90 | /// Arguments that support a fixed set of declared values as constant arguments. 91 | #[derive(Clone, Debug, PartialEq)] 92 | pub struct EnumerationArg { 93 | /// A human-readable name for this argument to help clarify use. 94 | name: Option, 95 | 96 | /// Additional description for this argument. 97 | description: Option, 98 | 99 | /// Set of valid string options for this argument. 100 | options: EnumOptions, 101 | } 102 | 103 | impl EnumerationArg { 104 | /// Returns the name of this argument. 105 | /// 106 | /// See [simple_extensions::EnumerationArg::name]. 107 | pub fn name(&self) -> Option<&String> { 108 | self.name.as_ref() 109 | } 110 | 111 | /// Returns the description of this argument. 112 | /// 113 | /// See [simple_extensions::EnumerationArg::description]. 114 | pub fn description(&self) -> Option<&String> { 115 | self.description.as_ref() 116 | } 117 | 118 | /// Returns the options of this argument. 119 | /// 120 | /// See [simple_extensions::EnumerationArg::options]. 121 | pub fn options(&self) -> &EnumOptions { 122 | &self.options 123 | } 124 | } 125 | 126 | impl Parse for simple_extensions::EnumerationArg { 127 | type Parsed = EnumerationArg; 128 | 129 | type Error = ArgumentsItemError; 130 | 131 | fn parse(self, ctx: &mut C) -> Result { 132 | Ok(EnumerationArg { 133 | name: ArgumentsItem::parse_name(self.name)?, 134 | description: ArgumentsItem::parse_description(self.description)?, 135 | options: ctx.parse(self.options)?, 136 | }) 137 | } 138 | } 139 | 140 | impl From for simple_extensions::EnumerationArg { 141 | fn from(value: EnumerationArg) -> Self { 142 | simple_extensions::EnumerationArg { 143 | name: value.name, 144 | description: value.description, 145 | options: value.options.into(), 146 | } 147 | } 148 | } 149 | 150 | impl From for simple_extensions::ArgumentsItem { 151 | fn from(value: EnumerationArg) -> Self { 152 | simple_extensions::ArgumentsItem::EnumerationArg(value.into()) 153 | } 154 | } 155 | 156 | impl From for ArgumentsItem { 157 | fn from(value: EnumerationArg) -> Self { 158 | ArgumentsItem::EnumArgument(value) 159 | } 160 | } 161 | 162 | /// Set of valid string options 163 | #[derive(Clone, Debug, PartialEq)] 164 | pub struct EnumOptions(HashSet); 165 | 166 | impl Deref for EnumOptions { 167 | type Target = HashSet; 168 | 169 | fn deref(&self) -> &Self::Target { 170 | &self.0 171 | } 172 | } 173 | 174 | impl Parse for simple_extensions::EnumOptions { 175 | type Parsed = EnumOptions; 176 | 177 | type Error = EnumOptionsError; 178 | 179 | fn parse(self, _ctx: &mut C) -> Result { 180 | let options = self.0; 181 | if options.is_empty() { 182 | return Err(EnumOptionsError::EmptyList); 183 | } 184 | 185 | let mut unique_options = HashSet::new(); 186 | for option in options.iter() { 187 | if option.is_empty() { 188 | return Err(EnumOptionsError::EmptyOption); 189 | } 190 | if !unique_options.insert(option.clone()) { 191 | return Err(EnumOptionsError::DuplicatedOption(option.clone())); 192 | } 193 | } 194 | 195 | Ok(EnumOptions(unique_options)) 196 | } 197 | } 198 | 199 | impl From for simple_extensions::EnumOptions { 200 | fn from(value: EnumOptions) -> Self { 201 | simple_extensions::EnumOptions(Vec::from_iter(value.0)) 202 | } 203 | } 204 | 205 | /// Parse errors for [simple_extensions::EnumOptions]. 206 | #[derive(Debug, Error, PartialEq)] 207 | pub enum EnumOptionsError { 208 | /// Empty list. 209 | #[error("empty list")] 210 | EmptyList, 211 | 212 | /// Duplicated option. 213 | #[error("duplicated option: {0}")] 214 | DuplicatedOption(String), 215 | 216 | /// Empty option. 217 | #[error("empty option")] 218 | EmptyOption, 219 | } 220 | 221 | /// Arguments that refer to a data value. 222 | #[derive(Clone, Debug)] 223 | pub struct ValueArg { 224 | /// A human-readable name for this argument to help clarify use. 225 | name: Option, 226 | 227 | /// Additional description for this argument. 228 | description: Option, 229 | 230 | /// A fully defined type or a type expression. 231 | /// 232 | /// todo: implement parsed [simple_extensions::Type]. 233 | value: simple_extensions::Type, 234 | 235 | /// Whether this argument is required to be a constant for invocation. 236 | /// For example, in some system a regular expression pattern would only be accepted as a literal 237 | /// and not a column value reference. 238 | constant: Option, 239 | } 240 | 241 | impl ValueArg { 242 | /// Returns the name of this argument. 243 | /// 244 | /// See [simple_extensions::ValueArg::name]. 245 | pub fn name(&self) -> Option<&String> { 246 | self.name.as_ref() 247 | } 248 | 249 | /// Returns the description of this argument. 250 | /// 251 | /// See [simple_extensions::ValueArg::description]. 252 | pub fn description(&self) -> Option<&String> { 253 | self.description.as_ref() 254 | } 255 | 256 | /// Returns the constant of this argument. 257 | /// Defaults to `false` if the underlying value is `None`. 258 | /// 259 | /// See [simple_extensions::ValueArg::constant]. 260 | pub fn constant(&self) -> bool { 261 | self.constant.unwrap_or(false) 262 | } 263 | } 264 | 265 | impl Parse for simple_extensions::ValueArg { 266 | type Parsed = ValueArg; 267 | 268 | type Error = ArgumentsItemError; 269 | 270 | fn parse(self, _ctx: &mut C) -> Result { 271 | Ok(ValueArg { 272 | name: ArgumentsItem::parse_name(self.name)?, 273 | description: ArgumentsItem::parse_description(self.description)?, 274 | value: self.value, 275 | constant: self.constant, 276 | }) 277 | } 278 | } 279 | 280 | impl From for simple_extensions::ValueArg { 281 | fn from(value: ValueArg) -> Self { 282 | simple_extensions::ValueArg { 283 | name: value.name, 284 | description: value.description, 285 | value: value.value, 286 | constant: value.constant, 287 | } 288 | } 289 | } 290 | 291 | impl From for simple_extensions::ArgumentsItem { 292 | fn from(value: ValueArg) -> Self { 293 | simple_extensions::ArgumentsItem::ValueArg(value.into()) 294 | } 295 | } 296 | 297 | impl From for ArgumentsItem { 298 | fn from(value: ValueArg) -> Self { 299 | ArgumentsItem::ValueArgument(value) 300 | } 301 | } 302 | 303 | /// Arguments that are used only to inform the evaluation and/or type derivation of the function. 304 | #[derive(Clone, Debug, PartialEq)] 305 | pub struct TypeArg { 306 | /// A human-readable name for this argument to help clarify use. 307 | name: Option, 308 | 309 | /// Additional description for this argument. 310 | description: Option, 311 | 312 | /// A partially or completely parameterized type. E.g. `List` or `K`. 313 | /// 314 | /// todo: implement parsed [simple_extensions::Type]. 315 | type_: String, 316 | } 317 | 318 | impl TypeArg { 319 | /// Returns the name of this argument. 320 | /// 321 | /// See [simple_extensions::TypeArg::name]. 322 | pub fn name(&self) -> Option<&String> { 323 | self.name.as_ref() 324 | } 325 | 326 | /// Returns the description of this argument. 327 | /// 328 | /// See [simple_extensions::TypeArg::description]. 329 | pub fn description(&self) -> Option<&String> { 330 | self.description.as_ref() 331 | } 332 | } 333 | 334 | impl Parse for simple_extensions::TypeArg { 335 | type Parsed = TypeArg; 336 | 337 | type Error = ArgumentsItemError; 338 | 339 | fn parse(self, _ctx: &mut C) -> Result { 340 | Ok(TypeArg { 341 | name: ArgumentsItem::parse_name(self.name)?, 342 | description: ArgumentsItem::parse_description(self.description)?, 343 | type_: self.type_, 344 | }) 345 | } 346 | } 347 | 348 | impl From for simple_extensions::TypeArg { 349 | fn from(value: TypeArg) -> Self { 350 | simple_extensions::TypeArg { 351 | name: value.name, 352 | description: value.description, 353 | type_: value.type_, 354 | } 355 | } 356 | } 357 | 358 | impl From for simple_extensions::ArgumentsItem { 359 | fn from(value: TypeArg) -> Self { 360 | simple_extensions::ArgumentsItem::TypeArg(value.into()) 361 | } 362 | } 363 | 364 | impl From for ArgumentsItem { 365 | fn from(value: TypeArg) -> Self { 366 | ArgumentsItem::TypeArgument(value) 367 | } 368 | } 369 | 370 | #[cfg(test)] 371 | mod tests { 372 | use super::*; 373 | use crate::text::simple_extensions; 374 | use crate::{parse::context::tests::Context, text}; 375 | 376 | #[test] 377 | fn parse_enum_argument() -> Result<(), ArgumentsItemError> { 378 | let enum_argument = 379 | simple_extensions::ArgumentsItem::EnumerationArg(simple_extensions::EnumerationArg { 380 | name: Some("arg".to_string()), 381 | description: Some("desc".to_string()), 382 | options: simple_extensions::EnumOptions(vec!["OVERFLOW".to_string()]), 383 | }); 384 | let item = enum_argument.parse(&mut Context::default())?; 385 | let enum_argument = match item { 386 | ArgumentsItem::EnumArgument(enum_argument) => enum_argument, 387 | _ => unreachable!(), 388 | }; 389 | assert_eq!( 390 | enum_argument, 391 | EnumerationArg { 392 | name: Some("arg".to_string()), 393 | description: Some("desc".to_string()), 394 | options: EnumOptions(HashSet::from(["OVERFLOW".to_string()])), 395 | } 396 | ); 397 | Ok(()) 398 | } 399 | 400 | #[test] 401 | fn parse_empty_enum_options() -> Result<(), ArgumentsItemError> { 402 | let options = simple_extensions::EnumOptions(vec![]); 403 | let is_err = options 404 | .parse(&mut Context::default()) 405 | .err() 406 | .map(|err| matches!(err, EnumOptionsError::EmptyList)); 407 | assert_eq!(is_err, Some(true)); 408 | Ok(()) 409 | } 410 | 411 | #[test] 412 | fn parse_enum_options_with_empty_value() -> Result<(), ArgumentsItemError> { 413 | let options = simple_extensions::EnumOptions(vec!["".to_string()]); 414 | let is_err = options 415 | .parse(&mut Context::default()) 416 | .err() 417 | .map(|err| matches!(err, EnumOptionsError::EmptyOption)); 418 | assert_eq!(is_err, Some(true)); 419 | Ok(()) 420 | } 421 | 422 | #[test] 423 | fn parse_enum_argument_with_duplicated_option() -> Result<(), ArgumentsItemError> { 424 | let options = 425 | simple_extensions::EnumOptions(vec!["OVERFLOW".to_string(), "OVERFLOW".to_string()]); 426 | let is_err = options 427 | .clone() 428 | .parse(&mut Context::default()) 429 | .err() 430 | .map(|err| { 431 | matches!( 432 | err, 433 | EnumOptionsError::DuplicatedOption(opt) if opt == "OVERFLOW" 434 | ) 435 | }); 436 | assert_eq!(is_err, Some(true)); 437 | Ok(()) 438 | } 439 | 440 | #[test] 441 | fn parse_value_argument() -> Result<(), ArgumentsItemError> { 442 | let item = simple_extensions::ArgumentsItem::ValueArg(simple_extensions::ValueArg { 443 | name: Some("arg".to_string()), 444 | description: Some("desc".to_string()), 445 | value: text::simple_extensions::Type::Variant0("i32".to_string()), 446 | constant: Some(true), 447 | }); 448 | let item = item.parse(&mut Context::default())?; 449 | match item { 450 | ArgumentsItem::ValueArgument(ValueArg { 451 | name, 452 | description, 453 | value, 454 | constant, 455 | }) => { 456 | assert_eq!(name, Some("arg".to_string())); 457 | assert_eq!(description, Some("desc".to_string())); 458 | assert!( 459 | matches!(value, text::simple_extensions::Type::Variant0(type_) if type_ == "i32") 460 | ); 461 | assert_eq!(constant, Some(true)); 462 | } 463 | _ => unreachable!(), 464 | }; 465 | Ok(()) 466 | } 467 | 468 | #[test] 469 | fn parse_type_argument() -> Result<(), ArgumentsItemError> { 470 | let type_argument = simple_extensions::ArgumentsItem::TypeArg(simple_extensions::TypeArg { 471 | name: Some("arg".to_string()), 472 | description: Some("desc".to_string()), 473 | type_: "".to_string(), 474 | }); 475 | let item = type_argument.parse(&mut Context::default())?; 476 | match item { 477 | ArgumentsItem::TypeArgument(TypeArg { 478 | name, 479 | description, 480 | type_, 481 | }) => { 482 | assert_eq!(name, Some("arg".to_string())); 483 | assert_eq!(description, Some("desc".to_string())); 484 | assert_eq!(type_, ""); 485 | } 486 | _ => unreachable!(), 487 | }; 488 | Ok(()) 489 | } 490 | 491 | #[test] 492 | fn parse_argument_with_nones() -> Result<(), ArgumentsItemError> { 493 | let items = vec![ 494 | simple_extensions::ArgumentsItem::EnumerationArg(simple_extensions::EnumerationArg { 495 | name: None, 496 | description: None, 497 | options: simple_extensions::EnumOptions(vec!["OVERFLOW".to_string()]), 498 | }), 499 | simple_extensions::ArgumentsItem::ValueArg(simple_extensions::ValueArg { 500 | name: None, 501 | description: None, 502 | value: text::simple_extensions::Type::Variant0("i32".to_string()), 503 | constant: None, 504 | }), 505 | simple_extensions::ArgumentsItem::TypeArg(simple_extensions::TypeArg { 506 | name: None, 507 | description: None, 508 | type_: "".to_string(), 509 | }), 510 | ]; 511 | 512 | for item in items { 513 | let item = item.parse(&mut Context::default())?; 514 | let (name, description) = match item { 515 | ArgumentsItem::EnumArgument(EnumerationArg { 516 | name, description, .. 517 | }) => (name, description), 518 | 519 | ArgumentsItem::ValueArgument(ValueArg { 520 | name, 521 | description, 522 | constant, 523 | .. 524 | }) => { 525 | assert!(constant.is_none()); 526 | (name, description) 527 | } 528 | 529 | ArgumentsItem::TypeArgument(TypeArg { 530 | name, description, .. 531 | }) => (name, description), 532 | }; 533 | assert!(name.is_none()); 534 | assert!(description.is_none()); 535 | } 536 | 537 | Ok(()) 538 | } 539 | 540 | #[test] 541 | fn parse_argument_with_empty_fields() -> Result<(), ArgumentsItemError> { 542 | let items = vec![ 543 | simple_extensions::ArgumentsItem::EnumerationArg(simple_extensions::EnumerationArg { 544 | name: Some("".to_string()), 545 | description: None, 546 | options: simple_extensions::EnumOptions(vec!["OVERFLOW".to_string()]), 547 | }), 548 | simple_extensions::ArgumentsItem::ValueArg(simple_extensions::ValueArg { 549 | name: Some("".to_string()), 550 | description: None, 551 | value: text::simple_extensions::Type::Variant0("i32".to_string()), 552 | constant: None, 553 | }), 554 | simple_extensions::ArgumentsItem::TypeArg(simple_extensions::TypeArg { 555 | name: Some("".to_string()), 556 | description: None, 557 | type_: "".to_string(), 558 | }), 559 | ]; 560 | for item in items { 561 | assert_eq!( 562 | item.parse(&mut Context::default()).err(), 563 | Some(ArgumentsItemError::EmptyOptionalField("name".to_string())) 564 | ); 565 | } 566 | 567 | let items = vec![ 568 | simple_extensions::ArgumentsItem::EnumerationArg(simple_extensions::EnumerationArg { 569 | name: None, 570 | description: Some("".to_string()), 571 | options: simple_extensions::EnumOptions(vec!["OVERFLOW".to_string()]), 572 | }), 573 | simple_extensions::ArgumentsItem::ValueArg(simple_extensions::ValueArg { 574 | name: None, 575 | description: Some("".to_string()), 576 | value: text::simple_extensions::Type::Variant0("i32".to_string()), 577 | constant: None, 578 | }), 579 | simple_extensions::ArgumentsItem::TypeArg(simple_extensions::TypeArg { 580 | name: None, 581 | description: Some("".to_string()), 582 | type_: "".to_string(), 583 | }), 584 | ]; 585 | for item in items { 586 | assert_eq!( 587 | item.parse(&mut Context::default()).err(), 588 | Some(ArgumentsItemError::EmptyOptionalField( 589 | "description".to_string() 590 | )) 591 | ); 592 | } 593 | 594 | Ok(()) 595 | } 596 | 597 | #[test] 598 | fn from_enum_argument() { 599 | let item: ArgumentsItem = EnumerationArg { 600 | name: Some("arg".to_string()), 601 | description: Some("desc".to_string()), 602 | options: EnumOptions(HashSet::from(["OVERFLOW".to_string()])), 603 | } 604 | .into(); 605 | 606 | let item: text::simple_extensions::ArgumentsItem = item.into(); 607 | match item { 608 | text::simple_extensions::ArgumentsItem::EnumerationArg( 609 | simple_extensions::EnumerationArg { 610 | name, 611 | description, 612 | options, 613 | }, 614 | ) => { 615 | assert_eq!(name, Some("arg".to_string())); 616 | assert_eq!(description, Some("desc".to_string())); 617 | assert_eq!(options.0, vec!["OVERFLOW".to_string()]); 618 | } 619 | _ => unreachable!(), 620 | } 621 | } 622 | 623 | #[test] 624 | fn from_value_argument() { 625 | let item: ArgumentsItem = ValueArg { 626 | name: Some("arg".to_string()), 627 | description: Some("desc".to_string()), 628 | value: text::simple_extensions::Type::Variant0("".to_string()), 629 | constant: Some(true), 630 | } 631 | .into(); 632 | 633 | let item: text::simple_extensions::ArgumentsItem = item.into(); 634 | match item { 635 | text::simple_extensions::ArgumentsItem::ValueArg(simple_extensions::ValueArg { 636 | name, 637 | description, 638 | value, 639 | constant, 640 | }) => { 641 | assert_eq!(name, Some("arg".to_string())); 642 | assert_eq!(description, Some("desc".to_string())); 643 | assert!( 644 | matches!(value, text::simple_extensions::Type::Variant0(type_) if type_.is_empty()) 645 | ); 646 | assert_eq!(constant, Some(true)); 647 | } 648 | _ => unreachable!(), 649 | } 650 | } 651 | 652 | #[test] 653 | fn from_type_argument() { 654 | let item: ArgumentsItem = TypeArg { 655 | name: Some("arg".to_string()), 656 | description: Some("desc".to_string()), 657 | type_: "".to_string(), 658 | } 659 | .into(); 660 | 661 | let item: text::simple_extensions::ArgumentsItem = item.into(); 662 | match item { 663 | text::simple_extensions::ArgumentsItem::TypeArg(simple_extensions::TypeArg { 664 | name, 665 | description, 666 | type_, 667 | }) => { 668 | assert_eq!(name, Some("arg".to_string())); 669 | assert_eq!(description, Some("desc".to_string())); 670 | assert_eq!(type_, ""); 671 | } 672 | _ => unreachable!(), 673 | } 674 | } 675 | 676 | #[cfg(feature = "extensions")] 677 | #[test] 678 | fn parse_extensions() { 679 | use crate::extensions::EXTENSIONS; 680 | use crate::parse::context::tests::Context; 681 | 682 | macro_rules! parse_arguments { 683 | ($url:expr, $fns:expr) => { 684 | $fns.iter().for_each(|f| { 685 | f.impls 686 | .iter() 687 | .filter_map(|i| i.args.as_ref()) 688 | .flat_map(|a| &a.0) 689 | .for_each(|item| { 690 | item.clone() 691 | .parse(&mut Context::default()) 692 | .unwrap_or_else(|err| { 693 | panic!( 694 | "found an invalid argument: {}, (url: {}, function: {}, arg: {:?})", 695 | err, 696 | $url.to_string(), 697 | f.name, 698 | item 699 | ); 700 | }); 701 | }) 702 | }); 703 | }; 704 | } 705 | 706 | EXTENSIONS.iter().for_each(|(url, ext)| { 707 | parse_arguments!(url, ext.scalar_functions); 708 | parse_arguments!(url, ext.aggregate_functions); 709 | parse_arguments!(url, ext.window_functions); 710 | }); 711 | } 712 | } 713 | -------------------------------------------------------------------------------- /src/parse/text/simple_extensions/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | //! Parsing of [text::simple_extensions] types. 4 | 5 | use thiserror::Error; 6 | 7 | use crate::{ 8 | parse::{Context, Parse}, 9 | text, 10 | }; 11 | 12 | pub mod argument; 13 | 14 | /// A parsed [text::simple_extensions::SimpleExtensions]. 15 | pub struct SimpleExtensions { 16 | // TODO 17 | } 18 | 19 | /// Parse errors for [text::simple_extensions::SimpleExtensions]. 20 | #[derive(Debug, Error, PartialEq)] 21 | pub enum SimpleExtensionsError { 22 | // TODO 23 | } 24 | 25 | impl Parse for text::simple_extensions::SimpleExtensions { 26 | type Parsed = SimpleExtensions; 27 | type Error = SimpleExtensionsError; 28 | 29 | fn parse(self, _ctx: &mut C) -> Result { 30 | // let text::simple_extensions::SimpleExtensions { 31 | // aggregate_functions, 32 | // dependencies, 33 | // scalar_functions, 34 | // type_variations, 35 | // types, 36 | // window_functions, 37 | // } = self; 38 | 39 | todo!("text::simple_extensions::SimpleExtensions - https://github.com/substrait-io/substrait-rs/issues/157") 40 | } 41 | } 42 | 43 | impl From for text::simple_extensions::SimpleExtensions { 44 | fn from(_value: SimpleExtensions) -> Self { 45 | todo!("text::simple_extensions::SimpleExtensions - https://github.com/substrait-io/substrait-rs/issues/157") 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/parse/typed.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | //! A generic new type wrapper. 4 | 5 | use std::{any, fmt, hash, marker::PhantomData}; 6 | 7 | /// A generic new type wrapper for values of type `T` that belong to items of type `U`. 8 | pub struct Typed { 9 | /// The wrapped value. 10 | value: T, 11 | /// The attached type. 12 | _type: PhantomData, 13 | } 14 | 15 | impl Typed { 16 | /// Internal method to construct a new wrapper from a value. 17 | pub(crate) fn new(value: T) -> Self { 18 | Self { 19 | value, 20 | _type: PhantomData, 21 | } 22 | } 23 | 24 | /// Returns a reference to the wrapped value. 25 | pub fn value(&self) -> &T { 26 | &self.value 27 | } 28 | 29 | /// Returns the inner value. 30 | pub fn into_inner(self) -> T { 31 | self.value 32 | } 33 | } 34 | 35 | impl, U, V: ?Sized> AsRef for Typed { 36 | fn as_ref(&self) -> &V { 37 | self.value.as_ref() 38 | } 39 | } 40 | 41 | impl Clone for Typed { 42 | fn clone(&self) -> Self { 43 | Self { 44 | value: self.value.clone(), 45 | _type: self._type, 46 | } 47 | } 48 | } 49 | 50 | impl Copy for Typed {} 51 | 52 | impl fmt::Debug for Typed { 53 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 54 | f.debug_tuple(any::type_name::()) 55 | .field(&self.value) 56 | .finish() 57 | } 58 | } 59 | 60 | impl fmt::Display for Typed { 61 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 62 | fmt::Display::fmt(&self.value, f) 63 | } 64 | } 65 | 66 | impl PartialEq for Typed { 67 | fn eq(&self, other: &Self) -> bool { 68 | self.value == other.value 69 | } 70 | } 71 | 72 | impl Eq for Typed {} 73 | 74 | impl hash::Hash for Typed { 75 | fn hash(&self, state: &mut H) { 76 | self.value.hash(state); 77 | } 78 | } 79 | 80 | /// A generic anchor new type for the anchor mechanism used in Substrait data. 81 | pub type Anchor = Typed; 82 | -------------------------------------------------------------------------------- /src/proto.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | #![allow( 4 | clippy::doc_overindented_list_items, 5 | clippy::large_enum_variant, 6 | clippy::needless_borrow, 7 | clippy::needless_borrows_for_generic_args, 8 | clippy::needless_lifetimes, 9 | rustdoc::invalid_html_tags 10 | )] 11 | 12 | //! Generated types for the protobuf `substrait` package. 13 | 14 | /// Generated types for the protobuf `substrait.extensions` package 15 | pub mod extensions { 16 | include!(concat!(env!("OUT_DIR"), "/substrait.extensions.rs")); 17 | 18 | #[cfg(feature = "serde")] 19 | include!(concat!(env!("OUT_DIR"), "/substrait.extensions.serde.rs")); 20 | } 21 | 22 | include!(concat!(env!("OUT_DIR"), "/substrait.rs")); 23 | 24 | #[cfg(feature = "serde")] 25 | include!(concat!(env!("OUT_DIR"), "/substrait.serde.rs")); 26 | 27 | #[cfg(test)] 28 | mod tests { 29 | #[cfg(feature = "serde")] 30 | #[test] 31 | fn pbjson_serde() -> Result<(), Box> { 32 | let plan = serde_json::from_str::( 33 | r#"{ 34 | "version": { "minorNumber": 19, "producer": "substrait-rs" }, 35 | "extensionUris": [ 36 | { 37 | "extensionUriAnchor": 1, 38 | "uri": "https://github.com/substrait-io/substrait/blob/main/extensions/functions_string.yaml" 39 | } 40 | ] 41 | }"#, 42 | )?; 43 | assert_eq!( 44 | plan.version, 45 | Some(super::Version { 46 | minor_number: 19, 47 | producer: "substrait-rs".into(), 48 | ..Default::default() 49 | }) 50 | ); 51 | assert_eq!(plan.extension_uris.len(), 1); 52 | Ok(()) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/text.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | #![allow( 4 | clippy::clone_on_copy, 5 | clippy::derivable_impls, 6 | clippy::to_string_trait_impl, 7 | clippy::uninlined_format_args, 8 | unused_variables 9 | )] 10 | 11 | //! Generated types for text-based definitions. 12 | 13 | include!(concat!(env!("OUT_DIR"), "/substrait_text.rs")); 14 | -------------------------------------------------------------------------------- /src/version.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | //! Substrait version information. 4 | //! 5 | //! The contents of this module are auto-generated using `build.rs`. It is 6 | //! included in the packaged crate, ignored by git, and automatically kept 7 | //! in-sync. 8 | 9 | use crate::proto::Version; 10 | 11 | include!("../gen/version.in"); 12 | 13 | /// Returns the version of Substrait used to build this crate. 14 | /// 15 | /// Note that this does not set [Version::producer]. See 16 | /// [version_with_producer]. 17 | pub fn version() -> Version { 18 | Version { 19 | major_number: SUBSTRAIT_MAJOR_VERSION, 20 | minor_number: SUBSTRAIT_MINOR_VERSION, 21 | patch_number: SUBSTRAIT_PATCH_VERSION, 22 | git_hash: if SUBSTRAIT_GIT_DEPTH != 0 { 23 | String::from(SUBSTRAIT_GIT_SHA) 24 | } else { 25 | String::default() 26 | }, 27 | ..Default::default() 28 | } 29 | } 30 | 31 | /// Returns the version of Substrait used to build this crate with 32 | /// [Version::producer] set to the passed `producer`. 33 | pub fn version_with_producer(producer: impl Into) -> Version { 34 | Version { 35 | producer: producer.into(), 36 | ..version() 37 | } 38 | } 39 | 40 | /// Returns the semantic version of Substrait used to build this crate. 41 | #[cfg(feature = "semver")] 42 | pub fn semver() -> semver::Version { 43 | semver::Version { 44 | major: SUBSTRAIT_MAJOR_VERSION as _, 45 | minor: SUBSTRAIT_MINOR_VERSION as _, 46 | patch: SUBSTRAIT_PATCH_VERSION as _, 47 | pre: if SUBSTRAIT_GIT_DEPTH != 0 { 48 | semver::Prerelease::new(&SUBSTRAIT_GIT_DEPTH.to_string()).unwrap() 49 | } else { 50 | semver::Prerelease::EMPTY 51 | }, 52 | build: semver::BuildMetadata::new(SUBSTRAIT_GIT_SHA).unwrap(), 53 | } 54 | } 55 | 56 | /// Returns the requirement of this crate for other Substrait versions. 57 | #[cfg(feature = "semver")] 58 | pub fn semver_req() -> semver::VersionReq { 59 | semver::VersionReq::parse(&format!("^{}", semver())).unwrap() 60 | } 61 | 62 | #[cfg(test)] 63 | // These tests ensure this crate uses a tagged Substrait release. 64 | mod tests { 65 | #[test] 66 | fn no_git_hash() { 67 | // An empty `git_hash` indicates that there are no additional commits 68 | // since the last tag. 69 | assert!(super::version().git_hash.is_empty()); 70 | } 71 | 72 | #[test] 73 | #[allow(clippy::assertions_on_constants)] 74 | fn not_dirty() { 75 | // There should be no `dirty` in the describe output. 76 | assert!(!super::SUBSTRAIT_GIT_DESCRIBE.contains("dirty")); 77 | assert!(!super::SUBSTRAIT_GIT_DIRTY); 78 | } 79 | 80 | #[test] 81 | fn no_pre_release() { 82 | // The pre-release should be unset. If set it indicates additional 83 | // commits after the tag. 84 | #[cfg(feature = "semver")] 85 | assert!(super::semver().pre.is_empty()); 86 | assert_eq!(super::SUBSTRAIT_GIT_DEPTH, 0); 87 | } 88 | } 89 | --------------------------------------------------------------------------------