From 75aba1fc51539a1de2f1435c00d308fd7659cbd8 Mon Sep 17 00:00:00 2001 From: "Eric B. Ridge" Date: Thu, 15 Jun 2023 14:11:21 -0400 Subject: [PATCH 01/32] bootstrap a `plrust-tests` create The new `plrust-tests` create is a standalone pgrx crate that only contains the unit tests from `plrust`. These were copied over verbatim. To run the tests: ```shell $ cd plrust-tests $ ./run-tests.sh pgXX [test_name] ``` --- Cargo.lock | 10 + Cargo.toml | 1 + plrust-tests/Cargo.toml | 24 + plrust-tests/plrust_tests.control | 6 + plrust-tests/run-tests.sh | 28 + plrust-tests/src/lib.rs | 1449 +++++++++++++++++++++++++++++ 6 files changed, 1518 insertions(+) create mode 100644 plrust-tests/Cargo.toml create mode 100644 plrust-tests/plrust_tests.control create mode 100755 plrust-tests/run-tests.sh create mode 100644 plrust-tests/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index c027a948..d780ea69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1182,6 +1182,16 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "plrust-tests" +version = "0.0.0" +dependencies = [ + "once_cell", + "pgrx", + "pgrx-tests", + "tempdir", +] + [[package]] name = "plrust-trusted-pgrx" version = "1.2.3" diff --git a/Cargo.toml b/Cargo.toml index 688c9e95..4bcb26a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ "plrust", "plrust-trusted-pgrx", + "plrust-tests", ] exclude = ["plrustc"]#, "builder"] diff --git a/plrust-tests/Cargo.toml b/plrust-tests/Cargo.toml new file mode 100644 index 00000000..69f8deec --- /dev/null +++ b/plrust-tests/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "plrust-tests" +version = "0.0.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[features] +default = ["pg13"] +pg13 = ["pgrx/pg13", "pgrx-tests/pg13" ] +pg14 = ["pgrx/pg14", "pgrx-tests/pg14" ] +pg15 = ["pgrx/pg15", "pgrx-tests/pg15" ] +pg_test = [] + +[dependencies] +pgrx = "=0.9.5" +tempdir = "0.3.7" +once_cell = "1.18.0" + +[dev-dependencies] +pgrx-tests = "=0.9.5" +tempdir = "0.3.7" +once_cell = "1.18.0" diff --git a/plrust-tests/plrust_tests.control b/plrust-tests/plrust_tests.control new file mode 100644 index 00000000..48a73b33 --- /dev/null +++ b/plrust-tests/plrust_tests.control @@ -0,0 +1,6 @@ +comment = 'plrust_tests: Created by pgrx' +default_version = '@CARGO_VERSION@' +module_pathname = '$libdir/plrust_tests' +relocatable = false +superuser = true +requires = 'plrust' diff --git a/plrust-tests/run-tests.sh b/plrust-tests/run-tests.sh new file mode 100755 index 00000000..e7dca515 --- /dev/null +++ b/plrust-tests/run-tests.sh @@ -0,0 +1,28 @@ +#! /bin/bash + +VERSION=$1 + +if [ -z ${VERSION} ]; then + echo "usage: ./run-tests.sh pgXX [test-name]" + exit 1 +fi + +TEST_DIR=`pwd` + +# install the plrust extension into the pgrx-managed postgres +echo "============================" +echo " installing plrust" +echo +cd ../plrust +echo "\q" | cargo pgrx run ${VERSION} + +# run the test suite from this crate +cd ${TEST_DIR} + +echo +echo "============================" +echo " running plrust-tests suite" +echo + +cargo pgrx test ${VERSION} $2 + diff --git a/plrust-tests/src/lib.rs b/plrust-tests/src/lib.rs new file mode 100644 index 00000000..5e58e4f1 --- /dev/null +++ b/plrust-tests/src/lib.rs @@ -0,0 +1,1449 @@ +use pgrx::prelude::*; + +pgrx::pg_module_magic!(); + +/* +Portions Copyright 2020-2021 ZomboDB, LLC. +Portions Copyright 2021-2023 Technology Concepts & Design, Inc. + +All rights reserved. + +Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. +*/ + +#[cfg(any(test, feature = "pg_test"))] +#[pgrx::pg_schema] +mod tests { + use pgrx::{datum::IntoDatum, prelude::*}; + use std::error::Error; + + // Bootstrap a testing table for non-immutable functions + extension_sql!( + r#" + CREATE TABLE contributors_pets ( + id serial8 not null primary key, + name text + ); + INSERT INTO contributors_pets (name) VALUES ('Brandy'); + INSERT INTO contributors_pets (name) VALUES ('Nami'); + INSERT INTO contributors_pets (name) VALUES ('Sally'); + INSERT INTO contributors_pets (name) VALUES ('Anchovy'); + "#, + name = "create_contributors_pets", + ); + + #[pg_test] + #[search_path(@extschema@)] + fn plrust_basic() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION sum_array(a BIGINT[]) RETURNS BIGINT + IMMUTABLE STRICT + LANGUAGE PLRUST AS + $$ + Ok(Some(a.into_iter().map(|v| v.unwrap_or_default()).sum())) + $$; + "#; + Spi::run(definition)?; + + let retval = Spi::get_one_with_args::( + r#" + SELECT sum_array($1); + "#, + vec![( + PgBuiltInOids::INT4ARRAYOID.oid(), + vec![1, 2, 3].into_datum(), + )], + ); + assert_eq!(retval, Ok(Some(6))); + Ok(()) + } + + #[pg_test] + #[search_path(@extschema@)] + fn plrust_update() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION update_me() RETURNS TEXT + IMMUTABLE STRICT + LANGUAGE PLRUST AS + $$ + Ok(String::from("booper").into()) + $$; + "#; + Spi::run(definition)?; + + let retval = Spi::get_one( + r#" + SELECT update_me(); + "#, + ); + assert_eq!(retval, Ok(Some("booper"))); + + let definition = r#" + CREATE OR REPLACE FUNCTION update_me() RETURNS TEXT + IMMUTABLE STRICT + LANGUAGE PLRUST AS + $$ + Ok(Some(String::from("swooper"))) + $$; + "#; + Spi::run(definition)?; + + let retval = Spi::get_one( + r#" + SELECT update_me(); + "#, + ); + assert_eq!(retval, Ok(Some("swooper"))); + Ok(()) + } + + #[pg_test] + #[search_path(@extschema@)] + fn plrust_spi() -> spi::Result<()> { + let random_definition = r#" + CREATE FUNCTION random_contributor_pet() RETURNS TEXT + STRICT + LANGUAGE PLRUST AS + $$ + Ok(Spi::get_one("SELECT name FROM contributors_pets ORDER BY random() LIMIT 1")?) + $$; + "#; + Spi::run(random_definition)?; + + let retval = Spi::get_one::( + r#" + SELECT random_contributor_pet(); + "#, + ); + assert!(retval.is_ok()); + assert!(retval.unwrap().is_some()); + + let specific_definition = r#" + CREATE FUNCTION contributor_pet(name TEXT) RETURNS BIGINT + STRICT + LANGUAGE PLRUST AS + $$ + use pgrx::IntoDatum; + Ok(Spi::get_one_with_args( + "SELECT id FROM contributors_pets WHERE name = $1", + vec![(PgBuiltInOids::TEXTOID.oid(), name.into_datum())], + )?) + $$; + "#; + Spi::run(specific_definition)?; + + let retval = Spi::get_one::( + r#" + SELECT contributor_pet('Nami'); + "#, + ); + assert_eq!(retval, Ok(Some(2))); + Ok(()) + } + + #[pg_test] + #[cfg(not(feature = "sandboxed"))] + #[search_path(@extschema@)] + fn plrust_deps_supported() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION colorize(input TEXT) RETURNS TEXT + IMMUTABLE STRICT + LANGUAGE PLRUST AS + $$ + [dependencies] + owo-colors = "3" + [code] + use owo_colors::OwoColorize; + Ok(Some(input.purple().to_string())) + $$; + "#; + Spi::run(definition)?; + + let retval = Spi::get_one_with_args::( + r#" + SELECT colorize($1); + "#, + vec![(PgBuiltInOids::TEXTOID.oid(), "Nami".into_datum())], + ); + assert!(retval.is_ok()); + assert!(retval.unwrap().is_some()); + + // Regression test: A previous version of PL/Rust would abort if this was called twice, so call it twice: + let retval = Spi::get_one_with_args::( + r#" + SELECT colorize($1); + "#, + vec![(PgBuiltInOids::TEXTOID.oid(), "Nami".into_datum())], + ); + assert!(retval.is_ok()); + assert!(retval.unwrap().is_some()); + Ok(()) + } + + #[pg_test] + #[cfg(not(feature = "sandboxed"))] + #[search_path(@extschema@)] + fn plrust_deps_supported_semver_parse() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION colorize(input TEXT) RETURNS TEXT + IMMUTABLE STRICT + LANGUAGE PLRUST AS + $$ + [dependencies] + owo-colors = ">2" + [code] + use owo_colors::OwoColorize; + Ok(Some(input.purple().to_string())) + $$; + "#; + Spi::run(definition)?; + + let retval = Spi::get_one_with_args::( + r#" + SELECT colorize($1); + "#, + vec![(PgBuiltInOids::TEXTOID.oid(), "Nami".into_datum())], + ); + assert!(retval.is_ok()); + assert!(retval.unwrap().is_some()); + + // Regression test: A previous version of PL/Rust would abort if this was called twice, so call it twice: + let retval = Spi::get_one_with_args::( + r#" + SELECT colorize($1); + "#, + vec![(PgBuiltInOids::TEXTOID.oid(), "Nami".into_datum())], + ); + assert!(retval.is_ok()); + assert!(retval.unwrap().is_some()); + Ok(()) + } + + #[pg_test] + #[cfg(not(feature = "sandboxed"))] + #[search_path(@extschema@)] + fn plrust_deps_supported_deps_in_toml_table() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION say_hello() RETURNS TEXT + IMMUTABLE STRICT + LANGUAGE PLRUST AS + $$ + [dependencies] + tokio = ">=1" + owo-colors = "3" + [code] + Ok(Some("hello".to_string())) + $$; + "#; + Spi::run(definition)?; + + let retval = Spi::get_one_with_args::( + r#" + SELECT say_hello(); + "#, + vec![(PgBuiltInOids::TEXTOID.oid(), "hello".into_datum())], + ); + assert_eq!(retval, Ok(Some("hello".to_string()))); + Ok(()) + } + + #[pg_test] + #[cfg(not(feature = "sandboxed"))] + #[search_path(@extschema@)] + fn plrust_deps_not_supported() { + let definition = r#" + CREATE FUNCTION colorize(input TEXT) RETURNS TEXT + IMMUTABLE STRICT + LANGUAGE PLRUST AS + $$ + [dependencies] + regex = "1.6.5" + [code] + Ok(Some("test")) + $$; + "#; + let res = std::panic::catch_unwind(|| { + Spi::run(definition).expect("SQL for plrust_deps_not_supported() failed") + }); + assert!(res.is_err()); + } + + #[pg_test] + #[search_path(@extschema@)] + fn plrust_returns_setof() -> spi::Result<()> { + let definition = r#" + CREATE OR REPLACE FUNCTION boop_srf(names TEXT[]) RETURNS SETOF TEXT + IMMUTABLE STRICT + LANGUAGE PLRUST AS + $$ + Ok(Some(::pgrx::iter::SetOfIterator::new(names.into_iter().map(|maybe| maybe.map(|name| name.to_string() + " was booped!"))))) + $$; + "#; + Spi::run(definition)?; + + let retval: spi::Result<_> = Spi::connect(|client| { + let mut table = client.select( + "SELECT * FROM boop_srf(ARRAY['Nami', 'Brandy'])", + None, + None, + )?; + + let mut found = vec![]; + while table.next().is_some() { + let value = table.get_one::()?; + found.push(value) + } + + Ok(Some(found)) + }); + + assert_eq!( + retval, + Ok(Some(vec![ + Some("Nami was booped!".into()), + Some("Brandy was booped!".into()), + ])) + ); + Ok(()) + } + + #[pg_test] + #[search_path(@extschema@)] + fn plrust_aggregate() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION plrust_sum_state(state INT, next INT) RETURNS INT + IMMUTABLE STRICT + LANGUAGE PLRUST AS + $$ + Ok(Some(state + next)) + $$; + CREATE AGGREGATE plrust_sum(INT) + ( + SFUNC = plrust_sum_state, + STYPE = INT, + INITCOND = '0' + ); + "#; + Spi::run(definition)?; + + let retval = Spi::get_one::( + r#" + SELECT plrust_sum(value) FROM UNNEST(ARRAY [1, 2, 3]) as value; + "#, + ); + assert_eq!(retval, Ok(Some(6))); + Ok(()) + } + + #[pg_test] + #[search_path(@extschema@)] + fn plrust_trigger() -> spi::Result<()> { + let definition = r#" + CREATE TABLE dogs ( + name TEXT, + scritches INT NOT NULL DEFAULT 0 + ); + + CREATE FUNCTION pet_trigger() RETURNS trigger AS $$ + let mut new = trigger.new().unwrap().into_owned(); + + let field = "scritches"; + + match new.get_by_name::(field)? { + Some(val) => new.set_by_name(field, val + 1)?, + None => (), + } + + Ok(Some(new)) + $$ LANGUAGE plrust; + + CREATE TRIGGER pet_trigger BEFORE INSERT OR UPDATE ON dogs + FOR EACH ROW EXECUTE FUNCTION pet_trigger(); + + INSERT INTO dogs (name) VALUES ('Nami'); + "#; + Spi::run(definition)?; + + let retval = Spi::get_one::( + r#" + SELECT scritches FROM dogs; + "#, + ); + assert_eq!(retval, Ok(Some(1))); + Ok(()) + } + + #[cfg(feature = "trusted")] + #[pg_test] + #[search_path(@extschema@)] + fn postgrestd_dont_make_files() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION make_file(filename TEXT) RETURNS TEXT + LANGUAGE PLRUST AS + $$ + Ok(std::fs::File::create(filename.unwrap_or("/somewhere/files/dont/belong.txt")) + .err() + .map(|e| e.to_string())) + $$; + "#; + Spi::run(definition)?; + + let retval = Spi::get_one_with_args::( + r#" + SELECT make_file($1); + "#, + vec![( + PgBuiltInOids::TEXTOID.oid(), + "/an/evil/place/to/put/a/file.txt".into_datum(), + )], + ); + assert_eq!( + retval, + Ok(Some("operation not supported on this platform".to_string())) + ); + Ok(()) + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic = "yup"] + fn pgrx_can_panic() { + panic!("yup") + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic = "yup"] + fn plrust_can_panic() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION shut_up_and_explode() + RETURNS text AS + $$ + panic!("yup"); + Ok(None) + $$ LANGUAGE plrust; + "#; + + Spi::run(definition)?; + let retval = Spi::get_one::("SELECT shut_up_and_explode();\n"); + assert_eq!(retval, Ok(None)); + Ok(()) + } + + #[cfg(feature = "trusted")] + #[pg_test] + #[search_path(@extschema@)] + #[should_panic = "Failed to execute command"] + fn postgrestd_subprocesses_panic() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION say_hello() + RETURNS text AS + $$ + let out = std::process::Command::new("echo") + .arg("Hello world") + .stdout(std::process::Stdio::piped()) + .output() + .expect("Failed to execute command"); + Ok(Some(String::from_utf8_lossy(&out.stdout).to_string())) + $$ LANGUAGE plrust; + "#; + Spi::run(definition)?; + + let retval = Spi::get_one::("SELECT say_hello();\n"); + assert_eq!(retval, Ok(Some("Hello world\n".into()))); + Ok(()) + } + + #[cfg(feature = "trusted")] + #[pg_test] + #[search_path(@extschema@)] + #[should_panic = "error: the `include_str`, `include_bytes`, and `include` macros are forbidden"] + fn postgrestd_no_include_str() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION include_str() + RETURNS text AS + $$ + let s = include_str!("/etc/passwd"); + Ok(Some(s.into())) + $$ LANGUAGE plrust; + "#; + Spi::run(definition)?; + + let retval = Spi::get_one::("SELECT include_str();\n")?; + assert_eq!(retval.unwrap(), ""); + Ok(()) + } + + #[pg_test] + #[search_path(@extschema@)] + #[cfg(feature = "trusted")] + #[should_panic = "No such file or directory (os error 2)"] + fn plrustc_include_exists_no_access() { + // This file is created in CI and exists, but can only be accessed by + // root. Check that the actual access is reported as file not found (we + // should be ensuring that via + // `PLRUSTC_USER_CRATE_ALLOWED_SOURCE_PATHS`). We don't need to gate + // this test on CI, since the file is unlikely to exist outside of CI + // (so the test will pass). + let definition = r#" + CREATE FUNCTION include_no_access() + RETURNS text AS $$ + include!("/var/ci-stuff/secret_rust_files/const_foo.rs"); + Ok(Some(format!("{BAR}"))) + $$ LANGUAGE plrust; + "#; + Spi::run(definition).unwrap() + } + + #[pg_test] + #[search_path(@extschema@)] + #[cfg(feature = "trusted")] + #[should_panic = "No such file or directory (os error 2)"] + fn plrustc_include_exists_external() { + // This file is created in CI, exists, and can be accessed by anybody, + // but the actual access is forbidden via + // `PLRUSTC_USER_CRATE_ALLOWED_SOURCE_PATHS`. We don't need to gate this test on + // CI, since the file is unlikely to exist outside of CI, so the test + // will pass anyway. + let definition = r#" + CREATE FUNCTION include_exists_external() + RETURNS text AS $$ + include!("/var/ci-stuff/const_bar.rs"); + Ok(Some(format!("{BAR}"))) + $$ LANGUAGE plrust; + "#; + Spi::run(definition).unwrap(); + } + + #[pg_test] + #[search_path(@extschema@)] + #[cfg(feature = "trusted")] + #[should_panic = "No such file or directory (os error 2)"] + fn plrustc_include_made_up() { + // This file does not exist, and should be reported as such. + let definition = r#" + CREATE FUNCTION include_madeup() + RETURNS int AS $$ + include!("/made/up/path/lol.rs"); + Ok(Some(1)) + $$ LANGUAGE plrust; + "#; + Spi::run(definition).unwrap(); + } + + #[pg_test] + #[search_path(@extschema@)] + #[cfg(feature = "trusted")] + #[should_panic = "No such file or directory (os error 2)"] + fn plrustc_include_path_traversal() { + use std::path::PathBuf; + let workdir = crate::gucs::work_dir(); + let wd: PathBuf = workdir + .canonicalize() + .ok() + .expect("Failed to canonicalize workdir"); + // Produce a path that looks like + // `/allowed/path/here/../../../illegal/path/here` and check that it's + // rejected, in order to ensure we are not succeptable to path traversal + // attacks. + let mut evil_path = wd.clone(); + for _ in wd.ancestors().skip(1) { + evil_path.push(".."); + } + debug_assert_eq!( + evil_path + .canonicalize() + .ok() + .expect("Failed to produce unpath") + .to_str(), + Some("/") + ); + evil_path.push("var/ci-stuff/const_bar.rs"); + // This file does not exist, and should be reported as such. + let definition = format!( + r#"CREATE FUNCTION include_sneaky_traversal() + RETURNS int AS $$ + include!({evil_path:?}); + Ok(Some(1)) + $$ LANGUAGE plrust;"# + ); + Spi::run(&definition).unwrap(); + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic = "error: usage of an `unsafe` block"] + fn plrust_block_unsafe_annotated() -> spi::Result<()> { + // PL/Rust should block creating obvious, correctly-annotated usage of unsafe code + let definition = r#" + CREATE FUNCTION naughty() + RETURNS text AS + $$ + use std::{os::raw as ffi, str, ffi::CStr}; + let int:u32 = 0xDEADBEEF; + // Note that it is always safe to create a pointer. + let ptr = int as *mut u64; + // What is unsafe is dereferencing it + let cstr = unsafe { + ptr.write(0x00_1BADC0DE_00); + CStr::from_ptr(ptr.cast::()) + }; + Ok(str::from_utf8(cstr.to_bytes()).ok().map(|s| s.to_owned())) + $$ LANGUAGE plrust; + "#; + Spi::run(definition) + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic = "call to unsafe function is unsafe and requires unsafe block"] + fn plrust_block_unsafe_hidden() -> spi::Result<()> { + // PL/Rust should not allow hidden injection of unsafe code + // that may rely on the way PGRX expands into `unsafe fn` to "sneak in" + let definition = r#" + CREATE FUNCTION naughty() + RETURNS text AS + $$ + use std::{os::raw as ffi, str, ffi::CStr}; + let int:u32 = 0xDEADBEEF; + let ptr = int as *mut u64; + ptr.write(0x00_1BADC0DE_00); + let cstr = CStr::from_ptr(ptr.cast::()); + Ok(str::from_utf8(cstr.to_bytes()).ok().map(|s| s.to_owned())) + $$ LANGUAGE plrust; + "#; + Spi::run(definition) + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic = "error: the `env` and `option_env` macros are forbidden"] + #[cfg(feature = "trusted")] + fn plrust_block_env() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION get_path() RETURNS text AS $$ + let path = env!("PATH"); + Ok(Some(path.to_string())) + $$ LANGUAGE plrust; + "#; + Spi::run(definition) + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic = "error: the `env` and `option_env` macros are forbidden"] + #[cfg(feature = "trusted")] + fn plrust_block_option_env() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION try_get_path() RETURNS text AS $$ + match option_env!("PATH") { + None => Ok(None), + Some(s) => Ok(Some(s.to_string())) + } + $$ LANGUAGE plrust; + "#; + Spi::run(definition) + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic = "error: usage of an `unsafe` block"] + fn plrust_block_unsafe_plutonium() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION super_safe() + RETURNS text AS + $$ + [dependencies] + plutonium = "*" + + [code] + use std::{os::raw as ffi, str, ffi::CStr}; + use plutonium::safe; + + #[safe] + fn super_safe() -> Option { + let int: u32 = 0xDEADBEEF; + let ptr = int as *mut u64; + ptr.write(0x00_1BADC0DE_00); + let cstr = CStr::from_ptr(ptr.cast::()); + str::from_utf8(cstr.to_bytes()).ok().map(|s| s.to_owned()) + } + + Ok(super_safe()) + $$ LANGUAGE plrust; + "#; + Spi::run(definition) + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic = "xxx"] + #[ignore] + fn plrust_pgloglevel_dont_allcaps_panic() -> spi::Result<()> { + // This test attempts to annihilate the database. + // It relies on the existing assumption that tests are run in the same Postgres instance, + // so this test will make all tests "flaky" if Postgres suddenly goes down with it. + let definition = r#" + CREATE FUNCTION dont_allcaps_panic() + RETURNS text AS + $$ + ereport!(PANIC, PgSqlErrorCode::ERRCODE_INTERNAL_ERROR, "If other tests completed, PL/Rust did not actually destroy the entire database, \ + But if you see this in the error output, something might be wrong."); + Ok(Some("lol".into())) + $$ LANGUAGE plrust; + "#; + Spi::run(definition)?; + let retval = Spi::get_one::("SELECT dont_allcaps_panic();\n"); + assert_eq!(retval, Ok(Some("lol".into()))); + Ok(()) + } + + #[pg_test] + #[search_path(@extschema@)] + fn plrust_call_1st() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION ret_1st(a int, b int) + RETURNS int AS + $$ + Ok(a) + $$ LANGUAGE plrust; + "#; + Spi::run(definition)?; + let result_1 = Spi::get_one::("SELECT ret_1st(1, 2);\n"); + assert_eq!(Ok(Some(1)), result_1); // may get: Some(1) + Ok(()) + } + + #[pg_test] + #[search_path(@extschema@)] + fn plrust_call_2nd() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION ret_2nd(a int, b int) + RETURNS int AS + $$ + Ok(b) + $$ LANGUAGE plrust; + "#; + Spi::run(definition)?; + let result_2 = Spi::get_one::("SELECT ret_2nd(1, 2);\n"); + assert_eq!(Ok(Some(2)), result_2); // may get: Some(2) + Ok(()) + } + + #[pg_test] + #[search_path(@extschema@)] + fn plrust_call_me() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION pick_ret(a int, b int, pick int) + RETURNS int AS + $$ + Ok(match pick { + Some(0) => a, + Some(1) => b, + _ => None, + }) + $$ LANGUAGE plrust; + "#; + Spi::run(definition)?; + let result_a = Spi::get_one::("SELECT pick_ret(3, 4, 0);"); + let result_b = Spi::get_one::("SELECT pick_ret(5, 6, 1);"); + let result_c = Spi::get_one::("SELECT pick_ret(7, 8, 2);"); + let result_z = Spi::get_one::("SELECT pick_ret(9, 99, -1);"); + assert_eq!(Ok(Some(3)), result_a); // may get: Some(4) or None + assert_eq!(Ok(Some(6)), result_b); // may get: None + assert_eq!(Ok(None), result_c); + assert_eq!(Ok(None), result_z); + Ok(()) + } + + #[pg_test] + #[search_path(@extschema@)] + fn plrust_call_me_call_me() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION ret_1st(a int, b int) + RETURNS int AS + $$ + Ok(a) + $$ LANGUAGE plrust; + + CREATE FUNCTION ret_2nd(a int, b int) + RETURNS int AS + $$ + Ok(b) + $$ LANGUAGE plrust; + + CREATE FUNCTION pick_ret(a int, b int, pick int) + RETURNS int AS + $$ + Ok(match pick { + Some(0) => a, + Some(1) => b, + _ => None, + }) + $$ LANGUAGE plrust; + "#; + Spi::run(definition)?; + let result_1 = Spi::get_one::("SELECT ret_1st(1, 2);\n"); + let result_2 = Spi::get_one::("SELECT ret_2nd(1, 2);\n"); + let result_a = Spi::get_one::("SELECT pick_ret(3, 4, 0);"); + let result_b = Spi::get_one::("SELECT pick_ret(5, 6, 1);"); + let result_c = Spi::get_one::("SELECT pick_ret(7, 8, 2);"); + let result_z = Spi::get_one::("SELECT pick_ret(9, 99, -1);"); + assert_eq!(Ok(None), result_z); + assert_eq!(Ok(None), result_c); + assert_eq!(Ok(Some(6)), result_b); // may get: None + assert_eq!(Ok(Some(3)), result_a); // may get: Some(4) or None + assert_eq!(Ok(Some(2)), result_2); // may get: Some(1) + assert_eq!(Ok(Some(1)), result_1); // may get: Some(2) + Ok(()) + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic = "parameter name \"a\" used more than once"] + fn plrust_dup_args() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION not_unique(a int, a int) + RETURNS int AS + $$ + Ok(a) + $$ LANGUAGE plrust; + "#; + Spi::run(definition)?; + let result = Spi::get_one::("SELECT not_unique(1, 2);\n"); + assert_eq!(Ok(Some(1)), result); + Ok(()) + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic = "PL/Rust does not support unnamed arguments"] + fn plrust_defaulting_dup_args() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION not_unique(int, arg0 int) + RETURNS int AS + $$ + Ok(arg0) + $$ LANGUAGE plrust; + "#; + Spi::run(definition)?; + let result = Spi::get_one::("SELECT not_unique(1, 2);\n"); + assert_eq!(Ok(Some(1)), result); + Ok(()) + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic = "plrust functions cannot have their STRICT property altered"] + fn plrust_cant_change_strict_off() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION cant_change_strict_off() + RETURNS int + LANGUAGE plrust + AS $$ Ok(Some(1)) $$; + "#; + Spi::run(definition)?; + Spi::run("ALTER FUNCTION cant_change_strict_off() CALLED ON NULL INPUT") + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic = "plrust functions cannot have their STRICT property altered"] + fn plrust_cant_change_strict_on() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION cant_change_strict_on() + RETURNS int + LANGUAGE plrust + AS $$ Ok(Some(1)) $$; + "#; + Spi::run(definition)?; + Spi::run("ALTER FUNCTION cant_change_strict_on() RETURNS NULL ON NULL INPUT") + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic(expected = "error: declaration of a function with `export_name`")] + fn plrust_block_unsafe_export_name() -> spi::Result<()> { + // A separate test covers #[no_mangle], but what about #[export_name]? + // Same idea. This tries to collide with free, which may symbol clash, + // or might override depending on how the OS and loader feel today. + // Let's not leave it up to forces beyond our control. + let definition = r#" + CREATE OR REPLACE FUNCTION export_hacked_free() RETURNS BIGINT + IMMUTABLE STRICT + LANGUAGE PLRUST AS + $$ + #[export_name = "free"] + pub extern "C" fn own_free(ptr: *mut c_void) { + // the contents don't matter + } + + Ok(Some(1)) + $$; + "#; + Spi::run(definition)?; + let result = Spi::get_one::("SELECT export_hacked_free();\n"); + assert_eq!(Ok(Some(1)), result); + Ok(()) + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic(expected = "error: declaration of a static with `link_section`")] + fn plrust_block_unsafe_link_section() -> spi::Result<()> { + let definition = r#" + CREATE OR REPLACE FUNCTION link_evil_section() RETURNS BIGINT + IMMUTABLE STRICT + LANGUAGE PLRUST AS + $$ + #[link_section = ".init_array"] + pub static INITIALIZE: &[u8; 136] = &GOGO; + + #[link_section = ".text"] + pub static GOGO: [u8; 136] = [ + 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 46, 99, 104, 111, 46, 114, 105, 1, 72, 49, 4, + 36, 72, 137, 231, 106, 1, 254, 12, 36, 72, 184, 99, 102, 105, 108, 101, 49, 50, 51, 80, 72, + 184, 114, 47, 116, 109, 112, 47, 112, 111, 80, 72, 184, 111, 117, 99, 104, 32, 47, 118, 97, + 80, 72, 184, 115, 114, 47, 98, 105, 110, 47, 116, 80, 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, + 72, 184, 114, 105, 1, 44, 98, 1, 46, 116, 72, 49, 4, 36, 49, 246, 86, 106, 14, 94, 72, 1, + 230, 86, 106, 19, 94, 72, 1, 230, 86, 106, 24, 94, 72, 1, 230, 86, 72, 137, 230, 49, 210, + 106, 59, 88, 15, 5, + ]; + + Ok(Some(1)) + $$; + "#; + Spi::run(definition)?; + let result = Spi::get_one::("SELECT link_evil_section();\n"); + assert_eq!(Ok(Some(1)), result); + Ok(()) + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic(expected = "error: declaration of a `no_mangle` static")] + fn plrust_block_unsafe_no_mangle() -> spi::Result<()> { + let definition = r#" + CREATE OR REPLACE FUNCTION not_mangled() RETURNS BIGINT + IMMUTABLE STRICT + LANGUAGE PLRUST AS + $$ + #[no_mangle] + #[link_section = ".init_array"] + pub static INITIALIZE: &[u8; 136] = &GOGO; + + #[no_mangle] + #[link_section = ".text"] + pub static GOGO: [u8; 136] = [ + 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 46, 99, 104, 111, 46, 114, 105, 1, 72, 49, 4, + 36, 72, 137, 231, 106, 1, 254, 12, 36, 72, 184, 99, 102, 105, 108, 101, 49, 50, 51, 80, 72, + 184, 114, 47, 116, 109, 112, 47, 112, 111, 80, 72, 184, 111, 117, 99, 104, 32, 47, 118, 97, + 80, 72, 184, 115, 114, 47, 98, 105, 110, 47, 116, 80, 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, + 72, 184, 114, 105, 1, 44, 98, 1, 46, 116, 72, 49, 4, 36, 49, 246, 86, 106, 14, 94, 72, 1, + 230, 86, 106, 19, 94, 72, 1, 230, 86, 106, 24, 94, 72, 1, 230, 86, 72, 137, 230, 49, 210, + 106, 59, 88, 15, 5, + ]; + + Ok(Some(1)) + $$; + "#; + Spi::run(definition)?; + let result = Spi::get_one::("SELECT not_mangled();\n"); + assert_eq!(Ok(Some(1)), result); + Ok(()) + } + + #[pg_test] + #[should_panic(expected = "issue78 works")] + fn test_issue_78() -> spi::Result<()> { + let sql = r#"CREATE OR REPLACE FUNCTION raise_error() RETURNS TEXT + IMMUTABLE STRICT + LANGUAGE PLRUST AS + $$ + pgrx::error!("issue78 works"); + Ok(Some("hi".to_string())) + $$;"#; + Spi::run(sql)?; + Spi::get_one::("SELECT raise_error()")?; + Ok(()) + } + + #[pg_test] + fn test_issue_79() -> spi::Result<()> { + let sql = r#" + create or replace function fn1(i int) returns int strict language plrust as $$ + [code] + notice!("{}", "fn1 started"); + let cmd = format!("select fn2({})", i); + Spi::connect(|client| + { + client.select(&cmd, None, None); + }); + notice!("{}", "fn1 finished"); + Ok(Some(1)) + $$; + + create or replace function fn2(i int) returns int strict language plrust as $$ + [code] + notice!("{}", "fn2 started"); + notice!("{}", "fn2 finished"); + Ok(Some(2)) + $$; + "#; + Spi::run(sql)?; + assert_eq!(Ok(Some(1)), Spi::get_one::("SELECT fn1(1)")); + Ok(()) + } + + #[pg_test] + fn replace_function() -> spi::Result<()> { + Spi::run("CREATE FUNCTION replace_me() RETURNS int LANGUAGE plrust AS $$ Ok(Some(1)) $$")?; + assert_eq!(Ok(Some(1)), Spi::get_one("SELECT replace_me()")); + + Spi::run( + "CREATE OR REPLACE FUNCTION replace_me() RETURNS int LANGUAGE plrust AS $$ Ok(Some(2)) $$", + )?; + assert_eq!(Ok(Some(2)), Spi::get_one("SELECT replace_me()")); + Ok(()) + } + + #[pg_test] + fn test_point() -> spi::Result<()> { + Spi::run( + r#"CREATE FUNCTION test_point(p point) RETURNS point LANGUAGE plrust AS $$ Ok(p) $$"#, + )?; + let p = Spi::get_one::("SELECT test_point('42, 99'::point);")? + .expect("SPI result was null"); + assert_eq!(p.x, 42.0); + assert_eq!(p.y, 99.0); + Ok(()) + } + + #[pg_test] + fn test_box() -> spi::Result<()> { + Spi::run(r#"CREATE FUNCTION test_box(b box) RETURNS box LANGUAGE plrust AS $$ Ok(b) $$"#)?; + let b = Spi::get_one::("SELECT test_box('1,2,3,4'::box);")? + .expect("SPI result was null"); + assert_eq!(b.high.x, 3.0); + assert_eq!(b.high.y, 4.0); + assert_eq!(b.low.x, 1.0); + assert_eq!(b.low.y, 2.0); + Ok(()) + } + + #[pg_test] + fn test_uuid() -> spi::Result<()> { + Spi::run( + r#"CREATE FUNCTION test_uuid(u uuid) RETURNS uuid LANGUAGE plrust AS $$ Ok(u) $$"#, + )?; + let u = Spi::get_one::( + "SELECT test_uuid('e4176a4d-790c-4750-85b7-665d72471173'::uuid);", + )? + .expect("SPI result was null"); + assert_eq!( + u, + pgrx::Uuid::from_bytes([ + 0xe4, 0x17, 0x6a, 0x4d, 0x79, 0x0c, 0x47, 0x50, 0x85, 0xb7, 0x66, 0x5d, 0x72, 0x47, + 0x11, 0x73 + ]) + ); + + Ok(()) + } + + #[pg_test] + fn test_int4range() -> spi::Result<()> { + Spi::run( + r#"CREATE FUNCTION test_int4range(r int4range) RETURNS int4range LANGUAGE plrust AS $$ Ok(r) $$"#, + )?; + let r = Spi::get_one::>("SELECT test_int4range('[1, 10)'::int4range);")? + .expect("SPI result was null"); + assert_eq!(r, (1..10).into()); + Ok(()) + } + + #[pg_test] + fn test_int8range() -> spi::Result<()> { + Spi::run( + r#"CREATE FUNCTION test_int8range(r int8range) RETURNS int8range LANGUAGE plrust AS $$ Ok(r) $$"#, + )?; + let r = Spi::get_one::>("SELECT test_int8range('[1, 10)'::int8range);")? + .expect("SPI result was null"); + assert_eq!(r, (1..10).into()); + Ok(()) + } + + #[pg_test] + fn test_numrange() -> spi::Result<()> { + Spi::run( + r#"CREATE FUNCTION test_numrange(r numrange) RETURNS numrange LANGUAGE plrust AS $$ Ok(r) $$"#, + )?; + let r = Spi::get_one::>("SELECT test_numrange('[1, 10]'::numrange);")? + .expect("SPI result was null"); + assert_eq!( + r, + Range::new( + AnyNumeric::try_from(1.0f32).unwrap(), + AnyNumeric::try_from(10.0f32).unwrap() + ) + ); + Ok(()) + } + + #[pg_test] + fn test_tid_roundtrip() -> spi::Result<()> { + Spi::run( + r#"CREATE FUNCTION tid_roundtrip(t tid) RETURNS tid LANGUAGE plrust AS $$ Ok(t) $$"#, + )?; + let tid = Spi::get_one::("SELECT tid_roundtrip('(42, 99)'::tid)")? + .expect("SPI result was null"); + let (blockno, offno) = pgrx::item_pointer_get_both(tid); + assert_eq!(blockno, 42); + assert_eq!(offno, 99); + Ok(()) + } + + #[pg_test] + fn test_return_bytea() -> spi::Result<()> { + Spi::run( + r#"CREATE FUNCTION return_bytea() RETURNS bytea LANGUAGE plrust AS $$ Ok(Some(vec![1,2,3])) $$"#, + )?; + let bytes = Spi::get_one::>("SELECT return_bytea()")?.expect("SPI result was null"); + assert_eq!(bytes, vec![1, 2, 3]); + Ok(()) + } + + #[pg_test] + fn test_cstring_roundtrip() -> Result<(), Box> { + use std::ffi::CStr; + Spi::run( + r#"CREATE FUNCTION cstring_roundtrip(s cstring) RETURNS cstring STRICT LANGUAGE plrust as $$ Ok(Some(s.into())) $$;"#, + )?; + let cstr = Spi::get_one::<&CStr>("SELECT cstring_roundtrip('hello')")? + .expect("SPI result was null"); + let expected = CStr::from_bytes_with_nul(b"hello\0")?; + assert_eq!(cstr, expected); + Ok(()) + } + + #[pg_test] + fn test_daterange() -> Result<(), Box> { + Spi::run( + r#"CREATE FUNCTION test_daterange(r daterange) RETURNS daterange LANGUAGE plrust AS $$ Ok(r) $$"#, + )?; + let r = Spi::get_one::>( + "SELECT test_daterange('[1977-03-20, 1980-01-01)'::daterange);", + )? + .expect("SPI result was null"); + assert_eq!( + r, + Range::new( + Date::new(1977, 3, 20)?, + RangeBound::Exclusive(Date::new(1980, 01, 01)?) + ) + ); + Ok(()) + } + + #[pg_test] + fn test_tsrange() -> Result<(), Box> { + Spi::run( + r#"CREATE FUNCTION test_tsrange(p tsrange) RETURNS tsrange LANGUAGE plrust AS $$ Ok(p) $$"#, + )?; + let r = Spi::get_one::>( + "SELECT test_tsrange('[1977-03-20, 1980-01-01)'::tsrange);", + )? + .expect("SPI result was null"); + assert_eq!( + r, + Range::new( + Timestamp::new(1977, 3, 20, 0, 0, 0.0)?, + RangeBound::Exclusive(Timestamp::new(1980, 01, 01, 0, 0, 0.0)?) + ) + ); + Ok(()) + } + + #[pg_test] + fn test_tstzrange() -> Result<(), Box> { + Spi::run( + r#"CREATE FUNCTION test_tstzrange(p tstzrange) RETURNS tstzrange LANGUAGE plrust AS $$ Ok(p) $$"#, + )?; + let r = Spi::get_one::>( + "SELECT test_tstzrange('[1977-03-20, 1980-01-01)'::tstzrange);", + )? + .expect("SPI result was null"); + assert_eq!( + r, + Range::new( + TimestampWithTimeZone::new(1977, 3, 20, 0, 0, 0.0)?, + RangeBound::Exclusive(TimestampWithTimeZone::new(1980, 01, 01, 0, 0, 0.0)?) + ) + ); + Ok(()) + } + + #[pg_test] + fn test_interval() -> Result<(), Box> { + Spi::run( + r#"CREATE FUNCTION get_interval_hours(i interval) RETURNS numeric STRICT LANGUAGE plrust AS $$ Ok(i.extract_part(DateTimeParts::Hour)) $$"#, + )?; + let hours = + Spi::get_one::("SELECT get_interval_hours('3 days 9 hours 12 seconds')")? + .expect("SPI result was null"); + assert_eq!(hours, AnyNumeric::from(9)); + Ok(()) + } + + #[cfg(feature = "trusted")] + #[pg_test] + #[search_path(@extschema@)] + fn postgrestd_net_is_unsupported() -> spi::Result<()> { + let sql = r#" + create or replace function pt106() returns text + IMMUTABLE STRICT + LANGUAGE PLRUST AS + $$ + [code] + use std::net::TcpStream; + + Ok(TcpStream::connect("127.0.0.1:22").err().map(|e| e.to_string())) + $$"#; + Spi::run(sql)?; + let string = Spi::get_one::("SELECT pt106()")?.expect("Unconditional return"); + assert_eq!("operation not supported on this platform", &string); + Ok(()) + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic(expected = "PL/Rust does not support unnamed arguments")] + fn unnamed_args() -> spi::Result<()> { + Spi::run("CREATE FUNCTION unnamed_arg(int) RETURNS int LANGUAGE plrust as $$ Ok(None) $$;") + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic(expected = "PL/Rust does not support unnamed arguments")] + fn named_unnamed_args() -> spi::Result<()> { + Spi::run("CREATE FUNCTION named_unnamed_arg(bob text, int) RETURNS int LANGUAGE plrust as $$ Ok(None) $$;") + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic( + expected = "is an invalid Rust identifier and cannot be used as an argument name" + )] + fn invalid_arg_identifier() -> spi::Result<()> { + Spi::run("CREATE FUNCTION invalid_arg_identifier(\"this isn't a valid rust identifier\" int) RETURNS int LANGUAGE plrust as $$ Ok(None) $$;") + } + + #[pg_test] + fn test_srf_one_col() -> spi::Result<()> { + Spi::run( + "CREATE FUNCTION srf_one_col() RETURNS TABLE (a int) LANGUAGE plrust AS $$ + Ok(Some(TableIterator::new(vec![( Some(1), )].into_iter()))) + $$;", + )?; + + let a = Spi::get_one::("SELECT * FROM srf_one_col()")?; + assert_eq!(a, Some(1)); + + Ok(()) + } + + #[pg_test] + fn test_srf_two_col() -> spi::Result<()> { + Spi::run( + "CREATE FUNCTION srf_two_col() RETURNS TABLE (a int, b int) LANGUAGE plrust AS $$ + Ok(Some(TableIterator::new(vec![(Some(1), Some(2))].into_iter()))) + $$;", + )?; + + let (a, b) = Spi::get_two::("SELECT * FROM srf_two_col()")?; + assert_eq!(a, Some(1)); + assert_eq!(b, Some(2)); + + Ok(()) + } + + #[pg_test] + fn test_udt() -> spi::Result<()> { + Spi::run( + r#" +CREATE TYPE person AS ( + name text, + age float8 +); + +create function make_person(name text, age float8) returns person + strict parallel safe + language plrust as +$$ + // create the Heap Tuple representation of the SQL type `person` + let mut p = PgHeapTuple::new_composite_type("person")?; + + // set a few of its attributes + // + // Runtime errors can occur if the attribute name is invalid or if the Rust type of the value + // is not compatible with the backing SQL type for that attribute. Hence the use of the `?` operator + p.set_by_name("name", name)?; + p.set_by_name("age", age)?; + + // return the `person` + Ok(Some(p)) +$$; + +create function get_person_name(p person) returns text + strict parallel safe + language plrust as +$$ + // `p` is a `PgHeapTuple` over the underlying data for `person` + Ok(p.get_by_name("name")?) +$$; + +create function get_person_age(p person) returns float8 + strict parallel safe + language plrust as +$$ + // `p` is a `PgHeapTuple` over the underlying data for `person` + Ok(p.get_by_name("age")?) +$$; + +create function get_person_attribute(p person, attname text) returns text + strict parallel safe + language plrust as +$$ + match attname.to_lowercase().as_str() { + "age" => { + let age:Option = p.get_by_name("age")?; + Ok(age.map(|v| v.to_string())) + }, + "name" => { + Ok(p.get_by_name("name")?) + }, + _ => panic!("unknown attribute: `{attname}`") + } +$$; + +create operator ->> (function = get_person_attribute, leftarg = person, rightarg = text); + +create table people +( + id serial8 not null primary key, + p person +); + +insert into people (p) values (make_person('Johnny', 46.24)); +insert into people (p) values (make_person('Joe', 99.09)); +insert into people (p) values (make_person('Dr. Beverly Crusher of the Starship Enterprise', 32.0)); + "#, + )?; + + let johnny = Spi::get_one::>( + "SELECT p FROM people WHERE p->>'name' = 'Johnny';", + )? + .expect("SPI result was null"); + + let age = johnny.get_by_name::("age")?.expect("age was null"); + assert_eq!(age, 46.24); + + Ok(()) + } +} + +#[cfg(any(test, feature = "pg_test"))] +pub mod pg_test { + use once_cell::sync::Lazy; + use tempdir::TempDir; + + static WORK_DIR: Lazy = Lazy::new(|| { + let work_dir = TempDir::new("plrust-tests").expect("Couldn't create tempdir"); + format!("plrust.work_dir='{}'", work_dir.path().display()) + }); + static LOG_LEVEL: &str = "plrust.tracing_level=trace"; + + static PLRUST_ALLOWED_DEPENDENCIES_FILE_NAME: &str = "allowed_deps.toml"; + static PLRUST_ALLOWED_DEPENDENCIES_FILE_DIRECTORY: Lazy = Lazy::new(|| { + use std::io::Write; + let temp_allowed_deps_dir = + TempDir::new("plrust-allowed-deps").expect("Couldnt create tempdir"); + + let file_path = temp_allowed_deps_dir + .path() + .join(PLRUST_ALLOWED_DEPENDENCIES_FILE_NAME); + let mut allowed_deps = std::fs::File::create(&file_path).unwrap(); + allowed_deps + .write_all( + r#" +owo-colors = "=3.5.0" +tokio = { version = "=1.19.2", features = ["rt", "net"]} +plutonium = "*" +"# + .as_bytes(), + ) + .unwrap(); + + temp_allowed_deps_dir + }); + + static PLRUST_ALLOWED_DEPENDENCIES: Lazy = Lazy::new(|| { + format!( + "plrust.allowed_dependencies='{}'", + PLRUST_ALLOWED_DEPENDENCIES_FILE_DIRECTORY + .path() + .join(PLRUST_ALLOWED_DEPENDENCIES_FILE_NAME) + .to_str() + .unwrap() + ) + }); + + pub fn setup(_options: Vec<&str>) { + // perform one-off initialization when the pg_test framework starts + } + + pub fn postgresql_conf_options() -> Vec<&'static str> { + vec![ + &*WORK_DIR, + &*LOG_LEVEL, + &*PLRUST_ALLOWED_DEPENDENCIES, + "shared_preload_libraries='plrust'", + ] + } +} + + +/* +#[cfg(any(test, feature = "pg_test"))] +#[pg_schema] +mod tests { + use pgrx::prelude::*; + + #[pg_test] + fn test_plrust_works() -> spi::Result<()> { + Spi::run("CREATE FUNCTION test_plrust_works() RETURNS int LANGUAGE plrust AS $$ Ok(Some(1)) $$;")?; + let result = Spi::get_one::("SELECT test_plrust_works();"); + assert_eq!(result, Ok(Some(1))); + + Ok(()) + } + +} + +/// This module is required by `cargo pgrx test` invocations. +/// It must be visible at the root of your extension crate. +#[cfg(test)] +pub mod pg_test { + pub fn setup(_options: Vec<&str>) { + // perform one-off initialization when the pg_test framework starts + } + + pub fn postgresql_conf_options() -> Vec<&'static str> { + // return any postgresql.conf settings that are required for your tests + vec![ + "shared_preload_libraries = 'plrust'", + "plrust.work_dir = '/tmp/plrust-work-dir'" + ] + } +} +*/ From 4a98659d2711343d3b362cb98c9368c5cc43f09b Mon Sep 17 00:00:00 2001 From: Dave Selph Date: Thu, 13 Jul 2023 13:31:11 -0600 Subject: [PATCH 02/32] split test from the lib.rs into different rs files each new rs covers an area of test --- plrust-tests/src/alter.rs | 45 + plrust-tests/src/argument.rs | 72 ++ plrust-tests/src/basic.rs | 126 +++ plrust-tests/src/blocked_code.rs | 183 ++++ plrust-tests/src/borrow_mut_error.rs | 57 + plrust-tests/src/ddl.rs | 93 ++ plrust-tests/src/dependencies.rs | 103 ++ plrust-tests/src/lib.rs | 1340 +----------------------- plrust-tests/src/matches.rs | 115 ++ plrust-tests/src/panics.rs | 63 ++ plrust-tests/src/range.rs | 54 + plrust-tests/src/recursion.rs | 15 + plrust-tests/src/return_values.rs | 84 ++ plrust-tests/src/round_trip.rs | 97 ++ plrust-tests/src/time_and_dates.rs | 86 ++ plrust-tests/src/trusted.rs | 236 +++++ plrust-tests/src/user_defined_types.rs | 100 ++ plrust-tests/src/versioning.rs | 54 + 18 files changed, 1602 insertions(+), 1321 deletions(-) create mode 100644 plrust-tests/src/alter.rs create mode 100644 plrust-tests/src/argument.rs create mode 100644 plrust-tests/src/basic.rs create mode 100644 plrust-tests/src/blocked_code.rs create mode 100644 plrust-tests/src/borrow_mut_error.rs create mode 100644 plrust-tests/src/ddl.rs create mode 100644 plrust-tests/src/dependencies.rs create mode 100644 plrust-tests/src/matches.rs create mode 100644 plrust-tests/src/panics.rs create mode 100644 plrust-tests/src/range.rs create mode 100644 plrust-tests/src/recursion.rs create mode 100644 plrust-tests/src/return_values.rs create mode 100644 plrust-tests/src/round_trip.rs create mode 100644 plrust-tests/src/time_and_dates.rs create mode 100644 plrust-tests/src/trusted.rs create mode 100644 plrust-tests/src/user_defined_types.rs create mode 100644 plrust-tests/src/versioning.rs diff --git a/plrust-tests/src/alter.rs b/plrust-tests/src/alter.rs new file mode 100644 index 00000000..7e37fcba --- /dev/null +++ b/plrust-tests/src/alter.rs @@ -0,0 +1,45 @@ + +/* +Portions Copyright 2020-2021 ZomboDB, LLC. +Portions Copyright 2021-2023 Technology Concepts & Design, Inc. + +All rights reserved. + +Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. +*/ + +#[cfg(any(test, feature = "pg_test"))] +#[pgrx::pg_schema] +mod tests { + use pgrx:: prelude::*; + + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic = "plrust functions cannot have their STRICT property altered"] + fn plrust_cant_change_strict_off() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION cant_change_strict_off() + RETURNS int + LANGUAGE plrust + AS $$ Ok(Some(1)) $$; + "#; + Spi::run(definition)?; + Spi::run("ALTER FUNCTION cant_change_strict_off() CALLED ON NULL INPUT") + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic = "plrust functions cannot have their STRICT property altered"] + fn plrust_cant_change_strict_on() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION cant_change_strict_on() + RETURNS int + LANGUAGE plrust + AS $$ Ok(Some(1)) $$; + "#; + Spi::run(definition)?; + Spi::run("ALTER FUNCTION cant_change_strict_on() RETURNS NULL ON NULL INPUT") + } + +} \ No newline at end of file diff --git a/plrust-tests/src/argument.rs b/plrust-tests/src/argument.rs new file mode 100644 index 00000000..9a06b065 --- /dev/null +++ b/plrust-tests/src/argument.rs @@ -0,0 +1,72 @@ + +/* +Portions Copyright 2020-2021 ZomboDB, LLC. +Portions Copyright 2021-2023 Technology Concepts & Design, Inc. + +All rights reserved. + +Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. +*/ + +#[cfg(any(test, feature = "pg_test"))] +#[pgrx::pg_schema] +mod tests { + use pgrx:: prelude::*; + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic = "parameter name \"a\" used more than once"] + fn plrust_dup_args() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION not_unique(a int, a int) + RETURNS int AS + $$ + Ok(a) + $$ LANGUAGE plrust; + "#; + Spi::run(definition)?; + let result = Spi::get_one::("SELECT not_unique(1, 2);\n"); + assert_eq!(Ok(Some(1)), result); + Ok(()) + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic = "PL/Rust does not support unnamed arguments"] + fn plrust_defaulting_dup_args() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION not_unique(int, arg0 int) + RETURNS int AS + $$ + Ok(arg0) + $$ LANGUAGE plrust; + "#; + Spi::run(definition)?; + let result = Spi::get_one::("SELECT not_unique(1, 2);\n"); + assert_eq!(Ok(Some(1)), result); + Ok(()) + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic(expected = "PL/Rust does not support unnamed arguments")] + fn unnamed_args() -> spi::Result<()> { + Spi::run("CREATE FUNCTION unnamed_arg(int) RETURNS int LANGUAGE plrust as $$ Ok(None) $$;") + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic(expected = "PL/Rust does not support unnamed arguments")] + fn named_unnamed_args() -> spi::Result<()> { + Spi::run("CREATE FUNCTION named_unnamed_arg(bob text, int) RETURNS int LANGUAGE plrust as $$ Ok(None) $$;") + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic( + expected = "is an invalid Rust identifier and cannot be used as an argument name" + )] + fn invalid_arg_identifier() -> spi::Result<()> { + Spi::run("CREATE FUNCTION invalid_arg_identifier(\"this isn't a valid rust identifier\" int) RETURNS int LANGUAGE plrust as $$ Ok(None) $$;") + } +} \ No newline at end of file diff --git a/plrust-tests/src/basic.rs b/plrust-tests/src/basic.rs new file mode 100644 index 00000000..628ddd56 --- /dev/null +++ b/plrust-tests/src/basic.rs @@ -0,0 +1,126 @@ + +/* +Portions Copyright 2020-2021 ZomboDB, LLC. +Portions Copyright 2021-2023 Technology Concepts & Design, Inc. + +All rights reserved. + +Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. +*/ + +#[cfg(any(test, feature = "pg_test"))] +#[pgrx::pg_schema] +mod tests { + use pgrx::{datum::IntoDatum, prelude::*}; + + + #[pg_test] + #[search_path(@extschema@)] + fn plrust_basic() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION sum_array(a BIGINT[]) RETURNS BIGINT + IMMUTABLE STRICT + LANGUAGE PLRUST AS + $$ + Ok(Some(a.into_iter().map(|v| v.unwrap_or_default()).sum())) + $$; + "#; + Spi::run(definition)?; + + let retval = Spi::get_one_with_args::( + r#" + SELECT sum_array($1); + "#, + vec![( + PgBuiltInOids::INT4ARRAYOID.oid(), + vec![1, 2, 3].into_datum(), + )], + ); + assert_eq!(retval, Ok(Some(6))); + Ok(()) + } + + + #[pg_test] + #[search_path(@extschema@)] + fn plrust_update() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION update_me() RETURNS TEXT + IMMUTABLE STRICT + LANGUAGE PLRUST AS + $$ + Ok(String::from("booper").into()) + $$; + "#; + Spi::run(definition)?; + + let retval = Spi::get_one( + r#" + SELECT update_me(); + "#, + ); + assert_eq!(retval, Ok(Some("booper"))); + + let definition = r#" + CREATE OR REPLACE FUNCTION update_me() RETURNS TEXT + IMMUTABLE STRICT + LANGUAGE PLRUST AS + $$ + Ok(Some(String::from("swooper"))) + $$; + "#; + Spi::run(definition)?; + + let retval = Spi::get_one( + r#" + SELECT update_me(); + "#, + ); + assert_eq!(retval, Ok(Some("swooper"))); + Ok(()) + } + + #[pg_test] + #[search_path(@extschema@)] + fn plrust_spi() -> spi::Result<()> { + let random_definition = r#" + CREATE FUNCTION random_contributor_pet() RETURNS TEXT + STRICT + LANGUAGE PLRUST AS + $$ + Ok(Spi::get_one("SELECT name FROM contributors_pets ORDER BY random() LIMIT 1")?) + $$; + "#; + Spi::run(random_definition)?; + + let retval = Spi::get_one::( + r#" + SELECT random_contributor_pet(); + "#, + ); + assert!(retval.is_ok()); + assert!(retval.unwrap().is_some()); + + let specific_definition = r#" + CREATE FUNCTION contributor_pet(name TEXT) RETURNS BIGINT + STRICT + LANGUAGE PLRUST AS + $$ + use pgrx::IntoDatum; + Ok(Spi::get_one_with_args( + "SELECT id FROM contributors_pets WHERE name = $1", + vec![(PgBuiltInOids::TEXTOID.oid(), name.into_datum())], + )?) + $$; + "#; + Spi::run(specific_definition)?; + + let retval = Spi::get_one::( + r#" + SELECT contributor_pet('Nami'); + "#, + ); + assert_eq!(retval, Ok(Some(2))); + Ok(()) + } +} diff --git a/plrust-tests/src/blocked_code.rs b/plrust-tests/src/blocked_code.rs new file mode 100644 index 00000000..f754fa05 --- /dev/null +++ b/plrust-tests/src/blocked_code.rs @@ -0,0 +1,183 @@ + +/* +Portions Copyright 2020-2021 ZomboDB, LLC. +Portions Copyright 2021-2023 Technology Concepts & Design, Inc. + +All rights reserved. + +Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. +*/ + +#[cfg(any(test, feature = "pg_test"))] +#[pgrx::pg_schema] +mod tests { + use pgrx:: prelude::*; + + #[pg_test] + #[search_path(@ extschema @)] + #[should_panic = "error: usage of an `unsafe` block"] + fn plrust_block_unsafe_annotated() -> spi::Result<()> { + // PL/Rust should block creating obvious, correctly-annotated usage of unsafe code + let definition = r#" + CREATE FUNCTION naughty() + RETURNS text AS + $$ + use std::{os::raw as ffi, str, ffi::CStr}; + let int:u32 = 0xDEADBEEF; + // Note that it is always safe to create a pointer. + let ptr = int as *mut u64; + // What is unsafe is dereferencing it + let cstr = unsafe { + ptr.write(0x00_1BADC0DE_00); + CStr::from_ptr(ptr.cast::()) + }; + Ok(str::from_utf8(cstr.to_bytes()).ok().map(|s| s.to_owned())) + $$ LANGUAGE plrust; + "#; + Spi::run(definition) + } + + #[pg_test] + #[search_path(@ extschema @)] + #[should_panic = "call to unsafe function is unsafe and requires unsafe block"] + fn plrust_block_unsafe_hidden() -> spi::Result<()> { + // PL/Rust should not allow hidden injection of unsafe code + // that may rely on the way PGRX expands into `unsafe fn` to "sneak in" + let definition = r#" + CREATE FUNCTION naughty() + RETURNS text AS + $$ + use std::{os::raw as ffi, str, ffi::CStr}; + let int:u32 = 0xDEADBEEF; + let ptr = int as *mut u64; + ptr.write(0x00_1BADC0DE_00); + let cstr = CStr::from_ptr(ptr.cast::()); + Ok(str::from_utf8(cstr.to_bytes()).ok().map(|s| s.to_owned())) + $$ LANGUAGE plrust; + "#; + Spi::run(definition) + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic = "error: usage of an `unsafe` block"] + fn plrust_block_unsafe_plutonium() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION super_safe() + RETURNS text AS + $$ + [dependencies] + plutonium = "*" + + [code] + use std::{os::raw as ffi, str, ffi::CStr}; + use plutonium::safe; + + #[safe] + fn super_safe() -> Option { + let int: u32 = 0xDEADBEEF; + let ptr = int as *mut u64; + ptr.write(0x00_1BADC0DE_00); + let cstr = CStr::from_ptr(ptr.cast::()); + str::from_utf8(cstr.to_bytes()).ok().map(|s| s.to_owned()) + } + + Ok(super_safe()) + $$ LANGUAGE plrust; + "#; + Spi::run(definition) + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic(expected = "error: declaration of a function with `export_name`")] + fn plrust_block_unsafe_export_name() -> spi::Result<()> { + // A separate test covers #[no_mangle], but what about #[export_name]? + // Same idea. This tries to collide with free, which may symbol clash, + // or might override depending on how the OS and loader feel today. + // Let's not leave it up to forces beyond our control. + let definition = r#" + CREATE OR REPLACE FUNCTION export_hacked_free() RETURNS BIGINT + IMMUTABLE STRICT + LANGUAGE PLRUST AS + $$ + #[export_name = "free"] + pub extern "C" fn own_free(ptr: *mut c_void) { + // the contents don't matter + } + + Ok(Some(1)) + $$; + "#; + Spi::run(definition)?; + let result = Spi::get_one::("SELECT export_hacked_free();\n"); + assert_eq!(Ok(Some(1)), result); + Ok(()) + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic(expected = "error: declaration of a static with `link_section`")] + fn plrust_block_unsafe_link_section() -> spi::Result<()> { + let definition = r#" + CREATE OR REPLACE FUNCTION link_evil_section() RETURNS BIGINT + IMMUTABLE STRICT + LANGUAGE PLRUST AS + $$ + #[link_section = ".init_array"] + pub static INITIALIZE: &[u8; 136] = &GOGO; + + #[link_section = ".text"] + pub static GOGO: [u8; 136] = [ + 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 46, 99, 104, 111, 46, 114, 105, 1, 72, 49, 4, + 36, 72, 137, 231, 106, 1, 254, 12, 36, 72, 184, 99, 102, 105, 108, 101, 49, 50, 51, 80, 72, + 184, 114, 47, 116, 109, 112, 47, 112, 111, 80, 72, 184, 111, 117, 99, 104, 32, 47, 118, 97, + 80, 72, 184, 115, 114, 47, 98, 105, 110, 47, 116, 80, 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, + 72, 184, 114, 105, 1, 44, 98, 1, 46, 116, 72, 49, 4, 36, 49, 246, 86, 106, 14, 94, 72, 1, + 230, 86, 106, 19, 94, 72, 1, 230, 86, 106, 24, 94, 72, 1, 230, 86, 72, 137, 230, 49, 210, + 106, 59, 88, 15, 5, + ]; + + Ok(Some(1)) + $$; + "#; + Spi::run(definition)?; + let result = Spi::get_one::("SELECT link_evil_section();\n"); + assert_eq!(Ok(Some(1)), result); + Ok(()) + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic(expected = "error: declaration of a `no_mangle` static")] + fn plrust_block_unsafe_no_mangle() -> spi::Result<()> { + let definition = r#" + CREATE OR REPLACE FUNCTION not_mangled() RETURNS BIGINT + IMMUTABLE STRICT + LANGUAGE PLRUST AS + $$ + #[no_mangle] + #[link_section = ".init_array"] + pub static INITIALIZE: &[u8; 136] = &GOGO; + + #[no_mangle] + #[link_section = ".text"] + pub static GOGO: [u8; 136] = [ + 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 46, 99, 104, 111, 46, 114, 105, 1, 72, 49, 4, + 36, 72, 137, 231, 106, 1, 254, 12, 36, 72, 184, 99, 102, 105, 108, 101, 49, 50, 51, 80, 72, + 184, 114, 47, 116, 109, 112, 47, 112, 111, 80, 72, 184, 111, 117, 99, 104, 32, 47, 118, 97, + 80, 72, 184, 115, 114, 47, 98, 105, 110, 47, 116, 80, 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, + 72, 184, 114, 105, 1, 44, 98, 1, 46, 116, 72, 49, 4, 36, 49, 246, 86, 106, 14, 94, 72, 1, + 230, 86, 106, 19, 94, 72, 1, 230, 86, 106, 24, 94, 72, 1, 230, 86, 72, 137, 230, 49, 210, + 106, 59, 88, 15, 5, + ]; + + Ok(Some(1)) + $$; + "#; + Spi::run(definition)?; + let result = Spi::get_one::("SELECT not_mangled();\n"); + assert_eq!(Ok(Some(1)), result); + Ok(()) + } +} \ No newline at end of file diff --git a/plrust-tests/src/borrow_mut_error.rs b/plrust-tests/src/borrow_mut_error.rs new file mode 100644 index 00000000..0d7c4a94 --- /dev/null +++ b/plrust-tests/src/borrow_mut_error.rs @@ -0,0 +1,57 @@ +/* +Portions Copyright 2020-2021 ZomboDB, LLC. +Portions Copyright 2021-2023 Technology Concepts & Design, Inc. + +All rights reserved. + +Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. +*/ + +#[cfg(any(test, feature = "pg_test"))] +#[pgrx::pg_schema] +mod tests { + use pgrx:: prelude::*; + + #[pg_test] + #[should_panic(expected = "issue78 works")] + fn test_issue_78() -> spi::Result<()> { + let sql = r#"CREATE OR REPLACE FUNCTION raise_error() RETURNS TEXT + IMMUTABLE STRICT + LANGUAGE PLRUST AS + $$ + pgrx::error!("issue78 works"); + Ok(Some("hi".to_string())) + $$;"#; + Spi::run(sql)?; + Spi::get_one::("SELECT raise_error()")?; + Ok(()) + } + + #[pg_test] + fn test_issue_79() -> spi::Result<()> { + let sql = r#" + create or replace function fn1(i int) returns int strict language plrust as $$ + [code] + notice!("{}", "fn1 started"); + let cmd = format!("select fn2({})", i); + Spi::connect(|client| + { + client.select(&cmd, None, None); + }); + notice!("{}", "fn1 finished"); + Ok(Some(1)) + $$; + + create or replace function fn2(i int) returns int strict language plrust as $$ + [code] + notice!("{}", "fn2 started"); + notice!("{}", "fn2 finished"); + Ok(Some(2)) + $$; + "#; + Spi::run(sql)?; + assert_eq!(Ok(Some(1)), Spi::get_one::("SELECT fn1(1)")); + Ok(()) + } + +} \ No newline at end of file diff --git a/plrust-tests/src/ddl.rs b/plrust-tests/src/ddl.rs new file mode 100644 index 00000000..0f649136 --- /dev/null +++ b/plrust-tests/src/ddl.rs @@ -0,0 +1,93 @@ + +/* +Portions Copyright 2020-2021 ZomboDB, LLC. +Portions Copyright 2021-2023 Technology Concepts & Design, Inc. + +All rights reserved. + +Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. +*/ + +#[cfg(any(test, feature = "pg_test"))] +#[pgrx::pg_schema] +mod tests { + use pgrx:: prelude::*; + + #[pg_test] + #[search_path(@extschema@)] + fn plrust_aggregate() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION plrust_sum_state(state INT, next INT) RETURNS INT + IMMUTABLE STRICT + LANGUAGE PLRUST AS + $$ + Ok(Some(state + next)) + $$; + CREATE AGGREGATE plrust_sum(INT) + ( + SFUNC = plrust_sum_state, + STYPE = INT, + INITCOND = '0' + ); + "#; + Spi::run(definition)?; + + let retval = Spi::get_one::( + r#" + SELECT plrust_sum(value) FROM UNNEST(ARRAY [1, 2, 3]) as value; + "#, + ); + assert_eq!(retval, Ok(Some(6))); + Ok(()) + } + + #[pg_test] + #[search_path(@extschema@)] + fn plrust_trigger() -> spi::Result<()> { + let definition = r#" + CREATE TABLE dogs ( + name TEXT, + scritches INT NOT NULL DEFAULT 0 + ); + + CREATE FUNCTION pet_trigger() RETURNS trigger AS $$ + let mut new = trigger.new().unwrap().into_owned(); + + let field = "scritches"; + + match new.get_by_name::(field)? { + Some(val) => new.set_by_name(field, val + 1)?, + None => (), + } + + Ok(Some(new)) + $$ LANGUAGE plrust; + + CREATE TRIGGER pet_trigger BEFORE INSERT OR UPDATE ON dogs + FOR EACH ROW EXECUTE FUNCTION pet_trigger(); + + INSERT INTO dogs (name) VALUES ('Nami'); + "#; + Spi::run(definition)?; + + let retval = Spi::get_one::( + r#" + SELECT scritches FROM dogs; + "#, + ); + assert_eq!(retval, Ok(Some(1))); + Ok(()) + } + + #[pg_test] + fn replace_function() -> spi::Result<()> { + Spi::run("CREATE FUNCTION replace_me() RETURNS int LANGUAGE plrust AS $$ Ok(Some(1)) $$")?; + assert_eq!(Ok(Some(1)), Spi::get_one("SELECT replace_me()")); + + Spi::run( + "CREATE OR REPLACE FUNCTION replace_me() RETURNS int LANGUAGE plrust AS $$ Ok(Some(2)) $$", + )?; + assert_eq!(Ok(Some(2)), Spi::get_one("SELECT replace_me()")); + Ok(()) + } +} \ No newline at end of file diff --git a/plrust-tests/src/dependencies.rs b/plrust-tests/src/dependencies.rs new file mode 100644 index 00000000..6a5f62da --- /dev/null +++ b/plrust-tests/src/dependencies.rs @@ -0,0 +1,103 @@ + +/* +Portions Copyright 2020-2021 ZomboDB, LLC. +Portions Copyright 2021-2023 Technology Concepts & Design, Inc. + +All rights reserved. + +Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. +*/ + +#[cfg(any(test, feature = "pg_test"))] +#[pgrx::pg_schema] +mod tests { + use pgrx:: prelude::*; + + #[pg_test] + #[cfg(not(feature = "sandboxed"))] + #[search_path(@extschema@)] + fn plrust_deps_supported() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION colorize(input TEXT) RETURNS TEXT + IMMUTABLE STRICT + LANGUAGE PLRUST AS + $$ + [dependencies] + owo-colors = "3" + [code] + use owo_colors::OwoColorize; + Ok(Some(input.purple().to_string())) + $$; + "#; + Spi::run(definition)?; + + let retval = Spi::get_one_with_args::( + r#" + SELECT colorize($1); + "#, + vec![(PgBuiltInOids::TEXTOID.oid(), "Nami".into_datum())], + ); + assert!(retval.is_ok()); + assert!(retval.unwrap().is_some()); + + // Regression test: A previous version of PL/Rust would abort if this was called twice, so call it twice: + let retval = Spi::get_one_with_args::( + r#" + SELECT colorize($1); + "#, + vec![(PgBuiltInOids::TEXTOID.oid(), "Nami".into_datum())], + ); + assert!(retval.is_ok()); + assert!(retval.unwrap().is_some()); + Ok(()) + } + + #[pg_test] + #[cfg(not(feature = "sandboxed"))] + #[search_path(@extschema@)] + fn plrust_deps_supported_deps_in_toml_table() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION say_hello() RETURNS TEXT + IMMUTABLE STRICT + LANGUAGE PLRUST AS + $$ + [dependencies] + tokio = ">=1" + owo-colors = "3" + [code] + Ok(Some("hello".to_string())) + $$; + "#; + Spi::run(definition)?; + + let retval = Spi::get_one_with_args::( + r#" + SELECT say_hello(); + "#, + vec![(PgBuiltInOids::TEXTOID.oid(), "hello".into_datum())], + ); + assert_eq!(retval, Ok(Some("hello".to_string()))); + Ok(()) + } + + #[pg_test] + #[cfg(not(feature = "sandboxed"))] + #[search_path(@extschema@)] + fn plrust_deps_not_supported() { + let definition = r#" + CREATE FUNCTION colorize(input TEXT) RETURNS TEXT + IMMUTABLE STRICT + LANGUAGE PLRUST AS + $$ + [dependencies] + regex = "1.6.5" + [code] + Ok(Some("test")) + $$; + "#; + let res = std::panic::catch_unwind(|| { + Spi::run(definition).expect("SQL for plrust_deps_not_supported() failed") + }); + assert!(res.is_err()); + } +} \ No newline at end of file diff --git a/plrust-tests/src/lib.rs b/plrust-tests/src/lib.rs index 5e58e4f1..0d0a7b16 100644 --- a/plrust-tests/src/lib.rs +++ b/plrust-tests/src/lib.rs @@ -1,3 +1,21 @@ +mod basic; +mod versioning; +mod dependencies; +mod return_values; +mod ddl; +mod blocked_code; +mod recursion; +mod matches; +mod argument; +mod range; +mod user_defined_types; +mod time_and_dates; +mod borrow_mut_error; +mod panics; +mod alter; +mod round_trip; +mod trusted; + use pgrx::prelude::*; pgrx::pg_module_magic!(); @@ -14,8 +32,7 @@ Use of this source code is governed by the PostgreSQL license that can be found #[cfg(any(test, feature = "pg_test"))] #[pgrx::pg_schema] mod tests { - use pgrx::{datum::IntoDatum, prelude::*}; - use std::error::Error; + use pgrx::{prelude::*}; // Bootstrap a testing table for non-immutable functions extension_sql!( @@ -31,1325 +48,6 @@ mod tests { "#, name = "create_contributors_pets", ); - - #[pg_test] - #[search_path(@extschema@)] - fn plrust_basic() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION sum_array(a BIGINT[]) RETURNS BIGINT - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - Ok(Some(a.into_iter().map(|v| v.unwrap_or_default()).sum())) - $$; - "#; - Spi::run(definition)?; - - let retval = Spi::get_one_with_args::( - r#" - SELECT sum_array($1); - "#, - vec![( - PgBuiltInOids::INT4ARRAYOID.oid(), - vec![1, 2, 3].into_datum(), - )], - ); - assert_eq!(retval, Ok(Some(6))); - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - fn plrust_update() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION update_me() RETURNS TEXT - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - Ok(String::from("booper").into()) - $$; - "#; - Spi::run(definition)?; - - let retval = Spi::get_one( - r#" - SELECT update_me(); - "#, - ); - assert_eq!(retval, Ok(Some("booper"))); - - let definition = r#" - CREATE OR REPLACE FUNCTION update_me() RETURNS TEXT - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - Ok(Some(String::from("swooper"))) - $$; - "#; - Spi::run(definition)?; - - let retval = Spi::get_one( - r#" - SELECT update_me(); - "#, - ); - assert_eq!(retval, Ok(Some("swooper"))); - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - fn plrust_spi() -> spi::Result<()> { - let random_definition = r#" - CREATE FUNCTION random_contributor_pet() RETURNS TEXT - STRICT - LANGUAGE PLRUST AS - $$ - Ok(Spi::get_one("SELECT name FROM contributors_pets ORDER BY random() LIMIT 1")?) - $$; - "#; - Spi::run(random_definition)?; - - let retval = Spi::get_one::( - r#" - SELECT random_contributor_pet(); - "#, - ); - assert!(retval.is_ok()); - assert!(retval.unwrap().is_some()); - - let specific_definition = r#" - CREATE FUNCTION contributor_pet(name TEXT) RETURNS BIGINT - STRICT - LANGUAGE PLRUST AS - $$ - use pgrx::IntoDatum; - Ok(Spi::get_one_with_args( - "SELECT id FROM contributors_pets WHERE name = $1", - vec![(PgBuiltInOids::TEXTOID.oid(), name.into_datum())], - )?) - $$; - "#; - Spi::run(specific_definition)?; - - let retval = Spi::get_one::( - r#" - SELECT contributor_pet('Nami'); - "#, - ); - assert_eq!(retval, Ok(Some(2))); - Ok(()) - } - - #[pg_test] - #[cfg(not(feature = "sandboxed"))] - #[search_path(@extschema@)] - fn plrust_deps_supported() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION colorize(input TEXT) RETURNS TEXT - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - [dependencies] - owo-colors = "3" - [code] - use owo_colors::OwoColorize; - Ok(Some(input.purple().to_string())) - $$; - "#; - Spi::run(definition)?; - - let retval = Spi::get_one_with_args::( - r#" - SELECT colorize($1); - "#, - vec![(PgBuiltInOids::TEXTOID.oid(), "Nami".into_datum())], - ); - assert!(retval.is_ok()); - assert!(retval.unwrap().is_some()); - - // Regression test: A previous version of PL/Rust would abort if this was called twice, so call it twice: - let retval = Spi::get_one_with_args::( - r#" - SELECT colorize($1); - "#, - vec![(PgBuiltInOids::TEXTOID.oid(), "Nami".into_datum())], - ); - assert!(retval.is_ok()); - assert!(retval.unwrap().is_some()); - Ok(()) - } - - #[pg_test] - #[cfg(not(feature = "sandboxed"))] - #[search_path(@extschema@)] - fn plrust_deps_supported_semver_parse() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION colorize(input TEXT) RETURNS TEXT - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - [dependencies] - owo-colors = ">2" - [code] - use owo_colors::OwoColorize; - Ok(Some(input.purple().to_string())) - $$; - "#; - Spi::run(definition)?; - - let retval = Spi::get_one_with_args::( - r#" - SELECT colorize($1); - "#, - vec![(PgBuiltInOids::TEXTOID.oid(), "Nami".into_datum())], - ); - assert!(retval.is_ok()); - assert!(retval.unwrap().is_some()); - - // Regression test: A previous version of PL/Rust would abort if this was called twice, so call it twice: - let retval = Spi::get_one_with_args::( - r#" - SELECT colorize($1); - "#, - vec![(PgBuiltInOids::TEXTOID.oid(), "Nami".into_datum())], - ); - assert!(retval.is_ok()); - assert!(retval.unwrap().is_some()); - Ok(()) - } - - #[pg_test] - #[cfg(not(feature = "sandboxed"))] - #[search_path(@extschema@)] - fn plrust_deps_supported_deps_in_toml_table() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION say_hello() RETURNS TEXT - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - [dependencies] - tokio = ">=1" - owo-colors = "3" - [code] - Ok(Some("hello".to_string())) - $$; - "#; - Spi::run(definition)?; - - let retval = Spi::get_one_with_args::( - r#" - SELECT say_hello(); - "#, - vec![(PgBuiltInOids::TEXTOID.oid(), "hello".into_datum())], - ); - assert_eq!(retval, Ok(Some("hello".to_string()))); - Ok(()) - } - - #[pg_test] - #[cfg(not(feature = "sandboxed"))] - #[search_path(@extschema@)] - fn plrust_deps_not_supported() { - let definition = r#" - CREATE FUNCTION colorize(input TEXT) RETURNS TEXT - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - [dependencies] - regex = "1.6.5" - [code] - Ok(Some("test")) - $$; - "#; - let res = std::panic::catch_unwind(|| { - Spi::run(definition).expect("SQL for plrust_deps_not_supported() failed") - }); - assert!(res.is_err()); - } - - #[pg_test] - #[search_path(@extschema@)] - fn plrust_returns_setof() -> spi::Result<()> { - let definition = r#" - CREATE OR REPLACE FUNCTION boop_srf(names TEXT[]) RETURNS SETOF TEXT - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - Ok(Some(::pgrx::iter::SetOfIterator::new(names.into_iter().map(|maybe| maybe.map(|name| name.to_string() + " was booped!"))))) - $$; - "#; - Spi::run(definition)?; - - let retval: spi::Result<_> = Spi::connect(|client| { - let mut table = client.select( - "SELECT * FROM boop_srf(ARRAY['Nami', 'Brandy'])", - None, - None, - )?; - - let mut found = vec![]; - while table.next().is_some() { - let value = table.get_one::()?; - found.push(value) - } - - Ok(Some(found)) - }); - - assert_eq!( - retval, - Ok(Some(vec![ - Some("Nami was booped!".into()), - Some("Brandy was booped!".into()), - ])) - ); - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - fn plrust_aggregate() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION plrust_sum_state(state INT, next INT) RETURNS INT - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - Ok(Some(state + next)) - $$; - CREATE AGGREGATE plrust_sum(INT) - ( - SFUNC = plrust_sum_state, - STYPE = INT, - INITCOND = '0' - ); - "#; - Spi::run(definition)?; - - let retval = Spi::get_one::( - r#" - SELECT plrust_sum(value) FROM UNNEST(ARRAY [1, 2, 3]) as value; - "#, - ); - assert_eq!(retval, Ok(Some(6))); - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - fn plrust_trigger() -> spi::Result<()> { - let definition = r#" - CREATE TABLE dogs ( - name TEXT, - scritches INT NOT NULL DEFAULT 0 - ); - - CREATE FUNCTION pet_trigger() RETURNS trigger AS $$ - let mut new = trigger.new().unwrap().into_owned(); - - let field = "scritches"; - - match new.get_by_name::(field)? { - Some(val) => new.set_by_name(field, val + 1)?, - None => (), - } - - Ok(Some(new)) - $$ LANGUAGE plrust; - - CREATE TRIGGER pet_trigger BEFORE INSERT OR UPDATE ON dogs - FOR EACH ROW EXECUTE FUNCTION pet_trigger(); - - INSERT INTO dogs (name) VALUES ('Nami'); - "#; - Spi::run(definition)?; - - let retval = Spi::get_one::( - r#" - SELECT scritches FROM dogs; - "#, - ); - assert_eq!(retval, Ok(Some(1))); - Ok(()) - } - - #[cfg(feature = "trusted")] - #[pg_test] - #[search_path(@extschema@)] - fn postgrestd_dont_make_files() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION make_file(filename TEXT) RETURNS TEXT - LANGUAGE PLRUST AS - $$ - Ok(std::fs::File::create(filename.unwrap_or("/somewhere/files/dont/belong.txt")) - .err() - .map(|e| e.to_string())) - $$; - "#; - Spi::run(definition)?; - - let retval = Spi::get_one_with_args::( - r#" - SELECT make_file($1); - "#, - vec![( - PgBuiltInOids::TEXTOID.oid(), - "/an/evil/place/to/put/a/file.txt".into_datum(), - )], - ); - assert_eq!( - retval, - Ok(Some("operation not supported on this platform".to_string())) - ); - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic = "yup"] - fn pgrx_can_panic() { - panic!("yup") - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic = "yup"] - fn plrust_can_panic() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION shut_up_and_explode() - RETURNS text AS - $$ - panic!("yup"); - Ok(None) - $$ LANGUAGE plrust; - "#; - - Spi::run(definition)?; - let retval = Spi::get_one::("SELECT shut_up_and_explode();\n"); - assert_eq!(retval, Ok(None)); - Ok(()) - } - - #[cfg(feature = "trusted")] - #[pg_test] - #[search_path(@extschema@)] - #[should_panic = "Failed to execute command"] - fn postgrestd_subprocesses_panic() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION say_hello() - RETURNS text AS - $$ - let out = std::process::Command::new("echo") - .arg("Hello world") - .stdout(std::process::Stdio::piped()) - .output() - .expect("Failed to execute command"); - Ok(Some(String::from_utf8_lossy(&out.stdout).to_string())) - $$ LANGUAGE plrust; - "#; - Spi::run(definition)?; - - let retval = Spi::get_one::("SELECT say_hello();\n"); - assert_eq!(retval, Ok(Some("Hello world\n".into()))); - Ok(()) - } - - #[cfg(feature = "trusted")] - #[pg_test] - #[search_path(@extschema@)] - #[should_panic = "error: the `include_str`, `include_bytes`, and `include` macros are forbidden"] - fn postgrestd_no_include_str() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION include_str() - RETURNS text AS - $$ - let s = include_str!("/etc/passwd"); - Ok(Some(s.into())) - $$ LANGUAGE plrust; - "#; - Spi::run(definition)?; - - let retval = Spi::get_one::("SELECT include_str();\n")?; - assert_eq!(retval.unwrap(), ""); - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - #[cfg(feature = "trusted")] - #[should_panic = "No such file or directory (os error 2)"] - fn plrustc_include_exists_no_access() { - // This file is created in CI and exists, but can only be accessed by - // root. Check that the actual access is reported as file not found (we - // should be ensuring that via - // `PLRUSTC_USER_CRATE_ALLOWED_SOURCE_PATHS`). We don't need to gate - // this test on CI, since the file is unlikely to exist outside of CI - // (so the test will pass). - let definition = r#" - CREATE FUNCTION include_no_access() - RETURNS text AS $$ - include!("/var/ci-stuff/secret_rust_files/const_foo.rs"); - Ok(Some(format!("{BAR}"))) - $$ LANGUAGE plrust; - "#; - Spi::run(definition).unwrap() - } - - #[pg_test] - #[search_path(@extschema@)] - #[cfg(feature = "trusted")] - #[should_panic = "No such file or directory (os error 2)"] - fn plrustc_include_exists_external() { - // This file is created in CI, exists, and can be accessed by anybody, - // but the actual access is forbidden via - // `PLRUSTC_USER_CRATE_ALLOWED_SOURCE_PATHS`. We don't need to gate this test on - // CI, since the file is unlikely to exist outside of CI, so the test - // will pass anyway. - let definition = r#" - CREATE FUNCTION include_exists_external() - RETURNS text AS $$ - include!("/var/ci-stuff/const_bar.rs"); - Ok(Some(format!("{BAR}"))) - $$ LANGUAGE plrust; - "#; - Spi::run(definition).unwrap(); - } - - #[pg_test] - #[search_path(@extschema@)] - #[cfg(feature = "trusted")] - #[should_panic = "No such file or directory (os error 2)"] - fn plrustc_include_made_up() { - // This file does not exist, and should be reported as such. - let definition = r#" - CREATE FUNCTION include_madeup() - RETURNS int AS $$ - include!("/made/up/path/lol.rs"); - Ok(Some(1)) - $$ LANGUAGE plrust; - "#; - Spi::run(definition).unwrap(); - } - - #[pg_test] - #[search_path(@extschema@)] - #[cfg(feature = "trusted")] - #[should_panic = "No such file or directory (os error 2)"] - fn plrustc_include_path_traversal() { - use std::path::PathBuf; - let workdir = crate::gucs::work_dir(); - let wd: PathBuf = workdir - .canonicalize() - .ok() - .expect("Failed to canonicalize workdir"); - // Produce a path that looks like - // `/allowed/path/here/../../../illegal/path/here` and check that it's - // rejected, in order to ensure we are not succeptable to path traversal - // attacks. - let mut evil_path = wd.clone(); - for _ in wd.ancestors().skip(1) { - evil_path.push(".."); - } - debug_assert_eq!( - evil_path - .canonicalize() - .ok() - .expect("Failed to produce unpath") - .to_str(), - Some("/") - ); - evil_path.push("var/ci-stuff/const_bar.rs"); - // This file does not exist, and should be reported as such. - let definition = format!( - r#"CREATE FUNCTION include_sneaky_traversal() - RETURNS int AS $$ - include!({evil_path:?}); - Ok(Some(1)) - $$ LANGUAGE plrust;"# - ); - Spi::run(&definition).unwrap(); - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic = "error: usage of an `unsafe` block"] - fn plrust_block_unsafe_annotated() -> spi::Result<()> { - // PL/Rust should block creating obvious, correctly-annotated usage of unsafe code - let definition = r#" - CREATE FUNCTION naughty() - RETURNS text AS - $$ - use std::{os::raw as ffi, str, ffi::CStr}; - let int:u32 = 0xDEADBEEF; - // Note that it is always safe to create a pointer. - let ptr = int as *mut u64; - // What is unsafe is dereferencing it - let cstr = unsafe { - ptr.write(0x00_1BADC0DE_00); - CStr::from_ptr(ptr.cast::()) - }; - Ok(str::from_utf8(cstr.to_bytes()).ok().map(|s| s.to_owned())) - $$ LANGUAGE plrust; - "#; - Spi::run(definition) - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic = "call to unsafe function is unsafe and requires unsafe block"] - fn plrust_block_unsafe_hidden() -> spi::Result<()> { - // PL/Rust should not allow hidden injection of unsafe code - // that may rely on the way PGRX expands into `unsafe fn` to "sneak in" - let definition = r#" - CREATE FUNCTION naughty() - RETURNS text AS - $$ - use std::{os::raw as ffi, str, ffi::CStr}; - let int:u32 = 0xDEADBEEF; - let ptr = int as *mut u64; - ptr.write(0x00_1BADC0DE_00); - let cstr = CStr::from_ptr(ptr.cast::()); - Ok(str::from_utf8(cstr.to_bytes()).ok().map(|s| s.to_owned())) - $$ LANGUAGE plrust; - "#; - Spi::run(definition) - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic = "error: the `env` and `option_env` macros are forbidden"] - #[cfg(feature = "trusted")] - fn plrust_block_env() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION get_path() RETURNS text AS $$ - let path = env!("PATH"); - Ok(Some(path.to_string())) - $$ LANGUAGE plrust; - "#; - Spi::run(definition) - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic = "error: the `env` and `option_env` macros are forbidden"] - #[cfg(feature = "trusted")] - fn plrust_block_option_env() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION try_get_path() RETURNS text AS $$ - match option_env!("PATH") { - None => Ok(None), - Some(s) => Ok(Some(s.to_string())) - } - $$ LANGUAGE plrust; - "#; - Spi::run(definition) - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic = "error: usage of an `unsafe` block"] - fn plrust_block_unsafe_plutonium() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION super_safe() - RETURNS text AS - $$ - [dependencies] - plutonium = "*" - - [code] - use std::{os::raw as ffi, str, ffi::CStr}; - use plutonium::safe; - - #[safe] - fn super_safe() -> Option { - let int: u32 = 0xDEADBEEF; - let ptr = int as *mut u64; - ptr.write(0x00_1BADC0DE_00); - let cstr = CStr::from_ptr(ptr.cast::()); - str::from_utf8(cstr.to_bytes()).ok().map(|s| s.to_owned()) - } - - Ok(super_safe()) - $$ LANGUAGE plrust; - "#; - Spi::run(definition) - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic = "xxx"] - #[ignore] - fn plrust_pgloglevel_dont_allcaps_panic() -> spi::Result<()> { - // This test attempts to annihilate the database. - // It relies on the existing assumption that tests are run in the same Postgres instance, - // so this test will make all tests "flaky" if Postgres suddenly goes down with it. - let definition = r#" - CREATE FUNCTION dont_allcaps_panic() - RETURNS text AS - $$ - ereport!(PANIC, PgSqlErrorCode::ERRCODE_INTERNAL_ERROR, "If other tests completed, PL/Rust did not actually destroy the entire database, \ - But if you see this in the error output, something might be wrong."); - Ok(Some("lol".into())) - $$ LANGUAGE plrust; - "#; - Spi::run(definition)?; - let retval = Spi::get_one::("SELECT dont_allcaps_panic();\n"); - assert_eq!(retval, Ok(Some("lol".into()))); - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - fn plrust_call_1st() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION ret_1st(a int, b int) - RETURNS int AS - $$ - Ok(a) - $$ LANGUAGE plrust; - "#; - Spi::run(definition)?; - let result_1 = Spi::get_one::("SELECT ret_1st(1, 2);\n"); - assert_eq!(Ok(Some(1)), result_1); // may get: Some(1) - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - fn plrust_call_2nd() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION ret_2nd(a int, b int) - RETURNS int AS - $$ - Ok(b) - $$ LANGUAGE plrust; - "#; - Spi::run(definition)?; - let result_2 = Spi::get_one::("SELECT ret_2nd(1, 2);\n"); - assert_eq!(Ok(Some(2)), result_2); // may get: Some(2) - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - fn plrust_call_me() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION pick_ret(a int, b int, pick int) - RETURNS int AS - $$ - Ok(match pick { - Some(0) => a, - Some(1) => b, - _ => None, - }) - $$ LANGUAGE plrust; - "#; - Spi::run(definition)?; - let result_a = Spi::get_one::("SELECT pick_ret(3, 4, 0);"); - let result_b = Spi::get_one::("SELECT pick_ret(5, 6, 1);"); - let result_c = Spi::get_one::("SELECT pick_ret(7, 8, 2);"); - let result_z = Spi::get_one::("SELECT pick_ret(9, 99, -1);"); - assert_eq!(Ok(Some(3)), result_a); // may get: Some(4) or None - assert_eq!(Ok(Some(6)), result_b); // may get: None - assert_eq!(Ok(None), result_c); - assert_eq!(Ok(None), result_z); - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - fn plrust_call_me_call_me() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION ret_1st(a int, b int) - RETURNS int AS - $$ - Ok(a) - $$ LANGUAGE plrust; - - CREATE FUNCTION ret_2nd(a int, b int) - RETURNS int AS - $$ - Ok(b) - $$ LANGUAGE plrust; - - CREATE FUNCTION pick_ret(a int, b int, pick int) - RETURNS int AS - $$ - Ok(match pick { - Some(0) => a, - Some(1) => b, - _ => None, - }) - $$ LANGUAGE plrust; - "#; - Spi::run(definition)?; - let result_1 = Spi::get_one::("SELECT ret_1st(1, 2);\n"); - let result_2 = Spi::get_one::("SELECT ret_2nd(1, 2);\n"); - let result_a = Spi::get_one::("SELECT pick_ret(3, 4, 0);"); - let result_b = Spi::get_one::("SELECT pick_ret(5, 6, 1);"); - let result_c = Spi::get_one::("SELECT pick_ret(7, 8, 2);"); - let result_z = Spi::get_one::("SELECT pick_ret(9, 99, -1);"); - assert_eq!(Ok(None), result_z); - assert_eq!(Ok(None), result_c); - assert_eq!(Ok(Some(6)), result_b); // may get: None - assert_eq!(Ok(Some(3)), result_a); // may get: Some(4) or None - assert_eq!(Ok(Some(2)), result_2); // may get: Some(1) - assert_eq!(Ok(Some(1)), result_1); // may get: Some(2) - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic = "parameter name \"a\" used more than once"] - fn plrust_dup_args() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION not_unique(a int, a int) - RETURNS int AS - $$ - Ok(a) - $$ LANGUAGE plrust; - "#; - Spi::run(definition)?; - let result = Spi::get_one::("SELECT not_unique(1, 2);\n"); - assert_eq!(Ok(Some(1)), result); - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic = "PL/Rust does not support unnamed arguments"] - fn plrust_defaulting_dup_args() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION not_unique(int, arg0 int) - RETURNS int AS - $$ - Ok(arg0) - $$ LANGUAGE plrust; - "#; - Spi::run(definition)?; - let result = Spi::get_one::("SELECT not_unique(1, 2);\n"); - assert_eq!(Ok(Some(1)), result); - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic = "plrust functions cannot have their STRICT property altered"] - fn plrust_cant_change_strict_off() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION cant_change_strict_off() - RETURNS int - LANGUAGE plrust - AS $$ Ok(Some(1)) $$; - "#; - Spi::run(definition)?; - Spi::run("ALTER FUNCTION cant_change_strict_off() CALLED ON NULL INPUT") - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic = "plrust functions cannot have their STRICT property altered"] - fn plrust_cant_change_strict_on() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION cant_change_strict_on() - RETURNS int - LANGUAGE plrust - AS $$ Ok(Some(1)) $$; - "#; - Spi::run(definition)?; - Spi::run("ALTER FUNCTION cant_change_strict_on() RETURNS NULL ON NULL INPUT") - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic(expected = "error: declaration of a function with `export_name`")] - fn plrust_block_unsafe_export_name() -> spi::Result<()> { - // A separate test covers #[no_mangle], but what about #[export_name]? - // Same idea. This tries to collide with free, which may symbol clash, - // or might override depending on how the OS and loader feel today. - // Let's not leave it up to forces beyond our control. - let definition = r#" - CREATE OR REPLACE FUNCTION export_hacked_free() RETURNS BIGINT - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - #[export_name = "free"] - pub extern "C" fn own_free(ptr: *mut c_void) { - // the contents don't matter - } - - Ok(Some(1)) - $$; - "#; - Spi::run(definition)?; - let result = Spi::get_one::("SELECT export_hacked_free();\n"); - assert_eq!(Ok(Some(1)), result); - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic(expected = "error: declaration of a static with `link_section`")] - fn plrust_block_unsafe_link_section() -> spi::Result<()> { - let definition = r#" - CREATE OR REPLACE FUNCTION link_evil_section() RETURNS BIGINT - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - #[link_section = ".init_array"] - pub static INITIALIZE: &[u8; 136] = &GOGO; - - #[link_section = ".text"] - pub static GOGO: [u8; 136] = [ - 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 46, 99, 104, 111, 46, 114, 105, 1, 72, 49, 4, - 36, 72, 137, 231, 106, 1, 254, 12, 36, 72, 184, 99, 102, 105, 108, 101, 49, 50, 51, 80, 72, - 184, 114, 47, 116, 109, 112, 47, 112, 111, 80, 72, 184, 111, 117, 99, 104, 32, 47, 118, 97, - 80, 72, 184, 115, 114, 47, 98, 105, 110, 47, 116, 80, 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, - 72, 184, 114, 105, 1, 44, 98, 1, 46, 116, 72, 49, 4, 36, 49, 246, 86, 106, 14, 94, 72, 1, - 230, 86, 106, 19, 94, 72, 1, 230, 86, 106, 24, 94, 72, 1, 230, 86, 72, 137, 230, 49, 210, - 106, 59, 88, 15, 5, - ]; - - Ok(Some(1)) - $$; - "#; - Spi::run(definition)?; - let result = Spi::get_one::("SELECT link_evil_section();\n"); - assert_eq!(Ok(Some(1)), result); - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic(expected = "error: declaration of a `no_mangle` static")] - fn plrust_block_unsafe_no_mangle() -> spi::Result<()> { - let definition = r#" - CREATE OR REPLACE FUNCTION not_mangled() RETURNS BIGINT - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - #[no_mangle] - #[link_section = ".init_array"] - pub static INITIALIZE: &[u8; 136] = &GOGO; - - #[no_mangle] - #[link_section = ".text"] - pub static GOGO: [u8; 136] = [ - 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 46, 99, 104, 111, 46, 114, 105, 1, 72, 49, 4, - 36, 72, 137, 231, 106, 1, 254, 12, 36, 72, 184, 99, 102, 105, 108, 101, 49, 50, 51, 80, 72, - 184, 114, 47, 116, 109, 112, 47, 112, 111, 80, 72, 184, 111, 117, 99, 104, 32, 47, 118, 97, - 80, 72, 184, 115, 114, 47, 98, 105, 110, 47, 116, 80, 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, - 72, 184, 114, 105, 1, 44, 98, 1, 46, 116, 72, 49, 4, 36, 49, 246, 86, 106, 14, 94, 72, 1, - 230, 86, 106, 19, 94, 72, 1, 230, 86, 106, 24, 94, 72, 1, 230, 86, 72, 137, 230, 49, 210, - 106, 59, 88, 15, 5, - ]; - - Ok(Some(1)) - $$; - "#; - Spi::run(definition)?; - let result = Spi::get_one::("SELECT not_mangled();\n"); - assert_eq!(Ok(Some(1)), result); - Ok(()) - } - - #[pg_test] - #[should_panic(expected = "issue78 works")] - fn test_issue_78() -> spi::Result<()> { - let sql = r#"CREATE OR REPLACE FUNCTION raise_error() RETURNS TEXT - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - pgrx::error!("issue78 works"); - Ok(Some("hi".to_string())) - $$;"#; - Spi::run(sql)?; - Spi::get_one::("SELECT raise_error()")?; - Ok(()) - } - - #[pg_test] - fn test_issue_79() -> spi::Result<()> { - let sql = r#" - create or replace function fn1(i int) returns int strict language plrust as $$ - [code] - notice!("{}", "fn1 started"); - let cmd = format!("select fn2({})", i); - Spi::connect(|client| - { - client.select(&cmd, None, None); - }); - notice!("{}", "fn1 finished"); - Ok(Some(1)) - $$; - - create or replace function fn2(i int) returns int strict language plrust as $$ - [code] - notice!("{}", "fn2 started"); - notice!("{}", "fn2 finished"); - Ok(Some(2)) - $$; - "#; - Spi::run(sql)?; - assert_eq!(Ok(Some(1)), Spi::get_one::("SELECT fn1(1)")); - Ok(()) - } - - #[pg_test] - fn replace_function() -> spi::Result<()> { - Spi::run("CREATE FUNCTION replace_me() RETURNS int LANGUAGE plrust AS $$ Ok(Some(1)) $$")?; - assert_eq!(Ok(Some(1)), Spi::get_one("SELECT replace_me()")); - - Spi::run( - "CREATE OR REPLACE FUNCTION replace_me() RETURNS int LANGUAGE plrust AS $$ Ok(Some(2)) $$", - )?; - assert_eq!(Ok(Some(2)), Spi::get_one("SELECT replace_me()")); - Ok(()) - } - - #[pg_test] - fn test_point() -> spi::Result<()> { - Spi::run( - r#"CREATE FUNCTION test_point(p point) RETURNS point LANGUAGE plrust AS $$ Ok(p) $$"#, - )?; - let p = Spi::get_one::("SELECT test_point('42, 99'::point);")? - .expect("SPI result was null"); - assert_eq!(p.x, 42.0); - assert_eq!(p.y, 99.0); - Ok(()) - } - - #[pg_test] - fn test_box() -> spi::Result<()> { - Spi::run(r#"CREATE FUNCTION test_box(b box) RETURNS box LANGUAGE plrust AS $$ Ok(b) $$"#)?; - let b = Spi::get_one::("SELECT test_box('1,2,3,4'::box);")? - .expect("SPI result was null"); - assert_eq!(b.high.x, 3.0); - assert_eq!(b.high.y, 4.0); - assert_eq!(b.low.x, 1.0); - assert_eq!(b.low.y, 2.0); - Ok(()) - } - - #[pg_test] - fn test_uuid() -> spi::Result<()> { - Spi::run( - r#"CREATE FUNCTION test_uuid(u uuid) RETURNS uuid LANGUAGE plrust AS $$ Ok(u) $$"#, - )?; - let u = Spi::get_one::( - "SELECT test_uuid('e4176a4d-790c-4750-85b7-665d72471173'::uuid);", - )? - .expect("SPI result was null"); - assert_eq!( - u, - pgrx::Uuid::from_bytes([ - 0xe4, 0x17, 0x6a, 0x4d, 0x79, 0x0c, 0x47, 0x50, 0x85, 0xb7, 0x66, 0x5d, 0x72, 0x47, - 0x11, 0x73 - ]) - ); - - Ok(()) - } - - #[pg_test] - fn test_int4range() -> spi::Result<()> { - Spi::run( - r#"CREATE FUNCTION test_int4range(r int4range) RETURNS int4range LANGUAGE plrust AS $$ Ok(r) $$"#, - )?; - let r = Spi::get_one::>("SELECT test_int4range('[1, 10)'::int4range);")? - .expect("SPI result was null"); - assert_eq!(r, (1..10).into()); - Ok(()) - } - - #[pg_test] - fn test_int8range() -> spi::Result<()> { - Spi::run( - r#"CREATE FUNCTION test_int8range(r int8range) RETURNS int8range LANGUAGE plrust AS $$ Ok(r) $$"#, - )?; - let r = Spi::get_one::>("SELECT test_int8range('[1, 10)'::int8range);")? - .expect("SPI result was null"); - assert_eq!(r, (1..10).into()); - Ok(()) - } - - #[pg_test] - fn test_numrange() -> spi::Result<()> { - Spi::run( - r#"CREATE FUNCTION test_numrange(r numrange) RETURNS numrange LANGUAGE plrust AS $$ Ok(r) $$"#, - )?; - let r = Spi::get_one::>("SELECT test_numrange('[1, 10]'::numrange);")? - .expect("SPI result was null"); - assert_eq!( - r, - Range::new( - AnyNumeric::try_from(1.0f32).unwrap(), - AnyNumeric::try_from(10.0f32).unwrap() - ) - ); - Ok(()) - } - - #[pg_test] - fn test_tid_roundtrip() -> spi::Result<()> { - Spi::run( - r#"CREATE FUNCTION tid_roundtrip(t tid) RETURNS tid LANGUAGE plrust AS $$ Ok(t) $$"#, - )?; - let tid = Spi::get_one::("SELECT tid_roundtrip('(42, 99)'::tid)")? - .expect("SPI result was null"); - let (blockno, offno) = pgrx::item_pointer_get_both(tid); - assert_eq!(blockno, 42); - assert_eq!(offno, 99); - Ok(()) - } - - #[pg_test] - fn test_return_bytea() -> spi::Result<()> { - Spi::run( - r#"CREATE FUNCTION return_bytea() RETURNS bytea LANGUAGE plrust AS $$ Ok(Some(vec![1,2,3])) $$"#, - )?; - let bytes = Spi::get_one::>("SELECT return_bytea()")?.expect("SPI result was null"); - assert_eq!(bytes, vec![1, 2, 3]); - Ok(()) - } - - #[pg_test] - fn test_cstring_roundtrip() -> Result<(), Box> { - use std::ffi::CStr; - Spi::run( - r#"CREATE FUNCTION cstring_roundtrip(s cstring) RETURNS cstring STRICT LANGUAGE plrust as $$ Ok(Some(s.into())) $$;"#, - )?; - let cstr = Spi::get_one::<&CStr>("SELECT cstring_roundtrip('hello')")? - .expect("SPI result was null"); - let expected = CStr::from_bytes_with_nul(b"hello\0")?; - assert_eq!(cstr, expected); - Ok(()) - } - - #[pg_test] - fn test_daterange() -> Result<(), Box> { - Spi::run( - r#"CREATE FUNCTION test_daterange(r daterange) RETURNS daterange LANGUAGE plrust AS $$ Ok(r) $$"#, - )?; - let r = Spi::get_one::>( - "SELECT test_daterange('[1977-03-20, 1980-01-01)'::daterange);", - )? - .expect("SPI result was null"); - assert_eq!( - r, - Range::new( - Date::new(1977, 3, 20)?, - RangeBound::Exclusive(Date::new(1980, 01, 01)?) - ) - ); - Ok(()) - } - - #[pg_test] - fn test_tsrange() -> Result<(), Box> { - Spi::run( - r#"CREATE FUNCTION test_tsrange(p tsrange) RETURNS tsrange LANGUAGE plrust AS $$ Ok(p) $$"#, - )?; - let r = Spi::get_one::>( - "SELECT test_tsrange('[1977-03-20, 1980-01-01)'::tsrange);", - )? - .expect("SPI result was null"); - assert_eq!( - r, - Range::new( - Timestamp::new(1977, 3, 20, 0, 0, 0.0)?, - RangeBound::Exclusive(Timestamp::new(1980, 01, 01, 0, 0, 0.0)?) - ) - ); - Ok(()) - } - - #[pg_test] - fn test_tstzrange() -> Result<(), Box> { - Spi::run( - r#"CREATE FUNCTION test_tstzrange(p tstzrange) RETURNS tstzrange LANGUAGE plrust AS $$ Ok(p) $$"#, - )?; - let r = Spi::get_one::>( - "SELECT test_tstzrange('[1977-03-20, 1980-01-01)'::tstzrange);", - )? - .expect("SPI result was null"); - assert_eq!( - r, - Range::new( - TimestampWithTimeZone::new(1977, 3, 20, 0, 0, 0.0)?, - RangeBound::Exclusive(TimestampWithTimeZone::new(1980, 01, 01, 0, 0, 0.0)?) - ) - ); - Ok(()) - } - - #[pg_test] - fn test_interval() -> Result<(), Box> { - Spi::run( - r#"CREATE FUNCTION get_interval_hours(i interval) RETURNS numeric STRICT LANGUAGE plrust AS $$ Ok(i.extract_part(DateTimeParts::Hour)) $$"#, - )?; - let hours = - Spi::get_one::("SELECT get_interval_hours('3 days 9 hours 12 seconds')")? - .expect("SPI result was null"); - assert_eq!(hours, AnyNumeric::from(9)); - Ok(()) - } - - #[cfg(feature = "trusted")] - #[pg_test] - #[search_path(@extschema@)] - fn postgrestd_net_is_unsupported() -> spi::Result<()> { - let sql = r#" - create or replace function pt106() returns text - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - [code] - use std::net::TcpStream; - - Ok(TcpStream::connect("127.0.0.1:22").err().map(|e| e.to_string())) - $$"#; - Spi::run(sql)?; - let string = Spi::get_one::("SELECT pt106()")?.expect("Unconditional return"); - assert_eq!("operation not supported on this platform", &string); - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic(expected = "PL/Rust does not support unnamed arguments")] - fn unnamed_args() -> spi::Result<()> { - Spi::run("CREATE FUNCTION unnamed_arg(int) RETURNS int LANGUAGE plrust as $$ Ok(None) $$;") - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic(expected = "PL/Rust does not support unnamed arguments")] - fn named_unnamed_args() -> spi::Result<()> { - Spi::run("CREATE FUNCTION named_unnamed_arg(bob text, int) RETURNS int LANGUAGE plrust as $$ Ok(None) $$;") - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic( - expected = "is an invalid Rust identifier and cannot be used as an argument name" - )] - fn invalid_arg_identifier() -> spi::Result<()> { - Spi::run("CREATE FUNCTION invalid_arg_identifier(\"this isn't a valid rust identifier\" int) RETURNS int LANGUAGE plrust as $$ Ok(None) $$;") - } - - #[pg_test] - fn test_srf_one_col() -> spi::Result<()> { - Spi::run( - "CREATE FUNCTION srf_one_col() RETURNS TABLE (a int) LANGUAGE plrust AS $$ - Ok(Some(TableIterator::new(vec![( Some(1), )].into_iter()))) - $$;", - )?; - - let a = Spi::get_one::("SELECT * FROM srf_one_col()")?; - assert_eq!(a, Some(1)); - - Ok(()) - } - - #[pg_test] - fn test_srf_two_col() -> spi::Result<()> { - Spi::run( - "CREATE FUNCTION srf_two_col() RETURNS TABLE (a int, b int) LANGUAGE plrust AS $$ - Ok(Some(TableIterator::new(vec![(Some(1), Some(2))].into_iter()))) - $$;", - )?; - - let (a, b) = Spi::get_two::("SELECT * FROM srf_two_col()")?; - assert_eq!(a, Some(1)); - assert_eq!(b, Some(2)); - - Ok(()) - } - - #[pg_test] - fn test_udt() -> spi::Result<()> { - Spi::run( - r#" -CREATE TYPE person AS ( - name text, - age float8 -); - -create function make_person(name text, age float8) returns person - strict parallel safe - language plrust as -$$ - // create the Heap Tuple representation of the SQL type `person` - let mut p = PgHeapTuple::new_composite_type("person")?; - - // set a few of its attributes - // - // Runtime errors can occur if the attribute name is invalid or if the Rust type of the value - // is not compatible with the backing SQL type for that attribute. Hence the use of the `?` operator - p.set_by_name("name", name)?; - p.set_by_name("age", age)?; - - // return the `person` - Ok(Some(p)) -$$; - -create function get_person_name(p person) returns text - strict parallel safe - language plrust as -$$ - // `p` is a `PgHeapTuple` over the underlying data for `person` - Ok(p.get_by_name("name")?) -$$; - -create function get_person_age(p person) returns float8 - strict parallel safe - language plrust as -$$ - // `p` is a `PgHeapTuple` over the underlying data for `person` - Ok(p.get_by_name("age")?) -$$; - -create function get_person_attribute(p person, attname text) returns text - strict parallel safe - language plrust as -$$ - match attname.to_lowercase().as_str() { - "age" => { - let age:Option = p.get_by_name("age")?; - Ok(age.map(|v| v.to_string())) - }, - "name" => { - Ok(p.get_by_name("name")?) - }, - _ => panic!("unknown attribute: `{attname}`") - } -$$; - -create operator ->> (function = get_person_attribute, leftarg = person, rightarg = text); - -create table people -( - id serial8 not null primary key, - p person -); - -insert into people (p) values (make_person('Johnny', 46.24)); -insert into people (p) values (make_person('Joe', 99.09)); -insert into people (p) values (make_person('Dr. Beverly Crusher of the Starship Enterprise', 32.0)); - "#, - )?; - - let johnny = Spi::get_one::>( - "SELECT p FROM people WHERE p->>'name' = 'Johnny';", - )? - .expect("SPI result was null"); - - let age = johnny.get_by_name::("age")?.expect("age was null"); - assert_eq!(age, 46.24); - - Ok(()) - } } #[cfg(any(test, feature = "pg_test"))] diff --git a/plrust-tests/src/matches.rs b/plrust-tests/src/matches.rs new file mode 100644 index 00000000..84f272dc --- /dev/null +++ b/plrust-tests/src/matches.rs @@ -0,0 +1,115 @@ + +/* +Portions Copyright 2020-2021 ZomboDB, LLC. +Portions Copyright 2021-2023 Technology Concepts & Design, Inc. + +All rights reserved. + +Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. +*/ + +#[cfg(any(test, feature = "pg_test"))] +#[pgrx::pg_schema] +mod tests { + use pgrx:: prelude::*; + + #[pg_test] +#[search_path(@extschema@)] +fn plrust_call_1st() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION ret_1st(a int, b int) + RETURNS int AS + $$ + Ok(a) + $$ LANGUAGE plrust; + "#; + Spi::run(definition)?; + let result_1 = Spi::get_one::("SELECT ret_1st(1, 2);\n"); + assert_eq!(Ok(Some(1)), result_1); // may get: Some(1) + Ok(()) +} + +#[pg_test] +#[search_path(@extschema@)] +fn plrust_call_2nd() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION ret_2nd(a int, b int) + RETURNS int AS + $$ + Ok(b) + $$ LANGUAGE plrust; + "#; + Spi::run(definition)?; + let result_2 = Spi::get_one::("SELECT ret_2nd(1, 2);\n"); + assert_eq!(Ok(Some(2)), result_2); // may get: Some(2) + Ok(()) +} + +#[pg_test] +#[search_path(@extschema@)] +fn plrust_call_me() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION pick_ret(a int, b int, pick int) + RETURNS int AS + $$ + Ok(match pick { + Some(0) => a, + Some(1) => b, + _ => None, + }) + $$ LANGUAGE plrust; + "#; + Spi::run(definition)?; + let result_a = Spi::get_one::("SELECT pick_ret(3, 4, 0);"); + let result_b = Spi::get_one::("SELECT pick_ret(5, 6, 1);"); + let result_c = Spi::get_one::("SELECT pick_ret(7, 8, 2);"); + let result_z = Spi::get_one::("SELECT pick_ret(9, 99, -1);"); + assert_eq!(Ok(Some(3)), result_a); // may get: Some(4) or None + assert_eq!(Ok(Some(6)), result_b); // may get: None + assert_eq!(Ok(None), result_c); + assert_eq!(Ok(None), result_z); + Ok(()) +} + +#[pg_test] +#[search_path(@extschema@)] +fn plrust_call_me_call_me() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION ret_1st(a int, b int) + RETURNS int AS + $$ + Ok(a) + $$ LANGUAGE plrust; + + CREATE FUNCTION ret_2nd(a int, b int) + RETURNS int AS + $$ + Ok(b) + $$ LANGUAGE plrust; + + CREATE FUNCTION pick_ret(a int, b int, pick int) + RETURNS int AS + $$ + Ok(match pick { + Some(0) => a, + Some(1) => b, + _ => None, + }) + $$ LANGUAGE plrust; + "#; + Spi::run(definition)?; + let result_1 = Spi::get_one::("SELECT ret_1st(1, 2);\n"); + let result_2 = Spi::get_one::("SELECT ret_2nd(1, 2);\n"); + let result_a = Spi::get_one::("SELECT pick_ret(3, 4, 0);"); + let result_b = Spi::get_one::("SELECT pick_ret(5, 6, 1);"); + let result_c = Spi::get_one::("SELECT pick_ret(7, 8, 2);"); + let result_z = Spi::get_one::("SELECT pick_ret(9, 99, -1);"); + assert_eq!(Ok(None), result_z); + assert_eq!(Ok(None), result_c); + assert_eq!(Ok(Some(6)), result_b); // may get: None + assert_eq!(Ok(Some(3)), result_a); // may get: Some(4) or None + assert_eq!(Ok(Some(2)), result_2); // may get: Some(1) + assert_eq!(Ok(Some(1)), result_1); // may get: Some(2) + Ok(()) +} +} \ No newline at end of file diff --git a/plrust-tests/src/panics.rs b/plrust-tests/src/panics.rs new file mode 100644 index 00000000..faa65cf6 --- /dev/null +++ b/plrust-tests/src/panics.rs @@ -0,0 +1,63 @@ + +/* +Portions Copyright 2020-2021 ZomboDB, LLC. +Portions Copyright 2021-2023 Technology Concepts & Design, Inc. + +All rights reserved. + +Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. +*/ + +#[cfg(any(test, feature = "pg_test"))] +#[pgrx::pg_schema] +mod tests { + use pgrx:: prelude::*; + + #[pg_test] +#[search_path(@extschema@)] +#[should_panic = "yup"] +fn pgrx_can_panic() { + panic!("yup") +} + +#[pg_test] +#[search_path(@extschema@)] +#[should_panic = "yup"] +fn plrust_can_panic() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION shut_up_and_explode() + RETURNS text AS + $$ + panic!("yup"); + Ok(None) + $$ LANGUAGE plrust; + "#; + + Spi::run(definition)?; + let retval = Spi::get_one::("SELECT shut_up_and_explode();\n"); + assert_eq!(retval, Ok(None)); + Ok(()) +} + #[pg_test] + #[search_path(@extschema@)] + #[should_panic = "xxx"] + #[ignore] + fn plrust_pgloglevel_dont_allcaps_panic() -> spi::Result<()> { + // This test attempts to annihilate the database. + // It relies on the existing assumption that tests are run in the same Postgres instance, + // so this test will make all tests "flaky" if Postgres suddenly goes down with it. + let definition = r#" + CREATE FUNCTION dont_allcaps_panic() + RETURNS text AS + $$ + ereport!(PANIC, PgSqlErrorCode::ERRCODE_INTERNAL_ERROR, "If other tests completed, PL/Rust did not actually destroy the entire database, \ + But if you see this in the error output, something might be wrong."); + Ok(Some("lol".into())) + $$ LANGUAGE plrust; + "#; + Spi::run(definition)?; + let retval = Spi::get_one::("SELECT dont_allcaps_panic();\n"); + assert_eq!(retval, Ok(Some("lol".into()))); + Ok(()) + } +} \ No newline at end of file diff --git a/plrust-tests/src/range.rs b/plrust-tests/src/range.rs new file mode 100644 index 00000000..bc903c4c --- /dev/null +++ b/plrust-tests/src/range.rs @@ -0,0 +1,54 @@ + +/* +Portions Copyright 2020-2021 ZomboDB, LLC. +Portions Copyright 2021-2023 Technology Concepts & Design, Inc. + +All rights reserved. + +Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. +*/ + +#[cfg(any(test, feature = "pg_test"))] +#[pgrx::pg_schema] +mod tests { + use pgrx:: prelude::*; + + #[pg_test] + fn test_int4range() -> spi::Result<()> { + Spi::run( + r#"CREATE FUNCTION test_int4range(r int4range) RETURNS int4range LANGUAGE plrust AS $$ Ok(r) $$"#, + )?; + let r = Spi::get_one::>("SELECT test_int4range('[1, 10)'::int4range);")? + .expect("SPI result was null"); + assert_eq!(r, (1..10).into()); + Ok(()) + } + + #[pg_test] + fn test_int8range() -> spi::Result<()> { + Spi::run( + r#"CREATE FUNCTION test_int8range(r int8range) RETURNS int8range LANGUAGE plrust AS $$ Ok(r) $$"#, + )?; + let r = Spi::get_one::>("SELECT test_int8range('[1, 10)'::int8range);")? + .expect("SPI result was null"); + assert_eq!(r, (1..10).into()); + Ok(()) + } + + #[pg_test] + fn test_numrange() -> spi::Result<()> { + Spi::run( + r#"CREATE FUNCTION test_numrange(r numrange) RETURNS numrange LANGUAGE plrust AS $$ Ok(r) $$"#, + )?; + let r = Spi::get_one::>("SELECT test_numrange('[1, 10]'::numrange);")? + .expect("SPI result was null"); + assert_eq!( + r, + Range::new( + AnyNumeric::try_from(1.0f32).unwrap(), + AnyNumeric::try_from(10.0f32).unwrap() + ) + ); + Ok(()) + } +} \ No newline at end of file diff --git a/plrust-tests/src/recursion.rs b/plrust-tests/src/recursion.rs new file mode 100644 index 00000000..d19fdd70 --- /dev/null +++ b/plrust-tests/src/recursion.rs @@ -0,0 +1,15 @@ + +/* +Portions Copyright 2020-2021 ZomboDB, LLC. +Portions Copyright 2021-2023 Technology Concepts & Design, Inc. + +All rights reserved. + +Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. +*/ + +#[cfg(any(test, feature = "pg_test"))] +#[pgrx::pg_schema] +mod tests { + +} \ No newline at end of file diff --git a/plrust-tests/src/return_values.rs b/plrust-tests/src/return_values.rs new file mode 100644 index 00000000..0fb95fc8 --- /dev/null +++ b/plrust-tests/src/return_values.rs @@ -0,0 +1,84 @@ + + +/* +Portions Copyright 2020-2021 ZomboDB, LLC. +Portions Copyright 2021-2023 Technology Concepts & Design, Inc. + +All rights reserved. + +Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. +*/ + +#[cfg(any(test, feature = "pg_test"))] +#[pgrx::pg_schema] +mod tests { + use pgrx:: prelude::*; + + #[pg_test] + #[search_path(@ extschema @)] + fn plrust_returns_setof() -> spi::Result<()> { + let definition = r#" + CREATE OR REPLACE FUNCTION boop_srf(names TEXT[]) RETURNS SETOF TEXT + IMMUTABLE STRICT + LANGUAGE PLRUST AS + $$ + Ok(Some(::pgrx::iter::SetOfIterator::new(names.into_iter().map(|maybe| maybe.map(|name| name.to_string() + " was booped!"))))) + $$; + "#; + Spi::run(definition)?; + + let retval: spi::Result<_> = Spi::connect(|client| { + let mut table = client.select( + "SELECT * FROM boop_srf(ARRAY['Nami', 'Brandy'])", + None, + None, + )?; + + let mut found = vec![]; + while table.next().is_some() { + let value = table.get_one::()?; + found.push(value) + } + + Ok(Some(found)) + }); + + assert_eq!( + retval, + Ok(Some(vec![ + Some("Nami was booped!".into()), + Some("Brandy was booped!".into()), + ])) + ); + Ok(()) + } + + #[pg_test] + fn test_srf_one_col() -> spi::Result<()> { + Spi::run( + "CREATE FUNCTION srf_one_col() RETURNS TABLE (a int) LANGUAGE plrust AS $$ + Ok(Some(TableIterator::new(vec![( Some(1), )].into_iter()))) + $$;", + )?; + + let a = Spi::get_one::("SELECT * FROM srf_one_col()")?; + assert_eq!(a, Some(1)); + + Ok(()) + } + + #[pg_test] + fn test_srf_two_col() -> spi::Result<()> { + Spi::run( + "CREATE FUNCTION srf_two_col() RETURNS TABLE (a int, b int) LANGUAGE plrust AS $$ + Ok(Some(TableIterator::new(vec![(Some(1), Some(2))].into_iter()))) + $$;", + )?; + + let (a, b) = Spi::get_two::("SELECT * FROM srf_two_col()")?; + assert_eq!(a, Some(1)); + assert_eq!(b, Some(2)); + + Ok(()) + } +} \ No newline at end of file diff --git a/plrust-tests/src/round_trip.rs b/plrust-tests/src/round_trip.rs new file mode 100644 index 00000000..606d7a70 --- /dev/null +++ b/plrust-tests/src/round_trip.rs @@ -0,0 +1,97 @@ + +/* +Portions Copyright 2020-2021 ZomboDB, LLC. +Portions Copyright 2021-2023 Technology Concepts & Design, Inc. + +All rights reserved. + +Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. +*/ + +#[cfg(any(test, feature = "pg_test"))] +#[pgrx::pg_schema] +mod tests { + use pgrx:: prelude::*; + use std::error::Error; + + #[pg_test] + fn test_tid_roundtrip() -> spi::Result<()> { + Spi::run( + r#"CREATE FUNCTION tid_roundtrip(t tid) RETURNS tid LANGUAGE plrust AS $$ Ok(t) $$"#, + )?; + let tid = Spi::get_one::("SELECT tid_roundtrip('(42, 99)'::tid)")? + .expect("SPI result was null"); + let (blockno, offno) = pgrx::item_pointer_get_both(tid); + assert_eq!(blockno, 42); + assert_eq!(offno, 99); + Ok(()) + } + + #[pg_test] + fn test_return_bytea() -> spi::Result<()> { + Spi::run( + r#"CREATE FUNCTION return_bytea() RETURNS bytea LANGUAGE plrust AS $$ Ok(Some(vec![1,2,3])) $$"#, + )?; + let bytes = Spi::get_one::>("SELECT return_bytea()")?.expect("SPI result was null"); + assert_eq!(bytes, vec![1, 2, 3]); + Ok(()) + } + + #[pg_test] + fn test_cstring_roundtrip() -> Result<(), Box> { + use std::ffi::CStr; + Spi::run( + r#"CREATE FUNCTION cstring_roundtrip(s cstring) RETURNS cstring STRICT LANGUAGE plrust as $$ Ok(Some(s.into())) $$;"#, + )?; + let cstr = Spi::get_one::<&CStr>("SELECT cstring_roundtrip('hello')")? + .expect("SPI result was null"); + let expected = CStr::from_bytes_with_nul(b"hello\0")?; + assert_eq!(cstr, expected); + Ok(()) + } + + #[pg_test] + fn test_point() -> spi::Result<()> { + Spi::run( + r#"CREATE FUNCTION test_point(p point) RETURNS point LANGUAGE plrust AS $$ Ok(p) $$"#, + )?; + let p = Spi::get_one::("SELECT test_point('42, 99'::point);")? + .expect("SPI result was null"); + assert_eq!(p.x, 42.0); + assert_eq!(p.y, 99.0); + Ok(()) + } + + #[pg_test] + fn test_box() -> spi::Result<()> { + Spi::run(r#"CREATE FUNCTION test_box(b box) RETURNS box LANGUAGE plrust AS $$ Ok(b) $$"#)?; + let b = Spi::get_one::("SELECT test_box('1,2,3,4'::box);")? + .expect("SPI result was null"); + assert_eq!(b.high.x, 3.0); + assert_eq!(b.high.y, 4.0); + assert_eq!(b.low.x, 1.0); + assert_eq!(b.low.y, 2.0); + Ok(()) + } + + #[pg_test] + fn test_uuid() -> spi::Result<()> { + Spi::run( + r#"CREATE FUNCTION test_uuid(u uuid) RETURNS uuid LANGUAGE plrust AS $$ Ok(u) $$"#, + )?; + let u = Spi::get_one::( + "SELECT test_uuid('e4176a4d-790c-4750-85b7-665d72471173'::uuid);", + )? + .expect("SPI result was null"); + assert_eq!( + u, + pgrx::Uuid::from_bytes([ + 0xe4, 0x17, 0x6a, 0x4d, 0x79, 0x0c, 0x47, 0x50, 0x85, 0xb7, 0x66, 0x5d, 0x72, 0x47, + 0x11, 0x73 + ]) + ); + + Ok(()) + } + +} \ No newline at end of file diff --git a/plrust-tests/src/time_and_dates.rs b/plrust-tests/src/time_and_dates.rs new file mode 100644 index 00000000..290323e8 --- /dev/null +++ b/plrust-tests/src/time_and_dates.rs @@ -0,0 +1,86 @@ + +/* +Portions Copyright 2020-2021 ZomboDB, LLC. +Portions Copyright 2021-2023 Technology Concepts & Design, Inc. + +All rights reserved. + +Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. +*/ + +#[cfg(any(test, feature = "pg_test"))] +#[pgrx::pg_schema] +mod tests { + use pgrx:: prelude::*; + use std::error::Error; + + #[pg_test] + fn test_daterange() -> Result<(), Box> { + Spi::run( + r#"CREATE FUNCTION test_daterange(r daterange) RETURNS daterange LANGUAGE plrust AS $$ Ok(r) $$"#, + )?; + let r = Spi::get_one::>( + "SELECT test_daterange('[1977-03-20, 1980-01-01)'::daterange);", + )? + .expect("SPI result was null"); + assert_eq!( + r, + Range::new( + Date::new(1977, 3, 20)?, + RangeBound::Exclusive(Date::new(1980, 01, 01)?) + ) + ); + Ok(()) + } + + #[pg_test] + fn test_tsrange() -> Result<(), Box> { + Spi::run( + r#"CREATE FUNCTION test_tsrange(p tsrange) RETURNS tsrange LANGUAGE plrust AS $$ Ok(p) $$"#, + )?; + let r = Spi::get_one::>( + "SELECT test_tsrange('[1977-03-20, 1980-01-01)'::tsrange);", + )? + .expect("SPI result was null"); + assert_eq!( + r, + Range::new( + Timestamp::new(1977, 3, 20, 0, 0, 0.0)?, + RangeBound::Exclusive(Timestamp::new(1980, 01, 01, 0, 0, 0.0)?) + ) + ); + Ok(()) + } + + #[pg_test] + fn test_tstzrange() -> Result<(), Box> { + Spi::run( + r#"CREATE FUNCTION test_tstzrange(p tstzrange) RETURNS tstzrange LANGUAGE plrust AS $$ Ok(p) $$"#, + )?; + let r = Spi::get_one::>( + "SELECT test_tstzrange('[1977-03-20, 1980-01-01)'::tstzrange);", + )? + .expect("SPI result was null"); + assert_eq!( + r, + Range::new( + TimestampWithTimeZone::new(1977, 3, 20, 0, 0, 0.0)?, + RangeBound::Exclusive(TimestampWithTimeZone::new(1980, 01, 01, 0, 0, 0.0)?) + ) + ); + Ok(()) + } + + #[pg_test] + fn test_interval() -> Result<(), Box> { + Spi::run( + r#"CREATE FUNCTION get_interval_hours(i interval) RETURNS numeric STRICT LANGUAGE plrust AS $$ Ok(i.extract_part(DateTimeParts::Hour)) $$"#, + )?; + let hours = + Spi::get_one::("SELECT get_interval_hours('3 days 9 hours 12 seconds')")? + .expect("SPI result was null"); + assert_eq!(hours, AnyNumeric::from(9)); + Ok(()) + } + +} \ No newline at end of file diff --git a/plrust-tests/src/trusted.rs b/plrust-tests/src/trusted.rs new file mode 100644 index 00000000..f6ffc4ac --- /dev/null +++ b/plrust-tests/src/trusted.rs @@ -0,0 +1,236 @@ + +/* +Portions Copyright 2020-2021 ZomboDB, LLC. +Portions Copyright 2021-2023 Technology Concepts & Design, Inc. + +All rights reserved. + +Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. +*/ + +#[cfg(any(test, feature = "pg_test"))] +#[pgrx::pg_schema] +mod tests { + + #[cfg(feature = "trusted")] + #[pg_test] + #[search_path(@extschema@)] + fn postgrestd_dont_make_files() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION make_file(filename TEXT) RETURNS TEXT + LANGUAGE PLRUST AS + $$ + Ok(std::fs::File::create(filename.unwrap_or("/somewhere/files/dont/belong.txt")) + .err() + .map(|e| e.to_string())) + $$; + "#; + Spi::run(definition)?; + + let retval = Spi::get_one_with_args::( + r#" + SELECT make_file($1); + "#, + vec![( + PgBuiltInOids::TEXTOID.oid(), + "/an/evil/place/to/put/a/file.txt".into_datum(), + )], + ); + assert_eq!( + retval, + Ok(Some("operation not supported on this platform".to_string())) + ); + Ok(()) + } + + #[cfg(feature = "trusted")] + #[pg_test] + #[search_path(@extschema@)] + #[should_panic = "Failed to execute command"] + fn postgrestd_subprocesses_panic() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION say_hello() + RETURNS text AS + $$ + let out = std::process::Command::new("echo") + .arg("Hello world") + .stdout(std::process::Stdio::piped()) + .output() + .expect("Failed to execute command"); + Ok(Some(String::from_utf8_lossy(&out.stdout).to_string())) + $$ LANGUAGE plrust; + "#; + Spi::run(definition)?; + + let retval = Spi::get_one::("SELECT say_hello();\n"); + assert_eq!(retval, Ok(Some("Hello world\n".into()))); + Ok(()) + } + + #[cfg(feature = "trusted")] + #[pg_test] + #[search_path(@extschema@)] + #[should_panic = "error: the `include_str`, `include_bytes`, and `include` macros are forbidden"] + fn postgrestd_no_include_str() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION include_str() + RETURNS text AS + $$ + let s = include_str!("/etc/passwd"); + Ok(Some(s.into())) + $$ LANGUAGE plrust; + "#; + Spi::run(definition)?; + + let retval = Spi::get_one::("SELECT include_str();\n")?; + assert_eq!(retval.unwrap(), ""); + Ok(()) + } + + #[pg_test] + #[search_path(@extschema@)] + #[cfg(feature = "trusted")] + #[should_panic = "No such file or directory (os error 2)"] + fn plrustc_include_exists_no_access() { + // This file is created in CI and exists, but can only be accessed by + // root. Check that the actual access is reported as file not found (we + // should be ensuring that via + // `PLRUSTC_USER_CRATE_ALLOWED_SOURCE_PATHS`). We don't need to gate + // this test on CI, since the file is unlikely to exist outside of CI + // (so the test will pass). + let definition = r#" + CREATE FUNCTION include_no_access() + RETURNS text AS $$ + include!("/var/ci-stuff/secret_rust_files/const_foo.rs"); + Ok(Some(format!("{BAR}"))) + $$ LANGUAGE plrust; + "#; + Spi::run(definition).unwrap() + } + + #[pg_test] + #[search_path(@extschema@)] + #[cfg(feature = "trusted")] + #[should_panic = "No such file or directory (os error 2)"] + fn plrustc_include_exists_external() { + // This file is created in CI, exists, and can be accessed by anybody, + // but the actual access is forbidden via + // `PLRUSTC_USER_CRATE_ALLOWED_SOURCE_PATHS`. We don't need to gate this test on + // CI, since the file is unlikely to exist outside of CI, so the test + // will pass anyway. + let definition = r#" + CREATE FUNCTION include_exists_external() + RETURNS text AS $$ + include!("/var/ci-stuff/const_bar.rs"); + Ok(Some(format!("{BAR}"))) + $$ LANGUAGE plrust; + "#; + Spi::run(definition).unwrap(); + } + + #[pg_test] + #[search_path(@extschema@)] + #[cfg(feature = "trusted")] + #[should_panic = "No such file or directory (os error 2)"] + fn plrustc_include_made_up() { + // This file does not exist, and should be reported as such. + let definition = r#" + CREATE FUNCTION include_madeup() + RETURNS int AS $$ + include!("/made/up/path/lol.rs"); + Ok(Some(1)) + $$ LANGUAGE plrust; + "#; + Spi::run(definition).unwrap(); + } + + #[pg_test] + #[search_path(@extschema@)] + #[cfg(feature = "trusted")] + #[should_panic = "No such file or directory (os error 2)"] + fn plrustc_include_path_traversal() { + use std::path::PathBuf; + let workdir = crate::gucs::work_dir(); + let wd: PathBuf = workdir + .canonicalize() + .ok() + .expect("Failed to canonicalize workdir"); + // Produce a path that looks like + // `/allowed/path/here/../../../illegal/path/here` and check that it's + // rejected, in order to ensure we are not succeptable to path traversal + // attacks. + let mut evil_path = wd.clone(); + for _ in wd.ancestors().skip(1) { + evil_path.push(".."); + } + debug_assert_eq!( + evil_path + .canonicalize() + .ok() + .expect("Failed to produce unpath") + .to_str(), + Some("/") + ); + evil_path.push("var/ci-stuff/const_bar.rs"); + // This file does not exist, and should be reported as such. + let definition = format!( + r#"CREATE FUNCTION include_sneaky_traversal() + RETURNS int AS $$ + include!({evil_path:?}); + Ok(Some(1)) + $$ LANGUAGE plrust;"# + ); + Spi::run(&definition).unwrap(); + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic = "error: the `env` and `option_env` macros are forbidden"] + #[cfg(feature = "trusted")] + fn plrust_block_env() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION get_path() RETURNS text AS $$ + let path = env!("PATH"); + Ok(Some(path.to_string())) + $$ LANGUAGE plrust; + "#; + Spi::run(definition) + } + + #[pg_test] + #[search_path(@extschema@)] + #[should_panic = "error: the `env` and `option_env` macros are forbidden"] + #[cfg(feature = "trusted")] + fn plrust_block_option_env() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION try_get_path() RETURNS text AS $$ + match option_env!("PATH") { + None => Ok(None), + Some(s) => Ok(Some(s.to_string())) + } + $$ LANGUAGE plrust; + "#; + Spi::run(definition) + } + + #[cfg(feature = "trusted")] + #[pg_test] + #[search_path(@extschema@)] + fn postgrestd_net_is_unsupported() -> spi::Result<()> { + let sql = r#" + create or replace function pt106() returns text + IMMUTABLE STRICT + LANGUAGE PLRUST AS + $$ + [code] + use std::net::TcpStream; + + Ok(TcpStream::connect("127.0.0.1:22").err().map(|e| e.to_string())) + $$"#; + Spi::run(sql)?; + let string = Spi::get_one::("SELECT pt106()")?.expect("Unconditional return"); + assert_eq!("operation not supported on this platform", &string); + Ok(()) + } + +} \ No newline at end of file diff --git a/plrust-tests/src/user_defined_types.rs b/plrust-tests/src/user_defined_types.rs new file mode 100644 index 00000000..cfe129af --- /dev/null +++ b/plrust-tests/src/user_defined_types.rs @@ -0,0 +1,100 @@ + +/* +Portions Copyright 2020-2021 ZomboDB, LLC. +Portions Copyright 2021-2023 Technology Concepts & Design, Inc. + +All rights reserved. + +Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. +*/ + +#[cfg(any(test, feature = "pg_test"))] +#[pgrx::pg_schema] +mod tests { + use pgrx:: prelude::*; + + #[pg_test] + fn test_udt() -> spi::Result<()> { + Spi::run( + r#" +CREATE TYPE person AS ( + name text, + age float8 +); + +create function make_person(name text, age float8) returns person + strict parallel safe + language plrust as +$$ + // create the Heap Tuple representation of the SQL type `person` + let mut p = PgHeapTuple::new_composite_type("person")?; + + // set a few of its attributes + // + // Runtime errors can occur if the attribute name is invalid or if the Rust type of the value + // is not compatible with the backing SQL type for that attribute. Hence the use of the `?` operator + p.set_by_name("name", name)?; + p.set_by_name("age", age)?; + + // return the `person` + Ok(Some(p)) +$$; + +create function get_person_name(p person) returns text + strict parallel safe + language plrust as +$$ + // `p` is a `PgHeapTuple` over the underlying data for `person` + Ok(p.get_by_name("name")?) +$$; + +create function get_person_age(p person) returns float8 + strict parallel safe + language plrust as +$$ + // `p` is a `PgHeapTuple` over the underlying data for `person` + Ok(p.get_by_name("age")?) +$$; + +create function get_person_attribute(p person, attname text) returns text + strict parallel safe + language plrust as +$$ + match attname.to_lowercase().as_str() { + "age" => { + let age:Option = p.get_by_name("age")?; + Ok(age.map(|v| v.to_string())) + }, + "name" => { + Ok(p.get_by_name("name")?) + }, + _ => panic!("unknown attribute: `{attname}`") + } +$$; + +create operator ->> (function = get_person_attribute, leftarg = person, rightarg = text); + +create table people +( + id serial8 not null primary key, + p person +); + +insert into people (p) values (make_person('Johnny', 46.24)); +insert into people (p) values (make_person('Joe', 99.09)); +insert into people (p) values (make_person('Dr. Beverly Crusher of the Starship Enterprise', 32.0)); + "#, + )?; + + let johnny = Spi::get_one::>( + "SELECT p FROM people WHERE p->>'name' = 'Johnny';", + )? + .expect("SPI result was null"); + + let age = johnny.get_by_name::("age")?.expect("age was null"); + assert_eq!(age, 46.24); + + Ok(()) + } + +} \ No newline at end of file diff --git a/plrust-tests/src/versioning.rs b/plrust-tests/src/versioning.rs new file mode 100644 index 00000000..00bc432d --- /dev/null +++ b/plrust-tests/src/versioning.rs @@ -0,0 +1,54 @@ + +/* +Portions Copyright 2020-2021 ZomboDB, LLC. +Portions Copyright 2021-2023 Technology Concepts & Design, Inc. + +All rights reserved. + +Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. +*/ + +#[cfg(any(test, feature = "pg_test"))] +#[pgrx::pg_schema] +mod tests { + use pgrx:: prelude::*; + + #[pg_test] + #[cfg(not(feature = "sandboxed"))] + #[search_path(@extschema@)] + fn plrust_deps_supported_semver_parse() -> spi::Result<()> { + let definition = r#" + CREATE FUNCTION colorize(input TEXT) RETURNS TEXT + IMMUTABLE STRICT + LANGUAGE PLRUST AS + $$ + [dependencies] + owo-colors = ">2" + [code] + use owo_colors::OwoColorize; + Ok(Some(input.purple().to_string())) + $$; + "#; + Spi::run(definition)?; + + let retval = Spi::get_one_with_args::( + r#" + SELECT colorize($1); + "#, + vec![(PgBuiltInOids::TEXTOID.oid(), "Nami".into_datum())], + ); + assert!(retval.is_ok()); + assert!(retval.unwrap().is_some()); + + // Regression test: A previous version of PL/Rust would abort if this was called twice, so call it twice: + let retval = Spi::get_one_with_args::( + r#" + SELECT colorize($1); + "#, + vec![(PgBuiltInOids::TEXTOID.oid(), "Nami".into_datum())], + ); + assert!(retval.is_ok()); + assert!(retval.unwrap().is_some()); + Ok(()) + } +} \ No newline at end of file From 84205baa1fbf6909ec21d8612a7312847c291f96 Mon Sep 17 00:00:00 2001 From: Dave Selph Date: Fri, 14 Jul 2023 13:50:27 -0600 Subject: [PATCH 03/32] update pgrx --- plrust-tests/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plrust-tests/Cargo.toml b/plrust-tests/Cargo.toml index 69f8deec..ee4df595 100644 --- a/plrust-tests/Cargo.toml +++ b/plrust-tests/Cargo.toml @@ -14,11 +14,11 @@ pg15 = ["pgrx/pg15", "pgrx-tests/pg15" ] pg_test = [] [dependencies] -pgrx = "=0.9.5" +pgrx = "=0.9.7" tempdir = "0.3.7" once_cell = "1.18.0" [dev-dependencies] -pgrx-tests = "=0.9.5" +pgrx-tests = "=0.9.7" tempdir = "0.3.7" once_cell = "1.18.0" From 8b9b75b831b73f86a5a55f542771980681fe315d Mon Sep 17 00:00:00 2001 From: Dave Selph Date: Tue, 18 Jul 2023 13:07:02 -0600 Subject: [PATCH 04/32] ci testing --- .github/workflows/ci.yml | 2 +- Cargo.lock | 68 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2fefc4b0..631eda23 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -326,7 +326,7 @@ jobs: - name: Test PL/rust as "untrusted" if: matrix.target == 'host' - run: cargo test --all --features "pg$PG_VER" --no-default-features + run: cargo pgrx test --all --features "pg$PG_VER" --no-default-features - name: Test PL/rust as "trusted" (inc. postgrestd) if: matrix.target == 'postgrestd' diff --git a/Cargo.lock b/Cargo.lock index d780ea69..3e6b0361 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -511,6 +511,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + [[package]] name = "funty" version = "2.0.0" @@ -1226,7 +1232,7 @@ dependencies = [ "hmac", "md-5", "memchr", - "rand", + "rand 0.8.5", "sha2", "stringprep", ] @@ -1282,6 +1288,19 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + [[package]] name = "rand" version = "0.8.5" @@ -1290,7 +1309,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -1300,9 +1319,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", ] +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + [[package]] name = "rand_core" version = "0.6.4" @@ -1334,6 +1368,15 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -1407,6 +1450,15 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -1696,6 +1748,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +dependencies = [ + "rand 0.4.6", + "remove_dir_all", +] + [[package]] name = "tempfile" version = "3.6.0" From 0632b5f21210505117915a2864530f0bf6fe5084 Mon Sep 17 00:00:00 2001 From: Dave Selph Date: Thu, 20 Jul 2023 11:15:46 -0600 Subject: [PATCH 05/32] pgx update to 0.9.8 --- Cargo.lock | 25 +++++++++++++------------ plrust-tests/Cargo.toml | 4 ++-- plrust-trusted-pgrx/Cargo.toml | 2 +- plrust/Cargo.toml | 4 ++-- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3e6b0361..64c760fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1015,9 +1015,9 @@ dependencies = [ [[package]] name = "pgrx" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6186d4aa5911be4c00b52e555779deece35a7563c87fcfe794407dc2e9cc4dc1" +checksum = "0e80e25d7f85997d5d24c824297529bcb73231bbdc74d77906004d41cd3ffee3" dependencies = [ "atomic-traits", "bitflags 2.3.3", @@ -1040,9 +1040,9 @@ dependencies = [ [[package]] name = "pgrx-macros" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479a66a8c582e0fdf101178473315cb13eaa10829c154db742c35ec0279cdaec" +checksum = "999ef782b36bb511806277f2a74a7f9e075edcad8c9439a3b90f4c90384f2a29" dependencies = [ "pgrx-sql-entity-graph", "proc-macro2", @@ -1052,9 +1052,9 @@ dependencies = [ [[package]] name = "pgrx-pg-config" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45c557631217a13859e223899c01d35982ef0c860ee5ab65af496f830b1316" +checksum = "a7b27ccd3d892e3b27bcb7a6e2bf86588d82fad3da622db168261bc6b534a737" dependencies = [ "cargo_toml", "dirs", @@ -1070,9 +1070,9 @@ dependencies = [ [[package]] name = "pgrx-pg-sys" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dde896a17c638b6475d6fc12b571a176013a8486437bbc8a64ac2afb8ba5d58" +checksum = "c0767fdf6930ba6fa2d1b1934313aae3694b70732e0b6169ece26b03de27f8dc" dependencies = [ "bindgen", "eyre", @@ -1092,9 +1092,9 @@ dependencies = [ [[package]] name = "pgrx-sql-entity-graph" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e9abc71b018d90aa9b7a34fedf48b76da5d55c04d2ed2288096827bebbf403" +checksum = "4d632abaa9c3da42e5e2a17a6268afb0449a7f655764c65e06695ee55763ff0e" dependencies = [ "convert_case", "eyre", @@ -1107,9 +1107,9 @@ dependencies = [ [[package]] name = "pgrx-tests" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39ac4ffedfa247f9d51421e4e2ac18c33d8d674350bad730f3fe5736bf298612" +checksum = "d44327bd084bcdc6fe4e72dfce8065e23b5b4522f36d63d14ee21c5000e7c73c" dependencies = [ "clap-cargo", "eyre", @@ -1120,6 +1120,7 @@ dependencies = [ "pgrx-macros", "pgrx-pg-config", "postgres", + "rand 0.8.5", "regex", "serde", "serde_json", diff --git a/plrust-tests/Cargo.toml b/plrust-tests/Cargo.toml index ee4df595..c31ba889 100644 --- a/plrust-tests/Cargo.toml +++ b/plrust-tests/Cargo.toml @@ -14,11 +14,11 @@ pg15 = ["pgrx/pg15", "pgrx-tests/pg15" ] pg_test = [] [dependencies] -pgrx = "=0.9.7" +pgrx = "=0.9.8" tempdir = "0.3.7" once_cell = "1.18.0" [dev-dependencies] -pgrx-tests = "=0.9.7" +pgrx-tests = "=0.9.8" tempdir = "0.3.7" once_cell = "1.18.0" diff --git a/plrust-trusted-pgrx/Cargo.toml b/plrust-trusted-pgrx/Cargo.toml index 2debe077..6cd11754 100644 --- a/plrust-trusted-pgrx/Cargo.toml +++ b/plrust-trusted-pgrx/Cargo.toml @@ -18,7 +18,7 @@ pg15 = ["pgrx/pg15"] [dependencies] # changing the pgrx version will likely require at least a minor version bump to this create -pgrx = { version = "=0.9.7", features = [ "no-schema-generation" ], default-features = false } +pgrx = { version = "=0.9.8", features = [ "no-schema-generation" ], default-features = false } [package.metadata.docs.rs] features = ["pg14"] diff --git a/plrust/Cargo.toml b/plrust/Cargo.toml index 999a3a65..671b0ce8 100644 --- a/plrust/Cargo.toml +++ b/plrust/Cargo.toml @@ -39,7 +39,7 @@ serde = "1.0.164" serde_json = "1.0.97" # pgrx core details -pgrx = { version = "=0.9.7" } +pgrx = { version = "=0.9.8" } # language handler support libloading = "0.8.0" @@ -66,6 +66,6 @@ memfd = "0.6.3" # for anonymously writing/loading user function .so [dev-dependencies] -pgrx-tests = { version = "=0.9.7" } +pgrx-tests = { version = "=0.9.8" } once_cell = "1.18.0" toml = "0.7.4" From b3e0dde59cbf89b3b2f21974e04cc932f67a97ae Mon Sep 17 00:00:00 2001 From: Dave Selph Date: Thu, 20 Jul 2023 13:16:30 -0600 Subject: [PATCH 06/32] ci testing --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 631eda23..63a445dd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -326,7 +326,7 @@ jobs: - name: Test PL/rust as "untrusted" if: matrix.target == 'host' - run: cargo pgrx test --all --features "pg$PG_VER" --no-default-features + run: cargo pgrx test "pg$PG_VER" --no-default-features - name: Test PL/rust as "trusted" (inc. postgrestd) if: matrix.target == 'postgrestd' From 0b7584751b43ca774d66e73e48848911389e6605 Mon Sep 17 00:00:00 2001 From: Dave Selph Date: Thu, 20 Jul 2023 15:06:16 -0600 Subject: [PATCH 07/32] ci testing --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 63a445dd..7b2df303 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -326,7 +326,7 @@ jobs: - name: Test PL/rust as "untrusted" if: matrix.target == 'host' - run: cargo pgrx test "pg$PG_VER" --no-default-features + run: cd plrust-tests && cargo pgrx test "pg$PG_VER" - name: Test PL/rust as "trusted" (inc. postgrestd) if: matrix.target == 'postgrestd' From 909e98467fc5fd75e90fa4dde55a7b0fa9716dce Mon Sep 17 00:00:00 2001 From: Dave Selph Date: Mon, 24 Jul 2023 17:17:24 -0600 Subject: [PATCH 08/32] ci testing --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b2df303..e42a4181 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -150,7 +150,7 @@ jobs: - name: Test PL/rust as "untrusted" if: matrix.target == 'host' - run: cargo test --all --features "pg$PG_VER" --no-default-features + run: cd plrust-tests && cargo pgrx test "pg$PG_VER" - name: Test PL/rust as "trusted" (inc. postgrestd) if: matrix.target == 'postgrestd' From 539f80649e008bfb5f7f2458545486c7d8627286 Mon Sep 17 00:00:00 2001 From: Dave Selph Date: Tue, 25 Jul 2023 12:44:46 -0600 Subject: [PATCH 09/32] ci testing --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e42a4181..42698040 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -326,7 +326,7 @@ jobs: - name: Test PL/rust as "untrusted" if: matrix.target == 'host' - run: cd plrust-tests && cargo pgrx test "pg$PG_VER" + run: cd plrust-tests && ./run-tests.sh "pg$PG_VER" - name: Test PL/rust as "trusted" (inc. postgrestd) if: matrix.target == 'postgrestd' From 5ffe1d0772ca4a202a2c9208be1c7d7a59c84a03 Mon Sep 17 00:00:00 2001 From: Dave Selph Date: Tue, 25 Jul 2023 16:29:33 -0600 Subject: [PATCH 10/32] add set to bash script `set -e` --- plrust-tests/run-tests.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plrust-tests/run-tests.sh b/plrust-tests/run-tests.sh index e7dca515..a90e6067 100755 --- a/plrust-tests/run-tests.sh +++ b/plrust-tests/run-tests.sh @@ -9,6 +9,8 @@ fi TEST_DIR=`pwd` +set -e + # install the plrust extension into the pgrx-managed postgres echo "============================" echo " installing plrust" From b0022de42e8c60add283b55a5faf8609034b7072 Mon Sep 17 00:00:00 2001 From: Dave Selph Date: Wed, 26 Jul 2023 12:46:57 -0600 Subject: [PATCH 11/32] ci testing --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 42698040..4e6abf82 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -150,7 +150,7 @@ jobs: - name: Test PL/rust as "untrusted" if: matrix.target == 'host' - run: cd plrust-tests && cargo pgrx test "pg$PG_VER" + run: cd plrust-tests && ./run-tests.sh "pg$PG_VER" - name: Test PL/rust as "trusted" (inc. postgrestd) if: matrix.target == 'postgrestd' From 3f6b82b45e3b0dd2a75701cf5d7c7106b538e451 Mon Sep 17 00:00:00 2001 From: Dave Selph Date: Thu, 27 Jul 2023 15:19:18 -0600 Subject: [PATCH 12/32] remove repeat tests --- plrust-tests/src/dependencies.rs | 73 +- plrust/src/tests.rs | 1462 ------------------------------ 2 files changed, 70 insertions(+), 1465 deletions(-) diff --git a/plrust-tests/src/dependencies.rs b/plrust-tests/src/dependencies.rs index 6a5f62da..b136a2ad 100644 --- a/plrust-tests/src/dependencies.rs +++ b/plrust-tests/src/dependencies.rs @@ -1,4 +1,3 @@ - /* Portions Copyright 2020-2021 ZomboDB, LLC. Portions Copyright 2021-2023 Technology Concepts & Design, Inc. @@ -11,7 +10,7 @@ Use of this source code is governed by the PostgreSQL license that can be found #[cfg(any(test, feature = "pg_test"))] #[pgrx::pg_schema] mod tests { - use pgrx:: prelude::*; + use pgrx::prelude::*; #[pg_test] #[cfg(not(feature = "sandboxed"))] @@ -100,4 +99,72 @@ mod tests { }); assert!(res.is_err()); } -} \ No newline at end of file + + #[pg_test] + fn test_allowed_dependencies() -> spi::Result<()> { + // Given the allowed list looks like this: + // owo-colors = "=3.5.0" + // tokio = { version = "=1.19.2", features = ["rt", "net"] } + // plutonium = "*" + // syn = { version = "=2.0.28", default-features = false } + // rand = ["=0.8.3", { version = ">0.8.4, <0.8.6", features = ["getrandom"] }] + let query = "SELECT * FROM plrust.allowed_dependencies();"; + + // The result will look like this: + // name | version | features | default_features + // ------------+----------------+-------------+------------------ + // owo-colors | =3.5.0 | {} | t + // plutonium | * | {} | t + // rand | =0.8.3 | {} | t + // rand | >0.8.4, <0.8.6 | {getrandom} | t + // syn | =2.0.28 | {} | f + // tokio | =1.19.2 | {rt,net} | t + + Spi::connect(|client| { + let expected_names = vec!["owo-colors", "plutonium", "rand", "rand", "syn", "tokio"]; + let expected_versions = vec![ + "=3.5.0", + "*", + "=0.8.3", + ">0.8.4, <0.8.6", + "=2.0.28", + "=1.19.2", + ]; + let expected_features = vec![ + vec![], + vec![], + vec![], + vec![String::from("getrandom")], + vec![], + vec![String::from("rt"), String::from("net")], + ]; + let expected_default_features = vec![true, true, true, true, false, true]; + let expected_len = expected_names.len(); + + let tup_table = client.select(query, None, None)?; + + assert_eq!(tup_table.len(), expected_len); + + for (i, row) in tup_table.into_iter().enumerate() { + assert_eq!( + row["name"].value::().unwrap(), + Some(expected_names[i].to_owned()) + ); + assert_eq!( + row["version"].value::().unwrap(), + Some(expected_versions[i].to_owned()) + ); + assert_eq!( + row["features"].value::>().unwrap(), + Some(expected_features[i].to_owned()) + ); + assert_eq!( + row["default_features"].value::().unwrap(), + Some(expected_default_features[i]) + ); + } + + Ok(()) + }) + } +} diff --git a/plrust/src/tests.rs b/plrust/src/tests.rs index 06f10c66..9423b112 100644 --- a/plrust/src/tests.rs +++ b/plrust/src/tests.rs @@ -6,1468 +6,6 @@ All rights reserved. Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. */ - -#[cfg(any(test, feature = "pg_test"))] -#[pgrx::pg_schema] -mod tests { - use pgrx::{datum::IntoDatum, prelude::*}; - use std::error::Error; - - // Bootstrap a testing table for non-immutable functions - extension_sql!( - r#" - CREATE TABLE contributors_pets ( - id serial8 not null primary key, - name text - ); - INSERT INTO contributors_pets (name) VALUES ('Brandy'); - INSERT INTO contributors_pets (name) VALUES ('Nami'); - INSERT INTO contributors_pets (name) VALUES ('Sally'); - INSERT INTO contributors_pets (name) VALUES ('Anchovy'); - "#, - name = "create_contributors_pets", - ); - - #[pg_test] - #[search_path(@extschema@)] - fn plrust_basic() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION sum_array(a BIGINT[]) RETURNS BIGINT - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - Ok(Some(a.into_iter().map(|v| v.unwrap_or_default()).sum())) - $$; - "#; - Spi::run(definition)?; - - let retval = Spi::get_one_with_args::( - r#" - SELECT sum_array($1); - "#, - vec![( - PgBuiltInOids::INT4ARRAYOID.oid(), - vec![1, 2, 3].into_datum(), - )], - ); - assert_eq!(retval, Ok(Some(6))); - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - fn plrust_text_array_with_initial_null() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION collect_text_array_with_initial_null(i text[]) RETURNS text[] - STRICT - LANGUAGE plrust AS - $$ - Ok(Some(i.into_iter().map(|s| s.map(|s| s.to_string())).collect())) - $$; - "#; - Spi::run(definition)?; - - let retval = Spi::get_one::>>( - r#" - SELECT collect_text_array_with_initial_null(ARRAY[NULL, 'a', 'b', 'c']); - "#, - ); - assert_eq!( - retval, - Ok(Some(vec![ - None, - Some("a".into()), - Some("b".into()), - Some("c".into()) - ])) - ); - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - fn plrust_update() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION update_me() RETURNS TEXT - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - Ok(String::from("booper").into()) - $$; - "#; - Spi::run(definition)?; - - let retval = Spi::get_one( - r#" - SELECT update_me(); - "#, - ); - assert_eq!(retval, Ok(Some("booper"))); - - let definition = r#" - CREATE OR REPLACE FUNCTION update_me() RETURNS TEXT - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - Ok(Some(String::from("swooper"))) - $$; - "#; - Spi::run(definition)?; - - let retval = Spi::get_one( - r#" - SELECT update_me(); - "#, - ); - assert_eq!(retval, Ok(Some("swooper"))); - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - fn plrust_spi() -> spi::Result<()> { - let random_definition = r#" - CREATE FUNCTION random_contributor_pet() RETURNS TEXT - STRICT - LANGUAGE PLRUST AS - $$ - Ok(Spi::get_one("SELECT name FROM contributors_pets ORDER BY random() LIMIT 1")?) - $$; - "#; - Spi::run(random_definition)?; - - let retval = Spi::get_one::( - r#" - SELECT random_contributor_pet(); - "#, - ); - assert!(retval.is_ok()); - assert!(retval.unwrap().is_some()); - - let specific_definition = r#" - CREATE FUNCTION contributor_pet(name TEXT) RETURNS BIGINT - STRICT - LANGUAGE PLRUST AS - $$ - use pgrx::IntoDatum; - Ok(Spi::get_one_with_args( - "SELECT id FROM contributors_pets WHERE name = $1", - vec![(PgBuiltInOids::TEXTOID.oid(), name.into_datum())], - )?) - $$; - "#; - Spi::run(specific_definition)?; - - let retval = Spi::get_one::( - r#" - SELECT contributor_pet('Nami'); - "#, - ); - assert_eq!(retval, Ok(Some(2))); - Ok(()) - } - - #[pg_test] - #[cfg(not(feature = "sandboxed"))] - #[search_path(@extschema@)] - fn plrust_deps_supported() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION colorize(input TEXT) RETURNS TEXT - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - [dependencies] - owo-colors = "3" - [code] - use owo_colors::OwoColorize; - Ok(Some(input.purple().to_string())) - $$; - "#; - Spi::run(definition)?; - - let retval = Spi::get_one_with_args::( - r#" - SELECT colorize($1); - "#, - vec![(PgBuiltInOids::TEXTOID.oid(), "Nami".into_datum())], - ); - assert!(retval.is_ok()); - assert!(retval.unwrap().is_some()); - - // Regression test: A previous version of PL/Rust would abort if this was called twice, so call it twice: - let retval = Spi::get_one_with_args::( - r#" - SELECT colorize($1); - "#, - vec![(PgBuiltInOids::TEXTOID.oid(), "Nami".into_datum())], - ); - assert!(retval.is_ok()); - assert!(retval.unwrap().is_some()); - Ok(()) - } - - #[pg_test] - #[cfg(not(feature = "sandboxed"))] - #[search_path(@extschema@)] - fn plrust_deps_supported_semver_parse() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION colorize(input TEXT) RETURNS TEXT - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - [dependencies] - owo-colors = ">2" - [code] - use owo_colors::OwoColorize; - Ok(Some(input.purple().to_string())) - $$; - "#; - Spi::run(definition)?; - - let retval = Spi::get_one_with_args::( - r#" - SELECT colorize($1); - "#, - vec![(PgBuiltInOids::TEXTOID.oid(), "Nami".into_datum())], - ); - assert!(retval.is_ok()); - assert!(retval.unwrap().is_some()); - - // Regression test: A previous version of PL/Rust would abort if this was called twice, so call it twice: - let retval = Spi::get_one_with_args::( - r#" - SELECT colorize($1); - "#, - vec![(PgBuiltInOids::TEXTOID.oid(), "Nami".into_datum())], - ); - assert!(retval.is_ok()); - assert!(retval.unwrap().is_some()); - Ok(()) - } - - #[pg_test] - #[cfg(not(feature = "sandboxed"))] - #[search_path(@extschema@)] - fn plrust_deps_supported_deps_in_toml_table() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION say_hello() RETURNS TEXT - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - [dependencies] - tokio = ">=1" - owo-colors = "3" - [code] - Ok(Some("hello".to_string())) - $$; - "#; - Spi::run(definition)?; - - let retval = Spi::get_one_with_args::( - r#" - SELECT say_hello(); - "#, - vec![(PgBuiltInOids::TEXTOID.oid(), "hello".into_datum())], - ); - assert_eq!(retval, Ok(Some("hello".to_string()))); - Ok(()) - } - - #[pg_test] - #[cfg(not(feature = "sandboxed"))] - #[search_path(@extschema@)] - fn plrust_deps_not_supported() { - let definition = r#" - CREATE FUNCTION colorize(input TEXT) RETURNS TEXT - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - [dependencies] - regex = "1.6.5" - [code] - Ok(Some("test")) - $$; - "#; - let res = std::panic::catch_unwind(|| { - Spi::run(definition).expect("SQL for plrust_deps_not_supported() failed") - }); - assert!(res.is_err()); - } - - // Regression for #348 - #[pg_test] - #[cfg(not(feature = "sandboxed"))] - #[search_path(@extschema@)] - fn plrust_rand_dep() { - let definition = r#" - CREATE FUNCTION rust_rand() RETURNS INT - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - [dependencies] - rand = "0.8.5" - [code] - Ok(Some(rand::random())) - $$; - "#; - Spi::run(definition).unwrap(); - - let rand = Spi::get_one::("SELECT rust_rand()").unwrap(); - assert!(rand.is_some()); - } - - #[pg_test] - #[search_path(@extschema@)] - fn plrust_returns_setof() -> spi::Result<()> { - let definition = r#" - CREATE OR REPLACE FUNCTION boop_srf(names TEXT[]) RETURNS SETOF TEXT - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - Ok(Some(::pgrx::iter::SetOfIterator::new(names.into_iter().map(|maybe| maybe.map(|name| name.to_string() + " was booped!"))))) - $$; - "#; - Spi::run(definition)?; - - let retval: spi::Result<_> = Spi::connect(|client| { - let mut table = client.select( - "SELECT * FROM boop_srf(ARRAY['Nami', 'Brandy'])", - None, - None, - )?; - - let mut found = vec![]; - while table.next().is_some() { - let value = table.get_one::()?; - found.push(value) - } - - Ok(Some(found)) - }); - - assert_eq!( - retval, - Ok(Some(vec![ - Some("Nami was booped!".into()), - Some("Brandy was booped!".into()), - ])) - ); - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - fn plrust_aggregate() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION plrust_sum_state(state INT, next INT) RETURNS INT - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - Ok(Some(state + next)) - $$; - CREATE AGGREGATE plrust_sum(INT) - ( - SFUNC = plrust_sum_state, - STYPE = INT, - INITCOND = '0' - ); - "#; - Spi::run(definition)?; - - let retval = Spi::get_one::( - r#" - SELECT plrust_sum(value) FROM UNNEST(ARRAY [1, 2, 3]) as value; - "#, - ); - assert_eq!(retval, Ok(Some(6))); - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - fn plrust_trigger() -> spi::Result<()> { - let definition = r#" - CREATE TABLE dogs ( - name TEXT, - scritches INT NOT NULL DEFAULT 0 - ); - - CREATE FUNCTION pet_trigger() RETURNS trigger AS $$ - let mut new = trigger.new().unwrap().into_owned(); - - let field = "scritches"; - - match new.get_by_name::(field)? { - Some(val) => new.set_by_name(field, val + 1)?, - None => (), - } - - Ok(Some(new)) - $$ LANGUAGE plrust; - - CREATE TRIGGER pet_trigger BEFORE INSERT OR UPDATE ON dogs - FOR EACH ROW EXECUTE FUNCTION pet_trigger(); - - INSERT INTO dogs (name) VALUES ('Nami'); - "#; - Spi::run(definition)?; - - let retval = Spi::get_one::( - r#" - SELECT scritches FROM dogs; - "#, - ); - assert_eq!(retval, Ok(Some(1))); - Ok(()) - } - - #[cfg(feature = "trusted")] - #[pg_test] - #[search_path(@extschema@)] - fn postgrestd_dont_make_files() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION make_file(filename TEXT) RETURNS TEXT - LANGUAGE PLRUST AS - $$ - Ok(std::fs::File::create(filename.unwrap_or("/somewhere/files/dont/belong.txt")) - .err() - .map(|e| e.to_string())) - $$; - "#; - Spi::run(definition)?; - - let retval = Spi::get_one_with_args::( - r#" - SELECT make_file($1); - "#, - vec![( - PgBuiltInOids::TEXTOID.oid(), - "/an/evil/place/to/put/a/file.txt".into_datum(), - )], - ); - assert_eq!( - retval, - Ok(Some("operation not supported on this platform".to_string())) - ); - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic = "yup"] - fn pgrx_can_panic() { - panic!("yup") - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic = "yup"] - fn plrust_can_panic() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION shut_up_and_explode() - RETURNS text AS - $$ - panic!("yup"); - Ok(None) - $$ LANGUAGE plrust; - "#; - - Spi::run(definition)?; - let retval = Spi::get_one::("SELECT shut_up_and_explode();\n"); - assert_eq!(retval, Ok(None)); - Ok(()) - } - - #[cfg(feature = "trusted")] - #[pg_test] - #[search_path(@extschema@)] - #[should_panic = "Failed to execute command"] - fn postgrestd_subprocesses_panic() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION say_hello() - RETURNS text AS - $$ - let out = std::process::Command::new("echo") - .arg("Hello world") - .stdout(std::process::Stdio::piped()) - .output() - .expect("Failed to execute command"); - Ok(Some(String::from_utf8_lossy(&out.stdout).to_string())) - $$ LANGUAGE plrust; - "#; - Spi::run(definition)?; - - let retval = Spi::get_one::("SELECT say_hello();\n"); - assert_eq!(retval, Ok(Some("Hello world\n".into()))); - Ok(()) - } - - #[cfg(feature = "trusted")] - #[pg_test] - #[search_path(@extschema@)] - #[should_panic = "error: the `include_str`, `include_bytes`, and `include` macros are forbidden"] - fn postgrestd_no_include_str() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION include_str() - RETURNS text AS - $$ - let s = include_str!("/etc/passwd"); - Ok(Some(s.into())) - $$ LANGUAGE plrust; - "#; - Spi::run(definition)?; - - let retval = Spi::get_one::("SELECT include_str();\n")?; - assert_eq!(retval.unwrap(), ""); - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - #[cfg(feature = "trusted")] - #[should_panic = "No such file or directory (os error 2)"] - fn plrustc_include_exists_no_access() { - // This file is created in CI and exists, but can only be accessed by - // root. Check that the actual access is reported as file not found (we - // should be ensuring that via - // `PLRUSTC_USER_CRATE_ALLOWED_SOURCE_PATHS`). We don't need to gate - // this test on CI, since the file is unlikely to exist outside of CI - // (so the test will pass). - let definition = r#" - CREATE FUNCTION include_no_access() - RETURNS text AS $$ - include!("/var/ci-stuff/secret_rust_files/const_foo.rs"); - Ok(Some(format!("{BAR}"))) - $$ LANGUAGE plrust; - "#; - Spi::run(definition).unwrap() - } - - #[pg_test] - #[search_path(@extschema@)] - #[cfg(feature = "trusted")] - #[should_panic = "No such file or directory (os error 2)"] - fn plrustc_include_exists_external() { - // This file is created in CI, exists, and can be accessed by anybody, - // but the actual access is forbidden via - // `PLRUSTC_USER_CRATE_ALLOWED_SOURCE_PATHS`. We don't need to gate this test on - // CI, since the file is unlikely to exist outside of CI, so the test - // will pass anyway. - let definition = r#" - CREATE FUNCTION include_exists_external() - RETURNS text AS $$ - include!("/var/ci-stuff/const_bar.rs"); - Ok(Some(format!("{BAR}"))) - $$ LANGUAGE plrust; - "#; - Spi::run(definition).unwrap(); - } - - #[pg_test] - #[search_path(@extschema@)] - #[cfg(feature = "trusted")] - #[should_panic = "No such file or directory (os error 2)"] - fn plrustc_include_made_up() { - // This file does not exist, and should be reported as such. - let definition = r#" - CREATE FUNCTION include_madeup() - RETURNS int AS $$ - include!("/made/up/path/lol.rs"); - Ok(Some(1)) - $$ LANGUAGE plrust; - "#; - Spi::run(definition).unwrap(); - } - - #[pg_test] - #[search_path(@extschema@)] - #[cfg(feature = "trusted")] - #[should_panic = "No such file or directory (os error 2)"] - fn plrustc_include_path_traversal() { - use std::path::PathBuf; - let workdir = crate::gucs::work_dir(); - let wd: PathBuf = workdir - .canonicalize() - .ok() - .expect("Failed to canonicalize workdir"); - // Produce a path that looks like - // `/allowed/path/here/../../../illegal/path/here` and check that it's - // rejected, in order to ensure we are not succeptable to path traversal - // attacks. - let mut evil_path = wd.clone(); - for _ in wd.ancestors().skip(1) { - evil_path.push(".."); - } - debug_assert_eq!( - evil_path - .canonicalize() - .ok() - .expect("Failed to produce unpath") - .to_str(), - Some("/") - ); - evil_path.push("var/ci-stuff/const_bar.rs"); - // This file does not exist, and should be reported as such. - let definition = format!( - r#"CREATE FUNCTION include_sneaky_traversal() - RETURNS int AS $$ - include!({evil_path:?}); - Ok(Some(1)) - $$ LANGUAGE plrust;"# - ); - Spi::run(&definition).unwrap(); - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic = "error: usage of an `unsafe` block"] - fn plrust_block_unsafe_annotated() -> spi::Result<()> { - // PL/Rust should block creating obvious, correctly-annotated usage of unsafe code - let definition = r#" - CREATE FUNCTION naughty() - RETURNS text AS - $$ - use std::{os::raw as ffi, str, ffi::CStr}; - let int:u32 = 0xDEADBEEF; - // Note that it is always safe to create a pointer. - let ptr = int as *mut u64; - // What is unsafe is dereferencing it - let cstr = unsafe { - ptr.write(0x00_1BADC0DE_00); - CStr::from_ptr(ptr.cast::()) - }; - Ok(str::from_utf8(cstr.to_bytes()).ok().map(|s| s.to_owned())) - $$ LANGUAGE plrust; - "#; - Spi::run(definition) - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic = "call to unsafe function is unsafe and requires unsafe block"] - fn plrust_block_unsafe_hidden() -> spi::Result<()> { - // PL/Rust should not allow hidden injection of unsafe code - // that may rely on the way PGRX expands into `unsafe fn` to "sneak in" - let definition = r#" - CREATE FUNCTION naughty() - RETURNS text AS - $$ - use std::{os::raw as ffi, str, ffi::CStr}; - let int:u32 = 0xDEADBEEF; - let ptr = int as *mut u64; - ptr.write(0x00_1BADC0DE_00); - let cstr = CStr::from_ptr(ptr.cast::()); - Ok(str::from_utf8(cstr.to_bytes()).ok().map(|s| s.to_owned())) - $$ LANGUAGE plrust; - "#; - Spi::run(definition) - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic = "error: the `env` and `option_env` macros are forbidden"] - #[cfg(feature = "trusted")] - fn plrust_block_env() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION get_path() RETURNS text AS $$ - let path = env!("PATH"); - Ok(Some(path.to_string())) - $$ LANGUAGE plrust; - "#; - Spi::run(definition) - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic = "error: the `env` and `option_env` macros are forbidden"] - #[cfg(feature = "trusted")] - fn plrust_block_option_env() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION try_get_path() RETURNS text AS $$ - match option_env!("PATH") { - None => Ok(None), - Some(s) => Ok(Some(s.to_string())) - } - $$ LANGUAGE plrust; - "#; - Spi::run(definition) - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic = "error: usage of an `unsafe` block"] - fn plrust_block_unsafe_plutonium() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION super_safe() - RETURNS text AS - $$ - [dependencies] - plutonium = "*" - - [code] - use std::{os::raw as ffi, str, ffi::CStr}; - use plutonium::safe; - - #[safe] - fn super_safe() -> Option { - let int: u32 = 0xDEADBEEF; - let ptr = int as *mut u64; - ptr.write(0x00_1BADC0DE_00); - let cstr = CStr::from_ptr(ptr.cast::()); - str::from_utf8(cstr.to_bytes()).ok().map(|s| s.to_owned()) - } - - Ok(super_safe()) - $$ LANGUAGE plrust; - "#; - Spi::run(definition) - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic = "xxx"] - #[ignore] - fn plrust_pgloglevel_dont_allcaps_panic() -> spi::Result<()> { - // This test attempts to annihilate the database. - // It relies on the existing assumption that tests are run in the same Postgres instance, - // so this test will make all tests "flaky" if Postgres suddenly goes down with it. - let definition = r#" - CREATE FUNCTION dont_allcaps_panic() - RETURNS text AS - $$ - ereport!(PANIC, PgSqlErrorCode::ERRCODE_INTERNAL_ERROR, "If other tests completed, PL/Rust did not actually destroy the entire database, \ - But if you see this in the error output, something might be wrong."); - Ok(Some("lol".into())) - $$ LANGUAGE plrust; - "#; - Spi::run(definition)?; - let retval = Spi::get_one::("SELECT dont_allcaps_panic();\n"); - assert_eq!(retval, Ok(Some("lol".into()))); - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - fn plrust_call_1st() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION ret_1st(a int, b int) - RETURNS int AS - $$ - Ok(a) - $$ LANGUAGE plrust; - "#; - Spi::run(definition)?; - let result_1 = Spi::get_one::("SELECT ret_1st(1, 2);\n"); - assert_eq!(Ok(Some(1)), result_1); // may get: Some(1) - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - fn plrust_call_2nd() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION ret_2nd(a int, b int) - RETURNS int AS - $$ - Ok(b) - $$ LANGUAGE plrust; - "#; - Spi::run(definition)?; - let result_2 = Spi::get_one::("SELECT ret_2nd(1, 2);\n"); - assert_eq!(Ok(Some(2)), result_2); // may get: Some(2) - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - fn plrust_call_me() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION pick_ret(a int, b int, pick int) - RETURNS int AS - $$ - Ok(match pick { - Some(0) => a, - Some(1) => b, - _ => None, - }) - $$ LANGUAGE plrust; - "#; - Spi::run(definition)?; - let result_a = Spi::get_one::("SELECT pick_ret(3, 4, 0);"); - let result_b = Spi::get_one::("SELECT pick_ret(5, 6, 1);"); - let result_c = Spi::get_one::("SELECT pick_ret(7, 8, 2);"); - let result_z = Spi::get_one::("SELECT pick_ret(9, 99, -1);"); - assert_eq!(Ok(Some(3)), result_a); // may get: Some(4) or None - assert_eq!(Ok(Some(6)), result_b); // may get: None - assert_eq!(Ok(None), result_c); - assert_eq!(Ok(None), result_z); - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - fn plrust_call_me_call_me() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION ret_1st(a int, b int) - RETURNS int AS - $$ - Ok(a) - $$ LANGUAGE plrust; - - CREATE FUNCTION ret_2nd(a int, b int) - RETURNS int AS - $$ - Ok(b) - $$ LANGUAGE plrust; - - CREATE FUNCTION pick_ret(a int, b int, pick int) - RETURNS int AS - $$ - Ok(match pick { - Some(0) => a, - Some(1) => b, - _ => None, - }) - $$ LANGUAGE plrust; - "#; - Spi::run(definition)?; - let result_1 = Spi::get_one::("SELECT ret_1st(1, 2);\n"); - let result_2 = Spi::get_one::("SELECT ret_2nd(1, 2);\n"); - let result_a = Spi::get_one::("SELECT pick_ret(3, 4, 0);"); - let result_b = Spi::get_one::("SELECT pick_ret(5, 6, 1);"); - let result_c = Spi::get_one::("SELECT pick_ret(7, 8, 2);"); - let result_z = Spi::get_one::("SELECT pick_ret(9, 99, -1);"); - assert_eq!(Ok(None), result_z); - assert_eq!(Ok(None), result_c); - assert_eq!(Ok(Some(6)), result_b); // may get: None - assert_eq!(Ok(Some(3)), result_a); // may get: Some(4) or None - assert_eq!(Ok(Some(2)), result_2); // may get: Some(1) - assert_eq!(Ok(Some(1)), result_1); // may get: Some(2) - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic = "parameter name \"a\" used more than once"] - fn plrust_dup_args() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION not_unique(a int, a int) - RETURNS int AS - $$ - Ok(a) - $$ LANGUAGE plrust; - "#; - Spi::run(definition)?; - let result = Spi::get_one::("SELECT not_unique(1, 2);\n"); - assert_eq!(Ok(Some(1)), result); - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic = "PL/Rust does not support unnamed arguments"] - fn plrust_defaulting_dup_args() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION not_unique(int, arg0 int) - RETURNS int AS - $$ - Ok(arg0) - $$ LANGUAGE plrust; - "#; - Spi::run(definition)?; - let result = Spi::get_one::("SELECT not_unique(1, 2);\n"); - assert_eq!(Ok(Some(1)), result); - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic = "plrust functions cannot have their STRICT property altered"] - fn plrust_cant_change_strict_off() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION cant_change_strict_off() - RETURNS int - LANGUAGE plrust - AS $$ Ok(Some(1)) $$; - "#; - Spi::run(definition)?; - Spi::run("ALTER FUNCTION cant_change_strict_off() CALLED ON NULL INPUT") - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic = "plrust functions cannot have their STRICT property altered"] - fn plrust_cant_change_strict_on() -> spi::Result<()> { - let definition = r#" - CREATE FUNCTION cant_change_strict_on() - RETURNS int - LANGUAGE plrust - AS $$ Ok(Some(1)) $$; - "#; - Spi::run(definition)?; - Spi::run("ALTER FUNCTION cant_change_strict_on() RETURNS NULL ON NULL INPUT") - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic(expected = "error: declaration of a function with `export_name`")] - fn plrust_block_unsafe_export_name() -> spi::Result<()> { - // A separate test covers #[no_mangle], but what about #[export_name]? - // Same idea. This tries to collide with free, which may symbol clash, - // or might override depending on how the OS and loader feel today. - // Let's not leave it up to forces beyond our control. - let definition = r#" - CREATE OR REPLACE FUNCTION export_hacked_free() RETURNS BIGINT - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - #[export_name = "free"] - pub extern "C" fn own_free(ptr: *mut c_void) { - // the contents don't matter - } - - Ok(Some(1)) - $$; - "#; - Spi::run(definition)?; - let result = Spi::get_one::("SELECT export_hacked_free();\n"); - assert_eq!(Ok(Some(1)), result); - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic(expected = "error: declaration of a static with `link_section`")] - fn plrust_block_unsafe_link_section() -> spi::Result<()> { - let definition = r#" - CREATE OR REPLACE FUNCTION link_evil_section() RETURNS BIGINT - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - #[link_section = ".init_array"] - pub static INITIALIZE: &[u8; 136] = &GOGO; - - #[link_section = ".text"] - pub static GOGO: [u8; 136] = [ - 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 46, 99, 104, 111, 46, 114, 105, 1, 72, 49, 4, - 36, 72, 137, 231, 106, 1, 254, 12, 36, 72, 184, 99, 102, 105, 108, 101, 49, 50, 51, 80, 72, - 184, 114, 47, 116, 109, 112, 47, 112, 111, 80, 72, 184, 111, 117, 99, 104, 32, 47, 118, 97, - 80, 72, 184, 115, 114, 47, 98, 105, 110, 47, 116, 80, 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, - 72, 184, 114, 105, 1, 44, 98, 1, 46, 116, 72, 49, 4, 36, 49, 246, 86, 106, 14, 94, 72, 1, - 230, 86, 106, 19, 94, 72, 1, 230, 86, 106, 24, 94, 72, 1, 230, 86, 72, 137, 230, 49, 210, - 106, 59, 88, 15, 5, - ]; - - Ok(Some(1)) - $$; - "#; - Spi::run(definition)?; - let result = Spi::get_one::("SELECT link_evil_section();\n"); - assert_eq!(Ok(Some(1)), result); - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic(expected = "error: declaration of a `no_mangle` static")] - fn plrust_block_unsafe_no_mangle() -> spi::Result<()> { - let definition = r#" - CREATE OR REPLACE FUNCTION not_mangled() RETURNS BIGINT - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - #[no_mangle] - #[link_section = ".init_array"] - pub static INITIALIZE: &[u8; 136] = &GOGO; - - #[no_mangle] - #[link_section = ".text"] - pub static GOGO: [u8; 136] = [ - 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 46, 99, 104, 111, 46, 114, 105, 1, 72, 49, 4, - 36, 72, 137, 231, 106, 1, 254, 12, 36, 72, 184, 99, 102, 105, 108, 101, 49, 50, 51, 80, 72, - 184, 114, 47, 116, 109, 112, 47, 112, 111, 80, 72, 184, 111, 117, 99, 104, 32, 47, 118, 97, - 80, 72, 184, 115, 114, 47, 98, 105, 110, 47, 116, 80, 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, - 72, 184, 114, 105, 1, 44, 98, 1, 46, 116, 72, 49, 4, 36, 49, 246, 86, 106, 14, 94, 72, 1, - 230, 86, 106, 19, 94, 72, 1, 230, 86, 106, 24, 94, 72, 1, 230, 86, 72, 137, 230, 49, 210, - 106, 59, 88, 15, 5, - ]; - - Ok(Some(1)) - $$; - "#; - Spi::run(definition)?; - let result = Spi::get_one::("SELECT not_mangled();\n"); - assert_eq!(Ok(Some(1)), result); - Ok(()) - } - - #[pg_test] - #[should_panic(expected = "issue78 works")] - fn test_issue_78() -> spi::Result<()> { - let sql = r#"CREATE OR REPLACE FUNCTION raise_error() RETURNS TEXT - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - pgrx::error!("issue78 works"); - Ok(Some("hi".to_string())) - $$;"#; - Spi::run(sql)?; - Spi::get_one::("SELECT raise_error()")?; - Ok(()) - } - - #[pg_test] - fn test_issue_79() -> spi::Result<()> { - let sql = r#" - create or replace function fn1(i int) returns int strict language plrust as $$ - [code] - notice!("{}", "fn1 started"); - let cmd = format!("select fn2({})", i); - Spi::connect(|client| - { - client.select(&cmd, None, None); - }); - notice!("{}", "fn1 finished"); - Ok(Some(1)) - $$; - - create or replace function fn2(i int) returns int strict language plrust as $$ - [code] - notice!("{}", "fn2 started"); - notice!("{}", "fn2 finished"); - Ok(Some(2)) - $$; - "#; - Spi::run(sql)?; - assert_eq!(Ok(Some(1)), Spi::get_one::("SELECT fn1(1)")); - Ok(()) - } - - #[pg_test] - fn replace_function() -> spi::Result<()> { - Spi::run("CREATE FUNCTION replace_me() RETURNS int LANGUAGE plrust AS $$ Ok(Some(1)) $$")?; - assert_eq!(Ok(Some(1)), Spi::get_one("SELECT replace_me()")); - - Spi::run( - "CREATE OR REPLACE FUNCTION replace_me() RETURNS int LANGUAGE plrust AS $$ Ok(Some(2)) $$", - )?; - assert_eq!(Ok(Some(2)), Spi::get_one("SELECT replace_me()")); - Ok(()) - } - - #[pg_test] - fn test_point() -> spi::Result<()> { - Spi::run( - r#"CREATE FUNCTION test_point(p point) RETURNS point LANGUAGE plrust AS $$ Ok(p) $$"#, - )?; - let p = Spi::get_one::("SELECT test_point('42, 99'::point);")? - .expect("SPI result was null"); - assert_eq!(p.x, 42.0); - assert_eq!(p.y, 99.0); - Ok(()) - } - - #[pg_test] - fn test_box() -> spi::Result<()> { - Spi::run(r#"CREATE FUNCTION test_box(b box) RETURNS box LANGUAGE plrust AS $$ Ok(b) $$"#)?; - let b = Spi::get_one::("SELECT test_box('1,2,3,4'::box);")? - .expect("SPI result was null"); - assert_eq!(b.high.x, 3.0); - assert_eq!(b.high.y, 4.0); - assert_eq!(b.low.x, 1.0); - assert_eq!(b.low.y, 2.0); - Ok(()) - } - - #[pg_test] - fn test_uuid() -> spi::Result<()> { - Spi::run( - r#"CREATE FUNCTION test_uuid(u uuid) RETURNS uuid LANGUAGE plrust AS $$ Ok(u) $$"#, - )?; - let u = Spi::get_one::( - "SELECT test_uuid('e4176a4d-790c-4750-85b7-665d72471173'::uuid);", - )? - .expect("SPI result was null"); - assert_eq!( - u, - pgrx::Uuid::from_bytes([ - 0xe4, 0x17, 0x6a, 0x4d, 0x79, 0x0c, 0x47, 0x50, 0x85, 0xb7, 0x66, 0x5d, 0x72, 0x47, - 0x11, 0x73 - ]) - ); - - Ok(()) - } - - #[pg_test] - fn test_int4range() -> spi::Result<()> { - Spi::run( - r#"CREATE FUNCTION test_int4range(r int4range) RETURNS int4range LANGUAGE plrust AS $$ Ok(r) $$"#, - )?; - let r = Spi::get_one::>("SELECT test_int4range('[1, 10)'::int4range);")? - .expect("SPI result was null"); - assert_eq!(r, (1..10).into()); - Ok(()) - } - - #[pg_test] - fn test_int8range() -> spi::Result<()> { - Spi::run( - r#"CREATE FUNCTION test_int8range(r int8range) RETURNS int8range LANGUAGE plrust AS $$ Ok(r) $$"#, - )?; - let r = Spi::get_one::>("SELECT test_int8range('[1, 10)'::int8range);")? - .expect("SPI result was null"); - assert_eq!(r, (1..10).into()); - Ok(()) - } - - #[pg_test] - fn test_numrange() -> spi::Result<()> { - Spi::run( - r#"CREATE FUNCTION test_numrange(r numrange) RETURNS numrange LANGUAGE plrust AS $$ Ok(r) $$"#, - )?; - let r = Spi::get_one::>("SELECT test_numrange('[1, 10]'::numrange);")? - .expect("SPI result was null"); - assert_eq!( - r, - Range::new( - AnyNumeric::try_from(1.0f32).unwrap(), - AnyNumeric::try_from(10.0f32).unwrap() - ) - ); - Ok(()) - } - - #[pg_test] - fn test_tid_roundtrip() -> spi::Result<()> { - Spi::run( - r#"CREATE FUNCTION tid_roundtrip(t tid) RETURNS tid LANGUAGE plrust AS $$ Ok(t) $$"#, - )?; - let tid = Spi::get_one::("SELECT tid_roundtrip('(42, 99)'::tid)")? - .expect("SPI result was null"); - let (blockno, offno) = pgrx::item_pointer_get_both(tid); - assert_eq!(blockno, 42); - assert_eq!(offno, 99); - Ok(()) - } - - #[pg_test] - fn test_return_bytea() -> spi::Result<()> { - Spi::run( - r#"CREATE FUNCTION return_bytea() RETURNS bytea LANGUAGE plrust AS $$ Ok(Some(vec![1,2,3])) $$"#, - )?; - let bytes = Spi::get_one::>("SELECT return_bytea()")?.expect("SPI result was null"); - assert_eq!(bytes, vec![1, 2, 3]); - Ok(()) - } - - #[pg_test] - fn test_cstring_roundtrip() -> Result<(), Box> { - use std::ffi::CStr; - Spi::run( - r#"CREATE FUNCTION cstring_roundtrip(s cstring) RETURNS cstring STRICT LANGUAGE plrust as $$ Ok(Some(s.into())) $$;"#, - )?; - let cstr = Spi::get_one::<&CStr>("SELECT cstring_roundtrip('hello')")? - .expect("SPI result was null"); - let expected = CStr::from_bytes_with_nul(b"hello\0")?; - assert_eq!(cstr, expected); - Ok(()) - } - - #[pg_test] - fn test_daterange() -> Result<(), Box> { - Spi::run( - r#"CREATE FUNCTION test_daterange(r daterange) RETURNS daterange LANGUAGE plrust AS $$ Ok(r) $$"#, - )?; - let r = Spi::get_one::>( - "SELECT test_daterange('[1977-03-20, 1980-01-01)'::daterange);", - )? - .expect("SPI result was null"); - assert_eq!( - r, - Range::new( - Date::new(1977, 3, 20)?, - RangeBound::Exclusive(Date::new(1980, 01, 01)?) - ) - ); - Ok(()) - } - - #[pg_test] - fn test_tsrange() -> Result<(), Box> { - Spi::run( - r#"CREATE FUNCTION test_tsrange(p tsrange) RETURNS tsrange LANGUAGE plrust AS $$ Ok(p) $$"#, - )?; - let r = Spi::get_one::>( - "SELECT test_tsrange('[1977-03-20, 1980-01-01)'::tsrange);", - )? - .expect("SPI result was null"); - assert_eq!( - r, - Range::new( - Timestamp::new(1977, 3, 20, 0, 0, 0.0)?, - RangeBound::Exclusive(Timestamp::new(1980, 01, 01, 0, 0, 0.0)?) - ) - ); - Ok(()) - } - - #[pg_test] - fn test_tstzrange() -> Result<(), Box> { - Spi::run( - r#"CREATE FUNCTION test_tstzrange(p tstzrange) RETURNS tstzrange LANGUAGE plrust AS $$ Ok(p) $$"#, - )?; - let r = Spi::get_one::>( - "SELECT test_tstzrange('[1977-03-20, 1980-01-01)'::tstzrange);", - )? - .expect("SPI result was null"); - assert_eq!( - r, - Range::new( - TimestampWithTimeZone::new(1977, 3, 20, 0, 0, 0.0)?, - RangeBound::Exclusive(TimestampWithTimeZone::new(1980, 01, 01, 0, 0, 0.0)?) - ) - ); - Ok(()) - } - - #[pg_test] - fn test_interval() -> Result<(), Box> { - Spi::run( - r#"CREATE FUNCTION get_interval_hours(i interval) RETURNS numeric STRICT LANGUAGE plrust AS $$ Ok(i.extract_part(DateTimeParts::Hour)) $$"#, - )?; - let hours = - Spi::get_one::("SELECT get_interval_hours('3 days 9 hours 12 seconds')")? - .expect("SPI result was null"); - assert_eq!(hours, AnyNumeric::from(9)); - Ok(()) - } - - #[cfg(feature = "trusted")] - #[pg_test] - #[search_path(@extschema@)] - fn postgrestd_net_is_unsupported() -> spi::Result<()> { - let sql = r#" - create or replace function pt106() returns text - IMMUTABLE STRICT - LANGUAGE PLRUST AS - $$ - [code] - use std::net::TcpStream; - - Ok(TcpStream::connect("127.0.0.1:22").err().map(|e| e.to_string())) - $$"#; - Spi::run(sql)?; - let string = Spi::get_one::("SELECT pt106()")?.expect("Unconditional return"); - assert_eq!("operation not supported on this platform", &string); - Ok(()) - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic(expected = "PL/Rust does not support unnamed arguments")] - fn unnamed_args() -> spi::Result<()> { - Spi::run("CREATE FUNCTION unnamed_arg(int) RETURNS int LANGUAGE plrust as $$ Ok(None) $$;") - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic(expected = "PL/Rust does not support unnamed arguments")] - fn named_unnamed_args() -> spi::Result<()> { - Spi::run("CREATE FUNCTION named_unnamed_arg(bob text, int) RETURNS int LANGUAGE plrust as $$ Ok(None) $$;") - } - - #[pg_test] - #[search_path(@extschema@)] - #[should_panic( - expected = "is an invalid Rust identifier and cannot be used as an argument name" - )] - fn invalid_arg_identifier() -> spi::Result<()> { - Spi::run("CREATE FUNCTION invalid_arg_identifier(\"this isn't a valid rust identifier\" int) RETURNS int LANGUAGE plrust as $$ Ok(None) $$;") - } - - #[pg_test] - fn test_srf_one_col() -> spi::Result<()> { - Spi::run( - "CREATE FUNCTION srf_one_col() RETURNS TABLE (a int) LANGUAGE plrust AS $$ - Ok(Some(TableIterator::new(vec![( Some(1), )].into_iter()))) - $$;", - )?; - - let a = Spi::get_one::("SELECT * FROM srf_one_col()")?; - assert_eq!(a, Some(1)); - - Ok(()) - } - - #[pg_test] - fn test_srf_two_col() -> spi::Result<()> { - Spi::run( - "CREATE FUNCTION srf_two_col() RETURNS TABLE (a int, b int) LANGUAGE plrust AS $$ - Ok(Some(TableIterator::new(vec![(Some(1), Some(2))].into_iter()))) - $$;", - )?; - - let (a, b) = Spi::get_two::("SELECT * FROM srf_two_col()")?; - assert_eq!(a, Some(1)); - assert_eq!(b, Some(2)); - - Ok(()) - } - - #[pg_test] - fn test_udt() -> spi::Result<()> { - Spi::run( - r#" -CREATE TYPE person AS ( - name text, - age float8 -); - -create function make_person(name text, age float8) returns person - strict parallel safe - language plrust as -$$ - // create the Heap Tuple representation of the SQL type `person` - let mut p = PgHeapTuple::new_composite_type("person")?; - - // set a few of its attributes - // - // Runtime errors can occur if the attribute name is invalid or if the Rust type of the value - // is not compatible with the backing SQL type for that attribute. Hence the use of the `?` operator - p.set_by_name("name", name)?; - p.set_by_name("age", age)?; - - // return the `person` - Ok(Some(p)) -$$; - -create function get_person_name(p person) returns text - strict parallel safe - language plrust as -$$ - // `p` is a `PgHeapTuple` over the underlying data for `person` - Ok(p.get_by_name("name")?) -$$; - -create function get_person_age(p person) returns float8 - strict parallel safe - language plrust as -$$ - // `p` is a `PgHeapTuple` over the underlying data for `person` - Ok(p.get_by_name("age")?) -$$; - -create function get_person_attribute(p person, attname text) returns text - strict parallel safe - language plrust as -$$ - match attname.to_lowercase().as_str() { - "age" => { - let age:Option = p.get_by_name("age")?; - Ok(age.map(|v| v.to_string())) - }, - "name" => { - Ok(p.get_by_name("name")?) - }, - _ => panic!("unknown attribute: `{attname}`") - } -$$; - -create operator ->> (function = get_person_attribute, leftarg = person, rightarg = text); - -create table people -( - id serial8 not null primary key, - p person -); - -insert into people (p) values (make_person('Johnny', 46.24)); -insert into people (p) values (make_person('Joe', 99.09)); -insert into people (p) values (make_person('Dr. Beverly Crusher of the Starship Enterprise', 32.0)); - "#, - )?; - - let johnny = Spi::get_one::>( - "SELECT p FROM people WHERE p->>'name' = 'Johnny';", - )? - .expect("SPI result was null"); - - let age = johnny.get_by_name::("age")?.expect("age was null"); - assert_eq!(age, 46.24); - - Ok(()) - } - - #[pg_test] - fn test_allowed_dependencies() -> spi::Result<()> { - // Given the allowed list looks like this: - // owo-colors = "=3.5.0" - // tokio = { version = "=1.19.2", features = ["rt", "net"] } - // plutonium = "*" - // syn = { version = "=2.0.28", default-features = false } - // rand = ["=0.8.3", { version = ">0.8.4, <0.8.6", features = ["getrandom"] }] - let query = "SELECT * FROM plrust.allowed_dependencies();"; - - // The result will look like this: - // name | version | features | default_features - // ------------+----------------+-------------+------------------ - // owo-colors | =3.5.0 | {} | t - // plutonium | * | {} | t - // rand | =0.8.3 | {} | t - // rand | >0.8.4, <0.8.6 | {getrandom} | t - // syn | =2.0.28 | {} | f - // tokio | =1.19.2 | {rt,net} | t - - Spi::connect(|client| { - let expected_names = vec!["owo-colors", "plutonium", "rand", "rand", "syn", "tokio"]; - let expected_versions = vec![ - "=3.5.0", - "*", - "=0.8.3", - ">0.8.4, <0.8.6", - "=2.0.28", - "=1.19.2", - ]; - let expected_features = vec![ - vec![], - vec![], - vec![], - vec![String::from("getrandom")], - vec![], - vec![String::from("rt"), String::from("net")], - ]; - let expected_default_features = vec![true, true, true, true, false, true]; - let expected_len = expected_names.len(); - - let tup_table = client.select(query, None, None)?; - - assert_eq!(tup_table.len(), expected_len); - - for (i, row) in tup_table.into_iter().enumerate() { - assert_eq!( - row["name"].value::().unwrap(), - Some(expected_names[i].to_owned()) - ); - assert_eq!( - row["version"].value::().unwrap(), - Some(expected_versions[i].to_owned()) - ); - assert_eq!( - row["features"].value::>().unwrap(), - Some(expected_features[i].to_owned()) - ); - assert_eq!( - row["default_features"].value::().unwrap(), - Some(expected_default_features[i]) - ); - } - - Ok(()) - }) - } -} - #[cfg(any(test, feature = "pg_test"))] pub mod pg_test { use once_cell::sync::Lazy; From 56014a2422e17d38e9834bfadb552c87feb60ec1 Mon Sep 17 00:00:00 2001 From: Dave Selph Date: Thu, 27 Jul 2023 15:21:47 -0600 Subject: [PATCH 13/32] add section that will run plrust test that are not ran with the plrust-tests run-tests.sh --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4e6abf82..52fc7066 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -156,6 +156,9 @@ jobs: if: matrix.target == 'postgrestd' run: cd plrust && STD_TARGETS="aarch64-postgres-linux-gnu" ./build && cargo test --verbose --no-default-features --features "pg$PG_VER trusted" + - name: Test PLRust non-#[pg_test] tests + run: cargo test --no-default-features --features "pg$PG_VER" -- --skip alter --skip argument --skip basic --skip blocked_code --skip borrow_mut_error --skip ddl --skip dependencies --skip matches --skip panics --skip range --skip recursion --skip return_values --skip round_trip --skip time_and_dates --skip trusted --skip user_defined_types --skip versioning + - name: Print sccache stats (after build) run: sccache --show-stats From 1a43a802ed96c259fbd8cc7d675abbb9a34718f7 Mon Sep 17 00:00:00 2001 From: Dave Selph Date: Thu, 27 Jul 2023 16:00:12 -0600 Subject: [PATCH 14/32] Add testing to ci in x86_64 --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 52fc7066..99a023cc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -335,6 +335,9 @@ jobs: if: matrix.target == 'postgrestd' run: cd plrust && STD_TARGETS="x86_64-postgres-linux-gnu" ./build && cargo test --verbose --no-default-features --features "pg$PG_VER trusted" + - name: Test PLRust non-#[pg_test] tests + run: cargo test --no-default-features --features "pg$PG_VER" -- --skip alter --skip argument --skip basic --skip blocked_code --skip borrow_mut_error --skip ddl --skip dependencies --skip matches --skip panics --skip range --skip recursion --skip return_values --skip round_trip --skip time_and_dates --skip trusted --skip user_defined_types --skip versioning + - name: Print sccache stats run: sccache --show-stats From 1446445b6fed5fccb4eccd9fcf4577c3c7917ba1 Mon Sep 17 00:00:00 2001 From: Brady Bonnette Date: Fri, 28 Jul 2023 16:37:59 -0400 Subject: [PATCH 15/32] First trial of getting trusted tests to run --- .github/workflows/ci.yml | 359 +++++++++++++++++++----------------- plrust-tests/Cargo.toml | 1 + plrust-tests/src/trusted.rs | 81 ++++---- 3 files changed, 229 insertions(+), 212 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99a023cc..5493d18f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,168 +20,168 @@ jobs: # The image created for running these tasks already have a majority of the dependencies installed, such as system # libraries, sccache, github runners, rust, etc. This is to cut back on the amount of time it takes to launch a runner. # If something is missing, it can be added here or baked into a new image outside of Github Actions. - plrust_arm64_amzn2: - name: aarch64 tests (amzn2) - runs-on: [self-hosted, linux, ARM64, launch_template_id__lt-0013395587950cb5d] - defaults: - run: - shell: bash - - strategy: - matrix: - version: ["pg13", "pg14", "pg15"] - target: ["host", "postgrestd"] - fail-fast: false - - # Note about environment variables: Even though a majority of these environment variables are baked into the runner image, - # Github Actions seemingly runs self-hosted tasks in a different manner such that even baken-in environment variables are - # removed. Howevever, they can be declared here and persist through all tasks. Assume that the home directory for the - # runner is /home/ec2-user - env: - AWS_CACHE_BUCKET: tcdi-ci-plrust-build-cache.private - CACHE_KEY_VERSION: v0 - CI: true - PLRUST_TRUSTED_PGRX_OVERRIDE: "pgrx = { path = '/home/ec2-user/actions-runner/_work/plrust/plrust/plrust-trusted-pgrx', package='plrust-trusted-pgrx' }" - RUSTUP_HOME: /home/ec2-user/.rustup - RUSTC_WRAPPER: sccache - RUSTFLAGS: -Copt-level=0 -Dwarnings - SCCACHE_BIN_DIR: /home/ec2-user/.local/bin - SCCACHE_CACHE_SIZE: 20G - SCCACHE_DIR: /home/ec2-user/.cache/sccache - SCCACHE_IDLE_TIMEOUT: 0 - WORK_DIR: /home/ec2-user/actions-runner/_work/plrust/plrust - - steps: - - uses: actions/checkout@v3 - - - name: Generate cache filename checksum - run: | - cd $WORK_DIR - shopt -s globstar - checksum=$(cat **/Cargo.lock **/rust-toolchain.toml .github/workflows/ci.yml .cargo/config | sha256sum | awk '{print $1}') - echo "CACHE_KEY_CHECKSUM=$checksum" >> $GITHUB_ENV - - - name: Set up (Linux) prerequisites and environment - run: | - echo "" - echo "----- Print kernel info -----" - uname -a - echo "" - - echo "----- Set up dynamic variables -----" - export PG_VER=$(echo ${{ matrix.version }} | cut -c 3-) - echo "PG_VER=$PG_VER" >> $GITHUB_ENV - echo "MAKEFLAGS=$MAKEFLAGS -j $(grep -c ^processor /proc/cpuinfo)" >> $GITHUB_ENV - echo "$SCCACHE_BIN_DIR" >> $GITHUB_PATH - echo "PATH=$HOME/.cargo/bin:$PATH" >> $GITHUB_ENV - echo "" - - echo "----- Install system dependencies -----" - # Add any extra dependencies here if necessary - - echo "----- Install PostgreSQL $PG_VER -----" - sudo yum install -y "postgresql$PG_VER" "postgresql$PG_VER-server" "postgresql$PG_VER-devel" - export PATH="/usr/pgsql-$PG_VER/bin/:$PATH" - echo "/usr/pgsql-$PG_VER/bin/" >> $GITHUB_PATH - - echo "----- Set postgres permissions -----" - sudo chmod a+rwx `$(which pg_config) --pkglibdir` `$(which pg_config) --sharedir`/extension - echo "" - - echo "----- Create artifacts directory -----" - mkdir -p $HOME/artifacts - echo "" - - cat $GITHUB_ENV - echo "" - env - - - name: Load Cargo cache if available - run: | - # See /.github/scripts/load_cache.sh for more details - . $WORK_DIR/.github/scripts/load_cache.sh - cargo_cache_key="plrust-arm64-amzn2-cargo-cache-$CACHE_KEY_VERSION-$CACHE_KEY_CHECKSUM.tar.lz4" - loadcache $cargo_cache_key - - - name: Create protected files - run: | - sudo mkdir -p /var/ci-stuff/secret_rust_files - sudo echo "const FOO:i32 = 7;" /var/ci-stuff/secret_rust_files/const_foo.rs - sudo echo "const BAR:i32 = 8;" /var/ci-stuff/const_bar.rs - sudo chmod -R 600 /var/ci-stuff/secret_rust_files - if: matrix.target == 'postgrestd' - - - name: Load sccache cache if available - run: | - # See /.github/scripts/load_cache.sh for more details - . $WORK_DIR/.github/scripts/load_cache.sh - sccache_key="plrust-arm64-amzn2-sccache-cache-$CACHE_KEY_VERSION-$CACHE_KEY_CHECKSUM.tar.lz4" - loadcache $sccache_key - - - name: Start sccache server - run: sccache --start-server && sccache --show-stats - - # See /.github/scripts/install_cargo_pgrx.sh for more details - - name: Install cargo-pgrx defined in plrust/Cargo.toml - run: | - . $WORK_DIR/.github/scripts/install_cargo_pgrx.sh - install_cargo_pgrx - - - name: Print sccache stats - run: sccache --show-stats - - - name: Install llvm-tools-preview - run: rustup component add llvm-tools-preview rustc-dev - - - name: Test plrustc - run: cd plrustc && cargo test -p plrustc - - - name: Print sccache stats - run: sccache --show-stats - - - name: install plrustc - run: cd plrustc && ./build.sh && cp ../build/bin/plrustc ~/.cargo/bin - - - name: Print sccache stats - run: sccache --show-stats - - - name: Run cargo pgrx init - run: cargo pgrx init --pg$PG_VER $(which pg_config) - - - name: Test PL/rust as "untrusted" - if: matrix.target == 'host' - run: cd plrust-tests && ./run-tests.sh "pg$PG_VER" - - - name: Test PL/rust as "trusted" (inc. postgrestd) - if: matrix.target == 'postgrestd' - run: cd plrust && STD_TARGETS="aarch64-postgres-linux-gnu" ./build && cargo test --verbose --no-default-features --features "pg$PG_VER trusted" - - - name: Test PLRust non-#[pg_test] tests - run: cargo test --no-default-features --features "pg$PG_VER" -- --skip alter --skip argument --skip basic --skip blocked_code --skip borrow_mut_error --skip ddl --skip dependencies --skip matches --skip panics --skip range --skip recursion --skip return_values --skip round_trip --skip time_and_dates --skip trusted --skip user_defined_types --skip versioning - - - name: Print sccache stats (after build) - run: sccache --show-stats - - - name: Stop sccache server - run: sccache --stop-server || true - - - name: Store Cargo cache if applicable - run: | - . $WORK_DIR/.github/scripts/save_cache.sh - # See /.github/scripts/save_cache.sh for more details - cargo_cache_key="plrust-arm64-amzn2-cargo-cache-$CACHE_KEY_VERSION-$CACHE_KEY_CHECKSUM.tar.lz4" - cargo_dirs=( \ - $HOME/.cargo/ \ - ) - savecache $cargo_cache_key "${cargo_dirs[@]}" - - - name: Store sccache if applicable - run: | - # See /.github/scripts/save_cache.sh for more details - . $WORK_DIR/.github/scripts/save_cache.sh - sccache_key="plrust-arm64-amzn2-sccache-cache-$CACHE_KEY_VERSION-$CACHE_KEY_CHECKSUM.tar.lz4" - sccache_dirs=($SCCACHE_DIR) - savecache $sccache_key "${sccache_dirs[@]}" + # plrust_arm64_amzn2: + # name: aarch64 tests (amzn2) + # runs-on: [self-hosted, linux, ARM64, launch_template_id__lt-0013395587950cb5d] + # defaults: + # run: + # shell: bash + + # strategy: + # matrix: + # version: ["pg13", "pg14", "pg15"] + # target: ["host", "postgrestd"] + # fail-fast: false + + # # Note about environment variables: Even though a majority of these environment variables are baked into the runner image, + # # Github Actions seemingly runs self-hosted tasks in a different manner such that even baken-in environment variables are + # # removed. Howevever, they can be declared here and persist through all tasks. Assume that the home directory for the + # # runner is /home/ec2-user + # env: + # AWS_CACHE_BUCKET: tcdi-ci-plrust-build-cache.private + # CACHE_KEY_VERSION: v0 + # CI: true + # PLRUST_TRUSTED_PGRX_OVERRIDE: "pgrx = { path = '/home/ec2-user/actions-runner/_work/plrust/plrust/plrust-trusted-pgrx', package='plrust-trusted-pgrx' }" + # RUSTUP_HOME: /home/ec2-user/.rustup + # RUSTC_WRAPPER: sccache + # RUSTFLAGS: -Copt-level=0 -Dwarnings + # SCCACHE_BIN_DIR: /home/ec2-user/.local/bin + # SCCACHE_CACHE_SIZE: 20G + # SCCACHE_DIR: /home/ec2-user/.cache/sccache + # SCCACHE_IDLE_TIMEOUT: 0 + # WORK_DIR: /home/ec2-user/actions-runner/_work/plrust/plrust + + # steps: + # - uses: actions/checkout@v3 + + # - name: Generate cache filename checksum + # run: | + # cd $WORK_DIR + # shopt -s globstar + # checksum=$(cat **/Cargo.lock **/rust-toolchain.toml .github/workflows/ci.yml .cargo/config | sha256sum | awk '{print $1}') + # echo "CACHE_KEY_CHECKSUM=$checksum" >> $GITHUB_ENV + + # - name: Set up (Linux) prerequisites and environment + # run: | + # echo "" + # echo "----- Print kernel info -----" + # uname -a + # echo "" + + # echo "----- Set up dynamic variables -----" + # export PG_VER=$(echo ${{ matrix.version }} | cut -c 3-) + # echo "PG_VER=$PG_VER" >> $GITHUB_ENV + # echo "MAKEFLAGS=$MAKEFLAGS -j $(grep -c ^processor /proc/cpuinfo)" >> $GITHUB_ENV + # echo "$SCCACHE_BIN_DIR" >> $GITHUB_PATH + # echo "PATH=$HOME/.cargo/bin:$PATH" >> $GITHUB_ENV + # echo "" + + # echo "----- Install system dependencies -----" + # # Add any extra dependencies here if necessary + + # echo "----- Install PostgreSQL $PG_VER -----" + # sudo yum install -y "postgresql$PG_VER" "postgresql$PG_VER-server" "postgresql$PG_VER-devel" + # export PATH="/usr/pgsql-$PG_VER/bin/:$PATH" + # echo "/usr/pgsql-$PG_VER/bin/" >> $GITHUB_PATH + + # echo "----- Set postgres permissions -----" + # sudo chmod a+rwx `$(which pg_config) --pkglibdir` `$(which pg_config) --sharedir`/extension + # echo "" + + # echo "----- Create artifacts directory -----" + # mkdir -p $HOME/artifacts + # echo "" + + # cat $GITHUB_ENV + # echo "" + # env + + # - name: Load Cargo cache if available + # run: | + # # See /.github/scripts/load_cache.sh for more details + # . $WORK_DIR/.github/scripts/load_cache.sh + # cargo_cache_key="plrust-arm64-amzn2-cargo-cache-$CACHE_KEY_VERSION-$CACHE_KEY_CHECKSUM.tar.lz4" + # loadcache $cargo_cache_key + + # - name: Create protected files + # run: | + # sudo mkdir -p /var/ci-stuff/secret_rust_files + # sudo echo "const FOO:i32 = 7;" /var/ci-stuff/secret_rust_files/const_foo.rs + # sudo echo "const BAR:i32 = 8;" /var/ci-stuff/const_bar.rs + # sudo chmod -R 600 /var/ci-stuff/secret_rust_files + # if: matrix.target == 'postgrestd' + + # - name: Load sccache cache if available + # run: | + # # See /.github/scripts/load_cache.sh for more details + # . $WORK_DIR/.github/scripts/load_cache.sh + # sccache_key="plrust-arm64-amzn2-sccache-cache-$CACHE_KEY_VERSION-$CACHE_KEY_CHECKSUM.tar.lz4" + # loadcache $sccache_key + + # - name: Start sccache server + # run: sccache --start-server && sccache --show-stats + + # # See /.github/scripts/install_cargo_pgrx.sh for more details + # - name: Install cargo-pgrx defined in plrust/Cargo.toml + # run: | + # . $WORK_DIR/.github/scripts/install_cargo_pgrx.sh + # install_cargo_pgrx + + # - name: Print sccache stats + # run: sccache --show-stats + + # - name: Install llvm-tools-preview + # run: rustup component add llvm-tools-preview rustc-dev + + # - name: Test plrustc + # run: cd plrustc && cargo test -p plrustc + + # - name: Print sccache stats + # run: sccache --show-stats + + # - name: install plrustc + # run: cd plrustc && ./build.sh && cp ../build/bin/plrustc ~/.cargo/bin + + # - name: Print sccache stats + # run: sccache --show-stats + + # - name: Run cargo pgrx init + # run: cargo pgrx init --pg$PG_VER $(which pg_config) + + # - name: Test PL/rust as "untrusted" + # if: matrix.target == 'host' + # run: cd plrust-tests && ./run-tests.sh "pg$PG_VER" + + # - name: Test PL/rust as "trusted" (inc. postgrestd) + # if: matrix.target == 'postgrestd' + # run: cd plrust && STD_TARGETS="aarch64-postgres-linux-gnu" ./build && cargo test --verbose --no-default-features --features "pg$PG_VER trusted" + + # - name: Test PLRust non-#[pg_test] tests + # run: cargo test --no-default-features --features "pg$PG_VER" -- --skip alter --skip argument --skip basic --skip blocked_code --skip borrow_mut_error --skip ddl --skip dependencies --skip matches --skip panics --skip range --skip recursion --skip return_values --skip round_trip --skip time_and_dates --skip trusted --skip user_defined_types --skip versioning + + # - name: Print sccache stats (after build) + # run: sccache --show-stats + + # - name: Stop sccache server + # run: sccache --stop-server || true + + # - name: Store Cargo cache if applicable + # run: | + # . $WORK_DIR/.github/scripts/save_cache.sh + # # See /.github/scripts/save_cache.sh for more details + # cargo_cache_key="plrust-arm64-amzn2-cargo-cache-$CACHE_KEY_VERSION-$CACHE_KEY_CHECKSUM.tar.lz4" + # cargo_dirs=( \ + # $HOME/.cargo/ \ + # ) + # savecache $cargo_cache_key "${cargo_dirs[@]}" + + # - name: Store sccache if applicable + # run: | + # # See /.github/scripts/save_cache.sh for more details + # . $WORK_DIR/.github/scripts/save_cache.sh + # sccache_key="plrust-arm64-amzn2-sccache-cache-$CACHE_KEY_VERSION-$CACHE_KEY_CHECKSUM.tar.lz4" + # sccache_dirs=($SCCACHE_DIR) + # savecache $sccache_key "${sccache_dirs[@]}" plrust_x86_64: name: x86_64 tests @@ -199,10 +199,12 @@ jobs: strategy: matrix: - version: ["pg13", "pg14", "pg15"] + # version: ["pg13", "pg14", "pg15"] + version: ["pg14"] os: ["ubuntu-latest"] # it would be nice to other contributors to return "macos-11" to the above array - target: ["host", "postgrestd"] + # target: ["host", "postgrestd"] + target: ["postgrestd"] fail-fast: false steps: @@ -318,6 +320,9 @@ jobs: - name: Print sccache stats run: sccache --show-stats + - name: Test plrustc + run: cd plrustc && cargo test + - name: Install plrustc run: cd plrustc && ./build.sh && cp ../build/bin/plrustc ~/.cargo/bin @@ -327,16 +332,28 @@ jobs: - name: Run 'cargo pgrx init' against system-level ${{ matrix.version }} run: cargo pgrx init --pg$PG_VER $(which pg_config) - - name: Test PL/rust as "untrusted" - if: matrix.target == 'host' - run: cd plrust-tests && ./run-tests.sh "pg$PG_VER" + - name: Test PL/Rust package as "trusted" + if: matrix.target == 'postgrestd' + run: cd plrust && cargo test --no-default-features --features "pg$PG_VER trusted" + + - name: Install PL/Rust as "trusted" + if: matrix.target == 'postgrestd' + run: cd plrust && echo "\q" | cargo pgrx run "pg$PG_VER" --features "trusted" - - name: Test PL/rust as "trusted" (inc. postgrestd) + - name: Run PL/Rust integration tests as "trusted" if: matrix.target == 'postgrestd' - run: cd plrust && STD_TARGETS="x86_64-postgres-linux-gnu" ./build && cargo test --verbose --no-default-features --features "pg$PG_VER trusted" + run: cd plrust-tests && cargo test --no-default-features --features "pg$PG_VER trusted" + + # - name: Test PL/rust as "untrusted" + # if: matrix.target == 'host' + # run: cd plrust-tests && ./run-tests.sh "pg$PG_VER" + + # - name: Test PL/rust as "trusted" (inc. postgrestd) + # if: matrix.target == 'postgrestd' + # run: cd plrust && STD_TARGETS="x86_64-postgres-linux-gnu" ./build && cargo test --verbose --no-default-features --features "pg$PG_VER trusted" - - name: Test PLRust non-#[pg_test] tests - run: cargo test --no-default-features --features "pg$PG_VER" -- --skip alter --skip argument --skip basic --skip blocked_code --skip borrow_mut_error --skip ddl --skip dependencies --skip matches --skip panics --skip range --skip recursion --skip return_values --skip round_trip --skip time_and_dates --skip trusted --skip user_defined_types --skip versioning + # - name: Test PLRust non-#[pg_test] tests + # run: cargo test --no-default-features --features "pg$PG_VER" -- --skip alter --skip argument --skip basic --skip blocked_code --skip borrow_mut_error --skip ddl --skip dependencies --skip matches --skip panics --skip range --skip recursion --skip return_values --skip round_trip --skip time_and_dates --skip trusted --skip user_defined_types --skip versioning - name: Print sccache stats run: sccache --show-stats diff --git a/plrust-tests/Cargo.toml b/plrust-tests/Cargo.toml index c31ba889..d767728c 100644 --- a/plrust-tests/Cargo.toml +++ b/plrust-tests/Cargo.toml @@ -12,6 +12,7 @@ pg13 = ["pgrx/pg13", "pgrx-tests/pg13" ] pg14 = ["pgrx/pg14", "pgrx-tests/pg14" ] pg15 = ["pgrx/pg15", "pgrx-tests/pg15" ] pg_test = [] +trusted = [] [dependencies] pgrx = "=0.9.8" diff --git a/plrust-tests/src/trusted.rs b/plrust-tests/src/trusted.rs index f6ffc4ac..3bd3342d 100644 --- a/plrust-tests/src/trusted.rs +++ b/plrust-tests/src/trusted.rs @@ -1,4 +1,3 @@ - /* Portions Copyright 2020-2021 ZomboDB, LLC. Portions Copyright 2021-2023 Technology Concepts & Design, Inc. @@ -11,6 +10,7 @@ Use of this source code is governed by the PostgreSQL license that can be found #[cfg(any(test, feature = "pg_test"))] #[pgrx::pg_schema] mod tests { + use pgrx::prelude::*; #[cfg(feature = "trusted")] #[pg_test] @@ -144,44 +144,44 @@ mod tests { Spi::run(definition).unwrap(); } - #[pg_test] - #[search_path(@extschema@)] - #[cfg(feature = "trusted")] - #[should_panic = "No such file or directory (os error 2)"] - fn plrustc_include_path_traversal() { - use std::path::PathBuf; - let workdir = crate::gucs::work_dir(); - let wd: PathBuf = workdir - .canonicalize() - .ok() - .expect("Failed to canonicalize workdir"); - // Produce a path that looks like - // `/allowed/path/here/../../../illegal/path/here` and check that it's - // rejected, in order to ensure we are not succeptable to path traversal - // attacks. - let mut evil_path = wd.clone(); - for _ in wd.ancestors().skip(1) { - evil_path.push(".."); - } - debug_assert_eq!( - evil_path - .canonicalize() - .ok() - .expect("Failed to produce unpath") - .to_str(), - Some("/") - ); - evil_path.push("var/ci-stuff/const_bar.rs"); - // This file does not exist, and should be reported as such. - let definition = format!( - r#"CREATE FUNCTION include_sneaky_traversal() - RETURNS int AS $$ - include!({evil_path:?}); - Ok(Some(1)) - $$ LANGUAGE plrust;"# - ); - Spi::run(&definition).unwrap(); - } + // #[pg_test] + // #[search_path(@extschema@)] + // #[cfg(feature = "trusted")] + // #[should_panic = "No such file or directory (os error 2)"] + // fn plrustc_include_path_traversal() { + // use std::path::PathBuf; + // let workdir = crate::gucs::work_dir(); + // let wd: PathBuf = workdir + // .canonicalize() + // .ok() + // .expect("Failed to canonicalize workdir"); + // // Produce a path that looks like + // // `/allowed/path/here/../../../illegal/path/here` and check that it's + // // rejected, in order to ensure we are not succeptable to path traversal + // // attacks. + // let mut evil_path = wd.clone(); + // for _ in wd.ancestors().skip(1) { + // evil_path.push(".."); + // } + // debug_assert_eq!( + // evil_path + // .canonicalize() + // .ok() + // .expect("Failed to produce unpath") + // .to_str(), + // Some("/") + // ); + // evil_path.push("var/ci-stuff/const_bar.rs"); + // // This file does not exist, and should be reported as such. + // let definition = format!( + // r#"CREATE FUNCTION include_sneaky_traversal() + // RETURNS int AS $$ + // include!({evil_path:?}); + // Ok(Some(1)) + // $$ LANGUAGE plrust;"# + // ); + // Spi::run(&definition).unwrap(); + // } #[pg_test] #[search_path(@extschema@)] @@ -232,5 +232,4 @@ mod tests { assert_eq!("operation not supported on this platform", &string); Ok(()) } - -} \ No newline at end of file +} From 22c2be372c4b57ada17a5f307feec3017f4f00b8 Mon Sep 17 00:00:00 2001 From: Brady Bonnette Date: Fri, 28 Jul 2023 16:59:13 -0400 Subject: [PATCH 16/32] Set up plrust targets properly --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5493d18f..49eab96a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -338,7 +338,7 @@ jobs: - name: Install PL/Rust as "trusted" if: matrix.target == 'postgrestd' - run: cd plrust && echo "\q" | cargo pgrx run "pg$PG_VER" --features "trusted" + run: cd plrust && STD_TARGETS="x86_64-postgres-linux-gnu" ./build && echo "\q" | cargo pgrx run "pg$PG_VER" --features "trusted" - name: Run PL/Rust integration tests as "trusted" if: matrix.target == 'postgrestd' From 13266fc18b47f310be55cdf6c1bd9420cbf2dfe0 Mon Sep 17 00:00:00 2001 From: Brady Bonnette Date: Fri, 28 Jul 2023 17:09:27 -0400 Subject: [PATCH 17/32] install plrust before testing --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 49eab96a..fd0c4cd5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -332,14 +332,14 @@ jobs: - name: Run 'cargo pgrx init' against system-level ${{ matrix.version }} run: cargo pgrx init --pg$PG_VER $(which pg_config) - - name: Test PL/Rust package as "trusted" - if: matrix.target == 'postgrestd' - run: cd plrust && cargo test --no-default-features --features "pg$PG_VER trusted" - - name: Install PL/Rust as "trusted" if: matrix.target == 'postgrestd' run: cd plrust && STD_TARGETS="x86_64-postgres-linux-gnu" ./build && echo "\q" | cargo pgrx run "pg$PG_VER" --features "trusted" + - name: Test PL/Rust package as "trusted" + if: matrix.target == 'postgrestd' + run: cd plrust && cargo test --no-default-features --features "pg$PG_VER trusted" + - name: Run PL/Rust integration tests as "trusted" if: matrix.target == 'postgrestd' run: cd plrust-tests && cargo test --no-default-features --features "pg$PG_VER trusted" From 69fdc1a53b73b60b8932ff58c5d4b36f295889de Mon Sep 17 00:00:00 2001 From: Brady Bonnette Date: Fri, 28 Jul 2023 17:31:34 -0400 Subject: [PATCH 18/32] not sure why this might be needed, but... --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fd0c4cd5..dec83a02 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -342,7 +342,7 @@ jobs: - name: Run PL/Rust integration tests as "trusted" if: matrix.target == 'postgrestd' - run: cd plrust-tests && cargo test --no-default-features --features "pg$PG_VER trusted" + run: cd plrust && echo "\q" | cargo pgrx run "pg$PG_VER" --features "trusted" && cd ../plrust-tests && cargo test --no-default-features --features "pg$PG_VER trusted" # - name: Test PL/rust as "untrusted" # if: matrix.target == 'host' From e7ddfa2b3845ce4315af4800805fdc5b053dfd07 Mon Sep 17 00:00:00 2001 From: Brady Bonnette Date: Fri, 28 Jul 2023 18:15:27 -0400 Subject: [PATCH 19/32] try untrusted tests --- .github/workflows/ci.yml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dec83a02..bb0f220f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -203,8 +203,8 @@ jobs: version: ["pg14"] os: ["ubuntu-latest"] # it would be nice to other contributors to return "macos-11" to the above array - # target: ["host", "postgrestd"] - target: ["postgrestd"] + target: ["host", "postgrestd"] + # target: ["postgrestd"] fail-fast: false steps: @@ -344,6 +344,18 @@ jobs: if: matrix.target == 'postgrestd' run: cd plrust && echo "\q" | cargo pgrx run "pg$PG_VER" --features "trusted" && cd ../plrust-tests && cargo test --no-default-features --features "pg$PG_VER trusted" + - name: Install PL/Rust as "untrusted" + if: matrix.target == 'host' + run: cd plrust && STD_TARGETS="x86_64-postgres-linux-gnu" ./build && echo "\q" | cargo pgrx run "pg$PG_VER" + + - name: Test PL/Rust package as "untrusted" + if: matrix.target == 'host' + run: cd plrust && cargo test --no-default-features --features "pg$PG_VER" + + - name: Run PL/Rust integration tests as "untrusted" + if: matrix.target == 'host' + run: cd plrust && echo "\q" | cargo pgrx run "pg$PG_VER" && cd ../plrust-tests && cargo test --no-default-features --features "pg$PG_VER" + # - name: Test PL/rust as "untrusted" # if: matrix.target == 'host' # run: cd plrust-tests && ./run-tests.sh "pg$PG_VER" From daae647749fbabfdd6e161b9527b174d0b30064b Mon Sep 17 00:00:00 2001 From: Brady Bonnette Date: Fri, 28 Jul 2023 18:54:20 -0400 Subject: [PATCH 20/32] Allow unused pgrx::prelude in trusted tests --- plrust-tests/src/trusted.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/plrust-tests/src/trusted.rs b/plrust-tests/src/trusted.rs index 3bd3342d..3d0113eb 100644 --- a/plrust-tests/src/trusted.rs +++ b/plrust-tests/src/trusted.rs @@ -10,6 +10,7 @@ Use of this source code is governed by the PostgreSQL license that can be found #[cfg(any(test, feature = "pg_test"))] #[pgrx::pg_schema] mod tests { + #[allow(unused)] use pgrx::prelude::*; #[cfg(feature = "trusted")] From 6d4ef8b386a134648bec206e78e009a6d9b1a477 Mon Sep 17 00:00:00 2001 From: Brady Bonnette Date: Fri, 28 Jul 2023 19:57:40 -0400 Subject: [PATCH 21/32] Try out aarch64 tests --- .github/workflows/ci.yml | 342 ++++++++++++++++++++------------------- 1 file changed, 180 insertions(+), 162 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb0f220f..0b653117 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,168 +20,186 @@ jobs: # The image created for running these tasks already have a majority of the dependencies installed, such as system # libraries, sccache, github runners, rust, etc. This is to cut back on the amount of time it takes to launch a runner. # If something is missing, it can be added here or baked into a new image outside of Github Actions. - # plrust_arm64_amzn2: - # name: aarch64 tests (amzn2) - # runs-on: [self-hosted, linux, ARM64, launch_template_id__lt-0013395587950cb5d] - # defaults: - # run: - # shell: bash - - # strategy: - # matrix: - # version: ["pg13", "pg14", "pg15"] - # target: ["host", "postgrestd"] - # fail-fast: false - - # # Note about environment variables: Even though a majority of these environment variables are baked into the runner image, - # # Github Actions seemingly runs self-hosted tasks in a different manner such that even baken-in environment variables are - # # removed. Howevever, they can be declared here and persist through all tasks. Assume that the home directory for the - # # runner is /home/ec2-user - # env: - # AWS_CACHE_BUCKET: tcdi-ci-plrust-build-cache.private - # CACHE_KEY_VERSION: v0 - # CI: true - # PLRUST_TRUSTED_PGRX_OVERRIDE: "pgrx = { path = '/home/ec2-user/actions-runner/_work/plrust/plrust/plrust-trusted-pgrx', package='plrust-trusted-pgrx' }" - # RUSTUP_HOME: /home/ec2-user/.rustup - # RUSTC_WRAPPER: sccache - # RUSTFLAGS: -Copt-level=0 -Dwarnings - # SCCACHE_BIN_DIR: /home/ec2-user/.local/bin - # SCCACHE_CACHE_SIZE: 20G - # SCCACHE_DIR: /home/ec2-user/.cache/sccache - # SCCACHE_IDLE_TIMEOUT: 0 - # WORK_DIR: /home/ec2-user/actions-runner/_work/plrust/plrust - - # steps: - # - uses: actions/checkout@v3 - - # - name: Generate cache filename checksum - # run: | - # cd $WORK_DIR - # shopt -s globstar - # checksum=$(cat **/Cargo.lock **/rust-toolchain.toml .github/workflows/ci.yml .cargo/config | sha256sum | awk '{print $1}') - # echo "CACHE_KEY_CHECKSUM=$checksum" >> $GITHUB_ENV - - # - name: Set up (Linux) prerequisites and environment - # run: | - # echo "" - # echo "----- Print kernel info -----" - # uname -a - # echo "" - - # echo "----- Set up dynamic variables -----" - # export PG_VER=$(echo ${{ matrix.version }} | cut -c 3-) - # echo "PG_VER=$PG_VER" >> $GITHUB_ENV - # echo "MAKEFLAGS=$MAKEFLAGS -j $(grep -c ^processor /proc/cpuinfo)" >> $GITHUB_ENV - # echo "$SCCACHE_BIN_DIR" >> $GITHUB_PATH - # echo "PATH=$HOME/.cargo/bin:$PATH" >> $GITHUB_ENV - # echo "" - - # echo "----- Install system dependencies -----" - # # Add any extra dependencies here if necessary - - # echo "----- Install PostgreSQL $PG_VER -----" - # sudo yum install -y "postgresql$PG_VER" "postgresql$PG_VER-server" "postgresql$PG_VER-devel" - # export PATH="/usr/pgsql-$PG_VER/bin/:$PATH" - # echo "/usr/pgsql-$PG_VER/bin/" >> $GITHUB_PATH - - # echo "----- Set postgres permissions -----" - # sudo chmod a+rwx `$(which pg_config) --pkglibdir` `$(which pg_config) --sharedir`/extension - # echo "" - - # echo "----- Create artifacts directory -----" - # mkdir -p $HOME/artifacts - # echo "" - - # cat $GITHUB_ENV - # echo "" - # env - - # - name: Load Cargo cache if available - # run: | - # # See /.github/scripts/load_cache.sh for more details - # . $WORK_DIR/.github/scripts/load_cache.sh - # cargo_cache_key="plrust-arm64-amzn2-cargo-cache-$CACHE_KEY_VERSION-$CACHE_KEY_CHECKSUM.tar.lz4" - # loadcache $cargo_cache_key - - # - name: Create protected files - # run: | - # sudo mkdir -p /var/ci-stuff/secret_rust_files - # sudo echo "const FOO:i32 = 7;" /var/ci-stuff/secret_rust_files/const_foo.rs - # sudo echo "const BAR:i32 = 8;" /var/ci-stuff/const_bar.rs - # sudo chmod -R 600 /var/ci-stuff/secret_rust_files - # if: matrix.target == 'postgrestd' - - # - name: Load sccache cache if available - # run: | - # # See /.github/scripts/load_cache.sh for more details - # . $WORK_DIR/.github/scripts/load_cache.sh - # sccache_key="plrust-arm64-amzn2-sccache-cache-$CACHE_KEY_VERSION-$CACHE_KEY_CHECKSUM.tar.lz4" - # loadcache $sccache_key - - # - name: Start sccache server - # run: sccache --start-server && sccache --show-stats - - # # See /.github/scripts/install_cargo_pgrx.sh for more details - # - name: Install cargo-pgrx defined in plrust/Cargo.toml - # run: | - # . $WORK_DIR/.github/scripts/install_cargo_pgrx.sh - # install_cargo_pgrx - - # - name: Print sccache stats - # run: sccache --show-stats - - # - name: Install llvm-tools-preview - # run: rustup component add llvm-tools-preview rustc-dev - - # - name: Test plrustc - # run: cd plrustc && cargo test -p plrustc - - # - name: Print sccache stats - # run: sccache --show-stats - - # - name: install plrustc - # run: cd plrustc && ./build.sh && cp ../build/bin/plrustc ~/.cargo/bin - - # - name: Print sccache stats - # run: sccache --show-stats - - # - name: Run cargo pgrx init - # run: cargo pgrx init --pg$PG_VER $(which pg_config) - - # - name: Test PL/rust as "untrusted" - # if: matrix.target == 'host' - # run: cd plrust-tests && ./run-tests.sh "pg$PG_VER" - - # - name: Test PL/rust as "trusted" (inc. postgrestd) - # if: matrix.target == 'postgrestd' - # run: cd plrust && STD_TARGETS="aarch64-postgres-linux-gnu" ./build && cargo test --verbose --no-default-features --features "pg$PG_VER trusted" - - # - name: Test PLRust non-#[pg_test] tests - # run: cargo test --no-default-features --features "pg$PG_VER" -- --skip alter --skip argument --skip basic --skip blocked_code --skip borrow_mut_error --skip ddl --skip dependencies --skip matches --skip panics --skip range --skip recursion --skip return_values --skip round_trip --skip time_and_dates --skip trusted --skip user_defined_types --skip versioning - - # - name: Print sccache stats (after build) - # run: sccache --show-stats - - # - name: Stop sccache server - # run: sccache --stop-server || true - - # - name: Store Cargo cache if applicable - # run: | - # . $WORK_DIR/.github/scripts/save_cache.sh - # # See /.github/scripts/save_cache.sh for more details - # cargo_cache_key="plrust-arm64-amzn2-cargo-cache-$CACHE_KEY_VERSION-$CACHE_KEY_CHECKSUM.tar.lz4" - # cargo_dirs=( \ - # $HOME/.cargo/ \ - # ) - # savecache $cargo_cache_key "${cargo_dirs[@]}" - - # - name: Store sccache if applicable - # run: | - # # See /.github/scripts/save_cache.sh for more details - # . $WORK_DIR/.github/scripts/save_cache.sh - # sccache_key="plrust-arm64-amzn2-sccache-cache-$CACHE_KEY_VERSION-$CACHE_KEY_CHECKSUM.tar.lz4" - # sccache_dirs=($SCCACHE_DIR) - # savecache $sccache_key "${sccache_dirs[@]}" + plrust_arm64_amzn2: + name: aarch64 tests (amzn2) + runs-on: [self-hosted, linux, ARM64, launch_template_id__lt-0013395587950cb5d] + defaults: + run: + shell: bash + + strategy: + matrix: + # version: ["pg13", "pg14", "pg15"] + version: ["pg14"] + target: ["host", "postgrestd"] + fail-fast: false + + # Note about environment variables: Even though a majority of these environment variables are baked into the runner image, + # Github Actions seemingly runs self-hosted tasks in a different manner such that even baken-in environment variables are + # removed. Howevever, they can be declared here and persist through all tasks. Assume that the home directory for the + # runner is /home/ec2-user + env: + AWS_CACHE_BUCKET: tcdi-ci-plrust-build-cache.private + CACHE_KEY_VERSION: v0 + CI: true + PLRUST_TRUSTED_PGRX_OVERRIDE: "pgrx = { path = '/home/ec2-user/actions-runner/_work/plrust/plrust/plrust-trusted-pgrx', package='plrust-trusted-pgrx' }" + RUSTUP_HOME: /home/ec2-user/.rustup + RUSTC_WRAPPER: sccache + RUSTFLAGS: -Copt-level=0 -Dwarnings + SCCACHE_BIN_DIR: /home/ec2-user/.local/bin + SCCACHE_CACHE_SIZE: 20G + SCCACHE_DIR: /home/ec2-user/.cache/sccache + SCCACHE_IDLE_TIMEOUT: 0 + WORK_DIR: /home/ec2-user/actions-runner/_work/plrust/plrust + + steps: + - uses: actions/checkout@v3 + + - name: Generate cache filename checksum + run: | + cd $WORK_DIR + shopt -s globstar + checksum=$(cat **/Cargo.lock **/rust-toolchain.toml .github/workflows/ci.yml .cargo/config | sha256sum | awk '{print $1}') + echo "CACHE_KEY_CHECKSUM=$checksum" >> $GITHUB_ENV + + - name: Set up (Linux) prerequisites and environment + run: | + echo "" + echo "----- Print kernel info -----" + uname -a + echo "" + + echo "----- Set up dynamic variables -----" + export PG_VER=$(echo ${{ matrix.version }} | cut -c 3-) + echo "PG_VER=$PG_VER" >> $GITHUB_ENV + echo "MAKEFLAGS=$MAKEFLAGS -j $(grep -c ^processor /proc/cpuinfo)" >> $GITHUB_ENV + echo "$SCCACHE_BIN_DIR" >> $GITHUB_PATH + echo "PATH=$HOME/.cargo/bin:$PATH" >> $GITHUB_ENV + echo "" + + echo "----- Install system dependencies -----" + # Add any extra dependencies here if necessary + + echo "----- Install PostgreSQL $PG_VER -----" + sudo yum install -y "postgresql$PG_VER" "postgresql$PG_VER-server" "postgresql$PG_VER-devel" + export PATH="/usr/pgsql-$PG_VER/bin/:$PATH" + echo "/usr/pgsql-$PG_VER/bin/" >> $GITHUB_PATH + + echo "----- Set postgres permissions -----" + sudo chmod a+rwx `$(which pg_config) --pkglibdir` `$(which pg_config) --sharedir`/extension + echo "" + + echo "----- Create artifacts directory -----" + mkdir -p $HOME/artifacts + echo "" + + cat $GITHUB_ENV + echo "" + env + + - name: Load Cargo cache if available + run: | + # See /.github/scripts/load_cache.sh for more details + . $WORK_DIR/.github/scripts/load_cache.sh + cargo_cache_key="plrust-arm64-amzn2-cargo-cache-$CACHE_KEY_VERSION-$CACHE_KEY_CHECKSUM.tar.lz4" + loadcache $cargo_cache_key + + - name: Create protected files + run: | + sudo mkdir -p /var/ci-stuff/secret_rust_files + sudo echo "const FOO:i32 = 7;" /var/ci-stuff/secret_rust_files/const_foo.rs + sudo echo "const BAR:i32 = 8;" /var/ci-stuff/const_bar.rs + sudo chmod -R 600 /var/ci-stuff/secret_rust_files + if: matrix.target == 'postgrestd' + + - name: Load sccache cache if available + run: | + # See /.github/scripts/load_cache.sh for more details + . $WORK_DIR/.github/scripts/load_cache.sh + sccache_key="plrust-arm64-amzn2-sccache-cache-$CACHE_KEY_VERSION-$CACHE_KEY_CHECKSUM.tar.lz4" + loadcache $sccache_key + + - name: Start sccache server + run: sccache --start-server && sccache --show-stats + + # See /.github/scripts/install_cargo_pgrx.sh for more details + - name: Install cargo-pgrx defined in plrust/Cargo.toml + run: | + . $WORK_DIR/.github/scripts/install_cargo_pgrx.sh + install_cargo_pgrx + + - name: Print sccache stats + run: sccache --show-stats + + - name: Install llvm-tools-preview + run: rustup component add llvm-tools-preview rustc-dev + + - name: Test plrustc + run: cd plrustc && cargo test -p plrustc + + - name: Print sccache stats + run: sccache --show-stats + + - name: install plrustc + run: cd plrustc && ./build.sh && cp ../build/bin/plrustc ~/.cargo/bin + + - name: Print sccache stats + run: sccache --show-stats + + - name: Run cargo pgrx init + run: cargo pgrx init --pg$PG_VER $(which pg_config) + + # ============================================================== + + - name: Install PL/Rust as "trusted" + if: matrix.target == 'postgrestd' + run: cd plrust && STD_TARGETS="aarch64-postgres-linux-gnu" ./build && echo "\q" | cargo pgrx run "pg$PG_VER" --features "trusted" + + - name: Test PL/Rust package as "trusted" + if: matrix.target == 'postgrestd' + run: cd plrust && cargo test --no-default-features --features "pg$PG_VER trusted" + + - name: Run PL/Rust integration tests as "trusted" + if: matrix.target == 'postgrestd' + run: cd plrust && echo "\q" | cargo pgrx run "pg$PG_VER" --features "trusted" && cd ../plrust-tests && cargo test --no-default-features --features "pg$PG_VER trusted" + + - name: Install PL/Rust as "untrusted" + if: matrix.target == 'host' + run: cd plrust && STD_TARGETS="aarch64-postgres-linux-gnu" ./build && echo "\q" | cargo pgrx run "pg$PG_VER" + + - name: Test PL/Rust package as "untrusted" + if: matrix.target == 'host' + run: cd plrust && cargo test --no-default-features --features "pg$PG_VER" + + - name: Run PL/Rust integration tests as "untrusted" + if: matrix.target == 'host' + run: cd plrust && echo "\q" | cargo pgrx run "pg$PG_VER" && cd ../plrust-tests && cargo test --no-default-features --features "pg$PG_VER" + + # ============================================================== + + - name: Print sccache stats (after build) + run: sccache --show-stats + + - name: Stop sccache server + run: sccache --stop-server || true + + - name: Store Cargo cache if applicable + run: | + . $WORK_DIR/.github/scripts/save_cache.sh + # See /.github/scripts/save_cache.sh for more details + cargo_cache_key="plrust-arm64-amzn2-cargo-cache-$CACHE_KEY_VERSION-$CACHE_KEY_CHECKSUM.tar.lz4" + cargo_dirs=( \ + $HOME/.cargo/ \ + ) + savecache $cargo_cache_key "${cargo_dirs[@]}" + + - name: Store sccache if applicable + run: | + # See /.github/scripts/save_cache.sh for more details + . $WORK_DIR/.github/scripts/save_cache.sh + sccache_key="plrust-arm64-amzn2-sccache-cache-$CACHE_KEY_VERSION-$CACHE_KEY_CHECKSUM.tar.lz4" + sccache_dirs=($SCCACHE_DIR) + savecache $sccache_key "${sccache_dirs[@]}" plrust_x86_64: name: x86_64 tests From f7d3b666fbe431444a523486cdd4de076e3e7074 Mon Sep 17 00:00:00 2001 From: Brady Bonnette Date: Mon, 31 Jul 2023 11:28:15 -0400 Subject: [PATCH 22/32] Try all versions --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b653117..22ad6106 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,8 +29,8 @@ jobs: strategy: matrix: - # version: ["pg13", "pg14", "pg15"] - version: ["pg14"] + version: ["pg13", "pg14", "pg15"] + # version: ["pg14"] target: ["host", "postgrestd"] fail-fast: false @@ -217,8 +217,8 @@ jobs: strategy: matrix: - # version: ["pg13", "pg14", "pg15"] - version: ["pg14"] + version: ["pg13", "pg14", "pg15"] + # version: ["pg14"] os: ["ubuntu-latest"] # it would be nice to other contributors to return "macos-11" to the above array target: ["host", "postgrestd"] From e4eec4c49131d104114dd6a3d01820c320972798 Mon Sep 17 00:00:00 2001 From: Brady Bonnette Date: Mon, 31 Jul 2023 13:12:32 -0400 Subject: [PATCH 23/32] Re-introduces trusted path-traversal test after fixing work_dir issue --- plrust-tests/src/trusted.rs | 80 +++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 38 deletions(-) diff --git a/plrust-tests/src/trusted.rs b/plrust-tests/src/trusted.rs index 3d0113eb..33b373a6 100644 --- a/plrust-tests/src/trusted.rs +++ b/plrust-tests/src/trusted.rs @@ -145,44 +145,48 @@ mod tests { Spi::run(definition).unwrap(); } - // #[pg_test] - // #[search_path(@extschema@)] - // #[cfg(feature = "trusted")] - // #[should_panic = "No such file or directory (os error 2)"] - // fn plrustc_include_path_traversal() { - // use std::path::PathBuf; - // let workdir = crate::gucs::work_dir(); - // let wd: PathBuf = workdir - // .canonicalize() - // .ok() - // .expect("Failed to canonicalize workdir"); - // // Produce a path that looks like - // // `/allowed/path/here/../../../illegal/path/here` and check that it's - // // rejected, in order to ensure we are not succeptable to path traversal - // // attacks. - // let mut evil_path = wd.clone(); - // for _ in wd.ancestors().skip(1) { - // evil_path.push(".."); - // } - // debug_assert_eq!( - // evil_path - // .canonicalize() - // .ok() - // .expect("Failed to produce unpath") - // .to_str(), - // Some("/") - // ); - // evil_path.push("var/ci-stuff/const_bar.rs"); - // // This file does not exist, and should be reported as such. - // let definition = format!( - // r#"CREATE FUNCTION include_sneaky_traversal() - // RETURNS int AS $$ - // include!({evil_path:?}); - // Ok(Some(1)) - // $$ LANGUAGE plrust;"# - // ); - // Spi::run(&definition).unwrap(); - // } + #[pg_test] + #[search_path(@extschema@)] + #[cfg(feature = "trusted")] + #[should_panic = "No such file or directory (os error 2)"] + fn plrustc_include_path_traversal() { + use std::path::PathBuf; + let workdir = Spi::get_one::("SHOW plrust.work_dir") + .expect("Could not get plrust.work_dir") + .unwrap(); + + let wd: PathBuf = PathBuf::from(workdir) + .canonicalize() + .ok() + .expect("Failed to canonicalize workdir"); + + // Produce a path that looks like + // `/allowed/path/here/../../../illegal/path/here` and check that it's + // rejected, in order to ensure we are not succeptable to path traversal + // attacks. + let mut evil_path = wd.clone(); + for _ in wd.ancestors().skip(1) { + evil_path.push(".."); + } + debug_assert_eq!( + evil_path + .canonicalize() + .ok() + .expect("Failed to produce unpath") + .to_str(), + Some("/") + ); + evil_path.push("var/ci-stuff/const_bar.rs"); + // This file does not exist, and should be reported as such. + let definition = format!( + r#"CREATE FUNCTION include_sneaky_traversal() + RETURNS int AS $$ + include!({evil_path:?}); + Ok(Some(1)) + $$ LANGUAGE plrust;"# + ); + Spi::run(&definition).unwrap(); + } #[pg_test] #[search_path(@extschema@)] From 1e77100cda432fd82c2db3d2084f5837a741d9b2 Mon Sep 17 00:00:00 2001 From: Brady Bonnette Date: Mon, 31 Jul 2023 14:43:49 -0400 Subject: [PATCH 24/32] some cleanup of ci.yml --- .github/workflows/ci.yml | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 22ad6106..36e275e9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,6 @@ jobs: strategy: matrix: version: ["pg13", "pg14", "pg15"] - # version: ["pg14"] target: ["host", "postgrestd"] fail-fast: false @@ -149,8 +148,6 @@ jobs: - name: Run cargo pgrx init run: cargo pgrx init --pg$PG_VER $(which pg_config) - # ============================================================== - - name: Install PL/Rust as "trusted" if: matrix.target == 'postgrestd' run: cd plrust && STD_TARGETS="aarch64-postgres-linux-gnu" ./build && echo "\q" | cargo pgrx run "pg$PG_VER" --features "trusted" @@ -175,8 +172,6 @@ jobs: if: matrix.target == 'host' run: cd plrust && echo "\q" | cargo pgrx run "pg$PG_VER" && cd ../plrust-tests && cargo test --no-default-features --features "pg$PG_VER" - # ============================================================== - - name: Print sccache stats (after build) run: sccache --show-stats @@ -218,11 +213,9 @@ jobs: strategy: matrix: version: ["pg13", "pg14", "pg15"] - # version: ["pg14"] os: ["ubuntu-latest"] # it would be nice to other contributors to return "macos-11" to the above array target: ["host", "postgrestd"] - # target: ["postgrestd"] fail-fast: false steps: @@ -374,17 +367,6 @@ jobs: if: matrix.target == 'host' run: cd plrust && echo "\q" | cargo pgrx run "pg$PG_VER" && cd ../plrust-tests && cargo test --no-default-features --features "pg$PG_VER" - # - name: Test PL/rust as "untrusted" - # if: matrix.target == 'host' - # run: cd plrust-tests && ./run-tests.sh "pg$PG_VER" - - # - name: Test PL/rust as "trusted" (inc. postgrestd) - # if: matrix.target == 'postgrestd' - # run: cd plrust && STD_TARGETS="x86_64-postgres-linux-gnu" ./build && cargo test --verbose --no-default-features --features "pg$PG_VER trusted" - - # - name: Test PLRust non-#[pg_test] tests - # run: cargo test --no-default-features --features "pg$PG_VER" -- --skip alter --skip argument --skip basic --skip blocked_code --skip borrow_mut_error --skip ddl --skip dependencies --skip matches --skip panics --skip range --skip recursion --skip return_values --skip round_trip --skip time_and_dates --skip trusted --skip user_defined_types --skip versioning - - name: Print sccache stats run: sccache --show-stats From 3437334f99f9b8b1b0a8f18e664112aeb366cfb6 Mon Sep 17 00:00:00 2001 From: Dave Selph Date: Thu, 10 Aug 2023 13:01:27 -0600 Subject: [PATCH 25/32] change pgrx to 0.9.7 --- plrust-tests/Cargo.toml | 4 ++-- plrust-trusted-pgrx/Cargo.toml | 2 +- plrust/Cargo.toml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plrust-tests/Cargo.toml b/plrust-tests/Cargo.toml index d767728c..acd0103f 100644 --- a/plrust-tests/Cargo.toml +++ b/plrust-tests/Cargo.toml @@ -15,11 +15,11 @@ pg_test = [] trusted = [] [dependencies] -pgrx = "=0.9.8" +pgrx = "=0.9.7" tempdir = "0.3.7" once_cell = "1.18.0" [dev-dependencies] -pgrx-tests = "=0.9.8" +pgrx-tests = "=0.9.7" tempdir = "0.3.7" once_cell = "1.18.0" diff --git a/plrust-trusted-pgrx/Cargo.toml b/plrust-trusted-pgrx/Cargo.toml index 6cd11754..2debe077 100644 --- a/plrust-trusted-pgrx/Cargo.toml +++ b/plrust-trusted-pgrx/Cargo.toml @@ -18,7 +18,7 @@ pg15 = ["pgrx/pg15"] [dependencies] # changing the pgrx version will likely require at least a minor version bump to this create -pgrx = { version = "=0.9.8", features = [ "no-schema-generation" ], default-features = false } +pgrx = { version = "=0.9.7", features = [ "no-schema-generation" ], default-features = false } [package.metadata.docs.rs] features = ["pg14"] diff --git a/plrust/Cargo.toml b/plrust/Cargo.toml index 671b0ce8..999a3a65 100644 --- a/plrust/Cargo.toml +++ b/plrust/Cargo.toml @@ -39,7 +39,7 @@ serde = "1.0.164" serde_json = "1.0.97" # pgrx core details -pgrx = { version = "=0.9.8" } +pgrx = { version = "=0.9.7" } # language handler support libloading = "0.8.0" @@ -66,6 +66,6 @@ memfd = "0.6.3" # for anonymously writing/loading user function .so [dev-dependencies] -pgrx-tests = { version = "=0.9.8" } +pgrx-tests = { version = "=0.9.7" } once_cell = "1.18.0" toml = "0.7.4" From 24c7c886f40ed8dcd19133ca9b00967c162557e0 Mon Sep 17 00:00:00 2001 From: Dave Selph Date: Tue, 22 Aug 2023 12:27:56 -0600 Subject: [PATCH 26/32] remove commented out code that lingered --- Cargo.lock | 25 ++++++++++++------------- plrust-tests/src/lib.rs | 35 ----------------------------------- 2 files changed, 12 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 64c760fa..3e6b0361 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1015,9 +1015,9 @@ dependencies = [ [[package]] name = "pgrx" -version = "0.9.8" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e80e25d7f85997d5d24c824297529bcb73231bbdc74d77906004d41cd3ffee3" +checksum = "6186d4aa5911be4c00b52e555779deece35a7563c87fcfe794407dc2e9cc4dc1" dependencies = [ "atomic-traits", "bitflags 2.3.3", @@ -1040,9 +1040,9 @@ dependencies = [ [[package]] name = "pgrx-macros" -version = "0.9.8" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "999ef782b36bb511806277f2a74a7f9e075edcad8c9439a3b90f4c90384f2a29" +checksum = "479a66a8c582e0fdf101178473315cb13eaa10829c154db742c35ec0279cdaec" dependencies = [ "pgrx-sql-entity-graph", "proc-macro2", @@ -1052,9 +1052,9 @@ dependencies = [ [[package]] name = "pgrx-pg-config" -version = "0.9.8" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7b27ccd3d892e3b27bcb7a6e2bf86588d82fad3da622db168261bc6b534a737" +checksum = "1e45c557631217a13859e223899c01d35982ef0c860ee5ab65af496f830b1316" dependencies = [ "cargo_toml", "dirs", @@ -1070,9 +1070,9 @@ dependencies = [ [[package]] name = "pgrx-pg-sys" -version = "0.9.8" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0767fdf6930ba6fa2d1b1934313aae3694b70732e0b6169ece26b03de27f8dc" +checksum = "0dde896a17c638b6475d6fc12b571a176013a8486437bbc8a64ac2afb8ba5d58" dependencies = [ "bindgen", "eyre", @@ -1092,9 +1092,9 @@ dependencies = [ [[package]] name = "pgrx-sql-entity-graph" -version = "0.9.8" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d632abaa9c3da42e5e2a17a6268afb0449a7f655764c65e06695ee55763ff0e" +checksum = "b1e9abc71b018d90aa9b7a34fedf48b76da5d55c04d2ed2288096827bebbf403" dependencies = [ "convert_case", "eyre", @@ -1107,9 +1107,9 @@ dependencies = [ [[package]] name = "pgrx-tests" -version = "0.9.8" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d44327bd084bcdc6fe4e72dfce8065e23b5b4522f36d63d14ee21c5000e7c73c" +checksum = "39ac4ffedfa247f9d51421e4e2ac18c33d8d674350bad730f3fe5736bf298612" dependencies = [ "clap-cargo", "eyre", @@ -1120,7 +1120,6 @@ dependencies = [ "pgrx-macros", "pgrx-pg-config", "postgres", - "rand 0.8.5", "regex", "serde", "serde_json", diff --git a/plrust-tests/src/lib.rs b/plrust-tests/src/lib.rs index 0d0a7b16..ab96dbeb 100644 --- a/plrust-tests/src/lib.rs +++ b/plrust-tests/src/lib.rs @@ -110,38 +110,3 @@ plutonium = "*" } } - -/* -#[cfg(any(test, feature = "pg_test"))] -#[pg_schema] -mod tests { - use pgrx::prelude::*; - - #[pg_test] - fn test_plrust_works() -> spi::Result<()> { - Spi::run("CREATE FUNCTION test_plrust_works() RETURNS int LANGUAGE plrust AS $$ Ok(Some(1)) $$;")?; - let result = Spi::get_one::("SELECT test_plrust_works();"); - assert_eq!(result, Ok(Some(1))); - - Ok(()) - } - -} - -/// This module is required by `cargo pgrx test` invocations. -/// It must be visible at the root of your extension crate. -#[cfg(test)] -pub mod pg_test { - pub fn setup(_options: Vec<&str>) { - // perform one-off initialization when the pg_test framework starts - } - - pub fn postgresql_conf_options() -> Vec<&'static str> { - // return any postgresql.conf settings that are required for your tests - vec![ - "shared_preload_libraries = 'plrust'", - "plrust.work_dir = '/tmp/plrust-work-dir'" - ] - } -} -*/ From 41fc38ef5759e6ac489357fb31e35941d4f85dcb Mon Sep 17 00:00:00 2001 From: Dave Selph Date: Tue, 22 Aug 2023 12:32:06 -0600 Subject: [PATCH 27/32] update tempdir --- plrust-tests/Cargo.toml | 4 ++-- plrust-tests/src/lib.rs | 8 +++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/plrust-tests/Cargo.toml b/plrust-tests/Cargo.toml index acd0103f..7c63ecf7 100644 --- a/plrust-tests/Cargo.toml +++ b/plrust-tests/Cargo.toml @@ -16,10 +16,10 @@ trusted = [] [dependencies] pgrx = "=0.9.7" -tempdir = "0.3.7" +tempdir = "0.3.8" once_cell = "1.18.0" [dev-dependencies] pgrx-tests = "=0.9.7" -tempdir = "0.3.7" +tempdir = "0.3.8" once_cell = "1.18.0" diff --git a/plrust-tests/src/lib.rs b/plrust-tests/src/lib.rs index ab96dbeb..0bb23bdc 100644 --- a/plrust-tests/src/lib.rs +++ b/plrust-tests/src/lib.rs @@ -53,19 +53,17 @@ mod tests { #[cfg(any(test, feature = "pg_test"))] pub mod pg_test { use once_cell::sync::Lazy; - use tempdir::TempDir; - + use tempfile::{tempdir, TempDir}; static WORK_DIR: Lazy = Lazy::new(|| { - let work_dir = TempDir::new("plrust-tests").expect("Couldn't create tempdir"); + let work_dir = tempdir().expect("Couldn't create tempdir"); format!("plrust.work_dir='{}'", work_dir.path().display()) }); static LOG_LEVEL: &str = "plrust.tracing_level=trace"; - static PLRUST_ALLOWED_DEPENDENCIES_FILE_NAME: &str = "allowed_deps.toml"; static PLRUST_ALLOWED_DEPENDENCIES_FILE_DIRECTORY: Lazy = Lazy::new(|| { use std::io::Write; let temp_allowed_deps_dir = - TempDir::new("plrust-allowed-deps").expect("Couldnt create tempdir"); + tempdir().expect("Couldnt create tempdir"); let file_path = temp_allowed_deps_dir .path() From 2e752919edf54396817f69f89bca5c335bc4be56 Mon Sep 17 00:00:00 2001 From: Jubilee Young Date: Tue, 22 Aug 2023 13:28:04 -0700 Subject: [PATCH 28/32] Update tempfile fully to 3.8.0 --- Cargo.lock | 116 +++++++++++----------------------------- plrust-tests/Cargo.toml | 4 +- 2 files changed, 32 insertions(+), 88 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3e6b0361..5b9aa62a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -479,12 +479,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "1.9.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" [[package]] name = "fixedbitset" @@ -511,12 +508,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" - [[package]] name = "funty" version = "2.0.0" @@ -716,15 +707,6 @@ dependencies = [ "hashbrown 0.14.0", ] -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - [[package]] name = "io-lifetimes" version = "1.0.11" @@ -786,6 +768,12 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +[[package]] +name = "linux-raw-sys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" + [[package]] name = "lock_api" version = "0.4.10" @@ -832,7 +820,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffc89ccdc6e10d6907450f753537ebc5c5d3460d2e4e62ea74bd571db62c0f9e" dependencies = [ - "rustix", + "rustix 0.37.23", ] [[package]] @@ -1195,7 +1183,7 @@ dependencies = [ "once_cell", "pgrx", "pgrx-tests", - "tempdir", + "tempfile", ] [[package]] @@ -1232,7 +1220,7 @@ dependencies = [ "hmac", "md-5", "memchr", - "rand 0.8.5", + "rand", "sha2", "stringprep", ] @@ -1288,19 +1276,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" -[[package]] -name = "rand" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -dependencies = [ - "fuchsia-cprng", - "libc", - "rand_core 0.3.1", - "rdrand", - "winapi", -] - [[package]] name = "rand" version = "0.8.5" @@ -1309,7 +1284,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -1319,24 +1294,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", + "rand_core", ] -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - [[package]] name = "rand_core" version = "0.6.4" @@ -1368,15 +1328,6 @@ dependencies = [ "num_cpus", ] -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", -] - [[package]] name = "redox_syscall" version = "0.2.16" @@ -1450,15 +1401,6 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846" -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] - [[package]] name = "rustc-demangle" version = "0.1.23" @@ -1499,7 +1441,20 @@ dependencies = [ "errno", "io-lifetimes", "libc", - "linux-raw-sys", + "linux-raw-sys 0.3.8", + "windows-sys", +] + +[[package]] +name = "rustix" +version = "0.38.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" +dependencies = [ + "bitflags 2.3.3", + "errno", + "libc", + "linux-raw-sys 0.4.5", "windows-sys", ] @@ -1748,27 +1703,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" -[[package]] -name = "tempdir" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" -dependencies = [ - "rand 0.4.6", - "remove_dir_all", -] - [[package]] name = "tempfile" -version = "3.6.0" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ - "autocfg", "cfg-if", "fastrand", "redox_syscall 0.3.5", - "rustix", + "rustix 0.38.8", "windows-sys", ] diff --git a/plrust-tests/Cargo.toml b/plrust-tests/Cargo.toml index 7c63ecf7..b32a49f1 100644 --- a/plrust-tests/Cargo.toml +++ b/plrust-tests/Cargo.toml @@ -16,10 +16,10 @@ trusted = [] [dependencies] pgrx = "=0.9.7" -tempdir = "0.3.8" +tempfile = "3.8.0" once_cell = "1.18.0" [dev-dependencies] pgrx-tests = "=0.9.7" -tempdir = "0.3.8" +tempfile = "3.8.0" once_cell = "1.18.0" From 5762750b1bbd990153d40e1b862bbf12bb3e7efc Mon Sep 17 00:00:00 2001 From: Jubilee Young Date: Tue, 22 Aug 2023 13:50:33 -0700 Subject: [PATCH 29/32] Run cargo fmt --- plrust-tests/src/alter.rs | 7 +- plrust-tests/src/argument.rs | 7 +- plrust-tests/src/basic.rs | 3 - plrust-tests/src/blocked_code.rs | 5 +- plrust-tests/src/borrow_mut_error.rs | 5 +- plrust-tests/src/ddl.rs | 5 +- plrust-tests/src/lib.rs | 28 +++---- plrust-tests/src/matches.rs | 105 ++++++++++++------------- plrust-tests/src/panics.rs | 35 ++++----- plrust-tests/src/range.rs | 5 +- plrust-tests/src/recursion.rs | 5 +- plrust-tests/src/return_values.rs | 6 +- plrust-tests/src/round_trip.rs | 8 +- plrust-tests/src/time_and_dates.rs | 12 ++- plrust-tests/src/user_defined_types.rs | 8 +- plrust-tests/src/versioning.rs | 5 +- 16 files changed, 111 insertions(+), 138 deletions(-) diff --git a/plrust-tests/src/alter.rs b/plrust-tests/src/alter.rs index 7e37fcba..78216270 100644 --- a/plrust-tests/src/alter.rs +++ b/plrust-tests/src/alter.rs @@ -1,4 +1,3 @@ - /* Portions Copyright 2020-2021 ZomboDB, LLC. Portions Copyright 2021-2023 Technology Concepts & Design, Inc. @@ -11,8 +10,7 @@ Use of this source code is governed by the PostgreSQL license that can be found #[cfg(any(test, feature = "pg_test"))] #[pgrx::pg_schema] mod tests { - use pgrx:: prelude::*; - + use pgrx::prelude::*; #[pg_test] #[search_path(@extschema@)] @@ -41,5 +39,4 @@ mod tests { Spi::run(definition)?; Spi::run("ALTER FUNCTION cant_change_strict_on() RETURNS NULL ON NULL INPUT") } - -} \ No newline at end of file +} diff --git a/plrust-tests/src/argument.rs b/plrust-tests/src/argument.rs index 9a06b065..1bc2a270 100644 --- a/plrust-tests/src/argument.rs +++ b/plrust-tests/src/argument.rs @@ -1,4 +1,3 @@ - /* Portions Copyright 2020-2021 ZomboDB, LLC. Portions Copyright 2021-2023 Technology Concepts & Design, Inc. @@ -11,7 +10,7 @@ Use of this source code is governed by the PostgreSQL license that can be found #[cfg(any(test, feature = "pg_test"))] #[pgrx::pg_schema] mod tests { - use pgrx:: prelude::*; + use pgrx::prelude::*; #[pg_test] #[search_path(@extschema@)] @@ -64,9 +63,9 @@ mod tests { #[pg_test] #[search_path(@extschema@)] #[should_panic( - expected = "is an invalid Rust identifier and cannot be used as an argument name" + expected = "is an invalid Rust identifier and cannot be used as an argument name" )] fn invalid_arg_identifier() -> spi::Result<()> { Spi::run("CREATE FUNCTION invalid_arg_identifier(\"this isn't a valid rust identifier\" int) RETURNS int LANGUAGE plrust as $$ Ok(None) $$;") } -} \ No newline at end of file +} diff --git a/plrust-tests/src/basic.rs b/plrust-tests/src/basic.rs index 628ddd56..10e882d7 100644 --- a/plrust-tests/src/basic.rs +++ b/plrust-tests/src/basic.rs @@ -1,4 +1,3 @@ - /* Portions Copyright 2020-2021 ZomboDB, LLC. Portions Copyright 2021-2023 Technology Concepts & Design, Inc. @@ -13,7 +12,6 @@ Use of this source code is governed by the PostgreSQL license that can be found mod tests { use pgrx::{datum::IntoDatum, prelude::*}; - #[pg_test] #[search_path(@extschema@)] fn plrust_basic() -> spi::Result<()> { @@ -40,7 +38,6 @@ mod tests { Ok(()) } - #[pg_test] #[search_path(@extschema@)] fn plrust_update() -> spi::Result<()> { diff --git a/plrust-tests/src/blocked_code.rs b/plrust-tests/src/blocked_code.rs index f754fa05..3376dfe6 100644 --- a/plrust-tests/src/blocked_code.rs +++ b/plrust-tests/src/blocked_code.rs @@ -1,4 +1,3 @@ - /* Portions Copyright 2020-2021 ZomboDB, LLC. Portions Copyright 2021-2023 Technology Concepts & Design, Inc. @@ -11,7 +10,7 @@ Use of this source code is governed by the PostgreSQL license that can be found #[cfg(any(test, feature = "pg_test"))] #[pgrx::pg_schema] mod tests { - use pgrx:: prelude::*; + use pgrx::prelude::*; #[pg_test] #[search_path(@ extschema @)] @@ -180,4 +179,4 @@ mod tests { assert_eq!(Ok(Some(1)), result); Ok(()) } -} \ No newline at end of file +} diff --git a/plrust-tests/src/borrow_mut_error.rs b/plrust-tests/src/borrow_mut_error.rs index 0d7c4a94..d78162f9 100644 --- a/plrust-tests/src/borrow_mut_error.rs +++ b/plrust-tests/src/borrow_mut_error.rs @@ -10,7 +10,7 @@ Use of this source code is governed by the PostgreSQL license that can be found #[cfg(any(test, feature = "pg_test"))] #[pgrx::pg_schema] mod tests { - use pgrx:: prelude::*; + use pgrx::prelude::*; #[pg_test] #[should_panic(expected = "issue78 works")] @@ -53,5 +53,4 @@ mod tests { assert_eq!(Ok(Some(1)), Spi::get_one::("SELECT fn1(1)")); Ok(()) } - -} \ No newline at end of file +} diff --git a/plrust-tests/src/ddl.rs b/plrust-tests/src/ddl.rs index 0f649136..44fb1383 100644 --- a/plrust-tests/src/ddl.rs +++ b/plrust-tests/src/ddl.rs @@ -1,4 +1,3 @@ - /* Portions Copyright 2020-2021 ZomboDB, LLC. Portions Copyright 2021-2023 Technology Concepts & Design, Inc. @@ -11,7 +10,7 @@ Use of this source code is governed by the PostgreSQL license that can be found #[cfg(any(test, feature = "pg_test"))] #[pgrx::pg_schema] mod tests { - use pgrx:: prelude::*; + use pgrx::prelude::*; #[pg_test] #[search_path(@extschema@)] @@ -90,4 +89,4 @@ mod tests { assert_eq!(Ok(Some(2)), Spi::get_one("SELECT replace_me()")); Ok(()) } -} \ No newline at end of file +} diff --git a/plrust-tests/src/lib.rs b/plrust-tests/src/lib.rs index 0bb23bdc..ca066415 100644 --- a/plrust-tests/src/lib.rs +++ b/plrust-tests/src/lib.rs @@ -1,20 +1,20 @@ +mod alter; +mod argument; mod basic; -mod versioning; -mod dependencies; -mod return_values; -mod ddl; mod blocked_code; -mod recursion; -mod matches; -mod argument; -mod range; -mod user_defined_types; -mod time_and_dates; mod borrow_mut_error; +mod ddl; +mod dependencies; +mod matches; mod panics; -mod alter; +mod range; +mod recursion; +mod return_values; mod round_trip; +mod time_and_dates; mod trusted; +mod user_defined_types; +mod versioning; use pgrx::prelude::*; @@ -32,7 +32,7 @@ Use of this source code is governed by the PostgreSQL license that can be found #[cfg(any(test, feature = "pg_test"))] #[pgrx::pg_schema] mod tests { - use pgrx::{prelude::*}; + use pgrx::prelude::*; // Bootstrap a testing table for non-immutable functions extension_sql!( @@ -62,8 +62,7 @@ pub mod pg_test { static PLRUST_ALLOWED_DEPENDENCIES_FILE_NAME: &str = "allowed_deps.toml"; static PLRUST_ALLOWED_DEPENDENCIES_FILE_DIRECTORY: Lazy = Lazy::new(|| { use std::io::Write; - let temp_allowed_deps_dir = - tempdir().expect("Couldnt create tempdir"); + let temp_allowed_deps_dir = tempdir().expect("Couldnt create tempdir"); let file_path = temp_allowed_deps_dir .path() @@ -107,4 +106,3 @@ plutonium = "*" ] } } - diff --git a/plrust-tests/src/matches.rs b/plrust-tests/src/matches.rs index 84f272dc..13c7b56a 100644 --- a/plrust-tests/src/matches.rs +++ b/plrust-tests/src/matches.rs @@ -1,4 +1,3 @@ - /* Portions Copyright 2020-2021 ZomboDB, LLC. Portions Copyright 2021-2023 Technology Concepts & Design, Inc. @@ -11,44 +10,44 @@ Use of this source code is governed by the PostgreSQL license that can be found #[cfg(any(test, feature = "pg_test"))] #[pgrx::pg_schema] mod tests { - use pgrx:: prelude::*; + use pgrx::prelude::*; #[pg_test] -#[search_path(@extschema@)] -fn plrust_call_1st() -> spi::Result<()> { - let definition = r#" + #[search_path(@extschema@)] + fn plrust_call_1st() -> spi::Result<()> { + let definition = r#" CREATE FUNCTION ret_1st(a int, b int) RETURNS int AS $$ Ok(a) $$ LANGUAGE plrust; "#; - Spi::run(definition)?; - let result_1 = Spi::get_one::("SELECT ret_1st(1, 2);\n"); - assert_eq!(Ok(Some(1)), result_1); // may get: Some(1) - Ok(()) -} + Spi::run(definition)?; + let result_1 = Spi::get_one::("SELECT ret_1st(1, 2);\n"); + assert_eq!(Ok(Some(1)), result_1); // may get: Some(1) + Ok(()) + } -#[pg_test] -#[search_path(@extschema@)] -fn plrust_call_2nd() -> spi::Result<()> { - let definition = r#" + #[pg_test] + #[search_path(@extschema@)] + fn plrust_call_2nd() -> spi::Result<()> { + let definition = r#" CREATE FUNCTION ret_2nd(a int, b int) RETURNS int AS $$ Ok(b) $$ LANGUAGE plrust; "#; - Spi::run(definition)?; - let result_2 = Spi::get_one::("SELECT ret_2nd(1, 2);\n"); - assert_eq!(Ok(Some(2)), result_2); // may get: Some(2) - Ok(()) -} + Spi::run(definition)?; + let result_2 = Spi::get_one::("SELECT ret_2nd(1, 2);\n"); + assert_eq!(Ok(Some(2)), result_2); // may get: Some(2) + Ok(()) + } -#[pg_test] -#[search_path(@extschema@)] -fn plrust_call_me() -> spi::Result<()> { - let definition = r#" + #[pg_test] + #[search_path(@extschema@)] + fn plrust_call_me() -> spi::Result<()> { + let definition = r#" CREATE FUNCTION pick_ret(a int, b int, pick int) RETURNS int AS $$ @@ -59,22 +58,22 @@ fn plrust_call_me() -> spi::Result<()> { }) $$ LANGUAGE plrust; "#; - Spi::run(definition)?; - let result_a = Spi::get_one::("SELECT pick_ret(3, 4, 0);"); - let result_b = Spi::get_one::("SELECT pick_ret(5, 6, 1);"); - let result_c = Spi::get_one::("SELECT pick_ret(7, 8, 2);"); - let result_z = Spi::get_one::("SELECT pick_ret(9, 99, -1);"); - assert_eq!(Ok(Some(3)), result_a); // may get: Some(4) or None - assert_eq!(Ok(Some(6)), result_b); // may get: None - assert_eq!(Ok(None), result_c); - assert_eq!(Ok(None), result_z); - Ok(()) -} + Spi::run(definition)?; + let result_a = Spi::get_one::("SELECT pick_ret(3, 4, 0);"); + let result_b = Spi::get_one::("SELECT pick_ret(5, 6, 1);"); + let result_c = Spi::get_one::("SELECT pick_ret(7, 8, 2);"); + let result_z = Spi::get_one::("SELECT pick_ret(9, 99, -1);"); + assert_eq!(Ok(Some(3)), result_a); // may get: Some(4) or None + assert_eq!(Ok(Some(6)), result_b); // may get: None + assert_eq!(Ok(None), result_c); + assert_eq!(Ok(None), result_z); + Ok(()) + } -#[pg_test] -#[search_path(@extschema@)] -fn plrust_call_me_call_me() -> spi::Result<()> { - let definition = r#" + #[pg_test] + #[search_path(@extschema@)] + fn plrust_call_me_call_me() -> spi::Result<()> { + let definition = r#" CREATE FUNCTION ret_1st(a int, b int) RETURNS int AS $$ @@ -97,19 +96,19 @@ fn plrust_call_me_call_me() -> spi::Result<()> { }) $$ LANGUAGE plrust; "#; - Spi::run(definition)?; - let result_1 = Spi::get_one::("SELECT ret_1st(1, 2);\n"); - let result_2 = Spi::get_one::("SELECT ret_2nd(1, 2);\n"); - let result_a = Spi::get_one::("SELECT pick_ret(3, 4, 0);"); - let result_b = Spi::get_one::("SELECT pick_ret(5, 6, 1);"); - let result_c = Spi::get_one::("SELECT pick_ret(7, 8, 2);"); - let result_z = Spi::get_one::("SELECT pick_ret(9, 99, -1);"); - assert_eq!(Ok(None), result_z); - assert_eq!(Ok(None), result_c); - assert_eq!(Ok(Some(6)), result_b); // may get: None - assert_eq!(Ok(Some(3)), result_a); // may get: Some(4) or None - assert_eq!(Ok(Some(2)), result_2); // may get: Some(1) - assert_eq!(Ok(Some(1)), result_1); // may get: Some(2) - Ok(()) + Spi::run(definition)?; + let result_1 = Spi::get_one::("SELECT ret_1st(1, 2);\n"); + let result_2 = Spi::get_one::("SELECT ret_2nd(1, 2);\n"); + let result_a = Spi::get_one::("SELECT pick_ret(3, 4, 0);"); + let result_b = Spi::get_one::("SELECT pick_ret(5, 6, 1);"); + let result_c = Spi::get_one::("SELECT pick_ret(7, 8, 2);"); + let result_z = Spi::get_one::("SELECT pick_ret(9, 99, -1);"); + assert_eq!(Ok(None), result_z); + assert_eq!(Ok(None), result_c); + assert_eq!(Ok(Some(6)), result_b); // may get: None + assert_eq!(Ok(Some(3)), result_a); // may get: Some(4) or None + assert_eq!(Ok(Some(2)), result_2); // may get: Some(1) + assert_eq!(Ok(Some(1)), result_1); // may get: Some(2) + Ok(()) + } } -} \ No newline at end of file diff --git a/plrust-tests/src/panics.rs b/plrust-tests/src/panics.rs index faa65cf6..9db4b48d 100644 --- a/plrust-tests/src/panics.rs +++ b/plrust-tests/src/panics.rs @@ -1,4 +1,3 @@ - /* Portions Copyright 2020-2021 ZomboDB, LLC. Portions Copyright 2021-2023 Technology Concepts & Design, Inc. @@ -11,20 +10,20 @@ Use of this source code is governed by the PostgreSQL license that can be found #[cfg(any(test, feature = "pg_test"))] #[pgrx::pg_schema] mod tests { - use pgrx:: prelude::*; + use pgrx::prelude::*; #[pg_test] -#[search_path(@extschema@)] -#[should_panic = "yup"] -fn pgrx_can_panic() { - panic!("yup") -} + #[search_path(@extschema@)] + #[should_panic = "yup"] + fn pgrx_can_panic() { + panic!("yup") + } -#[pg_test] -#[search_path(@extschema@)] -#[should_panic = "yup"] -fn plrust_can_panic() -> spi::Result<()> { - let definition = r#" + #[pg_test] + #[search_path(@extschema@)] + #[should_panic = "yup"] + fn plrust_can_panic() -> spi::Result<()> { + let definition = r#" CREATE FUNCTION shut_up_and_explode() RETURNS text AS $$ @@ -33,11 +32,11 @@ fn plrust_can_panic() -> spi::Result<()> { $$ LANGUAGE plrust; "#; - Spi::run(definition)?; - let retval = Spi::get_one::("SELECT shut_up_and_explode();\n"); - assert_eq!(retval, Ok(None)); - Ok(()) -} + Spi::run(definition)?; + let retval = Spi::get_one::("SELECT shut_up_and_explode();\n"); + assert_eq!(retval, Ok(None)); + Ok(()) + } #[pg_test] #[search_path(@extschema@)] #[should_panic = "xxx"] @@ -60,4 +59,4 @@ fn plrust_can_panic() -> spi::Result<()> { assert_eq!(retval, Ok(Some("lol".into()))); Ok(()) } -} \ No newline at end of file +} diff --git a/plrust-tests/src/range.rs b/plrust-tests/src/range.rs index bc903c4c..eacbbb1f 100644 --- a/plrust-tests/src/range.rs +++ b/plrust-tests/src/range.rs @@ -1,4 +1,3 @@ - /* Portions Copyright 2020-2021 ZomboDB, LLC. Portions Copyright 2021-2023 Technology Concepts & Design, Inc. @@ -11,7 +10,7 @@ Use of this source code is governed by the PostgreSQL license that can be found #[cfg(any(test, feature = "pg_test"))] #[pgrx::pg_schema] mod tests { - use pgrx:: prelude::*; + use pgrx::prelude::*; #[pg_test] fn test_int4range() -> spi::Result<()> { @@ -51,4 +50,4 @@ mod tests { ); Ok(()) } -} \ No newline at end of file +} diff --git a/plrust-tests/src/recursion.rs b/plrust-tests/src/recursion.rs index d19fdd70..12817423 100644 --- a/plrust-tests/src/recursion.rs +++ b/plrust-tests/src/recursion.rs @@ -1,4 +1,3 @@ - /* Portions Copyright 2020-2021 ZomboDB, LLC. Portions Copyright 2021-2023 Technology Concepts & Design, Inc. @@ -10,6 +9,4 @@ Use of this source code is governed by the PostgreSQL license that can be found #[cfg(any(test, feature = "pg_test"))] #[pgrx::pg_schema] -mod tests { - -} \ No newline at end of file +mod tests {} diff --git a/plrust-tests/src/return_values.rs b/plrust-tests/src/return_values.rs index 0fb95fc8..0e4b79a8 100644 --- a/plrust-tests/src/return_values.rs +++ b/plrust-tests/src/return_values.rs @@ -1,5 +1,3 @@ - - /* Portions Copyright 2020-2021 ZomboDB, LLC. Portions Copyright 2021-2023 Technology Concepts & Design, Inc. @@ -12,7 +10,7 @@ Use of this source code is governed by the PostgreSQL license that can be found #[cfg(any(test, feature = "pg_test"))] #[pgrx::pg_schema] mod tests { - use pgrx:: prelude::*; + use pgrx::prelude::*; #[pg_test] #[search_path(@ extschema @)] @@ -81,4 +79,4 @@ mod tests { Ok(()) } -} \ No newline at end of file +} diff --git a/plrust-tests/src/round_trip.rs b/plrust-tests/src/round_trip.rs index 606d7a70..dbb2eab4 100644 --- a/plrust-tests/src/round_trip.rs +++ b/plrust-tests/src/round_trip.rs @@ -1,4 +1,3 @@ - /* Portions Copyright 2020-2021 ZomboDB, LLC. Portions Copyright 2021-2023 Technology Concepts & Design, Inc. @@ -11,7 +10,7 @@ Use of this source code is governed by the PostgreSQL license that can be found #[cfg(any(test, feature = "pg_test"))] #[pgrx::pg_schema] mod tests { - use pgrx:: prelude::*; + use pgrx::prelude::*; use std::error::Error; #[pg_test] @@ -82,7 +81,7 @@ mod tests { let u = Spi::get_one::( "SELECT test_uuid('e4176a4d-790c-4750-85b7-665d72471173'::uuid);", )? - .expect("SPI result was null"); + .expect("SPI result was null"); assert_eq!( u, pgrx::Uuid::from_bytes([ @@ -93,5 +92,4 @@ mod tests { Ok(()) } - -} \ No newline at end of file +} diff --git a/plrust-tests/src/time_and_dates.rs b/plrust-tests/src/time_and_dates.rs index 290323e8..472ae381 100644 --- a/plrust-tests/src/time_and_dates.rs +++ b/plrust-tests/src/time_and_dates.rs @@ -1,4 +1,3 @@ - /* Portions Copyright 2020-2021 ZomboDB, LLC. Portions Copyright 2021-2023 Technology Concepts & Design, Inc. @@ -11,7 +10,7 @@ Use of this source code is governed by the PostgreSQL license that can be found #[cfg(any(test, feature = "pg_test"))] #[pgrx::pg_schema] mod tests { - use pgrx:: prelude::*; + use pgrx::prelude::*; use std::error::Error; #[pg_test] @@ -22,7 +21,7 @@ mod tests { let r = Spi::get_one::>( "SELECT test_daterange('[1977-03-20, 1980-01-01)'::daterange);", )? - .expect("SPI result was null"); + .expect("SPI result was null"); assert_eq!( r, Range::new( @@ -41,7 +40,7 @@ mod tests { let r = Spi::get_one::>( "SELECT test_tsrange('[1977-03-20, 1980-01-01)'::tsrange);", )? - .expect("SPI result was null"); + .expect("SPI result was null"); assert_eq!( r, Range::new( @@ -60,7 +59,7 @@ mod tests { let r = Spi::get_one::>( "SELECT test_tstzrange('[1977-03-20, 1980-01-01)'::tstzrange);", )? - .expect("SPI result was null"); + .expect("SPI result was null"); assert_eq!( r, Range::new( @@ -82,5 +81,4 @@ mod tests { assert_eq!(hours, AnyNumeric::from(9)); Ok(()) } - -} \ No newline at end of file +} diff --git a/plrust-tests/src/user_defined_types.rs b/plrust-tests/src/user_defined_types.rs index cfe129af..18ae7d2f 100644 --- a/plrust-tests/src/user_defined_types.rs +++ b/plrust-tests/src/user_defined_types.rs @@ -1,4 +1,3 @@ - /* Portions Copyright 2020-2021 ZomboDB, LLC. Portions Copyright 2021-2023 Technology Concepts & Design, Inc. @@ -11,7 +10,7 @@ Use of this source code is governed by the PostgreSQL license that can be found #[cfg(any(test, feature = "pg_test"))] #[pgrx::pg_schema] mod tests { - use pgrx:: prelude::*; + use pgrx::prelude::*; #[pg_test] fn test_udt() -> spi::Result<()> { @@ -89,12 +88,11 @@ insert into people (p) values (make_person('Dr. Beverly Crusher of the Starship let johnny = Spi::get_one::>( "SELECT p FROM people WHERE p->>'name' = 'Johnny';", )? - .expect("SPI result was null"); + .expect("SPI result was null"); let age = johnny.get_by_name::("age")?.expect("age was null"); assert_eq!(age, 46.24); Ok(()) } - -} \ No newline at end of file +} diff --git a/plrust-tests/src/versioning.rs b/plrust-tests/src/versioning.rs index 00bc432d..69d4147d 100644 --- a/plrust-tests/src/versioning.rs +++ b/plrust-tests/src/versioning.rs @@ -1,4 +1,3 @@ - /* Portions Copyright 2020-2021 ZomboDB, LLC. Portions Copyright 2021-2023 Technology Concepts & Design, Inc. @@ -11,7 +10,7 @@ Use of this source code is governed by the PostgreSQL license that can be found #[cfg(any(test, feature = "pg_test"))] #[pgrx::pg_schema] mod tests { - use pgrx:: prelude::*; + use pgrx::prelude::*; #[pg_test] #[cfg(not(feature = "sandboxed"))] @@ -51,4 +50,4 @@ mod tests { assert!(retval.unwrap().is_some()); Ok(()) } -} \ No newline at end of file +} From b7b3328c0fb630057260574226101a7f9180f484 Mon Sep 17 00:00:00 2001 From: Jubilee Young Date: Tue, 22 Aug 2023 14:43:14 -0700 Subject: [PATCH 30/32] Move back allowed_dependencies test for now --- plrust-tests/src/dependencies.rs | 68 ------------------------------- plrust/src/tests.rs | 69 ++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 68 deletions(-) diff --git a/plrust-tests/src/dependencies.rs b/plrust-tests/src/dependencies.rs index b136a2ad..08a20180 100644 --- a/plrust-tests/src/dependencies.rs +++ b/plrust-tests/src/dependencies.rs @@ -99,72 +99,4 @@ mod tests { }); assert!(res.is_err()); } - - #[pg_test] - fn test_allowed_dependencies() -> spi::Result<()> { - // Given the allowed list looks like this: - // owo-colors = "=3.5.0" - // tokio = { version = "=1.19.2", features = ["rt", "net"] } - // plutonium = "*" - // syn = { version = "=2.0.28", default-features = false } - // rand = ["=0.8.3", { version = ">0.8.4, <0.8.6", features = ["getrandom"] }] - let query = "SELECT * FROM plrust.allowed_dependencies();"; - - // The result will look like this: - // name | version | features | default_features - // ------------+----------------+-------------+------------------ - // owo-colors | =3.5.0 | {} | t - // plutonium | * | {} | t - // rand | =0.8.3 | {} | t - // rand | >0.8.4, <0.8.6 | {getrandom} | t - // syn | =2.0.28 | {} | f - // tokio | =1.19.2 | {rt,net} | t - - Spi::connect(|client| { - let expected_names = vec!["owo-colors", "plutonium", "rand", "rand", "syn", "tokio"]; - let expected_versions = vec![ - "=3.5.0", - "*", - "=0.8.3", - ">0.8.4, <0.8.6", - "=2.0.28", - "=1.19.2", - ]; - let expected_features = vec![ - vec![], - vec![], - vec![], - vec![String::from("getrandom")], - vec![], - vec![String::from("rt"), String::from("net")], - ]; - let expected_default_features = vec![true, true, true, true, false, true]; - let expected_len = expected_names.len(); - - let tup_table = client.select(query, None, None)?; - - assert_eq!(tup_table.len(), expected_len); - - for (i, row) in tup_table.into_iter().enumerate() { - assert_eq!( - row["name"].value::().unwrap(), - Some(expected_names[i].to_owned()) - ); - assert_eq!( - row["version"].value::().unwrap(), - Some(expected_versions[i].to_owned()) - ); - assert_eq!( - row["features"].value::>().unwrap(), - Some(expected_features[i].to_owned()) - ); - assert_eq!( - row["default_features"].value::().unwrap(), - Some(expected_default_features[i]) - ); - } - - Ok(()) - }) - } } diff --git a/plrust/src/tests.rs b/plrust/src/tests.rs index 9423b112..0bbb1764 100644 --- a/plrust/src/tests.rs +++ b/plrust/src/tests.rs @@ -6,6 +6,75 @@ All rights reserved. Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. */ + +#[pg_test] +fn test_allowed_dependencies() -> spi::Result<()> { + // Given the allowed list looks like this: + // owo-colors = "=3.5.0" + // tokio = { version = "=1.19.2", features = ["rt", "net"] } + // plutonium = "*" + // syn = { version = "=2.0.28", default-features = false } + // rand = ["=0.8.3", { version = ">0.8.4, <0.8.6", features = ["getrandom"] }] + let query = "SELECT * FROM plrust.allowed_dependencies();"; + + // The result will look like this: + // name | version | features | default_features + // ------------+----------------+-------------+------------------ + // owo-colors | =3.5.0 | {} | t + // plutonium | * | {} | t + // rand | =0.8.3 | {} | t + // rand | >0.8.4, <0.8.6 | {getrandom} | t + // syn | =2.0.28 | {} | f + // tokio | =1.19.2 | {rt,net} | t + + Spi::connect(|client| { + let expected_names = vec!["owo-colors", "plutonium", "rand", "rand", "syn", "tokio"]; + let expected_versions = vec![ + "=3.5.0", + "*", + "=0.8.3", + ">0.8.4, <0.8.6", + "=2.0.28", + "=1.19.2", + ]; + let expected_features = vec![ + vec![], + vec![], + vec![], + vec![String::from("getrandom")], + vec![], + vec![String::from("rt"), String::from("net")], + ]; + let expected_default_features = vec![true, true, true, true, false, true]; + let expected_len = expected_names.len(); + + let tup_table = client.select(query, None, None)?; + + assert_eq!(tup_table.len(), expected_len); + + for (i, row) in tup_table.into_iter().enumerate() { + assert_eq!( + row["name"].value::().unwrap(), + Some(expected_names[i].to_owned()) + ); + assert_eq!( + row["version"].value::().unwrap(), + Some(expected_versions[i].to_owned()) + ); + assert_eq!( + row["features"].value::>().unwrap(), + Some(expected_features[i].to_owned()) + ); + assert_eq!( + row["default_features"].value::().unwrap(), + Some(expected_default_features[i]) + ); + } + + Ok(()) + }) +} + #[cfg(any(test, feature = "pg_test"))] pub mod pg_test { use once_cell::sync::Lazy; From 950e9313409209eb81687a61f86d08371f4f70cc Mon Sep 17 00:00:00 2001 From: Jubilee Young Date: Tue, 22 Aug 2023 15:12:32 -0700 Subject: [PATCH 31/32] Include imports --- plrust/src/tests.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plrust/src/tests.rs b/plrust/src/tests.rs index 0bbb1764..dd5af5b0 100644 --- a/plrust/src/tests.rs +++ b/plrust/src/tests.rs @@ -7,6 +7,9 @@ All rights reserved. Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. */ +use pgrx::prelude::*; +use pgrx::spi; + #[pg_test] fn test_allowed_dependencies() -> spi::Result<()> { // Given the allowed list looks like this: From bc716310fd0839642d3c3b3e660fd24555602b5f Mon Sep 17 00:00:00 2001 From: Jubilee Young Date: Tue, 22 Aug 2023 15:41:17 -0700 Subject: [PATCH 32/32] Include ludicrous indentation --- plrust/src/tests.rs | 142 +++++++++++++++++++++++--------------------- 1 file changed, 73 insertions(+), 69 deletions(-) diff --git a/plrust/src/tests.rs b/plrust/src/tests.rs index dd5af5b0..6ff17da4 100644 --- a/plrust/src/tests.rs +++ b/plrust/src/tests.rs @@ -7,75 +7,79 @@ All rights reserved. Use of this source code is governed by the PostgreSQL license that can be found in the LICENSE.md file. */ -use pgrx::prelude::*; -use pgrx::spi; - -#[pg_test] -fn test_allowed_dependencies() -> spi::Result<()> { - // Given the allowed list looks like this: - // owo-colors = "=3.5.0" - // tokio = { version = "=1.19.2", features = ["rt", "net"] } - // plutonium = "*" - // syn = { version = "=2.0.28", default-features = false } - // rand = ["=0.8.3", { version = ">0.8.4, <0.8.6", features = ["getrandom"] }] - let query = "SELECT * FROM plrust.allowed_dependencies();"; - - // The result will look like this: - // name | version | features | default_features - // ------------+----------------+-------------+------------------ - // owo-colors | =3.5.0 | {} | t - // plutonium | * | {} | t - // rand | =0.8.3 | {} | t - // rand | >0.8.4, <0.8.6 | {getrandom} | t - // syn | =2.0.28 | {} | f - // tokio | =1.19.2 | {rt,net} | t - - Spi::connect(|client| { - let expected_names = vec!["owo-colors", "plutonium", "rand", "rand", "syn", "tokio"]; - let expected_versions = vec![ - "=3.5.0", - "*", - "=0.8.3", - ">0.8.4, <0.8.6", - "=2.0.28", - "=1.19.2", - ]; - let expected_features = vec![ - vec![], - vec![], - vec![], - vec![String::from("getrandom")], - vec![], - vec![String::from("rt"), String::from("net")], - ]; - let expected_default_features = vec![true, true, true, true, false, true]; - let expected_len = expected_names.len(); - - let tup_table = client.select(query, None, None)?; - - assert_eq!(tup_table.len(), expected_len); - - for (i, row) in tup_table.into_iter().enumerate() { - assert_eq!( - row["name"].value::().unwrap(), - Some(expected_names[i].to_owned()) - ); - assert_eq!( - row["version"].value::().unwrap(), - Some(expected_versions[i].to_owned()) - ); - assert_eq!( - row["features"].value::>().unwrap(), - Some(expected_features[i].to_owned()) - ); - assert_eq!( - row["default_features"].value::().unwrap(), - Some(expected_default_features[i]) - ); - } - - Ok(()) - }) +#[cfg(any(test, feature = "pg_test"))] +#[pgrx::pg_schema] +mod tests { + use pgrx::prelude::*; + use pgrx::spi; + + #[pg_test] + fn test_allowed_dependencies() -> spi::Result<()> { + // Given the allowed list looks like this: + // owo-colors = "=3.5.0" + // tokio = { version = "=1.19.2", features = ["rt", "net"] } + // plutonium = "*" + // syn = { version = "=2.0.28", default-features = false } + // rand = ["=0.8.3", { version = ">0.8.4, <0.8.6", features = ["getrandom"] }] + let query = "SELECT * FROM plrust.allowed_dependencies();"; + + // The result will look like this: + // name | version | features | default_features + // ------------+----------------+-------------+------------------ + // owo-colors | =3.5.0 | {} | t + // plutonium | * | {} | t + // rand | =0.8.3 | {} | t + // rand | >0.8.4, <0.8.6 | {getrandom} | t + // syn | =2.0.28 | {} | f + // tokio | =1.19.2 | {rt,net} | t + + Spi::connect(|client| { + let expected_names = vec!["owo-colors", "plutonium", "rand", "rand", "syn", "tokio"]; + let expected_versions = vec![ + "=3.5.0", + "*", + "=0.8.3", + ">0.8.4, <0.8.6", + "=2.0.28", + "=1.19.2", + ]; + let expected_features = vec![ + vec![], + vec![], + vec![], + vec![String::from("getrandom")], + vec![], + vec![String::from("rt"), String::from("net")], + ]; + let expected_default_features = vec![true, true, true, true, false, true]; + let expected_len = expected_names.len(); + + let tup_table = client.select(query, None, None)?; + + assert_eq!(tup_table.len(), expected_len); + + for (i, row) in tup_table.into_iter().enumerate() { + assert_eq!( + row["name"].value::().unwrap(), + Some(expected_names[i].to_owned()) + ); + assert_eq!( + row["version"].value::().unwrap(), + Some(expected_versions[i].to_owned()) + ); + assert_eq!( + row["features"].value::>().unwrap(), + Some(expected_features[i].to_owned()) + ); + assert_eq!( + row["default_features"].value::().unwrap(), + Some(expected_default_features[i]) + ); + } + + Ok(()) + }) + } } #[cfg(any(test, feature = "pg_test"))]