Skip to main content

pg_query/
parse_options.rs

1//! Per-call PostgreSQL scanner settings and structured diagnostics.
2
3use std::ffi::{c_void, CStr, CString};
4
5use prost::Message;
6
7use crate::bindings::*;
8use crate::{protobuf, Error, ParseMode, ParseResult, Result};
9
10/// Scanner settings captured when a complete SQL message is parsed. PostgreSQL's
11/// safe_encoding and on values for backslash_quote are equivalent for UTF-8.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct ParseOptions {
14    pub mode: ParseMode,
15    pub standard_conforming_strings: bool,
16    pub backslash_quote: bool,
17    pub escape_string_warning: bool,
18}
19
20impl Default for ParseOptions {
21    fn default() -> Self {
22        Self {
23            mode: ParseMode::Default,
24            standard_conforming_strings: true,
25            backslash_quote: true,
26            escape_string_warning: true,
27        }
28    }
29}
30
31impl ParseOptions {
32    pub(crate) fn bits(self) -> i32 {
33        self.mode as i32
34            | if self.standard_conforming_strings {
35                0
36            } else {
37                PG_QUERY_DISABLE_STANDARD_CONFORMING_STRINGS as i32
38            }
39            | if self.backslash_quote {
40                0
41            } else {
42                PG_QUERY_DISABLE_BACKSLASH_QUOTE as i32
43            }
44            | if self.escape_string_warning {
45                0
46            } else {
47                PG_QUERY_DISABLE_ESCAPE_STRING_WARNING as i32
48            }
49    }
50}
51
52/// PostgreSQL ErrorData fields copied while the parser owns their backing storage.
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct Diagnostic {
55    pub severity: i32,
56    pub sqlstate: String,
57    pub message: String,
58    pub detail: Option<String>,
59    pub hint: Option<String>,
60    pub context: Option<String>,
61    pub cursor_position: i32,
62}
63
64impl std::fmt::Display for Diagnostic {
65    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        formatter.write_str(&self.message)
67    }
68}
69
70/// Notices are retained in parser order, including notices emitted before an error.
71#[derive(Debug)]
72pub struct ParseOutcome<T> {
73    pub result: Result<T>,
74    pub diagnostics: Vec<Diagnostic>,
75}
76
77impl<T> ParseOutcome<T> {
78    pub(crate) fn error(error: Error) -> Self {
79        Self {
80            result: Err(error),
81            diagnostics: Vec::new(),
82        }
83    }
84}
85
86pub(crate) unsafe extern "C" fn capture_diagnostic(
87    context: *mut c_void,
88    diagnostic: *const PgQueryDiagnostic,
89) {
90    // This synchronous callback only copies borrowed fields. It never invokes the
91    // parser, calls user code, or leaves references into the C memory context.
92    let output = &mut *(context as *mut Vec<Diagnostic>);
93    let diagnostic = &*diagnostic;
94    let text = |value: *const std::os::raw::c_char| {
95        if value.is_null() {
96            None
97        } else {
98            Some(CStr::from_ptr(value).to_string_lossy().into_owned())
99        }
100    };
101    output.push(Diagnostic {
102        severity: diagnostic.severity,
103        sqlstate: CStr::from_ptr(diagnostic.sqlstate.as_ptr())
104            .to_string_lossy()
105            .into_owned(),
106        message: text(diagnostic.message).unwrap_or_default(),
107        detail: text(diagnostic.detail),
108        hint: text(diagnostic.hint),
109        context: text(diagnostic.context),
110        cursor_position: diagnostic.cursorpos,
111    });
112}
113
114pub(crate) unsafe fn parse_error(error: *const PgQueryError, diagnostics: &[Diagnostic]) -> Error {
115    if let Some(diagnostic) = diagnostics
116        .iter()
117        .rev()
118        .find(|diagnostic| diagnostic.severity >= 21)
119    {
120        Error::ParseDiagnostic(Box::new(diagnostic.clone()))
121    } else {
122        Error::Parse(
123            CStr::from_ptr((*error).message)
124                .to_string_lossy()
125                .into_owned(),
126        )
127    }
128}
129
130/// Parse with per-call scanner settings and original SQLSTATE, DETAIL, HINT and
131/// warning fields. Settings and callback state are restored even after errors.
132pub fn parse_with_options(statement: &str, options: ParseOptions) -> ParseOutcome<ParseResult> {
133    let input = match CString::new(statement) {
134        Ok(input) => input,
135        Err(error) => return ParseOutcome::error(error.into()),
136    };
137    let mut diagnostics = Vec::<Diagnostic>::new();
138    let result = unsafe {
139        pg_query_parse_protobuf_with_diagnostics(
140            input.as_ptr(),
141            options.bits(),
142            Some(capture_diagnostic),
143            (&mut diagnostics as *mut Vec<Diagnostic>).cast(),
144        )
145    };
146    let parsed = if !result.error.is_null() {
147        Err(unsafe { parse_error(result.error, &diagnostics) })
148    } else {
149        let data = unsafe {
150            std::slice::from_raw_parts(result.parse_tree.data as *const u8, result.parse_tree.len)
151        };
152        protobuf::ParseResult::decode(data)
153            .map_err(Error::Decode)
154            .map(|tree| ParseResult::new(tree, String::new()))
155    };
156    unsafe { pg_query_free_protobuf_parse_result(result) };
157    ParseOutcome {
158        result: parsed,
159        diagnostics,
160    }
161}