Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions pgrx-pg-sys/src/submodules/elog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,128 @@ pub fn interrupt_pending() -> bool {
unsafe { crate::InterruptPending != 0 }
}

/// Send some kind of message to Postgres similar to the ereport macro, while specifying
/// a text domain, analogous to Postgres' `ereport_domain` C macro.
///
/// The argument order is:
/// - `domain: &str` — the gettext text domain for message translation
/// - `log_level: [PgLogLevel]`
/// - `error_code: [PgSqlErrorCode]`
/// - `message: String`
/// - (optional) `detail: String`
///
/// ## Examples
///
/// ```rust,no_run
/// # use pgrx_pg_sys::ereport_domain;
/// # use pgrx_pg_sys::elog::PgLogLevel;
/// # use pgrx_pg_sys::errcodes::PgSqlErrorCode;
/// ereport_domain!("my_extension", PgLogLevel::ERROR, PgSqlErrorCode::ERRCODE_INTERNAL_ERROR, "oh noes!");
/// ```
///
/// ```rust,no_run
/// # use pgrx_pg_sys::ereport_domain;
/// # use pgrx_pg_sys::elog::PgLogLevel;
/// # use pgrx_pg_sys::errcodes::PgSqlErrorCode;
/// ereport_domain!("my_extension", PgLogLevel::LOG, PgSqlErrorCode::ERRCODE_SUCCESSFUL_COMPLETION, "translated message");
/// ```
#[macro_export]
macro_rules! ereport_domain {
($domain:expr, ERROR, $errcode:expr, $message:expr $(, $detail:expr)? $(,)?) => {
$crate::panic::ErrorReport::new($errcode, $message, $crate::function_name!())
.set_domain($domain)
$(.set_detail($detail))?
.report($crate::elog::PgLogLevel::ERROR);
unreachable!();
};

($domain:expr, PANIC, $errcode:expr, $message:expr $(, $detail:expr)? $(,)?) => {
$crate::panic::ErrorReport::new($errcode, $message, $crate::function_name!())
.set_domain($domain)
$(.set_detail($detail))?
.report($crate::elog::PgLogLevel::PANIC);
unreachable!();
};

($domain:expr, FATAL, $errcode:expr, $message:expr $(, $detail:expr)? $(,)?) => {
$crate::panic::ErrorReport::new($errcode, $message, $crate::function_name!())
.set_domain($domain)
$(.set_detail($detail))?
.report($crate::elog::PgLogLevel::FATAL);
unreachable!();
};

($domain:expr, WARNING, $errcode:expr, $message:expr $(, $detail:expr)? $(,)?) => {
$crate::panic::ErrorReport::new($errcode, $message, $crate::function_name!())
.set_domain($domain)
$(.set_detail($detail))?
.report($crate::elog::PgLogLevel::WARNING)
};

($domain:expr, NOTICE, $errcode:expr, $message:expr $(, $detail:expr)? $(,)?) => {
$crate::panic::ErrorReport::new($errcode, $message, $crate::function_name!())
.set_domain($domain)
$(.set_detail($detail))?
.report($crate::elog::PgLogLevel::NOTICE)
};

($domain:expr, INFO, $errcode:expr, $message:expr $(, $detail:expr)? $(,)?) => {
$crate::panic::ErrorReport::new($errcode, $message, $crate::function_name!())
.set_domain($domain)
$(.set_detail($detail))?
.report($crate::elog::PgLogLevel::INFO)
};

($domain:expr, LOG, $errcode:expr, $message:expr $(, $detail:expr)? $(,)?) => {
$crate::panic::ErrorReport::new($errcode, $message, $crate::function_name!())
.set_domain($domain)
$(.set_detail($detail))?
.report($crate::elog::PgLogLevel::LOG)
};

($domain:expr, DEBUG5, $errcode:expr, $message:expr $(, $detail:expr)? $(,)?) => {
$crate::panic::ErrorReport::new($errcode, $message, $crate::function_name!())
.set_domain($domain)
$(.set_detail($detail))?
.report($crate::elog::PgLogLevel::DEBUG5)
};

($domain:expr, DEBUG4, $errcode:expr, $message:expr $(, $detail:expr)? $(,)?) => {
$crate::panic::ErrorReport::new($errcode, $message, $crate::function_name!())
.set_domain($domain)
$(.set_detail($detail))?
.report($crate::elog::PgLogLevel::DEBUG4)
};

($domain:expr, DEBUG3, $errcode:expr, $message:expr $(, $detail:expr)? $(,)?) => {
$crate::panic::ErrorReport::new($errcode, $message, $crate::function_name!())
.set_domain($domain)
$(.set_detail($detail))?
.report($crate::elog::PgLogLevel::DEBUG3)
};

($domain:expr, DEBUG2, $errcode:expr, $message:expr $(, $detail:expr)? $(,)?) => {
$crate::panic::ErrorReport::new($errcode, $message, $crate::function_name!())
.set_domain($domain)
$(.set_detail($detail))?
.report($crate::elog::PgLogLevel::DEBUG2)
};

($domain:expr, DEBUG1, $errcode:expr, $message:expr $(, $detail:expr)? $(,)?) => {
$crate::panic::ErrorReport::new($errcode, $message, $crate::function_name!())
.set_domain($domain)
$(.set_detail($detail))?
.report($crate::elog::PgLogLevel::DEBUG1)
};

($domain:expr, $loglevel:expr, $errcode:expr, $message:expr $(, $detail:expr)? $(,)?) => {
$crate::panic::ErrorReport::new($errcode, $message, $crate::function_name!())
.set_domain($domain)
$(.set_detail($detail))?
.report($loglevel);
};
}

