Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
822f074
Added dbg
LucaCappelletti94 May 12, 2025
1c1ea18
Trying to add binary protocol derives
LucaCappelletti94 May 12, 2025
52c30ce
Trying to impl send/receive
LucaCappelletti94 May 12, 2025
83582c5
Trying to write the send/recv funcs
LucaCappelletti94 May 12, 2025
f4da133
Trying out a different approach
LucaCappelletti94 May 12, 2025
cf264d5
Trying another approach for send/receive
LucaCappelletti94 May 12, 2025
cd85573
Added full path to StringInfo
LucaCappelletti94 May 12, 2025
7cce005
Trying out another approach for receive
LucaCappelletti94 May 12, 2025
383b643
Maybe fixed typos
LucaCappelletti94 May 12, 2025
bebf375
Trying to fix codegen
LucaCappelletti94 May 12, 2025
0b5140a
Attempted to solve other issues
LucaCappelletti94 May 12, 2025
011f88e
Removed unnecessary option wrap
LucaCappelletti94 May 12, 2025
4e7fb85
Debugging
LucaCappelletti94 May 12, 2025
6f71884
Fixed other typos
LucaCappelletti94 May 12, 2025
07dcbf0
Tried to fix more generation errors
LucaCappelletti94 May 12, 2025
e33c6df
Tried to solve other issues
LucaCappelletti94 May 12, 2025
816169d
Specified type
LucaCappelletti94 May 12, 2025
2c933c1
Specified different type
LucaCappelletti94 May 12, 2025
6cef3f6
Trying different type
LucaCappelletti94 May 12, 2025
aaa6867
Added missing unsafe block
LucaCappelletti94 May 12, 2025
483e9e0
Testing different approach
LucaCappelletti94 May 12, 2025
cadbd0e
Testing different approach
LucaCappelletti94 May 12, 2025
b643f4d
Testing
LucaCappelletti94 May 12, 2025
7d85a10
Testing a different approach
LucaCappelletti94 May 13, 2025
8b3a117
Added missing use
LucaCappelletti94 May 13, 2025
84e027d
Trying to add the number of fields
LucaCappelletti94 May 13, 2025
2a9b74a
Made error more verbose
LucaCappelletti94 May 13, 2025
0f7443a
Fixed error in expect
LucaCappelletti94 May 13, 2025
d3c0e88
Attempting new approach based on discord discussion
LucaCappelletti94 May 14, 2025
bc974cf
Fixing let else syntax
LucaCappelletti94 May 14, 2025
ba1d028
Added full Oid path and fixed double unreachable
LucaCappelletti94 May 14, 2025
b857685
Attempting to skip 4 bytes for varlena
LucaCappelletti94 May 14, 2025
7a74459
Attempting again the `cbor`-based send/recv approach
LucaCappelletti94 May 16, 2025
e53e8e7
Formatted code
LucaCappelletti94 May 16, 2025
8df6026
Found where the `*_in` and `*_out` funcs are ignored and added `*_sen…
LucaCappelletti94 May 16, 2025
dabc642
Changed signature of `send` from `Internal` to `Vec<u8>`
LucaCappelletti94 May 16, 2025
f290caf
Moved location of `_send` and `_recv`
LucaCappelletti94 May 16, 2025
2d85c63
Fixed typo in generated code - missing semicolon
LucaCappelletti94 May 16, 2025
98baf8b
Fixed syntax relative to `unsafe` let Some else codeblock
LucaCappelletti94 May 16, 2025
238f1aa
Fixed error in calling `from_datum`
LucaCappelletti94 May 16, 2025
d90c25c
Testing what happens if body of functions is left out
LucaCappelletti94 May 16, 2025
1b1625d
Decommented part of the `recv` structure
LucaCappelletti94 May 16, 2025
42d4b46
Uncommented another portion of the `recv` function
LucaCappelletti94 May 16, 2025
7b53c38
Uncommented remainder of `recv` function
LucaCappelletti94 May 16, 2025
2a19e1e
Debugging - 1
LucaCappelletti94 May 16, 2025
382bce4
Moved todo used for debug - 2
LucaCappelletti94 May 16, 2025
d1d12a3
Still debugging cbor_decode
LucaCappelletti94 May 16, 2025
144cd9a
Deferenced varlena
LucaCappelletti94 May 16, 2025
74c9044
Debugging cbor decode
LucaCappelletti94 May 16, 2025
2445dbf
Added todo to debug buffer content
LucaCappelletti94 May 16, 2025
2e79a8f
Removed other todo
LucaCappelletti94 May 16, 2025
2f677ee
Commented code after todo
LucaCappelletti94 May 16, 2025
de12f11
Attempting to read the data slice
LucaCappelletti94 May 16, 2025
f387164
Continuing the debugging efforts
LucaCappelletti94 May 16, 2025
e2c8c78
Trying a different approach
LucaCappelletti94 May 16, 2025
41e0819
Re-exporting serde_cbor to use in macros
LucaCappelletti94 May 16, 2025
6e37f10
Now debugging retrieved object
LucaCappelletti94 May 16, 2025
b5a24a8
Experimented with different signature to support lifetimes
LucaCappelletti94 May 16, 2025
3495b65
Fixed error in receive
LucaCappelletti94 May 16, 2025
b0ba0cb
Restored to the approach with no support for lifetimes
LucaCappelletti94 May 16, 2025
ccee8dc
Attempting a different approach using transmute on buf data
LucaCappelletti94 May 16, 2025
03fdb8b
Debugging received data
LucaCappelletti94 May 16, 2025
11cc69b
Experimenting with using buf.data as *mut pg_sys::varlena
LucaCappelletti94 May 16, 2025
44a105a
Fixed path to varlena
LucaCappelletti94 May 16, 2025
46e70b3
Wrapped unsafe code
LucaCappelletti94 May 16, 2025
2fcbf08
Trying to circumnavigate cursor check
LucaCappelletti94 May 16, 2025
9cefb52
Decommented send
LucaCappelletti94 May 16, 2025
d1593e7
Trying a different approach
LucaCappelletti94 May 16, 2025
858eb31
Working on the self-contained varlena approach
LucaCappelletti94 May 16, 2025
231fa09
Fixed typing
LucaCappelletti94 May 16, 2025
511e131
Trying a different approach to create varlena using StringInfo
LucaCappelletti94 May 16, 2025
3262afc
Fixed relative pg_sys path
LucaCappelletti94 May 16, 2025
d6fa730
Added complete path to StringInfo
LucaCappelletti94 May 16, 2025
f72a410
Added missing from_raw_parts
LucaCappelletti94 May 16, 2025
b3973e8
Introduced #[pg_binary_protocol] to enable binary protocol derive
LucaCappelletti94 May 16, 2025
56e9aa1
Fixed errors in derive of PostgresType
LucaCappelletti94 May 16, 2025
362ba1e
Added missing attribute to list
LucaCappelletti94 May 16, 2025
c739ddb
Documented attribute
LucaCappelletti94 May 16, 2025
430e40d
Added missing curly braces
LucaCappelletti94 May 16, 2025
f4a2a91
Added missing `Some`
LucaCappelletti94 May 16, 2025
91e7ccd
Fixed when Default is enabled
LucaCappelletti94 May 16, 2025
00ec58c
Removed typo
LucaCappelletti94 May 16, 2025
2f3d5bf
Removed pointless comments
LucaCappelletti94 May 17, 2025
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
- **Easy Custom Types**
+ `#[derive(PostgresType)]` to use a Rust struct as a Postgres type
- By default, represented as a CBOR-encoded object in-memory/on-disk, and JSON as human-readable
- Supports `#[pg_binary_protocol]` to generate binary protocol send/recv functions
- Provide custom in-memory/on-disk/human-readable representations
+ `#[derive(PostgresEnum)]` to use a Rust enum as a Postgres enum
+ Composite types supported with the `pgrx::composite_type!("Sample")` macro
Expand Down
2 changes: 2 additions & 0 deletions articles/forging-sql-from-rust.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ These `sql` files must be generated from data within the Rust code. The SQL gene
- Create 'Shell types', in & out functions, and 'Base types' for each `#[derive(PostgresType)]` marked type.
```rust
#[derive(PostgresType, Serialize, Deserialize, Debug, Eq, PartialEq)]
#[pg_binary_protocol]
pub struct Animals {
names: Vec<String>,
age_lookup: HashMap<i32, String>,
Expand Down Expand Up @@ -140,6 +141,7 @@ These `sql` files must be generated from data within the Rust code. The SQL gene
Eq, PartialEq, Ord, Hash, PartialOrd,
PostgresType, Serialize, Deserialize
)]
#[pg_binary_protocol]
pub struct Thing(String);
```
```sql
Expand Down
71 changes: 66 additions & 5 deletions pgrx-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -698,7 +698,7 @@ fn impl_postgres_enum(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream>
stream.extend(quote! {
impl ::pgrx::datum::FromDatum for #enum_ident {
#[inline]
unsafe fn from_polymorphic_datum(datum: ::pgrx::pg_sys::Datum, is_null: bool, typeoid: ::pgrx::pg_sys::Oid) -> Option<#enum_ident> {
unsafe fn from_polymorphic_datum(datum: ::pgrx::pg_sys::Datum, is_null: bool, _typeoid: ::pgrx::pg_sys::Oid) -> Option<#enum_ident> {
if is_null {
None
} else {
Expand Down Expand Up @@ -781,6 +781,7 @@ Optionally accepts the following attributes:

* `inoutfuncs(some_in_fn, some_out_fn)`: Define custom in/out functions for the type.
* `pgvarlena_inoutfuncs(some_in_fn, some_out_fn)`: Define custom in/out functions for the `PgVarlena` of this type.
* `pg_binary_protocol`: Use the binary protocol for this type.
* `pgrx(alignment = "<align>")`: Derive Postgres alignment from Rust type. One of `"on"`, or `"off"`.
* `sql`: Same arguments as [`#[pgrx(sql = ..)]`](macro@pgrx).
*/
Expand All @@ -789,6 +790,7 @@ Optionally accepts the following attributes:
attributes(
inoutfuncs,
pgvarlena_inoutfuncs,
pg_binary_protocol,
bikeshed_postgres_type_manually_impl_from_into_datum,
requires,
pgrx
Expand All @@ -806,6 +808,9 @@ fn impl_postgres_type(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream>
let has_lifetimes = generics.lifetimes().next();
let funcname_in = Ident::new(&format!("{name}_in").to_lowercase(), name.span());
let funcname_out = Ident::new(&format!("{name}_out").to_lowercase(), name.span());
let funcname_recv = Ident::new(&format!("{name}_recv").to_lowercase(), name.span());
let funcname_send = Ident::new(&format!("{name}_send").to_lowercase(), name.span());

let mut args = parse_postgres_type_args(&ast.attrs);
let mut stream = proc_macro2::TokenStream::new();

Expand All @@ -825,7 +830,9 @@ fn impl_postgres_type(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream>
}
}

if args.is_empty() {
if !args.contains(&PostgresTypeAttribute::InOutFuncs)
&& !args.contains(&PostgresTypeAttribute::PgVarlenaInOutFuncs)
{
// assume the user wants us to implement the InOutFuncs
args.insert(PostgresTypeAttribute::Default);
}
Expand Down Expand Up @@ -937,14 +944,14 @@ fn impl_postgres_type(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream>
if args.contains(&PostgresTypeAttribute::Default) {
stream.extend(quote! {
#[doc(hidden)]
#[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
#[::pgrx::pgrx_macros::pg_extern(immutable, parallel_safe)]
pub fn #funcname_in #generics(input: Option<&#lifetime ::core::ffi::CStr>) -> Option<#name #generics> {
use ::pgrx::inoutfuncs::json_from_slice;
input.map(|cstr| json_from_slice(cstr.to_bytes()).ok()).flatten()
}

#[doc(hidden)]
#[::pgrx::pgrx_macros::pg_extern (immutable,parallel_safe)]
#[::pgrx::pgrx_macros::pg_extern (immutable, parallel_safe)]
pub fn #funcname_out #generics(input: #name #generics) -> ::pgrx::ffi::CString {
use ::pgrx::inoutfuncs::json_to_vec;
let mut bytes = json_to_vec(&input).unwrap();
Expand Down Expand Up @@ -1000,7 +1007,57 @@ fn impl_postgres_type(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream>
});
}

let sql_graph_entity_item = sql_gen::PostgresTypeDerive::from_derive_input(ast)?;
if args.contains(&PostgresTypeAttribute::PgBinaryProtocol) {
// At this time, the `PostgresTypeAttribute` does not impact the way we generate
// the `recv` and `send` functions.
stream.extend(quote! {
#[doc(hidden)]
#[::pgrx::pgrx_macros::pg_extern(immutable, strict, parallel_safe)]
pub fn #funcname_recv #generics(
internal: ::pgrx::datum::Internal,
) -> #name #generics {
let buf = unsafe { internal.get_mut::<::pgrx::pg_sys::StringInfoData>().unwrap() };

let mut serialized = ::pgrx::StringInfo::new();

serialized.push_bytes(&[0u8; ::pgrx::pg_sys::VARHDRSZ]); // reserve space for the header
serialized.push_bytes(unsafe {
core::slice::from_raw_parts(
buf.data as *const u8,
buf.len as usize
)
});

let size = serialized.len();
let varlena = serialized.into_char_ptr();

unsafe{
::pgrx::set_varsize_4b(varlena as *mut ::pgrx::pg_sys::varlena, size as i32);
buf.cursor = buf.len;
::pgrx::datum::cbor_decode(varlena as *mut ::pgrx::pg_sys::varlena)
}
}
#[doc(hidden)]
#[::pgrx::pgrx_macros::pg_extern(immutable, strict, parallel_safe)]
pub fn #funcname_send #generics(input: #name #generics) -> Vec<u8> {
use ::pgrx::datum::{FromDatum, IntoDatum};
let Some(datum): Option<::pgrx::pg_sys::Datum> = input.into_datum() else {
::pgrx::error!("Datum of type `{}` is unexpectedly NULL.", stringify!(#name));
};
unsafe {
let Some(serialized): Option<Vec<u8>> = FromDatum::from_datum(datum, false) else {
::pgrx::error!("Failed to CBOR-serialize Datum to type `{}`.", stringify!(#name));
};
serialized
}
}
});
}

let sql_graph_entity_item = sql_gen::PostgresTypeDerive::from_derive_input(
ast,
args.contains(&PostgresTypeAttribute::PgBinaryProtocol),
)?;
sql_graph_entity_item.to_tokens(&mut stream);

Ok(stream)
Expand Down Expand Up @@ -1097,6 +1154,7 @@ fn impl_guc_enum(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
#[derive(Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
enum PostgresTypeAttribute {
InOutFuncs,
PgBinaryProtocol,
PgVarlenaInOutFuncs,
Default,
ManualFromIntoDatum,
Expand All @@ -1112,6 +1170,9 @@ fn parse_postgres_type_args(attributes: &[Attribute]) -> HashSet<PostgresTypeAtt
"inoutfuncs" => {
categorized_attributes.insert(PostgresTypeAttribute::InOutFuncs);
}
"pg_binary_protocol" => {
categorized_attributes.insert(PostgresTypeAttribute::PgBinaryProtocol);
}
"pgvarlena_inoutfuncs" => {
categorized_attributes.insert(PostgresTypeAttribute::PgVarlenaInOutFuncs);
}
Expand Down
15 changes: 14 additions & 1 deletion pgrx-sql-entity-graph/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,10 @@ impl ToSql for SqlGraphEntity {
in_fn_module_path,
out_fn,
out_fn_module_path,
receive_fn,
receive_fn_module_path,
send_fn,
send_fn_module_path,
..
}) = &context.graph[neighbor]
else {
Expand All @@ -251,7 +255,16 @@ impl ToSql for SqlGraphEntity {
&& item.full_path.ends_with(in_fn);
let is_out_fn = item.full_path.starts_with(out_fn_module_path)
&& item.full_path.ends_with(out_fn);
is_in_fn || is_out_fn
let is_receive_fn =
receive_fn_module_path.as_ref().is_some_and(|receive_fn_module_path| {
item.full_path.starts_with(receive_fn_module_path)
}) && receive_fn
.is_some_and(|receive_fn| item.full_path.ends_with(receive_fn));
let is_send_fn =
send_fn_module_path.as_ref().is_some_and(|send_fn_module_path| {
item.full_path.starts_with(send_fn_module_path)
}) && send_fn.is_some_and(|send_fn| item.full_path.ends_with(send_fn));
is_in_fn || is_out_fn || is_receive_fn || is_send_fn
})
{
Ok(String::default())
Expand Down
119 changes: 117 additions & 2 deletions pgrx-sql-entity-graph/src/postgres_type/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ pub struct PostgresTypeEntity {
pub in_fn_module_path: String,
pub out_fn: &'static str,
pub out_fn_module_path: String,
pub receive_fn: Option<&'static str>,
pub receive_fn_module_path: Option<String>,
pub send_fn: Option<&'static str>,
pub send_fn_module_path: Option<String>,
pub to_sql_config: ToSqlConfigEntity,
pub alignment: Option<usize>,
}
Expand Down Expand Up @@ -152,6 +156,10 @@ impl ToSql for PostgresTypeEntity {
out_fn,
out_fn_module_path,
in_fn,
receive_fn,
receive_fn_module_path,
send_fn,
send_fn_module_path,
alignment,
..
}) = item_node
Expand Down Expand Up @@ -217,6 +225,80 @@ impl ToSql for PostgresTypeEntity {
.ok_or_else(|| eyre!("Could not find out_fn graph entity."))?;
let out_fn_sql = out_fn_entity.to_sql(context)?;

let receive_fn_graph_index_and_receive_fn_sql = receive_fn_module_path
.as_ref()
.zip(*receive_fn)
.map(|(receive_fn_module_path, receive_fn)| {
let receive_fn_module_path = if !receive_fn_module_path.is_empty() {
receive_fn_module_path.clone()
} else {
module_path.to_string() // Presume a local
};
let receive_fn_path = format!(
"{receive_fn_module_path}{maybe_colons}{receive_fn}",
maybe_colons = if !receive_fn_module_path.is_empty() { "::" } else { "" }
);

// Find the receive function in the context
let (_, _index) = context
.externs
.iter()
.find(|(k, _v)| k.full_path == receive_fn_path)
.ok_or_else(|| eyre::eyre!("Did not find `receive_fn`: {receive_fn_path}."))?;

let (receive_fn_graph_index, receive_fn_entity) = context
.graph
.neighbors_undirected(self_index)
.find_map(|neighbor| match &context.graph[neighbor] {
SqlGraphEntity::Function(func) if func.full_path == receive_fn_path => {
Some((neighbor, func))
}
_ => None,
})
.ok_or_else(|| eyre!("Could not find receive_fn graph entity."))?;
let receive_fn_sql = receive_fn_entity.to_sql(context)?;

Ok::<_, eyre::Report>((receive_fn_graph_index, receive_fn_sql, receive_fn_path))
})
.transpose()?;

let send_fn_graph_index_and_send_fn_sql = send_fn_module_path
.as_ref()
.zip(*send_fn)
.map(|(send_fn_module_path, send_fn)| {
let send_fn_module_path = if !send_fn_module_path.is_empty() {
send_fn_module_path.clone()
} else {
module_path.to_string() // Presume a local
};
let send_fn_path = format!(
"{send_fn_module_path}{maybe_colons}{send_fn}",
maybe_colons = if !send_fn_module_path.is_empty() { "::" } else { "" }
);

// Find the send function in the context
let (_, _index) = context
.externs
.iter()
.find(|(k, _v)| k.full_path == send_fn_path)
.ok_or_else(|| eyre::eyre!("Did not find `send_fn: {}`.", send_fn_path))?;

let (send_fn_graph_index, send_fn_entity) = context
.graph
.neighbors_undirected(self_index)
.find_map(|neighbor| match &context.graph[neighbor] {
SqlGraphEntity::Function(func) if func.full_path == send_fn_path => {
Some((neighbor, func))
}
_ => None,
})
.ok_or_else(|| eyre!("Could not find send_fn graph entity."))?;
let send_fn_sql = send_fn_entity.to_sql(context)?;

Ok::<_, eyre::Report>((send_fn_graph_index, send_fn_sql, send_fn_path))
})
.transpose()?;

let shell_type = format!(
"\n\
-- {file}:{line}\n\
Expand Down Expand Up @@ -244,6 +326,29 @@ impl ToSql for PostgresTypeEntity {
})
.unwrap_or_default();

let (receive_send_attributes, receive_send_sql) = receive_fn_graph_index_and_receive_fn_sql
.zip(send_fn_graph_index_and_send_fn_sql)
.map(|((receive_fn_graph_index, receive_fn_sql, receive_fn_path), (send_fn_graph_index, send_fn_sql, send_fn_path))| {
let receive_fn = receive_fn.unwrap();
let send_fn = send_fn.unwrap();
(
format! {
"\
\tRECEIVE = {schema_prefix_receive_fn}{receive_fn}, /* {receive_fn_path} */\n\
\tSEND = {schema_prefix_send_fn}{send_fn}, /* {send_fn_path} */\n\
",
schema_prefix_receive_fn = context.schema_prefix_for(&receive_fn_graph_index),
schema_prefix_send_fn = context.schema_prefix_for(&send_fn_graph_index),
},
format! {
"\n\
{receive_fn_sql}\n\
{send_fn_sql}\n\
"
}
)
}).unwrap_or_default();

let materialized_type = format! {
"\n\
-- {file}:{line}\n\
Expand All @@ -252,14 +357,24 @@ impl ToSql for PostgresTypeEntity {
\tINTERNALLENGTH = variable,\n\
\tINPUT = {schema_prefix_in_fn}{in_fn}, /* {in_fn_path} */\n\
\tOUTPUT = {schema_prefix_out_fn}{out_fn}, /* {out_fn_path} */\n\
{receive_send_attributes}\
\tSTORAGE = extended{alignment}\n\
);\
",
schema = context.schema_prefix_for(&self_index),
schema_prefix_in_fn = context.schema_prefix_for(&in_fn_graph_index),
schema_prefix_out_fn = context.schema_prefix_for(&out_fn_graph_index),
schema_prefix_out_fn = context.schema_prefix_for(&out_fn_graph_index)
};

Ok(shell_type + "\n" + &in_fn_sql + "\n" + &out_fn_sql + "\n" + &materialized_type)
let result = shell_type
+ "\n"
+ &in_fn_sql
+ "\n"
+ &out_fn_sql
+ &receive_send_sql
+ "\n"
+ &materialized_type;

Ok(result)
}
}
Loading
Loading