diff --git a/.cargo/config b/.cargo/config index 04ff481c..91756080 100644 --- a/.cargo/config +++ b/.cargo/config @@ -4,5 +4,8 @@ [target.'cfg(unix)'] rustflags = "-C link-arg=-undefineddynamic_lookup" +# Uncomment the appropiate line depending on your PG version [target.'cfg(windows)'] -rustflags = "-C link-arg=/FORCE" \ No newline at end of file +#rustflags = "-C link-arg=/FORCE:UNRESOLVED -C link-arg=C:/PROGRA~1/POSTGR~1/10/lib/postgres.lib" +#rustflags = "-C link-arg=/FORCE:UNRESOLVED -C link-arg=C:/PROGRA~1/POSTGR~1/11/lib/postgres.lib" +#rustflags = "-C link-arg=/FORCE:UNRESOLVED -C link-arg=C:/PROGRA~1/POSTGR~1/12/lib/postgres.lib" \ No newline at end of file diff --git a/README.md b/README.md index 761bf167..f9be8f65 100644 --- a/README.md +++ b/README.md @@ -42,14 +42,21 @@ First install Postgres. The build should be able to find the directory for the P `PG_INCLUDE_PATH=[/path/to/postgres]/include/server # e.g. /usr/local/pgsql/include/server` -For the dynamic library to compile, your project should also have `.cargo/config` file with content: +For the dynamic library to compile, your project should also have a `.cargo/config` file. The contents of this file varies based on your platform. +### POSIX (aka `unix` family) ```toml -[target.'cfg(unix)'] +[build] rustflags = "-C link-arg=-undefineddynamic_lookup" +``` + +This informs the linker that some of the symbols for Postgres won't be available until runtime on the dynamic library load. -[target.'cfg(windows)'] -rustflags = "-C link-arg=/FORCE" +### Windows +Building for Windows is somewhat cumbersome as the MSVC linker requires you to set the location of `postgres.lib`, but this will depend both on your installation path as well as your PG version. Here's an example where the installation path is the default one for PG 12: +```toml +[build] +rustflags = "-C link-arg=/FORCE:UNRESOLVED -C link-arg=C:/PROGRA~1/POSTGR~1/12/lib/postgres.lib" ``` This informs the linker that some of the symbols for Postgres won't be available until runtime on the dynamic library load. diff --git a/examples/adding/src/lib.rs b/examples/adding/src/lib.rs index 456e25a7..e44a6da1 100644 --- a/examples/adding/src/lib.rs +++ b/examples/adding/src/lib.rs @@ -62,7 +62,7 @@ fn sum_float_array(arr: &[f32]) -> f32 { arr.iter().sum() } -// Test array of f32 +// Test array of f64 #[pg_extern] fn sum_double_array(arr: &[f64]) -> f64 { arr.iter().sum() diff --git a/pg-extend/build.rs b/pg-extend/build.rs index 0ae66e3a..5820c9a8 100644 --- a/pg-extend/build.rs +++ b/pg-extend/build.rs @@ -16,7 +16,7 @@ use std::process::Command; fn main() { let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("postgres.rs"); - let pg_config = env::var("PG_CONFIG").unwrap_or_else(|_| "pg_config".to_string()); + let pg_config = env::var("PG_CONFIG").unwrap_or("pg_config".to_string()); // Re-run this if wrapper.h changes println!("cargo:rerun-if-changed=wrapper.h"); @@ -163,7 +163,7 @@ fn include_dir(pg_config: &str) -> Result { env::var("PG_INCLUDE_PATH").or_else(|err| { match Command::new(pg_config).arg("--includedir-server").output() { Ok(out) => Ok(String::from_utf8(out.stdout).unwrap().trim().to_string()), - Err(..) => Err(err), + Err(_) => Err(err), } }) } diff --git a/pg-extend/src/lib.rs b/pg-extend/src/lib.rs index f722ef35..06198f67 100644 --- a/pg-extend/src/lib.rs +++ b/pg-extend/src/lib.rs @@ -273,13 +273,20 @@ macro_rules! pg_create_stmt_bin { #[cfg(target_os = "macos")] const DYLIB_EXT: &str = "dylib"; - #[cfg(target_os = "windows")] - const DYLIB_EXT: &str = "dll"; + #[cfg(unix)] + fn main() { + let lib_name = env!("CARGO_PKG_NAME").replace("-", "_"); + + let lib_path = env::args().nth(1).unwrap_or(format!("target/release/lib{}.{}", lib_name, DYLIB_EXT)); + + $( println!("{}", lib::$func(&lib_path)); )* + } + #[cfg(windows)] fn main() { - const LIB_NAME: &str = env!("CARGO_PKG_NAME"); + let lib_name = env!("CARGO_PKG_NAME").replace("-", "_"); - let lib_path = env::args().nth(1).unwrap_or_else(|| format!("target/release/lib{}.{}", LIB_NAME, DYLIB_EXT)); + let lib_path = env::args().nth(1).unwrap_or(format!("target\\\\release\\\\{}.dll", lib_name)); $( println!("{}", lib::$func(&lib_path)); )* } diff --git a/pg-extend/src/log.rs b/pg-extend/src/log.rs index 00be350e..7ef50c56 100644 --- a/pg-extend/src/log.rs +++ b/pg-extend/src/log.rs @@ -50,6 +50,7 @@ //! [`pg_log!`]: ../macro.pg_log.html //! [`Level` enum]: enum.Level.html +#[cfg(not(windows))] use std::ffi::CString; use std::fmt; use std::os::raw::{c_char, c_int}; @@ -97,6 +98,29 @@ pub enum Level { Panic = pg_sys::PANIC as isize, } +impl std::fmt::Display for Level { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let level = match self { + Level::Debug5 => "DEBUG5", + Level::Debug4 => "DEBUG4", + Level::Debug3 => "DEBUG3", + Level::Debug2 => "DEBUG2", + Level::Debug1 => "DEBUG1", + Level::Log => "LOG", + #[cfg(not(feature = "postgres-9"))] + Level::LogServerOnly => "LOG_SERVER_ONLY", + Level::Info => "INFO", + Level::Notice => "NOTICE", + Level::Warning => "WARNING", + Level::Error => "ERROR", + Level::Fatal => "FATAL", + Level::Panic => "PANIC", + }; + + write!(f, "{}", level) + } +} + impl From for c_int { fn from(level: Level) -> Self { level as isize as c_int @@ -207,6 +231,7 @@ macro_rules! pg_log { // WARNING: this is not part of the crate's public API and is subject to change at any time #[doc(hidden)] +#[cfg(not(windows))] pub fn __private_api_log( args: fmt::Arguments, level: Level, @@ -242,3 +267,18 @@ pub fn __private_api_log( } } } + +// TODO: improve logging and figure out a way to deal with side-effects +#[doc(hidden)] +#[cfg(windows)] +pub fn __private_api_log( + args: fmt::Arguments, + level: Level, + &(_module_path, _file, _line): &(*const c_char, *const c_char, u32), +) { + eprintln!("{}: {}", level, args); + + if level as usize >= Level::Error as usize { + panic!(""); + } +} diff --git a/pg-extend/src/pg_datum.rs b/pg-extend/src/pg_datum.rs index b1c30fe6..bedeab7f 100644 --- a/pg-extend/src/pg_datum.rs +++ b/pg-extend/src/pg_datum.rs @@ -371,14 +371,14 @@ struct DetoastedArrayWrapper { } impl DetoastedArrayWrapper { - unsafe fn detoasted(datum: Datum) -> Result { + fn detoasted(datum: Datum) -> Result { let datum = datum as *mut pg_sys::varlena; if datum.is_null() { return Err("datum was NULL"); } #[allow(clippy::cast_ptr_alignment)] - let arr_type = pg_sys::pg_detoast_datum(datum) as *mut pg_sys::ArrayType; + let arr_type = unsafe { pg_sys::pg_detoast_datum(datum) as *mut pg_sys::ArrayType }; #[allow(clippy::cast_ptr_alignment)] let original_datum = datum as *mut pg_sys::ArrayType; @@ -395,16 +395,16 @@ impl DetoastedArrayWrapper { impl Drop for DetoastedArrayWrapper { fn drop(&mut self) { if self.arr_type != self.original_datum { - unsafe { - if !self.arr_type.is_null() { - pg_sys::pfree(self.arr_type as *mut _); - } - if !self.elements.is_null() { - pg_sys::pfree(self.elements as *mut _); - } - if !self.nulls.is_null() { - pg_sys::pfree(self.nulls as *mut _); - } + if !self.arr_type.is_null() { + unsafe { pg_sys::pfree(self.arr_type as *mut _) } + } + + if !self.elements.is_null() { + unsafe { pg_sys::pfree(self.elements as *mut _) } + } + + if !self.nulls.is_null() { + unsafe { pg_sys::pfree(self.nulls as *mut _) } } } } @@ -430,17 +430,18 @@ where 'mc: 's, { if let Some(datum) = datum.0 { - unsafe { - let mut detoasted_wrapper = DetoastedArrayWrapper::detoasted(datum)?; + let mut detoasted_wrapper = DetoastedArrayWrapper::detoasted(datum)?; - if (*(detoasted_wrapper.arr_type)).ndim > 1 { - return Err("argument must be empty or one-dimensional array"); - } + if unsafe { (*(detoasted_wrapper.arr_type)).ndim } > 1 { + return Err("argument must be empty or one-dimensional array"); + } - let mut elmlen: pg_sys::int16 = 0; - let mut elmbyval = pgbool!(false); - let mut elmalign: ::std::os::raw::c_char = 0; + let mut elmlen: pg_sys::int16 = 0; + let mut elmbyval = false; + let mut elmalign: c_char = 0; + let mut nelems = 0; + let datums = unsafe { pg_sys::get_typlenbyvalalign( (*(detoasted_wrapper.arr_type)).elemtype, &mut elmlen, @@ -448,8 +449,6 @@ where &mut elmalign, ); - let mut nelems: i32 = 0; - pg_sys::deconstruct_array( detoasted_wrapper.arr_type, (*(detoasted_wrapper.arr_type)).elemtype, @@ -461,27 +460,25 @@ where &mut nelems, ); - let datums = std::slice::from_raw_parts( + std::slice::from_raw_parts( detoasted_wrapper.elements as *const Datum, nelems as usize, - ); - - // This is where the conversion from `&[Datum]` is done to `&[T]` by a simple type casting, - // however, we should use `T::try_cast(&'mc PgAllocator, Datum)` to ignore nulls - let mem_size_datums = std::mem::size_of_val(datums); - let datums = if mem_size_datums == 0 { - std::slice::from_raw_parts(datums.as_ptr() as *const T, 0) - } else { - let mem_size_type = std::mem::size_of::(); - assert_eq!(mem_size_datums % mem_size_type, 0); - std::slice::from_raw_parts( - datums.as_ptr() as *const T, - mem_size_datums / mem_size_type, - ) - }; - - Ok(datums) - } + ) + }; + + // This is where the conversion from `&[Datum]` is done to `&[T]` by a simple type casting + let mem_size_datums = std::mem::size_of_val(datums); + let datums = if mem_size_datums == 0 { + unsafe { std::slice::from_raw_parts(datums.as_ptr() as *const T, 0) } + } else { + let mem_size_type = std::mem::size_of::(); + assert_eq!(mem_size_datums % mem_size_type, 0); + let len = mem_size_datums / mem_size_type; + + unsafe { std::slice::from_raw_parts(datums.as_ptr() as *const T, len) } + }; + + Ok(datums) } else { Err("datum was NULL") } diff --git a/pg-extern-attr/src/lib.rs b/pg-extern-attr/src/lib.rs index 087db2f8..cc131677 100644 --- a/pg-extern-attr/src/lib.rs +++ b/pg-extern-attr/src/lib.rs @@ -211,11 +211,8 @@ fn sql_function_options(arg_types: &[Type]) -> TokenStream { quote!( { let optional_args = [ #( <#arg_types>::is_option() ),* ]; - if optional_args.iter().all(|&x| x) { "" } - else if !optional_args.iter().any(|&x| x) { " STRICT" } - else { - panic!("Cannot mix Option and non-Option arguments."); - } + if !optional_args.iter().any(|&x| x) { " STRICT" } + else { "" } }, ) }