libpgquery_sys/
lib.rs

1#![allow(non_upper_case_globals)]
2#![allow(non_camel_case_types)]
3#![allow(non_snake_case)]
4#![allow(deref_nullptr)]
5
6include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
7include!(concat!(env!("OUT_DIR"), "/pg_query.rs"));
8
9// TODO: spin these wrappers out into a separate crate
10use prost::Message;
11use std::ffi::{CStr, CString, NulError};
12use thiserror::Error;
13
14#[derive(Debug, Error)]
15pub enum Error {
16    #[error("Invalid statement format: {0}")]
17    Conversion(#[from] NulError),
18    #[error("Error decoding result: {0}")]
19    Decode(#[from] prost::DecodeError),
20    #[error("Invalid statement: {0}")]
21    Parse(std::string::String),
22}
23
24/// Parses the given SQL statement into a JSON-formatted parse tree
25pub fn parseToJson(statement: &str) -> Result<std::string::String, Error> {
26    let input = CString::new(statement)?;
27    let result = unsafe { pg_query_parse(input.as_ptr()) };
28
29    let response = if !result.error.is_null() {
30        let message = unsafe { CStr::from_ptr((*result.error).message) }
31            .to_string_lossy()
32            .to_string();
33
34        Err(Error::Parse(message))
35    } else {
36        let parse_tree = unsafe { CStr::from_ptr(result.parse_tree) }
37            .to_string_lossy()
38            .to_string();
39
40        Ok(parse_tree)
41    };
42
43    unsafe { pg_query_free_parse_result(result) };
44
45    response
46}
47
48/// Parses the given SQL statement into a Protobuf-formatted parse tree
49pub fn parseToProtobuf(statement: &str) -> Result<ParseResult, Error> {
50    let input = CString::new(statement)?;
51    let result = unsafe { pg_query_parse_protobuf(input.as_ptr()) };
52
53    let response = if !result.error.is_null() {
54        let message = unsafe { CStr::from_ptr((*result.error).message) }
55            .to_string_lossy()
56            .to_string();
57
58        Err(Error::Parse(message))
59    } else {
60        let data = unsafe {
61            std::slice::from_raw_parts(
62                result.parse_tree.data as *const u8,
63                result.parse_tree.len as usize,
64            )
65        };
66
67        ParseResult::decode(data).map_err(Error::Decode)
68    };
69
70    unsafe { pg_query_free_protobuf_parse_result(result) };
71
72    response
73}
74
75/// wrapper around parseToProtobuf for ease-of-use
76pub fn parse(statement: &str) -> Result<ParseResult, Error> {
77    parseToProtobuf(statement)
78}
79
80#[cfg(test)]
81mod test {
82    use super::*;
83
84    // TODO:
85    // add libpg_query test suite
86    // add pg_query_go test suite
87
88    #[test]
89    fn parses_to_json() {
90        let json =
91            parseToJson("select * from items").expect("Error parsing valid statement into JSON");
92
93        let _map: serde_json::Map<std::string::String, serde_json::Value> =
94            serde_json::from_str(&json).expect("Error parsing response into JSON Object");
95    }
96
97    #[test]
98    fn parses_to_proto() {
99        let _ = parseToProtobuf("select null")
100            .expect("Error parsing statement 'select null' into Protobuf");
101
102        let _ = parseToProtobuf("select ''")
103            .expect("Error parsing statement 'select ''' into Protobuf");
104
105        let _ = parseToProtobuf("checkpoint")
106            .expect("Error parsing statement 'checkpoint' into Protobuf");
107
108        let _ = parseToProtobuf("select from items")
109            .expect("Error parsing statement 'select from items' into Protobuf");
110
111        let proto =
112            parseToProtobuf("select *").expect("Error parsing statement 'select *' into Protobuf");
113
114        assert_eq!(
115            proto.stmts.len(),
116            1,
117            "Failed to parse correct number of statements"
118        )
119    }
120}