├── .cargo └── config ├── .dockerignore ├── .envrc ├── .github ├── docker │ └── Dockerfile.debian-artifact-build ├── scripts │ ├── install_cargo_pgrx.sh │ ├── load_cache.sh │ └── save_cache.sh └── workflows │ ├── ci.yml │ ├── docs.yml │ ├── nightly.yml │ └── release_artifacts.yml ├── .gitignore ├── .licensure.yml ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── Dockerfile.try ├── LICENSE.md ├── README.md ├── SECURITY.md ├── doc ├── README.md ├── book.toml └── src │ ├── SUMMARY.md │ ├── architecture.md │ ├── assets │ └── architecture_1.png │ ├── built-in-functions.md │ ├── config-env-var.md │ ├── config-lints.md │ ├── config-pg.md │ ├── data-types.md │ ├── data-types │ ├── arrays.md │ ├── no-unsigned-types.md │ ├── udts.md │ └── working-with-null.md │ ├── dependencies.md │ ├── designing-for-trust.md │ ├── dynamic-function-calling.md │ ├── functions │ ├── anatomy.md │ ├── arguments.md │ ├── return-type.md │ └── set-returning-functions.md │ ├── install-plrust-on-debian-ubuntu.md │ ├── install-plrust.md │ ├── install-prerequisites.md │ ├── logging.md │ ├── plrust.md │ ├── spi.md │ ├── triggers.md │ ├── trusted-untrusted.md │ ├── try-plrust-with-docker.md │ ├── update-plrust.md │ └── use-plrust.md ├── plrust-tests ├── Cargo.toml ├── plrust_tests.control ├── run-tests.sh └── src │ ├── alter.rs │ ├── argument.rs │ ├── basic.rs │ ├── blocked_code.rs │ ├── borrow_mut_error.rs │ ├── ddl.rs │ ├── dependencies.rs │ ├── fn_call.rs │ ├── lib.rs │ ├── matches.rs │ ├── panics.rs │ ├── range.rs │ ├── recursion.rs │ ├── return_values.rs │ ├── round_trip.rs │ ├── time_and_dates.rs │ ├── trusted.rs │ ├── user_defined_types.rs │ └── versioning.rs ├── plrust-trusted-pgrx ├── Cargo.toml ├── README.md └── src │ └── lib.rs ├── plrust ├── Cargo.toml ├── build ├── build.rs ├── plrust.control └── src │ ├── allow_list.rs │ ├── error.rs │ ├── gucs.rs │ ├── hooks.rs │ ├── lib.rs │ ├── logging.rs │ ├── pgproc.rs │ ├── plrust.rs │ ├── prosrc.rs │ ├── target.rs │ ├── tests.rs │ └── user_crate │ ├── build.rs │ ├── capabilities.rs │ ├── cargo.rs │ ├── crate_variant.rs │ ├── crating.rs │ ├── lint.rs │ ├── loading.rs │ ├── mod.rs │ ├── ready.rs │ ├── validate.rs │ └── verify.rs ├── plrustc ├── .cargo │ └── config.toml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── build.sh ├── plrustc │ ├── Cargo.toml │ ├── src │ │ ├── lints │ │ │ ├── async_await.rs │ │ │ ├── autotrait_impls.rs │ │ │ ├── builtin_macros.rs │ │ │ ├── closure_trait_impl.rs │ │ │ ├── extern_blocks.rs │ │ │ ├── external_mod.rs │ │ │ ├── fn_ptr.rs │ │ │ ├── force_ice.rs │ │ │ ├── leaky.rs │ │ │ ├── lifetime_param_trait.rs │ │ │ ├── mod.rs │ │ │ ├── print_macros.rs │ │ │ ├── static_impls.rs │ │ │ ├── stdio.rs │ │ │ ├── sus_trait_object.rs │ │ │ └── utils.rs │ │ └── main.rs │ ├── tests │ │ └── uitests.rs │ └── uitests │ │ ├── async_await.rs │ │ ├── async_await.stderr │ │ ├── extern_block.rs │ │ ├── extern_block.stderr │ │ ├── external_mod.rs │ │ ├── external_mod.stderr │ │ ├── external_mod_included_file.txt │ │ ├── fn_pointer.rs │ │ ├── fn_pointer.stderr │ │ ├── fn_traits.rs │ │ ├── fn_traits.stderr │ │ ├── fs_macros.rs │ │ ├── fs_macros.stderr │ │ ├── fs_macros_included_file.txt │ │ ├── ice_hook.rs │ │ ├── ice_hook.stderr │ │ ├── impl_auto_trait.rs │ │ ├── impl_auto_trait.stderr │ │ ├── leaky.rs │ │ ├── leaky.stderr │ │ ├── lifetime_trait.rs │ │ ├── lifetime_trait.stderr │ │ ├── print_macros.rs │ │ ├── print_macros.stderr │ │ ├── static_closure.rs │ │ ├── static_closure.stderr │ │ ├── static_impl_etc.rs │ │ ├── static_impl_etc.stderr │ │ ├── static_impl_unsound.rs │ │ ├── static_impl_unsound.stderr │ │ ├── stdio.rs │ │ ├── stdio.stderr │ │ ├── sus_trait_obj_alias.rs │ │ ├── sus_trait_obj_alias.stderr │ │ ├── sus_trait_obj_items.rs │ │ ├── sus_trait_obj_items.stderr │ │ ├── sus_trait_obj_transmute.rs │ │ ├── sus_trait_obj_transmute.stderr │ │ ├── sus_trait_object_gat.rs │ │ ├── sus_trait_object_gat.stderr │ │ ├── tuple_struct_self_pat_box.rs │ │ ├── tuple_struct_self_pat_box.stderr │ │ ├── tuple_struct_self_pat_local_priv.rs │ │ ├── tuple_struct_self_pat_local_priv.stderr │ │ └── tuple_struct_self_pat_should_pass.rs └── rust-toolchain.toml ├── publish.sh ├── rust-toolchain.toml ├── sql └── plrust--1.0--1.1.sql ├── update-version.sh └── upgrade-deps.sh /.cargo/config: -------------------------------------------------------------------------------- 1 | [build] 2 | # Postgres symbols won't be available until runtime 3 | rustflags = ["-C", "link-args=-Wl,-undefined,dynamic_lookup"] 4 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | target/ 4 | *.iml 5 | **/*.rs.bk 6 | *.o 7 | *.so 8 | *.a 9 | cmake*/ 10 | .direnv 11 | plrust/postgrestd 12 | .git 13 | Dockerfile.* 14 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | # reload when these files change 2 | watch_file flake.nix 3 | watch_file flake.lock 4 | # load the flake devShell 5 | eval "$(nix print-dev-env)" 6 | -------------------------------------------------------------------------------- /.github/scripts/install_cargo_pgrx.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Installs/upgrades cargo-pgrx based on version specified in plrust/Cargo.toml 4 | # 5 | # Expects the following environment variables to already exist: 6 | # * None 7 | # 8 | # Expects the following parameters (in-order): 9 | # * None 10 | # 11 | # Example usage: 12 | # . /path/to/plrust/.github/scripts/install_cargo_pgrx.sh 13 | # install_cargo_pgrx 14 | 15 | function install_cargo_pgrx() { 16 | set -o pipefail 17 | set -e 18 | 19 | if TARGET_VERSION=$(cargo metadata --format-version 1 | jq -r '.packages[]|select(.name=="pgrx")|.version'); then 20 | echo "Installing/upgrading cargo-pgrx to version $TARGET_VERSION" 21 | cargo install cargo-pgrx --locked --force --version "$TARGET_VERSION" 22 | else 23 | echo "Could not determine cargo-pgrx version to install." 24 | exit 1 25 | fi 26 | } 27 | 28 | -------------------------------------------------------------------------------- /.github/scripts/load_cache.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Downloads and extracts cache archive from S3. 4 | # 5 | # Expects the following environment variables to already exist: 6 | # * AWS_CACHE_BUCKET: the S3 bucket in which to obtain the archive 7 | # * HOME: executing user's home directory 8 | # 9 | # Expects the following parameters (in-order): 10 | # * $1: the pre-calculated cache key 11 | # 12 | # Example usage: 13 | # . /path/to/plrust/.github/scripts/load_cache.sh 14 | # loadcache "some-cache-key-abc123" 15 | # 16 | # Note: This assumes the host in which this script is running on has the ability 17 | # to read and write from the bucket specified by AWS_CACHE_BUCKET 18 | 19 | function loadcache() { 20 | local cache_key="$1" 21 | 22 | echo "Checking to see if cache archive exists: $cache_key" 23 | 24 | if aws s3api head-object --bucket $AWS_CACHE_BUCKET --key $cache_key &> /dev/null; then 25 | echo "Cache archive exists for $cache_key -- downloading and extracting now." 26 | 27 | mkdir -p $HOME/artifacts/ 28 | archive_path=$HOME/artifacts/$cache_key 29 | 30 | echo "Downloadng archive $cache_key and storing to $archive_path" 31 | 32 | aws s3api get-object \ 33 | --bucket $AWS_CACHE_BUCKET \ 34 | --key $cache_key \ 35 | $archive_path 36 | 37 | echo "Extracting archive $cache_key" 38 | lz4 -dc --no-sparse $archive_path | tar xvC / 39 | 40 | echo "Removing archive $archive_path" 41 | rm $archive_path 42 | 43 | echo "Done." 44 | else 45 | echo "Cache archive does not exist for $cache_key -- skipping." 46 | fi 47 | } 48 | -------------------------------------------------------------------------------- /.github/scripts/save_cache.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Creates cache archive and uploads to S3. 4 | # 5 | # Expects the following environment variables to already exist: 6 | # * AWS_CACHE_BUCKET: the S3 bucket in which to obtain the archive 7 | # * HOME: executing user's home directory 8 | # 9 | # Expects the following parameters (in-order): 10 | # * $1: the pre-calculated cache key 11 | # * $2: array of full-path directories to be cached 12 | # 13 | # Example usage: 14 | # . /path/to/plrust/.github/scripts/save_cache.sh 15 | # my_paths=(/path/one /path/two /path/three) 16 | # savecache "some-cache-key-abc123" "${my_paths[@]}" 17 | # 18 | # Note: This assumes the host in which this script is running on has the ability 19 | # to read and write from the bucket specified by AWS_CACHE_BUCKET 20 | 21 | function savecache() { 22 | local cache_key="$1" 23 | shift 24 | local dirs_to_save=("$@") 25 | 26 | echo "Checking to see if cache archive exists: $cache_key" 27 | 28 | if aws s3api head-object --bucket $AWS_CACHE_BUCKET --key $cache_key &> /dev/null; then 29 | echo "Cache archive exists for $cache_key -- skipping archive creation." 30 | else 31 | echo "Cache archive does not exist for $cache_key -- creating archive now." 32 | 33 | archive_path=$HOME/artifacts/$cache_key 34 | 35 | echo "Creating archive at $archive_path" 36 | 37 | tar --ignore-failed-read -cvf - "${dirs_to_save[@]}" \ 38 | | lz4 - $archive_path 39 | 40 | echo "Created archive $archive_path -- uploading now" 41 | 42 | aws s3api put-object \ 43 | --bucket $AWS_CACHE_BUCKET \ 44 | --key $cache_key \ 45 | --body $archive_path 46 | 47 | echo "Removing $archive_path" 48 | rm $archive_path 49 | fi 50 | } 51 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | # Based on https://github.com/rust-lang/mdBook/wiki/Automated-Deployment%3A-GitHub-Actions 2 | name: Deploy mdbook 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | with: 14 | fetch-depth: 0 15 | - name: Install Rust 16 | run: | 17 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 18 | echo "PATH=$HOME/.cargo/bin:$PATH" >> $GITHUB_ENV 19 | - name: Install mdbook and other preprocessors 20 | run: | 21 | cargo install --locked mdbook-variables mdbook 22 | - name: Deploy GitHub Pages 23 | run: | 24 | # This assumes your book is in the root of your repository. 25 | # Just add a `cd` here if you need to change to another directory. 26 | cd doc 27 | mdbook build 28 | git worktree add gh-pages 29 | git config user.name "Deploy from CI" 30 | git config user.email "" 31 | cd gh-pages 32 | # Delete the ref to avoid keeping history. 33 | git update-ref -d refs/heads/gh-pages 34 | rm -rf * 35 | mv ../book/* . 36 | echo 'plrust.io' > CNAME 37 | git add . 38 | git commit -m "Deploy $GITHUB_SHA to gh-pages" 39 | git push --force --set-upstream origin gh-pages 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | /target 4 | *.iml 5 | **/*.rs.bk 6 | sql/*.generated.sql 7 | .direnv 8 | scratch 9 | postgrestd/ 10 | .vscode/ 11 | doc/book 12 | /build 13 | config.toml 14 | builder/target 15 | builder/sysroot/target 16 | builder/Cargo.lock 17 | 18 | -------------------------------------------------------------------------------- /.licensure.yml: -------------------------------------------------------------------------------- 1 | change_in_place: true 2 | excludes: 3 | - Cargo.toml 4 | - Cargo.lock 5 | - LICENSE 6 | - ".envrc" 7 | - ".gitignore" 8 | - "flake\\..*" 9 | - "logo.*" 10 | - ".github/**/*" 11 | - ".cargo/config" 12 | - "cargo-pgrx/src/templates/*" 13 | - ".*\\.control" 14 | - ".*\\.md" 15 | - ".*\\.nix" 16 | - ".*\\.yml" 17 | licenses: 18 | - files: any 19 | ident: PostgreSQL 20 | authors: 21 | - name: Technology Concepts & Design, Inc. 22 | email: opensource@tcdi.com 23 | template: | 24 | Copyright 2021-2023 Technology Concepts & Design, Inc. 25 | 26 | Use of this source code is governed by the [ident] license that can be found in the LICENSE.md file. 27 | comments: 28 | - columns: 120 29 | extensions: 30 | - rs 31 | - c 32 | - h 33 | - sql 34 | commenter: 35 | type: block 36 | start_block_char: "/*\n" 37 | end_block_char: "\n*/\n" 38 | - columns: 120 39 | extension: html 40 | commenter: 41 | type: block 42 | start_block_char: "" 44 | - columns: 80 45 | extension: any 46 | commenter: 47 | type: line 48 | comment_char: "#" 49 | trailing_lines: 0 50 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Dev Environment Setup 2 | 3 | System Requirements: 4 | - Rustup (or equivalent toolchain manager like Nix). Users may be able to use distro toolchains, but you won't get far without a proper Rust toolchain manager. 5 | - Otherwise, same as the user requirements. 6 | 7 | 8 | If you want to be ready to open a PR, you will want to run 9 | ```bash 10 | git clone --branch develop "https://github.com/tcdi/plrust" 11 | cd plrust 12 | ``` 13 | That will put you in a cloned repository with the **`develop`** branch opened, which is the one you will be opening pull 14 | requests against in most cases. 15 | 16 | After cloning the repository the first things you need to do are install `plrustc` and, if on Linux, perhaps `postgrestd`: 17 | 18 | ```bash 19 | 20 | ## install plrustc 21 | cd plrustc 22 | ./build.sh 23 | cp ../build/bin/plrustc ~/.cargo/bin 24 | 25 | ## install postgrestd 26 | cd ../plrust/ 27 | ./build 28 | ``` 29 | 30 | PL/Rust is a [pgrx](https://github.com/tcdi/pgrx) extension and will need `cargo-pgrx` installed: 31 | 32 | ```bash 33 | cargo install cargo-pgrx --locked 34 | ``` 35 | 36 | ## Pull Requests (PRs) 37 | 38 | - Pull requests for new code or bugfixes should be submitted against the **`develop`** branch 39 | - All pull requests against `develop` will be squashed on merge 40 | - Tests are *expected* to pass before merging 41 | - Diffs in `Cargo.lock` should be checked in 42 | 43 | ### Adding Dependencies 44 | 45 | If a new crate dependency is required for a pull request, and it can't or should not be marked optional and behind some 46 | kind of feature flag, then it should have its reason for being used stated in the Cargo.toml it is added to. This can 47 | be "as a member of a category", in the case of e.g. error handling: 48 | 49 | ```toml 50 | # error handling and logging 51 | eyre = "0.6.8" 52 | thiserror = "1.0" 53 | tracing = "0.1.34" 54 | tracing-error = "0.2.0" 55 | ``` 56 | 57 | It can be as notes for the individual dependencies: 58 | ```toml 59 | once_cell = "1.10.0" # polyfill until std::lazy::OnceCell stabilizes 60 | ``` 61 | 62 | Or it can be both: 63 | 64 | ```toml 65 | # exposed in public API 66 | atomic-traits = "0.3.0" # PgAtomic and shmem init 67 | bitflags = "1.3.2" # BackgroundWorker 68 | bitvec = "1.0" # processing array nullbitmaps 69 | ``` 70 | 71 | You do not need exceptional justification notes in your PR to justify a new dependency as your code will, in most cases, 72 | self-evidently justify the use of the dependency. PL/Rust uses the normal Rust approach of using dependencies based on their 73 | ability to improve correctness and make features possible. It does not reimplement things already available in the Rust 74 | ecosystem unless the addition is trivial (do not add custom derives to save 5~10 lines of code in one site) or the ecosystem 75 | crates are not compatible with Postgres (unfortunately common for Postgres data types). 76 | 77 | ## Releases 78 | 79 | On a new PL/Rust release, **`develop`** will be merged to **`main`** via merge commit. 80 | 81 | 82 | ### Release Candidates AKA Betas 83 | PL/Rust prefers using `x.y.z-{alpha,beta}.n` format for naming release candidates, 84 | starting at `alpha.0` if the new release candidate does not seem "feature complete", 85 | or at `beta.0` if it is not expected to need new feature work. Remember that `beta` will supersede `alpha` in versions 86 | for users who don't pin a version. 87 | 88 | ## Licensing 89 | 90 | You agree that all code you submit in pull requests to https://github.com/tcdi/plrust/pulls 91 | is offered according to the PostgreSQL License, thus may be freely licensed and sublicensed, 92 | and that you are satisfied with the existing copyright notice as of opening your PR, which is: 93 | 94 | ``` 95 | Portions Copyright 2020-2021 ZomboDB, LLC. 96 | 97 | Portions Copyright 2021-2023 Technology Concepts & Design, Inc. 98 | ``` 99 | 100 | It is the latter to which copyrights for all merged code is assigned. 101 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "plrust", 5 | "plrust-trusted-pgrx", 6 | "plrust-tests", 7 | ] 8 | exclude = ["plrustc"]#, "builder"] 9 | 10 | [profile.dev.build-override] 11 | opt-level = 3 12 | 13 | [profile.dev] 14 | panic = "unwind" 15 | 16 | [profile.release] 17 | panic = "unwind" 18 | opt-level = 3 19 | lto = "fat" 20 | codegen-units = 1 21 | strip = "debuginfo" 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The PostgreSQL License 2 | 3 | Copyright (c): Portions Copyright 2020-2021 ZomboDB, LLC. Portions Copyright 2021-2023 Technology Concepts & Design, Inc. 4 | 5 | Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. 6 | 7 | IN NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 8 | 9 | COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND COPYRIGHT HOLDER HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 10 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security 2 | 3 | ## Reporting Security Issues 4 | 5 | **Please do not report security vulnerabilities through public GitHub issues.** 6 | 7 | Instead, please open a [security advisory][advisory] to notify the maintainers. You should receive a response within 3 working days. If for some reason you do not, please follow up via email to ensure we received your original message. 8 | 9 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 10 | 11 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 12 | * Full paths of source file(s) related to the manifestation of the issue 13 | * The location of the affected source code (tag/branch/commit or direct URL) 14 | * Any special configuration required to reproduce the issue 15 | * Step-by-step instructions to reproduce the issue 16 | * Proof-of-concept or exploit code (if possible) 17 | * Impact of the issue, including how an attacker might exploit the issue 18 | 19 | This information will help us triage your report more quickly. 20 | 21 | If you find a vulnerability anywhere in this project, such as the source or scripts, 22 | then please let the maintainers know ASAP and we will fix it as a critical priority. 23 | 24 | [advisory]: https://github.com/tcdi/plrust/security/advisories/new 25 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | # Building doc book 2 | 3 | The book is built [using `mdbook`](https://rust-lang.github.io/mdBook/index.html). 4 | 5 | To install everything you need, run the following: 6 | 7 | ```bash 8 | cargo install --locked mdbook-variables mdbook 9 | ``` 10 | 11 | Note that at the time of this writing, you may see a warning message similar to: `Warning: The variables plugin was built against version 0.4.32 of mdbook, but we're being called from version 0.4.34`. This is a known issue from the mdbook-variables author. See here: https://gitlab.com/tglman/mdbook-variables/-/issues/3 12 | 13 | Serve the book locally and open your default browser. 14 | 15 | ```bash 16 | cd plrust/doc 17 | mdbook serve --open 18 | ``` -------------------------------------------------------------------------------- /doc/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Ryan Lambert"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "PL/Rust Guide" 7 | 8 | [preprocessor.variables.variables] 9 | toolchain_ver = "1.72.0" 10 | -------------------------------------------------------------------------------- /doc/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [What is PL/Rust?](./plrust.md) 4 | 5 | # Installation 6 | 7 | - [Install Prerequisites](./install-prerequisites.md) 8 | - [Install PL/Rust](./install-plrust.md) 9 | - [Update PL/Rust](./update-plrust.md) 10 | - [Install PL/Rust on Debian/Ubuntu](./install-plrust-on-debian-ubuntu.md) 11 | - [Try PL/Rust with Docker](./try-plrust-with-docker.md) 12 | 13 | 14 | # PL/Rust Usage 15 | 16 | - [PL/Rust Functions](./use-plrust.md) 17 | - [Function Anatomy](./functions/anatomy.md) 18 | - [Arguments](./functions/arguments.md) 19 | - [Return Type](./functions/return-type.md) 20 | - [Set Returning Functions](./functions/set-returning-functions.md) 21 | - [Data types](./data-types.md) 22 | - [No Unsigned Types](./data-types/no-unsigned-types.md) 23 | - [Arrays](./data-types/arrays.md) 24 | - [User Defined Types](./data-types/udts.md) 25 | - [Built-in functions](./built-in-functions.md) 26 | - [Logging to PostgreSQL from PL/Rust](./logging.md) 27 | - [Triggers](./triggers.md) 28 | - [SPI](./spi.md) 29 | - [Dynamic Function Calling](dynamic-function-calling.md) 30 | - [Trusted and Untrusted PL/Rust](./trusted-untrusted.md) 31 | - [PostgreSQL configuration](./config-pg.md) 32 | 33 | # PL/Rust Under the Hood 34 | 35 | - [Architecture](./architecture.md) 36 | - [Designing for Trust](./designing-for-trust.md) 37 | - [External Dependencies](./dependencies.md) 38 | - [Lints](./config-lints.md) 39 | - [Environment variables](./config-env-var.md) 40 | -------------------------------------------------------------------------------- /doc/src/assets/architecture_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcdi/plrust/4f17512c1ef7d2acede54a1f8cfb901a5815e1cf/doc/src/assets/architecture_1.png -------------------------------------------------------------------------------- /doc/src/built-in-functions.md: -------------------------------------------------------------------------------- 1 | # Built-in functions 2 | 3 | This page documents many of the high level functions, 4 | targeted functionality is covered on dedicated sub-sections. 5 | 6 | - [Server Programming Interface (SPI)](spi.md) 7 | - [Triggers](triggers.md) 8 | - [Logging to PostgreSQL from PL/Rust](logging.md) 9 | 10 | 11 | ## Functions available 12 | 13 | Functions available to PL/Rust are defined under 14 | the [`trusted-pgrx` directory in `lib.rs`](https://github.com/tcdi/plrust/blob/main/trusted-pgrx/src/lib.rs). User functions in `plrust` will not compile if they use 15 | the `unsafe` keyword. 16 | There are a handful of functions in `trusted-pgrx` that are 17 | declared unsafe; `plrust` functions cannot use them because they would need an `unsafe {}` block. 18 | 19 | 20 | ## Datum functions 21 | 22 | PL/Rust function support for various Datums are documented by 23 | [pgrx on docs.rs](https://docs.rs/pgrx/latest/pgrx/datum/index.html), 24 | the source is [on GitHub](https://github.com/tcdi/pgrx/tree/master/pgrx/src/datum) for those interested. 25 | There are Datums defined in `pgrx` that are not included in PL/Rust 26 | because they have not been imported by `plrust`. 27 | 28 | 29 | [`AnyNumeric`](https://docs.rs/pgrx/latest/pgrx/datum/numeric/struct.AnyNumeric.html): 30 | A plain PostgreSQL `NUMERIC` with default precision and scale values. This is a sufficient type to represent any Rust primitive value from `i128::MIN` to `u128::MAX` and anything in between. 31 | 32 | [`FromDatum`](https://docs.rs/pgrx/latest/pgrx/datum/trait.FromDatum.html) and [`IntoDatum`](https://docs.rs/pgrx/latest/pgrx/datum/trait.IntoDatum.html): Provide conversions between `pg_sys::Datum` and Rust types. 33 | 34 | 35 | [`Json`](https://docs.rs/pgrx/latest/pgrx/datum/struct.Json.html) 36 | and 37 | [`JsonB`](https://docs.rs/pgrx/latest/pgrx/datum/struct.JsonB.html) 38 | match the types in PostgreSQL of the same name. 39 | 40 | 41 | [`Date`](https://docs.rs/pgrx/latest/pgrx/datum/struct.Date.html): 42 | A plain PostgreSQL `DATE` type without a time component. 43 | 44 | 45 | `Time` / `TimeWithTimeZone` / `Timestamp` / `TimestampWithTimeZone` 46 | 47 | 48 | Range Support In progress 49 | 50 | 51 | -------------------------------------------------------------------------------- /doc/src/data-types.md: -------------------------------------------------------------------------------- 1 | # Data types 2 | 3 | PL/Rust provides mappings for many of the built-in PostgreSQL data types. Rust's ownership rules means that these 4 | mappings may be different based on their usage. Where it can PL/Rust borrows (zero-copy) arguments and returns 5 | owned values. 6 | 7 | | SQL | PL/Rust Argument | PL/Rust Return Type | 8 | |----------------------------|--------------------------------|--------------------------------| 9 | | `NULL` | `Option::None` | `Option::None` | 10 | | `"char"` | `i8` | `i8` | 11 | | `bigint` | `i64` | `i64` | 12 | | `bool` | `bool` | `bool` | 13 | | `box` | `BOX`1 | `BOX` | 14 | | `bytea` | `&[u8]` | `Vec` | 15 | | `cstring` | `&CStr` | `CString` | 16 | | `date` | `Date` | `Date` | 17 | | `daterange` | `Range` | `Range` | 18 | | `double precision` | `f64` | `f64` | 19 | | `int4range` | `Range` | `Range` | 20 | | `int8range` | `Range` | `Range` | 21 | | `integer` | `i32` | `i32` | 22 | | `interval` | `Interval` | `Interval` | 23 | | `json` | `Json(serde_json::Value)` | `Json(serde_json::Value)` | 24 | | `jsonb` | `JsonB(serde_json::Value)` | `JsonB(serde_json::Value)` | 25 | | `numeric` | `AnyNumeric` | `AnyNumeric` | 26 | | `numrange` | `Range` | `Range` | 27 | | `oid` | `Oid` | `Oid` | 28 | | `point` | `Point` | `Point` | 29 | | `real` | `f32` | `f32` | 30 | | `smallint` | `i16` | `i16` | 31 | | `text` | `&str` | `String` | 32 | | `tid` | `ItemPointerData` | `ItemPointerData` | 33 | | `time with time zone` | `TimeWithTimeZone` | `TimeWithTimeZone` | 34 | | `time` | `Time` | `Time` | 35 | | `timestamp with time zone` | `TimestampWithTimeZone` | `TimestampWithTimeZone` | 36 | | `timestamp` | `Timestamp` | `Timestamp` | 37 | | `tsrange` | `Range` | `Range` | 38 | | `tstzrange` | `Range` | `Range` | 39 | | `uuid` | `Uuid` | `Uuid` | 40 | | `varchar` | `&str` | `String` | 41 | | `void` | n/a | `()` | 42 | 43 | 1: This is Postgres' geometric BOX type, not to be confused with Rust's `Box` type, which stores allocated data on the heap 44 | -------------------------------------------------------------------------------- /doc/src/data-types/no-unsigned-types.md: -------------------------------------------------------------------------------- 1 | # No Unsigned Types 2 | 3 | Rust programmers may be asking "where are the unsigned types like `u32`?". PostgreSQL does not have unsigned integer types. 4 | As such, neither does PL/Rust. 5 | 6 | In order to represent a value larger than `i32::MAX`, `BIGINT` is the proper SQL type. To represent a value larger than 7 | `i64::MAX`, use the `NUMERIC` type. Postgres also has no concept of an `isize` or `usize`, so these have no 8 | corresponding SQL mapping. 9 | 10 | PL/Rust's `AnyNumeric` type has `From` and `TryFrom` implementations for all of Rust's primitive types (plus strings). 11 | This makes it fairly straightforward to up-convert a Rust primitive into a SQL `NUMERIC`: 12 | 13 | ```sql 14 | CREATE OR REPLACE FUNCTION upconvert_bigint(i BIGINT) RETURNS NUMERIC STRICT LANGUAGE plrust AS $$ 15 | let n: AnyNumeric = i.into(); // `i` is an `i64`, lets convert to `AnyNumeric` 16 | Ok(Some(n + 1)) 17 | $$; 18 | 19 | # SELECT upconvert_bigint(9223372036854775807); 20 | upconvert_bigint 21 | --------------------- 22 | 9223372036854775808 23 | (1 row) 24 | ``` 25 | -------------------------------------------------------------------------------- /doc/src/data-types/udts.md: -------------------------------------------------------------------------------- 1 | # User Defined Types 2 | 3 | PL/Rust supports using User Defined Types (UDTs; sometimes referred to as "composite types") in `LANGUAGE plrust` functions. 4 | UDTs can be used as arguments and return types. 5 | 6 | The general approach with UDTs is to first define one in SQL: 7 | 8 | ```sql 9 | CREATE TYPE person AS ( 10 | name text, 11 | age float8 12 | ); 13 | ``` 14 | 15 | `person` can now be used in any PL/Rust function. To instantiate a new `person`: 16 | 17 | ```sql 18 | create function make_person(name text, age float8) returns person 19 | strict parallel safe 20 | language plrust as 21 | $$ 22 | // create the Heap Tuple representation of the SQL type `person` 23 | let mut p = PgHeapTuple::new_composite_type("person")?; 24 | 25 | // set a few of its attributes 26 | // 27 | // Runtime errors can occur if the attribute name is invalid or if the Rust type of the value 28 | // is not compatible with the backing SQL type for that attribute. Hence the use of the `?` operator 29 | p.set_by_name("name", name)?; 30 | p.set_by_name("age", age)?; 31 | 32 | // return the `person` 33 | Ok(Some(p)) 34 | $$; 35 | ``` 36 | 37 | Individual field accessors for the properties are straight-forward: 38 | 39 | ```sql 40 | create function get_person_name(p person) returns text 41 | strict parallel safe 42 | language plrust as 43 | $$ 44 | // `p` is a `PgHeapTuple` over the underlying data for `person` 45 | Ok(p.get_by_name("name")?) 46 | $$; 47 | 48 | create function get_person_age(p person) returns float8 49 | strict parallel safe 50 | language plrust as 51 | $$ 52 | // `p` is a `PgHeapTuple` over the underlying data for `person` 53 | Ok(p.get_by_name("age")?) 54 | $$; 55 | ``` 56 | 57 | A generic accessor, for example, requires encoding knowledge of the UDT structure, but provides quite a bit of flexibility. 58 | 59 | Note that this function `returns text`. This is a common denominator type to represent the various attribute types used 60 | by `person`. Fortunately, Postgres and PL/Rust have fantastic support for converting values to text/Strings: 61 | 62 | ```sql 63 | create function get_person_attribute(p person, attname text) returns text 64 | strict parallel safe 65 | language plrust as 66 | $$ 67 | match attname.to_lowercase().as_str() { 68 | "age" => { 69 | let age:Option = p.get_by_name("age")?; 70 | Ok(age.map(|v| v.to_string())) 71 | }, 72 | "name" => { 73 | Ok(p.get_by_name("name")?) 74 | }, 75 | _ => panic!("unknown attribute: `{attname}`") 76 | } 77 | $$; 78 | ``` 79 | 80 | This lends itself nicely to creating a custom operator to extract a `person`'s named attribute. 81 | 82 | ```sql 83 | create operator ->> (function = get_person_attribute, leftarg = person, rightarg = text); 84 | ``` 85 | 86 | Tying these pieces together: 87 | 88 | ```sql 89 | 90 | -- assume all of the above sql has been executed 91 | 92 | create table people 93 | ( 94 | id serial8 not null primary key, 95 | p person 96 | ); 97 | 98 | insert into people (p) values (make_person('Johnny', 46.24)); 99 | insert into people (p) values (make_person('Joe', 99.09)); 100 | insert into people (p) values (make_person('Dr. Beverly Crusher of the Starship Enterprise', 32.0)); 101 | 102 | select p ->> 'name' as name, (p ->> 'age')::float8 as age from people; 103 | name | age 104 | ------------------------------------------------+------- 105 | Johnny | 46.24 106 | Joe | 99.09 107 | Dr. Beverly Crusher of the Starship Enterprise | 32 108 | (3 rows) 109 | ``` 110 | 111 | ## Discussion 112 | 113 | In Rust, [`PgHeapTuple`](https://docs.rs/plrust-trusted-pgrx/latest/plrust_trusted_pgrx/heap_tuple/struct.PgHeapTuple.html) 114 | is the type that generically represents all UDTs. 115 | 116 | `PgHeapTuple` provides the ability to construct a new UDT by its SQL name. It also provides attribute getter and setter methods 117 | for reading and mutating attributes. 118 | 119 | Attributes can be addressed by name or one-based index. Typical errors such as specifying an attribute name that doesn't 120 | exist, an index that is out of bounds, or a Rust type for the value that is not compatible with that attribute's SQL type 121 | will return a [`TryFromDatumError`](https://docs.rs/plrust-trusted-pgrx/latest/plrust_trusted_pgrx/heap_tuple/enum.TryFromDatumError.html). 122 | An early-return that error using the `?` operator (as demonstrated in the examples above) or matching on the error are 123 | both fine ways of handling such errors. -------------------------------------------------------------------------------- /doc/src/data-types/working-with-null.md: -------------------------------------------------------------------------------- 1 | # Working with NULL 2 | -------------------------------------------------------------------------------- /doc/src/functions/anatomy.md: -------------------------------------------------------------------------------- 1 | # Function Anatomy 2 | 3 | A PL/Rust function is Rust code embedded in an SQL `CREATE FUNCTION` statement. Behind the scenes, PL/Rust injects 4 | the function body into a true Rust function, automatically creating the wrapper function signature along with applying 5 | a set of lints. 6 | 7 | It's important to understand the surrounding code environment of an individual `LANGUAGE plrust` function, and this 8 | environment is different depending on certain properties of the function itself. The important differences arise around 9 | whether the function is declared as `STRICT`. This is discussed in the [`STRICT` and `NULL`](arguments.md) chapter. 10 | 11 | Using a PL/Rust function that converts a `TEXT` datum to lowercase: 12 | 13 | ```sql 14 | CREATE OR REPLACE FUNCTION lcase(s TEXT) RETURNS TEXT LANGUAGE plrust AS $$ 15 | Ok(Some(s.unwrap().to_lowercase())) 16 | $$; 17 | ``` 18 | 19 | PL/Rust then generates the following Rust code: 20 | 21 | ```rust 22 | mod forbidden { 23 | #![forbid(deprecated)] 24 | #![forbid(implied_bounds_entailment)] 25 | #![forbid(plrust_async)] 26 | #![forbid(plrust_autotrait_impls)] 27 | #![forbid(plrust_env_macros)] 28 | #![forbid(plrust_extern_blocks)] 29 | #![forbid(plrust_external_mod)] 30 | #![forbid(plrust_filesystem_macros)] 31 | #![forbid(plrust_fn_pointers)] 32 | #![forbid(plrust_leaky)] 33 | #![forbid(plrust_lifetime_parameterized_traits)] 34 | #![forbid(plrust_print_macros)] 35 | #![forbid(plrust_static_impls)] 36 | #![forbid(plrust_stdio)] 37 | #![forbid(plrust_suspicious_trait_object)] 38 | #![forbid(soft_unstable)] 39 | #![forbid(suspicious_auto_trait_impls)] 40 | #![forbid(unsafe_code)] 41 | #![forbid(where_clauses_object_safety)] 42 | 43 | #[allow(unused_imports)] 44 | use pgrx::prelude::*; 45 | 46 | #[allow(unused_lifetimes)] 47 | fn plrust_fn_oid_16384_16404<'a>( 48 | s: Option<&'a str>, 49 | ) -> ::std::result::Result< 50 | Option, 51 | Box, 52 | > { 53 | Ok(Some(s.unwrap().to_lowercase())) 54 | } 55 | } 56 | ``` 57 | 58 | ### `mod forbidden {}` 59 | 60 | Every PL/Rust function is wrapped with this module and cannot be influenced by users. It exists so that PL/Rust can 61 | apply [lints](../config-lints.md) to the user's code which will detect forbidden code patterns and idioms at compile time. 62 | 63 | ### `#[!forbid(...)]` 64 | 65 | These are the lints that, if triggered, will fail compilation. These [lints](../config-lints.md) are only applied here 66 | and are not applied to external dependencies. 67 | 68 | 69 | ### `use pgrx::prelude::*` 70 | 71 | A default set of types and traits available to every PL/Rust function. Despite the name, these originate from 72 | [`plrust-trusted-pgrx`](https://docs.rs/plrust-trusted-pgrx/latest/plrust_trusted_pgrx/). `plrust-trusted-pgrx` is a very 73 | small subset of `pgrx`, the crate upon which PL/Rust *and* `LANGUAGE plrust` functions are based. 74 | 75 | ### `fn plrust_fn_oid_16384_16404(...) -> ... {}` 76 | 77 | The function in which the `LANGUAGE plrust` function body is injected. The naming convention is the literal string 78 | `plrust_fn_oid_`, then the database's `OID`, an underscore, and the function's `OID`. 79 | 80 | A PL/Rust function author does not need to know this function name and would never have a reason to call it directly, but 81 | it's important to know how the name is generated. 82 | 83 | Generation of the function's [arguments](arguments.md) and [return type](return-type.md) are discussed in more detail in 84 | their respective sections. 85 | 86 | 87 | ### `Ok(Some(s.unwrap().to_lowercase()))` 88 | 89 | The function body itself. This is injected, unchanged, directly from the body of the `CREATE FUNCTION` statement. 90 | 91 | It's worth nothing that the function body is parsed for syntactic correctness by the Rust crate `syn` prior to 92 | generating the entire block of code outlined here. This means PL/Rust doesn't rely on the compiler for syntax checking 93 | -- it happens up-front. As such, syntax errors may report error messages that are sometimes unhelpful. 94 | 95 | -------------------------------------------------------------------------------- /doc/src/functions/arguments.md: -------------------------------------------------------------------------------- 1 | # Function Arguments 2 | 3 | PL/Rust function arguments are mapped in the same order declared by the `CREATE FUNCTION` statement. They'll have the 4 | same names and the types will be mapped following the [supported data type mappings](../data-types.md). Note that the 5 | `STRICT` function property impacts the actual type. This is described below. 6 | 7 | ## Naming 8 | 9 | The basic rules for naming are: 10 | 11 | 1. Argument names are case-sensitive. 12 | 2. Argument names must also be valid Rust identifiers. It's best to stick with lowercase ASCII in the set `[a-z0-9_]`. 13 | 3. Anonymous argument names are not supported. Procedural Languages such as `sql` and `plpgsql` support anonymous arguments where they can be referenced as `$1`, `$2`, etc. PL/Rust does not. 14 | 15 | ## Argument Ownership 16 | 17 | Except in the case of SQL the `TEXT/VARCHAR` and `BYTEA` types, all argument datums are passed to the PL/Rust function 18 | as owned, immutable instances. 19 | 20 | ## Quick Code Example 21 | 22 | Given a `LANGUAGE plrust` function like this: 23 | 24 | ```sql 25 | CREATE OR REPLACE FUNCTION lots_of_args(a TEXT, b INT, c BOOL[], d JSON) RETURNS INT STRICT LANGUAGE plrust AS $$ 26 | // ... code goes here ... 27 | $$; 28 | ``` 29 | 30 | PL/Rust essentially generates a wrapper Rust function like this: 31 | 32 | ```rust 33 | use pgrx::prelude::*; 34 | 35 | fn lots_of_args(a: &str, b: i32, c: Vec>, d: Json) -> Result, Box> { 36 | // ... code goes here ... 37 | } 38 | ``` 39 | 40 | It is the developer's responsibility to fully implement this function, including [returning the proper value](return-type.md). 41 | Note that the above is just an abridged example. The [anatomy](anatomy.md) section describes in detail what really happens. 42 | 43 | The section below describes how the `STRICT` keyword impacts the actual function signature, specifically each argument type. 44 | 45 | ## `STRICT` and `NULL` 46 | 47 | PL/Rust uses Rust's `Option` type to represent arguments that might be NULL, plus all return types. A Postgres UDF 48 | that is not declared as `STRICT` means that any of its arguments *might* be NULL, and PL/Rust is required to account 49 | for this at compile time. This means that the actual PL/Rust function argument type is context dependent. 50 | 51 | As a Postgres refresher, declaring a function as `STRICT` (which is *not* the default) means that if **any** argument 52 | value is `NULL` then the return value is also `NULL`. In this case, Postgres elides calling the function. 53 | 54 | This distinction allows PL/Rust to optimize a bit. `STRICT` functions have Rust argument types of `T` whereas non-`STRICT` 55 | functions have argument types of `Option`. 56 | 57 | Here is the "same" function, the first declared as `STRICT`, the second not: 58 | 59 | ```sql 60 | CREATE OR REPLACE FUNCTION lcase(s TEXT) RETURNS TEXT STRICT LANGUAGE plrust AS $$ 61 | let lcase = s.to_lowercase(); // `s` is a `&str` 62 | Ok(Some(lcase)) 63 | $$; 64 | 65 | # SELECT lcase('HELLO WORLD'), lcase(NULL) IS NULL AS is_null; 66 | lcase | is_null 67 | -------------+--------- 68 | hello world | t 69 | 70 | ``` 71 | 72 | ```sql 73 | CREATE OR REPLACE FUNCTION lcase(s TEXT) RETURNS TEXT LANGUAGE plrust AS $$ 74 | let unwrapped_s = s.unwrap(); // `s` is an `Option<&str>` and will panic if `s` IS NULL 75 | let lcase = unwrapped_s.to_lowercase(); 76 | Ok(Some(lcase)) 77 | $$; 78 | 79 | # SELECT lcase('HELLO WORLD'), lcase(NULL) IS NULL AS is_null; 80 | ERROR: called `Option::unwrap()` on a `None` value 81 | ``` 82 | 83 | Rust programmers likely recognize this error message. When a function is not declared as `STRICT`, it is the programmer's 84 | responsibility to properly handle the possibility of an argument being `Option::None`. 85 | 86 | ## `STRICT` is an Immutable Property 87 | 88 | PL/Rust requires that a `LANGUAGE plrust` function's `STRICT` property be immutable. As such, PL/Rust prohibits 89 | ALTERing the `STRICT` property: 90 | 91 | ```sql 92 | ALTER FUNCTION lcase STRICT; 93 | ERROR: plrust functions cannot have their STRICT property altered 94 | DETAIL: Use 'CREATE OR REPLACE FUNCTION' to alter the STRICT-ness of an existing plrust function 95 | ``` 96 | 97 | Instead, you must `CREATE OR REPLACE` the function. The reason for this is that the underlying Rust wrapper function's 98 | signature will be different and this will require that the code be changed to account for the new argument type 99 | (`Option` or `T`). 100 | 101 | -------------------------------------------------------------------------------- /doc/src/functions/return-type.md: -------------------------------------------------------------------------------- 1 | # Return Type 2 | 3 | Every `LANUAGE plrust` function has the same general return type, which is: 4 | 5 | ```rust 6 | fn foo() -> Result< 7 | Option, 8 | Box 9 | > { 10 | 11 | } 12 | ``` 13 | 14 | The `T` is determined by the mapping from the declared SQL type during `CREATE FUNCTION`, and the rest is essentially 15 | boilerplate to allow easy handling of Rust `Result`s and the SQL concept of NULL. 16 | 17 | ## Why `Option`? 18 | 19 | Any PostgreSQL procedural language function can return NULL. PL/Rust understands and represents SQL NULL as `Option::None`. 20 | It may seem cumbersome, but PL/Rust function must return an `Option`, as either `Some(T)` (non-null value) or `None` (NULL value). 21 | 22 | While PostgreSQL's `STRICT` function property can influence the return value such that "any NULL argument guarantees a 23 | NULL return", Postgres does not have a way to express that a function "will never return NULL". As such, PL/Rust 24 | functions have the opportunity to return NULL built into their underlying function signature. 25 | 26 | If a PL/Rust function would never return NULL, always return the `Some` variant. 27 | 28 | ## Why `Result<..., Box>`? 29 | 30 | Generally speaking, Postgres procedural language functions, and even Postgres internals, can be considered "fail fast" 31 | in that they tend to raise an error/exception at the exact point when it happens. Rust tends towards propagating errors 32 | up the stack, relying on the caller to handle it. 33 | 34 | PL/Rust bridges this gap by requiring all `LANGUAGE plrust` functions to return a `Result`, and PL/Rust itself will 35 | interpret the return value from the function and report a `Result::Err(e)` as a Postgres `ERROR`, aborting the current 36 | transaction. 37 | 38 | Returning a `Result` helps to simplify error handling, especially when a `LANGUAGE plrust` function uses [Spi](../spi.md) 39 | as the Rust `?` operator is usable to propagate errors during function execution. 40 | 41 | Since the Rust "Error" type cannot be expressed as part of the `CREATE FUNCTION` statement, PL/Rust generalizes the 42 | error to `Box` to provide as much compatability as possible with the 43 | wide range of concrete Error types in the Rust ecosystem. 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /doc/src/functions/set-returning-functions.md: -------------------------------------------------------------------------------- 1 | # Set Returning Functions 2 | 3 | PL/Rust supports both set returning function styles, `RETURNS SETOF $type` and `RETURNS TABLE (...)`. In both cases, 4 | the function returns a specialized `Iterator` for the specific style. 5 | 6 | It's useful to think of set returning functions as returning something that resembles a table, either with one unnamed 7 | column (`RETURNS SETOF`) or multiple, named columns (`RETURNS TABLE`). 8 | 9 | In both cases, the Iterator Item type is an `Option`, where `T` is the [return type](return-type.md). The reason 10 | for this is that PL/Rust needs to allow a returned row/tuple to be NULL (`Option::None`). 11 | 12 | ## `RETURNS SETOF $type` 13 | 14 | `RETURNS SETOF $type` returns a "table" with one, unnamed column. Each returned row must be an `Option` of the return 15 | type, either `Some(T)` or `None`, indicating NULL. 16 | 17 | A simple example of splitting a text string on whitespace, following Rust's rules: 18 | 19 | ```sql 20 | CREATE OR REPLACE FUNCTION split_whitespace(s text) RETURNS SETOF text STRICT LANGUAGE plrust AS $$ 21 | let by_whitespace = s.split_whitespace(); // borrows from `s` which is a `&str` 22 | let mapped = by_whitespace.map(|token| { 23 | if token == "this" { None } // just to demonstrate returning a NULL 24 | else { Some(token.to_string()) } 25 | }); 26 | let iter = SetOfIterator::new(mapped); 27 | Ok(Some(iter)) 28 | $$; 29 | ``` 30 | 31 | PL/Rust generates the following method signature for the above function: 32 | 33 | ```rust 34 | fn plrust_fn_oid_19691_336344<'a>( 35 | s: &'a str, 36 | ) -> ::std::result::Result< // the function itself can return a `Result::Err` 37 | Option< // `Option::None` will return zero rows 38 | ::pgrx::iter::SetOfIterator< // indicates returning a set of values 39 | 'a, // allows borrowing from `s` 40 | Option // and the type is an optional, owned string 41 | > 42 | >, 43 | Box, // boilerplate error type 44 | > { 45 | // 46 | } 47 | ``` 48 | 49 | And finally, its result: 50 | 51 | ```sql 52 | SELECT * FROM split_whitespace('hello world, this is a plrust set returning function'); 53 | split_whitespace 54 | ------------------ 55 | hello 56 | world, 57 | -- remember we returned `None` for the token "this" 58 | is 59 | a 60 | plrust 61 | set 62 | returning 63 | function 64 | (9 rows) 65 | ``` 66 | 67 | ## `RETURNS TABLE (...)` 68 | 69 | Returning a table with multiple named (and typed) columns is similar to returning a set. Instead of `SetOfIterator`, 70 | PL/Rust uses `TableIterator`. `TableIterator` is a Rust `Iterator` whose Item is a tuple where its field types match 71 | those of the UDF being created: 72 | 73 | ```sql 74 | CREATE OR REPLACE FUNCTION count_words(s text) RETURNS TABLE (count int, word text) STRICT LANGUAGE plrust AS $$ 75 | use std::collections::HashMap; 76 | let mut buckets: HashMap<&str, i32> = Default::default(); 77 | 78 | for word in s.split_whitespace() { 79 | buckets.entry(word).and_modify(|cnt| *cnt += 1).or_insert(1); 80 | } 81 | 82 | let as_tuples = buckets.into_iter().map(|(word, cnt)| { 83 | ( Some(cnt), Some(word.to_string()) ) 84 | }); 85 | Ok(Some(TableIterator::new(as_tuples))) 86 | $$; 87 | ``` 88 | 89 | PL/Rust generates this function signature: 90 | 91 | ```rust 92 | fn plrust_fn_oid_19691_336349<'a>( 93 | s: &'a str, 94 | ) -> ::std::result::Result::< // the function itself can return a `Result::Err` 95 | Option< // `Option::None` will return zero rows 96 | ::pgrx::iter::TableIterator< // indicates returning a "table" of tuples 97 | 'a, // allows borrowing from `s` 98 | ( // a Rust tuple 99 | ::pgrx::name!(count, Option < i32 >), // the "count" column, can be "NULL" with `Option::None` 100 | ::pgrx::name!(word, Option < String >), // the "word" column, can be "NULL" with `Option::None` 101 | ), 102 | >, 103 | >, 104 | Box, 105 | > { 106 | // 107 | } 108 | ``` 109 | 110 | And the results from this function are: 111 | 112 | ```sql 113 | # SELECT * FROM count_words('this is a test that is testing plrust''s SRF support'); 114 | count | word 115 | -------+---------- 116 | 1 | a 117 | 1 | test 118 | 1 | that 119 | 2 | is 120 | 1 | this 121 | 1 | testing 122 | 1 | SRF 123 | 1 | support 124 | 1 | plrust's 125 | (9 rows) 126 | ``` 127 | 128 | The important thing to keep in mind when writing PL/Rust functions that `RETURNS TABLE` is that the structure being 129 | returned is a Rust tuple of `Option`s where each field's `T` is the [return type](return-type.md) as specified in 130 | the `RETURNS TABLE (...)` clause. -------------------------------------------------------------------------------- /doc/src/spi.md: -------------------------------------------------------------------------------- 1 | # Server Programming Interface (SPI) 2 | 3 | PL/Rust provides support for PostgreSQL's [SPI](https://www.postgresql.org/docs/current/spi.html). 4 | 5 | 6 | 7 | `Error` 8 | 9 | `Result` 10 | 11 | `Spi` 12 | 13 | 14 | 15 | ## Example usage 16 | 17 | The following function uses `SPI` to create a PostgreSQL 18 | [Set Returning Function](https://www.postgresql.org/docs/current/functions-srf.html) (SRF). 19 | 20 | 21 | 22 | ```sql 23 | CREATE FUNCTION spi_srf() 24 | RETURNS SETOF BIGINT 25 | LANGUAGE plrust 26 | AS 27 | $$ 28 | let query = "SELECT id::BIGINT FROM generate_series(1, 3) id;"; 29 | 30 | Spi::connect(|client| { 31 | let mut results = Vec::new(); 32 | let mut tup_table = client.select(query, None, None)?; 33 | 34 | while let Some(row) = tup_table.next() { 35 | let id = row["id"].value::()?; 36 | results.push(id); 37 | } 38 | Ok(Some(SetOfIterator::new(results))) 39 | }) 40 | 41 | $$; 42 | ``` 43 | 44 | ## Complex return types 45 | 46 | PL/Rust currently [does not support `RETURNS TABLE`](https://github.com/tcdi/plrust/issues/36) or 47 | [complex types with `RETURNS SETOF`](https://github.com/tcdi/plrust/issues/200#issuecomment-1426880622). 48 | 49 | 50 | -------------------------------------------------------------------------------- /doc/src/triggers.md: -------------------------------------------------------------------------------- 1 | # Triggers 2 | 3 | PL/Rust functions can be used to define trigger functions on data changes. 4 | A trigger function is created using the `CREATE FUNCTION` command, declaring it as a function with no arguments and a return type of 5 | `trigger`. Trigger variables are available from in `trigger` 6 | to describe the condition that triggered the call and the `new` and `old` 7 | rows. 8 | 9 | PL/Rust trigger support options are [documented on docs.rs](https://docs.rs/pgrx/latest/pgrx/prelude/struct.PgTrigger.html) and defined in the `.rs` files in the 10 | [trigger_support](https://github.com/tcdi/pgrx/tree/master/pgrx/src/trigger_support) directory. 11 | 12 | These examples are an expansion of the code from [`plrust/plrust/src/tests.rs`](https://github.com/tcdi/plrust/blob/main/plrust/src/tests.rs). The elaborations here 13 | illustrate additional functionality. 14 | 15 | ## Table for Triggers 16 | 17 | Create the `plrust.dog` table to allow us to keep track of our dogs, and how much attention 18 | they have received via a count of `scritches`. 19 | 20 | 21 | ```sql 22 | CREATE TABLE plrust.dog ( 23 | id BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY PRIMARY KEY, 24 | name TEXT, 25 | scritches INT NOT NULL DEFAULT 0, 26 | last_scritch TIMESTAMPTZ NOT NULL DEFAULT NOW() 27 | ); 28 | ``` 29 | 30 | The `name` column in `plrust.dog` is the only column without a default 31 | value set. The `scritches` and `last_scritch` column both have defaults set. 32 | The goal of this design is to only have to define the `name` during `INSERT`. 33 | Subsequent `UPDATE` queries should only have to update the 34 | `last_scritch` column. 35 | 36 | ## Trigger example 37 | 38 | The following example creates a trigger function named `plrust.dog_trigger()`. 39 | The trigger will be used on `INSERT` and `UPDATE` with slightly different 40 | behavior depending on which operation is being used. This logic is based 41 | on the value of `trigger.op()?`, for `INSERT` the `trigger.new` object is used, 42 | for `UPDATE` the `trigger.old` object is used. 43 | This code is explained further after the code block. 44 | 45 | 46 | ```sql 47 | CREATE OR REPLACE FUNCTION plrust.dog_trigger() 48 | RETURNS trigger AS 49 | $$ 50 | let tg_op = trigger.op()?; 51 | 52 | let my_row = match tg_op { 53 | INSERT => trigger.new().unwrap(), 54 | _ => trigger.old().unwrap() 55 | }; 56 | let mut my_row = my_row.into_owned(); 57 | 58 | let counter_field = "scritches"; 59 | match my_row.get_by_name::(counter_field)? { 60 | Some(val) => my_row.set_by_name(counter_field, val + 1)?, 61 | None => (), 62 | } 63 | 64 | Ok(Some(my_row)) 65 | $$ 66 | LANGUAGE plrust; 67 | 68 | 69 | CREATE TRIGGER dog_trigger 70 | BEFORE INSERT OR UPDATE ON plrust.dog 71 | FOR EACH ROW 72 | EXECUTE FUNCTION plrust.dog_trigger(); 73 | ``` 74 | 75 | The `tg_op` variable is available from the `trigger.op()` method and has values 76 | of `INSERT`, `UPDATE`, `DELETE` and `TRUNCATE`. See the definition 77 | of [`PgTriggerOperation` for more](https://docs.rs/pgrx/latest/pgrx/prelude/enum.PgTriggerOperation.html). 78 | The `tg_op` value is used in a `match` to define the `my_row` variable. 79 | 80 | 81 | ```rust 82 | let tg_op = trigger.op()?; 83 | 84 | let my_row = match tg_op { 85 | INSERT => trigger.new().unwrap(), 86 | _ => trigger.old().unwrap() 87 | }; 88 | let mut my_row = my_row.into_owned(); 89 | ``` 90 | 91 | With the appropriate `my_row` identified, the next step is to increment the 92 | `scritches` column by 1. This is defined in the `counter_field` variable 93 | for easy reuse. The `get_by_name` and `set_by_name` functions are used for 94 | this operation. 95 | 96 | ```rust 97 | let counter_field = "scritches"; 98 | match my_row.get_by_name::(counter_field)? { 99 | Some(val) => my_row.set_by_name(counter_field, val + 1)?, 100 | None => (), 101 | } 102 | ``` 103 | 104 | Finally, the `my_row` is returned for the operation to proceed. 105 | 106 | ```rust 107 | Ok(Some(my_row)) 108 | ``` 109 | 110 | 111 | Next we `INSERT` a row and then query the table to observe the effects of the trigger. 112 | 113 | ```sql 114 | INSERT INTO plrust.dog (name) VALUES ('Nami'); 115 | SELECT * FROM plrust.dog; 116 | ``` 117 | 118 | The results show that while the `DEFAULT` value for the `scritches` column is 119 | defined as `0` in the table, the initial value is 1 because trigger updated 120 | the value. 121 | 122 | 123 | ```bash 124 | id | name | scritches | last_scritch 125 | ----+------+-----------+------------------------------- 126 | 1 | Nami | 1 | 2023-03-04 17:30:43.601525+00 127 | ``` 128 | 129 | If we update the record for Nami by setting the `last_scritch` value to `NOW()` 130 | the trigger will increment the `scritches` column value for us. 131 | 132 | ```sql 133 | UPDATE plrust.dog 134 | SET last_scritch = NOW() 135 | WHERE id = 1; 136 | 137 | SELECT * FROM plrust.dog; 138 | ``` 139 | 140 | 141 | ``` 142 | id | name | scritches | last_scritch 143 | ----+------+-----------+------------------------------- 144 | 1 | Nami | 2 | 2023-03-04 17:35:05.320482+00 145 | ``` 146 | 147 | 148 | 149 | ## Not yet supported 150 | 151 | Event Triggers and `DO` blocks are not (yet) supported by PL/Rust. 152 | 153 | 154 | -------------------------------------------------------------------------------- /doc/src/trusted-untrusted.md: -------------------------------------------------------------------------------- 1 | # Trusted and Untrusted PL/Rust 2 | 3 | Normally, PL/Rust is installed as a "trusted" programming language named `plrust`. 4 | In this setup, certain Rust and `pgrx` operations are disabled to preserve security. 5 | In general, the operations that are restricted are those that interact with the environment. 6 | This includes file handle operations, require, and use (for external modules). 7 | There is no way to access internals of the database server process or to gain 8 | OS-level access with the permissions of the server process, as a C function can do. 9 | Thus, any unprivileged database user can be permitted to use this language. 10 | 11 | Here is an example of a function that will not work because file system operations are not allowed for security reasons: 12 | 13 | ``` 14 | EXAMPLE COMING SOON 15 | ``` 16 | 17 | The creation of this function will fail as its use of a forbidden operation will be caught by the validator. 18 | 19 | Sometimes it is desirable to write Rust functions that are not restricted. 20 | To handle these cases, PL/Rust can also be installed as an "untrusted" language. 21 | In this case the full Rust language is available including `unsafe` code. 22 | See the 23 | [Untrusted install section](install-plrust.md#untrusted-install) 24 | for steps to install untrusted PL/Rust. 25 | 26 | The writer of an untrusted PL/Rust function must take care that the function cannot be used to do anything unwanted, since it will be able to do anything that could be done by a user logged in as the database administrator. Note that the database system allows only database superusers to create functions in untrusted languages. 27 | 28 | If the above function was created by a superuser using the untrusted `plrust`, execution would succeed. 29 | 30 | -------------------------------------------------------------------------------- /doc/src/try-plrust-with-docker.md: -------------------------------------------------------------------------------- 1 | # Try PL/Rust with Docker 2 | 3 | Giving PL/Rust a try has never been easier with Docker! This document outlines what is required to get a functional Postgres + PL/Rust environment running with just a few commands. 4 | 5 | The PL/Rust repository supplies a Dockerfile named `Dockerfile.try` that contains everything necessary to spin up and test PL/Rust in a target environment. 6 | 7 | The following instructions assume a very basic understanding of what [Docker](https://www.docker.com) is and that it is already installed in the target environment. If Docker is not yet installed, instructions can be found here: 8 | 9 | 1. Clone the PL/Rust code, and switch to that directory 10 | 1. From a command line, run the following from the root of the checkout directory (`sudo` or equivalent may be required): 11 | ``` 12 | docker build -f Dockerfile.try -t tcdi/try-plrust . 13 | ``` 14 | Note that this may take a little while to finish. 15 | 1. Once the above has finished, run the following (`sudo` may be required here): 16 | ``` 17 | docker run -it tcdi/try-plrust 18 | ``` 19 | 1. There will be some output that the Postgres server has started, and `psql` prompt will start up in the foreground: 20 | ``` 21 | Type "help" for help. 22 | 23 | postgres(plrust)=# 24 | ``` 25 | 26 | That's it! From here, the `psql` interactive prompt with PL/Rust installed can be used to create and run PL/Rust functions. Here is a very small example to get started: 27 | 28 | ```SQL 29 | CREATE FUNCTION plrust.one() 30 | RETURNS INT LANGUAGE plrust 31 | AS 32 | $$ 33 | Ok(Some(1)) 34 | $$; 35 | ``` 36 | 37 | Creating PL/Rust functions compiles Rust code in the backend, so this may take some time depending on the host's hardware specifications and internet connection speeds. Once this completes, the PL/Rust function can be executed similar to other Postgres functions: 38 | 39 | ```SQL 40 | SELECT * FROM plrust.one(); 41 | ``` 42 | 43 | which will provide the following results: 44 | 45 | ``` 46 | postgres(plrust)=# SELECT * FROM plrust.one(); 47 | one 48 | ----- 49 | 1 50 | (1 row) 51 | ``` 52 | 53 | To exit out of the prompt and the Docker container, type the Postgres command `quit`: 54 | ``` 55 | postgres(plrust)=# quit 56 | ``` 57 | 58 | ## Alternate running modes 59 | 60 | Running the Docker container using `docker run -it tcdi/try-plrust` as described above will spin up both the Postgres server in the background and the `psql` command line utility in the foreground in the same running container. However, the option exists to run the Postgres server only (with PL/Rust installed) so that an alternative Postgres client can be used. To do this, run the following command (which may require `sudo` or equivalent): 61 | 62 | ``` 63 | docker run -it -p 5432:5432 tcdi/try-plrust server 64 | ``` 65 | 66 | This will set up everything that is necessary and run the Postgres server only, binding to TCP port 5432. Output here will be all of the Postgres log entries, including any errors that result from a PL/Rust compilation error. The final `server` argument in the command indicates that it should launch the `server` script upon container bootup. In order to connect with an alternative client, the only pieces of information that are required are the Postgres username (`postgres`), the hostname or IP address (e.g. `localhost` or `192.168.0.2`) and the port (`5432`). There is no password set for the `postgres` user in this setup. An example Postgres URI might look like this: 67 | 68 | ``` 69 | postgres://postgres@localhost:5432 70 | ``` 71 | 72 | To exit out of server mode, press Ctrl+c in the running Docker container. 73 | 74 | ## Caveats 75 | 76 | * This Dockerfile and resulting image should not be used in production. It does not take many security precautions into consideration. As such, the way `Dockerfile.try` is constructed should not be considered a best practice as it relates to setting up and securing a Postgres instance with PL/Rust installed. 77 | 78 | * The Postgres data directories, logs and built PL/Rust functions are not persistent and are destroyed upon container termination. Externally mounting Postgres' data, log and function directories is outside the scope of this example. 79 | -------------------------------------------------------------------------------- /doc/src/update-plrust.md: -------------------------------------------------------------------------------- 1 | # Update PL/Rust 2 | 3 | This section explains how to update PL/Rust installations. This assumes 4 | you installed PL/Rust following our [installation guide](./install-plrust.md) and pgrx and PL/Rust are installed using the `postgres` Linux user. 5 | 6 | ## Update pgrx 7 | 8 | A PL/Rust update is often accompanied by an update of the underlying 9 | `pgrx` project. Install the latest version of pgrx. 10 | Changing into the plrust folder ensures the `rustc` version used 11 | for installation is the same required by PL/Rust. 12 | 13 | Start as a user with `sudo` access. 14 | 15 | 16 | ```bash 17 | sudo chown postgres -R /usr/share/postgresql/15/extension/ 18 | sudo chown postgres -R /usr/lib/postgresql/15/lib/ 19 | ``` 20 | 21 | 22 | 23 | ```bash 24 | sudo su - postgres 25 | cd ~/plrust 26 | git pull 27 | cargo install cargo-pgrx --locked 28 | ``` 29 | 30 | 31 | ## Update PL/Rust 32 | 33 | 34 | Follow these steps to upgrade PL/Rust from GitLab to use 35 | the latest release. 36 | 37 | 38 | Update `plrustc`, `postgrestd` and `plrust` installations. 39 | 40 | ```bash 41 | cd ~/plrust/plrustc 42 | ./build.sh 43 | mv ~/plrust/build/bin/plrustc ~/.cargo/bin/ 44 | 45 | cd ~/plrust/plrust 46 | PG_VER=15 \ 47 | STD_TARGETS="x86_64-postgres-linux-gnu " \ 48 | ./build 49 | 50 | cargo pgrx install --release \ 51 | --features trusted \ 52 | -c /usr/bin/pg_config 53 | ``` 54 | 55 | 56 | Exit out of `postgres` user back to user with sudo. 57 | 58 | ```bash 59 | exit 60 | ``` 61 | 62 | Restart Postgres, required b/c plrust is in `shared_preload_libraries`. 63 | Set permissions back to default. 64 | 65 | ```bash 66 | sudo systemctl restart postgresql 67 | 68 | sudo chown root -R /usr/share/postgresql/15/extension/ 69 | sudo chown root -R /usr/lib/postgresql/15/lib/ 70 | ``` 71 | 72 | ## Rust versions 73 | 74 | See the section(s) about Rust versions 75 | the the [Install PL/Rust](./install-plrust.md) section. 76 | Pay special attention to the versions defined by PL/Rust, and your 77 | system defaults for `rustc` and `rustup`. 78 | 79 | 80 | -------------------------------------------------------------------------------- /plrust-tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "plrust-tests" 3 | version = "1.2.8" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [features] 10 | default = ["pg13"] 11 | pg13 = ["pgrx/pg13", "pgrx-tests/pg13" ] 12 | pg14 = ["pgrx/pg14", "pgrx-tests/pg14" ] 13 | pg15 = ["pgrx/pg15", "pgrx-tests/pg15" ] 14 | pg16 = ["pgrx/pg16", "pgrx-tests/pg16" ] 15 | pg_test = [] 16 | trusted = [] 17 | 18 | [dependencies] 19 | pgrx = "=0.11.0" 20 | tempfile = "3.8.1" 21 | once_cell = "1.18.0" 22 | 23 | [dev-dependencies] 24 | pgrx-tests = "=0.11.0" 25 | tempfile = "3.8.1" 26 | once_cell = "1.18.0" 27 | -------------------------------------------------------------------------------- /plrust-tests/plrust_tests.control: -------------------------------------------------------------------------------- 1 | comment = 'plrust_tests: Created by pgrx' 2 | default_version = '@CARGO_VERSION@' 3 | module_pathname = '$libdir/plrust_tests' 4 | relocatable = false 5 | superuser = true 6 | requires = 'plrust' 7 | -------------------------------------------------------------------------------- /plrust-tests/run-tests.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | VERSION=$1 4 | 5 | if [ -z ${VERSION} ]; then 6 | echo "usage: ./run-tests.sh pgXX [test-name]" 7 | exit 1 8 | fi 9 | 10 | TEST_DIR=`pwd` 11 | 12 | set -e 13 | 14 | # install the plrust extension into the pgrx-managed postgres 15 | echo "============================" 16 | echo " installing plrust" 17 | echo 18 | cd ../plrust 19 | echo "\q" | cargo pgrx run ${VERSION} 20 | 21 | # run the test suite from this crate 22 | cd ${TEST_DIR} 23 | 24 | echo 25 | echo "============================" 26 | echo " running plrust-tests suite" 27 | echo 28 | 29 | cargo pgrx test ${VERSION} $2 30 | 31 | -------------------------------------------------------------------------------- /plrust-tests/src/alter.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Portions Copyright 2020-2021 ZomboDB, LLC. 3 | Portions Copyright 2021-2023 Technology Concepts & Design, Inc. 4 | 5 | All rights reserved. 6 | 7 | Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. 8 | */ 9 | 10 | #[cfg(any(test, feature = "pg_test"))] 11 | #[pgrx::pg_schema] 12 | mod tests { 13 | use pgrx::prelude::*; 14 | 15 | #[pg_test] 16 | #[search_path(@extschema@)] 17 | #[should_panic = "plrust functions cannot have their STRICT property altered"] 18 | fn plrust_cant_change_strict_off() -> spi::Result<()> { 19 | let definition = r#" 20 | CREATE FUNCTION cant_change_strict_off() 21 | RETURNS int 22 | LANGUAGE plrust 23 | AS $$ Ok(Some(1)) $$; 24 | "#; 25 | Spi::run(definition)?; 26 | Spi::run("ALTER FUNCTION cant_change_strict_off() CALLED ON NULL INPUT") 27 | } 28 | 29 | #[pg_test] 30 | #[search_path(@extschema@)] 31 | #[should_panic = "plrust functions cannot have their STRICT property altered"] 32 | fn plrust_cant_change_strict_on() -> spi::Result<()> { 33 | let definition = r#" 34 | CREATE FUNCTION cant_change_strict_on() 35 | RETURNS int 36 | LANGUAGE plrust 37 | AS $$ Ok(Some(1)) $$; 38 | "#; 39 | Spi::run(definition)?; 40 | Spi::run("ALTER FUNCTION cant_change_strict_on() RETURNS NULL ON NULL INPUT") 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /plrust-tests/src/argument.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Portions Copyright 2020-2021 ZomboDB, LLC. 3 | Portions Copyright 2021-2023 Technology Concepts & Design, Inc. 4 | 5 | All rights reserved. 6 | 7 | Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. 8 | */ 9 | 10 | #[cfg(any(test, feature = "pg_test"))] 11 | #[pgrx::pg_schema] 12 | mod tests { 13 | use pgrx::prelude::*; 14 | 15 | #[pg_test] 16 | #[search_path(@extschema@)] 17 | #[should_panic = "parameter name \"a\" used more than once"] 18 | fn plrust_dup_args() -> spi::Result<()> { 19 | let definition = r#" 20 | CREATE FUNCTION not_unique(a int, a int) 21 | RETURNS int AS 22 | $$ 23 | Ok(a) 24 | $$ LANGUAGE plrust; 25 | "#; 26 | Spi::run(definition)?; 27 | let result = Spi::get_one::("SELECT not_unique(1, 2);\n"); 28 | assert_eq!(Ok(Some(1)), result); 29 | Ok(()) 30 | } 31 | 32 | #[pg_test] 33 | #[search_path(@extschema@)] 34 | #[should_panic = "PL/Rust does not support unnamed arguments"] 35 | fn plrust_defaulting_dup_args() -> spi::Result<()> { 36 | let definition = r#" 37 | CREATE FUNCTION not_unique(int, arg0 int) 38 | RETURNS int AS 39 | $$ 40 | Ok(arg0) 41 | $$ LANGUAGE plrust; 42 | "#; 43 | Spi::run(definition)?; 44 | let result = Spi::get_one::("SELECT not_unique(1, 2);\n"); 45 | assert_eq!(Ok(Some(1)), result); 46 | Ok(()) 47 | } 48 | 49 | #[pg_test] 50 | #[search_path(@extschema@)] 51 | #[should_panic(expected = "PL/Rust does not support unnamed arguments")] 52 | fn unnamed_args() -> spi::Result<()> { 53 | Spi::run("CREATE FUNCTION unnamed_arg(int) RETURNS int LANGUAGE plrust as $$ Ok(None) $$;") 54 | } 55 | 56 | #[pg_test] 57 | #[search_path(@extschema@)] 58 | #[should_panic(expected = "PL/Rust does not support unnamed arguments")] 59 | fn named_unnamed_args() -> spi::Result<()> { 60 | Spi::run("CREATE FUNCTION named_unnamed_arg(bob text, int) RETURNS int LANGUAGE plrust as $$ Ok(None) $$;") 61 | } 62 | 63 | #[pg_test] 64 | #[search_path(@extschema@)] 65 | #[should_panic( 66 | expected = "is an invalid Rust identifier and cannot be used as an argument name" 67 | )] 68 | fn invalid_arg_identifier() -> spi::Result<()> { 69 | Spi::run("CREATE FUNCTION invalid_arg_identifier(\"this isn't a valid rust identifier\" int) RETURNS int LANGUAGE plrust as $$ Ok(None) $$;") 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /plrust-tests/src/basic.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Portions Copyright 2020-2021 ZomboDB, LLC. 3 | Portions Copyright 2021-2023 Technology Concepts & Design, Inc. 4 | 5 | All rights reserved. 6 | 7 | Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. 8 | */ 9 | 10 | #[cfg(any(test, feature = "pg_test"))] 11 | #[pgrx::pg_schema] 12 | mod tests { 13 | use pgrx::{datum::IntoDatum, prelude::*}; 14 | 15 | #[pg_test] 16 | #[search_path(@extschema@)] 17 | fn plrust_basic() -> spi::Result<()> { 18 | let definition = r#" 19 | CREATE FUNCTION sum_array(a BIGINT[]) RETURNS BIGINT 20 | IMMUTABLE STRICT 21 | LANGUAGE PLRUST AS 22 | $$ 23 | Ok(Some(a.into_iter().map(|v| v.unwrap_or_default()).sum())) 24 | $$; 25 | "#; 26 | Spi::run(definition)?; 27 | 28 | let retval = Spi::get_one_with_args::( 29 | r#" 30 | SELECT sum_array($1); 31 | "#, 32 | vec![( 33 | PgBuiltInOids::INT4ARRAYOID.oid(), 34 | vec![1, 2, 3].into_datum(), 35 | )], 36 | ); 37 | assert_eq!(retval, Ok(Some(6))); 38 | Ok(()) 39 | } 40 | 41 | #[pg_test] 42 | #[search_path(@extschema@)] 43 | fn plrust_update() -> spi::Result<()> { 44 | let definition = r#" 45 | CREATE FUNCTION update_me() RETURNS TEXT 46 | IMMUTABLE STRICT 47 | LANGUAGE PLRUST AS 48 | $$ 49 | Ok(String::from("booper").into()) 50 | $$; 51 | "#; 52 | Spi::run(definition)?; 53 | 54 | let retval = Spi::get_one( 55 | r#" 56 | SELECT update_me(); 57 | "#, 58 | ); 59 | assert_eq!(retval, Ok(Some("booper"))); 60 | 61 | let definition = r#" 62 | CREATE OR REPLACE FUNCTION update_me() RETURNS TEXT 63 | IMMUTABLE STRICT 64 | LANGUAGE PLRUST AS 65 | $$ 66 | Ok(Some(String::from("swooper"))) 67 | $$; 68 | "#; 69 | Spi::run(definition)?; 70 | 71 | let retval = Spi::get_one( 72 | r#" 73 | SELECT update_me(); 74 | "#, 75 | ); 76 | assert_eq!(retval, Ok(Some("swooper"))); 77 | Ok(()) 78 | } 79 | 80 | #[pg_test] 81 | #[search_path(@extschema@)] 82 | fn plrust_spi() -> spi::Result<()> { 83 | let random_definition = r#" 84 | CREATE FUNCTION random_contributor_pet() RETURNS TEXT 85 | STRICT 86 | LANGUAGE PLRUST AS 87 | $$ 88 | Ok(Spi::get_one("SELECT name FROM contributors_pets ORDER BY random() LIMIT 1")?) 89 | $$; 90 | "#; 91 | Spi::run(random_definition)?; 92 | 93 | let retval = Spi::get_one::( 94 | r#" 95 | SELECT random_contributor_pet(); 96 | "#, 97 | ); 98 | assert!(retval.is_ok()); 99 | assert!(retval.unwrap().is_some()); 100 | 101 | let specific_definition = r#" 102 | CREATE FUNCTION contributor_pet(name TEXT) RETURNS BIGINT 103 | STRICT 104 | LANGUAGE PLRUST AS 105 | $$ 106 | use pgrx::IntoDatum; 107 | Ok(Spi::get_one_with_args( 108 | "SELECT id FROM contributors_pets WHERE name = $1", 109 | vec![(PgBuiltInOids::TEXTOID.oid(), name.into_datum())], 110 | )?) 111 | $$; 112 | "#; 113 | Spi::run(specific_definition)?; 114 | 115 | let retval = Spi::get_one::( 116 | r#" 117 | SELECT contributor_pet('Nami'); 118 | "#, 119 | ); 120 | assert_eq!(retval, Ok(Some(2))); 121 | Ok(()) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /plrust-tests/src/borrow_mut_error.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Portions Copyright 2020-2021 ZomboDB, LLC. 3 | Portions Copyright 2021-2023 Technology Concepts & Design, Inc. 4 | 5 | All rights reserved. 6 | 7 | Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. 8 | */ 9 | 10 | #[cfg(any(test, feature = "pg_test"))] 11 | #[pgrx::pg_schema] 12 | mod tests { 13 | use pgrx::prelude::*; 14 | 15 | #[pg_test] 16 | #[should_panic(expected = "issue78 works")] 17 | fn test_issue_78() -> spi::Result<()> { 18 | let sql = r#"CREATE OR REPLACE FUNCTION raise_error() RETURNS TEXT 19 | IMMUTABLE STRICT 20 | LANGUAGE PLRUST AS 21 | $$ 22 | pgrx::error!("issue78 works"); 23 | Ok(Some("hi".to_string())) 24 | $$;"#; 25 | Spi::run(sql)?; 26 | Spi::get_one::("SELECT raise_error()")?; 27 | Ok(()) 28 | } 29 | 30 | #[pg_test] 31 | fn test_issue_79() -> spi::Result<()> { 32 | let sql = r#" 33 | create or replace function fn1(i int) returns int strict language plrust as $$ 34 | [code] 35 | notice!("{}", "fn1 started"); 36 | let cmd = format!("select fn2({})", i); 37 | Spi::connect(|client| 38 | { 39 | client.select(&cmd, None, None); 40 | }); 41 | notice!("{}", "fn1 finished"); 42 | Ok(Some(1)) 43 | $$; 44 | 45 | create or replace function fn2(i int) returns int strict language plrust as $$ 46 | [code] 47 | notice!("{}", "fn2 started"); 48 | notice!("{}", "fn2 finished"); 49 | Ok(Some(2)) 50 | $$; 51 | "#; 52 | Spi::run(sql)?; 53 | assert_eq!(Ok(Some(1)), Spi::get_one::("SELECT fn1(1)")); 54 | Ok(()) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /plrust-tests/src/ddl.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Portions Copyright 2020-2021 ZomboDB, LLC. 3 | Portions Copyright 2021-2023 Technology Concepts & Design, Inc. 4 | 5 | All rights reserved. 6 | 7 | Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. 8 | */ 9 | 10 | #[cfg(any(test, feature = "pg_test"))] 11 | #[pgrx::pg_schema] 12 | mod tests { 13 | use pgrx::prelude::*; 14 | 15 | #[pg_test] 16 | #[search_path(@extschema@)] 17 | fn plrust_aggregate() -> spi::Result<()> { 18 | let definition = r#" 19 | CREATE FUNCTION plrust_sum_state(state INT, next INT) RETURNS INT 20 | IMMUTABLE STRICT 21 | LANGUAGE PLRUST AS 22 | $$ 23 | Ok(Some(state + next)) 24 | $$; 25 | CREATE AGGREGATE plrust_sum(INT) 26 | ( 27 | SFUNC = plrust_sum_state, 28 | STYPE = INT, 29 | INITCOND = '0' 30 | ); 31 | "#; 32 | Spi::run(definition)?; 33 | 34 | let retval = Spi::get_one::( 35 | r#" 36 | SELECT plrust_sum(value) FROM UNNEST(ARRAY [1, 2, 3]) as value; 37 | "#, 38 | ); 39 | assert_eq!(retval, Ok(Some(6))); 40 | Ok(()) 41 | } 42 | 43 | #[pg_test] 44 | #[search_path(@extschema@)] 45 | fn plrust_trigger() -> spi::Result<()> { 46 | let definition = r#" 47 | CREATE TABLE dogs ( 48 | name TEXT, 49 | scritches INT NOT NULL DEFAULT 0 50 | ); 51 | 52 | CREATE FUNCTION pet_trigger() RETURNS trigger AS $$ 53 | let mut new = trigger.new().unwrap().into_owned(); 54 | 55 | let field = "scritches"; 56 | 57 | match new.get_by_name::(field)? { 58 | Some(val) => new.set_by_name(field, val + 1)?, 59 | None => (), 60 | } 61 | 62 | Ok(Some(new)) 63 | $$ LANGUAGE plrust; 64 | 65 | CREATE TRIGGER pet_trigger BEFORE INSERT OR UPDATE ON dogs 66 | FOR EACH ROW EXECUTE FUNCTION pet_trigger(); 67 | 68 | INSERT INTO dogs (name) VALUES ('Nami'); 69 | "#; 70 | Spi::run(definition)?; 71 | 72 | let retval = Spi::get_one::( 73 | r#" 74 | SELECT scritches FROM dogs; 75 | "#, 76 | ); 77 | assert_eq!(retval, Ok(Some(1))); 78 | Ok(()) 79 | } 80 | 81 | #[pg_test] 82 | fn replace_function() -> spi::Result<()> { 83 | Spi::run("CREATE FUNCTION replace_me() RETURNS int LANGUAGE plrust AS $$ Ok(Some(1)) $$")?; 84 | assert_eq!(Ok(Some(1)), Spi::get_one("SELECT replace_me()")); 85 | 86 | Spi::run( 87 | "CREATE OR REPLACE FUNCTION replace_me() RETURNS int LANGUAGE plrust AS $$ Ok(Some(2)) $$", 88 | )?; 89 | assert_eq!(Ok(Some(2)), Spi::get_one("SELECT replace_me()")); 90 | Ok(()) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /plrust-tests/src/dependencies.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Portions Copyright 2020-2021 ZomboDB, LLC. 3 | Portions Copyright 2021-2023 Technology Concepts & Design, Inc. 4 | 5 | All rights reserved. 6 | 7 | Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. 8 | */ 9 | 10 | #[cfg(any(test, feature = "pg_test"))] 11 | #[pgrx::pg_schema] 12 | mod tests { 13 | use pgrx::prelude::*; 14 | 15 | #[pg_test] 16 | #[cfg(not(feature = "sandboxed"))] 17 | #[search_path(@extschema@)] 18 | fn plrust_deps_supported() -> spi::Result<()> { 19 | let definition = r#" 20 | CREATE FUNCTION colorize(input TEXT) RETURNS TEXT 21 | IMMUTABLE STRICT 22 | LANGUAGE PLRUST AS 23 | $$ 24 | [dependencies] 25 | owo-colors = "3" 26 | [code] 27 | use owo_colors::OwoColorize; 28 | Ok(Some(input.purple().to_string())) 29 | $$; 30 | "#; 31 | Spi::run(definition)?; 32 | 33 | let retval = Spi::get_one_with_args::( 34 | r#" 35 | SELECT colorize($1); 36 | "#, 37 | vec![(PgBuiltInOids::TEXTOID.oid(), "Nami".into_datum())], 38 | ); 39 | assert!(retval.is_ok()); 40 | assert!(retval.unwrap().is_some()); 41 | 42 | // Regression test: A previous version of PL/Rust would abort if this was called twice, so call it twice: 43 | let retval = Spi::get_one_with_args::( 44 | r#" 45 | SELECT colorize($1); 46 | "#, 47 | vec![(PgBuiltInOids::TEXTOID.oid(), "Nami".into_datum())], 48 | ); 49 | assert!(retval.is_ok()); 50 | assert!(retval.unwrap().is_some()); 51 | Ok(()) 52 | } 53 | 54 | #[pg_test] 55 | #[cfg(not(feature = "sandboxed"))] 56 | #[search_path(@extschema@)] 57 | fn plrust_deps_supported_deps_in_toml_table() -> spi::Result<()> { 58 | let definition = r#" 59 | CREATE FUNCTION say_hello() RETURNS TEXT 60 | IMMUTABLE STRICT 61 | LANGUAGE PLRUST AS 62 | $$ 63 | [dependencies] 64 | tokio = ">=1" 65 | owo-colors = "3" 66 | [code] 67 | Ok(Some("hello".to_string())) 68 | $$; 69 | "#; 70 | Spi::run(definition)?; 71 | 72 | let retval = Spi::get_one_with_args::( 73 | r#" 74 | SELECT say_hello(); 75 | "#, 76 | vec![(PgBuiltInOids::TEXTOID.oid(), "hello".into_datum())], 77 | ); 78 | assert_eq!(retval, Ok(Some("hello".to_string()))); 79 | Ok(()) 80 | } 81 | 82 | #[pg_test] 83 | #[cfg(not(feature = "sandboxed"))] 84 | #[search_path(@extschema@)] 85 | fn plrust_deps_not_supported() { 86 | let definition = r#" 87 | CREATE FUNCTION colorize(input TEXT) RETURNS TEXT 88 | IMMUTABLE STRICT 89 | LANGUAGE PLRUST AS 90 | $$ 91 | [dependencies] 92 | regex = "1.6.5" 93 | [code] 94 | Ok(Some("test")) 95 | $$; 96 | "#; 97 | let res = std::panic::catch_unwind(|| { 98 | Spi::run(definition).expect("SQL for plrust_deps_not_supported() failed") 99 | }); 100 | assert!(res.is_err()); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /plrust-tests/src/fn_call.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Portions Copyright 2020-2021 ZomboDB, LLC. 3 | Portions Copyright 2021-2023 Technology Concepts & Design, Inc. 4 | 5 | All rights reserved. 6 | 7 | Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. 8 | */ 9 | 10 | #[cfg(any(test, feature = "pg_test"))] 11 | #[pgrx::pg_schema] 12 | mod tests { 13 | use pgrx::prelude::*; 14 | 15 | #[pg_test] 16 | #[search_path(@extschema@)] 17 | fn plrust_fn_call() -> spi::Result<()> { 18 | let sql = r#" 19 | CREATE FUNCTION dynamic_function(i int) RETURNS int LANGUAGE plrust AS $$ Ok(i) $$; 20 | 21 | CREATE FUNCTION test_plrust_fn_call() RETURNS int LANGUAGE plrust 22 | AS $$ 23 | let result = fn_call("dynamic_function", &[&Arg::Value(42i32)]); 24 | 25 | assert_eq!(result, Ok(Some(42i32))); 26 | 27 | Ok(None) 28 | $$; 29 | 30 | SELECT test_plrust_fn_call(); 31 | "#; 32 | Spi::run(sql) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /plrust-tests/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod alter; 2 | mod argument; 3 | mod basic; 4 | mod blocked_code; 5 | mod borrow_mut_error; 6 | mod ddl; 7 | mod dependencies; 8 | mod fn_call; 9 | mod matches; 10 | mod panics; 11 | mod range; 12 | mod recursion; 13 | mod return_values; 14 | mod round_trip; 15 | mod time_and_dates; 16 | mod trusted; 17 | mod user_defined_types; 18 | mod versioning; 19 | 20 | use pgrx::prelude::*; 21 | 22 | pgrx::pg_module_magic!(); 23 | 24 | /* 25 | Portions Copyright 2020-2021 ZomboDB, LLC. 26 | Portions Copyright 2021-2023 Technology Concepts & Design, Inc. 27 | 28 | All rights reserved. 29 | 30 | Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. 31 | */ 32 | 33 | #[cfg(any(test, feature = "pg_test"))] 34 | #[pgrx::pg_schema] 35 | mod tests { 36 | use pgrx::prelude::*; 37 | 38 | // Bootstrap a testing table for non-immutable functions 39 | extension_sql!( 40 | r#" 41 | CREATE TABLE contributors_pets ( 42 | id serial8 not null primary key, 43 | name text 44 | ); 45 | INSERT INTO contributors_pets (name) VALUES ('Brandy'); 46 | INSERT INTO contributors_pets (name) VALUES ('Nami'); 47 | INSERT INTO contributors_pets (name) VALUES ('Sally'); 48 | INSERT INTO contributors_pets (name) VALUES ('Anchovy'); 49 | "#, 50 | name = "create_contributors_pets", 51 | ); 52 | } 53 | 54 | #[cfg(any(test, feature = "pg_test"))] 55 | pub mod pg_test { 56 | use once_cell::sync::Lazy; 57 | use tempfile::{tempdir, TempDir}; 58 | static WORK_DIR: Lazy = Lazy::new(|| { 59 | let work_dir = tempdir().expect("Couldn't create tempdir"); 60 | format!("plrust.work_dir='{}'", work_dir.path().display()) 61 | }); 62 | static LOG_LEVEL: &str = "plrust.tracing_level=trace"; 63 | static PLRUST_ALLOWED_DEPENDENCIES_FILE_NAME: &str = "allowed_deps.toml"; 64 | static PLRUST_ALLOWED_DEPENDENCIES_FILE_DIRECTORY: Lazy = Lazy::new(|| { 65 | use std::io::Write; 66 | let temp_allowed_deps_dir = tempdir().expect("Couldnt create tempdir"); 67 | 68 | let file_path = temp_allowed_deps_dir 69 | .path() 70 | .join(PLRUST_ALLOWED_DEPENDENCIES_FILE_NAME); 71 | let mut allowed_deps = std::fs::File::create(&file_path).unwrap(); 72 | allowed_deps 73 | .write_all( 74 | r#" 75 | owo-colors = "=3.5.0" 76 | tokio = { version = "=1.19.2", features = ["rt", "net"]} 77 | plutonium = "*" 78 | "# 79 | .as_bytes(), 80 | ) 81 | .unwrap(); 82 | 83 | temp_allowed_deps_dir 84 | }); 85 | 86 | static PLRUST_ALLOWED_DEPENDENCIES: Lazy = Lazy::new(|| { 87 | format!( 88 | "plrust.allowed_dependencies='{}'", 89 | PLRUST_ALLOWED_DEPENDENCIES_FILE_DIRECTORY 90 | .path() 91 | .join(PLRUST_ALLOWED_DEPENDENCIES_FILE_NAME) 92 | .to_str() 93 | .unwrap() 94 | ) 95 | }); 96 | 97 | pub fn setup(_options: Vec<&str>) { 98 | // perform one-off initialization when the pg_test framework starts 99 | } 100 | 101 | pub fn postgresql_conf_options() -> Vec<&'static str> { 102 | vec![ 103 | &*WORK_DIR, 104 | &*LOG_LEVEL, 105 | &*PLRUST_ALLOWED_DEPENDENCIES, 106 | "shared_preload_libraries='plrust'", 107 | ] 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /plrust-tests/src/matches.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Portions Copyright 2020-2021 ZomboDB, LLC. 3 | Portions Copyright 2021-2023 Technology Concepts & Design, Inc. 4 | 5 | All rights reserved. 6 | 7 | Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. 8 | */ 9 | 10 | #[cfg(any(test, feature = "pg_test"))] 11 | #[pgrx::pg_schema] 12 | mod tests { 13 | use pgrx::prelude::*; 14 | 15 | #[pg_test] 16 | #[search_path(@extschema@)] 17 | fn plrust_call_1st() -> spi::Result<()> { 18 | let definition = r#" 19 | CREATE FUNCTION ret_1st(a int, b int) 20 | RETURNS int AS 21 | $$ 22 | Ok(a) 23 | $$ LANGUAGE plrust; 24 | "#; 25 | Spi::run(definition)?; 26 | let result_1 = Spi::get_one::("SELECT ret_1st(1, 2);\n"); 27 | assert_eq!(Ok(Some(1)), result_1); // may get: Some(1) 28 | Ok(()) 29 | } 30 | 31 | #[pg_test] 32 | #[search_path(@extschema@)] 33 | fn plrust_call_2nd() -> spi::Result<()> { 34 | let definition = r#" 35 | CREATE FUNCTION ret_2nd(a int, b int) 36 | RETURNS int AS 37 | $$ 38 | Ok(b) 39 | $$ LANGUAGE plrust; 40 | "#; 41 | Spi::run(definition)?; 42 | let result_2 = Spi::get_one::("SELECT ret_2nd(1, 2);\n"); 43 | assert_eq!(Ok(Some(2)), result_2); // may get: Some(2) 44 | Ok(()) 45 | } 46 | 47 | #[pg_test] 48 | #[search_path(@extschema@)] 49 | fn plrust_call_me() -> spi::Result<()> { 50 | let definition = r#" 51 | CREATE FUNCTION pick_ret(a int, b int, pick int) 52 | RETURNS int AS 53 | $$ 54 | Ok(match pick { 55 | Some(0) => a, 56 | Some(1) => b, 57 | _ => None, 58 | }) 59 | $$ LANGUAGE plrust; 60 | "#; 61 | Spi::run(definition)?; 62 | let result_a = Spi::get_one::("SELECT pick_ret(3, 4, 0);"); 63 | let result_b = Spi::get_one::("SELECT pick_ret(5, 6, 1);"); 64 | let result_c = Spi::get_one::("SELECT pick_ret(7, 8, 2);"); 65 | let result_z = Spi::get_one::("SELECT pick_ret(9, 99, -1);"); 66 | assert_eq!(Ok(Some(3)), result_a); // may get: Some(4) or None 67 | assert_eq!(Ok(Some(6)), result_b); // may get: None 68 | assert_eq!(Ok(None), result_c); 69 | assert_eq!(Ok(None), result_z); 70 | Ok(()) 71 | } 72 | 73 | #[pg_test] 74 | #[search_path(@extschema@)] 75 | fn plrust_call_me_call_me() -> spi::Result<()> { 76 | let definition = r#" 77 | CREATE FUNCTION ret_1st(a int, b int) 78 | RETURNS int AS 79 | $$ 80 | Ok(a) 81 | $$ LANGUAGE plrust; 82 | 83 | CREATE FUNCTION ret_2nd(a int, b int) 84 | RETURNS int AS 85 | $$ 86 | Ok(b) 87 | $$ LANGUAGE plrust; 88 | 89 | CREATE FUNCTION pick_ret(a int, b int, pick int) 90 | RETURNS int AS 91 | $$ 92 | Ok(match pick { 93 | Some(0) => a, 94 | Some(1) => b, 95 | _ => None, 96 | }) 97 | $$ LANGUAGE plrust; 98 | "#; 99 | Spi::run(definition)?; 100 | let result_1 = Spi::get_one::("SELECT ret_1st(1, 2);\n"); 101 | let result_2 = Spi::get_one::("SELECT ret_2nd(1, 2);\n"); 102 | let result_a = Spi::get_one::("SELECT pick_ret(3, 4, 0);"); 103 | let result_b = Spi::get_one::("SELECT pick_ret(5, 6, 1);"); 104 | let result_c = Spi::get_one::("SELECT pick_ret(7, 8, 2);"); 105 | let result_z = Spi::get_one::("SELECT pick_ret(9, 99, -1);"); 106 | assert_eq!(Ok(None), result_z); 107 | assert_eq!(Ok(None), result_c); 108 | assert_eq!(Ok(Some(6)), result_b); // may get: None 109 | assert_eq!(Ok(Some(3)), result_a); // may get: Some(4) or None 110 | assert_eq!(Ok(Some(2)), result_2); // may get: Some(1) 111 | assert_eq!(Ok(Some(1)), result_1); // may get: Some(2) 112 | Ok(()) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /plrust-tests/src/panics.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Portions Copyright 2020-2021 ZomboDB, LLC. 3 | Portions Copyright 2021-2023 Technology Concepts & Design, Inc. 4 | 5 | All rights reserved. 6 | 7 | Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. 8 | */ 9 | 10 | #[cfg(any(test, feature = "pg_test"))] 11 | #[pgrx::pg_schema] 12 | mod tests { 13 | use pgrx::prelude::*; 14 | 15 | #[pg_test] 16 | #[search_path(@extschema@)] 17 | #[should_panic = "yup"] 18 | fn pgrx_can_panic() { 19 | panic!("yup") 20 | } 21 | 22 | #[pg_test] 23 | #[search_path(@extschema@)] 24 | #[should_panic = "yup"] 25 | fn plrust_can_panic() -> spi::Result<()> { 26 | let definition = r#" 27 | CREATE FUNCTION shut_up_and_explode() 28 | RETURNS text AS 29 | $$ 30 | panic!("yup"); 31 | Ok(None) 32 | $$ LANGUAGE plrust; 33 | "#; 34 | 35 | Spi::run(definition)?; 36 | let retval = Spi::get_one::("SELECT shut_up_and_explode();\n"); 37 | assert_eq!(retval, Ok(None)); 38 | Ok(()) 39 | } 40 | #[pg_test] 41 | #[search_path(@extschema@)] 42 | #[should_panic = "xxx"] 43 | #[ignore] 44 | fn plrust_pgloglevel_dont_allcaps_panic() -> spi::Result<()> { 45 | // This test attempts to annihilate the database. 46 | // It relies on the existing assumption that tests are run in the same Postgres instance, 47 | // so this test will make all tests "flaky" if Postgres suddenly goes down with it. 48 | let definition = r#" 49 | CREATE FUNCTION dont_allcaps_panic() 50 | RETURNS text AS 51 | $$ 52 | ereport!(PANIC, PgSqlErrorCode::ERRCODE_INTERNAL_ERROR, "If other tests completed, PL/Rust did not actually destroy the entire database, \ 53 | But if you see this in the error output, something might be wrong."); 54 | Ok(Some("lol".into())) 55 | $$ LANGUAGE plrust; 56 | "#; 57 | Spi::run(definition)?; 58 | let retval = Spi::get_one::("SELECT dont_allcaps_panic();\n"); 59 | assert_eq!(retval, Ok(Some("lol".into()))); 60 | Ok(()) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /plrust-tests/src/range.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Portions Copyright 2020-2021 ZomboDB, LLC. 3 | Portions Copyright 2021-2023 Technology Concepts & Design, Inc. 4 | 5 | All rights reserved. 6 | 7 | Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. 8 | */ 9 | 10 | #[cfg(any(test, feature = "pg_test"))] 11 | #[pgrx::pg_schema] 12 | mod tests { 13 | use pgrx::prelude::*; 14 | 15 | #[pg_test] 16 | fn test_int4range() -> spi::Result<()> { 17 | Spi::run( 18 | r#"CREATE FUNCTION test_int4range(r int4range) RETURNS int4range LANGUAGE plrust AS $$ Ok(r) $$"#, 19 | )?; 20 | let r = Spi::get_one::>("SELECT test_int4range('[1, 10)'::int4range);")? 21 | .expect("SPI result was null"); 22 | assert_eq!(r, (1..10).into()); 23 | Ok(()) 24 | } 25 | 26 | #[pg_test] 27 | fn test_int8range() -> spi::Result<()> { 28 | Spi::run( 29 | r#"CREATE FUNCTION test_int8range(r int8range) RETURNS int8range LANGUAGE plrust AS $$ Ok(r) $$"#, 30 | )?; 31 | let r = Spi::get_one::>("SELECT test_int8range('[1, 10)'::int8range);")? 32 | .expect("SPI result was null"); 33 | assert_eq!(r, (1..10).into()); 34 | Ok(()) 35 | } 36 | 37 | #[pg_test] 38 | fn test_numrange() -> spi::Result<()> { 39 | Spi::run( 40 | r#"CREATE FUNCTION test_numrange(r numrange) RETURNS numrange LANGUAGE plrust AS $$ Ok(r) $$"#, 41 | )?; 42 | let r = Spi::get_one::>("SELECT test_numrange('[1, 10]'::numrange);")? 43 | .expect("SPI result was null"); 44 | assert_eq!( 45 | r, 46 | Range::new( 47 | AnyNumeric::try_from(1.0f32).unwrap(), 48 | AnyNumeric::try_from(10.0f32).unwrap() 49 | ) 50 | ); 51 | Ok(()) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /plrust-tests/src/recursion.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Portions Copyright 2020-2021 ZomboDB, LLC. 3 | Portions Copyright 2021-2023 Technology Concepts & Design, Inc. 4 | 5 | All rights reserved. 6 | 7 | Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. 8 | */ 9 | 10 | #[cfg(any(test, feature = "pg_test"))] 11 | #[pgrx::pg_schema] 12 | mod tests {} 13 | -------------------------------------------------------------------------------- /plrust-tests/src/return_values.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Portions Copyright 2020-2021 ZomboDB, LLC. 3 | Portions Copyright 2021-2023 Technology Concepts & Design, Inc. 4 | 5 | All rights reserved. 6 | 7 | Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. 8 | */ 9 | 10 | #[cfg(any(test, feature = "pg_test"))] 11 | #[pgrx::pg_schema] 12 | mod tests { 13 | use pgrx::prelude::*; 14 | 15 | #[pg_test] 16 | #[search_path(@ extschema @)] 17 | fn plrust_returns_setof() -> spi::Result<()> { 18 | let definition = r#" 19 | CREATE OR REPLACE FUNCTION boop_srf(names TEXT[]) RETURNS SETOF TEXT 20 | IMMUTABLE STRICT 21 | LANGUAGE PLRUST AS 22 | $$ 23 | Ok(Some(::pgrx::iter::SetOfIterator::new(names.into_iter().map(|maybe| maybe.map(|name| name.to_string() + " was booped!"))))) 24 | $$; 25 | "#; 26 | Spi::run(definition)?; 27 | 28 | let retval: spi::Result<_> = Spi::connect(|client| { 29 | let mut table = client.select( 30 | "SELECT * FROM boop_srf(ARRAY['Nami', 'Brandy'])", 31 | None, 32 | None, 33 | )?; 34 | 35 | let mut found = vec![]; 36 | while table.next().is_some() { 37 | let value = table.get_one::()?; 38 | found.push(value) 39 | } 40 | 41 | Ok(Some(found)) 42 | }); 43 | 44 | assert_eq!( 45 | retval, 46 | Ok(Some(vec![ 47 | Some("Nami was booped!".into()), 48 | Some("Brandy was booped!".into()), 49 | ])) 50 | ); 51 | Ok(()) 52 | } 53 | 54 | #[pg_test] 55 | fn test_srf_one_col() -> spi::Result<()> { 56 | Spi::run( 57 | "CREATE FUNCTION srf_one_col() RETURNS TABLE (a int) LANGUAGE plrust AS $$ 58 | Ok(Some(TableIterator::new(vec![( Some(1), )].into_iter()))) 59 | $$;", 60 | )?; 61 | 62 | let a = Spi::get_one::("SELECT * FROM srf_one_col()")?; 63 | assert_eq!(a, Some(1)); 64 | 65 | Ok(()) 66 | } 67 | 68 | #[pg_test] 69 | fn test_srf_two_col() -> spi::Result<()> { 70 | Spi::run( 71 | "CREATE FUNCTION srf_two_col() RETURNS TABLE (a int, b int) LANGUAGE plrust AS $$ 72 | Ok(Some(TableIterator::new(vec![(Some(1), Some(2))].into_iter()))) 73 | $$;", 74 | )?; 75 | 76 | let (a, b) = Spi::get_two::("SELECT * FROM srf_two_col()")?; 77 | assert_eq!(a, Some(1)); 78 | assert_eq!(b, Some(2)); 79 | 80 | Ok(()) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /plrust-tests/src/round_trip.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Portions Copyright 2020-2021 ZomboDB, LLC. 3 | Portions Copyright 2021-2023 Technology Concepts & Design, Inc. 4 | 5 | All rights reserved. 6 | 7 | Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. 8 | */ 9 | 10 | #[cfg(any(test, feature = "pg_test"))] 11 | #[pgrx::pg_schema] 12 | mod tests { 13 | use pgrx::prelude::*; 14 | use std::error::Error; 15 | 16 | #[pg_test] 17 | fn test_tid_roundtrip() -> spi::Result<()> { 18 | Spi::run( 19 | r#"CREATE FUNCTION tid_roundtrip(t tid) RETURNS tid LANGUAGE plrust AS $$ Ok(t) $$"#, 20 | )?; 21 | let tid = Spi::get_one::("SELECT tid_roundtrip('(42, 99)'::tid)")? 22 | .expect("SPI result was null"); 23 | let (blockno, offno) = pgrx::item_pointer_get_both(tid); 24 | assert_eq!(blockno, 42); 25 | assert_eq!(offno, 99); 26 | Ok(()) 27 | } 28 | 29 | #[pg_test] 30 | fn test_return_bytea() -> spi::Result<()> { 31 | Spi::run( 32 | r#"CREATE FUNCTION return_bytea() RETURNS bytea LANGUAGE plrust AS $$ Ok(Some(vec![1,2,3])) $$"#, 33 | )?; 34 | let bytes = Spi::get_one::>("SELECT return_bytea()")?.expect("SPI result was null"); 35 | assert_eq!(bytes, vec![1, 2, 3]); 36 | Ok(()) 37 | } 38 | 39 | #[pg_test] 40 | fn test_cstring_roundtrip() -> Result<(), Box> { 41 | use std::ffi::CStr; 42 | Spi::run( 43 | r#"CREATE FUNCTION cstring_roundtrip(s cstring) RETURNS cstring STRICT LANGUAGE plrust as $$ Ok(Some(s.into())) $$;"#, 44 | )?; 45 | let cstr = Spi::get_one::<&CStr>("SELECT cstring_roundtrip('hello')")? 46 | .expect("SPI result was null"); 47 | let expected = CStr::from_bytes_with_nul(b"hello\0")?; 48 | assert_eq!(cstr, expected); 49 | Ok(()) 50 | } 51 | 52 | #[pg_test] 53 | fn test_point() -> spi::Result<()> { 54 | Spi::run( 55 | r#"CREATE FUNCTION test_point(p point) RETURNS point LANGUAGE plrust AS $$ Ok(p) $$"#, 56 | )?; 57 | let p = Spi::get_one::("SELECT test_point('42, 99'::point);")? 58 | .expect("SPI result was null"); 59 | assert_eq!(p.x, 42.0); 60 | assert_eq!(p.y, 99.0); 61 | Ok(()) 62 | } 63 | 64 | #[pg_test] 65 | fn test_box() -> spi::Result<()> { 66 | Spi::run(r#"CREATE FUNCTION test_box(b box) RETURNS box LANGUAGE plrust AS $$ Ok(b) $$"#)?; 67 | let b = Spi::get_one::("SELECT test_box('1,2,3,4'::box);")? 68 | .expect("SPI result was null"); 69 | assert_eq!(b.high.x, 3.0); 70 | assert_eq!(b.high.y, 4.0); 71 | assert_eq!(b.low.x, 1.0); 72 | assert_eq!(b.low.y, 2.0); 73 | Ok(()) 74 | } 75 | 76 | #[pg_test] 77 | fn test_uuid() -> spi::Result<()> { 78 | Spi::run( 79 | r#"CREATE FUNCTION test_uuid(u uuid) RETURNS uuid LANGUAGE plrust AS $$ Ok(u) $$"#, 80 | )?; 81 | let u = Spi::get_one::( 82 | "SELECT test_uuid('e4176a4d-790c-4750-85b7-665d72471173'::uuid);", 83 | )? 84 | .expect("SPI result was null"); 85 | assert_eq!( 86 | u, 87 | pgrx::Uuid::from_bytes([ 88 | 0xe4, 0x17, 0x6a, 0x4d, 0x79, 0x0c, 0x47, 0x50, 0x85, 0xb7, 0x66, 0x5d, 0x72, 0x47, 89 | 0x11, 0x73 90 | ]) 91 | ); 92 | 93 | Ok(()) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /plrust-tests/src/time_and_dates.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Portions Copyright 2020-2021 ZomboDB, LLC. 3 | Portions Copyright 2021-2023 Technology Concepts & Design, Inc. 4 | 5 | All rights reserved. 6 | 7 | Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. 8 | */ 9 | 10 | #[cfg(any(test, feature = "pg_test"))] 11 | #[pgrx::pg_schema] 12 | mod tests { 13 | use pgrx::prelude::*; 14 | use std::error::Error; 15 | 16 | #[pg_test] 17 | fn test_daterange() -> Result<(), Box> { 18 | Spi::run( 19 | r#"CREATE FUNCTION test_daterange(r daterange) RETURNS daterange LANGUAGE plrust AS $$ Ok(r) $$"#, 20 | )?; 21 | let r = Spi::get_one::>( 22 | "SELECT test_daterange('[1977-03-20, 1980-01-01)'::daterange);", 23 | )? 24 | .expect("SPI result was null"); 25 | assert_eq!( 26 | r, 27 | Range::new( 28 | Date::new(1977, 3, 20)?, 29 | RangeBound::Exclusive(Date::new(1980, 01, 01)?) 30 | ) 31 | ); 32 | Ok(()) 33 | } 34 | 35 | #[pg_test] 36 | fn test_tsrange() -> Result<(), Box> { 37 | Spi::run( 38 | r#"CREATE FUNCTION test_tsrange(p tsrange) RETURNS tsrange LANGUAGE plrust AS $$ Ok(p) $$"#, 39 | )?; 40 | let r = Spi::get_one::>( 41 | "SELECT test_tsrange('[1977-03-20, 1980-01-01)'::tsrange);", 42 | )? 43 | .expect("SPI result was null"); 44 | assert_eq!( 45 | r, 46 | Range::new( 47 | Timestamp::new(1977, 3, 20, 0, 0, 0.0)?, 48 | RangeBound::Exclusive(Timestamp::new(1980, 01, 01, 0, 0, 0.0)?) 49 | ) 50 | ); 51 | Ok(()) 52 | } 53 | 54 | #[pg_test] 55 | fn test_tstzrange() -> Result<(), Box> { 56 | Spi::run( 57 | r#"CREATE FUNCTION test_tstzrange(p tstzrange) RETURNS tstzrange LANGUAGE plrust AS $$ Ok(p) $$"#, 58 | )?; 59 | let r = Spi::get_one::>( 60 | "SELECT test_tstzrange('[1977-03-20, 1980-01-01)'::tstzrange);", 61 | )? 62 | .expect("SPI result was null"); 63 | assert_eq!( 64 | r, 65 | Range::new( 66 | TimestampWithTimeZone::new(1977, 3, 20, 0, 0, 0.0)?, 67 | RangeBound::Exclusive(TimestampWithTimeZone::new(1980, 01, 01, 0, 0, 0.0)?) 68 | ) 69 | ); 70 | Ok(()) 71 | } 72 | 73 | #[pg_test] 74 | fn test_interval() -> Result<(), Box> { 75 | Spi::run( 76 | r#"CREATE FUNCTION get_interval_hours(i interval) RETURNS numeric STRICT LANGUAGE plrust AS $$ Ok(i.extract_part(DateTimeParts::Hour)) $$"#, 77 | )?; 78 | let hours = 79 | Spi::get_one::("SELECT get_interval_hours('3 days 9 hours 12 seconds')")? 80 | .expect("SPI result was null"); 81 | assert_eq!(hours, AnyNumeric::from(9)); 82 | Ok(()) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /plrust-tests/src/user_defined_types.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Portions Copyright 2020-2021 ZomboDB, LLC. 3 | Portions Copyright 2021-2023 Technology Concepts & Design, Inc. 4 | 5 | All rights reserved. 6 | 7 | Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. 8 | */ 9 | 10 | #[cfg(any(test, feature = "pg_test"))] 11 | #[pgrx::pg_schema] 12 | mod tests { 13 | use pgrx::prelude::*; 14 | 15 | #[pg_test] 16 | fn test_udt() -> spi::Result<()> { 17 | Spi::run( 18 | r#" 19 | CREATE TYPE person AS ( 20 | name text, 21 | age float8 22 | ); 23 | 24 | create function make_person(name text, age float8) returns person 25 | strict parallel safe 26 | language plrust as 27 | $$ 28 | // create the Heap Tuple representation of the SQL type `person` 29 | let mut p = PgHeapTuple::new_composite_type("person")?; 30 | 31 | // set a few of its attributes 32 | // 33 | // Runtime errors can occur if the attribute name is invalid or if the Rust type of the value 34 | // is not compatible with the backing SQL type for that attribute. Hence the use of the `?` operator 35 | p.set_by_name("name", name)?; 36 | p.set_by_name("age", age)?; 37 | 38 | // return the `person` 39 | Ok(Some(p)) 40 | $$; 41 | 42 | create function get_person_name(p person) returns text 43 | strict parallel safe 44 | language plrust as 45 | $$ 46 | // `p` is a `PgHeapTuple` over the underlying data for `person` 47 | Ok(p.get_by_name("name")?) 48 | $$; 49 | 50 | create function get_person_age(p person) returns float8 51 | strict parallel safe 52 | language plrust as 53 | $$ 54 | // `p` is a `PgHeapTuple` over the underlying data for `person` 55 | Ok(p.get_by_name("age")?) 56 | $$; 57 | 58 | create function get_person_attribute(p person, attname text) returns text 59 | strict parallel safe 60 | language plrust as 61 | $$ 62 | match attname.to_lowercase().as_str() { 63 | "age" => { 64 | let age:Option = p.get_by_name("age")?; 65 | Ok(age.map(|v| v.to_string())) 66 | }, 67 | "name" => { 68 | Ok(p.get_by_name("name")?) 69 | }, 70 | _ => panic!("unknown attribute: `{attname}`") 71 | } 72 | $$; 73 | 74 | create operator ->> (function = get_person_attribute, leftarg = person, rightarg = text); 75 | 76 | create table people 77 | ( 78 | id serial8 not null primary key, 79 | p person 80 | ); 81 | 82 | insert into people (p) values (make_person('Johnny', 46.24)); 83 | insert into people (p) values (make_person('Joe', 99.09)); 84 | insert into people (p) values (make_person('Dr. Beverly Crusher of the Starship Enterprise', 32.0)); 85 | "#, 86 | )?; 87 | 88 | let johnny = Spi::get_one::>( 89 | "SELECT p FROM people WHERE p->>'name' = 'Johnny';", 90 | )? 91 | .expect("SPI result was null"); 92 | 93 | let age = johnny.get_by_name::("age")?.expect("age was null"); 94 | assert_eq!(age, 46.24); 95 | 96 | Ok(()) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /plrust-tests/src/versioning.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Portions Copyright 2020-2021 ZomboDB, LLC. 3 | Portions Copyright 2021-2023 Technology Concepts & Design, Inc. 4 | 5 | All rights reserved. 6 | 7 | Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. 8 | */ 9 | 10 | #[cfg(any(test, feature = "pg_test"))] 11 | #[pgrx::pg_schema] 12 | mod tests { 13 | use pgrx::prelude::*; 14 | 15 | #[pg_test] 16 | #[cfg(not(feature = "sandboxed"))] 17 | #[search_path(@extschema@)] 18 | fn plrust_deps_supported_semver_parse() -> spi::Result<()> { 19 | let definition = r#" 20 | CREATE FUNCTION colorize(input TEXT) RETURNS TEXT 21 | IMMUTABLE STRICT 22 | LANGUAGE PLRUST AS 23 | $$ 24 | [dependencies] 25 | owo-colors = ">2" 26 | [code] 27 | use owo_colors::OwoColorize; 28 | Ok(Some(input.purple().to_string())) 29 | $$; 30 | "#; 31 | Spi::run(definition)?; 32 | 33 | let retval = Spi::get_one_with_args::( 34 | r#" 35 | SELECT colorize($1); 36 | "#, 37 | vec![(PgBuiltInOids::TEXTOID.oid(), "Nami".into_datum())], 38 | ); 39 | assert!(retval.is_ok()); 40 | assert!(retval.unwrap().is_some()); 41 | 42 | // Regression test: A previous version of PL/Rust would abort if this was called twice, so call it twice: 43 | let retval = Spi::get_one_with_args::( 44 | r#" 45 | SELECT colorize($1); 46 | "#, 47 | vec![(PgBuiltInOids::TEXTOID.oid(), "Nami".into_datum())], 48 | ); 49 | assert!(retval.is_ok()); 50 | assert!(retval.unwrap().is_some()); 51 | Ok(()) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /plrust-trusted-pgrx/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "plrust-trusted-pgrx" 3 | version = "1.2.8" 4 | authors = ["TCDI "] 5 | edition = "2021" 6 | license = "PostgreSQL" 7 | description = "Minimal set of `pgrx` rexports for plrust, which the authors have deemed trusted" 8 | homepage = "https://github.com/tcdi/plrust/" 9 | repository = "https://github.com/tcdi/plrust/" 10 | 11 | [lib] 12 | crate-type = ["rlib"] 13 | 14 | [features] 15 | pg13 = ["pgrx/pg13"] 16 | pg14 = ["pgrx/pg14"] 17 | pg15 = ["pgrx/pg15"] 18 | pg16 = ["pgrx/pg16"] 19 | 20 | [dependencies] 21 | # changing the pgrx version will likely require at least a minor version bump to this create 22 | pgrx = { version = "=0.11.0", features = [ "no-schema-generation" ], default-features = false } 23 | 24 | [package.metadata.docs.rs] 25 | features = ["pg14"] 26 | rustc-args = ["--cfg", "docsrs"] 27 | -------------------------------------------------------------------------------- /plrust-trusted-pgrx/README.md: -------------------------------------------------------------------------------- 1 | [![crates.io badge](https://img.shields.io/crates/v/plrust-trusted-pgrx.svg)](https://crates.io/crates/plrust-trusted-pgrx) 2 | [![docs.rs badge](https://docs.rs/pgrx/badge.svg)](https://docs.rs/plrust-trusted-pgrx) 3 | 4 | `plrust-trusted-pgrx` is a re-export crate based on [`pgrx`](https://crates.io/crates/pgrx) which exports the minimal set 5 | of capabilities necessary to compile [`plrust`](https://github.com/tcdi/plrust) user functions along with safe access to 6 | various parts of Postgres including some data types, logging, Spi, and triggers. 7 | 8 | You might be tempted to use this for your own pgrx extension development, but you shouldn't. It's intended for use only 9 | with plrust. -------------------------------------------------------------------------------- /plrust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "plrust" 3 | version = "1.2.8" 4 | authors = ["TCDI "] 5 | edition = "2021" 6 | license = "PostgreSQL Open Source License" 7 | description = "A Trusted Rust procedural language for PostgreSQL" 8 | homepage = "https://github.com/tcdi/plrust/" 9 | repository = "https://github.com/tcdi/plrust/" 10 | build = "build.rs" 11 | 12 | [lib] 13 | crate-type = ["cdylib"] 14 | 15 | [features] 16 | default = ["pg14"] 17 | pg13 = ["pgrx/pg13", "pgrx-tests/pg13"] 18 | pg14 = ["pgrx/pg14", "pgrx-tests/pg14"] 19 | pg15 = ["pgrx/pg15", "pgrx-tests/pg15"] 20 | pg16 = ["pgrx/pg16", "pgrx-tests/pg16"] 21 | # is plrust to be compiled as a "trusted" language handler, meaning it requires postgrestd at runtime 22 | trusted = [] 23 | pg_test = [] 24 | # Be accomodating to sandboxed builds with no network. 25 | sandboxed = [] 26 | # Forcibly enable a feature used by x86_64 MacOS machines because they're bad at `dlclose()` 27 | force_enable_x86_64_darwin_generations = [] 28 | # verify = [] # Revisit this later for other verification features 29 | 30 | [dependencies] 31 | cfg-if = "1" # platform conditional helper 32 | once_cell = "1.18.0" # polyfills a nightly feature 33 | semver = "1.0.20" 34 | home = "0.5.5" # where can we find cargo? 35 | 36 | # working with our entry in pg_catalog.pg_proc 37 | base64 = "0.21.5" 38 | flate2 = "1.0.28" 39 | serde = "1.0.192" 40 | serde_json = "1.0.108" 41 | 42 | # pgrx core details 43 | pgrx = { version = "=0.11.0" } 44 | 45 | # language handler support 46 | libloading = "0.8.1" 47 | toml = "0.8.8" 48 | tempfile = "3.8.1" 49 | 50 | # error handling, tracing, formatting 51 | thiserror = "1.0" 52 | eyre = "0.6" 53 | color-eyre = "0.6" 54 | tracing = { version = "0.1", features = [ "valuable" ] } 55 | tracing-subscriber = { version = "0.3", features = [ "env-filter" ] } 56 | tracing-error = "0.2" 57 | prettyplease = "0.2" 58 | 59 | # procedural macro handling 60 | syn = "2" 61 | quote = "1" 62 | proc-macro2 = "1" 63 | omnipath = "0.1.6" 64 | 65 | [target.'cfg(target_os="linux")'.dependencies] 66 | memfd = "0.6.4" # for anonymously writing/loading user function .so 67 | 68 | 69 | [dev-dependencies] 70 | pgrx-tests = { version = "=0.11.0" } 71 | once_cell = "1.18.0" 72 | toml = "0.8.8" 73 | -------------------------------------------------------------------------------- /plrust/build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -xe 3 | 4 | if [ -z "$STD_TARGETS" ]; then 5 | # if none specified, always build for these two targets 6 | if [ `uname` == "Darwin" ]; then 7 | STD_TARGETS="x86_64-apple-darwin-postgres aarch64-apple-darwin-postgres" 8 | else 9 | STD_TARGETS="x86_64-postgres-linux-gnu aarch64-postgres-linux-gnu" 10 | fi 11 | fi 12 | 13 | # and depending on the platform we're building on, we need to set a linker flag for the other 14 | # this'll get hairy when we support more platforms and we should port this script to Rust 15 | 16 | if [ `uname` == "Darwin" ]; then 17 | if [ `uname -m` == "arm64" ]; then 18 | if [[ -z "$CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER" ]] && [[ -z "$CARGO_TARGET_AARCH64_APPLE_DARWIN_POSTGRES_LINKER" ]]; then 19 | export CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER=cc 20 | export CARGO_TARGET_AARCH64_APPLE_DARWIN_POSTGRES_LINKER=cc 21 | fi 22 | elif [ `uname -m` == "x86_64" ]; then 23 | if [[ -z "$CARGO_TARGET_X86_64_APPLE_DARWIN_LINKER" ]] && [[ -z "$CARGO_TARGET_X86_64_APPLE_DARWIN_POSTGRES_LINKER" ]]; then 24 | export CARGO_TARGET_X86_64_APPLE_DARWIN_LINKER=cc 25 | export CARGO_TARGET_X86_64_APPLE_DARWIN_POSTGRES_LINKER=cc 26 | fi 27 | 28 | else 29 | echo unsupported macos build platform: $(uname -m) 30 | exit 1 31 | fi 32 | 33 | else 34 | if [ `uname -m` == "x86_64" ]; then 35 | if [[ -z "$CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER" ]] && [[ -z "$CARGO_TARGET_AARCH64_POSTGRES_LINUX_GNU_LINKER" ]]; then 36 | export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc 37 | export CARGO_TARGET_AARCH64_POSTGRES_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc 38 | fi 39 | elif [ `uname -m` == "aarch64" ]; then 40 | if [[ -z "$CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER" ]] && [[ -z "$CARGO_TARGET_X86_64_POSTGRES_LINUX_GNU_LINKER" ]]; then 41 | export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-linux-gnu-gcc 42 | export CARGO_TARGET_X86_64_POSTGRES_LINUX_GNU_LINKER=x86_64-linux-gnu-gcc 43 | fi 44 | else 45 | echo unsupported build platform: $(uname -m) 46 | exit 1 47 | fi 48 | fi 49 | 50 | # Make sure the tip of pgrx's develop branch is used, 51 | # until a release that has all the necessary features is cut. 52 | cargo update -p pgrx 53 | cargo fetch 54 | if [ "$CI" != true ]; then 55 | # Attempt to get pgrx version from cargo tree. Looking for a pattern such as "pgrx v0.10.0" 56 | PGRX_VERSION=$(cargo tree --depth 1 --package plrust | grep -E "\s*pgrx\s+v[0-9]+\.[0-9]+\.[0-9]+" | head -n 1 | cut -f2- -dv) 57 | 58 | if [ -z "$PGRX_VERSION" ]; then 59 | echo "Could not determine pgrx version from 'cargo tree'!" 60 | exit 1 61 | else 62 | echo "Installing cargo-pgrx version $PGRX_VERSION" 63 | cargo install cargo-pgrx \ 64 | --version "$PGRX_VERSION" \ 65 | --locked # use the Cargo.lock in the pgrx repo 66 | fi 67 | fi 68 | 69 | # Don't need to run cargo pgrx init: user might already have set it up, 70 | # and doing so risks clobbering their configuration. 71 | # If they get an error, it's fairly self-explanatory. 72 | 73 | ( 74 | if cd postgrestd; then 75 | git pull 76 | git submodule update --init --recursive 77 | else 78 | git clone https://github.com/tcdi/postgrestd.git --branch "rust-1.72.0" --recurse-submodules 79 | cd ./postgrestd 80 | fi 81 | rm -f rust-toolchain.toml 82 | STD_TARGETS="$STD_TARGETS" ./run clean 83 | STD_TARGETS="$STD_TARGETS" ./run install 84 | ) 85 | -------------------------------------------------------------------------------- /plrust/build.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Portions Copyright 2020-2021 ZomboDB, LLC. 3 | Portions Copyright 2021-2023 Technology Concepts & Design, Inc. 4 | 5 | All rights reserved. 6 | 7 | Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. 8 | */ 9 | use std::error::Error; 10 | use std::process::Command; 11 | 12 | fn main() -> Result<(), Box> { 13 | println!( 14 | "cargo:rustc-env=PLRUST_TRUSTED_PGRX_VERSION={}", 15 | find_trusted_pgrx_current_version()? 16 | ); 17 | 18 | // if the pgrx override definition changes we want a recompile 19 | println!("cargo:rerun-if-env-changed=PLRUST_TRUSTED_PGRX_OVERRIDE"); 20 | Ok(()) 21 | } 22 | 23 | fn find_trusted_pgrx_current_version() -> Result> { 24 | let output = Command::new("cargo") 25 | .arg("tree") 26 | .arg("-p") 27 | .arg("plrust-trusted-pgrx") 28 | .arg("--depth") 29 | .arg("0") 30 | .output()?; 31 | 32 | // looking for some output similar to: 33 | // 34 | // plrust-trusted-pgrx v1.0.0 (/home/zombodb/_work/plrust/plrust-trusted-pgrx) 35 | // 36 | // and we want the "v1.0.0" part 37 | let stdout = String::from_utf8_lossy(&output.stdout); 38 | let parts = stdout.split_whitespace().collect::>(); 39 | let version = parts 40 | .get(1) 41 | .ok_or_else(|| "unexpected `cargo tree` output")?; 42 | let version = &version[1..]; // strip off the leading 'v' 43 | Ok(version.to_string()) 44 | } 45 | -------------------------------------------------------------------------------- /plrust/plrust.control: -------------------------------------------------------------------------------- 1 | comment = 'plrust: A Trusted Rust procedural language for PostgreSQL' 2 | default_version = '1.1' 3 | module_pathname = '$libdir/plrust' 4 | relocatable = false 5 | superuser = false 6 | schema = plrust 7 | -------------------------------------------------------------------------------- /plrust/src/error.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021-2023 Technology Concepts & Design, Inc. 3 | 4 | All rights reserved. 5 | 6 | Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. 7 | */ 8 | 9 | use crate::target::CompilationTarget; 10 | use crate::user_crate::lint::LintSet; 11 | 12 | #[derive(thiserror::Error, Debug)] 13 | pub(crate) enum PlRustError { 14 | #[error("Failed pg_sys::CheckFunctionValidatorAccess")] 15 | CheckFunctionValidatorAccess, 16 | #[error("pgrx::pg_sys::FunctionCallInfo was Null")] 17 | NullFunctionCallInfo, 18 | #[error("pgrx::pg_sys::FmgrInfo was Null")] 19 | NullFmgrInfo, 20 | #[error("libloading error: {0}")] 21 | LibLoading(#[from] libloading::Error), 22 | #[error("`cargo build` failed")] 23 | CargoBuildFail, 24 | #[error("Generating `Cargo.toml`")] 25 | GeneratingCargoToml, 26 | #[error("Function `{0}` does not exist")] 27 | NoSuchFunction(pgrx::pg_sys::Oid), 28 | #[error("Oid `{0}` was not mappable to a Rust type")] 29 | NoOidToRustMapping(pgrx::pg_sys::Oid), 30 | #[error("Generated Rust type (`{1}`) for `{0}` was unparsable: {2}")] 31 | ParsingRustMapping(pgrx::pg_sys::Oid, String, syn::Error), 32 | #[error("Parsing `[code]` block: {0}")] 33 | ParsingCodeBlock(syn::Error), 34 | #[error("Parsing error at span `{:?}`", .0.span())] 35 | Parse(#[from] syn::Error), 36 | #[error("Function was not compiled for this host (`{0}`)")] 37 | FunctionNotCompiledForTarget(CompilationTarget), 38 | #[error("Function not compiled with required lints: {0}")] 39 | MissingLints(LintSet), 40 | } 41 | -------------------------------------------------------------------------------- /plrust/src/logging.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021-2023 Technology Concepts & Design, Inc. 3 | 4 | All rights reserved. 5 | 6 | Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. 7 | */ 8 | 9 | use std::io::Write; 10 | 11 | pub(crate) struct PgrxGuestWriter; 12 | 13 | impl Write for PgrxGuestWriter { 14 | fn write(&mut self, data: &[u8]) -> std::result::Result { 15 | let content = std::str::from_utf8(data).expect("Could not interpret stdout as UTF-8"); 16 | let prefixed = if IS_STDERR { 17 | String::from("stderr: ") + content 18 | } else { 19 | String::from("stdout: ") + content 20 | }; 21 | pgrx::log!("{}", &prefixed); 22 | Ok(data.len()) 23 | } 24 | fn flush(&mut self) -> std::result::Result<(), std::io::Error> { 25 | Ok(()) 26 | } 27 | } 28 | 29 | pub(crate) struct PgrxLogWriter; 30 | 31 | impl Write for PgrxLogWriter { 32 | fn write(&mut self, data: &[u8]) -> std::result::Result { 33 | let content = std::str::from_utf8(data).expect("Could not interpret stdout as UTF-8"); 34 | let content = if TRIM { content.trim_start() } else { content }; 35 | pgrx::log!("{}", content); 36 | Ok(data.len()) 37 | } 38 | fn flush(&mut self) -> std::result::Result<(), std::io::Error> { 39 | Ok(()) 40 | } 41 | } 42 | 43 | pub(crate) struct PgrxNoticeWriter; 44 | 45 | impl Write for PgrxNoticeWriter { 46 | fn write(&mut self, data: &[u8]) -> std::result::Result { 47 | let content = std::str::from_utf8(data).expect("Could not interpret stdout as UTF-8"); 48 | let content = if TRIM { content.trim_start() } else { content }; 49 | pgrx::notice!("{}", content); 50 | Ok(data.len()) 51 | } 52 | fn flush(&mut self) -> std::result::Result<(), std::io::Error> { 53 | Ok(()) 54 | } 55 | } 56 | 57 | pub(crate) struct PgrxWarningWriter; 58 | 59 | impl Write for PgrxWarningWriter { 60 | fn write(&mut self, data: &[u8]) -> std::result::Result { 61 | let content = std::str::from_utf8(data).expect("Could not interpret stdout as UTF-8"); 62 | let content = if TRIM { content.trim_start() } else { content }; 63 | pgrx::warning!("{}", content); 64 | Ok(data.len()) 65 | } 66 | fn flush(&mut self) -> std::result::Result<(), std::io::Error> { 67 | Ok(()) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /plrust/src/user_crate/capabilities.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::collections::BTreeSet; 3 | 4 | /// The capabilities that influence how PL/Rust generates wrapper code for a user function 5 | // NB: Make sure to add new ones down below to [`FunctionCapabilitySet::default()`] 6 | #[derive(Debug, Clone, Serialize, Deserialize, Ord, PartialOrd, PartialEq, Eq, Hash)] 7 | pub(crate) enum FunctionCapability { 8 | /// Indicates that `pgrx::Array<'a, T>` should be used instead of `Vec` for mapping 9 | /// arguments of SQL type `ARRAY[]::T[]` 10 | ZeroCopyArrays, 11 | } 12 | 13 | /// A set of [`FunctionCapability`] which is stored as metadata in the system catalogs 14 | #[derive(Debug, Clone, Serialize, Deserialize, Ord, PartialOrd, PartialEq, Eq, Hash)] 15 | pub(crate) struct FunctionCapabilitySet(BTreeSet); 16 | 17 | impl Default for FunctionCapabilitySet { 18 | /// Creates a default [`FunctionCapabilitySet`] which contains every [`FunctionCapability`] 19 | /// PL/Rust supports. 20 | #[inline] 21 | fn default() -> Self { 22 | let mut caps = BTreeSet::default(); 23 | caps.insert(FunctionCapability::ZeroCopyArrays); 24 | Self(caps) 25 | } 26 | } 27 | 28 | impl FunctionCapabilitySet { 29 | /// Create a [`FunctionCapabilitySet`] that contains nothing. This is a convenience method 30 | /// for backwards compatibility with PL/Rust v1.0.0 which did not have capabilities 31 | #[inline] 32 | pub(crate) fn empty() -> Self { 33 | Self(Default::default()) 34 | } 35 | 36 | /// Does the set contain the [`FunctionCapability::ZeroCopyArrays]` capability? 37 | #[inline] 38 | pub fn has_zero_copy_arrays(&self) -> bool { 39 | self.0.contains(&FunctionCapability::ZeroCopyArrays) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /plrust/src/user_crate/lint.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Borrow; 2 | use std::collections::BTreeSet; 3 | use std::fmt::{Display, Formatter}; 4 | use std::ops::{Deref, DerefMut}; 5 | 6 | use proc_macro2::{Ident, Span, TokenStream}; 7 | use quote::{ToTokens, TokenStreamExt}; 8 | use serde::{Deserialize, Serialize}; 9 | 10 | use crate::gucs::{PLRUST_COMPILE_LINTS, PLRUST_REQUIRED_LINTS}; 11 | 12 | #[derive(Debug, Clone, Serialize, Deserialize, Ord, PartialOrd, PartialEq, Eq, Hash)] 13 | pub(crate) struct Lint(String); 14 | 15 | #[derive(Debug, Clone, Serialize, Deserialize, Ord, PartialOrd, PartialEq, Eq, Hash)] 16 | pub(crate) struct LintSet(BTreeSet); 17 | 18 | impl Deref for LintSet { 19 | type Target = BTreeSet; 20 | 21 | fn deref(&self) -> &Self::Target { 22 | &self.0 23 | } 24 | } 25 | 26 | impl DerefMut for LintSet { 27 | fn deref_mut(&mut self) -> &mut Self::Target { 28 | &mut self.0 29 | } 30 | } 31 | 32 | impl Display for LintSet { 33 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 34 | write!(f, "{}", self.iter().collect::>().join(", ")) 35 | } 36 | } 37 | 38 | impl ToTokens for LintSet { 39 | fn to_tokens(&self, tokens: &mut TokenStream) { 40 | for lint in self.iter() { 41 | lint.to_tokens(tokens); 42 | } 43 | } 44 | } 45 | 46 | impl FromIterator for LintSet { 47 | fn from_iter>(iter: T) -> Self { 48 | Self(iter.into_iter().collect()) 49 | } 50 | } 51 | 52 | impl<'a> FromIterator<&'a Lint> for Vec { 53 | fn from_iter>(iter: T) -> Self { 54 | iter.into_iter().map(|l| l.to_string()).collect() 55 | } 56 | } 57 | 58 | impl Display for Lint { 59 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 60 | write!(f, "{}", self.0) 61 | } 62 | } 63 | 64 | impl Deref for Lint { 65 | type Target = str; 66 | 67 | fn deref(&self) -> &Self::Target { 68 | &self.0 69 | } 70 | } 71 | 72 | impl Borrow for Lint { 73 | fn borrow(&self) -> &str { 74 | &self.0 75 | } 76 | } 77 | 78 | impl From<&str> for Lint { 79 | fn from(value: &str) -> Self { 80 | Lint(value.to_string()) 81 | } 82 | } 83 | 84 | impl ToTokens for Lint { 85 | fn to_tokens(&self, tokens: &mut TokenStream) { 86 | let lint = Ident::new(&self.0, Span::call_site()); 87 | tokens.append_all(quote::quote!(#![forbid(#lint)])) 88 | } 89 | } 90 | 91 | /// Use the set of lints configured via the `plrust.compile_lints` GUC 92 | pub(crate) fn compile_lints() -> LintSet { 93 | PLRUST_COMPILE_LINTS 94 | .get() 95 | .unwrap_or_default() 96 | .to_str() 97 | .expect("plrust.compile_lints is not valid UTF8") 98 | .split(',') 99 | .filter(|x| !x.is_empty()) 100 | .map(|s| s.trim().into()) 101 | .collect() 102 | } 103 | 104 | /// Enumerates the set of lints that are required to have been applied to a plrust function during 105 | /// compilation. These should be squared against the metadata we have for each function before 106 | /// they're dlopen()'d 107 | pub(crate) fn required_lints() -> LintSet { 108 | let filter_map = |s: &str| { 109 | let trim = s.trim(); 110 | if !trim.is_empty() { 111 | Some(trim.into()) 112 | } else { 113 | None 114 | } 115 | }; 116 | 117 | // if a `PLRUST_REQUIRED_LINTS` environment variable exists, we always use it, no questions asked 118 | // 119 | // we do this because we want the person with root on the box, if they so desire, to require that 120 | // PL/Rust functions we're going to execute have the properties they require for their environment 121 | let mut forced = std::env::var("PLRUST_REQUIRED_LINTS") 122 | .unwrap_or_default() 123 | .split(',') 124 | .filter_map(filter_map) 125 | .collect::(); 126 | 127 | // maybe it's an unfounded belief, but perhaps the person with root is different than the person 128 | // that can edit postgresql.conf. So we union what might be in our environment with with 129 | // whatever might be configured in postgresql.conf 130 | let mut configured = PLRUST_REQUIRED_LINTS 131 | .get() 132 | .unwrap_or_default() 133 | .to_str() 134 | .expect("plrust.required_lints is not valid UTF8") 135 | .split(',') 136 | .filter_map(filter_map) 137 | .collect::(); 138 | configured.append(&mut forced); 139 | configured 140 | } 141 | -------------------------------------------------------------------------------- /plrust/src/user_crate/loading.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021-2023 Technology Concepts & Design, Inc. 3 | 4 | All rights reserved. 5 | 6 | Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. 7 | */ 8 | 9 | use pgrx::pg_sys; 10 | 11 | use crate::target::CompilationTarget; 12 | use crate::user_crate::lint::LintSet; 13 | use crate::user_crate::{CrateState, FnValidate}; 14 | 15 | /// Available and ready-to-load PL/Rust function 16 | /// 17 | /// - Requires: a dlopenable artifact 18 | /// - Produces: a dlopened artifact 19 | #[must_use] 20 | pub(crate) struct FnLoad { 21 | generation_number: u64, 22 | db_oid: pg_sys::Oid, 23 | fn_oid: pg_sys::Oid, 24 | target: CompilationTarget, 25 | symbol: Option, 26 | shared_object: Vec, 27 | lints: LintSet, 28 | } 29 | 30 | impl CrateState for FnLoad {} 31 | 32 | impl FnLoad { 33 | #[tracing::instrument(level = "debug", skip_all)] 34 | pub(crate) fn new( 35 | generation_number: u64, 36 | db_oid: pg_sys::Oid, 37 | fn_oid: pg_sys::Oid, 38 | target: CompilationTarget, 39 | symbol: Option, 40 | shared_object: Vec, 41 | lints: LintSet, 42 | ) -> Self { 43 | Self { 44 | generation_number, 45 | db_oid, 46 | fn_oid, 47 | target, 48 | symbol, 49 | shared_object, 50 | lints, 51 | } 52 | } 53 | 54 | pub(crate) fn into_inner(self) -> (CompilationTarget, Vec, LintSet) { 55 | (self.target, self.shared_object, self.lints) 56 | } 57 | 58 | #[tracing::instrument(level = "debug", skip_all, fields(db_oid = % self.db_oid, fn_oid = % self.fn_oid))] 59 | pub(crate) unsafe fn validate(self) -> eyre::Result { 60 | FnValidate::new( 61 | self.generation_number, 62 | self.db_oid, 63 | self.fn_oid, 64 | self.symbol, 65 | self.shared_object, 66 | self.lints, 67 | ) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /plrust/src/user_crate/validate.rs: -------------------------------------------------------------------------------- 1 | use pgrx::pg_sys; 2 | 3 | use crate::error::PlRustError; 4 | use crate::user_crate::lint::{required_lints, LintSet}; 5 | use crate::user_crate::{CrateState, FnReady}; 6 | 7 | pub(crate) struct FnValidate { 8 | generation_number: u64, 9 | db_oid: pg_sys::Oid, 10 | fn_oid: pg_sys::Oid, 11 | symbol: Option, 12 | shared_object: Vec, 13 | } 14 | 15 | impl CrateState for FnValidate {} 16 | 17 | impl FnValidate { 18 | #[tracing::instrument(level = "debug", skip_all)] 19 | pub(crate) fn new( 20 | generation_number: u64, 21 | db_oid: pg_sys::Oid, 22 | fn_oid: pg_sys::Oid, 23 | symbol: Option, 24 | shared_object: Vec, 25 | lints: LintSet, 26 | ) -> eyre::Result { 27 | // if the set of lints we're validating don't include every required lint, we raise an error 28 | // with the missing lints 29 | let missing_lints = required_lints() 30 | .difference(&lints) 31 | .cloned() 32 | .collect::(); 33 | if missing_lints.len() > 0 { 34 | return Err(eyre::eyre!(PlRustError::MissingLints(missing_lints))); 35 | } 36 | 37 | Ok(Self { 38 | generation_number, 39 | db_oid, 40 | fn_oid, 41 | symbol, 42 | shared_object, 43 | }) 44 | } 45 | 46 | pub(crate) unsafe fn load(self) -> eyre::Result { 47 | unsafe { 48 | // SAFETY: Caller is responsible for ensuring self.shared_object points to the proper 49 | // shared library to be loaded 50 | FnReady::load( 51 | self.generation_number, 52 | self.db_oid, 53 | self.fn_oid, 54 | self.symbol, 55 | self.shared_object, 56 | ) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /plrust/src/user_crate/verify.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021-2023 Technology Concepts & Design, Inc. 3 | 4 | All rights reserved. 5 | 6 | Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. 7 | */ 8 | 9 | /*! 10 | Provisioned and ready for validation steps 11 | 12 | To detect unsafe code in PL/Rust while still using PGRX requires some circumlocution. 13 | PGRX creates `#[no_mangle] unsafe extern "C" fn` wrappers that allow Postgres to call Rust, 14 | as PostgreSQL will dynamically load what it thinks is a C library and call C ABI wrapper fn 15 | that themselves handle the Postgres fn call ABI for the programmer and then, finally, 16 | call into the programmer's Rust ABI fn! 17 | This blocks simply using rustc's `unsafe` detection as pgrx-macros generated code is unsafe. 18 | 19 | The circumlocution is brutal, simple, and effective: 20 | pgrx-macros wraps actual Rust which can be safe if it contains no unsafe code! 21 | Such code is powerless (it really, truly, will not run, and may not even build) 22 | but it should still typecheck. Writing an empty shell function first 23 | allows using the linting power of rustc on it as a validation step. 24 | Then the function can be rewritten with annotations from pgrx-macros injected. 25 | */ 26 | 27 | use std::{ 28 | path::{Path, PathBuf}, 29 | process::Output, 30 | }; 31 | 32 | use eyre::{eyre, WrapErr}; 33 | use pgrx::pg_sys; 34 | 35 | use crate::user_crate::cargo::cargo; 36 | use crate::user_crate::lint::LintSet; 37 | use crate::user_crate::{CrateState, FnBuild, PlRustError}; 38 | 39 | /// Available and ready-to-validate PL/Rust crate 40 | /// 41 | /// - Requires: a provisioned Cargo crate directory 42 | /// - Produces: verified Rust source code 43 | #[must_use] 44 | pub(crate) struct FnVerify { 45 | generation_number: u64, 46 | db_oid: pg_sys::Oid, 47 | fn_oid: pg_sys::Oid, 48 | crate_name: String, 49 | crate_dir: PathBuf, 50 | lints: LintSet, 51 | } 52 | 53 | impl CrateState for FnVerify {} 54 | 55 | impl FnVerify { 56 | #[tracing::instrument(level = "debug", skip_all, fields(db_oid = %db_oid, fn_oid = %fn_oid, crate_name = %crate_name, crate_dir = %crate_dir.display()))] 57 | pub(crate) fn new( 58 | generation_number: u64, 59 | db_oid: pg_sys::Oid, 60 | fn_oid: pg_sys::Oid, 61 | crate_name: String, 62 | crate_dir: PathBuf, 63 | lints: LintSet, 64 | ) -> Self { 65 | Self { 66 | generation_number, 67 | db_oid, 68 | fn_oid, 69 | crate_name, 70 | crate_dir, 71 | lints, 72 | } 73 | } 74 | 75 | #[tracing::instrument( 76 | level = "debug", 77 | skip_all, 78 | fields( 79 | db_oid = %self.db_oid, 80 | fn_oid = %self.fn_oid, 81 | crate_dir = %self.crate_dir.display(), 82 | target_dir = tracing::field::display(cargo_target_dir.display()), 83 | ))] 84 | pub(crate) fn validate(self, cargo_target_dir: &Path) -> eyre::Result<(FnBuild, Output)> { 85 | // This is the step which would be used for running validation 86 | // after writing the lib.rs but before actually building it. 87 | // As PL/Rust is not fully configured to run user commands here, 88 | // this version check just smoke-tests the ability to run a command 89 | let mut command = cargo(cargo_target_dir, None)?; 90 | command.arg("--version"); 91 | command.arg("--verbose"); 92 | 93 | let output = command.output().wrap_err("verification failure")?; 94 | 95 | if output.status.success() { 96 | Ok(( 97 | FnBuild::new( 98 | self.generation_number, 99 | self.db_oid, 100 | self.fn_oid, 101 | self.crate_name, 102 | self.crate_dir, 103 | self.lints, 104 | ), 105 | output, 106 | )) 107 | } else { 108 | Err(eyre!(PlRustError::CargoBuildFail)) 109 | } 110 | } 111 | 112 | // for #[tracing] purposes 113 | pub(crate) fn fn_oid(&self) -> pg_sys::Oid { 114 | self.fn_oid 115 | } 116 | 117 | // for #[tracing] purposes 118 | pub(crate) fn db_oid(&self) -> pg_sys::Oid { 119 | self.db_oid 120 | } 121 | 122 | // for #[tracing] purposes 123 | pub(crate) fn crate_dir(&self) -> &Path { 124 | &self.crate_dir 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /plrustc/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [env] 2 | RUSTC_BOOTSTRAP = "1" 3 | -------------------------------------------------------------------------------- /plrustc/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /plrustc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["plrustc"] 3 | resolver = "2" 4 | -------------------------------------------------------------------------------- /plrustc/README.md: -------------------------------------------------------------------------------- 1 | # `plrustc` 2 | 3 | `plrustc` is the Rust compiler driver (e.g. it's a binary that uses `rustc_driver`) used by PL/Rust to implement trusted mode. 4 | 5 | Just to give a brief overview: trusted mode combines various lints with a [custom standard library](https://github.com/tcdi/postgrestd) in an effort to meet the PostgreSQL requirements for a trusted language handler, mainly restricting all access to the underlying operating system. Note that this is only done for the user crate and not dependencies, although dependencies can be disabled in PL/Rust's configuration (and you probably want to do so if you are using "trusted" mode). 6 | 7 | ## Developing 8 | 9 | It's recommended that you use rustup for your toolchain during development. Configure things as specified in the rust-toolchain.toml at the repository root. 10 | 11 | `plrustc` is not part of the overall `plrust` workspace for at least two reasons: 12 | 13 | - `-Clink-args=-Wl,-undefined,dynamic_lookup` causes problems for it 14 | - because it needs `rustc-dev` and `llvm-tools-preview` installed 15 | 16 | ### IDE configuration 17 | 18 | We have a few independent workspaces. My `rust-analyzer` configuration currently looks like this to support them. 19 | 20 | ```json 21 | { 22 | "rust-analyzer.linkedProjects": ["./Cargo.toml", "plrustc/Cargo.toml"], 23 | "rust-analyzer.rustc.source": "discover", 24 | "rust-analyzer.cargo.extraEnv": { "RUSTC_BOOTSTRAP": "1" }, 25 | "rust-analyzer.cargo.buildScripts.enable": true, 26 | "rust-analyzer.procMacro.enable": true, 27 | } 28 | ``` 29 | 30 | The use of `RUSTC_BOOTSTRAP` here is unfortunate, but at the moment things are the way they are. 31 | 32 | ## Usage 33 | 34 | Similar to `rustc`, `plrustc` is usually not invoked directly, but instead through `cargo`. 35 | 36 | -------------------------------------------------------------------------------- /plrustc/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | # set -x 4 | 5 | abs_path() { 6 | local path="$1" 7 | (unset CDPATH && cd "$path" > /dev/null && pwd) 8 | } 9 | 10 | script_root="$(abs_path "$(dirname "$0")")" 11 | repo_root="$(abs_path "$script_root/..")" 12 | 13 | cd "$repo_root" 14 | 15 | if [ -z "$CARGO_TARGET_DIR" ]; then 16 | export CARGO_TARGET_DIR="$repo_root/plrustc/target" 17 | fi 18 | if [ -z "$CARGO" ]; then 19 | CARGO="cargo" 20 | fi 21 | if [ -z "$RUSTC" ]; then 22 | RUSTC="rustc" 23 | fi 24 | 25 | export RUSTC_BOOTSTRAP=1 26 | 27 | version=$($RUSTC --version | cut -d ' ' -f 2) 28 | if [ "$version" != "1.72.0" ]; then 29 | echo "rustc ('$RUSTC') is not version 1.72.0" >&2 30 | exit 1 31 | fi 32 | 33 | host=$($RUSTC --version --verbose | grep "^host:" | cut -d ' ' -f 2) 34 | sysroot=$($RUSTC --print sysroot) 35 | libdir="$sysroot/lib/rustlib/$host/lib" 36 | 37 | if ! [ -d "$libdir" ]; then 38 | echo "No such directory '$libdir'. Make sure you have rustc-dev installed" >&2 39 | exit 2 40 | fi 41 | 42 | cd "$repo_root/plrustc/plrustc" 43 | 44 | # if [ -z "$RUSTFLAGS" ]; then 45 | RUSTFLAGS="-Zunstable-options -Zbinary-dep-depinfo" 46 | # fi 47 | 48 | 49 | if [ "$NO_RPATH" != "1" ]; then 50 | # This produces a portable binary assuming that the rust toolchain location 51 | # will not not moved, which is reasonable when using rustup, during 52 | # development, and probably in some production usage. For final install, 53 | # we'll want to have an option to do the fully correct rpath dance, with 54 | # `$ORIGIN` and `-z,origin` and similar shenanigans (with completely 55 | # different names) on macOS. In such a case, we should consider installing 56 | # to the same directory as e.g. rustc and/or cargo-clippy 57 | RUSTFLAGS="-Clink-args=-Wl,-rpath,$libdir $RUSTFLAGS" 58 | fi 59 | 60 | if [ "$SET_SYSROOT" = "1" ]; then 61 | # Set fallback sysroot if requested. 62 | export PLRUSTC_SYSROOT="$sysroot" 63 | fi 64 | 65 | echo "plrustc build starting..." >&2 66 | 67 | 68 | if [[ "$1" = "install" ]]; then 69 | env RUSTFLAGS="$RUSTFLAGS" \ 70 | "$CARGO" install \ 71 | --path . \ 72 | --target "$host" 73 | 74 | echo "plrustc installation (with 'cargo install') completed" >&2 75 | else 76 | env RUSTFLAGS="$RUSTFLAGS" \ 77 | "$CARGO" build --release \ 78 | -p plrustc --bin plrustc \ 79 | --target "$host" 80 | 81 | cd "$repo_root" 82 | 83 | mkdir -p "$repo_root/build/bin" 84 | cp "$CARGO_TARGET_DIR/$host/release/plrustc" "$repo_root/build/bin/plrustc" 85 | 86 | echo "plrustc build completed" >&2 87 | echo " result binary is located at '$repo_root/build/bin/plrustc'" >&2 88 | fi 89 | -------------------------------------------------------------------------------- /plrustc/plrustc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "plrustc" 3 | version = "1.2.8" 4 | edition = "2021" 5 | description = "`rustc_driver` wrapper for plrust" 6 | license = "PostgreSQL" 7 | authors = ["TCDI "] 8 | homepage = "https://github.com/tcdi/plrust/" 9 | repository = "https://github.com/tcdi/plrust/" 10 | 11 | [dependencies] 12 | libc = "0.2.150" 13 | once_cell = "1.18.0" 14 | 15 | [dev-dependencies] 16 | compiletest_rs = "0.10.2" 17 | 18 | [package.metadata.rust-analyzer] 19 | rustc_private = true 20 | 21 | -------------------------------------------------------------------------------- /plrustc/plrustc/src/lints/async_await.rs: -------------------------------------------------------------------------------- 1 | use rustc_ast as ast; 2 | use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; 3 | use rustc_span::Span; 4 | 5 | declare_plrust_lint!( 6 | pub(crate) PLRUST_ASYNC, 7 | "Disallow use of async and await", 8 | ); 9 | 10 | rustc_lint_defs::declare_lint_pass!(PlrustAsync => [PLRUST_ASYNC]); 11 | 12 | impl EarlyLintPass for PlrustAsync { 13 | fn check_expr(&mut self, cx: &EarlyContext, expr: &ast::Expr) { 14 | if let ast::ExprKind::Async(..) | ast::ExprKind::Await(..) = &expr.kind { 15 | cx.lint( 16 | PLRUST_ASYNC, 17 | "Use of async/await is forbidden in PL/Rust", 18 | |b| b.set_span(expr.span), 19 | ); 20 | } 21 | } 22 | fn check_fn( 23 | &mut self, 24 | cx: &EarlyContext, 25 | kind: ast::visit::FnKind<'_>, 26 | span: Span, 27 | _: ast::NodeId, 28 | ) { 29 | if let Some(h) = kind.header() { 30 | if h.asyncness.is_async() { 31 | cx.lint( 32 | PLRUST_ASYNC, 33 | "Use of async/await is forbidden in PL/Rust", 34 | |b| b.set_span(span), 35 | ); 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /plrustc/plrustc/src/lints/autotrait_impls.rs: -------------------------------------------------------------------------------- 1 | use rustc_hir as hir; 2 | use rustc_lint::{LateContext, LateLintPass, LintContext}; 3 | use rustc_span::Span; 4 | 5 | declare_plrust_lint!( 6 | pub(crate) PLRUST_AUTOTRAIT_IMPLS, 7 | "Disallow impls of auto traits", 8 | ); 9 | 10 | rustc_lint_defs::declare_lint_pass!(PlrustAutoTraitImpls => [PLRUST_AUTOTRAIT_IMPLS]); 11 | 12 | impl<'tcx> LateLintPass<'tcx> for PlrustAutoTraitImpls { 13 | fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { 14 | fn trigger(cx: &LateContext<'_>, span: Span) { 15 | cx.lint( 16 | PLRUST_AUTOTRAIT_IMPLS, 17 | "explicit implementations of auto traits are forbidden in PL/Rust", 18 | |b| b.set_span(span), 19 | ); 20 | } 21 | let hir::ItemKind::Impl(imp) = &item.kind else { 22 | return; 23 | }; 24 | let Some(trait_) = &imp.of_trait else { 25 | return; 26 | }; 27 | let Some(did) = trait_.trait_def_id() else { 28 | return; 29 | }; 30 | // I think ideally we'd resolve `did` into the actual trait type and 31 | // check if it's audo, but I don't know how to do that here, so I'll 32 | // just check for UnwindSafe, RefUnwindSafe, and Unpin explicitly (these 33 | // are currently the only safe stable auto traits, I believe). 34 | // 35 | // The former two have diagnostic items, the latter doesn't but is a 36 | // lang item. 37 | if matches!(cx.tcx.lang_items().unpin_trait(), Some(unpin_did) if unpin_did == did) { 38 | trigger(cx, item.span); 39 | } 40 | 41 | if let Some(thing) = cx.tcx.get_diagnostic_name(did) { 42 | let name = thing.as_str(); 43 | if name == "unwind_safe_trait" || name == "ref_unwind_safe_trait" { 44 | trigger(cx, item.span); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /plrustc/plrustc/src/lints/builtin_macros.rs: -------------------------------------------------------------------------------- 1 | use super::utils; 2 | use rustc_hir as hir; 3 | use rustc_lint::{LateContext, LateLintPass, LintContext}; 4 | use rustc_span::{Span, Symbol}; 5 | 6 | declare_plrust_lint!( 7 | pub(crate) PLRUST_FILESYSTEM_MACROS, 8 | "Disallow `include_str!`, and `include_bytes!`", 9 | ); 10 | 11 | declare_plrust_lint!( 12 | pub(crate) PLRUST_ENV_MACROS, 13 | "Disallow `env!`, and `option_env!`", 14 | ); 15 | 16 | rustc_lint_defs::declare_lint_pass!(PlrustBuiltinMacros => [PLRUST_FILESYSTEM_MACROS]); 17 | 18 | impl PlrustBuiltinMacros { 19 | fn lint_fs(&self, cx: &LateContext<'_>, sp: Span) { 20 | cx.lint( 21 | PLRUST_FILESYSTEM_MACROS, 22 | "the `include_str`, `include_bytes`, and `include` macros are forbidden in PL/Rust", 23 | |b| b.set_span(sp), 24 | ); 25 | } 26 | fn lint_env(&self, cx: &LateContext<'_>, sp: Span) { 27 | cx.lint( 28 | PLRUST_ENV_MACROS, 29 | "the `env` and `option_env` macros are forbidden", 30 | |b| b.set_span(sp), 31 | ); 32 | } 33 | fn check_span(&mut self, cx: &LateContext<'_>, span: Span) { 34 | let fs_diagnostic_items = [ 35 | sym!(include_str_macro), 36 | sym!(include_bytes_macro), 37 | sym!(include_macro), 38 | ]; 39 | if let Some((s, ..)) = utils::check_span_against_macro_diags(cx, span, &fs_diagnostic_items) 40 | { 41 | self.lint_fs(cx, s); 42 | return; 43 | } 44 | let fs_def_paths: &[&[Symbol]] = &[ 45 | &[sym!(core), sym!(macros), sym!(builtin), sym!(include)], 46 | &[sym!(core), sym!(macros), sym!(builtin), sym!(include_bytes)], 47 | &[sym!(core), sym!(macros), sym!(builtin), sym!(include_str)], 48 | ]; 49 | if let Some((s, ..)) = utils::check_span_against_macro_def_paths(cx, span, &fs_def_paths) { 50 | self.lint_fs(cx, s); 51 | return; 52 | } 53 | 54 | let env_diagnostic_items = [sym!(env_macro), sym!(option_env_macro)]; 55 | if let Some((s, ..)) = 56 | utils::check_span_against_macro_diags(cx, span, &env_diagnostic_items) 57 | { 58 | self.lint_env(cx, s); 59 | return; 60 | } 61 | let env_def_paths: &[&[Symbol]] = &[ 62 | &[sym!(core), sym!(macros), sym!(builtin), sym!(env)], 63 | &[sym!(core), sym!(macros), sym!(builtin), sym!(option_env)], 64 | ]; 65 | if let Some((s, ..)) = utils::check_span_against_macro_def_paths(cx, span, &env_def_paths) { 66 | self.lint_env(cx, s); 67 | return; 68 | } 69 | } 70 | } 71 | 72 | impl<'tcx> LateLintPass<'tcx> for PlrustBuiltinMacros { 73 | fn check_item(&mut self, cx: &LateContext<'tcx>, item: &hir::Item) { 74 | self.check_span(cx, item.span) 75 | } 76 | fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &hir::Stmt) { 77 | self.check_span(cx, stmt.span) 78 | } 79 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr) { 80 | self.check_span(cx, expr.span) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /plrustc/plrustc/src/lints/closure_trait_impl.rs: -------------------------------------------------------------------------------- 1 | //! Trait objects are [often inferred as 'static][trait-obj-default], 2 | //! which can result, when combined with things like boxed closures, 3 | //! in an incorrect lifetime also being inferred for the return type. 4 | //! Allowing such a lifetime to become visible in Rust allows treating 5 | //! `&'a mut str` as `&'static mut str`, with the "fun" that implies. 6 | //! 7 | //! Ideally we'd do this in such a way that it only forbids something that 8 | //! allows projection through to the return type, for example 9 | //! ```ignore (exposition-only) 10 | //! trait Trait { 11 | //! type Assoc; 12 | //! } 13 | //! impl R> Trait for F { 14 | //! type Assoc = R; 15 | //! } 16 | //! ``` 17 | //! allows `<_ as Trait>::Assoc` to get a functions return type. That said, 18 | //! actually writing this lint has totally defeated me at the moment, so this is 19 | //! good enough for now. 20 | //! 21 | //! For some intuition as to why this is tricky, consider cases like 22 | //! ```ignore (exposition-only) 23 | //! trait GivesAssoc { type Assoc; } 24 | //! impl GivesAssoc for A { type Assoc = A; } 25 | //! 26 | //! trait Child where Self: GivesAssoc {} 27 | //! impl R> Child for F {} 28 | //! ``` 29 | //! and similarly complicated variants. To figure this out you need to examine 30 | //! not just the directly implemented trait, but also all traits that are 31 | //! indirectly implemented via bounds as a result of the impl. This is possible, 32 | //! but... difficult. 33 | //! 34 | //! [trait-obj-default]: https://doc.rust-lang.org/reference/lifetime-elision.html#default-trait-object-lifetimes 35 | //! 36 | use rustc_hir as hir; 37 | use rustc_lint::{LateContext, LateLintPass, LintContext}; 38 | 39 | declare_plrust_lint!( 40 | pub(crate) PLRUST_CLOSURE_TRAIT_IMPL, 41 | "Disallow trait impls which are generic over closure type", 42 | ); 43 | 44 | rustc_lint_defs::declare_lint_pass!(PlrustClosureTraitImpl => [PLRUST_CLOSURE_TRAIT_IMPL]); 45 | 46 | impl<'tcx> LateLintPass<'tcx> for PlrustClosureTraitImpl { 47 | fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { 48 | let hir::ItemKind::Impl(impl_item) = item.kind else { 49 | return 50 | }; 51 | for pred in impl_item.generics.predicates { 52 | let hir::WherePredicate::BoundPredicate(bound_pred) = pred else { 53 | continue 54 | }; 55 | // TODO: should we ignore cases where `bound_pred.bounded_ty` isn't 56 | // from one of `item.generics.params`? 57 | for bound in bound_pred.bounds { 58 | match bound { 59 | hir::GenericBound::LangItemTrait( 60 | hir::LangItem::Fn 61 | | hir::LangItem::FnOnce 62 | | hir::LangItem::FnMut 63 | | hir::LangItem::FnPtrTrait, 64 | .., 65 | ) => { 66 | cx.lint( 67 | PLRUST_CLOSURE_TRAIT_IMPL, 68 | "trait impls bounded on function traits are forbidden in PL/Rust", 69 | |b| b.set_span(bound_pred.span), 70 | ); 71 | } 72 | hir::GenericBound::LangItemTrait(..) => { 73 | // other stable builtin traits aren't useful for projecting a function's return type 74 | } 75 | hir::GenericBound::Trait(poly_trait, ..) => { 76 | if super::utils::has_fn_trait(cx, poly_trait) { 77 | cx.lint( 78 | PLRUST_CLOSURE_TRAIT_IMPL, 79 | "trait impls bounded on function traits are forbidden in PL/Rust", 80 | |b| b.set_span(bound_pred.span), 81 | ); 82 | } 83 | // TODO: if that fails, do we need to 84 | // try_normalize_erasing_regions and retry? 85 | } 86 | hir::GenericBound::Outlives(..) => { 87 | // Lifetime generics are irrelevant for guards against type projection 88 | } 89 | } 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /plrustc/plrustc/src/lints/extern_blocks.rs: -------------------------------------------------------------------------------- 1 | use rustc_hir as hir; 2 | use rustc_lint::{LateContext, LateLintPass, LintContext}; 3 | 4 | declare_plrust_lint!( 5 | pub(crate) PLRUST_EXTERN_BLOCKS, 6 | "Disallow extern blocks" 7 | ); 8 | 9 | rustc_lint_defs::declare_lint_pass!(NoExternBlockPass => [PLRUST_EXTERN_BLOCKS]); 10 | 11 | impl<'tcx> LateLintPass<'tcx> for NoExternBlockPass { 12 | fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { 13 | if let hir::ItemKind::ForeignMod { .. } = &item.kind { 14 | // TODO: Do we need to allow ones from macros from pgrx? 15 | cx.lint( 16 | PLRUST_EXTERN_BLOCKS, 17 | "`extern` blocks are not allowed in PL/Rust", 18 | |b| b.set_span(item.span), 19 | ) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /plrustc/plrustc/src/lints/external_mod.rs: -------------------------------------------------------------------------------- 1 | use rustc_ast as ast; 2 | use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; 3 | 4 | declare_plrust_lint!( 5 | pub(crate) PLRUST_EXTERNAL_MOD, 6 | "Disallow use of `mod blah;`", 7 | ); 8 | 9 | rustc_lint_defs::declare_lint_pass!(PlrustExternalMod => [PLRUST_EXTERNAL_MOD]); 10 | 11 | impl EarlyLintPass for PlrustExternalMod { 12 | fn check_item(&mut self, cx: &EarlyContext, item: &ast::Item) { 13 | match &item.kind { 14 | ast::ItemKind::Mod(_, ast::ModKind::Unloaded) 15 | | ast::ItemKind::Mod(_, ast::ModKind::Loaded(_, ast::Inline::No, _)) => { 16 | cx.lint( 17 | PLRUST_EXTERNAL_MOD, 18 | "Use of external modules is forbidden in PL/Rust", 19 | |b| b.set_span(item.span), 20 | ); 21 | } 22 | _ => {} 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /plrustc/plrustc/src/lints/fn_ptr.rs: -------------------------------------------------------------------------------- 1 | use rustc_hir as hir; 2 | use rustc_lint::{LateContext, LateLintPass, LintContext}; 3 | 4 | declare_plrust_lint!( 5 | pub(crate) PLRUST_FN_POINTERS, 6 | "Disallow use of function pointers", 7 | ); 8 | 9 | rustc_lint_defs::declare_lint_pass!(PlrustFnPointer => [PLRUST_FN_POINTERS]); 10 | 11 | impl<'tcx> LateLintPass<'tcx> for PlrustFnPointer { 12 | fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &hir::Ty) { 13 | match &ty.kind { 14 | hir::TyKind::BareFn { .. } => { 15 | // TODO: ideally this would just be cases where they accept or 16 | // return nested references, however doing so is tricky, as it must 17 | // pierce through `&'a SomeStruct(&'b InternalRef)`. 18 | cx.lint( 19 | PLRUST_FN_POINTERS, 20 | "Use of function pointers is forbidden in PL/Rust", 21 | |b| b.set_span(ty.span), 22 | ); 23 | } 24 | hir::TyKind::TraitObject(traits, ..) => { 25 | for poly_trait in *traits { 26 | if super::utils::has_fn_trait(cx, poly_trait) { 27 | cx.lint( 28 | PLRUST_FN_POINTERS, 29 | "Use of function trait objects is forbidden in PL/Rust", 30 | |b| b.set_span(ty.span), 31 | ); 32 | } 33 | } 34 | } 35 | _ => {} 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /plrustc/plrustc/src/lints/force_ice.rs: -------------------------------------------------------------------------------- 1 | use rustc_ast as ast; 2 | use rustc_lint::{EarlyContext, EarlyLintPass}; 3 | use rustc_span::Span; 4 | 5 | // Used to force an ICE in our uitests. Only enabled if 6 | // `PLRUSTC_INCLUDE_TEST_ONLY_LINTS` is enabled in the environment, which we do 7 | // explicitly in the tests that need it. 8 | declare_plrust_lint! { 9 | pub(crate) PLRUST_TEST_ONLY_FORCE_ICE, 10 | "This message should not appear in the output" 11 | } 12 | 13 | rustc_lint_defs::declare_lint_pass!(PlrustcForceIce => [PLRUST_TEST_ONLY_FORCE_ICE]); 14 | 15 | impl EarlyLintPass for PlrustcForceIce { 16 | fn check_fn( 17 | &mut self, 18 | _: &EarlyContext<'_>, 19 | fn_kind: ast::visit::FnKind<'_>, 20 | _: Span, 21 | _: ast::NodeId, 22 | ) { 23 | use ast::visit::FnKind; 24 | const GIMME_ICE: &str = "plrustc_would_like_some_ice"; 25 | if matches!(&fn_kind, FnKind::Fn(_, id, ..) if id.name.as_str() == GIMME_ICE) { 26 | panic!("Here is your ICE"); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /plrustc/plrustc/src/lints/leaky.rs: -------------------------------------------------------------------------------- 1 | use rustc_hir as hir; 2 | use rustc_lint::{LateContext, LateLintPass, LintContext}; 3 | 4 | declare_plrust_lint!( 5 | pub(crate) PLRUST_LEAKY, 6 | "Disallow use of `{Box,Vec,String}::leak`, `mem::forget`, and similar functions", 7 | ); 8 | 9 | rustc_lint_defs::declare_lint_pass!(PlrustLeaky => [PLRUST_LEAKY]); 10 | 11 | impl<'tcx> LateLintPass<'tcx> for PlrustLeaky { 12 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr) { 13 | let paths: &[&[&str]] = &[ 14 | &["alloc", "boxed", "Box", "leak"], 15 | &["alloc", "vec", "Vec", "leak"], 16 | &["alloc", "string", "String", "leak"], 17 | &["core", "mem", "forget"], 18 | ]; 19 | for &path in paths { 20 | if super::utils::does_expr_call_path(cx, expr, path) { 21 | cx.lint( 22 | PLRUST_LEAKY, 23 | "Leaky functions are forbidden in PL/Rust", 24 | |b| b.set_span(expr.span), 25 | ); 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /plrustc/plrustc/src/lints/lifetime_param_trait.rs: -------------------------------------------------------------------------------- 1 | use rustc_hir as hir; 2 | use rustc_lint::{LateContext, LateLintPass, LintContext}; 3 | 4 | declare_plrust_lint!( 5 | pub(crate) PLRUST_LIFETIME_PARAMETERIZED_TRAITS, 6 | "Disallow lifetime parameterized traits" 7 | ); 8 | 9 | rustc_lint_defs::declare_lint_pass!(LifetimeParamTraitPass => [PLRUST_LIFETIME_PARAMETERIZED_TRAITS]); 10 | 11 | impl<'tcx> LateLintPass<'tcx> for LifetimeParamTraitPass { 12 | fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { 13 | if let hir::ItemKind::Trait(_is_auto, _unsafety, generics, ..) = &item.kind { 14 | for param in generics.params { 15 | if let hir::GenericParamKind::Lifetime { .. } = param.kind { 16 | cx.lint( 17 | PLRUST_LIFETIME_PARAMETERIZED_TRAITS, 18 | "PL/Rust forbids declaring traits with generic lifetime parameters", 19 | |b| b.set_span(item.span), 20 | ) 21 | } 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /plrustc/plrustc/src/lints/mod.rs: -------------------------------------------------------------------------------- 1 | use once_cell::sync::Lazy; 2 | use rustc_lint::LintStore; 3 | use rustc_lint_defs::{Lint, LintId}; 4 | use rustc_session::Session; 5 | 6 | #[macro_use] 7 | mod utils; 8 | 9 | mod async_await; 10 | mod autotrait_impls; 11 | mod builtin_macros; 12 | mod closure_trait_impl; 13 | mod extern_blocks; 14 | mod external_mod; 15 | mod fn_ptr; 16 | mod force_ice; 17 | mod leaky; 18 | mod lifetime_param_trait; 19 | mod print_macros; 20 | mod static_impls; 21 | mod stdio; 22 | mod sus_trait_object; 23 | 24 | static INCLUDE_TEST_ONLY_LINTS: Lazy = 25 | Lazy::new(|| std::env::var("PLRUSTC_INCLUDE_TEST_ONLY_LINTS").is_ok()); 26 | 27 | static PLRUST_LINTS: Lazy> = Lazy::new(|| { 28 | let mut v = vec![ 29 | async_await::PLRUST_ASYNC, 30 | autotrait_impls::PLRUST_AUTOTRAIT_IMPLS, 31 | closure_trait_impl::PLRUST_CLOSURE_TRAIT_IMPL, 32 | static_impls::PLRUST_STATIC_IMPLS, 33 | extern_blocks::PLRUST_EXTERN_BLOCKS, 34 | external_mod::PLRUST_EXTERNAL_MOD, 35 | builtin_macros::PLRUST_FILESYSTEM_MACROS, 36 | builtin_macros::PLRUST_ENV_MACROS, 37 | fn_ptr::PLRUST_FN_POINTERS, 38 | leaky::PLRUST_LEAKY, 39 | lifetime_param_trait::PLRUST_LIFETIME_PARAMETERIZED_TRAITS, 40 | print_macros::PLRUST_PRINT_MACROS, 41 | stdio::PLRUST_STDIO, 42 | sus_trait_object::PLRUST_SUSPICIOUS_TRAIT_OBJECT, 43 | ]; 44 | if *INCLUDE_TEST_ONLY_LINTS { 45 | let test_only_lints = [force_ice::PLRUST_TEST_ONLY_FORCE_ICE]; 46 | v.extend(test_only_lints); 47 | } 48 | v 49 | }); 50 | 51 | #[test] 52 | fn check_lints() { 53 | for lint in &**PLRUST_LINTS { 54 | assert!( 55 | lint.name.starts_with("PLRUST_"), 56 | "lint `{}` doesn't follow lint naming convention", 57 | lint.name, 58 | ); 59 | assert!( 60 | lint.report_in_external_macro, 61 | "lint `{}` should report in external macro expansion", 62 | lint.name, 63 | ); 64 | } 65 | } 66 | 67 | pub fn register(store: &mut LintStore, _sess: &Session) { 68 | store.register_lints(&**PLRUST_LINTS); 69 | 70 | store.register_group( 71 | true, 72 | "plrust_lints", 73 | None, 74 | PLRUST_LINTS.iter().map(|&lint| LintId::of(lint)).collect(), 75 | ); 76 | store.register_early_pass(move || Box::new(async_await::PlrustAsync)); 77 | store.register_early_pass(move || Box::new(external_mod::PlrustExternalMod)); 78 | store.register_late_pass(move |_| Box::new(closure_trait_impl::PlrustClosureTraitImpl)); 79 | store.register_late_pass(move |_| Box::new(sus_trait_object::PlrustSuspiciousTraitObject)); 80 | store.register_late_pass(move |_| Box::new(autotrait_impls::PlrustAutoTraitImpls)); 81 | store.register_late_pass(move |_| Box::new(static_impls::PlrustStaticImpls)); 82 | store.register_late_pass(move |_| Box::new(fn_ptr::PlrustFnPointer)); 83 | store.register_late_pass(move |_| Box::new(leaky::PlrustLeaky)); 84 | store.register_late_pass(move |_| Box::new(builtin_macros::PlrustBuiltinMacros)); 85 | store.register_late_pass(move |_| Box::new(print_macros::PlrustPrintMacros)); 86 | store.register_late_pass(move |_| Box::new(stdio::PlrustPrintFunctions)); 87 | store.register_late_pass(move |_| Box::new(extern_blocks::NoExternBlockPass)); 88 | store.register_late_pass(move |_| Box::new(lifetime_param_trait::LifetimeParamTraitPass)); 89 | 90 | if *INCLUDE_TEST_ONLY_LINTS { 91 | store.register_early_pass(move || Box::new(force_ice::PlrustcForceIce)); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /plrustc/plrustc/src/lints/print_macros.rs: -------------------------------------------------------------------------------- 1 | use rustc_hir as hir; 2 | use rustc_lint::{LateContext, LateLintPass, LintContext}; 3 | use rustc_span::Span; 4 | 5 | declare_plrust_lint!( 6 | pub(crate) PLRUST_PRINT_MACROS, 7 | "Disallow `print!`, `println!`, `eprint!` and `eprintln!`", 8 | ); 9 | 10 | rustc_lint_defs::declare_lint_pass!(PlrustPrintMacros => [PLRUST_PRINT_MACROS]); 11 | 12 | impl PlrustPrintMacros { 13 | fn check_span(&self, cx: &LateContext<'_>, srcspan: Span) { 14 | let diagnostic_items = [ 15 | sym!(print_macro), 16 | sym!(eprint_macro), 17 | sym!(println_macro), 18 | sym!(eprintln_macro), 19 | sym!(dbg_macro), 20 | ]; 21 | if let Some((span, _which, _did)) = 22 | super::utils::check_span_against_macro_diags(cx, srcspan, &diagnostic_items) 23 | { 24 | self.fire(cx, span); 25 | }; 26 | } 27 | fn fire(&self, cx: &LateContext<'_>, span: Span) { 28 | cx.lint( 29 | PLRUST_PRINT_MACROS, 30 | "the printing macros are forbidden in PL/Rust, \ 31 | consider using `pgrx::log!()` instead", 32 | |b| b.set_span(span), 33 | ); 34 | } 35 | } 36 | impl<'tcx> LateLintPass<'tcx> for PlrustPrintMacros { 37 | fn check_item(&mut self, cx: &LateContext<'tcx>, h: &hir::Item) { 38 | self.check_span(cx, h.span); 39 | } 40 | fn check_stmt(&mut self, cx: &LateContext<'tcx>, h: &hir::Stmt) { 41 | self.check_span(cx, h.span); 42 | } 43 | fn check_expr(&mut self, cx: &LateContext<'tcx>, h: &hir::Expr) { 44 | self.check_span(cx, h.span); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /plrustc/plrustc/src/lints/static_impls.rs: -------------------------------------------------------------------------------- 1 | use rustc_hir as hir; 2 | use rustc_lint::{LateContext, LateLintPass, LintContext}; 3 | 4 | declare_plrust_lint!( 5 | pub(crate) PLRUST_STATIC_IMPLS, 6 | "Disallow impl blocks for types containing `'static` references" 7 | ); 8 | 9 | rustc_lint_defs::declare_lint_pass!(PlrustStaticImpls => [PLRUST_STATIC_IMPLS]); 10 | 11 | impl<'tcx> LateLintPass<'tcx> for PlrustStaticImpls { 12 | fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { 13 | let hir::ItemKind::Impl(imp) = &item.kind else { 14 | return; 15 | }; 16 | if self.has_static(imp.self_ty) { 17 | cx.lint( 18 | PLRUST_STATIC_IMPLS, 19 | "`impl` blocks for types containing `'static` references \ 20 | are not allowed in PL/Rust", 21 | |b| b.set_span(imp.self_ty.span), 22 | ) 23 | } 24 | } 25 | } 26 | 27 | impl PlrustStaticImpls { 28 | /// This is pretty naïve and designed to match the specific patterns that 29 | /// trip up https://github.com/rust-lang/rust/issues/104005... 30 | /// 31 | /// Also, I feel like I should be able to use `hir::intravisit::walk_ty` 32 | /// here instead, but it doesn't seem to let be know the lifetime of refs, 33 | /// so... not sure... 34 | /// 35 | /// It's a method on `self` mostly to discourage use in other contexts, 36 | /// since it's probably wrong for them. 37 | fn has_static(&self, t: &hir::Ty) -> bool { 38 | use hir::{Lifetime, LifetimeName::Static, MutTy, TyKind}; 39 | match &t.kind { 40 | TyKind::Infer 41 | | TyKind::Err(..) 42 | // Doesn't exist 43 | | TyKind::Typeof(..) 44 | // Not strictly correct but we forbid this elsewhere anyway. 45 | | TyKind::BareFn(..) 46 | // TAIT stuff, still unstable at the moment, seems very hard to 47 | // prevent this for... 48 | | TyKind::OpaqueDef(..) 49 | | TyKind::Never => false, 50 | // Found one! 51 | TyKind::Ref(Lifetime { res: Static, .. }, _) | TyKind::TraitObject(_, Lifetime { res: Static, .. }, _) => true, 52 | // Need to keep looking. 53 | TyKind::Ref(_, MutTy { ty, .. }) 54 | | TyKind::Ptr(MutTy { ty, .. }) 55 | | TyKind::Array(ty, _) 56 | | TyKind::Slice(ty) => self.has_static(*ty), 57 | 58 | TyKind::Tup(types) => types.iter().any(|t| self.has_static(t)), 59 | 60 | TyKind::TraitObject(polytrait, ..) => { 61 | polytrait.iter().any(|poly| { 62 | self.segments_have_static(poly.trait_ref.path.segments) 63 | }) 64 | } 65 | // Something like `Vec` or `Option`. Need to look inside... 66 | TyKind::Path(qpath) => { 67 | let segs = match qpath { 68 | hir::QPath::Resolved(Some(maybe_ty), _) if self.has_static(maybe_ty) => return true, 69 | hir::QPath::TypeRelative(t, _) if self.has_static(*t) => return true, 70 | hir::QPath::LangItem(..) => return false, 71 | hir::QPath::Resolved(_, path) => path.segments, 72 | hir::QPath::TypeRelative(_, seg) => std::slice::from_ref(*seg), 73 | }; 74 | self.segments_have_static(segs) 75 | } 76 | } 77 | } 78 | 79 | fn segments_have_static(&self, segs: &[hir::PathSegment]) -> bool { 80 | segs.iter().any(|seg| { 81 | seg.args().args.iter().any(|arg| match arg { 82 | hir::GenericArg::Lifetime(hir::Lifetime { 83 | res: hir::LifetimeName::Static, 84 | .. 85 | }) => true, 86 | hir::GenericArg::Type(t) => self.has_static(t), 87 | hir::GenericArg::Const(_) | hir::GenericArg::Infer(_) 88 | // Wasn't static 89 | | hir::GenericArg::Lifetime(_) => false, 90 | }) 91 | }) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /plrustc/plrustc/src/lints/stdio.rs: -------------------------------------------------------------------------------- 1 | use rustc_hir as hir; 2 | use rustc_lint::{LateContext, LateLintPass, LintContext}; 3 | 4 | declare_plrust_lint!( 5 | pub(crate) PLRUST_STDIO, 6 | "Disallow functions like `io::{stdout, stderr, stdin}`", 7 | ); 8 | 9 | rustc_lint_defs::declare_lint_pass!(PlrustPrintFunctions => [PLRUST_STDIO]); 10 | 11 | impl<'tcx> LateLintPass<'tcx> for PlrustPrintFunctions { 12 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr) { 13 | let paths: &[&[&str]] = &[ 14 | &["std", "io", "stdio", "stdout"], 15 | &["std", "io", "stdio", "stderr"], 16 | &["std", "io", "stdio", "stdin"], 17 | ]; 18 | for &path in paths { 19 | if super::utils::does_expr_call_path(cx, expr, path) { 20 | cx.lint( 21 | PLRUST_STDIO, 22 | "the standard streams are forbidden in PL/Rust, \ 23 | consider using `pgrx::log!()` instead", 24 | |b| b.set_span(expr.span), 25 | ); 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /plrustc/plrustc/src/lints/sus_trait_object.rs: -------------------------------------------------------------------------------- 1 | use rustc_hir as hir; 2 | use rustc_lint::{LateContext, LateLintPass, LintContext}; 3 | use rustc_middle::ty; 4 | 5 | declare_plrust_lint!( 6 | pub(crate) PLRUST_SUSPICIOUS_TRAIT_OBJECT, 7 | "Disallow suspicious generic use of trait objects", 8 | ); 9 | 10 | rustc_lint_defs::declare_lint_pass!(PlrustSuspiciousTraitObject => [PLRUST_SUSPICIOUS_TRAIT_OBJECT]); 11 | 12 | impl<'tcx> LateLintPass<'tcx> for PlrustSuspiciousTraitObject { 13 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr) { 14 | let path_segments = match &expr.kind { 15 | hir::ExprKind::Path(hir::QPath::Resolved(_, path)) => path.segments, 16 | hir::ExprKind::Path(hir::QPath::TypeRelative(_, segment, ..)) 17 | | hir::ExprKind::MethodCall(segment, ..) => std::slice::from_ref(*segment), 18 | // We're looking for expressions that (directly, since `check_expr` 19 | // will visit stuff that contains them through `Expr`) contain 20 | // paths, and there's nothing else. 21 | _ => return, 22 | }; 23 | for segment in path_segments { 24 | let Some(args) = segment.args else { 25 | continue; 26 | }; 27 | for arg in args.args { 28 | let hir::GenericArg::Type(ty) = arg else { 29 | continue; 30 | }; 31 | let typeck_results = cx.typeck_results(); 32 | let is_trait_obj = 33 | matches!(typeck_results.node_type(ty.hir_id).kind(), ty::Dynamic(..)); 34 | if is_trait_obj { 35 | cx.lint( 36 | PLRUST_SUSPICIOUS_TRAIT_OBJECT, 37 | "using trait objects in turbofish position is forbidden by PL/Rust", 38 | |b| b.set_span(expr.span), 39 | ); 40 | } 41 | } 42 | } 43 | } 44 | 45 | fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { 46 | let generics = match &item.kind { 47 | hir::ItemKind::TyAlias(_, generics) => *generics, 48 | hir::ItemKind::Enum(_, generics) => *generics, 49 | hir::ItemKind::Struct(_, generics) => *generics, 50 | hir::ItemKind::Union(_, generics) => *generics, 51 | hir::ItemKind::Trait(_, _, generics, ..) => *generics, 52 | hir::ItemKind::Fn(_, generics, ..) => *generics, 53 | // Nothing else is stable and has `Generics`. 54 | _ => return, 55 | }; 56 | for param in generics.params { 57 | let hir::GenericParamKind::Type { default: Some(ty), .. } = ¶m.kind else { 58 | continue; 59 | }; 60 | if let hir::TyKind::TraitObject(..) = &ty.kind { 61 | cx.lint( 62 | PLRUST_SUSPICIOUS_TRAIT_OBJECT, 63 | "trait objects in generic defaults are forbidden", 64 | |b| b.set_span(item.span), 65 | ); 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /plrustc/plrustc/src/lints/utils.rs: -------------------------------------------------------------------------------- 1 | use hir::{def::Res, def_id::DefId}; 2 | use rustc_hir as hir; 3 | use rustc_lint::LateContext; 4 | use rustc_span::hygiene::ExpnData; 5 | use rustc_span::{Span, Symbol}; 6 | 7 | macro_rules! declare_plrust_lint { 8 | ( 9 | $(#[$attr:meta])* 10 | $v:vis $NAME:ident, 11 | $desc:expr $(,)? 12 | ) => { 13 | rustc_lint_defs::declare_lint! ( 14 | $(#[$attr])* 15 | $v $NAME, 16 | Allow, 17 | $desc, 18 | report_in_external_macro 19 | ); 20 | }; 21 | } 22 | 23 | /// `sym!(foo)` is shorthand for `Symbol::intern("foo")` 24 | /// 25 | /// We *technically* could use `rustc_span::sym::$id` in some cases, but 26 | /// rust-analyzer false-positives coupled with how often the set of symbols 27 | /// changes... it feels like it could be a genuine maintenance issue to do that. 28 | macro_rules! sym { 29 | ($id:ident) => { 30 | rustc_span::Symbol::intern(stringify!($id)) 31 | }; 32 | } 33 | 34 | pub fn check_span_against_macro_def_paths( 35 | cx: &LateContext<'_>, 36 | srcspan: Span, 37 | def_paths: &[&[Symbol]], 38 | ) -> Option<(Span, usize, DefId)> { 39 | let (which, defid) = iter_expn_data(srcspan).find_map(|expndata| { 40 | let Some(did) = expndata.macro_def_id else { 41 | return None; 42 | }; 43 | let macro_def_path = cx.get_def_path(did); 44 | def_paths 45 | .iter() 46 | .position(|&defpath| defpath == macro_def_path.as_slice()) 47 | .map(|pos| (pos, did)) 48 | })?; 49 | let outermost_span = outermost_expn_data(srcspan.ctxt().outer_expn_data()).call_site; 50 | Some((outermost_span, which, defid)) 51 | } 52 | 53 | pub fn outermost_expn_data(expn_data: ExpnData) -> ExpnData { 54 | if expn_data.call_site.from_expansion() { 55 | outermost_expn_data(expn_data.call_site.ctxt().outer_expn_data()) 56 | } else { 57 | expn_data 58 | } 59 | } 60 | 61 | pub fn iter_expn_data(span: Span) -> impl Iterator { 62 | let mut next = Some(span.ctxt().outer_expn_data()); 63 | std::iter::from_fn(move || { 64 | let curr = next.take()?; 65 | next = curr 66 | .call_site 67 | .from_expansion() 68 | .then_some(curr.call_site.ctxt().outer_expn_data()); 69 | Some(curr) 70 | }) 71 | } 72 | /// Note: this is error-prone! 73 | pub fn outer_macro_call_matching<'tcx, F, T>( 74 | cx: &LateContext<'tcx>, 75 | span: Span, 76 | mut matches: F, 77 | ) -> Option<(Span, T, DefId)> 78 | where 79 | F: FnMut(DefId, Option) -> Option, 80 | { 81 | let mut expn = span.ctxt().outer_expn_data(); 82 | let mut found = None::<(T, DefId)>; 83 | loop { 84 | let parent = expn.call_site.ctxt().outer_expn_data(); 85 | let Some(id) = parent.macro_def_id else { 86 | break; 87 | }; 88 | let Some(thing) = matches(id, cx.tcx.get_diagnostic_name(id)) else { 89 | break; 90 | }; 91 | expn = parent; 92 | found = Some((thing, id)); 93 | } 94 | found.map(|(thing, defid)| (expn.call_site, thing, defid)) 95 | } 96 | 97 | pub fn check_span_against_macro_diags( 98 | cx: &LateContext<'_>, 99 | span: Span, 100 | diag_syms: &[Symbol], 101 | ) -> Option<(Span, usize, DefId)> { 102 | outer_macro_call_matching(cx, span, |_did, diag_name| { 103 | let diag_name = diag_name?; 104 | diag_syms.iter().position(|&name| name == diag_name) 105 | }) 106 | } 107 | 108 | // Note: returns true if the expr is the path also. 109 | pub fn does_expr_call_path(cx: &LateContext<'_>, expr: &hir::Expr<'_>, segments: &[&str]) -> bool { 110 | path_res(cx, expr) 111 | .opt_def_id() 112 | .or_else(|| match &expr.kind { 113 | hir::ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id), 114 | _ => None, 115 | }) 116 | .map_or(false, |id| match_def_path(cx, id, segments)) 117 | } 118 | 119 | pub fn path_res(cx: &LateContext<'_>, ex: &hir::Expr<'_>) -> Res { 120 | if let hir::ExprKind::Path(qpath) = &ex.kind { 121 | cx.qpath_res(qpath, ex.hir_id) 122 | } else { 123 | Res::Err 124 | } 125 | } 126 | 127 | pub fn match_def_path<'tcx>(cx: &LateContext<'tcx>, did: DefId, syms: &[&str]) -> bool { 128 | let path = cx.get_def_path(did); 129 | syms.iter() 130 | .map(|x| Symbol::intern(x)) 131 | .eq(path.iter().copied()) 132 | } 133 | 134 | pub fn has_fn_trait(cx: &LateContext<'_>, poly_trait: &hir::PolyTraitRef<'_>) -> bool { 135 | let Some(impl_did) = poly_trait.trait_ref.path.res.opt_def_id() else { 136 | return false 137 | }; 138 | let lang_items = cx.tcx.lang_items(); 139 | let fntraits = [ 140 | lang_items.get(hir::LangItem::Fn), 141 | lang_items.get(hir::LangItem::FnOnce), 142 | lang_items.get(hir::LangItem::FnMut), 143 | lang_items.get(hir::LangItem::FnPtrTrait), 144 | ]; 145 | fntraits.contains(&Some(impl_did)) 146 | } 147 | -------------------------------------------------------------------------------- /plrustc/plrustc/tests/uitests.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | #[test] 4 | fn uitests() { 5 | let plrustc = env!("CARGO_BIN_EXE_plrustc"); 6 | let root = std::env::var_os("CARGO_MANIFEST_DIR") 7 | .map(PathBuf::from) 8 | .unwrap_or_else(|| env!("CARGO_MANIFEST_DIR").into()); 9 | let bless = std::env::var_os("PLRUSTC_BLESS").is_some(); 10 | 11 | compiletest_rs::run_tests(&compiletest_rs::Config { 12 | mode: compiletest_rs::common::Mode::Ui, 13 | edition: Some("2021".into()), 14 | rustc_path: std::path::PathBuf::from(&plrustc), 15 | bless, 16 | src_base: root.join("uitests"), 17 | target_rustcflags: Some("--emit=metadata -Fplrust_lints -Dwarnings -Zui-testing".into()), 18 | 19 | ..compiletest_rs::Config::default() 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/async_await.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "lib"] 2 | 3 | async fn foo() {} 4 | 5 | fn bar() { 6 | let _ = async { 1 }; 7 | } 8 | 9 | async fn baz() -> i32 { 10 | async { 1 }.await 11 | } 12 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/async_await.stderr: -------------------------------------------------------------------------------- 1 | error: Use of async/await is forbidden in PL/Rust 2 | --> $DIR/async_await.rs:3:1 3 | | 4 | LL | async fn foo() {} 5 | | ^^^^^^^^^^^^^^^^^ 6 | | 7 | = note: `-F plrust-async` implied by `-F plrust-lints` 8 | 9 | error: Use of async/await is forbidden in PL/Rust 10 | --> $DIR/async_await.rs:6:13 11 | | 12 | LL | let _ = async { 1 }; 13 | | ^^^^^^^^^^^ 14 | 15 | error: Use of async/await is forbidden in PL/Rust 16 | --> $DIR/async_await.rs:9:1 17 | | 18 | LL | / async fn baz() -> i32 { 19 | LL | | async { 1 }.await 20 | LL | | } 21 | | |_^ 22 | 23 | error: Use of async/await is forbidden in PL/Rust 24 | --> $DIR/async_await.rs:10:5 25 | | 26 | LL | async { 1 }.await 27 | | ^^^^^^^^^^^^^^^^^ 28 | 29 | error: Use of async/await is forbidden in PL/Rust 30 | --> $DIR/async_await.rs:10:5 31 | | 32 | LL | async { 1 }.await 33 | | ^^^^^^^^^^^ 34 | 35 | error: aborting due to 5 previous errors 36 | 37 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/extern_block.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "lib"] 2 | extern "C" {} 3 | extern "Rust" {} 4 | #[rustfmt::skip] 5 | extern {} 6 | 7 | macro_rules! foo { 8 | () => { 9 | extern "C" {} 10 | }; 11 | } 12 | 13 | foo!(); 14 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/extern_block.stderr: -------------------------------------------------------------------------------- 1 | error: `extern` blocks are not allowed in PL/Rust 2 | --> $DIR/extern_block.rs:2:1 3 | | 4 | LL | extern "C" {} 5 | | ^^^^^^^^^^^^^ 6 | | 7 | = note: `-F plrust-extern-blocks` implied by `-F plrust-lints` 8 | 9 | error: `extern` blocks are not allowed in PL/Rust 10 | --> $DIR/extern_block.rs:3:1 11 | | 12 | LL | extern "Rust" {} 13 | | ^^^^^^^^^^^^^^^^ 14 | 15 | error: `extern` blocks are not allowed in PL/Rust 16 | --> $DIR/extern_block.rs:5:1 17 | | 18 | LL | extern {} 19 | | ^^^^^^^^^ 20 | 21 | error: `extern` blocks are not allowed in PL/Rust 22 | --> $DIR/extern_block.rs:9:9 23 | | 24 | LL | extern "C" {} 25 | | ^^^^^^^^^^^^^ 26 | ... 27 | LL | foo!(); 28 | | ------ in this macro invocation 29 | | 30 | = note: this error originates in the macro `foo` (in Nightly builds, run with -Z macro-backtrace for more info) 31 | 32 | error: aborting due to 4 previous errors 33 | 34 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/external_mod.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "lib"] 2 | 3 | // Should be allowed. 4 | pub mod blah {} 5 | 6 | // Should not be allowed. 7 | #[path = "external_mod_included_file.txt"] 8 | pub mod foo; 9 | 10 | // Also should not be allowed, but I'm not really sure how to test it... 11 | 12 | // pub mod also_disallowed; 13 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/external_mod.stderr: -------------------------------------------------------------------------------- 1 | error: Use of external modules is forbidden in PL/Rust 2 | --> $DIR/external_mod.rs:8:1 3 | | 4 | LL | pub mod foo; 5 | | ^^^^^^^^^^^^ 6 | | 7 | = note: `-F plrust-external-mod` implied by `-F plrust-lints` 8 | 9 | error: aborting due to previous error 10 | 11 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/external_mod_included_file.txt: -------------------------------------------------------------------------------- 1 | // This is valid rust syntax, but is meaningless. 2 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/fn_pointer.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "lib"] 2 | 3 | pub fn foobar<'short, T>(r: &'short T) -> &'static T { 4 | fn foo<'out, 'input, T>(_dummy: &'out (), value: &'input T) -> (&'out &'input (), &'out T) { 5 | (&&(), value) 6 | } 7 | let foo1: for<'out, 'input> fn(&'out (), &'input T) -> (&'out &'input (), &'out T) = foo; 8 | let foo2: for<'input> fn(&'static (), &'input T) -> (&'static &'input (), &'static T) = foo1; 9 | let foo3: for<'input> fn(&'static (), &'input T) -> (&'input &'input (), &'static T) = foo2; 10 | let foo4: fn(&'static (), &'short T) -> (&'short &'short (), &'static T) = foo3; 11 | foo4(&(), r).1 12 | } 13 | 14 | pub fn should_be_allowed() { 15 | let a = &[1, 2, 3u8]; 16 | // This should be allowed, as it's not a function pointer. 17 | a.iter().for_each(|v| { 18 | let _ignored = v; 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/fn_pointer.stderr: -------------------------------------------------------------------------------- 1 | error: Use of function pointers is forbidden in PL/Rust 2 | --> $DIR/fn_pointer.rs:7:15 3 | | 4 | LL | let foo1: for<'out, 'input> fn(&'out (), &'input T) -> (&'out &'input (), &'out T) = foo; 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = note: `-F plrust-fn-pointers` implied by `-F plrust-lints` 8 | 9 | error: Use of function pointers is forbidden in PL/Rust 10 | --> $DIR/fn_pointer.rs:8:15 11 | | 12 | LL | let foo2: for<'input> fn(&'static (), &'input T) -> (&'static &'input (), &'static T) = foo1; 13 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 14 | 15 | error: Use of function pointers is forbidden in PL/Rust 16 | --> $DIR/fn_pointer.rs:9:15 17 | | 18 | LL | let foo3: for<'input> fn(&'static (), &'input T) -> (&'input &'input (), &'static T) = foo2; 19 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 20 | 21 | error: Use of function pointers is forbidden in PL/Rust 22 | --> $DIR/fn_pointer.rs:10:15 23 | | 24 | LL | let foo4: fn(&'static (), &'short T) -> (&'short &'short (), &'static T) = foo3; 25 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 26 | 27 | error: aborting due to 4 previous errors 28 | 29 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/fn_traits.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "lib"] 2 | 3 | fn blah() { 4 | let _a: &dyn Fn() = &|| {}; 5 | let _b: Box = Box::new(|| {}); 6 | let _c: Box = Box::new(|| {}); 7 | let _d: Box = Box::new(|| {}); 8 | } 9 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/fn_traits.stderr: -------------------------------------------------------------------------------- 1 | error: Use of function trait objects is forbidden in PL/Rust 2 | --> $DIR/fn_traits.rs:4:14 3 | | 4 | LL | let _a: &dyn Fn() = &|| {}; 5 | | ^^^^^^^^ 6 | | 7 | = note: `-F plrust-fn-pointers` implied by `-F plrust-lints` 8 | 9 | error: Use of function trait objects is forbidden in PL/Rust 10 | --> $DIR/fn_traits.rs:5:17 11 | | 12 | LL | let _b: Box = Box::new(|| {}); 13 | | ^^^^^^^^ 14 | 15 | error: Use of function trait objects is forbidden in PL/Rust 16 | --> $DIR/fn_traits.rs:6:17 17 | | 18 | LL | let _c: Box = Box::new(|| {}); 19 | | ^^^^^^^^^^^ 20 | 21 | error: Use of function trait objects is forbidden in PL/Rust 22 | --> $DIR/fn_traits.rs:7:17 23 | | 24 | LL | let _d: Box = Box::new(|| {}); 25 | | ^^^^^^^^^^^^ 26 | 27 | error: aborting due to 4 previous errors 28 | 29 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/fs_macros.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "lib"] 2 | 3 | const _A: &str = include_str!("fs_macros_included_file.txt"); 4 | 5 | const _B: &[u8] = include_bytes!("fs_macros_included_file.txt"); 6 | 7 | const _C: &str = core::include_str!("fs_macros_included_file.txt"); 8 | const _D: &[u8] = core::include_bytes!("fs_macros_included_file.txt"); 9 | 10 | macro_rules! indirect { 11 | ($callme:ident) => { 12 | $callme!("fs_macros_included_file.txt") 13 | }; 14 | } 15 | 16 | const _E: &str = indirect!(include_str); 17 | const _F: &[u8] = indirect!(include_bytes); 18 | 19 | macro_rules! in_macro { 20 | () => { 21 | include_str!("fs_macros_included_file.txt") 22 | }; 23 | ($invocation:expr) => { 24 | $invocation 25 | }; 26 | ($mac:ident, $arg:expr) => { 27 | $mac!($arg) 28 | }; 29 | } 30 | 31 | const _G: &str = in_macro!(); 32 | const _H: &str = in_macro!(include_str!("fs_macros_included_file.txt")); 33 | const _I: &[u8] = in_macro!(include_bytes!("fs_macros_included_file.txt")); 34 | const _J: &[u8] = in_macro!(include_bytes, "fs_macros_included_file.txt"); 35 | 36 | use core::include_str as sneaky; 37 | const _L: &str = sneaky!("fs_macros_included_file.txt"); 38 | const _M: &str = in_macro!(sneaky, "fs_macros_included_file.txt"); 39 | 40 | fn _foo() -> String { 41 | format!("{:?}", in_macro!()) 42 | } 43 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/fs_macros.stderr: -------------------------------------------------------------------------------- 1 | error: the `include_str`, `include_bytes`, and `include` macros are forbidden in PL/Rust 2 | --> $DIR/fs_macros.rs:3:18 3 | | 4 | LL | const _A: &str = include_str!("fs_macros_included_file.txt"); 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = note: `-F plrust-filesystem-macros` implied by `-F plrust-lints` 8 | 9 | error: the `include_str`, `include_bytes`, and `include` macros are forbidden in PL/Rust 10 | --> $DIR/fs_macros.rs:5:19 11 | | 12 | LL | const _B: &[u8] = include_bytes!("fs_macros_included_file.txt"); 13 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 14 | 15 | error: the `include_str`, `include_bytes`, and `include` macros are forbidden in PL/Rust 16 | --> $DIR/fs_macros.rs:7:18 17 | | 18 | LL | const _C: &str = core::include_str!("fs_macros_included_file.txt"); 19 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 20 | 21 | error: the `include_str`, `include_bytes`, and `include` macros are forbidden in PL/Rust 22 | --> $DIR/fs_macros.rs:8:19 23 | | 24 | LL | const _D: &[u8] = core::include_bytes!("fs_macros_included_file.txt"); 25 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 26 | 27 | error: the `include_str`, `include_bytes`, and `include` macros are forbidden in PL/Rust 28 | --> $DIR/fs_macros.rs:16:18 29 | | 30 | LL | const _E: &str = indirect!(include_str); 31 | | ^^^^^^^^^^^^^^^^^^^^^^ 32 | 33 | error: the `include_str`, `include_bytes`, and `include` macros are forbidden in PL/Rust 34 | --> $DIR/fs_macros.rs:17:19 35 | | 36 | LL | const _F: &[u8] = indirect!(include_bytes); 37 | | ^^^^^^^^^^^^^^^^^^^^^^^^ 38 | 39 | error: the `include_str`, `include_bytes`, and `include` macros are forbidden in PL/Rust 40 | --> $DIR/fs_macros.rs:31:18 41 | | 42 | LL | const _G: &str = in_macro!(); 43 | | ^^^^^^^^^^^ 44 | 45 | error: the `include_str`, `include_bytes`, and `include` macros are forbidden in PL/Rust 46 | --> $DIR/fs_macros.rs:32:28 47 | | 48 | LL | const _H: &str = in_macro!(include_str!("fs_macros_included_file.txt")); 49 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 50 | 51 | error: the `include_str`, `include_bytes`, and `include` macros are forbidden in PL/Rust 52 | --> $DIR/fs_macros.rs:33:29 53 | | 54 | LL | const _I: &[u8] = in_macro!(include_bytes!("fs_macros_included_file.txt")); 55 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 56 | 57 | error: the `include_str`, `include_bytes`, and `include` macros are forbidden in PL/Rust 58 | --> $DIR/fs_macros.rs:34:19 59 | | 60 | LL | const _J: &[u8] = in_macro!(include_bytes, "fs_macros_included_file.txt"); 61 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 62 | 63 | error: the `include_str`, `include_bytes`, and `include` macros are forbidden in PL/Rust 64 | --> $DIR/fs_macros.rs:37:18 65 | | 66 | LL | const _L: &str = sneaky!("fs_macros_included_file.txt"); 67 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 68 | 69 | error: the `include_str`, `include_bytes`, and `include` macros are forbidden in PL/Rust 70 | --> $DIR/fs_macros.rs:38:18 71 | | 72 | LL | const _M: &str = in_macro!(sneaky, "fs_macros_included_file.txt"); 73 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 74 | 75 | error: the `include_str`, `include_bytes`, and `include` macros are forbidden in PL/Rust 76 | --> $DIR/fs_macros.rs:41:21 77 | | 78 | LL | format!("{:?}", in_macro!()) 79 | | ^^^^^^^^^^^ 80 | 81 | error: aborting due to 13 previous errors 82 | 83 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/fs_macros_included_file.txt: -------------------------------------------------------------------------------- 1 | hello 2 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/ice_hook.rs: -------------------------------------------------------------------------------- 1 | // rustc-env:RUST_BACKTRACE=0 2 | // rustc-env:PLRUSTC_INCLUDE_TEST_ONLY_LINTS=1 3 | // normalize-stderr-test: "plrustc version: .*" -> "plrustc version: " 4 | // normalize-stderr-test: "force_ice.rs:\d*:\d*" -> "force_ice.rs" 5 | // normalize-stderr-test: "(?ms)query stack during panic:\n.*end of query stack\n" -> "" 6 | // normalize-stderr-test: "note: rustc .*? running on .*" -> "note: rustc running on " 7 | #![crate_type = "lib"] 8 | // The comments above are to clean up file/line/version numbers, backtrace info, 9 | // etc. We want to avoid ice_hook.stderr changing more than is needed. 10 | 11 | // This function name is special-cased in `PlrustcForceIce` 12 | pub fn plrustc_would_like_some_ice() {} 13 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/ice_hook.stderr: -------------------------------------------------------------------------------- 1 | thread 'rustc' panicked at 'Here is your ICE', plrustc/src/lints/force_ice.rs 2 | note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace 3 | 4 | error: the compiler unexpectedly panicked. this is a bug. 5 | 6 | note: we would appreciate a bug report: https://github.com/tcdi/plrust/issues/new 7 | 8 | note: rustc running on 9 | 10 | note: compiler flags: -C prefer-dynamic -Z ui-testing 11 | 12 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/impl_auto_trait.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "lib"] 2 | 3 | pub struct Foo(pub std::cell::Cell, pub std::marker::PhantomPinned); 4 | 5 | impl std::panic::UnwindSafe for Foo {} 6 | 7 | impl std::panic::RefUnwindSafe for Foo {} 8 | 9 | impl std::marker::Unpin for Foo {} 10 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/impl_auto_trait.stderr: -------------------------------------------------------------------------------- 1 | error: explicit implementations of auto traits are forbidden in PL/Rust 2 | --> $DIR/impl_auto_trait.rs:5:1 3 | | 4 | LL | impl std::panic::UnwindSafe for Foo {} 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = note: `-F plrust-autotrait-impls` implied by `-F plrust-lints` 8 | 9 | error: explicit implementations of auto traits are forbidden in PL/Rust 10 | --> $DIR/impl_auto_trait.rs:7:1 11 | | 12 | LL | impl std::panic::RefUnwindSafe for Foo {} 13 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 14 | 15 | error: explicit implementations of auto traits are forbidden in PL/Rust 16 | --> $DIR/impl_auto_trait.rs:9:1 17 | | 18 | LL | impl std::marker::Unpin for Foo {} 19 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 20 | 21 | error: aborting due to 3 previous errors 22 | 23 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/leaky.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "lib"] 2 | 3 | fn _f() { 4 | let a = Box::new(1u32); 5 | let _b = Box::leak(a); 6 | 7 | let c = vec![1u8, 2, 3]; 8 | let _d = c.leak(); 9 | 10 | let e = vec![1u8, 2, 3]; 11 | let _f = Vec::leak(e); 12 | 13 | let _g = std::mem::forget(vec![1u32]); 14 | 15 | let vec_leak = Vec::::leak; 16 | let _ = vec_leak(vec![1, 2, 3u8]); 17 | } 18 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/leaky.stderr: -------------------------------------------------------------------------------- 1 | error: Leaky functions are forbidden in PL/Rust 2 | --> $DIR/leaky.rs:5:14 3 | | 4 | LL | let _b = Box::leak(a); 5 | | ^^^^^^^^^ 6 | | 7 | = note: `-F plrust-leaky` implied by `-F plrust-lints` 8 | 9 | error: Leaky functions are forbidden in PL/Rust 10 | --> $DIR/leaky.rs:8:14 11 | | 12 | LL | let _d = c.leak(); 13 | | ^^^^^^^^ 14 | 15 | error: Leaky functions are forbidden in PL/Rust 16 | --> $DIR/leaky.rs:11:14 17 | | 18 | LL | let _f = Vec::leak(e); 19 | | ^^^^^^^^^ 20 | 21 | error: Leaky functions are forbidden in PL/Rust 22 | --> $DIR/leaky.rs:13:14 23 | | 24 | LL | let _g = std::mem::forget(vec![1u32]); 25 | | ^^^^^^^^^^^^^^^^ 26 | 27 | error: Leaky functions are forbidden in PL/Rust 28 | --> $DIR/leaky.rs:15:20 29 | | 30 | LL | let vec_leak = Vec::::leak; 31 | | ^^^^^^^^^^^^^^^ 32 | 33 | error: aborting due to 5 previous errors 34 | 35 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/lifetime_trait.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "lib"] 2 | // #![forbid(plrust_lifetime_parameterized_traits)] 3 | trait Foo<'a> {} 4 | 5 | trait Bar {} 6 | 7 | macro_rules! foobar { 8 | () => { 9 | trait Foobar<'a> {} 10 | }; 11 | } 12 | 13 | foobar!(); 14 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/lifetime_trait.stderr: -------------------------------------------------------------------------------- 1 | error: PL/Rust forbids declaring traits with generic lifetime parameters 2 | --> $DIR/lifetime_trait.rs:3:1 3 | | 4 | LL | trait Foo<'a> {} 5 | | ^^^^^^^^^^^^^^^^ 6 | | 7 | = note: `-F plrust-lifetime-parameterized-traits` implied by `-F plrust-lints` 8 | 9 | error: PL/Rust forbids declaring traits with generic lifetime parameters 10 | --> $DIR/lifetime_trait.rs:9:9 11 | | 12 | LL | trait Foobar<'a> {} 13 | | ^^^^^^^^^^^^^^^^^^^ 14 | ... 15 | LL | foobar!(); 16 | | --------- in this macro invocation 17 | | 18 | = note: this error originates in the macro `foobar` (in Nightly builds, run with -Z macro-backtrace for more info) 19 | 20 | error: aborting due to 2 previous errors 21 | 22 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/print_macros.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "lib"] 2 | 3 | fn _foo() { 4 | print!("hello"); 5 | println!("world"); 6 | 7 | eprint!("test"); 8 | eprintln!("123"); 9 | 10 | dbg!("baz"); 11 | } 12 | 13 | fn _bar() { 14 | use std::{dbg as d, eprint as e, eprintln as eln, print as p, println as pln}; 15 | p!("hello"); 16 | pln!("world"); 17 | 18 | e!("test"); 19 | eln!("123"); 20 | 21 | d!("baz"); 22 | } 23 | 24 | macro_rules! wrapped { 25 | () => {{ 26 | print!("hello"); 27 | println!("world"); 28 | 29 | eprint!("test"); 30 | eprintln!("123"); 31 | 32 | dbg!("baz"); 33 | }}; 34 | } 35 | 36 | fn _baz() { 37 | wrapped!(); 38 | } 39 | 40 | macro_rules! indirect { 41 | ($invocation:expr) => { 42 | $invocation 43 | }; 44 | ($mac:ident, $arg:expr) => { 45 | $mac!($arg) 46 | }; 47 | (@call $mac:ident) => { 48 | $mac!() 49 | }; 50 | } 51 | 52 | fn _indir() { 53 | indirect!(println!("foo")); 54 | indirect!(println, "foo"); 55 | } 56 | 57 | fn _indir2() { 58 | indirect!(wrapped!()); 59 | indirect!(@call wrapped); 60 | } 61 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/static_closure.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "lib"] 2 | use std::fmt; 3 | trait Trait { 4 | type Associated; 5 | } 6 | 7 | impl R> Trait for F { 8 | type Associated = R; 9 | } 10 | 11 | fn static_transfers_to_associated( 12 | _: &T, 13 | x: T::Associated, 14 | ) -> Box 15 | where 16 | T::Associated: fmt::Display, 17 | { 18 | Box::new(x) // T::Associated: 'static follows from T: 'static 19 | } 20 | 21 | pub fn make_static_displayable<'a>(s: &'a str) -> Box { 22 | let f = || -> &'a str { "" }; 23 | // problem is: the closure type of `f` is 'static 24 | static_transfers_to_associated(&f, s) 25 | } 26 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/static_closure.stderr: -------------------------------------------------------------------------------- 1 | error: trait impls bounded on function traits are forbidden in PL/Rust 2 | --> $DIR/static_closure.rs:7:10 3 | | 4 | LL | impl R> Trait for F { 5 | | ^^^^^^^^^^^ 6 | | 7 | = note: `-F plrust-closure-trait-impl` implied by `-F plrust-lints` 8 | 9 | error: aborting due to previous error 10 | 11 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/static_impl_etc.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "lib"] 2 | 3 | trait Foo {} 4 | struct Bar(core::marker::PhantomData<(A, B)>); 5 | struct Baz<'a, A>(core::marker::PhantomData<&'a A>); 6 | trait Quux {} 7 | 8 | impl Foo for &'static T {} 9 | impl Foo for &'static mut T {} 10 | impl Foo for [&'static T] {} 11 | impl Foo for &[&'static T] {} 12 | impl Foo for (i32, [&'static T]) {} 13 | impl Foo for (i32, [&'static T; 1]) {} 14 | impl Foo for *const &'static T {} 15 | impl Foo for Bar {} 16 | impl Foo for Baz<'static, T> {} 17 | impl Foo for dyn Quux<&'static T> {} 18 | impl Foo for &'static dyn Quux {} 19 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/static_impl_etc.stderr: -------------------------------------------------------------------------------- 1 | error: `impl` blocks for types containing `'static` references are not allowed in PL/Rust 2 | --> $DIR/static_impl_etc.rs:8:17 3 | | 4 | LL | impl Foo for &'static T {} 5 | | ^^^^^^^^^^ 6 | | 7 | = note: `-F plrust-static-impls` implied by `-F plrust-lints` 8 | 9 | error: `impl` blocks for types containing `'static` references are not allowed in PL/Rust 10 | --> $DIR/static_impl_etc.rs:9:17 11 | | 12 | LL | impl Foo for &'static mut T {} 13 | | ^^^^^^^^^^^^^^ 14 | 15 | error: `impl` blocks for types containing `'static` references are not allowed in PL/Rust 16 | --> $DIR/static_impl_etc.rs:10:17 17 | | 18 | LL | impl Foo for [&'static T] {} 19 | | ^^^^^^^^^^^^ 20 | 21 | error: `impl` blocks for types containing `'static` references are not allowed in PL/Rust 22 | --> $DIR/static_impl_etc.rs:11:17 23 | | 24 | LL | impl Foo for &[&'static T] {} 25 | | ^^^^^^^^^^^^^ 26 | 27 | error: `impl` blocks for types containing `'static` references are not allowed in PL/Rust 28 | --> $DIR/static_impl_etc.rs:12:17 29 | | 30 | LL | impl Foo for (i32, [&'static T]) {} 31 | | ^^^^^^^^^^^^^^^^^^^ 32 | 33 | error: `impl` blocks for types containing `'static` references are not allowed in PL/Rust 34 | --> $DIR/static_impl_etc.rs:13:17 35 | | 36 | LL | impl Foo for (i32, [&'static T; 1]) {} 37 | | ^^^^^^^^^^^^^^^^^^^^^^ 38 | 39 | error: `impl` blocks for types containing `'static` references are not allowed in PL/Rust 40 | --> $DIR/static_impl_etc.rs:14:17 41 | | 42 | LL | impl Foo for *const &'static T {} 43 | | ^^^^^^^^^^^^^^^^^ 44 | 45 | error: `impl` blocks for types containing `'static` references are not allowed in PL/Rust 46 | --> $DIR/static_impl_etc.rs:15:17 47 | | 48 | LL | impl Foo for Bar {} 49 | | ^^^^^^^^^^^^^^^^^^^^ 50 | 51 | error: `impl` blocks for types containing `'static` references are not allowed in PL/Rust 52 | --> $DIR/static_impl_etc.rs:16:17 53 | | 54 | LL | impl Foo for Baz<'static, T> {} 55 | | ^^^^^^^^^^^^^^^ 56 | 57 | error: `impl` blocks for types containing `'static` references are not allowed in PL/Rust 58 | --> $DIR/static_impl_etc.rs:17:17 59 | | 60 | LL | impl Foo for dyn Quux<&'static T> {} 61 | | ^^^^^^^^^^^^^^^^^^^^ 62 | 63 | error: `impl` blocks for types containing `'static` references are not allowed in PL/Rust 64 | --> $DIR/static_impl_etc.rs:18:17 65 | | 66 | LL | impl Foo for &'static dyn Quux {} 67 | | ^^^^^^^^^^^^^^^^^^^^ 68 | 69 | error: aborting due to 11 previous errors 70 | 71 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/static_impl_unsound.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "lib"] 2 | 3 | use std::fmt::Display; 4 | 5 | trait Displayable { 6 | fn display(self) -> Box; 7 | } 8 | 9 | // This is more complex than the one in the issue, to make sure the `Box`'s 10 | // lang_item status doesn't bite us. 11 | impl Displayable for (T, Box>) { 12 | fn display(self) -> Box { 13 | Box::new(self.0) 14 | } 15 | } 16 | 17 | fn extend_lt(val: T) -> Box 18 | where 19 | (T, Box>): Displayable, 20 | { 21 | Displayable::display((val, Box::new(None))) 22 | } 23 | 24 | pub fn get_garbage(s: &str) -> String { 25 | let val = extend_lt(&String::from(s)); 26 | val.to_string() 27 | } 28 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/static_impl_unsound.stderr: -------------------------------------------------------------------------------- 1 | error: `impl` blocks for types containing `'static` references are not allowed in PL/Rust 2 | --> $DIR/static_impl_unsound.rs:11:34 3 | | 4 | LL | impl Displayable for (T, Box>) { 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = note: `-F plrust-static-impls` implied by `-F plrust-lints` 8 | 9 | error: aborting due to previous error 10 | 11 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/stdio.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "lib"] 2 | 3 | fn _foo() { 4 | let _out = std::io::stdout(); 5 | let _in = std::io::stdin(); 6 | let _err = std::io::stderr(); 7 | } 8 | 9 | fn _bar() { 10 | use std::io::*; 11 | let _out = stdout(); 12 | let _in = stdin(); 13 | let _err = stderr(); 14 | } 15 | 16 | fn _baz() { 17 | use std::io::stdout as renamed; 18 | let _still_forbidden = renamed(); 19 | } 20 | 21 | fn _quux() { 22 | let as_func = std::io::stdout; 23 | let _also_forbidden = as_func(); 24 | } 25 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/stdio.stderr: -------------------------------------------------------------------------------- 1 | error: the standard streams are forbidden in PL/Rust, consider using `pgrx::log!()` instead 2 | --> $DIR/stdio.rs:4:16 3 | | 4 | LL | let _out = std::io::stdout(); 5 | | ^^^^^^^^^^^^^^^ 6 | | 7 | = note: `-F plrust-stdio` implied by `-F plrust-lints` 8 | 9 | error: the standard streams are forbidden in PL/Rust, consider using `pgrx::log!()` instead 10 | --> $DIR/stdio.rs:5:15 11 | | 12 | LL | let _in = std::io::stdin(); 13 | | ^^^^^^^^^^^^^^ 14 | 15 | error: the standard streams are forbidden in PL/Rust, consider using `pgrx::log!()` instead 16 | --> $DIR/stdio.rs:6:16 17 | | 18 | LL | let _err = std::io::stderr(); 19 | | ^^^^^^^^^^^^^^^ 20 | 21 | error: the standard streams are forbidden in PL/Rust, consider using `pgrx::log!()` instead 22 | --> $DIR/stdio.rs:11:16 23 | | 24 | LL | let _out = stdout(); 25 | | ^^^^^^ 26 | 27 | error: the standard streams are forbidden in PL/Rust, consider using `pgrx::log!()` instead 28 | --> $DIR/stdio.rs:12:15 29 | | 30 | LL | let _in = stdin(); 31 | | ^^^^^ 32 | 33 | error: the standard streams are forbidden in PL/Rust, consider using `pgrx::log!()` instead 34 | --> $DIR/stdio.rs:13:16 35 | | 36 | LL | let _err = stderr(); 37 | | ^^^^^^ 38 | 39 | error: the standard streams are forbidden in PL/Rust, consider using `pgrx::log!()` instead 40 | --> $DIR/stdio.rs:18:28 41 | | 42 | LL | let _still_forbidden = renamed(); 43 | | ^^^^^^^ 44 | 45 | error: the standard streams are forbidden in PL/Rust, consider using `pgrx::log!()` instead 46 | --> $DIR/stdio.rs:22:19 47 | | 48 | LL | let as_func = std::io::stdout; 49 | | ^^^^^^^^^^^^^^^ 50 | 51 | error: aborting due to 8 previous errors 52 | 53 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/sus_trait_obj_alias.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "lib"] 2 | trait Object { 3 | type Output; 4 | } 5 | 6 | impl Object for T { 7 | type Output = &'static u64; 8 | } 9 | 10 | fn foo<'a, T: ?Sized>(x: ::Output) -> &'a u64 { 11 | x 12 | } 13 | 14 | fn transmute_lifetime<'a, 'b>(x: &'a u64) -> &'b u64 { 15 | type A<'x> = dyn Object; 16 | type B<'x> = A<'x>; 17 | foo::>(x) 18 | } 19 | 20 | // And yes this is a genuine `transmute_lifetime`! 21 | fn get_dangling<'a>() -> &'a u64 { 22 | let x = 0; 23 | transmute_lifetime(&x) 24 | } 25 | 26 | pub fn problems() -> &'static u64 { 27 | get_dangling() 28 | } 29 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/sus_trait_obj_alias.stderr: -------------------------------------------------------------------------------- 1 | error: using trait objects in turbofish position is forbidden by PL/Rust 2 | --> $DIR/sus_trait_obj_alias.rs:17:5 3 | | 4 | LL | foo::>(x) 5 | | ^^^^^^^^^^^^ 6 | | 7 | = note: `-F plrust-suspicious-trait-object` implied by `-F plrust-lints` 8 | 9 | error: aborting due to previous error 10 | 11 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/sus_trait_obj_items.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "lib"] 2 | 3 | pub trait Foo {} 4 | 5 | pub trait Bar 6 | where 7 | T: ?Sized, 8 | { 9 | } 10 | 11 | #[allow(invalid_type_param_default)] // not the lint we're interested in testing 12 | pub fn sus_fn() 13 | where 14 | T: ?Sized, 15 | { 16 | } 17 | 18 | pub struct SusStruct(pub Box) 19 | where 20 | T: ?Sized; 21 | 22 | pub enum SusEnum 23 | where 24 | T: ?Sized, 25 | { 26 | Something(Box), 27 | } 28 | 29 | pub union SusUnion 30 | where 31 | T: ?Sized, 32 | { 33 | pub something: *const T, 34 | } 35 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/sus_trait_obj_items.stderr: -------------------------------------------------------------------------------- 1 | error: trait objects in generic defaults are forbidden 2 | --> $DIR/sus_trait_obj_items.rs:5:1 3 | | 4 | LL | / pub trait Bar 5 | LL | | where 6 | LL | | T: ?Sized, 7 | LL | | { 8 | LL | | } 9 | | |_^ 10 | | 11 | = note: `-F plrust-suspicious-trait-object` implied by `-F plrust-lints` 12 | 13 | error: trait objects in generic defaults are forbidden 14 | --> $DIR/sus_trait_obj_items.rs:12:1 15 | | 16 | LL | / pub fn sus_fn() 17 | LL | | where 18 | LL | | T: ?Sized, 19 | LL | | { 20 | LL | | } 21 | | |_^ 22 | 23 | error: trait objects in generic defaults are forbidden 24 | --> $DIR/sus_trait_obj_items.rs:18:1 25 | | 26 | LL | / pub struct SusStruct(pub Box) 27 | LL | | where 28 | LL | | T: ?Sized; 29 | | |______________^ 30 | 31 | error: trait objects in generic defaults are forbidden 32 | --> $DIR/sus_trait_obj_items.rs:22:1 33 | | 34 | LL | / pub enum SusEnum 35 | LL | | where 36 | LL | | T: ?Sized, 37 | LL | | { 38 | LL | | Something(Box), 39 | LL | | } 40 | | |_^ 41 | 42 | error: trait objects in generic defaults are forbidden 43 | --> $DIR/sus_trait_obj_items.rs:29:1 44 | | 45 | LL | / pub union SusUnion 46 | LL | | where 47 | LL | | T: ?Sized, 48 | LL | | { 49 | LL | | pub something: *const T, 50 | LL | | } 51 | | |_^ 52 | 53 | error: aborting due to 5 previous errors 54 | 55 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/sus_trait_obj_transmute.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "lib"] 2 | 3 | trait Object { 4 | type Output; 5 | } 6 | 7 | impl Object for T { 8 | type Output = U; 9 | } 10 | 11 | fn foo(x: >::Output) -> U { 12 | x 13 | } 14 | 15 | pub fn transmute(x: T) -> U { 16 | foo::, U>(x) 17 | } 18 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/sus_trait_obj_transmute.stderr: -------------------------------------------------------------------------------- 1 | error: using trait objects in turbofish position is forbidden by PL/Rust 2 | --> $DIR/sus_trait_obj_transmute.rs:16:5 3 | | 4 | LL | foo::, U>(x) 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = note: `-F plrust-suspicious-trait-object` implied by `-F plrust-lints` 8 | 9 | error: aborting due to previous error 10 | 11 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/sus_trait_object_gat.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "lib"] 2 | 3 | trait Object { 4 | type Output; 5 | } 6 | 7 | impl Object for T { 8 | type Output = &'static u64; 9 | } 10 | 11 | trait HasGenericAssoc { 12 | type Ohno<'a>: ?Sized; 13 | } 14 | 15 | impl HasGenericAssoc for () { 16 | type Ohno<'a> = dyn Object; 17 | } 18 | 19 | fn foo<'a, T: ?Sized>(x: ::Output) -> &'a u64 { 20 | x 21 | } 22 | 23 | fn transmute_lifetime<'a, 'b>(x: &'a u64) -> &'b u64 { 24 | foo::<<() as HasGenericAssoc>::Ohno<'a>>(x) 25 | } 26 | 27 | pub fn get_dangling<'a>() -> &'a u64 { 28 | let x = 0; 29 | transmute_lifetime(&x) 30 | } 31 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/sus_trait_object_gat.stderr: -------------------------------------------------------------------------------- 1 | error: using trait objects in turbofish position is forbidden by PL/Rust 2 | --> $DIR/sus_trait_object_gat.rs:24:5 3 | | 4 | LL | foo::<<() as HasGenericAssoc>::Ohno<'a>>(x) 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = note: `-F plrust-suspicious-trait-object` implied by `-F plrust-lints` 8 | 9 | error: aborting due to previous error 10 | 11 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/tuple_struct_self_pat_box.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "lib"] 2 | 3 | use core::ptr::NonNull; 4 | 5 | trait Bad { 6 | fn bad(&mut self); 7 | } 8 | impl Bad for Box { 9 | fn bad(&mut self) { 10 | let Self(ptr, _) = self; 11 | 12 | fn dangling() -> U 13 | where 14 | U: From>, 15 | { 16 | NonNull::dangling().into() 17 | } 18 | 19 | *ptr = dangling(); 20 | } 21 | } 22 | 23 | fn main() { 24 | let mut foo = Box::new(123); 25 | foo.bad(); 26 | } 27 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/tuple_struct_self_pat_box.stderr: -------------------------------------------------------------------------------- 1 | error[E0603]: tuple struct constructor `std::boxed::Box` is private 2 | --> $DIR/tuple_struct_self_pat_box.rs:10:13 3 | | 4 | LL | let Self(ptr, _) = self; 5 | | ^^^^^^^^^^^^ 6 | 7 | error: aborting due to previous error 8 | 9 | For more information about this error, try `rustc --explain E0603`. 10 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/tuple_struct_self_pat_local_priv.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "lib"] 2 | 3 | mod my { 4 | pub struct Foo(&'static str); 5 | } 6 | impl AsRef for my::Foo { 7 | fn as_ref(&self) -> &str { 8 | let Self(s) = self; 9 | s 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/tuple_struct_self_pat_local_priv.stderr: -------------------------------------------------------------------------------- 1 | error[E0603]: tuple struct constructor `my::Foo` is private 2 | --> $DIR/tuple_struct_self_pat_local_priv.rs:8:13 3 | | 4 | LL | let Self(s) = self; 5 | | ^^^^^^^ 6 | 7 | error: aborting due to previous error 8 | 9 | For more information about this error, try `rustc --explain E0603`. 10 | -------------------------------------------------------------------------------- /plrustc/plrustc/uitests/tuple_struct_self_pat_should_pass.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "lib"] 2 | 3 | pub struct Foo(&'static str); 4 | 5 | impl AsRef for Foo { 6 | fn as_ref(&self) -> &str { 7 | let Self(s) = self; 8 | s 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /plrustc/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.72.0" 3 | components = [ "rustfmt", "rust-src", "rustc-dev", "cargo", "llvm-tools" ] 4 | targets = [ ] 5 | profile = "minimal" 6 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Portions Copyright 2019-2021 ZomboDB, LLC. 3 | # Portions Copyright 2021-2022 Technology Concepts & Design, Inc. 4 | # 5 | # 6 | # All rights reserved. 7 | # 8 | # Use of this source code is governed by the MIT license that can be found in 9 | # the LICENSE file. 10 | 11 | set -x 12 | cd plrust-trusted-pgrx && cargo publish --no-verify 13 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.72.0" 3 | components = [ "rustfmt", "rust-src", "rustc-dev", "llvm-tools" ] 4 | targets = [ ] 5 | profile = "minimal" 6 | -------------------------------------------------------------------------------- /sql/plrust--1.0--1.1.sql: -------------------------------------------------------------------------------- 1 | DROP FUNCTION IF EXISTS plrust.list_allowed_dependencies(); 2 | 3 | -- plrust/src/lib.rs:197 4 | -- plrust::list_allowed_dependencies 5 | CREATE FUNCTION plrust."allowed_dependencies"() RETURNS TABLE ( 6 | "name" TEXT, /* alloc::string::String */ 7 | "version" TEXT, /* alloc::string::String */ 8 | "features" TEXT[], /* alloc::vec::Vec */ 9 | "default_features" bool /* bool */ 10 | ) 11 | STRICT 12 | LANGUAGE c /* Rust */ 13 | AS 'MODULE_PATHNAME', 'allowed_dependencies_wrapper'; 14 | -------------------------------------------------------------------------------- /update-version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Portions Copyright 2019-2021 ZomboDB, LLC. 3 | # Portions Copyright 2021-2022 Technology Concepts & Design, Inc. 4 | # 5 | # 6 | # All rights reserved. 7 | # 8 | # Use of this source code is governed by the MIT license that can be found in 9 | # the LICENSE file. 10 | 11 | ## 12 | ## This script requires `cargo install cargo-workspace-version` from https://crates.io/crates/cargo-workspace-version 13 | ## 14 | 15 | NEW_VERSION=$1 16 | 17 | if [ -z "${NEW_VERSION}" ]; then 18 | echo usage: ./update-version.sh new.version.number 19 | exit 1 20 | fi 21 | 22 | ## update versions 23 | cargo workspace-version update v"${NEW_VERSION}" 24 | cargo generate-lockfile 25 | 26 | cd plrustc 27 | cargo workspace-version update v"${NEW_VERSION}" 28 | cargo generate-lockfile 29 | -------------------------------------------------------------------------------- /upgrade-deps.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # requires: "cargo install cargo-edit" from https://github.com/killercup/cargo-edit 4 | 5 | cargo update 6 | 7 | # generally speaking, the only pinned dependency we use is pgrx, and generally speaking the only time we run this script 8 | # is when we want to upgrade to a newer pgrx. `--pinned` has entered the chat 9 | cargo upgrade --pinned --incompatible 10 | cargo generate-lockfile 11 | 12 | cd plrustc 13 | cargo upgrade --incompatible 14 | cargo generate-lockfile 15 | --------------------------------------------------------------------------------