External Dependencies

PL/Rust supports the use of external dependencies. By default, this is unrestricted even when PL/Rust is used as a Trusted Language Handler, allowing user functions to specify any desired dependency.

For instance:

CREATE OR REPLACE FUNCTION randint() RETURNS bigint LANGUAGE plrust AS $$
[dependencies]
rand = "0.8"

[code]
use rand::Rng; 
Ok(Some(rand::thread_rng().gen())) 
$$;

It is recommended that administrators create a dependency allow-list file and specify its path in postgresql.conf using the plrust.allowed_dependencies setting.

To disable external dependencies completely, create a zero-byte file or point the configuration to /dev/null.

The Allow-List File

The dependency allow-list is a TOML file. Its format mirrors that of the [dependencies] section in a standard Cargo.toml, albeit with certain requirements on the version strings.

The Format

The file consists of dependency_name = version_requirement pairs, where version_requirement can adopt several forms. It can be a quoted string such as "=1.2.3", a TOML table like { version = "=1.2.3", features = ["a", "b", "c"] }, or an array of either, such as [ "=1.2.3", { version = "=1.2.3" }, ">=4, <5".

Here is a valid allow-list file for reference:

rand = ">=0.8, <0.9"
bitvec = [">=1, <2", "=0.2", { version = "=1.0.1", features = [ "alloc" ], default-features = false }]

This added flexibility empowers administrators to specify the exact crate version and its associated features and properties.

When a LANGUAGE plrust function designates a dependency and version, the largest (presumably most recent) matching version from the allow-list is used.

Version Requirement Format

PL/Rust employs Cargo's interpretation of semver to manage dependency versions, but it requires each version requirement to be an exact value like =1.2.3, a bounded range such as >=1, <2, or a bare wildcard (*).

For example, these are valid version requirement values:

rand = "=0.8.5"
serde = ">=1.0.151, <1.1"
bitvec = "*"

These, however, are not:

rand = "0.8.5"
serde = ">1.1"

The cargo tool may select a slightly different version based on the specification. However, with exact and bounded values, cargo's choices are limited to the versions that administrators allow.

The bare wildcard pattern (*) is acceptable and has a unique interpretation within a user LANGUAGE plrust function.

Using a Dependency

As shown above, a LANGUAGE plrust function can include a [dependencies] section. Authors should specify exact versions for each dependency. PL/Rust will match this exact version with an entry in the allow-list.

If a function requests a version in the 1.2.3 format and it matches an entry on the allow-list, PL/Rust will revise it to an exact version, i.e., =1.2.3.

If the allow-list merely contains a wildcard version:

rand = "*"

... and the user function asks for a specific version, such as 0.8.5, PL/Rust will utilize that exact version.

Conversely, if the allow-list specifies one or more particular version requirements...

rand = [ "0.8.5", "0.6" ]

... and the PL/Rust function requests a wildcard (i.e., rand = "*"), PL/Rust will select the largest version requirement from the allow-list. In this case, it would be 0.8.5.

Working with Crate Features

When a user function employs a crate from the allow-list, the allow-list controls the permitted set of dependency properties such as features and default-features for each version. Users cannot override these. They can specify them, but the specifications must match exactly with the allow-list.

This control enables administrators to dictate the usage of dependencies.

For instance, this would be acceptable for a user function:

CREATE OR REPLACE FUNCTION randint(seed bigint) RETURNS bigint STRICT LANGUAGE plrust AS $$
[dependencies]
rand = { version = "*", features = [ "small_rng" ], default-features = false }

[code]
use rand::rngs::SmallRng;
use rand::SeedableRng;
use rand::RngCore;

let mut rng = SmallRng::seed_from_u64(seed as _);
Ok(Some(rng.next_u64() as _))
$$;

Provided that the allow-list includes the following:

rand = { version = "=0.8.5", features = [ "small_rng" ], default-features = false }

Note that the user function could omit the dependency features since the allow-list declares them:

CREATE OR REPLACE FUNCTION randint(seed bigint) RETURNS bigint STRICT LANGUAGE plrust AS $$
[dependencies]
rand = "*"

[code]
use rand::rngs::SmallRng;
use rand::SeedableRng;
use rand::RngCore;

let mut rng = SmallRng::seed_from_u64(seed as _);
Ok(Some(rng.next_u64() as _))
$$;

PL/Rust provides a function plrust.allowed_dependencies which lists all the allowlisted crates with their respective enabled features. For example, with an allowlist as follows:

rand = ">=0.8, <0.9"
bitvec = [">=1, <2", "=0.2", { version = "=1.0.1", features = [ "alloc" ], default-features = false }]

The result of plrust.allowed_dependencies would be:

SELECT * FROM plrust.allowed_dependencies();
  name  |   version   | features | default_features
--------+-------------+----------+------------------
 bitvec | =0.2        | {}       | t
 bitvec | >=1, <2     | {}       | t
 bitvec | =1.0.1      | {alloc}  | f
 rand   | >=0.8, <0.9 | {}       | t
(4 rows)

Operational Notes

  • The dependency allow-list file path must be set in plrust.allowed_dependencies GUC value in postgresql.conf.
  • Changing the GUC value requires a configuration reload on the database to take effect.
  • The file must be readable by the user that runs Postgres backend connections. Typically, this user is named postgres.
  • Every time a CREATE FUNCTION ... LANGUAGE plrust statement is executed, the file is read, parsed, and validated. This arrangement allows administrators to edit it without needing to restart the Postgres cluster.