Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trait TypmodInOutFuncs for input(input, oid, typmod) function #1878

Draft
wants to merge 5 commits into
base: develop
Choose a base branch
from
Draft
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
37 changes: 37 additions & 0 deletions pgrx-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -769,13 +769,15 @@ enum Animal {
Optionally accepts the following attributes:

* `inoutfuncs(some_in_fn, some_out_fn)`: Define custom in/out functions for the type.
* `typmod_inoutfuncs(some_in_fn, some_out_fn, some_typmod_in_fn)`: Define custom in/out functions for the type with typmod.
* `pgvarlena_inoutfuncs(some_in_fn, some_out_fn)`: Define custom in/out functions for the `PgVarlena` of this type.
* `sql`: Same arguments as [`#[pgrx(sql = ..)]`](macro@pgrx).
*/
#[proc_macro_derive(
PostgresType,
attributes(
inoutfuncs,
typmod_inoutfuncs,
pgvarlena_inoutfuncs,
bikeshed_postgres_type_manually_impl_from_into_datum,
requires,
Expand All @@ -793,6 +795,7 @@ fn impl_postgres_type(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream>
let generics = &ast.generics.clone();
let has_lifetimes = generics.lifetimes().next();
let funcname_in = Ident::new(&format!("{name}_in").to_lowercase(), name.span());
let funcname_typmod_in = Ident::new(&format!("{name}_typmod_in").to_lowercase(), name.span());
let funcname_out = Ident::new(&format!("{name}_out").to_lowercase(), name.span());
let mut args = parse_postgres_type_args(&ast.attrs);
let mut stream = proc_macro2::TokenStream::new();
Expand Down Expand Up @@ -963,6 +966,36 @@ fn impl_postgres_type(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream>
unsafe { buffer.leak_cstr().to_owned() }
}
});
} else if args.contains(&PostgresTypeAttribute::TypmodInOutFuncs) {
// otherwise if it's TypmodInOutFuncs our _in/_out functions use an owned type instance
stream.extend(quote! {
#[doc(hidden)]
#[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
pub fn #funcname_in #generics(input: Option<&::core::ffi::CStr>, oid: ::pgrx::pg_sys::Oid, typmod: i32) -> Option<#name #generics> {
input.map_or_else(|| {
for m in <#name as ::pgrx::inoutfuncs::TypmodInOutFuncs>::NULL_ERROR_MESSAGE {
::pgrx::pg_sys::error!("{m}");
}
None
}, |i| Some(<#name as ::pgrx::inoutfuncs::TypmodInOutFuncs>::input(i, oid, typmod)))
}

#[doc(hidden)]
#[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
pub fn #funcname_out #generics(input: #name #generics) -> ::pgrx::ffi::CString {
let mut buffer = ::pgrx::stringinfo::StringInfo::new();
::pgrx::inoutfuncs::TypmodInOutFuncs::output(&input, &mut buffer);
// SAFETY: We just constructed this StringInfo ourselves
unsafe { buffer.leak_cstr().to_owned() }
}

#[doc(hidden)]
#[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
pub fn #funcname_typmod_in #generics(input: Array<&::core::ffi::CStr>) -> i32 {
<#name as ::pgrx::inoutfuncs::TypmodInOutFuncs>::typmod_in(input)
}

});
} else if args.contains(&PostgresTypeAttribute::PgVarlenaInOutFuncs) {
// otherwise if it's PgVarlenaInOutFuncs our _in/_out functions use a PgVarlena
stream.extend(quote! {
Expand Down Expand Up @@ -1085,6 +1118,7 @@ fn impl_guc_enum(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
#[derive(Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
enum PostgresTypeAttribute {
InOutFuncs,
TypmodInOutFuncs,
PgVarlenaInOutFuncs,
Default,
ManualFromIntoDatum,
Expand All @@ -1100,6 +1134,9 @@ fn parse_postgres_type_args(attributes: &[Attribute]) -> HashSet<PostgresTypeAtt
"inoutfuncs" => {
categorized_attributes.insert(PostgresTypeAttribute::InOutFuncs);
}
"typmod_inoutfuncs" => {
categorized_attributes.insert(PostgresTypeAttribute::TypmodInOutFuncs);
}
"pgvarlena_inoutfuncs" => {
categorized_attributes.insert(PostgresTypeAttribute::PgVarlenaInOutFuncs);
}
Expand Down
8 changes: 6 additions & 2 deletions pgrx-sql-entity-graph/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,13 +232,15 @@ impl ToSql for SqlGraphEntity {
result
} else if context
.graph
.neighbors_undirected(*context.externs.get(item).unwrap())
.neighbors_undirected(context.graph_root/* *context.externs.get(item).unwrap()*/)
.any(|neighbor| {
let SqlGraphEntity::Type(PostgresTypeEntity {
in_fn,
in_fn_module_path,
out_fn,
out_fn_module_path,
typmod_in_fn,
typmod_in_fn_module_path,
..
}) = &context.graph[neighbor]
else {
Expand All @@ -249,7 +251,9 @@ 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_typmod_in_fn = item.full_path.starts_with(typmod_in_fn_module_path)
&& item.full_path.ends_with(typmod_in_fn);
is_in_fn || is_out_fn || is_typmod_in_fn
})
{
Ok(String::default())
Expand Down
34 changes: 33 additions & 1 deletion pgrx-sql-entity-graph/src/postgres_type/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ pub struct PostgresTypeEntity {
pub in_fn_module_path: String,
pub out_fn: &'static str,
pub out_fn_module_path: String,
pub typmod_in_fn: &'static str,
pub typmod_in_fn_module_path: String,
pub to_sql_config: ToSqlConfigEntity,
}

Expand Down Expand Up @@ -82,6 +84,8 @@ impl ToSql for PostgresTypeEntity {
out_fn,
out_fn_module_path,
in_fn,
typmod_in_fn,
typmod_in_fn_module_path,
..
}) = item_node
else {
Expand Down Expand Up @@ -146,6 +150,32 @@ 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 typmod_in_fn_module_path = if !typmod_in_fn_module_path.is_empty() {
typmod_in_fn_module_path.clone()
} else {
module_path.to_string() // Presume a local
};
let typmod_in_fn_path = format!(
"{typmod_in_fn_module_path}{maybe_colons}{typmod_in_fn}",
maybe_colons = if !typmod_in_fn_module_path.is_empty() { "::" } else { "" }
);
let (_, _index) = context
.externs
.iter()
.find(|(k, _v)| k.full_path == typmod_in_fn_path)
.ok_or_else(|| eyre::eyre!("Did not find `typmod_in_fn: {}`.", typmod_in_fn_path))?;
let (typmod_in_fn_graph_index, typmod_in_fn_entity) = context
.graph
.neighbors_undirected(context.graph_root)
.find_map(|neighbor| match &context.graph[neighbor] {
SqlGraphEntity::Function(func) if func.full_path == typmod_in_fn_path => {
Some((neighbor, func))
}
_ => None,
})
.ok_or_else(|| eyre!("Could not find typmod_in_fn graph entity."))?;
let typmod_in_fn_sql = typmod_in_fn_entity.to_sql(context)?;

let shell_type = format!(
"\n\
-- {file}:{line}\n\
Expand All @@ -163,14 +193,16 @@ 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\
\tTYPMOD_IN = {schema_prefix_typmod_in_fn}{typmod_in_fn}, /* {typmod_in_fn_path} */\n\
\tSTORAGE = extended\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_typmod_in_fn = context.schema_prefix_for(&typmod_in_fn_graph_index),
};

Ok(shell_type + "\n" + &in_fn_sql + "\n" + &out_fn_sql + "\n" + &materialized_type)
Ok(shell_type + "\n" + &in_fn_sql + "\n" + &out_fn_sql + "\n" + &typmod_in_fn_sql + "\n" + &materialized_type)
}
}
20 changes: 18 additions & 2 deletions pgrx-sql-entity-graph/src/postgres_type/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ pub struct PostgresTypeDerive {
generics: Generics,
in_fn: Ident,
out_fn: Ident,
typmod_in_fn: Ident,
to_sql_config: ToSqlConfig,
}

Expand All @@ -63,12 +64,13 @@ impl PostgresTypeDerive {
generics: Generics,
in_fn: Ident,
out_fn: Ident,
typmod_in_fn: Ident,
to_sql_config: ToSqlConfig,
) -> Result<CodeEnrichment<Self>, syn::Error> {
if !to_sql_config.overrides_default() {
crate::ident_is_acceptable_to_postgres(&name)?;
}
Ok(CodeEnrichment(Self { generics, name, in_fn, out_fn, to_sql_config }))
Ok(CodeEnrichment(Self { generics, name, in_fn, out_fn, typmod_in_fn, to_sql_config }))
}

pub fn from_derive_input(
Expand All @@ -90,11 +92,16 @@ impl PostgresTypeDerive {
&format!("{}_out", derive_input.ident).to_lowercase(),
derive_input.ident.span(),
);
let funcname_typmod_in = Ident::new(
&format!("{}_typmod_in", derive_input.ident).to_lowercase(),
derive_input.ident.span(),
);
Self::new(
derive_input.ident,
derive_input.generics,
funcname_in,
funcname_out,
funcname_typmod_in,
to_sql_config,
)
}
Expand Down Expand Up @@ -124,6 +131,7 @@ impl ToEntityGraphTokens for PostgresTypeDerive {

let in_fn = &self.in_fn;
let out_fn = &self.out_fn;
let typmod_in_fn = &self.typmod_in_fn;

let sql_graph_entity_fn_name = format_ident!("__pgrx_internals_type_{}", self.name);

Expand Down Expand Up @@ -189,6 +197,13 @@ impl ToEntityGraphTokens for PostgresTypeDerive {
let _ = path_items.pop(); // Drop the one we don't want.
path_items.join("::")
},
typmod_in_fn: stringify!(#typmod_in_fn),
typmod_in_fn_module_path: {
let typmod_in_fn = stringify!(#typmod_in_fn);
let mut path_items: Vec<_> = typmod_in_fn.split("::").collect();
let _ = path_items.pop(); // Drop the one we don't want.
path_items.join("::")
},
to_sql_config: #to_sql_config,
};
::pgrx::pgrx_sql_entity_graph::SqlGraphEntity::Type(submission)
Expand All @@ -205,6 +220,7 @@ impl Parse for CodeEnrichment<PostgresTypeDerive> {
let to_sql_config = ToSqlConfig::from_attributes(attrs.as_slice())?.unwrap_or_default();
let in_fn = Ident::new(&format!("{}_in", ident).to_lowercase(), ident.span());
let out_fn = Ident::new(&format!("{}_out", ident).to_lowercase(), ident.span());
PostgresTypeDerive::new(ident, generics, in_fn, out_fn, to_sql_config)
let typmod_in_fn = Ident::new(&format!("{}_typmod_in", ident).to_lowercase(), ident.span());
PostgresTypeDerive::new(ident, generics, in_fn, out_fn, typmod_in_fn, to_sql_config)
}
}
25 changes: 25 additions & 0 deletions pgrx/src/inoutfuncs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ use crate::datum::PgVarlena;
use crate::*;
#[doc(hidden)]
pub use serde_json::{from_slice as json_from_slice, to_vec as json_to_vec};
use crate::pg_sys::Oid;
use core::ffi::CStr;

/// `#[derive(Copy, Clone, PostgresType)]` types need to implement this trait to provide the text
/// input/output functions for that type
Expand Down Expand Up @@ -49,6 +51,29 @@ pub trait InOutFuncs {
/// Convert `Self` into text by writing to the supplied `StringInfo` buffer
fn output(&self, buffer: &mut StringInfo);

/// If PostgreSQL calls the conversion function with NULL as an argument, what
/// error message should be generated?
const NULL_ERROR_MESSAGE: Option<&'static str> = None;
}

/// `#[derive(Serialize, Deserialize, PostgresType)]` types may implement this trait if they prefer
/// a textual representation that isn't JSON
/// Input function taking three arguments of types `cstring`, `oid`, `integer`.
pub trait TypmodInOutFuncs {
/// Given a string representation of `Self`, parse it into `Self`.
///
/// It is expected that malformed input will raise an `error!()` or `panic!()`
fn input(input: &core::ffi::CStr, oid: Oid, typmod: i32) -> Self
where
Self: Sized;

/// Convert `Self` into text by writing to the supplied `StringInfo` buffer
fn output(&self, buffer: &mut StringInfo);

/// The type_modifier_input_function is passed the declared modifier(s) in the form of a cstring array. It must check the values for validity (throwing an error if they are wrong), and if they are correct, return a single non-negative integer value that will be stored as the column “typmod”.
fn typmod_in(input: Array<&CStr>) -> i32 ;


/// If PostgreSQL calls the conversion function with NULL as an argument, what
/// error message should be generated?
const NULL_ERROR_MESSAGE: Option<&'static str> = None;
Expand Down
2 changes: 1 addition & 1 deletion pgrx/src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub use crate::datum::{
Numeric, PgVarlena, PostgresType, Range, RangeBound, RangeSubType, Time, TimeWithTimeZone,
Timestamp, TimestampWithTimeZone, VariadicArray,
};
pub use crate::inoutfuncs::{InOutFuncs, PgVarlenaInOutFuncs};
pub use crate::inoutfuncs::{InOutFuncs, TypmodInOutFuncs, PgVarlenaInOutFuncs};

// Trigger support
pub use crate::trigger_support::{
Expand Down
Loading