From 3ae2cca99b1e582f9f5c70cd8819baedfdbc6f30 Mon Sep 17 00:00:00 2001 From: usamoi Date: Fri, 1 Nov 2024 19:07:16 +0800 Subject: [PATCH 1/8] fix cargo-pgrx and pgrx-tests on Windows --- .cargo/config.toml | 3 + Cargo.lock | 243 ++++++++- cargo-pgrx/Cargo.toml | 1 + cargo-pgrx/src/command/init.rs | 62 ++- cargo-pgrx/src/command/install.rs | 146 +++--- cargo-pgrx/src/command/run.rs | 27 +- cargo-pgrx/src/command/start.rs | 11 +- cargo-pgrx/src/command/status.rs | 3 +- cargo-pgrx/src/command/stop.rs | 10 +- cargo-pgrx/src/command/sudo_install.rs | 43 +- cargo-pgrx/src/command/test.rs | 5 + cargo-pgrx/src/command/version.rs | 14 +- cargo-pgrx/src/manifest.rs | 2 +- cargo-pgrx/src/templates/cargo_config_toml | 3 + pgrx-bindgen/Cargo.toml | 1 + pgrx-bindgen/src/build.rs | 72 ++- pgrx-pg-config/src/cargo.rs | 12 +- pgrx-pg-config/src/lib.rs | 53 +- pgrx-pg-sys/src/port.rs | 98 ++++ pgrx-tests/Cargo.toml | 16 +- pgrx-tests/src/framework.rs | 559 ++++++++++++++++----- pgrx-tests/src/framework/shutdown.rs | 12 +- pgrx-tests/src/tests/mod.rs | 1 + pgrx-tests/src/tests/result_tests.rs | 9 +- 24 files changed, 1130 insertions(+), 276 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 51de468659..5ef5ccd240 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -7,3 +7,6 @@ r = "run --features" [target.'cfg(target_os="macos")'] # Postgres symbols won't be available until runtime rustflags = ["-Clink-arg=-Wl,-undefined,dynamic_lookup"] + +[target.'cfg(target_env="msvc")'] +linker = "lld-link" diff --git a/Cargo.lock b/Cargo.lock index 220c385729..88dd544bae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,17 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -98,6 +109,15 @@ version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4" +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "async-compression" version = "0.4.18" @@ -300,6 +320,16 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + [[package]] name = "bzip2" version = "0.5.1" @@ -359,7 +389,7 @@ dependencies = [ name = "cargo-pgrx" version = "0.13.1" dependencies = [ - "bzip2", + "bzip2 0.5.1", "cargo-edit", "cargo_metadata 0.18.1", "cargo_toml", @@ -389,6 +419,7 @@ dependencies = [ "tracing-subscriber", "ureq", "url", + "zip-extract", ] [[package]] @@ -444,6 +475,8 @@ version = "1.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af" dependencies = [ + "jobserver", + "libc", "shlex", ] @@ -478,6 +511,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -601,6 +644,12 @@ version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad159cc964ac8f9d407cbc0aa44b02436c054b541f2b4b5f06972e1efdc54bc7" +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "convert_case" version = "0.7.1" @@ -645,6 +694,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.4.2" @@ -698,6 +762,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "deflate64" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" + [[package]] name = "der" version = "0.7.9" @@ -717,6 +787,17 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "derive_destructure2" version = "0.1.3" @@ -1361,6 +1442,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -1405,6 +1495,15 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + [[package]] name = "jobslot" version = "0.2.20" @@ -1490,12 +1589,28 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "lockfree-object-pool" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" + [[package]] name = "log" version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +[[package]] +name = "lzma-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" +dependencies = [ + "byteorder", + "crc", +] + [[package]] name = "matchers" version = "0.1.0" @@ -1758,6 +1873,16 @@ dependencies = [ "libc", ] +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -1816,6 +1941,7 @@ dependencies = [ "pgrx-pg-config", "proc-macro2", "quote", + "regex", "shlex", "syn", "walkdir", @@ -1897,8 +2023,10 @@ dependencies = [ "serde_json", "shlex", "sysinfo", + "tempfile", "thiserror 2.0.11", "trybuild", + "winapi", ] [[package]] @@ -2588,6 +2716,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.8" @@ -2614,6 +2753,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "siphasher" version = "1.0.1" @@ -3875,6 +4020,20 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "zerovec" @@ -3897,3 +4056,85 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zip" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae9c1ea7b3a5e1f4b922ff856a129881167511563dc219869afe3787fc0c1a45" +dependencies = [ + "aes", + "arbitrary", + "bzip2 0.4.4", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "deflate64", + "displaydoc", + "flate2", + "hmac", + "indexmap", + "lzma-rs", + "memchr", + "pbkdf2", + "rand 0.8.5", + "sha1", + "thiserror 2.0.11", + "time", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zip-extract" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a8c9e90f27d1435088a7b540b6cc8ae6ee525d992a695f16012d2f365b3d3c" +dependencies = [ + "log", + "thiserror 1.0.69", + "zip", +] + +[[package]] +name = "zopfli" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +dependencies = [ + "bumpalo", + "crc32fast", + "lockfree-object-pool", + "log", + "once_cell", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3051792fbdc2e1e143244dc28c60f73d8470e93f3f9cbd0ead44da5ed802722" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.14+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fb060d4926e4ac3a3ad15d864e99ceb5f343c6b34f5bd6d81ae6ed417311be5" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/cargo-pgrx/Cargo.toml b/cargo-pgrx/Cargo.toml index d1a935233d..b688ec8879 100644 --- a/cargo-pgrx/Cargo.toml +++ b/cargo-pgrx/Cargo.toml @@ -57,6 +57,7 @@ serde-xml-rs = "0.6.0" tar = "0.4.44" ureq = { version = "3.0.6", default-features = false, features = ["gzip"] } url.workspace = true +zip-extract = "0.2.1" # SQL schema generation proc-macro2.workspace = true diff --git a/cargo-pgrx/src/command/init.rs b/cargo-pgrx/src/command/init.rs index 00e5e0609a..244dfdb89c 100644 --- a/cargo-pgrx/src/command/init.rs +++ b/cargo-pgrx/src/command/init.rs @@ -14,7 +14,7 @@ use bzip2::bufread::BzDecoder; use eyre::{eyre, WrapErr}; use owo_colors::OwoColorize; use pgrx_pg_config::{ - get_c_locale_flags, prefix_path, ConfigToml, PgConfig, PgConfigSelector, Pgrx, PgrxHomeError, + get_c_locale_flags, ConfigToml, PgConfig, PgConfigSelector, Pgrx, PgrxHomeError, }; use tar::Archive; @@ -23,9 +23,10 @@ use std::fs::File; use std::io::{Read, Write}; use std::num::NonZeroUsize; use std::path::{Path, PathBuf}; -use std::process::{Command, Stdio}; +use std::process::Stdio; use std::sync::OnceLock; +#[cfg(not(target_os = "windows"))] static PROCESS_ENV_DENYLIST: &[&str] = &[ "DEBUG", "MAKEFLAGS", @@ -289,8 +290,18 @@ fn untar(bytes: &[u8], pgrxdir: &Path, pg_config: &PgConfig, init: &Init) -> eyr pg_config.version()?, unpackdir.display() ); - let mut tar_decoder = Archive::new(BzDecoder::new(bytes)); - tar_decoder.unpack(&unpackdir)?; + + if bytes.starts_with(b"PK\x03\x04") + || bytes.starts_with(b"PK\x05\x06") + || bytes.starts_with(b"PK\x07\x08") + { + // it's a zip download from EDB + use std::io::Cursor; + zip_extract::extract(Cursor::new(bytes), &unpackdir, false)?; + } else { + let mut tar_decoder = Archive::new(BzDecoder::new(bytes)); + tar_decoder.unpack(&unpackdir)?; + } let mut pgdir = pgrxdir.to_path_buf(); pgdir.push(&pg_config.version()?); @@ -329,7 +340,9 @@ fn untar(bytes: &[u8], pgrxdir: &Path, pg_config: &PgConfig, init: &Init) -> eyr Ok(pgdir) } -fn fixup_homebrew_for_icu(configure_cmd: &mut Command) { +#[cfg(not(target_os = "windows"))] +fn fixup_homebrew_for_icu(configure_cmd: &mut std::process::Command) { + use std::process::Command; // See if it's disabled via an argument if configure_cmd.get_args().any(|a| a == "--without-icu") { return; @@ -401,7 +414,10 @@ fn fixup_homebrew_for_icu(configure_cmd: &mut Command) { } } +#[cfg(not(target_os = "windows"))] fn configure_postgres(pg_config: &PgConfig, pgdir: &Path, init: &Init) -> eyre::Result<()> { + use pgrx_pg_config::prefix_path; + let _token = init.jobserver.get().unwrap().acquire().unwrap(); println!("{} Postgres v{}", " Configuring".bold().green(), pg_config.version()?); @@ -465,6 +481,12 @@ fn configure_postgres(pg_config: &PgConfig, pgdir: &Path, init: &Init) -> eyre:: } } +#[cfg(target_os = "windows")] +fn configure_postgres(_pg_config: &PgConfig, _pgdir: &Path, _init: &Init) -> eyre::Result<()> { + Ok(()) +} + +#[cfg(not(target_os = "windows"))] fn make_postgres(pg_config: &PgConfig, pgdir: &Path, init: &Init) -> eyre::Result<()> { println!("{} Postgres v{}", " Compiling".bold().green(), pg_config.version()?); let mut command = std::process::Command::new("make"); @@ -498,6 +520,12 @@ fn make_postgres(pg_config: &PgConfig, pgdir: &Path, init: &Init) -> eyre::Resul } } +#[cfg(target_os = "windows")] +fn make_postgres(_pg_config: &PgConfig, _pgdir: &Path, _init: &Init) -> eyre::Result<()> { + Ok(()) +} + +#[cfg(not(target_os = "windows"))] fn make_install_postgres(version: &PgConfig, pgdir: &Path, init: &Init) -> eyre::Result { println!( "{} Postgres v{} to {}", @@ -538,6 +566,18 @@ fn make_install_postgres(version: &PgConfig, pgdir: &Path, init: &Init) -> eyre: } } +#[cfg(target_os = "windows")] +fn make_install_postgres( + _version: &PgConfig, + pgdir: &Path, + _init: &Init, +) -> eyre::Result { + let mut pg_config = get_pg_installdir(pgdir); + pg_config.push("bin"); + pg_config.push("pg_config.exe"); + Ok(PgConfig::new_with_defaults(pg_config)) +} + fn validate_pg_config(pg_config: &PgConfig) -> eyre::Result<()> { println!( "{} {}", @@ -577,12 +617,18 @@ fn write_config(pg_configs: &Vec, init: &Init) -> eyre::Result<()> { Ok(()) } +#[cfg(not(target_os = "windows"))] fn get_pg_installdir(pgdir: &Path) -> PathBuf { let mut dir = pgdir.to_path_buf(); dir.push("pgrx-install"); dir } +#[cfg(target_os = "windows")] +fn get_pg_installdir(pgdir: &Path) -> PathBuf { + pgdir.to_path_buf() +} + #[cfg(unix)] fn is_root_user() -> bool { // SAFETY: No, the `nix` crate does not do anything more clever: @@ -599,7 +645,11 @@ fn is_root_user() -> bool { pub(crate) fn initdb(bindir: &Path, datadir: &Path) -> eyre::Result<()> { println!(" {} data directory at {}", "Initializing".bold().green(), datadir.display()); - let mut command = std::process::Command::new(format!("{}/initdb", bindir.display())); + #[cfg(not(target_os = "windows"))] + let initdb = bindir.join("initdb"); + #[cfg(target_os = "windows")] + let initdb = bindir.join("initdb.exe"); + let mut command = std::process::Command::new(initdb); command .stdout(Stdio::piped()) .stderr(Stdio::piped()) diff --git a/cargo-pgrx/src/command/install.rs b/cargo-pgrx/src/command/install.rs index f8077ba519..4f2ae93dc8 100644 --- a/cargo-pgrx/src/command/install.rs +++ b/cargo-pgrx/src/command/install.rs @@ -129,9 +129,6 @@ pub(crate) fn install_extension( features: &clap_cargo::Features, ) -> eyre::Result> { let mut output_tracking = Vec::new(); - let base_directory = base_directory.unwrap_or_else(|| PathBuf::from("/")); - tracing::Span::current() - .record("base_directory", tracing::field::display(&base_directory.display())); let manifest = Manifest::from_path(&package_manifest_path)?; let (control_file, extname) = find_control_file(&package_manifest_path)?; @@ -147,18 +144,25 @@ pub(crate) fn install_extension( build_command_stream.collect::, std::io::Error>>()?; println!("{} extension", " Installing".bold().green()); - let pkgdir = make_relative(pg_config.pkglibdir()?); - let extdir = make_relative(pg_config.extension_dir()?); let shlibpath = find_library_file(&manifest, &build_command_messages)?; + let extdir = if let Some(base_directory) = base_directory.as_ref() { + base_directory.join(make_relative_extdir(pg_config.extension_dir()?)) + } else { + pg_config.extension_dir()? + }; + + let pkglibdir = if let Some(base_directory) = base_directory.as_ref() { + base_directory.join(make_relative_pkglibdir(pg_config.pkglibdir()?)) + } else { + pg_config.pkglibdir()? + }; + { - let mut dest = base_directory.clone(); - dest.push(&extdir); - dest.push( - control_file - .file_name() - .ok_or_else(|| eyre!("Could not get filename for `{}`", control_file.display()))?, - ); + let filename = control_file + .file_name() + .ok_or_else(|| eyre!("Could not get filename for `{}`", control_file.display()))?; + let dest = extdir.join(filename); copy_file( &control_file, dest, @@ -171,9 +175,6 @@ pub(crate) fn install_extension( } { - let mut dest = base_directory.clone(); - dest.push(&pkgdir); - let so_name = if versioned_so { let extver = get_version(&package_manifest_path)?; // note: versioned so-name format must agree with pgrx-utils @@ -183,13 +184,18 @@ pub(crate) fn install_extension( }; // Since Postgres 16, the shared library extension on macOS is `dylib`, not `so`. // Ref https://github.com/postgres/postgres/commit/b55f62abb2c2e07dfae99e19a2b3d7ca9e58dc1a - let so_extension = if cfg!(target_os = "macos") && pg_config.major_version().unwrap() >= 16 - { - "dylib" - } else { + let so_ext = 'e: { + if cfg!(target_os = "macos") { + break 'e if pg_config.major_version().unwrap() >= 16 { "dylib" } else { "so" }; + } + if cfg!(target_os = "windows") { + break 'e "dll"; + } "so" }; - dest.push(format!("{so_name}.{so_extension}")); + let filename = format!("{so_name}.{so_ext}"); + + let dest = pkglibdir.join(filename); // Remove the existing shared libraries if present. This is a workaround for an // issue highlighted by the following apple documentation: @@ -224,7 +230,6 @@ pub(crate) fn install_extension( is_test, features, &extdir, - &base_directory, true, &mut output_tracking, )?; @@ -339,21 +344,6 @@ pub(crate) fn build_extension( } } -fn get_target_sql_file( - manifest_path: impl AsRef, - extdir: &Path, - base_directory: PathBuf, -) -> eyre::Result { - let mut dest = base_directory; - dest.push(extdir); - - let (_, extname) = find_control_file(&manifest_path)?; - let version = get_version(&manifest_path)?; - dest.push(format!("{extname}--{version}.sql")); - - Ok(dest) -} - fn copy_sql_files( user_manifest_path: Option>, user_package: Option<&String>, @@ -363,27 +353,30 @@ fn copy_sql_files( is_test: bool, features: &clap_cargo::Features, extdir: &Path, - base_directory: &Path, skip_build: bool, output_tracking: &mut Vec, ) -> eyre::Result<()> { - let dest = get_target_sql_file(&package_manifest_path, extdir, base_directory.to_path_buf())?; let (_, extname) = find_control_file(&package_manifest_path)?; + { + let version = get_version(&package_manifest_path)?; + let filename = format!("{extname}--{version}.sql"); + let dest = extdir.join(filename); - crate::command::schema::generate_schema( - pg_config, - user_manifest_path, - user_package, - &package_manifest_path, - profile, - is_test, - features, - Some(&dest), - Option::::None, - None, - skip_build, - output_tracking, - )?; + crate::command::schema::generate_schema( + pg_config, + user_manifest_path, + user_package, + &package_manifest_path, + profile, + is_test, + features, + Some(&dest), + Option::::None, + None, + skip_build, + output_tracking, + )?; + } // now copy all the version upgrade files too if let Ok(dir) = fs::read_dir(package_manifest_path.as_ref().parent().unwrap().join("sql/")) { @@ -395,13 +388,9 @@ fn copy_sql_files( regex::Regex::new(&format!(r"^{extname}--.+--.+\.sql$")).unwrap(); if re_update_script_name.is_match(filename.as_str()) { - let mut dest = base_directory.to_path_buf(); - dest.push(extdir); - dest.push(filename); - copy_file( &sql.path(), - dest, + extdir.join(filename), "extension schema upgrade file", true, &package_manifest_path, @@ -422,7 +411,15 @@ pub(crate) fn find_library_file( // cargo sometimes decides to change whether targets are kebab-case or snake_case in metadata, // so normalize away the difference let target_name = manifest.target_name()?.replace('-', "_"); - let so_ext = if cfg!(target_os = "macos") { "dylib" } else { "so" }; + let so_ext = 'so_ext: { + if cfg!(target_os = "macos") { + break 'so_ext "dylib"; + } + if cfg!(target_os = "windows") { + break 'so_ext "dll"; + } + "so" + }; // no hard and fast rule for the lib.so output filename exists, so we implement this routine // which is essentially a cope for cargo's disinterest in writing down any docs so far. @@ -511,17 +508,36 @@ fn get_git_hash(manifest_path: impl AsRef) -> eyre::Result { } } -fn make_relative(path: PathBuf) -> PathBuf { +#[cfg(not(target_os = "windows"))] +fn make_relative_pkglibdir(path: PathBuf) -> PathBuf { + use std::path::Component; if path.is_relative() { return path; } - let mut relative = PathBuf::new(); - let mut components = path.components(); - components.next(); // skip the root - for part in components { - relative.push(part) + path.components() + .skip_while(|x| matches!(x, Component::Prefix(_) | Component::RootDir)) + .collect() +} + +#[cfg(target_os = "windows")] +fn make_relative_pkglibdir(_: PathBuf) -> PathBuf { + "lib".into() +} + +#[cfg(not(target_os = "windows"))] +fn make_relative_extdir(path: PathBuf) -> PathBuf { + use std::path::Component; + if path.is_relative() { + return path; } - relative + path.components() + .skip_while(|x| matches!(x, Component::Prefix(_) | Component::RootDir)) + .collect() +} + +#[cfg(target_os = "windows")] +fn make_relative_extdir(_: PathBuf) -> PathBuf { + "share/extension".into() } pub(crate) fn format_display_path(path: impl AsRef) -> eyre::Result { diff --git a/cargo-pgrx/src/command/run.rs b/cargo-pgrx/src/command/run.rs index 36b4e23f33..a075554f8b 100644 --- a/cargo-pgrx/src/command/run.rs +++ b/cargo-pgrx/src/command/run.rs @@ -18,7 +18,6 @@ use eyre::eyre; use owo_colors::OwoColorize; use pgrx_pg_config::{createdb, PgConfig, Pgrx}; use std::path::Path; -use std::process::Command; /// Compile/install extension to a pgrx-managed Postgres instance and start psql #[derive(clap::Args, Debug)] @@ -144,6 +143,7 @@ pub(crate) fn run( #[cfg(unix)] pub(crate) fn exec_psql(pg_config: &PgConfig, dbname: &str, pgcli: bool) -> eyre::Result<()> { use std::os::unix::process::CommandExt; + use std::process::Command; let mut command = Command::new(match pgcli { false => pg_config.psql_path()?.into_os_string(), true => "pgcli".to_string().into(), @@ -165,5 +165,28 @@ pub(crate) fn exec_psql(pg_config: &PgConfig, dbname: &str, pgcli: bool) -> eyre #[cfg(not(unix))] pub(crate) fn exec_psql(pg_config: &PgConfig, dbname: &str, pgcli: bool) -> eyre::Result<()> { - panic!("Tried to exec on a platform that doesn't support exec!") + use std::process::Command; + use std::process::Stdio; + let mut command = Command::new(match pgcli { + false => pg_config.psql_path()?.into_os_string(), + true => "pgcli".to_string().into(), + }); + command + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .env_remove("PGDATABASE") + .env_remove("PGHOST") + .env_remove("PGPORT") + .env_remove("PGUSER") + .arg("-h") + .arg(pg_config.host()) + .arg("-p") + .arg(pg_config.port()?.to_string()) + .arg(dbname); + let command_str = format!("{command:?}"); + tracing::debug!(command = %command_str, "Running"); + let output = command.output()?; + tracing::trace!(status_code = %output.status, command = %command_str, "Finished"); + Ok(()) } diff --git a/cargo-pgrx/src/command/start.rs b/cargo-pgrx/src/command/start.rs index d96f0e99be..91ba59b261 100644 --- a/cargo-pgrx/src/command/start.rs +++ b/cargo-pgrx/src/command/start.rs @@ -91,7 +91,8 @@ pub(crate) fn start_postgres(pg_config: &PgConfig) -> eyre::Result<()> { pg_config.major_version()?, port.to_string().bold().cyan() ); - let mut command = std::process::Command::new(format!("{}/pg_ctl", bindir.display())); + let pg_ctl = pg_config.pg_ctl_path()?; + let mut command = std::process::Command::new(pg_ctl); command .stdout(Stdio::piped()) .stderr(Stdio::piped()) @@ -101,10 +102,16 @@ pub(crate) fn start_postgres(pg_config: &PgConfig) -> eyre::Result<()> { .arg(&datadir) .arg("-l") .arg(&logfile); + #[cfg(target_os = "windows")] + { + // on windows, created pipes are leaked, so that the command hangs + command.stdout(Stdio::inherit()).stderr(Stdio::inherit()); + } let command_str = format!("{command:?}"); + tracing::debug!(command = %command_str, "Running"); let output = command.output()?; - + tracing::trace!(status_code = %output.status, command = %command_str, "Finished"); if !output.status.success() { return Err(eyre!( "problem running pg_ctl: {}\n\n{}", diff --git a/cargo-pgrx/src/command/status.rs b/cargo-pgrx/src/command/status.rs index 63cf7a055d..87c3d1f531 100644 --- a/cargo-pgrx/src/command/status.rs +++ b/cargo-pgrx/src/command/status.rs @@ -64,8 +64,7 @@ pub(crate) fn status_postgres(pg_config: &PgConfig) -> eyre::Result { return Ok(false); } // if Err, let the filesystem and OS handle our impending failure - let mut pg_ctl = pg_config.bin_dir()?; - pg_ctl.push("pg_ctl"); + let pg_ctl = pg_config.pg_ctl_path()?; let mut command = process::Command::new(pg_ctl); command.stdout(Stdio::piped()).stderr(Stdio::piped()).arg("status").arg("-D").arg(&datadir); let command_str = format!("{command:?}"); diff --git a/cargo-pgrx/src/command/stop.rs b/cargo-pgrx/src/command/stop.rs index 24aea9e735..8370ee7efc 100644 --- a/cargo-pgrx/src/command/stop.rs +++ b/cargo-pgrx/src/command/stop.rs @@ -70,7 +70,6 @@ impl CommandExecute for Stop { #[tracing::instrument(level = "error", skip_all, fields(pg_version = %pg_config.version()?))] pub(crate) fn stop_postgres(pg_config: &PgConfig) -> eyre::Result<()> { let datadir = pg_config.data_dir()?; - let bindir = pg_config.bin_dir()?; if !(status_postgres(pg_config)?) { // it's not running, no need to stop it @@ -80,15 +79,16 @@ pub(crate) fn stop_postgres(pg_config: &PgConfig) -> eyre::Result<()> { println!("{} Postgres v{}", " Stopping".bold().green(), pg_config.major_version()?); - let mut command = std::process::Command::new(format!("{}/pg_ctl", bindir.display())); + let pg_ctl = pg_config.pg_ctl_path()?; + let mut command = std::process::Command::new(pg_ctl); command .stdout(Stdio::piped()) .stderr(Stdio::piped()) .arg("stop") - .arg("-m") - .arg("fast") .arg("-D") - .arg(&datadir); + .arg(&datadir) + .arg("-m") + .arg("fast"); let output = command.output()?; diff --git a/cargo-pgrx/src/command/sudo_install.rs b/cargo-pgrx/src/command/sudo_install.rs index 2e9302a644..dab65e71f3 100644 --- a/cargo-pgrx/src/command/sudo_install.rs +++ b/cargo-pgrx/src/command/sudo_install.rs @@ -67,28 +67,41 @@ impl CommandExecute for SudoInstall { eprintln!(); eprintln!("Using sudo to copy extension files from {}", outdir.display().cyan()); + let outdir = outdir.canonicalize()?; for src in output_files { let src = src.canonicalize()?; let dest_abs = make_absolute(src.strip_prefix(&outdir)?); let dest = dest_abs.canonicalize().unwrap_or(dest_abs); - // we're about to run `sudo` to copy some files, one at a time - let mut command = Command::new("sudo"); // NB: If we ever support Windows... - command.arg("cp").arg(&src).arg(&dest); + if !cfg!(target_os = "windows") { + println!( + "{} sudo cp {} {}", + " Running".bold().green(), + src.display(), + dest.display() + ); - println!( - "{} sudo cp {} {}", - " Running".bold().green(), - src.display(), - dest.display() - ); - let mut child = command.spawn()?; + // we're about to run `sudo` to copy some files, one at a time + let mut command = Command::new("sudo"); // NB: If we ever support Windows... + command.arg("cp").arg(&src).arg(&dest); - let status = child.wait()?; - if !status.success() { - // sudo failed. let the user know and get out now - eprintln!("sudo command failed with status `{}`", format!("{status:?}").red()); - std::process::exit(status.code().unwrap_or(1)); + let mut child = command.spawn()?; + + let status = child.wait()?; + if !status.success() { + // sudo failed. let the user know and get out now + eprintln!("sudo command failed with status `{}`", format!("{status:?}").red()); + std::process::exit(status.code().unwrap_or(1)); + } + } else { + println!( + "{} cp {} {}", + " Running".bold().green(), + src.display(), + dest.display() + ); + + std::fs::copy(src, dest)?; } } diff --git a/cargo-pgrx/src/command/test.rs b/cargo-pgrx/src/command/test.rs index 0be4f96396..fd2ff08b4c 100644 --- a/cargo-pgrx/src/command/test.rs +++ b/cargo-pgrx/src/command/test.rs @@ -126,6 +126,11 @@ pub fn test_extension( runas: Option, pgdata: Option, ) -> eyre::Result<()> { + #[cfg(target_os = "windows")] + if runas.is_some() { + eyre::bail!("`--runas` is not supported on Windows"); + } + if let Some(ref testname) = testname { tracing::Span::current().record("testname", tracing::field::display(&testname.as_ref())); } diff --git a/cargo-pgrx/src/command/version.rs b/cargo-pgrx/src/command/version.rs index 6c7364a574..e390ac5e92 100644 --- a/cargo-pgrx/src/command/version.rs +++ b/cargo-pgrx/src/command/version.rs @@ -30,6 +30,16 @@ mod rss { use crate::command::build_agent_for_url; + #[cfg(not(target_os = "windows"))] + fn download_url(major: u16, minor: u16) -> String { + format!("https://ftp.postgresql.org/pub/source/v{major}.{minor}/postgresql-{major}.{minor}.tar.bz2") + } + + #[cfg(target_os = "windows")] + fn download_url(major: u16, minor: u16) -> String { + format!("https://get.enterprisedb.com/postgresql/postgresql-{major}.{minor}-1-windows-x64-binaries.zip") + } + pub(super) struct PostgreSQLVersionRss; impl PostgreSQLVersionRss { @@ -69,9 +79,7 @@ mod rss { if matches!(known_pgver.minor, PgMinorVersion::Latest) { // fill in the latest minor version number and its url known_pgver.minor = PgMinorVersion::Release(minor); - known_pgver.url = Some(Url::parse( - &format!("https://ftp.postgresql.org/pub/source/v{major}.{minor}/postgresql-{major}.{minor}.tar.bz2") - )?); + known_pgver.url = Some(Url::parse(&download_url(major, minor))?); } } } diff --git a/cargo-pgrx/src/manifest.rs b/cargo-pgrx/src/manifest.rs index c1da00c7ae..d941e85714 100644 --- a/cargo-pgrx/src/manifest.rs +++ b/cargo-pgrx/src/manifest.rs @@ -96,7 +96,7 @@ pub(crate) fn modify_features_for_version( // that aren't valid for the manifest if test { features.features.retain(|flag| { - if manifest.features.contains_key(flag) { + if manifest.features.contains_key(flag) || flag == "pgrx/cshim" { true } else { use owo_colors::OwoColorize; diff --git a/cargo-pgrx/src/templates/cargo_config_toml b/cargo-pgrx/src/templates/cargo_config_toml index 13c456b5dd..1dd47369a8 100644 --- a/cargo-pgrx/src/templates/cargo_config_toml +++ b/cargo-pgrx/src/templates/cargo_config_toml @@ -1,3 +1,6 @@ [target.'cfg(target_os="macos")'] # Postgres symbols won't be available until runtime rustflags = ["-Clink-arg=-Wl,-undefined,dynamic_lookup"] + +[target.'cfg(target_env="msvc")'] +linker = "lld-link" diff --git a/pgrx-bindgen/Cargo.toml b/pgrx-bindgen/Cargo.toml index 405a19801f..0d984aa5e2 100644 --- a/pgrx-bindgen/Cargo.toml +++ b/pgrx-bindgen/Cargo.toml @@ -13,6 +13,7 @@ pgrx-pg-config.workspace = true eyre.workspace = true proc-macro2.workspace = true +regex.workspace = true syn.workspace = true walkdir.workspace = true diff --git a/pgrx-bindgen/src/build.rs b/pgrx-bindgen/src/build.rs index 640bcf6523..233266aa02 100644 --- a/pgrx-bindgen/src/build.rs +++ b/pgrx-bindgen/src/build.rs @@ -794,13 +794,12 @@ fn run_bindgen( let configure = pg_config.configure()?; let preferred_clang: Option<&std::path::Path> = configure.get("CLANG").map(|s| s.as_ref()); eprintln!("pg_config --configure CLANG = {preferred_clang:?}"); + let pg_target_includes = pg_target_includes(major_version, pg_config)?; + eprintln!("pg_target_includes = {pg_target_includes:?}"); let (autodetect, includes) = clang::detect_include_paths_for(preferred_clang); let mut binder = bindgen::Builder::default(); binder = add_blocklists(binder); - binder = binder - .allowlist_file(format!("{}.*", pg_target_include(major_version, pg_config)?)) - .allowlist_item("PGERROR") - .allowlist_item("SIG.*"); + binder = add_allowlists(binder, pg_target_includes.iter().map(|x| x.as_str())); binder = add_derives(binder); if !autodetect { let builtin_includes = includes.iter().filter_map(|p| Some(format!("-I{}", p.to_str()?))); @@ -812,7 +811,7 @@ fn run_bindgen( let bindings = binder .header(include_h.display().to_string()) .clang_args(extra_bindgen_clang_args(pg_config)?) - .clang_arg(format!("-I{}", pg_target_include(major_version, pg_config)?)) + .clang_args(pg_target_includes.iter().map(|x| format!("-I{x}"))) .detect_include_paths(autodetect) .parse_callbacks(Box::new(overrides)) .default_enum_style(bindgen::EnumVariation::ModuleConsts) @@ -900,6 +899,16 @@ fn add_blocklists(bind: bindgen::Builder) -> bindgen::Builder { .blocklist_item("ERROR") } +fn add_allowlists<'a>( + mut bind: bindgen::Builder, + pg_target_includes: impl Iterator, +) -> bindgen::Builder { + for pg_target_include in pg_target_includes { + bind = bind.allowlist_file(format!("{}.*", regex::escape(pg_target_include))) + } + bind.allowlist_item("PGERROR").allowlist_item("SIG.*") +} + fn add_derives(bind: bindgen::Builder) -> bindgen::Builder { bind.derive_debug(true) .derive_copy(true) @@ -947,23 +956,44 @@ fn target_env_tracked(s: &str) -> Option { env_tracked(&format!("{s}_{target}")).or_else(|| env_tracked(s)) } -fn pg_target_include(pg_version: u16, pg_config: &PgConfig) -> eyre::Result { - let var = "PGRX_INCLUDEDIR_SERVER"; +fn find_include( + pg_version: u16, + var: &str, + default: impl Fn() -> eyre::Result, +) -> eyre::Result { let value = target_env_tracked(&format!("{var}_PG{pg_version}")).or_else(|| target_env_tracked(var)); let path = match value { // No configured value: ask `pg_config`. - None => pg_config.includedir_server()?, + None => default()?, // Configured to non-empty string: pass to bindgen Some(overridden) => Path::new(&overridden).to_path_buf(), }; let path = std::fs::canonicalize(&path) .wrap_err(format!("cannot find {path:?} for C header files"))? .join("") // returning a `/`-ending path - .to_str() - .ok_or(eyre!("{path:?} is not valid UTF-8 string"))? + .display() .to_string(); - Ok(path) + if let Some(path) = path.strip_prefix("\\\\?\\") { + Ok(path.to_string()) + } else { + Ok(path) + } +} + +fn pg_target_includes(pg_version: u16, pg_config: &PgConfig) -> eyre::Result> { + let mut result = + vec![find_include(pg_version, "PGRX_INCLUDEDIR_SERVER", || pg_config.includedir_server())?]; + if let Some("msvc") = env_tracked("CARGO_CFG_TARGET_ENV").as_deref() { + result.push(find_include(pg_version, "PGRX_PKGINCLUDEDIR", || pg_config.pkgincludedir())?); + result.push(find_include(pg_version, "PGRX_INCLUDEDIR_SERVER_PORT_WIN32", || { + pg_config.includedir_server_port_win32() + })?); + result.push(find_include(pg_version, "PGRX_INCLUDEDIR_SERVER_PORT_WIN32_MSVC", || { + pg_config.includedir_server_port_win32_msvc() + })?); + } + Ok(result) } fn build_shim( @@ -976,7 +1006,15 @@ fn build_shim( std::fs::copy(shim_src, shim_dst).unwrap(); let mut build = cc::Build::new(); - build.flag(&format!("-I{}", pg_target_include(major_version, pg_config)?)); + if let Some("msvc") = env_tracked("CARGO_CFG_TARGET_ENV").as_deref() { + // without this, pgrx_embed would be linked to postgres.lib + build.compiler("clang"); + build.archiver("llvm-lib"); + build.flag("-flto=thin"); + } + for pg_target_include in pg_target_includes(major_version, pg_config)?.iter() { + build.flag(&format!("-I{pg_target_include}")); + } for flag in extra_bindgen_clang_args(pg_config)? { build.flag(&flag); } @@ -988,9 +1026,13 @@ fn build_shim( fn extra_bindgen_clang_args(pg_config: &PgConfig) -> eyre::Result> { let mut out = vec![]; let flags = shlex::split(&pg_config.cppflags()?.to_string_lossy()).unwrap_or_default(); - // Just give clang the full flag set, since presumably that's what we're - // getting when we build the C shim anyway. - out.extend(flags.iter().cloned()); + if env_tracked("CARGO_CFG_TARGET_OS").as_deref() != Some("windows") { + // Just give clang the full flag set, since presumably that's what we're + // getting when we build the C shim anyway. + // Skip it on Windows, since clang is used to generate cshim but MSVC is + // used to compile PostgreSQL. + out.extend(flags.iter().cloned()); + } if env_tracked("CARGO_CFG_TARGET_OS").as_deref() == Some("macos") { // Find the `-isysroot` flags so we can warn about them, so something // reasonable shows up if/when the build fails. diff --git a/pgrx-pg-config/src/cargo.rs b/pgrx-pg-config/src/cargo.rs index 3d002728f1..01d9faede3 100644 --- a/pgrx-pg-config/src/cargo.rs +++ b/pgrx-pg-config/src/cargo.rs @@ -89,8 +89,16 @@ impl PgrxManifestExt for Manifest { fn lib_filename(&self) -> eyre::Result { let lib_name = &self.lib_name()?; - let so_extension = if cfg!(target_os = "macos") { "dylib" } else { "so" }; - Ok(format!("lib{}.{}", lib_name.replace('-', "_"), so_extension)) + let (prefix, extension) = 'pe: { + if cfg!(target_os = "macos") { + break 'pe ("lib", "dylib"); + } + if cfg!(target_os = "windows") { + break 'pe ("", "dll"); + } + ("lib", "so") + }; + Ok(format!("{prefix}{}.{}", lib_name.replace('-', "_"), extension)) } } diff --git a/pgrx-pg-config/src/lib.rs b/pgrx-pg-config/src/lib.rs index b7a6eeee39..4048be4959 100644 --- a/pgrx-pg-config/src/lib.rs +++ b/pgrx-pg-config/src/lib.rs @@ -29,11 +29,7 @@ pub static BASE_POSTGRES_TESTING_PORT_NO: u16 = 32200; /// The flags to specify to get a "C.UTF-8" locale on this system, or "C" locale on systems without /// a "C.UTF-8" locale equivalent. pub fn get_c_locale_flags() -> &'static [&'static str] { - #[cfg(target_os = "macos")] - { - &["--locale=C", "--lc-ctype=UTF-8"] - } - #[cfg(not(target_os = "macos"))] + #[cfg(all(target_family = "unix", not(target_os = "macos")))] { match Command::new("locale").arg("-a").output() { Ok(cmd) @@ -47,6 +43,14 @@ pub fn get_c_locale_flags() -> &'static [&'static str] { _ => &["--locale=C"], } } + #[cfg(target_os = "macos")] + { + &["--locale=C", "--lc-ctype=UTF-8"] + } + #[cfg(target_os = "windows")] + { + &["--locale=C"] + } } // These methods were originally in `pgrx-utils`, but in an effort to consolidate @@ -330,32 +334,55 @@ impl PgConfig { pub fn postmaster_path(&self) -> eyre::Result { let mut path = self.bin_dir()?; + #[cfg(not(target_os = "windows"))] path.push("postgres"); - + #[cfg(target_os = "windows")] + path.push("postgres.exe"); Ok(path) } pub fn initdb_path(&self) -> eyre::Result { let mut path = self.bin_dir()?; + #[cfg(not(target_os = "windows"))] path.push("initdb"); + #[cfg(target_os = "windows")] + path.push("initdb.exe"); Ok(path) } pub fn createdb_path(&self) -> eyre::Result { let mut path = self.bin_dir()?; + #[cfg(not(target_os = "windows"))] path.push("createdb"); + #[cfg(target_os = "windows")] + path.push("createdb.exe"); Ok(path) } pub fn dropdb_path(&self) -> eyre::Result { let mut path = self.bin_dir()?; + #[cfg(not(target_os = "windows"))] path.push("dropdb"); + #[cfg(target_os = "windows")] + path.push("dropdb.exe"); + Ok(path) + } + + pub fn pg_ctl_path(&self) -> eyre::Result { + let mut path = self.bin_dir()?; + #[cfg(not(target_os = "windows"))] + path.push("pg_ctl"); + #[cfg(target_os = "windows")] + path.push("pg_ctl.exe"); Ok(path) } pub fn psql_path(&self) -> eyre::Result { let mut path = self.bin_dir()?; + #[cfg(not(target_os = "windows"))] path.push("psql"); + #[cfg(target_os = "windows")] + path.push("psql.exe"); Ok(path) } @@ -385,10 +412,24 @@ impl PgConfig { .collect()) } + pub fn pkgincludedir(&self) -> eyre::Result { + Ok(self.run("--pkgincludedir")?.into()) + } + pub fn includedir_server(&self) -> eyre::Result { Ok(self.run("--includedir-server")?.into()) } + pub fn includedir_server_port_win32(&self) -> eyre::Result { + let includedir_server = self.includedir_server()?; + Ok(includedir_server.join("port").join("win32")) + } + + pub fn includedir_server_port_win32_msvc(&self) -> eyre::Result { + let includedir_server = self.includedir_server()?; + Ok(includedir_server.join("port").join("win32_msvc")) + } + pub fn pkglibdir(&self) -> eyre::Result { Ok(self.run("--pkglibdir")?.into()) } diff --git a/pgrx-pg-sys/src/port.rs b/pgrx-pg-sys/src/port.rs index 9298ee82c8..05ef5ae467 100644 --- a/pgrx-pg-sys/src/port.rs +++ b/pgrx-pg-sys/src/port.rs @@ -156,11 +156,109 @@ pub fn get_pg_major_version_num() -> u16 { u16::from_str(super::get_pg_major_version_string()).unwrap() } +#[cfg(any(not(target_env = "msvc"), feature = "pg17"))] #[inline] pub fn get_pg_version_string() -> &'static str { super::PG_VERSION_STR.to_str().unwrap() } +#[cfg(all( + target_env = "msvc", + any(feature = "pg12", feature = "pg13", feature = "pg14", feature = "pg15", feature = "pg16") +))] +#[inline] +pub fn get_pg_version_string() -> &'static str { + // bindgen cannot get value of PG_VERSION_STR + // PostgreSQL @0@ on @1@-@2@, compiled by @3@-@4@, @5@-bit + static PG_VERSION_STR: [u8; 256] = const { + let major = super::PG_MAJORVERSION_NUM; + let minor = super::PG_MINORVERSION_NUM; + #[cfg(target_pointer_width = "32")] + let pointer_width = 32_u32; + #[cfg(target_pointer_width = "64")] + let pointer_width = 64_u32; + // a fake value + let msc_ver = b"1700"; + let mut buffer = [0u8; 256]; + let mut pointer = 0; + { + let s = b"PostgreSQL "; + let mut i = 0; + while i < s.len() { + buffer[pointer + i] = s[i]; + i += 1; + } + pointer += s.len(); + } + { + buffer[pointer + 0] = b'0' + (major / 10) as u8; + buffer[pointer + 1] = b'0' + (major % 10) as u8; + pointer += 2; + } + { + let s = b"."; + let mut i = 0; + while i < s.len() { + buffer[pointer + i] = s[i]; + i += 1; + } + pointer += s.len(); + } + if minor < 10 { + buffer[pointer + 0] = b'0' + (minor % 10) as u8; + pointer += 1; + } else { + buffer[pointer + 0] = b'0' + (minor / 10) as u8; + buffer[pointer + 1] = b'0' + (minor % 10) as u8; + pointer += 2; + } + { + let s = b", compiled by Visual C++ build "; + let mut i = 0; + while i < s.len() { + buffer[pointer + i] = s[i]; + i += 1; + } + pointer += s.len(); + } + { + let s = msc_ver; + let mut i = 0; + while i < s.len() { + buffer[pointer + i] = s[i]; + i += 1; + } + pointer += s.len(); + } + { + let s = b", "; + let mut i = 0; + while i < s.len() { + buffer[pointer + i] = s[i]; + i += 1; + } + pointer += s.len(); + } + { + buffer[pointer + 0] = b'0' + (pointer_width / 10) as u8; + buffer[pointer + 1] = b'0' + (pointer_width % 10) as u8; + pointer += 2; + } + { + let s = b"-bit"; + let mut i = 0; + while i < s.len() { + buffer[pointer + i] = s[i]; + i += 1; + } + pointer += s.len(); + } + buffer[pointer] = 0; + buffer + }; + unsafe { std::ffi::CStr::from_ptr(PG_VERSION_STR.as_ptr().cast()).to_str().unwrap() } +} + #[inline] pub fn get_pg_major_minor_version_string() -> &'static str { super::PG_VERSION.to_str().unwrap() diff --git a/pgrx-tests/Cargo.toml b/pgrx-tests/Cargo.toml index 3d4788e1cf..ce1b37c299 100644 --- a/pgrx-tests/Cargo.toml +++ b/pgrx-tests/Cargo.toml @@ -39,7 +39,10 @@ pg17 = ["pgrx/pg17"] pg_test = [] proptest = ["dep:proptest"] cshim = ["pgrx/cshim"] -no-schema-generation = ["pgrx/no-schema-generation", "pgrx-macros/no-schema-generation"] +no-schema-generation = [ + "pgrx/no-schema-generation", + "pgrx-macros/no-schema-generation", +] nightly = ["pgrx/nightly"] [package.metadata.docs.rs] @@ -67,6 +70,7 @@ thiserror.workspace = true paste = "1" postgres = "0.19.10" proptest = { version = "1", optional = true } +tempfile = "3.13.0" sysinfo = "0.33.1" rand = "0.9.0" @@ -75,6 +79,16 @@ path = "../pgrx" default-features = false version = "=0.13.1" +[target.'cfg(target_os = "windows")'.dependencies] +winapi = { version = "0.3.9", features = [ + "securitybaseapi", + "minwinbase", + "namedpipeapi", + "winbase", + "winerror", + "winnt", +] } + [dev-dependencies] eyre.workspace = true # testing functions that return `eyre::Result` trybuild = "1" diff --git a/pgrx-tests/src/framework.rs b/pgrx-tests/src/framework.rs index 190f11071a..c57d434921 100644 --- a/pgrx-tests/src/framework.rs +++ b/pgrx-tests/src/framework.rs @@ -114,7 +114,7 @@ pub fn run_test( ); return Ok(()); } - let (loglines, system_session_id) = initialize_test_framework(postgresql_conf)?; + let (loglines, system_session_id) = get_test_framework(postgresql_conf)?; let (mut client, session_id) = client()?; @@ -190,9 +190,7 @@ fn format_loglines(session_id: &str, loglines: &LogLines) -> String { result } -fn initialize_test_framework( - postgresql_conf: Vec<&'static str>, -) -> eyre::Result<(LogLines, String)> { +fn get_test_framework(postgresql_conf: Vec<&'static str>) -> eyre::Result<(LogLines, String)> { let mut state = TEST_MUTEX .get_or_init(|| { Mutex::new(SetupState { @@ -212,22 +210,31 @@ fn initialize_test_framework( }); if !state.installed { - shutdown::register_shutdown_hook(); - install_extension()?; - initdb(postgresql_conf)?; - - let system_session_id = start_pg(state.loglines.clone())?; - let pg_config = get_pg_config()?; - dropdb()?; - createdb(&pg_config, get_pg_dbname(), true, false, get_runas())?; - create_extension()?; - state.installed = true; - state.system_session_id = system_session_id; + initialize_test_framework(&mut state, postgresql_conf) + .expect("Could not initialize test framework"); } Ok((state.loglines.clone(), state.system_session_id.clone())) } +fn initialize_test_framework( + state: &mut SetupState, + postgresql_conf: Vec<&'static str>, +) -> eyre::Result<()> { + shutdown::register_shutdown_hook(); + install_extension()?; + initdb(postgresql_conf)?; + + let system_session_id = start_pg(state.loglines.clone())?; + let pg_config = get_pg_config()?; + dropdb()?; + createdb(&pg_config, get_pg_dbname(), true, false, get_runas())?; + create_extension()?; + state.installed = true; + state.system_session_id = system_session_id; + Ok(()) +} + fn get_pg_config() -> eyre::Result { let pgrx = Pgrx::from_config().wrap_err("Unable to get PGRX from config")?; @@ -418,7 +425,7 @@ fn maybe_make_pgdata>(pgdata: P) -> eyre::Result { } else { // if the directory doesn't exist, make it. If it does, then we reuse it if !pgdata.exists() { - std::fs::create_dir_all(&pgdata)?; + std::fs::create_dir_all(pgdata.parent().unwrap())?; // which is the only time we need to `initdb` it. need_initdb = true; @@ -446,25 +453,20 @@ fn initdb(postgresql_conf: Vec<&'static str>) -> eyre::Result<()> { }; command + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) .current_dir(pgdata.parent().unwrap()) .args(get_c_locale_flags()) .arg("-D") - .arg(&pgdata) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()); + .arg(&pgdata); let command_str = format!("{command:?}"); println!("{} {}", " Running".bold().green(), command_str); - let child = command.spawn().wrap_err_with(|| { - format!( - "Failed to spawn process for initializing database using command: '{command_str}': " - ) - })?; - let output = child.wait_with_output().wrap_err_with(|| { + let output = command.output().wrap_err_with(|| { format!( - "Failed waiting for spawned process attempting to initialize database using command: '{command_str}': " + "Failed to spawn process for initializing database using command: '{command_str}': " ) })?; @@ -476,6 +478,8 @@ fn initdb(postgresql_conf: Vec<&'static str>) -> eyre::Result<()> { String::from_utf8(output.stderr).unwrap() )); } + + println!("{} initializing database", " Finished".bold().green()); } modify_postgresql_conf(pgdata, postgresql_conf) @@ -485,8 +489,10 @@ fn modify_postgresql_conf(pgdata: PathBuf, postgresql_conf: Vec<&'static str>) - let mut contents = String::new(); contents.push_str("log_line_prefix='[%m] [%p] [%c]: '\n"); - contents - .push_str(&format!("unix_socket_directories = '{}'\n", pgdata.parent().unwrap().display())); + contents.push_str(&format!( + "unix_socket_directories = '{}'\n", + pgdata.parent().unwrap().display().to_string().replace("\\", "\\\\") + )); for setting in postgresql_conf { contents.push_str(&format!("{setting}\n")); } @@ -522,30 +528,58 @@ fn modify_postgresql_conf(pgdata: PathBuf, postgresql_conf: Vec<&'static str>) - fn start_pg(loglines: LogLines) -> eyre::Result { wait_for_pidfile()?; + #[cfg(target_family = "unix")] + let pipe = pipe::UnixFifo::create()?; + #[cfg(target_family = "unix")] + let make_pipe_opened_without_blocking = pipe.full()?; + #[cfg(target_os = "windows")] + let mut pipe = pipe::WindowsNamedPipe::create()?; + let pg_config = get_pg_config()?; - let postmaster_path = - pg_config.postmaster_path().wrap_err("unable to determine postmaster path")?; - - let mut command = if use_valgrind() { - let mut cmd = Command::new("valgrind"); - cmd.args([ - "--leak-check=no", - "--gen-suppressions=all", - "--time-stamp=yes", - "--error-markers=VALGRINDERROR-BEGIN,VALGRINDERROR-END", - "--trace-children=yes", - ]); - // Try to provide a suppressions file, we'll likely get false positives - // if we can't, but that might be better than nothing. + let pg_ctl = pg_config.pg_ctl_path()?; + + let postmaster_path = if use_valgrind() { + #[allow(unused_mut)] + let mut builder = tempfile::Builder::new(); + #[cfg(target_family = "unix")] + { + use std::os::unix::fs::PermissionsExt; + let permission = std::fs::Permissions::from_mode(0o700); + builder.permissions(permission); + } + let mut file = builder.tempfile()?; + file.write_all(b"#!/usr/bin/sh\n")?; + let mut command = Command::new("valgrind"); + command.arg("--leak-check=no"); + command.arg("--gen-suppressions=all"); + command.arg("--time-stamp=yes"); + command.arg("--error-markers=VALGRINDERROR-BEGIN,VALGRINDERROR-END"); + command.arg("--trace-children=yes"); if let Ok(path) = valgrind_suppressions_path(&pg_config) { - if path.exists() { - cmd.arg(format!("--suppressions={}", path.display())); + if let Ok(true) = std::fs::exists(&path) { + command.arg(format!("--suppressions={}", path.display())); } } + command.arg(pg_config.postmaster_path()?.display().to_string()); + file.write_all(format!("{command:?}").as_bytes())?; + file.write_all(b" \"$@\"\n")?; + Some(file.into_temp_path()) + } else { + None + }; - cmd.arg(postmaster_path); - cmd - } else if let Some(runas) = get_runas() { + let mut postmaster_args = Vec::new(); + postmaster_args.push("-i".into()); + postmaster_args.push("-p".into()); + postmaster_args.push(pg_config.test_port().expect("unable to determine test port").to_string()); + postmaster_args.push("-h".into()); + postmaster_args.push(pg_config.host().into()); + postmaster_args.push("-c".into()); + postmaster_args.push("log_destination=stderr".into()); + postmaster_args.push("-c".into()); + postmaster_args.push("logging_collector=off".into()); + + let mut command = if let Some(runas) = get_runas() { #[inline] fn accept_envar(var: &str) -> bool { // taken from https://doc.rust-lang.org/cargo/reference/environment-variables.html @@ -555,9 +589,7 @@ fn start_pg(loglines: LogLines) -> eyre::Result { || ["OUT_DIR", "TARGET", "HOST", "NUM_JOBS", "OPT_LEVEL", "DEBUG", "PROFILE"] .contains(&var) } - let mut cmd = sudo_command(runas); - // when running the `postmaster` process via `sudo`, we need to copy the cargo/rust-related // environment variables and pass as arguments to sudo, ahead of the `postmaster` command itself // @@ -568,98 +600,108 @@ fn start_pg(loglines: LogLines) -> eyre::Result { cmd.arg(env_as_arg); } } - - // now we can add the `postmaster` as the command for `sudo` to execute - cmd.arg(postmaster_path); + // now we can add the `pg_ctl` as the command for `sudo` to execute + cmd.arg(pg_ctl); cmd } else { - Command::new(postmaster_path) + Command::new(pg_ctl) }; command + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .arg("start") + .arg("-o") + .arg(postmaster_args.join(" ")) .arg("-D") .arg(get_pgdata_path()?.to_str().unwrap()) - .arg("-h") - .arg(pg_config.host()) - .arg("-p") - .arg(pg_config.test_port().expect("unable to determine test port").to_string()) - // Redirecting logs to files can hang the test framework, override it - .args(["-c", "log_destination=stderr", "-c", "logging_collector=off"]) - .stdout(Stdio::inherit()) - .stderr(Stdio::piped()); + .arg("-l") + .arg(pipe.path()); + if let Some(postmaster_path) = postmaster_path.as_ref() { + command + .arg("-W") // pg_ctl cannot detect if postmaster starts + .arg("-p") + .arg(postmaster_path); + } + #[cfg(target_os = "windows")] + { + // on windows, created pipes are leaked, so that the command hangs + command.stdout(Stdio::inherit()).stderr(Stdio::inherit()); + } let command_str = format!("{command:?}"); - // start Postgres and monitor its stderr in the background - // also notify the main thread when it's ready to accept connections - let session_id = monitor_pg(command, command_str, loglines); - - Ok(session_id) -} - -fn valgrind_suppressions_path(pg_config: &PgConfig) -> Result { - let mut home = Pgrx::home()?; - home.push(pg_config.version()?); - home.push("src/tools/valgrind.supp"); - Ok(home) -} + #[cfg(target_family = "unix")] + let (output, mut pipe) = { + let output = command.output(); + let pipe = pipe.read().expect("failed to connect to pipe"); + drop(make_pipe_opened_without_blocking); + (output?, pipe) + }; + #[cfg(target_os = "windows")] + let (output, mut pipe) = { + let (output, pipe) = std::thread::scope(|scope| { + let thread = scope.spawn(|| { + pipe.connect().expect("failed to connect to pg_ctl"); + pipe.connect().expect("failed to connect to pipe") + }); + (command.output(), thread.join().unwrap()) + }); + (output?, pipe) + }; -fn wait_for_pidfile() -> Result<(), eyre::Report> { - const MAX_PIDFILE_RETRIES: usize = 10; + if !output.status.success() { + let log = { + use std::io::Read; + let mut buffer = vec![0u8; 4096]; + let mut result = Vec::new(); + if let Ok(n) = pipe.read(&mut buffer) { + if n > 0 { + result.extend(&buffer[..n]); + } + } + result + }; + panic!( + "problem running pg_ctl: {}\n\n{}\n\n{}", + command_str, + String::from_utf8(output.stderr).unwrap(), + String::from_utf8(log).unwrap() + ); + } - let pidfile = get_pid_file()?; + add_shutdown_hook(|| { + let pg_config = get_pg_config().unwrap(); + let pg_ctl = pg_config.pg_ctl_path().unwrap(); - let mut retries = 0; - while pidfile.exists() { - if retries > MAX_PIDFILE_RETRIES { - // break out and try to start postgres anyways, maybe it'll report a decent error about what's going on - eprintln!("`{}` has existed for ~10s. There might be some problem with the pgrx testing Postgres instance", pidfile.display()); - break; + let mut command = if let Some(runas) = get_runas() { + let mut cmd = sudo_command(runas); + cmd.arg(pg_ctl); + cmd + } else { + Command::new(pg_ctl) + }; + command + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .arg("stop") + .arg("-D") + .arg(get_pgdata_path().unwrap().to_str().unwrap()) + .arg("-m") + .arg("fast"); + let command_str = format!("{command:?}"); + let output = command.output().unwrap(); + if !output.status.success() { + panic!( + "problem running pg_ctl: {}\n\n{}", + command_str, + String::from_utf8(output.stderr).unwrap() + ); } - eprintln!("`{}` still exists. Waiting...", pidfile.display()); - std::thread::sleep(Duration::from_secs(1)); - retries += 1; - } - Ok(()) -} + }); -fn monitor_pg(mut command: Command, cmd_string: String, loglines: LogLines) -> String { let (sender, receiver) = std::sync::mpsc::channel(); - std::thread::spawn(move || { - let mut child = command.spawn().expect("postmaster didn't spawn"); - - let pid = child.id(); - // Add a shutdown hook so we can terminate it when the test framework - // exits. TODO: Consider finding a way to handle cases where we fail to - // clean up due to a SIGNAL? - add_shutdown_hook(move || unsafe { - if let Some(_runas) = get_runas() { - sudo_command("root") // NB: we must be "root" to kill the `sudo` process we spawned to start postgres - .arg("kill") - .arg("-s") - .arg("SIGTERM") - .arg(pid.to_string()) - .spawn() - .expect("failed to spawn `sudo kill`") - .wait() - .expect("`sudo kill` didn't work"); - } else { - libc::kill(pid as libc::pid_t, libc::SIGTERM); - let message_string = std::ffi::CString::new( - format!("stopping postgres (pid={pid})\n").bold().blue().to_string(), - ) - .unwrap(); - // IMPORTANT: Rust string literals are not naturally null-terminated - libc::printf("%s\0".as_ptr().cast(), message_string.as_ptr()); - } - }); - - eprintln!("{cmd}\npid={p}", cmd = cmd_string.bold().blue(), p = pid.to_string().yellow()); - eprintln!("{}", pg_sys::get_pg_version_string().bold().purple()); - - // wait for the database to say its ready to start up - let reader = BufReader::new(child.stderr.take().expect("couldn't take postmaster stderr")); - + let reader = BufReader::new(pipe); let regex = regex::Regex::new(r"\[.*?\] \[.*?\] \[(?P.*?)\]").unwrap(); let mut is_started_yet = false; let mut lines = reader.lines(); @@ -699,21 +741,37 @@ fn monitor_pg(mut command: Command, cmd_string: String, loglines: LogLines) -> S let session_lines = loglines.entry(session_id).or_default(); session_lines.push(line); } - - // wait for Postgres to really finish - match child.try_wait() { - Ok(status) => { - if let Some(_status) = status { - // we exited normally - } - } - Err(e) => panic!("was going to let Postgres finish, but errored this time:\n{e}"), - } }); // wait for Postgres to indicate it's ready to accept connection // and return its pid when it is - receiver.recv().expect("Postgres failed to start") + Ok(receiver.recv().expect("Postgres failed to start")) +} + +fn valgrind_suppressions_path(pg_config: &PgConfig) -> Result { + let mut home = Pgrx::home()?; + home.push(pg_config.version()?); + home.push("src/tools/valgrind.supp"); + Ok(home) +} + +fn wait_for_pidfile() -> Result<(), eyre::Report> { + const MAX_PIDFILE_RETRIES: usize = 10; + + let pidfile = get_pid_file()?; + + let mut retries = 0; + while pidfile.exists() { + if retries > MAX_PIDFILE_RETRIES { + // break out and try to start postgres anyways, maybe it'll report a decent error about what's going on + eprintln!("`{}` has existed for ~10s. There might be some problem with the pgrx testing Postgres instance", pidfile.display()); + break; + } + eprintln!("`{}` still exists. Waiting...", pidfile.display()); + std::thread::sleep(Duration::from_secs(1)); + retries += 1; + } + Ok(()) } fn dropdb() -> eyre::Result<()> { @@ -819,8 +877,12 @@ pub(crate) fn get_pg_dbname() -> &'static str { } pub(crate) fn get_pg_user() -> String { + #[cfg(target_family = "unix")] + let varname = "USER"; + #[cfg(target_os = "windows")] + let varname = "USERNAME"; get_runas().unwrap_or_else(|| { - std::env::var("USER") + std::env::var(varname) .unwrap_or_else(|_| panic!("USER environment var is unset or invalid UTF-8")) }) } @@ -898,7 +960,7 @@ fn get_cargo_args() -> Vec { while let Some(process) = system.process(pid) { // only if it's "cargo"... (This works for now, but just because `cargo` // is at the end of the path. How *should* this handle `CARGO`?) - if process.exe().is_some_and(|p| p.ends_with("cargo")) { + if process.exe().is_some_and(|p| p.ends_with("cargo") || p.ends_with("cargo.exe")) { // ... and only if it's "cargo test"... if process.cmd().iter().any(|arg| arg == "test") && !process.cmd().iter().any(|arg| arg == "pgrx") @@ -959,3 +1021,224 @@ fn sudo_command>(user: U) -> Command { sudo.arg(user); sudo } + +pub mod pipe { + use rand::distr::Alphanumeric; + use rand::Rng; + use std::fs::File; + use std::io::Error; + use std::path::{Path, PathBuf}; + + #[cfg(target_family = "unix")] + pub struct UnixFifo { + path: PathBuf, + } + + #[cfg(target_family = "unix")] + impl UnixFifo { + pub fn create() -> std::io::Result { + use std::ffi::CString; + let filename: String = + rand::rng().sample_iter(Alphanumeric).map(char::from).take(6).collect(); + let path = format!(r"/tmp/{filename}"); + let arg = CString::new(path.clone()).unwrap(); + let mode = libc::S_IRUSR + | libc::S_IWUSR + | libc::S_IRGRP + | libc::S_IWGRP + | libc::S_IROTH + | libc::S_IWOTH; + let errno = unsafe { libc::mkfifo(arg.as_ptr(), mode) }; + if errno < 0 { + return Err(Error::last_os_error()); + } + let errno = unsafe { libc::chmod(arg.as_ptr(), mode) }; + if errno < 0 { + return Err(Error::last_os_error()); + } + Ok(UnixFifo { path: PathBuf::from(path) }) + } + pub fn path(&self) -> &Path { + &self.path + } + pub fn read(&self) -> std::io::Result { + use std::os::unix::fs::OpenOptionsExt; + let file = std::fs::OpenOptions::new() + .read(true) + .custom_flags(libc::O_NOCTTY) + .open(&self.path)?; + Ok(file) + } + pub fn full(&self) -> std::io::Result { + use std::os::unix::fs::OpenOptionsExt; + let file = std::fs::OpenOptions::new() + .read(true) + .write(true) + .custom_flags(libc::O_NOCTTY) + .open(&self.path)?; + Ok(file) + } + } + + #[cfg(target_family = "unix")] + impl Drop for UnixFifo { + fn drop(&mut self) { + let _ = std::fs::remove_file(&self.path); + } + } + + #[cfg(target_os = "windows")] + pub struct WindowsNamedPipe { + path: PathBuf, + file: File, + } + + #[cfg(target_os = "windows")] + impl WindowsNamedPipe { + pub fn create() -> std::io::Result { + let filename: String = + rand::thread_rng().sample_iter(Alphanumeric).map(char::from).take(6).collect(); + let path = format!(r"\\.\pipe\{filename}"); + let server = unsafe { + use std::os::windows::ffi::OsStrExt; + use std::os::windows::io::FromRawHandle; + let mut os_str = PathBuf::from(&path).as_os_str().to_os_string(); + os_str.push("\0"); + let arg = os_str.encode_wide().collect::>(); + let mut sd = { + let mut sd = std::mem::zeroed::(); + let success = winapi::um::securitybaseapi::InitializeSecurityDescriptor( + (&raw mut sd).cast(), + winapi::um::winnt::SECURITY_DESCRIPTOR_REVISION, + ); + if success == 0 { + return Err(Error::last_os_error()); + } + let success = winapi::um::securitybaseapi::SetSecurityDescriptorDacl( + (&raw mut sd).cast(), + 1, + std::ptr::null_mut(), + 0, + ); + if success == 0 { + return Err(Error::last_os_error()); + } + let success = winapi::um::securitybaseapi::SetSecurityDescriptorControl( + (&raw mut sd).cast(), + winapi::um::winnt::SE_DACL_PROTECTED, + winapi::um::winnt::SE_DACL_PROTECTED, + ); + if success == 0 { + return Err(Error::last_os_error()); + } + sd + }; + let mut sa = { + let mut sa = std::mem::zeroed::(); + sa.nLength = size_of::() as _; + sa.lpSecurityDescriptor = (&raw mut sd).cast(); + sa.bInheritHandle = 0; + sa + }; + let raw_handle = winapi::um::namedpipeapi::CreateNamedPipeW( + arg.as_ptr().cast(), + winapi::um::winbase::PIPE_ACCESS_DUPLEX + | winapi::um::winbase::FILE_FLAG_FIRST_PIPE_INSTANCE, + winapi::um::winbase::PIPE_TYPE_BYTE + | winapi::um::winbase::PIPE_READMODE_BYTE + | winapi::um::winbase::PIPE_WAIT, + winapi::um::winbase::PIPE_UNLIMITED_INSTANCES, + 65536, + 65536, + 0, + &raw mut sa, + ); + if raw_handle == winapi::um::handleapi::INVALID_HANDLE_VALUE { + return Err(Error::last_os_error()); + } + File::from_raw_handle(raw_handle.cast()) + }; + Ok(WindowsNamedPipe { path: PathBuf::from(path), file: server }) + } + pub fn path(&self) -> &Path { + &self.path + } + pub fn connect(&mut self) -> std::io::Result { + use std::os::windows::io::AsRawHandle; + let ret = unsafe { + winapi::um::namedpipeapi::ConnectNamedPipe( + self.file.as_raw_handle().cast(), + std::ptr::null_mut(), + ) + }; + if ret == 0 { + let last_os_error = Error::last_os_error(); + if last_os_error.raw_os_error() + != Some(winapi::shared::winerror::ERROR_PIPE_CONNECTED as _) + { + return Err(last_os_error); + } + } + let path = &self.path; + let server = unsafe { + use std::os::windows::ffi::OsStrExt; + use std::os::windows::io::FromRawHandle; + let mut os_str = PathBuf::from(&path).as_os_str().to_os_string(); + os_str.push("\0"); + let arg = os_str.encode_wide().collect::>(); + let mut sd = { + let mut sd = std::mem::zeroed::(); + let success = winapi::um::securitybaseapi::InitializeSecurityDescriptor( + (&raw mut sd).cast(), + winapi::um::winnt::SECURITY_DESCRIPTOR_REVISION, + ); + if success == 0 { + return Err(Error::last_os_error()); + } + let success = winapi::um::securitybaseapi::SetSecurityDescriptorDacl( + (&raw mut sd).cast(), + 1, + std::ptr::null_mut(), + 0, + ); + if success == 0 { + return Err(Error::last_os_error()); + } + let success = winapi::um::securitybaseapi::SetSecurityDescriptorControl( + (&raw mut sd).cast(), + winapi::um::winnt::SE_DACL_PROTECTED, + winapi::um::winnt::SE_DACL_PROTECTED, + ); + if success == 0 { + return Err(Error::last_os_error()); + } + sd + }; + let mut sa = { + let mut sa = std::mem::zeroed::(); + sa.nLength = size_of::() as _; + sa.lpSecurityDescriptor = (&raw mut sd).cast(); + sa.bInheritHandle = 0; + sa + }; + let raw_handle = winapi::um::namedpipeapi::CreateNamedPipeW( + arg.as_ptr().cast(), + winapi::um::winbase::PIPE_ACCESS_DUPLEX, + winapi::um::winbase::PIPE_TYPE_BYTE + | winapi::um::winbase::PIPE_READMODE_BYTE + | winapi::um::winbase::PIPE_WAIT, + winapi::um::winbase::PIPE_UNLIMITED_INSTANCES, + 65536, + 65536, + 0, + &raw mut sa, + ); + if raw_handle == winapi::um::handleapi::INVALID_HANDLE_VALUE { + return Err(Error::last_os_error()); + } + File::from_raw_handle(raw_handle.cast()) + }; + Ok(std::mem::replace(&mut self.file, server)) + } + } +} diff --git a/pgrx-tests/src/framework/shutdown.rs b/pgrx-tests/src/framework/shutdown.rs index cc3f91ab6d..9a439823d8 100644 --- a/pgrx-tests/src/framework/shutdown.rs +++ b/pgrx-tests/src/framework/shutdown.rs @@ -9,7 +9,7 @@ //LICENSE Use of this source code is governed by the MIT license that can be found in the LICENSE file. use std::panic::{self, AssertUnwindSafe, Location}; use std::sync::{Mutex, PoisonError}; -use std::{any, io, mem, process}; +use std::{any, mem, process}; /// Register a shutdown hook to be called when the process exits. /// @@ -114,12 +114,6 @@ fn failure_message(e: &(dyn any::Any + Send)) -> &str { /// Write to stderr, bypassing libtest's output redirection. Doesn't append `\n`. fn write_stderr(s: &str) { - loop { - let res = unsafe { libc::write(libc::STDERR_FILENO, s.as_ptr().cast(), s.len()) }; - // Handle EINTR to ensure we don't drop messages. - // `Error::last_os_error()` just reads from errno, so it's fine to use here. - if res >= 0 || io::Error::last_os_error().kind() != io::ErrorKind::Interrupted { - break; - } - } + use std::io::Write; + let _ = std::io::stderr().lock().write_all(s.as_bytes()); } diff --git a/pgrx-tests/src/tests/mod.rs b/pgrx-tests/src/tests/mod.rs index 48de56e703..a8ce2869a8 100644 --- a/pgrx-tests/src/tests/mod.rs +++ b/pgrx-tests/src/tests/mod.rs @@ -59,6 +59,7 @@ mod rel_tests; mod result_tests; mod roundtrip_tests; mod schema_tests; +#[cfg(target_family = "unix")] mod shmem_tests; mod spi_tests; mod srf_tests; diff --git a/pgrx-tests/src/tests/result_tests.rs b/pgrx-tests/src/tests/result_tests.rs index 77fe612f8f..4246cb93a1 100644 --- a/pgrx-tests/src/tests/result_tests.rs +++ b/pgrx-tests/src/tests/result_tests.rs @@ -96,10 +96,13 @@ mod tests { Err(pgrx::spi::Error::InvalidPosition) } - #[pg_test(error = "No such file or directory (os error 2)")] + #[pg_test(error = "I am a platform-independent and locale-agnostic string")] fn test_return_io_error() -> Result<(), std::io::Error> { - std::fs::read("/tmp/i-sure-hope-this-doest-exist.pgrx-tests::test_result_result") - .map(|_| ()) + use std::io::{Error, ErrorKind}; + Err(Error::new( + ErrorKind::NotFound, + "I am a platform-independent and locale-agnostic string", + )) } #[pg_test] From 65e392bb7cf321c67ad17900dc483a38d7f525b1 Mon Sep 17 00:00:00 2001 From: usamoi Date: Fri, 1 Nov 2024 21:02:16 +0800 Subject: [PATCH 2/8] fix shmem API on Windows --- pgrx-examples/shmem/src/lib.rs | 12 ++-- pgrx-tests/src/tests/mod.rs | 1 - pgrx-tests/src/tests/shmem_tests.rs | 4 +- pgrx/src/atomics.rs | 15 ++++- pgrx/src/lwlock.rs | 98 +++++++---------------------- pgrx/src/shmem.rs | 53 ++++++++-------- 6 files changed, 71 insertions(+), 112 deletions(-) diff --git a/pgrx-examples/shmem/src/lib.rs b/pgrx-examples/shmem/src/lib.rs index f092c03e7d..2a421f51ac 100644 --- a/pgrx-examples/shmem/src/lib.rs +++ b/pgrx-examples/shmem/src/lib.rs @@ -29,12 +29,12 @@ pub struct Pgtest { unsafe impl PGRXSharedMemory for Pgtest {} -static DEQUE: PgLwLock> = PgLwLock::new(); -static VEC: PgLwLock> = PgLwLock::new(); -static HASH: PgLwLock> = PgLwLock::new(); -static STRUCT: PgLwLock = PgLwLock::new(); -static PRIMITIVE: PgLwLock = PgLwLock::new(); -static ATOMIC: PgAtomic = PgAtomic::new(); +static DEQUE: PgLwLock> = PgLwLock::new(c"shmem_deque"); +static VEC: PgLwLock> = PgLwLock::new(c"shmem_vec"); +static HASH: PgLwLock> = PgLwLock::new(c"shmem_hash"); +static STRUCT: PgLwLock = PgLwLock::new(c"shmem_struct"); +static PRIMITIVE: PgLwLock = PgLwLock::new(c"shmem_primtive"); +static ATOMIC: PgAtomic = PgAtomic::new(c"shmem_atomic"); #[pg_guard] pub extern "C" fn _PG_init() { diff --git a/pgrx-tests/src/tests/mod.rs b/pgrx-tests/src/tests/mod.rs index a8ce2869a8..48de56e703 100644 --- a/pgrx-tests/src/tests/mod.rs +++ b/pgrx-tests/src/tests/mod.rs @@ -59,7 +59,6 @@ mod rel_tests; mod result_tests; mod roundtrip_tests; mod schema_tests; -#[cfg(target_family = "unix")] mod shmem_tests; mod spi_tests; mod srf_tests; diff --git a/pgrx-tests/src/tests/shmem_tests.rs b/pgrx-tests/src/tests/shmem_tests.rs index a28bc077ef..479fc1b9ee 100644 --- a/pgrx-tests/src/tests/shmem_tests.rs +++ b/pgrx-tests/src/tests/shmem_tests.rs @@ -11,8 +11,8 @@ use pgrx::prelude::*; use pgrx::{pg_shmem_init, PgAtomic, PgLwLock, PgSharedMemoryInitialization}; use std::sync::atomic::AtomicBool; -static ATOMIC: PgAtomic = PgAtomic::new(); -static LWLOCK: PgLwLock = PgLwLock::new(); +static ATOMIC: PgAtomic = PgAtomic::new(c"pgrx_tests_atomic"); +static LWLOCK: PgLwLock = PgLwLock::new(c"pgrx_tests_lwlock"); #[pg_guard] pub extern "C" fn _PG_init() { diff --git a/pgrx/src/atomics.rs b/pgrx/src/atomics.rs index 20647d7200..af722f455d 100644 --- a/pgrx/src/atomics.rs +++ b/pgrx/src/atomics.rs @@ -9,14 +9,20 @@ //LICENSE Use of this source code is governed by the MIT license that can be found in the LICENSE file. #![deny(unsafe_op_in_unsafe_fn)] use std::cell::UnsafeCell; +use std::ffi::CStr; pub struct PgAtomic { + name: &'static CStr, inner: UnsafeCell<*mut T>, } impl PgAtomic { - pub const fn new() -> Self { - Self { inner: UnsafeCell::new(std::ptr::null_mut()) } + pub const fn new(name: &'static CStr) -> Self { + Self { name, inner: UnsafeCell::new(std::ptr::null_mut()) } + } + + pub fn name(&self) -> &'static CStr { + self.name } } @@ -32,7 +38,10 @@ where } pub fn get(&self) -> &T { - unsafe { (*self.inner.get()).as_ref().expect("PgAtomic was not initialized") } + unsafe { + let shared = self.inner.get().read().as_ref().expect("PgAtomic was not initialized"); + shared + } } } diff --git a/pgrx/src/lwlock.rs b/pgrx/src/lwlock.rs index 972c8e7b48..c6e7827e5e 100644 --- a/pgrx/src/lwlock.rs +++ b/pgrx/src/lwlock.rs @@ -10,10 +10,8 @@ #![allow(clippy::needless_borrow)] use crate::pg_sys; use core::ops::{Deref, DerefMut}; -use once_cell::sync::OnceCell; use std::cell::UnsafeCell; -use std::fmt; -use uuid::Uuid; +use std::ffi::CStr; /// A Rust locking mechanism which uses a PostgreSQL LWLock to lock the data /// @@ -33,8 +31,8 @@ use uuid::Uuid; /// This lock can not be poisoned from Rust. Panic and Abort are handled by /// PostgreSQL cleanly. pub struct PgLwLock { - inner: UnsafeCell>, - name: OnceCell<&'static str>, + name: &'static CStr, + inner: UnsafeCell<*mut PgLwLockShared>, } unsafe impl Send for PgLwLock {} @@ -43,94 +41,44 @@ unsafe impl Sync for PgLwLock {} impl PgLwLock { /// Create an empty lock which can be created as a global with None as a /// sentinel value - pub const fn new() -> Self { - PgLwLock { inner: UnsafeCell::new(PgLwLockInner::empty()), name: OnceCell::new() } - } - - /// Create a new lock for T by attaching a LWLock, which is looked up by name - pub fn from_named(input_name: &'static str, value: *mut T) -> Self { - let name = OnceCell::new(); - let inner = UnsafeCell::new(PgLwLockInner::::new(input_name, value)); - name.set(input_name).unwrap(); - PgLwLock { inner, name } + pub const fn new(name: &'static CStr) -> Self { + PgLwLock { name, inner: UnsafeCell::new(std::ptr::null_mut()) } } /// Get the name of the PgLwLock - pub fn get_name(&self) -> &'static str { - match self.name.get() { - None => { - let name = Box::leak(Uuid::new_v4().to_string().into_boxed_str()); - self.name.set(name).unwrap(); - name - } - Some(name) => name, - } + pub fn name(&self) -> &'static CStr { + self.name } /// Obtain a shared lock (which comes with `&T` access) pub fn share(&self) -> PgLwLockShareGuard { - unsafe { self.inner.get().as_ref().unwrap().share() } + unsafe { + let shared = self.inner.get().read().as_ref().expect("PgLwLock was not initialized"); + pg_sys::LWLockAcquire((*shared).lock_ptr, pg_sys::LWLockMode::LW_SHARED); + PgLwLockShareGuard { data: &*(*shared).data.get(), lock: (*shared).lock_ptr } + } } /// Obtain an exclusive lock (which comes with `&mut T` access) pub fn exclusive(&self) -> PgLwLockExclusiveGuard { - unsafe { self.inner.get().as_ref().unwrap().exclusive() } + unsafe { + let shared = self.inner.get().read().as_ref().expect("PgLwLock was not initialized"); + pg_sys::LWLockAcquire((*shared).lock_ptr, pg_sys::LWLockMode::LW_EXCLUSIVE); + PgLwLockExclusiveGuard { data: &mut *(*shared).data.get(), lock: (*shared).lock_ptr } + } } /// Attach an empty PgLwLock lock to a LWLock, and wrap T /// SAFETY: Must only be called from inside the Postgres shared memory init hook - pub unsafe fn attach(&self, value: *mut T) { - *self.inner.get() = PgLwLockInner::::new(self.get_name(), value); + pub unsafe fn attach(&self, value: *mut PgLwLockShared) { + *self.inner.get() = value; } } -pub struct PgLwLockInner { - lock_ptr: *mut pg_sys::LWLock, - data: *mut T, -} - -impl fmt::Debug for PgLwLockInner { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("PgLwLockInner").finish() - } -} - -impl<'a, T> PgLwLockInner { - fn new(name: &'static str, data: *mut T) -> Self { - unsafe { - let lock = alloc::ffi::CString::new(name).expect("CString::new failed"); - PgLwLockInner { - lock_ptr: &mut (*pg_sys::GetNamedLWLockTranche(lock.as_ptr())).lock, - data, - } - } - } - - const fn empty() -> Self { - PgLwLockInner { lock_ptr: std::ptr::null_mut(), data: std::ptr::null_mut() } - } - - fn share(&self) -> PgLwLockShareGuard { - if self.lock_ptr.is_null() { - panic!("PgLwLock is not initialized"); - } - unsafe { - pg_sys::LWLockAcquire(self.lock_ptr, pg_sys::LWLockMode::LW_SHARED); - - PgLwLockShareGuard { data: self.data.as_ref().unwrap(), lock: self.lock_ptr } - } - } - - fn exclusive(&self) -> PgLwLockExclusiveGuard { - if self.lock_ptr.is_null() { - panic!("PgLwLock is not initialized"); - } - unsafe { - pg_sys::LWLockAcquire(self.lock_ptr, pg_sys::LWLockMode::LW_EXCLUSIVE); - - PgLwLockExclusiveGuard { data: self.data.as_mut().unwrap(), lock: self.lock_ptr } - } - } +#[repr(C)] +pub struct PgLwLockShared { + pub data: UnsafeCell, + pub lock_ptr: *mut pg_sys::LWLock, } pub struct PgLwLockShareGuard<'a, T> { diff --git a/pgrx/src/shmem.rs b/pgrx/src/shmem.rs index 9093f60cfe..877c4f9040 100644 --- a/pgrx/src/shmem.rs +++ b/pgrx/src/shmem.rs @@ -10,8 +10,8 @@ #![deny(unsafe_op_in_unsafe_fn)] use crate::lwlock::*; use crate::{pg_sys, PgAtomic}; +use std::cell::UnsafeCell; use std::hash::Hash; -use uuid::Uuid; /// Custom types that want to participate in shared memory must implement this marker trait pub unsafe trait PGRXSharedMemory {} @@ -38,10 +38,10 @@ pub unsafe trait PGRXSharedMemory {} /// use pgrx::{PgAtomic, PgLwLock, pg_shmem_init, PgSharedMemoryInitialization}; /// /// // primitive types must be protected behind a `PgLwLock` -/// static PRIMITIVE: PgLwLock = PgLwLock::new(); +/// static PRIMITIVE: PgLwLock = PgLwLock::new(c"primitive"); /// /// // Rust atomics can be used without locks, wrapped in a `PgAtomic` -/// static ATOMIC: PgAtomic = PgAtomic::new(); +/// static ATOMIC: PgAtomic = PgAtomic::new(c"atomic"); /// /// #[pg_guard] /// pub extern "C" fn _PG_init() { @@ -162,9 +162,8 @@ impl PgSharedMem { /// Must be run from PG_init, use for types which are guarded by a LWLock pub fn pg_init_locked(lock: &PgLwLock) { unsafe { - let lock = alloc::ffi::CString::new(lock.get_name()).expect("CString::new failed"); - pg_sys::RequestAddinShmemSpace(std::mem::size_of::()); - pg_sys::RequestNamedLWLockTranche(lock.as_ptr(), 1); + pg_sys::RequestAddinShmemSpace(std::mem::size_of::>()); + pg_sys::RequestNamedLWLockTranche(lock.name().as_ptr(), 1); } } @@ -178,18 +177,24 @@ impl PgSharedMem { /// Must be run from the shared memory init hook, use for types which are guarded by a `LWLock` /// SAFETY: Must only be called from inside the Postgres shared memory init hook pub unsafe fn shmem_init_locked(lock: &PgLwLock) { - let mut found = false; unsafe { - let shm_name = alloc::ffi::CString::new(lock.get_name()).expect("CString::new failed"); - let addin_shmem_init_lock: *mut pg_sys::LWLock = - &mut (*pg_sys::MainLWLockArray.add(21)).lock; + let shm_name = lock.name(); + let addin_shmem_init_lock = &raw mut (*pg_sys::MainLWLockArray.add(21)).lock; pg_sys::LWLockAcquire(addin_shmem_init_lock, pg_sys::LWLockMode::LW_EXCLUSIVE); - let fv_shmem = - pg_sys::ShmemInitStruct(shm_name.into_raw(), std::mem::size_of::(), &mut found) - as *mut T; - - std::ptr::write(fv_shmem, ::default()); + let mut found = false; + let fv_shmem = pg_sys::ShmemInitStruct( + shm_name.as_ptr(), + std::mem::size_of::>(), + &mut found, + ) + .cast::>(); + if !found { + fv_shmem.write(PgLwLockShared { + data: UnsafeCell::new(T::default()), + lock_ptr: &raw mut (*pg_sys::GetNamedLWLockTranche(shm_name.as_ptr())).lock, + }); + } lock.attach(fv_shmem); pg_sys::LWLockRelease(addin_shmem_init_lock); @@ -200,21 +205,19 @@ impl PgSharedMem { /// SAFETY: Must only be called from inside the Postgres shared memory init hook pub unsafe fn shmem_init_atomic(atomic: &PgAtomic) { unsafe { - let shm_name = alloc::ffi::CString::new(Uuid::new_v4().to_string()) - .expect("CString::new() failed"); - - let addin_shmem_init_lock: *mut pg_sys::LWLock = - &mut (*pg_sys::MainLWLockArray.add(21)).lock; + let shm_name = atomic.name(); + let addin_shmem_init_lock = &raw mut (*pg_sys::MainLWLockArray.add(21)).lock; + pg_sys::LWLockAcquire(addin_shmem_init_lock, pg_sys::LWLockMode::LW_EXCLUSIVE); let mut found = false; - pg_sys::LWLockAcquire(addin_shmem_init_lock, pg_sys::LWLockMode::LW_EXCLUSIVE); let fv_shmem = - pg_sys::ShmemInitStruct(shm_name.into_raw(), std::mem::size_of::(), &mut found) - as *mut T; + pg_sys::ShmemInitStruct(shm_name.as_ptr(), std::mem::size_of::(), &mut found) + .cast::(); + if !found { + fv_shmem.write(T::default()); + } atomic.attach(fv_shmem); - let atomic = T::default(); - std::ptr::copy(&atomic, fv_shmem, 1); pg_sys::LWLockRelease(addin_shmem_init_lock); } } From ccc297199803fca4f6db36f8e2b97aa3afac85d7 Mon Sep 17 00:00:00 2001 From: usamoi Date: Fri, 1 Nov 2024 21:06:06 +0800 Subject: [PATCH 3/8] fix runas.yml --- .github/workflows/runas.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/runas.yml b/.github/workflows/runas.yml index b927108878..5279338552 100644 --- a/.github/workflows/runas.yml +++ b/.github/workflows/runas.yml @@ -93,4 +93,6 @@ jobs: run: cargo pgrx init --pg14=$(which pg_config) - name: Test cargo pgrx test --runas - run: cd pgrx-examples/arrays && cargo pgrx test pg14 --runas postgres --pgdata=/tmp/pgdata + run: | + echo 0 | sudo tee /proc/sys/fs/protected_fifos + cd pgrx-examples/arrays && cargo pgrx test pg14 --runas postgres --pgdata=/tmp/pgdata From fd4ea555dfa4950e00f324e08c9da2c14a742952 Mon Sep 17 00:00:00 2001 From: usamoi Date: Sun, 3 Nov 2024 01:06:48 +0800 Subject: [PATCH 4/8] add Windows CI --- .github/workflows/tests.yml | 75 +++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f9e16464c2..4fc15aaa6b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -579,3 +579,78 @@ jobs: - name: Stop sccache server run: sccache --stop-server || true + + build_windows: + name: Windows build & test + needs: lintck + runs-on: ${{ matrix.os }} + if: "!contains(github.event.head_commit.message, 'nogha')" + env: + RUSTC_WRAPPER: sccache + SCCACHE_DIR: C:\Users\runneradmin\sccache + SCCACHE_IDLE_TIMEOUT: 0 + PG_VER: ${{ matrix.postgresql }} + + strategy: + matrix: + os: [ "windows-2022" ] + postgresql: [ 13, 17 ] + + steps: + - uses: actions/checkout@v4 + + - name: Set up prerequisites and environment + run: | + Write-Output "" + + echo "----- Install sccache -----" + Invoke-WebRequest -Uri "https://github.com/mozilla/sccache/releases/download/v0.5.4/sccache-v0.5.4-x86_64-pc-windows-msvc.tar.gz" -OutFile "sccache.tar.gz" + tar -xzvf sccache.tar.gz + Move-Item -Force "sccache-v0.5.4-x86_64-pc-windows-msvc\sccache.exe" -Destination "C:\Windows\System32" + New-Item -ItemType Directory -Force -Path $env:SCCACHE_DIR | Out-Null + sccache --version + + rustup update + + Write-Output "----- Outputting env -----" + Get-ChildItem Env: + Write-Output "" + + + - name: Cache sccache directory + uses: actions/cache@v4 + continue-on-error: false + with: + path: C:\Users\runneradmin\sccache + key: pgrx-sccache-${{matrix.os}}-${{ hashFiles('**/Cargo.lock', '.github/workflows/tests.yml', '.cargo/config.toml') }} + + - name: Cache cargo directory + uses: actions/cache@v4 + with: + path: | + ~/.cargo + key: pgrx-cargo-${{matrix.os}}-tests-${{ hashFiles('**/Cargo.lock', '.github/workflows/tests.yml', '.cargo/config.toml') }} + + - name: Start sccache server + run: sccache --start-server + + - name: Print sccache stats + run: sccache --show-stats + + - name: Install cargo-pgrx + run: cargo install --path cargo-pgrx/ --debug --force + + - name: Print sccache stats + run: sccache --show-stats + + - name: Run 'cargo pgrx init' + run: cargo pgrx init --pg$env:PG_VER=download + + - name: Run tests + run: cargo test --all --no-default-features --features "pg$env:PG_VER pg_test cshim proptest" --all-targets + + - name: Print sccache stats + run: sccache --show-stats + + - name: Stop sccache server + run: sccache --stop-server || true From 6760f11806bb0127222c6e68dcf169d031efd3e4 Mon Sep 17 00:00:00 2001 From: usamoi Date: Sun, 3 Nov 2024 02:23:10 +0800 Subject: [PATCH 5/8] workaround strange linker errors for PostgreSQL 14 on Windows --- pgrx-bindgen/src/build.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pgrx-bindgen/src/build.rs b/pgrx-bindgen/src/build.rs index 233266aa02..eb19001b55 100644 --- a/pgrx-bindgen/src/build.rs +++ b/pgrx-bindgen/src/build.rs @@ -897,6 +897,8 @@ fn add_blocklists(bind: bindgen::Builder) -> bindgen::Builder { .blocklist_function("varsize_any") // it's defined twice on Windows, so use PGERROR instead .blocklist_item("ERROR") + // it causes strange linker errors for PostgreSQL 14 on Windows + .blocklist_function("IsQueryIdEnabled") } fn add_allowlists<'a>( From 91dd26f7351198adc775379534186d802607c496 Mon Sep 17 00:00:00 2001 From: usamoi Date: Mon, 24 Feb 2025 18:32:41 +0800 Subject: [PATCH 6/8] use std::env::consts, instead of cfg and hardcored constants --- cargo-pgrx/src/command/install.rs | 29 +++++++++-------------------- pgrx-pg-config/src/cargo.rs | 12 ++---------- pgrx-pg-config/src/lib.rs | 31 +++++++------------------------ 3 files changed, 18 insertions(+), 54 deletions(-) diff --git a/cargo-pgrx/src/command/install.rs b/cargo-pgrx/src/command/install.rs index 4f2ae93dc8..07c63a7c1e 100644 --- a/cargo-pgrx/src/command/install.rs +++ b/cargo-pgrx/src/command/install.rs @@ -184,16 +184,12 @@ pub(crate) fn install_extension( }; // Since Postgres 16, the shared library extension on macOS is `dylib`, not `so`. // Ref https://github.com/postgres/postgres/commit/b55f62abb2c2e07dfae99e19a2b3d7ca9e58dc1a - let so_ext = 'e: { - if cfg!(target_os = "macos") { - break 'e if pg_config.major_version().unwrap() >= 16 { "dylib" } else { "so" }; - } - if cfg!(target_os = "windows") { - break 'e "dll"; - } - "so" + let so_suffix = if cfg!(target_os = "macos") && pg_config.major_version().unwrap() < 16 { + ".so" + } else { + std::env::consts::DLL_SUFFIX }; - let filename = format!("{so_name}.{so_ext}"); + let filename = format!("{so_name}{so_suffix}"); let dest = pkglibdir.join(filename); @@ -408,18 +404,11 @@ pub(crate) fn find_library_file( manifest: &Manifest, build_command_messages: &Vec, ) -> eyre::Result { + use std::env::consts::{DLL_EXTENSION, DLL_SUFFIX}; + // cargo sometimes decides to change whether targets are kebab-case or snake_case in metadata, // so normalize away the difference let target_name = manifest.target_name()?.replace('-', "_"); - let so_ext = 'so_ext: { - if cfg!(target_os = "macos") { - break 'so_ext "dylib"; - } - if cfg!(target_os = "windows") { - break 'so_ext "dll"; - } - "so" - }; // no hard and fast rule for the lib.so output filename exists, so we implement this routine // which is essentially a cope for cargo's disinterest in writing down any docs so far. @@ -437,11 +426,11 @@ pub(crate) fn find_library_file( artifact .filenames .iter() - .find(|filename| filename.extension() == Some(so_ext)) + .find(|filename| filename.extension() == Some(DLL_EXTENSION)) .map(|filename| filename.to_string()) }) .ok_or_else(|| { - eyre!("Could not get shared object file `{target_name}.{so_ext}` from Cargo output.") + eyre!("Could not get shared object file `{target_name}{DLL_SUFFIX}` from Cargo output.") })?; let library_file_path = PathBuf::from(library_file); diff --git a/pgrx-pg-config/src/cargo.rs b/pgrx-pg-config/src/cargo.rs index 01d9faede3..9992b2af29 100644 --- a/pgrx-pg-config/src/cargo.rs +++ b/pgrx-pg-config/src/cargo.rs @@ -88,17 +88,9 @@ impl PgrxManifestExt for Manifest { } fn lib_filename(&self) -> eyre::Result { + use std::env::consts::{DLL_PREFIX, DLL_SUFFIX}; let lib_name = &self.lib_name()?; - let (prefix, extension) = 'pe: { - if cfg!(target_os = "macos") { - break 'pe ("lib", "dylib"); - } - if cfg!(target_os = "windows") { - break 'pe ("", "dll"); - } - ("lib", "so") - }; - Ok(format!("{prefix}{}.{}", lib_name.replace('-', "_"), extension)) + Ok(format!("{DLL_PREFIX}{}{DLL_SUFFIX}", lib_name.replace('-', "_"))) } } diff --git a/pgrx-pg-config/src/lib.rs b/pgrx-pg-config/src/lib.rs index 4048be4959..d4bd2d2124 100644 --- a/pgrx-pg-config/src/lib.rs +++ b/pgrx-pg-config/src/lib.rs @@ -12,6 +12,7 @@ use eyre::{eyre, WrapErr}; use owo_colors::OwoColorize; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, HashMap}; +use std::env::consts::EXE_SUFFIX; use std::ffi::OsString; use std::fmt::{self, Debug, Display, Formatter}; use std::io::ErrorKind; @@ -334,55 +335,37 @@ impl PgConfig { pub fn postmaster_path(&self) -> eyre::Result { let mut path = self.bin_dir()?; - #[cfg(not(target_os = "windows"))] - path.push("postgres"); - #[cfg(target_os = "windows")] - path.push("postgres.exe"); + path.push(format!("postgres{EXE_SUFFIX}")); Ok(path) } pub fn initdb_path(&self) -> eyre::Result { let mut path = self.bin_dir()?; - #[cfg(not(target_os = "windows"))] - path.push("initdb"); - #[cfg(target_os = "windows")] - path.push("initdb.exe"); + path.push(format!("initdb{EXE_SUFFIX}")); Ok(path) } pub fn createdb_path(&self) -> eyre::Result { let mut path = self.bin_dir()?; - #[cfg(not(target_os = "windows"))] - path.push("createdb"); - #[cfg(target_os = "windows")] - path.push("createdb.exe"); + path.push(format!("createdb{EXE_SUFFIX}")); Ok(path) } pub fn dropdb_path(&self) -> eyre::Result { let mut path = self.bin_dir()?; - #[cfg(not(target_os = "windows"))] - path.push("dropdb"); - #[cfg(target_os = "windows")] - path.push("dropdb.exe"); + path.push(format!("dropdb{EXE_SUFFIX}")); Ok(path) } pub fn pg_ctl_path(&self) -> eyre::Result { let mut path = self.bin_dir()?; - #[cfg(not(target_os = "windows"))] - path.push("pg_ctl"); - #[cfg(target_os = "windows")] - path.push("pg_ctl.exe"); + path.push(format!("pg_ctl{EXE_SUFFIX}")); Ok(path) } pub fn psql_path(&self) -> eyre::Result { let mut path = self.bin_dir()?; - #[cfg(not(target_os = "windows"))] - path.push("psql"); - #[cfg(target_os = "windows")] - path.push("psql.exe"); + path.push(format!("psql{EXE_SUFFIX}")); Ok(path) } From 851fa23c5b32f51e84a2d149e2ca9360d0effebd Mon Sep 17 00:00:00 2001 From: usamoi Date: Tue, 25 Feb 2025 18:59:33 +0800 Subject: [PATCH 7/8] switch to C-unwind --- README.md | 2 +- cargo-pgrx/src/templates/bgworker_lib_rs | 4 +- pgrx-bindgen/src/build.rs | 3 +- pgrx-examples/bad_ideas/src/lib.rs | 4 +- pgrx-examples/bgworker/src/lib.rs | 4 +- pgrx-examples/shmem/src/lib.rs | 2 +- pgrx-examples/wal_decoder/src/lib.rs | 12 ++--- pgrx-macros/src/lib.rs | 6 +-- pgrx-pg-sys/src/cshim.rs | 2 +- pgrx-pg-sys/src/port.rs | 32 ++++++------ pgrx-pg-sys/src/submodules/ffi.rs | 10 ++-- pgrx-pg-sys/src/submodules/panic.rs | 15 +++--- pgrx-sql-entity-graph/src/finfo.rs | 2 +- pgrx-tests/src/tests/bgworker_tests.rs | 6 +-- pgrx-tests/src/tests/pg_guard_tests.rs | 6 +-- pgrx-tests/src/tests/pg_try_tests.rs | 2 +- .../src/tests/pgrx_module_qualification.rs | 2 +- pgrx-tests/src/tests/shmem_tests.rs | 2 +- pgrx/src/bgworkers.rs | 22 ++++---- pgrx/src/callbacks.rs | 4 +- pgrx/src/fcinfo.rs | 4 +- pgrx/src/hooks.rs | 52 ++++++++++--------- pgrx/src/lib.rs | 2 +- pgrx/src/memcxt.rs | 2 +- pgrx/src/shmem.rs | 14 ++--- 25 files changed, 109 insertions(+), 107 deletions(-) diff --git a/README.md b/README.md index 7bbdcd7b11..97d4358ad0 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ + Safe access to Postgres' `MemoryContext` system via `pgrx::PgMemoryContexts` + Executor/planner/transaction/subtransaction hooks + Safely use Postgres-provided pointers with `pgrx::PgBox` (akin to `alloc::boxed::Box`) - + `#[pg_guard]` proc-macro for guarding `extern "C"` Rust functions that need to be passed into Postgres + + `#[pg_guard]` proc-macro for guarding `extern "C-unwind"` Rust functions that need to be passed into Postgres + Access Postgres' logging system through `eprintln!`-like macros + Direct `unsafe` access to large parts of Postgres internals via the `pgrx::pg_sys` module + New features added regularly! diff --git a/cargo-pgrx/src/templates/bgworker_lib_rs b/cargo-pgrx/src/templates/bgworker_lib_rs index 60e823ff6d..f96c9553cb 100644 --- a/cargo-pgrx/src/templates/bgworker_lib_rs +++ b/cargo-pgrx/src/templates/bgworker_lib_rs @@ -21,7 +21,7 @@ this background worker #[allow(non_snake_case)] #[pg_guard] -pub extern "C" fn _PG_init() {{ +pub extern "C-unwind" fn _PG_init() {{ BackgroundWorkerBuilder::new("{name}") .set_function("background_worker_main") .set_library("{name}") @@ -32,7 +32,7 @@ pub extern "C" fn _PG_init() {{ #[pg_guard] #[no_mangle] -pub extern "C" fn background_worker_main(arg: pg_sys::Datum) {{ +pub extern "C-unwind" fn background_worker_main(arg: pg_sys::Datum) {{ let arg = unsafe {{ i32::from_datum(arg, false) }}; // these are the signals we want to receive. If we don't attach the SIGTERM handler, then diff --git a/pgrx-bindgen/src/build.rs b/pgrx-bindgen/src/build.rs index eb19001b55..dc19635b0e 100644 --- a/pgrx-bindgen/src/build.rs +++ b/pgrx-bindgen/src/build.rs @@ -829,6 +829,7 @@ fn run_bindgen( .wrap_static_fns(enable_cshim) .wrap_static_fns_path(out_path.join("pgrx-cshim-static")) .wrap_static_fns_suffix("__pgrx_cshim") + .override_abi(bindgen::Abi::CUnwind, ".*") .generate() .wrap_err_with(|| format!("Unable to generate bindings for pg{major_version}"))?; let mut binding_str = bindings.to_string(); @@ -866,7 +867,7 @@ fn add_blocklists(bind: bindgen::Builder) -> bindgen::Builder { .blocklist_var("CONFIGURE_ARGS") // configuration during build is hopefully irrelevant .blocklist_var("_*(?:HAVE|have)_.*") // header tracking metadata .blocklist_var("_[A-Z_]+_H") // more header metadata - // It's used by explict `extern "C"` + // It's used by explict `extern "C-unwind"` .blocklist_function("pg_re_throw") .blocklist_function("err(start|code|msg|detail|context_msg|hint|finish)") // These functions are already ported in Rust diff --git a/pgrx-examples/bad_ideas/src/lib.rs b/pgrx-examples/bad_ideas/src/lib.rs index 4ee7550b6c..268811dec0 100644 --- a/pgrx-examples/bad_ideas/src/lib.rs +++ b/pgrx-examples/bad_ideas/src/lib.rs @@ -114,9 +114,9 @@ fn random_abort() { } #[pg_guard] -pub unsafe extern "C" fn _PG_init() { +pub unsafe extern "C-unwind" fn _PG_init() { #[pg_guard] - extern "C" fn random_abort_callback( + extern "C-unwind" fn random_abort_callback( event: pg_sys::XactEvent::Type, _arg: *mut std::os::raw::c_void, ) { diff --git a/pgrx-examples/bgworker/src/lib.rs b/pgrx-examples/bgworker/src/lib.rs index b71d9accd6..d8e82a155d 100644 --- a/pgrx-examples/bgworker/src/lib.rs +++ b/pgrx-examples/bgworker/src/lib.rs @@ -29,7 +29,7 @@ use std::time::Duration; ::pgrx::pg_module_magic!(); #[pg_guard] -pub extern "C" fn _PG_init() { +pub extern "C-unwind" fn _PG_init() { BackgroundWorkerBuilder::new("Background Worker Example") .set_function("background_worker_main") .set_library("bgworker") @@ -40,7 +40,7 @@ pub extern "C" fn _PG_init() { #[pg_guard] #[no_mangle] -pub extern "C" fn background_worker_main(arg: pg_sys::Datum) { +pub extern "C-unwind" fn background_worker_main(arg: pg_sys::Datum) { let arg = unsafe { i32::from_polymorphic_datum(arg, false, pg_sys::INT4OID) }; // these are the signals we want to receive. If we don't attach the SIGTERM handler, then diff --git a/pgrx-examples/shmem/src/lib.rs b/pgrx-examples/shmem/src/lib.rs index 2a421f51ac..56f7b9d74e 100644 --- a/pgrx-examples/shmem/src/lib.rs +++ b/pgrx-examples/shmem/src/lib.rs @@ -37,7 +37,7 @@ static PRIMITIVE: PgLwLock = PgLwLock::new(c"shmem_primtive"); static ATOMIC: PgAtomic = PgAtomic::new(c"shmem_atomic"); #[pg_guard] -pub extern "C" fn _PG_init() { +pub extern "C-unwind" fn _PG_init() { pg_shmem_init!(DEQUE); pg_shmem_init!(VEC); pg_shmem_init!(HASH); diff --git a/pgrx-examples/wal_decoder/src/lib.rs b/pgrx-examples/wal_decoder/src/lib.rs index f4d6693b55..20a1b2693c 100644 --- a/pgrx-examples/wal_decoder/src/lib.rs +++ b/pgrx-examples/wal_decoder/src/lib.rs @@ -191,7 +191,7 @@ impl Serialize for Tuple { #[allow(non_snake_case)] #[no_mangle] #[pg_guard] -pub unsafe extern "C" fn _PG_output_plugin_init(cb_ptr: *mut pg_sys::OutputPluginCallbacks) { +pub unsafe extern "C-unwind" fn _PG_output_plugin_init(cb_ptr: *mut pg_sys::OutputPluginCallbacks) { let mut callbacks = unsafe { PgBox::from_pg(cb_ptr) }; callbacks.startup_cb = Some(pg_decode_startup); callbacks.begin_cb = Some(pg_decode_begin_txn); @@ -209,7 +209,7 @@ pub unsafe extern "C" fn _PG_output_plugin_init(cb_ptr: *mut pg_sys::OutputPlugi // #[pg_guard] -unsafe extern "C" fn pg_decode_startup( +unsafe extern "C-unwind" fn pg_decode_startup( ctx_ptr: *mut pg_sys::LogicalDecodingContext, options_ptr: *mut pg_sys::OutputPluginOptions, _is_init: bool, @@ -229,7 +229,7 @@ unsafe extern "C" fn pg_decode_startup( } #[pg_guard] -unsafe extern "C" fn pg_decode_begin_txn( +unsafe extern "C-unwind" fn pg_decode_begin_txn( ctx_ptr: *mut pg_sys::LogicalDecodingContext, _txn_ptr: *mut pg_sys::ReorderBufferTXN, ) { @@ -240,7 +240,7 @@ unsafe extern "C" fn pg_decode_begin_txn( } #[pg_guard] -unsafe extern "C" fn pg_decode_commit_txn( +unsafe extern "C-unwind" fn pg_decode_commit_txn( ctx_ptr: *mut pg_sys::LogicalDecodingContext, txn_ptr: *mut pg_sys::ReorderBufferTXN, _commit_lsn: pg_sys::XLogRecPtr, @@ -252,7 +252,7 @@ unsafe extern "C" fn pg_decode_commit_txn( } #[pg_guard] -unsafe extern "C" fn pg_decode_change( +unsafe extern "C-unwind" fn pg_decode_change( ctx_ptr: *mut pg_sys::LogicalDecodingContext, _txn_ptr: *mut pg_sys::ReorderBufferTXN, relation: pg_sys::Relation, @@ -269,7 +269,7 @@ unsafe extern "C" fn pg_decode_change( } #[pg_guard] -unsafe extern "C" fn pg_decode_shutdown(ctx_ptr: *mut pg_sys::LogicalDecodingContext) { +unsafe extern "C-unwind" fn pg_decode_shutdown(ctx_ptr: *mut pg_sys::LogicalDecodingContext) { let layout = Layout::new::(); let ctx = unsafe { PgBox::from_pg(ctx_ptr) }; unsafe { dealloc(ctx.output_plugin_private as *mut u8, layout) }; diff --git a/pgrx-macros/src/lib.rs b/pgrx-macros/src/lib.rs index 73cafcab3b..1c6a2465f2 100644 --- a/pgrx-macros/src/lib.rs +++ b/pgrx-macros/src/lib.rs @@ -27,7 +27,7 @@ use sql_gen::{ mod operators; mod rewriter; -/// Declare a function as `#[pg_guard]` to indicate that it is called from a Postgres `extern "C"` +/// Declare a function as `#[pg_guard]` to indicate that it is called from a Postgres `extern "C-unwind"` /// function so that Rust `panic!()`s (and Postgres `elog(ERROR)`s) will be properly handled by `pgrx` #[proc_macro_attribute] pub fn pg_guard(_attr: TokenStream, item: TokenStream) -> TokenStream { @@ -35,7 +35,7 @@ pub fn pg_guard(_attr: TokenStream, item: TokenStream) -> TokenStream { let ast = parse_macro_input!(item as syn::Item); let res = match ast { - // this is for processing the members of extern "C" { } blocks + // this is for processing the members of extern "C-unwind" { } blocks // functions inside the block get wrapped as public, top-level unsafe functions that are not "extern" Item::ForeignMod(block) => Ok(rewriter::extern_block(block)), @@ -43,7 +43,7 @@ pub fn pg_guard(_attr: TokenStream, item: TokenStream) -> TokenStream { Item::Fn(func) => rewriter::item_fn_without_rewrite(func), unknown => Err(syn::Error::new( unknown.span(), - "#[pg_guard] can only be applied to extern \"C\" blocks and top-level functions", + "#[pg_guard] can only be applied to extern \"C-unwind\" blocks and top-level functions", )), }; res.unwrap_or_else(|e| e.into_compile_error()).into() diff --git a/pgrx-pg-sys/src/cshim.rs b/pgrx-pg-sys/src/cshim.rs index 35126495e4..1f36fe5740 100644 --- a/pgrx-pg-sys/src/cshim.rs +++ b/pgrx-pg-sys/src/cshim.rs @@ -4,7 +4,7 @@ use crate as pg_sys; #[pgrx_macros::pg_guard] -extern "C" { +extern "C-unwind" { #[link_name = "SpinLockInit__pgrx_cshim"] pub fn SpinLockInit(lock: *mut pg_sys::slock_t); #[link_name = "SpinLockAcquire__pgrx_cshim"] diff --git a/pgrx-pg-sys/src/port.rs b/pgrx-pg-sys/src/port.rs index 05ef5ae467..e181d5b030 100644 --- a/pgrx-pg-sys/src/port.rs +++ b/pgrx-pg-sys/src/port.rs @@ -106,7 +106,7 @@ pub unsafe fn GetMemoryChunkContext(pointer: *mut std::os::raw::c_void) -> pg_sy #[cfg(any(feature = "pg16", feature = "pg17"))] { #[pgrx_macros::pg_guard] - extern "C" { + extern "C-unwind" { #[link_name = "GetMemoryChunkContext"] pub fn extern_fn(pointer: *mut std::os::raw::c_void) -> pg_sys::MemoryContext; } @@ -340,11 +340,11 @@ pub unsafe fn heap_tuple_get_struct(htup: super::HeapTuple) -> *mut T { // and we route people to the old symbols they were using before on later ones. #[cfg(any(feature = "pg12", feature = "pg13", feature = "pg14", feature = "pg15"))] #[::pgrx_macros::pg_guard] -extern "C" { +extern "C-unwind" { pub fn planstate_tree_walker( planstate: *mut super::PlanState, walker: ::core::option::Option< - unsafe extern "C" fn(*mut super::PlanState, *mut ::core::ffi::c_void) -> bool, + unsafe extern "C-unwind" fn(*mut super::PlanState, *mut ::core::ffi::c_void) -> bool, >, context: *mut ::core::ffi::c_void, ) -> bool; @@ -352,7 +352,7 @@ extern "C" { pub fn query_tree_walker( query: *mut super::Query, walker: ::core::option::Option< - unsafe extern "C" fn(*mut super::Node, *mut ::core::ffi::c_void) -> bool, + unsafe extern "C-unwind" fn(*mut super::Node, *mut ::core::ffi::c_void) -> bool, >, context: *mut ::core::ffi::c_void, flags: ::core::ffi::c_int, @@ -361,7 +361,7 @@ extern "C" { pub fn query_or_expression_tree_walker( node: *mut super::Node, walker: ::core::option::Option< - unsafe extern "C" fn(*mut super::Node, *mut ::core::ffi::c_void) -> bool, + unsafe extern "C-unwind" fn(*mut super::Node, *mut ::core::ffi::c_void) -> bool, >, context: *mut ::core::ffi::c_void, flags: ::core::ffi::c_int, @@ -370,7 +370,7 @@ extern "C" { pub fn range_table_entry_walker( rte: *mut super::RangeTblEntry, walker: ::core::option::Option< - unsafe extern "C" fn(*mut super::Node, *mut ::core::ffi::c_void) -> bool, + unsafe extern "C-unwind" fn(*mut super::Node, *mut ::core::ffi::c_void) -> bool, >, context: *mut ::core::ffi::c_void, flags: ::core::ffi::c_int, @@ -379,7 +379,7 @@ extern "C" { pub fn range_table_walker( rtable: *mut super::List, walker: ::core::option::Option< - unsafe extern "C" fn(*mut super::Node, *mut ::core::ffi::c_void) -> bool, + unsafe extern "C-unwind" fn(*mut super::Node, *mut ::core::ffi::c_void) -> bool, >, context: *mut ::core::ffi::c_void, flags: ::core::ffi::c_int, @@ -388,7 +388,7 @@ extern "C" { pub fn expression_tree_walker( node: *mut super::Node, walker: ::core::option::Option< - unsafe extern "C" fn(*mut super::Node, *mut ::core::ffi::c_void) -> bool, + unsafe extern "C-unwind" fn(*mut super::Node, *mut ::core::ffi::c_void) -> bool, >, context: *mut ::core::ffi::c_void, ) -> bool; @@ -396,7 +396,7 @@ extern "C" { pub fn raw_expression_tree_walker( node: *mut super::Node, walker: ::core::option::Option< - unsafe extern "C" fn(*mut super::Node, *mut ::core::ffi::c_void) -> bool, + unsafe extern "C-unwind" fn(*mut super::Node, *mut ::core::ffi::c_void) -> bool, >, context: *mut ::core::ffi::c_void, ) -> bool; @@ -406,7 +406,7 @@ extern "C" { pub unsafe fn planstate_tree_walker( planstate: *mut super::PlanState, walker: ::core::option::Option< - unsafe extern "C" fn(*mut super::PlanState, *mut ::core::ffi::c_void) -> bool, + unsafe extern "C-unwind" fn(*mut super::PlanState, *mut ::core::ffi::c_void) -> bool, >, context: *mut ::core::ffi::c_void, ) -> bool { @@ -417,7 +417,7 @@ pub unsafe fn planstate_tree_walker( pub unsafe fn query_tree_walker( query: *mut super::Query, walker: ::core::option::Option< - unsafe extern "C" fn(*mut super::Node, *mut ::core::ffi::c_void) -> bool, + unsafe extern "C-unwind" fn(*mut super::Node, *mut ::core::ffi::c_void) -> bool, >, context: *mut ::core::ffi::c_void, flags: ::core::ffi::c_int, @@ -429,7 +429,7 @@ pub unsafe fn query_tree_walker( pub unsafe fn query_or_expression_tree_walker( node: *mut super::Node, walker: ::core::option::Option< - unsafe extern "C" fn(*mut super::Node, *mut ::core::ffi::c_void) -> bool, + unsafe extern "C-unwind" fn(*mut super::Node, *mut ::core::ffi::c_void) -> bool, >, context: *mut ::core::ffi::c_void, flags: ::core::ffi::c_int, @@ -440,7 +440,7 @@ pub unsafe fn query_or_expression_tree_walker( #[cfg(any(feature = "pg16", feature = "pg17"))] pub unsafe fn expression_tree_walker( node: *mut crate::Node, - walker: Option bool>, + walker: Option bool>, context: *mut ::core::ffi::c_void, ) -> bool { crate::expression_tree_walker_impl(node, walker, context) @@ -450,7 +450,7 @@ pub unsafe fn expression_tree_walker( pub unsafe fn range_table_entry_walker( rte: *mut super::RangeTblEntry, walker: ::core::option::Option< - unsafe extern "C" fn(*mut super::Node, *mut ::core::ffi::c_void) -> bool, + unsafe extern "C-unwind" fn(*mut super::Node, *mut ::core::ffi::c_void) -> bool, >, context: *mut ::core::ffi::c_void, flags: ::core::ffi::c_int, @@ -462,7 +462,7 @@ pub unsafe fn range_table_entry_walker( pub unsafe fn range_table_walker( rtable: *mut super::List, walker: ::core::option::Option< - unsafe extern "C" fn(*mut super::Node, *mut ::core::ffi::c_void) -> bool, + unsafe extern "C-unwind" fn(*mut super::Node, *mut ::core::ffi::c_void) -> bool, >, context: *mut ::core::ffi::c_void, flags: ::core::ffi::c_int, @@ -473,7 +473,7 @@ pub unsafe fn range_table_walker( #[cfg(any(feature = "pg16", feature = "pg17"))] pub unsafe fn raw_expression_tree_walker( node: *mut crate::Node, - walker: Option bool>, + walker: Option bool>, context: *mut ::core::ffi::c_void, ) -> bool { crate::raw_expression_tree_walker_impl(node, walker, context) diff --git a/pgrx-pg-sys/src/submodules/ffi.rs b/pgrx-pg-sys/src/submodules/ffi.rs index 9aaa1a0c72..8fe33f405b 100644 --- a/pgrx-pg-sys/src/submodules/ffi.rs +++ b/pgrx-pg-sys/src/submodules/ffi.rs @@ -30,18 +30,18 @@ mod cee_scape { where F: for<'a> FnOnce(&'a SigJmpBufFields) -> c_int, { - extern "C" { + extern "C-unwind" { fn call_closure_with_sigsetjmp( savemask: c_int, closure_env_ptr: *mut c_void, - closure_code: extern "C" fn( + closure_code: extern "C-unwind" fn( jbuf: *const SigJmpBufFields, env_ptr: *mut c_void, ) -> c_int, ) -> c_int; } - extern "C" fn call_from_c_to_rust( + extern "C-unwind" fn call_from_c_to_rust( jbuf: *const SigJmpBufFields, closure_env_ptr: *mut c_void, ) -> c_int @@ -69,7 +69,7 @@ mod cee_scape { use cee_scape::{call_with_sigsetjmp, SigJmpBufFields}; /** -Given a closure that is assumed to be a wrapped Postgres `extern "C"` function, [pg_guard_ffi_boundary] +Given a closure that is assumed to be a wrapped Postgres `extern "C-unwind"` function, [pg_guard_ffi_boundary] works with the Postgres and C runtimes to create a "barrier" that allows Rust to catch Postgres errors (`elog(ERROR)`) while running the supplied closure. This is done for the sake of allowing Rust to run destructors before Postgres destroys the memory contexts that Rust-in-Postgres code may be enmeshed in. @@ -82,7 +82,7 @@ Wrapping the FFI into Postgres enables But only the first of these is considered paramount. At all times PGRX reserves the right to choose an implementation that achieves memory safety. -Currently, this function is used to protect **every** bindgen-generated Postgres `extern "C"` function. +Currently, this function is used to protect **every** bindgen-generated Postgres `extern "C-unwind"` function. Generally, the only time *you'll* need to use this function is when calling a Postgres-provided function pointer. diff --git a/pgrx-pg-sys/src/submodules/panic.rs b/pgrx-pg-sys/src/submodules/panic.rs index cdfc4be731..ab34a682fa 100644 --- a/pgrx-pg-sys/src/submodules/panic.rs +++ b/pgrx-pg-sys/src/submodules/panic.rs @@ -364,8 +364,8 @@ enum GuardAction { /// behind the `#[pg_guard]` and `#[pg_extern]` macros. Which means the function you'd like to guard /// is likely already guarded. /// -/// Where it does need to be used is as a wrapper around Rust `extern "C"` function pointers given -/// to Postgres, and the `#[pg_guard]` macro takes care of this for you. +/// Where it does need to be used is as a wrapper around Rust `extern "C-unwind"` function pointers +/// given to Postgres, and the `#[pg_guard]` macro takes care of this for you. /// /// In other words, this isn't the function you're looking for. /// @@ -381,8 +381,7 @@ enum GuardAction { /// objects have already been dropped. /// /// In practice, this should only ever be called at the top level of an -/// `extern "C" fn` (ideally `extern "C-unwind"`) implemented in -/// Rust. +/// `extern "C-unwind" fn` implemented in Rust. /// /// [trivially-deallocated stack frames](https://github.com/rust-lang/rfcs/blob/master/text/2945-c-unwind-abi.md#plain-old-frames) #[doc(hidden)] @@ -396,7 +395,7 @@ where GuardAction::Return(r) => r, GuardAction::ReThrow => { #[cfg_attr(target_os = "windows", link(name = "postgres"))] - extern "C" /* "C-unwind" */ { + extern "C-unwind" { fn pg_re_throw() -> !; } unsafe { @@ -511,7 +510,7 @@ fn do_ereport(ereport: ErrorReportWithLevel) { // #[cfg_attr(target_os = "windows", link(name = "postgres"))] - extern "C" { + extern "C-unwind" { fn errcode(sqlerrcode: ::std::os::raw::c_int) -> ::std::os::raw::c_int; fn errmsg(fmt: *const ::std::os::raw::c_char, ...) -> ::std::os::raw::c_int; fn errdetail(fmt: *const ::std::os::raw::c_char, ...) -> ::std::os::raw::c_int; @@ -527,7 +526,7 @@ fn do_ereport(ereport: ErrorReportWithLevel) { fn do_ereport_impl(ereport: ErrorReportWithLevel) { #[cfg_attr(target_os = "windows", link(name = "postgres"))] - extern "C" { + extern "C-unwind" { fn errstart(elevel: ::std::os::raw::c_int, domain: *const ::std::os::raw::c_char) -> bool; fn errfinish(filename: *const ::std::os::raw::c_char, lineno: ::std::os::raw::c_int, funcname: *const ::std::os::raw::c_char); } @@ -592,7 +591,7 @@ fn do_ereport(ereport: ErrorReportWithLevel) { fn do_ereport_impl(ereport: ErrorReportWithLevel) { #[cfg_attr(target_os = "windows", link(name = "postgres"))] - extern "C" { + extern "C-unwind" { fn errstart(elevel: ::std::os::raw::c_int, filename: *const ::std::os::raw::c_char, lineno: ::std::os::raw::c_int, funcname: *const ::std::os::raw::c_char, domain: *const ::std::os::raw::c_char) -> bool; fn errfinish(dummy: ::std::os::raw::c_int, ...); } diff --git a/pgrx-sql-entity-graph/src/finfo.rs b/pgrx-sql-entity-graph/src/finfo.rs index 51acedf002..b2663df7d5 100644 --- a/pgrx-sql-entity-graph/src/finfo.rs +++ b/pgrx-sql-entity-graph/src/finfo.rs @@ -33,7 +33,7 @@ pub fn finfo_v1_extern_c( let tokens = quote_spanned! { synthetic => #[no_mangle] #[doc(hidden)] - pub unsafe extern "C" fn #wrapper_symbol(#fcinfo: ::pgrx::pg_sys::FunctionCallInfo) -> ::pgrx::pg_sys::Datum { + pub unsafe extern "C-unwind" fn #wrapper_symbol(#fcinfo: ::pgrx::pg_sys::FunctionCallInfo) -> ::pgrx::pg_sys::Datum { #contents } }; diff --git a/pgrx-tests/src/tests/bgworker_tests.rs b/pgrx-tests/src/tests/bgworker_tests.rs index 92117bcf52..529ce99a0e 100644 --- a/pgrx-tests/src/tests/bgworker_tests.rs +++ b/pgrx-tests/src/tests/bgworker_tests.rs @@ -11,7 +11,7 @@ use pgrx::prelude::*; #[pg_guard] #[no_mangle] -pub extern "C" fn bgworker(arg: pg_sys::Datum) { +pub extern "C-unwind" fn bgworker(arg: pg_sys::Datum) { use pgrx::bgworkers::*; use std::time::Duration; BackgroundWorker::attach_signal_handlers(SignalWakeFlags::SIGHUP | SignalWakeFlags::SIGTERM); @@ -43,7 +43,7 @@ pub extern "C" fn bgworker(arg: pg_sys::Datum) { #[pg_guard] #[no_mangle] /// Here we test that `BackgroundWorker::transaction` can return data from the closure -pub extern "C" fn bgworker_return_value(arg: pg_sys::Datum) { +pub extern "C-unwind" fn bgworker_return_value(arg: pg_sys::Datum) { use pgrx::bgworkers::*; use std::time::Duration; BackgroundWorker::attach_signal_handlers(SignalWakeFlags::SIGHUP | SignalWakeFlags::SIGTERM); @@ -77,7 +77,7 @@ pub extern "C" fn bgworker_return_value(arg: pg_sys::Datum) { #[pg_guard] #[no_mangle] /// Simple background worker that waits to be terminated; used to test behaviour in case of worker slots exhaustion -pub extern "C" fn bgworker_sleep() { +pub extern "C-unwind" fn bgworker_sleep() { use pgrx::bgworkers::*; BackgroundWorker::attach_signal_handlers(SignalWakeFlags::SIGHUP | SignalWakeFlags::SIGTERM); while BackgroundWorker::wait_latch(None) {} diff --git a/pgrx-tests/src/tests/pg_guard_tests.rs b/pgrx-tests/src/tests/pg_guard_tests.rs index 26cd97d108..5412037e95 100644 --- a/pgrx-tests/src/tests/pg_guard_tests.rs +++ b/pgrx-tests/src/tests/pg_guard_tests.rs @@ -18,7 +18,7 @@ fn extern_func() -> bool { #[pg_guard] // Uncommenting the line below will make it fail to compile // #[no_mangle] -extern "C" fn extern_func_impl() -> bool { +extern "C-unwind" fn extern_func_impl() -> bool { true } @@ -26,7 +26,7 @@ extern "C" fn extern_func_impl() -> bool { // and [no_mangle] #[pg_guard] #[no_mangle] -extern "C" fn extern_func_impl_1() -> bool { +extern "C-unwind" fn extern_func_impl_1() -> bool { true } @@ -35,7 +35,7 @@ extern "C" fn extern_func_impl_1() -> bool { #[pg_guard] #[no_mangle] #[allow(unused_lifetimes)] -extern "C" fn extern_func_impl_2<'a>() -> bool { +extern "C-unwind" fn extern_func_impl_2<'a>() -> bool { true } diff --git a/pgrx-tests/src/tests/pg_try_tests.rs b/pgrx-tests/src/tests/pg_try_tests.rs index f75fd34500..7ebdd29b1d 100644 --- a/pgrx-tests/src/tests/pg_try_tests.rs +++ b/pgrx-tests/src/tests/pg_try_tests.rs @@ -24,7 +24,7 @@ fn crash() { } #[pg_guard] -extern "C" fn walker(_node: *mut pg_sys::Node, _void: *mut ::core::ffi::c_void) -> bool { +extern "C-unwind" fn walker(_node: *mut pg_sys::Node, _void: *mut ::core::ffi::c_void) -> bool { panic!("panic in walker"); } diff --git a/pgrx-tests/src/tests/pgrx_module_qualification.rs b/pgrx-tests/src/tests/pgrx_module_qualification.rs index aea1e98f11..e7b2a50d04 100644 --- a/pgrx-tests/src/tests/pgrx_module_qualification.rs +++ b/pgrx-tests/src/tests/pgrx_module_qualification.rs @@ -115,7 +115,7 @@ mod pgrx_modqual_tests { #[allow(dead_code)] #[pg_guard] - extern "C" fn extern_foo_func() {} + extern "C-unwind" fn extern_foo_func() {} #[pg_schema] mod foo_schema {} diff --git a/pgrx-tests/src/tests/shmem_tests.rs b/pgrx-tests/src/tests/shmem_tests.rs index 479fc1b9ee..17bfa99083 100644 --- a/pgrx-tests/src/tests/shmem_tests.rs +++ b/pgrx-tests/src/tests/shmem_tests.rs @@ -15,7 +15,7 @@ static ATOMIC: PgAtomic = PgAtomic::new(c"pgrx_tests_atomic"); static LWLOCK: PgLwLock = PgLwLock::new(c"pgrx_tests_lwlock"); #[pg_guard] -pub extern "C" fn _PG_init() { +pub extern "C-unwind" fn _PG_init() { // This ensures that this functionality works across PostgreSQL versions pg_shmem_init!(ATOMIC); pg_shmem_init!(LWLOCK); diff --git a/pgrx/src/bgworkers.rs b/pgrx/src/bgworkers.rs index a91490dbbb..b9efd506ab 100644 --- a/pgrx/src/bgworkers.rs +++ b/pgrx/src/bgworkers.rs @@ -20,7 +20,7 @@ use std::ptr::null_mut; use std::sync::atomic::{AtomicBool, Ordering}; use std::time::Duration; -pub static mut PREV_SHMEM_STARTUP_HOOK: Option = None; +pub static mut PREV_SHMEM_STARTUP_HOOK: Option = None; static GOT_SIGHUP: AtomicBool = AtomicBool::new(false); static GOT_SIGTERM: AtomicBool = AtomicBool::new(false); static GOT_SIGINT: AtomicBool = AtomicBool::new(false); @@ -256,23 +256,23 @@ impl BackgroundWorker { } } -unsafe extern "C" fn worker_spi_sighup(_signal_args: i32) { +unsafe extern "C-unwind" fn worker_spi_sighup(_signal_args: i32) { GOT_SIGHUP.store(true, Ordering::SeqCst); pg_sys::ProcessConfigFile(pg_sys::GucContext::PGC_SIGHUP); pg_sys::SetLatch(pg_sys::MyLatch); } -unsafe extern "C" fn worker_spi_sigterm(_signal_args: i32) { +unsafe extern "C-unwind" fn worker_spi_sigterm(_signal_args: i32) { GOT_SIGTERM.store(true, Ordering::SeqCst); pg_sys::SetLatch(pg_sys::MyLatch); } -unsafe extern "C" fn worker_spi_sigint(_signal_args: i32) { +unsafe extern "C-unwind" fn worker_spi_sigint(_signal_args: i32) { GOT_SIGINT.store(true, Ordering::SeqCst); pg_sys::SetLatch(pg_sys::MyLatch); } -unsafe extern "C" fn worker_spi_sigchld(_signal_args: i32) { +unsafe extern "C-unwind" fn worker_spi_sigchld(_signal_args: i32) { GOT_SIGCHLD.store(true, Ordering::SeqCst); pg_sys::SetLatch(pg_sys::MyLatch); } @@ -410,7 +410,7 @@ impl TerminatingDynamicBackgroundWorker { /// use pgrx::bgworkers::BackgroundWorkerBuilder; /// /// #[pg_guard] -/// pub extern "C" fn _PG_init() { +/// pub extern "C-unwind" fn _PG_init() { /// BackgroundWorkerBuilder::new("My Example BGWorker") /// .set_function("background_worker_main") /// .set_library("example") @@ -419,7 +419,7 @@ impl TerminatingDynamicBackgroundWorker { /// } /// /// #[pg_guard] -/// pub extern "C" fn background_worker_main(_arg: pg_sys::Datum) { +/// pub extern "C-unwind" fn background_worker_main(_arg: pg_sys::Datum) { /// // do bgworker stuff here /// } /// ``` @@ -434,7 +434,7 @@ pub struct BackgroundWorkerBuilder { bgw_main_arg: pg_sys::Datum, bgw_extra: String, bgw_notify_pid: pg_sys::pid_t, - shared_memory_startup_fn: Option, + shared_memory_startup_fn: Option, } impl BackgroundWorkerBuilder { @@ -470,7 +470,7 @@ impl BackgroundWorkerBuilder { /// /// `startup` allows specifying shared memory initialization startup hook. Ignored /// if [`BackgroundWorkerBuilder::load_dynamic`] is used. - pub fn enable_shmem_access(mut self, startup: Option) -> Self { + pub fn enable_shmem_access(mut self, startup: Option) -> Self { self.bgw_flags = self.bgw_flags | BGWflags::BGWORKER_SHMEM_ACCESS; self.shared_memory_startup_fn = startup; self @@ -514,7 +514,7 @@ impl BackgroundWorkerBuilder { /// process is started? /// /// The specified function **must** be: - /// - `extern "C"`, + /// - `extern "C-unwind"`, /// - guarded with `#[pg_guard]`, /// - take 1 argument of type `pgrx::pg_sys::Datum`, and /// - return "void" @@ -525,7 +525,7 @@ impl BackgroundWorkerBuilder { /// use pgrx::prelude::*; /// /// #[pg_guard] - /// pub extern "C" fn background_worker_main(_arg: pg_sys::Datum) { + /// pub extern "C-unwind" fn background_worker_main(_arg: pg_sys::Datum) { /// } /// ``` pub fn set_function(mut self, input: &str) -> Self { diff --git a/pgrx/src/callbacks.rs b/pgrx/src/callbacks.rs index e342726b39..d49becf656 100644 --- a/pgrx/src/callbacks.rs +++ b/pgrx/src/callbacks.rs @@ -164,7 +164,7 @@ where // internal function that we register as an XactCallback #[pg_guard] - unsafe extern "C" fn callback( + unsafe extern "C-unwind" fn callback( event: pg_sys::XactEvent::Type, _arg: *mut ::std::os::raw::c_void, ) { @@ -318,7 +318,7 @@ where static mut SUB_HOOKS: Option = None; #[pg_guard] - unsafe extern "C" fn callback( + unsafe extern "C-unwind" fn callback( event: pg_sys::SubXactEvent::Type, my_subid: pg_sys::SubTransactionId, parent_subid: pg_sys::SubTransactionId, diff --git a/pgrx/src/fcinfo.rs b/pgrx/src/fcinfo.rs index ce358e62e8..65f75d9907 100644 --- a/pgrx/src/fcinfo.rs +++ b/pgrx/src/fcinfo.rs @@ -313,7 +313,7 @@ pub unsafe fn direct_function_call( /// /// This function is unsafe as the function you're calling is also unsafe pub unsafe fn direct_pg_extern_function_call( - func: unsafe extern "C" fn(pg_sys::FunctionCallInfo) -> pg_sys::Datum, + func: unsafe extern "C-unwind" fn(pg_sys::FunctionCallInfo) -> pg_sys::Datum, args: &[Option], ) -> Option { direct_pg_extern_function_call_as_datum(func, args).and_then(|d| R::from_datum(d, false)) @@ -376,7 +376,7 @@ unsafe fn direct_function_call_as_datum_internal( /// /// This function is unsafe as the function you're calling is also unsafe pub unsafe fn direct_pg_extern_function_call_as_datum( - func: unsafe extern "C" fn(pg_sys::FunctionCallInfo) -> pg_sys::Datum, + func: unsafe extern "C-unwind" fn(pg_sys::FunctionCallInfo) -> pg_sys::Datum, args: &[Option], ) -> Option { direct_function_call_as_datum_internal(|fcinfo| func(fcinfo), args) diff --git a/pgrx/src/hooks.rs b/pgrx/src/hooks.rs index 102663747c..a9a12fdeda 100644 --- a/pgrx/src/hooks.rs +++ b/pgrx/src/hooks.rs @@ -246,7 +246,7 @@ pub unsafe fn register_hook(hook: &'static mut (dyn PgHooks)) { }); #[pg_guard] - unsafe extern "C" fn xact_callback(event: pg_sys::XactEvent::Type, _data: void_mut_ptr) { + unsafe extern "C-unwind" fn xact_callback(event: pg_sys::XactEvent::Type, _data: void_mut_ptr) { match event { pg_sys::XactEvent::XACT_EVENT_ABORT => HOOKS.as_mut().unwrap().current_hook.abort(), pg_sys::XactEvent::XACT_EVENT_PRE_COMMIT => { @@ -260,7 +260,7 @@ pub unsafe fn register_hook(hook: &'static mut (dyn PgHooks)) { } #[pg_guard] -unsafe extern "C" fn pgrx_executor_start(query_desc: *mut pg_sys::QueryDesc, eflags: i32) { +unsafe extern "C-unwind" fn pgrx_executor_start(query_desc: *mut pg_sys::QueryDesc, eflags: i32) { fn prev(query_desc: PgBox, eflags: i32) -> HookResult<()> { unsafe { (HOOKS.as_mut().unwrap().prev_executor_start_hook.as_ref().unwrap())( @@ -275,7 +275,7 @@ unsafe extern "C" fn pgrx_executor_start(query_desc: *mut pg_sys::QueryDesc, efl } #[pg_guard] -unsafe extern "C" fn pgrx_executor_run( +unsafe extern "C-unwind" fn pgrx_executor_run( query_desc: *mut pg_sys::QueryDesc, direction: pg_sys::ScanDirection::Type, count: u64, @@ -302,7 +302,7 @@ unsafe extern "C" fn pgrx_executor_run( } #[pg_guard] -unsafe extern "C" fn pgrx_executor_finish(query_desc: *mut pg_sys::QueryDesc) { +unsafe extern "C-unwind" fn pgrx_executor_finish(query_desc: *mut pg_sys::QueryDesc) { fn prev(query_desc: PgBox) -> HookResult<()> { unsafe { (HOOKS.as_mut().unwrap().prev_executor_finish_hook.as_ref().unwrap())( @@ -316,7 +316,7 @@ unsafe extern "C" fn pgrx_executor_finish(query_desc: *mut pg_sys::QueryDesc) { } #[pg_guard] -unsafe extern "C" fn pgrx_executor_end(query_desc: *mut pg_sys::QueryDesc) { +unsafe extern "C-unwind" fn pgrx_executor_end(query_desc: *mut pg_sys::QueryDesc) { fn prev(query_desc: PgBox) -> HookResult<()> { unsafe { (HOOKS.as_mut().unwrap().prev_executor_end_hook.as_ref().unwrap())(query_desc.into_pg()) @@ -329,7 +329,7 @@ unsafe extern "C" fn pgrx_executor_end(query_desc: *mut pg_sys::QueryDesc) { #[cfg(any(feature = "pg12", feature = "pg13", feature = "pg14", feature = "pg15"))] #[pg_guard] -unsafe extern "C" fn pgrx_executor_check_perms( +unsafe extern "C-unwind" fn pgrx_executor_check_perms( range_table: *mut pg_sys::List, ereport_on_violation: bool, ) -> bool { @@ -351,7 +351,7 @@ unsafe extern "C" fn pgrx_executor_check_perms( #[cfg(any(feature = "pg16", feature = "pg17"))] #[pg_guard] -unsafe extern "C" fn pgrx_executor_check_perms( +unsafe extern "C-unwind" fn pgrx_executor_check_perms( range_table: *mut pg_sys::List, rte_perm_infos: *mut pg_sys::List, ereport_on_violation: bool, @@ -381,7 +381,7 @@ unsafe extern "C" fn pgrx_executor_check_perms( #[cfg(any(feature = "pg12", feature = "pg13"))] #[pg_guard] -unsafe extern "C" fn pgrx_process_utility( +unsafe extern "C-unwind" fn pgrx_process_utility( pstmt: *mut pg_sys::PlannedStmt, query_string: *const ::std::os::raw::c_char, context: pg_sys::ProcessUtilityContext::Type, @@ -429,7 +429,7 @@ unsafe extern "C" fn pgrx_process_utility( } #[cfg(any(feature = "pg14", feature = "pg15", feature = "pg16", feature = "pg17"))] #[pg_guard] -unsafe extern "C" fn pgrx_process_utility( +unsafe extern "C-unwind" fn pgrx_process_utility( pstmt: *mut pg_sys::PlannedStmt, query_string: *const ::std::os::raw::c_char, read_only_tree: bool, @@ -480,7 +480,7 @@ unsafe extern "C" fn pgrx_process_utility( #[cfg(feature = "pg12")] #[pg_guard] -unsafe extern "C" fn pgrx_planner( +unsafe extern "C-unwind" fn pgrx_planner( parse: *mut pg_sys::Query, cursor_options: i32, bound_params: pg_sys::ParamListInfo, @@ -496,7 +496,7 @@ unsafe extern "C" fn pgrx_planner( feature = "pg17" ))] #[pg_guard] -unsafe extern "C" fn pgrx_planner( +unsafe extern "C-unwind" fn pgrx_planner( parse: *mut pg_sys::Query, query_string: *const ::std::os::raw::c_char, cursor_options: i32, @@ -506,7 +506,7 @@ unsafe extern "C" fn pgrx_planner( } #[pg_guard] -unsafe extern "C" fn pgrx_planner_impl( +unsafe extern "C-unwind" fn pgrx_planner_impl( parse: *mut pg_sys::Query, query_string: *const ::std::os::raw::c_char, cursor_options: i32, @@ -558,7 +558,7 @@ unsafe extern "C" fn pgrx_planner_impl( #[cfg(any(feature = "pg12", feature = "pg13"))] #[pg_guard] -unsafe extern "C" fn pgrx_post_parse_analyze( +unsafe extern "C-unwind" fn pgrx_post_parse_analyze( parse_state: *mut pg_sys::ParseState, query: *mut pg_sys::Query, ) { @@ -581,7 +581,7 @@ unsafe extern "C" fn pgrx_post_parse_analyze( #[cfg(any(feature = "pg14", feature = "pg15", feature = "pg16", feature = "pg17"))] #[pg_guard] -unsafe extern "C" fn pgrx_post_parse_analyze( +unsafe extern "C-unwind" fn pgrx_post_parse_analyze( parse_state: *mut pg_sys::ParseState, query: *mut pg_sys::Query, jumble_state: *mut JumbleState, @@ -612,7 +612,7 @@ unsafe extern "C" fn pgrx_post_parse_analyze( } #[pg_guard] -unsafe extern "C" fn pgrx_emit_log(error_data: *mut pg_sys::ErrorData) { +unsafe extern "C-unwind" fn pgrx_emit_log(error_data: *mut pg_sys::ErrorData) { fn prev(error_data: PgBox) -> HookResult<()> { HookResult::new(unsafe { match HOOKS.as_mut().unwrap().prev_emit_log_hook.as_ref() { @@ -627,7 +627,7 @@ unsafe extern "C" fn pgrx_emit_log(error_data: *mut pg_sys::ErrorData) { } #[pg_guard] -unsafe extern "C" fn pgrx_standard_executor_start_wrapper( +unsafe extern "C-unwind" fn pgrx_standard_executor_start_wrapper( query_desc: *mut pg_sys::QueryDesc, eflags: i32, ) { @@ -635,7 +635,7 @@ unsafe extern "C" fn pgrx_standard_executor_start_wrapper( } #[pg_guard] -unsafe extern "C" fn pgrx_standard_executor_run_wrapper( +unsafe extern "C-unwind" fn pgrx_standard_executor_run_wrapper( query_desc: *mut pg_sys::QueryDesc, direction: pg_sys::ScanDirection::Type, count: u64, @@ -645,18 +645,20 @@ unsafe extern "C" fn pgrx_standard_executor_run_wrapper( } #[pg_guard] -unsafe extern "C" fn pgrx_standard_executor_finish_wrapper(query_desc: *mut pg_sys::QueryDesc) { +unsafe extern "C-unwind" fn pgrx_standard_executor_finish_wrapper( + query_desc: *mut pg_sys::QueryDesc, +) { pg_sys::standard_ExecutorFinish(query_desc) } #[pg_guard] -unsafe extern "C" fn pgrx_standard_executor_end_wrapper(query_desc: *mut pg_sys::QueryDesc) { +unsafe extern "C-unwind" fn pgrx_standard_executor_end_wrapper(query_desc: *mut pg_sys::QueryDesc) { pg_sys::standard_ExecutorEnd(query_desc) } #[cfg(any(feature = "pg12", feature = "pg13", feature = "pg14", feature = "pg15"))] #[pg_guard] -unsafe extern "C" fn pgrx_standard_executor_check_perms_wrapper( +unsafe extern "C-unwind" fn pgrx_standard_executor_check_perms_wrapper( _range_table: *mut pg_sys::List, _ereport_on_violation: bool, ) -> bool { @@ -665,7 +667,7 @@ unsafe extern "C" fn pgrx_standard_executor_check_perms_wrapper( #[cfg(any(feature = "pg16", feature = "pg17"))] #[pg_guard] -unsafe extern "C" fn pgrx_standard_executor_check_perms_wrapper( +unsafe extern "C-unwind" fn pgrx_standard_executor_check_perms_wrapper( _range_table: *mut pg_sys::List, _rte_perm_infos: *mut pg_sys::List, _ereport_on_violation: bool, @@ -675,7 +677,7 @@ unsafe extern "C" fn pgrx_standard_executor_check_perms_wrapper( #[cfg(any(feature = "pg12", feature = "pg13"))] #[pg_guard] -unsafe extern "C" fn pgrx_standard_process_utility_wrapper( +unsafe extern "C-unwind" fn pgrx_standard_process_utility_wrapper( pstmt: *mut pg_sys::PlannedStmt, query_string: *const ::std::os::raw::c_char, context: pg_sys::ProcessUtilityContext::Type, @@ -697,7 +699,7 @@ unsafe extern "C" fn pgrx_standard_process_utility_wrapper( #[cfg(any(feature = "pg14", feature = "pg15", feature = "pg16", feature = "pg17"))] #[pg_guard] -unsafe extern "C" fn pgrx_standard_process_utility_wrapper( +unsafe extern "C-unwind" fn pgrx_standard_process_utility_wrapper( pstmt: *mut pg_sys::PlannedStmt, query_string: *const ::std::os::raw::c_char, read_only_tree: bool, @@ -721,7 +723,7 @@ unsafe extern "C" fn pgrx_standard_process_utility_wrapper( #[cfg(feature = "pg12")] #[pg_guard] -unsafe extern "C" fn pgrx_standard_planner_wrapper( +unsafe extern "C-unwind" fn pgrx_standard_planner_wrapper( parse: *mut pg_sys::Query, cursor_options: i32, bound_params: pg_sys::ParamListInfo, @@ -737,7 +739,7 @@ unsafe extern "C" fn pgrx_standard_planner_wrapper( feature = "pg17" ))] #[pg_guard] -unsafe extern "C" fn pgrx_standard_planner_wrapper( +unsafe extern "C-unwind" fn pgrx_standard_planner_wrapper( parse: *mut pg_sys::Query, query_string: *const ::std::os::raw::c_char, cursor_options: i32, diff --git a/pgrx/src/lib.rs b/pgrx/src/lib.rs index 51cea7c998..9175635218 100644 --- a/pgrx/src/lib.rs +++ b/pgrx/src/lib.rs @@ -233,7 +233,7 @@ macro_rules! pg_magic_func { #[no_mangle] #[allow(non_snake_case, unexpected_cfgs)] #[doc(hidden)] - pub extern "C" fn Pg_magic_func() -> &'static ::pgrx::pg_sys::Pg_magic_struct { + pub extern "C-unwind" fn Pg_magic_func() -> &'static ::pgrx::pg_sys::Pg_magic_struct { static MY_MAGIC: ::pgrx::pg_sys::Pg_magic_struct = ::pgrx::pg_sys::Pg_magic_struct { len: ::core::mem::size_of::<::pgrx::pg_sys::Pg_magic_struct>() as i32, version: ::pgrx::pg_sys::PG_VERSION_NUM as i32 / 100, diff --git a/pgrx/src/memcxt.rs b/pgrx/src/memcxt.rs index b33a204b19..4eb8643870 100644 --- a/pgrx/src/memcxt.rs +++ b/pgrx/src/memcxt.rs @@ -557,7 +557,7 @@ impl PgMemoryContexts { pub fn leak_and_drop_on_delete(&mut self, v: T) -> *mut T { use crate as pgrx; #[pgrx::pg_guard] - unsafe extern "C" fn drop_on_delete(ptr: void_mut_ptr) { + unsafe extern "C-unwind" fn drop_on_delete(ptr: void_mut_ptr) { let boxed = Box::from_raw(ptr as *mut T); drop(boxed); } diff --git a/pgrx/src/shmem.rs b/pgrx/src/shmem.rs index 877c4f9040..605411a6c4 100644 --- a/pgrx/src/shmem.rs +++ b/pgrx/src/shmem.rs @@ -44,7 +44,7 @@ pub unsafe trait PGRXSharedMemory {} /// static ATOMIC: PgAtomic = PgAtomic::new(c"atomic"); /// /// #[pg_guard] -/// pub extern "C" fn _PG_init() { +/// pub extern "C-unwind" fn _PG_init() { /// pg_shmem_init!(PRIMITIVE); /// pg_shmem_init!(ATOMIC); /// } @@ -56,12 +56,12 @@ macro_rules! pg_shmem_init { $thing.pg_init(); unsafe { - static mut PREV_SHMEM_STARTUP_HOOK: Option = None; + static mut PREV_SHMEM_STARTUP_HOOK: Option = None; PREV_SHMEM_STARTUP_HOOK = pg_sys::shmem_startup_hook; pg_sys::shmem_startup_hook = Some(__pgrx_private_shmem_hook); #[pg_guard] - extern "C" fn __pgrx_private_shmem_hook() { + extern "C-unwind" fn __pgrx_private_shmem_hook() { unsafe { if let Some(i) = PREV_SHMEM_STARTUP_HOOK { i(); @@ -78,12 +78,12 @@ macro_rules! pg_shmem_init { macro_rules! pg_shmem_init { ($thing:expr) => { unsafe { - static mut PREV_SHMEM_REQUEST_HOOK: Option = None; + static mut PREV_SHMEM_REQUEST_HOOK: Option = None; PREV_SHMEM_REQUEST_HOOK = pg_sys::shmem_request_hook; pg_sys::shmem_request_hook = Some(__pgrx_private_shmem_request_hook); #[pg_guard] - extern "C" fn __pgrx_private_shmem_request_hook() { + extern "C-unwind" fn __pgrx_private_shmem_request_hook() { unsafe { if let Some(i) = PREV_SHMEM_REQUEST_HOOK { i(); @@ -94,12 +94,12 @@ macro_rules! pg_shmem_init { } unsafe { - static mut PREV_SHMEM_STARTUP_HOOK: Option = None; + static mut PREV_SHMEM_STARTUP_HOOK: Option = None; PREV_SHMEM_STARTUP_HOOK = pg_sys::shmem_startup_hook; pg_sys::shmem_startup_hook = Some(__pgrx_private_shmem_hook); #[pg_guard] - extern "C" fn __pgrx_private_shmem_hook() { + extern "C-unwind" fn __pgrx_private_shmem_hook() { unsafe { if let Some(i) = PREV_SHMEM_STARTUP_HOOK { i(); From 3cfdc6c2c15e0ae21caba2f40771efb67166c2d9 Mon Sep 17 00:00:00 2001 From: usamoi Date: Tue, 25 Feb 2025 22:25:40 +0800 Subject: [PATCH 8/8] workaround for bindgen bug --- Cargo.toml | 2 +- pgrx-bindgen/src/build.rs | 23 ++++++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d9edd90bc9..f8c611514c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,7 +74,7 @@ regex = "1.11" # used for build/test serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" shlex = "1.3" # shell lexing, also used by many of our deps -syn = { version = "2", features = ["extra-traits", "full", "parsing"] } +syn = { version = "2", features = ["extra-traits", "full", "parsing", "visit-mut"] } toml = "0.8" # our config files toml_edit = "0.22.24" # format-preserving edits to toml files, used with cargo-edit thiserror = "2" diff --git a/pgrx-bindgen/src/build.rs b/pgrx-bindgen/src/build.rs index dc19635b0e..b8903975fb 100644 --- a/pgrx-bindgen/src/build.rs +++ b/pgrx-bindgen/src/build.rs @@ -331,7 +331,7 @@ fn generate_bindings( .wrap_err_with(|| format!("bindgen failed for pg{major_version}"))?; let oids = extract_oids(&bindgen_output); - let rewritten_items = rewrite_items(&bindgen_output, &oids) + let rewritten_items = rewrite_items(bindgen_output, &oids) .wrap_err_with(|| format!("failed to rewrite items for pg{major_version}"))?; let oids = format_builtin_oid_impl(oids); @@ -438,9 +438,10 @@ fn write_rs_file( /// Given a token stream representing a file, apply a series of transformations to munge /// the bindgen generated code with some postgres specific enhancements fn rewrite_items( - file: &syn::File, + mut file: syn::File, oids: &BTreeMap>, ) -> eyre::Result { + rewrite_c_abi_to_c_unwind(&mut file); let items_vec = rewrite_oid_consts(&file.items, oids); let mut items = apply_pg_guard(&items_vec)?; let pgnode_impls = impl_pg_node(&items_vec)?; @@ -829,7 +830,6 @@ fn run_bindgen( .wrap_static_fns(enable_cshim) .wrap_static_fns_path(out_path.join("pgrx-cshim-static")) .wrap_static_fns_suffix("__pgrx_cshim") - .override_abi(bindgen::Abi::CUnwind, ".*") .generate() .wrap_err_with(|| format!("Unable to generate bindings for pg{major_version}"))?; let mut binding_str = bindings.to_string(); @@ -1176,6 +1176,23 @@ fn apply_pg_guard(items: &Vec) -> eyre::Result eyre::Result<()> { // We shouldn't hit this path in a case where we care about it, but... just // in case we probably should respect RUSTFMT.