1use std::cell::RefCell;
10use std::rc::Rc;
11use std::sync::Arc;
12
13use crate::{NoticeLevel, SQLError, SQLNotice};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct ParserSettings {
18 pub standard_conforming_strings: bool,
19 pub backslash_quote: bool,
20 pub escape_string_warning: bool,
21}
22
23impl Default for ParserSettings {
24 fn default() -> Self {
25 Self {
26 standard_conforming_strings: true,
27 backslash_quote: true,
28 escape_string_warning: true,
29 }
30 }
31}
32
33impl ParserSettings {
34 pub fn from_settings<'a>(setting: impl Fn(&str) -> Option<&'a str>) -> Self {
36 Self {
37 standard_conforming_strings: setting("standard_conforming_strings") != Some("off"),
38 backslash_quote: setting("backslash_quote") != Some("off"),
39 escape_string_warning: setting("escape_string_warning") != Some("off"),
40 }
41 }
42
43 fn options(self, mode: pg_query::ParseMode) -> pg_query::ParseOptions {
44 pg_query::ParseOptions {
45 mode,
46 standard_conforming_strings: self.standard_conforming_strings,
47 backslash_quote: self.backslash_quote,
48 escape_string_warning: self.escape_string_warning,
49 }
50 }
51}
52
53#[derive(Debug, Clone, Default)]
55pub struct ParserMetadata {
56 pub settings: ParserSettings,
57 pub notices: Arc<[SQLNotice]>,
58}
59
60#[derive(Clone)]
61struct Context {
62 settings: ParserSettings,
63 notices: Rc<RefCell<Vec<SQLNotice>>>,
64 report: bool,
65}
66
67thread_local! {
68 static CONTEXT: RefCell<Option<Context>> = const { RefCell::new(None) };
69}
70
71struct Scope(Option<Context>);
72
73impl Scope {
74 fn enter(context: Context) -> Self {
75 Self(CONTEXT.with(|current| current.replace(Some(context))))
76 }
77}
78
79impl Drop for Scope {
80 fn drop(&mut self) {
81 CONTEXT.with(|current| current.replace(self.0.take()));
82 }
83}
84
85pub fn with_settings<T>(
87 settings: ParserSettings,
88 compile: impl FnOnce() -> Result<T, SQLError>,
89) -> (Result<T, SQLError>, ParserMetadata) {
90 let notices = Rc::new(RefCell::new(Vec::new()));
91 let _scope = Scope::enter(Context {
92 settings,
93 notices: Rc::clone(¬ices),
94 report: true,
95 });
96 let result = compile();
97 let notices = std::mem::take(&mut *notices.borrow_mut()).into();
98 (result, ParserMetadata { settings, notices })
99}
100
101pub(crate) fn settings() -> ParserSettings {
102 CONTEXT.with(|context| {
103 context
104 .borrow()
105 .as_ref()
106 .map_or_else(ParserSettings::default, |context| context.settings)
107 })
108}
109
110pub(crate) fn without_notices<T>(
112 compile: impl FnOnce() -> Result<T, SQLError>,
113) -> Result<T, SQLError> {
114 let context = CONTEXT.with(|context| context.borrow().clone());
115 let _scope = context.map(|mut context| {
116 context.report = false;
117 Scope::enter(context)
118 });
119 compile()
120}
121
122fn finish<T>(outcome: pg_query::ParseOutcome<T>) -> Result<T, SQLError> {
123 CONTEXT.with(|context| {
124 let context = context.borrow();
125 if let Some(context) = context.as_ref().filter(|context| context.report) {
126 context
127 .notices
128 .borrow_mut()
129 .extend(outcome.diagnostics.into_iter().filter_map(|diagnostic| {
130 let level = match diagnostic.severity {
131 10..=14 => NoticeLevel::Debug,
132 15 | 16 => NoticeLevel::Log,
133 17 => NoticeLevel::Info,
134 18 => NoticeLevel::Notice,
135 19 => NoticeLevel::Warning,
136 _ => return None,
137 };
138 Some(SQLNotice {
139 level,
140 sqlstate: diagnostic.sqlstate,
141 message: diagnostic.message,
142 detail: diagnostic.detail,
143 hint: diagnostic.hint,
144 })
145 }));
146 }
147 });
148 outcome.result.map_err(Into::into)
149}
150
151pub(crate) fn parse(sql: &str) -> Result<pg_query::ParseResult, SQLError> {
152 parse_with_mode(sql, pg_query::ParseMode::Default)
153}
154
155pub(crate) fn parse_with_mode(
156 sql: &str,
157 mode: pg_query::ParseMode,
158) -> Result<pg_query::ParseResult, SQLError> {
159 finish(pg_query::parse_with_options(sql, settings().options(mode)))
160}
161
162pub(crate) fn parse_plpgsql(
163 sql: &str,
164 catalog: Option<&pg_query::PlpgsqlCatalog>,
165) -> Result<serde_json::Value, SQLError> {
166 parse_plpgsql_mode(sql, catalog, crate::plpgsql::PLpgSQLCompileMode::Validate)
167}
168
169pub(crate) fn parse_plpgsql_mode(
170 sql: &str,
171 catalog: Option<&pg_query::PlpgsqlCatalog>,
172 mode: crate::plpgsql::PLpgSQLCompileMode,
173) -> Result<serde_json::Value, SQLError> {
174 let mode = match mode {
175 crate::plpgsql::PLpgSQLCompileMode::Validate => pg_query::PlpgsqlCompileMode::Validate,
176 crate::plpgsql::PLpgSQLCompileMode::Runtime => pg_query::PlpgsqlCompileMode::Runtime,
177 };
178 finish(pg_query::parse_plpgsql_with_mode(
179 sql,
180 catalog,
181 settings().options(pg_query::ParseMode::Default),
182 mode,
183 ))
184}
185
186pub(crate) fn scan(sql: &str) -> Result<pg_query::protobuf::ScanResult, SQLError> {
188 let mut options = settings().options(pg_query::ParseMode::Default);
189 options.escape_string_warning = false;
190 pg_query::scan_with_options(sql, options).map_err(Into::into)
191}
192
193#[cfg(test)]
194mod tests;