pgdog_plugin/
ast.rs

1//! Wrapper around `pg_query` protobuf-generated statement.
2//!
3//! This is passed through FFI safely by ensuring two conditions:
4//!
5//! 1. The version of the **Rust compiler** used to build the plugin is the same used to build PgDog
6//! 2. The version of the **`pg_query` library** used by the plugin is the same used by PgDog
7//!
8use std::{ffi::c_void, ops::Deref};
9
10use pg_query::protobuf::{ParseResult, RawStmt};
11
12use crate::bindings::PdStatement;
13
14impl PdStatement {
15    /// Create FFI binding from `pg_query` output.
16    ///
17    /// # Safety
18    ///
19    /// The reference must live for the entire time
20    /// this struct is used. This is _not_ checked by the compiler,
21    /// and is the responsibility of the caller.
22    ///
23    pub unsafe fn from_proto(value: &ParseResult) -> Self {
24        Self {
25            data: value.stmts.as_ptr() as *mut c_void,
26            version: value.version,
27            len: value.stmts.len() as u64,
28        }
29    }
30}
31
32/// Wrapper around [`pg_query::protobuf::ParseResult`], which allows
33/// the caller to use `pg_query` types and methods to inspect the statement.
34#[derive(Debug)]
35pub struct PdParseResult {
36    parse_result: Option<ParseResult>,
37    borrowed: bool,
38}
39
40impl Clone for PdParseResult {
41    /// Cloning the binding is safe. A new structure
42    /// will be created without any references to the original
43    /// reference.
44    fn clone(&self) -> Self {
45        Self {
46            parse_result: self.parse_result.clone(),
47            borrowed: false,
48        }
49    }
50}
51
52impl From<PdStatement> for PdParseResult {
53    /// Create the binding from a FFI-passed reference.
54    ///
55    /// SAFETY: Memory is owned by the caller.
56    ///
57    fn from(value: PdStatement) -> Self {
58        Self {
59            parse_result: Some(ParseResult {
60                version: value.version,
61                stmts: unsafe {
62                    Vec::from_raw_parts(
63                        value.data as *mut RawStmt,
64                        value.len as usize,
65                        value.len as usize,
66                    )
67                },
68            }),
69            borrowed: true,
70        }
71    }
72}
73
74impl Drop for PdParseResult {
75    /// Drop the binding and forget the memory if the binding
76    /// is using the referenced struct. Otherwise, deallocate as normal.
77    fn drop(&mut self) {
78        if self.borrowed {
79            let parse_result = self.parse_result.take();
80            std::mem::forget(parse_result.unwrap().stmts);
81        }
82    }
83}
84
85impl Deref for PdParseResult {
86    type Target = ParseResult;
87
88    fn deref(&self) -> &Self::Target {
89        self.parse_result.as_ref().unwrap()
90    }
91}
92
93impl PdStatement {
94    /// Get the protobuf-wrapped PostgreSQL statement. The returned structure, [`PdParseResult`],
95    /// implements [`Deref`] to [`pg_query::protobuf::ParseResult`] and can be used to parse the
96    /// statement.
97    pub fn protobuf(&self) -> PdParseResult {
98        PdParseResult::from(*self)
99    }
100}
101
102#[cfg(test)]
103mod test {
104    use pg_query::NodeEnum;
105
106    use super::*;
107
108    #[test]
109    fn test_ast() {
110        let ast = pg_query::parse("SELECT * FROM users WHERE id = $1").unwrap();
111        let ffi = unsafe { PdStatement::from_proto(&ast.protobuf) };
112        match ffi
113            .protobuf()
114            .stmts
115            .first()
116            .unwrap()
117            .stmt
118            .as_ref()
119            .unwrap()
120            .node
121            .as_ref()
122            .unwrap()
123        {
124            NodeEnum::SelectStmt(_) => (),
125            _ => {
126                panic!("not a select")
127            }
128        };
129
130        let _ = ffi.protobuf().clone();
131    }
132}