pgwire_lite/
notices.rs

1// src/notices.rs
2
3use std::collections::HashMap;
4use std::ffi::{c_void, CStr};
5use std::sync::{Arc, Mutex};
6
7use libpq_sys::{
8    PGVerbosity, PGresult, PQresultErrorField, PG_DIAG_COLUMN_NAME, PG_DIAG_CONSTRAINT_NAME,
9    PG_DIAG_CONTEXT, PG_DIAG_DATATYPE_NAME, PG_DIAG_INTERNAL_POSITION, PG_DIAG_INTERNAL_QUERY,
10    PG_DIAG_MESSAGE_DETAIL, PG_DIAG_MESSAGE_HINT, PG_DIAG_MESSAGE_PRIMARY, PG_DIAG_SCHEMA_NAME,
11    PG_DIAG_SEVERITY, PG_DIAG_SEVERITY_NONLOCALIZED, PG_DIAG_SOURCE_FILE, PG_DIAG_SOURCE_FUNCTION,
12    PG_DIAG_SOURCE_LINE, PG_DIAG_SQLSTATE, PG_DIAG_STATEMENT_POSITION, PG_DIAG_TABLE_NAME,
13};
14
15/// Error/notice verbosity level for PostgreSQL connections.
16///
17/// Controls the amount of detail included in error and notice messages.
18#[derive(Debug, Clone, Copy)]
19pub enum Verbosity {
20    Terse,
21    Default,
22    Verbose,
23    Sqlstate,
24}
25
26impl From<Verbosity> for PGVerbosity {
27    fn from(verbosity: Verbosity) -> Self {
28        match verbosity {
29            Verbosity::Terse => PGVerbosity::PQERRORS_TERSE,
30            Verbosity::Default => PGVerbosity::PQERRORS_DEFAULT,
31            Verbosity::Verbose => PGVerbosity::PQERRORS_VERBOSE,
32            Verbosity::Sqlstate => PGVerbosity::PQERRORS_SQLSTATE,
33        }
34    }
35}
36
37/// Represents a notice or warning message from PostgreSQL.
38///
39/// Notices are informational messages that don't cause a query to fail
40/// but provide important context about the execution.
41#[derive(Debug, Clone)]
42pub struct Notice {
43    /// A map of field identifiers to their values
44    ///
45    /// Common fields include:
46    /// - "severity": The severity level (e.g., "NOTICE", "WARNING")
47    /// - "message": The primary message text
48    /// - "detail": Additional detail about the problem
49    /// - "hint": Suggestion on how to fix the problem    
50    pub fields: HashMap<&'static str, String>,
51}
52
53/// Thread-safe storage for collected notices
54pub type NoticeStorage = Arc<Mutex<Vec<Notice>>>;
55
56/// C callback function that receives notices from PostgreSQL.
57///
58/// This function is called by libpq whenever a notice is generated.
59/// It extracts the relevant fields based on the verbosity level and
60/// stores them in the shared notice storage.
61///
62/// # Safety
63///
64/// This function is called directly by C code and must follow C calling conventions.
65/// It carefully handles null pointers and performs proper memory management.
66pub unsafe extern "C" fn notice_receiver(arg: *mut c_void, result: *const PGresult) {
67    if result.is_null() || arg.is_null() {
68        return;
69    }
70
71    let shared_notices = unsafe { &*(arg as *const Mutex<Vec<Notice>>) };
72
73    // Retrieve verbosity level from the connection
74    let verbosity = match shared_notices.lock() {
75        Ok(notices) => notices
76            // .get(0)
77            .first()
78            .map(|_| Verbosity::Verbose)
79            .unwrap_or(Verbosity::Default),
80        Err(_) => Verbosity::Default,
81    };
82
83    let field_kinds: Vec<(i32, &'static str)> = match verbosity {
84        Verbosity::Terse => vec![
85            (PG_DIAG_SEVERITY as i32, "severity"),
86            (PG_DIAG_MESSAGE_PRIMARY as i32, "message"),
87            (PG_DIAG_SQLSTATE as i32, "sqlstate"),
88        ],
89        Verbosity::Default => vec![
90            (PG_DIAG_SEVERITY as i32, "severity"),
91            (PG_DIAG_SQLSTATE as i32, "sqlstate"),
92            (PG_DIAG_MESSAGE_PRIMARY as i32, "message"),
93            (PG_DIAG_MESSAGE_DETAIL as i32, "detail"),
94            (PG_DIAG_MESSAGE_HINT as i32, "hint"),
95        ],
96        Verbosity::Verbose => vec![
97            (PG_DIAG_SEVERITY as i32, "severity"),
98            (
99                PG_DIAG_SEVERITY_NONLOCALIZED as i32,
100                "severity_nonlocalized",
101            ),
102            (PG_DIAG_SQLSTATE as i32, "sqlstate"),
103            (PG_DIAG_MESSAGE_PRIMARY as i32, "message"),
104            (PG_DIAG_MESSAGE_DETAIL as i32, "detail"),
105            (PG_DIAG_MESSAGE_HINT as i32, "hint"),
106            (PG_DIAG_STATEMENT_POSITION as i32, "statement_position"),
107            (PG_DIAG_INTERNAL_POSITION as i32, "internal_position"),
108            (PG_DIAG_INTERNAL_QUERY as i32, "internal_query"),
109            (PG_DIAG_CONTEXT as i32, "context"),
110            (PG_DIAG_SCHEMA_NAME as i32, "schema_name"),
111            (PG_DIAG_TABLE_NAME as i32, "table_name"),
112            (PG_DIAG_COLUMN_NAME as i32, "column_name"),
113            (PG_DIAG_DATATYPE_NAME as i32, "datatype_name"),
114            (PG_DIAG_CONSTRAINT_NAME as i32, "constraint_name"),
115            (PG_DIAG_SOURCE_FILE as i32, "source_file"),
116            (PG_DIAG_SOURCE_LINE as i32, "source_line"),
117            (PG_DIAG_SOURCE_FUNCTION as i32, "source_function"),
118        ],
119        Verbosity::Sqlstate => vec![
120            (PG_DIAG_SEVERITY as i32, "severity"),
121            (PG_DIAG_SQLSTATE as i32, "sqlstate"),
122        ],
123    };
124
125    let mut notice = Notice {
126        fields: HashMap::new(),
127    };
128
129    for (code, label) in &field_kinds {
130        let val_ptr = unsafe { PQresultErrorField(result, *code) };
131        if !val_ptr.is_null() {
132            let val = unsafe { CStr::from_ptr(val_ptr) }
133                .to_string_lossy()
134                .into_owned();
135            notice.fields.insert(*label, val);
136        }
137    }
138
139    if let Ok(mut vec) = shared_notices.lock() {
140        vec.push(notice);
141    }
142}