pg_query/
parse_options.rs1use std::ffi::{c_void, CStr, CString};
4
5use prost::Message;
6
7use crate::bindings::*;
8use crate::{protobuf, Error, ParseMode, ParseResult, Result};
9
10#[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#[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#[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 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
130pub 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}