Skip to main content

pg_query/
query.rs

1use std::ffi::{CStr, CString};
2use std::os::raw::c_char;
3
4use prost::Message;
5
6use crate::bindings::*;
7use crate::error::*;
8use crate::parse_result::ParseResult;
9use crate::protobuf;
10
11/// Represents the resulting fingerprint containing both the raw integer form as well as the
12/// corresponding 16 character hex value.
13pub struct Fingerprint {
14    pub value: u64,
15    pub hex: String,
16}
17
18/// PostgreSQL raw-parser entry mode.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20#[repr(i32)]
21pub enum ParseMode {
22    Default = 0,
23    TypeName = 1,
24    PlPgSqlExpr = 2,
25    PlPgSqlAssign1 = 3,
26    PlPgSqlAssign2 = 4,
27    PlPgSqlAssign3 = 5,
28}
29
30/// Parses the given SQL statement into the given abstract syntax tree.
31///
32/// # Example
33///
34/// ```rust
35/// use pg_query::{Node, NodeEnum, NodeRef};
36///
37/// let result = pg_query::parse("SELECT * FROM contacts");
38/// assert!(result.is_ok());
39/// let result = result.unwrap();
40/// assert_eq!(result.tables(), vec!["contacts"]);
41/// assert!(matches!(result.protobuf.nodes()[0].0, NodeRef::SelectStmt(_)));
42/// ```
43pub fn parse(statement: &str) -> Result<ParseResult> {
44    parse_with_mode(statement, ParseMode::Default)
45}
46
47/// Parses input using one of PostgreSQL's raw-parser modes.
48///
49/// PL/pgSQL consumers should use the mode recorded on `PLpgSQL_expr`
50/// instead of rewriting an expression or assignment into another SQL form.
51pub fn parse_with_mode(statement: &str, mode: ParseMode) -> Result<ParseResult> {
52    let input = CString::new(statement)?;
53    let result = unsafe { pg_query_parse_protobuf_opts(input.as_ptr(), mode as i32) };
54    let parse_result = if !result.error.is_null() {
55        let message = unsafe { CStr::from_ptr((*result.error).message) }.to_string_lossy().to_string();
56        Err(Error::Parse(message))
57    } else {
58        let data = unsafe { std::slice::from_raw_parts(result.parse_tree.data as *const u8, result.parse_tree.len as usize) };
59        let stderr = unsafe { CStr::from_ptr(result.stderr_buffer) }.to_string_lossy().to_string();
60        protobuf::ParseResult::decode(data).map_err(Error::Decode).map(|result| ParseResult::new(result, stderr))
61    };
62    unsafe { pg_query_free_protobuf_parse_result(result) };
63    parse_result
64}
65
66/// Converts a parsed tree back into a string.
67///
68/// # Example
69///
70/// ```rust
71/// use pg_query::{Node, NodeEnum, NodeRef};
72///
73/// let result = pg_query::parse("INSERT INTO other (name) SELECT name FROM contacts");
74/// let result = result.unwrap();
75/// let insert = result.protobuf.nodes()[0].0;
76/// let select = result.protobuf.nodes()[1].0;
77/// assert!(matches!(insert, NodeRef::InsertStmt(_)));
78/// assert!(matches!(select, NodeRef::SelectStmt(_)));
79///
80/// // The entire parse result can be deparsed:
81/// assert_eq!(result.deparse().unwrap(), "INSERT INTO other (name) SELECT name FROM contacts");
82/// // Or an individual node can be deparsed:
83/// assert_eq!(insert.deparse().unwrap(), "INSERT INTO other (name) SELECT name FROM contacts");
84/// assert_eq!(select.deparse().unwrap(), "SELECT name FROM contacts");
85/// ```
86///
87/// Note that this function will panic if called on a node not defined in `deparseStmt`
88pub fn deparse(protobuf: &protobuf::ParseResult) -> Result<String> {
89    let buffer = protobuf.encode_to_vec();
90    let len = buffer.len();
91    let data = buffer.as_ptr() as *const c_char as *mut c_char;
92    let protobuf = PgQueryProtobuf { data, len };
93    let result = unsafe { pg_query_deparse_protobuf(protobuf) };
94
95    let deparse_result = if !result.error.is_null() {
96        let message = unsafe { CStr::from_ptr((*result.error).message) }.to_string_lossy().to_string();
97        Err(Error::Parse(message))
98    } else {
99        let query = unsafe { CStr::from_ptr(result.query) }.to_string_lossy().to_string();
100        Ok(query)
101    };
102
103    unsafe { pg_query_free_deparse_result(result) };
104    deparse_result
105}
106
107/// Normalizes the given SQL statement, returning a parametized version.
108///
109/// # Example
110///
111/// ```rust
112/// let result = pg_query::normalize("SELECT * FROM contacts WHERE name='Paul'");
113/// assert!(result.is_ok());
114/// let result = result.unwrap();
115/// assert_eq!(result, "SELECT * FROM contacts WHERE name=$1");
116/// ```
117pub fn normalize(statement: &str) -> Result<String> {
118    let input = CString::new(statement)?;
119    let result = unsafe { pg_query_normalize(input.as_ptr()) };
120    let normalized_query = if !result.error.is_null() {
121        let message = unsafe { CStr::from_ptr((*result.error).message) }.to_string_lossy().to_string();
122        Err(Error::Parse(message))
123    } else {
124        let n = unsafe { CStr::from_ptr(result.normalized_query) };
125        Ok(n.to_string_lossy().to_string())
126    };
127    unsafe { pg_query_free_normalize_result(result) };
128    normalized_query
129}
130
131/// Fingerprints the given SQL statement. Useful for comparing parse trees across different implementations
132/// of `libpg_query`.
133///
134/// # Example
135///
136/// ```rust
137/// let result = pg_query::fingerprint("SELECT * FROM contacts WHERE name='Paul'");
138/// assert!(result.is_ok());
139/// let result = result.unwrap();
140/// assert_eq!(result.hex, "0e2581a461ece536");
141/// ```
142pub fn fingerprint(statement: &str) -> Result<Fingerprint> {
143    let input = CString::new(statement)?;
144    let result = unsafe { pg_query_fingerprint(input.as_ptr()) };
145    let fingerprint = if !result.error.is_null() {
146        let message = unsafe { CStr::from_ptr((*result.error).message) }.to_string_lossy().to_string();
147        Err(Error::Parse(message))
148    } else {
149        let hex = unsafe { CStr::from_ptr(result.fingerprint_str) };
150        Ok(Fingerprint { value: result.fingerprint, hex: hex.to_string_lossy().to_string() })
151    };
152    unsafe { pg_query_free_fingerprint_result(result) };
153    fingerprint
154}
155
156/// An experimental API which parses a PLPGSQL function. This currently returns the raw JSON structure.
157///
158/// # Example
159///
160/// ```rust
161/// let result = pg_query::parse_plpgsql("
162///     CREATE OR REPLACE FUNCTION cs_fmt_browser_version(v_name varchar, v_version varchar)
163///     RETURNS varchar AS $$
164///     BEGIN
165///         IF v_version IS NULL THEN
166///             RETURN v_name;
167///         END IF;
168///         RETURN v_name || '/' || v_version;
169///     END;
170///     $$ LANGUAGE plpgsql;
171/// ");
172/// assert!(result.is_ok());
173/// ```
174pub fn parse_plpgsql(stmt: &str) -> Result<serde_json::Value> {
175    let input = CString::new(stmt)?;
176    let result = unsafe { pg_query_parse_plpgsql(input.as_ptr()) };
177    let structure = if !result.error.is_null() {
178        let message = unsafe { CStr::from_ptr((*result.error).message) }.to_string_lossy().to_string();
179        Err(Error::Parse(message))
180    } else {
181        let raw = unsafe { CStr::from_ptr(result.plpgsql_funcs) };
182        serde_json::from_str(&raw.to_string_lossy()).map_err(|e| Error::InvalidJson(e.to_string()))
183    };
184    unsafe { pg_query_free_plpgsql_parse_result(result) };
185    structure
186}
187
188/// Split a well-formed query into separate statements.
189///
190/// # Example
191///
192/// ```rust
193/// let query = r#"select /*;*/ 1; select "2;", (select 3);"#;
194/// let statements = pg_query::split_with_parser(query).unwrap();
195/// assert_eq!(statements, vec!["select /*;*/ 1", r#"select "2;", (select 3)"#]);
196/// ```
197///
198/// However, `split_with_parser` will fail on malformed statements
199///
200/// ```rust
201/// let query = "select 1; this statement is not sql; select 2;";
202/// let result = pg_query::split_with_parser(query);
203/// let err = r#"syntax error at or near "this""#;
204/// assert_eq!(result, Err(pg_query::Error::Split(err.to_string())));
205/// ```
206pub fn split_with_parser(query: &str) -> Result<Vec<&str>> {
207    let input = CString::new(query)?;
208    let result = unsafe { pg_query_split_with_parser(input.as_ptr()) };
209    let split_result = if !result.error.is_null() {
210        let message = unsafe { CStr::from_ptr((*result.error).message) }.to_string_lossy().to_string();
211        Err(Error::Split(message))
212    } else {
213        let n_stmts = result.n_stmts as usize;
214        let mut statements = Vec::with_capacity(n_stmts);
215        for offset in 0..n_stmts {
216            let split_stmt = unsafe { *result.stmts.add(offset).read() };
217            let start = split_stmt.stmt_location as usize;
218            let end = start + split_stmt.stmt_len as usize;
219            statements.push(&query[start..end]);
220            // not sure the start..end slice'll hold up for non-utf8 charsets
221        }
222        Ok(statements)
223    };
224    unsafe { pg_query_free_split_result(result) };
225    split_result
226}
227
228/// Scan a sql query into a its component of tokens.
229///
230/// # Example
231///
232/// ```rust
233/// use pg_query::protobuf::*;
234/// let sql = "SELECT update AS left /* comment */ FROM between";
235/// let result = pg_query::scan(sql).unwrap();
236/// let tokens: Vec<std::string::String> = result.tokens.iter().map(|token| {
237///     format!("{:?}", token)
238/// }).collect();
239/// assert_eq!(
240///     tokens,
241///     vec![
242///         "ScanToken { start: 0, end: 6, token: Select, keyword_kind: ReservedKeyword }",
243///         "ScanToken { start: 7, end: 13, token: Update, keyword_kind: UnreservedKeyword }",
244///         "ScanToken { start: 14, end: 16, token: As, keyword_kind: ReservedKeyword }",
245///         "ScanToken { start: 17, end: 21, token: Left, keyword_kind: TypeFuncNameKeyword }",
246///         "ScanToken { start: 22, end: 35, token: CComment, keyword_kind: NoKeyword }",
247///         "ScanToken { start: 36, end: 40, token: From, keyword_kind: ReservedKeyword }",
248///         "ScanToken { start: 41, end: 48, token: Between, keyword_kind: ColNameKeyword }"
249///     ]);
250/// ```
251pub fn scan(sql: &str) -> Result<protobuf::ScanResult> {
252    let input = CString::new(sql)?;
253    let result = unsafe { pg_query_scan(input.as_ptr()) };
254    let scan_result = if !result.error.is_null() {
255        let message = unsafe { CStr::from_ptr((*result.error).message) }.to_string_lossy().to_string();
256        Err(Error::Scan(message))
257    } else {
258        let data = unsafe { std::slice::from_raw_parts(result.pbuf.data as *const u8, result.pbuf.len as usize) };
259        protobuf::ScanResult::decode(data).map_err(Error::Decode)
260    };
261    unsafe { pg_query_free_scan_result(result) };
262    scan_result
263}
264
265/// Split a potentially-malformed query into separate statements. Note that
266/// invalid tokens will be skipped
267/// ```rust
268/// let query = r#"select /*;*/ 1; asdf; select "2;", (select 3); asdf"#;
269/// let statements = pg_query::split_with_scanner(query).unwrap();
270/// assert_eq!(statements, vec![
271///     "select /*;*/ 1",
272///     // skipped " asdf" since it was an invalid token
273///     r#" select "2;", (select 3)"#,
274/// ]);
275/// ```
276pub fn split_with_scanner(query: &str) -> Result<Vec<&str>> {
277    let input = CString::new(query)?;
278    let result = unsafe { pg_query_split_with_scanner(input.as_ptr()) };
279    let split_result = if !result.error.is_null() {
280        let message = unsafe { CStr::from_ptr((*result.error).message) }.to_string_lossy().to_string();
281        Err(Error::Split(message))
282    } else {
283        // don't use result.stderr_buffer since it appears unused unless
284        // libpg_query is compiled with DEBUG defined.
285        let n_stmts = result.n_stmts as usize;
286        let mut start: usize;
287        let mut end: usize;
288        let mut statements = Vec::with_capacity(n_stmts);
289        for offset in 0..n_stmts {
290            let split_stmt = unsafe { *result.stmts.add(offset).read() };
291            start = split_stmt.stmt_location as usize;
292            // TODO: consider comparing the new value of start to the old value
293            // of end to see if any region larger than a statement-separator got skipped
294            end = start + split_stmt.stmt_len as usize;
295            statements.push(&query[start..end]);
296        }
297        Ok(statements)
298    };
299    unsafe { pg_query_free_split_result(result) };
300    split_result
301}