Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ fn main() {
bindgen::Abi::CUnwind,
"wrapped_raw_expression_tree_walker_impl",
)
.allowlist_item("StringInfo")
.allowlist_item("wrapped_raw_deparse")
.wrap_static_fns(true)
.wrap_static_fns_path(out_dir.join("wrap_static_fns"));
for struct_name in &node_structs {
Expand Down
64 changes: 64 additions & 0 deletions src/deparse.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use crate::mem::MemoryContext;
use crate::pg_error::PgError;
use crate::{Result, nodes, raw};
use std::ptr::{self, NonNull};

pub struct DeparseResult {
_mem: MemoryContext,
string_info: raw::StringInfo,
}

impl DeparseResult {
pub fn deparse(stmt: &nodes::RawStmt) -> Result<Self> {
let mem = MemoryContext::new(c"pg_raw_deparse");
let mut err = ptr::null_mut();
// SAFETY: We never panic
let string_info = unsafe {
mem.within(|| raw::wrapped_raw_deparse((&raw const *stmt).cast_mut(), &mut err))
};

match NonNull::new(err) {
Some(err) => Err(PgError::new(mem, err).into()),
None => Ok(Self {
_mem: mem,
string_info,
}),
}
}

pub fn as_str(&self) -> &str {
// SAFETY: We're always allocating a valid pointer
let info = unsafe { &*self.string_info };
// SAFETY: We're always allocating a valid pointer
let bytes =
unsafe { std::slice::from_raw_parts(info.data.cast_const().cast(), info.len as usize) };
std::str::from_utf8(bytes).expect("PG always returns valid UTF-8")
}
}

pub fn deparse(stmt: &nodes::RawStmt) -> Result<DeparseResult> {
DeparseResult::deparse(stmt)
}

#[test]
fn test_deparse() {
fn run_test(query: &str) {
let result = crate::parse(query).unwrap();
let stmt = result.raw_stmts().first().unwrap();
let deparsed = deparse(stmt).unwrap();

assert_eq!(query, deparsed.as_str());
}

run_test("SELECT 1");
run_test("SELECT id, name, email FROM users WHERE users.id = 1");
run_test(
"UPDATE users SET name = 'Sage', email = 'sage@pgdog.dev' WHERE users.id = 1 AND NOT EXISTS (SELECT 1 FROM users WHERE email = 'sage@pgdog.dev')",
);
run_test(
"INSERT INTO users (name, email) VALUES ('Sage', 'sage@pgdog.dev') ON CONFLICT DO NOTHING",
);
run_test("DELETE FROM users WHERE id = 1");
run_test("SET my_config TO 1");
run_test("TRUNCATE users");
}
16 changes: 9 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
use std::{ffi, fmt, ptr};

pub mod const_val;
mod deparse;
pub mod error;
pub mod list;
mod mem;
Expand All @@ -13,6 +14,7 @@ pub mod raw;
pub mod walk;

pub use crate::const_val::ConstValue;
pub use crate::deparse::{DeparseResult, deparse};
pub use crate::error::{Error, Result};
pub use crate::node_enum::Node;

Expand Down Expand Up @@ -53,18 +55,18 @@ unsafe impl Send for ParseResult {}
unsafe impl Sync for ParseResult {}

impl ParseResult {
/// Returns the list of statements received, panics if the list was a
/// type other than Node
/// Returns the statements that were parsed
pub fn stmts(&self) -> impl Iterator<Item = Node<'_>> {
self.raw_stmts().into_iter().map(|s| s.stmt())
}

/// Returns the raw statements that were parsed
pub fn raw_stmts(&self) -> &list::CastNodeList<&nodes::RawStmt> {
// SAFETY: The memory context of the tree is guaranteed to outlive
// the lifetime of self. We are returning a lifetime shorter than self.
unsafe { Node::from_ptr(self.tree.tree.cast()) }
.expect_node_list()
.into_iter()
.map(|n| match n {
Node::RawStmt(stmt) => stmt.stmt(),
n => n,
})
.cast()
}
}

Expand Down
35 changes: 35 additions & 0 deletions src/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ impl NodeList {
pub fn is_empty(&self) -> bool {
self.len() == 0
}

#[inline]
pub fn first(&self) -> Option<<&Self as IntoIterator>::Item> {
self.into_iter().next()
}
}

impl<'a> IntoIterator for &'a NodeList {
Expand Down Expand Up @@ -191,6 +196,36 @@ pub struct CastNodeList<T> {
_marker: PhantomData<T>,
}

impl<T> CastNodeList<T> {
#[inline]
pub fn iter<'a>(&'a self) -> <&'a Self as IntoIterator>::IntoIter
where
&'a Self: IntoIterator,
{
self.into_iter()
}

#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.list.length as usize
}

#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.list.len() == 0
}

#[inline]
pub fn first<'a>(&'a self) -> Option<<&'a Self as IntoIterator>::Item>
where
&'a Self: IntoIterator,
{
self.into_iter().next()
}
}

#[allow(clippy::unnecessary_operation, clippy::identity_op)]
const _: () = {
use crate::nodes::String;
Expand Down
6 changes: 6 additions & 0 deletions src/pg_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ unsafe impl Send for PgError {}
unsafe impl Sync for PgError {}

impl PgError {
pub(crate) fn new(mem: MemoryContext, error_data: NonNull<raw::ErrorData>) -> Self {
Self {
_mem: mem,
error_data: ErrorData(error_data),
}
}
/// Returns None if the raw error is NULL
pub(crate) fn from_raw(raw: raw::Error) -> Option<Self> {
NonNull::new(raw.error_data).map(|error_data| {
Expand Down
12 changes: 12 additions & 0 deletions wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,15 @@ static inline bool wrapped_raw_expression_tree_walker_impl(Node *n, tree_walker_
PG_END_TRY();
return result;
}

static inline StringInfo wrapped_raw_deparse(RawStmt *stmt, ErrorData **error) {
StringInfo str;
PG_TRY();
str = makeStringInfo();
deparseRawStmt(str, stmt);
PG_CATCH();
*error = CopyErrorData();
FlushErrorState();
PG_END_TRY();
return str;
}