/// If an interrupt is pending (perhaps a user-initiated "cancel query" message to this backend),
/// this will safely abort the current transaction
#[macro_export]
Expand Down
36 changes: 32 additions & 4 deletions pgrx-pg-sys/src/submodules/panic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ pub struct ErrorReport {
pub(crate) message: String,
pub(crate) hint: Option<String>,
pub(crate) detail: Option<String>,
pub(crate) domain: Option<String>,
pub(crate) location: ErrorReportLocation,
}

Expand Down Expand Up @@ -206,6 +207,11 @@ impl ErrorReportWithLevel {
self.inner.hint()
}

/// Returns the domain of this error report, if set
pub fn domain(&self) -> Option<&str> {
self.inner.domain()
}

/// Returns the name of the source file that generated this error report
pub fn file(&self) -> &str {
&self.inner.location.file
Expand Down Expand Up @@ -247,7 +253,7 @@ impl ErrorReport {
let mut location: ErrorReportLocation = Location::caller().into();
location.funcname = Some(funcname.to_string());

Self { sqlerrcode, message: message.into(), hint: None, detail: None, location }
Self { sqlerrcode, message: message.into(), hint: None, detail: None, domain: None, location }
}

/// Create an [ErrorReport] which can be raised via Rust's [std::panic::panic_any()] or as
Expand All @@ -259,7 +265,7 @@ impl ErrorReport {
message: S,
location: ErrorReportLocation,
) -> Self {
Self { sqlerrcode, message: message.into(), hint: None, detail: None, location }
Self { sqlerrcode, message: message.into(), hint: None, detail: None, domain: None, location }
}

/// Set the `detail` property, whose default is `None`
Expand All @@ -274,6 +280,16 @@ impl ErrorReport {
self
}

/// Set the `domain` property for message translation/internationalization, whose default is `None`.
///
/// This corresponds to the `domain` argument in Postgres' `ereport_domain` C macro.
/// When set, the domain is passed to `errstart()` to indicate the gettext text domain
/// for message translation.
pub fn set_domain<S: Into<String>>(mut self, domain: S) -> Self {
self.domain = Some(domain.into());
self
}

/// Returns the error message of this error report
pub fn message(&self) -> &str {
&self.message
Expand All @@ -289,6 +305,11 @@ impl ErrorReport {
self.hint.as_deref()
}

/// Returns the domain of this error report, if set
pub fn domain(&self) -> Option<&str> {
self.domain.as_deref()
}

/// Report this [ErrorReport], which will ultimately be reported by Postgres at the specified [PgLogLevel]
///
/// If the provided `level` is >= [`PgLogLevel::ERROR`] this function will not return.
Expand Down Expand Up @@ -497,7 +518,7 @@ pub(crate) fn downcast_panic_payload(e: Box<dyn Any + Send>) -> CaughtError {
/// trying to roll their own error handling.
fn do_ereport(ereport: ErrorReportWithLevel) {
const PERCENT_S: &CStr = c"%s";
const DOMAIN: *const ::std::os::raw::c_char = std::ptr::null_mut();
const DEFAULT_DOMAIN: *const ::std::os::raw::c_char = std::ptr::null_mut();

// the following code is definitely thread-unsafe -- not-the-main-thread can't be creating Postgres
// ereports. Our secret `extern "C"` definitions aren't wrapped by #[pg_guard] so we need to
Expand Down Expand Up @@ -530,8 +551,15 @@ fn do_ereport(ereport: ErrorReportWithLevel) {
}

let level = ereport.level();

// Use the domain from the ErrorReport if one was set, otherwise use the null default
let domain_cstring = ereport.inner.domain.as_ref().map(|d| {
std::ffi::CString::new(d.as_str()).expect("domain must not contain interior NUL bytes")
});
let domain_ptr = domain_cstring.as_ref().map_or(DEFAULT_DOMAIN, |c| c.as_ptr());

unsafe {
if errstart(level as _, DOMAIN) {
if errstart(level as _, domain_ptr) {
let sqlerrcode = ereport.sql_error_code();
let message = ereport.message().as_pg_cstr();
let detail = ereport.detail_with_backtrace().as_pg_cstr();
Expand Down
4 changes: 2 additions & 2 deletions pgrx/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@ pub use pg_sys::panic::pgrx_extern_c_guard;
pub use pg_sys::pg_try::PgTryBuilder;
pub use pg_sys::utils::name_data_to_str;
pub use pg_sys::{
FATAL, PANIC, check_for_interrupts, debug1, debug2, debug3, debug4, debug5, ereport, error,
function_name, info, log, notice, warning,
FATAL, PANIC, check_for_interrupts, debug1, debug2, debug3, debug4, debug5, ereport,
ereport_domain, error, function_name, info, log, notice, warning,
};

#[doc(hidden)]
Expand Down
4 changes: 2 additions & 2 deletions pgrx/src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,6 @@ pub use crate::spi::Spi;
pub use crate::pg_sys::elog::PgLogLevel;
pub use crate::pg_sys::errcodes::PgSqlErrorCode;
pub use crate::pg_sys::{
FATAL, PANIC, check_for_interrupts, debug1, debug2, debug3, debug4, debug5, ereport, error,
function_name, info, log, notice, warning,
FATAL, PANIC, check_for_interrupts, debug1, debug2, debug3, debug4, debug5, ereport,
ereport_domain, error, function_name, info, log, notice, warning,
};